Merge pull request 'feature/sponsorblock' (#17) from feature/sponsorblock into experimental
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Reviewed-on: #17
This commit is contained in:
Hazel 2024-04-27 09:41:36 +00:00
commit a451a97e1c
34 changed files with 257 additions and 726 deletions

View File

@ -20,12 +20,16 @@
"Bandcamp", "Bandcamp",
"dotenv", "dotenv",
"encyclopaedia", "encyclopaedia",
"ENDC",
"levenshtein", "levenshtein",
"metallum", "metallum",
"musify", "musify",
"OKBLUE", "OKBLUE",
"pathvalidate",
"Referer", "Referer",
"sponsorblock",
"tracksort", "tracksort",
"translit",
"unmap", "unmap",
"youtube" "youtube"
] ]

View File

@ -1,66 +0,0 @@
DROP TABLE IF EXISTS artist;
CREATE TABLE artist (
id TEXT PRIMARY KEY NOT NULL,
name TEXT
);
DROP TABLE IF EXISTS artist_release_group;
CREATE TABLE artist_release_group (
artist_id TEXT NOT NULL,
release_group_id TEXT NOT NULL
);
DROP TABLE IF EXISTS artist_track;
CREATE TABLE artist_track (
artist_id TEXT NOT NULL,
track_id TEXT NOT NULL
);
DROP TABLE IF EXISTS release_group;
CREATE TABLE release_group (
id TEXT PRIMARY KEY NOT NULL,
albumartist TEXT,
albumsort INT,
musicbrainz_albumtype TEXT,
compilation TEXT,
album_artist_id TEXT
);
DROP TABLE IF EXISTS release_;
CREATE TABLE release_ (
id TEXT PRIMARY KEY NOT NULL,
release_group_id TEXT NOT NULL,
title TEXT,
copyright TEXT,
album_status TEXT,
language TEXT,
year TEXT,
date TEXT,
country TEXT,
barcode TEXT
);
DROP TABLE IF EXISTS track;
CREATE TABLE track (
id TEXT PRIMARY KEY NOT NULL,
downloaded BOOLEAN NOT NULL DEFAULT 0,
release_id TEXT NOT NULL,
track TEXT,
length INT,
tracknumber TEXT,
isrc TEXT,
genre TEXT,
lyrics TEXT,
path TEXT,
file TEXT,
url TEXT,
src TEXT
);
DROP TABLE IF EXISTS source;
CREATE TABLE source (
track_id TEXT NOT NULL,
src TEXT NOT NULL,
url TEXT NOT NULL,
valid BOOLEAN NOT NULL DEFAULT 1
);

View File

@ -1,73 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg version="1.0" width="1024" height="1024" viewBox="0 0 1024.000000 1024.000000"
version="1.0" preserveAspectRatio="xMidYMid meet" id="svg168" sodipodi:docname="02.svg"
width="1024.000000pt" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="1024.000000pt" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1024.000000 1024.000000"
preserveAspectRatio="xMidYMid meet"
id="svg168"
sodipodi:docname="02.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"> xmlns:svg="http://www.w3.org/2000/svg">
<defs <defs id="defs172" />
id="defs172" />
<sodipodi:namedview <rect x="0" y="0" width="100%" height="100%" rx="10%" fill="#f0f0f0" id="background" />
id="namedview170"
pagecolor="#ffffff" <g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)" fill="#000000" id="wireframe">
bordercolor="#000000"
borderopacity="0.25" <path
inkscape:showpageshadow="2" d="M4784 8535 c-695 -66 -1296 -270 -1819 -616 -369 -245 -627 -477 -843 -763 -304 -402 -461 -948 -479 -1666 -9 -352 13 -581 82 -850 40 -156 61 -215 117 -323 55 -105 114 -169 194 -208 61 -30 69 -32 148 -27 179 12 320 123 356 281 8 38 6 64 -15 154 -14 59 -32 140 -41 178 -8 39 -21 95 -29 125 -41 165 -50 270 -50 565 0 261 3 309 28 480 30 214 28 242 -24 293 -41 40 -146 68 -312 84 -70 6 -127 15 -127 20 0 15 102 293 139 378 79 183 209 386 348 546 129 147 379 360 588 501 124 83 234 147 242 139 3 -3 -21 -36 -54 -73 -178 -203 -321 -426 -411 -643 -110 -265 -152 -484 -153 -804 -1 -338 43 -569 166 -877 56 -138 108 -235 192 -357 83 -119 95 -148 137 -323 54 -224 163 -505 223 -574 50 -57 102 -69 147 -34 46 36 34 86 -63 252 -65 113 -88 182 -107 332 -17 133 -20 142 -164 445 -148 313 -197 440 -250 650 -42 169 -60 311 -60 480 0 575 268 1118 733 1488 260 206 635 354 1060 418 142 21 566 26 722 9 323 -36 644 -133 905 -273 180 -96 322 -205 481 -368 464 -478 615 -1159 402 -1809 -22 -66 -78 -191 -142 -315 -275 -536 -251 -481 -271 -620 -10 -69 -28 -177 -40 -240 -27 -146 -37 -342 -20 -394 15 -47 51 -64 87 -41 73 49 164 319 184 549 17 208 39 271 158 461 197 313 285 530 342 845 31 167 34 543 6 685 -82 408 -210 682 -470 1005 -47 58 -83 107 -81 109 1 2 21 -7 43 -20 22 -13 77 -46 123 -73 324 -190 683 -538 883 -856 91 -145 268 -561 247 -582 -4 -3 -60 -16 -125 -27 -175 -31 -300 -80 -364 -141 -29 -26 -29 -54 -2 -190 64 -330 65 -751 3 -1081 -8 -46 -32 -145 -51 -219 -42 -157 -47 -246 -19 -329 20 -58 68 -118 120 -151 106 -65 273 -77 372 -27 140 71 251 273 328 592 55 229 76 429 76 725 0 991 -288 1664 -949 2213 -577 481 -1339 795 -2151 887 -154 18 -537 21 -696 5z"
inkscape:pageopacity="0.0" id="path150" />
inkscape:pagecheckerboard="0" <path
inkscape:deskcolor="#d1d1d1" d="M5963 4946 c-158 -51 -243 -191 -243 -398 0 -160 41 -281 122 -359 55 -53 99 -71 178 -72 55 -2 76 3 132 31 119 58 236 210 254 329 14 95 -50 278 -130 370 -72 82 -220 129 -313 99z m376 -302 c58 -49 66 -147 14 -198 -34 -34 -74 -34 -113 2 -57 50 -60 140 -8 193 36 36 67 37 107 3z"
inkscape:document-units="pt" id="path152" />
showgrid="false" <path
inkscape:zoom="0.69140625" d="M4089 4943 c-49 -8 -133 -66 -166 -116 -43 -64 -53 -102 -60 -224 -5 -91 -3 -110 21 -186 32 -103 76 -171 140 -214 126 -86 260 -73 354 33 73 82 97 158 97 310 0 121 0 121 -39 198 -51 101 -114 158 -203 186 -63 19 -88 22 -144 13z m-91 -294 c84 -29 79 -157 -8 -219 -65 -46 -110 -3 -113 107 -2 74 8 97 48 113 28 12 37 12 73 -1z"
inkscape:cx="437.51412" id="path154" />
inkscape:cy="984.22599" <path
inkscape:window-width="1866" d="M2585 3875 c-183 -29 -311 -98 -360 -194 -44 -88 -42 -163 6 -190 35 -20 65 -10 156 53 107 73 131 84 220 103 158 32 281 14 698 -102 301 -84 366 -93 423 -60 65 39 64 70 -5 146 -45 49 -65 58 -272 116 -516 143 -650 163 -866 128z"
inkscape:window-height="1012" id="path156" />
inkscape:window-x="0" <path
inkscape:window-y="0" d="M7350 3874 c-174 -23 -417 -78 -635 -145 -71 -22 -172 -49 -223 -59 -52 -10 -96 -21 -99 -24 -3 -3 -9 -24 -13 -48 -11 -57 7 -91 60 -112 74 -29 165 -17 465 63 295 79 375 94 505 94 82 1 131 -4 171 -16 58 -18 151 -69 188 -104 12 -11 38 -29 57 -39 31 -17 37 -17 62 -4 36 21 72 76 72 112 0 70 -68 167 -148 211 -77 42 -192 68 -317 72 -60 2 -126 1 -145 -1z"
inkscape:window-maximized="1" id="path158" />
inkscape:current-layer="g166" /> <path
<g d="M5404 3765 c-207 -147 -263 -172 -364 -162 -77 8 -129 33 -235 111 -86 63 -129 85 -142 73 -13 -13 50 -118 103 -170 82 -83 160 -119 277 -125 150 -8 252 32 350 137 70 75 111 171 73 171 -8 0 -36 -16 -62 -35z"
transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)" id="path160" />
fill="#000000" <path
stroke="none" d="M3981 3144 c-266 -178 -442 -186 -926 -41 -302 91 -455 97 -612 23 -157 -75 -251 -214 -222 -330 21 -83 76 -225 110 -284 100 -170 378 -370 654 -471 376 -137 757 -167 1102 -86 278 66 504 187 689 370 108 107 176 197 239 316 25 49 51 91 56 95 16 9 31 -9 94 -111 196 -316 448 -516 810 -641 336 -117 740 -122 1125 -14 297 84 533 213 711 389 164 163 221 269 222 416 1 66 -4 90 -25 135 -78 168 -302 263 -558 237 -41 -4 -156 -30 -255 -57 -223 -62 -311 -79 -446 -87 -183 -10 -352 31 -554 135 l-98 50 -22 -24 c-40 -44 -49 -77 -30 -117 29 -63 136 -154 230 -198 114 -54 192 -70 367 -76 177 -7 282 9 503 72 280 81 392 93 508 54 106 -35 157 -84 157 -151 0 -51 -59 -145 -134 -215 -226 -211 -559 -347 -961 -393 -216 -24 -499 5 -699 72 -314 105 -535 288 -671 556 -42 84 -31 81 -206 56 -100 -14 -118 -14 -186 0 -41 9 -79 16 -84 16 -5 0 -22 -30 -39 -66 -112 -249 -373 -466 -681 -568 -355 -118 -819 -76 -1207 109 -284 136 -425 272 -474 458 -11 41 -10 52 3 75 33 60 129 94 259 95 83 0 151 -15 325 -68 353 -109 499 -125 706 -75 157 38 305 134 365 236 23 39 24 48 14 78 -13 41 -47 86 -63 86 -7 0 -50 -25 -96 -56z"
id="g166"> id="path162" />
<rect </g>
x="10"
y="10"
width="10239.509"
height="10229.297"
rx="1503.97427"
fill="#f0f0f0"
id="rect148"
style="stroke-width:10.1935" />
<path
d="M4784 8535 c-695 -66 -1296 -270 -1819 -616 -369 -245 -627 -477 -843 -763 -304 -402 -461 -948 -479 -1666 -9 -352 13 -581 82 -850 40 -156 61 -215 117 -323 55 -105 114 -169 194 -208 61 -30 69 -32 148 -27 179 12 320 123 356 281 8 38 6 64 -15 154 -14 59 -32 140 -41 178 -8 39 -21 95 -29 125 -41 165 -50 270 -50 565 0 261 3 309 28 480 30 214 28 242 -24 293 -41 40 -146 68 -312 84 -70 6 -127 15 -127 20 0 15 102 293 139 378 79 183 209 386 348 546 129 147 379 360 588 501 124 83 234 147 242 139 3 -3 -21 -36 -54 -73 -178 -203 -321 -426 -411 -643 -110 -265 -152 -484 -153 -804 -1 -338 43 -569 166 -877 56 -138 108 -235 192 -357 83 -119 95 -148 137 -323 54 -224 163 -505 223 -574 50 -57 102 -69 147 -34 46 36 34 86 -63 252 -65 113 -88 182 -107 332 -17 133 -20 142 -164 445 -148 313 -197 440 -250 650 -42 169 -60 311 -60 480 0 575 268 1118 733 1488 260 206 635 354 1060 418 142 21 566 26 722 9 323 -36 644 -133 905 -273 180 -96 322 -205 481 -368 464 -478 615 -1159 402 -1809 -22 -66 -78 -191 -142 -315 -275 -536 -251 -481 -271 -620 -10 -69 -28 -177 -40 -240 -27 -146 -37 -342 -20 -394 15 -47 51 -64 87 -41 73 49 164 319 184 549 17 208 39 271 158 461 197 313 285 530 342 845 31 167 34 543 6 685 -82 408 -210 682 -470 1005 -47 58 -83 107 -81 109 1 2 21 -7 43 -20 22 -13 77 -46 123 -73 324 -190 683 -538 883 -856 91 -145 268 -561 247 -582 -4 -3 -60 -16 -125 -27 -175 -31 -300 -80 -364 -141 -29 -26 -29 -54 -2 -190 64 -330 65 -751 3 -1081 -8 -46 -32 -145 -51 -219 -42 -157 -47 -246 -19 -329 20 -58 68 -118 120 -151 106 -65 273 -77 372 -27 140 71 251 273 328 592 55 229 76 429 76 725 0 991 -288 1664 -949 2213 -577 481 -1339 795 -2151 887 -154 18 -537 21 -696 5z"
id="path150" />
<path
d="M5963 4946 c-158 -51 -243 -191 -243 -398 0 -160 41 -281 122 -359 55 -53 99 -71 178 -72 55 -2 76 3 132 31 119 58 236 210 254 329 14 95 -50 278 -130 370 -72 82 -220 129 -313 99z m376 -302 c58 -49 66 -147 14 -198 -34 -34 -74 -34 -113 2 -57 50 -60 140 -8 193 36 36 67 37 107 3z"
id="path152" />
<path
d="M4089 4943 c-49 -8 -133 -66 -166 -116 -43 -64 -53 -102 -60 -224 -5 -91 -3 -110 21 -186 32 -103 76 -171 140 -214 126 -86 260 -73 354 33 73 82 97 158 97 310 0 121 0 121 -39 198 -51 101 -114 158 -203 186 -63 19 -88 22 -144 13z m-91 -294 c84 -29 79 -157 -8 -219 -65 -46 -110 -3 -113 107 -2 74 8 97 48 113 28 12 37 12 73 -1z"
id="path154" />
<path
d="M2585 3875 c-183 -29 -311 -98 -360 -194 -44 -88 -42 -163 6 -190 35 -20 65 -10 156 53 107 73 131 84 220 103 158 32 281 14 698 -102 301 -84 366 -93 423 -60 65 39 64 70 -5 146 -45 49 -65 58 -272 116 -516 143 -650 163 -866 128z"
id="path156" />
<path
d="M7350 3874 c-174 -23 -417 -78 -635 -145 -71 -22 -172 -49 -223 -59 -52 -10 -96 -21 -99 -24 -3 -3 -9 -24 -13 -48 -11 -57 7 -91 60 -112 74 -29 165 -17 465 63 295 79 375 94 505 94 82 1 131 -4 171 -16 58 -18 151 -69 188 -104 12 -11 38 -29 57 -39 31 -17 37 -17 62 -4 36 21 72 76 72 112 0 70 -68 167 -148 211 -77 42 -192 68 -317 72 -60 2 -126 1 -145 -1z"
id="path158" />
<path
d="M5404 3765 c-207 -147 -263 -172 -364 -162 -77 8 -129 33 -235 111 -86 63 -129 85 -142 73 -13 -13 50 -118 103 -170 82 -83 160 -119 277 -125 150 -8 252 32 350 137 70 75 111 171 73 171 -8 0 -36 -16 -62 -35z"
id="path160" />
<path
d="M3981 3144 c-266 -178 -442 -186 -926 -41 -302 91 -455 97 -612 23 -157 -75 -251 -214 -222 -330 21 -83 76 -225 110 -284 100 -170 378 -370 654 -471 376 -137 757 -167 1102 -86 278 66 504 187 689 370 108 107 176 197 239 316 25 49 51 91 56 95 16 9 31 -9 94 -111 196 -316 448 -516 810 -641 336 -117 740 -122 1125 -14 297 84 533 213 711 389 164 163 221 269 222 416 1 66 -4 90 -25 135 -78 168 -302 263 -558 237 -41 -4 -156 -30 -255 -57 -223 -62 -311 -79 -446 -87 -183 -10 -352 31 -554 135 l-98 50 -22 -24 c-40 -44 -49 -77 -30 -117 29 -63 136 -154 230 -198 114 -54 192 -70 367 -76 177 -7 282 9 503 72 280 81 392 93 508 54 106 -35 157 -84 157 -151 0 -51 -59 -145 -134 -215 -226 -211 -559 -347 -961 -393 -216 -24 -499 5 -699 72 -314 105 -535 288 -671 556 -42 84 -31 81 -206 56 -100 -14 -118 -14 -186 0 -41 9 -79 16 -84 16 -5 0 -22 -30 -39 -66 -112 -249 -373 -466 -681 -568 -355 -118 -819 -76 -1207 109 -284 136 -425 272 -474 458 -11 41 -10 52 3 75 33 60 129 94 259 95 83 0 151 -15 325 -68 353 -109 499 -125 706 -75 157 38 305 134 365 236 23 39 24 48 14 78 -13 41 -47 86 -63 86 -7 0 -50 -25 -96 -56z"
id="path162" />
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

