feat(producer): optional targetChunkFrames to bound per-chunk frames (#1332)

* feat(producer): optional targetChunkFrames to bound per-chunk frames

* feat(cli): expose --target-chunk-frames on lambda + cloudrun render; document it
This commit is contained in:
James Russo
2026-06-10 18:09:59 -07:00
committed by GitHub
parent 69eac249d5
commit 9b18fadccd
10 changed files with 150 additions and 2 deletions
@@ -162,6 +162,81 @@ describe("resolveChunkPlan", () => {
}
}
});
// ── targetChunkFrames (optional per-chunk frame ceiling) ──
it("targetChunkFrames omitted is a no-op: identical to the 3-arg auto-sized result", () => {
// The default path must be byte-identical whether the 4th arg is absent or
// explicitly undefined.
for (const totalFrames of [50, 660, 1466, 54000]) {
for (const maxParallel of [8, 16, 64]) {
const base = resolveChunkPlan(totalFrames, undefined, maxParallel);
const withUndef = resolveChunkPlan(totalFrames, undefined, maxParallel, undefined);
expect(withUndef).toEqual(base);
}
}
});
it("targetChunkFrames collapses a short video to fewer chunks than the parallelism cap", () => {
// 1466 frames, target 300, cap 16: ceil(1466/300)=5 chunks (not 16), each
// ~293 frames. Fewer chunks → less per-chunk fixed overhead.
const result = resolveChunkPlan(1466, undefined, 16, 300);
expect(result.chunkCount).toBe(5);
expect(result.effectiveChunkSize).toBeLessThanOrEqual(300);
});
it("targetChunkFrames bounds a long video's per-chunk frames, adding chunks up to the cap", () => {
// 54000 frames (30 min @30fps), target 1600, cap 64: ceil(54000/1600)=34
// chunks, each <= 1600 frames so per-chunk render time stays under budget.
const result = resolveChunkPlan(54000, undefined, 64, 1600);
expect(result.chunkCount).toBe(34);
expect(result.effectiveChunkSize).toBeLessThanOrEqual(1600);
});
it("targetChunkFrames is clamped by maxParallelChunks: an extreme length stays at the cap (still over budget)", () => {
// 216000 frames (2 h), target 1600, cap 64: needs 135 chunks but clamps to
// 64; per-chunk frames then exceed the target — the genuine tier ceiling.
const result = resolveChunkPlan(216000, undefined, 64, 1600);
expect(result.chunkCount).toBe(64);
expect(result.effectiveChunkSize).toBeGreaterThan(1600);
});
it("explicit chunkSize wins over targetChunkFrames (targetChunkFrames is a no-op)", () => {
const withTarget = resolveChunkPlan(54000, 240, 64, 1600);
const chunkSizeOnly = resolveChunkPlan(54000, 240, 64);
expect(withTarget).toEqual(chunkSizeOnly);
});
it("rejects a non-positive or non-integer targetChunkFrames", () => {
expect(() => resolveChunkPlan(1466, undefined, 16, 0)).toThrow(/positive integer/);
expect(() => resolveChunkPlan(1466, undefined, 16, -100)).toThrow(/positive integer/);
expect(() => resolveChunkPlan(1466, undefined, 16, 300.5)).toThrow(/positive integer/);
});
it("never emits an empty or inverted slice across a grid of targetChunkFrames", () => {
for (const totalFrames of [37, 660, 1466, 12793, 54000]) {
for (const maxParallel of [8, 16, 64]) {
for (const target of [100, 300, 1600]) {
const { chunkCount, effectiveChunkSize } = resolveChunkPlan(
totalFrames,
undefined,
maxParallel,
target,
);
expect(chunkCount).toBeGreaterThanOrEqual(1);
expect(chunkCount).toBeLessThanOrEqual(maxParallel);
const slices = buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize);
let cursor = 0;
for (const s of slices) {
expect(s.startFrame).toBe(cursor);
expect(s.endFrame).toBeGreaterThan(s.startFrame);
cursor = s.endFrame;
}
expect(cursor).toBe(totalFrames);
}
}
}
});
});
describe("buildChunkSlices", () => {