fix: use heif-convert for HEIC decoding on Linux

Linux libheif packages provide heif-convert instead of heif-dec (which
is macOS-only). The decoder now tries heif-convert first, then falls
back to heif-dec. Both accept the same argument syntax.
This commit is contained in:
Siddharth Kumar Sah
2026-04-04 21:55:19 +08:00
parent d2381af9cd
commit 98553ed674
+24 -15
View File
@@ -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
* `<input> <output>` argument syntax.
*/
let cachedDecodeCmd: string | null = null;
async function findDecodeCmd(): Promise<string> {
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<Buffer> {
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<Buffer>
await rm(outputPath, { force: true }).catch(() => {});
}
}
/**
* Check whether the heif-enc and heif-dec CLI tools are available.
*/
export async function isHeicToolAvailable(): Promise<boolean> {
try {
await execFileAsync("heif-enc", ["--version"], { timeout: 5_000 });
await execFileAsync("heif-dec", ["--version"], { timeout: 5_000 });
return true;
} catch {
return false;
}
}