Skip to content

Repository files navigation

Aède — Archival Music Library Manager

CI Release Deploy to GitHub Pages Dependency security

Site: GitHub Pages Docs: Manual Latest release License: MPL-2.0

A digital sanctuary for serious music collectors, archivists, and audio curators.

Aède is a high-precision, non-destructive local music library manager and cataloging system written in Rust. Designed with an uncompromising commitment to archival integrity, Aède treats your master music collection as a sanctuary: it reads metadata, verifies audio container integrity, indexes complex credit graphs, and generates derivative assets—without ever writing a single byte back into your original audio files.

Tip

An aède (Greek ἀοιδός, aoidos) was the poet-singer of archaic Greece: he held the whole repertoire in memory and performed it. Keeping and playing, in one word — which is exactly what this program is for.


🏛️ Design Philosophy & Core Principles

  1. Vault Sanctity (Strict Non-Destructive Read-Only Storage)
    Aède never mutates, re-tags, or re-organizes the files inside your watched music directories. Tags and file names inside your library remain untouched. All annotations, user tags, play counts, manual merges, and query collections reside safely in a separate local state store (user.json).
  2. Bespoke Forensic Parsers
    The primary audio containers (FLAC, MP3, MP4/ALAC, Ogg Vorbis/Opus, WAV, AIFF) are parsed by native Rust code implemented directly from container format specifications. Every parser guarantees zero unwrap() calls and zero direct memory indexing—a corrupted or violently truncated file produces an explicit diagnostic error rather than a panic.
  3. Sample-Accurate Precision
    General-purpose tagging libraries frequently ignore crucial playback metadata. Aède extracts LAME encoder delay and padding, ALAC magic cookies, and Opus pre-skip samples, ensuring the exact foundation required for sample-accurate, gapless audio playback.
  4. Empirical & Deterministic Operations
    Aède shuns silent heuristics and hidden fallbacks. A query against a non-existent genre returns an explicit error rather than a deceptively empty list. Destination filesystems during transfers (aede copy) are probed empirically by writing invisible test files rather than relying on brittle OS lookup tables.
  5. Separation of Fact and Inference
    Container integrity checks (aede check) verify mathematical frame and page checksums ($CRC\text{-}8$, $CRC\text{-}16$, $CRC\text{-}32$). FlacCompagnon's acoustic analysis (aede analyze or aede import) measures decoded audio. Aède keeps container facts separate from acoustic inferences, preserving data provenance across all commands.

🛠️ System Architecture & Audio Parsers

Account identities, roles and credentials live in the standalone aede-accounts crate. aede-core owns the music catalog, personal data and protected file persistence; aede-server exposes them through authenticated HTTP and audio routes. The separate aede-dsp library provides shared processing for decoded audio samples.

Aède uses a two-tier parsing architecture. Mainstream, high-fidelity containers are parsed natively by custom, zero-panic Rust engines. Niche and legacy archival formats fall back gracefully to the audited lofty crate.

Container Codecs Tag Standards Duration Source Parser Tier
FLAC FLAC Vorbis Comment, ID3v2 Header STREAMINFO block Native (aede-core)
MP3 MPEG 1/2/2.5 Layers I–III ID3v2.2/2.3/2.4, ID3v1 Xing / VBRI / CBR calculation Native (aede-core)
MP4 / M4A ALAC, AAC iTunes Atoms, Freeform ---- mvhd / mdhd atom Native (aede-core)
Ogg Vorbis, Opus Vorbis Comment Granule position Native (aede-core)
WAV PCM LIST/INFO chunk, id3 chunk fmt + data chunk sizes Native (aede-core)
AIFF / AIFC PCM NAME/AUTH, ID3 chunk COMM chunk Native (aede-core)
AAC AAC ID3v2, ID3v1 ADTS frame headers Fallback (lofty)
WavPack WavPack APEv2, ID3v1 Block headers Fallback (lofty)
Monkey's Audio APE APEv2, ID3v1 Descriptor headers Fallback (lofty)
Musepack Musepack SV7/SV8 APEv2, ID3v1 Stream headers Fallback (lofty)
Speex Speex Vorbis Comment Granule position Fallback (lofty)

