Add beets sidecar for MusicBrainz enrichment of the Sorted folder
Summary
Stands up a Python beets sidecar next to the Node app that indexes the Sorted directory in place and enriches each album with MusicBrainz IDs via the autotagger. First step of a layered integration — read-only for Phase 0+1; interactive re-tag / fetchart / chroma are follow-ups.
- Beets runs as a second service in
docker-compose.yml. Built-inwebplugin on :8337 for reads, small FastAPI wrapper on :8338 for ingest job control. -
import.copy/move/writeare allno— files are never moved or modified. album-sort keeps full authority over theCategory/Artist/[YYYY.MM.DD] Albumlayout. Autotag is on withquiet_fallback: asisso every folder ends up in the library; confident matches get MB IDs. -
sortedAlbumsgainsbeetsAlbumId,mbAlbumId,mbAlbumArtistId,beetsIngestStatus,beetsLastSyncAt. Migration is the existing try/catch ALTER TABLE pattern. - Settings grows a Beets section: separate reachability pills, four metric tiles, live progress bar during ingest (polled from
/metrics), collapsible stdout log, and a collapsible job history of the last 20 ingests. - Job history is durable — the wrapper persists each job to
/data/beets/jobs.db. Jobs cut off mid-run by a container restart are flipped tointerruptedon startup rather than left dangling. -
SortedAlbumCardrenders a small MB badge linking to musicbrainz.org whenmbAlbumIdis present.
Commit map
-
Add beets sidecar container for MusicBrainz enrichment —
deploy/beets/*,deploy/docker-compose.yml,.gitignore -
Add beets/MusicBrainz fields to sortedAlbums schema —
schema.ts,db/index.ts,types/index.ts -
Add beets HTTP client, sync service, and proxy routes —
lib/server/beets/client.ts,services/beetsSync.ts,routes/api/beets/** -
Add beets ingest UI and MusicBrainz badge on sorted albums —
SortedAlbumCard.svelte,settings/+page.svelte
Persistence story
-
./beets-data:/data/beetsbind mount — beets'library.db+ the wrapper'sjobs.dbsurvive rebuilds -
./data:/data— album-sort DB (MB IDs onsortedAlbums, last-sync timestamp) already durable -
entrypoint.shoverwritesconfig.yamlfrom the image on every boot so repo config changes take effect on redeploy - In-flight ingest subprocess is killed on container stop;
incremental: yesmeans re-running picks up roughly where it left off, skipping already-indexed albums
Test plan
-
pnpm run buildsucceeds (verified locally) -
docker compose build beets && docker compose up -d— both containers start,album-sortlogs show it can reachbeets:8337+beets:8338 -
curl http://nuc:5180/api/beetsreturnsreachable: truewith wrapper + web plugin both green -
In settings, click Run ingest — progress bar advances as beets processes folders; stdout tail updates under Show log -
Stop the beets container mid-ingest; restart it; settings shows the prior job as interruptedin history -
After a successful ingest, sortedAlbums.mbAlbumIdis populated for confident matches; MB badge renders on matchingSortedAlbumCards; clicking it opens the right MusicBrainz release page -
beet list -f '$albumartist - $album ($mb_albumid)'(viadocker exec) shows the same MB IDs beets wrote into its library
Out of scope (follow-ups)
- Interactive re-tag picker that flips
import.write: yesunder user control -
fetchart/mbsync/chromatriggers from the UI - Beets query-DSL library browser route
- MB-ID-based duplicate detection in
src/lib/server/services/duplicates.ts