mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
fix: bound invalid render durations
This commit is contained in:
@@ -11,10 +11,13 @@ import {
|
||||
BROWSER_GPU_NOT_SOFTWARE,
|
||||
DISTRIBUTED_DURATION_OUT_OF_RANGE,
|
||||
MAX_DISTRIBUTED_DURATION_SECONDS,
|
||||
MAX_RENDER_DURATION_SECONDS,
|
||||
PlanValidationError,
|
||||
RENDER_DURATION_OUT_OF_RANGE,
|
||||
SYSTEM_FONT_USED,
|
||||
parseFontFamilyValue,
|
||||
validateDistributedDuration,
|
||||
validateRenderDuration,
|
||||
validateNoGpuEncode,
|
||||
validateNoSystemFonts,
|
||||
} from "./planValidation.js";
|
||||
@@ -95,6 +98,18 @@ describe("validateNoGpuEncode", () => {
|
||||
});
|
||||
|
||||
describe("validateDistributedDuration", () => {
|
||||
it("keeps the generic validator and legacy distributed API behavior aligned", () => {
|
||||
expect(MAX_RENDER_DURATION_SECONDS).toBe(MAX_DISTRIBUTED_DURATION_SECONDS);
|
||||
expect(RENDER_DURATION_OUT_OF_RANGE).toBe(DISTRIBUTED_DURATION_OUT_OF_RANGE);
|
||||
expect(() =>
|
||||
validateRenderDuration({
|
||||
duration: MAX_RENDER_DURATION_SECONDS,
|
||||
totalFrames: MAX_RENDER_DURATION_SECONDS * 30,
|
||||
fps: 30,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("accepts a finite duration within the distributed ceiling", () => {
|
||||
expect(() =>
|
||||
validateDistributedDuration({
|
||||
|
||||
@@ -67,17 +67,15 @@ export interface ValidateNoGpuEncodeInput {
|
||||
*/
|
||||
export const SYSTEM_FONT_USED = "SYSTEM_FONT_USED";
|
||||
|
||||
/**
|
||||
* Typed code for {@link validateDistributedDuration}. A duration this large
|
||||
* almost always means an unbounded runtime timeline escaped into plan(),
|
||||
* e.g. GSAP `repeat: -1` reporting its internal sentinel duration. Letting
|
||||
* that reach chunk planning creates billions of frames and turns an authoring
|
||||
* error into worker churn.
|
||||
*/
|
||||
/** Typed code for invalid duration metadata resolved by the shared browser probe. */
|
||||
export const DISTRIBUTED_DURATION_OUT_OF_RANGE = "DISTRIBUTED_DURATION_OUT_OF_RANGE";
|
||||
/** Generic alias; the legacy value remains stable for workflow retry policies. */
|
||||
export const RENDER_DURATION_OUT_OF_RANGE = DISTRIBUTED_DURATION_OUT_OF_RANGE;
|
||||
|
||||
/** Distributed renders are operationally bounded to one day of output. */
|
||||
/** All render paths are operationally bounded to one day of output. */
|
||||
export const MAX_DISTRIBUTED_DURATION_SECONDS = 24 * 60 * 60;
|
||||
/** Generic alias retained alongside the distributed public API. */
|
||||
export const MAX_RENDER_DURATION_SECONDS = MAX_DISTRIBUTED_DURATION_SECONDS;
|
||||
|
||||
/**
|
||||
* Reject any config that would let GPU encode or hardware-GL slip into a
|
||||
@@ -143,13 +141,13 @@ export function validateNoSystemFonts(compiledHtml: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function validateDistributedDuration(input: {
|
||||
export function validateRenderDuration(input: {
|
||||
duration: number;
|
||||
totalFrames: number;
|
||||
fps: number;
|
||||
}): void {
|
||||
const { duration, totalFrames, fps } = input;
|
||||
const maxFrames = Math.ceil(MAX_DISTRIBUTED_DURATION_SECONDS * fps);
|
||||
const maxFrames = Math.ceil(MAX_RENDER_DURATION_SECONDS * fps);
|
||||
if (
|
||||
Number.isFinite(duration) &&
|
||||
duration > 0 &&
|
||||
@@ -163,12 +161,21 @@ export function validateDistributedDuration(input: {
|
||||
}
|
||||
|
||||
throw new PlanValidationError(
|
||||
DISTRIBUTED_DURATION_OUT_OF_RANGE,
|
||||
`[planValidation] Distributed render duration is out of range: ` +
|
||||
RENDER_DURATION_OUT_OF_RANGE,
|
||||
`[planValidation] Render duration is out of range: ` +
|
||||
`duration=${String(duration)}s totalFrames=${String(totalFrames)} fps=${String(fps)} ` +
|
||||
`(maxDuration=${String(MAX_DISTRIBUTED_DURATION_SECONDS)}s, maxFrames=${String(maxFrames)}). ` +
|
||||
`(maxDuration=${String(MAX_RENDER_DURATION_SECONDS)}s, maxFrames=${String(maxFrames)}). ` +
|
||||
`This usually means an unbounded timeline escaped into render planning, such as ` +
|
||||
`GSAP repeat:-1 / yoyo loops without an explicit finite root duration. Add a finite ` +
|
||||
`data-duration or replace infinite repeats with a finite repeat count before rendering.`,
|
||||
);
|
||||
}
|
||||
|
||||
/** Backward-compatible distributed entry point for existing adopters. */
|
||||
export function validateDistributedDuration(input: {
|
||||
duration: number;
|
||||
totalFrames: number;
|
||||
fps: number;
|
||||
}): void {
|
||||
validateRenderDuration(input);
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ import {
|
||||
} from "./render/videoFrameCoverage.js";
|
||||
import { runCompileStage } from "./render/stages/compileStage.js";
|
||||
import { runProbeStage } from "./render/stages/probeStage.js";
|
||||
import { validateRenderDuration } from "./render/planValidation.js";
|
||||
import {
|
||||
runExtractVideosStage,
|
||||
shouldCopyExtractedFrames,
|
||||
@@ -1971,6 +1972,11 @@ async function executeRenderPipeline(input: {
|
||||
job.totalFrames = probeResult.totalFrames;
|
||||
const totalFrames = probeResult.totalFrames;
|
||||
captureTotalFrames = totalFrames;
|
||||
validateRenderDuration({
|
||||
duration: probeResult.duration,
|
||||
totalFrames,
|
||||
fps: fpsToNumber(job.config.fps),
|
||||
});
|
||||
|
||||
perfStages.browserProbeMs = probeResult.browserProbeMs;
|
||||
perfStages.compileMs = Date.now() - stage1Start;
|
||||
|
||||
Reference in New Issue
Block a user