feat(tools): vmaf-tune — x265 codec adapter#362
Merged
Conversation
377ad2a to
ee639a3
Compare
14 tasks
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>
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>
ee639a3 to
80c8db6
Compare
There was a problem hiding this comment.
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
X265Adapterand register it in the codec adapter registry so--encoder libx265is supported. - Extend
encode.parse_versions()to parse x265’s banner and recordlibx265-<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
--presetand--crfas x264-specific, but the tool now supports--encoder libx265too. 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.txtappends the slug0288-vmaf-tune-codec-adapter-x265, but the new fragment file added in this PR is named0276-vmaf-tune-codec-adapter-x265.md. With the current mismatch,concat-adr-index.shwill 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 to0288-...(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>.mdfiles. Ifdocs/adr/README.mdis 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
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>
This was referenced May 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the first sibling codec to the ADR-0237 Phase A
libx264scaffold:the
libx265adapter. This is the smallest unblock for PR #354 audit'stop blocker — "codec adapter coverage" — which was gating buckets #6
(bitrate-ladder), #7 (codec-comparison), #9 (HDR), #15 (Pareto). With
this PR
vmaf-tuneis no longer x264-only.tools/vmaf-tune/src/vmaftune/codec_adapters/x265.py— frozenX265Adaptermirroringx264.py's shape: ten presets (addsplacebo), 0..51 CRF window pinned to the same Phase A informativerange,
profile_for(pix_fmt)helper that mapsyuv420p10le→main10for downstream HDR work.codec_adapters/__init__.pyregisters it underlibx265. CLI--encoderacceptslibx264 | libx265viachoices=list(known_codecs()).encode.parse_versions(stderr, encoder=…)gains an encoder-awarebanner regex so corpus rows record
libx265-<version>correctly.Default remains
libx264for backward compatibility.tests/test_codec_adapter_x265.py. Real-binary integration testgated on
VMAF_TUNE_INTEGRATION=1(skipped here when unset).No
SCHEMA_VERSIONbump — the existingencoderrow column alreadycarries 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)
x264.py;the option-space is exhausted in ADR-0288 §Alternatives considered.
weighed: one-file mirror chosen vs. multi-codec single adapter,
defer-until-corpus, and svtav1-first).
tools/vmaf-tune/AGENTS.mdupdated todocument the wired-codecs list (libx264, libx265) and the per-codec
banner-regex carve-out in
parse_versions.python -m pytest tools/vmaf-tune/tests/.changelog.d/added/ADR-0288-vmaf-tune-x265-adapter.md.docs/rebase-notes.mdentry 0228.Note on CHANGELOG diff size
origin/masterwas already in drift against thechangelog.d/-fragments source-of-truth; runningscripts/release/concat-changelog-fragments.sh --write(the canonicalregeneration) flushed that drift, hence the large
CHANGELOG.mddiff.The actual added bullet for this PR is the single
changelog.d/added/ADR-0288-vmaf-tune-x265-adapter.mdfragment.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 --checkclean 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 indexregenerated from fragments (ADR-0221 pattern).
test_known_codecs_includes_x264_and_x265rewrite of the oldtest_known_codecs_phase_a_is_x264_onlyare the right shape.🤖 Generated with Claude Code