Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25871,6 +25871,14 @@ closing the last untested surface from the 2026-05-16 coverage audit.
assertion was always correct; only the code was misaligned.


- Fixed conflicting rate-control flags in `libx264` two-pass encoding (`T-VMAFTUNE-TWOPASS-CRF-INVALID-2026-08-30`).
In two-pass mode (`pass_number != 0`), `libx264` requires bitrate mode rather than CRF
(FFmpeg exits with status 187: `CRF/CQP is incompatible with 2pass.`). Both Go (`pkg/codecadapter`,
`pkg/ffencode`, `pkg/corpus`) and Python (`vmaftune.codec_adapters.x264`, `vmaftune.encode`) now omit `-crf`
when building multi-pass command lines, permitting clean two-pass execution with bitrate targets
passed in extra parameters.


- **queue.ListAll tenant_id regression**: `ListAll` omitted `tenant_id` from
both its SQL `SELECT` clauses and the matching `rows.Scan` call, causing
`Job.TenantID` to always be `""` in every result returned by `StreamJobs`.
Expand Down
6 changes: 6 additions & 0 deletions changelog.d/fixed/vmaftune-twopass-crf-conflict.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- Fixed conflicting rate-control flags in `libx264` two-pass encoding (`T-VMAFTUNE-TWOPASS-CRF-INVALID-2026-08-30`).
In two-pass mode (`pass_number != 0`), `libx264` requires bitrate mode rather than CRF
(FFmpeg exits with status 187: `CRF/CQP is incompatible with 2pass.`). Both Go (`pkg/codecadapter`,
`pkg/ffencode`, `pkg/corpus`) and Python (`vmaftune.codec_adapters.x264`, `vmaftune.encode`) now omit `-crf`
when building multi-pass command lines, permitting clean two-pass execution with bitrate targets
passed in extra parameters.
3 changes: 3 additions & 0 deletions docs/rebase-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
# Rebase notes

<!-- markdownlint-disable MD001 MD003 MD004 MD007 MD013 MD018 MD022 MD024 MD025 MD026 MD028 MD029 MD031 MD032 MD033 MD036 MD037 MD038 MD040 MD041 MD046 MD049 MD050 MD051 MD052 MD053 MD055 MD056 MD058 MD059 -->
## fix/vmaftune-state-bugs — libx264 two-pass CRF conflict fix (2026-09-03)

No rebase impact: all touched files (`pkg/codecadapter/`, `pkg/ffencode/`, `pkg/corpus/`, `tools/vmaf-tune/`) are fork-added Go and Python tuning tooling with no upstream Netflix/vmaf counterpart. No public C API, header, Meson option, or golden assertion is touched.
## feat/default-model-v1-0-16 — loud model-dimension validation in the CLI (2026-09-04)

- `core/src/feature/feature_dimensions.h`: wholly fork-added. Single point that turns the
Expand Down
7 changes: 5 additions & 2 deletions docs/state.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/usage/vmaf-tune-codec-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ one real adapter name per row.

| `--encoder` value | Codec | Backend | ADR | Status | Two-pass | Notes |
|---------------------|-------|---------|------------------------------------------------------------|------------------|----------|-----------------------------------------------------------|
| `libx264` | H.264 | CPU | [ADR-0237](../adr/0237-quality-aware-encode-automation.md) | Phase A baseline | yes | Reference adapter; uses FFmpeg `-pass` / `-passlogfile`. |
| `libx264` | H.264 | CPU | [ADR-0237](../adr/0237-quality-aware-encode-automation.md) | Phase A baseline | yes | Reference adapter; uses FFmpeg `-pass` / `-passlogfile` (omits `-crf` in multi-pass). |
| `libx265` | HEVC | CPU | [ADR-0288](../adr/0288-vmaf-tune-codec-adapter-x265.md) | Accepted | yes | x265-style preset names; same CRF range as libx264. |
| `libaom-av1` | AV1 | CPU | [ADR-0279](../adr/0279-vmaf-tune-codec-adapter-libaom.md) | Accepted | no | `--cpu-used` mapped from preset; long-encode warning. |
| `libsvtav1` | AV1 | CPU | [ADR-0294](../adr/0294-vmaf-tune-codec-adapter-svtav1.md) | Accepted | no | SVT-AV1 preset 0–13 mapped from `--preset` slug. |
Expand Down
39 changes: 37 additions & 2 deletions pkg/codecadapter/codecadapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,27 @@ func (a *Adapter) FFmpegCodecArgs(preset string, quality int) ([]string, error)
return out, nil
}

