#!/usr/bin/env python3 """ Resolves a Spotify URL to track metadata using spotdl (TzurSoffer fork). Usage: python3 spotify_resolve.py Outputs JSON to stdout. Errors go to stderr for clean separation. """ import sys import json import os import tempfile import subprocess def eprint(*args): print(*args, file=sys.stderr) def main(): if len(sys.argv) < 2: eprint("No URL provided") sys.exit(1) url = sys.argv[1] tmp = tempfile.mktemp(suffix=".spotdl") try: eprint(f"[spotdl] running: spotdl save {url}") result = subprocess.run( ["spotdl", "save", url, "--save-file", tmp, "--log-level", "ERROR"], capture_output=True, text=True, timeout=60 ) eprint(f"[spotdl] exit={result.returncode}") if result.stdout.strip(): eprint(f"[spotdl] stdout: {result.stdout.strip()[:300]}") if result.stderr.strip(): eprint(f"[spotdl] stderr: {result.stderr.strip()[:300]}") if result.returncode != 0: err = result.stderr.strip() or result.stdout.strip() or "spotdl exited non-zero" eprint(f"[spotdl] FAILED: {err}") sys.exit(1) if not os.path.exists(tmp): eprint("[spotdl] no output file produced") sys.exit(1) with open(tmp, "r", encoding="utf-8") as f: songs = json.load(f) if not songs: eprint("[spotdl] empty track list") sys.exit(1) eprint(f"[spotdl] got {len(songs)} tracks") def fmt_track(s): artists = s.get("artists") or [] artist_str = ", ".join(artists) if artists else s.get("artist", "") date = s.get("date") or (str(s["year"]) if s.get("year") else "") return { "trackName": s.get("name", ""), "artistName": artist_str, "collectionName": s.get("album_name", ""), "artworkUrl100": s.get("cover_url", ""), "releaseDate": date, "trackNumber": s.get("track_number"), "trackExplicitness": "explicit" if s.get("explicit") else "notExplicit", "durationMs": int(s["duration"] * 1000) if s.get("duration") else None, } tracks = [fmt_track(s) for s in songs] first = songs[0] artists0 = first.get("artists") or [] artist0 = ", ".join(artists0) if artists0 else first.get("artist", "") if "/track/" in url: out = { "type": "track", "name": first.get("name", ""), "artist": artist0, "artworkUrl": first.get("cover_url", ""), "tracks": tracks, } elif "/album/" in url: out = { "type": "album", "name": first.get("album_name", ""), "artist": first.get("album_artist", artist0), "artworkUrl": first.get("cover_url", ""), "tracks": tracks, } else: out = { "type": "playlist", "name": first.get("list_name", ""), "artist": first.get("album_artist", ""), "artworkUrl": first.get("cover_url", ""), "skippedLocal": 0, "tracks": tracks, } print(json.dumps(out, ensure_ascii=False)) finally: try: os.unlink(tmp) except Exception: pass if __name__ == "__main__": main()