Skip to main content

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-tensorflow pin in pyproject.toml for why)
  • uv
  • ffmpeg on PATH — 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

Release files for libavalon 0.0.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for libavalon 0.0.6
File Size Uploaded
libavalon-0.0.6.tar.gz 67.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for libavalon 0.0.6
File Interpreter ABI Platform
libavalon-0.0.6-py3-none-any.whl Python 3 none any Details

Total release size: 99.9 kB

Release files / libavalon-0.0.6.tar.gz

Download URL libavalon-0.0.6.tar.gz
Size 67.2 kB
Tags Source
SHA-256 checksum
How to use checksums
fec2f5f469038dd712402654f98e64e5b6b9ddf89e960cd258609c70905291d4
BLAKE2b-256 checksum
How to use checksums
57a60aa3e84e46e69e7672ac322ef6a98e454f878d70df264e91b69d062c950c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.10

Release files / libavalon-0.0.6-py3-none-any.whl

Download URL libavalon-0.0.6-py3-none-any.whl
Size 32.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
345c2fc1cf666ace8e1d61448321973eddce7d65145a2d69499054c90329d3f6
BLAKE2b-256 checksum
How to use checksums
bc719f1ac5c74e949666bb9c1c25c44d36eba440a625e6eed9d02ed747e2164d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.10

Release history Release notifications | RSS feed

This release

0.0.6 This release

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

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