Use heic-convert for HEIC-to-JPEG conversion
Sharp's prebuilt libvips lacks HEIF codec support. Replace with heic-convert (pure JS decoder) for reliable HEIC conversion on all platforms. Existing HEIC files on disk will be converted on-the-fly when served via /api/files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Generated
+51
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"framer-motion": "^11.2.0",
|
||||
"heic-convert": "^2.1.0",
|
||||
"lucide-react": "^0.400.0",
|
||||
"next": "^16.1.6",
|
||||
"qrcode": "^1.5.4",
|
||||
@@ -2334,6 +2335,41 @@
|
||||
"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-convert/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/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/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@@ -2415,12 +2451,27 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"framer-motion": "^11.2.0",
|
||||
"heic-convert": "^2.1.0",
|
||||
"lucide-react": "^0.400.0",
|
||||
"next": "^16.1.6",
|
||||
"qrcode": "^1.5.4",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { writeFile, mkdir } from 'fs/promises'
|
||||
import path from 'path'
|
||||
import { randomUUID } from 'crypto'
|
||||
import { getDb } from '@/lib/db'
|
||||
import sharp from 'sharp'
|
||||
import { convertHeicToJpeg } from '@/lib/heic'
|
||||
|
||||
export const runtime = 'nodejs'
|
||||
export const maxDuration = 60
|
||||
@@ -77,7 +77,7 @@ export async function POST(req: NextRequest) {
|
||||
let finalExt = ext
|
||||
if (mimeType === 'image/heic' || mimeType === 'image/heif') {
|
||||
try {
|
||||
finalBuffer = Buffer.from(await sharp(buffer).jpeg({ quality: 90 }).toBuffer())
|
||||
finalBuffer = await convertHeicToJpeg(buffer)
|
||||
finalExt = '.jpg'
|
||||
} catch {
|
||||
// Conversion failed — keep original
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import sharp from 'sharp'
|
||||
import { convertHeicToJpeg } from '@/lib/heic'
|
||||
|
||||
export const runtime = 'nodejs'
|
||||
|
||||
@@ -55,7 +56,7 @@ export async function GET(
|
||||
contentType = 'image/jpeg'
|
||||
} else {
|
||||
try {
|
||||
const converted = await sharp(fs.readFileSync(filePath), { failOn: 'none' }).jpeg({ quality: 90 }).toBuffer()
|
||||
const converted = await convertHeicToJpeg(fs.readFileSync(filePath))
|
||||
fs.writeFileSync(jpegPath, converted)
|
||||
fileToSendPath = jpegPath
|
||||
ext = '.jpg'
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from 'path'
|
||||
import { cookies } from 'next/headers'
|
||||
import { createHash, randomUUID } from 'crypto'
|
||||
import { getDb } from '@/lib/db'
|
||||
import sharp from 'sharp'
|
||||
import { convertHeicToJpeg } from '@/lib/heic'
|
||||
|
||||
export const runtime = 'nodejs'
|
||||
export const maxDuration = 60
|
||||
@@ -112,8 +112,8 @@ export async function POST(req: NextRequest) {
|
||||
// Convert HEIC/HEIF to JPEG so all browsers can display it
|
||||
if (mimeType === 'image/heic' || mimeType === 'image/heif') {
|
||||
try {
|
||||
const converted = await sharp(buffer).jpeg({ quality: 90 }).toBuffer()
|
||||
buffer = converted as unknown as Buffer
|
||||
const converted = await convertHeicToJpeg(buffer)
|
||||
buffer = converted
|
||||
ext = '.jpg'
|
||||
mimeType = 'image/jpeg'
|
||||
} catch {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// @ts-expect-error no type declarations available
|
||||
import heicConvert from 'heic-convert'
|
||||
|
||||
/**
|
||||
* Convert HEIC/HEIF buffer to JPEG.
|
||||
* Uses heic-convert (pure JS) since sharp's prebuilt libvips lacks HEIF support.
|
||||
*/
|
||||
export async function convertHeicToJpeg(buffer: Buffer): Promise<Buffer> {
|
||||
const result = await heicConvert({
|
||||
buffer,
|
||||
format: 'JPEG',
|
||||
quality: 0.9,
|
||||
})
|
||||
return Buffer.from(result)
|
||||
}
|
||||
Reference in New Issue
Block a user