const LIB_URLS = [ 'https://esm.sh/@imgly/background-removal@1.7.0?bundle', 'https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/+esm' ]; let removeBackgroundFn = null; let loadingPromise = null; async function loadLib() { if (removeBackgroundFn) return removeBackgroundFn; if (loadingPromise) return loadingPromise; loadingPromise = (async () => { let lastErr = null; for (const url of LIB_URLS) { try { const mod = await import(url); const fn = mod?.default || mod?.removeBackground || mod?.imglyRemoveBackground; if (typeof fn !== 'function') throw new Error(`No removeBackground export from ${url}`); removeBackgroundFn = fn; return removeBackgroundFn; } catch (err) { lastErr = err; } } throw lastErr || new Error('Failed to load background-removal library.'); })(); 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 decode frame image')); img.src = src; }); } async function toPngFile(src, name = 'frame.png') { const img = await loadImage(src); const w = img.naturalWidth || img.width; const h = img.naturalHeight || img.height; const canvas = document.createElement('canvas'); canvas.width = w; canvas.height = h; const ctx = canvas.getContext('2d'); if (!ctx) throw new Error('Could not create 2D canvas context'); ctx.clearRect(0, 0, w, h); ctx.drawImage(img, 0, 0, w, h); const blob = await new Promise((resolve, reject) => { canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('Failed converting frame to PNG'))), 'image/png', 1); }); return new File([blob], name, { type: 'image/png' }); } function blobToDataUrl(blob) { return new Promise((resolve, reject) => { const fr = new FileReader(); fr.onload = () => resolve(String(fr.result || '')); fr.onerror = () => reject(new Error('Failed to convert blob to data URL')); fr.readAsDataURL(blob); }); } export async function removeBackgroundFrames(frameDataUrls, { onProgress } = {}) { if (!Array.isArray(frameDataUrls) || !frameDataUrls.length) throw new Error('No frames for background removal.'); const removeBackground = await loadLib(); const out = []; const total = frameDataUrls.length; for (let i = 0; i < total; i++) { const frameNo = i + 1; const src = frameDataUrls[i]; onProgress?.({ pct: Math.round((i / total) * 100), text: `preparing frame ${frameNo} of ${total} for background removal...` }); const pngFile = await toPngFile(src, `frame-${frameNo}.png`); onProgress?.({ pct: Math.round((i / total) * 100), text: `removing background from frame ${frameNo} of ${total}...` }); const outBlob = await removeBackground(pngFile, { debug: false, output: { type: 'foreground', format: 'image/png', quality: 1 }, progress: (_key, current, stepTotal) => { if (!stepTotal) return; const inner = current / stepTotal; const pct = Math.round(((i + inner) / total) * 100); onProgress?.({ pct, text: `removing background from frame ${frameNo} of ${total}...` }); } }); const outDataUrl = await blobToDataUrl(outBlob); out.push(outDataUrl); onProgress?.({ pct: Math.round((frameNo / total) * 100), text: `frame ${frameNo} of ${total} background removed` }); } onProgress?.({ pct: 100, text: 'background removal done' }); return out; }