import gradio as gr import pandas as pd import spotipy import re import csv import pandas as pd from spotipy.oauth2 import SpotifyClientCredentials, SpotifyOAuth client_credentials_manager = SpotifyClientCredentials() sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) scope = "user-library-read user-follow-read user-top-read playlist-read-private playlist-modify-public" auth_manager = SpotifyOAuth( scope=scope, show_dialog=True ) # Get the authorization URL and prompt the user to visit it auth_url = auth_manager.get_authorize_url() get_window_url_params = """ function() { const params = new URLSearchParams(window.location.search); return params.has('code') ? {code: params.get('code')} : {}; } """ # Python function to set the visibility of the button based on URL parameters def set_button_visibility(params): # If 'code' parameter is present in the URL, hide the button if "code" in params: return gr.update(visible=False), params # If 'code' parameter is not present, show the button return gr.update(visible=True), params def create_spotify_playlist_from_df(df, user_id="", params={}, playlist_name="Song Ranker Rankings"): # Assuming auth_manager is a SpotifyOAuth instance with the appropriate scope global auth_manager token_info = auth_manager.get_access_token(params["code"]) sp = spotipy.Spotify(auth_manager=auth_manager) # Get the current user's ID current_user = sp.current_user() user_id = current_user['id'] # Check if the playlist already exists playlists = sp.current_user_playlists() playlist_id = None for playlist in playlists['items']: if playlist['name'] == playlist_name and playlist['owner']['id'] == user_id: playlist_id = playlist['id'] break # If the playlist does not exist, create a new one if not playlist_id: playlist = sp.user_playlist_create(user_id, playlist_name, public=True) playlist_id = playlist['id'] # Initialize a list to hold track URIs track_uris = [] # Search for each track and collect URIs for index, row in df.iterrows(): query = f"track:{row['song_title']} artist:{row['artist']}" search_result = sp.search(query, type='track', limit=1) tracks = search_result['tracks']['items'] if tracks: track_uris.append(tracks[0]['uri']) # Replace all tracks in the playlist with the new tracks if track_uris: sp.playlist_replace_items(playlist_id, track_uris) return gr.update(visible=False) def hide_playlist_button(): return gr.update(visible=False) def get_songs_from_spotify(playlist_link, songs_df, previews_df): if match := re.match(r"https://open.spotify.com/playlist/(.*)\?", playlist_link): playlist_uri = match.groups()[0] else: raise ValueError("Expected format: https://open.spotify.com/playlist/...") # Extract data and save to CSV file # Get playlist track information tracks = sp.playlist_tracks(playlist_uri)["items"] with open('track_info.csv', mode='w', newline='', encoding='utf-8') as file: writer = csv.writer(file) writer.writerow(['artist', 'song_title', 'preview_url']) for track in tracks: writer.writerow([track['track']['artists'][0]['name'], track['track']['name'], track['track']['preview_url']]) new_df = pd.read_csv("track_info.csv") new_df['elo_score'] = [1000]*len(new_df) try: songs_df = songs_df[["elo_score", "artist", "song_title"]] previews_df = previews_df[["elo_score", "artist", "song_title", "preview_url"]] except: songs_df = pd.DataFrame(columns=['elo_score', 'artist', 'song_title']) previews_df = pd.DataFrame(columns=["elo_score", "artist", "song_title", "preview_url"]) new_df = new_df[["elo_score", "artist", "song_title", "preview_url"]] previews_df = pd.concat([previews_df,new_df]) new_df = new_df[["elo_score", "artist", "song_title"]] songs_df = pd.concat([songs_df,new_df]) songs_df = songs_df.drop_duplicates(subset=['artist', 'song_title']) previews_df = previews_df.drop_duplicates(subset=['artist', 'song_title']) return songs_df, previews_df def update_scores(winner, loser, k_factor=100): score_difference = int(k_factor/(winner/loser)) winner += score_difference loser -= score_difference return winner, loser def vote_startup(songs_df, previews_df): try: songs_df = songs_df[["elo_score", "artist", "song_title"]] except: songs_df = pd.DataFrame(columns=['elo_score', 'artist', 'song_title']) if len(songs_df)>0: slice_size = 4 slice = int(len(songs_df)/slice_size) sample = songs_df[slice:(slice_size-1)*slice].sample(frac=1).iloc[0] song_title, artist = sample["song_title"], sample["artist"] if len(songs_df) > 1: # Randomly select a song to compare with sample = songs_df.sample(frac=1) comparison_song = sample.iloc[0] if comparison_song['song_title'] == song_title and comparison_song['artist'] == artist: comparison_song = sample.iloc[1] first_df = songs_df[songs_df["song_title"]==song_title][songs_df["artist"]==artist] first_string = first_df["song_title"].tolist()[0]+" - "+first_df["artist"].tolist()[0] second_df = comparison_song second_string = second_df["song_title"]+" - "+second_df["artist"] return f"Do you like '{artist} - {song_title}' better than '{comparison_song['artist']} - {comparison_song['song_title']}'?", first_string, second_string, display_rankings(songs_df), previews_df[previews_df["song_title"]==song_title].iloc[0]["preview_url"],previews_df[previews_df["song_title"]==comparison_song['song_title']].iloc[0]["preview_url"] else: return "Add some songs to start voting!", "", "", display_rankings(songs_df) def clean_string(string): string = string.strip().replace(" "," ").lower() string = " ".join([x[0].upper()+x[1:] for x in string.split()]) return string def add_and_compare(artist, song_title, songs_df): try: songs_df = songs_df[["elo_score", "artist", "song_title"]] except: songs_df = pd.DataFrame(columns=['elo_score', 'artist', 'song_title']) if artist != "" and song_title != "": artist = clean_string(artist) song_title = clean_string(song_title) new_song = pd.DataFrame({'artist': [artist], 'song_title': [song_title], 'elo_score': [1000]}) songs_df = pd.concat([songs_df, new_song], ignore_index=True) songs_df.to_csv("songs_df.csv") songs_df = songs_df[["elo_score", "artist", "song_title"]] return "", "", display_rankings(songs_df) # Function to update Elo ratings based on user's choice def update_ratings_pos(first_string, second_string, songs_df, previews_df): try: songs_df = songs_df[["elo_score", "artist", "song_title"]] except: songs_df = pd.DataFrame(columns=['elo_score', 'artist', 'song_title']) if len(songs_df)==0: return "Add some songs to start voting!", "", "", display_rankings(songs_df) if first_string != "": songs_df["combined"] = songs_df["song_title"] + " - " + songs_df["artist"] loser = songs_df[songs_df["combined"] == second_string] winner = songs_df[songs_df["combined"] == first_string] # Update Elo scores winner_score, loser_score = update_scores(winner['elo_score'].values[0], loser['elo_score'].values[0]) songs_df.at[winner.index[0], 'elo_score'] = winner_score songs_df.at[loser.index[0], 'elo_score'] = loser_score songs_df = songs_df.sort_values(by='elo_score', ascending=False) songs_df.to_csv("songs_df.csv") slice_size = 4 slice = int(len(songs_df)/slice_size) sample = songs_df[slice:(slice_size-1)*slice].sample(frac=1).iloc[0] song_title, artist = sample["song_title"], sample["artist"] if len(songs_df) > 1: # Randomly select a song to compare with sample = songs_df.sample(frac=1) comparison_song = sample.iloc[0] if comparison_song['song_title'] == song_title and comparison_song['artist'] == artist: comparison_song = sample.iloc[1] first_df = songs_df[songs_df["song_title"]==song_title][songs_df["artist"]==artist] first_string = first_df["song_title"].tolist()[0]+" - "+first_df["artist"].tolist()[0] second_df = comparison_song second_string = second_df["song_title"]+" - "+second_df["artist"] return f"Do you like '{artist} - {song_title}' better than '{comparison_song['artist']} - {comparison_song['song_title']}'?", first_string, second_string, display_rankings(songs_df), previews_df[previews_df["song_title"]==song_title].iloc[0]["preview_url"],previews_df[previews_df["song_title"]==comparison_song['song_title']].iloc[0]["preview_url"] else: return "Add some songs to start voting!", "", "", display_rankings(songs_df) # Function to update Elo ratings based on user's choice def update_ratings_neg(first_string, second_string, songs_df, previews_df): try: songs_df = songs_df[["elo_score", "artist", "song_title"]] except: songs_df = pd.DataFrame(columns=['elo_score', 'artist', 'song_title']) if len(songs_df)==0: return "Add some songs to start voting!", "", "", display_rankings(songs_df) if first_string != "": songs_df["combined"] = songs_df["song_title"] + " - " + songs_df["artist"] loser = songs_df[songs_df["combined"] == first_string] winner = songs_df[songs_df["combined"] == second_string] # Update Elo scores winner_score, loser_score = update_scores(winner['elo_score'].values[0], loser['elo_score'].values[0]) songs_df.at[winner.index[0], 'elo_score'] = winner_score songs_df.at[loser.index[0], 'elo_score'] = loser_score songs_df = songs_df.sort_values(by='elo_score', ascending=False) songs_df.to_csv("songs_df.csv") slice_size = 4 slice = int(len(songs_df)/slice_size) sample = songs_df[slice:(slice_size-1)*slice].sample(frac=1).iloc[0] song_title, artist = sample["song_title"], sample["artist"] if len(songs_df) > 1: # Randomly select a song to compare with sample = songs_df.sample(frac=1) comparison_song = sample.iloc[0] if comparison_song['song_title'] == song_title and comparison_song['artist'] == artist: comparison_song = sample.iloc[1] first_df = songs_df[songs_df["song_title"]==song_title][songs_df["artist"]==artist] first_string = first_df["song_title"].tolist()[0]+" - "+first_df["artist"].tolist()[0] second_df = comparison_song second_string = second_df["song_title"]+" - "+second_df["artist"] return f"Do you like '{artist} - {song_title}' better than '{comparison_song['artist']} - {comparison_song['song_title']}'?", first_string, second_string, display_rankings(songs_df), previews_df[previews_df["song_title"]==song_title].iloc[0]["preview_url"],previews_df[previews_df["song_title"]==comparison_song['song_title']].iloc[0]["preview_url"] else: return "Add some songs to start voting!", "", "", display_rankings(songs_df) def display_rankings(songs_df=pd.DataFrame(columns=['elo_score', 'artist', 'song_title'])): songs_df = songs_df.sort_values(by='elo_score', ascending=False) songs_df = songs_df[["elo_score", "artist", "song_title"]] songs_df.to_csv("songs_df.csv") return songs_df def export_csv(songs_df, previews_df): # Function to export DataFrame to CSV save_df = songs_df save_df["preview_url"] = [previews_df[previews_df["artist"]==artist][previews_df["song_title"]==song_title].iloc[0]["preview_url"] for artist, song_title in zip(songs_df["artist"].tolist(),songs_df["song_title"].tolist())] save_df.to_csv("songs_df.csv") return "songs_df.csv" def import_csv(file, songs_df, previews_df): if file is not None: #file_content = file.decode('utf-8') new_df = pd.read_csv(file) try: songs_df = songs_df[["elo_score", "artist", "song_title"]] previews_df = previews_df[["elo_score", "artist", "song_title", "preview_url"]] except: songs_df = pd.DataFrame(columns=['elo_score', 'artist', 'song_title']) previews_df = pd.DataFrame(columns=["elo_score", "artist", "song_title", "preview_url"]) new_df = new_df[["elo_score", "artist", "song_title", "preview_url"]] previews_df = pd.concat([previews_df,new_df]) new_df = new_df[["elo_score", "artist", "song_title"]] songs_df = pd.concat([songs_df,new_df]) songs_df = songs_df.drop_duplicates(subset=['artist', 'song_title']) previews_df = previews_df.drop_duplicates(subset=['artist', 'song_title']) return songs_df, previews_df # Function to remove a song def remove_song(artist, song_title, songs_df): # Find and remove the song from the DataFrame artist = clean_string(artist) song_title = clean_string(song_title) songs_df = songs_df[~((songs_df["artist"] == artist) & (songs_df["song_title"] == song_title))] return songs_df[["elo_score", "artist", "song_title"]] def reset_rankings(songs_df): songs_df["elo_score"] = [1000]*len(songs_df) songs_df = songs_df[["elo_score", "artist", "song_title"]] return display_rankings(songs_df) def clear_rankings(songs_df): songs_df = pd.DataFrame(columns=['elo_score', 'artist', 'song_title']) return display_rankings(songs_df) # Define a theme with a custom primary color theme = gr.themes.Soft(primary_hue="red", secondary_hue="blue") js=""" window.addEventListener('load', function () { gradioURL = window.location.href if (!gradioURL.endsWith('?__theme=dark')) { window.location.replace(gradioURL + '?__theme=dark'); } });""", # theme='Taithrah/Minimal' # Gradio interface with gr.Blocks(theme=theme) as app: gr.Markdown( """## Song Ranker for Spotify This tool helps you create **accurate rankings** of songs based on your personal preferences. It does this by asking you questions comparing a random pair of songs, and then using your answers to calculate Elo scores for ranking. Import songs by pasting a **song, playlist, or album** link below and clicking "Add". """ ) with gr.Row(visible=False) as playlist_part: playlist_button = gr.Button("Connect with Spotify to Start", link=auth_url, variant="primary") # Invisible component to store URL parameters url_params = gr.JSON(visible=False) # Load the URL parameters when the Gradio app starts init_values = app.load(fn=set_button_visibility, inputs=[url_params], outputs=[playlist_part, url_params], js=get_window_url_params) with gr.Row(): previews_df = pd.DataFrame(columns=['elo_score', 'artist', 'song_title', 'preview_url']) previews = gr.DataFrame(value=previews_df, interactive=False, visible=False) with gr.Column(): gr.Markdown( """### Vote to Rank Songs """ ) with gr.Row(): compare_output = gr.Textbox("Add some songs to start voting!", label="Comparison", interactive=False, scale=3) with gr.Row(): yes_button = gr.Button("Yes", variant="secondary") no_button = gr.Button("No", variant="primary") new_vote = gr.Button("New Vote") with gr.Row(): with gr.Column(): compare_index_1 = gr.Textbox(label="",interactive=False) first_song_audio = gr.Audio(label="") with gr.Column(): compare_index_2 = gr.Textbox(label="",interactive=False) second_song_audio = gr.Audio(label="") with gr.Column(): gr.Markdown( """### Rankings """ ) songs_df = pd.DataFrame(columns=['elo_score', 'artist', 'song_title']) rankings = gr.DataFrame(value=songs_df, interactive=False, headers=["Score","Artist", "Song"]) # gr.Markdown( # """### Add Songs from Spotify""" # ) with gr.Row(): spotify_link = gr.Textbox(label="Paste Spotify Link", scale=3) spotify_button = gr.Button("Add", scale=1) with gr.Column(visible=True) as playlist_creation: gr.Markdown("""### Create a playlist when you're done!""") with gr.Row(visible=True): spotify_username = gr.Textbox(label="Spotify Username", visible=False) create_playlist_button = gr.Button("Create Spotify Playlist") create_playlist_button.click(hide_playlist_button, outputs=[playlist_creation]) create_playlist_button.click(create_spotify_playlist_from_df, inputs=[rankings, spotify_username, url_params], outputs=[playlist_creation]) # with gr.Row(): # artist_input = gr.Textbox(label="Artist") # song_title_input = gr.Textbox(label="Song Title") # add_button = gr.Button("Add Song") spotify_button.click(get_songs_from_spotify, inputs=[spotify_link, rankings, previews], outputs=[rankings, previews]) # gr.Markdown( # """### Remove Songs # """ # ) # with gr.Row(): # remove_artist_input = gr.Textbox(label="Artist") # remove_song_title_input = gr.Textbox(label="Song Title") # remove_button = gr.Button("Remove Song") # remove_button.click(remove_song, inputs=[remove_artist_input, remove_song_title_input, rankings], outputs=rankings) gr.Markdown( """### Import and Export Rankings """ ) with gr.Row(): # Import CSV file to replace the existing DataFrame import_button = gr.File(label="Import CSV", file_count="single") import_button.change(fn=import_csv, inputs=[import_button, rankings, previews], outputs=[rankings, previews]) with gr.Column(): # Export button to download the DataFrame as CSV export_link = gr.File(label="Download CSV", file_count="single") export_button = gr.Button("Export as CSV") export_button.click(fn=export_csv, inputs=[rankings,previews], outputs=export_link) gr.Markdown("### Reset Data") with gr.Row(): reset_button = gr.Button("Reset Scores") reset_button.click(reset_rankings, inputs=[rankings], outputs=rankings) clear_button = gr.Button("Clear Table", variant="primary") clear_button.click(clear_rankings, inputs=[rankings], outputs=rankings) # add_button.click(add_and_compare, inputs=[artist_input, song_title_input, rankings], outputs=[artist_input, song_title_input, rankings]) yes_button.click(update_ratings_pos, inputs=[compare_index_1, compare_index_2, rankings, previews], outputs=[compare_output, compare_index_1, compare_index_2, rankings, first_song_audio, second_song_audio]) no_button.click(update_ratings_neg, inputs=[compare_index_1, compare_index_2, rankings, previews], outputs=[compare_output, compare_index_1, compare_index_2, rankings, first_song_audio, second_song_audio]) new_vote.click(vote_startup, inputs=[rankings, previews],outputs=[compare_output, compare_index_1, compare_index_2, rankings, first_song_audio, second_song_audio]) app.launch(share=False)