storyforge 0.7.1 → 0.7.2

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.
@@ -4,7 +4,7 @@ import {
4
4
  muxAudio,
5
5
  probeDuration,
6
6
  runCmd
7
- } from "./chunk-M6JAFPDI.js";
7
+ } from "./chunk-5SWMYFFS.js";
8
8
  import {
9
9
  log
10
10
  } from "./chunk-GJQ45C5W.js";
@@ -91,18 +91,18 @@ var require_p_limit = __commonJS({
91
91
  queue.dequeue()();
92
92
  }
93
93
  };
94
- const run = async (fn, resolve7, ...args) => {
94
+ const run = async (fn, resolve3, ...args) => {
95
95
  activeCount++;
96
96
  const result = (async () => fn(...args))();
97
- resolve7(result);
97
+ resolve3(result);
98
98
  try {
99
99
  await result;
100
100
  } catch {
101
101
  }
102
102
  next();
103
103
  };
104
- const enqueue = (fn, resolve7, ...args) => {
105
- queue.enqueue(run.bind(null, fn, resolve7, ...args));
104
+ const enqueue = (fn, resolve3, ...args) => {
105
+ queue.enqueue(run.bind(null, fn, resolve3, ...args));
106
106
  (async () => {
107
107
  await Promise.resolve();
108
108
  if (activeCount < concurrency && queue.size > 0) {
@@ -110,8 +110,8 @@ var require_p_limit = __commonJS({
110
110
  }
111
111
  })();
112
112
  };
113
- const generator = (fn, ...args) => new Promise((resolve7) => {
114
- enqueue(fn, resolve7, ...args);
113
+ const generator = (fn, ...args) => new Promise((resolve3) => {
114
+ enqueue(fn, resolve3, ...args);
115
115
  });
116
116
  Object.defineProperties(generator, {
117
117
  activeCount: {
@@ -135,11 +135,11 @@ var require_p_limit = __commonJS({
135
135
  // src/bridge-poller.ts
136
136
  import { spawn as spawn2, spawnSync } from "child_process";
137
137
  import * as crypto from "crypto";
138
- import * as fs9 from "fs";
138
+ import * as fs10 from "fs";
139
139
 
140
140
  // ../pipeline/src/clip-render/render-chunk.ts
141
- import * as fs8 from "fs";
142
- import * as path9 from "path";
141
+ import * as fs9 from "fs";
142
+ import * as path10 from "path";
143
143
 
144
144
  // ../../node_modules/zod/v3/external.js
145
145
  var external_exports = {};
@@ -619,8 +619,8 @@ function getErrorMap() {
619
619
 
620
620
  // ../../node_modules/zod/v3/helpers/parseUtil.js
621
621
  var makeIssue = (params) => {
622
- const { data, path: path10, errorMaps, issueData } = params;
623
- const fullPath = [...path10, ...issueData.path || []];
622
+ const { data, path: path11, errorMaps, issueData } = params;
623
+ const fullPath = [...path11, ...issueData.path || []];
624
624
  const fullIssue = {
625
625
  ...issueData,
626
626
  path: fullPath
@@ -736,11 +736,11 @@ var errorUtil;
736
736
 
737
737
  // ../../node_modules/zod/v3/types.js
738
738
  var ParseInputLazyPath = class {
739
- constructor(parent, value, path10, key) {
739
+ constructor(parent, value, path11, key) {
740
740
  this._cachedPath = [];
741
741
  this.parent = parent;
742
742
  this.data = value;
743
- this._path = path10;
743
+ this._path = path11;
744
744
  this._key = key;
745
745
  }
746
746
  get path() {
@@ -5353,16 +5353,65 @@ function nextFallback(engine) {
5353
5353
  var DEFAULT_RENDER_CONCURRENCY = 4;
5354
5354
 
5355
5355
  // ../pipeline/src/clip-render/engines/gemini-manim-remotion.ts
5356
- import * as fs2 from "fs";
5357
- import * as path2 from "path";
5356
+ import * as fs3 from "fs";
5357
+ import * as path3 from "path";
5358
5358
 
5359
- // ../pipeline/src/clip-render/engines/manim.ts
5359
+ // ../pipeline/src/clip-render/remotion-root.ts
5360
5360
  import * as fs from "fs";
5361
5361
  import * as path from "path";
5362
+ var MAX_PARENTS = 8;
5363
+ function findRemotionRoot(startDir = process.cwd()) {
5364
+ const envRoot = process.env.FORGE_REMOTION_ROOT;
5365
+ if (envRoot && envRoot.trim().length > 0) {
5366
+ const abs = path.resolve(envRoot);
5367
+ const entry = path.join(abs, "src", "Root.tsx");
5368
+ if (fs.existsSync(entry)) {
5369
+ return { cwd: abs, entry, source: "env" };
5370
+ }
5371
+ }
5372
+ let cur = path.resolve(startDir);
5373
+ for (let i = 0; i <= MAX_PARENTS; i++) {
5374
+ const candidate = path.join(cur, "packages", "remotion", "src", "Root.tsx");
5375
+ if (fs.existsSync(candidate)) {
5376
+ const cwd = path.dirname(path.dirname(candidate));
5377
+ return { cwd, entry: candidate, source: "monorepo-walk" };
5378
+ }
5379
+ const parent = path.dirname(cur);
5380
+ if (parent === cur) break;
5381
+ cur = parent;
5382
+ }
5383
+ cur = path.resolve(startDir);
5384
+ for (let i = 0; i <= MAX_PARENTS; i++) {
5385
+ const candidate = path.join(cur, "node_modules", "@forge", "remotion", "dist", "Root.js");
5386
+ if (fs.existsSync(candidate)) {
5387
+ const cwd = path.dirname(path.dirname(candidate));
5388
+ return { cwd, entry: candidate, source: "node_modules" };
5389
+ }
5390
+ const parent = path.dirname(cur);
5391
+ if (parent === cur) break;
5392
+ cur = parent;
5393
+ }
5394
+ return null;
5395
+ }
5396
+ function requireRemotionRoot(startDir = process.cwd()) {
5397
+ const root = findRemotionRoot(startDir);
5398
+ if (root) return root;
5399
+ throw new Error(
5400
+ `clip-render: cannot locate a Remotion project root.
5401
+ Searched from ${startDir} up ${MAX_PARENTS} levels for both:
5402
+ - packages/remotion/src/Root.tsx (monorepo dev)
5403
+ - node_modules/@forge/remotion/dist/Root.js (published)
5404
+ Set FORGE_REMOTION_ROOT to override, or run storyforge from a path inside the forge monorepo.`
5405
+ );
5406
+ }
5407
+
5408
+ // ../pipeline/src/clip-render/engines/manim.ts
5409
+ import * as fs2 from "fs";
5410
+ import * as path2 from "path";
5362
5411
  function findMp4(dir) {
5363
- if (!fs.existsSync(dir)) return null;
5364
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
5365
- const full = path.join(dir, entry.name);
5412
+ if (!fs2.existsSync(dir)) return null;
5413
+ for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
5414
+ const full = path2.join(dir, entry.name);
5366
5415
  if (entry.isDirectory()) {
5367
5416
  const f = findMp4(full);
5368
5417
  if (f) return f;
@@ -5377,11 +5426,11 @@ var renderManim = async (shot, ctx) => {
5377
5426
  throw new Error(`manim engine requires shot.manimCode for shot ${shot.id}`);
5378
5427
  }
5379
5428
  if (ctx.signal?.aborted) throw new Error("aborted");
5380
- const manimWorkDir = path.join(ctx.tmpDir, "manim", shot.id);
5381
- fs.mkdirSync(manimWorkDir, { recursive: true });
5382
- const scriptPath = path.join(manimWorkDir, `${shot.id}.py`);
5383
- const mediaDir = path.join(manimWorkDir, "media");
5384
- fs.writeFileSync(scriptPath, shot.manimCode);
5429
+ const manimWorkDir = path2.join(ctx.tmpDir, "manim", shot.id);
5430
+ fs2.mkdirSync(manimWorkDir, { recursive: true });
5431
+ const scriptPath = path2.join(manimWorkDir, `${shot.id}.py`);
5432
+ const mediaDir = path2.join(manimWorkDir, "media");
5433
+ fs2.writeFileSync(scriptPath, shot.manimCode);
5385
5434
  ctx.progress.emit({ phase: "rendering", shotId: shot.id });
5386
5435
  const r = await runCmd(
5387
5436
  "python3",
@@ -5403,9 +5452,9 @@ var renderManim = async (shot, ctx) => {
5403
5452
  const tail = r.stderr.split("\n").slice(-5).join(" ").slice(0, 300);
5404
5453
  throw new Error(`manim render failed (code ${r.code}): ${tail}`);
5405
5454
  }
5406
- const mp4 = findMp4(path.join(mediaDir, "videos"));
5455
+ const mp4 = findMp4(path2.join(mediaDir, "videos"));
5407
5456
  if (!mp4) throw new Error("manim produced no MP4 output");
5408
- fs.copyFileSync(mp4, ctx.sceneOutPath);
5457
+ fs2.copyFileSync(mp4, ctx.sceneOutPath);
5409
5458
  };
5410
5459
 
5411
5460
  // ../pipeline/src/clip-render/engines/gemini-manim-remotion.ts
@@ -5418,13 +5467,13 @@ async function resolveImageToLocal(imagePath, cacheDir, shotId, signal) {
5418
5467
  );
5419
5468
  }
5420
5469
  const buf = Buffer.from(await resp.arrayBuffer());
5421
- fs2.mkdirSync(cacheDir, { recursive: true });
5422
- const ext = path2.extname(new URL(imagePath).pathname) || ".png";
5423
- const local = path2.join(cacheDir, `${shotId}_bg${ext}`);
5424
- fs2.writeFileSync(local, buf);
5470
+ fs3.mkdirSync(cacheDir, { recursive: true });
5471
+ const ext = path3.extname(new URL(imagePath).pathname) || ".png";
5472
+ const local = path3.join(cacheDir, `${shotId}_bg${ext}`);
5473
+ fs3.writeFileSync(local, buf);
5425
5474
  return local;
5426
5475
  }
5427
- if (!fs2.existsSync(imagePath)) {
5476
+ if (!fs3.existsSync(imagePath)) {
5428
5477
  throw new Error(`gemini+manim+remotion: imagePath does not exist: ${imagePath}`);
5429
5478
  }
5430
5479
  return imagePath;
@@ -5440,12 +5489,12 @@ var renderGeminiManimRemotion = async (shot, ctx) => {
5440
5489
  ctx.progress.emit({ phase: "preparing", shotId: shot.id });
5441
5490
  const localImage = await resolveImageToLocal(
5442
5491
  shot.imagePath,
5443
- path2.join(ctx.tmpDir, "remotion", "images"),
5492
+ path3.join(ctx.tmpDir, "remotion", "images"),
5444
5493
  shot.id,
5445
5494
  ctx.signal
5446
5495
  );
5447
- const manimSceneOut = path2.join(ctx.tmpDir, "manim", `${shot.id}_manim.mp4`);
5448
- fs2.mkdirSync(path2.dirname(manimSceneOut), { recursive: true });
5496
+ const manimSceneOut = path3.join(ctx.tmpDir, "manim", `${shot.id}_manim.mp4`);
5497
+ fs3.mkdirSync(path3.dirname(manimSceneOut), { recursive: true });
5449
5498
  ctx.progress.emit({ phase: "rendering", shotId: shot.id });
5450
5499
  await renderManim(shot, { ...ctx, sceneOutPath: manimSceneOut });
5451
5500
  const manimDurationSec = await probeDuration(manimSceneOut) ?? 7;
@@ -5453,9 +5502,9 @@ var renderGeminiManimRemotion = async (shot, ctx) => {
5453
5502
  const totalFrames = Math.max(1, Math.round(shot.durationSec * ctx.fps));
5454
5503
  if (ctx.signal?.aborted) throw new Error("aborted");
5455
5504
  ctx.progress.emit({ phase: "compositing", shotId: shot.id });
5456
- const propsPath = path2.join(ctx.tmpDir, "remotion", `${shot.id}_gmr_props.json`);
5457
- fs2.mkdirSync(path2.dirname(propsPath), { recursive: true });
5458
- fs2.writeFileSync(
5505
+ const propsPath = path3.join(ctx.tmpDir, "remotion", `${shot.id}_gmr_props.json`);
5506
+ fs3.mkdirSync(path3.dirname(propsPath), { recursive: true });
5507
+ fs3.writeFileSync(
5459
5508
  propsPath,
5460
5509
  JSON.stringify(
5461
5510
  {
@@ -5470,14 +5519,14 @@ var renderGeminiManimRemotion = async (shot, ctx) => {
5470
5519
  2
5471
5520
  )
5472
5521
  );
5473
- const entryArg = path2.resolve(process.cwd(), "packages/remotion/src/Root.tsx");
5522
+ const remotion = requireRemotionRoot();
5474
5523
  const compId = ctx.aspect === "9:16" ? "ForgeVideoVertical" : "ForgeVideo";
5475
5524
  const r = await runCmd(
5476
5525
  "npx",
5477
5526
  [
5478
5527
  "remotion",
5479
5528
  "render",
5480
- entryArg,
5529
+ remotion.entry,
5481
5530
  compId,
5482
5531
  "--props",
5483
5532
  propsPath,
@@ -5486,11 +5535,11 @@ var renderGeminiManimRemotion = async (shot, ctx) => {
5486
5535
  "--concurrency",
5487
5536
  "4"
5488
5537
  ],
5489
- { timeoutMs: 3e5, signal: ctx.signal }
5538
+ { timeoutMs: 3e5, signal: ctx.signal, cwd: remotion.cwd }
5490
5539
  );
5491
5540
  if (r.code !== 0) {
5492
5541
  const tail = r.stderr.split("\n").slice(-5).join(" ").slice(0, 300);
5493
- fs2.copyFileSync(manimSceneOut, ctx.sceneOutPath);
5542
+ fs3.copyFileSync(manimSceneOut, ctx.sceneOutPath);
5494
5543
  ctx.progress.emit({
5495
5544
  phase: "compositing",
5496
5545
  shotId: shot.id,
@@ -5500,8 +5549,8 @@ var renderGeminiManimRemotion = async (shot, ctx) => {
5500
5549
  };
5501
5550
 
5502
5551
  // ../pipeline/src/clip-render/engines/gemini-remotion.ts
5503
- import * as fs3 from "fs";
5504
- import * as path3 from "path";
5552
+ import * as fs4 from "fs";
5553
+ import * as path4 from "path";
5505
5554
  var ASPECT_DIMS = {
5506
5555
  "16:9": { width: 1920, height: 1080 },
5507
5556
  "9:16": { width: 1080, height: 1920 }
@@ -5513,13 +5562,13 @@ async function resolveImageToLocal2(imagePath, cacheDir, shotId, signal) {
5513
5562
  throw new Error(`gemini+remotion: image fetch failed ${resp.status} for ${imagePath}`);
5514
5563
  }
5515
5564
  const buf = Buffer.from(await resp.arrayBuffer());
5516
- fs3.mkdirSync(cacheDir, { recursive: true });
5517
- const ext = path3.extname(new URL(imagePath).pathname) || ".png";
5518
- const local = path3.join(cacheDir, `${shotId}_bg${ext}`);
5519
- fs3.writeFileSync(local, buf);
5565
+ fs4.mkdirSync(cacheDir, { recursive: true });
5566
+ const ext = path4.extname(new URL(imagePath).pathname) || ".png";
5567
+ const local = path4.join(cacheDir, `${shotId}_bg${ext}`);
5568
+ fs4.writeFileSync(local, buf);
5520
5569
  return local;
5521
5570
  }
5522
- if (!fs3.existsSync(imagePath)) {
5571
+ if (!fs4.existsSync(imagePath)) {
5523
5572
  throw new Error(`gemini+remotion: imagePath does not exist: ${imagePath}`);
5524
5573
  }
5525
5574
  return imagePath;
@@ -5532,15 +5581,15 @@ var renderGeminiRemotion = async (shot, ctx) => {
5532
5581
  ctx.progress.emit({ phase: "preparing", shotId: shot.id });
5533
5582
  const localImage = await resolveImageToLocal2(
5534
5583
  shot.imagePath,
5535
- path3.join(ctx.tmpDir, "remotion", "images"),
5584
+ path4.join(ctx.tmpDir, "remotion", "images"),
5536
5585
  shot.id,
5537
5586
  ctx.signal
5538
5587
  );
5539
5588
  const durationFrames = Math.max(1, Math.round(shot.durationSec * ctx.fps));
5540
5589
  const dims = ASPECT_DIMS[ctx.aspect];
5541
- const propsPath = path3.join(ctx.tmpDir, "remotion", `${shot.id}_gr_props.json`);
5542
- fs3.mkdirSync(path3.dirname(propsPath), { recursive: true });
5543
- fs3.writeFileSync(
5590
+ const propsPath = path4.join(ctx.tmpDir, "remotion", `${shot.id}_gr_props.json`);
5591
+ fs4.mkdirSync(path4.dirname(propsPath), { recursive: true });
5592
+ fs4.writeFileSync(
5544
5593
  propsPath,
5545
5594
  JSON.stringify(
5546
5595
  {
@@ -5556,7 +5605,7 @@ var renderGeminiRemotion = async (shot, ctx) => {
5556
5605
  2
5557
5606
  )
5558
5607
  );
5559
- const entryArg = path3.resolve(process.cwd(), "packages/remotion/src/Root.tsx");
5608
+ const remotion = requireRemotionRoot();
5560
5609
  const compId = ctx.aspect === "9:16" ? "ForgeVideoVertical" : "ForgeVideo";
5561
5610
  ctx.progress.emit({
5562
5611
  phase: "compositing",
@@ -5569,7 +5618,7 @@ var renderGeminiRemotion = async (shot, ctx) => {
5569
5618
  [
5570
5619
  "remotion",
5571
5620
  "render",
5572
- entryArg,
5621
+ remotion.entry,
5573
5622
  compId,
5574
5623
  "--props",
5575
5624
  propsPath,
@@ -5578,7 +5627,7 @@ var renderGeminiRemotion = async (shot, ctx) => {
5578
5627
  "--concurrency",
5579
5628
  "4"
5580
5629
  ],
5581
- { timeoutMs: 3e5, signal: ctx.signal }
5630
+ { timeoutMs: 3e5, signal: ctx.signal, cwd: remotion.cwd }
5582
5631
  );
5583
5632
  if (r.code !== 0) {
5584
5633
  const tail = r.stderr.split("\n").slice(-5).join(" ").slice(0, 300);
@@ -5595,7 +5644,7 @@ var renderGeminiRemotion = async (shot, ctx) => {
5595
5644
  // ../pipeline/src/clip-render/engines/hyperframes.ts
5596
5645
  import { mkdir, writeFile } from "fs/promises";
5597
5646
  import { spawn } from "child_process";
5598
- import path4 from "path";
5647
+ import path5 from "path";
5599
5648
 
5600
5649
  // ../pipeline/src/clip-render/engines/hyperframes-template.ts
5601
5650
  function dimensionsForAspect(aspect) {
@@ -5671,10 +5720,10 @@ var HyperFramesNotInstalledError = class extends Error {
5671
5720
  }
5672
5721
  };
5673
5722
  async function defaultHyperframesAvailable() {
5674
- return new Promise((resolve7) => {
5723
+ return new Promise((resolve3) => {
5675
5724
  const proc = spawn("which", ["hyperframes"], { stdio: ["ignore", "pipe", "pipe"] });
5676
- proc.on("close", (code) => resolve7(code === 0));
5677
- proc.on("error", () => resolve7(false));
5725
+ proc.on("close", (code) => resolve3(code === 0));
5726
+ proc.on("error", () => resolve3(false));
5678
5727
  });
5679
5728
  }
5680
5729
  async function defaultRunHyperframes(htmlPath, outputMp4, fps, onStdoutLine) {
@@ -5682,7 +5731,7 @@ async function defaultRunHyperframes(htmlPath, outputMp4, fps, onStdoutLine) {
5682
5731
  await runProcess("npx", args, onStdoutLine);
5683
5732
  }
5684
5733
  function runProcess(cmd, args, onLine) {
5685
- return new Promise((resolve7, reject) => {
5734
+ return new Promise((resolve3, reject) => {
5686
5735
  let proc;
5687
5736
  try {
5688
5737
  proc = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
@@ -5707,7 +5756,7 @@ function runProcess(cmd, args, onLine) {
5707
5756
  });
5708
5757
  proc.on("error", reject);
5709
5758
  proc.on("close", (code) => {
5710
- if (code === 0) resolve7();
5759
+ if (code === 0) resolve3();
5711
5760
  else reject(new Error(`${cmd} ${args.join(" ")} exited ${code}
5712
5761
  ${stderrTail}`));
5713
5762
  });
@@ -5742,9 +5791,9 @@ var renderHyperframes = async (shot, ctx) => {
5742
5791
  throw new HyperFramesNotInstalledError();
5743
5792
  }
5744
5793
  const html = resolveShotHtml(shot, ctx.aspect);
5745
- const htmlDir = path4.join(ctx.tmpDir, "hyperframes");
5794
+ const htmlDir = path5.join(ctx.tmpDir, "hyperframes");
5746
5795
  await mkdir(htmlDir, { recursive: true });
5747
- const htmlPath = path4.join(htmlDir, `shot-${shot.id}.html`);
5796
+ const htmlPath = path5.join(htmlDir, `shot-${shot.id}.html`);
5748
5797
  await writeFile(htmlPath, html, "utf8");
5749
5798
  ctx.progress.emit({
5750
5799
  phase: "rendering",
@@ -5766,12 +5815,12 @@ var renderHyperframes = async (shot, ctx) => {
5766
5815
  };
5767
5816
 
5768
5817
  // ../pipeline/src/clip-render/engines/manim-remotion.ts
5769
- import * as fs4 from "fs";
5770
- import * as path5 from "path";
5818
+ import * as fs5 from "fs";
5819
+ import * as path6 from "path";
5771
5820
  var renderManimRemotion = async (shot, ctx) => {
5772
5821
  if (ctx.signal?.aborted) throw new Error("aborted");
5773
- const manimSceneOut = path5.join(ctx.tmpDir, "manim", `${shot.id}_manim.mp4`);
5774
- fs4.mkdirSync(path5.dirname(manimSceneOut), { recursive: true });
5822
+ const manimSceneOut = path6.join(ctx.tmpDir, "manim", `${shot.id}_manim.mp4`);
5823
+ fs5.mkdirSync(path6.dirname(manimSceneOut), { recursive: true });
5775
5824
  const manimCtx = { ...ctx, sceneOutPath: manimSceneOut };
5776
5825
  ctx.progress.emit({ phase: "rendering", shotId: shot.id });
5777
5826
  await renderManim(shot, manimCtx);
@@ -5780,9 +5829,9 @@ var renderManimRemotion = async (shot, ctx) => {
5780
5829
  const totalFrames = Math.max(1, Math.round(shot.durationSec * ctx.fps));
5781
5830
  if (ctx.signal?.aborted) throw new Error("aborted");
5782
5831
  ctx.progress.emit({ phase: "compositing", shotId: shot.id });
5783
- const propsPath = path5.join(ctx.tmpDir, "remotion", `${shot.id}_mr_props.json`);
5784
- fs4.mkdirSync(path5.dirname(propsPath), { recursive: true });
5785
- fs4.writeFileSync(
5832
+ const propsPath = path6.join(ctx.tmpDir, "remotion", `${shot.id}_mr_props.json`);
5833
+ fs5.mkdirSync(path6.dirname(propsPath), { recursive: true });
5834
+ fs5.writeFileSync(
5786
5835
  propsPath,
5787
5836
  JSON.stringify(
5788
5837
  {
@@ -5796,14 +5845,14 @@ var renderManimRemotion = async (shot, ctx) => {
5796
5845
  2
5797
5846
  )
5798
5847
  );
5799
- const entryArg = path5.resolve(process.cwd(), "packages/remotion/src/Root.tsx");
5848
+ const remotion = requireRemotionRoot();
5800
5849
  const compId = ctx.aspect === "9:16" ? "ForgeVideoVertical" : "ForgeVideo";
5801
5850
  const r = await runCmd(
5802
5851
  "npx",
5803
5852
  [
5804
5853
  "remotion",
5805
5854
  "render",
5806
- entryArg,
5855
+ remotion.entry,
5807
5856
  compId,
5808
5857
  "--props",
5809
5858
  propsPath,
@@ -5812,11 +5861,11 @@ var renderManimRemotion = async (shot, ctx) => {
5812
5861
  "--concurrency",
5813
5862
  "4"
5814
5863
  ],
5815
- { timeoutMs: 3e5, signal: ctx.signal }
5864
+ { timeoutMs: 3e5, signal: ctx.signal, cwd: remotion.cwd }
5816
5865
  );
5817
5866
  if (r.code !== 0) {
5818
5867
  const tail = r.stderr.split("\n").slice(-5).join(" ").slice(0, 300);
5819
- fs4.copyFileSync(manimSceneOut, ctx.sceneOutPath);
5868
+ fs5.copyFileSync(manimSceneOut, ctx.sceneOutPath);
5820
5869
  ctx.progress.emit({
5821
5870
  phase: "compositing",
5822
5871
  shotId: shot.id,
@@ -5827,18 +5876,18 @@ var renderManimRemotion = async (shot, ctx) => {
5827
5876
  };
5828
5877
 
5829
5878
  // ../pipeline/src/clip-render/engines/remotion.ts
5830
- import * as fs5 from "fs";
5831
- import * as path6 from "path";
5879
+ import * as fs6 from "fs";
5880
+ import * as path7 from "path";
5832
5881
  var ASPECT_DIMS2 = {
5833
5882
  "16:9": { width: 1920, height: 1080 },
5834
5883
  "9:16": { width: 1080, height: 1920 }
5835
5884
  };
5836
5885
  function writeEntryForInlineTsx(ctx, shotId, durationFrames) {
5837
5886
  const dims = ASPECT_DIMS2[ctx.aspect];
5838
- const dir = path6.join(ctx.tmpDir, "remotion", shotId);
5839
- fs5.mkdirSync(dir, { recursive: true });
5840
- const componentPath = path6.join(dir, `${shotId}.tsx`);
5841
- const entryPath = path6.join(dir, `${shotId}_entry.tsx`);
5887
+ const dir = path7.join(ctx.tmpDir, "remotion", shotId);
5888
+ fs6.mkdirSync(dir, { recursive: true });
5889
+ const componentPath = path7.join(dir, `${shotId}.tsx`);
5890
+ const entryPath = path7.join(dir, `${shotId}_entry.tsx`);
5842
5891
  const compId = "GeneratedChunk";
5843
5892
  const entry = `import { registerRoot } from 'remotion';
5844
5893
  import React from 'react';
@@ -5859,7 +5908,7 @@ const Root: React.FC = () => (
5859
5908
 
5860
5909
  registerRoot(Root);
5861
5910
  `;
5862
- fs5.writeFileSync(entryPath, entry);
5911
+ fs6.writeFileSync(entryPath, entry);
5863
5912
  return { entryPath, componentPath, compId };
5864
5913
  }
5865
5914
  var renderRemotion = async (shot, ctx) => {
@@ -5867,22 +5916,26 @@ var renderRemotion = async (shot, ctx) => {
5867
5916
  const durationFrames = Math.max(1, Math.round(shot.durationSec * ctx.fps));
5868
5917
  let entryArg;
5869
5918
  let compId;
5919
+ let remotionCwd;
5870
5920
  if (shot.remotionTsx && shot.remotionTsx.trim().length > 0) {
5871
5921
  const { entryPath, componentPath, compId: id } = writeEntryForInlineTsx(
5872
5922
  ctx,
5873
5923
  shot.id,
5874
5924
  durationFrames
5875
5925
  );
5876
- fs5.writeFileSync(componentPath, shot.remotionTsx);
5926
+ fs6.writeFileSync(componentPath, shot.remotionTsx);
5877
5927
  entryArg = entryPath;
5878
5928
  compId = id;
5929
+ remotionCwd = requireRemotionRoot().cwd;
5879
5930
  } else {
5880
- entryArg = path6.resolve(process.cwd(), "packages/remotion/src/Root.tsx");
5931
+ const remotion = requireRemotionRoot();
5932
+ entryArg = remotion.entry;
5881
5933
  compId = ctx.aspect === "9:16" ? "ForgeVideoVertical" : "ForgeVideo";
5934
+ remotionCwd = remotion.cwd;
5882
5935
  }
5883
- const propsPath = path6.join(ctx.tmpDir, "remotion", `${shot.id}_props.json`);
5884
- fs5.mkdirSync(path6.dirname(propsPath), { recursive: true });
5885
- fs5.writeFileSync(
5936
+ const propsPath = path7.join(ctx.tmpDir, "remotion", `${shot.id}_props.json`);
5937
+ fs6.mkdirSync(path7.dirname(propsPath), { recursive: true });
5938
+ fs6.writeFileSync(
5886
5939
  propsPath,
5887
5940
  JSON.stringify(
5888
5941
  {
@@ -5914,7 +5967,7 @@ var renderRemotion = async (shot, ctx) => {
5914
5967
  "--concurrency",
5915
5968
  "4"
5916
5969
  ],
5917
- { timeoutMs: 3e5, signal: ctx.signal }
5970
+ { timeoutMs: 3e5, signal: ctx.signal, cwd: remotionCwd }
5918
5971
  );
5919
5972
  if (r.code !== 0) {
5920
5973
  const tail = r.stderr.split("\n").slice(-5).join(" ").slice(0, 300);
@@ -5934,8 +5987,8 @@ var renderRemotionHtmlCanvas = async () => {
5934
5987
  };
5935
5988
 
5936
5989
  // ../pipeline/src/clip-render/engines/stock-remotion.ts
5937
- import * as fs6 from "fs";
5938
- import * as path7 from "path";
5990
+ import * as fs7 from "fs";
5991
+ import * as path8 from "path";
5939
5992
  async function searchAndDownloadPexels(query, desiredDuration, outputPath, signal) {
5940
5993
  const apiKey = process.env.PEXELS_API_KEY;
5941
5994
  if (!apiKey) throw new Error("stock+remotion: PEXELS_API_KEY not set");
@@ -5963,8 +6016,8 @@ async function searchAndDownloadPexels(query, desiredDuration, outputPath, signa
5963
6016
  throw new Error(`stock+remotion: Pexels download failed ${videoResp.status}`);
5964
6017
  }
5965
6018
  const buf = Buffer.from(await videoResp.arrayBuffer());
5966
- fs6.mkdirSync(path7.dirname(outputPath), { recursive: true });
5967
- fs6.writeFileSync(outputPath, buf);
6019
+ fs7.mkdirSync(path8.dirname(outputPath), { recursive: true });
6020
+ fs7.writeFileSync(outputPath, buf);
5968
6021
  }
5969
6022
  var renderStockRemotion = async (shot, ctx) => {
5970
6023
  if (ctx.signal?.aborted) throw new Error("aborted");
@@ -5973,13 +6026,13 @@ var renderStockRemotion = async (shot, ctx) => {
5973
6026
  throw new Error(`stock+remotion requires shot.prompt or intent for shot ${shot.id}`);
5974
6027
  }
5975
6028
  ctx.progress.emit({ phase: "preparing", shotId: shot.id });
5976
- const stockPath = path7.join(ctx.tmpDir, "remotion", `${shot.id}_stock.mp4`);
6029
+ const stockPath = path8.join(ctx.tmpDir, "remotion", `${shot.id}_stock.mp4`);
5977
6030
  await searchAndDownloadPexels(query, shot.durationSec, stockPath, ctx.signal);
5978
6031
  if (ctx.signal?.aborted) throw new Error("aborted");
5979
6032
  ctx.progress.emit({ phase: "compositing", shotId: shot.id });
5980
6033
  const totalFrames = Math.max(1, Math.round(shot.durationSec * ctx.fps));
5981
- const propsPath = path7.join(ctx.tmpDir, "remotion", `${shot.id}_stock_props.json`);
5982
- fs6.writeFileSync(
6034
+ const propsPath = path8.join(ctx.tmpDir, "remotion", `${shot.id}_stock_props.json`);
6035
+ fs7.writeFileSync(
5983
6036
  propsPath,
5984
6037
  JSON.stringify(
5985
6038
  {
@@ -5992,14 +6045,14 @@ var renderStockRemotion = async (shot, ctx) => {
5992
6045
  2
5993
6046
  )
5994
6047
  );
5995
- const entryArg = path7.resolve(process.cwd(), "packages/remotion/src/Root.tsx");
6048
+ const remotion = requireRemotionRoot();
5996
6049
  const compId = ctx.aspect === "9:16" ? "ForgeVideoVertical" : "ForgeVideo";
5997
6050
  const r = await runCmd(
5998
6051
  "npx",
5999
6052
  [
6000
6053
  "remotion",
6001
6054
  "render",
6002
- entryArg,
6055
+ remotion.entry,
6003
6056
  compId,
6004
6057
  "--props",
6005
6058
  propsPath,
@@ -6008,11 +6061,11 @@ var renderStockRemotion = async (shot, ctx) => {
6008
6061
  "--concurrency",
6009
6062
  "4"
6010
6063
  ],
6011
- { timeoutMs: 3e5, signal: ctx.signal }
6064
+ { timeoutMs: 3e5, signal: ctx.signal, cwd: remotion.cwd }
6012
6065
  );
6013
6066
  if (r.code !== 0) {
6014
6067
  const tail = r.stderr.split("\n").slice(-5).join(" ").slice(0, 300);
6015
- fs6.copyFileSync(stockPath, ctx.sceneOutPath);
6068
+ fs7.copyFileSync(stockPath, ctx.sceneOutPath);
6016
6069
  ctx.progress.emit({
6017
6070
  phase: "compositing",
6018
6071
  shotId: shot.id,
@@ -6042,11 +6095,11 @@ function isManimEngine(engine) {
6042
6095
  }
6043
6096
 
6044
6097
  // ../pipeline/src/clip-render/fs-layout.ts
6045
- import * as fs7 from "fs";
6046
- import * as path8 from "path";
6098
+ import * as fs8 from "fs";
6099
+ import * as path9 from "path";
6047
6100
  function resolveProjectRoot(projectSlug, callerOutputDir) {
6048
6101
  if (callerOutputDir && callerOutputDir.length > 0) {
6049
- if (!path8.isAbsolute(callerOutputDir)) {
6102
+ if (!path9.isAbsolute(callerOutputDir)) {
6050
6103
  throw new Error(
6051
6104
  `clip-render: spec.outputDir must be absolute, got "${callerOutputDir}"`
6052
6105
  );
@@ -6056,41 +6109,41 @@ function resolveProjectRoot(projectSlug, callerOutputDir) {
6056
6109
  if (!projectSlug || /[/\\]/.test(projectSlug)) {
6057
6110
  throw new Error(`clip-render: invalid projectSlug "${projectSlug}"`);
6058
6111
  }
6059
- return path8.resolve(process.cwd(), "forge-renders", projectSlug);
6112
+ return path9.resolve(process.cwd(), "forge-renders", projectSlug);
6060
6113
  }
6061
6114
  function chunkPaths(projectSlug, chunkId, callerOutputDir) {
6062
6115
  if (!chunkId || /[/\\]/.test(chunkId)) {
6063
6116
  throw new Error(`clip-render: invalid chunkId "${chunkId}"`);
6064
6117
  }
6065
6118
  const root = resolveProjectRoot(projectSlug, callerOutputDir);
6066
- const tmpDir = path8.join(root, ".tmp", chunkId);
6119
+ const tmpDir = path9.join(root, ".tmp", chunkId);
6067
6120
  return {
6068
6121
  root,
6069
- finalClipPath: path8.join(root, "clips", `${chunkId}.mp4`),
6122
+ finalClipPath: path9.join(root, "clips", `${chunkId}.mp4`),
6070
6123
  tmpDir,
6071
- scenesDir: path8.join(tmpDir, "scenes"),
6072
- manimDir: path8.join(tmpDir, "manim"),
6073
- remotionDir: path8.join(tmpDir, "remotion"),
6074
- hyperframesDir: path8.join(tmpDir, "hyperframes"),
6075
- progressLogPath: path8.join(root, "progress.json")
6124
+ scenesDir: path9.join(tmpDir, "scenes"),
6125
+ manimDir: path9.join(tmpDir, "manim"),
6126
+ remotionDir: path9.join(tmpDir, "remotion"),
6127
+ hyperframesDir: path9.join(tmpDir, "hyperframes"),
6128
+ progressLogPath: path9.join(root, "progress.json")
6076
6129
  };
6077
6130
  }
6078
6131
  function ensureChunkDirs(paths) {
6079
6132
  for (const dir of [
6080
- path8.dirname(paths.finalClipPath),
6133
+ path9.dirname(paths.finalClipPath),
6081
6134
  paths.tmpDir,
6082
6135
  paths.scenesDir,
6083
6136
  paths.manimDir,
6084
6137
  paths.remotionDir,
6085
6138
  paths.hyperframesDir
6086
6139
  ]) {
6087
- fs7.mkdirSync(dir, { recursive: true });
6140
+ fs8.mkdirSync(dir, { recursive: true });
6088
6141
  }
6089
6142
  }
6090
6143
  function appendProgress(progressLogPath, event) {
6091
6144
  try {
6092
- fs7.mkdirSync(path8.dirname(progressLogPath), { recursive: true });
6093
- fs7.appendFileSync(progressLogPath, JSON.stringify(event) + "\n");
6145
+ fs8.mkdirSync(path9.dirname(progressLogPath), { recursive: true });
6146
+ fs8.appendFileSync(progressLogPath, JSON.stringify(event) + "\n");
6094
6147
  } catch {
6095
6148
  }
6096
6149
  }
@@ -6193,10 +6246,10 @@ async function renderChunk(spec, opts = {}) {
6193
6246
  await renderWithEngine(spec, engine, paths, fps, tracker, opts);
6194
6247
  tracker.emit({ phase: "compositing", percent: 85 });
6195
6248
  const scenePaths = spec.shots.map((s) => sceneOutPath(paths, s));
6196
- const concatTarget = path9.join(paths.tmpDir, `${spec.chunkId}_video.mp4`);
6249
+ const concatTarget = path10.join(paths.tmpDir, `${spec.chunkId}_video.mp4`);
6197
6250
  await concatScenes(scenePaths, concatTarget, { signal: opts.signal });
6198
6251
  if (opts.signal?.aborted) throw new Error("aborted");
6199
- const audioAvailable = !!spec.audioPath && fs8.existsSync(spec.audioPath);
6252
+ const audioAvailable = !!spec.audioPath && fs9.existsSync(spec.audioPath);
6200
6253
  if (audioAvailable) {
6201
6254
  tracker.emit({ phase: "muxing", percent: 95 });
6202
6255
  await muxAudio(concatTarget, spec.audioPath, paths.finalClipPath, {
@@ -6208,7 +6261,7 @@ async function renderChunk(spec, opts = {}) {
6208
6261
  } else {
6209
6262
  warnings.push("no audioPath in spec \u2014 wrote silent clip");
6210
6263
  }
6211
- fs8.copyFileSync(concatTarget, paths.finalClipPath);
6264
+ fs9.copyFileSync(concatTarget, paths.finalClipPath);
6212
6265
  }
6213
6266
  tracker.emit({ phase: "done", percent: 100 });
6214
6267
  const renderTimeMs2 = (opts.now ?? Date.now)() - startedAt;
@@ -6247,7 +6300,7 @@ async function renderChunk(spec, opts = {}) {
6247
6300
  };
6248
6301
  }
6249
6302
  function sceneOutPath(paths, shot) {
6250
- return path9.join(paths.scenesDir, `${shot.id}.mp4`);
6303
+ return path10.join(paths.scenesDir, `${shot.id}.mp4`);
6251
6304
  }
6252
6305
  function abortedResult(spec, engine, fallbackDepth, startedAt, now) {
6253
6306
  return {
@@ -6280,7 +6333,7 @@ async function renderWithEngine(spec, engine, paths, fps, tracker, opts) {
6280
6333
  progress: tracker,
6281
6334
  resolvedEngine: engine
6282
6335
  };
6283
- fs8.mkdirSync(path9.dirname(ctx.sceneOutPath), { recursive: true });
6336
+ fs9.mkdirSync(path10.dirname(ctx.sceneOutPath), { recursive: true });
6284
6337
  const release = opts.acquireManimSlot ? await opts.acquireManimSlot() : void 0;
6285
6338
  try {
6286
6339
  tracker.emit({
@@ -6292,7 +6345,7 @@ async function renderWithEngine(spec, engine, paths, fps, tracker, opts) {
6292
6345
  } finally {
6293
6346
  release?.();
6294
6347
  }
6295
- if (!fs8.existsSync(ctx.sceneOutPath)) {
6348
+ if (!fs9.existsSync(ctx.sceneOutPath)) {
6296
6349
  throw new Error(
6297
6350
  `engine ${engine} produced no output at ${ctx.sceneOutPath} for shot ${shot.id}`
6298
6351
  );
@@ -6476,8 +6529,8 @@ var BridgePoller = class {
6476
6529
  if (!/^[a-z][a-z0-9-]*$/i.test(name)) return { available: false, path: null };
6477
6530
  const r = spawnSync("which", [name], { encoding: "utf-8", timeout: 2e3 });
6478
6531
  if (r.status !== 0) return { available: false, path: null };
6479
- const path10 = (r.stdout ?? "").trim();
6480
- return { available: !!path10, path: path10 || null };
6532
+ const path11 = (r.stdout ?? "").trim();
6533
+ return { available: !!path11, path: path11 || null };
6481
6534
  }
6482
6535
  async heartbeat() {
6483
6536
  if (this.stopped) return;
@@ -6745,16 +6798,16 @@ var BridgePoller = class {
6745
6798
  async invokeStitchFinal(jobId, payload) {
6746
6799
  let stitchFinalVideo;
6747
6800
  try {
6748
- const mod = await import("./stitch-7XB7ZA4D.js");
6801
+ const mod = await import("./stitch-BYQJTBWS.js");
6749
6802
  stitchFinalVideo = mod.stitchFinalVideo;
6750
6803
  } catch (err) {
6751
6804
  return {
6752
6805
  error: `stitch-final job requires @forge/pipeline/stitch (not installed): ${err.message}`
6753
6806
  };
6754
6807
  }
6755
- const path10 = await import("path");
6756
- const projectRoot = path10.resolve(process.cwd(), "forge-renders", payload.projectSlug);
6757
- const clipsDir = path10.join(projectRoot, "clips");
6808
+ const path11 = await import("path");
6809
+ const projectRoot = path11.resolve(process.cwd(), "forge-renders", payload.projectSlug);
6810
+ const clipsDir = path11.join(projectRoot, "clips");
6758
6811
  const publish = async (phase, eventPayload) => {
6759
6812
  try {
6760
6813
  await fetch(`${this.baseUrl}/api/cli-bridge/stitch-event`, {
@@ -6780,7 +6833,7 @@ var BridgePoller = class {
6780
6833
  projectSlug: payload.projectSlug,
6781
6834
  clipsDir,
6782
6835
  chunkOrder: payload.chunkOrder ?? [],
6783
- masterAudioPath: path10.isAbsolute(masterAudioRel) ? masterAudioRel : path10.join(projectRoot, masterAudioRel),
6836
+ masterAudioPath: path11.isAbsolute(masterAudioRel) ? masterAudioRel : path11.join(projectRoot, masterAudioRel),
6784
6837
  outputDir: projectRoot,
6785
6838
  aspect: payload.aspect,
6786
6839
  skipTransitions: payload.skipTransitions,
@@ -6964,10 +7017,10 @@ function collectImagesFromCodexStdout(stdout, maxCount) {
6964
7017
  for (const p of paths) {
6965
7018
  if (images.length >= maxCount) break;
6966
7019
  try {
6967
- const stat = fs9.statSync(p);
7020
+ const stat = fs10.statSync(p);
6968
7021
  if (!stat.isFile() || stat.size <= 0) continue;
6969
7022
  images.push({
6970
- base64: fs9.readFileSync(p).toString("base64"),
7023
+ base64: fs10.readFileSync(p).toString("base64"),
6971
7024
  mimeType: mimeTypeForPath(p),
6972
7025
  model: "codex-cli:imagegen"
6973
7026
  });
@@ -6999,7 +7052,7 @@ function mimeTypeForPath(p) {
6999
7052
  return "image/png";
7000
7053
  }
7001
7054
  function runSpawn(cmd, args, stdin, timeoutMs, onSpawn) {
7002
- return new Promise((resolve7, reject) => {
7055
+ return new Promise((resolve3, reject) => {
7003
7056
  const proc = spawn2(cmd, args, { stdio: ["pipe", "pipe", "pipe"] });
7004
7057
  if (onSpawn) {
7005
7058
  try {
@@ -7033,7 +7086,7 @@ function runSpawn(cmd, args, stdin, timeoutMs, onSpawn) {
7033
7086
  return;
7034
7087
  }
7035
7088
  if (code !== 0) reject(new Error(`${cmd} exit ${code}: ${stderr.slice(-300)}`));
7036
- else resolve7(stdout);
7089
+ else resolve3(stdout);
7037
7090
  });
7038
7091
  proc.stdin.end(stdin);
7039
7092
  });
@@ -4,8 +4,8 @@
4
4
  import { spawn } from "child_process";
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
- var defaultRunner = (cmd, args, { timeoutMs = 3e5, signal }) => new Promise((resolve, reject) => {
8
- const child = spawn(cmd, args, { stdio: ["pipe", "pipe", "pipe"] });
7
+ var defaultRunner = (cmd, args, { timeoutMs = 3e5, signal, cwd }) => new Promise((resolve, reject) => {
8
+ const child = spawn(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd });
9
9
  let stdout = "";
10
10
  let stderr = "";
11
11
  let timer;
package/dist/index.js CHANGED
@@ -1615,7 +1615,7 @@ Return ONLY the complete updated TSX. No markdown fences, no explanation.`;
1615
1615
  return "0.0.0";
1616
1616
  })();
1617
1617
  void (async () => {
1618
- const { BridgePoller } = await import("./bridge-poller-NSYYEHB3.js");
1618
+ const { BridgePoller } = await import("./bridge-poller-Z74DF5GN.js");
1619
1619
  const poller = new BridgePoller({ baseUrl: bridgeUrl, token: bridgeToken, clientVersion: `storyforge ${pkgVersion}` });
1620
1620
  poller.start();
1621
1621
  })();
@@ -1992,7 +1992,7 @@ async function installRenderersCommand(opts = {}) {
1992
1992
  }
1993
1993
 
1994
1994
  // src/index.ts
1995
- var VERSION = "0.7.1";
1995
+ var VERSION = "0.7.2";
1996
1996
  var HELP = `
1997
1997
  storyforge \u2014 local bridge for the Forge video production web app
1998
1998
 
@@ -5,7 +5,7 @@ import {
5
5
  buildNormalizeClipArgs,
6
6
  probeDuration,
7
7
  runCmd
8
- } from "./chunk-M6JAFPDI.js";
8
+ } from "./chunk-5SWMYFFS.js";
9
9
  import "./chunk-NSPRIPOP.js";
10
10
 
11
11
  // ../pipeline/src/stitch/stitch-final.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storyforge",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "StoryForge — local bridge for the Forge video production web app. Parallel clip-render orchestrator (Remotion 4 + Manim + HyperFrames + ffmpeg) + final video stitcher + dependency doctor.",
5
5
  "type": "module",
6
6
  "bin": {