Files
hiffi-artist-intelligence/scripts/fetch_spotify_verified_faces.py
Srilekha 78a46a8dbe
HIFFI Multi-Platform CI / test (3.12) (push) Successful in 50s
HIFFI Multi-Platform CI / test (3.11) (push) Successful in 50s
feat: finalize root api.py with X-Auth-Secret auth security and clean pure sales cohort
2026-11-04 16:49:39 +05:30

157 lines
5.7 KiB
Python

"""
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()