View File

@ -1,76 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M4965 7890 c-800 -37 -1523 -349 -2220 -960 -398 -349 -585 -575
-740 -895 -186 -381 -255 -705 -255 -1187 l0 -170 -31 7 c-16 4 -57 4 -90 0
l-59 -7 0 -46 c0 -26 7 -85 16 -132 32 -182 33 -172 -25 -256 -84 -120 -144
-270 -172 -427 -19 -116 -7 -352 25 -477 126 -486 561 -875 1080 -965 192 -33
458 -14 628 44 270 93 516 298 632 529 75 150 106 265 106 404 0 221 -64 380
-230 566 -256 287 -315 365 -382 509 -71 151 -81 208 -82 458 -1 207 1 226 26
322 27 103 90 244 147 327 56 80 154 168 237 212 93 49 184 72 354 87 l125 12
3 40 c5 73 -8 80 -150 78 -189 -2 -303 -33 -465 -124 -224 -126 -412 -428
-445 -713 -11 -97 -4 -324 13 -441 29 -193 116 -405 226 -552 22 -28 99 -118
173 -200 210 -233 261 -314 281 -445 24 -155 -6 -312 -94 -483 -54 -106 -197
-252 -312 -319 -177 -103 -317 -145 -515 -153 -202 -8 -362 24 -547 112 -140
66 -228 128 -339 239 -187 187 -278 396 -291 661 -11 242 60 492 169 595 32
30 101 64 176 86 42 12 62 23 61 34 0 8 -15 53 -33 100 -28 72 -37 85 -56 85
-24 0 -33 -11 -50 -57 -20 -57 -68 -20 -94 71 -9 31 -16 72 -16 89 0 32 0 32
50 32 l50 0 0 40 c0 32 5 43 29 61 l29 21 6 147 c11 255 40 516 73 645 84 337
221 619 423 869 58 72 181 197 233 237 39 31 36 11 -24 -121 -106 -239 -174
-489 -198 -729 -6 -58 -14 -124 -17 -147 l-5 -43 83 0 83 0 2 28 c6 87 48 350
69 434 102 408 382 841 719 1111 444 356 1004 562 1620 597 534 30 1027 -72
1505 -310 599 -298 964 -752 1090 -1355 25 -121 41 -502 22 -514 -7 -4 30 -5
81 -3 50 2 94 6 97 9 3 3 5 102 4 221 0 153 -6 251 -18 331 -17 110 -74 358
-95 414 -6 15 -7 27 -2 27 16 0 150 -148 223 -246 171 -231 272 -434 347 -701
66 -234 95 -428 76 -502 -7 -31 -8 -64 -2 -101 4 -30 11 -131 15 -225 l7 -170
70 -1 c135 -3 125 1 125 -41 0 -46 -25 -142 -40 -157 -6 -6 -53 -11 -113 -12
l-102 -1 -7 -49 c-4 -27 -9 -63 -13 -80 -5 -30 -5 -30 71 -54 89 -28 144 -72
205 -162 98 -143 139 -280 139 -459 0 -169 -28 -286 -105 -439 -162 -321 -435
-531 -787 -606 -126 -27 -362 -24 -493 5 -220 50 -383 138 -531 285 -108 109
-156 188 -188 312 -21 80 -20 225 0 317 32 139 97 245 300 488 186 221 240
310 307 502 l32 92 0 290 0 290 -33 95 c-18 52 -61 154 -97 225 -103 207 -209
316 -400 410 -144 72 -238 90 -460 88 -194 -1 -215 -7 -215 -62 0 -28 5 -43
18 -49 9 -5 82 -12 162 -16 235 -10 349 -41 485 -131 143 -95 243 -219 308
-383 57 -141 70 -223 70 -412 -1 -406 -86 -608 -406 -972 -117 -133 -170 -220
-215 -350 -116 -340 24 -729 352 -976 76 -58 249 -149 342 -180 209 -71 469
-85 697 -38 279 57 494 174 699 377 257 256 378 540 378 889 0 103 -5 146 -25
225 -39 157 -123 300 -221 380 l-45 37 21 36 c33 56 78 302 61 331 -5 7 -41
17 -81 22 -40 5 -75 11 -78 14 -2 2 -6 35 -8 72 -22 403 -38 538 -89 728 -152
580 -405 994 -886 1447 -188 177 -268 241 -496 398 -389 269 -901 464 -1397
535 -99 14 -425 36 -486 33 -14 -1 -97 -4 -185 -8z"/>
<path d="M2446 5430 c-70 -11 -124 -41 -200 -111 -74 -68 -120 -161 -142 -289
-52 -305 58 -798 216 -960 71 -73 124 -95 230 -95 107 0 172 27 231 94 114
129 143 301 137 811 l-3 305 -28 57 c-35 72 -110 140 -183 168 -66 24 -175 33
-258 20z m186 -340 c34 -42 39 -83 41 -385 1 -211 -1 -244 -16 -272 -35 -66
-122 -92 -175 -54 -58 41 -67 88 -66 346 1 248 8 353 28 380 32 44 146 35 188
-15z"/>
<path d="M7603 5430 c-118 -24 -229 -113 -266 -216 -37 -99 -47 -568 -17 -779
45 -314 174 -465 398 -465 96 0 158 27 225 99 76 82 111 169 148 377 30 167
34 565 6 664 -68 240 -261 366 -494 320z m153 -331 c31 -25 64 -116 75 -204
13 -106 5 -336 -15 -410 -40 -153 -118 -198 -196 -116 -56 59 -72 114 -78 271
-2 75 0 171 7 215 6 44 13 106 17 137 7 75 38 121 84 128 38 6 84 -3 106 -21z"/>
<path d="M4219 5372 c-199 -52 -323 -212 -364 -468 -6 -38 -4 -42 23 -53 65
-27 107 7 172 136 61 123 123 183 212 208 104 28 199 18 293 -30 41 -21 96
-60 122 -87 54 -54 76 -60 96 -22 29 56 -11 153 -93 228 -104 94 -296 131
-461 88z"/>
<path d="M5739 5278 c-135 -48 -216 -116 -250 -211 -13 -37 -13 -40 10 -58 37
-30 74 -25 105 16 15 19 51 47 79 62 45 23 65 27 142 27 76 0 97 -3 135 -23
113 -60 165 -112 263 -266 42 -65 73 -81 122 -61 58 24 50 47 -96 286 -112
185 -190 238 -359 247 -70 3 -96 0 -151 -19z"/>
<path d="M7247 5166 c-5 -15 -6 -31 -3 -34 8 -8 18 25 14 45 -3 13 -6 10 -11
-11z"/>
<path d="M4626 4169 c-50 -8 -86 -37 -114 -90 -20 -37 -23 -54 -18 -104 21
-232 161 -450 350 -544 175 -88 392 -92 571 -11 185 84 323 271 355 482 19
127 -9 233 -67 256 -30 13 -1008 22 -1077 11z m928 -169 c35 -13 40 -45 21
-121 -35 -134 -108 -240 -196 -284 -65 -33 -172 -48 -309 -43 -100 4 -122 8
-166 31 -89 45 -160 146 -189 269 -22 94 -20 137 8 148 30 12 799 13 831 0z"/>
<path d="M9980 552 c0 -4 21 -28 46 -52 55 -53 55 -40 2 19 -38 41 -48 48 -48
33z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

