From 87579566365b3d354b6ec941618bee256b0df659 Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Thu, 21 Mar 2024 20:44:55 -0700 Subject: [PATCH 01/18] Manually yield for better performance (#648) --- streamrip/client/downloadable.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/streamrip/client/downloadable.py b/streamrip/client/downloadable.py index f5207d9..2685a1b 100644 --- a/streamrip/client/downloadable.py +++ b/streamrip/client/downloadable.py @@ -17,6 +17,7 @@ import aiofiles import aiohttp import m3u8 +import requests from Cryptodome.Cipher import AES, Blowfish from Cryptodome.Util import Counter @@ -41,7 +42,7 @@ class Downloadable(ABC): session: aiohttp.ClientSession url: str extension: str - chunk_size = 1024 + chunk_size = 2**17 _size: Optional[int] = None async def download(self, path: str, callback: Callable[[int], Any]): @@ -71,19 +72,24 @@ def __init__(self, session: aiohttp.ClientSession, url: str, extension: str): self.extension = extension self._size = None - async def _download(self, path: str, callback: Callable[[int], None]): - async with self.session.get(self.url, allow_redirects=True) as response: - response.raise_for_status() - async with aiofiles.open(path, "wb") as file: - async for chunk in response.content.iter_chunked(self.chunk_size): - await file.write(chunk) - # typically a bar.update() + async def _download(self, path: str, callback): + # Attempt to fix async performance issues by manually and infrequently + # yielding to event loop selector + counter = 0 + yield_every = 16 + with open(path, "wb") as file: + with requests.get(self.url, allow_redirects=True, stream=True) as resp: + for chunk in resp.iter_content(chunk_size=self.chunk_size): + file.write(chunk) callback(len(chunk)) + if counter % yield_every == 0: + await asyncio.sleep(0) + counter += 1 class DeezerDownloadable(Downloadable): is_encrypted = re.compile("/m(?:obile|edia)/") - chunk_size = 2048 * 3 + # chunk_size = 2048 * 3 def __init__(self, session: aiohttp.ClientSession, info: dict): logger.debug("Deezer info for downloadable: %s", info) From ab08e54e373988f081c89607262aa7af53e18c24 Mon Sep 17 00:00:00 2001 From: yodatak Date: Wed, 1 May 2024 19:06:42 +0200 Subject: [PATCH 02/18] Fix rip config open command (#671) The rip config --open don't work so lets fix it ;) --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e69d69b..0b5e99c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -49,7 +49,7 @@ body: attributes: label: Config File description: | - Find the config file using `rip config --open` and paste the contents here. + Find the config file using `rip config open` and paste the contents here. Make sure you REMOVE YOUR CREDENTIALS! render: toml placeholder: Contents of config.toml From 6940eae650e6bcdc0fcf0aba639ee7d3d45b1cf4 Mon Sep 17 00:00:00 2001 From: Tanner Hoisington Date: Wed, 1 May 2024 13:07:36 -0400 Subject: [PATCH 03/18] Update README.md (#666) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0cda1b8..c3d07e3 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ rip url https://www.qobuz.com/us-en/album/back-in-black-ac-dc/0886444889841 http Download the album and convert it to `mp3` ```bash -rip url --codec mp3 https://open.qobuz.com/album/0060253780968 +rip url --codec=MP3 https://open.qobuz.com/album/0060253780968 ``` @@ -94,7 +94,7 @@ To set the maximum quality, use the `--max-quality` option to `0, 1, 2, 3, 4`: ```bash -rip url --quality 3 https://tidal.com/browse/album/147569387 +rip url --max-quality=3 https://tidal.com/browse/album/147569387 ``` > Using `4` is generally a waste of space. It is impossible for humans to perceive the between sampling rates higher than 44.1 kHz. It may be useful if you're processing/slowing down the audio. From 61397d616d97b39fcbd48c09701c17e1994902e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 10:37:21 -0700 Subject: [PATCH 04/18] Bump aiohttp from 3.9.2 to 3.9.4 (#669) Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.2 to 3.9.4. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.2...v3.9.4) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 156 ++++++++++++++++++++++++++-------------------------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2fe2b97..4bb1a1b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiodns" @@ -27,87 +27,87 @@ files = [ [[package]] name = "aiohttp" -version = "3.9.2" +version = "3.9.4" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:772fbe371788e61c58d6d3d904268e48a594ba866804d08c995ad71b144f94cb"}, - {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:edd4f1af2253f227ae311ab3d403d0c506c9b4410c7fc8d9573dec6d9740369f"}, - {file = "aiohttp-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cfee9287778399fdef6f8a11c9e425e1cb13cc9920fd3a3df8f122500978292b"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc158466f6a980a6095ee55174d1de5730ad7dec251be655d9a6a9dd7ea1ff9"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54ec82f45d57c9a65a1ead3953b51c704f9587440e6682f689da97f3e8defa35"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abeb813a18eb387f0d835ef51f88568540ad0325807a77a6e501fed4610f864e"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc91d07280d7d169f3a0f9179d8babd0ee05c79d4d891447629ff0d7d8089ec2"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b65e861f4bebfb660f7f0f40fa3eb9f2ab9af10647d05dac824390e7af8f75b7"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04fd8ffd2be73d42bcf55fd78cde7958eeee6d4d8f73c3846b7cba491ecdb570"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3d8d962b439a859b3ded9a1e111a4615357b01620a546bc601f25b0211f2da81"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ceb658afd12b27552597cf9a65d9807d58aef45adbb58616cdd5ad4c258c39e"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0e4ee4df741670560b1bc393672035418bf9063718fee05e1796bf867e995fad"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2dec87a556f300d3211decf018bfd263424f0690fcca00de94a837949fbcea02"}, - {file = "aiohttp-3.9.2-cp310-cp310-win32.whl", hash = "sha256:3e1a800f988ce7c4917f34096f81585a73dbf65b5c39618b37926b1238cf9bc4"}, - {file = "aiohttp-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea510718a41b95c236c992b89fdfc3d04cc7ca60281f93aaada497c2b4e05c46"}, - {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6aaa6f99256dd1b5756a50891a20f0d252bd7bdb0854c5d440edab4495c9f973"}, - {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a27d8c70ad87bcfce2e97488652075a9bdd5b70093f50b10ae051dfe5e6baf37"}, - {file = "aiohttp-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54287bcb74d21715ac8382e9de146d9442b5f133d9babb7e5d9e453faadd005e"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb3d05569aa83011fcb346b5266e00b04180105fcacc63743fc2e4a1862a891"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8534e7d69bb8e8d134fe2be9890d1b863518582f30c9874ed7ed12e48abe3c4"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd9d5b989d57b41e4ff56ab250c5ddf259f32db17159cce630fd543376bd96b"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa6904088e6642609981f919ba775838ebf7df7fe64998b1a954fb411ffb4663"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda42eb410be91b349fb4ee3a23a30ee301c391e503996a638d05659d76ea4c2"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:193cc1ccd69d819562cc7f345c815a6fc51d223b2ef22f23c1a0f67a88de9a72"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b9f1cb839b621f84a5b006848e336cf1496688059d2408e617af33e3470ba204"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d22a0931848b8c7a023c695fa2057c6aaac19085f257d48baa24455e67df97ec"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4112d8ba61fbd0abd5d43a9cb312214565b446d926e282a6d7da3f5a5aa71d36"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4ad4241b52bb2eb7a4d2bde060d31c2b255b8c6597dd8deac2f039168d14fd7"}, - {file = "aiohttp-3.9.2-cp311-cp311-win32.whl", hash = "sha256:ee2661a3f5b529f4fc8a8ffee9f736ae054adfb353a0d2f78218be90617194b3"}, - {file = "aiohttp-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:4deae2c165a5db1ed97df2868ef31ca3cc999988812e82386d22937d9d6fed52"}, - {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6f4cdba12539215aaecf3c310ce9d067b0081a0795dd8a8805fdb67a65c0572a"}, - {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:84e843b33d5460a5c501c05539809ff3aee07436296ff9fbc4d327e32aa3a326"}, - {file = "aiohttp-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8008d0f451d66140a5aa1c17e3eedc9d56e14207568cd42072c9d6b92bf19b52"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61c47ab8ef629793c086378b1df93d18438612d3ed60dca76c3422f4fbafa792"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc71f748e12284312f140eaa6599a520389273174b42c345d13c7e07792f4f57"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1c3a4d0ab2f75f22ec80bca62385db2e8810ee12efa8c9e92efea45c1849133"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a87aa0b13bbee025faa59fa58861303c2b064b9855d4c0e45ec70182bbeba1b"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2cc0d04688b9f4a7854c56c18aa7af9e5b0a87a28f934e2e596ba7e14783192"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1956e3ac376b1711c1533266dec4efd485f821d84c13ce1217d53e42c9e65f08"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:114da29f39eccd71b93a0fcacff178749a5c3559009b4a4498c2c173a6d74dff"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3f17999ae3927d8a9a823a1283b201344a0627272f92d4f3e3a4efe276972fe8"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f31df6a32217a34ae2f813b152a6f348154f948c83213b690e59d9e84020925c"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7a75307ffe31329928a8d47eae0692192327c599113d41b278d4c12b54e1bd11"}, - {file = "aiohttp-3.9.2-cp312-cp312-win32.whl", hash = "sha256:972b63d589ff8f305463593050a31b5ce91638918da38139b9d8deaba9e0fed7"}, - {file = "aiohttp-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:200dc0246f0cb5405c80d18ac905c8350179c063ea1587580e3335bfc243ba6a"}, - {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:158564d0d1020e0d3fe919a81d97aadad35171e13e7b425b244ad4337fc6793a"}, - {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:da1346cd0ccb395f0ed16b113ebb626fa43b7b07fd7344fce33e7a4f04a8897a"}, - {file = "aiohttp-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaa9256de26ea0334ffa25f1913ae15a51e35c529a1ed9af8e6286dd44312554"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1543e7fb00214fb4ccead42e6a7d86f3bb7c34751ec7c605cca7388e525fd0b4"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:186e94570433a004e05f31f632726ae0f2c9dee4762a9ce915769ce9c0a23d89"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d52d20832ac1560f4510d68e7ba8befbc801a2b77df12bd0cd2bcf3b049e52a4"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c45e4e815ac6af3b72ca2bde9b608d2571737bb1e2d42299fc1ffdf60f6f9a1"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa906b9bdfd4a7972dd0628dbbd6413d2062df5b431194486a78f0d2ae87bd55"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:68bbee9e17d66f17bb0010aa15a22c6eb28583edcc8b3212e2b8e3f77f3ebe2a"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4c189b64bd6d9a403a1a3f86a3ab3acbc3dc41a68f73a268a4f683f89a4dec1f"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8a7876f794523123bca6d44bfecd89c9fec9ec897a25f3dd202ee7fc5c6525b7"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d23fba734e3dd7b1d679b9473129cd52e4ec0e65a4512b488981a56420e708db"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b141753be581fab842a25cb319f79536d19c2a51995d7d8b29ee290169868eab"}, - {file = "aiohttp-3.9.2-cp38-cp38-win32.whl", hash = "sha256:103daf41ff3b53ba6fa09ad410793e2e76c9d0269151812e5aba4b9dd674a7e8"}, - {file = "aiohttp-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:328918a6c2835861ff7afa8c6d2c70c35fdaf996205d5932351bdd952f33fa2f"}, - {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5264d7327c9464786f74e4ec9342afbbb6ee70dfbb2ec9e3dfce7a54c8043aa3"}, - {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07205ae0015e05c78b3288c1517afa000823a678a41594b3fdc870878d645305"}, - {file = "aiohttp-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0a1e638cffc3ec4d4784b8b4fd1cf28968febc4bd2718ffa25b99b96a741bd"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d43302a30ba1166325974858e6ef31727a23bdd12db40e725bec0f759abce505"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16a967685907003765855999af11a79b24e70b34dc710f77a38d21cd9fc4f5fe"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fa3ee92cd441d5c2d07ca88d7a9cef50f7ec975f0117cd0c62018022a184308"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b500c5ad9c07639d48615a770f49618130e61be36608fc9bc2d9bae31732b8f"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c07327b368745b1ce2393ae9e1aafed7073d9199e1dcba14e035cc646c7941bf"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc7d6502c23a0ec109687bf31909b3fb7b196faf198f8cff68c81b49eb316ea9"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:07be2be7071723c3509ab5c08108d3a74f2181d4964e869f2504aaab68f8d3e8"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:122468f6fee5fcbe67cb07014a08c195b3d4c41ff71e7b5160a7bcc41d585a5f"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:00a9abcea793c81e7f8778ca195a1714a64f6d7436c4c0bb168ad2a212627000"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a9825fdd64ecac5c670234d80bb52bdcaa4139d1f839165f548208b3779c6c6"}, - {file = "aiohttp-3.9.2-cp39-cp39-win32.whl", hash = "sha256:5422cd9a4a00f24c7244e1b15aa9b87935c85fb6a00c8ac9b2527b38627a9211"}, - {file = "aiohttp-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:7d579dcd5d82a86a46f725458418458fa43686f6a7b252f2966d359033ffc8ab"}, - {file = "aiohttp-3.9.2.tar.gz", hash = "sha256:b0ad0a5e86ce73f5368a164c10ada10504bf91869c05ab75d982c6048217fbf7"}, + {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:76d32588ef7e4a3f3adff1956a0ba96faabbdee58f2407c122dd45aa6e34f372"}, + {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:56181093c10dbc6ceb8a29dfeea1e815e1dfdc020169203d87fd8d37616f73f9"}, + {file = "aiohttp-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7a5b676d3c65e88b3aca41816bf72831898fcd73f0cbb2680e9d88e819d1e4d"}, + {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1df528a85fb404899d4207a8d9934cfd6be626e30e5d3a5544a83dbae6d8a7e"}, + {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f595db1bceabd71c82e92df212dd9525a8a2c6947d39e3c994c4f27d2fe15b11"}, + {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c0b09d76e5a4caac3d27752027fbd43dc987b95f3748fad2b924a03fe8632ad"}, + {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689eb4356649ec9535b3686200b231876fb4cab4aca54e3bece71d37f50c1d13"}, + {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3666cf4182efdb44d73602379a66f5fdfd5da0db5e4520f0ac0dcca644a3497"}, + {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b65b0f8747b013570eea2f75726046fa54fa8e0c5db60f3b98dd5d161052004a"}, + {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1885d2470955f70dfdd33a02e1749613c5a9c5ab855f6db38e0b9389453dce7"}, + {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0593822dcdb9483d41f12041ff7c90d4d1033ec0e880bcfaf102919b715f47f1"}, + {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:47f6eb74e1ecb5e19a78f4a4228aa24df7fbab3b62d4a625d3f41194a08bd54f"}, + {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c8b04a3dbd54de6ccb7604242fe3ad67f2f3ca558f2d33fe19d4b08d90701a89"}, + {file = "aiohttp-3.9.4-cp310-cp310-win32.whl", hash = "sha256:8a78dfb198a328bfb38e4308ca8167028920fb747ddcf086ce706fbdd23b2926"}, + {file = "aiohttp-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:e78da6b55275987cbc89141a1d8e75f5070e577c482dd48bd9123a76a96f0bbb"}, + {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c111b3c69060d2bafc446917534150fd049e7aedd6cbf21ba526a5a97b4402a5"}, + {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efbdd51872cf170093998c87ccdf3cb5993add3559341a8e5708bcb311934c94"}, + {file = "aiohttp-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bfdb41dc6e85d8535b00d73947548a748e9534e8e4fddd2638109ff3fb081df"}, + {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd9d334412961125e9f68d5b73c1d0ab9ea3f74a58a475e6b119f5293eee7ba"}, + {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35d78076736f4a668d57ade00c65d30a8ce28719d8a42471b2a06ccd1a2e3063"}, + {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:824dff4f9f4d0f59d0fa3577932ee9a20e09edec8a2f813e1d6b9f89ced8293f"}, + {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52b8b4e06fc15519019e128abedaeb56412b106ab88b3c452188ca47a25c4093"}, + {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eae569fb1e7559d4f3919965617bb39f9e753967fae55ce13454bec2d1c54f09"}, + {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69b97aa5792428f321f72aeb2f118e56893371f27e0b7d05750bcad06fc42ca1"}, + {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d79aad0ad4b980663316f26d9a492e8fab2af77c69c0f33780a56843ad2f89e"}, + {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d6577140cd7db19e430661e4b2653680194ea8c22c994bc65b7a19d8ec834403"}, + {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:9860d455847cd98eb67897f5957b7cd69fbcb436dd3f06099230f16a66e66f79"}, + {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69ff36d3f8f5652994e08bd22f093e11cfd0444cea310f92e01b45a4e46b624e"}, + {file = "aiohttp-3.9.4-cp311-cp311-win32.whl", hash = "sha256:e27d3b5ed2c2013bce66ad67ee57cbf614288bda8cdf426c8d8fe548316f1b5f"}, + {file = "aiohttp-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d6a67e26daa686a6fbdb600a9af8619c80a332556245fa8e86c747d226ab1a1e"}, + {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c5ff8ff44825736a4065d8544b43b43ee4c6dd1530f3a08e6c0578a813b0aa35"}, + {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d12a244627eba4e9dc52cbf924edef905ddd6cafc6513849b4876076a6f38b0e"}, + {file = "aiohttp-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dcad56c8d8348e7e468899d2fb3b309b9bc59d94e6db08710555f7436156097f"}, + {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7e69a7fd4b5ce419238388e55abd220336bd32212c673ceabc57ccf3d05b55"}, + {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4870cb049f10d7680c239b55428916d84158798eb8f353e74fa2c98980dcc0b"}, + {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2feaf1b7031ede1bc0880cec4b0776fd347259a723d625357bb4b82f62687b"}, + {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939393e8c3f0a5bcd33ef7ace67680c318dc2ae406f15e381c0054dd658397de"}, + {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d2334e387b2adcc944680bebcf412743f2caf4eeebd550f67249c1c3696be04"}, + {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0198ea897680e480845ec0ffc5a14e8b694e25b3f104f63676d55bf76a82f1a"}, + {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e40d2cd22914d67c84824045861a5bb0fb46586b15dfe4f046c7495bf08306b2"}, + {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:aba80e77c227f4234aa34a5ff2b6ff30c5d6a827a91d22ff6b999de9175d71bd"}, + {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:fb68dc73bc8ac322d2e392a59a9e396c4f35cb6fdbdd749e139d1d6c985f2527"}, + {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f3460a92638dce7e47062cf088d6e7663adb135e936cb117be88d5e6c48c9d53"}, + {file = "aiohttp-3.9.4-cp312-cp312-win32.whl", hash = "sha256:32dc814ddbb254f6170bca198fe307920f6c1308a5492f049f7f63554b88ef36"}, + {file = "aiohttp-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:63f41a909d182d2b78fe3abef557fcc14da50c7852f70ae3be60e83ff64edba5"}, + {file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c3770365675f6be220032f6609a8fbad994d6dcf3ef7dbcf295c7ee70884c9af"}, + {file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:305edae1dea368ce09bcb858cf5a63a064f3bff4767dec6fa60a0cc0e805a1d3"}, + {file = "aiohttp-3.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6f121900131d116e4a93b55ab0d12ad72573f967b100e49086e496a9b24523ea"}, + {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b71e614c1ae35c3d62a293b19eface83d5e4d194e3eb2fabb10059d33e6e8cbf"}, + {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:419f009fa4cfde4d16a7fc070d64f36d70a8d35a90d71aa27670bba2be4fd039"}, + {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b39476ee69cfe64061fd77a73bf692c40021f8547cda617a3466530ef63f947"}, + {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b33f34c9c7decdb2ab99c74be6443942b730b56d9c5ee48fb7df2c86492f293c"}, + {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c78700130ce2dcebb1a8103202ae795be2fa8c9351d0dd22338fe3dac74847d9"}, + {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:268ba22d917655d1259af2d5659072b7dc11b4e1dc2cb9662fdd867d75afc6a4"}, + {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:17e7c051f53a0d2ebf33013a9cbf020bb4e098c4bc5bce6f7b0c962108d97eab"}, + {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7be99f4abb008cb38e144f85f515598f4c2c8932bf11b65add0ff59c9c876d99"}, + {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d58a54d6ff08d2547656356eea8572b224e6f9bbc0cf55fa9966bcaac4ddfb10"}, + {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7673a76772bda15d0d10d1aa881b7911d0580c980dbd16e59d7ba1422b2d83cd"}, + {file = "aiohttp-3.9.4-cp38-cp38-win32.whl", hash = "sha256:e4370dda04dc8951012f30e1ce7956a0a226ac0714a7b6c389fb2f43f22a250e"}, + {file = "aiohttp-3.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:eb30c4510a691bb87081192a394fb661860e75ca3896c01c6d186febe7c88530"}, + {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:84e90494db7df3be5e056f91412f9fa9e611fbe8ce4aaef70647297f5943b276"}, + {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d4845f8501ab28ebfdbeab980a50a273b415cf69e96e4e674d43d86a464df9d"}, + {file = "aiohttp-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69046cd9a2a17245c4ce3c1f1a4ff8c70c7701ef222fce3d1d8435f09042bba1"}, + {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b73a06bafc8dcc508420db43b4dd5850e41e69de99009d0351c4f3007960019"}, + {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:418bb0038dfafeac923823c2e63226179976c76f981a2aaad0ad5d51f2229bca"}, + {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71a8f241456b6c2668374d5d28398f8e8cdae4cce568aaea54e0f39359cd928d"}, + {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935c369bf8acc2dc26f6eeb5222768aa7c62917c3554f7215f2ead7386b33748"}, + {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4e48c8752d14ecfb36d2ebb3d76d614320570e14de0a3aa7a726ff150a03c"}, + {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:916b0417aeddf2c8c61291238ce25286f391a6acb6f28005dd9ce282bd6311b6"}, + {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9b6787b6d0b3518b2ee4cbeadd24a507756ee703adbac1ab6dc7c4434b8c572a"}, + {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:221204dbda5ef350e8db6287937621cf75e85778b296c9c52260b522231940ed"}, + {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:10afd99b8251022ddf81eaed1d90f5a988e349ee7d779eb429fb07b670751e8c"}, + {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2506d9f7a9b91033201be9ffe7d89c6a54150b0578803cce5cb84a943d075bc3"}, + {file = "aiohttp-3.9.4-cp39-cp39-win32.whl", hash = "sha256:e571fdd9efd65e86c6af2f332e0e95dad259bfe6beb5d15b3c3eca3a6eb5d87b"}, + {file = "aiohttp-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:7d29dd5319d20aa3b7749719ac9685fbd926f71ac8c77b2477272725f882072d"}, + {file = "aiohttp-3.9.4.tar.gz", hash = "sha256:6ff71ede6d9a5a58cfb7b6fffc83ab5d4a63138276c771ac91ceaaddf5459644"}, ] [package.dependencies] From a677ccb01893982e3cfe1c8c2590485fcd8bde2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 10:37:32 -0700 Subject: [PATCH 05/18] Bump idna from 3.6 to 3.7 (#664) Bumps [idna](https://github.com/kjd/idna) from 3.6 to 3.7. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst) - [Commits](https://github.com/kjd/idna/compare/v3.6...v3.7) --- updated-dependencies: - dependency-name: idna dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4bb1a1b..b170327 100644 --- a/poetry.lock +++ b/poetry.lock @@ -552,13 +552,13 @@ files = [ [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] From 079cef0c2a76946f9770ed61213a0d3f44817f89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 10:37:42 -0700 Subject: [PATCH 06/18] Bump pillow from 10.2.0 to 10.3.0 (#659) Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.2.0 to 10.3.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/10.2.0...10.3.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 139 ++++++++++++++++++++++++++-------------------------- 1 file changed, 70 insertions(+), 69 deletions(-) diff --git a/poetry.lock b/poetry.lock index b170327..1d82298 100644 --- a/poetry.lock +++ b/poetry.lock @@ -817,79 +817,80 @@ windows-curses = {version = ">=2.2.0,<3.0.0", markers = "sys_platform == \"win32 [[package]] name = "pillow" -version = "10.2.0" +version = "10.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, - {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, - {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, - {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, - {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, - {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, - {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, - {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, - {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, - {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, - {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, - {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, - {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, - {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, - {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, - {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, - {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, - {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, + {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"}, + {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"}, + {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"}, + {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"}, + {file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"}, + {file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"}, + {file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"}, + {file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"}, + {file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"}, + {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"}, + {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"}, + {file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"}, + {file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"}, + {file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"}, + {file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"}, + {file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"}, + {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"}, + {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"}, + {file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"}, + {file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"}, + {file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"}, + {file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"}, + {file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"}, + {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"}, + {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"}, + {file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"}, + {file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"}, + {file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"}, + {file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"}, + {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"}, + {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"}, + {file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"}, + {file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"}, + {file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"}, + {file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"}, ] [package.extras] From 868a8fff99accae7fba1a3204e337d25ce88085d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 10:37:52 -0700 Subject: [PATCH 07/18] Bump black from 22.12.0 to 24.3.0 (#653) Bumps [black](https://github.com/psf/black) from 22.12.0 to 24.3.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/22.12.0...24.3.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 57 +++++++++++++++++++++++++++++++++++--------------- pyproject.toml | 2 +- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1d82298..a0fc43b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -188,35 +188,47 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte [[package]] name = "black" -version = "22.12.0" +version = "24.3.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, - {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, - {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, - {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, - {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, - {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, - {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, - {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, - {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, - {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, - {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, - {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, + {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, + {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, + {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, + {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, + {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, + {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, + {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, + {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, + {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, + {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, + {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, + {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, + {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, + {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, + {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, + {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, + {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, + {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, + {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, + {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, + {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, + {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" +packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -1279,6 +1291,17 @@ files = [ {file = "types_Pillow-8.3.11-py3-none-any.whl", hash = "sha256:998189334e616b1dd42c9634669efbf726184039e96e9a23ec95246e0ecff3fc"}, ] +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + [[package]] name = "urllib3" version = "2.1.0" @@ -1424,4 +1447,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.10 <4.0" -content-hash = "e25a8700b08d464c0855a1bb25a15d3daa88387c4cf6977fdd401380ce3b7a7d" +content-hash = "5189d2cd4355c254b15f0f038d4584b146a6297726ccd6d3a0d0174b8cf5616f" diff --git a/pyproject.toml b/pyproject.toml index 7b9b2e8..c70a4ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ click-help-colors = "^0.9.2" types-click = "^7.1.2" types-Pillow = "^8.3.1" ruff = "^0.1" -black = "^22" +black = "^24" isort = "^5.9.3" flake8 = "^3.9.2" setuptools = "^67.4.0" From 527b52cae276d2bcf60f5ddc9252a1308137df76 Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Sat, 11 May 2024 23:17:41 -0700 Subject: [PATCH 08/18] More robust error handling (#678) * Handle no copyright case for tidal * Add default values for get calls * Fix LSP errors * Misc fixes --- pyproject.toml | 16 +++--- streamrip/client/downloadable.py | 82 +++++++++++++++++++++---------- streamrip/client/qobuz.py | 14 +++--- streamrip/media/album.py | 7 ++- streamrip/media/artist.py | 9 +++- streamrip/media/artwork.py | 6 ++- streamrip/media/label.py | 19 +++++-- streamrip/media/playlist.py | 6 ++- streamrip/media/track.py | 47 ++++++++++++++++-- streamrip/metadata/album.py | 39 ++++++++------- streamrip/metadata/playlist.py | 2 +- tests/silence.flac | Bin 28561 -> 29238 bytes 12 files changed, 175 insertions(+), 72 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c70a4ee..63e957a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,12 +10,10 @@ repository = "https://github.com/nathom/streamrip" include = ["src/config.toml"] keywords = ["hi-res", "free", "music", "download"] classifiers = [ - "License :: OSI Approved :: GNU General Public License (GPL)", - "Operating System :: OS Independent", -] -packages = [ - { include = "streamrip" } + "License :: OSI Approved :: GNU General Public License (GPL)", + "Operating System :: OS Independent", ] +packages = [{ include = "streamrip" }] [tool.poetry.scripts] rip = "streamrip.rip:rip" @@ -25,9 +23,9 @@ python = ">=3.10 <4.0" mutagen = "^1.45.1" tomlkit = "^0.7.2" pathvalidate = "^2.4.1" -simple-term-menu = {version = "^1.2.1", platform = 'darwin|linux'} -pick = {version = "^2", platform = 'win32|cygwin'} -windows-curses = {version = "^2.2.0", platform = 'win32|cygwin'} +simple-term-menu = { version = "^1.2.1", platform = 'darwin|linux' } +pick = { version = "^2", platform = 'win32|cygwin' } +windows-curses = { version = "^2.2.0", platform = 'win32|cygwin' } Pillow = ">=9,<11" deezer-py = "1.3.6" pycryptodomex = "^3.10.1" @@ -58,7 +56,7 @@ pytest = "^7.4" [tool.pytest.ini_options] minversion = "6.0" addopts = "-ra -q" -testpaths = [ "tests" ] +testpaths = ["tests"] log_level = "DEBUG" asyncio_mode = 'auto' log_cli = true diff --git a/streamrip/client/downloadable.py b/streamrip/client/downloadable.py index 2685a1b..9eba477 100644 --- a/streamrip/client/downloadable.py +++ b/streamrip/client/downloadable.py @@ -37,13 +37,38 @@ def generate_temp_path(url: str): ) +async def fast_async_download(path, url, headers, callback): + """Synchronous download with yield for every 1MB read. + + Using aiofiles/aiohttp resulted in a yield to the event loop for every 1KB, + which made file downloads CPU-bound. This resulted in a ~10MB max total download + speed. This fixes the issue by only yielding to the event loop for every 1MB read. + """ + chunk_size: int = 2**17 # 131 KB + counter = 0 + yield_every = 8 # 1 MB + with open(path, "wb") as file: # noqa: ASYNC101 + with requests.get( # noqa: ASYNC100 + url, + headers=headers, + allow_redirects=True, + stream=True, + ) as resp: + for chunk in resp.iter_content(chunk_size=chunk_size): + file.write(chunk) + callback(len(chunk)) + if counter % yield_every == 0: + await asyncio.sleep(0) + counter += 1 + + @dataclass(slots=True) class Downloadable(ABC): session: aiohttp.ClientSession url: str extension: str - chunk_size = 2**17 - _size: Optional[int] = None + source: str = "Unknown" + _size_base: Optional[int] = None async def download(self, path: str, callback: Callable[[int], Any]): await self._download(path, callback) @@ -58,6 +83,14 @@ async def size(self) -> int: self._size = int(content_length) return self._size + @property + def _size(self): + return self._size_base + + @_size.setter + def _size(self, v): + self._size_base = v + @abstractmethod async def _download(self, path: str, callback: Callable[[int], None]): raise NotImplementedError @@ -66,35 +99,31 @@ async def _download(self, path: str, callback: Callable[[int], None]): class BasicDownloadable(Downloadable): """Just downloads a URL.""" - def __init__(self, session: aiohttp.ClientSession, url: str, extension: str): + def __init__( + self, + session: aiohttp.ClientSession, + url: str, + extension: str, + source: str | None = None, + ): self.session = session self.url = url self.extension = extension self._size = None + self.source: str = source or "Unknown" async def _download(self, path: str, callback): - # Attempt to fix async performance issues by manually and infrequently - # yielding to event loop selector - counter = 0 - yield_every = 16 - with open(path, "wb") as file: - with requests.get(self.url, allow_redirects=True, stream=True) as resp: - for chunk in resp.iter_content(chunk_size=self.chunk_size): - file.write(chunk) - callback(len(chunk)) - if counter % yield_every == 0: - await asyncio.sleep(0) - counter += 1 + await fast_async_download(path, self.url, self.session.headers, callback) class DeezerDownloadable(Downloadable): is_encrypted = re.compile("/m(?:obile|edia)/") - # chunk_size = 2048 * 3 def __init__(self, session: aiohttp.ClientSession, info: dict): logger.debug("Deezer info for downloadable: %s", info) self.session = session self.url = info["url"] + self.source: str = "deezer" max_quality_available = max( i for i, size in enumerate(info["quality_to_size"]) if size > 0 ) @@ -125,11 +154,9 @@ async def _download(self, path: str, callback): if self.is_encrypted.search(self.url) is None: logger.debug(f"Deezer file at {self.url} not encrypted.") - async with aiofiles.open(path, "wb") as file: - async for chunk in resp.content.iter_chunked(self.chunk_size): - await file.write(chunk) - # typically a bar.update() - callback(len(chunk)) + await fast_async_download( + path, self.url, self.session.headers, callback + ) else: blowfish_key = self._generate_blowfish_key(self.id) logger.debug( @@ -144,10 +171,11 @@ async def _download(self, path: str, callback): buf += data callback(len(data)) + encrypt_chunk_size = 3 * 2048 async with aiofiles.open(path, "wb") as audio: buflen = len(buf) - for i in range(0, buflen, self.chunk_size): - data = buf[i : min(i + self.chunk_size, buflen)] + for i in range(0, buflen, encrypt_chunk_size): + data = buf[i : min(i + encrypt_chunk_size, buflen)] if len(data) >= 2048: decrypted_chunk = ( self._decrypt_chunk(blowfish_key, data[:2048]) @@ -199,6 +227,7 @@ def __init__( restrictions, ): self.session = session + self.source = "tidal" codec = codec.lower() if codec in ("flac", "mqa"): self.extension = "flac" @@ -217,7 +246,7 @@ def __init__( ) self.url = url self.enc_key = encryption_key - self.downloadable = BasicDownloadable(session, url, self.extension) + self.downloadable = BasicDownloadable(session, url, self.extension, "tidal") async def _download(self, path: str, callback): await self.downloadable._download(path, callback) @@ -276,6 +305,7 @@ class SoundcloudDownloadable(Downloadable): def __init__(self, session, info: dict): self.session = session self.file_type = info["type"] + self.source = "soundcloud" if self.file_type == "mp3": self.extension = "mp3" elif self.file_type == "original": @@ -291,7 +321,9 @@ async def _download(self, path, callback): await self._download_original(path, callback) async def _download_original(self, path: str, callback): - downloader = BasicDownloadable(self.session, self.url, "flac") + downloader = BasicDownloadable( + self.session, self.url, "flac", source="soundcloud" + ) await downloader.download(path, callback) self.size = downloader.size engine = converter.FLAC(path) diff --git a/streamrip/client/qobuz.py b/streamrip/client/qobuz.py index b9bf152..c181f9d 100644 --- a/streamrip/client/qobuz.py +++ b/streamrip/client/qobuz.py @@ -197,14 +197,14 @@ async def login(self): self.logged_in = True - async def get_metadata(self, item_id: str, media_type: str): + async def get_metadata(self, item: str, media_type: str): if media_type == "label": - return await self.get_label(item_id) + return await self.get_label(item) c = self.config.session.qobuz params = { "app_id": c.app_id, - f"{media_type}_id": item_id, + f"{media_type}_id": item, # Do these matter? "limit": 500, "offset": 0, @@ -302,9 +302,9 @@ async def get_user_playlists(self, limit: int = 500) -> list[dict]: epoint = "playlist/getUserPlaylists" return await self._paginate(epoint, {}, limit=limit) - async def get_downloadable(self, item_id: str, quality: int) -> Downloadable: + async def get_downloadable(self, item: str, quality: int) -> Downloadable: assert self.secret is not None and self.logged_in and 1 <= quality <= 4 - status, resp_json = await self._request_file_url(item_id, quality, self.secret) + status, resp_json = await self._request_file_url(item, quality, self.secret) assert status == 200 stream_url = resp_json.get("url") @@ -319,9 +319,7 @@ async def get_downloadable(self, item_id: str, quality: int) -> Downloadable: raise NonStreamableError return BasicDownloadable( - self.session, - stream_url, - "flac" if quality > 1 else "mp3", + self.session, stream_url, "flac" if quality > 1 else "mp3", source="qobuz" ) async def _paginate( diff --git a/streamrip/media/album.py b/streamrip/media/album.py index 7d97346..bdc3dc4 100644 --- a/streamrip/media/album.py +++ b/streamrip/media/album.py @@ -59,7 +59,12 @@ async def resolve(self) -> Album | None: ) return None - meta = AlbumMetadata.from_album_resp(resp, self.client.source) + try: + meta = AlbumMetadata.from_album_resp(resp, self.client.source) + except Exception as e: + logger.error(f"Error building album metadata for {id=}: {e}") + return None + if meta is None: logger.error( f"Album {self.id} not available to stream on {self.client.source}", diff --git a/streamrip/media/artist.py b/streamrip/media/artist.py index 0093e06..14d30de 100644 --- a/streamrip/media/artist.py +++ b/streamrip/media/artist.py @@ -190,7 +190,14 @@ async def resolve(self) -> Artist | None: ) return None - meta = ArtistMetadata.from_resp(resp, self.client.source) + try: + meta = ArtistMetadata.from_resp(resp, self.client.source) + except Exception as e: + logger.error( + f"Error building artist metadata: {e}", + ) + return None + albums = [ PendingAlbum(album_id, self.client, self.config, self.db) for album_id in meta.album_ids() diff --git a/streamrip/media/artwork.py b/streamrip/media/artwork.py index 33a98c5..d18747e 100644 --- a/streamrip/media/artwork.py +++ b/streamrip/media/artwork.py @@ -94,7 +94,11 @@ async def download_artwork( if len(downloadables) == 0: return embed_cover_path, saved_cover_path - await asyncio.gather(*downloadables) + try: + await asyncio.gather(*downloadables) + except Exception as e: + logger.error(f"Error downloading artwork: {e}") + return None, None # Update `covers` to reflect the current download state if save_artwork: diff --git a/streamrip/media/label.py b/streamrip/media/label.py index 7eadef8..5aa0e80 100644 --- a/streamrip/media/label.py +++ b/streamrip/media/label.py @@ -1,6 +1,9 @@ import asyncio +import logging from dataclasses import dataclass +from streamrip.exceptions import NonStreamableError + from ..client import Client from ..config import Config from ..db import Database @@ -8,6 +11,8 @@ from .album import PendingAlbum from .media import Media, Pending +logger = logging.getLogger("streamrip") + @dataclass(slots=True) class Label(Media): @@ -57,9 +62,17 @@ class PendingLabel(Pending): config: Config db: Database - async def resolve(self) -> Label: - resp = await self.client.get_metadata(self.id, "label") - meta = LabelMetadata.from_resp(resp, self.client.source) + async def resolve(self) -> Label | None: + try: + resp = await self.client.get_metadata(self.id, "label") + except NonStreamableError as e: + logger.error(f"Error resolving Label: {e}") + return None + try: + meta = LabelMetadata.from_resp(resp, self.client.source) + except Exception as e: + logger.error(f"Error resolving Label: {e}") + return None albums = [ PendingAlbum(album_id, self.client, self.config, self.db) for album_id in meta.album_ids() diff --git a/streamrip/media/playlist.py b/streamrip/media/playlist.py index 2e84a85..858b3c0 100644 --- a/streamrip/media/playlist.py +++ b/streamrip/media/playlist.py @@ -155,7 +155,11 @@ async def resolve(self) -> Playlist | None: ) return None - meta = PlaylistMetadata.from_resp(resp, self.client.source) + try: + meta = PlaylistMetadata.from_resp(resp, self.client.source) + except Exception as e: + logger.error(f"Error creating playlist: {e}") + return None name = meta.name parent = self.config.session.downloads.folder folder = os.path.join(parent, clean_filepath(name)) diff --git a/streamrip/media/track.py b/streamrip/media/track.py index 6e2d88b..32ba531 100644 --- a/streamrip/media/track.py +++ b/streamrip/media/track.py @@ -45,7 +45,32 @@ async def download(self): await self.downloadable.size(), f"Track {self.meta.tracknumber}", ) as callback: - await self.downloadable.download(self.download_path, callback) + try: + await self.downloadable.download(self.download_path, callback) + retry = False + except Exception as e: + logger.error( + f"Error downloading track '{self.meta.title}', retrying: {e}" + ) + retry = True + + if not retry: + return + + with get_progress_callback( + self.config.session.cli.progress_bars, + await self.downloadable.size(), + f"Track {self.meta.tracknumber} (retry)", + ) as callback: + try: + await self.downloadable.download(self.download_path, callback) + except Exception as e: + logger.error( + f"Persistent error downloading track '{self.meta.title}', skipping: {e}" + ) + self.db.set_failed( + self.downloadable.source, "track", self.meta.info.id + ) async def postprocess(self): if self.is_single: @@ -110,7 +135,12 @@ async def resolve(self) -> Track | None: logger.error(f"Track {self.id} not available for stream on {source}: {e}") return None - meta = TrackMetadata.from_resp(self.album, source, resp) + try: + meta = TrackMetadata.from_resp(self.album, source, resp) + except Exception as e: + logger.error(f"Error building track metadata for {id=}: {e}") + return None + if meta is None: logger.error(f"Track {self.id} not available for stream on {source}") self.db.set_failed(source, "track", self.id) @@ -154,7 +184,12 @@ async def resolve(self) -> Track | None: logger.error(f"Error fetching track {self.id}: {e}") return None # Patch for soundcloud - album = AlbumMetadata.from_track_resp(resp, self.client.source) + try: + album = AlbumMetadata.from_track_resp(resp, self.client.source) + except Exception as e: + logger.error(f"Error building album metadata for track {id=}: {e}") + return None + if album is None: self.db.set_failed(self.client.source, "track", self.id) logger.error( @@ -162,7 +197,11 @@ async def resolve(self) -> Track | None: ) return None - meta = TrackMetadata.from_resp(album, self.client.source, resp) + try: + meta = TrackMetadata.from_resp(album, self.client.source, resp) + except Exception as e: + logger.error(f"Error building track metadata for track {id=}: {e}") + return None if meta is None: self.db.set_failed(self.client.source, "track", self.id) diff --git a/streamrip/metadata/album.py b/streamrip/metadata/album.py index de941ec..dcac729 100644 --- a/streamrip/metadata/album.py +++ b/streamrip/metadata/album.py @@ -5,9 +5,9 @@ from dataclasses import dataclass from typing import Optional +from ..filepath_utils import clean_filename from .covers import Covers from .util import get_quality_id, safe_get, typed -from ..filepath_utils import clean_filename PHON_COPYRIGHT = "\u2117" COPYRIGHT = "\u00a9" @@ -64,12 +64,12 @@ def get_copyright(self) -> str | None: def format_folder_path(self, formatter: str) -> str: # Available keys: "albumartist", "title", "year", "bit_depth", "sampling_rate", - # "id", and "albumcomposer", + # "id", and "albumcomposer", none_str = "Unknown" info: dict[str, str | int | float] = { "albumartist": clean_filename(self.albumartist), - "albumcomposer": clean_filename(self.albumcomposer) or none_str, + "albumcomposer": clean_filename(self.albumcomposer or "") or none_str, "bit_depth": self.info.bit_depth or none_str, "id": self.info.id, "sampling_rate": self.info.sampling_rate or none_str, @@ -77,9 +77,9 @@ def format_folder_path(self, formatter: str) -> str: "year": self.year, "container": self.info.container, } - + return formatter.format(**info) - + @classmethod def from_qobuz(cls, resp: dict) -> AlbumMetadata: album = resp.get("title", "Unknown Album") @@ -96,12 +96,12 @@ def from_qobuz(cls, resp: dict) -> AlbumMetadata: else: albumartist = typed(safe_get(resp, "artist", "name"), str) - albumcomposer = typed(safe_get(resp, "composer", "name"), str | None) + albumcomposer = typed(safe_get(resp, "composer", "name", default=""), str) _label = resp.get("label") if isinstance(_label, dict): _label = _label["name"] - label = typed(_label, str | None) - description = typed(resp.get("description") or None, str | None) + label = typed(_label or "", str) + description = typed(resp.get("description", "") or None, str) disctotal = typed( max( track.get("media_number", 1) @@ -115,8 +115,8 @@ def from_qobuz(cls, resp: dict) -> AlbumMetadata: # Non-embedded information cover_urls = Covers.from_qobuz(resp) - bit_depth = typed(resp.get("maximum_bit_depth"), int | None) - sampling_rate = typed(resp.get("maximum_sampling_rate"), int | float | None) + bit_depth = typed(resp.get("maximum_bit_depth", -1), int) + sampling_rate = typed(resp.get("maximum_sampling_rate", -1.0), int | float) quality = get_quality_id(bit_depth, sampling_rate) # Make sure it is non-empty list booklets = typed(resp.get("goodies", None) or None, list | None) @@ -227,14 +227,14 @@ def from_soundcloud(cls, resp) -> AlbumMetadata: safe_get(track, "publisher_metadata", "explicit", default=False), bool, ) - genre = typed(track["genre"], str | None) + genre = typed(track.get("genre"), str | None) genres = [genre] if genre is not None else [] artist = typed(safe_get(track, "publisher_metadata", "artist"), str | None) artist = artist or typed(track["user"]["username"], str) albumartist = artist - date = typed(track["created_at"], str) + date = typed(track.get("created_at"), str) year = date[:4] - label = typed(track["label_name"], str | None) + label = typed(track.get("label_name"), str | None) description = typed(track.get("description"), str | None) album_title = typed( safe_get(track, "publisher_metadata", "album_title"), @@ -284,6 +284,7 @@ def from_tidal(cls, resp) -> AlbumMetadata | None: """ Args: + ---- resp: API response containing album metadata. Returns: AlbumMetadata instance if the album is streamable, otherwise None. @@ -300,12 +301,12 @@ def from_tidal(cls, resp) -> AlbumMetadata | None: # genre not returned by API date = typed(resp.get("releaseDate"), str) year = date[:4] - _copyright = typed(resp.get("copyright"), str) + _copyright = typed(resp.get("copyright", ""), str) artists = typed(resp.get("artists", []), list) albumartist = ", ".join(a["name"] for a in artists) if not albumartist: - albumartist = typed(safe_get(resp, "artist", "name"), str) + albumartist = typed(safe_get(resp, "artist", "name", default=""), str) disctotal = typed(resp.get("numberOfVolumes", 1), int) # label not returned by API @@ -367,7 +368,7 @@ def from_tidal(cls, resp) -> AlbumMetadata | None: ) @classmethod - def from_tidal_playlist_track_resp(cls, resp) -> AlbumMetadata | None: + def from_tidal_playlist_track_resp(cls, resp: dict) -> AlbumMetadata | None: album_resp = resp["album"] streamable = resp.get("allowStreaming", False) if not streamable: @@ -383,11 +384,13 @@ def from_tidal_playlist_track_resp(cls, resp) -> AlbumMetadata | None: else: year = "Unknown Year" - _copyright = typed(resp.get("copyright"), str) + _copyright = typed(resp.get("copyright", ""), str) artists = typed(resp.get("artists", []), list) albumartist = ", ".join(a["name"] for a in artists) if not albumartist: - albumartist = typed(safe_get(resp, "artist", "name"), str) + albumartist = typed( + safe_get(resp, "artist", "name", default="Unknown Albumbartist"), str + ) disctotal = typed(resp.get("volumeNumber", 1), int) # label not returned by API diff --git a/streamrip/metadata/playlist.py b/streamrip/metadata/playlist.py index d113ba6..58a563c 100644 --- a/streamrip/metadata/playlist.py +++ b/streamrip/metadata/playlist.py @@ -37,7 +37,7 @@ def get_soundcloud_id(resp: dict) -> str: def parse_soundcloud_id(item_id: str) -> tuple[str, str]: info = item_id.split("|") assert len(info) == 2 - return tuple(info) + return (info[0], info[1]) @dataclass(slots=True) diff --git a/tests/silence.flac b/tests/silence.flac index 108c437ac4075cafa1c30b07582879c2fa8f0559..302cef910cb7fa83a3f48ea74359cd7584360228 100644 GIT binary patch delta 19 bcmbPupK;q0#tqSdn>T1KW!fAaXeI{$U3&;m delta 13 Ucmdn?gmL11#tqSdn?lUw04`(&!vFvP From 22d6a9b137c578265654d6444fa144b8ffe2e11a Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Tue, 14 May 2024 15:18:58 -0700 Subject: [PATCH 09/18] Implement Disc folders (#679) * Add disc subdirectories * Smoother recovery on broken config --- streamrip/config.py | 5 ++++- streamrip/config.toml | 5 +++-- streamrip/media/track.py | 9 ++++++++- streamrip/metadata/album.py | 2 +- streamrip/rip/cli.py | 4 +++- tests/silence.flac | Bin 29238 -> 33300 bytes tests/test_config.py | 1 + tests/test_config.toml | 3 ++- tests/test_meta.py | 16 ++++++++-------- 9 files changed, 30 insertions(+), 15 deletions(-) diff --git a/streamrip/config.py b/streamrip/config.py index 1ed3ce2..d8ee2cb 100644 --- a/streamrip/config.py +++ b/streamrip/config.py @@ -1,4 +1,5 @@ """A config class that manages arguments between the config file and CLI.""" + import copy import logging import os @@ -15,7 +16,7 @@ APP_DIR = click.get_app_dir("streamrip") os.makedirs(APP_DIR, exist_ok=True) DEFAULT_CONFIG_PATH = os.path.join(APP_DIR, "config.toml") -CURRENT_CONFIG_VERSION = "2.0.3" +CURRENT_CONFIG_VERSION = "2.0.6" @dataclass(slots=True) @@ -181,6 +182,8 @@ class DownloadsConfig: folder: str # Put Qobuz albums in a 'Qobuz' folder, Tidal albums in 'Tidal' etc. source_subdirectories: bool + # Put tracks in an album with 2 or more discs into a subfolder named `Disc N` + disc_subdirectories: bool # Download (and convert) tracks all at once, instead of sequentially. # If you are converting the tracks, or have fast internet, this will # substantially improve processing speed. diff --git a/streamrip/config.toml b/streamrip/config.toml index b420d0d..6bc2eef 100644 --- a/streamrip/config.toml +++ b/streamrip/config.toml @@ -3,7 +3,8 @@ folder = "" # Put Qobuz albums in a 'Qobuz' folder, Tidal albums in 'Tidal' etc. source_subdirectories = false - +# Put tracks in an album with 2 or more discs into a subfolder named `Disc N` +disc_subdirectories = true # Download (and convert) tracks all at once, instead of sequentially. # If you are converting the tracks, or have fast internet, this will # substantially improve processing speed. @@ -186,6 +187,6 @@ max_search_results = 100 [misc] # Metadata to identify this config file. Do not change. -version = "2.0.3" +version = "2.0.6" # Print a message if a new version of streamrip is available check_for_updates = true diff --git a/streamrip/media/track.py b/streamrip/media/track.py index 32ba531..695ad9b 100644 --- a/streamrip/media/track.py +++ b/streamrip/media/track.py @@ -148,11 +148,18 @@ async def resolve(self) -> Track | None: quality = self.config.session.get_source(source).quality downloadable = await self.client.get_downloadable(self.id, quality) + + downloads_config = self.config.session.downloads + if downloads_config.disc_subdirectories and self.album.disctotal > 1: + folder = os.path.join(self.folder, f"Disc {meta.discnumber}") + else: + folder = self.folder + return Track( meta, downloadable, self.config, - self.folder, + folder, self.cover_path, self.db, ) diff --git a/streamrip/metadata/album.py b/streamrip/metadata/album.py index dcac729..aa0fb95 100644 --- a/streamrip/metadata/album.py +++ b/streamrip/metadata/album.py @@ -101,7 +101,7 @@ def from_qobuz(cls, resp: dict) -> AlbumMetadata: if isinstance(_label, dict): _label = _label["name"] label = typed(_label or "", str) - description = typed(resp.get("description", "") or None, str) + description = typed(resp.get("description", ""), str) disctotal = typed( max( track.get("media_number", 1) diff --git a/streamrip/rip/cli.py b/streamrip/rip/cli.py index cde7c09..0e0e517 100644 --- a/streamrip/rip/cli.py +++ b/streamrip/rip/cli.py @@ -153,6 +153,8 @@ def rip(ctx, config_path, folder, no_db, quality, codec, no_progress, verbose): @coro async def url(ctx, urls): """Download content from URLs.""" + if ctx.obj["config"] is None: + return with ctx.obj["config"] as cfg: cfg: Config updates = cfg.session.misc.check_for_updates @@ -237,7 +239,7 @@ def config(): @click.pass_context def config_open(ctx, vim): """Open the config file in a text editor.""" - config_path = ctx.obj["config"].path + config_path = ctx.obj["config_path"] console.print(f"Opening file at [bold cyan]{config_path}") if vim: diff --git a/tests/silence.flac b/tests/silence.flac index 302cef910cb7fa83a3f48ea74359cd7584360228..164e81a0a3777c9036c15249dfc3b58fa057019d 100644 GIT binary patch delta 17 Zcmdn?gmFp>(}IxA8#M3nZwfJ!0{}`n2Ymnl delta 13 UcmbQz!nExP Date: Tue, 14 May 2024 15:45:46 -0700 Subject: [PATCH 10/18] Preserve previous config data after update (#680) * Add config updating mechanism * Update tests * Fix version not updating --- streamrip/config.py | 89 +++++++++++++++++++++-- streamrip/rip/cli.py | 7 +- tests/silence.flac | Bin 33300 -> 50902 bytes tests/test_config.py | 96 +++++++++++++++++++++++++ tests/test_config_old.toml | 142 +++++++++++++++++++++++++++++++++++++ 5 files changed, 329 insertions(+), 5 deletions(-) create mode 100644 tests/test_config_old.toml diff --git a/streamrip/config.py b/streamrip/config.py index d8ee2cb..9705049 100644 --- a/streamrip/config.py +++ b/streamrip/config.py @@ -1,6 +1,7 @@ -"""A config class that manages arguments between the config file and CLI.""" +"""Classes and functions that manage config state.""" import copy +import functools import logging import os import shutil @@ -19,6 +20,10 @@ CURRENT_CONFIG_VERSION = "2.0.6" +class OutdatedConfigError(Exception): + pass + + @dataclass(slots=True) class QobuzConfig: use_auth_token: bool @@ -262,7 +267,7 @@ def from_toml(cls, toml_str: str): # TODO: handle the mistake where Windows people forget to escape backslash toml = parse(toml_str) if (v := toml["misc"]["version"]) != CURRENT_CONFIG_VERSION: # type: ignore - raise Exception( + raise OutdatedConfigError( f"Need to update config from {v} to {CURRENT_CONFIG_VERSION}", ) @@ -367,6 +372,26 @@ def save_file(self): self.file.update_toml() toml_file.write(dumps(self.file.toml)) + @staticmethod + def _update_file(old_path: str, new_path: str): + """Updates the current config based on a newer config `new_toml`.""" + with open(new_path) as new_conf: + new_toml = parse(new_conf.read()) + + toml_set_user_defaults(new_toml) + + with open(old_path) as old_conf: + old_toml = parse(old_conf.read()) + + update_config(old_toml, new_toml) + + with open(old_path, "w") as f: + f.write(dumps(new_toml)) + + @classmethod + def update_file(cls, path: str): + cls._update_file(path, BLANK_CONFIG_PATH) + @classmethod def defaults(cls): return cls(BLANK_CONFIG_PATH) @@ -384,9 +409,65 @@ def set_user_defaults(path: str, /): with open(path) as f: toml = parse(f.read()) + + toml_set_user_defaults(toml) + + with open(path, "w") as f: + f.write(dumps(toml)) + + +def toml_set_user_defaults(toml: TOMLDocument): toml["downloads"]["folder"] = DEFAULT_DOWNLOADS_FOLDER # type: ignore toml["database"]["downloads_path"] = DEFAULT_DOWNLOADS_DB_PATH # type: ignore toml["database"]["failed_downloads_path"] = DEFAULT_FAILED_DOWNLOADS_DB_PATH # type: ignore toml["youtube"]["video_downloads_folder"] = DEFAULT_YOUTUBE_VIDEO_DOWNLOADS_FOLDER # type: ignore - with open(path, "w") as f: - f.write(dumps(toml)) + + +def _get_dict_keys_r(d: dict) -> set[tuple]: + """Get all possible key combinations in nested dicts. + + See tests/test_config.py for example. + """ + keys = d.keys() + ret = set() + for cur in keys: + val = d[cur] + if isinstance(val, dict): + ret.update((cur, *remaining) for remaining in _get_dict_keys_r(val)) + else: + ret.add((cur,)) + return ret + + +def _nested_get(dictionary, *keys, default=None): + return functools.reduce( + lambda d, key: d.get(key, default) if isinstance(d, dict) else default, + keys, + dictionary, + ) + + +def _nested_set(dictionary, *keys, val): + """Nested set. Throws exception if keys are invalid.""" + assert len(keys) > 0 + final = functools.reduce(lambda d, key: d.get(key), keys[:-1], dictionary) + final[keys[-1]] = val + + +def update_config(old_with_data: dict, new_without_data: dict): + """Used to update config when a new config version is detected. + + All data associated with keys that are shared between the old and + new configs are copied from old to new. The remaining keep their default value. + + Assumes that new_without_data contains default config values of the + latest version. + """ + old_keys = _get_dict_keys_r(old_with_data) + new_keys = _get_dict_keys_r(new_without_data) + common = old_keys.intersection(new_keys) + common.discard(("misc", "version")) + + for k in common: + old_val = _nested_get(old_with_data, *k) + _nested_set(new_without_data, *k, val=old_val) diff --git a/streamrip/rip/cli.py b/streamrip/rip/cli.py index 0e0e517..2864ab7 100644 --- a/streamrip/rip/cli.py +++ b/streamrip/rip/cli.py @@ -17,7 +17,7 @@ from rich.traceback import install from .. import __version__, db -from ..config import DEFAULT_CONFIG_PATH, Config, set_user_defaults +from ..config import DEFAULT_CONFIG_PATH, Config, OutdatedConfigError, set_user_defaults from ..console import console from .main import Main @@ -116,6 +116,11 @@ def rip(ctx, config_path, folder, no_db, quality, codec, no_progress, verbose): try: c = Config(config_path) + except OutdatedConfigError as e: + console.print(e) + console.print("Auto-updating config file...") + Config.update_file(config_path) + c = Config(config_path) except Exception as e: console.print( f"Error loading config from [bold cyan]{config_path}[/bold cyan]: {e}\n" diff --git a/tests/silence.flac b/tests/silence.flac index 164e81a0a3777c9036c15249dfc3b58fa057019d..7db44ce17764e5e2e9401d9669557c2006f9d3ef 100644 GIT binary patch delta 24 ecmbQz!gQ^ddBeTr%^NiJtboK6uFdz7&Ex=%Sqj(y delta 14 WcmccC%RHrpX~Vtb%?nnT$pHX03I@Rd diff --git a/tests/test_config.py b/tests/test_config.py index c8a9da2..f4911ef 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,10 +1,14 @@ +import os import shutil import pytest +import tomlkit from streamrip.config import * +from streamrip.config import _get_dict_keys_r, _nested_set SAMPLE_CONFIG = "tests/test_config.toml" +OLD_CONFIG = "tests/test_config_old.toml" # Define a fixture to create a sample ConfigData instance for testing @@ -26,6 +30,98 @@ def sample_config() -> Config: return config +def test_get_keys_r(): + d = { + "key1": { + "key2": { + "key3": 1, + "key4": 1, + }, + "key6": [1, 2], + 5: 1, + } + } + res = _get_dict_keys_r(d) + print(res) + assert res == { + ("key1", "key2", "key3"), + ("key1", "key2", "key4"), + ("key1", "key6"), + ("key1", 5), + } + + +def test_safe_set(): + d = { + "key1": { + "key2": { + "key3": 1, + "key4": 1, + }, + "key6": [1, 2], + 5: 1, + } + } + _nested_set(d, "key1", "key2", "key3", val=5) + assert d == { + "key1": { + "key2": { + "key3": 5, + "key4": 1, + }, + "key6": [1, 2], + 5: 1, + } + } + + +def test_config_update(): + old = { + "downloads": {"folder": "some_path", "use_service": True}, + "qobuz": {"email": "asdf@gmail.com", "password": "test"}, + "legacy_conf": {"something": 1, "other": 2}, + } + new = { + "downloads": {"folder": "", "use_service": False, "keep_artwork": True}, + "qobuz": {"email": "", "password": ""}, + "tidal": {"email": "", "password": ""}, + } + update_config(old, new) + assert new == { + "downloads": {"folder": "some_path", "use_service": True, "keep_artwork": True}, + "qobuz": {"email": "asdf@gmail.com", "password": "test"}, + "tidal": {"email": "", "password": ""}, + } + + +def test_config_throws_outdated(): + with pytest.raises(Exception, match="update"): + _ = Config(OLD_CONFIG) + + +def test_config_file_update(): + tmp_conf = "tests/test_config_old2.toml" + shutil.copy("tests/test_config_old.toml", tmp_conf) + Config._update_file(tmp_conf, SAMPLE_CONFIG) + + with open(tmp_conf) as f: + s = f.read() + toml = tomlkit.parse(s) # type: ignore + + assert toml["downloads"]["folder"] == "old_value" # type: ignore + assert toml["downloads"]["source_subdirectories"] is True # type: ignore + assert toml["downloads"]["concurrency"] is True # type: ignore + assert toml["downloads"]["max_connections"] == 6 # type: ignore + assert toml["downloads"]["requests_per_minute"] == 60 # type: ignore + assert toml["cli"]["text_output"] is True # type: ignore + assert toml["cli"]["progress_bars"] is True # type: ignore + assert toml["cli"]["max_search_results"] == 100 # type: ignore + assert toml["misc"]["version"] == "2.0.6" # type: ignore + assert "YouTubeVideos" in str(toml["youtube"]["video_downloads_folder"]) + # type: ignore + os.remove("tests/test_config_old2.toml") + + def test_sample_config_data_properties(sample_config_data): # Test the properties of ConfigData assert sample_config_data.modified is False # Ensure initial state is not modified diff --git a/tests/test_config_old.toml b/tests/test_config_old.toml new file mode 100644 index 0000000..018c5ea --- /dev/null +++ b/tests/test_config_old.toml @@ -0,0 +1,142 @@ +[downloads] +# Folder where tracks are downloaded to +folder = "old_value" +# Put Qobuz albums in a 'Qobuz' folder, Tidal albums in 'Tidal' etc. +source_subdirectories = true + +[qobuz] +# 1: 320kbps MP3, 2: 16/44.1, 3: 24/<=96, 4: 24/>=96 +quality = 3 + +# Authenticate to Qobuz using auth token? Value can be true/false only +use_auth_token = false +# Enter your userid if the above use_auth_token is set to true, else enter your email +email_or_userid = "old_test@gmail.com" +# Enter your auth token if the above use_auth_token is set to true, else enter the md5 hash of your plaintext password +password_or_token = "old_test_pwd" +# Do not change +app_id = "old_12345" +# Do not change +secrets = ['old_secret1', 'old_secret2'] + +[tidal] +# 0: 256kbps AAC, 1: 320kbps AAC, 2: 16/44.1 "HiFi" FLAC, 3: 24/44.1 "MQA" FLAC +quality = 3 +# This will download videos included in Video Albums. +download_videos = true + +# Do not change any of the fields below +user_id = "old_userid" +country_code = "old_countrycode" +access_token = "old_accesstoken" +refresh_token = "old_refreshtoken" +# Tokens last 1 week after refresh. This is the Unix timestamp of the expiration +# time. If you haven't used streamrip in more than a week, you may have to log +# in again using `rip config --tidal` +token_expiry = "old_tokenexpiry" + +[deezer] +# 0, 1, or 2 +# This only applies to paid Deezer subscriptions. Those using deezloader +# are automatically limited to quality = 1 +quality = 2 +# An authentication cookie that allows streamrip to use your Deezer account +# See https://github.com/nathom/streamrip/wiki/Finding-Your-Deezer-ARL-Cookie +# for instructions on how to find this +arl = "old_testarl" +# This allows for free 320kbps MP3 downloads from Deezer +# If an arl is provided, deezloader is never used +use_deezloader = true +# This warns you when the paid deezer account is not logged in and rip falls +# back to deezloader, which is unreliable +deezloader_warnings = true + +[soundcloud] +# Only 0 is available for now +quality = 0 +# This changes periodically, so it needs to be updated +client_id = "old_clientid" +app_version = "old_appversion" + +[youtube] +# Only 0 is available for now +quality = 0 +# Download the video along with the audio +download_videos = false + +[database] +# Create a database that contains all the track IDs downloaded so far +# Any time a track logged in the database is requested, it is skipped +# This can be disabled temporarily with the --no-db flag +downloads_enabled = true +# Path to the downloads database +downloads_path = "old_downloadspath" +# If a download fails, the item ID is stored here. Then, `rip repair` can be +# called to retry the downloads +failed_downloads_enabled = true +failed_downloads_path = "old_faileddownloadspath" + +# Convert tracks to a codec after downloading them. +[conversion] +enabled = false +# FLAC, ALAC, OPUS, MP3, VORBIS, or AAC +codec = "old_ALAC" +# In Hz. Tracks are downsampled if their sampling rate is greater than this. +# Value of 48000 is recommended to maximize quality and minimize space +sampling_rate = 48000 +# Only 16 and 24 are available. It is only applied when the bit depth is higher +# than this value. +bit_depth = 24 +# Only applicable for lossy codecs +lossy_bitrate = 320 + +# Filter a Qobuz artist's discography. Set to 'true' to turn on a filter. +[qobuz_filters] +# Remove Collectors Editions, live recordings, etc. +extras = false +# Picks the highest quality out of albums with identical titles. +repeats = false +# Remove EPs and Singles +non_albums = false +# Remove albums whose artist is not the one requested +features = false +# Skip non studio albums +non_studio_albums = false +# Only download remastered albums +non_remaster = false + +[artwork] +# Write the image to the audio file +embed = true +# The size of the artwork to embed. Options: thumbnail, small, large, original. +# "original" images can be up to 30MB, and may fail embedding. +# Using "large" is recommended. +embed_size = "old_large" + + +[metadata] +# Sets the value of the 'ALBUM' field in the metadata to the playlist's name. +# This is useful if your music library software organizes tracks based on album name. +set_playlist_to_album = true +# If part of a playlist, sets the `tracknumber` field in the metadata to the track's +# position in the playlist instead of its position in its album +renumber_playlist_tracks = true +# The following metadata tags won't be applied +# See https://github.com/nathom/streamrip/wiki/Metadata-Tag-Names for more info +exclude = [] + +# Changes the folder and file names generated by streamrip. +[filepaths] +# Create folders for single tracks within the downloads directory using the folder_format +# template +add_singles_to_folder = false +# Available keys: "albumartist", "title", "year", "bit_depth", "sampling_rate", +# "id", and "albumcomposer" +folder_format = "old_{albumartist} - {title} ({year}) [{container}] [{bit_depth}B-{sampling_rate}kHz]" +# Available keys: "tracknumber", "artist", "albumartist", "composer", "title", +# and "albumcomposer", "explicit" + +[misc] +# Metadata to identify this config file. Do not change. +version = "0.0.1" +check_for_updates = true From c646c01789937ab30273c83b4a89acdb14d94a0d Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Tue, 14 May 2024 16:02:45 -0700 Subject: [PATCH 11/18] Fix silence.flag test diff (#681) --- tests/test_tagger.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/test_tagger.py b/tests/test_tagger.py index fc1681d..d22be72 100644 --- a/tests/test_tagger.py +++ b/tests/test_tagger.py @@ -1,15 +1,19 @@ +import os +import shutil + import pytest from mutagen.flac import FLAC from util import arun from streamrip.metadata import * -test_flac = "tests/silence.flac" +TEST_FLAC_ORIGINAL = "tests/silence.flac" +TEST_FLAC_COPY = "tests/silence_copy.flac" test_cover = "tests/1x1_pixel.jpg" def wipe_test_flac(): - audio = FLAC(test_flac) + audio = FLAC(TEST_FLAC_COPY) # Remove all tags audio.delete() audio.save() @@ -55,9 +59,10 @@ def sample_metadata() -> TrackMetadata: def test_tag_flac_no_cover(sample_metadata): + shutil.copy(TEST_FLAC_ORIGINAL, TEST_FLAC_COPY) wipe_test_flac() - arun(tag_file(test_flac, sample_metadata, None)) - file = FLAC(test_flac) + arun(tag_file(TEST_FLAC_COPY, sample_metadata, None)) + file = FLAC(TEST_FLAC_COPY) assert file["title"][0] == "testtitle" assert file["album"][0] == "testalbum" assert file["composer"][0] == "testcomposer" @@ -72,12 +77,14 @@ def test_tag_flac_no_cover(sample_metadata): assert file["tracktotal"][0] == "14" assert file["date"][0] == "1998-02-13" assert "purchase_date" not in file, file["purchase_date"] + os.remove(TEST_FLAC_COPY) def test_tag_flac_cover(sample_metadata): + shutil.copy(TEST_FLAC_ORIGINAL, TEST_FLAC_COPY) wipe_test_flac() - arun(tag_file(test_flac, sample_metadata, test_cover)) - file = FLAC(test_flac) + arun(tag_file(TEST_FLAC_COPY, sample_metadata, test_cover)) + file = FLAC(TEST_FLAC_COPY) assert file["title"][0] == "testtitle" assert file["album"][0] == "testalbum" assert file["composer"][0] == "testcomposer" @@ -94,3 +101,4 @@ def test_tag_flac_cover(sample_metadata): with open(test_cover, "rb") as img: assert file.pictures[0].data == img.read() assert "purchase_date" not in file, file["purchase_date"] + os.remove(TEST_FLAC_COPY) From 178168cc68487b394670e824640b95bd1762f3b6 Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Tue, 14 May 2024 16:10:52 -0700 Subject: [PATCH 12/18] Create ruff.yml --- .github/workflows/ruff.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/workflows/ruff.yml diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 0000000..3db4d54 --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,11 @@ +name: Ruff +on: [push, pull_request] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1 + - uses: chartboost/ruff-action@v1 + with: + args: 'format --check' From 54d05e1330abfc6f521853cc0204d9995150e9af Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Tue, 14 May 2024 16:27:41 -0700 Subject: [PATCH 13/18] Fix lints in tests (#682) * Fix lints on tests * Formatting * Formatting --- streamrip/converter.py | 12 ++++++--- streamrip/metadata/__init__.py | 1 + tests/fixtures/clients.py | 2 +- tests/fixtures/util.py | 6 ++--- tests/test_config.py | 46 ++++++++++++++++----------------- tests/test_config_toml_match.py | 3 ++- tests/test_meta.py | 4 +-- tests/test_tagger.py | 9 ++++++- tests/test_track.py | 11 -------- tests/util.py | 6 ++--- 10 files changed, 50 insertions(+), 50 deletions(-) diff --git a/streamrip/converter.py b/streamrip/converter.py index 29efd16..aa4aa2a 100644 --- a/streamrip/converter.py +++ b/streamrip/converter.py @@ -124,10 +124,14 @@ def _gen_command(self): aformat = [] if isinstance(self.sampling_rate, int): - sample_rates = "|".join(str(rate) for rate in SAMPLING_RATES if rate <= self.sampling_rate) + sample_rates = "|".join( + str(rate) for rate in SAMPLING_RATES if rate <= self.sampling_rate + ) aformat.append(f"sample_rates={sample_rates}") elif self.sampling_rate is not None: - raise TypeError(f"Sampling rate must be int, not {type(self.sampling_rate)}") + raise TypeError( + f"Sampling rate must be int, not {type(self.sampling_rate)}" + ) if isinstance(self.bit_depth, int): bit_depths = ["s16p", "s16"] @@ -143,9 +147,9 @@ def _gen_command(self): raise TypeError(f"Bit depth must be int, not {type(self.bit_depth)}") if aformat: - aformat_params = ':'.join(aformat) + aformat_params = ":".join(aformat) command.extend(["-af", f"aformat={aformat_params}"]) - + # automatically overwrite command.extend(["-y", self.tempfile]) diff --git a/streamrip/metadata/__init__.py b/streamrip/metadata/__init__.py index 71da8a8..88f4d77 100644 --- a/streamrip/metadata/__init__.py +++ b/streamrip/metadata/__init__.py @@ -1,4 +1,5 @@ """Manages the information that will be embeded in the audio file.""" + from . import util from .album import AlbumInfo, AlbumMetadata from .artist import ArtistMetadata diff --git a/tests/fixtures/clients.py b/tests/fixtures/clients.py index cbc1206..9ef2eed 100644 --- a/tests/fixtures/clients.py +++ b/tests/fixtures/clients.py @@ -4,8 +4,8 @@ import pytest from util import arun -from streamrip.config import Config from streamrip.client.qobuz import QobuzClient +from streamrip.config import Config @pytest.fixture(scope="session") diff --git a/tests/fixtures/util.py b/tests/fixtures/util.py index 3b1b41a..f4b2f8d 100644 --- a/tests/fixtures/util.py +++ b/tests/fixtures/util.py @@ -9,9 +9,9 @@ def arun(coro): def afor(async_gen): async def _afor(async_gen): - l = [] + item = [] async for item in async_gen: - l.append(item) - return l + item.append(item) + return item return arun(_afor(async_gen)) diff --git a/tests/test_config.py b/tests/test_config.py index f4911ef..23de002 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,8 +4,28 @@ import pytest import tomlkit -from streamrip.config import * -from streamrip.config import _get_dict_keys_r, _nested_set +from streamrip.config import ( + ArtworkConfig, + CliConfig, + Config, + ConfigData, + ConversionConfig, + DatabaseConfig, + DeezerConfig, + DownloadsConfig, + FilepathsConfig, + LastFmConfig, + MetadataConfig, + MiscConfig, + QobuzConfig, + QobuzDiscographyFilterConfig, + SoundcloudConfig, + TidalConfig, + YoutubeConfig, + _get_dict_keys_r, + _nested_set, + update_config, +) SAMPLE_CONFIG = "tests/test_config.toml" OLD_CONFIG = "tests/test_config_old.toml" @@ -242,15 +262,6 @@ def test_sample_config_data_fields(sample_config_data): assert sample_config_data.conversion == test_config.conversion -# def test_config_save_file_called_on_del(sample_config, mocker): -# sample_config.file.set_modified() -# mockf = mocker.Mock() -# -# sample_config.save_file = mockf -# sample_config.__del__() -# mockf.assert_called_once() - - def test_config_update_on_save(): tmp_config_path = "tests/config2.toml" shutil.copy(SAMPLE_CONFIG, tmp_config_path) @@ -264,19 +275,6 @@ def test_config_update_on_save(): assert conf2.session.downloads.folder == "new_folder" -# def test_config_update_on_del(): -# tmp_config_path = "tests/config2.toml" -# shutil.copy(SAMPLE_CONFIG, tmp_config_path) -# conf = Config(tmp_config_path) -# conf.file.downloads.folder = "new_folder" -# conf.file.set_modified() -# del conf -# conf2 = Config(tmp_config_path) -# os.remove(tmp_config_path) -# -# assert conf2.session.downloads.folder == "new_folder" - - def test_config_dont_update_without_set_modified(): tmp_config_path = "tests/config2.toml" shutil.copy(SAMPLE_CONFIG, tmp_config_path) diff --git a/tests/test_config_toml_match.py b/tests/test_config_toml_match.py index 7111bec..2b1c77c 100644 --- a/tests/test_config_toml_match.py +++ b/tests/test_config_toml_match.py @@ -1,7 +1,8 @@ import pytest import tomlkit +from tomlkit.toml_document import TOMLDocument -from streamrip.config import * +from streamrip.config import ConfigData @pytest.fixture() diff --git a/tests/test_meta.py b/tests/test_meta.py index 4516f54..01032ba 100644 --- a/tests/test_meta.py +++ b/tests/test_meta.py @@ -1,6 +1,6 @@ import json -from streamrip.metadata import * +from streamrip.metadata import AlbumMetadata, TrackMetadata with open("tests/qobuz_album_resp.json") as f: qobuz_album_resp = json.load(f) @@ -16,7 +16,7 @@ def test_album_metadata_qobuz(): assert info.quality == 3 assert info.container == "FLAC" assert info.label == "Rhino - Warner Records" - assert info.explicit == False + assert info.explicit is False assert info.sampling_rate == 96 assert info.bit_depth == 24 assert info.booklets is None diff --git a/tests/test_tagger.py b/tests/test_tagger.py index d22be72..1bea481 100644 --- a/tests/test_tagger.py +++ b/tests/test_tagger.py @@ -5,7 +5,14 @@ from mutagen.flac import FLAC from util import arun -from streamrip.metadata import * +from streamrip.metadata import ( + AlbumInfo, + AlbumMetadata, + Covers, + TrackInfo, + TrackMetadata, + tag_file, +) TEST_FLAC_ORIGINAL = "tests/silence.flac" TEST_FLAC_COPY = "tests/silence_copy.flac" diff --git a/tests/test_track.py b/tests/test_track.py index aed0408..26b168f 100644 --- a/tests/test_track.py +++ b/tests/test_track.py @@ -25,19 +25,8 @@ def test_pending_resolve(qobuz_client: QobuzClient): dir = "tests/tests/Fleetwood Mac - Rumours (1977) [FLAC] [24B-96kHz]" assert os.path.isdir(dir) assert os.path.isfile(os.path.join(dir, "cover.jpg")) - # embedded_cover_path aka t.cover_path is - # ./tests/./tests/Fleetwood Mac - Rumours (1977) [FLAC] [24B-96kHz]/ - # __artwork/cover-9202762427033526105.jpg assert os.path.isfile(t.cover_path) assert isinstance(t, Track) assert isinstance(t.downloadable, Downloadable) assert t.cover_path is not None shutil.rmtree(dir) - - -# def test_pending_resolve_mp3(qobuz_client: QobuzClient): -# qobuz_client.config.session.qobuz.quality = 1 -# p = PendingSingle("19512574", qobuz_client, qobuz_client.config) -# t = arun(p.resolve()) -# assert isinstance(t, Track) -# assert False diff --git a/tests/util.py b/tests/util.py index 3b1b41a..107ecc7 100644 --- a/tests/util.py +++ b/tests/util.py @@ -9,9 +9,9 @@ def arun(coro): def afor(async_gen): async def _afor(async_gen): - l = [] + items = [] async for item in async_gen: - l.append(item) - return l + items.append(item) + return items return arun(_afor(async_gen)) From 3e99dad408dc66b9bda0baf79565d72199d8ac05 Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Tue, 14 May 2024 16:37:12 -0700 Subject: [PATCH 14/18] Create pytest.yml --- .github/workflows/pytest.yml | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/pytest.yml diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 0000000..9a83ec6 --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,42 @@ +name: Python Poetry Test + +on: + push: + branches: + - main + - dev + pull_request: + branches: + - main + - dev + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + name: Check out repository code + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' # Specify the Python version + + - name: Install Poetry + uses: snok/install-poetry@v2 + with: + version: 1.3.2 # Specify the Poetry version + + - name: Configure Poetry + run: | + poetry config virtualenvs.create false + + - name: Install dependencies + run: poetry install + + - name: Run tests + run: poetry run pytest + + - name: Success message + if: success() + run: echo "Tests passed successfully!" From 45bf6f6b6563ec5a71d1f9417e3f581c8b4b8501 Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Tue, 14 May 2024 16:41:21 -0700 Subject: [PATCH 15/18] Update pytest.yml --- .github/workflows/pytest.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 9a83ec6..3b0ee92 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -21,15 +21,14 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.10' # Specify the Python version - - - name: Install Poetry - uses: snok/install-poetry@v2 + + - name: Install and configure Poetry + uses: snok/install-poetry@v1 with: - version: 1.3.2 # Specify the Poetry version - - - name: Configure Poetry - run: | - poetry config virtualenvs.create false + version: 1.5.1 + virtualenvs-create: false + virtualenvs-in-project: true + installer-parallel: true - name: Install dependencies run: poetry install From cc9bbddfff38593154a800fb25a2b02689ef788f Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 23 May 2024 21:47:03 +0200 Subject: [PATCH 16/18] Fixed issues with description in the README (#688) --- README.md | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index c3d07e3..8680cf0 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ When you type rip ``` -it should show the main help page. If you have no idea what these mean, or are having other issues installing, check out the [detailed installation instructions](https://github.com/nathom/streamrip/wiki#detailed-installation-instructions). +it should show the main help page. If you have no idea what these mean, or are having other issues installing, check out the [detailed installation instructions](https://github.com/nathom/streamrip/wiki#detailed-installation-instructions). -For Arch Linux users, an AUR package exists. Make sure to install required packages from the AUR before using `makepkg` or use an AUR helper to automatically resolve them. +For Arch Linux users, an AUR package exists. Make sure to install required packages from the AUR before using `makepkg` or use an AUR helper to automatically resolve them. ``` git clone https://aur.archlinux.org/streamrip.git cd streamrip @@ -46,7 +46,6 @@ makepkg -si paru -S streamrip ``` - ### Streamrip beta If you want to get access to the latest and greatest features without waiting for a new release, install @@ -72,17 +71,13 @@ Download multiple albums from Qobuz rip url https://www.qobuz.com/us-en/album/back-in-black-ac-dc/0886444889841 https://www.qobuz.com/us-en/album/blue-train-john-coltrane/0060253764852 ``` - - Download the album and convert it to `mp3` ```bash -rip url --codec=MP3 https://open.qobuz.com/album/0060253780968 +rip --codec mp3 url https://open.qobuz.com/album/0060253780968 ``` - - -To set the maximum quality, use the `--max-quality` option to `0, 1, 2, 3, 4`: +To set the maximum quality, use the `--quality` option to `0, 1, 2, 3, 4`: | Quality ID | Audio Quality | Available Sources | | ---------- | --------------------- | -------------------------------------------- | @@ -92,14 +87,13 @@ To set the maximum quality, use the `--max-quality` option to `0, 1, 2, 3, 4`: | 3 | 24 bit, ≤ 96 kHz | Tidal (MQA), Qobuz, SoundCloud (rarely) | | 4 | 24 bit, ≤ 192 kHz | Qobuz | - ```bash -rip url --max-quality=3 https://tidal.com/browse/album/147569387 +rip --quality 3 url https://tidal.com/browse/album/147569387 ``` -> Using `4` is generally a waste of space. It is impossible for humans to perceive the between sampling rates higher than 44.1 kHz. It may be useful if you're processing/slowing down the audio. +> Using `4` is generally a waste of space. It is impossible for humans to perceive the difference between sampling rates higher than 44.1 kHz. It may be useful if you're processing/slowing down the audio. -Search for albums matching `lil uzi vert` on SoundCloud +Search for playlists matching `rap` on Tidal ```bash rip search tidal playlist 'rap' @@ -125,9 +119,7 @@ For more customization, see the config file rip config open ``` - - -If you're confused about anything, see the help pages. The main help pages can be accessed by typing `rip` by itself in the command line. The help pages for each command can be accessed with the `-help` flag. For example, to see the help page for the `url` command, type +If you're confused about anything, see the help pages. The main help pages can be accessed by typing `rip` by itself in the command line. The help pages for each command can be accessed with the `--help` flag. For example, to see the help page for the `url` command, type ``` rip url --help @@ -139,7 +131,6 @@ rip url --help For more in-depth information about `streamrip`, see the help pages and the [wiki](https://github.com/nathom/streamrip/wiki/). - ## Contributions All contributions are appreciated! You can help out the project by opening an issue @@ -164,7 +155,7 @@ Please document any functions or obscure lines of code. ### The Wiki -To help out `streamrip` users that may be having trouble, consider contributing some information to the wiki. +To help out `streamrip` users that may be having trouble, consider contributing some information to the wiki. Nothing is too obvious and everything is appreciated. ## Acknowledgements @@ -178,8 +169,6 @@ Thanks to Vitiko98, Sorrow446, and DashLt for their contributions to this projec - [Tidal-Media-Downloader](https://github.com/yaronzz/Tidal-Media-Downloader) - [scdl](https://github.com/flyingrub/scdl) - - ## Disclaimer I will not be responsible for how **you** use `streamrip`. By using `streamrip`, you agree to the terms and conditions of the Qobuz, Tidal, and Deezer APIs. From f855572ebf323f3f60fde66fa7c6c7405ddb52eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 12:53:04 -0700 Subject: [PATCH 17/18] --- (#686) updated-dependencies: - dependency-name: requests dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index a0fc43b..3e6344d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1157,13 +1157,13 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "requests" -version = "2.31.0" +version = "2.32.0" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.0-py3-none-any.whl", hash = "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5"}, + {file = "requests-2.32.0.tar.gz", hash = "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8"}, ] [package.dependencies] From 5c6e452679c7a22a54f80b886085c32a6958432a Mon Sep 17 00:00:00 2001 From: Craig Date: Tue, 11 Jun 2024 05:36:10 +0200 Subject: [PATCH 18/18] Fix for [BUG] max() iterable argument is empty #677 (#683) * seems to work - halt progress if this info is missing * Minor fixes --------- Co-authored-by: Nathan Thomas --- streamrip/client/downloadable.py | 9 +++++++-- streamrip/media/playlist.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/streamrip/client/downloadable.py b/streamrip/client/downloadable.py index 9eba477..d778a8f 100644 --- a/streamrip/client/downloadable.py +++ b/streamrip/client/downloadable.py @@ -124,9 +124,14 @@ def __init__(self, session: aiohttp.ClientSession, info: dict): self.session = session self.url = info["url"] self.source: str = "deezer" - max_quality_available = max( + qualities_available = [ i for i, size in enumerate(info["quality_to_size"]) if size > 0 - ) + ] + if len(qualities_available) == 0: + raise NonStreamableError( + "Missing download info. Skipping.", + ) + max_quality_available = max(qualities_available) self.quality = min(info["quality"], max_quality_available) self._size = info["quality_to_size"][self.quality] if self.quality <= 1: diff --git a/streamrip/media/playlist.py b/streamrip/media/playlist.py index 858b3c0..bf3bbbe 100644 --- a/streamrip/media/playlist.py +++ b/streamrip/media/playlist.py @@ -79,7 +79,7 @@ async def resolve(self) -> Track | None: self.client.get_downloadable(self.id, quality), ) except NonStreamableError as e: - logger.error("Error fetching download info for track: %s", e) + logger.error(f"Error fetching download info for track {self.id}: {e}") self.db.set_failed(self.client.source, "track", self.id) return None