Files
NovaSonic/backend/spotify_resolve.py
T
HoneyandClaude Sonnet 4.6 9b197e119a fix: strip ?si= from Spotify URL, improve spotdl error logging
spotdl doesn't handle tracking params in URLs. Also separate stdout/stderr
in Python script and always try parsing stdout before checking exit code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 14:55:41 +02:00

110 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""
Resolves a Spotify URL to track metadata using spotdl (TzurSoffer fork).
Usage: python3 spotify_resolve.py <spotify_url>
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()