storyforge 0.7.3 → 0.7.5
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.
|
@@ -5587,13 +5587,22 @@ var renderGeminiRemotion = async (shot, ctx) => {
|
|
|
5587
5587
|
);
|
|
5588
5588
|
const durationFrames = Math.max(1, Math.round(shot.durationSec * ctx.fps));
|
|
5589
5589
|
const dims = ASPECT_DIMS[ctx.aspect];
|
|
5590
|
+
const remotion = requireRemotionRoot();
|
|
5591
|
+
const publicRel = path4.posix.join(
|
|
5592
|
+
"clip-render-cache",
|
|
5593
|
+
ctx.chunkId,
|
|
5594
|
+
`${shot.id}${path4.extname(localImage) || ".png"}`
|
|
5595
|
+
);
|
|
5596
|
+
const publicAbs = path4.join(remotion.cwd, "public", publicRel);
|
|
5597
|
+
fs4.mkdirSync(path4.dirname(publicAbs), { recursive: true });
|
|
5598
|
+
fs4.copyFileSync(localImage, publicAbs);
|
|
5590
5599
|
const propsPath = path4.join(ctx.tmpDir, "remotion", `${shot.id}_gr_props.json`);
|
|
5591
5600
|
fs4.mkdirSync(path4.dirname(propsPath), { recursive: true });
|
|
5592
5601
|
fs4.writeFileSync(
|
|
5593
5602
|
propsPath,
|
|
5594
5603
|
JSON.stringify(
|
|
5595
5604
|
{
|
|
5596
|
-
imagePath:
|
|
5605
|
+
imagePath: publicRel,
|
|
5597
5606
|
overlayText: shot.overlayText ?? "",
|
|
5598
5607
|
durationInFrames: durationFrames,
|
|
5599
5608
|
width: dims.width,
|
|
@@ -5605,8 +5614,7 @@ var renderGeminiRemotion = async (shot, ctx) => {
|
|
|
5605
5614
|
2
|
|
5606
5615
|
)
|
|
5607
5616
|
);
|
|
5608
|
-
const
|
|
5609
|
-
const compId = ctx.aspect === "9:16" ? "ForgeVideoVertical" : "ForgeVideo";
|
|
5617
|
+
const compId = ctx.aspect === "9:16" ? "ForgeShotVertical" : "ForgeShot";
|
|
5610
5618
|
ctx.progress.emit({
|
|
5611
5619
|
phase: "compositing",
|
|
5612
5620
|
shotId: shot.id,
|
|
@@ -6751,13 +6759,36 @@ var BridgePoller = class {
|
|
|
6751
6759
|
onProgress,
|
|
6752
6760
|
signal: cancelController.signal
|
|
6753
6761
|
});
|
|
6762
|
+
const specByChunk = new Map(payload.specs.map((s) => [s.chunkId, s]));
|
|
6763
|
+
const uploadOutcomes = [];
|
|
6764
|
+
for (const r of result.results) {
|
|
6765
|
+
if (!r.ok || !r.outputPath) continue;
|
|
6766
|
+
const spec = specByChunk.get(r.chunkId);
|
|
6767
|
+
if (!spec) continue;
|
|
6768
|
+
const upload = await this.uploadClipToStorj(jobId, payload.projectId, spec, r);
|
|
6769
|
+
uploadOutcomes.push({ chunkId: r.chunkId, ...upload });
|
|
6770
|
+
void this.postRenderProgress(jobId, payload.projectId, {
|
|
6771
|
+
chunkId: r.chunkId,
|
|
6772
|
+
phase: upload.ok ? "done" : "error",
|
|
6773
|
+
percent: upload.ok ? 100 : 95,
|
|
6774
|
+
elapsedMs: r.renderTimeMs,
|
|
6775
|
+
resolvedEngine: r.engine,
|
|
6776
|
+
storjKey: upload.storjKey,
|
|
6777
|
+
outputPath: r.outputPath,
|
|
6778
|
+
error: upload.ok ? void 0 : `upload failed: ${upload.error ?? "unknown"}`,
|
|
6779
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
6780
|
+
});
|
|
6781
|
+
}
|
|
6754
6782
|
const okCount = result.results.filter((r) => r.ok).length;
|
|
6783
|
+
const uploadedCount = uploadOutcomes.filter((u) => u.ok).length;
|
|
6755
6784
|
const summary = {
|
|
6756
6785
|
kind: "render-clips",
|
|
6757
|
-
ok: result.ok,
|
|
6786
|
+
ok: result.ok && uploadedCount === okCount,
|
|
6758
6787
|
total: result.results.length,
|
|
6759
6788
|
succeeded: okCount,
|
|
6760
6789
|
failed: result.results.length - okCount,
|
|
6790
|
+
uploaded: uploadedCount,
|
|
6791
|
+
uploadFailed: uploadOutcomes.filter((u) => !u.ok).length,
|
|
6761
6792
|
totalRenderTimeMs: result.totalRenderTimeMs,
|
|
6762
6793
|
observedPeakConcurrency: result.observedPeakConcurrency
|
|
6763
6794
|
};
|
|
@@ -6768,6 +6799,48 @@ var BridgePoller = class {
|
|
|
6768
6799
|
this.renderCancelControllers.delete(jobId);
|
|
6769
6800
|
}
|
|
6770
6801
|
}
|
|
6802
|
+
/**
|
|
6803
|
+
* POST a rendered clip to /api/cli-bridge/upload-clip as multipart
|
|
6804
|
+
* form data. The endpoint uploads the binary to Storj, inserts a
|
|
6805
|
+
* `clips` table row, and returns the storj_key. Best-effort — a
|
|
6806
|
+
* failed upload is recorded as a warning and the chunk's progress
|
|
6807
|
+
* event reports phase='error' but doesn't take down the whole batch.
|
|
6808
|
+
*/
|
|
6809
|
+
async uploadClipToStorj(bridgeJobId, projectId, spec, result) {
|
|
6810
|
+
try {
|
|
6811
|
+
const fs11 = await import("fs/promises");
|
|
6812
|
+
const stat = await fs11.stat(result.outputPath);
|
|
6813
|
+
if (!stat.isFile() || stat.size === 0) {
|
|
6814
|
+
return { ok: false, error: `local clip empty or missing at ${result.outputPath}` };
|
|
6815
|
+
}
|
|
6816
|
+
const buf = await fs11.readFile(result.outputPath);
|
|
6817
|
+
const durationFrames = Math.max(1, Math.round(result.durationSec * 30));
|
|
6818
|
+
const form = new FormData();
|
|
6819
|
+
form.append("bridgeJobId", bridgeJobId);
|
|
6820
|
+
form.append("chunkId", result.chunkId);
|
|
6821
|
+
form.append("engine", result.engine);
|
|
6822
|
+
form.append("aspect", spec.aspect);
|
|
6823
|
+
form.append("durationSec", String(result.durationSec));
|
|
6824
|
+
form.append("durationFrames", String(durationFrames));
|
|
6825
|
+
form.append("file", new Blob([new Uint8Array(buf)], { type: "video/mp4" }), `${result.chunkId}.mp4`);
|
|
6826
|
+
const resp = await fetch(`${this.baseUrl}/api/cli-bridge/upload-clip`, {
|
|
6827
|
+
method: "POST",
|
|
6828
|
+
headers: { Authorization: `Bearer ${this.token}` },
|
|
6829
|
+
body: form,
|
|
6830
|
+
signal: AbortSignal.timeout(5 * 6e4)
|
|
6831
|
+
});
|
|
6832
|
+
const text = await resp.text().catch(() => "");
|
|
6833
|
+
if (!resp.ok) {
|
|
6834
|
+
return { ok: false, error: `HTTP ${resp.status}: ${text.slice(0, 300)}` };
|
|
6835
|
+
}
|
|
6836
|
+
const parsed = text ? JSON.parse(text) : {};
|
|
6837
|
+
log.info(`[bridge] uploaded ${result.chunkId.slice(0, 8)} -> ${parsed.storjKey ?? "(no key)"}`);
|
|
6838
|
+
void projectId;
|
|
6839
|
+
return { ok: true, storjKey: parsed.storjKey };
|
|
6840
|
+
} catch (err) {
|
|
6841
|
+
return { ok: false, error: err.message };
|
|
6842
|
+
}
|
|
6843
|
+
}
|
|
6771
6844
|
/** POST a single RenderProgress event to /api/render-progress. */
|
|
6772
6845
|
async postRenderProgress(bridgeJobId, projectId, event) {
|
|
6773
6846
|
try {
|
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-
|
|
1618
|
+
const { BridgePoller } = await import("./bridge-poller-C2SMBUQX.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.
|
|
1995
|
+
var VERSION = "0.7.5";
|
|
1996
1996
|
var HELP = `
|
|
1997
1997
|
storyforge \u2014 local bridge for the Forge video production web app
|
|
1998
1998
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "storyforge",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
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": {
|