Music Classifier & Cleaner
Classifies a music library by artist genre (via MusicBrainz), reorganises folders by genre, and taps BPMs for local MP3 collections.
Installation
# Install in editable mode (recommended for development)
pip install -e .
# Or with Poetry
poetry install
Commands
| Command | Purpose |
|---|---|
classify-organize |
Scan library, classify artists by genre via MusicBrainz, reorganise into genre folders |
scan-library |
Scan library for artists with few songs, output a CSV for manual review |
discover-from-library |
Process the review CSV — remove artist folders or explore top tracks via Deezer |
tag-library-genres |
Tag all audio files with top 3 MusicBrainz genres + language tag |
archive-download |
Download from archive.org, tap BPM interactively, and organize by BPM range |
music-tapper |
Tap BPM for local MP3s in a folder and organize by BPM range |
download-ytmusic |
Download audio from YouTube Music playlists as MP3 |
All commands are available system-wide after pip install -e .. Alternatively use poetry run <command>.
Configuration
| Variable | Required | Description |
|---|---|---|
LASTFM_API_KEY |
No | Last.fm API key — enables Last.fm fallback when MusicBrainz has no data for an artist. Get one at https://www.last.fm/api/account/create |
Set it in your shell profile or pass it inline:
export LASTFM_API_KEY=your_key_here
# or
LASTFM_API_KEY=your_key_here classify-organize /path
classify-organize — Classify and organise by genre
Scans the library root for artist folders (any top-level directory that isn't a genre folder), plus loose MP3s without a parent artist folder. For each artist:
- Deduplicates similar folder names using fuzzy matching (e.g.
"Greenday"→"Green Day") - Renames the folder to the canonical name, merging if the target already exists
- Updates the
artistID3 tag in every MP3 inside the folder to match the canonical name - Queries MusicBrainz for the artist's genre tags
- Classifies the tags into one of the predefined genre buckets via keyword matching
- Moves the entire artist folder into the matching genre subfolder (or
other/if nothing matched)
Last.fm fallback: If MusicBrainz has no data for an artist, the tool falls back to Last.fm tags. Set the LASTFM_API_KEY environment variable to enable this (see Configuration).
classify-organize /path/to/music/library
scan-library — Scan for sparse artists
Walks every genre subfolder, counts songs per artist, and writes a CSV of artists with few songs for manual review.
- Skips empty artist folders (prompts to delete them)
- Also checks loose MP3s in the library root (tagged as genre
"root") - Outputs
artists_to_review.csvin the library root with columns:artist,genre,song_count,path,decision
# Default threshold: 4 songs
scan-library /path/to/music/library
# Custom threshold
scan-library /path/to/music/library -t 3
After filling in the decision column (remove or explore), process the CSV with discover-from-library.
discover-from-library — Process review decisions
Reads the CSV produced by scan-library and acts on each row:
remove— deletes the entire artist folder viashutil.rmtreeexplore— looks up the artist on Deezer and prints their top 5 tracks with durations
discover-from-library /path/to/artists_to_review.csv
tag-library-genres — Tag library with genres from MusicBrainz
Recursively walks every .mp3 and .flac file in the library. For each file:
- Reads the artist from the file's metadata (EasyID3 for MP3, FLAC Vorbis comments for FLAC)
- Looks up the artist on MusicBrainz — fetches genre tags and detects language from tags
- Verifies the match — only tags the file if the MusicBrainz matched name matches the file's artist tag (case-insensitive). Skips if they differ (e.g. MusicBrainz returned a different artist)
- Writes tags — up to 3 genre tags and an ISO 639-2 language code
- Skips already-tagged files — checks existing tags before querying or writing
Caches MusicBrainz results per artist so files by the same artist only trigger one API lookup.
Last.fm fallback: If MusicBrainz has no genre data for an artist, the tool falls back to Last.fm tags. Set the LASTFM_API_KEY environment variable to enable this (see Configuration). Language detection is not available from Last.fm.
# Preview only (no files are modified)
tag-library-genres /path/to/music/library -n
# Tag all files
tag-library-genres /path/to/music/library
Tags written:
| Format | Genre | Language |
|---|---|---|
| MP3 | TCON frame — comma-separated string (e.g. "Swing, Jazz, Big Band") |
TLAN frame — ISO 639-2 code (e.g. "eng") |
| FLAC | Multiple GENRE Vorbis comments |
LANGUAGE Vorbis comment |
archive-download — Download from archive.org
Downloads songs from archive.org, measures BPM interactively, and organizes files into BPM-range folders. Files that already have a BPM tag or [BPM] in the filename are skipped.
archive-download \
--text "swing jazz" \
--artist "count basie" \
--year-from 1930 \
--year-to 1945 \
--out library/
music-tapper — Tap BPM for local MP3s
Scans a single folder for MP3 files and opens an interactive BPM tapping UI. Files that already have a BPM tag or [BPM] in the filename are skipped. Organizes processed files into BPM-range folders (tens: 60s/, 70s/, ..., 250s/).
music-tapper /path/to/mp3s
music-tapper /path/to/mp3s --out /path/to/output
BPM Tapping Controls:
| Key / Button | Action |
|---|---|
SPACE |
Tap BPM |
ENTER |
Save BPM |
ESC |
Exit |
[> ] |
Play |
| `[ | |
[R] |
Restart track |
[>>] |
Skip track |
Files are renamed to (BPM) Artist - Title.mp3 and moved into BPM-range folders under the output directory.
download-ytmusic — Download from YouTube Music playlists
Downloads audio from YouTube Music playlist URLs and converts to MP3 (320k) via ffmpeg.
download-ytmusic "https://music.youtube.com/playlist?list=PL..."
download-ytmusic "https://music.youtube.com/playlist?list=PL..." --out /path/to/dir
Reorganizing existing music by BPM
A helper script is included for bulk-reorganizing existing MP3s into BPM tens-range folders:
# Preview what would move
python scripts/reorganize_by_bpm.py /path/to/music --dry-run
# Actually move files
python scripts/reorganize_by_bpm.py /path/to/music
Reads BPM from the (BPM) prefix in filenames. Files without a BPM tag are left in place.
Running tests
# Run all tests (121 tests)
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=music_classifier --cov-report=term-missing -v
# Generate HTML coverage report
pytest tests/ --cov=music_classifier --cov-report=html
open htmlcov/index.html
Project structure
music-classifier-cleaner/
├── pyproject.toml # Poetry config + console_scripts entry points
├── README.md
├── scripts/
│ └── reorganize_by_bpm.py # Bulk reorganize MP3s into BPM tens-range folders
├── music_classifier/ # Installable Python package
│ ├── __init__.py
│ ├── utils.py # Genre classification, MusicBrainz, tagging
│ ├── cli.py # CLI entry point functions (argparse)
│ ├── audio.py # BPM read/write, sanitize, normalize filenames
│ ├── pipeline.py # Click CLI: archive-download + music-tapper
│ ├── archive.py # Archive.org search & download
│ ├── bpm_ui.py # Pygame BPM tapping UI
│ ├── bpm_organizer.py # BPM range folder mapping (tens: 60s, 70s, ...)
│ ├── csv_store.py # CSV tracking of processed files
│ └── youtube.py # YouTube Music playlist downloader
└── tests/
├── __init__.py
├── test_utils.py # 95 tests for utils.py
└── test_tapper.py # 26 tests for audio, bpm_organizer, csv_store (121 total)
Metadata
Release files for music-classifier-cleaner 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| music_classifier_cleaner-0.3.0.tar.gz | 19.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| music_classifier_cleaner-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.9 kB
Release files / music_classifier_cleaner-0.3.0.tar.gz
| Download URL | music_classifier_cleaner-0.3.0.tar.gz |
|---|---|
| Size | 19.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5ea2b0769675abb7c846fefa9a23ccaf3f838565b75604ceb4b2d52022adb56c
|
|
BLAKE2b-256 checksum How to use checksums |
0893bcd9b1297fe263d89f696262b257f3577e579811c1b997fb425f32c1f1b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / music_classifier_cleaner-0.3.0-py3-none-any.whl
| Download URL | music_classifier_cleaner-0.3.0-py3-none-any.whl |
|---|---|
| Size | 21.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
aedb8a95b25deddc31f5ca70077614b10a375b8a6c346286c014782b390a115c
|
|
BLAKE2b-256 checksum How to use checksums |
5654f1f2270881bc1a2350f3fea370cec0aa3731d5652988b38126e750706f13
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|