| 24 | const MAX_IMAGE_SIZE_BYTES = 50 * 1024 * 1024; |
| 25 | |
| 26 | export async function toDataUri(image: string): Promise<string> { |
| 27 | if (image.startsWith('data:')) return image; |
| 28 | |
| 29 | if (image.startsWith('http://') || image.startsWith('https://')) { |
| 30 | const res = await fetch(image); |
| 31 | if (!res.ok) throw new CLIError(`Failed to download image: HTTP ${res.status}`, ExitCode.GENERAL); |
| 32 | const contentType = res.headers.get('content-type') || 'image/jpeg'; |
| 33 | const mime = contentType.split(';')[0]!.trim(); |
| 34 | const buf = await res.arrayBuffer(); |
| 35 | if (buf.byteLength > MAX_IMAGE_SIZE_BYTES) { |
| 36 | throw new CLIError( |
| 37 | `Image too large (${(buf.byteLength / 1024 / 1024).toFixed(1)} MB). Maximum is 50 MB.`, |
| 38 | ExitCode.USAGE, |
| 39 | ); |
| 40 | } |
| 41 | return `data:${mime};base64,${Buffer.from(buf).toString('base64')}`; |
| 42 | } |
| 43 | |
| 44 | if (!existsSync(image)) throw new CLIError(`File not found: ${image}`, ExitCode.USAGE); |
| 45 | const ext = extname(image).toLowerCase(); |
| 46 | if (!IMAGE_MIME_TYPES[ext]) throw new CLIError(`Unsupported image format "${ext}". Supported: jpg, jpeg, png, webp`, ExitCode.USAGE); |
| 47 | return localFileToDataUri(image); |
| 48 | } |