From 2efe3395766d93a32833dd1e4b84909923282920 Mon Sep 17 00:00:00 2001 From: Mathieu Date: Tue, 9 Jun 2026 11:50:00 +0200 Subject: [PATCH] fix: conversion HEIC serveur via heic-convert (WASM, supporte HEIC Apple vs sharp AVIF only) --- package-lock.json | 51 ++++++++++++++++++++++++ package.json | 1 + src/app/api/images/convert-heic/route.ts | 17 +++++--- src/types/heic-convert.d.ts | 1 + 4 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 src/types/heic-convert.d.ts diff --git a/package-lock.json b/package-lock.json index 56d43c8..2289509 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "bcryptjs": "^3.0.3", "better-sqlite3": "^12.10.0", "googleapis": "^173.0.0", + "heic-convert": "^2.1.0", "heic2any": "^0.0.4", "jszip": "^3.10.1", "next": "14.2.35", @@ -7342,6 +7343,32 @@ "node": ">= 0.4" } }, + "node_modules/heic-convert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/heic-convert/-/heic-convert-2.1.0.tgz", + "integrity": "sha512-1qDuRvEHifTVAj3pFIgkqGgJIr0M3X7cxEPjEp0oG4mo8GFjq99DpCo8Eg3kg17Cy0MTjxpFdoBHOatj7ZVKtg==", + "license": "ISC", + "dependencies": { + "heic-decode": "^2.0.0", + "jpeg-js": "^0.4.4", + "pngjs": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/heic-decode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/heic-decode/-/heic-decode-2.1.0.tgz", + "integrity": "sha512-0fB3O3WMk38+PScbHLVp66jcNhsZ/ErtQ6u2lMYu/YxXgbBtl+oKOhGQHa4RpvE68k8IzbWkABzHnyAIjR758A==", + "license": "ISC", + "dependencies": { + "libheif-js": "^1.19.8" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/heic2any": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/heic2any/-/heic2any-0.0.4.tgz", @@ -9165,6 +9192,12 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "license": "BSD-3-Clause" + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -9414,6 +9447,15 @@ "node": ">= 0.8.0" } }, + "node_modules/libheif-js": { + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/libheif-js/-/libheif-js-1.19.8.tgz", + "integrity": "sha512-vQJWusIxO7wavpON1dusciL8Go9jsIQ+EUrckauFYAiSTjcmLAsuJh3SszLpvkwPci3JcL41ek2n+LUZGFpPIQ==", + "license": "LGPL-3.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -10594,6 +10636,15 @@ "pathe": "^2.0.3" } }, + "node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "dev": true, diff --git a/package.json b/package.json index 78fe3d4..29d1453 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "bcryptjs": "^3.0.3", "better-sqlite3": "^12.10.0", "googleapis": "^173.0.0", + "heic-convert": "^2.1.0", "heic2any": "^0.0.4", "jszip": "^3.10.1", "next": "14.2.35", diff --git a/src/app/api/images/convert-heic/route.ts b/src/app/api/images/convert-heic/route.ts index 2abffe3..fa4f2bd 100644 --- a/src/app/api/images/convert-heic/route.ts +++ b/src/app/api/images/convert-heic/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from 'next/server' import { getServerSession } from 'next-auth' import { authOptions } from '@/lib/auth' -import sharp from 'sharp' +import convert from 'heic-convert' export async function POST(req: NextRequest) { const session = await getServerSession(authOptions) @@ -12,17 +12,24 @@ export async function POST(req: NextRequest) { const file = formData.get('file') as File | null if (!file) return NextResponse.json({ error: 'Fichier manquant' }, { status: 400 }) - const buffer = Buffer.from(await file.arrayBuffer()) - const jpegBuffer = await sharp(buffer).jpeg({ quality: 92 }).toBuffer() + const inputBuffer = Buffer.from(await file.arrayBuffer()) + // heic-convert utilise libheif WASM — supporte HEIC/HEIF sur tous les environnements + const outputBuffer = await convert({ + buffer: inputBuffer, + format: 'JPEG', + quality: 0.92, + }) - return new NextResponse(jpegBuffer.buffer as ArrayBuffer, { + const jpeg = Buffer.from(outputBuffer) + return new NextResponse(jpeg.buffer as ArrayBuffer, { headers: { 'Content-Type': 'image/jpeg', - 'Content-Length': String(jpegBuffer.length), + 'Content-Length': String(jpeg.length), }, }) } catch (err) { const message = err instanceof Error ? err.message : 'Erreur conversion' + console.error('[convert-heic API]', message) return NextResponse.json({ error: message }, { status: 500 }) } } diff --git a/src/types/heic-convert.d.ts b/src/types/heic-convert.d.ts new file mode 100644 index 0000000..5ee3a66 --- /dev/null +++ b/src/types/heic-convert.d.ts @@ -0,0 +1 @@ +declare module 'heic-convert';