40 KiB
AV1 Batch Video Transcoder
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
- Key Features
- Quick Start
- Installation
- Basic Usage
- Command Reference
- Batch Processing
- Audio Options
- Undefined Language Replacement
- Encoder Selection
- Configuration
- Project Structure
- Encoding Process
- Advanced Features
- Logging
- Troubleshooting
✨ Key Features
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)
- Language-Prefixed Subtitles - Finds language-specific files (movie.en.vtt, movie.eng.vtt)
- Subtitle Cleanup - Remove forced flags with
--unforce-subs - 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
- Structured Logging - JSON logs with media type, show name, season/episode context
- File Tagging - Output files get
- [EHX]suffix for easy identification - File Replacement - Replace originals or create suffix versions with
--replace - Move Mode - Save disk space by moving files during processing with
--move - Keep Original - Preserve source files after encoding with
--keep-original
Advanced Features
- Video Cropping - Center-crop to remove letterboxing with
--crop - Travel Mode - 720p optimized encoding for portable drives with
--travel - Mux-Only Mode - Re-mux without encoding using
--no-encode - Network Retry - Auto-retry for temporarily unavailable network sources
- Plex Integration - Wait intervals between files for Plex detection
🚀 Quick Start
Requirements
- Python 3.8+
- FFmpeg with libfdk-aac support
- NVIDIA GPU (GeForce RTX 2060+, Quadro, or newer)
- NVIDIA CUDA Toolkit (for NVENC support)
Installation
# Clone repository
git clone https://github.com/yourusername/conversion_project.git
cd conversion_project
# Install Python dependencies (if any needed in future)
# pip install -r requirements.txt
Basic Usage
# 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
# Force specific quality (CQ 30)
python main.py "P:\movies\Movie" --cq 30
# Force bitrate mode
python main.py "P:\tv\Show" --m bitrate
# Specific resolution
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
Command Reference
Encoder Selection
--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
--cq <value> # Use CQ mode with specific quality (0-51, lower=better)
--m {cq,bitrate} # Force encoding mode (default: smart two-phase)
Resolution
--r {480,720,1080} # Force specific output resolution
# Default: scale 4K→1080p, preserve lower
Audio Options
--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 <general> section:
<!-- Default language for undefined (und) audio tracks -->
<!-- Set to desired ISO 639-2 code (eng, spa, fra, deu, etc.) or 'und' to disable -->
<default_language>eng</default_language>
<!-- Replace undefined language tracks with default language -->
<replace_undefined_language>true</replace_undefined_language>
CLI Arguments
--default-language <LANG> - Override the default language for a single run:
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:
python main.py "P:\movies\Collection" --no-replace-und
Behavior
Priority Chain (highest to lowest):
--language eng(CLI): Tags ALL audio streams as 'eng'--default-language spa+ detectedund: Replaces with 'spa'- config.xml
default_language: eng+ detectedund: Replaces with 'eng' --no-replace-undflag: Skips allundreplacement
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
# config.xml already has: <default_language>eng</default_language>
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
python main.py --paths-file paths.txt
Output & File Options
--title-suffix <TEXT> # Text inserted before [EHX] suffix (e.g., "1080p", "v2", "WebRip")
# Output: "Movie Name - 1080p [EHX].mkv"
# If not specified, uses config.xml setting (default: empty)
Using Title Suffix:
# Single folder with suffix
python main.py "P:\movies\Collection" --title-suffix "WebRip-1080p"
# Batch mode with different suffixes per item
# paths.txt:
P:\movies\Movie1 --title-suffix "1080p"
P:\movies\Movie2 --title-suffix "4K"
P:\tv\Show --title-suffix "WebRip-1080p"
python main.py --paths-file paths.txt
Output Examples:
Input: Movie Name.mkv
Output: Movie Name - 1080p [EHX].mkv (with --title-suffix "1080p")
Input: Show.S01E01.mkv
Output: Show.S01E01 - WebRip-1080p [EHX].mkv (with --title-suffix "WebRip-1080p")
Input: Video.mkv
Output: Video - [EHX].mkv (no --title-suffix, default behavior)
Audio Titles & Track Control
--keep-all-titles # Preserve title metadata on audio tracks (default: titles are stripped)
--strip-all-titles # Remove audio titles (default behavior)
Subtitle Control
--unforce-subs # Remove forced flag from all subtitle tracks
Encoding Control
--no-encode # Skip encoding: copy video/audio streams as-is (mux-only mode)
# Useful with --unforce-subs to only re-mux subtitles
--ignore-tags # Process files even if they contain ignore tags (e.g., already encoded files)
--force-encode # Reconvert files even if size threshold is not met
File Handling
--replace # Replace original file instead of creating suffix version
# Requires --no-encode (mux-only mode)
--move # Move source files to processing folder instead of copying
# Saves disk space; files deleted after processing unless --keep-original set
--keep-original # Preserve original source files after processing instead of deleting
Processing & Wait Control
--test # Test mode: encode first file, show compression ratio, don't move
--wait, -w [seconds] # Wait after each file to give Plex time to detect changes
# Default: 30s with --no-encode, 0s otherwise
--crop <pixels> # Center-crop video to target height in pixels
# Example: --crop 816 for 1920x816 from 1920x1080
Travel Mode
--travel # Travel mode: force 720p resolution and CQ+2 quality
# Requires --output flag for destination folder
--output <folder> # Output folder for travel mode (creates subfolder based on input folder name)
Travel Mode Examples:
# Encode with travel-friendly settings (720p, optimized quality)
python main.py "P:\movies" --travel --output "D:\External\Movies"
# Result: Creates D:\External\Movies\Movies folder with 720p encoded files
Queue & Retry Mode
--retry-minutes <N> # Minutes to wait between retry attempts when batch fails
# Triggered when 2+ 'Folder not found' errors occur
# Useful for network sources that may be temporarily unavailable
# Default: from config.xml if not specified
--retry-timeout <N> # Total minutes to keep retrying before giving up
# Default: from config.xml if not specified
# If source becomes reachable during retry, queue restarts immediately
Queue/Retry Mode Examples:
# Batch mode with automatic retry for network sources
python main.py --paths-file network_paths.txt --retry-minutes 5 --retry-timeout 120
# Will retry every 5 minutes for up to 2 hours if network paths are temporarily unavailable
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:
python main.py --paths-file paths.txt
With base parameters (applied to all rows unless overridden):
python main.py --paths-file paths.txt --encoder hevc --strip-all-titles
Usage:
python main.py --paths-file paths.txt
With base parameters (applied to all rows unless overridden):
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:
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:
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:
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:
# 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,2160} # Resolution (max, downscales if source larger)
--cq <value> # 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 (default)
--keep-all-titles # Preserve audio titles
--unforce-subs # Remove forced subtitle flag
--title-suffix "1080p" # Add custom text before [EHX] suffix
--no-encode # Mux only, no encoding
--ignore-tags # Process files with ignore tags
--force-encode # Reconvert even if size threshold not met
--replace # Replace original instead of suffix (with --no-encode)
--move # Move source to processing folder (saves space)
--keep-original # Preserve original files after processing
--test # Test mode (first file only)
--crop <pixels> # Center-crop video to height
--wait [seconds] # Wait after each file
--travel # Travel mode: 720p + CQ+2
--output <folder> # Output folder for travel mode
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"
python main.py --paths-file movies.txt --encoder hevc --strip-all-titles
Example 2: TV Shows by Season
tv.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"
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"
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
# 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:
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:
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):
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):
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:
# 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:
- User-specified via
--audio-channels(highest priority) - Commentary detection (if titled "Commentary", forces 2-channel)
- Auto-detection (default, based on resolution and source channels)
Common Audio Scenarios
Scenario 1: TV Show with Commentary
python main.py "C:\TV" \
--audio-channels "0:2,1:2" \
--audio-titles "0:English,1:Commentary"
Scenario 2: Movie Collection (Multiple Languages)
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)
python main.py "C:\Anime" --audio-channels "0:2,1:2,2:2"
Encoder Selection
Dual Encoder Support
AV1 NVENC (Default)
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
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 encoding parameters:
Video Encoder Settings
<encoder default="av1">
<av1_nvenc preset="p7" bit_depth="8" pix_fmt="yuv420p" />
<hevc_nvenc preset="slow" bit_depth="10" pix_fmt="yuv420p10le" />
</encoder>
CQ Quality Per Content Type
<cq>
<tv_1080>28</tv_1080>
<tv_720>32</tv_720>
<movie_1080>32</movie_1080>
<movie_720>34</movie_720>
</cq>
Audio Bitrate Buckets
<audio>
<stereo>
<high>192000</high>
<medium>160000</medium>
</stereo>
<multi_channel>
<medium>448000</medium>
<low>384000</low>
</multi_channel>
</audio>
Bitrate Fallback (Phase 2)
<fallback>
<bitrate_1080>8000</bitrate_1080>
<bitrate_720>6000</bitrate_720>
<bitrate_480>4000</bitrate_480>
</fallback>
Subtitle Detection
<subtitles>
<enabled>true</enabled>
<extensions>.vtt,.srt,.ass,.ssa,.sub</extensions>
<codec>subrip</codec> <!-- subrip for .srt format -->
</subtitles>
General Settings
<general>
<processing_folder>.\processing</processing_folder>
<suffix> - [EHX]</suffix>
<video_extensions>.mkv,.mp4,.avi,.m2ts</video_extensions>
<reduction_ratio_threshold>0.75</reduction_ratio_threshold>
</general>
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:
-
Detect Subtitles
- Looks for exact match:
Video.vtt,Video.srt, etc. - Also searches for language-prefixed:
Video.en.vtt,Video.eng.vtt
- Looks for exact match:
-
Analyze Source
- Detect resolution (width × height)
- Extract audio streams (codec, channels, bitrate, language)
- Extract video codec and frame rate
-
Determine Parameters
- Target Resolution: Scale 4K→1080p, preserve lower resolutions (or use
--roverride) - 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)
- Target Resolution: Scale 4K→1080p, preserve lower resolutions (or use
-
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)
-
Size Validation
- Compare output size vs original size
- If output > 75% of original (default threshold), retry with Phase 2 (bitrate mode)
-
Move File
- Move from temp folder → original location
- Add
- [EHX]suffix to filename - Delete original file and subtitle file
-
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.mkvto 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:
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):
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:
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
Video Cropping
Center-crop video to a specific height (useful for letterboxed content):
# Crop 1920x1080 video to 1920x816 (removes letterboxing equally from top/bottom)
python main.py "P:\movies" --crop 816
# Crop 4K to specific height
python main.py "P:\movies" --crop 1440
Use Case: Remove black bars from films while preserving aspect ratio.
File Replacement & Move Modes
Replace Mode - Replace original instead of creating suffix version:
# With --no-encode: re-mux without encoding
python main.py "P:\movies" --no-encode --replace --unforce-subs
# Result: Original.mkv is replaced (no -[EHX] suffix)
Move Mode - Save disk space by moving files instead of copying:
# Move to processing folder instead of copying (saves ~2x space during encoding)
python main.py "P:\movies" --move
# Keep original files after processing (default: deletes after successful encoding)
python main.py "P:\movies" --move --keep-original
Use Case: When encoding large libraries on limited disk space.
Subtitle Cleanup
Remove Forced Subtitle Flags:
# Re-mux without encoding, only remove forced flag
python main.py "P:\movies" --no-encode --unforce-subs
# Or with encoding
python main.py "P:\movies" --unforce-subs
Use Case: Normalize subtitle flags across your media library.
Ignore & Force Encode
Process Already-Encoded Files:
# Ignore files with encoding suffix (e.g., files already tagged as -[EHX])
python main.py "P:\movies" --ignore-tags
# Force re-encoding even if size threshold wouldn't allow it
python main.py "P:\movies" --force-encode
Use Case: Re-process files with different parameters or recover from partial batch runs.
Audio Title Preservation
Keep or Strip Audio Titles:
# Preserve title metadata on audio tracks
python main.py "P:\movies" --keep-all-titles
# Strip all titles (default)
python main.py "P:\movies" # or --strip-all-titles
Use Case: Maintain descriptive audio track names vs. clean generic metadata.
Travel Mode
Encode with travel-optimized settings (smaller files for external drives):
# Create 720p optimized for portable use
python main.py "P:\movies" --travel --output "D:\External\Movies"
# Result: Automatic 720p + CQ+2 (slightly lower quality for smaller size)
Travel Mode Behavior:
- Automatically forces 720p resolution
- Uses CQ+2 (slightly lower quality)
- Creates subfolder based on input folder name
- Useful for portable media collections
Use Case: Encode your library for travel on limited-capacity drives.
Plex-Friendly Wait Intervals
Give Plex time to detect file changes:
# Wait 30 seconds after each file (useful with --no-encode to let Plex detect changes)
python main.py "P:\movies" --no-encode --wait
# Wait custom duration
python main.py "P:\movies" --wait 60 # Wait 60 seconds between files
Use Case: When processing Plex library in real-time without full library refresh.
Network Source Reliability (Queue & Retry)
Handle temporarily unavailable network sources:
# Batch with automatic retry for network paths
python main.py --paths-file network_paths.txt --retry-minutes 5 --retry-timeout 120
# Retry every 5 minutes for up to 2 hours
# Useful for mounted NAS/cloud storage that may disconnect
Retry Behavior:
- Triggered when 2+ "Folder not found" errors occur
- Retries at specified interval (default: from config.xml)
- Continues retrying until timeout or path becomes reachable
- Automatically restarts batch when source comes back online
Use Case: Robust batch processing for unreliable network sources (NAS, cloud, etc).
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:
Show.S01E01.mkv (1.5GB)
Show.S01E01.en.vtt (subtitle)
Output:
Show.S01E01 - [EHX].mkv (450MB, subtitle embedded, audio tagged)
Compression: 1.5GB → 450MB (30% ratio, 70% reduction)
🔧 Encoding Specs
| Setting | Value |
|---|---|
| Video Codec | AV1 (av1_nvenc) |
| Bit Depth | 8-bit (yuv420p) |
| GPU Preset | p1 (high quality) |
| Audio Codec | AAC |
| Audio Mode | Smart (copy or re-encode) |
| Container | MKV |
| Subtitles | Embedded SRT |
🎯 Workflow
- Scan folder for video files
- Detect subtitles, audio streams, resolution
- Encode with AV1 codec (Phase 1: CQ)
- Check size threshold (default 75%)
- Retry with Bitrate mode if needed (Phase 2)
- Move encoded file to original location
- Cleanup original + subtitles + temp files
- Log results to CSV tracker
📋 Requirements
- Windows 10/11 or Linux
- NVIDIA GPU with NVENC support
- NVIDIA CUDA Toolkit 11.0+
- FFmpeg compiled with av1_nvenc support
- Python 3.8+
🛠️ Troubleshooting
Files not moving?
- Check
reduction_ratio_thresholdin config.xml (default 0.75) - Run with
--testto see compression ratio
Subtitles not embedding?
- Verify filename:
video.en.vttorvideo.vtt - Check config.xml
<subtitles><enabled>true</enabled>
Wrong audio quality?
- Adjust CQ values in config.xml per content type
- Use
--cqoverride:python main.py folder --cq 30
"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.mkvto 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