reframe-video 0.6.4 → 0.6.6
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/bin.js +179 -71
- package/dist/browserEntry.js +95 -30
- package/dist/cli.js +154 -54
- package/dist/index.js +51 -7
- package/dist/labels.js +4 -1
- package/dist/renderer-canvas.js +43 -19
- package/dist/trace-cli.js +2 -1
- package/dist/types/assets.d.ts +3 -1
- package/dist/types/dsl.d.ts +4 -1
- package/dist/types/evaluate.d.ts +15 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/ir.d.ts +30 -0
- package/dist/types/montage.d.ts +4 -4
- package/guides/edsl-guide.md +30 -6
- package/package.json +1 -1
- package/preview/src/main.ts +2 -1
package/dist/bin.js
CHANGED
|
@@ -380,6 +380,7 @@ function validateScene(ir) {
|
|
|
380
380
|
if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
|
|
381
381
|
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
382
382
|
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
383
|
+
if (typeof props.fit === "string" && !IMAGE_FITS.has(props.fit)) problems.push(`node "${node.id}": unknown fit "${props.fit}" \u2014 use ${[...IMAGE_FITS].join(", ")}`);
|
|
383
384
|
if (node.type === "group") {
|
|
384
385
|
const clip = node.props.clip;
|
|
385
386
|
if (clip) {
|
|
@@ -594,7 +595,7 @@ function validateComposition(comp) {
|
|
|
594
595
|
}
|
|
595
596
|
if (problems.length > 0) throw new SceneValidationError(problems);
|
|
596
597
|
}
|
|
597
|
-
var FX_PROPS, BLEND_MODES, COMMON_PROPS, CAMERA_PROPS, PROPS_BY_TYPE, SceneValidationError, TRANSITIONS;
|
|
598
|
+
var FX_PROPS, BLEND_MODES, IMAGE_FITS, COMMON_PROPS, CAMERA_PROPS, PROPS_BY_TYPE, SceneValidationError, TRANSITIONS;
|
|
598
599
|
var init_validate = __esm({
|
|
599
600
|
"../core/src/validate.ts"() {
|
|
600
601
|
"use strict";
|
|
@@ -612,6 +613,7 @@ var init_validate = __esm({
|
|
|
612
613
|
"hard-light",
|
|
613
614
|
"difference"
|
|
614
615
|
]);
|
|
616
|
+
IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
|
|
615
617
|
COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
|
|
616
618
|
CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
|
|
617
619
|
PROPS_BY_TYPE = {
|
|
@@ -619,7 +621,8 @@ var init_validate = __esm({
|
|
|
619
621
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
620
622
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
621
623
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
622
|
-
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
624
|
+
image: [...COMMON_PROPS, "src", "width", "height", "fit"],
|
|
625
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart"],
|
|
623
626
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
624
627
|
group: COMMON_PROPS
|
|
625
628
|
};
|
|
@@ -1361,13 +1364,13 @@ var init_evaluate = __esm({
|
|
|
1361
1364
|
});
|
|
1362
1365
|
|
|
1363
1366
|
// ../core/src/assets.ts
|
|
1364
|
-
function
|
|
1367
|
+
function collectSrcs(ir, type) {
|
|
1365
1368
|
const srcs = /* @__PURE__ */ new Set();
|
|
1366
|
-
const
|
|
1369
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1367
1370
|
const walkNodes = (nodes) => {
|
|
1368
1371
|
for (const node of nodes) {
|
|
1369
|
-
if (node.type ===
|
|
1370
|
-
|
|
1372
|
+
if (node.type === type) {
|
|
1373
|
+
ids.add(node.id);
|
|
1371
1374
|
srcs.add(node.props.src);
|
|
1372
1375
|
}
|
|
1373
1376
|
if (node.type === "group") walkNodes(node.children);
|
|
@@ -1376,14 +1379,14 @@ function collectImageSrcs(ir) {
|
|
|
1376
1379
|
walkNodes(ir.nodes);
|
|
1377
1380
|
for (const overrides of Object.values(ir.states ?? {})) {
|
|
1378
1381
|
for (const [nodeId, props] of Object.entries(overrides)) {
|
|
1379
|
-
if (
|
|
1382
|
+
if (ids.has(nodeId) && typeof props.src === "string") srcs.add(props.src);
|
|
1380
1383
|
}
|
|
1381
1384
|
}
|
|
1382
1385
|
const walkTimeline = (step) => {
|
|
1383
1386
|
if (!step) return;
|
|
1384
1387
|
if (step.kind === "seq" || step.kind === "par" || step.kind === "stagger") {
|
|
1385
1388
|
for (const child of step.children) walkTimeline(child);
|
|
1386
|
-
} else if (step.kind === "tween" &&
|
|
1389
|
+
} else if (step.kind === "tween" && ids.has(step.target)) {
|
|
1387
1390
|
const src = step.props.src;
|
|
1388
1391
|
if (typeof src === "string") srcs.add(src);
|
|
1389
1392
|
}
|
|
@@ -1391,6 +1394,12 @@ function collectImageSrcs(ir) {
|
|
|
1391
1394
|
walkTimeline(ir.timeline);
|
|
1392
1395
|
return [...srcs];
|
|
1393
1396
|
}
|
|
1397
|
+
function collectImageSrcs(ir) {
|
|
1398
|
+
return collectSrcs(ir, "image");
|
|
1399
|
+
}
|
|
1400
|
+
function collectVideoSrcs(ir) {
|
|
1401
|
+
return collectSrcs(ir, "video");
|
|
1402
|
+
}
|
|
1394
1403
|
var init_assets = __esm({
|
|
1395
1404
|
"../core/src/assets.ts"() {
|
|
1396
1405
|
"use strict";
|
|
@@ -1955,12 +1964,12 @@ async function encodeMp4(framesDir, fps, outFile) {
|
|
|
1955
1964
|
"+faststart",
|
|
1956
1965
|
outFile
|
|
1957
1966
|
];
|
|
1958
|
-
await new Promise((
|
|
1967
|
+
await new Promise((resolve6, reject) => {
|
|
1959
1968
|
const proc = spawn2("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
1960
1969
|
let stderr = "";
|
|
1961
1970
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
1962
1971
|
proc.on("close", (code) => {
|
|
1963
|
-
if (code === 0)
|
|
1972
|
+
if (code === 0) resolve6();
|
|
1964
1973
|
else reject(new Error(`ffmpeg exited with ${code}:
|
|
1965
1974
|
${stderr.slice(-2e3)}`));
|
|
1966
1975
|
});
|
|
@@ -2042,6 +2051,103 @@ var init_images = __esm({
|
|
|
2042
2051
|
}
|
|
2043
2052
|
});
|
|
2044
2053
|
|
|
2054
|
+
// ../render-cli/src/videos.ts
|
|
2055
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
2056
|
+
import { mkdtemp as mkdtemp2, readFile as readFile4, readdir, rm as rm2 } from "node:fs/promises";
|
|
2057
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
2058
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
2059
|
+
import { extname as extname2, isAbsolute as isAbsolute3, join as join4, resolve as resolve3 } from "node:path";
|
|
2060
|
+
function runFfmpeg(args) {
|
|
2061
|
+
return new Promise((resolve6, reject) => {
|
|
2062
|
+
const proc = spawn3("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
2063
|
+
let stderr = "";
|
|
2064
|
+
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
2065
|
+
proc.on(
|
|
2066
|
+
"close",
|
|
2067
|
+
(code) => code === 0 ? resolve6() : reject(new Error(`ffmpeg exited ${code}:
|
|
2068
|
+
${stderr.slice(-2e3)}`))
|
|
2069
|
+
);
|
|
2070
|
+
proc.on("error", reject);
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
function neededSeconds(node, duration) {
|
|
2074
|
+
const start = node.props.start ?? 0;
|
|
2075
|
+
const rate = node.props.rate ?? 1;
|
|
2076
|
+
const clipStart = node.props.clipStart ?? 0;
|
|
2077
|
+
return clipStart + Math.max(0, duration - start) * Math.max(0, rate) + 1 / 30;
|
|
2078
|
+
}
|
|
2079
|
+
function videoNodes(ir) {
|
|
2080
|
+
const out = [];
|
|
2081
|
+
const walk = (nodes) => {
|
|
2082
|
+
for (const n of nodes) {
|
|
2083
|
+
if (n.type === "video") out.push(n);
|
|
2084
|
+
if (n.type === "group") walk(n.children);
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
walk(ir.nodes);
|
|
2088
|
+
return out;
|
|
2089
|
+
}
|
|
2090
|
+
async function buildVideoFrameAssets(ir, sceneDir, fps, duration) {
|
|
2091
|
+
const srcs = collectVideoSrcs(ir);
|
|
2092
|
+
if (srcs.length === 0) return {};
|
|
2093
|
+
const nodes = videoNodes(ir);
|
|
2094
|
+
const reachBySrc = /* @__PURE__ */ new Map();
|
|
2095
|
+
for (const n of nodes) {
|
|
2096
|
+
const reach = neededSeconds(n, duration);
|
|
2097
|
+
reachBySrc.set(n.props.src, Math.max(reachBySrc.get(n.props.src) ?? 0, reach));
|
|
2098
|
+
}
|
|
2099
|
+
const assets = {};
|
|
2100
|
+
for (const src of srcs) {
|
|
2101
|
+
if (!VIDEO_EXT.has(extname2(src).toLowerCase())) {
|
|
2102
|
+
throw new Error(
|
|
2103
|
+
`video "${src}": unsupported format "${extname2(src)}" \u2014 supported: ${[...VIDEO_EXT].join(" ")}`
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
2106
|
+
const candidates = [isAbsolute3(src) ? src : null, resolve3(sceneDir, src)].filter(
|
|
2107
|
+
(c) => c !== null
|
|
2108
|
+
);
|
|
2109
|
+
const found = candidates.find((c) => existsSync4(c));
|
|
2110
|
+
if (!found) throw new Error(`video "${src}" not found (tried: ${candidates.join(", ")})`);
|
|
2111
|
+
const dir = await mkdtemp2(join4(tmpdir3(), "reframe-vframes-"));
|
|
2112
|
+
try {
|
|
2113
|
+
const seconds = Math.max(1 / fps, reachBySrc.get(src) ?? duration);
|
|
2114
|
+
await runFfmpeg([
|
|
2115
|
+
"-y",
|
|
2116
|
+
"-i",
|
|
2117
|
+
found,
|
|
2118
|
+
"-t",
|
|
2119
|
+
seconds.toFixed(3),
|
|
2120
|
+
"-vf",
|
|
2121
|
+
`fps=${fps},scale='min(iw,1280)':-2`,
|
|
2122
|
+
"-q:v",
|
|
2123
|
+
"4",
|
|
2124
|
+
join4(dir, "%05d.jpg")
|
|
2125
|
+
]);
|
|
2126
|
+
const files = (await readdir(dir)).filter((f) => f.endsWith(".jpg")).sort();
|
|
2127
|
+
assets[src] = await Promise.all(
|
|
2128
|
+
files.map(async (f) => `data:image/jpeg;base64,${(await readFile4(join4(dir, f))).toString("base64")}`)
|
|
2129
|
+
);
|
|
2130
|
+
if (assets[src].length === 0) throw new Error(`video "${src}": ffmpeg extracted no frames`);
|
|
2131
|
+
} finally {
|
|
2132
|
+
await rm2(dir, { recursive: true, force: true });
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
return assets;
|
|
2136
|
+
}
|
|
2137
|
+
function resolveTiming(ir, opts) {
|
|
2138
|
+
const fps = opts.fps ?? ir.fps ?? 30;
|
|
2139
|
+
const duration = opts.duration ?? compileScene(ir).duration;
|
|
2140
|
+
return { fps, duration };
|
|
2141
|
+
}
|
|
2142
|
+
var VIDEO_EXT;
|
|
2143
|
+
var init_videos = __esm({
|
|
2144
|
+
"../render-cli/src/videos.ts"() {
|
|
2145
|
+
"use strict";
|
|
2146
|
+
init_src();
|
|
2147
|
+
VIDEO_EXT = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".m4v", ".mkv"]);
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
|
|
2045
2151
|
// ../render-cli/src/vclock.ts
|
|
2046
2152
|
var VCLOCK_SOURCE;
|
|
2047
2153
|
var init_vclock = __esm({
|
|
@@ -2122,7 +2228,7 @@ var init_reframeGlobal = __esm({
|
|
|
2122
2228
|
|
|
2123
2229
|
// ../render-cli/src/frameLoop.ts
|
|
2124
2230
|
import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
|
|
2125
|
-
import { join as
|
|
2231
|
+
import { join as join5, dirname as dirname4 } from "node:path";
|
|
2126
2232
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
2127
2233
|
import { build } from "esbuild";
|
|
2128
2234
|
import { chromium } from "playwright";
|
|
@@ -2147,14 +2253,14 @@ async function withPage(size, fn) {
|
|
|
2147
2253
|
async function browserBundle() {
|
|
2148
2254
|
if (bundleCache) return bundleCache;
|
|
2149
2255
|
if (true) {
|
|
2150
|
-
const { readFile:
|
|
2151
|
-
bundleCache = await
|
|
2152
|
-
|
|
2256
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
2257
|
+
bundleCache = await readFile7(
|
|
2258
|
+
join5(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
|
|
2153
2259
|
"utf8"
|
|
2154
2260
|
);
|
|
2155
2261
|
return bundleCache;
|
|
2156
2262
|
}
|
|
2157
|
-
const entry =
|
|
2263
|
+
const entry = join5(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.ts");
|
|
2158
2264
|
const result = await build({
|
|
2159
2265
|
entryPoints: [entry],
|
|
2160
2266
|
bundle: true,
|
|
@@ -2167,7 +2273,10 @@ async function browserBundle() {
|
|
|
2167
2273
|
}
|
|
2168
2274
|
async function captureIr(ir, opts) {
|
|
2169
2275
|
await mkdir2(opts.framesDir, { recursive: true });
|
|
2170
|
-
const
|
|
2276
|
+
const sceneDir = opts.sceneDir ?? process.cwd();
|
|
2277
|
+
const assets = await buildImageAssets(ir, sceneDir);
|
|
2278
|
+
const { fps, duration } = resolveTiming(ir, opts);
|
|
2279
|
+
const videoAssets = await buildVideoFrameAssets(ir, sceneDir, fps, duration);
|
|
2171
2280
|
const bundle = await browserBundle();
|
|
2172
2281
|
return withPage(ir.size, async (page) => {
|
|
2173
2282
|
await page.setContent(
|
|
@@ -2175,12 +2284,10 @@ async function captureIr(ir, opts) {
|
|
|
2175
2284
|
);
|
|
2176
2285
|
await injectFonts(page);
|
|
2177
2286
|
await page.addScriptTag({ content: bundle });
|
|
2178
|
-
|
|
2179
|
-
([sceneIr, imageAssets]) => window.__reframe.init(sceneIr, imageAssets),
|
|
2180
|
-
[ir, assets]
|
|
2287
|
+
await page.evaluate(
|
|
2288
|
+
([sceneIr, imageAssets, vAssets]) => window.__reframe.init(sceneIr, imageAssets, vAssets),
|
|
2289
|
+
[ir, assets, videoAssets]
|
|
2181
2290
|
);
|
|
2182
|
-
const fps = opts.fps ?? info.fps;
|
|
2183
|
-
const duration = opts.duration ?? info.duration;
|
|
2184
2291
|
const frameCount = Math.max(1, Math.round(duration * fps));
|
|
2185
2292
|
for (let f = 0; f < frameCount; f++) {
|
|
2186
2293
|
const dataUrl = await page.evaluate((t) => window.__reframe.renderFrame(t), f / fps);
|
|
@@ -2195,9 +2302,10 @@ var init_frameLoop = __esm({
|
|
|
2195
2302
|
"use strict";
|
|
2196
2303
|
init_fonts();
|
|
2197
2304
|
init_images();
|
|
2305
|
+
init_videos();
|
|
2198
2306
|
init_vclock();
|
|
2199
2307
|
init_reframeGlobal();
|
|
2200
|
-
framePath = (dir, i) =>
|
|
2308
|
+
framePath = (dir, i) => join5(dir, `${String(i).padStart(5, "0")}.png`);
|
|
2201
2309
|
bundleCache = null;
|
|
2202
2310
|
}
|
|
2203
2311
|
});
|
|
@@ -2210,9 +2318,9 @@ __export(batch_exports, {
|
|
|
2210
2318
|
parseCsv: () => parseCsv,
|
|
2211
2319
|
runBatch: () => runBatch
|
|
2212
2320
|
});
|
|
2213
|
-
import { mkdir as mkdir3, mkdtemp as
|
|
2214
|
-
import { tmpdir as
|
|
2215
|
-
import { join as
|
|
2321
|
+
import { mkdir as mkdir3, mkdtemp as mkdtemp3, readFile as readFile5, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
|
|
2322
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
2323
|
+
import { join as join6, dirname as dirname5 } from "node:path";
|
|
2216
2324
|
function overlayFromFlat(row, name) {
|
|
2217
2325
|
const doc = { reframeOverlay: 1, name };
|
|
2218
2326
|
for (const [key2, raw] of Object.entries(row)) {
|
|
@@ -2286,7 +2394,7 @@ function parseCsv(text2) {
|
|
|
2286
2394
|
});
|
|
2287
2395
|
}
|
|
2288
2396
|
async function loadRows(path2) {
|
|
2289
|
-
const text2 = await
|
|
2397
|
+
const text2 = await readFile5(path2, "utf8");
|
|
2290
2398
|
if (path2.endsWith(".csv")) return parseCsv(text2);
|
|
2291
2399
|
const parsed = JSON.parse(text2);
|
|
2292
2400
|
if (!Array.isArray(parsed)) throw new Error(`${path2}: expected a JSON array of row objects`);
|
|
@@ -2306,8 +2414,8 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2306
2414
|
try {
|
|
2307
2415
|
const rowOverlay = overlayFromFlat(row, name);
|
|
2308
2416
|
const { ir, report } = composeScene(scene2, ...opts.baseOverlays, rowOverlay);
|
|
2309
|
-
const framesDir = await
|
|
2310
|
-
const output =
|
|
2417
|
+
const framesDir = await mkdtemp3(join6(tmpdir4(), `reframe-batch-${index}-`));
|
|
2418
|
+
const output = join6(opts.outDir, `${name}.mp4`);
|
|
2311
2419
|
const plan = opts.noAudio ? null : resolveAudioPlan(compileScene(ir));
|
|
2312
2420
|
try {
|
|
2313
2421
|
const captured = await captureIr(ir, {
|
|
@@ -2319,12 +2427,12 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2319
2427
|
const videoTmp = `${output}.video.mp4`;
|
|
2320
2428
|
await encodeMp4(captured.framesDir, captured.fps, videoTmp);
|
|
2321
2429
|
await buildAudioTrack(plan, opts.scenePath ?? output, videoTmp, output);
|
|
2322
|
-
await
|
|
2430
|
+
await rm3(videoTmp, { force: true });
|
|
2323
2431
|
} else {
|
|
2324
2432
|
await encodeMp4(captured.framesDir, captured.fps, output);
|
|
2325
2433
|
}
|
|
2326
2434
|
} finally {
|
|
2327
|
-
await
|
|
2435
|
+
await rm3(framesDir, { recursive: true, force: true });
|
|
2328
2436
|
}
|
|
2329
2437
|
result = {
|
|
2330
2438
|
name,
|
|
@@ -2348,7 +2456,7 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2348
2456
|
};
|
|
2349
2457
|
await Promise.all(Array.from({ length: Math.max(1, opts.concurrency) }, worker));
|
|
2350
2458
|
await writeFile4(
|
|
2351
|
-
|
|
2459
|
+
join6(opts.outDir, "batch-report.json"),
|
|
2352
2460
|
JSON.stringify({ rows: results }, null, 2)
|
|
2353
2461
|
);
|
|
2354
2462
|
return results;
|
|
@@ -2373,11 +2481,11 @@ __export(loadScene_exports, {
|
|
|
2373
2481
|
loadScene: () => loadScene
|
|
2374
2482
|
});
|
|
2375
2483
|
import { build as build2 } from "esbuild";
|
|
2376
|
-
import { readFile as
|
|
2377
|
-
import { dirname as dirname6, resolve as
|
|
2484
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
2485
|
+
import { dirname as dirname6, resolve as resolve4 } from "node:path";
|
|
2378
2486
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
2379
2487
|
async function loadDefault(path2) {
|
|
2380
|
-
if (path2.endsWith(".json")) return JSON.parse(await
|
|
2488
|
+
if (path2.endsWith(".json")) return JSON.parse(await readFile6(path2, "utf8"));
|
|
2381
2489
|
let code;
|
|
2382
2490
|
try {
|
|
2383
2491
|
const out = await build2({
|
|
@@ -2427,26 +2535,26 @@ var init_loadScene = __esm({
|
|
|
2427
2535
|
"use strict";
|
|
2428
2536
|
init_src();
|
|
2429
2537
|
HERE = dirname6(fileURLToPath4(import.meta.url));
|
|
2430
|
-
CORE_ENTRY = true ?
|
|
2538
|
+
CORE_ENTRY = true ? resolve4(HERE, "index.js") : resolve4(HERE, "..", "..", "core", "src", "index.ts");
|
|
2431
2539
|
}
|
|
2432
2540
|
});
|
|
2433
2541
|
|
|
2434
2542
|
// ../render-cli/src/reframe.ts
|
|
2435
|
-
import { spawn as
|
|
2436
|
-
import { existsSync as
|
|
2543
|
+
import { spawn as spawn4, spawnSync } from "node:child_process";
|
|
2544
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2437
2545
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
2438
|
-
import { basename, isAbsolute as
|
|
2546
|
+
import { basename, isAbsolute as isAbsolute4, join as join7, resolve as resolve5 } from "node:path";
|
|
2439
2547
|
import { dirname as dirname7 } from "node:path";
|
|
2440
2548
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
2441
2549
|
var PACKAGED = true;
|
|
2442
2550
|
var HERE2 = dirname7(fileURLToPath5(import.meta.url));
|
|
2443
|
-
var ROOT2 = PACKAGED ?
|
|
2551
|
+
var ROOT2 = PACKAGED ? resolve5(HERE2, "..") : resolve5(HERE2, "..", "..", "..");
|
|
2444
2552
|
var USER_CWD = process.env.INIT_CWD ?? process.cwd();
|
|
2445
|
-
var RENDER_CLI = PACKAGED ?
|
|
2446
|
-
var LABELS = PACKAGED ?
|
|
2447
|
-
var PLAYER = PACKAGED ?
|
|
2448
|
-
var ANALYZE = PACKAGED ?
|
|
2449
|
-
var TRACE = PACKAGED ?
|
|
2553
|
+
var RENDER_CLI = PACKAGED ? join7(ROOT2, "dist", "cli.js") : join7(ROOT2, "packages", "render-cli", "src", "cli.ts");
|
|
2554
|
+
var LABELS = PACKAGED ? join7(ROOT2, "dist", "labels.js") : join7(ROOT2, "packages", "render-cli", "src", "labels.ts");
|
|
2555
|
+
var PLAYER = PACKAGED ? join7(ROOT2, "dist", "player.js") : join7(ROOT2, "packages", "render-cli", "src", "player.ts");
|
|
2556
|
+
var ANALYZE = PACKAGED ? join7(ROOT2, "dist", "analyze.js") : join7(ROOT2, "benchmark", "harness", "motion", "analyze.ts");
|
|
2557
|
+
var TRACE = PACKAGED ? join7(ROOT2, "dist", "trace-cli.js") : join7(ROOT2, "benchmark", "harness", "motion", "trace-cli.ts");
|
|
2450
2558
|
var CMD = PACKAGED ? "reframe" : "pnpm reframe";
|
|
2451
2559
|
var USAGE = `reframe \u2014 declarative motion graphics
|
|
2452
2560
|
|
|
@@ -2466,7 +2574,7 @@ usage:
|
|
|
2466
2574
|
${CMD} guide [--regen] print the scene-authoring guide (for you or your AI)
|
|
2467
2575
|
${CMD} demo run the edit-survival demo (3 mp4s into out/)
|
|
2468
2576
|
`;
|
|
2469
|
-
var userPath = (p) =>
|
|
2577
|
+
var userPath = (p) => isAbsolute4(p) ? p : resolve5(USER_CWD, p);
|
|
2470
2578
|
function fail(message) {
|
|
2471
2579
|
console.error(`error: ${message}`);
|
|
2472
2580
|
process.exit(2);
|
|
@@ -2478,7 +2586,7 @@ function preflightFfmpeg() {
|
|
|
2478
2586
|
}
|
|
2479
2587
|
function run(cmd, args, opts = {}) {
|
|
2480
2588
|
return new Promise((res) => {
|
|
2481
|
-
const proc =
|
|
2589
|
+
const proc = spawn4(cmd, args, {
|
|
2482
2590
|
cwd: opts.cwd ?? (PACKAGED ? USER_CWD : ROOT2),
|
|
2483
2591
|
stdio: ["inherit", "inherit", "pipe"],
|
|
2484
2592
|
...opts.env && { env: { ...process.env, ...opts.env } }
|
|
@@ -2568,7 +2676,7 @@ async function main() {
|
|
|
2568
2676
|
|
|
2569
2677
|
${USAGE}`);
|
|
2570
2678
|
const inputPath = userPath(input);
|
|
2571
|
-
if (!
|
|
2679
|
+
if (!existsSync5(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2572
2680
|
const mode = /\.(ts|json)$/.test(input) ? "ir" : /\.html$/.test(input) ? "html" : null;
|
|
2573
2681
|
if (!mode) {
|
|
2574
2682
|
fail(`cannot infer render mode from "${input}" \u2014 expected .ts/.json (reframe scene) or .html (GSAP page)`);
|
|
@@ -2578,11 +2686,11 @@ ${USAGE}`);
|
|
|
2578
2686
|
fail("html render requires --duration <seconds> (the page does not declare its own length)");
|
|
2579
2687
|
}
|
|
2580
2688
|
preflightFfmpeg();
|
|
2581
|
-
const outBase = PACKAGED ?
|
|
2689
|
+
const outBase = PACKAGED ? join7(USER_CWD, "out") : join7(ROOT2, "out");
|
|
2582
2690
|
let outArgs = args;
|
|
2583
2691
|
if (!args.includes("-o")) {
|
|
2584
2692
|
await mkdir4(outBase, { recursive: true });
|
|
2585
|
-
outArgs = [...args, "-o",
|
|
2693
|
+
outArgs = [...args, "-o", join7(outBase, `${basename(input).replace(/\.[^.]+$/, "")}.mp4`)];
|
|
2586
2694
|
}
|
|
2587
2695
|
outArgs = outArgs.map(
|
|
2588
2696
|
(a, i) => outArgs[i - 1] === "--overlay" || outArgs[i - 1] === "-o" ? userPath(a) : a
|
|
@@ -2597,7 +2705,7 @@ ${USAGE}`);
|
|
|
2597
2705
|
|
|
2598
2706
|
${USAGE}`);
|
|
2599
2707
|
const inputPath = userPath(input);
|
|
2600
|
-
if (!
|
|
2708
|
+
if (!existsSync5(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2601
2709
|
process.exit(
|
|
2602
2710
|
await (PACKAGED ? run(process.execPath, [LABELS, inputPath]) : run("npx", ["tsx", LABELS, inputPath]))
|
|
2603
2711
|
);
|
|
@@ -2608,10 +2716,10 @@ ${USAGE}`);
|
|
|
2608
2716
|
|
|
2609
2717
|
${USAGE}`);
|
|
2610
2718
|
const inputPath = userPath(input);
|
|
2611
|
-
if (!
|
|
2719
|
+
if (!existsSync5(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2612
2720
|
const oIdx = rest.indexOf("-o");
|
|
2613
|
-
const outBase = PACKAGED ?
|
|
2614
|
-
const outPath = oIdx >= 0 && rest[oIdx + 1] ? userPath(rest[oIdx + 1]) :
|
|
2721
|
+
const outBase = PACKAGED ? join7(USER_CWD, "out") : join7(ROOT2, "out");
|
|
2722
|
+
const outPath = oIdx >= 0 && rest[oIdx + 1] ? userPath(rest[oIdx + 1]) : join7(outBase, `${basename(input).replace(/\.[^.]+$/, "")}.html`);
|
|
2615
2723
|
await mkdir4(dirname7(outPath), { recursive: true });
|
|
2616
2724
|
process.exit(
|
|
2617
2725
|
await (PACKAGED ? run(process.execPath, [PLAYER, inputPath, outPath]) : run("npx", ["tsx", PLAYER, inputPath, outPath]))
|
|
@@ -2631,7 +2739,7 @@ ${USAGE}`);
|
|
|
2631
2739
|
fail(`usage: ${CMD} logo <logo.svg | brand-slug> ["Display Name"] [--motion <preset>] [--energy 0..1] [--speed n] [--intensity 0..1] [--from left|right|top|bottom] [--seed n] [-o out.mp4]`);
|
|
2632
2740
|
}
|
|
2633
2741
|
preflightFfmpeg();
|
|
2634
|
-
const { tmpdir:
|
|
2742
|
+
const { tmpdir: tmpdir5 } = await import("node:os");
|
|
2635
2743
|
const { resolveLogo: resolveLogo2, buildLogoSting: buildLogoSting2 } = await Promise.resolve().then(() => (init_logoSting(), logoSting_exports));
|
|
2636
2744
|
const num = (k) => flags[k] !== void 0 ? Number(flags[k]) : void 0;
|
|
2637
2745
|
console.log(`loading logo: ${arg} \u2026`);
|
|
@@ -2644,10 +2752,10 @@ ${USAGE}`);
|
|
|
2644
2752
|
seed: num("seed")
|
|
2645
2753
|
});
|
|
2646
2754
|
const sceneIR = buildLogoSting2(data);
|
|
2647
|
-
const tmp =
|
|
2755
|
+
const tmp = join7(tmpdir5(), `reframe-logo-${slug}-${process.pid}.json`);
|
|
2648
2756
|
await writeFile5(tmp, JSON.stringify(sceneIR));
|
|
2649
|
-
const outBase = PACKAGED ?
|
|
2650
|
-
const out = flags.o ? userPath(flags.o) :
|
|
2757
|
+
const outBase = PACKAGED ? join7(USER_CWD, "out") : join7(ROOT2, "out");
|
|
2758
|
+
const out = flags.o ? userPath(flags.o) : join7(outBase, `logo-${slug}.mp4`);
|
|
2651
2759
|
await mkdir4(dirname7(out), { recursive: true });
|
|
2652
2760
|
console.log(`rendering ${data.name} (${data.paths.length} path${data.paths.length > 1 ? "s" : ""}, motion: ${data.motion ?? "reveal-orbit"}) \u2192 ${out}`);
|
|
2653
2761
|
process.exit(
|
|
@@ -2659,9 +2767,9 @@ ${USAGE}`);
|
|
|
2659
2767
|
if (!sceneArg || !dataArg) fail(`usage: ${CMD} batch <scene.ts> <data.json|csv> [...]`);
|
|
2660
2768
|
const scenePath = userPath(sceneArg);
|
|
2661
2769
|
const dataPath = userPath(dataArg);
|
|
2662
|
-
for (const p of [scenePath, dataPath]) if (!
|
|
2770
|
+
for (const p of [scenePath, dataPath]) if (!existsSync5(p)) fail(`no such file: ${p}`);
|
|
2663
2771
|
preflightFfmpeg();
|
|
2664
|
-
let outDir = PACKAGED ?
|
|
2772
|
+
let outDir = PACKAGED ? join7(USER_CWD, "out", "batch") : join7(ROOT2, "out", "batch");
|
|
2665
2773
|
let concurrency = 3;
|
|
2666
2774
|
let fps;
|
|
2667
2775
|
const baseOverlayPaths = [];
|
|
@@ -2674,10 +2782,10 @@ ${USAGE}`);
|
|
|
2674
2782
|
}
|
|
2675
2783
|
const { loadRows: loadRows2, runBatch: runBatch2 } = await Promise.resolve().then(() => (init_batch(), batch_exports));
|
|
2676
2784
|
const { loadScene: loadScene2 } = await Promise.resolve().then(() => (init_loadScene(), loadScene_exports));
|
|
2677
|
-
const { readFile:
|
|
2785
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
2678
2786
|
const scene2 = await loadScene2(scenePath);
|
|
2679
2787
|
const baseOverlays = await Promise.all(
|
|
2680
|
-
baseOverlayPaths.map(async (p) => JSON.parse(await
|
|
2788
|
+
baseOverlayPaths.map(async (p) => JSON.parse(await readFile7(p, "utf8")))
|
|
2681
2789
|
);
|
|
2682
2790
|
const rows = await loadRows2(dataPath);
|
|
2683
2791
|
if (rows.length === 0) fail(`${dataPath}: no data rows`);
|
|
@@ -2700,7 +2808,7 @@ ${USAGE}`);
|
|
|
2700
2808
|
const orphaned = results.filter((r) => !r.error && r.orphans.length > 0).length;
|
|
2701
2809
|
console.log(
|
|
2702
2810
|
`
|
|
2703
|
-
${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed \u2014 report: ${
|
|
2811
|
+
${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed \u2014 report: ${join7(outDir, "batch-report.json")}`
|
|
2704
2812
|
);
|
|
2705
2813
|
process.exit(failed > 0 ? 1 : 0);
|
|
2706
2814
|
}
|
|
@@ -2711,9 +2819,9 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2711
2819
|
if (PACKAGED) {
|
|
2712
2820
|
const { createRequire } = await import("node:module");
|
|
2713
2821
|
const vitePkg = createRequire(import.meta.url).resolve("vite/package.json");
|
|
2714
|
-
const viteBin =
|
|
2822
|
+
const viteBin = join7(dirname7(vitePkg), "bin", "vite.js");
|
|
2715
2823
|
process.exit(
|
|
2716
|
-
await run(process.execPath, [viteBin,
|
|
2824
|
+
await run(process.execPath, [viteBin, join7(ROOT2, "preview")], {
|
|
2717
2825
|
env: { REFRAME_SCENE_DIR: USER_CWD }
|
|
2718
2826
|
})
|
|
2719
2827
|
);
|
|
@@ -2731,10 +2839,10 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2731
2839
|
fail(`scene name must be kebab-case (a-z, 0-9, -): got "${name}"`);
|
|
2732
2840
|
}
|
|
2733
2841
|
const inRepo = USER_CWD === ROOT2 || USER_CWD.startsWith(ROOT2 + "/");
|
|
2734
|
-
const targetDir = inRepo ?
|
|
2735
|
-
const target =
|
|
2842
|
+
const targetDir = inRepo ? join7(ROOT2, "examples", "scenes") : USER_CWD;
|
|
2843
|
+
const target = join7(targetDir, `${name}.ts`);
|
|
2736
2844
|
const shown = inRepo ? `examples/scenes/${name}.ts` : `${name}.ts`;
|
|
2737
|
-
if (
|
|
2845
|
+
if (existsSync5(target)) fail(`${shown} already exists`);
|
|
2738
2846
|
const id = name.split("-")[0] ?? name;
|
|
2739
2847
|
await writeFile5(target, SCENE_TEMPLATE(name, id));
|
|
2740
2848
|
console.log(`created ${shown}
|
|
@@ -2763,9 +2871,9 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2763
2871
|
);
|
|
2764
2872
|
}
|
|
2765
2873
|
case "guide": {
|
|
2766
|
-
const file = rest.includes("--regen") ? PACKAGED ?
|
|
2767
|
-
const { readFile:
|
|
2768
|
-
process.stdout.write(await
|
|
2874
|
+
const file = rest.includes("--regen") ? PACKAGED ? join7(ROOT2, "guides", "regen-contract.md") : join7(ROOT2, "docs", "regen-contract.md") : PACKAGED ? join7(ROOT2, "guides", "edsl-guide.md") : join7(ROOT2, "benchmark", "guides", "edsl-guide.md");
|
|
2875
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
2876
|
+
process.stdout.write(await readFile7(file, "utf8"));
|
|
2769
2877
|
return;
|
|
2770
2878
|
}
|
|
2771
2879
|
case "demo":
|
|
@@ -2776,7 +2884,7 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2776
2884
|
}
|
|
2777
2885
|
preflightFfmpeg();
|
|
2778
2886
|
process.exit(
|
|
2779
|
-
await run("npx", ["tsx",
|
|
2887
|
+
await run("npx", ["tsx", join7(ROOT2, "examples", "scripts", "demo-edit-survival.ts")])
|
|
2780
2888
|
);
|
|
2781
2889
|
default:
|
|
2782
2890
|
console.log(USAGE);
|