@shotstack/shotstack-canvas 1.1.6 → 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.
- package/dist/entry.node.cjs +132 -36
- 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 +132 -36
- package/dist/entry.node.js.map +1 -1
- package/dist/entry.web.js +57 -3
- package/dist/entry.web.js.map +1 -1
- package/package.json +2 -3
- package/public/hb.wasm +0 -0
package/dist/entry.node.cjs
CHANGED
|
@@ -164,9 +164,62 @@ var RichTextAssetSchema = import_joi.default.object({
|
|
|
164
164
|
|
|
165
165
|
// src/wasm/hb-loader.ts
|
|
166
166
|
var hbSingleton = null;
|
|
167
|
+
function isNode() {
|
|
168
|
+
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
169
|
+
}
|
|
170
|
+
async function loadWasmWeb(wasmBaseURL) {
|
|
171
|
+
try {
|
|
172
|
+
if (wasmBaseURL) {
|
|
173
|
+
const url = wasmBaseURL.endsWith(".wasm") ? wasmBaseURL : wasmBaseURL.endsWith("/") ? `${wasmBaseURL}hb.wasm` : `${wasmBaseURL}/hb.wasm`;
|
|
174
|
+
console.log(`Fetching WASM from: ${url}`);
|
|
175
|
+
const response = await fetch(url);
|
|
176
|
+
if (response.ok) {
|
|
177
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
178
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
179
|
+
if (bytes.length >= 4 && bytes[0] === 0 && bytes[1] === 97 && bytes[2] === 115 && bytes[3] === 109) {
|
|
180
|
+
console.log(`\u2705 Valid WASM binary loaded (${bytes.length} bytes)`);
|
|
181
|
+
return arrayBuffer;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return void 0;
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.error("Error in loadWasmWeb:", err);
|
|
188
|
+
return void 0;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function setupWasmFetchInterceptor(wasmBinary) {
|
|
192
|
+
const originalFetch = window.fetch;
|
|
193
|
+
window.fetch = function(input, init) {
|
|
194
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
195
|
+
if (url.includes("hb.wasm") || url.endsWith(".wasm")) {
|
|
196
|
+
console.log(`\u{1F504} Intercepted fetch for: ${url}`);
|
|
197
|
+
return Promise.resolve(
|
|
198
|
+
new Response(wasmBinary, {
|
|
199
|
+
status: 200,
|
|
200
|
+
statusText: "OK",
|
|
201
|
+
headers: {
|
|
202
|
+
"Content-Type": "application/wasm",
|
|
203
|
+
"Content-Length": wasmBinary.byteLength.toString()
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return originalFetch.apply(this, [input, init]);
|
|
209
|
+
};
|
|
210
|
+
}
|
|
167
211
|
async function initHB(wasmBaseURL) {
|
|
168
212
|
if (hbSingleton) return hbSingleton;
|
|
169
213
|
try {
|
|
214
|
+
let wasmBinary;
|
|
215
|
+
wasmBinary = await loadWasmWeb(wasmBaseURL);
|
|
216
|
+
if (!wasmBinary) {
|
|
217
|
+
throw new Error("Failed to load WASM binary from any source");
|
|
218
|
+
}
|
|
219
|
+
console.log(`\u2705 WASM binary loaded successfully (${wasmBinary.byteLength} bytes)`);
|
|
220
|
+
if (!isNode()) {
|
|
221
|
+
setupWasmFetchInterceptor(wasmBinary);
|
|
222
|
+
}
|
|
170
223
|
const mod = await import("harfbuzzjs");
|
|
171
224
|
const candidate = mod.default;
|
|
172
225
|
let hb;
|
|
@@ -180,10 +233,11 @@ async function initHB(wasmBaseURL) {
|
|
|
180
233
|
if (!hb || typeof hb.createBuffer !== "function" || typeof hb.createFont !== "function") {
|
|
181
234
|
throw new Error("Failed to initialize HarfBuzz: unexpected export shape from 'harfbuzzjs'.");
|
|
182
235
|
}
|
|
183
|
-
void wasmBaseURL;
|
|
184
236
|
hbSingleton = hb;
|
|
237
|
+
console.log("\u2705 HarfBuzz initialized successfully");
|
|
185
238
|
return hbSingleton;
|
|
186
239
|
} catch (err) {
|
|
240
|
+
console.error("Failed to initialize HarfBuzz:", err);
|
|
187
241
|
throw new Error(
|
|
188
242
|
`Failed to initialize HarfBuzz: ${err instanceof Error ? err.message : String(err)}`
|
|
189
243
|
);
|
|
@@ -222,7 +276,7 @@ var FontRegistry = class _FontRegistry {
|
|
|
222
276
|
}
|
|
223
277
|
async _doInit() {
|
|
224
278
|
try {
|
|
225
|
-
this.hb = await initHB(
|
|
279
|
+
this.hb = await initHB("https://shotstack-ingest-api-dev-sources.s3.ap-southeast-2.amazonaws.com/euo5r93oyr/zzz01k9h-yycyx-2x2y6-qx9bj-7n567b/source.wasm");
|
|
226
280
|
} catch (error) {
|
|
227
281
|
this.initPromise = void 0;
|
|
228
282
|
throw error;
|
|
@@ -1214,6 +1268,7 @@ async function createNodePainter(opts) {
|
|
|
1214
1268
|
const hasBackground = !!(op.bg && op.bg.color);
|
|
1215
1269
|
needsAlphaExtraction = !hasBackground;
|
|
1216
1270
|
if (op.bg && op.bg.color) {
|
|
1271
|
+
ctx.clearRect(0, 0, op.width, op.height);
|
|
1217
1272
|
const { color, opacity, radius } = op.bg;
|
|
1218
1273
|
const c = parseHex6(color, opacity);
|
|
1219
1274
|
ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
@@ -1227,10 +1282,15 @@ async function createNodePainter(opts) {
|
|
|
1227
1282
|
ctx.fillRect(0, 0, op.width, op.height);
|
|
1228
1283
|
}
|
|
1229
1284
|
} else {
|
|
1285
|
+
ctx.clearRect(0, 0, op.width, op.height);
|
|
1286
|
+
ctx.save();
|
|
1230
1287
|
ctx.fillStyle = "rgb(255, 255, 255)";
|
|
1231
1288
|
ctx.fillRect(0, 0, op.width, op.height);
|
|
1289
|
+
ctx.restore();
|
|
1290
|
+
offscreenCtx.save();
|
|
1232
1291
|
offscreenCtx.fillStyle = "rgb(0, 0, 0)";
|
|
1233
1292
|
offscreenCtx.fillRect(0, 0, op.width, op.height);
|
|
1293
|
+
offscreenCtx.restore();
|
|
1234
1294
|
}
|
|
1235
1295
|
continue;
|
|
1236
1296
|
}
|
|
@@ -1616,44 +1676,72 @@ var VideoGenerator = class {
|
|
|
1616
1676
|
tune = "animation",
|
|
1617
1677
|
profile = "high",
|
|
1618
1678
|
level = "4.2",
|
|
1619
|
-
pixFmt = "yuv420p"
|
|
1679
|
+
pixFmt = "yuv420p",
|
|
1680
|
+
hasAlpha = false
|
|
1620
1681
|
} = options;
|
|
1621
1682
|
const totalFrames = Math.max(2, Math.round(duration * fps) + 1);
|
|
1683
|
+
const finalOutputPath = hasAlpha ? outputPath.replace(/\.mp4$/, ".mov") : outputPath;
|
|
1622
1684
|
console.log(
|
|
1623
|
-
`\u{1F3AC} Generating video: ${width}x${height} @ ${fps}fps, ${duration}s (${totalFrames} frames)
|
|
1624
|
-
CRF=${crf}, preset=${preset}, tune=${tune}, profile=${profile}, level=${level}, pix_fmt=${pixFmt}`
|
|
1685
|
+
`\u{1F3AC} Generating video: ${width}x${height} @ ${fps}fps, ${duration}s (${totalFrames} frames)` + (hasAlpha ? " (MOV with alpha)" : "")
|
|
1625
1686
|
);
|
|
1626
1687
|
return new Promise(async (resolve, reject) => {
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1688
|
+
let args;
|
|
1689
|
+
if (hasAlpha) {
|
|
1690
|
+
args = [
|
|
1691
|
+
"-y",
|
|
1692
|
+
"-f",
|
|
1693
|
+
"image2pipe",
|
|
1694
|
+
"-vcodec",
|
|
1695
|
+
"png",
|
|
1696
|
+
"-framerate",
|
|
1697
|
+
String(fps),
|
|
1698
|
+
"-i",
|
|
1699
|
+
"-",
|
|
1700
|
+
"-c:v",
|
|
1701
|
+
"prores_ks",
|
|
1702
|
+
"-profile:v",
|
|
1703
|
+
"4444",
|
|
1704
|
+
"-pix_fmt",
|
|
1705
|
+
"yuva444p10le",
|
|
1706
|
+
"-vendor",
|
|
1707
|
+
"apl0",
|
|
1708
|
+
"-r",
|
|
1709
|
+
String(fps),
|
|
1710
|
+
finalOutputPath
|
|
1711
|
+
];
|
|
1712
|
+
console.log(` Output: ${finalOutputPath} (ProRes 4444 with alpha)`);
|
|
1713
|
+
} else {
|
|
1714
|
+
args = [
|
|
1715
|
+
"-y",
|
|
1716
|
+
"-f",
|
|
1717
|
+
"image2pipe",
|
|
1718
|
+
"-vcodec",
|
|
1719
|
+
"png",
|
|
1720
|
+
"-framerate",
|
|
1721
|
+
String(fps),
|
|
1722
|
+
"-i",
|
|
1723
|
+
"-",
|
|
1724
|
+
"-c:v",
|
|
1725
|
+
"libx264",
|
|
1726
|
+
"-preset",
|
|
1727
|
+
preset,
|
|
1728
|
+
"-crf",
|
|
1729
|
+
String(crf),
|
|
1730
|
+
"-tune",
|
|
1731
|
+
tune,
|
|
1732
|
+
"-profile:v",
|
|
1733
|
+
profile,
|
|
1734
|
+
"-level",
|
|
1735
|
+
level,
|
|
1736
|
+
"-pix_fmt",
|
|
1737
|
+
pixFmt,
|
|
1738
|
+
"-r",
|
|
1739
|
+
String(fps),
|
|
1740
|
+
"-movflags",
|
|
1741
|
+
"+faststart",
|
|
1742
|
+
outputPath
|
|
1743
|
+
];
|
|
1744
|
+
}
|
|
1657
1745
|
const ffmpeg = (0, import_child_process.spawn)(this.ffmpegPath, args, { stdio: ["pipe", "inherit", "pipe"] });
|
|
1658
1746
|
let ffmpegError = "";
|
|
1659
1747
|
ffmpeg.stderr.on("data", (data) => {
|
|
@@ -1902,13 +1990,21 @@ async function createTextEngine(opts = {}) {
|
|
|
1902
1990
|
},
|
|
1903
1991
|
async generateVideo(asset, options) {
|
|
1904
1992
|
try {
|
|
1993
|
+
const hasBackground = !!asset.background?.color;
|
|
1994
|
+
const hasAnimation = !!asset.animation?.preset;
|
|
1995
|
+
const needsAlpha = !hasBackground && hasAnimation;
|
|
1996
|
+
console.log(
|
|
1997
|
+
`\u{1F3A8} Video settings: Animation=${hasAnimation}, Background=${hasBackground}, Alpha=${needsAlpha}`
|
|
1998
|
+
);
|
|
1905
1999
|
const finalOptions = {
|
|
1906
2000
|
width: asset.width ?? width,
|
|
1907
2001
|
height: asset.height ?? height,
|
|
1908
2002
|
fps,
|
|
1909
2003
|
duration: asset.animation?.duration ?? 3,
|
|
1910
2004
|
outputPath: options.outputPath ?? "output.mp4",
|
|
1911
|
-
pixelRatio: asset.pixelRatio ?? pixelRatio
|
|
2005
|
+
pixelRatio: asset.pixelRatio ?? pixelRatio,
|
|
2006
|
+
hasAlpha: needsAlpha,
|
|
2007
|
+
...options
|
|
1912
2008
|
};
|
|
1913
2009
|
const frameGenerator = async (time) => {
|
|
1914
2010
|
return this.renderFrame(asset, time);
|