""" scripts/fetch_spotify_verified_faces.py Fetches the 100% authentic, verified label profile portrait for every artist in the 500 cohort directly from Spotify's Web API. """ import os import sys import base64 from pathlib import Path import pandas as pd import requests from dotenv import load_dotenv ROOT_DIR = Path(__file__).resolve().parent.parent if str(ROOT_DIR) not in sys.path: sys.path.insert(0, str(ROOT_DIR)) load_dotenv(ROOT_DIR / ".env") from core.database import get_connection SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID") SPOTIFY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET") def get_spotify_token(): if not SPOTIFY_CLIENT_ID or not SPOTIFY_CLIENT_SECRET: print("ERROR: SPOTIFY_CLIENT_ID or SPOTIFY_CLIENT_SECRET is missing from .env") sys.exit(1) auth_str = f"{SPOTIFY_CLIENT_ID}:{SPOTIFY_CLIENT_SECRET}" b64_auth = base64.b64encode(auth_str.encode()).decode() url = "https://accounts.spotify.com/api/token" headers = { "Authorization": f"Basic {b64_auth}", "Content-Type": "application/x-www-form-urlencoded" } data = {"grant_type": "client_credentials"} r = requests.post(url, headers=headers, data=data, timeout=10) if r.status_code == 200: return r.json().get("access_token") else: print(f"Failed to obtain Spotify token: {r.status_code} - {r.text}") sys.exit(1) def chunk_list(lst, size=50): for i in range(0, len(lst), size): yield lst[i:i + size] def main(): print("=" * 65) print("HIFFI INTELLIGENCE: SPOTIFY OFFICIAL ARTIST PORTRAIT ENGINE") print("=" * 65) token = get_spotify_token() headers = {"Authorization": f"Bearer {token}"} csv_path = ROOT_DIR / "data" / "hiffi_multiplatform_500.csv" if not csv_path.exists(): csv_path = ROOT_DIR / "cleaned_artists_inventory.csv" df_csv = pd.read_csv(csv_path) name_col = "name" if "name" in df_csv.columns else "artist_name" cohort = [str(n).strip() for n in df_csv[name_col].dropna()] conn = get_connection() cur = conn.cursor() placeholders = ",".join(["?"] * len(cohort)) query = f""" SELECT artist_id, artist_name, spotify_url FROM artists WHERE artist_name IN ({placeholders}) """ cur.execute(query, cohort) rows = cur.fetchall() id_to_artist = {} direct_search_artists = [] for artist_id, name, sp_url in rows: sp_url = sp_url or "" if "/artist/" in sp_url: sp_id = sp_url.split("/artist/")[-1].split("?")[0].strip("/") id_to_artist[sp_id] = (artist_id, name) else: direct_search_artists.append((artist_id, name)) print(f"Direct Spotify Artist IDs mapped: {len(id_to_artist)}") print(f"Artists needing search lookup : {len(direct_search_artists)}\n") updated = 0 # 1. Batch fetch official portraits via Spotify's /v1/artists endpoint (50 at a time) sp_ids = list(id_to_artist.keys()) for batch_num, batch in enumerate(chunk_list(sp_ids, 50), start=1): ids_str = ",".join(batch) url = f"https://api.spotify.com/v1/artists?ids={ids_str}" try: r = requests.get(url, headers=headers, timeout=12) if r.status_code == 200: artists_data = r.json().get("artists", []) for a in artists_data: if not a: continue sp_id = a.get("id") images = a.get("images", []) if images and sp_id in id_to_artist: # Grab the highest resolution portrait img_url = images[0].get("url") art_id, art_name = id_to_artist[sp_id] cur.execute(""" UPDATE artists SET image_url = ?, image_verified_flag = 1 WHERE artist_id = ? """, (img_url, art_id)) updated += 1 conn.commit() print(f"Batch {batch_num:>2}: Updated {len(artists_data)} verified portraits.") else: print(f"Batch {batch_num} error {r.status_code}: {r.text[:80]}") except Exception as e: print(f"Batch {batch_num} exception: {e}") # 2. Resolve the remaining search artists for idx, (artist_id, name) in enumerate(direct_search_artists, start=1): url = f"https://api.spotify.com/v1/search?q={name}&type=artist&limit=1" try: r = requests.get(url, headers=headers, timeout=8) if r.status_code == 200: items = r.json().get("artists", {}).get("items", []) if items: images = items[0].get("images", []) if images: img_url = images[0].get("url") sp_canonical = items[0].get("external_urls", {}).get("spotify") cur.execute(""" UPDATE artists SET image_url = ?, spotify_url = COALESCE(NULLIF(spotify_url, ''), ?), image_verified_flag = 1 WHERE artist_id = ? """, (img_url, sp_canonical, artist_id)) updated += 1 print(f"[Search {idx:>2}/{len(direct_search_artists)}] Verified portrait for {name}") conn.commit() except Exception as e: print(f"Error on {name}: {e}") conn.close() print("\n" + "=" * 65) print(f"Successfully populated authentic Spotify portraits for {updated} artists.") print("=" * 65) if __name__ == "__main__": main()