import os from pathlib import Path import sqlite3 import time import requests PROJECT_ROOT = Path(__file__).resolve().parent.parent DB_PATH = PROJECT_ROOT / "data" / "hiffi_catalog.db" # MusicBrainz API requires a clear User-Agent string MB_HEADERS = { "User-Agent": "HIFFIArtistIntelligence/1.0.0 ( contact@hiffi.com )", "Accept": "application/json", } conn = sqlite3.connect(DB_PATH) cur = conn.cursor() cur.execute(""" SELECT artist_id, artist_name, spotify_url FROM artists WHERE spotify_url LIKE '%/search/%' AND image_verified_flag = 1 """) rows = cur.fetchall() print(f"Targeting {len(rows)} artists needing canonical Spotify URLs via MusicBrainz entity resolution...\n") resolved = 0 unresolved = [] for artist_id, name, old_url in rows: clean_name = name.strip() try: # Step 1: Find artist entity by exact name search_url = "https://musicbrainz.org/ws/2/artist" params = {"query": f'artist:"{clean_name}"', "fmt": "json", "limit": 3} resp = requests.get(search_url, headers=MB_HEADERS, params=params, timeout=10) found_spotify_url = None if resp.status_code == 200: artists_data = resp.json().get("artists", []) for mb_artist in artists_data: mb_id = mb_artist.get("id") # Step 2: Fetch url-rels for the candidate artist rel_url = f"https://musicbrainz.org/ws/2/artist/{mb_id}" rel_resp = requests.get(rel_url, headers=MB_HEADERS, params={"inc": "url-rels", "fmt": "json"}, timeout=10) if rel_resp.status_code == 200: relations = rel_resp.json().get("relations", []) for rel in relations: target = rel.get("url", {}).get("resource", "") if "open.spotify.com/artist/" in target: # Strip any tracking parameters found_spotify_url = target.split("?")[0] break if found_spotify_url: break time.sleep(1.0) # MusicBrainz rate limit compliance (1 req/sec) if found_spotify_url: cur.execute(""" UPDATE artists SET spotify_url = ? WHERE artist_id = ? """, (found_spotify_url, artist_id)) conn.commit() resolved += 1 print(f"[{resolved}/{len(rows)}] {clean_name} -> {found_spotify_url}") else: unresolved.append((artist_id, clean_name)) print(f"[NO MATCH] {clean_name}") except Exception as e: print(f"[ERR] {clean_name}: {e}") # Rate limiting sleep for MusicBrainz service time.sleep(1.1) conn.close() print(f"\nCompleted: {resolved}/{len(rows)} resolved.") if unresolved: print(f"{len(unresolved)} remaining artists flagged for targeted fallback.")