What I wanted was to standardize my entire library using a single M4B as the audio file format. I also wanted the directory naming and structure to be exactly the same for all books with accurate metadata. So I created a workflow that works for me. I start by manually finding and downloading the books I want, then the sort script copies them into folders to be processed as needed:
Ebooks go to the Calibre import directory Single M4B files go to the import directory for beets Multi-M4B and all other audio files go to auto-m4b to be converted into a single M4B Auto-m4b output goes to the beets import directory Beets gets everything named using the standard method I want to use Audiobooks to ABS does the final touches and moves everything into Audiobookshelf
Automated audiobook processing pipeline for Unraid systems, integrating qBittorrent, Beets with Audible plugin, autom4b, and AudioBookshelf (ABS).
- Workflow Overview
- Unraid Docker Setup Guide
- Standard Linux Setup
- Complete Workflow Example
- Troubleshooting
- Requirements
- License
qBittorrent → 01_Download_Sort.sh → Beets (audible plugin) → 02_Audiobooks to ABS.sh → AudioBookshelf
↓
autom4b (for MP3/M4A conversion)
This pipeline automates the entire audiobook workflow from download to library:
- qBittorrent downloads audiobooks and triggers sorting script on completion
- 01_Download_Sort.sh sorts files by type into appropriate processing directories
- Beets with Audible plugin fetches metadata and organizes files
- 02_Audiobooks to ABS.sh performs final processing and moves to library
- AudioBookshelf serves the organized audiobook library
This section provides complete setup instructions for Unraid users using Docker containers. If you're running standard Linux, skip to Standard Linux Setup.
You'll install three Docker containers:
- autom4b - Converts audio files (MP3, M4A, FLAC) to M4B format
- Beets - Tags audiobooks with Audible metadata
- qBittorrent - Downloads audiobooks (if not already installed)
- Open Community Applications in Unraid
- Search for "autom4b"
- Install and configure:
- Input Directory:
/mnt/user/Media/Processing/autom4b_input - Output Directory:
/mnt/user/Media/Processing/beets_untagged
- Input Directory:
- Start the container
CRITICAL VERSION REQUIREMENT: The Audible plugin for Beets from linuxserver.io only supports up to version 2.3.0.
- Open Community Applications and search for "beets"
- Select the linuxserver/beets template
- Before installing, change the Repository field to:
lscr.io/linuxserver/beets:2.3.0 - Configure basic paths:
- Config Directory:
/mnt/user/appdata/beets - Music/Audiobooks Directory:
/mnt/user/Media/Processing/beets_tagged - Downloads:
/mnt/user/Media/Processing/beets_untagged
- Config Directory:
- Click "Apply" to install the container
- Start the container (creates initial config directory structure)
- Stop the container (we need to modify configs before running properly)
- Navigate to
/mnt/user/appdata/beetson your Unraid server - Backup the default config:
mv config.yaml config.yaml.stock
- Copy custom configuration files from this repository:
- Copy
beets-audible.config.yaml→/mnt/user/appdata/beets/ - Copy
custom-cont-init.d/directory →/mnt/user/appdata/beets/
- Copy
- Rename the custom config:
mv beets-audible.config.yaml config.yaml
- Edit
config.yamlto match your directory paths:- Update
directory:to your final library location - Verify paths match your Docker container mappings
- Save the file
- Update
This enables automatic installation of required Beets plugins on container startup.
- Edit the Beets container in Unraid's Docker UI
- Click "Add another Path, Port, Variable, Label or Device"
- Configure the new path:
- Config Type: Path
- Name:
custom-cont-init.d - Container Path:
/custom-cont-init.d - Host Path:
/mnt/user/appdata/beets/custom-cont-init.d/ - Access Mode: Read Only
- Click "Apply" to save
- Start the Beets container
- Check container logs to verify:
install-deps.shexecuted successfully- Plugins installed:
beets-audible,beets-copyartifacts3,beets[web]
- Open
01_Download_Sort.shin a text editor - Update configuration variables (lines 17-35):
INPUT_DIR="/mnt/user/Downloads/complete" LOG_DIR="/mnt/user/Downloads/complete/logs" FILETYPE_DIRS=( ["mp3"]="/mnt/user/Media/Processing/autom4b_input" ["m4a"]="/mnt/user/Media/Processing/autom4b_input" ["m4b"]="/mnt/user/Media/Processing/beets_untagged" ["epub"]="/mnt/user/Media/Processing/calibre_import" ) ENABLE_MULTI_M4B_REDIRECT=true MULTI_M4B_DEST_DIR="/mnt/user/Media/Processing/autom4b_input"
- Make executable:
chmod +x 01_Download_Sort.sh
- Open
02_Audiobooks to ABS.shin a text editor - Update configuration variables (lines 5-44):
INPUT_DIR="/mnt/user/Media/Processing/beets_tagged" MOVE_TARGET="/mnt/user/Media/Audiobooks" OVERWRITE_POLICY="newer" PUID=99 PGID=100
- Make executable:
chmod +x "02_Audiobooks to ABS.sh"
- Open qBittorrent settings
- Navigate to Downloads → Run external program on torrent completion
- Enable and add:
(Adjust path to where you stored the script)
/mnt/user/scripts/audiobook-processing/01_Download_Sort.sh "%F"
# Dry run test
./01_Download_Sort.sh --dry-run --verbose /path/to/test/audiobookReview output to ensure files would route correctly.
- Download a test audiobook via qBittorrent
- Verify file sorting:
- Check logs:
$LOG_DIR/download_sort_*.log - Verify files moved to autom4b input (if MP3/M4A) or beets_untagged (if M4B)
- Check logs:
- Wait for autom4b to convert (if applicable)
- Run Beets import:
docker exec -it beets beet import /downloads - Run post-processing:
./02_Audiobooks\ to\ ABS.sh
- Verify files in final library location
Your final directory structure should look like:
/mnt/user/Media/
├── Downloads/ # qBittorrent download location
├── Processing/
│ ├── autom4b_input/ # 01_Download_Sort.sh sends MP3/M4A here
│ ├── beets_untagged/ # autom4b outputs + single M4B files
│ └── beets_tagged/ # Beets outputs tagged audiobooks
└── Audiobooks/ # Final library (AudioBookshelf)
The following sections apply to standard Linux installations (non-Docker) or provide additional configuration details for both Unraid and Linux systems.
- In qBittorrent settings, go to Downloads → Run external program on torrent completion
- Add the following command:
/path/to/01_Download_Sort.sh "%F"%Fpasses the content path (file or folder) to the script- The script will automatically sort downloaded files by type
Create a category for audiobooks to organize downloads:
- Category name:
audiobooks - Save path:
/mnt/user/downloads/audiobooks
Automatically sorts downloaded files by type into appropriate processing directories when triggered by qBittorrent.
- MP3/M4A → autom4b directory for M4B conversion
- M4B → Beets untagged directory (single files) or configurable multi-M4B destination (multiple files)
- EPUB/MOBI/PDF → Calibre import directory
- Other audio formats (FLAC, OGG, WMA, etc.) → autom4b for conversion
When enabled, directories containing multiple M4B files are redirected to a separate destination (typically autom4b for processing). This prevents conflicts in the Beets tagging process which often expects single audiobook files per directory.
Edit lines 17-35 to customize directories:
INPUT_DIR="/path/to/downloads/complete"
LOG_DIR="/path/to/downloads/complete/logs"
# File type destination mapping
FILETYPE_DIRS=(
["mp3"]="/path/to/autom4b/input"
["m4a"]="/path/to/autom4b/input"
["m4b"]="/path/to/beets/untagged"
["epub"]="/path/to/calibre/import"
# ... additional mappings
)
# Multi-M4B handling configuration
ENABLE_MULTI_M4B_REDIRECT=true # Enable special handling for multiple M4B files
MULTI_M4B_DEST_DIR="/path/to/autom4b/input" # Where to send directories with multiple M4B files# Manual run
./01_Download_Sort.sh [options] [input_path]
# Options
--force # Force processing even if files were recently modified
--dry-run # Test mode - show what would happen without making changes
--verbose # Enable detailed logging- Logs stored in:
$LOG_DIR/ - Format:
download_sort_YYYY-MM-DD.log
# Install beets and audible plugin
pip install beets
pip install beets-audibleKey configuration sections:
directory: /path/to/audiobooks/library
library: /path/to/beets/musiclibrary.db
import:
move: yes
write: yes
plugins: audible copyartifacts edit fromfilename scrub web permissions
audible:
fetch_art: yes
fetch_chapters: yes
match_chapters: yes
paths:
audiobook:series: $series/$series {$series_position} - $title/$series {$series_position} - $title
audiobook:^series: $artist/$title/$title
copyartifacts:
extensions: .yml .yaml .jpg .jpeg .png .pdf .txt .nfo .cue .m3u# Import audiobooks from untagged directory
beet -c beets-audible.config.yaml import /path/to/beets/untagged
# Interactive import with manual search
beet -c beets-audible.config.yaml import -s /path/to/beets/untagged- Fetches metadata from Audible
- Organizes files into series/title structure
- Embeds metadata into M4B files
- Downloads cover art
- Creates chapter files
- Preserves sidecar files (metadata.yml, cover images, etc.)
Final processing after Beets tagging - renames sidecar files, fixes CUE files, and moves to AudioBookshelf library.
- Renames sidecar files to match audiobook filename
- Corrects CUE file references
- Synchronizes cover.jpg ↔ folder.jpg
- Moves processed files to final library location
- Cleans up empty directories
Edit lines 5-44 to customize:
INPUT_DIR="/path/to/beets/processed"
MOVE_TARGET="/path/to/audiobookshelf/library"
LOG_FILE="$INPUT_DIR/audiobook_processing.log"
OVERWRITE_POLICY="never" # Options: never, always, newer, larger# Run post-processing
./02_Audiobooks_to_ABS.sh
# Test mode (edit script to set DRY_RUN=true)
DRY_RUN=true ./02_Audiobooks_to_ABS.sh- Scans for audiobook files (M4B, MP3, M4A)
- Renames associated sidecar files to match audiobook
- Updates CUE file internal references
- Ensures both cover.jpg and folder.jpg exist
- Moves entire directory to AudioBookshelf library
- Sets proper ownership (99:100 for Unraid)
- In AudioBookshelf, add library pointing to:
/path/to/audiobookshelf/library - Enable folder watching for automatic imports
- Configure metadata preferences to use embedded tags
Final organized structure:
/path/to/audiobookshelf/library/
├── Author Name/
│ ├── Series Name/
│ │ ├── Series Name 01 - Book Title/
│ │ │ ├── Book Title - Author Name.m4b
│ │ │ ├── Book Title - Author Name.cue
│ │ │ ├── cover.jpg
│ │ │ ├── folder.jpg
│ │ │ └── metadata.yml
│ │ └── Series Name 02 - Second Book/
│ │ └── ...
│ └── Standalone Book/
│ └── Standalone Book - Author Name.m4b
└── Another Author/
└── ...
- Download: qBittorrent downloads audiobook torrent
- Sort: On completion, triggers
01_Download_Sort.sh- MP3 files → sent to autom4b for M4B conversion
- M4B files → sent to Beets untagged directory
- Tag: Manually run Beets to tag with Audible metadata
beet -c beets-audible.config.yaml import /path/to/beets/untagged
- Post-Process: Run
02_Audiobooks_to_ABS.shto finalize - Library: AudioBookshelf automatically imports from watched folder
- Download sort:
/path/to/downloads/complete/logs/ - Post-processing:
/path/to/beets/processed/audiobook_processing.log - Beets: Run with
-vflag for verbose output - Docker containers: Check Unraid Docker tab for container logs
Files not moving from downloads
- Check qBittorrent is calling script with correct path
- Verify script has execute permissions:
chmod +x 01_Download_Sort.sh - Run manually with
--verboseflag to see detailed output - For Unraid: Ensure qBittorrent has access to script location
Beets not finding matches
- Use interactive mode:
beet import -s - Try from filename:
beet import -f - Check Audible plugin is installed:
pip list | grep audible(Linux) ordocker exec beets pip list | grep audible(Unraid)
Sidecar files not renamed
- Check
OVERWRITE_POLICYsetting in02_Audiobooks_to_ABS.sh - Verify file permissions (should be 99:100 for Unraid)
- Run with
DRY_RUN=trueto test
AudioBookshelf not importing
- Verify library path matches
MOVE_TARGET - Check folder watching is enabled
- Ensure proper permissions on target directory
Beets plugins not installing
- Symptoms: Beets doesn't recognize
audibleplugin commands - Solutions:
- Check container logs for errors during startup
- Verify
custom-cont-init.dpath mapping is correct - Ensure
install-deps.shhas execute permissions - Manually install:
docker exec -it beets pip install beets-audible beets-copyartifacts3 beets[web]
Metadata not fetching from Audible
- Symptoms: Beets imports audiobooks but without Audible metadata
- Solutions:
- Verify Audible plugin installed:
docker exec beets beet version - Check
config.yamlhasaudiblein plugins list - Ensure audiobook files have reasonable filenames for matching
- Try manual fetch:
docker exec beets beet audible -f
- Verify Audible plugin installed:
Version compatibility issues
- Symptoms: Audible plugin errors after Beets update
- Solutions:
- Verify container running version 2.3.0:
docker exec beets beet version - If updated accidentally, change repository back to
lscr.io/linuxserver/beets:2.3.0 - Recreate the container with correct version
- Verify container running version 2.3.0:
Permission errors in Docker containers
- Symptoms: Files created with wrong ownership, access denied errors
- Solutions:
- Verify PUID=99 and PGID=100 in Docker container settings
- Check host paths are accessible by user nobody (99:100)
- Manually fix:
chown -R 99:100 /mnt/user/Media/Processing/
Path mapping issues
- Symptoms: Container can't find files, "No such file or directory" errors
- Solutions:
- Verify all Docker path mappings are correct in container settings
- Ensure host paths actually exist
- Check paths in
config.yamlmatch container paths (not host paths) - Example: If host path is
/mnt/user/Media/Processing/beets_untaggedand container path is/downloads, use/downloadsin config.yaml
- Unraid or Linux system
- Bash 4.0+ (for associative arrays)
- Python 3.6+ (for Beets)
- qBittorrent
- Beets with audible plugin
- autom4b (optional, for audio conversion)
- AudioBookshelf server
This project is licensed under the MIT License. See the LICENSE file for details.