Upload 3 files
Browse filesAdd usage of Spotify API
- 01_Recommend_from_Song🎤.py +147 -0
- 02_Recommend_from_Genre_Features🎸.py +175 -0
- Introduction🎶.py +33 -0
01_Recommend_from_Song🎤.py
ADDED
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import spotipy
|
2 |
+
import streamlit as st
|
3 |
+
import numpy as np
|
4 |
+
from spotipy.oauth2 import SpotifyClientCredentials
|
5 |
+
from PIL import Image
|
6 |
+
import requests
|
7 |
+
import pandas as pd
|
8 |
+
|
9 |
+
st.set_page_config(
|
10 |
+
page_title="Find Songs Similar to Yours🎤", page_icon="🎤", layout="wide"
|
11 |
+
)
|
12 |
+
|
13 |
+
# Spotify API
|
14 |
+
SPOTIPY_CLIENT_ID = st.secrets.spot_creds.spot_client_id
|
15 |
+
SPOTIPY_CLIENT_SECRET = st.secrets.spot_creds.spot_client_secret
|
16 |
+
|
17 |
+
sp = spotipy.Spotify(
|
18 |
+
auth_manager=SpotifyClientCredentials(
|
19 |
+
client_id=SPOTIPY_CLIENT_ID, client_secret=SPOTIPY_CLIENT_SECRET
|
20 |
+
)
|
21 |
+
)
|
22 |
+
|
23 |
+
"""
|
24 |
+
# Analyze Song and Get Recommendations🎤
|
25 |
+
|
26 |
+
Input a song title and the app will return recommendations as well as the features of the song.
|
27 |
+
|
28 |
+
Data is obtained using the Python library [Spotipy](https://spotipy.readthedocs.io/en/2.18.0/) that uses [Spotify Web API.](https://developer.spotify.com/documentation/web-api/)
|
29 |
+
|
30 |
+
"""
|
31 |
+
song = st.text_input("Enter a song title", value="Somebody Else")
|
32 |
+
search = sp.search(q="track:" + song, type="track")
|
33 |
+
|
34 |
+
|
35 |
+
class GetSongInfo:
|
36 |
+
def __init__(self, search):
|
37 |
+
self.search = search
|
38 |
+
|
39 |
+
def song_id(self):
|
40 |
+
song_id = search["tracks"]["items"][0]["id"] # -gets song id
|
41 |
+
return song_id
|
42 |
+
|
43 |
+
def song_album(self):
|
44 |
+
song_album = search["tracks"]["items"][0]["album"][
|
45 |
+
"name"
|
46 |
+
] # -gets song album name
|
47 |
+
return song_album
|
48 |
+
|
49 |
+
def song_image(self):
|
50 |
+
song_image = search["tracks"]["items"][0]["album"]["images"][0][
|
51 |
+
"url"
|
52 |
+
] # -gets song image URL
|
53 |
+
return song_image
|
54 |
+
|
55 |
+
def song_artist_name(self):
|
56 |
+
song_artist_name = search["tracks"]["items"][0]["artists"][0][
|
57 |
+
"name"
|
58 |
+
] # -gets artist for song
|
59 |
+
return song_artist_name
|
60 |
+
|
61 |
+
def song_name(self):
|
62 |
+
song_name = search["tracks"]["items"][0]["name"] # -gets song name
|
63 |
+
return song_name
|
64 |
+
|
65 |
+
def song_preview(self):
|
66 |
+
song_preview = search["tracks"]["items"][0]["preview_url"]
|
67 |
+
return song_preview
|
68 |
+
|
69 |
+
|
70 |
+
songs = GetSongInfo(song)
|
71 |
+
|
72 |
+
###
|
73 |
+
|
74 |
+
|
75 |
+
def url(song):
|
76 |
+
url_to_song = "https://open.spotify.com/track/" + songs.song_id()
|
77 |
+
st.write(
|
78 |
+
f"Link to stream '{songs.song_name()}' by {songs.song_artist_name()} on Spotify: {url_to_song}"
|
79 |
+
)
|
80 |
+
|
81 |
+
|
82 |
+
# Set up two-column layout for Streamlit app
|
83 |
+
image, stats = st.columns(2)
|
84 |
+
|
85 |
+
with image:
|
86 |
+
try:
|
87 |
+
url(song)
|
88 |
+
r = requests.get(songs.song_image())
|
89 |
+
open("img/" + songs.song_id() + ".jpg", "w+b").write(r.content)
|
90 |
+
image_album = Image.open("img/" + songs.song_id() + ".jpg")
|
91 |
+
st.image(
|
92 |
+
image_album,
|
93 |
+
caption=f"{songs.song_artist_name()} - {songs.song_album()}",
|
94 |
+
use_column_width="auto",
|
95 |
+
)
|
96 |
+
|
97 |
+
feat = sp.audio_features(tracks=[songs.song_id()])
|
98 |
+
features = feat[0]
|
99 |
+
p = pd.Series(features).to_frame()
|
100 |
+
data_feat = p.loc[
|
101 |
+
[
|
102 |
+
"acousticness",
|
103 |
+
"danceability",
|
104 |
+
"energy",
|
105 |
+
"liveness",
|
106 |
+
"speechiness",
|
107 |
+
"valence",
|
108 |
+
]
|
109 |
+
]
|
110 |
+
bpm = p.loc[["tempo"]]
|
111 |
+
values = bpm.values[0]
|
112 |
+
bpms = values.item()
|
113 |
+
ticks = np.linspace(0, 1, 11)
|
114 |
+
|
115 |
+
plot = data_feat.plot.barh(
|
116 |
+
xticks=ticks, legend=False, color="limegreen"
|
117 |
+
) # Use Pandas plot
|
118 |
+
plot.set_xlabel("Value")
|
119 |
+
plot.set_ylabel("Parameters")
|
120 |
+
plot.set_title(f"Analysing '{songs.song_name()}' by {songs.song_artist_name()}")
|
121 |
+
plot.invert_yaxis()
|
122 |
+
st.pyplot(plot.figure)
|
123 |
+
st.subheader(f"BPM (Beats Per Minute): {bpms}")
|
124 |
+
|
125 |
+
st.warning(
|
126 |
+
"Note: Audio previews may have very high default volume and will reset after page refresh"
|
127 |
+
)
|
128 |
+
st.audio(songs.song_preview(), format="audio/wav")
|
129 |
+
|
130 |
+
except IndexError or NameError:
|
131 |
+
st.error(
|
132 |
+
"This error is possibly due to the API being unable to find the song. Maybe try to retype it using the song title followed by artist without any hyphens (e.g. In my Blood Shawn Mendes)"
|
133 |
+
)
|
134 |
+
|
135 |
+
# Recommendations
|
136 |
+
with stats:
|
137 |
+
st.subheader("You might also like")
|
138 |
+
|
139 |
+
reco = sp.recommendations(
|
140 |
+
seed_artists=None, seed_tracks=[songs.song_id()], seed_genres=[], limit=10
|
141 |
+
)
|
142 |
+
|
143 |
+
for i in reco["tracks"]:
|
144 |
+
st.write(f"\"{i['name']}\" - {i['artists'][0]['name']}")
|
145 |
+
image_reco = requests.get(i["album"]["images"][2]["url"])
|
146 |
+
open("img/" + i["id"] + ".jpg", "w+b").write(image_reco.content)
|
147 |
+
st.image(Image.open("img/" + i["id"] + ".jpg"))
|
02_Recommend_from_Genre_Features🎸.py
ADDED
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
# Configure Streamlit page
|
4 |
+
st.set_page_config(
|
5 |
+
page_title="Find the Song that You Like🎸", page_icon="🎸", layout="wide"
|
6 |
+
)
|
7 |
+
|
8 |
+
import pandas as pd
|
9 |
+
import plotly.express as px
|
10 |
+
import streamlit.components.v1 as components
|
11 |
+
from sklearn.neighbors import NearestNeighbors
|
12 |
+
|
13 |
+
|
14 |
+
@st.cache(allow_output_mutation=True)
|
15 |
+
def data_import():
|
16 |
+
"""Function for loading in cleaned data csv file."""
|
17 |
+
df = pd.read_csv("data/clean_data.csv")
|
18 |
+
df["genres"] = df.genres.apply(
|
19 |
+
lambda x: [i[1:-1] for i in str(x)[1:-1].split(", ")]
|
20 |
+
)
|
21 |
+
df_explode = df.explode("genres")
|
22 |
+
return df_explode
|
23 |
+
|
24 |
+
|
25 |
+
genre_names = [
|
26 |
+
"Dance Pop",
|
27 |
+
"Electronic",
|
28 |
+
"Electropop",
|
29 |
+
"Hip Hop",
|
30 |
+
"Jazz",
|
31 |
+
"K-pop",
|
32 |
+
"Latin",
|
33 |
+
"Pop",
|
34 |
+
"Pop Rap",
|
35 |
+
"R&B",
|
36 |
+
"Rock",
|
37 |
+
]
|
38 |
+
audio_params = [
|
39 |
+
"acousticness",
|
40 |
+
"danceability",
|
41 |
+
"energy",
|
42 |
+
"instrumentalness",
|
43 |
+
"valence",
|
44 |
+
"tempo",
|
45 |
+
]
|
46 |
+
|
47 |
+
df_explode = data_import()
|
48 |
+
|
49 |
+
|
50 |
+
def match_song(genre, yr_start, yr_end, test_feat):
|
51 |
+
"""Function for finding similar songs with KNN algorithm."""
|
52 |
+
genre = genre.lower()
|
53 |
+
genre_data = df_explode[
|
54 |
+
(df_explode["genres"] == genre)
|
55 |
+
& (df_explode["release_year"] >= yr_start)
|
56 |
+
& (df_explode["release_year"] <= yr_end)
|
57 |
+
]
|
58 |
+
genre_data = genre_data.sort_values(by="popularity", ascending=False)[:500]
|
59 |
+
|
60 |
+
# Load KNN from SkLearn
|
61 |
+
neigh = NearestNeighbors()
|
62 |
+
neigh.fit(genre_data[audio_params].to_numpy())
|
63 |
+
|
64 |
+
n_neighbors = neigh.kneighbors(
|
65 |
+
[test_feat], n_neighbors=len(genre_data), return_distance=False
|
66 |
+
)[0]
|
67 |
+
|
68 |
+
uris = genre_data.iloc[n_neighbors]["uri"].tolist()
|
69 |
+
audios = genre_data.iloc[n_neighbors][audio_params].to_numpy()
|
70 |
+
|
71 |
+
return uris, audios
|
72 |
+
|
73 |
+
|
74 |
+
# Setup page order
|
75 |
+
def page():
|
76 |
+
title = "Find Your Song🎸"
|
77 |
+
st.title(title)
|
78 |
+
|
79 |
+
st.write(
|
80 |
+
"Get recommended songs on Spotify based on genre and key audio parameters."
|
81 |
+
)
|
82 |
+
st.markdown("##")
|
83 |
+
|
84 |
+
# Streamlit column layout
|
85 |
+
with st.container():
|
86 |
+
col1, col2, col3, col4 = st.columns((2, 0.5, 0.5, 0.5))
|
87 |
+
|
88 |
+
with col3:
|
89 |
+
st.markdown("***Select genre:***")
|
90 |
+
genre = st.radio("", genre_names, index=genre_names.index("Rock"))
|
91 |
+
|
92 |
+
with col1:
|
93 |
+
st.markdown("***Select audio parameters to customize:***")
|
94 |
+
yr_start, yr_end = st.slider(
|
95 |
+
"Select the year range", 1908, 2022, (1980, 2022)
|
96 |
+
)
|
97 |
+
acousticness = st.slider("Acousticness", 0.0, 1.0, 0.5)
|
98 |
+
danceability = st.slider("Danceability", 0.0, 1.0, 0.5)
|
99 |
+
energy = st.slider("Energy", 0.0, 1.0, 0.5)
|
100 |
+
instrumentalness = st.slider("Instrumentalness", 0.0, 1.0, 0.5)
|
101 |
+
valence = st.slider("Valence", 0.0, 1.0, 0.45)
|
102 |
+
tempo = st.slider("Tempo", 0.0, 244.0, 125.01)
|
103 |
+
|
104 |
+
pr_page_tracks = 6
|
105 |
+
test_feat = [acousticness, danceability, energy, instrumentalness, valence, tempo]
|
106 |
+
uris, audios = match_song(genre, yr_start, yr_end, test_feat)
|
107 |
+
|
108 |
+
tracks = []
|
109 |
+
for uri in uris:
|
110 |
+
track = """<iframe src="https://open.spotify.com/embed/track/{}" width="280" height="400" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>""".format(
|
111 |
+
uri
|
112 |
+
)
|
113 |
+
tracks.append(track)
|
114 |
+
|
115 |
+
if "previous_inputs" not in st.session_state:
|
116 |
+
st.session_state["previous_inputs"] = [genre, yr_start, yr_end] + test_feat
|
117 |
+
|
118 |
+
current_inputs = [genre, yr_start, yr_end] + test_feat
|
119 |
+
if current_inputs != st.session_state["previous_inputs"]:
|
120 |
+
if "start_track_i" in st.session_state:
|
121 |
+
st.session_state["start_track_i"] = 0
|
122 |
+
|
123 |
+
st.session_state["previous_inputs"] = current_inputs
|
124 |
+
|
125 |
+
if "start_track_i" not in st.session_state:
|
126 |
+
st.session_state["start_track_i"] = 0
|
127 |
+
|
128 |
+
with st.container():
|
129 |
+
col1, col2, col3 = st.columns([2, 1, 2])
|
130 |
+
if st.button("More Songs"):
|
131 |
+
if st.session_state["start_track_i"] < len(tracks):
|
132 |
+
st.session_state["start_track_i"] += pr_page_tracks
|
133 |
+
|
134 |
+
current_tracks = tracks[
|
135 |
+
st.session_state["start_track_i"] : st.session_state["start_track_i"]
|
136 |
+
+ pr_page_tracks
|
137 |
+
]
|
138 |
+
current_audios = audios[
|
139 |
+
st.session_state["start_track_i"] : st.session_state["start_track_i"]
|
140 |
+
+ pr_page_tracks
|
141 |
+
]
|
142 |
+
if st.session_state["start_track_i"] < len(tracks):
|
143 |
+
for i, (track, audio) in enumerate(zip(current_tracks, current_audios)):
|
144 |
+
if i % 2 == 0:
|
145 |
+
with col1:
|
146 |
+
components.html(
|
147 |
+
track,
|
148 |
+
height=400,
|
149 |
+
)
|
150 |
+
with st.expander("Display Chart"):
|
151 |
+
df = pd.DataFrame(dict(r=audio[:5], theta=audio_params[:5]))
|
152 |
+
fig = px.line_polar(
|
153 |
+
df, r="r", theta="theta", line_close=True
|
154 |
+
)
|
155 |
+
fig.update_layout(height=400, width=340)
|
156 |
+
st.plotly_chart(fig)
|
157 |
+
|
158 |
+
else:
|
159 |
+
with col3:
|
160 |
+
components.html(
|
161 |
+
track,
|
162 |
+
height=400,
|
163 |
+
)
|
164 |
+
with st.expander("Display Chart"):
|
165 |
+
df = pd.DataFrame(dict(r=audio[:5], theta=audio_params[:5]))
|
166 |
+
fig = px.line_polar(
|
167 |
+
df, r="r", theta="theta", line_close=True
|
168 |
+
)
|
169 |
+
fig.update_layout(height=400, width=340)
|
170 |
+
st.plotly_chart(fig)
|
171 |
+
else:
|
172 |
+
st.write("No more songs")
|
173 |
+
|
174 |
+
|
175 |
+
page()
|
Introduction🎶.py
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from streamlit import session_state as session
|
3 |
+
|
4 |
+
# Configure Streamlit page
|
5 |
+
st.set_page_config(page_title="Song Recommender🎶", page_icon="🎶")
|
6 |
+
|
7 |
+
st.title("Song Recommender🎶")
|
8 |
+
st.markdown("Click on '**Recommend from Song🎤**' from the side panel to get recommended songs via the Spotify API.")
|
9 |
+
st.markdown("**How does '**Recommend from Genre Features🎸**' work?**")
|
10 |
+
st.markdown(
|
11 |
+
"The songs come from the [Spotify and Genius Track Dataset](https://www.kaggle.com/datasets/saurabhshahane/spotgen-music-dataset) on Kaggle. The [k-Nearest Neighbor algorithm](https://scikit-learn.org/stable/modules/neighbors.html) is used to obtain recommendations, i.e., the top songs which are closest in distance to the set of parameter inputs specified by you."
|
12 |
+
)
|
13 |
+
|
14 |
+
st.markdown("This app will recommend you songs based on the characteristics below.")
|
15 |
+
st.markdown(
|
16 |
+
"""
|
17 |
+
**Acousticness**: A metric describing the 'acousticness' of a song. 1.0 represents high confidence the song is acoustic.<br>
|
18 |
+
|
19 |
+
**Danceability**: Describes a song's suitability for dancing based on combination of elements including tempo, rhythm stability, beat strength, and overall regularity.
|
20 |
+
0.0 is least danceable and 1.0 is most danceable.<br>
|
21 |
+
|
22 |
+
**Energy**: Measure of intensity and activity. Often, energetic songs feel fast, loud, and noisy.<br>
|
23 |
+
|
24 |
+
**Liveness**: A metric describing the likelihood that a track is a recording of a live performance.<br>
|
25 |
+
|
26 |
+
**Speechiness**: How much lyrics the track contains.<br>
|
27 |
+
|
28 |
+
**Valence**: A metric ranging from 0.0 to 1.0 describing the positivity conveyed by a track.<br>
|
29 |
+
|
30 |
+
Source: [Spotify Web API](https://developer.spotify.com/documentation/web-api/reference)
|
31 |
+
""",
|
32 |
+
unsafe_allow_html=True,
|
33 |
+
)
|