@hyperframes/producer 0.8.145 → 0.8.147

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.
@@ -292,7 +292,7 @@ function buildVirtualTimeShim(options) {
292
292
  const seedRandomFromFrame = options.seedRandomFromFrame === true;
293
293
  const seededRandomBlock = seedRandomFromFrame ? String.raw`
294
294
  // Seeded Math.random / crypto.getRandomValues, keyed by virtual time.
295
- // Mulberry32 — single uint32 state, deterministic, fast.
295
+ // Mulberry32: single uint32 state, deterministic, fast.
296
296
  var rngState = 0;
297
297
  function mulberry32() {
298
298
  rngState |= 0; rngState = (rngState + 0x6D2B79F5) | 0;
@@ -303,7 +303,7 @@ function buildVirtualTimeShim(options) {
303
303
  }
304
304
  function reseedRngFromTime(ms) {
305
305
  var ms32 = Math.max(0, Math.floor(Number(ms) || 0)) | 0;
306
- // Knuth's multiplicative hash + golden-ratio offset — gives a well-
306
+ // Knuth's multiplicative hash + golden-ratio offset gives a well-
307
307
  // distributed seed even for frame 0 (otherwise rngState=0 degenerates
308
308
  // the PRNG's first few outputs).
309
309
  rngState = (Math.imul(ms32, -1640531527) + 0x9E3779B9) | 0;
@@ -545,7 +545,7 @@ function buildRenderModeScript(fps) {
545
545
  };
546
546
  window.__playerReady = true;
547
547
  // Media-fallback player has no timeline to bind, so render-ready is immediate.
548
- // init.ts defers __renderReady until the timeline is bound \u2014 different runtime.
548
+ // init.ts defers __renderReady until the timeline is bound (a different runtime).
549
549
  window.__renderReady = true;
550
550
  return true;
551
551
  }
@@ -2136,7 +2136,7 @@ function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStart
2136
2136
  if (matrix && !isTranslationOnly) {
2137
2137
  if (clipped && log) {
2138
2138
  log.debug(
2139
- `HDR clip rect on affine-transformed element ${el.id} \u2014 clip not applied (affine scissor not yet supported)`
2139
+ `HDR clip rect on affine-transformed element ${el.id}: clip not applied (affine scissor not yet supported)`
2140
2140
  );
2141
2141
  }
2142
2142
  blitRgb48leAffine(
@@ -2193,7 +2193,7 @@ function blitHdrImageLayer(canvas, el, hdrImageBuffers, hdrImageTransferCache, w
2193
2193
  return;
2194
2194
  }
2195
2195
  if (el.clipRect && log) {
2196
- log.debug(`HDR clip rect on image element ${el.id} \u2014 clip not yet supported for images`);
2196
+ log.debug(`HDR clip rect on image element ${el.id}: clip not yet supported for images`);
2197
2197
  }
2198
2198
  try {
2199
2199
  if (hdrPerf) hdrPerf.hdrImageLayerBlits += 1;
@@ -3379,12 +3379,13 @@ function buildRenderPerfSummary(input) {
3379
3379
  // src/services/render/stages/captureStreamingStage.ts
3380
3380
  var DEFAULT_CAPTURE_STALL_MS = 6e4;
3381
3381
  var DE_STALL_POLL_MS = 5e3;
3382
+ var STALL_ADVICE = "Chrome stopped answering. If another app is keeping the GPU or CPU busy (a 3D render, a game, another export), let it finish or render one job at a time, then retry.";
3382
3383
  var ParallelCaptureStallError = class extends Error {
3383
3384
  variant;
3384
3385
  constructor(args) {
3385
3386
  const phases = args.phaseSummary ? ` Last worker phases: ${args.phaseSummary}.` : "";
3386
3387
  super(
3387
- args.variant === "drain" ? `[Render] Parallel ${args.captureLabel} capture stalled: no frame progress for ${args.stallTimeoutMs}ms (stuck at ${args.lastCapturedFrames}/${args.totalFrames}).${phases}` : `[Render] Parallel ${args.captureLabel} capture stalled after ${args.stallTimeoutMs}ms with no frame progress (last frame ${args.lastCapturedFrames}/${args.totalFrames}); retrying on a fresh screenshot session.${phases}`
3388
+ (args.variant === "drain" ? `[Render] Parallel ${args.captureLabel} capture stalled: no frame progress for ${args.stallTimeoutMs}ms (stuck at ${args.lastCapturedFrames}/${args.totalFrames}).${phases}` : `[Render] Parallel ${args.captureLabel} capture stalled after ${args.stallTimeoutMs}ms with no frame progress (last frame ${args.lastCapturedFrames}/${args.totalFrames}); retrying on a fresh screenshot session.${phases}`) + ` ${STALL_ADVICE}`
3388
3389
  );
3389
3390
  this.name = "ParallelCaptureStallError";
3390
3391
  this.variant = args.variant;
@@ -3402,12 +3403,13 @@ function captureModeLabel(mode) {
3402
3403
  }
3403
3404
  var SequentialCaptureStallError = class extends Error {
3404
3405
  captureMode;
3405
- constructor(captureMode, stallTimeoutMs, frameIndex, totalFrames) {
3406
+ constructor(session, stallTimeoutMs, frameIndex, totalFrames) {
3407
+ const step = session.captureStep ? ` while ${session.captureStep}` : "";
3406
3408
  super(
3407
- `[Render] Sequential ${captureModeLabel(captureMode)} capture stalled: no frame progress for ${stallTimeoutMs}ms (stuck at frame ${frameIndex}/${totalFrames}).`
3409
+ `[Render] Sequential ${captureModeLabel(session.captureMode)} capture stalled: no frame progress for ${stallTimeoutMs}ms (stuck at frame ${frameIndex}/${totalFrames}${step}). ` + STALL_ADVICE
3408
3410
  );
3409
3411
  this.name = "SequentialCaptureStallError";
3410
- this.captureMode = captureMode;
3412
+ this.captureMode = session.captureMode;
3411
3413
  }
3412
3414
  };
3413
3415
  function raceAgainstStall(promise, deadlineMs, input, signal) {
@@ -3416,9 +3418,9 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
3416
3418
  () => {
3417
3419
  reject(
3418
3420
  signal?.aborted ? new Error(
3419
- `[Render] Sequential ${captureModeLabel(input.captureMode)} capture aborted while a capture call was in flight (frame ${input.frameIndex}/${input.totalFrames}).`
3421
+ `[Render] Sequential ${captureModeLabel(input.session.captureMode)} capture aborted while a capture call was in flight (frame ${input.frameIndex}/${input.totalFrames}).`
3420
3422
  ) : new SequentialCaptureStallError(
3421
- input.captureMode,
3423
+ input.session,
3422
3424
  input.stallTimeoutMs,
3423
3425
  input.frameIndex,
3424
3426
  input.totalFrames
@@ -3546,7 +3548,7 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
3546
3548
  const captureWithStallGuard = (idx, promise) => raceAgainstStall(
3547
3549
  promise,
3548
3550
  stallTimeoutMs - (Date.now() - lastProgressAt),
3549
- { captureMode: session.captureMode, frameIndex: idx, totalFrames, stallTimeoutMs },
3551
+ { session, frameIndex: idx, totalFrames, stallTimeoutMs },
3550
3552
  abortSignal
3551
3553
  );
3552
3554
  const drainPrev = async () => {
@@ -3900,7 +3902,7 @@ async function runCaptureStreamingStage(input) {
3900
3902
  const { buffer } = await raceAgainstStall(
3901
3903
  captureFrameToBuffer(session, i, time),
3902
3904
  stallTimeoutMs - (Date.now() - lastProgressAt),
3903
- { captureMode: session.captureMode, frameIndex: i, totalFrames, stallTimeoutMs },
3905
+ { session, frameIndex: i, totalFrames, stallTimeoutMs },
3904
3906
  abortSignal
3905
3907
  );
3906
3908
  await reorderBuffer.waitForFrame(i);
@@ -3989,7 +3991,7 @@ async function captureSegmentFrames(ctx, segment, encoder) {
3989
3991
  ctx.captureFrame(ctx.session, i, time),
3990
3992
  ctx.stallTimeoutMs - (Date.now() - lastProgressAt),
3991
3993
  {
3992
- captureMode: ctx.session.captureMode,
3994
+ session: ctx.session,
3993
3995
  frameIndex: i,
3994
3996
  totalFrames: ctx.totalFrames,
3995
3997
  stallTimeoutMs: ctx.stallTimeoutMs
@@ -4385,7 +4387,7 @@ import {
4385
4387
  function formatDowngradeWarning(outputFormat, forcedHdrWithoutSources) {
4386
4388
  const hdrSourceReason = forcedHdrWithoutSources ? "HDR was forced without detected HDR sources" : "HDR source detected";
4387
4389
  const formatReason = outputFormat === "hls" ? "HLS output is SDR-only (H.264 in MPEG-TS)" : "HDR + alpha is not supported";
4388
- return `[Render] ${hdrSourceReason}, but format is "${outputFormat}" \u2014 falling back to SDR. ${formatReason}. Use --format mp4 for HDR10 output.`;
4390
+ return `[Render] ${hdrSourceReason}, but format is "${outputFormat}", so falling back to SDR. ${formatReason}. Use --format mp4 for HDR10 output.`;
4389
4391
  }
4390
4392
  function findRenderHdrAutoPromotionTrigger(input) {
4391
4393
  for (const extracted of input.extractionResult?.extracted ?? []) {
@@ -4398,12 +4400,12 @@ function findRenderHdrAutoPromotionTrigger(input) {
4398
4400
  function logHdrResolution(input, hdrMode, effectiveHdr, forcedHdrWithoutSources) {
4399
4401
  if (forcedHdrWithoutSources) {
4400
4402
  input.log.warn(
4401
- "[Render] HDR forced by --hdr flag, but no HDR sources were detected \u2014 defaulting to HLG. SDR-only compositions may look perceptually wrong on HDR displays."
4403
+ "[Render] HDR forced by --hdr flag, but no HDR sources were detected, defaulting to HLG. SDR-only compositions may look perceptually wrong on HDR displays."
4402
4404
  );
4403
4405
  }
4404
4406
  if (!effectiveHdr) {
4405
4407
  input.log.info(
4406
- hdrMode === "force-sdr" ? "[Render] SDR forced by --sdr flag" : "[Render] No HDR sources detected \u2014 rendering SDR"
4408
+ hdrMode === "force-sdr" ? "[Render] SDR forced by --sdr flag" : "[Render] No HDR sources detected, rendering SDR"
4407
4409
  );
4408
4410
  return;
4409
4411
  }
@@ -4418,9 +4420,9 @@ function logHdrResolution(input, hdrMode, effectiveHdr, forcedHdrWithoutSources)
4418
4420
  );
4419
4421
  return;
4420
4422
  }
4421
- const reason = forcedHdrWithoutSources ? "forced by --hdr flag (no HDR sources detected \u2014 defaulting to HLG)" : "forced by --hdr flag";
4423
+ const reason = forcedHdrWithoutSources ? "forced by --hdr flag (no HDR sources detected, defaulting to HLG)" : "forced by --hdr flag";
4422
4424
  input.log.info(
4423
- `[Render] HDR ${reason} \u2014 output: ${effectiveHdr.transfer.toUpperCase()} (BT.2020, 10-bit H.265)`
4425
+ `[Render] HDR ${reason}, output: ${effectiveHdr.transfer.toUpperCase()} (BT.2020, 10-bit H.265)`
4424
4426
  );
4425
4427
  }
4426
4428
  function resolveEffectiveHdrMode(input) {
@@ -4516,14 +4518,14 @@ function resolveRenderWorkerCount(totalFrames, requestedWorkers, cfg, compiled,
4516
4518
  const reasonCodes = new Set(compiled.renderModeHints.reasons.map((r) => r.code));
4517
4519
  if (reasonCodes.has("htmlInCanvas")) {
4518
4520
  log.warn(
4519
- "[Render] html-in-canvas (drawElementImage) detected \u2014 pinning to 1 worker (Chrome concurrency limitation).",
4521
+ "[Render] html-in-canvas (drawElementImage) detected, pinning to 1 worker (Chrome concurrency limitation).",
4520
4522
  { requestedWorkers }
4521
4523
  );
4522
4524
  return 1;
4523
4525
  }
4524
4526
  if (cfg.lowMemoryMode && requestedWorkers === void 0) {
4525
4527
  log.info(
4526
- "[Render] Low-memory profile \u2014 pinning to 1 capture worker (auto-worker calibration skipped)."
4528
+ "[Render] Low-memory profile, pinning to 1 capture worker (auto-worker calibration skipped)."
4527
4529
  );
4528
4530
  return 1;
4529
4531
  }
@@ -5158,7 +5160,7 @@ function assertVideoFrameCoverage(reports, threshold) {
5158
5160
  const thresholdPct = (threshold * 100).toFixed(1);
5159
5161
  const suffix = sorted.length > 1 ? ` (+${sorted.length - 1} more clip(s) below threshold)` : "";
5160
5162
  throw new VideoFrameCoverageError(
5161
- `Video "${worst.videoId}" captured ${worst.capturedFrames} of expected ${worst.expectedFrames} frames (coverage ${pct}%, threshold ${thresholdPct}%). check/snapshot may pass while the encoded MP4 renders this clip blank \u2014 aborting render to prevent shipping a wrong MP4.${suffix} Set HF_VIDEO_COVERAGE_THRESHOLD=0 to disable this gate.`,
5163
+ `Video "${worst.videoId}" captured ${worst.capturedFrames} of expected ${worst.expectedFrames} frames (coverage ${pct}%, threshold ${thresholdPct}%). check/snapshot may pass while the encoded MP4 renders this clip blank, so the render is aborted to prevent shipping a wrong MP4.${suffix} Set HF_VIDEO_COVERAGE_THRESHOLD=0 to disable this gate.`,
5162
5164
  { threshold, worst, failedReports: sorted }
5163
5165
  );
5164
5166
  }
@@ -6054,7 +6056,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6054
6056
  if (tagName !== "video") {
6055
6057
  if (error instanceof NotMediaPayloadError2) {
6056
6058
  log?.warn(
6057
- `[compile] Audio "${elementIdentity}" (${src}) is a text document, not a media file \u2014 the element is dropped from the render. Point it at a rendered media file.`
6059
+ `[compile] Audio "${elementIdentity}" (${src}) is a text document, not a media file, so the element is dropped from the render. Point it at a rendered media file.`
6058
6060
  );
6059
6061
  }
6060
6062
  return { duration: null, resolvedPath: filePath };
@@ -6143,7 +6145,7 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
6143
6145
  if (r.maxDuration != null && shouldClampResolvedMediaDuration(r.tagName, r.duration, r.maxDuration)) {
6144
6146
  clampList.push({ id: r.id, duration: r.maxDuration });
6145
6147
  log?.warn(
6146
- `[compile] Audio "${r.id}" (${r.src}) is ${r.maxDuration.toFixed(2)}s but its data-duration is ${r.duration.toFixed(2)}s \u2014 the slot is shortened to the media length. Set data-duration to ~${r.maxDuration.toFixed(2)}s, trim data-media-start, or use a longer/looping source if that isn't intended.`
6148
+ `[compile] Audio "${r.id}" (${r.src}) is ${r.maxDuration.toFixed(2)}s but its data-duration is ${r.duration.toFixed(2)}s, so the slot is shortened to the media length. Set data-duration to ~${r.maxDuration.toFixed(2)}s, trim data-media-start, or use a longer/looping source if that isn't intended.`
6147
6149
  );
