mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-04-07 14:04:35 +08:00
[bug] Move from jimp to sharp; Fix bug 76854, 76727, 48506
This commit is contained in:
@ -33,11 +33,9 @@
|
|||||||
- cron 1.5.0 ([MIT](https://raw.githubusercontent.com/kelektiv/node-cron/main/LICENSE))
|
- cron 1.5.0 ([MIT](https://raw.githubusercontent.com/kelektiv/node-cron/main/LICENSE))
|
||||||
- dmdb 1.0.36002 ([none](https://www.npmjs.com/package/dmdb))
|
- dmdb 1.0.36002 ([none](https://www.npmjs.com/package/dmdb))
|
||||||
- ejs 3.1.10 ([Apache-2.0](https://raw.githubusercontent.com/mde/ejs/main/LICENSE))
|
- ejs 3.1.10 ([Apache-2.0](https://raw.githubusercontent.com/mde/ejs/main/LICENSE))
|
||||||
- exif-parser 0.1.12 ([MIT](https://raw.githubusercontent.com/bwindels/exif-parser/master/LICENSE.md))
|
|
||||||
- express 4.21.2 ([MIT](https://raw.githubusercontent.com/expressjs/express/master/LICENSE))
|
- express 4.21.2 ([MIT](https://raw.githubusercontent.com/expressjs/express/master/LICENSE))
|
||||||
- fakeredis 2.0.0 ([MIT](https://github.com/hdachev/fakeredis?tab=readme-ov-file#license))
|
- 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))
|
- ioredis 5.6.0 ([MIT](https://raw.githubusercontent.com/redis/ioredis/main/LICENSE))
|
||||||
- jimp 0.22.10 ([MIT](https://raw.githubusercontent.com/jimp-dev/jimp/main/LICENSE))
|
|
||||||
- jsonwebtoken 9.0.2 ([MIT](https://raw.githubusercontent.com/auth0/node-jsonwebtoken/master/LICENSE))
|
- jsonwebtoken 9.0.2 ([MIT](https://raw.githubusercontent.com/auth0/node-jsonwebtoken/master/LICENSE))
|
||||||
- mime 2.3.1 ([MIT](https://raw.githubusercontent.com/broofa/mime/main/LICENSE))
|
- 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))
|
- mime-db 1.53.0 ([MIT](https://raw.githubusercontent.com/jshttp/mime-db/master/LICENSE))
|
||||||
@ -51,6 +49,7 @@
|
|||||||
- pg 8.14.0 ([MIT](https://raw.githubusercontent.com/brianc/node-postgres/master/LICENSE))
|
- pg 8.14.0 ([MIT](https://raw.githubusercontent.com/brianc/node-postgres/master/LICENSE))
|
||||||
- redis 4.7.0 ([MIT](https://raw.githubusercontent.com/redis/node-redis/master/LICENSE))
|
- redis 4.7.0 ([MIT](https://raw.githubusercontent.com/redis/node-redis/master/LICENSE))
|
||||||
- retry 0.13.1 ([MIT](https://raw.githubusercontent.com/tim-kos/node-retry/master/License))
|
- retry 0.13.1 ([MIT](https://raw.githubusercontent.com/tim-kos/node-retry/master/License))
|
||||||
|
- sharp 0.33.5 ([Apache-2.0](https://raw.githubusercontent.com/lovell/sharp/master/LICENSE))
|
||||||
- socket.io 4.8.1 ([MIT](https://raw.githubusercontent.com/socketio/socket.io/main/LICENSE))
|
- socket.io 4.8.1 ([MIT](https://raw.githubusercontent.com/socketio/socket.io/main/LICENSE))
|
||||||
- underscore 1.13.7 ([MIT](https://raw.githubusercontent.com/jashkenas/underscore/master/LICENSE))
|
- underscore 1.13.7 ([MIT](https://raw.githubusercontent.com/jashkenas/underscore/master/LICENSE))
|
||||||
- utf7 1.0.2 ([BSD](https://www.npmjs.com/package/utf7))
|
- utf7 1.0.2 ([BSD](https://www.npmjs.com/package/utf7))
|
||||||
|
|||||||
@ -388,7 +388,7 @@
|
|||||||
"utils": {
|
"utils": {
|
||||||
"utils_common_fontdir": "null",
|
"utils_common_fontdir": "null",
|
||||||
"utils_fonts_search_patterns": "*.ttf;*.ttc;*.otf",
|
"utils_fonts_search_patterns": "*.ttf;*.ttc;*.otf",
|
||||||
"limits_image_types_upload": "jpg;jpeg;jpe;png;gif;bmp;svg;tiff;tif"
|
"limits_image_types_upload": "jpg;jpeg;jpe;png;gif;bmp;svg;tiff;tif;webp;heic;heif;avif"
|
||||||
},
|
},
|
||||||
"sql": {
|
"sql": {
|
||||||
"type": "postgres",
|
"type": "postgres",
|
||||||
|
|||||||
872
DocService/npm-shrinkwrap.json
generated
872
DocService/npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
@ -21,10 +21,9 @@
|
|||||||
"cron": "1.5.0",
|
"cron": "1.5.0",
|
||||||
"dmdb": "1.0.36002",
|
"dmdb": "1.0.36002",
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.10",
|
||||||
"exif-parser": "0.1.12",
|
|
||||||
"express": "4.21.2",
|
"express": "4.21.2",
|
||||||
"ioredis": "5.6.0",
|
"ioredis": "5.6.0",
|
||||||
"jimp": "0.22.10",
|
"sharp": "0.33.5",
|
||||||
"jsonwebtoken": "9.0.2",
|
"jsonwebtoken": "9.0.2",
|
||||||
"mime": "2.3.1",
|
"mime": "2.3.1",
|
||||||
"mime-db": "1.53.0",
|
"mime-db": "1.53.0",
|
||||||
@ -55,7 +54,9 @@
|
|||||||
"../Common/node_modules/axios/dist/node/axios.cjs"
|
"../Common/node_modules/axios/dist/node/axios.cjs"
|
||||||
],
|
],
|
||||||
"assets": [
|
"assets": [
|
||||||
"node_modules/oracledb/build/Release/oracledb-*.node"
|
"node_modules/oracledb/build/Release/oracledb-*.node",
|
||||||
|
"node_modules/@img/sharp-*/**/*",
|
||||||
|
"node_modules/@img/sharp-libvips-*/**/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -770,9 +770,10 @@ function* commandImgurls(ctx, conn, cmd, outputData) {
|
|||||||
|
|
||||||
let outputUrl = {url: 'error', path: 'error'};
|
let outputUrl = {url: 'error', path: 'error'};
|
||||||
if (data) {
|
if (data) {
|
||||||
data = yield utilsDocService.fixImageExifRotation(ctx, data);
|
// process image: fix EXIF rotation and convert unsupported formats to optimal format
|
||||||
|
data = yield utilsDocService.processImageOptimal(ctx, data);
|
||||||
|
|
||||||
let format = formatChecker.getImageFormat(ctx, data);
|
const format = formatChecker.getImageFormat(ctx, data);
|
||||||
let formatStr;
|
let formatStr;
|
||||||
let isAllow = false;
|
let isAllow = false;
|
||||||
if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN !== format) {
|
if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN !== format) {
|
||||||
@ -792,11 +793,6 @@ function* commandImgurls(ctx, conn, cmd, outputData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isAllow) {
|
if (isAllow) {
|
||||||
if (format === constants.AVS_OFFICESTUDIO_FILE_IMAGE_TIFF) {
|
|
||||||
data = yield utilsDocService.convertImageToPng(ctx, data);
|
|
||||||
format = constants.AVS_OFFICESTUDIO_FILE_IMAGE_PNG;
|
|
||||||
formatStr = formatChecker.getStringFromFormat(format);
|
|
||||||
}
|
|
||||||
let strLocalPath = 'media/' + crypto.randomBytes(16).toString('hex') + '_';
|
let strLocalPath = 'media/' + crypto.randomBytes(16).toString('hex') + '_';
|
||||||
if (urlParsed) {
|
if (urlParsed) {
|
||||||
const urlBasename = pathModule.basename(urlParsed.pathname);
|
const urlBasename = pathModule.basename(urlParsed.pathname);
|
||||||
|
|||||||
@ -36,7 +36,6 @@ const co = require('co');
|
|||||||
const utilsDocService = require('./utilsDocService');
|
const utilsDocService = require('./utilsDocService');
|
||||||
const docsCoServer = require('./DocsCoServer');
|
const docsCoServer = require('./DocsCoServer');
|
||||||
const utils = require('./../../Common/sources/utils');
|
const utils = require('./../../Common/sources/utils');
|
||||||
const constants = require('./../../Common/sources/constants');
|
|
||||||
const storageBase = require('./../../Common/sources/storage/storage-base');
|
const storageBase = require('./../../Common/sources/storage/storage-base');
|
||||||
const formatChecker = require('./../../Common/sources/formatchecker');
|
const formatChecker = require('./../../Common/sources/formatchecker');
|
||||||
const commonDefines = require('./../../Common/sources/commondefines');
|
const commonDefines = require('./../../Common/sources/commondefines');
|
||||||
@ -103,7 +102,9 @@ exports.uploadImageFile = function (req, res) {
|
|||||||
if (200 === httpStatus && docId && req.body && Buffer.isBuffer(req.body)) {
|
if (200 === httpStatus && docId && req.body && Buffer.isBuffer(req.body)) {
|
||||||
let buffer = req.body;
|
let buffer = req.body;
|
||||||
if (buffer.length <= tenImageSize) {
|
if (buffer.length <= tenImageSize) {
|
||||||
let format = formatChecker.getImageFormat(ctx, buffer);
|
// process image: fix EXIF rotation and convert unsupported formats to optimal format
|
||||||
|
buffer = yield utilsDocService.processImageOptimal(ctx, buffer);
|
||||||
|
const format = formatChecker.getImageFormat(ctx, buffer);
|
||||||
let formatStr = formatChecker.getStringFromFormat(format);
|
let formatStr = formatChecker.getStringFromFormat(format);
|
||||||
if (encrypted && PATTERN_ENCRYPTED === buffer.toString('utf8', 0, PATTERN_ENCRYPTED.length)) {
|
if (encrypted && PATTERN_ENCRYPTED === buffer.toString('utf8', 0, PATTERN_ENCRYPTED.length)) {
|
||||||
formatStr = buffer.toString('utf8', PATTERN_ENCRYPTED.length, buffer.indexOf(';', PATTERN_ENCRYPTED.length));
|
formatStr = buffer.toString('utf8', PATTERN_ENCRYPTED.length, buffer.indexOf(';', PATTERN_ENCRYPTED.length));
|
||||||
@ -111,18 +112,11 @@ exports.uploadImageFile = function (req, res) {
|
|||||||
const supportedFormats = tenTypesUpload || 'jpg';
|
const supportedFormats = tenTypesUpload || 'jpg';
|
||||||
const formatLimit = formatStr && -1 !== supportedFormats.indexOf(formatStr);
|
const formatLimit = formatStr && -1 !== supportedFormats.indexOf(formatStr);
|
||||||
if (formatLimit) {
|
if (formatLimit) {
|
||||||
if (format === constants.AVS_OFFICESTUDIO_FILE_IMAGE_TIFF) {
|
|
||||||
buffer = yield utilsDocService.convertImageToPng(ctx, buffer);
|
|
||||||
format = constants.AVS_OFFICESTUDIO_FILE_IMAGE_PNG;
|
|
||||||
formatStr = formatChecker.getStringFromFormat(format);
|
|
||||||
}
|
|
||||||
//a hash is written at the beginning to avoid errors during parallel upload in co-editing
|
//a hash is written at the beginning to avoid errors during parallel upload in co-editing
|
||||||
const strImageName = crypto.randomBytes(16).toString('hex');
|
const strImageName = crypto.randomBytes(16).toString('hex');
|
||||||
const strPathRel = 'media/' + strImageName + '.' + formatStr;
|
const strPathRel = 'media/' + strImageName + '.' + formatStr;
|
||||||
const strPath = docId + '/' + strPathRel;
|
const strPath = docId + '/' + strPathRel;
|
||||||
|
|
||||||
buffer = yield utilsDocService.fixImageExifRotation(ctx, buffer);
|
|
||||||
|
|
||||||
yield storageBase.putObject(ctx, strPath, buffer, buffer.length);
|
yield storageBase.putObject(ctx, strPath, buffer, buffer.length);
|
||||||
output[strPathRel] = yield storageBase.getSignedUrl(
|
output[strPathRel] = yield storageBase.getSignedUrl(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@ -34,12 +34,7 @@
|
|||||||
|
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
const exifParser = require('exif-parser');
|
const sharp = require('sharp');
|
||||||
//set global window to fix issue https://github.com/photopea/UTIF.js/issues/130
|
|
||||||
if (!global.window) {
|
|
||||||
global.window = global;
|
|
||||||
}
|
|
||||||
const Jimp = require('jimp');
|
|
||||||
const locale = require('windows-locale');
|
const locale = require('windows-locale');
|
||||||
const ms = require('ms');
|
const ms = require('ms');
|
||||||
|
|
||||||
@ -49,42 +44,103 @@ const cfgStartNotifyFrom = ms(config.get('license.warning_license_expiration'));
|
|||||||
const cfgNotificationRuleLicenseExpirationWarning = config.get('notification.rules.licenseExpirationWarning.template');
|
const cfgNotificationRuleLicenseExpirationWarning = config.get('notification.rules.licenseExpirationWarning.template');
|
||||||
const cfgNotificationRuleLicenseExpirationError = config.get('notification.rules.licenseExpirationError.template');
|
const cfgNotificationRuleLicenseExpirationError = config.get('notification.rules.licenseExpirationError.template');
|
||||||
|
|
||||||
async function fixImageExifRotation(ctx, buffer) {
|
/**
|
||||||
if (!buffer) {
|
* Determine optimal format (PNG vs JPEG) for image conversion based on image characteristics.
|
||||||
return buffer;
|
* @param {operationContext} ctx Operation context for logging
|
||||||
|
* @param {Object} metadata Image metadata from sharp
|
||||||
|
* @returns {('png'|'jpeg')} Optimal format for conversion
|
||||||
|
*/
|
||||||
|
function determineOptimalFormat(ctx, metadata) {
|
||||||
|
// If image has alpha channel, only PNG can preserve transparency
|
||||||
|
if (metadata.hasAlpha) {
|
||||||
|
return 'png';
|
||||||
}
|
}
|
||||||
//todo move to DocService dir common
|
|
||||||
|
// Analyze color characteristics
|
||||||
|
const width = metadata.width || 0;
|
||||||
|
const height = metadata.height || 0;
|
||||||
|
|
||||||
|
// Small images (likely icons/logos) - prefer PNG
|
||||||
|
// Only apply when dimensions are known (greater than zero)
|
||||||
|
if (width > 0 && height > 0 && width <= 256 && height <= 256) {
|
||||||
|
return 'png';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Large photographic images - prefer JPEG
|
||||||
|
if (width > 800 || height > 600) {
|
||||||
|
return 'jpeg';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to JPEG for general compatibility and smaller file sizes
|
||||||
|
return 'jpeg';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process and optimize image buffer with EXIF rotation fix and modern format conversion.
|
||||||
|
* 1. Fixes EXIF rotation and strips metadata for all images
|
||||||
|
* 2. Converts modern/unsupported formats to optimal formats:
|
||||||
|
* - WebP/HEIC/HEIF/AVIF/TIFF: Convert to optimal format (PNG or JPEG) based on image characteristics
|
||||||
|
* @param {operationContext} ctx Operation context for logging
|
||||||
|
* @param {Buffer} buffer Source image bytes
|
||||||
|
* @returns {Promise<Buffer>} Processed and optimally converted buffer or original buffer
|
||||||
|
*/
|
||||||
|
async function processImageOptimal(ctx, buffer) {
|
||||||
|
if (!buffer) return buffer;
|
||||||
|
|
||||||
|
let needsRotation = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parser = exifParser.create(buffer);
|
const meta = await sharp(buffer, {failOn: 'none'}).metadata();
|
||||||
const exif = parser.parse();
|
needsRotation = meta.orientation && meta.orientation > 1;
|
||||||
if (exif.tags?.Orientation > 1) {
|
const fmt = (meta.format || '').toLowerCase();
|
||||||
ctx.logger.debug('fixImageExifRotation remove exif and rotate:%j', exif);
|
|
||||||
buffer = convertImageTo(ctx, buffer, Jimp.AUTO);
|
// Handle modern formats that need conversion
|
||||||
|
if (fmt === 'webp' || fmt === 'heic' || fmt === 'heif' || fmt === 'avif') {
|
||||||
|
const optimalFormat = determineOptimalFormat(ctx, meta);
|
||||||
|
ctx.logger.debug('processImageOptimal: detected %s, converting to %s%s', fmt, 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
if (fmt === 'png') {
|
||||||
|
return await pipeline.png({compressionLevel: 7}).toBuffer();
|
||||||
|
}
|
||||||
|
return await pipeline.toBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ctx.logger.debug('fixImageExifRotation error:%s', e.stack);
|
ctx.logger.debug('processImageOptimal error:%s', e.stack);
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
async function convertImageToPng(ctx, buffer) {
|
|
||||||
return await convertImageTo(ctx, buffer, Jimp.MIME_PNG);
|
|
||||||
}
|
|
||||||
async function convertImageTo(ctx, buffer, mime) {
|
|
||||||
try {
|
|
||||||
ctx.logger.debug('convertImageTo %s', mime);
|
|
||||||
const image = await Jimp.read(buffer);
|
|
||||||
//remove exif
|
|
||||||
image.bitmap.exifBuffer = undefined;
|
|
||||||
//set jpeg and png quality
|
|
||||||
//https://www.imagemagick.org/script/command-line-options.php#quality
|
|
||||||
image.quality(90);
|
|
||||||
image.deflateLevel(7);
|
|
||||||
buffer = await image.getBufferAsync(mime);
|
|
||||||
} catch (e) {
|
|
||||||
ctx.logger.debug('convertImageTo error:%s', e.stack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} lang
|
* @param {string} lang
|
||||||
@ -143,7 +199,7 @@ async function notifyLicenseExpiration(ctx, endDate) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.fixImageExifRotation = fixImageExifRotation;
|
module.exports.processImageOptimal = processImageOptimal;
|
||||||
module.exports.convertImageToPng = convertImageToPng;
|
module.exports.determineOptimalFormat = determineOptimalFormat;
|
||||||
module.exports.localeToLCID = localeToLCID;
|
module.exports.localeToLCID = localeToLCID;
|
||||||
module.exports.notifyLicenseExpiration = notifyLicenseExpiration;
|
module.exports.notifyLicenseExpiration = notifyLicenseExpiration;
|
||||||
|
|||||||
@ -32,8 +32,6 @@
|
|||||||
"code:check": "run-s lint:check format:check",
|
"code:check": "run-s lint:check format:check",
|
||||||
"code:fix": "run-s lint:fix format:fix",
|
"code:fix": "run-s lint:fix format:fix",
|
||||||
"perf-expired": "cd ./DocService&& cross-env NODE_ENV=development-windows NODE_CONFIG_DIR=../Common/config node ../tests/perf/checkFileExpire.js",
|
"perf-expired": "cd ./DocService&& cross-env NODE_ENV=development-windows NODE_CONFIG_DIR=../Common/config node ../tests/perf/checkFileExpire.js",
|
||||||
"perf-exif": "cd ./DocService&& cross-env NODE_ENV=development-windows NODE_CONFIG_DIR=../Common/config node ../tests/perf/fixImageExifRotation.js",
|
|
||||||
"perf-png": "cd ./DocService&& cross-env NODE_ENV=development-windows NODE_CONFIG_DIR=../Common/config node ../tests/perf/convertImageToPng.js",
|
|
||||||
"unit tests": "cd ./DocService && jest unit --inject-globals=false --config=../tests/jest.config.js",
|
"unit tests": "cd ./DocService && jest unit --inject-globals=false --config=../tests/jest.config.js",
|
||||||
"integration tests with server instance": "cd ./DocService && jest integration/withServerInstance --inject-globals=false --config=../tests/jest.config.js",
|
"integration tests with server instance": "cd ./DocService && jest integration/withServerInstance --inject-globals=false --config=../tests/jest.config.js",
|
||||||
"storage-tests": "cd ./DocService && jest integration/withServerInstance/storage.tests.js --inject-globals=false --config=../tests/jest.config.js",
|
"storage-tests": "cd ./DocService && jest integration/withServerInstance/storage.tests.js --inject-globals=false --config=../tests/jest.config.js",
|
||||||
|
|||||||
@ -1,125 +0,0 @@
|
|||||||
/*
|
|
||||||
* (c) Copyright Ascensio System SIA 2010-2023
|
|
||||||
*
|
|
||||||
* This program is a free software product. You can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
|
||||||
* version 3 as published by the Free Software Foundation. In accordance with
|
|
||||||
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
|
||||||
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
|
||||||
* of any third-party rights.
|
|
||||||
*
|
|
||||||
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
|
||||||
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
|
||||||
*
|
|
||||||
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
|
||||||
* street, Riga, Latvia, EU, LV-1050.
|
|
||||||
*
|
|
||||||
* The interactive user interfaces in modified source and object code versions
|
|
||||||
* of the Program must display Appropriate Legal Notices, as required under
|
|
||||||
* Section 5 of the GNU AGPL version 3.
|
|
||||||
*
|
|
||||||
* Pursuant to Section 7(b) of the License you must retain the original Product
|
|
||||||
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
|
||||||
* grant you any rights under trademark law for use of our trademarks.
|
|
||||||
*
|
|
||||||
* All the Product's GUI elements, including illustrations and icon sets, as
|
|
||||||
* well as technical writing content are licensed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
|
||||||
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const {createHistogram, performance, PerformanceObserver} = require('node:perf_hooks');
|
|
||||||
|
|
||||||
const {readdir, mkdir, readFile, writeFile} = require('node:fs/promises');
|
|
||||||
const path = require('path');
|
|
||||||
// const Jimp = require('Jimp');
|
|
||||||
const utils = require('./../../Common/sources/utils');
|
|
||||||
const operationContext = require('./../../Common/sources/operationContext');
|
|
||||||
const utilsDocService = require('./../../DocService/sources/utilsDocService');
|
|
||||||
|
|
||||||
const ctx = operationContext.global;
|
|
||||||
|
|
||||||
const histograms = {};
|
|
||||||
|
|
||||||
async function beforeStart() {
|
|
||||||
const timerify = function (func) {
|
|
||||||
const histogram = createHistogram();
|
|
||||||
histograms[func.name] = histogram;
|
|
||||||
return performance.timerify(func, {histogram});
|
|
||||||
};
|
|
||||||
utilsDocService.convertImageToPng = timerify(utilsDocService.convertImageToPng);
|
|
||||||
// Jimp.read = timerify(Jimp.read);
|
|
||||||
|
|
||||||
const obs = new PerformanceObserver(list => {
|
|
||||||
const entries = list.getEntries();
|
|
||||||
entries.forEach(entry => {
|
|
||||||
const duration = Math.round(entry.duration * 1000) / 1000;
|
|
||||||
console.log(`${entry.name}:${duration}ms`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
obs.observe({entryTypes: ['function']});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function beforeEnd() {
|
|
||||||
const logHistogram = function (histogram, name) {
|
|
||||||
const mean = Math.round(histogram.mean / 1000) / 1000;
|
|
||||||
const min = Math.round(histogram.min / 1000) / 1000;
|
|
||||||
const max = Math.round(histogram.max / 1000) / 1000;
|
|
||||||
const count = histogram.count;
|
|
||||||
ctx.logger.info(`histogram ${name}: count=${count}, mean=${mean}ms, min=${min}ms, max=${max}ms`);
|
|
||||||
};
|
|
||||||
await utils.sleep(1000);
|
|
||||||
for (const name in histograms) {
|
|
||||||
logHistogram(histograms[name], name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fixInDir(dirIn, dirOut) {
|
|
||||||
ctx.logger.info('dirIn:%s', dirIn);
|
|
||||||
ctx.logger.info('dirOut:%s', dirOut);
|
|
||||||
const dirents = await readdir(dirIn, {withFileTypes: true, recursive: true});
|
|
||||||
for (const dirent of dirents) {
|
|
||||||
if (dirent.isFile()) {
|
|
||||||
const file = dirent.name;
|
|
||||||
ctx.logger.info('fixInDir:%s', file);
|
|
||||||
const buffer = await readFile(path.join(dirent.path, file));
|
|
||||||
const bufferNew = await utilsDocService.convertImageToPng(ctx, buffer);
|
|
||||||
if (buffer !== bufferNew) {
|
|
||||||
const outputPath = path.join(dirOut, dirent.path.substring(dirIn.length), path.basename(file, path.extname(file)) + '.png');
|
|
||||||
await mkdir(path.dirname(outputPath), {recursive: true});
|
|
||||||
await writeFile(outputPath, bufferNew);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function startTest() {
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
if (args.length < 2) {
|
|
||||||
ctx.logger.error('missing arguments.USAGE: convertImageToPng.js "dirIn" "dirOut"');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.logger.info('test started');
|
|
||||||
await beforeStart();
|
|
||||||
|
|
||||||
await fixInDir(args[0], args[1]);
|
|
||||||
|
|
||||||
await beforeEnd();
|
|
||||||
ctx.logger.info('test finished');
|
|
||||||
}
|
|
||||||
|
|
||||||
startTest()
|
|
||||||
.then(() => {
|
|
||||||
//delay to log observer events
|
|
||||||
return utils.sleep(1000);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
ctx.logger.error(err.stack);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
/*
|
|
||||||
* (c) Copyright Ascensio System SIA 2010-2024
|
|
||||||
*
|
|
||||||
* This program is a free software product. You can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
|
||||||
* version 3 as published by the Free Software Foundation. In accordance with
|
|
||||||
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
|
||||||
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
|
||||||
* of any third-party rights.
|
|
||||||
*
|
|
||||||
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
|
||||||
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
|
||||||
*
|
|
||||||
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
|
||||||
* street, Riga, Latvia, EU, LV-1050.
|
|
||||||
*
|
|
||||||
* The interactive user interfaces in modified source and object code versions
|
|
||||||
* of the Program must display Appropriate Legal Notices, as required under
|
|
||||||
* Section 5 of the GNU AGPL version 3.
|
|
||||||
*
|
|
||||||
* Pursuant to Section 7(b) of the License you must retain the original Product
|
|
||||||
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
|
||||||
* grant you any rights under trademark law for use of our trademarks.
|
|
||||||
*
|
|
||||||
* All the Product's GUI elements, including illustrations and icon sets, as
|
|
||||||
* well as technical writing content are licensed under the terms of the
|
|
||||||
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
|
||||||
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const {createHistogram, performance, PerformanceObserver} = require('node:perf_hooks');
|
|
||||||
|
|
||||||
const {readdir, mkdir, readFile, writeFile} = require('node:fs/promises');
|
|
||||||
const path = require('path');
|
|
||||||
// const Jimp = require('Jimp');
|
|
||||||
const utils = require('./../../Common/sources/utils');
|
|
||||||
const operationContext = require('./../../Common/sources/operationContext');
|
|
||||||
const utilsDocService = require('./../../DocService/sources/utilsDocService');
|
|
||||||
|
|
||||||
const ctx = operationContext.global;
|
|
||||||
|
|
||||||
const histograms = {};
|
|
||||||
|
|
||||||
async function beforeStart() {
|
|
||||||
const timerify = function (func) {
|
|
||||||
const histogram = createHistogram();
|
|
||||||
histograms[func.name] = histogram;
|
|
||||||
return performance.timerify(func, {histogram});
|
|
||||||
};
|
|
||||||
utilsDocService.fixImageExifRotation = timerify(utilsDocService.fixImageExifRotation);
|
|
||||||
// Jimp.read = timerify(Jimp.read);
|
|
||||||
|
|
||||||
const obs = new PerformanceObserver(list => {
|
|
||||||
const entries = list.getEntries();
|
|
||||||
entries.forEach(entry => {
|
|
||||||
const duration = Math.round(entry.duration * 1000) / 1000;
|
|
||||||
console.log(`${entry.name}:${duration}ms`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
obs.observe({entryTypes: ['function']});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function beforeEnd() {
|
|
||||||
const logHistogram = function (histogram, name) {
|
|
||||||
const mean = Math.round(histogram.mean / 1000) / 1000;
|
|
||||||
const min = Math.round(histogram.min / 1000) / 1000;
|
|
||||||
const max = Math.round(histogram.max / 1000) / 1000;
|
|
||||||
const count = histogram.count;
|
|
||||||
ctx.logger.info(`histogram ${name}: count=${count}, mean=${mean}ms, min=${min}ms, max=${max}ms`);
|
|
||||||
};
|
|
||||||
await utils.sleep(1000);
|
|
||||||
for (const name in histograms) {
|
|
||||||
logHistogram(histograms[name], name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fixInDir(dirIn, dirOut) {
|
|
||||||
ctx.logger.info('dirIn:%s', dirIn);
|
|
||||||
ctx.logger.info('dirOut:%s', dirOut);
|
|
||||||
const dirents = await readdir(dirIn, {withFileTypes: true, recursive: true});
|
|
||||||
for (const dirent of dirents) {
|
|
||||||
if (dirent.isFile()) {
|
|
||||||
const file = dirent.name;
|
|
||||||
ctx.logger.info('fixInDir:%s', file);
|
|
||||||
const buffer = await readFile(path.join(dirent.path, file));
|
|
||||||
const bufferNew = await utilsDocService.fixImageExifRotation(ctx, buffer);
|
|
||||||
if (buffer !== bufferNew) {
|
|
||||||
const outputPath = path.join(dirOut, dirent.path.substring(dirIn.length), file);
|
|
||||||
await mkdir(path.dirname(outputPath), {recursive: true});
|
|
||||||
await writeFile(outputPath, bufferNew);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function startTest() {
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
if (args.length < 2) {
|
|
||||||
ctx.logger.error('missing arguments.USAGE: fixImageExifRotation.js "dirIn" "dirOut"');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.logger.info('test started');
|
|
||||||
await beforeStart();
|
|
||||||
|
|
||||||
await fixInDir(args[0], args[1]);
|
|
||||||
|
|
||||||
await beforeEnd();
|
|
||||||
ctx.logger.info('test finished');
|
|
||||||
}
|
|
||||||
|
|
||||||
startTest()
|
|
||||||
.then(() => {
|
|
||||||
//delay to log observer events
|
|
||||||
return utils.sleep(1000);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
ctx.logger.error(err.stack);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user