81 lines
2.9 KiB
Python
81 lines
2.9 KiB
Python
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.")
|