hyperframes 0.4.20 → 0.4.21

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.
Files changed (2) hide show
  1. package/dist/cli.js +1077 -528
  2. package/package.json +1 -1
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.20" : "0.0.0-dev";
57
+ VERSION = true ? "0.4.21" : "0.0.0-dev";
58
58
  }
59
59
  });
60
60
 
@@ -4801,6 +4801,7 @@ function extractGsapWindows(script) {
4801
4801
  position: animation.position,
4802
4802
  end: animation.position + meta.effectiveDuration,
4803
4803
  properties: meta.properties.length > 0 ? meta.properties : Object.keys(animation.properties),
4804
+ propertyValues: meta.propertyValues,
4804
4805
  overwriteAuto: meta.overwriteAuto,
4805
4806
  method: match[1] ?? "to",
4806
4807
  raw
@@ -4809,8 +4810,14 @@ function extractGsapWindows(script) {
4809
4810
  return windows;
4810
4811
  }
4811
4812
  function parseGsapWindowMeta(method, argsStr) {
4813
+ const emptyMeta = {
4814
+ effectiveDuration: 0,
4815
+ properties: [],
4816
+ propertyValues: {},
4817
+ overwriteAuto: false
4818
+ };
4812
4819
  const selectorMatch = argsStr.match(/^\s*["']([^"']+)["']\s*,/);
4813
- if (!selectorMatch) return { effectiveDuration: 0, properties: [], overwriteAuto: false };
4820
+ if (!selectorMatch) return emptyMeta;
4814
4821
  const afterSelector = argsStr.slice(selectorMatch[0].length);
4815
4822
  let properties = {};
4816
4823
  let fromProperties = {};
@@ -4848,6 +4855,7 @@ function parseGsapWindowMeta(method, argsStr) {
4848
4855
  return {
4849
4856
  effectiveDuration: method === "set" ? 0 : effectiveDuration,
4850
4857
  properties: [...propertyNames],
4858
+ propertyValues: properties,
4851
4859
  overwriteAuto
4852
4860
  };
4853
4861
  }
@@ -4895,6 +4903,94 @@ function stringValue(value) {
4895
4903
  if (typeof value === "number") return String(value);
4896
4904
  return null;
4897
4905
  }
4906
+ function zeroValue(value) {
4907
+ if (typeof value === "number") return value === 0;
4908
+ if (typeof value !== "string") return false;
4909
+ return Number(value.trim()) === 0;
4910
+ }
4911
+ function isHiddenGsapState(values) {
4912
+ const visibility = stringValue(values.visibility)?.toLowerCase();
4913
+ const display = stringValue(values.display)?.toLowerCase();
4914
+ return zeroValue(values.opacity) || zeroValue(values.autoAlpha) || visibility === "hidden" || display === "none";
4915
+ }
4916
+ function isSceneBoundaryExit(win) {
4917
+ if (win.end <= win.position) return false;
4918
+ if (win.method !== "to" && win.method !== "fromTo") return false;
4919
+ return isHiddenGsapState(win.propertyValues);
4920
+ }
4921
+ function isHardKillSet(win, selector, boundary) {
4922
+ return win.method === "set" && win.targetSelector === selector && Math.abs(win.position - boundary) <= SCENE_BOUNDARY_EPSILON_SECONDS && isHiddenGsapState(win.propertyValues);
4923
+ }
4924
+ function hiddenStateLiteral(values) {
4925
+ if (zeroValue(values.autoAlpha)) return "{ autoAlpha: 0 }";
4926
+ if (zeroValue(values.opacity)) return "{ opacity: 0 }";
4927
+ if (stringValue(values.visibility)?.toLowerCase() === "hidden") return '{ visibility: "hidden" }';
4928
+ if (stringValue(values.display)?.toLowerCase() === "none") return '{ display: "none" }';
4929
+ return "{ opacity: 0 }";
4930
+ }
4931
+ function findTagEnd(source, tag) {
4932
+ const escapedTagName = tag.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4933
+ const pattern = new RegExp(`<\\/?${escapedTagName}\\b[^>]*>`, "gi");
4934
+ pattern.lastIndex = tag.index;
4935
+ let depth = 0;
4936
+ let match;
4937
+ while ((match = pattern.exec(source)) !== null) {
4938
+ const raw = match[0];
4939
+ const isClosing = /^<\s*\//.test(raw);
4940
+ const isSelfClosing = /\/\s*>$/.test(raw);
4941
+ if (!isClosing && !isSelfClosing) depth += 1;
4942
+ if (isClosing) depth -= 1;
4943
+ if (depth === 0) return pattern.lastIndex;
4944
+ }
4945
+ return source.length;
4946
+ }
4947
+ function collectCompositionRanges(source, tags) {
4948
+ return tags.map((tag) => {
4949
+ const id = readAttr(tag.raw, "data-composition-id");
4950
+ if (!id) return null;
4951
+ return {
4952
+ id,
4953
+ start: tag.index,
4954
+ end: findTagEnd(source, tag)
4955
+ };
4956
+ }).filter((range) => range !== null);
4957
+ }
4958
+ function findContainingCompositionId(tag, ranges) {
4959
+ let match = null;
4960
+ for (const range of ranges) {
4961
+ if (tag.index < range.start || tag.index >= range.end) continue;
4962
+ if (!match || range.start >= match.start) match = range;
4963
+ }
4964
+ return match?.id || null;
4965
+ }
4966
+ function collectClipStartBoundariesByComposition(source, tags) {
4967
+ const ranges = collectCompositionRanges(source, tags);
4968
+ const boundaries = /* @__PURE__ */ new Map();
4969
+ for (const tag of tags) {
4970
+ const classAttr = readAttr(tag.raw, "class") || "";
4971
+ const classes = classAttr.split(/\s+/).filter(Boolean);
4972
+ if (!classes.includes("clip")) continue;
4973
+ const compositionId = findContainingCompositionId(tag, ranges);
4974
+ if (!compositionId) continue;
4975
+ const start = numberValue(readAttr(tag.raw, "data-start") ?? void 0);
4976
+ if (start == null || start <= 0) continue;
4977
+ const compositionBoundaries = boundaries.get(compositionId) ?? /* @__PURE__ */ new Set();
4978
+ compositionBoundaries.add(start);
4979
+ boundaries.set(compositionId, compositionBoundaries);
4980
+ }
4981
+ return new Map(
4982
+ [...boundaries.entries()].map(([compositionId, values]) => [
4983
+ compositionId,
4984
+ [...values].sort((a, b) => a - b)
4985
+ ])
4986
+ );
4987
+ }
4988
+ function findMatchingSceneBoundary(time, boundaries) {
4989
+ for (const boundary of boundaries) {
4990
+ if (Math.abs(time - boundary) <= SCENE_BOUNDARY_EPSILON_SECONDS) return boundary;
4991
+ }
4992
+ return null;
4993
+ }
4898
4994
  function isSuspiciousGlobalSelector(selector) {
4899
4995
  if (!selector) return false;
4900
4996
  if (selector.includes("[data-composition-id=")) return false;
@@ -4933,16 +5029,17 @@ function cssTransformToGsapProps(cssTransform) {
4933
5029
  }
4934
5030
  return parts.length > 0 ? parts.join(", ") : null;
4935
5031
  }
4936
- var META_GSAP_KEYS, gsapRules;
5032
+ var META_GSAP_KEYS, SCENE_BOUNDARY_EPSILON_SECONDS, gsapRules;
4937
5033
  var init_gsap = __esm({
4938
5034
  "../core/src/lint/rules/gsap.ts"() {
4939
5035
  "use strict";
4940
5036
  init_gsapParser();
4941
5037
  init_utils();
4942
5038
  META_GSAP_KEYS = /* @__PURE__ */ new Set(["duration", "ease", "repeat", "yoyo", "overwrite", "delay"]);
5039
+ SCENE_BOUNDARY_EPSILON_SECONDS = 0.05;
4943
5040
  gsapRules = [
4944
5041
  // overlapping_gsap_tweens + gsap_animates_clip_element + unscoped_gsap_selector
4945
- ({ tags, scripts, rootCompositionId }) => {
5042
+ ({ source, tags, scripts, rootCompositionId }) => {
4946
5043
  const findings = [];
4947
5044
  const clipIds = /* @__PURE__ */ new Map();
4948
5045
  const clipClasses = /* @__PURE__ */ new Map();
@@ -4962,9 +5059,11 @@ var init_gsap = __esm({
4962
5059
  }
4963
5060
  }
4964
5061
  const classUsage = countClassUsage(tags);
5062
+ const clipStartBoundariesByComposition = collectClipStartBoundariesByComposition(source, tags);
4965
5063
  for (const script of scripts) {
4966
5064
  const localTimelineCompId = readRegisteredTimelineCompositionId(script.content);
4967
5065
  const gsapWindows = extractGsapWindows(script.content);
5066
+ const clipStartBoundaries = clipStartBoundariesByComposition.get(localTimelineCompId || rootCompositionId || "") ?? [];
4968
5067
  for (let i2 = 0; i2 < gsapWindows.length; i2++) {
4969
5068
  const left = gsapWindows[i2];
4970
5069
  if (!left) continue;
@@ -4993,6 +5092,25 @@ ${right.raw}`)
4993
5092
  });
4994
5093
  }
4995
5094
  }
5095
+ if (clipStartBoundaries.length > 0) {
5096
+ for (const win of gsapWindows) {
5097
+ if (!isSceneBoundaryExit(win)) continue;
5098
+ const boundary = findMatchingSceneBoundary(win.end, clipStartBoundaries);
5099
+ if (boundary == null) continue;
5100
+ const hasHardKill = gsapWindows.some(
5101
+ (candidate) => isHardKillSet(candidate, win.targetSelector, boundary)
5102
+ );
5103
+ if (hasHardKill) continue;
5104
+ findings.push({
5105
+ code: "gsap_exit_missing_hard_kill",
5106
+ severity: "warning",
5107
+ message: `GSAP exit on "${win.targetSelector}" ends at the ${boundary.toFixed(2)}s clip start boundary without a matching tl.set hard kill. Non-linear seeking can land after the fade and leave stale visibility state.`,
5108
+ selector: win.targetSelector,
5109
+ fixHint: `Add \`tl.set("${win.targetSelector}", ${hiddenStateLiteral(win.propertyValues)}, ${boundary.toFixed(2)})\` after the exit tween.`,
5110
+ snippet: truncateSnippet(win.raw)
5111
+ });
5112
+ }
5113
+ }
4996
5114
  for (const win of gsapWindows) {
4997
5115
  const sel = win.targetSelector;
4998
5116
  const clipInfo = clipIds.get(sel) || clipClasses.get(sel);
@@ -8231,22 +8349,22 @@ function getMeasureContext() {
8231
8349
  throw new Error("Text measurement requires OffscreenCanvas or a DOM canvas context.");
8232
8350
  }
8233
8351
  function getSegmentMetricCache(font) {
8234
- let cache = segmentMetricCaches.get(font);
8235
- if (!cache) {
8236
- cache = /* @__PURE__ */ new Map();
8237
- segmentMetricCaches.set(font, cache);
8352
+ let cache2 = segmentMetricCaches.get(font);
8353
+ if (!cache2) {
8354
+ cache2 = /* @__PURE__ */ new Map();
8355
+ segmentMetricCaches.set(font, cache2);
8238
8356
  }
8239
- return cache;
8357
+ return cache2;
8240
8358
  }
8241
- function getSegmentMetrics(seg, cache) {
8242
- let metrics = cache.get(seg);
8359
+ function getSegmentMetrics(seg, cache2) {
8360
+ let metrics = cache2.get(seg);
8243
8361
  if (metrics === void 0) {
8244
8362
  const ctx = getMeasureContext();
8245
8363
  metrics = {
8246
8364
  width: ctx.measureText(seg).width,
8247
8365
  containsCJK: isCJK(seg)
8248
8366
  };
8249
- cache.set(seg, metrics);
8367
+ cache2.set(seg, metrics);
8250
8368
  }
8251
8369
  return metrics;
8252
8370
  }
@@ -8335,7 +8453,7 @@ function getCorrectedSegmentWidth(seg, metrics, emojiCorrection) {
8335
8453
  return metrics.width;
8336
8454
  return metrics.width - getEmojiCount(seg, metrics) * emojiCorrection;
8337
8455
  }
8338
- function getSegmentBreakableFitAdvances(seg, metrics, cache, emojiCorrection, mode) {
8456
+ function getSegmentBreakableFitAdvances(seg, metrics, cache2, emojiCorrection, mode) {
8339
8457
  if (metrics.breakableFitAdvances !== void 0)
8340
8458
  return metrics.breakableFitAdvances;
8341
8459
  const graphemeSegmenter = getSharedGraphemeSegmenter();
@@ -8350,7 +8468,7 @@ function getSegmentBreakableFitAdvances(seg, metrics, cache, emojiCorrection, mo
8350
8468
  if (mode === "sum-graphemes") {
8351
8469
  const advances2 = [];
8352
8470
  for (const grapheme of graphemes) {
8353
- const graphemeMetrics = getSegmentMetrics(grapheme, cache);
8471
+ const graphemeMetrics = getSegmentMetrics(grapheme, cache2);
8354
8472
  advances2.push(getCorrectedSegmentWidth(grapheme, graphemeMetrics, emojiCorrection));
8355
8473
  }
8356
8474
  metrics.breakableFitAdvances = advances2;
@@ -8361,13 +8479,13 @@ function getSegmentBreakableFitAdvances(seg, metrics, cache, emojiCorrection, mo
8361
8479
  let previousGrapheme = null;
8362
8480
  let previousWidth = 0;
8363
8481
  for (const grapheme of graphemes) {
8364
- const graphemeMetrics = getSegmentMetrics(grapheme, cache);
8482
+ const graphemeMetrics = getSegmentMetrics(grapheme, cache2);
8365
8483
  const currentWidth = getCorrectedSegmentWidth(grapheme, graphemeMetrics, emojiCorrection);
8366
8484
  if (previousGrapheme === null) {
8367
8485
  advances2.push(currentWidth);
8368
8486
  } else {
8369
8487
  const pair = previousGrapheme + grapheme;
8370
- const pairMetrics = getSegmentMetrics(pair, cache);
8488
+ const pairMetrics = getSegmentMetrics(pair, cache2);
8371
8489
  advances2.push(getCorrectedSegmentWidth(pair, pairMetrics, emojiCorrection) - previousWidth);
8372
8490
  }
8373
8491
  previousGrapheme = grapheme;
@@ -8381,7 +8499,7 @@ function getSegmentBreakableFitAdvances(seg, metrics, cache, emojiCorrection, mo
8381
8499
  let prefixWidth = 0;
8382
8500
  for (const grapheme of graphemes) {
8383
8501
  prefix += grapheme;
8384
- const prefixMetrics = getSegmentMetrics(prefix, cache);
8502
+ const prefixMetrics = getSegmentMetrics(prefix, cache2);
8385
8503
  const nextPrefixWidth = getCorrectedSegmentWidth(prefix, prefixMetrics, emojiCorrection);
8386
8504
  advances.push(nextPrefixWidth - prefixWidth);
8387
8505
  prefixWidth = nextPrefixWidth;
@@ -8392,10 +8510,10 @@ function getSegmentBreakableFitAdvances(seg, metrics, cache, emojiCorrection, mo
8392
8510
  function getFontMeasurementState(font, needsEmojiCorrection) {
8393
8511
  const ctx = getMeasureContext();
8394
8512
  ctx.font = font;
8395
- const cache = getSegmentMetricCache(font);
8513
+ const cache2 = getSegmentMetricCache(font);
8396
8514
  const fontSize = parseFontSize(font);
8397
8515
  const emojiCorrection = needsEmojiCorrection ? getEmojiCorrection(font, fontSize) : 0;
8398
- return { cache, fontSize, emojiCorrection };
8516
+ return { cache: cache2, fontSize, emojiCorrection };
8399
8517
  }
8400
8518
  var measureContext, segmentMetricCaches, cachedEngineProfile, MAX_PREFIX_FIT_GRAPHEMES, emojiPresentationRe, maybeEmojiRe, sharedGraphemeSegmenter, emojiCorrectionCache;
8401
8519
  var init_measurement = __esm({
@@ -8939,9 +9057,9 @@ function mergeKeepAllTextUnits(units) {
8939
9057
  }
8940
9058
  function measureAnalysis(analysis, font, includeSegments, wordBreak) {
8941
9059
  const engineProfile = getEngineProfile();
8942
- const { cache, emojiCorrection } = getFontMeasurementState(font, textMayContainEmoji(analysis.normalized));
8943
- const discretionaryHyphenWidth = getCorrectedSegmentWidth("-", getSegmentMetrics("-", cache), emojiCorrection);
8944
- const spaceWidth = getCorrectedSegmentWidth(" ", getSegmentMetrics(" ", cache), emojiCorrection);
9060
+ const { cache: cache2, emojiCorrection } = getFontMeasurementState(font, textMayContainEmoji(analysis.normalized));
9061
+ const discretionaryHyphenWidth = getCorrectedSegmentWidth("-", getSegmentMetrics("-", cache2), emojiCorrection);
9062
+ const spaceWidth = getCorrectedSegmentWidth(" ", getSegmentMetrics(" ", cache2), emojiCorrection);
8945
9063
  const tabStopAdvance = spaceWidth * 8;
8946
9064
  if (analysis.len === 0)
8947
9065
  return createEmptyPrepared(includeSegments);
@@ -8968,7 +9086,7 @@ function measureAnalysis(analysis, font, includeSegments, wordBreak) {
8968
9086
  segments.push(text);
8969
9087
  }
8970
9088
  function pushMeasuredTextSegment(text, kind, start, wordLike, allowOverflowBreaks) {
8971
- const textMetrics = getSegmentMetrics(text, cache);
9089
+ const textMetrics = getSegmentMetrics(text, cache2);
8972
9090
  const width = getCorrectedSegmentWidth(text, textMetrics, emojiCorrection);
8973
9091
  const lineEndFitAdvance = kind === "space" || kind === "preserved-space" || kind === "zero-width-break" ? 0 : width;
8974
9092
  const lineEndPaintAdvance = kind === "space" || kind === "zero-width-break" ? 0 : width;
@@ -8979,7 +9097,7 @@ function measureAnalysis(analysis, font, includeSegments, wordBreak) {
8979
9097
  } else if (engineProfile.preferPrefixWidthsForBreakableRuns) {
8980
9098
  fitMode = "segment-prefixes";
8981
9099
  }
8982
- const fitAdvances = getSegmentBreakableFitAdvances(text, textMetrics, cache, emojiCorrection, fitMode);
9100
+ const fitAdvances = getSegmentBreakableFitAdvances(text, textMetrics, cache2, emojiCorrection, fitMode);
8983
9101
  pushMeasuredSegment(text, width, lineEndFitAdvance, lineEndPaintAdvance, kind, start, fitAdvances);
8984
9102
  return;
8985
9103
  }
@@ -9003,7 +9121,7 @@ function measureAnalysis(analysis, font, includeSegments, wordBreak) {
9003
9121
  pushMeasuredSegment(segText, 0, 0, 0, segKind, segStart, null);
9004
9122
  continue;
9005
9123
  }
9006
- const segMetrics = getSegmentMetrics(segText, cache);
9124
+ const segMetrics = getSegmentMetrics(segText, cache2);
9007
9125
  if (segKind === "text" && segMetrics.containsCJK) {
9008
9126
  const baseUnits = buildBaseCjkUnits(segText, engineProfile);
9009
9127
  const measuredUnits = wordBreak === "keep-all" ? mergeKeepAllTextUnits(baseUnits) : baseUnits;
@@ -9801,7 +9919,24 @@ function trackRenderComplete(props) {
9801
9919
  capture_avg_ms: props.captureAvgMs,
9802
9920
  capture_peak_ms: props.capturePeakMs,
9803
9921
  peak_memory_mb: props.peakMemoryMb,
9804
- memory_free_mb: props.memoryFreeMb
9922
+ memory_free_mb: props.memoryFreeMb,
9923
+ tmp_peak_bytes: props.tmpPeakBytes,
9924
+ stage_compile_ms: props.stageCompileMs,
9925
+ stage_video_extract_ms: props.stageVideoExtractMs,
9926
+ stage_audio_process_ms: props.stageAudioProcessMs,
9927
+ stage_capture_ms: props.stageCaptureMs,
9928
+ stage_encode_ms: props.stageEncodeMs,
9929
+ stage_assemble_ms: props.stageAssembleMs,
9930
+ extract_resolve_ms: props.extractResolveMs,
9931
+ extract_hdr_probe_ms: props.extractHdrProbeMs,
9932
+ extract_hdr_preflight_ms: props.extractHdrPreflightMs,
9933
+ extract_hdr_preflight_count: props.extractHdrPreflightCount,
9934
+ extract_vfr_probe_ms: props.extractVfrProbeMs,
9935
+ extract_vfr_preflight_ms: props.extractVfrPreflightMs,
9936
+ extract_vfr_preflight_count: props.extractVfrPreflightCount,
9937
+ extract_phase3_ms: props.extractPhase3Ms,
9938
+ extract_cache_hits: props.extractCacheHits,
9939
+ extract_cache_misses: props.extractCacheMisses
9805
9940
  });
9806
9941
  }
9807
9942
  function trackRenderError(props) {
@@ -24970,7 +25105,8 @@ function resolveConfig(overrides) {
24970
25105
  DEFAULT_CONFIG2.renderReadyTimeout
24971
25106
  ),
24972
25107
  verifyRuntime: env("PRODUCER_VERIFY_HYPERFRAME_RUNTIME") !== "false",
24973
- runtimeManifestPath: env("PRODUCER_HYPERFRAME_MANIFEST_PATH")
25108
+ runtimeManifestPath: env("PRODUCER_HYPERFRAME_MANIFEST_PATH"),
25109
+ extractCacheDir: env("HYPERFRAMES_EXTRACT_CACHE_DIR")
24974
25110
  };
24975
25111
  const cleanEnv = Object.fromEntries(Object.entries(fromEnv).filter(([, v]) => v !== void 0));
24976
25112
  return {
@@ -27127,13 +27263,130 @@ var init_urlDownloader = __esm({
27127
27263
  }
27128
27264
  });
27129
27265
 
27266
+ // ../engine/src/utils/htmlTemplate.ts
27267
+ function parseHTMLContent(html) {
27268
+ const trimmed = html.trimStart().toLowerCase();
27269
+ if (trimmed.startsWith("<!doctype") || trimmed.startsWith("<html")) {
27270
+ return parseHTML(html).document;
27271
+ }
27272
+ return parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`).document;
27273
+ }
27274
+ function getSingleMeaningfulChild(container) {
27275
+ let child = null;
27276
+ for (const node of Array.from(container.childNodes)) {
27277
+ if (node.nodeType === 3 && !(node.textContent || "").trim()) continue;
27278
+ if (node.nodeType === 8) continue;
27279
+ if (node.nodeType !== 1) return null;
27280
+ if (child) return null;
27281
+ child = node;
27282
+ }
27283
+ return child;
27284
+ }
27285
+ function unwrapTemplate(html) {
27286
+ const lowered = html.toLowerCase();
27287
+ if (!lowered.includes("<template") || !lowered.includes("</template>")) {
27288
+ return html;
27289
+ }
27290
+ const { body } = parseHTMLContent(html);
27291
+ if (!body) return html;
27292
+ let container = body;
27293
+ const bodyWrapper = getSingleMeaningfulChild(container);
27294
+ if (bodyWrapper?.tagName === "BODY") {
27295
+ container = bodyWrapper;
27296
+ }
27297
+ const template = getSingleMeaningfulChild(container);
27298
+ if (template?.tagName !== "TEMPLATE") {
27299
+ return html;
27300
+ }
27301
+ return template.innerHTML ?? html;
27302
+ }
27303
+ var init_htmlTemplate = __esm({
27304
+ "../engine/src/utils/htmlTemplate.ts"() {
27305
+ "use strict";
27306
+ init_esm10();
27307
+ }
27308
+ });
27309
+
27310
+ // ../engine/src/services/extractionCache.ts
27311
+ import { createHash as createHash2 } from "crypto";
27312
+ import { mkdirSync as mkdirSync12, readdirSync as readdirSync8, statSync as statSync6, writeFileSync as writeFileSync10 } from "fs";
27313
+ import { existsSync as existsSync20 } from "fs";
27314
+ import { join as join22 } from "path";
27315
+ function readKeyStat(videoPath) {
27316
+ try {
27317
+ const stat3 = statSync6(videoPath);
27318
+ return { mtimeMs: Math.floor(stat3.mtimeMs), size: stat3.size };
27319
+ } catch {
27320
+ return null;
27321
+ }
27322
+ }
27323
+ function canonicalKeyBlob(input) {
27324
+ const durationForKey = Number.isFinite(input.duration) ? input.duration : -1;
27325
+ return JSON.stringify({
27326
+ p: input.videoPath,
27327
+ m: input.mtimeMs,
27328
+ s: input.size,
27329
+ ms: input.mediaStart,
27330
+ d: durationForKey,
27331
+ f: input.fps,
27332
+ fmt: input.format
27333
+ });
27334
+ }
27335
+ function computeCacheKey(input) {
27336
+ return createHash2("sha256").update(canonicalKeyBlob(input)).digest("hex");
27337
+ }
27338
+ function cacheEntryDirName(keyHash) {
27339
+ return SCHEMA_PREFIX + keyHash.slice(0, KEY_HEX_CHARS);
27340
+ }
27341
+ function lookupCacheEntry(rootDir, input) {
27342
+ const keyHash = computeCacheKey(input);
27343
+ const dir = join22(rootDir, cacheEntryDirName(keyHash));
27344
+ const complete = existsSync20(join22(dir, COMPLETE_SENTINEL));
27345
+ return { entry: { dir, keyHash }, hit: complete };
27346
+ }
27347
+ function ensureCacheEntryDir(entry) {
27348
+ mkdirSync12(entry.dir, { recursive: true });
27349
+ }
27350
+ function markCacheEntryComplete(entry) {
27351
+ writeFileSync10(join22(entry.dir, COMPLETE_SENTINEL), "", "utf-8");
27352
+ }
27353
+ function rehydrateCacheEntry(entry, options) {
27354
+ const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${options.format}`;
27355
+ const framePaths = /* @__PURE__ */ new Map();
27356
+ const suffix = `.${options.format}`;
27357
+ const files = readdirSync8(entry.dir).filter((f3) => f3.startsWith(FRAME_FILENAME_PREFIX) && f3.endsWith(suffix)).sort();
27358
+ files.forEach((file, idx) => {
27359
+ framePaths.set(idx, join22(entry.dir, file));
27360
+ });
27361
+ return {
27362
+ videoId: options.videoId,
27363
+ srcPath: options.srcPath,
27364
+ outputDir: entry.dir,
27365
+ framePattern,
27366
+ fps: options.fps,
27367
+ totalFrames: framePaths.size,
27368
+ metadata: options.metadata,
27369
+ framePaths
27370
+ };
27371
+ }
27372
+ var FRAME_FILENAME_PREFIX, COMPLETE_SENTINEL, SCHEMA_PREFIX, KEY_HEX_CHARS;
27373
+ var init_extractionCache = __esm({
27374
+ "../engine/src/services/extractionCache.ts"() {
27375
+ "use strict";
27376
+ FRAME_FILENAME_PREFIX = "frame_";
27377
+ COMPLETE_SENTINEL = ".hf-complete";
27378
+ SCHEMA_PREFIX = "hfcache-v2-";
27379
+ KEY_HEX_CHARS = 16;
27380
+ }
27381
+ });
27382
+
27130
27383
  // ../engine/src/services/videoFrameExtractor.ts
27131
27384
  import { spawn as spawn7 } from "child_process";
27132
- import { existsSync as existsSync20, mkdirSync as mkdirSync12, readdirSync as readdirSync8, rmSync as rmSync5 } from "fs";
27133
- import { isAbsolute as isAbsolute2, join as join22 } from "path";
27385
+ import { existsSync as existsSync21, mkdirSync as mkdirSync13, readdirSync as readdirSync9, rmSync as rmSync5 } from "fs";
27386
+ import { isAbsolute as isAbsolute2, join as join23 } from "path";
27134
27387
  function parseVideoElements(html) {
27135
27388
  const videos = [];
27136
- const { document: document2 } = parseHTML(html);
27389
+ const { document: document2 } = parseHTML(unwrapTemplate(html));
27137
27390
  const videoEls = document2.querySelectorAll("video[src]");
27138
27391
  let autoIdCounter = 0;
27139
27392
  for (const el of videoEls) {
@@ -27170,7 +27423,7 @@ function parseVideoElements(html) {
27170
27423
  }
27171
27424
  function parseImageElements(html) {
27172
27425
  const images = [];
27173
- const { document: document2 } = parseHTML(html);
27426
+ const { document: document2 } = parseHTML(unwrapTemplate(html));
27174
27427
  const imgEls = document2.querySelectorAll("img[src]");
27175
27428
  let autoIdCounter = 0;
27176
27429
  for (const el of imgEls) {
@@ -27196,14 +27449,14 @@ function parseImageElements(html) {
27196
27449
  }
27197
27450
  return images;
27198
27451
  }
27199
- async function extractVideoFramesRange(videoPath, videoId, startTime, duration, options, signal, config) {
27452
+ async function extractVideoFramesRange(videoPath, videoId, startTime, duration, options, signal, config, outputDirOverride) {
27200
27453
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
27201
27454
  const { fps, outputDir, quality = 95, format = "jpg" } = options;
27202
- const videoOutputDir = join22(outputDir, videoId);
27203
- if (!existsSync20(videoOutputDir)) mkdirSync12(videoOutputDir, { recursive: true });
27455
+ const videoOutputDir = outputDirOverride ?? join23(outputDir, videoId);
27456
+ if (!existsSync21(videoOutputDir)) mkdirSync13(videoOutputDir, { recursive: true });
27204
27457
  const metadata = await extractMediaMetadata(videoPath);
27205
- const framePattern = `frame_%05d.${format}`;
27206
- const outputPattern = join22(videoOutputDir, framePattern);
27458
+ const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${format}`;
27459
+ const outputPattern = join23(videoOutputDir, framePattern);
27207
27460
  const isHdr = isHdrColorSpace(metadata.colorSpace);
27208
27461
  const isMacOS = process.platform === "darwin";
27209
27462
  const args = [];
@@ -27251,9 +27504,9 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
27251
27504
  return;
27252
27505
  }
27253
27506
  const framePaths = /* @__PURE__ */ new Map();
27254
- const files = readdirSync8(videoOutputDir).filter((f3) => f3.startsWith("frame_") && f3.endsWith(`.${format}`)).sort();
27507
+ const files = readdirSync9(videoOutputDir).filter((f3) => f3.startsWith(FRAME_FILENAME_PREFIX) && f3.endsWith(`.${format}`)).sort();
27255
27508
  files.forEach((file, index) => {
27256
- framePaths.set(index, join22(videoOutputDir, file));
27509
+ framePaths.set(index, join23(videoOutputDir, file));
27257
27510
  });
27258
27511
  resolve38({
27259
27512
  videoId,
@@ -27277,12 +27530,19 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
27277
27530
  });
27278
27531
  });
27279
27532
  }
27280
- async function convertSdrToHdr(inputPath, outputPath, targetTransfer, signal, config) {
27533
+ async function convertSdrToHdr(inputPath, outputPath, startTime, duration, targetTransfer, signal, config) {
27534
+ if (duration <= 0) {
27535
+ throw new Error(`convertSdrToHdr: duration must be positive (got ${duration})`);
27536
+ }
27281
27537
  const timeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
27282
27538
  const colorTrc = targetTransfer === "pq" ? "smpte2084" : "arib-std-b67";
27283
27539
  const args = [
27540
+ "-ss",
27541
+ String(startTime),
27284
27542
  "-i",
27285
27543
  inputPath,
27544
+ "-t",
27545
+ String(duration),
27286
27546
  "-vf",
27287
27547
  "colorspace=all=bt2020:iall=bt709:range=tv",
27288
27548
  "-color_primaries",
@@ -27309,6 +27569,11 @@ async function convertSdrToHdr(inputPath, outputPath, targetTransfer, signal, co
27309
27569
  );
27310
27570
  }
27311
27571
  }
27572
+ function resolveSegmentDuration(requested, mediaStart, metadata) {
27573
+ if (Number.isFinite(requested) && requested > 0) return requested;
27574
+ const sourceRemaining = metadata.durationSeconds - mediaStart;
27575
+ return sourceRemaining > 0 ? sourceRemaining : metadata.durationSeconds;
27576
+ }
27312
27577
  async function convertVfrToCfr(inputPath, outputPath, targetFps, startTime, duration, signal, config) {
27313
27578
  const timeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
27314
27579
  const args = [
@@ -27345,21 +27610,34 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
27345
27610
  const extracted = [];
27346
27611
  const errors = [];
27347
27612
  let totalFramesExtracted = 0;
27613
+ const breakdown = {
27614
+ resolveMs: 0,
27615
+ hdrProbeMs: 0,
27616
+ hdrPreflightMs: 0,
27617
+ hdrPreflightCount: 0,
27618
+ vfrProbeMs: 0,
27619
+ vfrPreflightMs: 0,
27620
+ vfrPreflightCount: 0,
27621
+ extractMs: 0,
27622
+ cacheHits: 0,
27623
+ cacheMisses: 0
27624
+ };
27625
+ const phase1Start = Date.now();
27348
27626
  const resolvedVideos = [];
27349
27627
  for (const video of videos) {
27350
27628
  if (signal?.aborted) break;
27351
27629
  try {
27352
27630
  let videoPath = video.src;
27353
27631
  if (!isAbsolute2(videoPath) && !isHttpUrl(videoPath)) {
27354
- const fromCompiled = compiledDir ? join22(compiledDir, videoPath) : null;
27355
- videoPath = fromCompiled && existsSync20(fromCompiled) ? fromCompiled : join22(baseDir, videoPath);
27632
+ const fromCompiled = compiledDir ? join23(compiledDir, videoPath) : null;
27633
+ videoPath = fromCompiled && existsSync21(fromCompiled) ? fromCompiled : join23(baseDir, videoPath);
27356
27634
  }
27357
27635
  if (isHttpUrl(videoPath)) {
27358
- const downloadDir = join22(options.outputDir, "_downloads");
27359
- mkdirSync12(downloadDir, { recursive: true });
27636
+ const downloadDir = join23(options.outputDir, "_downloads");
27637
+ mkdirSync13(downloadDir, { recursive: true });
27360
27638
  videoPath = await downloadToTemp(videoPath, downloadDir);
27361
27639
  }
27362
- if (!existsSync20(videoPath)) {
27640
+ if (!existsSync21(videoPath)) {
27363
27641
  errors.push({ videoId: video.id, error: `Video file not found: ${videoPath}` });
27364
27642
  continue;
27365
27643
  }
@@ -27368,27 +27646,66 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
27368
27646
  errors.push({ videoId: video.id, error: err instanceof Error ? err.message : String(err) });
27369
27647
  }
27370
27648
  }
27371
- const videoColorSpaces = await Promise.all(
27372
- resolvedVideos.map(async ({ videoPath }) => {
27373
- const metadata = await extractMediaMetadata(videoPath);
27374
- return metadata.colorSpace;
27375
- })
27649
+ breakdown.resolveMs = Date.now() - phase1Start;
27650
+ const cacheKeyInputs = resolvedVideos.map(({ video, videoPath }) => {
27651
+ const stat3 = readKeyStat(videoPath);
27652
+ if (!stat3) return null;
27653
+ return {
27654
+ videoPath,
27655
+ mtimeMs: stat3.mtimeMs,
27656
+ size: stat3.size,
27657
+ mediaStart: video.mediaStart,
27658
+ start: video.start,
27659
+ end: video.end
27660
+ };
27661
+ });
27662
+ const phase2ProbeStart = Date.now();
27663
+ const videoMetadata = await Promise.all(
27664
+ resolvedVideos.map(({ videoPath }) => extractMediaMetadata(videoPath))
27376
27665
  );
27666
+ const videoColorSpaces = videoMetadata.map((m2) => m2.colorSpace);
27667
+ breakdown.hdrProbeMs = Date.now() - phase2ProbeStart;
27668
+ const hdrPreflightStart = Date.now();
27377
27669
  const hdrInfo = analyzeCompositionHdr(videoColorSpaces);
27670
+ const hdrSkippedIndices = /* @__PURE__ */ new Set();
27378
27671
  if (hdrInfo.hasHdr && hdrInfo.dominantTransfer) {
27379
27672
  const targetTransfer = hdrInfo.dominantTransfer;
27380
- const convertDir = join22(options.outputDir, "_hdr_normalized");
27381
- mkdirSync12(convertDir, { recursive: true });
27673
+ const convertDir = join23(options.outputDir, "_hdr_normalized");
27674
+ mkdirSync13(convertDir, { recursive: true });
27382
27675
  for (let i2 = 0; i2 < resolvedVideos.length; i2++) {
27383
27676
  if (signal?.aborted) break;
27384
27677
  const cs = videoColorSpaces[i2] ?? null;
27385
27678
  if (!isHdrColorSpace(cs)) {
27386
27679
  const entry = resolvedVideos[i2];
27387
- if (!entry) continue;
27388
- const convertedPath = join22(convertDir, `${entry.video.id}_hdr.mp4`);
27680
+ const metadata = videoMetadata[i2];
27681
+ if (!entry || !metadata) continue;
27682
+ if (entry.video.mediaStart >= metadata.durationSeconds) {
27683
+ errors.push({
27684
+ videoId: entry.video.id,
27685
+ error: `SDR\u2192HDR conversion skipped: mediaStart (${entry.video.mediaStart}s) \u2265 source duration (${metadata.durationSeconds}s)`
27686
+ });
27687
+ hdrSkippedIndices.add(i2);
27688
+ continue;
27689
+ }
27690
+ let segDuration = entry.video.end - entry.video.start;
27691
+ if (!Number.isFinite(segDuration) || segDuration <= 0) {
27692
+ const sourceRemaining = metadata.durationSeconds - entry.video.mediaStart;
27693
+ segDuration = sourceRemaining > 0 ? sourceRemaining : metadata.durationSeconds;
27694
+ }
27695
+ const convertedPath = join23(convertDir, `${entry.video.id}_hdr.mp4`);
27389
27696
  try {
27390
- await convertSdrToHdr(entry.videoPath, convertedPath, targetTransfer, signal, config);
27697
+ await convertSdrToHdr(
27698
+ entry.videoPath,
27699
+ convertedPath,
27700
+ entry.video.mediaStart,
27701
+ segDuration,
27702
+ targetTransfer,
27703
+ signal,
27704
+ config
27705
+ );
27391
27706
  entry.videoPath = convertedPath;
27707
+ entry.video = { ...entry.video, mediaStart: 0 };
27708
+ breakdown.hdrPreflightCount += 1;
27392
27709
  } catch (err) {
27393
27710
  errors.push({
27394
27711
  videoId: entry.video.id,
@@ -27398,20 +27715,34 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
27398
27715
  }
27399
27716
  }
27400
27717
  }
27401
- const vfrNormDir = join22(options.outputDir, "_vfr_normalized");
27718
+ breakdown.hdrPreflightMs = Date.now() - hdrPreflightStart;
27719
+ if (hdrSkippedIndices.size > 0) {
27720
+ for (let i2 = resolvedVideos.length - 1; i2 >= 0; i2--) {
27721
+ if (hdrSkippedIndices.has(i2)) {
27722
+ resolvedVideos.splice(i2, 1);
27723
+ videoMetadata.splice(i2, 1);
27724
+ videoColorSpaces.splice(i2, 1);
27725
+ cacheKeyInputs.splice(i2, 1);
27726
+ }
27727
+ }
27728
+ }
27729
+ const vfrPreflightStart = Date.now();
27730
+ const vfrNormDir = join23(options.outputDir, "_vfr_normalized");
27402
27731
  for (let i2 = 0; i2 < resolvedVideos.length; i2++) {
27403
27732
  if (signal?.aborted) break;
27404
27733
  const entry = resolvedVideos[i2];
27405
27734
  if (!entry) continue;
27735
+ const vfrProbeStart = Date.now();
27406
27736
  const metadata = await extractMediaMetadata(entry.videoPath);
27737
+ breakdown.vfrProbeMs += Date.now() - vfrProbeStart;
27407
27738
  if (!metadata.isVFR) continue;
27408
27739
  let segDuration = entry.video.end - entry.video.start;
27409
27740
  if (!Number.isFinite(segDuration) || segDuration <= 0) {
27410
27741
  const sourceRemaining = metadata.durationSeconds - entry.video.mediaStart;
27411
27742
  segDuration = sourceRemaining > 0 ? sourceRemaining : metadata.durationSeconds;
27412
27743
  }
27413
- mkdirSync12(vfrNormDir, { recursive: true });
27414
- const normalizedPath = join22(vfrNormDir, `${entry.video.id}_cfr.mp4`);
27744
+ mkdirSync13(vfrNormDir, { recursive: true });
27745
+ const normalizedPath = join23(vfrNormDir, `${entry.video.id}_cfr.mp4`);
27415
27746
  try {
27416
27747
  await convertVfrToCfr(
27417
27748
  entry.videoPath,
@@ -27424,6 +27755,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
27424
27755
  );
27425
27756
  entry.videoPath = normalizedPath;
27426
27757
  entry.video = { ...entry.video, mediaStart: 0 };
27758
+ breakdown.vfrPreflightCount += 1;
27427
27759
  } catch (err) {
27428
27760
  errors.push({
27429
27761
  videoId: entry.video.id,
@@ -27431,19 +27763,72 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
27431
27763
  });
27432
27764
  }
27433
27765
  }
27766
+ breakdown.vfrPreflightMs = Date.now() - vfrPreflightStart;
27767
+ const phase3Start = Date.now();
27768
+ const cacheRootDir = config?.extractCacheDir;
27769
+ const cacheFormat = options.format ?? "jpg";
27770
+ async function tryCachedExtract(video, videoPath, videoDuration, i2) {
27771
+ if (!cacheRootDir) return null;
27772
+ const keyInput = cacheKeyInputs[i2];
27773
+ const probedMeta = videoMetadata[i2];
27774
+ if (!keyInput || !probedMeta) return null;
27775
+ const keyDuration = resolveSegmentDuration(
27776
+ keyInput.end - keyInput.start,
27777
+ keyInput.mediaStart,
27778
+ probedMeta
27779
+ );
27780
+ const lookup = lookupCacheEntry(cacheRootDir, {
27781
+ videoPath: keyInput.videoPath,
27782
+ mtimeMs: keyInput.mtimeMs,
27783
+ size: keyInput.size,
27784
+ mediaStart: keyInput.mediaStart,
27785
+ duration: keyDuration,
27786
+ fps: options.fps,
27787
+ format: cacheFormat
27788
+ });
27789
+ if (lookup.hit) {
27790
+ breakdown.cacheHits += 1;
27791
+ const rehydrated = rehydrateCacheEntry(lookup.entry, {
27792
+ videoId: video.id,
27793
+ srcPath: keyInput.videoPath,
27794
+ fps: options.fps,
27795
+ format: cacheFormat,
27796
+ metadata: probedMeta
27797
+ });
27798
+ return { ...rehydrated, ownedByLookup: true };
27799
+ }
27800
+ breakdown.cacheMisses += 1;
27801
+ ensureCacheEntryDir(lookup.entry);
27802
+ const result = await extractVideoFramesRange(
27803
+ videoPath,
27804
+ video.id,
27805
+ video.mediaStart,
27806
+ videoDuration,
27807
+ options,
27808
+ signal,
27809
+ config,
27810
+ lookup.entry.dir
27811
+ );
27812
+ markCacheEntryComplete(lookup.entry);
27813
+ return { ...result, ownedByLookup: true };
27814
+ }
27434
27815
  const results = await Promise.all(
27435
- resolvedVideos.map(async ({ video, videoPath }) => {
27816
+ resolvedVideos.map(async ({ video, videoPath }, i2) => {
27436
27817
  if (signal?.aborted) {
27437
27818
  throw new Error("Video frame extraction cancelled");
27438
27819
  }
27439
27820
  try {
27440
- let videoDuration = video.end - video.start;
27441
- if (!Number.isFinite(videoDuration) || videoDuration <= 0) {
27442
- const metadata = await extractMediaMetadata(videoPath);
27443
- const sourceDuration = metadata.durationSeconds - video.mediaStart;
27444
- videoDuration = sourceDuration > 0 ? sourceDuration : metadata.durationSeconds;
27821
+ const probedMeta = videoMetadata[i2] ?? await extractMediaMetadata(videoPath);
27822
+ const videoDuration = resolveSegmentDuration(
27823
+ video.end - video.start,
27824
+ video.mediaStart,
27825
+ probedMeta
27826
+ );
27827
+ if (video.end - video.start !== videoDuration) {
27445
27828
  video.end = video.start + videoDuration;
27446
27829
  }
27830
+ const cached2 = await tryCachedExtract(video, videoPath, videoDuration, i2);
27831
+ if (cached2) return { result: cached2 };
27447
27832
  const result = await extractVideoFramesRange(
27448
27833
  videoPath,
27449
27834
  video.id,
@@ -27464,6 +27849,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
27464
27849
  }
27465
27850
  })
27466
27851
  );
27852
+ breakdown.extractMs = Date.now() - phase3Start;
27467
27853
  for (const item of results) {
27468
27854
  if ("error" in item && item.error) {
27469
27855
  errors.push(item.error);
@@ -27477,7 +27863,8 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
27477
27863
  extracted,
27478
27864
  errors,
27479
27865
  totalFramesExtracted,
27480
- durationMs: Date.now() - startTime
27866
+ durationMs: Date.now() - startTime,
27867
+ phaseBreakdown: breakdown
27481
27868
  };
27482
27869
  }
27483
27870
  function getFrameAtTime(extracted, globalTime, videoStart) {
@@ -27507,6 +27894,8 @@ var init_videoFrameExtractor = __esm({
27507
27894
  init_urlDownloader();
27508
27895
  init_runFfmpeg();
27509
27896
  init_config2();
27897
+ init_htmlTemplate();
27898
+ init_extractionCache();
27510
27899
  FrameLookupTable = class {
27511
27900
  videos = /* @__PURE__ */ new Map();
27512
27901
  orderedVideos = [];
@@ -27590,7 +27979,8 @@ var init_videoFrameExtractor = __esm({
27590
27979
  }
27591
27980
  cleanup() {
27592
27981
  for (const video of this.videos.values()) {
27593
- if (existsSync20(video.extracted.outputDir)) {
27982
+ if (video.extracted.ownedByLookup) continue;
27983
+ if (existsSync21(video.extracted.outputDir)) {
27594
27984
  rmSync5(video.extracted.outputDir, { recursive: true, force: true });
27595
27985
  }
27596
27986
  }
@@ -27605,23 +27995,23 @@ var init_videoFrameExtractor = __esm({
27605
27995
  // ../engine/src/services/videoFrameInjector.ts
27606
27996
  import { promises as fs } from "fs";
27607
27997
  function createFrameDataUriCache(cacheLimit) {
27608
- const cache = /* @__PURE__ */ new Map();
27998
+ const cache2 = /* @__PURE__ */ new Map();
27609
27999
  const inFlight = /* @__PURE__ */ new Map();
27610
28000
  function remember(framePath, dataUri) {
27611
- if (cache.has(framePath)) {
27612
- cache.delete(framePath);
28001
+ if (cache2.has(framePath)) {
28002
+ cache2.delete(framePath);
27613
28003
  }
27614
- cache.set(framePath, dataUri);
27615
- if (cache.size > cacheLimit) {
27616
- const oldestKey = cache.keys().next().value;
28004
+ cache2.set(framePath, dataUri);
28005
+ if (cache2.size > cacheLimit) {
28006
+ const oldestKey = cache2.keys().next().value;
27617
28007
  if (oldestKey) {
27618
- cache.delete(oldestKey);
28008
+ cache2.delete(oldestKey);
27619
28009
  }
27620
28010
  }
27621
28011
  return dataUri;
27622
28012
  }
27623
28013
  async function get(framePath) {
27624
- const cached2 = cache.get(framePath);
28014
+ const cached2 = cache2.get(framePath);
27625
28015
  if (cached2) {
27626
28016
  remember(framePath, cached2);
27627
28017
  return cached2;
@@ -27898,11 +28288,11 @@ var init_videoFrameInjector = __esm({
27898
28288
  });
27899
28289
 
27900
28290
  // ../engine/src/services/audioMixer.ts
27901
- import { existsSync as existsSync21, mkdirSync as mkdirSync13, rmSync as rmSync6 } from "fs";
27902
- import { isAbsolute as isAbsolute3, join as join23, dirname as dirname8 } from "path";
28291
+ import { existsSync as existsSync22, mkdirSync as mkdirSync14, rmSync as rmSync6 } from "fs";
28292
+ import { isAbsolute as isAbsolute3, join as join24, dirname as dirname8 } from "path";
27903
28293
  function parseAudioElements(html) {
27904
28294
  const elements = [];
27905
- const { document: document2 } = parseHTML(html);
28295
+ const { document: document2 } = parseHTML(unwrapTemplate(html));
27906
28296
  const audioEls = document2.querySelectorAll("audio[id][src]");
27907
28297
  for (const el of audioEls) {
27908
28298
  const id = el.getAttribute("id");
@@ -27950,7 +28340,7 @@ function parseAudioElements(html) {
27950
28340
  async function extractAudioFromVideo(videoPath, outputPath, options, signal, config) {
27951
28341
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
27952
28342
  const outputDir = dirname8(outputPath);
27953
- if (!existsSync21(outputDir)) mkdirSync13(outputDir, { recursive: true });
28343
+ if (!existsSync22(outputDir)) mkdirSync14(outputDir, { recursive: true });
27954
28344
  const args = ["-i", videoPath];
27955
28345
  if (options?.startTime !== void 0) args.push("-ss", String(options.startTime));
27956
28346
  if (options?.duration !== void 0) args.push("-t", String(options.duration));
@@ -27977,7 +28367,7 @@ async function extractAudioFromVideo(videoPath, outputPath, options, signal, con
27977
28367
  async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, signal, config) {
27978
28368
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
27979
28369
  const outputDir = dirname8(outputPath);
27980
- if (!existsSync21(outputDir)) mkdirSync13(outputDir, { recursive: true });
28370
+ if (!existsSync22(outputDir)) mkdirSync14(outputDir, { recursive: true });
27981
28371
  const args = [
27982
28372
  "-ss",
27983
28373
  String(mediaStart),
@@ -28013,7 +28403,7 @@ async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, sign
28013
28403
  async function generateSilence(outputPath, duration, signal, config) {
28014
28404
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
28015
28405
  const outputDir = dirname8(outputPath);
28016
- if (!existsSync21(outputDir)) mkdirSync13(outputDir, { recursive: true });
28406
+ if (!existsSync22(outputDir)) mkdirSync14(outputDir, { recursive: true });
28017
28407
  const args = [
28018
28408
  "-f",
28019
28409
  "lavfi",
@@ -28056,7 +28446,7 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
28056
28446
  };
28057
28447
  }
28058
28448
  const outputDir = dirname8(outputPath);
28059
- if (!existsSync21(outputDir)) mkdirSync13(outputDir, { recursive: true });
28449
+ if (!existsSync22(outputDir)) mkdirSync14(outputDir, { recursive: true });
28060
28450
  const inputs = [];
28061
28451
  const filterParts = [];
28062
28452
  tracks.forEach((track, i2) => {
@@ -28117,7 +28507,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
28117
28507
  const startMs = Date.now();
28118
28508
  const tracks = [];
28119
28509
  const errors = [];
28120
- if (!existsSync21(workDir)) mkdirSync13(workDir, { recursive: true });
28510
+ if (!existsSync22(workDir)) mkdirSync14(workDir, { recursive: true });
28121
28511
  await Promise.all(
28122
28512
  elements.map(async (element) => {
28123
28513
  if (signal?.aborted) {
@@ -28127,8 +28517,8 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
28127
28517
  try {
28128
28518
  let srcPath = element.src;
28129
28519
  if (!isAbsolute3(srcPath) && !isHttpUrl(srcPath)) {
28130
- const fromCompiled = compiledDir ? join23(compiledDir, srcPath) : null;
28131
- srcPath = fromCompiled && existsSync21(fromCompiled) ? fromCompiled : join23(baseDir, srcPath);
28520
+ const fromCompiled = compiledDir ? join24(compiledDir, srcPath) : null;
28521
+ srcPath = fromCompiled && existsSync22(fromCompiled) ? fromCompiled : join24(baseDir, srcPath);
28132
28522
  }
28133
28523
  if (isHttpUrl(srcPath)) {
28134
28524
  try {
@@ -28140,7 +28530,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
28140
28530
  return;
28141
28531
  }
28142
28532
  }
28143
- if (!existsSync21(srcPath)) {
28533
+ if (!existsSync22(srcPath)) {
28144
28534
  errors.push(`Source not found: ${element.id} (${element.src})`);
28145
28535
  return;
28146
28536
  }
@@ -28151,7 +28541,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
28151
28541
  }
28152
28542
  let audioSrcPath = srcPath;
28153
28543
  if (element.type === "video") {
28154
- const extractedPath = join23(workDir, `${element.id}-extracted.wav`);
28544
+ const extractedPath = join24(workDir, `${element.id}-extracted.wav`);
28155
28545
  const extractResult = await extractAudioFromVideo(
28156
28546
  srcPath,
28157
28547
  extractedPath,
@@ -28168,7 +28558,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
28168
28558
  }
28169
28559
  audioSrcPath = extractedPath;
28170
28560
  } else {
28171
- const trimmedPath = join23(workDir, `${element.id}-trimmed.wav`);
28561
+ const trimmedPath = join24(workDir, `${element.id}-trimmed.wav`);
28172
28562
  const prepResult = await prepareAudioTrack(
28173
28563
  srcPath,
28174
28564
  trimmedPath,
@@ -28216,14 +28606,15 @@ var init_audioMixer = __esm({
28216
28606
  init_urlDownloader();
28217
28607
  init_config2();
28218
28608
  init_runFfmpeg();
28609
+ init_htmlTemplate();
28219
28610
  }
28220
28611
  });
28221
28612
 
28222
28613
  // ../engine/src/services/parallelCoordinator.ts
28223
28614
  import { cpus as cpus2, freemem, totalmem as totalmem2 } from "os";
28224
- import { existsSync as existsSync22, mkdirSync as mkdirSync14, readdirSync as readdirSync9 } from "fs";
28615
+ import { existsSync as existsSync23, mkdirSync as mkdirSync15, readdirSync as readdirSync10 } from "fs";
28225
28616
  import { copyFile, rename } from "fs/promises";
28226
- import { join as join24 } from "path";
28617
+ import { join as join25 } from "path";
28227
28618
  function calculateOptimalWorkers(totalFrames, requested, config) {
28228
28619
  const effectiveMaxWorkers = (() => {
28229
28620
  const concurrency = config?.concurrency ?? DEFAULT_CONFIG2.concurrency;
@@ -28266,7 +28657,7 @@ function distributeFrames(totalFrames, workerCount, workDir) {
28266
28657
  workerId: i2,
28267
28658
  startFrame,
28268
28659
  endFrame,
28269
- outputDir: join24(workDir, `worker-${i2}`)
28660
+ outputDir: join25(workDir, `worker-${i2}`)
28270
28661
  });
28271
28662
  }
28272
28663
  return tasks;
@@ -28274,7 +28665,7 @@ function distributeFrames(totalFrames, workerCount, workDir) {
28274
28665
  async function executeWorkerTask(task, serverUrl, captureOptions, createBeforeCaptureHook, signal, onFrameCaptured, onFrameBuffer, config) {
28275
28666
  const startTime = Date.now();
28276
28667
  let framesCaptured = 0;
28277
- if (!existsSync22(task.outputDir)) mkdirSync14(task.outputDir, { recursive: true });
28668
+ if (!existsSync23(task.outputDir)) mkdirSync15(task.outputDir, { recursive: true });
28278
28669
  let session = null;
28279
28670
  let perf;
28280
28671
  try {
@@ -28364,17 +28755,17 @@ async function executeParallelCapture(serverUrl, workDir, tasks, captureOptions,
28364
28755
  return results;
28365
28756
  }
28366
28757
  async function mergeWorkerFrames(workDir, tasks, outputDir) {
28367
- if (!existsSync22(outputDir)) mkdirSync14(outputDir, { recursive: true });
28758
+ if (!existsSync23(outputDir)) mkdirSync15(outputDir, { recursive: true });
28368
28759
  let totalFrames = 0;
28369
28760
  const sortedTasks = [...tasks].sort((a, b) => a.startFrame - b.startFrame);
28370
28761
  for (const task of sortedTasks) {
28371
- if (!existsSync22(task.outputDir)) {
28762
+ if (!existsSync23(task.outputDir)) {
28372
28763
  continue;
28373
28764
  }
28374
- const files = readdirSync9(task.outputDir).filter((f3) => f3.startsWith("frame_") && (f3.endsWith(".jpg") || f3.endsWith(".png"))).sort();
28765
+ const files = readdirSync10(task.outputDir).filter((f3) => f3.startsWith("frame_") && (f3.endsWith(".jpg") || f3.endsWith(".png"))).sort();
28375
28766
  const copyTasks = files.map(async (file) => {
28376
- const sourcePath = join24(task.outputDir, file);
28377
- const targetPath = join24(outputDir, file);
28767
+ const sourcePath = join25(task.outputDir, file);
28768
+ const targetPath = join25(outputDir, file);
28378
28769
  try {
28379
28770
  await rename(sourcePath, targetPath);
28380
28771
  } catch {
@@ -28411,8 +28802,8 @@ var init_parallelCoordinator = __esm({
28411
28802
  // ../engine/src/services/fileServer.ts
28412
28803
  import { Hono as Hono2 } from "hono";
28413
28804
  import { serve } from "@hono/node-server";
28414
- import { readFileSync as readFileSync16, existsSync as existsSync23, statSync as statSync6 } from "fs";
28415
- import { join as join25, extname as extname6 } from "path";
28805
+ import { readFileSync as readFileSync16, existsSync as existsSync24, statSync as statSync7 } from "fs";
28806
+ import { join as join26, extname as extname6 } from "path";
28416
28807
  function stripEmbeddedRuntimeScripts(html) {
28417
28808
  if (!html) return html;
28418
28809
  const scriptRe = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
@@ -28481,12 +28872,12 @@ function createFileServer(options) {
28481
28872
  let requestPath = c2.req.path;
28482
28873
  if (requestPath === "/") requestPath = "/index.html";
28483
28874
  const relativePath = requestPath.replace(/^\//, "");
28484
- const compiledPath = compiledDir ? join25(compiledDir, relativePath) : null;
28875
+ const compiledPath = compiledDir ? join26(compiledDir, relativePath) : null;
28485
28876
  const hasCompiledFile = Boolean(
28486
- compiledPath && existsSync23(compiledPath) && statSync6(compiledPath).isFile()
28877
+ compiledPath && existsSync24(compiledPath) && statSync7(compiledPath).isFile()
28487
28878
  );
28488
- const filePath = hasCompiledFile ? compiledPath : join25(projectDir, relativePath);
28489
- if (!existsSync23(filePath) || !statSync6(filePath).isFile()) {
28879
+ const filePath = hasCompiledFile ? compiledPath : join26(projectDir, relativePath);
28880
+ if (!existsSync24(filePath) || !statSync7(filePath).isFile()) {
28490
28881
  return c2.text("Not found", 404);
28491
28882
  }
28492
28883
  const ext = extname6(filePath).toLowerCase();
@@ -29764,8 +30155,8 @@ var init_shaderTransitions = __esm({
29764
30155
  });
29765
30156
 
29766
30157
  // ../engine/src/services/hdrCapture.ts
29767
- import { existsSync as existsSync24, readdirSync as readdirSync10 } from "fs";
29768
- import { join as join26 } from "path";
30158
+ import { existsSync as existsSync25, readdirSync as readdirSync11 } from "fs";
30159
+ import { join as join27 } from "path";
29769
30160
  import { homedir as homedir6 } from "os";
29770
30161
  function linearToPQ(L2) {
29771
30162
  const Lp = Math.max(0, L2 * SDR_NITS / PQ_MAX_NITS);
@@ -29881,12 +30272,12 @@ function float16ToPqRgb(rawBuffer, bytesPerRow, width, height) {
29881
30272
  return output;
29882
30273
  }
29883
30274
  function resolveHeadedChromePath() {
29884
- const baseDir = join26(homedir6(), ".cache", "puppeteer", "chrome");
29885
- if (!existsSync24(baseDir)) return void 0;
29886
- const versions = readdirSync10(baseDir).sort().reverse();
30275
+ const baseDir = join27(homedir6(), ".cache", "puppeteer", "chrome");
30276
+ if (!existsSync25(baseDir)) return void 0;
30277
+ const versions = readdirSync11(baseDir).sort().reverse();
29887
30278
  for (const version of versions) {
29888
30279
  const candidates = [
29889
- join26(
30280
+ join27(
29890
30281
  baseDir,
29891
30282
  version,
29892
30283
  "chrome-mac-arm64",
@@ -29895,7 +30286,7 @@ function resolveHeadedChromePath() {
29895
30286
  "MacOS",
29896
30287
  "Google Chrome for Testing"
29897
30288
  ),
29898
- join26(
30289
+ join27(
29899
30290
  baseDir,
29900
30291
  version,
29901
30292
  "chrome-mac-x64",
@@ -29904,11 +30295,11 @@ function resolveHeadedChromePath() {
29904
30295
  "MacOS",
29905
30296
  "Google Chrome for Testing"
29906
30297
  ),
29907
- join26(baseDir, version, "chrome-linux64", "chrome"),
29908
- join26(baseDir, version, "chrome-win64", "chrome.exe")
30298
+ join27(baseDir, version, "chrome-linux64", "chrome"),
30299
+ join27(baseDir, version, "chrome-win64", "chrome.exe")
29909
30300
  ];
29910
30301
  for (const binary of candidates) {
29911
- if (existsSync24(binary)) return binary;
30302
+ if (existsSync25(binary)) return binary;
29912
30303
  }
29913
30304
  }
29914
30305
  return void 0;
@@ -30179,10 +30570,10 @@ var init_staticGuard = __esm({
30179
30570
  });
30180
30571
 
30181
30572
  // ../core/src/compiler/htmlBundler.ts
30182
- import { readFileSync as readFileSync17, existsSync as existsSync25 } from "fs";
30183
- import { join as join27, resolve as resolve12, isAbsolute as isAbsolute4, sep as sep2 } from "path";
30573
+ import { readFileSync as readFileSync17, existsSync as existsSync26 } from "fs";
30574
+ import { join as join28, resolve as resolve12, isAbsolute as isAbsolute4, sep as sep2 } from "path";
30184
30575
  import { transformSync } from "esbuild";
30185
- function parseHTMLContent(html) {
30576
+ function parseHTMLContent2(html) {
30186
30577
  const trimmed = html.trimStart().toLowerCase();
30187
30578
  if (trimmed.startsWith("<!doctype") || trimmed.startsWith("<html")) {
30188
30579
  return parseHTML(html).document;
@@ -30248,7 +30639,7 @@ function isRelativeUrl(url) {
30248
30639
  return !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("//") && !url.startsWith("data:") && !isAbsolute4(url);
30249
30640
  }
30250
30641
  function safeReadFile(filePath) {
30251
- if (!existsSync25(filePath)) return null;
30642
+ if (!existsSync26(filePath)) return null;
30252
30643
  try {
30253
30644
  return readFileSync17(filePath, "utf-8");
30254
30645
  } catch {
@@ -30256,7 +30647,7 @@ function safeReadFile(filePath) {
30256
30647
  }
30257
30648
  }
30258
30649
  function safeReadFileBuffer(filePath) {
30259
- if (!existsSync25(filePath)) return null;
30650
+ if (!existsSync26(filePath)) return null;
30260
30651
  try {
30261
30652
  return readFileSync17(filePath);
30262
30653
  } catch {
@@ -30449,8 +30840,8 @@ function stripJsCommentsParserSafe(source) {
30449
30840
  }
30450
30841
  }
30451
30842
  async function bundleToSingleHtml(projectDir, options) {
30452
- const indexPath = join27(projectDir, "index.html");
30453
- if (!existsSync25(indexPath)) throw new Error("index.html not found in project directory");
30843
+ const indexPath = join28(projectDir, "index.html");
30844
+ if (!existsSync26(indexPath)) throw new Error("index.html not found in project directory");
30454
30845
  const rawHtml = readFileSync17(indexPath, "utf-8");
30455
30846
  const compiled = await compileHtml(rawHtml, projectDir, options?.probeMediaDuration);
30456
30847
  const staticGuard = validateHyperframeHtmlContract(compiled);
@@ -30531,11 +30922,11 @@ async function bundleToSingleHtml(projectDir, options) {
30531
30922
  console.warn(`[Bundler] Composition file not found: ${src}`);
30532
30923
  continue;
30533
30924
  }
30534
- const compDoc = parseHTMLContent(compHtml);
30925
+ const compDoc = parseHTMLContent2(compHtml);
30535
30926
  const compId = hostEl.getAttribute("data-composition-id");
30536
30927
  const contentRoot = compDoc.querySelector("template");
30537
30928
  const contentHtml = contentRoot ? contentRoot.innerHTML || "" : compDoc.body.innerHTML || "";
30538
- const contentDoc = parseHTMLContent(contentHtml);
30929
+ const contentDoc = parseHTMLContent2(contentHtml);
30539
30930
  const innerRoot = compId ? contentDoc.querySelector(`[data-composition-id="${compId}"]`) : contentDoc.querySelector("[data-composition-id]");
30540
30931
  if (!contentRoot && compDoc.head) {
30541
30932
  for (const s2 of [...compDoc.head.querySelectorAll("style")]) {
@@ -30600,7 +30991,7 @@ async function bundleToSingleHtml(projectDir, options) {
30600
30991
  if (!host) continue;
30601
30992
  if (host.children.length > 0) continue;
30602
30993
  const templateHtml = templateEl.innerHTML || "";
30603
- const innerDoc = parseHTMLContent(templateHtml);
30994
+ const innerDoc = parseHTMLContent2(templateHtml);
30604
30995
  const innerRoot = innerDoc.querySelector(`[data-composition-id="${compId}"]`);
30605
30996
  if (innerRoot) {
30606
30997
  for (const styleEl of [...innerRoot.querySelectorAll("style")]) {
@@ -30725,8 +31116,8 @@ var init_compiler = __esm({
30725
31116
  });
30726
31117
 
30727
31118
  // ../producer/src/services/hyperframeRuntimeLoader.ts
30728
- import { createHash as createHash2 } from "crypto";
30729
- import { existsSync as existsSync26, readFileSync as readFileSync18 } from "fs";
31119
+ import { createHash as createHash3 } from "crypto";
31120
+ import { existsSync as existsSync27, readFileSync as readFileSync18 } from "fs";
30730
31121
  import { dirname as dirname9, resolve as resolve13 } from "path";
30731
31122
  import { fileURLToPath as fileURLToPath2 } from "url";
30732
31123
  function resolveHyperframeManifestPath() {
@@ -30739,7 +31130,7 @@ function resolveHyperframeManifestPath() {
30739
31130
  MODULE_RELATIVE_MANIFEST_PATH
30740
31131
  ];
30741
31132
  for (const candidate of candidates) {
30742
- if (existsSync26(candidate)) {
31133
+ if (existsSync27(candidate)) {
30743
31134
  return candidate;
30744
31135
  }
30745
31136
  }
@@ -30750,7 +31141,7 @@ function getVerifiedHyperframeRuntimeSource() {
30750
31141
  }
30751
31142
  function resolveVerifiedHyperframeRuntime() {
30752
31143
  const manifestPath = resolveHyperframeManifestPath();
30753
- if (!existsSync26(manifestPath)) {
31144
+ if (!existsSync27(manifestPath)) {
30754
31145
  throw new Error(
30755
31146
  `[HyperframeRuntimeLoader] Missing manifest at ${manifestPath}. Build core runtime artifacts before rendering.`
30756
31147
  );
@@ -30764,11 +31155,11 @@ function resolveVerifiedHyperframeRuntime() {
30764
31155
  );
30765
31156
  }
30766
31157
  const runtimePath = resolve13(dirname9(manifestPath), runtimeFileName);
30767
- if (!existsSync26(runtimePath)) {
31158
+ if (!existsSync27(runtimePath)) {
30768
31159
  throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
30769
31160
  }
30770
31161
  const runtimeSource = readFileSync18(runtimePath, "utf8");
30771
- const runtimeSha = createHash2("sha256").update(runtimeSource, "utf8").digest("hex");
31162
+ const runtimeSha = createHash3("sha256").update(runtimeSource, "utf8").digest("hex");
30772
31163
  if (runtimeSha !== manifest.sha256) {
30773
31164
  throw new Error(
30774
31165
  `[HyperframeRuntimeLoader] Runtime checksum mismatch. expected=${manifest.sha256} actual=${runtimeSha}`
@@ -30806,16 +31197,16 @@ var init_hyperframeRuntimeLoader = __esm({
30806
31197
  // ../producer/src/services/fileServer.ts
30807
31198
  import { Hono as Hono3 } from "hono";
30808
31199
  import { serve as serve2 } from "@hono/node-server";
30809
- import { readFileSync as readFileSync19, existsSync as existsSync27, realpathSync, statSync as statSync7 } from "fs";
30810
- import { join as join28, extname as extname7, resolve as resolve14, sep as sep3 } from "path";
31200
+ import { readFileSync as readFileSync19, existsSync as existsSync28, realpathSync, statSync as statSync8 } from "fs";
31201
+ import { join as join29, extname as extname7, resolve as resolve14, sep as sep3 } from "path";
30811
31202
  function isPathInside(child, parent, options = {}) {
30812
31203
  const { resolveSymlinks = false, pathModule } = options;
30813
31204
  const resolveFn = pathModule?.resolve ?? resolve14;
30814
31205
  const separator = pathModule?.sep ?? sep3;
30815
31206
  const resolvedChild = resolveFn(child);
30816
31207
  const resolvedParent = resolveFn(parent);
30817
- const normalizedChild = resolveSymlinks && existsSync27(resolvedChild) ? realpathSync.native(resolvedChild) : resolvedChild;
30818
- const normalizedParent = resolveSymlinks && existsSync27(resolvedParent) ? realpathSync.native(resolvedParent) : resolvedParent;
31208
+ const normalizedChild = resolveSymlinks && existsSync28(resolvedChild) ? realpathSync.native(resolvedChild) : resolvedChild;
31209
+ const normalizedParent = resolveSymlinks && existsSync28(resolvedParent) ? realpathSync.native(resolvedParent) : resolvedParent;
30819
31210
  if (normalizedChild === normalizedParent) return true;
30820
31211
  const parentWithSep = normalizedParent.endsWith(separator) ? normalizedParent : normalizedParent + separator;
30821
31212
  return normalizedChild.startsWith(parentWithSep);
@@ -30904,14 +31295,14 @@ function createFileServer2(options) {
30904
31295
  const relativePath = requestPath.replace(/^\//, "");
30905
31296
  let filePath = null;
30906
31297
  if (compiledDir) {
30907
- const candidate = join28(compiledDir, relativePath);
30908
- if (existsSync27(candidate) && isPathInside(candidate, compiledDir, { resolveSymlinks: true }) && statSync7(candidate).isFile()) {
31298
+ const candidate = join29(compiledDir, relativePath);
31299
+ if (existsSync28(candidate) && isPathInside(candidate, compiledDir, { resolveSymlinks: true }) && statSync8(candidate).isFile()) {
30909
31300
  filePath = candidate;
30910
31301
  }
30911
31302
  }
30912
31303
  if (!filePath) {
30913
- const candidate = join28(projectDir, relativePath);
30914
- if (existsSync27(candidate) && isPathInside(candidate, projectDir, { resolveSymlinks: true }) && statSync7(candidate).isFile()) {
31304
+ const candidate = join29(projectDir, relativePath);
31305
+ if (existsSync28(candidate) && isPathInside(candidate, projectDir, { resolveSymlinks: true }) && statSync8(candidate).isFile()) {
30915
31306
  filePath = candidate;
30916
31307
  }
30917
31308
  }
@@ -31285,7 +31676,7 @@ var init_ffprobe2 = __esm({
31285
31676
  });
31286
31677
 
31287
31678
  // ../producer/src/utils/paths.ts
31288
- import { resolve as resolve15, basename as basename2, join as join29, relative as relative2, isAbsolute as isAbsolute5 } from "path";
31679
+ import { resolve as resolve15, basename as basename2, join as join30, relative as relative2, isAbsolute as isAbsolute5 } from "path";
31289
31680
  function isPathInside2(childPath, parentPath) {
31290
31681
  const absChild = resolve15(childPath);
31291
31682
  const absParent = resolve15(parentPath);
@@ -31306,7 +31697,7 @@ function toExternalAssetKey(absPath) {
31306
31697
  function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
31307
31698
  const absoluteProjectDir = resolve15(projectDir);
31308
31699
  const projectName = basename2(absoluteProjectDir);
31309
- const resolvedOutputPath = outputPath ?? join29(rendersDir, `${projectName}.mp4`);
31700
+ const resolvedOutputPath = outputPath ?? join30(rendersDir, `${projectName}.mp4`);
31310
31701
  const absoluteOutputPath = resolve15(resolvedOutputPath);
31311
31702
  return { absoluteProjectDir, absoluteOutputPath };
31312
31703
  }
@@ -31379,9 +31770,9 @@ var init_fontData_generated = __esm({
31379
31770
  });
31380
31771
 
31381
31772
  // ../producer/src/services/deterministicFonts.ts
31382
- import { existsSync as existsSync28, mkdirSync as mkdirSync15, readFileSync as readFileSync20, writeFileSync as writeFileSync10 } from "fs";
31773
+ import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
31383
31774
  import { homedir as homedir7 } from "os";
31384
- import { join as join30 } from "path";
31775
+ import { join as join31 } from "path";
31385
31776
  function normalizeFamilyName(family) {
31386
31777
  return family.trim().replace(/^['"]|['"]$/g, "").trim().toLowerCase();
31387
31778
  }
@@ -31492,14 +31883,14 @@ function fontSlug(familyName) {
31492
31883
  return familyName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
31493
31884
  }
31494
31885
  function fontCacheDir(slug) {
31495
- const dir = join30(GOOGLE_FONTS_CACHE_DIR, slug);
31496
- if (!existsSync28(dir)) {
31497
- mkdirSync15(dir, { recursive: true });
31886
+ const dir = join31(GOOGLE_FONTS_CACHE_DIR, slug);
31887
+ if (!existsSync29(dir)) {
31888
+ mkdirSync16(dir, { recursive: true });
31498
31889
  }
31499
31890
  return dir;
31500
31891
  }
31501
31892
  function cachedWoff2Path(slug, weight, style) {
31502
- return join30(fontCacheDir(slug), `${weight}-${style}.woff2`);
31893
+ return join31(fontCacheDir(slug), `${weight}-${style}.woff2`);
31503
31894
  }
31504
31895
  async function fetchGoogleFont(familyName) {
31505
31896
  const slug = fontSlug(familyName);
@@ -31525,12 +31916,12 @@ async function fetchGoogleFont(familyName) {
31525
31916
  const woff2Url = match[3] || "";
31526
31917
  if (!woff2Url) continue;
31527
31918
  const cachePath2 = cachedWoff2Path(slug, weight, style);
31528
- if (!existsSync28(cachePath2)) {
31919
+ if (!existsSync29(cachePath2)) {
31529
31920
  try {
31530
31921
  const fontRes = await fetch(woff2Url);
31531
31922
  if (!fontRes.ok) continue;
31532
31923
  const buffer = Buffer.from(await fontRes.arrayBuffer());
31533
- writeFileSync10(cachePath2, buffer);
31924
+ writeFileSync11(cachePath2, buffer);
31534
31925
  } catch {
31535
31926
  continue;
31536
31927
  }
@@ -31711,14 +32102,14 @@ var init_deterministicFonts = __esm({
31711
32102
  poppins: "poppins",
31712
32103
  "segoe ui": "roboto"
31713
32104
  };
31714
- GOOGLE_FONTS_CACHE_DIR = join30(homedir7(), ".cache", "hyperframes", "fonts");
32105
+ GOOGLE_FONTS_CACHE_DIR = join31(homedir7(), ".cache", "hyperframes", "fonts");
31715
32106
  WOFF2_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
31716
32107
  }
31717
32108
  });
31718
32109
 
31719
32110
  // ../producer/src/services/htmlCompiler.ts
31720
- import { readFileSync as readFileSync21, existsSync as existsSync29, mkdirSync as mkdirSync16 } from "fs";
31721
- import { join as join31, dirname as dirname10, resolve as resolve16 } from "path";
32111
+ import { readFileSync as readFileSync21, existsSync as existsSync30, mkdirSync as mkdirSync17 } from "fs";
32112
+ import { join as join32, dirname as dirname10, resolve as resolve16 } from "path";
31722
32113
  import postcss from "postcss";
31723
32114
  function dedupeElementsById(elements) {
31724
32115
  const deduped = /* @__PURE__ */ new Map();
@@ -31769,16 +32160,16 @@ function detectRenderModeHints(html) {
31769
32160
  async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
31770
32161
  let filePath = src;
31771
32162
  if (isHttpUrl(src)) {
31772
- if (!existsSync29(downloadDir)) mkdirSync16(downloadDir, { recursive: true });
32163
+ if (!existsSync30(downloadDir)) mkdirSync17(downloadDir, { recursive: true });
31773
32164
  try {
31774
32165
  filePath = await downloadToTemp(src, downloadDir);
31775
32166
  } catch {
31776
32167
  return { duration: 0, resolvedPath: src };
31777
32168
  }
31778
32169
  } else if (!filePath.startsWith("/")) {
31779
- filePath = join31(baseDir, filePath);
32170
+ filePath = join32(baseDir, filePath);
31780
32171
  }
31781
- if (!existsSync29(filePath)) {
32172
+ if (!existsSync30(filePath)) {
31782
32173
  return { duration: 0, resolvedPath: filePath };
31783
32174
  }
31784
32175
  const metadata = tagName19 === "video" ? await extractMediaMetadata(filePath) : await extractAudioMetadata(filePath);
@@ -31847,7 +32238,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
31847
32238
  if (visited.has(filePath)) {
31848
32239
  continue;
31849
32240
  }
31850
- if (!existsSync29(filePath)) {
32241
+ if (!existsSync30(filePath)) {
31851
32242
  continue;
31852
32243
  }
31853
32244
  const rawSubHtml = readFileSync21(filePath, "utf-8");
@@ -32057,7 +32448,7 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
32057
32448
  let compHtml = subCompositions.get(srcPath) || null;
32058
32449
  if (!compHtml) {
32059
32450
  const filePath = resolve16(projectDir, srcPath);
32060
- if (existsSync29(filePath)) {
32451
+ if (existsSync30(filePath)) {
32061
32452
  compHtml = readFileSync21(filePath, "utf-8");
32062
32453
  }
32063
32454
  }
@@ -32285,7 +32676,7 @@ function collectExternalAssets(html, projectDir) {
32285
32676
  if (isPathInside2(absPath, absProjectDir)) {
32286
32677
  return null;
32287
32678
  }
32288
- if (!existsSync29(absPath)) return null;
32679
+ if (!existsSync30(absPath)) return null;
32289
32680
  const safeKey = toExternalAssetKey(absPath);
32290
32681
  externalAssets.set(safeKey, absPath);
32291
32682
  return safeKey;
@@ -32481,9 +32872,10 @@ async function recompileWithResolutions(compiled, resolutions, projectDir, downl
32481
32872
  const mainVideos = parseVideoElements(html);
32482
32873
  const mainAudios = parseAudioElements(html);
32483
32874
  const mainImages = parseImageElements(html);
32484
- const videos = dedupeElementsById([...mainVideos, ...subVideos]);
32485
- const audios = dedupeElementsById([...mainAudios, ...subAudios]);
32486
- const images = dedupeElementsById([...mainImages, ...subImages]);
32875
+ const hasSubMedia = subVideos.length > 0 || subAudios.length > 0 || subImages.length > 0;
32876
+ const videos = hasSubMedia ? dedupeElementsById([...mainVideos, ...subVideos]) : compiled.videos;
32877
+ const audios = hasSubMedia ? dedupeElementsById([...mainAudios, ...subAudios]) : compiled.audios;
32878
+ const images = hasSubMedia ? dedupeElementsById([...mainImages, ...subImages]) : compiled.images;
32487
32879
  const remaining = compiled.unresolvedCompositions.filter(
32488
32880
  (c2) => !resolutions.some((r2) => r2.id === c2.id)
32489
32881
  );
@@ -32560,18 +32952,124 @@ var init_logger = __esm({
32560
32952
  }
32561
32953
  });
32562
32954
 
32955
+ // ../producer/src/services/frameDirCache.ts
32956
+ import { readdirSync as readdirSync12 } from "fs";
32957
+ function getMaxFrameIndex(frameDir) {
32958
+ const cached2 = cache.get(frameDir);
32959
+ if (cached2 !== void 0) {
32960
+ cache.delete(frameDir);
32961
+ cache.set(frameDir, cached2);
32962
+ return cached2;
32963
+ }
32964
+ let max = 0;
32965
+ try {
32966
+ for (const name of readdirSync12(frameDir)) {
32967
+ const m2 = FRAME_FILENAME_RE.exec(name);
32968
+ if (!m2) continue;
32969
+ const n = Number(m2[1]);
32970
+ if (Number.isFinite(n) && n > max) max = n;
32971
+ }
32972
+ } catch {
32973
+ }
32974
+ if (cache.size >= MAX_ENTRIES) {
32975
+ const oldest = cache.keys().next().value;
32976
+ if (oldest !== void 0) cache.delete(oldest);
32977
+ }
32978
+ cache.set(frameDir, max);
32979
+ return max;
32980
+ }
32981
+ function clearMaxFrameIndex(frameDir) {
32982
+ return cache.delete(frameDir);
32983
+ }
32984
+ var cache, FRAME_FILENAME_RE, MAX_ENTRIES;
32985
+ var init_frameDirCache = __esm({
32986
+ "../producer/src/services/frameDirCache.ts"() {
32987
+ "use strict";
32988
+ cache = /* @__PURE__ */ new Map();
32989
+ FRAME_FILENAME_RE = /^frame_(\d+)\.png$/;
32990
+ MAX_ENTRIES = 1e3;
32991
+ }
32992
+ });
32993
+
32994
+ // ../producer/src/services/hdrImageTransferCache.ts
32995
+ function createHdrImageTransferCache(options = {}) {
32996
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
32997
+ if (!Number.isInteger(maxBytes) || maxBytes < 0) {
32998
+ throw new Error(
32999
+ `createHdrImageTransferCache: maxBytes must be a non-negative integer, got ${String(maxBytes)}`
33000
+ );
33001
+ }
33002
+ const entries2 = /* @__PURE__ */ new Map();
33003
+ let totalBytes = 0;
33004
+ function makeKey(imageId, targetTransfer) {
33005
+ return `${imageId}|${targetTransfer}`;
33006
+ }
33007
+ function evictUntilRoom(needed) {
33008
+ while (totalBytes + needed > maxBytes && entries2.size > 0) {
33009
+ const lruKey = entries2.keys().next().value;
33010
+ if (lruKey === void 0) break;
33011
+ const evicted = entries2.get(lruKey);
33012
+ if (evicted) totalBytes -= evicted.byteLength;
33013
+ entries2.delete(lruKey);
33014
+ }
33015
+ }
33016
+ return {
33017
+ getConverted(imageId, sourceTransfer, targetTransfer, source) {
33018
+ if (sourceTransfer === targetTransfer) {
33019
+ return source;
33020
+ }
33021
+ if (maxBytes === 0) {
33022
+ const fresh = Buffer.from(source);
33023
+ convertTransfer(fresh, sourceTransfer, targetTransfer);
33024
+ return fresh;
33025
+ }
33026
+ const key2 = makeKey(imageId, targetTransfer);
33027
+ const existing = entries2.get(key2);
33028
+ if (existing) {
33029
+ entries2.delete(key2);
33030
+ entries2.set(key2, existing);
33031
+ return existing;
33032
+ }
33033
+ const converted = Buffer.from(source);
33034
+ convertTransfer(converted, sourceTransfer, targetTransfer);
33035
+ if (converted.byteLength > maxBytes) {
33036
+ return converted;
33037
+ }
33038
+ evictUntilRoom(converted.byteLength);
33039
+ entries2.set(key2, converted);
33040
+ totalBytes += converted.byteLength;
33041
+ return converted;
33042
+ },
33043
+ size() {
33044
+ return entries2.size;
33045
+ },
33046
+ bytesUsed() {
33047
+ return totalBytes;
33048
+ }
33049
+ };
33050
+ }
33051
+ var DEFAULT_MAX_BYTES;
33052
+ var init_hdrImageTransferCache = __esm({
33053
+ "../producer/src/services/hdrImageTransferCache.ts"() {
33054
+ "use strict";
33055
+ init_src2();
33056
+ DEFAULT_MAX_BYTES = 200 * 1024 * 1024;
33057
+ }
33058
+ });
33059
+
32563
33060
  // ../producer/src/services/renderOrchestrator.ts
32564
33061
  import {
32565
- existsSync as existsSync30,
32566
- mkdirSync as mkdirSync17,
33062
+ existsSync as existsSync31,
33063
+ mkdirSync as mkdirSync18,
32567
33064
  rmSync as rmSync7,
32568
33065
  readFileSync as readFileSync22,
32569
- readdirSync as readdirSync11,
32570
- writeFileSync as writeFileSync11,
33066
+ readdirSync as readdirSync13,
33067
+ statSync as statSync9,
33068
+ writeFileSync as writeFileSync12,
32571
33069
  copyFileSync as copyFileSync2,
32572
33070
  appendFileSync
32573
33071
  } from "fs";
32574
- import { join as join32, dirname as dirname11, resolve as resolve17 } from "path";
33072
+ import { join as join33, dirname as dirname11, resolve as resolve17 } from "path";
32575
33073
  import { randomUUID as randomUUID2 } from "crypto";
32576
33074
  import { freemem as freemem2 } from "os";
32577
33075
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -32584,21 +33082,32 @@ async function safeCleanup(label2, fn, log2 = defaultLogger) {
32584
33082
  });
32585
33083
  }
32586
33084
  }
32587
- function getMaxFrameIndex(frameDir) {
32588
- const cached2 = frameDirMaxIndexCache.get(frameDir);
32589
- if (cached2 !== void 0) return cached2;
32590
- let max = 0;
32591
- try {
32592
- for (const name of readdirSync11(frameDir)) {
32593
- const m2 = FRAME_FILENAME_RE.exec(name);
32594
- if (!m2) continue;
32595
- const n = Number(m2[1]);
32596
- if (Number.isFinite(n) && n > max) max = n;
33085
+ function sampleDirectoryBytes(dir) {
33086
+ let total = 0;
33087
+ const stack = [dir];
33088
+ while (stack.length > 0) {
33089
+ const current = stack.pop();
33090
+ if (!current) continue;
33091
+ let entries2 = [];
33092
+ try {
33093
+ entries2 = readdirSync13(current);
33094
+ } catch {
33095
+ continue;
33096
+ }
33097
+ for (const name of entries2) {
33098
+ const full = join33(current, name);
33099
+ try {
33100
+ const st2 = statSync9(full);
33101
+ if (st2.isDirectory()) {
33102
+ stack.push(full);
33103
+ } else if (st2.isFile()) {
33104
+ total += st2.size;
33105
+ }
33106
+ } catch {
33107
+ }
32597
33108
  }
32598
- } catch {
32599
33109
  }
32600
- frameDirMaxIndexCache.set(frameDir, max);
32601
- return max;
33110
+ return total;
32602
33111
  }
32603
33112
  function countNonZeroAlpha(rgba) {
32604
33113
  let n = 0;
@@ -32615,6 +33124,9 @@ function countNonZeroRgb48(buf) {
32615
33124
  }
32616
33125
  return n;
32617
33126
  }
33127
+ function projectBrowserEndToCompositionTimeline(existingStart, browserStart, browserEnd) {
33128
+ return browserEnd + (existingStart - browserStart);
33129
+ }
32618
33130
  function updateJobStatus(job, status, stage, progress, onProgress) {
32619
33131
  job.status = status;
32620
33132
  job.currentStage = stage;
@@ -32658,21 +33170,21 @@ function installDebugLogger(logPath, log2 = defaultLogger) {
32658
33170
  };
32659
33171
  }
32660
33172
  function writeCompiledArtifacts(compiled, workDir, includeSummary) {
32661
- const compileDir = join32(workDir, "compiled");
32662
- mkdirSync17(compileDir, { recursive: true });
32663
- writeFileSync11(join32(compileDir, "index.html"), compiled.html, "utf-8");
33173
+ const compileDir = join33(workDir, "compiled");
33174
+ mkdirSync18(compileDir, { recursive: true });
33175
+ writeFileSync12(join33(compileDir, "index.html"), compiled.html, "utf-8");
32664
33176
  for (const [srcPath, html] of compiled.subCompositions) {
32665
- const outPath = join32(compileDir, srcPath);
32666
- mkdirSync17(dirname11(outPath), { recursive: true });
32667
- writeFileSync11(outPath, html, "utf-8");
33177
+ const outPath = join33(compileDir, srcPath);
33178
+ mkdirSync18(dirname11(outPath), { recursive: true });
33179
+ writeFileSync12(outPath, html, "utf-8");
32668
33180
  }
32669
33181
  for (const [relativePath, absolutePath] of compiled.externalAssets) {
32670
- const outPath = resolve17(join32(compileDir, relativePath));
33182
+ const outPath = resolve17(join33(compileDir, relativePath));
32671
33183
  if (!isPathInside2(outPath, compileDir)) {
32672
33184
  console.warn(`[Render] Skipping external asset with unsafe path: ${relativePath}`);
32673
33185
  continue;
32674
33186
  }
32675
- mkdirSync17(dirname11(outPath), { recursive: true });
33187
+ mkdirSync18(dirname11(outPath), { recursive: true });
32676
33188
  copyFileSync2(absolutePath, outPath);
32677
33189
  }
32678
33190
  if (includeSummary) {
@@ -32697,7 +33209,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
32697
33209
  subCompositions: Array.from(compiled.subCompositions.keys()),
32698
33210
  renderModeHints: compiled.renderModeHints
32699
33211
  };
32700
- writeFileSync11(join32(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
33212
+ writeFileSync12(join33(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
32701
33213
  }
32702
33214
  }
32703
33215
  function applyRenderModeHints(cfg, compiled, log2 = defaultLogger) {
@@ -32718,8 +33230,8 @@ function blitHdrVideoLayer(canvas, el, time, fps, hdrFrameDirs, hdrStartTimes, w
32718
33230
  if (videoFrameIndex < 1) return;
32719
33231
  const maxIndex = getMaxFrameIndex(frameDir);
32720
33232
  const effectiveIndex = maxIndex > 0 ? Math.min(videoFrameIndex, maxIndex) : videoFrameIndex;
32721
- const framePath = join32(frameDir, `frame_${String(effectiveIndex).padStart(4, "0")}.png`);
32722
- if (!existsSync30(framePath)) {
33233
+ const framePath = join33(frameDir, `frame_${String(effectiveIndex).padStart(4, "0")}.png`);
33234
+ if (!existsSync31(framePath)) {
32723
33235
  return;
32724
33236
  }
32725
33237
  try {
@@ -32765,17 +33277,13 @@ function blitHdrVideoLayer(canvas, el, time, fps, hdrFrameDirs, hdrStartTimes, w
32765
33277
  }
32766
33278
  }
32767
33279
  }
32768
- function blitHdrImageLayer(canvas, el, hdrImageBuffers, width, height, log2, sourceTransfer, targetTransfer) {
33280
+ function blitHdrImageLayer(canvas, el, hdrImageBuffers, hdrImageTransferCache, width, height, log2, sourceTransfer, targetTransfer) {
32769
33281
  const buf = hdrImageBuffers.get(el.id);
32770
33282
  if (!buf) {
32771
33283
  return;
32772
33284
  }
32773
33285
  try {
32774
- let hdrRgb = buf.data;
32775
- if (sourceTransfer && targetTransfer && sourceTransfer !== targetTransfer) {
32776
- hdrRgb = Buffer.from(buf.data);
32777
- convertTransfer(hdrRgb, sourceTransfer, targetTransfer);
32778
- }
33286
+ const hdrRgb = sourceTransfer && targetTransfer ? hdrImageTransferCache.getConverted(el.id, sourceTransfer, targetTransfer, buf.data) : buf.data;
32779
33287
  const viewportMatrix = parseTransformMatrix(el.transform);
32780
33288
  const br = el.borderRadius;
32781
33289
  const hasBorderRadius = br[0] > 0 || br[1] > 0 || br[2] > 0 || br[3] > 0;
@@ -32825,6 +33333,7 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
32825
33333
  effectiveHdr,
32826
33334
  nativeHdrImageIds,
32827
33335
  hdrImageBuffers,
33336
+ hdrImageTransferCache,
32828
33337
  hdrFrameDirs,
32829
33338
  hdrVideoStartTimes,
32830
33339
  imageTransfers,
@@ -32864,6 +33373,7 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
32864
33373
  canvas,
32865
33374
  layer.element,
32866
33375
  hdrImageBuffers,
33376
+ hdrImageTransferCache,
32867
33377
  width,
32868
33378
  height,
32869
33379
  log2,
@@ -32904,7 +33414,7 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
32904
33414
  const startTime = hdrVideoStartTimes.get(layer.element.id) ?? 0;
32905
33415
  const localTime = time - startTime;
32906
33416
  const frameNum = Math.floor(localTime * fps) + 1;
32907
- const expectedFrame = frameDir ? join32(frameDir, `frame_${String(frameNum).padStart(4, "0")}.png`) : null;
33417
+ const expectedFrame = frameDir ? join33(frameDir, `frame_${String(frameNum).padStart(4, "0")}.png`) : null;
32908
33418
  log2.info("[diag] hdr layer blit", {
32909
33419
  frame: debugFrameIndex,
32910
33420
  layerIdx,
@@ -32916,7 +33426,7 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
32916
33426
  localTime: localTime.toFixed(3),
32917
33427
  hdrFrameNum: frameNum,
32918
33428
  expectedFrame,
32919
- expectedFrameExists: expectedFrame ? existsSync30(expectedFrame) : false
33429
+ expectedFrameExists: expectedFrame ? existsSync31(expectedFrame) : false
32920
33430
  });
32921
33431
  }
32922
33432
  }
@@ -32941,8 +33451,8 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
32941
33451
  if (shouldLog && debugDumpDir) {
32942
33452
  const after2 = countNonZeroRgb48(canvas);
32943
33453
  const dumpName = `frame_${String(debugFrameIndex).padStart(4, "0")}_layer_${String(layerIdx).padStart(2, "0")}_dom.png`;
32944
- const dumpPath = join32(debugDumpDir, dumpName);
32945
- writeFileSync11(dumpPath, domPng);
33454
+ const dumpPath = join33(debugDumpDir, dumpName);
33455
+ writeFileSync12(dumpPath, domPng);
32946
33456
  log2.info("[diag] dom layer blit", {
32947
33457
  frame: debugFrameIndex,
32948
33458
  layerIdx,
@@ -33015,8 +33525,8 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
33015
33525
  async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
33016
33526
  const moduleDir = dirname11(fileURLToPath3(import.meta.url));
33017
33527
  const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve17(process.env.PRODUCER_RENDERS_DIR, "..") : resolve17(moduleDir, "../..");
33018
- const debugDir = join32(producerRoot, ".debug");
33019
- const workDir = job.config.debug ? join32(debugDir, job.id) : join32(dirname11(outputPath), `work-${job.id}`);
33528
+ const debugDir = join33(producerRoot, ".debug");
33529
+ const workDir = job.config.debug ? join33(debugDir, job.id) : join33(dirname11(outputPath), `work-${job.id}`);
33020
33530
  const pipelineStart = Date.now();
33021
33531
  const log2 = job.config.logger ?? defaultLogger;
33022
33532
  let fileServer = null;
@@ -33028,7 +33538,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33028
33538
  videoExtractionFailures: 0,
33029
33539
  imageDecodeFailures: 0
33030
33540
  };
33031
- const perfOutputPath = join32(workDir, "perf-summary.json");
33541
+ const perfOutputPath = join33(workDir, "perf-summary.json");
33032
33542
  const cfg = { ...job.config.producerConfig ?? resolveConfig() };
33033
33543
  const outputFormat = job.config.format ?? "mp4";
33034
33544
  const isWebm = outputFormat === "webm";
@@ -33061,22 +33571,22 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33061
33571
  };
33062
33572
  job.startedAt = /* @__PURE__ */ new Date();
33063
33573
  assertNotAborted();
33064
- if (!existsSync30(workDir)) mkdirSync17(workDir, { recursive: true });
33574
+ if (!existsSync31(workDir)) mkdirSync18(workDir, { recursive: true });
33065
33575
  if (job.config.debug) {
33066
- const logPath = join32(workDir, "render.log");
33576
+ const logPath = join33(workDir, "render.log");
33067
33577
  restoreLogger = installDebugLogger(logPath, log2);
33068
33578
  }
33069
33579
  const entryFile = job.config.entryFile || "index.html";
33070
- let htmlPath = join32(projectDir, entryFile);
33071
- if (!existsSync30(htmlPath)) {
33580
+ let htmlPath = join33(projectDir, entryFile);
33581
+ if (!existsSync31(htmlPath)) {
33072
33582
  throw new Error(`Entry file not found: ${htmlPath}`);
33073
33583
  }
33074
33584
  assertNotAborted();
33075
33585
  const rawEntry = readFileSync22(htmlPath, "utf-8");
33076
33586
  if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
33077
- const wrapperPath = join32(workDir, "standalone-entry.html");
33078
- const projectIndexPath = join32(projectDir, "index.html");
33079
- if (!existsSync30(projectIndexPath)) {
33587
+ const wrapperPath = join33(workDir, "standalone-entry.html");
33588
+ const projectIndexPath = join33(projectDir, "index.html");
33589
+ if (!existsSync31(projectIndexPath)) {
33080
33590
  throw new Error(
33081
33591
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
33082
33592
  );
@@ -33090,7 +33600,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33090
33600
  `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
33091
33601
  );
33092
33602
  }
33093
- writeFileSync11(wrapperPath, standaloneHtml, "utf-8");
33603
+ writeFileSync12(wrapperPath, standaloneHtml, "utf-8");
33094
33604
  htmlPath = wrapperPath;
33095
33605
  log2.info("Extracted standalone entry from index.html host context", {
33096
33606
  entryFile
@@ -33099,7 +33609,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33099
33609
  const stage1Start = Date.now();
33100
33610
  updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress);
33101
33611
  const compileStart = Date.now();
33102
- let compiled = await compileForRender(projectDir, htmlPath, join32(workDir, "downloads"));
33612
+ let compiled = await compileForRender(projectDir, htmlPath, join33(workDir, "downloads"));
33103
33613
  assertNotAborted();
33104
33614
  perfStages.compileOnlyMs = Date.now() - compileStart;
33105
33615
  applyRenderModeHints(cfg, compiled, log2);
@@ -33131,7 +33641,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33131
33641
  reasons.push(`${compiled.unresolvedCompositions.length} unresolved composition(s)`);
33132
33642
  fileServer = await createFileServer2({
33133
33643
  projectDir,
33134
- compiledDir: join32(workDir, "compiled"),
33644
+ compiledDir: join33(workDir, "compiled"),
33135
33645
  port: 0,
33136
33646
  preHeadScripts: [VIRTUAL_TIME_SHIM]
33137
33647
  });
@@ -33145,7 +33655,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33145
33655
  };
33146
33656
  probeSession = await createCaptureSession(
33147
33657
  fileServer.url,
33148
- join32(workDir, "probe"),
33658
+ join33(workDir, "probe"),
33149
33659
  captureOpts,
33150
33660
  null,
33151
33661
  cfg
@@ -33177,7 +33687,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33177
33687
  compiled,
33178
33688
  resolutions,
33179
33689
  projectDir,
33180
- join32(workDir, "downloads")
33690
+ join33(workDir, "downloads")
33181
33691
  );
33182
33692
  assertNotAborted();
33183
33693
  composition.videos = compiled.videos;
@@ -33204,10 +33714,15 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33204
33714
  if (existing.src !== src) {
33205
33715
  existing.src = src;
33206
33716
  }
33207
- if (el.end > 0 && (existing.end <= 0 || Math.abs(existing.end - el.end) > 1e-4)) {
33208
- existing.end = el.end;
33717
+ const projectedEnd = projectBrowserEndToCompositionTimeline(
33718
+ existing.start,
33719
+ el.start,
33720
+ el.end
33721
+ );
33722
+ if (projectedEnd > 0 && (existing.end <= 0 || Math.abs(existing.end - projectedEnd) > BROWSER_MEDIA_EPSILON)) {
33723
+ existing.end = projectedEnd;
33209
33724
  }
33210
- if (el.mediaStart > 0 && (existing.mediaStart <= 0 || Math.abs(existing.mediaStart - el.mediaStart) > 1e-4)) {
33725
+ if (el.mediaStart > 0 && (existing.mediaStart <= 0 || Math.abs(existing.mediaStart - el.mediaStart) > BROWSER_MEDIA_EPSILON)) {
33211
33726
  existing.mediaStart = el.mediaStart;
33212
33727
  }
33213
33728
  if (el.hasAudio && !existing.hasAudio) {
@@ -33232,13 +33747,18 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33232
33747
  if (existing.src !== src) {
33233
33748
  existing.src = src;
33234
33749
  }
33235
- if (el.end > 0 && (existing.end <= 0 || Math.abs(existing.end - el.end) > 1e-4)) {
33236
- existing.end = el.end;
33750
+ const projectedEnd = projectBrowserEndToCompositionTimeline(
33751
+ existing.start,
33752
+ el.start,
33753
+ el.end
33754
+ );
33755
+ if (projectedEnd > 0 && (existing.end <= 0 || Math.abs(existing.end - projectedEnd) > BROWSER_MEDIA_EPSILON)) {
33756
+ existing.end = projectedEnd;
33237
33757
  }
33238
- if (el.mediaStart > 0 && (existing.mediaStart <= 0 || Math.abs(existing.mediaStart - el.mediaStart) > 1e-4)) {
33758
+ if (el.mediaStart > 0 && (existing.mediaStart <= 0 || Math.abs(existing.mediaStart - el.mediaStart) > BROWSER_MEDIA_EPSILON)) {
33239
33759
  existing.mediaStart = el.mediaStart;
33240
33760
  }
33241
- if (el.volume > 0 && Math.abs((existing.volume ?? 1) - el.volume) > 1e-4) {
33761
+ if (el.volume > 0 && Math.abs((existing.volume ?? 1) - el.volume) > BROWSER_MEDIA_EPSILON) {
33242
33762
  existing.volume = el.volume;
33243
33763
  }
33244
33764
  }
@@ -33317,7 +33837,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33317
33837
  const stage2Start = Date.now();
33318
33838
  updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
33319
33839
  let frameLookup = null;
33320
- const compiledDir = join32(workDir, "compiled");
33840
+ const compiledDir = join33(workDir, "compiled");
33321
33841
  let extractionResult = null;
33322
33842
  const nativeHdrVideoIds = /* @__PURE__ */ new Set();
33323
33843
  const videoTransfers = /* @__PURE__ */ new Map();
@@ -33326,10 +33846,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33326
33846
  composition.videos.map(async (v) => {
33327
33847
  let videoPath = v.src;
33328
33848
  if (!videoPath.startsWith("/")) {
33329
- const fromCompiled = existsSync30(join32(compiledDir, videoPath)) ? join32(compiledDir, videoPath) : join32(projectDir, videoPath);
33849
+ const fromCompiled = existsSync31(join33(compiledDir, videoPath)) ? join33(compiledDir, videoPath) : join33(projectDir, videoPath);
33330
33850
  videoPath = fromCompiled;
33331
33851
  }
33332
- if (!existsSync30(videoPath)) return;
33852
+ if (!existsSync31(videoPath)) return;
33333
33853
  const meta = await extractMediaMetadata(videoPath);
33334
33854
  if (isHdrColorSpace(meta.colorSpace)) {
33335
33855
  nativeHdrVideoIds.add(v.id);
@@ -33347,10 +33867,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33347
33867
  composition.images.map(async (img) => {
33348
33868
  let imgPath = img.src;
33349
33869
  if (!imgPath.startsWith("/")) {
33350
- const fromCompiled = existsSync30(join32(compiledDir, imgPath)) ? join32(compiledDir, imgPath) : join32(projectDir, imgPath);
33870
+ const fromCompiled = existsSync31(join33(compiledDir, imgPath)) ? join33(compiledDir, imgPath) : join33(projectDir, imgPath);
33351
33871
  imgPath = fromCompiled;
33352
33872
  }
33353
- if (!existsSync30(imgPath)) return null;
33873
+ if (!existsSync31(imgPath)) return null;
33354
33874
  const meta = await extractMediaMetadata(imgPath);
33355
33875
  if (isHdrColorSpace(meta.colorSpace)) {
33356
33876
  nativeHdrImageIds.add(img.id);
@@ -33366,9 +33886,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33366
33886
  extractionResult = await extractAllVideoFrames(
33367
33887
  composition.videos,
33368
33888
  projectDir,
33369
- { fps: job.config.fps, outputDir: join32(workDir, "video-frames") },
33889
+ { fps: job.config.fps, outputDir: join33(workDir, "video-frames") },
33370
33890
  abortSignal,
33371
- void 0,
33891
+ { extractCacheDir: cfg.extractCacheDir },
33372
33892
  compiledDir
33373
33893
  );
33374
33894
  assertNotAborted();
@@ -33424,13 +33944,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33424
33944
  }
33425
33945
  const stage3Start = Date.now();
33426
33946
  updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
33427
- const audioOutputPath = join32(workDir, "audio.aac");
33947
+ const audioOutputPath = join33(workDir, "audio.aac");
33428
33948
  let hasAudio = false;
33429
33949
  if (composition.audios.length > 0) {
33430
33950
  const audioResult = await processCompositionAudio(
33431
33951
  composition.audios,
33432
33952
  projectDir,
33433
- join32(workDir, "audio-work"),
33953
+ join33(workDir, "audio-work"),
33434
33954
  audioOutputPath,
33435
33955
  job.duration,
33436
33956
  abortSignal,
@@ -33448,14 +33968,14 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33448
33968
  if (!fileServer) {
33449
33969
  fileServer = await createFileServer2({
33450
33970
  projectDir,
33451
- compiledDir: join32(workDir, "compiled"),
33971
+ compiledDir: join33(workDir, "compiled"),
33452
33972
  port: 0,
33453
33973
  preHeadScripts: [VIRTUAL_TIME_SHIM]
33454
33974
  });
33455
33975
  assertNotAborted();
33456
33976
  }
33457
- const framesDir = join32(workDir, "captured-frames");
33458
- if (!existsSync30(framesDir)) mkdirSync17(framesDir, { recursive: true });
33977
+ const framesDir = join33(workDir, "captured-frames");
33978
+ if (!existsSync31(framesDir)) mkdirSync18(framesDir, { recursive: true });
33459
33979
  const captureOptions = {
33460
33980
  width,
33461
33981
  height,
@@ -33470,7 +33990,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33470
33990
  const workerCount = calculateOptimalWorkers(totalFrames, job.config.workers, cfg);
33471
33991
  const FORMAT_EXT2 = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
33472
33992
  const videoExt = FORMAT_EXT2[outputFormat] ?? ".mp4";
33473
- const videoOnlyPath = join32(workDir, `video-only${videoExt}`);
33993
+ const videoOnlyPath = join33(workDir, `video-only${videoExt}`);
33474
33994
  const nativeHdrIds = /* @__PURE__ */ new Set([...nativeHdrVideoIds, ...nativeHdrImageIds]);
33475
33995
  const hasHdrContent = effectiveHdr && nativeHdrIds.size > 0;
33476
33996
  const encoderHdr = hasHdrContent ? effectiveHdr : void 0;
@@ -33492,8 +34012,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33492
34012
  if (!hdrVideoIds.includes(v.id)) continue;
33493
34013
  let srcPath = v.src;
33494
34014
  if (!srcPath.startsWith("/")) {
33495
- const fromCompiled = join32(compiledDir, srcPath);
33496
- srcPath = existsSync30(fromCompiled) ? fromCompiled : join32(projectDir, srcPath);
34015
+ const fromCompiled = join33(compiledDir, srcPath);
34016
+ srcPath = existsSync31(fromCompiled) ? fromCompiled : join33(projectDir, srcPath);
33497
34017
  }
33498
34018
  hdrVideoSrcPaths.set(v.id, srcPath);
33499
34019
  }
@@ -33623,8 +34143,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33623
34143
  for (const [videoId, srcPath] of hdrVideoSrcPaths) {
33624
34144
  const video = composition.videos.find((v) => v.id === videoId);
33625
34145
  if (!video) continue;
33626
- const frameDir = join32(framesDir, `hdr_${videoId}`);
33627
- mkdirSync17(frameDir, { recursive: true });
34146
+ const frameDir = join33(framesDir, `hdr_${videoId}`);
34147
+ mkdirSync18(frameDir, { recursive: true });
33628
34148
  const duration = video.end - video.start;
33629
34149
  const dims = hdrExtractionDims.get(videoId) ?? { width, height };
33630
34150
  const ffmpegArgs = [
@@ -33643,7 +34163,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33643
34163
  "-c:v",
33644
34164
  "png",
33645
34165
  "-y",
33646
- join32(frameDir, "frame_%04d.png")
34166
+ join33(frameDir, "frame_%04d.png")
33647
34167
  ];
33648
34168
  const result = await runFfmpeg(ffmpegArgs, { signal: abortSignal });
33649
34169
  if (!result.success) {
@@ -33711,15 +34231,19 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33711
34231
  }
33712
34232
  }
33713
34233
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
33714
- const debugDumpDir = debugDumpEnabled ? join32(framesDir, "debug-composite") : null;
33715
- if (debugDumpDir && !existsSync30(debugDumpDir)) {
33716
- mkdirSync17(debugDumpDir, { recursive: true });
34234
+ const debugDumpDir = debugDumpEnabled ? join33(framesDir, "debug-composite") : null;
34235
+ if (debugDumpDir && !existsSync31(debugDumpDir)) {
34236
+ mkdirSync18(debugDumpDir, { recursive: true });
33717
34237
  }
33718
34238
  if (!effectiveHdr) {
33719
34239
  throw new Error(
33720
34240
  "Internal: HDR render path entered without effectiveHdr \u2014 this is a bug."
33721
34241
  );
33722
34242
  }
34243
+ const hdrCacheMaxBytes = process.env.HDR_TRANSFER_CACHE_MAX_BYTES ? Number(process.env.HDR_TRANSFER_CACHE_MAX_BYTES) : void 0;
34244
+ const hdrImageTransferCache = createHdrImageTransferCache(
34245
+ hdrCacheMaxBytes !== void 0 ? { maxBytes: hdrCacheMaxBytes } : {}
34246
+ );
33723
34247
  const hdrCompositeCtx = {
33724
34248
  log: log2,
33725
34249
  domSession,
@@ -33730,6 +34254,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33730
34254
  effectiveHdr,
33731
34255
  nativeHdrImageIds,
33732
34256
  hdrImageBuffers,
34257
+ hdrImageTransferCache,
33733
34258
  hdrFrameDirs,
33734
34259
  hdrVideoStartTimes,
33735
34260
  imageTransfers,
@@ -33790,6 +34315,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33790
34315
  sceneBuf,
33791
34316
  el,
33792
34317
  hdrImageBuffers,
34318
+ hdrImageTransferCache,
33793
34319
  width,
33794
34320
  height,
33795
34321
  log2,
@@ -33853,11 +34379,11 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33853
34379
  i2
33854
34380
  );
33855
34381
  if (debugDumpEnabled && debugDumpDir && i2 % 30 === 0) {
33856
- const previewPath = join32(
34382
+ const previewPath = join33(
33857
34383
  debugDumpDir,
33858
34384
  `frame_${String(i2).padStart(4, "0")}_final_rgb48le.bin`
33859
34385
  );
33860
- writeFileSync11(previewPath, normalCanvas);
34386
+ writeFileSync12(previewPath, normalCanvas);
33861
34387
  }
33862
34388
  hdrEncoder.writeFrame(normalCanvas);
33863
34389
  }
@@ -33877,7 +34403,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33877
34403
  error: err instanceof Error ? err.message : String(err)
33878
34404
  });
33879
34405
  }
33880
- frameDirMaxIndexCache.delete(frameDir);
34406
+ clearMaxFrameIndex(frameDir);
33881
34407
  hdrFrameDirs.delete(videoId);
33882
34408
  }
33883
34409
  cleanedUpVideos.add(videoId);
@@ -33928,7 +34454,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
33928
34454
  });
33929
34455
  }
33930
34456
  for (const frameDir of hdrFrameDirs.values()) {
33931
- frameDirMaxIndexCache.delete(frameDir);
34457
+ clearMaxFrameIndex(frameDir);
33932
34458
  }
33933
34459
  hdrFrameDirs.clear();
33934
34460
  }
@@ -34200,6 +34726,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
34200
34726
  updateJobStatus(job, "complete", "Render complete", 100, onProgress);
34201
34727
  const totalElapsed = Date.now() - pipelineStart;
34202
34728
  sampleMemory();
34729
+ const tmpPeakBytes = existsSync31(workDir) ? sampleDirectoryBytes(workDir) : 0;
34203
34730
  const perfSummary = {
34204
34731
  renderId: job.id,
34205
34732
  totalElapsedMs: totalElapsed,
@@ -34214,6 +34741,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
34214
34741
  videoCount: composition.videos.length,
34215
34742
  audioCount: composition.audios.length,
34216
34743
  stages: perfStages,
34744
+ videoExtractBreakdown: extractionResult?.phaseBreakdown,
34745
+ tmpPeakBytes,
34217
34746
  hdrDiagnostics: hdrDiagnostics.videoExtractionFailures > 0 || hdrDiagnostics.imageDecodeFailures > 0 ? { ...hdrDiagnostics } : void 0,
34218
34747
  captureAvgMs: totalFrames > 0 ? Math.round((perfStages.captureMs ?? 0) / totalFrames) : void 0,
34219
34748
  peakRssMb: Math.round(peakRssBytes / (1024 * 1024)),
@@ -34222,7 +34751,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
34222
34751
  job.perfSummary = perfSummary;
34223
34752
  if (job.config.debug) {
34224
34753
  try {
34225
- writeFileSync11(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
34754
+ writeFileSync12(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
34226
34755
  } catch (err) {
34227
34756
  log2.debug("Failed to write perf summary", {
34228
34757
  perfOutputPath,
@@ -34231,8 +34760,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
34231
34760
  }
34232
34761
  }
34233
34762
  if (job.config.debug) {
34234
- if (existsSync30(outputPath)) {
34235
- const debugOutput = join32(workDir, `output${videoExt}`);
34763
+ if (existsSync31(outputPath)) {
34764
+ const debugOutput = join33(workDir, `output${videoExt}`);
34236
34765
  copyFileSync2(outputPath, debugOutput);
34237
34766
  }
34238
34767
  } else if (process.env.KEEP_TEMP === "1") {
@@ -34318,7 +34847,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
34318
34847
  await safeCleanup(
34319
34848
  "remove workDir (error)",
34320
34849
  () => {
34321
- if (existsSync30(workDir)) rmSync7(workDir, { recursive: true, force: true });
34850
+ if (existsSync31(workDir)) rmSync7(workDir, { recursive: true, force: true });
34322
34851
  },
34323
34852
  log2
34324
34853
  );
@@ -34329,7 +34858,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
34329
34858
  clearInterval(memSamplerInterval);
34330
34859
  }
34331
34860
  }
34332
- var frameDirMaxIndexCache, FRAME_FILENAME_RE, RenderCancelledError;
34861
+ var RenderCancelledError, BROWSER_MEDIA_EPSILON;
34333
34862
  var init_renderOrchestrator = __esm({
34334
34863
  "../producer/src/services/renderOrchestrator.ts"() {
34335
34864
  "use strict";
@@ -34339,8 +34868,8 @@ var init_renderOrchestrator = __esm({
34339
34868
  init_htmlCompiler2();
34340
34869
  init_logger();
34341
34870
  init_paths();
34342
- frameDirMaxIndexCache = /* @__PURE__ */ new Map();
34343
- FRAME_FILENAME_RE = /^frame_(\d+)\.png$/;
34871
+ init_frameDirCache();
34872
+ init_hdrImageTransferCache();
34344
34873
  RenderCancelledError = class extends Error {
34345
34874
  reason;
34346
34875
  constructor(message = "render_cancelled", reason = "aborted") {
@@ -34349,6 +34878,7 @@ var init_renderOrchestrator = __esm({
34349
34878
  this.reason = reason;
34350
34879
  }
34351
34880
  };
34881
+ BROWSER_MEDIA_EPSILON = 1e-4;
34352
34882
  }
34353
34883
  });
34354
34884
 
@@ -34377,8 +34907,8 @@ var init_config3 = __esm({
34377
34907
  });
34378
34908
 
34379
34909
  // ../producer/src/services/hyperframeLint.ts
34380
- import { existsSync as existsSync31, readFileSync as readFileSync23, statSync as statSync8 } from "fs";
34381
- import { resolve as resolve18, join as join33 } from "path";
34910
+ import { existsSync as existsSync32, readFileSync as readFileSync23, statSync as statSync10 } from "fs";
34911
+ import { resolve as resolve18, join as join34 } from "path";
34382
34912
  function isStringRecord(value) {
34383
34913
  if (!value || typeof value !== "object" || Array.isArray(value)) {
34384
34914
  return false;
@@ -34406,7 +34936,7 @@ function pickEntryFile(files, preferredEntryFile) {
34406
34936
  }
34407
34937
  function readProjectEntryFile(projectDir, preferredEntryFile) {
34408
34938
  const absProjectDir = resolve18(projectDir);
34409
- if (!existsSync31(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
34939
+ if (!existsSync32(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
34410
34940
  return { error: `Project directory not found: ${absProjectDir}` };
34411
34941
  }
34412
34942
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -34417,7 +34947,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
34417
34947
  if (!absoluteEntryPath.startsWith(absProjectDir)) {
34418
34948
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
34419
34949
  }
34420
- if (existsSync31(absoluteEntryPath) && statSync8(absoluteEntryPath).isFile()) {
34950
+ if (existsSync32(absoluteEntryPath) && statSync10(absoluteEntryPath).isFile()) {
34421
34951
  return {
34422
34952
  entryFile,
34423
34953
  html: readFileSync23(absoluteEntryPath, "utf-8"),
@@ -34426,7 +34956,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
34426
34956
  }
34427
34957
  }
34428
34958
  return {
34429
- error: `No HTML entry file found in project directory: ${join33(absProjectDir, preferredEntryFile || "index.html")}`
34959
+ error: `No HTML entry file found in project directory: ${join34(absProjectDir, preferredEntryFile || "index.html")}`
34430
34960
  };
34431
34961
  }
34432
34962
  function prepareHyperframeLintBody(body) {
@@ -34514,15 +35044,15 @@ var init_semaphore = __esm({
34514
35044
 
34515
35045
  // ../producer/src/server.ts
34516
35046
  import {
34517
- existsSync as existsSync32,
34518
- mkdirSync as mkdirSync18,
34519
- statSync as statSync9,
35047
+ existsSync as existsSync33,
35048
+ mkdirSync as mkdirSync19,
35049
+ statSync as statSync11,
34520
35050
  mkdtempSync as mkdtempSync2,
34521
- writeFileSync as writeFileSync12,
35051
+ writeFileSync as writeFileSync13,
34522
35052
  rmSync as rmSync8,
34523
35053
  createReadStream
34524
35054
  } from "fs";
34525
- import { resolve as resolve19, dirname as dirname12, join as join34 } from "path";
35055
+ import { resolve as resolve19, dirname as dirname12, join as join35 } from "path";
34526
35056
  import { tmpdir as tmpdir3 } from "os";
34527
35057
  import { parseArgs as parseArgs2 } from "util";
34528
35058
  import crypto2 from "crypto";
@@ -34545,11 +35075,11 @@ async function prepareRenderBody(body) {
34545
35075
  const projectDir = typeof body.projectDir === "string" ? body.projectDir : void 0;
34546
35076
  if (projectDir) {
34547
35077
  const absProjectDir = resolve19(projectDir);
34548
- if (!existsSync32(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
35078
+ if (!existsSync33(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
34549
35079
  return { error: `Project directory not found: ${absProjectDir}` };
34550
35080
  }
34551
35081
  const entry = options.entryFile || "index.html";
34552
- if (!existsSync32(resolve19(absProjectDir, entry))) {
35082
+ if (!existsSync33(resolve19(absProjectDir, entry))) {
34553
35083
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
34554
35084
  }
34555
35085
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -34574,8 +35104,8 @@ async function prepareRenderBody(body) {
34574
35104
  }
34575
35105
  }
34576
35106
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
34577
- const tempProjectDir = mkdtempSync2(join34(tempRoot, "producer-project-"));
34578
- writeFileSync12(join34(tempProjectDir, "index.html"), htmlContent, "utf-8");
35107
+ const tempProjectDir = mkdtempSync2(join35(tempRoot, "producer-project-"));
35108
+ writeFileSync13(join35(tempProjectDir, "index.html"), htmlContent, "utf-8");
34579
35109
  return {
34580
35110
  prepared: {
34581
35111
  input: {
@@ -34698,7 +35228,7 @@ function createRenderHandlers(options = {}) {
34698
35228
  log2
34699
35229
  );
34700
35230
  const outputDir = dirname12(absoluteOutputPath);
34701
- if (!existsSync32(outputDir)) mkdirSync18(outputDir, { recursive: true });
35231
+ if (!existsSync33(outputDir)) mkdirSync19(outputDir, { recursive: true });
34702
35232
  const release2 = await renderSemaphore.acquire();
34703
35233
  log2.info("render started", {
34704
35234
  requestId,
@@ -34725,7 +35255,7 @@ function createRenderHandlers(options = {}) {
34725
35255
  log2.info(`render progress ${pct}%`, { requestId, stage: j2.currentStage, message });
34726
35256
  }
34727
35257
  });
34728
- const fileSize = existsSync32(absoluteOutputPath) ? statSync9(absoluteOutputPath).size : 0;
35258
+ const fileSize = existsSync33(absoluteOutputPath) ? statSync11(absoluteOutputPath).size : 0;
34729
35259
  const durationMs = Date.now() - t0;
34730
35260
  const outputToken = store.register(absoluteOutputPath);
34731
35261
  const outputUrl = `${outputUrlPrefix}/${outputToken}`;
@@ -34809,7 +35339,7 @@ function createRenderHandlers(options = {}) {
34809
35339
  log2
34810
35340
  );
34811
35341
  const outputDir = dirname12(absoluteOutputPath);
34812
- if (!existsSync32(outputDir)) mkdirSync18(outputDir, { recursive: true });
35342
+ if (!existsSync33(outputDir)) mkdirSync19(outputDir, { recursive: true });
34813
35343
  log2.info("render-stream started", { requestId, projectDir: input.projectDir });
34814
35344
  const job = createRenderJob({
34815
35345
  fps: input.fps,
@@ -34854,7 +35384,7 @@ function createRenderHandlers(options = {}) {
34854
35384
  },
34855
35385
  abortController.signal
34856
35386
  );
34857
- const fileSize = existsSync32(absoluteOutputPath) ? statSync9(absoluteOutputPath).size : 0;
35387
+ const fileSize = existsSync33(absoluteOutputPath) ? statSync11(absoluteOutputPath).size : 0;
34858
35388
  const outputToken = store.register(absoluteOutputPath);
34859
35389
  const outputUrl = `${outputUrlPrefix}/${outputToken}`;
34860
35390
  log2.info("render-stream completed", { requestId, fileSize, perf: job.perfSummary ?? null });
@@ -34913,11 +35443,11 @@ function createRenderHandlers(options = {}) {
34913
35443
  if (!artifact) {
34914
35444
  return c2.json({ success: false, error: "Output artifact not found or expired" }, 404);
34915
35445
  }
34916
- if (!existsSync32(artifact.path)) {
35446
+ if (!existsSync33(artifact.path)) {
34917
35447
  store.delete(token);
34918
35448
  return c2.json({ success: false, error: "Output artifact file missing" }, 404);
34919
35449
  }
34920
- const stats = statSync9(artifact.path);
35450
+ const stats = statSync11(artifact.path);
34921
35451
  return new Response(createReadStream(artifact.path), {
34922
35452
  headers: {
34923
35453
  "content-type": "video/mp4",
@@ -35050,20 +35580,20 @@ __export(studioServer_exports, {
35050
35580
  });
35051
35581
  import { Hono as Hono5 } from "hono";
35052
35582
  import { streamSSE as streamSSE3 } from "hono/streaming";
35053
- import { existsSync as existsSync33, readFileSync as readFileSync24, writeFileSync as writeFileSync13, statSync as statSync10 } from "fs";
35054
- import { resolve as resolve20, join as join35, basename as basename3 } from "path";
35583
+ import { existsSync as existsSync34, readFileSync as readFileSync24, writeFileSync as writeFileSync14, statSync as statSync12 } from "fs";
35584
+ import { resolve as resolve20, join as join36, basename as basename3 } from "path";
35055
35585
  function resolveDistDir() {
35056
35586
  const builtPath = resolve20(__dirname, "studio");
35057
- if (existsSync33(resolve20(builtPath, "index.html"))) return builtPath;
35587
+ if (existsSync34(resolve20(builtPath, "index.html"))) return builtPath;
35058
35588
  const devPath = resolve20(__dirname, "..", "..", "..", "studio", "dist");
35059
- if (existsSync33(resolve20(devPath, "index.html"))) return devPath;
35589
+ if (existsSync34(resolve20(devPath, "index.html"))) return devPath;
35060
35590
  return builtPath;
35061
35591
  }
35062
35592
  function resolveRuntimePath() {
35063
35593
  const builtPath = resolve20(__dirname, "hyperframe-runtime.js");
35064
- if (existsSync33(builtPath)) return builtPath;
35594
+ if (existsSync34(builtPath)) return builtPath;
35065
35595
  const iifePath = resolve20(__dirname, "hyperframe.runtime.iife.js");
35066
- if (existsSync33(iifePath)) return iifePath;
35596
+ if (existsSync34(iifePath)) return iifePath;
35067
35597
  const devPath = resolve20(
35068
35598
  __dirname,
35069
35599
  "..",
@@ -35073,7 +35603,7 @@ function resolveRuntimePath() {
35073
35603
  "dist",
35074
35604
  "hyperframe.runtime.iife.js"
35075
35605
  );
35076
- if (existsSync33(devPath)) return devPath;
35606
+ if (existsSync34(devPath)) return devPath;
35077
35607
  return builtPath;
35078
35608
  }
35079
35609
  async function getThumbnailBrowser() {
@@ -35135,7 +35665,7 @@ function createStudioServer(options) {
35135
35665
  return lintHyperframeHtml2(html, opts);
35136
35666
  },
35137
35667
  runtimeUrl: "/api/runtime.js",
35138
- rendersDir: () => join35(projectDir, "renders"),
35668
+ rendersDir: () => join36(projectDir, "renders"),
35139
35669
  startRender(opts) {
35140
35670
  const state = {
35141
35671
  id: opts.jobId,
@@ -35168,7 +35698,7 @@ function createStudioServer(options) {
35168
35698
  state.status = "complete";
35169
35699
  state.progress = 100;
35170
35700
  const metaPath = opts.outputPath.replace(/\.(mp4|webm|mov)$/, ".meta.json");
35171
- writeFileSync13(
35701
+ writeFileSync14(
35172
35702
  metaPath,
35173
35703
  JSON.stringify({ status: "complete", durationMs: Date.now() - startTime })
35174
35704
  );
@@ -35177,7 +35707,7 @@ function createStudioServer(options) {
35177
35707
  state.error = err instanceof Error ? err.message : String(err);
35178
35708
  try {
35179
35709
  const metaPath = opts.outputPath.replace(/\.(mp4|webm|mov)$/, ".meta.json");
35180
- writeFileSync13(metaPath, JSON.stringify({ status: "failed" }));
35710
+ writeFileSync14(metaPath, JSON.stringify({ status: "failed" }));
35181
35711
  } catch {
35182
35712
  }
35183
35713
  }
@@ -35250,7 +35780,7 @@ function createStudioServer(options) {
35250
35780
  });
35251
35781
  app.get("/api/runtime.js", (c2) => {
35252
35782
  const serve4 = async () => {
35253
- const runtimeSource = await loadRuntimeSource() ?? (existsSync33(runtimePath) ? readFileSync24(runtimePath, "utf-8") : null);
35783
+ const runtimeSource = await loadRuntimeSource() ?? (existsSync34(runtimePath) ? readFileSync24(runtimePath, "utf-8") : null);
35254
35784
  if (!runtimeSource) return c2.text("runtime not available", 404);
35255
35785
  return c2.body(runtimeSource, 200, {
35256
35786
  "Content-Type": "text/javascript",
@@ -35286,7 +35816,7 @@ function createStudioServer(options) {
35286
35816
  });
35287
35817
  app.get("/assets/*", (c2) => {
35288
35818
  const filePath = resolve20(studioDir, c2.req.path.slice(1));
35289
- if (!existsSync33(filePath) || !statSync10(filePath).isFile()) return c2.text("not found", 404);
35819
+ if (!existsSync34(filePath) || !statSync12(filePath).isFile()) return c2.text("not found", 404);
35290
35820
  const content = readFileSync24(filePath);
35291
35821
  return new Response(content, {
35292
35822
  headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
@@ -35294,7 +35824,7 @@ function createStudioServer(options) {
35294
35824
  });
35295
35825
  app.get("/icons/*", (c2) => {
35296
35826
  const filePath = resolve20(studioDir, c2.req.path.slice(1));
35297
- if (!existsSync33(filePath) || !statSync10(filePath).isFile()) return c2.text("not found", 404);
35827
+ if (!existsSync34(filePath) || !statSync12(filePath).isFile()) return c2.text("not found", 404);
35298
35828
  const content = readFileSync24(filePath);
35299
35829
  return new Response(content, {
35300
35830
  headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
@@ -35302,7 +35832,7 @@ function createStudioServer(options) {
35302
35832
  });
35303
35833
  app.get("*", (c2) => {
35304
35834
  const indexPath = resolve20(studioDir, "index.html");
35305
- if (!existsSync33(indexPath)) {
35835
+ if (!existsSync34(indexPath)) {
35306
35836
  return c2.text("Studio not found. Rebuild with: pnpm run build", 500);
35307
35837
  }
35308
35838
  return c2.html(readFileSync24(indexPath, "utf-8"));
@@ -35329,20 +35859,20 @@ __export(preview_exports, {
35329
35859
  examples: () => examples
35330
35860
  });
35331
35861
  import { spawn as spawn8 } from "child_process";
35332
- import { existsSync as existsSync34, lstatSync, symlinkSync, unlinkSync as unlinkSync5, readlinkSync, mkdirSync as mkdirSync19 } from "fs";
35333
- import { resolve as resolve21, dirname as dirname13, basename as basename4, join as join36 } from "path";
35862
+ import { existsSync as existsSync35, lstatSync, symlinkSync, unlinkSync as unlinkSync5, readlinkSync, mkdirSync as mkdirSync20 } from "fs";
35863
+ import { resolve as resolve21, dirname as dirname13, basename as basename4, join as join37 } from "path";
35334
35864
  import { fileURLToPath as fileURLToPath4 } from "url";
35335
35865
  import { createRequire } from "module";
35336
35866
  async function runDevMode(dir, projectName) {
35337
35867
  const thisFile = fileURLToPath4(import.meta.url);
35338
35868
  const repoRoot = resolve21(dirname13(thisFile), "..", "..", "..", "..");
35339
- const projectsDir = join36(repoRoot, "packages", "studio", "data", "projects");
35869
+ const projectsDir = join37(repoRoot, "packages", "studio", "data", "projects");
35340
35870
  const pName = projectName ?? basename4(dir);
35341
- const symlinkPath = join36(projectsDir, pName);
35342
- mkdirSync19(projectsDir, { recursive: true });
35871
+ const symlinkPath = join37(projectsDir, pName);
35872
+ mkdirSync20(projectsDir, { recursive: true });
35343
35873
  let createdSymlink = false;
35344
35874
  if (dir !== symlinkPath) {
35345
- if (existsSync34(symlinkPath)) {
35875
+ if (existsSync35(symlinkPath)) {
35346
35876
  try {
35347
35877
  const stat3 = lstatSync(symlinkPath);
35348
35878
  if (stat3.isSymbolicLink()) {
@@ -35354,7 +35884,7 @@ async function runDevMode(dir, projectName) {
35354
35884
  } catch {
35355
35885
  }
35356
35886
  }
35357
- if (!existsSync34(symlinkPath)) {
35887
+ if (!existsSync35(symlinkPath)) {
35358
35888
  symlinkSync(dir, symlinkPath, "dir");
35359
35889
  createdSymlink = true;
35360
35890
  }
@@ -35362,7 +35892,7 @@ async function runDevMode(dir, projectName) {
35362
35892
  Wt2(c.bold("hyperframes preview"));
35363
35893
  const s2 = be();
35364
35894
  s2.start("Starting studio...");
35365
- const studioPkgDir = join36(repoRoot, "packages", "studio");
35895
+ const studioPkgDir = join37(repoRoot, "packages", "studio");
35366
35896
  const child = spawn8("pnpm", ["exec", "vite"], {
35367
35897
  cwd: studioPkgDir,
35368
35898
  stdio: ["ignore", "pipe", "pipe"]
@@ -35396,7 +35926,7 @@ async function runDevMode(dir, projectName) {
35396
35926
  if (createdSymlink) {
35397
35927
  process.on("exit", () => {
35398
35928
  try {
35399
- if (existsSync34(symlinkPath)) unlinkSync5(symlinkPath);
35929
+ if (existsSync35(symlinkPath)) unlinkSync5(symlinkPath);
35400
35930
  } catch {
35401
35931
  }
35402
35932
  });
@@ -35407,7 +35937,7 @@ async function runDevMode(dir, projectName) {
35407
35937
  }
35408
35938
  function hasLocalStudio(dir) {
35409
35939
  try {
35410
- const req = createRequire(join36(dir, "package.json"));
35940
+ const req = createRequire(join37(dir, "package.json"));
35411
35941
  req.resolve("@hyperframes/studio/package.json");
35412
35942
  return true;
35413
35943
  } catch {
@@ -35415,20 +35945,20 @@ function hasLocalStudio(dir) {
35415
35945
  }
35416
35946
  }
35417
35947
  async function runLocalStudioMode(dir, projectName) {
35418
- const req = createRequire(join36(dir, "package.json"));
35948
+ const req = createRequire(join37(dir, "package.json"));
35419
35949
  const studioPkgPath = dirname13(req.resolve("@hyperframes/studio/package.json"));
35420
35950
  const pName = projectName ?? basename4(dir);
35421
- const projectsDir = join36(studioPkgPath, "data", "projects");
35422
- const symlinkPath = join36(projectsDir, pName);
35423
- mkdirSync19(projectsDir, { recursive: true });
35951
+ const projectsDir = join37(studioPkgPath, "data", "projects");
35952
+ const symlinkPath = join37(projectsDir, pName);
35953
+ mkdirSync20(projectsDir, { recursive: true });
35424
35954
  let createdSymlink = false;
35425
35955
  if (dir !== symlinkPath) {
35426
- if (existsSync34(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
35956
+ if (existsSync35(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
35427
35957
  if (resolve21(readlinkSync(symlinkPath)) !== resolve21(dir)) {
35428
35958
  unlinkSync5(symlinkPath);
35429
35959
  }
35430
35960
  }
35431
- if (!existsSync34(symlinkPath)) {
35961
+ if (!existsSync35(symlinkPath)) {
35432
35962
  symlinkSync(dir, symlinkPath, "dir");
35433
35963
  createdSymlink = true;
35434
35964
  }
@@ -35467,7 +35997,7 @@ async function runLocalStudioMode(dir, projectName) {
35467
35997
  if (createdSymlink) {
35468
35998
  process.on("exit", () => {
35469
35999
  try {
35470
- if (existsSync34(symlinkPath)) unlinkSync5(symlinkPath);
36000
+ if (existsSync35(symlinkPath)) unlinkSync5(symlinkPath);
35471
36001
  } catch {
35472
36002
  }
35473
36003
  });
@@ -35607,8 +36137,8 @@ var init_preview2 = __esm({
35607
36137
  const dir = resolve21(rawArg ?? ".");
35608
36138
  const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
35609
36139
  const projectName = isImplicitCwd ? basename4(process.env.PWD ?? dir) : basename4(dir);
35610
- const indexPath = join36(dir, "index.html");
35611
- if (existsSync34(indexPath)) {
36140
+ const indexPath = join37(dir, "index.html");
36141
+ if (existsSync35(indexPath)) {
35612
36142
  const project = { dir, name: projectName, indexPath };
35613
36143
  const lintResult = lintProject(project);
35614
36144
  if (lintResult.totalErrors > 0 || lintResult.totalWarnings > 0) {
@@ -35637,15 +36167,15 @@ __export(init_exports, {
35637
36167
  examples: () => examples2
35638
36168
  });
35639
36169
  import {
35640
- existsSync as existsSync35,
35641
- mkdirSync as mkdirSync20,
36170
+ existsSync as existsSync36,
36171
+ mkdirSync as mkdirSync21,
35642
36172
  copyFileSync as copyFileSync3,
35643
36173
  cpSync,
35644
- writeFileSync as writeFileSync14,
36174
+ writeFileSync as writeFileSync15,
35645
36175
  readFileSync as readFileSync25,
35646
- readdirSync as readdirSync12
36176
+ readdirSync as readdirSync14
35647
36177
  } from "fs";
35648
- import { resolve as resolve22, basename as basename5, join as join37, dirname as dirname14 } from "path";
36178
+ import { resolve as resolve22, basename as basename5, join as join38, dirname as dirname14 } from "path";
35649
36179
  import { fileURLToPath as fileURLToPath5 } from "url";
35650
36180
  import { execFileSync as execFileSync4, spawn as spawn9 } from "child_process";
35651
36181
  function probeVideo(filePath) {
@@ -35717,7 +36247,7 @@ function resolveAssetDir(devSegments, builtSegments) {
35717
36247
  const base = dirname14(fileURLToPath5(import.meta.url));
35718
36248
  const devPath = resolve22(base, ...devSegments);
35719
36249
  const builtPath = resolve22(base, ...builtSegments);
35720
- return existsSync35(devPath) ? devPath : builtPath;
36250
+ return existsSync36(devPath) ? devPath : builtPath;
35721
36251
  }
35722
36252
  function getStaticTemplateDir(templateId) {
35723
36253
  return resolveAssetDir(["..", "templates", templateId], ["templates", templateId]);
@@ -35726,7 +36256,7 @@ function getSharedTemplateDir() {
35726
36256
  return resolveAssetDir(["..", "templates", "_shared"], ["templates", "_shared"]);
35727
36257
  }
35728
36258
  function patchVideoSrc(dir, videoFilename, durationSeconds) {
35729
- const htmlFiles = readdirSync12(dir, { withFileTypes: true, recursive: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".html")).map((e2) => join37(e2.parentPath ?? e2.path, e2.name));
36259
+ const htmlFiles = readdirSync14(dir, { withFileTypes: true, recursive: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".html")).map((e2) => join38(e2.parentPath ?? e2.path, e2.name));
35730
36260
  for (const file of htmlFiles) {
35731
36261
  let content = readFileSync25(file, "utf-8");
35732
36262
  if (videoFilename) {
@@ -35739,7 +36269,7 @@ function patchVideoSrc(dir, videoFilename, durationSeconds) {
35739
36269
  }
35740
36270
  const dur = durationSeconds ? String(Math.round(durationSeconds * 100) / 100) : "10";
35741
36271
  content = content.replaceAll("__VIDEO_DURATION__", dur);
35742
- writeFileSync14(file, content, "utf-8");
36272
+ writeFileSync15(file, content, "utf-8");
35743
36273
  }
35744
36274
  }
35745
36275
  async function patchTranscript(dir, transcriptPath) {
@@ -35831,15 +36361,15 @@ async function handleVideoFile(videoPath, destDir, interactive) {
35831
36361
  return { meta, localVideoName };
35832
36362
  }
35833
36363
  async function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds) {
35834
- mkdirSync20(destDir, { recursive: true });
36364
+ mkdirSync21(destDir, { recursive: true });
35835
36365
  const templateDir = getStaticTemplateDir(templateId);
35836
- if (existsSync35(templateDir)) {
36366
+ if (existsSync36(templateDir)) {
35837
36367
  cpSync(templateDir, destDir, { recursive: true });
35838
36368
  } else {
35839
36369
  await fetchRemoteTemplate(templateId, destDir);
35840
36370
  }
35841
36371
  patchVideoSrc(destDir, localVideoName, durationSeconds);
35842
- writeFileSync14(
36372
+ writeFileSync15(
35843
36373
  resolve22(destDir, "meta.json"),
35844
36374
  JSON.stringify(
35845
36375
  {
@@ -35852,14 +36382,14 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
35852
36382
  ),
35853
36383
  "utf-8"
35854
36384
  );
35855
- if (!existsSync35(resolve22(destDir, "hyperframes.json"))) {
36385
+ if (!existsSync36(resolve22(destDir, "hyperframes.json"))) {
35856
36386
  const { writeProjectConfig: writeProjectConfig2, DEFAULT_PROJECT_CONFIG: DEFAULT_PROJECT_CONFIG2 } = await Promise.resolve().then(() => (init_projectConfig(), projectConfig_exports));
35857
36387
  writeProjectConfig2(destDir, DEFAULT_PROJECT_CONFIG2);
35858
36388
  }
35859
36389
  const sharedDir = getSharedTemplateDir();
35860
- if (existsSync35(sharedDir)) {
35861
- for (const entry of readdirSync12(sharedDir, { withFileTypes: true })) {
35862
- const src = join37(sharedDir, entry.name);
36390
+ if (existsSync36(sharedDir)) {
36391
+ for (const entry of readdirSync14(sharedDir, { withFileTypes: true })) {
36392
+ const src = join38(sharedDir, entry.name);
35863
36393
  const dest = resolve22(destDir, entry.name);
35864
36394
  if (entry.isFile() || entry.isSymbolicLink()) {
35865
36395
  copyFileSync3(src, dest);
@@ -35973,11 +36503,11 @@ var init_init = __esm({
35973
36503
  const templateId2 = exampleFlag ?? "blank";
35974
36504
  const name2 = args.name ?? "my-video";
35975
36505
  const destDir2 = resolve22(name2);
35976
- if (existsSync35(destDir2) && readdirSync12(destDir2).length > 0) {
36506
+ if (existsSync36(destDir2) && readdirSync14(destDir2).length > 0) {
35977
36507
  console.error(c.error(`Directory already exists and is not empty: ${name2}`));
35978
36508
  process.exit(1);
35979
36509
  }
35980
- mkdirSync20(destDir2, { recursive: true });
36510
+ mkdirSync21(destDir2, { recursive: true });
35981
36511
  let localVideoName2;
35982
36512
  let videoDuration2;
35983
36513
  let sourceFilePath2;
@@ -35987,7 +36517,7 @@ var init_init = __esm({
35987
36517
  }
35988
36518
  if (videoFlag) {
35989
36519
  const videoPath = resolve22(videoFlag);
35990
- if (!existsSync35(videoPath)) {
36520
+ if (!existsSync36(videoPath)) {
35991
36521
  console.error(c.error(`Video file not found: ${videoFlag}`));
35992
36522
  process.exit(1);
35993
36523
  }
@@ -36001,7 +36531,7 @@ var init_init = __esm({
36001
36531
  }
36002
36532
  if (audioFlag) {
36003
36533
  const audioPath = resolve22(audioFlag);
36004
- if (!existsSync35(audioPath)) {
36534
+ if (!existsSync36(audioPath)) {
36005
36535
  console.error(c.error(`Audio file not found: ${audioFlag}`));
36006
36536
  process.exit(1);
36007
36537
  }
@@ -36047,11 +36577,11 @@ var init_init = __esm({
36047
36577
  }
36048
36578
  trackInitTemplate(templateId2);
36049
36579
  const transcriptFile2 = resolve22(destDir2, "transcript.json");
36050
- if (existsSync35(transcriptFile2)) {
36580
+ if (existsSync36(transcriptFile2)) {
36051
36581
  await patchTranscript(destDir2, transcriptFile2);
36052
36582
  }
36053
36583
  console.log(c.success(`Created ${c.accent(name2 + "/")}`));
36054
- for (const f3 of readdirSync12(destDir2).filter((f4) => !f4.startsWith("."))) {
36584
+ for (const f3 of readdirSync14(destDir2).filter((f4) => !f4.startsWith("."))) {
36055
36585
  console.log(` ${c.accent(f3)}`);
36056
36586
  }
36057
36587
  console.log();
@@ -36099,7 +36629,7 @@ var init_init = __esm({
36099
36629
  name = nameResult;
36100
36630
  }
36101
36631
  const destDir = resolve22(name);
36102
- if (existsSync35(destDir) && readdirSync12(destDir).length > 0) {
36632
+ if (existsSync36(destDir) && readdirSync14(destDir).length > 0) {
36103
36633
  const overwrite = await Rt({
36104
36634
  message: `Directory ${c.accent(name)} already exists and is not empty. Overwrite?`,
36105
36635
  initialValue: false
@@ -36114,24 +36644,24 @@ var init_init = __esm({
36114
36644
  let videoDuration;
36115
36645
  if (videoFlag) {
36116
36646
  const videoPath = resolve22(videoFlag);
36117
- if (!existsSync35(videoPath)) {
36647
+ if (!existsSync36(videoPath)) {
36118
36648
  R2.error(`File not found: ${videoFlag}`);
36119
36649
  Nt("Setup cancelled.");
36120
36650
  process.exit(1);
36121
36651
  }
36122
- mkdirSync20(destDir, { recursive: true });
36652
+ mkdirSync21(destDir, { recursive: true });
36123
36653
  sourceFilePath = videoPath;
36124
36654
  const result = await handleVideoFile(videoPath, destDir, true);
36125
36655
  localVideoName = result.localVideoName;
36126
36656
  videoDuration = result.meta.durationSeconds;
36127
36657
  } else if (audioFlag) {
36128
36658
  const audioPath = resolve22(audioFlag);
36129
- if (!existsSync35(audioPath)) {
36659
+ if (!existsSync36(audioPath)) {
36130
36660
  R2.error(`File not found: ${audioFlag}`);
36131
36661
  Nt("Setup cancelled.");
36132
36662
  process.exit(1);
36133
36663
  }
36134
- mkdirSync20(destDir, { recursive: true });
36664
+ mkdirSync21(destDir, { recursive: true });
36135
36665
  sourceFilePath = audioPath;
36136
36666
  copyFileSync3(audioPath, resolve22(destDir, basename5(audioPath)));
36137
36667
  R2.info(`Audio copied to ${c.accent(basename5(audioPath))}`);
@@ -36219,10 +36749,10 @@ ${c.dim("Use --example blank for offline use.")}`
36219
36749
  }
36220
36750
  trackInitTemplate(templateId);
36221
36751
  const transcriptFile = resolve22(destDir, "transcript.json");
36222
- if (existsSync35(transcriptFile)) {
36752
+ if (existsSync36(transcriptFile)) {
36223
36753
  await patchTranscript(destDir, transcriptFile);
36224
36754
  }
36225
- const files = readdirSync12(destDir);
36755
+ const files = readdirSync14(destDir);
36226
36756
  Vt2(files.map((f3) => c.accent(f3)).join("\n"), c.success(`Created ${name}/`));
36227
36757
  if (!skipSkills) {
36228
36758
  const installSkills = await Rt({
@@ -36305,7 +36835,7 @@ __export(add_exports, {
36305
36835
  remapTarget: () => remapTarget,
36306
36836
  runAdd: () => runAdd
36307
36837
  });
36308
- import { existsSync as existsSync36 } from "fs";
36838
+ import { existsSync as existsSync37 } from "fs";
36309
36839
  import { resolve as resolve23, relative as relative3 } from "path";
36310
36840
  function remapTarget(item, originalTarget, paths) {
36311
36841
  if (item.type === "hyperframes:block") {
@@ -36331,8 +36861,8 @@ function buildSnippet(item, relativeTarget) {
36331
36861
  async function runAdd(opts) {
36332
36862
  const projectDir = resolve23(opts.projectDir);
36333
36863
  let config = loadProjectConfig(projectDir);
36334
- const hasConfig = existsSync36(projectConfigPath(projectDir));
36335
- if (!hasConfig && existsSync36(resolve23(projectDir, "index.html"))) {
36864
+ const hasConfig = existsSync37(projectConfigPath(projectDir));
36865
+ if (!hasConfig && existsSync37(resolve23(projectDir, "index.html"))) {
36336
36866
  writeProjectConfig(projectDir, DEFAULT_PROJECT_CONFIG);
36337
36867
  config = DEFAULT_PROJECT_CONFIG;
36338
36868
  }
@@ -36431,10 +36961,10 @@ var init_add = __esm({
36431
36961
  const projectDir = resolve23(args.dir ?? process.cwd());
36432
36962
  const json = args.json === true;
36433
36963
  const skipClipboard = args["no-clipboard"] === true;
36434
- const hasConfigBefore = existsSync36(projectConfigPath(projectDir));
36964
+ const hasConfigBefore = existsSync37(projectConfigPath(projectDir));
36435
36965
  try {
36436
36966
  const result = await runAdd({ name: args.name, projectDir, skipClipboard });
36437
- const wroteConfig = !hasConfigBefore && existsSync36(projectConfigPath(projectDir));
36967
+ const wroteConfig = !hasConfigBefore && existsSync37(projectConfigPath(projectDir));
36438
36968
  if (json) {
36439
36969
  console.log(JSON.stringify(result));
36440
36970
  return;
@@ -36644,17 +37174,17 @@ var init_format = __esm({
36644
37174
  });
36645
37175
 
36646
37176
  // src/utils/project.ts
36647
- import { existsSync as existsSync37, statSync as statSync11 } from "fs";
37177
+ import { existsSync as existsSync38, statSync as statSync13 } from "fs";
36648
37178
  import { resolve as resolve25, basename as basename6 } from "path";
36649
37179
  function resolveProject(dirArg) {
36650
37180
  const dir = resolve25(dirArg ?? ".");
36651
37181
  const name = basename6(dir);
36652
37182
  const indexPath = resolve25(dir, "index.html");
36653
- if (!existsSync37(dir) || !statSync11(dir).isDirectory()) {
37183
+ if (!existsSync38(dir) || !statSync13(dir).isDirectory()) {
36654
37184
  errorBox("Not a directory: " + dir);
36655
37185
  process.exit(1);
36656
37186
  }
36657
- if (!existsSync37(indexPath)) {
37187
+ if (!existsSync38(indexPath)) {
36658
37188
  errorBox(
36659
37189
  "No composition found in " + dir,
36660
37190
  "No index.html file found.",
@@ -36677,7 +37207,7 @@ __export(play_exports, {
36677
37207
  default: () => play_default,
36678
37208
  examples: () => examples5
36679
37209
  });
36680
- import { existsSync as existsSync38, readFileSync as readFileSync26 } from "fs";
37210
+ import { existsSync as existsSync39, readFileSync as readFileSync26 } from "fs";
36681
37211
  import { resolve as resolve26, dirname as dirname15 } from "path";
36682
37212
  function commandDir() {
36683
37213
  return dirname15(new URL(import.meta.url).pathname);
@@ -36692,7 +37222,7 @@ function resolveRuntimePath2() {
36692
37222
  resolve26(d, "..", "..", "..", "core", "dist", "hyperframe.runtime.iife.js")
36693
37223
  ];
36694
37224
  for (const p of candidates) {
36695
- if (existsSync38(p)) return p;
37225
+ if (existsSync39(p)) return p;
36696
37226
  }
36697
37227
  return null;
36698
37228
  }
@@ -36706,7 +37236,7 @@ function resolvePlayerPath() {
36706
37236
  resolve26(d, "..", "hyperframes-player.global.js")
36707
37237
  ];
36708
37238
  for (const p of candidates) {
36709
- if (existsSync38(p)) return p;
37239
+ if (existsSync39(p)) return p;
36710
37240
  }
36711
37241
  return null;
36712
37242
  }
@@ -36808,7 +37338,7 @@ var init_play = __esm({
36808
37338
  const reqPath = ctx.req.path.replace("/composition/", "");
36809
37339
  const filePath = resolve26(project.dir, reqPath);
36810
37340
  if (!filePath.startsWith(project.dir)) return ctx.text("Forbidden", 403);
36811
- if (!existsSync38(filePath)) return ctx.text("Not found", 404);
37341
+ if (!existsSync39(filePath)) return ctx.text("Not found", 404);
36812
37342
  const content = readFileSync26(filePath, "utf-8");
36813
37343
  if (filePath.endsWith(".html")) {
36814
37344
  const injected = injectRuntime(content);
@@ -36884,23 +37414,23 @@ var init_play = __esm({
36884
37414
  });
36885
37415
 
36886
37416
  // src/utils/publishProject.ts
36887
- import { basename as basename7, join as join38, relative as relative4 } from "path";
36888
- import { readdirSync as readdirSync13, readFileSync as readFileSync27, statSync as statSync12 } from "fs";
37417
+ import { basename as basename7, join as join39, relative as relative4 } from "path";
37418
+ import { readdirSync as readdirSync15, readFileSync as readFileSync27, statSync as statSync14 } from "fs";
36889
37419
  import AdmZip from "adm-zip";
36890
37420
  function shouldIgnoreSegment(segment) {
36891
37421
  return segment.startsWith(".") || IGNORED_DIRS.has(segment) || IGNORED_FILES.has(segment);
36892
37422
  }
36893
37423
  function collectProjectFiles(rootDir, currentDir, paths) {
36894
- for (const entry of readdirSync13(currentDir, { withFileTypes: true })) {
37424
+ for (const entry of readdirSync15(currentDir, { withFileTypes: true })) {
36895
37425
  if (shouldIgnoreSegment(entry.name)) continue;
36896
- const absolutePath = join38(currentDir, entry.name);
37426
+ const absolutePath = join39(currentDir, entry.name);
36897
37427
  const relativePath = relative4(rootDir, absolutePath).replaceAll("\\", "/");
36898
37428
  if (!relativePath) continue;
36899
37429
  if (entry.isDirectory()) {
36900
37430
  collectProjectFiles(rootDir, absolutePath, paths);
36901
37431
  continue;
36902
37432
  }
36903
- if (!statSync12(absolutePath).isFile()) continue;
37433
+ if (!statSync14(absolutePath).isFile()) continue;
36904
37434
  paths.push(relativePath);
36905
37435
  }
36906
37436
  }
@@ -36912,7 +37442,7 @@ function createPublishArchive(projectDir) {
36912
37442
  }
36913
37443
  const archive = new AdmZip();
36914
37444
  for (const filePath of filePaths) {
36915
- archive.addFile(filePath, readFileSync27(join38(projectDir, filePath)));
37445
+ archive.addFile(filePath, readFileSync27(join39(projectDir, filePath)));
36916
37446
  }
36917
37447
  return {
36918
37448
  buffer: archive.toBuffer(),
@@ -36968,8 +37498,8 @@ __export(publish_exports, {
36968
37498
  examples: () => examples6
36969
37499
  });
36970
37500
  import { basename as basename8, resolve as resolve27 } from "path";
36971
- import { existsSync as existsSync39 } from "fs";
36972
- import { join as join39 } from "path";
37501
+ import { existsSync as existsSync40 } from "fs";
37502
+ import { join as join40 } from "path";
36973
37503
  var examples6, publish_default;
36974
37504
  var init_publish = __esm({
36975
37505
  "src/commands/publish.ts"() {
@@ -37004,8 +37534,8 @@ var init_publish = __esm({
37004
37534
  const dir = resolve27(rawArg ?? ".");
37005
37535
  const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
37006
37536
  const projectName = isImplicitCwd ? basename8(process.env["PWD"] ?? dir) : basename8(dir);
37007
- const indexPath = join39(dir, "index.html");
37008
- if (existsSync39(indexPath)) {
37537
+ const indexPath = join40(dir, "index.html");
37538
+ if (existsSync40(indexPath)) {
37009
37539
  const lintResult = lintProject({ dir, name: projectName, indexPath });
37010
37540
  if (lintResult.totalErrors > 0 || lintResult.totalWarnings > 0) {
37011
37541
  console.log();
@@ -37176,9 +37706,9 @@ __export(render_exports, {
37176
37706
  default: () => render_default,
37177
37707
  examples: () => examples7
37178
37708
  });
37179
- import { mkdirSync as mkdirSync21, readFileSync as readFileSync28, statSync as statSync13, writeFileSync as writeFileSync15, rmSync as rmSync9 } from "fs";
37709
+ import { mkdirSync as mkdirSync22, readFileSync as readFileSync28, statSync as statSync15, writeFileSync as writeFileSync16, rmSync as rmSync9 } from "fs";
37180
37710
  import { cpus as cpus3, freemem as freemem3, tmpdir as tmpdir4 } from "os";
37181
- import { resolve as resolve28, dirname as dirname16, join as join40, basename as basename9 } from "path";
37711
+ import { resolve as resolve28, dirname as dirname16, join as join41, basename as basename9 } from "path";
37182
37712
  import { execFileSync as execFileSync5, spawn as spawn10 } from "child_process";
37183
37713
  function defaultWorkerCount() {
37184
37714
  return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT * 3 / 4), 8));
@@ -37191,7 +37721,7 @@ function resolveDockerfilePath() {
37191
37721
  const devPath = resolve28(__dirname, "..", "src", "docker", "Dockerfile.render");
37192
37722
  for (const p of [builtPath, devPath]) {
37193
37723
  try {
37194
- statSync13(p);
37724
+ statSync15(p);
37195
37725
  return p;
37196
37726
  } catch {
37197
37727
  continue;
@@ -37215,9 +37745,9 @@ function ensureDockerImage(version, quiet) {
37215
37745
  }
37216
37746
  if (!quiet) console.log(c.dim(` Building Docker image: ${tag}...`));
37217
37747
  const dockerfilePath = resolveDockerfilePath();
37218
- const tmpDir = join40(tmpdir4(), `hyperframes-docker-${Date.now()}`);
37219
- mkdirSync21(tmpDir, { recursive: true });
37220
- writeFileSync15(join40(tmpDir, "Dockerfile"), readFileSync28(dockerfilePath));
37748
+ const tmpDir = join41(tmpdir4(), `hyperframes-docker-${Date.now()}`);
37749
+ mkdirSync22(tmpDir, { recursive: true });
37750
+ writeFileSync16(join41(tmpDir, "Dockerfile"), readFileSync28(dockerfilePath));
37221
37751
  try {
37222
37752
  execFileSync5(
37223
37753
  "docker",
@@ -37364,6 +37894,8 @@ function trackRenderMetrics(job, elapsedMs, options, docker) {
37364
37894
  const perf = job.perfSummary;
37365
37895
  const compositionDurationMs = perf ? Math.round(perf.compositionDurationSeconds * 1e3) : void 0;
37366
37896
  const speedRatio = compositionDurationMs && compositionDurationMs > 0 && elapsedMs > 0 ? Math.round(compositionDurationMs / elapsedMs * 100) / 100 : void 0;
37897
+ const stages = perf?.stages ?? {};
37898
+ const extract = perf?.videoExtractBreakdown;
37367
37899
  trackRenderComplete({
37368
37900
  durationMs: elapsedMs,
37369
37901
  fps: options.fps,
@@ -37378,6 +37910,23 @@ function trackRenderMetrics(job, elapsedMs, options, docker) {
37378
37910
  speedRatio,
37379
37911
  captureAvgMs: perf?.captureAvgMs,
37380
37912
  capturePeakMs: perf?.capturePeakMs,
37913
+ tmpPeakBytes: perf?.tmpPeakBytes,
37914
+ stageCompileMs: stages.compileMs,
37915
+ stageVideoExtractMs: stages.videoExtractMs,
37916
+ stageAudioProcessMs: stages.audioProcessMs,
37917
+ stageCaptureMs: stages.captureMs,
37918
+ stageEncodeMs: stages.encodeMs,
37919
+ stageAssembleMs: stages.assembleMs,
37920
+ extractResolveMs: extract?.resolveMs,
37921
+ extractHdrProbeMs: extract?.hdrProbeMs,
37922
+ extractHdrPreflightMs: extract?.hdrPreflightMs,
37923
+ extractHdrPreflightCount: extract?.hdrPreflightCount,
37924
+ extractVfrProbeMs: extract?.vfrProbeMs,
37925
+ extractVfrPreflightMs: extract?.vfrPreflightMs,
37926
+ extractVfrPreflightCount: extract?.vfrPreflightCount,
37927
+ extractPhase3Ms: extract?.extractMs,
37928
+ extractCacheHits: extract?.cacheHits,
37929
+ extractCacheMisses: extract?.cacheMisses,
37381
37930
  ...getMemorySnapshot()
37382
37931
  });
37383
37932
  }
@@ -37385,7 +37934,7 @@ function printRenderComplete(outputPath, elapsedMs, quiet) {
37385
37934
  if (quiet) return;
37386
37935
  let fileSize = "unknown";
37387
37936
  try {
37388
- fileSize = formatBytes(statSync13(outputPath).size);
37937
+ fileSize = formatBytes(statSync15(outputPath).size);
37389
37938
  } catch {
37390
37939
  }
37391
37940
  const duration = formatDuration(elapsedMs);
@@ -37546,8 +38095,8 @@ var init_render2 = __esm({
37546
38095
  const now = /* @__PURE__ */ new Date();
37547
38096
  const datePart = now.toISOString().slice(0, 10);
37548
38097
  const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
37549
- const outputPath = args.output ? resolve28(args.output) : join40(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
37550
- mkdirSync21(dirname16(outputPath), { recursive: true });
38098
+ const outputPath = args.output ? resolve28(args.output) : join41(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
38099
+ mkdirSync22(dirname16(outputPath), { recursive: true });
37551
38100
  const useDocker = args.docker ?? false;
37552
38101
  const useGpu = args.gpu ?? false;
37553
38102
  const quiet = args.quiet ?? false;
@@ -37894,16 +38443,16 @@ __export(info_exports, {
37894
38443
  default: () => info_default,
37895
38444
  examples: () => examples9
37896
38445
  });
37897
- import { readFileSync as readFileSync29, readdirSync as readdirSync14, statSync as statSync14 } from "fs";
37898
- import { join as join41 } from "path";
38446
+ import { readFileSync as readFileSync29, readdirSync as readdirSync16, statSync as statSync16 } from "fs";
38447
+ import { join as join42 } from "path";
37899
38448
  function totalSize(dir) {
37900
38449
  let total = 0;
37901
- for (const entry of readdirSync14(dir, { withFileTypes: true })) {
37902
- const path2 = join41(dir, entry.name);
38450
+ for (const entry of readdirSync16(dir, { withFileTypes: true })) {
38451
+ const path2 = join42(dir, entry.name);
37903
38452
  if (entry.isDirectory()) {
37904
38453
  total += totalSize(path2);
37905
38454
  } else {
37906
- total += statSync14(path2).size;
38455
+ total += statSync16(path2).size;
37907
38456
  }
37908
38457
  }
37909
38458
  return total;
@@ -37987,7 +38536,7 @@ __export(compositions_exports, {
37987
38536
  default: () => compositions_default,
37988
38537
  examples: () => examples10
37989
38538
  });
37990
- import { existsSync as existsSync40, readFileSync as readFileSync30 } from "fs";
38539
+ import { existsSync as existsSync41, readFileSync as readFileSync30 } from "fs";
37991
38540
  import { resolve as resolve29, dirname as dirname17 } from "path";
37992
38541
  function parseCompositions(html, baseDir) {
37993
38542
  const parser = new DOMParser();
@@ -38001,7 +38550,7 @@ function parseCompositions(html, baseDir) {
38001
38550
  const compositionSrc = div.getAttribute("data-composition-src");
38002
38551
  if (compositionSrc) {
38003
38552
  const subPath = resolve29(baseDir, compositionSrc);
38004
- if (existsSync40(subPath)) {
38553
+ if (existsSync41(subPath)) {
38005
38554
  const subHtml = readFileSync30(subPath, "utf-8");
38006
38555
  const subInfo = parseSubComposition(subHtml, id, width, height);
38007
38556
  compositions.push({ ...subInfo, source: compositionSrc });
@@ -38134,8 +38683,8 @@ __export(benchmark_exports, {
38134
38683
  default: () => benchmark_default,
38135
38684
  examples: () => examples11
38136
38685
  });
38137
- import { existsSync as existsSync41, statSync as statSync15 } from "fs";
38138
- import { resolve as resolve30, join as join42 } from "path";
38686
+ import { existsSync as existsSync42, statSync as statSync17 } from "fs";
38687
+ import { resolve as resolve30, join as join43 } from "path";
38139
38688
  var examples11, DEFAULT_CONFIGS, benchmark_default;
38140
38689
  var init_benchmark = __esm({
38141
38690
  "src/commands/benchmark.ts"() {
@@ -38210,7 +38759,7 @@ var init_benchmark = __esm({
38210
38759
  s2?.start(`Benchmarking ${config.label}...`);
38211
38760
  for (let i2 = 0; i2 < runsPerConfig; i2++) {
38212
38761
  s2?.message(`${config.label} \u2014 run ${i2 + 1}/${runsPerConfig}`);
38213
- const outputPath = join42(
38762
+ const outputPath = join43(
38214
38763
  benchDir,
38215
38764
  `${config.label.replace(/[^a-zA-Z0-9]/g, "_")}_run${i2}.mp4`
38216
38765
  );
@@ -38224,8 +38773,8 @@ var init_benchmark = __esm({
38224
38773
  await producer.executeRenderJob(job, project.dir, outputPath);
38225
38774
  const elapsedMs = Date.now() - startTime;
38226
38775
  let fileSize = null;
38227
- if (existsSync41(outputPath)) {
38228
- const stat3 = statSync15(outputPath);
38776
+ if (existsSync42(outputPath)) {
38777
+ const stat3 = statSync17(outputPath);
38229
38778
  fileSize = stat3.size;
38230
38779
  }
38231
38780
  runs.push({ elapsedMs, fileSize });
@@ -38440,8 +38989,8 @@ __export(transcribe_exports2, {
38440
38989
  default: () => transcribe_default,
38441
38990
  examples: () => examples13
38442
38991
  });
38443
- import { existsSync as existsSync42, writeFileSync as writeFileSync16 } from "fs";
38444
- import { resolve as resolve31, join as join43, extname as extname8 } from "path";
38992
+ import { existsSync as existsSync43, writeFileSync as writeFileSync17 } from "fs";
38993
+ import { resolve as resolve31, join as join44, extname as extname8 } from "path";
38445
38994
  async function importTranscript(inputPath, dir, json) {
38446
38995
  const { loadTranscript: loadTranscript2, patchCaptionHtml: patchCaptionHtml2 } = await Promise.resolve().then(() => (init_normalize(), normalize_exports));
38447
38996
  const { words, format } = loadTranscript2(inputPath);
@@ -38449,8 +38998,8 @@ async function importTranscript(inputPath, dir, json) {
38449
38998
  console.error(c.error("No words found in transcript."));
38450
38999
  process.exit(1);
38451
39000
  }
38452
- const outPath = join43(dir, "transcript.json");
38453
- writeFileSync16(outPath, JSON.stringify(words, null, 2));
39001
+ const outPath = join44(dir, "transcript.json");
39002
+ writeFileSync17(outPath, JSON.stringify(words, null, 2));
38454
39003
  patchCaptionHtml2(dir, words);
38455
39004
  if (json) {
38456
39005
  console.log(
@@ -38485,7 +39034,7 @@ async function transcribeAudio(inputPath, dir, opts) {
38485
39034
  );
38486
39035
  }
38487
39036
  }
38488
- writeFileSync16(result.transcriptPath, JSON.stringify(words, null, 2));
39037
+ writeFileSync17(result.transcriptPath, JSON.stringify(words, null, 2));
38489
39038
  patchCaptionHtml2(dir, words);
38490
39039
  if (opts.json) {
38491
39040
  console.log(
@@ -38566,7 +39115,7 @@ var init_transcribe2 = __esm({
38566
39115
  },
38567
39116
  async run({ args }) {
38568
39117
  const inputPath = resolve31(args.input);
38569
- if (!existsSync42(inputPath)) {
39118
+ if (!existsSync43(inputPath)) {
38570
39119
  console.error(c.error(`File not found: ${args.input}`));
38571
39120
  process.exit(1);
38572
39121
  }
@@ -38587,9 +39136,9 @@ var init_transcribe2 = __esm({
38587
39136
  });
38588
39137
 
38589
39138
  // src/tts/manager.ts
38590
- import { existsSync as existsSync43, mkdirSync as mkdirSync22 } from "fs";
39139
+ import { existsSync as existsSync44, mkdirSync as mkdirSync23 } from "fs";
38591
39140
  import { homedir as homedir8 } from "os";
38592
- import { join as join44 } from "path";
39141
+ import { join as join45 } from "path";
38593
39142
  function inferLangFromVoiceId(voiceId) {
38594
39143
  const first = voiceId.charAt(0).toLowerCase();
38595
39144
  return VOICE_PREFIX_LANG[first] ?? "en-us";
@@ -38598,29 +39147,29 @@ function isSupportedLang(value) {
38598
39147
  return SUPPORTED_LANGS.includes(value);
38599
39148
  }
38600
39149
  async function ensureModel2(model = DEFAULT_MODEL2, options) {
38601
- const modelPath = join44(MODELS_DIR2, `${model}.onnx`);
38602
- if (existsSync43(modelPath)) return modelPath;
39150
+ const modelPath = join45(MODELS_DIR2, `${model}.onnx`);
39151
+ if (existsSync44(modelPath)) return modelPath;
38603
39152
  const url = MODEL_URLS[model];
38604
39153
  if (!url) {
38605
39154
  throw new Error(
38606
39155
  `Unknown TTS model: ${model}. Available: ${Object.keys(MODEL_URLS).join(", ")}`
38607
39156
  );
38608
39157
  }
38609
- mkdirSync22(MODELS_DIR2, { recursive: true });
39158
+ mkdirSync23(MODELS_DIR2, { recursive: true });
38610
39159
  options?.onProgress?.(`Downloading TTS model ${model} (~311 MB)...`);
38611
39160
  await downloadFile(url, modelPath);
38612
- if (!existsSync43(modelPath)) {
39161
+ if (!existsSync44(modelPath)) {
38613
39162
  throw new Error(`Model download failed: ${model}`);
38614
39163
  }
38615
39164
  return modelPath;
38616
39165
  }
38617
39166
  async function ensureVoices(options) {
38618
- const voicesPath = join44(VOICES_DIR, "voices-v1.0.bin");
38619
- if (existsSync43(voicesPath)) return voicesPath;
38620
- mkdirSync22(VOICES_DIR, { recursive: true });
39167
+ const voicesPath = join45(VOICES_DIR, "voices-v1.0.bin");
39168
+ if (existsSync44(voicesPath)) return voicesPath;
39169
+ mkdirSync23(VOICES_DIR, { recursive: true });
38621
39170
  options?.onProgress?.("Downloading voice data (~27 MB)...");
38622
39171
  await downloadFile(VOICES_URL, voicesPath);
38623
- if (!existsSync43(voicesPath)) {
39172
+ if (!existsSync44(voicesPath)) {
38624
39173
  throw new Error("Voice data download failed");
38625
39174
  }
38626
39175
  return voicesPath;
@@ -38630,9 +39179,9 @@ var init_manager3 = __esm({
38630
39179
  "src/tts/manager.ts"() {
38631
39180
  "use strict";
38632
39181
  init_download();
38633
- CACHE_DIR3 = join44(homedir8(), ".cache", "hyperframes", "tts");
38634
- MODELS_DIR2 = join44(CACHE_DIR3, "models");
38635
- VOICES_DIR = join44(CACHE_DIR3, "voices");
39182
+ CACHE_DIR3 = join45(homedir8(), ".cache", "hyperframes", "tts");
39183
+ MODELS_DIR2 = join45(CACHE_DIR3, "models");
39184
+ VOICES_DIR = join45(CACHE_DIR3, "voices");
38636
39185
  DEFAULT_MODEL2 = "kokoro-v1.0";
38637
39186
  MODEL_URLS = {
38638
39187
  "kokoro-v1.0": "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx"
@@ -38693,8 +39242,8 @@ __export(synthesize_exports, {
38693
39242
  synthesize: () => synthesize
38694
39243
  });
38695
39244
  import { execFileSync as execFileSync6 } from "child_process";
38696
- import { existsSync as existsSync44, writeFileSync as writeFileSync17, mkdirSync as mkdirSync23, readdirSync as readdirSync15, unlinkSync as unlinkSync6 } from "fs";
38697
- import { join as join45, dirname as dirname18, basename as basename10 } from "path";
39245
+ import { existsSync as existsSync45, writeFileSync as writeFileSync18, mkdirSync as mkdirSync24, readdirSync as readdirSync17, unlinkSync as unlinkSync6 } from "fs";
39246
+ import { join as join46, dirname as dirname18, basename as basename10 } from "path";
38698
39247
  import { homedir as homedir9 } from "os";
38699
39248
  function findPython() {
38700
39249
  for (const name of ["python3", "python"]) {
@@ -38730,15 +39279,15 @@ function hasPythonPackage(python, pkg) {
38730
39279
  }
38731
39280
  }
38732
39281
  function ensureSynthScript() {
38733
- if (!existsSync44(SCRIPT_PATH)) {
38734
- mkdirSync23(SCRIPT_DIR, { recursive: true });
38735
- writeFileSync17(SCRIPT_PATH, SYNTH_SCRIPT);
39282
+ if (!existsSync45(SCRIPT_PATH)) {
39283
+ mkdirSync24(SCRIPT_DIR, { recursive: true });
39284
+ writeFileSync18(SCRIPT_PATH, SYNTH_SCRIPT);
38736
39285
  const currentName = basename10(SCRIPT_PATH);
38737
39286
  try {
38738
- for (const entry of readdirSync15(SCRIPT_DIR)) {
39287
+ for (const entry of readdirSync17(SCRIPT_DIR)) {
38739
39288
  if (entry !== currentName && /^synth(-v\d+)?\.py$/.test(entry)) {
38740
39289
  try {
38741
- unlinkSync6(join45(SCRIPT_DIR, entry));
39290
+ unlinkSync6(join46(SCRIPT_DIR, entry));
38742
39291
  } catch {
38743
39292
  }
38744
39293
  }
@@ -38772,7 +39321,7 @@ async function synthesize(text, outputPath, options) {
38772
39321
  ensureVoices({ onProgress: options?.onProgress })
38773
39322
  ]);
38774
39323
  const scriptPath = ensureSynthScript();
38775
- mkdirSync23(dirname18(outputPath), { recursive: true });
39324
+ mkdirSync24(dirname18(outputPath), { recursive: true });
38776
39325
  options?.onProgress?.(`Generating speech with voice ${voice} (${lang})...`);
38777
39326
  try {
38778
39327
  const stdout2 = execFileSync6(
@@ -38784,7 +39333,7 @@ async function synthesize(text, outputPath, options) {
38784
39333
  stdio: ["pipe", "pipe", "pipe"]
38785
39334
  }
38786
39335
  );
38787
- if (!existsSync44(outputPath)) {
39336
+ if (!existsSync45(outputPath)) {
38788
39337
  throw new Error("Synthesis completed but no output file was created");
38789
39338
  }
38790
39339
  const lines = stdout2.trim().split("\n");
@@ -38797,7 +39346,7 @@ async function synthesize(text, outputPath, options) {
38797
39346
  langApplied: result.langApplied
38798
39347
  };
38799
39348
  } catch (err) {
38800
- if (err instanceof SyntaxError && existsSync44(outputPath)) {
39349
+ if (err instanceof SyntaxError && existsSync45(outputPath)) {
38801
39350
  throw new Error(
38802
39351
  "Speech was generated but metadata could not be read. Check the output file manually."
38803
39352
  );
@@ -38848,8 +39397,8 @@ print(json.dumps({
38848
39397
  "langApplied": bool(lang and supports_lang),
38849
39398
  }))
38850
39399
  `;
38851
- SCRIPT_DIR = join45(homedir9(), ".cache", "hyperframes", "tts");
38852
- SCRIPT_PATH = join45(SCRIPT_DIR, "synth-v2.py");
39400
+ SCRIPT_DIR = join46(homedir9(), ".cache", "hyperframes", "tts");
39401
+ SCRIPT_PATH = join46(SCRIPT_DIR, "synth-v2.py");
38853
39402
  }
38854
39403
  });
38855
39404
 
@@ -38859,7 +39408,7 @@ __export(tts_exports, {
38859
39408
  default: () => tts_default,
38860
39409
  examples: () => examples14
38861
39410
  });
38862
- import { existsSync as existsSync45, readFileSync as readFileSync31 } from "fs";
39411
+ import { existsSync as existsSync46, readFileSync as readFileSync31 } from "fs";
38863
39412
  import { resolve as resolve32, extname as extname9 } from "path";
38864
39413
  function listVoices(json) {
38865
39414
  const rows = BUNDLED_VOICES.map((v) => ({ ...v, defaultLang: inferLangFromVoiceId(v.id) }));
@@ -38969,7 +39518,7 @@ var init_tts = __esm({
38969
39518
  }
38970
39519
  let text;
38971
39520
  const maybeFile = resolve32(args.input);
38972
- if (existsSync45(maybeFile) && extname9(maybeFile).toLowerCase() === ".txt") {
39521
+ if (existsSync46(maybeFile) && extname9(maybeFile).toLowerCase() === ".txt") {
38973
39522
  text = readFileSync31(maybeFile, "utf-8").trim();
38974
39523
  if (!text) {
38975
39524
  console.error(c.error("File is empty."));
@@ -39062,15 +39611,15 @@ __export(docs_exports, {
39062
39611
  default: () => docs_default,
39063
39612
  examples: () => examples15
39064
39613
  });
39065
- import { readFileSync as readFileSync32, existsSync as existsSync46 } from "fs";
39066
- import { resolve as resolve33, dirname as dirname19, join as join46 } from "path";
39614
+ import { readFileSync as readFileSync32, existsSync as existsSync47 } from "fs";
39615
+ import { resolve as resolve33, dirname as dirname19, join as join47 } from "path";
39067
39616
  import { fileURLToPath as fileURLToPath6 } from "url";
39068
39617
  function docsDir() {
39069
39618
  const thisFile = fileURLToPath6(import.meta.url);
39070
39619
  const dir = dirname19(thisFile);
39071
39620
  const devPath = resolve33(dir, "..", "docs");
39072
39621
  const builtPath = resolve33(dir, "docs");
39073
- return existsSync46(devPath) ? devPath : builtPath;
39622
+ return existsSync47(devPath) ? devPath : builtPath;
39074
39623
  }
39075
39624
  function formatInlineCode(line) {
39076
39625
  return line.replace(/`([^`]+)`/g, (_match, code) => c.accent(code));
@@ -39167,8 +39716,8 @@ var init_docs = __esm({
39167
39716
  }
39168
39717
  process.exit(1);
39169
39718
  }
39170
- const filePath = join46(docsDir(), entry.file);
39171
- if (!existsSync46(filePath)) {
39719
+ const filePath = join47(docsDir(), entry.file);
39720
+ if (!existsSync47(filePath)) {
39172
39721
  console.error(c.error(`Doc file not found: ${filePath}`));
39173
39722
  process.exit(1);
39174
39723
  }
@@ -39598,8 +40147,8 @@ var validate_exports = {};
39598
40147
  __export(validate_exports, {
39599
40148
  default: () => validate_default
39600
40149
  });
39601
- import { existsSync as existsSync47, readFileSync as readFileSync33 } from "fs";
39602
- import { resolve as resolve34, join as join47, dirname as dirname20 } from "path";
40150
+ import { existsSync as existsSync48, readFileSync as readFileSync33 } from "fs";
40151
+ import { resolve as resolve34, join as join48, dirname as dirname20 } from "path";
39603
40152
  import { fileURLToPath as fileURLToPath7 } from "url";
39604
40153
  async function getCompositionDuration2(page) {
39605
40154
  return page.evaluate(() => {
@@ -39654,7 +40203,7 @@ async function validateInBrowser(projectDir, opts) {
39654
40203
  "dist",
39655
40204
  "hyperframe.runtime.iife.js"
39656
40205
  );
39657
- if (existsSync47(runtimePath)) {
40206
+ if (existsSync48(runtimePath)) {
39658
40207
  const runtimeSource = readFileSync33(runtimePath, "utf-8");
39659
40208
  html = html.replace(
39660
40209
  /<script[^>]*data-hyperframes-preview-runtime[^>]*src="[^"]*"[^>]*><\/script>/,
@@ -39670,8 +40219,8 @@ async function validateInBrowser(projectDir, opts) {
39670
40219
  res.end(html);
39671
40220
  return;
39672
40221
  }
39673
- const filePath = join47(projectDir, decodeURIComponent(url));
39674
- if (existsSync47(filePath)) {
40222
+ const filePath = join48(projectDir, decodeURIComponent(url));
40223
+ if (existsSync48(filePath)) {
39675
40224
  res.writeHead(200, { "Content-Type": getMimeType2(filePath) });
39676
40225
  res.end(readFileSync33(filePath));
39677
40226
  return;
@@ -39864,13 +40413,13 @@ __export(snapshot_exports, {
39864
40413
  examples: () => examples19
39865
40414
  });
39866
40415
  import { spawn as spawn11 } from "child_process";
39867
- import { existsSync as existsSync48, mkdtempSync as mkdtempSync3, readFileSync as readFileSync34, mkdirSync as mkdirSync24, rmSync as rmSync10 } from "fs";
40416
+ import { existsSync as existsSync49, mkdtempSync as mkdtempSync3, readFileSync as readFileSync34, mkdirSync as mkdirSync25, rmSync as rmSync10 } from "fs";
39868
40417
  import { tmpdir as tmpdir5 } from "os";
39869
- import { resolve as resolve35, join as join48, dirname as dirname21, relative as relative5, isAbsolute as isAbsolute6 } from "path";
40418
+ import { resolve as resolve35, join as join49, dirname as dirname21, relative as relative5, isAbsolute as isAbsolute6 } from "path";
39870
40419
  import { fileURLToPath as fileURLToPath8 } from "url";
39871
40420
  async function extractVideoFrameToBuffer(videoPath, timeSeconds) {
39872
- const tmp = mkdtempSync3(join48(tmpdir5(), "hf-snapshot-frame-"));
39873
- const outPath = join48(tmp, "frame.png");
40421
+ const tmp = mkdtempSync3(join49(tmpdir5(), "hf-snapshot-frame-"));
40422
+ const outPath = join49(tmp, "frame.png");
39874
40423
  try {
39875
40424
  const result = await new Promise(
39876
40425
  (resolvePromise) => {
@@ -39908,7 +40457,7 @@ async function extractVideoFrameToBuffer(videoPath, timeSeconds) {
39908
40457
  });
39909
40458
  }
39910
40459
  );
39911
- if (result.code !== 0 || result.timedOut || !existsSync48(outPath)) return null;
40460
+ if (result.code !== 0 || result.timedOut || !existsSync49(outPath)) return null;
39912
40461
  return readFileSync34(outPath);
39913
40462
  } finally {
39914
40463
  try {
@@ -39931,7 +40480,7 @@ async function captureSnapshots(projectDir, opts) {
39931
40480
  "dist",
39932
40481
  "hyperframe.runtime.iife.js"
39933
40482
  );
39934
- if (existsSync48(runtimePath)) {
40483
+ if (existsSync49(runtimePath)) {
39935
40484
  const runtimeSource = readFileSync34(runtimePath, "utf-8");
39936
40485
  html = html.replace(
39937
40486
  /<script[^>]*data-hyperframes-preview-runtime[^>]*src="[^"]*"[^>]*><\/script>/,
@@ -39954,7 +40503,7 @@ async function captureSnapshots(projectDir, opts) {
39954
40503
  res.end();
39955
40504
  return;
39956
40505
  }
39957
- if (existsSync48(filePath)) {
40506
+ if (existsSync49(filePath)) {
39958
40507
  res.writeHead(200, { "Content-Type": getMimeType2(filePath) });
39959
40508
  res.end(readFileSync34(filePath));
39960
40509
  return;
@@ -40029,8 +40578,8 @@ async function captureSnapshots(projectDir, opts) {
40029
40578
  return [];
40030
40579
  }
40031
40580
  const positions = opts.at?.length ? opts.at : numFrames === 1 ? [duration / 2] : Array.from({ length: numFrames }, (_2, i2) => i2 / (numFrames - 1) * duration);
40032
- const snapshotDir = join48(projectDir, "snapshots");
40033
- mkdirSync24(snapshotDir, { recursive: true });
40581
+ const snapshotDir = join49(projectDir, "snapshots");
40582
+ mkdirSync25(snapshotDir, { recursive: true });
40034
40583
  let injectVideoFramesBatch2 = null;
40035
40584
  let syncVideoFrameVisibility2 = null;
40036
40585
  try {
@@ -40090,7 +40639,7 @@ async function captureSnapshots(projectDir, opts) {
40090
40639
  const decodedPath = decodeURIComponent(url.pathname).replace(/^\//, "");
40091
40640
  const candidate = resolve35(projectDir, decodedPath);
40092
40641
  const rel = relative5(projectDir, candidate);
40093
- if (!rel.startsWith("..") && !isAbsolute6(rel) && existsSync48(candidate)) {
40642
+ if (!rel.startsWith("..") && !isAbsolute6(rel) && existsSync49(candidate)) {
40094
40643
  filePath = candidate;
40095
40644
  }
40096
40645
  } catch {
@@ -40116,7 +40665,7 @@ async function captureSnapshots(projectDir, opts) {
40116
40665
  }
40117
40666
  const timeLabel = opts.at?.length ? `${time.toFixed(1)}s` : `${Math.round(time / duration * 100)}pct`;
40118
40667
  const filename = `frame-${String(i2).padStart(2, "0")}-at-${timeLabel}.png`;
40119
- const framePath = join48(snapshotDir, filename);
40668
+ const framePath = join49(snapshotDir, filename);
40120
40669
  await page.screenshot({ path: framePath, type: "png" });
40121
40670
  savedPaths.push(`snapshots/${filename}`);
40122
40671
  }
@@ -40201,14 +40750,14 @@ ${c.error("\u2717")} Snapshot failed: ${msg}`);
40201
40750
  });
40202
40751
 
40203
40752
  // src/capture/assetDownloader.ts
40204
- import { writeFileSync as writeFileSync18, mkdirSync as mkdirSync25 } from "fs";
40205
- import { join as join49, extname as extname10 } from "path";
40753
+ import { writeFileSync as writeFileSync19, mkdirSync as mkdirSync26 } from "fs";
40754
+ import { join as join50, extname as extname10 } from "path";
40206
40755
  async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks) {
40207
- const assetsDir = join49(outputDir, "assets");
40208
- mkdirSync25(assetsDir, { recursive: true });
40756
+ const assetsDir = join50(outputDir, "assets");
40757
+ mkdirSync26(assetsDir, { recursive: true });
40209
40758
  const assets = [];
40210
40759
  const downloadedUrls = /* @__PURE__ */ new Set();
40211
- mkdirSync25(join49(outputDir, "assets", "svgs"), { recursive: true });
40760
+ mkdirSync26(join50(outputDir, "assets", "svgs"), { recursive: true });
40212
40761
  for (let i2 = 0; i2 < tokens.svgs.length && i2 < 30; i2++) {
40213
40762
  const svg = tokens.svgs[i2];
40214
40763
  if (!svg.outerHTML || svg.outerHTML.length < 50) continue;
@@ -40216,7 +40765,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
40216
40765
  const name = label2 ? slugify(label2) + ".svg" : svg.isLogo ? `logo-${i2}.svg` : `icon-${i2}.svg`;
40217
40766
  const localPath = `assets/svgs/${name}`;
40218
40767
  try {
40219
- writeFileSync18(join49(outputDir, localPath), svg.outerHTML, "utf-8");
40768
+ writeFileSync19(join50(outputDir, localPath), svg.outerHTML, "utf-8");
40220
40769
  assets.push({ url: "", localPath, type: "svg" });
40221
40770
  } catch {
40222
40771
  }
@@ -40229,7 +40778,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
40229
40778
  const localPath = `assets/${name}`;
40230
40779
  const buffer = await fetchBuffer(icon.href);
40231
40780
  if (buffer) {
40232
- writeFileSync18(join49(outputDir, localPath), buffer);
40781
+ writeFileSync19(join50(outputDir, localPath), buffer);
40233
40782
  assets.push({ url: icon.href, localPath, type: "favicon" });
40234
40783
  break;
40235
40784
  }
@@ -40286,7 +40835,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
40286
40835
  const slug = isMeaningful ? slugify(rawName) : `${prefix}-${imgIdx}`;
40287
40836
  const name = `${slug}${ext}`;
40288
40837
  const localPath = `assets/${name}`;
40289
- writeFileSync18(join49(outputDir, localPath), buffer);
40838
+ writeFileSync19(join50(outputDir, localPath), buffer);
40290
40839
  assets.push({ url, localPath, type: "image" });
40291
40840
  imgIdx++;
40292
40841
  } catch {
@@ -40299,7 +40848,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks)
40299
40848
  const localPath = `assets/og-image${ext}`;
40300
40849
  const buffer = await fetchBuffer(tokens.ogImage);
40301
40850
  if (buffer && buffer.length > 5e3) {
40302
- writeFileSync18(join49(outputDir, localPath), buffer);
40851
+ writeFileSync19(join50(outputDir, localPath), buffer);
40303
40852
  assets.push({ url: tokens.ogImage, localPath, type: "image" });
40304
40853
  }
40305
40854
  } catch {
@@ -40322,8 +40871,8 @@ function normalizeUrl(u) {
40322
40871
  }
40323
40872
  }
40324
40873
  async function downloadAndRewriteFonts(css, outputDir) {
40325
- const assetsDir = join49(outputDir, "assets", "fonts");
40326
- mkdirSync25(assetsDir, { recursive: true });
40874
+ const assetsDir = join50(outputDir, "assets", "fonts");
40875
+ mkdirSync26(assetsDir, { recursive: true });
40327
40876
  const fontUrlRegex = /url\(['"]?(https?:\/\/[^'")\s]+\.(?:woff2?|ttf|otf)[^'")\s]*?)['"]?\)/g;
40328
40877
  const fontUrls = /* @__PURE__ */ new Set();
40329
40878
  let match;
@@ -40358,11 +40907,11 @@ async function downloadAndRewriteFonts(css, outputDir) {
40358
40907
  try {
40359
40908
  const urlObj = new URL(fontUrl);
40360
40909
  const filename = urlObj.pathname.split("/").pop() || `font-${count}.woff2`;
40361
- const localPath = join49(assetsDir, filename);
40910
+ const localPath = join50(assetsDir, filename);
40362
40911
  const relativePath = `assets/fonts/${filename}`;
40363
40912
  const buffer = await fetchBuffer(fontUrl);
40364
40913
  if (buffer) {
40365
- writeFileSync18(localPath, buffer);
40914
+ writeFileSync19(localPath, buffer);
40366
40915
  rewritten = rewritten.split(fontUrl).join(relativePath);
40367
40916
  familyCounts.set(family, familyCount + 1);
40368
40917
  count++;
@@ -41155,8 +41704,8 @@ var init_animationCataloger = __esm({
41155
41704
  });
41156
41705
 
41157
41706
  // src/capture/mediaCapture.ts
41158
- import { mkdirSync as mkdirSync26, writeFileSync as writeFileSync19, readdirSync as readdirSync16, readFileSync as readFileSync35, statSync as statSync16 } from "fs";
41159
- import { join as join50 } from "path";
41707
+ import { mkdirSync as mkdirSync27, writeFileSync as writeFileSync20, readdirSync as readdirSync18, readFileSync as readFileSync35, statSync as statSync18 } from "fs";
41708
+ import { join as join51 } from "path";
41160
41709
  async function saveLottieAnimations(discoveredLotties, lottieDir) {
41161
41710
  let savedCount = 0;
41162
41711
  const savedHashes = /* @__PURE__ */ new Set();
@@ -41189,7 +41738,7 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
41189
41738
  const hash2 = buf.toString("base64").slice(0, 100);
41190
41739
  if (savedHashes.has(hash2)) continue;
41191
41740
  savedHashes.add(hash2);
41192
- writeFileSync19(join50(lottieDir, `animation-${savedCount}.lottie`), buf);
41741
+ writeFileSync20(join51(lottieDir, `animation-${savedCount}.lottie`), buf);
41193
41742
  savedCount++;
41194
41743
  continue;
41195
41744
  }
@@ -41207,7 +41756,7 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
41207
41756
  } catch {
41208
41757
  continue;
41209
41758
  }
41210
- writeFileSync19(join50(lottieDir, `animation-${savedCount}.json`), jsonData, "utf-8");
41759
+ writeFileSync20(join51(lottieDir, `animation-${savedCount}.json`), jsonData, "utf-8");
41211
41760
  savedCount++;
41212
41761
  }
41213
41762
  } catch {
@@ -41217,22 +41766,22 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
41217
41766
  }
41218
41767
  async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
41219
41768
  const manifest = [];
41220
- const previewDir = join50(lottieDir, "previews");
41221
- mkdirSync26(previewDir, { recursive: true });
41222
- for (const file of readdirSync16(lottieDir)) {
41769
+ const previewDir = join51(lottieDir, "previews");
41770
+ mkdirSync27(previewDir, { recursive: true });
41771
+ for (const file of readdirSync18(lottieDir)) {
41223
41772
  if (!file.endsWith(".json")) continue;
41224
41773
  try {
41225
- const raw = JSON.parse(readFileSync35(join50(lottieDir, file), "utf-8"));
41774
+ const raw = JSON.parse(readFileSync35(join51(lottieDir, file), "utf-8"));
41226
41775
  const fr = raw.fr || 30;
41227
41776
  const dur = ((raw.op || 0) - (raw.ip || 0)) / fr;
41228
41777
  const previewName = file.replace(".json", "-preview.png");
41229
- const fileSize = statSync16(join50(lottieDir, file)).size;
41778
+ const fileSize = statSync18(join51(lottieDir, file)).size;
41230
41779
  if (fileSize > 2e6) continue;
41231
41780
  let previewPage;
41232
41781
  try {
41233
41782
  previewPage = await chromeBrowser.newPage();
41234
41783
  await previewPage.setViewport({ width: 400, height: 400 });
41235
- const animData = JSON.parse(readFileSync35(join50(lottieDir, file), "utf-8"));
41784
+ const animData = JSON.parse(readFileSync35(join51(lottieDir, file), "utf-8"));
41236
41785
  const midFrame = Math.floor(((raw.op || 0) - (raw.ip || 0)) * 0.3);
41237
41786
  await previewPage.setContent(
41238
41787
  `<!DOCTYPE html>
@@ -41262,7 +41811,7 @@ async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
41262
41811
  await previewPage.waitForFunction(() => window.__READY === true, { timeout: 5e3 }).catch(() => {
41263
41812
  });
41264
41813
  await previewPage.screenshot({
41265
- path: join50(previewDir, previewName),
41814
+ path: join51(previewDir, previewName),
41266
41815
  type: "png",
41267
41816
  omitBackground: true
41268
41817
  });
@@ -41285,8 +41834,8 @@ async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
41285
41834
  }
41286
41835
  }
41287
41836
  if (manifest.length > 0) {
41288
- writeFileSync19(
41289
- join50(outputDir, "extracted", "lottie-manifest.json"),
41837
+ writeFileSync20(
41838
+ join51(outputDir, "extracted", "lottie-manifest.json"),
41290
41839
  JSON.stringify(manifest, null, 2),
41291
41840
  "utf-8"
41292
41841
  );
@@ -41348,15 +41897,15 @@ async function captureVideoManifest(page, outputDir, progress) {
41348
41897
  return true;
41349
41898
  });
41350
41899
  if (uniqueVideos.length > 0) {
41351
- const videoManifestDir = join50(outputDir, "assets", "videos");
41352
- mkdirSync26(videoManifestDir, { recursive: true });
41353
- const previewDir = join50(videoManifestDir, "previews");
41354
- mkdirSync26(previewDir, { recursive: true });
41900
+ const videoManifestDir = join51(outputDir, "assets", "videos");
41901
+ mkdirSync27(videoManifestDir, { recursive: true });
41902
+ const previewDir = join51(videoManifestDir, "previews");
41903
+ mkdirSync27(previewDir, { recursive: true });
41355
41904
  const videoManifest = [];
41356
41905
  for (let vi = 0; vi < uniqueVideos.length && vi < 20; vi++) {
41357
41906
  const v = uniqueVideos[vi];
41358
41907
  const previewName = `video-${vi}-preview.png`;
41359
- const previewPath = join50(previewDir, previewName);
41908
+ const previewPath = join51(previewDir, previewName);
41360
41909
  try {
41361
41910
  await page.evaluate(`window.scrollTo(0, ${Math.max(0, v.top - 100)})`);
41362
41911
  await new Promise((r2) => setTimeout(r2, 300));
@@ -41394,8 +41943,8 @@ async function captureVideoManifest(page, outputDir, progress) {
41394
41943
  });
41395
41944
  }
41396
41945
  if (videoManifest.length > 0) {
41397
- writeFileSync19(
41398
- join50(outputDir, "extracted", "video-manifest.json"),
41946
+ writeFileSync20(
41947
+ join51(outputDir, "extracted", "video-manifest.json"),
41399
41948
  JSON.stringify(videoManifest, null, 2),
41400
41949
  "utf-8"
41401
41950
  );
@@ -48349,7 +48898,7 @@ var require_node_domexception = __commonJS({
48349
48898
  });
48350
48899
 
48351
48900
  // ../../node_modules/.bun/fetch-blob@3.2.0/node_modules/fetch-blob/from.js
48352
- import { statSync as statSync17, createReadStream as createReadStream2, promises as fs2 } from "fs";
48901
+ import { statSync as statSync19, createReadStream as createReadStream2, promises as fs2 } from "fs";
48353
48902
  import { basename as basename11 } from "path";
48354
48903
  var import_node_domexception, stat, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
48355
48904
  var init_from = __esm({
@@ -48359,10 +48908,10 @@ var init_from = __esm({
48359
48908
  init_file();
48360
48909
  init_fetch_blob();
48361
48910
  ({ stat } = fs2);
48362
- blobFromSync = (path2, type) => fromBlob(statSync17(path2), path2, type);
48911
+ blobFromSync = (path2, type) => fromBlob(statSync19(path2), path2, type);
48363
48912
  blobFrom = (path2, type) => stat(path2).then((stat3) => fromBlob(stat3, path2, type));
48364
48913
  fileFrom = (path2, type) => stat(path2).then((stat3) => fromFile(stat3, path2, type));
48365
- fileFromSync = (path2, type) => fromFile(statSync17(path2), path2, type);
48914
+ fileFromSync = (path2, type) => fromFile(statSync19(path2), path2, type);
48366
48915
  fromBlob = (stat3, path2, type = "") => new fetch_blob_default([new BlobDataItem({
48367
48916
  path: path2,
48368
48917
  size: stat3.size,
@@ -62153,7 +62702,7 @@ var require_websocket = __commonJS({
62153
62702
  var http4 = __require("http");
62154
62703
  var net2 = __require("net");
62155
62704
  var tls = __require("tls");
62156
- var { randomBytes, createHash: createHash3 } = __require("crypto");
62705
+ var { randomBytes, createHash: createHash4 } = __require("crypto");
62157
62706
  var { Duplex, Readable: Readable3 } = __require("stream");
62158
62707
  var { URL: URL2 } = __require("url");
62159
62708
  var PerMessageDeflate = require_permessage_deflate();
@@ -62813,7 +63362,7 @@ var require_websocket = __commonJS({
62813
63362
  abortHandshake(websocket, socket, "Invalid Upgrade header");
62814
63363
  return;
62815
63364
  }
62816
- const digest = createHash3("sha1").update(key2 + GUID).digest("base64");
63365
+ const digest = createHash4("sha1").update(key2 + GUID).digest("base64");
62817
63366
  if (res.headers["sec-websocket-accept"] !== digest) {
62818
63367
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
62819
63368
  return;
@@ -63180,7 +63729,7 @@ var require_websocket_server = __commonJS({
63180
63729
  var EventEmitter = __require("events");
63181
63730
  var http4 = __require("http");
63182
63731
  var { Duplex } = __require("stream");
63183
- var { createHash: createHash3 } = __require("crypto");
63732
+ var { createHash: createHash4 } = __require("crypto");
63184
63733
  var extension = require_extension();
63185
63734
  var PerMessageDeflate = require_permessage_deflate();
63186
63735
  var subprotocol = require_subprotocol();
@@ -63481,7 +64030,7 @@ var require_websocket_server = __commonJS({
63481
64030
  );
63482
64031
  }
63483
64032
  if (this._state > RUNNING) return abortHandshake(socket, 503);
63484
- const digest = createHash3("sha1").update(key2 + GUID).digest("base64");
64033
+ const digest = createHash4("sha1").update(key2 + GUID).digest("base64");
63485
64034
  const headers = [
63486
64035
  "HTTP/1.1 101 Switching Protocols",
63487
64036
  "Upgrade: websocket",
@@ -81850,8 +82399,8 @@ ${underline2}`);
81850
82399
  });
81851
82400
 
81852
82401
  // src/capture/contentExtractor.ts
81853
- import { readdirSync as readdirSync17, statSync as statSync18, readFileSync as readFileSync36 } from "fs";
81854
- import { join as join51 } from "path";
82402
+ import { readdirSync as readdirSync19, statSync as statSync20, readFileSync as readFileSync36 } from "fs";
82403
+ import { join as join52 } from "path";
81855
82404
  async function detectLibraries(page, capturedShaders) {
81856
82405
  let detectedLibraries = [];
81857
82406
  try {
@@ -81971,7 +82520,7 @@ async function captionImagesWithGemini(outputDir, progress, warnings) {
81971
82520
  try {
81972
82521
  const { GoogleGenAI: GoogleGenAI2 } = await Promise.resolve().then(() => (init_node4(), node_exports));
81973
82522
  const ai = new GoogleGenAI2({ apiKey: geminiKey });
81974
- const imageFiles = readdirSync17(join51(outputDir, "assets")).filter(
82523
+ const imageFiles = readdirSync19(join52(outputDir, "assets")).filter(
81975
82524
  (f3) => /\.(png|jpg|jpeg|webp|gif)$/i.test(f3)
81976
82525
  );
81977
82526
  const model = process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
@@ -81980,8 +82529,8 @@ async function captionImagesWithGemini(outputDir, progress, warnings) {
81980
82529
  const batch = imageFiles.slice(i2, i2 + BATCH_SIZE);
81981
82530
  const results = await Promise.allSettled(
81982
82531
  batch.map(async (file) => {
81983
- const filePath = join51(outputDir, "assets", file);
81984
- const stat3 = statSync18(filePath);
82532
+ const filePath = join52(outputDir, "assets", file);
82533
+ const stat3 = statSync20(filePath);
81985
82534
  if (stat3.size > 4e6) return { file, caption: "" };
81986
82535
  const buffer = readFileSync36(filePath);
81987
82536
  const base64 = buffer.toString("base64");
@@ -82029,12 +82578,12 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
82029
82578
  const uncaptionedLines = [];
82030
82579
  const svgLines = [];
82031
82580
  const fontLines = [];
82032
- const assetsPath = join51(outputDir, "assets");
82581
+ const assetsPath = join52(outputDir, "assets");
82033
82582
  try {
82034
- for (const file of readdirSync17(assetsPath)) {
82583
+ for (const file of readdirSync19(assetsPath)) {
82035
82584
  if (file === "svgs" || file === "fonts" || file === "lottie" || file === "videos") continue;
82036
- const filePath = join51(assetsPath, file);
82037
- const stat3 = statSync18(filePath);
82585
+ const filePath = join52(assetsPath, file);
82586
+ const stat3 = statSync20(filePath);
82038
82587
  if (!stat3.isFile()) continue;
82039
82588
  const sizeKb = Math.round(stat3.size / 1024);
82040
82589
  const catalogMatch = catalogedAssets.find(
@@ -82062,8 +82611,8 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
82062
82611
  } catch {
82063
82612
  }
82064
82613
  try {
82065
- const svgsPath = join51(assetsPath, "svgs");
82066
- for (const file of readdirSync17(svgsPath)) {
82614
+ const svgsPath = join52(assetsPath, "svgs");
82615
+ for (const file of readdirSync19(svgsPath)) {
82067
82616
  if (!file.endsWith(".svg")) continue;
82068
82617
  const svgMatch = tokens.svgs.find(
82069
82618
  (s2) => s2.label && file.includes(
@@ -82077,8 +82626,8 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
82077
82626
  } catch {
82078
82627
  }
82079
82628
  try {
82080
- const fontsPath = join51(assetsPath, "fonts");
82081
- for (const file of readdirSync17(fontsPath)) {
82629
+ const fontsPath = join52(assetsPath, "fonts");
82630
+ for (const file of readdirSync19(fontsPath)) {
82082
82631
  fontLines.push(`fonts/${file} \u2014 font file`);
82083
82632
  }
82084
82633
  } catch {
@@ -82096,13 +82645,13 @@ var agentPromptGenerator_exports = {};
82096
82645
  __export(agentPromptGenerator_exports, {
82097
82646
  generateAgentPrompt: () => generateAgentPrompt
82098
82647
  });
82099
- import { writeFileSync as writeFileSync20 } from "fs";
82100
- import { join as join52 } from "path";
82648
+ import { writeFileSync as writeFileSync21 } from "fs";
82649
+ import { join as join53 } from "path";
82101
82650
  function generateAgentPrompt(outputDir, url, tokens, _animations, hasScreenshot, hasLottie, hasShaders, _catalogedAssets, detectedLibraries) {
82102
82651
  const prompt = buildPrompt(url, tokens, hasScreenshot, hasLottie, hasShaders, detectedLibraries);
82103
- writeFileSync20(join52(outputDir, "AGENTS.md"), prompt, "utf-8");
82104
- writeFileSync20(join52(outputDir, "CLAUDE.md"), prompt, "utf-8");
82105
- writeFileSync20(join52(outputDir, ".cursorrules"), prompt, "utf-8");
82652
+ writeFileSync21(join53(outputDir, "AGENTS.md"), prompt, "utf-8");
82653
+ writeFileSync21(join53(outputDir, "CLAUDE.md"), prompt, "utf-8");
82654
+ writeFileSync21(join53(outputDir, ".cursorrules"), prompt, "utf-8");
82106
82655
  }
82107
82656
  function buildPrompt(url, tokens, hasScreenshot, hasLottie, hasShaders, detectedLibraries) {
82108
82657
  const title = tokens.title || new URL(url).hostname.replace(/^www\./, "");
@@ -82169,8 +82718,8 @@ var init_agentPromptGenerator = __esm({
82169
82718
  });
82170
82719
 
82171
82720
  // src/capture/scaffolding.ts
82172
- import { existsSync as existsSync49, writeFileSync as writeFileSync21, readFileSync as readFileSync37 } from "fs";
82173
- import { join as join53, resolve as resolve36 } from "path";
82721
+ import { existsSync as existsSync50, writeFileSync as writeFileSync22, readFileSync as readFileSync37 } from "fs";
82722
+ import { join as join54, resolve as resolve36 } from "path";
82174
82723
  function loadEnvFile(startDir) {
82175
82724
  try {
82176
82725
  let dir = resolve36(startDir);
@@ -82196,10 +82745,10 @@ function loadEnvFile(startDir) {
82196
82745
  }
82197
82746
  }
82198
82747
  async function generateProjectScaffold(outputDir, url, tokens, animationCatalog, hasScreenshots, hasLotties, hasShaders, catalogedAssets, progress, warnings, detectedLibraries) {
82199
- const metaPath = join53(outputDir, "meta.json");
82200
- if (!existsSync49(metaPath)) {
82748
+ const metaPath = join54(outputDir, "meta.json");
82749
+ if (!existsSync50(metaPath)) {
82201
82750
  const hostname = new URL(url).hostname.replace(/^www\./, "");
82202
- writeFileSync21(
82751
+ writeFileSync22(
82203
82752
  metaPath,
82204
82753
  JSON.stringify({ id: hostname + "-video", name: tokens.title || hostname }, null, 2),
82205
82754
  "utf-8"
@@ -82234,11 +82783,11 @@ var screenshotCapture_exports = {};
82234
82783
  __export(screenshotCapture_exports, {
82235
82784
  captureScrollScreenshots: () => captureScrollScreenshots
82236
82785
  });
82237
- import { writeFileSync as writeFileSync22, mkdirSync as mkdirSync27 } from "fs";
82238
- import { join as join54 } from "path";
82786
+ import { writeFileSync as writeFileSync23, mkdirSync as mkdirSync28 } from "fs";
82787
+ import { join as join55 } from "path";
82239
82788
  async function captureScrollScreenshots(page, outputDir) {
82240
- const screenshotsDir = join54(outputDir, "screenshots");
82241
- mkdirSync27(screenshotsDir, { recursive: true });
82789
+ const screenshotsDir = join55(outputDir, "screenshots");
82790
+ mkdirSync28(screenshotsDir, { recursive: true });
82242
82791
  const MAX_SCREENSHOTS = 20;
82243
82792
  const filePaths = [];
82244
82793
  try {
@@ -82271,9 +82820,9 @@ async function captureScrollScreenshots(page, outputDir) {
82271
82820
  finalPositions[i2] / Math.max(1, scrollHeight - viewportHeight) * 100
82272
82821
  );
82273
82822
  const filename = `scroll-${String(Math.min(pct, 100)).padStart(3, "0")}.png`;
82274
- const filePath = join54(screenshotsDir, filename);
82823
+ const filePath = join55(screenshotsDir, filename);
82275
82824
  const buffer = await page.screenshot({ type: "png" });
82276
- writeFileSync22(filePath, buffer);
82825
+ writeFileSync23(filePath, buffer);
82277
82826
  filePaths.push(`screenshots/${filename}`);
82278
82827
  }
82279
82828
  await page.evaluate(`window.scrollTo(0, 0)`);
@@ -82584,8 +83133,8 @@ var capture_exports = {};
82584
83133
  __export(capture_exports, {
82585
83134
  captureWebsite: () => captureWebsite
82586
83135
  });
82587
- import { mkdirSync as mkdirSync28, writeFileSync as writeFileSync23, existsSync as existsSync50 } from "fs";
82588
- import { join as join55 } from "path";
83136
+ import { mkdirSync as mkdirSync29, writeFileSync as writeFileSync24, existsSync as existsSync51 } from "fs";
83137
+ import { join as join56 } from "path";
82589
83138
  async function captureWebsite(opts, onProgress) {
82590
83139
  const {
82591
83140
  url,
@@ -82602,9 +83151,9 @@ async function captureWebsite(opts, onProgress) {
82602
83151
  onProgress?.(stage, detail);
82603
83152
  };
82604
83153
  loadEnvFile(outputDir);
82605
- mkdirSync28(join55(outputDir, "extracted"), { recursive: true });
82606
- mkdirSync28(join55(outputDir, "screenshots"), { recursive: true });
82607
- mkdirSync28(join55(outputDir, "assets"), { recursive: true });
83154
+ mkdirSync29(join56(outputDir, "extracted"), { recursive: true });
83155
+ mkdirSync29(join56(outputDir, "screenshots"), { recursive: true });
83156
+ mkdirSync29(join56(outputDir, "assets"), { recursive: true });
82608
83157
  progress("browser", "Launching headless Chrome...");
82609
83158
  const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
82610
83159
  const browser = await ensureBrowser2();
@@ -82760,8 +83309,8 @@ async function captureWebsite(opts, onProgress) {
82760
83309
  } catch {
82761
83310
  }
82762
83311
  if (discoveredLotties.length > 0) {
82763
- const lottieDir = join55(outputDir, "assets", "lottie");
82764
- mkdirSync28(lottieDir, { recursive: true });
83312
+ const lottieDir = join56(outputDir, "assets", "lottie");
83313
+ mkdirSync29(lottieDir, { recursive: true });
82765
83314
  const savedCount = await saveLottieAnimations(discoveredLotties, lottieDir);
82766
83315
  if (savedCount > 0) {
82767
83316
  await renderLottiePreviews(chromeBrowser, lottieDir, outputDir);
@@ -82779,8 +83328,8 @@ async function captureWebsite(opts, onProgress) {
82779
83328
  return true;
82780
83329
  });
82781
83330
  capturedShaders = unique;
82782
- writeFileSync23(
82783
- join55(outputDir, "extracted", "shaders.json"),
83331
+ writeFileSync24(
83332
+ join56(outputDir, "extracted", "shaders.json"),
82784
83333
  JSON.stringify(unique, null, 2),
82785
83334
  "utf-8"
82786
83335
  );
@@ -82790,8 +83339,8 @@ async function captureWebsite(opts, onProgress) {
82790
83339
  }
82791
83340
  progress("tokens", "Extracting design tokens...");
82792
83341
  const tokens = await extractTokens(page1);
82793
- writeFileSync23(
82794
- join55(outputDir, "extracted", "tokens.json"),
83342
+ writeFileSync24(
83343
+ join56(outputDir, "extracted", "tokens.json"),
82795
83344
  JSON.stringify(tokens, null, 2),
82796
83345
  "utf-8"
82797
83346
  );
@@ -82864,8 +83413,8 @@ async function captureWebsite(opts, onProgress) {
82864
83413
  scrollTriggeredElements: (animationCatalog.scrollTargets || []).length,
82865
83414
  representativeAnimations: representativeAnims
82866
83415
  };
82867
- writeFileSync23(
82868
- join55(outputDir, "extracted", "animations.json"),
83416
+ writeFileSync24(
83417
+ join56(outputDir, "extracted", "animations.json"),
82869
83418
  JSON.stringify(leanCatalog, null, 2),
82870
83419
  "utf-8"
82871
83420
  );
@@ -82876,18 +83425,18 @@ async function captureWebsite(opts, onProgress) {
82876
83425
  assets = await downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks);
82877
83426
  }
82878
83427
  if (visibleTextContent) {
82879
- writeFileSync23(join55(outputDir, "extracted", "visible-text.txt"), visibleTextContent, "utf-8");
83428
+ writeFileSync24(join56(outputDir, "extracted", "visible-text.txt"), visibleTextContent, "utf-8");
82880
83429
  }
82881
83430
  if (catalogedAssets.length > 0) {
82882
- writeFileSync23(
82883
- join55(outputDir, "extracted", "assets-catalog.json"),
83431
+ writeFileSync24(
83432
+ join56(outputDir, "extracted", "assets-catalog.json"),
82884
83433
  JSON.stringify(catalogedAssets, null, 2),
82885
83434
  "utf-8"
82886
83435
  );
82887
83436
  }
82888
83437
  if (detectedLibraries.length > 0) {
82889
- writeFileSync23(
82890
- join55(outputDir, "extracted", "detected-libraries.json"),
83438
+ writeFileSync24(
83439
+ join56(outputDir, "extracted", "detected-libraries.json"),
82891
83440
  JSON.stringify(detectedLibraries, null, 2),
82892
83441
  "utf-8"
82893
83442
  );
@@ -82897,8 +83446,8 @@ async function captureWebsite(opts, onProgress) {
82897
83446
  try {
82898
83447
  const lines = generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCaptions);
82899
83448
  if (lines.length > 0) {
82900
- writeFileSync23(
82901
- join55(outputDir, "extracted", "asset-descriptions.md"),
83449
+ writeFileSync24(
83450
+ join56(outputDir, "extracted", "asset-descriptions.md"),
82902
83451
  "# Asset Descriptions\n\nOne line per file. Read this instead of opening every image individually.\n\n" + lines.map((l) => "- " + l).join("\n") + "\n",
82903
83452
  "utf-8"
82904
83453
  );
@@ -82914,7 +83463,7 @@ async function captureWebsite(opts, onProgress) {
82914
83463
  animationCatalog,
82915
83464
  screenshots.length > 0,
82916
83465
  discoveredLotties.length > 0,
82917
- existsSync50(join55(outputDir, "extracted", "shaders.json")),
83466
+ existsSync51(join56(outputDir, "extracted", "shaders.json")),
82918
83467
  catalogedAssets,
82919
83468
  progress,
82920
83469
  warnings,
@@ -83093,11 +83642,11 @@ var init_capture2 = __esm({
83093
83642
  } catch (err) {
83094
83643
  const errMsg = err instanceof Error ? err.message : String(err);
83095
83644
  try {
83096
- const { mkdirSync: mkdirSync30, writeFileSync: writeFileSync24 } = await import("fs");
83097
- mkdirSync30(outputDir, { recursive: true });
83645
+ const { mkdirSync: mkdirSync31, writeFileSync: writeFileSync25 } = await import("fs");
83646
+ mkdirSync31(outputDir, { recursive: true });
83098
83647
  const isTimeout = /timeout|timed out/i.test(errMsg);
83099
83648
  const reason = isTimeout ? "Page navigation timed out \u2014 the site may be blocking headless browsers or requires authentication." : `Capture failed: ${errMsg}`;
83100
- writeFileSync24(
83649
+ writeFileSync25(
83101
83650
  `${outputDir}/BLOCKED.md`,
83102
83651
  `# Capture Failed
83103
83652
 
@@ -83261,9 +83810,9 @@ __export(autoUpdate_exports, {
83261
83810
  scheduleBackgroundInstall: () => scheduleBackgroundInstall
83262
83811
  });
83263
83812
  import { spawn as spawn12 } from "child_process";
83264
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync29, openSync } from "fs";
83813
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync30, openSync } from "fs";
83265
83814
  import { homedir as homedir10 } from "os";
83266
- import { join as join56 } from "path";
83815
+ import { join as join57 } from "path";
83267
83816
  import { compareVersions as compareVersions2 } from "compare-versions";
83268
83817
  function isAutoInstallDisabled() {
83269
83818
  if (isDevMode()) return true;
@@ -83278,15 +83827,15 @@ function majorOf(version) {
83278
83827
  }
83279
83828
  function log(line) {
83280
83829
  try {
83281
- mkdirSync29(CONFIG_DIR2, { recursive: true, mode: 448 });
83830
+ mkdirSync30(CONFIG_DIR2, { recursive: true, mode: 448 });
83282
83831
  appendFileSync2(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
83283
83832
  `, { mode: 384 });
83284
83833
  } catch {
83285
83834
  }
83286
83835
  }
83287
83836
  function launchDetachedInstall(installCommand, version) {
83288
- mkdirSync29(CONFIG_DIR2, { recursive: true, mode: 448 });
83289
- const configFile = join56(CONFIG_DIR2, "config.json");
83837
+ mkdirSync30(CONFIG_DIR2, { recursive: true, mode: 448 });
83838
+ const configFile = join57(CONFIG_DIR2, "config.json");
83290
83839
  const nodeScript = `
83291
83840
  const { exec } = require("node:child_process");
83292
83841
  const { readFileSync, renameSync, writeFileSync } = require("node:fs");
@@ -83405,8 +83954,8 @@ var init_autoUpdate = __esm({
83405
83954
  init_config();
83406
83955
  init_env();
83407
83956
  init_installerDetection();
83408
- CONFIG_DIR2 = join56(homedir10(), ".hyperframes");
83409
- LOG_FILE = join56(CONFIG_DIR2, "auto-update.log");
83957
+ CONFIG_DIR2 = join57(homedir10(), ".hyperframes");
83958
+ LOG_FILE = join57(CONFIG_DIR2, "auto-update.log");
83410
83959
  PENDING_TIMEOUT_MS = 10 * 60 * 1e3;
83411
83960
  }
83412
83961
  });