hyperframes 0.8.15 → 0.8.17

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.
@@ -17022,7 +17022,7 @@ var require_util = __commonJS({
17022
17022
  return path;
17023
17023
  }
17024
17024
  exports.normalize = normalize;
17025
- function join55(aRoot, aPath) {
17025
+ function join56(aRoot, aPath) {
17026
17026
  if (aRoot === "") {
17027
17027
  aRoot = ".";
17028
17028
  }
@@ -17054,7 +17054,7 @@ var require_util = __commonJS({
17054
17054
  }
17055
17055
  return joined;
17056
17056
  }
17057
- exports.join = join55;
17057
+ exports.join = join56;
17058
17058
  exports.isAbsolute = function(aPath) {
17059
17059
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
17060
17060
  };
@@ -17227,7 +17227,7 @@ var require_util = __commonJS({
17227
17227
  parsed.path = parsed.path.substring(0, index + 1);
17228
17228
  }
17229
17229
  }
17230
- sourceURL = join55(urlGenerate(parsed), sourceURL);
17230
+ sourceURL = join56(urlGenerate(parsed), sourceURL);
17231
17231
  }
17232
17232
  return normalize(sourceURL);
17233
17233
  }
@@ -50798,7 +50798,7 @@ function compileTag(tag, isVideo, generateId) {
50798
50798
  result = injectAttr(result, "data-hf-auto-start", "");
50799
50799
  startStr = "0";
50800
50800
  }
50801
- const start = parseFloat(startStr);
50801
+ const start = parseNumeric(startStr);
50802
50802
  const attrReader = { getAttribute: (name) => getAttr(result, name) };
50803
50803
  const mediaStart = readMediaStart(attrReader);
50804
50804
  const playbackRate = readElementPlaybackRate(attrReader);
@@ -50806,14 +50806,15 @@ function compileTag(tag, isVideo, generateId) {
50806
50806
  const durationStr = getAttr(result, "data-duration");
50807
50807
  const duration = parseStrictFiniteTimingNumber(durationStr);
50808
50808
  if (duration != null) {
50809
- const end = start + duration;
50810
- result = injectAttr(result, "data-end", String(end));
50809
+ if (start != null) {
50810
+ result = injectAttr(result, "data-end", String(start + duration));
50811
+ }
50811
50812
  } else if (id) {
50812
50813
  unresolved = {
50813
50814
  id,
50814
50815
  tagName: isVideo ? "video" : "audio",
50815
50816
  src: getAttr(result, "src") ?? void 0,
50816
- start,
50817
+ start: start ?? 0,
50817
50818
  mediaStart,
50818
50819
  playbackRate
50819
50820
  };
@@ -50854,7 +50855,7 @@ function compileTimingAttrs(html) {
50854
50855
  unresolved.push({
50855
50856
  id,
50856
50857
  tagName: "div",
50857
- start: startStr ? parseFloat(startStr) : 0,
50858
+ start: parseNumeric(startStr) ?? 0,
50858
50859
  mediaStart: 0,
50859
50860
  playbackRate: 1,
50860
50861
  compositionSrc: compositionSrc ?? void 0
@@ -50873,9 +50874,10 @@ function injectDurations(html, resolutions) {
50873
50874
  result = setAttr(result, "data-duration", String(duration));
50874
50875
  }
50875
50876
  if (!hasAttr(result, "data-end")) {
50876
- const startStr = getAttr(result, "data-start");
50877
- const start = startStr ? parseFloat(startStr) : 0;
50878
- result = injectAttr(result, "data-end", String(start + duration));
50877
+ const start = parseNumeric(getAttr(result, "data-start"));
50878
+ if (start != null) {
50879
+ result = injectAttr(result, "data-end", String(start + duration));
50880
+ }
50879
50881
  }
50880
50882
  return result;
50881
50883
  });
@@ -50906,7 +50908,7 @@ function extractResolvedMedia(html) {
50906
50908
  id,
50907
50909
  tagName: isVideo ? "video" : "audio",
50908
50910
  src: getAttr(tag, "src") ?? void 0,
50909
- start: startStr !== null ? parseFloat(startStr) : 0,
50911
+ start: parseNumeric(startStr) ?? 0,
50910
50912
  duration,
50911
50913
  mediaStart: readMediaStart(attrReader),
50912
50914
  playbackRate: readElementPlaybackRate(attrReader),
@@ -50920,9 +50922,10 @@ function clampDurations(html, clamps) {
50920
50922
  const idPattern = new RegExp(`(<[^>]*id=["']${escapeRegex2(id)}["'][^>]*>)`, "gi");
50921
50923
  html = html.replace(idPattern, (tag) => {
50922
50924
  tag = tag.replace(/data-duration=["'][^"']*["']/, `data-duration="${duration}"`);
50923
- const startStr = getAttr(tag, "data-start");
50924
- const start = startStr ? parseFloat(startStr) : 0;
50925
- tag = tag.replace(/data-end=["'][^"']*["']/, `data-end="${start + duration}"`);
50925
+ const start = parseNumeric(getAttr(tag, "data-start"));
50926
+ if (start != null) {
50927
+ tag = tag.replace(/data-end=["'][^"']*["']/, `data-end="${start + duration}"`);
50928
+ }
50926
50929
  return tag;
50927
50930
  });
50928
50931
  }
@@ -50932,6 +50935,7 @@ var MEDIA_DURATION_CLAMP_EPSILON_SECONDS, INERT_REGION_RE;
50932
50935
  var init_timingCompiler = __esm({
50933
50936
  "../core/dist/compiler/timingCompiler.js"() {
50934
50937
  "use strict";
50938
+ init_compositionContract();
50935
50939
  init_playbackRate();
50936
50940
  MEDIA_DURATION_CLAMP_EPSILON_SECONDS = 0.05;
50937
50941
  INERT_REGION_RE = /<!--[\s\S]*?-->|<script\b[\s\S]*?<\/script\s*>|<style\b[\s\S]*?<\/style\s*>/gi;
@@ -50939,16 +50943,6 @@ var init_timingCompiler = __esm({
50939
50943
  });
50940
50944
 
50941
50945
  // ../core/dist/compiler/mediaRenderIds.js
50942
- function hasPlayableSource(el) {
50943
- if (el.getAttribute("src"))
50944
- return true;
50945
- const sources = el.querySelectorAll?.("source[src]");
50946
- if (!sources)
50947
- return false;
50948
- for (const _ of sources)
50949
- return true;
50950
- return false;
50951
- }
50952
50946
  function uniqueRenderId(baseId, taken) {
50953
50947
  if (!taken.has(baseId))
50954
50948
  return baseId;
@@ -50961,8 +50955,6 @@ function assignMediaRenderIds(document2) {
50961
50955
  const taken = /* @__PURE__ */ new Set();
50962
50956
  const pending = [];
50963
50957
  for (const el of document2.querySelectorAll(MEDIA_SELECTOR)) {
50964
- if (!hasPlayableSource(el))
50965
- continue;
50966
50958
  const existing = el.getAttribute(MEDIA_RENDER_ID_ATTR);
50967
50959
  if (existing) {
50968
50960
  taken.add(existing);
@@ -56094,7 +56086,12 @@ async function captureDrawElementFrame(page, width, height, format = "jpeg", qua
56094
56086
  }) => {
56095
56087
  const canvas = document.getElementById("__hf_de_canvas");
56096
56088
  const root = document.querySelector("[data-composition-id]");
56097
- if (!canvas || !root) throw new Error("drawElement canvas not initialized");
56089
+ if (!root) {
56090
+ throw new Error("HF_DE_COMPOSITION_ROOT_MISSING: drawElement composition root not found");
56091
+ }
56092
+ if (!canvas) {
56093
+ throw new Error("HF_DE_CANVAS_NOT_INITIALIZED: drawElement canvas not initialized");
56094
+ }
56098
56095
  const ctx = canvas.getContext("2d");
56099
56096
  if (!ctx) throw new Error("drawElement: 2d context unavailable");
56100
56097
  const aw = window;
@@ -56341,7 +56338,12 @@ async function produceDrawElementFrame(page, width, height, quality = 80, syncTo
56341
56338
  ({ w, h, q, sync, fid }) => {
56342
56339
  const canvas = document.getElementById("__hf_de_canvas");
56343
56340
  const root = document.querySelector("[data-composition-id]");
56344
- if (!canvas || !root) throw new Error("drawElement canvas not initialized");
56341
+ if (!root) {
56342
+ throw new Error("HF_DE_COMPOSITION_ROOT_MISSING: drawElement composition root not found");
56343
+ }
56344
+ if (!canvas) {
56345
+ throw new Error("HF_DE_CANVAS_NOT_INITIALIZED: drawElement canvas not initialized");
56346
+ }
56345
56347
  const ctx = canvas.getContext("2d");
56346
56348
  if (!ctx) throw new Error("drawElement: 2d context unavailable");
56347
56349
  const aw = window;
@@ -56508,7 +56510,18 @@ async function produceDrawElementFrameBatch(page, times, width, height, quality
56508
56510
  }) => {
56509
56511
  const canvas = document.getElementById("__hf_de_canvas");
56510
56512
  const root = document.querySelector("[data-composition-id]");
56511
- if (!canvas || !root) return { failedAt: 0, error: "drawElement canvas not initialized" };
56513
+ if (!root) {
56514
+ return {
56515
+ failedAt: 0,
56516
+ error: "HF_DE_COMPOSITION_ROOT_MISSING: drawElement composition root not found"
56517
+ };
56518
+ }
56519
+ if (!canvas) {
56520
+ return {
56521
+ failedAt: 0,
56522
+ error: "HF_DE_CANVAS_NOT_INITIALIZED: drawElement canvas not initialized"
56523
+ };
56524
+ }
56512
56525
  const ctx = canvas.getContext("2d");
56513
56526
  if (!ctx) return { failedAt: 0, error: "drawElement: 2d context unavailable" };
56514
56527
  const aw = window;
@@ -56647,10 +56660,11 @@ async function produceDrawElementFrameBatch(page, times, width, height, quality
56647
56660
  }
56648
56661
  return { encodeResults, failedAt: outcome.failedAt, error: outcome.error };
56649
56662
  }
56650
- var workerEncodeStates, workerEncodeBoundPages;
56663
+ var DE_CANVAS_NOT_INITIALIZED_CODE, workerEncodeStates, workerEncodeBoundPages;
56651
56664
  var init_drawElementService = __esm({
56652
56665
  "../engine/src/services/drawElementService.ts"() {
56653
56666
  "use strict";
56667
+ DE_CANVAS_NOT_INITIALIZED_CODE = "HF_DE_CANVAS_NOT_INITIALIZED";
56654
56668
  workerEncodeStates = /* @__PURE__ */ new WeakMap();
56655
56669
  workerEncodeBoundPages = /* @__PURE__ */ new WeakSet();
56656
56670
  }
@@ -58558,11 +58572,12 @@ function recordCaptureWarnings(session, warnings) {
58558
58572
  function recordSubTimelineWarning(session, timeoutMs) {
58559
58573
  if (session.subTimelineWaitOutcome === "ready" || !session.subTimelineWaitOutcome) return;
58560
58574
  const scriptFailure = session.subTimelineWaitOutcome === "script_failure";
58575
+ const hasRuntimeErrors = session.scriptLoadFailures.some((f) => f.startsWith("runtime-error:"));
58561
58576
  recordCaptureWarnings(session, [
58562
58577
  {
58563
58578
  code: scriptFailure ? "sub_timeline_script_failure" : "sub_timeline_readiness_timeout",
58564
- message: scriptFailure ? "A sub-composition timeline script failed to load" : `Sub-composition timelines did not become ready within ${timeoutMs}ms`,
58565
- details: { timeoutMs }
58579
+ message: scriptFailure ? hasRuntimeErrors ? `A sub-composition script threw during execution \u2014 timeline registration never arrived (${session.scriptLoadFailures.join(", ")})` : `A sub-composition timeline script failed to load (${session.scriptLoadFailures.join(", ")})` : `Sub-composition timelines did not become ready within ${timeoutMs}ms`,
58580
+ details: { timeoutMs, sources: [...session.scriptLoadFailures] }
58566
58581
  }
58567
58582
  ]);
58568
58583
  }
@@ -58646,6 +58661,11 @@ async function initializeSession(session) {
58646
58661
  const diagnostic = formatConsoleDiagnostic(type, text, locationUrl);
58647
58662
  if (!diagnostic.suppressHostLog) console.log(diagnostic.text);
58648
58663
  appendBrowserDiagnostic(session, diagnostic.text);
58664
+ if (type === "error" && text.startsWith("[HyperFrames] composition script error:")) {
58665
+ const detail = text.slice("[HyperFrames] composition script error:".length).trim();
58666
+ const compId = detail.split(" ")[0] || "unknown";
58667
+ recordScriptLoadFailure(session, `runtime-error:${compId}`);
58668
+ }
58649
58669
  });
58650
58670
  page.on("pageerror", (err) => {
58651
58671
  const message = err instanceof Error ? err.message : String(err);
@@ -59417,7 +59437,14 @@ async function computeTimelineAtRiskFrames(page, fps) {
59417
59437
  }
59418
59438
  function isNoCachedPaintRecordError(err) {
59419
59439
  const msg = err instanceof Error ? err.message : String(err);
59420
- return msg.includes("No cached paint record");
59440
+ return msg.includes("No cached paint record for element");
59441
+ }
59442
+ function isCanvasNotInitializedError(err) {
59443
+ const msg = err instanceof Error ? err.message : String(err);
59444
+ return msg.includes(DE_CANVAS_NOT_INITIALIZED_CODE);
59445
+ }
59446
+ function isRecoverableDrawElementError(err) {
59447
+ return isNoCachedPaintRecordError(err) || isCanvasNotInitializedError(err);
59421
59448
  }
59422
59449
  async function captureFrameCore(session, frameIndex, time) {
59423
59450
  const { page, options } = session;
@@ -59491,10 +59518,11 @@ async function captureFrameCore(session, frameIndex, time) {
59491
59518
  }
59492
59519
  }
59493
59520
  } catch (err) {
59494
- if (isNoCachedPaintRecordError(err)) {
59521
+ if (isRecoverableDrawElementError(err)) {
59495
59522
  session.deNcprFallbacks = (session.deNcprFallbacks ?? 0) + 1;
59523
+ const reason = isCanvasNotInitializedError(err) ? "drawElement canvas not initialized" : "No cached paint record";
59496
59524
  console.log(
59497
- `[engine] fast capture: frame ${frameIndex} \u2014 No cached paint record; screenshot fallback for this frame (see fast-capture-limitations.md)`
59525
+ `[engine] fast capture: frame ${frameIndex} \u2014 ${reason}; screenshot fallback for this frame (see fast-capture-limitations.md)`
59498
59526
  );
59499
59527
  screenshotBuffer = await pageScreenshotCapture(page, options);
59500
59528
  } else {
@@ -59611,10 +59639,11 @@ async function captureFrameToBufferPipelined(session, frameIndex, time) {
59611
59639
  }
59612
59640
  return { encodeResult, captureTimeMs };
59613
59641
  } catch (captureError) {
59614
- if (isNoCachedPaintRecordError(captureError)) {
59642
+ if (isRecoverableDrawElementError(captureError)) {
59615
59643
  session.deNcprFallbacks = (session.deNcprFallbacks ?? 0) + 1;
59644
+ const reason = isCanvasNotInitializedError(captureError) ? "drawElement canvas not initialized" : "No cached paint record";
59616
59645
  console.log(
59617
- `[engine] fast capture: frame ${frameIndex} \u2014 No cached paint record; screenshot fallback for this frame (see fast-capture-limitations.md)`
59646
+ `[engine] fast capture: frame ${frameIndex} \u2014 ${reason}; screenshot fallback for this frame (see fast-capture-limitations.md)`
59618
59647
  );
59619
59648
  const buffer = await pageScreenshotCapture(page, options);
59620
59649
  return { encodeResult: Promise.resolve(buffer), captureTimeMs: Date.now() - startTime };
@@ -59677,15 +59706,36 @@ async function captureFramesBatchPipelined(session, frameIndices, times) {
59677
59706
  results.push({ frameIndex, encodeResult });
59678
59707
  }
59679
59708
  if (failedAt !== null) {
59680
- console.log(
59681
- `[engine] fast capture: batch produce failed at frame ${frameIndices[failedAt] ?? "?"} (${error ?? "?"}); re-capturing ${frameIndices.length - failedAt} frame(s) per-frame`
59682
- );
59683
- for (let i = failedAt; i < frameIndices.length; i++) {
59684
- const frameIndex = frameIndices[i];
59685
- const time = times[i];
59686
- if (frameIndex === void 0 || time === void 0) break;
59687
- const { encodeResult } = await captureFrameToBufferPipelined(session, frameIndex, time);
59688
- results.push({ frameIndex, encodeResult });
59709
+ if (isRecoverableDrawElementError(error)) {
59710
+ const reason = isCanvasNotInitializedError(error) ? "drawElement canvas not initialized" : "No cached paint record";
59711
+ console.log(
59712
+ `[engine] fast capture: batch produce failed at frame ${frameIndices[failedAt] ?? "?"} (${reason}); screenshot fallback for ${frameIndices.length - failedAt} frame(s) (see fast-capture-limitations.md)`
59713
+ );
59714
+ for (let i = failedAt; i < frameIndices.length; i++) {
59715
+ const frameIndex = frameIndices[i];
59716
+ const time = times[i];
59717
+ if (frameIndex === void 0 || time === void 0) break;
59718
+ session.deNcprFallbacks = (session.deNcprFallbacks ?? 0) + 1;
59719
+ await prepareFrameForCapture(session, frameIndex, time);
59720
+ const buffer = await pageScreenshotCapture(page, options);
59721
+ const encodeResult = Promise.resolve(buffer);
59722
+ if (session.staticFrames) {
59723
+ session.lastEncodeResult = encodeResult;
59724
+ session.lastEncodeResultFrame = frameIndex;
59725
+ }
59726
+ results.push({ frameIndex, encodeResult });
59727
+ }
59728
+ } else {
59729
+ console.log(
59730
+ `[engine] fast capture: batch produce failed at frame ${frameIndices[failedAt] ?? "?"} (${error ?? "?"}); re-capturing ${frameIndices.length - failedAt} frame(s) per-frame`
59731
+ );
59732
+ for (let i = failedAt; i < frameIndices.length; i++) {
59733
+ const frameIndex = frameIndices[i];
59734
+ const time = times[i];
59735
+ if (frameIndex === void 0 || time === void 0) break;
59736
+ const { encodeResult } = await captureFrameToBufferPipelined(session, frameIndex, time);
59737
+ results.push({ frameIndex, encodeResult });
59738
+ }
59689
59739
  }
59690
59740
  }
59691
59741
  const last = results[results.length - 1];
@@ -59843,6 +59893,13 @@ function percentileOf(samples, p) {
59843
59893
  }
59844
59894
  function getCapturePerfSummary(session) {
59845
59895
  const frames = Math.max(1, session.capturePerf.frames);
59896
+ const ncprFallbacks = session.deNcprFallbacks ?? 0;
59897
+ if (frames > 0 && ncprFallbacks / frames > DE_FALLBACK_RATIO_WARN_THRESHOLD) {
59898
+ const pct = Math.round(ncprFallbacks / frames * 100);
59899
+ console.warn(
59900
+ `[engine] fast capture: ${ncprFallbacks}/${frames} frame(s) (${pct}%) fell back to screenshot capture (canvas-not-initialized / no-cached-paint-record) \u2014 drawElement likely failed to engage for this render rather than recovering a few edge-case frames; see fast-capture-limitations.md.`
59901
+ );
59902
+ }
59846
59903
  return {
59847
59904
  frames: session.capturePerf.frames,
59848
59905
  avgTotalMs: Math.round(session.capturePerf.totalMs / frames),
@@ -59881,10 +59938,10 @@ function getCapturePerfSummary(session) {
59881
59938
  deVerifyArmed: session.deVerifyFrames?.size ?? 0,
59882
59939
  deVerifyInitMs: session.deVerifyInitMs ?? 0,
59883
59940
  deBoundaryFrames: session.clipBoundaryFrames?.size ?? 0,
59884
- deNcprFallbacks: session.deNcprFallbacks ?? 0
59941
+ deNcprFallbacks: ncprFallbacks
59885
59942
  };
59886
59943
  }
59887
- var DrawElementVerificationError, BROWSER_CONSOLE_BUFFER_SIZE, CAPTURE_SESSION_CLOSE_TIMEOUT_MS, LOCKED_WARMUP_TICKS, realSleep, BEGIN_FRAME_CAPTURE_HEADROOM_INTERVALS, BEGIN_FRAME_COMMIT_LEAD_INTERVALS, BEGIN_FRAME_PROBE_LEAD_INTERVALS, HF_READY_DIAGNOSTIC_EXPR, LIVE_MAP_MARKERS, MAX_STATIC_DEDUP_ANALYSIS_FRAMES, STATIC_VERIFY_REFERENCE_STRIDE, STATIC_VERIFY_MAX_MS, STATIC_VERIFY_MIN_SCREENSHOT_CAP, STATIC_VERIFY_SAMPLE_CAP_MULTIPLIER, STATIC_VERIFY_MAX_GLOBAL_SAMPLE_FLOOR;
59944
+ var DrawElementVerificationError, BROWSER_CONSOLE_BUFFER_SIZE, CAPTURE_SESSION_CLOSE_TIMEOUT_MS, LOCKED_WARMUP_TICKS, realSleep, BEGIN_FRAME_CAPTURE_HEADROOM_INTERVALS, BEGIN_FRAME_COMMIT_LEAD_INTERVALS, BEGIN_FRAME_PROBE_LEAD_INTERVALS, HF_READY_DIAGNOSTIC_EXPR, LIVE_MAP_MARKERS, MAX_STATIC_DEDUP_ANALYSIS_FRAMES, STATIC_VERIFY_REFERENCE_STRIDE, STATIC_VERIFY_MAX_MS, STATIC_VERIFY_MIN_SCREENSHOT_CAP, STATIC_VERIFY_SAMPLE_CAP_MULTIPLIER, STATIC_VERIFY_MAX_GLOBAL_SAMPLE_FLOOR, DE_FALLBACK_RATIO_WARN_THRESHOLD;
59888
59945
  var init_frameCapture = __esm({
59889
59946
  "../engine/src/services/frameCapture.ts"() {
59890
59947
  "use strict";
@@ -59952,6 +60009,7 @@ var init_frameCapture = __esm({
59952
60009
  STATIC_VERIFY_MIN_SCREENSHOT_CAP = 400;
59953
60010
  STATIC_VERIFY_SAMPLE_CAP_MULTIPLIER = 8;
59954
60011
  STATIC_VERIFY_MAX_GLOBAL_SAMPLE_FLOOR = STATIC_VERIFY_MIN_SCREENSHOT_CAP / STATIC_VERIFY_SAMPLE_CAP_MULTIPLIER;
60012
+ DE_FALLBACK_RATIO_WARN_THRESHOLD = 0.5;
59955
60013
  }
59956
60014
  });
59957
60015
 
@@ -60799,7 +60857,8 @@ async function extractMediaMetadata(filePath) {
60799
60857
  hasAudio: false,
60800
60858
  isVFR: false,
60801
60859
  hasAlpha: false,
60802
- colorSpace: stillImageMeta.colorSpace
60860
+ colorSpace: stillImageMeta.colorSpace,
60861
+ frames: 1
60803
60862
  };
60804
60863
  }
60805
60864
  throw new Error("[FFmpeg] No video stream found");
@@ -60825,6 +60884,8 @@ async function extractMediaMetadata(filePath) {
60825
60884
  const streamDuration = videoStream.duration ? parseFloat(videoStream.duration) : 0;
60826
60885
  const parsedStreamStart = videoStream.start_time ? parseFloat(videoStream.start_time) : 0;
60827
60886
  const streamStart = Number.isFinite(parsedStreamStart) ? parsedStreamStart : 0;
60887
+ const parsedNbFrames = videoStream.nb_frames ? parseInt(videoStream.nb_frames, 10) : NaN;
60888
+ const frames = Number.isFinite(parsedNbFrames) && parsedNbFrames > 0 ? parsedNbFrames : void 0;
60828
60889
  return {
60829
60890
  durationSeconds: containerDuration,
60830
60891
  videoStreamDurationSeconds: streamDuration > 0 ? streamDuration : containerDuration,
@@ -60836,7 +60897,8 @@ async function extractMediaMetadata(filePath) {
60836
60897
  hasAudio: output?.streams.some((s) => s.codec_type === "audio") ?? false,
60837
60898
  isVFR,
60838
60899
  hasAlpha,
60839
- colorSpace
60900
+ colorSpace,
60901
+ frames
60840
60902
  };
60841
60903
  })();
60842
60904
  videoMetadataCache.set(filePath, probePromise);
@@ -61517,8 +61579,6 @@ async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, confi
61517
61579
  args.push("-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart");
61518
61580
  }
61519
61581
  }
61520
- const copiesContainerizedAac = !isWebm && shouldCopyAudio && config?.preserveAudioPrimingEditList === true;
61521
- if (!copiesContainerizedAac) args.push("-avoid_negative_ts", "make_zero");
61522
61582
  appendRenderProvenanceArgs(args, outputPath);
61523
61583
  if (fps !== void 0) {
61524
61584
  args.push("-r", fpsToFfmpegArg(fps));
@@ -69775,6 +69835,14 @@ function hasAttrName(tagSource, attr) {
69775
69835
  const attrs = tagSource.replace(/^<\s*[a-z][\w:-]*/i, "");
69776
69836
  return new RegExp(`(?:^|\\s)${escaped}(?:\\s*=|\\s|/?>)`, "i").test(attrs);
69777
69837
  }
69838
+ function mediaHasResolvableSrc(tag, tags) {
69839
+ if (readAttr(tag.raw, "src")) return true;
69840
+ const end = tag.closeIndex ?? tag.endIndex;
69841
+ if (end == null) return false;
69842
+ return tags.some(
69843
+ (child) => child.name === "source" && child.index > tag.index && child.index < end && Boolean(readAttr(child.raw, "src"))
69844
+ );
69845
+ }
69778
69846
  function classNamesFromAttr(classAttr) {
69779
69847
  if (!classAttr) return [];
69780
69848
  return classAttr.split(/\s+/).filter(Boolean);
@@ -71707,7 +71775,7 @@ var init_dist3 = __esm({
71707
71775
  if (tag.name !== "video" && tag.name !== "audio") continue;
71708
71776
  const hasDataStart = readAttr(tag.raw, "data-start");
71709
71777
  const hasId = readAttr(tag.raw, "id");
71710
- const hasSrc = readAttr(tag.raw, "src");
71778
+ const hasSrc = mediaHasResolvableSrc(tag, tags);
71711
71779
  if (hasSrc && !hasDataStart) {
71712
71780
  findings.push({
71713
71781
  code: "media_missing_data_start",
@@ -71742,9 +71810,9 @@ var init_dist3 = __esm({
71742
71810
  findings.push({
71743
71811
  code: "media_missing_src",
71744
71812
  severity: "error",
71745
- message: `<${tag.name} id="${hasId}"> has data-start but no src attribute. The renderer cannot load this media.`,
71813
+ message: `<${tag.name} id="${hasId}"> has data-start but no src (on the element or a <source> child). The renderer cannot load this media.`,
71746
71814
  elementId: hasId,
71747
- fixHint: `Add a src attribute to the <${tag.name}> element directly. If using <source> children, the renderer still requires src on the parent element.`,
71815
+ fixHint: `Add src on the <${tag.name}> element, or a <source src="..."> child.`,
71748
71816
  snippet: truncateSnippet(tag.raw)
71749
71817
  });
71750
71818
  }
@@ -74507,20 +74575,73 @@ var init_htmlTemplate = __esm({
74507
74575
  }
74508
74576
  });
74509
74577
 
74578
+ // ../engine/src/services/extractedFrameIndex.ts
74579
+ import { readdirSync as readdirSync4 } from "fs";
74580
+ import { join as join9 } from "path";
74581
+ function extractedFrameIndex(file, format) {
74582
+ const match = new RegExp(`^${FRAME_FILENAME_PREFIX}(\\d+)\\.${format}$`).exec(file);
74583
+ if (!match) {
74584
+ throw new ExtractedFrameSequenceError(`Invalid extracted frame filename: ${file}`);
74585
+ }
74586
+ const ordinal = Number.parseInt(match[1], 10);
74587
+ if (!Number.isSafeInteger(ordinal) || ordinal < 1) {
74588
+ throw new ExtractedFrameSequenceError(`Invalid extracted frame ordinal: ${file}`);
74589
+ }
74590
+ return ordinal - 1;
74591
+ }
74592
+ function framePathsFromDirectory(outputDir, format) {
74593
+ const suffix = `.${format}`;
74594
+ const indexed = /* @__PURE__ */ new Map();
74595
+ for (const file of readdirSync4(outputDir)) {
74596
+ if (!file.startsWith(FRAME_FILENAME_PREFIX) || !file.endsWith(suffix)) continue;
74597
+ const index = extractedFrameIndex(file, format);
74598
+ if (indexed.has(index)) {
74599
+ throw new ExtractedFrameSequenceError(
74600
+ `Duplicate extracted frame index ${index}: ${indexed.get(index)} and ${file}`
74601
+ );
74602
+ }
74603
+ indexed.set(index, join9(outputDir, file));
74604
+ }
74605
+ const ordered = /* @__PURE__ */ new Map();
74606
+ for (let index = 0; index < indexed.size; index += 1) {
74607
+ const path = indexed.get(index);
74608
+ if (!path) {
74609
+ throw new ExtractedFrameSequenceError(
74610
+ `Missing extracted frame index ${index} in ${outputDir}`
74611
+ );
74612
+ }
74613
+ ordered.set(index, path);
74614
+ }
74615
+ return ordered;
74616
+ }
74617
+ var FRAME_FILENAME_PREFIX, ExtractedFrameSequenceError;
74618
+ var init_extractedFrameIndex = __esm({
74619
+ "../engine/src/services/extractedFrameIndex.ts"() {
74620
+ "use strict";
74621
+ FRAME_FILENAME_PREFIX = "frame_";
74622
+ ExtractedFrameSequenceError = class extends Error {
74623
+ constructor(message) {
74624
+ super(message);
74625
+ this.name = "ExtractedFrameSequenceError";
74626
+ }
74627
+ };
74628
+ }
74629
+ });
74630
+
74510
74631
  // ../engine/src/services/extractionCache.ts
74511
74632
  import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
74512
74633
  import {
74513
74634
  existsSync as existsSync9,
74514
74635
  lstatSync as lstatSync2,
74515
74636
  mkdirSync as mkdirSync5,
74516
- readdirSync as readdirSync4,
74637
+ readdirSync as readdirSync5,
74517
74638
  renameSync,
74518
74639
  rmSync as rmSync2,
74519
74640
  statSync as statSync5,
74520
74641
  utimesSync,
74521
74642
  writeFileSync as writeFileSync3
74522
74643
  } from "fs";
74523
- import { join as join9 } from "path";
74644
+ import { join as join10 } from "path";
74524
74645
  function readKeyStat(videoPath) {
74525
74646
  try {
74526
74647
  const stat = statSync5(videoPath);
@@ -74549,10 +74670,17 @@ function computeCacheKey(input) {
74549
74670
  function cacheEntryDirName(keyHash) {
74550
74671
  return SCHEMA_PREFIX + keyHash.slice(0, KEY_HEX_CHARS);
74551
74672
  }
74673
+ function hasFrameFiles(dir) {
74674
+ try {
74675
+ return readdirSync5(dir).some((file) => file.startsWith(FRAME_FILENAME_PREFIX));
74676
+ } catch {
74677
+ return false;
74678
+ }
74679
+ }
74552
74680
  function lookupCacheEntry(rootDir, input) {
74553
74681
  const keyHash = computeCacheKey(input);
74554
- const dir = join9(rootDir, cacheEntryDirName(keyHash));
74555
- const complete = existsSync9(join9(dir, COMPLETE_SENTINEL));
74682
+ const dir = join10(rootDir, cacheEntryDirName(keyHash));
74683
+ const complete = existsSync9(join10(dir, COMPLETE_SENTINEL)) && hasFrameFiles(dir);
74556
74684
  return { entry: { dir, keyHash }, hit: complete };
74557
74685
  }
74558
74686
  function partialCacheEntryDir(entry) {
@@ -74563,13 +74691,13 @@ function isTargetExistsRenameError(err) {
74563
74691
  return code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM";
74564
74692
  }
74565
74693
  function adoptPublishedWinner(entry, partialDir) {
74566
- if (!existsSync9(join9(entry.dir, COMPLETE_SENTINEL))) return null;
74694
+ if (!existsSync9(join10(entry.dir, COMPLETE_SENTINEL))) return null;
74567
74695
  removeDir(partialDir);
74568
74696
  return { dir: entry.dir, published: true };
74569
74697
  }
74570
74698
  function publishCacheEntry(entry, partialDir) {
74571
74699
  try {
74572
- writeFileSync3(join9(partialDir, COMPLETE_SENTINEL), "", "utf-8");
74700
+ writeFileSync3(join10(partialDir, COMPLETE_SENTINEL), "", "utf-8");
74573
74701
  } catch {
74574
74702
  return { dir: partialDir, published: false };
74575
74703
  }
@@ -74596,7 +74724,7 @@ function publishCacheEntry(entry, partialDir) {
74596
74724
  function touchCacheEntry(entry) {
74597
74725
  try {
74598
74726
  const now = /* @__PURE__ */ new Date();
74599
- utimesSync(join9(entry.dir, COMPLETE_SENTINEL), now, now);
74727
+ utimesSync(join10(entry.dir, COMPLETE_SENTINEL), now, now);
74600
74728
  } catch {
74601
74729
  }
74602
74730
  }
@@ -74616,12 +74744,12 @@ function directorySizeBytes(path) {
74616
74744
  let total = 0;
74617
74745
  let children;
74618
74746
  try {
74619
- children = readdirSync4(path);
74747
+ children = readdirSync5(path);
74620
74748
  } catch {
74621
74749
  return 0;
74622
74750
  }
74623
74751
  for (const child of children) {
74624
- const childPath = join9(path, child);
74752
+ const childPath = join10(path, child);
74625
74753
  try {
74626
74754
  const stat = lstatSync2(childPath);
74627
74755
  if (stat.isDirectory()) {
@@ -74650,7 +74778,7 @@ function collectGcEntry(dir, name, now, minAgeMs, stats) {
74650
74778
  }
74651
74779
  let lastUseMs = dirStat.mtimeMs;
74652
74780
  try {
74653
- lastUseMs = statSync5(join9(dir, COMPLETE_SENTINEL)).mtimeMs;
74781
+ lastUseMs = statSync5(join10(dir, COMPLETE_SENTINEL)).mtimeMs;
74654
74782
  } catch {
74655
74783
  }
74656
74784
  return { dir, size: directorySizeBytes(dir), lastUseMs, ageMs: now - lastUseMs };
@@ -74660,7 +74788,7 @@ function collectGcEntry(dir, name, now, minAgeMs, stats) {
74660
74788
  }
74661
74789
  function gcSweepDue(rootDir, maxAgeMs) {
74662
74790
  try {
74663
- return Date.now() - statSync5(join9(rootDir, GC_MARKER)).mtimeMs > maxAgeMs;
74791
+ return Date.now() - statSync5(join10(rootDir, GC_MARKER)).mtimeMs > maxAgeMs;
74664
74792
  } catch {
74665
74793
  return true;
74666
74794
  }
@@ -74668,16 +74796,16 @@ function gcSweepDue(rootDir, maxAgeMs) {
74668
74796
  function gcExtractionCache(rootDir, opts) {
74669
74797
  const stats = { evictedEntries: 0, evictedBytes: 0, agedPartialsRemoved: 0 };
74670
74798
  try {
74671
- writeFileSync3(join9(rootDir, GC_MARKER), "", "utf-8");
74799
+ writeFileSync3(join10(rootDir, GC_MARKER), "", "utf-8");
74672
74800
  } catch {
74673
74801
  }
74674
74802
  try {
74675
74803
  const now = Date.now();
74676
74804
  const entries2 = [];
74677
- for (const child of readdirSync4(rootDir, { withFileTypes: true })) {
74805
+ for (const child of readdirSync5(rootDir, { withFileTypes: true })) {
74678
74806
  if (!child.isDirectory() || !isCacheLikeChild(child.name)) continue;
74679
74807
  const entry = collectGcEntry(
74680
- join9(rootDir, child.name),
74808
+ join10(rootDir, child.name),
74681
74809
  child.name,
74682
74810
  now,
74683
74811
  opts.minAgeMs,
@@ -74702,12 +74830,7 @@ function gcExtractionCache(rootDir, opts) {
74702
74830
  }
74703
74831
  function rehydrateCacheEntry(entry, options) {
74704
74832
  const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${options.format}`;
74705
- const framePaths = /* @__PURE__ */ new Map();
74706
- const suffix = `.${options.format}`;
74707
- const files = readdirSync4(entry.dir).filter((f) => f.startsWith(FRAME_FILENAME_PREFIX) && f.endsWith(suffix)).sort();
74708
- files.forEach((file, idx) => {
74709
- framePaths.set(idx, join9(entry.dir, file));
74710
- });
74833
+ const framePaths = framePathsFromDirectory(entry.dir, options.format);
74711
74834
  return {
74712
74835
  videoId: options.videoId,
74713
74836
  srcPath: options.srcPath,
@@ -74719,11 +74842,12 @@ function rehydrateCacheEntry(entry, options) {
74719
74842
  framePaths
74720
74843
  };
74721
74844
  }
74722
- var FRAME_FILENAME_PREFIX, COMPLETE_SENTINEL, GC_MARKER, SCHEMA_PREFIX, KEY_HEX_CHARS, CACHE_GENERATION_PREFIX;
74845
+ var COMPLETE_SENTINEL, GC_MARKER, SCHEMA_PREFIX, KEY_HEX_CHARS, CACHE_GENERATION_PREFIX;
74723
74846
  var init_extractionCache = __esm({
74724
74847
  "../engine/src/services/extractionCache.ts"() {
74725
74848
  "use strict";
74726
- FRAME_FILENAME_PREFIX = "frame_";
74849
+ init_extractedFrameIndex();
74850
+ init_extractedFrameIndex();
74727
74851
  COMPLETE_SENTINEL = ".hf-complete";
74728
74852
  GC_MARKER = ".hf-last-gc";
74729
74853
  SCHEMA_PREFIX = "hfcache-v4-";
@@ -74733,8 +74857,8 @@ var init_extractionCache = __esm({
74733
74857
  });
74734
74858
 
74735
74859
  // ../engine/src/services/videoFrameExtractor.ts
74736
- import { copyFileSync as copyFileSync2, existsSync as existsSync10, linkSync as linkSync2, mkdirSync as mkdirSync6, readdirSync as readdirSync5, rmSync as rmSync3 } from "fs";
74737
- import { isAbsolute, join as join10, posix as posix2, resolve as resolve5, sep as sep2 } from "path";
74860
+ import { copyFileSync as copyFileSync2, existsSync as existsSync10, linkSync as linkSync2, mkdirSync as mkdirSync6, rmSync as rmSync3 } from "fs";
74861
+ import { isAbsolute, join as join11, posix as posix2, resolve as resolve5, sep as sep2 } from "path";
74738
74862
  function isVideoFrameFormat(value) {
74739
74863
  return typeof value === "string" && VIDEO_FRAME_FORMATS.includes(value);
74740
74864
  }
@@ -74956,15 +75080,27 @@ async function runVideoExtractionWithRetry(operation, options = {}) {
74956
75080
  }
74957
75081
  }
74958
75082
  }
75083
+ function resolveMediaElementSrc(el) {
75084
+ const direct = el.getAttribute("src");
75085
+ if (direct) return direct;
75086
+ let remote = null;
75087
+ for (const source of el.querySelectorAll("source")) {
75088
+ const src = source.getAttribute("src");
75089
+ if (!src) continue;
75090
+ if (!/^https?:\/\//i.test(src)) return src;
75091
+ remote ??= src;
75092
+ }
75093
+ return remote;
75094
+ }
74959
75095
  function parseVideoElements(html) {
74960
75096
  const videos = [];
74961
75097
  const { document: document2 } = parseHTML(unwrapTemplate(html));
74962
75098
  const startCache = /* @__PURE__ */ new Map();
74963
75099
  const visiting = /* @__PURE__ */ new Set();
74964
- const videoEls = document2.querySelectorAll("video[src]");
75100
+ const videoEls = document2.querySelectorAll("video");
74965
75101
  let autoIdCounter = 0;
74966
75102
  for (const el of videoEls) {
74967
- const src = el.getAttribute("src");
75103
+ const src = resolveMediaElementSrc(el);
74968
75104
  if (!src) continue;
74969
75105
  const id = el.getAttribute(MEDIA_RENDER_ID_ATTR) || el.getAttribute("id") || `hf-video-${autoIdCounter++}`;
74970
75106
  if (!el.getAttribute("id")) {
@@ -75040,7 +75176,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
75040
75176
  const normalizedFps = toFps(options.fps);
75041
75177
  const fps = fpsToNumber(normalizedFps);
75042
75178
  const ffmpegFps = fpsToFfmpegArg(normalizedFps);
75043
- const videoOutputDir = outputDirOverride ?? join10(outputDir, videoId);
75179
+ const videoOutputDir = outputDirOverride ?? join11(outputDir, videoId);
75044
75180
  if (!existsSync10(videoOutputDir)) mkdirSync6(videoOutputDir, { recursive: true });
75045
75181
  let metadata;
75046
75182
  try {
@@ -75067,7 +75203,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
75067
75203
  }
75068
75204
  const format = resolveFrameFormat(metadata, options.format);
75069
75205
  const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${format}`;
75070
- const outputPattern = join10(videoOutputDir, framePattern);
75206
+ const outputPattern = join11(videoOutputDir, framePattern);
75071
75207
  const isHdr = isHdrColorSpace(metadata.colorSpace);
75072
75208
  const isMacOS = process.platform === "darwin";
75073
75209
  const args = [];
@@ -75129,11 +75265,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
75129
75265
  diagnostic
75130
75266
  );
75131
75267
  }
75132
- const framePaths = /* @__PURE__ */ new Map();
75133
- const files = readdirSync5(videoOutputDir).filter((f) => f.startsWith(FRAME_FILENAME_PREFIX) && f.endsWith(`.${format}`)).sort();
75134
- files.forEach((file, index) => {
75135
- framePaths.set(index, join10(videoOutputDir, file));
75136
- });
75268
+ const framePaths = framePathsFromDirectory(videoOutputDir, format);
75137
75269
  if (framePaths.size === 0 && duration > 0) {
75138
75270
  throw new VideoSourceExtractionError(
75139
75271
  "zero_output",
@@ -75323,16 +75455,9 @@ function resolveFrameFormat(metadata, requested) {
75323
75455
  if (requested === "png" || requested === "jpg") return requested;
75324
75456
  return "jpg";
75325
75457
  }
75326
- function extractedFrameFileNames(outputDir, format) {
75327
- const suffix = `.${format}`;
75328
- return readdirSync5(outputDir).filter((file) => file.startsWith(FRAME_FILENAME_PREFIX) && file.endsWith(suffix)).sort();
75329
- }
75330
75458
  function extractedFramesFromDirectory(work, outputDir, srcPath, fps) {
75331
75459
  const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${work.format}`;
75332
- const framePaths = /* @__PURE__ */ new Map();
75333
- extractedFrameFileNames(outputDir, work.format).forEach((file, index) => {
75334
- framePaths.set(index, join10(outputDir, file));
75335
- });
75460
+ const framePaths = framePathsFromDirectory(outputDir, work.format);
75336
75461
  return {
75337
75462
  videoId: work.video.id,
75338
75463
  srcPath,
@@ -75452,7 +75577,7 @@ function sliceSupersetMember(member, superset, outputDir, fps, configuredFps) {
75452
75577
  for (let i = 0; i < frameCount; i += 1) {
75453
75578
  const sourceFrame = superset.framePaths.get(member.offsetFrames + i);
75454
75579
  if (!sourceFrame) throw new Error(`superset frame ${member.offsetFrames + i} missing`);
75455
- linkOrCopyFrame(sourceFrame, join10(outputDir, frameFileName(i + 1, work.format)));
75580
+ linkOrCopyFrame(sourceFrame, join11(outputDir, frameFileName(i + 1, work.format)));
75456
75581
  }
75457
75582
  return extractedFramesFromDirectory(work, outputDir, work.videoPath, fps);
75458
75583
  }
@@ -75465,22 +75590,22 @@ function resolveProjectRelativeSrc(src, baseDir, compiledDir) {
75465
75590
  if (!candidates.includes(candidate)) candidates.push(candidate);
75466
75591
  };
75467
75592
  for (const variant of decodeUrlPathVariants(cleanSrc)) {
75468
- const fromCompiled = compiledDir ? join10(compiledDir, variant) : null;
75469
- const fromBase = join10(baseDir, variant);
75593
+ const fromCompiled = compiledDir ? join11(compiledDir, variant) : null;
75594
+ const fromBase = join11(baseDir, variant);
75470
75595
  const baseAbs = resolve5(baseDir);
75471
75596
  const fromBaseAbs = resolve5(fromBase);
75472
75597
  if (!fromBaseAbs.startsWith(baseAbs + sep2) && fromBaseAbs !== baseAbs) {
75473
75598
  const normalized = posix2.normalize(variant.replace(/\\/g, "/"));
75474
75599
  const stripped = normalized.replace(/^(\.\.\/)+/, "");
75475
75600
  if (stripped && stripped !== variant && !stripped.startsWith("..")) {
75476
- if (compiledDir) addCandidate(join10(compiledDir, stripped));
75477
- addCandidate(join10(baseDir, stripped));
75601
+ if (compiledDir) addCandidate(join11(compiledDir, stripped));
75602
+ addCandidate(join11(baseDir, stripped));
75478
75603
  }
75479
75604
  }
75480
75605
  if (fromCompiled) addCandidate(fromCompiled);
75481
75606
  addCandidate(fromBase);
75482
75607
  }
75483
- return candidates.find(existsSync10) ?? join10(baseDir, cleanSrc);
75608
+ return candidates.find(existsSync10) ?? join11(baseDir, cleanSrc);
75484
75609
  }
75485
75610
  async function extractAllVideoFrames(videos, baseDir, options, signal, config, compiledDir) {
75486
75611
  if (options.timelineEnd !== void 0 && !Number.isFinite(options.timelineEnd)) {
@@ -75527,7 +75652,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
75527
75652
  videoPath = resolveProjectRelativeSrc(video.src, baseDir, compiledDir);
75528
75653
  }
75529
75654
  if (isHttpUrl(videoPath)) {
75530
- const downloadDir = join10(options.outputDir, "_downloads");
75655
+ const downloadDir = join11(options.outputDir, "_downloads");
75531
75656
  mkdirSync6(downloadDir, { recursive: true });
75532
75657
  videoPath = await downloadToTemp(
75533
75658
  videoPath,
@@ -75735,7 +75860,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
75735
75860
  async function extractDirectMiss(miss, maxTransientRetries = options.maxTransientRetries ?? 0) {
75736
75861
  const { work, cacheTarget } = miss;
75737
75862
  if (!cacheTarget) {
75738
- const outputDir = join10(options.outputDir, work.video.id);
75863
+ const outputDir = join11(options.outputDir, work.video.id);
75739
75864
  const attempted2 = await runVideoExtractionWithRetry(
75740
75865
  () => extractVideoFramesRange(
75741
75866
  work.videoPath,
@@ -75803,7 +75928,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
75803
75928
  return sliceSupersetMember(
75804
75929
  member,
75805
75930
  superset,
75806
- join10(options.outputDir, work.video.id),
75931
+ join11(options.outputDir, work.video.id),
75807
75932
  fps,
75808
75933
  configuredFps
75809
75934
  );
@@ -75820,7 +75945,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
75820
75945
  async function executeSupersetGroup(group) {
75821
75946
  const first = group.members[0]?.miss.work;
75822
75947
  if (!first) return [];
75823
- const tempDir = cacheRootDir ? join10(cacheRootDir, `${group.groupId}.partial-${process.pid}`) : join10(options.outputDir, group.groupId);
75948
+ const tempDir = cacheRootDir ? join11(cacheRootDir, `${group.groupId}.partial-${process.pid}`) : join11(options.outputDir, group.groupId);
75824
75949
  try {
75825
75950
  rmSync3(tempDir, { recursive: true, force: true });
75826
75951
  const superset = await extractVideoFramesRange(
@@ -76051,6 +76176,7 @@ var init_videoFrameExtractor = __esm({
76051
76176
  init_config();
76052
76177
  init_htmlTemplate();
76053
76178
  init_extractionCache();
76179
+ init_extractedFrameIndex();
76054
76180
  VIDEO_FRAME_FORMATS = ["auto", "jpg", "png"];
76055
76181
  EXTRACT_CACHE_MIN_AGE_MS = 60 * 60 * 1e3;
76056
76182
  GC_STALENESS_MS = 24 * 60 * 60 * 1e3;
@@ -76884,7 +77010,7 @@ var init_audio_fx_runtime_inline = __esm({
76884
77010
  // ../engine/src/services/audioFxRender.ts
76885
77011
  import { existsSync as existsSync11, mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
76886
77012
  import { tmpdir as tmpdir2 } from "os";
76887
- import { join as join11 } from "path";
77013
+ import { join as join12 } from "path";
76888
77014
  import { pathToFileURL } from "url";
76889
77015
  function readWavChunks(buf) {
76890
77016
  const head = { format: 1, channels: 1, sampleRate: 48e3, bits: 16 };
@@ -76997,7 +77123,7 @@ async function applyAudioFxChain(inputWav, chain, outputWav, options) {
76997
77123
  const { samples, sampleRate, channels, float } = readWav(inputWav);
76998
77124
  const planes = deinterleave(samples, channels);
76999
77125
  if ((planes[0]?.length ?? 0) === 0) return { path: inputWav, envelopeBaked: false };
77000
- const hostDir = mkdtempSync2(join11(tmpdir2(), "hf-fx-host-"));
77126
+ const hostDir = mkdtempSync2(join12(tmpdir2(), "hf-fx-host-"));
77001
77127
  let lease = null;
77002
77128
  try {
77003
77129
  if (options.signal?.aborted) {
@@ -77006,7 +77132,7 @@ async function applyAudioFxChain(inputWav, chain, outputWav, options) {
77006
77132
  lease = await acquireBrowser(["--no-sandbox", "--autoplay-policy=no-user-gesture-required"]);
77007
77133
  const page = await lease.browser.newPage();
77008
77134
  try {
77009
- const hostPage = join11(hostDir, "audio-fx.html");
77135
+ const hostPage = join12(hostDir, "audio-fx.html");
77010
77136
  writeFileSync5(hostPage, "<!doctype html><meta charset=utf-8><title>audio fx</title>");
77011
77137
  await page.goto(pathToFileURL(hostPage).href, { waitUntil: "domcontentloaded" });
77012
77138
  await page.addScriptTag({ content: getAudioFxRuntimeScript() });
@@ -77143,7 +77269,7 @@ var init_audioFxRender = __esm({
77143
77269
 
77144
77270
  // ../engine/src/services/audioMixer.ts
77145
77271
  import { closeSync as closeSync2, existsSync as existsSync12, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, openSync as openSync2, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
77146
- import { join as join12, dirname as dirname7 } from "path";
77272
+ import { join as join13, dirname as dirname7 } from "path";
77147
77273
  function memberGroupKey(el) {
77148
77274
  return el.getAttribute(AUDIO_GROUP_RENDER_ID_ATTR) ?? el.getAttribute(HF_AUDIO_GROUP_ATTR);
77149
77275
  }
@@ -77394,7 +77520,7 @@ function parseAudioElements(html) {
77394
77520
  const groupId = memberGroupKey(el);
77395
77521
  return groupId ? groupsById.get(groupId)?.hidden ?? false : false;
77396
77522
  };
77397
- const build = (el, id, type) => {
77523
+ const build = (el, id, src, type) => {
77398
77524
  const playbackRateAttr = el.getAttribute("data-playback-rate");
77399
77525
  const layerAttr = el.getAttribute("data-layer");
77400
77526
  const volumeAttr = el.getAttribute("data-volume");
@@ -77404,7 +77530,7 @@ function parseAudioElements(html) {
77404
77530
  const group = groupId ? groupsById.get(groupId) : void 0;
77405
77531
  return {
77406
77532
  id,
77407
- src: el.getAttribute("src"),
77533
+ src,
77408
77534
  start: resolveStart2(el),
77409
77535
  end: parseEnd(el.getAttribute("data-end")),
77410
77536
  mediaStart: readMediaStart(el),
@@ -77425,17 +77551,19 @@ function parseAudioElements(html) {
77425
77551
  };
77426
77552
  };
77427
77553
  const trackId = (el) => el.getAttribute(MEDIA_RENDER_ID_ATTR) || el.getAttribute("id");
77428
- for (const el of document2.querySelectorAll("audio[id][src]")) {
77554
+ for (const el of document2.querySelectorAll("audio[id]")) {
77429
77555
  const id = trackId(el);
77430
- if (!id || !el.getAttribute("src") || isHidden(el) || memberGroupHidden(el)) continue;
77556
+ const src = resolveMediaElementSrc(el);
77557
+ if (!id || !src || isHidden(el) || memberGroupHidden(el)) continue;
77431
77558
  if (isKnownInactiveTimelineWindow(el, resolveStart2(el))) continue;
77432
- elements.push(build(el, id, "audio"));
77559
+ elements.push(build(el, id, src, "audio"));
77433
77560
  }
77434
- for (const el of document2.querySelectorAll('video[id][src][data-has-audio="true"]')) {
77561
+ for (const el of document2.querySelectorAll('video[id][data-has-audio="true"]')) {
77435
77562
  const id = trackId(el);
77436
- if (!id || !el.getAttribute("src") || isHidden(el)) continue;
77563
+ const src = resolveMediaElementSrc(el);
77564
+ if (!id || !src || isHidden(el)) continue;
77437
77565
  if (isKnownInactiveTimelineWindow(el, resolveStart2(el))) continue;
77438
- elements.push(build(el, `${id}-audio`, "video"));
77566
+ elements.push(build(el, `${id}-audio`, src, "video"));
77439
77567
  }
77440
77568
  return elements;
77441
77569
  }
@@ -77607,8 +77735,8 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
77607
77735
  const runMix = async (ignoreAutomation) => {
77608
77736
  const inputs = [];
77609
77737
  tracks.forEach((track) => inputs.push("-i", track.srcPath));
77610
- const scriptDir = mkdtempSync3(join12(outputDir, ".filter-complex-"));
77611
- const scriptPath = join12(scriptDir, "graph.txt");
77738
+ const scriptDir = mkdtempSync3(join13(outputDir, ".filter-complex-"));
77739
+ const scriptPath = join13(scriptDir, "graph.txt");
77612
77740
  const fd = openSync2(scriptPath, "wx", 384);
77613
77741
  try {
77614
77742
  writeFileSync6(fd, buildFilterComplex(ignoreAutomation));
@@ -77714,8 +77842,8 @@ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal,
77714
77842
  `${mixInputs}amix=inputs=${memberTracks.length}:duration=longest:dropout_transition=0[mixed];[mixed]volume=${formatFilterNumber(memberTracks.length)}[out]`
77715
77843
  );
77716
77844
  const filterComplex = [...inputFilters, mixFilter].join(";");
77717
- const scriptDir = mkdtempSync3(join12(outputDir, ".group-filter-complex-"));
77718
- const scriptPath = join12(scriptDir, "graph.txt");
77845
+ const scriptDir = mkdtempSync3(join13(outputDir, ".group-filter-complex-"));
77846
+ const scriptPath = join13(scriptDir, "graph.txt");
77719
77847
  const fd = openSync2(scriptPath, "wx", 384);
77720
77848
  try {
77721
77849
  writeFileSync6(fd, filterComplex);
@@ -77865,7 +77993,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
77865
77993
  }
77866
77994
  let audioSrcPath = srcPath;
77867
77995
  if (element.type === "video") {
77868
- const extractedPath = join12(workDir, `${element.id}-extracted.wav`);
77996
+ const extractedPath = join13(workDir, `${element.id}-extracted.wav`);
77869
77997
  const extractResult = await extractAudioFromVideo(
77870
77998
  srcPath,
77871
77999
  extractedPath,
@@ -77892,7 +78020,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
77892
78020
  }
77893
78021
  audioSrcPath = extractedPath;
77894
78022
  } else {
77895
- const trimmedPath = join12(workDir, `${element.id}-trimmed.wav`);
78023
+ const trimmedPath = join13(workDir, `${element.id}-trimmed.wav`);
77896
78024
  const prepResult = await prepareAudioTrack(
77897
78025
  srcPath,
77898
78026
  trimmedPath,
@@ -77936,7 +78064,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
77936
78064
  const fxResult = await applyAudioFxChain(
77937
78065
  audioSrcPath,
77938
78066
  chain,
77939
- join12(workDir, `${element.id}-fx.wav`),
78067
+ join13(workDir, `${element.id}-fx.wav`),
77940
78068
  {
77941
78069
  trackId: element.id,
77942
78070
  signal: effectiveSignal,
@@ -78027,7 +78155,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
78027
78155
  const meta = groupMeta.get(groupId);
78028
78156
  if (!meta) continue;
78029
78157
  const pathId = safePathSegment(groupId, groupIndex);
78030
- const groupWavPath = join12(workDir, `group-${pathId}.wav`);
78158
+ const groupWavPath = join13(workDir, `group-${pathId}.wav`);
78031
78159
  try {
78032
78160
  const subMix = await mixGroupMembers(
78033
78161
  memberTracks,
@@ -78063,7 +78191,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
78063
78191
  const fxResult = await applyAudioFxChain(
78064
78192
  groupSrcPath,
78065
78193
  chain,
78066
- join12(workDir, `group-${pathId}-fx.wav`),
78194
+ join13(workDir, `group-${pathId}-fx.wav`),
78067
78195
  {
78068
78196
  trackId: groupId,
78069
78197
  signal: effectiveSignal,
@@ -78161,14 +78289,14 @@ var init_audioMixer = __esm({
78161
78289
  import { execFile as execFile2 } from "child_process";
78162
78290
  import { mkdtemp, rm, writeFile } from "fs/promises";
78163
78291
  import { tmpdir as tmpdir3 } from "os";
78164
- import { join as join13 } from "path";
78292
+ import { join as join14 } from "path";
78165
78293
  import { promisify as promisify2 } from "util";
78166
78294
  async function psnrDb(a, b) {
78167
78295
  const execFileP = promisify2(execFile2);
78168
- const dir = await mkdtemp(join13(tmpdir3(), "hf-de-verify-"));
78296
+ const dir = await mkdtemp(join14(tmpdir3(), "hf-de-verify-"));
78169
78297
  try {
78170
- const pa = join13(dir, "a.jpg");
78171
- const pb = join13(dir, "b.jpg");
78298
+ const pa = join14(dir, "a.jpg");
78299
+ const pb = join14(dir, "b.jpg");
78172
78300
  await Promise.all([writeFile(pa, a), writeFile(pb, b)]);
78173
78301
  const { stderr } = await execFileP(
78174
78302
  getFfmpegBinary(),
@@ -78279,7 +78407,7 @@ var init_readWebGlVendorInfoFromCanvas = __esm({
78279
78407
  import { cpus, freemem } from "os";
78280
78408
  import { existsSync as existsSync13, mkdirSync as mkdirSync8, readdirSync as readdirSync6 } from "fs";
78281
78409
  import { copyFile, readFile, rename } from "fs/promises";
78282
- import { join as join14 } from "path";
78410
+ import { join as join15 } from "path";
78283
78411
  import { getHeapStatistics } from "v8";
78284
78412
  function defaultSafeMaxWorkers() {
78285
78413
  return Math.max(6, Math.min(16, Math.floor(cpus().length / 8)));
@@ -78408,7 +78536,7 @@ function distributeFrames(totalFrames, workerCount, workDir, rangeStart = 0) {
78408
78536
  workerId: i,
78409
78537
  startFrame,
78410
78538
  endFrame,
78411
- outputDir: join14(workDir, `worker-${i}`),
78539
+ outputDir: join15(workDir, `worker-${i}`),
78412
78540
  outputFrameOffset: rangeStart
78413
78541
  });
78414
78542
  }
@@ -78422,7 +78550,7 @@ function distributeFramesInterleaved(totalFrames, workerCount, workDir, rangeSta
78422
78550
  startFrame: rangeStart + i,
78423
78551
  endFrame: rangeStart + totalFrames,
78424
78552
  frameStride: workerCount,
78425
- outputDir: join14(workDir, `worker-${i}`),
78553
+ outputDir: join15(workDir, `worker-${i}`),
78426
78554
  outputFrameOffset: rangeStart
78427
78555
  });
78428
78556
  }
@@ -78580,7 +78708,7 @@ async function verifyDiskDrawElementSamples(session, task, streaming) {
78580
78708
  for (const idx of selectVerifySampleIndicesForTask(truths.keys(), task)) {
78581
78709
  const truth = truths.get(idx);
78582
78710
  if (!truth) continue;
78583
- const framePath = join14(task.outputDir, `frame_${String(idx - offset2).padStart(6, "0")}.${ext}`);
78711
+ const framePath = join15(task.outputDir, `frame_${String(idx - offset2).padStart(6, "0")}.${ext}`);
78584
78712
  const db = await psnrForDiskSample(framePath, truth, task.workerId, idx);
78585
78713
  if (db === null) continue;
78586
78714
  assertDiskSampleAboveFloor(db, verifyMinDb, idx, task.workerId);
@@ -78746,8 +78874,8 @@ async function mergeWorkerFrames(workDir, tasks, outputDir) {
78746
78874
  }
78747
78875
  const files = readdirSync6(task.outputDir).filter((f) => f.startsWith("frame_") && (f.endsWith(".jpg") || f.endsWith(".png"))).sort();
78748
78876
  const copyTasks = files.map(async (file) => {
78749
- const sourcePath = join14(task.outputDir, file);
78750
- const targetPath = join14(outputDir, file);
78877
+ const sourcePath = join15(task.outputDir, file);
78878
+ const targetPath = join15(outputDir, file);
78751
78879
  try {
78752
78880
  await rename(sourcePath, targetPath);
78753
78881
  } catch {
@@ -78793,7 +78921,7 @@ var init_parallelCoordinator = __esm({
78793
78921
  import { Hono } from "hono";
78794
78922
  import { serve } from "@hono/node-server";
78795
78923
  import { readFileSync as readFileSync5, existsSync as existsSync14, statSync as statSync6 } from "fs";
78796
- import { join as join15, extname as extname3 } from "path";
78924
+ import { join as join16, extname as extname3 } from "path";
78797
78925
  function createFileServer(options) {
78798
78926
  const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
78799
78927
  const headScripts = options.headScripts ?? [];
@@ -78803,11 +78931,11 @@ function createFileServer(options) {
78803
78931
  let requestPath = c.req.path;
78804
78932
  if (requestPath === "/") requestPath = "/index.html";
78805
78933
  const relativePath = requestPath.replace(/^\//, "");
78806
- const compiledPath = compiledDir ? join15(compiledDir, relativePath) : null;
78934
+ const compiledPath = compiledDir ? join16(compiledDir, relativePath) : null;
78807
78935
  const hasCompiledFile = Boolean(
78808
78936
  compiledPath && existsSync14(compiledPath) && statSync6(compiledPath).isFile()
78809
78937
  );
78810
- const filePath = hasCompiledFile ? compiledPath : join15(projectDir, relativePath);
78938
+ const filePath = hasCompiledFile ? compiledPath : join16(projectDir, relativePath);
78811
78939
  if (!existsSync14(filePath) || !statSync6(filePath).isFile()) {
78812
78940
  return c.text("Not found", 404);
78813
78941
  }
@@ -80260,7 +80388,7 @@ var init_shaderTransitions = __esm({
80260
80388
 
80261
80389
  // ../engine/src/services/hdrCapture.ts
80262
80390
  import { existsSync as existsSync15, readdirSync as readdirSync7 } from "fs";
80263
- import { join as join16 } from "path";
80391
+ import { join as join17 } from "path";
80264
80392
  import { homedir as homedir2 } from "os";
80265
80393
  function linearToPQ(L) {
80266
80394
  const Lp = Math.max(0, L * SDR_NITS / PQ_MAX_NITS);
@@ -80376,12 +80504,12 @@ function float16ToPqRgb(rawBuffer, bytesPerRow, width, height) {
80376
80504
  return output;
80377
80505
  }
80378
80506
  function resolveHeadedChromePath() {
80379
- const baseDir = join16(homedir2(), ".cache", "puppeteer", "chrome");
80507
+ const baseDir = join17(homedir2(), ".cache", "puppeteer", "chrome");
80380
80508
  if (!existsSync15(baseDir)) return void 0;
80381
80509
  const versions = readdirSync7(baseDir).sort().reverse();
80382
80510
  for (const version2 of versions) {
80383
80511
  const candidates = [
80384
- join16(
80512
+ join17(
80385
80513
  baseDir,
80386
80514
  version2,
80387
80515
  "chrome-mac-arm64",
@@ -80390,7 +80518,7 @@ function resolveHeadedChromePath() {
80390
80518
  "MacOS",
80391
80519
  "Google Chrome for Testing"
80392
80520
  ),
80393
- join16(
80521
+ join17(
80394
80522
  baseDir,
80395
80523
  version2,
80396
80524
  "chrome-mac-x64",
@@ -80399,8 +80527,8 @@ function resolveHeadedChromePath() {
80399
80527
  "MacOS",
80400
80528
  "Google Chrome for Testing"
80401
80529
  ),
80402
- join16(baseDir, version2, "chrome-linux64", "chrome"),
80403
- join16(baseDir, version2, "chrome-win64", "chrome.exe")
80530
+ join17(baseDir, version2, "chrome-linux64", "chrome"),
80531
+ join17(baseDir, version2, "chrome-win64", "chrome.exe")
80404
80532
  ];
80405
80533
  for (const binary of candidates) {
80406
80534
  if (existsSync15(binary)) return binary;
@@ -80643,6 +80771,7 @@ __export(src_exports, {
80643
80771
  resolveHeadlessShellPath: () => resolveHeadlessShellPath,
80644
80772
  resolvePlayableVideoDuration: () => resolvePlayableVideoDuration,
80645
80773
  resolveProjectRelativeSrc: () => resolveProjectRelativeSrc,
80774
+ resolveReferencedStart: () => resolveReferencedStart,
80646
80775
  resolveTimelineExtractionWindow: () => resolveTimelineExtractionWindow,
80647
80776
  resolveVideoExtractionDuration: () => resolveVideoExtractionDuration,
80648
80777
  resolveVideoExtractionWindow: () => resolveVideoExtractionWindow,
@@ -80680,6 +80809,7 @@ var init_src = __esm({
80680
80809
  init_chunkEncoder();
80681
80810
  init_streamingEncoder();
80682
80811
  init_videoFrameExtractor();
80812
+ init_referenceResolver();
80683
80813
  init_videoFrameInjector();
80684
80814
  init_audioMixer();
80685
80815
  init_captureWarning();
@@ -83875,7 +84005,7 @@ __export(fontCompression_exports, {
83875
84005
  import { createHash as createHash6 } from "crypto";
83876
84006
  import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync8, writeFileSync as writeFileSync8 } from "fs";
83877
84007
  import { homedir as homedir4, tmpdir as tmpdir4 } from "os";
83878
- import { dirname as dirname11, join as join23 } from "path";
84008
+ import { dirname as dirname11, join as join24 } from "path";
83879
84009
  async function compressToWoff2(input) {
83880
84010
  return Buffer.from(await compress(input));
83881
84011
  }
@@ -83883,12 +84013,12 @@ function rawMimeType(format) {
83883
84013
  return RAW_MIME_TYPES[format] ?? "font/ttf";
83884
84014
  }
83885
84015
  function defaultCacheDir() {
83886
- const root = process.env.HYPERFRAMES_FONT_CACHE_DIR ?? (process.env.AWS_LAMBDA_FUNCTION_NAME ? join23(tmpdir4(), "hyperframes", "fonts") : join23(homedir4(), ".cache", "hyperframes", "fonts"));
83887
- return join23(root, "local-compression-v1");
84016
+ const root = process.env.HYPERFRAMES_FONT_CACHE_DIR ?? (process.env.AWS_LAMBDA_FUNCTION_NAME ? join24(tmpdir4(), "hyperframes", "fonts") : join24(homedir4(), ".cache", "hyperframes", "fonts"));
84017
+ return join24(root, "local-compression-v1");
83888
84018
  }
83889
84019
  function cachedCompressionPath(input, originalFormat, cacheDir) {
83890
84020
  const digest = createHash6("sha256").update("hyperframes-local-font-compression-v1\0").update(originalFormat).update("\0").update(input).digest("hex");
83891
- return join23(cacheDir, `${digest}.woff2`);
84021
+ return join24(cacheDir, `${digest}.woff2`);
83892
84022
  }
83893
84023
  function readCachedCompression(path) {
83894
84024
  try {
@@ -83968,7 +84098,7 @@ import {
83968
84098
  appendFileSync
83969
84099
  } from "fs";
83970
84100
  import { tmpdir as tmpdir7 } from "os";
83971
- import { join as join40, dirname as dirname16, resolve as resolve13 } from "path";
84101
+ import { join as join41, dirname as dirname16, resolve as resolve13 } from "path";
83972
84102
  import { totalmem as totalmem3 } from "os";
83973
84103
  import { randomUUID as randomUUID3 } from "crypto";
83974
84104
  import { fileURLToPath as fileURLToPath5 } from "url";
@@ -83981,7 +84111,7 @@ import { serve as serve2 } from "@hono/node-server";
83981
84111
  import { existsSync as existsSync17, realpathSync as realpathSync2, statSync as statSync7, createReadStream as createReadStream3 } from "fs";
83982
84112
  import { readFile as readFile2 } from "fs/promises";
83983
84113
  import { Readable as Readable2 } from "stream";
83984
- import { join as join17, extname as extname4, resolve as resolve7, sep as sep3 } from "path";
84114
+ import { join as join18, extname as extname4, resolve as resolve7, sep as sep3 } from "path";
83985
84115
 
83986
84116
  // ../producer/src/services/hyperframeRuntimeLoader.ts
83987
84117
  import { createHash as createHash4 } from "crypto";
@@ -84571,13 +84701,13 @@ function createFileServer2(options) {
84571
84701
  }).join("/");
84572
84702
  let filePath = null;
84573
84703
  if (compiledDir) {
84574
- const candidate = join17(compiledDir, relativePath);
84704
+ const candidate = join18(compiledDir, relativePath);
84575
84705
  if (existsSync17(candidate) && isPathInside2(candidate, compiledDir) && statSync7(candidate).isFile()) {
84576
84706
  filePath = candidate;
84577
84707
  }
84578
84708
  }
84579
84709
  if (!filePath) {
84580
- const candidate = join17(projectDir, relativePath);
84710
+ const candidate = join18(projectDir, relativePath);
84581
84711
  if (existsSync17(candidate) && isPathInside2(candidate, projectDir) && statSync7(candidate).isFile()) {
84582
84712
  filePath = candidate;
84583
84713
  }
@@ -84682,12 +84812,12 @@ import {
84682
84812
  symlinkSync,
84683
84813
  writeFileSync as writeFileSync7
84684
84814
  } from "fs";
84685
- import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute2, join as join19, relative, resolve as resolve8 } from "path";
84815
+ import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute2, join as join20, relative, resolve as resolve8 } from "path";
84686
84816
 
84687
84817
  // ../producer/src/utils/paths.ts
84688
84818
  import {
84689
84819
  basename as basename3,
84690
- join as join18,
84820
+ join as join19,
84691
84821
  resolve as nodeResolve,
84692
84822
  relative as nodeRelative,
84693
84823
  isAbsolute as nodeIsAbsolute
@@ -84725,7 +84855,7 @@ function formatExportFrameName(index, ext) {
84725
84855
  function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
84726
84856
  const absoluteProjectDir = nodeResolve(projectDir);
84727
84857
  const projectName = basename3(absoluteProjectDir);
84728
- const resolvedOutputPath = outputPath ?? join18(rendersDir, `${projectName}.mp4`);
84858
+ const resolvedOutputPath = outputPath ?? join19(rendersDir, `${projectName}.mp4`);
84729
84859
  const absoluteOutputPath = nodeResolve(resolvedOutputPath);
84730
84860
  return { absoluteProjectDir, absoluteOutputPath };
84731
84861
  }
@@ -84762,16 +84892,16 @@ function resolveDeviceScaleFactor(input) {
84762
84892
  return target.width / input.compositionWidth;
84763
84893
  }
84764
84894
  function writeCompiledArtifacts(compiled, workDir, includeSummary) {
84765
- const compileDir = join19(workDir, "compiled");
84895
+ const compileDir = join20(workDir, "compiled");
84766
84896
  mkdirSync9(compileDir, { recursive: true });
84767
- writeFileSync7(join19(compileDir, "index.html"), compiled.html, "utf-8");
84897
+ writeFileSync7(join20(compileDir, "index.html"), compiled.html, "utf-8");
84768
84898
  for (const [srcPath, html] of compiled.subCompositions) {
84769
- const outPath = join19(compileDir, srcPath);
84899
+ const outPath = join20(compileDir, srcPath);
84770
84900
  mkdirSync9(dirname9(outPath), { recursive: true });
84771
84901
  writeFileSync7(outPath, html, "utf-8");
84772
84902
  }
84773
84903
  for (const [relativePath, absolutePath] of compiled.externalAssets) {
84774
- const outPath = resolve8(join19(compileDir, relativePath));
84904
+ const outPath = resolve8(join20(compileDir, relativePath));
84775
84905
  if (!isPathInside3(outPath, compileDir)) {
84776
84906
  console.warn(`[Render] Skipping external asset with unsafe path: ${relativePath}`);
84777
84907
  continue;
@@ -84802,7 +84932,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
84802
84932
  renderModeHints: compiled.renderModeHints,
84803
84933
  hasShaderTransitions: compiled.hasShaderTransitions
84804
84934
  };
84805
- writeFileSync7(join19(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
84935
+ writeFileSync7(join20(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
84806
84936
  }
84807
84937
  }
84808
84938
  function applyRenderModeHints(alreadyForced, compiled, log = defaultLogger) {
@@ -84829,7 +84959,7 @@ function updateJobStatus(job, status, stage, progress, onProgress) {
84829
84959
  }
84830
84960
  var materializePathModule = {
84831
84961
  resolve: resolve8,
84832
- join: join19,
84962
+ join: join20,
84833
84963
  dirname: dirname9,
84834
84964
  basename: basename4,
84835
84965
  relative,
@@ -85097,17 +85227,29 @@ import {
85097
85227
  renameSync as renameSync3,
85098
85228
  rmSync as rmSync7
85099
85229
  } from "fs";
85100
- import { basename as basename5, dirname as dirname10, extname as extname5, join as join20, resolve as resolve9 } from "path";
85230
+ import { basename as basename5, dirname as dirname10, extname as extname5, join as join21, resolve as resolve9 } from "path";
85231
+
85232
+ // ../producer/src/utils/ffprobe.ts
85233
+ init_src();
85234
+
85235
+ // ../producer/src/services/render/artifactTransaction.ts
85101
85236
  var defaultFileSystem = {
85102
85237
  existsSync: existsSync19,
85103
85238
  renameSync: renameSync3,
85104
85239
  rmSync: rmSync7
85105
85240
  };
85241
+ async function defaultArtifactDurationProbe(path) {
85242
+ const meta = await extractMediaMetadata(path);
85243
+ return {
85244
+ durationSeconds: meta.durationSeconds,
85245
+ frames: meta.frames
85246
+ };
85247
+ }
85106
85248
  function createSiblingTransactionDirectory(destination) {
85107
85249
  const parent = dirname10(destination);
85108
85250
  const extension = extname5(destination);
85109
85251
  const stem = extension ? basename5(destination, extension) : basename5(destination);
85110
- return mkdtempSync4(join20(parent, `.${stem}.hf-transaction-`));
85252
+ return mkdtempSync4(join21(parent, `.${stem}.hf-transaction-`));
85111
85253
  }
85112
85254
  function assertReadableNonEmptyFile(path) {
85113
85255
  const fd = openSync3(path, "r");
@@ -85125,7 +85267,7 @@ function collectDirectoryFiles(root) {
85125
85267
  const files = [];
85126
85268
  const visit = (directory) => {
85127
85269
  for (const entry of readdirSync8(directory, { withFileTypes: true })) {
85128
- const path = join20(directory, entry.name);
85270
+ const path = join21(directory, entry.name);
85129
85271
  if (entry.isDirectory()) visit(path);
85130
85272
  else if (entry.isFile()) files.push(path);
85131
85273
  }
@@ -85133,14 +85275,44 @@ function collectDirectoryFiles(root) {
85133
85275
  visit(root);
85134
85276
  return files;
85135
85277
  }
85278
+ function durationToleranceSeconds(expected) {
85279
+ if (expected.toleranceSeconds !== void 0) return expected.toleranceSeconds;
85280
+ const fps = expected.fps;
85281
+ return fps && fps > 0 ? 1 / fps : 0.02;
85282
+ }
85283
+ function assertUsableProbedDuration(probedSeconds, expectedSeconds, stagingPath) {
85284
+ if (!Number.isFinite(probedSeconds) || probedSeconds <= 0) {
85285
+ throw new Error(
85286
+ `Render artifact duration probe returned no usable duration for ${stagingPath} (got ${String(probedSeconds)}); expected ${expectedSeconds.toFixed(3)}s. Refusing to publish an artifact whose duration cannot be verified.`
85287
+ );
85288
+ }
85289
+ }
85290
+ function assertDurationWithinTolerance(expectedSeconds, probedSeconds, toleranceSeconds, stagingPath) {
85291
+ const deficit = expectedSeconds - probedSeconds;
85292
+ if (deficit <= toleranceSeconds) return;
85293
+ throw new Error(
85294
+ `Render artifact is truncated: expected ${expectedSeconds.toFixed(3)}s, probed ${probedSeconds.toFixed(3)}s (deficit ${deficit.toFixed(3)}s exceeds tolerance ${toleranceSeconds.toFixed(3)}s). Artifact: ${stagingPath}`
85295
+ );
85296
+ }
85297
+ function assertFrameCountWithinTolerance(expectedFrames, probedFrames, stagingPath) {
85298
+ if (probedFrames === void 0 || !Number.isFinite(probedFrames) || probedFrames <= 0) {
85299
+ return;
85300
+ }
85301
+ const shortfall = expectedFrames - probedFrames;
85302
+ if (shortfall <= 1) return;
85303
+ throw new Error(
85304
+ `Render artifact is truncated: expected ${expectedFrames} frames, probed ${probedFrames} frames (shortfall ${shortfall} frames). Artifact: ${stagingPath}`
85305
+ );
85306
+ }
85136
85307
  var ArtifactTransaction = class {
85137
- constructor(destinationPath, kind, fileSystem = defaultFileSystem) {
85308
+ constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe) {
85138
85309
  this.kind = kind;
85139
85310
  this.fileSystem = fileSystem;
85140
85311
  this.destinationPath = resolve9(destinationPath);
85141
85312
  this.transactionDirectory = createSiblingTransactionDirectory(this.destinationPath);
85142
- this.stagingPath = join20(this.transactionDirectory, basename5(this.destinationPath));
85143
- this.backupPath = join20(this.transactionDirectory, "backup");
85313
+ this.stagingPath = join21(this.transactionDirectory, basename5(this.destinationPath));
85314
+ this.backupPath = join21(this.transactionDirectory, "backup");
85315
+ this.durationProbe = durationProbe;
85144
85316
  }
85145
85317
  kind;
85146
85318
  fileSystem;
@@ -85149,9 +85321,11 @@ var ArtifactTransaction = class {
85149
85321
  transactionDirectory;
85150
85322
  backupPath;
85151
85323
  state = "active";
85152
- validate() {
85324
+ durationProbe;
85325
+ async validate(expected) {
85153
85326
  if (this.kind === "file") {
85154
85327
  assertReadableNonEmptyFile(this.stagingPath);
85328
+ if (expected) await this.assertArtifactDuration(expected);
85155
85329
  return;
85156
85330
  }
85157
85331
  let files;
@@ -85167,11 +85341,37 @@ var ArtifactTransaction = class {
85167
85341
  }
85168
85342
  for (const file of files) assertReadableNonEmptyFile(file);
85169
85343
  }
85170
- commit() {
85344
+ async assertArtifactDuration(expected) {
85345
+ const expectedSeconds = expected.expectedDurationSeconds;
85346
+ if (!Number.isFinite(expectedSeconds) || expectedSeconds <= 0) return;
85347
+ const probed = await this.runDurationProbe();
85348
+ assertUsableProbedDuration(probed.durationSeconds, expectedSeconds, this.stagingPath);
85349
+ const toleranceSeconds = durationToleranceSeconds(expected);
85350
+ assertDurationWithinTolerance(
85351
+ expectedSeconds,
85352
+ probed.durationSeconds,
85353
+ toleranceSeconds,
85354
+ this.stagingPath
85355
+ );
85356
+ if (expected.expectedFrames !== void 0) {
85357
+ assertFrameCountWithinTolerance(expected.expectedFrames, probed.frames, this.stagingPath);
85358
+ }
85359
+ }
85360
+ async runDurationProbe() {
85361
+ try {
85362
+ return await this.durationProbe(this.stagingPath);
85363
+ } catch (error) {
85364
+ throw new Error(
85365
+ `Render artifact duration probe failed for ${this.stagingPath}: ${error instanceof Error ? error.message : String(error)}`,
85366
+ { cause: error }
85367
+ );
85368
+ }
85369
+ }
85370
+ async commit() {
85171
85371
  if (this.state !== "active") {
85172
85372
  throw new Error(`Cannot commit an artifact transaction in state ${this.state}`);
85173
85373
  }
85174
- this.validate();
85374
+ await this.validate();
85175
85375
  const hadDestination = this.fileSystem.existsSync(this.destinationPath);
85176
85376
  if (this.kind === "file" || !hadDestination) {
85177
85377
  this.fileSystem.renameSync(this.stagingPath, this.destinationPath);
@@ -85676,7 +85876,7 @@ function resolveVideoCaptureBeyondViewport(videoCount) {
85676
85876
  // ../producer/src/services/render/captureCost.ts
85677
85877
  init_dist2();
85678
85878
  init_src();
85679
- import { join as join21 } from "path";
85879
+ import { join as join22 } from "path";
85680
85880
  var CAPTURE_CALIBRATION_TARGET_MS = 600;
85681
85881
  var MAX_MEASURED_CAPTURE_COST_MULTIPLIER = 8;
85682
85882
  var CAPTURE_CALIBRATION_PROTOCOL_TIMEOUT_MS = 3e4;
@@ -85912,7 +86112,7 @@ async function runCaptureCalibration(input) {
85912
86112
  });
85913
86113
  let calibration;
85914
86114
  try {
85915
- calibration = await runOneCalibration(join21(workDir, "capture-calibration"), calibrationCfg);
86115
+ calibration = await runOneCalibration(join22(workDir, "capture-calibration"), calibrationCfg);
85916
86116
  } catch (error) {
85917
86117
  const shouldFallback = !forceScreenshot && shouldFallbackToScreenshotAfterCalibrationError(error);
85918
86118
  if (!shouldFallback) {
@@ -85945,7 +86145,7 @@ async function runCaptureCalibration(input) {
85945
86145
  const screenshotCfg = createCaptureCalibrationConfig({ ...cfg, forceScreenshot: true });
85946
86146
  try {
85947
86147
  calibration = await runOneCalibration(
85948
- join21(workDir, "capture-calibration-screenshot"),
86148
+ join22(workDir, "capture-calibration-screenshot"),
85949
86149
  screenshotCfg
85950
86150
  );
85951
86151
  } catch (fallbackError) {
@@ -86366,7 +86566,7 @@ function countAuthoredTimedClips(html) {
86366
86566
 
86367
86567
  // ../producer/src/services/render/stages/compileStage.ts
86368
86568
  init_dist2();
86369
- import { join as join27 } from "path";
86569
+ import { join as join28 } from "path";
86370
86570
 
86371
86571
  // ../producer/src/services/htmlCompiler.ts
86372
86572
  init_esm10();
@@ -86376,10 +86576,7 @@ init_compiler();
86376
86576
  init_subCompositionValidity();
86377
86577
  init_assetResolution();
86378
86578
  import { createReadStream as createReadStream4, existsSync as existsSync25, mkdirSync as mkdirSync13, readFileSync as readFileSync10 } from "fs";
86379
- import { join as join26, dirname as dirname13, resolve as resolve12, basename as basename6 } from "path";
86380
-
86381
- // ../producer/src/utils/ffprobe.ts
86382
- init_src();
86579
+ import { join as join27, dirname as dirname13, resolve as resolve12, basename as basename6 } from "path";
86383
86580
 
86384
86581
  // ../producer/src/services/renderMediaCollector.ts
86385
86582
  init_esm10();
@@ -86392,7 +86589,7 @@ function parseNumeric2(value) {
86392
86589
  const parsed = Number.parseFloat(value);
86393
86590
  return Number.isFinite(parsed) ? parsed : null;
86394
86591
  }
86395
- function resolveHostWindow(element) {
86592
+ function resolveHostWindow(element, document2, startCache, visiting) {
86396
86593
  const hosts = [];
86397
86594
  for (let ancestor2 = element.parentElement; ancestor2; ancestor2 = ancestor2.parentElement) {
86398
86595
  if (ancestor2.hasAttribute(COMPOSITION_HOST_ATTR)) hosts.push(ancestor2);
@@ -86401,7 +86598,7 @@ function resolveHostWindow(element) {
86401
86598
  let offset2 = 0;
86402
86599
  let limit = Infinity;
86403
86600
  for (const host of hosts.reverse()) {
86404
- const hostStart = parseNumeric2(host.getAttribute("data-start")) ?? 0;
86601
+ const hostStart = resolveReferencedStart(document2, host, startCache, visiting);
86405
86602
  const hostEnd = parseNumeric2(host.getAttribute("data-end"));
86406
86603
  if (hostEnd != null) limit = Math.min(limit, offset2 + hostEnd);
86407
86604
  offset2 += hostStart;
@@ -86411,10 +86608,15 @@ function resolveHostWindow(element) {
86411
86608
  function collectHostWindows(html) {
86412
86609
  const { document: document2 } = parseHTML(html);
86413
86610
  const windows = /* @__PURE__ */ new Map();
86611
+ const startCache = /* @__PURE__ */ new Map();
86612
+ const visiting = /* @__PURE__ */ new Set();
86414
86613
  for (const element of document2.querySelectorAll(`[${MEDIA_RENDER_ID_ATTR}]`)) {
86415
86614
  const renderId = element.getAttribute(MEDIA_RENDER_ID_ATTR);
86416
86615
  if (!renderId) continue;
86417
- windows.set(renderId, resolveHostWindow(element));
86616
+ windows.set(
86617
+ renderId,
86618
+ resolveHostWindow(element, document2, startCache, visiting)
86619
+ );
86418
86620
  }
86419
86621
  return windows;
86420
86622
  }
@@ -86462,7 +86664,7 @@ init_src();
86462
86664
  import { createHash as createHash7 } from "crypto";
86463
86665
  import { existsSync as existsSync22, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
86464
86666
  import { homedir as homedir5, tmpdir as tmpdir5 } from "os";
86465
- import { join as join24 } from "path";
86667
+ import { join as join25 } from "path";
86466
86668
 
86467
86669
  // ../core/dist/fonts/aliases.js
86468
86670
  init_dist();
@@ -86471,7 +86673,7 @@ init_dist();
86471
86673
  import { execFileSync as execFileSync2 } from "child_process";
86472
86674
  import { existsSync as existsSync20, lstatSync as lstatSync3, readdirSync as readdirSync9, realpathSync as realpathSync3 } from "fs";
86473
86675
  import { homedir as homedir3, platform as platform2 } from "os";
86474
- import { join as join22, resolve as resolve10 } from "path";
86676
+ import { join as join23, resolve as resolve10 } from "path";
86475
86677
  var SYSTEM_FONT_SIZE_LIMIT = 5 * 1024 * 1024;
86476
86678
  var PROFILER_TIMEOUT_MS = 5e3;
86477
86679
  var FONT_EXT_RE = /\.(otf|ttf|ttc|woff2?)$/i;
@@ -86568,7 +86770,7 @@ function fontDirectories() {
86568
86770
  const home = homedir3();
86569
86771
  if (platform2() === "darwin") {
86570
86772
  return [
86571
- join22(home, "Library", "Fonts"),
86773
+ join23(home, "Library", "Fonts"),
86572
86774
  "/Library/Fonts",
86573
86775
  "/System/Library/Fonts",
86574
86776
  "/System/Library/Fonts/Supplemental"
@@ -86576,13 +86778,13 @@ function fontDirectories() {
86576
86778
  }
86577
86779
  if (platform2() === "win32") {
86578
86780
  return [
86579
- join22(process.env.WINDIR || "C:\\Windows", "Fonts"),
86580
- join22(process.env.LOCALAPPDATA || join22(homedir3(), "AppData", "Local"), "Microsoft", "Windows", "Fonts")
86781
+ join23(process.env.WINDIR || "C:\\Windows", "Fonts"),
86782
+ join23(process.env.LOCALAPPDATA || join23(homedir3(), "AppData", "Local"), "Microsoft", "Windows", "Fonts")
86581
86783
  ];
86582
86784
  }
86583
86785
  return [
86584
- join22(home, ".fonts"),
86585
- join22(home, ".local", "share", "fonts"),
86786
+ join23(home, ".fonts"),
86787
+ join23(home, ".local", "share", "fonts"),
86586
86788
  "/usr/local/share/fonts",
86587
86789
  "/usr/share/fonts"
86588
86790
  ];
@@ -86593,7 +86795,7 @@ function collectFontFileEntries(dir, depth = 0) {
86593
86795
  const entries2 = [];
86594
86796
  try {
86595
86797
  for (const entry of readdirSync9(dir, { withFileTypes: true })) {
86596
- const fullPath = join22(dir, entry.name);
86798
+ const fullPath = join23(dir, entry.name);
86597
86799
  if (entry.isDirectory()) {
86598
86800
  entries2.push(...collectFontFileEntries(fullPath, depth + 1));
86599
86801
  continue;
@@ -87241,17 +87443,17 @@ function resolveFontCacheRoot() {
87241
87443
  return process.env.HYPERFRAMES_FONT_CACHE_DIR;
87242
87444
  }
87243
87445
  if (process.env.AWS_LAMBDA_FUNCTION_NAME) {
87244
- lambdaFontCacheRoot ??= mkdtempSync5(join24(tmpdir5(), "hyperframes-fonts-"));
87446
+ lambdaFontCacheRoot ??= mkdtempSync5(join25(tmpdir5(), "hyperframes-fonts-"));
87245
87447
  return lambdaFontCacheRoot;
87246
87448
  }
87247
- return join24(homedir5(), ".cache", "hyperframes", "fonts");
87449
+ return join25(homedir5(), ".cache", "hyperframes", "fonts");
87248
87450
  }
87249
87451
  var WOFF2_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
87250
87452
  function fontSlug(familyName) {
87251
87453
  return familyName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
87252
87454
  }
87253
87455
  function fontCacheDir(slug) {
87254
- const dir = join24(resolveFontCacheRoot(), slug);
87456
+ const dir = join25(resolveFontCacheRoot(), slug);
87255
87457
  if (!existsSync22(dir)) {
87256
87458
  mkdirSync11(dir, { recursive: true });
87257
87459
  }
@@ -87261,7 +87463,7 @@ function subsetToken(woff2Url) {
87261
87463
  return createHash7("sha1").update(woff2Url).digest("hex").slice(0, 12);
87262
87464
  }
87263
87465
  function cachedWoff2Path(slug, weight, style, subset) {
87264
- return join24(fontCacheDir(slug), `${weight}-${style}-${subset}.woff2`);
87466
+ return join25(fontCacheDir(slug), `${weight}-${style}-${subset}.woff2`);
87265
87467
  }
87266
87468
  var FONT_FETCH_FAILED = "FONT_FETCH_FAILED";
87267
87469
  var FONT_FETCH_UNAVAILABLE = "FONT_FETCH_UNAVAILABLE";
@@ -87578,7 +87780,7 @@ import {
87578
87780
  rmSync as rmSync9,
87579
87781
  statSync as statSync8
87580
87782
  } from "fs";
87581
- import { dirname as dirname12, isAbsolute as isAbsolute3, join as join25, resolve as resolve11 } from "path";
87783
+ import { dirname as dirname12, isAbsolute as isAbsolute3, join as join26, resolve as resolve11 } from "path";
87582
87784
  var PREPARED_GIF_SUBDIR = "_animated_gif";
87583
87785
  var CACHE_SCHEMA = "hfgif-v1";
87584
87786
  function splitUrlSuffix(src) {
@@ -87812,7 +88014,7 @@ function replaceImageWithVideo(input) {
87812
88014
  return video;
87813
88015
  }
87814
88016
  async function prepareAnimatedGifInputs(html, options) {
87815
- const outputDir = options.outputDir ?? join25(options.downloadDir, PREPARED_GIF_SUBDIR);
88017
+ const outputDir = options.outputDir ?? join26(options.downloadDir, PREPARED_GIF_SUBDIR);
87816
88018
  const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_GIF_SUBDIR);
87817
88019
  const cacheDir = options.cacheDir ?? outputDir;
87818
88020
  const { document: document2 } = parseHTML(html);
@@ -87834,8 +88036,8 @@ async function prepareAnimatedGifInputs(html, options) {
87834
88036
  const { loopIterations, padSeconds } = resolvePreparedPlayback(metadata, loop, windowSeconds);
87835
88037
  const hash2 = computePreparedGifHash(bytes, loopIterations, padSeconds);
87836
88038
  const filename = `${CACHE_SCHEMA}-${hash2.slice(0, 24)}.webm`;
87837
- const cachePath = join25(cacheDir, filename);
87838
- const outputPath = join25(outputDir, filename);
88039
+ const cachePath = join26(cacheDir, filename);
88040
+ const outputPath = join26(outputDir, filename);
87839
88041
  const outputSrc = `${outputSrcPrefix}/${filename}`;
87840
88042
  await ensurePreparedWebm({
87841
88043
  sourcePath,
@@ -88504,7 +88706,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
88504
88706
  return { duration: null, resolvedPath: src };
88505
88707
  }
88506
88708
  } else if (!filePath.startsWith("/")) {
88507
- filePath = join26(baseDir, filePath);
88709
+ filePath = join27(baseDir, filePath);
88508
88710
  }
88509
88711
  if (!existsSync25(filePath)) {
88510
88712
  return { duration: null, resolvedPath: filePath };
@@ -89036,6 +89238,7 @@ function collectExternalAssets(html, projectDir) {
89036
89238
  }
89037
89239
  var REMOTE_MEDIA_SUBDIR = "_remote_media";
89038
89240
  var REMOTE_MEDIA_TAG_RE = /<(?:video|audio)\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
89241
+ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
89039
89242
  var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
89040
89243
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
89041
89244
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
@@ -89076,15 +89279,17 @@ async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLab
89076
89279
  }
89077
89280
  async function localizeRemoteMediaSources(html, downloadDir) {
89078
89281
  const urlSet = /* @__PURE__ */ new Set();
89079
- const re = new RegExp(REMOTE_MEDIA_TAG_RE.source, REMOTE_MEDIA_TAG_RE.flags);
89080
- let m;
89081
- while ((m = re.exec(html)) !== null) {
89082
- if (m[1]) urlSet.add(m[1]);
89282
+ for (const tagRe of [REMOTE_MEDIA_TAG_RE, REMOTE_SOURCE_TAG_RE]) {
89283
+ const re = new RegExp(tagRe.source, tagRe.flags);
89284
+ let m;
89285
+ while ((m = re.exec(html)) !== null) {
89286
+ if (m[1]) urlSet.add(m[1]);
89287
+ }
89083
89288
  }
89084
89289
  return downloadAndRewriteUrls(
89085
89290
  urlSet,
89086
89291
  html,
89087
- join26(downloadDir, REMOTE_MEDIA_SUBDIR),
89292
+ join27(downloadDir, REMOTE_MEDIA_SUBDIR),
89088
89293
  "Remote media download failed",
89089
89294
  "Localized remote media source(s)"
89090
89295
  );
@@ -89099,7 +89304,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
89099
89304
  return downloadAndRewriteUrls(
89100
89305
  urlSet,
89101
89306
  html,
89102
- join26(downloadDir, REMOTE_MEDIA_SUBDIR),
89307
+ join27(downloadDir, REMOTE_MEDIA_SUBDIR),
89103
89308
  "Remote image download failed",
89104
89309
  "Localized remote image source(s)"
89105
89310
  );
@@ -89115,7 +89320,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
89115
89320
  return downloadAndRewriteUrls(
89116
89321
  urlSet,
89117
89322
  html,
89118
- join26(downloadDir, REMOTE_MEDIA_SUBDIR),
89323
+ join27(downloadDir, REMOTE_MEDIA_SUBDIR),
89119
89324
  "Remote background-image download failed",
89120
89325
  "Localized remote background-image(s)",
89121
89326
  // Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
@@ -89250,7 +89455,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
89250
89455
  return downloadAndRewriteUrls(
89251
89456
  urlSet,
89252
89457
  processed,
89253
- join26(downloadDir, REMOTE_MEDIA_SUBDIR),
89458
+ join27(downloadDir, REMOTE_MEDIA_SUBDIR),
89254
89459
  "Remote font download failed",
89255
89460
  "Localized remote font face(s)",
89256
89461
  (h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
@@ -89499,7 +89704,7 @@ async function discoverMediaFromBrowser(page) {
89499
89704
  const htmlEl = el;
89500
89705
  const isImage = htmlEl.tagName.toLowerCase() === "img";
89501
89706
  const tagName19 = isImage ? "image" : htmlEl.tagName.toLowerCase() === "video" ? "video" : "audio";
89502
- const id = htmlEl.id || (isImage ? autoImageIds.get(htmlEl) : void 0);
89707
+ const id = htmlEl.getAttribute("data-hf-render-id") || htmlEl.id || (isImage ? autoImageIds.get(htmlEl) : void 0);
89503
89708
  if (!id) return;
89504
89709
  const src = htmlEl.currentSrc || htmlEl.src || htmlEl.getAttribute("src") || "";
89505
89710
  const start = parseFloat(htmlEl.getAttribute("data-start") || "0");
@@ -89546,7 +89751,7 @@ async function discoverAudioVolumeAutomationFromTimeline(page, audioIds, composi
89546
89751
  const sampleStep = 1 / Math.min(60, Math.max(1, sampleFps));
89547
89752
  const rawWindows = await page.evaluate((ids) => {
89548
89753
  return ids.flatMap((id) => {
89549
- const el = document.getElementById(id) ?? document.getElementById(id.replace(/-audio$/, ""));
89754
+ const el = window.__hfMediaEl?.(id) ?? document.getElementById(id) ?? document.getElementById(id.replace(/-audio$/, ""));
89550
89755
  if (!(el instanceof HTMLAudioElement) && !(el instanceof HTMLVideoElement)) return [];
89551
89756
  return [
89552
89757
  {
@@ -89605,7 +89810,7 @@ async function discoverAudioVolumeAutomationFromTimeline(page, audioIds, composi
89605
89810
  }
89606
89811
  };
89607
89812
  for (const { id, start, end } of clips2) {
89608
- const el = document.getElementById(id) ?? document.getElementById(id.replace(/-audio$/, ""));
89813
+ const el = window.__hfMediaEl?.(id) ?? document.getElementById(id) ?? document.getElementById(id.replace(/-audio$/, ""));
89609
89814
  if (!(el instanceof HTMLAudioElement) && !(el instanceof HTMLVideoElement)) continue;
89610
89815
  const sampleStart = Math.max(0, start);
89611
89816
  const sampleEnd = Math.min(duration, end);
@@ -89681,7 +89886,7 @@ async function discoverVideoVisibilityFromTimeline(page, compositionDuration) {
89681
89886
  const BINARY_PRECISION = 1 / 60;
89682
89887
  const entries2 = [];
89683
89888
  for (const videoEl of videos) {
89684
- const id = videoEl.id;
89889
+ const id = videoEl.getAttribute?.("data-hf-render-id") || videoEl.id;
89685
89890
  if (!id) continue;
89686
89891
  entries2.push({
89687
89892
  id,
@@ -89828,13 +90033,13 @@ async function runCompileStage(input) {
89828
90033
  abortSignal
89829
90034
  } = input;
89830
90035
  const compileStart = Date.now();
89831
- const compiled = await compileForRender(projectDir, htmlPath, join27(workDir, "downloads"), {
90036
+ const compiled = await compileForRender(projectDir, htmlPath, join28(workDir, "downloads"), {
89832
90037
  log,
89833
90038
  failClosedFontFetch: failClosedFontFetch === true,
89834
90039
  allowSystemFontCapture,
89835
90040
  abortSignal,
89836
90041
  variables: input.variables,
89837
- animatedGifCacheDir: cfg.extractCacheDir ? join27(cfg.extractCacheDir, "animated-gif") : void 0,
90042
+ animatedGifCacheDir: cfg.extractCacheDir ? join28(cfg.extractCacheDir, "animated-gif") : void 0,
89838
90043
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
89839
90044
  });
89840
90045
  assertNotAborted();
@@ -89896,7 +90101,7 @@ async function runCompileStage(input) {
89896
90101
  };
89897
90102
  await preflightCompositionAssetMediaTypes({
89898
90103
  projectDir,
89899
- compiledDir: join27(workDir, "compiled"),
90104
+ compiledDir: join28(workDir, "compiled"),
89900
90105
  composition,
89901
90106
  signal: abortSignal
89902
90107
  });
@@ -89944,7 +90149,7 @@ async function runCompileStage(input) {
89944
90149
  init_esm10();
89945
90150
  init_src();
89946
90151
  init_dist2();
89947
- import { join as join28 } from "path";
90152
+ import { join as join29 } from "path";
89948
90153
 
89949
90154
  // ../producer/src/services/render/stages/probeFailures.ts
89950
90155
  var NETWORK_FAILURE_PATTERN = /404|ERR_NAME_NOT_RESOLVED|ERR_CONNECTION_REFUSED|net::ERR_/i;
@@ -90045,7 +90250,7 @@ async function runProbeStage(input) {
90045
90250
  });
90046
90251
  fileServer = await createFileServer2({
90047
90252
  projectDir,
90048
- compiledDir: join28(workDir, "compiled"),
90253
+ compiledDir: join29(workDir, "compiled"),
90049
90254
  port: 0,
90050
90255
  preHeadScripts: [VIRTUAL_TIME_SHIM],
90051
90256
  fps: job.config.fps
@@ -90067,7 +90272,7 @@ async function runProbeStage(input) {
90067
90272
  log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
90068
90273
  probeSession = await createCaptureSession(
90069
90274
  fileServer.url,
90070
- join28(workDir, "probe"),
90275
+ join29(workDir, "probe"),
90071
90276
  captureOpts,
90072
90277
  null,
90073
90278
  probeCfg
@@ -90152,7 +90357,7 @@ async function runProbeStage(input) {
90152
90357
  });
90153
90358
  probeSession = await createCaptureSession(
90154
90359
  fileServer.url,
90155
- join28(workDir, "probe-screenshot"),
90360
+ join29(workDir, "probe-screenshot"),
90156
90361
  captureOpts,
90157
90362
  null,
90158
90363
  { ...probeCfg, forceScreenshot: true }
@@ -90188,7 +90393,7 @@ async function runProbeStage(input) {
90188
90393
  compiled,
90189
90394
  resolutions,
90190
90395
  projectDir,
90191
- join28(workDir, "downloads")
90396
+ join29(workDir, "downloads")
90192
90397
  );
90193
90398
  assertNotAborted();
90194
90399
  composition.videos = compiled.videos;
@@ -90359,7 +90564,7 @@ async function runProbeStage(input) {
90359
90564
  try {
90360
90565
  await preflightCompositionAssetMediaTypes({
90361
90566
  projectDir,
90362
- compiledDir: join28(workDir, "compiled"),
90567
+ compiledDir: join29(workDir, "compiled"),
90363
90568
  composition,
90364
90569
  signal: abortSignal
90365
90570
  });
@@ -90487,13 +90692,13 @@ function validateRenderDuration(input) {
90487
90692
  // ../producer/src/services/render/stages/extractVideosStage.ts
90488
90693
  init_src();
90489
90694
  import { existsSync as existsSync26 } from "fs";
90490
- import { join as join29 } from "path";
90695
+ import { join as join30 } from "path";
90491
90696
  function shouldCopyExtractedFrames(platform3) {
90492
90697
  return platform3 === "win32";
90493
90698
  }
90494
90699
  function resolveVideoExtractionPolicy(env = process.env) {
90495
90700
  const rawMode = env.HF_VIDEO_EXTRACTION_FAILURE_MODE?.trim().toLowerCase();
90496
- const failureMode = rawMode === "observe" || rawMode === "enforce" ? rawMode : "off";
90701
+ const failureMode = rawMode === "observe" || rawMode === "off" ? rawMode : "enforce";
90497
90702
  const maxTransientRetries = failureMode !== "off" && env.HF_VIDEO_EXTRACTION_MAX_RETRIES?.trim() === "1" ? 1 : 0;
90498
90703
  return { failureMode, maxTransientRetries };
90499
90704
  }
@@ -90668,7 +90873,7 @@ async function runExtractVideosStage(input) {
90668
90873
  // because short boundary counts can differ by one frame.
90669
90874
  {
90670
90875
  fps: job.config.fps,
90671
- outputDir: join29(compiledDir, "__hyperframes_video_frames"),
90876
+ outputDir: join30(compiledDir, "__hyperframes_video_frames"),
90672
90877
  format: job.config.videoFrameFormat ?? "auto",
90673
90878
  timelineEnd: composition.duration,
90674
90879
  maxTransientRetries: extractionPolicy.maxTransientRetries,
@@ -90732,7 +90937,7 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
90732
90937
 
90733
90938
  // ../producer/src/services/render/stages/audioStage.ts
90734
90939
  init_src();
90735
- import { join as join30 } from "path";
90940
+ import { join as join31 } from "path";
90736
90941
  async function runAudioStage(input) {
90737
90942
  const {
90738
90943
  projectDir,
@@ -90747,7 +90952,7 @@ async function runAudioStage(input) {
90747
90952
  log
90748
90953
  } = input;
90749
90954
  const stage3Start = Date.now();
90750
- const audioOutputPath = join30(workDir, MIXED_AUDIO_FILENAME);
90955
+ const audioOutputPath = join31(workDir, MIXED_AUDIO_FILENAME);
90751
90956
  let hasAudio = false;
90752
90957
  let audioError;
90753
90958
  let audioFailures;
@@ -90757,7 +90962,7 @@ async function runAudioStage(input) {
90757
90962
  audioResult = await processCompositionAudio(
90758
90963
  audios,
90759
90964
  projectDir,
90760
- join30(workDir, "audio-work"),
90965
+ join31(workDir, "audio-work"),
90761
90966
  audioOutputPath,
90762
90967
  duration,
90763
90968
  abortSignal,
@@ -90991,7 +91196,7 @@ async function runCaptureStage(input) {
90991
91196
  init_src();
90992
91197
  import { mkdtemp as mkdtemp2, writeFile as writeFile2 } from "fs/promises";
90993
91198
  import { tmpdir as tmpdir6 } from "os";
90994
- import { join as join33 } from "path";
91199
+ import { join as join34 } from "path";
90995
91200
 
90996
91201
  // ../producer/src/services/render/stages/captureHdrFrameShared.ts
90997
91202
  init_src();
@@ -90999,7 +91204,7 @@ init_src();
90999
91204
  // ../producer/src/services/hdrCompositor.ts
91000
91205
  init_src();
91001
91206
  import { readSync as readSync2, closeSync as closeSync4 } from "fs";
91002
- import { join as join31 } from "path";
91207
+ import { join as join32 } from "path";
91003
91208
  function countNonZeroAlpha(rgba) {
91004
91209
  let n = 0;
91005
91210
  for (let p = 3; p < rgba.length; p += 4) {
@@ -91405,7 +91610,7 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
91405
91610
  if (shouldLog && debugDumpDir) {
91406
91611
  const after2 = countNonZeroRgb48(canvas);
91407
91612
  const dumpName = `frame_${String(debugFrameIndex).padStart(4, "0")}_layer_${String(layerIdx).padStart(2, "0")}_dom.png`;
91408
- const dumpPath = join31(debugDumpDir, dumpName);
91613
+ const dumpPath = join32(debugDumpDir, dumpName);
91409
91614
  writeFileExclusiveSync(dumpPath, domPng);
91410
91615
  log.info("[diag] dom layer blit", {
91411
91616
  frame: debugFrameIndex,
@@ -91453,7 +91658,7 @@ import {
91453
91658
  rmSync as rmSync10,
91454
91659
  statfsSync
91455
91660
  } from "fs";
91456
- import { join as join32 } from "path";
91661
+ import { join as join33 } from "path";
91457
91662
  var NO_FOLLOW_FLAG = constants2.O_NOFOLLOW ?? 0;
91458
91663
  function tempDirSafePrefix(id) {
91459
91664
  const safe = id.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 80);
@@ -91697,10 +91902,10 @@ async function extractHdrVideoFrames(args) {
91697
91902
  const window3 = extractionWindows.get(videoId);
91698
91903
  if (!video || !window3) continue;
91699
91904
  mkdirSync14(framesDir, { recursive: true });
91700
- const frameDir = mkdtempSync6(join32(framesDir, `hdr_${tempDirSafePrefix(videoId)}-`));
91905
+ const frameDir = mkdtempSync6(join33(framesDir, `hdr_${tempDirSafePrefix(videoId)}-`));
91701
91906
  createdFrameDirs.add(frameDir);
91702
91907
  const dims = prep.hdrExtractionDims.get(videoId) ?? { width, height };
91703
- const rawPath = join32(frameDir, "frames.rgb48le");
91908
+ const rawPath = join33(frameDir, "frames.rgb48le");
91704
91909
  const extractionStart = String(window3.extractionMediaStart ?? window3.mediaStart);
91705
91910
  const ffmpegArgs = [];
91706
91911
  if (window3.finalFrameOnly) {
@@ -92161,11 +92366,11 @@ function createDrainFrameGuard(args) {
92161
92366
  return buf;
92162
92367
  }
92163
92368
  if (db < verifyMinDb) {
92164
- const dumpDir = await mkdtemp2(join33(tmpdir6(), "hf-de-verify-fail-")).catch(() => null);
92369
+ const dumpDir = await mkdtemp2(join34(tmpdir6(), "hf-de-verify-fail-")).catch(() => null);
92165
92370
  if (dumpDir) {
92166
92371
  await Promise.all([
92167
- writeFile2(join33(dumpDir, `frame-${idx}-de.jpg`), buf),
92168
- writeFile2(join33(dumpDir, `frame-${idx}-truth.jpg`), truth)
92372
+ writeFile2(join34(dumpDir, `frame-${idx}-de.jpg`), buf),
92373
+ writeFile2(join34(dumpDir, `frame-${idx}-truth.jpg`), truth)
92169
92374
  ]).catch(() => {
92170
92375
  });
92171
92376
  }
@@ -92560,7 +92765,7 @@ async function runCaptureStreamingStage(input) {
92560
92765
  init_src();
92561
92766
  init_dist2();
92562
92767
  import { existsSync as existsSync28, mkdirSync as mkdirSync15 } from "fs";
92563
- import { join as join37 } from "path";
92768
+ import { join as join38 } from "path";
92564
92769
 
92565
92770
  // ../producer/src/services/hdrImageTransferCache.ts
92566
92771
  init_src();
@@ -92624,7 +92829,7 @@ function createHdrImageTransferCache(options = {}) {
92624
92829
 
92625
92830
  // ../producer/src/services/render/stages/captureHdrSequentialLoop.ts
92626
92831
  init_src();
92627
- import { join as join34 } from "path";
92832
+ import { join as join35 } from "path";
92628
92833
  async function runSequentialLayeredFrameLoop(input) {
92629
92834
  const {
92630
92835
  job,
@@ -92745,7 +92950,7 @@ async function runSequentialLayeredFrameLoop(input) {
92745
92950
  );
92746
92951
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
92747
92952
  writeFileExclusiveSync(
92748
- join34(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
92953
+ join35(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
92749
92954
  normalCanvas
92750
92955
  );
92751
92956
  }
@@ -92780,12 +92985,12 @@ async function runSequentialLayeredFrameLoop(input) {
92780
92985
 
92781
92986
  // ../producer/src/services/render/stages/captureHdrHybridLoop.ts
92782
92987
  init_src();
92783
- import { join as join36 } from "path";
92988
+ import { join as join37 } from "path";
92784
92989
 
92785
92990
  // ../producer/src/services/shaderTransitionWorkerPool.ts
92786
92991
  import { Worker as Worker2 } from "worker_threads";
92787
92992
  import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL2 } from "url";
92788
- import { dirname as dirname14, join as join35 } from "path";
92993
+ import { dirname as dirname14, join as join36 } from "path";
92789
92994
  import { createRequire as createRequire2 } from "module";
92790
92995
  import { existsSync as existsSync27 } from "fs";
92791
92996
  import { cpus as cpus3 } from "os";
@@ -92799,9 +93004,9 @@ function resolveWorkerEntry(explicit) {
92799
93004
  return { path: override, isTs };
92800
93005
  }
92801
93006
  const moduleDir = dirname14(fileURLToPath4(import.meta.url));
92802
- const jsPath = join35(moduleDir, "shaderTransitionWorker.js");
93007
+ const jsPath = join36(moduleDir, "shaderTransitionWorker.js");
92803
93008
  if (existsSync27(jsPath)) return { path: jsPath, isTs: false };
92804
- const tsPath = join35(moduleDir, "shaderTransitionWorker.ts");
93009
+ const tsPath = join36(moduleDir, "shaderTransitionWorker.ts");
92805
93010
  return { path: tsPath, isTs: true };
92806
93011
  }
92807
93012
  function buildExecArgv(entryIsTs) {
@@ -93171,7 +93376,7 @@ async function runHybridLayeredFrameLoop(input) {
93171
93376
  );
93172
93377
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
93173
93378
  writeFileExclusiveSync(
93174
- join36(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
93379
+ join37(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
93175
93380
  canvas
93176
93381
  );
93177
93382
  }
@@ -93357,7 +93562,7 @@ async function runCaptureHdrStage(input) {
93357
93562
  if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
93358
93563
  }
93359
93564
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
93360
- const debugDumpDir = debugDumpEnabled ? join37(framesDir, "debug-composite") : null;
93565
+ const debugDumpDir = debugDumpEnabled ? join38(framesDir, "debug-composite") : null;
93361
93566
  if (debugDumpDir && !existsSync28(debugDumpDir)) {
93362
93567
  mkdirSync15(debugDumpDir, { recursive: true });
93363
93568
  }
@@ -93509,10 +93714,10 @@ async function runCaptureHdrStage(input) {
93509
93714
  // ../producer/src/services/render/stages/encodeStage.ts
93510
93715
  init_src();
93511
93716
  import { copyFileSync as copyFileSync5, existsSync as existsSync29, mkdirSync as mkdirSync16, readdirSync as readdirSync10, rmSync as rmSync11, statSync as statSync9 } from "fs";
93512
- import { dirname as dirname15, join as join39 } from "path";
93717
+ import { dirname as dirname15, join as join40 } from "path";
93513
93718
 
93514
93719
  // ../producer/src/services/render/stages/gifEncodeArgs.ts
93515
- import { join as join38 } from "path";
93720
+ import { join as join39 } from "path";
93516
93721
  function fpsToFfmpegArg2(fps) {
93517
93722
  return fps.den === 1 ? String(fps.num) : `${fps.num}/${fps.den}`;
93518
93723
  }
@@ -93524,7 +93729,7 @@ function buildGifPalettegenArgs(input) {
93524
93729
  "-framerate",
93525
93730
  fpsArg,
93526
93731
  "-i",
93527
- join38(input.framesDir, input.framePattern),
93732
+ join39(input.framesDir, input.framePattern),
93528
93733
  "-vf",
93529
93734
  `fps=${fpsArg},palettegen=stats_mode=diff${transparency}`,
93530
93735
  input.palettePath
@@ -93538,7 +93743,7 @@ function buildGifPaletteuseArgs(input) {
93538
93743
  "-framerate",
93539
93744
  fpsArg,
93540
93745
  "-i",
93541
- join38(input.framesDir, input.framePattern),
93746
+ join39(input.framesDir, input.framePattern),
93542
93747
  "-i",
93543
93748
  input.palettePath,
93544
93749
  "-lavfi",
@@ -93655,11 +93860,11 @@ async function runEncodeStage(input) {
93655
93860
  );
93656
93861
  }
93657
93862
  captured.forEach((name, i) => {
93658
- const dst = join39(outputPath, formatExportFrameName(i, "png"));
93659
- copyFileSync5(join39(framesDir, name), dst);
93863
+ const dst = join40(outputPath, formatExportFrameName(i, "png"));
93864
+ copyFileSync5(join40(framesDir, name), dst);
93660
93865
  });
93661
93866
  if (hasAudio && audioOutputPath && existsSync29(audioOutputPath)) {
93662
- copyFileSync5(audioOutputPath, join39(outputPath, MIXED_AUDIO_FILENAME));
93867
+ copyFileSync5(audioOutputPath, join40(outputPath, MIXED_AUDIO_FILENAME));
93663
93868
  log.info(
93664
93869
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME}`
93665
93870
  );
@@ -93678,7 +93883,7 @@ async function runEncodeStage(input) {
93678
93883
  const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
93679
93884
  fps: job.config.fps,
93680
93885
  loop,
93681
- palettePath: join39(dirname15(videoOnlyPath), "gif-palette.png"),
93886
+ palettePath: join40(dirname15(videoOnlyPath), "gif-palette.png"),
93682
93887
  preserveAlpha: needsAlpha,
93683
93888
  signal: abortSignal,
93684
93889
  timeout: engineCfg.ffmpegEncodeTimeout
@@ -94044,8 +94249,7 @@ async function runAssembleStage(input) {
94044
94249
  outputPath,
94045
94250
  abortSignal,
94046
94251
  {
94047
- audioCodec: "aac",
94048
- preserveAudioPrimingEditList: normalizeResult.operation !== "copy"
94252
+ audioCodec: "aac"
94049
94253
  },
94050
94254
  job.config.fps
94051
94255
  );
@@ -94083,7 +94287,7 @@ function sampleDirectoryBytes(dir) {
94083
94287
  continue;
94084
94288
  }
94085
94289
  for (const name of entries2) {
94086
- const full = join40(current2, name);
94290
+ const full = join41(current2, name);
94087
94291
  try {
94088
94292
  const st = statSync10(full);
94089
94293
  if (st.isDirectory()) {
@@ -94166,7 +94370,10 @@ function applyRenderWarningPolicy(job, captureWarnings, log = defaultLogger) {
94166
94370
  const hasAudioProcessingFailure = job.warnings.some(
94167
94371
  (warning) => warning.code === "audio_processing_failed"
94168
94372
  );
94169
- if (strictness === "strict" || hasAudioProcessingFailure) {
94373
+ const hasSubTimelineScriptFailure = job.warnings.some(
94374
+ (warning) => warning.code === "sub_timeline_script_failure"
94375
+ );
94376
+ if (strictness === "strict" || hasAudioProcessingFailure || hasSubTimelineScriptFailure) {
94170
94377
  throw new RenderQualityError(job.warnings);
94171
94378
  }
94172
94379
  }
@@ -94228,7 +94435,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
94228
94435
  const ranges = [];
94229
94436
  let rangeStart = null;
94230
94437
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
94231
- const framePath = join40(framesDir, formatCaptureFrameName(frameIndex, frameExt));
94438
+ const framePath = join41(framesDir, formatCaptureFrameName(frameIndex, frameExt));
94232
94439
  const missing = !existsSync30(framePath) || statSync10(framePath).size <= 8;
94233
94440
  if (missing && rangeStart === null) {
94234
94441
  rangeStart = frameIndex;
@@ -94251,7 +94458,7 @@ function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, ran
94251
94458
  workerId,
94252
94459
  startFrame: rangeStart + range.startFrame,
94253
94460
  endFrame: rangeStart + range.endFrame,
94254
- outputDir: join40(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`),
94461
+ outputDir: join41(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`),
94255
94462
  outputFrameOffset: rangeStart
94256
94463
  }));
94257
94464
  batches.push(batch);
@@ -94274,8 +94481,8 @@ function getNextRetryWorkerCount(currentWorkers) {
94274
94481
  return Math.max(1, Math.floor(currentWorkers / 2));
94275
94482
  }
94276
94483
  function resolveRenderWorkDirPrefix(outputPath, jobId, platform3 = process.platform, systemTempDir = tmpdir7()) {
94277
- if (platform3 === "win32") return join40(systemTempDir, "hf-render-");
94278
- return join40(dirname16(outputPath), `work-${jobId}-`);
94484
+ if (platform3 === "win32") return join41(systemTempDir, "hf-render-");
94485
+ return join41(dirname16(outputPath), `work-${jobId}-`);
94279
94486
  }
94280
94487
  var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
94281
94488
  function captureAttemptMadeProgress(attemptTargetFrameCount, remainingFrameCount) {
@@ -94304,7 +94511,7 @@ The composition is too large for the available memory. To reduce memory pressure
94304
94511
  function countCapturedFrames(totalFrames, framesDir, frameExt) {
94305
94512
  let captured = 0;
94306
94513
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
94307
- const framePath = join40(framesDir, formatCaptureFrameName(frameIndex, frameExt));
94514
+ const framePath = join41(framesDir, formatCaptureFrameName(frameIndex, frameExt));
94308
94515
  if (existsSync30(framePath)) captured++;
94309
94516
  }
94310
94517
  return captured;
@@ -94329,7 +94536,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
94329
94536
  reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
94330
94537
  });
94331
94538
  pendingTransientRetry = false;
94332
- const attemptWorkDir = join40(options.workDir, `capture-attempt-${attempt}`);
94539
+ const attemptWorkDir = join41(options.workDir, `capture-attempt-${attempt}`);
94333
94540
  const batches = missingRanges ? buildMissingFrameRetryBatches(
94334
94541
  missingRanges,
94335
94542
  currentWorkers,
@@ -94668,13 +94875,13 @@ function deVerifyFallbackTelemetry(err) {
94668
94875
  async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal) {
94669
94876
  const moduleDir = dirname16(fileURLToPath5(import.meta.url));
94670
94877
  const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve13(process.env.PRODUCER_RENDERS_DIR, "..") : resolve13(moduleDir, "../..");
94671
- const debugDir = join40(producerRoot, ".debug");
94878
+ const debugDir = join41(producerRoot, ".debug");
94672
94879
  const outputDir = dirname16(outputPath);
94673
94880
  if (!existsSync30(outputDir)) mkdirSync17(outputDir, { recursive: true });
94674
- const workDir = job.config.debug ? join40(debugDir, job.id) : mkdtempSync7(resolveRenderWorkDirPrefix(outputPath, job.id));
94881
+ const workDir = job.config.debug ? join41(debugDir, job.id) : mkdtempSync7(resolveRenderWorkDirPrefix(outputPath, job.id));
94675
94882
  const pipelineStart = Date.now();
94676
94883
  const baseLog = job.config.logger ?? defaultLogger;
94677
- const logPath = job.config.debug ? join40(workDir, "render.log") : null;
94884
+ const logPath = job.config.debug ? join41(workDir, "render.log") : null;
94678
94885
  const execution = new RenderExecutionContext({
94679
94886
  request: { renderJobId: job.id, projectDir, outputPath },
94680
94887
  logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
@@ -94722,7 +94929,7 @@ async function executeRenderPipeline(input) {
94722
94929
  imageDecodeFailures: 0
94723
94930
  };
94724
94931
  let hdrPerf;
94725
- const perfOutputPath = join40(workDir, "perf-summary.json");
94932
+ const perfOutputPath = join41(workDir, "perf-summary.json");
94726
94933
  const cfg = { ...job.config.producerConfig ?? resolveConfig() };
94727
94934
  const observability = new RenderObservabilityRecorder({
94728
94935
  pipelineStartMs: pipelineStart,
@@ -94814,15 +95021,15 @@ async function executeRenderPipeline(input) {
94814
95021
  requestedWorkers: job.config.workers ?? "auto"
94815
95022
  });
94816
95023
  const entryFile = job.config.entryFile || "index.html";
94817
- let htmlPath = join40(projectDir, entryFile);
95024
+ let htmlPath = join41(projectDir, entryFile);
94818
95025
  if (!existsSync30(htmlPath)) {
94819
95026
  throw new Error(`Entry file not found: ${htmlPath}`);
94820
95027
  }
94821
95028
  assertNotAborted();
94822
95029
  const rawEntry = readFileSync12(htmlPath, "utf-8");
94823
95030
  if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
94824
- const wrapperPath = join40(workDir, "standalone-entry.html");
94825
- const projectIndexPath = join40(projectDir, "index.html");
95031
+ const wrapperPath = join41(workDir, "standalone-entry.html");
95032
+ const projectIndexPath = join41(projectDir, "index.html");
94826
95033
  if (!existsSync30(projectIndexPath)) {
94827
95034
  throw new Error(
94828
95035
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
@@ -94977,7 +95184,7 @@ async function executeRenderPipeline(input) {
94977
95184
  beginFrameStalled: probeResult.beginFrameStalled
94978
95185
  });
94979
95186
  updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
94980
- const compiledDir = join40(workDir, "compiled");
95187
+ const compiledDir = join41(workDir, "compiled");
94981
95188
  const extractResult = await observeRenderStage(
94982
95189
  observability,
94983
95190
  "video_extract",
@@ -95097,7 +95304,7 @@ async function executeRenderPipeline(input) {
95097
95304
  try {
95098
95305
  fileServer = await createFileServer2({
95099
95306
  projectDir,
95100
- compiledDir: join40(workDir, "compiled"),
95307
+ compiledDir: join41(workDir, "compiled"),
95101
95308
  port: 0,
95102
95309
  preHeadScripts: [VIRTUAL_TIME_SHIM],
95103
95310
  fps: job.config.fps
@@ -95115,7 +95322,7 @@ async function executeRenderPipeline(input) {
95115
95322
  if (!activeFileServer) {
95116
95323
  throw new Error("File server failed to initialize before frame capture");
95117
95324
  }
95118
- const framesDir = join40(workDir, "captured-frames");
95325
+ const framesDir = join41(workDir, "captured-frames");
95119
95326
  if (!existsSync30(framesDir)) mkdirSync17(framesDir, { recursive: true });
95120
95327
  const resolvedBrowserGpuMode = await resolveBrowserGpuMode(cfg.browserGpuMode, {
95121
95328
  chromePath: resolveHeadlessShellPath(cfg),
@@ -95440,7 +95647,7 @@ async function executeRenderPipeline(input) {
95440
95647
  gif: ".gif"
95441
95648
  };
95442
95649
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
95443
- const videoOnlyPath = join40(workDir, `video-only${videoExt}`);
95650
+ const videoOnlyPath = join41(workDir, `video-only${videoExt}`);
95444
95651
  const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
95445
95652
  if (usePageSideCompositingForTransitions) {
95446
95653
  activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
@@ -95958,7 +96165,13 @@ async function executeRenderPipeline(input) {
95958
96165
  } else {
95959
96166
  observability.checkpoint("assemble", `skipped for ${outputFormat}`);
95960
96167
  }
95961
- artifactTransaction.validate();
96168
+ await artifactTransaction.validate(
96169
+ !isPngSequence && !isGif && Number.isFinite(job.duration) && job.duration > 0 ? {
96170
+ expectedDurationSeconds: job.duration,
96171
+ fps: fpsToNumber(job.config.fps),
96172
+ expectedFrames: captureTotalFrames
96173
+ } : void 0
96174
+ );
95962
96175
  const totalElapsed = Date.now() - pipelineStart;
95963
96176
  const tmpPeakBytes = existsSync30(workDir) ? sampleDirectoryBytes(workDir) : 0;
95964
96177
  recordTransientRetryObservability();
@@ -96026,11 +96239,11 @@ async function executeRenderPipeline(input) {
96026
96239
  }
96027
96240
  if (job.config.debug) {
96028
96241
  if (!isPngSequence && existsSync30(stagedOutputPath)) {
96029
- const debugOutput = join40(workDir, `output${videoExt}`);
96242
+ const debugOutput = join41(workDir, `output${videoExt}`);
96030
96243
  copyFileSync6(stagedOutputPath, debugOutput);
96031
96244
  }
96032
96245
  }
96033
- artifactTransaction.commit();
96246
+ await artifactTransaction.commit();
96034
96247
  job.outputPath = outputPath;
96035
96248
  updateJobStatus(job, "complete", "Render complete", 100, onProgress);
96036
96249
  await eventPublisher.flush();
@@ -96363,7 +96576,7 @@ import {
96363
96576
  rmSync as rmSync14,
96364
96577
  createReadStream as createReadStream5
96365
96578
  } from "fs";
96366
- import { resolve as resolve15, dirname as dirname18, join as join43 } from "path";
96579
+ import { resolve as resolve15, dirname as dirname18, join as join44 } from "path";
96367
96580
  import { tmpdir as tmpdir8 } from "os";
96368
96581
  import { parseArgs } from "util";
96369
96582
  import crypto from "crypto";
@@ -96374,7 +96587,7 @@ import { serve as serve3 } from "@hono/node-server";
96374
96587
  // ../producer/src/services/hyperframeLint.ts
96375
96588
  init_dist3();
96376
96589
  import { existsSync as existsSync31, readFileSync as readFileSync13, statSync as statSync11 } from "fs";
96377
- import { resolve as resolve14, join as join41 } from "path";
96590
+ import { resolve as resolve14, join as join42 } from "path";
96378
96591
  function isStringRecord(value) {
96379
96592
  if (!value || typeof value !== "object" || Array.isArray(value)) {
96380
96593
  return false;
@@ -96422,7 +96635,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
96422
96635
  }
96423
96636
  }
96424
96637
  return {
96425
- error: `No HTML entry file found in project directory: ${join41(absProjectDir, preferredEntryFile || "index.html")}`
96638
+ error: `No HTML entry file found in project directory: ${join42(absProjectDir, preferredEntryFile || "index.html")}`
96426
96639
  };
96427
96640
  }
96428
96641
  function prepareHyperframeLintBody(body) {
@@ -96465,7 +96678,7 @@ async function runHyperframeLint(prepared) {
96465
96678
  // ../producer/src/services/healthWorker.ts
96466
96679
  import { Worker as Worker3 } from "worker_threads";
96467
96680
  import { fileURLToPath as fileURLToPath6 } from "url";
96468
- import { dirname as dirname17, join as join42 } from "path";
96681
+ import { dirname as dirname17, join as join43 } from "path";
96469
96682
  import { existsSync as existsSync32 } from "fs";
96470
96683
  var DEFAULT_HEALTH_PORT = 9848;
96471
96684
  async function startHealthWorker(options = {}) {
@@ -96541,7 +96754,7 @@ function defaultLogger2() {
96541
96754
  }
96542
96755
  function resolveWorkerEntry2() {
96543
96756
  const here = dirname17(fileURLToPath6(import.meta.url));
96544
- const candidates = [join42(here, "healthWorkerThread.js"), join42(here, "healthWorkerThread.ts")];
96757
+ const candidates = [join43(here, "healthWorkerThread.js"), join43(here, "healthWorkerThread.ts")];
96545
96758
  for (const candidate of candidates) {
96546
96759
  if (existsSync32(candidate)) return candidate;
96547
96760
  }
@@ -96743,8 +96956,8 @@ async function resolveInlineRenderHtml(body) {
96743
96956
  }
96744
96957
  function materializeInlineProject(html, options) {
96745
96958
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir8();
96746
- const tempProjectDir = mkdtempSync8(join43(tempRoot, "producer-project-"));
96747
- writeFileSync11(join43(tempProjectDir, "index.html"), html, "utf-8");
96959
+ const tempProjectDir = mkdtempSync8(join44(tempRoot, "producer-project-"));
96960
+ writeFileSync11(join44(tempProjectDir, "index.html"), html, "utf-8");
96748
96961
  return {
96749
96962
  prepared: {
96750
96963
  input: { projectDir: tempProjectDir, ...options },
@@ -97193,11 +97406,11 @@ init_src();
97193
97406
  init_dist2();
97194
97407
  init_src();
97195
97408
  import { cpSync as cpSync2, existsSync as existsSync36, mkdirSync as mkdirSync20, renameSync as renameSync6, rmSync as rmSync15, writeFileSync as writeFileSync13 } from "fs";
97196
- import { join as join47, relative as relative4, sep as sep4 } from "path";
97409
+ import { join as join48, relative as relative4, sep as sep4 } from "path";
97197
97410
 
97198
97411
  // ../producer/src/services/render/stages/freezePlan.ts
97199
97412
  import { existsSync as existsSync34, mkdirSync as mkdirSync19, readFileSync as readFileSync14, readdirSync as readdirSync12, writeFileSync as writeFileSync12 } from "fs";
97200
- import { join as join44, relative as relative2, resolve as resolve16 } from "path";
97413
+ import { join as join45, relative as relative2, resolve as resolve16 } from "path";
97201
97414
 
97202
97415
  // ../producer/src/services/distributed/planProtocol.ts
97203
97416
  var PLAN_SCHEMA_VERSION = 1;
@@ -97239,7 +97452,7 @@ var FIELD_DELIMITER = Buffer.from([0]);
97239
97452
  // ../producer/src/services/distributed/shared.ts
97240
97453
  init_src();
97241
97454
  import { execFile as execFileCallback } from "child_process";
97242
- import { dirname as dirname19, join as join45 } from "path";
97455
+ import { dirname as dirname19, join as join46 } from "path";
97243
97456
  import { existsSync as existsSync35, readFileSync as readFileSync15 } from "fs";
97244
97457
  import { fileURLToPath as fileURLToPath7 } from "url";
97245
97458
  import { promisify as promisify3 } from "util";
@@ -97249,7 +97462,7 @@ var execFile3 = promisify3(execFileCallback);
97249
97462
  // ../producer/src/services/distributed/planSize.ts
97250
97463
  import { createHash as createHash10 } from "crypto";
97251
97464
  import { lstatSync as lstatSync4, readdirSync as readdirSync13 } from "fs";
97252
- import { extname as extname7, join as join46, relative as relative3 } from "path";
97465
+ import { extname as extname7, join as join47, relative as relative3 } from "path";
97253
97466
  var PLAN_ROOT_CATEGORY = {
97254
97467
  compiled: "compiled",
97255
97468
  [PLAN_AUDIO_RELATIVE_PATH]: "audio",
@@ -97261,9 +97474,9 @@ var PLAN_ROOT_CATEGORY = {
97261
97474
  var PLAN_DIR_SIZE_LIMIT_BYTES = 2 * 1024 * 1024 * 1024;
97262
97475
  var FREEZE_OWNED_PLAN_FILES = [
97263
97476
  "plan.json",
97264
- join47("meta", "composition.json"),
97265
- join47("meta", "encoder.json"),
97266
- join47("meta", "chunks.json")
97477
+ join48("meta", "composition.json"),
97478
+ join48("meta", "encoder.json"),
97479
+ join48("meta", "chunks.json")
97267
97480
  ];
97268
97481
 
97269
97482
  // ../producer/src/services/distributed/planV2.ts
@@ -97286,7 +97499,7 @@ import {
97286
97499
  } from "fs";
97287
97500
  import { createHash as createHash11 } from "crypto";
97288
97501
  import { tmpdir as tmpdir9 } from "os";
97289
- import { dirname as dirname21, isAbsolute as isAbsolute4, join as join50, relative as relative5, resolve as resolve17, sep as sep5 } from "path";
97502
+ import { dirname as dirname21, isAbsolute as isAbsolute4, join as join51, relative as relative5, resolve as resolve17, sep as sep5 } from "path";
97290
97503
 
97291
97504
  // ../producer/src/services/distributed/planV2Publisher.ts
97292
97505
  import {
@@ -97300,15 +97513,15 @@ import {
97300
97513
  statSync as statSync13,
97301
97514
  writeFileSync as writeFileSync14
97302
97515
  } from "fs";
97303
- import { dirname as dirname20, join as join49 } from "path";
97516
+ import { dirname as dirname20, join as join50 } from "path";
97304
97517
 
97305
97518
  // ../producer/src/services/distributed/planV2Layout.ts
97306
- import { join as join48 } from "path";
97519
+ import { join as join49 } from "path";
97307
97520
 
97308
97521
  // ../producer/src/services/distributed/planV2Execution.ts
97309
97522
  import { mkdtempSync as mkdtempSync11, rmSync as rmSync20 } from "fs";
97310
97523
  import { tmpdir as tmpdir10 } from "os";
97311
- import { join as join53 } from "path";
97524
+ import { join as join54 } from "path";
97312
97525
 
97313
97526
  // ../producer/src/services/distributed/assemble.ts
97314
97527
  init_src();
@@ -97323,19 +97536,19 @@ import {
97323
97536
  statSync as statSync15,
97324
97537
  writeFileSync as writeFileSync16
97325
97538
  } from "fs";
97326
- import { dirname as dirname22, join as join51 } from "path";
97539
+ import { dirname as dirname22, join as join52 } from "path";
97327
97540
 
97328
97541
  // ../producer/src/services/distributed/renderChunk.ts
97329
97542
  init_src();
97330
97543
  import { randomBytes as randomBytes2 } from "crypto";
97331
97544
  import { existsSync as existsSync40, mkdirSync as mkdirSync24, readFileSync as readFileSync18, readdirSync as readdirSync16, rmSync as rmSync19, writeFileSync as writeFileSync17 } from "fs";
97332
- import { extname as extname8, join as join52 } from "path";
97545
+ import { extname as extname8, join as join53 } from "path";
97333
97546
  init_src();
97334
97547
 
97335
97548
  // ../producer/src/services/distributed/projectHash.ts
97336
97549
  import { readdirSync as readdirSync17, readFileSync as readFileSync19 } from "fs";
97337
97550
  import { createHash as createHash12 } from "crypto";
97338
- import { join as join54, relative as relative6 } from "path";
97551
+ import { join as join55, relative as relative6 } from "path";
97339
97552
 
97340
97553
  // src/fontLocalize.ts
97341
97554
  function safeVersion(version2) {
@@ -97376,8 +97589,8 @@ async function runFontLocalize(io, localize) {
97376
97589
  }
97377
97590
 
97378
97591
  // src/version.ts
97379
- var VERSION = true ? "0.8.15" : "0.0.0-dev";
97380
- var PRODUCER_VERSION = true ? "0.8.15" : "0.0.0-dev";
97592
+ var VERSION = true ? "0.8.17" : "0.0.0-dev";
97593
+ var PRODUCER_VERSION = true ? "0.8.17" : "0.0.0-dev";
97381
97594
 
97382
97595
  // src/fontLocalizeCli.ts
97383
97596
  async function readStdin() {