diff --git a/streamrip/clients.py b/streamrip/clients.py index c6432e8..79ae50c 100644 --- a/streamrip/clients.py +++ b/streamrip/clients.py @@ -152,7 +152,7 @@ class QobuzClient(ClientInterface): self.session = requests.Session() # for multithreading adapter = requests.adapters.HTTPAdapter(pool_connections=100, pool_maxsize=100) - self.session.mount('https://', adapter) + self.session.mount("https://", adapter) self.session.headers.update( { "User-Agent": AGENT, @@ -377,6 +377,11 @@ class DeezerClient(ClientInterface): def __init__(self): self.session = requests.Session() + # for multithreading + adapter = requests.adapters.HTTPAdapter(pool_connections=300, pool_maxsize=300) + self.session.mount("https://", adapter) + + # no login required self.logged_in = True def search(self, query: str, media_type: str = "album", limit: int = 200) -> dict: @@ -392,9 +397,6 @@ class DeezerClient(ClientInterface): # TODO: more robust url sanitize query = query.replace(" ", "+") - if media_type.endswith("s"): - media_type = media_type[:-1] - # TODO: use limit parameter response = self.session.get(f"{DEEZER_BASE}/search/{media_type}?q={query}") response.raise_for_status() @@ -453,7 +455,7 @@ class TidalClient(ClientInterface): self.session = requests.Session() # for multithreading adapter = requests.adapters.HTTPAdapter(pool_connections=200, pool_maxsize=200) - self.session.mount('https://', adapter) + self.session.mount("https://", adapter) def login( self, @@ -675,7 +677,7 @@ class TidalClient(ClientInterface): return r def _update_authorization(self): - self.session.headers.update({'authorization': f"Bearer {self.access_token}"}) + self.session.headers.update({"authorization": f"Bearer {self.access_token}"}) class SoundCloudClient(ClientInterface): diff --git a/streamrip/config.py b/streamrip/config.py index 7ff2af6..462ea05 100644 --- a/streamrip/config.py +++ b/streamrip/config.py @@ -86,7 +86,7 @@ class Config: "path_format": {"folder": FOLDER_FORMAT, "track": TRACK_FORMAT}, "check_for_updates": True, "lastfm": {"source": "qobuz"}, - "concurrent_downloads": {"enabled": True, "max_connections": None} + "concurrent_downloads": {"enabled": True, "max_connections": None}, } def __init__(self, path: str = None): diff --git a/streamrip/converter.py b/streamrip/converter.py index 544f2fc..4960eef 100644 --- a/streamrip/converter.py +++ b/streamrip/converter.py @@ -5,13 +5,12 @@ import subprocess from tempfile import gettempdir from typing import Optional -from mutagen.flac import FLAC as FLAC_META -from mutagen.mp4 import MP4 as M4A_META - from .exceptions import ConversionError logger = logging.getLogger(__name__) +SAMPLING_RATES = (44100, 48000, 88200, 96000, 176400, 192000) + class Converter: """Base class for audio codecs.""" @@ -112,13 +111,10 @@ class Converter: if self.lossless: if isinstance(self.sampling_rate, int): - meta_objects = { - "flac": FLAC_META, - "alac": M4A_META, - } - audio = meta_objects[self.container](self.filename) - old_sr = audio.info.sample_rate - command.extend(["-ar", str(min(old_sr, self.sampling_rate))]) + sampling_rates = "|".join( + str(rate) for rate in SAMPLING_RATES if rate <= self.sampling_rate + ) + command.extend(['-af', f"aformat=sample_rates={sampling_rates}"]) elif self.sampling_rate is not None: raise TypeError( @@ -135,6 +131,7 @@ class Converter: elif self.bit_depth is not None: raise TypeError(f"Bit depth must be int, not {type(self.bit_depth)}") + # automatically overwrite command.extend(["-y", self.tempfile]) return command diff --git a/streamrip/core.py b/streamrip/core.py index ae6749e..79fb889 100644 --- a/streamrip/core.py +++ b/streamrip/core.py @@ -167,7 +167,9 @@ class MusicDL(list): ], "stay_temp": self.config.session["conversion"]["enabled"], "conversion": self.config.session["conversion"], - "concurrent_downloads": self.config.session['concurrent_downloads']['enabled'], + "concurrent_downloads": self.config.session["concurrent_downloads"][ + "enabled" + ], } logger.debug("Arguments from config: %s", arguments) for item in self: diff --git a/streamrip/downloader.py b/streamrip/downloader.py index 7a2389f..05cb6a8 100644 --- a/streamrip/downloader.py +++ b/streamrip/downloader.py @@ -609,8 +609,9 @@ class Tracklist(list): if kwargs.get("concurrent_downloads", True): processes = [] for item in self: - proc = threading.Thread(target=target, args=(item,), kwargs=kwargs) - proc.daemon = True + proc = threading.Thread( + target=target, args=(item,), kwargs=kwargs, daemon=True + ) proc.start() processes.append(proc) @@ -844,7 +845,11 @@ class Album(Tracklist): embed_cover_size in self.cover_urls ), f"Invalid cover size. Must be in {self.cover_urls.keys()}" - tqdm_download(self.cover_urls[embed_cover_size], cover_path) + embed_cover_url = self.cover_urls[embed_cover_size] + if embed_cover_url is not None: + tqdm_download(embed_cover_url, cover_path) + else: # sometimes happens with Deezer + tqdm_download(self.cover_urls["small"], cover_path) if kwargs.get("keep_hires_cover", True): tqdm_download( @@ -880,17 +885,15 @@ class Album(Tracklist): disc_folder = os.path.join(self.folder, f"Disc {track.meta.discnumber}") kwargs["parent_folder"] = disc_folder else: - kwargs['parent_folder'] = self.folder + kwargs["parent_folder"] = self.folder - track.download( - quality=quality, database=database, **kwargs - ) + track.download(quality=quality, database=database, **kwargs) # deezer tracks come tagged if kwargs.get("tag_tracks", True) and self.client.source != "deezer": track.tag(cover=self.cover_obj, embed_cover=kwargs.get("embed_cover", True)) - if safe_get(kwargs, 'conversion', 'enabled', default=False): + if safe_get(kwargs, "conversion", "enabled", default=False): track.convert(**kwargs["conversion"]) @staticmethod diff --git a/streamrip/utils.py b/streamrip/utils.py index b2079ea..ca810e7 100644 --- a/streamrip/utils.py +++ b/streamrip/utils.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) session = requests.Session() adapter = requests.adapters.HTTPAdapter(pool_connections=100, pool_maxsize=100) -session.mount('https://', adapter) +session.mount("https://", adapter) def safe_get(d: dict, *keys: Hashable, default=None): @@ -117,6 +117,7 @@ def tqdm_download(url: str, filepath: str, params: dict = None): total = int(r.headers.get("content-length", 0)) logger.debug(f"File size = {total}") if total < 1000 and not url.endswith("jpg") and not url.endswith("png"): + print(url) raise NonStreamable(url) try: