mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-02-10 09:55:11 +08:00
[bug] Fallback to heic-decode for HEIC/HEIF when Sharp fails; Fix bug 76727
This commit is contained in:
@ -37,6 +37,7 @@
|
||||
- fakeredis 2.0.0 ([MIT](https://github.com/hdachev/fakeredis?tab=readme-ov-file#license))
|
||||
- ioredis 5.6.0 ([MIT](https://raw.githubusercontent.com/redis/ioredis/main/LICENSE))
|
||||
- jsonwebtoken 9.0.2 ([MIT](https://raw.githubusercontent.com/auth0/node-jsonwebtoken/master/LICENSE))
|
||||
- heic-decode 2.1.0 ([ISC](https://opensource.org/license/isc-license-txt))
|
||||
- mime 2.3.1 ([MIT](https://raw.githubusercontent.com/broofa/mime/main/LICENSE))
|
||||
- mime-db 1.53.0 ([MIT](https://raw.githubusercontent.com/jshttp/mime-db/master/LICENSE))
|
||||
- ms 2.1.3 ([MIT](https://raw.githubusercontent.com/vercel/ms/master/license.md))
|
||||
|
||||
13
DocService/npm-shrinkwrap.json
generated
13
DocService/npm-shrinkwrap.json
generated
@ -1189,6 +1189,14 @@
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"requires": {
|
||||
"libheif-js": "^1.19.8"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
@ -1437,6 +1445,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"libheif-js": {
|
||||
"version": "1.19.8",
|
||||
"resolved": "https://registry.npmjs.org/libheif-js/-/libheif-js-1.19.8.tgz",
|
||||
"integrity": "sha512-vQJWusIxO7wavpON1dusciL8Go9jsIQ+EUrckauFYAiSTjcmLAsuJh3SszLpvkwPci3JcL41ek2n+LUZGFpPIQ=="
|
||||
},
|
||||
"lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
"dmdb": "1.0.36002",
|
||||
"ejs": "3.1.10",
|
||||
"express": "4.21.2",
|
||||
"heic-decode": "2.1.0",
|
||||
"ioredis": "5.6.0",
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"mime": "2.3.1",
|
||||
|
||||
@ -37,6 +37,7 @@ const util = require('util');
|
||||
const config = require('config');
|
||||
const locale = require('windows-locale');
|
||||
const ms = require('ms');
|
||||
const decodeHeic = require('heic-decode');
|
||||
const operationContext = require('./../../Common/sources/operationContext');
|
||||
|
||||
function initializeSharp() {
|
||||
@ -117,6 +118,36 @@ function determineOptimalFormat(ctx, metadata) {
|
||||
return 'jpeg';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Sharp pipeline to buffer in optimal format (PNG or JPEG).
|
||||
* @param {Object} pipeline Sharp pipeline instance
|
||||
* @param {string} format Target format ('png' or 'jpeg')
|
||||
* @returns {Promise<Buffer>} Converted image buffer
|
||||
*/
|
||||
async function convertToFormat(pipeline, format) {
|
||||
if (format === 'png') {
|
||||
return await pipeline.png({compressionLevel: 7}).toBuffer();
|
||||
}
|
||||
return await pipeline.jpeg({quality: 90, chromaSubsampling: '4:4:4'}).toBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode HEIC/HEIF buffer using heic-decode library and create Sharp instance.
|
||||
* @param {Buffer} buffer HEIC/HEIF image buffer
|
||||
* @returns {Promise<Object>} Sharp instance with decoded raw image data
|
||||
*/
|
||||
async function decodeHeicToSharp(buffer) {
|
||||
const decodedImage = await decodeHeic({buffer});
|
||||
return sharp(decodedImage.data, {
|
||||
failOn: 'none',
|
||||
raw: {
|
||||
width: decodedImage.width,
|
||||
height: decodedImage.height,
|
||||
channels: 4
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process and optimize image buffer with EXIF rotation fix and modern format conversion.
|
||||
* 1. Fixes EXIF rotation and strips metadata for all images
|
||||
@ -135,42 +166,35 @@ async function processImageOptimal(ctx, buffer) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
let needsRotation = false;
|
||||
|
||||
try {
|
||||
const meta = await sharp(buffer, {failOn: 'none'}).metadata();
|
||||
needsRotation = meta.orientation && meta.orientation > 1;
|
||||
const fmt = (meta.format || '').toLowerCase();
|
||||
const needsRotation = meta.orientation && meta.orientation > 1;
|
||||
|
||||
// Handle modern formats that need conversion
|
||||
if (fmt === 'webp' || fmt === 'heic' || fmt === 'heif' || fmt === 'avif') {
|
||||
// Handle modern formats requiring conversion
|
||||
if (fmt === 'heic' || fmt === 'heif' || fmt === 'webp' || fmt === 'avif' || fmt === 'tiff' || fmt === 'tif') {
|
||||
const optimalFormat = determineOptimalFormat(ctx, meta);
|
||||
ctx.logger.debug('processImageOptimal: detected %s, converting to %s%s', fmt, optimalFormat, needsRotation ? ' with EXIF rotation' : '');
|
||||
ctx.logger.debug('processImageOptimal: converting %s to %s%s', fmt, optimalFormat, needsRotation ? ' with rotation' : '');
|
||||
|
||||
const pipeline = sharp(buffer, {failOn: 'none'}).rotate();
|
||||
if (optimalFormat === 'png') {
|
||||
return await pipeline.png({compressionLevel: 7}).toBuffer();
|
||||
} else {
|
||||
return await pipeline.jpeg({quality: 90, chromaSubsampling: '4:4:4'}).toBuffer();
|
||||
try {
|
||||
const pipeline = sharp(buffer, {failOn: 'none'}).rotate();
|
||||
return await convertToFormat(pipeline, optimalFormat);
|
||||
} catch (sharpError) {
|
||||
// Fallback to heic-decode for HEIC/HEIF when Sharp fails
|
||||
if (fmt === 'heic' || fmt === 'heif') {
|
||||
ctx.logger.debug('processImageOptimal: Sharp failed for %s, using heic-decode fallback', fmt);
|
||||
const heicPipeline = await decodeHeicToSharp(buffer);
|
||||
return await convertToFormat(heicPipeline, optimalFormat);
|
||||
}
|
||||
throw sharpError;
|
||||
}
|
||||
}
|
||||
|
||||
if (fmt === 'tiff' || fmt === 'tif') {
|
||||
const optimalFormat = determineOptimalFormat(ctx, meta);
|
||||
ctx.logger.debug('processImageOptimal: detected TIFF, converting to %s%s', optimalFormat, needsRotation ? ' with EXIF rotation' : '');
|
||||
|
||||
const pipeline = sharp(buffer, {failOn: 'none'}).rotate();
|
||||
if (optimalFormat === 'png') {
|
||||
return await pipeline.png({compressionLevel: 7}).toBuffer();
|
||||
} else {
|
||||
return await pipeline.jpeg({quality: 90, chromaSubsampling: '4:4:4'}).toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
// For other formats, only apply EXIF rotation if needed
|
||||
// For standard formats, only apply EXIF rotation if needed
|
||||
if (needsRotation) {
|
||||
ctx.logger.debug('processImageOptimal: applying EXIF rotation to %s', fmt);
|
||||
const pipeline = sharp(buffer, {failOn: 'none'}).rotate();
|
||||
|
||||
if (fmt === 'jpeg' || fmt === 'jpg') {
|
||||
return await pipeline.jpeg({quality: 90, chromaSubsampling: '4:4:4'}).toBuffer();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user