Spaces:
Running
Running
| import sharp from 'sharp'; | |
| import fs from 'fs'; | |
| import { tempPath, escapeXml } from './helpers.js'; | |
| // Accept positional args for compatibility with Bubble.js caller | |
| export async function createTextBackgroundPng(text, fontSize=40, fontName='Arial', boxColor='#ffffff', boxBorderW=0, paddingX=20, paddingY=8, radius=10, fontColor='#000000'){ | |
| // estimate size and create an SVG with rounded rect only (text is drawn by ffmpeg drawtext to ensure crisp font) | |
| const paddingXPx = Math.round(paddingX || 20); | |
| const paddingYPx = Math.round(paddingY || 8); | |
| const safeText = (text == null) ? '' : String(text); | |
| const estimatedWidth = Math.max(60, Math.round((fontSize || 40) * Math.max(1, safeText.length) * 0.6) + paddingXPx * 2); | |
| const estimatedHeight = Math.max(24, Math.round((fontSize || 40) * 1.4) + paddingYPx * 2); | |
| const rx = Math.max(0, Math.round(radius || 0)); | |
| const svg = `<?xml version="1.0" encoding="utf-8"?>\n<svg xmlns='http://www.w3.org/2000/svg' width='${estimatedWidth}' height='${estimatedHeight}'>\n <rect x='0' y='0' width='100%' height='100%' rx='${rx}' ry='${rx}' fill='${boxColor || '#ffffff'}' stroke='none' />\n</svg>`; | |
| const tmp = tempPath('text-bg','png'); | |
| await sharp(Buffer.from(svg)).png().toFile(tmp); | |
| const meta = await sharp(tmp).metadata(); | |
| return { path: tmp, width: meta.width, height: meta.height }; | |
| } | |
| // Accept positional args for compatibility | |
| export async function processImageWithBg(srcPath, width, height, backgroundColor=null, radius=0){ | |
| const tmp = tempPath('img-out','png'); | |
| const rx = Math.max(0, Math.round(radius || 0)); | |
| let img = sharp(srcPath).resize(width, height, { fit: 'cover', position: 'centre' }); | |
| const overlayBuf = await img.png().toBuffer(); | |
| if (rx > 0) { | |
| const maskSvg = `<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><rect x="0" y="0" rx="${rx}" ry="${rx}" width="${width}" height="${height}" fill="#fff"/></svg>`; | |
| const rounded = await sharp(overlayBuf).composite([{ input: Buffer.from(maskSvg), blend: 'dest-in' }]).png().toBuffer(); | |
| if (backgroundColor) { | |
| const bg = sharp({ create: { width, height, channels: 4, background: backgroundColor } }).png(); | |
| const out = tempPath('img-composite','png'); | |
| await bg.composite([{ input: rounded, gravity: 'centre' }]).png().toFile(out); | |
| return out; | |
| } | |
| await sharp(rounded).png().toFile(tmp); | |
| return tmp; | |
| } | |
| if (backgroundColor) { | |
| const bg = sharp({ create: { width, height, channels: 4, background: backgroundColor } }).png(); | |
| const out = tempPath('img-composite','png'); | |
| await bg.composite([{ input: overlayBuf, gravity: 'centre' }]).png().toFile(out); | |
| return out; | |
| } | |
| await sharp(overlayBuf).png().toFile(tmp); | |
| return tmp; | |
| } | |