mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
feat(producer): renderStretch to re-time short compositions across longer scenes (#2676)
Linear: VA-1859 ## Problem For a `fit_to_scene` B-roll where the composition's intrinsic timeline (e.g. `data-duration=1.0s` → 30 frames) is shorter than the scene it fills (e.g. 4.8s narration), the producer renders only the intrinsic 30 frames and the downstream compositor frame-holds/PTS-stretches that fixed clip to the scene length. Spreading 30 unique frames over 4.8s starves motion to ~6 effective fps → a visibly choppy result. Root cause: the producer welds one `composition.duration` to both the frame count and the 1:1 seek mapping, with no notion of a target output length. ## Fix Add optional `renderStretch: number` (default `1.0` = no-op), `renderStretch = intrinsic / target`: - **Frame count** comes from the target: `outputDuration = intrinsic / renderStretch`, `totalFrames = outputDuration × fps` (`probeStage.ts`). `composition.duration` stays intrinsic (drives video/audio windows). - **Per-frame seek** is scaled: `time = (frameIndex / fps) × renderStretch`, so the N output frames map across `[0, intrinsic]` — a fresh frame per output frame. All seek sites go through a single shared `outputFrameToTimelineSeconds(frameIndex, fps, renderStretch)` helper (`core.types.ts`), consumed by every capture path so none can silently diverge: - parallel (`parallelCoordinator.ts`), `sdr_streaming` (`captureStreamingStage.ts` ×3), `sdr_disk` (`captureStage.ts`), HDR loops. - DrawElement + static self-verify (`frameCapture.ts`) — ground-truth seek uses the same mapping, so PSNR compares like-for-like (no spurious verification failure on stretched comps). - Distributed path: `renderStretch` threaded through `DistributedRenderConfig` → chunk workers, and **folded into the plan hash only when `!= 1`** so a pre-stretch cached plan is never reused. With `renderStretch = 1` (or omitted → `?? 1`): every seek is `×1.0` (IEEE-754 identity), frame counts unchanged, and the plan hash is byte-identical — a provable no-op. `player.ts` absolute-seek is untouched. ## Verify - typecheck (core + engine + producer): pass. lint/format/fallow/commitlint: pass. `planHash` + `renderRequest` unit suites: pass. - Adversarial self-review found + fixed three capture-path gaps (streaming, self-verify, distributed) before this revision. - **Not yet runtime-verified** on a real render — needs a fit_to_scene render at `renderStretch < 1` confirming N distinct frames over the target length (draft until then). Paired with experiment-framework#42766, which computes and forwards `renderStretch = hf intrinsic / scene duration`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
@@ -110,6 +110,7 @@ interface RenderInput {
|
||||
* compositions as an aspect-ratio mismatch.
|
||||
*/
|
||||
outputResolutionAspectAgnostic?: boolean;
|
||||
renderStretch?: number;
|
||||
}
|
||||
|
||||
interface PreparedRenderInput {
|
||||
@@ -167,6 +168,10 @@ export function parseRenderOptions(body: Record<string, unknown>): Omit<RenderIn
|
||||
|
||||
const { variables, outputResolution, outputResolutionAspectAgnostic } =
|
||||
parseRenderOverrides(body);
|
||||
const renderStretch =
|
||||
typeof body.renderStretch === "number" && body.renderStretch > 0
|
||||
? body.renderStretch
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
outputPath,
|
||||
@@ -182,6 +187,7 @@ export function parseRenderOptions(body: Record<string, unknown>): Omit<RenderIn
|
||||
outputResolution,
|
||||
outputResolutionAspectAgnostic,
|
||||
videoFrameFormat,
|
||||
renderStretch,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -239,6 +245,7 @@ function buildRenderJobConfig(input: RenderInput, outputPath: string, log: Produ
|
||||
outputResolution: input.outputResolution,
|
||||
outputResolutionAspectAgnostic: input.outputResolutionAspectAgnostic,
|
||||
videoFrameFormat: input.videoFrameFormat,
|
||||
renderStretch: input.renderStretch,
|
||||
},
|
||||
});
|
||||
return renderConfigFromRequest(request, { logger: log });
|
||||
|
||||
Reference in New Issue
Block a user