14 KiB
VMAF Optimiser
Automated video library optimization to AV1 using VMAF (Video Multimethod Assessment Fusion) quality targets. Intelligently searches for optimal encoding parameters and gracefully degrades quality when needed to achieve target file size savings.
Quick Start
Requirements
Prerequisites:
- Python 3.8+
- FFmpeg with VMAF support (
ffmpeg -filters 2>&1 | grep libvmaf) - ab-av1 binary (v0.10.3+)
Installation:
# Install ab-av1 via cargo
cargo install ab-av1
# Or download pre-built binary
wget https://github.com/alexheretic/ab-av1/releases/download/v0.10.3/ab-av1-x86_64-unknown-linux-musl
chmod +x ab-av1
Running the Optimiser
Windows / macOS
Use the PowerShell wrapper script:
# Interactive mode (shows prompts)
.\run_optimisation.ps1
# Direct execution with parameters
.\run_optimisation.ps1 --directory "D:\Movies" --vmaf 95 --preset 6 --workers 1
Available flags:
--directory <path>- Root directory to scan (default: current directory)--vmaf <score>- Target VMAF score (default: 95.0)--preset <value>- SVT-AV1 Preset (default: 6)--workers <count>- Concurrent files to process (default: 1)--samples <count>- Samples for CRF search (default: 4)--thorough- Use thorough mode (slower, more accurate)--encoder <name>- ab-av1 encoder (default: svt-av1)--hwaccel <value>- Hardware acceleration (default: none)
Linux / WSL
Use the bash wrapper script:
# Interactive mode
./run_optimisation.sh
# Direct execution with parameters
./run_optimisation.sh --directory /mnt/Media/Movies --vmaf 95 --workers 1
Same flags as PowerShell version:
--directory <path>- Root directory to scan--vmaf <score>- Target VMAF score--preset <value>- SVT-AV1 Preset--workers <count>- Concurrent files to process--samples <count>- Samples for CRF search--thorough- Use thorough mode--encoder <name>- ab-av1 encoder--hwaccel <value>- Hardware acceleration
How It Works
Phase 1: Video Analysis
- Scans directory for video files (.mkv, .mp4)
- Uses
ffprobeto get:- Codec (h264, hevc, etc.)
- Resolution (width × height)
- Bitrate (calculated from size/duration)
- HDR status (color transfer detection)
- Skips if already AV1 encoded
Phase 2: VMAF Target Search (Intelligent Fallback)
The script tries VMAF targets in descending order (highest quality first):
Try VMAF 94 (Premium)
↓
Can achieve?
↓ Yes ↓ No
Calculate savings Try VMAF 93
↓
Savings ≥ 12%?
↓ Yes ↓ No
Encode at VMAF 94 Calculate savings
↓
Savings ≥ 12%?
↓ Yes ↓ No
Encode at VMAF 93 Find 15% (test 92, 90)
Fallback Logic:
- If VMAF 94 gives ≥12% savings → Encode at VMAF 94
- If VMAF 94 <12% but VMAF 93 ≥12% → Encode at VMAF 93
- If both <12% → Find what VMAF gives 15%+ savings:
- Tests VMAF 93, 92, 90
- Reports "FOUND 15%+ SAVINGS" with exact parameters
- Logs for manual review (no encoding)
Phase 3: CRF Search
Uses ab-av1 crf-search with --thorough flag:
- Takes multiple samples (20-30s segments) from video
- Interpolates binary search for optimal CRF
- Outputs: Best CRF, Mean VMAF, Predicted size
- Uses
--samplesto control accuracy (default: 4 samples)
Phase 4: Full Encoding (with Real-time Output)
If savings threshold met:
- Runs
ab-av1 encodewith found CRF - Streams all output in real-time (you see progress live)
- Shows ETA, encoding speed, frame count
- Uses
--acodec copyto preserve audio/subtitles
Real-time output example:
→ Running encoding (CRF 34)
Encoded 4320/125400 frames (3.4%)
Encoded 8640/125400 frames (6.9%)
Encoded 12960/125400 frames (10.3%)
...
Encoded 125400/125400 frames (100.0%)
Speed: 15.2 fps, ETA: 2s
Phase 5: Verification & Replacement
- Probes encoded file for actual stats
- Calculates actual savings
- Only replaces original if new file is smaller
- Converts .mp4 to .mkv if needed
Configuration
Key settings (edit in optimize_library.py):
TARGETS = [94.0, 93.0, 92.0, 90.0] # VMAF targets to try
MIN_SAVINGS_PERCENT = 12.0 # Encode if savings ≥12%
TARGET_SAVINGS_FOR_ESTIMATE = 15.0 # Estimate for this level
PRESET = 6 # SVT-AV1 preset (4=best, 8=fast)
EXTENSIONS = {".mkv", ".mp4", ".mov", ".avi", ".ts"}
What is CRF?
Constant Rate Factor (CRF): Quality/bitrate trade-off
- Lower CRF = Higher quality, larger files (e.g., CRF 20)
- Higher CRF = Lower quality, smaller files (e.g., CRF 40)
- AV1 CRF range: 0-63 (default for VMAF 94 is ~34-36)
What is VMAF?
Video Multimethod Assessment Fusion: Netflix's quality metric
- VMAF 95: "Visually lossless" - indistinguishable from source
- VMAF 94: Premium quality - minor artifacts
- VMAF 93: Good quality - acceptable for most content
- VMAF 90: Standard quality - may have noticeable artifacts
- VMAF 85: Acceptable quality for mobile/low bandwidth
Hardware Acceleration
Automatic hwaccel detection:
When --hwaccel auto is specified, the script selects appropriate hardware acceleration:
| Platform | Auto Selection | Notes |
|---|---|---|
| Windows | d3d11va | Direct3D Video Acceleration |
| macOS | videotoolbox | VideoToolbox framework |
| Linux/WSL | vaapi | Video Acceleration via VA-API |
Discrete GPU vs iGPU priority:
- Discrete GPU (e.g., AMD RX 7900 XT) takes priority over iGPU
- FFmpeg/ab-av1 will prefer the more capable encoder
- For AV1 encoding, discrete GPU is selected if present
To disable hardware acceleration:
.\run_optimisation.ps1 --hwaccel none
./run_optimisation.sh --hwaccel none
Running on Multiple Machines
Lock File Mechanism
Each video file has a corresponding lock file:
/opt/Optmiser/.lock/{video_filename}
Process:
- Machine A checks for lock → None found, creates lock
- Machine A starts encoding
- Machine B checks for lock → Found, skips file
- Machine A finishes, removes lock
- Machine B can now process that file
Result: Different machines automatically process different files!
Multi-Machine Setup
Machine 1 (Linux Server - Intel i9-12900H):
cd /opt/Optmiser
git pull origin main
./run_optimisation.sh /mnt/Media/movies --vmaf 95
Machine 2 (Windows PC - AMD RX 7900 XT):
cd C:\Optmiser
git pull origin main
.\run_optimisation.ps1 D:\Media\movies --vmaf 95 --hwaccel auto
Machine 3 (Another Linux PC):
cd /opt/Optmiser
git pull origin main
./run_optimisation.sh /home/user/Media/tv --vmaf 95
All three can run simultaneously - lock files prevent duplicates!
Logging System
All logs stored in /opt/Optmiser/logs/ directory:
| File | Purpose |
|---|---|
tv_movies.jsonl |
Successful TV & Movie encodes |
content.jsonl |
Successful Content folder encodes |
low_savings_skips.jsonl |
Files with <12% savings + 15% estimates |
failed_searches.jsonl |
Files that couldn't hit any VMAF target |
failed_encodes.jsonl |
Encoding errors |
Log Entry Format
Successful encode:
{
"file": "/path/to/file.mkv",
"status": "success",
"vmaf": 94.0,
"crf": 34.0,
"before": {
"codec": "h264",
"bitrate": 8500,
"size": 2684354560,
"duration": 1379.44
},
"after": {
"codec": "av1",
"bitrate": 6400,
"size": 2013265920,
"duration": 1379.44
},
"duration": 145.2,
"savings": 25.0,
"timestamp": "2025-12-31T12:00:00.000Z"
}
Low savings with 15% estimate:
{
"file": "/path/to/file.mkv",
"vmaf_94": 94.0,
"savings_94": 7.0,
"vmaf_93": 93.0,
"savings_93": 18.0,
"target_for_15_percent": {
"target_vmaf": 93,
"crf": 37,
"savings": 18.0,
"quality_drop": 1,
"found": true
},
"recommendations": "logged_for_review",
"timestamp": "2025-12-31T12:00:00.000Z"
}
Viewing Logs
# Watch logs in real-time
tail -f /opt/Optmiser/logs/tv_movies.jsonl | jq '.'
# Check files logged for review (both 94 and 93 <12%)
cat /opt/Optmiser/logs/low_savings_skips.jsonl | jq '.[] | select(.recommendations=="logged_for_review")'
# Statistics
jq -r '.status' /opt/Optmiser/logs/tv_movies.jsonl | sort | uniq -c
# Find what CRF/VMAF combinations are being used most
jq -r '[.vmaf, .crf] | @tsv' /opt/Optmiser/logs/tv_movies.jsonl | sort | uniq -c
Troubleshooting
Issue: "0k bitrate" display
Cause: VBR (Variable Bitrate) files show 0 in ffprobe's format bitrate field.
Solution: Calculate from (size × 8) / duration
Issue: Multiple machines encoding same file
Cause: No coordination between machines.
Solution: Lock files in /opt/Optmiser/.lock/{video_filename}
Issue: Encode fails with "unexpected argument"
Cause: Using wrong flags for ab-av1 commands.
Solution: Script now validates ab-av1 support at runtime and warns gracefully.
Issue: Out of Memory
Solution: Reduce workers or increase swap:
# Increase swap (if needed)
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Use more conservative settings
./run_optimisation.sh --workers 1 --vmaf 93
Best Practices
For Servers (Intel i9-12900H)
-
Use 50% CPU mode if running other services (Plex, Jellyfin):
./run_optimisation.sh --workers 1 --cpu-limit 50 -
Run during off-peak hours to minimize impact on users
-
Monitor CPU temperature:
watch -n 2 'sensors | grep "Package id"' -
Use higher preset for faster encodes (preset 7-8):
./run_optimisation.sh --preset 8 --vmaf 93
For Windows PC (AMD RX 7900 XT)
-
Enable hardware acceleration for massive speedup:
.\run_optimisation.ps1 --hwaccel auto -
Test small sample first to verify settings:
.\run_optimisation.ps1 --directory "D:\Media\sample" --thorough --vmaf 95 -
Monitor GPU usage:
# Task Manager or radeontop (if available) -
Consider quality compensation: GPU encoding may need slightly lower VMAF target (e.g., VMAF 92) to match CPU quality.
For WSL
-
Access Windows drives via /mnt/c/:
ls /mnt/c/Media/movies -
Increase memory limits if encoding 4K content:
# Edit ~/.wslconfig [wsl2] memory=16GB
Customization
Changing VMAF Targets
Edit optimize_library.py:
# More aggressive (smaller files, lower quality)
TARGETS = [92.0, 90.0, 88.0]
# Conservative (larger files, higher quality)
TARGETS = [95.0, 94.0, 93.0]
Changing Savings Threshold
# More aggressive (encode more)
MIN_SAVINGS_PERCENT = 8.0
# Less aggressive (encode fewer)
MIN_SAVINGS_PERCENT = 15.0
Changing Encoder Preset
# Faster encodes (larger files, lower quality)
PRESET = 8
# Better quality (slower encodes, smaller files)
PRESET = 4
Changing Estimate Target
# Target higher savings for estimates
TARGET_SAVINGS_FOR_ESTIMATE = 20.0
File Structure
/opt/Optmiser/
├── optimize_library.py # Main encoding engine
├── run_optimisation.sh # Linux/Server wrapper
├── run_optimisation.ps1 # Windows wrapper
├── bin/
│ └── ab-av1 # ab-av1 binary
├── tmp/ # Temporary encoding files
├── logs/ # Log files (JSONL format)
│ ├── tv_movies.jsonl
│ ├── content.jsonl
│ ├── low_savings_skips.jsonl
│ ├── failed_searches.jsonl
│ └── failed_encodes.jsonl
├── .lock/ # Multi-machine coordination (created at runtime)
├── README.md # This file
├── SETUP.md # Setup instructions
└── AGENTS.md # Technical documentation
Platform Support Matrix
| Platform | Status | Notes |
|---|---|---|
| Linux (Intel CPU) | ✅ Supported | Software encoding, multi-worker capable |
| Windows (AMD GPU) | ✅ Supported | Hardware acceleration via d3d11va (auto-detects) |
| Windows (Intel CPU) | ✅ Supported | Software encoding |
| macOS (Apple Silicon) | ✅ Supported | Hardware via videotoolbox (auto-detects) |
| WSL (Ubuntu/Debian) | ✅ Supported | Linux compatibility layer |
| WSL (Windows drives) | ✅ Supported | Access via /mnt/c/ |
Git Workflow
Initial Setup
cd /opt/Optmiser
git init
git remote add origin https://gitea.theflagroup.com/bnair/VMAFOptimiser.git
git branch -M main
git add .
git commit -m "Initial commit: VMAF optimisation pipeline"
git push -u origin main
Daily Updates
cd /opt/Optmiser
git pull origin main
# Run optimisation
./run_optimisation.sh /media tv_movies
# Review changes
git diff
Committing Changes
cd /opt/Optmiser
git status
# Add changed files
git add optimize_library.py run_optimisation.sh run_optimisation.ps1
# Commit with message
git commit -m "feat: add Windows and Linux wrapper scripts"
# Push
git push
View History
cd /opt/Optmiser
git log --oneline
git log --graph --all
FAQ
Q: Can I run this on multiple machines at once? A: Yes! Each machine will process different files due to lock file mechanism.
Q: Should I use Windows or WSL? A: WSL is recommended for Linux compatibility. Use Windows native if you need direct hardware access or performance.
Q: Will hardware encoding work better than CPU? A: For AMD RX 7900 XT, hardware AV1 encoding is ~3-10x faster than CPU. However, GPU encoding may need slightly lower VMAF targets to match quality.
Q: What VMAF target should I use? A: Start with VMAF 94 or 95. Drop to 92-90 if you need more savings.
Q: How do I know which files are being processed?
A: Check .lock/ directory: ls -la /opt/Optmiser/.lock/
Q: Can I pause/resume? A: Pause by stopping the script (Ctrl+C). Resume by running again - it skips processed files.
Q: What happens if encoding fails?
A: Error is logged to failed_encodes.jsonl. Original file is NOT modified.
Q: How much CPU does encoding use?
A: Full CPU by default. Use --workers 1 for single-threaded, or limit with --cpu-limit 50 for 50% (12 threads on 24-core).
Last Updated: December 31, 2025 Version: 2.0 with Windows and Linux Wrapper Scripts