reframe-video 0.6.5 → 0.6.7
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 +331 -94
- package/dist/browserEntry.js +82 -35
- package/dist/cli.js +305 -75
- package/dist/index.js +79 -8
- 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/audio.d.ts +15 -0
- package/dist/types/dsl.d.ts +4 -1
- package/dist/types/evaluate.d.ts +12 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/ir.d.ts +26 -0
- package/guides/edsl-guide.md +25 -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", "volume"],
|
|
625
626
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
626
627
|
group: COMMON_PROPS
|
|
627
628
|
};
|
|
@@ -1209,11 +1210,34 @@ var init_motionOps = __esm({
|
|
|
1209
1210
|
});
|
|
1210
1211
|
|
|
1211
1212
|
// ../core/src/audio.ts
|
|
1213
|
+
function collectClipAudio(ir, duration, warnings) {
|
|
1214
|
+
const out = [];
|
|
1215
|
+
const walk = (nodes) => {
|
|
1216
|
+
for (const node of nodes) {
|
|
1217
|
+
if (node.type === "video") {
|
|
1218
|
+
const gain = node.props.volume ?? 1;
|
|
1219
|
+
const start = node.props.start ?? 0;
|
|
1220
|
+
if (gain <= 0) continue;
|
|
1221
|
+
if (start >= duration) {
|
|
1222
|
+
warnings.push(`video "${node.id}": start ${start.toFixed(2)}s past the scene end \u2014 audio dropped`);
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
out.push({ nodeId: node.id, src: node.props.src, start, rate: node.props.rate ?? 1, clipStart: node.props.clipStart ?? 0, gain });
|
|
1226
|
+
}
|
|
1227
|
+
if (node.type === "group") walk(node.children);
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
walk(ir.nodes);
|
|
1231
|
+
return out;
|
|
1232
|
+
}
|
|
1212
1233
|
function resolveAudioPlan(compiled) {
|
|
1213
1234
|
const audio = compiled.ir.audio;
|
|
1214
|
-
if (!audio || !audio.bgm && (audio.cues ?? []).length === 0) return null;
|
|
1215
1235
|
const warnings = [];
|
|
1216
1236
|
const duration = compiled.duration;
|
|
1237
|
+
const clipAudio = collectClipAudio(compiled.ir, duration, warnings);
|
|
1238
|
+
if (!audio || !audio.bgm && (audio.cues ?? []).length === 0) {
|
|
1239
|
+
return clipAudio.length === 0 ? null : { duration, bgm: null, cues: [], duckWindows: [], clipAudio, warnings };
|
|
1240
|
+
}
|
|
1217
1241
|
const cues = [];
|
|
1218
1242
|
for (const [index, cue] of (audio.cues ?? []).entries()) {
|
|
1219
1243
|
let anchor;
|
|
@@ -1249,6 +1273,7 @@ function resolveAudioPlan(compiled) {
|
|
|
1249
1273
|
bgm: resolveBgm(audio.bgm),
|
|
1250
1274
|
cues,
|
|
1251
1275
|
duckWindows: mergeDuckWindows(cues, duration),
|
|
1276
|
+
clipAudio,
|
|
1252
1277
|
warnings
|
|
1253
1278
|
};
|
|
1254
1279
|
}
|
|
@@ -1363,13 +1388,13 @@ var init_evaluate = __esm({
|
|
|
1363
1388
|
});
|
|
1364
1389
|
|
|
1365
1390
|
// ../core/src/assets.ts
|
|
1366
|
-
function
|
|
1391
|
+
function collectSrcs(ir, type) {
|
|
1367
1392
|
const srcs = /* @__PURE__ */ new Set();
|
|
1368
|
-
const
|
|
1393
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1369
1394
|
const walkNodes = (nodes) => {
|
|
1370
1395
|
for (const node of nodes) {
|
|
1371
|
-
if (node.type ===
|
|
1372
|
-
|
|
1396
|
+
if (node.type === type) {
|
|
1397
|
+
ids.add(node.id);
|
|
1373
1398
|
srcs.add(node.props.src);
|
|
1374
1399
|
}
|
|
1375
1400
|
if (node.type === "group") walkNodes(node.children);
|
|
@@ -1378,14 +1403,14 @@ function collectImageSrcs(ir) {
|
|
|
1378
1403
|
walkNodes(ir.nodes);
|
|
1379
1404
|
for (const overrides of Object.values(ir.states ?? {})) {
|
|
1380
1405
|
for (const [nodeId, props] of Object.entries(overrides)) {
|
|
1381
|
-
if (
|
|
1406
|
+
if (ids.has(nodeId) && typeof props.src === "string") srcs.add(props.src);
|
|
1382
1407
|
}
|
|
1383
1408
|
}
|
|
1384
1409
|
const walkTimeline = (step) => {
|
|
1385
1410
|
if (!step) return;
|
|
1386
1411
|
if (step.kind === "seq" || step.kind === "par" || step.kind === "stagger") {
|
|
1387
1412
|
for (const child of step.children) walkTimeline(child);
|
|
1388
|
-
} else if (step.kind === "tween" &&
|
|
1413
|
+
} else if (step.kind === "tween" && ids.has(step.target)) {
|
|
1389
1414
|
const src = step.props.src;
|
|
1390
1415
|
if (typeof src === "string") srcs.add(src);
|
|
1391
1416
|
}
|
|
@@ -1393,6 +1418,12 @@ function collectImageSrcs(ir) {
|
|
|
1393
1418
|
walkTimeline(ir.timeline);
|
|
1394
1419
|
return [...srcs];
|
|
1395
1420
|
}
|
|
1421
|
+
function collectImageSrcs(ir) {
|
|
1422
|
+
return collectSrcs(ir, "image");
|
|
1423
|
+
}
|
|
1424
|
+
function collectVideoSrcs(ir) {
|
|
1425
|
+
return collectSrcs(ir, "video");
|
|
1426
|
+
}
|
|
1396
1427
|
var init_assets = __esm({
|
|
1397
1428
|
"../core/src/assets.ts"() {
|
|
1398
1429
|
"use strict";
|
|
@@ -1824,11 +1855,89 @@ var init_sfx = __esm({
|
|
|
1824
1855
|
}
|
|
1825
1856
|
});
|
|
1826
1857
|
|
|
1827
|
-
// ../render-cli/src/audio/
|
|
1858
|
+
// ../render-cli/src/audio/clip.ts
|
|
1828
1859
|
import { spawn } from "node:child_process";
|
|
1860
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1861
|
+
import { isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
|
|
1862
|
+
function run(cmd, args) {
|
|
1863
|
+
return new Promise((res, reject) => {
|
|
1864
|
+
const proc = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
1865
|
+
let stdout = "", stderr = "";
|
|
1866
|
+
proc.stdout.on("data", (d) => stdout += d.toString());
|
|
1867
|
+
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
1868
|
+
proc.on("close", (code) => res({ code: code ?? 1, stdout, stderr }));
|
|
1869
|
+
proc.on("error", reject);
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
async function hasAudioStream(file) {
|
|
1873
|
+
const { stdout } = await run("ffprobe", [
|
|
1874
|
+
"-v",
|
|
1875
|
+
"error",
|
|
1876
|
+
"-select_streams",
|
|
1877
|
+
"a",
|
|
1878
|
+
"-show_entries",
|
|
1879
|
+
"stream=index",
|
|
1880
|
+
"-of",
|
|
1881
|
+
"csv=p=0",
|
|
1882
|
+
file
|
|
1883
|
+
]);
|
|
1884
|
+
return stdout.trim().length > 0;
|
|
1885
|
+
}
|
|
1886
|
+
function resolveSrc(src, sceneDir) {
|
|
1887
|
+
const candidates = [isAbsolute2(src) ? src : null, resolve2(sceneDir, src)].filter(
|
|
1888
|
+
(c) => c !== null
|
|
1889
|
+
);
|
|
1890
|
+
const found = candidates.find((c) => existsSync3(c));
|
|
1891
|
+
if (!found) throw new Error(`video "${src}" not found (tried: ${candidates.join(", ")})`);
|
|
1892
|
+
return found;
|
|
1893
|
+
}
|
|
1894
|
+
async function resolveClipAudio(entry, sceneDir, workDir) {
|
|
1895
|
+
const src = resolveSrc(entry.src, sceneDir);
|
|
1896
|
+
if (!await hasAudioStream(src)) return null;
|
|
1897
|
+
const out = join2(workDir, `clip-${entry.nodeId}.wav`);
|
|
1898
|
+
const { code, stderr } = await run("ffmpeg", [
|
|
1899
|
+
"-y",
|
|
1900
|
+
"-i",
|
|
1901
|
+
src,
|
|
1902
|
+
"-vn",
|
|
1903
|
+
"-ac",
|
|
1904
|
+
"2",
|
|
1905
|
+
"-ar",
|
|
1906
|
+
"44100",
|
|
1907
|
+
"-c:a",
|
|
1908
|
+
"pcm_s16le",
|
|
1909
|
+
out
|
|
1910
|
+
]);
|
|
1911
|
+
if (code !== 0) throw new Error(`clip audio extract failed for "${entry.src}":
|
|
1912
|
+
${stderr.slice(-1500)}`);
|
|
1913
|
+
return out;
|
|
1914
|
+
}
|
|
1915
|
+
var init_clip = __esm({
|
|
1916
|
+
"../render-cli/src/audio/clip.ts"() {
|
|
1917
|
+
"use strict";
|
|
1918
|
+
}
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1921
|
+
// ../render-cli/src/audio/mux.ts
|
|
1922
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
1829
1923
|
import { mkdtemp, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
1830
1924
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
1831
|
-
import { join as
|
|
1925
|
+
import { join as join3 } from "node:path";
|
|
1926
|
+
function atempoChain(rate) {
|
|
1927
|
+
if (!(rate > 0) || rate === 1) return [];
|
|
1928
|
+
const out = [];
|
|
1929
|
+
let r = rate;
|
|
1930
|
+
while (r > 2) {
|
|
1931
|
+
out.push("atempo=2.0");
|
|
1932
|
+
r /= 2;
|
|
1933
|
+
}
|
|
1934
|
+
while (r < 0.5) {
|
|
1935
|
+
out.push("atempo=0.5");
|
|
1936
|
+
r /= 0.5;
|
|
1937
|
+
}
|
|
1938
|
+
out.push(`atempo=${r.toFixed(4)}`);
|
|
1939
|
+
return out;
|
|
1940
|
+
}
|
|
1832
1941
|
function buildFilterGraph(plan, inputs) {
|
|
1833
1942
|
const lines = [];
|
|
1834
1943
|
const mixIn = ["[anchor]"];
|
|
@@ -1861,15 +1970,25 @@ function buildFilterGraph(plan, inputs) {
|
|
|
1861
1970
|
mixIn.push(`[c${i}]`);
|
|
1862
1971
|
inputIndex++;
|
|
1863
1972
|
});
|
|
1973
|
+
(inputs.clipFiles ?? []).forEach(({ audio }, i) => {
|
|
1974
|
+
const chain = [];
|
|
1975
|
+
if (audio.clipStart > 0) chain.push(`atrim=start=${audio.clipStart.toFixed(3)}`, "asetpts=PTS-STARTPTS");
|
|
1976
|
+
chain.push(...atempoChain(audio.rate), FORMAT, `volume=${audio.gain}`);
|
|
1977
|
+
const delayMs = Math.round(audio.start * 1e3);
|
|
1978
|
+
if (delayMs > 0) chain.push(`adelay=${delayMs}:all=1`);
|
|
1979
|
+
lines.push(`[${inputIndex}:a]${chain.join(",")}[k${i}]`);
|
|
1980
|
+
mixIn.push(`[k${i}]`);
|
|
1981
|
+
inputIndex++;
|
|
1982
|
+
});
|
|
1864
1983
|
lines.push(
|
|
1865
1984
|
`${mixIn.join("")}amix=inputs=${mixIn.length}:duration=first:normalize=0,alimiter=limit=0.891,aresample=async=1:first_pts=0[aout]`
|
|
1866
1985
|
);
|
|
1867
1986
|
return lines.join(";\n");
|
|
1868
1987
|
}
|
|
1869
1988
|
async function muxAudio(videoIn, plan, inputs, outFile) {
|
|
1870
|
-
const work = await mkdtemp(
|
|
1989
|
+
const work = await mkdtemp(join3(tmpdir2(), "reframe-mux-"));
|
|
1871
1990
|
try {
|
|
1872
|
-
const graphFile =
|
|
1991
|
+
const graphFile = join3(work, "graph.txt");
|
|
1873
1992
|
await writeFile2(graphFile, buildFilterGraph(plan, inputs));
|
|
1874
1993
|
const args = [
|
|
1875
1994
|
"-y",
|
|
@@ -1877,6 +1996,7 @@ async function muxAudio(videoIn, plan, inputs, outFile) {
|
|
|
1877
1996
|
videoIn,
|
|
1878
1997
|
...plan.bgm && inputs.bgmFile ? ["-i", inputs.bgmFile] : [],
|
|
1879
1998
|
...inputs.cueFiles.flatMap((f) => ["-i", f]),
|
|
1999
|
+
...(inputs.clipFiles ?? []).flatMap((c) => ["-i", c.file]),
|
|
1880
2000
|
"-filter_complex_script",
|
|
1881
2001
|
graphFile,
|
|
1882
2002
|
"-map",
|
|
@@ -1895,7 +2015,7 @@ async function muxAudio(videoIn, plan, inputs, outFile) {
|
|
|
1895
2015
|
outFile
|
|
1896
2016
|
];
|
|
1897
2017
|
await new Promise((resolvePromise, reject) => {
|
|
1898
|
-
const proc =
|
|
2018
|
+
const proc = spawn2("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
1899
2019
|
let stderr = "";
|
|
1900
2020
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
1901
2021
|
proc.on("close", (code) => {
|
|
@@ -1918,17 +2038,35 @@ var init_mux = __esm({
|
|
|
1918
2038
|
});
|
|
1919
2039
|
|
|
1920
2040
|
// ../render-cli/src/audio/index.ts
|
|
1921
|
-
import {
|
|
2041
|
+
import { copyFile, mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
|
|
2042
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
2043
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
1922
2044
|
async function buildAudioTrack(plan, scenePath, videoIn, outFile) {
|
|
1923
2045
|
const sceneDir = dirname2(scenePath);
|
|
1924
2046
|
const cueFiles = await Promise.all(plan.cues.map((cue) => resolveCueFile(cue, sceneDir)));
|
|
1925
2047
|
const bgmFile = plan.bgm ? await resolveBgmFile(plan.bgm.source, plan.duration, sceneDir) : null;
|
|
1926
|
-
|
|
2048
|
+
const work = await mkdtemp2(join4(tmpdir3(), "reframe-clipaudio-"));
|
|
2049
|
+
try {
|
|
2050
|
+
const clipFiles = [];
|
|
2051
|
+
for (const entry of plan.clipAudio) {
|
|
2052
|
+
const file = await resolveClipAudio(entry, sceneDir, work);
|
|
2053
|
+
if (file) clipFiles.push({ audio: entry, file });
|
|
2054
|
+
else console.error(`audio: video "${entry.nodeId}" has no audio track \u2014 skipped`);
|
|
2055
|
+
}
|
|
2056
|
+
if (!plan.bgm && plan.cues.length === 0 && clipFiles.length === 0) {
|
|
2057
|
+
await copyFile(videoIn, outFile);
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
await muxAudio(videoIn, plan, { cueFiles, bgmFile, clipFiles }, outFile);
|
|
2061
|
+
} finally {
|
|
2062
|
+
await rm2(work, { recursive: true, force: true });
|
|
2063
|
+
}
|
|
1927
2064
|
}
|
|
1928
2065
|
var init_audio2 = __esm({
|
|
1929
2066
|
"../render-cli/src/audio/index.ts"() {
|
|
1930
2067
|
"use strict";
|
|
1931
2068
|
init_sfx();
|
|
2069
|
+
init_clip();
|
|
1932
2070
|
init_mux();
|
|
1933
2071
|
init_mux();
|
|
1934
2072
|
init_synth();
|
|
@@ -1937,7 +2075,7 @@ var init_audio2 = __esm({
|
|
|
1937
2075
|
});
|
|
1938
2076
|
|
|
1939
2077
|
// ../render-cli/src/encode.ts
|
|
1940
|
-
import { spawn as
|
|
2078
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
1941
2079
|
async function encodeMp4(framesDir, fps, outFile) {
|
|
1942
2080
|
const args = [
|
|
1943
2081
|
"-y",
|
|
@@ -1957,12 +2095,12 @@ async function encodeMp4(framesDir, fps, outFile) {
|
|
|
1957
2095
|
"+faststart",
|
|
1958
2096
|
outFile
|
|
1959
2097
|
];
|
|
1960
|
-
await new Promise((
|
|
1961
|
-
const proc =
|
|
2098
|
+
await new Promise((resolve7, reject) => {
|
|
2099
|
+
const proc = spawn3("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
1962
2100
|
let stderr = "";
|
|
1963
2101
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
1964
2102
|
proc.on("close", (code) => {
|
|
1965
|
-
if (code === 0)
|
|
2103
|
+
if (code === 0) resolve7();
|
|
1966
2104
|
else reject(new Error(`ffmpeg exited with ${code}:
|
|
1967
2105
|
${stderr.slice(-2e3)}`));
|
|
1968
2106
|
});
|
|
@@ -1977,13 +2115,13 @@ var init_encode = __esm({
|
|
|
1977
2115
|
|
|
1978
2116
|
// ../render-cli/src/fonts.ts
|
|
1979
2117
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
1980
|
-
import { dirname as dirname3, join as
|
|
2118
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
1981
2119
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1982
2120
|
async function fontFaceCss() {
|
|
1983
2121
|
if (cssCache) return cssCache;
|
|
1984
2122
|
const rules = await Promise.all(
|
|
1985
2123
|
WEIGHTS.map(async (weight) => {
|
|
1986
|
-
const data = await readFile2(
|
|
2124
|
+
const data = await readFile2(join5(FONTS_DIR, `inter-${weight}.woff2`));
|
|
1987
2125
|
return `@font-face {
|
|
1988
2126
|
font-family: "Inter";
|
|
1989
2127
|
font-style: normal;
|
|
@@ -1999,7 +2137,7 @@ var FONTS_DIR, WEIGHTS, cssCache;
|
|
|
1999
2137
|
var init_fonts = __esm({
|
|
2000
2138
|
"../render-cli/src/fonts.ts"() {
|
|
2001
2139
|
"use strict";
|
|
2002
|
-
FONTS_DIR = true ?
|
|
2140
|
+
FONTS_DIR = true ? join5(dirname3(fileURLToPath2(import.meta.url)), "..", "assets", "fonts") : join5(dirname3(fileURLToPath2(import.meta.url)), "..", "..", "..", "assets", "fonts");
|
|
2003
2141
|
WEIGHTS = [400, 700, 800];
|
|
2004
2142
|
cssCache = null;
|
|
2005
2143
|
}
|
|
@@ -2007,8 +2145,8 @@ var init_fonts = __esm({
|
|
|
2007
2145
|
|
|
2008
2146
|
// ../render-cli/src/images.ts
|
|
2009
2147
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
2010
|
-
import { existsSync as
|
|
2011
|
-
import { extname, isAbsolute as
|
|
2148
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
2149
|
+
import { extname, isAbsolute as isAbsolute3, resolve as resolve3 } from "node:path";
|
|
2012
2150
|
async function buildImageAssets(ir, sceneDir) {
|
|
2013
2151
|
const assets = {};
|
|
2014
2152
|
for (const src of collectImageSrcs(ir)) {
|
|
@@ -2018,10 +2156,10 @@ async function buildImageAssets(ir, sceneDir) {
|
|
|
2018
2156
|
`image "${src}": unsupported format "${extname(src)}" \u2014 supported: ${Object.keys(MIME).join(" ")}`
|
|
2019
2157
|
);
|
|
2020
2158
|
}
|
|
2021
|
-
const candidates = [
|
|
2159
|
+
const candidates = [isAbsolute3(src) ? src : null, resolve3(sceneDir, src)].filter(
|
|
2022
2160
|
(c) => c !== null
|
|
2023
2161
|
);
|
|
2024
|
-
const found = candidates.find((c) =>
|
|
2162
|
+
const found = candidates.find((c) => existsSync4(c));
|
|
2025
2163
|
if (!found) {
|
|
2026
2164
|
throw new Error(`image "${src}" not found (tried: ${candidates.join(", ")})`);
|
|
2027
2165
|
}
|
|
@@ -2044,6 +2182,103 @@ var init_images = __esm({
|
|
|
2044
2182
|
}
|
|
2045
2183
|
});
|
|
2046
2184
|
|
|
2185
|
+
// ../render-cli/src/videos.ts
|
|
2186
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
2187
|
+
import { mkdtemp as mkdtemp3, readFile as readFile4, readdir, rm as rm3 } from "node:fs/promises";
|
|
2188
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2189
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
2190
|
+
import { extname as extname2, isAbsolute as isAbsolute4, join as join6, resolve as resolve4 } from "node:path";
|
|
2191
|
+
function runFfmpeg(args) {
|
|
2192
|
+
return new Promise((resolve7, reject) => {
|
|
2193
|
+
const proc = spawn4("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
2194
|
+
let stderr = "";
|
|
2195
|
+
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
2196
|
+
proc.on(
|
|
2197
|
+
"close",
|
|
2198
|
+
(code) => code === 0 ? resolve7() : reject(new Error(`ffmpeg exited ${code}:
|
|
2199
|
+
${stderr.slice(-2e3)}`))
|
|
2200
|
+
);
|
|
2201
|
+
proc.on("error", reject);
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
function neededSeconds(node, duration) {
|
|
2205
|
+
const start = node.props.start ?? 0;
|
|
2206
|
+
const rate = node.props.rate ?? 1;
|
|
2207
|
+
const clipStart = node.props.clipStart ?? 0;
|
|
2208
|
+
return clipStart + Math.max(0, duration - start) * Math.max(0, rate) + 1 / 30;
|
|
2209
|
+
}
|
|
2210
|
+
function videoNodes(ir) {
|
|
2211
|
+
const out = [];
|
|
2212
|
+
const walk = (nodes) => {
|
|
2213
|
+
for (const n of nodes) {
|
|
2214
|
+
if (n.type === "video") out.push(n);
|
|
2215
|
+
if (n.type === "group") walk(n.children);
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
walk(ir.nodes);
|
|
2219
|
+
return out;
|
|
2220
|
+
}
|
|
2221
|
+
async function buildVideoFrameAssets(ir, sceneDir, fps, duration) {
|
|
2222
|
+
const srcs = collectVideoSrcs(ir);
|
|
2223
|
+
if (srcs.length === 0) return {};
|
|
2224
|
+
const nodes = videoNodes(ir);
|
|
2225
|
+
const reachBySrc = /* @__PURE__ */ new Map();
|
|
2226
|
+
for (const n of nodes) {
|
|
2227
|
+
const reach = neededSeconds(n, duration);
|
|
2228
|
+
reachBySrc.set(n.props.src, Math.max(reachBySrc.get(n.props.src) ?? 0, reach));
|
|
2229
|
+
}
|
|
2230
|
+
const assets = {};
|
|
2231
|
+
for (const src of srcs) {
|
|
2232
|
+
if (!VIDEO_EXT.has(extname2(src).toLowerCase())) {
|
|
2233
|
+
throw new Error(
|
|
2234
|
+
`video "${src}": unsupported format "${extname2(src)}" \u2014 supported: ${[...VIDEO_EXT].join(" ")}`
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
const candidates = [isAbsolute4(src) ? src : null, resolve4(sceneDir, src)].filter(
|
|
2238
|
+
(c) => c !== null
|
|
2239
|
+
);
|
|
2240
|
+
const found = candidates.find((c) => existsSync5(c));
|
|
2241
|
+
if (!found) throw new Error(`video "${src}" not found (tried: ${candidates.join(", ")})`);
|
|
2242
|
+
const dir = await mkdtemp3(join6(tmpdir4(), "reframe-vframes-"));
|
|
2243
|
+
try {
|
|
2244
|
+
const seconds = Math.max(1 / fps, reachBySrc.get(src) ?? duration);
|
|
2245
|
+
await runFfmpeg([
|
|
2246
|
+
"-y",
|
|
2247
|
+
"-i",
|
|
2248
|
+
found,
|
|
2249
|
+
"-t",
|
|
2250
|
+
seconds.toFixed(3),
|
|
2251
|
+
"-vf",
|
|
2252
|
+
`fps=${fps},scale='min(iw,1280)':-2`,
|
|
2253
|
+
"-q:v",
|
|
2254
|
+
"4",
|
|
2255
|
+
join6(dir, "%05d.jpg")
|
|
2256
|
+
]);
|
|
2257
|
+
const files = (await readdir(dir)).filter((f) => f.endsWith(".jpg")).sort();
|
|
2258
|
+
assets[src] = await Promise.all(
|
|
2259
|
+
files.map(async (f) => `data:image/jpeg;base64,${(await readFile4(join6(dir, f))).toString("base64")}`)
|
|
2260
|
+
);
|
|
2261
|
+
if (assets[src].length === 0) throw new Error(`video "${src}": ffmpeg extracted no frames`);
|
|
2262
|
+
} finally {
|
|
2263
|
+
await rm3(dir, { recursive: true, force: true });
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
return assets;
|
|
2267
|
+
}
|
|
2268
|
+
function resolveTiming(ir, opts) {
|
|
2269
|
+
const fps = opts.fps ?? ir.fps ?? 30;
|
|
2270
|
+
const duration = opts.duration ?? compileScene(ir).duration;
|
|
2271
|
+
return { fps, duration };
|
|
2272
|
+
}
|
|
2273
|
+
var VIDEO_EXT;
|
|
2274
|
+
var init_videos = __esm({
|
|
2275
|
+
"../render-cli/src/videos.ts"() {
|
|
2276
|
+
"use strict";
|
|
2277
|
+
init_src();
|
|
2278
|
+
VIDEO_EXT = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".m4v", ".mkv"]);
|
|
2279
|
+
}
|
|
2280
|
+
});
|
|
2281
|
+
|
|
2047
2282
|
// ../render-cli/src/vclock.ts
|
|
2048
2283
|
var VCLOCK_SOURCE;
|
|
2049
2284
|
var init_vclock = __esm({
|
|
@@ -2124,7 +2359,7 @@ var init_reframeGlobal = __esm({
|
|
|
2124
2359
|
|
|
2125
2360
|
// ../render-cli/src/frameLoop.ts
|
|
2126
2361
|
import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
|
|
2127
|
-
import { join as
|
|
2362
|
+
import { join as join7, dirname as dirname4 } from "node:path";
|
|
2128
2363
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
2129
2364
|
import { build } from "esbuild";
|
|
2130
2365
|
import { chromium } from "playwright";
|
|
@@ -2149,14 +2384,14 @@ async function withPage(size, fn) {
|
|
|
2149
2384
|
async function browserBundle() {
|
|
2150
2385
|
if (bundleCache) return bundleCache;
|
|
2151
2386
|
if (true) {
|
|
2152
|
-
const { readFile:
|
|
2153
|
-
bundleCache = await
|
|
2154
|
-
|
|
2387
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
2388
|
+
bundleCache = await readFile7(
|
|
2389
|
+
join7(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
|
|
2155
2390
|
"utf8"
|
|
2156
2391
|
);
|
|
2157
2392
|
return bundleCache;
|
|
2158
2393
|
}
|
|
2159
|
-
const entry =
|
|
2394
|
+
const entry = join7(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.ts");
|
|
2160
2395
|
const result = await build({
|
|
2161
2396
|
entryPoints: [entry],
|
|
2162
2397
|
bundle: true,
|
|
@@ -2169,7 +2404,10 @@ async function browserBundle() {
|
|
|
2169
2404
|
}
|
|
2170
2405
|
async function captureIr(ir, opts) {
|
|
2171
2406
|
await mkdir2(opts.framesDir, { recursive: true });
|
|
2172
|
-
const
|
|
2407
|
+
const sceneDir = opts.sceneDir ?? process.cwd();
|
|
2408
|
+
const assets = await buildImageAssets(ir, sceneDir);
|
|
2409
|
+
const { fps, duration } = resolveTiming(ir, opts);
|
|
2410
|
+
const videoAssets = await buildVideoFrameAssets(ir, sceneDir, fps, duration);
|
|
2173
2411
|
const bundle = await browserBundle();
|
|
2174
2412
|
return withPage(ir.size, async (page) => {
|
|
2175
2413
|
await page.setContent(
|
|
@@ -2177,12 +2415,10 @@ async function captureIr(ir, opts) {
|
|
|
2177
2415
|
);
|
|
2178
2416
|
await injectFonts(page);
|
|
2179
2417
|
await page.addScriptTag({ content: bundle });
|
|
2180
|
-
|
|
2181
|
-
([sceneIr, imageAssets]) => window.__reframe.init(sceneIr, imageAssets),
|
|
2182
|
-
[ir, assets]
|
|
2418
|
+
await page.evaluate(
|
|
2419
|
+
([sceneIr, imageAssets, vAssets]) => window.__reframe.init(sceneIr, imageAssets, vAssets),
|
|
2420
|
+
[ir, assets, videoAssets]
|
|
2183
2421
|
);
|
|
2184
|
-
const fps = opts.fps ?? info.fps;
|
|
2185
|
-
const duration = opts.duration ?? info.duration;
|
|
2186
2422
|
const frameCount = Math.max(1, Math.round(duration * fps));
|
|
2187
2423
|
for (let f = 0; f < frameCount; f++) {
|
|
2188
2424
|
const dataUrl = await page.evaluate((t) => window.__reframe.renderFrame(t), f / fps);
|
|
@@ -2197,9 +2433,10 @@ var init_frameLoop = __esm({
|
|
|
2197
2433
|
"use strict";
|
|
2198
2434
|
init_fonts();
|
|
2199
2435
|
init_images();
|
|
2436
|
+
init_videos();
|
|
2200
2437
|
init_vclock();
|
|
2201
2438
|
init_reframeGlobal();
|
|
2202
|
-
framePath = (dir, i) =>
|
|
2439
|
+
framePath = (dir, i) => join7(dir, `${String(i).padStart(5, "0")}.png`);
|
|
2203
2440
|
bundleCache = null;
|
|
2204
2441
|
}
|
|
2205
2442
|
});
|
|
@@ -2212,9 +2449,9 @@ __export(batch_exports, {
|
|
|
2212
2449
|
parseCsv: () => parseCsv,
|
|
2213
2450
|
runBatch: () => runBatch
|
|
2214
2451
|
});
|
|
2215
|
-
import { mkdir as mkdir3, mkdtemp as
|
|
2216
|
-
import { tmpdir as
|
|
2217
|
-
import { join as
|
|
2452
|
+
import { mkdir as mkdir3, mkdtemp as mkdtemp4, readFile as readFile5, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
|
|
2453
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
2454
|
+
import { join as join8, dirname as dirname5 } from "node:path";
|
|
2218
2455
|
function overlayFromFlat(row, name) {
|
|
2219
2456
|
const doc = { reframeOverlay: 1, name };
|
|
2220
2457
|
for (const [key2, raw] of Object.entries(row)) {
|
|
@@ -2288,7 +2525,7 @@ function parseCsv(text2) {
|
|
|
2288
2525
|
});
|
|
2289
2526
|
}
|
|
2290
2527
|
async function loadRows(path2) {
|
|
2291
|
-
const text2 = await
|
|
2528
|
+
const text2 = await readFile5(path2, "utf8");
|
|
2292
2529
|
if (path2.endsWith(".csv")) return parseCsv(text2);
|
|
2293
2530
|
const parsed = JSON.parse(text2);
|
|
2294
2531
|
if (!Array.isArray(parsed)) throw new Error(`${path2}: expected a JSON array of row objects`);
|
|
@@ -2308,8 +2545,8 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2308
2545
|
try {
|
|
2309
2546
|
const rowOverlay = overlayFromFlat(row, name);
|
|
2310
2547
|
const { ir, report } = composeScene(scene2, ...opts.baseOverlays, rowOverlay);
|
|
2311
|
-
const framesDir = await
|
|
2312
|
-
const output =
|
|
2548
|
+
const framesDir = await mkdtemp4(join8(tmpdir5(), `reframe-batch-${index}-`));
|
|
2549
|
+
const output = join8(opts.outDir, `${name}.mp4`);
|
|
2313
2550
|
const plan = opts.noAudio ? null : resolveAudioPlan(compileScene(ir));
|
|
2314
2551
|
try {
|
|
2315
2552
|
const captured = await captureIr(ir, {
|
|
@@ -2321,12 +2558,12 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2321
2558
|
const videoTmp = `${output}.video.mp4`;
|
|
2322
2559
|
await encodeMp4(captured.framesDir, captured.fps, videoTmp);
|
|
2323
2560
|
await buildAudioTrack(plan, opts.scenePath ?? output, videoTmp, output);
|
|
2324
|
-
await
|
|
2561
|
+
await rm4(videoTmp, { force: true });
|
|
2325
2562
|
} else {
|
|
2326
2563
|
await encodeMp4(captured.framesDir, captured.fps, output);
|
|
2327
2564
|
}
|
|
2328
2565
|
} finally {
|
|
2329
|
-
await
|
|
2566
|
+
await rm4(framesDir, { recursive: true, force: true });
|
|
2330
2567
|
}
|
|
2331
2568
|
result = {
|
|
2332
2569
|
name,
|
|
@@ -2350,7 +2587,7 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2350
2587
|
};
|
|
2351
2588
|
await Promise.all(Array.from({ length: Math.max(1, opts.concurrency) }, worker));
|
|
2352
2589
|
await writeFile4(
|
|
2353
|
-
|
|
2590
|
+
join8(opts.outDir, "batch-report.json"),
|
|
2354
2591
|
JSON.stringify({ rows: results }, null, 2)
|
|
2355
2592
|
);
|
|
2356
2593
|
return results;
|
|
@@ -2375,11 +2612,11 @@ __export(loadScene_exports, {
|
|
|
2375
2612
|
loadScene: () => loadScene
|
|
2376
2613
|
});
|
|
2377
2614
|
import { build as build2 } from "esbuild";
|
|
2378
|
-
import { readFile as
|
|
2379
|
-
import { dirname as dirname6, resolve as
|
|
2615
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
2616
|
+
import { dirname as dirname6, resolve as resolve5 } from "node:path";
|
|
2380
2617
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
2381
2618
|
async function loadDefault(path2) {
|
|
2382
|
-
if (path2.endsWith(".json")) return JSON.parse(await
|
|
2619
|
+
if (path2.endsWith(".json")) return JSON.parse(await readFile6(path2, "utf8"));
|
|
2383
2620
|
let code;
|
|
2384
2621
|
try {
|
|
2385
2622
|
const out = await build2({
|
|
@@ -2429,26 +2666,26 @@ var init_loadScene = __esm({
|
|
|
2429
2666
|
"use strict";
|
|
2430
2667
|
init_src();
|
|
2431
2668
|
HERE = dirname6(fileURLToPath4(import.meta.url));
|
|
2432
|
-
CORE_ENTRY = true ?
|
|
2669
|
+
CORE_ENTRY = true ? resolve5(HERE, "index.js") : resolve5(HERE, "..", "..", "core", "src", "index.ts");
|
|
2433
2670
|
}
|
|
2434
2671
|
});
|
|
2435
2672
|
|
|
2436
2673
|
// ../render-cli/src/reframe.ts
|
|
2437
|
-
import { spawn as
|
|
2438
|
-
import { existsSync as
|
|
2674
|
+
import { spawn as spawn5, spawnSync } from "node:child_process";
|
|
2675
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
2439
2676
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
2440
|
-
import { basename, isAbsolute as
|
|
2677
|
+
import { basename, isAbsolute as isAbsolute5, join as join9, resolve as resolve6 } from "node:path";
|
|
2441
2678
|
import { dirname as dirname7 } from "node:path";
|
|
2442
2679
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
2443
2680
|
var PACKAGED = true;
|
|
2444
2681
|
var HERE2 = dirname7(fileURLToPath5(import.meta.url));
|
|
2445
|
-
var ROOT2 = PACKAGED ?
|
|
2682
|
+
var ROOT2 = PACKAGED ? resolve6(HERE2, "..") : resolve6(HERE2, "..", "..", "..");
|
|
2446
2683
|
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 ?
|
|
2684
|
+
var RENDER_CLI = PACKAGED ? join9(ROOT2, "dist", "cli.js") : join9(ROOT2, "packages", "render-cli", "src", "cli.ts");
|
|
2685
|
+
var LABELS = PACKAGED ? join9(ROOT2, "dist", "labels.js") : join9(ROOT2, "packages", "render-cli", "src", "labels.ts");
|
|
2686
|
+
var PLAYER = PACKAGED ? join9(ROOT2, "dist", "player.js") : join9(ROOT2, "packages", "render-cli", "src", "player.ts");
|
|
2687
|
+
var ANALYZE = PACKAGED ? join9(ROOT2, "dist", "analyze.js") : join9(ROOT2, "benchmark", "harness", "motion", "analyze.ts");
|
|
2688
|
+
var TRACE = PACKAGED ? join9(ROOT2, "dist", "trace-cli.js") : join9(ROOT2, "benchmark", "harness", "motion", "trace-cli.ts");
|
|
2452
2689
|
var CMD = PACKAGED ? "reframe" : "pnpm reframe";
|
|
2453
2690
|
var USAGE = `reframe \u2014 declarative motion graphics
|
|
2454
2691
|
|
|
@@ -2468,7 +2705,7 @@ usage:
|
|
|
2468
2705
|
${CMD} guide [--regen] print the scene-authoring guide (for you or your AI)
|
|
2469
2706
|
${CMD} demo run the edit-survival demo (3 mp4s into out/)
|
|
2470
2707
|
`;
|
|
2471
|
-
var userPath = (p) =>
|
|
2708
|
+
var userPath = (p) => isAbsolute5(p) ? p : resolve6(USER_CWD, p);
|
|
2472
2709
|
function fail(message) {
|
|
2473
2710
|
console.error(`error: ${message}`);
|
|
2474
2711
|
process.exit(2);
|
|
@@ -2478,9 +2715,9 @@ function preflightFfmpeg() {
|
|
|
2478
2715
|
fail("ffmpeg not found on PATH \u2014 install it first (macOS: brew install ffmpeg, debian: apt install ffmpeg)");
|
|
2479
2716
|
}
|
|
2480
2717
|
}
|
|
2481
|
-
function
|
|
2718
|
+
function run2(cmd, args, opts = {}) {
|
|
2482
2719
|
return new Promise((res) => {
|
|
2483
|
-
const proc =
|
|
2720
|
+
const proc = spawn5(cmd, args, {
|
|
2484
2721
|
cwd: opts.cwd ?? (PACKAGED ? USER_CWD : ROOT2),
|
|
2485
2722
|
stdio: ["inherit", "inherit", "pipe"],
|
|
2486
2723
|
...opts.env && { env: { ...process.env, ...opts.env } }
|
|
@@ -2570,7 +2807,7 @@ async function main() {
|
|
|
2570
2807
|
|
|
2571
2808
|
${USAGE}`);
|
|
2572
2809
|
const inputPath = userPath(input);
|
|
2573
|
-
if (!
|
|
2810
|
+
if (!existsSync6(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2574
2811
|
const mode = /\.(ts|json)$/.test(input) ? "ir" : /\.html$/.test(input) ? "html" : null;
|
|
2575
2812
|
if (!mode) {
|
|
2576
2813
|
fail(`cannot infer render mode from "${input}" \u2014 expected .ts/.json (reframe scene) or .html (GSAP page)`);
|
|
@@ -2580,17 +2817,17 @@ ${USAGE}`);
|
|
|
2580
2817
|
fail("html render requires --duration <seconds> (the page does not declare its own length)");
|
|
2581
2818
|
}
|
|
2582
2819
|
preflightFfmpeg();
|
|
2583
|
-
const outBase = PACKAGED ?
|
|
2820
|
+
const outBase = PACKAGED ? join9(USER_CWD, "out") : join9(ROOT2, "out");
|
|
2584
2821
|
let outArgs = args;
|
|
2585
2822
|
if (!args.includes("-o")) {
|
|
2586
2823
|
await mkdir4(outBase, { recursive: true });
|
|
2587
|
-
outArgs = [...args, "-o",
|
|
2824
|
+
outArgs = [...args, "-o", join9(outBase, `${basename(input).replace(/\.[^.]+$/, "")}.mp4`)];
|
|
2588
2825
|
}
|
|
2589
2826
|
outArgs = outArgs.map(
|
|
2590
2827
|
(a, i) => outArgs[i - 1] === "--overlay" || outArgs[i - 1] === "-o" ? userPath(a) : a
|
|
2591
2828
|
);
|
|
2592
2829
|
process.exit(
|
|
2593
|
-
await (PACKAGED ?
|
|
2830
|
+
await (PACKAGED ? run2(process.execPath, [RENDER_CLI, mode, inputPath, ...outArgs]) : run2("npx", ["tsx", RENDER_CLI, mode, inputPath, ...outArgs]))
|
|
2594
2831
|
);
|
|
2595
2832
|
}
|
|
2596
2833
|
case "labels": {
|
|
@@ -2599,9 +2836,9 @@ ${USAGE}`);
|
|
|
2599
2836
|
|
|
2600
2837
|
${USAGE}`);
|
|
2601
2838
|
const inputPath = userPath(input);
|
|
2602
|
-
if (!
|
|
2839
|
+
if (!existsSync6(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2603
2840
|
process.exit(
|
|
2604
|
-
await (PACKAGED ?
|
|
2841
|
+
await (PACKAGED ? run2(process.execPath, [LABELS, inputPath]) : run2("npx", ["tsx", LABELS, inputPath]))
|
|
2605
2842
|
);
|
|
2606
2843
|
}
|
|
2607
2844
|
case "player": {
|
|
@@ -2610,13 +2847,13 @@ ${USAGE}`);
|
|
|
2610
2847
|
|
|
2611
2848
|
${USAGE}`);
|
|
2612
2849
|
const inputPath = userPath(input);
|
|
2613
|
-
if (!
|
|
2850
|
+
if (!existsSync6(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2614
2851
|
const oIdx = rest.indexOf("-o");
|
|
2615
|
-
const outBase = PACKAGED ?
|
|
2616
|
-
const outPath = oIdx >= 0 && rest[oIdx + 1] ? userPath(rest[oIdx + 1]) :
|
|
2852
|
+
const outBase = PACKAGED ? join9(USER_CWD, "out") : join9(ROOT2, "out");
|
|
2853
|
+
const outPath = oIdx >= 0 && rest[oIdx + 1] ? userPath(rest[oIdx + 1]) : join9(outBase, `${basename(input).replace(/\.[^.]+$/, "")}.html`);
|
|
2617
2854
|
await mkdir4(dirname7(outPath), { recursive: true });
|
|
2618
2855
|
process.exit(
|
|
2619
|
-
await (PACKAGED ?
|
|
2856
|
+
await (PACKAGED ? run2(process.execPath, [PLAYER, inputPath, outPath]) : run2("npx", ["tsx", PLAYER, inputPath, outPath]))
|
|
2620
2857
|
);
|
|
2621
2858
|
}
|
|
2622
2859
|
case "logo": {
|
|
@@ -2633,7 +2870,7 @@ ${USAGE}`);
|
|
|
2633
2870
|
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
2871
|
}
|
|
2635
2872
|
preflightFfmpeg();
|
|
2636
|
-
const { tmpdir:
|
|
2873
|
+
const { tmpdir: tmpdir6 } = await import("node:os");
|
|
2637
2874
|
const { resolveLogo: resolveLogo2, buildLogoSting: buildLogoSting2 } = await Promise.resolve().then(() => (init_logoSting(), logoSting_exports));
|
|
2638
2875
|
const num = (k) => flags[k] !== void 0 ? Number(flags[k]) : void 0;
|
|
2639
2876
|
console.log(`loading logo: ${arg} \u2026`);
|
|
@@ -2646,14 +2883,14 @@ ${USAGE}`);
|
|
|
2646
2883
|
seed: num("seed")
|
|
2647
2884
|
});
|
|
2648
2885
|
const sceneIR = buildLogoSting2(data);
|
|
2649
|
-
const tmp =
|
|
2886
|
+
const tmp = join9(tmpdir6(), `reframe-logo-${slug}-${process.pid}.json`);
|
|
2650
2887
|
await writeFile5(tmp, JSON.stringify(sceneIR));
|
|
2651
|
-
const outBase = PACKAGED ?
|
|
2652
|
-
const out = flags.o ? userPath(flags.o) :
|
|
2888
|
+
const outBase = PACKAGED ? join9(USER_CWD, "out") : join9(ROOT2, "out");
|
|
2889
|
+
const out = flags.o ? userPath(flags.o) : join9(outBase, `logo-${slug}.mp4`);
|
|
2653
2890
|
await mkdir4(dirname7(out), { recursive: true });
|
|
2654
2891
|
console.log(`rendering ${data.name} (${data.paths.length} path${data.paths.length > 1 ? "s" : ""}, motion: ${data.motion ?? "reveal-orbit"}) \u2192 ${out}`);
|
|
2655
2892
|
process.exit(
|
|
2656
|
-
await (PACKAGED ?
|
|
2893
|
+
await (PACKAGED ? run2(process.execPath, [RENDER_CLI, "ir", tmp, "-o", out, "--no-audio"]) : run2("npx", ["tsx", RENDER_CLI, "ir", tmp, "-o", out, "--no-audio"]))
|
|
2657
2894
|
);
|
|
2658
2895
|
}
|
|
2659
2896
|
case "batch": {
|
|
@@ -2661,9 +2898,9 @@ ${USAGE}`);
|
|
|
2661
2898
|
if (!sceneArg || !dataArg) fail(`usage: ${CMD} batch <scene.ts> <data.json|csv> [...]`);
|
|
2662
2899
|
const scenePath = userPath(sceneArg);
|
|
2663
2900
|
const dataPath = userPath(dataArg);
|
|
2664
|
-
for (const p of [scenePath, dataPath]) if (!
|
|
2901
|
+
for (const p of [scenePath, dataPath]) if (!existsSync6(p)) fail(`no such file: ${p}`);
|
|
2665
2902
|
preflightFfmpeg();
|
|
2666
|
-
let outDir = PACKAGED ?
|
|
2903
|
+
let outDir = PACKAGED ? join9(USER_CWD, "out", "batch") : join9(ROOT2, "out", "batch");
|
|
2667
2904
|
let concurrency = 3;
|
|
2668
2905
|
let fps;
|
|
2669
2906
|
const baseOverlayPaths = [];
|
|
@@ -2676,10 +2913,10 @@ ${USAGE}`);
|
|
|
2676
2913
|
}
|
|
2677
2914
|
const { loadRows: loadRows2, runBatch: runBatch2 } = await Promise.resolve().then(() => (init_batch(), batch_exports));
|
|
2678
2915
|
const { loadScene: loadScene2 } = await Promise.resolve().then(() => (init_loadScene(), loadScene_exports));
|
|
2679
|
-
const { readFile:
|
|
2916
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
2680
2917
|
const scene2 = await loadScene2(scenePath);
|
|
2681
2918
|
const baseOverlays = await Promise.all(
|
|
2682
|
-
baseOverlayPaths.map(async (p) => JSON.parse(await
|
|
2919
|
+
baseOverlayPaths.map(async (p) => JSON.parse(await readFile7(p, "utf8")))
|
|
2683
2920
|
);
|
|
2684
2921
|
const rows = await loadRows2(dataPath);
|
|
2685
2922
|
if (rows.length === 0) fail(`${dataPath}: no data rows`);
|
|
@@ -2702,7 +2939,7 @@ ${USAGE}`);
|
|
|
2702
2939
|
const orphaned = results.filter((r) => !r.error && r.orphans.length > 0).length;
|
|
2703
2940
|
console.log(
|
|
2704
2941
|
`
|
|
2705
|
-
${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed \u2014 report: ${
|
|
2942
|
+
${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed \u2014 report: ${join9(outDir, "batch-report.json")}`
|
|
2706
2943
|
);
|
|
2707
2944
|
process.exit(failed > 0 ? 1 : 0);
|
|
2708
2945
|
}
|
|
@@ -2713,15 +2950,15 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2713
2950
|
if (PACKAGED) {
|
|
2714
2951
|
const { createRequire } = await import("node:module");
|
|
2715
2952
|
const vitePkg = createRequire(import.meta.url).resolve("vite/package.json");
|
|
2716
|
-
const viteBin =
|
|
2953
|
+
const viteBin = join9(dirname7(vitePkg), "bin", "vite.js");
|
|
2717
2954
|
process.exit(
|
|
2718
|
-
await
|
|
2955
|
+
await run2(process.execPath, [viteBin, join9(ROOT2, "preview")], {
|
|
2719
2956
|
env: { REFRAME_SCENE_DIR: USER_CWD }
|
|
2720
2957
|
})
|
|
2721
2958
|
);
|
|
2722
2959
|
}
|
|
2723
2960
|
process.exit(
|
|
2724
|
-
await
|
|
2961
|
+
await run2("pnpm", ["--filter", "@reframe/preview", "dev"], {
|
|
2725
2962
|
env: { REFRAME_SCENE_DIR: USER_CWD }
|
|
2726
2963
|
})
|
|
2727
2964
|
);
|
|
@@ -2733,10 +2970,10 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2733
2970
|
fail(`scene name must be kebab-case (a-z, 0-9, -): got "${name}"`);
|
|
2734
2971
|
}
|
|
2735
2972
|
const inRepo = USER_CWD === ROOT2 || USER_CWD.startsWith(ROOT2 + "/");
|
|
2736
|
-
const targetDir = inRepo ?
|
|
2737
|
-
const target =
|
|
2973
|
+
const targetDir = inRepo ? join9(ROOT2, "examples", "scenes") : USER_CWD;
|
|
2974
|
+
const target = join9(targetDir, `${name}.ts`);
|
|
2738
2975
|
const shown = inRepo ? `examples/scenes/${name}.ts` : `${name}.ts`;
|
|
2739
|
-
if (
|
|
2976
|
+
if (existsSync6(target)) fail(`${shown} already exists`);
|
|
2740
2977
|
const id = name.split("-")[0] ?? name;
|
|
2741
2978
|
await writeFile5(target, SCENE_TEMPLATE(name, id));
|
|
2742
2979
|
console.log(`created ${shown}
|
|
@@ -2750,7 +2987,7 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2750
2987
|
if (!input) fail(`usage: ${CMD} motion <mp4|framesDir> [...args]`);
|
|
2751
2988
|
preflightFfmpeg();
|
|
2752
2989
|
process.exit(
|
|
2753
|
-
await (PACKAGED ?
|
|
2990
|
+
await (PACKAGED ? run2(process.execPath, [ANALYZE, userPath(input), ...rest.slice(1)]) : run2("npx", ["tsx", ANALYZE, userPath(input), ...rest.slice(1)]))
|
|
2754
2991
|
);
|
|
2755
2992
|
}
|
|
2756
2993
|
case "trace": {
|
|
@@ -2761,13 +2998,13 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2761
2998
|
(a, i) => rest.slice(1)[i - 1] === "--apply" || rest.slice(1)[i - 1] === "-o" ? userPath(a) : a
|
|
2762
2999
|
);
|
|
2763
3000
|
process.exit(
|
|
2764
|
-
await (PACKAGED ?
|
|
3001
|
+
await (PACKAGED ? run2(process.execPath, [TRACE, userPath(input), ...args]) : run2("npx", ["tsx", TRACE, userPath(input), ...args]))
|
|
2765
3002
|
);
|
|
2766
3003
|
}
|
|
2767
3004
|
case "guide": {
|
|
2768
|
-
const file = rest.includes("--regen") ? PACKAGED ?
|
|
2769
|
-
const { readFile:
|
|
2770
|
-
process.stdout.write(await
|
|
3005
|
+
const file = rest.includes("--regen") ? PACKAGED ? join9(ROOT2, "guides", "regen-contract.md") : join9(ROOT2, "docs", "regen-contract.md") : PACKAGED ? join9(ROOT2, "guides", "edsl-guide.md") : join9(ROOT2, "benchmark", "guides", "edsl-guide.md");
|
|
3006
|
+
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
3007
|
+
process.stdout.write(await readFile7(file, "utf8"));
|
|
2771
3008
|
return;
|
|
2772
3009
|
}
|
|
2773
3010
|
case "demo":
|
|
@@ -2778,7 +3015,7 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2778
3015
|
}
|
|
2779
3016
|
preflightFfmpeg();
|
|
2780
3017
|
process.exit(
|
|
2781
|
-
await
|
|
3018
|
+
await run2("npx", ["tsx", join9(ROOT2, "examples", "scripts", "demo-edit-survival.ts")])
|
|
2782
3019
|
);
|
|
2783
3020
|
default:
|
|
2784
3021
|
console.log(USAGE);
|