ccolas commited on
Commit
bc64e5a
1 Parent(s): e91a84d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +145 -56
app.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  from PIL import Image
2
  from utils import *
3
  from app_utils import *
@@ -23,18 +25,19 @@ def log_to_spotify():
23
  client_credentials_manager = SpotifyClientCredentials()
24
  sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
25
  user_id = None
 
26
  else:
27
- sp, user_id = new_get_client(session=st.session_state)
28
  if sp != None:
29
  legit_genres = sp.recommendation_genre_seeds()['genres']
30
- st.session_state['login'] = (sp, user_id, legit_genres)
31
  st.success('You are logged in.')
32
  else:
33
  legit_genres = None
34
  else:
35
- sp, user_id, legit_genres = st.session_state['login']
36
  st.success('You are logged in.')
37
- return sp, user_id, legit_genres
38
 
39
 
40
  @st.cache(suppress_st_warning=True)
@@ -61,27 +64,28 @@ def get_filtered_user_playlists(user_links):
61
  if n_playlists <= 1:
62
  return all_uris
63
  else:
64
- # let the user uncheck playlists
65
- st.markdown("Check boxes to select playlists from the selected users."
66
- "Note: to check all, first uncheck all (bug).")
67
- columns = st.columns(np.ones(5))
68
- with columns[1]:
69
- check_all_playlists = st.button('Check all')
70
- with columns[3]:
71
- uncheck_all_playlists = st.button('Uncheck all')
72
-
73
- if 'checkboxes' not in st.session_state.keys():
74
- st.session_state['checkboxes_playlists'] = [True] * n_playlists
75
-
76
- empty_checkboxes = wall_of_checkboxes(all_names, max_width=5)
77
- if check_all_playlists:
78
- st.session_state['checkboxes_playlists'] = [True] * n_playlists
79
- if uncheck_all_playlists:
80
- st.session_state['checkboxes_playlists'] = [False] * n_playlists
81
- for i_emc, emc in enumerate(empty_checkboxes):
82
- st.session_state['checkboxes_playlists'][i_emc] = emc.checkbox(all_names[i_emc], value=st.session_state['checkboxes_playlists'][i_emc])
83
-
84
- filter_playlist = centered_button(st.button, 'Update user playlists', n_columns=5)
 
85
  if filter_playlist:
86
  return list(np.array(all_uris)[np.where(st.session_state['checkboxes_playlists'])])
87
  else:
@@ -187,31 +191,41 @@ def select_songs(legit_genres):
187
 
188
  def customize_widgets(genres_labels):
189
  st.subheader("Step 3: Customize it!")
190
- st.markdown("##### Which genres?")
191
- st.markdown("Check boxes to select genres, see how many tracks were selected below. Note: to check all, first uncheck all (bug).")
192
- columns = st.columns(np.ones(5))
193
- with columns[1]:
194
- check_all = st.button('Check all')
195
- with columns[3]:
196
- uncheck_all = st.button('Uncheck all')
197
-
198
- if 'checkboxes' not in st.session_state.keys():
199
- st.session_state['checkboxes'] = [True] * len(genres_labels)
200
-
201
- empty_checkboxes = wall_of_checkboxes(genres_labels, max_width=5)
202
- if check_all:
203
- st.session_state['checkboxes'] = [True] * len(genres_labels)
204
- if uncheck_all:
205
- st.session_state['checkboxes'] = [False] * len(genres_labels)
206
- for i_emc, emc in enumerate(empty_checkboxes):
207
- st.session_state['checkboxes'][i_emc] = emc.checkbox(genres_labels[i_emc], value=st.session_state['checkboxes'][i_emc])
 
 
 
 
208
 
209
  st.markdown("##### What's the mood?")
210
- valence = st.slider('Valence (0 negative, 100 positive)', min_value=0, max_value=100, value=100, step=1) / 100
211
- energy = st.slider('Energy (0 low, 100 high)', min_value=0, max_value=100, value=100, step=1) / 100
212
- danceability = st.slider('Danceability (0 low, 100 high)', min_value=0, max_value=100, value=100, step=1) / 100
213
  target_mood = np.array([valence, energy, danceability]).reshape(1, 3)
214
- return target_mood
 
 
 
 
 
 
215
 
216
  @st.cache
217
  def filter_songs_by_genre(checkboxes, genres_labels, genres):
@@ -223,6 +237,7 @@ def filter_songs_by_genre(checkboxes, genres_labels, genres):
223
  genre_selected_indexes = np.array(sorted(set(genre_selected_indexes)))
224
  return genre_selected_indexes
225
 
 
226
  def find_best_songs_for_mood(all_tracks_audio_features, genre_selected_indexes, target_mood):
227
  candidate_moods = np.array([np.array(all_tracks_audio_features[feature])[genre_selected_indexes] for feature in ['valence', 'energy', 'danceability']]).T
228
  distances = np.sqrt(((candidate_moods - target_mood) ** 2).sum(axis=1))
@@ -230,6 +245,72 @@ def find_best_songs_for_mood(all_tracks_audio_features, genre_selected_indexes,
230
  n_candidates = distances.shape[0]
231
  return min_dist_indexes, n_candidates
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
  def run_app():
235
  global sp
@@ -243,13 +324,19 @@ def run_app():
243
  "* **You're in control**: you provide a source of candidate songs, select a list of genres and choose the mood for the playlist.")
244
  fake = centered_button(st.button, "Let's go", n_columns=7, disabled=True)
245
 
246
- sp, user_id, legit_genres = log_to_spotify()
 
 
 
 
 
 
247
 
248
  if 'login' in st.session_state or debug:
249
  all_tracks_uris, all_tracks_audio_features, genres, genres_labels = select_songs(legit_genres)
250
 
251
  if all_tracks_uris is not None:
252
- target_mood = customize_widgets(genres_labels)
253
  custom_button = centered_button(st.button, 'Run customization', n_columns=5)
254
  if custom_button or 'run_custom' in st.session_state.keys():
255
  st.session_state['run_custom'] = True
@@ -265,27 +352,29 @@ def run_app():
265
  init_time = time.time()
266
  if genre_selected_indexes is not None:
267
  min_dist_indexes, n_candidates = find_best_songs_for_mood(all_tracks_audio_features, genre_selected_indexes, target_mood)
268
-
269
  print(f'7. filter by mood: {time.time() - init_time:.2f}')
270
  init_time = time.time()
 
271
  if n_candidates < 25:
272
  st.warning('Please add more music sources or select more genres.')
273
  else:
