Spaces:
Runtime error
Runtime error
File size: 11,190 Bytes
6befa81 f813921 6befa81 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
'''
Python libraries allow users to extend the abilities of the language compiler. For this project, I will be using the following libraries:
- pandas and numpy (for data analysis and manipulation)
- streamlit and plotly (for UI design and data visualization)
- pyodbc and spotipy (for Spotify API and SQL Server connections)
'''
# import libraries
import pandas as pd
import numpy as np
import streamlit as st
import plotly.express as px
from random import seed
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
# define function to highlight output dataframe cells based on value
def highlight_colors(val, color_if_true, color_if_false):
color = color_if_true if val >= 0.75 and val <= 1.0 else color_if_false
return 'background-color: {}'.format(color)
# establish API connection
cid = '3fda75b7146a4769b207ee44017b3abe'
secret = '2a755cb04a18406b9394dbef2f8069dd'
client_credentials_manager = SpotifyClientCredentials(client_id=cid, client_secret=secret)
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
# establish SQL Server connection
# read data from parquet file
query = pd.read_parquet("tracks.parquet.gzip")
# create metrics for analysis
query2 = pd.melt(query, id_vars=['uri'], var_name='metrics', value_name='score', value_vars=['instrumentalness', 'danceability', 'energy', 'acousticness', 'valence', 'liveness'])
# name the app
st.set_page_config(page_title='Song Recommendation App', layout='centered')
# create internal CSS
st.markdown(""" <style>
.title { font-size: 45px;
text-align: center;}
.track { font-size: 20px;
text-align: left;
padding-left: 50px;
padding-top: 30px;}
.artist { font-size: 15px;
text-align: left;
padding-left: 85px;}
.subheader { font-size: 22px;
text-align: left;}
.header { font-size: 20px;
padding-left: 35px;
font-weight: bold;}
.header2 { font-size: 20px;
padding-left: 33px;
font-weight: bold;}
.header3 { font-size: 20px;
padding-left: 25px;
font-weight: bold;}
.header4 { font-size: 20px;
padding-left: 0px;
font-weight: bold;}
.header5 { font-size: 25px;
padding-left: 225px;
margin-top: 50px;
margin-bottom: 50px;}
.ban-font { font-size: 30px;
padding-left: 25px;}
.ban-font2 { font-size: 30px;
padding-left: 25px;}
.ban-font3 { font-size: 30px;
padding-left: 0px;}
.ban-font4 { font-size: 30px;
padding-left: 30px;}
.image { padding-top: 10px;}
.streamlit-expanderHeader { font-size: 22px;}
.row_heading.level0 {display:none}
.blank {display:none}
</style>""", unsafe_allow_html=True)
# create sidebar menu
sidebar_title = st.sidebar.header('Pick Your Favorite Song')
artists = query['artist_name'].drop_duplicates()
artists = artists.sort_values()
artist_choice = st.sidebar.selectbox('Choose an Artist:', artists)
tracks = query['track_name'].loc[query['artist_name'] == artist_choice].drop_duplicates()
tracks = tracks.sort_values()
track_choice = st.sidebar.selectbox('Choose a Song', tracks)
empty = st.sidebar.text('')
output = query['uri'].loc[(query['track_name'] == track_choice) & (query['artist_name'] == artist_choice)].values
output_bpm = query['tempo'].loc[(query['track_name'] == track_choice) & (query['artist_name'] == artist_choice)].drop_duplicates().values
output_bpm = output_bpm.astype(float)
output_bpm = np.round(output_bpm, decimals=0)
output_bpm = output_bpm.astype(int)
uri_output = st.sidebar.selectbox('Select the URI:', options=(output))
viz_query = query2.loc[query2['uri'] == uri_output]
# create title for main interface
page_title = st.markdown(f'''<h1 class="title"">Song Recommendation Engine 2.0</h1>''', unsafe_allow_html=True)
# create dropdown menu for app description
st.markdown('<br>', unsafe_allow_html=True)
with st.expander('Description'):
st.markdown('''Have you ever wondered how Spotify's Song Recommendation Algorithm works? This app allows you to take a behind-the-scenes look at how Spotify uses your data to recommend songs based on various metrics.''', unsafe_allow_html=True)
# allow user to preview song and view album cover
st.markdown('<br><br><h4>Song Preview</h4><br><br>', unsafe_allow_html=True)
img_query = pd.json_normalize(sp.track(uri_output), record_path=['album', ['images']])
img_url = img_query['url'][0]
audio_query = pd.json_normalize(sp.track(uri_output))
audio_url = audio_query['preview_url'][0]
col1, col2, col3 = st.columns([1, 4, 1])
with col2:
if audio_url != None:
st.audio(audio_url)
else:
st.text('No Audio Available')
col1, col2, col3, col4, col5 = st.columns([1, 1, 1, 4, 1])
with col3:
album_image = st.markdown(f'<img class= "image" src={img_url} width="125" height="125"></img>', unsafe_allow_html=True)
with col4:
st.markdown(f'<p class="track">{track_choice}</p>\n<p class="artist">{artist_choice}</p>', unsafe_allow_html=True)
# create BANs for data visualizations
col1, col2, col3, col4, col5 = st.columns([1, 2, 1, 1, 1])
with col1:
st.text('')
st.text('')
st.text('')
st.text('')
filters_txt = st.markdown('<h4>Features</h4><br><br>', unsafe_allow_html=True)
col1, col2, col3, col4 = st.columns([1, 1, 1, 1])
with col1:
bpm_ban = st.markdown(f'''<p class="header">BPM</p><p class="ban-font">{output_bpm}</p>''', unsafe_allow_html=True)
# create data visualization using new query from uri output
fig = px.bar_polar(viz_query, theta='metrics', r='score', range_r=[0.0,1.0], hover_name='metrics', hover_data={'score':True, 'metrics':False}, width=750, height=600, color_continuous_scale='Sunset', color='score', range_color=[0.0,1.0], template='plotly', title='Song Metrics')
fig = fig.update_layout(polar_radialaxis_gridcolor="#e3ecf6", polar_angularaxis_gridcolor="#e3ecf6", polar= dict(radialaxis= dict(showticklabels= False)), hovermode="x")
fig = fig.update_traces(hovertemplate="<b>Metric: %{theta}<br>Score: %{r}</b>", hoverlabel= dict(bgcolor="#ffffff"))
st.plotly_chart(fig)
# create drop-down menu to display definitions for each metric
with st.expander('Metric Definitions'):
st.markdown(f'''<h4><b><u>Acousticness</u></b></h4>\nA confidence measure from 0.00 to 1.00 of whether a track is acoustic. 1.0 represents high confidence the track is acoustic.\n\n<h4><b><u>Danceability</u></b></h4>\nThis describes how suitable a track is for dancing based on a combination of musical elements including tempo (BPM), rhythm stability, beat strength, and overall regularity. A value of 0.00 is least danceable and 1.00 is most danceable.\n\n<h4><b><u>Energy</u></b></h4>\nA measure from 0.00 to 1.00 that represents a perceptual measure of intensity and activity. Typically, energetic tracks feel fast, loud, and noisy. Perceptual features contributing to this attribute include dynamic range, perceived loudness, timbre, onset rate, and general entropy.\n\n<h4><b><u>Instrumentalness</u></b></h4>\nPredicts whether a tracks contains no vocals. "Ooh" and "Aah" sounds are treated as instrumental in this context. The closer the value is to 1.00, the greater likelihood the track contains no vocal content.\n\n<h4><b><u>Liveness</u></b></h4>\nDetects the presence of an audience in the recoding. The great the value is to 1.00, the greater the likelihood that the track was performed live.\n\n<h4><b><u>Valence</u></b></h4>\nA measure from 0.00 to 1.00 describing the musical positiveness by a track. Tracks with high valence (> 0.50) sound more positive, whereas tracks with low valence (< 0.50) sound more negative.\n\n<br><i>* Web API Reference: Get Track Audio Features, Spotify, developer.spotify.com/documentation/web-api/reference/#/operations/get-audio-features.</i>''', unsafe_allow_html=True)
# create drop-down menu to display song recommendations based on user input
with st.expander('Song Recommendations'):
st.subheader('Your Song')
result_query = query.loc[query['track_uri'] == uri_output]
result_query = result_query.drop_duplicates()
result_query = result_query.reset_index()
result_df = pd.DataFrame(result_query)
result_df = result_df[['track_name', 'artist_name', 'album_name', 'acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'valence', 'artist_uri', 'uri']]
st.dataframe(result_df)
# get all artist data
result_list2 = pd.json_normalize(sp.recommendations(seed_tracks=[result_df['uri'][0]], seed_artists=[result_df['artist_uri'][0]], limit=25), record_path=['tracks', ['artists']])
result_list2 = result_list2.merge(query, left_on='uri', right_on='artist_uri')
result_list2 = result_list2.rename(columns={'name': 'Artist Name', 'uri_x': 'Artist URI'})
result_list2 = result_list2.rename(columns={'track_name': 'Track Name'})
result_list2 = result_list2[['Track Name', 'Artist Name', 'album_name', 'acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'valence']]
final_df = result_list2.head(25)
result_df = result_df.reset_index()
final_df = final_df.reset_index()
# create new field to calculate likeness for song metrics
final_df['acousticness'] = round(final_df['acousticness'].astype(float), 3)
final_df['danceability'] = round(final_df['danceability'].astype(float), 3)
final_df['energy'] = round(final_df['energy'].astype(float), 3)
final_df['instrumentalness'] = round(final_df['instrumentalness'].astype(float), 3)
final_df['liveness'] = round(final_df['liveness'].astype(float), 3)
final_df['valence'] = round(final_df['valence'].astype(float), 3)
final_df = final_df[['Track Name', 'Artist Name', 'acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'valence']]
final_df = final_df.drop_duplicates()
final_df = final_df.style.applymap(highlight_colors, color_if_true='#5EFF33', color_if_false='white', subset=['acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'valence'])
st.subheader('Recommendations (by likeness)')
st.dataframe(final_df)
|