Root cause of bad album/title tags: yt-dlp's --parse-metadata reads a
single-word FROM (matching field_to_template's ^[a-zA-Z_]+$) as a *field
name*, so literal one-word titles/albums like "Cochise" became "NA". Inject
literals via seed-then-replace into meta_<tag> instead (--parse-metadata to
create the field, --replace-in-metadata with literal args to set it), which
is immune to template parsing and also creates tags the source lacks.
- yt_download: literal-safe meta_artist/title/album; hit album no longer
clobbered by the Unknown-Album default; artist tag now created when missing.
- lidarr_search: connection/timeout errors surface via err() ("Lidarr
unreachable … falling back to YouTube") instead of silent dbg(), so the
YouTube fallback isn't mistaken for "no Lidarr match".
- Dockerfile: install deno (arch-aware) — the JS runtime yt-dlp needs for
YouTube; without it: "No supported JavaScript runtime" / HTTP 403.
- repair: treat NA/Unknown placeholders as bogus and overwrite title/artist
from source (was fill-missing-only); normalise literal "NA" album to
"Unknown Album"; rename bogus "NA [<id>]" filenames to the recovered title.
- README updated; .gitignore excludes server/log.txt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
111 lines
4.5 KiB
Python
111 lines
4.5 KiB
Python
import server.mf # noqa: F401
|
|
import musicfetch_core as mf
|
|
|
|
DISCOVERY_MBID = "48117b90-a16e-34ca-a514-19c702df1158"
|
|
|
|
DISCOVERY_ALBUM = {"title": "Discovery", "artist": {"artistName": "Daft Punk"},
|
|
"releaseDate": "2001-01-01", "foreignAlbumId": DISCOVERY_MBID}
|
|
|
|
|
|
def test_artist_track_uses_mbid_exact_lookup(monkeypatch):
|
|
monkeypatch.setattr(mf, "API_KEY", "testkey")
|
|
monkeypatch.setattr(mf, "musicbrainz_best_album",
|
|
lambda artist, track: {"album_title": "Discovery", "artist": "Daft Punk",
|
|
"year": "2001", "rg_mbid": DISCOVERY_MBID})
|
|
seen = {}
|
|
|
|
def fake_get(path, params=None, timeout=15):
|
|
seen["term"] = (params or {}).get("term")
|
|
if path == "/api/v1/album/lookup" and seen["term"] == f"mbid:{DISCOVERY_MBID}":
|
|
return [DISCOVERY_ALBUM]
|
|
return []
|
|
monkeypatch.setattr(mf, "lidarr_get", fake_get)
|
|
|
|
hits = mf.lidarr_search("Daft Punk - Harder Better Faster Stronger", 10)
|
|
assert seen["term"] == f"mbid:{DISCOVERY_MBID}"
|
|
assert hits[0].album == "Discovery"
|
|
assert hits[0].artist == "Daft Punk"
|
|
assert hits[0].payload["album"]["foreignAlbumId"] == DISCOVERY_MBID
|
|
|
|
|
|
def test_year_enriched_from_musicbrainz(monkeypatch):
|
|
monkeypatch.setattr(mf, "API_KEY", "testkey")
|
|
monkeypatch.setattr(mf, "musicbrainz_best_album",
|
|
lambda artist, track: {"album_title": "Discovery", "artist": "Daft Punk",
|
|
"year": "2001", "rg_mbid": DISCOVERY_MBID})
|
|
no_year = [{"title": "Discovery", "artist": {"artistName": "Daft Punk"},
|
|
"releaseDate": "", "foreignAlbumId": DISCOVERY_MBID}]
|
|
monkeypatch.setattr(mf, "lidarr_get",
|
|
lambda path, params=None, timeout=15: no_year if path == "/api/v1/album/lookup" else [])
|
|
hits = mf.lidarr_search("Daft Punk - Discovery", 10)
|
|
assert hits[0].year == "2001"
|
|
|
|
|
|
def test_no_api_key_returns_empty(monkeypatch):
|
|
monkeypatch.setattr(mf, "API_KEY", "")
|
|
assert mf.lidarr_search("Daft Punk - Discovery", 10) == []
|
|
|
|
|
|
def test_mb_miss_falls_back_to_lookup(monkeypatch):
|
|
monkeypatch.setattr(mf, "API_KEY", "testkey")
|
|
monkeypatch.setattr(mf, "musicbrainz_best_album", lambda artist, track: None)
|
|
monkeypatch.setattr(mf, "lidarr_get",
|
|
lambda path, params=None, timeout=15: [DISCOVERY_ALBUM] if path == "/api/v1/album/lookup" else [])
|
|
hits = mf.lidarr_search("Daft Punk - Discovery", 10)
|
|
assert hits[0].album == "Discovery"
|
|
|
|
|
|
def test_single_term_is_artist_first(monkeypatch):
|
|
monkeypatch.setattr(mf, "API_KEY", "testkey")
|
|
|
|
def fake_get(path, params=None, timeout=15):
|
|
if path == "/api/v1/artist/lookup":
|
|
return [{"artistName": "Daft Punk"}]
|
|
if path == "/api/v1/album/lookup":
|
|
return [DISCOVERY_ALBUM]
|
|
return []
|
|
monkeypatch.setattr(mf, "lidarr_get", fake_get)
|
|
hits = mf.lidarr_search("Daft Punk", 10)
|
|
assert hits[0].kind == "artist"
|
|
assert hits[0].artist == "Daft Punk"
|
|
|
|
|
|
def test_last_resort_universal_search(monkeypatch):
|
|
monkeypatch.setattr(mf, "API_KEY", "testkey")
|
|
monkeypatch.setattr(mf, "musicbrainz_best_album", lambda artist, track: None)
|
|
|
|
def fake_get(path, params=None, timeout=15):
|
|
if path == "/api/v1/search":
|
|
return [{"album": DISCOVERY_ALBUM}]
|
|
return []
|
|
monkeypatch.setattr(mf, "lidarr_get", fake_get)
|
|
hits = mf.lidarr_search("Daft Punk - Discovery", 10)
|
|
assert hits and hits[0].album == "Discovery"
|
|
|
|
|
|
def test_unreachable_lidarr_warns_loudly(monkeypatch, capsys):
|
|
# A connection error must surface on stderr (not silent dbg) so the
|
|
# YouTube fallback isn't mistaken for "Lidarr had no match".
|
|
monkeypatch.setattr(mf, "API_KEY", "testkey")
|
|
monkeypatch.setattr(mf, "DEBUG", False)
|
|
|
|
def boom(path, params=None, timeout=15):
|
|
raise mf.ReqConnectionError("Name or service not known")
|
|
monkeypatch.setattr(mf, "lidarr_get", boom)
|
|
|
|
hits = mf._lidarr_album_candidates("anything")
|
|
assert hits == []
|
|
assert "Lidarr unreachable" in capsys.readouterr().err
|
|
|
|
|
|
def test_http_error_stays_quiet(monkeypatch, capsys):
|
|
monkeypatch.setattr(mf, "API_KEY", "testkey")
|
|
monkeypatch.setattr(mf, "DEBUG", False)
|
|
|
|
def boom(path, params=None, timeout=15):
|
|
raise mf.RequestException("500 Server Error")
|
|
monkeypatch.setattr(mf, "lidarr_get", boom)
|
|
|
|
assert mf._lidarr_album_candidates("anything") == []
|
|
assert "Lidarr unreachable" not in capsys.readouterr().err
|