Mthrfkr commited on
Commit
a7296a8
Β·
verified Β·
1 Parent(s): 74efcb1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +135 -41
app.py CHANGED
@@ -1,54 +1,148 @@
1
- import os, gradio as gr, requests
 
 
 
 
 
2
 
3
- CID = os.getenv("SPOTIFY_CLIENT_IDS", "").split(',')[0]
4
- CSEC = os.getenv("SPOTIFY_CLIENT_SECRETS", "").split(',')[0]
 
 
 
 
5
 
6
- def fetch_public_playlist(url):
7
- # 1) Auth
8
- if not (CID and CSEC):
9
- return "❌ Faltan creds SPOTIFY_CLIENT_IDS o SPOTIFY_CLIENT_SECRETS"
10
- tr = requests.post(
 
11
  "https://accounts.spotify.com/api/token",
12
  data={"grant_type":"client_credentials"},
13
- auth=(CID, CSEC)
14
  )
15
- if tr.status_code != 200:
16
- return f"❌ Token error {tr.status_code}"
17
- token = tr.json().get("access_token")
18
-
19
- # 2) Extraemos ID
20
- pid = url.strip().rstrip('/').split('/')[-1].split('?')[0]
21
- if len(pid) != 22:
22
- return f"❌ ID invÑlido: '{pid}' (len={len(pid)})"
23
-
24
- # 3) Metadata
25
- meta = requests.get(
 
 
 
 
26
  f"https://api.spotify.com/v1/playlists/{pid}",
27
  headers={"Authorization":f"Bearer {token}"}
28
  )
29
- if meta.status_code != 200:
30
- return f"❌ No pública o no existe: status {meta.status_code}"
31
- data = meta.json()
32
- name = data.get("name","?")
33
- total = data.get("tracks",{}).get("total","?")
34
-
35
- # 4) Tracks (ejemplo, lΓ­mite 5 para demo)
36
- tracks = requests.get(
37
- f"https://api.spotify.com/v1/playlists/{pid}/tracks",
38
- headers={"Authorization":f"Bearer {token}"},
39
- params={"limit":5}
40
- ).json().get("items",[])
41
- titles = [t["track"]["name"] for t in tracks if t.get("track")]
42
-
43
- return f"βœ… β€œ{name}”: {total} tracks\n🎡 Muestra: {titles}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
 
 
 
45
  iface = gr.Interface(
46
- fn=fetch_public_playlist,
47
- inputs=gr.Textbox(label="URL de playlist"),
48
- outputs="text",
49
- title="πŸ”Ž Checa playlist pΓΊblica",
50
- description="Solo funciona con playlists pΓΊblicas"
 
 
 
 
 
 
 
 
 
 
 
 
51
  )
52
 
53
  if __name__=="__main__":
54
- iface.launch()
 
1
+ import os
2
+ import requests
3
+ import pandas as pd
4
+ import gradio as gr
5
+ from tempfile import NamedTemporaryFile
6
+ import shutil
7
 
8
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
9
+ # 1️⃣ Config de Spotify
10
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
11
+ CLIENT_ID = os.getenv("SPOTIFY_CLIENT_IDS", "").split(',')[0]
12
+ CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRETS", "").split(',')[0]
13
+ TOKEN_CACHE = None
14
 
15
+ def get_token():
16
+ """Pide un token Client Credentials y lo cachea."""
17
+ global TOKEN_CACHE
18
+ if TOKEN_CACHE:
19
+ return TOKEN_CACHE
20
+ resp = requests.post(
21
  "https://accounts.spotify.com/api/token",
22
  data={"grant_type":"client_credentials"},
23
+ auth=(CLIENT_ID, CLIENT_SECRET),
24
  )
25
+ resp.raise_for_status()
26
+ TOKEN_CACHE = resp.json()["access_token"]
27
+ return TOKEN_CACHE
28
+
29
+ def extract_pid(url):
30
+ """Saca el ID limpio de la URL."""
31
+ return url.strip().rstrip('/').split('/')[-1].split('?')[0]
32
+
33
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
34
+ # 2️⃣ Funciones core
35
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
36
+ def is_public_playlist(pid):
37
+ """Chequea metadata: devuelve (True, nombre, total) o (False, mensaje, None)."""
38
+ token = get_token()
39
+ r = requests.get(
40
  f"https://api.spotify.com/v1/playlists/{pid}",
41
  headers={"Authorization":f"Bearer {token}"}
42
  )
