fix: Odesli links 'not found' when Odesli omits YouTube link
Root cause: Odesli's linksByPlatform frequently lacks youtube/youtubeMusic (confirmed live for many Spotify tracks). resolve_link_hits only added a YouTube hit when Odesli supplied one, so with no Lidarr match the hit list was empty -> server 404 'No results found'. Fix: always run a normal youtube_search (via build_combined_hits) for the fallback; when Odesli DID return a link, insert its exact track as the first YouTube hit. Lidarr-first ordering preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
18
musicfetch
18
musicfetch
@@ -898,18 +898,22 @@ def get_artist_from_metadata(meta: dict) -> str:
|
|||||||
|
|
||||||
def resolve_link_hits(url: str, limit: int) -> tuple[str, list[Hit]]:
|
def resolve_link_hits(url: str, limit: int) -> tuple[str, list[Hit]]:
|
||||||
"""Resolve a non-YouTube/SoundCloud link via Odesli into a search query plus
|
"""Resolve a non-YouTube/SoundCloud link via Odesli into a search query plus
|
||||||
hits: Lidarr album candidates for "Artist - Title", followed by the EXACT
|
Lidarr-first, YouTube-fallback hits for "Artist - Title". Odesli frequently
|
||||||
YouTube track from the shared link (not a fuzzy re-search). Raises OdesliError
|
omits a YouTube link, so we always run a normal youtube_search for the
|
||||||
if the link can't be resolved."""
|
fallback; when Odesli DID supply a link, its exact track is inserted as the
|
||||||
|
preferred (first) YouTube hit. Raises OdesliError if the link can't resolve."""
|
||||||
r = odesli_resolve(url)
|
r = odesli_resolve(url)
|
||||||
if r is None:
|
if r is None:
|
||||||
raise OdesliError(url)
|
raise OdesliError(url)
|
||||||
query = f"{r.artist} - {r.title}".strip(" -")
|
query = f"{r.artist} - {r.title}".strip(" -")
|
||||||
hits = lidarr_search(query, limit)
|
hits = build_combined_hits(query, limit, yt_first=False,
|
||||||
|
lidarr_only=False, yt_only=False)
|
||||||
if r.youtube_url:
|
if r.youtube_url:
|
||||||
hits = hits + [Hit(source="youtube", kind="track", title=r.title,
|
exact = Hit(source="youtube", kind="track", title=r.title,
|
||||||
artist=r.artist, thumbnail=r.thumb,
|
artist=r.artist, thumbnail=r.thumb,
|
||||||
payload={"url": r.youtube_url})]
|
payload={"url": r.youtube_url})
|
||||||
|
idx = next((i for i, h in enumerate(hits) if h.source == "youtube"), len(hits))
|
||||||
|
hits = hits[:idx] + [exact] + hits[idx:]
|
||||||
return query, hits
|
return query, hits
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -107,28 +107,38 @@ def _resolved(yt="https://music.youtube.com/watch?v=YYY"):
|
|||||||
thumb="https://img/cover.jpg", youtube_url=yt)
|
thumb="https://img/cover.jpg", youtube_url=yt)
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_link_hits_builds_query_and_exact_yt(monkeypatch):
|
def _ytsearch_hit():
|
||||||
|
return mf.Hit(source="youtube", kind="track", title="Bloom (search)",
|
||||||
|
artist="ODESZA", payload={"videoId": "srch"})
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_link_hits_lidarr_first_then_exact_then_search(monkeypatch):
|
||||||
|
# Odesli supplies a YouTube link -> exact track is the FIRST youtube hit,
|
||||||
|
# ahead of the fuzzy youtube_search results, and after the Lidarr hits.
|
||||||
monkeypatch.setattr(mf, "odesli_resolve", lambda url: _resolved())
|
monkeypatch.setattr(mf, "odesli_resolve", lambda url: _resolved())
|
||||||
lid = mf.Hit(source="lidarr", kind="album", title="A Moment Apart",
|
lid = mf.Hit(source="lidarr", kind="album", title="A Moment Apart",
|
||||||
artist="ODESZA", album="A Moment Apart", year="2017",
|
artist="ODESZA", album="A Moment Apart", year="2017",
|
||||||
payload={"album": {"id": 9}})
|
payload={"album": {"id": 9}})
|
||||||
monkeypatch.setattr(mf, "lidarr_search", lambda q, limit: [lid])
|
monkeypatch.setattr(mf, "lidarr_search", lambda q, limit: [lid])
|
||||||
|
monkeypatch.setattr(mf, "youtube_search", lambda q, limit: [_ytsearch_hit()])
|
||||||
query, hits = mf.resolve_link_hits("https://open.spotify.com/track/abc", 10)
|
query, hits = mf.resolve_link_hits("https://open.spotify.com/track/abc", 10)
|
||||||
assert query == "ODESZA - Bloom"
|
assert query == "ODESZA - Bloom"
|
||||||
assert hits[0].source == "lidarr"
|
assert hits[0].source == "lidarr"
|
||||||
yt = hits[-1]
|
yt = [h for h in hits if h.source == "youtube"]
|
||||||
assert yt.source == "youtube" and yt.kind == "track"
|
assert yt[0].payload.get("url") == "https://music.youtube.com/watch?v=YYY" # exact first
|
||||||
assert yt.title == "Bloom" and yt.artist == "ODESZA"
|
assert any(h.payload.get("videoId") == "srch" for h in yt) # search fallback present
|
||||||
assert yt.payload["url"] == "https://music.youtube.com/watch?v=YYY"
|
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_link_hits_no_yt_link_lidarr_only(monkeypatch):
|
def test_resolve_link_hits_no_odesli_yt_uses_search_fallback(monkeypatch):
|
||||||
|
# Regression: Odesli often omits YouTube links. With no Lidarr match and no
|
||||||
|
# Odesli YouTube link, a normal youtube_search must still yield hits (not empty).
|
||||||
monkeypatch.setattr(mf, "odesli_resolve", lambda url: _resolved(yt=""))
|
monkeypatch.setattr(mf, "odesli_resolve", lambda url: _resolved(yt=""))
|
||||||
lid = mf.Hit(source="lidarr", kind="album", title="X", artist="ODESZA",
|
monkeypatch.setattr(mf, "lidarr_search", lambda q, limit: [])
|
||||||
payload={"album": {"id": 9}})
|
monkeypatch.setattr(mf, "youtube_search", lambda q, limit: [_ytsearch_hit()])
|
||||||
monkeypatch.setattr(mf, "lidarr_search", lambda q, limit: [lid])
|
|
||||||
query, hits = mf.resolve_link_hits("https://open.spotify.com/track/abc", 10)
|
query, hits = mf.resolve_link_hits("https://open.spotify.com/track/abc", 10)
|
||||||
assert all(h.source == "lidarr" for h in hits)
|
assert hits, "must not be empty when YouTube search finds the track"
|
||||||
|
assert all(h.source == "youtube" for h in hits)
|
||||||
|
assert not any(h.payload.get("url") for h in hits) # no exact Odesli hit injected
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_link_hits_odesli_miss_raises(monkeypatch):
|
def test_resolve_link_hits_odesli_miss_raises(monkeypatch):
|
||||||
|
|||||||
Reference in New Issue
Block a user