From a46b9867b2ccad547320f335e3b645a08cee1ef0 Mon Sep 17 00:00:00 2001 From: nathom Date: Wed, 24 Mar 2021 10:39:37 -0700 Subject: [PATCH] =?UTF-8?q?finished=20=E2=80=98shell=E2=80=99=20of=20cli?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- music_dl/cli.py | 239 +++++++++++++++++++++------------------------ music_dl/config.py | 11 ++- music_dl/core.py | 20 ++-- 3 files changed, 126 insertions(+), 144 deletions(-) diff --git a/music_dl/cli.py b/music_dl/cli.py index e64e91b..28c596c 100644 --- a/music_dl/cli.py +++ b/music_dl/cli.py @@ -2,12 +2,12 @@ import logging import os -from getpass import getpass +from pprint import pformat import click from .config import Config -from .constants import CACHE_DIR, CONFIG_DIR, CONFIG_PATH +from .constants import CACHE_DIR, CONFIG_DIR, CONFIG_PATH, QOBUZ_FEATURED_KEYS from .core import MusicDL from .utils import init_log @@ -19,156 +19,143 @@ if not os.path.isdir(CONFIG_DIR): if not os.path.isdir(CACHE_DIR): os.makedirs(CONFIG_DIR) -config = Config(CONFIG_PATH) -core = MusicDL(config) - -@click.group() -@click.option( - "--flush-cache", - metavar="PATH", - help="Flush the cache before running (only for extreme cases)", -) +@click.group(invoke_without_command=True) +@click.option("-c", "--convert", metavar="CODEC") +@click.option("-u", '--urls', metavar='URLS') @click.pass_context def cli(ctx, **kwargs): - """cli. - - $ rip www.qobuz.com/album/id1089374 convert -c ALAC -sr 48000 - - > download and convert to alac, downsample to 48kHz - - $ rip config --read - - > Config(...) - - $ rip www.qobuz.com/artist/id223049 filter --studio-albums --no-repeats - - > download discography with given filters """ - - -@click.command(name="dl") -@click.option("-q", "--quality", metavar="INT", help="Quality integer ID (5, 6, 7, 27)") -@click.option("-f", "--folder", metavar="PATH", help="Custom download folder") -@click.option("-s", "--search", metavar="QUERY") -@click.option("-nd", "--no-db", is_flag=True) -@click.option("-c", "--convert", metavar="CODEC") -@click.option("-sr", "--sampling-rate", metavar="INT") -@click.option("-bd", "--bit-depth", metavar="INT") -@click.option("--debug", default=False, is_flag=True, help="Enable debug logging") -@click.argument("items", nargs=-1) -@click.pass_context -def download(ctx, **kwargs): - """ - Download an URL, space separated URLs or a text file with URLs. - Mixed arguments are also supported. - Examples: - * `qobuz-dl dl https://some.url/some_type/some_id` + $ rip {url} --convert alac - * `qobuz-dl dl file_with_urls.txt` + Download the url and convert to alac - * `qobuz-dl dl URL URL URL` + $ rip {artist_url} -c alac filter --repeats --non-albums - Supported sources and their types: + Download a discography, filtering repeats and non-albums - * Deezer (album, artist, track, playlist) + $ rip interactive --search - * Qobuz (album, artist, label, track, playlist) + Start an interactive search session + + $ rip interactive --discover + + Start an interactive discover session + + $ rip config --open + + Open config file + + $ rip config --qobuz + + Set qobuz credentials - * Tidal (album, artist, track, playlist) """ - if kwargs.get("debug"): - init_log() + global config + global core - config.update_from_cli(**ctx.params) - for item in kwargs["items"]: - try: - if os.path.isfile(item): - core.from_txt(item) - click.secho(f"File input found: {item}", fg="yellow") - else: - core.handle_url(item) - except Exception as error: - logger.error(error, exc_info=True) - click.secho( - f"{type(error).__name__} raised processing {item}: {error}", fg="red" - ) - - if ctx.params["convert"] is not None: - core.convert_all( - ctx.params["convert"], - sampling_rate=ctx.params["sampling_rate"], - bit_depth=ctx.params["bit_depth"], - ) + config = Config() + core = MusicDL(config) -@click.command(name="config") -@click.option("-o", "--open", is_flag=True) -@click.option("-q", "--qobuz", is_flag=True) -@click.option("-t", "--tidal", is_flag=True) -def edit_config(open, qobuz, tidal): - if open: - # open in text editor - click.launch(CONFIG_PATH) - return +@cli.command(name="filter") +@click.option("--repeats", is_flag=True) +@click.option("--non-albums", is_flag=True) +@click.option("--extras", is_flag=True) +@click.option("--features", is_flag=True) +@click.option("--non-studio-albums", is_flag=True) +@click.option("--non-remasters", is_flag=True) +@click.argument("URLS", nargs=-1) +@click.pass_context +def filter_discography(ctx, **kwargs): + """ONLY AVAILABLE FOR QOBUZ - if qobuz: - config["qobuz"]["email"] = input("Qobuz email: ") - config["qobuz"]["password"] = getpass("Qobuz password: ") - config.save() - click.secho(f"Config saved at {CONFIG_PATH}", fg="green") + The Qobuz API returns a massive number of tangentially related + albums when requesting an artist's discography. This command + can filter out most of the junk. - if tidal: - config["tidal"]["email"] = input("Tidal email: ") - config["tidal"]["password"] = getpass("Tidal password: ") - config.save() - click.secho(f"Config saved at {CONFIG_PATH}", fg="green") + For basic filtering, use the `--repeats` and `--features` filters. + """ + + filters = [k for k, v in kwargs.items() if v] + filters.remove('urls') + print(f"loaded filters {filters}") + config.session["filters"] = filters + print(f"downloading {kwargs['urls']} with filters") -@click.command() -@click.option( - "-t", - "--type", - default="album", - help="Type to search for. Can be album, artist, playlist, track", -) -@click.argument("QUERY") -def search(media_type, query): - print(f"searching for {media_type} with {query=}") +@cli.command() +@click.option("-t", "--type", default="album") +@click.option("-d", "--discover", is_flag=True) +@click.argument("QUERY", nargs=-1) +@click.pass_context +def interactive(ctx, **kwargs): + f"""Interactive search for a query. This will display a menu + from which you can choose an item to download. + + If the source is Qobuz, you can use the `--discover` option with + one of the following queries to fetch and interactively download + the featured albums. + + {pformat(QOBUZ_FEATURED_KEYS)} + + * most-streamed + + * recent-releases + + * best-sellers + + * press-awards + + * ideal-discography + + * editor-picks + + * most-featured + + * qobuzissims + + * new-releases + + * new-releases-full + + * harmonia-mundi + + * universal-classic + + * universal-jazz + + * universal-jeunesse + + * universal-chanson + + """ + + print(f"starting interactive mode for type {kwargs['type']}") + if kwargs['discover']: + if kwargs['query'] == (): + kwargs['query'] = 'ideal-discography' + print(f"doing a discover search of type {kwargs['query']}") + else: + query = ' '.join(kwargs['query']) + print(f"searching for query '{query}'") -@click.command() -def interactive(): - pass +def parse_urls(arg: str): + if os.path.isfile(arg): + return arg, "txt" + if "http" in arg: + return arg, "urls" - -@click.command() -@click.option("--no-extras", is_flag=True, help="Ignore extras") -@click.option("--no-features", is_flag=True, help="Ignore features") -@click.option("--studio-albums", is_flag=True, help="Ignore non-studio albums") -@click.option("--remaster-only", is_flag=True, help="Ignore non-remastered albums") -@click.option("--albums-only", is_flag=True, help="Ignore non-album downloads") -def filter(*args): - print(f"filter {args=}") - - -@click.command() -@click.option( - "--default-comment", metavar="COMMENT", help="Custom comment tag for audio files" -) -@click.option("--no-cover", help="Do not embed cover into audio file.") -def tags(default_comment, no_cover): - print(f"{default_comment=}, {no_cover=}") + raise ValueError(f"Invalid argument {arg}") def main(): - cli.add_command(download) - cli.add_command(filter) - cli.add_command(tags) - cli.add_command(edit_config) - cli() + cli.add_command(filter_discography) + cli.add_command(interactive) + cli(obj={}) if __name__ == "__main__": diff --git a/music_dl/config.py b/music_dl/config.py index 1f39813..e6deed2 100644 --- a/music_dl/config.py +++ b/music_dl/config.py @@ -43,11 +43,12 @@ class Config: "downloads_database": None, "conversion": {"codec": None, "sampling_rate": None, "bit_depth": None}, "filters": { - "no_extras": False, - "albums_only": False, - "no_features": False, - "studio_albums": False, - "remaster_only": False, + "extras": False, + "repeats": False, + "non_albums": False, + "features": False, + "non_studio_albums": False, + "non_remaster": False, }, "downloads": {"folder": DOWNLOADS_DIR, "quality": 7}, "metadata": { diff --git a/music_dl/core.py b/music_dl/core.py index 5a1b303..9783bb2 100644 --- a/music_dl/core.py +++ b/music_dl/core.py @@ -96,7 +96,7 @@ class MusicDL(list): :raises InvalidSourceError :raises ParsingError """ - source, url_type, item_id = self.parse_url(url) + source, url_type, item_id = self.parse_urls(url)[0] if item_id in self.db: logger.info(f"{url} already downloaded, use --no-db to override.") return @@ -162,7 +162,7 @@ class MusicDL(list): ) = client.get_tokens() self.config.save() - def parse_url(self, url: str) -> Tuple[str, str]: + def parse_urls(self, url: str) -> Tuple[str, str]: """Returns the type of the url and the id. Compatible with urls of the form: @@ -176,13 +176,10 @@ class MusicDL(list): :raises exceptions.ParsingError """ - parsed = self.url_parse.search(url) + parsed = self.url_parse.findall(url) - if parsed is not None: - parsed = parsed.groups() - - if len(parsed) == 3: - return tuple(parsed) # Convert from Seq for the sake of typing + if parsed != []: + return parsed raise ParsingError(f"Error parsing URL: `{url}`") @@ -196,14 +193,11 @@ class MusicDL(list): :raises exceptions.ParsingError """ with open(filepath) as txt: - lines = ( + lines = " ".join( line for line in txt.readlines() if not line.strip().startswith("#") ) - click.secho(f"URLs found in text file: {len(lines)}") - - for line in lines: - self.handle_url(line) + return self.parse_urls(lines) def search( self, source: str, query: str, media_type: str = "album", limit: int = 200