import spotipy import spotipy.oauth2 as oauth2 from spotdl.metadata import ProviderBase from spotdl.metadata.exceptions import SpotifyMetadataNotFoundError from spotdl.authorize.services import AuthorizeSpotify from spotdl.authorize import SpotifyAuthorizationError import spotdl.util import logging logger = logging.getLogger(__name__) class ProviderSpotify(ProviderBase): """ Fetch metadata using Spotify API in standardized form. Parameters ---------- spotify: :class:`spotdl.authorize.services.AuthorizeSpotify`, :class:`spotipy.Spotify`, ``None`` An authorized instance to make API calls to Spotify endpoints. If ``None``, it will attempt to reference an already created :class:`spotdl.authorize.services.AuthorizeSpotify` instance or you can set your own *Client ID* and *Client Secret* by calling :func:`ProviderSpotify.set_credentials` later on. Examples -------- - Fetching a track's metadata using Spotify URI: >>> from spotdl.authorize.services import AuthorizeSpotify # It is necessary to authorize Spotify API otherwise API # calls won't pass through Spotify. That means we won't # be able to fetch metadata from Spotify. >>> AuthorizeSpotify( ... client_id="your_spotify_client_id", ... client_secret="your_spotify_client_secret", ... ) >>> >>> from spotdl.metadata.providers import ProviderSpotify >>> provider = ProviderSpotify() >>> metadata = provider.from_url( ... "https://open.spotify.com/track/0aTiUssEOy0Mt69bsavj6K" ... ) >>> metadata["name"] 'Descending' """ def __init__(self, spotify=None): if spotify is None: try: spotify = AuthorizeSpotify() except SpotifyAuthorizationError: pass self.spotify = spotify def set_credentials(self, client_id, client_secret): """ Set your own credentials to authorize with Spotify API. This is useful if you initially didn't authorize API calls while creating an instance of :class:`ProviderSpotify`. """ token = self._generate_token(client_id, client_secret) self.spotify = spotipy.Spotify(auth=token) def assert_credentials(self): if self.spotify is None: raise SpotifyAuthorizationError( "You must first setup an AuthorizeSpotify instance, or pass " "in client_id and client_secret to the set_credentials method." ) def from_url(self, url): self.assert_credentials() logger.debug('Fetching Spotify metadata for "{url}".'.format(url=url)) metadata = self.spotify.track(url) return self._metadata_to_standard_form(metadata) def from_query(self, query): self.assert_credentials() tracks = self.search(query)["tracks"]["items"] if not tracks: raise SpotifyMetadataNotFoundError( 'Spotify returned no tracks for the search query "{}".'.format( query, ) ) return self._metadata_to_standard_form(tracks[0]) def search(self, query): self.assert_credentials() return self.spotify.search(query) def _generate_token(self, client_id, client_secret): credentials = oauth2.SpotifyClientCredentials( client_secret=client_secret, ) token = credentials.get_access_token() return token def _metadata_to_standard_form(self, metadata): self.assert_credentials() artist = self.spotify.artist(metadata["artists"][0]["id"]) album = self.spotify.album(metadata["album"]["id"]) try: metadata[u"genre"] = spotdl.util.titlecase(artist["genres"][0]) except IndexError: metadata[u"genre"] = None try: metadata[u"copyright"] = album["copyrights"][0]["text"] except IndexError: metadata[u"copyright"] = None try: metadata[u"external_ids"][u"isrc"] except KeyError: metadata[u"external_ids"][u"isrc"] = None metadata[u"release_date"] = album["release_date"] metadata[u"publisher"] = album["label"] metadata[u"total_tracks"] = album["tracks"]["total"] # Some sugar metadata["year"], *_ = metadata["release_date"].split("-") metadata["duration"] = metadata["duration_ms"] / 1000.0 metadata["provider"] = "spotify" # Remove unwanted parameters del metadata["duration_ms"] del metadata["available_markets"] del metadata["album"]["available_markets"] return metadata