mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-14 15:59:38 +00:00
Remove fluent-ffmpeg dependency
This commit is contained in:
parent
8562b8d1b3
commit
b61ecefce4
35 changed files with 4405 additions and 50 deletions
178
server/libs/fluentFfmpeg/options/audio.js
Normal file
178
server/libs/fluentFfmpeg/options/audio.js
Normal 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;
|
||||
};
|
||||
};
|
||||
212
server/libs/fluentFfmpeg/options/custom.js
Normal file
212
server/libs/fluentFfmpeg/options/custom.js
Normal 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;
|
||||
};
|
||||
};
|
||||
178
server/libs/fluentFfmpeg/options/inputs.js
Normal file
178
server/libs/fluentFfmpeg/options/inputs.js
Normal 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;
|
||||
};
|
||||
};
|
||||
41
server/libs/fluentFfmpeg/options/misc.js
Normal file
41
server/libs/fluentFfmpeg/options/misc.js
Normal 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;
|
||||
};
|
||||
};
|
||||
162
server/libs/fluentFfmpeg/options/output.js
Normal file
162
server/libs/fluentFfmpeg/options/output.js
Normal 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;
|
||||
};
|
||||
};
|
||||
184
server/libs/fluentFfmpeg/options/video.js
Normal file
184
server/libs/fluentFfmpeg/options/video.js
Normal 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;
|
||||
};
|
||||
};
|
||||
291
server/libs/fluentFfmpeg/options/videosize.js
Normal file
291
server/libs/fluentFfmpeg/options/videosize.js
Normal 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;
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue