video-stitch
Version:
Stitches video clips on top of another clip using ffmpeg
142 lines (107 loc) • 3.5 kB
JavaScript
;
let _ = require('lodash');
let moment = require('moment');
require('moment-duration-format');
let tmp = require('tmp');
let shelljs = require('shelljs');
let fext = require('file-extension');
let Promise = require('bluebird');
module.exports = function videoCut(spec) {
let that = null;
let originalVideo = null, clips = null;
const durationFormat = 'hh:mm:ss';
spec = _.defaults(spec, {
silent: true,
ffmpeg_path:'ffmpeg'
});
function setOriginal(org) {
if (!org.fileName || !org.duration) {
throw new Error("Expected properties `fileName` or `duration` not found.");
}
originalVideo = org;
return that;
}
function setClips(_clips) {
if (_.isArray(_clips)) {
clips = _(_clips).map(clip => {
clip.duration = moment.duration(clip.duration).format(durationFormat, { trim: false });
return clip;
}).sortBy('startTime').value();
} else {
throw new Error('Expected parameter to be of type `Array`');
}
return that;
}
function getCutsForVideo(clips) {
let originalCuts = [];
clips.forEach((clip, index) => {
let startForThisClip = index === 0
? moment.duration()
: moment.duration(clips[index - 1].startTime)
.add(moment.duration(clips[index - 1].duration));
let durationOfThisClip = moment.duration(clip.startTime).subtract(startForThisClip);
originalCuts.push({
startTime: startForThisClip.format(durationFormat, { trim: false }),
duration: durationOfThisClip.format(durationFormat, { trim: false })
});
});
// Add remaining clip
let lastClip = clips[clips.length - 1];
let lastClipStartTime = moment.duration(lastClip.startTime)
.add(moment.duration(lastClip.duration));
let lastClipDuration = moment.duration(originalVideo.duration)
.subtract(lastClipStartTime);
originalCuts.push({
startTime: lastClipStartTime
.format(durationFormat, { trim: false }),
duration: lastClipDuration.format(durationFormat, { trim: false })
});
return originalCuts;
}
/**
* Cuts a video using ffmpeg
* @param {string} args.startTime Start time of the cut in `hh:mm:ss` format.
* @param {string} args.duration Duration of the cut in `hh:mm:ss` format.
* @param {string} args.fileName Filename of the video to be cut
* @return {Promise} A promise for an object containing startTime, duration, and fileName of the cut clip.
*/
function cutVideo(args) {
let cutFileName = tmp.tmpNameSync({
prefix: 'video-cut-',
postfix: `.${fext(originalVideo.fileName)}`
});
return new Promise((resolve, reject) => {
let commandQuery =
`${spec.ffmpeg_path} -i ${args.fileName} -ss ${args.startTime} -t ${args.duration} ${cutFileName} -y`;
let child = shelljs.exec(commandQuery, { async: true, silent: spec.silent });
child.on('exit', (code, signal) => {
if (code === 0) {
resolve({
startTime: args.startTime,
duration: args.duration,
fileName: cutFileName,
});
} else {
reject({ err: { code: code, signal: signal } });
}
});
});
}
function doCut() {
let originalVideoCuts = getCutsForVideo(clips);
let cutPromises = originalVideoCuts.map((cut) => {
return cutVideo({
startTime: cut.startTime,
duration: cut.duration,
fileName: originalVideo.fileName,
});
});
return Promise.all(cutPromises);
}
that = Object.create({
original: setOriginal,
exclude: setClips,
cut: doCut,
});
return that;
}