reframe-video 0.6.18 → 0.6.20
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/.claude-plugin/marketplace.json +14 -0
- package/.claude-plugin/plugin.json +9 -0
- package/README.md +45 -0
- package/dist/bin.js +125 -31
- package/dist/cli.js +67 -25
- package/dist/compile-api.d.ts +6 -0
- package/dist/compile-api.js +477 -0
- package/dist/compile.js +477 -0
- package/dist/diff.js +67 -25
- package/dist/frame.js +1149 -0
- package/dist/labels.js +71 -31
- package/dist/renderer-canvas.d.ts +1 -0
- package/dist/renderer-canvas.js +1 -1
- package/dist/types-renderer/index.d.ts +34 -0
- package/package.json +14 -3
- package/skills/reframe/SKILL.md +91 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reframe",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Kiyeon Jeon",
|
|
5
|
+
"url": "https://github.com/kiyeonjeon21"
|
|
6
|
+
},
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "reframe",
|
|
10
|
+
"source": "./",
|
|
11
|
+
"description": "Motion-graphics videos as addressable data — generate, tweak, regenerate without losing human edits."
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reframe",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create and iterate motion-graphics videos as addressable data: deterministic mp4 renders, human edits that survive AI regeneration, label-anchored audio, data-driven batch rendering.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Kiyeon Jeon",
|
|
7
|
+
"url": "https://github.com/kiyeonjeon21"
|
|
8
|
+
}
|
|
9
|
+
}
|
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ npx reframe-video render hello.ts # → out/hello.mp4
|
|
|
22
22
|
|---|---|
|
|
23
23
|
| `reframe render <scene.ts> [--overlay edits.json] [-o out.mp4]` | deterministic mp4 |
|
|
24
24
|
| `reframe batch <scene.ts> <data.json\|csv>` | one mp4 per data row (row keys are overlay addresses) |
|
|
25
|
+
| `reframe compile <scene.ts> [-o out.json] [--json]` | bundle + validate a scene to SceneIR JSON, no render (fast; no ffmpeg/chromium) |
|
|
25
26
|
| `reframe preview` | scrub/play/edit UI for scenes in the current directory; edits export as overlay JSON |
|
|
26
27
|
| `reframe new <name>` | scaffold a documented starter scene |
|
|
27
28
|
| `reframe motion <mp4>` | calibrated motion profile of a rendered clip |
|
|
@@ -63,6 +64,50 @@ Audio is label-anchored (`audio: { cues: [{ at: "enter", sfx: "whoosh" }] }`)
|
|
|
63
64
|
so sound design follows retiming and regeneration. Full syntax:
|
|
64
65
|
`npx reframe-video guide`.
|
|
65
66
|
|
|
67
|
+
## Rendering to a canvas (live preview)
|
|
68
|
+
|
|
69
|
+
The same renderer that produces the mp4 is exported as a subpath for drawing
|
|
70
|
+
frames to a 2D canvas in the browser — so an editor or preview can render a
|
|
71
|
+
scene live and match the export. Compile once, draw any time `t`:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { compileScene, evaluate } from "reframe-video";
|
|
75
|
+
import { renderFrame, drawDisplayList } from "reframe-video/renderer";
|
|
76
|
+
|
|
77
|
+
const compiled = compileScene(myScene); // myScene: SceneIR
|
|
78
|
+
const ctx = canvas.getContext("2d")!;
|
|
79
|
+
|
|
80
|
+
renderFrame(ctx, compiled, t); // clears + paints the frame at time t
|
|
81
|
+
// or drive the DisplayList yourself (you own the clear/background):
|
|
82
|
+
drawDisplayList(ctx, evaluate(compiled, t));
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Camera, clips, track mattes, group effects, gradients and text are handled
|
|
86
|
+
exactly like the mp4 path — `renderFrame` bakes the scene camera in, so don't
|
|
87
|
+
apply one yourself. Images and video need registries: pass `images`
|
|
88
|
+
(`{ get(src) }`) and `videos` (`{ frame(src, i) }`) returning decoded
|
|
89
|
+
`CanvasImageSource`s. The default entry (scene authoring + `compileScene` /
|
|
90
|
+
`evaluate`) is unchanged.
|
|
91
|
+
|
|
92
|
+
## Compiling source to IR in-process (server)
|
|
93
|
+
|
|
94
|
+
For a backend that has an LLM author eDSL source and needs the **SceneIR** back
|
|
95
|
+
(to preview, diff, or self-correct) without rendering, the loader is exported as
|
|
96
|
+
a Node-only subpath:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { loadSceneFromCode, loadScene, SceneLoadError } from "reframe-video/compile";
|
|
100
|
+
|
|
101
|
+
const ir = await loadSceneFromCode(generatedSource); // bundle + validate, no ffmpeg/chromium
|
|
102
|
+
// errors are classified + sanitized (no base64 bundle dump):
|
|
103
|
+
try { await loadSceneFromCode(badSource); }
|
|
104
|
+
catch (e) { if (e instanceof SceneLoadError) console.log(e.kind, e.message); } // "eval" | "bundle" | "validation"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
This runs the scene module in-process — bound untrusted/model-authored source
|
|
108
|
+
(a timeout) and run it where it can't do harm. The same thing on the CLI is
|
|
109
|
+
`reframe compile … --json`.
|
|
110
|
+
|
|
66
111
|
## Why this instead of generating Remotion/HTML?
|
|
67
112
|
|
|
68
113
|
One-shot generation quality is a wash (we measured it). The difference is the
|
package/dist/bin.js
CHANGED
|
@@ -2480,13 +2480,13 @@ async function captureIr(ir, opts) {
|
|
|
2480
2480
|
const assets = await buildImageAssets(ir, sceneDir);
|
|
2481
2481
|
const { fps, duration } = resolveTiming(ir, opts);
|
|
2482
2482
|
const videoAssets = await buildVideoFrameAssets(ir, sceneDir, fps, duration);
|
|
2483
|
-
const
|
|
2483
|
+
const bundle2 = await browserBundle();
|
|
2484
2484
|
return withPage(ir.size, async (page) => {
|
|
2485
2485
|
await page.setContent(
|
|
2486
2486
|
`<!DOCTYPE html><html><body style="margin:0;background:#000"></body></html>`
|
|
2487
2487
|
);
|
|
2488
2488
|
await injectFonts(page);
|
|
2489
|
-
await page.addScriptTag({ content:
|
|
2489
|
+
await page.addScriptTag({ content: bundle2 });
|
|
2490
2490
|
await page.evaluate(
|
|
2491
2491
|
([sceneIr, imageAssets, vAssets]) => window.__reframe.init(sceneIr, imageAssets, vAssets),
|
|
2492
2492
|
[ir, assets, videoAssets]
|
|
@@ -2679,66 +2679,108 @@ var init_batch = __esm({
|
|
|
2679
2679
|
// ../render-cli/src/loadScene.ts
|
|
2680
2680
|
var loadScene_exports = {};
|
|
2681
2681
|
__export(loadScene_exports, {
|
|
2682
|
+
SceneLoadError: () => SceneLoadError,
|
|
2682
2683
|
isComposition: () => isComposition,
|
|
2683
2684
|
loadModule: () => loadModule,
|
|
2684
|
-
loadScene: () => loadScene
|
|
2685
|
+
loadScene: () => loadScene,
|
|
2686
|
+
loadSceneFromCode: () => loadSceneFromCode
|
|
2685
2687
|
});
|
|
2686
2688
|
import { build as build2 } from "esbuild";
|
|
2687
2689
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
2688
2690
|
import { dirname as dirname6, resolve as resolve5 } from "node:path";
|
|
2689
2691
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
2690
|
-
async function
|
|
2691
|
-
|
|
2692
|
-
|
|
2692
|
+
async function bundle(input) {
|
|
2693
|
+
const common = {
|
|
2694
|
+
bundle: true,
|
|
2695
|
+
format: "esm",
|
|
2696
|
+
platform: "neutral",
|
|
2697
|
+
write: false,
|
|
2698
|
+
logLevel: "silent",
|
|
2699
|
+
sourcemap: "inline",
|
|
2700
|
+
alias: ALIAS
|
|
2701
|
+
};
|
|
2693
2702
|
try {
|
|
2694
|
-
const out = await build2(
|
|
2695
|
-
entryPoints: [
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
platform: "neutral",
|
|
2699
|
-
write: false,
|
|
2700
|
-
logLevel: "silent",
|
|
2701
|
-
sourcemap: "inline",
|
|
2702
|
-
// both specifiers accepted: the guide's canonical "@reframe/core" and
|
|
2703
|
-
// the published package name
|
|
2704
|
-
alias: { "@reframe/core": CORE_ENTRY, "reframe-video": CORE_ENTRY }
|
|
2705
|
-
});
|
|
2706
|
-
code = out.outputFiles[0].text;
|
|
2703
|
+
const out = await build2(
|
|
2704
|
+
"path" in input ? { ...common, entryPoints: [input.path] } : { ...common, stdin: { contents: input.code, resolveDir: input.resolveDir, loader: "ts", sourcefile: "scene.ts" } }
|
|
2705
|
+
);
|
|
2706
|
+
return out.outputFiles[0].text;
|
|
2707
2707
|
} catch (err) {
|
|
2708
|
-
throw new
|
|
2709
|
-
${err instanceof Error ? err.message : String(err)}`);
|
|
2708
|
+
throw new SceneLoadError("bundle", clean(err), { cause: err });
|
|
2710
2709
|
}
|
|
2711
|
-
|
|
2712
|
-
|
|
2710
|
+
}
|
|
2711
|
+
async function importDefault(code, label) {
|
|
2712
|
+
let mod;
|
|
2713
|
+
try {
|
|
2714
|
+
mod = await import(`data:text/javascript;base64,${Buffer.from(code).toString("base64")}`);
|
|
2715
|
+
} catch (err) {
|
|
2716
|
+
const kind = err instanceof Error && err.name === "SceneValidationError" ? "validation" : "eval";
|
|
2717
|
+
throw new SceneLoadError(kind, clean(err), { cause: err });
|
|
2718
|
+
}
|
|
2719
|
+
if (mod.default === void 0) throw new SceneLoadError("eval", `${label} must default-export a scene or composition`);
|
|
2713
2720
|
return mod.default;
|
|
2714
2721
|
}
|
|
2722
|
+
async function loadDefault(path2) {
|
|
2723
|
+
if (path2.endsWith(".json")) {
|
|
2724
|
+
try {
|
|
2725
|
+
return JSON.parse(await readFile6(path2, "utf8"));
|
|
2726
|
+
} catch (err) {
|
|
2727
|
+
throw new SceneLoadError("eval", `failed to read ${path2}: ${clean(err)}`, { cause: err });
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
return importDefault(await bundle({ path: path2 }), path2);
|
|
2731
|
+
}
|
|
2715
2732
|
function isComposition(def) {
|
|
2716
2733
|
return typeof def === "object" && def !== null && Array.isArray(def.scenes);
|
|
2717
2734
|
}
|
|
2718
|
-
|
|
2719
|
-
const def = await loadDefault(path2);
|
|
2735
|
+
function asScene(def, label) {
|
|
2720
2736
|
if (isComposition(def)) {
|
|
2721
|
-
throw new
|
|
2737
|
+
throw new SceneLoadError("validation", `${label} is a composition \u2014 render it directly, not as a single scene`);
|
|
2738
|
+
}
|
|
2739
|
+
try {
|
|
2740
|
+
validateScene(def);
|
|
2741
|
+
} catch (err) {
|
|
2742
|
+
throw new SceneLoadError("validation", clean(err), { cause: err });
|
|
2722
2743
|
}
|
|
2723
|
-
validateScene(def);
|
|
2724
2744
|
return def;
|
|
2725
2745
|
}
|
|
2746
|
+
async function loadScene(path2) {
|
|
2747
|
+
return asScene(await loadDefault(path2), path2);
|
|
2748
|
+
}
|
|
2749
|
+
async function loadSceneFromCode(code, resolveDir = process.cwd()) {
|
|
2750
|
+
return asScene(await importDefault(await bundle({ code, resolveDir }), "<source>"), "<source>");
|
|
2751
|
+
}
|
|
2726
2752
|
async function loadModule(path2) {
|
|
2727
2753
|
const def = await loadDefault(path2);
|
|
2728
2754
|
if (isComposition(def)) {
|
|
2729
|
-
|
|
2755
|
+
try {
|
|
2756
|
+
validateComposition(def);
|
|
2757
|
+
} catch (err) {
|
|
2758
|
+
throw new SceneLoadError("validation", clean(err), { cause: err });
|
|
2759
|
+
}
|
|
2730
2760
|
return { kind: "composition", ir: def };
|
|
2731
2761
|
}
|
|
2732
|
-
|
|
2733
|
-
return { kind: "scene", ir: def };
|
|
2762
|
+
return { kind: "scene", ir: asScene(def, path2) };
|
|
2734
2763
|
}
|
|
2735
|
-
var HERE, CORE_ENTRY;
|
|
2764
|
+
var HERE, CORE_ENTRY, SceneLoadError, clean, ALIAS;
|
|
2736
2765
|
var init_loadScene = __esm({
|
|
2737
2766
|
"../render-cli/src/loadScene.ts"() {
|
|
2738
2767
|
"use strict";
|
|
2739
2768
|
init_src();
|
|
2740
2769
|
HERE = dirname6(fileURLToPath4(import.meta.url));
|
|
2741
2770
|
CORE_ENTRY = true ? resolve5(HERE, "index.js") : resolve5(HERE, "..", "..", "core", "src", "index.ts");
|
|
2771
|
+
SceneLoadError = class extends Error {
|
|
2772
|
+
kind;
|
|
2773
|
+
constructor(kind, message, options) {
|
|
2774
|
+
super(message, options);
|
|
2775
|
+
this.name = "SceneLoadError";
|
|
2776
|
+
this.kind = kind;
|
|
2777
|
+
}
|
|
2778
|
+
};
|
|
2779
|
+
clean = (err) => (err instanceof Error ? err.message : String(err)).replace(
|
|
2780
|
+
/data:text\/javascript;base64,[A-Za-z0-9+/=]+/g,
|
|
2781
|
+
"<scene bundle>"
|
|
2782
|
+
);
|
|
2783
|
+
ALIAS = { "@reframe/core": CORE_ENTRY, "reframe-video": CORE_ENTRY };
|
|
2742
2784
|
}
|
|
2743
2785
|
});
|
|
2744
2786
|
|
|
@@ -2755,7 +2797,9 @@ var ROOT2 = PACKAGED ? resolve6(HERE2, "..") : resolve6(HERE2, "..", "..", "..")
|
|
|
2755
2797
|
var USER_CWD = process.env.INIT_CWD ?? process.cwd();
|
|
2756
2798
|
var RENDER_CLI = PACKAGED ? join9(ROOT2, "dist", "cli.js") : join9(ROOT2, "packages", "render-cli", "src", "cli.ts");
|
|
2757
2799
|
var LABELS = PACKAGED ? join9(ROOT2, "dist", "labels.js") : join9(ROOT2, "packages", "render-cli", "src", "labels.ts");
|
|
2800
|
+
var COMPILE = PACKAGED ? join9(ROOT2, "dist", "compile.js") : join9(ROOT2, "packages", "render-cli", "src", "compile.ts");
|
|
2758
2801
|
var DIFF = PACKAGED ? join9(ROOT2, "dist", "diff.js") : join9(ROOT2, "packages", "render-cli", "src", "diff.ts");
|
|
2802
|
+
var FRAME = PACKAGED ? join9(ROOT2, "dist", "frame.js") : join9(ROOT2, "packages", "render-cli", "src", "frame.ts");
|
|
2759
2803
|
var PLAYER = PACKAGED ? join9(ROOT2, "dist", "player.js") : join9(ROOT2, "packages", "render-cli", "src", "player.ts");
|
|
2760
2804
|
var ANALYZE = PACKAGED ? join9(ROOT2, "dist", "analyze.js") : join9(ROOT2, "benchmark", "harness", "motion", "analyze.ts");
|
|
2761
2805
|
var TRACE = PACKAGED ? join9(ROOT2, "dist", "trace-cli.js") : join9(ROOT2, "benchmark", "harness", "motion", "trace-cli.ts");
|
|
@@ -2773,6 +2817,10 @@ usage:
|
|
|
2773
2817
|
${CMD} preview open the scrub/edit UI (lists scenes in your directory)
|
|
2774
2818
|
${CMD} new <scene-name> scaffold <scene-name>.ts in your directory
|
|
2775
2819
|
${CMD} labels <scene.ts|.json> print the event clock (label \u2192 exact seconds; for sound design / timing)
|
|
2820
|
+
${CMD} compile <scene.ts|.json> [-o out.json] [--stdin] [--code "<src>"] [--json]
|
|
2821
|
+
bundle + validate a scene to SceneIR JSON, no render (fast; no ffmpeg/chromium)
|
|
2822
|
+
${CMD} frame <scene.ts|.json> [--t <sec>] [-o out.png] render ONE frame at time t to a PNG (no mp4; for a render-and-look loop)
|
|
2823
|
+
${CMD} skill [--path] print the authoring skill (SKILL.md) for an agent; --path prints the plugin dir to load
|
|
2776
2824
|
${CMD} motion <mp4|framesDir> motion-profile a rendered clip
|
|
2777
2825
|
${CMD} trace <ref.mp4> [--apply scene.ts] extract a video's motion structure \u2192 MotionSketch / timeline
|
|
2778
2826
|
${CMD} diff <ref-image> [<scene.ts>] [--t S] [--mode side|blend|diff|grid] compare/measure a render against a reference image
|
|
@@ -2921,6 +2969,42 @@ ${USAGE}`);
|
|
|
2921
2969
|
await (PACKAGED ? run2(process.execPath, [LABELS, inputPath]) : run2("npx", ["tsx", LABELS, inputPath]))
|
|
2922
2970
|
);
|
|
2923
2971
|
}
|
|
2972
|
+
case "compile": {
|
|
2973
|
+
const hasInlineSource = rest.includes("--stdin") || rest.includes("--code");
|
|
2974
|
+
const fileArg = rest.find((a, i) => !a.startsWith("-") && !["-o", "--code", "--timeout"].includes(rest[i - 1] ?? ""));
|
|
2975
|
+
if (!fileArg && !hasInlineSource) fail(`compile needs a scene file, --stdin, or --code "<src>"
|
|
2976
|
+
|
|
2977
|
+
${USAGE}`);
|
|
2978
|
+
const passed = rest.map((a, i) => {
|
|
2979
|
+
if (a === fileArg) return userPath(a);
|
|
2980
|
+
if (rest[i - 1] === "-o") return userPath(a);
|
|
2981
|
+
return a;
|
|
2982
|
+
});
|
|
2983
|
+
if (fileArg && !existsSync6(userPath(fileArg))) fail(`no such file: ${userPath(fileArg)}`);
|
|
2984
|
+
process.exit(
|
|
2985
|
+
await (PACKAGED ? run2(process.execPath, [COMPILE, ...passed]) : run2("npx", ["tsx", COMPILE, ...passed]))
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2988
|
+
case "frame": {
|
|
2989
|
+
const input = rest[0];
|
|
2990
|
+
if (!input || input.startsWith("-")) fail(`frame needs a scene file
|
|
2991
|
+
|
|
2992
|
+
${USAGE}`);
|
|
2993
|
+
const inputPath = userPath(input);
|
|
2994
|
+
if (!existsSync6(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2995
|
+
const args = rest.slice(1);
|
|
2996
|
+
const outBase = PACKAGED ? join9(USER_CWD, "out") : join9(ROOT2, "out");
|
|
2997
|
+
const stem = `${basename(input).replace(/\.[^.]+$/, "")}.png`;
|
|
2998
|
+
let outArgs = args;
|
|
2999
|
+
if (args.indexOf("-o") === -1) {
|
|
3000
|
+
await mkdir4(outBase, { recursive: true });
|
|
3001
|
+
outArgs = [...args, "-o", join9(outBase, stem)];
|
|
3002
|
+
}
|
|
3003
|
+
outArgs = outArgs.map((a, i) => outArgs[i - 1] === "-o" ? userPath(a) : a);
|
|
3004
|
+
process.exit(
|
|
3005
|
+
await (PACKAGED ? run2(process.execPath, [FRAME, inputPath, ...outArgs]) : run2("npx", ["tsx", FRAME, inputPath, ...outArgs]))
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
2924
3008
|
case "player": {
|
|
2925
3009
|
const input = rest[0];
|
|
2926
3010
|
if (!input || input.startsWith("-")) fail(`player needs a scene file
|
|
@@ -3109,6 +3193,16 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
3109
3193
|
process.stdout.write(await readFile7(file, "utf8"));
|
|
3110
3194
|
return;
|
|
3111
3195
|
}
|
|
3196
|
+
case "skill": {
|
|
3197
|
+
if (rest.includes("--path")) {
|
|
3198
|
+
process.stdout.write(`${ROOT2}
|
|
3199
|
+
`);
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
3203
|
+
process.stdout.write(await readFile7(join9(ROOT2, "skills", "reframe", "SKILL.md"), "utf8"));
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3112
3206
|
case "demo":
|
|
3113
3207
|
if (PACKAGED) {
|
|
3114
3208
|
fail(
|
package/dist/cli.js
CHANGED
|
@@ -1906,13 +1906,13 @@ async function captureIr(ir, opts) {
|
|
|
1906
1906
|
const assets = await buildImageAssets(ir, sceneDir);
|
|
1907
1907
|
const { fps, duration } = resolveTiming(ir, opts);
|
|
1908
1908
|
const videoAssets = await buildVideoFrameAssets(ir, sceneDir, fps, duration);
|
|
1909
|
-
const
|
|
1909
|
+
const bundle2 = await browserBundle();
|
|
1910
1910
|
return withPage(ir.size, async (page) => {
|
|
1911
1911
|
await page.setContent(
|
|
1912
1912
|
`<!DOCTYPE html><html><body style="margin:0;background:#000"></body></html>`
|
|
1913
1913
|
);
|
|
1914
1914
|
await injectFonts(page);
|
|
1915
|
-
await page.addScriptTag({ content:
|
|
1915
|
+
await page.addScriptTag({ content: bundle2 });
|
|
1916
1916
|
await page.evaluate(
|
|
1917
1917
|
([sceneIr, imageAssets, vAssets]) => window.__reframe.init(sceneIr, imageAssets, vAssets),
|
|
1918
1918
|
[ir, assets, videoAssets]
|
|
@@ -2077,42 +2077,84 @@ import { dirname as dirname6, resolve as resolve5 } from "node:path";
|
|
|
2077
2077
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
2078
2078
|
var HERE = dirname6(fileURLToPath4(import.meta.url));
|
|
2079
2079
|
var CORE_ENTRY = true ? resolve5(HERE, "index.js") : resolve5(HERE, "..", "..", "core", "src", "index.ts");
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2080
|
+
var SceneLoadError = class extends Error {
|
|
2081
|
+
kind;
|
|
2082
|
+
constructor(kind, message, options) {
|
|
2083
|
+
super(message, options);
|
|
2084
|
+
this.name = "SceneLoadError";
|
|
2085
|
+
this.kind = kind;
|
|
2086
|
+
}
|
|
2087
|
+
};
|
|
2088
|
+
var clean = (err) => (err instanceof Error ? err.message : String(err)).replace(
|
|
2089
|
+
/data:text\/javascript;base64,[A-Za-z0-9+/=]+/g,
|
|
2090
|
+
"<scene bundle>"
|
|
2091
|
+
);
|
|
2092
|
+
var ALIAS = { "@reframe/core": CORE_ENTRY, "reframe-video": CORE_ENTRY };
|
|
2093
|
+
async function bundle(input) {
|
|
2094
|
+
const common = {
|
|
2095
|
+
bundle: true,
|
|
2096
|
+
format: "esm",
|
|
2097
|
+
platform: "neutral",
|
|
2098
|
+
write: false,
|
|
2099
|
+
logLevel: "silent",
|
|
2100
|
+
sourcemap: "inline",
|
|
2101
|
+
alias: ALIAS
|
|
2102
|
+
};
|
|
2083
2103
|
try {
|
|
2084
|
-
const out = await build2(
|
|
2085
|
-
entryPoints: [
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
platform: "neutral",
|
|
2089
|
-
write: false,
|
|
2090
|
-
logLevel: "silent",
|
|
2091
|
-
sourcemap: "inline",
|
|
2092
|
-
// both specifiers accepted: the guide's canonical "@reframe/core" and
|
|
2093
|
-
// the published package name
|
|
2094
|
-
alias: { "@reframe/core": CORE_ENTRY, "reframe-video": CORE_ENTRY }
|
|
2095
|
-
});
|
|
2096
|
-
code = out.outputFiles[0].text;
|
|
2104
|
+
const out = await build2(
|
|
2105
|
+
"path" in input ? { ...common, entryPoints: [input.path] } : { ...common, stdin: { contents: input.code, resolveDir: input.resolveDir, loader: "ts", sourcefile: "scene.ts" } }
|
|
2106
|
+
);
|
|
2107
|
+
return out.outputFiles[0].text;
|
|
2097
2108
|
} catch (err) {
|
|
2098
|
-
throw new
|
|
2099
|
-
${err instanceof Error ? err.message : String(err)}`);
|
|
2109
|
+
throw new SceneLoadError("bundle", clean(err), { cause: err });
|
|
2100
2110
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2111
|
+
}
|
|
2112
|
+
async function importDefault(code, label) {
|
|
2113
|
+
let mod;
|
|
2114
|
+
try {
|
|
2115
|
+
mod = await import(`data:text/javascript;base64,${Buffer.from(code).toString("base64")}`);
|
|
2116
|
+
} catch (err) {
|
|
2117
|
+
const kind = err instanceof Error && err.name === "SceneValidationError" ? "validation" : "eval";
|
|
2118
|
+
throw new SceneLoadError(kind, clean(err), { cause: err });
|
|
2119
|
+
}
|
|
2120
|
+
if (mod.default === void 0) throw new SceneLoadError("eval", `${label} must default-export a scene or composition`);
|
|
2103
2121
|
return mod.default;
|
|
2104
2122
|
}
|
|
2123
|
+
async function loadDefault(path2) {
|
|
2124
|
+
if (path2.endsWith(".json")) {
|
|
2125
|
+
try {
|
|
2126
|
+
return JSON.parse(await readFile4(path2, "utf8"));
|
|
2127
|
+
} catch (err) {
|
|
2128
|
+
throw new SceneLoadError("eval", `failed to read ${path2}: ${clean(err)}`, { cause: err });
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return importDefault(await bundle({ path: path2 }), path2);
|
|
2132
|
+
}
|
|
2105
2133
|
function isComposition(def) {
|
|
2106
2134
|
return typeof def === "object" && def !== null && Array.isArray(def.scenes);
|
|
2107
2135
|
}
|
|
2136
|
+
function asScene(def, label) {
|
|
2137
|
+
if (isComposition(def)) {
|
|
2138
|
+
throw new SceneLoadError("validation", `${label} is a composition \u2014 render it directly, not as a single scene`);
|
|
2139
|
+
}
|
|
2140
|
+
try {
|
|
2141
|
+
validateScene(def);
|
|
2142
|
+
} catch (err) {
|
|
2143
|
+
throw new SceneLoadError("validation", clean(err), { cause: err });
|
|
2144
|
+
}
|
|
2145
|
+
return def;
|
|
2146
|
+
}
|
|
2108
2147
|
async function loadModule(path2) {
|
|
2109
2148
|
const def = await loadDefault(path2);
|
|
2110
2149
|
if (isComposition(def)) {
|
|
2111
|
-
|
|
2150
|
+
try {
|
|
2151
|
+
validateComposition(def);
|
|
2152
|
+
} catch (err) {
|
|
2153
|
+
throw new SceneLoadError("validation", clean(err), { cause: err });
|
|
2154
|
+
}
|
|
2112
2155
|
return { kind: "composition", ir: def };
|
|
2113
2156
|
}
|
|
2114
|
-
|
|
2115
|
-
return { kind: "scene", ir: def };
|
|
2157
|
+
return { kind: "scene", ir: asScene(def, path2) };
|
|
2116
2158
|
}
|
|
2117
2159
|
|
|
2118
2160
|
// ../render-cli/src/cli.ts
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CompositionIR, SceneIR } from "./index.js";
|
|
2
|
+
export declare class SceneLoadError extends Error { readonly kind: "bundle" | "eval" | "validation"; }
|
|
3
|
+
export declare function loadScene(path: string): Promise<SceneIR>;
|
|
4
|
+
export declare function loadSceneFromCode(code: string, resolveDir?: string): Promise<SceneIR>;
|
|
5
|
+
export declare function isComposition(def: unknown): def is CompositionIR;
|
|
6
|
+
export declare function loadModule(path: string): Promise<{ kind: "scene"; ir: SceneIR } | { kind: "composition"; ir: CompositionIR }>;
|