mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-02-10 09:55:11 +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))
|
||||
- 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))
|
||||
- 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))
|
||||
- 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))
|
||||
- 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))
|
||||
- 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))
|
||||
@ -51,6 +49,7 @@
|
||||
- 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))
|
||||
- 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))
|
||||
- underscore 1.13.7 ([MIT](https://raw.githubusercontent.com/jashkenas/underscore/master/LICENSE))
|
||||
- utf7 1.0.2 ([BSD](https://www.npmjs.com/package/utf7))
|
||||
|
||||
@ -388,7 +388,7 @@
|
||||
"utils": {
|
||||
"utils_common_fontdir": "null",
|
||||
"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": {
|
||||
"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",
|
||||
"dmdb": "1.0.36002",
|
||||
"ejs": "3.1.10",
|
||||
"exif-parser": "0.1.12",
|
||||
"express": "4.21.2",
|
||||
"ioredis": "5.6.0",
|
||||
"jimp": "0.22.10",
|
||||
"sharp": "0.33.5",
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"mime": "2.3.1",
|
||||
"mime-db": "1.53.0",
|
||||
@ -55,7 +54,9 @@
|
||||
"../Common/node_modules/axios/dist/node/axios.cjs"
|
||||
],
|
||||
"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'};
|
||||
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 isAllow = false;
|
||||
if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN !== format) {
|
||||
@ -792,11 +793,6 @@ function* commandImgurls(ctx, conn, cmd, outputData) {
|
||||
}
|
||||
}
|
||||
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') + '_';
|
||||
if (urlParsed) {
|
||||
const urlBasename = pathModule.basename(urlParsed.pathname);
|
||||
|
||||
@ -36,7 +36,6 @@ const co = require('co');
|
||||
const utilsDocService = require('./utilsDocService');
|
||||
const docsCoServer = require('./DocsCoServer');
|
||||
const utils = require('./../../Common/sources/utils');
|
||||
const constants = require('./../../Common/sources/constants');
|
||||
const storageBase = require('./../../Common/sources/storage/storage-base');
|
||||
const formatChecker = require('./../../Common/sources/formatchecker');
|
||||
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)) {
|
||||
let buffer = req.body;
|
||||
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);
|
||||
if (encrypted && PATTERN_ENCRYPTED === buffer.toString('utf8', 0, 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 formatLimit = formatStr && -1 !== supportedFormats.indexOf(formatStr);
|
||||
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
|
||||
const strImageName = crypto.randomBytes(16).toString('hex');
|
||||
const strPathRel = 'media/' + strImageName + '.' + formatStr;
|
||||
const strPath = docId + '/' + strPathRel;
|
||||
|
||||
buffer = yield utilsDocService.fixImageExifRotation(ctx, buffer);
|
||||
|
||||
yield storageBase.putObject(ctx, strPath, buffer, buffer.length);
|
||||
output[strPathRel] = yield storageBase.getSignedUrl(
|
||||
ctx,
|
||||
|
||||
@ -34,12 +34,7 @@
|
||||
|
||||
const util = require('util');
|
||||
const config = require('config');
|
||||
const exifParser = require('exif-parser');
|
||||
//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 sharp = require('sharp');
|
||||
const locale = require('windows-locale');
|
||||
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 cfgNotificationRuleLicenseExpirationError = config.get('notification.rules.licenseExpirationError.template');
|
||||
|
||||
async function fixImageExifRotation(ctx, buffer) {
|
||||
if (!buffer) {
|
||||
return buffer;
|
||||
/**
|
||||
* Determine optimal format (PNG vs JPEG) for image conversion based on image characteristics.
|
||||
* @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 {
|
||||
const parser = exifParser.create(buffer);
|
||||
const exif = parser.parse();
|
||||
if (exif.tags?.Orientation > 1) {
|
||||
ctx.logger.debug('fixImageExifRotation remove exif and rotate:%j', exif);
|
||||
buffer = convertImageTo(ctx, buffer, Jimp.AUTO);
|
||||
const meta = await sharp(buffer, {failOn: 'none'}).metadata();
|
||||
needsRotation = meta.orientation && meta.orientation > 1;
|
||||
const fmt = (meta.format || '').toLowerCase();
|
||||
|
||||
// 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) {
|
||||
ctx.logger.debug('fixImageExifRotation 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);
|
||||
ctx.logger.debug('processImageOptimal error:%s', e.stack);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} lang
|
||||
@ -143,7 +199,7 @@ async function notifyLicenseExpiration(ctx, endDate) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.fixImageExifRotation = fixImageExifRotation;
|
||||
module.exports.convertImageToPng = convertImageToPng;
|
||||
module.exports.processImageOptimal = processImageOptimal;
|
||||
module.exports.determineOptimalFormat = determineOptimalFormat;
|
||||
module.exports.localeToLCID = localeToLCID;
|
||||
module.exports.notifyLicenseExpiration = notifyLicenseExpiration;
|
||||
|
||||
@ -32,8 +32,6 @@
|
||||
"code:check": "run-s lint:check format:check",
|
||||
"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-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",
|
||||
"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",
|
||||
|
||||
@ -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