plex-claude: Taming a 200-Book Audiobook Inbox with Agent Swarms

claude-codeai-developmentagentsinfrastructureDocker

My audiobook Inbox on the NAS had gotten out of hand. 200+ files dumped in a flat folder — some were single .m4b files, some were 30-part MP3 folders, none of them had proper metadata, and Plex couldn’t make sense of any of it.

The solution: plex-claude, a Python pipeline that runs as a Docker container (or a Claude skill), processes everything in the Inbox, and leaves behind a clean, correctly-tagged library.

The Problem Space

The Inbox looked like this:

A Hologram for the KingDave Eggers.m4b
Alan Taylor - The Internal Enemy Slavery and War in Virginia, 1772-1832 (Unabridged)/ (folder with 28 MP3s)
{B0DJC1T9VG} Agatha Christie - The Mysterious Affair at Styles (Full Cast)/
HowToEatWellWithADHD-RK.m4b
...

Three categories of chaos:

  1. Loose files — single M4B/MP3/M4A with no metadata and a garbage filename
  2. Single-file folders — correctly structured but untagged
  3. Multi-part folders — 10-30 MP3s that need to be combined and converted

And Plex won’t show Author, Title, Year, or Genre properly without embedded tags.

What plex-claude Does

Inbox scan

Loose file? → lookup metadata → tag → move to Genre/Author-Title/
Folder?
  ├─ 1 file → tag → move
  └─ N files → ffmpeg concat → single M4B → tag → move

                               iTunes cover art (or OpenLibrary fallback)

Each book ends up at Archieve Audiobooks/Genre/Author - Title/Author - Title.m4b with full metadata embedded.

The Hard Parts

Filename Parsing

200 filenames, zero consistency. Some patterns I had to handle:

  • Author - Title → Author then Title
  • Title - Author → need to guess from word count
  • Title (Author Name) → parens with a person name
  • TitleAuthor.m4b → no separator at all
  • {ASIN} Author - Title (Full Cast) [MP3] → noise tokens everywhere

The parser strips noise tokens first ({ASIN}, [MP3], (Unabridged)), then tries to split on - and guess which side is the author based on word count. Gets it right ~85% of the time — the rest Plex can fix manually.

Genre Mapping

Google Books returns categories like "Social Science / Ethnic Studies / African American Studies". I need to map that to one of 40+ existing genre folders on the NAS.

Built a keyword rule table — 40 rules, checked in order, first match wins:

_RULES = [
    (["warhammer", "40k", "space marine"], "Warhammer 40K"),
    (["science fiction", "cyberpunk", "dystopia"], "Science Fiction"),
    (["african american", "jim crow", "slavery"], "African American and Race Studies"),
    ...
    # fallthrough
    "General Nonfiction"
]

Works well enough. The hard cases (is “Octavia Butler” sci-fi or African American lit?) will sometimes land wrong, but they’re easy to manually fix once they’re tagged.

ffmpeg Concat

Multi-part MP3s need to be joined cleanly. The concat demuxer requires a file list:

# files.txt
file '/path/to/001.mp3'
file '/path/to/002.mp3'
...

ffmpeg -f concat -safe 0 -i files.txt -c:a aac -b:a 64k output.m4b

For M4B parts (already AAC), stream copy works and is instant:

ffmpeg -f concat -safe 0 -i files.txt -c copy output.m4b

Mixed format? Re-encode everything to AAC. Slow, but rare.

Plex Tag Conventions

This tripped me up. The correct tags for Plex to show Author properly:

FieldM4B keyMP3 ID3 frame
AuthoraART (Album Artist)TPE2
Narrator©ARTTPE1
Title©alb + ©namTALB + TIT2
CovercovrAPIC

Using ©ART for author (which seems obvious) actually maps to “Narrator” in Plex’s audiobook agent. Took a few failed tests to figure that out.

Cover Art

Two sources, tried in order:

  1. iTunes Search API — free, no key, returns audiobook artwork:

    https://itunes.apple.com/search?term={title}+{author}&media=audiobook

    The returned URLs are 100x100bb — swap to 600x600bb for hi-res.

  2. OpenLibrary Covers API — fallback when iTunes has no match:

    https://covers.openlibrary.org/b/isbn/{isbn}-L.jpg

Hit rate is around 70-75% — older or obscure books often have nothing.

Agent Swarm Build

The whole project was built using parallel agent swarms:

  • Research agent — Plex naming conventions, iTunes API, ffmpeg commands, mutagen tag keys
  • Scaffold agent — project structure, Dockerfile, docker-compose, config.yml
  • Module agents (2 in parallel) — classifier + cover_art, combiner + tagger + mover
  • Verify + security agent — checked imports, shell injection, path traversal, added dry-run mode

Total wall time to first working code: about 12 minutes. The agents occasionally stepped on each other (one overwrote my tagger with the old version), but the coordination overhead was minimal.

Running It

# Direct
cd projects/plex-claude
python3 -m src.pipeline

# Docker (one button)
docker compose up --build

# Claude skill
/plex-inbox

# Dry run (preview only)
DRY_RUN=1 python3 -m src.pipeline

Outputs results.json with per-file status, new paths, and what metadata was found.

Stack

  • Python 3.12, mutagen, requests, pyyaml
  • ffmpeg for audio processing
  • Google Books API + OpenLibrary for metadata
  • iTunes Search API for cover art
  • Docker for containerized runs