6148
6150
  }
6149
6151
  }
@@ -6532,7 +6534,7 @@ ${safeText}
6532
6534
  } else {
6533
6535
  if (download.reason instanceof ScriptIntegrityError) throw download.reason;
6534
6536
  defaultLogger.warn(
6535
- `[Compiler] WARNING: Failed to download CDN script: ${src} \u2014 ${download.reason}. The render may fail if this script is required (e.g. GSAP). Consider bundling it locally in your project.`
6537
+ `[Compiler] WARNING: Failed to download CDN script: ${src} (${download.reason}). The render may fail if this script is required (e.g. GSAP). Consider bundling it locally in your project.`
6536
6538
  );
6537
6539
  }
6538
6540
  }
@@ -6584,7 +6586,7 @@ function collectExternalAssets(html, projectDir) {
6584
6586
  }
6585
6587
  if (externalAssets.size > 0) {
6586
6588
  defaultLogger.info(
6587
- `[Compiler] Found ${externalAssets.size} asset(s) outside project directory \u2014 will copy to render output`
6589
+ `[Compiler] Found ${externalAssets.size} asset(s) outside project directory, will copy to render output`
6588
6590
  );
6589
6591
  }
6590
6592
  return {
@@ -6609,7 +6611,7 @@ async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLab
6609
6611
  urlToLocal.set(url, localPath);
6610
6612
  } catch (err) {
6611
6613
  const identity = safeDownloadUrlIdentity(url);
6612
- defaultLogger.warn(`[Compiler] ${warnLabel} \u2014 using original URL as fallback.`, {
6614
+ defaultLogger.warn(`[Compiler] ${warnLabel}, using original URL as fallback.`, {
6613
6615
  urlFingerprint: identity.urlFingerprint,
6614
6616
  host: identity.host,
6615
6617
  error: err instanceof Error ? err.message : String(err)
@@ -6702,7 +6704,7 @@ async function fetchExternalStylesheetCss(href) {
6702
6704
  timeoutMs: 15e3
6703
6705
  });
6704
6706
  } catch (err) {
6705
- defaultLogger.warn("[Compiler] External stylesheet fetch failed \u2014 preserving link tag.", {
6707
+ defaultLogger.warn("[Compiler] External stylesheet fetch failed, preserving link tag.", {
6706
6708
  urlFingerprint: identity.urlFingerprint,
6707
6709
  host: identity.host,
6708
6710
  error: err instanceof Error ? err.message : String(err)
@@ -7346,12 +7348,15 @@ async function resolveCompositionDurations(page, unresolved) {
7346
7348
  const resolved = [];
7347
7349
  for (const id of compIds) {
7348
7350
  const tl = timelines[id];
7349
- if (tl && typeof tl.duration === "function") {
7350
- const dur = tl.duration();
7351
- if (dur > 0) {
7352
- resolved.push({ id, duration: dur, source: "__timelines" });
7353
- continue;
7354
- }
7351
+ let dur = 0;
7352
+ try {
7353
+ dur = typeof tl?.duration === "function" ? Number(tl.duration()) : 0;
7354
+ } catch {
7355
+ dur = 0;
7356
+ }
7357
+ if (dur > 0) {
7358
+ resolved.push({ id, duration: dur, source: "__timelines" });
7359
+ continue;
7355
7360
  }
7356
7361
  const el = document.getElementById(id);
7357
7362
  if (el) {
@@ -7447,28 +7452,28 @@ async function runCompileStage(input) {
7447
7452
  cfg.useDrawElement = false;
7448
7453
  deCompileGate = "3d";
7449
7454
  log.info(
7450
- "[Render] Fast capture: composition uses a CSS 3D rendering context (perspective / preserve-3d / backface-visibility) \u2014 disabling drawElementImage for this render. Capture uses the platform's baseline route."
7455
+ "[Render] Fast capture: composition uses a CSS 3D rendering context (perspective / preserve-3d / backface-visibility), so disabling drawElementImage for this render. Capture uses the platform's baseline route."
7451
7456
  );
7452
7457
  }
7453
7458
  if (cfg.useDrawElement && process.env.HF_FAST_CAPTURE_BLEND !== "true" && compiled.usesMixBlendMode) {
7454
7459
  cfg.useDrawElement = false;
7455
7460
  deCompileGate = "mix_blend_mode";
7456
7461
  log.info(
7457
- "[Render] Fast capture: composition uses mix-blend-mode \u2014 disabling drawElementImage for this render. Capture uses the platform's baseline route."
7462
+ "[Render] Fast capture: composition uses mix-blend-mode, so disabling drawElementImage for this render. Capture uses the platform's baseline route."
7458
7463
  );
7459
7464
  }
7460
7465
  if (cfg.useDrawElement && process.env.HF_FAST_CAPTURE_ANCESTOR_BG !== "true" && compiled.hasAncestorBackgroundImage) {
7461
7466
  cfg.useDrawElement = false;
7462
7467
  deCompileGate = "ancestor_background_image";
7463
7468
  log.info(
7464
- "[Render] Fast capture: composition root's ancestors (body/html/wrapper) carry a background-image \u2014 disabling drawElementImage for this render. Capture uses the platform's baseline route."
7469
+ "[Render] Fast capture: composition root's ancestors (body/html/wrapper) carry a background-image, so disabling drawElementImage for this render. Capture uses the platform's baseline route."
7465
7470
  );
7466
7471
  }
7467
7472
  if (cfg.useDrawElement && process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE !== "true" && compiled.hasShaderTransitions) {
7468
7473
  cfg.useDrawElement = false;
7469
7474
  deCompileGate = "shader_transitions";
7470
7475
  log.info(
7471
- "[Render] Fast capture: composition uses shader transitions \u2014 disabling drawElementImage so page-side compositing stays available."
7476
+ "[Render] Fast capture: composition uses shader transitions, so disabling drawElementImage so page-side compositing stays available."
7472
7477
  );
7473
7478
  }
7474
7479
  if (!cfg.useDrawElement && cfg.pageSideCompositingAutoDisabled) {
@@ -7796,7 +7801,7 @@ async function runProbeStage(input) {
7796
7801
  } else {
7797
7802
  beginFrameStalled = true;
7798
7803
  log.warn(
7799
- "[Render] BeginFrame liveness probe timed out \u2014 this composition stalls BeginFrame on this host (SwiftShader heavy-layer pattern). Relaunching the probe browser in screenshot capture mode; the render will use screenshot capture throughout.",
7804
+ "[Render] BeginFrame liveness probe timed out: this composition stalls BeginFrame on this host (SwiftShader heavy-layer pattern). Relaunching the probe browser in screenshot capture mode; the render will use screenshot capture throughout.",
7800
7805
  { probeTimeoutMs }
7801
7806
  );
7802
7807
  lastBrowserConsole = probeSession.browserConsoleBuffer;
@@ -8010,7 +8015,7 @@ async function runProbeStage(input) {
8010
8015
  video.start = win.visibleStart;
8011
8016
  video.end = win.visibleEnd;
8012
8017
  log.info(
8013
- `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s\u2013${win.visibleEnd.toFixed(2)}s`
8018
+ `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s to ${win.visibleEnd.toFixed(2)}s`
8014
8019
  );
8015
8020
  }
8016
8021
  }
@@ -8058,7 +8063,7 @@ async function runProbeStage(input) {
8058
8063
  });
8059
8064
  if (!timelinesInfo.gsapLoaded) {
8060
8065
  diagnostics.push(
8061
- "GSAP is not loaded \u2014 CDN script may have failed to download. Bundle GSAP locally in your project instead of using a CDN <script src>."
8066
+ "GSAP is not loaded. The CDN script may have failed to download. Bundle GSAP locally in your project instead of using a CDN <script src>."
8062
8067
  );
8063
8068
  } else if (timelinesInfo.timelineKeys.length === 0) {
8064
8069
  diagnostics.push(
@@ -8075,10 +8080,10 @@ async function runProbeStage(input) {
8075
8080
  log.warn("Failed to gather browser diagnostics for zero-duration composition", {
8076
8081
  error: err instanceof Error ? err.message : String(err)
8077
8082
  });
8078
- diagnostics.push("(Could not gather browser diagnostics \u2014 page may have crashed)");
8083
+ diagnostics.push("(Could not gather browser diagnostics. The page may have crashed.)");
8079
8084
  }
8080
8085
  const hint = diagnostics.length > 0 ? "\n\nDiagnostics:\n - " + diagnostics.join("\n - ") : "\n\nCheck that GSAP timelines are registered on window.__timelines.";
8081
- throw new Error("Composition duration is 0 \u2014 this would produce a black video." + hint);
8086
+ throw new Error("Composition duration is 0, so this would produce a black video." + hint);
8082
8087
  }
8083
8088
  if (probeSession) {
8084
8089
  const failedRequests = probeSession.browserConsoleBuffer.filter(isActionableProbeFailure);
@@ -8159,6 +8164,7 @@ import {
8159
8164
  detectTransfer,
8160
8165
  extractAllVideoFrames,
8161
8166
  extractMediaMetadata as extractMediaMetadata3,
8167
+ isVideoPastTimelineEnd,
8162
8168
  isHdrColorSpace as isHdrColorSpace2,
8163
8169
  resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
8164
8170
  runVideoExtractionWithRetry,
@@ -8373,6 +8379,9 @@ async function runExtractVideosStage(input) {
8373
8379
  } = input;
8374
8380
  const stage2Start = Date.now();
8375
8381
  const extractionPolicy = resolveVideoExtractionPolicy();
8382
+ composition.videos = composition.videos.filter(
8383
+ (video) => !isVideoPastTimelineEnd(video, composition.duration)
8384
+ );
8376
8385
  let frameLookup = null;
8377
8386
  let extractionResult = null;
8378
8387
  let failureToEnforce = null;
@@ -8459,7 +8468,8 @@ async function runExtractVideosStage(input) {
8459
8468
  timelineEnd: composition.duration,
8460
8469
  maxTransientRetries: extractionPolicy.maxTransientRetries,
8461
8470
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
8462
- deferRangeExtraction
8471
+ deferRangeExtraction,
8472
+ contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)]
8463
8473
  },
8464
8474
  abortSignal,
8465
8475
  cfg,
@@ -11085,7 +11095,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
11085
11095
  const madeProgress = captureAttemptMadeProgress(frameCount, remainingCount);
11086
11096
  if (!madeProgress) {
11087
11097
  options.log.warn(
11088
- "[Render] Capture attempt made no forward progress; composition is likely structurally broken \u2014 not retrying.",
11098
+ "[Render] Capture attempt made no forward progress; composition is likely structurally broken, not retrying.",
11089
11099
  { attempt, frameCount, remainingCount, workers: currentWorkers }
11090
11100
  );
11091
11101
  }
@@ -11154,7 +11164,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
11154
11164
  }
11155
11165
  if (!madeProgress) {
11156
11166
  options.log.warn(
11157
- "[Render] Capture attempt made no forward progress; composition is likely structurally broken \u2014 not retrying.",
11167
+ "[Render] Capture attempt made no forward progress; composition is likely structurally broken, not retrying.",
11158
11168
  { attempt, frameCount, remainingCount, workers: currentWorkers }
11159
11169
  );
11160
11170
  }
@@ -11617,7 +11627,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
11617
11627
  execution.defer("remove workDir", () => {
11618
11628
  if (job.config.debug) return;
11619
11629
  if (job.status === "complete" && process.env.KEEP_TEMP === "1") {
11620
- log.info("KEEP_TEMP=1 \u2014 leaving workDir on disk for inspection", { workDir });
11630
+ log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
11621
11631
  return;
11622
11632
  }
11623
11633
  rmSync9(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
@@ -11854,7 +11864,7 @@ async function executeRenderPipeline(input) {
11854
11864
  captureForceScreenshot = true;
11855
11865
  updateCaptureObservability({ forceScreenshot: captureForceScreenshot });
11856
11866
  log.info(
11857
- "[Render] Low-memory render profile active \u2014 screenshot capture, auto-worker calibration skipped" + (job.config.workers === void 0 ? ", pinned to 1 worker" : "") + ". Override with --no-low-memory-mode or PRODUCER_LOW_MEMORY_MODE=false.",
11867
+ "[Render] Low-memory render profile active: screenshot capture, auto-worker calibration skipped" + (job.config.workers === void 0 ? ", pinned to 1 worker" : "") + ". Override with --no-low-memory-mode or PRODUCER_LOW_MEMORY_MODE=false.",
11858
11868
  { totalMemMb: getSystemTotalMb(), thresholdMb: LOW_MEMORY_TOTAL_MB_THRESHOLD }
11859
11869
  );
11860
11870
  }
@@ -12422,7 +12432,7 @@ async function executeRenderPipeline(input) {
12422
12432
  cfg.useDrawElement = false;
12423
12433
  deClampReason = workerCount > 1 ? "parallel" : "disk_path";
12424
12434
  log.info(
12425
- "[Render] Fast capture: default-on drawElement disabled for this render \u2014 " + (workerCount > 1 ? "parallel capture" : "the disk capture path") + " has no runtime self-verification. Set PRODUCER_EXPERIMENTAL_FAST_CAPTURE=true to override."
12435
+ "[Render] Fast capture: default-on drawElement disabled for this render: " + (workerCount > 1 ? "parallel capture" : "the disk capture path") + " has no runtime self-verification. Set PRODUCER_EXPERIMENTAL_FAST_CAPTURE=true to override."
12426
12436
  );
12427
12437
  if (probeSession && probeSession.captureMode === "drawelement") {
12428
12438
  lastBrowserConsole = probeSession.browserConsoleBuffer;
@@ -12518,7 +12528,7 @@ async function executeRenderPipeline(input) {
12518
12528
  });
12519
12529
  updateCaptureObservability({ forceScreenshot: captureForceScreenshot });
12520
12530
  log.info(
12521
- `[Render] Page-side compositing enabled \u2014 bypassing Node-side layered shader-blend path. Engine will capture one ${needsAlpha ? "RGBA PNG" : "opaque RGB"} screenshot per output frame.`
12531
+ `[Render] Page-side compositing enabled, bypassing Node-side layered shader-blend path. Engine will capture one ${needsAlpha ? "RGBA PNG" : "opaque RGB"} screenshot per output frame.`
12522
12532
  );
12523
12533
  }
12524
12534
  const useLayeredComposite = !usePageSideCompositingForTransitions && shouldUseLayeredComposite({
@@ -13015,7 +13025,7 @@ async function executeRenderPipeline(input) {
13015
13025
  cfg.useDrawElement = false;
13016
13026
  deClampReason = deClampReason ?? "disk_path";
13017
13027
  log.info(
13018
- "[Render] Fast capture: drawElement disabled for the disk fallback \u2014 streaming encoder spawn failed and the disk path has no runtime self-verification."
13028
+ "[Render] Fast capture: drawElement disabled for the disk fallback: streaming encoder spawn failed and the disk path has no runtime self-verification."
13019
13029
  );
13020
13030
  if (probeSession && probeSession.captureMode === "drawelement") {
13021
13031
  lastBrowserConsole = probeSession.browserConsoleBuffer;
@@ -13633,10 +13643,10 @@ function validateJsonSafeValue(value, field) {
13633
13643
  walkVariables(value, field, /* @__PURE__ */ new WeakSet());
13634
13644
  }
13635
13645
  var LEAF_REJECTIONS = {
13636
- undefined: "undefined leaves are silently dropped by JSON.stringify \u2014 use null if you mean an absent value",
13646
+ undefined: "undefined leaves are silently dropped by JSON.stringify. Use null if you mean an absent value",
13637
13647
  function: "functions are not JSON-serializable",
13638
13648
  symbol: "Symbols are not JSON-serializable",
13639
- bigint: "BigInt values throw at JSON.stringify \u2014 encode as a string if you need 64-bit integers"
13649
+ bigint: "BigInt values throw at JSON.stringify. Encode as a string if you need 64-bit integers"
13640
13650
  };
13641
13651
  function walkVariables(value, path, seen) {
13642
13652
  const t = typeof value;
@@ -13657,7 +13667,7 @@ function walkVariables(value, path, seen) {
13657
13667
  if (seen.has(value)) {
13658
13668
  throw new InvalidConfigError(
13659
13669
  path,
13660
- "circular reference detected \u2014 JSON.stringify cannot serialize cycles"
13670
+ "circular reference detected: JSON.stringify cannot serialize cycles"
13661
13671
  );
13662
13672
  }
13663
13673
  seen.add(value);
@@ -13997,7 +14007,7 @@ function validateOutputResolutionOverride(body) {
13997
14007
  return `Invalid outputResolution "${body.outputResolution}". Must be one of: landscape, portrait, landscape-4k, portrait-4k, square, square-4k (aliases: 1080p, 4k, \u2026).`;
13998
14008
  }
13999
14009
  if (normalized !== void 0 && (body.format === "webm" || body.format === "mov")) {
14000
- return `outputResolution is not supported with format "${body.format}" \u2014 the alpha (webm/mov) capture path can't supersample. Use format "mp4", or omit outputResolution to render at the composition's native dimensions.`;
14010
+ return `outputResolution is not supported with format "${body.format}": the alpha (webm/mov) capture path can't supersample. Use format "mp4", or omit outputResolution to render at the composition's native dimensions.`;
14001
14011
  }
14002
14012
  return void 0;
14003
14013
  }
@@ -67,6 +67,7 @@ export declare function collectExternalAssets(html: string, projectDir: string):
67
67
  html: string;
68
68
  externalAssets: Map<string, string>;
69
69
  };
70
+ export declare const REMOTE_MEDIA_SUBDIR = "_remote_media";
70
71
  /**
71
72
  * Download any remote `src` URLs on `<video>` / `<audio>` elements and their
72
73
  * `<source>` children into a local subdirectory of `downloadDir`, rewrite the
@@ -44,8 +44,9 @@ import type { ProducerLogger } from "../../../logger.js";
44
44
  import type { ProgressCallback, RenderJob } from "../../renderOrchestrator.js";
45
45
  import type { SdrStreamingCapturePlan } from "../capturePlan.js";
46
46
  export declare function resolveCaptureStallTimeoutMs(): number;
47
+ type StalledSession = Pick<CaptureSession, "captureMode" | "captureStep">;
47
48
  export declare function raceAgainstStall<T>(promise: Promise<T>, deadlineMs: number, input: {
48
- captureMode: CaptureSession["captureMode"];
49
+ session: StalledSession;
49
50
  frameIndex: number;
50
51
  totalFrames: number;
51
52
  stallTimeoutMs: number;
@@ -123,3 +124,4 @@ export type CaptureStreamingStageResult = {
123
124
  success: false;
124
125
  };
125
126
  export declare function runCaptureStreamingStage(input: CaptureStreamingStageInput): Promise<CaptureStreamingStageResult>;
127
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/producer",
3
- "version": "0.8.145",
3
+ "version": "0.8.147",
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/core": "0.8.145",
39
- "@hyperframes/engine": "0.8.145",
40
- "@hyperframes/lint": "0.8.145",
41
- "@hyperframes/parsers": "0.8.145",
42
- "@hyperframes/studio-server": "0.8.145"
38
+ "@hyperframes/engine": "0.8.147",
39
+ "@hyperframes/core": "0.8.147",
40
+ "@hyperframes/lint": "0.8.147",
41
+ "@hyperframes/parsers": "0.8.147",
42
+ "@hyperframes/studio-server": "0.8.147"
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.145"
51
+ "@hyperframes/aws-lambda": "0.8.147"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=22"