@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.
- package/dist/entry.node.cjs +92 -37
- package/dist/entry.node.cjs.map +1 -1
- package/dist/entry.node.d.cts +1 -0
- package/dist/entry.node.d.ts +1 -0
- package/dist/entry.node.js +92 -37
- package/dist/entry.node.js.map +1 -1
- package/dist/entry.web.js +14 -1
- package/dist/entry.web.js.map +1 -1
- package/package.json +1 -1
package/dist/entry.node.cjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
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);
|