Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -11,7 +11,7 @@ client_ids = os.getenv("SPOTIFY_CLIENT_IDS", "").split(',')
|
|
| 11 |
client_secrets = os.getenv("SPOTIFY_CLIENT_SECRETS", "").split(',')
|
| 12 |
|
| 13 |
if not client_ids or not client_secrets:
|
| 14 |
-
raise ValueError("❌
|
| 15 |
|
| 16 |
# Gestión de Rotación de Clientes
|
| 17 |
current_client_index = 0
|
|
@@ -22,7 +22,7 @@ def rotate_client():
|
|
| 22 |
global current_client_index, request_counter
|
| 23 |
current_client_index = (current_client_index + 1) % len(client_ids)
|
| 24 |
request_counter = 0
|
| 25 |
-
print(f"🔄
|
| 26 |
|
| 27 |
def get_spotify_token():
|
| 28 |
global current_client_index
|
|
@@ -40,10 +40,10 @@ def get_spotify_token():
|
|
| 40 |
if response.status_code == 200:
|
| 41 |
return response.json().get('access_token')
|
| 42 |
|
| 43 |
-
print(f"❌ Error
|
| 44 |
rotate_client()
|
| 45 |
|
| 46 |
-
raise Exception("🚨
|
| 47 |
|
| 48 |
def make_spotify_request(url, params=None):
|
| 49 |
global request_counter
|
|
@@ -55,9 +55,9 @@ def make_spotify_request(url, params=None):
|
|
| 55 |
request_counter += 1
|
| 56 |
|
| 57 |
if response.status_code == 429:
|
| 58 |
-
print(f"⚠️
|
| 59 |
rotate_client()
|
| 60 |
-
time.sleep(int(response.headers.get('Retry-After', 10))) #
|
| 61 |
continue
|
| 62 |
|
| 63 |
if response.status_code == 200:
|
|
@@ -71,10 +71,10 @@ def make_spotify_request(url, params=None):
|
|
| 71 |
return None
|
| 72 |
|
| 73 |
def extract_spotify_id(url, resource_type):
|
| 74 |
-
"""
|
| 75 |
clean_url = url.strip().split('?')[0].replace("https://open.spotify.com/", "")
|
| 76 |
|
| 77 |
-
#
|
| 78 |
if "/intl-" in clean_url:
|
| 79 |
parts = clean_url.split("/")
|
| 80 |
return parts[-1]
|
|
@@ -93,10 +93,10 @@ def extract_spotify_id(url, resource_type):
|
|
| 93 |
def get_playlist_tracks(playlist_url):
|
| 94 |
playlist_id = extract_spotify_id(playlist_url, "playlist")
|
| 95 |
if not playlist_id or len(playlist_id) != 22:
|
| 96 |
-
print(f"❌
|
| 97 |
return []
|
| 98 |
|
| 99 |
-
print(f"🎧
|
| 100 |
all_tracks = []
|
| 101 |
url = f'https://api.spotify.com/v1/playlists/{playlist_id}/tracks'
|
| 102 |
params = {'market': 'US', 'limit': 100}
|
|
@@ -109,16 +109,16 @@ def get_playlist_tracks(playlist_url):
|
|
| 109 |
all_tracks.extend([item['track'] for item in data.get('items', []) if item.get('track')])
|
| 110 |
url = data.get('next')
|
| 111 |
|
| 112 |
-
print(f"✅
|
| 113 |
return all_tracks
|
| 114 |
|
| 115 |
def get_album_tracks(album_url):
|
| 116 |
album_id = extract_spotify_id(album_url, "album")
|
| 117 |
if not album_id or len(album_id) != 22:
|
| 118 |
-
print(f"❌
|
| 119 |
return []
|
| 120 |
|
| 121 |
-
print(f"💿
|
| 122 |
all_tracks = []
|
| 123 |
url = f'https://api.spotify.com/v1/albums/{album_id}/tracks'
|
| 124 |
params = {'market': 'US', 'limit': 50}
|
|
@@ -153,7 +153,7 @@ def process_tracks(urls):
|
|
| 153 |
if track_data:
|
| 154 |
all_tracks.append(track_data)
|
| 155 |
except Exception as e:
|
| 156 |
-
print(f"⚠️ Error
|
| 157 |
|
| 158 |
return all_tracks
|
| 159 |
|
|
@@ -181,9 +181,9 @@ def create_final_dataframe(tracks, include_all_info=True):
|
|
| 181 |
duration_min = f"{track['duration_ms']//60000}:{str(track['duration_ms']%60000//1000).zfill(2)}"
|
| 182 |
|
| 183 |
track_data = {
|
| 184 |
-
'Artist': main_artist.get('name', '
|
| 185 |
-
'Title': track.get('name', '
|
| 186 |
-
'Album': track.get('album', {}).get('name', '
|
| 187 |
'ISRC': track.get('external_ids', {}).get('isrc', 'Not available'),
|
| 188 |
'Popularity': track.get('popularity', 0),
|
| 189 |
'Duration': duration_min,
|
|
@@ -205,7 +205,7 @@ def create_final_dataframe(tracks, include_all_info=True):
|
|
| 205 |
|
| 206 |
df = pd.DataFrame(processed)
|
| 207 |
|
| 208 |
-
#
|
| 209 |
df['dup_key'] = df.apply(
|
| 210 |
lambda x: x['ISRC'] if x['ISRC'] != 'Not available' else f"{x['Artist']}|{x['Title']}",
|
| 211 |
axis=1
|
|
@@ -215,19 +215,19 @@ def create_final_dataframe(tracks, include_all_info=True):
|
|
| 215 |
return df
|
| 216 |
|
| 217 |
def main_interface(project_name, spotify_urls, include_all_info=True):
|
| 218 |
-
#
|
| 219 |
urls = [url.strip() for url in spotify_urls.replace('\n', ',').split(',') if url.strip()]
|
| 220 |
|
| 221 |
if not urls:
|
| 222 |
-
return pd.DataFrame({"Error": ["❌
|
| 223 |
|
| 224 |
-
print("⏳
|
| 225 |
tracks = process_tracks(urls)
|
| 226 |
|
| 227 |
if not tracks:
|
| 228 |
-
return pd.DataFrame({"Error": ["❌ No
|
| 229 |
|
| 230 |
-
print("📊
|
| 231 |
df = create_final_dataframe(tracks, include_all_info)
|
| 232 |
|
| 233 |
with NamedTemporaryFile(delete=False, suffix='.xlsx') as tmp:
|
|
@@ -235,30 +235,30 @@ def main_interface(project_name, spotify_urls, include_all_info=True):
|
|
| 235 |
final_filename = f"{project_name or 'spotify_tracks'}.xlsx"
|
| 236 |
shutil.move(tmp.name, final_filename)
|
| 237 |
|
| 238 |
-
print("✅
|
| 239 |
return df, final_filename
|
| 240 |
|
| 241 |
-
#
|
| 242 |
iface = gr.Interface(
|
| 243 |
fn=main_interface,
|
| 244 |
inputs=[
|
| 245 |
-
gr.Textbox(label="
|
| 246 |
gr.Textbox(
|
| 247 |
-
label="
|
| 248 |
-
placeholder="
|
| 249 |
lines=5
|
| 250 |
),
|
| 251 |
-
gr.Checkbox(label="
|
| 252 |
],
|
| 253 |
outputs=[
|
| 254 |
-
gr.Dataframe(label="
|
| 255 |
-
gr.File(label="
|
| 256 |
],
|
| 257 |
title="🎵 Spotify Track Collector PRO",
|
| 258 |
-
description="
|
| 259 |
examples=[
|
| 260 |
-
["
|
| 261 |
-
["
|
| 262 |
],
|
| 263 |
allow_flagging="never",
|
| 264 |
theme=gr.themes.Soft()
|
|
|
|
| 11 |
client_secrets = os.getenv("SPOTIFY_CLIENT_SECRETS", "").split(',')
|
| 12 |
|
| 13 |
if not client_ids or not client_secrets:
|
| 14 |
+
raise ValueError("❌ Missing environment variables SPOTIFY_CLIENT_IDS or SPOTIFY_CLIENT_SECRETS")
|
| 15 |
|
| 16 |
# Gestión de Rotación de Clientes
|
| 17 |
current_client_index = 0
|
|
|
|
| 22 |
global current_client_index, request_counter
|
| 23 |
current_client_index = (current_client_index + 1) % len(client_ids)
|
| 24 |
request_counter = 0
|
| 25 |
+
print(f"🔄 Rotating to Spotify client #{current_client_index + 1}")
|
| 26 |
|
| 27 |
def get_spotify_token():
|
| 28 |
global current_client_index
|
|
|
|
| 40 |
if response.status_code == 200:
|
| 41 |
return response.json().get('access_token')
|
| 42 |
|
| 43 |
+
print(f"❌ Error with client {current_client_index}: {response.text}")
|
| 44 |
rotate_client()
|
| 45 |
|
| 46 |
+
raise Exception("🚨 All clients failed to obtain token")
|
| 47 |
|
| 48 |
def make_spotify_request(url, params=None):
|
| 49 |
global request_counter
|
|
|
|
| 55 |
request_counter += 1
|
| 56 |
|
| 57 |
if response.status_code == 429:
|
| 58 |
+
print(f"⚠️ Rate limit reached. Client: {current_client_index}")
|
| 59 |
rotate_client()
|
| 60 |
+
time.sleep(int(response.headers.get('Retry-After', 10))) # Minimum wait of 10 seconds
|
| 61 |
continue
|
| 62 |
|
| 63 |
if response.status_code == 200:
|
|
|
|
| 71 |
return None
|
| 72 |
|
| 73 |
def extract_spotify_id(url, resource_type):
|
| 74 |
+
"""Extracts ID from different Spotify URL formats"""
|
| 75 |
clean_url = url.strip().split('?')[0].replace("https://open.spotify.com/", "")
|
| 76 |
|
| 77 |
+
# Handle URLs with /intl-xx/
|
| 78 |
if "/intl-" in clean_url:
|
| 79 |
parts = clean_url.split("/")
|
| 80 |
return parts[-1]
|
|
|
|
| 93 |
def get_playlist_tracks(playlist_url):
|
| 94 |
playlist_id = extract_spotify_id(playlist_url, "playlist")
|
| 95 |
if not playlist_id or len(playlist_id) != 22:
|
| 96 |
+
print(f"❌ Invalid playlist ID: {playlist_id}")
|
| 97 |
return []
|
| 98 |
|
| 99 |
+
print(f"🎧 Getting playlist: {playlist_id}")
|
| 100 |
all_tracks = []
|
| 101 |
url = f'https://api.spotify.com/v1/playlists/{playlist_id}/tracks'
|
| 102 |
params = {'market': 'US', 'limit': 100}
|
|
|
|
| 109 |
all_tracks.extend([item['track'] for item in data.get('items', []) if item.get('track')])
|
| 110 |
url = data.get('next')
|
| 111 |
|
| 112 |
+
print(f"✅ Found {len(all_tracks)} tracks")
|
| 113 |
return all_tracks
|
| 114 |
|
| 115 |
def get_album_tracks(album_url):
|
| 116 |
album_id = extract_spotify_id(album_url, "album")
|
| 117 |
if not album_id or len(album_id) != 22:
|
| 118 |
+
print(f"❌ Invalid album ID: {album_id}")
|
| 119 |
return []
|
| 120 |
|
| 121 |
+
print(f"💿 Getting album: {album_id}")
|
| 122 |
all_tracks = []
|
| 123 |
url = f'https://api.spotify.com/v1/albums/{album_id}/tracks'
|
| 124 |
params = {'market': 'US', 'limit': 50}
|
|
|
|
| 153 |
if track_data:
|
| 154 |
all_tracks.append(track_data)
|
| 155 |
except Exception as e:
|
| 156 |
+
print(f"⚠️ Error processing {url}: {str(e)}")
|
| 157 |
|
| 158 |
return all_tracks
|
| 159 |
|
|
|
|
| 181 |
duration_min = f"{track['duration_ms']//60000}:{str(track['duration_ms']%60000//1000).zfill(2)}"
|
| 182 |
|
| 183 |
track_data = {
|
| 184 |
+
'Artist': main_artist.get('name', 'Unknown'),
|
| 185 |
+
'Title': track.get('name', 'No title'),
|
| 186 |
+
'Album': track.get('album', {}).get('name', 'No album'),
|
| 187 |
'ISRC': track.get('external_ids', {}).get('isrc', 'Not available'),
|
| 188 |
'Popularity': track.get('popularity', 0),
|
| 189 |
'Duration': duration_min,
|
|
|
|
| 205 |
|
| 206 |
df = pd.DataFrame(processed)
|
| 207 |
|
| 208 |
+
# Remove duplicates using ISRC or artist-title combination
|
| 209 |
df['dup_key'] = df.apply(
|
| 210 |
lambda x: x['ISRC'] if x['ISRC'] != 'Not available' else f"{x['Artist']}|{x['Title']}",
|
| 211 |
axis=1
|
|
|
|
| 215 |
return df
|
| 216 |
|
| 217 |
def main_interface(project_name, spotify_urls, include_all_info=True):
|
| 218 |
+
# Convert URLs separated by commas or line breaks
|
| 219 |
urls = [url.strip() for url in spotify_urls.replace('\n', ',').split(',') if url.strip()]
|
| 220 |
|
| 221 |
if not urls:
|
| 222 |
+
return pd.DataFrame({"Error": ["❌ Enter at least one valid URL"]}), None
|
| 223 |
|
| 224 |
+
print("⏳ Processing URLs...")
|
| 225 |
tracks = process_tracks(urls)
|
| 226 |
|
| 227 |
if not tracks:
|
| 228 |
+
return pd.DataFrame({"Error": ["❌ No tracks were found"]}), None
|
| 229 |
|
| 230 |
+
print("📊 Creating dataframe...")
|
| 231 |
df = create_final_dataframe(tracks, include_all_info)
|
| 232 |
|
| 233 |
with NamedTemporaryFile(delete=False, suffix='.xlsx') as tmp:
|
|
|
|
| 235 |
final_filename = f"{project_name or 'spotify_tracks'}.xlsx"
|
| 236 |
shutil.move(tmp.name, final_filename)
|
| 237 |
|
| 238 |
+
print("✅ Process completed")
|
| 239 |
return df, final_filename
|
| 240 |
|
| 241 |
+
# Configuration of the Interface
|
| 242 |
iface = gr.Interface(
|
| 243 |
fn=main_interface,
|
| 244 |
inputs=[
|
| 245 |
+
gr.Textbox(label="Project Name", placeholder="My Music Collection"),
|
| 246 |
gr.Textbox(
|
| 247 |
+
label="Spotify URLs",
|
| 248 |
+
placeholder="Paste URLs separated by commas or line breaks (playlists, albums, songs)...",
|
| 249 |
lines=5
|
| 250 |
),
|
| 251 |
+
gr.Checkbox(label="Include all information", value=True)
|
| 252 |
],
|
| 253 |
outputs=[
|
| 254 |
+
gr.Dataframe(label="Results"),
|
| 255 |
+
gr.File(label="Download Excel")
|
| 256 |
],
|
| 257 |
title="🎵 Spotify Track Collector PRO",
|
| 258 |
+
description="Extract detailed information from multiple Spotify URLs and generate a unified Excel file",
|
| 259 |
examples=[
|
| 260 |
+
["Classic Rock", "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M, https://open.spotify.com/album/1R5BORZZxNUg8QMgbqt0nd", True],
|
| 261 |
+
["Current Hits", "https://open.spotify.com/playlist/37i9dQZEtDXc8QFRQ4qegR", False]
|
| 262 |
],
|
| 263 |
allow_flagging="never",
|
| 264 |
theme=gr.themes.Soft()
|