274
  playlist_length = st.number_input(f'Pick a playlist length, given {n_candidates} candidates.', min_value=5,
275
- value=min(10, n_candidates//5), max_value=n_candidates//5)
276
 
277
- selected_tracks_indexes = genre_selected_indexes[min_dist_indexes[:playlist_length]]
278
- selected_tracks_uris = all_tracks_uris[selected_tracks_indexes]
279
- np.random.shuffle(selected_tracks_uris)
280
 
281
  playlist_name = st.text_input('Playlist name', value='Mood Playlist')
282
  if playlist_name == '':
283
  st.warning('Please enter a playlist name.')
284
  else:
285
- print(f'8. build playlist: {time.time() - init_time:.2f}')
286
- init_time = time.time()
287
  generation_button = centered_button(st.button, 'Generate playlist', n_columns=5)
288
  if generation_button:
 
 
 
 
289
  target_mood = np.array(target_mood).flatten() * 100
290
  description = f'Emotion Playlist for Valence: {int(target_mood[0])}, ' \
291
  f'Energy: {int(target_mood[1])}, ' \
 
1
+ import requests
2
+ import streamlit
3
  from PIL import Image
4
  from utils import *
5
  from app_utils import *
 
25
  client_credentials_manager = SpotifyClientCredentials()
26
  sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
27
  user_id = None
28
+ auth_manager = None
29
  else:
30
+ sp, user_id, auth_manager = new_get_client(session=st.session_state)
31
  if sp != None:
32
  legit_genres = sp.recommendation_genre_seeds()['genres']
33
+ st.session_state['login'] = (sp, user_id, legit_genres, auth_manager)
34
  st.success('You are logged in.')
35
  else:
36
  legit_genres = None
37
  else:
38
+ sp, user_id, legit_genres, auth_manager = st.session_state['login']
39
  st.success('You are logged in.')
40
+ return sp, user_id, legit_genres, auth_manager
41
 
42
 
43
  @st.cache(suppress_st_warning=True)
 
64
  if n_playlists <= 1:
65
  return all_uris
66
  else:
67
+ with st.expander("##### Select user playlists (default all)"):
68
+ # let the user uncheck playlists
69
+ st.markdown("Check boxes to select playlists from the selected users."
70
+ "Note: to check all, first uncheck all (bug).")
71
+ columns = st.columns(np.ones(5))
72
+ with columns[1]:
73
+ check_all_playlists = st.button('Check all')
74
+ with columns[3]:
75
+ uncheck_all_playlists = st.button('Uncheck all')
76
+
77
+ if 'checkboxes' not in st.session_state.keys():
78
+ st.session_state['checkboxes_playlists'] = [True] * n_playlists
79
+
80
+ empty_checkboxes = wall_of_checkboxes(all_names, max_width=5)
81
+ if check_all_playlists:
82
+ st.session_state['checkboxes_playlists'] = [True] * n_playlists
83
+ if uncheck_all_playlists:
84
+ st.session_state['checkboxes_playlists'] = [False] * n_playlists
85
+ for i_emc, emc in enumerate(empty_checkboxes):
86
+ st.session_state['checkboxes_playlists'][i_emc] = emc.checkbox(all_names[i_emc], value=st.session_state['checkboxes_playlists'][i_emc])
87
+
88
+ filter_playlist = centered_button(st.button, 'Update user playlists', n_columns=5)
89
  if filter_playlist:
90
  return list(np.array(all_uris)[np.where(st.session_state['checkboxes_playlists'])])
91
  else:
 
191
 
192
  def customize_widgets(genres_labels):
193
  st.subheader("Step 3: Customize it!")
194
+ st.markdown('##### Which genres?')
195
+
196
+ expanded = True if 'expanded_genres' in st.session_state else False
197
+ with st.expander("Unroll to select (default all)", expanded=expanded):
198
+ st.session_state['expanded_genres'] = True
199
+ st.markdown("Check boxes to select genres, see how many tracks were selected below. Note: to check all, first uncheck all (bug).")
200
+ columns = st.columns(np.ones(5))
201
+ with columns[1]:
202
+ check_all = st.button('Check all')
203
+ with columns[3]:
204
+ uncheck_all = st.button('Uncheck all')
205
+
206
+ if 'checkboxes' not in st.session_state.keys():
207
+ st.session_state['checkboxes'] = [True] * len(genres_labels)
208
+
209
+ empty_checkboxes = wall_of_checkboxes(genres_labels, max_width=5)
210
+ if check_all:
211
+ st.session_state['checkboxes'] = [True] * len(genres_labels)
212
+ if uncheck_all:
213
+ st.session_state['checkboxes'] = [False] * len(genres_labels)
214
+ for i_emc, emc in enumerate(empty_checkboxes):
215
+ st.session_state['checkboxes'][i_emc] = emc.checkbox(genres_labels[i_emc], value=st.session_state['checkboxes'][i_emc])
216
 
217
  st.markdown("##### What's the mood?")
218
+ valence = st.slider('Valence (0 negative, 100 positive)', min_value=0, max_value=100, value=60, step=1) / 100
219
+ energy = st.slider('Energy (0 low, 100 high)', min_value=0, max_value=100, value=60, step=1) / 100
220
+ danceability = st.slider('Danceability (0 low, 100 high)', min_value=0, max_value=100, value=60, step=1) / 100
221
  target_mood = np.array([valence, energy, danceability]).reshape(1, 3)
222
+ streamlit.markdown('##### Shall we explore?')
223
+ streamlit.write("Set the strength of music exploration:\n"
224
+ "* 0%: all songs are selected from the music sources\n"
225
+ "* 100%: all songs are new.")
226
+ exploration = st.slider('Exploration (0%, 100%)', min_value=0, max_value=100, value=50, step=1) / 100
227
+
228
+ return target_mood, exploration
229
 
230
  @st.cache
231
  def filter_songs_by_genre(checkboxes, genres_labels, genres):
 
237
  genre_selected_indexes = np.array(sorted(set(genre_selected_indexes)))
238
  return genre_selected_indexes
239
 
240
+ @st.cache
241
  def find_best_songs_for_mood(all_tracks_audio_features, genre_selected_indexes, target_mood):
242
  candidate_moods = np.array([np.array(all_tracks_audio_features[feature])[genre_selected_indexes] for feature in ['valence', 'energy', 'danceability']]).T
243
  distances = np.sqrt(((candidate_moods - target_mood) ** 2).sum(axis=1))
 
245
  n_candidates = distances.shape[0]
246
  return min_dist_indexes, n_candidates
247
 
248
+ @st.cache
249
+ def run_exploration(selected_tracks_uris, playlist_length, exploration, all_tracks_uris, target_mood, reauthenticate):
250
+ # sample exploration songs
251
+ if exploration > 0:
252
+ n_known = int(playlist_length * (1 - exploration))
253
+ n_new = playlist_length - n_known
254
+ print(f'Number of new songs: {n_new}, known songs: {n_known}')
255
+ known_songs = selected_tracks_uris[:n_known]
256
+ seed_songs = selected_tracks_uris[-n_new:]
257
+ dict_args = dict() # enforce bounds on recommendations' moods
258
+ for i_m, m in enumerate(['valence', 'energy', 'danceability']):
259
+ dict_args[f'min_{m}'] = max(0, target_mood[i_m] - 0.1)
260
+ dict_args[f'max_{m}'] = min(1, target_mood[i_m] + 0.1)
261
+ dict_args_loose = dict() # enforce bounds on recommendations' moods
262
+ for i_m, m in enumerate(['valence', 'energy', 'danceability']):
263
+ dict_args_loose[f'min_{m}'] = max(0, target_mood[i_m] - 0.2)
264
+ dict_args_loose[f'max_{m}'] = min(1, target_mood[i_m] + 0.2)
265
+ dict_args_looser = dict() # enforce bounds on recommendations' moods
266
+ for i_m, m in enumerate(['valence', 'energy', 'danceability']):
267
+ dict_args_loose[f'min_{m}'] = max(0, target_mood[i_m] - 0.3)
268
+ dict_args_loose[f'max_{m}'] = min(1, target_mood[i_m] + 0.3)
269
+ new_songs = []
270
+ counter_seed = 0
271
+ counter_failure = 0
272
+ while len(new_songs) < n_new:
273
+ try:
274
+ print(seed_songs[counter_seed])
275
+ print(dict_args)
276
+ reco = sp.recommendations(seed_tracks=[seed_songs[counter_seed]], market="from_token", **dict_args)['tracks']
277
+ if len(reco) == 0:
278
+ print('Using loose bounds')
279
+ reco = sp.recommendations(seed_tracks=[seed_songs[counter_seed]], market="from_token", **dict_args_loose)['tracks']
280
+ if len(reco) == 0:
281
+ print('Using looser bounds')
282
+ reco = sp.recommendations(seed_tracks=[seed_songs[counter_seed]], market="from_token", **dict_args_looser)['tracks']
283
+ if len(reco) == 0:
284
+ print('Removing bounds')
285
+ reco = sp.recommendations(seed_tracks=[seed_songs[counter_seed]], market="from_token")['tracks']
286
+ assert len(reco) > 0
287
+ for r in reco:
288
+ if r['uri'] not in all_tracks_uris:
289
+ new_songs.append(r['uri'])
290
+ break
291
+ except:
292
+ pass
293
+ print(counter_seed, len(new_songs))
294
+ counter_seed = (counter_seed + 1) % len(seed_songs)
295
+
296
+ assert len(new_songs) == n_new
297
+ assert len(known_songs) == n_known
298
+ selected_tracks_uris = np.array(list(known_songs) + new_songs)
299
+ np.random.shuffle(selected_tracks_uris)
300
+ return selected_tracks_uris
301
+
302
+ @st.cache
303
+ def sample_playlist(n_candidates, playlist_length, genre_selected_indexes, min_dist_indexes, all_tracks_uris):
304
+ # give more freedom to randomize the playlist
305
+ if n_candidates > 5 * playlist_length:
306
+ selected_tracks_indexes = genre_selected_indexes[min_dist_indexes[:int(playlist_length * 2)]]
307
+
308
+ else:
309
+ selected_tracks_indexes = genre_selected_indexes[min_dist_indexes[:playlist_length]]
310
+ selected_tracks_uris = all_tracks_uris[selected_tracks_indexes]
311
+ np.random.shuffle(selected_tracks_uris)
312
+ selected_tracks_uris = selected_tracks_uris[:playlist_length]
313
+ return selected_tracks_uris
314
 
315
  def run_app():
316
  global sp
 
324
  "* **You're in control**: you provide a source of candidate songs, select a list of genres and choose the mood for the playlist.")
325
  fake = centered_button(st.button, "Let's go", n_columns=7, disabled=True)
326
 
327
+ sp, user_id, legit_genres, auth_manager = log_to_spotify()
328
+
329
+ def reauthenticate():
330
+ global sp
331
+ auth_manager.get_access_token(st.experimental_get_query_params()['code'])
332
+ sp = spotipy.Spotify(auth_manager=auth_manager)
333
+ return sp
334
 
335
  if 'login' in st.session_state or debug:
336
  all_tracks_uris, all_tracks_audio_features, genres, genres_labels = select_songs(legit_genres)
337
 
338
  if all_tracks_uris is not None:
339
+ target_mood, exploration = customize_widgets(genres_labels)
340
  custom_button = centered_button(st.button, 'Run customization', n_columns=5)
341
  if custom_button or 'run_custom' in st.session_state.keys():
342
  st.session_state['run_custom'] = True
 
352
  init_time = time.time()
353
  if genre_selected_indexes is not None:
354
  min_dist_indexes, n_candidates = find_best_songs_for_mood(all_tracks_audio_features, genre_selected_indexes, target_mood)
 
355
  print(f'7. filter by mood: {time.time() - init_time:.2f}')
356
  init_time = time.time()
357
+
358
  if n_candidates < 25:
359
  st.warning('Please add more music sources or select more genres.')
360
  else:
361
  playlist_length = st.number_input(f'Pick a playlist length, given {n_candidates} candidates.', min_value=5,
362
+ value=min(10, n_candidates//3), max_value=n_candidates//3)
363
 
364
+ selected_tracks_uris = sample_playlist(n_candidates, playlist_length, genre_selected_indexes, min_dist_indexes, all_tracks_uris)
365
+ print(f'8. Sample songs: {time.time() - init_time:.2f}')
366
+ init_time = time.time()
367
 
368
  playlist_name = st.text_input('Playlist name', value='Mood Playlist')
369
  if playlist_name == '':
370
  st.warning('Please enter a playlist name.')
371
  else:
 
 
372
  generation_button = centered_button(st.button, 'Generate playlist', n_columns=5)
373
  if generation_button:
374
+ selected_tracks_uris = run_exploration(selected_tracks_uris, playlist_length, exploration, all_tracks_uris, target_mood.flatten(), reauthenticate)
375
+ print(f'9. run exploration: {time.time() - init_time:.2f}')
376
+ init_time = time.time()
377
+
378
  target_mood = np.array(target_mood).flatten() * 100
379
  description = f'Emotion Playlist for Valence: {int(target_mood[0])}, ' \
380
  f'Energy: {int(target_mood[1])}, ' \