Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions orpheus.py
Original file line number Diff line number Diff line change
Expand Up @@ -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' \
Expand Down Expand Up @@ -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']))
Expand All @@ -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')
Expand Down Expand Up @@ -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]
Expand Down
4 changes: 3 additions & 1 deletion orpheus/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
if os.path.exists('temp'): shutil.rmtree('temp')
82 changes: 60 additions & 22 deletions orpheus/music_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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}')
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}'

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -538,15 +576,15 @@ 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:
old_codec_data = codec_data[codec]
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']:
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down
11 changes: 11 additions & 0 deletions utils/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ class DownloadTypeEnum(Flag):
playlist = auto()
artist = auto()
album = auto()
label = auto()


@dataclass
Expand Down Expand Up @@ -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
Expand Down