From 725553e501731ffb9b334d6af348b79735db6f53 Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Fri, 22 Dec 2023 11:43:37 -0800 Subject: [PATCH 01/18] Add output file feature --- streamrip/rip/cli.py | 24 +++++++++++++++++++++-- streamrip/rip/main.py | 45 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/streamrip/rip/cli.py b/streamrip/rip/cli.py index 2756d38..7102ddc 100644 --- a/streamrip/rip/cli.py +++ b/streamrip/rip/cli.py @@ -266,22 +266,42 @@ def database_browse(ctx, table): help="Automatically download the first search result without showing the menu.", is_flag=True, ) +@click.option( + "-o", + "--output-file", + help="Write search results to a file instead of showing interactive menu.", + type=click.Path(writable=True), +) +@click.option( + "-n", + "--num-results", + help="Maximum number of search results to show", + default=100, + type=click.IntRange(min=1), +) @click.argument("source", required=True) @click.argument("media-type", required=True) @click.argument("query", required=True) @click.pass_context @coro -async def search(ctx, first, source, media_type, query): +async def search(ctx, first, output_file, num_results, source, media_type, query): """Search for content using a specific source. Example: - ------- + rip search qobuz album 'rumours' """ + if first and output_file: + console.print("Cannot choose --first and --output-file!") + return with ctx.obj["config"] as cfg: async with Main(cfg) as main: if first: await main.search_take_first(source, media_type, query) + elif output_file: + await main.search_output_file( + source, media_type, query, output_file, num_results + ) else: await main.search_interactive(source, media_type, query) await main.resolve() diff --git a/streamrip/rip/main.py b/streamrip/rip/main.py index daa0f2e..1b0d2d3 100644 --- a/streamrip/rip/main.py +++ b/streamrip/rip/main.py @@ -1,6 +1,8 @@ import asyncio import logging -import os +import platform + +import aiofiles from .. import db from ..client import Client, DeezerClient, QobuzClient, SoundcloudClient, TidalClient @@ -105,7 +107,6 @@ class Main: with console.status(f"[cyan]Logging into {source}", spinner="dots"): # Log into client using credentials from config await client.login() - # await client.login() assert client.logged_in return client @@ -135,7 +136,7 @@ class Main: return search_results = SearchResults.from_pages(source, media_type, pages) - if os.name == "nt": + if platform.system() == "Windows": # simple term menu not supported for windows from pick import pick choices = pick( @@ -150,7 +151,7 @@ class Main: assert isinstance(choices, list) await self.add_all( - [f"http://{source}.com/{media_type}/{item.id}" for item, i in choices], + [self.dummy_url(source, media_type, item.id) for item, _ in choices], ) else: @@ -175,14 +176,16 @@ class Main: choices = search_results.get_choices(chosen_ind) await self.add_all( [ - f"http://{source}.com/{item.media_type()}/{item.id}" + self.dummy_url(source, item.media_type(), item.id) for item in choices ], ) async def search_take_first(self, source: str, media_type: str, query: str): client = await self.get_logged_in_client(source) - pages = await client.search(media_type, query, limit=1) + with console.status(f"[bold]Searching {source}", spinner="dots"): + pages = await client.search(media_type, query, limit=1) + if len(pages) == 0: console.print(f"[red]No search results found for query {query}") return @@ -190,7 +193,31 @@ class Main: search_results = SearchResults.from_pages(source, media_type, pages) assert len(search_results.results) > 0 first = search_results.results[0] - await self.add(f"http://{source}.com/{first.media_type()}/{first.id}") + url = self.dummy_url(source, first.media_type(), first.id) + await self.add(url) + + async def search_output_file( + self, source: str, media_type: str, query: str, filepath: str, limit: int + ): + client = await self.get_logged_in_client(source) + with console.status(f"[bold]Searching {source}", spinner="dots"): + pages = await client.search(media_type, query, limit=limit) + + if len(pages) == 0: + console.print(f"[red]No search results found for query {query}") + return + + search_results = SearchResults.from_pages(source, media_type, pages) + file_contents = "\n".join( + f"{self.dummy_url(source, item.media_type(), item.id)} [{item.summarize()}]" + for item in search_results.results + ) + async with aiofiles.open(filepath, "w") as f: + await f.write(file_contents) + + console.print( + f"Wrote dummy urls for [purple]{len(search_results.results)}[/purple] results to [cyan]{filepath}!" + ) async def resolve_lastfm(self, playlist_url: str): """Resolve a last.fm playlist.""" @@ -214,6 +241,10 @@ class Main: if playlist is not None: self.media.append(playlist) + @staticmethod + def dummy_url(source, media_type, item_id): + return f"http://{source}.com/{media_type}/{item_id}" + async def __aenter__(self): return self From 71f71d554c10f7daf06a1065920f8d5b60c6a057 Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Sat, 23 Dec 2023 11:09:31 -0800 Subject: [PATCH 02/18] Switch to json for storing search results --- streamrip/metadata/search_results.py | 19 ++++++++++++++---- streamrip/rip/cli.py | 29 +++++++++++++++++++++++++--- streamrip/rip/main.py | 6 ++---- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/streamrip/metadata/search_results.py b/streamrip/metadata/search_results.py index 40c204e..4fa2ac7 100644 --- a/streamrip/metadata/search_results.py +++ b/streamrip/metadata/search_results.py @@ -46,7 +46,7 @@ class ArtistSummary(Summary): @classmethod def from_item(cls, item: dict): - id = item["id"] + id = str(item["id"]) name = ( item.get("name") or item.get("performer", {}).get("name") @@ -80,7 +80,7 @@ class TrackSummary(Summary): @classmethod def from_item(cls, item: dict): - id = item["id"] + id = str(item["id"]) name = item.get("title") or item.get("name") or "Unknown" artist = ( item.get("performer", {}).get("name") @@ -126,7 +126,7 @@ class AlbumSummary(Summary): @classmethod def from_item(cls, item: dict): - id = item["id"] + id = str(item["id"]) name = item.get("title") or "Unknown Title" artist = ( item.get("performer", {}).get("name") @@ -174,7 +174,7 @@ class LabelSummary(Summary): @classmethod def from_item(cls, item: dict): - id = item["id"] + id = str(item["id"]) name = item["name"] return cls(id, name) @@ -273,3 +273,14 @@ class SearchResults: assert ind is not None i = int(ind.group(0)) return self.results[i - 1].preview() + + def as_list(self, source: str) -> list[dict[str, str]]: + return [ + { + "source": source, + "media_type": i.media_type(), + "id": i.id, + "desc": i.summarize(), + } + for i in self.results + ] diff --git a/streamrip/rip/cli.py b/streamrip/rip/cli.py index 7102ddc..695f53b 100644 --- a/streamrip/rip/cli.py +++ b/streamrip/rip/cli.py @@ -1,4 +1,5 @@ import asyncio +import json import logging import os import shutil @@ -147,7 +148,11 @@ async def url(ctx, urls): @rip.command() -@click.argument("path", required=True) +@click.argument( + "path", + required=True, + type=click.Path(exists=True, readable=True, file_okay=True, dir_okay=False), +) @click.pass_context @coro async def file(ctx, path): @@ -159,8 +164,26 @@ async def file(ctx, path): """ with ctx.obj["config"] as cfg: async with Main(cfg) as main: - async with aiofiles.open(path) as f: - await main.add_all([line async for line in f]) + async with aiofiles.open(path, "r") as f: + try: + items = json.loads(await f.read()) + loaded = True + except json.JSONDecodeError: + items = [line async for line in f] + loaded = False + if loaded: + console.print( + f"Detected json file. Loading [yellow]{len(items)}[/yellow] items" + ) + await main.add_all_by_id( + [(i["source"], i["media_type"], i["id"]) for i in items] + ) + else: + console.print( + f"Detected list of urls. Loading [yellow]{len(items)}[/yellow] items" + ) + await main.add_all(items) + await main.resolve() await main.rip() diff --git a/streamrip/rip/main.py b/streamrip/rip/main.py index 1b0d2d3..718a543 100644 --- a/streamrip/rip/main.py +++ b/streamrip/rip/main.py @@ -1,4 +1,5 @@ import asyncio +import json import logging import platform @@ -208,10 +209,7 @@ class Main: return search_results = SearchResults.from_pages(source, media_type, pages) - file_contents = "\n".join( - f"{self.dummy_url(source, item.media_type(), item.id)} [{item.summarize()}]" - for item in search_results.results - ) + file_contents = json.dumps(search_results.as_list(source), indent=4) async with aiofiles.open(filepath, "w") as f: await f.write(file_contents) From bde9f7adecadb37b1e7b290a5cf2471cf91bdc00 Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Sat, 23 Dec 2023 11:14:52 -0800 Subject: [PATCH 03/18] Typing --- streamrip/rip/cli.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/streamrip/rip/cli.py b/streamrip/rip/cli.py index f835cbc..1b4c547 100644 --- a/streamrip/rip/cli.py +++ b/streamrip/rip/cli.py @@ -5,6 +5,7 @@ import os import shutil import subprocess from functools import wraps +from typing import Any import aiofiles import click @@ -176,10 +177,10 @@ async def file(ctx, path): async with Main(cfg) as main: async with aiofiles.open(path, "r") as f: try: - items = json.loads(await f.read()) + items: Any = json.loads(await f.read()) loaded = True except json.JSONDecodeError: - items = [line async for line in f] + items: Any = [line async for line in f] loaded = False if loaded: console.print( From c8ce5847d4cb6d790cfb5c607a867a6e72adc6ca Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Sun, 24 Dec 2023 10:52:27 -0800 Subject: [PATCH 04/18] Fix downloads when flac not available --- streamrip/client/deezer.py | 2 +- streamrip/client/downloadable.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/streamrip/client/deezer.py b/streamrip/client/deezer.py index 9cb1a68..e0f8535 100644 --- a/streamrip/client/deezer.py +++ b/streamrip/client/deezer.py @@ -153,7 +153,7 @@ class DeezerClient(Client): _, format_str = quality_map[quality] dl_info["quality_to_size"] = [ - track_info.get(f"FILESIZE_{format}", 0) for _, format in quality_map + int(track_info.get(f"FILESIZE_{format}", 0)) for _, format in quality_map ] token = track_info["TRACK_TOKEN"] diff --git a/streamrip/client/downloadable.py b/streamrip/client/downloadable.py index 43584d3..566c2fd 100644 --- a/streamrip/client/downloadable.py +++ b/streamrip/client/downloadable.py @@ -89,8 +89,11 @@ class DeezerDownloadable(Downloadable): logger.debug("Deezer info for downloadable: %s", info) self.session = session self.url = info["url"] - self.quality = info["quality"] - self._size = int(info["quality_to_size"][self.quality]) + max_quality_available = max( + i for i, size in enumerate(info["quality_to_size"]) if size > 0 + ) + self.quality = min(info["quality"], max_quality_available) + self._size = info["quality_to_size"][self.quality] if self.quality <= 1: self.extension = "mp3" else: From 732e3381ba9688155a74b1f0b614ca3e87cc6f2c Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Sun, 24 Dec 2023 10:53:57 -0800 Subject: [PATCH 05/18] Remove debug statements --- streamrip/metadata/track_metadata.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/streamrip/metadata/track_metadata.py b/streamrip/metadata/track_metadata.py index 822fe70..9bb6df1 100644 --- a/streamrip/metadata/track_metadata.py +++ b/streamrip/metadata/track_metadata.py @@ -1,6 +1,5 @@ from __future__ import annotations -import json import logging from dataclasses import dataclass from typing import Optional @@ -86,10 +85,6 @@ class TrackMetadata: @classmethod def from_deezer(cls, album: AlbumMetadata, resp) -> TrackMetadata | None: - with open("resp.json", "w") as f: - json.dump(resp, f) - - logger.debug(resp.keys()) track_id = str(resp["id"]) bit_depth = 16 sampling_rate = 44.1 @@ -152,9 +147,6 @@ class TrackMetadata: @classmethod def from_tidal(cls, album: AlbumMetadata, track) -> TrackMetadata: - with open("tidal_track.json", "w") as f: - json.dump(track, f) - title = typed(track["title"], str).strip() item_id = str(track["id"]) version = track.get("version") From 5ad725569dbce7d8335a700a19f29e64ccc602f9 Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Sun, 24 Dec 2023 11:43:03 -0800 Subject: [PATCH 06/18] Misc soundcloud issues fixed --- streamrip/client/downloadable.py | 3 ++- streamrip/client/soundcloud.py | 3 +++ streamrip/media/artwork.py | 5 ++++- streamrip/metadata/album_metadata.py | 5 +++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/streamrip/client/downloadable.py b/streamrip/client/downloadable.py index 43584d3..45f192d 100644 --- a/streamrip/client/downloadable.py +++ b/streamrip/client/downloadable.py @@ -48,7 +48,7 @@ class Downloadable(ABC): await self._download(path, callback) async def size(self) -> int: - if self._size is not None: + if hasattr(self, "_size") and self._size is not None: return self._size async with self.session.head(self.url) as response: @@ -290,6 +290,7 @@ class SoundcloudDownloadable(Downloadable): async def _download_original(self, path: str, callback): downloader = BasicDownloadable(self.session, self.url, "flac") await downloader.download(path, callback) + self.size = downloader.size engine = converter.FLAC(path) await engine.convert(path) diff --git a/streamrip/client/soundcloud.py b/streamrip/client/soundcloud.py index 251adb7..a34954e 100644 --- a/streamrip/client/soundcloud.py +++ b/streamrip/client/soundcloud.py @@ -93,6 +93,9 @@ class SoundcloudClient(Client): } resp, status = await self._api_request(f"search/{media_type}s", params=params) assert status == 200 + if media_type == "track": + for item in resp["collection"]: + item["id"] = self._get_custom_id(item) return [resp] async def get_downloadable(self, item_info: str, _) -> SoundcloudDownloadable: diff --git a/streamrip/media/artwork.py b/streamrip/media/artwork.py index f115486..0de6ad4 100644 --- a/streamrip/media/artwork.py +++ b/streamrip/media/artwork.py @@ -18,7 +18,10 @@ logger = logging.getLogger("streamrip") def remove_artwork_tempdirs(): logger.debug("Removing dirs %s", _artwork_tempdirs) for path in _artwork_tempdirs: - shutil.rmtree(path) + try: + shutil.rmtree(path) + except FileNotFoundError: + pass async def download_artwork( diff --git a/streamrip/metadata/album_metadata.py b/streamrip/metadata/album_metadata.py index f0bc244..ccb4e76 100644 --- a/streamrip/metadata/album_metadata.py +++ b/streamrip/metadata/album_metadata.py @@ -224,7 +224,8 @@ class AlbumMetadata: safe_get(track, "publisher_metadata", "explicit", default=False), bool, ) - genre = typed(track["genre"], str) + genre = typed(track["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 @@ -259,7 +260,7 @@ class AlbumMetadata: album_title, albumartist, year, - genre=[genre], + genre=genres, covers=covers, albumcomposer=None, comment=None, From a0eebfa6b69feb2c02ba01787f62c61c0e4fa4d1 Mon Sep 17 00:00:00 2001 From: Nathan Thomas Date: Sun, 24 Dec 2023 11:46:51 -0800 Subject: [PATCH 07/18] Remove dummy URL methods --- streamrip/rip/main.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/streamrip/rip/main.py b/streamrip/rip/main.py index 6fd5610..4394bfe 100644 --- a/streamrip/rip/main.py +++ b/streamrip/rip/main.py @@ -228,8 +228,7 @@ class Main: search_results = SearchResults.from_pages(source, media_type, pages) assert len(search_results.results) > 0 first = search_results.results[0] - url = self.dummy_url(source, first.media_type(), first.id) - await self.add(url) + await self.add_by_id(source, first.media_type(), first.id) async def search_output_file( self, source: str, media_type: str, query: str, filepath: str, limit: int @@ -248,7 +247,7 @@ class Main: await f.write(file_contents) console.print( - f"Wrote dummy urls for [purple]{len(search_results.results)}[/purple] results to [cyan]{filepath}!" + f"Wrote [purple]{len(search_results.results)}[/purple] results to [cyan]{filepath} as JSON!" ) async def resolve_lastfm(self, playlist_url: str): @@ -273,10 +272,6 @@ class Main: if playlist is not None: self.media.append(playlist) - @staticmethod - def dummy_url(source, media_type, item_id): - return f"http://{source}.com/{media_type}/{item_id}" - async def __aenter__(self): return self From 3051861a27a0b5c18668ae20e692f71ef809385f Mon Sep 17 00:00:00 2001 From: draco Date: Tue, 26 Dec 2023 20:11:58 +0100 Subject: [PATCH 08/18] run `poetry lock --no-update` --- poetry.lock | 486 +++------------------------------------------------- 1 file changed, 28 insertions(+), 458 deletions(-) diff --git a/poetry.lock b/poetry.lock index cd25bf4..af8873c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiodns" version = "3.1.1" description = "Simple DNS resolver for asyncio" -category = "main" optional = false python-versions = "*" files = [ @@ -19,7 +18,6 @@ pycares = ">=4.0.0" name = "aiofiles" version = "0.7.0" description = "File support for asyncio." -category = "main" optional = false python-versions = ">=3.6,<4.0" files = [ @@ -31,7 +29,6 @@ files = [ name = "aiohttp" version = "3.9.1" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -128,7 +125,6 @@ speedups = ["Brotli", "aiodns", "brotlicffi"] name = "aiolimiter" version = "1.1.0" description = "asyncio rate limiter, a leaky bucket implementation" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -140,7 +136,6 @@ files = [ name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -151,23 +146,10 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" -[[package]] -name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, -] - [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = "*" files = [ @@ -179,7 +161,6 @@ files = [ name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -191,7 +172,6 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -206,63 +186,10 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -[[package]] -name = "autodoc" -version = "0.5.0" -description = "Autodoc Python implementation." -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "autodoc-0.5.0.tar.gz", hash = "sha256:c4387c5a0f1c09b055bb2e384542ee1e016542f313b2a33d904ca77f0460ded3"}, -] - -[package.dependencies] -decorator = "*" -webtest = "*" - -[[package]] -name = "babel" -version = "2.13.1" -description = "Internationalization utilities" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, - {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, -] - -[package.dependencies] -setuptools = {version = "*", markers = "python_version >= \"3.12\""} - -[package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] - -[[package]] -name = "beautifulsoup4" -version = "4.12.2" -description = "Screen-scraping library" -category = "dev" -optional = false -python-versions = ">=3.6.0" -files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, -] - -[package.dependencies] -soupsieve = ">1.2" - -[package.extras] -html5lib = ["html5lib"] -lxml = ["lxml"] - [[package]] name = "black" version = "22.12.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -297,7 +224,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -309,7 +235,6 @@ files = [ name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -374,7 +299,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -474,7 +398,6 @@ files = [ name = "cleo" version = "2.1.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -490,7 +413,6 @@ rapidfuzz = ">=3.0.0,<4.0.0" name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -505,7 +427,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "click-help-colors" version = "0.9.4" description = "Colorization of help messages in Click" -category = "main" optional = false python-versions = "*" files = [ @@ -523,7 +444,6 @@ dev = ["mypy", "pytest"] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -535,7 +455,6 @@ files = [ name = "crashtest" version = "0.4.1" description = "Manage Python errors with ease" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -543,23 +462,10 @@ files = [ {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, ] -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] - [[package]] name = "deezer-py" version = "1.3.6" description = "A wrapper for all Deezer's APIs" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -570,23 +476,10 @@ files = [ [package.dependencies] requests = "*" -[[package]] -name = "docutils" -version = "0.17.1" -description = "Docutils -- Python Documentation Utilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, - {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, -] - [[package]] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -601,7 +494,6 @@ test = ["pytest (>=6)"] name = "flake8" version = "3.9.2" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -618,7 +510,6 @@ pyflakes = ">=2.3.0,<2.4.0" name = "frozenlist" version = "1.4.0" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -689,7 +580,6 @@ files = [ name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -697,23 +587,10 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -725,7 +602,6 @@ files = [ name = "iso8601" version = "2.1.0" description = "Simple module to parse ISO 8601 dates" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -737,7 +613,6 @@ files = [ name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -751,29 +626,10 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib" plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - [[package]] name = "m3u8" version = "0.9.0" description = "Python m3u8 parser" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -788,7 +644,6 @@ iso8601 = "*" name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -809,81 +664,10 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] -[[package]] -name = "markupsafe" -version = "2.1.3" -description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - [[package]] name = "mccabe" version = "0.6.1" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = "*" files = [ @@ -895,7 +679,6 @@ files = [ name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -907,7 +690,6 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -991,7 +773,6 @@ files = [ name = "mutagen" version = "1.47.0" description = "read and write audio tags for many formats" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1003,7 +784,6 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1015,7 +795,6 @@ files = [ name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1027,7 +806,6 @@ files = [ name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1039,7 +817,6 @@ files = [ name = "pathvalidate" version = "2.5.2" description = "pathvalidate is a Python library to sanitize/validate a string such as filenames/file-paths/etc." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1054,7 +831,6 @@ test = ["allpairspy", "click", "faker", "pytest (>=6.0.1)", "pytest-discord (>=0 name = "pick" version = "2.2.0" description = "Pick an option in the terminal with a simple GUI" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1069,7 +845,6 @@ windows-curses = {version = ">=2.2.0,<3.0.0", markers = "sys_platform == \"win32 name = "pillow" version = "9.5.0" description = "Python Imaging Library (Fork)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1149,7 +924,6 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "platformdirs" version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1165,7 +939,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1181,7 +954,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pycares" version = "4.4.0" description = "Python interface for c-ares" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1248,7 +1020,6 @@ idna = ["idna (>=2.1)"] name = "pycodestyle" version = "2.7.0" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1260,7 +1031,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1272,7 +1042,6 @@ files = [ name = "pycryptodomex" version = "3.19.0" description = "Cryptographic library for Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1314,7 +1083,6 @@ files = [ name = "pyflakes" version = "2.3.1" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1326,7 +1094,6 @@ files = [ name = "pygments" version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1342,7 +1109,6 @@ windows-terminal = ["colorama (>=0.4.6)"] name = "pytest" version = "7.4.3" description = "pytest: simple powerful testing with Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1365,7 +1131,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.21.1" description = "Pytest support for asyncio" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1384,7 +1149,6 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy name = "pytest-mock" version = "3.12.0" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1402,7 +1166,6 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "rapidfuzz" version = "3.5.2" description = "rapid fuzzy string matching" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1505,7 +1268,6 @@ full = ["numpy"] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1527,7 +1289,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -1542,11 +1303,36 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "ruff" +version = "0.1.9" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e6a212f436122ac73df851f0cf006e0c6612fe6f9c864ed17ebefce0eff6a5fd"}, + {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:28d920e319783d5303333630dae46ecc80b7ba294aeffedf946a02ac0b7cc3db"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:104aa9b5e12cb755d9dce698ab1b97726b83012487af415a4512fedd38b1459e"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e63bf5a4a91971082a4768a0aba9383c12392d0d6f1e2be2248c1f9054a20da"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d0738917c203246f3e275b37006faa3aa96c828b284ebfe3e99a8cb413c8c4b"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69dac82d63a50df2ab0906d97a01549f814b16bc806deeac4f064ff95c47ddf5"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2aec598fb65084e41a9c5d4b95726173768a62055aafb07b4eff976bac72a592"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:744dfe4b35470fa3820d5fe45758aace6269c578f7ddc43d447868cfe5078bcb"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479ca4250cab30f9218b2e563adc362bd6ae6343df7c7b5a7865300a5156d5a6"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:aa8344310f1ae79af9ccd6e4b32749e93cddc078f9b5ccd0e45bd76a6d2e8bb6"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:837c739729394df98f342319f5136f33c65286b28b6b70a87c28f59354ec939b"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e6837202c2859b9f22e43cb01992373c2dbfeae5c0c91ad691a4a2e725392464"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:331aae2cd4a0554667ac683243b151c74bd60e78fb08c3c2a4ac05ee1e606a39"}, + {file = "ruff-0.1.9-py3-none-win32.whl", hash = "sha256:8151425a60878e66f23ad47da39265fc2fad42aed06fb0a01130e967a7a064f4"}, + {file = "ruff-0.1.9-py3-none-win_amd64.whl", hash = "sha256:c497d769164df522fdaf54c6eba93f397342fe4ca2123a2e014a5b8fc7df81c7"}, + {file = "ruff-0.1.9-py3-none-win_arm64.whl", hash = "sha256:0e17f53bcbb4fff8292dfd84cf72d767b5e146f009cccd40c2fad27641f8a7a9"}, + {file = "ruff-0.1.9.tar.gz", hash = "sha256:b041dee2734719ddbb4518f762c982f2e912e7f28b8ee4fe1dee0b15d1b6e800"}, +] + [[package]] name = "setuptools" version = "67.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1563,7 +1349,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "simple-term-menu" version = "1.6.3" description = "A Python package which creates simple interactive menus on the command line." -category = "main" optional = false python-versions = "~=3.5" files = [ @@ -1571,165 +1356,10 @@ files = [ {file = "simple_term_menu-1.6.3-py3-none-any.whl", hash = "sha256:27bf782f3a415b93ac310f70ace8c2db403b4058411ad6f8144ed4a1f34a7e57"}, ] -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] - -[[package]] -name = "soupsieve" -version = "2.5" -description = "A modern CSS selector implementation for Beautiful Soup." -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, -] - -[[package]] -name = "sphinx" -version = "4.5.0" -description = "Python documentation generator" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, - {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, -] - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=1.3" -colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.18" -imagesize = "*" -Jinja2 = ">=2.3" -packaging = "*" -Pygments = ">=2.0" -requests = ">=2.5.0" -snowballstemmer = ">=1.1" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.4" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, - {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.1" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, - {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1741,7 +1371,6 @@ files = [ name = "tomlkit" version = "0.7.2" description = "Style preserving TOML library" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1753,7 +1382,6 @@ files = [ name = "tqdm" version = "4.66.1" description = "Fast, Extensible Progress Meter" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1774,7 +1402,6 @@ telegram = ["requests"] name = "types-click" version = "7.1.8" description = "Typing stubs for click" -category = "dev" optional = false python-versions = "*" files = [ @@ -1786,7 +1413,6 @@ files = [ name = "types-pillow" version = "8.3.11" description = "Typing stubs for Pillow" -category = "dev" optional = false python-versions = "*" files = [ @@ -1798,7 +1424,6 @@ files = [ name = "urllib3" version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1811,64 +1436,10 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "waitress" -version = "2.1.2" -description = "Waitress WSGI server" -category = "dev" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "waitress-2.1.2-py3-none-any.whl", hash = "sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a"}, - {file = "waitress-2.1.2.tar.gz", hash = "sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba"}, -] - -[package.extras] -docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] -testing = ["coverage (>=5.0)", "pytest", "pytest-cover"] - -[[package]] -name = "webob" -version = "1.8.7" -description = "WSGI request and response object" -category = "dev" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" -files = [ - {file = "WebOb-1.8.7-py2.py3-none-any.whl", hash = "sha256:73aae30359291c14fa3b956f8b5ca31960e420c28c1bec002547fb04928cf89b"}, - {file = "WebOb-1.8.7.tar.gz", hash = "sha256:b64ef5141be559cfade448f044fa45c2260351edcb6a8ef6b7e00c7dcef0c323"}, -] - -[package.extras] -docs = ["Sphinx (>=1.7.5)", "pylons-sphinx-themes"] -testing = ["coverage", "pytest (>=3.1.0)", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "webtest" -version = "3.0.0" -description = "Helper to test WSGI applications" -category = "dev" -optional = false -python-versions = ">=3.6, <4" -files = [ - {file = "WebTest-3.0.0-py3-none-any.whl", hash = "sha256:2a001a9efa40d2a7e5d9cd8d1527c75f41814eb6afce2c3d207402547b1e5ead"}, - {file = "WebTest-3.0.0.tar.gz", hash = "sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb"}, -] - -[package.dependencies] -beautifulsoup4 = "*" -waitress = ">=0.8.5" -WebOb = ">=1.2" - -[package.extras] -docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.8)"] -tests = ["PasteDeploy", "WSGIProxy2", "coverage", "pyquery", "pytest", "pytest-cov"] - [[package]] name = "windows-curses" version = "2.3.2" description = "Support for the standard curses module on Windows" -category = "main" optional = false python-versions = "*" files = [ @@ -1892,7 +1463,6 @@ files = [ name = "yarl" version = "1.9.3" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1995,4 +1565,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.10 <4.0" -content-hash = "2395d1cc4b03aba9e939f3bdb4f4e90ba757227c43acbad2d310ed891fe5c307" +content-hash = "65ad2363e3856aed80ba967c8d53e5435c8f4230e27e1178bf832866fb91e1c3" From 399139e6b492a0c011b1e202383d3de7d3086db4 Mon Sep 17 00:00:00 2001 From: draco Date: Tue, 26 Dec 2023 21:20:34 +0100 Subject: [PATCH 09/18] fix import paths/names --- tests/test_qobuz_client.py | 8 ++++---- tests/test_tagger.py | 2 +- tests/test_track.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_qobuz_client.py b/tests/test_qobuz_client.py index dd849d4..efed40a 100644 --- a/tests/test_qobuz_client.py +++ b/tests/test_qobuz_client.py @@ -4,9 +4,9 @@ import pytest from util import afor, arun from streamrip.config import Config -from streamrip.downloadable import BasicDownloadable -from streamrip.exceptions import MissingCredentials -from streamrip.qobuz_client import QobuzClient +from streamrip.client.downloadable import BasicDownloadable +from streamrip.exceptions import MissingCredentialsError +from streamrip.client.qobuz import QobuzClient logger = logging.getLogger("streamrip") @@ -18,7 +18,7 @@ def client(qobuz_client): def test_client_raises_missing_credentials(): c = Config.defaults() - with pytest.raises(MissingCredentials): + with pytest.raises(MissingCredentialsError): arun(QobuzClient(c).login()) diff --git a/tests/test_tagger.py b/tests/test_tagger.py index 7a138b7..4ff7118 100644 --- a/tests/test_tagger.py +++ b/tests/test_tagger.py @@ -3,7 +3,7 @@ from mutagen.flac import FLAC from util import arun from streamrip.metadata import * -from streamrip.tagger import tag_file +from streamrip.metadata.tagger import tag_file test_flac = "tests/silence.flac" test_cover = "tests/1x1_pixel.jpg" diff --git a/tests/test_track.py b/tests/test_track.py index 4aad9f5..eae8561 100644 --- a/tests/test_track.py +++ b/tests/test_track.py @@ -3,9 +3,9 @@ import shutil from util import arun -from streamrip.downloadable import Downloadable -from streamrip.qobuz_client import QobuzClient -from streamrip.track import PendingSingle, Track +from streamrip.client.downloadable import Downloadable +from streamrip.client.qobuz import QobuzClient +from streamrip.media.track import PendingSingle, Track def test_pending_resolve(qobuz_client: QobuzClient): From bfac933203ac27932ff0a26b1e2121b85bc24628 Mon Sep 17 00:00:00 2001 From: draco Date: Tue, 26 Dec 2023 21:35:21 +0100 Subject: [PATCH 10/18] add dummy database as missing arg --- tests/test_track.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_track.py b/tests/test_track.py index eae8561..d11b164 100644 --- a/tests/test_track.py +++ b/tests/test_track.py @@ -6,11 +6,12 @@ from util import arun from streamrip.client.downloadable import Downloadable from streamrip.client.qobuz import QobuzClient from streamrip.media.track import PendingSingle, Track +import streamrip.db as db def test_pending_resolve(qobuz_client: QobuzClient): qobuz_client.config.session.downloads.folder = "./tests" - p = PendingSingle("19512574", qobuz_client, qobuz_client.config) + p = PendingSingle("19512574", qobuz_client, qobuz_client.config, db.Database(db.Dummy(), db.Dummy())) t = arun(p.resolve()) dir = "tests/Fleetwood Mac - Rumours (1977) [FLAC] [24B-96kHz]" assert os.path.isdir(dir) From 2094e521e2942388b80077ce94f2a34da88fa1ea Mon Sep 17 00:00:00 2001 From: draco Date: Tue, 26 Dec 2023 21:55:46 +0100 Subject: [PATCH 11/18] update test asserts The asserts for this test had wrong data, updated to use the correct data. Test now passes. --- tests/test_meta.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/test_meta.py b/tests/test_meta.py index 00d5457..ca5e426 100644 --- a/tests/test_meta.py +++ b/tests/test_meta.py @@ -48,17 +48,16 @@ def test_album_metadata_qobuz(): def test_track_metadata_qobuz(): a = AlbumMetadata.from_qobuz(qobuz_track_resp["album"]) t = TrackMetadata.from_qobuz(a, qobuz_track_resp) - assert t.title == "Dreams (2001 Remaster)" info = t.info - assert info.id == "19512574" + assert info.id == "216020864" assert info.quality == 3 assert info.bit_depth == 24 assert info.sampling_rate == 96 assert info.work is None - assert t.title == "Dreams (2001 Remaster)" + assert t.title == "Water Tower" assert t.album == a - assert t.artist == "Fleetwood Mac" - assert t.tracknumber == 2 + assert t.artist == "The Mountain Goats" + assert t.tracknumber == 9 assert t.discnumber == 1 - assert t.composer == None + assert t.composer == "John Darnielle" From de88ab64593eb3fab297530b93a96f5bfb7f8d8b Mon Sep 17 00:00:00 2001 From: draco Date: Tue, 26 Dec 2023 22:17:32 +0100 Subject: [PATCH 12/18] update config to match new defaults and names --- tests/test_config.toml | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/tests/test_config.toml b/tests/test_config.toml index 8737919..95e5e91 100644 --- a/tests/test_config.toml +++ b/tests/test_config.toml @@ -12,10 +12,10 @@ concurrency = true # If you have very fast internet, you will benefit from a higher value, # A value that is too high for your bandwidth may cause slowdowns # Set to -1 for no limit -max_connections = 3 -# Max number of API requests to handle per minute +max_connections = 6 +# Max number of API requests per source to handle per minute # Set to -1 for no limit -requests_per_minute = -1 +requests_per_minute = 60 [qobuz] # 1: 320kbps MP3, 2: 16/44.1, 3: 24/<=96, 4: 24/>=96 @@ -81,11 +81,13 @@ download_videos = false # The path to download the videos to video_downloads_folder = "videodownloadsfolder" -# This stores a list of item IDs so that repeats are not downloaded. [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 = "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 @@ -141,8 +143,9 @@ saved_max_width = -1 # 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 -# Replaces the original track's tracknumber with it's position in the playlist -new_playlist_tracknumbers = 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 = [] @@ -162,7 +165,7 @@ track_format = "{tracknumber}. {artist} - {title}{explicit}" restrict_characters = false # Truncate the filename if it is greater than this number of characters # Setting this to false may cause downloads to fail on some systems -truncate_to = 200 +truncate_to = 120 # Last.fm playlists are downloaded by searching for the titles of the tracks [lastfm] @@ -170,11 +173,15 @@ truncate_to = 200 source = "qobuz" # If no results were found with the primary source, the item is searched for # on this one. -fallback_source = "deezer" +fallback_source = "" -[theme] -# Options: "dainty" or "plain" -progress_bar = "dainty" +[cli] +# Print "Downloading {Album name}" etc. to screen +text_output = true +# Show resolve, download progress bars +progress_bars = true +# The maximum number of search results to show in the interactive menu +max_search_results = 100 [misc] # Metadata to identify this config file. Do not change. From 8424219bfc33dc06eabeba7c541ad994eccc678d Mon Sep 17 00:00:00 2001 From: draco Date: Tue, 26 Dec 2023 22:25:06 +0100 Subject: [PATCH 13/18] update test values to match new config, now passes --- tests/test_config.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index de68344..e37623c 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -44,8 +44,8 @@ def test_sample_config_data_fields(sample_config_data): folder="test_folder", source_subdirectories=False, concurrency=True, - max_connections=3, - requests_per_minute=-1, + max_connections=6, + requests_per_minute=60, ), qobuz=QobuzConfig( use_auth_token=False, @@ -81,13 +81,13 @@ def test_sample_config_data_fields(sample_config_data): quality=0, download_videos=False, ), - lastfm=LastFmConfig(source="qobuz", fallback_source="deezer"), + lastfm=LastFmConfig(source="qobuz", fallback_source=""), filepaths=FilepathsConfig( add_singles_to_folder=False, folder_format="{albumartist} - {title} ({year}) [{container}] [{bit_depth}B-{sampling_rate}kHz]", track_format="{tracknumber}. {artist} - {title}{explicit}", restrict_characters=False, - truncate_to=200, + truncate_to=120, ), artwork=ArtworkConfig( embed=True, @@ -109,6 +109,11 @@ def test_sample_config_data_fields(sample_config_data): non_studio_albums=False, non_remaster=False, ), + cli=CliConfig( + text_output=False, + progress_bars=False, + max_search_results=100, + ), database=DatabaseConfig( downloads_enabled=True, downloads_path="downloadspath", From 5d34eda4b902aca7024ec5b6eb91a30447c8f6ad Mon Sep 17 00:00:00 2001 From: draco Date: Tue, 26 Dec 2023 23:26:13 +0100 Subject: [PATCH 14/18] correct imports, new embed cover path, passes Qobuz client and fixture had to be imported The embedded cover is saved somewhere else now. Uses the path from the object itself to check if it gets downloaded, unsure if this is static or dynamic. --- tests/fixtures/clients.py | 2 +- tests/test_track.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/fixtures/clients.py b/tests/fixtures/clients.py index b604fee..6dd4d6d 100644 --- a/tests/fixtures/clients.py +++ b/tests/fixtures/clients.py @@ -5,7 +5,7 @@ import pytest from util import arun from streamrip.config import Config -from streamrip.qobuz_client import QobuzClient +from streamrip.client.qobuz import QobuzClient @pytest.fixture(scope="session") diff --git a/tests/test_track.py b/tests/test_track.py index d11b164..159c99b 100644 --- a/tests/test_track.py +++ b/tests/test_track.py @@ -7,16 +7,20 @@ from streamrip.client.downloadable import Downloadable from streamrip.client.qobuz import QobuzClient from streamrip.media.track import PendingSingle, Track import streamrip.db as db +from fixtures.clients import qobuz_client def test_pending_resolve(qobuz_client: QobuzClient): qobuz_client.config.session.downloads.folder = "./tests" p = PendingSingle("19512574", qobuz_client, qobuz_client.config, db.Database(db.Dummy(), db.Dummy())) t = arun(p.resolve()) - dir = "tests/Fleetwood Mac - Rumours (1977) [FLAC] [24B-96kHz]" + 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")) - assert os.path.isfile(os.path.join(dir, "embed_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 From 267c48962ed0e7a9990242d59668394b729c6129 Mon Sep 17 00:00:00 2001 From: draco Date: Tue, 26 Dec 2023 23:55:30 +0100 Subject: [PATCH 15/18] fix import, argument order, afor->arun, album ID The album ID was the ID of the artist, not the album. Fixed now. The tests in this file now pass. --- tests/test_qobuz_client.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_qobuz_client.py b/tests/test_qobuz_client.py index efed40a..b444ce6 100644 --- a/tests/test_qobuz_client.py +++ b/tests/test_qobuz_client.py @@ -7,6 +7,8 @@ from streamrip.config import Config from streamrip.client.downloadable import BasicDownloadable from streamrip.exceptions import MissingCredentialsError from streamrip.client.qobuz import QobuzClient +from fixtures.clients import qobuz_client + logger = logging.getLogger("streamrip") @@ -23,7 +25,7 @@ def test_client_raises_missing_credentials(): def test_client_get_metadata(client): - meta = arun(client.get_metadata("lzpf67e8f4h1a", "album")) + meta = arun(client.get_metadata("s9nzkwg2rh1nc", "album")) assert meta["title"] == "I Killed Your Dog" assert len(meta["tracks"]["items"]) == 16 assert meta["maximum_bit_depth"] == 24 @@ -38,18 +40,19 @@ def test_client_get_downloadable(client): def test_client_search_limit(client): - res = client.search("rumours", "album", limit=5) + res = client.search("album", "rumours", limit=5) total = 0 - for r in afor(res): + for r in arun(res): total += len(r["albums"]["items"]) assert total == 5 def test_client_search_no_limit(client): - res = client.search("rumours", "album", limit=None) + # Setting no limit has become impossible because `limit: int` now + res = client.search("album", "rumours", limit=10000) correct_total = 0 total = 0 - for r in afor(res): + for r in arun(res): total += len(r["albums"]["items"]) correct_total = max(correct_total, r["albums"]["total"]) assert total == correct_total From 8bb5fc9b2351a72290a15f4fea0eceaaed9adeb9 Mon Sep 17 00:00:00 2001 From: draco Date: Wed, 27 Dec 2023 00:03:48 +0100 Subject: [PATCH 16/18] import TrackInfo, AlbumInfo, add silence.flac Maybe should change `metadata/__init__.py` instead so the line `from streamrip.metadata import *` becomes sufficient? --- tests/silence.flac | Bin 0 -> 15004 bytes tests/test_tagger.py | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 tests/silence.flac diff --git a/tests/silence.flac b/tests/silence.flac new file mode 100644 index 0000000000000000000000000000000000000000..fe5f88bebabaec4d5dc190c0f4b9052f1bb3b040 GIT binary patch literal 15004 zcmeI1&u`mkddIzst_dy=)oahWc3=1Z32qC=%EN^us{z5x{#;akYQwDQlLO@-nC>0=pnU( zVm!!<`*3>dpHM(LfU+)ns zZ#bbYGzIo?c%t69n(K42*$wCzs}aSNv9pyEANkwuwkg#4O0&67H!7JzMCr+c8G>x3 zJQbBGQJJD*b`ci&LS2Z)2cETZ+fFNOLknms9uPcYIgZyEb&?M-7z<$*uBh4XpSG&p zz*F!j9A9AWA0=T0>eTe-OR?vTyI9a!E=?YvFQ=HYEz+DQpqOSH(-UfRig!)gD-JU}%w*8J{iE#ZwrohfbR8N7OnyB@1 zrZ+}YCQ1xgmoD)(-K;DR4z=BoH~8X>oiqSqfWb5EauYW zz%x5-S;+^4RPGd}(WzSBn}Y9|D$Cg`Z&*ZC6D$JVL2TW$m+PIJ=nOo?a(nnOi!HgU zhR2`P7x``6QB!_uK9jh$x%stNC{4HcQAIGDd3w9kE)YI>o9BYl_Vy z)GIBTxo;cg_U?L^nO!Im^2?}raT@40Y3gp1f<^9@H!77zG5^G)(3CPHo$XxC)uOa> z^Bq1wtY+aIIX7%`w zZ{b~q@bu&HKo_w)P?HIT00#esVy*U@e@%Kq;M_o_&z!=4h01r_#b zz5tdWw-4eE;-A&hmpNN@5FN>^#M`EbXr@tI`C1|kM6&|RlqPEJ9_d(KYj{=Rx@v?( zT)+1dpFV6<+i$3~tYPAAz%dc4PSo4oE}Xwql+^-Q|IkN~4(bQ}J#ZhXSS-!Nhs}v5 z+wXnCGKI+fpbxXGb|aH_4om&SX*WQdGKyiu+0~O0F@GufOR+<8h_=VZF8{+1$7W7S zut@os%0}a7o??~5xSo6(yjuR88&+A|f=jM%T}q<1Ee}GSx!0e=+9&FcY3F1VoKsut zSZIMO5?5u!tmQ<0b0f17_GH%b#{)I|4kDzVPQEoo9gDEYMmC~Bol+%gorV=nAMHS? zc$B(#AxnoBQempfHyL#yqg7NQZzi8UXRy|ge3;6gh%y!twtX6oIk0nw-m@!Iy8ecc zDqW0j$)=E)Xf&!Jk!h4W#gC~f&7#Gf7~6HXQ)TXID%eu7=-;^9T2qEuPeyq-zG=>p zVb3ffADOl^U*ZIATWN!{bah6n$++7Mm|(TTYMsv%3n5!Hjt6X*fERg1tW`891yyGq zkmVd{I*6{TQ{xXO!!0-(6_wSgDp;9_PjdO~d#3kLA`>(n^r0}ZTtltYG`bKLl#UNJ zUO8^RWEWQ5WZ^%}E*c#X7Am5BL|hM~<-v4^*R%#y=QrcVt?WWpRav5#u6{`#Ls21Q z$%~H(pHBG?ST@}H5$gS-&pIK}OpNMu7(RU+*(jkllGlnwtOHfUtfG?fKA?!hr5 z(o*@$`k9+lIvvRd*6r$-O6=fmr&y)+11v87LOrJU7lVFI=UoZ&W||xpi&8Ns8{pTKf8}*~t3AOucQdFgL2S*Fs|D$NNsk{6;o7hjXTdmR}pfH{@1q!4w$g6DQNf9QkeC9LPWj2?!*_5;%E_79sa44GN;B-K;9C`NlX=Ayk~eDmkQFM#=N$~S$};yp$iE4@W_=4* zx$Q%Z)aKrdowwznpEh6MZo)` zZHN#^yc1>k3Cj}sqca*#j0{+I2mrL~uRg)FRuHsE{PKOm%8AVH83F$%pO0}`BeoE<2O!yS~^l59ZFcT)O3eKT-NSchMX&A7JkRZ8G*Fe~D7I%a?7!kGJ z(5Jv3I?IhJC$MZZ3jc5dFh^4tlTUXul@+4=G7NZQP-QrlEan>!2dm1kN;hDI7Z8>` z<^1O4E#OU-$%5BhD$X)%%0t4Bu}&`q$tL?4zKW=ml24?R({*HYZWDgApjbkDZ#1T_`W+4j=u#F3P~Yn$fqQB06)Y>cVnE`iVr+6kx5=BjH{N zt8*Lf%A{D`=t&BIid9@IC1}i9obb z0?c18F=rg|E?5rdrnRQ7H zrv2t2R2R+BUHAqwdfx%|1Z+i|u-(Y_yN8R0~4#l|X7Q?*Ict!!xEx&IB} z>(=B}iVMj*7w6*Edz^?{cU3mt-kw9qIeR#nd_XAxD(i1xGXpGtuanNKFk0sRv%2Yh z;T_gWK_AnuTbhw_SE? z6_uW1=0KyYYSC&0`WV0m4Df6FcdLkV`g+g{IMSPkSaRM_)mDk~0>*tvqL_2GkY%L2 zojVHV-FAoomUS89;Ute2e}QFMGi*?`5EVGn_P?T(b~g~!G_`5kms^=em8zbCNIqcg z?e{IdQGq&wnvPKZJk;e6TYDx1C{74xPD ztpSxK@*NTgnTF!!#6Iw1Lyf?%S8VC>!rjH>p|JBUd;^qkIh>cH<9`QgB}`5TPt;8L z{Oow=Lw|6tE`X7|2Ty0RGZ38)9Bk-q9s50+TGzm>lhADkC#s@k^%DB%?q zAG1+mD>szNM?Cns3dd}Z&E>qvA{I_OQ>mpPYFQ3( z$L10&j*z5z^3Z#jE`{0n%V~4J&pEF*s*#$Ze)s#TM>!T>LC{hq?OxAq#vB8Ztjcc! zP=FaE22o^*FULc>DXRx`WKt=WT94EK*f|t~igsWlbK-W2sRrh9QjShqBEK&`s=6l{ z+yq{vF7BL^ZicT+5!V2g5))0Pq4%N6IRMm!pN<>G1>IUzVw&oa(%@UzSTvHomC3$i z!uc=|!QW7ZKT82(BMa!=@0`(1=$wHQp(X-0}As(O&@+@4(2QZAw(PVC$cK}~HpId3nWfzO(P%q3(E z8@GvT>72bH%CuNdUk7Y`9)ULc^TZ^w00=q|^!YuQr^Hypnv+dh>q4uR1?YwD3AU1r z0}BC1PG%cK4-W41Uo!$}PMvy;l z*d7+fF;z9_E|fULr|sKaO-)K9*VPlN!3$LnkM3m&l;G5!1X9Ffs*@9*ZIJ2%CWA| zD+J^%-wT4oK!Y~mx?>u2>khj^i=1EyTbILA6OGy&jGOvx)1CFGtrU!J#9_%Dm8jt* zc;us>0`5ZBItT8z4L#jrdk_Ub`1#G)#aHX!AD{C6FnRIquYR*R2ICiq4r+BiHF=xh zRpxda#$4KswG8kv#6N}(O3FXoD8IGAH&%8Aj6_0V(O`4MbP?KX_Sfe$bel|V&foq4 z4Nb!&e`8G|Sr_WeoHxdRszBy1!UUW(jJx`Wm$?}@w#AD6_WZl?56loWYy^43k{^0z z>&XEu+!+=i^kF6j!x_NPX%tal6g5lu57XD0;FX@T?YHJ~q7e)F-|i9D06-KtZdYa? z1iy8e94cKf9>?i!4W>HdTh~;W$N*yA22XNO7jv{R@W{?^9atnf*oPju0Ify^s11&i zVH|qjZj zX1?3pd<7A$VM5uroE@WW467kQHB4GywQM-v9$vd0ZzF2C=6&6hX?<}I`V7-@hB*SRv+R$C9`5F9Wi!a^O!pw7|JT)321L zyQ`HZn5n;i+_~&<`!v&RfF%>M2_EFSyvQwoH1^Bc@l5^oQL>l5i?`waJ=qev#t&{~ zVO(n&v0epZh%y>8Em(Tk9bQ5Jq?H(zh4Gqu$o(cHkbqB=NA8rzk{`ibXaJ{oht`ji zAUQsljg9gRed=bQFm0WgYPZUKJWB3OkEWdGrJ^H<0&1Op}w zYX;aGq$%{!ebb4-<96>NEBX1YnwWe510S4#Wznud1Gl|mVA8z&2~5&9_rk0PnBe64 z#ee_fzm2^Uo)pv_fjgIh=nsV`wfyrG-T}`ncSd|K=J)donf*p)10`GDJ%`*PXg~75YdBxxBY_zvo7zz=t$G2 zHuo6ixDOMX4dn>MpV~#ajiJejKm+^~W(2usH5y(7hFq5H3?kVm6tHO=!QP)rvS~SI zh;v79kE9}WshKL_rg1}kXJz>g54}T_X1VQq=THd>x}Y@}0k!QC^lGzlTU-S2pJ+i- zmP^l0A32Z{c7BIZuXt8do}%k+Yb830ZBgEN12gh&qbPS)PAXcX@#%aK@PftAU@)5A zhlyqT0EPy&W#wtR^D(d{VAPSos*|t=q-x21PA3D28X`)GF3eDjAOE9J8T%Qj5V}N(= z-0DSmJj7|WbUF+0pnx9k&=J^%p5Cp39x&C^5Vr(>z-BAO%yPLjbS5w*f-~KBA@32=&O&^pZr>&@W0yfg9B#OP zKm)}PX*l>K4B1MN>MI!hHPtKQAshZHrl|v^_LQUDYy*6&=QjPn8$L!7@;-0R@?G9?dMBp=)2av Q0Zp4k>Uc22+)G{mA3X%zCIA2c literal 0 HcmV?d00001 diff --git a/tests/test_tagger.py b/tests/test_tagger.py index 4ff7118..71b09c5 100644 --- a/tests/test_tagger.py +++ b/tests/test_tagger.py @@ -4,6 +4,8 @@ from util import arun from streamrip.metadata import * from streamrip.metadata.tagger import tag_file +from streamrip.metadata.track_metadata import TrackInfo +from streamrip.metadata.album_metadata import AlbumInfo test_flac = "tests/silence.flac" test_cover = "tests/1x1_pixel.jpg" From 0ba450dcfef01cbaaa3c7d1f0d0d289c89858510 Mon Sep 17 00:00:00 2001 From: draco Date: Wed, 27 Dec 2023 00:34:07 +0100 Subject: [PATCH 17/18] close client session on teardown, fixes error messages --- tests/fixtures/clients.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/clients.py b/tests/fixtures/clients.py index 6dd4d6d..cbc1206 100644 --- a/tests/fixtures/clients.py +++ b/tests/fixtures/clients.py @@ -21,4 +21,6 @@ def qobuz_client(): client = QobuzClient(config) arun(client.login()) - return client + yield client + + arun(client.session.close()) From 23d2c37486d182dbcf080cd96ca58af6e32200f0 Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 27 Dec 2023 01:12:26 +0100 Subject: [PATCH 18/18] smaller silence.flac --- tests/silence.flac | Bin 15004 -> 8928 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/silence.flac b/tests/silence.flac index fe5f88bebabaec4d5dc190c0f4b9052f1bb3b040..f5b15124da99401fe1c31a7f1dce04c6d6889374 100644 GIT binary patch delta 1106 zcmbV}Nl?>J7{_0hrcEhop&($DQc%!ArHe=%J5HL08Z9lRLB(6a6sy#hHi$aoRNVC> z?mAv@#~UJUCvn#s?#l)D<%Iiksqdw3qs}CeGbL|QBxAa-*BR^V$z-tSWi z%$E|4S&(}Ke_bu_^C++>9&L@G?mV1N;GFSjcX0}BF5MD&Y{*J-iq0H{OWYPFiC zHCmk^Q~X1Agu z7){f<3|(GkW}dakSY-WuB!35gyr<3ry#{qfU}k{pF+z`}E`UPVs0vdCbW#R}6Qqh# zYiMl-4D=2II7SdSNvKpL3DYa!Iw19`tRf3TWzPz#%`FCNd$OZbQ|#*Q%MtnqOKc7C zRkSvDcwTV|IJAdKgrOQ{YUb}wd z=B?Xz?%uoq@X_NZPoE{9zj*oT^_#cv-hcS`>GKz4h5;fi2A_X5(?c_yBneWD%rLwS zPC`$riY!zXGfN$8$u?WtH3nBlXLlc6Y!e1^8sh!h+!Fi4(XWx4qV~-!`46>K|K+Br oA+w)i_koVU;Q0}HFb#bCQnqj9wA8(JfRj{r3q|(COyvb%0o%}1uK)l5 delta 69 wcmaFhI;V7k6BC2RL>Coy1|Vo;P}sbYg_&jZ0hTY!_=(8}Sb{ch;n0x+0A27JqW}N^