CLI that scans a music library and lists or deletes tracks that match your rules: DRM-locked files, low bitrates, and other quality bars. Point it at a file or folder; filters decide what matches.
Built for bulk library cleanup. Header and tag inspection only (via mutagen); it does not decode audio or strip DRM.
- What it does
- Quick Start
- Local development (git checkout)
- Filters
- Examples
- Security Notice
- Supported formats
- Shell completions
- Man page
- Development
- Changelog
- License
- Scan files or trees. Recursively finds common audio extensions under the paths you pass.
- Filter by DRM. Best-effort detection for Windows Media DRM (WMA/ASF encryption objects) and common FairPlay signals (
.m4p, MP4 protection atoms). - Filter by quality. Bitrate caps for lossy codecs (e.g. MP3 under 192 kbps), plus sample rate and bit depth for lossless/PCM.
- List or delete. Default action lists matches.
--deleteremoves them; combine with--dry-runto preview. - Reacquisition report.
--report FILEwrites CSV or JSONL of every match with path, why it matched, DRM label, bitrate, and action (matched/would_delete/deleted/delete_failed) so you can track what to re-rip or re-buy. - Terminal UI. On an interactive terminal: discovery spinner, scan progress bar, results table, and a summary panel. Piping,
--json,--plain, or-qstay script-friendly (paths or JSON only). - JSON output.
--jsonemits one object per match on stdout for scripting. - No phoning home. No analytics or metrics collection; this tool does not call home in any way.
Once published to PyPI:
uvx sanitune ~/Music --max-bitrate 192 --format mp3While hacking on this repo, do not rely on plain uvx --from .. That command builds a wheel and caches it; source edits will not show up until the project version bumps or the uv cache is cleared. Symptom: old UX or missing features after you just changed the code.
Use one of these instead (both always run the tree on disk):
uv sync
uv run sanitune ~/Music --max-bitrate 192 --format mp3 -v
# same idea via uvx + editable install
uvx --with-editable . --from . sanitune ~/Music --drmConfirm you are on the code you think you are:
uv run sanitune --version
uv run python -c "import sanitune.display as d; print(d.__file__)"If you specifically want to test the packaged (non-editable) install:
# after bumping version in pyproject.toml:
uvx --from . --refresh sanitune --version
# nuclear option:
uv cache clean
uvx --from . --refresh sanitune ~/Music --drm| Flag | Meaning |
|---|---|
--drm / --no-drm |
Match only DRM-locked files, or only non-DRM |
--format mp3,m4a,... |
Restrict to logical formats |
--max-bitrate KBPS |
Lossy files at or below this bitrate |
--min-bitrate KBPS |
Lossy files at or above this bitrate |
--max-sample-rate HZ |
Sample rate at or below |
--min-sample-rate HZ |
Sample rate at or above |
--max-bit-depth BITS |
Bit depth at or below |
--min-bit-depth BITS |
Bit depth at or above |
--lossy / --lossless |
Codec family |
Filters combine with AND. Bitrate rules apply to lossy streams only; FLAC and friends are not matched by --max-bitrate alone.
# List MP3s at or under 192 kbps
uvx sanitune ~/Music --format mp3 --max-bitrate 192
# Find DRM-locked files (WMA FairPlay-era libraries, etc.)
uvx sanitune ~/Music --drm -v
# Preview deletes
uvx sanitune ~/Music --format mp3 --max-bitrate 128 --delete --dry-run
# Delete and keep a spreadsheet of what went away (and why)
uv run sanitune ~/Music --drm --delete --report ~/Music/sanitune-deleted.csv
# Dry-run with a JSONL audit trail before you commit
uv run sanitune ~/Music --max-bitrate 128 --format mp3 --delete --dry-run \
--report ./would-delete.jsonl
# Scripting (stdout)
uvx sanitune ~/Music --drm --json
# Force plain text (CI, logs, no rich UI)
uvx sanitune ~/Music --max-bitrate 192 --plainCSV/JSONL fields include: timestamp, action, path, format, codec, bitrate_kbps, sample_rate, bit_depth, drm, drm_label, drm_reason, reasons (why the filters matched), error, sanitune_version. Format is taken from the file extension (.csv / .jsonl) or --report-format.
Interactive runs show a progress bar while probing and a results table (path, format, bitrate, DRM). Use -v for a Why column. --delete requires at least one filter (or --force) so a bare delete cannot wipe a tree by accident.
- Deletes are permanent. There is no trash integration. Prefer
--delete --dry-runfirst. - DRM detection is heuristic. Unknown protection schemes may be missed; see SECURITY.md.
MP3, M4A/AAC/ALAC (MP4), WMA/ASF, FLAC, WAV, AIFF, Ogg Vorbis, Opus, WavPack, APE, Musepack, and other types mutagen recognizes. Directory walks skip video containers (.mkv, .avi, …) unless --include-video.
Early (0.2). Useful for library cleanup; treat DRM results as best-effort and review deletes before running without --dry-run.
# bash
eval "$(sanitune --print-completion bash)"
# zsh
eval "$(sanitune --print-completion zsh)"
# fish
sanitune --print-completion fish | source
# or install permanently:
sanitune --print-completion fish > ~/.config/fish/completions/sanitune.fishAdd the eval line to your shell rc if you want it every session.
# view without installing
sanitune --print-man | man -l -
# install for the local user (example)
mkdir -p ~/.local/share/man/man1
sanitune --print-man > ~/.local/share/man/man1/sanitune.1
# ensure ~/.local/share/man is on MANPATH, then:
man sanituneuv sync --group dev
uv run pytest -v # enforces 100% coverage of the sanitune package
uv run ruff check .
uv run ruff format --check .See CONTRIBUTING.md for the workflow.
See CHANGELOG.md for release history.
MIT; see LICENSE. PRs and constructive feedback are welcome.
Runtime dependency mutagen is GPL-2.0-or-later; redistributors of binary bundles that include mutagen should account for that license on the combined work.
