added interactive mode

This commit is contained in:
nathom 2021-03-22 22:27:33 -07:00
parent c79cbbd6f4
commit 5abe14aeb9
5 changed files with 77 additions and 14 deletions

View File

@ -20,6 +20,7 @@ if not os.path.isdir(CACHE_DIR):
os.makedirs(CONFIG_DIR)
config = Config(CONFIG_PATH)
core = MusicDL(config)
@click.group()
@ -82,7 +83,6 @@ def download(ctx, **kwargs):
init_log()
config.update_from_cli(**ctx.params)
core = MusicDL(config, database=list() if kwargs["no_db"] else None)
for item in kwargs["items"]:
try:
if os.path.isfile(item):

View File

@ -359,6 +359,7 @@ class DeezerClient(ClientInterface):
self.session = requests.Session()
self.logged_in = True
@region.cache_on_arguments(expiration_time=RELEASE_CACHE_TIME)
def search(self, query: str, media_type: str = "album", limit: int = 200) -> dict:
"""Search API for query.

View File

@ -1,4 +1,5 @@
import os
from pathlib import Path
import click
import mutagen.id3 as id3
@ -11,6 +12,8 @@ CONFIG_PATH = os.path.join(CONFIG_DIR, "config.yaml")
LOG_DIR = click.get_app_dir(APPNAME)
DB_PATH = os.path.join(LOG_DIR, "music-dl.db")
DOWNLOADS_DIR = os.path.join(Path.home(), "Music Downloads")
AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0"
TIDAL_COVER_URL = "https://resources.tidal.com/images/{uuid}/{width}x{height}.jpg"

View File

@ -2,9 +2,11 @@ import logging
import os
import re
from getpass import getpass
from string import Formatter
from typing import Generator, Optional, Tuple, Union
import click
from simple_term_menu import TerminalMenu
from tqdm import tqdm
from .clients import DeezerClient, QobuzClient, TidalClient
@ -62,12 +64,12 @@ class MusicDL(list):
:type source: str
"""
click.secho(f"Enter {capitalize(source)} email:", fg="green")
self.config[source]["email"] = input()
self.config.file[source]["email"] = input()
click.secho(
f"Enter {capitalize(source)} password (will not show on screen):",
fg="green",
)
self.config[source]["password"] = getpass(
self.config.file[source]["password"] = getpass(
prompt=""
) # does hashing work for tidal?
@ -81,8 +83,8 @@ class MusicDL(list):
return
if (
self.config[source]["email"] is None
or self.config[source]["password"] is None
self.config.file[source]["email"] is None
or self.config.file[source]["password"] is None
):
self.prompt_creds(source)
@ -110,8 +112,7 @@ class MusicDL(list):
"embed_cover": self.config.metadata["embed_cover"],
}
client = self.clients[source]
self.login(client)
client = self.get_client(source)
item = MEDIA_CLASS[media_type](client=client, id=item_id)
self.append(item)
@ -128,6 +129,13 @@ class MusicDL(list):
click.secho(f"Downloading {item!s}", fg="bright_green")
item.download(**arguments)
def get_client(self, source: str):
client = self.clients[source]
if not client.logged_in:
self.assert_creds(source)
self.login(client)
return client
def convert_all(self, codec, **kwargs):
click.secho("Converting the downloaded tracks...", fg="cyan")
for item in self:
@ -198,14 +206,65 @@ class MusicDL(list):
self.handle_url(line)
def search(
self, query: str, media_type: str = "album", limit: int = 200
self, source: str, query: str, media_type: str = "album", limit: int = 200
) -> Generator:
results = self.client.search(query, media_type, limit)
client = self.get_client(source)
results = client.search(query, media_type)
i = 0
if isinstance(results, Generator): # QobuzClient
for page in results:
for item in page[f"{media_type}s"]["items"]:
yield MEDIA_CLASS[media_type].from_api(item, self.client)
yield MEDIA_CLASS[media_type].from_api(item, client)
i += 1
if i > limit:
return
else:
for item in results.get("data") or results.get("items"):
yield MEDIA_CLASS[media_type].from_api(item, self.client)
yield MEDIA_CLASS[media_type].from_api(item, client)
i += 1
if i > limit:
return
def preview_media(self, media):
if isinstance(media, Album):
fmt = (
"{albumartist} - {title}\n"
"Released on {year}\n{tracktotal} tracks\n"
"{bit_depth} bit / {sampling_rate} Hz\n"
"Version: {version}"
)
fields = (fname for _, fname, _, _ in Formatter().parse(fmt) if fname)
ret = fmt.format(**{k: media.get(k, "Unknown") for k in fields})
else:
raise NotImplementedError
return ret
def interactive_search(
self, query: str, source: str = "qobuz", media_type: str = "album"
):
results = tuple(self.search(source, query, media_type, limit=30))
def title(res):
return f"{res[0]+1}. {res[1].title}"
def from_title(s):
num = []
for char in s:
if char.isdigit():
num.append(char)
else:
break
return self.preview_media(results[int("".join(num)) - 1])
menu = TerminalMenu(
map(title, enumerate(results)),
preview_command=from_title,
preview_size=0.5,
title=f"{capitalize(source)} {media_type} search",
cycle_cursor=True,
clear_screen=True,
)
choice = menu.show()
return results[choice]

View File

@ -692,7 +692,7 @@ class Album(Tracklist):
"title": resp.get("title"),
"_artist": safe_get(resp, "artist", "name"),
"albumartist": safe_get(resp, "artist", "name"),
"year": str(resp.get("year"))[:4],
"year": str(resp.get("year"))[:4] or "Unknown",
# version not given by API
"cover_urls": {
sk: resp.get(rk) # size key, resp key
@ -705,7 +705,7 @@ class Album(Tracklist):
"quality": 6, # all tracks are 16/44.1 streamable
"bit_depth": 16,
"sampling_rate": 44100,
"tracktotal": resp.get("track_total"),
"tracktotal": resp.get("track_total") or resp.get("nb_tracks"),
}
raise InvalidSourceError(client.source)
@ -733,7 +733,7 @@ class Album(Tracklist):
:rtype: str
"""
album_title = self._title
if isinstance(self.version, str):
if hasattr(self, "version") and isinstance(self.version, str):
if self.version.lower() not in album_title.lower():
album_title = f"{album_title} ({self.version})"