strapi/packages/core/upload/server/services/image-manipulation.js

176 lines
4.2 KiB
JavaScript
Raw Normal View History

'use strict';
/**
* Image manipulation functions
*/
2022-01-04 19:21:05 +01:00
const fs = require('fs');
const { join } = require('path');
const sharp = require('sharp');
2021-08-19 22:27:00 +02:00
const { getService } = require('../utils');
const { bytesToKbytes } = require('../utils/file');
2022-01-04 19:21:05 +01:00
const writeStreamToFile = (stream, path) =>
new Promise((resolve, reject) => {
const writeStream = fs.createWriteStream(path);
stream.pipe(writeStream);
writeStream.on('close', resolve);
writeStream.on('error', reject);
});
const getMetadata = file =>
new Promise((resolve, reject) => {
const pipeline = sharp();
pipeline
.metadata()
.then(resolve)
.catch(reject);
file.getStream().pipe(pipeline);
});
2022-01-04 19:21:05 +01:00
const getDimensions = async file => {
const { width = null, height = null } = await getMetadata(file);
return { width, height };
};
const THUMBNAIL_RESIZE_OPTIONS = {
width: 245,
height: 156,
fit: 'inside',
};
2022-01-04 19:21:05 +01:00
const resizeFileTo = async (file, options, { name, hash }, { tmpFolderPath }) => {
const filePath = join(tmpFolderPath, hash);
await writeStreamToFile(file.getStream().pipe(sharp().resize(options)), filePath);
2022-01-04 19:21:05 +01:00
const newFile = {
name,
hash,
ext: file.ext,
mime: file.mime,
path: file.path || null,
getStream: () => fs.createReadStream(filePath),
};
2022-01-04 19:21:05 +01:00
const { width, height, size } = await getMetadata(newFile);
2022-01-04 19:21:05 +01:00
Object.assign(newFile, { width, height, size: bytesToKbytes(size) });
return newFile;
};
2022-01-04 19:21:05 +01:00
const generateThumbnail = async (file, { tmpFolderPath }) => {
if (!(await canBeProccessed(file))) {
return null;
}
2022-01-04 19:21:05 +01:00
if (
file.width > THUMBNAIL_RESIZE_OPTIONS.width ||
file.height > THUMBNAIL_RESIZE_OPTIONS.height
) {
const newFile = await resizeFileTo(
file,
THUMBNAIL_RESIZE_OPTIONS,
{
name: `thumbnail_${file.name}`,
hash: `thumbnail_${file.hash}`,
2022-01-04 19:21:05 +01:00
},
{ tmpFolderPath }
);
return newFile;
}
return null;
};
2022-01-04 19:21:05 +01:00
const optimize = async (file, { tmpFolderPath }) => {
2021-08-19 22:27:00 +02:00
const { sizeOptimization = false, autoOrientation = false } = await getService(
'upload'
).getSettings();
2022-01-04 19:21:05 +01:00
const newFile = { ...file };
if (!sizeOptimization || !(await canBeProccessed(file))) {
return newFile;
}
2022-01-04 19:21:05 +01:00
if (autoOrientation) {
const filePath = join(tmpFolderPath, `optimized-${file.hash}`);
await writeStreamToFile(file.getStream().pipe(sharp().rotate()), filePath);
newFile.getStream = () => fs.createReadStream(filePath);
}
const { width, height, size } = await getMetadata(newFile);
Object.assign(newFile, { width, height, size: bytesToKbytes(size) });
return newFile;
};
const DEFAULT_BREAKPOINTS = {
large: 1000,
medium: 750,
small: 500,
};
2021-08-06 18:09:49 +02:00
const getBreakpoints = () => strapi.config.get('plugin.upload.breakpoints', DEFAULT_BREAKPOINTS);
2022-01-04 19:21:05 +01:00
const generateResponsiveFormats = async (file, { tmpFolderPath }) => {
2021-08-19 22:27:00 +02:00
const { responsiveDimensions = false } = await getService('upload').getSettings();
if (!responsiveDimensions) return [];
2022-01-04 19:21:05 +01:00
if (!(await canBeProccessed(file))) {
return [];
}
2022-01-04 19:21:05 +01:00
const originalDimensions = await getDimensions(file);
const breakpoints = getBreakpoints();
return Promise.all(
Object.keys(breakpoints).map(key => {
const breakpoint = breakpoints[key];
if (breakpointSmallerThan(breakpoint, originalDimensions)) {
2022-01-04 19:21:05 +01:00
return generateBreakpoint(key, { file, breakpoint, originalDimensions }, { tmpFolderPath });
}
})
);
};
2022-01-04 19:21:05 +01:00
const generateBreakpoint = async (key, { file, breakpoint }, { tmpFolderPath }) => {
const newFile = await resizeFileTo(
file,
{
width: breakpoint,
height: breakpoint,
fit: 'inside',
},
{
name: `${key}_${file.name}`,
hash: `${key}_${file.hash}`,
},
{ tmpFolderPath }
);
return {
key,
file: newFile,
};
};
const breakpointSmallerThan = (breakpoint, { width, height }) => {
return breakpoint < width || breakpoint < height;
};
const formatsToProccess = ['jpeg', 'png', 'webp', 'tiff'];
2022-01-04 19:21:05 +01:00
const canBeProccessed = async file => {
const { format } = await getMetadata(file);
return format && formatsToProccess.includes(format);
};
2021-07-08 11:20:13 +02:00
module.exports = () => ({
getDimensions,
generateResponsiveFormats,
generateThumbnail,
bytesToKbytes,
optimize,
2021-07-08 11:20:13 +02:00
});