View File

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.0"
width="1024.000000pt"
height="1024.000000pt"
viewBox="0 0 1024.000000 1024.000000"
preserveAspectRatio="xMidYMid meet"
id="svg168"
sodipodi:docname="02.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs172" />
<sodipodi:namedview
id="namedview170"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="pt"
showgrid="false"
inkscape:zoom="0.69140625"
inkscape:cx="437.51412"
inkscape:cy="984.22599"
inkscape:window-width="1866"
inkscape:window-height="1012"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g166" />
<g
transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
fill="#000000"
stroke="none"
id="g166">
<rect
x="10"
y="10"
width="10239.509"
height="10229.297"
rx="1503.97427"
fill="#f0f0f0"
id="rect148"
style="stroke-width:10.1935" />
<path
d="M4784 8535 c-695 -66 -1296 -270 -1819 -616 -369 -245 -627 -477 -843 -763 -304 -402 -461 -948 -479 -1666 -9 -352 13 -581 82 -850 40 -156 61 -215 117 -323 55 -105 114 -169 194 -208 61 -30 69 -32 148 -27 179 12 320 123 356 281 8 38 6 64 -15 154 -14 59 -32 140 -41 178 -8 39 -21 95 -29 125 -41 165 -50 270 -50 565 0 261 3 309 28 480 30 214 28 242 -24 293 -41 40 -146 68 -312 84 -70 6 -127 15 -127 20 0 15 102 293 139 378 79 183 209 386 348 546 129 147 379 360 588 501 124 83 234 147 242 139 3 -3 -21 -36 -54 -73 -178 -203 -321 -426 -411 -643 -110 -265 -152 -484 -153 -804 -1 -338 43 -569 166 -877 56 -138 108 -235 192 -357 83 -119 95 -148 137 -323 54 -224 163 -505 223 -574 50 -57 102 -69 147 -34 46 36 34 86 -63 252 -65 113 -88 182 -107 332 -17 133 -20 142 -164 445 -148 313 -197 440 -250 650 -42 169 -60 311 -60 480 0 575 268 1118 733 1488 260 206 635 354 1060 418 142 21 566 26 722 9 323 -36 644 -133 905 -273 180 -96 322 -205 481 -368 464 -478 615 -1159 402 -1809 -22 -66 -78 -191 -142 -315 -275 -536 -251 -481 -271 -620 -10 -69 -28 -177 -40 -240 -27 -146 -37 -342 -20 -394 15 -47 51 -64 87 -41 73 49 164 319 184 549 17 208 39 271 158 461 197 313 285 530 342 845 31 167 34 543 6 685 -82 408 -210 682 -470 1005 -47 58 -83 107 -81 109 1 2 21 -7 43 -20 22 -13 77 -46 123 -73 324 -190 683 -538 883 -856 91 -145 268 -561 247 -582 -4 -3 -60 -16 -125 -27 -175 -31 -300 -80 -364 -141 -29 -26 -29 -54 -2 -190 64 -330 65 -751 3 -1081 -8 -46 -32 -145 -51 -219 -42 -157 -47 -246 -19 -329 20 -58 68 -118 120 -151 106 -65 273 -77 372 -27 140 71 251 273 328 592 55 229 76 429 76 725 0 991 -288 1664 -949 2213 -577 481 -1339 795 -2151 887 -154 18 -537 21 -696 5z"
id="path150" />
<path
d="M5963 4946 c-158 -51 -243 -191 -243 -398 0 -160 41 -281 122 -359 55 -53 99 -71 178 -72 55 -2 76 3 132 31 119 58 236 210 254 329 14 95 -50 278 -130 370 -72 82 -220 129 -313 99z m376 -302 c58 -49 66 -147 14 -198 -34 -34 -74 -34 -113 2 -57 50 -60 140 -8 193 36 36 67 37 107 3z"
id="path152" />
<path
d="M4089 4943 c-49 -8 -133 -66 -166 -116 -43 -64 -53 -102 -60 -224 -5 -91 -3 -110 21 -186 32 -103 76 -171 140 -214 126 -86 260 -73 354 33 73 82 97 158 97 310 0 121 0 121 -39 198 -51 101 -114 158 -203 186 -63 19 -88 22 -144 13z m-91 -294 c84 -29 79 -157 -8 -219 -65 -46 -110 -3 -113 107 -2 74 8 97 48 113 28 12 37 12 73 -1z"
id="path154" />
<path
d="M2585 3875 c-183 -29 -311 -98 -360 -194 -44 -88 -42 -163 6 -190 35 -20 65 -10 156 53 107 73 131 84 220 103 158 32 281 14 698 -102 301 -84 366 -93 423 -60 65 39 64 70 -5 146 -45 49 -65 58 -272 116 -516 143 -650 163 -866 128z"
id="path156" />
<path
d="M7350 3874 c-174 -23 -417 -78 -635 -145 -71 -22 -172 -49 -223 -59 -52 -10 -96 -21 -99 -24 -3 -3 -9 -24 -13 -48 -11 -57 7 -91 60 -112 74 -29 165 -17 465 63 295 79 375 94 505 94 82 1 131 -4 171 -16 58 -18 151 -69 188 -104 12 -11 38 -29 57 -39 31 -17 37 -17 62 -4 36 21 72 76 72 112 0 70 -68 167 -148 211 -77 42 -192 68 -317 72 -60 2 -126 1 -145 -1z"
id="path158" />
<path
d="M5404 3765 c-207 -147 -263 -172 -364 -162 -77 8 -129 33 -235 111 -86 63 -129 85 -142 73 -13 -13 50 -118 103 -170 82 -83 160 -119 277 -125 150 -8 252 32 350 137 70 75 111 171 73 171 -8 0 -36 -16 -62 -35z"
id="path160" />
<path
d="M3981 3144 c-266 -178 -442 -186 -926 -41 -302 91 -455 97 -612 23 -157 -75 -251 -214 -222 -330 21 -83 76 -225 110 -284 100 -170 378 -370 654 -471 376 -137 757 -167 1102 -86 278 66 504 187 689 370 108 107 176 197 239 316 25 49 51 91 56 95 16 9 31 -9 94 -111 196 -316 448 -516 810 -641 336 -117 740 -122 1125 -14 297 84 533 213 711 389 164 163 221 269 222 416 1 66 -4 90 -25 135 -78 168 -302 263 -558 237 -41 -4 -156 -30 -255 -57 -223 -62 -311 -79 -446 -87 -183 -10 -352 31 -554 135 l-98 50 -22 -24 c-40 -44 -49 -77 -30 -117 29 -63 136 -154 230 -198 114 -54 192 -70 367 -76 177 -7 282 9 503 72 280 81 392 93 508 54 106 -35 157 -84 157 -151 0 -51 -59 -145 -134 -215 -226 -211 -559 -347 -961 -393 -216 -24 -499 5 -699 72 -314 105 -535 288 -671 556 -42 84 -31 81 -206 56 -100 -14 -118 -14 -186 0 -41 9 -79 16 -84 16 -5 0 -22 -30 -39 -66 -112 -249 -373 -466 -681 -568 -355 -118 -819 -76 -1207 109 -284 136 -425 272 -474 458 -11 41 -10 52 3 75 33 60 129 94 259 95 83 0 151 -15 325 -68 353 -109 499 -125 706 -75 157 38 305 134 365 236 23 39 24 48 14 78 -13 41 -47 86 -63 86 -7 0 -50 -25 -96 -56z"
id="path162" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1,137 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1884 10147 c-438 -456 -723 -1077 -825 -1797 -30 -207 -33 -589 -6
-688 26 -97 31 -92 -81 -91 -349 0 -651 -131 -891 -388 l-81 -86 0 -313 c0
-173 2 -314 4 -314 2 0 15 17 27 38 105 172 237 352 259 352 4 0 -3 -39 -16
-87 -179 -642 -244 -1229 -215 -1938 11 -258 41 -647 62 -785 5 -36 14 -99 19
-140 16 -113 67 -403 106 -600 8 -41 26 -119 40 -172 14 -53 24 -105 22 -115
-3 -19 -114 198 -212 417 -31 69 -66 139 -76 155 l-20 30 0 -296 0 -295 68
-105 c113 -172 229 -298 351 -380 64 -44 227 -124 300 -149 36 -12 114 -33
175 -47 130 -29 314 -35 462 -14 50 8 97 12 104 9 19 -6 -282 -123 -407 -158
-163 -46 -305 -64 -458 -57 -140 7 -176 14 -359 72 -92 29 -127 36 -158 31
-76 -12 -78 -15 -78 -124 l0 -97 43 -26 c76 -48 103 -58 218 -83 203 -44 260
-51 424 -50 322 2 609 85 1027 296 148 74 162 79 250 89 98 10 132 24 358 144
81 43 150 59 150 35 0 -20 -48 -105 -96 -170 -132 -181 -370 -374 -601 -489
-241 -120 -476 -181 -804 -210 -128 -11 -181 -34 -255 -113 -74 -78 -97 -144
-95 -278 0 -60 7 -130 16 -163 55 -211 304 -437 615 -560 127 -50 353 -97 470
-97 57 0 222 23 252 35 13 5 23 -7 47 -57 67 -141 187 -236 371 -292 l85 -27
450 3 450 3 118 38 c222 71 402 159 557 273 104 76 282 259 339 349 28 44 55
85 60 91 11 14 395 24 886 24 l361 0 60 -88 c225 -333 663 -595 1091 -652 116
-16 379 -7 482 15 184 41 364 115 512 211 94 61 233 199 289 286 l43 67 56
-28 c299 -150 843 -78 1089 144 63 57 112 139 112 184 -1 35 -32 101 -61 128
-13 12 -105 64 -205 116 -206 107 -221 117 -286 170 -72 58 -106 112 -234 367
-170 340 -232 438 -363 573 -36 37 -63 67 -60 67 4 0 27 -9 53 -20 25 -12 139
-43 253 -70 115 -27 246 -58 293 -69 47 -11 122 -27 167 -35 65 -11 117 -32
250 -96 92 -45 192 -94 220 -108 29 -14 59 -33 68 -42 14 -15 8 -16 -68 -17
-45 -1 -134 -2 -197 -3 -127 -3 -283 23 -366 59 -26 12 -52 21 -58 21 -19 0
-130 77 -195 135 -67 60 -81 66 -81 34 0 -29 104 -129 182 -174 69 -41 195
-85 303 -106 80 -16 480 -20 530 -5 l30 9 -30 14 -30 14 30 -6 c99 -21 159
-17 298 19 149 38 249 77 321 122 l45 29 1 182 0 183 -113 -98 c-63 -53 -137
-110 -166 -125 -133 -73 -289 -87 -426 -39 -90 31 -199 96 -192 115 2 7 35 15
80 19 139 13 270 39 395 80 127 41 299 117 365 162 21 14 42 26 47 26 6 0 10
86 10 218 0 215 0 217 -19 187 -17 -28 -97 -107 -166 -165 -105 -87 -270 -174
-410 -216 -108 -32 -214 -56 -228 -51 -5 1 2 16 15 32 96 118 238 513 307 855
89 444 125 846 126 1405 0 395 -6 505 -51 866 -42 343 -63 459 -130 723 -63
243 -167 480 -309 701 -98 152 -120 194 -107 207 17 17 176 22 259 9 101 -16
160 -33 247 -73 189 -86 279 -179 415 -433 18 -33 37 -64 42 -70 5 -5 9 89 9
240 l-1 250 -77 69 c-151 134 -339 226 -560 271 -62 12 -115 25 -118 28 -4 3
2 30 12 59 16 46 19 83 18 278 -1 231 -12 334 -62 575 -71 338 -235 765 -393
1022 -23 38 -54 89 -70 115 -97 163 -237 350 -377 506 l-74 82 -177 0 -176 0
91 -92 c204 -208 333 -376 488 -631 186 -307 325 -643 405 -977 69 -291 80
-381 56 -468 -10 -34 -23 -67 -31 -72 -9 -8 -80 -9 -234 -4 -266 8 -247 -1
-340 171 -252 464 -543 835 -890 1133 -562 482 -1190 792 -1850 912 -121 22
-145 23 -690 23 l-565 0 -120 -27 c-66 -15 -163 -36 -215 -47 -604 -133 -1184
-448 -1625 -885 -276 -274 -477 -557 -660 -931 -74 -153 -135 -251 -208 -340
-23 -28 -30 -30 -94 -31 -37 -1 -122 -5 -188 -9 -153 -10 -177 -1 -200 77 -45
151 0 415 140 835 162 483 398 878 756 1266 l91 97 -165 0 -164 0 -89 -93z
m3676 -162 c570 -67 1152 -279 1613 -586 465 -311 866 -729 1110 -1159 78
-137 182 -371 217 -489 25 -82 48 -224 42 -257 -5 -30 -30 -40 -162 -68 -184
-38 -221 -56 -315 -150 -73 -72 -89 -95 -143 -206 -87 -177 -126 -307 -178
-598 -9 -50 -20 -95 -25 -98 -6 -3 -21 1 -34 10 -32 21 -288 130 -405 173
-311 113 -759 239 -1045 293 -437 83 -625 101 -1115 107 -430 5 -548 0 -846
-38 -568 -71 -1208 -279 -1583 -514 -57 -36 -107 -65 -111 -65 -4 0 -11 17
-15 38 -24 135 -126 458 -183 583 -83 184 -243 382 -388 480 -39 26 -74 54
-77 61 -5 13 19 80 70 203 14 33 50 121 80 195 284 706 734 1232 1389 1623
420 251 966 428 1449 470 122 11 541 6 655 -8z m-4078 -2636 c256 -52 479
-221 607 -461 44 -81 99 -235 124 -343 57 -249 114 -543 147 -770 63 -417 58
-1157 -11 -1695 -14 -112 -67 -419 -84 -489 -32 -134 -66 -252 -95 -331 -17
-47 -39 -107 -49 -135 -26 -71 -120 -260 -164 -327 -20 -31 -53 -69 -72 -83
-75 -58 -219 -101 -355 -105 -85 -3 -82 -7 -46 68 36 74 50 105 90 202 15 36
32 74 37 85 25 55 94 258 125 367 42 150 75 287 93 388 34 184 59 412 76 690
22 348 21 450 -16 930 -25 333 -111 861 -198 1215 -25 103 -101 327 -131 385
-34 67 -108 179 -152 229 -76 88 -82 141 -17 173 18 10 35 18 36 18 1 0 26 -5
55 -11z m-491 -123 c299 -144 526 -765 614 -1681 45 -472 38 -1171 -16 -1595
-66 -520 -179 -884 -368 -1194 -57 -94 -141 -196 -161 -196 -43 0 -215 142
-313 258 -103 123 -219 414 -277 698 -129 623 -153 1928 -49 2609 75 495 180
842 308 1021 63 87 93 106 166 102 33 -2 76 -12 96 -22z m7908 -18 c25 -12 82
-58 126 -102 194 -195 347 -570 454 -1111 58 -292 85 -524 106 -903 40 -715
-2 -1279 -136 -1817 -78 -314 -216 -629 -307 -705 -57 -47 -150 -90 -195 -90
-53 0 -122 36 -164 85 -46 53 -240 432 -305 595 -116 290 -148 590 -163 1520
-18 1111 53 1735 250 2179 48 108 141 269 188 326 45 53 74 58 146 23z m-605
-168 c30 -11 33 -32 10 -86 -143 -338 -218 -739 -254 -1349 -19 -324 -9 -1015
20 -1465 13 -193 15 -279 6 -287 -21 -22 -82 112 -121 266 -115 457 -123 1127
-20 1782 15 94 25 199 25 262 0 113 13 177 85 432 56 202 96 305 152 398 36
59 49 66 97 47z m-2813 -365 c222 -23 294 -32 429 -55 656 -110 1118 -249
1549 -464 153 -77 210 -111 218 -133 3 -9 -3 -113 -15 -232 -12 -119 -27 -290
-34 -381 -17 -228 -17 -856 0 -990 20 -163 59 -374 92 -500 17 -63 37 -167 45
-230 8 -63 22 -138 30 -167 31 -106 152 -227 270 -272 33 -13 67 -26 75 -30
16 -8 294 -548 306 -595 l7 -29 -79 6 c-48 3 -133 22 -219 47 -613 182 -800
219 -1155 227 -242 6 -372 -6 -485 -46 -111 -39 -143 -80 -152 -195 -10 -127
32 -283 149 -561 189 -449 438 -737 743 -860 151 -61 228 -76 395 -79 179 -3
228 -15 270 -63 38 -43 40 -102 7 -229 -29 -107 -67 -185 -126 -256 -136 -162
-311 -251 -546 -278 -441 -50 -865 95 -1184 403 -150 145 -244 310 -401 707
-159 398 -271 599 -425 755 -82 84 -114 109 -179 141 -70 34 -90 39 -164 43
-100 4 -167 -12 -189 -46 -30 -45 -66 -169 -198 -673 -83 -315 -163 -522 -276
-710 -241 -406 -581 -631 -1089 -722 -46 -8 -148 -13 -265 -12 -160 0 -204 3
-277 22 -197 49 -294 130 -347 289 -46 136 -15 274 74 336 22 15 111 52 197
82 286 99 398 165 573 339 147 147 244 288 322 466 111 254 162 475 142 616
-13 96 -23 121 -62 162 -66 69 -159 26 -190 -89 -8 -30 -29 -130 -47 -224 -40
-205 -75 -318 -142 -449 -108 -210 -243 -352 -453 -478 -109 -65 -161 -87
-309 -128 -161 -44 -210 -68 -290 -141 -95 -86 -150 -235 -127 -339 16 -70 8
-74 -132 -66 -302 17 -623 128 -771 267 -177 165 -178 393 -3 428 29 6 111 20
182 31 260 41 443 97 651 199 227 111 379 220 588 421 149 144 363 413 430
539 41 79 86 211 86 255 0 41 -32 91 -71 112 -53 27 -110 15 -265 -59 -76 -36
-209 -93 -294 -127 -85 -33 -166 -65 -179 -71 -13 -5 -26 -7 -29 -5 -5 6 30
77 170 346 79 153 111 224 183 410 90 232 107 267 168 347 68 91 71 113 30
230 -32 93 -33 127 -19 818 9 461 4 630 -30 1060 -18 234 -19 249 -10 263 18
29 135 99 296 178 532 261 1153 421 1760 454 204 11 611 3 791 -15z m1629
-4090 c183 -13 507 -96 635 -163 174 -92 443 -407 684 -802 136 -223 184 -291
265 -378 71 -75 128 -115 188 -133 18 -5 85 -14 148 -19 160 -14 170 -17 185
-53 16 -39 6 -69 -38 -116 -104 -109 -427 -145 -776 -86 -88 15 -161 50 -185
88 -8 12 -16 60 -19 107 -11 166 -52 230 -192 295 -158 73 -181 104 -250 340
-14 50 -46 153 -70 230 -24 77 -49 168 -55 202 -29 147 -158 205 -294 133 -57
-29 -122 -77 -183 -134 -28 -26 -33 -37 -33 -77 0 -55 19 -104 57 -149 36 -43
79 -60 151 -60 47 0 73 7 140 40 l82 39 14 -22 c37 -56 134 -421 122 -453 -15
-38 -228 -2 -340 57 -126 65 -291 222 -400 379 -123 175 -246 488 -246 625 0
73 6 83 54 96 42 12 214 28 251 23 11 -2 58 -5 105 -9z m-2122 -601 c116 -58
283 -291 390 -544 53 -126 152 -388 152 -403 0 -8 -10 -17 -22 -21 -30 -8
-772 -8 -820 0 -20 4 -39 12 -42 19 -2 7 2 44 9 81 7 38 27 139 44 224 50 258
131 551 178 643 17 34 43 35 111 1z"/>
<path d="M3903 5575 c-345 -79 -564 -312 -613 -650 -35 -245 31 -497 178 -687
151 -195 345 -300 597 -324 145 -13 345 39 495 129 112 67 260 215 313 312
134 245 140 552 14 785 -109 202 -291 344 -532 415 -75 22 -114 27 -240 31
-106 2 -168 -1 -212 -11z m8 -326 c127 -55 196 -232 138 -359 -58 -129 -200
-188 -345 -145 -147 43 -214 213 -146 368 26 58 109 142 153 156 48 15 141 5
200 -20z"/>
<path d="M3696 5084 c-11 -30 -6 -53 20 -83 29 -35 59 -39 92 -12 30 26 30 74
0 95 -30 21 -104 21 -112 0z"/>
<path d="M6154 5579 c-119 -20 -277 -91 -375 -169 -124 -98 -216 -227 -268
-375 -36 -104 -42 -313 -12 -440 66 -280 279 -530 531 -623 261 -96 558 -54
781 112 187 139 306 323 354 549 26 121 12 297 -34 418 -117 309 -471 542
-820 538 -58 -1 -129 -5 -157 -10z m60 -313 c87 -51 140 -150 140 -261 -1
-111 -53 -188 -151 -225 -180 -67 -352 50 -353 240 0 117 53 214 142 259 60
31 157 25 222 -13z"/>
<path d="M6016 5065 c-9 -9 -16 -23 -16 -32 0 -25 30 -81 47 -87 20 -8 56 23
48 41 -2 8 0 11 5 8 17 -11 11 32 -7 53 -25 29 -58 36 -77 17z m50 -53 c1 -7
0 -8 -3 -2 -2 5 -9 8 -14 4 -5 -3 -9 0 -9 6 0 15 23 7 26 -8z"/>
<path d="M10020 505 c24 -24 46 -42 49 -40 6 7 -70 85 -83 85 -6 0 10 -20 34
-45z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -1,66 +0,0 @@
DROP TABLE IF EXISTS artist;
CREATE TABLE artist (
id TEXT PRIMARY KEY NOT NULL,
name TEXT
);
DROP TABLE IF EXISTS artist_release_group;
CREATE TABLE artist_release_group (
artist_id TEXT NOT NULL,
release_group_id TEXT NOT NULL
);
DROP TABLE IF EXISTS artist_track;
CREATE TABLE artist_track (
artist_id TEXT NOT NULL,
track_id TEXT NOT NULL
);
DROP TABLE IF EXISTS release_group;
CREATE TABLE release_group (
id TEXT PRIMARY KEY NOT NULL,
albumartist TEXT,
albumsort INT,
musicbrainz_albumtype TEXT,
compilation TEXT,
album_artist_id TEXT
);
DROP TABLE IF EXISTS release_;
CREATE TABLE release_ (
id TEXT PRIMARY KEY NOT NULL,
release_group_id TEXT NOT NULL,
title TEXT,
copyright TEXT,
album_status TEXT,
language TEXT,
year TEXT,
date TEXT,
country TEXT,
barcode TEXT
);
DROP TABLE IF EXISTS track;
CREATE TABLE track (
id TEXT PRIMARY KEY NOT NULL,
downloaded BOOLEAN NOT NULL DEFAULT 0,
release_id TEXT NOT NULL,
track TEXT,
length INT,
tracknumber TEXT,
isrc TEXT,
genre TEXT,
lyrics TEXT,
path TEXT,
file TEXT,
url TEXT,
src TEXT
);
DROP TABLE IF EXISTS source;
CREATE TABLE source (
track_id TEXT NOT NULL,
src TEXT NOT NULL,
url TEXT NOT NULL,
valid BOOLEAN NOT NULL DEFAULT 1
);

View File

@ -6,9 +6,8 @@ logging.getLogger().setLevel(logging.DEBUG)
if __name__ == "__main__": if __name__ == "__main__":
commands = [ commands = [
"s: #a Ghost Bath", "s: #a Crystal F",
"0", "d: 20",
"d: 1",
] ]

View File

@ -68,9 +68,11 @@ def write_metadata_to_target(metadata: Metadata, target: Target, song: Song):
LOGGER.info(str(metadata)) LOGGER.info(str(metadata))
if song.artwork.best_variant is not None: if song.artwork.best_variant is not None:
best_variant = song.artwork.best_variant
r = artwork_connection.get( r = artwork_connection.get(
url=song.artwork.best_variant["url"], url=best_variant["url"],
disable_cache=False, name=song.artwork.get_variant_name(best_variant),
) )
temp_target: Target = Target.temp() temp_target: Target = Target.temp()

View File

@ -13,6 +13,8 @@ from ..utils.support_classes.query import Query
from ..utils.support_classes.download_result import DownloadResult from ..utils.support_classes.download_result import DownloadResult
from ..utils.exception.download import UrlNotFoundException from ..utils.exception.download import UrlNotFoundException
from ..utils.enums.colors import BColors from ..utils.enums.colors import BColors
from .. import console
from ..download.results import Results, Option, PageResults from ..download.results import Results, Option, PageResults
from ..download.page_attributes import Pages from ..download.page_attributes import Pages
from ..pages import Page from ..pages import Page
@ -174,12 +176,14 @@ class Downloader:
page_count = 0 page_count = 0
for option in self.current_results.formated_generator(max_items_per_page=self.max_displayed_options): for option in self.current_results.formated_generator(max_items_per_page=self.max_displayed_options):
if isinstance(option, Option): if isinstance(option, Option):
color = BColors.BOLD.value if self.pages.is_downloadable(option.music_object) else BColors.GREY.value _downloadable = self.pages.is_downloadable(option.music_object)
print(f"{color}{option.index:0{self.option_digits}} {option.music_object.option_string}{BColors.ENDC.value}")
r = f"{BColors.GREY.value}{option.index:0{self.option_digits}}{BColors.ENDC.value} {option.music_object.option_string}"
print(r)
else: else:
prefix = ALPHABET[page_count % len(ALPHABET)] prefix = ALPHABET[page_count % len(ALPHABET)]
print( print(
f"{BColors.HEADER.value}({prefix}) ------------------------{option.__name__:{PAGE_NAME_FILL}<{MAX_PAGE_LEN}}------------{BColors.ENDC.value}") f"{BColors.HEADER.value}({prefix}) --------------------------------{option.__name__:{PAGE_NAME_FILL}<{MAX_PAGE_LEN}}--------------------{BColors.ENDC.value}")
self.page_dict[prefix] = option self.page_dict[prefix] = option
self.page_dict[option.__name__] = option self.page_dict[option.__name__] = option
@ -211,6 +215,9 @@ class Downloader:
return True return True
def _process_parsed(self, key_text: Dict[str, str], query: str) -> Query: def _process_parsed(self, key_text: Dict[str, str], query: str) -> Query:
# strip all the values in key_text
key_text = {key: value.strip() for key, value in key_text.items()}
song = None if not "t" in key_text else Song(title=key_text["t"], dynamic=True) song = None if not "t" in key_text else Song(title=key_text["t"], dynamic=True)
album = None if not "r" in key_text else Album(title=key_text["r"], dynamic=True) album = None if not "r" in key_text else Album(title=key_text["r"], dynamic=True)
artist = None if not "a" in key_text else Artist(name=key_text["a"], dynamic=True) artist = None if not "a" in key_text else Artist(name=key_text["a"], dynamic=True)

View File

@ -1,12 +1,13 @@
import json import json
from pathlib import Path from pathlib import Path
from dataclasses import dataclass from dataclasses import dataclass, field
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import List, Optional from typing import List, Optional
from functools import lru_cache from functools import lru_cache
import logging import logging
from ..utils.config import main_settings from ..utils.config import main_settings
from ..utils.string_processing import fit_to_file_system
@dataclass @dataclass
@ -17,6 +18,8 @@ class CacheAttribute:
created: datetime created: datetime
expires: datetime expires: datetime
additional_info: dict = field(default_factory=dict)
@property @property
def id(self): def id(self):
return f"{self.module}_{self.name}" return f"{self.module}_{self.name}"
@ -31,6 +34,12 @@ class CacheAttribute:
return self.__dict__ == other.__dict__ return self.__dict__ == other.__dict__
@dataclass
class CacheResult:
content: bytes
attribute: CacheAttribute
class Cache: class Cache:
def __init__(self, module: str, logger: logging.Logger): def __init__(self, module: str, logger: logging.Logger):
self.module = module self.module = module
@ -48,13 +57,16 @@ class Cache:
self._time_fields = {"created", "expires"} self._time_fields = {"created", "expires"}
with self.index.open("r") as i: with self.index.open("r") as i:
for c in json.loads(i.read()): try:
for key in self._time_fields: for c in json.loads(i.read()):
c[key] = datetime.fromisoformat(c[key]) for key in self._time_fields:
c[key] = datetime.fromisoformat(c[key])
ca = CacheAttribute(**c) ca = CacheAttribute(**c)
self.cached_attributes.append(ca) self.cached_attributes.append(ca)
self._id_to_attribute[ca.id] = ca self._id_to_attribute[ca.id] = ca
except json.JSONDecodeError:
pass
@lru_cache() @lru_cache()
def _init_module(self, module: str) -> Path: def _init_module(self, module: str) -> Path:
@ -63,7 +75,7 @@ class Cache:
:return: the module path :return: the module path
""" """
r = Path(self._dir, module) r = Path(self._dir, module)
r.mkdir(exist_ok=True) r.mkdir(exist_ok=True, parents=True)
return r return r
def _write_index(self, indent: int = 4): def _write_index(self, indent: int = 4):
@ -99,7 +111,7 @@ class Cache:
return True return True
def set(self, content: bytes, name: str, expires_in: float = 10, module: str = ""): def set(self, content: bytes, name: str, expires_in: float = 10, module: str = "", additional_info: dict = None):
""" """
:param content: :param content:
:param module: :param module:
@ -110,6 +122,7 @@ class Cache:
if name == "": if name == "":
return return
additional_info = additional_info or {}
module = self.module if module == "" else module module = self.module if module == "" else module
module_path = self._init_module(module) module_path = self._init_module(module)
@ -119,27 +132,31 @@ class Cache:
name=name, name=name,
created=datetime.now(), created=datetime.now(),
expires=datetime.now() + timedelta(days=expires_in), expires=datetime.now() + timedelta(days=expires_in),
additional_info=additional_info,
) )
self._write_attribute(cache_attribute) self._write_attribute(cache_attribute)
cache_path = Path(module_path, name) cache_path = fit_to_file_system(Path(module_path, name), hidden_ok=True)
with cache_path.open("wb") as content_file: with cache_path.open("wb") as content_file:
self.logger.debug(f"writing cache to {cache_path}") self.logger.debug(f"writing cache to {cache_path}")
content_file.write(content) content_file.write(content)
def get(self, name: str) -> Optional[bytes]: def get(self, name: str) -> Optional[CacheResult]:
path = Path(self._dir, self.module, name) path = fit_to_file_system(Path(self._dir, self.module, name), hidden_ok=True)
if not path.is_file(): if not path.is_file():
return None return None
# check if it is outdated # check if it is outdated
if f"{self.module}_{name}" not in self._id_to_attribute:
path.unlink()
return
existing_attribute: CacheAttribute = self._id_to_attribute[f"{self.module}_{name}"] existing_attribute: CacheAttribute = self._id_to_attribute[f"{self.module}_{name}"]
if not existing_attribute.is_valid: if not existing_attribute.is_valid:
return return
with path.open("rb") as f: with path.open("rb") as f:
return f.read() return CacheResult(content=f.read(), attribute=existing_attribute)
def clean(self): def clean(self):
keep = set() keep = set()
@ -148,7 +165,7 @@ class Cache:
if ca.name == "": if ca.name == "":
continue continue
file = Path(self._dir, ca.module, ca.name) file = fit_to_file_system(Path(self._dir, ca.module, ca.name), hidden_ok=True)
if not ca.is_valid: if not ca.is_valid:
self.logger.debug(f"deleting cache {ca.id}") self.logger.debug(f"deleting cache {ca.id}")

View File

