// 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))); }