Remove fluent-ffmpeg dependency

This commit is contained in:
advplyr 2022-07-06 17:38:19 -05:00
parent 8562b8d1b3
commit b61ecefce4
35 changed files with 4405 additions and 50 deletions

View file

@ -0,0 +1,178 @@
/*jshint node:true*/
'use strict';
var utils = require('../utils');
/*
*! Audio-related methods
*/
module.exports = function(proto) {
/**
* Disable audio in the output
*
* @method FfmpegCommand#noAudio
* @category Audio
* @aliases withNoAudio
* @return FfmpegCommand
*/
proto.withNoAudio =
proto.noAudio = function() {
this._currentOutput.audio.clear();
this._currentOutput.audioFilters.clear();
this._currentOutput.audio('-an');
return this;
};
/**
* Specify audio codec
*
* @method FfmpegCommand#audioCodec
* @category Audio
* @aliases withAudioCodec
*
* @param {String} codec audio codec name
* @return FfmpegCommand
*/
proto.withAudioCodec =
proto.audioCodec = function(codec) {
this._currentOutput.audio('-acodec', codec);
return this;
};
/**
* Specify audio bitrate
*
* @method FfmpegCommand#audioBitrate
* @category Audio
* @aliases withAudioBitrate
*
* @param {String|Number} bitrate audio bitrate in kbps (with an optional 'k' suffix)
* @return FfmpegCommand
*/
proto.withAudioBitrate =
proto.audioBitrate = function(bitrate) {
this._currentOutput.audio('-b:a', ('' + bitrate).replace(/k?$/, 'k'));
return this;
};
/**
* Specify audio channel count
*
* @method FfmpegCommand#audioChannels
* @category Audio
* @aliases withAudioChannels
*
* @param {Number} channels channel count
* @return FfmpegCommand
*/
proto.withAudioChannels =
proto.audioChannels = function(channels) {
this._currentOutput.audio('-ac', channels);
return this;
};
/**
* Specify audio frequency
*
* @method FfmpegCommand#audioFrequency
* @category Audio
* @aliases withAudioFrequency
*
* @param {Number} freq audio frequency in Hz
* @return FfmpegCommand
*/
proto.withAudioFrequency =
proto.audioFrequency = function(freq) {
this._currentOutput.audio('-ar', freq);
return this;
};
/**
* Specify audio quality
*
* @method FfmpegCommand#audioQuality
* @category Audio
* @aliases withAudioQuality
*
* @param {Number} quality audio quality factor
* @return FfmpegCommand
*/
proto.withAudioQuality =
proto.audioQuality = function(quality) {
this._currentOutput.audio('-aq', quality);
return this;
};
/**
* Specify custom audio filter(s)
*
* Can be called both with one or many filters, or a filter array.
*
* @example
* command.audioFilters('filter1');
*
* @example
* command.audioFilters('filter1', 'filter2=param1=value1:param2=value2');
*
* @example
* command.audioFilters(['filter1', 'filter2']);
*
* @example
* command.audioFilters([
* {
* filter: 'filter1'
* },
* {
* filter: 'filter2',
* options: 'param=value:param=value'
* }
* ]);
*
* @example
* command.audioFilters(
* {
* filter: 'filter1',
* options: ['value1', 'value2']
* },
* {
* filter: 'filter2',
* options: { param1: 'value1', param2: 'value2' }
* }
* );
*
* @method FfmpegCommand#audioFilters
* @aliases withAudioFilter,withAudioFilters,audioFilter
* @category Audio
*
* @param {...String|String[]|Object[]} filters audio filter strings, string array or
* filter specification array, each with the following properties:
* @param {String} filters.filter filter name
* @param {String|String[]|Object} [filters.options] filter option string, array, or object
* @return FfmpegCommand
*/
proto.withAudioFilter =
proto.withAudioFilters =
proto.audioFilter =
proto.audioFilters = function(filters) {
if (arguments.length > 1) {
filters = [].slice.call(arguments);
}
if (!Array.isArray(filters)) {
filters = [filters];
}
this._currentOutput.audioFilters(utils.makeFilterStrings(filters));
return this;
};
};

View file

@ -0,0 +1,212 @@
/*jshint node:true*/
'use strict';
var utils = require('../utils');
/*
*! Custom options methods
*/
module.exports = function(proto) {
/**
* Add custom input option(s)
*
* When passing a single string or an array, each string containing two
* words is split (eg. inputOptions('-option value') is supported) for
* compatibility reasons. This is not the case when passing more than
* one argument.
*
* @example
* command.inputOptions('option1');
*
* @example
* command.inputOptions('option1', 'option2');
*
* @example
* command.inputOptions(['option1', 'option2']);
*
* @method FfmpegCommand#inputOptions
* @category Custom options
* @aliases addInputOption,addInputOptions,withInputOption,withInputOptions,inputOption
*
* @param {...String} options option string(s) or string array
* @return FfmpegCommand
*/
proto.addInputOption =
proto.addInputOptions =
proto.withInputOption =
proto.withInputOptions =
proto.inputOption =
proto.inputOptions = function(options) {
if (!this._currentInput) {
throw new Error('No input specified');
}
var doSplit = true;
if (arguments.length > 1) {
options = [].slice.call(arguments);
doSplit = false;
}
if (!Array.isArray(options)) {
options = [options];
}
this._currentInput.options(options.reduce(function(options, option) {
var split = String(option).split(' ');
if (doSplit && split.length === 2) {
options.push(split[0], split[1]);
} else {
options.push(option);
}
return options;
}, []));
return this;
};
/**
* Add custom output option(s)
*
* @example
* command.outputOptions('option1');
*
* @example
* command.outputOptions('option1', 'option2');
*
* @example
* command.outputOptions(['option1', 'option2']);
*
* @method FfmpegCommand#outputOptions
* @category Custom options
* @aliases addOutputOption,addOutputOptions,addOption,addOptions,withOutputOption,withOutputOptions,withOption,withOptions,outputOption
*
* @param {...String} options option string(s) or string array
* @return FfmpegCommand
*/
proto.addOutputOption =
proto.addOutputOptions =
proto.addOption =
proto.addOptions =
proto.withOutputOption =
proto.withOutputOptions =
proto.withOption =
proto.withOptions =
proto.outputOption =
proto.outputOptions = function(options) {
var doSplit = true;
if (arguments.length > 1) {
options = [].slice.call(arguments);
doSplit = false;
}
if (!Array.isArray(options)) {
options = [options];
}
this._currentOutput.options(options.reduce(function(options, option) {
var split = String(option).split(' ');
if (doSplit && split.length === 2) {
options.push(split[0], split[1]);
} else {
options.push(option);
}
return options;
}, []));
return this;
};
/**
* Specify a complex filtergraph
*
* Calling this method will override any previously set filtergraph, but you can set
* as many filters as needed in one call.
*
* @example <caption>Overlay an image over a video (using a filtergraph string)</caption>
* ffmpeg()
* .input('video.avi')
* .input('image.png')
* .complexFilter('[0:v][1:v]overlay[out]', ['out']);
*
* @example <caption>Overlay an image over a video (using a filter array)</caption>
* ffmpeg()
* .input('video.avi')
* .input('image.png')
* .complexFilter([{
* filter: 'overlay',
* inputs: ['0:v', '1:v'],
* outputs: ['out']
* }], ['out']);
*
* @example <caption>Split video into RGB channels and output a 3x1 video with channels side to side</caption>
* ffmpeg()
* .input('video.avi')
* .complexFilter([
* // Duplicate video stream 3 times into streams a, b, and c
* { filter: 'split', options: '3', outputs: ['a', 'b', 'c'] },
*
* // Create stream 'red' by cancelling green and blue channels from stream 'a'
* { filter: 'lutrgb', options: { g: 0, b: 0 }, inputs: 'a', outputs: 'red' },
*
* // Create stream 'green' by cancelling red and blue channels from stream 'b'
* { filter: 'lutrgb', options: { r: 0, b: 0 }, inputs: 'b', outputs: 'green' },
*
* // Create stream 'blue' by cancelling red and green channels from stream 'c'
* { filter: 'lutrgb', options: { r: 0, g: 0 }, inputs: 'c', outputs: 'blue' },
*
* // Pad stream 'red' to 3x width, keeping the video on the left, and name output 'padded'
* { filter: 'pad', options: { w: 'iw*3', h: 'ih' }, inputs: 'red', outputs: 'padded' },
*
* // Overlay 'green' onto 'padded', moving it to the center, and name output 'redgreen'
* { filter: 'overlay', options: { x: 'w', y: 0 }, inputs: ['padded', 'green'], outputs: 'redgreen'},
*
* // Overlay 'blue' onto 'redgreen', moving it to the right
* { filter: 'overlay', options: { x: '2*w', y: 0 }, inputs: ['redgreen', 'blue']},
* ]);
*
* @method FfmpegCommand#complexFilter
* @category Custom options
* @aliases filterGraph
*
* @param {String|Array} spec filtergraph string or array of filter specification
* objects, each having the following properties:
* @param {String} spec.filter filter name
* @param {String|Array} [spec.inputs] (array of) input stream specifier(s) for the filter,
* defaults to ffmpeg automatically choosing the first unused matching streams
* @param {String|Array} [spec.outputs] (array of) output stream specifier(s) for the filter,
* defaults to ffmpeg automatically assigning the output to the output file
* @param {Object|String|Array} [spec.options] filter options, can be omitted to not set any options
* @param {Array} [map] (array of) stream specifier(s) from the graph to include in
* ffmpeg output, defaults to ffmpeg automatically choosing the first matching streams.
* @return FfmpegCommand
*/
proto.filterGraph =
proto.complexFilter = function(spec, map) {
this._complexFilters.clear();
if (!Array.isArray(spec)) {
spec = [spec];
}
this._complexFilters('-filter_complex', utils.makeFilterStrings(spec).join(';'));
if (Array.isArray(map)) {
var self = this;
map.forEach(function(streamSpec) {
self._complexFilters('-map', streamSpec.replace(utils.streamRegexp, '[$1]'));
});
} else if (typeof map === 'string') {
this._complexFilters('-map', map.replace(utils.streamRegexp, '[$1]'));
}
return this;
};
};

View file

@ -0,0 +1,178 @@
/*jshint node:true*/
'use strict';
var utils = require('../utils');
/*
*! Input-related methods
*/
module.exports = function(proto) {
/**
* Add an input to command
*
* Also switches "current input", that is the input that will be affected
* by subsequent input-related methods.
*
* Note: only one stream input is supported for now.
*
* @method FfmpegCommand#input
* @category Input
* @aliases mergeAdd,addInput
*
* @param {String|Readable} source input file path or readable stream
* @return FfmpegCommand
*/
proto.mergeAdd =
proto.addInput =
proto.input = function(source) {
var isFile = false;
var isStream = false;
if (typeof source !== 'string') {
if (!('readable' in source) || !(source.readable)) {
throw new Error('Invalid input');
}
var hasInputStream = this._inputs.some(function(input) {
return input.isStream;
});
if (hasInputStream) {
throw new Error('Only one input stream is supported');
}
isStream = true;
source.pause();
} else {
var protocol = source.match(/^([a-z]{2,}):/i);
isFile = !protocol || protocol[0] === 'file';
}
this._inputs.push(this._currentInput = {
source: source,
isFile: isFile,
isStream: isStream,
options: utils.args()
});
return this;
};
/**
* Specify input format for the last specified input
*
* @method FfmpegCommand#inputFormat
* @category Input
* @aliases withInputFormat,fromFormat
*
* @param {String} format input format
* @return FfmpegCommand
*/
proto.withInputFormat =
proto.inputFormat =
proto.fromFormat = function(format) {
if (!this._currentInput) {
throw new Error('No input specified');
}
this._currentInput.options('-f', format);
return this;
};
/**
* Specify input FPS for the last specified input
* (only valid for raw video formats)
*
* @method FfmpegCommand#inputFps
* @category Input
* @aliases withInputFps,withInputFPS,withFpsInput,withFPSInput,inputFPS,inputFps,fpsInput
*
* @param {Number} fps input FPS
* @return FfmpegCommand
*/
proto.withInputFps =
proto.withInputFPS =
proto.withFpsInput =
proto.withFPSInput =
proto.inputFPS =
proto.inputFps =
proto.fpsInput =
proto.FPSInput = function(fps) {
if (!this._currentInput) {
throw new Error('No input specified');
}
this._currentInput.options('-r', fps);
return this;
};
/**
* Use native framerate for the last specified input
*
* @method FfmpegCommand#native
* @category Input
* @aliases nativeFramerate,withNativeFramerate
*
* @return FfmmegCommand
*/
proto.nativeFramerate =
proto.withNativeFramerate =
proto.native = function() {
if (!this._currentInput) {
throw new Error('No input specified');
}
this._currentInput.options('-re');
return this;
};
/**
* Specify input seek time for the last specified input
*
* @method FfmpegCommand#seekInput
* @category Input
* @aliases setStartTime,seekTo
*
* @param {String|Number} seek seek time in seconds or as a '[hh:[mm:]]ss[.xxx]' string
* @return FfmpegCommand
*/
proto.setStartTime =
proto.seekInput = function(seek) {
if (!this._currentInput) {
throw new Error('No input specified');
}
this._currentInput.options('-ss', seek);
return this;
};
/**
* Loop over the last specified input
*
* @method FfmpegCommand#loop
* @category Input
*
* @param {String|Number} [duration] loop duration in seconds or as a '[[hh:]mm:]ss[.xxx]' string
* @return FfmpegCommand
*/
proto.loop = function(duration) {
if (!this._currentInput) {
throw new Error('No input specified');
}
this._currentInput.options('-loop', '1');
if (typeof duration !== 'undefined') {
this.duration(duration);
}
return this;
};
};

View file

@ -0,0 +1,41 @@
/*jshint node:true*/
'use strict';
var path = require('path');
/*
*! Miscellaneous methods
*/
module.exports = function(proto) {
/**
* Use preset
*
* @method FfmpegCommand#preset
* @category Miscellaneous
* @aliases usingPreset
*
* @param {String|Function} preset preset name or preset function
*/
proto.usingPreset =
proto.preset = function(preset) {
if (typeof preset === 'function') {
preset(this);
} else {
try {
var modulePath = path.join(this.options.presets, preset);
var module = require(modulePath);
if (typeof module.load === 'function') {
module.load(this);
} else {
throw new Error('preset ' + modulePath + ' has no load() function');
}
} catch (err) {
throw new Error('preset ' + modulePath + ' could not be loaded: ' + err.message);
}
}
return this;
};
};

View file

@ -0,0 +1,162 @@
/*jshint node:true*/
'use strict';
var utils = require('../utils');
/*
*! Output-related methods
*/
module.exports = function(proto) {
/**
* Add output
*
* @method FfmpegCommand#output
* @category Output
* @aliases addOutput
*
* @param {String|Writable} target target file path or writable stream
* @param {Object} [pipeopts={}] pipe options (only applies to streams)
* @return FfmpegCommand
*/
proto.addOutput =
proto.output = function(target, pipeopts) {
var isFile = false;
if (!target && this._currentOutput) {
// No target is only allowed when called from constructor
throw new Error('Invalid output');
}
if (target && typeof target !== 'string') {
if (!('writable' in target) || !(target.writable)) {
throw new Error('Invalid output');
}
} else if (typeof target === 'string') {
var protocol = target.match(/^([a-z]{2,}):/i);
isFile = !protocol || protocol[0] === 'file';
}
if (target && !('target' in this._currentOutput)) {
// For backwards compatibility, set target for first output
this._currentOutput.target = target;
this._currentOutput.isFile = isFile;
this._currentOutput.pipeopts = pipeopts || {};
} else {
if (target && typeof target !== 'string') {
var hasOutputStream = this._outputs.some(function(output) {
return typeof output.target !== 'string';
});
if (hasOutputStream) {
throw new Error('Only one output stream is supported');
}
}
this._outputs.push(this._currentOutput = {
target: target,
isFile: isFile,
flags: {},
pipeopts: pipeopts || {}
});
var self = this;
['audio', 'audioFilters', 'video', 'videoFilters', 'sizeFilters', 'options'].forEach(function(key) {
self._currentOutput[key] = utils.args();
});
if (!target) {
// Call from constructor: remove target key
delete this._currentOutput.target;
}
}
return this;
};
/**
* Specify output seek time
*
* @method FfmpegCommand#seek
* @category Input
* @aliases seekOutput
*
* @param {String|Number} seek seek time in seconds or as a '[hh:[mm:]]ss[.xxx]' string
* @return FfmpegCommand
*/
proto.seekOutput =
proto.seek = function(seek) {
this._currentOutput.options('-ss', seek);
return this;
};
/**
* Set output duration
*
* @method FfmpegCommand#duration
* @category Output
* @aliases withDuration,setDuration
*
* @param {String|Number} duration duration in seconds or as a '[[hh:]mm:]ss[.xxx]' string
* @return FfmpegCommand
*/
proto.withDuration =
proto.setDuration =
proto.duration = function(duration) {
this._currentOutput.options('-t', duration);
return this;
};
/**
* Set output format
*
* @method FfmpegCommand#format
* @category Output
* @aliases toFormat,withOutputFormat,outputFormat
*
* @param {String} format output format name
* @return FfmpegCommand
*/
proto.toFormat =
proto.withOutputFormat =
proto.outputFormat =
proto.format = function(format) {
this._currentOutput.options('-f', format);
return this;
};
/**
* Add stream mapping to output
*
* @method FfmpegCommand#map
* @category Output
*
* @param {String} spec stream specification string, with optional square brackets
* @return FfmpegCommand
*/
proto.map = function(spec) {
this._currentOutput.options('-map', spec.replace(utils.streamRegexp, '[$1]'));
return this;
};
/**
* Run flvtool2/flvmeta on output
*
* @method FfmpegCommand#flvmeta
* @category Output
* @aliases updateFlvMetadata
*
* @return FfmpegCommand
*/
proto.updateFlvMetadata =
proto.flvmeta = function() {
this._currentOutput.flags.flvmeta = true;
return this;
};
};

