Make Deezer/Deezloader transitions smoother

This commit is contained in:
nathom 2021-07-28 21:41:21 -07:00
parent 96b15d9917
commit aac254516f
5 changed files with 1273 additions and 1253 deletions

View file

@ -38,12 +38,20 @@ refresh_token = ""
# in again using `rip config --tidal` # in again using `rip config --tidal`
token_expiry = "" token_expiry = ""
# Doesn't require login
[deezer] [deezer]
# 0, 1, or 2 # 0, 1, or 2
# This only applies to paid Deezer subscriptions. Those using deezloader
# are automatically limited to quality = 1
quality = 2 quality = 2
use_deemix_server = true # An authentication cookie that allows streamrip to use your Deezer account
# See [ADD LINK] for instructions on how to find this
arl = "" arl = ""
# This allows for free 320kbps MP3 downloads from Deezer
# If an arl is provided, deezloader is never used
use_deezloader = true
# This warns you when the paid deezer account is not logged in and rip falls
# back to deezloader, which is unreliable
deezloader_warnings = true
[soundcloud] [soundcloud]
# Only 0 is available for now # Only 0 is available for now

View file

@ -35,6 +35,7 @@ from streamrip.clients import (
) )
from .config import Config from .config import Config
from streamrip.constants import MEDIA_TYPES from streamrip.constants import MEDIA_TYPES
from streamrip.utils import set_progress_bar_theme, TQDM_DEFAULT_THEME
from .constants import ( from .constants import (
URL_REGEX, URL_REGEX,
SOUNDCLOUD_URL_REGEX, SOUNDCLOUD_URL_REGEX,
@ -56,7 +57,12 @@ from streamrip.exceptions import (
NoResultsFound, NoResultsFound,
ParsingError, ParsingError,
) )
from .utils import extract_deezer_dynamic_link, extract_interpreter_url from .utils import (
extract_deezer_dynamic_link,
extract_interpreter_url,
)
from .exceptions import DeezloaderFallback
logger = logging.getLogger("streamrip") logger = logging.getLogger("streamrip")
@ -191,8 +197,6 @@ class RipCore(list):
:param item_id: :param item_id:
:type item_id: str :type item_id: str
""" """
self.assert_creds(source)
client = self.get_client(source) client = self.get_client(source)
if media_type not in MEDIA_TYPES: if media_type not in MEDIA_TYPES:
@ -340,8 +344,11 @@ class RipCore(list):
""" """
client = self.clients[source] client = self.clients[source]
if not client.logged_in: if not client.logged_in:
self.assert_creds(source) try:
self.login(client) self.login(client)
except DeezloaderFallback:
client = self.clients["deezloader"]
return client return client
def login(self, client): def login(self, client):
@ -350,16 +357,27 @@ class RipCore(list):
:param client: :param client:
""" """
creds = self.config.creds(client.source) creds = self.config.creds(client.source)
if client.source == "deezer" and creds["arl"] == "":
if self.config.session["deezer"]["deezloader_warnings"]:
click.secho(
"Falling back to Deezloader (max 320kbps MP3). If you have a subscription, run ",
nl=False,
fg="yellow",
)
click.secho("rip config --deezer ", nl=False, bold=True)
click.secho("to download FLAC files.\n\n", fg="yellow")
raise DeezloaderFallback
while True: while True:
try: try:
client.login(**creds) client.login(**creds)
break break
except AuthenticationError: except AuthenticationError:
click.secho("Invalid credentials, try again.") click.secho("Invalid credentials, try again.", fg="yellow")
self.prompt_creds(client.source) self.prompt_creds(client.source)
creds = self.config.creds(client.source) creds = self.config.creds(client.source)
except MissingCredentials: except MissingCredentials:
logger.debug("Credentials are missing. Prompting..") logger.debug("Credentials are missing. Prompting..", fg="yellow")
self.prompt_creds(client.source) self.prompt_creds(client.source)
creds = self.config.creds(client.source) creds = self.config.creds(client.source)
@ -571,13 +589,6 @@ class RipCore(list):
else page["albums"]["items"] else page["albums"]["items"]
) )
for i, item in enumerate(tracklist): for i, item in enumerate(tracklist):
if item_id := str(item["id"]) in self.db:
click.secho(
f"ID {item_id} already logged in database. Skipping.",
fg="magenta",
)
continue
yield MEDIA_CLASS[ # type: ignore yield MEDIA_CLASS[ # type: ignore
media_type if media_type != "featured" else "album" media_type if media_type != "featured" else "album"
].from_api(item, client) ].from_api(item, client)
@ -811,8 +822,20 @@ class RipCore(list):
fg="green", fg="green",
) )
elif source == "deezer": elif source == "deezer":
click.secho("Enter Deezer ARL: ", fg="green") click.secho(
self.config.file["deezer"]["arl"] = input() "If you're not sure how to find the ARL cookie, see the instructions at ",
italic=True,
nl=False,
dim=True,
)
click.secho(
"https://github.com/nathom/streamrip/wiki/Finding-your-Deezer-ARL-Cookie",
underline=True,
italic=True,
fg="blue",
)
self.config.file["deezer"]["arl"] = input(click.style("ARL: ", fg="green"))
self.config.save() self.config.save()
click.secho( click.secho(
f'Credentials saved to config file at "{self.config._path}"', f'Credentials saved to config file at "{self.config._path}"',
@ -821,28 +844,6 @@ class RipCore(list):
else: else:
raise Exception raise Exception
def assert_creds(self, source: str):
"""Ensure that the credentials for `source` are valid.
:param source:
:type source: str
"""
assert source in (
"qobuz",
"tidal",
"deezer",
"soundcloud",
), f"Invalid source {source}"
if source == "soundcloud":
return
if source == "qobuz" and (
self.config.file[source]["email"] is None
or self.config.file[source]["password"] is None
):
self.prompt_creds(source)
def _config_updating_message(self): def _config_updating_message(self):
click.secho( click.secho(
"Updating config file... Some settings may be lost. Please run the " "Updating config file... Some settings may be lost. Please run the "

2
rip/exceptions.py Normal file
View file

@ -0,0 +1,2 @@
class DeezloaderFallback(Exception):
pass

File diff suppressed because it is too large Load diff

View file

@ -280,12 +280,20 @@ class Track(Media):
try: try:
download_url = dl_info["url"] download_url = dl_info["url"]
except KeyError as e: except KeyError as e:
if restrictions := dl_info["restrictions"]:
# Turn CamelCase code into a readable sentence
words = re.findall(r"([A-Z][a-z]+)", restrictions[0]["code"])
raise NonStreamable(
words[0] + " " + " ".join(map(str.lower, words[1:])) + "."
)
click.secho(f"Panic: {e} dl_info = {dl_info}", fg="red") click.secho(f"Panic: {e} dl_info = {dl_info}", fg="red")
raise NonStreamable
_quick_download(download_url, self.path, desc=self._progress_desc) _quick_download(download_url, self.path, desc=self._progress_desc)
elif isinstance(self.client, DeezloaderClient): elif isinstance(self.client, DeezloaderClient):
tqdm_download(dl_info["url"], self.path, desc=self._progress_desc) _quick_download(dl_info["url"], self.path, desc=self._progress_desc)
elif self.client.source == "deezer": elif self.client.source == "deezer":
# We can only find out if the requested quality is available # We can only find out if the requested quality is available
@ -1205,7 +1213,8 @@ class Tracklist(list):
""" """
click.secho( click.secho(
f"\n\nDownloading {self.title} ({self.__class__.__name__})\n", f"\n\nDownloading {self.title} ({self.__class__.__name__})\n",
fg="blue", fg="magenta",
bold=True,
) )
@staticmethod @staticmethod
@ -1345,7 +1354,6 @@ class Album(Tracklist, Media):
# Generate the folder name # Generate the folder name
self.folder_format = kwargs.get("folder_format", FOLDER_FORMAT) self.folder_format = kwargs.get("folder_format", FOLDER_FORMAT)
self.quality = min(kwargs.get("quality", 3), self.client.max_quality) self.quality = min(kwargs.get("quality", 3), self.client.max_quality)
print(f"{self.quality=} {self.client.max_quality = }")
self.folder = self._get_formatted_folder( self.folder = self._get_formatted_folder(
kwargs.get("parent_folder", "StreamripDownloads"), self.quality kwargs.get("parent_folder", "StreamripDownloads"), self.quality