class YoutubeClient: """ The Youtube client class used to interface with the Youtube API. """ def __init__(self): self._api_service_name = "youtube" self._api_version = "v3" # The scopes of permissions request from the user self._scopes = ["https://www.googleapis.com/auth/youtube.readonly"] # Parameter store to get and update Spotify secrets self._parameter_store = ParameterStore('Youtube', constants.YOUTUBE_SECRETS) self._credentials = self._init_credentials() self._client = self._init_youtube_client() def _init_credentials(self): """ Instantiates and returns a Credentials object. This is used to instantiate the Youtube API client, and to refresh the Youtube access token on expiration. """ youtube_secrets = self._parameter_store.get_secrets() return Credentials ( token=youtube_secrets.get("access_token"), refresh_token=youtube_secrets.get("refresh_token"), token_uri=youtube_secrets.get("token_uri"), client_id=youtube_secrets.get("client_id"), client_secret=youtube_secrets.get("client_secret"), scopes=self._scopes ) def _init_youtube_client(self): """ Instantiates and returns a Youtube API client. """ return build( self._api_service_name, self._api_version, credentials=self._credentials, cache_discovery=False ) def refresh(self): """ Refreshes the Youtube access token. """ self._credentials.refresh(Request()) self._parameter_store.update_secrets({ "access_token": self._credentials.token, "refresh_token": self._credentials.refresh_token }) self._client = build( self._api_service_name, self._api_version, credentials=self._credentials ) def get_liked_videos(self, pageToken = None): """ Returns the provided page of the user's liked Youtube videos """ request = self._client.videos().list( part="snippet", maxResults=10, myRating="like", pageToken=pageToken, fields="items(id,snippet.title),nextPageToken" ) return request.execute() def get_valid_songs(self, response, recent_video_id): """ Iterates through the provided liked videos response from the Youtube API, and uses YoutubeDL to parse out the videos that are music tracks. """ valid_songs = [] already_processed = False ydl_opts = { 'skip_download': True, 'quiet': True, 'no_warnings': True } for item in response["items"]: if item["id"] == recent_video_id: print("[NOTICE] Reached already processed video.") already_processed = True break youtube_url = "https://www.youtube.com/watch?v={}".format( item["id"] ) try: # Get a Youtube video's info video = YoutubeDL(ydl_opts).extract_info( youtube_url, download=False ) except: continue song_name = video["track"] artist = video["artist"] if song_name and artist: # If the video is a music track, add it to the valid songs array valid_songs.append ({ "title": song_name, "artist": artist }) return valid_songs, already_processed def store_recent_video_id(self, video_id): """ Stores the video id of the most recently liked video. """ self._parameter_store.update_secrets({ "recent_video_id": video_id }) def get_recent_video_id(self): """ Returns the video id of the most recently liked video. """ youtube_secrets = self._parameter_store.get_secrets() return youtube_secrets.get("recent_video_id")
class SpotifyClient: """ The Spotify client class used to interface with the Spotify Web API. """ def __init__(self): # The scope of permissions request from the user self._scope = "playlist-modify-public" # Parameter store to get and update Spotify secrets self._parameter_store = ParameterStore('Spotify', constants.SPOTIFY_SECRETS) self._sp_oauth = self._init_spotify_oauth() self._client = self._init_spotify_client() def _init_spotify_oauth(self): """ Instantiates and returns a SpotifyOAuth object. This is used to refresh the Spotify access token on expiration. """ spotify_secrets = self._parameter_store.get_secrets() return oauth2.SpotifyOAuth( client_id=spotify_secrets.get("client_id"), client_secret=spotify_secrets.get("client_secret"), redirect_uri=spotify_secrets.get("redirect_uri"), scope=self._scope) def _init_spotify_client(self): """ Instantiates and returns a Spotify Web API client. """ spotify_secrets = self._parameter_store.get_secrets() return spotipy.Spotify(auth=spotify_secrets.get("access_token")) def refresh(self): """ Refreshes the Spotify access token. """ spotify_secrets = self._parameter_store.get_secrets() token_info = self._sp_oauth.refresh_access_token( spotify_secrets.get('refresh_token')) self._parameter_store.update_secrets({ "access_token": token_info["access_token"], "refresh_token": token_info["refresh_token"] }) self._client = spotipy.Spotify(auth=token_info.get("access_token")) def get_playlist(self): """ Returns the playlist id of the user's "Youtube Liked Videos" playlist on Spotify """ spotify_secrets = self._parameter_store.get_secrets() playlist_id = spotify_secrets.get("playlist_id") if playlist_id: # If a playlist parameter exists, check to see that it exists # in the user's spotify and return its id try: playlist = self._client.playlist(playlist_id) return playlist['id'] except spotipy.exceptions.SpotifyException: # If there is an exception (i.e. the playlist does not exist), # create it and return its id return self._create_playlist() else: # If the playlist does not exist, create it and return its id return self._create_playlist() def _create_playlist(self): """ Creates a new Spotify playlist and returns its id """ user_id = self._client.me()['id'] response = self._client.user_playlist_create( user=user_id, name="Youtube Liked Songs", public=False, description="My Youtube liked songs") playlist_id = response["id"] self._parameter_store.update_secrets({"playlist_id": playlist_id}) return playlist_id def add_songs_to_playlist(self, songs, playlist_id, existing_track_uris): """ Adds the provided tracks to the provided playlist """ if not songs: return track_uris = [] for song in songs: # Query the Spotify API for the current track search_results = self._client.search( self._format_query(song["title"], song["artist"])) tracks = search_results["tracks"]["items"] if not tracks: # If the track could not be found on Spotify, continue continue track_uri = tracks[0]["uri"] if track_uri in existing_track_uris: # If the track already exists in the playlist, continue print("Track already in playlist") continue track_uris.append(track_uri) if not track_uris: return user_id = self._client.me()['id'] self._client.user_playlist_add_tracks(user_id, playlist_id, track_uris) def _format_query(self, title, artist): """ Formats the track query for the Spotify API """ return "{track} artist:{artist}".format(track=title, artist=artist) def get_existing_tracks(self, playlist_id): """ Returns the uris of all the tracks currently in the playlist """ track_uris = [] user_id = self._client.me()['id'] playlist = self._client.user_playlist(user_id, playlist_id) while playlist: # Iterate through all pages of track results from the playlist for track in playlist["tracks"]["items"]: track_uris.append(track["track"]['uri']) if playlist["tracks"]["next"]: playlist = self._client.next(playlist["tracks"]) else: playlist = None return track_uris