hyperframes 0.6.13 → 0.6.14

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.6.13" : "0.0.0-dev";
57
+ VERSION = true ? "0.6.14" : "0.0.0-dev";
58
58
  }
59
59
  });
60
60
 
@@ -11321,8 +11321,8 @@ function flushSync() {
11321
11321
  eventQueue = [];
11322
11322
  const payload = JSON.stringify({ api_key: POSTHOG_API_KEY, batch });
11323
11323
  try {
11324
- const { spawn: spawn16 } = __require("child_process");
11325
- const child = spawn16(
11324
+ const { spawn: spawn17 } = __require("child_process");
11325
+ const child = spawn17(
11326
11326
  process.execPath,
11327
11327
  [
11328
11328
  "-e",
@@ -12235,6 +12235,37 @@ var init_skills = __esm({
12235
12235
  }
12236
12236
  });
12237
12237
 
12238
+ // src/utils/openBrowser.ts
12239
+ import { spawn as spawn2 } from "child_process";
12240
+ function buildBrowserArgs(url, options) {
12241
+ const args = [];
12242
+ if (options.userDataDir) {
12243
+ args.push(`--user-data-dir=${options.userDataDir}`);
12244
+ }
12245
+ args.push(url);
12246
+ return args;
12247
+ }
12248
+ function openBrowser(url, options = {}) {
12249
+ if (options.browserPath) {
12250
+ const args = buildBrowserArgs(url, options);
12251
+ const child = spawn2(options.browserPath, args, {
12252
+ detached: true,
12253
+ stdio: "ignore"
12254
+ });
12255
+ child.on("error", () => {
12256
+ });
12257
+ child.unref();
12258
+ return;
12259
+ }
12260
+ import("open").then((mod) => mod.default(url)).catch(() => {
12261
+ });
12262
+ }
12263
+ var init_openBrowser = __esm({
12264
+ "src/utils/openBrowser.ts"() {
12265
+ "use strict";
12266
+ }
12267
+ });
12268
+
12238
12269
  // ../core/src/lint/index.ts
12239
12270
  var lint_exports = {};
12240
12271
  __export(lint_exports, {
@@ -13055,7 +13086,7 @@ var init_mime = __esm({
13055
13086
  });
13056
13087
 
13057
13088
  // ../core/src/studio-api/helpers/waveform.ts
13058
- import { spawn as spawn2 } from "child_process";
13089
+ import { spawn as spawn3 } from "child_process";
13059
13090
  import { existsSync as existsSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5 } from "fs";
13060
13091
  import { join as join11 } from "path";
13061
13092
  function buildWaveformCacheKey(assetPath) {
@@ -13079,7 +13110,7 @@ function computePeaks(floats, count) {
13079
13110
  }
13080
13111
  function decodeAudioPeaks(audioPath) {
13081
13112
  return new Promise((resolve43, reject) => {
13082
- const proc = spawn2(
13113
+ const proc = spawn3(
13083
13114
  "ffmpeg",
13084
13115
  [
13085
13116
  "-i",
@@ -27898,7 +27929,10 @@ function registerThumbnailRoutes(api, adapter2) {
27898
27929
  selectorIndex
27899
27930
  });
27900
27931
  if (!buffer) {
27901
- return c3.json({ error: "Thumbnail generation returned null" }, 500);
27932
+ return c3.json(
27933
+ { error: "Thumbnail generation failed \u2014 Chrome browser may not be available" },
27934
+ 500
27935
+ );
27902
27936
  }
27903
27937
  if (!existsSync14(cacheDir)) mkdirSync8(cacheDir, { recursive: true });
27904
27938
  writeFileSync9(cachePath2, buffer);
@@ -29854,10 +29888,10 @@ var init_frameCapture = __esm({
29854
29888
  });
29855
29889
 
29856
29890
  // ../engine/src/utils/gpuEncoder.ts
29857
- import { spawn as spawn3 } from "child_process";
29891
+ import { spawn as spawn4 } from "child_process";
29858
29892
  async function detectGpuEncoder() {
29859
29893
  return new Promise((resolve43) => {
29860
- const ffmpeg = spawn3("ffmpeg", ["-encoders"], {
29894
+ const ffmpeg = spawn4("ffmpeg", ["-encoders"], {
29861
29895
  stdio: ["pipe", "pipe", "pipe"]
29862
29896
  });
29863
29897
  let stdout2 = "";
@@ -29977,7 +30011,7 @@ var init_hdr = __esm({
29977
30011
  });
29978
30012
 
29979
30013
  // ../engine/src/utils/runFfmpeg.ts
29980
- import { spawn as spawn4 } from "child_process";
30014
+ import { spawn as spawn5 } from "child_process";
29981
30015
  function formatFfmpegError(exitCode, stderr, tailLines = DEFAULT_STDERR_TAIL_LINES) {
29982
30016
  const tail = (stderr ?? "").split(/\r?\n/).filter((line) => line.length > 0).slice(-tailLines).join("\n");
29983
30017
  if (exitCode === null) {
@@ -29993,7 +30027,7 @@ async function runFfmpeg(args, opts) {
29993
30027
  const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
29994
30028
  const onStderr = opts?.onStderr;
29995
30029
  return new Promise((resolve43) => {
29996
- const ffmpeg = spawn4("ffmpeg", args);
30030
+ const ffmpeg = spawn5("ffmpeg", args);
29997
30031
  let stderr = "";
29998
30032
  const onAbort = () => {
29999
30033
  ffmpeg.kill("SIGTERM");
@@ -30047,7 +30081,7 @@ var init_runFfmpeg = __esm({
30047
30081
  });
30048
30082
 
30049
30083
  // ../engine/src/services/chunkEncoder.ts
30050
- import { spawn as spawn5 } from "child_process";
30084
+ import { spawn as spawn6 } from "child_process";
30051
30085
  import { copyFileSync, existsSync as existsSync20, mkdirSync as mkdirSync11, readdirSync as readdirSync10, statSync as statSync6, writeFileSync as writeFileSync12 } from "fs";
30052
30086
  import { join as join24, dirname as dirname7 } from "path";
30053
30087
  function getEncoderPreset(quality, format = "mp4", hdr) {
@@ -30260,7 +30294,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
30260
30294
  const inputArgs = ["-framerate", fpsToFfmpegArg(options.fps), "-i", inputPath];
30261
30295
  const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
30262
30296
  return new Promise((resolve43) => {
30263
- const ffmpeg = spawn5("ffmpeg", args);
30297
+ const ffmpeg = spawn6("ffmpeg", args);
30264
30298
  let stderr = "";
30265
30299
  const onAbort = () => {
30266
30300
  ffmpeg.kill("SIGTERM");
@@ -30370,7 +30404,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
30370
30404
  if (options.useGpu) gpuEncoder = await getCachedGpuEncoder();
30371
30405
  const args = buildEncoderArgs(options, inputArgs, chunkPath, gpuEncoder);
30372
30406
  const chunkResult = await new Promise((resolve43) => {
30373
- const ffmpeg = spawn5("ffmpeg", args);
30407
+ const ffmpeg = spawn6("ffmpeg", args);
30374
30408
  let stderr = "";
30375
30409
  ffmpeg.stderr.on("data", (d2) => {
30376
30410
  stderr += d2.toString();
@@ -30411,7 +30445,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
30411
30445
  outputPath
30412
30446
  ];
30413
30447
  const concatResult = await new Promise((resolve43) => {
30414
- const ffmpeg = spawn5("ffmpeg", concatArgs);
30448
+ const ffmpeg = spawn6("ffmpeg", concatArgs);
30415
30449
  let stderr = "";
30416
30450
  ffmpeg.stderr.on("data", (d2) => {
30417
30451
  stderr += d2.toString();
@@ -30517,7 +30551,7 @@ var init_chunkEncoder = __esm({
30517
30551
  });
30518
30552
 
30519
30553
  // ../engine/src/services/streamingEncoder.ts
30520
- import { spawn as spawn6 } from "child_process";
30554
+ import { spawn as spawn7 } from "child_process";
30521
30555
  import { existsSync as existsSync21, mkdirSync as mkdirSync12, statSync as statSync7 } from "fs";
30522
30556
  import { dirname as dirname8 } from "path";
30523
30557
  function createFrameReorderBuffer(startFrame, endFrame) {
@@ -30727,7 +30761,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
30727
30761
  }
30728
30762
  const args = buildStreamingArgs(options, outputPath, gpuEncoder);
30729
30763
  const startTime = Date.now();
30730
- const ffmpeg = spawn6("ffmpeg", args, {
30764
+ const ffmpeg = spawn7("ffmpeg", args, {
30731
30765
  stdio: ["pipe", "pipe", "pipe"]
30732
30766
  });
30733
30767
  let exitStatus = "running";
@@ -30766,21 +30800,31 @@ Process error: ${err.message}`;
30766
30800
  }
30767
30801
  }
30768
30802
  const streamingTimeout = config?.ffmpegStreamingTimeout ?? DEFAULT_CONFIG2.ffmpegStreamingTimeout;
30769
- const timer = setTimeout(() => {
30770
- if (exitStatus === "running") {
30771
- ffmpeg.kill("SIGTERM");
30772
- }
30773
- }, streamingTimeout);
30803
+ let timer = null;
30804
+ const resetTimer = () => {
30805
+ if (timer) clearTimeout(timer);
30806
+ timer = setTimeout(() => {
30807
+ if (exitStatus === "running") {
30808
+ ffmpeg.kill("SIGTERM");
30809
+ }
30810
+ }, streamingTimeout);
30811
+ };
30812
+ resetTimer();
30774
30813
  const encoder = {
30775
30814
  writeFrame: (buffer) => {
30776
30815
  if (exitStatus !== "running" || !ffmpeg.stdin || ffmpeg.stdin.destroyed) {
30777
30816
  return false;
30778
30817
  }
30779
30818
  const copy = Buffer.from(buffer);
30780
- return ffmpeg.stdin.write(copy);
30819
+ const accepted = ffmpeg.stdin.write(copy);
30820
+ if (accepted) resetTimer();
30821
+ return accepted;
30781
30822
  },
30782
30823
  close: async () => {
30783
- clearTimeout(timer);
30824
+ if (timer) {
30825
+ clearTimeout(timer);
30826
+ timer = null;
30827
+ }
30784
30828
  if (signal) signal.removeEventListener("abort", onAbort);
30785
30829
  const stdin = ffmpeg.stdin;
30786
30830
  if (stdin && !stdin.destroyed) {
@@ -30825,12 +30869,12 @@ var init_streamingEncoder = __esm({
30825
30869
  });
30826
30870
 
30827
30871
  // ../engine/src/utils/ffprobe.ts
30828
- import { spawn as spawn7 } from "child_process";
30872
+ import { spawn as spawn8 } from "child_process";
30829
30873
  import { readFileSync as readFileSync18 } from "fs";
30830
30874
  import { extname as extname5 } from "path";
30831
30875
  function runFfprobe(args) {
30832
30876
  return new Promise((resolve43, reject) => {
30833
- const proc = spawn7("ffprobe", args);
30877
+ const proc = spawn8("ffprobe", args);
30834
30878
  let stdout2 = "";
30835
30879
  let stderr = "";
30836
30880
  proc.stdout.on("data", (data) => {
@@ -32534,7 +32578,7 @@ var init_extractionCache = __esm({
32534
32578
  });
32535
32579
 
32536
32580
  // ../engine/src/services/videoFrameExtractor.ts
32537
- import { spawn as spawn8 } from "child_process";
32581
+ import { spawn as spawn9 } from "child_process";
32538
32582
  import { existsSync as existsSync25, mkdirSync as mkdirSync15, readdirSync as readdirSync12, rmSync as rmSync5 } from "fs";
32539
32583
  import { isAbsolute as isAbsolute4, join as join28, posix as posix2, resolve as resolve15, sep as sep4 } from "path";
32540
32584
  function parseVideoElements(html) {
@@ -32632,7 +32676,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
32632
32676
  if (format === "png") args.push("-compression_level", "6");
32633
32677
  args.push("-y", outputPattern);
32634
32678
  return new Promise((resolve43, reject) => {
32635
- const ffmpeg = spawn8("ffmpeg", args);
32679
+ const ffmpeg = spawn9("ffmpeg", args);
32636
32680
  let stderr = "";
32637
32681
  const onAbort = () => {
32638
32682
  ffmpeg.kill("SIGTERM");
@@ -43882,7 +43926,7 @@ var init_renderChunk = __esm({
43882
43926
  });
43883
43927
 
43884
43928
  // ../producer/src/services/render/audioPadTrim.ts
43885
- import { spawn as spawn9 } from "child_process";
43929
+ import { spawn as spawn10 } from "child_process";
43886
43930
  function buildPadTrimAudioArgs(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
43887
43931
  const delta = targetDurationSeconds - sourceDurationSeconds;
43888
43932
  const targetSec = formatSeconds(targetDurationSeconds);
@@ -44048,7 +44092,7 @@ async function defaultRunFfmpeg(args) {
44048
44092
  }
44049
44093
  function runFfprobeJson(args) {
44050
44094
  return new Promise((resolve43, reject) => {
44051
- const proc = spawn9("ffprobe", args);
44095
+ const proc = spawn10("ffprobe", args);
44052
44096
  let stdout2 = "";
44053
44097
  let stderr = "";
44054
44098
  proc.stdout.on("data", (data) => {
@@ -44406,7 +44450,10 @@ async function getThumbnailBrowser() {
44406
44450
  }
44407
44451
  } catch {
44408
44452
  }
44409
- const acquired = await acquireBrowser2(buildChromeArgs2({ width: 1920, height: 1080 }));
44453
+ const acquired = await acquireBrowser2(
44454
+ buildChromeArgs2({ width: 1920, height: 1080, captureMode: "screenshot" }),
44455
+ { forceScreenshot: true }
44456
+ );
44410
44457
  _thumbnailBrowser = acquired.browser;
44411
44458
  _thumbnailBrowser.on("disconnected", () => {
44412
44459
  _thumbnailBrowser = null;
@@ -44423,7 +44470,11 @@ async function getThumbnailBrowser() {
44423
44470
  process.once("SIGTERM", () => void onExit());
44424
44471
  process.once("SIGINT", () => void onExit());
44425
44472
  return _thumbnailBrowser;
44426
- } catch {
44473
+ } catch (err) {
44474
+ console.warn(
44475
+ "[Studio] Failed to launch thumbnail browser:",
44476
+ err instanceof Error ? err.message : err
44477
+ );
44427
44478
  _thumbnailBrowserInitializing = null;
44428
44479
  return null;
44429
44480
  }
@@ -44530,26 +44581,37 @@ function createStudioServer(options) {
44530
44581
  },
44531
44582
  async generateThumbnail(opts) {
44532
44583
  const browser = await getThumbnailBrowser();
44533
- if (!browser) return null;
44584
+ if (!browser) {
44585
+ console.warn("[Studio] Thumbnail: no browser available \u2014 Chrome may not be installed");
44586
+ return null;
44587
+ }
44534
44588
  let page = null;
44535
44589
  try {
44536
44590
  page = await browser.newPage();
44537
44591
  await page.setViewport({ width: opts.width || 1920, height: opts.height || 1080 });
44538
44592
  await page.goto(opts.previewUrl, { waitUntil: "domcontentloaded", timeout: 1e4 });
44539
- await page.waitForFunction(() => !!window.__timelines || !!window.__playerReady, {
44540
- timeout: 5e3
44541
- }).catch(() => {
44593
+ await page.waitForFunction(
44594
+ () => {
44595
+ const w3 = window;
44596
+ return !!(w3.__timelines && Object.keys(w3.__timelines).length > 0);
44597
+ },
44598
+ { timeout: 5e3 }
44599
+ ).catch(() => {
44542
44600
  });
44543
44601
  await page.evaluate((t2) => {
44544
- const win = window;
44545
- if (win.__player?.seek) win.__player.seek(t2);
44546
- else if (win.__timeline?.seek) {
44547
- win.__timeline.pause();
44548
- win.__timeline.seek(t2);
44602
+ const w3 = window;
44603
+ if (typeof w3.__player?.seek === "function") {
44604
+ w3.__player.seek(t2);
44605
+ } else if (w3.__timelines) {
44606
+ for (const tl of Object.values(w3.__timelines)) {
44607
+ tl?.pause?.(t2);
44608
+ }
44609
+ w3.gsap?.ticker?.tick?.();
44549
44610
  }
44550
44611
  }, opts.seekTime);
44551
44612
  const manifestContent = readStudioManualEditManifestContent(opts.project.dir);
44552
44613
  await applyStudioManualEditsToThumbnailPage(page, manifestContent, opts.compPath);
44614
+ await page.evaluate(() => document.fonts?.ready);
44553
44615
  await new Promise((r2) => setTimeout(r2, 200));
44554
44616
  await reapplyStudioManualEditsToThumbnailPage(page);
44555
44617
  let clip;
@@ -44567,7 +44629,11 @@ function createStudioServer(options) {
44567
44629
  }
44568
44630
  );
44569
44631
  return screenshot;
44570
- } catch {
44632
+ } catch (err) {
44633
+ console.warn(
44634
+ "[Studio] Thumbnail generation failed:",
44635
+ err instanceof Error ? err.message : err
44636
+ );
44571
44637
  return null;
44572
44638
  } finally {
44573
44639
  await page?.close().catch(() => {
@@ -44714,7 +44780,7 @@ __export(preview_exports, {
44714
44780
  default: () => preview_default,
44715
44781
  examples: () => examples
44716
44782
  });
44717
- import { spawn as spawn10 } from "child_process";
44783
+ import { spawn as spawn11 } from "child_process";
44718
44784
  import { existsSync as existsSync48, lstatSync as lstatSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, readlinkSync, mkdirSync as mkdirSync30 } from "fs";
44719
44785
  import { resolve as resolve25, dirname as dirname18, basename as basename6, join as join58 } from "path";
44720
44786
  import { fileURLToPath as fileURLToPath6 } from "url";
@@ -44749,7 +44815,7 @@ async function runDevMode(dir, options) {
44749
44815
  const s2 = ft();
44750
44816
  s2.start("Starting studio...");
44751
44817
  const studioPkgDir = join58(repoRoot, "packages", "studio");
44752
- const child = spawn10("bun", ["run", "dev"], {
44818
+ const child = spawn11("bun", ["run", "dev"], {
44753
44819
  cwd: studioPkgDir,
44754
44820
  stdio: ["ignore", "pipe", "pipe"]
44755
44821
  });
@@ -44768,7 +44834,9 @@ async function runDevMode(dir, options) {
44768
44834
  console.log();
44769
44835
  if (!options?.noOpen) {
44770
44836
  const urlToOpen = `${frontendUrl}#project/${pName}`;
44771
- import("open").then((mod) => mod.default(urlToOpen)).catch(() => {
44837
+ openBrowser(urlToOpen, {
44838
+ browserPath: options?.browserPath,
44839
+ userDataDir: options?.userDataDir
44772
44840
  });
44773
44841
  }
44774
44842
  child.stdout?.removeListener("data", handleOutput);
@@ -44824,7 +44892,7 @@ async function runLocalStudioMode(dir, options) {
44824
44892
  ge(c2.bold("hyperframes preview") + c2.dim(" (local studio)"));
44825
44893
  const s2 = ft();
44826
44894
  s2.start("Starting studio...");
44827
- const child = spawn10("npx", ["vite"], {
44895
+ const child = spawn11("npx", ["vite"], {
44828
44896
  cwd: studioPkgPath,
44829
44897
  stdio: ["ignore", "pipe", "pipe"]
44830
44898
  });
@@ -44843,7 +44911,9 @@ async function runLocalStudioMode(dir, options) {
44843
44911
  console.log(` ${c2.dim("Press Ctrl+C to stop")}`);
44844
44912
  console.log();
44845
44913
  if (!options?.noOpen) {
44846
- import("open").then((mod) => mod.default(`${url}#project/${pName}`)).catch(() => {
44914
+ openBrowser(`${url}#project/${pName}`, {
44915
+ browserPath: options?.browserPath,
44916
+ userDataDir: options?.userDataDir
44847
44917
  });
44848
44918
  }
44849
44919
  }
@@ -44910,7 +44980,9 @@ async function runEmbeddedMode(dir, startPort, options) {
44910
44980
  );
44911
44981
  console.log();
44912
44982
  if (!options?.noOpen) {
44913
- import("open").then((mod) => mod.default(`${url2}#project/${pName}`)).catch(() => {
44983
+ openBrowser(`${url2}#project/${pName}`, {
44984
+ browserPath: options?.browserPath,
44985
+ userDataDir: options?.userDataDir
44914
44986
  });
44915
44987
  }
44916
44988
  return;
@@ -44931,7 +45003,9 @@ async function runEmbeddedMode(dir, startPort, options) {
44931
45003
  console.log(` ${c2.dim("Press Ctrl+C to stop")}`);
44932
45004
  console.log();
44933
45005
  if (!options?.noOpen) {
44934
- import("open").then((mod) => mod.default(`${url}#project/${pName}`)).catch(() => {
45006
+ openBrowser(`${url}#project/${pName}`, {
45007
+ browserPath: options?.browserPath,
45008
+ userDataDir: options?.userDataDir
44935
45009
  });
44936
45010
  }
44937
45011
  let rl;
@@ -44967,6 +45041,7 @@ var init_preview2 = __esm({
44967
45041
  init_dist5();
44968
45042
  init_colors();
44969
45043
  init_env();
45044
+ init_openBrowser();
44970
45045
  init_lintProject();
44971
45046
  init_lintFormat();
44972
45047
  init_portUtils();
@@ -44976,6 +45051,7 @@ var init_preview2 = __esm({
44976
45051
  ["Use a custom port", "hyperframes preview --port 8080"],
44977
45052
  ["Force a new server even if one is already running", "hyperframes preview --force-new"],
44978
45053
  ["Start without opening the browser", "hyperframes preview --no-open"],
45054
+ ["Open with a specific browser", "hyperframes preview --browser-path /usr/bin/chromium"],
44979
45055
  ["List all active preview servers", "hyperframes preview --list"],
44980
45056
  ["Kill all active preview servers", "hyperframes preview --kill-all"]
44981
45057
  ];
@@ -45003,6 +45079,14 @@ var init_preview2 = __esm({
45003
45079
  type: "boolean",
45004
45080
  default: true,
45005
45081
  description: "Open browser automatically"
45082
+ },
45083
+ "browser-path": {
45084
+ type: "string",
45085
+ description: "Path to the browser executable to open"
45086
+ },
45087
+ "user-data-dir": {
45088
+ type: "string",
45089
+ description: "Chromium-compatible user data directory (requires --browser-path)"
45006
45090
  }
45007
45091
  },
45008
45092
  async run({ args }) {
@@ -45053,15 +45137,28 @@ var init_preview2 = __esm({
45053
45137
  console.log();
45054
45138
  }
45055
45139
  }
45140
+ if (args["user-data-dir"] && !args["browser-path"]) {
45141
+ R2.error("--user-data-dir requires --browser-path");
45142
+ process.exitCode = 1;
45143
+ return;
45144
+ }
45056
45145
  const noOpen = !args.open;
45146
+ const browserPath = args["browser-path"];
45147
+ const userDataDir = args["user-data-dir"];
45057
45148
  if (isDevMode()) {
45058
- return runDevMode(dir, { projectName, noOpen });
45149
+ return runDevMode(dir, { projectName, noOpen, browserPath, userDataDir });
45059
45150
  }
45060
45151
  if (hasLocalStudio(dir)) {
45061
- return runLocalStudioMode(dir, { projectName, noOpen });
45152
+ return runLocalStudioMode(dir, { projectName, noOpen, browserPath, userDataDir });
45062
45153
  }
45063
45154
  const forceNew = !!args["force-new"];
45064
- return runEmbeddedMode(dir, startPort, { projectName, forceNew, noOpen });
45155
+ return runEmbeddedMode(dir, startPort, {
45156
+ projectName,
45157
+ forceNew,
45158
+ noOpen,
45159
+ browserPath,
45160
+ userDataDir
45161
+ });
45065
45162
  }
45066
45163
  });
45067
45164
  }
@@ -45086,7 +45183,7 @@ import {
45086
45183
  } from "fs";
45087
45184
  import { resolve as resolve26, basename as basename7, join as join59, dirname as dirname19 } from "path";
45088
45185
  import { fileURLToPath as fileURLToPath7 } from "url";
45089
- import { execFileSync as execFileSync5, spawn as spawn11 } from "child_process";
45186
+ import { execFileSync as execFileSync5, spawn as spawn12 } from "child_process";
45090
45187
  function probeVideo(filePath) {
45091
45188
  try {
45092
45189
  const raw = execFileSync5(
@@ -45128,7 +45225,7 @@ function isWebCompatible(codec) {
45128
45225
  }
45129
45226
  function transcodeToMp4(inputPath, outputPath) {
45130
45227
  return new Promise((resolvePromise) => {
45131
- const child = spawn11(
45228
+ const child = spawn12(
45132
45229
  "ffmpeg",
45133
45230
  [
45134
45231
  "-i",
@@ -46439,11 +46536,13 @@ var init_play = __esm({
46439
46536
  init_dist5();
46440
46537
  init_colors();
46441
46538
  init_project();
46539
+ init_openBrowser();
46442
46540
  examples5 = [
46443
46541
  ["Play the current project", "hyperframes play"],
46444
46542
  ["Play a specific project directory", "hyperframes play ./my-video"],
46445
46543
  ["Use a custom port", "hyperframes play --port 8080"],
46446
- ["Start without opening the browser", "hyperframes play --no-open"]
46544
+ ["Start without opening the browser", "hyperframes play --no-open"],
46545
+ ["Open with a specific browser", "hyperframes play --browser-path /usr/bin/chromium"]
46447
46546
  ];
46448
46547
  play_default = defineCommand({
46449
46548
  meta: { name: "play", description: "Play a composition in a lightweight browser player" },
@@ -46454,11 +46553,24 @@ var init_play = __esm({
46454
46553
  type: "boolean",
46455
46554
  default: true,
46456
46555
  description: "Open browser automatically"
46556
+ },
46557
+ "browser-path": {
46558
+ type: "string",
46559
+ description: "Path to the browser executable to open"
46560
+ },
46561
+ "user-data-dir": {
46562
+ type: "string",
46563
+ description: "Chromium-compatible user data directory (requires --browser-path)"
46457
46564
  }
46458
46565
  },
46459
46566
  async run({ args }) {
46460
46567
  const project = resolveProject(args.dir);
46461
46568
  const startPort = parseInt(args.port ?? "3003", 10);
46569
+ if (args["user-data-dir"] && !args["browser-path"]) {
46570
+ R2.error("--user-data-dir requires --browser-path");
46571
+ process.exitCode = 1;
46572
+ return;
46573
+ }
46462
46574
  const runtimePath = resolveRuntimePath2();
46463
46575
  if (!runtimePath) {
46464
46576
  R2.error("HyperFrames runtime not found. Run `bun run build` first.");
@@ -46559,7 +46671,9 @@ var init_play = __esm({
46559
46671
  console.log(` ${c2.dim("Press Ctrl+C to stop")}`);
46560
46672
  console.log();
46561
46673
  if (args.open) {
46562
- import("open").then((mod) => mod.default(url)).catch(() => {
46674
+ void openBrowser(url, {
46675
+ browserPath: args["browser-path"],
46676
+ userDataDir: args["user-data-dir"]
46563
46677
  });
46564
46678
  }
46565
46679
  return new Promise(() => {
@@ -47046,7 +47160,7 @@ __export(render_exports, {
47046
47160
  import { mkdirSync as mkdirSync32, readdirSync as readdirSync23, readFileSync as readFileSync36, statSync as statSync20, writeFileSync as writeFileSync26, rmSync as rmSync14 } from "fs";
47047
47161
  import { cpus as cpus4, freemem as freemem3, tmpdir as tmpdir4 } from "os";
47048
47162
  import { resolve as resolve32, dirname as dirname21, join as join62, basename as basename11 } from "path";
47049
- import { execFileSync as execFileSync6, spawn as spawn12 } from "child_process";
47163
+ import { execFileSync as execFileSync6, spawn as spawn13 } from "child_process";
47050
47164
  function formatFpsParseError(input, reason) {
47051
47165
  switch (reason) {
47052
47166
  case "empty":
@@ -47266,7 +47380,7 @@ async function renderDocker(projectDir, outputPath, options) {
47266
47380
  }
47267
47381
  try {
47268
47382
  await new Promise((resolvePromise, reject) => {
47269
- const child = spawn12("docker", dockerArgs, {
47383
+ const child = spawn13("docker", dockerArgs, {
47270
47384
  // When quiet, still show stderr so container errors surface
47271
47385
  stdio: options.quiet ? ["pipe", "pipe", "inherit"] : "inherit"
47272
47386
  });
@@ -49471,7 +49585,7 @@ __export(pipeline_exports, {
49471
49585
  resolveRenderTargets: () => resolveRenderTargets,
49472
49586
  waitForExit: () => waitForExit
49473
49587
  });
49474
- import { spawn as spawn13 } from "child_process";
49588
+ import { spawn as spawn14 } from "child_process";
49475
49589
  import { extname as extname10 } from "path";
49476
49590
  function inferOutputFormat(outputPath) {
49477
49591
  const ext = extname10(outputPath).toLowerCase();
@@ -49648,7 +49762,7 @@ async function render2(options) {
49648
49762
  }
49649
49763
  }
49650
49764
  function spawnFfmpeg(args, label2, stdio) {
49651
- const proc = spawn13("ffmpeg", args, { stdio });
49765
+ const proc = spawn14("ffmpeg", args, { stdio });
49652
49766
  let stderrBuf = "";
49653
49767
  proc.stderr?.on("data", (d2) => {
49654
49768
  stderrBuf += d2.toString();
@@ -51486,7 +51600,7 @@ __export(snapshot_exports, {
51486
51600
  default: () => snapshot_default,
51487
51601
  examples: () => examples22
51488
51602
  });
51489
- import { spawn as spawn14 } from "child_process";
51603
+ import { spawn as spawn15 } from "child_process";
51490
51604
  import { existsSync as existsSync66, mkdtempSync as mkdtempSync3, readFileSync as readFileSync44, mkdirSync as mkdirSync36, rmSync as rmSync15 } from "fs";
51491
51605
  import { tmpdir as tmpdir5 } from "os";
51492
51606
  import { resolve as resolve40, join as join72, relative as relative9, isAbsolute as isAbsolute9 } from "path";
@@ -51512,7 +51626,7 @@ async function extractVideoFrameToBuffer(videoPath, timeSeconds, useVp9AlphaDeco
51512
51626
  "-y",
51513
51627
  outPath
51514
51628
  );
51515
- const ff = spawn14("ffmpeg", args);
51629
+ const ff = spawn15("ffmpeg", args);
51516
51630
  let stderr = "";
51517
51631
  let timedOut = false;
51518
51632
  const timer = setTimeout(() => {
@@ -51629,19 +51743,14 @@ async function captureSnapshots(projectDir, opts) {
51629
51743
  for (let i2 = 0; i2 < positions.length; i2++) {
51630
51744
  const time = positions[i2];
51631
51745
  await page.evaluate((t2) => {
51632
- const win = window;
51633
- if (win.__player?.seek) {
51634
- win.__player.seek(t2);
51635
- } else {
51636
- const tls = win.__timelines;
51637
- if (tls) {
51638
- for (const key2 in tls) {
51639
- if (tls[key2]?.seek) {
51640
- tls[key2].pause();
51641
- tls[key2].seek(t2);
51642
- }
51643
- }
51746
+ const w3 = window;
51747
+ if (typeof w3.__player?.seek === "function") {
51748
+ w3.__player.seek(t2);
51749
+ } else if (w3.__timelines) {
51750
+ for (const tl of Object.values(w3.__timelines)) {
51751
+ tl?.pause?.(t2);
51644
51752
  }
51753
+ w3.gsap?.ticker?.tick?.();
51645
51754
  }
51646
51755
  }, time);
51647
51756
  await page.evaluate(
@@ -94925,7 +95034,7 @@ __export(autoUpdate_exports, {
94925
95034
  reportCompletedUpdate: () => reportCompletedUpdate,
94926
95035
  scheduleBackgroundInstall: () => scheduleBackgroundInstall
94927
95036
  });
94928
- import { spawn as spawn15 } from "child_process";
95037
+ import { spawn as spawn16 } from "child_process";
94929
95038
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync41, openSync as openSync2 } from "fs";
94930
95039
  import { homedir as homedir12 } from "os";
94931
95040
  import { join as join80 } from "path";
@@ -94976,7 +95085,7 @@ function launchDetachedInstall(installCommand, version) {
94976
95085
  });
94977
95086
  `;
94978
95087
  const out = openSync2(LOG_FILE, "a", 384);
94979
- const child = spawn15(process.execPath, ["-e", nodeScript], {
95088
+ const child = spawn16(process.execPath, ["-e", nodeScript], {
94980
95089
  detached: true,
94981
95090
  stdio: ["ignore", out, out],
94982
95091
  windowsHide: true,