@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.
package/dist/index.js CHANGED
@@ -272,7 +272,7 @@ function buildVirtualTimeShim(options) {
272
272
  const seedRandomFromFrame = options.seedRandomFromFrame === true;
273
273
  const seededRandomBlock = seedRandomFromFrame ? String.raw`
274
274
  // Seeded Math.random / crypto.getRandomValues, keyed by virtual time.
275
- // Mulberry32 — single uint32 state, deterministic, fast.
275
+ // Mulberry32: single uint32 state, deterministic, fast.
276
276
  var rngState = 0;
277
277
  function mulberry32() {
278
278
  rngState |= 0; rngState = (rngState + 0x6D2B79F5) | 0;
@@ -283,7 +283,7 @@ function buildVirtualTimeShim(options) {
283
283
  }
284
284
  function reseedRngFromTime(ms) {
285
285
  var ms32 = Math.max(0, Math.floor(Number(ms) || 0)) | 0;
286
- // Knuth's multiplicative hash + golden-ratio offset — gives a well-
286
+ // Knuth's multiplicative hash + golden-ratio offset gives a well-
287
287
  // distributed seed even for frame 0 (otherwise rngState=0 degenerates
288
288
  // the PRNG's first few outputs).
289
289
  rngState = (Math.imul(ms32, -1640531527) + 0x9E3779B9) | 0;
@@ -525,7 +525,7 @@ function buildRenderModeScript(fps) {
525
525
  };
526
526
  window.__playerReady = true;
527
527
  // Media-fallback player has no timeline to bind, so render-ready is immediate.
528
- // init.ts defers __renderReady until the timeline is bound \u2014 different runtime.
528
+ // init.ts defers __renderReady until the timeline is bound (a different runtime).
529
529
  window.__renderReady = true;
530
530
  return true;
531
531
  }
@@ -2117,7 +2117,7 @@ function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStart
2117
2117
  if (matrix && !isTranslationOnly) {
2118
2118
  if (clipped && log) {
2119
2119
  log.debug(
2120
- `HDR clip rect on affine-transformed element ${el.id} \u2014 clip not applied (affine scissor not yet supported)`
2120
+ `HDR clip rect on affine-transformed element ${el.id}: clip not applied (affine scissor not yet supported)`
2121
2121
  );
2122
2122
  }
2123
2123
  blitRgb48leAffine(
@@ -2174,7 +2174,7 @@ function blitHdrImageLayer(canvas, el, hdrImageBuffers, hdrImageTransferCache, w
2174
2174
  return;
2175
2175
  }
2176
2176
  if (el.clipRect && log) {
2177
- log.debug(`HDR clip rect on image element ${el.id} \u2014 clip not yet supported for images`);
2177
+ log.debug(`HDR clip rect on image element ${el.id}: clip not yet supported for images`);
2178
2178
  }
2179
2179
  try {
2180
2180
  if (hdrPerf) hdrPerf.hdrImageLayerBlits += 1;
@@ -3360,12 +3360,13 @@ function buildRenderPerfSummary(input) {
3360
3360
  // src/services/render/stages/captureStreamingStage.ts
3361
3361
  var DEFAULT_CAPTURE_STALL_MS = 6e4;
3362
3362
  var DE_STALL_POLL_MS = 5e3;
3363
+ 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.";
3363
3364
  var ParallelCaptureStallError = class extends Error {
3364
3365
  variant;
3365
3366
  constructor(args) {
3366
3367
  const phases = args.phaseSummary ? ` Last worker phases: ${args.phaseSummary}.` : "";
3367
3368
  super(
3368
- 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}`
3369
+ (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}`
3369
3370
  );
3370
3371
  this.name = "ParallelCaptureStallError";
3371
3372
  this.variant = args.variant;
@@ -3383,12 +3384,13 @@ function captureModeLabel(mode) {
3383
3384
  }
3384
3385
  var SequentialCaptureStallError = class extends Error {
3385
3386
  captureMode;
3386
- constructor(captureMode, stallTimeoutMs, frameIndex, totalFrames) {
3387
+ constructor(session, stallTimeoutMs, frameIndex, totalFrames) {
3388
+ const step = session.captureStep ? ` while ${session.captureStep}` : "";
3387
3389
  super(
3388
- `[Render] Sequential ${captureModeLabel(captureMode)} capture stalled: no frame progress for ${stallTimeoutMs}ms (stuck at frame ${frameIndex}/${totalFrames}).`
3390
+ `[Render] Sequential ${captureModeLabel(session.captureMode)} capture stalled: no frame progress for ${stallTimeoutMs}ms (stuck at frame ${frameIndex}/${totalFrames}${step}). ` + STALL_ADVICE
3389
3391
  );
3390
3392
  this.name = "SequentialCaptureStallError";
3391
- this.captureMode = captureMode;
3393
+ this.captureMode = session.captureMode;
3392
3394
  }
3393
3395
  };
3394
3396
  function raceAgainstStall(promise, deadlineMs, input, signal) {
@@ -3397,9 +3399,9 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
3397
3399
  () => {
3398
3400
  reject(
3399
3401
  signal?.aborted ? new Error(
3400
- `[Render] Sequential ${captureModeLabel(input.captureMode)} capture aborted while a capture call was in flight (frame ${input.frameIndex}/${input.totalFrames}).`
3402
+ `[Render] Sequential ${captureModeLabel(input.session.captureMode)} capture aborted while a capture call was in flight (frame ${input.frameIndex}/${input.totalFrames}).`
3401
3403
  ) : new SequentialCaptureStallError(
3402
- input.captureMode,
3404
+ input.session,
3403
3405
  input.stallTimeoutMs,
3404
3406
  input.frameIndex,
3405
3407
  input.totalFrames
@@ -3527,7 +3529,7 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
3527
3529
  const captureWithStallGuard = (idx, promise) => raceAgainstStall(
3528
3530
  promise,
3529
3531
  stallTimeoutMs - (Date.now() - lastProgressAt),
3530
- { captureMode: session.captureMode, frameIndex: idx, totalFrames, stallTimeoutMs },
3532
+ { session, frameIndex: idx, totalFrames, stallTimeoutMs },
3531
3533
  abortSignal
3532
3534
  );
3533
3535
  const drainPrev = async () => {
@@ -3881,7 +3883,7 @@ async function runCaptureStreamingStage(input) {
3881
3883
  const { buffer } = await raceAgainstStall(
3882
3884
  captureFrameToBuffer(session, i, time),
3883
3885
  stallTimeoutMs - (Date.now() - lastProgressAt),
3884
- { captureMode: session.captureMode, frameIndex: i, totalFrames, stallTimeoutMs },
3886
+ { session, frameIndex: i, totalFrames, stallTimeoutMs },
3885
3887
  abortSignal
3886
3888
  );
3887
3889
  await reorderBuffer.waitForFrame(i);
@@ -3970,7 +3972,7 @@ async function captureSegmentFrames(ctx, segment, encoder) {
3970
3972
  ctx.captureFrame(ctx.session, i, time),
3971
3973
  ctx.stallTimeoutMs - (Date.now() - lastProgressAt),
3972
3974
  {
3973
- captureMode: ctx.session.captureMode,
3975
+ session: ctx.session,
3974
3976
  frameIndex: i,
3975
3977
  totalFrames: ctx.totalFrames,
3976
3978
  stallTimeoutMs: ctx.stallTimeoutMs
@@ -4366,7 +4368,7 @@ import {
4366
4368
  function formatDowngradeWarning(outputFormat, forcedHdrWithoutSources) {
4367
4369
  const hdrSourceReason = forcedHdrWithoutSources ? "HDR was forced without detected HDR sources" : "HDR source detected";
4368
4370
  const formatReason = outputFormat === "hls" ? "HLS output is SDR-only (H.264 in MPEG-TS)" : "HDR + alpha is not supported";
4369
- return `[Render] ${hdrSourceReason}, but format is "${outputFormat}" \u2014 falling back to SDR. ${formatReason}. Use --format mp4 for HDR10 output.`;
4371
+ return `[Render] ${hdrSourceReason}, but format is "${outputFormat}", so falling back to SDR. ${formatReason}. Use --format mp4 for HDR10 output.`;
4370
4372
  }
4371
4373
  function findRenderHdrAutoPromotionTrigger(input) {
4372
4374
  for (const extracted of input.extractionResult?.extracted ?? []) {
@@ -4379,12 +4381,12 @@ function findRenderHdrAutoPromotionTrigger(input) {
4379
4381
  function logHdrResolution(input, hdrMode, effectiveHdr, forcedHdrWithoutSources) {
4380
4382
  if (forcedHdrWithoutSources) {
4381
4383
  input.log.warn(
4382
- "[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."
4384
+ "[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."
4383
4385
  );
4384
4386
  }
4385
4387
  if (!effectiveHdr) {
4386
4388
  input.log.info(
4387
- hdrMode === "force-sdr" ? "[Render] SDR forced by --sdr flag" : "[Render] No HDR sources detected \u2014 rendering SDR"
4389
+ hdrMode === "force-sdr" ? "[Render] SDR forced by --sdr flag" : "[Render] No HDR sources detected, rendering SDR"
4388
4390
  );
4389
4391
  return;
4390
4392
  }
@@ -4399,9 +4401,9 @@ function logHdrResolution(input, hdrMode, effectiveHdr, forcedHdrWithoutSources)
4399
4401
  );
4400
4402
  return;
4401
4403
  }
4402
- const reason = forcedHdrWithoutSources ? "forced by --hdr flag (no HDR sources detected \u2014 defaulting to HLG)" : "forced by --hdr flag";
4404
+ const reason = forcedHdrWithoutSources ? "forced by --hdr flag (no HDR sources detected, defaulting to HLG)" : "forced by --hdr flag";
4403
4405
  input.log.info(
4404
- `[Render] HDR ${reason} \u2014 output: ${effectiveHdr.transfer.toUpperCase()} (BT.2020, 10-bit H.265)`
4406
+ `[Render] HDR ${reason}, output: ${effectiveHdr.transfer.toUpperCase()} (BT.2020, 10-bit H.265)`
4405
4407
  );
4406
4408
  }
4407
4409
  function resolveEffectiveHdrMode(input) {
@@ -4497,14 +4499,14 @@ function resolveRenderWorkerCount(totalFrames, requestedWorkers, cfg, compiled,
4497
4499
  const reasonCodes = new Set(compiled.renderModeHints.reasons.map((r) => r.code));
4498
4500
  if (reasonCodes.has("htmlInCanvas")) {
4499
4501
  log.warn(
4500
- "[Render] html-in-canvas (drawElementImage) detected \u2014 pinning to 1 worker (Chrome concurrency limitation).",
4502
+ "[Render] html-in-canvas (drawElementImage) detected, pinning to 1 worker (Chrome concurrency limitation).",
4501
4503
  { requestedWorkers }
4502
4504
  );
4503
4505
  return 1;
4504
4506
  }
4505
4507
  if (cfg.lowMemoryMode && requestedWorkers === void 0) {
4506
4508
  log.info(
4507
- "[Render] Low-memory profile \u2014 pinning to 1 capture worker (auto-worker calibration skipped)."
4509
+ "[Render] Low-memory profile, pinning to 1 capture worker (auto-worker calibration skipped)."
4508
4510
  );
4509
4511
  return 1;
4510
4512
  }
@@ -5139,7 +5141,7 @@ function assertVideoFrameCoverage(reports, threshold) {
5139
5141
  const thresholdPct = (threshold * 100).toFixed(1);
5140
5142
  const suffix = sorted.length > 1 ? ` (+${sorted.length - 1} more clip(s) below threshold)` : "";
5141
5143
  throw new VideoFrameCoverageError(
5142
- `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.`,
5144
+ `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.`,
5143
5145
  { threshold, worst, failedReports: sorted }
5144
5146
  );
5145
5147
  }
@@ -6035,7 +6037,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6035
6037
  if (tagName !== "video") {
6036
6038
  if (error instanceof NotMediaPayloadError2) {
6037
6039
  log?.warn(
6038
- `[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.`
6040
+ `[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.`
6039
6041
  );
6040
6042
  }
6041
6043
  return { duration: null, resolvedPath: filePath };
@@ -6124,7 +6126,7 @@ async function compileHtmlFile(html, baseDir, downloadDir, log) {
6124
6126
  if (r.maxDuration != null && shouldClampResolvedMediaDuration(r.tagName, r.duration, r.maxDuration)) {
6125
6127
  clampList.push({ id: r.id, duration: r.maxDuration });
6126
6128
  log?.warn(
6127
- `[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.`
6129
+ `[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.`
6128
6130
  );
6129
6131
  }
6130
6132
  }
@@ -6513,7 +6515,7 @@ ${safeText}
6513
6515
  } else {
6514
6516
  if (download.reason instanceof ScriptIntegrityError) throw download.reason;
6515
6517
  defaultLogger.warn(
6516
- `[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.`
6518
+ `[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.`
6517
6519
  );
6518
6520
  }
6519
6521
  }
@@ -6565,7 +6567,7 @@ function collectExternalAssets(html, projectDir) {
6565
6567
  }
6566
6568
  if (externalAssets.size > 0) {
6567
6569
  defaultLogger.info(
6568
- `[Compiler] Found ${externalAssets.size} asset(s) outside project directory \u2014 will copy to render output`
6570
+ `[Compiler] Found ${externalAssets.size} asset(s) outside project directory, will copy to render output`
6569
6571
  );
6570
6572
  }
6571
6573
  return {
@@ -6590,7 +6592,7 @@ async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLab
6590
6592
  urlToLocal.set(url, localPath);
6591
6593
  } catch (err) {
6592
6594
  const identity = safeDownloadUrlIdentity(url);
6593
- defaultLogger.warn(`[Compiler] ${warnLabel} \u2014 using original URL as fallback.`, {
6595
+ defaultLogger.warn(`[Compiler] ${warnLabel}, using original URL as fallback.`, {
6594
6596
  urlFingerprint: identity.urlFingerprint,
6595
6597
  host: identity.host,
6596
6598
  error: err instanceof Error ? err.message : String(err)
@@ -6683,7 +6685,7 @@ async function fetchExternalStylesheetCss(href) {
6683
6685
  timeoutMs: 15e3
6684
6686
  });
6685
6687
  } catch (err) {
6686
- defaultLogger.warn("[Compiler] External stylesheet fetch failed \u2014 preserving link tag.", {
6688
+ defaultLogger.warn("[Compiler] External stylesheet fetch failed, preserving link tag.", {
6687
6689
  urlFingerprint: identity.urlFingerprint,
6688
6690
  host: identity.host,
6689
6691
  error: err instanceof Error ? err.message : String(err)
@@ -7327,12 +7329,15 @@ async function resolveCompositionDurations(page, unresolved) {
7327
7329
  const resolved = [];
7328
7330
  for (const id of compIds) {
7329
7331
  const tl = timelines[id];
7330
- if (tl && typeof tl.duration === "function") {
7331
- const dur = tl.duration();
7332
- if (dur > 0) {
7333
- resolved.push({ id, duration: dur, source: "__timelines" });
7334
- continue;
7335
- }
7332
+ let dur = 0;
7333
+ try {
7334
+ dur = typeof tl?.duration === "function" ? Number(tl.duration()) : 0;
7335
+ } catch {
7336
+ dur = 0;
7337
+ }
7338
+ if (dur > 0) {
7339
+ resolved.push({ id, duration: dur, source: "__timelines" });
7340
+ continue;
7336
7341
  }
7337
7342
  const el = document.getElementById(id);
7338
7343
  if (el) {
@@ -7428,28 +7433,28 @@ async function runCompileStage(input) {
7428
7433
  cfg.useDrawElement = false;
7429
7434
  deCompileGate = "3d";
7430
7435
  log.info(
7431
- "[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."
7436
+ "[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."
7432
7437
  );
7433
7438
  }
7434
7439
  if (cfg.useDrawElement && process.env.HF_FAST_CAPTURE_BLEND !== "true" && compiled.usesMixBlendMode) {
7435
7440
  cfg.useDrawElement = false;
7436
7441
  deCompileGate = "mix_blend_mode";
7437
7442
  log.info(
7438
- "[Render] Fast capture: composition uses mix-blend-mode \u2014 disabling drawElementImage for this render. Capture uses the platform's baseline route."
7443
+ "[Render] Fast capture: composition uses mix-blend-mode, so disabling drawElementImage for this render. Capture uses the platform's baseline route."
7439
7444
  );
7440
7445
  }
7441
7446
  if (cfg.useDrawElement && process.env.HF_FAST_CAPTURE_ANCESTOR_BG !== "true" && compiled.hasAncestorBackgroundImage) {
7442
7447
  cfg.useDrawElement = false;
7443
7448
  deCompileGate = "ancestor_background_image";
7444
7449
  log.info(
7445
- "[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."
7450
+ "[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."
7446
7451
  );
7447
7452
  }
7448
7453
  if (cfg.useDrawElement && process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE !== "true" && compiled.hasShaderTransitions) {
7449
7454
  cfg.useDrawElement = false;
7450
7455
  deCompileGate = "shader_transitions";
7451
7456
  log.info(
7452
- "[Render] Fast capture: composition uses shader transitions \u2014 disabling drawElementImage so page-side compositing stays available."
7457
+ "[Render] Fast capture: composition uses shader transitions, so disabling drawElementImage so page-side compositing stays available."
7453
7458
  );
7454
7459
  }
7455
7460
  if (!cfg.useDrawElement && cfg.pageSideCompositingAutoDisabled) {
@@ -7777,7 +7782,7 @@ async function runProbeStage(input) {
7777
7782
  } else {
7778
7783
  beginFrameStalled = true;
7779
7784
  log.warn(
7780
- "[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.",
7785
+ "[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.",
7781
7786
  { probeTimeoutMs }
7782
7787
  );
7783
7788
  lastBrowserConsole = probeSession.browserConsoleBuffer;
@@ -7991,7 +7996,7 @@ async function runProbeStage(input) {
7991
7996
  video.start = win.visibleStart;
7992
7997
  video.end = win.visibleEnd;
7993
7998
  log.info(
7994
- `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s\u2013${win.visibleEnd.toFixed(2)}s`
7999
+ `[Probe] Runtime video discovery: ${video.id} visible ${win.visibleStart.toFixed(2)}s to ${win.visibleEnd.toFixed(2)}s`
7995
8000
  );
7996
8001
  }
7997
8002
  }
@@ -8039,7 +8044,7 @@ async function runProbeStage(input) {
8039
8044
  });
8040
8045
  if (!timelinesInfo.gsapLoaded) {
8041
8046
  diagnostics.push(
8042
- "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>."
8047
+ "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>."
8043
8048
  );
8044
8049
  } else if (timelinesInfo.timelineKeys.length === 0) {
8045
8050
  diagnostics.push(
@@ -8056,10 +8061,10 @@ async function runProbeStage(input) {
8056
8061
  log.warn("Failed to gather browser diagnostics for zero-duration composition", {
8057
8062
  error: err instanceof Error ? err.message : String(err)
8058
8063
  });
8059
- diagnostics.push("(Could not gather browser diagnostics \u2014 page may have crashed)");
8064
+ diagnostics.push("(Could not gather browser diagnostics. The page may have crashed.)");
8060
8065
  }
8061
8066
  const hint = diagnostics.length > 0 ? "\n\nDiagnostics:\n - " + diagnostics.join("\n - ") : "\n\nCheck that GSAP timelines are registered on window.__timelines.";
8062
- throw new Error("Composition duration is 0 \u2014 this would produce a black video." + hint);
8067
+ throw new Error("Composition duration is 0, so this would produce a black video." + hint);
8063
8068
  }
8064
8069
  if (probeSession) {
8065
8070
  const failedRequests = probeSession.browserConsoleBuffer.filter(isActionableProbeFailure);
@@ -8124,7 +8129,7 @@ function validateNoGpuEncode(config) {
8124
8129
  if (config.useGpu === true) {
8125
8130
  throw new PlanValidationError(
8126
8131
  BROWSER_GPU_NOT_SOFTWARE,
8127
- "[planValidation] GPU encode is banned in distributed mode: config.useGpu === true. Distributed retries must be byte-identical, but NVENC/QSV/VAAPI produce different output across machines. Set useGpu=false (the default) \u2014 software libx264/libx265 is the only supported encoder in distributed mode."
8132
+ "[planValidation] GPU encode is banned in distributed mode: config.useGpu === true. Distributed retries must be byte-identical, but NVENC/QSV/VAAPI produce different output across machines. Set useGpu=false (the default): software libx264/libx265 is the only supported encoder in distributed mode."
8128
8133
  );
8129
8134
  }
8130
8135
  if (config.browserGpuMode !== void 0 && config.browserGpuMode !== "software") {
@@ -8171,6 +8176,7 @@ import {
8171
8176
  detectTransfer,
8172
8177
  extractAllVideoFrames,
8173
8178
  extractMediaMetadata as extractMediaMetadata3,
8179
+ isVideoPastTimelineEnd,
8174
8180
  isHdrColorSpace as isHdrColorSpace2,
8175
8181
  resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
8176
8182
  runVideoExtractionWithRetry,
@@ -8390,6 +8396,9 @@ async function runExtractVideosStage(input) {
8390
8396
  } = input;
8391
8397
  const stage2Start = Date.now();
8392
8398
  const extractionPolicy = resolveVideoExtractionPolicy();
8399
+ composition.videos = composition.videos.filter(
8400
+ (video) => !isVideoPastTimelineEnd(video, composition.duration)
8401
+ );
8393
8402
  let frameLookup = null;
8394
8403
  let extractionResult = null;
8395
8404
  let failureToEnforce = null;
@@ -8476,7 +8485,8 @@ async function runExtractVideosStage(input) {
8476
8485
  timelineEnd: composition.duration,
8477
8486
  maxTransientRetries: extractionPolicy.maxTransientRetries,
8478
8487
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
8479
- deferRangeExtraction
8488
+ deferRangeExtraction,
8489
+ contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)]
8480
8490
  },
8481
8491
  abortSignal,
8482
8492
  cfg,
@@ -11102,7 +11112,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
11102
11112
  const madeProgress = captureAttemptMadeProgress(frameCount, remainingCount);
11103
11113
  if (!madeProgress) {
11104
11114
  options.log.warn(
11105
- "[Render] Capture attempt made no forward progress; composition is likely structurally broken \u2014 not retrying.",
11115
+ "[Render] Capture attempt made no forward progress; composition is likely structurally broken, not retrying.",
11106
11116
  { attempt, frameCount, remainingCount, workers: currentWorkers }
11107
11117
  );
11108
11118
  }
@@ -11171,7 +11181,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
11171
11181
  }
11172
11182
  if (!madeProgress) {
11173
11183
  options.log.warn(
11174
- "[Render] Capture attempt made no forward progress; composition is likely structurally broken \u2014 not retrying.",
11184
+ "[Render] Capture attempt made no forward progress; composition is likely structurally broken, not retrying.",
11175
11185
  { attempt, frameCount, remainingCount, workers: currentWorkers }
11176
11186
  );
11177
11187
  }
@@ -11634,7 +11644,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
11634
11644
  execution.defer("remove workDir", () => {
11635
11645
  if (job.config.debug) return;
11636
11646
  if (job.status === "complete" && process.env.KEEP_TEMP === "1") {
11637
- log.info("KEEP_TEMP=1 \u2014 leaving workDir on disk for inspection", { workDir });
11647
+ log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
11638
11648
  return;
11639
11649
  }
11640
11650
  rmSync9(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
@@ -11871,7 +11881,7 @@ async function executeRenderPipeline(input) {
11871
11881
  captureForceScreenshot = true;
11872
11882
  updateCaptureObservability({ forceScreenshot: captureForceScreenshot });
11873
11883
  log.info(
11874
- "[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.",
11884
+ "[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.",
11875
11885
  { totalMemMb: getSystemTotalMb(), thresholdMb: LOW_MEMORY_TOTAL_MB_THRESHOLD }
11876
11886
  );
11877
11887
  }
@@ -12439,7 +12449,7 @@ async function executeRenderPipeline(input) {
12439
12449
  cfg.useDrawElement = false;
12440
12450
  deClampReason = workerCount > 1 ? "parallel" : "disk_path";
12441
12451
  log.info(
12442
- "[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."
12452
+ "[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."
12443
12453
  );
12444
12454
  if (probeSession && probeSession.captureMode === "drawelement") {
12445
12455
  lastBrowserConsole = probeSession.browserConsoleBuffer;
@@ -12535,7 +12545,7 @@ async function executeRenderPipeline(input) {
12535
12545
  });
12536
12546
  updateCaptureObservability({ forceScreenshot: captureForceScreenshot });
12537
12547
  log.info(
12538
- `[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.`
12548
+ `[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.`
12539
12549
  );
12540
12550
  }
12541
12551
  const useLayeredComposite = !usePageSideCompositingForTransitions && shouldUseLayeredComposite({
@@ -13032,7 +13042,7 @@ async function executeRenderPipeline(input) {
13032
13042
  cfg.useDrawElement = false;
13033
13043
  deClampReason = deClampReason ?? "disk_path";
13034
13044
  log.info(
13035
- "[Render] Fast capture: drawElement disabled for the disk fallback \u2014 streaming encoder spawn failed and the disk path has no runtime self-verification."
13045
+ "[Render] Fast capture: drawElement disabled for the disk fallback: streaming encoder spawn failed and the disk path has no runtime self-verification."
13036
13046
  );
13037
13047
  if (probeSession && probeSession.captureMode === "drawelement") {
13038
13048
  lastBrowserConsole = probeSession.browserConsoleBuffer;
@@ -13584,10 +13594,10 @@ function validateJsonSafeValue(value, field) {
13584
13594
  walkVariables(value, field, /* @__PURE__ */ new WeakSet());
13585
13595
  }
13586
13596
  var LEAF_REJECTIONS = {
13587
- undefined: "undefined leaves are silently dropped by JSON.stringify \u2014 use null if you mean an absent value",
13597
+ undefined: "undefined leaves are silently dropped by JSON.stringify. Use null if you mean an absent value",
13588
13598
  function: "functions are not JSON-serializable",
13589
13599
  symbol: "Symbols are not JSON-serializable",
13590
- bigint: "BigInt values throw at JSON.stringify \u2014 encode as a string if you need 64-bit integers"
13600
+ bigint: "BigInt values throw at JSON.stringify. Encode as a string if you need 64-bit integers"
13591
13601
  };
13592
13602
  function walkVariables(value, path, seen) {
13593
13603
  const t = typeof value;
@@ -13608,7 +13618,7 @@ function walkVariables(value, path, seen) {
13608
13618
  if (seen.has(value)) {
13609
13619
  throw new InvalidConfigError(
13610
13620
  path,
13611
- "circular reference detected \u2014 JSON.stringify cannot serialize cycles"
13621
+ "circular reference detected: JSON.stringify cannot serialize cycles"
13612
13622
  );
13613
13623
  }
13614
13624
  seen.add(value);
@@ -14299,7 +14309,7 @@ function validateOutputResolutionOverride(body) {
14299
14309
  return `Invalid outputResolution "${body.outputResolution}". Must be one of: landscape, portrait, landscape-4k, portrait-4k, square, square-4k (aliases: 1080p, 4k, \u2026).`;
14300
14310
  }
14301
14311
  if (normalized !== void 0 && (body.format === "webm" || body.format === "mov")) {
14302
- 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.`;
14312
+ 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.`;
14303
14313
  }
14304
14314
  return void 0;
14305
14315
  }
@@ -15024,7 +15034,7 @@ function collectPlanAssetShas(planDir) {
15024
15034
  }
15025
15035
  if (compositionHtml === null) {
15026
15036
  throw new Error(
15027
- `[freezePlan] compiled HTML missing at ${COMPILED_INDEX_RELATIVE_PATH} \u2014 upstream compile stage did not materialize the expected file.`
15037
+ `[freezePlan] compiled HTML missing at ${COMPILED_INDEX_RELATIVE_PATH} (upstream compile stage did not materialize the expected file).`
15028
15038
  );
15029
15039
  }
15030
15040
  return { compositionHtml, assets };
@@ -15607,7 +15617,7 @@ var PlanTooLargeError = class extends Error {
15607
15617
  const breakdownSuffix = breakdown ? ` Breakdown: video-frames=${formatBytes(breakdown.videoFramesBytes)}, compiled=${formatBytes(breakdown.compiledBytes)} (source-media=${formatBytes(breakdown.sourceMediaBytes)}), audio=${formatBytes(breakdown.audioBytes)}, metadata=${formatBytes(breakdown.metadataBytes)}, other=${formatBytes(breakdown.otherBytes)}, files=${breakdown.fileCount}.` : "";
15608
15618
  const observationSuffix = observedAt ? ` Observed at ${observedAt}.` : "";
15609
15619
  super(
15610
- `[plan] planDir size ${formatBytes(sizeBytes)} exceeds the configured ceiling ${formatBytes(limitBytes)} (PLAN_TOO_LARGE). The default 2 GB cap fits inside AWS Lambda's 10 GB /tmp budget alongside the chunk worker's frame buffer and ffmpeg's working set. To unblock: use the content-addressed \`planV2()\` transport, shorten the composition, lower the framerate, or use the in-process renderer (\`executeRenderJob\`) \u2014 it has no planDir size cap.` + observationSuffix + breakdownSuffix
15620
+ `[plan] planDir size ${formatBytes(sizeBytes)} exceeds the configured ceiling ${formatBytes(limitBytes)} (PLAN_TOO_LARGE). The default 2 GB cap fits inside AWS Lambda's 10 GB /tmp budget alongside the chunk worker's frame buffer and ffmpeg's working set. To unblock: use the content-addressed \`planV2()\` transport, shorten the composition, lower the framerate, or use the in-process renderer (\`executeRenderJob\`), which has no planDir size cap.` + observationSuffix + breakdownSuffix
15611
15621
  );
15612
15622
  this.name = "PlanTooLargeError";
15613
15623
  this.sizeBytes = sizeBytes;
@@ -15624,7 +15634,7 @@ var FormatNotSupportedInDistributedError = class extends Error {
15624
15634
  reason;
15625
15635
  constructor(format, reason) {
15626
15636
  super(
15627
- `[plan] format ${JSON.stringify(format)} is not supported in distributed mode: ${reason}. Render with the in-process renderer (\`executeRenderJob\`) \u2014 it has full format support \u2014 or pick a distributed-supported format: mp4 SDR, mov ProRes 4444, png-sequence, or webm VP9.`
15637
+ `[plan] format ${JSON.stringify(format)} is not supported in distributed mode: ${reason}. Render with the in-process renderer (\`executeRenderJob\`), which has full format support, or pick a distributed-supported format: mp4 SDR, mov ProRes 4444, png-sequence, or webm VP9.`
15628
15638
  );
15629
15639
  this.name = "FormatNotSupportedInDistributedError";
15630
15640
  this.format = format;
@@ -15713,7 +15723,7 @@ async function readFontSnapshotSha() {
15713
15723
  const data = module.EMBEDDED_FONT_DATA;
15714
15724
  if (!data || typeof data !== "object") {
15715
15725
  throw new Error(
15716
- "[plan] EMBEDDED_FONT_DATA missing from @hyperframes/core/fonts/embedded-data \u2014 was `bun run build:fonts` run?"
15726
+ "[plan] EMBEDDED_FONT_DATA missing from @hyperframes/core/fonts/embedded-data. Was `bun run build:fonts` run?"
15717
15727
  );
15718
15728
  }
15719
15729
  const dataObj = data;
@@ -15763,7 +15773,7 @@ function resolveEncoderTriple(config) {
15763
15773
  }
15764
15774
  if (config.codec !== void 0) {
15765
15775
  throw new Error(
15766
- `[plan] DistributedRenderConfig.codec is only valid for format="mp4"; received codec=${JSON.stringify(config.codec)} with format=${JSON.stringify(config.format)}. Omit codec for non-mp4 formats \u2014 mov is always ProRes 4444, webm is always libvpx-vp9, and png-sequence has no encoder.`
15776
+ `[plan] DistributedRenderConfig.codec is only valid for format="mp4"; received codec=${JSON.stringify(config.codec)} with format=${JSON.stringify(config.format)}. Omit codec for non-mp4 formats: mov is always ProRes 4444, webm is always libvpx-vp9, and png-sequence has no encoder.`
15767
15777
  );
15768
15778
  }
15769
15779
  return resolveNonMp4EncoderTriple(config.format, config.quality ?? "standard");