storyforge 0.7.5 → 0.8.0

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.
@@ -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 fs10 from "fs";
138
+ import * as fs11 from "fs";
139
139
 
140
140
  // ../pipeline/src/clip-render/render-chunk.ts
141
- import * as fs9 from "fs";
142
- import * as path10 from "path";
141
+ import * as fs10 from "fs";
142
+ import * as path11 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: path11, errorMaps, issueData } = params;
623
- const fullPath = [...path11, ...issueData.path || []];
622
+ const { data, path: path12, errorMaps, issueData } = params;
623
+ const fullPath = [...path12, ...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, path11, key) {
739
+ constructor(parent, value, path12, key) {
740
740
  this._cachedPath = [];
741
741
  this.parent = parent;
742
742
  this.data = value;
743
- this._path = path11;
743
+ this._path = path12;
744
744
  this._key = key;
745
745
  }
746
746
  get path() {
@@ -5345,7 +5345,8 @@ var ENGINE_FALLBACK_CHAIN = {
5345
5345
  hyperframes: "remotion",
5346
5346
  "stock+remotion": "remotion",
5347
5347
  manim: "remotion",
5348
- remotion: null
5348
+ remotion: null,
5349
+ "documentary-v2": "gemini+remotion"
5349
5350
  };
5350
5351
  function nextFallback(engine) {
5351
5352
  return ENGINE_FALLBACK_CHAIN[engine] ?? null;
@@ -6082,6 +6083,81 @@ var renderStockRemotion = async (shot, ctx) => {
6082
6083
  }
6083
6084
  };
6084
6085
 
6086
+ // ../pipeline/src/clip-render/engines/documentary-v2.ts
6087
+ import * as fs8 from "fs";
6088
+ import * as path9 from "path";
6089
+ var renderDocumentaryV2 = async (shot, ctx) => {
6090
+ if (ctx.signal?.aborted) throw new Error("aborted");
6091
+ if (!shot.prompt) {
6092
+ throw new Error(
6093
+ `documentary-v2: shot.prompt must be the path to a CompositionV2Data JSON file (shot ${shot.id})`
6094
+ );
6095
+ }
6096
+ ctx.progress.emit({ phase: "preparing", shotId: shot.id });
6097
+ let data;
6098
+ if (shot.prompt.trimStart().startsWith("{")) {
6099
+ data = JSON.parse(shot.prompt);
6100
+ } else {
6101
+ if (!fs8.existsSync(shot.prompt)) {
6102
+ throw new Error(`documentary-v2: CompositionV2Data file not found: ${shot.prompt}`);
6103
+ }
6104
+ data = JSON.parse(fs8.readFileSync(shot.prompt, "utf8"));
6105
+ }
6106
+ const durationFrames = data.totalDurationFrames > 0 ? data.totalDurationFrames : Math.max(1, Math.round(shot.durationSec * ctx.fps));
6107
+ const remotion = requireRemotionRoot();
6108
+ const cacheRel = path9.posix.join("clip-render-cache", ctx.chunkId);
6109
+ const cacheAbs = path9.join(remotion.cwd, "public", cacheRel);
6110
+ fs8.mkdirSync(cacheAbs, { recursive: true });
6111
+ for (const scene of data.scenes) {
6112
+ for (const layer of scene.layers) {
6113
+ if (!layer.assetPath) continue;
6114
+ if (path9.isAbsolute(layer.assetPath) && fs8.existsSync(layer.assetPath)) {
6115
+ const filename = path9.basename(layer.assetPath);
6116
+ const destAbs = path9.join(cacheAbs, filename);
6117
+ if (!fs8.existsSync(destAbs)) {
6118
+ fs8.copyFileSync(layer.assetPath, destAbs);
6119
+ }
6120
+ layer.assetPath = path9.posix.join(cacheRel, filename);
6121
+ }
6122
+ }
6123
+ }
6124
+ const propsPath = path9.join(ctx.tmpDir, "remotion", `${shot.id}_dv2_props.json`);
6125
+ fs8.mkdirSync(path9.dirname(propsPath), { recursive: true });
6126
+ fs8.writeFileSync(propsPath, JSON.stringify({ compositionData: data }, null, 2));
6127
+ ctx.progress.emit({
6128
+ phase: "rendering",
6129
+ shotId: shot.id,
6130
+ framesRendered: 0,
6131
+ totalFrames: durationFrames
6132
+ });
6133
+ const r = await runCmd(
6134
+ "npx",
6135
+ [
6136
+ "remotion",
6137
+ "render",
6138
+ remotion.entry,
6139
+ "DocumentaryV2",
6140
+ "--props",
6141
+ propsPath,
6142
+ "--output",
6143
+ ctx.sceneOutPath,
6144
+ "--concurrency",
6145
+ "4"
6146
+ ],
6147
+ { timeoutMs: 6e5, signal: ctx.signal, cwd: remotion.cwd }
6148
+ );
6149
+ if (r.code !== 0) {
6150
+ const tail = r.stderr.split("\n").slice(-8).join(" ").slice(0, 400);
6151
+ throw new Error(`documentary-v2 render failed (code ${r.code}): ${tail}`);
6152
+ }
6153
+ ctx.progress.emit({
6154
+ phase: "rendering",
6155
+ shotId: shot.id,
6156
+ framesRendered: durationFrames,
6157
+ totalFrames: durationFrames
6158
+ });
6159
+ };
6160
+
6085
6161
  // ../pipeline/src/clip-render/engines/index.ts
6086
6162
  var ENGINE_ADAPTERS = {
6087
6163
  "gemini+remotion": renderGeminiRemotion,
@@ -6091,7 +6167,8 @@ var ENGINE_ADAPTERS = {
6091
6167
  "remotion": renderRemotion,
6092
6168
  "stock+remotion": renderStockRemotion,
6093
6169
  "hyperframes": renderHyperframes,
6094
- "remotion+htmlcanvas": renderRemotionHtmlCanvas
6170
+ "remotion+htmlcanvas": renderRemotionHtmlCanvas,
6171
+ "documentary-v2": renderDocumentaryV2
6095
6172
  };
6096
6173
  var MANIM_ENGINES = /* @__PURE__ */ new Set([
6097
6174
  "manim",
@@ -6103,11 +6180,11 @@ function isManimEngine(engine) {
6103
6180
  }
6104
6181
 
6105
6182
  // ../pipeline/src/clip-render/fs-layout.ts
6106
- import * as fs8 from "fs";
6107
- import * as path9 from "path";
6183
+ import * as fs9 from "fs";
6184
+ import * as path10 from "path";
6108
6185
  function resolveProjectRoot(projectSlug, callerOutputDir) {
6109
6186
  if (callerOutputDir && callerOutputDir.length > 0) {
6110
- if (!path9.isAbsolute(callerOutputDir)) {
6187
+ if (!path10.isAbsolute(callerOutputDir)) {
6111
6188
  throw new Error(
6112
6189
  `clip-render: spec.outputDir must be absolute, got "${callerOutputDir}"`
6113
6190
  );
@@ -6117,41 +6194,41 @@ function resolveProjectRoot(projectSlug, callerOutputDir) {
6117
6194
  if (!projectSlug || /[/\\]/.test(projectSlug)) {
6118
6195
  throw new Error(`clip-render: invalid projectSlug "${projectSlug}"`);
6119
6196
  }
6120
- return path9.resolve(process.cwd(), "forge-renders", projectSlug);
6197
+ return path10.resolve(process.cwd(), "forge-renders", projectSlug);
6121
6198
  }
6122
6199
  function chunkPaths(projectSlug, chunkId, callerOutputDir) {
6123
6200
  if (!chunkId || /[/\\]/.test(chunkId)) {
6124
6201
  throw new Error(`clip-render: invalid chunkId "${chunkId}"`);
6125
6202
  }
6126
6203
  const root = resolveProjectRoot(projectSlug, callerOutputDir);
6127
- const tmpDir = path9.join(root, ".tmp", chunkId);
6204
+ const tmpDir = path10.join(root, ".tmp", chunkId);
6128
6205
  return {
6129
6206
  root,
6130
- finalClipPath: path9.join(root, "clips", `${chunkId}.mp4`),
6207
+ finalClipPath: path10.join(root, "clips", `${chunkId}.mp4`),
6131
6208
  tmpDir,
6132
- scenesDir: path9.join(tmpDir, "scenes"),
6133
- manimDir: path9.join(tmpDir, "manim"),
6134
- remotionDir: path9.join(tmpDir, "remotion"),
6135
- hyperframesDir: path9.join(tmpDir, "hyperframes"),
6136
- progressLogPath: path9.join(root, "progress.json")
6209
+ scenesDir: path10.join(tmpDir, "scenes"),
6210
+ manimDir: path10.join(tmpDir, "manim"),
6211
+ remotionDir: path10.join(tmpDir, "remotion"),
6212
+ hyperframesDir: path10.join(tmpDir, "hyperframes"),
6213
+ progressLogPath: path10.join(root, "progress.json")
6137
6214
  };
6138
6215
  }
6139
6216
  function ensureChunkDirs(paths) {
6140
6217
  for (const dir of [
6141
- path9.dirname(paths.finalClipPath),
6218
+ path10.dirname(paths.finalClipPath),
6142
6219
  paths.tmpDir,
6143
6220
  paths.scenesDir,
6144
6221
  paths.manimDir,
6145
6222
  paths.remotionDir,
6146
6223
  paths.hyperframesDir
6147
6224
  ]) {
6148
- fs8.mkdirSync(dir, { recursive: true });
6225
+ fs9.mkdirSync(dir, { recursive: true });
6149
6226
  }
6150
6227
  }
6151
6228
  function appendProgress(progressLogPath, event) {
6152
6229
  try {
6153
- fs8.mkdirSync(path9.dirname(progressLogPath), { recursive: true });
6154
- fs8.appendFileSync(progressLogPath, JSON.stringify(event) + "\n");
6230
+ fs9.mkdirSync(path10.dirname(progressLogPath), { recursive: true });
6231
+ fs9.appendFileSync(progressLogPath, JSON.stringify(event) + "\n");
6155
6232
  } catch {
6156
6233
  }
6157
6234
  }
@@ -6254,10 +6331,10 @@ async function renderChunk(spec, opts = {}) {
6254
6331
  await renderWithEngine(spec, engine, paths, fps, tracker, opts);
6255
6332
  tracker.emit({ phase: "compositing", percent: 85 });
6256
6333
  const scenePaths = spec.shots.map((s) => sceneOutPath(paths, s));
6257
- const concatTarget = path10.join(paths.tmpDir, `${spec.chunkId}_video.mp4`);
6334
+ const concatTarget = path11.join(paths.tmpDir, `${spec.chunkId}_video.mp4`);
6258
6335
  await concatScenes(scenePaths, concatTarget, { signal: opts.signal });
6259
6336
  if (opts.signal?.aborted) throw new Error("aborted");
6260
- const audioAvailable = !!spec.audioPath && fs9.existsSync(spec.audioPath);
6337
+ const audioAvailable = !!spec.audioPath && fs10.existsSync(spec.audioPath);
6261
6338
  if (audioAvailable) {
6262
6339
  tracker.emit({ phase: "muxing", percent: 95 });
6263
6340
  await muxAudio(concatTarget, spec.audioPath, paths.finalClipPath, {
@@ -6269,7 +6346,7 @@ async function renderChunk(spec, opts = {}) {
6269
6346
  } else {
6270
6347
  warnings.push("no audioPath in spec \u2014 wrote silent clip");
6271
6348
  }
6272
- fs9.copyFileSync(concatTarget, paths.finalClipPath);
6349
+ fs10.copyFileSync(concatTarget, paths.finalClipPath);
6273
6350
  }
6274
6351
  tracker.emit({ phase: "done", percent: 100 });
6275
6352
  const renderTimeMs2 = (opts.now ?? Date.now)() - startedAt;
@@ -6308,7 +6385,7 @@ async function renderChunk(spec, opts = {}) {
6308
6385
  };
6309
6386
  }
6310
6387
  function sceneOutPath(paths, shot) {
6311
- return path10.join(paths.scenesDir, `${shot.id}.mp4`);
6388
+ return path11.join(paths.scenesDir, `${shot.id}.mp4`);
6312
6389
  }
6313
6390
  function abortedResult(spec, engine, fallbackDepth, startedAt, now) {
6314
6391
  return {
@@ -6341,7 +6418,7 @@ async function renderWithEngine(spec, engine, paths, fps, tracker, opts) {
6341
6418
  progress: tracker,
6342
6419
  resolvedEngine: engine
6343
6420
  };
6344
- fs9.mkdirSync(path10.dirname(ctx.sceneOutPath), { recursive: true });
6421
+ fs10.mkdirSync(path11.dirname(ctx.sceneOutPath), { recursive: true });
6345
6422
  const release = opts.acquireManimSlot ? await opts.acquireManimSlot() : void 0;
6346
6423
  try {
6347
6424
  tracker.emit({
@@ -6353,7 +6430,7 @@ async function renderWithEngine(spec, engine, paths, fps, tracker, opts) {
6353
6430
  } finally {
6354
6431
  release?.();
6355
6432
  }
6356
- if (!fs9.existsSync(ctx.sceneOutPath)) {
6433
+ if (!fs10.existsSync(ctx.sceneOutPath)) {
6357
6434
  throw new Error(
6358
6435
  `engine ${engine} produced no output at ${ctx.sceneOutPath} for shot ${shot.id}`
6359
6436
  );
@@ -6537,8 +6614,8 @@ var BridgePoller = class {
6537
6614
  if (!/^[a-z][a-z0-9-]*$/i.test(name)) return { available: false, path: null };
6538
6615
  const r = spawnSync("which", [name], { encoding: "utf-8", timeout: 2e3 });
6539
6616
  if (r.status !== 0) return { available: false, path: null };
6540
- const path11 = (r.stdout ?? "").trim();
6541
- return { available: !!path11, path: path11 || null };
6617
+ const path12 = (r.stdout ?? "").trim();
6618
+ return { available: !!path12, path: path12 || null };
6542
6619
  }
6543
6620
  async heartbeat() {
6544
6621
  if (this.stopped) return;
@@ -6808,12 +6885,12 @@ var BridgePoller = class {
6808
6885
  */
6809
6886
  async uploadClipToStorj(bridgeJobId, projectId, spec, result) {
6810
6887
  try {
6811
- const fs11 = await import("fs/promises");
6812
- const stat = await fs11.stat(result.outputPath);
6888
+ const fs12 = await import("fs/promises");
6889
+ const stat = await fs12.stat(result.outputPath);
6813
6890
  if (!stat.isFile() || stat.size === 0) {
6814
6891
  return { ok: false, error: `local clip empty or missing at ${result.outputPath}` };
6815
6892
  }
6816
- const buf = await fs11.readFile(result.outputPath);
6893
+ const buf = await fs12.readFile(result.outputPath);
6817
6894
  const durationFrames = Math.max(1, Math.round(result.durationSec * 30));
6818
6895
  const form = new FormData();
6819
6896
  form.append("bridgeJobId", bridgeJobId);
@@ -6878,9 +6955,9 @@ var BridgePoller = class {
6878
6955
  error: `stitch-final job requires @forge/pipeline/stitch (not installed): ${err.message}`
6879
6956
  };
6880
6957
  }
6881
- const path11 = await import("path");
6882
- const projectRoot = path11.resolve(process.cwd(), "forge-renders", payload.projectSlug);
6883
- const clipsDir = path11.join(projectRoot, "clips");
6958
+ const path12 = await import("path");
6959
+ const projectRoot = path12.resolve(process.cwd(), "forge-renders", payload.projectSlug);
6960
+ const clipsDir = path12.join(projectRoot, "clips");
6884
6961
  const publish = async (phase, eventPayload) => {
6885
6962
  try {
6886
6963
  await fetch(`${this.baseUrl}/api/cli-bridge/stitch-event`, {
@@ -6906,7 +6983,7 @@ var BridgePoller = class {
6906
6983
  projectSlug: payload.projectSlug,
6907
6984
  clipsDir,
6908
6985
  chunkOrder: payload.chunkOrder ?? [],
6909
- masterAudioPath: path11.isAbsolute(masterAudioRel) ? masterAudioRel : path11.join(projectRoot, masterAudioRel),
6986
+ masterAudioPath: path12.isAbsolute(masterAudioRel) ? masterAudioRel : path12.join(projectRoot, masterAudioRel),
6910
6987
  outputDir: projectRoot,
6911
6988
  aspect: payload.aspect,
6912
6989
  skipTransitions: payload.skipTransitions,
@@ -7090,10 +7167,10 @@ function collectImagesFromCodexStdout(stdout, maxCount) {
7090
7167
  for (const p of paths) {
7091
7168
  if (images.length >= maxCount) break;
7092
7169
  try {
7093
- const stat = fs10.statSync(p);
7170
+ const stat = fs11.statSync(p);
7094
7171
  if (!stat.isFile() || stat.size <= 0) continue;
7095
7172
  images.push({
7096
- base64: fs10.readFileSync(p).toString("base64"),
7173
+ base64: fs11.readFileSync(p).toString("base64"),
7097
7174
  mimeType: mimeTypeForPath(p),
7098
7175
  model: "codex-cli:imagegen"
7099
7176
  });
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-C2SMBUQX.js");
1618
+ const { BridgePoller } = await import("./bridge-poller-HA7G7ILD.js");
1619
1619
  const poller = new BridgePoller({ baseUrl: bridgeUrl, token: bridgeToken, clientVersion: `storyforge ${pkgVersion}` });
1620
1620
  poller.start();
1621
1621
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storyforge",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
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": {