const LIB_URLS = [ 'https://esm.sh/wasm-webp@0.1.0', 'https://cdn.jsdelivr.net/npm/wasm-webp@0.1.0/+esm' ]; let webpLib = null; let loadingPromise = null; async function loadWebpLib() { if (webpLib) return webpLib; if (loadingPromise) return loadingPromise; loadingPromise = (async () => { let lastErr = null; for (const url of LIB_URLS) { try { const mod = await import(url); if (typeof mod?.encodeAnimation !== 'function') { throw new Error(`encodeAnimation export not found from ${url}`); } webpLib = mod; return webpLib; } catch (err) { lastErr = err; } } throw lastErr || new Error('Failed to load animated WebP encoder.'); })(); return loadingPromise; } function loadImage(src) { return new Promise((resolve, reject) => { const img = new Image(); img.decoding = 'async'; img.onload = () => resolve(img); img.onerror = () => reject(new Error('Failed to load frame image')); img.src = src; }); } export async function assembleAnimatedWebp(frames, fps, maxSize, onProgress) { if (!frames?.length) throw new Error('No frames to assemble.'); const { encodeAnimation } = await loadWebpLib(); const images = await Promise.all(frames.map(loadImage)); let w = images[0].naturalWidth; let h = images[0].naturalHeight; if (maxSize && (w > maxSize || h > maxSize)) { if (w > h) { h = Math.round((h * maxSize) / w); w = maxSize; } else { w = Math.round((w * maxSize) / h); h = maxSize; } } const delay = Math.max(1, Math.round(1000 / Math.max(1, fps))); const canvas = document.createElement('canvas'); canvas.width = w; canvas.height = h; const ctx = canvas.getContext('2d', { willReadFrequently: true }); if (!ctx) throw new Error('Could not create 2D canvas context.'); const animFrames = []; for (let i = 0; i < images.length; i++) { ctx.clearRect(0, 0, w, h); ctx.drawImage(images[i], 0, 0, w, h); const imgData = ctx.getImageData(0, 0, w, h); animFrames.push({ data: new Uint8Array(imgData.data), duration: delay }); onProgress?.(Math.round(((i + 1) / images.length) * 90), `packing frame ${i + 1} of ${images.length}...`); } onProgress?.(95, 'encoding animated webp...'); const webpData = await encodeAnimation(w, h, true, animFrames); if (!webpData) throw new Error('Failed to encode animated WebP.'); onProgress?.(100, 'animated webp ready'); return new Blob([webpData], { type: 'image/webp' }); }