@shotstack/shotstack-canvas 1.1.7 → 1.1.9

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.
@@ -1248,6 +1248,7 @@ async function createNodePainter(opts) {
1248
1248
  if (!ctx) throw new Error("2D context unavailable in Node (canvas).");
1249
1249
  const offscreenCanvas = createCanvas(canvas.width, canvas.height);
1250
1250
  const offscreenCtx = offscreenCanvas.getContext("2d");
1251
+ const gradientCache = /* @__PURE__ */ new Map();
1251
1252
  const api = {
1252
1253
  async render(ops) {
1253
1254
  const globalBox = computeGlobalTextBounds(ops);
@@ -1268,6 +1269,7 @@ async function createNodePainter(opts) {
1268
1269
  const hasBackground = !!(op.bg && op.bg.color);
1269
1270
  needsAlphaExtraction = !hasBackground;
1270
1271
  if (op.bg && op.bg.color) {
1272
+ ctx.clearRect(0, 0, op.width, op.height);
1271
1273
  const { color, opacity, radius } = op.bg;
1272
1274
  const c = parseHex6(color, opacity);
1273
1275
  ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
@@ -1281,10 +1283,15 @@ async function createNodePainter(opts) {
1281
1283
  ctx.fillRect(0, 0, op.width, op.height);
1282
1284
  }
1283
1285
  } else {
1286
+ ctx.clearRect(0, 0, op.width, op.height);
1287
+ ctx.save();
1284
1288
  ctx.fillStyle = "rgb(255, 255, 255)";
1285
1289
  ctx.fillRect(0, 0, op.width, op.height);
1290
+ ctx.restore();
1291
+ offscreenCtx.save();
1286
1292
  offscreenCtx.fillStyle = "rgb(0, 0, 0)";
1287
1293
  offscreenCtx.fillRect(0, 0, op.width, op.height);
1294
+ offscreenCtx.restore();
1288
1295
  }
1289
1296
  continue;
1290
1297
  }
@@ -1303,11 +1310,23 @@ async function createNodePainter(opts) {
1303
1310
  context.translate(fillOp.x, fillOp.y);
1304
1311
  const s = fillOp.scale ?? 1;
1305
1312
  context.scale(s, -s);
1306
- context.beginPath();
1307
- drawSvgPathOnCtx(context, fillOp.path);
1308
1313
  const bbox = fillOp.gradientBBox ?? globalBox;
1309
- const fill = makeGradientFromBBox(context, fillOp.fill, bbox);
1314
+ const localBBox = {
1315
+ x: (bbox.x - fillOp.x) / s,
1316
+ y: -(bbox.y - fillOp.y) / s,
1317
+ w: bbox.w / s,
1318
+ h: bbox.h / s
1319
+ };
1320
+ const cacheKey = JSON.stringify({ fill: fillOp.fill, bbox: localBBox });
1321
+ let fill = gradientCache.get(cacheKey);
1322
+ if (!fill) {
1323
+ fill = makeGradientFromBBox(context, fillOp.fill, localBBox);
1324
+ gradientCache.set(cacheKey, fill);
1325
+ console.log("[Node Painter] Created NEW gradient for local bbox:", localBBox);
1326
+ }
1310
1327
  context.fillStyle = fill;
1328
+ context.beginPath();
1329
+ drawSvgPathOnCtx(context, fillOp.path);
1311
1330
  context.fill();
1312
1331
  context.restore();
1313
1332
  });
@@ -1670,44 +1689,72 @@ var VideoGenerator = class {
1670
1689
  tune = "animation",
1671
1690
  profile = "high",
1672
1691
  level = "4.2",
1673
- pixFmt = "yuv420p"
1692
+ pixFmt = "yuv420p",
1693
+ hasAlpha = false
1674
1694
  } = options;
1675
1695
  const totalFrames = Math.max(2, Math.round(duration * fps) + 1);
1696
+ const finalOutputPath = hasAlpha ? outputPath.replace(/\.mp4$/, ".mov") : outputPath;
1676
1697
  console.log(
1677
- `\u{1F3AC} Generating video: ${width}x${height} @ ${fps}fps, ${duration}s (${totalFrames} frames)
1678
- CRF=${crf}, preset=${preset}, tune=${tune}, profile=${profile}, level=${level}, pix_fmt=${pixFmt}`
1698
+ `\u{1F3AC} Generating video: ${width}x${height} @ ${fps}fps, ${duration}s (${totalFrames} frames)` + (hasAlpha ? " (MOV with alpha)" : "")
1679
1699
  );
1680
1700
  return new Promise(async (resolve, reject) => {
1681
- const args = [
1682
- "-y",
1683
- "-f",
1684
- "image2pipe",
1685
- "-vcodec",
1686
- "png",
1687
- "-framerate",
1688
- String(fps),
1689
- "-i",
1690
- "-",
1691
- "-c:v",
1692
- "libx264",
1693
- "-preset",
1694
- preset,
1695
- "-crf",
1696
- String(crf),
1697
- "-tune",
1698
- tune,
1699
- "-profile:v",
1700
- profile,
1701
- "-level",
1702
- level,
1703
- "-pix_fmt",
1704
- pixFmt,
1705
- "-r",
1706
- String(fps),
1707
- "-movflags",
1708
- "+faststart",
1709
- outputPath
1710
- ];
1701
+ let args;
1702
+ if (hasAlpha) {
1703
+ args = [
1704
+ "-y",
1705
+ "-f",
1706
+ "image2pipe",
1707
+ "-vcodec",
1708
+ "png",
1709
+ "-framerate",
1710
+ String(fps),
1711
+ "-i",
1712
+ "-",
1713
+ "-c:v",
1714
+ "prores_ks",
1715
+ "-profile:v",
1716
+ "4444",
1717
+ "-pix_fmt",
1718
+ "yuva444p10le",
1719
+ "-vendor",
1720
+ "apl0",
1721
+ "-r",
1722
+ String(fps),
1723
+ finalOutputPath
1724
+ ];
1725
+ console.log(` Output: ${finalOutputPath} (ProRes 4444 with alpha)`);
1726
+ } else {
1727
+ args = [
1728
+ "-y",
1729
+ "-f",
1730
+ "image2pipe",
1731
+ "-vcodec",
1732
+ "png",
1733
+ "-framerate",
1734
+ String(fps),
1735
+ "-i",
1736
+ "-",
1737
+ "-c:v",
1738
+ "libx264",
1739
+ "-preset",
1740
+ preset,
1741
+ "-crf",
1742
+ String(crf),
1743
+ "-tune",
1744
+ tune,
1745
+ "-profile:v",
1746
+ profile,
1747
+ "-level",
1748
+ level,
1749
+ "-pix_fmt",
1750
+ pixFmt,
1751
+ "-r",
1752
+ String(fps),
1753
+ "-movflags",
1754
+ "+faststart",
1755
+ outputPath
1756
+ ];
1757
+ }
1711
1758
  const ffmpeg = (0, import_child_process.spawn)(this.ffmpegPath, args, { stdio: ["pipe", "inherit", "pipe"] });
1712
1759
  let ffmpegError = "";
1713
1760
  ffmpeg.stderr.on("data", (data) => {
@@ -1956,13 +2003,21 @@ async function createTextEngine(opts = {}) {
1956
2003
  },
1957
2004
  async generateVideo(asset, options) {
1958
2005
  try {
2006
+ const hasBackground = !!asset.background?.color;
2007
+ const hasAnimation = !!asset.animation?.preset;
2008
+ const needsAlpha = !hasBackground && hasAnimation;
2009
+ console.log(
2010
+ `\u{1F3A8} Video settings: Animation=${hasAnimation}, Background=${hasBackground}, Alpha=${needsAlpha}`
2011
+ );
1959
2012
  const finalOptions = {
1960
2013
  width: asset.width ?? width,
1961
2014
  height: asset.height ?? height,
1962
2015
  fps,
1963
2016
  duration: asset.animation?.duration ?? 3,
1964
2017
  outputPath: options.outputPath ?? "output.mp4",
1965
- pixelRatio: asset.pixelRatio ?? pixelRatio
2018
+ pixelRatio: asset.pixelRatio ?? pixelRatio,
2019
+ hasAlpha: needsAlpha,
2020
+ ...options
1966
2021
  };
1967
2022
  const frameGenerator = async (time) => {
1968
2023
  return this.renderFrame(asset, time);