hyperframes 0.4.14 → 0.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -54,7 +54,7 @@ var VERSION;
54
54
  var init_version = __esm({
55
55
  "src/version.ts"() {
56
56
  "use strict";
57
- VERSION = true ? "0.4.14" : "0.0.0-dev";
57
+ VERSION = true ? "0.4.15" : "0.0.0-dev";
58
58
  }
59
59
  });
60
60
 
@@ -9821,7 +9821,7 @@ import { get as httpsGet } from "https";
9821
9821
  import { pipeline } from "stream/promises";
9822
9822
  function downloadFile(url, dest) {
9823
9823
  const tmp = `${dest}.tmp`;
9824
- return new Promise((resolve35, reject) => {
9824
+ return new Promise((resolve37, reject) => {
9825
9825
  const follow = (u) => {
9826
9826
  httpsGet(u, (res) => {
9827
9827
  if (res.statusCode === 301 || res.statusCode === 302) {
@@ -9838,7 +9838,7 @@ function downloadFile(url, dest) {
9838
9838
  const file = createWriteStream(tmp);
9839
9839
  pipeline(res, file).then(() => {
9840
9840
  renameSync(tmp, dest);
9841
- resolve35();
9841
+ resolve37();
9842
9842
  }).catch((err) => {
9843
9843
  try {
9844
9844
  unlinkSync(tmp);
@@ -10556,7 +10556,7 @@ function hasNpx() {
10556
10556
  }
10557
10557
  }
10558
10558
  function runSkillsAdd(repo) {
10559
- return new Promise((resolve35, reject) => {
10559
+ return new Promise((resolve37, reject) => {
10560
10560
  const child = spawn("npx", ["skills", "add", repo, "--all"], {
10561
10561
  stdio: "inherit",
10562
10562
  timeout: 12e4,
@@ -10570,7 +10570,7 @@ function runSkillsAdd(repo) {
10570
10570
  env: { ...process.env, GIT_CLONE_PROTECTION_ACTIVE: "0" }
10571
10571
  });
10572
10572
  child.on("close", (code, signal) => {
10573
- if (code === 0) resolve35();
10573
+ if (code === 0) resolve37();
10574
10574
  else if (signal === "SIGINT" || code === 130) process.exit(0);
10575
10575
  else reject(new Error(`npx skills add exited with code ${code}`));
10576
10576
  });
@@ -14850,10 +14850,10 @@ function compareDocumentPosition(nodeA, nodeB) {
14850
14850
  function uniqueSort(nodes) {
14851
14851
  nodes = nodes.filter((node, i2, arr) => !arr.includes(node, i2 + 1));
14852
14852
  nodes.sort((a, b) => {
14853
- const relative5 = compareDocumentPosition(a, b);
14854
- if (relative5 & DocumentPosition.PRECEDING) {
14853
+ const relative6 = compareDocumentPosition(a, b);
14854
+ if (relative6 & DocumentPosition.PRECEDING) {
14855
14855
  return -1;
14856
- } else if (relative5 & DocumentPosition.FOLLOWING) {
14856
+ } else if (relative6 & DocumentPosition.FOLLOWING) {
14857
14857
  return 1;
14858
14858
  }
14859
14859
  return 0;
@@ -15315,8 +15315,8 @@ var init_custom_element_registry = __esm({
15315
15315
  } : (element) => element.localName === localName;
15316
15316
  registry.set(localName, { Class, check });
15317
15317
  if (waiting.has(localName)) {
15318
- for (const resolve35 of waiting.get(localName))
15319
- resolve35(Class);
15318
+ for (const resolve37 of waiting.get(localName))
15319
+ resolve37(Class);
15320
15320
  waiting.delete(localName);
15321
15321
  }
15322
15322
  ownerDocument.querySelectorAll(
@@ -15356,13 +15356,13 @@ var init_custom_element_registry = __esm({
15356
15356
  */
15357
15357
  whenDefined(localName) {
15358
15358
  const { registry, waiting } = this;
15359
- return new Promise((resolve35) => {
15359
+ return new Promise((resolve37) => {
15360
15360
  if (registry.has(localName))
15361
- resolve35(registry.get(localName).Class);
15361
+ resolve37(registry.get(localName).Class);
15362
15362
  else {
15363
15363
  if (!waiting.has(localName))
15364
15364
  waiting.set(localName, []);
15365
- waiting.get(localName).push(resolve35);
15365
+ waiting.get(localName).push(resolve37);
15366
15366
  }
15367
15367
  });
15368
15368
  }
@@ -21068,12 +21068,12 @@ var require_CSSValueExpression = __commonJS({
21068
21068
  return false;
21069
21069
  }
21070
21070
  };
21071
- CSSOM.CSSValueExpression.prototype._parseJSString = function(token, idx, sep3) {
21072
- var endIdx = this._findMatchedIdx(token, idx, sep3), text;
21071
+ CSSOM.CSSValueExpression.prototype._parseJSString = function(token, idx, sep4) {
21072
+ var endIdx = this._findMatchedIdx(token, idx, sep4), text;
21073
21073
  if (endIdx === -1) {
21074
21074
  return false;
21075
21075
  } else {
21076
- text = token.substring(idx, endIdx + sep3.length);
21076
+ text = token.substring(idx, endIdx + sep4.length);
21077
21077
  return {
21078
21078
  idx: endIdx,
21079
21079
  text
@@ -21113,15 +21113,15 @@ var require_CSSValueExpression = __commonJS({
21113
21113
  if (!isLegal) {
21114
21114
  return false;
21115
21115
  } else {
21116
- var sep3 = "/";
21117
- return this._parseJSString(token, idx, sep3);
21116
+ var sep4 = "/";
21117
+ return this._parseJSString(token, idx, sep4);
21118
21118
  }
21119
21119
  };
21120
- CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep3) {
21120
+ CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep4) {
21121
21121
  var startIdx = idx, endIdx;
21122
21122
  var NOT_FOUND = -1;
21123
21123
  while (true) {
21124
- endIdx = token.indexOf(sep3, startIdx + 1);
21124
+ endIdx = token.indexOf(sep4, startIdx + 1);
21125
21125
  if (endIdx === -1) {
21126
21126
  endIdx = NOT_FOUND;
21127
21127
  break;
@@ -24755,7 +24755,7 @@ function resolveConfig(overrides) {
24755
24755
  hdr: (() => {
24756
24756
  const raw = env("PRODUCER_HDR_TRANSFER");
24757
24757
  if (raw === "hlg" || raw === "pq") return { transfer: raw };
24758
- return void 0;
24758
+ return false;
24759
24759
  })(),
24760
24760
  hdrAutoDetect: envBool("PRODUCER_HDR_AUTO_DETECT", DEFAULT_CONFIG2.hdrAutoDetect),
24761
24761
  audioGain: envNum("PRODUCER_AUDIO_GAIN", DEFAULT_CONFIG2.audioGain),
@@ -25220,7 +25220,8 @@ async function injectVideoFramesBatch(page, updates) {
25220
25220
  let img = video.nextElementSibling;
25221
25221
  const isNewImage = !img || !img.classList.contains("__render_frame__");
25222
25222
  const computedStyle = window.getComputedStyle(video);
25223
- const computedOpacity = parseFloat(computedStyle.opacity) || 1;
25223
+ const opacityParsed = parseFloat(computedStyle.opacity);
25224
+ const computedOpacity = Number.isNaN(opacityParsed) ? 1 : opacityParsed;
25224
25225
  const sourceIsStatic = !computedStyle.position || computedStyle.position === "static";
25225
25226
  if (isNewImage) {
25226
25227
  img = document.createElement("img");
@@ -25266,7 +25267,6 @@ async function injectVideoFramesBatch(page, updates) {
25266
25267
  img.style.opacity = String(computedOpacity);
25267
25268
  img.style.visibility = "visible";
25268
25269
  video.style.setProperty("visibility", "hidden", "important");
25269
- video.style.setProperty("opacity", "0", "important");
25270
25270
  video.style.setProperty("pointer-events", "none", "important");
25271
25271
  }
25272
25272
  if (pendingDecodes.length > 0) {
@@ -25293,7 +25293,6 @@ async function syncVideoFrameVisibility(page, activeVideoIds) {
25293
25293
  } else {
25294
25294
  video.style.removeProperty("display");
25295
25295
  video.style.setProperty("visibility", "hidden", "important");
25296
- video.style.setProperty("opacity", "0", "important");
25297
25296
  video.style.setProperty("pointer-events", "none", "important");
25298
25297
  if (hasImg) {
25299
25298
  img.style.visibility = "hidden";
@@ -25397,7 +25396,7 @@ async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100)
25397
25396
  while (Date.now() < deadline) {
25398
25397
  const ready = Boolean(await page.evaluate(expression));
25399
25398
  if (ready) return true;
25400
- await new Promise((resolve35) => setTimeout(resolve35, intervalMs));
25399
+ await new Promise((resolve37) => setTimeout(resolve37, intervalMs));
25401
25400
  }
25402
25401
  return Boolean(await page.evaluate(expression));
25403
25402
  }
@@ -25628,9 +25627,15 @@ async function captureFrameToBuffer(session, frameIndex, time) {
25628
25627
  return { buffer, captureTimeMs };
25629
25628
  }
25630
25629
  async function closeCaptureSession(session) {
25631
- if (session.page) await session.page.close().catch(() => {
25632
- });
25633
- if (session.browser) await releaseBrowser(session.browser, session.config);
25630
+ if (!session.pageReleased && session.page) {
25631
+ await session.page.close().catch(() => {
25632
+ });
25633
+ session.pageReleased = true;
25634
+ }
25635
+ if (!session.browserReleased && session.browser) {
25636
+ await releaseBrowser(session.browser, session.config);
25637
+ session.browserReleased = true;
25638
+ }
25634
25639
  session.isInitialized = false;
25635
25640
  }
25636
25641
  function prepareCaptureSessionForReuse(session, outputDir, onBeforeCapture) {
@@ -25680,7 +25685,7 @@ var init_frameCapture = __esm({
25680
25685
  // ../engine/src/utils/gpuEncoder.ts
25681
25686
  import { spawn as spawn2 } from "child_process";
25682
25687
  async function detectGpuEncoder() {
25683
- return new Promise((resolve35) => {
25688
+ return new Promise((resolve37) => {
25684
25689
  const ffmpeg = spawn2("ffmpeg", ["-encoders"], {
25685
25690
  stdio: ["pipe", "pipe", "pipe"]
25686
25691
  });
@@ -25689,13 +25694,13 @@ async function detectGpuEncoder() {
25689
25694
  stdout2 += data.toString();
25690
25695
  });
25691
25696
  ffmpeg.on("close", () => {
25692
- if (stdout2.includes("h264_nvenc")) resolve35("nvenc");
25693
- else if (stdout2.includes("h264_videotoolbox")) resolve35("videotoolbox");
25694
- else if (stdout2.includes("h264_vaapi")) resolve35("vaapi");
25695
- else if (stdout2.includes("h264_qsv")) resolve35("qsv");
25696
- else resolve35(null);
25697
+ if (stdout2.includes("h264_nvenc")) resolve37("nvenc");
25698
+ else if (stdout2.includes("h264_videotoolbox")) resolve37("videotoolbox");
25699
+ else if (stdout2.includes("h264_vaapi")) resolve37("vaapi");
25700
+ else if (stdout2.includes("h264_qsv")) resolve37("qsv");
25701
+ else resolve37(null);
25697
25702
  });
25698
- ffmpeg.on("error", () => resolve35(null));
25703
+ ffmpeg.on("error", () => resolve37(null));
25699
25704
  });
25700
25705
  }
25701
25706
  async function getCachedGpuEncoder() {
@@ -25727,6 +25732,51 @@ var init_gpuEncoder = __esm({
25727
25732
  }
25728
25733
  });
25729
25734
 
25735
+ // ../engine/src/utils/hdr.ts
25736
+ function isHdrColorSpace(cs) {
25737
+ if (!cs) return false;
25738
+ return cs.colorPrimaries.includes("bt2020") || cs.colorSpace.includes("bt2020") || cs.colorTransfer === "smpte2084" || cs.colorTransfer === "arib-std-b67";
25739
+ }
25740
+ function detectTransfer(cs) {
25741
+ if (cs?.colorTransfer === "smpte2084") return "pq";
25742
+ return "hlg";
25743
+ }
25744
+ function getHdrEncoderColorParams(transfer, mastering = DEFAULT_HDR10_MASTERING) {
25745
+ const colorTrc = transfer === "pq" ? "smpte2084" : "arib-std-b67";
25746
+ const tagging = `colorprim=bt2020:transfer=${colorTrc}:colormatrix=bt2020nc`;
25747
+ const metadata = `master-display=${mastering.masterDisplay}:max-cll=${mastering.maxCll}`;
25748
+ return {
25749
+ colorPrimaries: "bt2020",
25750
+ colorTrc,
25751
+ colorspace: "bt2020nc",
25752
+ pixelFormat: "yuv420p10le",
25753
+ x265ColorParams: `${tagging}:${metadata}`,
25754
+ mastering
25755
+ };
25756
+ }
25757
+ function analyzeCompositionHdr(colorSpaces) {
25758
+ let hasPq = false;
25759
+ let hasHdr = false;
25760
+ for (const cs of colorSpaces) {
25761
+ if (!isHdrColorSpace(cs)) continue;
25762
+ hasHdr = true;
25763
+ if (cs?.colorTransfer === "smpte2084") hasPq = true;
25764
+ }
25765
+ if (!hasHdr) return { hasHdr: false, dominantTransfer: null };
25766
+ const dominantTransfer = hasPq ? "pq" : "hlg";
25767
+ return { hasHdr: true, dominantTransfer };
25768
+ }
25769
+ var DEFAULT_HDR10_MASTERING;
25770
+ var init_hdr = __esm({
25771
+ "../engine/src/utils/hdr.ts"() {
25772
+ "use strict";
25773
+ DEFAULT_HDR10_MASTERING = {
25774
+ masterDisplay: "G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(10000000,1)",
25775
+ maxCll: "1000,400"
25776
+ };
25777
+ }
25778
+ });
25779
+
25730
25780
  // ../engine/src/utils/runFfmpeg.ts
25731
25781
  import { spawn as spawn3 } from "child_process";
25732
25782
  async function runFfmpeg(args, opts) {
@@ -25734,7 +25784,7 @@ async function runFfmpeg(args, opts) {
25734
25784
  const signal = opts?.signal;
25735
25785
  const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
25736
25786
  const onStderr = opts?.onStderr;
25737
- return new Promise((resolve35) => {
25787
+ return new Promise((resolve37) => {
25738
25788
  const ffmpeg = spawn3("ffmpeg", args);
25739
25789
  let stderr = "";
25740
25790
  const onAbort = () => {
@@ -25760,7 +25810,7 @@ async function runFfmpeg(args, opts) {
25760
25810
  ffmpeg.on("close", (code) => {
25761
25811
  clearTimeout(timer);
25762
25812
  if (signal) signal.removeEventListener("abort", onAbort);
25763
- resolve35({
25813
+ resolve37({
25764
25814
  success: !signal?.aborted && code === 0,
25765
25815
  exitCode: code,
25766
25816
  stderr,
@@ -25770,7 +25820,7 @@ async function runFfmpeg(args, opts) {
25770
25820
  ffmpeg.on("error", (err) => {
25771
25821
  clearTimeout(timer);
25772
25822
  if (signal) signal.removeEventListener("abort", onAbort);
25773
- resolve35({
25823
+ resolve37({
25774
25824
  success: false,
25775
25825
  exitCode: null,
25776
25826
  stderr: err.message,
@@ -25830,6 +25880,12 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
25830
25880
  pixelFormat = "yuv420p",
25831
25881
  useGpu = false
25832
25882
  } = options;
25883
+ if (options.hdr && codec === "h264") {
25884
+ console.warn(
25885
+ "[chunkEncoder] HDR is not supported with codec=h264 (libx264 has no HDR support). Stripping HDR metadata and tagging output as SDR/BT.709. Use codec=h265 for HDR output."
25886
+ );
25887
+ options = { ...options, hdr: void 0 };
25888
+ }
25833
25889
  const args = [...inputArgs, "-r", String(fps)];
25834
25890
  const shouldUseGpu = useGpu && gpuEncoder !== null;
25835
25891
  if (codec === "h264" || codec === "h265") {
@@ -25868,7 +25924,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
25868
25924
  if (bitrate) args.push("-b:v", bitrate);
25869
25925
  else args.push("-crf", String(quality));
25870
25926
  const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
25871
- const colorParams = "colorprim=bt709:transfer=bt709:colormatrix=bt709";
25927
+ const colorParams = codec === "h265" && options.hdr ? getHdrEncoderColorParams(options.hdr.transfer).x265ColorParams : "colorprim=bt709:transfer=bt709:colormatrix=bt709";
25872
25928
  if (preset === "ultrafast") {
25873
25929
  args.push(xParamsFlag, `aq-mode=3:${colorParams}`);
25874
25930
  } else {
@@ -25892,16 +25948,30 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
25892
25948
  return [...args, "-y", outputPath];
25893
25949
  }
25894
25950
  if (codec === "h264" || codec === "h265") {
25895
- args.push(
25896
- "-colorspace:v",
25897
- "bt709",
25898
- "-color_primaries:v",
25899
- "bt709",
25900
- "-color_trc:v",
25901
- "bt709",
25902
- "-color_range",
25903
- "tv"
25904
- );
25951
+ if (options.hdr) {
25952
+ const transferTag = options.hdr.transfer === "pq" ? "smpte2084" : "arib-std-b67";
25953
+ args.push(
25954
+ "-colorspace:v",
25955
+ "bt2020nc",
25956
+ "-color_primaries:v",
25957
+ "bt2020",
25958
+ "-color_trc:v",
25959
+ transferTag,
25960
+ "-color_range",
25961
+ "tv"
25962
+ );
25963
+ } else {
25964
+ args.push(
25965
+ "-colorspace:v",
25966
+ "bt709",
25967
+ "-color_primaries:v",
25968
+ "bt709",
25969
+ "-color_trc:v",
25970
+ "bt709",
25971
+ "-color_range",
25972
+ "tv"
25973
+ );
25974
+ }
25905
25975
  if (gpuEncoder === "vaapi") {
25906
25976
  const vfIdx = args.indexOf("-vf");
25907
25977
  if (vfIdx !== -1) {
@@ -25941,7 +26011,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
25941
26011
  const inputPath = join19(framesDir, framePattern);
25942
26012
  const inputArgs = ["-framerate", String(options.fps), "-i", inputPath];
25943
26013
  const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
25944
- return new Promise((resolve35) => {
26014
+ return new Promise((resolve37) => {
25945
26015
  const ffmpeg = spawn4("ffmpeg", args);
25946
26016
  let stderr = "";
25947
26017
  const onAbort = () => {
@@ -25966,7 +26036,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
25966
26036
  if (signal) signal.removeEventListener("abort", onAbort);
25967
26037
  const durationMs = Date.now() - startTime;
25968
26038
  if (signal?.aborted) {
25969
- resolve35({
26039
+ resolve37({
25970
26040
  success: false,
25971
26041
  outputPath,
25972
26042
  durationMs,
@@ -25977,7 +26047,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
25977
26047
  return;
25978
26048
  }
25979
26049
  if (code !== 0) {
25980
- resolve35({
26050
+ resolve37({
25981
26051
  success: false,
25982
26052
  outputPath,
25983
26053
  durationMs,
@@ -25988,12 +26058,12 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
25988
26058
  return;
25989
26059
  }
25990
26060
  const fileSize = existsSync15(outputPath) ? statSync4(outputPath).size : 0;
25991
- resolve35({ success: true, outputPath, durationMs, framesEncoded: frameCount, fileSize });
26061
+ resolve37({ success: true, outputPath, durationMs, framesEncoded: frameCount, fileSize });
25992
26062
  });
25993
26063
  ffmpeg.on("error", (err) => {
25994
26064
  clearTimeout(timer);
25995
26065
  if (signal) signal.removeEventListener("abort", onAbort);
25996
- resolve35({
26066
+ resolve37({
25997
26067
  success: false,
25998
26068
  outputPath,
25999
26069
  durationMs: Date.now() - startTime,
@@ -26051,18 +26121,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
26051
26121
  let gpuEncoder = null;
26052
26122
  if (options.useGpu) gpuEncoder = await getCachedGpuEncoder();
26053
26123
  const args = buildEncoderArgs(options, inputArgs, chunkPath, gpuEncoder);
26054
- const chunkResult = await new Promise((resolve35) => {
26124
+ const chunkResult = await new Promise((resolve37) => {
26055
26125
  const ffmpeg = spawn4("ffmpeg", args);
26056
26126
  let stderr = "";
26057
26127
  ffmpeg.stderr.on("data", (d) => {
26058
26128
  stderr += d.toString();
26059
26129
  });
26060
26130
  ffmpeg.on("close", (code) => {
26061
- if (code === 0) resolve35({ success: true });
26062
- else resolve35({ success: false, error: `Chunk ${i2} encode failed: ${stderr.slice(-400)}` });
26131
+ if (code === 0) resolve37({ success: true });
26132
+ else resolve37({ success: false, error: `Chunk ${i2} encode failed: ${stderr.slice(-400)}` });
26063
26133
  });
26064
26134
  ffmpeg.on("error", (err) => {
26065
- resolve35({ success: false, error: `Chunk ${i2} encode error: ${err.message}` });
26135
+ resolve37({ success: false, error: `Chunk ${i2} encode error: ${err.message}` });
26066
26136
  });
26067
26137
  });
26068
26138
  if (!chunkResult.success) {
@@ -26092,18 +26162,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
26092
26162
  "-y",
26093
26163
  outputPath
26094
26164
  ];
26095
- const concatResult = await new Promise((resolve35) => {
26165
+ const concatResult = await new Promise((resolve37) => {
26096
26166
  const ffmpeg = spawn4("ffmpeg", concatArgs);
26097
26167
  let stderr = "";
26098
26168
  ffmpeg.stderr.on("data", (d) => {
26099
26169
  stderr += d.toString();
26100
26170
  });
26101
26171
  ffmpeg.on("close", (code) => {
26102
- if (code === 0) resolve35({ success: true });
26103
- else resolve35({ success: false, error: `Chunk concat failed: ${stderr.slice(-400)}` });
26172
+ if (code === 0) resolve37({ success: true });
26173
+ else resolve37({ success: false, error: `Chunk concat failed: ${stderr.slice(-400)}` });
26104
26174
  });
26105
26175
  ffmpeg.on("error", (err) => {
26106
- resolve35({ success: false, error: `Chunk concat error: ${err.message}` });
26176
+ resolve37({ success: false, error: `Chunk concat error: ${err.message}` });
26107
26177
  });
26108
26178
  });
26109
26179
  if (!concatResult.success) {
@@ -26185,6 +26255,7 @@ var init_chunkEncoder = __esm({
26185
26255
  "use strict";
26186
26256
  init_config2();
26187
26257
  init_gpuEncoder();
26258
+ init_hdr();
26188
26259
  init_runFfmpeg();
26189
26260
  init_gpuEncoder();
26190
26261
  ENCODER_PRESETS = {
@@ -26195,51 +26266,6 @@ var init_chunkEncoder = __esm({
26195
26266
  }
26196
26267
  });
26197
26268
 
26198
- // ../engine/src/utils/hdr.ts
26199
- function isHdrColorSpace(cs) {
26200
- if (!cs) return false;
26201
- return cs.colorPrimaries.includes("bt2020") || cs.colorSpace.includes("bt2020") || cs.colorTransfer === "smpte2084" || cs.colorTransfer === "arib-std-b67";
26202
- }
26203
- function detectTransfer(cs) {
26204
- if (cs?.colorTransfer === "smpte2084") return "pq";
26205
- return "hlg";
26206
- }
26207
- function getHdrEncoderColorParams(transfer, mastering = DEFAULT_HDR10_MASTERING) {
26208
- const colorTrc = transfer === "pq" ? "smpte2084" : "arib-std-b67";
26209
- const tagging = `colorprim=bt2020:transfer=${colorTrc}:colormatrix=bt2020nc`;
26210
- const metadata = `master-display=${mastering.masterDisplay}:max-cll=${mastering.maxCll}`;
26211
- return {
26212
- colorPrimaries: "bt2020",
26213
- colorTrc,
26214
- colorspace: "bt2020nc",
26215
- pixelFormat: "yuv420p10le",
26216
- x265ColorParams: `${tagging}:${metadata}`,
26217
- mastering
26218
- };
26219
- }
26220
- function analyzeCompositionHdr(colorSpaces) {
26221
- let hasPq = false;
26222
- let hasHdr = false;
26223
- for (const cs of colorSpaces) {
26224
- if (!isHdrColorSpace(cs)) continue;
26225
- hasHdr = true;
26226
- if (cs?.colorTransfer === "smpte2084") hasPq = true;
26227
- }
26228
- if (!hasHdr) return { hasHdr: false, dominantTransfer: null };
26229
- const dominantTransfer = hasPq ? "pq" : "hlg";
26230
- return { hasHdr: true, dominantTransfer };
26231
- }
26232
- var DEFAULT_HDR10_MASTERING;
26233
- var init_hdr = __esm({
26234
- "../engine/src/utils/hdr.ts"() {
26235
- "use strict";
26236
- DEFAULT_HDR10_MASTERING = {
26237
- masterDisplay: "G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(10000000,1)",
26238
- maxCll: "1000,400"
26239
- };
26240
- }
26241
- });
26242
-
26243
26269
  // ../engine/src/services/streamingEncoder.ts
26244
26270
  import { spawn as spawn5 } from "child_process";
26245
26271
  import { existsSync as existsSync16, mkdirSync as mkdirSync10, statSync as statSync5 } from "fs";
@@ -26247,37 +26273,37 @@ import { dirname as dirname6 } from "path";
26247
26273
  function createFrameReorderBuffer(startFrame, endFrame) {
26248
26274
  let cursor = startFrame;
26249
26275
  const pending = /* @__PURE__ */ new Map();
26250
- const enqueueAt = (frame, resolve35) => {
26276
+ const enqueueAt = (frame, resolve37) => {
26251
26277
  const list = pending.get(frame);
26252
26278
  if (list === void 0) {
26253
- pending.set(frame, [resolve35]);
26279
+ pending.set(frame, [resolve37]);
26254
26280
  } else {
26255
- list.push(resolve35);
26281
+ list.push(resolve37);
26256
26282
  }
26257
26283
  };
26258
26284
  const flushAt = (frame) => {
26259
26285
  const list = pending.get(frame);
26260
26286
  if (list === void 0) return;
26261
26287
  pending.delete(frame);
26262
- for (const resolve35 of list) resolve35();
26288
+ for (const resolve37 of list) resolve37();
26263
26289
  };
26264
- const waitForFrame = (frame) => new Promise((resolve35) => {
26290
+ const waitForFrame = (frame) => new Promise((resolve37) => {
26265
26291
  if (frame === cursor) {
26266
- resolve35();
26292
+ resolve37();
26267
26293
  return;
26268
26294
  }
26269
- enqueueAt(frame, resolve35);
26295
+ enqueueAt(frame, resolve37);
26270
26296
  });
26271
26297
  const advanceTo = (frame) => {
26272
26298
  cursor = frame;
26273
26299
  flushAt(frame);
26274
26300
  };
26275
- const waitForAllDone = () => new Promise((resolve35) => {
26301
+ const waitForAllDone = () => new Promise((resolve37) => {
26276
26302
  if (cursor >= endFrame) {
26277
- resolve35();
26303
+ resolve37();
26278
26304
  return;
26279
26305
  }
26280
- enqueueAt(endFrame, resolve35);
26306
+ enqueueAt(endFrame, resolve37);
26281
26307
  });
26282
26308
  return { waitForFrame, advanceTo, waitForAllDone };
26283
26309
  }
@@ -26439,7 +26465,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
26439
26465
  let stderr = "";
26440
26466
  let exitCode = null;
26441
26467
  let exitPromiseResolve = null;
26442
- const exitPromise = new Promise((resolve35) => exitPromiseResolve = resolve35);
26468
+ const exitPromise = new Promise((resolve37) => exitPromiseResolve = resolve37);
26443
26469
  ffmpeg.stderr?.on("data", (data) => {
26444
26470
  stderr += data.toString();
26445
26471
  });
@@ -26485,8 +26511,8 @@ Process error: ${err.message}`;
26485
26511
  if (signal) signal.removeEventListener("abort", onAbort);
26486
26512
  const stdin = ffmpeg.stdin;
26487
26513
  if (stdin && !stdin.destroyed) {
26488
- await new Promise((resolve35) => {
26489
- stdin.end(() => resolve35());
26514
+ await new Promise((resolve37) => {
26515
+ stdin.end(() => resolve37());
26490
26516
  });
26491
26517
  }
26492
26518
  await exitPromise;
@@ -26528,7 +26554,7 @@ import { spawn as spawn6 } from "child_process";
26528
26554
  import { readFileSync as readFileSync14 } from "fs";
26529
26555
  import { extname as extname4 } from "path";
26530
26556
  function runFfprobe(args) {
26531
- return new Promise((resolve35, reject) => {
26557
+ return new Promise((resolve37, reject) => {
26532
26558
  const proc = spawn6("ffprobe", args);
26533
26559
  let stdout2 = "";
26534
26560
  let stderr = "";
@@ -26542,7 +26568,7 @@ function runFfprobe(args) {
26542
26568
  if (code !== 0) {
26543
26569
  reject(new Error(`[FFmpeg] ffprobe exited with code ${code}: ${stderr}`));
26544
26570
  } else {
26545
- resolve35(stdout2);
26571
+ resolve37(stdout2);
26546
26572
  }
26547
26573
  });
26548
26574
  proc.on("error", (err) => {
@@ -26956,7 +26982,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
26956
26982
  args.push("-q:v", format === "jpg" ? String(Math.ceil((100 - quality) / 3)) : "0");
26957
26983
  if (format === "png") args.push("-compression_level", "6");
26958
26984
  args.push("-y", outputPattern);
26959
- return new Promise((resolve35, reject) => {
26985
+ return new Promise((resolve37, reject) => {
26960
26986
  const ffmpeg = spawn7("ffmpeg", args);
26961
26987
  let stderr = "";
26962
26988
  const onAbort = () => {
@@ -26991,7 +27017,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
26991
27017
  files.forEach((file, index) => {
26992
27018
  framePaths.set(index, join21(videoOutputDir, file));
26993
27019
  });
26994
- resolve35({
27020
+ resolve37({
26995
27021
  videoId,
26996
27022
  srcPath: videoPath,
26997
27023
  outputDir: videoOutputDir,
@@ -27013,8 +27039,9 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
27013
27039
  });
27014
27040
  });
27015
27041
  }
27016
- async function convertSdrToHdr(inputPath, outputPath, signal, config) {
27042
+ async function convertSdrToHdr(inputPath, outputPath, targetTransfer, signal, config) {
27017
27043
  const timeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
27044
+ const colorTrc = targetTransfer === "pq" ? "smpte2084" : "arib-std-b67";
27018
27045
  const args = [
27019
27046
  "-i",
27020
27047
  inputPath,
@@ -27023,7 +27050,7 @@ async function convertSdrToHdr(inputPath, outputPath, signal, config) {
27023
27050
  "-color_primaries",
27024
27051
  "bt2020",
27025
27052
  "-color_trc",
27026
- "arib-std-b67",
27053
+ colorTrc,
27027
27054
  "-colorspace",
27028
27055
  "bt2020nc",
27029
27056
  "-c:v",
@@ -27109,8 +27136,9 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
27109
27136
  return metadata.colorSpace;
27110
27137
  })
27111
27138
  );
27112
- const hasAnyHdr = videoColorSpaces.some(isHdrColorSpace);
27113
- if (hasAnyHdr) {
27139
+ const hdrInfo = analyzeCompositionHdr(videoColorSpaces);
27140
+ if (hdrInfo.hasHdr && hdrInfo.dominantTransfer) {
27141
+ const targetTransfer = hdrInfo.dominantTransfer;
27114
27142
  const convertDir = join21(options.outputDir, "_hdr_normalized");
27115
27143
  mkdirSync12(convertDir, { recursive: true });
27116
27144
  for (let i2 = 0; i2 < resolvedVideos.length; i2++) {
@@ -27121,7 +27149,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
27121
27149
  if (!entry) continue;
27122
27150
  const convertedPath = join21(convertDir, `${entry.video.id}_hdr.mp4`);
27123
27151
  try {
27124
- await convertSdrToHdr(entry.videoPath, convertedPath, signal, config);
27152
+ await convertSdrToHdr(entry.videoPath, convertedPath, targetTransfer, signal, config);
27125
27153
  entry.videoPath = convertedPath;
27126
27154
  } catch (err) {
27127
27155
  errors.push({
@@ -27424,7 +27452,6 @@ async function hideVideoElements(page, videoIds) {
27424
27452
  const el = document.getElementById(id);
27425
27453
  if (el) {
27426
27454
  el.style.setProperty("visibility", "hidden", "important");
27427
- el.style.setProperty("opacity", "0", "important");
27428
27455
  const img = document.getElementById(`__render_frame_${id}__`);
27429
27456
  if (img) img.style.setProperty("visibility", "hidden", "important");
27430
27457
  }
@@ -27438,7 +27465,6 @@ async function showVideoElements(page, videoIds) {
27438
27465
  const el = document.getElementById(id);
27439
27466
  if (el) {
27440
27467
  el.style.removeProperty("visibility");
27441
- el.style.removeProperty("opacity");
27442
27468
  const img = document.getElementById(`__render_frame_${id}__`);
27443
27469
  if (img) img.style.removeProperty("visibility");
27444
27470
  }
@@ -27465,8 +27491,10 @@ async function queryVideoElementBounds(page, videoIds) {
27465
27491
  }
27466
27492
  const rect = el.getBoundingClientRect();
27467
27493
  const style = window.getComputedStyle(el);
27468
- const zIndex = parseInt(style.zIndex) || 0;
27469
- const opacity = parseFloat(style.opacity) || 1;
27494
+ const zIndexParsed = parseInt(style.zIndex);
27495
+ const zIndex = Number.isNaN(zIndexParsed) ? 0 : zIndexParsed;
27496
+ const opacityParsed = parseFloat(style.opacity);
27497
+ const opacity = Number.isNaN(opacityParsed) ? 1 : opacityParsed;
27470
27498
  const transform = style.transform || "none";
27471
27499
  const visible = style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
27472
27500
  return {
@@ -27504,12 +27532,12 @@ async function queryElementStacking(page, nativeHdrIds) {
27504
27532
  function resolveRadius(value, el) {
27505
27533
  if (value.includes("%")) {
27506
27534
  const pct = parseFloat(value) / 100;
27507
- const htmlEl = el;
27508
- const w = htmlEl.offsetWidth || 0;
27509
- const h3 = htmlEl.offsetHeight || 0;
27535
+ const w = el instanceof HTMLElement ? el.offsetWidth : 0;
27536
+ const h3 = el instanceof HTMLElement ? el.offsetHeight : 0;
27510
27537
  return pct * Math.min(w, h3);
27511
27538
  }
27512
- return parseFloat(value) || 0;
27539
+ const parsed = parseFloat(value);
27540
+ return Number.isNaN(parsed) ? 0 : parsed;
27513
27541
  }
27514
27542
  const selfCs = window.getComputedStyle(node);
27515
27543
  const selfRadii = [
@@ -27593,8 +27621,7 @@ async function queryElementStacking(page, nativeHdrIds) {
27593
27621
  const style = window.getComputedStyle(el);
27594
27622
  const zIndex = getEffectiveZIndex(el);
27595
27623
  const isHdrEl = hdrSet.has(id);
27596
- const opacityStartNode = isHdrEl ? el.parentElement : el;
27597
- const opacity = opacityStartNode ? getEffectiveOpacity(opacityStartNode) : 1;
27624
+ const opacity = getEffectiveOpacity(el);
27598
27625
  const visible = style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
27599
27626
  const htmlEl = el instanceof HTMLElement ? el : null;
27600
27627
  results.push({
@@ -28237,11 +28264,11 @@ function createFileServer(options) {
28237
28264
  headers: { "Content-Type": contentType }
28238
28265
  });
28239
28266
  });
28240
- return new Promise((resolve35) => {
28267
+ return new Promise((resolve37) => {
28241
28268
  const server = serve({ fetch: app.fetch, port }, (info) => {
28242
28269
  const actualPort = info.port;
28243
28270
  const url = `http://localhost:${actualPort}`;
28244
- resolve35({
28271
+ resolve37({
28245
28272
  url,
28246
28273
  port: actualPort,
28247
28274
  close: () => server.close()
@@ -30502,8 +30529,20 @@ var init_hyperframeRuntimeLoader = __esm({
30502
30529
  // ../producer/src/services/fileServer.ts
30503
30530
  import { Hono as Hono3 } from "hono";
30504
30531
  import { serve as serve2 } from "@hono/node-server";
30505
- import { readFileSync as readFileSync18, existsSync as existsSync25, statSync as statSync7 } from "fs";
30506
- import { join as join27, extname as extname7 } from "path";
30532
+ import { readFileSync as readFileSync18, existsSync as existsSync25, realpathSync, statSync as statSync7 } from "fs";
30533
+ import { join as join27, extname as extname7, resolve as resolve13, sep as sep3 } from "path";
30534
+ function isPathInside(child, parent, options = {}) {
30535
+ const { resolveSymlinks = false, pathModule } = options;
30536
+ const resolveFn = pathModule?.resolve ?? resolve13;
30537
+ const separator = pathModule?.sep ?? sep3;
30538
+ const resolvedChild = resolveFn(child);
30539
+ const resolvedParent = resolveFn(parent);
30540
+ const normalizedChild = resolveSymlinks && existsSync25(resolvedChild) ? realpathSync.native(resolvedChild) : resolvedChild;
30541
+ const normalizedParent = resolveSymlinks && existsSync25(resolvedParent) ? realpathSync.native(resolvedParent) : resolvedParent;
30542
+ if (normalizedChild === normalizedParent) return true;
30543
+ const parentWithSep = normalizedParent.endsWith(separator) ? normalizedParent : normalizedParent + separator;
30544
+ return normalizedChild.startsWith(parentWithSep);
30545
+ }
30507
30546
  function stripEmbeddedRuntimeScripts3(html) {
30508
30547
  if (!html) return html;
30509
30548
  const scriptRe = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
@@ -30586,12 +30625,20 @@ function createFileServer2(options) {
30586
30625
  let requestPath = c2.req.path;
30587
30626
  if (requestPath === "/") requestPath = "/index.html";
30588
30627
  const relativePath = requestPath.replace(/^\//, "");
30589
- const compiledPath = compiledDir ? join27(compiledDir, relativePath) : null;
30590
- const hasCompiledFile = Boolean(
30591
- compiledPath && existsSync25(compiledPath) && statSync7(compiledPath).isFile()
30592
- );
30593
- const filePath = hasCompiledFile ? compiledPath : join27(projectDir, relativePath);
30594
- if (!existsSync25(filePath) || !statSync7(filePath).isFile()) {
30628
+ let filePath = null;
30629
+ if (compiledDir) {
30630
+ const candidate = join27(compiledDir, relativePath);
30631
+ if (existsSync25(candidate) && isPathInside(candidate, compiledDir, { resolveSymlinks: true }) && statSync7(candidate).isFile()) {
30632
+ filePath = candidate;
30633
+ }
30634
+ }
30635
+ if (!filePath) {
30636
+ const candidate = join27(projectDir, relativePath);
30637
+ if (existsSync25(candidate) && isPathInside(candidate, projectDir, { resolveSymlinks: true }) && statSync7(candidate).isFile()) {
30638
+ filePath = candidate;
30639
+ }
30640
+ }
30641
+ if (!filePath) {
30595
30642
  if (!/favicon\.ico$/i.test(requestPath)) {
30596
30643
  console.warn(`[FileServer] 404 Not Found: ${requestPath}`);
30597
30644
  }
@@ -30615,10 +30662,10 @@ function createFileServer2(options) {
30615
30662
  headers: { "Content-Type": contentType }
30616
30663
  });
30617
30664
  });
30618
- return new Promise((resolve35) => {
30665
+ return new Promise((resolve37) => {
30619
30666
  const connections = /* @__PURE__ */ new Set();
30620
30667
  const server = serve2({ fetch: app.fetch, port }, (info) => {
30621
- resolve35({
30668
+ resolve37({
30622
30669
  url: `http://localhost:${info.port}`,
30623
30670
  port: info.port,
30624
30671
  close: () => {
@@ -30961,10 +31008,10 @@ var init_ffprobe2 = __esm({
30961
31008
  });
30962
31009
 
30963
31010
  // ../producer/src/utils/paths.ts
30964
- import { resolve as resolve13, basename, join as join28, relative as relative2, isAbsolute as isAbsolute3 } from "path";
30965
- function isPathInside(childPath, parentPath) {
30966
- const absChild = resolve13(childPath);
30967
- const absParent = resolve13(parentPath);
31011
+ import { resolve as resolve14, basename, join as join28, relative as relative2, isAbsolute as isAbsolute3 } from "path";
31012
+ function isPathInside2(childPath, parentPath) {
31013
+ const absChild = resolve14(childPath);
31014
+ const absParent = resolve14(parentPath);
30968
31015
  if (absChild === absParent) return true;
30969
31016
  const rel = relative2(absParent, absChild);
30970
31017
  return rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel);
@@ -30980,17 +31027,17 @@ function toExternalAssetKey(absPath) {
30980
31027
  return "hf-ext/" + normalised;
30981
31028
  }
30982
31029
  function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
30983
- const absoluteProjectDir = resolve13(projectDir);
31030
+ const absoluteProjectDir = resolve14(projectDir);
30984
31031
  const projectName = basename(absoluteProjectDir);
30985
31032
  const resolvedOutputPath = outputPath ?? join28(rendersDir, `${projectName}.mp4`);
30986
- const absoluteOutputPath = resolve13(resolvedOutputPath);
31033
+ const absoluteOutputPath = resolve14(resolvedOutputPath);
30987
31034
  return { absoluteProjectDir, absoluteOutputPath };
30988
31035
  }
30989
31036
  var DEFAULT_RENDERS_DIR;
30990
31037
  var init_paths = __esm({
30991
31038
  "../producer/src/utils/paths.ts"() {
30992
31039
  "use strict";
30993
- DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve13(new URL(import.meta.url).pathname, "../../..", "renders");
31040
+ DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve14(new URL(import.meta.url).pathname, "../../..", "renders");
30994
31041
  }
30995
31042
  });
30996
31043
 
@@ -31394,7 +31441,7 @@ var init_deterministicFonts = __esm({
31394
31441
 
31395
31442
  // ../producer/src/services/htmlCompiler.ts
31396
31443
  import { readFileSync as readFileSync20, existsSync as existsSync27, mkdirSync as mkdirSync16 } from "fs";
31397
- import { join as join30, dirname as dirname9, resolve as resolve14 } from "path";
31444
+ import { join as join30, dirname as dirname9, resolve as resolve15 } from "path";
31398
31445
  import postcss from "postcss";
31399
31446
  function dedupeElementsById(elements) {
31400
31447
  const deduped = /* @__PURE__ */ new Map();
@@ -31519,7 +31566,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
31519
31566
  const elEnd = elEndRaw ? parseFloat(elEndRaw) : Infinity;
31520
31567
  const absoluteStart = parentOffset + elStart;
31521
31568
  const absoluteEnd = Math.min(parentEnd, isFinite(elEnd) ? parentOffset + elEnd : Infinity);
31522
- const filePath = resolve14(projectDir, srcPath);
31569
+ const filePath = resolve15(projectDir, srcPath);
31523
31570
  if (visited.has(filePath)) {
31524
31571
  continue;
31525
31572
  }
@@ -31732,7 +31779,7 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
31732
31779
  if (!srcPath) continue;
31733
31780
  let compHtml = subCompositions.get(srcPath) || null;
31734
31781
  if (!compHtml) {
31735
- const filePath = resolve14(projectDir, srcPath);
31782
+ const filePath = resolve15(projectDir, srcPath);
31736
31783
  if (existsSync27(filePath)) {
31737
31784
  compHtml = readFileSync20(filePath, "utf-8");
31738
31785
  }
@@ -31949,7 +31996,7 @@ ${safeText}
31949
31996
  return wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
31950
31997
  }
31951
31998
  function collectExternalAssets(html, projectDir) {
31952
- const absProjectDir = resolve14(projectDir);
31999
+ const absProjectDir = resolve15(projectDir);
31953
32000
  const externalAssets = /* @__PURE__ */ new Map();
31954
32001
  const CSS_URL_RE2 = /\burl\(\s*(["']?)([^)"']+)\1\s*\)/g;
31955
32002
  function processPath(rawPath) {
@@ -31957,8 +32004,8 @@ function collectExternalAssets(html, projectDir) {
31957
32004
  if (!trimmed || trimmed.startsWith("/") || trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("//") || trimmed.startsWith("data:") || trimmed.startsWith("#")) {
31958
32005
  return null;
31959
32006
  }
31960
- const absPath = resolve14(absProjectDir, trimmed);
31961
- if (isPathInside(absPath, absProjectDir)) {
32007
+ const absPath = resolve15(absProjectDir, trimmed);
32008
+ if (isPathInside2(absPath, absProjectDir)) {
31962
32009
  return null;
31963
32010
  }
31964
32011
  if (!existsSync27(absPath)) return null;
@@ -32038,7 +32085,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
32038
32085
  const images = dedupeElementsById([...mainImages, ...subImages]);
32039
32086
  for (const video of videos) {
32040
32087
  if (isHttpUrl(video.src)) continue;
32041
- const videoPath = resolve14(projectDir, video.src);
32088
+ const videoPath = resolve15(projectDir, video.src);
32042
32089
  const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
32043
32090
  Promise.all([analyzeKeyframeIntervals(videoPath), extractVideoMetadata(videoPath)]).then(([analysis, metadata]) => {
32044
32091
  if (analysis.isProblematic) {
@@ -32244,7 +32291,7 @@ import {
32244
32291
  copyFileSync as copyFileSync2,
32245
32292
  appendFileSync
32246
32293
  } from "fs";
32247
- import { join as join31, dirname as dirname10, resolve as resolve15 } from "path";
32294
+ import { join as join31, dirname as dirname10, resolve as resolve16 } from "path";
32248
32295
  import { randomUUID as randomUUID2 } from "crypto";
32249
32296
  import { freemem as freemem2 } from "os";
32250
32297
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -32325,8 +32372,8 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
32325
32372
  writeFileSync10(outPath, html, "utf-8");
32326
32373
  }
32327
32374
  for (const [relativePath, absolutePath] of compiled.externalAssets) {
32328
- const outPath = resolve15(join31(compileDir, relativePath));
32329
- if (!isPathInside(outPath, compileDir)) {
32375
+ const outPath = resolve16(join31(compileDir, relativePath));
32376
+ if (!isPathInside2(outPath, compileDir)) {
32330
32377
  console.warn(`[Render] Skipping external asset with unsafe path: ${relativePath}`);
32331
32378
  continue;
32332
32379
  }
@@ -32513,7 +32560,7 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
32513
32560
  }
32514
32561
  async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
32515
32562
  const moduleDir = dirname10(fileURLToPath3(import.meta.url));
32516
- const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve15(process.env.PRODUCER_RENDERS_DIR, "..") : resolve15(moduleDir, "../..");
32563
+ const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve16(process.env.PRODUCER_RENDERS_DIR, "..") : resolve16(moduleDir, "../..");
32517
32564
  const debugDir = join31(producerRoot, ".debug");
32518
32565
  const workDir = job.config.debug ? join31(debugDir, job.id) : join31(dirname10(outputPath), `work-${job.id}`);
32519
32566
  const pipelineStart = Date.now();
@@ -32957,6 +33004,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
32957
33004
  const hasHdrContent = effectiveHdr && nativeHdrIds.size > 0;
32958
33005
  const encoderHdr = hasHdrContent ? effectiveHdr : void 0;
32959
33006
  const preset = getEncoderPreset(job.config.quality, outputFormat, encoderHdr);
33007
+ if (job.config.crf != null && job.config.videoBitrate) {
33008
+ log2.warn(
33009
+ `[Render] Both crf=${job.config.crf} and videoBitrate=${job.config.videoBitrate} were set. These are mutually exclusive; honoring crf and ignoring videoBitrate. Set only one to silence this warning.`
33010
+ );
33011
+ }
33012
+ const effectiveQuality = job.config.crf ?? preset.quality;
33013
+ const effectiveBitrate = job.config.crf != null ? void 0 : job.config.videoBitrate;
32960
33014
  job.framesRendered = 0;
32961
33015
  if (hasHdrContent) {
32962
33016
  log2.info("[Render] HDR layered composite: z-ordered DOM + native HLG video layers");
@@ -32980,411 +33034,251 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
32980
33034
  createVideoFrameInjector(frameLookup),
32981
33035
  cfg
32982
33036
  );
32983
- await initializeSession(domSession);
32984
- assertNotAborted();
32985
- lastBrowserConsole = domSession.browserConsoleBuffer;
32986
- await initTransparentBackground(domSession.page);
32987
- const transitionMeta = await domSession.page.evaluate(() => {
32988
- return window.__hf?.transitions ?? [];
32989
- });
32990
- const sceneElements = await domSession.page.evaluate(() => {
32991
- const scenes = document.querySelectorAll(".scene");
32992
- const map = {};
32993
- for (const scene of scenes) {
32994
- const els = scene.querySelectorAll("[data-start]");
32995
- map[scene.id] = Array.from(els).map((e2) => e2.id);
32996
- }
32997
- return map;
32998
- });
32999
- const transitionRanges = transitionMeta.map((t3) => ({
33000
- ...t3,
33001
- startFrame: Math.floor(t3.time * job.config.fps),
33002
- endFrame: Math.ceil((t3.time + t3.duration) * job.config.fps)
33003
- }));
33004
- if (transitionRanges.length > 0) {
33005
- log2.info("[Render] Detected shader transitions for HDR compositing", {
33006
- count: transitionRanges.length,
33007
- transitions: transitionRanges.map((t3) => ({
33008
- shader: t3.shader,
33009
- from: t3.fromScene,
33010
- to: t3.toScene,
33011
- frames: `${t3.startFrame}-${t3.endFrame}`
33012
- }))
33013
- });
33014
- }
33015
- const hdrEncoder = await spawnStreamingEncoder(
33016
- videoOnlyPath,
33017
- {
33018
- fps: job.config.fps,
33019
- width,
33020
- height,
33021
- codec: preset.codec,
33022
- preset: preset.preset,
33023
- quality: preset.quality,
33024
- pixelFormat: preset.pixelFormat,
33025
- hdr: preset.hdr,
33026
- rawInputFormat: "rgb48le"
33027
- },
33028
- abortSignal,
33029
- { ffmpegStreamingTimeout: 36e5 }
33030
- );
33031
- assertNotAborted();
33032
- const hdrExtractionDims = /* @__PURE__ */ new Map();
33033
- const hdrImageFitInfo = /* @__PURE__ */ new Map();
33034
- const hdrVideoStartTimes = /* @__PURE__ */ new Map();
33035
- for (const v of composition.videos) {
33036
- if (hdrVideoIds.includes(v.id)) {
33037
- hdrVideoStartTimes.set(v.id, v.start);
33038
- }
33039
- }
33040
- const hdrImageStartTimes = /* @__PURE__ */ new Map();
33041
- for (const img of composition.images) {
33042
- if (nativeHdrImageIds.has(img.id)) {
33043
- hdrImageStartTimes.set(img.id, img.start);
33044
- }
33045
- }
33046
- const uniqueStartTimes = [
33047
- .../* @__PURE__ */ new Set([...hdrVideoStartTimes.values(), ...hdrImageStartTimes.values()])
33048
- ].sort((a, b) => a - b);
33049
- for (const seekTime of uniqueStartTimes) {
33050
- await domSession.page.evaluate((t3) => {
33051
- if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t3);
33052
- }, seekTime);
33053
- if (domSession.onBeforeCapture) {
33054
- await domSession.onBeforeCapture(domSession.page, seekTime);
33055
- }
33056
- const stacking = await queryElementStacking(domSession.page, nativeHdrIds);
33057
- for (const el of stacking) {
33058
- if (el.isHdr && el.layoutWidth > 0 && el.layoutHeight > 0 && !hdrExtractionDims.has(el.id)) {
33059
- hdrExtractionDims.set(el.id, { width: el.layoutWidth, height: el.layoutHeight });
33060
- }
33061
- if (el.isHdr && nativeHdrImageIds.has(el.id) && !hdrImageFitInfo.has(el.id)) {
33062
- hdrImageFitInfo.set(el.id, {
33063
- fit: el.objectFit,
33064
- position: el.objectPosition
33065
- });
33066
- }
33067
- }
33068
- }
33037
+ let hdrEncoder = null;
33038
+ let hdrEncoderClosed = false;
33039
+ let domSessionClosed = false;
33069
33040
  const hdrFrameDirs = /* @__PURE__ */ new Map();
33070
- for (const [videoId, srcPath] of hdrVideoSrcPaths) {
33071
- const video = composition.videos.find((v) => v.id === videoId);
33072
- if (!video) continue;
33073
- const frameDir = join31(framesDir, `hdr_${videoId}`);
33074
- mkdirSync17(frameDir, { recursive: true });
33075
- const duration = video.end - video.start;
33076
- const dims = hdrExtractionDims.get(videoId) ?? { width, height };
33077
- const ffmpegArgs = [
33078
- "-ss",
33079
- String(video.mediaStart),
33080
- "-i",
33081
- srcPath,
33082
- "-t",
33083
- String(duration),
33084
- "-r",
33085
- String(job.config.fps),
33086
- "-vf",
33087
- `scale=${dims.width}:${dims.height}:force_original_aspect_ratio=increase,crop=${dims.width}:${dims.height}`,
33088
- "-pix_fmt",
33089
- "rgb48le",
33090
- "-c:v",
33091
- "png",
33092
- "-y",
33093
- join31(frameDir, "frame_%04d.png")
33094
- ];
33095
- const result = await runFfmpeg(ffmpegArgs, { signal: abortSignal });
33096
- if (!result.success) {
33097
- hdrDiagnostics.videoExtractionFailures += 1;
33098
- log2.error("HDR frame pre-extraction failed; aborting render", {
33099
- videoId,
33100
- srcPath,
33101
- stderr: result.stderr.slice(-400)
33041
+ try {
33042
+ await initializeSession(domSession);
33043
+ assertNotAborted();
33044
+ lastBrowserConsole = domSession.browserConsoleBuffer;
33045
+ await initTransparentBackground(domSession.page);
33046
+ const transitionMeta = await domSession.page.evaluate(() => {
33047
+ return window.__hf?.transitions ?? [];
33048
+ });
33049
+ const sceneElements = await domSession.page.evaluate(() => {
33050
+ const scenes = document.querySelectorAll(".scene");
33051
+ const map = {};
33052
+ for (const scene of scenes) {
33053
+ const els = scene.querySelectorAll("[data-start]");
33054
+ map[scene.id] = Array.from(els).map((e2) => e2.id);
33055
+ }
33056
+ return map;
33057
+ });
33058
+ const transitionRanges = transitionMeta.map((t3) => ({
33059
+ ...t3,
33060
+ startFrame: Math.floor(t3.time * job.config.fps),
33061
+ endFrame: Math.ceil((t3.time + t3.duration) * job.config.fps)
33062
+ }));
33063
+ if (transitionRanges.length > 0) {
33064
+ log2.info("[Render] Detected shader transitions for HDR compositing", {
33065
+ count: transitionRanges.length,
33066
+ transitions: transitionRanges.map((t3) => ({
33067
+ shader: t3.shader,
33068
+ from: t3.fromScene,
33069
+ to: t3.toScene,
33070
+ frames: `${t3.startFrame}-${t3.endFrame}`
33071
+ }))
33102
33072
  });
33103
- throw new Error(
33104
- `HDR frame extraction failed for video "${videoId}". Aborting render to avoid shipping black HDR layers.`
33105
- );
33106
33073
  }
33107
- hdrFrameDirs.set(videoId, frameDir);
33108
- }
33109
- const hdrImageBuffers = /* @__PURE__ */ new Map();
33110
- for (const [imageId, srcPath] of hdrImageSrcPaths) {
33111
- try {
33112
- const decoded = decodePngToRgb48le(readFileSync21(srcPath));
33113
- const layout2 = hdrExtractionDims.get(imageId);
33114
- const fitInfo = hdrImageFitInfo.get(imageId);
33115
- if (layout2 && (layout2.width !== decoded.width || layout2.height !== decoded.height)) {
33116
- const fit = normalizeObjectFit(fitInfo?.fit);
33117
- const resampled = resampleRgb48leObjectFit(
33118
- decoded.data,
33119
- decoded.width,
33120
- decoded.height,
33121
- layout2.width,
33122
- layout2.height,
33123
- fit,
33124
- fitInfo?.position
33125
- );
33126
- hdrImageBuffers.set(imageId, {
33127
- data: resampled,
33128
- width: layout2.width,
33129
- height: layout2.height
33130
- });
33131
- } else {
33132
- hdrImageBuffers.set(imageId, {
33133
- data: Buffer.from(decoded.data),
33134
- width: decoded.width,
33135
- height: decoded.height
33136
- });
33074
+ hdrEncoder = await spawnStreamingEncoder(
33075
+ videoOnlyPath,
33076
+ {
33077
+ fps: job.config.fps,
33078
+ width,
33079
+ height,
33080
+ codec: preset.codec,
33081
+ preset: preset.preset,
33082
+ quality: effectiveQuality,
33083
+ bitrate: effectiveBitrate,
33084
+ pixelFormat: preset.pixelFormat,
33085
+ hdr: preset.hdr,
33086
+ rawInputFormat: "rgb48le"
33087
+ },
33088
+ abortSignal,
33089
+ { ffmpegStreamingTimeout: 36e5 }
33090
+ );
33091
+ assertNotAborted();
33092
+ const hdrExtractionDims = /* @__PURE__ */ new Map();
33093
+ const hdrImageFitInfo = /* @__PURE__ */ new Map();
33094
+ const hdrVideoStartTimes = /* @__PURE__ */ new Map();
33095
+ for (const v of composition.videos) {
33096
+ if (hdrVideoIds.includes(v.id)) {
33097
+ hdrVideoStartTimes.set(v.id, v.start);
33137
33098
  }
33138
- } catch (err) {
33139
- hdrDiagnostics.imageDecodeFailures += 1;
33140
- log2.error("HDR image decode failed; aborting render", {
33141
- imageId,
33142
- srcPath,
33143
- error: err instanceof Error ? err.message : String(err)
33144
- });
33145
- throw new Error(
33146
- `HDR image decode failed for image "${imageId}". Aborting render to avoid shipping missing HDR image layers.`
33147
- );
33148
33099
  }
33149
- }
33150
- assertNotAborted();
33151
- try {
33152
- let countNonZeroAlpha2 = function(rgba) {
33153
- let n = 0;
33154
- for (let p = 3; p < rgba.length; p += 4) {
33155
- if (rgba[p] !== 0) n++;
33100
+ const hdrImageStartTimes = /* @__PURE__ */ new Map();
33101
+ for (const img of composition.images) {
33102
+ if (nativeHdrImageIds.has(img.id)) {
33103
+ hdrImageStartTimes.set(img.id, img.start);
33156
33104
  }
33157
- return n;
33158
- }, countNonZeroRgb482 = function(buf) {
33159
- let n = 0;
33160
- for (let p = 0; p < buf.length; p += 6) {
33161
- if (buf[p] !== 0 || buf[p + 1] !== 0 || buf[p + 2] !== 0) n++;
33105
+ }
33106
+ const uniqueStartTimes = [
33107
+ .../* @__PURE__ */ new Set([...hdrVideoStartTimes.values(), ...hdrImageStartTimes.values()])
33108
+ ].sort((a, b) => a - b);
33109
+ for (const seekTime of uniqueStartTimes) {
33110
+ await domSession.page.evaluate((t3) => {
33111
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t3);
33112
+ }, seekTime);
33113
+ if (domSession.onBeforeCapture) {
33114
+ await domSession.onBeforeCapture(domSession.page, seekTime);
33115
+ }
33116
+ const stacking = await queryElementStacking(domSession.page, nativeHdrIds);
33117
+ for (const el of stacking) {
33118
+ if (el.isHdr && el.layoutWidth > 0 && el.layoutHeight > 0 && !hdrExtractionDims.has(el.id)) {
33119
+ hdrExtractionDims.set(el.id, { width: el.layoutWidth, height: el.layoutHeight });
33120
+ }
33121
+ if (el.isHdr && nativeHdrImageIds.has(el.id) && !hdrImageFitInfo.has(el.id)) {
33122
+ hdrImageFitInfo.set(el.id, {
33123
+ fit: el.objectFit,
33124
+ position: el.objectPosition
33125
+ });
33126
+ }
33162
33127
  }
33163
- return n;
33164
- };
33165
- var countNonZeroAlpha = countNonZeroAlpha2, countNonZeroRgb48 = countNonZeroRgb482;
33166
- const beforeCaptureHook = domSession.onBeforeCapture;
33167
- const cleanedUpVideos = /* @__PURE__ */ new Set();
33168
- const hdrVideoEndTimes = /* @__PURE__ */ new Map();
33169
- for (const v of composition.videos) {
33170
- if (hdrFrameDirs.has(v.id)) {
33171
- hdrVideoEndTimes.set(v.id, v.end);
33172
- }
33173
- }
33174
- const debugDumpEnabled = process.env.KEEP_TEMP === "1";
33175
- const debugDumpDir = debugDumpEnabled ? join31(framesDir, "debug-composite") : null;
33176
- if (debugDumpDir && !existsSync28(debugDumpDir)) {
33177
- mkdirSync17(debugDumpDir, { recursive: true });
33178
- }
33179
- async function compositeToBuffer(canvas, time, fullStacking, elementFilter, debugFrameIndex = -1) {
33180
- const filteredStacking = elementFilter ? fullStacking.filter((e2) => elementFilter.has(e2.id)) : fullStacking;
33181
- const layers = groupIntoLayers(filteredStacking);
33182
- const shouldLog = debugDumpEnabled && debugFrameIndex >= 0;
33183
- if (shouldLog) {
33184
- log2.info("[diag] compositeToBuffer plan", {
33185
- frame: debugFrameIndex,
33186
- time: time.toFixed(3),
33187
- filterSize: elementFilter?.size,
33188
- fullStackingCount: fullStacking.length,
33189
- filteredCount: filteredStacking.length,
33190
- layerCount: layers.length,
33191
- layers: layers.map(
33192
- (l) => l.type === "hdr" ? {
33193
- type: "hdr",
33194
- id: l.element.id,
33195
- z: l.element.zIndex,
33196
- visible: l.element.visible,
33197
- opacity: l.element.opacity,
33198
- bounds: `${Math.round(l.element.x)},${Math.round(l.element.y)} ${Math.round(l.element.width)}x${Math.round(l.element.height)}`
33199
- } : { type: "dom", ids: l.elementIds }
33200
- )
33128
+ }
33129
+ for (const [videoId, srcPath] of hdrVideoSrcPaths) {
33130
+ const video = composition.videos.find((v) => v.id === videoId);
33131
+ if (!video) continue;
33132
+ const frameDir = join31(framesDir, `hdr_${videoId}`);
33133
+ mkdirSync17(frameDir, { recursive: true });
33134
+ const duration = video.end - video.start;
33135
+ const dims = hdrExtractionDims.get(videoId) ?? { width, height };
33136
+ const ffmpegArgs = [
33137
+ "-ss",
33138
+ String(video.mediaStart),
33139
+ "-i",
33140
+ srcPath,
33141
+ "-t",
33142
+ String(duration),
33143
+ "-r",
33144
+ String(job.config.fps),
33145
+ "-vf",
33146
+ `scale=${dims.width}:${dims.height}:force_original_aspect_ratio=increase,crop=${dims.width}:${dims.height}`,
33147
+ "-pix_fmt",
33148
+ "rgb48le",
33149
+ "-c:v",
33150
+ "png",
33151
+ "-y",
33152
+ join31(frameDir, "frame_%04d.png")
33153
+ ];
33154
+ const result = await runFfmpeg(ffmpegArgs, { signal: abortSignal });
33155
+ if (!result.success) {
33156
+ hdrDiagnostics.videoExtractionFailures += 1;
33157
+ log2.error("HDR frame pre-extraction failed; aborting render", {
33158
+ videoId,
33159
+ srcPath,
33160
+ stderr: result.stderr.slice(-400)
33201
33161
  });
33162
+ throw new Error(
33163
+ `HDR frame extraction failed for video "${videoId}". Aborting render to avoid shipping black HDR layers.`
33164
+ );
33202
33165
  }
33203
- for (const [layerIdx, layer] of layers.entries()) {
33204
- if (layer.type === "hdr") {
33205
- const before2 = shouldLog ? countNonZeroRgb482(canvas) : 0;
33206
- const isHdrImage = nativeHdrImageIds.has(layer.element.id);
33207
- if (isHdrImage) {
33208
- blitHdrImageLayer(
33209
- canvas,
33210
- layer.element,
33211
- hdrImageBuffers,
33212
- width,
33213
- height,
33214
- log2,
33215
- imageTransfers.get(layer.element.id),
33216
- effectiveHdr?.transfer
33217
- );
33218
- } else {
33219
- blitHdrVideoLayer(
33220
- canvas,
33221
- layer.element,
33222
- time,
33223
- job.config.fps,
33224
- hdrFrameDirs,
33225
- hdrVideoStartTimes,
33226
- width,
33227
- height,
33228
- log2,
33229
- videoTransfers.get(layer.element.id),
33230
- effectiveHdr?.transfer
33231
- );
33232
- }
33233
- if (shouldLog) {
33234
- const after2 = countNonZeroRgb482(canvas);
33235
- if (isHdrImage) {
33236
- const buf = hdrImageBuffers.get(layer.element.id);
33237
- log2.info("[diag] hdr layer blit", {
33238
- frame: debugFrameIndex,
33239
- layerIdx,
33240
- id: layer.element.id,
33241
- kind: "image",
33242
- pixelsAdded: after2 - before2,
33243
- totalNonZero: after2,
33244
- bufferDecoded: !!buf,
33245
- bufferDims: buf ? `${buf.width}x${buf.height}` : null
33246
- });
33247
- } else {
33248
- const frameDir = hdrFrameDirs.get(layer.element.id);
33249
- const startTime = hdrVideoStartTimes.get(layer.element.id) ?? 0;
33250
- const localTime = time - startTime;
33251
- const frameNum = Math.floor(localTime * job.config.fps) + 1;
33252
- const expectedFrame = frameDir ? join31(frameDir, `frame_${String(frameNum).padStart(4, "0")}.png`) : null;
33253
- log2.info("[diag] hdr layer blit", {
33254
- frame: debugFrameIndex,
33255
- layerIdx,
33256
- id: layer.element.id,
33257
- kind: "video",
33258
- pixelsAdded: after2 - before2,
33259
- totalNonZero: after2,
33260
- startTime,
33261
- localTime: localTime.toFixed(3),
33262
- hdrFrameNum: frameNum,
33263
- expectedFrame,
33264
- expectedFrameExists: expectedFrame ? existsSync28(expectedFrame) : false
33265
- });
33266
- }
33267
- }
33166
+ hdrFrameDirs.set(videoId, frameDir);
33167
+ }
33168
+ const hdrImageBuffers = /* @__PURE__ */ new Map();
33169
+ for (const [imageId, srcPath] of hdrImageSrcPaths) {
33170
+ try {
33171
+ const decoded = decodePngToRgb48le(readFileSync21(srcPath));
33172
+ const layout2 = hdrExtractionDims.get(imageId);
33173
+ const fitInfo = hdrImageFitInfo.get(imageId);
33174
+ if (layout2 && (layout2.width !== decoded.width || layout2.height !== decoded.height)) {
33175
+ const fit = normalizeObjectFit(fitInfo?.fit);
33176
+ const resampled = resampleRgb48leObjectFit(
33177
+ decoded.data,
33178
+ decoded.width,
33179
+ decoded.height,
33180
+ layout2.width,
33181
+ layout2.height,
33182
+ fit,
33183
+ fitInfo?.position
33184
+ );
33185
+ hdrImageBuffers.set(imageId, {
33186
+ data: resampled,
33187
+ width: layout2.width,
33188
+ height: layout2.height
33189
+ });
33268
33190
  } else {
33269
- const allElementIds = fullStacking.map((e2) => e2.id);
33270
- const layerIds = new Set(layer.elementIds);
33271
- const hideIds = allElementIds.filter((id) => !layerIds.has(id));
33272
- await domSession.page.evaluate((t3) => {
33273
- if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t3);
33274
- }, time);
33275
- if (beforeCaptureHook) {
33276
- await beforeCaptureHook(domSession.page, time);
33277
- }
33278
- await applyDomLayerMask(domSession.page, layer.elementIds, hideIds);
33279
- const domPng = await captureAlphaPng(domSession.page, width, height);
33280
- await removeDomLayerMask(domSession.page, hideIds);
33281
- try {
33282
- const { data: domRgba } = decodePng(domPng);
33283
- if (!effectiveHdr) {
33284
- throw new Error(
33285
- "Invariant violation: effectiveHdr is undefined inside HDR layer branch"
33286
- );
33287
- }
33288
- const before2 = shouldLog ? countNonZeroRgb482(canvas) : 0;
33289
- const alphaPixels = shouldLog ? countNonZeroAlpha2(domRgba) : 0;
33290
- blitRgba8OverRgb48le(domRgba, canvas, width, height, effectiveHdr.transfer);
33291
- if (shouldLog && debugDumpDir) {
33292
- const after2 = countNonZeroRgb482(canvas);
33293
- const dumpName = `frame_${String(debugFrameIndex).padStart(4, "0")}_layer_${String(layerIdx).padStart(2, "0")}_dom.png`;
33294
- const dumpPath = join31(debugDumpDir, dumpName);
33295
- writeFileSync10(dumpPath, domPng);
33296
- log2.info("[diag] dom layer blit", {
33297
- frame: debugFrameIndex,
33298
- layerIdx,
33299
- layerIds: layer.elementIds,
33300
- hideCount: hideIds.length,
33301
- pngBytes: domPng.length,
33302
- alphaPixels,
33303
- pixelsAdded: after2 - before2,
33304
- totalNonZero: after2,
33305
- dumpPath
33306
- });
33307
- }
33308
- } catch (err) {
33309
- log2.warn("DOM layer decode/blit failed; skipping overlay", {
33310
- layerIds: layer.elementIds,
33311
- error: err instanceof Error ? err.message : String(err)
33312
- });
33313
- }
33191
+ hdrImageBuffers.set(imageId, {
33192
+ data: Buffer.from(decoded.data),
33193
+ width: decoded.width,
33194
+ height: decoded.height
33195
+ });
33314
33196
  }
33315
- }
33316
- if (shouldLog && debugDumpDir) {
33317
- const finalNonZero = countNonZeroRgb482(canvas);
33318
- log2.info("[diag] compositeToBuffer end", {
33319
- frame: debugFrameIndex,
33320
- finalNonZeroPixels: finalNonZero,
33321
- totalPixels: width * height,
33322
- coverage: (finalNonZero / (width * height) * 100).toFixed(1) + "%"
33197
+ } catch (err) {
33198
+ hdrDiagnostics.imageDecodeFailures += 1;
33199
+ log2.error("HDR image decode failed; aborting render", {
33200
+ imageId,
33201
+ srcPath,
33202
+ error: err instanceof Error ? err.message : String(err)
33323
33203
  });
33204
+ throw new Error(
33205
+ `HDR image decode failed for image "${imageId}". Aborting render to avoid shipping missing HDR image layers.`
33206
+ );
33324
33207
  }
33325
33208
  }
33326
- const bufSize = width * height * 6;
33327
- const hasTransitions = transitionRanges.length > 0;
33328
- const transBufferA = hasTransitions ? Buffer.alloc(bufSize) : null;
33329
- const transBufferB = hasTransitions ? Buffer.alloc(bufSize) : null;
33330
- const transOutput = hasTransitions ? Buffer.alloc(bufSize) : null;
33331
- const normalCanvas = Buffer.alloc(bufSize);
33332
- for (let i2 = 0; i2 < totalFrames; i2++) {
33333
- assertNotAborted();
33334
- const time = i2 / job.config.fps;
33335
- await domSession.page.evaluate((t3) => {
33336
- if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t3);
33337
- }, time);
33338
- if (beforeCaptureHook) {
33339
- await beforeCaptureHook(domSession.page, time);
33340
- }
33341
- const stackingInfo = await queryElementStacking(domSession.page, nativeHdrIds);
33342
- const activeTransition = transitionRanges.find(
33343
- (t3) => i2 >= t3.startFrame && i2 <= t3.endFrame
33344
- );
33345
- if (i2 % 30 === 0) {
33346
- const hdrEl = stackingInfo.find((e2) => e2.isHdr);
33347
- log2.debug("[Render] HDR layer composite frame", {
33348
- frame: i2,
33349
- time: time.toFixed(2),
33350
- hdrElement: hdrEl ? { z: hdrEl.zIndex, visible: hdrEl.visible, width: hdrEl.width } : null,
33351
- stackingCount: stackingInfo.length,
33352
- activeTransition: activeTransition?.shader
33353
- });
33209
+ assertNotAborted();
33210
+ try {
33211
+ let countNonZeroAlpha2 = function(rgba) {
33212
+ let n = 0;
33213
+ for (let p = 3; p < rgba.length; p += 4) {
33214
+ if (rgba[p] !== 0) n++;
33215
+ }
33216
+ return n;
33217
+ }, countNonZeroRgb482 = function(buf) {
33218
+ let n = 0;
33219
+ for (let p = 0; p < buf.length; p += 6) {
33220
+ if (buf[p] !== 0 || buf[p + 1] !== 0 || buf[p + 2] !== 0 || buf[p + 3] !== 0 || buf[p + 4] !== 0 || buf[p + 5] !== 0)
33221
+ n++;
33222
+ }
33223
+ return n;
33224
+ };
33225
+ var countNonZeroAlpha = countNonZeroAlpha2, countNonZeroRgb48 = countNonZeroRgb482;
33226
+ const beforeCaptureHook = domSession.onBeforeCapture;
33227
+ const cleanedUpVideos = /* @__PURE__ */ new Set();
33228
+ const hdrVideoEndTimes = /* @__PURE__ */ new Map();
33229
+ for (const v of composition.videos) {
33230
+ if (hdrFrameDirs.has(v.id)) {
33231
+ hdrVideoEndTimes.set(v.id, v.end);
33232
+ }
33354
33233
  }
33355
- if (activeTransition && transBufferA && transBufferB && transOutput) {
33356
- const progress = activeTransition.endFrame === activeTransition.startFrame ? 1 : (i2 - activeTransition.startFrame) / (activeTransition.endFrame - activeTransition.startFrame);
33357
- const sceneAIds = new Set(sceneElements[activeTransition.fromScene] ?? []);
33358
- const sceneBIds = new Set(sceneElements[activeTransition.toScene] ?? []);
33359
- transBufferA.fill(0);
33360
- transBufferB.fill(0);
33361
- for (const [sceneBuf, sceneIds] of [
33362
- [transBufferA, sceneAIds],
33363
- [transBufferB, sceneBIds]
33364
- ]) {
33365
- await domSession.page.evaluate((t3) => {
33366
- if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t3);
33367
- }, time);
33368
- if (beforeCaptureHook) {
33369
- await beforeCaptureHook(domSession.page, time);
33370
- }
33371
- for (const el of stackingInfo) {
33372
- if (!el.isHdr || !sceneIds.has(el.id)) continue;
33373
- if (nativeHdrImageIds.has(el.id)) {
33234
+ const debugDumpEnabled = process.env.KEEP_TEMP === "1";
33235
+ const debugDumpDir = debugDumpEnabled ? join31(framesDir, "debug-composite") : null;
33236
+ if (debugDumpDir && !existsSync28(debugDumpDir)) {
33237
+ mkdirSync17(debugDumpDir, { recursive: true });
33238
+ }
33239
+ async function compositeToBuffer(canvas, time, fullStacking, elementFilter, debugFrameIndex = -1) {
33240
+ const filteredStacking = elementFilter ? fullStacking.filter((e2) => elementFilter.has(e2.id)) : fullStacking;
33241
+ const layers = groupIntoLayers(filteredStacking);
33242
+ const shouldLog = debugDumpEnabled && debugFrameIndex >= 0;
33243
+ if (shouldLog) {
33244
+ log2.info("[diag] compositeToBuffer plan", {
33245
+ frame: debugFrameIndex,
33246
+ time: time.toFixed(3),
33247
+ filterSize: elementFilter?.size,
33248
+ fullStackingCount: fullStacking.length,
33249
+ filteredCount: filteredStacking.length,
33250
+ layerCount: layers.length,
33251
+ layers: layers.map(
33252
+ (l) => l.type === "hdr" ? {
33253
+ type: "hdr",
33254
+ id: l.element.id,
33255
+ z: l.element.zIndex,
33256
+ visible: l.element.visible,
33257
+ opacity: l.element.opacity,
33258
+ bounds: `${Math.round(l.element.x)},${Math.round(l.element.y)} ${Math.round(l.element.width)}x${Math.round(l.element.height)}`
33259
+ } : { type: "dom", ids: l.elementIds }
33260
+ )
33261
+ });
33262
+ }
33263
+ for (const [layerIdx, layer] of layers.entries()) {
33264
+ if (layer.type === "hdr") {
33265
+ const before2 = shouldLog ? countNonZeroRgb482(canvas) : 0;
33266
+ const isHdrImage = nativeHdrImageIds.has(layer.element.id);
33267
+ if (isHdrImage) {
33374
33268
  blitHdrImageLayer(
33375
- sceneBuf,
33376
- el,
33269
+ canvas,
33270
+ layer.element,
33377
33271
  hdrImageBuffers,
33378
33272
  width,
33379
33273
  height,
33380
33274
  log2,
33381
- imageTransfers.get(el.id),
33275
+ imageTransfers.get(layer.element.id),
33382
33276
  effectiveHdr?.transfer
33383
33277
  );
33384
33278
  } else {
33385
33279
  blitHdrVideoLayer(
33386
- sceneBuf,
33387
- el,
33280
+ canvas,
33281
+ layer.element,
33388
33282
  time,
33389
33283
  job.config.fps,
33390
33284
  hdrFrameDirs,
@@ -33392,100 +33286,294 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33392
33286
  width,
33393
33287
  height,
33394
33288
  log2,
33395
- videoTransfers.get(el.id),
33289
+ videoTransfers.get(layer.element.id),
33396
33290
  effectiveHdr?.transfer
33397
33291
  );
33398
33292
  }
33293
+ if (shouldLog) {
33294
+ const after2 = countNonZeroRgb482(canvas);
33295
+ if (isHdrImage) {
33296
+ const buf = hdrImageBuffers.get(layer.element.id);
33297
+ log2.info("[diag] hdr layer blit", {
33298
+ frame: debugFrameIndex,
33299
+ layerIdx,
33300
+ id: layer.element.id,
33301
+ kind: "image",
33302
+ pixelsAdded: after2 - before2,
33303
+ totalNonZero: after2,
33304
+ bufferDecoded: !!buf,
33305
+ bufferDims: buf ? `${buf.width}x${buf.height}` : null
33306
+ });
33307
+ } else {
33308
+ const frameDir = hdrFrameDirs.get(layer.element.id);
33309
+ const startTime = hdrVideoStartTimes.get(layer.element.id) ?? 0;
33310
+ const localTime = time - startTime;
33311
+ const frameNum = Math.floor(localTime * job.config.fps) + 1;
33312
+ const expectedFrame = frameDir ? join31(frameDir, `frame_${String(frameNum).padStart(4, "0")}.png`) : null;
33313
+ log2.info("[diag] hdr layer blit", {
33314
+ frame: debugFrameIndex,
33315
+ layerIdx,
33316
+ id: layer.element.id,
33317
+ kind: "video",
33318
+ pixelsAdded: after2 - before2,
33319
+ totalNonZero: after2,
33320
+ startTime,
33321
+ localTime: localTime.toFixed(3),
33322
+ hdrFrameNum: frameNum,
33323
+ expectedFrame,
33324
+ expectedFrameExists: expectedFrame ? existsSync28(expectedFrame) : false
33325
+ });
33326
+ }
33327
+ }
33328
+ } else {
33329
+ const allElementIds = fullStacking.map((e2) => e2.id);
33330
+ const layerIds = new Set(layer.elementIds);
33331
+ const hideIds = allElementIds.filter((id) => !layerIds.has(id));
33332
+ await domSession.page.evaluate((t3) => {
33333
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t3);
33334
+ }, time);
33335
+ if (beforeCaptureHook) {
33336
+ await beforeCaptureHook(domSession.page, time);
33337
+ }
33338
+ await applyDomLayerMask(domSession.page, layer.elementIds, hideIds);
33339
+ const domPng = await captureAlphaPng(domSession.page, width, height);
33340
+ await removeDomLayerMask(domSession.page, hideIds);
33341
+ try {
33342
+ const { data: domRgba } = decodePng(domPng);
33343
+ if (!effectiveHdr) {
33344
+ throw new Error(
33345
+ "Invariant violation: effectiveHdr is undefined inside HDR layer branch"
33346
+ );
33347
+ }
33348
+ const before2 = shouldLog ? countNonZeroRgb482(canvas) : 0;
33349
+ const alphaPixels = shouldLog ? countNonZeroAlpha2(domRgba) : 0;
33350
+ blitRgba8OverRgb48le(domRgba, canvas, width, height, effectiveHdr.transfer);
33351
+ if (shouldLog && debugDumpDir) {
33352
+ const after2 = countNonZeroRgb482(canvas);
33353
+ const dumpName = `frame_${String(debugFrameIndex).padStart(4, "0")}_layer_${String(layerIdx).padStart(2, "0")}_dom.png`;
33354
+ const dumpPath = join31(debugDumpDir, dumpName);
33355
+ writeFileSync10(dumpPath, domPng);
33356
+ log2.info("[diag] dom layer blit", {
33357
+ frame: debugFrameIndex,
33358
+ layerIdx,
33359
+ layerIds: layer.elementIds,
33360
+ hideCount: hideIds.length,
33361
+ pngBytes: domPng.length,
33362
+ alphaPixels,
33363
+ pixelsAdded: after2 - before2,
33364
+ totalNonZero: after2,
33365
+ dumpPath
33366
+ });
33367
+ }
33368
+ } catch (err) {
33369
+ log2.warn("DOM layer decode/blit failed; skipping overlay", {
33370
+ layerIds: layer.elementIds,
33371
+ error: err instanceof Error ? err.message : String(err)
33372
+ });
33373
+ }
33399
33374
  }
33400
- const showIds = Array.from(sceneIds);
33401
- const hideIds = stackingInfo.map((e2) => e2.id).filter((id) => !sceneIds.has(id) || nativeHdrIds.has(id));
33402
- await applyDomLayerMask(domSession.page, showIds, hideIds);
33403
- const domPng = await captureAlphaPng(domSession.page, width, height);
33404
- await removeDomLayerMask(domSession.page, hideIds);
33405
- try {
33406
- const { data: domRgba } = decodePng(domPng);
33407
- if (!effectiveHdr) {
33408
- throw new Error(
33409
- "Invariant violation: effectiveHdr is undefined inside hasHdrVideo branch"
33375
+ }
33376
+ if (shouldLog && debugDumpDir) {
33377
+ const finalNonZero = countNonZeroRgb482(canvas);
33378
+ log2.info("[diag] compositeToBuffer end", {
33379
+ frame: debugFrameIndex,
33380
+ finalNonZeroPixels: finalNonZero,
33381
+ totalPixels: width * height,
33382
+ coverage: (finalNonZero / (width * height) * 100).toFixed(1) + "%"
33383
+ });
33384
+ }
33385
+ }
33386
+ const bufSize = width * height * 6;
33387
+ const hasTransitions = transitionRanges.length > 0;
33388
+ const transBufferA = hasTransitions ? Buffer.alloc(bufSize) : null;
33389
+ const transBufferB = hasTransitions ? Buffer.alloc(bufSize) : null;
33390
+ const transOutput = hasTransitions ? Buffer.alloc(bufSize) : null;
33391
+ const normalCanvas = Buffer.alloc(bufSize);
33392
+ for (let i2 = 0; i2 < totalFrames; i2++) {
33393
+ assertNotAborted();
33394
+ const time = i2 / job.config.fps;
33395
+ await domSession.page.evaluate((t3) => {
33396
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t3);
33397
+ }, time);
33398
+ if (beforeCaptureHook) {
33399
+ await beforeCaptureHook(domSession.page, time);
33400
+ }
33401
+ const stackingInfo = await queryElementStacking(domSession.page, nativeHdrIds);
33402
+ const activeTransition = transitionRanges.find(
33403
+ (t3) => i2 >= t3.startFrame && i2 <= t3.endFrame
33404
+ );
33405
+ if (i2 % 30 === 0) {
33406
+ const hdrEl = stackingInfo.find((e2) => e2.isHdr);
33407
+ log2.debug("[Render] HDR layer composite frame", {
33408
+ frame: i2,
33409
+ time: time.toFixed(2),
33410
+ hdrElement: hdrEl ? { z: hdrEl.zIndex, visible: hdrEl.visible, width: hdrEl.width } : null,
33411
+ stackingCount: stackingInfo.length,
33412
+ activeTransition: activeTransition?.shader
33413
+ });
33414
+ }
33415
+ if (activeTransition && transBufferA && transBufferB && transOutput) {
33416
+ const progress = activeTransition.endFrame === activeTransition.startFrame ? 1 : (i2 - activeTransition.startFrame) / (activeTransition.endFrame - activeTransition.startFrame);
33417
+ const sceneAIds = new Set(sceneElements[activeTransition.fromScene] ?? []);
33418
+ const sceneBIds = new Set(sceneElements[activeTransition.toScene] ?? []);
33419
+ transBufferA.fill(0);
33420
+ transBufferB.fill(0);
33421
+ for (const [sceneBuf, sceneIds] of [
33422
+ [transBufferA, sceneAIds],
33423
+ [transBufferB, sceneBIds]
33424
+ ]) {
33425
+ assertNotAborted();
33426
+ await domSession.page.evaluate((t3) => {
33427
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t3);
33428
+ }, time);
33429
+ if (beforeCaptureHook) {
33430
+ await beforeCaptureHook(domSession.page, time);
33431
+ }
33432
+ for (const el of stackingInfo) {
33433
+ if (!el.isHdr || !sceneIds.has(el.id)) continue;
33434
+ if (nativeHdrImageIds.has(el.id)) {
33435
+ blitHdrImageLayer(
33436
+ sceneBuf,
33437
+ el,
33438
+ hdrImageBuffers,
33439
+ width,
33440
+ height,
33441
+ log2,
33442
+ imageTransfers.get(el.id),
33443
+ effectiveHdr?.transfer
33444
+ );
33445
+ } else {
33446
+ blitHdrVideoLayer(
33447
+ sceneBuf,
33448
+ el,
33449
+ time,
33450
+ job.config.fps,
33451
+ hdrFrameDirs,
33452
+ hdrVideoStartTimes,
33453
+ width,
33454
+ height,
33455
+ log2,
33456
+ videoTransfers.get(el.id),
33457
+ effectiveHdr?.transfer
33458
+ );
33459
+ }
33460
+ }
33461
+ const showIds = Array.from(sceneIds);
33462
+ const hideIds = stackingInfo.map((e2) => e2.id).filter((id) => !sceneIds.has(id) || nativeHdrIds.has(id));
33463
+ await applyDomLayerMask(domSession.page, showIds, hideIds);
33464
+ const domPng = await captureAlphaPng(domSession.page, width, height);
33465
+ await removeDomLayerMask(domSession.page, hideIds);
33466
+ try {
33467
+ const { data: domRgba } = decodePng(domPng);
33468
+ if (!effectiveHdr) {
33469
+ throw new Error(
33470
+ "Invariant violation: effectiveHdr is undefined inside hasHdrVideo branch"
33471
+ );
33472
+ }
33473
+ blitRgba8OverRgb48le(
33474
+ domRgba,
33475
+ sceneBuf,
33476
+ width,
33477
+ height,
33478
+ effectiveHdr.transfer
33410
33479
  );
33480
+ } catch (err) {
33481
+ log2.warn("DOM layer decode/blit failed; skipping overlay for transition scene", {
33482
+ frameIndex: i2,
33483
+ sceneIds: Array.from(sceneIds),
33484
+ error: err instanceof Error ? err.message : String(err)
33485
+ });
33411
33486
  }
33412
- blitRgba8OverRgb48le(
33413
- domRgba,
33414
- sceneBuf,
33415
- width,
33416
- height,
33417
- effectiveHdr.transfer
33487
+ }
33488
+ const transitionFn = TRANSITIONS[activeTransition.shader] ?? crossfade;
33489
+ transitionFn(transBufferA, transBufferB, transOutput, width, height, progress);
33490
+ hdrEncoder.writeFrame(transOutput);
33491
+ } else {
33492
+ normalCanvas.fill(0);
33493
+ await compositeToBuffer(normalCanvas, time, stackingInfo, void 0, i2);
33494
+ if (debugDumpEnabled && debugDumpDir && i2 % 30 === 0) {
33495
+ const previewPath = join31(
33496
+ debugDumpDir,
33497
+ `frame_${String(i2).padStart(4, "0")}_final_rgb48le.bin`
33418
33498
  );
33419
- } catch (err) {
33420
- log2.warn("DOM layer decode/blit failed; skipping overlay for transition scene", {
33421
- frameIndex: i2,
33422
- sceneIds: Array.from(sceneIds),
33423
- error: err instanceof Error ? err.message : String(err)
33424
- });
33499
+ writeFileSync10(previewPath, normalCanvas);
33425
33500
  }
33501
+ hdrEncoder.writeFrame(normalCanvas);
33426
33502
  }
33427
- const transitionFn = TRANSITIONS[activeTransition.shader] ?? crossfade;
33428
- transitionFn(transBufferA, transBufferB, transOutput, width, height, progress);
33429
- hdrEncoder.writeFrame(transOutput);
33430
- } else {
33431
- normalCanvas.fill(0);
33432
- await compositeToBuffer(normalCanvas, time, stackingInfo, void 0, i2);
33433
- if (debugDumpEnabled && debugDumpDir && i2 % 30 === 0) {
33434
- const previewPath = join31(
33435
- debugDumpDir,
33436
- `frame_${String(i2).padStart(4, "0")}_final_rgb48le.bin`
33437
- );
33438
- writeFileSync10(previewPath, normalCanvas);
33439
- }
33440
- hdrEncoder.writeFrame(normalCanvas);
33441
- }
33442
- if (process.env.KEEP_TEMP !== "1") {
33443
- for (const [videoId, endTime] of hdrVideoEndTimes) {
33444
- if (time > endTime && !cleanedUpVideos.has(videoId)) {
33445
- const stillNeeded = activeTransition && (sceneElements[activeTransition.fromScene]?.includes(videoId) || sceneElements[activeTransition.toScene]?.includes(videoId));
33446
- if (!stillNeeded) {
33447
- const frameDir = hdrFrameDirs.get(videoId);
33448
- if (frameDir) {
33449
- try {
33450
- rmSync6(frameDir, { recursive: true, force: true });
33451
- } catch (err) {
33452
- log2.warn("Failed to clean up HDR frame directory", {
33453
- videoId,
33454
- frameDir,
33455
- error: err instanceof Error ? err.message : String(err)
33456
- });
33503
+ if (process.env.KEEP_TEMP !== "1") {
33504
+ for (const [videoId, endTime] of hdrVideoEndTimes) {
33505
+ if (time > endTime && !cleanedUpVideos.has(videoId)) {
33506
+ const stillNeeded = activeTransition && (sceneElements[activeTransition.fromScene]?.includes(videoId) || sceneElements[activeTransition.toScene]?.includes(videoId));
33507
+ if (!stillNeeded) {
33508
+ const frameDir = hdrFrameDirs.get(videoId);
33509
+ if (frameDir) {
33510
+ try {
33511
+ rmSync6(frameDir, { recursive: true, force: true });
33512
+ } catch (err) {
33513
+ log2.warn("Failed to clean up HDR frame directory", {
33514
+ videoId,
33515
+ frameDir,
33516
+ error: err instanceof Error ? err.message : String(err)
33517
+ });
33518
+ }
33519
+ frameDirMaxIndexCache.delete(frameDir);
33520
+ hdrFrameDirs.delete(videoId);
33457
33521
  }
33522
+ cleanedUpVideos.add(videoId);
33458
33523
  }
33459
- cleanedUpVideos.add(videoId);
33460
33524
  }
33461
33525
  }
33462
33526
  }
33527
+ job.framesRendered = i2 + 1;
33528
+ if ((i2 + 1) % 10 === 0 || i2 + 1 === totalFrames) {
33529
+ const frameProgress = (i2 + 1) / totalFrames;
33530
+ updateJobStatus(
33531
+ job,
33532
+ "rendering",
33533
+ `HDR composite frame ${i2 + 1}/${job.totalFrames}`,
33534
+ Math.round(25 + frameProgress * 55),
33535
+ onProgress
33536
+ );
33537
+ }
33463
33538
  }
33464
- job.framesRendered = i2 + 1;
33465
- if ((i2 + 1) % 10 === 0 || i2 + 1 === totalFrames) {
33466
- const frameProgress = (i2 + 1) / totalFrames;
33467
- updateJobStatus(
33468
- job,
33469
- "rendering",
33470
- `HDR composite frame ${i2 + 1}/${job.totalFrames}`,
33471
- Math.round(25 + frameProgress * 55),
33472
- onProgress
33473
- );
33474
- }
33539
+ } finally {
33540
+ lastBrowserConsole = domSession.browserConsoleBuffer;
33541
+ await closeCaptureSession(domSession);
33542
+ domSessionClosed = true;
33475
33543
  }
33544
+ const hdrEncodeResult = await hdrEncoder.close();
33545
+ hdrEncoderClosed = true;
33546
+ assertNotAborted();
33547
+ if (!hdrEncodeResult.success) {
33548
+ throw new Error(`HDR encode failed: ${hdrEncodeResult.error}`);
33549
+ }
33550
+ perfStages.captureMs = Date.now() - stage4Start;
33551
+ perfStages.encodeMs = hdrEncodeResult.durationMs;
33476
33552
  } finally {
33477
- lastBrowserConsole = domSession.browserConsoleBuffer;
33478
- await closeCaptureSession(domSession);
33479
- }
33480
- const hdrEncodeResult = await hdrEncoder.close();
33481
- assertNotAborted();
33482
- if (!hdrEncodeResult.success) {
33483
- throw new Error(`HDR encode failed: ${hdrEncodeResult.error}`);
33553
+ if (hdrEncoder && !hdrEncoderClosed) {
33554
+ try {
33555
+ await hdrEncoder.close();
33556
+ } catch (err) {
33557
+ log2.warn("hdrEncoder defensive close failed", {
33558
+ err: err instanceof Error ? err.message : String(err)
33559
+ });
33560
+ }
33561
+ }
33562
+ if (!domSessionClosed) {
33563
+ await closeCaptureSession(domSession).catch((err) => {
33564
+ log2.warn("closeCaptureSession defensive close failed", {
33565
+ err: err instanceof Error ? err.message : String(err)
33566
+ });
33567
+ });
33568
+ }
33569
+ for (const frameDir of hdrFrameDirs.values()) {
33570
+ frameDirMaxIndexCache.delete(frameDir);
33571
+ }
33572
+ hdrFrameDirs.clear();
33484
33573
  }
33485
- perfStages.captureMs = Date.now() - stage4Start;
33486
- perfStages.encodeMs = hdrEncodeResult.durationMs;
33487
33574
  } else {
33488
33575
  let streamingEncoder = null;
33576
+ let streamingEncoderClosed = false;
33489
33577
  if (enableStreamingEncode) {
33490
33578
  streamingEncoder = await spawnStreamingEncoder(
33491
33579
  videoOnlyPath,
@@ -33495,7 +33583,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33495
33583
  height,
33496
33584
  codec: preset.codec,
33497
33585
  preset: preset.preset,
33498
- quality: preset.quality,
33586
+ quality: effectiveQuality,
33587
+ bitrate: effectiveBitrate,
33499
33588
  pixelFormat: preset.pixelFormat,
33500
33589
  useGpu: job.config.useGpu,
33501
33590
  imageFormat: captureOptions.format || "jpeg",
@@ -33505,201 +33594,215 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33505
33594
  );
33506
33595
  assertNotAborted();
33507
33596
  }
33508
- if (enableStreamingEncode && streamingEncoder) {
33509
- const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
33510
- const currentEncoder = streamingEncoder;
33511
- if (workerCount > 1) {
33512
- const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
33513
- const onFrameBuffer = async (frameIndex, buffer) => {
33514
- await reorderBuffer.waitForFrame(frameIndex);
33515
- currentEncoder.writeFrame(buffer);
33516
- reorderBuffer.advanceTo(frameIndex + 1);
33517
- };
33518
- await executeParallelCapture(
33519
- fileServer.url,
33520
- workDir,
33521
- tasks,
33522
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
33523
- () => createVideoFrameInjector(frameLookup),
33524
- abortSignal,
33525
- (progress) => {
33526
- job.framesRendered = progress.capturedFrames;
33527
- const frameProgress = progress.capturedFrames / progress.totalFrames;
33528
- const progressPct = 25 + frameProgress * 55;
33529
- if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
33597
+ try {
33598
+ if (enableStreamingEncode && streamingEncoder) {
33599
+ const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
33600
+ const currentEncoder = streamingEncoder;
33601
+ if (workerCount > 1) {
33602
+ const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
33603
+ const onFrameBuffer = async (frameIndex, buffer) => {
33604
+ await reorderBuffer.waitForFrame(frameIndex);
33605
+ currentEncoder.writeFrame(buffer);
33606
+ reorderBuffer.advanceTo(frameIndex + 1);
33607
+ };
33608
+ await executeParallelCapture(
33609
+ fileServer.url,
33610
+ workDir,
33611
+ tasks,
33612
+ { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
33613
+ () => createVideoFrameInjector(frameLookup),
33614
+ abortSignal,
33615
+ (progress) => {
33616
+ job.framesRendered = progress.capturedFrames;
33617
+ const frameProgress = progress.capturedFrames / progress.totalFrames;
33618
+ const progressPct = 25 + frameProgress * 55;
33619
+ if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
33620
+ updateJobStatus(
33621
+ job,
33622
+ "rendering",
33623
+ `Streaming frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
33624
+ Math.round(progressPct),
33625
+ onProgress
33626
+ );
33627
+ }
33628
+ },
33629
+ onFrameBuffer,
33630
+ cfg
33631
+ );
33632
+ if (probeSession) {
33633
+ lastBrowserConsole = probeSession.browserConsoleBuffer;
33634
+ await closeCaptureSession(probeSession);
33635
+ probeSession = null;
33636
+ }
33637
+ } else {
33638
+ const videoInjector = createVideoFrameInjector(frameLookup);
33639
+ const session = probeSession ?? await createCaptureSession(
33640
+ fileServer.url,
33641
+ framesDir,
33642
+ { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
33643
+ videoInjector,
33644
+ cfg
33645
+ );
33646
+ if (probeSession) {
33647
+ prepareCaptureSessionForReuse(session, framesDir, videoInjector);
33648
+ probeSession = null;
33649
+ }
33650
+ try {
33651
+ if (!session.isInitialized) {
33652
+ await initializeSession(session);
33653
+ }
33654
+ assertNotAborted();
33655
+ lastBrowserConsole = session.browserConsoleBuffer;
33656
+ for (let i2 = 0; i2 < totalFrames; i2++) {
33657
+ assertNotAborted();
33658
+ const time = i2 / job.config.fps;
33659
+ const { buffer } = await captureFrameToBuffer(session, i2, time);
33660
+ await reorderBuffer.waitForFrame(i2);
33661
+ currentEncoder.writeFrame(buffer);
33662
+ reorderBuffer.advanceTo(i2 + 1);
33663
+ job.framesRendered = i2 + 1;
33664
+ const frameProgress = (i2 + 1) / totalFrames;
33665
+ const progress = 25 + frameProgress * 55;
33530
33666
  updateJobStatus(
33531
33667
  job,
33532
33668
  "rendering",
33533
- `Streaming frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
33534
- Math.round(progressPct),
33669
+ `Streaming frame ${i2 + 1}/${job.totalFrames}`,
33670
+ Math.round(progress),
33535
33671
  onProgress
33536
33672
  );
33537
33673
  }
33538
- },
33539
- onFrameBuffer,
33540
- cfg
33541
- );
33542
- if (probeSession) {
33543
- lastBrowserConsole = probeSession.browserConsoleBuffer;
33544
- await closeCaptureSession(probeSession);
33545
- probeSession = null;
33674
+ } finally {
33675
+ lastBrowserConsole = session.browserConsoleBuffer;
33676
+ await closeCaptureSession(session);
33677
+ }
33546
33678
  }
33547
- } else {
33548
- const videoInjector = createVideoFrameInjector(frameLookup);
33549
- const session = probeSession ?? await createCaptureSession(
33550
- fileServer.url,
33551
- framesDir,
33552
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
33553
- videoInjector,
33554
- cfg
33555
- );
33556
- if (probeSession) {
33557
- prepareCaptureSessionForReuse(session, framesDir, videoInjector);
33558
- probeSession = null;
33679
+ const encodeResult = await currentEncoder.close();
33680
+ streamingEncoderClosed = true;
33681
+ assertNotAborted();
33682
+ if (!encodeResult.success) {
33683
+ throw new Error(`Streaming encode failed: ${encodeResult.error}`);
33559
33684
  }
33560
- try {
33561
- if (!session.isInitialized) {
33562
- await initializeSession(session);
33685
+ perfStages.captureMs = Date.now() - stage4Start;
33686
+ perfStages.encodeMs = encodeResult.durationMs;
33687
+ } else {
33688
+ if (workerCount > 1) {
33689
+ const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
33690
+ await executeParallelCapture(
33691
+ fileServer.url,
33692
+ workDir,
33693
+ tasks,
33694
+ { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
33695
+ () => createVideoFrameInjector(frameLookup),
33696
+ abortSignal,
33697
+ (progress) => {
33698
+ job.framesRendered = progress.capturedFrames;
33699
+ const frameProgress = progress.capturedFrames / progress.totalFrames;
33700
+ const progressPct = 25 + frameProgress * 45;
33701
+ if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
33702
+ updateJobStatus(
33703
+ job,
33704
+ "rendering",
33705
+ `Capturing frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
33706
+ Math.round(progressPct),
33707
+ onProgress
33708
+ );
33709
+ }
33710
+ },
33711
+ void 0,
33712
+ cfg
33713
+ );
33714
+ await mergeWorkerFrames(workDir, tasks, framesDir);
33715
+ if (probeSession) {
33716
+ lastBrowserConsole = probeSession.browserConsoleBuffer;
33717
+ await closeCaptureSession(probeSession);
33718
+ probeSession = null;
33563
33719
  }
33564
- assertNotAborted();
33565
- lastBrowserConsole = session.browserConsoleBuffer;
33566
- for (let i2 = 0; i2 < totalFrames; i2++) {
33567
- assertNotAborted();
33568
- const time = i2 / job.config.fps;
33569
- const { buffer } = await captureFrameToBuffer(session, i2, time);
33570
- await reorderBuffer.waitForFrame(i2);
33571
- currentEncoder.writeFrame(buffer);
33572
- reorderBuffer.advanceTo(i2 + 1);
33573
- job.framesRendered = i2 + 1;
33574
- const frameProgress = (i2 + 1) / totalFrames;
33575
- const progress = 25 + frameProgress * 55;
33576
- updateJobStatus(
33577
- job,
33578
- "rendering",
33579
- `Streaming frame ${i2 + 1}/${job.totalFrames}`,
33580
- Math.round(progress),
33581
- onProgress
33582
- );
33720
+ } else {
33721
+ const videoInjector = createVideoFrameInjector(frameLookup);
33722
+ const session = probeSession ?? await createCaptureSession(
33723
+ fileServer.url,
33724
+ framesDir,
33725
+ { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
33726
+ videoInjector,
33727
+ cfg
33728
+ );
33729
+ if (probeSession) {
33730
+ prepareCaptureSessionForReuse(session, framesDir, videoInjector);
33731
+ probeSession = null;
33583
33732
  }
33584
- } finally {
33585
- lastBrowserConsole = session.browserConsoleBuffer;
33586
- await closeCaptureSession(session);
33587
- }
33588
- }
33589
- const encodeResult = await currentEncoder.close();
33590
- assertNotAborted();
33591
- if (!encodeResult.success) {
33592
- throw new Error(`Streaming encode failed: ${encodeResult.error}`);
33593
- }
33594
- perfStages.captureMs = Date.now() - stage4Start;
33595
- perfStages.encodeMs = encodeResult.durationMs;
33596
- } else {
33597
- if (workerCount > 1) {
33598
- const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
33599
- await executeParallelCapture(
33600
- fileServer.url,
33601
- workDir,
33602
- tasks,
33603
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
33604
- () => createVideoFrameInjector(frameLookup),
33605
- abortSignal,
33606
- (progress) => {
33607
- job.framesRendered = progress.capturedFrames;
33608
- const frameProgress = progress.capturedFrames / progress.totalFrames;
33609
- const progressPct = 25 + frameProgress * 45;
33610
- if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
33733
+ try {
33734
+ if (!session.isInitialized) {
33735
+ await initializeSession(session);
33736
+ }
33737
+ assertNotAborted();
33738
+ lastBrowserConsole = session.browserConsoleBuffer;
33739
+ for (let i2 = 0; i2 < job.totalFrames; i2++) {
33740
+ assertNotAborted();
33741
+ const time = i2 / job.config.fps;
33742
+ await captureFrame(session, i2, time);
33743
+ job.framesRendered = i2 + 1;
33744
+ const frameProgress = (i2 + 1) / job.totalFrames;
33745
+ const progress = 25 + frameProgress * 45;
33611
33746
  updateJobStatus(
33612
33747
  job,
33613
33748
  "rendering",
33614
- `Capturing frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
33615
- Math.round(progressPct),
33749
+ `Capturing frame ${i2 + 1}/${job.totalFrames}`,
33750
+ Math.round(progress),
33616
33751
  onProgress
33617
33752
  );
33618
33753
  }
33619
- },
33620
- void 0,
33621
- cfg
33622
- );
33623
- await mergeWorkerFrames(workDir, tasks, framesDir);
33624
- if (probeSession) {
33625
- lastBrowserConsole = probeSession.browserConsoleBuffer;
33626
- await closeCaptureSession(probeSession);
33627
- probeSession = null;
33754
+ } finally {
33755
+ lastBrowserConsole = session.browserConsoleBuffer;
33756
+ await closeCaptureSession(session);
33757
+ }
33628
33758
  }
33629
- } else {
33630
- const videoInjector = createVideoFrameInjector(frameLookup);
33631
- const session = probeSession ?? await createCaptureSession(
33632
- fileServer.url,
33759
+ perfStages.captureMs = Date.now() - stage4Start;
33760
+ const stage5Start = Date.now();
33761
+ updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
33762
+ const frameExt = needsAlpha ? "png" : "jpg";
33763
+ const framePattern = `frame_%06d.${frameExt}`;
33764
+ const encoderOpts = {
33765
+ fps: job.config.fps,
33766
+ width,
33767
+ height,
33768
+ codec: preset.codec,
33769
+ preset: preset.preset,
33770
+ quality: effectiveQuality,
33771
+ bitrate: effectiveBitrate,
33772
+ pixelFormat: preset.pixelFormat,
33773
+ useGpu: job.config.useGpu,
33774
+ hdr: preset.hdr
33775
+ };
33776
+ const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
33633
33777
  framesDir,
33634
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
33635
- videoInjector,
33636
- cfg
33778
+ framePattern,
33779
+ videoOnlyPath,
33780
+ encoderOpts,
33781
+ chunkedEncodeSize,
33782
+ abortSignal
33783
+ ) : await encodeFramesFromDir(
33784
+ framesDir,
33785
+ framePattern,
33786
+ videoOnlyPath,
33787
+ encoderOpts,
33788
+ abortSignal
33637
33789
  );
33638
- if (probeSession) {
33639
- prepareCaptureSessionForReuse(session, framesDir, videoInjector);
33640
- probeSession = null;
33790
+ assertNotAborted();
33791
+ if (!encodeResult.success) {
33792
+ throw new Error(`Encoding failed: ${encodeResult.error}`);
33641
33793
  }
33794
+ perfStages.encodeMs = Date.now() - stage5Start;
33795
+ }
33796
+ } finally {
33797
+ if (streamingEncoder && !streamingEncoderClosed) {
33642
33798
  try {
33643
- if (!session.isInitialized) {
33644
- await initializeSession(session);
33645
- }
33646
- assertNotAborted();
33647
- lastBrowserConsole = session.browserConsoleBuffer;
33648
- for (let i2 = 0; i2 < job.totalFrames; i2++) {
33649
- assertNotAborted();
33650
- const time = i2 / job.config.fps;
33651
- await captureFrame(session, i2, time);
33652
- job.framesRendered = i2 + 1;
33653
- const frameProgress = (i2 + 1) / job.totalFrames;
33654
- const progress = 25 + frameProgress * 45;
33655
- updateJobStatus(
33656
- job,
33657
- "rendering",
33658
- `Capturing frame ${i2 + 1}/${job.totalFrames}`,
33659
- Math.round(progress),
33660
- onProgress
33661
- );
33662
- }
33663
- } finally {
33664
- lastBrowserConsole = session.browserConsoleBuffer;
33665
- await closeCaptureSession(session);
33799
+ await streamingEncoder.close();
33800
+ } catch (err) {
33801
+ log2.warn("streamingEncoder defensive close failed", {
33802
+ err: err instanceof Error ? err.message : String(err)
33803
+ });
33666
33804
  }
33667
33805
  }
33668
- perfStages.captureMs = Date.now() - stage4Start;
33669
- const stage5Start = Date.now();
33670
- updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
33671
- const frameExt = needsAlpha ? "png" : "jpg";
33672
- const framePattern = `frame_%06d.${frameExt}`;
33673
- const encoderOpts = {
33674
- fps: job.config.fps,
33675
- width,
33676
- height,
33677
- codec: preset.codec,
33678
- preset: preset.preset,
33679
- quality: preset.quality,
33680
- pixelFormat: preset.pixelFormat,
33681
- useGpu: job.config.useGpu,
33682
- hdr: preset.hdr
33683
- };
33684
- const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
33685
- framesDir,
33686
- framePattern,
33687
- videoOnlyPath,
33688
- encoderOpts,
33689
- chunkedEncodeSize,
33690
- abortSignal
33691
- ) : await encodeFramesFromDir(
33692
- framesDir,
33693
- framePattern,
33694
- videoOnlyPath,
33695
- encoderOpts,
33696
- abortSignal
33697
- );
33698
- assertNotAborted();
33699
- if (!encodeResult.success) {
33700
- throw new Error(`Encoding failed: ${encodeResult.error}`);
33701
- }
33702
- perfStages.encodeMs = Date.now() - stage5Start;
33703
33806
  }
33704
33807
  }
33705
33808
  if (probeSession !== null) {
@@ -33909,7 +34012,7 @@ var init_config3 = __esm({
33909
34012
 
33910
34013
  // ../producer/src/services/hyperframeLint.ts
33911
34014
  import { existsSync as existsSync29, readFileSync as readFileSync22, statSync as statSync8 } from "fs";
33912
- import { resolve as resolve16, join as join32 } from "path";
34015
+ import { resolve as resolve17, join as join32 } from "path";
33913
34016
  function isStringRecord(value) {
33914
34017
  if (!value || typeof value !== "object" || Array.isArray(value)) {
33915
34018
  return false;
@@ -33936,7 +34039,7 @@ function pickEntryFile(files, preferredEntryFile) {
33936
34039
  return null;
33937
34040
  }
33938
34041
  function readProjectEntryFile(projectDir, preferredEntryFile) {
33939
- const absProjectDir = resolve16(projectDir);
34042
+ const absProjectDir = resolve17(projectDir);
33940
34043
  if (!existsSync29(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
33941
34044
  return { error: `Project directory not found: ${absProjectDir}` };
33942
34045
  }
@@ -33944,7 +34047,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
33944
34047
  (value) => typeof value === "string" && value.trim().length > 0
33945
34048
  );
33946
34049
  for (const entryFile of entryCandidates) {
33947
- const absoluteEntryPath = resolve16(absProjectDir, entryFile);
34050
+ const absoluteEntryPath = resolve17(absProjectDir, entryFile);
33948
34051
  if (!absoluteEntryPath.startsWith(absProjectDir)) {
33949
34052
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
33950
34053
  }
@@ -34019,10 +34122,10 @@ var init_semaphore = __esm({
34019
34122
  this.active++;
34020
34123
  return () => this.release();
34021
34124
  }
34022
- return new Promise((resolve35) => {
34125
+ return new Promise((resolve37) => {
34023
34126
  this.queue.push(() => {
34024
34127
  this.active++;
34025
- resolve35(() => this.release());
34128
+ resolve37(() => this.release());
34026
34129
  });
34027
34130
  });
34028
34131
  }
@@ -34053,7 +34156,7 @@ import {
34053
34156
  rmSync as rmSync7,
34054
34157
  createReadStream
34055
34158
  } from "fs";
34056
- import { resolve as resolve17, dirname as dirname11, join as join33 } from "path";
34159
+ import { resolve as resolve18, dirname as dirname11, join as join33 } from "path";
34057
34160
  import { tmpdir as tmpdir2 } from "os";
34058
34161
  import { parseArgs as parseArgs2 } from "util";
34059
34162
  import crypto2 from "crypto";
@@ -34075,12 +34178,12 @@ async function prepareRenderBody(body) {
34075
34178
  const options = parseRenderOptions(body);
34076
34179
  const projectDir = typeof body.projectDir === "string" ? body.projectDir : void 0;
34077
34180
  if (projectDir) {
34078
- const absProjectDir = resolve17(projectDir);
34181
+ const absProjectDir = resolve18(projectDir);
34079
34182
  if (!existsSync30(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
34080
34183
  return { error: `Project directory not found: ${absProjectDir}` };
34081
34184
  }
34082
34185
  const entry = options.entryFile || "index.html";
34083
- if (!existsSync30(resolve17(absProjectDir, entry))) {
34186
+ if (!existsSync30(resolve18(absProjectDir, entry))) {
34084
34187
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
34085
34188
  }
34086
34189
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -34121,7 +34224,7 @@ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log2) {
34121
34224
  try {
34122
34225
  return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
34123
34226
  } catch (error) {
34124
- const fallbackPath = resolve17(rendersDir, `producer-fallback-${Date.now()}.mp4`);
34227
+ const fallbackPath = resolve18(rendersDir, `producer-fallback-${Date.now()}.mp4`);
34125
34228
  log2.warn("Failed to resolve output path, using fallback", {
34126
34229
  fallback: fallbackPath,
34127
34230
  error: error instanceof Error ? error.message : String(error)
@@ -34509,7 +34612,7 @@ var init_server = __esm({
34509
34612
  init_paths();
34510
34613
  init_logger();
34511
34614
  init_semaphore();
34512
- entryScript = process.argv[1] ? resolve17(process.argv[1]) : "";
34615
+ entryScript = process.argv[1] ? resolve18(process.argv[1]) : "";
34513
34616
  isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
34514
34617
  if (isPublicServerEntry) {
34515
34618
  const { values } = parseArgs2({
@@ -34582,18 +34685,18 @@ __export(studioServer_exports, {
34582
34685
  import { Hono as Hono5 } from "hono";
34583
34686
  import { streamSSE as streamSSE3 } from "hono/streaming";
34584
34687
  import { existsSync as existsSync31, readFileSync as readFileSync23, writeFileSync as writeFileSync12, statSync as statSync10 } from "fs";
34585
- import { resolve as resolve18, join as join34, basename as basename2 } from "path";
34688
+ import { resolve as resolve19, join as join34, basename as basename2 } from "path";
34586
34689
  function resolveDistDir() {
34587
- const builtPath = resolve18(__dirname, "studio");
34588
- if (existsSync31(resolve18(builtPath, "index.html"))) return builtPath;
34589
- const devPath = resolve18(__dirname, "..", "..", "..", "studio", "dist");
34590
- if (existsSync31(resolve18(devPath, "index.html"))) return devPath;
34690
+ const builtPath = resolve19(__dirname, "studio");
34691
+ if (existsSync31(resolve19(builtPath, "index.html"))) return builtPath;
34692
+ const devPath = resolve19(__dirname, "..", "..", "..", "studio", "dist");
34693
+ if (existsSync31(resolve19(devPath, "index.html"))) return devPath;
34591
34694
  return builtPath;
34592
34695
  }
34593
34696
  function resolveRuntimePath() {
34594
- const builtPath = resolve18(__dirname, "hyperframe-runtime.js");
34697
+ const builtPath = resolve19(__dirname, "hyperframe-runtime.js");
34595
34698
  if (existsSync31(builtPath)) return builtPath;
34596
- const devPath = resolve18(
34699
+ const devPath = resolve19(
34597
34700
  __dirname,
34598
34701
  "..",
34599
34702
  "..",
@@ -34814,7 +34917,7 @@ function createStudioServer(options) {
34814
34917
  return api.fetch(forwardReq);
34815
34918
  });
34816
34919
  app.get("/assets/*", (c2) => {
34817
- const filePath = resolve18(studioDir, c2.req.path.slice(1));
34920
+ const filePath = resolve19(studioDir, c2.req.path.slice(1));
34818
34921
  if (!existsSync31(filePath) || !statSync10(filePath).isFile()) return c2.text("not found", 404);
34819
34922
  const content = readFileSync23(filePath);
34820
34923
  return new Response(content, {
@@ -34822,7 +34925,7 @@ function createStudioServer(options) {
34822
34925
  });
34823
34926
  });
34824
34927
  app.get("/icons/*", (c2) => {
34825
- const filePath = resolve18(studioDir, c2.req.path.slice(1));
34928
+ const filePath = resolve19(studioDir, c2.req.path.slice(1));
34826
34929
  if (!existsSync31(filePath) || !statSync10(filePath).isFile()) return c2.text("not found", 404);
34827
34930
  const content = readFileSync23(filePath);
34828
34931
  return new Response(content, {
@@ -34830,7 +34933,7 @@ function createStudioServer(options) {
34830
34933
  });
34831
34934
  });
34832
34935
  app.get("*", (c2) => {
34833
- const indexPath = resolve18(studioDir, "index.html");
34936
+ const indexPath = resolve19(studioDir, "index.html");
34834
34937
  if (!existsSync31(indexPath)) {
34835
34938
  return c2.text("Studio not found. Rebuild with: pnpm run build", 500);
34836
34939
  }
@@ -34859,12 +34962,12 @@ __export(preview_exports, {
34859
34962
  });
34860
34963
  import { spawn as spawn8 } from "child_process";
34861
34964
  import { existsSync as existsSync32, lstatSync, symlinkSync, unlinkSync as unlinkSync5, readlinkSync, mkdirSync as mkdirSync19 } from "fs";
34862
- import { resolve as resolve19, dirname as dirname12, basename as basename3, join as join35 } from "path";
34965
+ import { resolve as resolve20, dirname as dirname12, basename as basename3, join as join35 } from "path";
34863
34966
  import { fileURLToPath as fileURLToPath4 } from "url";
34864
34967
  import { createRequire } from "module";
34865
34968
  async function runDevMode(dir, projectName) {
34866
34969
  const thisFile = fileURLToPath4(import.meta.url);
34867
- const repoRoot = resolve19(dirname12(thisFile), "..", "..", "..", "..");
34970
+ const repoRoot = resolve20(dirname12(thisFile), "..", "..", "..", "..");
34868
34971
  const projectsDir = join35(repoRoot, "packages", "studio", "data", "projects");
34869
34972
  const pName = projectName ?? basename3(dir);
34870
34973
  const symlinkPath = join35(projectsDir, pName);
@@ -34876,7 +34979,7 @@ async function runDevMode(dir, projectName) {
34876
34979
  const stat3 = lstatSync(symlinkPath);
34877
34980
  if (stat3.isSymbolicLink()) {
34878
34981
  const target = readlinkSync(symlinkPath);
34879
- if (resolve19(target) !== resolve19(dir)) {
34982
+ if (resolve20(target) !== resolve20(dir)) {
34880
34983
  unlinkSync5(symlinkPath);
34881
34984
  }
34882
34985
  }
@@ -34930,8 +35033,8 @@ async function runDevMode(dir, projectName) {
34930
35033
  }
34931
35034
  });
34932
35035
  }
34933
- return new Promise((resolve35) => {
34934
- child.on("close", () => resolve35());
35036
+ return new Promise((resolve37) => {
35037
+ child.on("close", () => resolve37());
34935
35038
  });
34936
35039
  }
34937
35040
  function hasLocalStudio(dir) {
@@ -34953,7 +35056,7 @@ async function runLocalStudioMode(dir, projectName) {
34953
35056
  let createdSymlink = false;
34954
35057
  if (dir !== symlinkPath) {
34955
35058
  if (existsSync32(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
34956
- if (resolve19(readlinkSync(symlinkPath)) !== resolve19(dir)) {
35059
+ if (resolve20(readlinkSync(symlinkPath)) !== resolve20(dir)) {
34957
35060
  unlinkSync5(symlinkPath);
34958
35061
  }
34959
35062
  }
@@ -35001,8 +35104,8 @@ async function runLocalStudioMode(dir, projectName) {
35001
35104
  }
35002
35105
  });
35003
35106
  }
35004
- return new Promise((resolve35) => {
35005
- child.on("close", () => resolve35());
35107
+ return new Promise((resolve37) => {
35108
+ child.on("close", () => resolve37());
35006
35109
  });
35007
35110
  }
35008
35111
  async function runEmbeddedMode(dir, startPort, projectName, forceNew = false) {
@@ -35133,7 +35236,7 @@ var init_preview2 = __esm({
35133
35236
  return;
35134
35237
  }
35135
35238
  const rawArg = args.dir;
35136
- const dir = resolve19(rawArg ?? ".");
35239
+ const dir = resolve20(rawArg ?? ".");
35137
35240
  const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
35138
35241
  const projectName = isImplicitCwd ? basename3(process.env.PWD ?? dir) : basename3(dir);
35139
35242
  const indexPath = join35(dir, "index.html");
@@ -35174,7 +35277,7 @@ import {
35174
35277
  readFileSync as readFileSync24,
35175
35278
  readdirSync as readdirSync12
35176
35279
  } from "fs";
35177
- import { resolve as resolve20, basename as basename4, join as join36, dirname as dirname13 } from "path";
35280
+ import { resolve as resolve21, basename as basename4, join as join36, dirname as dirname13 } from "path";
35178
35281
  import { fileURLToPath as fileURLToPath5 } from "url";
35179
35282
  import { execFileSync as execFileSync4, spawn as spawn9 } from "child_process";
35180
35283
  function probeVideo(filePath) {
@@ -35244,8 +35347,8 @@ function transcodeToMp4(inputPath, outputPath) {
35244
35347
  }
35245
35348
  function resolveAssetDir(devSegments, builtSegments) {
35246
35349
  const base = dirname13(fileURLToPath5(import.meta.url));
35247
- const devPath = resolve20(base, ...devSegments);
35248
- const builtPath = resolve20(base, ...builtSegments);
35350
+ const devPath = resolve21(base, ...devSegments);
35351
+ const builtPath = resolve21(base, ...builtSegments);
35249
35352
  return existsSync33(devPath) ? devPath : builtPath;
35250
35353
  }
35251
35354
  function getStaticTemplateDir(templateId) {
@@ -35330,7 +35433,7 @@ async function handleVideoFile(videoPath, destDir, interactive) {
35330
35433
  }
35331
35434
  if (shouldTranscode) {
35332
35435
  const mp4Name = localVideoName.replace(/\.[^.]+$/, ".mp4");
35333
- const mp4Path = resolve20(destDir, mp4Name);
35436
+ const mp4Path = resolve21(destDir, mp4Name);
35334
35437
  const spin = be();
35335
35438
  spin.start("Transcoding to H.264 MP4...");
35336
35439
  const ok = await transcodeToMp4(videoPath, mp4Path);
@@ -35339,10 +35442,10 @@ async function handleVideoFile(videoPath, destDir, interactive) {
35339
35442
  localVideoName = mp4Name;
35340
35443
  } else {
35341
35444
  spin.stop(c.warn("Transcode failed \u2014 copying original file"));
35342
- copyFileSync3(videoPath, resolve20(destDir, localVideoName));
35445
+ copyFileSync3(videoPath, resolve21(destDir, localVideoName));
35343
35446
  }
35344
35447
  } else {
35345
- copyFileSync3(videoPath, resolve20(destDir, localVideoName));
35448
+ copyFileSync3(videoPath, resolve21(destDir, localVideoName));
35346
35449
  }
35347
35450
  } else {
35348
35451
  if (interactive) {
@@ -35352,10 +35455,10 @@ async function handleVideoFile(videoPath, destDir, interactive) {
35352
35455
  console.log(c.warn("ffmpeg not installed \u2014 cannot transcode. Copying original."));
35353
35456
  console.log(c.dim("Install: ") + c.accent("brew install ffmpeg"));
35354
35457
  }
35355
- copyFileSync3(videoPath, resolve20(destDir, localVideoName));
35458
+ copyFileSync3(videoPath, resolve21(destDir, localVideoName));
35356
35459
  }
35357
35460
  } else {
35358
- copyFileSync3(videoPath, resolve20(destDir, localVideoName));
35461
+ copyFileSync3(videoPath, resolve21(destDir, localVideoName));
35359
35462
  }
35360
35463
  return { meta, localVideoName };
35361
35464
  }
@@ -35369,7 +35472,7 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
35369
35472
  }
35370
35473
  patchVideoSrc(destDir, localVideoName, durationSeconds);
35371
35474
  writeFileSync13(
35372
- resolve20(destDir, "meta.json"),
35475
+ resolve21(destDir, "meta.json"),
35373
35476
  JSON.stringify(
35374
35477
  {
35375
35478
  id: name,
@@ -35381,7 +35484,7 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
35381
35484
  ),
35382
35485
  "utf-8"
35383
35486
  );
35384
- if (!existsSync33(resolve20(destDir, "hyperframes.json"))) {
35487
+ if (!existsSync33(resolve21(destDir, "hyperframes.json"))) {
35385
35488
  const { writeProjectConfig: writeProjectConfig2, DEFAULT_PROJECT_CONFIG: DEFAULT_PROJECT_CONFIG2 } = await Promise.resolve().then(() => (init_projectConfig(), projectConfig_exports));
35386
35489
  writeProjectConfig2(destDir, DEFAULT_PROJECT_CONFIG2);
35387
35490
  }
@@ -35389,7 +35492,7 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
35389
35492
  if (existsSync33(sharedDir)) {
35390
35493
  for (const entry of readdirSync12(sharedDir, { withFileTypes: true })) {
35391
35494
  const src = join36(sharedDir, entry.name);
35392
- const dest = resolve20(destDir, entry.name);
35495
+ const dest = resolve21(destDir, entry.name);
35393
35496
  if (entry.isFile() || entry.isSymbolicLink()) {
35394
35497
  copyFileSync3(src, dest);
35395
35498
  }
@@ -35501,7 +35604,7 @@ var init_init = __esm({
35501
35604
  if (!interactive) {
35502
35605
  const templateId2 = exampleFlag ?? "blank";
35503
35606
  const name2 = args.name ?? "my-video";
35504
- const destDir2 = resolve20(name2);
35607
+ const destDir2 = resolve21(name2);
35505
35608
  if (existsSync33(destDir2) && readdirSync12(destDir2).length > 0) {
35506
35609
  console.error(c.error(`Directory already exists and is not empty: ${name2}`));
35507
35610
  process.exit(1);
@@ -35515,7 +35618,7 @@ var init_init = __esm({
35515
35618
  process.exit(1);
35516
35619
  }
35517
35620
  if (videoFlag) {
35518
- const videoPath = resolve20(videoFlag);
35621
+ const videoPath = resolve21(videoFlag);
35519
35622
  if (!existsSync33(videoPath)) {
35520
35623
  console.error(c.error(`Video file not found: ${videoFlag}`));
35521
35624
  process.exit(1);
@@ -35529,13 +35632,13 @@ var init_init = __esm({
35529
35632
  );
35530
35633
  }
35531
35634
  if (audioFlag) {
35532
- const audioPath = resolve20(audioFlag);
35635
+ const audioPath = resolve21(audioFlag);
35533
35636
  if (!existsSync33(audioPath)) {
35534
35637
  console.error(c.error(`Audio file not found: ${audioFlag}`));
35535
35638
  process.exit(1);
35536
35639
  }
35537
35640
  sourceFilePath2 = audioPath;
35538
- copyFileSync3(audioPath, resolve20(destDir2, basename4(audioPath)));
35641
+ copyFileSync3(audioPath, resolve21(destDir2, basename4(audioPath)));
35539
35642
  console.log(`Audio: ${basename4(audioPath)}`);
35540
35643
  }
35541
35644
  if (sourceFilePath2 && !skipTranscribe) {
@@ -35575,7 +35678,7 @@ var init_init = __esm({
35575
35678
  process.exit(1);
35576
35679
  }
35577
35680
  trackInitTemplate(templateId2);
35578
- const transcriptFile2 = resolve20(destDir2, "transcript.json");
35681
+ const transcriptFile2 = resolve21(destDir2, "transcript.json");
35579
35682
  if (existsSync33(transcriptFile2)) {
35580
35683
  await patchTranscript(destDir2, transcriptFile2);
35581
35684
  }
@@ -35627,7 +35730,7 @@ var init_init = __esm({
35627
35730
  }
35628
35731
  name = nameResult;
35629
35732
  }
35630
- const destDir = resolve20(name);
35733
+ const destDir = resolve21(name);
35631
35734
  if (existsSync33(destDir) && readdirSync12(destDir).length > 0) {
35632
35735
  const overwrite = await Rt({
35633
35736
  message: `Directory ${c.accent(name)} already exists and is not empty. Overwrite?`,
@@ -35642,7 +35745,7 @@ var init_init = __esm({
35642
35745
  let sourceFilePath;
35643
35746
  let videoDuration;
35644
35747
  if (videoFlag) {
35645
- const videoPath = resolve20(videoFlag);
35748
+ const videoPath = resolve21(videoFlag);
35646
35749
  if (!existsSync33(videoPath)) {
35647
35750
  R2.error(`File not found: ${videoFlag}`);
35648
35751
  Nt("Setup cancelled.");
@@ -35654,7 +35757,7 @@ var init_init = __esm({
35654
35757
  localVideoName = result.localVideoName;
35655
35758
  videoDuration = result.meta.durationSeconds;
35656
35759
  } else if (audioFlag) {
35657
- const audioPath = resolve20(audioFlag);
35760
+ const audioPath = resolve21(audioFlag);
35658
35761
  if (!existsSync33(audioPath)) {
35659
35762
  R2.error(`File not found: ${audioFlag}`);
35660
35763
  Nt("Setup cancelled.");
@@ -35662,7 +35765,7 @@ var init_init = __esm({
35662
35765
  }
35663
35766
  mkdirSync20(destDir, { recursive: true });
35664
35767
  sourceFilePath = audioPath;
35665
- copyFileSync3(audioPath, resolve20(destDir, basename4(audioPath)));
35768
+ copyFileSync3(audioPath, resolve21(destDir, basename4(audioPath)));
35666
35769
  R2.info(`Audio copied to ${c.accent(basename4(audioPath))}`);
35667
35770
  }
35668
35771
  if (sourceFilePath) {
@@ -35747,7 +35850,7 @@ ${c.dim("Use --example blank for offline use.")}`
35747
35850
  process.exit(1);
35748
35851
  }
35749
35852
  trackInitTemplate(templateId);
35750
- const transcriptFile = resolve20(destDir, "transcript.json");
35853
+ const transcriptFile = resolve21(destDir, "transcript.json");
35751
35854
  if (existsSync33(transcriptFile)) {
35752
35855
  await patchTranscript(destDir, transcriptFile);
35753
35856
  }
@@ -35835,7 +35938,7 @@ __export(add_exports, {
35835
35938
  runAdd: () => runAdd
35836
35939
  });
35837
35940
  import { existsSync as existsSync34 } from "fs";
35838
- import { resolve as resolve21, relative as relative3 } from "path";
35941
+ import { resolve as resolve22, relative as relative3 } from "path";
35839
35942
  function remapTarget(item, originalTarget, paths) {
35840
35943
  if (item.type === "hyperframes:block") {
35841
35944
  const blocksDir = paths.blocks.replace(/\/+$/, "");
@@ -35858,10 +35961,10 @@ function buildSnippet(item, relativeTarget) {
35858
35961
  return "";
35859
35962
  }
35860
35963
  async function runAdd(opts) {
35861
- const projectDir = resolve21(opts.projectDir);
35964
+ const projectDir = resolve22(opts.projectDir);
35862
35965
  let config = loadProjectConfig(projectDir);
35863
35966
  const hasConfig = existsSync34(projectConfigPath(projectDir));
35864
- if (!hasConfig && existsSync34(resolve21(projectDir, "index.html"))) {
35967
+ if (!hasConfig && existsSync34(resolve22(projectDir, "index.html"))) {
35865
35968
  writeProjectConfig(projectDir, DEFAULT_PROJECT_CONFIG);
35866
35969
  config = DEFAULT_PROJECT_CONFIG;
35867
35970
  }
@@ -35957,7 +36060,7 @@ var init_add = __esm({
35957
36060
  }
35958
36061
  },
35959
36062
  async run({ args }) {
35960
- const projectDir = resolve21(args.dir ?? process.cwd());
36063
+ const projectDir = resolve22(args.dir ?? process.cwd());
35961
36064
  const json = args.json === true;
35962
36065
  const skipClipboard = args["no-clipboard"] === true;
35963
36066
  const hasConfigBefore = existsSync34(projectConfigPath(projectDir));
@@ -36005,7 +36108,7 @@ __export(catalog_exports, {
36005
36108
  default: () => catalog_default,
36006
36109
  examples: () => examples4
36007
36110
  });
36008
- import { resolve as resolve22 } from "path";
36111
+ import { resolve as resolve23 } from "path";
36009
36112
  var examples4, catalog_default;
36010
36113
  var init_catalog = __esm({
36011
36114
  "src/commands/catalog.ts"() {
@@ -36049,7 +36152,7 @@ var init_catalog = __esm({
36049
36152
  async run({ args }) {
36050
36153
  const json = args.json === true;
36051
36154
  const interactive = args["human-friendly"] === true;
36052
- const dir = resolve22(process.cwd());
36155
+ const dir = resolve23(process.cwd());
36053
36156
  const config = loadProjectConfig(dir) ?? DEFAULT_PROJECT_CONFIG;
36054
36157
  let typeFilter;
36055
36158
  if (args.type === "block") typeFilter = "hyperframes:block";
@@ -36174,11 +36277,11 @@ var init_format = __esm({
36174
36277
 
36175
36278
  // src/utils/project.ts
36176
36279
  import { existsSync as existsSync35, statSync as statSync11 } from "fs";
36177
- import { resolve as resolve23, basename as basename5 } from "path";
36280
+ import { resolve as resolve24, basename as basename5 } from "path";
36178
36281
  function resolveProject(dirArg) {
36179
- const dir = resolve23(dirArg ?? ".");
36282
+ const dir = resolve24(dirArg ?? ".");
36180
36283
  const name = basename5(dir);
36181
- const indexPath = resolve23(dir, "index.html");
36284
+ const indexPath = resolve24(dir, "index.html");
36182
36285
  if (!existsSync35(dir) || !statSync11(dir).isDirectory()) {
36183
36286
  errorBox("Not a directory: " + dir);
36184
36287
  process.exit(1);
@@ -36207,7 +36310,7 @@ __export(play_exports, {
36207
36310
  examples: () => examples5
36208
36311
  });
36209
36312
  import { existsSync as existsSync36, readFileSync as readFileSync25 } from "fs";
36210
- import { resolve as resolve24, dirname as dirname14 } from "path";
36313
+ import { resolve as resolve25, dirname as dirname14 } from "path";
36211
36314
  function commandDir() {
36212
36315
  return dirname14(new URL(import.meta.url).pathname);
36213
36316
  }
@@ -36215,10 +36318,10 @@ function resolveRuntimePath2() {
36215
36318
  const d = commandDir();
36216
36319
  const candidates = [
36217
36320
  // Bundled with CLI dist
36218
- resolve24(d, "hyperframe-runtime.js"),
36219
- resolve24(d, "..", "hyperframe-runtime.js"),
36321
+ resolve25(d, "hyperframe-runtime.js"),
36322
+ resolve25(d, "..", "hyperframe-runtime.js"),
36220
36323
  // Monorepo dev: commands/ → src/ → cli/ → packages/ then into core/dist/
36221
- resolve24(d, "..", "..", "..", "core", "dist", "hyperframe.runtime.iife.js")
36324
+ resolve25(d, "..", "..", "..", "core", "dist", "hyperframe.runtime.iife.js")
36222
36325
  ];
36223
36326
  for (const p of candidates) {
36224
36327
  if (existsSync36(p)) return p;
@@ -36229,10 +36332,10 @@ function resolvePlayerPath() {
36229
36332
  const d = commandDir();
36230
36333
  const candidates = [
36231
36334
  // Monorepo dev: commands/ → src/ → cli/ → packages/ then into player/dist/
36232
- resolve24(d, "..", "..", "..", "player", "dist", "hyperframes-player.global.js"),
36335
+ resolve25(d, "..", "..", "..", "player", "dist", "hyperframes-player.global.js"),
36233
36336
  // Bundled with CLI dist
36234
- resolve24(d, "hyperframes-player.global.js"),
36235
- resolve24(d, "..", "hyperframes-player.global.js")
36337
+ resolve25(d, "hyperframes-player.global.js"),
36338
+ resolve25(d, "..", "hyperframes-player.global.js")
36236
36339
  ];
36237
36340
  for (const p of candidates) {
36238
36341
  if (existsSync36(p)) return p;
@@ -36335,7 +36438,7 @@ var init_play = __esm({
36335
36438
  });
36336
36439
  app.get("/composition/*", async (ctx) => {
36337
36440
  const reqPath = ctx.req.path.replace("/composition/", "");
36338
- const filePath = resolve24(project.dir, reqPath);
36441
+ const filePath = resolve25(project.dir, reqPath);
36339
36442
  if (!filePath.startsWith(project.dir)) return ctx.text("Forbidden", 403);
36340
36443
  if (!existsSync36(filePath)) return ctx.text("Not found", 404);
36341
36444
  const content = readFileSync25(filePath, "utf-8");
@@ -36412,6 +36515,184 @@ var init_play = __esm({
36412
36515
  }
36413
36516
  });
36414
36517
 
36518
+ // src/utils/publishProject.ts
36519
+ import { basename as basename6, join as join37, relative as relative4 } from "path";
36520
+ import { readdirSync as readdirSync13, readFileSync as readFileSync26, statSync as statSync12 } from "fs";
36521
+ import AdmZip from "adm-zip";
36522
+ function shouldIgnoreSegment(segment) {
36523
+ return segment.startsWith(".") || IGNORED_DIRS.has(segment) || IGNORED_FILES.has(segment);
36524
+ }
36525
+ function collectProjectFiles(rootDir, currentDir, paths) {
36526
+ for (const entry of readdirSync13(currentDir, { withFileTypes: true })) {
36527
+ if (shouldIgnoreSegment(entry.name)) continue;
36528
+ const absolutePath = join37(currentDir, entry.name);
36529
+ const relativePath = relative4(rootDir, absolutePath).replaceAll("\\", "/");
36530
+ if (!relativePath) continue;
36531
+ if (entry.isDirectory()) {
36532
+ collectProjectFiles(rootDir, absolutePath, paths);
36533
+ continue;
36534
+ }
36535
+ if (!statSync12(absolutePath).isFile()) continue;
36536
+ paths.push(relativePath);
36537
+ }
36538
+ }
36539
+ function createPublishArchive(projectDir) {
36540
+ const filePaths = [];
36541
+ collectProjectFiles(projectDir, projectDir, filePaths);
36542
+ if (!filePaths.includes("index.html")) {
36543
+ throw new Error("Project must include an index.html file at the root before publish.");
36544
+ }
36545
+ const archive = new AdmZip();
36546
+ for (const filePath of filePaths) {
36547
+ archive.addFile(filePath, readFileSync26(join37(projectDir, filePath)));
36548
+ }
36549
+ return {
36550
+ buffer: archive.toBuffer(),
36551
+ fileCount: filePaths.length
36552
+ };
36553
+ }
36554
+ function getPublishApiBaseUrl() {
36555
+ return (process.env["HYPERFRAMES_PUBLISHED_PROJECTS_API_URL"] || process.env["HEYGEN_API_URL"] || "https://api2.heygen.com").replace(/\/$/, "");
36556
+ }
36557
+ async function publishProjectArchive(projectDir) {
36558
+ const title = basename6(projectDir);
36559
+ const archive = createPublishArchive(projectDir);
36560
+ const archiveBytes = new Uint8Array(archive.buffer.byteLength);
36561
+ archiveBytes.set(archive.buffer);
36562
+ const body = new FormData();
36563
+ body.set("title", title);
36564
+ body.set("file", new File([archiveBytes], `${title}.zip`, { type: "application/zip" }));
36565
+ const headers = {
36566
+ heygen_route: "canary"
36567
+ };
36568
+ const response = await fetch(`${getPublishApiBaseUrl()}/v1/hyperframes/projects/publish`, {
36569
+ method: "POST",
36570
+ body,
36571
+ headers,
36572
+ signal: AbortSignal.timeout(3e4)
36573
+ });
36574
+ const payload = await response.json().catch(() => null);
36575
+ const message = typeof payload?.message === "string" ? payload.message : "Failed to publish project";
36576
+ if (!response.ok || !payload?.data) {
36577
+ throw new Error(message);
36578
+ }
36579
+ return {
36580
+ projectId: String(payload.data.project_id),
36581
+ title: String(payload.data.title),
36582
+ fileCount: Number(payload.data.file_count),
36583
+ url: String(payload.data.url),
36584
+ claimToken: String(payload.data.claim_token)
36585
+ };
36586
+ }
36587
+ var IGNORED_DIRS, IGNORED_FILES;
36588
+ var init_publishProject = __esm({
36589
+ "src/utils/publishProject.ts"() {
36590
+ "use strict";
36591
+ IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".next", "coverage"]);
36592
+ IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db"]);
36593
+ }
36594
+ });
36595
+
36596
+ // src/commands/publish.ts
36597
+ var publish_exports = {};
36598
+ __export(publish_exports, {
36599
+ default: () => publish_default,
36600
+ examples: () => examples6
36601
+ });
36602
+ import { basename as basename7, resolve as resolve26 } from "path";
36603
+ import { existsSync as existsSync37 } from "fs";
36604
+ import { join as join38 } from "path";
36605
+ var examples6, publish_default;
36606
+ var init_publish = __esm({
36607
+ "src/commands/publish.ts"() {
36608
+ "use strict";
36609
+ init_dist();
36610
+ init_dist3();
36611
+ init_colors();
36612
+ init_lintProject();
36613
+ init_lintFormat();
36614
+ init_publishProject();
36615
+ examples6 = [
36616
+ ["Publish the current project with a public URL", "hyperframes publish"],
36617
+ ["Publish a specific directory", "hyperframes publish ./my-video"],
36618
+ ["Skip the consent prompt (scripts)", "hyperframes publish --yes"]
36619
+ ];
36620
+ publish_default = defineCommand({
36621
+ meta: {
36622
+ name: "publish",
36623
+ description: "Upload the project and return a stable public URL"
36624
+ },
36625
+ args: {
36626
+ dir: { type: "positional", description: "Project directory", required: false },
36627
+ yes: {
36628
+ type: "boolean",
36629
+ alias: "y",
36630
+ description: "Skip the publish confirmation prompt",
36631
+ default: false
36632
+ }
36633
+ },
36634
+ async run({ args }) {
36635
+ const rawArg = args.dir;
36636
+ const dir = resolve26(rawArg ?? ".");
36637
+ const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
36638
+ const projectName = isImplicitCwd ? basename7(process.env["PWD"] ?? dir) : basename7(dir);
36639
+ const indexPath = join38(dir, "index.html");
36640
+ if (existsSync37(indexPath)) {
36641
+ const lintResult = lintProject({ dir, name: projectName, indexPath });
36642
+ if (lintResult.totalErrors > 0 || lintResult.totalWarnings > 0) {
36643
+ console.log();
36644
+ for (const line of formatLintFindings(lintResult)) console.log(line);
36645
+ console.log();
36646
+ }
36647
+ }
36648
+ if (args.yes !== true) {
36649
+ console.log();
36650
+ console.log(
36651
+ ` ${c.bold("hyperframes publish uploads this project and creates a stable public URL.")}`
36652
+ );
36653
+ console.log(
36654
+ ` ${c.dim("Anyone with the URL can open the published project and claim it after authenticating.")}`
36655
+ );
36656
+ console.log();
36657
+ const approved = await Rt({ message: "Publish this project?" });
36658
+ if (Ct(approved) || approved !== true) {
36659
+ console.log();
36660
+ console.log(` ${c.dim("Aborted.")}`);
36661
+ console.log();
36662
+ return;
36663
+ }
36664
+ }
36665
+ Wt2(c.bold("hyperframes publish"));
36666
+ const publishSpinner = be();
36667
+ publishSpinner.start("Uploading project...");
36668
+ try {
36669
+ const published = await publishProjectArchive(dir);
36670
+ const claimUrl = new URL(published.url);
36671
+ claimUrl.searchParams.set("claim_token", published.claimToken);
36672
+ publishSpinner.stop(c.success("Project published"));
36673
+ console.log();
36674
+ console.log(` ${c.dim("Project")} ${c.accent(published.title)}`);
36675
+ console.log(` ${c.dim("Files")} ${String(published.fileCount)}`);
36676
+ console.log(` ${c.dim("Public")} ${c.accent(claimUrl.toString())}`);
36677
+ console.log();
36678
+ console.log(
36679
+ ` ${c.dim("Open the URL on hyperframes.dev to claim the project and continue editing.")}`
36680
+ );
36681
+ console.log();
36682
+ return;
36683
+ } catch (err) {
36684
+ publishSpinner.stop(c.error("Publish failed"));
36685
+ console.error();
36686
+ console.error(` ${err.message}`);
36687
+ console.error();
36688
+ process.exitCode = 1;
36689
+ return;
36690
+ }
36691
+ }
36692
+ });
36693
+ }
36694
+ });
36695
+
36415
36696
  // src/utils/producer.ts
36416
36697
  async function loadProducer() {
36417
36698
  return await Promise.resolve().then(() => (init_src3(), src_exports3));
@@ -36525,11 +36806,11 @@ var init_ffmpeg = __esm({
36525
36806
  var render_exports = {};
36526
36807
  __export(render_exports, {
36527
36808
  default: () => render_default,
36528
- examples: () => examples6
36809
+ examples: () => examples7
36529
36810
  });
36530
- import { mkdirSync as mkdirSync21, readFileSync as readFileSync26, statSync as statSync12, writeFileSync as writeFileSync14, rmSync as rmSync8 } from "fs";
36811
+ import { mkdirSync as mkdirSync21, readFileSync as readFileSync27, statSync as statSync13, writeFileSync as writeFileSync14, rmSync as rmSync8 } from "fs";
36531
36812
  import { cpus as cpus3, freemem as freemem3, tmpdir as tmpdir3 } from "os";
36532
- import { resolve as resolve25, dirname as dirname15, join as join37, basename as basename6 } from "path";
36813
+ import { resolve as resolve27, dirname as dirname15, join as join39, basename as basename8 } from "path";
36533
36814
  import { execFileSync as execFileSync5, spawn as spawn10 } from "child_process";
36534
36815
  function defaultWorkerCount() {
36535
36816
  return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT * 3 / 4), 8));
@@ -36538,11 +36819,11 @@ function dockerImageTag(version) {
36538
36819
  return `${DOCKER_IMAGE_PREFIX}:${version}`;
36539
36820
  }
36540
36821
  function resolveDockerfilePath() {
36541
- const builtPath = resolve25(__dirname, "docker", "Dockerfile.render");
36542
- const devPath = resolve25(__dirname, "..", "src", "docker", "Dockerfile.render");
36822
+ const builtPath = resolve27(__dirname, "docker", "Dockerfile.render");
36823
+ const devPath = resolve27(__dirname, "..", "src", "docker", "Dockerfile.render");
36543
36824
  for (const p of [builtPath, devPath]) {
36544
36825
  try {
36545
- statSync12(p);
36826
+ statSync13(p);
36546
36827
  return p;
36547
36828
  } catch {
36548
36829
  continue;
@@ -36566,9 +36847,9 @@ function ensureDockerImage(version, quiet) {
36566
36847
  }
36567
36848
  if (!quiet) console.log(c.dim(` Building Docker image: ${tag}...`));
36568
36849
  const dockerfilePath = resolveDockerfilePath();
36569
- const tmpDir = join37(tmpdir3(), `hyperframes-docker-${Date.now()}`);
36850
+ const tmpDir = join39(tmpdir3(), `hyperframes-docker-${Date.now()}`);
36570
36851
  mkdirSync21(tmpDir, { recursive: true });
36571
- writeFileSync14(join37(tmpDir, "Dockerfile"), readFileSync26(dockerfilePath));
36852
+ writeFileSync14(join39(tmpDir, "Dockerfile"), readFileSync27(dockerfilePath));
36572
36853
  try {
36573
36854
  execFileSync5(
36574
36855
  "docker",
@@ -36613,11 +36894,11 @@ async function renderDocker(projectDir, outputPath, options) {
36613
36894
  process.exit(1);
36614
36895
  }
36615
36896
  const outputDir = dirname15(outputPath);
36616
- const outputFilename = basename6(outputPath);
36897
+ const outputFilename = basename8(outputPath);
36617
36898
  const dockerArgs = buildDockerRunArgs({
36618
36899
  imageTag,
36619
- projectDir: resolve25(projectDir),
36620
- outputDir: resolve25(outputDir),
36900
+ projectDir: resolve27(projectDir),
36901
+ outputDir: resolve27(outputDir),
36621
36902
  outputFilename,
36622
36903
  options: {
36623
36904
  fps: options.fps,
@@ -36736,7 +37017,7 @@ function printRenderComplete(outputPath, elapsedMs, quiet) {
36736
37017
  if (quiet) return;
36737
37018
  let fileSize = "unknown";
36738
37019
  try {
36739
- fileSize = formatBytes(statSync12(outputPath).size);
37020
+ fileSize = formatBytes(statSync13(outputPath).size);
36740
37021
  } catch {
36741
37022
  }
36742
37023
  const duration = formatDuration(elapsedMs);
@@ -36744,7 +37025,7 @@ function printRenderComplete(outputPath, elapsedMs, quiet) {
36744
37025
  console.log(c.success("\u25C7") + " " + c.accent(outputPath));
36745
37026
  console.log(" " + c.bold(fileSize) + c.dim(" \xB7 " + duration + " \xB7 completed"));
36746
37027
  }
36747
- var examples6, VALID_FPS, VALID_QUALITY, VALID_FORMAT, FORMAT_EXT, CPU_CORE_COUNT, render_default, DOCKER_IMAGE_PREFIX;
37028
+ var examples7, VALID_FPS, VALID_QUALITY, VALID_FORMAT, FORMAT_EXT, CPU_CORE_COUNT, render_default, DOCKER_IMAGE_PREFIX;
36748
37029
  var init_render2 = __esm({
36749
37030
  "src/commands/render.ts"() {
36750
37031
  "use strict";
@@ -36761,7 +37042,7 @@ var init_render2 = __esm({
36761
37042
  init_version();
36762
37043
  init_env();
36763
37044
  init_dockerRunArgs();
36764
- examples6 = [
37045
+ examples7 = [
36765
37046
  ["Render to MP4", "hyperframes render --output output.mp4"],
36766
37047
  ["Render transparent overlay (ProRes)", "hyperframes render --format mov --output overlay.mov"],
36767
37048
  ["Render transparent WebM overlay", "hyperframes render --format webm --output overlay.webm"],
@@ -36892,12 +37173,12 @@ var init_render2 = __esm({
36892
37173
  }
36893
37174
  process.env.PRODUCER_MAX_CONCURRENT_RENDERS = String(parsed);
36894
37175
  }
36895
- const rendersDir = resolve25("renders");
37176
+ const rendersDir = resolve27("renders");
36896
37177
  const ext = FORMAT_EXT[format] ?? ".mp4";
36897
37178
  const now = /* @__PURE__ */ new Date();
36898
37179
  const datePart = now.toISOString().slice(0, 10);
36899
37180
  const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
36900
- const outputPath = args.output ? resolve25(args.output) : join37(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
37181
+ const outputPath = args.output ? resolve27(args.output) : join39(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
36901
37182
  mkdirSync21(dirname15(outputPath), { recursive: true });
36902
37183
  const useDocker = args.docker ?? false;
36903
37184
  const useGpu = args.gpu ?? false;
@@ -37126,9 +37407,9 @@ var init_updateCheck = __esm({
37126
37407
  var lint_exports2 = {};
37127
37408
  __export(lint_exports2, {
37128
37409
  default: () => lint_default,
37129
- examples: () => examples7
37410
+ examples: () => examples8
37130
37411
  });
37131
- var examples7, lint_default;
37412
+ var examples8, lint_default;
37132
37413
  var init_lint3 = __esm({
37133
37414
  "src/commands/lint.ts"() {
37134
37415
  "use strict";
@@ -37138,7 +37419,7 @@ var init_lint3 = __esm({
37138
37419
  init_lintProject();
37139
37420
  init_project();
37140
37421
  init_updateCheck();
37141
- examples7 = [
37422
+ examples8 = [
37142
37423
  ["Lint the current project", "hyperframes lint"],
37143
37424
  ["Lint a specific directory", "hyperframes lint ./my-video"],
37144
37425
  ["Output findings as JSON", "hyperframes lint --json"],
@@ -37243,23 +37524,23 @@ var init_dom = __esm({
37243
37524
  var info_exports = {};
37244
37525
  __export(info_exports, {
37245
37526
  default: () => info_default,
37246
- examples: () => examples8
37527
+ examples: () => examples9
37247
37528
  });
37248
- import { readFileSync as readFileSync27, readdirSync as readdirSync13, statSync as statSync13 } from "fs";
37249
- import { join as join38 } from "path";
37529
+ import { readFileSync as readFileSync28, readdirSync as readdirSync14, statSync as statSync14 } from "fs";
37530
+ import { join as join40 } from "path";
37250
37531
  function totalSize(dir) {
37251
37532
  let total = 0;
37252
- for (const entry of readdirSync13(dir, { withFileTypes: true })) {
37253
- const path2 = join38(dir, entry.name);
37533
+ for (const entry of readdirSync14(dir, { withFileTypes: true })) {
37534
+ const path2 = join40(dir, entry.name);
37254
37535
  if (entry.isDirectory()) {
37255
37536
  total += totalSize(path2);
37256
37537
  } else {
37257
- total += statSync13(path2).size;
37538
+ total += statSync14(path2).size;
37258
37539
  }
37259
37540
  }
37260
37541
  return total;
37261
37542
  }
37262
- var examples8, info_default;
37543
+ var examples9, info_default;
37263
37544
  var init_info = __esm({
37264
37545
  "src/commands/info.ts"() {
37265
37546
  "use strict";
@@ -37270,7 +37551,7 @@ var init_info = __esm({
37270
37551
  init_dom();
37271
37552
  init_project();
37272
37553
  init_updateCheck();
37273
- examples8 = [
37554
+ examples9 = [
37274
37555
  ["Show project metadata", "hyperframes info"],
37275
37556
  ["Output as JSON", "hyperframes info --json"]
37276
37557
  ];
@@ -37282,7 +37563,7 @@ var init_info = __esm({
37282
37563
  },
37283
37564
  async run({ args }) {
37284
37565
  const project = resolveProject(args.dir);
37285
- const html = readFileSync27(project.indexPath, "utf-8");
37566
+ const html = readFileSync28(project.indexPath, "utf-8");
37286
37567
  ensureDOMParser();
37287
37568
  const parsed = parseHtml(html);
37288
37569
  const tracks = new Set(parsed.elements.map((el) => el.zIndex));
@@ -37336,10 +37617,10 @@ var init_info = __esm({
37336
37617
  var compositions_exports = {};
37337
37618
  __export(compositions_exports, {
37338
37619
  default: () => compositions_default,
37339
- examples: () => examples9
37620
+ examples: () => examples10
37340
37621
  });
37341
- import { existsSync as existsSync37, readFileSync as readFileSync28 } from "fs";
37342
- import { resolve as resolve26, dirname as dirname16 } from "path";
37622
+ import { existsSync as existsSync38, readFileSync as readFileSync29 } from "fs";
37623
+ import { resolve as resolve28, dirname as dirname16 } from "path";
37343
37624
  function parseCompositions(html, baseDir) {
37344
37625
  const parser = new DOMParser();
37345
37626
  const doc = parser.parseFromString(html, "text/html");
@@ -37351,9 +37632,9 @@ function parseCompositions(html, baseDir) {
37351
37632
  const height = parseInt(div.getAttribute("data-height") ?? "1080", 10);
37352
37633
  const compositionSrc = div.getAttribute("data-composition-src");
37353
37634
  if (compositionSrc) {
37354
- const subPath = resolve26(baseDir, compositionSrc);
37355
- if (existsSync37(subPath)) {
37356
- const subHtml = readFileSync28(subPath, "utf-8");
37635
+ const subPath = resolve28(baseDir, compositionSrc);
37636
+ if (existsSync38(subPath)) {
37637
+ const subHtml = readFileSync29(subPath, "utf-8");
37357
37638
  const subInfo = parseSubComposition(subHtml, id, width, height);
37358
37639
  compositions.push({ ...subInfo, source: compositionSrc });
37359
37640
  return;
@@ -37426,7 +37707,7 @@ function parseSubComposition(html, fallbackId, fallbackWidth, fallbackHeight) {
37426
37707
  }
37427
37708
  return { id, duration, width, height, elementCount };
37428
37709
  }
37429
- var examples9, compositions_default;
37710
+ var examples10, compositions_default;
37430
37711
  var init_compositions = __esm({
37431
37712
  "src/commands/compositions.ts"() {
37432
37713
  "use strict";
@@ -37435,7 +37716,7 @@ var init_compositions = __esm({
37435
37716
  init_dom();
37436
37717
  init_project();
37437
37718
  init_updateCheck();
37438
- examples9 = [
37719
+ examples10 = [
37439
37720
  ["List compositions in the current project", "hyperframes compositions"],
37440
37721
  ["Output as JSON", "hyperframes compositions --json"]
37441
37722
  ];
@@ -37447,7 +37728,7 @@ var init_compositions = __esm({
37447
37728
  },
37448
37729
  async run({ args }) {
37449
37730
  const project = resolveProject(args.dir);
37450
- const html = readFileSync28(project.indexPath, "utf-8");
37731
+ const html = readFileSync29(project.indexPath, "utf-8");
37451
37732
  ensureDOMParser();
37452
37733
  const compositions = parseCompositions(html, dirname16(project.indexPath));
37453
37734
  if (compositions.length === 0) {
@@ -37483,11 +37764,11 @@ var init_compositions = __esm({
37483
37764
  var benchmark_exports = {};
37484
37765
  __export(benchmark_exports, {
37485
37766
  default: () => benchmark_default,
37486
- examples: () => examples10
37767
+ examples: () => examples11
37487
37768
  });
37488
- import { existsSync as existsSync38, statSync as statSync14 } from "fs";
37489
- import { resolve as resolve27, join as join39 } from "path";
37490
- var examples10, DEFAULT_CONFIGS, benchmark_default;
37769
+ import { existsSync as existsSync39, statSync as statSync15 } from "fs";
37770
+ import { resolve as resolve29, join as join41 } from "path";
37771
+ var examples11, DEFAULT_CONFIGS, benchmark_default;
37491
37772
  var init_benchmark = __esm({
37492
37773
  "src/commands/benchmark.ts"() {
37493
37774
  "use strict";
@@ -37498,7 +37779,7 @@ var init_benchmark = __esm({
37498
37779
  init_format();
37499
37780
  init_dist3();
37500
37781
  init_updateCheck();
37501
- examples10 = [
37782
+ examples11 = [
37502
37783
  ["Run benchmarks with default settings (3 runs)", "hyperframes benchmark"],
37503
37784
  ["Run 5 iterations per config", "hyperframes benchmark --runs 5"],
37504
37785
  ["Output results as JSON", "hyperframes benchmark --json"]
@@ -37528,7 +37809,7 @@ var init_benchmark = __esm({
37528
37809
  process.exit(1);
37529
37810
  }
37530
37811
  const jsonOutput = args.json ?? false;
37531
- const benchDir = resolve27("renders", ".benchmark");
37812
+ const benchDir = resolve29("renders", ".benchmark");
37532
37813
  let producer = null;
37533
37814
  try {
37534
37815
  producer = await loadProducer();
@@ -37561,7 +37842,7 @@ var init_benchmark = __esm({
37561
37842
  s2?.start(`Benchmarking ${config.label}...`);
37562
37843
  for (let i2 = 0; i2 < runsPerConfig; i2++) {
37563
37844
  s2?.message(`${config.label} \u2014 run ${i2 + 1}/${runsPerConfig}`);
37564
- const outputPath = join39(
37845
+ const outputPath = join41(
37565
37846
  benchDir,
37566
37847
  `${config.label.replace(/[^a-zA-Z0-9]/g, "_")}_run${i2}.mp4`
37567
37848
  );
@@ -37575,8 +37856,8 @@ var init_benchmark = __esm({
37575
37856
  await producer.executeRenderJob(job, project.dir, outputPath);
37576
37857
  const elapsedMs = Date.now() - startTime;
37577
37858
  let fileSize = null;
37578
- if (existsSync38(outputPath)) {
37579
- const stat3 = statSync14(outputPath);
37859
+ if (existsSync39(outputPath)) {
37860
+ const stat3 = statSync15(outputPath);
37580
37861
  fileSize = stat3.size;
37581
37862
  }
37582
37863
  runs.push({ elapsedMs, fileSize });
@@ -37658,7 +37939,7 @@ var init_benchmark = __esm({
37658
37939
  var browser_exports = {};
37659
37940
  __export(browser_exports, {
37660
37941
  default: () => browser_default,
37661
- examples: () => examples11
37942
+ examples: () => examples12
37662
37943
  });
37663
37944
  async function runEnsure() {
37664
37945
  Wt2(c.bold("hyperframes browser ensure"));
@@ -37721,7 +38002,7 @@ function runClear() {
37721
38002
  Gt(c.dim("No cached browser to remove."));
37722
38003
  }
37723
38004
  }
37724
- var examples11, browser_default;
38005
+ var examples12, browser_default;
37725
38006
  var init_browser = __esm({
37726
38007
  "src/commands/browser.ts"() {
37727
38008
  "use strict";
@@ -37731,7 +38012,7 @@ var init_browser = __esm({
37731
38012
  init_format();
37732
38013
  init_manager2();
37733
38014
  init_events();
37734
- examples11 = [
38015
+ examples12 = [
37735
38016
  ["Find or download Chrome for rendering", "hyperframes browser ensure"],
37736
38017
  ["Print the Chrome executable path", "hyperframes browser path"],
37737
38018
  ["Remove cached Chrome download", "hyperframes browser clear"]
@@ -37789,10 +38070,10 @@ Run ${c.accent("hyperframes browser --help")} for usage.`
37789
38070
  var transcribe_exports2 = {};
37790
38071
  __export(transcribe_exports2, {
37791
38072
  default: () => transcribe_default,
37792
- examples: () => examples12
38073
+ examples: () => examples13
37793
38074
  });
37794
- import { existsSync as existsSync39, writeFileSync as writeFileSync15 } from "fs";
37795
- import { resolve as resolve28, join as join40, extname as extname8 } from "path";
38075
+ import { existsSync as existsSync40, writeFileSync as writeFileSync15 } from "fs";
38076
+ import { resolve as resolve30, join as join42, extname as extname8 } from "path";
37796
38077
  async function importTranscript(inputPath, dir, json) {
37797
38078
  const { loadTranscript: loadTranscript2, patchCaptionHtml: patchCaptionHtml2 } = await Promise.resolve().then(() => (init_normalize(), normalize_exports));
37798
38079
  const { words, format } = loadTranscript2(inputPath);
@@ -37800,7 +38081,7 @@ async function importTranscript(inputPath, dir, json) {
37800
38081
  console.error(c.error("No words found in transcript."));
37801
38082
  process.exit(1);
37802
38083
  }
37803
- const outPath = join40(dir, "transcript.json");
38084
+ const outPath = join42(dir, "transcript.json");
37804
38085
  writeFileSync15(outPath, JSON.stringify(words, null, 2));
37805
38086
  patchCaptionHtml2(dir, words);
37806
38087
  if (json) {
@@ -37867,7 +38148,7 @@ async function transcribeAudio(inputPath, dir, opts) {
37867
38148
  process.exit(1);
37868
38149
  }
37869
38150
  }
37870
- var examples12, transcribe_default;
38151
+ var examples13, transcribe_default;
37871
38152
  var init_transcribe2 = __esm({
37872
38153
  "src/commands/transcribe.ts"() {
37873
38154
  "use strict";
@@ -37875,7 +38156,7 @@ var init_transcribe2 = __esm({
37875
38156
  init_dist3();
37876
38157
  init_colors();
37877
38158
  init_manager();
37878
- examples12 = [
38159
+ examples13 = [
37879
38160
  ["Transcribe an audio file", "hyperframes transcribe audio.mp3"],
37880
38161
  ["Transcribe a video file", "hyperframes transcribe video.mp4"],
37881
38162
  ["Use a larger model for better accuracy", "hyperframes transcribe audio.mp3 --model medium.en"],
@@ -37916,12 +38197,12 @@ var init_transcribe2 = __esm({
37916
38197
  }
37917
38198
  },
37918
38199
  async run({ args }) {
37919
- const inputPath = resolve28(args.input);
37920
- if (!existsSync39(inputPath)) {
38200
+ const inputPath = resolve30(args.input);
38201
+ if (!existsSync40(inputPath)) {
37921
38202
  console.error(c.error(`File not found: ${args.input}`));
37922
38203
  process.exit(1);
37923
38204
  }
37924
- const dir = resolve28(args.dir ?? ".");
38205
+ const dir = resolve30(args.dir ?? ".");
37925
38206
  const ext = extname8(inputPath).toLowerCase();
37926
38207
  const isImport = ext === ".json" || ext === ".srt" || ext === ".vtt";
37927
38208
  if (isImport) {
@@ -37938,9 +38219,9 @@ var init_transcribe2 = __esm({
37938
38219
  });
37939
38220
 
37940
38221
  // src/tts/manager.ts
37941
- import { existsSync as existsSync40, mkdirSync as mkdirSync22 } from "fs";
38222
+ import { existsSync as existsSync41, mkdirSync as mkdirSync22 } from "fs";
37942
38223
  import { homedir as homedir8 } from "os";
37943
- import { join as join41 } from "path";
38224
+ import { join as join43 } from "path";
37944
38225
  function inferLangFromVoiceId(voiceId) {
37945
38226
  const first = voiceId.charAt(0).toLowerCase();
37946
38227
  return VOICE_PREFIX_LANG[first] ?? "en-us";
@@ -37949,8 +38230,8 @@ function isSupportedLang(value) {
37949
38230
  return SUPPORTED_LANGS.includes(value);
37950
38231
  }
37951
38232
  async function ensureModel2(model = DEFAULT_MODEL2, options) {
37952
- const modelPath = join41(MODELS_DIR2, `${model}.onnx`);
37953
- if (existsSync40(modelPath)) return modelPath;
38233
+ const modelPath = join43(MODELS_DIR2, `${model}.onnx`);
38234
+ if (existsSync41(modelPath)) return modelPath;
37954
38235
  const url = MODEL_URLS[model];
37955
38236
  if (!url) {
37956
38237
  throw new Error(
@@ -37960,18 +38241,18 @@ async function ensureModel2(model = DEFAULT_MODEL2, options) {
37960
38241
  mkdirSync22(MODELS_DIR2, { recursive: true });
37961
38242
  options?.onProgress?.(`Downloading TTS model ${model} (~311 MB)...`);
37962
38243
  await downloadFile(url, modelPath);
37963
- if (!existsSync40(modelPath)) {
38244
+ if (!existsSync41(modelPath)) {
37964
38245
  throw new Error(`Model download failed: ${model}`);
37965
38246
  }
37966
38247
  return modelPath;
37967
38248
  }
37968
38249
  async function ensureVoices(options) {
37969
- const voicesPath = join41(VOICES_DIR, "voices-v1.0.bin");
37970
- if (existsSync40(voicesPath)) return voicesPath;
38250
+ const voicesPath = join43(VOICES_DIR, "voices-v1.0.bin");
38251
+ if (existsSync41(voicesPath)) return voicesPath;
37971
38252
  mkdirSync22(VOICES_DIR, { recursive: true });
37972
38253
  options?.onProgress?.("Downloading voice data (~27 MB)...");
37973
38254
  await downloadFile(VOICES_URL, voicesPath);
37974
- if (!existsSync40(voicesPath)) {
38255
+ if (!existsSync41(voicesPath)) {
37975
38256
  throw new Error("Voice data download failed");
37976
38257
  }
37977
38258
  return voicesPath;
@@ -37981,9 +38262,9 @@ var init_manager3 = __esm({
37981
38262
  "src/tts/manager.ts"() {
37982
38263
  "use strict";
37983
38264
  init_download();
37984
- CACHE_DIR3 = join41(homedir8(), ".cache", "hyperframes", "tts");
37985
- MODELS_DIR2 = join41(CACHE_DIR3, "models");
37986
- VOICES_DIR = join41(CACHE_DIR3, "voices");
38265
+ CACHE_DIR3 = join43(homedir8(), ".cache", "hyperframes", "tts");
38266
+ MODELS_DIR2 = join43(CACHE_DIR3, "models");
38267
+ VOICES_DIR = join43(CACHE_DIR3, "voices");
37987
38268
  DEFAULT_MODEL2 = "kokoro-v1.0";
37988
38269
  MODEL_URLS = {
37989
38270
  "kokoro-v1.0": "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx"
@@ -38044,8 +38325,8 @@ __export(synthesize_exports, {
38044
38325
  synthesize: () => synthesize
38045
38326
  });
38046
38327
  import { execFileSync as execFileSync6 } from "child_process";
38047
- import { existsSync as existsSync41, writeFileSync as writeFileSync16, mkdirSync as mkdirSync23, readdirSync as readdirSync14, unlinkSync as unlinkSync6 } from "fs";
38048
- import { join as join42, dirname as dirname17, basename as basename7 } from "path";
38328
+ import { existsSync as existsSync42, writeFileSync as writeFileSync16, mkdirSync as mkdirSync23, readdirSync as readdirSync15, unlinkSync as unlinkSync6 } from "fs";
38329
+ import { join as join44, dirname as dirname17, basename as basename9 } from "path";
38049
38330
  import { homedir as homedir9 } from "os";
38050
38331
  function findPython() {
38051
38332
  for (const name of ["python3", "python"]) {
@@ -38081,15 +38362,15 @@ function hasPythonPackage(python, pkg) {
38081
38362
  }
38082
38363
  }
38083
38364
  function ensureSynthScript() {
38084
- if (!existsSync41(SCRIPT_PATH)) {
38365
+ if (!existsSync42(SCRIPT_PATH)) {
38085
38366
  mkdirSync23(SCRIPT_DIR, { recursive: true });
38086
38367
  writeFileSync16(SCRIPT_PATH, SYNTH_SCRIPT);
38087
- const currentName = basename7(SCRIPT_PATH);
38368
+ const currentName = basename9(SCRIPT_PATH);
38088
38369
  try {
38089
- for (const entry of readdirSync14(SCRIPT_DIR)) {
38370
+ for (const entry of readdirSync15(SCRIPT_DIR)) {
38090
38371
  if (entry !== currentName && /^synth(-v\d+)?\.py$/.test(entry)) {
38091
38372
  try {
38092
- unlinkSync6(join42(SCRIPT_DIR, entry));
38373
+ unlinkSync6(join44(SCRIPT_DIR, entry));
38093
38374
  } catch {
38094
38375
  }
38095
38376
  }
@@ -38135,7 +38416,7 @@ async function synthesize(text, outputPath, options) {
38135
38416
  stdio: ["pipe", "pipe", "pipe"]
38136
38417
  }
38137
38418
  );
38138
- if (!existsSync41(outputPath)) {
38419
+ if (!existsSync42(outputPath)) {
38139
38420
  throw new Error("Synthesis completed but no output file was created");
38140
38421
  }
38141
38422
  const lines = stdout2.trim().split("\n");
@@ -38148,7 +38429,7 @@ async function synthesize(text, outputPath, options) {
38148
38429
  langApplied: result.langApplied
38149
38430
  };
38150
38431
  } catch (err) {
38151
- if (err instanceof SyntaxError && existsSync41(outputPath)) {
38432
+ if (err instanceof SyntaxError && existsSync42(outputPath)) {
38152
38433
  throw new Error(
38153
38434
  "Speech was generated but metadata could not be read. Check the output file manually."
38154
38435
  );
@@ -38199,8 +38480,8 @@ print(json.dumps({
38199
38480
  "langApplied": bool(lang and supports_lang),
38200
38481
  }))
38201
38482
  `;
38202
- SCRIPT_DIR = join42(homedir9(), ".cache", "hyperframes", "tts");
38203
- SCRIPT_PATH = join42(SCRIPT_DIR, "synth-v2.py");
38483
+ SCRIPT_DIR = join44(homedir9(), ".cache", "hyperframes", "tts");
38484
+ SCRIPT_PATH = join44(SCRIPT_DIR, "synth-v2.py");
38204
38485
  }
38205
38486
  });
38206
38487
 
@@ -38208,10 +38489,10 @@ print(json.dumps({
38208
38489
  var tts_exports = {};
38209
38490
  __export(tts_exports, {
38210
38491
  default: () => tts_default,
38211
- examples: () => examples13
38492
+ examples: () => examples14
38212
38493
  });
38213
- import { existsSync as existsSync42, readFileSync as readFileSync29 } from "fs";
38214
- import { resolve as resolve29, extname as extname9 } from "path";
38494
+ import { existsSync as existsSync43, readFileSync as readFileSync30 } from "fs";
38495
+ import { resolve as resolve31, extname as extname9 } from "path";
38215
38496
  function listVoices(json) {
38216
38497
  const rows = BUNDLED_VOICES.map((v) => ({ ...v, defaultLang: inferLangFromVoiceId(v.id) }));
38217
38498
  if (json) {
@@ -38241,7 +38522,7 @@ ${c.bold("Available voices")} (Kokoro-82M)
38241
38522
  `
38242
38523
  );
38243
38524
  }
38244
- var examples13, voiceList, langList, tts_default;
38525
+ var examples14, voiceList, langList, tts_default;
38245
38526
  var init_tts = __esm({
38246
38527
  "src/commands/tts.ts"() {
38247
38528
  "use strict";
@@ -38250,7 +38531,7 @@ var init_tts = __esm({
38250
38531
  init_colors();
38251
38532
  init_format();
38252
38533
  init_manager3();
38253
- examples13 = [
38534
+ examples14 = [
38254
38535
  ["Generate speech from text", 'hyperframes tts "Welcome to HyperFrames"'],
38255
38536
  ["Choose a voice", 'hyperframes tts "Hello world" --voice am_adam'],
38256
38537
  ["Save to a specific file", 'hyperframes tts "Intro" --voice bf_emma --output narration.wav'],
@@ -38319,9 +38600,9 @@ var init_tts = __esm({
38319
38600
  process.exit(1);
38320
38601
  }
38321
38602
  let text;
38322
- const maybeFile = resolve29(args.input);
38323
- if (existsSync42(maybeFile) && extname9(maybeFile).toLowerCase() === ".txt") {
38324
- text = readFileSync29(maybeFile, "utf-8").trim();
38603
+ const maybeFile = resolve31(args.input);
38604
+ if (existsSync43(maybeFile) && extname9(maybeFile).toLowerCase() === ".txt") {
38605
+ text = readFileSync30(maybeFile, "utf-8").trim();
38325
38606
  if (!text) {
38326
38607
  console.error(c.error("File is empty."));
38327
38608
  process.exit(1);
@@ -38333,7 +38614,7 @@ var init_tts = __esm({
38333
38614
  console.error(c.error("No text provided."));
38334
38615
  process.exit(1);
38335
38616
  }
38336
- const output = resolve29(args.output ?? "speech.wav");
38617
+ const output = resolve31(args.output ?? "speech.wav");
38337
38618
  const voice = args.voice ?? DEFAULT_VOICE;
38338
38619
  const speed = args.speed ? parseFloat(args.speed) : 1;
38339
38620
  if (isNaN(speed) || speed <= 0 || speed > 3) {
@@ -38411,17 +38692,17 @@ var init_tts = __esm({
38411
38692
  var docs_exports = {};
38412
38693
  __export(docs_exports, {
38413
38694
  default: () => docs_default,
38414
- examples: () => examples14
38695
+ examples: () => examples15
38415
38696
  });
38416
- import { readFileSync as readFileSync30, existsSync as existsSync43 } from "fs";
38417
- import { resolve as resolve30, dirname as dirname18, join as join43 } from "path";
38697
+ import { readFileSync as readFileSync31, existsSync as existsSync44 } from "fs";
38698
+ import { resolve as resolve32, dirname as dirname18, join as join45 } from "path";
38418
38699
  import { fileURLToPath as fileURLToPath6 } from "url";
38419
38700
  function docsDir() {
38420
38701
  const thisFile = fileURLToPath6(import.meta.url);
38421
38702
  const dir = dirname18(thisFile);
38422
- const devPath = resolve30(dir, "..", "docs");
38423
- const builtPath = resolve30(dir, "docs");
38424
- return existsSync43(devPath) ? devPath : builtPath;
38703
+ const devPath = resolve32(dir, "..", "docs");
38704
+ const builtPath = resolve32(dir, "docs");
38705
+ return existsSync44(devPath) ? devPath : builtPath;
38425
38706
  }
38426
38707
  function formatInlineCode(line) {
38427
38708
  return line.replace(/`([^`]+)`/g, (_match, code) => c.accent(code));
@@ -38448,13 +38729,13 @@ function renderMarkdown(content) {
38448
38729
  console.log(formatInlineCode(line));
38449
38730
  }
38450
38731
  }
38451
- var examples14, TOPICS, TOPIC_NAMES, docs_default;
38732
+ var examples15, TOPICS, TOPIC_NAMES, docs_default;
38452
38733
  var init_docs = __esm({
38453
38734
  "src/commands/docs.ts"() {
38454
38735
  "use strict";
38455
38736
  init_dist();
38456
38737
  init_colors();
38457
- examples14 = [
38738
+ examples15 = [
38458
38739
  ["List all available topics", "hyperframes docs"],
38459
38740
  ["Read about data attributes", "hyperframes docs data-attributes"],
38460
38741
  ["Read about rendering", "hyperframes docs rendering"],
@@ -38518,12 +38799,12 @@ var init_docs = __esm({
38518
38799
  }
38519
38800
  process.exit(1);
38520
38801
  }
38521
- const filePath = join43(docsDir(), entry.file);
38522
- if (!existsSync43(filePath)) {
38802
+ const filePath = join45(docsDir(), entry.file);
38803
+ if (!existsSync44(filePath)) {
38523
38804
  console.error(c.error(`Doc file not found: ${filePath}`));
38524
38805
  process.exit(1);
38525
38806
  }
38526
- const content = readFileSync30(filePath, "utf-8");
38807
+ const content = readFileSync31(filePath, "utf-8");
38527
38808
  console.log();
38528
38809
  renderMarkdown(content);
38529
38810
  }
@@ -38535,7 +38816,7 @@ var init_docs = __esm({
38535
38816
  var doctor_exports = {};
38536
38817
  __export(doctor_exports, {
38537
38818
  default: () => doctor_default,
38538
- examples: () => examples15
38819
+ examples: () => examples16
38539
38820
  });
38540
38821
  import { execSync as execSync3 } from "child_process";
38541
38822
  import { freemem as freemem4, platform as platform4 } from "os";
@@ -38677,7 +38958,7 @@ function checkEnvironment() {
38677
38958
  }
38678
38959
  return { ok: true, detail: parts.join(" \xB7 ") };
38679
38960
  }
38680
- var examples15, doctor_default;
38961
+ var examples16, doctor_default;
38681
38962
  var init_doctor = __esm({
38682
38963
  "src/commands/doctor.ts"() {
38683
38964
  "use strict";
@@ -38688,7 +38969,7 @@ var init_doctor = __esm({
38688
38969
  init_version();
38689
38970
  init_updateCheck();
38690
38971
  init_system();
38691
- examples15 = [["Check system dependencies", "hyperframes doctor"]];
38972
+ examples16 = [["Check system dependencies", "hyperframes doctor"]];
38692
38973
  doctor_default = defineCommand({
38693
38974
  meta: { name: "doctor", description: "Check system dependencies and environment" },
38694
38975
  args: {},
@@ -38743,10 +39024,10 @@ var init_doctor = __esm({
38743
39024
  var upgrade_exports = {};
38744
39025
  __export(upgrade_exports, {
38745
39026
  default: () => upgrade_default,
38746
- examples: () => examples16
39027
+ examples: () => examples17
38747
39028
  });
38748
39029
  import { execSync as execSync4 } from "child_process";
38749
- var examples16, upgrade_default;
39030
+ var examples17, upgrade_default;
38750
39031
  var init_upgrade = __esm({
38751
39032
  "src/commands/upgrade.ts"() {
38752
39033
  "use strict";
@@ -38755,7 +39036,7 @@ var init_upgrade = __esm({
38755
39036
  init_colors();
38756
39037
  init_version();
38757
39038
  init_updateCheck();
38758
- examples16 = [
39039
+ examples17 = [
38759
39040
  ["Check for updates interactively", "hyperframes upgrade"],
38760
39041
  ["Check for updates without prompting", "hyperframes upgrade --check"],
38761
39042
  ["Upgrade non-interactively", "hyperframes upgrade --yes"]
@@ -38833,7 +39114,7 @@ var init_upgrade = __esm({
38833
39114
  var telemetry_exports = {};
38834
39115
  __export(telemetry_exports, {
38835
39116
  default: () => telemetry_default,
38836
- examples: () => examples17
39117
+ examples: () => examples18
38837
39118
  });
38838
39119
  function runEnable() {
38839
39120
  const config = readConfig();
@@ -38863,14 +39144,14 @@ function runStatus() {
38863
39144
  console.log(` ${c.dim("Env var:")} ${c.accent("HYPERFRAMES_NO_TELEMETRY=1")}`);
38864
39145
  console.log();
38865
39146
  }
38866
- var examples17, telemetry_default;
39147
+ var examples18, telemetry_default;
38867
39148
  var init_telemetry = __esm({
38868
39149
  "src/commands/telemetry.ts"() {
38869
39150
  "use strict";
38870
39151
  init_dist();
38871
39152
  init_colors();
38872
39153
  init_config();
38873
- examples17 = [
39154
+ examples18 = [
38874
39155
  ["Check current telemetry status", "hyperframes telemetry status"],
38875
39156
  ["Disable telemetry", "hyperframes telemetry disable"],
38876
39157
  ["Enable telemetry", "hyperframes telemetry enable"]
@@ -38949,8 +39230,8 @@ var validate_exports = {};
38949
39230
  __export(validate_exports, {
38950
39231
  default: () => validate_default
38951
39232
  });
38952
- import { existsSync as existsSync44, readFileSync as readFileSync31 } from "fs";
38953
- import { resolve as resolve31, join as join44, dirname as dirname19 } from "path";
39233
+ import { existsSync as existsSync45, readFileSync as readFileSync32 } from "fs";
39234
+ import { resolve as resolve33, join as join46, dirname as dirname19 } from "path";
38954
39235
  import { fileURLToPath as fileURLToPath7 } from "url";
38955
39236
  async function getCompositionDuration2(page) {
38956
39237
  return page.evaluate(() => {
@@ -38996,7 +39277,7 @@ async function validateInBrowser(projectDir, opts) {
38996
39277
  const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
38997
39278
  const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
38998
39279
  let html = await bundleToSingleHtml2(projectDir);
38999
- const runtimePath = resolve31(
39280
+ const runtimePath = resolve33(
39000
39281
  __dirname2,
39001
39282
  "..",
39002
39283
  "..",
@@ -39005,8 +39286,8 @@ async function validateInBrowser(projectDir, opts) {
39005
39286
  "dist",
39006
39287
  "hyperframe.runtime.iife.js"
39007
39288
  );
39008
- if (existsSync44(runtimePath)) {
39009
- const runtimeSource = readFileSync31(runtimePath, "utf-8");
39289
+ if (existsSync45(runtimePath)) {
39290
+ const runtimeSource = readFileSync32(runtimePath, "utf-8");
39010
39291
  html = html.replace(
39011
39292
  /<script[^>]*data-hyperframes-preview-runtime[^>]*src="[^"]*"[^>]*><\/script>/,
39012
39293
  () => `<script data-hyperframes-preview-runtime="1">${runtimeSource}</script>`
@@ -39021,10 +39302,10 @@ async function validateInBrowser(projectDir, opts) {
39021
39302
  res.end(html);
39022
39303
  return;
39023
39304
  }
39024
- const filePath = join44(projectDir, decodeURIComponent(url));
39025
- if (existsSync44(filePath)) {
39305
+ const filePath = join46(projectDir, decodeURIComponent(url));
39306
+ if (existsSync45(filePath)) {
39026
39307
  res.writeHead(200, { "Content-Type": getMimeType2(filePath) });
39027
- res.end(readFileSync31(filePath));
39308
+ res.end(readFileSync32(filePath));
39028
39309
  return;
39029
39310
  }
39030
39311
  res.writeHead(404);
@@ -39212,16 +39493,16 @@ Examples:
39212
39493
  var snapshot_exports = {};
39213
39494
  __export(snapshot_exports, {
39214
39495
  default: () => snapshot_default,
39215
- examples: () => examples18
39496
+ examples: () => examples19
39216
39497
  });
39217
39498
  import { spawn as spawn11 } from "child_process";
39218
- import { existsSync as existsSync45, mkdtempSync as mkdtempSync2, readFileSync as readFileSync32, mkdirSync as mkdirSync24, rmSync as rmSync9 } from "fs";
39499
+ import { existsSync as existsSync46, mkdtempSync as mkdtempSync2, readFileSync as readFileSync33, mkdirSync as mkdirSync24, rmSync as rmSync9 } from "fs";
39219
39500
  import { tmpdir as tmpdir4 } from "os";
39220
- import { resolve as resolve32, join as join45, dirname as dirname20, relative as relative4, isAbsolute as isAbsolute4 } from "path";
39501
+ import { resolve as resolve34, join as join47, dirname as dirname20, relative as relative5, isAbsolute as isAbsolute4 } from "path";
39221
39502
  import { fileURLToPath as fileURLToPath8 } from "url";
39222
39503
  async function extractVideoFrameToBuffer(videoPath, timeSeconds) {
39223
- const tmp = mkdtempSync2(join45(tmpdir4(), "hf-snapshot-frame-"));
39224
- const outPath = join45(tmp, "frame.png");
39504
+ const tmp = mkdtempSync2(join47(tmpdir4(), "hf-snapshot-frame-"));
39505
+ const outPath = join47(tmp, "frame.png");
39225
39506
  try {
39226
39507
  const result = await new Promise(
39227
39508
  (resolvePromise) => {
@@ -39259,8 +39540,8 @@ async function extractVideoFrameToBuffer(videoPath, timeSeconds) {
39259
39540
  });
39260
39541
  }
39261
39542
  );
39262
- if (result.code !== 0 || result.timedOut || !existsSync45(outPath)) return null;
39263
- return readFileSync32(outPath);
39543
+ if (result.code !== 0 || result.timedOut || !existsSync46(outPath)) return null;
39544
+ return readFileSync33(outPath);
39264
39545
  } finally {
39265
39546
  try {
39266
39547
  rmSync9(tmp, { recursive: true, force: true });
@@ -39273,7 +39554,7 @@ async function captureSnapshots(projectDir, opts) {
39273
39554
  const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
39274
39555
  const numFrames = opts.frames ?? 5;
39275
39556
  let html = await bundleToSingleHtml2(projectDir);
39276
- const runtimePath = resolve32(
39557
+ const runtimePath = resolve34(
39277
39558
  __dirname3,
39278
39559
  "..",
39279
39560
  "..",
@@ -39282,8 +39563,8 @@ async function captureSnapshots(projectDir, opts) {
39282
39563
  "dist",
39283
39564
  "hyperframe.runtime.iife.js"
39284
39565
  );
39285
- if (existsSync45(runtimePath)) {
39286
- const runtimeSource = readFileSync32(runtimePath, "utf-8");
39566
+ if (existsSync46(runtimePath)) {
39567
+ const runtimeSource = readFileSync33(runtimePath, "utf-8");
39287
39568
  html = html.replace(
39288
39569
  /<script[^>]*data-hyperframes-preview-runtime[^>]*src="[^"]*"[^>]*><\/script>/,
39289
39570
  () => `<script data-hyperframes-preview-runtime="1">${runtimeSource}</script>`
@@ -39298,16 +39579,16 @@ async function captureSnapshots(projectDir, opts) {
39298
39579
  res.end(html);
39299
39580
  return;
39300
39581
  }
39301
- const filePath = resolve32(projectDir, decodeURIComponent(url).replace(/^\//, ""));
39302
- const rel = relative4(projectDir, filePath);
39582
+ const filePath = resolve34(projectDir, decodeURIComponent(url).replace(/^\//, ""));
39583
+ const rel = relative5(projectDir, filePath);
39303
39584
  if (rel.startsWith("..") || isAbsolute4(rel)) {
39304
39585
  res.writeHead(403);
39305
39586
  res.end();
39306
39587
  return;
39307
39588
  }
39308
- if (existsSync45(filePath)) {
39589
+ if (existsSync46(filePath)) {
39309
39590
  res.writeHead(200, { "Content-Type": getMimeType2(filePath) });
39310
- res.end(readFileSync32(filePath));
39591
+ res.end(readFileSync33(filePath));
39311
39592
  return;
39312
39593
  }
39313
39594
  res.writeHead(404);
@@ -39380,7 +39661,7 @@ async function captureSnapshots(projectDir, opts) {
39380
39661
  return [];
39381
39662
  }
39382
39663
  const positions = opts.at?.length ? opts.at : numFrames === 1 ? [duration / 2] : Array.from({ length: numFrames }, (_2, i2) => i2 / (numFrames - 1) * duration);
39383
- const snapshotDir = join45(projectDir, "snapshots");
39664
+ const snapshotDir = join47(projectDir, "snapshots");
39384
39665
  mkdirSync24(snapshotDir, { recursive: true });
39385
39666
  let injectVideoFramesBatch2 = null;
39386
39667
  let syncVideoFrameVisibility2 = null;
@@ -39439,9 +39720,9 @@ async function captureSnapshots(projectDir, opts) {
39439
39720
  try {
39440
39721
  const url = new URL(v.src);
39441
39722
  const decodedPath = decodeURIComponent(url.pathname).replace(/^\//, "");
39442
- const candidate = resolve32(projectDir, decodedPath);
39443
- const rel = relative4(projectDir, candidate);
39444
- if (!rel.startsWith("..") && !isAbsolute4(rel) && existsSync45(candidate)) {
39723
+ const candidate = resolve34(projectDir, decodedPath);
39724
+ const rel = relative5(projectDir, candidate);
39725
+ if (!rel.startsWith("..") && !isAbsolute4(rel) && existsSync46(candidate)) {
39445
39726
  filePath = candidate;
39446
39727
  }
39447
39728
  } catch {
@@ -39467,7 +39748,7 @@ async function captureSnapshots(projectDir, opts) {
39467
39748
  }
39468
39749
  const timeLabel = opts.at?.length ? `${time.toFixed(1)}s` : `${Math.round(time / duration * 100)}pct`;
39469
39750
  const filename = `frame-${String(i2).padStart(2, "0")}-at-${timeLabel}.png`;
39470
- const framePath = join45(snapshotDir, filename);
39751
+ const framePath = join47(snapshotDir, filename);
39471
39752
  await page.screenshot({ path: framePath, type: "png" });
39472
39753
  savedPaths.push(`snapshots/${filename}`);
39473
39754
  }
@@ -39479,7 +39760,7 @@ async function captureSnapshots(projectDir, opts) {
39479
39760
  }
39480
39761
  return savedPaths;
39481
39762
  }
39482
- var __filename2, __dirname3, FFMPEG_EXTRACT_TIMEOUT_MS, examples18, snapshot_default;
39763
+ var __filename2, __dirname3, FFMPEG_EXTRACT_TIMEOUT_MS, examples19, snapshot_default;
39483
39764
  var init_snapshot = __esm({
39484
39765
  "src/commands/snapshot.ts"() {
39485
39766
  "use strict";
@@ -39489,7 +39770,7 @@ var init_snapshot = __esm({
39489
39770
  __filename2 = fileURLToPath8(import.meta.url);
39490
39771
  __dirname3 = dirname20(__filename2);
39491
39772
  FFMPEG_EXTRACT_TIMEOUT_MS = 3e4;
39492
- examples18 = [
39773
+ examples19 = [
39493
39774
  ["Capture 5 key frames from a composition", "snapshot captures/stripe"],
39494
39775
  ["Capture 10 evenly-spaced frames", "snapshot captures/stripe --frames 10"]
39495
39776
  ];
@@ -39553,13 +39834,13 @@ ${c.error("\u2717")} Snapshot failed: ${msg}`);
39553
39834
 
39554
39835
  // src/capture/assetDownloader.ts
39555
39836
  import { writeFileSync as writeFileSync17, mkdirSync as mkdirSync25 } from "fs";
39556
- import { join as join46, extname as extname10 } from "path";
39837
+ import { join as join48, extname as extname10 } from "path";
39557
39838
  async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks) {
39558
- const assetsDir = join46(outputDir, "assets");
39839
+ const assetsDir = join48(outputDir, "assets");
39559
39840
  mkdirSync25(assetsDir, { recursive: true });
39560
39841
  const assets = [];
39561
39842
  const downloadedUrls = /* @__PURE__ */ new Set();
39562
- mkdirSync25(join46(outputDir, "assets", "svgs"), { recursive: true });
39843
+ mkdirSync25(join48(outputDir, "assets", "svgs"), { recursive: true });
39563
39844
  for (let i2 = 0; i2 < tokens.svgs.length && i2 < 30; i2++) {
39564
39845
  const svg = tokens.svgs[i2];
39565
39846
  if (!svg.outerHTML || svg.outerHTML.length < 50) continue;
@@ -39567,7 +39848,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
39567
39848
  const name = label2 ? slugify(label2) + ".svg" : svg.isLogo ? `logo-${i2}.svg` : `icon-${i2}.svg`;
39568
39849
  const localPath = `assets/svgs/${name}`;
39569
39850
  try {
39570
- writeFileSync17(join46(outputDir, localPath), svg.outerHTML, "utf-8");
39851
+ writeFileSync17(join48(outputDir, localPath), svg.outerHTML, "utf-8");
39571
39852
  assets.push({ url: "", localPath, type: "svg" });
39572
39853
  } catch {
39573
39854
  }
@@ -39580,7 +39861,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
39580
39861
  const localPath = `assets/${name}`;
39581
39862
  const buffer = await fetchBuffer(icon.href);
39582
39863
  if (buffer) {
39583
- writeFileSync17(join46(outputDir, localPath), buffer);
39864
+ writeFileSync17(join48(outputDir, localPath), buffer);
39584
39865
  assets.push({ url: icon.href, localPath, type: "favicon" });
39585
39866
  break;
39586
39867
  }
@@ -39637,7 +39918,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
39637
39918
  const slug = isMeaningful ? slugify(rawName) : `${prefix}-${imgIdx}`;
39638
39919
  const name = `${slug}${ext}`;
39639
39920
  const localPath = `assets/${name}`;
39640
- writeFileSync17(join46(outputDir, localPath), buffer);
39921
+ writeFileSync17(join48(outputDir, localPath), buffer);
39641
39922
  assets.push({ url, localPath, type: "image" });
39642
39923
  imgIdx++;
39643
39924
  } catch {
@@ -39650,7 +39931,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
39650
39931
  const localPath = `assets/og-image${ext}`;
39651
39932
  const buffer = await fetchBuffer(tokens.ogImage);
39652
39933
  if (buffer && buffer.length > 5e3) {
39653
- writeFileSync17(join46(outputDir, localPath), buffer);
39934
+ writeFileSync17(join48(outputDir, localPath), buffer);
39654
39935
  assets.push({ url: tokens.ogImage, localPath, type: "image" });
39655
39936
  }
39656
39937
  } catch {
@@ -39673,7 +39954,7 @@ function normalizeUrl(u) {
39673
39954
  }
39674
39955
  }
39675
39956
  async function downloadAndRewriteFonts(css, outputDir) {
39676
- const assetsDir = join46(outputDir, "assets", "fonts");
39957
+ const assetsDir = join48(outputDir, "assets", "fonts");
39677
39958
  mkdirSync25(assetsDir, { recursive: true });
39678
39959
  const fontUrlRegex = /url\(['"]?(https?:\/\/[^'")\s]+\.(?:woff2?|ttf|otf)[^'")\s]*?)['"]?\)/g;
39679
39960
  const fontUrls = /* @__PURE__ */ new Set();
@@ -39709,7 +39990,7 @@ async function downloadAndRewriteFonts(css, outputDir) {
39709
39990
  try {
39710
39991
  const urlObj = new URL(fontUrl);
39711
39992
  const filename = urlObj.pathname.split("/").pop() || `font-${count}.woff2`;
39712
- const localPath = join46(assetsDir, filename);
39993
+ const localPath = join48(assetsDir, filename);
39713
39994
  const relativePath = `assets/fonts/${filename}`;
39714
39995
  const buffer = await fetchBuffer(fontUrl);
39715
39996
  if (buffer) {
@@ -39931,7 +40212,7 @@ async function extractHtml(page, opts = {}) {
39931
40212
  (_match, pre, urls, post) => {
39932
40213
  const fixed = urls.replace(/&amp;/g, "&").replace(
39933
40214
  /(^|,\s*)(\/[^\s,]+)/g,
39934
- (_m, sep3, path2) => sep3 + pageOrigin + path2
40215
+ (_m, sep4, path2) => sep4 + pageOrigin + path2
39935
40216
  );
39936
40217
  return pre + fixed + post;
39937
40218
  }
@@ -40506,8 +40787,8 @@ var init_animationCataloger = __esm({
40506
40787
  });
40507
40788
 
40508
40789
  // src/capture/mediaCapture.ts
40509
- import { mkdirSync as mkdirSync26, writeFileSync as writeFileSync18, readdirSync as readdirSync15, readFileSync as readFileSync33, statSync as statSync15 } from "fs";
40510
- import { join as join47 } from "path";
40790
+ import { mkdirSync as mkdirSync26, writeFileSync as writeFileSync18, readdirSync as readdirSync16, readFileSync as readFileSync34, statSync as statSync16 } from "fs";
40791
+ import { join as join49 } from "path";
40511
40792
  async function saveLottieAnimations(discoveredLotties, lottieDir) {
40512
40793
  let savedCount = 0;
40513
40794
  const savedHashes = /* @__PURE__ */ new Set();
@@ -40527,8 +40808,8 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
40527
40808
  const buf = Buffer.from(await res.arrayBuffer());
40528
40809
  if (lottieItem.url.endsWith(".lottie")) {
40529
40810
  try {
40530
- const AdmZip = (await import("adm-zip")).default;
40531
- const zip = new AdmZip(buf);
40811
+ const AdmZip2 = (await import("adm-zip")).default;
40812
+ const zip = new AdmZip2(buf);
40532
40813
  const entries2 = zip.getEntries();
40533
40814
  const animEntry = entries2.find(
40534
40815
  (e2) => (e2.entryName.startsWith("a/") || e2.entryName.startsWith("animations/")) && e2.entryName.endsWith(".json")
@@ -40540,7 +40821,7 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
40540
40821
  const hash2 = buf.toString("base64").slice(0, 100);
40541
40822
  if (savedHashes.has(hash2)) continue;
40542
40823
  savedHashes.add(hash2);
40543
- writeFileSync18(join47(lottieDir, `animation-${savedCount}.lottie`), buf);
40824
+ writeFileSync18(join49(lottieDir, `animation-${savedCount}.lottie`), buf);
40544
40825
  savedCount++;
40545
40826
  continue;
40546
40827
  }
@@ -40558,7 +40839,7 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
40558
40839
  } catch {
40559
40840
  continue;
40560
40841
  }
40561
- writeFileSync18(join47(lottieDir, `animation-${savedCount}.json`), jsonData, "utf-8");
40842
+ writeFileSync18(join49(lottieDir, `animation-${savedCount}.json`), jsonData, "utf-8");
40562
40843
  savedCount++;
40563
40844
  }
40564
40845
  } catch {
@@ -40568,22 +40849,22 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
40568
40849
  }
40569
40850
  async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
40570
40851
  const manifest = [];
40571
- const previewDir = join47(lottieDir, "previews");
40852
+ const previewDir = join49(lottieDir, "previews");
40572
40853
  mkdirSync26(previewDir, { recursive: true });
40573
- for (const file of readdirSync15(lottieDir)) {
40854
+ for (const file of readdirSync16(lottieDir)) {
40574
40855
  if (!file.endsWith(".json")) continue;
40575
40856
  try {
40576
- const raw = JSON.parse(readFileSync33(join47(lottieDir, file), "utf-8"));
40857
+ const raw = JSON.parse(readFileSync34(join49(lottieDir, file), "utf-8"));
40577
40858
  const fr = raw.fr || 30;
40578
40859
  const dur = ((raw.op || 0) - (raw.ip || 0)) / fr;
40579
40860
  const previewName = file.replace(".json", "-preview.png");
40580
- const fileSize = statSync15(join47(lottieDir, file)).size;
40861
+ const fileSize = statSync16(join49(lottieDir, file)).size;
40581
40862
  if (fileSize > 2e6) continue;
40582
40863
  let previewPage;
40583
40864
  try {
40584
40865
  previewPage = await chromeBrowser.newPage();
40585
40866
  await previewPage.setViewport({ width: 400, height: 400 });
40586
- const animData = JSON.parse(readFileSync33(join47(lottieDir, file), "utf-8"));
40867
+ const animData = JSON.parse(readFileSync34(join49(lottieDir, file), "utf-8"));
40587
40868
  const midFrame = Math.floor(((raw.op || 0) - (raw.ip || 0)) * 0.3);
40588
40869
  await previewPage.setContent(
40589
40870
  `<!DOCTYPE html>
@@ -40613,7 +40894,7 @@ async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
40613
40894
  await previewPage.waitForFunction(() => window.__READY === true, { timeout: 5e3 }).catch(() => {
40614
40895
  });
40615
40896
  await previewPage.screenshot({
40616
- path: join47(previewDir, previewName),
40897
+ path: join49(previewDir, previewName),
40617
40898
  type: "png",
40618
40899
  omitBackground: true
40619
40900
  });
@@ -40637,7 +40918,7 @@ async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
40637
40918
  }
40638
40919
  if (manifest.length > 0) {
40639
40920
  writeFileSync18(
40640
- join47(outputDir, "extracted", "lottie-manifest.json"),
40921
+ join49(outputDir, "extracted", "lottie-manifest.json"),
40641
40922
  JSON.stringify(manifest, null, 2),
40642
40923
  "utf-8"
40643
40924
  );
@@ -40699,15 +40980,15 @@ async function captureVideoManifest(page, outputDir, progress) {
40699
40980
  return true;
40700
40981
  });
40701
40982
  if (uniqueVideos.length > 0) {
40702
- const videoManifestDir = join47(outputDir, "assets", "videos");
40983
+ const videoManifestDir = join49(outputDir, "assets", "videos");
40703
40984
  mkdirSync26(videoManifestDir, { recursive: true });
40704
- const previewDir = join47(videoManifestDir, "previews");
40985
+ const previewDir = join49(videoManifestDir, "previews");
40705
40986
  mkdirSync26(previewDir, { recursive: true });
40706
40987
  const videoManifest = [];
40707
40988
  for (let vi = 0; vi < uniqueVideos.length && vi < 20; vi++) {
40708
40989
  const v = uniqueVideos[vi];
40709
40990
  const previewName = `video-${vi}-preview.png`;
40710
- const previewPath = join47(previewDir, previewName);
40991
+ const previewPath = join49(previewDir, previewName);
40711
40992
  try {
40712
40993
  await page.evaluate(`window.scrollTo(0, ${Math.max(0, v.top - 100)})`);
40713
40994
  await new Promise((r2) => setTimeout(r2, 300));
@@ -40746,7 +41027,7 @@ async function captureVideoManifest(page, outputDir, progress) {
40746
41027
  }
40747
41028
  if (videoManifest.length > 0) {
40748
41029
  writeFileSync18(
40749
- join47(outputDir, "extracted", "video-manifest.json"),
41030
+ join49(outputDir, "extracted", "video-manifest.json"),
40750
41031
  JSON.stringify(videoManifest, null, 2),
40751
41032
  "utf-8"
40752
41033
  );
@@ -41028,7 +41309,7 @@ var require_p_retry = __commonJS({
41028
41309
  return error;
41029
41310
  };
41030
41311
  var isNetworkError = (errorMessage) => networkErrorMsgs.includes(errorMessage);
41031
- var pRetry2 = (input, options) => new Promise((resolve35, reject) => {
41312
+ var pRetry2 = (input, options) => new Promise((resolve37, reject) => {
41032
41313
  options = {
41033
41314
  onFailedAttempt: () => {
41034
41315
  },
@@ -41038,7 +41319,7 @@ var require_p_retry = __commonJS({
41038
41319
  const operation = retry.operation(options);
41039
41320
  operation.attempt(async (attemptNumber) => {
41040
41321
  try {
41041
- resolve35(await input(attemptNumber));
41322
+ resolve37(await input(attemptNumber));
41042
41323
  } catch (error) {
41043
41324
  if (!(error instanceof Error)) {
41044
41325
  reject(new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`));
@@ -41574,8 +41855,8 @@ var require_retry3 = __commonJS({
41574
41855
  }
41575
41856
  const delay = getNextRetryDelay(config);
41576
41857
  err.config.retryConfig.currentRetryAttempt += 1;
41577
- const backoff = config.retryBackoff ? config.retryBackoff(err, delay) : new Promise((resolve35) => {
41578
- setTimeout(resolve35, delay);
41858
+ const backoff = config.retryBackoff ? config.retryBackoff(err, delay) : new Promise((resolve37) => {
41859
+ setTimeout(resolve37, delay);
41579
41860
  });
41580
41861
  if (config.onRetryAttempt) {
41581
41862
  await config.onRetryAttempt(err);
@@ -42483,8 +42764,8 @@ var require_helpers = __commonJS({
42483
42764
  function req(url, opts = {}) {
42484
42765
  const href = typeof url === "string" ? url : url.href;
42485
42766
  const req2 = (href.startsWith("https:") ? https2 : http4).request(url, opts);
42486
- const promise = new Promise((resolve35, reject) => {
42487
- req2.once("response", resolve35).once("error", reject).end();
42767
+ const promise = new Promise((resolve37, reject) => {
42768
+ req2.once("response", resolve37).once("error", reject).end();
42488
42769
  });
42489
42770
  req2.then = promise.then.bind(promise);
42490
42771
  return req2;
@@ -42661,7 +42942,7 @@ var require_parse_proxy_response = __commonJS({
42661
42942
  var debug_1 = __importDefault(require_src2());
42662
42943
  var debug = (0, debug_1.default)("https-proxy-agent:parse-proxy-response");
42663
42944
  function parseProxyResponse(socket) {
42664
- return new Promise((resolve35, reject) => {
42945
+ return new Promise((resolve37, reject) => {
42665
42946
  let buffersLength = 0;
42666
42947
  const buffers = [];
42667
42948
  function read() {
@@ -42727,7 +43008,7 @@ var require_parse_proxy_response = __commonJS({
42727
43008
  }
42728
43009
  debug("got proxy server response: %o %o", firstLine, headers);
42729
43010
  cleanup();
42730
- resolve35({
43011
+ resolve37({
42731
43012
  connect: {
42732
43013
  statusCode,
42733
43014
  statusText,
@@ -42971,7 +43252,7 @@ var require_ponyfill_es2018 = __commonJS({
42971
43252
  return new originalPromise(executor);
42972
43253
  }
42973
43254
  function promiseResolvedWith(value) {
42974
- return newPromise((resolve35) => resolve35(value));
43255
+ return newPromise((resolve37) => resolve37(value));
42975
43256
  }
42976
43257
  function promiseRejectedWith(reason) {
42977
43258
  return originalPromiseReject(reason);
@@ -43141,8 +43422,8 @@ var require_ponyfill_es2018 = __commonJS({
43141
43422
  return new TypeError("Cannot " + name + " a stream using a released reader");
43142
43423
  }
43143
43424
  function defaultReaderClosedPromiseInitialize(reader) {
43144
- reader._closedPromise = newPromise((resolve35, reject) => {
43145
- reader._closedPromise_resolve = resolve35;
43425
+ reader._closedPromise = newPromise((resolve37, reject) => {
43426
+ reader._closedPromise_resolve = resolve37;
43146
43427
  reader._closedPromise_reject = reject;
43147
43428
  });
43148
43429
  }
@@ -43316,8 +43597,8 @@ var require_ponyfill_es2018 = __commonJS({
43316
43597
  }
43317
43598
  let resolvePromise;
43318
43599
  let rejectPromise;
43319
- const promise = newPromise((resolve35, reject) => {
43320
- resolvePromise = resolve35;
43600
+ const promise = newPromise((resolve37, reject) => {
43601
+ resolvePromise = resolve37;
43321
43602
  rejectPromise = reject;
43322
43603
  });
43323
43604
  const readRequest = {
@@ -43422,8 +43703,8 @@ var require_ponyfill_es2018 = __commonJS({
43422
43703
  const reader = this._reader;
43423
43704
  let resolvePromise;
43424
43705
  let rejectPromise;
43425
- const promise = newPromise((resolve35, reject) => {
43426
- resolvePromise = resolve35;
43706
+ const promise = newPromise((resolve37, reject) => {
43707
+ resolvePromise = resolve37;
43427
43708
  rejectPromise = reject;
43428
43709
  });
43429
43710
  const readRequest = {
@@ -44442,8 +44723,8 @@ var require_ponyfill_es2018 = __commonJS({
44442
44723
  }
44443
44724
  let resolvePromise;
44444
44725
  let rejectPromise;
44445
- const promise = newPromise((resolve35, reject) => {
44446
- resolvePromise = resolve35;
44726
+ const promise = newPromise((resolve37, reject) => {
44727
+ resolvePromise = resolve37;
44447
44728
  rejectPromise = reject;
44448
44729
  });
44449
44730
  const readIntoRequest = {
@@ -44755,10 +45036,10 @@ var require_ponyfill_es2018 = __commonJS({
44755
45036
  wasAlreadyErroring = true;
44756
45037
  reason = void 0;
44757
45038
  }
44758
- const promise = newPromise((resolve35, reject) => {
45039
+ const promise = newPromise((resolve37, reject) => {
44759
45040
  stream._pendingAbortRequest = {
44760
45041
  _promise: void 0,
44761
- _resolve: resolve35,
45042
+ _resolve: resolve37,
44762
45043
  _reject: reject,
44763
45044
  _reason: reason,
44764
45045
  _wasAlreadyErroring: wasAlreadyErroring
@@ -44775,9 +45056,9 @@ var require_ponyfill_es2018 = __commonJS({
44775
45056
  if (state === "closed" || state === "errored") {
44776
45057
  return promiseRejectedWith(new TypeError(`The stream (in ${state} state) is not in the writable state and cannot be closed`));
44777
45058
  }
44778
- const promise = newPromise((resolve35, reject) => {
45059
+ const promise = newPromise((resolve37, reject) => {
44779
45060
  const closeRequest = {
44780
- _resolve: resolve35,
45061
+ _resolve: resolve37,
44781
45062
  _reject: reject
44782
45063
  };
44783
45064
  stream._closeRequest = closeRequest;
@@ -44790,9 +45071,9 @@ var require_ponyfill_es2018 = __commonJS({
44790
45071
  return promise;
44791
45072
  }
44792
45073
  function WritableStreamAddWriteRequest(stream) {
44793
- const promise = newPromise((resolve35, reject) => {
45074
+ const promise = newPromise((resolve37, reject) => {
44794
45075
  const writeRequest = {
44795
- _resolve: resolve35,
45076
+ _resolve: resolve37,
44796
45077
  _reject: reject
44797
45078
  };
44798
45079
  stream._writeRequests.push(writeRequest);
@@ -45408,8 +45689,8 @@ var require_ponyfill_es2018 = __commonJS({
45408
45689
  return new TypeError("Cannot " + name + " a stream using a released writer");
45409
45690
  }
45410
45691
  function defaultWriterClosedPromiseInitialize(writer) {
45411
- writer._closedPromise = newPromise((resolve35, reject) => {
45412
- writer._closedPromise_resolve = resolve35;
45692
+ writer._closedPromise = newPromise((resolve37, reject) => {
45693
+ writer._closedPromise_resolve = resolve37;
45413
45694
  writer._closedPromise_reject = reject;
45414
45695
  writer._closedPromiseState = "pending";
45415
45696
  });
@@ -45445,8 +45726,8 @@ var require_ponyfill_es2018 = __commonJS({
45445
45726
  writer._closedPromiseState = "resolved";
45446
45727
  }
45447
45728
  function defaultWriterReadyPromiseInitialize(writer) {
45448
- writer._readyPromise = newPromise((resolve35, reject) => {
45449
- writer._readyPromise_resolve = resolve35;
45729
+ writer._readyPromise = newPromise((resolve37, reject) => {
45730
+ writer._readyPromise_resolve = resolve37;
45450
45731
  writer._readyPromise_reject = reject;
45451
45732
  });
45452
45733
  writer._readyPromiseState = "pending";
@@ -45533,7 +45814,7 @@ var require_ponyfill_es2018 = __commonJS({
45533
45814
  source._disturbed = true;
45534
45815
  let shuttingDown = false;
45535
45816
  let currentWrite = promiseResolvedWith(void 0);
45536
- return newPromise((resolve35, reject) => {
45817
+ return newPromise((resolve37, reject) => {
45537
45818
  let abortAlgorithm;
45538
45819
  if (signal !== void 0) {
45539
45820
  abortAlgorithm = () => {
@@ -45678,7 +45959,7 @@ var require_ponyfill_es2018 = __commonJS({
45678
45959
  if (isError) {
45679
45960
  reject(error);
45680
45961
  } else {
45681
- resolve35(void 0);
45962
+ resolve37(void 0);
45682
45963
  }
45683
45964
  return null;
45684
45965
  }
@@ -45959,8 +46240,8 @@ var require_ponyfill_es2018 = __commonJS({
45959
46240
  let branch1;
45960
46241
  let branch2;
45961
46242
  let resolveCancelPromise;
45962
- const cancelPromise = newPromise((resolve35) => {
45963
- resolveCancelPromise = resolve35;
46243
+ const cancelPromise = newPromise((resolve37) => {
46244
+ resolveCancelPromise = resolve37;
45964
46245
  });
45965
46246
  function pullAlgorithm() {
45966
46247
  if (reading) {
@@ -46051,8 +46332,8 @@ var require_ponyfill_es2018 = __commonJS({
46051
46332
  let branch1;
46052
46333
  let branch2;
46053
46334
  let resolveCancelPromise;
46054
- const cancelPromise = newPromise((resolve35) => {
46055
- resolveCancelPromise = resolve35;
46335
+ const cancelPromise = newPromise((resolve37) => {
46336
+ resolveCancelPromise = resolve37;
46056
46337
  });
46057
46338
  function forwardReaderError(thisReader) {
46058
46339
  uponRejection(thisReader._closedPromise, (r2) => {
@@ -46832,8 +47113,8 @@ var require_ponyfill_es2018 = __commonJS({
46832
47113
  const writableHighWaterMark = ExtractHighWaterMark(writableStrategy, 1);
46833
47114
  const writableSizeAlgorithm = ExtractSizeAlgorithm(writableStrategy);
46834
47115
  let startPromise_resolve;
46835
- const startPromise = newPromise((resolve35) => {
46836
- startPromise_resolve = resolve35;
47116
+ const startPromise = newPromise((resolve37) => {
47117
+ startPromise_resolve = resolve37;
46837
47118
  });
46838
47119
  InitializeTransformStream(this, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm);
46839
47120
  SetUpTransformStreamDefaultControllerFromTransformer(this, transformer);
@@ -46926,8 +47207,8 @@ var require_ponyfill_es2018 = __commonJS({
46926
47207
  if (stream._backpressureChangePromise !== void 0) {
46927
47208
  stream._backpressureChangePromise_resolve();
46928
47209
  }
46929
- stream._backpressureChangePromise = newPromise((resolve35) => {
46930
- stream._backpressureChangePromise_resolve = resolve35;
47210
+ stream._backpressureChangePromise = newPromise((resolve37) => {
47211
+ stream._backpressureChangePromise_resolve = resolve37;
46931
47212
  });
46932
47213
  stream._backpressure = backpressure;
46933
47214
  }
@@ -47095,8 +47376,8 @@ var require_ponyfill_es2018 = __commonJS({
47095
47376
  return controller._finishPromise;
47096
47377
  }
47097
47378
  const readable = stream._readable;
47098
- controller._finishPromise = newPromise((resolve35, reject) => {
47099
- controller._finishPromise_resolve = resolve35;
47379
+ controller._finishPromise = newPromise((resolve37, reject) => {
47380
+ controller._finishPromise_resolve = resolve37;
47100
47381
  controller._finishPromise_reject = reject;
47101
47382
  });
47102
47383
  const cancelPromise = controller._cancelAlgorithm(reason);
@@ -47122,8 +47403,8 @@ var require_ponyfill_es2018 = __commonJS({
47122
47403
  return controller._finishPromise;
47123
47404
  }
47124
47405
  const readable = stream._readable;
47125
- controller._finishPromise = newPromise((resolve35, reject) => {
47126
- controller._finishPromise_resolve = resolve35;
47406
+ controller._finishPromise = newPromise((resolve37, reject) => {
47407
+ controller._finishPromise_resolve = resolve37;
47127
47408
  controller._finishPromise_reject = reject;
47128
47409
  });
47129
47410
  const flushPromise = controller._flushAlgorithm();
@@ -47153,8 +47434,8 @@ var require_ponyfill_es2018 = __commonJS({
47153
47434
  return controller._finishPromise;
47154
47435
  }
47155
47436
  const writable = stream._writable;
47156
- controller._finishPromise = newPromise((resolve35, reject) => {
47157
- controller._finishPromise_resolve = resolve35;
47437
+ controller._finishPromise = newPromise((resolve37, reject) => {
47438
+ controller._finishPromise_resolve = resolve37;
47158
47439
  controller._finishPromise_reject = reject;
47159
47440
  });
47160
47441
  const cancelPromise = controller._cancelAlgorithm(reason);
@@ -47700,8 +47981,8 @@ var require_node_domexception = __commonJS({
47700
47981
  });
47701
47982
 
47702
47983
  // ../../node_modules/.bun/fetch-blob@3.2.0/node_modules/fetch-blob/from.js
47703
- import { statSync as statSync16, createReadStream as createReadStream2, promises as fs2 } from "fs";
47704
- import { basename as basename8 } from "path";
47984
+ import { statSync as statSync17, createReadStream as createReadStream2, promises as fs2 } from "fs";
47985
+ import { basename as basename10 } from "path";
47705
47986
  var import_node_domexception, stat, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
47706
47987
  var init_from = __esm({
47707
47988
  "../../node_modules/.bun/fetch-blob@3.2.0/node_modules/fetch-blob/from.js"() {
@@ -47710,10 +47991,10 @@ var init_from = __esm({
47710
47991
  init_file();
47711
47992
  init_fetch_blob();
47712
47993
  ({ stat } = fs2);
47713
- blobFromSync = (path2, type) => fromBlob(statSync16(path2), path2, type);
47994
+ blobFromSync = (path2, type) => fromBlob(statSync17(path2), path2, type);
47714
47995
  blobFrom = (path2, type) => stat(path2).then((stat3) => fromBlob(stat3, path2, type));
47715
47996
  fileFrom = (path2, type) => stat(path2).then((stat3) => fromFile(stat3, path2, type));
47716
- fileFromSync = (path2, type) => fromFile(statSync16(path2), path2, type);
47997
+ fileFromSync = (path2, type) => fromFile(statSync17(path2), path2, type);
47717
47998
  fromBlob = (stat3, path2, type = "") => new fetch_blob_default([new BlobDataItem({
47718
47999
  path: path2,
47719
48000
  size: stat3.size,
@@ -47725,7 +48006,7 @@ var init_from = __esm({
47725
48006
  size: stat3.size,
47726
48007
  lastModified: stat3.mtimeMs,
47727
48008
  start: 0
47728
- })], basename8(path2), { type, lastModified: stat3.mtimeMs });
48009
+ })], basename10(path2), { type, lastModified: stat3.mtimeMs });
47729
48010
  BlobDataItem = class _BlobDataItem {
47730
48011
  #path;
47731
48012
  #start;
@@ -49123,7 +49404,7 @@ import zlib from "zlib";
49123
49404
  import Stream2, { PassThrough as PassThrough2, pipeline as pump } from "stream";
49124
49405
  import { Buffer as Buffer3 } from "buffer";
49125
49406
  async function fetch3(url, options_) {
49126
- return new Promise((resolve35, reject) => {
49407
+ return new Promise((resolve37, reject) => {
49127
49408
  const request = new Request2(url, options_);
49128
49409
  const { parsedURL, options } = getNodeRequestOptions(request);
49129
49410
  if (!supportedSchemas.has(parsedURL.protocol)) {
@@ -49132,7 +49413,7 @@ async function fetch3(url, options_) {
49132
49413
  if (parsedURL.protocol === "data:") {
49133
49414
  const data = dist_default(request.url);
49134
49415
  const response2 = new Response2(data, { headers: { "Content-Type": data.typeFull } });
49135
- resolve35(response2);
49416
+ resolve37(response2);
49136
49417
  return;
49137
49418
  }
49138
49419
  const send = (parsedURL.protocol === "https:" ? https : http3).request;
@@ -49254,7 +49535,7 @@ async function fetch3(url, options_) {
49254
49535
  if (responseReferrerPolicy) {
49255
49536
  requestOptions.referrerPolicy = responseReferrerPolicy;
49256
49537
  }
49257
- resolve35(fetch3(new Request2(locationURL, requestOptions)));
49538
+ resolve37(fetch3(new Request2(locationURL, requestOptions)));
49258
49539
  finalize();
49259
49540
  return;
49260
49541
  }
@@ -49287,7 +49568,7 @@ async function fetch3(url, options_) {
49287
49568
  const codings = headers.get("Content-Encoding");
49288
49569
  if (!request.compress || request.method === "HEAD" || codings === null || response_.statusCode === 204 || response_.statusCode === 304) {
49289
49570
  response = new Response2(body, responseOptions);
49290
- resolve35(response);
49571
+ resolve37(response);
49291
49572
  return;
49292
49573
  }
49293
49574
  const zlibOptions = {
@@ -49301,7 +49582,7 @@ async function fetch3(url, options_) {
49301
49582
  }
49302
49583
  });
49303
49584
  response = new Response2(body, responseOptions);
49304
- resolve35(response);
49585
+ resolve37(response);
49305
49586
  return;
49306
49587
  }
49307
49588
  if (codings === "deflate" || codings === "x-deflate") {
@@ -49325,12 +49606,12 @@ async function fetch3(url, options_) {
49325
49606
  });
49326
49607
  }
49327
49608
  response = new Response2(body, responseOptions);
49328
- resolve35(response);
49609
+ resolve37(response);
49329
49610
  });
49330
49611
  raw.once("end", () => {
49331
49612
  if (!response) {
49332
49613
  response = new Response2(body, responseOptions);
49333
- resolve35(response);
49614
+ resolve37(response);
49334
49615
  }
49335
49616
  });
49336
49617
  return;
@@ -49342,11 +49623,11 @@ async function fetch3(url, options_) {
49342
49623
  }
49343
49624
  });
49344
49625
  response = new Response2(body, responseOptions);
49345
- resolve35(response);
49626
+ resolve37(response);
49346
49627
  return;
49347
49628
  }
49348
49629
  response = new Response2(body, responseOptions);
49349
- resolve35(response);
49630
+ resolve37(response);
49350
49631
  });
49351
49632
  writeToStream(request_, request).catch(reject);
49352
49633
  });
@@ -55428,7 +55709,7 @@ var require_jwtaccess = __commonJS({
55428
55709
  }
55429
55710
  }
55430
55711
  fromStreamAsync(inputStream) {
55431
- return new Promise((resolve35, reject) => {
55712
+ return new Promise((resolve37, reject) => {
55432
55713
  if (!inputStream) {
55433
55714
  reject(new Error("Must pass in a stream containing the service account auth settings."));
55434
55715
  }
@@ -55437,7 +55718,7 @@ var require_jwtaccess = __commonJS({
55437
55718
  try {
55438
55719
  const data = JSON.parse(s2);
55439
55720
  this.fromJSON(data);
55440
- resolve35();
55721
+ resolve37();
55441
55722
  } catch (err) {
55442
55723
  reject(err);
55443
55724
  }
@@ -55676,7 +55957,7 @@ var require_jwtclient = __commonJS({
55676
55957
  }
55677
55958
  }
55678
55959
  fromStreamAsync(inputStream) {
55679
- return new Promise((resolve35, reject) => {
55960
+ return new Promise((resolve37, reject) => {
55680
55961
  if (!inputStream) {
55681
55962
  throw new Error("Must pass in a stream containing the service account auth settings.");
55682
55963
  }
@@ -55685,7 +55966,7 @@ var require_jwtclient = __commonJS({
55685
55966
  try {
55686
55967
  const data = JSON.parse(s2);
55687
55968
  this.fromJSON(data);
55688
- resolve35();
55969
+ resolve37();
55689
55970
  } catch (e2) {
55690
55971
  reject(e2);
55691
55972
  }
@@ -55818,7 +56099,7 @@ var require_refreshclient = __commonJS({
55818
56099
  }
55819
56100
  }
55820
56101
  async fromStreamAsync(inputStream) {
55821
- return new Promise((resolve35, reject) => {
56102
+ return new Promise((resolve37, reject) => {
55822
56103
  if (!inputStream) {
55823
56104
  return reject(new Error("Must pass in a stream containing the user refresh token."));
55824
56105
  }
@@ -55827,7 +56108,7 @@ var require_refreshclient = __commonJS({
55827
56108
  try {
55828
56109
  const data = JSON.parse(s2);
55829
56110
  this.fromJSON(data);
55830
- return resolve35();
56111
+ return resolve37();
55831
56112
  } catch (err) {
55832
56113
  return reject(err);
55833
56114
  }
@@ -57660,7 +57941,7 @@ var require_pluggable_auth_handler = __commonJS({
57660
57941
  * @return A promise that resolves with the executable response.
57661
57942
  */
57662
57943
  retrieveResponseFromExecutable(envMap) {
57663
- return new Promise((resolve35, reject) => {
57944
+ return new Promise((resolve37, reject) => {
57664
57945
  const child = childProcess.spawn(this.commandComponents[0], this.commandComponents.slice(1), {
57665
57946
  env: { ...process.env, ...Object.fromEntries(envMap) }
57666
57947
  });
@@ -57682,7 +57963,7 @@ var require_pluggable_auth_handler = __commonJS({
57682
57963
  try {
57683
57964
  const responseJson = JSON.parse(output);
57684
57965
  const response = new executable_response_1.ExecutableResponse(responseJson);
57685
- return resolve35(response);
57966
+ return resolve37(response);
57686
57967
  } catch (error) {
57687
57968
  if (error instanceof executable_response_1.ExecutableResponseError) {
57688
57969
  return reject(error);
@@ -58585,7 +58866,7 @@ var require_googleauth = __commonJS({
58585
58866
  }
58586
58867
  }
58587
58868
  fromStreamAsync(inputStream, options) {
58588
- return new Promise((resolve35, reject) => {
58869
+ return new Promise((resolve37, reject) => {
58589
58870
  if (!inputStream) {
58590
58871
  throw new Error("Must pass in a stream containing the Google auth settings.");
58591
58872
  }
@@ -58595,7 +58876,7 @@ var require_googleauth = __commonJS({
58595
58876
  try {
58596
58877
  const data = JSON.parse(chunks.join(""));
58597
58878
  const r2 = this._cacheClientFromJSON(data, options);
58598
- return resolve35(r2);
58879
+ return resolve37(r2);
58599
58880
  } catch (err) {
58600
58881
  if (!this.keyFilename)
58601
58882
  throw err;
@@ -58605,7 +58886,7 @@ var require_googleauth = __commonJS({
58605
58886
  });
58606
58887
  this.cachedCredential = client;
58607
58888
  this.setGapicJWTValues(client);
58608
- return resolve35(client);
58889
+ return resolve37(client);
58609
58890
  }
58610
58891
  } catch (err) {
58611
58892
  return reject(err);
@@ -58641,17 +58922,17 @@ var require_googleauth = __commonJS({
58641
58922
  * Run the Google Cloud SDK command that prints the default project ID
58642
58923
  */
58643
58924
  async getDefaultServiceProjectId() {
58644
- return new Promise((resolve35) => {
58925
+ return new Promise((resolve37) => {
58645
58926
  (0, child_process_1.exec)("gcloud config config-helper --format json", (err, stdout2) => {
58646
58927
  if (!err && stdout2) {
58647
58928
  try {
58648
58929
  const projectId = JSON.parse(stdout2).configuration.properties.core.project;
58649
- resolve35(projectId);
58930
+ resolve37(projectId);
58650
58931
  return;
58651
58932
  } catch (e2) {
58652
58933
  }
58653
58934
  }
58654
- resolve35(null);
58935
+ resolve37(null);
58655
58936
  });
58656
58937
  });
58657
58938
  }
@@ -66423,14 +66704,14 @@ function __asyncValues(o) {
66423
66704
  }, i2);
66424
66705
  function verb(n) {
66425
66706
  i2[n] = o[n] && function(v) {
66426
- return new Promise(function(resolve35, reject) {
66427
- v = o[n](v), settle(resolve35, reject, v.done, v.value);
66707
+ return new Promise(function(resolve37, reject) {
66708
+ v = o[n](v), settle(resolve37, reject, v.done, v.value);
66428
66709
  });
66429
66710
  };
66430
66711
  }
66431
- function settle(resolve35, reject, d, v) {
66712
+ function settle(resolve37, reject, d, v) {
66432
66713
  Promise.resolve(v).then(function(v2) {
66433
- resolve35({ value: v2, done: d });
66714
+ resolve37({ value: v2, done: d });
66434
66715
  }, reject);
66435
66716
  }
66436
66717
  }
@@ -76933,8 +77214,8 @@ var init_node4 = __esm({
76933
77214
  const url = `${websocketBaseUrl}/ws/google.ai.generativelanguage.${apiVersion}.GenerativeService.BidiGenerateMusic?key=${apiKey}`;
76934
77215
  let onopenResolve = () => {
76935
77216
  };
76936
- const onopenPromise = new Promise((resolve35) => {
76937
- onopenResolve = resolve35;
77217
+ const onopenPromise = new Promise((resolve37) => {
77218
+ onopenResolve = resolve37;
76938
77219
  });
76939
77220
  const callbacks = params.callbacks;
76940
77221
  const onopenAwaitedCallback = function() {
@@ -77140,8 +77421,8 @@ var init_node4 = __esm({
77140
77421
  }
77141
77422
  let onopenResolve = () => {
77142
77423
  };
77143
- const onopenPromise = new Promise((resolve35) => {
77144
- onopenResolve = resolve35;
77424
+ const onopenPromise = new Promise((resolve37) => {
77425
+ onopenResolve = resolve37;
77145
77426
  });
77146
77427
  const callbacks = params.callbacks;
77147
77428
  const onopenAwaitedCallback = function() {
@@ -79450,7 +79731,7 @@ var init_node4 = __esm({
79450
79731
  return void 0;
79451
79732
  }
79452
79733
  };
79453
- sleep$1 = (ms) => new Promise((resolve35) => setTimeout(resolve35, ms));
79734
+ sleep$1 = (ms) => new Promise((resolve37) => setTimeout(resolve37, ms));
79454
79735
  FallbackEncoder = ({ headers, body }) => {
79455
79736
  return {
79456
79737
  bodyHeaders: {
@@ -79959,8 +80240,8 @@ ${underline2}`);
79959
80240
  };
79960
80241
  APIPromise = class _APIPromise extends Promise {
79961
80242
  constructor(client, responsePromise, parseResponse = defaultParseResponse) {
79962
- super((resolve35) => {
79963
- resolve35(null);
80243
+ super((resolve37) => {
80244
+ resolve37(null);
79964
80245
  });
79965
80246
  this.responsePromise = responsePromise;
79966
80247
  this.parseResponse = parseResponse;
@@ -81201,8 +81482,8 @@ ${underline2}`);
81201
81482
  });
81202
81483
 
81203
81484
  // src/capture/contentExtractor.ts
81204
- import { readdirSync as readdirSync16, statSync as statSync17, readFileSync as readFileSync34 } from "fs";
81205
- import { join as join48 } from "path";
81485
+ import { readdirSync as readdirSync17, statSync as statSync18, readFileSync as readFileSync35 } from "fs";
81486
+ import { join as join50 } from "path";
81206
81487
  async function detectLibraries(page, capturedShaders) {
81207
81488
  let detectedLibraries = [];
81208
81489
  try {
@@ -81322,7 +81603,7 @@ async function captionImagesWithGemini(outputDir, progress, warnings) {
81322
81603
  try {
81323
81604
  const { GoogleGenAI: GoogleGenAI2 } = await Promise.resolve().then(() => (init_node4(), node_exports));
81324
81605
  const ai = new GoogleGenAI2({ apiKey: geminiKey });
81325
- const imageFiles = readdirSync16(join48(outputDir, "assets")).filter(
81606
+ const imageFiles = readdirSync17(join50(outputDir, "assets")).filter(
81326
81607
  (f3) => /\.(png|jpg|jpeg|webp|gif)$/i.test(f3)
81327
81608
  );
81328
81609
  const model = process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
@@ -81331,10 +81612,10 @@ async function captionImagesWithGemini(outputDir, progress, warnings) {
81331
81612
  const batch = imageFiles.slice(i2, i2 + BATCH_SIZE);
81332
81613
  const results = await Promise.allSettled(
81333
81614
  batch.map(async (file) => {
81334
- const filePath = join48(outputDir, "assets", file);
81335
- const stat3 = statSync17(filePath);
81615
+ const filePath = join50(outputDir, "assets", file);
81616
+ const stat3 = statSync18(filePath);
81336
81617
  if (stat3.size > 4e6) return { file, caption: "" };
81337
- const buffer = readFileSync34(filePath);
81618
+ const buffer = readFileSync35(filePath);
81338
81619
  const base64 = buffer.toString("base64");
81339
81620
  const ext = file.split(".").pop()?.toLowerCase() || "png";
81340
81621
  const mimeType = ext === "jpg" ? "image/jpeg" : `image/${ext}`;
@@ -81380,12 +81661,12 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
81380
81661
  const uncaptionedLines = [];
81381
81662
  const svgLines = [];
81382
81663
  const fontLines = [];
81383
- const assetsPath = join48(outputDir, "assets");
81664
+ const assetsPath = join50(outputDir, "assets");
81384
81665
  try {
81385
- for (const file of readdirSync16(assetsPath)) {
81666
+ for (const file of readdirSync17(assetsPath)) {
81386
81667
  if (file === "svgs" || file === "fonts" || file === "lottie" || file === "videos") continue;
81387
- const filePath = join48(assetsPath, file);
81388
- const stat3 = statSync17(filePath);
81668
+ const filePath = join50(assetsPath, file);
81669
+ const stat3 = statSync18(filePath);
81389
81670
  if (!stat3.isFile()) continue;
81390
81671
  const sizeKb = Math.round(stat3.size / 1024);
81391
81672
  const catalogMatch = catalogedAssets.find(
@@ -81413,8 +81694,8 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
81413
81694
  } catch {
81414
81695
  }
81415
81696
  try {
81416
- const svgsPath = join48(assetsPath, "svgs");
81417
- for (const file of readdirSync16(svgsPath)) {
81697
+ const svgsPath = join50(assetsPath, "svgs");
81698
+ for (const file of readdirSync17(svgsPath)) {
81418
81699
  if (!file.endsWith(".svg")) continue;
81419
81700
  const svgMatch = tokens.svgs.find(
81420
81701
  (s2) => s2.label && file.includes(
@@ -81428,8 +81709,8 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
81428
81709
  } catch {
81429
81710
  }
81430
81711
  try {
81431
- const fontsPath = join48(assetsPath, "fonts");
81432
- for (const file of readdirSync16(fontsPath)) {
81712
+ const fontsPath = join50(assetsPath, "fonts");
81713
+ for (const file of readdirSync17(fontsPath)) {
81433
81714
  fontLines.push(`fonts/${file} \u2014 font file`);
81434
81715
  }
81435
81716
  } catch {
@@ -81448,12 +81729,12 @@ __export(agentPromptGenerator_exports, {
81448
81729
  generateAgentPrompt: () => generateAgentPrompt
81449
81730
  });
81450
81731
  import { writeFileSync as writeFileSync19 } from "fs";
81451
- import { join as join49 } from "path";
81732
+ import { join as join51 } from "path";
81452
81733
  function generateAgentPrompt(outputDir, url, tokens, _animations, hasScreenshot, hasLottie, hasShaders, _catalogedAssets, detectedLibraries) {
81453
81734
  const prompt = buildPrompt(url, tokens, hasScreenshot, hasLottie, hasShaders, detectedLibraries);
81454
- writeFileSync19(join49(outputDir, "AGENTS.md"), prompt, "utf-8");
81455
- writeFileSync19(join49(outputDir, "CLAUDE.md"), prompt, "utf-8");
81456
- writeFileSync19(join49(outputDir, ".cursorrules"), prompt, "utf-8");
81735
+ writeFileSync19(join51(outputDir, "AGENTS.md"), prompt, "utf-8");
81736
+ writeFileSync19(join51(outputDir, "CLAUDE.md"), prompt, "utf-8");
81737
+ writeFileSync19(join51(outputDir, ".cursorrules"), prompt, "utf-8");
81457
81738
  }
81458
81739
  function buildPrompt(url, tokens, hasScreenshot, hasLottie, hasShaders, detectedLibraries) {
81459
81740
  const title = tokens.title || new URL(url).hostname.replace(/^www\./, "");
@@ -81520,15 +81801,15 @@ var init_agentPromptGenerator = __esm({
81520
81801
  });
81521
81802
 
81522
81803
  // src/capture/scaffolding.ts
81523
- import { existsSync as existsSync46, writeFileSync as writeFileSync20, readFileSync as readFileSync35 } from "fs";
81524
- import { join as join50, resolve as resolve33 } from "path";
81804
+ import { existsSync as existsSync47, writeFileSync as writeFileSync20, readFileSync as readFileSync36 } from "fs";
81805
+ import { join as join52, resolve as resolve35 } from "path";
81525
81806
  function loadEnvFile(startDir) {
81526
81807
  try {
81527
- let dir = resolve33(startDir);
81808
+ let dir = resolve35(startDir);
81528
81809
  for (let i2 = 0; i2 < 5; i2++) {
81529
- const envPath = resolve33(dir, ".env");
81810
+ const envPath = resolve35(dir, ".env");
81530
81811
  try {
81531
- const envContent = readFileSync35(envPath, "utf-8");
81812
+ const envContent = readFileSync36(envPath, "utf-8");
81532
81813
  for (const line of envContent.split("\n")) {
81533
81814
  const trimmed = line.trim();
81534
81815
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -81540,15 +81821,15 @@ function loadEnvFile(startDir) {
81540
81821
  }
81541
81822
  break;
81542
81823
  } catch {
81543
- dir = resolve33(dir, "..");
81824
+ dir = resolve35(dir, "..");
81544
81825
  }
81545
81826
  }
81546
81827
  } catch {
81547
81828
  }
81548
81829
  }
81549
81830
  async function generateProjectScaffold(outputDir, url, tokens, animationCatalog, hasScreenshots, hasLotties, hasShaders, catalogedAssets, progress, warnings, detectedLibraries) {
81550
- const metaPath = join50(outputDir, "meta.json");
81551
- if (!existsSync46(metaPath)) {
81831
+ const metaPath = join52(outputDir, "meta.json");
81832
+ if (!existsSync47(metaPath)) {
81552
81833
  const hostname = new URL(url).hostname.replace(/^www\./, "");
81553
81834
  writeFileSync20(
81554
81835
  metaPath,
@@ -81586,9 +81867,9 @@ __export(screenshotCapture_exports, {
81586
81867
  captureScrollScreenshots: () => captureScrollScreenshots
81587
81868
  });
81588
81869
  import { writeFileSync as writeFileSync21, mkdirSync as mkdirSync27 } from "fs";
81589
- import { join as join51 } from "path";
81870
+ import { join as join53 } from "path";
81590
81871
  async function captureScrollScreenshots(page, outputDir) {
81591
- const screenshotsDir = join51(outputDir, "screenshots");
81872
+ const screenshotsDir = join53(outputDir, "screenshots");
81592
81873
  mkdirSync27(screenshotsDir, { recursive: true });
81593
81874
  const MAX_SCREENSHOTS = 20;
81594
81875
  const filePaths = [];
@@ -81622,7 +81903,7 @@ async function captureScrollScreenshots(page, outputDir) {
81622
81903
  finalPositions[i2] / Math.max(1, scrollHeight - viewportHeight) * 100
81623
81904
  );
81624
81905
  const filename = `scroll-${String(Math.min(pct, 100)).padStart(3, "0")}.png`;
81625
- const filePath = join51(screenshotsDir, filename);
81906
+ const filePath = join53(screenshotsDir, filename);
81626
81907
  const buffer = await page.screenshot({ type: "png" });
81627
81908
  writeFileSync21(filePath, buffer);
81628
81909
  filePaths.push(`screenshots/${filename}`);
@@ -81935,8 +82216,8 @@ var capture_exports = {};
81935
82216
  __export(capture_exports, {
81936
82217
  captureWebsite: () => captureWebsite
81937
82218
  });
81938
- import { mkdirSync as mkdirSync28, writeFileSync as writeFileSync22, existsSync as existsSync47 } from "fs";
81939
- import { join as join52 } from "path";
82219
+ import { mkdirSync as mkdirSync28, writeFileSync as writeFileSync22, existsSync as existsSync48 } from "fs";
82220
+ import { join as join54 } from "path";
81940
82221
  async function captureWebsite(opts, onProgress) {
81941
82222
  const {
81942
82223
  url,
@@ -81953,9 +82234,9 @@ async function captureWebsite(opts, onProgress) {
81953
82234
  onProgress?.(stage, detail);
81954
82235
  };
81955
82236
  loadEnvFile(outputDir);
81956
- mkdirSync28(join52(outputDir, "extracted"), { recursive: true });
81957
- mkdirSync28(join52(outputDir, "screenshots"), { recursive: true });
81958
- mkdirSync28(join52(outputDir, "assets"), { recursive: true });
82237
+ mkdirSync28(join54(outputDir, "extracted"), { recursive: true });
82238
+ mkdirSync28(join54(outputDir, "screenshots"), { recursive: true });
82239
+ mkdirSync28(join54(outputDir, "assets"), { recursive: true });
81959
82240
  progress("browser", "Launching headless Chrome...");
81960
82241
  const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
81961
82242
  const browser = await ensureBrowser2();
@@ -82111,7 +82392,7 @@ async function captureWebsite(opts, onProgress) {
82111
82392
  } catch {
82112
82393
  }
82113
82394
  if (discoveredLotties.length > 0) {
82114
- const lottieDir = join52(outputDir, "assets", "lottie");
82395
+ const lottieDir = join54(outputDir, "assets", "lottie");
82115
82396
  mkdirSync28(lottieDir, { recursive: true });
82116
82397
  const savedCount = await saveLottieAnimations(discoveredLotties, lottieDir);
82117
82398
  if (savedCount > 0) {
@@ -82131,7 +82412,7 @@ async function captureWebsite(opts, onProgress) {
82131
82412
  });
82132
82413
  capturedShaders = unique;
82133
82414
  writeFileSync22(
82134
- join52(outputDir, "extracted", "shaders.json"),
82415
+ join54(outputDir, "extracted", "shaders.json"),
82135
82416
  JSON.stringify(unique, null, 2),
82136
82417
  "utf-8"
82137
82418
  );
@@ -82142,7 +82423,7 @@ async function captureWebsite(opts, onProgress) {
82142
82423
  progress("tokens", "Extracting design tokens...");
82143
82424
  const tokens = await extractTokens(page1);
82144
82425
  writeFileSync22(
82145
- join52(outputDir, "extracted", "tokens.json"),
82426
+ join54(outputDir, "extracted", "tokens.json"),
82146
82427
  JSON.stringify(tokens, null, 2),
82147
82428
  "utf-8"
82148
82429
  );
@@ -82216,7 +82497,7 @@ async function captureWebsite(opts, onProgress) {
82216
82497
  representativeAnimations: representativeAnims
82217
82498
  };
82218
82499
  writeFileSync22(
82219
- join52(outputDir, "extracted", "animations.json"),
82500
+ join54(outputDir, "extracted", "animations.json"),
82220
82501
  JSON.stringify(leanCatalog, null, 2),
82221
82502
  "utf-8"
82222
82503
  );
@@ -82227,18 +82508,18 @@ async function captureWebsite(opts, onProgress) {
82227
82508
  assets = await downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks);
82228
82509
  }
82229
82510
  if (visibleTextContent) {
82230
- writeFileSync22(join52(outputDir, "extracted", "visible-text.txt"), visibleTextContent, "utf-8");
82511
+ writeFileSync22(join54(outputDir, "extracted", "visible-text.txt"), visibleTextContent, "utf-8");
82231
82512
  }
82232
82513
  if (catalogedAssets.length > 0) {
82233
82514
  writeFileSync22(
82234
- join52(outputDir, "extracted", "assets-catalog.json"),
82515
+ join54(outputDir, "extracted", "assets-catalog.json"),
82235
82516
  JSON.stringify(catalogedAssets, null, 2),
82236
82517
  "utf-8"
82237
82518
  );
82238
82519
  }
82239
82520
  if (detectedLibraries.length > 0) {
82240
82521
  writeFileSync22(
82241
- join52(outputDir, "extracted", "detected-libraries.json"),
82522
+ join54(outputDir, "extracted", "detected-libraries.json"),
82242
82523
  JSON.stringify(detectedLibraries, null, 2),
82243
82524
  "utf-8"
82244
82525
  );
@@ -82249,7 +82530,7 @@ async function captureWebsite(opts, onProgress) {
82249
82530
  const lines = generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCaptions);
82250
82531
  if (lines.length > 0) {
82251
82532
  writeFileSync22(
82252
- join52(outputDir, "extracted", "asset-descriptions.md"),
82533
+ join54(outputDir, "extracted", "asset-descriptions.md"),
82253
82534
  "# Asset Descriptions\n\nOne line per file. Read this instead of opening every image individually.\n\n" + lines.map((l) => "- " + l).join("\n") + "\n",
82254
82535
  "utf-8"
82255
82536
  );
@@ -82265,7 +82546,7 @@ async function captureWebsite(opts, onProgress) {
82265
82546
  animationCatalog,
82266
82547
  screenshots.length > 0,
82267
82548
  discoveredLotties.length > 0,
82268
- existsSync47(join52(outputDir, "extracted", "shaders.json")),
82549
+ existsSync48(join54(outputDir, "extracted", "shaders.json")),
82269
82550
  catalogedAssets,
82270
82551
  progress,
82271
82552
  warnings,
@@ -82305,15 +82586,15 @@ var init_capture = __esm({
82305
82586
  var capture_exports2 = {};
82306
82587
  __export(capture_exports2, {
82307
82588
  default: () => capture_default,
82308
- examples: () => examples19
82589
+ examples: () => examples20
82309
82590
  });
82310
- import { resolve as resolve34 } from "path";
82311
- var examples19, capture_default;
82591
+ import { resolve as resolve36 } from "path";
82592
+ var examples20, capture_default;
82312
82593
  var init_capture2 = __esm({
82313
82594
  "src/commands/capture.ts"() {
82314
82595
  "use strict";
82315
82596
  init_dist();
82316
- examples19 = [
82597
+ examples20 = [
82317
82598
  ["Capture a website", "hyperframes capture https://stripe.com"],
82318
82599
  ["Capture to a specific directory", "hyperframes capture https://linear.app -o linear-video"],
82319
82600
  ["JSON output for AI agents", "hyperframes capture https://example.com --json"]
@@ -82366,7 +82647,7 @@ var init_capture2 = __esm({
82366
82647
  const hostname = new URL(url).hostname.replace(/^www\./, "");
82367
82648
  outputName = `captures/${hostname.replace(/\./g, "-")}`;
82368
82649
  }
82369
- const outputDir = resolve34(outputName);
82650
+ const outputDir = resolve36(outputName);
82370
82651
  const isJson = args.json;
82371
82652
  if (!isJson) {
82372
82653
  const { c: c2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
@@ -82513,13 +82794,13 @@ var init_telemetry2 = __esm({
82513
82794
  });
82514
82795
 
82515
82796
  // src/utils/installerDetection.ts
82516
- import { realpathSync } from "fs";
82797
+ import { realpathSync as realpathSync2 } from "fs";
82517
82798
  import { posix as posix2 } from "path";
82518
82799
  function resolveEntry() {
82519
82800
  const entry = process.argv[1];
82520
82801
  if (!entry) return null;
82521
82802
  try {
82522
- return realpathSync(entry);
82803
+ return realpathSync2(entry);
82523
82804
  } catch {
82524
82805
  return entry;
82525
82806
  }
@@ -82614,7 +82895,7 @@ __export(autoUpdate_exports, {
82614
82895
  import { spawn as spawn12 } from "child_process";
82615
82896
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync29, openSync } from "fs";
82616
82897
  import { homedir as homedir10 } from "os";
82617
- import { join as join53 } from "path";
82898
+ import { join as join55 } from "path";
82618
82899
  import { compareVersions as compareVersions2 } from "compare-versions";
82619
82900
  function isAutoInstallDisabled() {
82620
82901
  if (isDevMode()) return true;
@@ -82637,7 +82918,7 @@ function log(line) {
82637
82918
  }
82638
82919
  function launchDetachedInstall(installCommand, version) {
82639
82920
  mkdirSync29(CONFIG_DIR2, { recursive: true, mode: 448 });
82640
- const configFile = join53(CONFIG_DIR2, "config.json");
82921
+ const configFile = join55(CONFIG_DIR2, "config.json");
82641
82922
  const nodeScript = `
82642
82923
  const { exec } = require("node:child_process");
82643
82924
  const { readFileSync, renameSync, writeFileSync } = require("node:fs");
@@ -82756,8 +83037,8 @@ var init_autoUpdate = __esm({
82756
83037
  init_config();
82757
83038
  init_env();
82758
83039
  init_installerDetection();
82759
- CONFIG_DIR2 = join53(homedir10(), ".hyperframes");
82760
- LOG_FILE = join53(CONFIG_DIR2, "auto-update.log");
83040
+ CONFIG_DIR2 = join55(homedir10(), ".hyperframes");
83041
+ LOG_FILE = join55(CONFIG_DIR2, "auto-update.log");
82761
83042
  PENDING_TIMEOUT_MS = 10 * 60 * 1e3;
82762
83043
  }
82763
83044
  });
@@ -82810,10 +83091,10 @@ function renderRootHelp() {
82810
83091
  lines.push(`Run ${c.cyan("hyperframes <command> --help")} for more information about a command.`);
82811
83092
  return lines.join("\n");
82812
83093
  }
82813
- function formatExamples(examples20) {
83094
+ function formatExamples(examples21) {
82814
83095
  const lines = [];
82815
83096
  lines.push(c.bold("Examples:"));
82816
- for (const [comment, command2] of examples20) {
83097
+ for (const [comment, command2] of examples21) {
82817
83098
  lines.push(` ${c.gray(`# ${comment}`)}`);
82818
83099
  lines.push(` ${command2}`);
82819
83100
  lines.push("");
@@ -82830,9 +83111,9 @@ async function showUsage2(cmd, parent) {
82830
83111
  console.log(usage + "\n");
82831
83112
  const name = meta?.name;
82832
83113
  if (name) {
82833
- const examples20 = STATIC_EXAMPLES[name] ?? await loadExamples(name);
82834
- if (examples20) {
82835
- console.log(formatExamples(examples20) + "\n");
83114
+ const examples21 = STATIC_EXAMPLES[name] ?? await loadExamples(name);
83115
+ if (examples21) {
83116
+ console.log(formatExamples(examples21) + "\n");
82836
83117
  }
82837
83118
  }
82838
83119
  }
@@ -82853,6 +83134,7 @@ var init_help = __esm({
82853
83134
  ["capture", "Capture a website for video production"],
82854
83135
  ["catalog", "Browse and install blocks and components"],
82855
83136
  ["preview", "Start the studio for previewing compositions"],
83137
+ ["publish", "Upload a project and get a stable public URL"],
82856
83138
  ["render", "Render a composition to MP4 or WebM"]
82857
83139
  ]
82858
83140
  },
@@ -82897,6 +83179,7 @@ var init_help = __esm({
82897
83179
  ROOT_EXAMPLES = [
82898
83180
  ["Create a new project", "hyperframes init my-video"],
82899
83181
  ["Start the live preview studio", "hyperframes preview"],
83182
+ ["Publish to hyperframes.dev", "hyperframes publish"],
82900
83183
  ["Render to MP4", "hyperframes render -o out.mp4"],
82901
83184
  ["Transparent WebM overlay", "hyperframes render --format webm -o out.webm"],
82902
83185
  ["Validate your composition", "hyperframes lint"],
@@ -82922,6 +83205,7 @@ var subCommands = {
82922
83205
  catalog: () => Promise.resolve().then(() => (init_catalog(), catalog_exports)).then((m2) => m2.default),
82923
83206
  play: () => Promise.resolve().then(() => (init_play(), play_exports)).then((m2) => m2.default),
82924
83207
  preview: () => Promise.resolve().then(() => (init_preview2(), preview_exports)).then((m2) => m2.default),
83208
+ publish: () => Promise.resolve().then(() => (init_publish(), publish_exports)).then((m2) => m2.default),
82925
83209
  render: () => Promise.resolve().then(() => (init_render2(), render_exports)).then((m2) => m2.default),
82926
83210
  lint: () => Promise.resolve().then(() => (init_lint3(), lint_exports2)).then((m2) => m2.default),
82927
83211
  info: () => Promise.resolve().then(() => (init_info(), info_exports)).then((m2) => m2.default),