sanitune
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.
Contents
- What it does
- Quick Start
- Local development (git checkout)
- Filters
- Examples
- Security Notice
- Supported formats
- Shell completions
- Man page
- Development
- License
What it does
- 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.
Quick Start
Once published to PyPI:
uvx sanitune ~/Music --max-bitrate 192 --format mp3
Local development (git checkout)
While 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 --drm
Confirm 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
Filters
| 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.
Examples
# 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 --plain
Report columns
CSV/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.
Security Notice
- 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.
Supported formats
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.
Status
Early (0.2). Useful for library cleanup; treat DRM results as best-effort and review deletes before running without --dry-run.
Shell completions
# 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.fish
Add the eval line to your shell rc if you want it every session.
Man page
# 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 sanitune
Development
uv 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.
License
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.
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 sanitune-0.2.0.tar.gz.
File metadata
- Download URL: sanitune-0.2.0.tar.gz
- Upload date:
- Size: 511.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b87416414fae7bd375781f9412e83b39e7c11f529b1313e01564d0f109eeec04
|
|
| MD5 |
818f89e1232b7c7b68f03c6a85483df6
|
|
| BLAKE2b-256 |
124551abc30a868a8d7a4cca3c4ce16a102dbd82bc7219f1cc7c58d6b2ca4137
|
File details
Details for the file sanitune-0.2.0-py3-none-any.whl.
File metadata
- Download URL: sanitune-0.2.0-py3-none-any.whl
- Upload date:
- Size: 27.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca98a81cad4b711b7be120653a2f9115e7ef62ee589cfe21648517b5e001d674
|
|
| MD5 |
6b0611f0b79e1a9678cbf8706c0a097a
|
|
| BLAKE2b-256 |
17ffe7fcb4238cf528199c4349f48d4ba6abc8200fb54cf3128ea9ac61b75c6e
|