diff --git a/pyproject.toml b/pyproject.toml index 056f58a..b96ac6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "streamrip" -version = "1.1" +version = "1.2" description = "A fast, all-in-one music ripper for Qobuz, Deezer, Tidal, and SoundCloud" authors = ["nathom "] license = "GPL-3.0-only" @@ -29,9 +29,9 @@ click = "^8.0.1" tqdm = "^4.61.1" tomlkit = "^0.7.2" pathvalidate = "^2.4.1" -simple-term-menu = {version = "^1.2.1", platform = 'linux or darwin'} +simple-term-menu = {version = "^1.2.1", platform = 'darwin|linux'} pick = {version = "^1.0.0", platform = 'win32 or cygwin'} -windows-curses = {version = "^2.2.0", platform = 'win32 or cygwin'} +windows-curses = {version = "^2.2.0", platform = 'win32|cygwin'} Pillow = "^8.3.0" deezer-py = "^1.0.4" pycryptodomex = "^3.10.1" diff --git a/rip/cli.py b/rip/cli.py index d87a073..9bbf7ab 100644 --- a/rip/cli.py +++ b/rip/cli.py @@ -276,12 +276,34 @@ class ConfigCommand(Command): self.line("Credentials saved to config.") if self.option("deezer"): + from streamrip.clients import DeezerClient + from streamrip.exceptions import AuthenticationError + self.line( "Follow the instructions at https://github.com" "/nathom/streamrip/wiki/Finding-your-Deezer-ARL-Cookie" ) - config.file["deezer"]["arl"] = self.ask("Paste your ARL here: ") + given_arl = self.ask("Paste your ARL here: ").strip() + self.line("Validating arl...") + + try: + DeezerClient().login(arl=given_arl) + config.file["deezer"]["arl"] = given_arl + config.save() + self.line("Sucessfully logged in!") + + except AuthenticationError: + self.line("Could not log in. Double check your ARL") + + if self.option("qobuz"): + import hashlib + import getpass + + config.file["qobuz"]["email"] = self.ask("Qobuz email:") + config.file["qobuz"]["password"] = hashlib.md5( + getpass.getpass("Qobuz password (won't show on screen): ").encode() + ).hexdigest() config.save() @@ -412,7 +434,12 @@ class Application(BaseApplication): def _run(self, io): if io.is_debug(): + from .constants import CONFIG_DIR + logger.setLevel(logging.DEBUG) + fh = logging.FileHandler(os.path.join(CONFIG_DIR, "streamrip.log")) + fh.setLevel(logging.DEBUG) + logger.addHandler(fh) super()._run(io) diff --git a/rip/config.py b/rip/config.py index 6e2e133..7223566 100644 --- a/rip/config.py +++ b/rip/config.py @@ -64,6 +64,7 @@ class Config: self.load() else: logger.debug("Creating toml config file at '%s'", self._path) + os.makedirs(os.path.dirname(self._path), exist_ok=True) shutil.copy(self.default_config_path, self._path) self.load() self.file["downloads"]["folder"] = DOWNLOADS_DIR diff --git a/rip/config.toml b/rip/config.toml index 0db5f3f..58777ab 100644 --- a/rip/config.toml +++ b/rip/config.toml @@ -156,4 +156,4 @@ progress_bar = "dainty" [misc] # Metadata to identify this config file. Do not change. -version = "1.1" +version = "1.2" diff --git a/rip/core.py b/rip/core.py index 9248bc4..3a41e32 100644 --- a/rip/core.py +++ b/rip/core.py @@ -488,6 +488,14 @@ class RipCore(list): self._config_corrupted_message(err) exit() + # Do not include tracks that have (re)mix, live, karaoke in their titles + # within parentheses or brackets + # This will match somthing like "Test (Person Remix]" though, so its not perfect + banned_words_plain = re.compile(r"(?i)(?:(?:re)?mix|live|karaoke)") + banned_words = re.compile( + rf"(?i)[\(\[][^\)\]]*?(?:(?:re)?mix|live|karaoke)[^\)\]]*[\]\)]" + ) + def search_query(title, artist, playlist) -> bool: """Search for a query and add the first result to playlist. @@ -503,7 +511,16 @@ class RipCore(list): query = QUERY_FORMAT[lastfm_source].format( title=title, artist=artist ) - track = next(self.search(source, query, media_type="track")) + query_is_clean = banned_words_plain.search(query) is None + + search_results = self.search(source, query, media_type="track") + track = next(search_results) + + if query_is_clean: + while banned_words.search(track["title"]) is not None: + logger.debug("Track title banned for query=%s", query) + track = next(search_results) + # Because the track is searched as a single we need to set # this manually track.part_of_tracklist = True diff --git a/streamrip/__init__.py b/streamrip/__init__.py index 2f75a8f..85f07b7 100644 --- a/streamrip/__init__.py +++ b/streamrip/__init__.py @@ -1,5 +1,5 @@ """streamrip: the all in one music downloader.""" -__version__ = "1.1" +__version__ = "1.2" from . import clients, constants, converter, media diff --git a/streamrip/clients.py b/streamrip/clients.py index 1aa549c..2503414 100644 --- a/streamrip/clients.py +++ b/streamrip/clients.py @@ -118,6 +118,7 @@ class QobuzClient(Client): :type pwd: str :param kwargs: app_id: str, secrets: list, return_secrets: bool """ + # TODO: make this faster secho(f"Logging into {self.source}", fg="green") email: str = kwargs["email"] pwd: str = kwargs["pwd"] @@ -128,7 +129,7 @@ class QobuzClient(Client): logger.debug("Already logged in") return - if (kwargs.get("app_id") or kwargs.get("secrets")) in (None, [], ""): + if not (kwargs.get("app_id") or kwargs.get("secrets")): secho("Fetching tokens — this may take a few seconds.", fg="magenta") logger.info("Fetching tokens from Qobuz") spoofer = Spoofer() diff --git a/streamrip/media.py b/streamrip/media.py index 8bc8740..7177653 100644 --- a/streamrip/media.py +++ b/streamrip/media.py @@ -1409,9 +1409,18 @@ class Album(Tracklist, Media): self.folder_format = kwargs.get("folder_format", FOLDER_FORMAT) self.quality = min(kwargs.get("quality", 3), self.client.max_quality) - self.folder = self._get_formatted_folder( - kwargs.get("parent_folder", "StreamripDownloads"), self.quality - ) + self.container = get_container(self.quality, self.client.source) + # necessary to format the folder + if self.container in ("AAC", "MP3"): + # lossy codecs don't have these metrics + self.bit_depth = self.sampling_rate = None + + parent_folder = kwargs.get("parent_folder", "StreamripDownloads") + if self.folder_format: + self.folder = self._get_formatted_folder(parent_folder) + else: + self.folder = parent_folder + os.makedirs(self.folder, exist_ok=True) self.download_message() @@ -1483,7 +1492,11 @@ class Album(Tracklist, Media): :rtype: bool """ logger.debug("Downloading track to %s", self.folder) - if self.disctotal > 1 and isinstance(item, Track): + if ( + self.disctotal > 1 + and isinstance(item, Track) + and kwargs.get("folder_format") + ): disc_folder = os.path.join(self.folder, f"Disc {item.meta.discnumber}") kwargs["parent_folder"] = disc_folder else: @@ -1566,7 +1579,7 @@ class Album(Tracklist, Media): logger.debug("Formatter: %s", fmt) return fmt - def _get_formatted_folder(self, parent_folder: str, quality: int) -> str: + def _get_formatted_folder(self, parent_folder: str) -> str: """Generate the folder name for this album. :param parent_folder: @@ -1575,11 +1588,6 @@ class Album(Tracklist, Media): :type quality: int :rtype: str """ - # necessary to format the folder - self.container = get_container(quality, self.client.source) - if self.container in ("AAC", "MP3"): - # lossy codecs don't have these metrics - self.bit_depth = self.sampling_rate = None formatted_folder = clean_format(self.folder_format, self._get_formatter()) @@ -1764,8 +1772,11 @@ class Playlist(Tracklist, Media): logger.debug(f"Loaded {len(self)} tracks from playlist {self.name}") def _prepare_download(self, parent_folder: str = "StreamripDownloads", **kwargs): - fname = sanitize_filename(self.name) - self.folder = os.path.join(parent_folder, fname) + if kwargs.get("folder_format"): + fname = sanitize_filename(self.name) + self.folder = os.path.join(parent_folder, fname) + else: + self.folder = parent_folder # Used for safe concurrency with tracknumbers instead of an object # level that stores an index @@ -1953,8 +1964,11 @@ class Artist(Tracklist, Media): :param kwargs: :rtype: Iterable """ - folder = sanitize_filename(self.name) - self.folder = os.path.join(parent_folder, folder) + if kwargs.get("folder_format"): + folder = sanitize_filename(self.name) + self.folder = os.path.join(parent_folder, folder) + else: + self.folder = parent_folder logger.debug("Artist folder: %s", folder) logger.debug(f"Length of tracklist {len(self)}")