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)
@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):
"""
Examples:
$ rip {url} --convert alac
Download the url and convert to alac
$ rip {artist_url} -c alac filter --repeats --non-albums
Download a discography, filtering repeats and non-albums
$ rip interactive --search
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
"""
global config
global core
config = Config()
core = MusicDL(config) core = MusicDL(config)
@click.group() @cli.command(name="filter")
@click.option( @click.option("--repeats", is_flag=True)
"--flush-cache", @click.option("--non-albums", is_flag=True)
metavar="PATH", @click.option("--extras", is_flag=True)
help="Flush the cache before running (only for extreme cases)", @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 @click.pass_context
def cli(ctx, **kwargs): def filter_discography(ctx, **kwargs):
"""cli. """ONLY AVAILABLE FOR QOBUZ
$ rip www.qobuz.com/album/id1089374 convert -c ALAC -sr 48000 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.
> download and convert to alac, downsample to 48kHz For basic filtering, use the `--repeats` and `--features` filters.
$ rip config --read
> Config(...)
$ rip www.qobuz.com/artist/id223049 filter --studio-albums --no-repeats
> download discography with given 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(name="dl")
@click.option("-q", "--quality", metavar="INT", help="Quality integer ID (5, 6, 7, 27)") @cli.command()
@click.option("-f", "--folder", metavar="PATH", help="Custom download folder") @click.option("-t", "--type", default="album")
@click.option("-s", "--search", metavar="QUERY") @click.option("-d", "--discover", is_flag=True)
@click.option("-nd", "--no-db", is_flag=True) @click.argument("QUERY", nargs=-1)
@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 @click.pass_context
def download(ctx, **kwargs): 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
""" """
Download an URL, space separated URLs or a text file with URLs.
Mixed arguments are also supported.
Examples: print(f"starting interactive mode for type {kwargs['type']}")
if kwargs['discover']:
* `qobuz-dl dl https://some.url/some_type/some_id` if kwargs['query'] == ():
kwargs['query'] = 'ideal-discography'
* `qobuz-dl dl file_with_urls.txt` print(f"doing a discover search of type {kwargs['query']}")
* `qobuz-dl dl URL URL URL`
Supported sources and their types:
* Deezer (album, artist, track, playlist)
* Qobuz (album, artist, label, track, playlist)
* Tidal (album, artist, track, playlist)
"""
if kwargs.get("debug"):
init_log()
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: else:
core.handle_url(item) query = ' '.join(kwargs['query'])
except Exception as error: print(f"searching for query '{query}'")
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") def parse_urls(arg: str):
@click.option("-o", "--open", is_flag=True) if os.path.isfile(arg):
@click.option("-q", "--qobuz", is_flag=True) return arg, "txt"
@click.option("-t", "--tidal", is_flag=True) if "http" in arg:
def edit_config(open, qobuz, tidal): return arg, "urls"
if open:
# open in text editor
click.launch(CONFIG_PATH)
return
if qobuz: raise ValueError(f"Invalid argument {arg}")
config["qobuz"]["email"] = input("Qobuz email: ")
config["qobuz"]["password"] = getpass("Qobuz password: ")
config.save()
click.secho(f"Config saved at {CONFIG_PATH}", fg="green")
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")
@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=}")
@click.command()
def interactive():
pass
@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