hyperframes 0.2.3-alpha.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -54,7 +54,7 @@ var VERSION;
54
54
  var init_version = __esm({
55
55
  "src/version.ts"() {
56
56
  "use strict";
57
- VERSION = true ? "0.2.3-alpha.1" : "0.0.0-dev";
57
+ VERSION = true ? "0.2.3" : "0.0.0-dev";
58
58
  }
59
59
  });
60
60
 
@@ -2631,8 +2631,8 @@ function flushSync() {
2631
2631
  eventQueue = [];
2632
2632
  const payload = JSON.stringify({ api_key: POSTHOG_API_KEY, batch });
2633
2633
  try {
2634
- const { spawn: spawn10 } = __require("child_process");
2635
- const child = spawn10(
2634
+ const { spawn: spawn11 } = __require("child_process");
2635
+ const child = spawn11(
2636
2636
  process.execPath,
2637
2637
  [
2638
2638
  "-e",
@@ -4561,6 +4561,28 @@ ${right.raw}`)
4561
4561
  }
4562
4562
  return findings;
4563
4563
  },
4564
+ // gsap_infinite_repeat
4565
+ ({ scripts }) => {
4566
+ const findings = [];
4567
+ for (const script of scripts) {
4568
+ const content = script.content;
4569
+ const pattern = /repeat\s*:\s*-1(?!\d)/g;
4570
+ let match;
4571
+ while ((match = pattern.exec(content)) !== null) {
4572
+ const contextStart = Math.max(0, match.index - 60);
4573
+ const contextEnd = Math.min(content.length, match.index + match[0].length + 60);
4574
+ const snippet = content.slice(contextStart, contextEnd).trim();
4575
+ findings.push({
4576
+ code: "gsap_infinite_repeat",
4577
+ severity: "error",
4578
+ message: "GSAP tween uses `repeat: -1` (infinite). Infinite repeats break the deterministic capture engine which seeks to exact frame times. Use a finite repeat count calculated from the composition duration: `repeat: Math.ceil(duration / cycleDuration) - 1`.",
4579
+ fixHint: "Replace `repeat: -1` with a finite count, e.g. `repeat: Math.ceil(totalDuration / singleCycleDuration) - 1`.",
4580
+ snippet: truncateSnippet(snippet)
4581
+ });
4582
+ }
4583
+ }
4584
+ return findings;
4585
+ },
4564
4586
  // scene_layer_missing_visibility_kill
4565
4587
  ({ scripts, tags }) => {
4566
4588
  const findings = [];
@@ -5643,6 +5665,7 @@ var init_mime = __esm({
5643
5665
  ".webp": "image/webp",
5644
5666
  ".ico": "image/x-icon",
5645
5667
  ".mp4": "video/mp4",
5668
+ ".mov": "video/quicktime",
5646
5669
  ".webm": "video/webm",
5647
5670
  ".mp3": "audio/mpeg",
5648
5671
  ".wav": "audio/wav",
@@ -18532,7 +18555,9 @@ function registerRenderRoutes(api, adapter2) {
18532
18555
  const project = await adapter2.resolveProject(c2.req.param("id"));
18533
18556
  if (!project) return c2.json({ error: "not found" }, 404);
18534
18557
  const body = await c2.req.json().catch(() => ({}));
18535
- const format = body.format === "webm" ? "webm" : "mp4";
18558
+ const VALID_FORMATS = /* @__PURE__ */ new Set(["mp4", "webm", "mov"]);
18559
+ const FORMAT_EXT2 = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
18560
+ const format = VALID_FORMATS.has(body.format ?? "") ? body.format : "mp4";
18536
18561
  const fps = body.fps === 24 || body.fps === 60 ? body.fps : 30;
18537
18562
  const quality = ["draft", "standard", "high"].includes(body.quality ?? "") ? body.quality : "standard";
18538
18563
  const now = /* @__PURE__ */ new Date();
@@ -18541,7 +18566,7 @@ function registerRenderRoutes(api, adapter2) {
18541
18566
  const jobId = `${project.id}_${datePart}_${timePart}`;
18542
18567
  const rendersDir = adapter2.rendersDir(project);
18543
18568
  if (!existsSync10(rendersDir)) mkdirSync6(rendersDir, { recursive: true });
18544
- const ext = format === "webm" ? ".webm" : ".mp4";
18569
+ const ext = FORMAT_EXT2[format] ?? ".mp4";
18545
18570
  const outputPath = join12(rendersDir, `${jobId}${ext}`);
18546
18571
  const jobState = adapter2.startRender({
18547
18572
  project,
@@ -18594,14 +18619,23 @@ function registerRenderRoutes(api, adapter2) {
18594
18619
  }
18595
18620
  });
18596
18621
  });
18622
+ const RENDER_MIME = {
18623
+ ".mp4": "video/mp4",
18624
+ ".webm": "video/webm",
18625
+ ".mov": "video/quicktime"
18626
+ };
18627
+ const RENDER_EXTENSIONS = Object.keys(RENDER_MIME);
18628
+ function renderContentType(filePath) {
18629
+ const ext = RENDER_EXTENSIONS.find((e) => filePath.endsWith(e));
18630
+ return (ext && RENDER_MIME[ext]) ?? "video/mp4";
18631
+ }
18597
18632
  api.get("/render/:jobId/view", (c2) => {
18598
18633
  const { jobId } = c2.req.param();
18599
18634
  const job = renderJobs.get(jobId);
18600
18635
  if (!job?.outputPath || !existsSync10(job.outputPath)) {
18601
18636
  return c2.json({ error: "not found" }, 404);
18602
18637
  }
18603
- const isWebm = job.outputPath.endsWith(".webm");
18604
- const contentType = isWebm ? "video/webm" : "video/mp4";
18638
+ const contentType = renderContentType(job.outputPath);
18605
18639
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
18606
18640
  const content = readFileSync11(job.outputPath);
18607
18641
  return new Response(content, {
@@ -18619,8 +18653,7 @@ function registerRenderRoutes(api, adapter2) {
18619
18653
  if (!job?.outputPath || !existsSync10(job.outputPath)) {
18620
18654
  return c2.json({ error: "not found" }, 404);
18621
18655
  }
18622
- const isWebm = job.outputPath.endsWith(".webm");
18623
- const contentType = isWebm ? "video/webm" : "video/mp4";
18656
+ const contentType = renderContentType(job.outputPath);
18624
18657
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
18625
18658
  const content = readFileSync11(job.outputPath);
18626
18659
  return new Response(content, {
@@ -18635,7 +18668,7 @@ function registerRenderRoutes(api, adapter2) {
18635
18668
  for (const [, state] of renderJobs) {
18636
18669
  if (state.id === jobId && state.outputPath) {
18637
18670
  const dir = state.outputPath.replace(/\/[^/]+$/, "");
18638
- for (const ext of [".mp4", ".webm", ".meta.json"]) {
18671
+ for (const ext of [".mp4", ".webm", ".mov", ".meta.json"]) {
18639
18672
  const fp = join12(dir, `${jobId}${ext}`);
18640
18673
  if (existsSync10(fp)) unlinkSync4(fp);
18641
18674
  }
@@ -18653,8 +18686,7 @@ function registerRenderRoutes(api, adapter2) {
18653
18686
  const rendersDir = adapter2.rendersDir(project);
18654
18687
  const fp = join12(rendersDir, filename);
18655
18688
  if (!existsSync10(fp)) return c2.json({ error: "not found" }, 404);
18656
- const isWebm = fp.endsWith(".webm");
18657
- const contentType = isWebm ? "video/webm" : "video/mp4";
18689
+ const contentType = renderContentType(fp);
18658
18690
  const content = readFileSync11(fp);
18659
18691
  return new Response(content, {
18660
18692
  headers: {
@@ -18670,10 +18702,10 @@ function registerRenderRoutes(api, adapter2) {
18670
18702
  if (!project) return c2.json({ error: "not found" }, 404);
18671
18703
  const rendersDir = adapter2.rendersDir(project);
18672
18704
  if (!existsSync10(rendersDir)) return c2.json({ renders: [] });
18673
- const files = readdirSync5(rendersDir).filter((f) => f.endsWith(".mp4") || f.endsWith(".webm")).map((f) => {
18705
+ const files = readdirSync5(rendersDir).filter((f) => f.endsWith(".mp4") || f.endsWith(".webm") || f.endsWith(".mov")).map((f) => {
18674
18706
  const fp = join12(rendersDir, f);
18675
18707
  const stat = statSync3(fp);
18676
- const rid = f.replace(/\.(mp4|webm)$/, "");
18708
+ const rid = f.replace(/\.(mp4|webm|mov)$/, "");
18677
18709
  const metaPath = join12(rendersDir, `${rid}.meta.json`);
18678
18710
  let status = "complete";
18679
18711
  let durationMs;
@@ -20561,6 +20593,14 @@ function getEncoderPreset(quality, format = "mp4") {
20561
20593
  pixelFormat: "yuva420p"
20562
20594
  };
20563
20595
  }
20596
+ if (format === "mov") {
20597
+ return {
20598
+ preset: "4444",
20599
+ quality: base.quality,
20600
+ codec: "prores",
20601
+ pixelFormat: "yuva444p10le"
20602
+ };
20603
+ }
20564
20604
  return { ...base, pixelFormat: "yuv420p" };
20565
20605
  }
20566
20606
  function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
@@ -20610,6 +20650,13 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
20610
20650
  args.push("-c:v", encoderName, "-preset", preset);
20611
20651
  if (bitrate) args.push("-b:v", bitrate);
20612
20652
  else args.push("-crf", String(quality));
20653
+ const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
20654
+ const colorParams = "colorprim=bt709:transfer=bt709:colormatrix=bt709";
20655
+ if (preset === "ultrafast") {
20656
+ args.push(xParamsFlag, `aq-mode=3:${colorParams}`);
20657
+ } else {
20658
+ args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
20659
+ }
20613
20660
  }
20614
20661
  } else if (codec === "vp9") {
20615
20662
  args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
@@ -20621,8 +20668,30 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
20621
20668
  }
20622
20669
  } else if (codec === "prores") {
20623
20670
  args.push("-c:v", "prores_ks", "-profile:v", preset, "-vendor", "apl0");
20671
+ args.push("-pix_fmt", pixelFormat);
20624
20672
  return [...args, "-y", outputPath];
20625
20673
  }
20674
+ if (codec === "h264" || codec === "h265") {
20675
+ args.push(
20676
+ "-colorspace:v",
20677
+ "bt709",
20678
+ "-color_primaries:v",
20679
+ "bt709",
20680
+ "-color_trc:v",
20681
+ "bt709",
20682
+ "-color_range",
20683
+ "tv"
20684
+ );
20685
+ if (gpuEncoder === "vaapi") {
20686
+ const vfIdx = args.indexOf("-vf");
20687
+ if (vfIdx !== -1) {
20688
+ args[vfIdx + 1] = `scale=in_range=pc:out_range=tv,${args[vfIdx + 1]}`;
20689
+ }
20690
+ } else if (!shouldUseGpu) {
20691
+ args.push("-vf", "scale=in_range=pc:out_range=tv");
20692
+ }
20693
+ args.push("-video_track_timescale", "90000");
20694
+ }
20626
20695
  if (gpuEncoder !== "vaapi") {
20627
20696
  args.push("-pix_fmt", pixelFormat);
20628
20697
  }
@@ -20746,7 +20815,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
20746
20815
  }
20747
20816
  const startNumber = i * chunkSize;
20748
20817
  const framesInChunk = Math.min(chunkSize, files.length - startNumber);
20749
- const ext = outputPath.endsWith(".webm") ? ".webm" : ".mp4";
20818
+ const ext = outputPath.endsWith(".webm") ? ".webm" : outputPath.endsWith(".mov") ? ".mov" : ".mp4";
20750
20819
  const chunkPath = join17(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
20751
20820
  const inputPath = join17(framesDir, framePattern);
20752
20821
  const inputArgs = [
@@ -20840,9 +20909,12 @@ async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, confi
20840
20909
  const outputDir = dirname4(outputPath);
20841
20910
  if (!existsSync15(outputDir)) mkdirSync9(outputDir, { recursive: true });
20842
20911
  const isWebm = outputPath.endsWith(".webm");
20912
+ const isMov = outputPath.endsWith(".mov");
20843
20913
  const args = ["-i", videoPath, "-i", audioPath, "-c:v", "copy"];
20844
20914
  if (isWebm) {
20845
20915
  args.push("-c:a", "libopus", "-b:a", "128k");
20916
+ } else if (isMov) {
20917
+ args.push("-c:a", "aac", "-b:a", "192k");
20846
20918
  } else {
20847
20919
  args.push("-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart");
20848
20920
  }
@@ -20865,7 +20937,7 @@ async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, confi
20865
20937
  };
20866
20938
  }
20867
20939
  async function applyFaststart(inputPath, outputPath, signal, config) {
20868
- if (outputPath.endsWith(".webm")) {
20940
+ if (outputPath.endsWith(".webm") || outputPath.endsWith(".mov")) {
20869
20941
  if (inputPath !== outputPath) copyFileSync(inputPath, outputPath);
20870
20942
  return { success: true, outputPath, durationMs: 0 };
20871
20943
  }
@@ -20993,6 +21065,13 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
20993
21065
  args.push("-c:v", encoderName, "-preset", preset);
20994
21066
  if (bitrate) args.push("-b:v", bitrate);
20995
21067
  else args.push("-crf", String(quality));
21068
+ const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
21069
+ const colorParams = "colorprim=bt709:transfer=bt709:colormatrix=bt709";
21070
+ if (preset === "ultrafast") {
21071
+ args.push(xParamsFlag, `aq-mode=3:${colorParams}`);
21072
+ } else {
21073
+ args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
21074
+ }
20996
21075
  }
20997
21076
  } else if (codec === "vp9") {
20998
21077
  args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
@@ -21004,8 +21083,30 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
21004
21083
  }
21005
21084
  } else if (codec === "prores") {
21006
21085
  args.push("-c:v", "prores_ks", "-profile:v", preset, "-vendor", "apl0");
21086
+ args.push("-pix_fmt", pixelFormat);
21007
21087
  return [...args, "-y", outputPath];
21008
21088
  }
21089
+ if (codec === "h264" || codec === "h265") {
21090
+ args.push(
21091
+ "-colorspace:v",
21092
+ "bt709",
21093
+ "-color_primaries:v",
21094
+ "bt709",
21095
+ "-color_trc:v",
21096
+ "bt709",
21097
+ "-color_range",
21098
+ "tv"
21099
+ );
21100
+ if (gpuEncoder === "vaapi") {
21101
+ const vfIdx = args.indexOf("-vf");
21102
+ if (vfIdx !== -1) {
21103
+ args[vfIdx + 1] = `scale=in_range=pc:out_range=tv,${args[vfIdx + 1]}`;
21104
+ }
21105
+ } else if (!shouldUseGpu) {
21106
+ args.push("-vf", "scale=in_range=pc:out_range=tv");
21107
+ }
21108
+ args.push("-video_track_timescale", "90000");
21109
+ }
21009
21110
  if (gpuEncoder !== "vaapi") {
21010
21111
  args.push("-pix_fmt", pixelFormat);
21011
21112
  }
@@ -22915,6 +23016,17 @@ async function bundleToSingleHtml(projectDir, options) {
22915
23016
  const contentHtml = contentRoot ? contentRoot.innerHTML || "" : compDoc.body.innerHTML || "";
22916
23017
  const contentDoc = parseHTMLContent(contentHtml);
22917
23018
  const innerRoot = compId ? contentDoc.querySelector(`[data-composition-id="${compId}"]`) : contentDoc.querySelector("[data-composition-id]");
23019
+ if (!contentRoot && compDoc.head) {
23020
+ for (const s of [...compDoc.head.querySelectorAll("style")]) {
23021
+ compStyleChunks.push(rewriteCssAssetUrls(s.textContent || "", src));
23022
+ }
23023
+ for (const s of [...compDoc.head.querySelectorAll("script")]) {
23024
+ const externalSrc = (s.getAttribute("src") || "").trim();
23025
+ if (externalSrc && !compExternalScriptSrcs.includes(externalSrc)) {
23026
+ compExternalScriptSrcs.push(externalSrc);
23027
+ }
23028
+ }
23029
+ }
22918
23030
  for (const s of [...contentDoc.querySelectorAll("style")]) {
22919
23031
  compStyleChunks.push(rewriteCssAssetUrls(s.textContent || "", src));
22920
23032
  s.remove();
@@ -24095,6 +24207,26 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
24095
24207
  const contentDoc = parseHTML(contentHtml).document;
24096
24208
  const innerRoot = compId ? contentDoc.querySelector(`[data-composition-id="${compId}"]`) : contentDoc.querySelector("[data-composition-id]");
24097
24209
  const inferredCompId = innerRoot?.getAttribute("data-composition-id")?.trim() || null;
24210
+ if (!templateEl) {
24211
+ const compHead = compDoc.querySelector("head");
24212
+ if (compHead) {
24213
+ for (const styleEl of compHead.querySelectorAll("style")) {
24214
+ const css = rewriteCssAssetUrls(styleEl.textContent || "", srcPath);
24215
+ const scopeId = compId || inferredCompId;
24216
+ if (scopeId && css.trim()) {
24217
+ collectedStyles.push(scopeCssToComposition(css, scopeId));
24218
+ } else {
24219
+ collectedStyles.push(css);
24220
+ }
24221
+ }
24222
+ for (const scriptEl of compHead.querySelectorAll("script")) {
24223
+ const src = (scriptEl.getAttribute("src") || "").trim();
24224
+ if (src && !collectedExternalScriptSrcs.includes(src)) {
24225
+ collectedExternalScriptSrcs.push(src);
24226
+ }
24227
+ }
24228
+ }
24229
+ }
24098
24230
  for (const styleEl of contentDoc.querySelectorAll("style")) {
24099
24231
  const css = rewriteCssAssetUrls(styleEl.textContent || "", srcPath);
24100
24232
  const scopeId = compId || inferredCompId;
@@ -24702,7 +24834,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
24702
24834
  const cfg = { ...job.config.producerConfig ?? resolveConfig() };
24703
24835
  const outputFormat = job.config.format ?? "mp4";
24704
24836
  const isWebm = outputFormat === "webm";
24705
- if (isWebm) {
24837
+ const isMov = outputFormat === "mov";
24838
+ const needsAlpha = isWebm || isMov;
24839
+ if (needsAlpha) {
24706
24840
  cfg.forceScreenshot = true;
24707
24841
  }
24708
24842
  const enableChunkedEncode = cfg.enableChunkedEncode;
@@ -24791,8 +24925,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
24791
24925
  width,
24792
24926
  height,
24793
24927
  fps: job.config.fps,
24794
- format: isWebm ? "png" : "jpeg",
24795
- quality: isWebm ? void 0 : 80
24928
+ format: needsAlpha ? "png" : "jpeg",
24929
+ quality: needsAlpha ? void 0 : 80
24796
24930
  };
24797
24931
  probeSession = await createCaptureSession(
24798
24932
  fileServer.url,
@@ -25032,11 +25166,12 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25032
25166
  width,
25033
25167
  height,
25034
25168
  fps: job.config.fps,
25035
- format: isWebm ? "png" : "jpeg",
25036
- quality: isWebm ? void 0 : job.config.quality === "draft" ? 80 : 95
25169
+ format: needsAlpha ? "png" : "jpeg",
25170
+ quality: needsAlpha ? void 0 : job.config.quality === "draft" ? 80 : 95
25037
25171
  };
25038
25172
  const workerCount = calculateOptimalWorkers(job.totalFrames, job.config.workers, cfg);
25039
- const videoExt = isWebm ? ".webm" : ".mp4";
25173
+ const FORMAT_EXT2 = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
25174
+ const videoExt = FORMAT_EXT2[outputFormat] ?? ".mp4";
25040
25175
  const videoOnlyPath = join26(workDir, `video-only${videoExt}`);
25041
25176
  const preset = getEncoderPreset(job.config.quality, outputFormat);
25042
25177
  job.framesRendered = 0;
@@ -25222,7 +25357,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25222
25357
  perfStages.captureMs = Date.now() - stage4Start;
25223
25358
  const stage5Start = Date.now();
25224
25359
  updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
25225
- const frameExt = isWebm ? "png" : "jpg";
25360
+ const frameExt = needsAlpha ? "png" : "jpg";
25226
25361
  const framePattern = `frame_%06d.${frameExt}`;
25227
25362
  const encoderOpts = {
25228
25363
  fps: job.config.fps,
@@ -25316,7 +25451,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25316
25451
  }
25317
25452
  if (job.config.debug) {
25318
25453
  if (existsSync26(outputPath)) {
25319
- const debugOutput = join26(workDir, isWebm ? "output.webm" : "output.mp4");
25454
+ const debugOutput = join26(workDir, `output${videoExt}`);
25320
25455
  copyFileSync2(outputPath, debugOutput);
25321
25456
  }
25322
25457
  } else {
@@ -25590,7 +25725,7 @@ function parseRenderOptions(body) {
25590
25725
  const debug = body.debug === true;
25591
25726
  const outputPath = typeof body.outputPath === "string" && body.outputPath.trim().length > 0 ? body.outputPath : typeof body.output === "string" && body.output.trim().length > 0 ? body.output : null;
25592
25727
  const entryFile = typeof body.entryFile === "string" && body.entryFile.trim().length > 0 ? body.entryFile.trim() : void 0;
25593
- const format = ["mp4", "webm"].includes(body.format) ? body.format : void 0;
25728
+ const format = ["mp4", "webm", "mov"].includes(body.format) ? body.format : void 0;
25594
25729
  return { outputPath, fps, quality, workers, useGpu, debug, entryFile, format };
25595
25730
  }
25596
25731
  async function prepareRenderBody(body) {
@@ -26195,7 +26330,7 @@ function createStudioServer(options) {
26195
26330
  await executeRenderJob2(job, opts.project.dir, opts.outputPath, onProgress);
26196
26331
  state.status = "complete";
26197
26332
  state.progress = 100;
26198
- const metaPath = opts.outputPath.replace(/\.(mp4|webm)$/, ".meta.json");
26333
+ const metaPath = opts.outputPath.replace(/\.(mp4|webm|mov)$/, ".meta.json");
26199
26334
  writeFileSync10(
26200
26335
  metaPath,
26201
26336
  JSON.stringify({ status: "complete", durationMs: Date.now() - startTime })
@@ -26204,7 +26339,7 @@ function createStudioServer(options) {
26204
26339
  state.status = "failed";
26205
26340
  state.error = err instanceof Error ? err.message : String(err);
26206
26341
  try {
26207
- const metaPath = opts.outputPath.replace(/\.(mp4|webm)$/, ".meta.json");
26342
+ const metaPath = opts.outputPath.replace(/\.(mp4|webm|mov)$/, ".meta.json");
26208
26343
  writeFileSync10(metaPath, JSON.stringify({ status: "failed" }));
26209
26344
  } catch {
26210
26345
  }
@@ -26403,7 +26538,7 @@ async function runDevMode(dir, projectName) {
26403
26538
  console.log();
26404
26539
  console.log(` ${c.dim("Press Ctrl+C to stop")}`);
26405
26540
  console.log();
26406
- const urlToOpen = `${frontendUrl}#/project/${pName}`;
26541
+ const urlToOpen = `${frontendUrl}#project/${pName}`;
26407
26542
  import("open").then((mod) => mod.default(urlToOpen)).catch(() => {
26408
26543
  });
26409
26544
  child.stdout?.removeListener("data", handleOutput);
@@ -27520,30 +27655,149 @@ __export(render_exports, {
27520
27655
  default: () => render_default,
27521
27656
  examples: () => examples4
27522
27657
  });
27523
- import { existsSync as existsSync34, mkdirSync as mkdirSync20, statSync as statSync12 } from "fs";
27524
- import { cpus as cpus3, freemem as freemem3 } from "os";
27525
- import { resolve as resolve20, dirname as dirname14, join as join33 } from "path";
27658
+ import { mkdirSync as mkdirSync20, readFileSync as readFileSync23, statSync as statSync12, writeFileSync as writeFileSync12, rmSync as rmSync8 } from "fs";
27659
+ import { cpus as cpus3, freemem as freemem3, tmpdir as tmpdir3 } from "os";
27660
+ import { resolve as resolve20, dirname as dirname14, join as join33, basename as basename6 } from "path";
27661
+ import { execFileSync as execFileSync5, spawn as spawn10 } from "child_process";
27526
27662
  function defaultWorkerCount() {
27527
27663
  return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT * 3 / 4), 8));
27528
27664
  }
27665
+ function dockerImageTag(version) {
27666
+ return `${DOCKER_IMAGE_PREFIX}:${version}`;
27667
+ }
27668
+ function resolveDockerfilePath() {
27669
+ const builtPath = resolve20(__dirname, "docker", "Dockerfile.render");
27670
+ const devPath = resolve20(__dirname, "..", "src", "docker", "Dockerfile.render");
27671
+ for (const p of [builtPath, devPath]) {
27672
+ try {
27673
+ statSync12(p);
27674
+ return p;
27675
+ } catch {
27676
+ continue;
27677
+ }
27678
+ }
27679
+ throw new Error("Dockerfile.render not found \u2014 CLI package may be corrupted");
27680
+ }
27681
+ function dockerImageExists(tag) {
27682
+ try {
27683
+ execFileSync5("docker", ["image", "inspect", tag], { stdio: "pipe", timeout: 1e4 });
27684
+ return true;
27685
+ } catch {
27686
+ return false;
27687
+ }
27688
+ }
27689
+ function ensureDockerImage(version, quiet) {
27690
+ const tag = dockerImageTag(version);
27691
+ if (dockerImageExists(tag)) {
27692
+ if (!quiet) console.log(c.dim(` Docker image: ${tag} (cached)`));
27693
+ return tag;
27694
+ }
27695
+ if (!quiet) console.log(c.dim(` Building Docker image: ${tag}...`));
27696
+ const dockerfilePath = resolveDockerfilePath();
27697
+ const tmpDir = join33(tmpdir3(), `hyperframes-docker-${Date.now()}`);
27698
+ mkdirSync20(tmpDir, { recursive: true });
27699
+ writeFileSync12(join33(tmpDir, "Dockerfile"), readFileSync23(dockerfilePath));
27700
+ try {
27701
+ execFileSync5(
27702
+ "docker",
27703
+ [
27704
+ "build",
27705
+ "--platform",
27706
+ "linux/amd64",
27707
+ "--build-arg",
27708
+ `HYPERFRAMES_VERSION=${version}`,
27709
+ "-t",
27710
+ tag,
27711
+ tmpDir
27712
+ ],
27713
+ { stdio: quiet ? "pipe" : "inherit", timeout: 6e5 }
27714
+ );
27715
+ } catch (error) {
27716
+ const message = error instanceof Error ? error.message : String(error);
27717
+ throw new Error(`Failed to build Docker image: ${message}`);
27718
+ } finally {
27719
+ rmSync8(tmpDir, { recursive: true, force: true });
27720
+ }
27721
+ if (!quiet) console.log(c.dim(` Docker image: ${tag} (built)`));
27722
+ return tag;
27723
+ }
27529
27724
  async function renderDocker(projectDir, outputPath, options) {
27530
- const producer = await loadProducer();
27531
27725
  const startTime = Date.now();
27532
- let job;
27726
+ const dockerVersion = isDevMode() ? "latest" : VERSION;
27727
+ if (!options.quiet && isDevMode()) {
27728
+ console.log(c.dim(" Dev mode: using hyperframes@latest in Docker image"));
27729
+ }
27730
+ let imageTag;
27731
+ try {
27732
+ imageTag = ensureDockerImage(dockerVersion, options.quiet);
27733
+ } catch (error) {
27734
+ const message = error instanceof Error ? error.message : String(error);
27735
+ const isDockerMissing = /connect|not found|ENOENT/i.test(message);
27736
+ errorBox(
27737
+ isDockerMissing ? "Docker not available" : "Docker image build failed",
27738
+ message,
27739
+ isDockerMissing ? "Install Docker: https://docs.docker.com/get-docker/" : "Check Docker is running: docker info"
27740
+ );
27741
+ process.exit(1);
27742
+ }
27743
+ const outputDir = dirname14(outputPath);
27744
+ const outputFilename = basename6(outputPath);
27745
+ const dockerArgs = [
27746
+ "run",
27747
+ "--rm",
27748
+ "--platform",
27749
+ "linux/amd64",
27750
+ "--shm-size=2g",
27751
+ // GPU encoding requires host GPU passthrough
27752
+ ...options.gpu ? ["--gpus", "all"] : [],
27753
+ "-v",
27754
+ `${resolve20(projectDir)}:/project:ro`,
27755
+ "-v",
27756
+ `${resolve20(outputDir)}:/output`,
27757
+ imageTag,
27758
+ "/project",
27759
+ "--output",
27760
+ `/output/${outputFilename}`,
27761
+ "--fps",
27762
+ String(options.fps),
27763
+ "--quality",
27764
+ options.quality,
27765
+ "--format",
27766
+ options.format,
27767
+ "--workers",
27768
+ String(options.workers),
27769
+ ...options.quiet ? ["--quiet"] : [],
27770
+ ...options.gpu ? ["--gpu"] : []
27771
+ ];
27772
+ if (!options.quiet) {
27773
+ console.log(c.dim(" Running render in Docker container..."));
27774
+ console.log("");
27775
+ }
27533
27776
  try {
27534
- job = producer.createRenderJob({
27535
- fps: options.fps,
27536
- quality: options.quality,
27537
- format: options.format,
27538
- workers: options.workers,
27539
- useGpu: options.gpu
27777
+ await new Promise((resolvePromise, reject) => {
27778
+ const child = spawn10("docker", dockerArgs, {
27779
+ // When quiet, still show stderr so container errors surface
27780
+ stdio: options.quiet ? ["pipe", "pipe", "inherit"] : "inherit"
27781
+ });
27782
+ child.on("close", (code) => {
27783
+ if (code === 0) resolvePromise();
27784
+ else reject(new Error(`Docker render exited with code ${code}`));
27785
+ });
27786
+ child.on("error", (err) => reject(err));
27540
27787
  });
27541
- await producer.executeRenderJob(job, projectDir, outputPath);
27542
27788
  } catch (error) {
27543
27789
  handleRenderError(error, options, startTime, true, "Check Docker is running: docker info");
27544
27790
  }
27545
27791
  const elapsed = Date.now() - startTime;
27546
- trackRenderMetrics(job, elapsed, options, true);
27792
+ trackRenderComplete({
27793
+ durationMs: elapsed,
27794
+ fps: options.fps,
27795
+ quality: options.quality,
27796
+ workers: options.workers,
27797
+ docker: true,
27798
+ gpu: options.gpu,
27799
+ ...getMemorySnapshot()
27800
+ });
27547
27801
  printRenderComplete(outputPath, elapsed, options.quiet);
27548
27802
  }
27549
27803
  async function renderLocal(projectDir, outputPath, options) {
@@ -27616,16 +27870,16 @@ function trackRenderMetrics(job, elapsedMs, options, docker) {
27616
27870
  function printRenderComplete(outputPath, elapsedMs, quiet) {
27617
27871
  if (quiet) return;
27618
27872
  let fileSize = "unknown";
27619
- if (existsSync34(outputPath)) {
27620
- const stat = statSync12(outputPath);
27621
- fileSize = formatBytes(stat.size);
27873
+ try {
27874
+ fileSize = formatBytes(statSync12(outputPath).size);
27875
+ } catch {
27622
27876
  }
27623
27877
  const duration = formatDuration(elapsedMs);
27624
27878
  console.log("");
27625
27879
  console.log(c.success("\u25C7") + " " + c.accent(outputPath));
27626
27880
  console.log(" " + c.bold(fileSize) + c.dim(" \xB7 " + duration + " \xB7 completed"));
27627
27881
  }
27628
- var examples4, VALID_FPS, VALID_QUALITY, VALID_FORMAT, CPU_CORE_COUNT, render_default;
27882
+ var examples4, VALID_FPS, VALID_QUALITY, VALID_FORMAT, FORMAT_EXT, CPU_CORE_COUNT, render_default, DOCKER_IMAGE_PREFIX;
27629
27883
  var init_render2 = __esm({
27630
27884
  "src/commands/render.ts"() {
27631
27885
  "use strict";
@@ -27639,8 +27893,11 @@ var init_render2 = __esm({
27639
27893
  init_progress();
27640
27894
  init_events();
27641
27895
  init_system();
27896
+ init_version();
27897
+ init_env();
27642
27898
  examples4 = [
27643
27899
  ["Render to MP4", "hyperframes render --output output.mp4"],
27900
+ ["Render transparent overlay (ProRes)", "hyperframes render --format mov --output overlay.mov"],
27644
27901
  ["Render transparent WebM overlay", "hyperframes render --format webm --output overlay.webm"],
27645
27902
  ["High quality at 60fps", "hyperframes render --fps 60 --quality high --output hd.mp4"],
27646
27903
  ["Deterministic render via Docker", "hyperframes render --docker --output deterministic.mp4"],
@@ -27648,12 +27905,13 @@ var init_render2 = __esm({
27648
27905
  ];
27649
27906
  VALID_FPS = /* @__PURE__ */ new Set([24, 30, 60]);
27650
27907
  VALID_QUALITY = /* @__PURE__ */ new Set(["draft", "standard", "high"]);
27651
- VALID_FORMAT = /* @__PURE__ */ new Set(["mp4", "webm"]);
27908
+ VALID_FORMAT = /* @__PURE__ */ new Set(["mp4", "webm", "mov"]);
27909
+ FORMAT_EXT = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
27652
27910
  CPU_CORE_COUNT = cpus3().length;
27653
27911
  render_default = defineCommand({
27654
27912
  meta: {
27655
27913
  name: "render",
27656
- description: "Render a composition to MP4 or WebM"
27914
+ description: "Render a composition to MP4, WebM, or MOV"
27657
27915
  },
27658
27916
  args: {
27659
27917
  dir: {
@@ -27677,7 +27935,7 @@ var init_render2 = __esm({
27677
27935
  },
27678
27936
  format: {
27679
27937
  type: "string",
27680
- description: "Output format: mp4, webm (WebM renders with transparency)",
27938
+ description: "Output format: mp4, webm, mov (MOV/WebM render with transparency)",
27681
27939
  default: "mp4"
27682
27940
  },
27683
27941
  workers: {
@@ -27722,7 +27980,7 @@ var init_render2 = __esm({
27722
27980
  const quality = qualityRaw;
27723
27981
  const formatRaw = args.format ?? "mp4";
27724
27982
  if (!VALID_FORMAT.has(formatRaw)) {
27725
- errorBox("Invalid format", `Got "${formatRaw}". Must be mp4 or webm.`);
27983
+ errorBox("Invalid format", `Got "${formatRaw}". Must be mp4, webm, or mov.`);
27726
27984
  process.exit(1);
27727
27985
  }
27728
27986
  const format = formatRaw;
@@ -27736,7 +27994,7 @@ var init_render2 = __esm({
27736
27994
  workers = parsed;
27737
27995
  }
27738
27996
  const rendersDir = resolve20("renders");
27739
- const ext = format === "webm" ? ".webm" : ".mp4";
27997
+ const ext = FORMAT_EXT[format] ?? ".mp4";
27740
27998
  const now = /* @__PURE__ */ new Date();
27741
27999
  const datePart = now.toISOString().slice(0, 10);
27742
28000
  const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
@@ -27839,6 +28097,7 @@ var init_render2 = __esm({
27839
28097
  }
27840
28098
  }
27841
28099
  });
28100
+ DOCKER_IMAGE_PREFIX = "hyperframes-renderer";
27842
28101
  }
27843
28102
  });
27844
28103
 
@@ -28059,7 +28318,7 @@ __export(info_exports, {
28059
28318
  default: () => info_default,
28060
28319
  examples: () => examples6
28061
28320
  });
28062
- import { readFileSync as readFileSync23, readdirSync as readdirSync11, statSync as statSync13 } from "fs";
28321
+ import { readFileSync as readFileSync24, readdirSync as readdirSync11, statSync as statSync13 } from "fs";
28063
28322
  import { join as join34 } from "path";
28064
28323
  function totalSize(dir) {
28065
28324
  let total = 0;
@@ -28096,7 +28355,7 @@ var init_info = __esm({
28096
28355
  },
28097
28356
  async run({ args }) {
28098
28357
  const project = resolveProject(args.dir);
28099
- const html = readFileSync23(project.indexPath, "utf-8");
28358
+ const html = readFileSync24(project.indexPath, "utf-8");
28100
28359
  ensureDOMParser();
28101
28360
  const parsed = parseHtml(html);
28102
28361
  const tracks = new Set(parsed.elements.map((el) => el.zIndex));
@@ -28152,7 +28411,7 @@ __export(compositions_exports, {
28152
28411
  default: () => compositions_default,
28153
28412
  examples: () => examples7
28154
28413
  });
28155
- import { existsSync as existsSync35, readFileSync as readFileSync24 } from "fs";
28414
+ import { existsSync as existsSync34, readFileSync as readFileSync25 } from "fs";
28156
28415
  import { resolve as resolve21, dirname as dirname15 } from "path";
28157
28416
  function parseCompositions(html, baseDir) {
28158
28417
  const parser = new DOMParser();
@@ -28166,8 +28425,8 @@ function parseCompositions(html, baseDir) {
28166
28425
  const compositionSrc = div.getAttribute("data-composition-src");
28167
28426
  if (compositionSrc) {
28168
28427
  const subPath = resolve21(baseDir, compositionSrc);
28169
- if (existsSync35(subPath)) {
28170
- const subHtml = readFileSync24(subPath, "utf-8");
28428
+ if (existsSync34(subPath)) {
28429
+ const subHtml = readFileSync25(subPath, "utf-8");
28171
28430
  const subInfo = parseSubComposition(subHtml, id, width, height);
28172
28431
  compositions.push({ ...subInfo, source: compositionSrc });
28173
28432
  return;
@@ -28261,7 +28520,7 @@ var init_compositions = __esm({
28261
28520
  },
28262
28521
  async run({ args }) {
28263
28522
  const project = resolveProject(args.dir);
28264
- const html = readFileSync24(project.indexPath, "utf-8");
28523
+ const html = readFileSync25(project.indexPath, "utf-8");
28265
28524
  ensureDOMParser();
28266
28525
  const compositions = parseCompositions(html, dirname15(project.indexPath));
28267
28526
  if (compositions.length === 0) {
@@ -28299,7 +28558,7 @@ __export(benchmark_exports, {
28299
28558
  default: () => benchmark_default,
28300
28559
  examples: () => examples8
28301
28560
  });
28302
- import { existsSync as existsSync36, statSync as statSync14 } from "fs";
28561
+ import { existsSync as existsSync35, statSync as statSync14 } from "fs";
28303
28562
  import { resolve as resolve22, join as join35 } from "path";
28304
28563
  var examples8, DEFAULT_CONFIGS, benchmark_default;
28305
28564
  var init_benchmark = __esm({
@@ -28389,7 +28648,7 @@ var init_benchmark = __esm({
28389
28648
  await producer.executeRenderJob(job, project.dir, outputPath);
28390
28649
  const elapsedMs = Date.now() - startTime;
28391
28650
  let fileSize = null;
28392
- if (existsSync36(outputPath)) {
28651
+ if (existsSync35(outputPath)) {
28393
28652
  const stat = statSync14(outputPath);
28394
28653
  fileSize = stat.size;
28395
28654
  }
@@ -28605,7 +28864,7 @@ __export(transcribe_exports2, {
28605
28864
  default: () => transcribe_default,
28606
28865
  examples: () => examples10
28607
28866
  });
28608
- import { existsSync as existsSync37, writeFileSync as writeFileSync12 } from "fs";
28867
+ import { existsSync as existsSync36, writeFileSync as writeFileSync13 } from "fs";
28609
28868
  import { resolve as resolve23, join as join36, extname as extname7 } from "path";
28610
28869
  async function importTranscript(inputPath, dir, json) {
28611
28870
  const { loadTranscript: loadTranscript2, patchCaptionHtml: patchCaptionHtml2 } = await Promise.resolve().then(() => (init_normalize(), normalize_exports));
@@ -28615,7 +28874,7 @@ async function importTranscript(inputPath, dir, json) {
28615
28874
  process.exit(1);
28616
28875
  }
28617
28876
  const outPath = join36(dir, "transcript.json");
28618
- writeFileSync12(outPath, JSON.stringify(words, null, 2));
28877
+ writeFileSync13(outPath, JSON.stringify(words, null, 2));
28619
28878
  patchCaptionHtml2(dir, words);
28620
28879
  if (json) {
28621
28880
  console.log(
@@ -28650,7 +28909,7 @@ async function transcribeAudio(inputPath, dir, opts) {
28650
28909
  );
28651
28910
  }
28652
28911
  }
28653
- writeFileSync12(result.transcriptPath, JSON.stringify(words, null, 2));
28912
+ writeFileSync13(result.transcriptPath, JSON.stringify(words, null, 2));
28654
28913
  patchCaptionHtml2(dir, words);
28655
28914
  if (opts.json) {
28656
28915
  console.log(
@@ -28731,7 +28990,7 @@ var init_transcribe2 = __esm({
28731
28990
  },
28732
28991
  async run({ args }) {
28733
28992
  const inputPath = resolve23(args.input);
28734
- if (!existsSync37(inputPath)) {
28993
+ if (!existsSync36(inputPath)) {
28735
28994
  console.error(c.error(`File not found: ${args.input}`));
28736
28995
  process.exit(1);
28737
28996
  }
@@ -28752,12 +29011,12 @@ var init_transcribe2 = __esm({
28752
29011
  });
28753
29012
 
28754
29013
  // src/tts/manager.ts
28755
- import { existsSync as existsSync38, mkdirSync as mkdirSync21 } from "fs";
29014
+ import { existsSync as existsSync37, mkdirSync as mkdirSync21 } from "fs";
28756
29015
  import { homedir as homedir6 } from "os";
28757
29016
  import { join as join37 } from "path";
28758
29017
  async function ensureModel2(model = DEFAULT_MODEL2, options) {
28759
29018
  const modelPath = join37(MODELS_DIR2, `${model}.onnx`);
28760
- if (existsSync38(modelPath)) return modelPath;
29019
+ if (existsSync37(modelPath)) return modelPath;
28761
29020
  const url = MODEL_URLS[model];
28762
29021
  if (!url) {
28763
29022
  throw new Error(
@@ -28767,18 +29026,18 @@ async function ensureModel2(model = DEFAULT_MODEL2, options) {
28767
29026
  mkdirSync21(MODELS_DIR2, { recursive: true });
28768
29027
  options?.onProgress?.(`Downloading TTS model ${model} (~311 MB)...`);
28769
29028
  await downloadFile(url, modelPath);
28770
- if (!existsSync38(modelPath)) {
29029
+ if (!existsSync37(modelPath)) {
28771
29030
  throw new Error(`Model download failed: ${model}`);
28772
29031
  }
28773
29032
  return modelPath;
28774
29033
  }
28775
29034
  async function ensureVoices(options) {
28776
29035
  const voicesPath = join37(VOICES_DIR, "voices-v1.0.bin");
28777
- if (existsSync38(voicesPath)) return voicesPath;
29036
+ if (existsSync37(voicesPath)) return voicesPath;
28778
29037
  mkdirSync21(VOICES_DIR, { recursive: true });
28779
29038
  options?.onProgress?.("Downloading voice data (~27 MB)...");
28780
29039
  await downloadFile(VOICES_URL, voicesPath);
28781
- if (!existsSync38(voicesPath)) {
29040
+ if (!existsSync37(voicesPath)) {
28782
29041
  throw new Error("Voice data download failed");
28783
29042
  }
28784
29043
  return voicesPath;
@@ -28815,19 +29074,19 @@ var synthesize_exports = {};
28815
29074
  __export(synthesize_exports, {
28816
29075
  synthesize: () => synthesize
28817
29076
  });
28818
- import { execFileSync as execFileSync5 } from "child_process";
28819
- import { existsSync as existsSync39, writeFileSync as writeFileSync13, mkdirSync as mkdirSync22 } from "fs";
29077
+ import { execFileSync as execFileSync6 } from "child_process";
29078
+ import { existsSync as existsSync38, writeFileSync as writeFileSync14, mkdirSync as mkdirSync22 } from "fs";
28820
29079
  import { join as join38, dirname as dirname16 } from "path";
28821
29080
  import { homedir as homedir7 } from "os";
28822
29081
  function findPython() {
28823
29082
  for (const name of ["python3", "python"]) {
28824
29083
  try {
28825
- const result = execFileSync5("which", [name], {
29084
+ const result = execFileSync6("which", [name], {
28826
29085
  encoding: "utf-8",
28827
29086
  stdio: ["pipe", "pipe", "pipe"],
28828
29087
  timeout: 5e3
28829
29088
  }).trim();
28830
- const version = execFileSync5(result, ["--version"], {
29089
+ const version = execFileSync6(result, ["--version"], {
28831
29090
  encoding: "utf-8",
28832
29091
  stdio: ["pipe", "pipe", "pipe"],
28833
29092
  timeout: 5e3
@@ -28840,7 +29099,7 @@ function findPython() {
28840
29099
  }
28841
29100
  function hasPythonPackage(python, pkg) {
28842
29101
  try {
28843
- execFileSync5(python, ["-c", `import ${pkg}`], {
29102
+ execFileSync6(python, ["-c", `import ${pkg}`], {
28844
29103
  stdio: ["pipe", "pipe", "pipe"],
28845
29104
  timeout: 1e4
28846
29105
  });
@@ -28850,9 +29109,9 @@ function hasPythonPackage(python, pkg) {
28850
29109
  }
28851
29110
  }
28852
29111
  function ensureSynthScript() {
28853
- if (!existsSync39(SCRIPT_PATH)) {
29112
+ if (!existsSync38(SCRIPT_PATH)) {
28854
29113
  mkdirSync22(SCRIPT_DIR, { recursive: true });
28855
- writeFileSync13(SCRIPT_PATH, SYNTH_SCRIPT);
29114
+ writeFileSync14(SCRIPT_PATH, SYNTH_SCRIPT);
28856
29115
  }
28857
29116
  return SCRIPT_PATH;
28858
29117
  }
@@ -28882,7 +29141,7 @@ async function synthesize(text, outputPath, options) {
28882
29141
  mkdirSync22(dirname16(outputPath), { recursive: true });
28883
29142
  options?.onProgress?.(`Generating speech with voice ${voice}...`);
28884
29143
  try {
28885
- const stdout2 = execFileSync5(
29144
+ const stdout2 = execFileSync6(
28886
29145
  python,
28887
29146
  [scriptPath, modelPath, voicesPath, text, voice, String(speed), outputPath],
28888
29147
  {
@@ -28891,7 +29150,7 @@ async function synthesize(text, outputPath, options) {
28891
29150
  stdio: ["pipe", "pipe", "pipe"]
28892
29151
  }
28893
29152
  );
28894
- if (!existsSync39(outputPath)) {
29153
+ if (!existsSync38(outputPath)) {
28895
29154
  throw new Error("Synthesis completed but no output file was created");
28896
29155
  }
28897
29156
  const lines = stdout2.trim().split("\n");
@@ -28903,7 +29162,7 @@ async function synthesize(text, outputPath, options) {
28903
29162
  durationSeconds: result.durationSeconds
28904
29163
  };
28905
29164
  } catch (err) {
28906
- if (err instanceof SyntaxError && existsSync39(outputPath)) {
29165
+ if (err instanceof SyntaxError && existsSync38(outputPath)) {
28907
29166
  throw new Error(
28908
29167
  "Speech was generated but metadata could not be read. Check the output file manually."
28909
29168
  );
@@ -28957,7 +29216,7 @@ __export(tts_exports, {
28957
29216
  default: () => tts_default,
28958
29217
  examples: () => examples11
28959
29218
  });
28960
- import { existsSync as existsSync40, readFileSync as readFileSync25 } from "fs";
29219
+ import { existsSync as existsSync39, readFileSync as readFileSync26 } from "fs";
28961
29220
  import { resolve as resolve24, extname as extname8 } from "path";
28962
29221
  function listVoices(json) {
28963
29222
  if (json) {
@@ -29047,8 +29306,8 @@ var init_tts = __esm({
29047
29306
  }
29048
29307
  let text;
29049
29308
  const maybeFile = resolve24(args.input);
29050
- if (existsSync40(maybeFile) && extname8(maybeFile).toLowerCase() === ".txt") {
29051
- text = readFileSync25(maybeFile, "utf-8").trim();
29309
+ if (existsSync39(maybeFile) && extname8(maybeFile).toLowerCase() === ".txt") {
29310
+ text = readFileSync26(maybeFile, "utf-8").trim();
29052
29311
  if (!text) {
29053
29312
  console.error(c.error("File is empty."));
29054
29313
  process.exit(1);
@@ -29113,7 +29372,7 @@ __export(docs_exports, {
29113
29372
  default: () => docs_default,
29114
29373
  examples: () => examples12
29115
29374
  });
29116
- import { readFileSync as readFileSync26, existsSync as existsSync41 } from "fs";
29375
+ import { readFileSync as readFileSync27, existsSync as existsSync40 } from "fs";
29117
29376
  import { resolve as resolve25, dirname as dirname17, join as join39 } from "path";
29118
29377
  import { fileURLToPath as fileURLToPath6 } from "url";
29119
29378
  function docsDir() {
@@ -29121,7 +29380,7 @@ function docsDir() {
29121
29380
  const dir = dirname17(thisFile);
29122
29381
  const devPath = resolve25(dir, "..", "docs");
29123
29382
  const builtPath = resolve25(dir, "docs");
29124
- return existsSync41(devPath) ? devPath : builtPath;
29383
+ return existsSync40(devPath) ? devPath : builtPath;
29125
29384
  }
29126
29385
  function formatInlineCode(line) {
29127
29386
  return line.replace(/`([^`]+)`/g, (_match, code) => c.accent(code));
@@ -29219,11 +29478,11 @@ var init_docs = __esm({
29219
29478
  process.exit(1);
29220
29479
  }
29221
29480
  const filePath = join39(docsDir(), entry.file);
29222
- if (!existsSync41(filePath)) {
29481
+ if (!existsSync40(filePath)) {
29223
29482
  console.error(c.error(`Doc file not found: ${filePath}`));
29224
29483
  process.exit(1);
29225
29484
  }
29226
- const content = readFileSync26(filePath, "utf-8");
29485
+ const content = readFileSync27(filePath, "utf-8");
29227
29486
  console.log();
29228
29487
  renderMarkdown(content);
29229
29488
  }
@@ -29637,7 +29896,7 @@ var validate_exports = {};
29637
29896
  __export(validate_exports, {
29638
29897
  default: () => validate_default
29639
29898
  });
29640
- import { existsSync as existsSync42, readFileSync as readFileSync27 } from "fs";
29899
+ import { existsSync as existsSync41, readFileSync as readFileSync28 } from "fs";
29641
29900
  import { resolve as resolve26, join as join40, dirname as dirname18 } from "path";
29642
29901
  import { fileURLToPath as fileURLToPath7 } from "url";
29643
29902
  async function validateInBrowser(projectDir, opts) {
@@ -29653,8 +29912,8 @@ async function validateInBrowser(projectDir, opts) {
29653
29912
  "dist",
29654
29913
  "hyperframe.runtime.iife.js"
29655
29914
  );
29656
- if (existsSync42(runtimePath)) {
29657
- const runtimeSource = readFileSync27(runtimePath, "utf-8");
29915
+ if (existsSync41(runtimePath)) {
29916
+ const runtimeSource = readFileSync28(runtimePath, "utf-8");
29658
29917
  html = html.replace(
29659
29918
  /<script[^>]*data-hyperframes-preview-runtime[^>]*src="[^"]*"[^>]*><\/script>/,
29660
29919
  `<script data-hyperframes-preview-runtime="1">${runtimeSource}</script>`
@@ -29670,9 +29929,9 @@ async function validateInBrowser(projectDir, opts) {
29670
29929
  return;
29671
29930
  }
29672
29931
  const filePath = join40(projectDir, decodeURIComponent(url));
29673
- if (existsSync42(filePath)) {
29932
+ if (existsSync41(filePath)) {
29674
29933
  res.writeHead(200, { "Content-Type": getMimeType2(filePath) });
29675
- res.end(readFileSync27(filePath));
29934
+ res.end(readFileSync28(filePath));
29676
29935
  return;
29677
29936
  }
29678
29937
  res.writeHead(404);