View file

@ -0,0 +1,184 @@
/*jshint node:true*/
'use strict';
var utils = require('../utils');
/*
*! Video-related methods
*/
module.exports = function(proto) {
/**
* Disable video in the output
*
* @method FfmpegCommand#noVideo
* @category Video
* @aliases withNoVideo
*
* @return FfmpegCommand
*/
proto.withNoVideo =
proto.noVideo = function() {
this._currentOutput.video.clear();
this._currentOutput.videoFilters.clear();
this._currentOutput.video('-vn');
return this;
};
/**
* Specify video codec
*
* @method FfmpegCommand#videoCodec
* @category Video
* @aliases withVideoCodec
*
* @param {String} codec video codec name
* @return FfmpegCommand
*/
proto.withVideoCodec =
proto.videoCodec = function(codec) {
this._currentOutput.video('-vcodec', codec);
return this;
};
/**
* Specify video bitrate
*
* @method FfmpegCommand#videoBitrate
* @category Video
* @aliases withVideoBitrate
*
* @param {String|Number} bitrate video bitrate in kbps (with an optional 'k' suffix)
* @param {Boolean} [constant=false] enforce constant bitrate
* @return FfmpegCommand
*/
proto.withVideoBitrate =
proto.videoBitrate = function(bitrate, constant) {
bitrate = ('' + bitrate).replace(/k?$/, 'k');
this._currentOutput.video('-b:v', bitrate);
if (constant) {
this._currentOutput.video(
'-maxrate', bitrate,
'-minrate', bitrate,
'-bufsize', '3M'
);
}
return this;
};
/**
* Specify custom video filter(s)
*
* Can be called both with one or many filters, or a filter array.
*
* @example
* command.videoFilters('filter1');
*
* @example
* command.videoFilters('filter1', 'filter2=param1=value1:param2=value2');
*
* @example
* command.videoFilters(['filter1', 'filter2']);
*
* @example
* command.videoFilters([
* {
* filter: 'filter1'
* },
* {
* filter: 'filter2',
* options: 'param=value:param=value'
* }
* ]);
*
* @example
* command.videoFilters(
* {
* filter: 'filter1',
* options: ['value1', 'value2']
* },
* {
* filter: 'filter2',
* options: { param1: 'value1', param2: 'value2' }
* }
* );
*
* @method FfmpegCommand#videoFilters
* @category Video
* @aliases withVideoFilter,withVideoFilters,videoFilter
*
* @param {...String|String[]|Object[]} filters video filter strings, string array or
* filter specification array, each with the following properties:
* @param {String} filters.filter filter name
* @param {String|String[]|Object} [filters.options] filter option string, array, or object
* @return FfmpegCommand
*/
proto.withVideoFilter =
proto.withVideoFilters =
proto.videoFilter =
proto.videoFilters = function(filters) {
if (arguments.length > 1) {
filters = [].slice.call(arguments);
}
if (!Array.isArray(filters)) {
filters = [filters];
}
this._currentOutput.videoFilters(utils.makeFilterStrings(filters));
return this;
};
/**
* Specify output FPS
*
* @method FfmpegCommand#fps
* @category Video
* @aliases withOutputFps,withOutputFPS,withFpsOutput,withFPSOutput,withFps,withFPS,outputFPS,outputFps,fpsOutput,FPSOutput,FPS
*
* @param {Number} fps output FPS
* @return FfmpegCommand
*/
proto.withOutputFps =
proto.withOutputFPS =
proto.withFpsOutput =
proto.withFPSOutput =
proto.withFps =
proto.withFPS =
proto.outputFPS =
proto.outputFps =
proto.fpsOutput =
proto.FPSOutput =
proto.fps =
proto.FPS = function(fps) {
this._currentOutput.video('-r', fps);
return this;
};
/**
* Only transcode a certain number of frames
*
* @method FfmpegCommand#frames
* @category Video
* @aliases takeFrames,withFrames
*
* @param {Number} frames frame count
* @return FfmpegCommand
*/
proto.takeFrames =
proto.withFrames =
proto.frames = function(frames) {
this._currentOutput.video('-vframes', frames);
return this;
};
};

View file

@ -0,0 +1,291 @@
/*jshint node:true*/
'use strict';
/*
*! Size helpers
*/
/**
* Return filters to pad video to width*height,
*
* @param {Number} width output width
* @param {Number} height output height
* @param {Number} aspect video aspect ratio (without padding)
* @param {Number} color padding color
* @return scale/pad filters
* @private
*/
function getScalePadFilters(width, height, aspect, color) {
/*
let a be the input aspect ratio, A be the requested aspect ratio
if a > A, padding is done on top and bottom
if a < A, padding is done on left and right
*/
return [
/*
In both cases, we first have to scale the input to match the requested size.
When using computed width/height, we truncate them to multiples of 2
*/
{
filter: 'scale',
options: {
w: 'if(gt(a,' + aspect + '),' + width + ',trunc(' + height + '*a/2)*2)',
h: 'if(lt(a,' + aspect + '),' + height + ',trunc(' + width + '/a/2)*2)'
}
},
/*
Then we pad the scaled input to match the target size
(here iw and ih refer to the padding input, i.e the scaled output)
*/
{
filter: 'pad',
options: {
w: width,
h: height,
x: 'if(gt(a,' + aspect + '),0,(' + width + '-iw)/2)',
y: 'if(lt(a,' + aspect + '),0,(' + height + '-ih)/2)',
color: color
}
}
];
}
/**
* Recompute size filters
*
* @param {Object} output
* @param {String} key newly-added parameter name ('size', 'aspect' or 'pad')
* @param {String} value newly-added parameter value
* @return filter string array
* @private
*/
function createSizeFilters(output, key, value) {
// Store parameters
var data = output.sizeData = output.sizeData || {};
data[key] = value;
if (!('size' in data)) {
// No size requested, keep original size
return [];
}
// Try to match the different size string formats
var fixedSize = data.size.match(/([0-9]+)x([0-9]+)/);
var fixedWidth = data.size.match(/([0-9]+)x\?/);
var fixedHeight = data.size.match(/\?x([0-9]+)/);
var percentRatio = data.size.match(/\b([0-9]{1,3})%/);
var width, height, aspect;
if (percentRatio) {
var ratio = Number(percentRatio[1]) / 100;
return [{
filter: 'scale',
options: {
w: 'trunc(iw*' + ratio + '/2)*2',
h: 'trunc(ih*' + ratio + '/2)*2'
}
}];
} else if (fixedSize) {
// Round target size to multiples of 2
width = Math.round(Number(fixedSize[1]) / 2) * 2;
height = Math.round(Number(fixedSize[2]) / 2) * 2;
aspect = width / height;
if (data.pad) {
return getScalePadFilters(width, height, aspect, data.pad);
} else {
// No autopad requested, rescale to target size
return [{ filter: 'scale', options: { w: width, h: height }}];
}
} else if (fixedWidth || fixedHeight) {
if ('aspect' in data) {
// Specified aspect ratio
width = fixedWidth ? fixedWidth[1] : Math.round(Number(fixedHeight[1]) * data.aspect);
height = fixedHeight ? fixedHeight[1] : Math.round(Number(fixedWidth[1]) / data.aspect);
// Round to multiples of 2
width = Math.round(width / 2) * 2;
height = Math.round(height / 2) * 2;
if (data.pad) {
return getScalePadFilters(width, height, data.aspect, data.pad);
} else {
// No autopad requested, rescale to target size
return [{ filter: 'scale', options: { w: width, h: height }}];
}
} else {
// Keep input aspect ratio
if (fixedWidth) {
return [{
filter: 'scale',
options: {
w: Math.round(Number(fixedWidth[1]) / 2) * 2,
h: 'trunc(ow/a/2)*2'
}
}];
} else {
return [{
filter: 'scale',
options: {
w: 'trunc(oh*a/2)*2',
h: Math.round(Number(fixedHeight[1]) / 2) * 2
}
}];
}
}
} else {
throw new Error('Invalid size specified: ' + data.size);
}
}
/*
*! Video size-related methods
*/
module.exports = function(proto) {
/**
* Keep display aspect ratio
*
* This method is useful when converting an input with non-square pixels to an output format
* that does not support non-square pixels. It rescales the input so that the display aspect
* ratio is the same.
*
* @method FfmpegCommand#keepDAR
* @category Video size
* @aliases keepPixelAspect,keepDisplayAspect,keepDisplayAspectRatio
*
* @return FfmpegCommand
*/
proto.keepPixelAspect = // Only for compatibility, this is not about keeping _pixel_ aspect ratio
proto.keepDisplayAspect =
proto.keepDisplayAspectRatio =
proto.keepDAR = function() {
return this.videoFilters([
{
filter: 'scale',
options: {
w: 'if(gt(sar,1),iw*sar,iw)',
h: 'if(lt(sar,1),ih/sar,ih)'
}
},
{
filter: 'setsar',
options: '1'
}
]);
};
/**
* Set output size
*
* The 'size' parameter can have one of 4 forms:
* - 'X%': rescale to xx % of the original size
* - 'WxH': specify width and height
* - 'Wx?': specify width and compute height from input aspect ratio
* - '?xH': specify height and compute width from input aspect ratio
*
* Note: both dimensions will be truncated to multiples of 2.
*
* @method FfmpegCommand#size
* @category Video size
* @aliases withSize,setSize
*
* @param {String} size size string, eg. '33%', '320x240', '320x?', '?x240'
* @return FfmpegCommand
*/
proto.withSize =
proto.setSize =
proto.size = function(size) {
var filters = createSizeFilters(this._currentOutput, 'size', size);
this._currentOutput.sizeFilters.clear();
this._currentOutput.sizeFilters(filters);
return this;
};
/**
* Set output aspect ratio
*
* @method FfmpegCommand#aspect
* @category Video size
* @aliases withAspect,withAspectRatio,setAspect,setAspectRatio,aspectRatio
*
* @param {String|Number} aspect aspect ratio (number or 'X:Y' string)
* @return FfmpegCommand
*/
proto.withAspect =
proto.withAspectRatio =
proto.setAspect =
proto.setAspectRatio =
proto.aspect =
proto.aspectRatio = function(aspect) {
var a = Number(aspect);
if (isNaN(a)) {
var match = aspect.match(/^(\d+):(\d+)$/);
if (match) {
a = Number(match[1]) / Number(match[2]);
} else {
throw new Error('Invalid aspect ratio: ' + aspect);
}
}
var filters = createSizeFilters(this._currentOutput, 'aspect', a);
this._currentOutput.sizeFilters.clear();
this._currentOutput.sizeFilters(filters);
return this;
};
/**
* Enable auto-padding the output
*
* @method FfmpegCommand#autopad
* @category Video size
* @aliases applyAutopadding,applyAutoPadding,applyAutopad,applyAutoPad,withAutopadding,withAutoPadding,withAutopad,withAutoPad,autoPad
*
* @param {Boolean} [pad=true] enable/disable auto-padding
* @param {String} [color='black'] pad color
*/
proto.applyAutopadding =
proto.applyAutoPadding =
proto.applyAutopad =
proto.applyAutoPad =
proto.withAutopadding =
proto.withAutoPadding =
proto.withAutopad =
proto.withAutoPad =
proto.autoPad =
proto.autopad = function(pad, color) {
// Allow autopad(color)
if (typeof pad === 'string') {
color = pad;
pad = true;
}
// Allow autopad() and autopad(undefined, color)
if (typeof pad === 'undefined') {
pad = true;
}
var filters = createSizeFilters(this._currentOutput, 'pad', pad ? color || 'black' : false);
this._currentOutput.sizeFilters.clear();
this._currentOutput.sizeFilters(filters);
return this;
};
};