diff --git a/requirements.txt b/requirements.txt index 186024e4..bbbfaa68 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ sentry_sdk~=1.5 yt-dlp>=2022.01.21 spotipy~=2.19 mutagen~=1.45 -rich~=12.0 +rich~=12.0 \ No newline at end of file diff --git a/spotify_dl/spotify.py b/spotify_dl/spotify.py index 879dc98e..4fbcaa7d 100644 --- a/spotify_dl/spotify.py +++ b/spotify_dl/spotify.py @@ -2,7 +2,8 @@ from spotify_dl.scaffold import log from spotify_dl.utils import sanitize -from rich.progress import Progress + + def fetch_tracks(sp, item_type, url): """ Fetches tracks from the provided URL. @@ -15,85 +16,77 @@ def fetch_tracks(sp, item_type, url): offset = 0 if item_type == 'playlist': - with Progress() as progress: - songs_task = progress.add_task(description="Fetching songs from playlist..") - while True: - items = sp.playlist_items(playlist_id=url, - fields='items.track.name,items.track.artists(name, uri),' - 'items.track.album(name, release_date, total_tracks, images),' - 'items.track.track_number,total, next,offset,' - 'items.track.id', - additional_types=['track'], offset=offset) - total_songs = items.get('total') - track_info_task = progress.add_task(description="Fetching track info", total=len(items['items'])) - for item in items['items']: - track_info = item.get('track') - # If the user has a podcast in their playlist, there will be no track - # Without this conditional, the program will fail later on when the metadata is fetched - if track_info is None: - offset += 1 - continue - track_album_info = track_info.get('album') - track_num = track_info.get('track_number') - spotify_id = track_info.get('id') - track_name = track_info.get('name') - track_artist = ", ".join([artist['name'] for artist in track_info.get('artists')]) - if track_album_info: - track_album = track_album_info.get('name') - track_year = track_album_info.get('release_date')[:4] if track_album_info.get('release_date') else '' - album_total = track_album_info.get('total_tracks') - if len(item['track']['album']['images']) > 0: - cover = item['track']['album']['images'][0]['url'] - else: - cover = None - - artists = track_info.get('artists') - main_artist_id = artists[0].get('uri', None) if len(artists) > 0 else None - genres = sp.artist(artist_id=main_artist_id).get('genres', []) if main_artist_id else [] - if len(genres) > 0: - genre = genres[0] - else: - genre = "" - songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year, - "num_tracks": album_total, "num": track_num, "playlist_num": offset + 1, - "cover": cover, "genre": genre, "spotify_id": spotify_id}) + while True: + items = sp.playlist_items(playlist_id=url, + fields='items.track.name,items.track.artists(name, uri),' + 'items.track.album(name, release_date, total_tracks, images),' + 'items.track.track_number,total, next,offset,' + 'items.track.id', + additional_types=['track'], offset=offset) + total_songs = items.get('total') + for item in items['items']: + track_info = item.get('track') + # If the user has a podcast in their playlist, there will be no track + # Without this conditional, the program will fail later on when the metadata is fetched + if track_info is None: offset += 1 - progress.update(task_id=track_info_task, description=f"Fetching track info for \n{track_name}",advance=1) - progress.update(task_id=songs_task, description=f"Fetched {offset} of {total_songs} songs from the playlist", advance=100, total=total_songs) - if total_songs == offset: - break - - elif item_type == 'album': - with Progress() as progress: - album_songs_task = progress.add_task(description="Fetching songs from the album..") - while True: - album_info = sp.album(album_id=url) - items = sp.album_tracks(album_id=url, offset=offset) - total_songs = items.get('total') - track_album = album_info.get('name') - track_year = album_info.get('release_date')[:4] if album_info.get('release_date') else '' - album_total = album_info.get('total_tracks') - if len(album_info['images']) > 0: - cover = album_info['images'][0]['url'] + continue + track_album_info = track_info.get('album') + track_num = track_info.get('track_number') + spotify_id = track_info.get('id') + track_name = track_info.get('name') + track_artist = ", ".join([artist['name'] for artist in track_info.get('artists')]) + if track_album_info: + track_album = track_album_info.get('name') + track_year = track_album_info.get('release_date')[:4] if track_album_info.get('release_date') else '' + album_total = track_album_info.get('total_tracks') + if len(item['track']['album']['images']) > 0: + cover = item['track']['album']['images'][0]['url'] else: cover = None - if len(sp.artist(artist_id=album_info['artists'][0]['uri'])['genres']) > 0: - genre = sp.artist(artist_id=album_info['artists'][0]['uri'])['genres'][0] + + artists = track_info.get('artists') + main_artist_id = artists[0].get('uri', None) if len(artists) > 0 else None + genres = sp.artist(artist_id=main_artist_id).get('genres', []) if main_artist_id else [] + if len(genres) > 0: + genre = genres[0] else: - genre = "" - for item in items['items']: - track_name = item.get('name') - track_artist = ", ".join([artist['name'] for artist in item['artists']]) - track_num = item['track_number'] - spotify_id = item.get('id') - songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year, - "num_tracks": album_total, "num": track_num, "playlist_num": offset + 1, - "cover": cover, "genre": genre, "spotify_id": spotify_id}) - offset += 1 + genre = "" + songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year, + "num_tracks": album_total, "num": track_num, "playlist_num": offset + 1, + "cover": cover, "genre": genre, "spotify_id": spotify_id}) + offset += 1 + if total_songs == offset: + break + + elif item_type == 'album': + while True: + album_info = sp.album(album_id=url) + items = sp.album_tracks(album_id=url, offset=offset) + total_songs = items.get('total') + track_album = album_info.get('name') + track_year = album_info.get('release_date')[:4] if album_info.get('release_date') else '' + album_total = album_info.get('total_tracks') + if len(album_info['images']) > 0: + cover = album_info['images'][0]['url'] + else: + cover = None + if len(sp.artist(artist_id=album_info['artists'][0]['uri'])['genres']) > 0: + genre = sp.artist(artist_id=album_info['artists'][0]['uri'])['genres'][0] + else: + genre = "" + for item in items['items']: + track_name = item.get('name') + track_artist = ", ".join([artist['name'] for artist in item['artists']]) + track_num = item['track_number'] + spotify_id = item.get('id') + songs_list.append({"name": track_name, "artist": track_artist, "album": track_album, "year": track_year, + "num_tracks": album_total, "num": track_num, "playlist_num": offset + 1, + "cover": cover, "genre": genre, "spotify_id": spotify_id}) + offset += 1 - progress.update(task_id=album_songs_task, description=f"Fetched {offset} of {album_total} songs from the album {track_album}", advance=offset, total=album_total) - if album_total == offset: - break + if album_total == offset: + break elif item_type == 'track': items = sp.track(track_id=url) diff --git a/spotify_dl/spotify_dl.py b/spotify_dl/spotify_dl.py old mode 100755 new mode 100644 index 11136ba8..6b3e7dc5 --- a/spotify_dl/spotify_dl.py +++ b/spotify_dl/spotify_dl.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse import json +from multiprocessing.dummy import Process import os import sys from logging import DEBUG @@ -74,32 +75,43 @@ def spotify_dl(): C_ID, C_SECRET = tokens sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials(client_id=C_ID,client_secret=C_SECRET)) - log.debug('Arguments: {}'.format(args)) + log.debug(f'Arguments: {args}') + processes = [] for url in args.url: - if url: - valid_item = validate_spotify_url(url) - - if not valid_item: - sys.exit(1) - - - - if args.output: - item_type, item_id = parse_spotify_url(url) - directory_name = get_item_name(sp, item_type, item_id) - save_path = Path(PurePath.joinpath(Path(args.output), Path(directory_name))) - save_path.mkdir(parents=True, exist_ok=True) - console.print(f"Saving songs to [bold green]{directory_name}[/bold green] directory") - songs = fetch_tracks(sp, item_type, url) - else: - songs = {} - if args.download is True: - file_name_f = default_filename - if args.keep_playlist_order: - file_name_f = playlist_num_filename - if save_path is not None: - download_songs(songs, save_path, args.format_str, args.skip_mp3, args.keep_playlist_order, args.no_overwrites, args.skip_non_music_sections, file_name_f) + processes.append(Process(target=download_for_one_link, args=(url, args, sp))) + processes[-1].start() + + for process in processes: + process.join() + + +def download_for_one_link(url, args, sp) : + """ + Downloads songs for one playlist/album/track url. + This function with the required arg can be passed to + Process constructor to initiate parallel download. + """ + if url: + valid_item = validate_spotify_url(url) + + if not valid_item: + sys.exit(1) + + if args.output: + item_type, item_id = parse_spotify_url(url) + directory_name = get_item_name(sp, item_type, item_id) + save_path = Path(PurePath.joinpath(Path(args.output), Path(directory_name))) + save_path.mkdir(parents=True, exist_ok=True) + console.print(f"Saving songs to [bold green]{directory_name}[/bold green] directory") + + songs = fetch_tracks(sp, item_type, url) + if args.download is True: + file_name_f = default_filename + if args.keep_playlist_order: + file_name_f = playlist_num_filename + + download_songs(songs, save_path, args.format_str, args.skip_mp3, args.keep_playlist_order, args.no_overwrites, args.skip_non_music_sections, file_name_f) if __name__ == '__main__':