Skip to content

Decode clean-aperture sources at their full stored size, never resampled - #106

Open
StuartCameronCode wants to merge 1 commit into
mainfrom
fix-clean-aperture-rescale
Open

StuartCameronCode wants to merge 1 commit into
mainfrom
fix-clean-aperture-rescale

Conversation

@StuartCameronCode

Copy link
Copy Markdown
Owner

Bug

Since FFmpeg 7.1, ffmpeg applies container crop metadata by default. pal-sd-25.mov (720×576 ProRes with a QuickTime clap atom) decodes at 702×576; the decoder's -s 720x576 then rescaled it back to 720. So every clean-aperture source was cropped and stretched 2.6% horizontally before any filter ran, still stamped with the source SAR. Matroska PixelCrop behaves the same.

Fix

  • Both decoders use -apply_cropping codec and no -s: the pipeline sees exactly ffprobe's size, and the user's Crop controls are the only crop. (none would be wrong — H.264 1080p decodes at 1920×1088.)
  • SAR unchanged: it describes the pixel grid, which is the same with or without the edges. The only loss is the clap hint in the output; an 8/9 Crop gives the same picture.
  • A size mismatch is now an error: a guard crop filter is a no-op at the right size (bit-identical) and fails the decode otherwise. The worker reports the guard failure before the vspipe/encoder status, since pipe_source would otherwise pad a short stream into a "successful" job.
  • The worker probes the frame size when a job omits it (fill_frame_size). The old 720×480 fallback plus -s was squeezing sources; integration_video_trimming_test had been turning a 720×576 AVI into 720×480 and passing.
  • The app's "before" frame and timeline thumbnails decode the same way (PreviewGenerator.sourceDecodeOptions).

Behaviour change: a source that changes resolution mid-stream (e.g. DVB 720→544 at an ad break) was silently rescaled and now fails the job with an explanation.

Tests

  • Rust: 8 new source_decode tests; preview_integration_test reference now decodes the full frame unscaled.
  • New heavy integration_clean_aperture_test.dart: clap MOV and an MKV PixelCrop remux come through bit-identical to a full-frame decode; a mid-stream 720→544 TS fails with the explanation. All three fail without the fix.
  • Locally (macOS arm64): cargo test green except the whisper test (add-on not installed); full heavy suite 391 pass (the 2 restart tests need VAPOURBOX_WORKER, then pass); --exclude-tags heavy green except vapoursynth_integration_test, which can't find deps/ from a worktree.
  • The existing Rust preview test's threshold (20) can't tell fixed (5.8) from unfixed (9.0); the heavy test is what catches this.

Not verified: Windows/Linux/macOS x64 (all pinned to FFmpeg 9.0, which has the option); no newly authored clean-aperture file (ffmpeg can't write the metadata).

Docs: new "Source Frame Size" rule in CLAUDE.md; dated write-up in docs/ENGINEERING_NOTES.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_014GLXdGLfPwgYjW1AkonGqN

pal-sd-25.mov is 720x576 per ffprobe but carries a QuickTime clean-aperture
(clap) atom. FFmpeg 7.1+ applies container cropping by default, so it
decoded at 702x576, and the decoder's `-s 720x576` (added to stop the
resulting raw-stream desync) silently rescaled the crop back to 720: the
source was cropped and resampled, 2.6% stretched, before any filter ran.

- New worker/src/source_decode.rs builds both decoders (encode + preview):
  `-apply_cropping codec` before -i (drop container crop, keep the SPS crop
  ffprobe's width/height already include), no -s, and a size-guard crop
  filter that passes the probed size untouched and fails the decode on any
  other size, including a mid-stream resolution change. The worker reports
  that failure with an explanation ahead of vspipe/encoder errors.
- The worker probes width/height when a job omits them, instead of the
  720x480 fallback the guard would now reject (and -s used to squeeze to).
- The app's before-frame and thumbnails decode the same full frame
  (PreviewGenerator.sourceDecodeOptions).
- Tests: source_decode unit tests on the emitted args; heavy
  integration_clean_aperture_test.dart requires a passthrough FFV1 encode of
  the clap MOV (and an MKV PixelCrop remux) to be bit-identical to a
  full-frame decode, and a mid-stream size change to fail with the
  explanation. All three fail against the previous worker.
- CLAUDE.md rule + dated write-up in docs/ENGINEERING_NOTES.md.

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