reframe-video 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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", "volume"],
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
  };