Skip to content

perf(engine): segment-scope convertSdrToHdr re-encode#432

Open
jrusso1020 wants to merge 1 commit intoperf/producer-phase-instrumentationfrom
perf/producer-segment-scope-hdr-preflight
Open

perf(engine): segment-scope convertSdrToHdr re-encode#432
jrusso1020 wants to merge 1 commit intoperf/producer-phase-instrumentationfrom
perf/producer-segment-scope-hdr-preflight

Conversation

@jrusso1020
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 commented Apr 23, 2026

What

Segment-scope the SDR→HDR preflight in convertSdrToHdr so only the used composition window is re-encoded, not the full source. Direct mirror of the fix #360 already landed for convertVfrToCfr.

Why

PR 2 of the 5-PR stack from hyperframes-notes/producer-render-architecture-review-2026-04-21.md. The doc flags the HDR preflight as one of the expensive items because it re-encodes the whole source regardless of how much of it the composition uses — a 4-second window of a 60-minute screen recording paid minutes of transcode on every render.

The architecture review specifically calls this out as a direct parallel to the VFR fix:

convertSdrToHdr is not segment-scoped. Matches the VFR fix that just landed — this is a straightforward parallel.

How

Two changes in videoFrameExtractor.ts:

  1. SignatureconvertSdrToHdr now takes startTime and duration and uses -ss $start -i $path -t $duration (identical ordering to convertVfrToCfr, which also puts input seek before -i for fast seek + accurate duration).

  2. Caller — Phase 2a now:

    • Reuses the existing probe metadata to compute a segDuration from the composition window (video.end - video.start), falling back to durationSeconds - mediaStart for unbounded clips (same fallback pattern as Phase 3 and Phase 2b).
    • Zeroes out entry.video.mediaStart after a successful re-encode, because the segment-scoped output file starts at t=0. This matches what the VFR fix does and ensures Phase 2b (VFR preflight) and Phase 3 (extraction) seek from 0, not from the original source mediaStart.

The probe map changed shape — now returns { colorSpace, durationSeconds } per entry instead of just colorSpace — so the HDR phase can compute the fallback duration without a second ffprobe.

Interaction with the VFR preflight

HDR runs first (Phase 2a), then VFR (Phase 2b). If both apply to the same SDR source:

  • Phase 2a trims to the composition window and re-encodes to HDR, zeroing mediaStart.
  • Phase 2b reads the HDR-converted file (now the composition-window duration starting at t=0), sees it as VFR if it is, and re-encodes that whole thing to CFR.

That's correct: the VFR pass sees exactly the segment that needs extracting, seeking from 0.

Test plan

  • New regression test in videoFrameExtractor.test.ts with two synthesized fixtures:

    • 10-second SDR source
    • 2-second HDR-tagged source (HLG transfer, bt2020 primaries/colorspace)

    Compose a 2-second window out of the 10-second SDR source alongside the HDR clip (triggering mixed-timeline HDR preflight). Assertions:

    • phaseBreakdown.hdrPreflightCount === 1 (the preflight actually ran)
    • The on-disk converted SDR file's duration is ~2s, not ~10s (this is the regression guard — pre-fix it would be the full source)
    • Phase 3 extraction still produces the expected frame count (~60 frames @ 30fps for the 2s window)
  • Full engine suite: 319 tests pass (was 318).

  • Typecheck clean in engine + producer.

  • Lint + format clean on changed files.

Stack

Built on #430 (phase-level instrumentation) so the improvement can be measured via the new videoExtractBreakdown.hdrPreflightMs metric.

Follow-ups (remaining PRs in the stack)

  1. Extraction cache keyed by (srcHash, windowStart, windowEnd, fps, hasAlpha)
  2. WebP as the single extraction format
  3. Hardware-accelerated SDR decode gated on !hasAlpha and segment-length floor

Copy link
Copy Markdown
Collaborator Author

jrusso1020 commented Apr 23, 2026

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