Skip to content

Add beets sidecar for MusicBrainz enrichment of the Sorted folder

jedmund requested to merge jedmund/beets-sidecar into main

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-in web plugin on :8337 for reads, small FastAPI wrapper on :8338 for ingest job control.
  • import.copy / move / write are all no — files are never moved or modified. album-sort keeps full authority over the Category/Artist/[YYYY.MM.DD] Album layout. Autotag is on with quiet_fallback: asis so every folder ends up in the library; confident matches get MB IDs.
  • sortedAlbums gains beetsAlbumId, 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 to interrupted on startup rather than left dangling.
  • SortedAlbumCard renders a small MB badge linking to musicbrainz.org when mbAlbumId is present.

Commit map

  1. Add beets sidecar container for MusicBrainz enrichmentdeploy/beets/*, deploy/docker-compose.yml, .gitignore
  2. Add beets/MusicBrainz fields to sortedAlbums schemaschema.ts, db/index.ts, types/index.ts
  3. Add beets HTTP client, sync service, and proxy routeslib/server/beets/client.ts, services/beetsSync.ts, routes/api/beets/**
  4. Add beets ingest UI and MusicBrainz badge on sorted albumsSortedAlbumCard.svelte, settings/+page.svelte

Persistence story

  • ./beets-data:/data/beets bind mount — beets' library.db + the wrapper's jobs.db survive rebuilds
  • ./data:/data — album-sort DB (MB IDs on sortedAlbums, last-sync timestamp) already durable
  • entrypoint.sh overwrites config.yaml from the image on every boot so repo config changes take effect on redeploy
  • In-flight ingest subprocess is killed on container stop; incremental: yes means re-running picks up roughly where it left off, skipping already-indexed albums

Test plan

  • pnpm run build succeeds (verified locally)
  • docker compose build beets && docker compose up -d — both containers start, album-sort logs show it can reach beets:8337 + beets:8338
  • curl http://nuc:5180/api/beets returns reachable: true with 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 interrupted in history
  • After a successful ingest, sortedAlbums.mbAlbumId is populated for confident matches; MB badge renders on matching SortedAlbumCards; clicking it opens the right MusicBrainz release page
  • beet list -f '$albumartist - $album ($mb_albumid)' (via docker exec) shows the same MB IDs beets wrote into its library

Out of scope (follow-ups)

  • Interactive re-tag picker that flips import.write: yes under user control
  • fetchart / mbsync / chroma triggers from the UI
  • Beets query-DSL library browser route
  • MB-ID-based duplicate detection in src/lib/server/services/duplicates.ts

Merge request reports

Loading