hyperframes 0.7.43 → 0.7.44

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/cli.js CHANGED
@@ -50,7 +50,7 @@ var VERSION;
50
50
  var init_version = __esm({
51
51
  "src/version.ts"() {
52
52
  "use strict";
53
- VERSION = true ? "0.7.43" : "0.0.0-dev";
53
+ VERSION = true ? "0.7.44" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -62953,6 +62953,7 @@ async function createCaptureSession(serverUrl, outputDir, options, onBeforeCaptu
62953
62953
  onBeforeCapture,
62954
62954
  isInitialized: false,
62955
62955
  browserConsoleBuffer: [],
62956
+ scriptLoadFailures: [],
62956
62957
  capturePerf: {
62957
62958
  frames: 0,
62958
62959
  seekMs: 0,
@@ -62993,15 +62994,6 @@ function formatConsoleDiagnostic(type, text2, locationUrl) {
62993
62994
  const prefix = isResourceLoadError ? "[non-blocking]" : type === "error" ? "[Browser:ERROR]" : type === "warn" ? "[Browser:WARN]" : "[Browser]";
62994
62995
  return { text: `${prefix} ${text2}`, suppressHostLog: false };
62995
62996
  }
62996
- async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100) {
62997
- const deadline = Date.now() + timeoutMs;
62998
- while (Date.now() < deadline) {
62999
- const ready = Boolean(await page.evaluate(expression));
63000
- if (ready) return true;
63001
- await new Promise((resolve62) => setTimeout(resolve62, intervalMs));
63002
- }
63003
- return Boolean(await page.evaluate(expression));
63004
- }
63005
62997
  function buildZeroDurationDiagnostic(diag) {
63006
62998
  const hints = [];
63007
62999
  if (!diag.hasPlayer) {
@@ -63058,7 +63050,7 @@ async function pollHfReady(page, timeoutMs, intervalMs = 100) {
63058
63050
  State: __hf=${diag.hasHf}, seek=${diag.hasSeek}, player=${diag.hasPlayer}, renderReady=${diag.renderReady}, duration=${diag.duration}`
63059
63051
  );
63060
63052
  }
63061
- async function pollSubCompositionTimelines(page, timeoutMs, intervalMs = 150) {
63053
+ async function pollSubCompositionTimelines(page, timeoutMs, intervalMs = 150, getScriptLoadFailures, scriptFailureGraceMs = 2e3) {
63062
63054
  const expression = `(function() {
63063
63055
  var hosts = document.querySelectorAll("[data-composition-id]");
63064
63056
  if (hosts.length === 0) return true;
@@ -63071,30 +63063,52 @@ async function pollSubCompositionTimelines(page, timeoutMs, intervalMs = 150) {
63071
63063
  }
63072
63064
  return true;
63073
63065
  })()`;
63074
- const ready = await pollPageExpression(page, expression, timeoutMs, intervalMs);
63066
+ const start = Date.now();
63067
+ const deadline = start + timeoutMs;
63068
+ let ready = false;
63069
+ let scriptFailureBail = false;
63070
+ for (; ; ) {
63071
+ ready = Boolean(await page.evaluate(expression));
63072
+ if (ready) break;
63073
+ const now = Date.now();
63074
+ if (now >= deadline) break;
63075
+ const failures = getScriptLoadFailures?.() ?? [];
63076
+ if (failures.length > 0 && now - start >= scriptFailureGraceMs) {
63077
+ scriptFailureBail = true;
63078
+ console.warn(
63079
+ `[FrameCapture] Sub-composition timeline wait cut short after ${now - start}ms: script resource(s) failed to load (${failures.join(", ")}) \u2014 the timeline registration they carry can never arrive. Fix the script reference; the render proceeds without those animations.`
63080
+ );
63081
+ break;
63082
+ }
63083
+ await new Promise((resolve62) => setTimeout(resolve62, intervalMs));
63084
+ }
63075
63085
  if (ready) {
63076
63086
  await page.evaluate(`(function() {
63077
63087
  if (typeof window.__hfForceTimelineRebind === "function") {
63078
63088
  window.__hfForceTimelineRebind();
63079
63089
  }
63080
63090
  })()`);
63091
+ return "ready";
63081
63092
  }
63082
- if (!ready) {
63083
- const missing = await page.evaluate(`(function() {
63084
- var hosts = document.querySelectorAll("[data-composition-id]");
63085
- var timelines = window.__timelines || {};
63086
- var m = [];
63087
- for (var i = 0; i < hosts.length; i++) {
63088
- if (hosts[i].hasAttribute("data-no-timeline")) continue;
63089
- var id = hosts[i].getAttribute("data-composition-id");
63090
- if (id && !timelines[id]) m.push(id);
63091
- }
63092
- return m.join(", ");
63093
- })()`);
63093
+ const missing = await page.evaluate(`(function() {
63094
+ var hosts = document.querySelectorAll("[data-composition-id]");
63095
+ var timelines = window.__timelines || {};
63096
+ var m = [];
63097
+ for (var i = 0; i < hosts.length; i++) {
63098
+ if (hosts[i].hasAttribute("data-no-timeline")) continue;
63099
+ var id = hosts[i].getAttribute("data-composition-id");
63100
+ if (id && !timelines[id]) m.push(id);
63101
+ }
63102
+ return m.join(", ");
63103
+ })()`);
63104
+ if (scriptFailureBail) {
63105
+ console.warn(`[FrameCapture] Composition(s) still waiting on the failed script: ${missing}.`);
63106
+ } else {
63094
63107
  console.warn(
63095
63108
  `[FrameCapture] Sub-composition timelines not registered after ${timeoutMs}ms: ${missing}. Compositions that load data asynchronously (e.g. fetch) must register window.__timelines[id] after setup completes. Compositions intentionally driven without GSAP timelines (CSS animations / rAF) can mark the host with data-no-timeline to skip this wait.`
63096
63109
  );
63097
63110
  }
63111
+ return scriptFailureBail ? "script_failure" : "timeout";
63098
63112
  }
63099
63113
  async function pollVideosReady(page, skipIds, timeoutMs, intervalMs = 100) {
63100
63114
  const check = async () => {
@@ -63193,6 +63207,11 @@ async function waitForOptionalTailwindReady(page, timeoutMs) {
63193
63207
  );
63194
63208
  }
63195
63209
  }
63210
+ function recordScriptLoadFailure(session, url) {
63211
+ if (!session.scriptLoadFailures.includes(url)) {
63212
+ session.scriptLoadFailures.push(url);
63213
+ }
63214
+ }
63196
63215
  async function initializeSession(session) {
63197
63216
  const { page, serverUrl } = session;
63198
63217
  page.on("console", (msg) => {
@@ -63213,6 +63232,9 @@ async function initializeSession(session) {
63213
63232
  appendBrowserDiagnostic(session, text2);
63214
63233
  });
63215
63234
  page.on("requestfailed", (request) => {
63235
+ if (request.resourceType() === "script") {
63236
+ recordScriptLoadFailure(session, request.url());
63237
+ }
63216
63238
  appendBrowserDiagnostic(
63217
63239
  session,
63218
63240
  formatRequestFailureDiagnostic({
@@ -63227,6 +63249,9 @@ async function initializeSession(session) {
63227
63249
  const status = response.status();
63228
63250
  if (status < 400) return;
63229
63251
  const request = response.request();
63252
+ if (request.resourceType() === "script") {
63253
+ recordScriptLoadFailure(session, response.url());
63254
+ }
63230
63255
  appendBrowserDiagnostic(
63231
63256
  session,
63232
63257
  formatHttpErrorDiagnostic({
@@ -63278,8 +63303,13 @@ async function initializeSession(session) {
63278
63303
  const pageReadyTimeout2 = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG2.playerReadyTimeout;
63279
63304
  await pollHfReady(page, pageReadyTimeout2);
63280
63305
  logInitPhase("pollHfReady complete");
63281
- await pollSubCompositionTimelines(page, pageReadyTimeout2);
63282
- logInitPhase("pollSubCompositionTimelines complete");
63306
+ session.subTimelineWaitOutcome = await pollSubCompositionTimelines(
63307
+ page,
63308
+ pageReadyTimeout2,
63309
+ void 0,
63310
+ () => session.scriptLoadFailures
63311
+ );
63312
+ logInitPhase(`pollSubCompositionTimelines complete (${session.subTimelineWaitOutcome})`);
63283
63313
  await applyVideoMetadataHints(page, session.options.videoMetadataHints);
63284
63314
  logInitPhase("applyVideoMetadataHints complete");
63285
63315
  const skipVideoIds = session.options.skipReadinessVideoIds ?? [];
@@ -63371,8 +63401,13 @@ async function initializeSession(session) {
63371
63401
  warmupState.running = false;
63372
63402
  throw err;
63373
63403
  }
63374
- await pollSubCompositionTimelines(page, pageReadyTimeout);
63375
- logInitPhase("pollSubCompositionTimelines complete");
63404
+ session.subTimelineWaitOutcome = await pollSubCompositionTimelines(
63405
+ page,
63406
+ pageReadyTimeout,
63407
+ void 0,
63408
+ () => session.scriptLoadFailures
63409
+ );
63410
+ logInitPhase(`pollSubCompositionTimelines complete (${session.subTimelineWaitOutcome})`);
63376
63411
  await applyVideoMetadataHints(page, session.options.videoMetadataHints);
63377
63412
  logInitPhase("applyVideoMetadataHints complete");
63378
63413
  const bfSkipVideoIds = session.options.skipReadinessVideoIds ?? [];
@@ -63899,9 +63934,20 @@ async function captureFrameToBufferPipelined(session, frameIndex, time) {
63899
63934
  const { page, options } = session;
63900
63935
  const startTime = Date.now();
63901
63936
  if (session.staticFrames?.has(frameIndex) && session.lastEncodeResult) {
63902
- session.staticDedupCount = (session.staticDedupCount ?? 0) + 1;
63903
- session.capturePerf.frames += 1;
63904
- return { encodeResult: session.lastEncodeResult, captureTimeMs: Date.now() - startTime };
63937
+ const lastIdx = session.lastEncodeResultFrame ?? frameIndex - 1;
63938
+ let gapStatic = true;
63939
+ for (let j3 = lastIdx + 1; j3 <= frameIndex; j3++) {
63940
+ if (!session.staticFrames.has(j3)) {
63941
+ gapStatic = false;
63942
+ break;
63943
+ }
63944
+ }
63945
+ if (gapStatic) {
63946
+ session.staticDedupCount = (session.staticDedupCount ?? 0) + 1;
63947
+ session.capturePerf.frames += 1;
63948
+ session.lastEncodeResultFrame = frameIndex;
63949
+ return { encodeResult: session.lastEncodeResult, captureTimeMs: Date.now() - startTime };
63950
+ }
63905
63951
  }
63906
63952
  try {
63907
63953
  const { quantizedTime, seekMs, beforeCaptureMs } = await prepareFrameForCapture(
@@ -63921,7 +63967,10 @@ async function captureFrameToBufferPipelined(session, frameIndex, time) {
63921
63967
  session.capturePerf.frameMs.push(boundaryMs);
63922
63968
  }
63923
63969
  const boundaryResult = Promise.resolve(buffer);
63924
- if (session.staticFrames) session.lastEncodeResult = boundaryResult;
63970
+ if (session.staticFrames) {
63971
+ session.lastEncodeResult = boundaryResult;
63972
+ session.lastEncodeResultFrame = frameIndex;
63973
+ }
63925
63974
  return { encodeResult: boundaryResult, captureTimeMs: Date.now() - startTime };
63926
63975
  }
63927
63976
  const { encodeResult } = await produceDrawElementFrame(
@@ -63938,7 +63987,10 @@ async function captureFrameToBufferPipelined(session, frameIndex, time) {
63938
63987
  session.capturePerf.screenshotMs += captureTimeMs - seekMs - beforeCaptureMs;
63939
63988
  session.capturePerf.totalMs += captureTimeMs;
63940
63989
  session.capturePerf.frameMs.push(captureTimeMs);
63941
- if (session.staticFrames) session.lastEncodeResult = encodeResult;
63990
+ if (session.staticFrames) {
63991
+ session.lastEncodeResult = encodeResult;
63992
+ session.lastEncodeResultFrame = frameIndex;
63993
+ }
63942
63994
  return { encodeResult, captureTimeMs };
63943
63995
  } catch (captureError) {
63944
63996
  if (isNoCachedPaintRecordError(captureError)) {
@@ -64164,6 +64216,7 @@ function getCapturePerfSummary(session) {
64164
64216
  avgBeforeCaptureMs: Math.round(session.capturePerf.beforeCaptureMs / frames),
64165
64217
  avgScreenshotMs: Math.round(session.capturePerf.screenshotMs / frames),
64166
64218
  p50TotalMs: medianOf(session.capturePerf.frameMs),
64219
+ subTimelineWaitOutcome: session.subTimelineWaitOutcome,
64167
64220
  staticDedupReused: session.staticDedupCount ?? 0,
64168
64221
  staticDedupEnabled: session.staticDedupEnabled ?? false,
64169
64222
  // armed ⟺ a non-empty static set survived verification; predicted === its size.
@@ -65634,40 +65687,57 @@ import { existsSync as existsSync7, mkdirSync as mkdirSync4, statSync as statSyn
65634
65687
  import { dirname as dirname5 } from "path";
65635
65688
  function createFrameReorderBuffer(startFrame, endFrame) {
65636
65689
  let cursor = startFrame;
65690
+ let aborted = null;
65637
65691
  const pending = /* @__PURE__ */ new Map();
65638
- const enqueueAt = (frame, resolve62) => {
65692
+ const enqueueAt = (frame, resolve62, reject) => {
65639
65693
  const list = pending.get(frame);
65640
65694
  if (list === void 0) {
65641
- pending.set(frame, [resolve62]);
65695
+ pending.set(frame, [{ resolve: resolve62, reject }]);
65642
65696
  } else {
65643
- list.push(resolve62);
65697
+ list.push({ resolve: resolve62, reject });
65644
65698
  }
65645
65699
  };
65646
65700
  const flushAt = (frame) => {
65647
65701
  const list = pending.get(frame);
65648
65702
  if (list === void 0) return;
65649
65703
  pending.delete(frame);
65650
- for (const resolve62 of list) resolve62();
65704
+ for (const waiter of list) waiter.resolve();
65651
65705
  };
65652
- const waitForFrame = (frame) => new Promise((resolve62) => {
65706
+ const waitForFrame = (frame) => new Promise((resolve62, reject) => {
65707
+ if (aborted) {
65708
+ reject(aborted);
65709
+ return;
65710
+ }
65653
65711
  if (frame === cursor) {
65654
65712
  resolve62();
65655
65713
  return;
65656
65714
  }
65657
- enqueueAt(frame, resolve62);
65715
+ enqueueAt(frame, resolve62, reject);
65658
65716
  });
65659
65717
  const advanceTo = (frame) => {
65660
65718
  cursor = frame;
65661
65719
  flushAt(frame);
65662
65720
  };
65663
- const waitForAllDone = () => new Promise((resolve62) => {
65721
+ const waitForAllDone = () => new Promise((resolve62, reject) => {
65722
+ if (aborted) {
65723
+ reject(aborted);
65724
+ return;
65725
+ }
65664
65726
  if (cursor >= endFrame) {
65665
65727
  resolve62();
65666
65728
  return;
65667
65729
  }
65668
- enqueueAt(endFrame, resolve62);
65730
+ enqueueAt(endFrame, resolve62, reject);
65669
65731
  });
65670
- return { waitForFrame, advanceTo, waitForAllDone };
65732
+ const abort = (err) => {
65733
+ if (aborted) return;
65734
+ aborted = err;
65735
+ for (const [frame, list] of pending) {
65736
+ pending.delete(frame);
65737
+ for (const waiter of list) waiter.reject(err);
65738
+ }
65739
+ };
65740
+ return { waitForFrame, advanceTo, waitForAllDone, abort };
65671
65741
  }
65672
65742
  function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
65673
65743
  const {
@@ -76185,19 +76255,79 @@ function distributeFrames(totalFrames, workerCount, workDir, rangeStart = 0) {
76185
76255
  }
76186
76256
  return tasks;
76187
76257
  }
76258
+ function distributeFramesInterleaved(totalFrames, workerCount, workDir, rangeStart = 0) {
76259
+ const tasks = [];
76260
+ for (let i2 = 0; i2 < workerCount && i2 < totalFrames; i2++) {
76261
+ tasks.push({
76262
+ workerId: i2,
76263
+ startFrame: rangeStart + i2,
76264
+ endFrame: rangeStart + totalFrames,
76265
+ frameStride: workerCount,
76266
+ outputDir: join15(workDir, `worker-${i2}`),
76267
+ outputFrameOffset: rangeStart
76268
+ });
76269
+ }
76270
+ return tasks;
76271
+ }
76188
76272
  function shouldVerifyWorkerGpu(workerId, config) {
76189
76273
  return config?.browserGpuMode === "software" && workerId === 0;
76190
76274
  }
76191
76275
  async function captureFrameRange(session, task, captureOptions, signal, onFrameCaptured, onFrameBuffer) {
76192
76276
  let framesCaptured = 0;
76193
76277
  const outputOffset = task.outputFrameOffset ?? 0;
76194
- for (let i2 = task.startFrame; i2 < task.endFrame; i2++) {
76278
+ const stride = task.frameStride ?? 1;
76279
+ if (onFrameBuffer && session.workerEncodeEnabled) {
76280
+ const dbg = process.env.HF_DE_PAR_DEBUG === "1";
76281
+ const dbgT0 = Date.now();
76282
+ const dbgWin = 40 * stride;
76283
+ let prev = null;
76284
+ for (let i2 = task.startFrame; i2 < task.endFrame; i2 += stride) {
76285
+ if (signal?.aborted) throw new Error("Parallel worker cancelled");
76286
+ const time = i2 * captureOptions.fps.den / captureOptions.fps.num;
76287
+ if (dbg && i2 < task.startFrame + dbgWin) {
76288
+ console.log(`[par:w${task.workerId}] +${Date.now() - dbgT0}ms produce ${i2} start`);
76289
+ }
76290
+ const { encodeResult } = await captureFrameToBufferPipelined(session, i2 - outputOffset, time);
76291
+ encodeResult.catch(() => {
76292
+ });
76293
+ if (dbg && i2 < task.startFrame + dbgWin) {
76294
+ console.log(`[par:w${task.workerId}] +${Date.now() - dbgT0}ms produce ${i2} kicked`);
76295
+ }
76296
+ if (prev) {
76297
+ if (dbg && prev.idx < task.startFrame + dbgWin) {
76298
+ console.log(
76299
+ `[par:w${task.workerId}] +${Date.now() - dbgT0}ms drain ${prev.idx} await-encode`
76300
+ );
76301
+ }
76302
+ const buf = await prev.encodeResult;
76303
+ if (dbg && prev.idx < task.startFrame + dbgWin) {
76304
+ console.log(
76305
+ `[par:w${task.workerId}] +${Date.now() - dbgT0}ms drain ${prev.idx} encoded ${buf.length}B`
76306
+ );
76307
+ }
76308
+ await onFrameBuffer(prev.idx, buf, session);
76309
+ if (dbg && prev.idx < task.startFrame + dbgWin) {
76310
+ console.log(`[par:w${task.workerId}] +${Date.now() - dbgT0}ms drain ${prev.idx} written`);
76311
+ }
76312
+ framesCaptured++;
76313
+ if (onFrameCaptured) onFrameCaptured(task.workerId, prev.idx);
76314
+ }
76315
+ prev = { idx: i2, encodeResult };
76316
+ }
76317
+ if (prev) {
76318
+ await onFrameBuffer(prev.idx, await prev.encodeResult, session);
76319
+ framesCaptured++;
76320
+ if (onFrameCaptured) onFrameCaptured(task.workerId, prev.idx);
76321
+ }
76322
+ return framesCaptured;
76323
+ }
76324
+ for (let i2 = task.startFrame; i2 < task.endFrame; i2 += stride) {
76195
76325
  if (signal?.aborted) throw new Error("Parallel worker cancelled");
76196
76326
  const time = i2 * captureOptions.fps.den / captureOptions.fps.num;
76197
76327
  const fileFrameIdx = i2 - outputOffset;
76198
76328
  if (onFrameBuffer) {
76199
76329
  const { buffer } = await captureFrameToBuffer(session, fileFrameIdx, time);
76200
- await onFrameBuffer(i2, buffer);
76330
+ await onFrameBuffer(i2, buffer, session);
76201
76331
  } else {
76202
76332
  await captureFrame(session, fileFrameIdx, time);
76203
76333
  }
@@ -76228,10 +76358,18 @@ async function executeWorkerTask(task, serverUrl, captureOptions, createBeforeCa
76228
76358
  createBeforeCaptureHook(),
76229
76359
  workerConfig
76230
76360
  );
76361
+ if (process.env.HF_DE_PAR_DEBUG === "1") {
76362
+ console.log(`[par:w${task.workerId}] session created`);
76363
+ }
76231
76364
  if (shouldVerifyWorkerGpu(task.workerId, workerConfig)) {
76232
76365
  await assertSwiftShader(session.page, readWebGlVendorInfoFromCanvas);
76233
76366
  }
76234
76367
  await initializeSession(session);
76368
+ if (process.env.HF_DE_PAR_DEBUG === "1") {
76369
+ console.log(
76370
+ `[par:w${task.workerId}] init done (mode=${session.captureMode} workerEncode=${session.workerEncodeEnabled === true})`
76371
+ );
76372
+ }
76235
76373
  framesCaptured = await captureFrameRange(
76236
76374
  session,
76237
76375
  task,
@@ -76268,7 +76406,10 @@ async function executeWorkerTask(task, serverUrl, captureOptions, createBeforeCa
76268
76406
  }
76269
76407
  }
76270
76408
  async function executeParallelCapture(serverUrl, workDir, tasks, captureOptions, createBeforeCaptureHook, signal, onProgress, onFrameBuffer, config) {
76271
- const totalFrames = tasks.reduce((sum, t2) => sum + (t2.endFrame - t2.startFrame), 0);
76409
+ const totalFrames = tasks.reduce(
76410
+ (sum, t2) => sum + Math.ceil((t2.endFrame - t2.startFrame) / (t2.frameStride ?? 1)),
76411
+ 0
76412
+ );
76272
76413
  const workerProgress = /* @__PURE__ */ new Map();
76273
76414
  for (const task of tasks) workerProgress.set(task.workerId, 0);
76274
76415
  const onFrameCaptured = (workerId, _frameIndex) => {
@@ -77960,6 +78101,7 @@ __export(src_exports, {
77960
78101
  detectTransfer: () => detectTransfer,
77961
78102
  discardWarmupCapture: () => discardWarmupCapture,
77962
78103
  distributeFrames: () => distributeFrames,
78104
+ distributeFramesInterleaved: () => distributeFramesInterleaved,
77963
78105
  downloadToTemp: () => downloadToTemp,
77964
78106
  drainBrowserPool: () => drainBrowserPool,
77965
78107
  encodeFramesChunkedConcat: () => encodeFramesChunkedConcat,
@@ -78634,6 +78776,7 @@ __export(events_exports, {
78634
78776
  });
78635
78777
  function renderObservabilityEventProperties(props) {
78636
78778
  return {
78779
+ sub_timeline_wait: props.subTimelineWait,
78637
78780
  observability_render_job_id: props.observabilityRenderJobId,
78638
78781
  observability_composition_hash: props.observabilityCompositionHash,
78639
78782
  observability_event_count: props.observabilityEventCount,
@@ -83465,6 +83608,7 @@ var init_skills = __esm({
83465
83608
  var transcribe_exports = {};
83466
83609
  __export(transcribe_exports, {
83467
83610
  detectSpeechOnset: () => detectSpeechOnset,
83611
+ dtwPresetForModel: () => dtwPresetForModel,
83468
83612
  transcribe: () => transcribe
83469
83613
  });
83470
83614
  import { execFileSync as execFileSync5 } from "child_process";
@@ -83598,6 +83742,9 @@ function prepareAudio(audioPath) {
83598
83742
  );
83599
83743
  return wavPath;
83600
83744
  }
83745
+ function dtwPresetForModel(model) {
83746
+ return model.replace(/-/g, ".");
83747
+ }
83601
83748
  async function transcribe(inputPath, outputDir, options) {
83602
83749
  const model = options?.model ?? DEFAULT_MODEL;
83603
83750
  options?.onProgress?.("Checking whisper...");
@@ -83649,7 +83796,7 @@ async function transcribe(inputPath, outputDir, options) {
83649
83796
  "--output-file",
83650
83797
  outputBase,
83651
83798
  "--dtw",
83652
- effectiveModel,
83799
+ dtwPresetForModel(effectiveModel),
83653
83800
  "--suppress-nst"
83654
83801
  ];
83655
83802
  if (detectedLanguage) {
@@ -84619,9 +84766,12 @@ function renderObservabilityTelemetryPayload(observability) {
84619
84766
  };
84620
84767
  }
84621
84768
  function renderJobObservabilityTelemetryPayload(job) {
84622
- return renderObservabilityTelemetryPayload(
84623
- job?.errorDetails?.observability ?? job?.perfSummary?.observability
84624
- );
84769
+ return {
84770
+ ...renderObservabilityTelemetryPayload(
84771
+ job?.errorDetails?.observability ?? job?.perfSummary?.observability
84772
+ ),
84773
+ subTimelineWait: job?.errorDetails?.subTimelineWait ?? job?.perfSummary?.subTimelineWait
84774
+ };
84625
84775
  }
84626
84776
  var init_renderObservability = __esm({
84627
84777
  "src/telemetry/renderObservability.ts"() {
@@ -99988,7 +100138,8 @@ function buildRenderErrorDetails(input2) {
99988
100138
  browserConsoleTail: input2.lastBrowserConsole.length > 0 ? input2.lastBrowserConsole.slice(-30) : void 0,
99989
100139
  perfStages: Object.keys(input2.perfStages).length > 0 ? { ...input2.perfStages } : void 0,
99990
100140
  hdrDiagnostics: input2.hdrDiagnostics.videoExtractionFailures > 0 || input2.hdrDiagnostics.imageDecodeFailures > 0 ? { ...input2.hdrDiagnostics } : void 0,
99991
- observability: input2.observability
100141
+ observability: input2.observability,
100142
+ subTimelineWait: input2.subTimelineWait
99992
100143
  };
99993
100144
  }
99994
100145
  var init_cleanup = __esm({
@@ -100168,6 +100319,13 @@ var init_hdrPerf = __esm({
100168
100319
  });
100169
100320
 
100170
100321
  // ../producer/src/services/render/perfSummary.ts
100322
+ function worstSubTimelineWaitOutcome(perfs) {
100323
+ const outcomes = perfs.map((p2) => p2.subTimelineWaitOutcome).filter((o) => !!o);
100324
+ if (outcomes.length === 0) return void 0;
100325
+ if (outcomes.includes("script_failure")) return "script_failure";
100326
+ if (outcomes.includes("timeout")) return "timeout";
100327
+ return "ready";
100328
+ }
100171
100329
  function pushWorkerDedupPerfs(results, sink) {
100172
100330
  for (const r2 of results) {
100173
100331
  if (r2.perf) sink.push(r2.perf);
@@ -100249,6 +100407,7 @@ function buildRenderPerfSummary(input2) {
100249
100407
  captureAvgMs: input2.totalFrames > 0 ? Math.round(
100250
100408
  (input2.perfStages.captureFrameMs ?? input2.perfStages.captureMs ?? 0) / input2.totalFrames
100251
100409
  ) : void 0,
100410
+ subTimelineWait: worstSubTimelineWaitOutcome(input2.dedupPerfs),
100252
100411
  captureP50Ms: (() => {
100253
100412
  const withSamples = input2.dedupPerfs.filter((p2) => (p2.p50TotalMs ?? 0) > 0);
100254
100413
  if (withSamples.length === 0) return void 0;
@@ -108231,9 +108390,8 @@ async function psnrDb(a, b2) {
108231
108390
  });
108232
108391
  }
108233
108392
  }
108234
- async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEncoder, reorderBuffer, assertNotAborted, onProgress, log2, stats) {
108235
- let prev = null;
108236
- const frameTime = (i2) => i2 * job.config.fps.den / job.config.fps.num;
108393
+ function createDrainFrameGuard(args) {
108394
+ const { log: log2, stats, frameTime } = args;
108237
108395
  const verifyMinDbRaw = Number(process.env.HF_DE_VERIFY_MIN_DB ?? "32");
108238
108396
  const verifyMinDb = Number.isFinite(verifyMinDbRaw) && verifyMinDbRaw >= 10 && verifyMinDbRaw <= 60 ? verifyMinDbRaw : 32;
108239
108397
  if (process.env.HF_DE_VERIFY_MIN_DB !== void 0 && verifyMinDb !== verifyMinDbRaw) {
@@ -108250,7 +108408,7 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
108250
108408
  return Math.max(absFloor, median * 0.12);
108251
108409
  };
108252
108410
  let acceptedSmall = null;
108253
- const guardFrame = async (idx, buf) => {
108411
+ return async (session, idx, buf) => {
108254
108412
  if (process.env.HF_FORCE_DRAWELEMENT !== "1") {
108255
108413
  const floor = blankFloor();
108256
108414
  if (floor > 0 && buf.length < floor && acceptedSmall?.equals(buf)) {
@@ -108325,6 +108483,12 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
108325
108483
  }
108326
108484
  return buf;
108327
108485
  };
108486
+ }
108487
+ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEncoder, reorderBuffer, assertNotAborted, onProgress, log2, stats) {
108488
+ let prev = null;
108489
+ const frameTime = (i2) => i2 * job.config.fps.den / job.config.fps.num;
108490
+ const guard = createDrainFrameGuard({ log: log2, stats, frameTime });
108491
+ const guardFrame = (idx, buf) => guard(session, idx, buf);
108328
108492
  const drainPrev = async () => {
108329
108493
  if (!prev) return;
108330
108494
  assertNotAborted();
@@ -108451,37 +108615,81 @@ async function runCaptureStreamingStage(input2) {
108451
108615
  try {
108452
108616
  const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
108453
108617
  if (workerCount > 1) {
108454
- const tasks = distributeFrames(totalFrames, workerCount, workDir);
108455
- const onFrameBuffer = async (frameIndex, buffer) => {
108456
- await reorderBuffer.waitForFrame(frameIndex);
108457
- ensureFrameWritten(await currentEncoder.writeFrame(buffer), frameIndex, currentEncoder);
108458
- reorderBuffer.advanceTo(frameIndex + 1);
108618
+ const deParallelStream = process.env.HF_DE_PARALLEL_STREAM === "true";
108619
+ const tasks = deParallelStream ? distributeFramesInterleaved(totalFrames, workerCount, workDir) : distributeFrames(totalFrames, workerCount, workDir);
108620
+ const parallelStats = {
108621
+ verifyChecked: 0,
108622
+ blankSuspects: 0,
108623
+ blankDeterministicAccepts: 0,
108624
+ blankRecaptures: 0
108459
108625
  };
108460
- const workerResults = await executeParallelCapture(
108461
- fileServer.url,
108462
- workDir,
108463
- tasks,
108464
- buildCaptureOptions(),
108465
- createRenderVideoFrameInjector,
108466
- abortSignal,
108467
- (progress) => {
108468
- job.framesRendered = progress.capturedFrames;
108469
- const frameProgress = progress.capturedFrames / progress.totalFrames;
108470
- const progressPct = 25 + frameProgress * 55;
108471
- if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
108472
- updateJobStatus(
108473
- job,
108474
- "rendering",
108475
- `Streaming frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
108476
- Math.round(progressPct),
108477
- onProgress
108478
- );
108626
+ const parallelGuard = createDrainFrameGuard({
108627
+ log: log2,
108628
+ stats: parallelStats,
108629
+ frameTime: (i2) => i2 * job.config.fps.den / job.config.fps.num
108630
+ });
108631
+ let parallelGuardRan = false;
108632
+ let parallelDrainError = null;
108633
+ const onFrameBuffer = async (frameIndex, buffer, workerSession) => {
108634
+ try {
108635
+ if (deParallelStream && workerSession.captureMode === "drawelement") {
108636
+ parallelGuardRan = true;
108637
+ buffer = await parallelGuard(workerSession, frameIndex, buffer);
108479
108638
  }
108480
- },
108481
- onFrameBuffer,
108482
- captureCfg
108483
- );
108639
+ await reorderBuffer.waitForFrame(frameIndex);
108640
+ ensureFrameWritten(await currentEncoder.writeFrame(buffer), frameIndex, currentEncoder);
108641
+ reorderBuffer.advanceTo(frameIndex + 1);
108642
+ } catch (err) {
108643
+ const e3 = err instanceof Error ? err : new Error(String(err));
108644
+ if (!parallelDrainError) {
108645
+ parallelDrainError = e3;
108646
+ reorderBuffer.abort(e3);
108647
+ }
108648
+ throw e3;
108649
+ }
108650
+ };
108651
+ let workerResults;
108652
+ try {
108653
+ workerResults = await executeParallelCapture(
108654
+ fileServer.url,
108655
+ workDir,
108656
+ tasks,
108657
+ buildCaptureOptions(),
108658
+ createRenderVideoFrameInjector,
108659
+ abortSignal,
108660
+ (progress) => {
108661
+ job.framesRendered = progress.capturedFrames;
108662
+ const frameProgress = progress.capturedFrames / progress.totalFrames;
108663
+ const progressPct = 25 + frameProgress * 55;
108664
+ if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
108665
+ updateJobStatus(
108666
+ job,
108667
+ "rendering",
108668
+ `Streaming frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
108669
+ Math.round(progressPct),
108670
+ onProgress
108671
+ );
108672
+ }
108673
+ },
108674
+ onFrameBuffer,
108675
+ // Interleaved DE workers each need their own browser PROCESS:
108676
+ // pages co-tenant in one browser share one compositor, whose
108677
+ // internal frame scheduling deprioritizes non-active pages — their
108678
+ // canvas `paint` events starve and the drawElement paint-wait slows
108679
+ // ~3x (measured: 86s vs 30s on a 3,245-frame rAF comp). This is
108680
+ // Chromium's internal frame-production scheduling on ALL platforms,
108681
+ // NOT the Linux-only HeadlessExperimental.beginFrame capture mode.
108682
+ deParallelStream ? { ...captureCfg, enableBrowserPool: false } : captureCfg
108683
+ );
108684
+ } catch (err) {
108685
+ if (parallelDrainError) throw parallelDrainError;
108686
+ throw err;
108687
+ }
108688
+ if (parallelDrainError) throw parallelDrainError;
108484
108689
  pushWorkerDedupPerfs(workerResults, dedupPerfs);
108690
+ if (parallelGuardRan) {
108691
+ deDrainStats = parallelStats;
108692
+ }
108485
108693
  if (probeSession) {
108486
108694
  captureBeyondViewport = probeSession.options.captureBeyondViewport;
108487
108695
  lastBrowserConsole = probeSession.browserConsoleBuffer;
@@ -110435,6 +110643,7 @@ function shouldUseStreamingEncode(cfg, outputFormat, workerCount, durationSecond
110435
110643
  if (outputFormat === "gif") return false;
110436
110644
  if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return false;
110437
110645
  if (durationSeconds > cfg.streamingEncodeMaxDurationSeconds) return false;
110646
+ if (process.env.HF_DE_PARALLEL_STREAM === "true") return true;
110438
110647
  return workerCount === 1;
110439
110648
  }
110440
110649
  function shouldPreferSingleWorkerDrawElement(args) {
@@ -110529,6 +110738,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110529
110738
  captureObservability.captureMode = captureObservability.forceScreenshot ? "screenshot" : "beginframe";
110530
110739
  };
110531
110740
  const captureAttempts = [];
110741
+ const dedupPerfs = [];
110532
110742
  const recordTransientRetryObservability = () => {
110533
110743
  const count = captureAttempts.filter((a) => a.reason === "transient-retry").length;
110534
110744
  if (count > 0) updateCaptureObservability({ transientRetries: count });
@@ -110883,7 +111093,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110883
111093
  layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
110884
111094
  supersampling: deviceScaleFactor > 1,
110885
111095
  probeDeGated: probeSession !== null && probeSession.captureMode !== "drawelement" && !probeSession.deInitDeferred,
110886
- experimentalParallelDeOptIn: process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true"
111096
+ experimentalParallelDeOptIn: process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true" || // Verified parallel DE streaming (opt-in) wants its parallelism kept.
111097
+ process.env.HF_DE_PARALLEL_STREAM === "true"
110887
111098
  });
110888
111099
  if (job.config.workers === void 0 && totalFrames >= 60 && !htmlInCanvasDetected && !cfg.lowMemoryMode && !deInversionEligible) {
110889
111100
  const outcome = await observeRenderStage(
@@ -110960,7 +111171,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110960
111171
  durationSeconds: job.duration,
110961
111172
  maxDurationSeconds: cfg.streamingEncodeMaxDurationSeconds
110962
111173
  });
110963
- if (cfg.useDrawElement && process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE !== "true" && (!useStreamingEncode || workerCount > 1)) {
111174
+ const deParallelStreamVerified = process.env.HF_DE_PARALLEL_STREAM === "true" && useStreamingEncode && workerCount > 1;
111175
+ if (cfg.useDrawElement && process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE !== "true" && (!useStreamingEncode || workerCount > 1) && !deParallelStreamVerified) {
110964
111176
  cfg.useDrawElement = false;
110965
111177
  deClampReason = workerCount > 1 ? "parallel" : "disk_path";
110966
111178
  log2.info(
@@ -110971,7 +111183,6 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110971
111183
  probeSession = null;
110972
111184
  }
110973
111185
  }
110974
- const dedupPerfs = [];
110975
111186
  const FORMAT_EXT3 = {
110976
111187
  mp4: ".mp4",
110977
111188
  webm: ".webm",
@@ -111437,7 +111648,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111437
111648
  lastBrowserConsole,
111438
111649
  perfStages,
111439
111650
  hdrDiagnostics,
111440
- observability: observabilitySummary
111651
+ observability: observabilitySummary,
111652
+ subTimelineWait: worstSubTimelineWaitOutcome(dedupPerfs)
111441
111653
  });
111442
111654
  log2.info("[Render] Failure summary", {
111443
111655
  failedStage: job.currentStage,
@@ -114313,7 +114525,9 @@ async function findFromHyperframesCache() {
114313
114525
  );
114314
114526
  installed = [];
114315
114527
  }
114316
- const match = installed.find((b2) => b2.browser === Browser.CHROMEHEADLESSSHELL);
114528
+ const match = installed.find(
114529
+ (b2) => b2.browser === Browser.CHROMEHEADLESSSHELL && b2.buildId === CHROME_VERSION
114530
+ );
114317
114531
  if (match && existsSync53(match.executablePath)) {
114318
114532
  return { result: { executablePath: match.executablePath, source: "cache" } };
114319
114533
  }
@@ -120525,6 +120739,7 @@ function trackRenderMetrics(job, elapsedMs, options, docker) {
120525
120739
  speedRatio,
120526
120740
  captureAvgMs: perf?.captureAvgMs,
120527
120741
  captureP50Ms: perf?.captureP50Ms,
120742
+ subTimelineWait: perf?.subTimelineWait,
120528
120743
  videoCount: perf?.videoCount,
120529
120744
  capturePeakMs: perf?.capturePeakMs,
120530
120745
  tmpPeakBytes: perf?.tmpPeakBytes,