// Client-side HEIC/HEIF -> JPEG conversion.
//
// iPhones store photos as HEIC. Most browsers (everything except Safari/WebKit)
// can't decode HEIC in
/canvas, so previewing and cropping a freshly
// picked HEIC file fails. We convert to JPEG right after selection so the rest
// of the upload flow (preview, ImageEditor crop, upload) works everywhere.
//
// heic2any is browser-only (uses a wasm libheif build), so it must be imported
// dynamically inside the handler — never at module top level (SSR would break).
const HEIC_EXT_RE = /\.(heic|heif)$/i;
/** True if the file looks like HEIC/HEIF by MIME type or, when the browser
* reports no/!generic type (common on iOS), by file extension. */
export function isHeic(file: File): boolean {
const type = (file.type || "").toLowerCase();
if (
type === "image/heic" ||
type === "image/heif" ||
type === "image/heic-sequence" ||
type === "image/heif-sequence"
) {
return true;
}
if (type === "" || type === "application/octet-stream") {
return HEIC_EXT_RE.test(file.name);
}
return false;
}
/**
* Returns a JPEG File when given a HEIC/HEIF file; otherwise returns the
* original file unchanged. Never throws — on conversion failure it falls back
* to the original file (the backend's pillow-heif may still handle it).
*/
export async function convertHeicToJpeg(
file: File,
quality = 0.92
): Promise {
if (typeof window === "undefined" || !isHeic(file)) return file;
try {
const heic2any = (await import("heic2any")).default;
const result = (await heic2any({
blob: file,
toType: "image/jpeg",
quality,
})) as Blob | Blob[];
const blob = Array.isArray(result) ? result[0] : result;
const baseName = file.name.replace(HEIC_EXT_RE, "") || "image";
return new File([blob], `${baseName}.jpg`, {
type: "image/jpeg",
lastModified: Date.now(),
});
} catch (e) {
console.error("HEIC->JPEG conversion failed; using original file", e);
return file;
}
}
/** Convert any HEIC/HEIF files in a list, leaving others untouched. */
export async function convertHeicFiles(files: File[]): Promise {
return Promise.all(files.map((f) => convertHeicToJpeg(f)));
}