Merge branch 'dev'

This commit is contained in:
nathom 2021-06-29 10:03:20 -07:00
commit 70a0928db5
7 changed files with 57 additions and 45 deletions

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "streamrip"
version = "0.6.4"
version = "0.6.5"
description = "A fast, all-in-one music ripper for Qobuz, Deezer, Tidal, and SoundCloud"
authors = ["nathom <nathanthomas707@gmail.com>"]
license = "GPL-3.0-only"

View file

@ -1,3 +1,3 @@
"""streamrip: the all in one music downloader."""
__version__ = "0.6.4"
__version__ = "0.6.5"

View file

@ -842,7 +842,6 @@ class Tracklist(list):
if kwargs.get("concurrent_downloads", True):
# Tidal errors out with unlimited concurrency
# max_workers = 15 if self.client.source == "tidal" else 90
with concurrent.futures.ThreadPoolExecutor(15) as executor:
futures = [executor.submit(target, item, **kwargs) for item in self]
try:

View file

@ -249,7 +249,7 @@ class MusicDL(list):
if not (isinstance(item, Tracklist) and item.loaded):
logger.debug("Loading metadata")
try:
item.load_meta()
item.load_meta(**arguments)
except NonStreamable:
click.secho(f"{item!s} is not available, skipping.", fg="red")
continue

View file

@ -19,7 +19,7 @@ from .constants import (
TRACK_KEYS,
)
from .exceptions import InvalidContainerError, InvalidSourceError
from .utils import get_quality_id, safe_get, tidal_cover_url
from .utils import get_quality_id, safe_get, tidal_cover_url, get_cover_urls
logger = logging.getLogger("streamrip")
@ -151,8 +151,7 @@ class TrackMetadata:
# Non-embedded information
self.version = resp.get("version")
self.cover_urls = OrderedDict(resp["image"])
self.cover_urls["original"] = self.cover_urls["large"].replace("600", "org")
self.cover_urls = get_cover_urls(resp, self.__source)
self.streamable = resp.get("streamable", False)
self.bit_depth = resp.get("maximum_bit_depth")
self.sampling_rate = resp.get("maximum_sampling_rate")
@ -177,13 +176,7 @@ class TrackMetadata:
# non-embedded
self.explicit = resp.get("explicit", False)
# 80, 160, 320, 640, 1280
uuid = resp.get("cover")
self.cover_urls = OrderedDict(
{
sk: tidal_cover_url(uuid, size)
for sk, size in zip(COVER_SIZES, (160, 320, 640, 1280))
}
)
self.cover_urls = get_cover_urls(resp, self.__source)
self.streamable = resp.get("allowStreaming", False)
if q := resp.get("audioQuality"): # for album entries in single tracks
@ -205,15 +198,7 @@ class TrackMetadata:
self.explicit = bool(resp.get("parental_warning"))
self.quality = 2
self.bit_depth = 16
self.cover_urls = OrderedDict(
{
sk: resp.get(rk) # size key, resp key
for sk, rk in zip(
COVER_SIZES,
("cover", "cover_medium", "cover_large", "cover_xl"),
)
}
)
self.cover_urls = get_cover_urls(resp, self.__source)
self.sampling_rate = 44100
self.streamable = True

View file