📦 Installation & System Dependencies

Prerequisites

  • Rust Toolchain: Rust compiler (1.89+) and cargo, including rustfmt and clippy.
  • Python 3.9+: Standard library only, for the published-release build helper.
  • FFmpeg (Optional, Recommended): Required for on-the-fly transcoding (aede copy --compress) and acoustic spectrogram generation (aede spectrum).

System Dependencies

macOS (Homebrew)

brew install rust ffmpeg

Debian / Ubuntu

sudo apt update && sudo apt install -y build-essential pkg-config libssl-dev libasound2-dev ffmpeg

Arch Linux

sudo pacman -S base-devel rust alsa-lib ffmpeg

Downloadable archives are prepared as drafts by the Release workflow. See local CI and release preparation for Rust 1.89 checks, GitHub logs and the publication procedure. The v0.4.0 release notes provide the draft highlights. Previous draft highlights remain available in the v0.3.0 notes.

Building & Installing from Source

# Clone the repository
git clone https://github.com/craft-and-code/aede.git
cd aede

# Select the latest published FlacCompagnon Core, verify and build Aede
bash tools/build.sh

# Install the verified binary using the same locked dependencies
cargo install --path crates/aede-cli --locked --offline

tools/build.sh queries FlacCompagnon's latest published stable GitHub release, updates the shared flaccompagnon-core tag and its exact revision in Cargo.lock, fetches dependencies, then runs Aède's full verification and release build. It requires internet access and stops if release discovery or dependency resolution fails; it never silently falls back to an older release. Draft releases and prereleases are excluded. The selected version is printed before compilation.

To update just the dependency, run python3 tools/update-flaccompagnon.py. Keep the resulting Cargo.toml and Cargo.lock changes together. Ordinary cargo build --release --locked --offline and tools/check.sh keep using that recorded revision without discovering a newer release. CI and release archives also retain their checked-in lockfile.


🚀 Quick Start Guide

# 1. Register your music directories (watched roots)
aede roots ~/Music/FLAC /Volumes/AudioArchive

# 2. Perform an initial library scan
aede scan

# 3. Perform a container integrity audit (detect bit rot)
aede check

# 4. Search your collection using relational queries
aede query "artist:Coltrane year:1959..1965 lossless:true"

# 5. Export a curated selection for a portable player with MP3 conversion
aede copy /Volumes/DAP --query "loved rating:>=4" --compress mp3 --quality V0

# 6. Check library health and metadata anomalies
aede doctor

📋 Complete Technical Command Reference

Core Vault & Catalog Management

Command Arguments Key Options Description
aede roots [paths...] --exclude <path>, --remove, --no-scan Display, add, or exclude watched storage directories.
aede scan [path] --full, --dry-run, --json Traverses roots to index audio files, tags, and structure.
aede serve None --bind <IP>, --port <N>, TLS options Serves the catalog/audio API, optional authenticated HTTPS, and coordinates Unix CLI writes.
aede accounts list, init, create, password, role, rename, enable, disable, revoke --password-stdin, --json Manages local accounts, roles and session revocation; preserves existing personal ownership.
aede cancel <task-id> None Requests cancellation of a delegated scan or fetch.
aede check [path] --full Audits frame/page checksums ($CRC\text{-}8$, $CRC\text{-}16$, $CRC\text{-}32$) for bit rot.
aede doctor None None Run a health check: metadata, duplicates, source conflicts and incomplete credits.
aede credits [album] --json, --limit, --offset, --all Show recording/work and exact-edition credit coverage by album and identify fetchable gaps.
aede credit None --add <scope> --artist=<name> --role=<role>, --exclude=<ID>, --undo=<ID> Add a manual credit or exclude/restore one sourced credit without changing tags.
aede review None --interactive, --accept=<ID>, --reject=<ID>, --undo=<ID>, --all Resolve uncertain source identities without rewriting tags.
aede stats None None Displays catalog metrics, audio quality distribution, and credit roles.
aede reset None --yes Removes the catalog, watched roots and exclusions; preserves independent stores.

Query, Search & Catalog Browsing

Command Arguments Key Options Description
aede query <expression> --m3u, --csv, --json Evaluates a relational search expression across the catalog graph.
aede search <term> --comments, --notes, --lyrics Search artists, albums, tracks, recordings, works, release groups and optional prose.
aede albums None --artist, --genre, --year, --compilations, --no-compilations, --limit, --offset, --all, --csv, --json List and filter album records with pagination support.
aede artists None --role <role>, --country <code>, --limit, --offset, --all, --csv, --json List artists, filter by credit role, or map by geographic origin.
aede genres [name] --m3u, --csv, --json Browse music genres or export tracks matching a specific genre.
aede labels [name] --m3u, --csv, --json Survey record imprints and catalog releases.
aede artist <name|MBID> --with <artist>, --members, --role, --m3u, --csv Show a local artist or source-only contributor, with their credits and links.
aede album <title|MBID> --m3u, --csv Show one local edition, its tracks, credits, and graph links.
aede track <title> --artist, --lyrics, --limit Show a local placement, tags, credits, and technical facts.
aede recording <title|MBID|local:path> None Show a recorded performance, every local placement, and sourced works.
aede work <title|MBID> None Show a composition and its canonical or source-backed recordings.
aede release-group <title|MBID> None Show the album identity shared by every local edition.
aede label <name> --m3u, --csv Show a label catalog and its explicit, confirmed, proposed, or conflicting identity.
aede relations [name] --source, --tag, --limit, --json, --output=<file> List every local or sourced graph edge with its stable ID and provenance.
aede relation <ID> --text, --tag, --remove Inspect one edge or attach personal notes and labels to the relationship itself.
aede countries None --csv, --output=<file> Summarize artist geographical distributions sourced via MusicBrainz.
aede missing <artist> None Queries MusicBrainz to list missing official studio releases.

External Metadata & Artwork

Command Arguments Key Options Description
aede fetch [name|folder…] --summaries, --discography, --lyrics, --covers, --portraits, --logos, --labels, --credits, --fanart, --dry-run, --full Retrieves attributed metadata, rich credits and derivative assets without modifying audio files.
aede fetch [name|folder…] --fanart with --no-logo, --no-label-logo, --no-portrait, --no-background, --no-banner, --no-album-cover, --no-cdart Retrieves all useful Fanart.tv image families, minus any explicitly excluded families; 4K backgrounds win.
aede fetch [name|folder…] --covers --size <250|500|1200|original>, --images Retrieves missing Cover Art Archive images while leaving every existing local image untouched.

Fanart.tv access requires a free key in AEDE_FANARTTV_KEY. A complete run can then be tailored without enumerating what should remain enabled:

# Everything Fanart.tv offers for the local library
aede fetch --fanart

# Everything except portraits and disc artwork
aede fetch --fanart --no-portrait --no-cdart

# Restrict the same selection to one artist or one part of the shelf
aede fetch --fanart --no-banner "Miles Davis"
aede fetch --fanart --no-album-cover ~/Music/Jazz

Artist logos, portraits, banners, and backgrounds are written beside the artist's music when there is one shared folder, or under Aède's assets/ directory otherwise. Label logos live under assets/labels/<MusicBrainz ID>/; Fanart.tv album covers and cdART live in each album's artwork/ directory. Existing files are never overwritten.

Rich recording and work credits are fetched from recording identifiers already present in the library. Edition credits use exact release identifiers. No title is guessed:

aede credits
aede credits "Patient Number 9"
aede fetch --credits "Patient Number 9"
aede recording <MusicBrainz-recording-ID>
aede work <MusicBrainz-work-ID>
aede track "Patient Number 9" --json

Recording performers and production roles remain distinct from work composers, lyricists, writers and arrangers, and both remain distinct from credits attached to an album edition. Credited-as names, instruments, qualifiers, dates, ordering and MusicBrainz relationship identifiers keep their provenance in sources.json. The former --recordings option remains a compatibility alias for --credits.

To correct a sourced credit, find its exact ID with aede relations, exclude that claim, then add the corrected assertion at its proper scope:

aede relations --source=musicbrainz
aede credit --exclude=<credit-ID>
aede credit --add recording:<recording-ID> --artist="Jane Doe" --role=producer
aede credit --add release:<release-ID> --artist="Jane Doe" --role=engineer
aede credit --undo=<credit-ID>

--add also accepts work:<work-ID>, --artist-id=<MusicBrainz-artist-ID> and --instrument=<name>. These are manual, attributed assertions in Aède's data, not edits to the audio tags. Exclusion affects only the selected sourced relationship; the original evidence remains inspectable. Credits read from tags cannot be excluded this way because the tags are the local record.

Trusted MusicBrainz credits also make contributors absent from local artist tags navigable. aede artist <MusicBrainz artist ID> opens a source-backed card with their roles, local albums, recordings and works, without adding an artist to the tag-built catalog. aede search <name> finds these contributors in a separate section, and track, album, recording and work pages link to them by exact ID. If a local artist has the same name, the name keeps opening the local card; that card points to the separate sourced identity. Equal names with different IDs are never merged automatically. Untrusted claims remain visible evidence but do not create navigable contributors.

aede credits is read-only and measures recording/work and exact-edition lookup coverage separately. Its global summary counts canonical recordings once, even when a recording appears on several editions; each album counts its own distinct recordings. An album detail names a local file for each recording and distinguishes waiting (no completed MusicBrainz relationship lookup), empty (queried, but no recording or work credit returned), credited, untrusted (source evidence not accepted for the graph), and unidentified (no local recording MBID). The detail suggests a folder-scoped aede fetch --credits command for waiting recordings. An empty answer is not retried unless you explicitly use --full. Edition counts refer to local releases, require a completed trusted answer for the exact release ID, and remain independent when recordings are shared. Manual edition credits are shown separately and do not complete an external lookup. Existing recording JSON fields remain available alongside the added edition coverage.

For classical music, explicit MusicBrainz part-of-work relationships connect a movement to its containing work. aede work <work ID> shows the parent and the locally held parts in order; the parent work is navigable even when no file tags name it directly, and aede search finds it in a sourced section. aede album and aede track keep movement tags separate from sourced parent claims, and work:<parent ID> finds the local recordings of identified parts. Orchestra, choir, ensemble, soloist and conductor credits remain distinct from composition credits:

aede fetch --credits --full ~/Music/Classical/Beethoven
aede work 'MUSICBRAINZ_PARENT_WORK_ID'
aede query 'work:MUSICBRAINZ_PARENT_WORK_ID conductor:"Carlos Kleiber"'

Use --full for albums queried before work/part relationships were retained. No parent work is inferred from a matching title or movement number; without an explicit MusicBrainz relation, Aède shows and can search local work and movement tags without merging same-titled works.

Approximate attachments and exact source identities that conflict with local tags are reviewed explicitly:

aede review
aede review --interactive  # compare local and sourced facts, then decide one by one
aede review manson          # narrow the pending list by entity name
aede review --accept=<ID>   # allow this claim into navigation and queries
aede review --reject=<ID>   # retain it as evidence only
aede review --undo=<ID>

The decision is persistent and reversible. It is bound to the exact proposed identifier, never changes the original confidence, and never rewrites an audio file. aede doctor also reports unresolved identities, trusted recordings with incomplete credits, and contradictions between trusted sources.

The local graph links placements, recordings, works, editions, release groups, artists and labels in both directions. Guest appearances, compilation appearances, discography entries and writing or production contributions remain distinct relations. aede track, recording, work, album, artist and label expose the relevant paths, while aede search also finds recordings, works and release groups directly.

Entity pages end with copyable Continue commands for their related objects. Search results carry the command that opens each hit, and MusicBrainz identifiers are preferred wherever they remove title ambiguity. In particular, aede release-group <MBID> leads to every local edition and each edition can now be opened precisely with aede album <release-MBID>.

The whole graph can also be inspected independently of an entity page. Every edge has a stable selector and keeps its source and trust state:

aede relations "Andrew Watt"
aede relation <ID>
aede relation <ID> --text="Check the original booklet" --tag=dubious
aede relations --tag=dubious

The note belongs to that exact relationship, not to either endpoint. It lives in user.json, survives scans, and is kept waiting if the edge temporarily disappears; aede doctor --severity=info then makes it visible.

Transfer, Export & Derivative Generation

Command Arguments Key Options Description
aede copy <destination> --query, --collection, --compress <fmt>, --quality <q>, --extras <mode>, --verify, --safe-names, --dry-run, --threads Copies audio to external devices, preserving folder layouts and transcoding lossless files on the fly.
aede spectrum [path] --size <half|full>, --dry-run, --full, --threads Generates $900 \times 470$ or $1800 \times 940$ FFT acoustic spectrogram PNGs via FFmpeg.
aede playlist [path] --simple, --artists, --dry-run Writes relative .m3u playlist files directly into physical album directories.
aede collection <name> --query <expr>, --m3u, --csv, --json, --remove Defines or manages dynamic, self-refreshing smart playlists.
aede export None --csv, --tracks, --graph, --json, --output=<file> Export the catalog, a flat table, or every attributed graph layer in one JSON document.

Forensic Ingestion & Annotations

Command Arguments Key Options Description
aede import <path> --list, --pending, --forget, --source Ingests external FlacCompagnon JSON reports for spectral analysis.
aede analyze [folder…] --json, --threads <n> Runs FlacCompagnon's Rust analysis engine; --json saves one report per album folder.
aede note <entity> <name> --text <str>, --file <path>, --append, --remove Attaches plain-text or Markdown notes to tracks, albums, or artists.
aede rating <entity> <name> <1-5>, --remove Sets a personal star rating ($1\text{--}5$).
aede tag <entity> <name> <tag_name>, --remove Assigns or removes custom tags.
aede notes None --export, --import, --output=<file> Backs up or restores user annotations across systems.
aede rules None --export, --import, --output=<file> Lists or transports reproducible decisions without copying fetched prose or listening history.
aede backup <file.json> None Bundles catalog, conclusions, user annotations, remote sources and private account credentials.
aede restore <file.json> --yes Restores vault state from a versioned Aède backup bundle.

aede export --graph --output=graph.json is the complete local-first export: the derived catalog, attributed source evidence, confidence and review states, personal data, and a materialized relation list are kept side by side. For a smaller, replayable file containing only human decisions, use:

aede rules --export --output=rules.json
aede rules --import=rules.json

That bundle carries accepted or rejected identities, manual source records, artist filing rules, releases set aside from the missing-album report, saved queries, and relationship annotations. It does not copy artwork, biographies, listening history, or replace any audio tag.


🔍 Relational Query Syntax & Operators

Aède features a unified relational query grammar. Options compose logically via AND, OR, groupings, range queries, and structural scopes (album., artist., track.).

# Range query with Boolean logic and role matching
aede query "(artist:Ozzy OR artist:Dio) year:1980..1989 album.rating:>=4"

# Querying unplayed favorite tracks
aede query "loved played:0"

# Lossless files larger than 50 MB
aede query "lossless:true size:>50000000"

# Isolating credit contributions
aede query "composer:Rhoads mainartist:Ozzy"

# Traverse the canonical graph and credit relationships
aede query "work:\"War Pigs\" instrument:guitar"
aede query "guest:\"Zakk Wylde\" -compilationartist"

Relational fields include explicit tags, exact non-conflicting identities and source claims accepted through aede review. Pending and rejected matches remain evidence and are not promoted into query results.

Available Query Fields

Field Type Description Example Syntax
title Text Track title title:Interstellar
artist / albumartist Text Track performer or album artist artist:Coltrane
album Text Album title album:"Kind of Blue"
recording Text / ID Canonical recorded performance recording:"So What"
work Text / ID Composition realised by it work:"War Pigs"
releasegroup Text / ID Album identity across editions releasegroup:5c…
genre Text Musical genre genre:=Jazz, genre:Metal
label Text Record label imprint label:"Blue Note"
year Range / Number Release year year:1990..1999, year:1994
duration Duration Length in mm:ss or seconds duration:..4:00, duration:3:30..5:00
size Bytes File size in bytes size:>50000000
codec / format Text Codec name or container extension codec:flac, format:mp3
bitrate / samplerate Number Stream parameters bitrate:>=320, samplerate:96000
lossless Boolean Compression state lossless:true, -lossless
compilation Boolean Multi-artist compilation flag compilation:true
played Counter Play count played:0, played:>=10
lyrics Text Embedded or .lrc sidecar text lyrics:train
comment Text Container ID3/Vorbis comment tag comment:"vinyl rip"
Credits
composer, lyricist, producer, engineer, conductor, remixer Text Specific liner note credit role composer:Rhoads, producer:"Rick Rubin"
performing Text Anyone audible on the recording performing:"Zakk Wylde"
instrument Text Instrument or credit attribute instrument:guitar
guest / compilationartist Text Performing participation class guest:"Zakk Wylde"
contributor / with Text Non-performing credit / co-performer with:"Zakk Wylde"
Annotations
rating Numeric ($1\text{--}5$) User star rating rating:>=4, album.rating:5
loved Boolean Personal favorite status loved, -loved, track.loved
tag Text User assigned tag tag:vinyl, album.tag:audiophile
note Text Markdown note content note:remaster, artist.note:concert

🚚 Exporting & Transcoding

When transferring audio to portable devices or external drives, aede copy preserves folder layouts while handling non-standard target filesystems cleanly:

  1. Destination names: A safe exclusive probe checks character restrictions during a real transfer. Dry-run creates no probe. Portable case-insensitive collision handling distinguishes both files and folders; --safe-names or --raw-names fixes the adaptation policy.
  2. Lossless Transcoding Rule: When --compress is active, only lossless source files (FLAC, WAV, ALAC) are re-encoded. Existing lossy files (MP3, AAC, Opus) are copied untouched to prevent generation loss.
  3. Threading Optimization: Transcoding jobs run in parallel across all CPU cores. Plain uncompressed file transfers queue sequentially to prevent disk head thrashing on mechanical drives or SD cards.
# Copy loved tracks to a phone SD card, encoding FLACs to Opus @ 128k
aede copy /Volumes/Phone --query "loved" --compress opus --quality 128k

# Copy a saved collection with byte-for-byte read-back verification
aede copy /Volumes/Player --collection wishlist --verify --playlists

--verify-existing validates existing output content before resuming, preserving mismatches unless --replace is requested. Converted outputs are compared with a fresh encode using the current recipe. WAV preserves known source precision. --playlists writes paths matching converted/adapted outputs. Destination symlinks are refused, and new files are published from private exclusive temporary directories.

💾 Vault State & Storage Footprint

All metadata and state persist in a unified directory configured via $AEDE_HOME or defaulting to $XDG_DATA_HOME/aede (~/.local/share/aede/).

~/.local/share/aede/
├── catalog.json      # Scanned files, tags and the derived music graph
├── conclusions.json  # Integrity verdicts, fingerprints and imported analyses
├── user.json         # Irreplaceable annotations, collections and merges
└── sources.json      # Attributed source data and reversible review decisions

Storage Benchmarks

Historical measurements from before conclusions were split out. The current M2 synthetic benchmark uses a different generated catalog, so its sizes and timings are not directly comparable.

Tracks catalog.json Size Save Time Load Time Peak RAM
10,000 12.4 MB 0.79 s 0.41 s 181 MB
50,000 62.5 MB 3.88 s 2.17 s 897 MB
200,000 252.0 MB 16.37 s 13.42 s 3 586 MB

📖 Documentation

Complete guides to Aède's features and architecture:

Design & Architecture

Development & Engineering


⚖️ License & Archival Ethos

Aède is open-source software released under the Mozilla Public License v2.0.

Designed for collectors who view digital music not as disposable streams, but as an irreplaceable historical record requiring meticulous care, clear provenance, and persistent ownership.

About

A fast, local-first music library engine built in Rust — turning your music collection into a rich, searchable library of metadata, tags, relationships and more.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages