mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
Add Plex format coercion (.mp4 → .m4b)
Implement Plex-compatible file-extension coercion to avoid Plex silently ignoring .mp4 (and single-file .m4a) audiobooks (issue #166). Adds a DB migration and configuration key (plex_format_coercion_enabled, default true), exposes a toggle in the setup wizard and Admin Paths settings, and persists/reads the setting in the admin/setup APIs. Introduces src/lib/utils/format-coercion.ts (coerceToPlexCompatible) and related constants in src/lib/constants/audio-formats.ts (PLEX_COMPATIBLE_EXTENSIONS, COERCION_RENAME_MAP, DRM_EXTENSIONS, TRANSCODE_REQUIRED_EXTENSIONS). The organize-files processor now runs coercion after organizing/tagging and before generating the filesHash and triggering scans; coercion is idempotent, never overwrites existing targets, logs warnings on DRM/transcode/permission errors, and is non-fatal. Adds unit tests for the coercion util and updates processor & setup UI tests. Updates documentation (TABLEOFCONTENTS, file-organization, fixes/file-hash-matching, settings-pages) describing behavior, config, and constraints.
This commit is contained in:
@@ -69,6 +69,7 @@
|
||||
- **qBittorrent integration (torrents)** → [phase3/qbittorrent.md](phase3/qbittorrent.md)
|
||||
- **SABnzbd integration (Usenet/NZB)** → [phase3/sabnzbd.md](phase3/sabnzbd.md)
|
||||
- **File organization, seeding** → [phase3/file-organization.md](phase3/file-organization.md)
|
||||
- **Plex-compatible format coercion (.mp4 → .m4b)** → [phase3/file-organization.md](phase3/file-organization.md#plex-format-coercion)
|
||||
- **Chapter merging (auto-merge to M4B)** → [features/chapter-merging.md](features/chapter-merging.md)
|
||||
|
||||
## Background Jobs
|
||||
|
||||
@@ -154,6 +154,12 @@ model Audiobook {
|
||||
- Hash generated AFTER merging
|
||||
- **Works correctly:** Hash reflects final organized state
|
||||
|
||||
### Coerced Files (Plex Format Coercion)
|
||||
- Files renamed from `.mp4` → `.m4b` (or single-file `.m4a` → `.m4b`) by Plex format coercion
|
||||
- Hash generated AFTER coercion → reflects post-coercion filenames
|
||||
- **Works correctly going forward:** ABS sees post-coercion names, hash matches
|
||||
- **Pre-existing library entries** hashed before coercion was enabled will NOT match post-coercion files — retroactive library sweep is out of scope (see issue #166)
|
||||
|
||||
### Multiple Downloads (Same Book)
|
||||
- User re-downloads same audiobook (different edition/request)
|
||||
- Multiple records with same `filesHash`
|
||||
|
||||
@@ -44,10 +44,11 @@ Result: Douglas Adams/Stephen Fry/The Hitchhiker's Guide to the Galaxy/
|
||||
5. **Copy** files (not move - originals stay for seeding)
|
||||
6. **Tag metadata** (if enabled) - writes correct title, author, narrator, ASIN to audio files
|
||||
7. Copy cover art if found, else download from Audible
|
||||
8. **Generate file hash** - SHA256 of sorted audio filenames for library matching (see: [fixes/file-hash-matching.md](../fixes/file-hash-matching.md))
|
||||
9. Update request status to `downloaded` and store file hash in `audiobooks.files_hash`
|
||||
10. **Trigger filesystem scan** (if enabled) - tells Plex/ABS to scan for new files
|
||||
11. Originals remain until seeding requirements met
|
||||
8. **Coerce file formats** (if enabled) - rename .mp4 → .m4b and single-file .m4a → .m4b for Plex compatibility (see: Plex Format Coercion below)
|
||||
9. **Generate file hash** - SHA256 of sorted audio filenames for library matching (see: [fixes/file-hash-matching.md](../fixes/file-hash-matching.md))
|
||||
10. Update request status to `downloaded` and store file hash in `audiobooks.files_hash`
|
||||
11. **Trigger filesystem scan** (if enabled) - tells Plex/ABS to scan for new files
|
||||
12. Originals remain until seeding requirements met
|
||||
|
||||
## Filesystem Scan Triggering
|
||||
|
||||
@@ -150,6 +151,61 @@ exiftool "audiobook.m4b" | grep -i asin
|
||||
- Multi-container: `docker exec readmeabook ffmpeg -version`
|
||||
- Unified: `docker exec readmeabook-unified ffmpeg -version`
|
||||
|
||||
## Plex Format Coercion
|
||||
|
||||
**Status:** ✅ Implemented | Issue #166
|
||||
|
||||
**Purpose:** Rename audiobook files to Plex-recognized extensions before the library scan. Plex silently ignores `.mp4` files in audiobook libraries; this step prevents that silent-failure mode. Rename-only — no transcoding.
|
||||
|
||||
**When:** After file organization and metadata tagging, before file-hash generation and before library scan trigger.
|
||||
|
||||
**Scope:** Audio path only. Not applied to ebook organization.
|
||||
|
||||
**Coercion Table:**
|
||||
|
||||
| Source ext | Action |
|
||||
|---|---|
|
||||
| `.mp4` | Rename to `.m4b` |
|
||||
| `.m4a` (single audio file in folder) | Rename to `.m4b` |
|
||||
| `.m4a` (multi-file folder) | No-op |
|
||||
| `.m4b`, `.mp3`, `.flac`, `.aac`, `.wav`, `.alac` | No-op |
|
||||
| `.aa`, `.aax` | No-op + warn ("DRM, Plex cannot import") |
|
||||
| `.ogg`, `.opus`, `.wma`, other | No-op + warn ("requires transcode, not supported in v1") |
|
||||
|
||||
**Configuration:**
|
||||
- Key: `plex_format_coercion_enabled` (Configuration table)
|
||||
- Default: `true`
|
||||
- Read contract: `value !== 'false'` enables (default-on semantics)
|
||||
- Configurable in: Setup wizard (Paths step), Admin settings (Paths tab)
|
||||
|
||||
**Behavior:**
|
||||
- Each audio file evaluated independently (mixed-format folders supported).
|
||||
- Pre-rename collision check: if target exists → no-op + info log. Never overwrites.
|
||||
- Idempotent: re-running on already-coerced folder is a no-op (extension is the signal — no marker files).
|
||||
- Operates on `targetPath` (organized library files) only — never touches `/downloads` (seeding-safe).
|
||||
|
||||
**Failure Isolation:**
|
||||
- Coercion wrapped in try/catch at processor level.
|
||||
- Any failure (e.g., EPERM) logs a warning; request remains organized; original file untouched.
|
||||
- A failed rename never regresses the request to "stuck."
|
||||
|
||||
**Tech Stack:**
|
||||
- `src/lib/utils/format-coercion.ts` — coercion module
|
||||
- `src/lib/constants/audio-formats.ts` — `PLEX_COMPATIBLE_EXTENSIONS`, `COERCION_RENAME_MAP`, `DRM_EXTENSIONS`, `TRANSCODE_REQUIRED_EXTENSIONS`
|
||||
- Invoked from `src/lib/processors/organize-files.processor.ts` between file organization and `generateFilesHash`
|
||||
- `fs.rename` (same filesystem — no cross-mount issues)
|
||||
|
||||
**Hash Interaction:**
|
||||
- File hash (`audiobooks.files_hash`) is generated AFTER coercion → reflects post-coercion filenames.
|
||||
- See: [fixes/file-hash-matching.md](../fixes/file-hash-matching.md) for hash semantics.
|
||||
|
||||
**Out of Scope (v1):**
|
||||
- Transcoding (`.ogg`, `.opus`, `.wma`)
|
||||
- DRM decoding (`.aa`, `.aax`)
|
||||
- FLAC → M4B (already Plex-recognized)
|
||||
- Per-request override UI
|
||||
- Retroactive library sweep (new downloads only)
|
||||
|
||||
## Seeding Support
|
||||
|
||||
**Config:** `seeding_time_minutes` (0 = unlimited, never cleanup)
|
||||
@@ -203,6 +259,7 @@ async function organize(
|
||||
- **Path template:** Read from database config key `audiobook_path_template` (default: `{author}/{title} {asin}`)
|
||||
- **Metadata tagging:** `metadata_tagging_enabled` (boolean, default: true)
|
||||
- **Chapter merging:** `chapter_merging_enabled` (boolean, default: false)
|
||||
- **Plex format coercion:** `plex_format_coercion_enabled` (boolean, default: true)
|
||||
- **Fallback:** `/media/audiobooks` if media_dir not configured
|
||||
- **Temp directory:** `/tmp/readmeabook` (or `TEMP_DIR` env var)
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ src/app/admin/settings/
|
||||
2. **Audiobookshelf** - URL, API token (masked), library ID, Audible region, filesystem scan trigger toggle
|
||||
3. **Prowlarr** - URL, API key (masked), indexer selection with priority, seeding time, RSS monitoring toggle, **audiobook/ebook categories per indexer**
|
||||
4. **Download Client** - Type (qBittorrent, Transmission, SABnzbd), URL, credentials (masked), custom download path (per-client relative sub-path with live preview)
|
||||
5. **Paths** - Download + media directories, audiobook organization template, metadata tagging toggle, chapter merging toggle
|
||||
5. **Paths** - Download + media directories, audiobook organization template, metadata tagging toggle, chapter merging toggle, Plex format coercion toggle
|
||||
6. **E-book Sidecar** - Multi-source ebook downloads (Anna's Archive + Indexer Search), preferred format
|
||||
7. **BookDate** - AI provider, API key (encrypted), model selection, library scope, custom prompt, swipe history
|
||||
8. **Notifications** - Multiple backends (Discord, Pushover), event subscriptions, test functionality
|
||||
@@ -222,6 +222,27 @@ src/app/admin/settings/
|
||||
- When disabled: User relies on media server's filesystem watcher or manual scans
|
||||
- Error handling: Scan failures logged but don't fail organize job (graceful degradation)
|
||||
|
||||
## Plex Format Coercion
|
||||
|
||||
**Purpose:** Rename audiobook files to Plex-recognized extensions (`.mp4` → `.m4b`, single-file `.m4a` → `.m4b`) before the library scan. Prevents Plex silently ignoring `.mp4` audiobooks. Rename-only — no transcoding. See: [phase3/file-organization.md](phase3/file-organization.md#plex-format-coercion).
|
||||
|
||||
**Configuration:**
|
||||
- Key: `plex_format_coercion_enabled` (boolean, default: `true`)
|
||||
- Read contract: `value !== 'false'` enables (default-on)
|
||||
- Configurable in: Setup wizard (Paths step), Admin settings (Paths tab)
|
||||
|
||||
**UI:**
|
||||
- Checkbox toggle in PathsTab, between metadata tagging and chapter merging
|
||||
- Default: Checked (enabled)
|
||||
- Label: "Coerce file formats for Plex compatibility"
|
||||
- Sub-text: "Rename .mp4 audiobook files (and single-file .m4a) to .m4b before Plex scans. No re-encoding."
|
||||
|
||||
**Behavior:**
|
||||
- When enabled: After organize, rename files per coercion table before scan trigger
|
||||
- When disabled: Files left as-is (Plex may silently skip `.mp4`)
|
||||
- Failure isolation: Rename errors logged but don't fail organize job
|
||||
- Universal (Plex + ABS) — rename is lossless, no per-backend distinction
|
||||
|
||||
## Validation Flow
|
||||
|
||||
**Plex, Download Client, Paths:**
|
||||
|
||||
Reference in New Issue
Block a user