diff --git a/apps/api/src/lib/heic-converter.ts b/apps/api/src/lib/heic-converter.ts index 1510d1a0..b7f0d11d 100644 --- a/apps/api/src/lib/heic-converter.ts +++ b/apps/api/src/lib/heic-converter.ts @@ -8,18 +8,40 @@ import { promisify } from "node:util"; const execFileAsync = promisify(execFile); /** - * Decode a HEIC/HEIF buffer to PNG using the system `heif-dec` CLI tool. + * Find the HEIF decode command. macOS (Homebrew) provides `heif-dec`, + * while Linux packages provide `heif-convert`. Both accept the same + * ` ` argument syntax. + */ +let cachedDecodeCmd: string | null = null; + +async function findDecodeCmd(): Promise { + if (cachedDecodeCmd) return cachedDecodeCmd; + for (const cmd of ["heif-convert", "heif-dec"]) { + try { + await execFileAsync(cmd, ["--version"], { timeout: 5_000 }); + cachedDecodeCmd = cmd; + return cmd; + } catch { + // try next + } + } + throw new Error("No HEIF decoder found. Install libheif-examples (Linux) or libheif (macOS)."); +} + +/** + * Decode a HEIC/HEIF buffer to PNG using the system HEIF decoder CLI. * This is needed because Sharp's bundled libheif does not include the * HEVC decoder required for true HEIC files (iPhone photos). */ export async function decodeHeic(buffer: Buffer): Promise { + const cmd = await findDecodeCmd(); const id = randomUUID(); const inputPath = join(tmpdir(), `heic-in-${id}.heic`); const outputPath = join(tmpdir(), `heic-out-${id}.png`); try { await writeFile(inputPath, buffer); - await execFileAsync("heif-dec", [inputPath, outputPath], { timeout: 30_000 }); + await execFileAsync(cmd, [inputPath, outputPath], { timeout: 30_000 }); return await readFile(outputPath); } finally { await rm(inputPath, { force: true }).catch(() => {}); @@ -47,16 +69,3 @@ export async function encodeHeic(buffer: Buffer, quality = 80): Promise await rm(outputPath, { force: true }).catch(() => {}); } } - -/** - * Check whether the heif-enc and heif-dec CLI tools are available. - */ -export async function isHeicToolAvailable(): Promise { - try { - await execFileAsync("heif-enc", ["--version"], { timeout: 5_000 }); - await execFileAsync("heif-dec", ["--version"], { timeout: 5_000 }); - return true; - } catch { - return false; - } -}