diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index 447c599..0000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,422 +0,0 @@ -# Interactive Audio Stream Selection - Architecture Diagram - -## System Architecture - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ main.py │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ ArgumentParser │ │ -│ │ --filter-audio (enables audio filtering) │ │ -│ │ --interactive (enables interactive mode) ← NEW │ │ -│ │ --cq, --r, --m, --language, --test (existing) │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ ↓ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ normalize_input_path() → folder path │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ ↓ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ process_folder( │ │ -│ │ filter_audio=True/False, │ │ -│ │ interactive_audio=True/False ← NEW │ │ -│ │ ) │ │ -│ └──────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────────────────────┐ -│ core/process_manager.py │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ process_folder(folder, ..., filter_audio, interactive) │ │ -│ │ ↑ NEW param │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ ↓ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ For each video file: │ │ -│ │ 1. Get source resolution & target resolution │ │ -│ │ 2. Create audio_filter_config dict: │ │ -│ │ { │ │ -│ │ "enabled": filter_audio, │ │ -│ │ "interactive": interactive_audio ← NEW FIELD │ │ -│ │ } │ │ -│ │ 3. Call run_ffmpeg() with audio_filter_config │ │ -│ └──────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────────────────────┐ -│ core/encode_engine.py │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ run_ffmpeg( │ │ -│ │ input_file, output_file, ..., │ │ -│ │ audio_filter_config={enabled, interactive} │ │ -│ │ ) │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ ↓ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ 1. streams = get_audio_streams(input_file) │ │ -│ │ └─ Returns: [(index, ch, br, lang, meta), ...] │ │ -│ │ │ │ -│ │ 2. if audio_filter_config.get("enabled"): │ │ -│ │ ├─ if audio_filter_config.get("interactive"): │ │ -│ │ │ └─ Call: prompt_user_audio_selection(streams) ← ◆ │ │ -│ │ │ [SHOWS PROMPT TO USER] │ │ -│ │ │ └─ Returns: filtered_streams │ │ -│ │ │ │ │ -│ │ └─ else: │ │ -│ │ └─ Call: filter_audio_streams(input_file, streams) │ │ -│ │ (Automatic: keep best English + Commentary) │ │ -│ │ └─ Returns: filtered_streams │ │ -│ │ │ │ -│ │ 3. For each stream in filtered_streams: │ │ -│ │ ├─ choose_audio_bitrate() (codec selection) │ │ -│ │ └─ Build FFmpeg codec params (-c:a, -b:a, etc.) │ │ -│ │ │ │ -│ │ 4. subprocess.run(ffmpeg_cmd) │ │ -│ └──────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────────────────────┐ -│ core/audio_handler.py │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ def prompt_user_audio_selection(streams) ← NEW FUNCTION │ │ -│ │ ◆ Interactive User Prompt ◆ │ │ -│ │ │ │ -│ │ Display: │ │ -│ │ ┌──────────────────────────────────────────────┐ │ │ -│ │ │ 🎵 AUDIO STREAM SELECTION │ │ │ -│ │ │ │ │ │ -│ │ │ Stream #0: 2ch | Lang: eng | Bitrate: 128kbps │ │ -│ │ │ Stream #1: 6ch | Lang: eng | Bitrate: 448kbps │ │ -│ │ │ Stream #2: 2ch | Lang: spa | Bitrate: 128kbps │ │ -│ │ │ Stream #3: 2ch | Lang: comment | Bitrate: 64kbps │ │ -│ │ │ │ │ │ -│ │ │ Keep streams: 1,3 │ │ │ -│ │ │ │ │ │ -│ │ │ ✅ Keeping 2 stream(s), removing 2 stream(s) │ │ -│ │ └──────────────────────────────────────────────┘ │ │ -│ │ │ │ -│ │ Process: │ │ -│ │ 1. Check if streams empty/single → return as-is │ │ -│ │ 2. Display all streams with formatting │ │ -│ │ 3. Prompt user for comma-separated indices │ │ -│ │ 4. Parse and validate input │ │ -│ │ 5. Filter streams to selected only │ │ -│ │ 6. Log selections & removed streams │ │ -│ │ 7. Return filtered_streams │ │ -│ │ │ │ -│ │ Error Handling: │ │ -│ │ • Invalid input → Keep all (log warning) │ │ -│ │ • No selections → Keep all (log warning) │ │ -│ │ • Empty input → Keep all (user confirmed) │ │ -│ └──────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -## Data Flow Example - -### User Command -```bash -python main.py "C:\Videos" --filter-audio --interactive -``` - -### Data Transformation - -``` -Step 1: ArgumentParser -───────────────────── -Input Args: - folder = "C:\Videos" - filter_audio = True - interactive_audio = True - -Output: args object - -──────────────────────────────────────────────────────── - -Step 2: main() → process_folder() -─────────────────────────────────── -Input: - folder, filter_audio=True, interactive_audio=True - -Output: Called with both flags - -──────────────────────────────────────────────────────── - -Step 3: process_folder() → Builds audio_filter_config -────────────────────────────────────────────────────── -Input: - filter_audio=True - interactive_audio=True - -Logic: - if filter_audio is not None: - audio_filter_config = { - "enabled": True, - "interactive": True ← NEW - } - -Output: audio_filter_config dict - -──────────────────────────────────────────────────────── - -Step 4: process_folder() → run_ffmpeg() -───────────────────────────────────────── -Input: - input_file = "movie.mkv" - audio_filter_config = {"enabled": True, "interactive": True} - -Output: Called with config - -──────────────────────────────────────────────────────── - -Step 5: run_ffmpeg() → Audio Stream Detection -────────────────────────────────────────────── -Input: - input_file = "movie.mkv" - -Output: - streams = [ - (0, 2, 128, "eng", 0), # Stream #0: 2ch English 128kbps - (1, 6, 448, "eng", 0), # Stream #1: 6ch English 448kbps - (2, 2, 128, "spa", 0), # Stream #2: 2ch Spanish 128kbps - (3, 2, 64, "und", 0) # Stream #3: 2ch Undefined 64kbps - ] - -──────────────────────────────────────────────────────── - -Step 6: Audio Filtering Decision -──────────────────────────────── -Input: - audio_filter_config = {"enabled": True, "interactive": True} - streams = [4 streams above] - -Logic: - if audio_filter_config.get("enabled"): ✓ True - if audio_filter_config.get("interactive"): ✓ True - → Call prompt_user_audio_selection() ← INTERACTIVE PATH - -Output: User prompt shown to console - -──────────────────────────────────────────────────────── - -Step 7: prompt_user_audio_selection() → User Input -────────────────────────────────────────────────────── -Input: - streams = [4 streams] - -Display: - 🎵 AUDIO STREAM SELECTION - ════════════════════════════════════════════════════ - Stream #0: 2ch | Lang: eng | Bitrate: 128kbps - Stream #1: 6ch | Lang: eng | Bitrate: 448kbps - Stream #2: 2ch | Lang: spa | Bitrate: 128kbps - Stream #3: 2ch | Lang: undefined | Bitrate: 64kbps - - Keep streams: ← WAIT FOR USER INPUT - -User Input: - "1,3" - -Parse: - selected_indices = {1, 3} - -Filter: - filtered = [ - (1, 6, 448, "eng", 0), ✓ Keep - (3, 2, 64, "und", 0) ✓ Keep - ] - -Output: - ✅ Keeping 2 stream(s), removing 2 stream(s) - - Return: filtered streams - -──────────────────────────────────────────────────────── - -Step 8: Back to run_ffmpeg() → Codec Selection -────────────────────────────────────────────── -Input: - streams = [ - (1, 6, 448, "eng", 0), - (3, 2, 64, "und", 0) - ] - -Process each stream: - Stream 1: 6ch → choose_audio_bitrate() → ("eac3", 384000) - Stream 3: 2ch → choose_audio_bitrate() → ("aac", 160000) - -Output: - FFmpeg codec params: - -c:a:1 eac3 -b:a:1 384k -ac:1 6 -channel_layout:1 5.1 - -c:a:3 aac -b:a:3 160k -ac:3 2 -channel_layout:3 stereo - -──────────────────────────────────────────────────────── - -Step 9: FFmpeg Encoding -─────────────────────── -Input: - ffmpeg -i movie.mkv \ - -vf scale=... \ - -c:v av1_nvenc \ - -c:a:1 eac3 -b:a:1 384k ... \ - -c:a:3 aac -b:a:3 160k ... \ - output.mkv - -Process: - FFmpeg encodes video and audio streams - Only streams 1 and 3 included (streams 0 and 2 excluded) - -Output: - output.mkv (with only selected audio tracks) -``` - -## State Diagram - -``` - ┌─────────────────────────────────┐ - │ User Runs Script │ - │ --filter-audio --interactive │ - └──────────────┬──────────────────┘ - │ - ▼ - ┌─────────────────────────────────┐ - │ Parse Arguments │ - │ interactive_audio = True │ - └──────────────┬──────────────────┘ - │ - ▼ - ┌─────────────────────────────────┐ - │ process_folder() │ - │ Build audio_filter_config │ - │ {enabled: T, interactive: T} │ - └──────────────┬──────────────────┘ - │ - ┌────────────┴────────────┐ - │ │ - ▼ ▼ - For each file Detect audio streams - ┌──────────────┐ get_audio_streams() - │ run_ffmpeg() │ └─ Returns 4 streams - └──────┬───────┘ - │ - ▼ - ┌──────────────────────────┐ - │ Check filter enabled? │ - │ audio_filter_config │ - └──────┬─────────────┬─────┘ - │ No │ Yes - │ ▼ - │ ┌─────────────────────┐ - │ │ Check interactive? │ - │ └────┬────────────┬───┘ - │ │ No │ Yes - │ │ ▼ - │ │ ┌───────────────────┐ - │ │ │ INTERACTIVE PROMPT│ - │ │ │ Show streams │ - │ │ │ Get user input │ - │ │ │ Filter streams │ - │ │ └─────────┬─────────┘ - │ │ │ - │ ▼ │ - │ ┌──────────────────┐ │ - │ │ Automatic Filter │ │ - │ │ (Best English + │ │ - │ │ Commentary) │ │ - │ └─────────┬────────┘ │ - │ │ │ - └────────────────┴───────────┘ - │ - ▼ - ┌────────────────────────────────┐ - │ Apply Codec Selection │ - │ (for selected streams only) │ - │ choose_audio_bitrate() │ - └────────────┬───────────────────┘ - │ - ▼ - ┌────────────────────────────────┐ - │ Build FFmpeg Command │ - │ (with selected audio streams) │ - └────────────┬───────────────────┘ - │ - ▼ - ┌────────────────────────────────┐ - │ Run FFmpeg Encoding │ - │ subprocess.run(cmd) │ - └────────────┬───────────────────┘ - │ - ▼ - ┌────────────────────────────────┐ - │ Success/Failure Handling │ - │ Log Results │ - └────────────┬───────────────────┘ - │ - ┌────────────┴─────────┐ - │ │ - Next file? Process Complete -``` - -## Component Interaction - -``` -┌─────────────┐ -│ main.py │ -└──────┬──────┘ - │ calls with (filter_audio, interactive_audio) - │ - ▼ -┌──────────────────────┐ -│ process_manager.py │ -├──────────────────────┤ -│ • Build config │ ◄─── Set "interactive" field -│ • For each file: │ in audio_filter_config -│ └─ run_ffmpeg() │ -└──────┬───────────────┘ - │ passes audio_filter_config - │ - ▼ -┌──────────────────────┐ -│ encode_engine.py │ -├──────────────────────┤ -│ • Check "enabled" │ ◄─── Decide which -│ • Check "interactive"│ filtering method -│ • Route to: │ to use -│ ├─ interactive path│ -│ └─ automatic path │ -└──────┬───────────────┘ - │ passes streams - │ - ▼ -┌──────────────────────┐ -│ audio_handler.py │ -├──────────────────────┤ -│ • Interactive: │ -│ prompt_user_...() │◄──── NEW FUNCTION -│ └─ Show & filter │ Shows prompt -│ │ Gets user input -│ • Automatic: │ Returns filtered -│ filter_audio_...() │ -│ └─ Logic filter │ -└──────────────────────┘ - │ returns filtered streams - │ - ▼ -┌──────────────────────┐ -│ encode_engine.py │ -├──────────────────────┤ -│ • Codec selection │ -│ • Build FFmpeg cmd │ -│ • Run encoding │ -└──────────────────────┘ -``` - ---- - -This architecture ensures clean separation of concerns: -- **main.py**: CLI interface -- **process_manager.py**: Orchestration & config building -- **encode_engine.py**: FFmpeg command building & execution -- **audio_handler.py**: Audio detection & stream filtering - -The interactive prompt is cleanly isolated in `audio_handler.py` and only called when needed. diff --git a/ENCODER_SWITCH.md b/ENCODER_SWITCH.md deleted file mode 100644 index e0ad594..0000000 --- a/ENCODER_SWITCH.md +++ /dev/null @@ -1,81 +0,0 @@ -# Dual Encoder Support - Implementation Complete ✅ - -## Features Added - -The transcoder now supports switching between two video encoders via the `--encoder` CLI option: - -### 1. **HEVC NVENC 10-bit** (Default) -- **Command**: `--encoder nvenc` or default (no flag needed) -- **Codec**: `hevc_nvenc` -- **Preset**: `slow` (high quality) -- **Bit Depth**: 10-bit -- **Pixel Format**: `yuv420p10le` -- **Use Case**: Best quality archival format, suitable for Plex compatibility - -### 2. **AV1 NVENC 8-bit** -- **Command**: `--encoder av1` -- **Codec**: `av1_nvenc` -- **Preset**: `p7` (high quality) -- **Bit Depth**: 8-bit -- **Pixel Format**: `yuv420p` -- **Use Case**: Maximum file size reduction, modern playback devices - -## Usage Examples - -```bash -# Default to HEVC NVENC 10-bit with smart resolution scaling -python main.py "C:\Videos\Movies" - -# Force AV1 NVENC 8-bit encoding -python main.py "C:\Videos\TV" --encoder av1 - -# AV1 with explicit resolution -python main.py "C:\Videos\Anime" --encoder av1 --r 1080 - -# AV1 with CQ mode at specific quality -python main.py "C:\Videos\Low-Res" --encoder av1 --cq 28 - -# AV1 with bitrate mode -python main.py "C:\Videos\Movies" --encoder av1 --m bitrate - -# HEVC (explicit, though it's the default) -python main.py "C:\Videos\TV" --encoder nvenc --cq 26 -``` - -## Configuration - -Encoder settings are stored in `config.xml`: - -```xml - - - - -``` - -The `default="nvenc"` attribute can be changed, but CLI `--encoder` flag always takes precedence. - -## Files Modified - -1. **config.xml** - Added `` section with both encoder configurations -2. **main.py** - Added `--encoder` argument, defaults to "nvenc" -3. **encode_engine.py** - Updated `run_ffmpeg()` to: - - Accept `encoder` parameter - - Dynamically set encoder codec, preset, bit depth, and pixel format - - Display encoder details in logging output -4. **process_manager.py** - Updated to: - - Accept and pass `encoder` parameter through processing pipeline - - Updated both Phase 1 (initial encode) and Phase 2 (bitrate retry) encode calls - -## Quality Notes - -| Aspect | HEVC NVENC | AV1 NVENC | -|--------|-----------|----------| -| **File Size** | ~80-90% of AV1 | Smallest (baseline) | -| **Quality** | Excellent | Excellent | -| **Preset** | slow (p6) | p7 | -| **Bit Depth** | 10-bit | 8-bit | -| **Compatibility** | Excellent (Plex) | Good (modern devices) | -| **Encoding Speed** | Fast | Fast | - -Both encoders use NVIDIA GPU acceleration (NVENC) for fast encoding. diff --git a/IMPLEMENTATION_COMPLETE.md b/IMPLEMENTATION_COMPLETE.md deleted file mode 100644 index 89c83bc..0000000 --- a/IMPLEMENTATION_COMPLETE.md +++ /dev/null @@ -1,280 +0,0 @@ -# Interactive Audio Stream Selection - Complete Implementation - -## Overview -✅ **COMPLETE** - Interactive audio stream selection feature has been successfully implemented. - -Users can now view all available audio streams in each video file and select which ones to keep for encoding, providing fine-grained control over audio track inclusion. - -## Features Implemented - -### 1. Stream Display ✅ -- Shows all audio streams with human-readable format -- Displays: Stream number, channel count, language code, bitrate -- Clear visual separation and organized layout -- Example: `Stream #0: 2ch | Lang: eng | Bitrate: 128kbps` - -### 2. User Input ✅ -- Accepts comma-separated stream indices: `0,1,3` -- Accepts single stream: `1` -- Accepts blank input (keep all streams) -- Input validation with helpful error messages -- Optional spaces in comma-separated list: `0, 1, 3` - -### 3. Filtering ✅ -- Removes non-selected streams from encoding -- Preserves original stream indices for FFmpeg mapping -- Logs all selections and removals -- Falls back to keeping all streams on invalid input - -### 4. CLI Integration ✅ -- New flag: `--interactive` (boolean) -- Works with `--filter-audio` flag -- Can be used independently (auto-enables filtering) -- Integrated into argument parser with help text - -### 5. Processing Pipeline ✅ -- Called from `run_ffmpeg()` in encode_engine.py -- Executed after stream detection -- Executed before codec selection -- Per-file prompting (allows different selections per video) - -### 6. Logging ✅ -- Logs user selections: `User selected X audio stream(s): [0, 1, 3]` -- Logs removed streams: `Removed X audio stream(s): [2]` -- Logs invalid input attempts -- Integrated with project's logging system - -## File Changes Summary - -### main.py -**Added**: -- `--interactive` argument to argparse -- Pass `args.interactive_audio` to `process_folder()` - -**Lines Changed**: 2 - -### core/process_manager.py -**Added**: -- `interactive_audio: bool = False` parameter to function signature -- Logic to set `audio_filter_config["interactive"]` based on CLI args -- Auto-enable filtering if `--interactive` used without `--filter-audio` - -**Lines Changed**: ~5 - -### core/encode_engine.py -**Added**: -- Import `prompt_user_audio_selection` -- Check for `audio_filter_config.get("interactive", False)` -- Route to interactive or automatic filtering accordingly - -**Lines Changed**: ~5 - -### core/audio_handler.py -**Added**: -- `prompt_user_audio_selection()` function (64 lines) -- Comprehensive docstring -- User-friendly output formatting -- Input validation and error handling -- Logging integration - -**Lines Changed**: +64 (new function) - -## Code Structure - -### Function: `prompt_user_audio_selection(streams: list) -> list` -**Location**: `core/audio_handler.py` (line 297) - -**Parameters**: -- `streams`: List of (index, channels, bitrate, language, metadata) tuples - -**Returns**: -- Filtered list containing only user-selected streams - -**Key Features**: -1. Early return if 0-1 streams (no selection needed) -2. Display header with visual formatting -3. Show each stream with index, channels, language, bitrate -4. Prompt for user input with examples -5. Parse comma-separated input -6. Validate stream indices -7. Handle edge cases (empty input, invalid input) -8. Log results to project logger -9. Return filtered streams ready for encoding - -**Error Handling**: -- ValueError on unparseable input → keep all -- No valid selections → keep all with warning -- Empty input → keep all (user confirmed) - -## Execution Flow - -``` -User runs: -$ python main.py "C:\Videos" --filter-audio --interactive - -↓ - -main.py parses arguments - - filter_audio = True (from --filter-audio) - - interactive_audio = True (from --interactive) - -↓ - -process_folder() called with both flags - -↓ - -For each video file: - └─ run_ffmpeg() called - └─ get_audio_streams() detects streams - └─ Check audio_filter_config.enabled - └─ True: Apply filtering - └─ Check audio_filter_config.interactive - └─ True: Call prompt_user_audio_selection() - └─ [INTERACTIVE PROMPT APPEARS] - └─ User sees streams and selects - └─ Returns filtered stream list - └─ False: Call filter_audio_streams() - └─ Automatic filtering (keep best English + Commentary) - └─ Process selected streams for encoding -``` - -## Usage Examples - -### Basic Interactive Mode -```bash -python main.py "C:\Videos\Movies" --filter-audio --interactive -``` - -### Combined with Other Options -```bash -python main.py "C:\Videos\TV" --filter-audio --interactive --cq 28 --r 1080 --language eng -``` - -### Interactive Without Explicit --filter-audio -```bash -python main.py "C:\Videos\Anime" --interactive -``` -(Filtering is auto-enabled with interactive mode) - -## Testing Scenarios - -### Scenario 1: Multiple Audio Languages -**Input**: Video with English (stereo), English (5.1), Spanish, Commentary -**Expected**: Prompt shows 4 streams, user can select any combination - -### Scenario 2: Invalid Selection -**Input**: User types "abc" or non-existent stream number -**Expected**: Tool logs warning, keeps all streams, continues - -### Scenario 3: Single Audio Stream -**Input**: Video with only one audio track -**Expected**: Function returns early, no prompt shown - -### Scenario 4: Empty Input -**Input**: User presses Enter without typing -**Expected**: All streams kept, confirmation message shown - -## Backward Compatibility - -✅ **Fully Backward Compatible** -- Existing `--filter-audio` behavior unchanged -- New feature is opt-in via `--interactive` flag -- Default behavior (no interactive) preserved -- No changes to config.xml schema required -- All existing scripts/automation continues to work - -## Integration Points - -### With Audio Language Tagging -- `--language eng --filter-audio --interactive` works together -- User selects streams, then language metadata applied to all - -### With Resolution/CQ Options -- `--filter-audio --interactive --cq 28 --r 1080` fully compatible -- Interactive selection happens first, encoding follows - -### With Test Mode -- `--filter-audio --interactive --test` shows interactive prompt on first file -- Useful for testing selections before batch encoding - -## Performance Impact - -✅ **Minimal Impact** -- Interactive prompt only appears when user explicitly requests it -- No performance overhead when `--interactive` not used -- Per-file prompt adds negligible time (user wait for input) -- No change to FFmpeg encoding performance - -## Documentation Provided - -1. **INTERACTIVE_AUDIO.md** - User guide with examples -2. **IMPLEMENTATION_NOTES.md** - Technical implementation details -3. **QUICK_REFERENCE.md** - Quick reference guide and FAQ -4. This summary document - -## Completion Checklist - -✅ Function implementation (prompt_user_audio_selection) -✅ CLI argument (--interactive) -✅ Integration with process_manager -✅ Integration with encode_engine -✅ Input validation -✅ Error handling -✅ Logging integration -✅ Backward compatibility -✅ Documentation -✅ Syntax validation -✅ Code review - -## Example Output - -When user runs with `--filter-audio --interactive`: - -``` -================================================================================ -🎵 AUDIO STREAM SELECTION -================================================================================ - -Stream #0: 2ch | Lang: eng | Bitrate: 128kbps - -Stream #1: 6ch | Lang: eng | Bitrate: 448kbps - -Stream #2: 2ch | Lang: spa | Bitrate: 128kbps - -Stream #3: 2ch | Lang: comment | Bitrate: 64kbps - -──────────────────────────────────────────────────────────────────────────── -Enter stream numbers to keep (comma-separated, e.g.: 1,2 or just 2) -Leave blank to keep all streams -──────────────────────────────────────────────────────────────────────────── -➜ Keep streams: 1,3 -✅ Keeping 2 stream(s), removing 2 stream(s) - -🎬 Running CQ encode: output.mkv -... -``` - -## Next Steps (Optional Enhancements) - -Future improvements could include: -- [ ] Preset buttons for common selections (e.g., "Best Audio", "English Only", "All") -- [ ] Auto-numbering display for clarity -- [ ] Arrow key selection interface (more interactive) -- [ ] Save/load selection templates for batch consistency -- [ ] GUI interface for stream selection -- [ ] Default selection from config for silent/batch operation - ---- - -## Summary - -The interactive audio stream selection feature is **complete and ready for use**. Users can now: - -1. ✅ See all available audio streams with details -2. ✅ Choose which streams to keep for encoding -3. ✅ Get immediate confirmation of their selection -4. ✅ Have per-file control in batch operations -5. ✅ Maintain automatic fallback if input is invalid - -The implementation is clean, well-documented, backward-compatible, and fully integrated into the existing codebase. diff --git a/IMPLEMENTATION_NOTES.md b/IMPLEMENTATION_NOTES.md deleted file mode 100644 index ec6bfe4..0000000 --- a/IMPLEMENTATION_NOTES.md +++ /dev/null @@ -1,141 +0,0 @@ -# Interactive Audio Stream Selection - Implementation Summary - -## Changes Made - -### 1. New Function: `prompt_user_audio_selection()` in audio_handler.py -- **Purpose**: Display audio streams and prompt user for selection -- **Input**: List of streams with (index, channels, bitrate, language, metadata) -- **Output**: Filtered list containing only user-selected streams -- **Features**: - - Displays stream info: `Stream #X: YYch | Lang: YYY | Bitrate: XYZkbps` - - Accepts comma-separated input: `1,2,3` or `1` or empty (keep all) - - Validates input and logs selections - - Falls back to keeping all streams on invalid input - -### 2. Updated: `run_ffmpeg()` in encode_engine.py -- Now checks `audio_filter_config.get("interactive", False)` -- Routes to interactive prompt if `interactive=True` -- Routes to automatic filtering if `interactive=False` -- Both modes filter streams before codec selection - -### 3. Updated: `process_folder()` in process_manager.py -- New parameter: `interactive_audio: bool = False` -- Builds audio_filter_config with both `enabled` and `interactive` fields -- If `--interactive` used without `--filter-audio`, enables both automatically - -### 4. Updated: main.py -- New CLI argument: `--interactive` -- Action: `store_true` (binary flag) -- Passed through to `process_folder()` -- Help text: "Interactive mode: show audio streams and let user select which to keep (requires --filter-audio)" - -## Usage Examples - -### Example 1: Automatic Filtering (Existing) -```bash -python main.py "C:\Videos" --filter-audio -``` -- Automatically keeps best English + Commentary -- No user interaction - -### Example 2: Interactive Selection (New) -```bash -python main.py "C:\Videos" --filter-audio --interactive -``` -- Shows each file's audio streams -- User picks which streams to keep -- Different selections per file allowed - -### Example 3: Interactive Without --filter-audio -```bash -python main.py "C:\Videos" --interactive -``` -- Same as Example 2 (enables filtering automatically) -- More intuitive UX - -## Stream Display Format - -When interactive mode runs, user sees: -``` -================================================================================ -🎵 AUDIO STREAM SELECTION -================================================================================ - -Stream #0: 2ch | Lang: eng | Bitrate: 128kbps - -Stream #1: 6ch | Lang: eng | Bitrate: 448kbps - -Stream #2: 2ch | Lang: spa | Bitrate: 128kbps - -──────────────────────────────────────────────────────────────────────────── -Enter stream numbers to keep (comma-separated, e.g.: 1,2 or just 2) -Leave blank to keep all streams -──────────────────────────────────────────────────────────────────────────── -➜ Keep streams: -``` - -## Logging Output - -When user selects streams: -``` -✅ Keeping 2 stream(s), removing 1 stream(s) - -User selected 2 audio stream(s): [1, 2] -Removed 1 audio stream(s): [0] -``` - -## Audio Filter Config Structure - -**Old (Automatic only)**: -```python -{ - "enabled": True/False -} -``` - -**New (With Interactive)**: -```python -{ - "enabled": True/False, - "interactive": True/False -} -``` - -## Flow Diagram - -``` -main.py - └─ parse args (--filter-audio, --interactive) - └─ process_folder() - └─ for each file: - └─ run_ffmpeg() - └─ get_audio_streams() - └─ if audio_filter_config.enabled: - ├─ if audio_filter_config.interactive: - │ └─ prompt_user_audio_selection() ← NEW - │ └─ [User sees streams and selects] - └─ else: - └─ filter_audio_streams() (automatic) - └─ encode with selected streams -``` - -## Input Validation - -- **Valid**: `1`, `0,1,3`, `2, 3, 5` (spaces OK) -- **Invalid**: `abc`, `1.5`, `1-3` (ranges not supported) -- **On Invalid**: Keep all streams, log warning - -## Edge Cases Handled - -1. **No streams**: Return original (nothing to filter) -2. **Single stream**: Return as-is (no selection needed) -3. **Invalid stream indices**: Keep all streams -4. **Empty input**: Keep all streams -5. **No valid selections**: Keep all streams (with warning) - -## Backward Compatibility - -- Existing `--filter-audio` behavior unchanged (automatic mode) -- `--interactive` is optional, defaults to False -- No breaking changes to config.xml structure -- Language tagging (--language) still works alongside audio filtering diff --git a/INTERACTIVE_AUDIO.md b/INTERACTIVE_AUDIO.md deleted file mode 100644 index 6298c91..0000000 --- a/INTERACTIVE_AUDIO.md +++ /dev/null @@ -1,109 +0,0 @@ -# Interactive Audio Stream Selection - -## Overview -The conversion tool now supports **interactive audio stream selection**, allowing you to manually choose which audio tracks to keep during encoding rather than relying on automatic filtering. - -## Usage - -### Enable Interactive Mode -Use both `--filter-audio` and `--interactive` flags together: - -```bash -python main.py "C:\path\to\videos" --filter-audio --interactive -``` - -### What Happens -When encoding each file with multiple audio streams: - -1. **Audio Stream Display** - - The tool displays all available audio streams with details: - ``` - 🎵 AUDIO STREAM SELECTION - ================================================================================ - - Stream #0: 2ch | Lang: eng | Bitrate: 128kbps - - Stream #1: 6ch | Lang: eng | Bitrate: 448kbps - - Stream #2: 2ch | Lang: spa | Bitrate: 128kbps - - Stream #3: 2ch | Lang: comment | Bitrate: 64kbps - ``` - -2. **User Prompt** - - You're asked to select which streams to keep: - ``` - ──────────────────────────────────────────────────────────────────────────── - Enter stream numbers to keep (comma-separated, e.g.: 1,2 or just 2) - Leave blank to keep all streams - ──────────────────────────────────────────────────────────────────────────── - ➜ Keep streams: 1,3 - ``` - -3. **Encoding -** - - Only selected streams are included in the encoded output - - Other streams are removed - - Selection is logged for reference - -## Input Format - -- **Multiple streams**: `0,1,3` or `0, 1, 3` (spaces optional) -- **Single stream**: `1` or `2` -- **Keep all**: Press Enter without typing anything - -## Example Scenarios - -### Scenario 1: Keep Main Audio Only -``` -Streams: - Stream #0: 2ch (English, 128kbps) - Stream #1: 6ch (English Surround, 448kbps) ← Best quality - Stream #2: 2ch (Spanish, 128kbps) - -Input: 1 - -Result: Only Stream #1 (6ch English Surround) is encoded -``` - -### Scenario 2: Keep Multiple Languages -``` -Streams: - Stream #0: 2ch (English, 128kbps) - Stream #1: 6ch (English Surround, 448kbps) - Stream #2: 2ch (Spanish, 128kbps) - Stream #3: 2ch (Commentary, 64kbps) - -Input: 1,2,3 - -Result: Streams #1, #2, and #3 are encoded (English Surround, Spanish, Commentary) -``` - -## Comparison with Automatic Filtering - -### Automatic Mode (--filter-audio only) -- Keeps: Best English audio + all Commentary tracks -- No user interaction -- Faster batch processing - -### Interactive Mode (--filter-audio --interactive) -- Shows all streams and asks user to choose -- Per-file control -- Better for selective archiving/organization -- Useful when automatic filtering doesn't match your preferences - -## Logging - -All user selections are logged to the conversion log for reference: -``` -User selected 2 audio stream(s): [1, 3] -Removed 1 audio stream(s): [2] -``` - -## Notes - -- Interactive mode requires `--filter-audio` to be enabled -- If you use `--interactive` without `--filter-audio`, filtering is automatically enabled -- Invalid input (non-existent stream numbers) falls back to keeping all streams -- Empty input keeps all audio streams unchanged -- The prompt appears for each file being encoded, allowing different selections per file diff --git a/LOGGING_STRUCTURE.md b/LOGGING_STRUCTURE.md deleted file mode 100644 index bf8abe9..0000000 --- a/LOGGING_STRUCTURE.md +++ /dev/null @@ -1,101 +0,0 @@ -# Structured Logging with Media Context - -## Overview - -The conversion system now uses **structured JSON logging** to enable organized analysis and filtering of conversion results by media type, show name, season, and episode. - -## Terminal vs Log Output - -- **Terminal Output**: Clean, human-readable print statements (VIDEO/AUDIO/PROGRESS sections) -- **Log Output**: Rich structured JSON with full media context for programmatic analysis - -## Media Context Fields - -Extracted automatically from file path structure: - -```python -{ - "video_filename": "episode01.mkv", - "media_type": "tv", # "tv", "anime", "movie", or "other" - "show_name": "Breaking Bad", - "season": "01", # Optional (TV/anime only) - "episode": "01" # Optional (TV/anime only) -} -``` - -## Usage Examples - -### Path Structure Recognition - -**TV Show**: -``` -P:\tv\Breaking Bad\season01\episode01.mkv -→ media_type: "tv", show_name: "Breaking Bad", season: "01", episode: "01" -``` - -**Anime**: -``` -P:\anime\Demon Slayer\season02\e12.mkv -→ media_type: "anime", show_name: "Demon Slayer", season: "02", episode: "12" -``` - -**Movie**: -``` -P:\movies\Inception.mkv -→ media_type: "movie", show_name: "Inception" -``` - -## Log Output Format - -JSON logs contain both the message and media context: - -```json -{ - "timestamp": "2026-02-22 15:30:45", - "level": "INFO", - "message": "✅ CONVERSION COMPLETE: episode01[EHX].mkv", - "video_filename": "episode01.mkv", - "media_type": "tv", - "show_name": "Breaking Bad", - "season": "01", - "episode": "01", - "method": "CQ", - "original_size_mb": 4096.5, - "output_size_mb": 1843.2, - "reduction_pct": 55.0 -} -``` - -## Filtering Logs Later - -You can parse the JSON logs to group by show/season/episode: - -```python -import json - -# Filter all Breaking Bad conversions -with open("logs/conversion.log") as f: - for line in f: - entry = json.loads(line) - if entry.get("show_name") == "Breaking Bad": - print(f"S{entry['season']}E{entry['episode']}: {entry['reduction_pct']}% reduction") -``` - -## Current Implementation - -**Files Updated**: -- `core/process_manager.py`: - - Added `get_media_context()` function to parse file paths - - Extracts media context once per file processing - - Passes context to all logging calls via `extra={}` parameter - -- `core/logger_helper.py`: - - JsonFormatter automatically includes all extra fields in output - - Added `log_event()` helper for consistent structured logging - -## Best Practices - -1. Always call `get_media_context()` once per file -2. Pass result to all logging calls for that file: `logger.info(msg, extra=media_context)` -3. For additional context: `logger.info(msg, extra={**media_context, "custom_field": value})` -4. Parse logs with JSON reader for reliable data extraction diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md deleted file mode 100644 index 094c1b3..0000000 --- a/PROJECT_STRUCTURE.md +++ /dev/null @@ -1,184 +0,0 @@ -# AV1 Batch Video Transcoder - Project Structure - -## Overview -A modular batch AV1 video transcoding system using NVIDIA's av1_nvenc codec (8-bit yuv420p) with intelligent audio/video processing, subtitle embedding, and optional audio language tagging. - -## Recent Changes (Latest Session) - -### Removed -- ❌ **Sonarr/Radarr integration** - Removed helper module, cache loading, and config sections (simplified to basic tagging) -- ❌ **Auto-rename functionality** - No longer renames based on Sonarr metadata -- ❌ **Web UI** - Removed `/webui` folder (can be added back if needed) -- ❌ **Rename tool** - Moved to separate `/rename` folder - -### Added -- ✅ **Subtitle detection & embedding** - Auto-finds .vtt, .srt, .ass, .ssa, .sub files (including language-prefixed like .en.vtt) -- ✅ **Subtitle cleanup** - Deletes embedded subtitle files after successful encoding -- ✅ **Test mode** (`--test`) - Encodes first file, shows compression ratio, doesn't move files -- ✅ **Optional language tagging** (`--language`) - Only tags audio if explicitly provided (default: no tagging) -- ✅ **Always output MKV** - Changed from using source extension to always outputting .mkv -- ✅ **Improved subtitle matching** - Finds both exact matches (video.vtt) and language-prefixed (video.en.vtt) - -### Refactored -- 🔧 **File structure reorganization**: Moved path_manager GUI, rename tool, and cache to separate folders -- 🔧 **Config simplification**: Removed Sonarr/Radarr sections, cleaner general settings -- 🔧 **Suffix handling**: Applied once during encoding, moved directly without re-tagging -- 🔧 **Audio language**: Changed from config-based default to CLI-only optional flag - -## Architecture - -### Entry Point -- **main.py** - CLI with argparse - - Arguments: `folder`, `--cq`, `--m {cq,bitrate}`, `--r {480,720,1080}`, `--test`, `--language` - - Loads config.xml, initializes logging - - Calls `process_folder()` from process_manager - -### Core Modules - -#### `core/config_helper.py` -- **`load_config_xml(config_path)`** - Parses XML configuration -- Returns dict with keys: - - `general`: processing_folder, suffix (" - [EHX]"), extensions, reduction_ratio_threshold, subtitles config - - `encode.cq`: CQ values per content type (tv_1080, tv_720, movie_1080, movie_720) - - `encode.fallback`: Bitrate fallback (Phase 2 retry) - - `audio`: Bitrate buckets for stereo/multichannel - - `path_mappings`: Windows ↔ Linux path conversion - -#### `core/logger_helper.py` -- Sets up logging to `logs/conversion.log` (INFO+) and console (DEBUG+) -- Separate failure logger for `logs/conversion_failures.log` -- Captures encoding decisions, bitrates, resolutions, timings - -#### `core/process_manager.py` -- **`process_folder(folder, cq, transcode_mode, resolution, config, tracker_file, test_mode, audio_language)`** - - Scans folder for video files - - **Per file**: Copy to temp, detect subtitles, analyze streams, encode, move, cleanup - - **Subtitle detection**: Looks for exact match + glob pattern (filename.*.ext) - - **Phase 1 (CQ)**: Try CQ-based encoding, check size threshold - - **Phase 2 (Bitrate)**: Retry failed files with bitrate mode - - **Cleanup**: Delete original + subtitle + temp copies on success -- **`_save_successful_encoding(...)`** - Moves file from temp → original folder - - File already has ` - [EHX]` suffix from temp_output filename - - Deletes original file, subtitle file, and temp copies - - Logs to CSV tracker - -#### `core/encode_engine.py` -- **`run_ffmpeg(input_file, output_file, cq, scale_width, scale_height, src_width, src_height, filter_flags, audio_config, method, bitrate_config, subtitle_file, audio_language)`** - - Builds FFmpeg command with av1_nvenc codec (preset p7, pix_fmt yuv420p) - - Per-stream audio codec/bitrate decisions - - Conditional subtitle input mapping (if subtitle_file provided) - - Optional audio language metadata (only if audio_language not None) - - Returns: (orig_size, out_size, reduction_ratio) - -#### `core/audio_handler.py` -- **`get_audio_streams(input_file)`** - Detects all audio streams with bitrate info -- **`choose_audio_bitrate(channels, avg_bitrate, audio_config, is_1080_class)`** - Returns (codec, target_bitrate) tuple - - Stereo 1080p: >192k → encode to 192k, ≤192k → copy - - Stereo 720p: >160k → encode to 160k, ≤160k → copy - - Multichannel: Encode to 384k (low) or 448k (medium) - -#### `core/video_handler.py` -- **`get_source_resolution(input_file)`** - ffprobe detection -- **`determine_target_resolution(src_width, src_height, explicit_resolution)`** - Smart scaling - - If >1080p → scale to 1080p - - Else → preserve source - - Override with `--r {480,720,1080}` - -## Workflow Example - -```bash -python main.py "P:\tv\Supernatural\Season 7" --language eng -``` - -**Processing:** -1. Scan folder for .mkv/.mp4 files -2. For each file: - - Copy to `processing/Supernatural - S07E01 - Pilot.mkv` - - Look for subtitle: `Supernatural - S07E01 - Pilot.en.vtt` ✓ found - - Detect source: 1920x1080 (1080p) ✓ - - Get audio streams: [AAC 2ch @ 192k, AC3 6ch @ 448k] - - Determine CQ: tv_1080 → CQ 28 - - Build FFmpeg command: - - Video: av1_nvenc (CQ 28) - - Audio 0: Copy AAC (≤192k already good) - - Audio 1: Re-encode AC3 to AAC 6ch @ 448k - - Subtitles: Input subtitle, map as srt stream, language=eng - - Output: `processing/Supernatural - S07E01 - Pilot - [EHX].mkv` - - FFmpeg runs, outputs ~400MB (original 1.2GB) - - Check size: 400/1200 = 33.3% < 75% ✓ SUCCESS - - Move: `processing/... - [EHX].mkv` → `P:\tv\Supernatural\Season 7/... - [EHX].mkv` - - Cleanup: Delete original + subtitle + temp copy - - Log to CSV - -**Result:** -- Original files gone -- New `Supernatural - S07E01 - Pilot - [EHX].mkv` (subtitle embedded, audio tagged with language=eng) - -## Configuration - -### config.xml Key Sections - -```xml - - processing - - [EHX] - .mkv,.mp4 - 0.75 - - true - .vtt,.srt,.ass,.ssa,.sub - srt - - - - - - 28 - 32 - 32 - 34 - - - - -``` - -## File Movements - -``` -Original: - P:\tv\Show\Episode.mkv (1.2GB) - P:\tv\Show\Episode.en.vtt - -During Encoding: - processing/Episode.mkv (temp copy) - processing/Episode - [EHX].mkv (encoding output) - -After Success: - P:\tv\Show\Episode - [EHX].mkv (1.2GB → 400MB) - (original .mkv deleted) - (original .en.vtt deleted) - (temp folder cleaned) -``` - -## Validation Checklist - -- ✅ All core modules import correctly -- ✅ Config loads without Sonarr/Radarr references -- ✅ Subtitle detection finds exact matches + language-prefixed files -- ✅ Audio language tagging only applied with --language flag -- ✅ Output always MKV regardless of source format -- ✅ Suffix applied once (in temp output filename) -- ✅ Subtitle files deleted with original files -- ✅ Test mode shows compression ratio and stops -- ✅ Phase 1 (CQ) and Phase 2 (Bitrate) retry logic works -- ✅ CSV tracking logs all conversions diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md deleted file mode 100644 index f4e6009..0000000 --- a/QUICK_REFERENCE.md +++ /dev/null @@ -1,182 +0,0 @@ -# Interactive Audio Selection - Quick Reference - -## Command Syntax - -### Enable Interactive Audio Selection -```bash -python main.py "C:\path\to\videos" --filter-audio --interactive -``` - -### Other Flags (Optional) -```bash ---filter-audio --interactive --cq 28 --r 1080 --language eng --test -``` - -## What User Sees - -### Per File Prompt (appears for each video) -``` -================================================================================ -🎵 AUDIO STREAM SELECTION -================================================================================ - -Stream #0: 2ch | Lang: eng | Bitrate: 128kbps - -Stream #1: 6ch | Lang: eng | Bitrate: 448kbps - -Stream #2: 2ch | Lang: spa | Bitrate: 128kbps - -Stream #3: 2ch | Lang: comment | Bitrate: 64kbps - -──────────────────────────────────────────────────────────────────────────── -Enter stream numbers to keep (comma-separated, e.g.: 1,2 or just 2) -Leave blank to keep all streams -──────────────────────────────────────────────────────────────────────────── -➜ Keep streams: -``` - -### User Input Examples - -| Input | Result | -|-------|--------| -| `1` | Keep Stream #1 (6ch English, 448kbps) | -| `1,3` | Keep Streams #1 and #3 | -| `0,1,2` | Keep Streams #0, #1, and #2 | -| ` ` (blank) | Keep all 4 streams | - -### Expected Output -``` -✅ Keeping 1 stream(s), removing 3 stream(s) - -🎬 Running CQ encode: output.mkv -... -``` - -## Features - -✅ **Per-File Control**: Different selections for each video -✅ **Clear Display**: See channel count, language, bitrate for each stream -✅ **Flexible Input**: Comma-separated numbers, optional spaces -✅ **Safe Defaults**: Invalid input keeps all streams -✅ **Logging**: All selections recorded in conversion log -✅ **Backwards Compatible**: Doesn't break existing workflows - -## Common Scenarios - -### Movie with Multiple Audio Tracks -``` -Stream #0: 2ch English (128kbps) -Stream #1: 6ch English Surround (448kbps) ← Main audio -Stream #2: 2ch Spanish (128kbps) -Stream #3: 2ch Commentary (64kbps) - -Input: 1,3 -Output: Keep English 5.1 + Commentary -``` - -### TV Episode with Multiple Languages -``` -Stream #0: 6ch English (384kbps) -Stream #1: 6ch Spanish (384kbps) -Stream #2: 2ch Commentary (64kbps) - -Input: 0,1,2 -Output: Keep all (English, Spanish, Commentary) -``` - -### File with Only One Audio Track -``` -Stream #0: 6ch English (448kbps) - -Input: (blank or 0) -Output: Keep the only track -``` - -## FAQ - -**Q: What if I provide an invalid stream number?** -A: The tool keeps all streams and logs a warning. - -**Q: Can I specify stream ranges like "0-2"?** -A: No, use comma-separated individual numbers: "0,1,2" - -**Q: Do I have to answer the prompt for every file?** -A: Yes, this allows different selections per file. Use automatic --filter-audio mode if you want consistent filtering across all files. - -**Q: What happens with invalid input like "abc" or "1.5"?** -A: The tool keeps all streams and logs the invalid input. Then continues to the next file. - -**Q: Does --interactive work alone?** -A: Yes! If you use --interactive without --filter-audio, filtering is automatically enabled with interactive mode. - -**Q: Can I combine this with --language tagging?** -A: Yes! Use: `--filter-audio --interactive --language eng` -This lets you select streams AND tag them with language metadata. - -## Integration Points - -### When Called -- After audio stream detection in `run_ffmpeg()` -- Before codec selection and FFmpeg command building -- Only if `audio_filter_config.enabled = True` AND `audio_filter_config.interactive = True` - -### Stream Information Provided -- **Index**: Stream number in FFmpeg (0-based) -- **Channels**: 2ch, 6ch, etc. -- **Language**: eng, spa, und (undefined), etc. -- **Bitrate**: Detected bitrate in kbps - -### What Gets Removed -- All streams NOT selected by user -- Metadata and descriptors for removed streams -- No re-encoding of audio (codec decisions apply per stream) - -## Tips & Tricks - -### Keeping Only Surround Audio -Most videos have stereo + surround. To keep only 5.1/6ch: -``` -Input: 1 (if Stream #1 is 6ch) -``` - -### Keeping All Commentary -Commentary tracks are usually indexed separately: -``` -Input: 0,2,3 (Stream #0 main + #2 and #3 commentary) -``` - -### English Only -If you have multiple languages: -``` -Input: 0,1 (Stream #0 and #1 English only) -``` - -## Log Output Examples - -**Successful Selection** -``` -User selected 2 audio stream(s): [1, 3] -Removed 1 audio stream(s): [0, 2] -``` - -**Invalid Input** -``` -User provided invalid audio selection input -Keeping all audio streams -``` - -**No Selection** -``` -Keeping all audio streams -``` - -## Troubleshooting - -**Issue**: Prompt doesn't appear -- **Solution**: Make sure both --filter-audio AND --interactive are specified - -**Issue**: Selection is ignored -- **Solution**: Check log file for errors. Verify stream indices exist. - -**Issue**: Want automatic mode back -- **Solution**: Use --filter-audio alone (without --interactive) diff --git a/README.md b/README.md index 1e8bb9f..a1bb447 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,57 @@ # AV1 Batch Video Transcoder -A high-performance batch video transcoding tool using NVIDIA's **AV1 NVENC** codec with intelligent audio/subtitle handling and automatic quality optimization. +A high-performance batch video transcoding tool using NVIDIA's **AV1 NVENC** or **HEVC NVENC** codec with intelligent audio/subtitle handling, interactive stream selection, and automatic quality optimization. + +--- + +## 📋 Table of Contents + +1. [Key Features](#-key-features) +2. [Quick Start](#-quick-start) +3. [Installation](#installation) +4. [Basic Usage](#basic-usage) +5. [Command Reference](#command-reference) +6. [Batch Processing](#batch-processing) +7. [Audio Options](#audio-options) +8. [Undefined Language Replacement](#undefined-language-und-replacement) +9. [Encoder Selection](#encoder-selection) +10. [Configuration](#configuration) +11. [Project Structure](#project-structure) +12. [Encoding Process](#encoding-process) +13. [Troubleshooting](#troubleshooting) + +--- ## ✨ Key Features -- **8-bit AV1 Encoding** - NVIDIA GPU acceleration (yuv420p, preset p7) -- **Smart Audio Processing** - Auto-detects bitrate, AAC for stereo, EAC3 for 5.1, downmixes, re-encodes only when needed -- **Audio Filtering** - Keep only best English audio + Commentary tracks (remove other languages) +### Video Encoding +- **Dual Encoder Support** - NVIDIA AV1 NVENC (8-bit, default) or HEVC NVENC (10-bit) +- **Smart Resolution** - Detects source resolution, scales 4K→1080p, preserves lower resolutions +- **GPU Acceleration** - Hardware encoding via NVIDIA NVENC (RTX 2060+) +- **Two-Phase Mode** - CQ mode first, automatic Bitrate fallback if size threshold exceeded +- **Quality Presets** - Per-content-type CQ values (TV/Movies, 720p/1080p) + +### Audio Processing +- **Smart Audio Processing** - Auto-detects bitrate, AAC for stereo, EAC3 for 5.1 +- **Stream Selection** - Keep only best English + Commentary (automatic) or choose manually (interactive) +- **Interactive Selection** - View all audio streams and select which ones to keep per file +- **Channel Control** - Force specific channel counts (2-channel stereo or 6-channel 5.1 surround) +- **Audio Titles** - Rename/tag audio streams with custom titles +- **Language Tagging** - Optional language code metadata for audio streams + +### Subtitles & Metadata - **Subtitle Embedding** - Auto-detects and embeds subtitles (.vtt, .srt, .ass, .ssa, .sub) -- **Smart Resolution** - Scales 4K→1080p, preserves lower resolutions -- **Two-Phase Encoding** - CQ mode first, automatic Bitrate fallback if size threshold exceeded +- **Language-Prefixed Subtitles** - Finds language-specific files (movie.en.vtt, movie.eng.vtt) +- **Automatic Cleanup** - Deletes subtitle files after embedding + +### Processing Features +- **CSV Tracking** - Detailed conversion logs with compression ratios and media context - **Automatic Cleanup** - Deletes originals + subtitles after successful encoding - **Test Mode** - Encode one file, check compression ratio before batch processing -- **Optional Language Tagging** - Tag audio streams with language codes -- **CSV Tracking** - Detailed conversion logs with compression ratios +- **Structured Logging** - JSON logs with media type, show name, season/episode context +- **File Tagging** - Output files get ` - [EHX]` suffix for easy identification + +--- ## 🚀 Quick Start @@ -22,7 +60,7 @@ A high-performance batch video transcoding tool using NVIDIA's **AV1 NVENC** cod - **Python 3.8+** - **FFmpeg** with libfdk-aac support - **NVIDIA GPU** (GeForce RTX 2060+, Quadro, or newer) -- **NVIDIA CUDA Toolkit** (for av1_nvenc support) +- **NVIDIA CUDA Toolkit** (for NVENC support) ### Installation @@ -38,9 +76,12 @@ cd conversion_project ### Basic Usage ```bash -# Encode a TV folder (smart mode) +# Encode a TV folder (smart mode with AV1, default) python main.py "P:\tv\Show Name" +# Use HEVC encoder (10-bit, better Plex compatibility) +python main.py "P:\tv\Show Name" --encoder nvenc + # Test single file before batch processing python main.py "P:\tv\Show Name" --test @@ -55,27 +96,607 @@ python main.py "P:\movies" --r 720 # Tag audio with language python main.py "P:\tv\Show" --language eng + +# Interactive audio selection +python main.py "P:\tv\Show" --filter-audio --interactive ``` -## 📖 Documentation +--- -- **[Full Usage Guide](README_RESTRUCTURE.md)** - Detailed commands, features, troubleshooting -- **[Technical Architecture](PROJECT_STRUCTURE.md)** - Module breakdown, workflow, config reference +## Command Reference + +### Encoder Selection +```bash +--encoder {av1,nvenc} +``` +- `av1` (default): AV1 NVENC, 8-bit, smaller files (~baseline compression) +- `nvenc`: HEVC NVENC, 10-bit, larger files (~80-90% of AV1), better compatibility + +### Quality & Mode +```bash +--cq # Use CQ mode with specific quality (0-51, lower=better) +--m {cq,bitrate} # Force encoding mode (default: smart two-phase) +``` + +### Resolution +```bash +--r {480,720,1080} # Force specific output resolution + # Default: scale 4K→1080p, preserve lower +``` + +### Audio Options +```bash +--filter-audio # Enable automatic audio filtering (keep English + Commentary) +--interactive # Interactive mode: show audio streams, user selects which to keep +--audio-select "0,1" # Pre-select specific audio streams (indices) +--audio-titles "0:English,1:Commentary" # Rename/title audio tracks +--audio-channels "0:2,1:6" # Force channel count (2=stereo, 6=5.1 surround) +--language eng # Tag audio streams with language code +--default-language spa # Set default language for undefined (und) audio tracks +--no-replace-und # Disable undefined language tag replacement +``` + +### Undefined Language (und) Replacement + +Automatically replaces undefined (`und`) audio language tags with a configurable default language. Most useful when your video library has audio with undefined language tags that should actually be marked as a specific language. + +#### Configuration + +Add to `config.xml` in the `` section: + +```xml + + +eng + + +true +``` + +#### CLI Arguments + +**`--default-language `** - Override the default language for a single run: +```bash +python main.py "P:\movies\Collection" --default-language spa +python main.py "P:\tv\Show" --default-language fra +``` + +**`--no-replace-und`** - Disable undefined language replacement for a single run: +```bash +python main.py "P:\movies\Collection" --no-replace-und +``` + +#### Behavior + +**Priority Chain** (highest to lowest): +1. `--language eng` (CLI): Tags ALL audio streams as 'eng' +2. `--default-language spa` + detected `und`: Replaces with 'spa' +3. config.xml `default_language: eng` + detected `und`: Replaces with 'eng' +4. `--no-replace-und` flag: Skips all `und` replacement + +**Key Points:** +- Only affects 'und' language tags (existing language tags are preserved) +- Works with batch processing (`--paths-file`) +- Compatible with TV/movie/anime detection +- Works in CQ mode, Bitrate mode, and Smart mode + +#### Common Language Codes + +| Code | Language | Code | Language | +|------|----------|------|----------| +| eng | English | jpn | Japanese | +| spa | Spanish | kor | Korean | +| fra | French | zho | Chinese (Mandarin) | +| deu | German | rus | Russian | +| ita | Italian | por | Portuguese | + +Full list: https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes + +#### Examples + +**Example 1: Most content is English with `und` tags** +```bash +# config.xml already has: eng +python main.py "P:\movies" +# Result: All 'und' audio streams are tagged as 'eng' +``` + +**Example 2: Mixed language batch with different defaults** +``` +# paths.txt +"P:\movies\English Collection" --default-language eng +"P:\movies\Spanish Collection" --default-language spa +"P:\movies\Mixed" --no-replace-und +``` +```bash +python main.py --paths-file paths.txt +``` + +### Processing +```bash +--test # Test mode: encode first file, show compression ratio, don't move +``` + +--- + +## Batch Processing + +### Overview + +Process multiple folders sequentially with different parameters for each. Perfect for encoding your entire media library with specific settings per movie/show. + +### Quick Start + +```bash +# Simple list format (paths.txt) +python main.py --paths-file paths.txt + +# CSV format with custom parameters (paths_batch.csv) +python main.py --paths-file paths_batch.csv +``` + +### File Formats + +#### Format 1: Simple List (paths.txt) + +One path per line, with optional per-row parameters: + +``` +P:\movies\Nobody 2 (2025) +P:\movies\The French Dispatch (2021) --r 720 +P:\movies\Let's Be Cops (2014) --r 720 --cq 28 +P:\movies\Akira (1988) --encoder av1 --strip-all-titles +## Batch Processing + +### Overview + +Process multiple folders sequentially with different parameters for each. This replaces the need for manual `.bat` files or shell scripts. Perfect for encoding your entire media library with specific settings per movie/show. + +### Quick Start + +#### Simple List Format (paths.txt) + +One path per line, with optional per-row parameters: + +``` +P:\movies\Nobody 2 (2025) +P:\movies\The French Dispatch (2021) --r 720 +P:\movies\Let's Be Cops (2014) --r 720 --cq 28 +P:\movies\Akira (1988) --encoder av1 --strip-all-titles +``` + +**Usage:** +```bash +python main.py --paths-file paths.txt +``` + +**With base parameters** (applied to all rows unless overridden): +```bash +python main.py --paths-file paths.txt --encoder hevc --strip-all-titles +``` + +#### CSV Format (paths_batch.csv) + +First column is path, remaining columns are optional parameters: + +```csv +path,resolution,cq,encoder,notes +"P:\movies\Nobody 2 (2025)","--r 1080","--cq 32","--encoder hevc","4K source" +"P:\movies\The French Dispatch (2021)","--r 720","--cq 28","--encoder av1","720p source" +"P:\movies\Let's Be Cops (2014)","--r 720" +"P:\movies\Akira (1988)","","--cq 26" +``` + +**Usage:** +```bash +python main.py --paths-file paths_batch.csv +``` + +The CSV header row is ignored - use it for your own documentation. + +### Supported List Formats + +#### Simple List (One per line) + +``` +# Path only (uses base parameters) +P:\movies\Movie1 + +# Path with space-separated parameters +P:\movies\Movie2 --r 720 +P:\movies\Movie3 --r 720 --cq 28 --encoder av1 + +# Quoted paths with spaces +"P:\movies\Movie with Spaces" --r 720 + +# Comment out paths with # +# P:\movies\Disabled --r 480 +``` + +#### CSV Format (Recommended for complex setups) + +First column is always the path. Remaining columns can be any parameters: + +```csv +path,resolution,quality,encoder,language +"P:\movies\Movie1","--r 1080","--cq 32","--encoder hevc","--language eng" +"P:\movies\Movie2","--r 720","--cq 28","--encoder av1" +"P:\movies\Movie3","--r 720" +``` + +### Per-Row Parameter Override + +Row parameters **override** base CLI parameters for that specific path. + +**Example:** + +```bash +# Base command with --strip-all-titles applied to all +python main.py --paths-file paths.txt --strip-all-titles +``` + +With `paths.txt`: +``` +P:\movies\Movie1 # Uses --strip-all-titles from base +P:\movies\Movie2 --keep-all-titles # OVERRIDES to keep titles, still strips +P:\movies\Movie3 --encoder av1 # OVERRIDES encoder to av1 +P:\movies\Movie4 --r 720 --cq 28 # OVERRIDES resolution and quality +``` + +### All Supported Per-Row Parameters + +Any CLI parameter can be used in rows: + +``` +--r {480,720,1080} # Resolution +--cq # CQ quality (0-51, lower is better) +--m {cq,bitrate} # Encoding mode +--encoder {av1,hevc} # Video encoder +--audio-channels "0:2,1:6" # Force audio channels +--audio-titles "0:English,1:Commentary" # Audio track names +--audio-select "0,1" # Pre-select audio streams +--language eng # Language tag +--filter-audio # Enable audio filtering +--strip-all-titles # Remove audio titles +--keep-all-titles # Preserve audio titles +--unforce-subs # Remove forced subtitle flag +--no-encode # Mux only, no encoding +--test # Test mode (first file only) +--crop-height # Crop to height +--color-bit {8,10} # Color bit depth (HEVC only) +``` + +### Real-World Examples + +**Example 1: Movie Library with Varying Quality** + +`movies.txt`: +``` +P:\movies\4K Movies\Movie1 --r 1080 --cq 26 +P:\movies\4K Movies\Movie2 --r 1080 --cq 26 +P:\movies\720p Movies\Movie3 --r 720 --cq 28 +P:\movies\Anime\Anime1 --encoder av1 --cq 26 +P:\movies\Anime\Anime2 --encoder av1 --cq 26 --audio-channels "0:2" +``` + +```bash +python main.py --paths-file movies.txt --encoder hevc --strip-all-titles +``` + +**Example 2: TV Shows by Season** + +`tv.csv`: +```csv +path,params +"P:\tv\Breaking Bad\Season 1","--cq 26" +"P:\tv\Breaking Bad\Season 2","--cq 28" +"P:\tv\Breaking Bad\Season 3","--cq 28 --r 720" +"P:\tv\Game of Thrones\Season 1","--encoder hevc --cq 24" +"P:\tv\Game of Thrones\Season 2","--encoder hevc --cq 26" +``` + +```bash +python main.py --paths-file tv.csv --filter-audio +``` + +**Example 3: Mixed Content with Language Tags** + +`archive.txt`: +``` +# Movies +P:\content\Movies\Action\MovieA --r 1080 --cq 28 --encoder hevc +P:\content\Movies\Drama\MovieB --r 1080 --cq 26 --encoder hevc + +# TV Shows +P:\content\TV\Show1\Season1 --cq 28 --filter-audio +P:\content\TV\Show2\Season1 --cq 26 --filter-audio --language eng + +# Anime +P:\content\Anime\SeriesA --encoder av1 --cq 24 +P:\content\Anime\SeriesB --encoder av1 --cq 26 --audio-channels "0:2" +``` + +```bash +python main.py --paths-file archive.txt --strip-all-titles +``` + +### Batch Processing Output + +During execution, you'll see detailed progress: + +``` +════════════════════════════════════════════════════════════════════════════════ +BATCH MODE: Processing paths from paths.txt +════════════════════════════════════════════════════════════════════════════════ + +──────────────────────────────────────────────────────────────────────────────── +BATCH [1/5]: P:\movies\Movie1 +──────────────────────────────────────────────────────────────────────────────── + +[... encoding output ...] + +✓ [BATCH 1/5] Completed: Movie1 + +──────────────────────────────────────────────────────────────────────────────── +BATCH [2/5]: P:\movies\Movie2 +Parameters: --r 720 +──────────────────────────────────────────────────────────────────────────────── + +[... encoding output ...] + +✓ [BATCH 2/5] Completed: Movie2 + +[... continues ...] + +════════════════════════════════════════════════════════════════════════════════ +✓ BATCH PROCESSING COMPLETE: Processed 5 path(s) +════════════════════════════════════════════════════════════════════════════════ +``` + +**Key features:** +- Each path prefixed with `[BATCH X/Y]` in logs +- If a path fails, processing continues to next path +- Overall summary shows completion status + +### Batch Processing Tips & Best Practices + +#### 1. Test Before Full Batch + +Use `--test` flag per-row to verify settings: + +``` +P:\movies\TestMovie --test --r 720 --cq 28 +P:\movies\Movie1 --r 720 --cq 28 +P:\movies\Movie2 --r 720 --cq 28 +``` + +#### 2. Start Simple, Add Complexity + +```bash +# Phase 1: Just paths +python main.py --paths-file paths.txt + +# Phase 2: Add base parameters +python main.py --paths-file paths.txt --encoder hevc + +# Phase 3: Add per-row customization in the file +``` + +#### 3. Monitor Progress + +Check logs in `logs/conversion.log` during batch processing: +- Each entry includes `[BATCH X/Y]` prefix +- All parameters are logged per file +- Search for errors with the batch prefix + +#### 4. CSV Format Tips + +- Quote paths with spaces: `"P:\path with spaces"` +- Leave empty cells for defaults: `"P:\movies\Movie1","--r 720"` (uses default CQ) +- Or combine in one cell: `"P:\movies\Movie1","--r 720 --cq 28"` + +#### 5. Line Endings + +- Both Unix (LF) and Windows (CRLF) line endings work +- Avoid mixing line endings in the same file + +#### 6. Encoding Issues + +If you get encoding errors: +- Ensure file is saved as UTF-8 +- Use English characters in parameter values +- Avoid special characters in paths + +### Migration from .bat Files + +**Old .bat approach:** +```batch +python main.py "P:\movies\Movie1" +python main.py "P:\movies\Movie2" --r 720 +python main.py "P:\movies\Movie3" --r 720 --cq 28 +``` + +**New --paths-file approach:** +```bash +python main.py --paths-file paths.txt +``` + +With `paths.txt`: +``` +P:\movies\Movie1 +P:\movies\Movie2 --r 720 +P:\movies\Movie3 --r 720 --cq 28 +``` + +**Benefits:** +- Single command to start batch +- All parameters visible in one file +- Easier to edit and maintain +- Better error handling (continues on failure) +- Unified logging with batch context + +--- + +## Audio Options + +### Three Main Audio Control Methods + +| Option | Purpose | Format | Example | +|--------|---------|--------|---------| +| `--audio-select` | Pre-select which streams to keep | Comma-separated indices | `--audio-select "0,1,2"` | +| `--audio-titles` | Rename/title tracks | `index:title` pairs | `--audio-titles "0:English,1:Commentary"` | +| `--audio-channels` | Set channel count per track | `index:channels` pairs | `--audio-channels "0:2,1:6"` | + +### Audio Filtering + +**Automatic Filtering** (`--filter-audio`): +```bash +python main.py "C:\Videos" --filter-audio +``` +- Automatically keeps best English audio + all Commentary tracks +- Removes other languages +- No user interaction + +**Interactive Filtering** (`--filter-audio --interactive`): +```bash +python main.py "C:\Videos" --filter-audio --interactive +``` +- Shows all audio streams for each file +- User selects which streams to keep +- Different selections allowed per file + +**Stream Display Format**: +``` +🎵 AUDIO STREAM SELECTION +================================================================================ + +Stream #0: 2ch | Lang: eng | Bitrate: 128kbps + +Stream #1: 6ch | Lang: eng | Bitrate: 448kbps + +Stream #2: 2ch | Lang: spa | Bitrate: 128kbps + +──────────────────────────────────────────────────────────────────────────── +Enter stream numbers to keep (comma-separated, e.g.: 1,2 or just 2) +Leave blank to keep all streams +──────────────────────────────────────────────────────────────────────────── +➜ Keep streams: 1,3 +``` + +### Audio Channel Control + +**--audio-channels** allows forcing specific channel configurations: + +```bash +# Force stereo for all tracks +python main.py "C:\Videos" --audio-channels "0:2,1:2" + +# Keep 5.1 main audio, compress commentary to stereo +python main.py "C:\Videos" --audio-channels "0:6,1:2" --audio-titles "0:English 5.1,1:Commentary" +``` + +**Valid Channels**: 2 (stereo) or 6 (5.1 surround) + +**Automatic Bitrate Selection**: +| Resolution | Stereo (2ch) | 5.1 Surround (6ch) | +|-----------|------------|-----------------| +| 1080p | 192 kbps | 384 kbps | +| 720p | 160 kbps | 320 kbps | + +### Audio Filtering Priority + +When determining output channels for a stream: +1. **User-specified via `--audio-channels`** (highest priority) +2. **Commentary detection** (if titled "Commentary", forces 2-channel) +3. **Auto-detection** (default, based on resolution and source channels) + +### Common Audio Scenarios + +**Scenario 1: TV Show with Commentary** +```bash +python main.py "C:\TV" \ + --audio-channels "0:2,1:2" \ + --audio-titles "0:English,1:Commentary" +``` + +**Scenario 2: Movie Collection (Multiple Languages)** +```bash +python main.py "C:\Movies" \ + --audio-select "0,1,2" \ + --audio-channels "0:6,1:2,2:2" \ + --audio-titles "0:Japanese 5.1,1:English Stereo,2:Spanish Stereo" +``` + +**Scenario 3: Anime (All Stereo)** +```bash +python main.py "C:\Anime" --audio-channels "0:2,1:2,2:2" +``` + +--- + +## Encoder Selection + +### Dual Encoder Support + +**AV1 NVENC (Default)** +```bash +python main.py "C:\Videos" --encoder av1 +``` +- **Codec**: av1_nvenc +- **Preset**: p7 (high quality) +- **Bit Depth**: 8-bit +- **Pixel Format**: yuv420p +- **File Size**: Smallest (~baseline) +- **Use Case**: Maximum file size reduction, modern playback devices + +**HEVC NVENC** +```bash +python main.py "C:\Videos" --encoder nvenc +``` +- **Codec**: hevc_nvenc +- **Preset**: slow (high quality) +- **Bit Depth**: 10-bit +- **Pixel Format**: yuv420p10le +- **File Size**: ~80-90% of AV1 size +- **Use Case**: Best quality archival, excellent Plex compatibility + +### Quality Comparison + +| Aspect | HEVC NVENC | AV1 NVENC | +|--------|-----------|----------| +| **File Size** | ~80-90% of AV1 | Smallest (baseline) | +| **Quality** | Excellent | Excellent | +| **Preset** | slow (p6) | p7 | +| **Bit Depth** | 10-bit | 8-bit | +| **Compatibility** | Excellent (Plex) | Good (modern devices) | +| **Encoding Speed** | Fast | Fast | + +--- ## ⚙️ Configuration -Edit `config.xml` to customize: +Edit `config.xml` to customize encoding parameters: +### Video Encoder Settings +```xml + + + + +``` + +### CQ Quality Per Content Type ```xml - 28 32 32 34 +``` - +### Audio Bitrate Buckets +```xml +``` - +### Bitrate Fallback (Phase 2) +```xml + + 8000 + 6000 + 4000 + +``` + +### Subtitle Detection +```xml true .vtt,.srt,.ass,.ssa,.sub + subrip ``` +### General Settings +```xml + + .\processing + - [EHX] + .mkv,.mp4,.avi,.m2ts + 0.75 + +``` + +--- + +## Project Structure + +``` +conversion_project/ +├── main.py - CLI entry point for batch transcoding +├── config.xml - Configuration (encoding settings, audio buckets, etc.) +│ +├── core/ - Core modules +│ ├── config_helper.py - XML configuration loader +│ ├── logger_helper.py - Logging setup (JSON structured logs) +│ ├── process_manager.py - Main transcoding orchestration +│ ├── encode_engine.py - FFmpeg command builder and execution +│ ├── audio_handler.py - Audio stream analysis, bitrate decisions, interactive selection +│ ├── video_handler.py - Video resolution detection and scaling logic +│ └── hardware_helper.py - Hardware detection (GPU/CPU) +│ +├── /rename/ - Separate rename utility (rolling_rename.py) +├── /path_manager/ - GUI path management +│ ├── gui_path_manager.py +│ ├── transcode.bat +│ ├── paths.txt +│ └── cache/ +│ +├── logs/ - Log files and conversion tracker CSV +├── processing/ - Temporary encoding files (cleaned up after move) +└── README.md - This file +``` + +--- + +## Encoding Process + +### Per-File Workflow + +For each video file in the target folder: + +1. **Detect Subtitles** + - Looks for exact match: `Video.vtt`, `Video.srt`, etc. + - Also searches for language-prefixed: `Video.en.vtt`, `Video.eng.vtt` + +2. **Analyze Source** + - Detect resolution (width × height) + - Extract audio streams (codec, channels, bitrate, language) + - Extract video codec and frame rate + +3. **Determine Parameters** + - **Target Resolution**: Scale 4K→1080p, preserve lower resolutions (or use `--r` override) + - **CQ Value**: Select based on content type and resolution (tv_1080, movie_720, etc.) + - **Audio Processing**: + - Apply filtering/selection if specified + - Choose codec (AAC for stereo, EAC3 for 5.1) + - Choose bitrate (depends on resolution and stream quality) + +4. **FFmpeg Encode** + - **Video**: AV1 NVENC 8-bit yuv420p (or HEVC NVENC 10-bit) + - **Audio**: Per-stream decisions (copy if good quality, re-encode if excessive bitrate) + - **Subtitles**: Embed as SRT (if found) + +5. **Size Validation** + - Compare output size vs original size + - If output > 75% of original (default threshold), retry with Phase 2 (bitrate mode) + +6. **Move File** + - Move from temp folder → original location + - Add ` - [EHX]` suffix to filename + - Delete original file and subtitle file + +7. **Logging** + - Record to CSV tracker: filename, original size, output size, compression ratio + - Structured JSON logging with media context (show name, season, episode) + +### Audio Encoding Logic + +``` +For each audio stream: + + Stereo audio (2 channels)? + ├─ YES + 1080p: + │ ├─ If >192kbps → ENCODE to 192 kbps AAC + │ └─ If ≤192kbps → COPY (no re-encoding) + ├─ YES + 720p: + │ ├─ If >160kbps → ENCODE to 160 kbps AAC + │ └─ If ≤160kbps → COPY (no re-encoding) + │ + └─ NO (Multichannel/5.1+): + ├─ ENCODE to 384 kbps or 448 kbps EAC3 (depending on resolution) +``` + +--- + +## 📊 Example Output + +**Input File:** +``` +SupernaturalS07E21.mkv (size: 1.5GB, 6-channel AC3 384kbps, 1920x1080) +SupernaturalS07E21.en.vtt (subtitle) +``` + +**Processing:** +``` +Detected: 1920x1080 (1080p) → TV episode CQ 28 +Audio: 6ch AC3 384kbps (needs re-encoding for 5.1) +Subtitle: Detected SupernaturalS07E21.en.vtt +``` + +**Output File:** +``` +SupernaturalS07E21 - [EHX].mkv (size: 420MB, EAC3 384kbps, subtitle embedded) +Compression: 1500MB → 420MB (72% reduction) +``` + +--- + +## Troubleshooting + +### Common Issues + +**"FFmpeg not found"** +- Ensure FFmpeg is installed and in your system PATH +- Test with: `ffmpeg -version` + +**"GPU out of memory"** +- NVIDIA GPU is full, close other GPU applications +- Or reduce resolution with `--r 720` + +**"Subtitle not embedding"** +- Ensure subtitle file is in same folder as video +- Check subtitle extension is in config.xml (vtt, srt, ass, etc.) +- Verify filename matches exactly (e.g., `Video.en.vtt`) + +**"No audio streams detected"** +- Run `ffprobe input.mkv` to verify audio streams exist +- Audio may be embedded as data track, not audio track + +**"CUDA Kernel Error" during encoding** +- GPU driver is outdated, update NVIDIA drivers +- GPU may be overheating, check temperature + +**"Output file larger than original"** +- Phase 1 (CQ mode) exceeded size threshold +- Phase 2 (bitrate mode) will retry automatically +- Or manually increase CQ value (lower quality) in config.xml + +--- + +## Advanced Features + +### Test Mode + +Encodes first file only without moving it: + +```bash +python main.py "P:\tv\Show" --test +``` + +**Use Case**: Test CQ values, check quality before batch processing entire library. + +### Language Tagging + +Tags audio streams with language metadata (only if specified): + +```bash +python main.py "P:\tv\Show" --language eng +``` + +Without the flag, original audio metadata is preserved. + +### Resolution Override + +Force specific output resolution regardless of source: + +```bash +python main.py "P:\movies" --r 720 # Force 720p +python main.py "P:\tv" --r 1080 # Force 1080p +``` + +### CSV Conversion Tracking + +All conversions logged to CSV file with: +- Filename +- Original size +- Output size +- Compression ratio +- Encoding method (CQ/Bitrate) +- Duration +- Timestamp + +--- + +## Logging + +The system uses structured JSON logging with media context: + +### Log Fields + +- **timestamp**: When the conversion occurred +- **level**: INFO, WARNING, ERROR +- **message**: Human-readable conversion status +- **video_filename**: Original filename +- **media_type**: "tv", "anime", "movie", or "other" +- **show_name**: Extracted from path (e.g., "Breaking Bad") +- **season**: TV/anime season number (optional) +- **episode**: TV/anime episode number (optional) +- **method**: "CQ" or "Bitrate" +- **original_size_mb**: Input file size +- **output_size_mb**: Output file size +- **reduction_pct**: Compression percentage + +### Log Files + +- **logs/conversion.log**: Full structured JSON logs (INFO level and above) +- **logs/conversion_failures.log**: Error and failure logs only +- **conversion_tracker.csv**: Human-readable conversion summary + +--- + +## Recent Changes + +### Latest Updates + +✅ **Dual Encoder Support** - Switch between AV1 and HEVC with `--encoder` flag +✅ **Interactive Audio Selection** - Manually choose which audio streams to keep +✅ **Audio Channel Control** - Force specific channel counts (2-ch stereo or 6-ch 5.1) +✅ **Audio Stream Titles** - Rename/tag individual audio tracks +✅ **Structured JSON Logging** - Media context in all logs (show name, season, episode) +✅ **Subtitle Embedding** - Auto-detect and embed subtitles with language prefixes +✅ **Test Mode** - Encode first file to verify settings before batch processing + +### Removed Features + +❌ Sonarr/Radarr integration (simplified for reliability) +❌ Auto-rename based on external metadata +❌ Web UI (can be added back if needed) + +--- + ## 📊 Example Output **Input:** @@ -154,14 +1035,27 @@ Show.S01E01 - [EHX].mkv (450MB, subtitle embedded, audio tagged) - Adjust CQ values in config.xml per content type - Use `--cq` override: `python main.py folder --cq 30` -See [Full Guide](README_RESTRUCTURE.md) for more help. +**"FFmpeg not found"** +- Ensure FFmpeg is installed and in your system PATH +- Test with: `ffmpeg -version` + +**"GPU out of memory"** +- NVIDIA GPU is full, close other GPU applications +- Or reduce resolution with `--r 720` + +**"No audio streams detected"** +- Run `ffprobe input.mkv` to verify audio streams exist +- Audio may be embedded as data track, not audio track + +**"CUDA Kernel Error" during encoding** +- GPU driver is outdated, update NVIDIA drivers +- GPU may be overheating, check temperature + +**"Output file larger than original"** +- Phase 1 (CQ mode) exceeded size threshold +- Phase 2 (bitrate mode) will retry automatically +- Or manually increase CQ value (lower quality) in config.xml ## 📄 License MIT - -## 📞 Support - -For detailed usage, see [README_RESTRUCTURE.md](README_RESTRUCTURE.md) - -For technical architecture, see [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md) diff --git a/README_RESTRUCTURE.md b/README_RESTRUCTURE.md deleted file mode 100644 index 22b0c56..0000000 --- a/README_RESTRUCTURE.md +++ /dev/null @@ -1,184 +0,0 @@ -# AV1 Batch Video Transcoder - -A clean, modular batch video transcoding system using NVIDIA's AV1 NVENC codec with intelligent audio and subtitle handling. - -## Project Structure - -``` -conversion_project/ -├── main.py - CLI entry point for batch transcoding -├── config.xml - Configuration (encoding settings, audio buckets, etc.) -│ -├── core/ - Core modules -│ ├── config_helper.py - XML configuration loader -│ ├── logger_helper.py - Logging setup -│ ├── process_manager.py - Main transcoding orchestration -│ ├── encode_engine.py - FFmpeg command builder and execution -│ ├── audio_handler.py - Audio stream analysis and bitrate decisions -│ ├── video_handler.py - Video resolution detection and scaling logic -│ └── hardware_helper.py - Hardware detection (GPU/CPU) -│ -├── /rename/ - Separate rename utility (rolling_rename.py) -├── /path_manager/ - GUI path management (kept separate from conversion) -│ ├── gui_path_manager.py -│ ├── transcode.bat -│ ├── paths.txt -│ └── cache/ -│ -├── logs/ - Log files and conversion tracker CSV -├── processing/ - Temporary encoding files (cleaned up after move) -└── cache/ (removed) - Folder cache now in /path_manager/cache -``` - -## Quick Start - -### Basic Usage - -```bash -# Encode a folder (smart mode: CQ first, bitrate fallback if size exceeds 75%) -python main.py "P:\tv\Show Name" - -# Force CQ mode with specific quality -python main.py "P:\movies\Movie" --cq 30 - -# Force Bitrate mode -python main.py "P:\tv\Show" --m bitrate - -# Explicit resolution -python main.py "P:\movies\Movie" --r 1080 - -# Test mode: encode first file only, show compression ratio, don't move files -python main.py "P:\tv\Show" --test - -# Optional: tag audio streams with language code -python main.py "P:\tv\Show" --language eng -``` - -## Features - -- **Hardware Encoding**: NVIDIA av1_nvenc (8-bit yuv420p, preset p7) -- **Smart Audio**: Analyzes streams, re-encodes excessive bitrate, preserves good quality -- **Smart Video**: Detects source resolution, scales 4K→1080p, preserves lower resolutions -- **Subtitle Detection**: Auto-finds and embeds subtitles (vtt, srt, ass, ssa, sub) - - Supports language-prefixed files: `movie.en.vtt`, `movie.eng.vtt` - - Cleans up subtitle files after embedding -- **Two-Phase Encoding** (smart mode): - - Phase 1: Try CQ mode for quality - - Phase 2: Retry failed files with Bitrate mode -- **File Tagging**: Encodes output with ` - [EHX]` suffix -- **CSV Tracking**: Detailed conversion log with compression ratios -- **Automatic Cleanup**: Deletes originals + subtitles after successful move - -## Configuration - -Edit `config.xml` to customize: -- **CQ Values**: Per content type (tv_1080, tv_720, movie_1080, movie_720) -- **Audio Buckets**: Bitrate targets for stereo/multichannel -- **Fallback Bitrates**: Used in Phase 2 bitrate retry -- **Subtitle Settings**: Extensions to detect, codec for embedding -- **Path Mappings**: Windows ↔ Linux path conversion (optional) - -## Encoding Process (Per File) - -1. **Detect subtitles**: Looks for matching `.en.vtt`, `.srt`, etc. -2. **Analyze source**: Resolution, audio streams, bitrates -3. **FFmpeg encode**: - - Video: AV1 NVENC (8-bit yuv420p) - - Audio: Per-stream decisions (copy or re-encode) - - Subtitles: Embedded as SRT (if found) -4. **Size check**: Compare output vs original (default 75% threshold) -5. **Move file**: From temp folder → original location with `- [EHX]` suffix -6. **Cleanup**: Delete original file + subtitle file - -## Audio Encoding Logic - -``` -Stereo audio? -├─ YES + 1080p: [>192kbps] ENCODE to 192k AAC, [≤192k] COPY -├─ YES + 720p: [>160kbps] ENCODE to 160k AAC, [≤160k] COPY -└─ NO (Multichannel): ENCODE to 384k/448k AAC (5.1) -``` - -## Removed Features - -- ❌ Sonarr/Radarr integration (was complex, removed for simplicity) -- ❌ Auto-rename based on Sonarr metadata -- ❌ Web UI (kept separate if needed in future) -- ❌ Rename functionality (moved to `/rename` folder) - -## Advanced Options - -### Test Mode - -Encodes first file only, shows compression ratio, leaves file in temp folder: - -```bash -python main.py "P:\tv\Show" --test -``` - -Useful for: Testing CQ values, checking quality before batch conversion. - -### Language Tagging (Optional) - -Only tags audio if explicitly provided: - -```bash -python main.py "P:\tv\Show" --language eng -``` - -Without `--language` flag, original audio metadata is preserved. - -### Resolution Override - -Force specific output resolution: - -```bash -python main.py "P:\movies" --r 720 # Force 720p -python main.py "P:\tv" --r 1080 # Force 1080p -``` - -## Output Examples - -**Input File:** -``` -SupernaturalS07E21.mkv (size: 1.5GB) -SupernaturalS07E21.en.vtt (subtitle) -``` - -**Output:** -``` -SupernaturalS07E21 - [EHX].mkv (size: 450MB, subtitle embedded) -(original files deleted) -``` - -## Troubleshooting - -### Wrong Bitrate - -Check CQ values in config.xml or use `--cq` override: - -```bash -python main.py "P:\tv\Show" --cq 31 -``` - -### Subtitles Not Embedding - -- Verify file is named correctly: `filename.en.vtt` or `filename.vtt` -- Check `config.xml` has subtitles enabled and extensions listed -- Check logs for "Found subtitle" message - -### Files Not Moving - -Check if reduction ratio threshold (default 0.75) is exceeded: - -```bash -python main.py "P:\tv\Show" --test # Check ratio in Phase 1 -``` - -If ratio is high, lower CQ value or use bitrate mode. - -## Logs - -- `logs/conversion.log`: Detailed encoding info, errors, decisions -- `logs/conversion_tracker.csv`: Summary table of all conversions -- `logs/conversion_failures.log`: Failed file tracking diff --git a/config.xml b/config.xml index f1b2642..d6ff773 100644 --- a/config.xml +++ b/config.xml @@ -11,6 +11,10 @@ - [EHX] + + + + .mkv,.mp4 @@ -23,8 +27,9 @@ true - .vtt,.srt,.ass,.ssa,.sub + .vtt,.srt,.ass,.ssa,.sub,.mov srt + @@ -36,15 +41,24 @@ eng + + + + + eng + + + + true - - - + + +