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.
@@ -342,7 +342,7 @@
342
342
  line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
343
343
  text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
344
344
  image: [...COMMON_PROPS, "src", "width", "height", "fit"],
345
- video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart"],
345
+ video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
346
346
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
347
347
  group: COMMON_PROPS
348
348
  };
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env tsx
2
2
 
3
3
  // ../render-cli/src/cli.ts
4
- import { mkdtemp as mkdtemp4, readFile as readFile5, rm as rm4 } from "node:fs/promises";
5
- import { tmpdir as tmpdir5 } from "node:os";
6
- import { basename, dirname as dirname7, join as join7, resolve as resolve5 } from "node:path";
4
+ import { mkdtemp as mkdtemp5, readFile as readFile5, rm as rm5 } from "node:fs/promises";
5
+ import { tmpdir as tmpdir6 } from "node:os";
6
+ import { basename, dirname as dirname7, join as join9, resolve as resolve6 } from "node:path";
7
7
 
8
8
  // ../core/src/ir.ts
9
9
  var DEFAULT_CROSSFADE = 0.5;
@@ -347,7 +347,7 @@ var PROPS_BY_TYPE = {
347
347
  line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
348
348
  text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
349
349
  image: [...COMMON_PROPS, "src", "width", "height", "fit"],
350
- video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart"],
350
+ video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume"],
351
351
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
352
352
  group: COMMON_PROPS
353
353
  };
@@ -885,11 +885,34 @@ var SFX_DURATION = {
885
885
  thud: 0.25
886
886
  };
887
887
  var FILE_CUE_DURATION = 0.4;
888
+ function collectClipAudio(ir, duration, warnings) {
889
+ const out = [];
890
+ const walk = (nodes) => {
891
+ for (const node of nodes) {
892
+ if (node.type === "video") {
893
+ const gain = node.props.volume ?? 1;
894
+ const start = node.props.start ?? 0;
895
+ if (gain <= 0) continue;
896
+ if (start >= duration) {
897
+ warnings.push(`video "${node.id}": start ${start.toFixed(2)}s past the scene end \u2014 audio dropped`);
898
+ continue;
899
+ }
900
+ out.push({ nodeId: node.id, src: node.props.src, start, rate: node.props.rate ?? 1, clipStart: node.props.clipStart ?? 0, gain });
901
+ }
902
+ if (node.type === "group") walk(node.children);
903
+ }
904
+ };
905
+ walk(ir.nodes);
906
+ return out;
907
+ }
888
908
  function resolveAudioPlan(compiled) {
889
909
  const audio = compiled.ir.audio;
890
- if (!audio || !audio.bgm && (audio.cues ?? []).length === 0) return null;
891
910
  const warnings = [];
892
911
  const duration = compiled.duration;
912
+ const clipAudio = collectClipAudio(compiled.ir, duration, warnings);
913
+ if (!audio || !audio.bgm && (audio.cues ?? []).length === 0) {
914
+ return clipAudio.length === 0 ? null : { duration, bgm: null, cues: [], duckWindows: [], clipAudio, warnings };
915
+ }
893
916
  const cues = [];
894
917
  for (const [index, cue] of (audio.cues ?? []).entries()) {
895
918
  let anchor;
@@ -925,6 +948,7 @@ function resolveAudioPlan(compiled) {
925
948
  bgm: resolveBgm(audio.bgm),
926
949
  cues,
927
950
  duckWindows: mergeDuckWindows(cues, duration),
951
+ clipAudio,
928
952
  warnings
929
953
  };
930
954
  }
@@ -958,6 +982,7 @@ function resolveCompositionAudioPlan(comp) {
958
982
  const duration = comp.duration;
959
983
  const warnings = [];
960
984
  const cues = [];
985
+ const clipAudio = [];
961
986
  for (const placement of comp.scenes) {
962
987
  const plan = resolveAudioPlan(placement.compiled);
963
988
  if (!plan) continue;
@@ -970,6 +995,11 @@ function resolveCompositionAudioPlan(comp) {
970
995
  if (t >= duration) continue;
971
996
  cues.push({ ...cue, t });
972
997
  }
998
+ for (const clip of plan.clipAudio) {
999
+ const start = clip.start + placement.start;
1000
+ if (start >= duration) continue;
1001
+ clipAudio.push({ ...clip, start });
1002
+ }
973
1003
  }
974
1004
  for (const [index, cue] of (audio?.cues ?? []).entries()) {
975
1005
  if (typeof cue.at !== "number") {
@@ -989,13 +1019,14 @@ function resolveCompositionAudioPlan(comp) {
989
1019
  source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
990
1020
  });
991
1021
  }
992
- if (!audio?.bgm && cues.length === 0) return null;
1022
+ if (!audio?.bgm && cues.length === 0 && clipAudio.length === 0) return null;
993
1023
  cues.sort((a, b) => a.t - b.t);
994
1024
  return {
995
1025
  duration,
996
1026
  bgm: resolveBgm(audio?.bgm),
997
1027
  cues,
998
1028
  duckWindows: mergeDuckWindows(cues, duration),
1029
+ clipAudio,
999
1030
  warnings
1000
1031
  };
1001
1032
  }
@@ -1083,7 +1114,9 @@ function collectVideoSrcs(ir) {
1083
1114
  }
1084
1115
 
1085
1116
  // ../render-cli/src/audio/index.ts
1086
- import { dirname as dirname2 } from "node:path";
1117
+ import { copyFile, mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
1118
+ import { tmpdir as tmpdir3 } from "node:os";
1119
+ import { dirname as dirname2, join as join4 } from "node:path";
1087
1120
 
1088
1121
  // ../render-cli/src/audio/sfx.ts
1089
1122
  import { mkdir, rename, writeFile } from "node:fs/promises";
@@ -1305,12 +1338,85 @@ async function resolveBgmFile(source, duration, sceneDir) {
1305
1338
  return writeCached(`ambient-pad-${duration.toFixed(2)}`, () => synthAmbientPad(duration));
1306
1339
  }
1307
1340
 
1308
- // ../render-cli/src/audio/mux.ts
1341
+ // ../render-cli/src/audio/clip.ts
1309
1342
  import { spawn } from "node:child_process";
1343
+ import { existsSync as existsSync2 } from "node:fs";
1344
+ import { isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
1345
+ function run(cmd, args) {
1346
+ return new Promise((res, reject) => {
1347
+ const proc = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
1348
+ let stdout = "", stderr = "";
1349
+ proc.stdout.on("data", (d) => stdout += d.toString());
1350
+ proc.stderr.on("data", (d) => stderr += d.toString());
1351
+ proc.on("close", (code) => res({ code: code ?? 1, stdout, stderr }));
1352
+ proc.on("error", reject);
1353
+ });
1354
+ }
1355
+ async function hasAudioStream(file) {
1356
+ const { stdout } = await run("ffprobe", [
1357
+ "-v",
1358
+ "error",
1359
+ "-select_streams",
1360
+ "a",
1361
+ "-show_entries",
1362
+ "stream=index",
1363
+ "-of",
1364
+ "csv=p=0",
1365
+ file
1366
+ ]);
1367
+ return stdout.trim().length > 0;
1368
+ }
1369
+ function resolveSrc(src, sceneDir) {
1370
+ const candidates = [isAbsolute2(src) ? src : null, resolve2(sceneDir, src)].filter(
1371
+ (c) => c !== null
1372
+ );
1373
+ const found = candidates.find((c) => existsSync2(c));
1374
+ if (!found) throw new Error(`video "${src}" not found (tried: ${candidates.join(", ")})`);
1375
+ return found;
1376
+ }
1377
+ async function resolveClipAudio(entry, sceneDir, workDir) {
1378
+ const src = resolveSrc(entry.src, sceneDir);
1379
+ if (!await hasAudioStream(src)) return null;
1380
+ const out = join2(workDir, `clip-${entry.nodeId}.wav`);
1381
+ const { code, stderr } = await run("ffmpeg", [
1382
+ "-y",
1383
+ "-i",
1384
+ src,
1385
+ "-vn",
1386
+ "-ac",
1387
+ "2",
1388
+ "-ar",
1389
+ "44100",
1390
+ "-c:a",
1391
+ "pcm_s16le",
1392
+ out
1393
+ ]);
1394
+ if (code !== 0) throw new Error(`clip audio extract failed for "${entry.src}":
1395
+ ${stderr.slice(-1500)}`);
1396
+ return out;
1397
+ }
1398
+
1399
+ // ../render-cli/src/audio/mux.ts
1400
+ import { spawn as spawn2 } from "node:child_process";
1310
1401
  import { mkdtemp, rm, writeFile as writeFile2 } from "node:fs/promises";
1311
1402
  import { tmpdir as tmpdir2 } from "node:os";
1312
- import { join as join2 } from "node:path";
1403
+ import { join as join3 } from "node:path";
1313
1404
  var FORMAT = "aformat=sample_rates=44100:channel_layouts=stereo";
1405
+ function atempoChain(rate) {
1406
+ if (!(rate > 0) || rate === 1) return [];
1407
+ const out = [];
1408
+ let r = rate;
1409
+ while (r > 2) {
1410
+ out.push("atempo=2.0");
1411
+ r /= 2;
1412
+ }
1413
+ while (r < 0.5) {
1414
+ out.push("atempo=0.5");
1415
+ r /= 0.5;
1416
+ }
1417
+ out.push(`atempo=${r.toFixed(4)}`);
1418
+ return out;
1419
+ }
1314
1420
  function buildFilterGraph(plan, inputs) {
1315
1421
  const lines = [];
1316
1422
  const mixIn = ["[anchor]"];
@@ -1343,15 +1449,25 @@ function buildFilterGraph(plan, inputs) {
1343
1449
  mixIn.push(`[c${i}]`);
1344
1450
  inputIndex++;
1345
1451
  });
1452
+ (inputs.clipFiles ?? []).forEach(({ audio }, i) => {
1453
+ const chain = [];
1454
+ if (audio.clipStart > 0) chain.push(`atrim=start=${audio.clipStart.toFixed(3)}`, "asetpts=PTS-STARTPTS");
1455
+ chain.push(...atempoChain(audio.rate), FORMAT, `volume=${audio.gain}`);
1456
+ const delayMs = Math.round(audio.start * 1e3);
1457
+ if (delayMs > 0) chain.push(`adelay=${delayMs}:all=1`);
1458
+ lines.push(`[${inputIndex}:a]${chain.join(",")}[k${i}]`);
1459
+ mixIn.push(`[k${i}]`);
1460
+ inputIndex++;
1461
+ });
1346
1462
  lines.push(
1347
1463
  `${mixIn.join("")}amix=inputs=${mixIn.length}:duration=first:normalize=0,alimiter=limit=0.891,aresample=async=1:first_pts=0[aout]`
1348
1464
  );
1349
1465
  return lines.join(";\n");
1350
1466
  }
1351
1467
  async function muxAudio(videoIn, plan, inputs, outFile) {
1352
- const work = await mkdtemp(join2(tmpdir2(), "reframe-mux-"));
1468
+ const work = await mkdtemp(join3(tmpdir2(), "reframe-mux-"));
1353
1469
  try {
1354
- const graphFile = join2(work, "graph.txt");
1470
+ const graphFile = join3(work, "graph.txt");
1355
1471
  await writeFile2(graphFile, buildFilterGraph(plan, inputs));
1356
1472
  const args = [
1357
1473
  "-y",
@@ -1359,6 +1475,7 @@ async function muxAudio(videoIn, plan, inputs, outFile) {
1359
1475
  videoIn,
1360
1476
  ...plan.bgm && inputs.bgmFile ? ["-i", inputs.bgmFile] : [],
1361
1477
  ...inputs.cueFiles.flatMap((f) => ["-i", f]),
1478
+ ...(inputs.clipFiles ?? []).flatMap((c) => ["-i", c.file]),
1362
1479
  "-filter_complex_script",
1363
1480
  graphFile,
1364
1481
  "-map",
@@ -1377,7 +1494,7 @@ async function muxAudio(videoIn, plan, inputs, outFile) {
1377
1494
  outFile
1378
1495
  ];
1379
1496
  await new Promise((resolvePromise, reject) => {
1380
- const proc = spawn("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1497
+ const proc = spawn2("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1381
1498
  let stderr = "";
1382
1499
  proc.stderr.on("data", (d) => stderr += d.toString());
1383
1500
  proc.on("close", (code) => {
@@ -1397,17 +1514,32 @@ async function buildAudioTrack(plan, scenePath, videoIn, outFile) {
1397
1514
  const sceneDir = dirname2(scenePath);
1398
1515
  const cueFiles = await Promise.all(plan.cues.map((cue) => resolveCueFile(cue, sceneDir)));
1399
1516
  const bgmFile = plan.bgm ? await resolveBgmFile(plan.bgm.source, plan.duration, sceneDir) : null;
1400
- await muxAudio(videoIn, plan, { cueFiles, bgmFile }, outFile);
1517
+ const work = await mkdtemp2(join4(tmpdir3(), "reframe-clipaudio-"));
1518
+ try {
1519
+ const clipFiles = [];
1520
+ for (const entry of plan.clipAudio) {
1521
+ const file = await resolveClipAudio(entry, sceneDir, work);
1522
+ if (file) clipFiles.push({ audio: entry, file });
1523
+ else console.error(`audio: video "${entry.nodeId}" has no audio track \u2014 skipped`);
1524
+ }
1525
+ if (!plan.bgm && plan.cues.length === 0 && clipFiles.length === 0) {
1526
+ await copyFile(videoIn, outFile);
1527
+ return;
1528
+ }
1529
+ await muxAudio(videoIn, plan, { cueFiles, bgmFile, clipFiles }, outFile);
1530
+ } finally {
1531
+ await rm2(work, { recursive: true, force: true });
1532
+ }
1401
1533
  }
1402
1534
 
1403
1535
  // ../render-cli/src/composition.ts
1404
- import { spawn as spawn4 } from "node:child_process";
1405
- import { copyFile, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
1406
- import { tmpdir as tmpdir4 } from "node:os";
1407
- import { dirname as dirname5, join as join6 } from "node:path";
1536
+ import { spawn as spawn5 } from "node:child_process";
1537
+ import { copyFile as copyFile2, mkdtemp as mkdtemp4, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
1538
+ import { tmpdir as tmpdir5 } from "node:os";
1539
+ import { dirname as dirname5, join as join8 } from "node:path";
1408
1540
 
1409
1541
  // ../render-cli/src/encode.ts
1410
- import { spawn as spawn2 } from "node:child_process";
1542
+ import { spawn as spawn3 } from "node:child_process";
1411
1543
  async function encodeMp4(framesDir, fps, outFile) {
1412
1544
  const args = [
1413
1545
  "-y",
@@ -1427,12 +1559,12 @@ async function encodeMp4(framesDir, fps, outFile) {
1427
1559
  "+faststart",
1428
1560
  outFile
1429
1561
  ];
1430
- await new Promise((resolve6, reject) => {
1431
- const proc = spawn2("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1562
+ await new Promise((resolve7, reject) => {
1563
+ const proc = spawn3("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1432
1564
  let stderr = "";
1433
1565
  proc.stderr.on("data", (d) => stderr += d.toString());
1434
1566
  proc.on("close", (code) => {
1435
- if (code === 0) resolve6();
1567
+ if (code === 0) resolve7();
1436
1568
  else reject(new Error(`ffmpeg exited with ${code}:
1437
1569
  ${stderr.slice(-2e3)}`));
1438
1570
  });
@@ -1442,23 +1574,23 @@ ${stderr.slice(-2e3)}`));
1442
1574
 
1443
1575
  // ../render-cli/src/frameLoop.ts
1444
1576
  import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
1445
- import { join as join5, dirname as dirname4 } from "node:path";
1577
+ import { join as join7, dirname as dirname4 } from "node:path";
1446
1578
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
1447
1579
  import { build } from "esbuild";
1448
1580
  import { chromium } from "playwright";
1449
1581
 
1450
1582
  // ../render-cli/src/fonts.ts
1451
1583
  import { readFile } from "node:fs/promises";
1452
- import { dirname as dirname3, join as join3 } from "node:path";
1584
+ import { dirname as dirname3, join as join5 } from "node:path";
1453
1585
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1454
- var FONTS_DIR = true ? join3(dirname3(fileURLToPath2(import.meta.url)), "..", "assets", "fonts") : join3(dirname3(fileURLToPath2(import.meta.url)), "..", "..", "..", "assets", "fonts");
1586
+ var FONTS_DIR = true ? join5(dirname3(fileURLToPath2(import.meta.url)), "..", "assets", "fonts") : join5(dirname3(fileURLToPath2(import.meta.url)), "..", "..", "..", "assets", "fonts");
1455
1587
  var WEIGHTS = [400, 700, 800];
1456
1588
  var cssCache = null;
1457
1589
  async function fontFaceCss() {
1458
1590
  if (cssCache) return cssCache;
1459
1591
  const rules = await Promise.all(
1460
1592
  WEIGHTS.map(async (weight) => {
1461
- const data = await readFile(join3(FONTS_DIR, `inter-${weight}.woff2`));
1593
+ const data = await readFile(join5(FONTS_DIR, `inter-${weight}.woff2`));
1462
1594
  return `@font-face {
1463
1595
  font-family: "Inter";
1464
1596
  font-style: normal;
@@ -1473,8 +1605,8 @@ async function fontFaceCss() {
1473
1605
 
1474
1606
  // ../render-cli/src/images.ts
1475
1607
  import { readFile as readFile2 } from "node:fs/promises";
1476
- import { existsSync as existsSync2 } from "node:fs";
1477
- import { extname, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
1608
+ import { existsSync as existsSync3 } from "node:fs";
1609
+ import { extname, isAbsolute as isAbsolute3, resolve as resolve3 } from "node:path";
1478
1610
  var MIME = {
1479
1611
  ".png": "image/png",
1480
1612
  ".jpg": "image/jpeg",
@@ -1490,10 +1622,10 @@ async function buildImageAssets(ir, sceneDir) {
1490
1622
  `image "${src}": unsupported format "${extname(src)}" \u2014 supported: ${Object.keys(MIME).join(" ")}`
1491
1623
  );
1492
1624
  }
1493
- const candidates = [isAbsolute2(src) ? src : null, resolve2(sceneDir, src)].filter(
1625
+ const candidates = [isAbsolute3(src) ? src : null, resolve3(sceneDir, src)].filter(
1494
1626
  (c) => c !== null
1495
1627
  );
1496
- const found = candidates.find((c) => existsSync2(c));
1628
+ const found = candidates.find((c) => existsSync3(c));
1497
1629
  if (!found) {
1498
1630
  throw new Error(`image "${src}" not found (tried: ${candidates.join(", ")})`);
1499
1631
  }
@@ -1504,20 +1636,20 @@ async function buildImageAssets(ir, sceneDir) {
1504
1636
  }
1505
1637
 
1506
1638
  // ../render-cli/src/videos.ts
1507
- import { spawn as spawn3 } from "node:child_process";
1508
- import { mkdtemp as mkdtemp2, readFile as readFile3, readdir, rm as rm2 } from "node:fs/promises";
1509
- import { existsSync as existsSync3 } from "node:fs";
1510
- import { tmpdir as tmpdir3 } from "node:os";
1511
- import { extname as extname2, isAbsolute as isAbsolute3, join as join4, resolve as resolve3 } from "node:path";
1639
+ import { spawn as spawn4 } from "node:child_process";
1640
+ import { mkdtemp as mkdtemp3, readFile as readFile3, readdir, rm as rm3 } from "node:fs/promises";
1641
+ import { existsSync as existsSync4 } from "node:fs";
1642
+ import { tmpdir as tmpdir4 } from "node:os";
1643
+ import { extname as extname2, isAbsolute as isAbsolute4, join as join6, resolve as resolve4 } from "node:path";
1512
1644
  var VIDEO_EXT = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".m4v", ".mkv"]);
1513
1645
  function runFfmpeg(args) {
1514
- return new Promise((resolve6, reject) => {
1515
- const proc = spawn3("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1646
+ return new Promise((resolve7, reject) => {
1647
+ const proc = spawn4("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1516
1648
  let stderr = "";
1517
1649
  proc.stderr.on("data", (d) => stderr += d.toString());
1518
1650
  proc.on(
1519
1651
  "close",
1520
- (code) => code === 0 ? resolve6() : reject(new Error(`ffmpeg exited ${code}:
1652
+ (code) => code === 0 ? resolve7() : reject(new Error(`ffmpeg exited ${code}:
1521
1653
  ${stderr.slice(-2e3)}`))
1522
1654
  );
1523
1655
  proc.on("error", reject);
@@ -1556,12 +1688,12 @@ async function buildVideoFrameAssets(ir, sceneDir, fps, duration) {
1556
1688
  `video "${src}": unsupported format "${extname2(src)}" \u2014 supported: ${[...VIDEO_EXT].join(" ")}`
1557
1689
  );
1558
1690
  }
1559
- const candidates = [isAbsolute3(src) ? src : null, resolve3(sceneDir, src)].filter(
1691
+ const candidates = [isAbsolute4(src) ? src : null, resolve4(sceneDir, src)].filter(
1560
1692
  (c) => c !== null
1561
1693
  );
1562
- const found = candidates.find((c) => existsSync3(c));
1694
+ const found = candidates.find((c) => existsSync4(c));
1563
1695
  if (!found) throw new Error(`video "${src}" not found (tried: ${candidates.join(", ")})`);
1564
- const dir = await mkdtemp2(join4(tmpdir3(), "reframe-vframes-"));
1696
+ const dir = await mkdtemp3(join6(tmpdir4(), "reframe-vframes-"));
1565
1697
  try {
1566
1698
  const seconds = Math.max(1 / fps, reachBySrc.get(src) ?? duration);
1567
1699
  await runFfmpeg([
@@ -1574,15 +1706,15 @@ async function buildVideoFrameAssets(ir, sceneDir, fps, duration) {
1574
1706
  `fps=${fps},scale='min(iw,1280)':-2`,
1575
1707
  "-q:v",
1576
1708
  "4",
1577
- join4(dir, "%05d.jpg")
1709
+ join6(dir, "%05d.jpg")
1578
1710
  ]);
1579
1711
  const files = (await readdir(dir)).filter((f) => f.endsWith(".jpg")).sort();
1580
1712
  assets[src] = await Promise.all(
1581
- files.map(async (f) => `data:image/jpeg;base64,${(await readFile3(join4(dir, f))).toString("base64")}`)
1713
+ files.map(async (f) => `data:image/jpeg;base64,${(await readFile3(join6(dir, f))).toString("base64")}`)
1582
1714
  );
1583
1715
  if (assets[src].length === 0) throw new Error(`video "${src}": ffmpeg extracted no frames`);
1584
1716
  } finally {
1585
- await rm2(dir, { recursive: true, force: true });
1717
+ await rm3(dir, { recursive: true, force: true });
1586
1718
  }
1587
1719
  }
1588
1720
  return assets;
@@ -1666,7 +1798,7 @@ async function injectFonts(page) {
1666
1798
  await document.fonts.ready;
1667
1799
  });
1668
1800
  }
1669
- var framePath = (dir, i) => join5(dir, `${String(i).padStart(5, "0")}.png`);
1801
+ var framePath = (dir, i) => join7(dir, `${String(i).padStart(5, "0")}.png`);
1670
1802
  async function withPage(size, fn) {
1671
1803
  const browser = await chromium.launch({
1672
1804
  args: ["--force-color-profile=srgb", "--font-render-hinting=none"]
@@ -1684,12 +1816,12 @@ async function browserBundle() {
1684
1816
  if (true) {
1685
1817
  const { readFile: readFile6 } = await import("node:fs/promises");
1686
1818
  bundleCache = await readFile6(
1687
- join5(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
1819
+ join7(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
1688
1820
  "utf8"
1689
1821
  );
1690
1822
  return bundleCache;
1691
1823
  }
1692
- const entry = join5(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.ts");
1824
+ const entry = join7(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.ts");
1693
1825
  const result = await build({
1694
1826
  entryPoints: [entry],
1695
1827
  bundle: true,
@@ -1750,24 +1882,24 @@ async function captureHtml(htmlPath, opts) {
1750
1882
 
1751
1883
  // ../render-cli/src/composition.ts
1752
1884
  function runFfmpeg2(args) {
1753
- return new Promise((resolve6, reject) => {
1754
- const proc = spawn4("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1885
+ return new Promise((resolve7, reject) => {
1886
+ const proc = spawn5("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1755
1887
  let stderr = "";
1756
1888
  proc.stderr.on("data", (d) => stderr += d.toString());
1757
- proc.on("close", (code) => code === 0 ? resolve6() : reject(new Error(`ffmpeg exited ${code}:
1889
+ proc.on("close", (code) => code === 0 ? resolve7() : reject(new Error(`ffmpeg exited ${code}:
1758
1890
  ${stderr.slice(-2e3)}`)));
1759
1891
  proc.on("error", reject);
1760
1892
  });
1761
1893
  }
1762
1894
  var sanitize = (id) => id.replace(/[^a-z0-9_-]/gi, "_");
1763
1895
  async function renderSceneVideo(scene, sceneDir, fps, out) {
1764
- const framesDir = await mkdtemp3(join6(tmpdir4(), "reframe-frames-"));
1896
+ const framesDir = await mkdtemp4(join8(tmpdir5(), "reframe-frames-"));
1765
1897
  try {
1766
1898
  const result = await captureIr(scene, { framesDir, sceneDir, ...fps !== void 0 && { fps } });
1767
1899
  await encodeMp4(result.framesDir, result.fps, out);
1768
1900
  return { fps: result.fps, frameCount: result.frameCount };
1769
1901
  } finally {
1770
- await rm3(framesDir, { recursive: true, force: true });
1902
+ await rm4(framesDir, { recursive: true, force: true });
1771
1903
  }
1772
1904
  }
1773
1905
  async function renderStandaloneScene(scene, sceneDir, fps, noAudio, out) {
@@ -1775,15 +1907,15 @@ async function renderStandaloneScene(scene, sceneDir, fps, noAudio, out) {
1775
1907
  if (plan) {
1776
1908
  const videoOut = `${out}.video.mp4`;
1777
1909
  await renderSceneVideo(scene, sceneDir, fps, videoOut);
1778
- await buildAudioTrack(plan, join6(sceneDir, "scene"), videoOut, out);
1779
- await rm3(videoOut, { force: true });
1910
+ await buildAudioTrack(plan, join8(sceneDir, "scene"), videoOut, out);
1911
+ await rm4(videoOut, { force: true });
1780
1912
  } else {
1781
1913
  await renderSceneVideo(scene, sceneDir, fps, out);
1782
1914
  }
1783
1915
  }
1784
1916
  async function concatVideos(files, out) {
1785
1917
  if (files.length === 1) {
1786
- await copyFile(files[0], out);
1918
+ await copyFile2(files[0], out);
1787
1919
  return;
1788
1920
  }
1789
1921
  const list = `${out}.concat.txt`;
@@ -1819,7 +1951,7 @@ async function combineWithTransitions(videos, out, tmp) {
1819
1951
  let accDur = videos[0].placement.duration;
1820
1952
  for (let i = 1; i < videos.length; i++) {
1821
1953
  const { overlap, duration } = videos[i].placement;
1822
- const step = join6(tmp, `step${i}.mp4`);
1954
+ const step = join8(tmp, `step${i}.mp4`);
1823
1955
  if (overlap <= 0) {
1824
1956
  await concatVideos([acc, videos[i].file], step);
1825
1957
  accDur += duration;
@@ -1830,7 +1962,7 @@ async function combineWithTransitions(videos, out, tmp) {
1830
1962
  }
1831
1963
  acc = step;
1832
1964
  }
1833
- await copyFile(acc, out);
1965
+ await copyFile2(acc, out);
1834
1966
  }
1835
1967
  async function renderComposition(comp, opts) {
1836
1968
  const cc = compileComposition(comp);
@@ -1841,15 +1973,15 @@ async function renderComposition(comp, opts) {
1841
1973
  await renderStandaloneScene(p.scene, sceneDir, opts.fps, opts.noAudio, opts.out);
1842
1974
  return { duration: p.duration, sceneCount: 1 };
1843
1975
  }
1844
- const tmp = await mkdtemp3(join6(tmpdir4(), "reframe-comp-"));
1976
+ const tmp = await mkdtemp4(join8(tmpdir5(), "reframe-comp-"));
1845
1977
  try {
1846
1978
  const videos = [];
1847
1979
  for (const p of cc.scenes) {
1848
- const file = join6(tmp, `${sanitize(p.id)}.mp4`);
1980
+ const file = join8(tmp, `${sanitize(p.id)}.mp4`);
1849
1981
  const { fps } = await renderSceneVideo(p.scene, sceneDir, opts.fps, file);
1850
1982
  videos.push({ id: p.id, file, placement: p, fps });
1851
1983
  }
1852
- const combined = join6(tmp, "combined.mp4");
1984
+ const combined = join8(tmp, "combined.mp4");
1853
1985
  const allCut = cc.scenes.every((s) => s.overlap === 0);
1854
1986
  if (allCut) await concatVideos(videos.map((v) => v.file), combined);
1855
1987
  else await combineWithTransitions(videos, combined, tmp);
@@ -1859,24 +1991,24 @@ async function renderComposition(comp, opts) {
1859
1991
  for (const w of plan.warnings) console.error(`audio: ${w}`);
1860
1992
  await buildAudioTrack(plan, opts.compositionPath, combined, opts.out);
1861
1993
  } else {
1862
- await copyFile(combined, opts.out);
1994
+ await copyFile2(combined, opts.out);
1863
1995
  }
1864
1996
  } else {
1865
- await copyFile(combined, opts.out);
1997
+ await copyFile2(combined, opts.out);
1866
1998
  }
1867
1999
  return { duration: cc.duration, sceneCount: cc.scenes.length };
1868
2000
  } finally {
1869
- await rm3(tmp, { recursive: true, force: true });
2001
+ await rm4(tmp, { recursive: true, force: true });
1870
2002
  }
1871
2003
  }
1872
2004
 
1873
2005
  // ../render-cli/src/loadScene.ts
1874
2006
  import { build as build2 } from "esbuild";
1875
2007
  import { readFile as readFile4 } from "node:fs/promises";
1876
- import { dirname as dirname6, resolve as resolve4 } from "node:path";
2008
+ import { dirname as dirname6, resolve as resolve5 } from "node:path";
1877
2009
  import { fileURLToPath as fileURLToPath4 } from "node:url";
1878
2010
  var HERE = dirname6(fileURLToPath4(import.meta.url));
1879
- var CORE_ENTRY = true ? resolve4(HERE, "index.js") : resolve4(HERE, "..", "..", "core", "src", "index.ts");
2011
+ var CORE_ENTRY = true ? resolve5(HERE, "index.js") : resolve5(HERE, "..", "..", "core", "src", "index.ts");
1880
2012
  async function loadDefault(path2) {
1881
2013
  if (path2.endsWith(".json")) return JSON.parse(await readFile4(path2, "utf8"));
1882
2014
  let code;
@@ -1926,7 +2058,7 @@ function parseArgs(argv) {
1926
2058
  }
1927
2059
  const args = {
1928
2060
  mode,
1929
- input: resolve5(input),
2061
+ input: resolve6(input),
1930
2062
  out: `${basename(input).replace(/\.[^.]+$/, "")}.mp4`,
1931
2063
  keepFrames: false,
1932
2064
  overlays: [],
@@ -1938,8 +2070,8 @@ function parseArgs(argv) {
1938
2070
  else if (a === "--fps") args.fps = Number(rest[++i]);
1939
2071
  else if (a === "--duration") args.duration = Number(rest[++i]);
1940
2072
  else if (a === "--keep-frames") args.keepFrames = true;
1941
- else if (a === "--frames-dir") args.framesDir = resolve5(rest[++i]);
1942
- else if (a === "--overlay") args.overlays.push(resolve5(rest[++i]));
2073
+ else if (a === "--frames-dir") args.framesDir = resolve6(rest[++i]);
2074
+ else if (a === "--overlay") args.overlays.push(resolve6(rest[++i]));
1943
2075
  else if (a === "--no-audio") args.noAudio = true;
1944
2076
  else if (a === "--scene") args.scene = rest[++i];
1945
2077
  else {
@@ -1968,7 +2100,7 @@ async function main() {
1968
2100
  );
1969
2101
  return;
1970
2102
  }
1971
- const framesDir = args.framesDir ?? await mkdtemp4(join7(tmpdir5(), "reframe-frames-"));
2103
+ const framesDir = args.framesDir ?? await mkdtemp5(join9(tmpdir6(), "reframe-frames-"));
1972
2104
  let result;
1973
2105
  let audioJob = null;
1974
2106
  if (args.mode === "ir") {
@@ -2007,10 +2139,10 @@ async function main() {
2007
2139
  await encodeMp4(result.framesDir, result.fps, audioJob ? audioJob.videoOut : args.out);
2008
2140
  if (audioJob) {
2009
2141
  await buildAudioTrack(audioJob.plan, args.input, audioJob.videoOut, args.out);
2010
- await rm4(audioJob.videoOut, { force: true });
2142
+ await rm5(audioJob.videoOut, { force: true });
2011
2143
  }
2012
2144
  if (!args.keepFrames && args.framesDir === void 0) {
2013
- await rm4(framesDir, { recursive: true, force: true });
2145
+ await rm5(framesDir, { recursive: true, force: true });
2014
2146
  }
2015
2147
  console.log(
2016
2148
  `${args.out} (${result.frameCount} frames @ ${result.fps}fps${audioJob ? `, ${audioJob.plan.cues.length} audio cues` : ""})`