@hyperframes/producer 0.8.207 → 0.8.209

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.
package/dist/index.js CHANGED
@@ -775,7 +775,7 @@ function createFileServer(options) {
775
775
  if (!streamOwnsFd) closeSync(fd);
776
776
  }
777
777
  });
778
- return new Promise((resolve13) => {
778
+ return new Promise((resolve13, reject) => {
779
779
  const connections = /* @__PURE__ */ new Set();
780
780
  const server = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, (info) => {
781
781
  resolve13({
@@ -795,6 +795,7 @@ function createFileServer(options) {
795
795
  connections.add(socket);
796
796
  socket.on("close", () => connections.delete(socket));
797
797
  });
798
+ server.on("error", reject);
798
799
  });
799
800
  }
800
801
 
@@ -5668,7 +5669,7 @@ import { gsapCdnDist, normalizeGsapBaseUrl } from "@hyperframes/core/gsap-cdn";
5668
5669
  import {
5669
5670
  assignBundledRuntimeCompositionIds,
5670
5671
  assignMediaRenderIds,
5671
- buildVariablesByCompScript,
5672
+ emitVariablesByCompScript,
5672
5673
  inlineSubCompositions as inlineSubCompositionsShared,
5673
5674
  ensureExternalLinkTag,
5674
5675
  ensureExternalScriptTag,
@@ -7240,9 +7241,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
7240
7241
  }
7241
7242
  }
7242
7243
  }
7243
- const variablesByCompScript = buildVariablesByCompScript(result.variablesByComp);
7244
+ emitVariablesByCompScript(document2, result.variablesByComp);
7244
7245
  if (body) {
7245
- let pending = variablesByCompScript ? [variablesByCompScript] : [];
7246
+ let pending = [];
7246
7247
  const flushInline = () => {
7247
7248
  if (!pending.length) return;
7248
7249
  const joined = pending.join("\n;\n");
@@ -9256,10 +9257,11 @@ var VideoExtractionStageError = class extends Error {
9256
9257
  kind: failure.kind,
9257
9258
  affectedElementCount: failure.count
9258
9259
  }))
9259
- )) {
9260
+ ), ffmpegReason) {
9260
9261
  const total = failures.reduce((sum2, failure) => sum2 + failure.count, 0);
9261
9262
  const breakdown = failures.map((failure) => `${failure.kind}=${failure.count}`).join(",");
9262
- super(`Video extraction failed for ${total} source(s) [${code}; ${breakdown}]`);
9263
+ const reason = ffmpegReason ? `: ffmpeg said: ${ffmpegReason}` : "";
9264
+ super(`Video extraction failed for ${total} source(s) [${code}; ${breakdown}]${reason}`);
9263
9265
  this.code = code;
9264
9266
  this.retryable = retryable;
9265
9267
  this.failures = failures;
@@ -9306,7 +9308,8 @@ function buildVideoExtractionStageError(result) {
9306
9308
  retryable ? "VIDEO_EXTRACTION_FAILED" : "VIDEO_SOURCE_UNRENDERABLE",
9307
9309
  retryable,
9308
9310
  failures,
9309
- extractionFailureMetadataFromResult(result)
9311
+ extractionFailureMetadataFromResult(result),
9312
+ result.errors.find((failure) => failure.ffmpegReason)?.ffmpegReason
9310
9313
  );
9311
9314
  }
9312
9315
  function buildHdrProbeStageError(failures) {
@@ -10303,6 +10306,7 @@ async function runCaptureStage(input) {
10303
10306
  dedupPerfs,
10304
10307
  pageErrorLines: workerPageErrors,
10305
10308
  openAttempt: input.openAttempt,
10309
+ verifyFirstSession: input.verifyFirstSession,
10306
10310
  onProgress: (progress) => {
10307
10311
  if (progress.latestWorkerPhase) {
10308
10312
  if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
@@ -12362,7 +12366,8 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
12362
12366
  });
12363
12367
  } : void 0,
12364
12368
  void 0,
12365
- options.cfg
12369
+ options.cfg,
12370
+ { verifyFirstSession: options.verifyFirstSession }
12366
12371
  );
12367
12372
  pushWorkerDedupPerfs(workerResults, options.dedupPerfs);
12368
12373
  if (options.pageErrorLines) pushWorkerPageErrors(workerResults, options.pageErrorLines);
@@ -20245,16 +20250,18 @@ function observedChunkCaptureMode(perfs) {
20245
20250
  }
20246
20251
  return mode;
20247
20252
  }