@ -19,6 +19,7 @@ from .exceptions import InvalidSourceError, NonStreamable
from .metadata import TrackMetadata
from .utils import (
clean_format,
get_cover_urls,
get_container,
get_stats_from_quality,
safe_get,
@ -68,7 +69,7 @@ class Album(Tracklist):
self.loaded = False
self.downloaded = False
def load_meta(self):
def load_meta(self, **kwargs):
"""Load detailed metadata from API using the id."""
assert hasattr(self, "id"), "id must be set to load metadata"
resp = self.client.get(self.id, media_type="album")
@ -220,7 +221,7 @@ class Album(Tracklist):
This uses a classmethod to convert an item into a Track object, which
stores the metadata inside a TrackMetadata object.
"""
logging.debug(f"Loading {self.tracktotal} tracks to album")
logging.debug("Loading %d tracks to album", self.tracktotal)
for track in _get_tracklist(resp, self.client.source):
if track.get("type") == "Music Video":
self.append(Video.from_album_meta(track, self.client))
@ -238,7 +239,13 @@ class Album(Tracklist):
"""
fmt = {key: self.get(key) for key in ALBUM_KEYS}
stats = get_stats_from_quality(self.quality)
stats = tuple(
min(bd, sr)
for bd, sr in zip(
(self.meta.bit_depth, self.meta.sampling_rate),
get_stats_from_quality(self.quality),
)
)
# The quality chosen is not the maximum available quality
if stats != (fmt.get("sampling_rate"), fmt.get("bit_depth")):
@ -338,6 +345,7 @@ class Playlist(Tracklist):
:type album_id: Union[str, int]
:param kwargs:
"""
self.name: str
self.client = client
for k, v in kwargs.items():
@ -373,7 +381,7 @@ class Playlist(Tracklist):
self._load_tracks(**kwargs)
self.loaded = True
def _load_tracks(self, new_tracknumbers: bool = True):
def _load_tracks(self, new_tracknumbers: bool = True, **kwargs):
"""Parse the tracklist returned by the API.
:param new_tracknumbers: replace tracknumber tag with playlist position
@ -386,9 +394,6 @@ class Playlist(Tracklist):
tracklist = self.meta["tracks"]["items"]
def gen_cover(track):
return track["album"]["image"]["small"]
def meta_args(track):
return {"track": track, "album": track["album"]}
@ -399,10 +404,6 @@ class Playlist(Tracklist):
tracklist = self.meta["tracks"]
def gen_cover(track):
cover_url = tidal_cover_url(track["album"]["cover"], 640)
return cover_url
def meta_args(track):
return {
"track": track,
@ -416,18 +417,12 @@ class Playlist(Tracklist):
tracklist = self.meta["tracks"]
def gen_cover(track):
return track["album"]["cover_medium"]
elif self.client.source == "soundcloud":
self.name = self.meta["title"]
# self.image = self.meta.get("artwork_url").replace("large", "t500x500")
self.creator = self.meta["user"]["username"]
tracklist = self.meta["tracks"]
def gen_cover(track):
return track["artwork_url"].replace("large", "t500x500")
else:
raise NotImplementedError
@ -443,13 +438,16 @@ class Playlist(Tracklist):
# tracknumber tags might cause conflicts if the playlist files are
# inside of a library folder
meta = TrackMetadata(track=track, source=self.client.source)
cover_url = get_cover_urls(track["album"], self.client.source)[
kwargs.get("embed_cover_size", "large")
]
self.append(
Track(
self.client,
id=track.get("id"),
meta=meta,
cover_url=gen_cover(track),
cover_url=cover_url,
part_of_tracklist=True,
)
)
@ -484,7 +482,7 @@ class Playlist(Tracklist):
if self.downloaded and self.client.source != "deezer":
item.tag(embed_cover=kwargs.get("embed_cover", True))
if playlist_to_album and self.client.source == "deezer":
if self.downloaded and playlist_to_album and self.client.source == "deezer":
# Because Deezer tracks come pre-tagged, the `set_playlist_to_album`
# option is never set. Here, we manually do this
from mutagen.flac import FLAC
@ -584,7 +582,7 @@ class Artist(Tracklist):
self.loaded = False
def load_meta(self):
def load_meta(self, **kwargs):
"""Send an API call to get album info based on id."""
self.meta = self.client.get(self.id, media_type="artist")
self._load_albums()
@ -857,7 +855,7 @@ class Artist(Tracklist):
class Label(Artist):
"""Represents a downloadable Label."""
def load_meta(self):
def load_meta(self, **kwargs):
"""Load metadata given an id."""
assert self.client.source == "qobuz", "Label source must be qobuz"

View file

@ -8,6 +8,7 @@ import os
import re
from string import Formatter
from typing import Dict, Hashable, Optional, Tuple, Union
from collections import OrderedDict
import click
import requests
@ -15,7 +16,7 @@ from pathvalidate import sanitize_filename
from requests.packages import urllib3
from tqdm import tqdm
from .constants import AGENT, TIDAL_COVER_URL
from .constants import AGENT, TIDAL_COVER_URL, COVER_SIZES
from .exceptions import InvalidQuality, InvalidSourceError, NonStreamable
urllib3.disable_warnings()
@ -382,3 +383,32 @@ def get_container(quality: int, source: str) -> str:
return "AAC"
return "MP3"
def get_cover_urls(resp: dict, source: str) -> dict:
if source == "qobuz":
cover_urls = OrderedDict(resp["image"])
cover_urls["original"] = cover_urls["large"].replace("600", "org")
return cover_urls
if source == "tidal":
uuid = resp["cover"]
return OrderedDict(
{
sk: tidal_cover_url(uuid, size)
for sk, size in zip(COVER_SIZES, (160, 320, 640, 1280))
}
)
if source == "deezer":
return OrderedDict(
{
sk: resp.get(rk) # size key, resp key
for sk, rk in zip(
COVER_SIZES,
("cover", "cover_medium", "cover_large", "cover_xl"),
)
}
)
raise InvalidSourceError(source)