Spaces:
Runtime error
Runtime error
File size: 15,565 Bytes
cb32cb9 80d5795 3dd7abd 80d5795 1767f84 3dd7abd 36321f6 3dd7abd cb32cb9 80d5795 cb32cb9 3dd7abd cb32cb9 3dd7abd cb32cb9 300f883 80d5795 300f883 80d5795 300f883 80d5795 300f883 80d5795 cb32cb9 80d5795 300f883 80d5795 1767f84 80d5795 300f883 80d5795 300f883 80d5795 cb32cb9 80d5795 300f883 80d5795 300f883 80d5795 cb32cb9 80d5795 cb32cb9 80d5795 cb32cb9 80d5795 3dd7abd 80d5795 cb32cb9 80d5795 3dd7abd 80d5795 3dd7abd 80d5795 cb32cb9 80d5795 cb32cb9 80d5795 300f883 80d5795 cb32cb9 80d5795 |
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 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
from PIL import Image
from utils import *
from app_utils import *
import time
from spotipy.oauth2 import SpotifyClientCredentials
debug = True
dir_path = os.path.dirname(os.path.realpath(__file__))
st.set_page_config(
page_title="EmotionPlaylist",
page_icon="🎧",
)
st.title('Customize Emotional Playlists')
def log_to_spotify():
st.subheader("Step 1: Connect to your Spotify app")
st.markdown("Log into your Spotify account to let the app create the custom playlist.")
if 'login' not in st.session_state or debug:
if debug:
client_credentials_manager = SpotifyClientCredentials()
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
user_id = None
else:
sp, user_id = new_get_client(session=st.session_state)
if sp != None:
legit_genres = sp.recommendation_genre_seeds()['genres']
st.session_state['login'] = (sp, user_id, legit_genres)
else:
sp, user_id, legit_genres = st.session_state['login']
st.success('You are logged in.')
return sp, user_id, legit_genres
@st.cache(suppress_st_warning=True)
def get_user_playlists(users_links):
global sp
# Scanning users
n_playlists = 0
all_uris, all_names = [], []
if users_links != "":
try:
print(users_links)
user_ids = extract_uris_from_links(users_links, url_type='user')
print(user_ids)
all_uris, all_names = get_all_playlists_uris_from_users(sp, user_ids)
n_playlists = len(all_uris)
except:
st.warning('Please enter a valid list of user names (one url per line)')
return all_uris, all_names, n_playlists
def get_filtered_user_playlists(user_links):
global sp
st.spinner(text="Scanning users..")
all_uris, all_names, n_playlists = get_user_playlists(user_links)
if n_playlists <= 1:
return all_uris
else:
# let the user uncheck playlists
st.markdown("Check boxes to select playlists from the selected users."
"Note: to check all, first uncheck all (bug).")
columns = st.columns(np.ones(5))
with columns[1]:
check_all_playlists = st.button('Check all')
with columns[3]:
uncheck_all_playlists = st.button('Uncheck all')
if 'checkboxes' not in st.session_state.keys():
st.session_state['checkboxes_playlists'] = [True] * n_playlists
empty_checkboxes = wall_of_checkboxes(all_names, max_width=5)
if check_all_playlists:
st.session_state['checkboxes_playlists'] = [True] * n_playlists
if uncheck_all_playlists:
st.session_state['checkboxes_playlists'] = [False] * n_playlists
for i_emc, emc in enumerate(empty_checkboxes):
st.session_state['checkboxes_playlists'][i_emc] = emc.checkbox(all_names[i_emc], value=st.session_state['checkboxes_playlists'][i_emc])
filter_playlist = centered_button(st.button, 'Update user playlists', n_columns=5)
if filter_playlist:
return list(np.array(all_uris)[np.where(st.session_state['checkboxes_playlists'])])
else:
return []
@st.cache(suppress_st_warning=True)
def get_non_user_playlists(playlist_links):
# Scanning playlists
new_playlist_uris = []
if playlist_links != "":
st.spinner(text="Scanning playlists..")
try:
new_playlist_uris = extract_uris_from_links(playlist_links, url_type='playlist')
except:
st.warning('Please enter a valid list of playlists (one url per line)')
return new_playlist_uris
@st.cache
def extract_tracks(playlist_uris):
global sp
# extracting tracks
data_tracks = get_all_tracks_from_playlists(sp, playlist_uris, verbose=True)
return data_tracks
@st.cache
def extract_audio_features(data_tracks, legit_genres):
# Extract audio features
all_tracks_uris = np.array(list(data_tracks.keys()))
all_audio_features = [data_tracks[uri]['track']['audio_features'] for uri in all_tracks_uris]
valid_indexes = np.array([i for i in range(len(all_tracks_uris)) if all_audio_features[i] is not None])
all_tracks_uris = all_tracks_uris[valid_indexes]
all_audio_features = np.array(all_audio_features)[valid_indexes]
all_tracks_audio_features = dict(zip(relevant_audio_features, [[audio_f[k] for audio_f in all_audio_features] for k in relevant_audio_features]))
genres = dict()
for index, uri in enumerate(all_tracks_uris):
track = data_tracks[uri]
track_genres = track['track']['genres']
for g in track_genres:
if g not in genres.keys():
genres[g] = [index]
else:
genres[g].append(index)
genres = aggregate_genres(genres, legit_genres)
genres_labels = sorted(genres.keys())
return all_tracks_uris, all_tracks_audio_features, genres, genres_labels
# st.session_state['music_extracted'] = dict(all_tracks_uris=all_tracks_uris,
# all_tracks_audio_features=all_tracks_audio_features,
# genres=genres,
# genres_labels=genres_labels)
def select_songs(legit_genres):
global sp
st.subheader("Step 2: Select candidate songs")
st.markdown("This can be done in two ways: \n"
"1. Get songs from a list of users (and their playlists)\n"
"2. Get songs from a list of playlists.\n"
"For this you'll need to collect user urls (e.g. https://open.spotify.com/user/bkayf) and/or playlist urls (e.g. "
"https://open.spotify.com/playlist/1H7a4q8JZArMQiidRy6qon) by clicking on 'Share' and copying the url. "
"You need to provide at least one source of music.")
users_playlists = "Add a list of user urls, one per line (optional)"
users_links = st.text_area(users_playlists, value="")
label_playlists = "Add a list of playlists urls, one per line (optional)"
playlist_links = st.text_area(label_playlists, value="https://open.spotify.com/playlist/1H7a4q8JZArMQiidRy6qon")
extract_button = centered_button(st.button, 'Extract music', n_columns=5)
all_tracks_uris, all_tracks_audio_features, genres, genres_labels = [None] * 4
if extract_button or debug or 'extract_button' in st.session_state.keys():
st.session_state['extract_button'] = True
# check the user input music sources
if playlist_links == "" and users_links == "":
st.warning('Please enter at least one source of music.')
else:
st.spinner(text="Scanning music sources..")
playlist_uris = []
init_time = time.time()
init_time_tot = init_time
user_playlists = get_filtered_user_playlists(users_links)
playlist_uris += user_playlists
print(f'1. user playlist: {time.time() - init_time:.2f}')
init_time = time.time()
new_playlist_uris = get_non_user_playlists(playlist_links)
playlist_uris += new_playlist_uris
n_users = len(users_links.split('\n'))
st.success(f'{len(playlist_uris)} new playlists added from {n_users} users.')
print(f'2. non user playlist: {time.time() - init_time:.2f}')
init_time = time.time()
if str(playlist_uris) in st.session_state.keys():
data_tracks = st.session_state[str(playlist_uris)]
else:
data_tracks = extract_tracks(playlist_uris)
st.session_state[str(playlist_uris)] = data_tracks
print(f'3. track extraction: {time.time() - init_time:.2f}')
init_time = time.time()
if len(data_tracks.keys()) < 10:
st.warning('Please select more music sources.')
else:
all_tracks_uris, all_tracks_audio_features, genres, genres_labels = extract_audio_features(data_tracks, legit_genres)
print(f'4. audio feature extraction: {time.time() - init_time:.2f}')
print(f'\t total extraction: {time.time() - init_time_tot:.2f}')
st.success(f'{len(data_tracks.keys())} tracks found!')
return all_tracks_uris, all_tracks_audio_features, genres, genres_labels
def customize_widgets(genres_labels):
st.subheader("Step 3: Customize it!")
st.markdown("##### Which genres?")
st.markdown("Check boxes to select genres, see how many tracks were selected below. Note: to check all, first uncheck all (bug).")
columns = st.columns(np.ones(5))
with columns[1]:
check_all = st.button('Check all')
with columns[3]:
uncheck_all = st.button('Uncheck all')
if 'checkboxes' not in st.session_state.keys():
st.session_state['checkboxes'] = [True] * len(genres_labels)
empty_checkboxes = wall_of_checkboxes(genres_labels, max_width=5)
if check_all:
st.session_state['checkboxes'] = [True] * len(genres_labels)
if uncheck_all:
st.session_state['checkboxes'] = [False] * len(genres_labels)
for i_emc, emc in enumerate(empty_checkboxes):
st.session_state['checkboxes'][i_emc] = emc.checkbox(genres_labels[i_emc], value=st.session_state['checkboxes'][i_emc])
st.markdown("##### What's the mood?")
valence = st.slider('Valence (0 negative, 100 positive)', min_value=0, max_value=100, value=100, step=1) / 100
energy = st.slider('Energy (0 low, 100 high)', min_value=0, max_value=100, value=100, step=1) / 100
danceability = st.slider('Danceability (0 low, 100 high)', min_value=0, max_value=100, value=100, step=1) / 100
target_mood = np.array([valence, energy, danceability]).reshape(1, 3)
return target_mood
@st.cache
def filter_songs_by_genre(checkboxes, genres_labels, genres):
# filter songs by genres
selected_labels = [genres_labels[i] for i in range(len(genres_labels)) if checkboxes[i]]
genre_selected_indexes = []
for label in selected_labels:
genre_selected_indexes += genres[label]
genre_selected_indexes = np.array(sorted(set(genre_selected_indexes)))
return genre_selected_indexes
def find_best_songs_for_mood(all_tracks_audio_features, genre_selected_indexes, target_mood):
candidate_moods = np.array([np.array(all_tracks_audio_features[feature])[genre_selected_indexes] for feature in ['valence', 'energy', 'danceability']]).T
distances = np.sqrt(((candidate_moods - target_mood) ** 2).sum(axis=1))
min_dist_indexes = np.argsort(distances)
n_candidates = distances.shape[0]
return min_dist_indexes, n_candidates
def run_app():
global sp
setup_credentials()
image = Image.open(dir_path + '/image.png')
st.image(image)
st.markdown("This app let's you quickly build playlists in a customized way: ")
st.markdown("* **It's easy**: you won't have to add songs one by one,\n"
"* **You're in control**: you provide a source of candidate songs, select a list of genres and choose the mood for the playlist.")
fake = centered_button(st.button, "Let's go", n_columns=7, disabled=True)
sp, user_id, legit_genres = log_to_spotify()
if 'login' in st.session_state or debug:
all_tracks_uris, all_tracks_audio_features, genres, genres_labels = select_songs(legit_genres)
if all_tracks_uris is not None:
target_mood = customize_widgets(genres_labels)
custom_button = centered_button(st.button, 'Run customization', n_columns=5)
if custom_button or 'run_custom' in st.session_state.keys():
st.session_state['run_custom'] = True
checkboxes = st.session_state['checkboxes'].copy()
init_time = time.time()
genre_selected_indexes = filter_songs_by_genre(checkboxes, genres_labels, genres)
if len(genre_selected_indexes) < 10:
genre_selected_indexes = None
st.warning('Please select more genres or add more music sources.')
else:
st.success(f'{len(genre_selected_indexes)} candidate tracks selected.')
print(f'6. filter by genre: {time.time() - init_time:.2f}')
init_time = time.time()
if genre_selected_indexes is not None:
min_dist_indexes, n_candidates = find_best_songs_for_mood(all_tracks_audio_features, genre_selected_indexes, target_mood)
print(f'7. filter by mood: {time.time() - init_time:.2f}')
init_time = time.time()
if n_candidates < 25:
st.warning('Please add more music sources or select more genres.')
else:
playlist_length = st.number_input(f'Pick a playlist length, given {n_candidates} candidates.', min_value=5,
value=min(10, n_candidates//5), max_value=n_candidates//5)
selected_tracks_indexes = genre_selected_indexes[min_dist_indexes[:playlist_length]]
selected_tracks_uris = all_tracks_uris[selected_tracks_indexes]
np.random.shuffle(selected_tracks_uris)
playlist_name = st.text_input('Playlist name', value='Mood Playlist')
if playlist_name == '':
st.warning('Please enter a playlist name.')
else:
print(f'8. build playlist: {time.time() - init_time:.2f}')
init_time = time.time()
generation_button = centered_button(st.button, 'Generate playlist', n_columns=5)
if generation_button:
target_mood = np.array(target_mood).flatten() * 100
description = f'Emotion Playlist for Valence: {int(target_mood[0])}, ' \
f'Energy: {int(target_mood[1])}, ' \
f'Danceability: {int(target_mood[2])}). ' \
f'Playlist generated by the EmotionPlaylist app: https://huggingface.co/spaces/ccolas/EmotionPlaylist.'
playlist_info = sp.user_playlist_create(user_id, playlist_name, public=True, collaborative=False, description=description)
playlist_uri = playlist_info['uri'].split(':')[-1]
sp.playlist_add_items(playlist_uri, selected_tracks_uris)
st.write(
f"""
<html>
<body>
<center>
<iframe style = "border-radius:12px" src="https://open.spotify.com/embed/playlist/{playlist_uri}" allowtransparency="true"
allow="encrypted-media" width="80%" height="580" frameborder="0"></iframe></center></body></html>
""", unsafe_allow_html=True)
st.success(f'The playlist has been generated, find it [here](https://open.spotify.com/playlist/{playlist_uri}).')
stop = 1
if __name__ == '__main__':
run_app() |