Spaces:
Sleeping
Sleeping
| 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 Top 100 Songs"): | |
| # 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 list(df.iterrows())[:100]: | |
| 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) |