Music Classifier & Cleaner
Classifies a music library by artist genre (via MusicBrainz), reorganises folders by genre, and taps BPMs for local MP3 collections.
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.
A processed.csv is written in the output directory, tracking every downloaded and processed file (artist, title, BPM, filename, archive ID). On subsequent runs, files already present in the CSV are skipped, avoiding duplicate downloads.
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] |
Play |
[Pause] |
Stop |
[Restart] |
Restart track |
[next] |
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 (125 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 # 30 tests for audio, bpm_organizer, csv_store (125 total)
Metadata
Release files for music-classifier-cleaner 0.4.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.4.0.tar.gz | 19.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| music_classifier_cleaner-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 41.1 kB
Release files / music_classifier_cleaner-0.4.0.tar.gz
| Download URL | music_classifier_cleaner-0.4.0.tar.gz |
|---|---|
| Size | 19.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5d5d9f779cf8131cf6bbcfd2fada93ccb2653af3edfb620655c9e30d02a59635
|
|
BLAKE2b-256 checksum How to use checksums |
a1b7dd8cabf3380ec5b2e72c520f64eb75f42a3dbc91578775b6506363fadebd
|
| 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.4.0-py3-none-any.whl
| Download URL | music_classifier_cleaner-0.4.0-py3-none-any.whl |
|---|---|
| Size | 21.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a187a74b908a6a1668c114e569078338bd1009c8f199a572c9d39ae44186ff14
|
|
BLAKE2b-256 checksum How to use checksums |
9726eae111fddd20f408558d2f08ca5468ad44c04ea64870506c8328c8e6c1bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|