Skip to main content

Sync beets metadata into the musefs SQLite store

Project description

beets-musefs

A beets plugin that syncs your beets metadata (tags + cover art) into a musefs SQLite store, so a live musefs mount shows a re-tagged view of your library without rewriting any audio.

How it fits together

  • The plugin owns the tags (and cover art, when beets has it) of each track, keyed by the file's canonical real path.
  • The structural columns (audio offsets, size, mtime) can only come from musefs probing the file, so the plugin runs musefs scan for you (via the bin config) before syncing — it never tries to compute those itself.
  • beet musefs scans the library and then syncs; the import/write hooks scan just the touched file and then sync. musefs's auto-refresh shows changes live — no remount, and no separate scan step.

Install (local / development)

No install needed — point beets at the plugin's beetsplug directory. beets adds pluginpath entries directly to the beetsplug package path, so it must be the beetsplug dir itself (not its parent). In your beets config.yaml:

The plugin depends on the shared python-musefs library, which is unpublished and lives in this repo. Install it from the working tree before the plugin:

pip install -e contrib/python-musefs
pip install -e "contrib/beets[test]"
pluginpath: /path/to/musefs/contrib/beets/beetsplug
plugins: musefs
musefs:
  db: ~/musefs.db          # path to the musefs SQLite store (required)
  bin: musefs              # musefs executable for auto-scan; use a full path if
                           # not on $PATH, e.g. /path/to/musefs/target/release/musefs
  # autoscan: yes          # default; runs `musefs scan` for you. Set `no` to
  #                        # manage scanning yourself (hooks then best-effort).
  # fields:                # optional: map extra beets fields to musefs keys
  #   comments: comment

Workflow (test drive)

# Sync beets metadata into the store. Auto-scans the library first (creating the
# DB if needed) — no separate `musefs scan` step.
beet musefs                      # everything
beet musefs albumartist:"Boards of Canada"   # a subset (scans just those files)
beet musefs -n                   # dry run: report counts, write nothing

# Mount the re-tagged view.
musefs mount ~/mnt --db ~/musefs.db \
    --template '$albumartist/$album/$tracknumber - $title'

# ...or mirror your beets library layout exactly, via the computed beets_path tag.
musefs mount ~/mnt --db ~/musefs.db --template '$!{beets_path}'

Imports and tag write-backs auto-sync via event hooks: beet import and beet modify -w … record the touched items and reconcile them once the command finishes — when each file's path is final (beets has no move event, and a write fires before its move). The reconcile scans the new path and prunes the row left behind at the old one. A metadata-only beet modify (no -w) doesn't fire a hook — re-run beet musefs. With autoscan: no, run musefs scan yourself first; the hooks then skip gracefully if the DB is missing.

Notes

  • Cover art: taken from the album's artpath (beets' external cover file). beets art wins when present; otherwise any art musefs scan ingested from embedded pictures is preserved.
  • Computed path (beets_path): each sync also writes a beets_path text tag holding the track's beets library-relative path (from your paths: config, via item.destination), with the file extension removed — musefs re-appends it. Mount with --template '$!{beets_path}' (the $!{} path field keeps / as directory separators) to mirror your beets layout, including layouts musefs's own template engine can't express. Set write_path: no in the musefs: config to skip it. Do not add an extension in a template that consumes beets_path. See the computed-tag workflow in ARCHITECTURE.md.
  • Moves & deletes: every sync (the command and the end-of-command reconcile) prunes track rows whose backing file is no longer present, so renames/moves don't leave stale entries. Caveat: a file that's merely offline at sync time (e.g. an unmounted network share) is also pruned — sync while the library is available.
  • Orphaned art: replacing art can orphan old blobs; musefs scan --revalidate garbage-collects them.
  • Schema version: the plugin refuses to run if the DB's user_version differs from the version it targets — rebuild after upgrading musefs.

Tests

The tests live under tests/ and use a local virtualenv with beets + pytest.

cd contrib/beets
uv venv                                   # create .venv (once)
source .venv/bin/activate
uv pip install -e ../python-musefs        # shared library (unpublished; install first)
uv pip install -r requirements.txt        # beets + pytest

python -m pytest                          # unit + integration (no Rust binary)
python -m pytest -m musefs_bin            # path-matching gate vs the real `musefs` binary
python -m pytest -m e2e                   # full beets -> mount -> playback end-to-end

The musefs_bin gate shells out to the real musefs binary, so build it first from the repo root (cargo build) and run it against a fresh build. The e2e tier additionally needs ffmpeg and /dev/fuse + fusermount: it generates audio, imports it with beets, retags, syncs, mounts via FUSE, and verifies the mount's tags and byte-identical audio (including a move-reconcile case). Both tiers are deselected from the default run and skip cleanly if their tools are absent.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

beets_musefs-0.0.1.tar.gz (23.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

beets_musefs-0.0.1-py3-none-any.whl (11.0 kB view details)

Uploaded Python 3

File details

Details for the file beets_musefs-0.0.1.tar.gz.

File metadata

  • Download URL: beets_musefs-0.0.1.tar.gz
  • Upload date:
  • Size: 23.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for beets_musefs-0.0.1.tar.gz
Algorithm Hash digest
SHA256 59cfd4659929e2d1c30fc1b0dee37af8fa47c7b1f679d0f1cd64cd3c3557f7c9
MD5 8fc12a458a56de1b23b2dc843a8806f2
BLAKE2b-256 4677877d8d6847b8d644bec32ed03e007e76f2182ff884341852d9ac63af88f6

See more details on using hashes here.

File details

Details for the file beets_musefs-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: beets_musefs-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 11.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for beets_musefs-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8f08b8e6f95903058c72c542441664f3296a7f31bda66a290f09502a8c8282ae
MD5 f71181540e7ff804ed2dcbdfcbd3df90
BLAKE2b-256 18683eb618026f12c205db9a762c19db23aba76d57a695df3e4e4af6b9dff3e4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page