@shotstack/shotstack-canvas 1.1.7 → 1.1.8

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.
@@ -204,6 +204,7 @@ interface VideoGenerationOptions {
204
204
  duration: number;
205
205
  outputPath: string;
206
206
  pixelRatio?: number;
207
+ hasAlpha?: boolean;
207
208
  crf?: number;
208
209
  preset?: "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "slower" | "veryslow";
209
210
  tune?: "film" | "animation" | "grain" | "stillimage" | "psnr" | "ssim" | "fastdecode" | "zerolatency";
@@ -204,6 +204,7 @@ interface VideoGenerationOptions {
204
204
  duration: number;
205
205
  outputPath: string;
206
206
  pixelRatio?: number;
207
+ hasAlpha?: boolean;
207
208
  crf?: number;
208
209
  preset?: "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "slower" | "veryslow";
209
210
  tune?: "film" | "animation" | "grain" | "stillimage" | "psnr" | "ssim" | "fastdecode" | "zerolatency";
@@ -1230,6 +1230,7 @@ async function createNodePainter(opts) {
1230
1230
  const hasBackground = !!(op.bg && op.bg.color);
1231
1231
  needsAlphaExtraction = !hasBackground;
1232
1232
  if (op.bg && op.bg.color) {
1233
+ ctx.clearRect(0, 0, op.width, op.height);
1233
1234
  const { color, opacity, radius } = op.bg;
1234
1235
  const c = parseHex6(color, opacity);
1235
1236
  ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
@@ -1243,10 +1244,15 @@ async function createNodePainter(opts) {
1243
1244
  ctx.fillRect(0, 0, op.width, op.height);
1244
1245
  }
1245
1246
  } else {
1247
+ ctx.clearRect(0, 0, op.width, op.height);
1248
+ ctx.save();
1246
1249
  ctx.fillStyle = "rgb(255, 255, 255)";
1247
1250
  ctx.fillRect(0, 0, op.width, op.height);
1251
+ ctx.restore();
1252
+ offscreenCtx.save();
1248
1253
  offscreenCtx.fillStyle = "rgb(0, 0, 0)";
1249
1254
  offscreenCtx.fillRect(0, 0, op.width, op.height);
1255
+ offscreenCtx.restore();
1250
1256
  }
1251
1257
  continue;
1252
1258
  }
@@ -1632,44 +1638,72 @@ var VideoGenerator = class {
1632
1638
  tune = "animation",
1633
1639
  profile = "high",
1634
1640
  level = "4.2",
1635
- pixFmt = "yuv420p"
1641
+ pixFmt = "yuv420p",
1642
+ hasAlpha = false
1636
1643
  } = options;
1637
1644
  const totalFrames = Math.max(2, Math.round(duration * fps) + 1);
1645
+ const finalOutputPath = hasAlpha ? outputPath.replace(/\.mp4$/, ".mov") : outputPath;
1638
1646
  console.log(
1639
- `\u{1F3AC} Generating video: ${width}x${height} @ ${fps}fps, ${duration}s (${totalFrames} frames)
1640
- CRF=${crf}, preset=${preset}, tune=${tune}, profile=${profile}, level=${level}, pix_fmt=${pixFmt}`
1647
+ `\u{1F3AC} Generating video: ${width}x${height} @ ${fps}fps, ${duration}s (${totalFrames} frames)` + (hasAlpha ? " (MOV with alpha)" : "")
1641
1648
  );
1642
1649
  return new Promise(async (resolve, reject) => {
1643
- const args = [
1644
- "-y",
1645
- "-f",
1646
- "image2pipe",
1647
- "-vcodec",
1648
- "png",
1649
- "-framerate",
1650
- String(fps),
1651
- "-i",
1652
- "-",
1653
- "-c:v",
1654
- "libx264",
1655
- "-preset",
1656
- preset,
1657
- "-crf",
1658
- String(crf),
1659
- "-tune",
1660
- tune,
1661
- "-profile:v",
1662
- profile,
1663
- "-level",
1664
- level,
1665
- "-pix_fmt",
1666
- pixFmt,
1667
- "-r",
1668
- String(fps),
1669
- "-movflags",
1670
- "+faststart",
1671
- outputPath
1672
- ];
1650
+ let args;
1651
+ if (hasAlpha) {
1652
+ args = [
1653
+ "-y",
1654
+ "-f",
1655
+ "image2pipe",
1656
+ "-vcodec",
1657
+ "png",
1658
+ "-framerate",
1659
+ String(fps),
1660
+ "-i",
1661
+ "-",
1662
+ "-c:v",
1663
+ "prores_ks",
1664
+ "-profile:v",
1665
+ "4444",
1666
+ "-pix_fmt",
1667
+ "yuva444p10le",
1668
+ "-vendor",
1669
+ "apl0",
1670
+ "-r",
1671
+ String(fps),
1672
+ finalOutputPath
1673
+ ];
1674
+ console.log(` Output: ${finalOutputPath} (ProRes 4444 with alpha)`);
1675
+ } else {
1676
+ args = [
1677
+ "-y",
1678
+ "-f",
1679
+ "image2pipe",
1680
+ "-vcodec",
1681
+ "png",
1682
+ "-framerate",
1683
+ String(fps),
1684
+ "-i",
1685
+ "-",
1686
+ "-c:v",
1687
+ "libx264",
1688
+ "-preset",
1689
+ preset,
1690
+ "-crf",
1691
+ String(crf),
1692
+ "-tune",
1693
+ tune,
1694
+ "-profile:v",
1695
+ profile,
1696
+ "-level",
1697
+ level,
1698
+ "-pix_fmt",
1699
+ pixFmt,
1700
+ "-r",
1701
+ String(fps),
1702
+ "-movflags",
1703
+ "+faststart",
1704
+ outputPath
1705
+ ];
1706
+ }
1673
1707
  const ffmpeg = spawn(this.ffmpegPath, args, { stdio: ["pipe", "inherit", "pipe"] });
1674
1708
  let ffmpegError = "";
1675
1709
  ffmpeg.stderr.on("data", (data) => {
@@ -1918,13 +1952,21 @@ async function createTextEngine(opts = {}) {
1918
1952
  },
1919
1953
  async generateVideo(asset, options) {
1920
1954
  try {
1955
+ const hasBackground = !!asset.background?.color;
1956
+ const hasAnimation = !!asset.animation?.preset;
1957
+ const needsAlpha = !hasBackground && hasAnimation;
1958
+ console.log(
1959
+ `\u{1F3A8} Video settings: Animation=${hasAnimation}, Background=${hasBackground}, Alpha=${needsAlpha}`
1960
+ );
1921
1961
  const finalOptions = {
1922
1962
  width: asset.width ?? width,
1923
1963
  height: asset.height ?? height,
1924
1964
  fps,
1925
1965
  duration: asset.animation?.duration ?? 3,
1926
1966
  outputPath: options.outputPath ?? "output.mp4",
1927
- pixelRatio: asset.pixelRatio ?? pixelRatio
1967
+ pixelRatio: asset.pixelRatio ?? pixelRatio,
1968
+ hasAlpha: needsAlpha,
1969
+ ...options
1928
1970
  };
1929
1971
  const frameGenerator = async (time) => {
1930
1972
  return this.renderFrame(asset, time);