Metadata-Version: 2.4
Name: sanitune
Version: 0.2.0
Summary: CLI to find or purge DRM-locked and low-quality music files
Project-URL: Homepage, https://github.com/nathan-v/sanitune
Project-URL: Repository, https://github.com/nathan-v/sanitune
Project-URL: Issues, https://github.com/nathan-v/sanitune/issues
Author: Nathan V
License: MIT
License-File: LICENSE
Keywords: audio,cli,drm,library,music,mutagen
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Requires-Dist: mutagen>=1.47
Requires-Dist: rich>=15.0.0
Description-Content-Type: text/markdown

# sanitune

[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/nathan-v/sanitune/blob/main/LICENSE)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![CI](https://github.com/nathan-v/sanitune/actions/workflows/ci.yml/badge.svg)](https://github.com/nathan-v/sanitune/actions/workflows/ci.yml)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/nathan-v/sanitune)](https://github.com/nathan-v/sanitune/releases)
[![GitHub last commit](https://img.shields.io/github/last-commit/nathan-v/sanitune)](https://github.com/nathan-v/sanitune/commits/main)

![sanitune logo](assets/sanitune-logo.png)

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](https://github.com/quodlibet/mutagen)); it does not decode audio or strip DRM.

## Contents

- [What it does](#what-it-does)
- [Quick Start](#quick-start)
- [Local development (git checkout)](#local-development-git-checkout)
- [Filters](#filters)
- [Examples](#examples)
- [Security Notice](#security-notice)
- [Supported formats](#supported-formats)
- [Shell completions](#shell-completions)
- [Man page](#man-page)
- [Development](#development)
- [License](#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. `--delete` removes them; combine with `--dry-run` to preview.
- **Reacquisition report.** `--report FILE` writes 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 `-q` stay script-friendly (paths or JSON only).
- **JSON output.** `--json` emits 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:

```bash
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):

```bash
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:

```bash
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:

```bash
# 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

```bash
# 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-run` first.
- **DRM detection is heuristic.** Unknown protection schemes may be missed; see [SECURITY.md](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
# 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

```bash
# 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

```bash
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](CONTRIBUTING.md) for the workflow.

## License

MIT; see [LICENSE](LICENSE). PRs and constructive feedback are welcome.

Runtime dependency [mutagen](https://github.com/quodlibet/mutagen) is GPL-2.0-or-later; redistributors of binary bundles that include mutagen should account for that license on the combined work.
