Skip to content

Frame-accurate preview + seeking (FrameMap frame-mapping seam)#45

Merged
StuartCameronCode merged 1 commit into
mainfrom
frame-accurate-preview
Jul 1, 2026
Merged

Frame-accurate preview + seeking (FrameMap frame-mapping seam)#45
StuartCameronCode merged 1 commit into
mainfrom
frame-accurate-preview

Conversation

@StuartCameronCode

Copy link
Copy Markdown
Owner

Summary

Makes the preview frame-accurate and adds frame-accurate seeking, built on a new FrameMap descriptor that becomes the single source↔output frame-mapping seam in the worker.

FrameMap (worker/src/models/processing_pipeline.rs)

  • enum FrameMap { Identity, Fanout, Decimate, Retime } with output_count() (forward) and inverse() (carrying an exact flag).
  • ProcessingPipeline::{frame_maps, output_count, invert, total_radius} compose the per-pass maps. Today: QTGMC double-rate → Fanout{2}; IVTC/soft-telecine → Decimate{cycle, keep}; everything else identity.
  • Replaces the hardcoded is_double_rate / is_ivtc progress ladder in pipeline_executor.rs with pipeline.output_count(vspipe_total) — behavior-preserving (proven by test).
  • A future frame-rate filter plugs in as a Retime variant with no new special-cases.

Frame-accurate preview (worker + templates)

  • generate_preview takes an integer source frame, decodes the window [target-radius ..= target+radius] (radius = total_radius(), floored at MIN_PREVIEW_RADIUS), and emits the exact output index output_count(local) — replacing the old clip[num_frames // 2] "middle of 11" heuristic.
  • Seeks land exactly via center-of-interval (frame - 0.5)/fps (with -ss before -i), avoiding ±1 PTS-rounding ambiguity. The same fix makes start-frame trimming exact. Window geometry is the pure, unit-tested preview_window().
  • Echoes PREVIEW_INFO:frame=N back. Stays inside the pipe_source streaming model — no BestSource reintroduced.

Flutter

  • Preview driven by an integer frame index end-to-end (no time round-trip). Before/after now seek to the same source frame.
  • New FrameMath (lib/services/frame_math.dart) — pure, stable position⇄frame conversion.
  • View-model exposes currentFrameIndex / totalFrames / seekToFrame / stepFrame; the preview panel gains ◀/▶ step-by-frame buttons + a f N / total readout.
  • generateProcessedPreview now guards against 0×0 (probe-pending) jobs — a slow first-run ffprobe no longer surfaces a misleading "Invalid video dimensions" error; it self-heals once probing completes.

Testing

Suite Result
cargo test --lib 75 pass (incl. FrameMap + preview_window edge cases)
cargo test --test filter_integration_test 56 pass (incl. new test_55_preview_selects_exact_frame, test_56_frame_count_mapping)
flutter test test/frame_math_test.dart 5 pass (incl. round-trip-no-drift)
Flutter pure unit tests 62 pass
flutter analyze (changed files) clean

Also built and launched the macOS debug app.

Notes

  • Docs: added a processing_pipeline.rs row to the CLAUDE.md worker key-files table.
  • Not in this PR: warming bundled ffmpeg/ffprobe after deps download (a separate macOS-beta first-run Gatekeeper-assessment mitigation).

🤖 Generated with Claude Code

Add a FrameMap descriptor (Identity/Fanout/Decimate/Retime) in
processing_pipeline.rs as the single source<->output frame-mapping seam:
- output_count() drives the progress total, replacing the hardcoded
  double-rate/IVTC ladder in pipeline_executor.rs (behavior-preserving).
- invert()/total_radius() drive the frame-accurate preview window.

Worker: generate_preview now takes an integer source frame, decodes a
window centred on it, and emits the exact output index it maps to
(accounting for QTGMC double-rate) instead of the old "middle of 11"
heuristic. Seeks land exactly via center-of-interval ((frame-0.5)/fps);
the same fix makes start-frame trimming exact.

Flutter: the preview is driven by an integer frame index end-to-end (no
time round-trip). FrameMath does the pure position<->frame conversion;
the view-model exposes currentFrameIndex/totalFrames/seekToFrame/
stepFrame; the preview panel gains step-by-frame buttons + a frame
readout. Before/after now seek to the same source frame. Guard
generateProcessedPreview against 0x0 (probe-pending) jobs so a slow
first-run ffprobe no longer surfaces a misleading dimensions error.

Tests: FrameMap + preview_window unit tests, integration tests 55/56,
and FrameMath Dart round-trip tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@StuartCameronCode StuartCameronCode merged commit 3070ca7 into main Jul 1, 2026
4 checks passed
StuartCameronCode added a commit that referenced this pull request Jul 1, 2026
Add a FrameMap descriptor (Identity/Fanout/Decimate/Retime) in
processing_pipeline.rs as the single source<->output frame-mapping seam:
- output_count() drives the progress total, replacing the hardcoded
  double-rate/IVTC ladder in pipeline_executor.rs (behavior-preserving).
- invert()/total_radius() drive the frame-accurate preview window.

Worker: generate_preview now takes an integer source frame, decodes a
window centred on it, and emits the exact output index it maps to
(accounting for QTGMC double-rate) instead of the old "middle of 11"
heuristic. Seeks land exactly via center-of-interval ((frame-0.5)/fps);
the same fix makes start-frame trimming exact.

Flutter: the preview is driven by an integer frame index end-to-end (no
time round-trip). FrameMath does the pure position<->frame conversion;
the view-model exposes currentFrameIndex/totalFrames/seekToFrame/
stepFrame; the preview panel gains step-by-frame buttons + a frame
readout. Before/after now seek to the same source frame. Guard
generateProcessedPreview against 0x0 (probe-pending) jobs so a slow
first-run ffprobe no longer surfaces a misleading dimensions error.

Tests: FrameMap + preview_window unit tests, integration tests 55/56,
and FrameMath Dart round-trip tests.
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