Mthrfkr commited on
Commit
ef6a438
·
verified ·
1 Parent(s): 54a24f7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +157 -150
app.py CHANGED
@@ -27,10 +27,11 @@ def get_token(client_id, client_secret):
27
  payload = {'grant_type': 'client_credentials'}
28
  response = requests.post(url, headers=headers, data=payload, auth=(client_id, client_secret))
29
  global total_requests
30
- total_requests += 1 # Counting request
31
  if response.status_code == 200:
32
  return response.json().get('access_token')
33
  else:
 
34
  return None
35
 
36
  def handle_rate_limit(response, attempt):
@@ -45,200 +46,206 @@ def make_request_with_retry(url, headers, params=None, max_retries=5):
45
  global total_requests
46
  for attempt in range(max_retries):
47
  response = requests.get(url, headers=headers, params=params)
48
- total_requests += 1 # Counting request
49
  if handle_rate_limit(response, attempt):
50
  continue
51
  if response.status_code == 200:
52
  return response
53
  else:
54
- break
 
55
  return None
56
 
57
- def get_audio_features(token, track_ids):
58
- audio_features = {}
59
- url = 'https://api.spotify.com/v1/audio-features'
60
- headers = {'Authorization': f'Bearer {token}'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
- # Print track IDs for debugging
63
- print(f"Getting audio features for {len(track_ids)} tracks")
64
-
65
- for i in range(0, len(track_ids), 100):
66
- batch_ids = track_ids[i:i+100]
67
- params = {'ids': ','.join(batch_ids)}
68
- response = make_request_with_retry(url, headers, params)
69
- if response:
70
- features_list = response.json().get('audio_features', [])
71
- print(f"Received {len(features_list)} audio features")
72
- for feature in features_list:
73
- if feature and 'id' in feature:
74
- audio_features[feature['id']] = feature
75
-
76
- # Print how many valid features we found
77
- print(f"Found {len(audio_features)} valid audio features")
78
- return audio_features
79
-
80
- def get_tracks_and_features(token, url):
81
  headers = {'Authorization': f'Bearer {token}'}
82
- track_ids = []
83
-
84
- if "track" in url:
85
- # Handle various URL formats
86
- parts = url.split("/")
87
- for part in parts:
88
- if part and ("?" in part):
89
- track_id = part.split("?")[0]
90
- track_ids = [track_id]
91
- break
92
- if not track_ids and len(parts) > 0:
93
- potential_id = parts[-1]
94
- if potential_id:
95
- track_ids = [potential_id]
96
- elif "playlist" in url:
97
- # Handle various URL formats
98
- parts = url.split("/")
99
- playlist_id = None
100
- for part in parts:
101
- if part and ("?" in part):
102
- playlist_id = part.split("?")[0]
103
- break
104
- if not playlist_id and len(parts) > 0:
105
- playlist_id = parts[-1]
106
 
107
- if playlist_id:
108
- tracks_url = f'https://api.spotify.com/v1/playlists/{playlist_id}/tracks'
109
- response = make_request_with_retry(tracks_url, headers)
110
- if response and response.json().get('items'):
111
- track_ids = [item['track']['id'] for item in response.json().get('items', [])
112
- if item.get('track') and item['track'].get('id')]
113
-
114
- if not track_ids:
115
- return None, None
116
-
117
- audio_features = get_audio_features(token, track_ids)
118
- return track_ids, audio_features
119
 
120
- def find_similar_tracks(token, audio_features, n_songs=10):
 
 
 
 
121
  headers = {'Authorization': f'Bearer {token}'}
122
- seed_tracks = list(audio_features.keys())[:5] # Spotify allows up to 5 seed tracks
123
-
124
- params = {
125
- 'seed_tracks': ','.join(seed_tracks),
126
- 'limit': n_songs
127
- }
128
-
129
- url = 'https://api.spotify.com/v1/recommendations'
130
- response = make_request_with_retry(url, headers, params=params)
131
-
132
- if response:
133
- recommended_tracks = response.json().get('tracks', [])
134
- track_ids = [track['id'] for track in recommended_tracks]
135
- return get_track_information(token, track_ids)
136
 
 
 
 
137
  return []
138
 
139
- def get_track_information(token, track_ids):
 
140
  tracks_info = []
141
- audio_features = get_audio_features(token, track_ids) # Get audio features
142
- url = 'https://api.spotify.com/v1/tracks'
143
- headers = {'Authorization': f'Bearer {token}'}
144
-
145
- for i in range(0, len(track_ids), 50):
146
- batch_ids = track_ids[i:i+50]
147
- params = {'ids': ','.join(batch_ids)}
148
- response = make_request_with_retry(url, headers, params)
149
- if response:
150
- tracks = response.json().get('tracks', [])
151
- for track in tracks:
152
- features = audio_features.get(track['id'], {})
153
- tracks_info.append({
154
- 'artist': track['artists'][0]['name'] if track['artists'] else 'Unknown',
155
- 'title': track['name'],
156
- 'isrc': track['external_ids'].get('isrc', 'Not available'),
157
- 'popularity': track.get('popularity', 'Not available'),
158
- 'release_year': track.get('album', {}).get('release_date', 'Not available').split('-')[0] if track.get('album', {}).get('release_date') else 'Not available',
159
- 'duration': track.get('duration_ms', 'Not available'),
160
- 'danceability': features.get('danceability', 'Not available'),
161
- 'energy': features.get('energy', 'Not available'),
162
- 'tempo': features.get('tempo', 'Not available'),
163
- 'valence': features.get('valence', 'Not available'),
164
- 'url': track['external_urls']['spotify']
165
- })
166
  return tracks_info
167
 
168
  # Main Interface Function
169
- def interface(project_name, spotify_url, num_similar_songs=10):
170
- # Input validation
171
- if not spotify_url or not ("spotify.com" in spotify_url and ("track" in spotify_url or "playlist" in spotify_url)):
172
- error_message = "Invalid URL format. Please enter a valid Spotify track or playlist URL."
173
- # Return empty DataFrame with error message and None for file
 
 
 
 
 
174
  return gr.Dataframe(value=pd.DataFrame({"Error": [error_message]})), None
175
-
176
- # Log the URL for debugging
177
- print(f"Processing Spotify URL: {spotify_url}")
178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  token_spotify = get_token(client_ids[current_api_index], client_secrets[current_api_index])
180
  if not token_spotify:
181
  error_message = "Failed to authenticate with Spotify API. Please try again later."
182
  return gr.Dataframe(value=pd.DataFrame({"Error": [error_message]})), None
183
 
184
- print(f"Successfully obtained token")
185
-
186
- try:
187
- track_ids, audio_features = get_tracks_and_features(token_spotify, spotify_url)
188
- print(f"Track IDs: {track_ids[:5]}{'...' if len(track_ids) > 5 else ''}")
189
-
190
- if not track_ids:
191
- error_message = "No valid tracks found for the provided URL. Please check the URL and try again."
192
- return gr.Dataframe(value=pd.DataFrame({"Error": [error_message]})), None
193
-
194
- if not audio_features:
195
- error_message = "Could not retrieve audio features for the tracks. Please try again."
196
- return gr.Dataframe(value=pd.DataFrame({"Error": [error_message]})), None
197
-
198
- if len(audio_features) == 0:
199
- error_message = "Received empty audio features. The track might not be available in your region."
200
- return gr.Dataframe(value=pd.DataFrame({"Error": [error_message]})), None
201
- except Exception as e:
202
- error_message = f"An error occurred: {str(e)}"
203
- print(f"Error: {str(e)}")
204
- return gr.Dataframe(value=pd.DataFrame({"Error": [error_message]})), None
205
-
206
- similar_tracks_info = find_similar_tracks(token_spotify, audio_features, num_similar_songs)
207
 
208
- if not similar_tracks_info:
209
- error_message = "Could not find similar tracks. Please try with a different track or playlist."
210
  return gr.Dataframe(value=pd.DataFrame({"Error": [error_message]})), None
211
 
212
- # Create DataFrame
213
- df = pd.DataFrame(similar_tracks_info)
214
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  # Save DataFrame to an Excel file
216
  tmpfile = NamedTemporaryFile(delete=False, suffix='.xlsx')
217
  df.to_excel(tmpfile.name, index=False)
218
-
219
  # Rename the file with the project name
220
- project_file_name = f"{project_name if project_name else 'spotify_similar_tracks'}.xlsx"
221
  shutil.move(tmpfile.name, project_file_name)
222
-
223
- return df, project_file_name # Returns the DataFrame and the link to the Excel file
224
 
225
  # Gradio Interface Configuration
226
  iface = gr.Interface(
227
  fn=interface,
228
  inputs=[
229
- gr.Textbox(label="Project Name", placeholder="Enter a name for your project"),
230
- gr.Textbox(label="Spotify URL (Track or Playlist)", placeholder="https://open.spotify.com/track/... or https://open.spotify.com/playlist/..."),
231
- gr.Slider(label="Number of Similar Songs", minimum=1, maximum=100, value=10, step=1)
 
 
 
 
232
  ],
233
  outputs=[
234
  gr.Dataframe(),
235
  gr.File(label="Download Excel")
236
  ],
237
- title="Spotify Similar Track Finder",
238
- description="Enter a Spotify URL to find similar songs based on their audio features.",
239
  examples=[
240
- ["Pop Hits", "https://open.spotify.com/track/1mWdTewIgB3gtBM3TOSFhB", 10],
241
- ["Rock Classics", "https://open.spotify.com/playlist/37i9dQZF1DWXRqgorJj26U", 15]
242
  ],
243
  allow_flagging="never"
244
  )
 
27
  payload = {'grant_type': 'client_credentials'}
28
  response = requests.post(url, headers=headers, data=payload, auth=(client_id, client_secret))
29
  global total_requests
30
+ total_requests += 1
31
  if response.status_code == 200:
32
  return response.json().get('access_token')
33
  else:
34
+ print(f"Error getting token: {response.status_code} - {response.text}")
35
  return None
36
 
37
  def handle_rate_limit(response, attempt):
 
46
  global total_requests
47
  for attempt in range(max_retries):
48
  response = requests.get(url, headers=headers, params=params)
49
+ total_requests += 1
50
  if handle_rate_limit(response, attempt):
51
  continue
52
  if response.status_code == 200:
53
  return response
54
  else:
55
+ print(f"Request failed: {response.status_code} - {response.text}")
56
+ time.sleep(1) # Small delay before retrying
57
  return None
58
 
59
+ def extract_id_from_url(url, type_keyword):
60
+ """Extract Spotify ID from URL for either track or playlist."""
61
+ parts = url.split("/")
62
+ for i, part in enumerate(parts):
63
+ if type_keyword in part and i + 1 < len(parts):
64
+ potential_id = parts[i + 1].split("?")[0]
65
+ if potential_id:
66
+ return potential_id
67
+
68
+ # If above fails, try to find ID in the last part of the URL
69
+ last_part = parts[-1]
70
+ if "?" in last_part:
71
+ return last_part.split("?")[0]
72
+ return last_part
73
+
74
+ def get_playlist_tracks(token, playlist_url):
75
+ """Get all tracks from a playlist URL."""
76
+ playlist_id = extract_id_from_url(playlist_url, "playlist")
77
+ print(f"Extracted playlist ID: {playlist_id}")
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  headers = {'Authorization': f'Bearer {token}'}
80
+ tracks_url = f'https://api.spotify.com/v1/playlists/{playlist_id}/tracks'
81
+
82
+ all_tracks = []
83
+ next_url = tracks_url
84
+
85
+ while next_url:
86
+ print(f"Fetching tracks from: {next_url}")
87
+ response = make_request_with_retry(next_url, headers)
88
+ if not response:
89
+ break
90
+
91
+ data = response.json()
92
+ items = data.get('items', [])
93
+ for item in items:
94
+ if item and item.get('track'):
95
+ track = item['track']
96
+ all_tracks.append(track)
 
 
 
 
 
 
 
97
 
98
+ next_url = data.get('next')
99
+
100
+ print(f"Found {len(all_tracks)} tracks in playlist")
101
+ return all_tracks
 
 
 
 
 
 
 
 
102
 
103
+ def get_track_info(token, track_url):
104
+ """Get information about a single track."""
105
+ track_id = extract_id_from_url(track_url, "track")
106
+ print(f"Extracted track ID: {track_id}")
107
+
108
  headers = {'Authorization': f'Bearer {token}'}
109
+ url = f'https://api.spotify.com/v1/tracks/{track_id}'
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
+ response = make_request_with_retry(url, headers)
112
+ if response:
113
+ return [response.json()]
114
  return []
115
 
116
+ def extract_track_details(tracks):
117
+ """Extract relevant information from track objects."""
118
  tracks_info = []
119
+ for track in tracks:
120
+ if not track:
121
+ continue
122
+
123
+ tracks_info.append({
124
+ 'artist': track['artists'][0]['name'] if track.get('artists') else 'Unknown',
125
+ 'title': track.get('name', 'Unknown'),
126
+ 'album': track.get('album', {}).get('name', 'Unknown'),
127
+ 'isrc': track.get('external_ids', {}).get('isrc', 'Not available'),
128
+ 'popularity': track.get('popularity', 'Not available'),
129
+ 'release_date': track.get('album', {}).get('release_date', 'Not available'),
130
+ 'duration_ms': track.get('duration_ms', 'Not available'),
131
+ 'spotify_url': track.get('external_urls', {}).get('spotify', 'Not available'),
132
+ 'preview_url': track.get('preview_url', 'Not available'),
133
+ 'playlist_source': getattr(track, 'playlist_source', 'Direct Track')
134
+ })
 
 
 
 
 
 
 
 
 
135
  return tracks_info
136
 
137
  # Main Interface Function
138
+ def interface(project_name, spotify_urls, include_all_info=True):
139
+ """Process multiple Spotify URLs (tracks or playlists) and combine results."""
140
+ if not project_name:
141
+ project_name = "spotify_tracks"
142
+
143
+ # Split and clean URLs
144
+ urls_list = [url.strip() for url in spotify_urls.strip().split('\n') if url.strip()]
145
+
146
+ if not urls_list:
147
+ error_message = "Please enter at least one Spotify URL (track or playlist)."
148
  return gr.Dataframe(value=pd.DataFrame({"Error": [error_message]})), None
 
 
 
149
 
150
+ # Validate URLs
151
+ valid_urls = []
152
+ for url in urls_list:
153
+ if "spotify.com" in url and ("track" in url or "playlist" in url):
154
+ valid_urls.append(url)
155
+ else:
156
+ print(f"Invalid URL format, skipping: {url}")
157
+
158
+ if not valid_urls:
159
+ error_message = "No valid Spotify URLs found. Please enter valid track or playlist URLs."
160
+ return gr.Dataframe(value=pd.DataFrame({"Error": [error_message]})), None
161
+
162
+ print(f"Processing {len(valid_urls)} valid Spotify URLs")
163
+
164
+ # Get token
165
  token_spotify = get_token(client_ids[current_api_index], client_secrets[current_api_index])
166
  if not token_spotify:
167
  error_message = "Failed to authenticate with Spotify API. Please try again later."
168
  return gr.Dataframe(value=pd.DataFrame({"Error": [error_message]})), None
169
 
170
+ all_tracks = []
171
+
172
+ # Process each URL
173
+ for url in valid_urls:
174
+ try:
175
+ print(f"Processing URL: {url}")
176
+ if "playlist" in url:
177
+ tracks = get_playlist_tracks(token_spotify, url)
178
+ # Add source information
179
+ for track in tracks:
180
+ track['playlist_source'] = url
181
+ all_tracks.extend(tracks)
182
+ elif "track" in url:
183
+ track = get_track_info(token_spotify, url)
184
+ if track:
185
+ track[0]['playlist_source'] = url
186
+ all_tracks.extend(track)
187
+ except Exception as e:
188
+ print(f"Error processing URL {url}: {str(e)}")
189
+ continue
 
 
 
190
 
191
+ if not all_tracks:
192
+ error_message = "Could not find any tracks in the provided URLs."
193
  return gr.Dataframe(value=pd.DataFrame({"Error": [error_message]})), None
194
 
195
+ # Extract track details
196
+ tracks_info = extract_track_details(all_tracks)
197
+
198
+ # Remove duplicate tracks (based on ISRC or title+artist if ISRC not available)
199
+ df = pd.DataFrame(tracks_info)
200
+
201
+ # Create a key for deduplication
202
+ df['dedup_key'] = df.apply(
203
+ lambda row: row['isrc'] if row['isrc'] != 'Not available' else f"{row['artist']}_{row['title']}",
204
+ axis=1
205
+ )
206
+
207
+ # Drop duplicates
208
+ df = df.drop_duplicates(subset='dedup_key')
209
+ df = df.drop(columns=['dedup_key'])
210
+
211
+ print(f"Found {len(df)} unique tracks after deduplication")
212
+
213
+ # Filter columns if not include_all_info
214
+ if not include_all_info:
215
+ columns_to_keep = ['artist', 'title', 'album', 'release_date', 'popularity', 'spotify_url']
216
+ df = df[columns_to_keep]
217
+
218
  # Save DataFrame to an Excel file
219
  tmpfile = NamedTemporaryFile(delete=False, suffix='.xlsx')
220
  df.to_excel(tmpfile.name, index=False)
221
+
222
  # Rename the file with the project name
223
+ project_file_name = f"{project_name}.xlsx"
224
  shutil.move(tmpfile.name, project_file_name)
225
+
226
+ return df, project_file_name
227
 
228
  # Gradio Interface Configuration
229
  iface = gr.Interface(
230
  fn=interface,
231
  inputs=[
232
+ gr.Textbox(label="Project Name", placeholder="Enter a name for your export"),
233
+ gr.Textbox(
234
+ label="Spotify URLs (Tracks or Playlists)",
235
+ placeholder="Enter one Spotify URL per line (tracks or playlists)",
236
+ lines=5
237
+ ),
238
+ gr.Checkbox(label="Include All Track Information", value=True)
239
  ],
240
  outputs=[
241
  gr.Dataframe(),
242
  gr.File(label="Download Excel")
243
  ],
244
+ title="Spotify Track Collector",
245
+ description="Extract tracks from multiple Spotify playlists and tracks into a single Excel file.",
246
  examples=[
247
+ ["Pop Collection", "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M\nhttps://open.spotify.com/track/4cOdK2wGLETKBW3PvgPWqT", True],
248
+ ["Rock Collection", "https://open.spotify.com/playlist/37i9dQZF1DWXRqgorJj26U", False]
249
  ],
250
  allow_flagging="never"
251
  )