Production-grade batch video compression tool with real-time UI and event-driven clean architecture.
VBC compresses video libraries to AV1 using GPU (NVENC) or CPU (SVT-AV1) with intelligent queue management, camera-specific quality presets, and automatic rotation. Built for photographers, videographers, and content creators managing large video collections.
- Why VBC?
- Key Features
- Demo & Screenshots
- How It Works
- Quick Start
- Installation
- Configuration
- Usage
- Architecture
- Advanced Features
- Troubleshooting
- Development
- Contributing
- License
Modern video libraries grow fast. Raw 4K footage from cameras and drones consumes terabytes of storage. VBC solves this with:
- Massive Space Savings: AV1 codec achieves 30-70% size reduction vs H.264/HEVC
- Quality Preservation: Perceptually lossless compression with configurable quality targets
- Batch Efficiency: Process thousands of files with smart queue management and parallel processing
- Camera Intelligence: Apply different quality settings per camera model automatically
- Real-Time Control: Adjust threads, monitor progress, gracefully stop/resume without losing work
Perfect for: Photographers with mixed camera libraries, drone pilots with rotated footage, content creators archiving projects, anyone with 500GB+ of video needing organization.
- ✅ AV1 Encoding: GPU (NVENC) for speed or CPU (SVT-AV1) for maximum quality
- ✅ Dynamic Quality: Camera-specific quality presets (e.g., Sony A7RV at CQ38, DJI at CQ45)
- ✅ Manifest Queue: Dynamically ingest JSON requests and concatenate multipart recordings in one FFmpeg pass
- ✅ Smart Filtering: Skip AV1-already-encoded files, filter by camera model, size thresholds
- ✅ Min Ratio Check: Keep original if compression savings below threshold (default 10%)
- ✅ Color Space Fixes: Automatic recovery for FFmpeg 7.x "reserved" color space bugs
- ✅ Multi-Threaded: Concurrent compressions with runtime adjustment (
</>clamps to 1-8) - ✅ Submit-on-Demand: Memory-efficient queue (doesn't load 10K futures upfront)
- ✅ Metadata Caching: Thread-safe cache avoids redundant ExifTool calls
- ✅ Hardware Detection: Automatic GPU capability error detection with optional CPU fallback
- ✅ Graceful Shutdown: Finish active jobs cleanly (
S) or immediate interrupt (Ctrl+C)
- ✅ Auto-Rotation: Regex-based filename pattern matching (GoPro, DJI, iPhone patterns)
- ✅ Deep Metadata: EXIF/XMP/GPS preservation + custom VBC tags (original size, quality, encoder, rate-control notes)
- ✅ Queue Refresh: Re-scan manually, poll NFS video directories, or ingest completed JSON manifests through Linux inotify
- ✅ Error Markers:
.errfiles track failures with optional cleanup/retry - ✅ Resume Capability: Automatically skip already-compressed files
- ✅ Rich TUI Dashboard: 6-panel real-time interface with live statistics
- ✅ Keyboard Controls: Adjust threads (
</>), refresh (R), shutdown (S) without stopping - ✅ GPU Monitoring: Sparkline visualizations for utilization, memory, temperature, power
- ✅ Demo Mode: Simulate full runs without touching files (perfect for testing configs)
- ✅ Progress Tracking: Throughput (files/hour), ETA, per-job progress
The dashboard provides real-time visibility into:
- Status Panel: Files discovered, compression progress, thread count
- Active Jobs Panel: Currently processing files with codec, FPS, size, elapsed time
- Recent Completions: Last 5 finished jobs with compression ratio
- Queue Panel: Next 5 files to process with camera model and size
- Summary: Overall stats (success, errors, hardware limits, skipped files)
- GPU Sparklines (if enabled): Real-time utilization, memory, temperature, power draw
Press C to toggle Prefs view showing:
- Input/output directory mappings
- Encoder settings (GPU/CPU, threads, quality)
- Camera-specific quality overrides
- Auto-rotation patterns
- Active filters and thresholds
Dynamic thread adjustment while processing:
- Press
.or>to increase threads (instant response) - Press
,or<to decrease threads - UI shows immediate feedback: "Threads: 4 → 8"
![]() Dashboard |
![]() Prefs [C] |
![]() Keys [M] |
![]() I/O Folders [F] |
![]() Ref [E] |
![]() TUI Settings [T] |
![]() Thread Decrease |
![]() Graceful Shutdown [S] |
![]() Compact Mode |
VBC follows Clean Architecture with strict layer separation and event-driven communication:
graph TB
subgraph UI["UI Layer (vbc/ui)"]
Dashboard[Dashboard]
UIManager[UIManager]
Keyboard[KeyboardListener]
end
subgraph Pipeline["Pipeline Layer (vbc/pipeline)"]
Orchestrator[Orchestrator]
end
subgraph Infrastructure["Infrastructure Layer (vbc/infrastructure)"]
EventBus[EventBus]
FFmpeg[FFmpegAdapter]
ExifTool[ExifToolAdapter]
FFprobe[FFprobeAdapter]
Scanner[FileScanner]
end
subgraph Domain["Domain Layer (vbc/domain)"]
Models[Models]
Events[Events]
end
Dashboard -.->|subscribes| EventBus
UIManager -.->|subscribes| EventBus
Keyboard -.->|publishes| EventBus
EventBus -.->|events| Orchestrator
Orchestrator -->|uses| Scanner
Orchestrator -->|uses| FFmpeg
Orchestrator -->|uses| ExifTool
Orchestrator -->|uses| FFprobe
Orchestrator -.->|publishes| EventBus
Orchestrator -->|creates| Models
EventBus -->|transmits| Events
style UI fill:#e1f5ff
style Pipeline fill:#fff9c4
style Infrastructure fill:#f3e5f5
style Domain fill:#c8e6c9
style EventBus fill:#ffccbc
Submit-on-Demand Queue Pattern
- Only queues
prefetch_factor × threadsjobs (not all 10,000 files) - Submits new jobs as workers complete
- Memory-efficient for large directories
- Responsive to runtime thread adjustments
Thread-Safe State Management
- Condition variables for dynamic concurrency control
- Graceful shutdown coordination (finish active jobs)
- Queue refresh signaling without race conditions
Event-Driven Architecture
- EventBus decouples Pipeline from UI
- Event types defined in
vbc/domain/events.pyplus UI control events invbc/ui/keyboard.py - Pydantic events ensure type safety
- Extensible: add new subscribers without modifying publishers
- Python 3.12+
- FFmpeg 6.0+ with AV1 support (
av1_nvencand/orlibsvtav1) - ExifTool (required in current runtime flow)
uvpackage manager
# Install dependencies
cd /path/to/vbc
uv sync --frozen
# If you intentionally updated dependencies or uv.lock:
# uv sync
# Bootstrap local config (required on first run)
cp conf/vbc.yaml.example conf/vbc.yaml
# Run VBC on a directory
uv run vbc /path/to/videos- Discovery: Scans
/path/to/videosfor video files (.mp4,.mov,.avi,.flv,.webm) - Output Created: Creates
/path/to/videos_out/directory automatically - Compression: Starts processing using settings from
conf/vbc.yaml(defaults apply when fields are omitted) - Dashboard: Shows real-time progress, active jobs, queue, and summary
# View compressed files
ls -lh /path/to/videos_out/
# Check logs
cat /tmp/vbc/compression.log
# View metadata tags
exiftool -XMP-vbc:all /path/to/videos_out/compressed_video.mp4Expected Output Structure:
/path/to/videos/ # Input
├── video1.mp4
├── video2.mov
└── subfolder/
└── video3.avi
/path/to/videos_out/ # Output (auto-created)
├── video1.mp4 # Compressed to AV1
├── video2.mp4 # Compressed (converted to output format; mp4 by default)
├── subfolder/
│ └── video3.mp4 # Compressed
/path/to/videos_err/ # Errors directory (auto-created from suffix_errors_dirs: "_err")
└── failed_video.err # Final location of error marker after run
| Requirement | Version | Purpose |
|---|---|---|
| Python | 3.12+ | Runtime |
| FFmpeg | 6.0+ | Video encoding (with av1_nvenc or libsvtav1) |
| ExifTool | Any recent | Metadata extraction and copy (required in current runtime flow) |
| nvtop | - | GPU monitoring panel (NVIDIA GPUs only) |
| OS | Linux, macOS, Windows (WSL) | Platform support |
sudo apt update
sudo apt install ffmpeg libimage-exiftool-perl
ffmpeg -codecs | grep av1 # Verify AV1 supportbrew install ffmpeg exiftool
ffmpeg -codecs | grep av1sudo pacman -S ffmpeg perl-image-exiftoolcurl -LsSf https://astral.sh/uv/install.sh | sh# Clone repository
git clone https://github.com/your-org/vbc.git
cd vbc
# Install dependencies from lockfile (reproducible)
uv sync --frozen
# If you intentionally updated dependencies or uv.lock:
# uv sync
# Bootstrap local runtime config
cp conf/vbc.yaml.example conf/vbc.yaml
# Verify installation
uv run vbc --helpFor NVIDIA GPU acceleration:
# Verify NVIDIA driver
nvidia-smi
# Verify GPU monitoring tool (for sparklines)
nvtop -s
# Verify NVENC support in FFmpeg
ffmpeg -codecs | grep nvenc
# Should show: av1_nvenc, hevc_nvenc, h264_nvencNVENC Session Limits (important for thread count):
- RTX 30-series: ~5 concurrent sessions → max 4-5 threads
- RTX 40-series (e.g., RTX 4090): 10-12 sessions (hardware), VBC keyboard runtime control clamps to 1-8
- Professional GPUs (Quadro, A-series): Higher/unlimited, VBC keyboard runtime control clamps to 1-8
10-bit AV1: Requires RTX 40-series or newer.
# Test with a small directory
uv run vbc /path/to/test/videos --threads 1 --quality 45
# Run demo mode (no video file processing I/O; logs are still written)
uv run vbc --demoFull installation details: docs/getting-started/installation.md
Default: conf/vbc.yaml
Create configuration file for persistent settings:
📋 Full configuration example (conf/vbc.yaml.example) - Click to expand
# VBC Configuration Example
# All available parameters with descriptions
# --- Input/Output Settings ---
# Ordered list of input directories.
# CLI argument overrides this (no merge).
# UI preserves this exact order.
input_dirs:
- path: /path/to/videos
enabled: true
watch: true
watch_mode: polling
poll_interval_seconds: 10
- path: /path/to/metadata
enabled: false
metadata: true
watch: true
watch_mode: inotify
- path: /another/path
enabled: true
- path: /disabled/path
enabled: false
# Explicit output directories (one per enabled input directory, in order).
# Must have the same count as enabled input_dirs entries.
# Cannot be used if suffix_output_dirs is set.
output_dirs: []
# Suffix appended to each input directory name to create output directory.
# Example: /videos -> /videos_out
# Default: "_out"
suffix_output_dirs: "_out"
# Explicit directories for failed files (one per enabled input directory, in order).
# Cannot be used if suffix_errors_dirs is set.
errors_dirs: []
# Suffix appended to each input directory name for failed files.
# After processing, failed source files and their .err markers are moved here.
# Default: "_err"
suffix_errors_dirs: "_err"
# Manifest policy. This is separate from general.copy_metadata.
metadata:
# fail, ignore, or delete parts without usable video
audio_only: ignore
# Maximum missing output video frames accepted for one logical manifest job.
# Default: 0 (strict).
max_dropped_frames: 0
# keep, delete_after_success, move_after_success, or move_all
# Terminal manifest failures quarantine JSON, .err, and existing inputs
# together under <source directory>_err/<username>.
# source_policy: move_all
# move_after_success_dir: /path/to/source-archive
# --- General Settings ---
general:
# Max concurrent compression threads (>0, practical cap: executor max_workers=16).
# Runtime keyboard adjustment (< and >) clamps to 1-8.
threads: 4
# Submit-on-demand queue multiplier (>=1).
# Formula: max_queued = prefetch_factor * threads.
prefetch_factor: 1
# Use GPU acceleration (NVENC AV1) if true, otherwise CPU (SVT-AV1).
gpu: true
# [DEPRECATED] GPU polling interval in updates per second.
# Use gpu_config.sample_interval_s instead.
gpu_refresh_rate: 5
# Processing order: name, rand, dir, size, size-asc, size-desc, ext,
# or source-mtime-desc (newest source recording first).
queue_sort: "name"
# Optional seed for deterministic random queue sorting.
queue_seed: null
# Path to log file. Default: /tmp/vbc/compression.log
log_path: "/tmp/vbc/compression.log"
# Retry on CPU if NVENC hits hardware capability error.
cpu_fallback: false
# Max CPU threads per FFmpeg worker (null = auto).
ffmpeg_cpu_threads: null
# Copy EXIF/XMP/GPS tags from source to output.
copy_metadata: true
# keep | delete_after_success. Deletion forces output verification.
source_policy: keep
# Use ExifTool for deep metadata analysis (required for dynamic_quality/filtering).
use_exif: true
# Only process specific camera models (substring match, case-insensitive).
# Example: ["ILCE-7RM5", "DJI"]
filter_cameras: []
# Camera-specific quality rules (first match wins).
# Legacy scalar format is not supported:
# dynamic_quality: {"Sony": 35}
dynamic_quality:
"ILCE-7RM5":
cq: 38
rate:
bps: "0.8"
minrate: "0.7"
maxrate: "0.9"
rate_target_max_bps: "95M"
"DJI":
cq: 40
rate:
bps: "180M"
# Optional global hard cap for rate-mode resolved target bitrate.
# Absolute only (e.g. 95M, 100Mbps, 100000k, 100000000).
# Note: this caps target bitrate, not instantaneous peak bitrate.
rate_target_max_bps: null
# File extensions to scan and process.
extensions:
- ".mp4"
- ".mov"
- ".avi"
- ".flv"
- ".webm"
# Minimum input file size in bytes to process (Default: 1 MiB).
min_size_bytes: 1048576
# Remove existing .err markers on startup and retry those files.
clean_errors: false
# Skip files already encoded in AV1 codec.
skip_av1: false
# Replace non-ASCII characters with '?' in UI to prevent alignment issues.
strip_unicode_display: true
# Global rotation override (null, 0, 90, 180, 270).
manual_rotation: null
# Minimum compression savings required (0.0-1.0).
# If compression < 10%, keep original file instead of compressed version.
min_compression_ratio: 0.1
# Attempt repair pass for failed files moved to errors directories.
# Runs in the TUI after processing and before WAITING/exit.
auto_repair_errors: true
# Legacy/special repair setting for corrupted FLV/MP4 stream dumps.
repair_corrupted_flv: false
# Enable verbose debug logging.
debug: false
# --- GPU Monitoring Settings ---
gpu_config:
# Preferred GPU monitoring config.
# Takes precedence over legacy general.gpu_refresh_rate.
# Enable GPU monitoring panel and sparklines.
enabled: true
# How often to sample GPU metrics (seconds).
sample_interval_s: 5.0
# Total time window shown in sparklines (seconds).
history_window_s: 300.0
# Index of the GPU device to monitor.
nvtop_device_index: 0
# Optional: specific device name to monitor (overrides index).
nvtop_device_name: null
# --- GPU Encoder Settings ---
gpu_encoder:
# Use advanced_args instead of common_args when true.
advanced: false
# Base NVENC args (used when advanced=false).
common_args:
- "-c:v av1_nvenc"
- "-preset p7"
- "-tune hq"
- "-b:v 0"
- "-cq 45"
- "-f mp4"
# Full NVENC args (used when advanced=true).
advanced_args:
- "-c:v av1_nvenc"
- "-preset p7"
- "-tune hq"
- "-b:v 0"
- "-cq 45"
- "-rc vbr"
- "-multipass fullres"
- "-rc-lookahead 32"
- "-spatial-aq 1"
- "-temporal-aq 1"
- "-aq-strength 8"
- "-b_ref_mode middle"
- "-f mp4"
# --- CPU Encoder Settings ---
cpu_encoder:
# Use advanced_args instead of common_args when true.
advanced: false
# Base SVT-AV1 args (used when advanced=false).
common_args:
- "-c:v libsvtav1"
- "-preset 6"
- "-crf 32"
- "-svtav1-params tune=0:enable-overlays=1"
- "-f mp4"
# Enforce input pix_fmt when advanced=true (adds -pix_fmt from ffprobe).
advanced_enforce_input_pix_fmt: true
# Full CPU args (used when advanced=true).
advanced_args:
- "-c:v libaom-av1"
- "-crf 30"
- "-b:v 0"
- "-cpu-used 0"
- "-tune ssim"
- "-lag-in-frames 35"
- "-aq-mode 1"
- "-row-mt 1"
- "-threads 0"
- "-f matroska"
# --- UI Settings ---
ui:
# Max events shown in the activity feed panel (1-20).
activity_feed_max_items: 5
# Max concurrent jobs to display in the active panel (1-16).
active_jobs_max_display: 8
# Vertical scaling factor for dashboard panels (0.3-1.0).
panel_height_scale: 0.7
# --- Auto-Rotation Settings ---
autorotate:
# Regex pattern -> Rotation angle (0, 90, 180, 270).
# First match wins.
patterns:
"GOPR\d+\.MP4": 180
"IMG_\d{4}\.MOV": 90Mode 1: Suffix (Default)
input_dirs:
- path: /videos
enabled: true
- path: /archive
enabled: false
suffix_output_dirs: _out # Creates /videos_out
suffix_errors_dirs: _err # Creates /videos_errMode 2: Explicit (1:1 Pairing)
input_dirs:
- path: /videos
enabled: true
- path: /drone_footage
enabled: true
output_dirs:
- /compressed/videos
- /compressed/drone
suffix_output_dirs: null # Disable suffix modeCLI Override:
# CLI input always overrides config input_dirs (no merge)
uv run vbc /custom/path # Ignores config input_dirsFull configuration reference: docs/getting-started/configuration.md
# Process directory with defaults
uv run vbc /path/to/videos
# GPU mode with 8 threads
uv run vbc /path/to/videos --gpu --threads 8
# CPU mode for archival quality
uv run vbc /path/to/videos --cpu --quality 35 --threads 4
# Custom config file
uv run vbc /path/to/videos --config conf/production.yaml
# Camera-specific processing
uv run vbc /path/to/videos --camera "ILCE-7RM5,DJI" --quality 38
# Rotate all videos 180°
uv run vbc /path/to/videos --rotate-180
# Clean error markers and retry failed files
uv run vbc /path/to/videos --clean-errors
# Debug mode with verbose logging
uv run vbc /path/to/videos --debug --threads 2| Flag | Type | Default | Description |
|---|---|---|---|
INPUT_DIR |
Positional | - | Directory to process (or comma-separated list) |
--config, -c |
Path | conf/vbc.yaml |
Configuration file |
--threads, -t |
Integer | From config (1) | Max concurrent threads |
--quality-mode |
String | cq |
Rate-control mode: cq or rate |
--quality |
Integer | From encoder args (-cq/-crf) |
Quality override (CQ/CRF, 0-63, lower=better); invalid with --quality-mode rate |
--bps |
String | None | Target bitrate in rate mode (absolute or ratio, e.g. 200Mbps, 0.8) |
--minrate |
String | None | Optional min bitrate clamp in rate mode (same numeric class as --bps) |
--maxrate |
String | None | Optional max bitrate clamp in rate mode (same numeric class as --bps) |
--gpu / --cpu |
Boolean | From config (gpu: true) |
Use GPU (NVENC) or CPU (SVT-AV1) |
--queue-sort |
String | name |
Queue order (name, rand, dir, size, size-asc, size-desc, ext) |
--queue-seed |
Integer | None | Seed for deterministic random order |
--skip-av1 |
Boolean | False | Skip AV1-encoded files |
--camera |
String | All | Filter by camera model (comma-separated) |
--min-size |
Integer | 1048576 | Min file size in bytes |
--min-ratio |
Float | 0.1 | Min compression ratio (10% savings) |
--rotate-180 |
Boolean | False | Rotate all videos 180° |
--clean-errors |
Boolean | False | Remove .err markers and retry |
--log-path |
Path | /tmp/vbc/compression.log |
Log file path |
--debug |
Boolean | False | Verbose debug logging |
--demo |
Boolean | False | Simulation mode (no video file processing I/O; logs still written) |
--demo-config |
Path | conf/demo.yaml |
Demo simulation settings |
Configuration Precedence: CLI overrides > local VBC.YAML overrides > global YAML (conf/vbc.yaml) > defaults
Rate Mode Validation Rules:
--qualitycannot be used with--quality-mode rate--bps/--minrate/--maxraterequire--quality-mode rate
Full CLI reference: docs/user-guide/cli.md
While VBC is running, use these keyboard shortcuts:
| Key | Action | Description |
|---|---|---|
< or , |
Decrease threads | Reduce max concurrent threads by 1 (min: 1) |
> or . |
Increase threads | Increase max concurrent threads by 1 (max: 8) |
S or s |
Graceful shutdown | Stop accepting new jobs, finish active ones (press again to cancel) |
R or r |
Refresh queue | Re-scan directory and add new files |
C or c |
Overlay: Prefs | Open/close Prefs tab |
F or f |
Overlay: I/O | Open/close I/O tab |
M or m |
Overlay: Keys | Open/close Keys tab |
E or e |
Overlay: Ref | Open/close Ref tab |
L or l |
Overlay: Logs | Open/close logs tab (session errors) |
[ / ] |
Logs pagination | Previous / next page in Logs tab |
Tab |
Cycle overlay tabs | Move to next tab |
Esc |
Close overlay | Close active overlay tab |
Ctrl+C |
Immediate interrupt | Terminate all active jobs immediately |
Thread Adjustment: Changes take effect instantly. New jobs start as slots become available.
Graceful Shutdown: Press S to stop queue, let active jobs finish. Press S again to cancel shutdown and resume.
Queue Refresh: Press R to re-scan input directory. New files added to queue without duplicating existing ones.
Full runtime controls guide: docs/user-guide/runtime-controls.md
┌─────────────────────────────────────────────────┐
│ UI Layer │
│ Dashboard, KeyboardListener, UIState │
│ (Rich library, terminal rendering) │
└────────────────┬────────────────────────────────┘
│ Events (Pub/Sub via EventBus)
┌────────────────┴────────────────────────────────┐
│ Pipeline Layer │
│ Orchestrator (job lifecycle, queue, threads) │
└────────────────┬────────────────────────────────┘
│ Domain Models (VideoFile, Job)
┌────────────────┴────────────────────────────────┐
│ Infrastructure Layer │
│ FFmpeg, ExifTool, FFprobe, FileScanner │
│ (External tool adapters) │
└─────────────────────────────────────────────────┘
Benefits:
- Testability: Mock infrastructure adapters easily
- Maintainability: Change UI without touching pipeline logic
- Extensibility: Add new event subscribers (webhooks, logging) without modifying publishers
stateDiagram-v2
[*] --> Discovery: Scan directory
Discovery --> Queued: Files found
Queued --> Processing: Thread slot available
Processing --> Compressed: FFmpeg success
Processing --> Failed: FFmpeg error
Processing --> HW_CAP: GPU capability exceeded
Processing --> Interrupted: Ctrl+C
Compressed --> MetadataCopy: Copy EXIF/XMP
MetadataCopy --> RatioCheck: Compare sizes
RatioCheck --> Completed: Savings ≥ threshold
RatioCheck --> Original_Kept: Savings < threshold
Completed --> [*]
Original_Kept --> [*]
Failed --> [*]: .err marker written
HW_CAP --> [*]: .err marker written
HW_CAP --> Queued: CPU fallback enabled
Interrupted --> [*]: .tmp cleanup
All components communicate via EventBus (synchronous pub/sub):
Discovery Phase:
Orchestrator → DiscoveryStarted → UIManager → Update UI
Orchestrator → DiscoveryFinished → UIManager → Show file counts
Job Processing:
Orchestrator → JobStarted → UIManager → Add to active jobs panel
FFmpegAdapter → JobCompleted → UIManager → Update stats, move to recent
FFmpegAdapter → JobFailed → UIManager → Increment error counter
User Control:
User presses '>' → KeyboardListener → ThreadControlEvent → Orchestrator → Increase threads
User presses 'S' → KeyboardListener → RequestShutdown → Orchestrator → Graceful shutdown
User presses 'R' → KeyboardListener → RefreshRequested → Orchestrator → Re-scan directory
Architecture deep dive: docs/architecture/overview.md
Apply different quality settings per camera model:
gpu_encoder:
common_args:
- "-cq 45" # Default for unknown cameras
general:
dynamic_quality:
"ILCE-7RM5":
cq: 38 # CQ mode override
rate: # Optional rate mode override
bps: "0.8"
minrate: "0.7"
maxrate: "0.9"
rate_target_max_bps: "95M"
"DC-GH7":
cq: 40
"DJI OsmoPocket3":
cq: 48
rate:
bps: "180M"How it works:
- VBC extracts full EXIF metadata via ExifTool
- Searches all metadata fields for camera model strings
- First match wins (order matters in YAML)
- Uses
cqinquality_mode=cq, orratewhenquality_mode=rate(if present)
rate_target_max_bps can be set globally (general.rate_target_max_bps) or per camera
(general.dynamic_quality.<pattern>.rate.rate_target_max_bps) to hard-cap resolved rate targets.
For libsvtav1, VBC also enforces encoder-safe limits and records applied decisions in XMP:VBCJsonNotes.
Example:
- File:
IMG_1234.MOV - EXIF:
Model = "ILCE-7RM5" - Result: Uses CQ 38 instead of default 45
Automatically rotate videos based on filename patterns:
autorotate:
patterns:
"DJI_.*\\.MP4": 0 # DJI drones - no rotation needed
"GOPR\\d+\\.MP4": 180 # GoPro - flip upside down
"IMG_\\d{4}\\.MOV": 90 # iPhone - rotate 90° clockwiseRotation Angles:
0= No rotation90= Clockwise (transpose=1)180= Upside down (transpose=2,transpose=2)270= Counter-clockwise (transpose=2)
Manual Override:
uv run vbc /videos --rotate-180 # Overrides all patternsProcess only files from specific cameras:
general:
filter_cameras:
- "ILCE-7RM5"
- "DJI OsmoPocket3"
- "Canon EOS R5"Or via CLI:
uv run vbc /videos --camera "ILCE-7RM5,DJI"Matching: Substring match (case-insensitive). Example:
- Filter:
["ILCE-7RM5"] - File camera:
ILCE-7RM5 - Match: ✓ (
"ILCE-7RM5"appears in camera model)
Keep original file if compression savings are insufficient:
general:
min_compression_ratio: 0.1 # Require 10% savingsExample:
Input: 100 MB
Output: 92 MB
Savings: 8% (1 - 92/100 = 0.08)
Result: 8% < 10% → Keep original (copy 100 MB file to output)
Use case: Prevent "compression" that makes files larger or provides negligible savings.
VBC automatically detects and handles GPU limitations:
Error Detection:
- FFmpeg outputs:
"Hardware is lacking required capabilities" - Or exits with code 187
- VBC sets job status to
HW_CAP_LIMIT, creates.errmarker
Common Causes:
- Session limits exceeded: Too many concurrent threads for GPU
- 10-bit encoding unavailable: Older GPUs lack 10-bit AV1 support
Workarounds:
# Reduce threads to GPU session limit
uv run vbc /videos --threads 5 # For RTX 30-series
# Enable CPU fallback (automatic retry on CPU)
# In conf/vbc.yaml:
general:
cpu_fallback: true
# Or use pure CPU mode
uv run vbc /videos --cpuFFmpeg 7.x rejects "reserved" color space values. VBC automatically fixes this:
- Detects
color_space=reservedvia FFprobe - Remuxes with bitstream filter to set valid color space
- Compresses using fixed file
- Cleans up temporary remuxed file
Supported codecs: HEVC (h265), H.264. Transparent to user.
Full advanced features guide: docs/user-guide/advanced.md
Test VBC without processing real files:
# Run demo with defaults
uv run vbc --demo
# Custom demo config
uv run vbc --demo --demo-config conf/demo.yamlDemo configuration (conf/demo.yaml):
# Minimal override example (full schema: conf/demo.yaml)
seed: 1337
files:
count: 50
sizes:
distribution: triangular
min_mb: 20
mode_mb: 180
max_mb: 1800
errors:
total: 3UI shows: VBC - demo title and synthetic filenames. Full event flow matches real runs.
Symptom: Jobs fail with HW_CAP_LIMIT status
Causes:
- Too many concurrent threads for GPU session limit
- 10-bit AV1 encoding not supported by GPU
Solutions:
# Reduce threads to GPU limit
uv run vbc /videos --threads 5 # RTX 30-series
uv run vbc /videos --threads 8 # RTX 4090 (runtime keyboard control max: 8)
# Enable CPU fallback
# In conf/vbc.yaml:
general:
cpu_fallback: true
# Switch to CPU mode entirely
uv run vbc /videos --cpuSymptom: FFmpeg fails with color space warnings
Solution: VBC automatically applies color space fix. Enable debug logging to verify:
uv run vbc /videos --debug
# Look for: "FFMPEG_COLORFIX" or "Successfully fixed color space"Symptom: Failed files are moved to *_err and remain unreadable
Solution: VBC runs one automatic repair pass by default after the queue becomes empty:
general:
auto_repair_errors: trueBehavior:
- After normal processing, VBC shows REPAIR in the TUI.
- VBC moves
.errfiles created in the current session and their source files to error directories. - VBC attempts repair once per source file for the current session.
- Successfully repaired files are restored to source folders, usually as
.mkv. - On normal completion, restored files are queued and compressed immediately, even if
.mkvis not ingeneral.extensions. - If repair was reached after graceful shutdown (
S), VBC repairs once and then exits without compressing restored files.
Causes:
- Already compressed (output exists and is newer than input)
- Below
min_size_bytesthreshold - Has
.errmarker (unless--clean-errors) - Already AV1-encoded (if
--skip-av1enabled) - Camera model doesn't match
filter_cameras
Solution: Check discovery counters in UI:
Files found: 100 | To process: 50 | Already compressed: 30 | Ignored: size:15, err:5
Symptom: Press . but threads don't increase
Causes:
- Already at max (8 threads)
- Graceful shutdown active (
Spressed)
Solution: Check UI for "SHUTDOWN requested" message. Press S again to cancel shutdown.
Symptom: Pressed S by mistake, want to continue
Solution: Press S again immediately to cancel shutdown:
Press: S
UI: "SHUTDOWN requested (press S to cancel)"
Active: 4 jobs continue | Queue: 50 jobs frozen
Press: S (again)
UI: "SHUTDOWN cancelled"
Queue: Resumes processing
Check detailed logs:
cat /tmp/vbc/compression.log
# Or custom path
cat /path/to/custom.log
# Fatal uncaught exceptions (startup/runtime)
cat error.log
# Live tail
tail -f /tmp/vbc/compression.log
# Error markers moved after run
find /path/to/videos_err -name "*.err"vbc/
├── config/ # Configuration (Pydantic models + YAML loader)
├── domain/ # Core business logic (models, events)
├── infrastructure/ # External adapters (FFmpeg, ExifTool, EventBus)
├── pipeline/ # Processing coordination (Orchestrator)
└── ui/ # User interface (Rich dashboard, keyboard)
docs/ # MkDocs documentation
├── getting-started/ # Installation, quickstart, configuration
├── user-guide/ # CLI, runtime controls, advanced features
├── architecture/ # Overview, events, pipeline flow
└── development/ # Contributing, testing
tests/ # Pytest test suite
├── unit/ # Fast isolated tests
├── integration/ # Slower end-to-end tests
└── fixtures/ # Test data
# All tests
uv run pytest
# Unit tests only
uv run pytest tests/unit/
# Skip slow tests
uv run pytest -m "not slow"
# With coverage
uv run pytest --cov=vbc --cov-report=html
# Specific test
uv run pytest tests/unit/test_config_models.py::test_general_config_defaultsBuild and serve docs locally:
# Install docs dependencies
uv sync --extra docs
# Serve documentation
./scripts/serve-docs.sh
# Or: uv run mkdocs serve
# Open browser: http://127.0.0.1:8000# Optional quality tools (not pinned in `pyproject.toml` by default):
# uv add --group dev ruff black mypy
# Then run:
uv run ruff check vbc/
uv run black vbc/
uv run mypy vbc/Testing guide: docs/development/testing.md
We welcome contributions! VBC follows clean architecture principles and event-driven design.
# Clone repository
git clone git@github.com:your-org/vbc.git
cd vbc
# Install dependencies
uv sync
# Run tests
uv run pytest
# Make changes
git checkout -b feature/my-feature
# Commit (use conventional commits)
git commit -m "feat: add support for H.265 encoding"
# Push and create PR
git push origin feature/my-feature- Python Style: PEP 8 with 120-character line length
- Type Hints: Required for all public functions
- Docstrings: Google-style for public classes/methods
- Dependency Injection: No global state, inject via constructors
- Event-Driven: Use EventBus for cross-component communication
- Pydantic Models: All config and data validation via Pydantic
feat: add new feature
fix: fix bug
docs: update documentation
test: add tests
refactor: refactor code
chore: update dependenciesFull contributing guide: docs/development/contributing.md
MIT License (see LICENSE)
VBC is built with excellent open-source tools:
- FFmpeg - Video processing engine
- Rich - Terminal UI framework
- Pydantic - Data validation
- Typer - CLI framework
- PyExifTool - ExifTool Python wrapper
- inotify-simple - Linux manifest-directory events
- uv - Fast Python package manager
- Documentation: docs/
- Issue Tracker: https://github.com/your-org/vbc/issues
- Discussions: https://github.com/your-org/vbc/discussions
- Documentation Changelog: docs/DOCUMENTATION_CHANGELOG.md
Quick Links:








