Milestone 6: ListenBrainz recommendations provider
Add a third playback source: a ListenBrainz recommendations Atom feed. Each suggestion already carries a MusicBrainz recording MBID, title and artist, so it is keyed directly by MBID (source-agnostic identity, no extra lookup) and resolved to a concrete file — Navidrome search3 first, then a yt-dlp ytsearch1: fallback. - providers/listenbrainz.py: parse the Atom/HTML feed, anti-repeat on the MBID key, resolve Navidrome-then-yt-dlp. Feed may be an http(s) URL or a local path (for testing). - subsonic.py: add search_songs (search3) for resolution. - canonicalizer.py: short-circuit when a Track already has an MBID, so feed-provided MBIDs are trusted and MusicBrainz is not hit. - __main__.py: wire the provider in; register the yt-dlp fetcher as a resolution backend even when the yt-dlp source is off; close providers on shutdown. - config/compose/.env.example: RADIEO_LISTENBRAINZ_URL + weight. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7e0f08b863
commit
66d93e5034
9 changed files with 284 additions and 16 deletions
|
|
@ -85,6 +85,17 @@ class SubsonicClient:
|
|||
body = self._get_json("getPlaylist", id=playlist_id)
|
||||
return body.get("playlist", {}).get("entry", [])
|
||||
|
||||
def search_songs(self, query: str, count: int = 25) -> list[dict]:
|
||||
"""Full-text song search (``search3``). Songs only, no artists/albums."""
|
||||
body = self._get_json(
|
||||
"search3",
|
||||
query=query,
|
||||
songCount=count,
|
||||
artistCount=0,
|
||||
albumCount=0,
|
||||
)
|
||||
return body.get("searchResult3", {}).get("song", [])
|
||||
|
||||
def download(self, song_id: str, dest: Path, hint_ext: str | None = None) -> str:
|
||||
"""Download a song to ``dest``; return the file extension used.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue