""" scripts/resolve_curated_65_faces.py Dynamically queries Wikipedia PageImages API and Deezer fallback to fetch verified, authentic face portraits for all 67 flagged artists. Complies with Wikimedia bot policy to avoid 404, 403, and 429 errors. """ import sqlite3 import requests import re import time from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent DB_PATH = BASE_DIR / "data" / "hiffi_catalog.db" AVATAR_DIR = BASE_DIR / "data" / "artist_avatars" AVATAR_DIR.mkdir(parents=True, exist_ok=True) # Wikimedia-compliant User-Agent header (required to prevent 403/429 blocks) HEADERS = { "User-Agent": "HIFFIArtistIntelligence/1.0 (https://hiffi.ai; team@hiffi.ai)", "Accept": "application/json,image/*" } # The target cohort artists needing verified faces TARGET_ARTISTS = [ "Elucid", "billy woods", "Jme", "Ashnikko", "BabySmoove", "Azealia Banks", "AZ", "Autumn!", "BabyTron", "BLP Kosher", "Bladee", "BONES", "Bow Wow", "Buddy", "Certified Trapper", "Che Noir", "Chip", "Coco Jones", "Cozz", "Danny Brown", "Cypress Hill", "Dax", "De La Soul", "Destroy Lonely", "DJ Paul", "Dirtbike LB", "Dillon Cooper", "Dr. Dre", "Drake", "Drodi", "Ecco2k", "Earl Sweatshirt", "EARTHGANG", "Gunna", "Hodgy Beats", "Hak", "Ice Spice", "Jeezy", "Kano", "Kankan", "Kash Doll", "Killah Priest", "Kool A.D.", "LiBand", "Latto", "Le$", "Little Big", "Lil Wayne", "Lord Apex", "NBA YoungBoy", "NoCap", "Omen", "Ratking", "Rick Ross", "Rome Streetz", "Saba", "Spillage Village", "Takeoff", "That Mexican OT", "Thaiboy Digital", "Three 6 Mafia", "The Underachievers", "TiaCorine", "The Notorious B.I.G.", "Token", "Westside Gunn", "YG" ] def clean_file_slug(name: str) -> str: cleaned = re.sub(r'[^a-zA-Z0-9_-]', '_', name.strip().lower()) return re.sub(r'_+', '_', cleaned).strip('_') def fetch_wikipedia_photo(artist_name: str) -> str: """Queries MediaWiki Action API for the article lead portrait.""" clean_name = re.sub(r'[\$@#!\?]', '', artist_name).strip() search_queries = [ f"{clean_name} rapper", f"{clean_name} musician", clean_name ] for q in search_queries: api_url = ( f"https://en.wikipedia.org/w/api.php?action=query&generator=search" f"&gsrsearch={requests.utils.quote(q)}&gsrlimit=1" f"&prop=pageimages&pithumbsize=500&format=json" ) try: res = requests.get(api_url, headers=HEADERS, timeout=8).json() pages = res.get("query", {}).get("pages", {}) for pid, pdata in pages.items(): if "thumbnail" in pdata and pdata["thumbnail"].get("source"): img_url = pdata["thumbnail"]["source"] # Ignore common generic placeholder icons if not any(bad in img_url.lower() for bad in ["flag", "logo", "icon", "stub", "symbol"]): return img_url except Exception: continue return "" def fetch_deezer_photo(artist_name: str) -> str: """Fallback: Queries Deezer API for official artist picture.""" clean_name = re.sub(r'[\$@#!\?]', '', artist_name).strip() api_url = f"https://api.deezer.com/search/artist?q={requests.utils.quote(clean_name)}&limit=3" try: res = requests.get(api_url, headers=HEADERS, timeout=8).json() if "data" in res and len(res["data"]) > 0: for item in res["data"]: pic = item.get("picture_big") or item.get("picture_medium") if pic and "artist//500x500" not in pic: return pic except Exception: pass return "" def main(): if not DB_PATH.exists(): print(f"[-] Database not found at {DB_PATH}") return conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() print("\n" + "="*60) print(f" RESOLVING REAL FACE PORTRAITS FOR {len(TARGET_ARTISTS)} ARTISTS") print("="*60 + "\n") saved_count = 0 for idx, name in enumerate(TARGET_ARTISTS, 1): cursor.execute("SELECT artist_id FROM artists WHERE artist_name = ? COLLATE NOCASE", (name,)) row = cursor.fetchone() artist_id = str(row[0]) if row else "0" slug = clean_file_slug(name) target_file = AVATAR_DIR / f"{slug}_{artist_id}.jpg" print(f"[{idx:02d}/{len(TARGET_ARTISTS)}] Finding real face: {name}...", end=" ", flush=True) # 1. Look up Wikipedia editorial photo photo_url = fetch_wikipedia_photo(name) time.sleep(0.3) # Gentle delay to avoid 429 rate-limiting # 2. Look up Deezer if Wikipedia had no photo if not photo_url: photo_url = fetch_deezer_photo(name) time.sleep(0.2) if photo_url: try: img_res = requests.get(photo_url, headers=HEADERS, timeout=12) if img_res.status_code == 200 and len(img_res.content) > 2500: with open(target_file, "wb") as f: f.write(img_res.content) size_kb = len(img_res.content) / 1024 print(f"SAVED -> {target_file.name} ({size_kb:.1f} KB)") cursor.execute( "UPDATE artists SET image_url = ?, image_verified_flag = 1 WHERE artist_id = ?", (photo_url, artist_id) ) conn.commit() saved_count += 1 else: print(f"HTTP {img_res.status_code}") except Exception as e: print(f"DOWNLOAD ERROR: {e}") else: print("NO FACE FOUND") conn.close() print("\n" + "="*60) print(f"[+] Complete! Successfully resolved {saved_count}/{len(TARGET_ARTISTS)} faces.") print("="*60 + "\n") if __name__ == "__main__": main()