plex-claude: Taming a 200-Book Audiobook Inbox with Agent Swarms
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:
- Loose files — single M4B/MP3/M4A with no metadata and a garbage filename
- Single-file folders — correctly structured but untagged
- 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 TitleTitle - Author→ need to guess from word countTitle (Author Name)→ parens with a person nameTitleAuthor.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:
| Field | M4B key | MP3 ID3 frame |
|---|---|---|
| Author | aART (Album Artist) | TPE2 |
| Narrator | ©ART | TPE1 |
| Title | ©alb + ©nam | TALB + TIT2 |
| Cover | covr | APIC |
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:
-
iTunes Search API — free, no key, returns audiobook artwork:
https://itunes.apple.com/search?term={title}+{author}&media=audiobookThe returned URLs are
100x100bb— swap to600x600bbfor hi-res. -
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