Scanning

musefs --version (or -V) prints the build version; --help on the root or any subcommand lists its flags.

Scan

musefs scan /path/to/music --db library.db             # additive ingest
musefs scan /path/to/music --db library.db --force     # reseed existing rows

scan probes each audio file (FLAC, MP3, M4A/M4B, Ogg, WAV), recording its audio byte range, tags, and embedded art in the store. Bare scan is additive: it leaves already tracked rows alone, while --force re-seeds existing rows from disk. (Refreshing already-tracked files and pruning gone ones is the job of musefs revalidate — see Maintenance.) It takes one or more files or directories, and --jobs N controls probe parallelism. --follow-symlinks walks symlinked files and directories (off by default, so symlinks are logged and skipped). --quiet (-q) suppresses the per-target summary for scripting; scan failures still surface on stderr (raise detail with -v/-vv, or RUST_LOG=info).

scan (with or without --force) shows a live progress indicator: on an interactive terminal, a discovery spinner followed by a determinate bar (position, percent, ETA, current file); on a non-interactive stderr (piped or logged), throttled processed N/M (P%) lines. --quiet (-q) suppresses the progress indicator and the per-target summary. Each summary line ends with the elapsed time. Anything logged during the scan (skip warnings, per-file failures) is printed above the bar, which is lifted out of the way and redrawn underneath, so warnings stay readable and scroll back intact.

The per-target summary reads scanned N: … already present Z, skipped X, failed Y. already present counts files bare scan skipped because they were already tracked. skipped counts every file that isn't a supported audio format — cover art, .cue / .log / .nfo sidecars, and anything else non-audio — so a large skipped number (hundreds or thousands on a big library) is expected, not an error. A per-extension breakdown of the skip count is logged at end of scan at info (e.g. skipped 42: jpg=20, cue=10, log=8, <none>=4, so it needs -v or RUST_LOG=info), letting you tell expected sidecars from anything genuinely unexpected. failed is the one to watch: those are audio files musefs recognised by extension but could not parse, or could not store. Its own breakdown by reason is logged at end of scan too — failed 38: unparseable=30, io=5, oversize=2, rejected=1 — at warn, so it is visible without -v; a further walk errors N: unreadable=9, symlink=3 line accounts for directories and entries the walk itself could not read (those are counted in neither skipped nor failed, since no file was ever queued for them).

A rejected bucket in that breakdown means the store refused a file's rows on a constraint — the tag, art or track values it parsed were not something the schema accepts. Each one is logged with its path and the constraint text, and the rest of the library scans normally; nothing partial is stored for a rejected file, so the mount never shows a track quietly missing its tags. These are worth reporting: unlike oversize, which names a documented limit, a rejected file is a shape musefs did not anticipate.

Per-file skip messages are capped at ten per reason per scan; the rest drop to debug (-vv / RUST_LOG=debug) so an unreadable subtree or a share that vanished mid-scan cannot emit one line per file. The end-of-scan breakdowns carry the full counts either way. Format dispatch is by extension only — there is no content sniffing and no fallback to another parser, so a file whose contents don't match its extension (e.g. a FLAC named .mp3) is handed to the wrong parser, fails, and is counted here rather than retried. Renaming files across formats makes them vanish from the mount; fix the extension and rescan.

Non-standard containers are tolerated where the audio is still recoverable: a FLAC that carries one or more ID3 tags in front of the fLaC marker parses, and any tags or cover art in that ID3 header are ingested as a fallback for what the FLAC itself does not carry — see Leading ID3 tags.

If any file fails (failed Y with Y > 0), scan exits 2 even though the batch otherwise completes and the parseable files are ingested — so a pipeline like musefs scan … && musefs mount … stops on a partial or total ingest failure rather than mounting an incomplete library. A successful scan exits 0; a hard error (a missing target, an unreadable DB) still exits 1. The exit code is the only machine-detectable signal; per-file failures otherwise surface only on stderr. The full exit-code contract, and how to raise log detail on those failures, are in Logging & troubleshooting.

Content checksums and move re-identification

--checksum=none|fingerprint|full (env MUSEFS_CHECKSUM, default fingerprint) controls what content checksums scan computes and stores.

  • none — no checksums (legacy behavior).
  • fingerprint — compute a cheap fingerprint for each file, derived from the probe's parsed output (tags, audio bounds, embedded art). This is the default: it rides the existing probe at essentially no extra I/O cost and is sufficient for routine move detection.
  • full — fingerprint plus an eager full-file SHA-256. Use this when you want collision-proof retargeting or a forensic content identity for every file.

Two flags govern how a fingerprint match is confirmed before retargeting a moved file:

  • --fast (env MUSEFS_FAST) — fingerprint match is always sufficient; never reads the full file even when a stored content_hash exists.
  • --strict (env MUSEFS_STRICT) — require a full-hash match; if the matched candidate has no stored content_hash, refuse the retarget and insert a fresh row instead. The default (neither flag) auto-escalates: full-hash the new file when the candidate already has a content_hash, and trust the fingerprint alone when it does not.

--fast and --strict are mutually exclusive.

Move re-identification workflow. After moving or reorganizing your backing library, run a normal musefs scan on the new locations. For each file not already in the store, the scanner looks up rows whose fingerprint matches and whose old path is gone, and retargets the unique match in place — its id, tags, and art are preserved. Move recovery only applies to rows that were fingerprinted before the move (rows scanned under --checksum=none have no fingerprint and cannot be retargeted until a later fingerprint-tier pass). Run scan after a move and ideally before any revalidaterevalidate only refreshes already tracked rows, so a moved file must be re-seeded before the maintenance pass can see it. Use revalidate --prune only when you are ready to drop missing rows.