mirror of
https://github.com/nathom/streamrip.git
synced 2025-05-18 00:54:50 -04:00
Formatting
Signed-off-by: nathom <nathanthomas707@gmail.com>
This commit is contained in:
parent
1e1e3c7062
commit
76ba2d413b
6 changed files with 42 additions and 111 deletions
|
@ -45,9 +45,7 @@ logger = logging.getLogger("streamrip")
|
||||||
|
|
||||||
TYPE_REGEXES = {
|
TYPE_REGEXES = {
|
||||||
"remaster": re.compile(r"(?i)(re)?master(ed)?"),
|
"remaster": re.compile(r"(?i)(re)?master(ed)?"),
|
||||||
"extra": re.compile(
|
"extra": re.compile(r"(?i)(anniversary|deluxe|live|collector|demo|expanded)"),
|
||||||
r"(?i)(anniversary|deluxe|live|collector|demo|expanded)"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -120,15 +118,12 @@ class Track:
|
||||||
if self.client.source == "qobuz":
|
if self.client.source == "qobuz":
|
||||||
self.cover_url = self.resp["album"]["image"]["large"]
|
self.cover_url = self.resp["album"]["image"]["large"]
|
||||||
elif self.client.source == "tidal":
|
elif self.client.source == "tidal":
|
||||||
self.cover_url = tidal_cover_url(
|
self.cover_url = tidal_cover_url(self.resp["album"]["cover"], 320)
|
||||||
self.resp["album"]["cover"], 320
|
|
||||||
)
|
|
||||||
elif self.client.source == "deezer":
|
elif self.client.source == "deezer":
|
||||||
self.cover_url = self.resp["album"]["cover_medium"]
|
self.cover_url = self.resp["album"]["cover_medium"]
|
||||||
elif self.client.source == "soundcloud":
|
elif self.client.source == "soundcloud":
|
||||||
self.cover_url = (
|
self.cover_url = (
|
||||||
self.resp["artwork_url"]
|
self.resp["artwork_url"] or self.resp["user"].get("avatar_url")
|
||||||
or self.resp["user"].get("avatar_url")
|
|
||||||
).replace("large", "t500x500")
|
).replace("large", "t500x500")
|
||||||
else:
|
else:
|
||||||
raise InvalidSourceError(self.client.source)
|
raise InvalidSourceError(self.client.source)
|
||||||
|
@ -175,9 +170,7 @@ class Track:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.download_cover() # only downloads for playlists and singles
|
self.download_cover() # only downloads for playlists and singles
|
||||||
self.path = os.path.join(
|
self.path = os.path.join(gettempdir(), f"{hash(self.id)}_{self.quality}.tmp")
|
||||||
gettempdir(), f"{hash(self.id)}_{self.quality}.tmp"
|
|
||||||
)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def download(
|
def download(
|
||||||
|
@ -352,9 +345,7 @@ class Track:
|
||||||
if not hasattr(self, "cover_url"):
|
if not hasattr(self, "cover_url"):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.cover_path = os.path.join(
|
self.cover_path = os.path.join(gettempdir(), f"cover{hash(self.cover_url)}.jpg")
|
||||||
gettempdir(), f"cover{hash(self.cover_url)}.jpg"
|
|
||||||
)
|
|
||||||
logger.debug(f"Downloading cover from {self.cover_url}")
|
logger.debug(f"Downloading cover from {self.cover_url}")
|
||||||
# click.secho(f"\nDownloading cover art for {self!s}", fg="blue")
|
# click.secho(f"\nDownloading cover art for {self!s}", fg="blue")
|
||||||
|
|
||||||
|
@ -377,18 +368,16 @@ class Track:
|
||||||
formatter = self.meta.get_formatter(max_quality=self.quality)
|
formatter = self.meta.get_formatter(max_quality=self.quality)
|
||||||
logger.debug("Track meta formatter %s", formatter)
|
logger.debug("Track meta formatter %s", formatter)
|
||||||
filename = clean_format(self.file_format, formatter)
|
filename = clean_format(self.file_format, formatter)
|
||||||
self.final_path = os.path.join(self.folder, filename)[
|
self.final_path = os.path.join(self.folder, filename)[:250].strip() + ext(
|
||||||
:250
|
self.quality, self.client.source
|
||||||
].strip() + ext(self.quality, self.client.source)
|
)
|
||||||
|
|
||||||
logger.debug("Formatted path: %s", self.final_path)
|
logger.debug("Formatted path: %s", self.final_path)
|
||||||
|
|
||||||
return self.final_path
|
return self.final_path
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_album_meta(
|
def from_album_meta(cls, album: TrackMetadata, track: dict, client: Client):
|
||||||
cls, album: TrackMetadata, track: dict, client: Client
|
|
||||||
):
|
|
||||||
"""Return a new Track object initialized with info.
|
"""Return a new Track object initialized with info.
|
||||||
|
|
||||||
:param album: album metadata returned by API
|
:param album: album metadata returned by API
|
||||||
|
@ -456,9 +445,7 @@ class Track:
|
||||||
:param embed_cover: Embed cover art into file
|
:param embed_cover: Embed cover art into file
|
||||||
:type embed_cover: bool
|
:type embed_cover: bool
|
||||||
"""
|
"""
|
||||||
assert isinstance(
|
assert isinstance(self.meta, TrackMetadata), "meta must be TrackMetadata"
|
||||||
self.meta, TrackMetadata
|
|
||||||
), "meta must be TrackMetadata"
|
|
||||||
if not self.downloaded:
|
if not self.downloaded:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Track %s not tagged because it was not downloaded",
|
"Track %s not tagged because it was not downloaded",
|
||||||
|
@ -554,9 +541,7 @@ class Track:
|
||||||
"""
|
"""
|
||||||
if not self.downloaded:
|
if not self.downloaded:
|
||||||
logger.debug("Track not downloaded, skipping conversion")
|
logger.debug("Track not downloaded, skipping conversion")
|
||||||
click.secho(
|
click.secho("Track not downloaded, skipping conversion", fg="magenta")
|
||||||
"Track not downloaded, skipping conversion", fg="magenta"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
CONV_CLASS = {
|
CONV_CLASS = {
|
||||||
|
@ -573,21 +558,15 @@ class Track:
|
||||||
try:
|
try:
|
||||||
self.container = codec.upper()
|
self.container = codec.upper()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
click.secho(
|
click.secho("Error: No audio codec chosen to convert to.", fg="red")
|
||||||
"Error: No audio codec chosen to convert to.", fg="red"
|
|
||||||
)
|
|
||||||
raise click.Abort
|
raise click.Abort
|
||||||
|
|
||||||
if not hasattr(self, "final_path"):
|
if not hasattr(self, "final_path"):
|
||||||
self.format_final_path()
|
self.format_final_path()
|
||||||
|
|
||||||
if not os.path.isfile(self.path):
|
if not os.path.isfile(self.path):
|
||||||
logger.info(
|
logger.info("File %s does not exist. Skipping conversion.", self.path)
|
||||||
"File %s does not exist. Skipping conversion.", self.path
|
click.secho(f"{self!s} does not exist. Skipping conversion.", fg="red")
|
||||||
)
|
|
||||||
click.secho(
|
|
||||||
f"{self!s} does not exist. Skipping conversion.", fg="red"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
|
@ -854,9 +833,7 @@ class Tracklist(list):
|
||||||
# Tidal errors out with unlimited concurrency
|
# Tidal errors out with unlimited concurrency
|
||||||
# max_workers = 15 if self.client.source == "tidal" else 90
|
# max_workers = 15 if self.client.source == "tidal" else 90
|
||||||
with concurrent.futures.ThreadPoolExecutor(15) as executor:
|
with concurrent.futures.ThreadPoolExecutor(15) as executor:
|
||||||
futures = [
|
futures = [executor.submit(target, item, **kwargs) for item in self]
|
||||||
executor.submit(target, item, **kwargs) for item in self
|
|
||||||
]
|
|
||||||
try:
|
try:
|
||||||
concurrent.futures.wait(futures)
|
concurrent.futures.wait(futures)
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
|
|
@ -144,7 +144,9 @@ ALBUM_KEYS = (
|
||||||
"composer",
|
"composer",
|
||||||
)
|
)
|
||||||
# TODO: rename these to DEFAULT_FOLDER_FORMAT etc
|
# TODO: rename these to DEFAULT_FOLDER_FORMAT etc
|
||||||
FOLDER_FORMAT = "{albumartist} - {title} ({year}) [{container}] [{bit_depth}B-{sampling_rate}kHz]"
|
FOLDER_FORMAT = (
|
||||||
|
"{albumartist} - {title} ({year}) [{container}] [{bit_depth}B-{sampling_rate}kHz]"
|
||||||
|
)
|
||||||
TRACK_FORMAT = "{tracknumber}. {artist} - {title}"
|
TRACK_FORMAT = "{tracknumber}. {artist} - {title}"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,7 @@ class Converter:
|
||||||
|
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.final_fn = f"{os.path.splitext(filename)[0]}.{self.container}"
|
self.final_fn = f"{os.path.splitext(filename)[0]}.{self.container}"
|
||||||
self.tempfile = os.path.join(
|
self.tempfile = os.path.join(gettempdir(), os.path.basename(self.final_fn))
|
||||||
gettempdir(), os.path.basename(self.final_fn)
|
|
||||||
)
|
|
||||||
self.remove_source = remove_source
|
self.remove_source = remove_source
|
||||||
self.sampling_rate = sampling_rate
|
self.sampling_rate = sampling_rate
|
||||||
self.bit_depth = bit_depth
|
self.bit_depth = bit_depth
|
||||||
|
@ -117,13 +115,9 @@ class Converter:
|
||||||
if self.lossless:
|
if self.lossless:
|
||||||
if isinstance(self.sampling_rate, int):
|
if isinstance(self.sampling_rate, int):
|
||||||
sampling_rates = "|".join(
|
sampling_rates = "|".join(
|
||||||
str(rate)
|
str(rate) for rate in SAMPLING_RATES if rate <= self.sampling_rate
|
||||||
for rate in SAMPLING_RATES
|
|
||||||
if rate <= self.sampling_rate
|
|
||||||
)
|
|
||||||
command.extend(
|
|
||||||
["-af", f"aformat=sample_rates={sampling_rates}"]
|
|
||||||
)
|
)
|
||||||
|
command.extend(["-af", f"aformat=sample_rates={sampling_rates}"])
|
||||||
|
|
||||||
elif self.sampling_rate is not None:
|
elif self.sampling_rate is not None:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
|
@ -138,9 +132,7 @@ class Converter:
|
||||||
else:
|
else:
|
||||||
raise ValueError("Bit depth must be 16, 24, or 32")
|
raise ValueError("Bit depth must be 16, 24, or 32")
|
||||||
elif self.bit_depth is not None:
|
elif self.bit_depth is not None:
|
||||||
raise TypeError(
|
raise TypeError(f"Bit depth must be int, not {type(self.bit_depth)}")
|
||||||
f"Bit depth must be int, not {type(self.bit_depth)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# automatically overwrite
|
# automatically overwrite
|
||||||
command.extend(["-y", self.tempfile])
|
command.extend(["-y", self.tempfile])
|
||||||
|
@ -203,9 +195,7 @@ class Vorbis(Converter):
|
||||||
codec_name = "vorbis"
|
codec_name = "vorbis"
|
||||||
codec_lib = "libvorbis"
|
codec_lib = "libvorbis"
|
||||||
container = "ogg"
|
container = "ogg"
|
||||||
default_ffmpeg_arg = (
|
default_ffmpeg_arg = "-q:a 6" # 160, aka the "high" quality profile from Spotify
|
||||||
"-q:a 6" # 160, aka the "high" quality profile from Spotify
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OPUS(Converter):
|
class OPUS(Converter):
|
||||||
|
|
|
@ -130,9 +130,7 @@ class TrackMetadata:
|
||||||
self.album = resp.get("title", "Unknown Album")
|
self.album = resp.get("title", "Unknown Album")
|
||||||
self.tracktotal = resp.get("tracks_count", 1)
|
self.tracktotal = resp.get("tracks_count", 1)
|
||||||
self.genre = resp.get("genres_list") or resp.get("genre")
|
self.genre = resp.get("genres_list") or resp.get("genre")
|
||||||
self.date = resp.get("release_date_original") or resp.get(
|
self.date = resp.get("release_date_original") or resp.get("release_date")
|
||||||
"release_date"
|
|
||||||
)
|
|
||||||
self.copyright = resp.get("copyright")
|
self.copyright = resp.get("copyright")
|
||||||
self.albumartist = safe_get(resp, "artist", "name")
|
self.albumartist = safe_get(resp, "artist", "name")
|
||||||
self.composer = safe_get(resp, "composer", "name")
|
self.composer = safe_get(resp, "composer", "name")
|
||||||
|
@ -141,9 +139,7 @@ class TrackMetadata:
|
||||||
self.disctotal = (
|
self.disctotal = (
|
||||||
max(
|
max(
|
||||||
track.get("media_number", 1)
|
track.get("media_number", 1)
|
||||||
for track in safe_get(
|
for track in safe_get(resp, "tracks", "items", default=[{}])
|
||||||
resp, "tracks", "items", default=[{}]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
or 1
|
or 1
|
||||||
)
|
)
|
||||||
|
@ -155,9 +151,7 @@ class TrackMetadata:
|
||||||
# Non-embedded information
|
# Non-embedded information
|
||||||
self.version = resp.get("version")
|
self.version = resp.get("version")
|
||||||
self.cover_urls = OrderedDict(resp["image"])
|
self.cover_urls = OrderedDict(resp["image"])
|
||||||
self.cover_urls["original"] = self.cover_urls["large"].replace(
|
self.cover_urls["original"] = self.cover_urls["large"].replace("600", "org")
|
||||||
"600", "org"
|
|
||||||
)
|
|
||||||
self.streamable = resp.get("streamable", False)
|
self.streamable = resp.get("streamable", False)
|
||||||
self.bit_depth = resp.get("maximum_bit_depth")
|
self.bit_depth = resp.get("maximum_bit_depth")
|
||||||
self.sampling_rate = resp.get("maximum_sampling_rate")
|
self.sampling_rate = resp.get("maximum_sampling_rate")
|
||||||
|
@ -191,22 +185,14 @@ class TrackMetadata:
|
||||||
)
|
)
|
||||||
self.streamable = resp.get("allowStreaming", False)
|
self.streamable = resp.get("allowStreaming", False)
|
||||||
|
|
||||||
if q := resp.get(
|
if q := resp.get("audioQuality"): # for album entries in single tracks
|
||||||
"audioQuality"
|
|
||||||
): # for album entries in single tracks
|
|
||||||
self._get_tidal_quality(q)
|
self._get_tidal_quality(q)
|
||||||
|
|
||||||
elif self.__source == "deezer":
|
elif self.__source == "deezer":
|
||||||
self.album = resp.get("title", "Unknown Album")
|
self.album = resp.get("title", "Unknown Album")
|
||||||
self.tracktotal = resp.get("track_total", 0) or resp.get(
|
self.tracktotal = resp.get("track_total", 0) or resp.get("nb_tracks", 0)
|
||||||
"nb_tracks", 0
|
|
||||||
)
|
|
||||||
self.disctotal = (
|
self.disctotal = (
|
||||||
max(
|
max(track.get("disk_number") for track in resp.get("tracks", [{}])) or 1
|
||||||
track.get("disk_number")
|
|
||||||
for track in resp.get("tracks", [{}])
|
|
||||||
)
|
|
||||||
or 1
|
|
||||||
)
|
)
|
||||||
self.genre = safe_get(resp, "genres", "data")
|
self.genre = safe_get(resp, "genres", "data")
|
||||||
self.date = resp.get("release_date")
|
self.date = resp.get("release_date")
|
||||||
|
@ -370,9 +356,7 @@ class TrackMetadata:
|
||||||
|
|
||||||
if isinstance(self._genres, list):
|
if isinstance(self._genres, list):
|
||||||
if self.__source == "qobuz":
|
if self.__source == "qobuz":
|
||||||
genres: Iterable = re.findall(
|
genres: Iterable = re.findall(r"([^\u2192\/]+)", "/".join(self._genres))
|
||||||
r"([^\u2192\/]+)", "/".join(self._genres)
|
|
||||||
)
|
|
||||||
genres = set(genres)
|
genres = set(genres)
|
||||||
elif self.__source == "deezer":
|
elif self.__source == "deezer":
|
||||||
genres = ", ".join(g["name"] for g in self._genres)
|
genres = ", ".join(g["name"] for g in self._genres)
|
||||||
|
@ -404,9 +388,7 @@ class TrackMetadata:
|
||||||
if hasattr(self, "_copyright"):
|
if hasattr(self, "_copyright"):
|
||||||
if self._copyright is None:
|
if self._copyright is None:
|
||||||
return None
|
return None
|
||||||
copyright: str = re.sub(
|
copyright: str = re.sub(r"(?i)\(P\)", PHON_COPYRIGHT, self._copyright)
|
||||||
r"(?i)\(P\)", PHON_COPYRIGHT, self._copyright
|
|
||||||
)
|
|
||||||
copyright = re.sub(r"(?i)\(C\)", COPYRIGHT, copyright)
|
copyright = re.sub(r"(?i)\(C\)", COPYRIGHT, copyright)
|
||||||
return copyright
|
return copyright
|
||||||
|
|
||||||
|
@ -598,9 +580,7 @@ class TrackMetadata:
|
||||||
|
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
return sum(
|
return sum(hash(v) for v in self.asdict().values() if isinstance(v, Hashable))
|
||||||
hash(v) for v in self.asdict().values() if isinstance(v, Hashable)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
"""Return the string representation of the metadata object.
|
"""Return the string representation of the metadata object.
|
||||||
|
|
|
@ -131,9 +131,7 @@ class Album(Tracklist):
|
||||||
tqdm_download(cover_url, cover_path)
|
tqdm_download(cover_url, cover_path)
|
||||||
|
|
||||||
hires_cov_path = os.path.join(self.folder, "cover.jpg")
|
hires_cov_path = os.path.join(self.folder, "cover.jpg")
|
||||||
if kwargs.get("keep_hires_cover", True) and not os.path.exists(
|
if kwargs.get("keep_hires_cover", True) and not os.path.exists(hires_cov_path):
|
||||||
hires_cov_path
|
|
||||||
):
|
|
||||||
tqdm_download(self.cover_urls["original"], hires_cov_path)
|
tqdm_download(self.cover_urls["original"], hires_cov_path)
|
||||||
|
|
||||||
cover_size = os.path.getsize(cover_path)
|
cover_size = os.path.getsize(cover_path)
|
||||||
|
@ -184,9 +182,7 @@ class Album(Tracklist):
|
||||||
"""
|
"""
|
||||||
logger.debug("Downloading track to %s", self.folder)
|
logger.debug("Downloading track to %s", self.folder)
|
||||||
if self.disctotal > 1 and isinstance(track, Track):
|
if self.disctotal > 1 and isinstance(track, Track):
|
||||||
disc_folder = os.path.join(
|
disc_folder = os.path.join(self.folder, f"Disc {track.meta.discnumber}")
|
||||||
self.folder, f"Disc {track.meta.discnumber}"
|
|
||||||
)
|
|
||||||
kwargs["parent_folder"] = disc_folder
|
kwargs["parent_folder"] = disc_folder
|
||||||
else:
|
else:
|
||||||
kwargs["parent_folder"] = self.folder
|
kwargs["parent_folder"] = self.folder
|
||||||
|
@ -273,9 +269,7 @@ class Album(Tracklist):
|
||||||
# lossy codecs don't have these metrics
|
# lossy codecs don't have these metrics
|
||||||
self.bit_depth = self.sampling_rate = None
|
self.bit_depth = self.sampling_rate = None
|
||||||
|
|
||||||
formatted_folder = clean_format(
|
formatted_folder = clean_format(self.folder_format, self._get_formatter())
|
||||||
self.folder_format, self._get_formatter()
|
|
||||||
)
|
|
||||||
|
|
||||||
return os.path.join(parent_folder, formatted_folder)
|
return os.path.join(parent_folder, formatted_folder)
|
||||||
|
|
||||||
|
@ -388,9 +382,7 @@ class Playlist(Tracklist):
|
||||||
if self.client.source == "qobuz":
|
if self.client.source == "qobuz":
|
||||||
self.name = self.meta["name"]
|
self.name = self.meta["name"]
|
||||||
self.image = self.meta["images"]
|
self.image = self.meta["images"]
|
||||||
self.creator = safe_get(
|
self.creator = safe_get(self.meta, "owner", "name", default="Qobuz")
|
||||||
self.meta, "owner", "name", default="Qobuz"
|
|
||||||
)
|
|
||||||
|
|
||||||
tracklist = self.meta["tracks"]["items"]
|
tracklist = self.meta["tracks"]["items"]
|
||||||
|
|
||||||
|
@ -403,9 +395,7 @@ class Playlist(Tracklist):
|
||||||
elif self.client.source == "tidal":
|
elif self.client.source == "tidal":
|
||||||
self.name = self.meta["title"]
|
self.name = self.meta["title"]
|
||||||
self.image = tidal_cover_url(self.meta["image"], 640)
|
self.image = tidal_cover_url(self.meta["image"], 640)
|
||||||
self.creator = safe_get(
|
self.creator = safe_get(self.meta, "creator", "name", default="TIDAL")
|
||||||
self.meta, "creator", "name", default="TIDAL"
|
|
||||||
)
|
|
||||||
|
|
||||||
tracklist = self.meta["tracks"]
|
tracklist = self.meta["tracks"]
|
||||||
|
|
||||||
|
@ -422,9 +412,7 @@ class Playlist(Tracklist):
|
||||||
elif self.client.source == "deezer":
|
elif self.client.source == "deezer":
|
||||||
self.name = self.meta["title"]
|
self.name = self.meta["title"]
|
||||||
self.image = self.meta["picture_big"]
|
self.image = self.meta["picture_big"]
|
||||||
self.creator = safe_get(
|
self.creator = safe_get(self.meta, "creator", "name", default="Deezer")
|
||||||
self.meta, "creator", "name", default="Deezer"
|
|
||||||
)
|
|
||||||
|
|
||||||
tracklist = self.meta["tracks"]
|
tracklist = self.meta["tracks"]
|
||||||
|
|
||||||
|
@ -467,9 +455,7 @@ class Playlist(Tracklist):
|
||||||
|
|
||||||
logger.debug(f"Loaded {len(self)} tracks from playlist {self.name}")
|
logger.debug(f"Loaded {len(self)} tracks from playlist {self.name}")
|
||||||
|
|
||||||
def _prepare_download(
|
def _prepare_download(self, parent_folder: str = "StreamripDownloads", **kwargs):
|
||||||
self, parent_folder: str = "StreamripDownloads", **kwargs
|
|
||||||
):
|
|
||||||
fname = sanitize_filename(self.name)
|
fname = sanitize_filename(self.name)
|
||||||
self.folder = os.path.join(parent_folder, fname)
|
self.folder = os.path.join(parent_folder, fname)
|
||||||
|
|
||||||
|
@ -667,9 +653,7 @@ class Artist(Tracklist):
|
||||||
final = self
|
final = self
|
||||||
|
|
||||||
if isinstance(filters, tuple) and self.client.source == "qobuz":
|
if isinstance(filters, tuple) and self.client.source == "qobuz":
|
||||||
filter_funcs = (
|
filter_funcs = (getattr(self, f"_{filter_}") for filter_ in filters)
|
||||||
getattr(self, f"_{filter_}") for filter_ in filters
|
|
||||||
)
|
|
||||||
for func in filter_funcs:
|
for func in filter_funcs:
|
||||||
final = filter(func, final)
|
final = filter(func, final)
|
||||||
|
|
||||||
|
@ -788,10 +772,7 @@ class Artist(Tracklist):
|
||||||
best_bd = bit_depth(a["bit_depth"] for a in group)
|
best_bd = bit_depth(a["bit_depth"] for a in group)
|
||||||
best_sr = sampling_rate(a["sampling_rate"] for a in group)
|
best_sr = sampling_rate(a["sampling_rate"] for a in group)
|
||||||
for album in group:
|
for album in group:
|
||||||
if (
|
if album["bit_depth"] == best_bd and album["sampling_rate"] == best_sr:
|
||||||
album["bit_depth"] == best_bd
|
|
||||||
and album["sampling_rate"] == best_sr
|
|
||||||
):
|
|
||||||
yield album
|
yield album
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Miscellaneous utility functions."""
|
"""Miscellaneous utility functions."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue