reframe-video 0.6.5 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -342,6 +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
346
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
346
347
  group: COMMON_PROPS
347
348
  };
@@ -786,6 +787,33 @@
786
787
  });
787
788
  return;
788
789
  }
790
+ case "video": {
791
+ const width = num(id, "width", node.props.width);
792
+ const height = num(id, "height", node.props.height);
793
+ const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
794
+ const fps = compiled2.ir.fps ?? 30;
795
+ const start = node.props.start ?? 0;
796
+ const rate = node.props.rate ?? 1;
797
+ const clipStart = node.props.clipStart ?? 0;
798
+ const srcT = clipStart + Math.max(0, t - start) * rate;
799
+ const frame = Math.max(0, Math.round(srcT * fps));
800
+ ops.push({
801
+ type: "video",
802
+ id,
803
+ transform: matrix,
804
+ opacity,
805
+ src: str(id, "src", node.props.src),
806
+ width,
807
+ height,
808
+ offsetX: -width * ax,
809
+ offsetY: -height * ay,
810
+ frame,
811
+ ...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
812
+ ...fx,
813
+ ...clipSpread
814
+ });
815
+ return;
816
+ }
789
817
  case "path": {
790
818
  const ox = num(id, "originX", node.props.originX ?? 0);
791
819
  const oy = num(id, "originY", node.props.originY ?? 0);
@@ -879,7 +907,7 @@
879
907
  for (const s of paint.stops) g.addColorStop(Math.max(0, Math.min(1, s.offset)), s.color);
880
908
  return g;
881
909
  }
882
- function renderFrame(ctx2, compiled2, t, images2) {
910
+ function renderFrame(ctx2, compiled2, t, images2, videos2) {
883
911
  const { size, background } = compiled2.ir;
884
912
  ctx2.setTransform(1, 0, 0, 1, 0, 0);
885
913
  ctx2.clearRect(0, 0, size.width, size.height);
@@ -887,9 +915,9 @@
887
915
  ctx2.fillStyle = background;
888
916
  ctx2.fillRect(0, 0, size.width, size.height);
889
917
  }
890
- drawDisplayList(ctx2, evaluate(compiled2, t), images2);
918
+ drawDisplayList(ctx2, evaluate(compiled2, t), images2, videos2);
891
919
  }
892
- function drawDisplayList(ctx2, ops, images2) {
920
+ function drawDisplayList(ctx2, ops, images2, videos2) {
893
921
  for (const op of ops) {
894
922
  ctx2.save();
895
923
  if (op.clips) {
@@ -971,28 +999,11 @@
971
999
  break;
972
1000
  }
973
1001
  case "image": {
974
- const img = images2?.get(op.src);
975
- if (img) {
976
- if (op.fit === "cover") {
977
- const [iw, ih] = intrinsicSize(img);
978
- const { sx, sy, sw, sh } = coverRect(iw, ih, op.width, op.height);
979
- ctx2.drawImage(img, sx, sy, sw, sh, op.offsetX, op.offsetY, op.width, op.height);
980
- } else {
981
- ctx2.drawImage(img, op.offsetX, op.offsetY, op.width, op.height);
982
- }
983
- } else {
984
- ctx2.fillStyle = "#2A2A30";
985
- ctx2.fillRect(op.offsetX, op.offsetY, op.width, op.height);
986
- ctx2.strokeStyle = "#FF00FF";
987
- ctx2.lineWidth = 2;
988
- ctx2.strokeRect(op.offsetX, op.offsetY, op.width, op.height);
989
- ctx2.beginPath();
990
- ctx2.moveTo(op.offsetX, op.offsetY);
991
- ctx2.lineTo(op.offsetX + op.width, op.offsetY + op.height);
992
- ctx2.moveTo(op.offsetX + op.width, op.offsetY);
993
- ctx2.lineTo(op.offsetX, op.offsetY + op.height);
994
- ctx2.stroke();
995
- }
1002
+ drawRaster(ctx2, images2?.get(op.src), op);
1003
+ break;
1004
+ }
1005
+ case "video": {
1006
+ drawRaster(ctx2, videos2?.frame(op.src, op.frame), op);
996
1007
  break;
997
1008
  }
998
1009
  case "path": {
@@ -1047,6 +1058,29 @@
1047
1058
  const a = img;
1048
1059
  return [a.naturalWidth || a.width || 0, a.naturalHeight || a.height || 0];
1049
1060
  }
1061
+ function drawRaster(ctx2, img, op) {
1062
+ if (img) {
1063
+ if (op.fit === "cover") {
1064
+ const [iw, ih] = intrinsicSize(img);
1065
+ const { sx, sy, sw, sh } = coverRect(iw, ih, op.width, op.height);
1066
+ ctx2.drawImage(img, sx, sy, sw, sh, op.offsetX, op.offsetY, op.width, op.height);
1067
+ } else {
1068
+ ctx2.drawImage(img, op.offsetX, op.offsetY, op.width, op.height);
1069
+ }
1070
+ return;
1071
+ }
1072
+ ctx2.fillStyle = "#2A2A30";
1073
+ ctx2.fillRect(op.offsetX, op.offsetY, op.width, op.height);
1074
+ ctx2.strokeStyle = "#FF00FF";
1075
+ ctx2.lineWidth = 2;
1076
+ ctx2.strokeRect(op.offsetX, op.offsetY, op.width, op.height);
1077
+ ctx2.beginPath();
1078
+ ctx2.moveTo(op.offsetX, op.offsetY);
1079
+ ctx2.lineTo(op.offsetX + op.width, op.offsetY + op.height);
1080
+ ctx2.moveTo(op.offsetX + op.width, op.offsetY);
1081
+ ctx2.lineTo(op.offsetX, op.offsetY + op.height);
1082
+ ctx2.stroke();
1083
+ }
1050
1084
  function quoteFamily(family) {
1051
1085
  return family.includes(" ") && !family.includes('"') ? `"${family}"` : family;
1052
1086
  }
@@ -1069,9 +1103,22 @@
1069
1103
  var ctx = null;
1070
1104
  var canvas = null;
1071
1105
  var images = /* @__PURE__ */ new Map();
1106
+ var videoFrames = /* @__PURE__ */ new Map();
1107
+ var decode = (dataUrl) => {
1108
+ const img = new Image();
1109
+ img.src = dataUrl;
1110
+ return img.decode().then(() => img);
1111
+ };
1112
+ var videos = {
1113
+ frame(src, index) {
1114
+ const frames = videoFrames.get(src);
1115
+ if (!frames || frames.length === 0) return void 0;
1116
+ return frames[Math.max(0, Math.min(index, frames.length - 1))];
1117
+ }
1118
+ };
1072
1119
  window.__reframe = {
1073
- // fully decode every image before the first frame — renderFrame is sync
1074
- async init(ir, assets = {}) {
1120
+ // fully decode every image/video frame before the first frame — renderFrame is sync
1121
+ async init(ir, assets = {}, videoAssets = {}) {
1075
1122
  compiled = compileScene(ir);
1076
1123
  canvas = document.createElement("canvas");
1077
1124
  canvas.width = ir.size.width;
@@ -1079,19 +1126,19 @@
1079
1126
  document.body.appendChild(canvas);
1080
1127
  ctx = canvas.getContext("2d", { willReadFrequently: true });
1081
1128
  if (!ctx) throw new Error("could not create 2d context");
1082
- await Promise.all(
1083
- Object.entries(assets).map(async ([src, dataUrl]) => {
1084
- const img = new Image();
1085
- img.src = dataUrl;
1086
- await img.decode();
1087
- images.set(src, img);
1129
+ await Promise.all([
1130
+ ...Object.entries(assets).map(async ([src, dataUrl]) => {
1131
+ images.set(src, await decode(dataUrl));
1132
+ }),
1133
+ ...Object.entries(videoAssets).map(async ([src, frames]) => {
1134
+ videoFrames.set(src, await Promise.all(frames.map(decode)));
1088
1135
  })
1089
- );
1136
+ ]);
1090
1137
  return { duration: compiled.duration, fps: ir.fps ?? 30 };
1091
1138
  },
1092
1139
  renderFrame(t) {
1093
1140
  if (!compiled || !ctx || !canvas) throw new Error("init() not called");
1094
- renderFrame(ctx, compiled, t, images);
1141
+ renderFrame(ctx, compiled, t, images, videos);
1095
1142
  return canvas.toDataURL("image/png");
1096
1143
  }
1097
1144
  };
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 mkdtemp3, readFile as readFile4, rm as rm3 } from "node:fs/promises";
5
- import { tmpdir as tmpdir4 } from "node:os";
6
- import { basename, dirname as dirname7, join as join6, resolve as resolve4 } from "node:path";
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";
7
7
 
8
8
  // ../core/src/ir.ts
9
9
  var DEFAULT_CROSSFADE = 0.5;
@@ -347,6 +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
351
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
351
352
  group: COMMON_PROPS
352
353
  };
@@ -1044,13 +1045,13 @@ var EASE_TABLE = {
1044
1045
  var EASE_NAMES = Object.keys(EASE_TABLE);
1045
1046
 
1046
1047
  // ../core/src/assets.ts
1047
- function collectImageSrcs(ir) {
1048
+ function collectSrcs(ir, type) {
1048
1049
  const srcs = /* @__PURE__ */ new Set();
1049
- const imageIds = /* @__PURE__ */ new Set();
1050
+ const ids = /* @__PURE__ */ new Set();
1050
1051
  const walkNodes = (nodes) => {
1051
1052
  for (const node of nodes) {
1052
- if (node.type === "image") {
1053
- imageIds.add(node.id);
1053
+ if (node.type === type) {
1054
+ ids.add(node.id);
1054
1055
  srcs.add(node.props.src);
1055
1056
  }
1056
1057
  if (node.type === "group") walkNodes(node.children);
@@ -1059,14 +1060,14 @@ function collectImageSrcs(ir) {
1059
1060
  walkNodes(ir.nodes);
1060
1061
  for (const overrides of Object.values(ir.states ?? {})) {
1061
1062
  for (const [nodeId, props] of Object.entries(overrides)) {
1062
- if (imageIds.has(nodeId) && typeof props.src === "string") srcs.add(props.src);
1063
+ if (ids.has(nodeId) && typeof props.src === "string") srcs.add(props.src);
1063
1064
  }
1064
1065
  }
1065
1066
  const walkTimeline = (step) => {
1066
1067
  if (!step) return;
1067
1068
  if (step.kind === "seq" || step.kind === "par" || step.kind === "stagger") {
1068
1069
  for (const child of step.children) walkTimeline(child);
1069
- } else if (step.kind === "tween" && imageIds.has(step.target)) {
1070
+ } else if (step.kind === "tween" && ids.has(step.target)) {
1070
1071
  const src = step.props.src;
1071
1072
  if (typeof src === "string") srcs.add(src);
1072
1073
  }
@@ -1074,6 +1075,12 @@ function collectImageSrcs(ir) {
1074
1075
  walkTimeline(ir.timeline);
1075
1076
  return [...srcs];
1076
1077
  }
1078
+ function collectImageSrcs(ir) {
1079
+ return collectSrcs(ir, "image");
1080
+ }
1081
+ function collectVideoSrcs(ir) {
1082
+ return collectSrcs(ir, "video");
1083
+ }
1077
1084
 
1078
1085
  // ../render-cli/src/audio/index.ts
1079
1086
  import { dirname as dirname2 } from "node:path";
@@ -1394,10 +1401,10 @@ async function buildAudioTrack(plan, scenePath, videoIn, outFile) {
1394
1401
  }
1395
1402
 
1396
1403
  // ../render-cli/src/composition.ts
1397
- import { spawn as spawn3 } from "node:child_process";
1398
- import { copyFile, mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
1399
- import { tmpdir as tmpdir3 } from "node:os";
1400
- import { dirname as dirname5, join as join5 } from "node:path";
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";
1401
1408
 
1402
1409
  // ../render-cli/src/encode.ts
1403
1410
  import { spawn as spawn2 } from "node:child_process";
@@ -1420,12 +1427,12 @@ async function encodeMp4(framesDir, fps, outFile) {
1420
1427
  "+faststart",
1421
1428
  outFile
1422
1429
  ];
1423
- await new Promise((resolve5, reject) => {
1430
+ await new Promise((resolve6, reject) => {
1424
1431
  const proc = spawn2("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1425
1432
  let stderr = "";
1426
1433
  proc.stderr.on("data", (d) => stderr += d.toString());
1427
1434
  proc.on("close", (code) => {
1428
- if (code === 0) resolve5();
1435
+ if (code === 0) resolve6();
1429
1436
  else reject(new Error(`ffmpeg exited with ${code}:
1430
1437
  ${stderr.slice(-2e3)}`));
1431
1438
  });
@@ -1435,7 +1442,7 @@ ${stderr.slice(-2e3)}`));
1435
1442
 
1436
1443
  // ../render-cli/src/frameLoop.ts
1437
1444
  import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
1438
- import { join as join4, dirname as dirname4 } from "node:path";
1445
+ import { join as join5, dirname as dirname4 } from "node:path";
1439
1446
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
1440
1447
  import { build } from "esbuild";
1441
1448
  import { chromium } from "playwright";
@@ -1496,6 +1503,96 @@ async function buildImageAssets(ir, sceneDir) {
1496
1503
  return assets;
1497
1504
  }
1498
1505
 
1506
+ // ../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";
1512
+ var VIDEO_EXT = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".m4v", ".mkv"]);
1513
+ function runFfmpeg(args) {
1514
+ return new Promise((resolve6, reject) => {
1515
+ const proc = spawn3("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1516
+ let stderr = "";
1517
+ proc.stderr.on("data", (d) => stderr += d.toString());
1518
+ proc.on(
1519
+ "close",
1520
+ (code) => code === 0 ? resolve6() : reject(new Error(`ffmpeg exited ${code}:
1521
+ ${stderr.slice(-2e3)}`))
1522
+ );
1523
+ proc.on("error", reject);
1524
+ });
1525
+ }
1526
+ function neededSeconds(node, duration) {
1527
+ const start = node.props.start ?? 0;
1528
+ const rate = node.props.rate ?? 1;
1529
+ const clipStart = node.props.clipStart ?? 0;
1530
+ return clipStart + Math.max(0, duration - start) * Math.max(0, rate) + 1 / 30;
1531
+ }
1532
+ function videoNodes(ir) {
1533
+ const out = [];
1534
+ const walk = (nodes) => {
1535
+ for (const n of nodes) {
1536
+ if (n.type === "video") out.push(n);
1537
+ if (n.type === "group") walk(n.children);
1538
+ }
1539
+ };
1540
+ walk(ir.nodes);
1541
+ return out;
1542
+ }
1543
+ async function buildVideoFrameAssets(ir, sceneDir, fps, duration) {
1544
+ const srcs = collectVideoSrcs(ir);
1545
+ if (srcs.length === 0) return {};
1546
+ const nodes = videoNodes(ir);
1547
+ const reachBySrc = /* @__PURE__ */ new Map();
1548
+ for (const n of nodes) {
1549
+ const reach = neededSeconds(n, duration);
1550
+ reachBySrc.set(n.props.src, Math.max(reachBySrc.get(n.props.src) ?? 0, reach));
1551
+ }
1552
+ const assets = {};
1553
+ for (const src of srcs) {
1554
+ if (!VIDEO_EXT.has(extname2(src).toLowerCase())) {
1555
+ throw new Error(
1556
+ `video "${src}": unsupported format "${extname2(src)}" \u2014 supported: ${[...VIDEO_EXT].join(" ")}`
1557
+ );
1558
+ }
1559
+ const candidates = [isAbsolute3(src) ? src : null, resolve3(sceneDir, src)].filter(
1560
+ (c) => c !== null
1561
+ );
1562
+ const found = candidates.find((c) => existsSync3(c));
1563
+ if (!found) throw new Error(`video "${src}" not found (tried: ${candidates.join(", ")})`);
1564
+ const dir = await mkdtemp2(join4(tmpdir3(), "reframe-vframes-"));
1565
+ try {
1566
+ const seconds = Math.max(1 / fps, reachBySrc.get(src) ?? duration);
1567
+ await runFfmpeg([
1568
+ "-y",
1569
+ "-i",
1570
+ found,
1571
+ "-t",
1572
+ seconds.toFixed(3),
1573
+ "-vf",
1574
+ `fps=${fps},scale='min(iw,1280)':-2`,
1575
+ "-q:v",
1576
+ "4",
1577
+ join4(dir, "%05d.jpg")
1578
+ ]);
1579
+ const files = (await readdir(dir)).filter((f) => f.endsWith(".jpg")).sort();
1580
+ assets[src] = await Promise.all(
1581
+ files.map(async (f) => `data:image/jpeg;base64,${(await readFile3(join4(dir, f))).toString("base64")}`)
1582
+ );
1583
+ if (assets[src].length === 0) throw new Error(`video "${src}": ffmpeg extracted no frames`);
1584
+ } finally {
1585
+ await rm2(dir, { recursive: true, force: true });
1586
+ }
1587
+ }
1588
+ return assets;
1589
+ }
1590
+ function resolveTiming(ir, opts) {
1591
+ const fps = opts.fps ?? ir.fps ?? 30;
1592
+ const duration = opts.duration ?? compileScene(ir).duration;
1593
+ return { fps, duration };
1594
+ }
1595
+
1499
1596
  // ../render-cli/src/vclock.ts
1500
1597
  var VCLOCK_SOURCE = String.raw`
1501
1598
  (() => {
@@ -1569,7 +1666,7 @@ async function injectFonts(page) {
1569
1666
  await document.fonts.ready;
1570
1667
  });
1571
1668
  }
1572
- var framePath = (dir, i) => join4(dir, `${String(i).padStart(5, "0")}.png`);
1669
+ var framePath = (dir, i) => join5(dir, `${String(i).padStart(5, "0")}.png`);
1573
1670
  async function withPage(size, fn) {
1574
1671
  const browser = await chromium.launch({
1575
1672
  args: ["--force-color-profile=srgb", "--font-render-hinting=none"]
@@ -1585,14 +1682,14 @@ var bundleCache = null;
1585
1682
  async function browserBundle() {
1586
1683
  if (bundleCache) return bundleCache;
1587
1684
  if (true) {
1588
- const { readFile: readFile5 } = await import("node:fs/promises");
1589
- bundleCache = await readFile5(
1590
- join4(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
1685
+ const { readFile: readFile6 } = await import("node:fs/promises");
1686
+ bundleCache = await readFile6(
1687
+ join5(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
1591
1688
  "utf8"
1592
1689
  );
1593
1690
  return bundleCache;
1594
1691
  }
1595
- const entry = join4(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.ts");
1692
+ const entry = join5(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.ts");
1596
1693
  const result = await build({
1597
1694
  entryPoints: [entry],
1598
1695
  bundle: true,
@@ -1605,7 +1702,10 @@ async function browserBundle() {
1605
1702
  }
1606
1703
  async function captureIr(ir, opts) {
1607
1704
  await mkdir2(opts.framesDir, { recursive: true });
1608
- const assets = await buildImageAssets(ir, opts.sceneDir ?? process.cwd());
1705
+ const sceneDir = opts.sceneDir ?? process.cwd();
1706
+ const assets = await buildImageAssets(ir, sceneDir);
1707
+ const { fps, duration } = resolveTiming(ir, opts);
1708
+ const videoAssets = await buildVideoFrameAssets(ir, sceneDir, fps, duration);
1609
1709
  const bundle = await browserBundle();
1610
1710
  return withPage(ir.size, async (page) => {
1611
1711
  await page.setContent(
@@ -1613,12 +1713,10 @@ async function captureIr(ir, opts) {
1613
1713
  );
1614
1714
  await injectFonts(page);
1615
1715
  await page.addScriptTag({ content: bundle });
1616
- const info = await page.evaluate(
1617
- ([sceneIr, imageAssets]) => window.__reframe.init(sceneIr, imageAssets),
1618
- [ir, assets]
1716
+ await page.evaluate(
1717
+ ([sceneIr, imageAssets, vAssets]) => window.__reframe.init(sceneIr, imageAssets, vAssets),
1718
+ [ir, assets, videoAssets]
1619
1719
  );
1620
- const fps = opts.fps ?? info.fps;
1621
- const duration = opts.duration ?? info.duration;
1622
1720
  const frameCount = Math.max(1, Math.round(duration * fps));
1623
1721
  for (let f = 0; f < frameCount; f++) {
1624
1722
  const dataUrl = await page.evaluate((t) => window.__reframe.renderFrame(t), f / fps);
@@ -1651,25 +1749,25 @@ async function captureHtml(htmlPath, opts) {
1651
1749
  }
1652
1750
 
1653
1751
  // ../render-cli/src/composition.ts
1654
- function runFfmpeg(args) {
1655
- return new Promise((resolve5, reject) => {
1656
- const proc = spawn3("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1752
+ function runFfmpeg2(args) {
1753
+ return new Promise((resolve6, reject) => {
1754
+ const proc = spawn4("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
1657
1755
  let stderr = "";
1658
1756
  proc.stderr.on("data", (d) => stderr += d.toString());
1659
- proc.on("close", (code) => code === 0 ? resolve5() : reject(new Error(`ffmpeg exited ${code}:
1757
+ proc.on("close", (code) => code === 0 ? resolve6() : reject(new Error(`ffmpeg exited ${code}:
1660
1758
  ${stderr.slice(-2e3)}`)));
1661
1759
  proc.on("error", reject);
1662
1760
  });
1663
1761
  }
1664
1762
  var sanitize = (id) => id.replace(/[^a-z0-9_-]/gi, "_");
1665
1763
  async function renderSceneVideo(scene, sceneDir, fps, out) {
1666
- const framesDir = await mkdtemp2(join5(tmpdir3(), "reframe-frames-"));
1764
+ const framesDir = await mkdtemp3(join6(tmpdir4(), "reframe-frames-"));
1667
1765
  try {
1668
1766
  const result = await captureIr(scene, { framesDir, sceneDir, ...fps !== void 0 && { fps } });
1669
1767
  await encodeMp4(result.framesDir, result.fps, out);
1670
1768
  return { fps: result.fps, frameCount: result.frameCount };
1671
1769
  } finally {
1672
- await rm2(framesDir, { recursive: true, force: true });
1770
+ await rm3(framesDir, { recursive: true, force: true });
1673
1771
  }
1674
1772
  }
1675
1773
  async function renderStandaloneScene(scene, sceneDir, fps, noAudio, out) {
@@ -1677,8 +1775,8 @@ async function renderStandaloneScene(scene, sceneDir, fps, noAudio, out) {
1677
1775
  if (plan) {
1678
1776
  const videoOut = `${out}.video.mp4`;
1679
1777
  await renderSceneVideo(scene, sceneDir, fps, videoOut);
1680
- await buildAudioTrack(plan, join5(sceneDir, "scene"), videoOut, out);
1681
- await rm2(videoOut, { force: true });
1778
+ await buildAudioTrack(plan, join6(sceneDir, "scene"), videoOut, out);
1779
+ await rm3(videoOut, { force: true });
1682
1780
  } else {
1683
1781
  await renderSceneVideo(scene, sceneDir, fps, out);
1684
1782
  }
@@ -1690,10 +1788,10 @@ async function concatVideos(files, out) {
1690
1788
  }
1691
1789
  const list = `${out}.concat.txt`;
1692
1790
  await writeFile4(list, files.map((f) => `file '${f.replace(/'/g, "'\\''")}'`).join("\n"));
1693
- await runFfmpeg(["-y", "-f", "concat", "-safe", "0", "-i", list, "-c", "copy", "-movflags", "+faststart", out]);
1791
+ await runFfmpeg2(["-y", "-f", "concat", "-safe", "0", "-i", list, "-c", "copy", "-movflags", "+faststart", out]);
1694
1792
  }
1695
1793
  async function xfade2(a, b, overlap, offset, out) {
1696
- await runFfmpeg([
1794
+ await runFfmpeg2([
1697
1795
  "-y",
1698
1796
  "-i",
1699
1797
  a,
@@ -1721,7 +1819,7 @@ async function combineWithTransitions(videos, out, tmp) {
1721
1819
  let accDur = videos[0].placement.duration;
1722
1820
  for (let i = 1; i < videos.length; i++) {
1723
1821
  const { overlap, duration } = videos[i].placement;
1724
- const step = join5(tmp, `step${i}.mp4`);
1822
+ const step = join6(tmp, `step${i}.mp4`);
1725
1823
  if (overlap <= 0) {
1726
1824
  await concatVideos([acc, videos[i].file], step);
1727
1825
  accDur += duration;
@@ -1743,15 +1841,15 @@ async function renderComposition(comp, opts) {
1743
1841
  await renderStandaloneScene(p.scene, sceneDir, opts.fps, opts.noAudio, opts.out);
1744
1842
  return { duration: p.duration, sceneCount: 1 };
1745
1843
  }
1746
- const tmp = await mkdtemp2(join5(tmpdir3(), "reframe-comp-"));
1844
+ const tmp = await mkdtemp3(join6(tmpdir4(), "reframe-comp-"));
1747
1845
  try {
1748
1846
  const videos = [];
1749
1847
  for (const p of cc.scenes) {
1750
- const file = join5(tmp, `${sanitize(p.id)}.mp4`);
1848
+ const file = join6(tmp, `${sanitize(p.id)}.mp4`);
1751
1849
  const { fps } = await renderSceneVideo(p.scene, sceneDir, opts.fps, file);
1752
1850
  videos.push({ id: p.id, file, placement: p, fps });
1753
1851
  }
1754
- const combined = join5(tmp, "combined.mp4");
1852
+ const combined = join6(tmp, "combined.mp4");
1755
1853
  const allCut = cc.scenes.every((s) => s.overlap === 0);
1756
1854
  if (allCut) await concatVideos(videos.map((v) => v.file), combined);
1757
1855
  else await combineWithTransitions(videos, combined, tmp);
@@ -1768,19 +1866,19 @@ async function renderComposition(comp, opts) {
1768
1866
  }
1769
1867
  return { duration: cc.duration, sceneCount: cc.scenes.length };
1770
1868
  } finally {
1771
- await rm2(tmp, { recursive: true, force: true });
1869
+ await rm3(tmp, { recursive: true, force: true });
1772
1870
  }
1773
1871
  }
1774
1872
 
1775
1873
  // ../render-cli/src/loadScene.ts
1776
1874
  import { build as build2 } from "esbuild";
1777
- import { readFile as readFile3 } from "node:fs/promises";
1778
- import { dirname as dirname6, resolve as resolve3 } from "node:path";
1875
+ import { readFile as readFile4 } from "node:fs/promises";
1876
+ import { dirname as dirname6, resolve as resolve4 } from "node:path";
1779
1877
  import { fileURLToPath as fileURLToPath4 } from "node:url";
1780
1878
  var HERE = dirname6(fileURLToPath4(import.meta.url));
1781
- var CORE_ENTRY = true ? resolve3(HERE, "index.js") : resolve3(HERE, "..", "..", "core", "src", "index.ts");
1879
+ var CORE_ENTRY = true ? resolve4(HERE, "index.js") : resolve4(HERE, "..", "..", "core", "src", "index.ts");
1782
1880
  async function loadDefault(path2) {
1783
- if (path2.endsWith(".json")) return JSON.parse(await readFile3(path2, "utf8"));
1881
+ if (path2.endsWith(".json")) return JSON.parse(await readFile4(path2, "utf8"));
1784
1882
  let code;
1785
1883
  try {
1786
1884
  const out = await build2({
@@ -1828,7 +1926,7 @@ function parseArgs(argv) {
1828
1926
  }
1829
1927
  const args = {
1830
1928
  mode,
1831
- input: resolve4(input),
1929
+ input: resolve5(input),
1832
1930
  out: `${basename(input).replace(/\.[^.]+$/, "")}.mp4`,
1833
1931
  keepFrames: false,
1834
1932
  overlays: [],
@@ -1840,8 +1938,8 @@ function parseArgs(argv) {
1840
1938
  else if (a === "--fps") args.fps = Number(rest[++i]);
1841
1939
  else if (a === "--duration") args.duration = Number(rest[++i]);
1842
1940
  else if (a === "--keep-frames") args.keepFrames = true;
1843
- else if (a === "--frames-dir") args.framesDir = resolve4(rest[++i]);
1844
- else if (a === "--overlay") args.overlays.push(resolve4(rest[++i]));
1941
+ else if (a === "--frames-dir") args.framesDir = resolve5(rest[++i]);
1942
+ else if (a === "--overlay") args.overlays.push(resolve5(rest[++i]));
1845
1943
  else if (a === "--no-audio") args.noAudio = true;
1846
1944
  else if (a === "--scene") args.scene = rest[++i];
1847
1945
  else {
@@ -1870,14 +1968,14 @@ async function main() {
1870
1968
  );
1871
1969
  return;
1872
1970
  }
1873
- const framesDir = args.framesDir ?? await mkdtemp3(join6(tmpdir4(), "reframe-frames-"));
1971
+ const framesDir = args.framesDir ?? await mkdtemp4(join7(tmpdir5(), "reframe-frames-"));
1874
1972
  let result;
1875
1973
  let audioJob = null;
1876
1974
  if (args.mode === "ir") {
1877
1975
  let ir = loaded.ir;
1878
1976
  if (args.overlays.length > 0) {
1879
1977
  const docs = await Promise.all(
1880
- args.overlays.map(async (p) => JSON.parse(await readFile4(p, "utf8")))
1978
+ args.overlays.map(async (p) => JSON.parse(await readFile5(p, "utf8")))
1881
1979
  );
1882
1980
  const composed = composeScene(ir, ...docs);
1883
1981
  console.error(formatComposeReport(composed.report));
@@ -1909,10 +2007,10 @@ async function main() {
1909
2007
  await encodeMp4(result.framesDir, result.fps, audioJob ? audioJob.videoOut : args.out);
1910
2008
  if (audioJob) {
1911
2009
  await buildAudioTrack(audioJob.plan, args.input, audioJob.videoOut, args.out);
1912
- await rm3(audioJob.videoOut, { force: true });
2010
+ await rm4(audioJob.videoOut, { force: true });
1913
2011
  }
1914
2012
  if (!args.keepFrames && args.framesDir === void 0) {
1915
- await rm3(framesDir, { recursive: true, force: true });
2013
+ await rm4(framesDir, { recursive: true, force: true });
1916
2014
  }
1917
2015
  console.log(
1918
2016
  `${args.out} (${result.frameCount} frames @ ${result.fps}fps${audioJob ? `, ${audioJob.plan.cues.length} audio cues` : ""})`