2018-08-30 19:30:36 +03:00
|
|
|
const Promise = require('bluebird');
|
2020-03-25 13:25:25 +03:00
|
|
|
const errors = require('@tryghost/errors');
|
2018-11-12 20:52:36 +03:00
|
|
|
const fs = require('fs-extra');
|
2020-03-25 23:49:55 +03:00
|
|
|
const path = require('path');
|
2018-08-30 19:30:36 +03:00
|
|
|
|
2020-07-02 20:00:12 +03:00
|
|
|
/**
|
|
|
|
* Check if this tool can handle any file transformations as Sharp is an optional dependency
|
|
|
|
*/
|
|
|
|
const canTransformFiles = () => {
|
|
|
|
try {
|
|
|
|
require('sharp');
|
|
|
|
return true;
|
|
|
|
} catch (err) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if this tool can handle a particular extension
|
|
|
|
* NOTE: .gif optimization is currently not supported by sharp but will be soon
|
|
|
|
* as there has been support added in underlying libvips library https://github.com/lovell/sharp/issues/1372
|
|
|
|
* As for .svg files, sharp only supports conversion to png, and this does not
|
|
|
|
* play well with animated svg files
|
|
|
|
* @param {String} ext the extension to check, including the leading dot
|
|
|
|
*/
|
|
|
|
const canTransformFileExtension = ext => !['.gif', '.svg', '.svgz', '.ico'].includes(ext);
|
|
|
|
|
2018-08-30 19:30:36 +03:00
|
|
|
/**
|
2020-03-25 15:53:33 +03:00
|
|
|
* @NOTE: Sharp cannot operate on the same image path, that's why we have to use in & out paths.
|
|
|
|
*
|
|
|
|
* We currently can't enable compression or having more config options, because of
|
|
|
|
* https://github.com/lovell/sharp/issues/1360.
|
|
|
|
*
|
|
|
|
* Resize an image referenced by the `in` path and write it to the `out` path
|
|
|
|
* @param {{in, out, width}} options
|
|
|
|
*/
|
|
|
|
const unsafeResizeFromPath = (options = {}) => {
|
2018-11-12 20:52:36 +03:00
|
|
|
return fs.readFile(options.in)
|
|
|
|
.then((data) => {
|
2020-03-25 15:53:33 +03:00
|
|
|
return unsafeResizeFromBuffer(data, {
|
2018-12-14 07:54:52 +03:00
|
|
|
width: options.width
|
2018-08-30 19:30:36 +03:00
|
|
|
});
|
2018-12-14 07:54:52 +03:00
|
|
|
})
|
|
|
|
.then((data) => {
|
|
|
|
return fs.writeFile(options.out, data);
|
2018-08-30 19:30:36 +03:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2020-03-25 15:53:33 +03:00
|
|
|
/**
|
|
|
|
* Resize an image
|
|
|
|
*
|
|
|
|
* @param {Buffer} originalBuffer image to resize
|
|
|
|
* @param {{width, height}} options
|
|
|
|
* @returns {Buffer} the resizedBuffer
|
|
|
|
*/
|
|
|
|
const unsafeResizeFromBuffer = (originalBuffer, {width, height} = {}) => {
|
2018-12-13 16:25:24 +03:00
|
|
|
const sharp = require('sharp');
|
|
|
|
return sharp(originalBuffer)
|
|
|
|
.resize(width, height, {
|
|
|
|
// CASE: dont make the image bigger than it was
|
|
|
|
withoutEnlargement: true
|
|
|
|
})
|
|
|
|
// CASE: Automatically remove metadata and rotate based on the orientation.
|
|
|
|
.rotate()
|
|
|
|
.toBuffer()
|
|
|
|
.then((resizedBuffer) => {
|
|
|
|
return resizedBuffer.length < originalBuffer.length ? resizedBuffer : originalBuffer;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2020-03-25 15:53:33 +03:00
|
|
|
/**
|
|
|
|
* Internal utility to wrap all transform functions in error handling
|
|
|
|
* Allows us to keep Sharp as an optional dependency
|
|
|
|
*
|
|
|
|
* @param {Function} fn
|
|
|
|
*/
|
2018-12-14 07:54:52 +03:00
|
|
|
const makeSafe = fn => (...args) => {
|
2018-12-13 16:25:24 +03:00
|
|
|
try {
|
|
|
|
require('sharp');
|
2018-12-14 07:54:52 +03:00
|
|
|
} catch (err) {
|
2020-03-25 13:25:25 +03:00
|
|
|
return Promise.reject(new errors.InternalServerError({
|
2018-12-14 07:54:52 +03:00
|
|
|
message: 'Sharp wasn\'t installed',
|
|
|
|
code: 'SHARP_INSTALLATION',
|
|
|
|
err: err
|
|
|
|
}));
|
2018-12-13 16:25:24 +03:00
|
|
|
}
|
2018-12-14 07:54:52 +03:00
|
|
|
return fn(...args).catch((err) => {
|
2020-03-25 13:25:25 +03:00
|
|
|
throw new errors.InternalServerError({
|
2018-12-14 07:54:52 +03:00
|
|
|
message: 'Unable to manipulate image.',
|
|
|
|
err: err,
|
|
|
|
code: 'IMAGE_PROCESSING'
|
|
|
|
});
|
|
|
|
});
|
2018-12-13 16:25:24 +03:00
|
|
|
};
|
2018-12-14 07:54:52 +03:00
|
|
|
|
2020-03-25 23:49:55 +03:00
|
|
|
const generateOriginalImageName = (originalPath) => {
|
|
|
|
const parsedFileName = path.parse(originalPath);
|
|
|
|
return path.join(parsedFileName.dir, `${parsedFileName.name}_o${parsedFileName.ext}`);
|
|
|
|
};
|
|
|
|
|
2020-07-02 20:00:12 +03:00
|
|
|
module.exports.canTransformFiles = canTransformFiles;
|
2019-01-03 12:28:37 +03:00
|
|
|
module.exports.canTransformFileExtension = canTransformFileExtension;
|
2020-07-02 20:00:12 +03:00
|
|
|
module.exports.generateOriginalImageName = generateOriginalImageName;
|
2020-03-25 15:53:33 +03:00
|
|
|
module.exports.resizeFromPath = makeSafe(unsafeResizeFromPath);
|
|
|
|
module.exports.resizeFromBuffer = makeSafe(unsafeResizeFromBuffer);
|