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
42
README.md
42
README.md
|
|
@ -61,18 +61,33 @@ the stream plays whatever is already in `cache/` (the milestone-1/2 behaviour).
|
|||
For the yt-dlp source, list the URLs to draw from in `config/urls.txt` (copy
|
||||
`config/urls.txt.example`). Each line is either a direct track URL or a
|
||||
container URL (playlist, album, label, artist page) from which one track is
|
||||
picked at random. The relative mix between sources is set by
|
||||
`RADIEO_WEIGHT_NAVIDROME` / `RADIEO_WEIGHT_YTDLP` (a weight of 0 disables a
|
||||
source); the file being absent also disables yt-dlp.
|
||||
picked at random.
|
||||
|
||||
For the ListenBrainz source, set `RADIEO_LISTENBRAINZ_URL` to your
|
||||
recommendations feed (the Atom syndication URL, e.g.
|
||||
`https://listenbrainz.org/syndication-feed/user/<you>/recommendations/weekly-exploration`,
|
||||
or a local file path under `config/` for testing). ListenBrainz only *names*
|
||||
tracks, so each suggestion is resolved to a real file: Navidrome first
|
||||
(a `search3` lookup), then yt-dlp (`ytsearch1:`) as a fallback. The
|
||||
MusicBrainz recording MBID that the feed already carries is used as the
|
||||
track's canonical identity (no extra lookup needed).
|
||||
|
||||
The relative mix between sources is set by `RADIEO_WEIGHT_NAVIDROME` /
|
||||
`RADIEO_WEIGHT_YTDLP` / `RADIEO_WEIGHT_LISTENBRAINZ` (a weight of 0 disables a
|
||||
source); an empty URL / missing file also disables the corresponding source.
|
||||
|
||||
## Current status
|
||||
|
||||
**Milestone 5 — MBID canonicalizer: done.**
|
||||
**Milestone 6 — ListenBrainz provider: done.**
|
||||
|
||||
- Two playback sources feed a weighted scheduler: a Navidrome/OpenSubsonic
|
||||
playlist and a hand-maintained list of yt-dlp URLs (`config/urls.txt`).
|
||||
Container URLs (playlist/album/label/artist) are expanded and one track is
|
||||
drawn at random.
|
||||
- Three playback sources feed a weighted scheduler: a Navidrome/OpenSubsonic
|
||||
playlist, a hand-maintained list of yt-dlp URLs (`config/urls.txt`), and a
|
||||
ListenBrainz recommendations feed. Container URLs (playlist/album/label/artist)
|
||||
are expanded and one track is drawn at random.
|
||||
- ListenBrainz suggestions carry a MusicBrainz recording MBID, a title and an
|
||||
artist; each is resolved to a concrete file (Navidrome `search3` first, then
|
||||
a yt-dlp `ytsearch1:` fallback) and keyed directly by its MBID — so the same
|
||||
song is de-duplicated across all three sources for free.
|
||||
- Each track is canonicalized to a MusicBrainz recording MBID (no API key
|
||||
needed; ~1 req/s, best-effort, results cached in SQLite). This gives a
|
||||
source-agnostic identity, so the same song from two sources collapses to one;
|
||||
|
|
@ -92,9 +107,10 @@ source); the file being absent also disables yt-dlp.
|
|||
- HTTP stream served at `http://localhost:8000/radio.mp3` (MP3, 192 kbps),
|
||||
multiple simultaneous listeners supported.
|
||||
|
||||
The ListenBrainz suggestion feed comes next. (Known cosmetic quirk: at startup
|
||||
the fallback logs a few harmless ffmpeg "Invalid data" warnings while probing
|
||||
non-audio files such as `.gitkeep`; to be quieted in the polish milestone.)
|
||||
Polish comes next (crossfade tuning, robustness, optional web player, config
|
||||
file). (Known cosmetic quirk: at startup the fallback logs a few harmless
|
||||
ffmpeg "Invalid data" warnings while probing non-audio files such as
|
||||
`.gitkeep`; to be quieted in the polish milestone.)
|
||||
|
||||
## Roadmap
|
||||
|
||||
|
|
@ -107,6 +123,6 @@ non-audio files such as `.gitkeep`; to be quieted in the polish milestone.)
|
|||
weighted mixing between sources.
|
||||
5. ✅ **Canonicalizer** — MusicBrainz MBID lookup for source-agnostic
|
||||
de-duplication.
|
||||
6. **ListenBrainz provider** — parse the RSS suggestions feed and resolve each
|
||||
one to Navidrome or yt-dlp.
|
||||
6. ✅ **ListenBrainz provider** — parse the recommendations feed and resolve
|
||||
each suggestion to Navidrome or yt-dlp.
|
||||
7. **Polish** — crossfade, robustness, optional web player, config file.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue