mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
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:
@@ -8,18 +8,40 @@ import { promisify } from "node:util";
|
|||||||
const execFileAsync = promisify(execFile);
|
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
|
* This is needed because Sharp's bundled libheif does not include the
|
||||||
* HEVC decoder required for true HEIC files (iPhone photos).
|
* HEVC decoder required for true HEIC files (iPhone photos).
|
||||||
*/
|
*/
|
||||||
export async function decodeHeic(buffer: Buffer): Promise<Buffer> {
|
export async function decodeHeic(buffer: Buffer): Promise<Buffer> {
|
||||||
|
const cmd = await findDecodeCmd();
|
||||||
const id = randomUUID();
|
const id = randomUUID();
|
||||||
const inputPath = join(tmpdir(), `heic-in-${id}.heic`);
|
const inputPath = join(tmpdir(), `heic-in-${id}.heic`);
|
||||||
const outputPath = join(tmpdir(), `heic-out-${id}.png`);
|
const outputPath = join(tmpdir(), `heic-out-${id}.png`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await writeFile(inputPath, buffer);
|
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);
|
return await readFile(outputPath);
|
||||||
} finally {
|
} finally {
|
||||||
await rm(inputPath, { force: true }).catch(() => {});
|
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(() => {});
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user