finished ‘shell’ of cli

This commit is contained in:
nathom 2021-03-24 10:39:37 -07:00
parent 5abe14aeb9
commit a46b9867b2
3 changed files with 126 additions and 144 deletions

View file

@ -2,12 +2,12 @@
import logging import logging
import os import os
from getpass import getpass from pprint import pformat
import click import click
from .config import Config 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 .core import MusicDL
from .utils import init_log from .utils import init_log
@ -19,156 +19,143 @@ if not os.path.isdir(CONFIG_DIR):
if not os.path.isdir(CACHE_DIR): if not os.path.isdir(CACHE_DIR):
os.makedirs(CONFIG_DIR) os.makedirs(CONFIG_DIR)
config = Config(CONFIG_PATH)
core = MusicDL(config)
@click.group(invoke_without_command=True)
@click.group() @click.option("-c", "--convert", metavar="CODEC")
@click.option( @click.option("-u", '--urls', metavar='URLS')
"--flush-cache",
metavar="PATH",
help="Flush the cache before running (only for extreme cases)",
)
@click.pass_context @click.pass_context
def cli(ctx, **kwargs): 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: 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"): global config
init_log() global core
config.update_from_cli(**ctx.params) config = Config()
for item in kwargs["items"]: core = MusicDL(config)
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"],
)
@click.command(name="config") @cli.command(name="filter")
@click.option("-o", "--open", is_flag=True) @click.option("--repeats", is_flag=True)
@click.option("-q", "--qobuz", is_flag=True) @click.option("--non-albums", is_flag=True)
@click.option("-t", "--tidal", is_flag=True) @click.option("--extras", is_flag=True)
def edit_config(open, qobuz, tidal): @click.option("--features", is_flag=True)
if open: @click.option("--non-studio-albums", is_flag=True)
# open in text editor @click.option("--non-remasters", is_flag=True)
click.launch(CONFIG_PATH) @click.argument("URLS", nargs=-1)
return @click.pass_context
def filter_discography(ctx, **kwargs):
"""ONLY AVAILABLE FOR QOBUZ
if qobuz: The Qobuz API returns a massive number of tangentially related
config["qobuz"]["email"] = input("Qobuz email: ") albums when requesting an artist's discography. This command
config["qobuz"]["password"] = getpass("Qobuz password: ") can filter out most of the junk.
config.save()
click.secho(f"Config saved at {CONFIG_PATH}", fg="green")
if tidal: For basic filtering, use the `--repeats` and `--features` filters.
config["tidal"]["email"] = input("Tidal email: ") """
config["tidal"]["password"] = getpass("Tidal password: ")
config.save() filters = [k for k, v in kwargs.items() if v]
click.secho(f"Config saved at {CONFIG_PATH}", fg="green") filters.remove('urls')
print(f"loaded filters {filters}")
config.session["filters"] = filters
print(f"downloading {kwargs['urls']} with filters")
@click.command() @cli.command()
@click.option( @click.option("-t", "--type", default="album")
"-t", @click.option("-d", "--discover", is_flag=True)
"--type", @click.argument("QUERY", nargs=-1)
default="album", @click.pass_context
help="Type to search for. Can be album, artist, playlist, track", def interactive(ctx, **kwargs):
) f"""Interactive search for a query. This will display a menu
@click.argument("QUERY") from which you can choose an item to download.
def search(media_type, query):
print(f"searching for {media_type} with {query=}") 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 parse_urls(arg: str):
def interactive(): if os.path.isfile(arg):
pass return arg, "txt"
if "http" in arg:
return arg, "urls"
raise ValueError(f"Invalid argument {arg}")
@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=}")
def main(): def main():
cli.add_command(download) cli.add_command(filter_discography)
cli.add_command(filter) cli.add_command(interactive)
cli.add_command(tags) cli(obj={})
cli.add_command(edit_config)
cli()
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -43,11 +43,12 @@ class Config:
"downloads_database": None, "downloads_database": None,
"conversion": {"codec": None, "sampling_rate": None, "bit_depth": None}, "conversion": {"codec": None, "sampling_rate": None, "bit_depth": None},
"filters": { "filters": {
"no_extras": False, "extras": False,
"albums_only": False, "repeats": False,
"no_features": False, "non_albums": False,
"studio_albums": False, "features": False,
"remaster_only": False, "non_studio_albums": False,
"non_remaster": False,
}, },
"downloads": {"folder": DOWNLOADS_DIR, "quality": 7}, "downloads": {"folder": DOWNLOADS_DIR, "quality": 7},
"metadata": { "metadata": {

View file

@ -96,7 +96,7 @@ class MusicDL(list):
:raises InvalidSourceError :raises InvalidSourceError
:raises ParsingError :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: if item_id in self.db:
logger.info(f"{url} already downloaded, use --no-db to override.") logger.info(f"{url} already downloaded, use --no-db to override.")
return return
@ -162,7 +162,7 @@ class MusicDL(list):
) = client.get_tokens() ) = client.get_tokens()
self.config.save() 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. """Returns the type of the url and the id.
Compatible with urls of the form: Compatible with urls of the form:
@ -176,13 +176,10 @@ class MusicDL(list):
:raises exceptions.ParsingError :raises exceptions.ParsingError
""" """
parsed = self.url_parse.search(url) parsed = self.url_parse.findall(url)
if parsed is not None: if parsed != []:
parsed = parsed.groups() return parsed
if len(parsed) == 3:
return tuple(parsed) # Convert from Seq for the sake of typing
raise ParsingError(f"Error parsing URL: `{url}`") raise ParsingError(f"Error parsing URL: `{url}`")
@ -196,14 +193,11 @@ class MusicDL(list):
:raises exceptions.ParsingError :raises exceptions.ParsingError
""" """
with open(filepath) as txt: with open(filepath) as txt:
lines = ( lines = " ".join(
line for line in txt.readlines() if not line.strip().startswith("#") line for line in txt.readlines() if not line.strip().startswith("#")
) )
click.secho(f"URLs found in text file: {len(lines)}") return self.parse_urls(lines)
for line in lines:
self.handle_url(line)
def search( def search(
self, source: str, query: str, media_type: str = "album", limit: int = 200 self, source: str, query: str, media_type: str = "album", limit: int = 200