def from_search_term( query: str, output_format: str = None, use_youtube: bool = False, lyrics_provider: str = None, ) -> List[SongObject]: """ Queries Spotify for a song and returns the best match `str` `query` : what you'd type into Spotify's search box `str` `output_format` : output format of the song returns a `list<SongObject>` containing Url's of each track in the given album """ # get a spotify client spotify_client = SpotifyClient() # get possible matches from spotify result = spotify_client.search(query, type="track") # return first result link or if no matches are found, raise Exception if result is None or len(result.get("tracks", {}).get("items", [])) == 0: raise Exception("No song matches found on Spotify") song_url = "http://open.spotify.com/track/" + result["tracks"]["items"][0]["id"] try: song = from_spotify_url( song_url, output_format, use_youtube, lyrics_provider, None ) return [song] if song.youtube_link is not None else [] except (LookupError, OSError, ValueError): return []
def from_saved_tracks(output_format: str = None, use_youtube: bool = False, threads: int = 1) -> List[SongObject]: """ Create and return list containing SongObject for every song that user has saved `str` `output_format` : output format of the song returns a `list<songObj>` containing Url's of each track in the user's saved tracks """ spotify_client = SpotifyClient() saved_tracks_response = spotify_client.current_user_saved_tracks() if saved_tracks_response is None: raise Exception("Couldn't get saved tracks") saved_tracks = saved_tracks_response["items"] tracks = [] # Fetch all saved tracks while saved_tracks_response["next"]: response = spotify_client.next(saved_tracks_response) # response is wrong, break if response is None: break saved_tracks_response = response saved_tracks.extend(saved_tracks_response["items"]) # Remove songs without id saved_tracks = [ track for track in saved_tracks if track is not None and track.get("track") is not None and track.get("track", {}).get("id") is not None ] def get_song(track): try: return from_spotify_url( "https://open.spotify.com/track/" + track["track"]["id"], output_format, use_youtube, ) except (LookupError, ValueError, OSError): return None with concurrent.futures.ThreadPoolExecutor( max_workers=threads) as executor: results = executor.map(get_song, saved_tracks) for result in results: if result is not None and result.youtube_link is not None: tracks.append(result) return tracks
def from_url(spotify_url: str): if not ("open.spotify.com" in spotify_url and "track" in spotify_url): raise Exception(f"passed URL is not that of a track: {spotify_url}") # query spotify for song, artist, album details spotify_client = SpotifyClient() raw_track_meta = spotify_client.track(spotify_url) if raw_track_meta is None: raise Exception( "Couldn't get metadata, check if you have passed correct track id") primary_artist_id = raw_track_meta["artists"][0]["id"] raw_artist_meta = spotify_client.artist(primary_artist_id) albumId = raw_track_meta["album"]["id"] raw_album_meta = spotify_client.album(albumId) return raw_track_meta, raw_artist_meta, raw_album_meta
def from_artist( artist_url: str, output_format: str = None, use_youtube: bool = False, lyrics_provider: str = None, threads: int = 1, ) -> List[SongObject]: """ `str` `artist_url` : Spotify Url of the artist whose tracks are to be retrieved returns a `list<songObj>` containing Url's of each track in the artist profile """ spotify_client = SpotifyClient() artist_tracks = [] artist_response = spotify_client.artist_albums( artist_url, album_type="album,single" ) if artist_response is None: raise ValueError("Wrong artist id") albums_list = artist_response["items"] albums_object: Dict[str, str] = {} # Fetch all artist albums while artist_response and artist_response["next"]: response = spotify_client.next(artist_response) if response is None: break artist_response = response albums_list.extend(artist_response["items"]) # Remove duplicate albums for album in albums_list: # return an iterable containing the string's alphanumeric characters alpha_numeric_filter = filter(str.isalnum, album["name"].lower()) # join all characters into one string album_name = "".join(alpha_numeric_filter) if albums_object.get(album_name) is None: albums_object[album_name] = album["uri"] tracks_list = [] tracks_object: Dict[str, str] = {} # Fetch all tracks from all albums for album_uri in albums_object.values(): response = spotify_client.album_tracks(album_uri) if response is None: continue album_response = response album_tracks = album_response["items"] while album_response["next"]: album_response = spotify_client.next(album_response) if album_response is None: break album_tracks.extend(album_response["items"]) tracks_list.extend(album_tracks) # Filter tracks to remove duplicates and songs without our artist for track in tracks_list: # ignore None tracks or tracks without uri if track is not None and track.get("uri") is None: continue # return an iterable containing the string's alphanumeric characters alphaNumericFilter = filter(str.isalnum, track["name"].lower()) # join all characters into one string trackName = "".join(alphaNumericFilter) if tracks_object.get(trackName) is None: for artist in track["artists"]: # get artist id from url # https://api.spotify.com/v1/artists/1fZAAHNWdSM5gqbi9o5iEA/albums # # split string # ['https:', '', 'api.spotify.com', 'v1', 'artists', # '1fZAAHNWdSM5gqbi9o5iEA', 'albums'] # # get second element from the end # '1fZAAHNWdSM5gqbi9o5iEA' artistId = artist_response["href"].split("/")[-2] # ignore tracks that are not from our artist by checking # the id if artist["id"] == artistId: tracks_object[trackName] = track["uri"] # Create song objects from track ids def get_song(track_uri): try: return from_spotify_url( f"https://open.spotify.com/track/{track_uri.split(':')[-1]}", output_format, use_youtube, lyrics_provider, None, ) except (LookupError, ValueError, OSError): return None with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor: results = executor.map(get_song, tracks_object.values()) for result in results: if result is not None and result.youtube_link is not None: artist_tracks.append(result) return artist_tracks
def from_playlist( playlist_url: str, output_format: str = None, use_youtube: bool = False, lyrics_provider: str = None, generate_m3u: bool = False, threads: int = 1, path_template: str = None, ) -> List[SongObject]: """ Create and return list containing SongObject for every song in the playlist `str` `album_url` : Spotify Url of the album whose tracks are to be retrieved `str` `output_format` : output format of the song returns a `list<SongObject>` containing Url's of each track in the given album """ spotify_client = SpotifyClient() tracks = [] playlist_response = spotify_client.playlist_tracks(playlist_url) playlist = spotify_client.playlist(playlist_url) if playlist_response is None: raise ValueError("Wrong playlist id") playlist_tracks = [ track for track in playlist_response["items"] # check if track has id if track is not None and track.get("track") is not None and track["track"].get("id") is not None ] # Get all tracks from playlist while playlist_response["next"]: playlist_response = spotify_client.next(playlist_response) # Failed to get response, break the loop if playlist_response is None: break playlist_tracks.extend(playlist_response["items"]) # Remove songs without id playlist_tracks = [ track for track in playlist_tracks if track is not None and track.get("track") is not None and track["track"].get("id") is not None ] def get_song(track): try: song = from_spotify_url( "https://open.spotify.com/track/" + track["track"]["id"], output_format, use_youtube, lyrics_provider, playlist, ) if generate_m3u: if path_template: file_path = _parse_path_template(path_template, song, output_format) else: file_path = _get_converted_file_path(song, output_format) return song, f"{file_path}\n" return song, None except (LookupError, ValueError): return None, None except OSError: if generate_m3u: song_obj = SongObject( track["track"], {}, {}, None, "", playlist_response ) if path_template: file_path = _parse_path_template( path_template, song_obj, output_format ) else: file_path = _get_converted_file_path(song_obj, output_format) return None, f"{file_path}\n" return None, None with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor: results = executor.map(get_song, playlist_tracks) playlist_text = "" for result in results: if result[1] is not None: playlist_text += result[1] if result[0] is not None and result[0].youtube_link is not None: tracks.append(result[0]) if playlist_response and generate_m3u is True: playlist_data = spotify_client.playlist(playlist_url) if playlist_data is not None: playlist_name = playlist_data["name"] else: playlist_name = playlist_tracks[0]["track"]["name"] playlist_name = format_name(playlist_name) playlist_file = Path(f"{playlist_name}.m3u") with open(playlist_file, "w", encoding="utf-8") as file: file.write(playlist_text) return tracks
def from_album( album_url: str, output_format: str = None, use_youtube: bool = False, lyrics_provider: str = None, generate_m3u: bool = False, threads: int = 1, ) -> List[SongObject]: """ Create and return list containing SongObject for every song in the album `str` `album_url` : Spotify Url of the album whose tracks are to be retrieved `str` `output_format` : output format of the song returns a `list<SongObject>` containing Url's of each track in the given album """ spotify_client = SpotifyClient() tracks = [] album_response = spotify_client.album_tracks(album_url) if album_response is None: raise ValueError("Wrong album id") album_tracks = album_response["items"] # Get all tracks from album while album_response["next"]: album_response = spotify_client.next(album_response) # Failed to get response, break the loop if album_response is None: break album_tracks.extend(album_response["items"]) # Remove songs without id album_tracks = [ track for track in album_tracks if track is not None and track.get("id") is not None ] def get_tracks(track): try: song = from_spotify_url( "https://open.spotify.com/track/" + track["id"], output_format, use_youtube, lyrics_provider, None, ) if generate_m3u: file_path = _get_converted_file_path(song, output_format) return song, f"{file_path}\n" return song, None except (LookupError, ValueError): return None, None except OSError: if generate_m3u: file_path = (str( provider_utils._create_song_title( track["name"], [artist["name"] for artist in track["artists"]], )) + "." + output_format if output_format is not None else "mp3") if len(file_path) > 256: file_path = (str( provider_utils._create_song_title( track["name"], [track["artists"][0]["name"]])) + "." + output_format if output_format is not None else "mp3") return None, f"{file_path}\n" return None, None with concurrent.futures.ThreadPoolExecutor( max_workers=threads) as executor: results = executor.map(get_tracks, album_tracks) album_text = "" for result in results: if result[1] is not None: album_text += "".join(char for char in result[1] if char not in "/?\\*|<>") if result[0] is not None and result[0].youtube_link is not None: tracks.append(result[0]) if album_response and generate_m3u is True: album_data = spotify_client.album(album_url) if album_data is not None: album_name = album_data["name"] else: album_name = album_tracks[0]["name"] album_name = "".join(char for char in album_name if char not in "/?\\*|<>") album_file = Path(f"{album_name}.m3u") with open(album_file, "w", encoding="utf-8") as file: file.write(album_text) return tracks
def console_entry_point(): """ This is where all the console processing magic happens. Its super simple, rudimentary even but, it's dead simple & it works. """ # If -v parameter is specified print version and exit if len(sys.argv) >= 2 and sys.argv[1] in ["-v", "--version"]: version = pkg_resources.require("spotdl")[0].version print(version) sys.exit(0) # Parser arguments arguments = parse_arguments() # Convert arguments to dict args_dict = vars(arguments) # Check if ffmpeg has correct version, if not exit if (ffmpeg.has_correct_version(arguments.ignore_ffmpeg_version, arguments.ffmpeg or "ffmpeg") is False): sys.exit(1) if "saved" in arguments.query and not arguments.user_auth: arguments.user_auth = True print( "Detected 'saved' in command line, but no --user-auth flag. Enabling Anyways." ) print("Please Log In...") # Initialize spotify client SpotifyClient.init( client_id="5f573c9620494bae87890c0f08a60293", client_secret="212476d9b0f3472eaa762d90b19b0ba8", user_auth=arguments.user_auth, ) # Change directory if user specified correct output path if arguments.output: if not os.path.isdir(arguments.output): sys.exit("The output directory doesn't exist.") print(f"Will download to: {os.path.abspath(arguments.output)}") os.chdir(arguments.output) # Start download manager with DownloadManager(args_dict) as downloader: if not arguments.debug_termination: def graceful_exit(signal, frame): downloader.display_manager.close() sys.exit(0) signal.signal(signal.SIGINT, graceful_exit) signal.signal(signal.SIGTERM, graceful_exit) # Find tracking files in queries tracking_files = [ query for query in arguments.query if query.endswith(".spotdlTrackingFile") ] # Restart downloads for tracking_file in tracking_files: print("Preparing to resume download...") downloader.resume_download_from_tracking_file(tracking_file) # Get songs song_list = parse_query( arguments.query, arguments.output_format, arguments.use_youtube, arguments.generate_m3u, arguments.search_threads, ) # Start downloading if len(song_list) > 0: downloader.download_multiple_songs(song_list)
def new_initialize(client_id, client_secret, user_auth): """This function allows calling `initialize()` multiple times""" try: return SpotifyClient() except: return ORIGINAL_INITIALIZE(client_id, client_secret, user_auth)
import os import platform import re from functools import partial from pathlib import Path from pprint import pprint from typing import Callable, List, NamedTuple import spotdl.download as spotdl from spotdl.search import SpotifyClient, from_spotify_url PLAYLIST_RE = re.compile("https?://open\.spotify\.com/playlist/*") SpotifyClient.init( client_id="9fec59cd741a4692b5ae2ccb84a725af", client_secret="4e347cc3d2b74c93b20b57b6a5f75951", user_auth=False, ) CLIENT = SpotifyClient() class Song(NamedTuple): title: str artists: List[str] album: str url: str class Diff(NamedTuple): to_delete: List[Path]