Ghost/ghost/image-transform/lib/transform.js

110 lines
3.6 KiB
JavaScript
Raw Normal View History

const Promise = require('bluebird');
const errors = require('@tryghost/errors');
const fs = require('fs-extra');
const path = require('path');
/**
* 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);
/**
* @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 = {}) => {
return fs.readFile(options.in)
.then((data) => {
return unsafeResizeFromBuffer(data, {
width: options.width
});
})
.then((data) => {
return fs.writeFile(options.out, data);
});
};
/**
* Resize an image
*
* @param {Buffer} originalBuffer image to resize
* @param {{width, height}} options
* @returns {Buffer} the resizedBuffer
*/
const unsafeResizeFromBuffer = (originalBuffer, {width, height} = {}) => {
const sharp = require('sharp');
// Disable the internal libvips cache - https://sharp.pixelplumbing.com/api-utility#cache
sharp.cache(false);
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;
});
};
/**
* Internal utility to wrap all transform functions in error handling
* Allows us to keep Sharp as an optional dependency
*
* @param {Function} fn
*/
const makeSafe = fn => (...args) => {
try {
require('sharp');
} catch (err) {
return Promise.reject(new errors.InternalServerError({
message: 'Sharp wasn\'t installed',
code: 'SHARP_INSTALLATION',
err: err
}));
}
return fn(...args).catch((err) => {
throw new errors.InternalServerError({
message: 'Unable to manipulate image.',
err: err,
code: 'IMAGE_PROCESSING'
});
});
};
const generateOriginalImageName = (originalPath) => {
const parsedFileName = path.parse(originalPath);
return path.join(parsedFileName.dir, `${parsedFileName.name}_o${parsedFileName.ext}`);
};
module.exports.canTransformFiles = canTransformFiles;
module.exports.canTransformFileExtension = canTransformFileExtension;
module.exports.generateOriginalImageName = generateOriginalImageName;
module.exports.resizeFromPath = makeSafe(unsafeResizeFromPath);
module.exports.resizeFromBuffer = makeSafe(unsafeResizeFromBuffer);