// FFmpegCodecArgsForPass returns the FFmpeg argv slice for the codec, optionally
// taking passNumber into account. In two-pass mode (passNumber != 0), libx264
// omits -crf because FFmpeg's libx264 two-pass rate control is incompatible
// with CRF mode (T-VMAFTUNE-TWOPASS-CRF-INVALID-2026-08-30).
func (a *Adapter) FFmpegCodecArgsForPass(preset string, quality int, passNumber int) ([]string, error) {
if a.unavailable != "" && !(a.availableFn != nil && a.availableFn()) {
if a.Name == "av1_videotoolbox" {
return nil, ErrAv1VideoToolboxUnavailable
}
return nil, fmt.Errorf("%s", a.unavailable)
}
out := []string{"-c:v", a.Encoder}
out = append(out, a.presetTokens(preset)...)
if passNumber != 0 && a.Name == "libx264" {
return out, nil
}
out = append(out, a.qualityTokens(quality)...)
out = append(out, a.qualityTail...)
return out, nil
}

// ExtraParams returns codec-level flags orthogonal to quality and preset
// (libvpx-vp9's "-row-mt 1"; empty for every other shipped codec).
func (a *Adapter) ExtraParams() []string {
Expand All @@ -466,7 +487,12 @@ func (a *Adapter) ExtraParams() []string {
// duplicate. The Go port emits each AMF token once; every other codec's argv
// is byte-identical to the Python original.
func (a *Adapter) ResolveCodecArgs(preset string, quality int) ([]string, error) {
args, err := a.FFmpegCodecArgs(preset, quality)
return a.ResolveCodecArgsForPass(preset, quality, 0)
}

// ResolveCodecArgsForPass returns FFmpegCodecArgsForPass followed by ExtraParams.
func (a *Adapter) ResolveCodecArgsForPass(preset string, quality int, passNumber int) ([]string, error) {
args, err := a.FFmpegCodecArgsForPass(preset, quality, passNumber)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -530,8 +556,17 @@ func LegacyCodecArgs(encoder, preset string, quality int) []string {
// callers that only hold an encoder string would otherwise repeat the
// Get-then-fall-back dance at every call site.
func ResolveCodecArgs(encoder, preset string, quality int) ([]string, error) {
return ResolveCodecArgsForPass(encoder, preset, quality, 0)
}

// ResolveCodecArgsForPass resolves by encoder name and passNumber, falling back
// to LegacyCodecArgs for an unregistered encoder.
func ResolveCodecArgsForPass(encoder, preset string, quality int, passNumber int) ([]string, error) {
a, err := Get(encoder)
if err != nil {
if passNumber != 0 && encoder == "libx264" {
return []string{"-c:v", encoder, "-preset", preset}, nil
}
return LegacyCodecArgs(encoder, preset, quality), nil
}
// Validate before building. The (*Adapter) method deliberately does not
Expand All @@ -541,7 +576,7 @@ func ResolveCodecArgs(encoder, preset string, quality int) ([]string, error) {
if err := a.Validate(preset, quality); err != nil {
return nil, err
}
return a.ResolveCodecArgs(preset, quality)
return a.ResolveCodecArgsForPass(preset, quality, passNumber)
}

// DefaultPreset is encoder_profile._default_preset: "medium" when the codec
Expand Down
50 changes: 50 additions & 0 deletions pkg/codecadapter/codecadapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,56 @@ func TestResolveCodecArgs(t *testing.T) {
}
}

// TestResolveCodecArgsForPass_x264OmitsCRFInMultiPass pins the BUG 1 fix:
// libx264 two-pass encoding omits -crf so rate-control flags do not conflict.
func TestResolveCodecArgsForPass_x264OmitsCRFInMultiPass(t *testing.T) {
t.Parallel()

a, err := codecadapter.Get("libx264")
if err != nil {
t.Fatalf("Get(libx264): %v", err)
}

// Single-pass (pass 0) emits -crf
gotPass0, err := a.ResolveCodecArgsForPass("medium", 23, 0)
if err != nil {
t.Fatalf("ResolveCodecArgsForPass pass 0: %v", err)
}
wantPass0 := []string{"-c:v", "libx264", "-preset", "medium", "-crf", "23"}
if !slices.Equal(gotPass0, wantPass0) {
t.Errorf("pass 0 got %v, want %v", gotPass0, wantPass0)
}

// Multi-pass (pass 1 and 2) omits -crf
for _, pass := range []int{1, 2} {
gotPass, err := a.ResolveCodecArgsForPass("medium", 23, pass)
if err != nil {
t.Fatalf("ResolveCodecArgsForPass pass %d: %v", pass, err)
}
wantPass := []string{"-c:v", "libx264", "-preset", "medium"}
if !slices.Equal(gotPass, wantPass) {
t.Errorf("pass %d got %v, want %v", pass, gotPass, wantPass)
}
}

// Other encoders (e.g. libvpx-vp9, libaom-av1) keep quality knob in 2-pass
vpx, err := codecadapter.Get("libvpx-vp9")
if err != nil {
t.Fatalf("Get(libvpx-vp9): %v", err)
}
gotVPX, err := vpx.ResolveCodecArgsForPass("3", 32, 1)
if err != nil {
t.Fatalf("vpx ResolveCodecArgsForPass: %v", err)
}
wantVPX := []string{
"-c:v", "libvpx-vp9", "-deadline", "good", "-cpu-used", "3",
"-crf", "32", "-b:v", "0", "-row-mt", "1",
}
if !slices.Equal(gotVPX, wantVPX) {
t.Errorf("vpx pass 1 got %v, want %v", gotVPX, wantVPX)
}
}

// TestGOPArgs covers the keyint emission contract.
func TestGOPArgs(t *testing.T) {
t.Parallel()
Expand Down
4 changes: 2 additions & 2 deletions pkg/corpus/encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func TestBuildFFmpegCommand(t *testing.T) {
"ffmpeg", "-y", "-hide_banner", "-loglevel", "info",
"-f", "rawvideo", "-pix_fmt", "yuv420p", "-s", "1920x1080", "-r", "24.0",
"-i", "/refs/clip.yuv",
"-c:v", "libx264", "-preset", "medium", "-crf", "26",
"-c:v", "libx264", "-preset", "medium",
"-pass", "1", "-passlogfile", "/tmp/stats",
"-f", "null", "-",
},
Expand All @@ -136,7 +136,7 @@ func TestBuildFFmpegCommand(t *testing.T) {
"ffmpeg", "-y", "-hide_banner", "-loglevel", "info",
"-f", "rawvideo", "-pix_fmt", "yuv420p", "-s", "1920x1080", "-r", "24.0",
"-i", "/refs/clip.yuv",
"-c:v", "libx264", "-preset", "medium", "-crf", "26",
"-c:v", "libx264", "-preset", "medium",
"-pass", "2", "-passlogfile", "/tmp/stats",
"/enc/clip__libx264__medium__crf26.mp4",
},
Expand Down
12 changes: 3 additions & 9 deletions pkg/ffencode/ffencode.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -178,15 +177,10 @@ func InputArgs(req Request) []string {

// resolveCodecArgs routes through the codec-adapter registry, falling back to
// the historic "-c:v <enc> -preset <p> -crf <q>" shape for unregistered
// encoders (the Python _legacy_codec_args path).
// encoders (the Python _legacy_codec_args path). In two-pass mode (PassNumber != 0),
// libx264 omits -crf to prevent conflicting rate control flags.
func resolveCodecArgs(req Request) ([]string, error) {
adapter, err := codecadapter.Get(req.Encoder)
if err != nil {
return []string{
"-c:v", req.Encoder, "-preset", req.Preset, "-crf", strconv.Itoa(req.CRF),
}, nil
}
return adapter.ResolveCodecArgs(req.Preset, req.CRF)
return codecadapter.ResolveCodecArgsForPass(req.Encoder, req.Preset, req.CRF, req.PassNumber)
}

var (
Expand Down
23 changes: 21 additions & 2 deletions pkg/ffencode/ffencode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func TestBuildFFmpegCommand(t *testing.T) {
"ffmpeg", "-y", "-hide_banner", "-loglevel", "info",
"-f", "rawvideo", "-pix_fmt", "yuv420p", "-s", "1920x1080", "-r", "24.0",
"-i", "/src/ref.yuv",
"-c:v", "libx264", "-preset", "medium", "-crf", "23",
"-c:v", "libx264", "-preset", "medium",
"-pass", "1", "-passlogfile", "/tmp/stats",
"-f", "null", "-",
},
Expand All @@ -141,8 +141,27 @@ func TestBuildFFmpegCommand(t *testing.T) {
"ffmpeg", "-y", "-hide_banner", "-loglevel", "info",
"-f", "rawvideo", "-pix_fmt", "yuv420p", "-s", "1920x1080", "-r", "24.0",
"-i", "/src/ref.yuv",
"-c:v", "libx264", "-preset", "medium", "-crf", "23",
"-c:v", "libx264", "-preset", "medium",
"-pass", "2", "-passlogfile", "/tmp/stats",
"/out/enc.mp4",
},
},
{
name: "pass 2 with bitrate in ExtraParams retains bitrate flag without crf conflict",
req: ffencode.Request{
Source: "/src/ref.yuv", Width: 1920, Height: 1080,
PixFmt: "yuv420p", Framerate: 24.0,
Encoder: "libx264", Preset: "medium", CRF: 23,
Output: "/out/enc.mp4", PassNumber: 2, StatsPath: "/tmp/stats",
ExtraParams: []string{"-b:v", "2000k"},
},
want: []string{
"ffmpeg", "-y", "-hide_banner", "-loglevel", "info",
"-f", "rawvideo", "-pix_fmt", "yuv420p", "-s", "1920x1080", "-r", "24.0",
"-i", "/src/ref.yuv",
"-c:v", "libx264", "-preset", "medium",
"-pass", "2", "-passlogfile", "/tmp/stats",
"-b:v", "2000k",
"/out/enc.mp4",
},
},
Expand Down
10 changes: 7 additions & 3 deletions tools/vmaf-tune/src/vmaftune/codec_adapters/x264.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,17 @@ def validate(self, preset: str, crf: int) -> None:
if not lo <= crf <= hi:
raise ValueError(f"crf {crf} outside Phase A range [{lo}, {hi}]")

def ffmpeg_codec_args(self, preset: str, quality: int) -> list[str]:
"""FFmpeg argv slice for libx264 single-pass CRF.
def ffmpeg_codec_args(self, preset: str, quality: int, pass_number: int = 0) -> list[str]:
"""FFmpeg argv slice for libx264.

Adapter-contract entry point used by the codec-agnostic
dispatcher (ADR-0294). Identical to the legacy hard-coded
x264 path: ``-c:v libx264 -preset <p> -crf <q>``.
x264 path: ``-c:v libx264 -preset <p> -crf <q>`` on single-pass
(pass_number == 0). In 2-pass mode (pass_number in (1, 2)), omits
-crf to avoid conflicting rate-control flags (T-VMAFTUNE-TWOPASS-CRF-INVALID-2026-08-30).
"""
if pass_number != 0:
return ["-c:v", self.encoder, "-preset", preset]
return ["-c:v", self.encoder, "-preset", preset, "-crf", str(quality)]

def extra_params(self) -> tuple[str, ...]:
Expand Down
8 changes: 7 additions & 1 deletion tools/vmaf-tune/src/vmaftune/encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,13 @@ def _resolve_codec_args(req: EncodeRequest) -> list[str]:
if fn is None:
return _legacy_codec_args(req.encoder, req.preset, req.crf)

args = list(fn(req.preset, req.crf))
import inspect

sig = inspect.signature(fn)
if "pass_number" in sig.parameters:
args = list(fn(req.preset, req.crf, pass_number=req.pass_number))
else:
args = list(fn(req.preset, req.crf))
# Append adapter-level extra_params (codec-specific flags that are
# orthogonal to quality/preset — e.g. -svtav1-params for SVT-AV1,
# -row-mt for libaom-av1, -b:v 0 for VBR-mode encoders).
Expand Down
29 changes: 29 additions & 0 deletions tools/vmaf-tune/tests/test_codec_adapter_x265_two_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ def test_build_ffmpeg_command_x264_pass1_uses_native_passlogfile(tmp_path: Path)
stats_path=stats,
)
cmd = build_ffmpeg_command(req)
assert "-crf" not in cmd
assert "-pass" in cmd
assert cmd[cmd.index("-pass") + 1] == "1"
assert "-passlogfile" in cmd
Expand All @@ -201,6 +202,32 @@ def test_build_ffmpeg_command_x264_pass2_writes_real_output(tmp_path: Path):
stats_path=stats,
)
cmd = build_ffmpeg_command(req)
assert "-crf" not in cmd
assert cmd[cmd.index("-pass") + 1] == "2"
assert cmd[cmd.index("-passlogfile") + 1] == str(stats)
assert cmd[-1] == str(req.output)


def test_build_ffmpeg_command_x264_pass2_with_bitrate_extra_params(tmp_path: Path):
stats = tmp_path / "encode.stats"
req = EncodeRequest(
source=tmp_path / "ref.yuv",
width=64,
height=64,
pix_fmt="yuv420p",
framerate=24.0,
encoder="libx264",
preset="medium",
crf=23,
output=tmp_path / "out.mp4",
pass_number=2,
stats_path=stats,
extra_params=("-b:v", "2000k"),
)
cmd = build_ffmpeg_command(req)
assert "-crf" not in cmd
assert "-b:v" in cmd
assert cmd[cmd.index("-b:v") + 1] == "2000k"
assert cmd[cmd.index("-pass") + 1] == "2"
assert cmd[cmd.index("-passlogfile") + 1] == str(stats)
assert cmd[-1] == str(req.output)
Expand Down Expand Up @@ -324,6 +351,8 @@ def fake_run(cmd, capture_output, text, check):
assert res.exit_status == 0
assert res.encode_size_bytes == 4096
assert len(invocations) == 2
assert "-crf" not in invocations[0]
assert "-crf" not in invocations[1]
assert invocations[0][invocations[0].index("-pass") + 1] == "1"
assert invocations[1][invocations[1].index("-pass") + 1] == "2"
pass1_log = invocations[0][invocations[0].index("-passlogfile") + 1]
Expand Down
Loading