From f003731e3189107063189147afca84ee0c8f4301 Mon Sep 17 00:00:00 2001 From: samadhi-spice <128258751+samadhi-spice@users.noreply.github.com> Date: Tue, 28 Mar 2023 02:51:52 -0600 Subject: [PATCH] Added label type DownloadTypeEnum.label class LabelInfo: def download_label() --- orpheus.py | 25 +++++------ orpheus/core.py | 4 +- orpheus/music_downloader.py | 82 +++++++++++++++++++++++++++---------- utils/models.py | 11 +++++ 4 files changed, 87 insertions(+), 35 deletions(-) diff --git a/orpheus.py b/orpheus.py index a792c88..7c8467f 100755 --- a/orpheus.py +++ b/orpheus.py @@ -10,16 +10,16 @@ def main(): print(''' - ____ _ _____ _ - / __ \ | | | __ \| | - | | | |_ __ _ __ | |__ ___ _ _ ___| | | | | - | | | | '__| '_ \| '_ \ / _ \ | | / __| | | | | - | |__| | | | |_) | | | | __/ |_| \__ \ |__| | |____ + ____ _ _____ _ + / __ \ | | | __ \| | + | | | |_ __ _ __ | |__ ___ _ _ ___| | | | | + | | | | '__| '_ \| '_ \ / _ \ | | / __| | | | | + | |__| | | | |_) | | | | __/ |_| \__ \ |__| | |____ \____/|_| | .__/|_| |_|\___|\__,_|___/_____/|______| - | | - |_| + | | + |_| \n''') - + help_ = 'Use "settings [option]" for orpheus controls (coreupdate, fullupdate, modinstall), "settings [module]' \ '[option]" for module specific options (update, test, setup), searching by "[search/luckysearch] [module]' \ '[track/artist/playlist/album] [query]", or just putting in urls. (you may need to wrap the URLs in double' \ @@ -108,7 +108,7 @@ def main(): except KeyError: raise Exception(f'{args.arguments[2].lower()} is not a valid search type! Choose {media_types}') lucky_mode = True if orpheus_mode == 'luckysearch' else False - + query = ' '.join(args.arguments[3:]) module = orpheus.load_module(modulename) items = module.search(query_type, query, limit = (1 if lucky_mode else orpheus.settings['global']['general']['search_limit'])) @@ -128,7 +128,7 @@ def main(): print(f'{str(index)}. {item.name} - {", ".join(artists)} {additional_details}') else: print(f'{str(index)}. {item.name} {additional_details}') - + selection_input = input('Selection: ') if selection_input.lower() in ['e', 'q', 'x', 'exit', 'quit']: exit() if not selection_input.isdigit(): raise Exception('Input a number') @@ -182,14 +182,15 @@ def main(): if not components or len(components) <= 2: print(f'\tInvalid URL: "{link}"') exit() # TODO: replace with InvalidInput - + url_constants = orpheus.module_settings[service_name].url_constants if not url_constants: url_constants = { 'track': DownloadTypeEnum.track, 'album': DownloadTypeEnum.album, 'playlist': DownloadTypeEnum.playlist, - 'artist': DownloadTypeEnum.artist + 'artist': DownloadTypeEnum.artist, + 'label': DownloadTypeEnum.label } type_matches = [media_type for url_check, media_type in url_constants.items() if url_check in components] diff --git a/orpheus/core.py b/orpheus/core.py index bed2ee8..346da49 100644 --- a/orpheus/core.py +++ b/orpheus/core.py @@ -401,7 +401,9 @@ def orpheus_core_download(orpheus_session: Orpheus, media_to_download, third_par downloader.download_playlist(media_id, extra_kwargs=media.extra_kwargs) elif mediatype is DownloadTypeEnum.artist: downloader.download_artist(media_id, extra_kwargs=media.extra_kwargs) + elif mediatype is DownloadTypeEnum.label: + downloader.download_label(media_id, extra_kwargs=media.extra_kwargs) else: raise Exception(f'\tUnknown media type "{mediatype}"') - if os.path.exists('temp'): shutil.rmtree('temp') \ No newline at end of file + if os.path.exists('temp'): shutil.rmtree('temp') diff --git a/orpheus/music_downloader.py b/orpheus/music_downloader.py index 19e3abf..71929b2 100644 --- a/orpheus/music_downloader.py +++ b/orpheus/music_downloader.py @@ -27,7 +27,7 @@ def beauty_format_seconds(seconds: int) -> str: class Downloader: def __init__(self, settings, module_controls, oprinter, path): - self.path = path if path.endswith('/') else path + '/' + self.path = path if path.endswith('/') else path + '/' self.third_party_modules = None self.download_mode = None self.service = None @@ -75,22 +75,22 @@ def download_playlist(self, playlist_id, custom_module=None, extra_kwargs={}): number_of_tracks = len(playlist_info.tracks) self.print(f'Number of tracks: {number_of_tracks!s}') self.print(f'Service: {self.module_settings[self.service_name].service_name}') - + playlist_tags = {k: sanitise_name(v) for k, v in asdict(playlist_info).items()} playlist_tags['explicit'] = ' [E]' if playlist_info.explicit else '' playlist_path = self.path + self.global_settings['formatting']['playlist_format'].format(**playlist_tags) # fix path byte limit playlist_path = fix_byte_limit(playlist_path) + '/' os.makedirs(playlist_path, exist_ok=True) - + if playlist_info.cover_url: self.print('Downloading playlist cover') download_file(playlist_info.cover_url, f'{playlist_path}cover.{playlist_info.cover_type.name}', artwork_settings=self._get_artwork_settings()) - + if playlist_info.animated_cover_url and self.global_settings['covers']['save_animated_cover']: self.print('Downloading animated playlist cover') download_file(playlist_info.animated_cover_url, playlist_path + 'cover.mp4', enable_progress_bar=True) - + if playlist_info.description: with open(playlist_path + 'description.txt', 'w', encoding='utf-8') as f: f.write(playlist_info.description) @@ -113,7 +113,7 @@ def download_playlist(self, playlist_id, custom_module=None, extra_kwargs={}): tracks_errored = set() if custom_module: - supported_modes = self.module_settings[custom_module].module_supported_modes + supported_modes = self.module_settings[custom_module].module_supported_modes if ModuleModes.download not in supported_modes and ModuleModes.playlist not in supported_modes: raise Exception(f'Module "{custom_module}" cannot be used to download a playlist') # TODO: replace with ModuleDoesNotSupportAbility self.print(f'Service used for downloading: {self.module_settings[custom_module].service_name}') @@ -129,12 +129,12 @@ def download_playlist(self, playlist_id, custom_module=None, extra_kwargs={}): proprietary_codecs = self.global_settings['codecs']['proprietary_codecs'], ) track_info: TrackInfo = self.loaded_modules[original_service].get_track_info(track_id, quality_tier, codec_options, **playlist_info.track_extra_kwargs) - + self.service = self.loaded_modules[custom_module] self.service_name = custom_module results = self.search_by_tags(custom_module, track_info) track_id_new = results[0].result_id if len(results) else None - + if track_id_new: self.download_track(track_id_new, album_location=playlist_path, track_index=index, number_of_tracks=number_of_tracks, indent_level=2, m3u_playlist=m3u_playlist_path, extra_kwargs=results[0].extra_kwargs) else: @@ -213,7 +213,7 @@ def download_album(self, album_id, artist_name='', path=None, indent_level=1, ex if number_of_tracks > 1 or self.global_settings['formatting']['force_album_format']: # Creates the album_location folders album_path = self._create_album_location(path, album_id, album_info) - + if self.download_mode is DownloadTypeEnum.album: self.set_indent_number(1) elif self.download_mode is DownloadTypeEnum.artist: @@ -229,7 +229,7 @@ def download_album(self, album_id, artist_name='', path=None, indent_level=1, ex if album_info.booklet_url and not os.path.exists(album_path + 'Booklet.pdf'): self.print('Downloading booklet') download_file(album_info.booklet_url, album_path + 'Booklet.pdf') - + cover_temp_location = download_to_temp(album_info.all_track_cover_jpg_url) if album_info.all_track_cover_jpg_url else '' # Download booklet, animated album cover and album cover if present @@ -249,6 +249,44 @@ def download_album(self, album_id, artist_name='', path=None, indent_level=1, ex return album_info.tracks + def download_label(self, label_id, extra_kwargs={}): + label_info: LabelInfo = self.service.get_label_info(label_id, **extra_kwargs) + label_name = label_info.label_name + + self.set_indent_number(1) + + number_of_albums = len(label_info.albums) + number_of_tracks = len(label_info.tracks) + + self.print(f'=== Downloading label {label_name} ({label_id}) ===', drop_level=1) + if number_of_albums: self.print(f'Number of albums: {number_of_albums!s}') + if number_of_tracks: self.print(f'Number of tracks: {number_of_tracks!s}') + self.print(f'Service: {self.module_settings[self.service_name].service_name}') + label_path = self.path + sanitise_name(label_name) + '/' + + self.set_indent_number(2) + tracks_downloaded = [] + for index, album_id in enumerate(label_info.albums, start=1): + artist_name = label_info.names[index-1] + artist_path = label_path + sanitise_name(artist_name) + '/' + + print() + self.print(f'Album {index}/{number_of_albums}', drop_level=1) + tracks_downloaded += self.download_album(album_id, artist_name=artist_name, path=artist_path, indent_level=2, extra_kwargs=label_info.album_extra_kwargs) + + self.set_indent_number(2) + tracks_to_download = [i for i in label_info.tracks if (i not in tracks_downloaded)] + number_of_tracks_new = len(tracks_to_download) + for index, track_id in enumerate(tracks_to_download, start=1): + print() + self.print(f'Track {index}/{number_of_tracks_new}', drop_level=1) + self.download_track(track_id, album_location=artist_path, main_artist=artist_name, number_of_tracks=1, indent_level=2, extra_kwargs=label_info.track_extra_kwargs) + + self.set_indent_number(1) + tracks_skipped = number_of_tracks - number_of_tracks_new + if tracks_skipped > 0: self.print(f'Tracks skipped: {tracks_skipped!s}', drop_level=1) + self.print(f'=== Label {label_name} downloaded ===', drop_level=1) + def download_artist(self, artist_id, extra_kwargs={}): artist_info: ArtistInfo = self.service.get_artist_info(artist_id, self.global_settings['artist_downloading']['return_credited_albums'], **extra_kwargs) artist_name = artist_info.name @@ -292,7 +330,7 @@ def download_track(self, track_id, album_location='', main_artist='', track_inde proprietary_codecs = self.global_settings['codecs']['proprietary_codecs'], ) track_info: TrackInfo = self.service.get_track_info(track_id, quality_tier, codec_options, **extra_kwargs) - + if main_artist.lower() not in [i.lower() for i in track_info.artists] and self.global_settings['advanced']['ignore_different_artists'] and self.download_mode is DownloadTypeEnum.artist: self.print('Track is not from the correct artist, skipping', drop_level=1) return @@ -364,7 +402,7 @@ def download_track(self, track_id, album_location='', main_artist='', track_inde except: conversions = {} self.print('Warning: codec_conversions setting is invalid!') - + container = codec_data[codec].container track_location = f'{track_location_name}.{container.name}' @@ -418,13 +456,13 @@ def download_track(self, track_id, album_location='', main_artist='', track_inde covers_module_name = covers_module_name if covers_module_name != self.service_name else None if covers_module_name: print() self.print('Downloading artwork' + ((' with ' + covers_module_name) if covers_module_name else '')) - + jpg_cover_options = CoverOptions(file_type=ImageFileTypeEnum.jpg, resolution=self.global_settings['covers']['main_resolution'], \ compression=CoverCompressionEnum[self.global_settings['covers']['main_compression'].lower()]) ext_cover_options = CoverOptions(file_type=ImageFileTypeEnum[self.global_settings['covers']['external_format']], \ resolution=self.global_settings['covers']['external_resolution'], \ compression=CoverCompressionEnum[self.global_settings['covers']['external_compression'].lower()]) - + if covers_module_name: default_temp = download_to_temp(track_info.cover_url) test_cover_options = CoverOptions(file_type=ImageFileTypeEnum.jpg, resolution=get_image_resolution(default_temp), compression=CoverCompressionEnum.high) @@ -480,7 +518,7 @@ def download_track(self, track_id, album_location='', main_artist='', track_inde else: lyrics_track_id = track_id extra_kwargs = {} - + if lyrics_track_id: lyrics_info: LyricsInfo = lyrics_module.get_track_lyrics(lyrics_track_id, **extra_kwargs) # if lyrics_info.embedded or lyrics_info.synced: @@ -522,7 +560,7 @@ def download_track(self, track_id, album_location='', main_artist='', track_inde else: credits_track_id = track_id extra_kwargs = {} - + if credits_track_id: credits_list = credits_module.get_track_credits(credits_track_id, **extra_kwargs) # if credits_list: @@ -538,7 +576,7 @@ def download_track(self, track_id, album_location='', main_artist='', track_inde # self.print('Credits retrieved') # else: # self.print('No credits available') - + # Do conversions old_track_location, old_container = None, None if codec in conversions: @@ -546,7 +584,7 @@ def download_track(self, track_id, album_location='', main_artist='', track_inde new_codec = conversions[codec] new_codec_data = codec_data[new_codec] self.print(f'Converting to {new_codec_data.pretty_name}') - + if old_codec_data.spatial or new_codec_data.spatial: self.print('Warning: converting spacial formats is not allowed, skipping') elif not old_codec_data.lossless and new_codec_data.lossless and not self.global_settings['advanced']['enable_undesirable_conversions']: @@ -564,11 +602,11 @@ def download_track(self, track_id, album_location='', main_artist='', track_inde except: conversion_flags = {} self.print('Warning: conversion_flags setting is invalid, using defaults') - + conv_flags = conversion_flags[new_codec] if new_codec in conversion_flags else {} temp_track_location = f'{create_temp_filename()}.{new_codec_data.container.name}' new_track_location = f'{track_location_name}.{new_codec_data.container.name}' - + stream: ffmpeg = ffmpeg.input(track_location, hide_banner=None, y=None) # capture_stderr is required for the error output to be captured try: @@ -612,7 +650,7 @@ def download_track(self, track_id, album_location='', main_artist='', track_inde else: silentremove(track_location) - container = new_codec_data.container + container = new_codec_data.container track_location = new_track_location # Add the playlist track to the m3u playlist @@ -631,7 +669,7 @@ def download_track(self, track_id, album_location='', main_artist='', track_inde self.print('Tagging failed, tags saved to text file') if delete_cover: silentremove(cover_temp_location) - + self.print(f'=== Track {track_id} downloaded ===', drop_level=1) def _get_artwork_settings(self, module_name = None, is_external = False): diff --git a/utils/models.py b/utils/models.py index cb75b00..5116e74 100644 --- a/utils/models.py +++ b/utils/models.py @@ -181,6 +181,7 @@ class DownloadTypeEnum(Flag): playlist = auto() artist = auto() album = auto() + label = auto() @dataclass @@ -312,6 +313,16 @@ class ArtistInfo: track_extra_kwargs: Optional[dict] = field(default_factory=dict) +@dataclass +class LabelInfo: + label_name: str + names: Optional[list] = field(default_factory=list) + albums: Optional[list] = field(default_factory=list) + album_extra_kwargs: Optional[dict] = field(default_factory=dict) + tracks: Optional[list] = field(default_factory=list) + track_extra_kwargs: Optional[dict] = field(default_factory=dict) + + @dataclass class PlaylistInfo: name: str