hyperframes 0.6.14 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +44 -31
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -54,7 +54,7 @@ var VERSION;
54
54
  var init_version = __esm({
55
55
  "src/version.ts"() {
56
56
  "use strict";
57
- VERSION = true ? "0.6.14" : "0.0.0-dev";
57
+ VERSION = true ? "0.6.15" : "0.0.0-dev";
58
58
  }
59
59
  });
60
60
 
@@ -34012,18 +34012,19 @@ function calculateOptimalWorkers(totalFrames, requested, config) {
34012
34012
  }
34013
34013
  return finalWorkers;
34014
34014
  }
34015
- function distributeFrames(totalFrames, workerCount, workDir) {
34015
+ function distributeFrames(totalFrames, workerCount, workDir, rangeStart = 0) {
34016
34016
  const tasks = [];
34017
34017
  const framesPerWorker = Math.ceil(totalFrames / workerCount);
34018
34018
  for (let i2 = 0; i2 < workerCount; i2++) {
34019
- const startFrame = i2 * framesPerWorker;
34020
- const endFrame = Math.min((i2 + 1) * framesPerWorker, totalFrames);
34021
- if (startFrame >= totalFrames) break;
34019
+ const startFrame = rangeStart + i2 * framesPerWorker;
34020
+ const endFrame = Math.min(rangeStart + (i2 + 1) * framesPerWorker, rangeStart + totalFrames);
34021
+ if (startFrame >= rangeStart + totalFrames) break;
34022
34022
  tasks.push({
34023
34023
  workerId: i2,
34024
34024
  startFrame,
34025
34025
  endFrame,
34026
- outputDir: join30(workDir, `worker-${i2}`)
34026
+ outputDir: join30(workDir, `worker-${i2}`),
34027
+ outputFrameOffset: rangeStart
34027
34028
  });
34028
34029
  }
34029
34030
  return tasks;
@@ -34043,16 +34044,18 @@ async function executeWorkerTask(task, serverUrl, captureOptions, createBeforeCa
34043
34044
  config
34044
34045
  );
34045
34046
  await initializeSession(session);
34047
+ const outputOffset = task.outputFrameOffset ?? 0;
34046
34048
  for (let i2 = task.startFrame; i2 < task.endFrame; i2++) {
34047
34049
  if (signal?.aborted) {
34048
34050
  throw new Error("Parallel worker cancelled");
34049
34051
  }
34050
34052
  const time = i2 * captureOptions.fps.den / captureOptions.fps.num;
34053
+ const fileFrameIdx = i2 - outputOffset;
34051
34054
  if (onFrameBuffer) {
34052
- const { buffer } = await captureFrameToBuffer(session, i2, time);
34055
+ const { buffer } = await captureFrameToBuffer(session, fileFrameIdx, time);
34053
34056
  await onFrameBuffer(i2, buffer);
34054
34057
  } else {
34055
- await captureFrame(session, i2, time);
34058
+ await captureFrame(session, fileFrameIdx, time);
34056
34059
  }
34057
34060
  framesCaptured++;
34058
34061
  if (onFrameCaptured) onFrameCaptured(task.workerId, i2);
@@ -39073,17 +39076,18 @@ async function runCaptureStage(input) {
39073
39076
  let { workerCount, probeSession } = input;
39074
39077
  let lastBrowserConsole = [];
39075
39078
  const captureCfg = cfg.forceScreenshot === forceScreenshot ? cfg : { ...cfg, forceScreenshot };
39076
- if (frameRange !== void 0 && workerCount > 1) {
39077
- throw new Error(
39078
- `[captureStage] frameRange capture requires workerCount === 1 (received workerCount=${workerCount}). Distributed chunk workers fan out at the activity layer; reduce workerCount to 1 when passing frameRange.`
39079
- );
39080
- }
39081
39079
  if (frameRange !== void 0) {
39082
39080
  if (!Number.isFinite(frameRange.startFrame) || !Number.isFinite(frameRange.endFrame) || frameRange.startFrame < 0 || frameRange.endFrame <= frameRange.startFrame) {
39083
39081
  throw new Error(
39084
39082
  `[captureStage] invalid frameRange: ${JSON.stringify(frameRange)}. Expected non-negative startFrame strictly less than endFrame.`
39085
39083
  );
39086
39084
  }
39085
+ const rangeFrames = frameRange.endFrame - frameRange.startFrame;
39086
+ if (rangeFrames !== totalFrames) {
39087
+ throw new Error(
39088
+ `[captureStage] frameRange size (${rangeFrames}) must equal totalFrames (${totalFrames}). Received frameRange=${JSON.stringify(frameRange)}.`
39089
+ );
39090
+ }
39087
39091
  }
39088
39092
  if (workerCount > 1) {
39089
39093
  const attempts = await executeDiskCaptureWithAdaptiveRetry({
@@ -39097,6 +39101,7 @@ async function runCaptureStage(input) {
39097
39101
  captureOptions: buildCaptureOptions(),
39098
39102
  createBeforeCaptureHook: createRenderVideoFrameInjector,
39099
39103
  abortSignal,
39104
+ frameRangeStart: frameRange?.startFrame,
39100
39105
  onProgress: (progress) => {
39101
39106
  job.framesRendered = progress.capturedFrames;
39102
39107
  const frameProgress = progress.capturedFrames / progress.totalFrames;
@@ -40986,16 +40991,17 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
40986
40991
  }
40987
40992
  return ranges;
40988
40993
  }
40989
- function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt) {
40994
+ function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0) {
40990
40995
  const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
40991
40996
  const batches = [];
40992
40997
  for (let i2 = 0; i2 < ranges.length; i2 += workersPerBatch) {
40993
40998
  const batchIndex = batches.length;
40994
40999
  const batch = ranges.slice(i2, i2 + workersPerBatch).map((range, workerId) => ({
40995
41000
  workerId,
40996
- startFrame: range.startFrame,
40997
- endFrame: range.endFrame,
40998
- outputDir: join49(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`)
41001
+ startFrame: rangeStart + range.startFrame,
41002
+ endFrame: rangeStart + range.endFrame,
41003
+ outputDir: join49(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`),
41004
+ outputFrameOffset: rangeStart
40999
41005
  }));
41000
41006
  batches.push(batch);
41001
41007
  }
@@ -41026,6 +41032,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
41026
41032
  let currentWorkers = options.initialWorkerCount;
41027
41033
  let missingRanges = null;
41028
41034
  let attempt = 0;
41035
+ const rangeStart = options.frameRangeStart ?? 0;
41029
41036
  while (true) {
41030
41037
  const frameCount = missingRanges ? countFrameRanges(missingRanges) : options.totalFrames;
41031
41038
  attempts.push({
@@ -41035,7 +41042,13 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
41035
41042
  reason: attempt === 0 ? "initial" : "retry"
41036
41043
  });
41037
41044
  const attemptWorkDir = join49(options.workDir, `capture-attempt-${attempt}`);
41038
- const batches = missingRanges ? buildMissingFrameRetryBatches(missingRanges, currentWorkers, attemptWorkDir, attempt) : [distributeFrames(options.totalFrames, currentWorkers, attemptWorkDir)];
41045
+ const batches = missingRanges ? buildMissingFrameRetryBatches(
41046
+ missingRanges,
41047
+ currentWorkers,
41048
+ attemptWorkDir,
41049
+ attempt,
41050
+ rangeStart
41051
+ ) : [distributeFrames(options.totalFrames, currentWorkers, attemptWorkDir, rangeStart)];
41039
41052
  try {
41040
41053
  for (const tasks of batches) {
41041
41054
  const capturedBeforeBatch = countCapturedFrames(
@@ -43467,6 +43480,14 @@ async function plan(projectDir, config, planDir) {
43467
43480
  height,
43468
43481
  format: config.format
43469
43482
  };
43483
+ try {
43484
+ rmSync11(workDir, { recursive: true, force: true });
43485
+ } catch (err) {
43486
+ log2.warn("[plan] failed to remove temp work dir", {
43487
+ workDir,
43488
+ error: err instanceof Error ? err.message : String(err)
43489
+ });
43490
+ }
43470
43491
  const freezeResult = await freezePlan({
43471
43492
  planDir,
43472
43493
  composition: compositionJson,
@@ -43480,14 +43501,6 @@ async function plan(projectDir, config, planDir) {
43480
43501
  hasAudio: audioResult.hasAudio
43481
43502
  });
43482
43503
  const planHash = freezeResult.planHash;
43483
- try {
43484
- rmSync11(workDir, { recursive: true, force: true });
43485
- } catch (err) {
43486
- log2.warn("[plan] failed to remove temp work dir", {
43487
- workDir,
43488
- error: err instanceof Error ? err.message : String(err)
43489
- });
43490
- }
43491
43504
  const sizeLimitBytes = config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
43492
43505
  const planDirBytes = measurePlanDirBytes(planDir);
43493
43506
  if (planDirBytes > sizeLimitBytes) {
@@ -43775,6 +43788,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
43775
43788
  session = await createCaptureSession(fileServer.url, framesDir, captureOptions, null, cfg);
43776
43789
  await assertSwiftShader(session.page, readWebGlVendorInfoFromCanvas);
43777
43790
  await initializeSession(session);
43791
+ const chunkWorkerCount = calculateOptimalWorkers(framesInChunk, void 0, cfg);
43778
43792
  await runCaptureStage({
43779
43793
  fileServer,
43780
43794
  workDir,
@@ -43784,11 +43798,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
43784
43798
  cfg,
43785
43799
  forceScreenshot: encoder.forceScreenshot,
43786
43800
  log: log2,
43787
- workerCount: 1,
43788
- // Pass the pre-warmed session through as `probeSession` so captureStage
43789
- // reuses it via `prepareCaptureSessionForReuse` instead of spinning up
43790
- // a fresh browser. The stage closes the session in its `finally`,
43791
- // so we MUST clear our own reference here to avoid a double-close.
43801
+ workerCount: chunkWorkerCount,
43802
+ // The parallel branch closes this session and spins up its own
43803
+ // worker sessions, wasting the ~3-5s of pre-warmed setup. Worth a
43804
+ // follow-up to skip pre-warmup when the resolved workerCount > 1.
43792
43805
  probeSession: session,
43793
43806
  needsAlpha: plan2.dimensions.format !== "mp4",
43794
43807
  captureAttempts: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperframes",
3
- "version": "0.6.14",
3
+ "version": "0.6.15",
4
4
  "description": "HyperFrames CLI — create, preview, and render HTML video compositions",
5
5
  "repository": {
6
6
  "type": "git",