From 14a1a33594398fd497f3d211a2bca04d87da3500 Mon Sep 17 00:00:00 2001 From: jonathandow <89482514+jonathandow@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:33:33 -0700 Subject: [PATCH] Changes to alg and restructure --- app.py | 213 ++++++++++++++-------------------------------- tempfolder/SpotAI | 1 + 2 files changed, 65 insertions(+), 149 deletions(-) create mode 160000 tempfolder/SpotAI diff --git a/app.py b/app.py index ee817c4..1f9b82f 100644 --- a/app.py +++ b/app.py @@ -117,74 +117,34 @@ def get_all_saved_tracks(self): saved.extend(results['items']) return saved - - def create_playlist(self, playlist_name, num_clusters=5, num_iterations=10): - self.clear_log_file('spotai.log') - print("Creating Playlist......") - start_time = time.time() - top_artists = self.sp.current_user_top_artists(limit=5, time_range='medium_term')['items'] - - #getting all tracks form user's library (thinking of not using recent_songs not sure) - liked_songs = self.get_all_saved_tracks() - recent_songs = self.get_recent_tracks() - top_songs = self.get_all_top_tracks() - - - #appending all of these tracks to check if the user already knows them - known_names = {item['track']['name'] for item in liked_songs} - known_names.update({item['track']['name'] for item in recent_songs}) - known_names.update({item['name'] for item in top_songs}) - - known_tracks = {item['track']['id'] for item in liked_songs} - known_tracks.update({item['track']['id'] for item in recent_songs}) - known_tracks.update({item['id'] for item in top_songs}) - - track_ids = [track['id'] for track in top_songs[:200]] - #getting audio features for the tracks - try: - features = self.get_audio_features_for_tracks(track_ids) - except spotipy.exceptions.SpotifyException as e: - return jsonify({'error': str(e)}), 500 - - # Filter out None values from features - features = [feature for feature in features if feature is not None] - - if not features: - return jsonify({'error': "No audio features retrieved for the tracks"}), 500 - + def recommend_tracks(self, track_ids, num_clusters=5, num_iterations=10, features=None): features_df = pd.DataFrame(features) # X = features_df[['danceability', 'energy', 'key', 'loudness', 'mode', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']] plays = Counter(track_ids) - #normalizing the play count + max_plays = max(plays.values()) features_df['play count'] = features_df['id'].apply(lambda x: plays[x] / max_plays) - #selecting the features to be used for clustering + feature_columns = ['danceability', 'energy', 'key', 'loudness', 'mode', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo', 'play count'] X = features_df[feature_columns] - print("Scaler...") - #scaling the features + scaler = StandardScaler() X_scaled = scaler.fit_transform(X) - print("PCA...") - #applying PCA to reduce the dimensions - pca = PCA(n_components=5) - X_pca = pca.fit_transform(X_scaled) - print("KMeans...") - #applying KMeans clustering + + # pca = PCA(n_components=5) + # X_pca = pca.fit_transform(X_scaled) + kmeans = KMeans(n_clusters=num_clusters, random_state=42) - kmeans.fit(X_pca) + kmeans.fit(X_scaled) features_df['kmeans_cluster'] = kmeans.labels_ centroids_kmeans = kmeans.cluster_centers_ - print("GMM...") - #applying GMM clustering + gmm = GaussianMixture(n_components=num_clusters, random_state=42) - gmm.fit(X_pca) - features_df['cluster'] = gmm.predict(X_pca) + gmm.fit(X_scaled) + features_df['cluster'] = gmm.predict(X_scaled) centroids = gmm.means_ - - #combining the centroids of KMeans and GMM kmeans_weights = np.bincount(features_df['kmeans_cluster']) gmm_weights = np.bincount(features_df['cluster']) @@ -193,24 +153,40 @@ def create_playlist(self, playlist_name, num_clusters=5, num_iterations=10): combined_centroids = (weighted_kmeans_centroids + weighted_gmm_centroids) / 2 - # OLD IMPLEMENTATION (TESTING) # kmeans = KMeans(n_clusters=num_clusters, random_state=42) # kmeans.fit(X) # features_df['cluster'] = kmeans.labels_ # centroids = kmeans.cluster_centers_ - print("Recommendations...") - #getting the recommendations + recommendations = [] for i in range(num_clusters): centroid = combined_centroids[i] cluster_tracks = features_df[features_df['cluster'] == i] - cluster_tracks_pca = X_pca[features_df['cluster'] == i] + cluster_indices = cluster_tracks.index + cluster_tracks_scaled = X_scaled[cluster_indices] centroid_array = np.array(centroid) - closest_index = np.argmin(np.sum((cluster_tracks_pca - centroid_array) ** 2, axis=1)) - closest_id = track_ids[cluster_tracks.index[closest_index]] - recommendations.append(closest_id) + distance = np.linalg.norm(cluster_tracks_scaled - centroid_array, axis=1) + closest_index = np.argmin(distance) + + closest_id = cluster_indices[closest_index] + print(closest_id) + close = track_ids[closest_id] + recommendations.append(close) + return recommendations + + def get_known_tracks(self, liked_songs, recent_songs, top_songs): + known_names = {item['track']['name'] for item in liked_songs} + known_names.update({item['track']['name'] for item in recent_songs[:100]}) + known_names.update({item['name'] for item in top_songs}) + + known_tracks = {item['track']['id'] for item in liked_songs} + known_tracks.update({item['track']['id'] for item in recent_songs[:100]}) + known_tracks.update({item['id'] for item in top_songs}) + return known_tracks, known_names + def playlist_create(self, recommendations, liked_songs, top_songs, recent_songs, start_time, playlist_name, num_iterations=10): + known_tracks, known_names = self.get_known_tracks(liked_songs, recent_songs, top_songs) user_id = self.sp.me()['id'] playlist_description = f"SpotAI Recommendations. Updated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}." playlists = self.sp.user_playlists(user_id)['items'] @@ -229,7 +205,7 @@ def create_playlist(self, playlist_name, num_clusters=5, num_iterations=10): for x in range(num_iterations): random_tracks = random.sample(recommendations, min(5, len(recommendations))) try: - recommended_tracks = self.sp.recommendations(seed_tracks=random_tracks, limit=100) + recommended_tracks = self.sp.recommendations(seed_tracks=random_tracks[:5], limit=100) except spotipy.exceptions.SpotifyException as e: return jsonify({'error': str(e)}), 500 @@ -238,11 +214,36 @@ def create_playlist(self, playlist_name, num_clusters=5, num_iterations=10): most_common_recommendations = [track_id for track_id, count in rec_counter.most_common(100)] self.sp.user_playlist_add_tracks(user=user_id, playlist_id=playlist_id, tracks=most_common_recommendations) - end_time = time.time() # Record the end time duration = end_time - start_time # Calculate the duration print(f"Playlist generation took {duration:.2f} seconds") # Print the duration + return playlist_id + + def create_playlist(self, playlist_name, num_clusters=5, num_iterations=10): + self.clear_log_file('spotai.log') + print("Creating Playlist......") + start_time = time.time() + + #getting all tracks form user's library (thinking of not using recent_songs not sure) + liked_songs = self.get_all_saved_tracks() + recent_songs = self.get_recent_tracks() + top_songs = self.get_all_top_tracks() + track_ids = [track['id'] for track in top_songs[:200]] + #getting audio features for the tracks + try: + features = self.get_audio_features_for_tracks(track_ids) + except spotipy.exceptions.SpotifyException as e: + return jsonify({'error': str(e)}), 500 + + # Filter out None values from features + features = [feature for feature in features if feature is not None] + + if not features: + return jsonify({'error': "No audio features retrieved for the tracks"}), 500 + + recommendations = self.recommend_tracks(track_ids, num_clusters, num_iterations, features) + playlist_id = self.playlist_create(recommendations, liked_songs, top_songs, recent_songs, start_time, playlist_name) return render_template('playlist.html', playlist_name=playlist_name, playlist_url=f"https://open.spotify.com/playlist/{playlist_id}") def create_playlist_from_playlist(self, selected_playlist_id, playlist_name, num_clusters=5, num_iterations=10): @@ -274,14 +275,6 @@ def create_playlist_from_playlist(self, selected_playlist_id, playlist_name, num recent_songs = self.get_recent_tracks() top_songs = self.get_all_top_tracks() - known_names = {item['track']['name'] for item in liked_songs} - known_names.update({item['track']['name'] for item in recent_songs[:100]}) - known_names.update({item['name'] for item in top_songs}) - - known_tracks = {item['track']['id'] for item in liked_songs} - known_tracks.update({item['track']['id'] for item in recent_songs[:100]}) - known_tracks.update({item['id'] for item in top_songs}) - # track_ids = [track['track']['id'] for track in tracks] try: @@ -292,86 +285,8 @@ def create_playlist_from_playlist(self, selected_playlist_id, playlist_name, num if not features: return jsonify({'error': "No audio features retrieved for the tracks"}), 500 - features_df = pd.DataFrame(features) - # X = features_df[['danceability', 'energy', 'key', 'loudness', 'mode', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo']] - - plays = Counter(track_ids) - - max_plays = max(plays.values()) - features_df['play count'] = features_df['id'].apply(lambda x: plays[x] / max_plays) - - feature_columns = ['danceability', 'energy', 'key', 'loudness', 'mode', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo', 'play count'] - X = features_df[feature_columns] - - scaler = StandardScaler() - X_scaled = scaler.fit_transform(X) - - pca = PCA(n_components=5) - X_pca = pca.fit_transform(X_scaled) - - kmeans = KMeans(n_clusters=num_clusters, random_state=42) - kmeans.fit(X_pca) - features_df['kmeans_cluster'] = kmeans.labels_ - centroids_kmeans = kmeans.cluster_centers_ - - gmm = GaussianMixture(n_components=num_clusters, random_state=42) - gmm.fit(X_pca) - features_df['cluster'] = gmm.predict(X_pca) - centroids = gmm.means_ - - kmeans_weights = np.bincount(features_df['kmeans_cluster']) - gmm_weights = np.bincount(features_df['cluster']) - - weighted_kmeans_centroids = np.dot(kmeans_weights, centroids_kmeans) / np.sum(kmeans_weights) - weighted_gmm_centroids = np.dot(gmm_weights, centroids) / np.sum(gmm_weights) - - combined_centroids = (weighted_kmeans_centroids + weighted_gmm_centroids) / 2 - - # kmeans = KMeans(n_clusters=num_clusters, random_state=42) - # kmeans.fit(X) - # features_df['cluster'] = kmeans.labels_ - # centroids = kmeans.cluster_centers_ - - recommendations = [] - for i in range(num_clusters): - centroid = combined_centroids[i] - cluster_tracks = features_df[features_df['cluster'] == i] - cluster_tracks_pca = X_pca[features_df['cluster'] == i] - centroid_array = np.array(centroid) - - closest_index = np.argmin(np.sum((cluster_tracks_pca - centroid_array) ** 2, axis=1)) - closest_id = track_ids[cluster_tracks.index[closest_index]] - recommendations.append(closest_id) - user_id = self.sp.me()['id'] - playlist_description = f"SpotAI Recommendations. Based on {self.sp.playlist(selected_playlist_id)['name']}. Updated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}." - playlists = self.sp.user_playlists(user_id)['items'] - playlist_id = None - for playlist in playlists: - if playlist['name'] == playlist_name: - playlist_id = playlist['id'] - self.sp.playlist_replace_items(playlist_id, []) - self.sp.playlist_change_details(playlist_id, description=playlist_description) - break - if not playlist_id: - new_playlist = self.sp.user_playlist_create(user=user_id, name=playlist_name, public=True, description=playlist_description) - playlist_id = new_playlist['id'] - - rec_counter = Counter() - for x in range(num_iterations): - random_tracks = random.sample(recommendations, min(5, len(recommendations))) - try: - recommended_tracks = self.sp.recommendations(seed_tracks=random_tracks[:5], limit=100) - except spotipy.exceptions.SpotifyException as e: - return jsonify({'error': str(e)}), 500 - - new_recommendations = [track['id'] for track in recommended_tracks['tracks'] if track['id'] not in known_tracks and track['name'] not in known_names] - rec_counter.update(new_recommendations) - - most_common_recommendations = [track_id for track_id, count in rec_counter.most_common(100)] - self.sp.user_playlist_add_tracks(user=user_id, playlist_id=playlist_id, tracks=most_common_recommendations) - end_time = time.time() # Record the end time - duration = end_time - start_time # Calculate the duration - print(f"Playlist generation took {duration:.2f} seconds") # Print the duration + recommendations = self.recommend_tracks(track_ids, num_clusters, num_iterations, features) + playlist_id = self.playlist_create(recommendations, liked_songs, top_songs, recent_songs, start_time, playlist_name) return render_template('playlist.html', playlist_name=playlist_name, playlist_url=f"https://open.spotify.com/playlist/{playlist_id}") spot_ai = SpotAI(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, redirect_uri="http://localhost:5000/callback") diff --git a/tempfolder/SpotAI b/tempfolder/SpotAI new file mode 160000 index 0000000..be63df6 --- /dev/null +++ b/tempfolder/SpotAI @@ -0,0 +1 @@ +Subproject commit be63df64650d324046b37f248154859cee8c3887