avalon
Analyzes, tags, and organizes a music library:
- BPM/key extraction, mood/genre/energy descriptors via Essentia
- ID3/Vorbis/MP4 tag normalization
- cover art, format conversion
Runs once over a folder or as a watching daemon. MusicBrainz/Discogs reconciliation is left to Picard.
Requirements
- Python 3.10–3.11 (see the
essentia-tensorflowpin inpyproject.tomlfor why) - uv
ffmpegonPATH—brew install ffmpeg/apt install ffmpeg
Install
git clone <repository-url> && cd avalon
uv sync
OR
pip install libavalon
First run downloads Essentia's models (~26.5MB) to ~/.cache/avalon/models/.
Usage
# tag in place
uv run avalon analyze ~/Music/Downloads --recursive
# reorganize into {artist}/{album}/{title}.{ext}
uv run avalon analyze ~/Music/Downloads --recursive --dest ~/Music/Library
# convert lossless sources, cap bit depth/sample rate (lossy sources untouched)
uv run avalon analyze ~/Music/Downloads --dest ~/Music/Library \
--convert-lossless-to aiff --max-bit-depth 16 --max-sample-rate 48000
# watch continuously, -v so you can see it working (scans on startup, then
# re-scans every --rescan-seconds to catch anything the OS didn't report)
uv run avalon watch ~/Music/Downloads --dest ~/Music/Library -v
# backfill a large library faster with 8 concurrent worker processes
uv run avalon analyze ~/Music/Downloads --recursive --dest ~/Music/Library --workers 8
# see what's actually in a file's tags
uv run avalon inspect ~/Music/Library/Artist/Album/01\ -\ Title.aiff
Full flag list: avalon analyze --help / avalon watch --help.
How it works
flowchart TD
src[source file]
src --> analyze[essentia analysis]
src --> conv{convert?}
conv -->|yes| ffmpeg
conv -->|no| copy[copy in place]
analyze --> write[write tags + art]
ffmpeg --> write
copy --> write
write --> out[output file]
Analysis runs against the original file, before any conversion. Canonical
fields (title/artist/album/genre/bpm/key) only fill in when missing —
nothing gets overwritten unless you pass --force-reanalyze.
--workers N runs analysis in N separate worker processes instead of one
at a time — each has its own Essentia/TensorFlow session, so results never
cross between files. Destination-path collisions (e.g. two files with
missing tags both falling back to the same Unknown Artist/Unknown Album
path) are still resolved from a single process before any work is handed
to a worker, so numbering stays correct under --workers too.
Watch mode
watch notices files two ways, and needs both. Filesystem events give it
low latency; a full rescan every --rescan-seconds (default 300) gives it
correctness. The rescan is not redundant — a recursive watch is really one
watch descriptor per subdirectory, added only after the observer sees the
parent appear, so a folder created and filled faster than that (dragging an
album in, an rsync, an unzip) can have its contents land before anything is
watching them. Those files produce no event at all, and event-driven-only
watching strands them silently and forever. Set --rescan-seconds 0 to turn
the sweep off.
Both commands keep a .avalon_state.json fingerprint index so re-runs skip
files that haven't changed. It lives in --dest for analyze and in the
first watched folder for watch — the two track different key sets (source
paths under the watched folder vs. under the library), and each save
rewrites the whole file from that process's in-memory copy, so two runs
sharing one state file will erase each other's entries. Use --state-dir if
you need to place it explicitly.
Tags
Two avalon-owned tags per file: a short headline (bpm:128;key:Am;camelot:8A; energy:0.71;genre:Techno, in COMM/DESCRIPTION/desc, configurable via
--headline-tag/--headline-format) and an extended tag with the full
descriptor roster (TXXX:AVALON_ANALYSIS / a Vorbis field / an MP4 atom).
MusicBrainz/Discogs/AcoustID reconciliation isn't handled by avalon — run Picard over the library separately for that.
Development
uv sync --extra test
uv run pytest
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 libavalon-0.0.6.tar.gz.
File metadata
- Download URL: libavalon-0.0.6.tar.gz
- Upload date:
- Size: 67.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fec2f5f469038dd712402654f98e64e5b6b9ddf89e960cd258609c70905291d4
|
|
| MD5 |
7c955e3cd1e9846b3f8b82c1109c671f
|
|
| BLAKE2b-256 |
57a60aa3e84e46e69e7672ac322ef6a98e454f878d70df264e91b69d062c950c
|
File details
Details for the file libavalon-0.0.6-py3-none-any.whl.
File metadata
- Download URL: libavalon-0.0.6-py3-none-any.whl
- Upload date:
- Size: 32.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
345c2fc1cf666ace8e1d61448321973eddce7d65145a2d69499054c90329d3f6
|
|
| MD5 |
9cce2eb9617d4e7226e0db92f56c2994
|
|
| BLAKE2b-256 |
bc719f1ac5c74e949666bb9c1c25c44d36eba440a625e6eed9d02ed747e2164d
|