A Navidrome plugin that mirrors your ListenBrainz "Created For You" playlists into Navidrome as personal, per-user playlists — and drives the tonus backend to download the tracks you don't own yet.
Status: v0.5.0 — LB Weekly Mirror is the primary feature. Optional Genre Mixes on top. Multi-user from the ground up.
Every ListenBrainz user gets four auto-generated playlists — Weekly Exploration, Weekly Jams, Last Week's Exploration, Last Week's Jams. This plugin mirrors all four into Navidrome as playlists owned by the mapped Navidrome user, under the same names, and keeps them fresh:
- Tracks you already have in your library are added to the Navidrome playlist immediately.
- Tracks you don't have are queued in tonus for download and reconciled into the playlist as they finish (rate-limited downloads can trickle in over hours).
- Each run replaces the playlist in place — same behaviour as ListenBrainz, the playlist just updates itself weekly. The playlist ID stays stable, so anything you copy out of it into your own playlists is unaffected.
Enabled per user via the lb_weekly_mirror toggle (on by default).
On top of the weekly mirror, each user can define any number of Genre Mixes — Spotify-style weekly playlists per genre (metalcore, hip-hop, shoegaze, …). Each mix has its own two-phase cron:
- Discovery (default Wed 02:00) — fetches genre tracks from ListenBrainz, queues the missing ones for download.
- Build (default Fri 06:00, 48 h later) — builds/replaces the Subsonic playlist from whatever finished downloading in the meantime.
A discovery_ratio (default 0.4 = 40 % new / 60 % familiar) controls the new-vs-known mix. Same replace-in-place pattern — favourites you copy elsewhere are safe.
- The plugin downloads nothing itself — that's tonus's job. It's a thin trigger + Subsonic-playlist scheduler.
- No live queue inside Navidrome. The plugin UI is limited to its settings page; use tonus's own web UI for live download status.
- No listen-tracking / scrobble ingestion — it reads ListenBrainz's output (the Created-For-You playlists), not your raw listens.
The plugin is a thin trigger; the backend is the brain. A TaskQueue with concurrency 1 receives, per Navidrome user per cron tick, one discovery task and one reconcile task per playlist slot. Discovery fires POST /api/plugin/lbweekly/discovery (or /api/plugin/mix/discovery for Genre Mixes) — tonus returns the in-library tracks synchronously and queues the missing ones for download in the background. Reconcile pulls GET /api/plugin/finished-tracks for the user, merges those with the persisted in-library IDs, and pushes the result into a Subsonic playlist via host.SubsonicAPICall("/rest/createPlaylist?u=USERNAME") — Navidrome injects the user's auth automatically because the plugin holds the users permission. tonus resolves Subsonic track IDs server-side (admin-auth search3, cached in the job payload) so the reconcile stays well under the 30 s plugin-callback timeout.
- Navidrome ≥ 0.60 (Extism-based plugin system).
- A reachable tonus backend (≥ v0.6.1) exposing
/api/plugin/health,/api/plugin/lbweekly/discovery,/api/plugin/mix/discovery,/api/plugin/finished-tracks,/api/plugin/sync-status. - A public ListenBrainz username per Navidrome user you want to mirror (the Created-For-You playlists must be publicly readable).
- For building: TinyGo
≥ 0.40and Go≥ 1.23. The Makefile falls back to plaingoif TinyGo is missing, but the resulting WASM is much larger.
Grab the latest tonus-navidrome-plugin.ndp from the GitHub Releases page. Each release is built by the release.yml GitHub Action whenever a v*.*.* tag is pushed.
git clone https://github.com/madmax1301/tonus-navidrome-plugin.git
cd tonus-navidrome-plugin
make package # → tonus-navidrome-plugin.ndp- Copy
tonus-navidrome-plugin.ndpinto your Navidromeplugins/folder (or upload it via the admin Plugins page), then Rescan / restart. - Enable the plugin. Required permissions:
- HTTP to your tonus host (manifest uses
"*"— Navidrome may prompt you to confirm). - Scheduler for the cron registration.
- KVStore for per-user status + in-library-track persistence.
- SubsonicAPI for playlist create/replace.
- Users — flip on
Allow all users, or pick the users you want mirrored. - TaskQueue for the async per-user pipelines.
- HTTP to your tonus host (manifest uses
- Open the plugin settings and fill in:
- Connection:
tonus_url(e.g.http://192.168.1.200:8000) andtonus_token(a tonus PAT or the legacyTONUS_API_TOKEN; leave empty if tonus runs tokenless). - Schedule: the global
cron_expressionfor the weekly mirror (default0 7 * * *). - Users: click
+per Navidrome user, setnavidrome_username+listenbrainz_username, leavelb_weekly_mirroron, and optionally add one or moremixes.
- Connection:
- OnInit registers the global cron with
host.SchedulerScheduleRecurring(...), plus one discovery- and one build-cron per configured Genre Mix, and creates the TaskQueue. - OnCallback (cron tick) reads the user-mapping array and enqueues the relevant tasks — for the weekly mirror, a discovery + reconcile pair for each of the four LB slots per user. Returns in under a second.
- OnTaskExecute runs one task:
lbweekly-discovery/mix-discovery: health-check + discovery POST. tonus returns the in-library tracks (persisted toKVStore) and queues missing tracks for download.reconcile/mix-build:GET /api/plugin/finished-tracks, merge with the persisted in-library IDs, dedupe, andfindOrCreate+ replace the Subsonic playlist.
- Status: per-user state (timestamps, counts, last error) is persisted in
KVStore. tonus's web UI also shows a global status card.
The first time the plugin authenticates as a fresh Navidrome user you may see one module closed with context deadline exceeded warning — that's Navidrome registering a new player context for that user. The built-in TaskQueue retry (MaxRetries=3, BackoffMs=30000) succeeds within seconds. After 1–2 successful reconciles the cold start is over.
- Plugin = trigger, backend = brain. All discovery + download stay in tonus where they're easy to debug. The plugin knows only a handful of tonus endpoints + Subsonic.
- Reconcile pattern, not polling. Tracks queued today may not finish for hours. Each run reconciles again, idempotently picking up newly finished tracks, never duplicating.
- In-library IDs persisted in KVStore. The reconcile merges tracks you already own (returned by discovery) with freshly downloaded ones — so a mirror is complete even when nothing new had to be downloaded.
- Per-user playlist ownership via
host.SubsonicAPICall("?u=USERNAME"). No user passwords stored anywhere — Navidrome injects auth from the plugin'suserspermission. - ListenBrainz
createdforreturns metadata only. The actual tracks live behind/playlist/<mbid>; tonus does the two-step fetch server-side so the plugin never sees the LB API directly.
MIT — see LICENSE.