Adding to the above solution, which I wasn't a huge fan of.
The routine below tries to simulate the object-fit: contain CSS cropping and sizing. This first figures out what size the intermediate resize step needs to be to both maintain the aspect ratio and provide the necessary width and height to crop to the desired output, and then runs a video filter crop on the result to just pull the desired output dimensions.
I also use the temp
npm package to generate empty files in the system temp folder that will be used as output file destinations for ffmpeg
.
import FFMpeg from 'fluent-ffmpeg';
import temp from 'temp-write';
function getDimensions(media) {
return new Promise((resolve, reject) => {
FFMpeg.ffprobe(media, async (err, metadata) => {
if (err) {
reject(err);
return;
}
resolve({
mediaWidth: metadata.streams[0].width,
mediaHeight: metadata.streams[0].height,
});
});
});
}
function FFMpegPromisify(routine, output) {
return new Promise((resolve, reject) => {
routine
.on('error', (err) => {
reject(err);
})
.on('end', () => {
resolve();
})
.save(output);
});
}
module.exports = {
resize: async ({ data, width, height }) => {
let path = temp.sync(data);
const { mediaWidth, mediaHeight } = await getDimensions(path);
let mediaAspectRatio = mediaWidth / mediaHeight;
let widthResizeRatio = width / mediaWidth;
let heightResizeRatio = height / mediaHeight;
let maxAdjustedWidth = Math.round(Math.max(mediaWidth * widthResizeRatio, height * mediaAspectRatio));
let maxAdjustedHeight = Math.round(Math.max(mediaHeight * heightResizeRatio, width / mediaAspectRatio));
let tempResizePath = temp.sync('', 'file.mp4');
await FFMpegPromisify(FFMpeg(path).format('mp4').size(`${maxAdjustedWidth}x${maxAdjustedHeight}`), tempResizePath);
let tempCropPath = temp.sync('', 'file.mp4');
let cropX = (maxAdjustedWidth - width) / 2;
let cropY = (maxAdjustedHeight - height) / 2;
await FFMpegPromisify(FFMpeg(tempResizePath).format('mp4').videoFilter([
{
filter: "crop",
options: {
w: width,
h: height,
x: cropX,
y: cropY
},
}
]), tempCropPath);
return tempCropPath; // contains the final, cropped result
}
}
let file = require('fs').readFileSync('C:\\FFMpeg\\sample.mp4');
module.exports.resize({ data: file, width: 320, height: 1080 });