Skip to main content

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:

  1. Deduplicates similar folder names using fuzzy matching (e.g. "Greenday" → "Green Day")
  2. Renames the folder to the canonical name, merging if the target already exists
  3. Updates the artist ID3 tag in every MP3 inside the folder to match the canonical name
  4. Queries MusicBrainz for the artist's genre tags
  5. Classifies the tags into one of the predefined genre buckets via keyword matching
  6. 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.csv in 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 via shutil.rmtree
  • explore — 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:

  1. Reads the artist from the file's metadata (EasyID3 for MP3, FLAC Vorbis comments for FLAC)
  2. Looks up the artist on MusicBrainz — fetches genre tags and detects language from tags
  3. 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)
  4. Writes tags — up to 3 genre tags and an ISO 639-2 language code
  5. 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)

Source distribution for music-classifier-cleaner 0.3.0
File Size Uploaded
music_classifier_cleaner-0.3.0.tar.gz 19.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for music-classifier-cleaner 0.3.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

This release

0.3.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page