@ -15,6 +15,8 @@ from tqdm import tqdm
from .cache import Cache from .cache import Cache
from .rotating import RotatingProxy from .rotating import RotatingProxy
from ..objects import Target from ..objects import Target
from ..utils import request_trace
from ..utils.string_processing import shorten_display_url
from ..utils.config import main_settings from ..utils.config import main_settings
from ..utils.support_classes.download_result import DownloadResult from ..utils.support_classes.download_result import DownloadResult
from ..utils.hacking import merge_args from ..utils.hacking import merge_args
@ -123,12 +125,17 @@ class Connection:
return headers return headers
def save(self, r: requests.Response, name: str, error: bool = False, **kwargs): def save(self, r: requests.Response, name: str, error: bool = False, no_update_if_valid_exists: bool = False, **kwargs):
n_kwargs = {} n_kwargs = {}
if error: if error:
n_kwargs["module"] = "failed_requests" n_kwargs["module"] = "failed_requests"
self.cache.set(r.content, name, expires_in=kwargs.get("expires_in", self.cache_expiring_duration), **n_kwargs) if self.cache.get(name) is not None and no_update_if_valid_exists:
return
self.cache.set(r.content, name, expires_in=kwargs.get("expires_in", self.cache_expiring_duration), additional_info={
"encoding": r.encoding,
}, **n_kwargs)
def request( def request(
self, self,
@ -143,6 +150,7 @@ class Connection:
sleep_after_404: float = None, sleep_after_404: float = None,
is_heartbeat: bool = False, is_heartbeat: bool = False,
disable_cache: bool = None, disable_cache: bool = None,
enable_cache_readonly: bool = False,
method: str = None, method: str = None,
name: str = "", name: str = "",
exclude_headers: List[str] = None, exclude_headers: List[str] = None,
@ -152,7 +160,7 @@ class Connection:
raise AttributeError("method is not set.") raise AttributeError("method is not set.")
method = method.upper() method = method.upper()
headers = dict() if headers is None else headers headers = dict() if headers is None else headers
disable_cache = headers.get("Cache-Control", "").lower() == "no-cache" if disable_cache is None else disable_cache disable_cache = (headers.get("Cache-Control", "").lower() == "no-cache" if disable_cache is None else disable_cache) or kwargs.get("stream", False)
accepted_response_codes = self.ACCEPTED_RESPONSE_CODES if accepted_response_codes is None else accepted_response_codes accepted_response_codes = self.ACCEPTED_RESPONSE_CODES if accepted_response_codes is None else accepted_response_codes
current_kwargs = copy.copy(locals()) current_kwargs = copy.copy(locals())
@ -160,6 +168,7 @@ class Connection:
current_kwargs.update(**kwargs) current_kwargs.update(**kwargs)
parsed_url = urlparse(url) parsed_url = urlparse(url)
trace_string = f"{method} {shorten_display_url(url)} \t{'[stream]' if kwargs.get('stream', False) else ''}"
if not raw_headers: if not raw_headers:
_headers = copy.copy(self.HEADER_VALUES) _headers = copy.copy(self.HEADER_VALUES)
@ -175,15 +184,23 @@ class Connection:
request_url = parsed_url.geturl() if not raw_url else url request_url = parsed_url.geturl() if not raw_url else url
if name != "" and not disable_cache: if name != "" and (not disable_cache or enable_cache_readonly):
cached = self.cache.get(name) cached = self.cache.get(name)
if cached is not None: if cached is not None:
request_trace(f"{trace_string}\t[cached]")
with responses.RequestsMock() as resp: with responses.RequestsMock() as resp:
additional_info = cached.attribute.additional_info
body = cached.content
if additional_info.get("encoding", None) is not None:
body = body.decode(additional_info["encoding"])
resp.add( resp.add(
method=method, method=method,
url=request_url, url=request_url,
body=cached, body=body,
) )
return requests.request(method=method, url=url, timeout=timeout, headers=headers, **kwargs) return requests.request(method=method, url=url, timeout=timeout, headers=headers, **kwargs)
@ -199,6 +216,9 @@ class Connection:
if header in headers: if header in headers:
del headers[header] del headers[header]
if try_count <= 0:
request_trace(trace_string)
r = None r = None
connection_failed = False connection_failed = False
try: try:
@ -228,10 +248,10 @@ class Connection:
self.lock = False self.lock = False
if r is None: if r is None:
self.LOGGER.warning(f"{self.HOST.netloc} didn't respond at {url}. ({try_count}-{self.TRIES})") self.LOGGER.warning(f"{parsed_url.netloc} didn't respond at {url}. ({try_count}-{self.TRIES})")
self.LOGGER.debug("request headers:\n\t"+ "\n\t".join(f"{k}\t=\t{v}" for k, v in headers.items())) self.LOGGER.debug("request headers:\n\t"+ "\n\t".join(f"{k}\t=\t{v}" for k, v in headers.items()))
else: else:
self.LOGGER.warning(f"{self.HOST.netloc} responded wit {r.status_code} at {url}. ({try_count}-{self.TRIES})") self.LOGGER.warning(f"{parsed_url.netloc} responded wit {r.status_code} at {url}. ({try_count}-{self.TRIES})")
self.LOGGER.debug("request headers:\n\t"+ "\n\t".join(f"{k}\t=\t{v}" for k, v in r.request.headers.items())) self.LOGGER.debug("request headers:\n\t"+ "\n\t".join(f"{k}\t=\t{v}" for k, v in r.request.headers.items()))
self.LOGGER.debug("response headers:\n\t"+ "\n\t".join(f"{k}\t=\t{v}" for k, v in r.headers.items())) self.LOGGER.debug("response headers:\n\t"+ "\n\t".join(f"{k}\t=\t{v}" for k, v in r.headers.items()))
self.LOGGER.debug(r.content) self.LOGGER.debug(r.content)

View File

@ -50,6 +50,9 @@ class Artwork:
return None return None
return min(self._variant_mapping.values(), key=lambda x: x["deviation"]) return min(self._variant_mapping.values(), key=lambda x: x["deviation"])
def get_variant_name(self, variant: ArtworkVariant) -> str:
return f"artwork_{variant['width']}x{variant['height']}_{hash_url(variant['url']).replace('/', '_')}"
def __merge__(self, other: Artwork, override: bool = False) -> None: def __merge__(self, other: Artwork, override: bool = False) -> None:
for key, value in other._variant_mapping.items(): for key, value in other._variant_mapping.items():
if key not in self._variant_mapping or override: if key not in self._variant_mapping or override:

View File

@ -1,110 +0,0 @@
from collections import defaultdict
from typing import Dict, List, Optional
import weakref
from .parents import DatabaseObject
"""
This is a cache for the objects, that et pulled out of the database.
This is necessary, to not have duplicate objects with the same id.
Using a cache that maps the ojects to their id has multiple benefits:
- if you modify the object at any point, all objects with the same id get modified *(copy by reference)*
- less ram usage
- to further decrease ram usage I only store weak refs and not a strong reference, for the gc to still work
"""
class ObjectCache:
"""
ObjectCache is a cache for the objects retrieved from a database.
It maps each object to its id and uses weak references to manage its memory usage.
Using a cache for these objects provides several benefits:
- Modifying an object updates all objects with the same id (due to copy by reference)
- Reduced memory usage
:attr object_to_id: Dictionary that maps DatabaseObjects to their id.
:attr weakref_map: Dictionary that uses weak references to DatabaseObjects as keys and their id as values.
:method exists: Check if a DatabaseObject already exists in the cache.
:method append: Add a DatabaseObject to the cache if it does not already exist.
:method extent: Add a list of DatabaseObjects to the cache.
:method remove: Remove a DatabaseObject from the cache by its id.
:method get: Retrieve a DatabaseObject from the cache by its id. """
object_to_id: Dict[str, DatabaseObject]
weakref_map: Dict[weakref.ref, str]
def __init__(self) -> None:
self.object_to_id = dict()
self.weakref_map = defaultdict()
def exists(self, database_object: DatabaseObject) -> bool:
"""
Check if a DatabaseObject with the same id already exists in the cache.
:param database_object: The DatabaseObject to check for.
:return: True if the DatabaseObject exists, False otherwise.
"""
if database_object.dynamic:
return True
return database_object.id in self.object_to_id
def on_death(self, weakref_: weakref.ref) -> None:
"""
Callback function that gets triggered when the reference count of a DatabaseObject drops to 0.
This function removes the DatabaseObject from the cache.
:param weakref_: The weak reference of the DatabaseObject that has been garbage collected.
"""
data_id = self.weakref_map.pop(weakref_)
self.object_to_id.pop(data_id)
def get_weakref(self, database_object: DatabaseObject) -> weakref.ref:
return weakref.ref(database_object, self.on_death)
def append(self, database_object: DatabaseObject) -> bool:
"""
Add a DatabaseObject to the cache.
:param database_object: The DatabaseObject to add to the cache.
:return: True if the DatabaseObject already exists in the cache, False otherwise.
"""
if self.exists(database_object):
return True
self.weakref_map[weakref.ref(database_object, self.on_death)] = database_object.id
self.object_to_id[database_object.id] = database_object
return False
def extent(self, database_object_list: List[DatabaseObject]):
"""
adjacent to the extent method of list, this appends n Object
"""
for database_object in database_object_list:
self.append(database_object)
def remove(self, _id: str):
"""
Remove a DatabaseObject from the cache.
:param _id: The id of the DatabaseObject to remove from the cache.
"""
data = self.object_to_id.get(_id)
if data:
self.weakref_map.pop(weakref.ref(data))
self.object_to_id.pop(_id)
def __getitem__(self, item) -> Optional[DatabaseObject]:
"""
this returns the data obj
:param item: the id of the music object
:return:
"""
return self.object_to_id.get(item)
def get(self, _id: str) -> Optional[DatabaseObject]:
return self.__getitem__(_id)

View File

@ -161,3 +161,8 @@ class Collection(Generic[T]):
def __getitem__(self, item: int): def __getitem__(self, item: int):
return self._data[item] return self._data[item]
def get(self, item: int, default = None):
if item >= len(self._data):
return default
return self._data[item]

View File

@ -267,9 +267,9 @@ class OuterProxy:
return r return r
def __repr__(self):
return f"{type(self).__name__}({', '.join(key + ': ' + str(val) for key, val in self.indexing_values)})"
@property @property
def title_string(self) -> str: def title_string(self) -> str:
return str(self.__getattribute__(self.TITEL)) return str(self.__getattribute__(self.TITEL))
def __repr__(self):
return f"{type(self).__name__}({self.title_string})"

View File

@ -27,14 +27,49 @@ from ..utils.string_processing import unify
from .parents import OuterProxy as Base from .parents import OuterProxy as Base
from ..utils.config import main_settings from ..utils.config import main_settings
from ..utils.enums.colors import BColors
""" """
All Objects dependent All Objects dependent
""" """
CountryTyping = type(list(pycountry.countries)[0]) CountryTyping = type(list(pycountry.countries)[0])
OPTION_STRING_DELIMITER = " | "
OPTION_BACKGROUND = BColors.GREY
OPTION_FOREGROUND = BColors.OKBLUE
def get_collection_string(
collection: Collection[Base],
template: str,
ignore_titles: Set[str] = None,
background: BColors = OPTION_BACKGROUND,
foreground: BColors = OPTION_FOREGROUND
) -> str:
if collection.empty:
return ""
foreground = foreground.value
background = background.value
ignore_titles = ignore_titles or set()
r = background
element: Base
titel_list: List[str] = [element.title_string.strip() for element in collection if element.title_string not in ignore_titles]
for i, titel in enumerate(titel_list):
delimiter = ", "
if i == len(collection) - 1:
delimiter = ""
elif i == len(collection) - 2:
delimiter = " and "
r += foreground + titel + BColors.ENDC.value + background + delimiter + BColors.ENDC.value
r += BColors.ENDC.value
return template.format(r)
class Song(Base): class Song(Base):
title: str title: str
@ -152,18 +187,12 @@ class Song(Base):
return main_artists return main_artists
return f"{main_artists} feat. {feature_artists}" return f"{main_artists} feat. {feature_artists}"
def __repr__(self) -> str:
return f"Song(\"{self.title}\")"
@property @property
def option_string(self) -> str: def option_string(self) -> str:
r = f"{self.__repr__()}" r = OPTION_FOREGROUND.value + self.title + BColors.ENDC.value + OPTION_BACKGROUND.value
if not self.album_collection.empty: r += get_collection_string(self.album_collection, " from {}", ignore_titles={self.title})
r += f" from Album({OPTION_STRING_DELIMITER.join(album.title for album in self.album_collection)})" r += get_collection_string(self.main_artist_collection, " by {}")
if not self.main_artist_collection.empty: r += get_collection_string(self.feature_artist_collection, " feat. {}")
r += f" by Artist({OPTION_STRING_DELIMITER.join(artist.name for artist in self.main_artist_collection)})"
if not self.feature_artist_collection.empty:
r += f" feat. Artist({OPTION_STRING_DELIMITER.join(artist.name for artist in self.feature_artist_collection)})"
return r return r
@property @property
@ -302,14 +331,15 @@ class Album(Base):
id3Mapping.ALBUMSORTORDER: [str(self.albumsort)] if self.albumsort is not None else [] id3Mapping.ALBUMSORTORDER: [str(self.albumsort)] if self.albumsort is not None else []
}) })
def __repr__(self):
return f"Album(\"{self.title}\")"
@property @property
def option_string(self) -> str: def option_string(self) -> str:
return f"{self.__repr__()} " \ r = OPTION_FOREGROUND.value + self.title + BColors.ENDC.value + OPTION_BACKGROUND.value
f"by Artist({OPTION_STRING_DELIMITER.join([artist.name + str(artist.id) for artist in self.artist_collection])}) " \ r += get_collection_string(self.artist_collection, " by {}")
f"under Label({OPTION_STRING_DELIMITER.join([label.name for label in self.label_collection])})" r += get_collection_string(self.label_collection, " under {}")
if len(self.song_collection) > 0:
r += f" with {len(self.song_collection)} songs"
return r
@property @property
def options(self) -> List[P]: def options(self) -> List[P]:
@ -570,8 +600,18 @@ class Artist(Base):
@property @property
def option_string(self) -> str: def option_string(self) -> str:
return f"{self.__repr__()} " \ r = OPTION_FOREGROUND.value + self.name + BColors.ENDC.value + OPTION_BACKGROUND.value
f"under Label({OPTION_STRING_DELIMITER.join([label.name for label in self.label_collection])})" r += get_collection_string(self.label_collection, " under {}")
r += OPTION_BACKGROUND.value
if len(self.main_album_collection) > 0:
r += f" with {len(self.main_album_collection)} albums"
if len(self.feature_song_collection) > 0:
r += f" featured in {len(self.feature_song_collection)} songs"
r += BColors.ENDC.value
return r
@property @property
def options(self) -> List[P]: def options(self) -> List[P]:
@ -689,4 +729,4 @@ class Label(Base):
@property @property
def option_string(self): def option_string(self):
return self.__repr__() return OPTION_FOREGROUND.value + self.name + BColors.ENDC.value

View File

