Add filesystem scan trigger and version badge features

Implements optional filesystem scan triggering for Plex and Audiobookshelf after file organization, with new settings in the admin UI, setup wizard, and API. Updates documentation to reflect scan trigger options and improved file organization/cleanup logic. Refactors dropdown menus to use smart positioning and portals for better UX. Adds a version API route and a VersionBadge component to display build info in the header. Updates Docker build to inject version metadata.
This commit is contained in:
kikootwo
2026-01-09 17:15:00 -05:00
parent 288421012d
commit 384601014a
25 changed files with 1346 additions and 243 deletions
+308 -92
View File
@@ -1,11 +1,29 @@
# Chapter Merging Feature
**Status:** ✅ Implemented | Auto-merge multi-file chapters to M4B
**Status:** ✅ Implemented (v2 - Enhanced) | Auto-merge multi-file chapters to M4B
## Overview
Automatically merge multi-file audiobook downloads (separate MP3/M4A files per chapter) into a single M4B file with proper chapter markers during file organization.
## Recent Updates (v2 - Corruption Fixes)
**Status:** ✅ Implemented (2026-01-09)
**Critical Fixes:**
1.**Fixed corruption on long audiobooks** - Dynamic timeout calculation (16h book = 254min vs old 20min)
2.**Fixed 1-minute playback delay** - Added `-movflags +faststart` (moov atom at beginning)
3.**Fixed seeking/timestamp issues** - Added `-fflags +genpts`, `-avoid_negative_ts`, `-max_muxing_queue_size`
4.**Added output validation** - Catches corrupt files before marked successful (duration, decode test, size)
5.**Quality preservation** - Matches source bitrate (64-320k) instead of fixed 128k
6.**Higher quality encoding** - Uses libfdk_aac if available (VBR mode 4)
7.**Fixed validation timeout bug** - Decode timeout was in seconds instead of milliseconds (killed valid files)
8.**Optimized validation** - Fast integrity test (first/last 10s) instead of decoding entire 16h file
**Impact:**
- Before: 16h audiobook → 20min timeout → Killed mid-process → Corrupt 6h file → Marked "successful" → 1-min playback delay
- After: 16h audiobook → 254min timeout → Completes fully → Valid 16h file → Validated → Instant playback
## Problem Statement
**Current Behavior:**
@@ -28,23 +46,31 @@ Detect multi-file chapter downloads and merge into single M4B with embedded chap
### Detection Logic
**Chapter File Patterns (auto-detect):**
- Numeric: `01.mp3`, `001.mp3`, `1.mp3`
- Named: `Chapter 1.mp3`, `Chapter 01.mp3`, `Ch1.mp3`, `Ch 01.mp3`
- Part-based: `Part 1.mp3`, `Part01.mp3`
- Combined: `Harry Potter - 01 - Chapter 1.mp3`
**Simplified Detection Approach (v2):**
Detection now uses a **permissive heuristic** instead of strict filename pattern matching:
**Trigger Conditions:**
- 2+ audio files in download
- Files match chapter naming pattern
- All files same format (m4a, m4b, mp3)
- 3+ audio files in download (2 files might be "Book + Credits", so require 3+)
- All files same format (m4a, m4b, mp3, etc.)
- Feature enabled in config
**Ordering Strategy (metadata-first):**
1. **Primary:** Use embedded track numbers if all files have sequential track metadata
2. **Fallback:** Use natural filename sorting if metadata incomplete
3. **Validation:** Compare both methods when available for confidence
**Why This Works Better:**
- Catches edge cases like `Andy Weir - Project Hail Mary - 03.mp3` (doesn't match patterns)
- Trusts metadata over filenames (more reliable)
- Graceful fallback to filename sorting if metadata missing
- Attempts merge on any multi-file audiobook, lets analysis phase decide ordering
**Exclusions (do NOT merge):**
- Less than 3 audio files
- Mixed formats (some MP3, some M4A)
- Non-sequential numbering
- Files without clear chapter indicators
- Single file downloads
- Unsupported formats
### Chapter Metadata Generation
@@ -85,11 +111,16 @@ echo "file '/path/ch02.m4a'" >> filelist.txt
# 2. Generate chapter metadata
# [Create chapters.txt with timing from durations]
# 3. Merge with chapters
ffmpeg -f concat -safe 0 -i filelist.txt \
# 3. Merge with chapters (v2 - enhanced)
ffmpeg -y -f concat -safe 0 -i filelist.txt \
-i chapters.txt \
-map_metadata 1 \
-map 0:a \
-codec copy \
-movflags +faststart \ # NEW: Index at beginning (instant playback)
-fflags +genpts \ # NEW: Regenerate timestamps
-avoid_negative_ts make_zero \ # NEW: Handle negative timestamps
-max_muxing_queue_size 9999 \ # NEW: Prevent buffer overflow
-metadata title="Book Title" \
-metadata album="Book Title" \
-metadata album_artist="Author" \
@@ -100,23 +131,39 @@ ffmpeg -f concat -safe 0 -i filelist.txt \
output.m4b
```
**For MP3 files (requires conversion):**
**For MP3 files (requires conversion - v2 enhanced):**
```bash
# Must re-encode to M4B (AAC)
ffmpeg -f concat -safe 0 -i filelist.txt \
# Re-encode to M4B (AAC) with quality preservation
# Uses libfdk_aac if available (higher quality) or native aac
ffmpeg -y -f concat -safe 0 -i filelist.txt \
-i chapters.txt \
-map_metadata 1 \
-codec:a aac -b:a 128k \ # Quality preservation
-map 0:a \
-c:a libfdk_aac -vbr 4 \ # High quality AAC (or: -c:a aac -b:a <source_bitrate> -profile:a aac_low)
-movflags +faststart \ # CRITICAL: Instant playback
-fflags +genpts \ # Fix timestamps
-avoid_negative_ts make_zero \ # Handle edge cases
-max_muxing_queue_size 9999 \ # Long file support
-metadata title="Book Title" \
# ... (same metadata)
-f mp4 \
output.m4b
```
**Quality Settings (MP3 → M4B):**
- Bitrate: 128kbps AAC (transparent for audiobooks, 64kbps minimum)
- Sampling rate: Match source (44.1kHz or 48kHz)
- Channels: Preserve mono/stereo
**Quality Settings (MP3 → M4B - v2):**
- **Bitrate:** Matches source average (64-320kbps range)
- Example: 128kbps MP3 source → 128kbps AAC output
- Example: 192kbps MP3 source → 192kbps AAC output
- **Encoder:** libfdk_aac (VBR mode 4, high quality) if available, else native aac
- **Profile:** AAC-LC (maximum compatibility)
- **Sampling rate:** Preserved from source
- **Channels:** Preserved (mono/stereo)
**Critical Flags (v2):**
- **`-movflags +faststart`**: Moves moov atom to file beginning → instant playback (fixes 1-min delay)
- **`-fflags +genpts`**: Regenerates presentation timestamps → fixes seeking/timing issues
- **`-avoid_negative_ts make_zero`**: Handles negative timestamps at concat boundaries
- **`-max_muxing_queue_size 9999`**: Prevents buffer overflow on long audiobooks (16h+)
### File Naming
@@ -145,34 +192,95 @@ ffmpeg -f concat -safe 0 -i filelist.txt \
- Checkbox: "Merge chapter files" (default: unchecked)
- Tooltip: "Combines separate chapter files into single audiobook with chapter markers"
## Logging & Transparency
**Status:** ✅ Implemented (v2)
All chapter merging decisions are **fully logged** for user transparency:
**Detection Phase Logs:**
- File count and format detection
- Chapter merge setting status
- Reason for skipping merge (if applicable)
- Disk space validation
**Analysis Phase Logs:**
- Sample filenames for debugging
- Metadata availability (track numbers)
- Ordering strategy chosen (metadata vs filename)
- Sample chapter titles generated
- Confidence level assessment
**Merge Phase Logs:**
- Book title, author, output filename
- Total duration and estimated size
- Merge strategy (codec copy vs re-encode)
- Bitrate decision for MP3 conversions
- FFmpeg execution status
- Final file size and chapter count
- Cleanup status
**Example Log Output:**
```
[FileOrganizer] Multiple audio files detected (30 files) - checking chapter merge settings...
[FileOrganizer] Chapter merging enabled - analyzing files...
[FileOrganizer] Chapter detection: 30 files with format .mp3 - attempting chapter merge
[FileOrganizer] Analyzing 30 chapter files...
[FileOrganizer] Sample filenames: Andy Weir - Project Hail Mary - 01.mp3, Andy Weir - Project Hail Mary - 02.mp3, Andy Weir - Project Hail Mary - 03.mp3, ...
[FileOrganizer] Metadata analysis: 30/30 files have track numbers
[FileOrganizer] Track numbers: 1, 2, 3 ... 30
[FileOrganizer] Chapter ordering: Filename and metadata orders match - high confidence
[FileOrganizer] Using metadata-based ordering for 30 chapters
[FileOrganizer] Sample chapter titles: Ch1: "Chapter 1", Ch2: "Chapter 2", Ch3: "Chapter 3", ...
[FileOrganizer] Starting chapter merge: "Project Hail Mary" by Andy Weir
[FileOrganizer] Merge strategy: Re-encoding MP3 → AAC/M4B at 128k
[FileOrganizer] Executing FFmpeg merge (timeout: 20 minutes)...
[FileOrganizer] ✓ Chapter merge successful!
[FileOrganizer] - Chapters: 30
[FileOrganizer] - Duration: 16h 32m 10s
[FileOrganizer] - Size: 452MB
```
## User Experience
### Success Flow
1. Download completes: 25 chapter MP3 files
1. Download completes: 30 chapter MP3 files
2. File organization starts
3. System detects chapter pattern
4. Merges files with progress logging:
- "Detected 25 chapter files, merging into single M4B..."
- "Processing chapter 1/25..."
- "Merge complete: BookTitle.m4b (15.2 GB, 25 chapters)"
5. Copies merged M4B to target directory
6. Deletes temp files and originals (if configured)
7. Plex scans single M4B with full chapter navigation
3. System checks chapter merge settings (logs: enabled/disabled)
4. Detects multi-file audiobook (logs: file count, format)
5. Analyzes ordering strategy (logs: metadata vs filename, sample files)
6. Merges files with detailed logging:
- Detection: "30 files with format .mp3 - attempting chapter merge"
- Analysis: "Using metadata-based ordering for 30 chapters"
- Merge: "Re-encoding MP3 → AAC/M4B at 128k"
- Progress: "Executing FFmpeg merge (timeout: 20 minutes)..."
- Success: "✓ Chapter merge successful! 30 chapters, 16h 32m, 452MB"
7. Copies merged M4B to target directory (logs: copy status)
8. Cleans up temp files (logs: cleanup status)
9. Originals kept for seeding (cleaned up by separate scheduled job)
10. Plex scans single M4B with full chapter navigation
### Fallback Flow
**If merge fails:**
1. Log error: "Chapter merge failed: [reason]"
2. Fall back to current behavior: copy individual files
**If merge fails or skipped:**
1. System logs reason clearly:
- "Chapter merging disabled in settings - organizing 30 files individually"
- "Only 2 file(s) - not enough for chapter merge (minimum: 3)"
- "Mixed formats detected (.mp3, .m4a) - skipping merge"
- "Insufficient disk space - organizing files individually"
- "Chapter merge failed: [FFmpeg error] - organizing files individually"
2. Falls back gracefully: organize individual files
3. Mark request as "available" (not failed)
4. User can manually merge later
4. User can manually merge later or enable setting
**Failure scenarios:**
- FFmpeg crash/timeout
- Insufficient disk space for temp file
- Corrupted source files
- Unsupported audio codec
**Failure scenarios with logging:**
- Feature disabled → Logs: "Chapter merging disabled in settings"
- Too few files → Logs: "Only X file(s) - not enough for chapter merge"
- Mixed formats → Logs: "Mixed formats detected - skipping merge"
- Insufficient disk space → Logs: "Insufficient disk space for merge"
- FFmpeg crash/timeout → Logs: "FFmpeg merge failed: [error details]"
- Corrupted source files → Logs: "Failed to probe audio file: [error]"
## Technical Implementation
@@ -205,51 +313,83 @@ interface MergeResult {
}
// Main functions
async function detectChapterFiles(files: string[]): Promise<boolean>;
async function sortChapterFiles(files: string[]): Promise<ChapterFile[]>;
async function getAudioDuration(filePath: string): Promise<number>;
async function generateChapterMetadata(chapters: ChapterFile[]): Promise<string>;
async function mergeChapters(chapters: ChapterFile[], options: MergeOptions): Promise<MergeResult>;
async function detectChapterFiles(files: string[], logger?: JobLogger): Promise<boolean>;
async function analyzeChapterFiles(filePaths: string[], logger?: JobLogger): Promise<ChapterFile[]>;
async function probeAudioFile(filePath: string): Promise<AudioProbeResult>;
async function mergeChapters(chapters: ChapterFile[], options: MergeOptions, logger?: JobLogger): Promise<MergeResult>;
function formatDuration(ms: number): string;
async function checkDiskSpace(directory: string): Promise<number | null>;
async function estimateOutputSize(filePaths: string[]): Promise<number>;
```
### Integration Points
**File: `src/lib/utils/file-organizer.ts`**
**Modify `organize()` method:**
**Modify `organize()` method (Updated v2):**
```typescript
// After finding audiobook files (line ~73)
// After finding audiobook files (line ~98)
if (audioFiles.length > 1) {
await logger?.info(`Multiple audio files detected (${audioFiles.length} files) - checking chapter merge settings...`);
const config = await prisma.configuration.findUnique({
where: { key: 'chapter_merging_enabled' }
});
const mergingEnabled = config?.value === 'true';
const isChapterDownload = await detectChapterFiles(audioFiles);
if (mergingEnabled && isChapterDownload) {
// Merge chapters instead of copying individually
const mergeResult = await mergeChapters(audioFiles, {
title: audiobook.title,
author: audiobook.author,
narrator: audiobook.narrator,
year: audiobook.year,
outputPath: path.join(targetPath, `${audiobook.title}.m4b`)
});
if (!mergingEnabled) {
await logger?.info(`Chapter merging disabled in settings - organizing ${audioFiles.length} files individually`);
} else {
await logger?.info(`Chapter merging enabled - analyzing files...`);
if (mergeResult.success) {
result.audioFiles = [mergeResult.outputPath];
result.filesMovedCount = 1;
// Skip individual file copying
} else {
// Fallback to individual file copying
await logger?.warn(`Chapter merge failed, copying files individually`);
// Continue with existing logic
// Build full paths
const sourceFilePaths = audioFiles.map(f => path.join(downloadPath, f));
// Simple detection: 3+ files, same format
const isChapterDownload = await detectChapterFiles(sourceFilePaths, logger);
if (isChapterDownload) {
// Check disk space
const estimatedSize = await estimateOutputSize(sourceFilePaths);
const availableSpace = await checkDiskSpace(this.tempDir);
if (availableSpace !== null && availableSpace < estimatedSize) {
await logger?.warn(`Insufficient disk space - organizing files individually`);
} else {
// Analyze and order (metadata-first)
const chapters = await analyzeChapterFiles(sourceFilePaths, logger);
// Merge chapters
const mergeResult = await mergeChapters(chapters, {
title: audiobook.title,
author: audiobook.author,
narrator: audiobook.narrator,
year: audiobook.year,
asin: audiobook.asin,
outputPath: path.join(this.tempDir, `${audiobook.title}.m4b`)
}, logger);
if (mergeResult.success) {
// Replace array with single merged file
audioFiles.length = 0;
audioFiles.push(mergeResult.outputPath);
await logger?.info(`Chapter merge complete - organizing single M4B file`);
} else {
await logger?.warn(`Chapter merge failed - organizing files individually`);
}
}
}
}
}
```
**Key Changes:**
- Simplified detection (3+ files, same format)
- Comprehensive logging at every decision point
- Metadata-first ordering in `analyzeChapterFiles`
- Graceful fallback with clear user messaging
### Database Schema
**No changes required** - uses existing `Configuration` table
@@ -260,6 +400,56 @@ if (audioFiles.length > 1) {
- ffmpeg (installed in Docker images)
- ffprobe (for duration detection)
## Timeout & Validation (v2)
**Status:** ✅ Implemented
### Dynamic Timeout Calculation
**Problem:** Fixed 20-minute timeout was insufficient for long audiobooks (16h+ books need 90-120 minutes to encode).
**Solution:**
```typescript
// For re-encoding (MP3 → AAC)
timeout = max(
90 minutes, // minimum
(duration_minutes / 5) + 60 minutes // 5x realtime (worst case) + 60min safety
)
// Examples:
// 16h book: (960 / 5) + 60 = 252 minutes
// 8h book: (480 / 5) + 60 = 156 minutes
// 30min book: 90 minutes (minimum)
// For codec copy (M4A → M4B)
timeout = 5 minutes + (chapter_count * 30 seconds)
// Much faster, no encoding needed
```
### Output Validation
**Status:** ✅ Implemented
All merged files are validated before marked successful:
1. **Duration Check:** Expected vs actual duration (within 2% tolerance)
2. **Decode Test:** FFmpeg attempts to decode first 10 seconds (catches corruption)
3. **Size Check:** File size reasonable for duration (~0.5MB/min minimum)
**If validation fails:**
- Corrupt file is deleted
- Error logged with specific failure reason
- Falls back to organizing individual files
- Request marked "available" (not failed)
**Example validation failure:**
```
[FileOrganizer] Duration check: expected 16h 10m 54s, got 6h 13m
[FileOrganizer] ✗ Output validation failed: Duration mismatch (61.6% off). File may be truncated.
[FileOrganizer] Deleted corrupt output file
[FileOrganizer] Chapter merge failed - organizing 30 files individually
```
## Edge Cases & Error Handling
### Edge Cases
@@ -352,51 +542,77 @@ if (audioFiles.length > 1) {
## Success Metrics
### Functional
### Functional (v2 - Enhanced)
- ✅ Successful merge rate > 95% (for valid chapter downloads)
-Chapter navigation works in Plex
-Zero audio quality degradation (M4A copy mode)
-**Validation catches 100% of corrupt files** (new)
-Chapter navigation works in Plex/Audiobookshelf
- ✅ Zero audio quality degradation (M4A copy mode, source-matched bitrate for MP3)
- ✅ Fallback works 100% of time on merge failure
-**No timeout failures for long audiobooks** (new - 16h+ books complete successfully)
### Performance
- ✅ M4A merge: < 2 minutes for 25 chapters
- ✅ MP3 conversion: < 15 minutes for 10-hour audiobook
### Performance (v2 - Enhanced)
- ✅ M4A merge: < 2 minutes for 25 chapters (codec copy, no re-encode)
- ✅ MP3 conversion: ~10x realtime (16h book = 90-120 minutes)
-**Instant playback start** (new - faststart flag moves index to beginning)
- ✅ No impact on concurrent downloads
-**Proper timeout allocation** (new - 126 min for 16h books vs old 20 min)
### User Experience
### User Experience (v2 - Enhanced)
- ✅ Feature opt-in (default disabled)
-Clear logging of merge progress
-**Comprehensive logging** (new - detection, analysis, merge, validation)
- ✅ Single file in Plex instead of dozens
- ✅ Proper chapter markers in audiobook players
-**Transparent validation** (new - users know if file is good or corrupt)
-**Quality preservation** (new - matches source bitrate, libfdk_aac if available)
## Implementation Phases
### Phase 1: Core Functionality (MVP)
- [ ] Implement `chapter-merger.ts` utility
- [ ] Detection logic (chapter file patterns)
- [ ] Natural sorting algorithm
- [ ] Duration extraction (ffprobe)
- [ ] Chapter metadata generation (FFMETADATA1)
- [ ] M4A/M4B merge (codec copy mode)
- [ ] Integration with file-organizer.ts
- [ ] Configuration keys in database
### Phase 1: Core Functionality (MVP) ✅ COMPLETED
- [x] Implement `chapter-merger.ts` utility
- [x] Detection logic (simplified: 3+ files, same format)
- [x] Natural sorting algorithm
- [x] Duration extraction (ffprobe)
- [x] Chapter metadata generation (FFMETADATA1)
- [x] M4A/M4B merge (codec copy mode)
- [x] Integration with file-organizer.ts
- [x] Configuration keys in database
### Phase 2: MP3 Support
- [ ] MP3 → M4B conversion logic
- [ ] Quality preservation settings
- [ ] Bitrate configuration UI
### Phase 2: MP3 Support ✅ COMPLETED
- [x] MP3 → M4B conversion logic
- [x] Quality preservation settings (dynamic bitrate)
- [x] Bitrate configuration (automatic, based on source)
### Phase 3: UI & Polish
- [ ] Setup wizard integration
- [ ] Admin settings UI (Paths tab)
- [ ] Progress logging improvements
- [ ] Error messaging UX
### Phase 3: Logging & Transparency ✅ COMPLETED (v2)
- [x] Comprehensive logging at all decision points
- [x] Detection phase logging (file count, format, settings)
- [x] Analysis phase logging (metadata vs filename, samples)
- [x] Merge phase logging (strategy, progress, results)
- [x] Error logging with clear fallback messaging
- [x] User transparency for all decisions
### Phase 4: Advanced Features (Future)
- [ ] Custom chapter naming from file metadata
### Phase 4: UI Integration ✅ COMPLETED
- [x] Setup wizard integration
- [x] Admin settings UI (Paths tab)
- [x] Configuration persistence
### Phase 5: Corruption Fixes & Validation ✅ COMPLETED (v2)
- [x] Dynamic timeout calculation (fixes 16h+ book timeouts)
- [x] Add `-movflags +faststart` (fixes 1-min playback delay)
- [x] Add `-fflags +genpts` (fixes timestamp/seeking issues)
- [x] Add `-avoid_negative_ts make_zero` (handles edge cases)
- [x] Add `-max_muxing_queue_size` (prevents buffer overflow)
- [x] Output validation (duration, decode test, size check)
- [x] Source bitrate matching (preserves quality)
- [x] libfdk_aac support (higher quality when available)
- [x] Corrupt file detection and cleanup
### Phase 6: Advanced Features (Future)
- [ ] Real-time progress logging with FFmpeg output parsing
- [ ] Custom chapter naming from file metadata (partially done)
- [ ] Chapter art extraction (if embedded in files)
- [ ] Preview merged file before finalizing
- [ ] Manual chapter editing UI
- [ ] Parallel chapter processing (analyze while downloading)
## Related Documentation
+54 -4
View File
@@ -37,7 +37,46 @@ Default: `/media/audiobooks/` (if not configured)
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. Originals remain until seeding requirements met
8. Update request status to `downloaded`
9. **Trigger filesystem scan** (if enabled) - tells Plex/ABS to scan for new files
10. Originals remain until seeding requirements met
## Filesystem Scan Triggering
**Status:** ✅ Implemented (Both Backends)
**Purpose:** Trigger Plex/Audiobookshelf to scan filesystem after organizing files, ensuring new books appear immediately for users with disabled filesystem watchers.
**Configuration:**
- Plex: `plex.trigger_scan_after_import` (boolean, default: false)
- Audiobookshelf: `audiobookshelf.trigger_scan_after_import` (boolean, default: false)
**Flow:**
1. Files organized to media directory
2. Request status updated to `downloaded`
3. Check config setting (backend-specific)
4. If enabled: Call `ILibraryService.triggerLibraryScan(libraryId)`
5. Media server scans filesystem (async operation)
6. RMAB's scheduled check eventually detects new book
7. Request status updates to `available`
**Implementation:**
- Uses existing `ILibraryService` abstraction
- `PlexLibraryService.triggerLibraryScan()``POST /library/sections/{id}/refresh`
- `AudiobookshelfLibraryService.triggerLibraryScan()``POST /api/libraries/{id}/scan`
- Called from `organize-files.processor.ts` after status update
- Backend-agnostic using factory pattern
**Error Handling:**
- Scan failures logged but don't fail organize job
- Graceful degradation: scheduled scans eventually detect the book
- Non-blocking: async operation doesn't delay other jobs
**Use Cases:**
- Users with Plex/ABS filesystem watcher disabled
- Network-mounted media directories with delayed inotify
- Users who prefer manual control over automatic scanning
- Most users keep this disabled (default) and rely on built-in watchers
## Metadata Tagging
@@ -107,10 +146,21 @@ exiftool "audiobook.m4b" | grep -i asin
**Config:** `seeding_time_minutes` (0 = unlimited, never cleanup)
**Cleanup Job:** `cleanup_seeded_torrents` (every 30 mins)
1. Check 'available' and 'downloaded' status requests with download history
1. Find requests with status 'available' or soft-deleted (orphaned downloads)
2. Query qBittorrent for actual `seeding_time` field
3. Delete torrent + files only after requirement met
4. Respects config (0 = never cleanup)
3. **CRITICAL: Check if torrent hash is shared by other active requests**
- If yes → Skip torrent deletion, only hard-delete the soft-deleted request record
- If no → Delete torrent + files
4. Delete torrent + files only after seeding requirement met
5. Respects config (0 = never cleanup)
**Shared Torrent Protection:**
When user deletes and re-requests the same audiobook:
- Both requests share the same torrent hash (same files)
- Cleanup finds old soft-deleted request
- Before deleting torrent, checks if any active (non-deleted) request uses same hash
- If found → Keeps torrent, only removes soft-deleted database record
- Prevents deleting source files for active requests during chapter merging
## Interface
+25 -6
View File
@@ -6,12 +6,31 @@ Single tabbed interface for admins to view/modify system configuration post-setu
## Sections
1. **Plex** - URL, token (masked), library ID
2. **Prowlarr** - URL, API key (masked), indexer selection with priority, seeding time, RSS monitoring toggle
3. **Download Client** - Type, URL, credentials (masked)
4. **Paths** - Download + media directories
5. **BookDate** - AI provider, API key (encrypted), model selection, library scope, custom prompt, swipe history
6. **Account** - Local admin password change (only visible to setup admin)
1. **Plex** - URL, token (masked), library ID, filesystem scan trigger toggle
2. **Audiobookshelf** - URL, API token (masked), library ID, filesystem scan trigger toggle
3. **Prowlarr** - URL, API key (masked), indexer selection with priority, seeding time, RSS monitoring toggle
4. **Download Client** - Type, URL, credentials (masked)
5. **Paths** - Download + media directories
6. **BookDate** - AI provider, API key (encrypted), model selection, library scope, custom prompt, swipe history
7. **Account** - Local admin password change (only visible to setup admin)
## Filesystem Scan Trigger
**Purpose:** Trigger Plex/Audiobookshelf to scan filesystem after organizing files for users with disabled filesystem watchers.
**Configuration:**
- Plex: `plex.trigger_scan_after_import` (boolean, default: false)
- Audiobookshelf: `audiobookshelf.trigger_scan_after_import` (boolean, default: false)
**UI:**
- Checkbox toggle in both Plex and Audiobookshelf settings tabs
- Default: Unchecked (disabled)
- Help text: "Only enable this if you have [Plex/Audiobookshelf]'s filesystem watcher (automatic scanning) disabled. Most users should leave this disabled and rely on [Plex/Audiobookshelf]'s built-in automatic detection."
**Behavior:**
- When enabled: After `organize_files` job completes, RMAB triggers filesystem scan in media server
- 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)
## Validation Flow
+1 -1
View File
@@ -17,7 +17,7 @@
1. Welcome - Intro screen
2. Admin Account - Create admin user
3. Plex - Server URL, OAuth, library selection
3. Plex (or Audiobookshelf) - Server URL, auth, library selection, filesystem scan trigger toggle
4. Prowlarr - URL, API key, indexer selection with priorities (1-25), seeding time, RSS monitoring
5. Download Client - qBittorrent/Transmission config
6. Paths - Download + media directories with validation