You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nodebb/src/image.js

158 lines
4.3 KiB
JavaScript

'use strict';
const os = require('os');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const winston = require('winston');
const file = require('./file');
const plugins = require('./plugins');
const meta = require('./meta');
const image = module.exports;
function requireSharp() {
const sharp = require('sharp');
if (os.platform() === 'win32') {
// https://github.com/lovell/sharp/issues/1259
sharp.cache(false);
}
return sharp;
}
image.isFileTypeAllowed = async function (path) {
const plugins = require('./plugins');
if (plugins.hooks.hasListeners('filter:image.isFileTypeAllowed')) {
return await plugins.hooks.fire('filter:image.isFileTypeAllowed', path);
}
const sharp = require('sharp');
await sharp(path, {
failOnError: true,
}).metadata();
};
image.resizeImage = async function (data) {
if (plugins.hooks.hasListeners('filter:image.resize')) {
await plugins.hooks.fire('filter:image.resize', {
path: data.path,
target: data.target,
width: data.width,
height: data.height,
quality: data.quality,
});
} else {
const sharp = requireSharp();
const buffer = await fs.promises.readFile(data.path);
const sharpImage = sharp(buffer, {
failOnError: true,
});
const metadata = await sharpImage.metadata();
sharpImage.rotate(); // auto-orients based on exif data
sharpImage.resize(data.hasOwnProperty('width') ? data.width : null, data.hasOwnProperty('height') ? data.height : null);
if (data.quality && metadata.format === 'jpeg') {
sharpImage.jpeg({ quality: data.quality });
}
await sharpImage.toFile(data.target || data.path);
}
};
image.normalise = async function (path) {
if (plugins.hooks.hasListeners('filter:image.normalise')) {
await plugins.hooks.fire('filter:image.normalise', {
path: path,
});
} else {
const sharp = requireSharp();
await sharp(path, { failOnError: true }).png().toFile(path + '.png');
}
return path + '.png';
};
image.size = async function (path) {
let imageData;
if (plugins.hooks.hasListeners('filter:image.size')) {
imageData = await plugins.hooks.fire('filter:image.size', {
path: path,
});
} else {
const sharp = requireSharp();
imageData = await sharp(path, { failOnError: true }).metadata();
}
return imageData ? { width: imageData.width, height: imageData.height } : undefined;
};
image.stripEXIF = async function (path) {
if (!meta.config.stripEXIFData || path.endsWith('.gif')) {
return;
}
try {
const buffer = await fs.promises.readFile(path);
const sharp = requireSharp();
await sharp(buffer, { failOnError: true }).rotate().toFile(path);
} catch (err) {
winston.error(err.stack);
}
};
image.checkDimensions = async function (path) {
const meta = require('./meta');
const result = await image.size(path);
if (result.width > meta.config.rejectImageWidth || result.height > meta.config.rejectImageHeight) {
throw new Error('[[error:invalid-image-dimensions]]');
}
};
image.convertImageToBase64 = async function (path) {
return await fs.promises.readFile(path, 'base64');
};
image.mimeFromBase64 = function (imageData) {
return imageData.slice(5, imageData.indexOf('base64') - 1);
};
image.extensionFromBase64 = function (imageData) {
return file.typeToExtension(image.mimeFromBase64(imageData));
};
image.writeImageDataToTempFile = async function (imageData) {
const filename = crypto.createHash('md5').update(imageData).digest('hex');
const type = image.mimeFromBase64(imageData);
const extension = file.typeToExtension(type);
const filepath = path.join(os.tmpdir(), filename + extension);
const buffer = Buffer.from(imageData.slice(imageData.indexOf('base64') + 7), 'base64');
await fs.promises.writeFile(filepath, buffer, { encoding: 'base64' });
return filepath;
};
image.sizeFromBase64 = function (imageData) {
return Buffer.from(imageData.slice(imageData.indexOf('base64') + 7), 'base64').length;
};
image.uploadImage = async function (filename, folder, imageData) {
if (plugins.hooks.hasListeners('filter:uploadImage')) {
return await plugins.hooks.fire('filter:uploadImage', {
image: imageData,
uid: imageData.uid,
folder: folder,
});
}
await image.isFileTypeAllowed(imageData.path);
const upload = await file.saveFileToLocal(filename, folder, imageData.path);
return {
url: upload.url,
path: upload.path,
name: imageData.name,
};
};
require('./promisify')(image);