@wazolab/music-tagger (0.1.0)

Published 2026-11-04 16:50:17 +00:00 by wazolab

Installation

@wazolab:registry=
npm config set @wazolab:registry=
npm install @wazolab/music-tagger@0.1.0
"@wazolab/music-tagger": "0.1.0"

About this package

music-tagger

A music tagging pipeline: non-destructively copies your library, converts everything to MP3 320 kbps, removes duplicates, normalizes filenames and ID3 tags, fetches cover art, detects genre with ML models, and prepares files for Rekordbox analysis.

Requirements

  • Node.js >= 20
  • Python >= 3.10 + pip (for ot-rename, ot-artwork, ot-genre, ot-energy)
  • ffmpeg + ffprobe (for ot-convert) — sudo pacman -S ffmpeg

Install

pnpm add -g @wazolab/music-tagger

Or run directly without installing:

pnpm dlx @wazolab/music-tagger <source-dir>

Install Python dependencies

make install
# or manually:
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

Workflow

Step 1 — Copy source files (non-destructive)

ot-batch ~/Music/incoming
# or:
make copy SRC=~/Music/incoming OUT=~/Music/analysed-tracks

Files are copied to ./analysed-tracks. Source is never touched.

ot-batch <source-dir> [options]

Arguments:
  source-dir             source music folder (read-only, never modified)

Options:
  -o, --output <dir>     output folder (default: "./analysed-tracks")
  --dry-run              scan and show copy plan only — no writes
  -h, --help             display help

Step 2 — Format normalization (MP3 320 kbps)

ot-convert ./analysed-tracks
# or:
make convert OUT=./analysed-tracks

Accepts a folder (batch mode) or a single file (one-shot mode):

ot-convert track.flac      # converts one file to MP3 320
ot-convert ./analysed-tracks  # converts all files in folder

Enforces the project's target format (MP3 320 kbps):

  • Non-MP3 files (FLAC, WAV, AIFF, M4A…) are converted via ffmpeg -b:a 320k. Tags are preserved. Original file is removed after a successful conversion.
  • MP3 files below 310 kbps are listed as warnings — not re-encoded (upsampling a lossy file degrades quality).
  • MP3 files at 310 kbps or above are left untouched.
ot-convert <file|folder> [options]
  --dry-run              show plan without converting or deleting
  --workers <n>          parallel ffmpeg workers (default: 4, folder mode only)

The conversion logic is also available as a programmatic API:

import { convertFile } from '@wazolab/music-tagger';

const result = await convertFile('/path/to/track.flac');
// { outputPath: '/path/to/track.mp3', bitrate: 320000, changed: true }

convertFile(filePath) returns { outputPath, bitrate, changed }:

  • outputPath — path to the resulting MP3 file
  • bitrate — bitrate of the output file in bps (always 320000 for converted files, actual bitrate for MP3 pass-throughs)
  • changed — true if the file was transcoded, false if it was an MP3 and returned as-is

Step 3 — Duplicate detection

ot-dedup ./analysed-tracks
# or:
make dedup OUT=./analysed-tracks

Detects duplicate MP3 tracks at two confidence levels:

  • High confidence (auto-deleted by make dedup) — identical file hash (MD5). Bit-for-bit identical files only. Keeps the highest bitrate / largest copy.
  • Review hints — same normalised artist+title tags, or same filename slug. Could be different versions of the same track. Writes dedup-hints.json; surfaces in ot-rename review with a ⚠ Duplicate? warning and a [d] delete action.
ot-dedup <folder> [options]
  --dry-run              show plan without deleting or writing hints
  --auto                 delete all high-confidence duplicates without prompting
  --hints <file>         output file for rename hints (default: <folder>/dedup-hints.json)

Step 4 — Filename & tag normalization

make rename OUT=~/Music/analysed-tracks
# or step by step:
ot-rename scan ~/Music/analysed-tracks --musicbrainz
ot-rename review
ot-rename apply ~/Music/analysed-tracks

ot-rename normalizes filenames and ID3 tags in three stages:

Scan — parses every file into a rename plan:

  • Reads existing tags (artist, title, album, label, year)
  • Splits the filename into segments (Artist - Title, Label - Artist - Title, etc.)
  • Fuzzy-matches filename segments against existing tags to confirm or flag mismatches
  • Applies smart title case (preserves mixed-case words like McFly, uppercases acronyms like DJ/EP, handles dotted abbreviations like O.B.F.)
  • Detects special patterns: compilations (VA - Title-Artist), feat. in title, partial tags, label-name segments
  • Enriches ambiguous entries via MusicBrainz (enabled by default via make rename, ~1 s/track)
  • Outputs a rename-plan.json with auto (high confidence) and review (needs human input) entries

Review — interactive TTY session for review entries:

  • Shows the proposed filename, artist, title, album, label, year
  • Pre-fills empty fields from session memory (a segment resolved earlier in the session is reused automatically)
  • Retroactively corrects previously approved entries when a casing or spelling is fixed mid-session
  • Shows ⚠ Duplicate? warning for files flagged by ot-dedup; adds [d] delete action
  • Press [i] to display the full audit trail for the current file (rules tried, refinements applied, MusicBrainz result)
  • Accept (a), edit (e), skip (s), delete (d), or inspect (i) each file

Apply — writes the approved plan:

  • Renames files to Artist - Title.ext
  • Writes artist, title, album, label, year ID3 tags

Scan also writes rename-audit.ndjson alongside the plan — one JSON line per file recording every rule tried, refinement applied, and MusicBrainz result. See docs/rename-rules.md for the format.

ot-rename scan <folder> [options]
  --confidence <n>       fuzzy match threshold 0–100 (default: 85)
  --output <file>        plan file (default: rename-plan.json)
  --musicbrainz          enrich review entries via MusicBrainz API (~1s/track)

ot-rename review [options]
  --plan <file>          plan file (default: rename-plan.json)

ot-rename apply <folder> [options]
  --plan <file>          plan file (default: rename-plan.json)

Step 5 — Cover art

ot-artwork ./analysed-tracks
# or:
make artwork OUT=./analysed-tracks

Fetches and embeds cover art for MP3 files that don't already have one, using a cascade of free sources:

  1. Deezer API — free, no credentials, 500×500
  2. iTunes Search API — free, 500×500
  3. SoundCloud — via yt-dlp, 500×500
  4. YouTube — via yt-dlp, 16:9 thumbnail centre-cropped to 500×500

Art is embedded as APIC type 3 (Cover Front) at 500×500 JPEG. Files with existing artwork are skipped.

ot-artwork <folder> [options]
  --dry-run              fetch artwork but do not embed
  --skip-tagged          suppress output for already-tagged files

Step 6 — Genre detection

ot-genre ./analysed-tracks
# or:
make genre OUT=./analysed-tracks

Runs the Discogs-Effnet ML model on every file and writes the GENRE tag (FLAC) or TCON tag (MP3). Models are auto-downloaded to ~/.cache/ot-genre/ on first run (~100 MB total).

ot-genre <folder> [options]
  --dry-run              detect and print genres without writing tags
  --top <n>              number of subgenres to write (default: 3)
  --skip-tagged          skip files that already have a GENRE tag
  --workers <n>          parallel workers (default: 4)

Output format: top N subgenres joined with / — e.g. House / Tech House / Deep House

Step 7 — Energy detection (optional)

ot-energy ./analysed-tracks
# or:
make energy OUT=./analysed-tracks

Runs Essentia's emomusic arousal model on every file and writes the ENERGY tag (scale 1-10). Models are auto-downloaded to ~/.cache/ot-energy/ on first run (~30 MB total).

Not included in make start — run separately when needed.

ot-energy <folder> [options]
  --dry-run              detect and print energy without writing tags
  --skip-tagged          skip files that already have an ENERGY tag
  --workers <n>          parallel workers (default: 4)

Output format: integer 1-10 — e.g. 7 (low = chill/ambient, high = peak/hard)

Step 8 — BPM, key, and final tagging with Rekordbox

Import ./analysed-tracks into Rekordbox and let it analyze BPM and musical key. Once done, write the analysis back to the audio files so tags are portable:

File > Library > Write Tags to Files (or right-click selection → Write Tag Information to File)

This writes TBPM and TKEY directly into the audio files, making them readable by any other DJ software (Traktor, Serato, VirtualDJ…).

Why Rekordbox instead of ot-key/ot-bpm? Rekordbox's BPM engine is more accurate, and writing tags through Rekordbox also keeps your library in sync with its internal database.

Full pipeline

make start SRC=~/Music/incoming OUT=~/Music/analysed-tracks
# optionally:
make energy OUT=~/Music/analysed-tracks
# then Rekordbox: import + File > Library > Write Tags to Files

make start runs: copy → convert → dedup → rename (interactive) → artwork → genre

After step 8, every file carries portable tags: ARTIST, TITLE, ALBUM, LABEL, YEAR, GENRE, ENERGY, TBPM, TKEY.

Non-destructive guarantee

ot-batch uses fs.copyFile to write copies and never touches the source directory. The source path is only read with readdir and stat. Tests verify that source file checksums are identical before and after a run.

Re-runs are idempotent

Files already present in the output folder with the same size are skipped. Running ot-batch twice on the same source is safe and fast.

Supported formats

ot-batch accepts: .mp3 .flac .aiff .aif .wav .m4a .ogg (case-insensitive)

ot-convert converts all non-MP3 formats to MP3 320 kbps. All subsequent steps operate on MP3 only.

Without Rekordbox

If you don't use Rekordbox, run local BPM and key analysis instead:

BPM detection

ot-bpm ./analysed-tracks

Requires: paru -S bpm-tools + sudo pacman -S id3v2

ot-bpm <folder> [options]
  -m, --min <bpm>          minimum BPM (default: 60)
  -x, --max <bpm>          maximum BPM (default: 220)
  --dry-run                detect and print BPM without writing tags
  --concurrency <n>        parallel workers (default: 4)

Key detection

ot-key ./analysed-tracks

Requires: paru -S keyfinder-cli + sudo pacman -S ffmpeg

ot-key <folder> [options]
  -n, --notation <type>    camelot (default), openkey, musical
  --dry-run                detect and print keys without writing tags
  --concurrency <n>        parallel workers (default: 4)

Limitations

  • MusicBrainz rate-limits to 1 request/second — large collections take time. White labels and unreleased tracks are often not indexed.
  • ot-artwork depends on yt-dlp for SoundCloud and YouTube sources — install with pip install yt-dlp.
  • ot-genre models auto-download ~100 MB on first run. Strong coverage for electronic music; weaker for very niche or regional styles.
  • ot-energy models auto-download ~30 MB on first run.
  • Rekordbox's "Write Tags to Files" writes TBPM and TKEY — cue points and loops stay in Rekordbox's internal database and are not portable.

Development

pnpm test          # run tests
pnpm test:watch    # watch mode

Dependencies

Dependencies

ID Version
commander ^12.0.0
p-limit ^7.3.1

Development dependencies

ID Version
vitest ^2.0.0
Details
npm
2026-11-04 16:50:17 +00:00
15
2026-11-05 03:39:30 +00:00
latest
40 KiB
Assets (1)
Versions (1) View all
0.1.0 2026-11-04