43
+ if r.status_code == 200:
44
+ j = r.json()
45
+ return True, j["name"], j["tracks"]["total"]
46
+ else:
47
+ return False, f"no pΓΊblica / error {r.status_code}", None
48
+
49
+ def fetch_all_tracks(pid, sample_limit=None):
50
+ """
51
+ Trae todos los tracks de la playlist en una lista de dicts.
52
+ Si sample_limit estΓ‘ definido, sΓ³lo jala esa cantidad (para demo).
53
+ """
54
+ token = get_token()
55
+ url = f"https://api.spotify.com/v1/playlists/{pid}/tracks"
56
+ params = {"limit":100, "market":"US"}
57
+ all_t = []
58
+ while url:
59
+ r = requests.get(url, headers={"Authorization":f"Bearer {token}"}, params=params)
60
+ r.raise_for_status()
61
+ data = r.json()
62
+ items = data.get("items", [])
63
+ for it in items:
64
+ tr = it.get("track")
65
+ if tr:
66
+ all_t.append({
67
+ "Artist": tr["artists"][0]["name"],
68
+ "Title": tr["name"],
69
+ "Album": tr["album"]["name"],
70
+ "Duration_ms": tr["duration_ms"],
71
+ "Popularity": tr["popularity"],
72
+ "Explicit": tr["explicit"],
73
+ "Track ID": tr["id"],
74
+ })
75
+ if sample_limit and len(all_t) >= sample_limit:
76
+ return all_t
77
+ url = data.get("next")
78
+ return all_t
79
+
80
+ def build_dataframe(tracks, include_all):
81
+ """Convierte la lista de tracks en un DataFrame y limpia duplicados."""
82
+ df = pd.DataFrame(tracks)
83
+ # Ejemplo de columnita extra si include_all
84
+ if include_all:
85
+ df["Duration_min"] = df["Duration_ms"].floordiv(60000).astype(str) + ":" + (df["Duration_ms"]%60000//1000).astype(str).str.zfill(2)
86
+ # Quitamos dupes por Track ID
87
+ return df.drop_duplicates("Track ID", ignore_index=True)
88
+
89
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
90
+ # 3️⃣ FunciΓ³n principal
91
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
92
+ def main(project_name, urls_text, include_all):
93
+ urls = [u for u in urls_text.replace("\n",",").split(",") if u.strip()]
94
+ if not urls:
95
+ return pd.DataFrame({"Error":["❌ Pon al menos una URL vÑlida"]}), None
96
+
97
+ resultado = []
98
+ logs = []
99
+ for url in urls:
100
+ pid = extract_pid(url)
101
+ ok, nombre, total = is_public_playlist(pid)
102
+ if not ok:
103
+ logs.append(f"⚠️ {pid}: {nombre}")
104
+ continue
105
+ logs.append(f"βœ… {pid}: β€œ{nombre}” ({total} tracks)")
106
+ # Para demo sample_limit=5, quita sample_limit para todo
107
+ tracks = fetch_all_tracks(pid, sample_limit=None)
108
+ resultado.extend(tracks)
109
+
110
+ if not resultado:
111
+ return pd.DataFrame({"Error":logs}), None
112
+
113
+ df = build_dataframe(resultado, include_all)
114
+ # Generar Excel
115
+ with NamedTemporaryFile(delete=False, suffix=".xlsx") as tmp:
116
+ df.to_excel(tmp.name, index=False)
117
+ fname = f"{project_name or 'spotify_tracks'}.xlsx"
118
+ shutil.move(tmp.name, fname)
119
+
120
+ # Metemos los logs al final del DF (opcional)
121
+ df_logs = pd.DataFrame({"Logs": logs})
122
+ return df, fname
123
 
124
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
125
+ # 4️⃣ Interfaz Gradio
126
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
127
  iface = gr.Interface(
128
+ fn=main,
129
+ inputs=[
130
+ gr.Textbox(label="Nombre del proyecto"),
131
+ gr.Textbox(label="URLs de Spotify (coma o salto de lΓ­nea)", lines=4),
132
+ gr.Checkbox(label="Incluir info extra", value=True),
133
+ ],
134
+ outputs=[
135
+ gr.Dataframe(label="Resultados"),
136
+ gr.File(label="Descargar Excel"),
137
+ ],
138
+ title="🎡 Spotify Track Collector PRO",
139
+ description="SΓ³lo pull playlists pΓΊblicas. Salta las demΓ‘s.",
140
+ examples=[
141
+ ["Mi Playlist Verano", "https://open.spotify.com/playlist/13QBKWGO2VY1pc1RJz91YN", True],
142
+ ],
143
+ allow_flagging="never",
144
+ theme=gr.themes.Soft(),
145
  )
146
 
147
  if __name__=="__main__":
148
+ iface.launch(server_name="0.0.0.0", server_port=7860)