Essentia-powered genre & mood auto-tagger for the music-manager suite
Project description
essentia-autotagger
Essentia-powered genre & mood auto-tagger that writes clean, multi-value tags for Picard, Navidrome, Jellyfin, and friends.
essentia-autotagger analyses your library with Essentia's Discogs-EffNet
models and writes discrete, multi-value genre and mood tags, the way modern players expect them.
Features
- Player-correct tags. Each genre/mood is written as a separate value (repeated Vorbis
comments, null-separated ID3v2.4 frames, MP4 value lists, …), never a
"Rock; Hip Hop"blob, so Picard/Navidrome surface them as individual, browsable tags. - Non-destructive. Only the genre and mood fields are touched; existing comments and other
tags are never altered, and already-tagged files are skipped unless you pass
--overwrite. - Hierarchy-aware genres.
Rock---Alternative RockbecomesRock+Alternative Rock, so you can browse by broad or specific genre. - Wide format support. FLAC, MP3, Ogg/Opus, MP4/M4A, WMA, WavPack, APE, AIFF/WAV, DSF, and more (full mapping below).
- Tunable & scriptable. TOML config + CLI flags, exclude lists, a genre-normalization map,
output caps, parallel workers, a dry-run
existing → proposeddiff, and an optional JSON report.
Requirements
- Linux or WSL. The analysis backend (
essentia-tensorflow) ships Linux-only wheels and bundles TensorFlow. - conda / mamba. Used to pin Python 3.11 (the newest interpreter
essentia-tensorflowsupports) without touching your system Python. - ~23 MB of disk for the models, downloaded once.
Quick start
From a clone of this repository:
# 1. Create and activate the environment (Python 3.11 + dependencies)
conda env create -f environment.yml # or: mamba env create -f environment.yml
conda activate essentia-autotagger
# 2. Fetch the Essentia models once (~23 MB)
essentia-autotagger download-models # have ~/essentia_models already? add --from-dir ~/essentia_models
# 3. Preview what would be written; nothing is modified
essentia-autotagger /path/to/Album --dry-run
# 4. Tag for real
essentia-autotagger /path/to/Album
Models are stored per-user at ~/.local/share/essentia-autotagger/models.
Usage
# Whole library, parallel, replacing existing genre/mood tags
essentia-autotagger /srv/Music --workers 6 --overwrite
# A single file (e.g. driven by a file-watcher)
essentia-autotagger "/srv/Music/Artist/Album/03 Song.flac"
# Genres only, stricter threshold, at most 3 values, with a run log
essentia-autotagger /srv/Music --no-moods --genre-threshold 25 --max-genres 3 --log-dir ./logs
# Dry run that also exports predictions + confidences as JSON
essentia-autotagger /srv/Music/Album --dry-run --report ./preview.json
A dry run shows the change for every file so you can review before committing:
$ essentia-autotagger "Artist/Album/03 Song.flac" --dry-run
essentia-autotagger [DRY RUN] - models: ~/.local/share/essentia-autotagger/models
Found 1 audio file(s)
03 Song.flac
genres: (none) -> Hip Hop, Pop Rap
moods : (none) -> Energetic, Happy
Done. processed=1 written=0 skipped=0 errors=0
Run with no arguments for interactive prompts. essentia-autotagger --help lists every flag.
Key options
| Flag | Default | Description |
|---|---|---|
--dry-run |
off | Analyse and print existing → proposed; change nothing |
--overwrite |
off | Replace existing tags (otherwise already-tagged files are skipped) |
--top-genres N |
3 | How many genre predictions to consider |
--genre-threshold PCT |
15 | Genre confidence floor, in percent |
--max-genres N |
5 | Cap on genre values written (0 = no cap) |
--leaf-only |
off | Write only the specific genre, not its parent |
--mood-threshold PCT |
0.5 | Mood confidence floor, in percent |
--max-moods N |
3 | Cap on mood values written |
--workers N |
auto | Parallel workers (0 = half the cores, 1 = sequential) |
--report FILE |
none | Write per-file predictions + confidences as JSON |
--no-genres / --no-moods |
off | Disable one side of the analysis |
Configuration
Every default can be set in a TOML file (CLI flags still take precedence). Start from the example:
cp config.example.toml ~/.config/essentia-autotagger/config.toml
Search order: --config FILE → $ESSENTIA_AUTOTAGGER_CONFIG → ~/.config/essentia-autotagger/config.toml →
./config.toml.
A few options are config-only:
exclude_genres/exclude_moods: drop labels you never want (case-insensitive), e.g.exclude_genres = ["Non-Music", "Field Recording"].[tagger.genre_map]: canonicalize or merge Discogs labels into your own vocabulary, e.g."Hip Hop" = "Hip-Hop".
Tag mapping
Each format gets the field its players expect, written as multiple discrete values:
| Format | Genre field | Mood field |
|---|---|---|
| FLAC / Ogg / Opus | GENRE |
MOOD |
| MP3 / AIFF / WAV / DSF | TCON (ID3v2.4) |
TMOO (ID3v2.4) |
| M4A / MP4 / AAC | ©gen |
----:com.apple.iTunes:MOOD |
| WMA / ASF | WM/Genre |
WM/Mood |
| WavPack / APE / Musepack | Genre |
Mood |
How it works
- Recursively scan for audio; skip files already tagged (unless
--overwrite). - Decode to 16 kHz mono (capped at
--max-duration, default 5 min) and compute Discogs-EffNet embeddings. - Run the
genre_discogs400andmtg_jamendo_moodthemeclassification heads. - Expand genres parent+leaf, apply your map/excludes, and cap the count.
- Write multi-value tags with mutagen. Tag writing always
happens in the main process; only analysis is parallelized, so output is identical at any
--workers.
Development
conda env create -f environment.yml && conda activate essentia-autotagger
pytest # unit tests; ffmpeg synthesizes fixtures, no models needed
ruff check . && ruff format --check .
src/essentia_autotagger/
cli.py argparse entry point + interactive mode
config.py dataclass Config + TOML loader
paths.py model/config/log directory resolution (XDG)
models.py model download + single load_bundle()
analysis.py audio -> genre/mood predictions (shared by all paths)
formatting.py labels -> clean multi-value strings
tagging.py per-format multi-value read/write
discovery.py file scan + cheap existing-tag check
pipeline.py orchestration (sequential + multiprocessing)
logging_setup.py stdlib logging
Acknowledgements
Inspired by WB2024/Essentia-to-Metadata. Built on Essentia and its pretrained Discogs-EffNet models, with mutagen for tag I/O.
License
This project is released under the MIT License.
The Essentia models are downloaded from essentia.upf.edu and carry their own licenses: the Discogs and MTG-Jamendo models are CC BY-NC-SA 4.0 (non-commercial). Review them before commercial use or redistribution.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file essentia_autotagger-0.1.0.tar.gz.
File metadata
- Download URL: essentia_autotagger-0.1.0.tar.gz
- Upload date:
- Size: 28.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
afb0861a6d3648494d31fa9f462c964760c9cc17d809c0dd5ab48141eebeb68b
|
|
| MD5 |
89465acd87ebf24cd7a08a2a2297a82b
|
|
| BLAKE2b-256 |
f55a8cfa44efd6eb14819179d722eb60425124f57a69198638d80c1eb7251c3b
|
Provenance
The following attestation bundles were made for essentia_autotagger-0.1.0.tar.gz:
Publisher:
release.yml on cookiephone/essentia-autotagger
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
essentia_autotagger-0.1.0.tar.gz -
Subject digest:
afb0861a6d3648494d31fa9f462c964760c9cc17d809c0dd5ab48141eebeb68b - Sigstore transparency entry: 1704561379
- Sigstore integration time:
-
Permalink:
cookiephone/essentia-autotagger@7b8226ae79cb2d2503ad6253d4c0d40044d17781 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/cookiephone
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7b8226ae79cb2d2503ad6253d4c0d40044d17781 -
Trigger Event:
push
-
Statement type:
File details
Details for the file essentia_autotagger-0.1.0-py3-none-any.whl.
File metadata
- Download URL: essentia_autotagger-0.1.0-py3-none-any.whl
- Upload date:
- Size: 26.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
75da223db2c859b8971184553a7aca6efde369808a9df516ebc9e556e3fa1367
|
|
| MD5 |
e73f540cd14ff4427e91876e02d70253
|
|
| BLAKE2b-256 |
4fbf18997a26add773146caa4baa5ca3511a900d9f9e8000b8824c96bb50ac4f
|
Provenance
The following attestation bundles were made for essentia_autotagger-0.1.0-py3-none-any.whl:
Publisher:
release.yml on cookiephone/essentia-autotagger
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
essentia_autotagger-0.1.0-py3-none-any.whl -
Subject digest:
75da223db2c859b8971184553a7aca6efde369808a9df516ebc9e556e3fa1367 - Sigstore transparency entry: 1704561384
- Sigstore integration time:
-
Permalink:
cookiephone/essentia-autotagger@7b8226ae79cb2d2503ad6253d4c0d40044d17781 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/cookiephone
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7b8226ae79cb2d2503ad6253d4c0d40044d17781 -
Trigger Event:
push
-
Statement type: