reframe-video 0.6.6 → 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 +219 -88
- package/dist/browserEntry.js +1 -1
- package/dist/cli.js +202 -70
- package/dist/index.js +34 -3
- package/dist/labels.js +1 -1
- package/dist/trace-cli.js +1 -1
- package/dist/types/audio.d.ts +15 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/ir.d.ts +5 -0
- package/guides/edsl-guide.md +9 -5
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -622,7 +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
|
+
video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
|
|
626
626
|
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
627
627
|
group: COMMON_PROPS
|
|
628
628
|
};
|
|
@@ -1210,11 +1210,34 @@ var init_motionOps = __esm({
|
|
|
1210
1210
|
});
|
|
1211
1211
|
|
|
1212
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
|
+
}
|
|
1213
1233
|
function resolveAudioPlan(compiled) {
|
|
1214
1234
|
const audio = compiled.ir.audio;
|
|
1215
|
-
if (!audio || !audio.bgm && (audio.cues ?? []).length === 0) return null;
|
|
1216
1235
|
const warnings = [];
|
|
1217
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
|
+
}
|
|
1218
1241
|
const cues = [];
|
|
1219
1242
|
for (const [index, cue] of (audio.cues ?? []).entries()) {
|
|
1220
1243
|
let anchor;
|
|
@@ -1250,6 +1273,7 @@ function resolveAudioPlan(compiled) {
|
|
|
1250
1273
|
bgm: resolveBgm(audio.bgm),
|
|
1251
1274
|
cues,
|
|
1252
1275
|
duckWindows: mergeDuckWindows(cues, duration),
|
|
1276
|
+
clipAudio,
|
|
1253
1277
|
warnings
|
|
1254
1278
|
};
|
|
1255
1279
|
}
|
|
@@ -1831,11 +1855,89 @@ var init_sfx = __esm({
|
|
|
1831
1855
|
}
|
|
1832
1856
|
});
|
|
1833
1857
|
|
|
1834
|
-
// ../render-cli/src/audio/
|
|
1858
|
+
// ../render-cli/src/audio/clip.ts
|
|
1835
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";
|
|
1836
1923
|
import { mkdtemp, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
1837
1924
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
1838
|
-
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
|
+
}
|
|
1839
1941
|
function buildFilterGraph(plan, inputs) {
|
|
1840
1942
|
const lines = [];
|
|
1841
1943
|
const mixIn = ["[anchor]"];
|
|
@@ -1868,15 +1970,25 @@ function buildFilterGraph(plan, inputs) {
|
|
|
1868
1970
|
mixIn.push(`[c${i}]`);
|
|
1869
1971
|
inputIndex++;
|
|
1870
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
|
+
});
|
|
1871
1983
|
lines.push(
|
|
1872
1984
|
`${mixIn.join("")}amix=inputs=${mixIn.length}:duration=first:normalize=0,alimiter=limit=0.891,aresample=async=1:first_pts=0[aout]`
|
|
1873
1985
|
);
|
|
1874
1986
|
return lines.join(";\n");
|
|
1875
1987
|
}
|
|
1876
1988
|
async function muxAudio(videoIn, plan, inputs, outFile) {
|
|
1877
|
-
const work = await mkdtemp(
|
|
1989
|
+
const work = await mkdtemp(join3(tmpdir2(), "reframe-mux-"));
|
|
1878
1990
|
try {
|
|
1879
|
-
const graphFile =
|
|
1991
|
+
const graphFile = join3(work, "graph.txt");
|
|
1880
1992
|
await writeFile2(graphFile, buildFilterGraph(plan, inputs));
|
|
1881
1993
|
const args = [
|
|
1882
1994
|
"-y",
|
|
@@ -1884,6 +1996,7 @@ async function muxAudio(videoIn, plan, inputs, outFile) {
|
|
|
1884
1996
|
videoIn,
|
|
1885
1997
|
...plan.bgm && inputs.bgmFile ? ["-i", inputs.bgmFile] : [],
|
|
1886
1998
|
...inputs.cueFiles.flatMap((f) => ["-i", f]),
|
|
1999
|
+
...(inputs.clipFiles ?? []).flatMap((c) => ["-i", c.file]),
|
|
1887
2000
|
"-filter_complex_script",
|
|
1888
2001
|
graphFile,
|
|
1889
2002
|
"-map",
|
|
@@ -1902,7 +2015,7 @@ async function muxAudio(videoIn, plan, inputs, outFile) {
|
|
|
1902
2015
|
outFile
|
|
1903
2016
|
];
|
|
1904
2017
|
await new Promise((resolvePromise, reject) => {
|
|
1905
|
-
const proc =
|
|
2018
|
+
const proc = spawn2("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
1906
2019
|
let stderr = "";
|
|
1907
2020
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
1908
2021
|
proc.on("close", (code) => {
|
|
@@ -1925,17 +2038,35 @@ var init_mux = __esm({
|
|
|
1925
2038
|
});
|
|
1926
2039
|
|
|
1927
2040
|
// ../render-cli/src/audio/index.ts
|
|
1928
|
-
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";
|
|
1929
2044
|
async function buildAudioTrack(plan, scenePath, videoIn, outFile) {
|
|
1930
2045
|
const sceneDir = dirname2(scenePath);
|
|
1931
2046
|
const cueFiles = await Promise.all(plan.cues.map((cue) => resolveCueFile(cue, sceneDir)));
|
|
1932
2047
|
const bgmFile = plan.bgm ? await resolveBgmFile(plan.bgm.source, plan.duration, sceneDir) : null;
|
|
1933
|
-
|
|
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
|
+
}
|
|
1934
2064
|
}
|
|
1935
2065
|
var init_audio2 = __esm({
|
|
1936
2066
|
"../render-cli/src/audio/index.ts"() {
|
|
1937
2067
|
"use strict";
|
|
1938
2068
|
init_sfx();
|
|
2069
|
+
init_clip();
|
|
1939
2070
|
init_mux();
|
|
1940
2071
|
init_mux();
|
|
1941
2072
|
init_synth();
|
|
@@ -1944,7 +2075,7 @@ var init_audio2 = __esm({
|
|
|
1944
2075
|
});
|
|
1945
2076
|
|
|
1946
2077
|
// ../render-cli/src/encode.ts
|
|
1947
|
-
import { spawn as
|
|
2078
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
1948
2079
|
async function encodeMp4(framesDir, fps, outFile) {
|
|
1949
2080
|
const args = [
|
|
1950
2081
|
"-y",
|
|
@@ -1964,12 +2095,12 @@ async function encodeMp4(framesDir, fps, outFile) {
|
|
|
1964
2095
|
"+faststart",
|
|
1965
2096
|
outFile
|
|
1966
2097
|
];
|
|
1967
|
-
await new Promise((
|
|
1968
|
-
const proc =
|
|
2098
|
+
await new Promise((resolve7, reject) => {
|
|
2099
|
+
const proc = spawn3("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
1969
2100
|
let stderr = "";
|
|
1970
2101
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
1971
2102
|
proc.on("close", (code) => {
|
|
1972
|
-
if (code === 0)
|
|
2103
|
+
if (code === 0) resolve7();
|
|
1973
2104
|
else reject(new Error(`ffmpeg exited with ${code}:
|
|
1974
2105
|
${stderr.slice(-2e3)}`));
|
|
1975
2106
|
});
|
|
@@ -1984,13 +2115,13 @@ var init_encode = __esm({
|
|
|
1984
2115
|
|
|
1985
2116
|
// ../render-cli/src/fonts.ts
|
|
1986
2117
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
1987
|
-
import { dirname as dirname3, join as
|
|
2118
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
1988
2119
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1989
2120
|
async function fontFaceCss() {
|
|
1990
2121
|
if (cssCache) return cssCache;
|
|
1991
2122
|
const rules = await Promise.all(
|
|
1992
2123
|
WEIGHTS.map(async (weight) => {
|
|
1993
|
-
const data = await readFile2(
|
|
2124
|
+
const data = await readFile2(join5(FONTS_DIR, `inter-${weight}.woff2`));
|
|
1994
2125
|
return `@font-face {
|
|
1995
2126
|
font-family: "Inter";
|
|
1996
2127
|
font-style: normal;
|
|
@@ -2006,7 +2137,7 @@ var FONTS_DIR, WEIGHTS, cssCache;
|
|
|
2006
2137
|
var init_fonts = __esm({
|
|
2007
2138
|
"../render-cli/src/fonts.ts"() {
|
|
2008
2139
|
"use strict";
|
|
2009
|
-
FONTS_DIR = true ?
|
|
2140
|
+
FONTS_DIR = true ? join5(dirname3(fileURLToPath2(import.meta.url)), "..", "assets", "fonts") : join5(dirname3(fileURLToPath2(import.meta.url)), "..", "..", "..", "assets", "fonts");
|
|
2010
2141
|
WEIGHTS = [400, 700, 800];
|
|
2011
2142
|
cssCache = null;
|
|
2012
2143
|
}
|
|
@@ -2014,8 +2145,8 @@ var init_fonts = __esm({
|
|
|
2014
2145
|
|
|
2015
2146
|
// ../render-cli/src/images.ts
|
|
2016
2147
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
2017
|
-
import { existsSync as
|
|
2018
|
-
import { extname, isAbsolute as
|
|
2148
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
2149
|
+
import { extname, isAbsolute as isAbsolute3, resolve as resolve3 } from "node:path";
|
|
2019
2150
|
async function buildImageAssets(ir, sceneDir) {
|
|
2020
2151
|
const assets = {};
|
|
2021
2152
|
for (const src of collectImageSrcs(ir)) {
|
|
@@ -2025,10 +2156,10 @@ async function buildImageAssets(ir, sceneDir) {
|
|
|
2025
2156
|
`image "${src}": unsupported format "${extname(src)}" \u2014 supported: ${Object.keys(MIME).join(" ")}`
|
|
2026
2157
|
);
|
|
2027
2158
|
}
|
|
2028
|
-
const candidates = [
|
|
2159
|
+
const candidates = [isAbsolute3(src) ? src : null, resolve3(sceneDir, src)].filter(
|
|
2029
2160
|
(c) => c !== null
|
|
2030
2161
|
);
|
|
2031
|
-
const found = candidates.find((c) =>
|
|
2162
|
+
const found = candidates.find((c) => existsSync4(c));
|
|
2032
2163
|
if (!found) {
|
|
2033
2164
|
throw new Error(`image "${src}" not found (tried: ${candidates.join(", ")})`);
|
|
2034
2165
|
}
|
|
@@ -2052,19 +2183,19 @@ var init_images = __esm({
|
|
|
2052
2183
|
});
|
|
2053
2184
|
|
|
2054
2185
|
// ../render-cli/src/videos.ts
|
|
2055
|
-
import { spawn as
|
|
2056
|
-
import { mkdtemp as
|
|
2057
|
-
import { existsSync as
|
|
2058
|
-
import { tmpdir as
|
|
2059
|
-
import { extname as extname2, isAbsolute as
|
|
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";
|
|
2060
2191
|
function runFfmpeg(args) {
|
|
2061
|
-
return new Promise((
|
|
2062
|
-
const proc =
|
|
2192
|
+
return new Promise((resolve7, reject) => {
|
|
2193
|
+
const proc = spawn4("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
2063
2194
|
let stderr = "";
|
|
2064
2195
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
2065
2196
|
proc.on(
|
|
2066
2197
|
"close",
|
|
2067
|
-
(code) => code === 0 ?
|
|
2198
|
+
(code) => code === 0 ? resolve7() : reject(new Error(`ffmpeg exited ${code}:
|
|
2068
2199
|
${stderr.slice(-2e3)}`))
|
|
2069
2200
|
);
|
|
2070
2201
|
proc.on("error", reject);
|
|
@@ -2103,12 +2234,12 @@ async function buildVideoFrameAssets(ir, sceneDir, fps, duration) {
|
|
|
2103
2234
|
`video "${src}": unsupported format "${extname2(src)}" \u2014 supported: ${[...VIDEO_EXT].join(" ")}`
|
|
2104
2235
|
);
|
|
2105
2236
|
}
|
|
2106
|
-
const candidates = [
|
|
2237
|
+
const candidates = [isAbsolute4(src) ? src : null, resolve4(sceneDir, src)].filter(
|
|
2107
2238
|
(c) => c !== null
|
|
2108
2239
|
);
|
|
2109
|
-
const found = candidates.find((c) =>
|
|
2240
|
+
const found = candidates.find((c) => existsSync5(c));
|
|
2110
2241
|
if (!found) throw new Error(`video "${src}" not found (tried: ${candidates.join(", ")})`);
|
|
2111
|
-
const dir = await
|
|
2242
|
+
const dir = await mkdtemp3(join6(tmpdir4(), "reframe-vframes-"));
|
|
2112
2243
|
try {
|
|
2113
2244
|
const seconds = Math.max(1 / fps, reachBySrc.get(src) ?? duration);
|
|
2114
2245
|
await runFfmpeg([
|
|
@@ -2121,15 +2252,15 @@ async function buildVideoFrameAssets(ir, sceneDir, fps, duration) {
|
|
|
2121
2252
|
`fps=${fps},scale='min(iw,1280)':-2`,
|
|
2122
2253
|
"-q:v",
|
|
2123
2254
|
"4",
|
|
2124
|
-
|
|
2255
|
+
join6(dir, "%05d.jpg")
|
|
2125
2256
|
]);
|
|
2126
2257
|
const files = (await readdir(dir)).filter((f) => f.endsWith(".jpg")).sort();
|
|
2127
2258
|
assets[src] = await Promise.all(
|
|
2128
|
-
files.map(async (f) => `data:image/jpeg;base64,${(await readFile4(
|
|
2259
|
+
files.map(async (f) => `data:image/jpeg;base64,${(await readFile4(join6(dir, f))).toString("base64")}`)
|
|
2129
2260
|
);
|
|
2130
2261
|
if (assets[src].length === 0) throw new Error(`video "${src}": ffmpeg extracted no frames`);
|
|
2131
2262
|
} finally {
|
|
2132
|
-
await
|
|
2263
|
+
await rm3(dir, { recursive: true, force: true });
|
|
2133
2264
|
}
|
|
2134
2265
|
}
|
|
2135
2266
|
return assets;
|
|
@@ -2228,7 +2359,7 @@ var init_reframeGlobal = __esm({
|
|
|
2228
2359
|
|
|
2229
2360
|
// ../render-cli/src/frameLoop.ts
|
|
2230
2361
|
import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
|
|
2231
|
-
import { join as
|
|
2362
|
+
import { join as join7, dirname as dirname4 } from "node:path";
|
|
2232
2363
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
2233
2364
|
import { build } from "esbuild";
|
|
2234
2365
|
import { chromium } from "playwright";
|
|
@@ -2255,12 +2386,12 @@ async function browserBundle() {
|
|
|
2255
2386
|
if (true) {
|
|
2256
2387
|
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
2257
2388
|
bundleCache = await readFile7(
|
|
2258
|
-
|
|
2389
|
+
join7(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
|
|
2259
2390
|
"utf8"
|
|
2260
2391
|
);
|
|
2261
2392
|
return bundleCache;
|
|
2262
2393
|
}
|
|
2263
|
-
const entry =
|
|
2394
|
+
const entry = join7(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.ts");
|
|
2264
2395
|
const result = await build({
|
|
2265
2396
|
entryPoints: [entry],
|
|
2266
2397
|
bundle: true,
|
|
@@ -2305,7 +2436,7 @@ var init_frameLoop = __esm({
|
|
|
2305
2436
|
init_videos();
|
|
2306
2437
|
init_vclock();
|
|
2307
2438
|
init_reframeGlobal();
|
|
2308
|
-
framePath = (dir, i) =>
|
|
2439
|
+
framePath = (dir, i) => join7(dir, `${String(i).padStart(5, "0")}.png`);
|
|
2309
2440
|
bundleCache = null;
|
|
2310
2441
|
}
|
|
2311
2442
|
});
|
|
@@ -2318,9 +2449,9 @@ __export(batch_exports, {
|
|
|
2318
2449
|
parseCsv: () => parseCsv,
|
|
2319
2450
|
runBatch: () => runBatch
|
|
2320
2451
|
});
|
|
2321
|
-
import { mkdir as mkdir3, mkdtemp as
|
|
2322
|
-
import { tmpdir as
|
|
2323
|
-
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";
|
|
2324
2455
|
function overlayFromFlat(row, name) {
|
|
2325
2456
|
const doc = { reframeOverlay: 1, name };
|
|
2326
2457
|
for (const [key2, raw] of Object.entries(row)) {
|
|
@@ -2414,8 +2545,8 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2414
2545
|
try {
|
|
2415
2546
|
const rowOverlay = overlayFromFlat(row, name);
|
|
2416
2547
|
const { ir, report } = composeScene(scene2, ...opts.baseOverlays, rowOverlay);
|
|
2417
|
-
const framesDir = await
|
|
2418
|
-
const output =
|
|
2548
|
+
const framesDir = await mkdtemp4(join8(tmpdir5(), `reframe-batch-${index}-`));
|
|
2549
|
+
const output = join8(opts.outDir, `${name}.mp4`);
|
|
2419
2550
|
const plan = opts.noAudio ? null : resolveAudioPlan(compileScene(ir));
|
|
2420
2551
|
try {
|
|
2421
2552
|
const captured = await captureIr(ir, {
|
|
@@ -2427,12 +2558,12 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2427
2558
|
const videoTmp = `${output}.video.mp4`;
|
|
2428
2559
|
await encodeMp4(captured.framesDir, captured.fps, videoTmp);
|
|
2429
2560
|
await buildAudioTrack(plan, opts.scenePath ?? output, videoTmp, output);
|
|
2430
|
-
await
|
|
2561
|
+
await rm4(videoTmp, { force: true });
|
|
2431
2562
|
} else {
|
|
2432
2563
|
await encodeMp4(captured.framesDir, captured.fps, output);
|
|
2433
2564
|
}
|
|
2434
2565
|
} finally {
|
|
2435
|
-
await
|
|
2566
|
+
await rm4(framesDir, { recursive: true, force: true });
|
|
2436
2567
|
}
|
|
2437
2568
|
result = {
|
|
2438
2569
|
name,
|
|
@@ -2456,7 +2587,7 @@ async function runBatch(scene2, rows, opts) {
|
|
|
2456
2587
|
};
|
|
2457
2588
|
await Promise.all(Array.from({ length: Math.max(1, opts.concurrency) }, worker));
|
|
2458
2589
|
await writeFile4(
|
|
2459
|
-
|
|
2590
|
+
join8(opts.outDir, "batch-report.json"),
|
|
2460
2591
|
JSON.stringify({ rows: results }, null, 2)
|
|
2461
2592
|
);
|
|
2462
2593
|
return results;
|
|
@@ -2482,7 +2613,7 @@ __export(loadScene_exports, {
|
|
|
2482
2613
|
});
|
|
2483
2614
|
import { build as build2 } from "esbuild";
|
|
2484
2615
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
2485
|
-
import { dirname as dirname6, resolve as
|
|
2616
|
+
import { dirname as dirname6, resolve as resolve5 } from "node:path";
|
|
2486
2617
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
2487
2618
|
async function loadDefault(path2) {
|
|
2488
2619
|
if (path2.endsWith(".json")) return JSON.parse(await readFile6(path2, "utf8"));
|
|
@@ -2535,26 +2666,26 @@ var init_loadScene = __esm({
|
|
|
2535
2666
|
"use strict";
|
|
2536
2667
|
init_src();
|
|
2537
2668
|
HERE = dirname6(fileURLToPath4(import.meta.url));
|
|
2538
|
-
CORE_ENTRY = true ?
|
|
2669
|
+
CORE_ENTRY = true ? resolve5(HERE, "index.js") : resolve5(HERE, "..", "..", "core", "src", "index.ts");
|
|
2539
2670
|
}
|
|
2540
2671
|
});
|
|
2541
2672
|
|
|
2542
2673
|
// ../render-cli/src/reframe.ts
|
|
2543
|
-
import { spawn as
|
|
2544
|
-
import { existsSync as
|
|
2674
|
+
import { spawn as spawn5, spawnSync } from "node:child_process";
|
|
2675
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
2545
2676
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
2546
|
-
import { basename, isAbsolute as
|
|
2677
|
+
import { basename, isAbsolute as isAbsolute5, join as join9, resolve as resolve6 } from "node:path";
|
|
2547
2678
|
import { dirname as dirname7 } from "node:path";
|
|
2548
2679
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
2549
2680
|
var PACKAGED = true;
|
|
2550
2681
|
var HERE2 = dirname7(fileURLToPath5(import.meta.url));
|
|
2551
|
-
var ROOT2 = PACKAGED ?
|
|
2682
|
+
var ROOT2 = PACKAGED ? resolve6(HERE2, "..") : resolve6(HERE2, "..", "..", "..");
|
|
2552
2683
|
var USER_CWD = process.env.INIT_CWD ?? process.cwd();
|
|
2553
|
-
var RENDER_CLI = PACKAGED ?
|
|
2554
|
-
var LABELS = PACKAGED ?
|
|
2555
|
-
var PLAYER = PACKAGED ?
|
|
2556
|
-
var ANALYZE = PACKAGED ?
|
|
2557
|
-
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");
|
|
2558
2689
|
var CMD = PACKAGED ? "reframe" : "pnpm reframe";
|
|
2559
2690
|
var USAGE = `reframe \u2014 declarative motion graphics
|
|
2560
2691
|
|
|
@@ -2574,7 +2705,7 @@ usage:
|
|
|
2574
2705
|
${CMD} guide [--regen] print the scene-authoring guide (for you or your AI)
|
|
2575
2706
|
${CMD} demo run the edit-survival demo (3 mp4s into out/)
|
|
2576
2707
|
`;
|
|
2577
|
-
var userPath = (p) =>
|
|
2708
|
+
var userPath = (p) => isAbsolute5(p) ? p : resolve6(USER_CWD, p);
|
|
2578
2709
|
function fail(message) {
|
|
2579
2710
|
console.error(`error: ${message}`);
|
|
2580
2711
|
process.exit(2);
|
|
@@ -2584,9 +2715,9 @@ function preflightFfmpeg() {
|
|
|
2584
2715
|
fail("ffmpeg not found on PATH \u2014 install it first (macOS: brew install ffmpeg, debian: apt install ffmpeg)");
|
|
2585
2716
|
}
|
|
2586
2717
|
}
|
|
2587
|
-
function
|
|
2718
|
+
function run2(cmd, args, opts = {}) {
|
|
2588
2719
|
return new Promise((res) => {
|
|
2589
|
-
const proc =
|
|
2720
|
+
const proc = spawn5(cmd, args, {
|
|
2590
2721
|
cwd: opts.cwd ?? (PACKAGED ? USER_CWD : ROOT2),
|
|
2591
2722
|
stdio: ["inherit", "inherit", "pipe"],
|
|
2592
2723
|
...opts.env && { env: { ...process.env, ...opts.env } }
|
|
@@ -2676,7 +2807,7 @@ async function main() {
|
|
|
2676
2807
|
|
|
2677
2808
|
${USAGE}`);
|
|
2678
2809
|
const inputPath = userPath(input);
|
|
2679
|
-
if (!
|
|
2810
|
+
if (!existsSync6(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2680
2811
|
const mode = /\.(ts|json)$/.test(input) ? "ir" : /\.html$/.test(input) ? "html" : null;
|
|
2681
2812
|
if (!mode) {
|
|
2682
2813
|
fail(`cannot infer render mode from "${input}" \u2014 expected .ts/.json (reframe scene) or .html (GSAP page)`);
|
|
@@ -2686,17 +2817,17 @@ ${USAGE}`);
|
|
|
2686
2817
|
fail("html render requires --duration <seconds> (the page does not declare its own length)");
|
|
2687
2818
|
}
|
|
2688
2819
|
preflightFfmpeg();
|
|
2689
|
-
const outBase = PACKAGED ?
|
|
2820
|
+
const outBase = PACKAGED ? join9(USER_CWD, "out") : join9(ROOT2, "out");
|
|
2690
2821
|
let outArgs = args;
|
|
2691
2822
|
if (!args.includes("-o")) {
|
|
2692
2823
|
await mkdir4(outBase, { recursive: true });
|
|
2693
|
-
outArgs = [...args, "-o",
|
|
2824
|
+
outArgs = [...args, "-o", join9(outBase, `${basename(input).replace(/\.[^.]+$/, "")}.mp4`)];
|
|
2694
2825
|
}
|
|
2695
2826
|
outArgs = outArgs.map(
|
|
2696
2827
|
(a, i) => outArgs[i - 1] === "--overlay" || outArgs[i - 1] === "-o" ? userPath(a) : a
|
|
2697
2828
|
);
|
|
2698
2829
|
process.exit(
|
|
2699
|
-
await (PACKAGED ?
|
|
2830
|
+
await (PACKAGED ? run2(process.execPath, [RENDER_CLI, mode, inputPath, ...outArgs]) : run2("npx", ["tsx", RENDER_CLI, mode, inputPath, ...outArgs]))
|
|
2700
2831
|
);
|
|
2701
2832
|
}
|
|
2702
2833
|
case "labels": {
|
|
@@ -2705,9 +2836,9 @@ ${USAGE}`);
|
|
|
2705
2836
|
|
|
2706
2837
|
${USAGE}`);
|
|
2707
2838
|
const inputPath = userPath(input);
|
|
2708
|
-
if (!
|
|
2839
|
+
if (!existsSync6(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2709
2840
|
process.exit(
|
|
2710
|
-
await (PACKAGED ?
|
|
2841
|
+
await (PACKAGED ? run2(process.execPath, [LABELS, inputPath]) : run2("npx", ["tsx", LABELS, inputPath]))
|
|
2711
2842
|
);
|
|
2712
2843
|
}
|
|
2713
2844
|
case "player": {
|
|
@@ -2716,13 +2847,13 @@ ${USAGE}`);
|
|
|
2716
2847
|
|
|
2717
2848
|
${USAGE}`);
|
|
2718
2849
|
const inputPath = userPath(input);
|
|
2719
|
-
if (!
|
|
2850
|
+
if (!existsSync6(inputPath)) fail(`no such file: ${inputPath}`);
|
|
2720
2851
|
const oIdx = rest.indexOf("-o");
|
|
2721
|
-
const outBase = PACKAGED ?
|
|
2722
|
-
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`);
|
|
2723
2854
|
await mkdir4(dirname7(outPath), { recursive: true });
|
|
2724
2855
|
process.exit(
|
|
2725
|
-
await (PACKAGED ?
|
|
2856
|
+
await (PACKAGED ? run2(process.execPath, [PLAYER, inputPath, outPath]) : run2("npx", ["tsx", PLAYER, inputPath, outPath]))
|
|
2726
2857
|
);
|
|
2727
2858
|
}
|
|
2728
2859
|
case "logo": {
|
|
@@ -2739,7 +2870,7 @@ ${USAGE}`);
|
|
|
2739
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]`);
|
|
2740
2871
|
}
|
|
2741
2872
|
preflightFfmpeg();
|
|
2742
|
-
const { tmpdir:
|
|
2873
|
+
const { tmpdir: tmpdir6 } = await import("node:os");
|
|
2743
2874
|
const { resolveLogo: resolveLogo2, buildLogoSting: buildLogoSting2 } = await Promise.resolve().then(() => (init_logoSting(), logoSting_exports));
|
|
2744
2875
|
const num = (k) => flags[k] !== void 0 ? Number(flags[k]) : void 0;
|
|
2745
2876
|
console.log(`loading logo: ${arg} \u2026`);
|
|
@@ -2752,14 +2883,14 @@ ${USAGE}`);
|
|
|
2752
2883
|
seed: num("seed")
|
|
2753
2884
|
});
|
|
2754
2885
|
const sceneIR = buildLogoSting2(data);
|
|
2755
|
-
const tmp =
|
|
2886
|
+
const tmp = join9(tmpdir6(), `reframe-logo-${slug}-${process.pid}.json`);
|
|
2756
2887
|
await writeFile5(tmp, JSON.stringify(sceneIR));
|
|
2757
|
-
const outBase = PACKAGED ?
|
|
2758
|
-
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`);
|
|
2759
2890
|
await mkdir4(dirname7(out), { recursive: true });
|
|
2760
2891
|
console.log(`rendering ${data.name} (${data.paths.length} path${data.paths.length > 1 ? "s" : ""}, motion: ${data.motion ?? "reveal-orbit"}) \u2192 ${out}`);
|
|
2761
2892
|
process.exit(
|
|
2762
|
-
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"]))
|
|
2763
2894
|
);
|
|
2764
2895
|
}
|
|
2765
2896
|
case "batch": {
|
|
@@ -2767,9 +2898,9 @@ ${USAGE}`);
|
|
|
2767
2898
|
if (!sceneArg || !dataArg) fail(`usage: ${CMD} batch <scene.ts> <data.json|csv> [...]`);
|
|
2768
2899
|
const scenePath = userPath(sceneArg);
|
|
2769
2900
|
const dataPath = userPath(dataArg);
|
|
2770
|
-
for (const p of [scenePath, dataPath]) if (!
|
|
2901
|
+
for (const p of [scenePath, dataPath]) if (!existsSync6(p)) fail(`no such file: ${p}`);
|
|
2771
2902
|
preflightFfmpeg();
|
|
2772
|
-
let outDir = PACKAGED ?
|
|
2903
|
+
let outDir = PACKAGED ? join9(USER_CWD, "out", "batch") : join9(ROOT2, "out", "batch");
|
|
2773
2904
|
let concurrency = 3;
|
|
2774
2905
|
let fps;
|
|
2775
2906
|
const baseOverlayPaths = [];
|
|
@@ -2808,7 +2939,7 @@ ${USAGE}`);
|
|
|
2808
2939
|
const orphaned = results.filter((r) => !r.error && r.orphans.length > 0).length;
|
|
2809
2940
|
console.log(
|
|
2810
2941
|
`
|
|
2811
|
-
${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")}`
|
|
2812
2943
|
);
|
|
2813
2944
|
process.exit(failed > 0 ? 1 : 0);
|
|
2814
2945
|
}
|
|
@@ -2819,15 +2950,15 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2819
2950
|
if (PACKAGED) {
|
|
2820
2951
|
const { createRequire } = await import("node:module");
|
|
2821
2952
|
const vitePkg = createRequire(import.meta.url).resolve("vite/package.json");
|
|
2822
|
-
const viteBin =
|
|
2953
|
+
const viteBin = join9(dirname7(vitePkg), "bin", "vite.js");
|
|
2823
2954
|
process.exit(
|
|
2824
|
-
await
|
|
2955
|
+
await run2(process.execPath, [viteBin, join9(ROOT2, "preview")], {
|
|
2825
2956
|
env: { REFRAME_SCENE_DIR: USER_CWD }
|
|
2826
2957
|
})
|
|
2827
2958
|
);
|
|
2828
2959
|
}
|
|
2829
2960
|
process.exit(
|
|
2830
|
-
await
|
|
2961
|
+
await run2("pnpm", ["--filter", "@reframe/preview", "dev"], {
|
|
2831
2962
|
env: { REFRAME_SCENE_DIR: USER_CWD }
|
|
2832
2963
|
})
|
|
2833
2964
|
);
|
|
@@ -2839,10 +2970,10 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2839
2970
|
fail(`scene name must be kebab-case (a-z, 0-9, -): got "${name}"`);
|
|
2840
2971
|
}
|
|
2841
2972
|
const inRepo = USER_CWD === ROOT2 || USER_CWD.startsWith(ROOT2 + "/");
|
|
2842
|
-
const targetDir = inRepo ?
|
|
2843
|
-
const target =
|
|
2973
|
+
const targetDir = inRepo ? join9(ROOT2, "examples", "scenes") : USER_CWD;
|
|
2974
|
+
const target = join9(targetDir, `${name}.ts`);
|
|
2844
2975
|
const shown = inRepo ? `examples/scenes/${name}.ts` : `${name}.ts`;
|
|
2845
|
-
if (
|
|
2976
|
+
if (existsSync6(target)) fail(`${shown} already exists`);
|
|
2846
2977
|
const id = name.split("-")[0] ?? name;
|
|
2847
2978
|
await writeFile5(target, SCENE_TEMPLATE(name, id));
|
|
2848
2979
|
console.log(`created ${shown}
|
|
@@ -2856,7 +2987,7 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2856
2987
|
if (!input) fail(`usage: ${CMD} motion <mp4|framesDir> [...args]`);
|
|
2857
2988
|
preflightFfmpeg();
|
|
2858
2989
|
process.exit(
|
|
2859
|
-
await (PACKAGED ?
|
|
2990
|
+
await (PACKAGED ? run2(process.execPath, [ANALYZE, userPath(input), ...rest.slice(1)]) : run2("npx", ["tsx", ANALYZE, userPath(input), ...rest.slice(1)]))
|
|
2860
2991
|
);
|
|
2861
2992
|
}
|
|
2862
2993
|
case "trace": {
|
|
@@ -2867,11 +2998,11 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2867
2998
|
(a, i) => rest.slice(1)[i - 1] === "--apply" || rest.slice(1)[i - 1] === "-o" ? userPath(a) : a
|
|
2868
2999
|
);
|
|
2869
3000
|
process.exit(
|
|
2870
|
-
await (PACKAGED ?
|
|
3001
|
+
await (PACKAGED ? run2(process.execPath, [TRACE, userPath(input), ...args]) : run2("npx", ["tsx", TRACE, userPath(input), ...args]))
|
|
2871
3002
|
);
|
|
2872
3003
|
}
|
|
2873
3004
|
case "guide": {
|
|
2874
|
-
const file = rest.includes("--regen") ? PACKAGED ?
|
|
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");
|
|
2875
3006
|
const { readFile: readFile7 } = await import("node:fs/promises");
|
|
2876
3007
|
process.stdout.write(await readFile7(file, "utf8"));
|
|
2877
3008
|
return;
|
|
@@ -2884,7 +3015,7 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
2884
3015
|
}
|
|
2885
3016
|
preflightFfmpeg();
|
|
2886
3017
|
process.exit(
|
|
2887
|
-
await
|
|
3018
|
+
await run2("npx", ["tsx", join9(ROOT2, "examples", "scripts", "demo-edit-survival.ts")])
|
|
2888
3019
|
);
|
|
2889
3020
|
default:
|
|
2890
3021
|
console.log(USAGE);
|