20253
+ var PROBE_SELECTED_SCREENSHOT = "[renderChunk] probe selected screenshot capture";
20248
20254
  function shouldRetryChunkCaptureWithScreenshot(error) {
20249
20255
  const failure = classifyCaptureFailure4(error);
20250
20256
  if (failure.kind === "cancelled" || failure.kind === "memory_exhaustion") return false;
20251
- return /HeadlessExperimental\.beginFrame/i.test(failure.message) || /beginFrame probe timeout/i.test(failure.message) || /Another frame is pending|Frame still pending/i.test(failure.message);
20257
+ return /HeadlessExperimental\.beginFrame/i.test(failure.message) || /beginFrame probe timeout/i.test(failure.message) || failure.message.includes(PROBE_SELECTED_SCREENSHOT) || /Another frame is pending|Frame still pending/i.test(failure.message);
20252
20258
  }
20253
20259
  async function runCaptureWithScreenshotFallback(input) {
20254
20260
  try {
20255
20261
  return await input.run(input.forceScreenshot);
20256
20262
  } catch (error) {
20257
- if (input.forceScreenshot || !shouldRetryChunkCaptureWithScreenshot(error)) throw error;
20263
+ if (input.forceScreenshot || await input.canRetry?.() === false) throw error;
20264
+ if (!shouldRetryChunkCaptureWithScreenshot(error)) throw error;
20258
20265
  input.onFallback?.(error);
20259
20266
  await input.resetForScreenshotRetry();
20260
20267
  return await input.run(true);
@@ -20463,7 +20470,24 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20463
20470
  let extraction;
20464
20471
  const overlap = { current: null };
20465
20472
  try {
20466
- if (chunkWorkerCount === 1 || !forceScreenshotForChunk) {
20473
+ const verifyInWorker = chunkWorkerCount > 1 && !forceScreenshotForChunk;
20474
+ let boxesMeasured;
20475
+ let boxesFailed;
20476
+ const boxesReady = new Promise((resolve13, reject) => {
20477
+ boxesMeasured = resolve13;
20478
+ boxesFailed = reject;
20479
+ });
20480
+ boxesReady.catch(() => {
20481
+ });
20482
+ const measureBoxes = async (page) => {
20483
+ shownBoxes = await measureVideoShownBoxes(
20484
+ page,
20485
+ [...shownFrames.keys()],
20486
+ chunkFrameNumbers.map((frame) => frame * chunkFps.den / chunkFps.num)
20487
+ );
20488
+ boxesMeasured();
20489
+ };
20490
+ if (chunkWorkerCount === 1) {
20467
20491
  const bootStarted = Date.now();
20468
20492
  session = await createVerifiedDistributedCaptureSession(
20469
20493
  fileServer.url,
@@ -20472,11 +20496,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20472
20496
  cfg
20473
20497
  );
20474
20498
  sessionBootMs = Date.now() - bootStarted;
20475
- shownBoxes = await measureVideoShownBoxes(
20476
- session.page,
20477
- [...shownFrames.keys()],
20478
- chunkFrameNumbers.map((frame) => frame * chunkFps.den / chunkFps.num)
20479
- );
20499
+ await measureBoxes(session.page);
20480
20500
  const browserSelectedScreenshot = session.launchCaptureMode === "screenshot";
20481
20501
  const beginFrameStalled = !browserSelectedScreenshot && await beginFrameSessionNeedsScreenshotFallback(session);
20482
20502
  if (browserSelectedScreenshot) {
@@ -20485,10 +20505,6 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20485
20505
  "[renderChunk] Browser capability probe selected screenshot capture for the entire chunk",
20486
20506
  { chunkIndex }
20487
20507
  );
20488
- if (chunkWorkerCount > 1) {
20489
- await closeCaptureSession11(session);
20490
- session = null;
20491
- }
20492
20508
  } else if (beginFrameStalled) {
20493
20509
  forceScreenshotForChunk = true;
20494
20510
  log.warn(
@@ -20498,41 +20514,66 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20498
20514
  await closeCaptureSession11(session).catch(() => {
20499
20515
  });
20500
20516
  session = null;
20501
- if (chunkWorkerCount === 1) {
20502
- const screenshotBootStarted = Date.now();
20503
- const screenshotCfg = {
20504
- ...cfg,
20505
- forceScreenshot: true,
20506
- forceScreenshotExplicitlyOptedOut: false
20507
- };
20508
- session = await createVerifiedDistributedCaptureSession(
20509
- fileServer.url,
20510
- framesDir,
20511
- captureOptions,
20512
- screenshotCfg
20517
+ const screenshotBootStarted = Date.now();
20518
+ const screenshotCfg = {
20519
+ ...cfg,
20520
+ forceScreenshot: true,
20521
+ forceScreenshotExplicitlyOptedOut: false
20522
+ };
20523
+ session = await createVerifiedDistributedCaptureSession(
20524
+ fileServer.url,
20525
+ framesDir,
20526
+ captureOptions,
20527
+ screenshotCfg
20528
+ );
20529
+ sessionBootMs += Date.now() - screenshotBootStarted;
20530
+ }
20531
+ } else if (!verifyInWorker) {
20532
+ boxesMeasured();
20533
+ }
20534
+ let verification;
20535
+ const verifyFirstWorker = (first) => verification ??= (async () => {
20536
+ sessionBootMs = Date.now() - verifyStarted;
20537
+ try {
20538
+ await measureBoxes(first.page);
20539
+ const selectedScreenshot = first.launchCaptureMode === "screenshot";
20540
+ if (selectedScreenshot || await beginFrameSessionNeedsScreenshotFallback(first)) {
20541
+ forceScreenshotForChunk = true;
20542
+ log.warn(
20543
+ selectedScreenshot ? "[renderChunk] Browser capability probe selected screenshot capture for the entire chunk" : "[renderChunk] BeginFrame liveness probe failed; using screenshot capture for the entire chunk",
20544
+ { chunkIndex }
20513
20545
  );
20514
- sessionBootMs += Date.now() - screenshotBootStarted;
20546
+ throw new Error(PROBE_SELECTED_SCREENSHOT);
20515
20547
  }
20516
- } else if (chunkWorkerCount > 1) {
20517
- await closeCaptureSession11(session);
20518
- session = null;
20548
+ } catch (error) {
20549
+ if (!forceScreenshotForChunk) {
20550
+ verifyFailure = error;
20551
+ boxesFailed(error);
20552
+ }
20553
+ throw error;
20519
20554
  }
20520
- }
20521
- const extractStarted = Date.now();
20522
- const deferredVideoFrames = planVideos ? extractDeferredVideoFramesForChunk({
20523
- planDir,
20524
- planVideos,
20525
- frames: chunkFrameNumbers,
20526
- fps: chunkFps,
20527
- outputRoot: videoFramesDir,
20528
- cfg,
20529
- remoteSources,
20530
- shownBoxes
20531
- }).then((frames) => {
20532
- videoExtractMs = Date.now() - extractStarted;
20533
- return frames;
20534
- }) : Promise.resolve([]);
20555
+ })();
20556
+ let verifyStarted = 0;
20557
+ let verifyFailure;
20558
+ const deferredVideoFrames = planVideos ? boxesReady.then(() => {
20559
+ const extractStarted = Date.now();
20560
+ return extractDeferredVideoFramesForChunk({
20561
+ planDir,
20562
+ planVideos,
20563
+ frames: chunkFrameNumbers,
20564
+ fps: chunkFps,
20565
+ outputRoot: videoFramesDir,
20566
+ cfg,
20567
+ remoteSources,
20568
+ shownBoxes
20569
+ }).then((frames) => {
20570
+ videoExtractMs = Date.now() - extractStarted;
20571
+ return frames;
20572
+ });
20573
+ }) : boxesReady.then(() => []);
20535
20574
  extraction = deferredVideoFrames;
20575
+ deferredVideoFrames.catch(() => {
20576
+ });
20536
20577
  const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? deferredVideoFrames.then(
20537
20578
  (deferred) => createFrameLookupTable5(
20538
20579
  planVideos.videos,
@@ -20611,6 +20652,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20611
20652
  const captureStarted = Date.now();
20612
20653
  captureMode = await runCaptureWithScreenshotFallback({
20613
20654
  forceScreenshot: forceScreenshotForChunk,
20655
+ canRetry: () => verifyFailure === void 0,
20614
20656
  // fallow-ignore-next-line complexity
20615
20657
  run: async (forceScreenshot) => {
20616
20658
  const captureCfg = cfg.forceScreenshot === forceScreenshot ? cfg : {
@@ -20640,6 +20682,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20640
20682
  }
20641
20683
  const captureSession = session;
20642
20684
  session = null;
20685
+ verifyStarted = Date.now();
20643
20686
  overlap.current = canEncodeWhileCapturing(encodeInput, chunkWorkerCount, cpus3().length) ? encodeWhileCapturing(encodeInput, framesInChunk) : null;
20644
20687
  await observeRenderStage(
20645
20688
  new RenderObservabilityRecorder({ pipelineStartMs: start, log }),
@@ -20667,7 +20710,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20667
20710
  frameRange: range,
20668
20711
  onFrameLanded: (frame) => overlap.current?.onFrameLanded(frame),
20669
20712
  interleaveFrames: interleavesCapture(overlap.current !== null, options),
20670
- openAttempt
20713
+ openAttempt,
20714
+ verifyFirstSession: forceScreenshot || !verifyInWorker ? void 0 : verifyFirstWorker
20671
20715
  }),
20672
20716
  { stall: frameStallRule(job) }
20673
20717
  ).catch(async (captureError) => {
@@ -20679,6 +20723,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20679
20723
  },
20680
20724
  resetForScreenshotRetry: () => {
20681
20725
  rmSync17(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20726
+ boxesMeasured();
20682
20727
  mkdirSync19(framesDir, { recursive: true });
20683
20728
  capturePerfs.length = 0;
20684
20729
  captureAttempts.length = 0;
@@ -20694,6 +20739,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20694
20739
  );
20695
20740
  }
20696
20741
  }).catch(async (captureError) => {
20742
+ if (verifyFailure !== void 0) throw verifyFailure;
20743
+ boxesFailed(captureError);
20697
20744
  await deferredVideoFrames;
20698
20745
  throw captureError;
20699
20746
  });
@@ -796,7 +796,7 @@ function createFileServer(options) {
796
796
  if (!streamOwnsFd) closeSync(fd);
797
797
  }
798
798
  });
799
- return new Promise((resolve11) => {
799
+ return new Promise((resolve11, reject) => {
800
800
  const connections = /* @__PURE__ */ new Set();
801
801
  const server = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, (info) => {
802
802
  resolve11({
@@ -816,6 +816,7 @@ function createFileServer(options) {
816
816
  connections.add(socket);
817
817
  socket.on("close", () => connections.delete(socket));
818
818
  });
819
+ server.on("error", reject);
819
820
  });
820
821
  }
821
822
 
@@ -5685,7 +5686,7 @@ import { gsapCdnDist, normalizeGsapBaseUrl } from "@hyperframes/core/gsap-cdn";
5685
5686
  import {
5686
5687
  assignBundledRuntimeCompositionIds,
5687
5688
  assignMediaRenderIds,
5688
- buildVariablesByCompScript,
5689
+ emitVariablesByCompScript,
5689
5690
  inlineSubCompositions as inlineSubCompositionsShared,
5690
5691
  ensureExternalLinkTag,
5691
5692
  ensureExternalScriptTag,
@@ -7257,9 +7258,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
7257
7258
  }
7258
7259
  }
7259
7260
  }
7260
- const variablesByCompScript = buildVariablesByCompScript(result.variablesByComp);
7261
+ emitVariablesByCompScript(document2, result.variablesByComp);
7261
7262
  if (body) {
7262
- let pending = variablesByCompScript ? [variablesByCompScript] : [];
7263
+ let pending = [];
7263
7264
  const flushInline = () => {
7264
7265
  if (!pending.length) return;
7265
7266
  const joined = pending.join("\n;\n");
@@ -9242,10 +9243,11 @@ var VideoExtractionStageError = class extends Error {
9242
9243
  kind: failure.kind,
9243
9244
  affectedElementCount: failure.count
9244
9245
  }))
9245
- )) {
9246
+ ), ffmpegReason) {
9246
9247
  const total = failures.reduce((sum, failure) => sum + failure.count, 0);
9247
9248
  const breakdown = failures.map((failure) => `${failure.kind}=${failure.count}`).join(",");
9248
- super(`Video extraction failed for ${total} source(s) [${code}; ${breakdown}]`);
9249
+ const reason = ffmpegReason ? `: ffmpeg said: ${ffmpegReason}` : "";
9250
+ super(`Video extraction failed for ${total} source(s) [${code}; ${breakdown}]${reason}`);
9249
9251
  this.code = code;
9250
9252
  this.retryable = retryable;
9251
9253
  this.failures = failures;
@@ -9287,7 +9289,8 @@ function buildVideoExtractionStageError(result) {
9287
9289
  retryable ? "VIDEO_EXTRACTION_FAILED" : "VIDEO_SOURCE_UNRENDERABLE",
9288
9290
  retryable,
9289
9291
  failures,
9290
- extractionFailureMetadataFromResult(result)
9292
+ extractionFailureMetadataFromResult(result),
9293
+ result.errors.find((failure) => failure.ffmpegReason)?.ffmpegReason
9291
9294
  );
9292
9295
  }
9293
9296
  function buildHdrProbeStageError(failures) {
@@ -10284,6 +10287,7 @@ async function runCaptureStage(input) {
10284
10287
  dedupPerfs,
10285
10288
  pageErrorLines: workerPageErrors,
10286
10289
  openAttempt: input.openAttempt,
10290
+ verifyFirstSession: input.verifyFirstSession,
10287
10291
  onProgress: (progress) => {
10288
10292
  if (progress.latestWorkerPhase) {
10289
10293
  if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
@@ -12343,7 +12347,8 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
12343
12347
  });
12344
12348
  } : void 0,
12345
12349
  void 0,
12346
- options.cfg
12350
+ options.cfg,
12351
+ { verifyFirstSession: options.verifyFirstSession }
12347
12352
  );
12348
12353
  pushWorkerDedupPerfs(workerResults, options.dedupPerfs);
12349
12354
  if (options.pageErrorLines) pushWorkerPageErrors(workerResults, options.pageErrorLines);
@@ -156,6 +156,8 @@ export declare function shouldRetryChunkCaptureWithScreenshot(error: unknown): b
156
156
  */
157
157
  export declare function runCaptureWithScreenshotFallback<T>(input: {
158
158
  forceScreenshot: boolean;
159
+ /** A failure that is not capture's (the probe's own) is never retried. */
160
+ canRetry?: () => boolean | Promise<boolean>;
159
161
  run: (forceScreenshot: boolean) => Promise<T>;
160
162
  resetForScreenshotRetry: () => Promise<void> | void;
161
163
  onFallback?: (error: unknown) => void;
@@ -88,6 +88,7 @@ export interface CaptureStageInput {
88
88
  interleaveFrames?: boolean;
89
89
  /** Each capture attempt opens one, so a stalled attempt is retried by the ladders below. */
90
90
  openAttempt?: OpenStallAttempt;
91
+ verifyFirstSession?: (session: CaptureSession) => Promise<void>;
91
92
  }
92
93
  export interface CaptureStageResult {
93
94
  /** Final worker count after any adaptive retry. */
@@ -117,7 +117,7 @@ export declare class VideoExtractionStageError extends Error {
117
117
  * validating and applying policy to it.
118
118
  */
119
119
  readonly publicMetadata: Readonly<Record<string, unknown>>;
120
- constructor(code: VideoExtractionStageErrorCode, retryable: boolean, failures: readonly VideoExtractionStageFailureSummary[], extractionFailure?: ExtractionFailureMetadataV1);
120
+ constructor(code: VideoExtractionStageErrorCode, retryable: boolean, failures: readonly VideoExtractionStageFailureSummary[], extractionFailure?: ExtractionFailureMetadataV1, ffmpegReason?: string);
121
121
  }
122
122
  export declare function assertVideoExtractionSucceeded(result: ExtractionResult): void;
123
123
  export declare function buildHdrProbeStageError(failures: readonly Pick<ReturnType<typeof classifyVideoExtractionError>, "kind" | "retryable">[]): VideoExtractionStageError;
@@ -610,6 +610,7 @@ export declare function executeDiskCaptureWithAdaptiveRetry(options: {
610
610
  /** Mutated in place: the workers' page-error console lines, for the render's error count. */
611
611
  pageErrorLines?: string[];
612
612
  openAttempt?: OpenStallAttempt;
613
+ verifyFirstSession?: (session: CaptureSession) => Promise<void>;
613
614
  }): Promise<CaptureAttemptSummary[]>;
614
615
  export type RenderConfigInput = Omit<RenderConfig, "fps"> & {
615
616
  fps: FpsInput;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/producer",
3
- "version": "0.8.207",
3
+ "version": "0.8.209",
4
4
  "description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,11 +35,11 @@
35
35
  "postcss": "^8.4.0",
36
36
  "puppeteer": "^25.2.1",
37
37
  "puppeteer-core": "^25.2.1",
38
- "@hyperframes/engine": "0.8.207",
39
- "@hyperframes/core": "0.8.207",
40
- "@hyperframes/lint": "0.8.207",
41
- "@hyperframes/parsers": "0.8.207",
42
- "@hyperframes/studio-server": "0.8.207"
38
+ "@hyperframes/core": "0.8.209",
39
+ "@hyperframes/engine": "0.8.209",
40
+ "@hyperframes/lint": "0.8.209",
41
+ "@hyperframes/parsers": "0.8.209",
42
+ "@hyperframes/studio-server": "0.8.209"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^25.0.10",
@@ -48,7 +48,7 @@
48
48
  "tsx": "^4.21.0",
49
49
  "typescript": "^5.7.2",
50
50
  "vitest": "^4.1.11",
51
- "@hyperframes/aws-lambda": "0.8.207"
51
+ "@hyperframes/aws-lambda": "0.8.209"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=22"