Skip to content

feat(tools): vmaf-tune — x265 codec adapter#362

Merged
lusoris merged 4 commits intomasterfrom
feat/vmaf-tune-codec-adapter-x265
May 5, 2026
Merged

feat(tools): vmaf-tune — x265 codec adapter#362
lusoris merged 4 commits intomasterfrom
feat/vmaf-tune-codec-adapter-x265

Conversation

@lusoris
Copy link
Copy Markdown
Owner

@lusoris lusoris commented May 3, 2026

Summary

Adds the first sibling codec to the ADR-0237 Phase A libx264 scaffold:
the libx265 adapter. This is the smallest unblock for PR #354 audit's
top blocker — "codec adapter coverage" — which was gating buckets #6
(bitrate-ladder), #7 (codec-comparison), #9 (HDR), #15 (Pareto). With
this PR vmaf-tune is no longer x264-only.

  • New tools/vmaf-tune/src/vmaftune/codec_adapters/x265.py — frozen
    X265Adapter mirroring x264.py's shape: ten presets (adds
    placebo), 0..51 CRF window pinned to the same Phase A informative
    range, profile_for(pix_fmt) helper that maps yuv420p10le
    main10 for downstream HDR work.
  • codec_adapters/__init__.py registers it under libx265. CLI
    --encoder accepts libx264 | libx265 via
    choices=list(known_codecs()).
  • encode.parse_versions(stderr, encoder=…) gains an encoder-aware
    banner regex so corpus rows record libx265-<version> correctly.
    Default remains libx264 for backward compatibility.
  • 14 new subprocess-mocked smoke tests under
    tests/test_codec_adapter_x265.py. Real-binary integration test
    gated on VMAF_TUNE_INTEGRATION=1 (skipped here when unset).

No SCHEMA_VERSION bump — the existing encoder row column already
carries codec identity, so Phase B/C consumers receive the new codec
without a contract change. ADR-0235 codec-aware FR regressor inherits
multi-codec corpus rows the moment x265 encodes accumulate.

ADR: ADR-0276.
Parent: ADR-0237.

Six deep-dive deliverables (ADR-0108)

  • (1) Research digest: no digest needed: trivial mirror of x264.py;
    the option-space is exhausted in ADR-0288 §Alternatives considered.
  • (2) Decision matrix: ADR-0288 §Alternatives considered (4 options
    weighed: one-file mirror chosen vs. multi-codec single adapter,
    defer-until-corpus, and svtav1-first).
  • (3) AGENTS.md invariant note: tools/vmaf-tune/AGENTS.md updated to
    document the wired-codecs list (libx264, libx265) and the per-codec
    banner-regex carve-out in parse_versions.
  • (4) Reproducer / smoke-test command: python -m pytest tools/vmaf-tune/tests/.
  • (5) CHANGELOG fragment: changelog.d/added/ADR-0288-vmaf-tune-x265-adapter.md.
  • (6) Rebase note: docs/rebase-notes.md entry 0228.

Note on CHANGELOG diff size

origin/master was already in drift against the
changelog.d/-fragments source-of-truth; running
scripts/release/concat-changelog-fragments.sh --write (the canonical
regeneration) flushed that drift, hence the large CHANGELOG.md diff.
The actual added bullet for this PR is the single
changelog.d/added/ADR-0288-vmaf-tune-x265-adapter.md fragment.

Test plan

  • python -m pytest tools/vmaf-tune/tests/ — 29 passed, 1 skipped
    (the skipped case is the real-binary integration test gated on
    VMAF_TUNE_INTEGRATION=1).
  • ruff check + ruff format --check clean on touched files
    (also enforced by pre-commit black + isort + ruff hooks).
  • bash scripts/release/concat-changelog-fragments.sh --check — green.
  • bash scripts/docs/concat-adr-index.sh --write — README index
    regenerated from fragments (ADR-0221 pattern).
  • Reviewer: confirm the 14 new x265 tests + the membership-based
    test_known_codecs_includes_x264_and_x265 rewrite of the old
    test_known_codecs_phase_a_is_x264_only are the right shape.

🤖 Generated with Claude Code

@lusoris lusoris force-pushed the feat/vmaf-tune-codec-adapter-x265 branch 2 times, most recently from 377ad2a to ee639a3 Compare May 3, 2026 19:37
lusoris pushed a commit that referenced this pull request May 3, 2026
…s 17 adapters)

Refactors `tools/vmaf-tune/src/vmaftune/encode.py` away from the Phase A
hard-coded `libx264` `-c:v / -preset / -crf` argv. `run_encode` now
looks up the codec adapter via `codec_adapters.get_adapter(req.encoder)`
and asks it for the FFmpeg argv slice via
`adapter.ffmpeg_codec_args(preset, quality)` plus an optional
`adapter.extra_params()`. Adapters that don't yet expose
`ffmpeg_codec_args` fall back silently to the legacy x264-CRF shape so
partial in-flight adapter PRs stay drivable end-to-end.
`parse_versions(stderr, encoder=...)` selects a per-codec version probe
(libx264, libx265, libsvtav1, libvpx-vp9, libaom-av1, libvvenc, NVENC,
QSV, AMF, VideoToolbox); unknown encoders return "unknown" rather than
raising. The `EncodeRequest.crf` field is preserved unchanged for the
SCHEMA_VERSION=1 row contract; a `quality` property mirrors it for
adapter-side codec-agnostic vocabulary.

Existing 13-test x264 suite still green; new 19-test multi-codec suite
covers 9 representative codec shapes plus the unknown-codec /
missing-method fallback paths. Unblocks 17 in-flight codec adapter PRs
(#360 libaom, #362 libx265, #364 NVENC, #366 AMF, #367 QSV, #368
libvvenc, #370 libsvtav1, #373 VideoToolbox, plus follow-on waves)
which can now drive end-to-end encodes without copying or mutating the
harness.

Ships ADR-0294 + research digest 0054, vmaf-tune.md "Codec adapter
contract" section, rebase-notes #228 invariant, CHANGELOG entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lusoris lusoris marked this pull request as ready for review May 5, 2026 07:26
Copilot AI review requested due to automatic review settings May 5, 2026 07:26
Adds the first sibling codec to the ADR-0237 Phase A `libx264` scaffold:
a one-file `X265Adapter` mirroring the `x264.py` shape (10 presets
including `placebo`, 0..51 CRF window, `profile_for(pix_fmt)` helper
that maps `yuv420p10le` → `main10` for downstream HDR work).
Registered under `libx265` in `codec_adapters/__init__.py`; CLI
`--encoder` now accepts `libx264 | libx265` via
`choices=list(known_codecs())`. `encode.parse_versions` gains an
encoder-aware regex so corpus rows record `libx265-<version>` correctly
(default remains `libx264` for backward compatibility).

No `SCHEMA_VERSION` bump — the existing `encoder` row column already
carries codec identity. Phase B/C consumers receive the new codec
without any contract change.

14 new subprocess-mocked smoke tests under
`tools/vmaf-tune/tests/test_codec_adapter_x265.py` (29 of 30 vmaf-tune
tests pass green; the one skipped case is the real-binary integration
test gated on `VMAF_TUNE_INTEGRATION=1`).

Unblocks ADR-0235 codec-aware FR regressor and PR #354 audit's
buckets #6 (bitrate-ladder), #7 (codec-comparison), #9 (HDR), #15
(Pareto).

Six deep-dive deliverables (ADR-0108):
1. research digest: no digest needed — trivial mirror of `x264.py`;
   alternatives matrix is exhausted in ADR-0276.
2. decision matrix: ADR-0276 §Alternatives considered.
3. AGENTS.md invariant note: tools/vmaf-tune/AGENTS.md updated to
   document the per-codec banner-regex carve-out in `parse_versions`
   and the wired-codecs list (libx264, libx265).
4. reproducer: `python -m pytest tools/vmaf-tune/tests/`.
5. CHANGELOG: changelog.d/added/ADR-0276-vmaf-tune-x265-adapter.md.
6. rebase-notes: docs/rebase-notes.md entry 0228.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lusoris lusoris force-pushed the feat/vmaf-tune-codec-adapter-x265 branch from ee639a3 to 80c8db6 Compare May 5, 2026 07:28
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a libx265 codec adapter to the tools/vmaf-tune/ Phase A harness (previously x264-only), including CLI wiring, encoder-version parsing, tests, and documentation/ADR updates.

Changes:

  • Add X265Adapter and register it in the codec adapter registry so --encoder libx265 is supported.
  • Extend encode.parse_versions() to parse x265’s banner and record libx265-<version> in corpus rows.
  • Add a subprocess-mocked x265 test suite (plus opt-in integration smoke) and update docs/ADR index + changelog fragment.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tools/vmaf-tune/src/vmaftune/codec_adapters/x265.py New frozen x265 adapter (presets, CRF range, pix_fmt→profile helper).
tools/vmaf-tune/src/vmaftune/codec_adapters/init.py Register libx265 and export X265Adapter.
tools/vmaf-tune/src/vmaftune/encode.py Add x265 banner regex + encoder-aware parse_versions(); pass encoder through from run_encode().
tools/vmaf-tune/src/vmaftune/cli.py Expand CLI help text and --encoder choices to include x265.
tools/vmaf-tune/tests/test_codec_adapter_x265.py New x265-focused smoke tests (mocked subprocess + opt-in real ffmpeg run).
tools/vmaf-tune/tests/test_corpus.py Update codec registry test to be membership-based and include x265.
tools/vmaf-tune/AGENTS.md Document multi-codec posture and the allowed parse_versions() codec dispatch exception.
docs/usage/vmaf-tune.md Document multi-codec Phase A and add an x265 usage example.
docs/adr/0288-vmaf-tune-codec-adapter-x265.md New ADR for the x265 adapter decision.
docs/adr/_index_fragments/0276-vmaf-tune-codec-adapter-x265.md New ADR index-row fragment (but slug/filename mismatch vs ADR number).
docs/adr/_index_fragments/_order.txt Append new ADR slug to the ADR index manifest.
docs/adr/README.md Regenerated ADR index table output.
docs/rebase-notes.md Add rebase note for the new x265 adapter changes.
changelog.d/added/ADR-0276-vmaf-tune-x265-adapter.md New changelog fragment describing the adapter addition (filename/ADR number mismatch).
Comments suppressed due to low confidence (3)

docs/usage/vmaf-tune.md:74

  • The CLI flags table still describes --preset and --crf as x264-specific, but the tool now supports --encoder libx265 too. Please update these descriptions to be codec-agnostic (e.g., “encoder preset name” / “encoder CRF integer”) so the docs match the new multi-codec behavior.
`--source` is repeatable — pass one flag per source clip. The grid is the
Cartesian product of `--preset × --crf`.

docs/adr/_index_fragments/_order.txt:164

  • _order.txt appends the slug 0288-vmaf-tune-codec-adapter-x265, but the new fragment file added in this PR is named 0276-vmaf-tune-codec-adapter-x265.md. With the current mismatch, concat-adr-index.sh will emit a warning (“_order.txt lists missing fragment ...”) and the row will be appended in the fallback tail instead of appearing in the intended position. Rename the fragment to 0288-... (or adjust the slug here) so the manifest and fragment filenames match.
0221-changelog-adr-fragment-pattern
0241-hip-first-consumer-psnr
0257-mobilesal-real-weights-deferred
0263-ossf-scorecard-policy
0253-speed-qa-extractor

docs/adr/README.md:259

  • The regenerated ADR index appears to have dropped rows for ADRs that still exist under docs/adr/ (e.g. 0222-vmaf-per-shot-tool.md, 0237-quality-aware-encode-automation.md, and others) because there are no corresponding _index_fragments/<slug>.md files. If docs/adr/README.md is source-of-truth rendered from fragments, those missing ADRs need fragments added (and optionally added to _order.txt) before rewriting the README; otherwise the index becomes incomplete.
| [ADR-0221](0221-changelog-adr-fragment-pattern.md) | T7-39 — CHANGELOG + ADR-index fragment-file pattern. New `changelog.d/<section>/<topic>.md` and `docs/adr/_index_fragments/<slug>.md` per-PR fragment trees + two in-tree concat scripts (`scripts/release/concat-changelog-fragments.sh`, `scripts/docs/concat-adr-index.sh`) replace edits to the consolidated `CHANGELOG.md` Unreleased block + `docs/adr/README.md` index table. Eliminates the per-PR merge-conflict surface that cost ≈16 min per PR over the 2026-04-28 sprint. Migration is content-preserving: existing 3119-line Unreleased body archived verbatim as `changelog.d/_pre_fragment_legacy.md`; 159 ADR rows split per-slug with a frozen `_order.txt` preserving the existing commit-merge order. PR template + Doc-Substance Gate (ADR-0167) updated to recognise fragment files as CHANGELOG entries. release-please workflow integration tracked as T7-39b follow-up. | Accepted | process, release, docs, ci, fork-local |
| [ADR-0241](0241-hip-first-consumer-psnr.md) | T7-10 first-consumer PR — `integer_psnr_hip` host scaffolding via mirrored kernel-template. Ships [`libvmaf/src/hip/kernel_template.{h,c}`](../../libvmaf/src/hip/kernel_template.h) (field-for-field mirror of `cuda/kernel_template.h` from ADR-0221: `VmafHipKernelLifecycle` private-stream + 2-event struct, `VmafHipKernelReadback` device-accumulator + pinned-host pair, six lifecycle helpers) + [`libvmaf/src/feature/hip/integer_psnr_hip.{c,h}`](../../libvmaf/src/feature/hip/integer_psnr_hip.c) (first consumer; mirrors `integer_psnr_cuda.c`'s init/submit/collect/close call graph verbatim). Helpers and the consumer's submit/collect return `-ENOSYS` while the runtime PR (T7-10b) is pending; bodies flip to live HIP without touching consumer call-sites. New `vmaf_fex_psnr_hip` registered in `feature_extractor_list` under `#if HAVE_HIP` so callers get "extractor found, runtime not ready" instead of "no such extractor". New `VMAF_FEATURE_EXTRACTOR_HIP = 1 << 6` flag bit reserved (unused until T7-10b adds the picture buffer-type plumbing). Smoke test grows 5 sub-tests pinning template-helper `-ENOSYS` contracts + extractor-name lookup; 14/14 pass under `-Denable_hip=true`. CPU baseline (47/47) + HIP scaffold (48/48) green. No ROCm SDK required (matches ADR-0212's audit-first split). Out-of-line helpers (not `static inline` like CUDA) so the runtime PR has one editing target. Mirrors the Vulkan T5-1 → T5-1b cadence; runtime + remaining kernels remain in T7-10b. | Accepted | gpu, hip, rocm, amd, kernel-template, fork-local |
| [ADR-0257](0257-mobilesal-real-weights-deferred.md) | Defers the T6-2a-followup real-weights swap for `mobilesal_placeholder_v0` indefinitely. Upstream `yuhuan-wu/MobileSal` is CC BY-NC-SA 4.0 (incompatible with the fork's BSD-3-Clause-Plus-Patent), distributes weights only via Google Drive viewer URLs (no GitHub release / pinnable raw URL), and is RGB-D (the C contract is RGB-only). ADR-0218's "MIT-licensed" claim was inaccurate; corrected here and in `docs/ai/models/mobilesal.md`. Smoke-only placeholder remains shipped; recommended replacement is U-2-Net `u2netp` (Apache-2.0), filed as backlog row T6-2a-replace-with-u2netp. Companion to Research-0053. | Accepted | ai, dnn, mobilesal, saliency, license, fork-local, docs |
| [ADR-0253](0253-ossf-scorecard-policy.md) | OSSF Scorecard policy + remediation cadence. Diagnoses the months-long red `scorecard.yml` workflow (root cause: `github/codeql-action/upload-sarif` SHA `b25d0ebf...` is an "imposter commit" — no longer exists in the action's repository, so Scorecard's webapp returns 400 on publish). Repins to current `v4` head `e46ed2cbd0...` to unblock. Documents per-check scoring against the 6.2 / 10 baseline (target ≥ 7.0): accepted blockers are `Code-Review` (solo-maintainer artefact), `Branch-Protection` (`GITHUB_TOKEN` can't read classic rules without a fine-grained PAT, which the secret-policy forbids), `Maintained` (auto-resolves at 90-day age), `CII-Best-Practices` (out-of-scope external badge application). Active remediation queue: `Vulnerabilities` (bump `python/requirements.txt` lower bounds), `Pinned-Dependencies` (upstream Dockerfile-parser bug + SHA-pin sweep), `Fuzzing` (OSS-Fuzz onboarding), `Signed-Releases` (resolves on first release-please cut), `Packaging`. Companion research digest: [`docs/research/0053-ossf-scorecard-investigation.md`](../research/0053-ossf-scorecard-investigation.md). 90-day re-evaluation cadence. | Accepted | ci, security, supply-chain, docs, fork-local |
| [ADR-0253](0253-speed-qa-extractor.md) | Defer SpEED-QA full-reference reduction. Closes the user's 2026-04-21 deep-research queued track. The fork keeps `speed_chroma` / `speed_temporal` (PR #213, port of upstream `d3647c73`) as research-stage extractors gated behind `-Denable_float=true`; does not add a `speed_qa` reduction; does not register a SpEED-driven model. Rationale: SpEED-QA overlaps `vif` substantially (both GSM-prior divisive-normalisation entropy estimators), the "speed" headline inverts on the fork's AVX-512 / CUDA / SYCL VIF stack, and the assumed `model/speed_4_v0.6.0.json` upstream binary does not exist — there is no Netflix artefact to mirror. Reversible on three named triggers (Netflix lands a model JSON consuming SpEED features; explicit user request; FUNQUE+ / pVMAF / tiny-AI fusion model names SpEED-QA as load-bearing input). Companion research digest: [`docs/research/0051-speed-qa-feasibility.md`](../research/0051-speed-qa-feasibility.md). | Proposed | metrics, research, feature-extractor, roadmap |
| [ADR-0265](0265-u2netp-saliency-replacement-blocked.md) | Defers the T6-2a-replace-with-u2netp model-family swap recommended by ADR-0257. Upstream `xuebinqin/U-2-Net` is Apache-2.0 (license OK) but `u2netp.pth` is distributed only via Google Drive (no GitHub release, no pinnable raw URL — same blocker as MobileSal in ADR-0257), and U-2-Net's `F.upsample(..., mode='bilinear')` lowers to ONNX `Resize` which is not on the fork's `op_allowlist.c`. Smoke-only placeholder remains shipped; recommended next step is `T6-2a-widen-allowlist-resize` (separate ADR-scope decision) before another saliency-replacement attempt. Companion to Research-0054. | Accepted | ai, dnn, mobilesal, u2netp, saliency, license, op-allowlist, fork-local, docs |
| [ADR-0241](0241-hip-first-consumer-psnr.md) | T7-10 first-consumer PR — `integer_psnr_hip` host scaffolding via mirrored kernel-template. Ships [`libvmaf/src/hip/kernel_template.{h,c}`](../../libvmaf/src/hip/kernel_template.h) (field-for-field mirror of `cuda/kernel_template.h` from ADR-0221: `VmafHipKernelLifecycle` private-stream + 2-event struct, `VmafHipKernelReadback` device-accumulator + pinned-host pair, six lifecycle helpers) + [`libvmaf/src/feature/hip/integer_psnr_hip.{c,h}`](../../libvmaf/src/feature/hip/integer_psnr_hip.c) (first consumer; mirrors `integer_psnr_cuda.c`'s init/submit/collect/close call graph verbatim). Helpers and the consumer's submit/collect return `-ENOSYS` while the runtime PR (T7-10b) is pending; bodies flip to live HIP without touching consumer call-sites. New `vmaf_fex_psnr_hip` registered in `feature_extractor_list` under `#if HAVE_HIP` so callers get "extractor found, runtime not ready" instead of "no such extractor". New `VMAF_FEATURE_EXTRACTOR_HIP = 1 << 6` flag bit reserved (unused until T7-10b adds the picture buffer-type plumbing). Smoke test grows 5 sub-tests pinning template-helper `-ENOSYS` contracts + extractor-name lookup; 14/14 pass under `-Denable_hip=true`. CPU baseline (47/47) + HIP scaffold (48/48) green. No ROCm SDK required (matches ADR-0212's audit-first split). Out-of-line helpers (not `static inline` like CUDA) so the runtime PR has one editing target. Mirrors the Vulkan T5-1 → T5-1b cadence; runtime + remaining kernels remain in T7-10b. | Accepted | gpu, hip, rocm, amd, kernel-template, fork-local |

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +106 to +113
if encoder == "libx265":
enc = _X265_VERSION_RE.search(stderr)
encoder_version = f"libx265-{enc.group(1)}" if enc else "unknown"
else:
# libx264 (and any future codec without a dedicated branch) falls
# back to the x264 regex so old callers keep working.
enc = _X264_VERSION_RE.search(stderr)
encoder_version = f"libx264-{enc.group(1)}" if enc else "unknown"
assert res.encode_size_bytes == 8192
assert res.ffmpeg_version == "6.1.1"
assert res.encoder_version == "libx265-3.5+1-f0c1022b6"
assert res.encode_time_ms > 0.0
@lusoris lusoris merged commit 50269ba into master May 5, 2026
54 checks passed
@lusoris lusoris deleted the feat/vmaf-tune-codec-adapter-x265 branch May 5, 2026 08:00
lusoris pushed a commit that referenced this pull request May 5, 2026
…s 17 adapters)

Refactors `tools/vmaf-tune/src/vmaftune/encode.py` away from the Phase A
hard-coded `libx264` `-c:v / -preset / -crf` argv. `run_encode` now
looks up the codec adapter via `codec_adapters.get_adapter(req.encoder)`
and asks it for the FFmpeg argv slice via
`adapter.ffmpeg_codec_args(preset, quality)` plus an optional
`adapter.extra_params()`. Adapters that don't yet expose
`ffmpeg_codec_args` fall back silently to the legacy x264-CRF shape so
partial in-flight adapter PRs stay drivable end-to-end.
`parse_versions(stderr, encoder=...)` selects a per-codec version probe
(libx264, libx265, libsvtav1, libvpx-vp9, libaom-av1, libvvenc, NVENC,
QSV, AMF, VideoToolbox); unknown encoders return "unknown" rather than
raising. The `EncodeRequest.crf` field is preserved unchanged for the
SCHEMA_VERSION=1 row contract; a `quality` property mirrors it for
adapter-side codec-agnostic vocabulary.

Existing 13-test x264 suite still green; new 19-test multi-codec suite
covers 9 representative codec shapes plus the unknown-codec /
missing-method fallback paths. Unblocks 17 in-flight codec adapter PRs
(#360 libaom, #362 libx265, #364 NVENC, #366 AMF, #367 QSV, #368
libvvenc, #370 libsvtav1, #373 VideoToolbox, plus follow-on waves)
which can now drive end-to-end encodes without copying or mutating the
harness.

Ships ADR-0294 + research digest 0054, vmaf-tune.md "Codec adapter
contract" section, rebase-notes #228 invariant, CHANGELOG entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lusoris added a commit that referenced this pull request May 5, 2026
…s 17 adapters) (#376)

* feat(tools): vmaf-tune encode.py — codec-agnostic dispatcher (unblocks 17 adapters)

Refactors `tools/vmaf-tune/src/vmaftune/encode.py` away from the Phase A
hard-coded `libx264` `-c:v / -preset / -crf` argv. `run_encode` now
looks up the codec adapter via `codec_adapters.get_adapter(req.encoder)`
and asks it for the FFmpeg argv slice via
`adapter.ffmpeg_codec_args(preset, quality)` plus an optional
`adapter.extra_params()`. Adapters that don't yet expose
`ffmpeg_codec_args` fall back silently to the legacy x264-CRF shape so
partial in-flight adapter PRs stay drivable end-to-end.
`parse_versions(stderr, encoder=...)` selects a per-codec version probe
(libx264, libx265, libsvtav1, libvpx-vp9, libaom-av1, libvvenc, NVENC,
QSV, AMF, VideoToolbox); unknown encoders return "unknown" rather than
raising. The `EncodeRequest.crf` field is preserved unchanged for the
SCHEMA_VERSION=1 row contract; a `quality` property mirrors it for
adapter-side codec-agnostic vocabulary.

Existing 13-test x264 suite still green; new 19-test multi-codec suite
covers 9 representative codec shapes plus the unknown-codec /
missing-method fallback paths. Unblocks 17 in-flight codec adapter PRs
(#360 libaom, #362 libx265, #364 NVENC, #366 AMF, #367 QSV, #368
libvvenc, #370 libsvtav1, #373 VideoToolbox, plus follow-on waves)
which can now drive end-to-end encodes without copying or mutating the
harness.

Ships ADR-0294 + research digest 0054, vmaf-tune.md "Codec adapter
contract" section, rebase-notes #228 invariant, CHANGELOG entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(docs): renumber encode-multi-codec ADR 0294→0297 + research 0069→0070

---------

Co-authored-by: Lusoris <lusoris@pm.me>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants