def update_singles_playlists(**kwargs): # Don't want artist.singles_df to instantiate # a new spotify (sp) object for every artist df = dataframe() sp = utilities.get_user_sp() sdfs = [] artist_names = [] time_window = kwargs.get('time_window', 100) cutoff = datetime.today() - timedelta(days=time_window) n_artists = kwargs.get('n_artists', len(df.track_artist_id.unique())) artist_ids = list(df.track_artist_id.unique()[600:n_artists]) for i, artist_id in enumerate(artist_ids): sdf = artist.singles_df(artist_id=artist_id, sp=sp) print(f'artist {i+1} of {len(artist_ids)}') cutoff_date = '-'.join( [str(integer).zfill(2) for integer in cutoff.isocalendar()]) if sdf.empty == True: print(f'No singles from {artist_name} since {cutoff_date}') else: artist_name = sdf.artist_name.iloc[0] # Drop singles from before the cut off date, which # defaults to current day - offset bool_inds = [] for rd in sdf.release_date: try: bool_ind = datetime.fromisoformat(rd) >= cutoff except: bool_ind = False bool_inds.append(bool_ind) sdf = sdf.loc[bool_inds, :] sdfs.append(sdf) artist_names.append(artist_name) for single_album_id in sdf.album_id.values: try: album = sp.album(single_album_id) tracks = sp.album_tracks(single_album_id)['items'] except: # Might need to refresh an expired token sp = utilities.get_user_sp() album = sp.album(single_album_id) tracks = sp.album_tracks(single_album_id)['items'] rd = album['release_date'] for track in tracks: add_single_to_playlist(track, release_date=rd, suffix='auto')
def add_track_to_playlist(track, playlist_name, **kwargs): """ Look up the playlist id corresponding to playlist_name in the dataframe returned by database() and add track to that playlist. This function won't add track to playlist if track is already on that playlist. """ assert type(track) == dict, "track must be a dictionary" db = database() sp = kwargs.get('sp', utilities.get_user_sp()) # If the sp object was passed as a kwarg, we need to make # sure it isn't expired try: sp.user(constants.user_vars['username']) except: sp = utilities.get_user_sp() user = constants.user_vars['username'] # Need to update so that if playlist_name not in db.name.values, # create a new playlist with that track using playlist.new_playlist() # which will add the new playlist's information to playlist.database(). assert playlist_name in db.name.values, "playlist name not recorded in playlist.database()" # Check if the track is already on the playlist. If so, don't add it # and let the user know playlist_id = db.set_index('name').loc[playlist_name, 'id'] # Get the playlist spotify object so we can check whether the # track is already in the playlist pl = sp.user_playlist(user=user, playlist_id=playlist_id) track_items = pl['tracks']['items'] # This will only get the 100 earliest added tracks, so duplicates # could still be added. Trying to figure out a loop to reliably # make extension playlists everytime the orginial playlist gets # to 100 tracks existing_ids = [track_item['track']['id'] for track_item in track_items] if track['id'] in existing_ids: print(f"{track['name']} already in {playlist_name}") return else: track_ids = [track['id']] sp.user_playlist_add_tracks(user, playlist_id, tracks=track_ids, position=None) print( f"Added {track['name']} by {track['artists'][0]['name']} to {playlist_name}" )
def recently_played(**kwargs): """ Return the list of track_dicts for the 50 (limited by spotify) tracks for the user. Adds 'played_at' item to track_dict as a datetime object in central timezone """ sp = kwargs.get('sp', utilities.get_user_sp()) recently_played = sp.current_user_recently_played(limit=50)['items'] track_dicts = [] for item in recently_played: played_at = item['played_at'] played_at_dt = parse(played_at) played_at_central_dt = played_at_dt - timedelta(hours=5) # Get rid of timezone awareness because it # simplifies things downstream played_at_central_dt = played_at_central_dt.replace(tzinfo=None) track = item['track'] track['played_at'] = played_at_central_dt track_dicts.append(track) return track_dicts
def get_current_track(**kwargs): """ Return the currently playing track (a dictionary) """ sp = get_user_sp() track = sp.current_user_playing_track() return track['item']
def new_playlist(**kwargs): """ Create an empty playlist and add it to playlist.database() using playlist.update_database(). If playlist_name is already in the database, warn the user, return None and don't make a new playlist. """ username = kwargs.get('username', constants.user_vars['username']) now = datetime.now() name = kwargs.get('name', f'Playlist {now.year}{now.month}{now.day}') public = kwargs.get('public', False) sp = utilities.get_user_sp() db = database() # The playlist will have been create at the top # of playlist organization, so update_datebase() # will find it and add it to the database if name in db.name.values: print(f'Playlist already exists with name: {name}') return False else: # user_playlist_create() returns the playlist dict # object new_pl = sp.user_playlist_create(username, name, public) update_database() return True
def make_playlists_df(): sp = utilities.get_user_sp() username = constants.user_vars['username'] playlists = get_playlists_from_db() playlist_dfs = [] keys = ['name', 'uri', 'id'] for i, playlist_dict in enumerate(playlists): print(f"Making playlist DataFrame {i+1} of {len(playlists)}", end='\r') columns_dict = {} for key in keys: columns_dict[key] = playlist_dict[key] if key == 'id': tracks_df = make_playlist_tracks_df( playlist_id=playlist_dict[key], sp=sp) # need to add functinality to get song release date and my add date # to make_playlist_tracks_df() for col in tracks_df.columns: columns_dict[f'track_{col}'] = tracks_df.loc[:, col] playlist_df = pd.DataFrame(columns_dict) playlist_dfs.append(playlist_df) playlists_df = pd.concat(playlist_dfs) playlists_df.to_csv(constants.user_vars['playlist_df_path'], index=False) return playlists_df
def n_tracks(playlist_id, **kwargs): sp = kwargs.get('sp', utilities.get_user_sp()) user = constants.user_vars['username'] pl = sp.user_playlist(user=user, playlist_id=playlist_id) track_items = pl['tracks']['items'] existing_ids = [track_item['track']['id'] for track_item in track_items] n = len(existing_ids) return n
def add_clipboard_to_queue(): """ Search spotify for the string on the clipboard and add the first track result to queue """ sp = get_user_sp() track = get_clipboard_uri() clipboard_uri = get_clipboard_uri() sp.add_to_queue(clipboard_uri) print(f"Added song with uri '{clipboard_uri}' to queue")
def seek_for(increment_s=30): """ Seek the currently playing track to current track progress in ms + 15 seconds (s) """ increment_ms = increment_s * 1000 sp = get_user_sp() current_track = sp.current_playback() current_prog_ms = current_track['progress_ms'] sp.seek_track(current_prog_ms + increment_ms)
def seek_rev(increment_s=15): """ Seek the currently playing track to current track progress in ms - 30 seconds (s) """ increment_ms = increment_s * 1000 sp = get_user_sp() current_track = sp.current_playback() current_prog_ms = current_track['progress_ms'] target_ms = current_prog_ms - increment_ms sp.seek_track(target_ms)
def pseudoskip(fraction=0.001): """ Navigate to the very last 0.1 % of currently playing track. If you do this instead of skipping the track, it makes it into recently played. """ sp = get_user_sp() current_track = sp.current_playback() duration_ms = current_track['item']['duration_ms'] target_ms = duration_ms - round(duration_ms * fraction) sp.seek_track(target_ms)
def get_playlists_from_db(): """ Return a list of playlist objects returned by sp.playlist(<playlist_id>) for each playlist_id in playlist.database() """ sp = utilities.get_user_sp() db = database() playlist_ids = db.id playlists = [sp.playlist(plid) for plid in playlist_ids] print(f"Found {len(playlists)} playlists") return playlists
def play_clipboard(**kwargs): """ Search spotify for whatever's on the clipboard and play the first resulting track """ sp = kwargs.get('sp', get_user_sp()) track_uri = get_clipboard_uri() if track_uri: sp.start_playback(uris=[track_uri]) print(f"Playing track") else: print("Couldn't find a track with this clipboard query")
def get_playlist(playlist_id='7Gr9kNeQNwapj3KYaAIhCu', **kwargs): """ Return playlist (a dictionary). playlist_id can be looked up by name in in the DataFrame returned by playlist.database() This function does instantiate a user spotipy object (utilities.get_user_sp()) """ sp = kwargs.get('sp', utilities.get_user_sp()) try: playlist = sp.playlist(playlist_id, fields="tracks,next") except Exception as e: playlist = None print( f"Couldn't find playlist with id: {playlist_id}\nSpotipy error:\n{e}" ) print(f'You can look up playlist ids in playlist.database()') return playlist
def make_playlists_db(): sp = utilities.get_user_sp() username = constants.user_vars['username'] playlists = sp.user_playlists(username, limit=50) playlist_dfs = [] keys = ['name', 'uri', 'id'] for i, playlist_dict in enumerate(playlists['items']): columns_dict = {} for key in keys: columns_dict[key] = playlist_dict[key] playlist_df = pd.DataFrame(columns_dict, index=[i]) playlist_dfs.append(playlist_df) playlists_df = pd.concat(playlist_dfs) return playlists_df
def singles_df(**kwargs): """ Return a dataframe with a row for each of the last 20 (limited by spotify api) singles released by the artist_name or artist_id passed to singles_df. This function uses artits.drop_clean_and_dup_tracks() to remove duplicated and clean versions of singles albums Should be tested by passing kwarg artist_name = nonsense ('asdlfkj'), artist with no singles ("Infinity Crush") which will return either an empty dataframe or None. Then artist name that's not in playlist.dataframe(), and finally real artist who is in the archive ("TOPS"). Right now all of these situations are handled in artist.all_artists_singles_df() which compiles all singles albums from all artists found in playlist.dataframe() """ # Get objects from spotify_jpc pldf = playlist.dataframe() pldb = playlist.database() sp = kwargs.get('sp', utilities.get_user_sp()) # Parse keyword args artist_name = kwargs.get('artist_name', None) artist_id = kwargs.get('artist_id', None) if artist_name != None: if artist_name not in pldf.track_artist_name.values: print( "Must pass an artist name that exists in playlist.dataframe().track_artist_name" ) return None else: pass artist_slice = pldf.set_index('track_artist_name', drop=False).loc[artist_name, :] try: # Works if there are multiple appearances of this artist # across user playlists artist_id = artist_slice.track_artist_id.unique()[0] except: # Works if there's only one appearance of this artst # across user playlists artist_id = artist_slice.track_artist_id else: artist_id = kwargs.get('artist_id', pldf.track_artist_id[0]) artist_slice = pldf.set_index('track_artist_id', drop=False).loc[artist_id, :] artist_name = artist_slice.track_artist_name[0] try: albums = sp.artist_albums(artist_id, album_type='single')['items'] except: print(f"No singles found for artist {artist_name}") return None # We need to scan each album for the explicit version # Since album objects are not tagged with the 'explicit' # key, we have to look through each track in each album # to find whether the album was explicit df = pd.DataFrame() for i, album in enumerate(albums): album_title = album['name'] album_id = album['id'] album_uri = album['uri'] albums_tracks = sp.album_tracks(album_id, limit=50)['items'] explicit_tracks = False release_date = album['release_date'] for track in albums_tracks: if track['explicit'] == True: explicit_tracks = True df.loc[i, 'artist_name'] = artist_name df.loc[i, 'artist_id'] = artist_id df.loc[i, 'album_title'] = album_title df.loc[i, 'album_id'] = album_id df.loc[i, 'explicit_tracks'] = explicit_tracks df.loc[i, 'release_date'] = release_date df.loc[i, 'album_uri'] = album_uri print(f'Compiled singles DataFrame for {artist_name}') singles_df = drop_clean_and_dup_tracks(df) return singles_df