reframe-video 0.6.5 → 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 +175 -69
- package/dist/browserEntry.js +82 -35
- package/dist/cli.js +151 -53
- package/dist/index.js +46 -6
- package/dist/labels.js +1 -0
- package/dist/renderer-canvas.js +31 -25
- package/dist/trace-cli.js +1 -0
- package/dist/types/assets.d.ts +3 -1
- package/dist/types/dsl.d.ts +4 -1
- package/dist/types/evaluate.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/ir.d.ts +21 -0
- package/guides/edsl-guide.md +21 -0
- package/package.json +1 -1
- package/preview/src/main.ts +2 -1
package/dist/bin.js
CHANGED
|
@@ -622,6 +622,7 @@ var init_validate = __esm({
|
|
|
622
622
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
|
|
623
623
|
text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
624
624
|
image: [...COMMON_PROPS, "src", "width", "height", "fit"],
|
|
625
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart"],
|
|
625
626
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
626
627
|
group: COMMON_PROPS
|
|
627
628
|
};
|
|
@@ -1363,13 +1364,13 @@ var init_evaluate = __esm({
|
|
|
1363
1364
|
});
|
|
1364
1365
|
|
|
1365
1366
|
// ../core/src/assets.ts
|
|
1366
|
-
function
|
|
1367
|
+
function collectSrcs(ir, type) {
|
|
1367
1368
|
const srcs = /* @__PURE__ */ new Set();
|
|
1368
|
-
const
|
|
1369
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1369
1370
|
const walkNodes = (nodes) => {
|
|
1370
1371
|
for (const node of nodes) {
|
|
1371
|
-
if (node.type ===
|
|
1372
|
-
|
|
1372
|
+
if (node.type === type) {
|
|
1373
|
+
ids.add(node.id);
|
|
1373
1374
|
srcs.add(node.props.src);
|
|
1374
1375
|
}
|
|
1375
1376
|
if (node.type === "group") walkNodes(node.children);
|
|
@@ -1378,14 +1379,14 @@ function collectImageSrcs(ir) {
|
|
|
1378
1379
|
walkNodes(ir.nodes);
|
|
1379
1380
|
for (const overrides of Object.values(ir.states ?? {})) {
|
|
1380
1381
|
for (const [nodeId, props] of Object.entries(overrides)) {
|
|
1381
|
-
if (
|
|
1382
|
+
if (ids.has(nodeId) && typeof props.src === "string") srcs.add(props.src);
|
|
1382
1383
|
}
|
|
1383
1384
|
}
|
|
1384
1385
|
const walkTimeline = (step) => {
|
|
1385
1386
|
if (!step) return;
|
|
1386
1387
|
if (step.kind === "seq" || step.kind === "par" || step.kind === "stagger") {
|
|
1387
1388
|
for (const child of step.children) walkTimeline(child);
|
|
1388
|
-
} else if (step.kind === "tween" &&
|
|
1389
|
+
} else if (step.kind === "tween" && ids.has(step.target)) {
|
|
1389
1390
|
const src = step.props.src;
|
|
1390
1391
|
if (typeof src === "string") srcs.add(src);
|
|
1391
1392
|
}
|
|
@@ -1393,6 +1394,12 @@ function collectImageSrcs(ir) {
|
|
|
1393
1394
|
walkTimeline(ir.timeline);
|
|
1394
1395
|
return [...srcs];
|
|
1395
1396
|
}
|
|
1397
|
+
function collectImageSrcs(ir) {
|
|
1398
|
+
return collectSrcs(ir, "image");
|
|
1399
|
+
}
|
|
1400
|
+
function collectVideoSrcs(ir) {
|
|
1401
|
+
return collectSrcs(ir, "video");
|
|
1402
|
+
}
|
|
1396
1403
|
var init_assets = __esm({
|
|
1397
1404
|
"../core/src/assets.ts"() {
|
|
1398
1405
|
"use strict";
|
|
@@ -1957,12 +1964,12 @@ async function encodeMp4(framesDir, fps, outFile) {
|
|
|
1957
1964
|
"+faststart",
|
|
1958
1965
|
outFile
|
|
1959
1966
|
];
|
|
1960
|
-
await new Promise((
|
|
1967
|
+
await new Promise((resolve6, reject) => {
|
|
1961
1968
|
const proc = spawn2("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
1962
1969
|
let stderr = "";
|
|
1963
1970
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
1964
1971
|
proc.on("close", (code) => {
|
|
1965
|
-
if (code === 0)
|
|
1972
|
+
if (code === 0) resolve6();
|
|
1966
1973
|
else reject(new Error(`ffmpeg exited with ${code}:
|
|
1967
1974
|
${stderr.slice(-2e3)}`));
|
|
1968
1975
|
});
|
|
@@ -2044,6 +2051,103 @@ var init_images = __esm({
|
|
|
2044
2051
|
}
|
|
2045
2052
|
});
|
|
2046
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
|
+
|
|
2047
2151
|
// ../render-cli/src/vclock.ts
|
|
2048
2152
|
var VCLOCK_SOURCE;
|
|
2049
2153
|
var init_vclock = __esm({
|
|
@@ -2124,7 +2228,7 @@ var init_reframeGlobal = __esm({
|
|
|
2124
2228
|
|
|
2125
2229
|
// ../render-cli/src/frameLoop.ts
|
|
2126
2230
|
import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
|
|
2127
|
-
import { join as
|
|
2231
|
+
import { join as join5, dirname as dirname4 } from "node:path";
|
|
2128
2232
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
2129
2233
|
import { build } from "esbuild";
|
|
2130
2234
|
import { chromium } from "playwright";
|
|
@@ -2149,14 +2253,14 @@ async function withPage(size, fn) {
|
|
|
2149
2253
|
async function browserBundle() {
|
|
2150
2254
|
if (bundleCache) return bundleCache;
|
|
2151
2255
|
if (true) {
|
|
2152
|
-
const { readFile:
|
|
2153
|
-
bundleCache = await
|
|
2154
|
-
|
|
2256
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
2257
|
+
bundleCache = await readFile7(
|
|
2258
|
+
join5(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
|
|
2155
2259
|
"utf8"
|
|
2156
2260
|
);
|
|
2157
2261
|
return bundleCache;
|
|
2158
2262
|
}
|
|
2159
|
-
const entry =
|
|
2263
|
+
const entry = join5(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.ts");
|
|
2160
2264
|
const result = await build({
|
|
2161
2265
|
entryPoints: [entry],
|
|
2162
2266
|
bundle: true,
|
|
@@ -2169,7 +2273,10 @@ async function browserBundle() {
|
|
|
2169
2273
|
}
|
|
2170
2274
|
async function captureIr(ir, opts) {
|
|
2171
2275
|
await mkdir2(opts.framesDir, { recursive: true });
|
|
2172
|
-
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);
|
|
2173
2280
|
const bundle = await browserBundle();
|
|
2174
2281
|
return withPage(ir.size, async (page) => {
|
|
2175
2282
|
await page.setContent(
|
|
@@ -2177,12 +2284,10 @@ async function captureIr(ir, opts) {
|
|
|
2177
2284
|
);
|
|
2178
2285
|
await injectFonts(page);
|
|
2179
2286
|
await page.addScriptTag({ content: bundle });
|
|
2180
|
-
|
|
2181
|
-
([sceneIr, imageAssets]) => window.__reframe.init(sceneIr, imageAssets),
|
|
2182
|
-
[ir, assets]
|
|
2287
|
+
await page.evaluate(
|
|
2288
|
+
([sceneIr, imageAssets, vAssets]) => window.__reframe.init(sceneIr, imageAssets, vAssets),
|
|
2289
|
+
[ir, assets, videoAssets]
|
|
2183
2290
|
);
|
|
2184
|
-
const fps = opts.fps ?? info.fps;
|
|
2185
|
-
const duration = opts.duration ?? info.duration;
|
|
2186
2291
|
const frameCount = Math.max(1, Math.round(duration * fps));
|
|
2187
2292
|
for (let f = 0; f < frameCount; f++) {
|
|
2188
2293
|
const dataUrl = await page.evaluate((t) => window.__reframe.renderFrame(t), f / fps);
|
|
@@ -2197,9 +2302,10 @@ var init_frameLoop = __esm({
|
|
|
2197
2302
|
"use strict";
|
|
2198
2303
|
init_fonts();
|
|
2199
2304
|
init_images();
|
|
2305
|
+
init_videos();
|
|
2200
2306
|
init_vclock();
|
|
2201
2307
|
init_reframeGlobal();
|
|
2202
|
-
framePath = (dir, i) =>
|
|
2308
|
+
framePath = (dir, i) => join5(dir, `${String(i).padStart(5, "0")}.png`);
|
|
2203
2309
|
bundleCache = null;
|
|
2204
2310
|
}
|
|
2205
2311
|
});
|
|
@@ -2212,9 +2318,9 @@ __export(batch_exports, {
|
|
|
2212
2318
|
parseCsv: () => parseCsv,
|
|
2213
2319
|
runBatch: () => runBatch
|
|
2214
2320
|
});
|
|
2215
|
-
import { mkdir as mkdir3, mkdtemp as
|
|
2216
|
-
import { tmpdir as
|
|
2217
|
-
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";
|
|
2218
2324
|
function overlayFromFlat(row, name) {
|
|
2219
2325
|
const doc = { reframeOverlay: 1, name };
|
|
2220
2326
|
for (const [key2, raw] of Object.entries(row)) {
|
|
@@ -2288,7 +2394,7 @@ function parseCsv(text2) {
|
|
|
2288
2394
|
});
|
|
2289
2395
|
}
|
|
2290
2396
|
async function loadRows(path2) {
|
|
2291
|
-
const text2 = await
|
|
2397
|
+
const text2 = await readFile5(path2, "utf8");
|
|
2292
2398
|
if (path2.endsWith(".csv")) return parseCsv(text2);
|
|
2293
2399
|
const parsed = JSON.parse(text2);
|
|
2294
2400
|
if (!Array.isArray(parsed)) throw new Error(`${path2}: expected a JSON array of row objects`);
|
|
@@ -2308,8 +2414,8 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2308
2414
|
try {
|
|
2309
2415
|
const rowOverlay = overlayFromFlat(row, name);
|
|
2310
2416
|
const { ir, report } = composeScene(scene2, ...opts.baseOverlays, rowOverlay);
|
|
2311
|
-
const framesDir = await
|
|
2312
|
-
const output =
|
|
2417
|
+
const framesDir = await mkdtemp3(join6(tmpdir4(), `reframe-batch-${index}-`));
|
|
2418
|
+
const output = join6(opts.outDir, `${name}.mp4`);
|
|
2313
2419
|
const plan = opts.noAudio ? null : resolveAudioPlan(compileScene(ir));
|
|
2314
2420
|
try {
|
|
2315
2421
|
const captured = await captureIr(ir, {
|
|
@@ -2321,12 +2427,12 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2321
2427
|
const videoTmp = `${output}.video.mp4`;
|
|
2322
2428
|
await encodeMp4(captured.framesDir, captured.fps, videoTmp);
|
|
2323
2429
|
await buildAudioTrack(plan, opts.scenePath ?? output, videoTmp, output);
|
|
2324
|
-
await
|
|
2430
|
+
await rm3(videoTmp, { force: true });
|
|
2325
2431
|
} else {
|
|
2326
2432
|
await encodeMp4(captured.framesDir, captured.fps, output);
|
|
2327
2433
|
}
|
|
2328
2434
|
} finally {
|
|
2329
|
-
await
|
|
2435
|
+
await rm3(framesDir, { recursive: true, force: true });
|
|
2330
2436
|
}
|
|
2331
2437
|
result = {
|
|
2332
2438
|
name,
|
|
@@ -2350,7 +2456,7 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2350
2456
|
};
|
|
2351
2457
|
await Promise.all(Array.from({ length: Math.max(1, opts.concurrency) }, worker));
|
|
2352
2458
|
await writeFile4(
|
|
2353
|
-
|
|
2459
|
+
join6(opts.outDir, "batch-report.json"),
|
|
2354
2460
|
JSON.stringify({ rows: results }, null, 2)
|
|
2355
2461
|
);
|
|
2356
2462
|
return results;
|
|
@@ -2375,11 +2481,11 @@ __export(loadScene_exports, {
|
|
|
2375
2481
|
loadScene: () => loadScene
|
|
2376
2482
|
});
|
|
2377
2483
|
import { build as build2 } from "esbuild";
|
|
2378
|
-
import { readFile as
|
|
2379
|
-
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";
|
|
2380
2486
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
2381
2487
|
async function loadDefault(path2) {
|
|
2382
|
-
if (path2.endsWith(".json")) return JSON.parse(await
|
|
2488
|
+
if (path2.endsWith(".json")) return JSON.parse(await readFile6(path2, "utf8"));
|
|
2383
2489
|
let code;
|
|
2384
2490
|
try {
|
|
2385
2491
|
const out = await build2({
|
|
@@ -2429,26 +2535,26 @@ var init_loadScene = __esm({
|
|
|
2429
2535
|
"use strict";
|
|
2430
2536
|
init_src();
|
|
2431
2537
|
HERE = dirname6(fileURLToPath4(import.meta.url));
|
|
2432
|
-
CORE_ENTRY = true ?
|
|
2538
|
+
CORE_ENTRY = true ? resolve4(HERE, "index.js") : resolve4(HERE, "..", "..", "core", "src", "index.ts");
|
|
2433
2539
|
}
|
|
2434
2540
|
});
|
|
2435
2541
|
|
|
2436
2542
|
// ../render-cli/src/reframe.ts
|
|
2437
|
-
import { spawn as
|
|
2438
|
-
import { existsSync as
|
|
2543
|
+
import { spawn as spawn4, spawnSync } from "node:child_process";
|
|
2544
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2439
2545
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
2440
|
-
import { basename, isAbsolute as
|
|
2546
|
+
import { basename, isAbsolute as isAbsolute4, join as join7, resolve as resolve5 } from "node:path";
|
|
2441
2547
|
import { dirname as dirname7 } from "node:path";
|
|
2442
2548
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
2443
2549
|
var PACKAGED = true;
|
|
2444
2550
|
var HERE2 = dirname7(fileURLToPath5(import.meta.url));
|
|
2445
|
-
var ROOT2 = PACKAGED ?
|
|
2551
|
+
var ROOT2 = PACKAGED ? resolve5(HERE2, "..") : resolve5(HERE2, "..", "..", "..");
|
|
2446
2552
|
var USER_CWD = process.env.INIT_CWD ?? process.cwd();
|
|
2447
|
-
var RENDER_CLI = PACKAGED ?
|
|
2448
|
-
var LABELS = PACKAGED ?
|
|
2449
|
-
var PLAYER = PACKAGED ?
|
|
2450
|
-
var ANALYZE = PACKAGED ?
|
|
2451
|
-
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");
|
|
2452
2558
|
var CMD = PACKAGED ? "reframe" : "pnpm reframe";
|
|
2453
2559
|
var USAGE = `reframe \u2014 declarative motion graphics
|
|
2454
2560
|
|
|
@@ -2468,7 +2574,7 @@ usage:
|
|
|
2468
2574
|
${CMD} guide [--regen] print the scene-authoring guide (for you or your AI)
|
|
2469
2575
|
${CMD} demo run the edit-survival demo (3 mp4s into out/)
|
|
2470
2576
|
`;
|
|
2471
|
-
var userPath = (p) =>
|
|
2577
|
+
var userPath = (p) => isAbsolute4(p) ? p : resolve5(USER_CWD, p);
|
|
2472
2578
|
function fail(message) {
|
|
2473
2579
|
console.error(`error: ${message}`);
|
|
2474
2580
|
process.exit(2);
|
|
@@ -2480,7 +2586,7 @@ function preflightFfmpeg() {
|
|
|
2480
2586
|
}
|
|
2481
2587
|
function run(cmd, args, opts = {}) {
|
|
2482
2588
|
return new Promise((res) => {
|
|
2483
|
-
const proc =
|
|
2589
|
+
const proc = spawn4(cmd, args, {
|
|
2484
2590
|
cwd: opts.cwd ?? (PACKAGED ? USER_CWD : ROOT2),
|
|
2485
2591
|
stdio: ["inherit", "inherit", "pipe"],
|
|
2486
2592
|
...opts.env && { env: { ...process.env, ...opts.env } }
|
|
@@ -2570,7 +2676,7 @@ async function main() {
|
|
|
2570
2676
|
|
|
2571
2677
|
${USAGE}`);
|
|
2572
2678
|
const inputPath = userPath(input);
|
|
2573
|
-
if (!
|
|
2679
|
+
if (!existsSync5(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2574
2680
|
const mode = /\.(ts|json)$/.test(input) ? "ir" : /\.html$/.test(input) ? "html" : null;
|
|
2575
2681
|
if (!mode) {
|
|
2576
2682
|
fail(`cannot infer render mode from "${input}" \u2014 expected .ts/.json (reframe scene) or .html (GSAP page)`);
|
|
@@ -2580,11 +2686,11 @@ ${USAGE}`);
|
|
|
2580
2686
|
fail("html render requires --duration <seconds> (the page does not declare its own length)");
|
|
2581
2687
|
}
|
|
2582
2688
|
preflightFfmpeg();
|
|
2583
|
-
const outBase = PACKAGED ?
|
|
2689
|
+
const outBase = PACKAGED ? join7(USER_CWD, "out") : join7(ROOT2, "out");
|
|
2584
2690
|
let outArgs = args;
|
|
2585
2691
|
if (!args.includes("-o")) {
|
|
2586
2692
|
await mkdir4(outBase, { recursive: true });
|
|
2587
|
-
outArgs = [...args, "-o",
|
|
2693
|
+
outArgs = [...args, "-o", join7(outBase, `${basename(input).replace(/\.[^.]+$/, "")}.mp4`)];
|
|
2588
2694
|
}
|
|
2589
2695
|
outArgs = outArgs.map(
|
|
2590
2696
|
(a, i) => outArgs[i - 1] === "--overlay" || outArgs[i - 1] === "-o" ? userPath(a) : a
|
|
@@ -2599,7 +2705,7 @@ ${USAGE}`);
|
|
|
2599
2705
|
|
|
2600
2706
|
${USAGE}`);
|
|
2601
2707
|
const inputPath = userPath(input);
|
|
2602
|
-
if (!
|
|
2708
|
+
if (!existsSync5(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2603
2709
|
process.exit(
|
|
2604
2710
|
await (PACKAGED ? run(process.execPath, [LABELS, inputPath]) : run("npx", ["tsx", LABELS, inputPath]))
|
|
2605
2711
|
);
|
|
@@ -2610,10 +2716,10 @@ ${USAGE}`);
|
|
|
2610
2716
|
|
|
2611
2717
|
${USAGE}`);
|
|
2612
2718
|
const inputPath = userPath(input);
|
|
2613
|
-
if (!
|
|
2719
|
+
if (!existsSync5(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2614
2720
|
const oIdx = rest.indexOf("-o");
|
|
2615
|
-
const outBase = PACKAGED ?
|
|
2616
|
-
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`);
|
|
2617
2723
|
await mkdir4(dirname7(outPath), { recursive: true });
|
|
2618
2724
|
process.exit(
|
|
2619
2725
|
await (PACKAGED ? run(process.execPath, [PLAYER, inputPath, outPath]) : run("npx", ["tsx", PLAYER, inputPath, outPath]))
|
|
@@ -2633,7 +2739,7 @@ ${USAGE}`);
|
|
|
2633
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]`);
|
|
2634
2740
|
}
|
|
2635
2741
|
preflightFfmpeg();
|
|
2636
|
-
const { tmpdir:
|
|
2742
|
+
const { tmpdir: tmpdir5 } = await import("node:os");
|
|
2637
2743
|
const { resolveLogo: resolveLogo2, buildLogoSting: buildLogoSting2 } = await Promise.resolve().then(() => (init_logoSting(), logoSting_exports));
|
|
2638
2744
|
const num = (k) => flags[k] !== void 0 ? Number(flags[k]) : void 0;
|
|
2639
2745
|
console.log(`loading logo: ${arg} \u2026`);
|
|
@@ -2646,10 +2752,10 @@ ${USAGE}`);
|
|
|
2646
2752
|
seed: num("seed")
|
|
2647
2753
|
});
|
|
2648
2754
|
const sceneIR = buildLogoSting2(data);
|
|
2649
|
-
const tmp =
|
|
2755
|
+
const tmp = join7(tmpdir5(), `reframe-logo-${slug}-${process.pid}.json`);
|
|
2650
2756
|
await writeFile5(tmp, JSON.stringify(sceneIR));
|
|
2651
|
-
const outBase = PACKAGED ?
|
|
2652
|
-
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`);
|
|
2653
2759
|
await mkdir4(dirname7(out), { recursive: true });
|
|
2654
2760
|
console.log(`rendering ${data.name} (${data.paths.length} path${data.paths.length > 1 ? "s" : ""}, motion: ${data.motion ?? "reveal-orbit"}) \u2192 ${out}`);
|
|
2655
2761
|
process.exit(
|
|
@@ -2661,9 +2767,9 @@ ${USAGE}`);
|
|
|
2661
2767
|
if (!sceneArg || !dataArg) fail(`usage: ${CMD} batch <scene.ts> <data.json|csv> [...]`);
|
|
2662
2768
|
const scenePath = userPath(sceneArg);
|
|
2663
2769
|
const dataPath = userPath(dataArg);
|
|
2664
|
-
for (const p of [scenePath, dataPath]) if (!
|
|
2770
|
+
for (const p of [scenePath, dataPath]) if (!existsSync5(p)) fail(`no such file: ${p}`);
|
|
2665
2771
|
preflightFfmpeg();
|
|
2666
|
-
let outDir = PACKAGED ?
|
|
2772
|
+
let outDir = PACKAGED ? join7(USER_CWD, "out", "batch") : join7(ROOT2, "out", "batch");
|
|
2667
2773
|
let concurrency = 3;
|
|
2668
2774
|
let fps;
|
|
2669
2775
|
const baseOverlayPaths = [];
|
|
@@ -2676,10 +2782,10 @@ ${USAGE}`);
|
|
|
2676
2782
|
}
|
|
2677
2783
|
const { loadRows: loadRows2, runBatch: runBatch2 } = await Promise.resolve().then(() => (init_batch(), batch_exports));
|
|
2678
2784
|
const { loadScene: loadScene2 } = await Promise.resolve().then(() => (init_loadScene(), loadScene_exports));
|
|
2679
|
-
const { readFile:
|
|
2785
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
2680
2786
|
const scene2 = await loadScene2(scenePath);
|
|
2681
2787
|
const baseOverlays = await Promise.all(
|
|
2682
|
-
baseOverlayPaths.map(async (p) => JSON.parse(await
|
|
2788
|
+
baseOverlayPaths.map(async (p) => JSON.parse(await readFile7(p, "utf8")))
|
|
2683
2789
|
);
|
|
2684
2790
|
const rows = await loadRows2(dataPath);
|
|
2685
2791
|
if (rows.length === 0) fail(`${dataPath}: no data rows`);
|
|
@@ -2702,7 +2808,7 @@ ${USAGE}`);
|
|
|
2702
2808
|
const orphaned = results.filter((r) => !r.error && r.orphans.length > 0).length;
|
|
2703
2809
|
console.log(
|
|
2704
2810
|
`
|
|
2705
|
-
${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")}`
|
|
2706
2812
|
);
|
|
2707
2813
|
process.exit(failed > 0 ? 1 : 0);
|
|
2708
2814
|
}
|
|
@@ -2713,9 +2819,9 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2713
2819
|
if (PACKAGED) {
|
|
2714
2820
|
const { createRequire } = await import("node:module");
|
|
2715
2821
|
const vitePkg = createRequire(import.meta.url).resolve("vite/package.json");
|
|
2716
|
-
const viteBin =
|
|
2822
|
+
const viteBin = join7(dirname7(vitePkg), "bin", "vite.js");
|
|
2717
2823
|
process.exit(
|
|
2718
|
-
await run(process.execPath, [viteBin,
|
|
2824
|
+
await run(process.execPath, [viteBin, join7(ROOT2, "preview")], {
|
|
2719
2825
|
env: { REFRAME_SCENE_DIR: USER_CWD }
|
|
2720
2826
|
})
|
|
2721
2827
|
);
|
|
@@ -2733,10 +2839,10 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2733
2839
|
fail(`scene name must be kebab-case (a-z, 0-9, -): got "${name}"`);
|
|
2734
2840
|
}
|
|
2735
2841
|
const inRepo = USER_CWD === ROOT2 || USER_CWD.startsWith(ROOT2 + "/");
|
|
2736
|
-
const targetDir = inRepo ?
|
|
2737
|
-
const target =
|
|
2842
|
+
const targetDir = inRepo ? join7(ROOT2, "examples", "scenes") : USER_CWD;
|
|
2843
|
+
const target = join7(targetDir, `${name}.ts`);
|
|
2738
2844
|
const shown = inRepo ? `examples/scenes/${name}.ts` : `${name}.ts`;
|
|
2739
|
-
if (
|
|
2845
|
+
if (existsSync5(target)) fail(`${shown} already exists`);
|
|
2740
2846
|
const id = name.split("-")[0] ?? name;
|
|
2741
2847
|
await writeFile5(target, SCENE_TEMPLATE(name, id));
|
|
2742
2848
|
console.log(`created ${shown}
|
|
@@ -2765,9 +2871,9 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2765
2871
|
);
|
|
2766
2872
|
}
|
|
2767
2873
|
case "guide": {
|
|
2768
|
-
const file = rest.includes("--regen") ? PACKAGED ?
|
|
2769
|
-
const { readFile:
|
|
2770
|
-
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"));
|
|
2771
2877
|
return;
|
|
2772
2878
|
}
|
|
2773
2879
|
case "demo":
|
|
@@ -2778,7 +2884,7 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2778
2884
|
}
|
|
2779
2885
|
preflightFfmpeg();
|
|
2780
2886
|
process.exit(
|
|
2781
|
-
await run("npx", ["tsx",
|
|
2887
|
+
await run("npx", ["tsx", join7(ROOT2, "examples", "scripts", "demo-edit-survival.ts")])
|
|
2782
2888
|
);
|
|
2783
2889
|
default:
|
|
2784
2890
|
console.log(USAGE);
|