mirror of
https://github.com/nathom/streamrip.git
synced 2025-05-18 00:54:50 -04:00
added interactive mode
This commit is contained in:
parent
c79cbbd6f4
commit
5abe14aeb9
5 changed files with 77 additions and 14 deletions
|
@ -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):
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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})"
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue