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("Faltan variables de entorno SPOTIFY_CLIENT_IDS o SPOTIFY_CLIENT_SECRETS")
|
| 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
|
|
@@ -36,6 +36,7 @@ def get_spotify_token():
|
|
| 36 |
data={'grant_type': 'client_credentials'},
|
| 37 |
auth=(client_id, client_secret)
|
| 38 |
)
|
|
|
|
| 39 |
if response.status_code == 200:
|
| 40 |
return response.json().get('access_token')
|
| 41 |
|
|
@@ -44,13 +45,12 @@ def get_spotify_token():
|
|
| 44 |
|
| 45 |
raise Exception("🚨 Todos los clientes fallaron al obtener token")
|
| 46 |
|
| 47 |
-
# Funciones Principales
|
| 48 |
def make_spotify_request(url, params=None):
|
| 49 |
global request_counter
|
| 50 |
token = get_spotify_token()
|
| 51 |
headers = {'Authorization': f'Bearer {token}'}
|
| 52 |
|
| 53 |
-
for _ in range(3): #
|
| 54 |
response = requests.get(url, headers=headers, params=params)
|
| 55 |
request_counter += 1
|
| 56 |
|
|
@@ -70,30 +70,39 @@ def make_spotify_request(url, params=None):
|
|
| 70 |
|
| 71 |
return None
|
| 72 |
|
| 73 |
-
def extract_spotify_id(url,
|
| 74 |
"""Extrae ID de diferentes formatos de URLs de Spotify"""
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
| 79 |
|
| 80 |
-
|
| 81 |
-
parts = [p for p in url.split("/") if p]
|
| 82 |
-
for i, part in enumerate(parts):
|
| 83 |
-
if part == type_keyword and i < len(parts)-1:
|
| 84 |
-
return parts[i+1].split("?")[0]
|
| 85 |
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
def get_playlist_tracks(playlist_url):
|
| 89 |
playlist_id = extract_spotify_id(playlist_url, "playlist")
|
| 90 |
-
|
|
|
|
|
|
|
| 91 |
|
|
|
|
| 92 |
all_tracks = []
|
| 93 |
url = f'https://api.spotify.com/v1/playlists/{playlist_id}/tracks'
|
|
|
|
| 94 |
|
| 95 |
while url:
|
| 96 |
-
data = make_spotify_request(url)
|
| 97 |
if not data:
|
| 98 |
break
|
| 99 |
|
|
@@ -105,30 +114,23 @@ def get_playlist_tracks(playlist_url):
|
|
| 105 |
|
| 106 |
def get_album_tracks(album_url):
|
| 107 |
album_id = extract_spotify_id(album_url, "album")
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
# Obtener información básica del álbum
|
| 111 |
-
album_data = make_spotify_request(f'https://api.spotify.com/v1/albums/{album_id}')
|
| 112 |
-
if not album_data:
|
| 113 |
return []
|
| 114 |
|
|
|
|
| 115 |
all_tracks = []
|
| 116 |
url = f'https://api.spotify.com/v1/albums/{album_id}/tracks'
|
|
|
|
| 117 |
|
| 118 |
while url:
|
| 119 |
-
data = make_spotify_request(url)
|
| 120 |
if not data:
|
| 121 |
break
|
| 122 |
|
| 123 |
-
# Obtener detalles completos de cada track
|
| 124 |
for item in data.get('items', []):
|
| 125 |
track_data = make_spotify_request(f"https://api.spotify.com/v1/tracks/{item['id']}")
|
| 126 |
if track_data:
|
| 127 |
-
track_data['album'] = { # Añadir info del álbum
|
| 128 |
-
'name': album_data.get('name'),
|
| 129 |
-
'release_date': album_data.get('release_date'),
|
| 130 |
-
'id': album_id
|
| 131 |
-
}
|
| 132 |
all_tracks.append(track_data)
|
| 133 |
|
| 134 |
url = data.get('next')
|
|
@@ -155,7 +157,6 @@ def process_tracks(urls):
|
|
| 155 |
|
| 156 |
return all_tracks
|
| 157 |
|
| 158 |
-
# Procesamiento de Datos
|
| 159 |
def create_final_dataframe(tracks, include_all_info=True):
|
| 160 |
artist_cache = {}
|
| 161 |
|
|
@@ -213,38 +214,38 @@ def create_final_dataframe(tracks, include_all_info=True):
|
|
| 213 |
|
| 214 |
return df
|
| 215 |
|
| 216 |
-
# Interfaz Gradio
|
| 217 |
def main_interface(project_name, spotify_urls, include_all_info=True):
|
| 218 |
-
|
|
|
|
| 219 |
|
| 220 |
if not urls:
|
| 221 |
-
return pd.DataFrame({"Error": ["Ingresa al menos una URL válida"]}), None
|
| 222 |
|
| 223 |
print("⏳ Procesando URLs...")
|
| 224 |
tracks = process_tracks(urls)
|
| 225 |
|
| 226 |
if not tracks:
|
| 227 |
-
return pd.DataFrame({"Error": ["No se encontraron tracks"]}), None
|
| 228 |
|
| 229 |
print("📊 Creando dataframe...")
|
| 230 |
df = create_final_dataframe(tracks, include_all_info)
|
| 231 |
|
| 232 |
with NamedTemporaryFile(delete=False, suffix='.xlsx') as tmp:
|
| 233 |
df.to_excel(tmp.name, index=False)
|
| 234 |
-
final_filename = f"{project_name
|
| 235 |
shutil.move(tmp.name, final_filename)
|
| 236 |
|
| 237 |
print("✅ Proceso completado")
|
| 238 |
return df, final_filename
|
| 239 |
|
| 240 |
-
# Configuración de la
|
| 241 |
iface = gr.Interface(
|
| 242 |
fn=main_interface,
|
| 243 |
inputs=[
|
| 244 |
gr.Textbox(label="Nombre del Proyecto", placeholder="Mi Colección Musical"),
|
| 245 |
gr.Textbox(
|
| 246 |
-
label="URLs de Spotify
|
| 247 |
-
placeholder="Pega
|
| 248 |
lines=5
|
| 249 |
),
|
| 250 |
gr.Checkbox(label="Incluir toda la información", value=True)
|
|
@@ -253,11 +254,11 @@ iface = gr.Interface(
|
|
| 253 |
gr.Dataframe(label="Resultados"),
|
| 254 |
gr.File(label="Descargar Excel")
|
| 255 |
],
|
| 256 |
-
title="
|
| 257 |
-
description="Extrae información detallada de múltiples URLs de Spotify
|
| 258 |
examples=[
|
| 259 |
-
["
|
| 260 |
-
["
|
| 261 |
],
|
| 262 |
allow_flagging="never",
|
| 263 |
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("❌ Faltan variables de entorno SPOTIFY_CLIENT_IDS o 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"🔄 Rotando a cliente Spotify #{current_client_index + 1}")
|
| 26 |
|
| 27 |
def get_spotify_token():
|
| 28 |
global current_client_index
|
|
|
|
| 36 |
data={'grant_type': 'client_credentials'},
|
| 37 |
auth=(client_id, client_secret)
|
| 38 |
)
|
| 39 |
+
|
| 40 |
if response.status_code == 200:
|
| 41 |
return response.json().get('access_token')
|
| 42 |
|
|
|
|
| 45 |
|
| 46 |
raise Exception("🚨 Todos los clientes fallaron al obtener token")
|
| 47 |
|
|
|
|
| 48 |
def make_spotify_request(url, params=None):
|
| 49 |
global request_counter
|
| 50 |
token = get_spotify_token()
|
| 51 |
headers = {'Authorization': f'Bearer {token}'}
|
| 52 |
|
| 53 |
+
for _ in range(3): # 3 intentos por cliente
|
| 54 |
response = requests.get(url, headers=headers, params=params)
|
| 55 |
request_counter += 1
|
| 56 |
|
|
|
|
| 70 |
|
| 71 |
return None
|
| 72 |
|
| 73 |
+
def extract_spotify_id(url, resource_type):
|
| 74 |
"""Extrae ID de diferentes formatos de URLs de Spotify"""
|
| 75 |
+
clean_url = url.strip().split('?')[0].replace("https://open.spotify.com/", "")
|
| 76 |
+
|
| 77 |
+
# Manejar URLs con /intl-xx/
|
| 78 |
+
if "/intl-" in clean_url:
|
| 79 |
+
parts = clean_url.split("/")
|
| 80 |
+
return parts[-1]
|
| 81 |
|
| 82 |
+
parts = clean_url.split("/")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
+
try:
|
| 85 |
+
index = parts.index(resource_type)
|
| 86 |
+
return parts[index + 1] if index + 1 < len(parts) else None
|
| 87 |
+
except ValueError:
|
| 88 |
+
for part in parts:
|
| 89 |
+
if len(part) == 22 and resource_type in url:
|
| 90 |
+
return part
|
| 91 |
+
return None
|
| 92 |
|
| 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"❌ ID de playlist inválido: {playlist_id}")
|
| 97 |
+
return []
|
| 98 |
|
| 99 |
+
print(f"🎧 Obteniendo playlist: {playlist_id}")
|
| 100 |
all_tracks = []
|
| 101 |
url = f'https://api.spotify.com/v1/playlists/{playlist_id}/tracks'
|
| 102 |
+
params = {'market': 'US', 'limit': 100}
|
| 103 |
|
| 104 |
while url:
|
| 105 |
+
data = make_spotify_request(url, params)
|
| 106 |
if not data:
|
| 107 |
break
|
| 108 |
|
|
|
|
| 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"❌ ID de álbum inválido: {album_id}")
|
|
|
|
|
|
|
|
|
|
| 119 |
return []
|
| 120 |
|
| 121 |
+
print(f"💿 Obteniendo álbum: {album_id}")
|
| 122 |
all_tracks = []
|
| 123 |
url = f'https://api.spotify.com/v1/albums/{album_id}/tracks'
|
| 124 |
+
params = {'market': 'US', 'limit': 50}
|
| 125 |
|
| 126 |
while url:
|
| 127 |
+
data = make_spotify_request(url, params)
|
| 128 |
if not data:
|
| 129 |
break
|
| 130 |
|
|
|
|
| 131 |
for item in data.get('items', []):
|
| 132 |
track_data = make_spotify_request(f"https://api.spotify.com/v1/tracks/{item['id']}")
|
| 133 |
if track_data:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
all_tracks.append(track_data)
|
| 135 |
|
| 136 |
url = data.get('next')
|
|
|
|
| 157 |
|
| 158 |
return all_tracks
|
| 159 |
|
|
|
|
| 160 |
def create_final_dataframe(tracks, include_all_info=True):
|
| 161 |
artist_cache = {}
|
| 162 |
|
|
|
|
| 214 |
|
| 215 |
return df
|
| 216 |
|
|
|
|
| 217 |
def main_interface(project_name, spotify_urls, include_all_info=True):
|
| 218 |
+
# Convertir URLs separadas por comas o saltos de línea
|
| 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": ["❌ Ingresa al menos una URL válida"]}), None
|
| 223 |
|
| 224 |
print("⏳ Procesando URLs...")
|
| 225 |
tracks = process_tracks(urls)
|
| 226 |
|
| 227 |
if not tracks:
|
| 228 |
+
return pd.DataFrame({"Error": ["❌ No se encontraron tracks"]}), None
|
| 229 |
|
| 230 |
print("📊 Creando dataframe...")
|
| 231 |
df = create_final_dataframe(tracks, include_all_info)
|
| 232 |
|
| 233 |
with NamedTemporaryFile(delete=False, suffix='.xlsx') as tmp:
|
| 234 |
df.to_excel(tmp.name, index=False)
|
| 235 |
+
final_filename = f"{project_name or 'spotify_tracks'}.xlsx"
|
| 236 |
shutil.move(tmp.name, final_filename)
|
| 237 |
|
| 238 |
print("✅ Proceso completado")
|
| 239 |
return df, final_filename
|
| 240 |
|
| 241 |
+
# Configuración de la Interfaz
|
| 242 |
iface = gr.Interface(
|
| 243 |
fn=main_interface,
|
| 244 |
inputs=[
|
| 245 |
gr.Textbox(label="Nombre del Proyecto", placeholder="Mi Colección Musical"),
|
| 246 |
gr.Textbox(
|
| 247 |
+
label="URLs de Spotify",
|
| 248 |
+
placeholder="Pega URLs separadas por comas o saltos de línea (playlists, álbumes, canciones)...",
|
| 249 |
lines=5
|
| 250 |
),
|
| 251 |
gr.Checkbox(label="Incluir toda la información", value=True)
|
|
|
|
| 254 |
gr.Dataframe(label="Resultados"),
|
| 255 |
gr.File(label="Descargar Excel")
|
| 256 |
],
|
| 257 |
+
title="🎵 Spotify Track Collector PRO",
|
| 258 |
+
description="Extrae información detallada de múltiples URLs de Spotify y genera un Excel unificado",
|
| 259 |
examples=[
|
| 260 |
+
["Rock Clásico", "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M, https://open.spotify.com/album/1R5BORZZxNUg8QMgbqt0nd", True],
|
| 261 |
+
["Éxitos Actuales", "https://open.spotify.com/playlist/37i9dQZEtDXc8QFRQ4qegR", False]
|
| 262 |
],
|
| 263 |
allow_flagging="never",
|
| 264 |
theme=gr.themes.Soft()
|