Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions packages/producer/src/services/render/stages/probeStage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,30 @@ describe("runProbeStage — forceScreenshot threading", () => {
});
});

describe("runProbeStage — decimal duration frame count", () => {
it("does not add a frame for a six-decimal duration rounded from an exact frame boundary", async () => {
const { runProbeStage } = await import("./probeStage.js");
const input = makeProbeInput({});
input.composition.duration = 32.866667;
input.compiled.staticDuration = 32.866667;

const result = await runProbeStage(input);

expect(result.totalFrames).toBe(986);
});

it("still ceilings a duration that genuinely extends into the next frame", async () => {
const { runProbeStage } = await import("./probeStage.js");
const input = makeProbeInput({});
input.composition.duration = 32.867;
input.compiled.staticDuration = 32.867;

const result = await runProbeStage(input);

expect(result.totalFrames).toBe(987);
});
});

describe("runProbeStage — transient browser error retry (#1687)", () => {
it("retries once on a transient 'Navigating frame was detached' error and succeeds", async () => {
resetRetryMocks();
Expand Down
12 changes: 11 additions & 1 deletion packages/producer/src/services/render/stages/probeStage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ export interface ProbeStageInput {
deviceScaleFactor: number;
}

const FRAME_BOUNDARY_EPSILON = 1e-3;

function durationToFrameCount(duration: number, fps: number): number {
const rawFrameCount = duration * fps;
const nearestFrame = Math.round(rawFrameCount);
return Math.abs(rawFrameCount - nearestFrame) <= FRAME_BOUNDARY_EPSILON
? nearestFrame
: Math.ceil(rawFrameCount);
}

export interface ProbeStageResult {
/** May be reassigned from `recompileWithResolutions`. */
compiled: CompiledComposition;
Expand Down Expand Up @@ -536,7 +546,7 @@ export async function runProbeStage(input: ProbeStageInput): Promise<ProbeStageR
const browserProbeMs = Date.now() - probeStart;

const duration = composition.duration;
const totalFrames = Math.ceil(duration * fpsToNumber(job.config.fps));
const totalFrames = durationToFrameCount(duration, fpsToNumber(job.config.fps));

if (duration <= 0) {
// Gather diagnostics to help users understand why the render would produce a black video.
Expand Down
Loading