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

  1. Scans directory for video files (.mkv, .mp4)
  2. Uses ffprobe to get:
    • Codec (h264, hevc, etc.)
    • Resolution (width × height)
    • Bitrate (calculated from size/duration)
    • HDR status (color transfer detection)
  3. 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)

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 --samples to control accuracy (default: 4 samples)

Phase 4: Full Encoding (with Real-time Output)

If savings threshold met:

  1. Runs ab-av1 encode with found CRF
  2. Streams all output in real-time (you see progress live)
  3. Shows ETA, encoding speed, frame count
  4. Uses --acodec copy to 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

  1. Probes encoded file for actual stats
  2. Calculates actual savings
  3. Only replaces original if new file is smaller
  4. 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:

  1. Machine A checks for lock → None found, creates lock
  2. Machine A starts encoding
  3. Machine B checks for lock → Found, skips file
  4. Machine A finishes, removes lock
  5. 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)

  1. Use 50% CPU mode if running other services (Plex, Jellyfin):

    ./run_optimisation.sh --workers 1 --cpu-limit 50
    
  2. Run during off-peak hours to minimize impact on users

  3. Monitor CPU temperature:

    watch -n 2 'sensors | grep "Package id"'
    
  4. Use higher preset for faster encodes (preset 7-8):

    ./run_optimisation.sh --preset 8 --vmaf 93
    

For Windows PC (AMD RX 7900 XT)

  1. Enable hardware acceleration for massive speedup:

    .\run_optimisation.ps1 --hwaccel auto
    
  2. Test small sample first to verify settings:

    .\run_optimisation.ps1 --directory "D:\Media\sample" --thorough --vmaf 95
    
  3. Monitor GPU usage:

    # Task Manager or radeontop (if available)
    
  4. Consider quality compensation: GPU encoding may need slightly lower VMAF target (e.g., VMAF 92) to match CPU quality.

For WSL

  1. Access Windows drives via /mnt/c/:

    ls /mnt/c/Media/movies
    
  2. 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

91418fa898

Description
Find best params and try and optimise to VMAF to target at least 12-15% reduction in storage if not more
Readme 1.4 MiB
Languages
Python 82.2%
PowerShell 11.5%
Shell 6.3%