song_ranker / app.py
helliun's picture
Update app.py
157194d verified
raw
history blame
19.6 kB
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)