docs/v1/ - Original beetfs documentation:
- analysis.md, components.md, data-flow.md, drawbacks.md
- features.md, modernization.md, rust-migration.md
- benchmark-plan.md, benchmark-results.md, e2e-test-plan.md
- README.md
docs/v2/ - New MusicFS architecture:
- requirements.md: Full requirements spec (FR-1 to FR-25, NFR-1 to NFR-14)
- P0: Multi-origin, plugins, CAS, control API
- P1: Search, album art, prefetch, metadata sources
- P3: HA, 10M+ files scalability
- architecture.md: Google BlueDoc style design document
- PlantUML diagrams for all components
- Design requirements with quantitative targets
- Alternatives considered, implementation plan
9.1 KiB
beetfs Feature Set
Overview
beetfs is a FUSE filesystem plugin for beets that presents your music library as a virtual filesystem organized by metadata. Files appear with paths derived from their database metadata, and reading file headers returns metadata from the beets database rather than the actual file tags.
Author: Martin Eve (2010)
License: GPLv3
Python: 2.7 (uses fuse-python)
Core Features
1. Virtual Metadata-Based Directory Structure
Files are presented in a configurable path format based on beets database fields:
$artist/$album ($year) [$format_upper]/$track - $artist - $title.$format
Example:
/mnt/beetfs/
├── Metallica/
│ └── 72 Seasons (2023) [FLAC]/
│ ├── 01 - Metallica - 72 Seasons.flac
│ ├── 02 - Metallica - Shadows Follow.flac
│ └── ...
├── Pink Floyd/
│ └── The Dark Side of the Moon (1973) [FLAC]/
│ └── ...
Available template variables:
$artist,$album,$title,$genre,$composer,$grouping$year,$month,$day$track,$tracktotal,$disc,$disctotal$format,$format_upper(file extension)$lyrics,$comments,$bpm,$comp
2. Metadata Overlay (Read)
When you read a file through beetfs, the metadata header is synthesized from the beets database, not read from the actual file on disk.
How it works:
- Open file → beetfs reads the real file from disk
- Parse the audio format header (FLAC/MP3)
- Replace metadata fields with values from beets database
- Return synthesized header + original audio data
Supported fields for overlay:
title,artist,album,genre(FLAC only currently)
Use case: Your files may have inconsistent or wrong tags, but beetfs presents them with the corrected metadata from your beets library.
3. Metadata Passthrough (Write)
When you write to file headers through beetfs, the changes are saved to the beets database, not to the actual file.
How it works:
- Application writes new metadata to file header region
- beetfs intercepts the write
- Parses the new metadata values
- Updates the beets database (
lib.store(),lib.save()) - Regenerates the synthesized header
Result: Tag editors (Picard, Kid3, etc.) can edit metadata through beetfs, and changes persist in the beets database without modifying the original files.
4. Format Support
| Format | Read | Metadata Overlay | Write to DB |
|---|---|---|---|
| FLAC | ✅ | ✅ Full | ✅ |
| MP3 | ✅ | ❌ Disabled | ❌ |
| Other | ❌ | ❌ | ❌ |
FLAC Implementation:
- Uses
InterpolatedFLACclass extending mutagen - Reconstructs Vorbis comment block with DB values
- Preserves audio data and other metadata blocks
MP3 Implementation:
- Passthrough only (no interpolation)
self.bound = 0disables header replacement
5. File Caching
Open files are cached in FileHandler objects:
- First open: Load entire file into memory, parse headers
- Subsequent opens: Reuse cached
FileHandler - Reference counting for multiple opens
- Release when reference count reaches zero
Memory impact: Each open file consumes ~filesize RAM.
FUSE Operations
Implemented (Functional)
| Operation | Description |
|---|---|
getattr |
File/directory stat (size, mode, timestamps) |
access |
Permission checking |
opendir |
Open directory for listing |
readdir |
List directory contents |
releasedir |
Close directory |
open |
Open file for reading/writing |
read |
Read file contents |
write |
Write to file (header region only) |
release |
Close file |
fgetattr |
Stat with file handle |
statfs |
Filesystem statistics |
Not Implemented (Return EOPNOTSUPP)
| Operation | Reason |
|---|---|
create |
Read-only structure |
mknod |
Read-only structure |
mkdir |
Read-only structure |
unlink |
Read-only structure |
rmdir |
Read-only structure |
symlink |
Not needed |
link |
Not needed |
rename |
Would break DB consistency |
chmod |
Metadata-only FS |
chown |
Metadata-only FS |
truncate |
Would corrupt audio |
utime |
Metadata-only FS |
Usage
Mount
beet mount /mnt/beetfs
Unmount
fusermount -u /mnt/beetfs
Example Session
# Mount the filesystem
beet mount /mnt/music
# Browse by artist
ls /mnt/music/
# Metallica/ Pink Floyd/ The Beatles/ ...
# List an album
ls "/mnt/music/Metallica/72 Seasons (2023) [FLAC]/"
# 01 - Metallica - 72 Seasons.flac
# 02 - Metallica - Shadows Follow.flac
# ...
# Play through any music player
mpv "/mnt/music/Metallica/72 Seasons (2023) [FLAC]/01 - Metallica - 72 Seasons.flac"
# Edit tags (changes go to beets DB)
kid3 "/mnt/music/Metallica/72 Seasons (2023) [FLAC]/"
# Unmount
fusermount -u /mnt/music
Architecture
┌─────────────────────────────────────────────────────────────┐
│ User Applications │
│ (mpv, Rhythmbox, Kid3, etc.) │
└─────────────────────────┬───────────────────────────────────┘
│ POSIX calls (open, read, write)
▼
┌─────────────────────────────────────────────────────────────┐
│ Linux Kernel │
│ FUSE module │
└─────────────────────────┬───────────────────────────────────┘
│ /dev/fuse
▼
┌─────────────────────────────────────────────────────────────┐
│ beetfs │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────┐ │
│ │ FSNode Tree │ │ FileHandler │ │ InterpolatedFLAC │ │
│ │ (in-memory) │ │ (cache) │ │ (header synth) │ │
│ └─────────────┘ └──────────────┘ └───────────────────┘ │
└────────┬────────────────┬───────────────────┬───────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Beets DB │ │ Real Files │ │ Mutagen │
│ (SQLite) │ │ (on disk) │ │ (parsing) │
└─────────────┘ └─────────────────┘ └─────────────────┘
Limitations
Current Bugs (Non-Functional)
- Nested Methods Bug: Lines 758-1144 are indented inside
access(), making FUSE operations unreachable - Directory Tree Bug:
FSNode.adddir()crashes when building tree for non-empty library
Design Limitations
- Memory Usage: Entire file loaded into RAM on open
- Mount Time: O(N) - loads all library items at mount
- No Lazy Loading: Full directory tree built upfront
- Single Format: Only FLAC has full metadata overlay
- No Real File Modification: Writes only update DB, not actual files
- Python 2.7 GIL: Single-threaded performance
Not Supported
- Creating/deleting files or directories
- Moving/renaming files
- Modifying audio content
- Album art / embedded images
- Multi-value tags
- Non-ASCII in some edge cases
Configuration
Currently hardcoded. Potential configuration points:
| Setting | Current Value | Description |
|---|---|---|
PATH_FORMAT |
$artist/$album ($year)... |
Directory structure template |
METADATA_RW_FIELDS |
17 fields | Fields available for read/write |
| Caching | Always on | FileHandler caching behavior |
| Threading | Disabled | multithreaded = 0 |
Dependencies
- Python 2.7
- fuse-python
- beets 1.4.x
- mutagen (FLAC/MP3 parsing)
See Also
- e2e-test-plan.md - Test strategy and bug documentation
- benchmark-plan.md - Performance measurement methodology
- benchmark-results.md - Current benchmark status