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 scanfor you (via thebinconfig) before syncing — it never tries to compute those itself. beet musefsscans 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 artmusefs scaningested from embedded pictures is preserved. - Computed path (
beets_path): each sync also writes abeets_pathtext tag holding the track's beets library-relative path (from yourpaths:config, viaitem.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. Setwrite_path: noin themusefs:config to skip it. Do not add an extension in a template that consumesbeets_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 --revalidategarbage-collects them. - Schema version: the plugin refuses to run if the DB's
user_versiondiffers 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
59cfd4659929e2d1c30fc1b0dee37af8fa47c7b1f679d0f1cd64cd3c3557f7c9
|
|
| MD5 |
8fc12a458a56de1b23b2dc843a8806f2
|
|
| BLAKE2b-256 |
4677877d8d6847b8d644bec32ed03e007e76f2182ff884341852d9ac63af88f6
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f08b8e6f95903058c72c542441664f3296a7f31bda66a290f09502a8c8282ae
|
|
| MD5 |
f71181540e7ff804ed2dcbdfcbd3df90
|
|
| BLAKE2b-256 |
18683eb618026f12c205db9a762c19db23aba76d57a695df3e4e4af6b9dff3e4
|