Skip to content

Declare colour tags on the Y4M input so tagging the output stops re-matrixing pixels - #105

Open
StuartCameronCode wants to merge 1 commit into
mainfrom
fix-colour-tag-pixel-shift
Open

StuartCameronCode wants to merge 1 commit into
mainfrom
fix-colour-tag-pixel-shift

Conversation

@StuartCameronCode

Copy link
Copy Markdown
Owner

Bug

The worker re-stamps the source's colour tags as encoder output flags. The Y4M pipe from vspipe carries no colour info, so FFmpeg 9.0 negotiated csp:unknown → csp:bt709 in the filter graph and auto-inserted a scaler that treated "unknown" as BT.601 — every encode from a tagged source was re-matrixed 601→709 (e.g. Y/U/V 565/236/756 → 546/259/741). -colorspace alone triggers it; rawvideo and Y4M inputs behave the same; ProRes 4444 with its pinned pix_fmt too.

Also latent: output-only -color_primaries/-color_trc never reached the file (the encoder copies them from the untagged frames), so only 2 of 4 tags were written.

Fix

ColorMetadata gains to_ffmpeg_input_args() / to_ffmpeg_output_args() (the same list by construction). build_ffmpeg_args and build_ffmpeg_args_for_test declare the tags between -f yuv4mpegpipe and -i -, and keep the output flags. Input and output agree, so nothing is converted, and all four tags are written.

  • Not setparams in -vf: a Custom FFmpeg Argument containing -vf replaces the whole chain and would silently bring the shift back.
  • Output flags kept: input-only declaration negotiated a full-range source down to limited.
  • Checked pixel-identical for every matrix/primaries/transfer/range value ColorMetadata accepts, and for x264, ProRes 4444, HuffYUV (-pix_fmt yuv422p) and hevc_videotoolbox.
  • Preview unchanged — it already fed the source tags to swscale, which is why preview and encode disagreed.

User-visible: encodes from tagged sources change pixels (to the correct ones).

Tests

  • Rust: new ColorMetadata unit tests; arg-position test for the Y4M input declaration; a check on build_ffmpeg_args' own source (the real builder needs located deps).
  • New heavy integration_colour_tag_pixels_test.dart on tagged pal-sd-25.mov: FFV1 tagged encode bit-identical to untagged and to the source, all four tags present; encode matches preview (diff 0.000/255); ProRes 4444 tagged == untagged.
  • With the input declaration disabled, all three encode tests fail (md5 mismatches; preview diff 6.131/255) and the new Rust tests fail.
  • Locally (macOS arm64): cargo test green except the whisper test (add-on not installed); flutter test --exclude-tags heavy green except vapoursynth_integration_test, which can't find deps/ from a worktree.

Not verified: NVENC/QSV/AMF; Windows/Linux/macOS x64; a full-range source end to end (raw ffmpeg only).

Docs: CLAUDE.md colour-metadata rule updated; dated write-up in docs/ENGINEERING_NOTES.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_014GLXdGLfPwgYjW1AkonGqN

…atrixing pixels

The worker re-stamped the source's colour tags as encoder output flags only.
With FFmpeg 9.0.1 that is not a relabelling: the Y4M pipe is untagged
(csp:unknown), the output demands bt709, so the graph auto-inserts a scaler
that treats "unknown" as BT.601 and converts every pixel 601->709. Every encode
from a tagged source was colour-shifted (flat 10-bit frame 565/236/756 came
back 546/259/741; the encode differed from its own preview by 6.1/255).
Output-only -color_primaries/-color_trc also never reached the file.

Declare the same tags on the pipe input (between -f yuv4mpegpipe and -i -)
as well as on the output, so there is nothing to convert. Input options rather
than setparams because a Custom FFmpeg Argument -vf replaces the whole -vf
chain. The output flags stay: input-only let a full-range source be negotiated
down to limited. build_ffmpeg_args and build_ffmpeg_args_for_test updated
together; the misleading "metadata fix, not a pixel one" comment is corrected.

Tests: Rust unit tests for input/output agreement and argument placement (plus
a source-level check on the real builder); a heavy Flutter test that encodes
the tagged pal-sd-25.mov fixture and asserts bit-identical samples vs the
untagged encode and the source (FFV1), vs untagged for ProRes 4444's pinned
pix_fmt, all four tags written, and a match with the preview. All fail with
the input declaration removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GLXdGLfPwgYjW1AkonGqN
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.

1 participant