@ -256,7 +256,7 @@ class Page:
stop_at_level=stop_at_level, stop_at_level=stop_at_level,
post_process=False, post_process=False,
type_string=type(music_object).__name__, type_string=type(music_object).__name__,
title_string=music_object.title_string, entity_string=music_object.option_string,
) )
if new_music_object is None: if new_music_object is None:
@ -278,7 +278,7 @@ class Page:
enforce_type: Type[DatabaseObject] = None, enforce_type: Type[DatabaseObject] = None,
post_process: bool = True, post_process: bool = True,
type_string: str = "", type_string: str = "",
title_string: str = "", entity_string: str = "",
) -> Optional[DatabaseObject]: ) -> Optional[DatabaseObject]:
obj_type = self.get_source_type(source) obj_type = self.get_source_type(source)
@ -306,7 +306,7 @@ class Page:
return None return None
if stop_at_level > 0: if stop_at_level > 0:
trace(f"fetching {type_string} [{title_string}] [stop_at_level={stop_at_level}]") trace(f"fetching {type_string} [{entity_string}] [stop_at_level={stop_at_level}]")
collection: Collection collection: Collection
for collection_str in music_object.DOWNWARDS_COLLECTION_STRING_ATTRIBUTES: for collection_str in music_object.DOWNWARDS_COLLECTION_STRING_ATTRIBUTES:
@ -364,7 +364,7 @@ class Page:
skip_details: bool = False, skip_details: bool = False,
process_metadata_anyway: bool = True process_metadata_anyway: bool = True
) -> DownloadResult: ) -> DownloadResult:
trace(f"downloading {type(music_object).__name__} [{music_object.title_string}]") trace(f"downloading {type(music_object).__name__} [{music_object.option_string}]")
skip_next_details = skip_details skip_next_details = skip_details
# Skips all releases, that are defined in shared.ALBUM_TYPE_BLACKLIST, if download_all is False # Skips all releases, that are defined in shared.ALBUM_TYPE_BLACKLIST, if download_all is False
@ -451,7 +451,7 @@ class Page:
source = sources[0] source = sources[0]
if not found_on_disc: if not found_on_disc:
r = self.download_song_to_target(source=source, target=temp_target, desc=song.title) r = self.download_song_to_target(source=source, target=temp_target, desc=song.option_string)
if not r.is_fatal_error: if not r.is_fatal_error:
r.merge(self._post_process_targets(song, temp_target, r.merge(self._post_process_targets(song, temp_target,

View File

@ -136,7 +136,7 @@ class Bandcamp(Page):
"full_page": True, "full_page": True,
"search_filter": filter_string, "search_filter": filter_string,
"search_text": search_query, "search_text": search_query,
}) }, name=f"search_{filter_string}_{search_query}")
if r is None: if r is None:
return results return results
@ -224,7 +224,7 @@ class Bandcamp(Page):
def fetch_artist(self, source: Source, stop_at_level: int = 1) -> Artist: def fetch_artist(self, source: Source, stop_at_level: int = 1) -> Artist:
artist = Artist() artist = Artist()
r = self.connection.get(_parse_artist_url(source.url)) r = self.connection.get(_parse_artist_url(source.url), name=f"artist_{urlparse(source.url).scheme}_{urlparse(source.url).netloc}")
if r is None: if r is None:
return artist return artist
@ -271,7 +271,7 @@ class Bandcamp(Page):
def fetch_album(self, source: Source, stop_at_level: int = 1) -> Album: def fetch_album(self, source: Source, stop_at_level: int = 1) -> Album:
album = Album() album = Album()
r = self.connection.get(source.url) r = self.connection.get(source.url, name=f"album_{urlparse(source.url).netloc.split('.')[0]}_{urlparse(source.url).path.replace('/', '').replace('album', '')}")
if r is None: if r is None:
return album return album
@ -338,7 +338,7 @@ class Bandcamp(Page):
return [] return []
def fetch_song(self, source: Source, stop_at_level: int = 1) -> Song: def fetch_song(self, source: Source, stop_at_level: int = 1) -> Song:
r = self.connection.get(source.url) r = self.connection.get(source.url, name=f"song_{urlparse(source.url).netloc.split('.')[0]}_{urlparse(source.url).path.replace('/', '').replace('track', '')}")
if r is None: if r is None:
return Song() return Song()

View File

@ -26,6 +26,7 @@ from ..objects import (
) )
from ..utils.config import logging_settings from ..utils.config import logging_settings
from ..utils import string_processing, shared from ..utils import string_processing, shared
from ..utils.string_processing import clean_song_title
from ..utils.support_classes.query import Query from ..utils.support_classes.query import Query
from ..utils.support_classes.download_result import DownloadResult from ..utils.support_classes.download_result import DownloadResult
@ -120,6 +121,7 @@ class Musify(Page):
self.connection: Connection = Connection( self.connection: Connection = Connection(
host="https://musify.club/", host="https://musify.club/",
logger=self.LOGGER, logger=self.LOGGER,
module="musify",
) )
self.stream_connection: Connection = Connection( self.stream_connection: Connection = Connection(
@ -355,8 +357,10 @@ class Musify(Page):
if raw_id.isdigit(): if raw_id.isdigit():
_id = raw_id _id = raw_id
return Song( return Song(
title=song_title, title=clean_song_title(song_title, artist_name=artist_list[0].name if len(artist_list) > 0 else None),
main_artist_list=artist_list, main_artist_list=artist_list,
source_list=source_list source_list=source_list
) )
@ -372,7 +376,7 @@ class Musify(Page):
def general_search(self, search_query: str) -> List[DatabaseObject]: def general_search(self, search_query: str) -> List[DatabaseObject]:
search_results = [] search_results = []
r = self.connection.get(f"https://musify.club/search?searchText={search_query}") r = self.connection.get(f"https://musify.club/search?searchText={search_query}", name="search_" + search_query)
if r is None: if r is None:
return [] return []
search_soup: BeautifulSoup = self.get_soup_from_response(r) search_soup: BeautifulSoup = self.get_soup_from_response(r)
@ -390,10 +394,11 @@ class Musify(Page):
return search_results return search_results
def fetch_song(self, source: Source, stop_at_level: int = 1) -> Song: def fetch_song(self, source: Source, stop_at_level: int = 1) -> Song:
# https://musify.club/track/linkin-park-numb-210765 musify_url = parse_url(source.url)
r = self.connection.get(source.url)
r = self.connection.get(source.url, name="track_" + musify_url.name_with_id)
if r is None: if r is None:
return Song return Song()
soup = self.get_soup_from_response(r) soup = self.get_soup_from_response(r)
@ -502,7 +507,7 @@ class Musify(Page):
)) ))
return Song( return Song(
title=track_name, title=clean_song_title(track_name, artist_name=artist_list[0].name if len(artist_list) > 0 else None),
source_list=source_list, source_list=source_list,
lyrics_list=lyrics_list, lyrics_list=lyrics_list,
main_artist_list=artist_list, main_artist_list=artist_list,
@ -645,7 +650,7 @@ class Musify(Page):
)) ))
return Song( return Song(
title=song_name, title=clean_song_title(song_name, artist_name=artist_list[0].name if len(artist_list) > 0 else None),
tracksort=tracksort, tracksort=tracksort,
main_artist_list=artist_list, main_artist_list=artist_list,
source_list=source_list source_list=source_list
@ -669,7 +674,7 @@ class Musify(Page):
url = parse_url(source.url) url = parse_url(source.url)
endpoint = self.HOST + "/release/" + url.name_with_id endpoint = self.HOST + "/release/" + url.name_with_id
r = self.connection.get(endpoint) r = self.connection.get(endpoint, name=url.name_with_id)
if r is None: if r is None:
return Album() return Album()
@ -706,7 +711,7 @@ class Musify(Page):
:return: :return:
""" """
r = self.connection.get(f"https://musify.club/{url.source_type.value}/{url.name_with_id}?_pjax=#bodyContent") r = self.connection.get(f"https://musify.club/{url.source_type.value}/{url.name_with_id}?_pjax=#bodyContent", name="artist_attributes_" + url.name_with_id)
if r is None: if r is None:
return Artist() return Artist()
@ -1072,7 +1077,7 @@ class Musify(Page):
"SortOrder.Property": "dateCreated", "SortOrder.Property": "dateCreated",
"SortOrder.IsAscending": False, "SortOrder.IsAscending": False,
"X-Requested-With": "XMLHttpRequest" "X-Requested-With": "XMLHttpRequest"
}) }, name="discography_" + url.name_with_id)
if r is None: if r is None:
return [] return []
soup: BeautifulSoup = BeautifulSoup(r.content, features="html.parser") soup: BeautifulSoup = BeautifulSoup(r.content, features="html.parser")
@ -1123,4 +1128,4 @@ class Musify(Page):
self.LOGGER.warning(f"The source has no audio link. Falling back to {endpoint}.") self.LOGGER.warning(f"The source has no audio link. Falling back to {endpoint}.")
return self.stream_connection.stream_into(endpoint, target, raw_url=True, exclude_headers=["Host"]) return self.stream_connection.stream_into(endpoint, target, raw_url=True, exclude_headers=["Host"], name=desc)

View File

@ -2,8 +2,7 @@ from typing import List, Optional, Type, Tuple
from urllib.parse import urlparse, urlunparse, parse_qs from urllib.parse import urlparse, urlunparse, parse_qs
from enum import Enum from enum import Enum
import sponsorblock import python_sponsorblock
from sponsorblock.errors import HTTPException, NotFoundException
from ..objects import Source, DatabaseObject, Song, Target from ..objects import Source, DatabaseObject, Song, Target
from .abstract import Page from .abstract import Page
@ -63,8 +62,9 @@ class YouTube(SuperYouTube):
) )
# the stuff with the connection is, to ensure sponsorblock uses the proxies, my programm does # the stuff with the connection is, to ensure sponsorblock uses the proxies, my programm does
_sponsorblock_connection: Connection = Connection(host="https://sponsor.ajay.app/") _sponsorblock_connection: Connection = Connection()
self.sponsorblock_client = sponsorblock.Client(session=_sponsorblock_connection.session) self.sponsorblock = python_sponsorblock.SponsorBlock(silent=True, session=_sponsorblock_connection.session)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -344,10 +344,10 @@ class YouTube(SuperYouTube):
segments = [] segments = []
try: try:
segments = self.sponsorblock_client.get_skip_segments(parsed.id) segments = self.sponsorblock.get_segments(parsed.id)
except NotFoundException: except NotFoundException:
self.LOGGER.debug(f"No sponsor found for the video {parsed.id}.") self.LOGGER.debug(f"No sponsor found for the video {parsed.id}.")
except HTTPException as e: except HTTPException as e:
self.LOGGER.warning(f"{e}") self.LOGGER.warning(f"{e}")
return [(segment.start, segment.end) for segment in segments] return [(segment.segment[0], segment.segment[1]) for segment in segments]

View File

@ -2,6 +2,7 @@ from typing import List, Optional
from enum import Enum from enum import Enum
from ...utils.config import youtube_settings, logging_settings from ...utils.config import youtube_settings, logging_settings
from ...utils.string_processing import clean_song_title
from ...objects import Source, DatabaseObject from ...objects import Source, DatabaseObject
from ..abstract import Page from ..abstract import Page
from ...objects import ( from ...objects import (
@ -59,7 +60,7 @@ def parse_run_element(run_element: dict) -> Optional[DatabaseObject]:
if element_type == PageType.SONG or (element_type == PageType.VIDEO and not youtube_settings["youtube_music_clean_data"]) or (element_type == PageType.OFFICIAL_MUSIC_VIDEO and not youtube_settings["youtube_music_clean_data"]): if element_type == PageType.SONG or (element_type == PageType.VIDEO and not youtube_settings["youtube_music_clean_data"]) or (element_type == PageType.OFFICIAL_MUSIC_VIDEO and not youtube_settings["youtube_music_clean_data"]):
source = Source(SOURCE_PAGE, f"https://music.youtube.com/watch?v={element_id}") source = Source(SOURCE_PAGE, f"https://music.youtube.com/watch?v={element_id}")
return Song(title=element_text, source_list=[source]) return Song(title=clean_song_title(element_text), source_list=[source])
if element_type == PageType.ARTIST or (element_type == PageType.CHANNEL and not youtube_settings["youtube_music_clean_data"]): if element_type == PageType.ARTIST or (element_type == PageType.CHANNEL and not youtube_settings["youtube_music_clean_data"]):
source = Source(SOURCE_PAGE, f"https://music.youtube.com/channel/{element_id}") source = Source(SOURCE_PAGE, f"https://music.youtube.com/channel/{element_id}")

View File

@ -3,8 +3,7 @@ from urllib.parse import urlparse, urlunparse, parse_qs
from enum import Enum from enum import Enum
import requests import requests
import sponsorblock import python_sponsorblock
from sponsorblock.errors import HTTPException, NotFoundException
from ...objects import Source, DatabaseObject, Song, Target from ...objects import Source, DatabaseObject, Song, Target
from ..abstract import Page from ..abstract import Page
@ -143,9 +142,8 @@ class SuperYouTube(Page):
) )
# the stuff with the connection is, to ensure sponsorblock uses the proxies, my programm does # the stuff with the connection is, to ensure sponsorblock uses the proxies, my programm does
_sponsorblock_connection: Connection = Connection(host="https://sponsor.ajay.app/") _sponsorblock_connection: Connection = Connection()
self.sponsorblock_client = sponsorblock.Client(session=_sponsorblock_connection.session) self.sponsorblock = python_sponsorblock.SponsorBlock(silent=True, session=_sponsorblock_connection.session)
def get_source_type(self, source: Source) -> Optional[Type[DatabaseObject]]: def get_source_type(self, source: Source) -> Optional[Type[DatabaseObject]]:
_url_type = { _url_type = {
@ -213,10 +211,10 @@ class SuperYouTube(Page):
segments = [] segments = []
try: try:
segments = self.sponsorblock_client.get_skip_segments(parsed.id) segments = self.sponsorblock.get_segments(parsed.id)
except NotFoundException: except NotFoundException:
self.LOGGER.debug(f"No sponsor found for the video {parsed.id}.") self.LOGGER.debug(f"No sponsor found for the video {parsed.id}.")
except HTTPException as e: except HTTPException as e:
self.LOGGER.warning(f"{e}") self.LOGGER.warning(f"{e}")
return [(segment.start, segment.end) for segment in segments] return [(segment.segment[0], segment.segment[1]) for segment in segments]

View File

@ -171,7 +171,7 @@ class YoutubeMusic(SuperYouTube):
def __init__(self, *args, ydl_opts: dict = None, **kwargs): def __init__(self, *args, ydl_opts: dict = None, **kwargs):
self.yt_music_connection: YoutubeMusicConnection = YoutubeMusicConnection( self.yt_music_connection: YoutubeMusicConnection = YoutubeMusicConnection(
logger=self.LOGGER, logger=self.LOGGER,
accept_language="en-US,en;q=0.5" accept_language="en-US,en;q=0.5",
) )
self.credentials: YouTubeMusicCredentials = YouTubeMusicCredentials( self.credentials: YouTubeMusicCredentials = YouTubeMusicCredentials(
api_key=youtube_settings["youtube_music_api_key"], api_key=youtube_settings["youtube_music_api_key"],
@ -212,7 +212,7 @@ class YoutubeMusic(SuperYouTube):
search for: "innertubeApiKey" search for: "innertubeApiKey"
""" """
r = self.yt_music_connection.get("https://music.youtube.com/") r = self.yt_music_connection.get("https://music.youtube.com/", name="youtube_music_index.html", disable_cache=True, enable_cache_readonly=True)
if r is None: if r is None:
return return
@ -232,7 +232,7 @@ class YoutubeMusic(SuperYouTube):
'set_ytc': 'true', 'set_ytc': 'true',
'set_apyt': 'true', 'set_apyt': 'true',
'set_eom': 'false' 'set_eom': 'false'
}) }, disable_cache=True)
if r is None: if r is None:
return return
@ -247,9 +247,9 @@ class YoutubeMusic(SuperYouTube):
# save cookies in settings # save cookies in settings
youtube_settings["youtube_music_consent_cookies"] = cookie_dict youtube_settings["youtube_music_consent_cookies"] = cookie_dict
else: else:
self.yt_music_connection.save(r, "index.html") self.yt_music_connection.save(r, "youtube_music_index.html", no_update_if_valid_exists=True)
r = self.yt_music_connection.get("https://music.youtube.com/", name="index.html") r = self.yt_music_connection.get("https://music.youtube.com/", name="youtube_music_index.html")
if r is None: if r is None:
return return
@ -374,7 +374,8 @@ class YoutubeMusic(SuperYouTube):
}, },
headers={ headers={
"Referer": get_youtube_url(path=f"/search", query=f"q={urlescaped_query}") "Referer": get_youtube_url(path=f"/search", query=f"q={urlescaped_query}")
} },
name=f"search_{search_query}.json"
) )
if r is None: if r is None:
@ -411,7 +412,8 @@ class YoutubeMusic(SuperYouTube):
json={ json={
"browseId": browse_id, "browseId": browse_id,
"context": {**self.credentials.context, "adSignalsInfo": {"params": []}} "context": {**self.credentials.context, "adSignalsInfo": {"params": []}}
} },
name=f"fetch_artist_{browse_id}.json"
) )
if r is None: if r is None:
return artist return artist
@ -454,7 +456,8 @@ class YoutubeMusic(SuperYouTube):
json={ json={
"browseId": browse_id, "browseId": browse_id,
"context": {**self.credentials.context, "adSignalsInfo": {"params": []}} "context": {**self.credentials.context, "adSignalsInfo": {"params": []}}
} },
name=f"fetch_album_{browse_id}.json"
) )
if r is None: if r is None:
return album return album
@ -549,7 +552,12 @@ class YoutubeMusic(SuperYouTube):
return self.download_values_by_url[source.url] return self.download_values_by_url[source.url]
if ydl_res is None: if ydl_res is None:
ydl_res = self.ydl.extract_info(url=source.url, download=False) try:
ydl_res = self.ydl.extract_info(url=source.url, download=False)
except DownloadError as e:
self.not_download[source.hash_url] = e
self.LOGGER.error(f"Couldn't fetch song from {source.url}. {e}")
return {"error": e}
_best_format = _get_best_format(ydl_res.get("formats", [{}])) _best_format = _get_best_format(ydl_res.get("formats", [{}]))
self.download_values_by_url[source.url] = { self.download_values_by_url[source.url] = {
@ -564,7 +572,7 @@ class YoutubeMusic(SuperYouTube):
def download_song_to_target(self, source: Source, target: Target, desc: str = None) -> DownloadResult: def download_song_to_target(self, source: Source, target: Target, desc: str = None) -> DownloadResult:
media = self.fetch_media_url(source) media = self.fetch_media_url(source)
if source.hash_url not in self.not_download: if source.hash_url not in self.not_download and "error" not in media:
result = self.download_connection.stream_into( result = self.download_connection.stream_into(
media["url"], media["url"],
target, target,
@ -577,7 +585,7 @@ class YoutubeMusic(SuperYouTube):
method="GET", method="GET",
) )
else: else:
result = DownloadResult(error_message=str(self.not_download[source.hash_url])) result = DownloadResult(error_message=str(media.get("error") or self.not_download[source.hash_url]))
if result.is_fatal_error: if result.is_fatal_error:
result.merge(super().download_song_to_target(source=source, target=target, desc=desc)) result.merge(super().download_song_to_target(source=source, target=target, desc=desc))

View File

@ -51,14 +51,20 @@ def trace(msg: str):
if not DEBUG_TRACE: if not DEBUG_TRACE:
return return
output("trace: " + msg, BColors.OKBLUE) output(BColors.OKBLUE.value + "trace: " + BColors.ENDC.value + msg)
def request_trace(msg: str):
if not DEBUG_TRACE:
return
output(BColors.OKGREEN.value + "request: " + BColors.ENDC.value + msg)
def object_trace(obj): def object_trace(obj):
if not DEBUG_OBJECT_TRACE: if not DEBUG_OBJECT_TRACE:
return return
appendix = f" called by [{' | '.join(f'{s.function} {Path(s.filename).name}:{str(s.lineno)}' for s in inspect.stack()[1:5])}]" if DEBUG_OBJECT_TRACE_CALLSTACK else "" appendix = f" called by [{' | '.join(f'{s.function} {Path(s.filename).name}:{str(s.lineno)}' for s in inspect.stack()[1:5])}]" if DEBUG_OBJECT_TRACE_CALLSTACK else ""
output("object: " + str(obj) + appendix, BColors.GREY) output("object: " + str(obj) + appendix)
""" """

View File

@ -34,13 +34,15 @@ def unify(string: str) -> str:
return string.lower() return string.lower()
def fit_to_file_system(string: Union[str, Path]) -> Union[str, Path]: def fit_to_file_system(string: Union[str, Path], hidden_ok: bool = False) -> Union[str, Path]:
def fit_string(string: str) -> str: def fit_string(string: str) -> str:
nonlocal hidden_ok
if string == "/": if string == "/":
return "/" return "/"
string = string.strip() string = string.strip()
while string[0] == ".": while string[0] == "." and not hidden_ok:
if len(string) == 0: if len(string) == 0:
return string return string
@ -93,7 +95,7 @@ def clean_song_title(raw_song_title: str, artist_name: Optional[str] = None) ->
break break
substring = raw_song_title[open_bracket_index + 1:close_bracket_index] substring = raw_song_title[open_bracket_index + 1:close_bracket_index]
if any(disallowed_substring in substring for disallowed_substring in DISALLOWED_SUBSTRING_IN_BRACKETS): if any(disallowed_substring in substring.lower() for disallowed_substring in DISALLOWED_SUBSTRING_IN_BRACKETS):
raw_song_title = raw_song_title[:open_bracket_index] + raw_song_title[close_bracket_index + 1:] raw_song_title = raw_song_title[:open_bracket_index] + raw_song_title[close_bracket_index + 1:]
else: else:
start = close_bracket_index + 1 start = close_bracket_index + 1
@ -177,3 +179,8 @@ def match_length(length_1: int | None, length_2: int | None) -> bool:
return True return True
return abs(length_1 - length_2) <= ALLOWED_LENGTH_DISTANCE return abs(length_1 - length_2) <= ALLOWED_LENGTH_DISTANCE
def shorten_display_url(url: str, max_length: int = 150, chars_at_end: int = 4, shorten_string: str = "[...]") -> str:
if len(url) <= max_length + chars_at_end + len(shorten_string):
return url
return url[:max_length] + shorten_string + url[-chars_at_end:]

View File

@ -69,7 +69,7 @@ dependencies = [
"toml~=0.10.2", "toml~=0.10.2",
"typing_extensions~=4.7.1", "typing_extensions~=4.7.1",
"sponsorblock~=0.1.3", "python-sponsorblock~=0.0.0",
"youtube_dl", "youtube_dl",
] ]
dynamic = [ dynamic = [

View File

@ -1,25 +0,0 @@
requests~=2.31.0
mutagen~=1.46.0
musicbrainzngs~=0.7.1
jellyfish~=0.9.0
beautifulsoup4~=4.11.1
pycountry~=24.0.1
python-dateutil~=2.8.2
pandoc~=2.3
SQLAlchemy~=2.0.7
setuptools~=68.2.0
tqdm~=4.65.0
ffmpeg-python~=0.2.0
platformdirs~=4.2.0
transliterate~=1.10.2
sponsorblock~=0.1.3
regex~=2022.9.13
pyffmpeg~=2.4.2.18
ffmpeg-progress-yield~=0.7.8
pathvalidate~=2.5.2
guppy3~=3.1.3
toml~=0.10.2
typing_extensions~=4.7.1
responses~=0.24.1
youtube_dl
merge_args~=0.1.5