| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="utf-8"> |
| | <title>JSDoc: Source: processor.js</title> |
| |
|
| | <script src="scripts/prettify/prettify.js"> </script> |
| | <script src="scripts/prettify/lang-css.js"> </script> |
| | |
| | |
| | |
| | <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> |
| | <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> |
| | </head> |
| |
|
| | <body> |
| |
|
| | <div id="main"> |
| |
|
| | <h1 class="page-title">Source: processor.js</h1> |
| |
|
| | |
| |
|
| |
|
| | |
| | <section> |
| | <article> |
| | <pre class="prettyprint source linenums"><code>/*jshint node:true*/ |
| | 'use strict'; |
| |
|
| | var spawn = require('child_process').spawn; |
| | var path = require('path'); |
| | var fs = require('fs'); |
| | var async = require('async'); |
| | var utils = require('./utils'); |
| |
|
| | var nlRegexp = /\r\n|\r|\n/g; |
| |
|
| | /* |
| | *! Processor methods |
| | */ |
| |
|
| |
|
| | /** |
| | * Run ffprobe asynchronously and store data in command |
| | * |
| | * @param {FfmpegCommand} command |
| | * @private |
| | */ |
| | function runFfprobe(command) { |
| | command.ffprobe(0, function(err, data) { |
| | command._ffprobeData = data; |
| | }); |
| | } |
| |
|
| |
|
| | module.exports = function(proto) { |
| | /** |
| | * Emitted just after ffmpeg has been spawned. |
| | * |
| | * @event FfmpegCommand#start |
| | * @param {String} command ffmpeg command line |
| | */ |
| |
|
| | /** |
| | * Emitted when ffmpeg reports progress information |
| | * |
| | * @event FfmpegCommand#progress |
| | * @param {Object} progress progress object |
| | * @param {Number} progress.frames number of frames transcoded |
| | * @param {Number} progress.currentFps current processing speed in frames per second |
| | * @param {Number} progress.currentKbps current output generation speed in kilobytes per second |
| | * @param {Number} progress.targetSize current output file size |
| | * @param {String} progress.timemark current video timemark |
| | * @param {Number} [progress.percent] processing progress (may not be available depending on input) |
| | */ |
| |
|
| | /** |
| | * Emitted when ffmpeg outputs to stderr |
| | * |
| | * @event FfmpegCommand#stderr |
| | * @param {String} line stderr output line |
| | */ |
| |
|
| | /** |
| | * Emitted when ffmpeg reports input codec data |
| | * |
| | * @event FfmpegCommand#codecData |
| | * @param {Object} codecData codec data object |
| | * @param {String} codecData.format input format name |
| | * @param {String} codecData.audio input audio codec name |
| | * @param {String} codecData.audio_details input audio codec parameters |
| | * @param {String} codecData.video input video codec name |
| | * @param {String} codecData.video_details input video codec parameters |
| | */ |
| |
|
| | /** |
| | * Emitted when an error happens when preparing or running a command |
| | * |
| | * @event FfmpegCommand#error |
| | * @param {Error} error error object |
| | * @param {String|null} stdout ffmpeg stdout, unless outputting to a stream |
| | * @param {String|null} stderr ffmpeg stderr |
| | */ |
| |
|
| | /** |
| | * Emitted when a command finishes processing |
| | * |
| | * @event FfmpegCommand#end |
| | * @param {Array|String|null} [filenames|stdout] generated filenames when taking screenshots, ffmpeg stdout when not outputting to a stream, null otherwise |
| | * @param {String|null} stderr ffmpeg stderr |
| | */ |
| |
|
| |
|
| | /** |
| | * Spawn an ffmpeg process |
| | * |
| | * The 'options' argument may contain the following keys: |
| | * - 'niceness': specify process niceness, ignored on Windows (default: 0) |
| | * - `cwd`: change working directory |
| | * - 'captureStdout': capture stdout and pass it to 'endCB' as its 2nd argument (default: false) |
| | * - 'stdoutLines': override command limit (default: use command limit) |
| | * |
| | * The 'processCB' callback, if present, is called as soon as the process is created and |
| | * receives a nodejs ChildProcess object. It may not be called at all if an error happens |
| | * before spawning the process. |
| | * |
| | * The 'endCB' callback is called either when an error occurs or when the ffmpeg process finishes. |
| | * |
| | * @method FfmpegCommand#_spawnFfmpeg |
| | * @param {Array} args ffmpeg command line argument list |
| | * @param {Object} [options] spawn options (see above) |
| | * @param {Function} [processCB] callback called with process object and stdout/stderr ring buffers when process has been created |
| | * @param {Function} endCB callback called with error (if applicable) and stdout/stderr ring buffers when process finished |
| | * @private |
| | */ |
| | proto._spawnFfmpeg = function(args, options, processCB, endCB) { |
| | // Enable omitting options |
| | if (typeof options === 'function') { |
| | endCB = processCB; |
| | processCB = options; |
| | options = {}; |
| | } |
| |
|
| | // Enable omitting processCB |
| | if (typeof endCB === 'undefined') { |
| | endCB = processCB; |
| | processCB = function() {}; |
| | } |
| |
|
| | var maxLines = 'stdoutLines' in options ? options.stdoutLines : this.options.stdoutLines; |
| |
|
| | // Find ffmpeg |
| | this._getFfmpegPath(function(err, command) { |
| | if (err) { |
| | return endCB(err); |
| | } else if (!command || command.length === 0) { |
| | return endCB(new Error('Cannot find ffmpeg')); |
| | } |
| |
|
| | // Apply niceness |
| | if (options.niceness && options.niceness !== 0 && !utils.isWindows) { |
| | args.unshift('-n', options.niceness, command); |
| | command = 'nice'; |
| | } |
| |
|
| | var stdoutRing = utils.linesRing(maxLines); |
| | var stdoutClosed = false; |
| |
|
| | var stderrRing = utils.linesRing(maxLines); |
| | var stderrClosed = false; |
| |
|
| | // Spawn process |
| | var ffmpegProc = spawn(command, args, options); |
| |
|
| | if (ffmpegProc.stderr) { |
| | ffmpegProc.stderr.setEncoding('utf8'); |
| | } |
| |
|
| | ffmpegProc.on('error', function(err) { |
| | endCB(err); |
| | }); |
| |
|
| | // Ensure we wait for captured streams to end before calling endCB |
| | var exitError = null; |
| | function handleExit(err) { |
| | if (err) { |
| | exitError = err; |
| | } |
| |
|
| | if (processExited && (stdoutClosed || !options.captureStdout) && stderrClosed) { |
| | endCB(exitError, stdoutRing, stderrRing); |
| | } |
| | } |
| |
|
| | // Handle process exit |
| | var processExited = false; |
| | ffmpegProc.on('exit', function(code, signal) { |
| | processExited = true; |
| |
|
| | if (signal) { |
| | handleExit(new Error('ffmpeg was killed with signal ' + signal)); |
| | } else if (code) { |
| | handleExit(new Error('ffmpeg exited with code ' + code)); |
| | } else { |
| | handleExit(); |
| | } |
| | }); |
| |
|
| | // Capture stdout if specified |
| | if (options.captureStdout) { |
| | ffmpegProc.stdout.on('data', function(data) { |
| | stdoutRing.append(data); |
| | }); |
| |
|
| | ffmpegProc.stdout.on('close', function() { |
| | stdoutRing.close(); |
| | stdoutClosed = true; |
| | handleExit(); |
| | }); |
| | } |
| |
|
| | // Capture stderr if specified |
| | ffmpegProc.stderr.on('data', function(data) { |
| | stderrRing.append(data); |
| | }); |
| |
|
| | ffmpegProc.stderr.on('close', function() { |
| | stderrRing.close(); |
| | stderrClosed = true; |
| | handleExit(); |
| | }); |
| |
|
| | // Call process callback |
| | processCB(ffmpegProc, stdoutRing, stderrRing); |
| | }); |
| | }; |
| |
|
| |
|
| | /** |
| | * Build the argument list for an ffmpeg command |
| | * |
| | * @method FfmpegCommand#_getArguments |
| | * @return argument list |
| | * @private |
| | */ |
| | proto._getArguments = function() { |
| | var complexFilters = this._complexFilters.get(); |
| |
|
| | var fileOutput = this._outputs.some(function(output) { |
| | return output.isFile; |
| | }); |
| |
|
| | return [].concat( |
| | // Inputs and input options |
| | this._inputs.reduce(function(args, input) { |
| | var source = (typeof input.source === 'string') ? input.source : 'pipe:0'; |
| |
|
| | // For each input, add input options, then '-i <source>' |
| | return args.concat( |
| | input.options.get(), |
| | ['-i', source] |
| | ); |
| | }, []), |
| |
|
| | // Global options |
| | this._global.get(), |
| |
|
| | // Overwrite if we have file outputs |
| | fileOutput ? ['-y'] : [], |
| |
|
| | // Complex filters |
| | complexFilters, |
| |
|
| | // Outputs, filters and output options |
| | this._outputs.reduce(function(args, output) { |
| | var sizeFilters = utils.makeFilterStrings(output.sizeFilters.get()); |
| | var audioFilters = output.audioFilters.get(); |
| | var videoFilters = output.videoFilters.get().concat(sizeFilters); |
| | var outputArg; |
| |
|
| | if (!output.target) { |
| | outputArg = []; |
| | } else if (typeof output.target === 'string') { |
| | outputArg = [output.target]; |
| | } else { |
| | outputArg = ['pipe:1']; |
| | } |
| |
|
| | return args.concat( |
| | output.audio.get(), |
| | audioFilters.length ? ['-filter:a', audioFilters.join(',')] : [], |
| | output.video.get(), |
| | videoFilters.length ? ['-filter:v', videoFilters.join(',')] : [], |
| | output.options.get(), |
| | outputArg |
| | ); |
| | }, []) |
| | ); |
| | }; |
| |
|
| |
|
| | /** |
| | * Prepare execution of an ffmpeg command |
| | * |
| | * Checks prerequisites for the execution of the command (codec/format availability, flvtool...), |
| | * then builds the argument list for ffmpeg and pass them to 'callback'. |
| | * |
| | * @method FfmpegCommand#_prepare |
| | * @param {Function} callback callback with signature (err, args) |
| | * @param {Boolean} [readMetadata=false] read metadata before processing |
| | * @private |
| | */ |
| | proto._prepare = function(callback, readMetadata) { |
| | var self = this; |
| |
|
| | async.waterfall([ |
| | // Check codecs and formats |
| | function(cb) { |
| | self._checkCapabilities(cb); |
| | }, |
| |
|
| | // Read metadata if required |
| | function(cb) { |
| | if (!readMetadata) { |
| | return cb(); |
| | } |
| |
|
| | self.ffprobe(0, function(err, data) { |
| | if (!err) { |
| | self._ffprobeData = data; |
| | } |
| |
|
| | cb(); |
| | }); |
| | }, |
| |
|
| | // Check for flvtool2/flvmeta if necessary |
| | function(cb) { |
| | var flvmeta = self._outputs.some(function(output) { |
| | // Remove flvmeta flag on non-file output |
| | if (output.flags.flvmeta && !output.isFile) { |
| | self.logger.warn('Updating flv metadata is only supported for files'); |
| | output.flags.flvmeta = false; |
| | } |
| |
|
| | return output.flags.flvmeta; |
| | }); |
| |
|
| | if (flvmeta) { |
| | self._getFlvtoolPath(function(err) { |
| | cb(err); |
| | }); |
| | } else { |
| | cb(); |
| | } |
| | }, |
| |
|
| | // Build argument list |
| | function(cb) { |
| | var args; |
| | try { |
| | args = self._getArguments(); |
| | } catch(e) { |
| | return cb(e); |
| | } |
| |
|
| | cb(null, args); |
| | }, |
| |
|
| | // Add "-strict experimental" option where needed |
| | function(args, cb) { |
| | self.availableEncoders(function(err, encoders) { |
| | for (var i = 0; i < args.length; i++) { |
| | if (args[i] === '-acodec' || args[i] === '-vcodec') { |
| | i++; |
| |
|
| | if ((args[i] in encoders) && encoders[args[i]].experimental) { |
| | args.splice(i + 1, 0, '-strict', 'experimental'); |
| | i += 2; |
| | } |
| | } |
| | } |
| |
|
| | cb(null, args); |
| | }); |
| | } |
| | ], callback); |
| |
|
| | if (!readMetadata) { |
| | // Read metadata as soon as 'progress' listeners are added |
| |
|
| | if (this.listeners('progress').length > 0) { |
| | // Read metadata in parallel |
| | runFfprobe(this); |
| | } else { |
| | // Read metadata as soon as the first 'progress' listener is added |
| | this.once('newListener', function(event) { |
| | if (event === 'progress') { |
| | runFfprobe(this); |
| | } |
| | }); |
| | } |
| | } |
| | }; |
| |
|
| |
|
| | /** |
| | * Run ffmpeg command |
| | * |
| | * @method FfmpegCommand#run |
| | * @category Processing |
| | * @aliases exec,execute |
| | */ |
| | proto.exec = |
| | proto.execute = |
| | proto.run = function() { |
| | var self = this; |
| |
|
| | // Check if at least one output is present |
| | var outputPresent = this._outputs.some(function(output) { |
| | return 'target' in output; |
| | }); |
| |
|
| | if (!outputPresent) { |
| | throw new Error('No output specified'); |
| | } |
| |
|
| | // Get output stream if any |
| | var outputStream = this._outputs.filter(function(output) { |
| | return typeof output.target !== 'string'; |
| | })[0]; |
| |
|
| | // Get input stream if any |
| | var inputStream = this._inputs.filter(function(input) { |
| | return typeof input.source !== 'string'; |
| | })[0]; |
| |
|
| | // Ensure we send 'end' or 'error' only once |
| | var ended = false; |
| | function emitEnd(err, stdout, stderr) { |
| | if (!ended) { |
| | ended = true; |
| |
|
| | if (err) { |
| | self.emit('error', err, stdout, stderr); |
| | } else { |
| | self.emit('end', stdout, stderr); |
| | } |
| | } |
| | } |
| |
|
| | self._prepare(function(err, args) { |
| | if (err) { |
| | return emitEnd(err); |
| | } |
| |
|
| | // Run ffmpeg |
| | self._spawnFfmpeg( |
| | args, |
| | { |
| | captureStdout: !outputStream, |
| | niceness: self.options.niceness, |
| | cwd: self.options.cwd |
| | }, |
| |
|
| | function processCB(ffmpegProc, stdoutRing, stderrRing) { |
| | self.ffmpegProc = ffmpegProc; |
| | self.emit('start', 'ffmpeg ' + args.join(' ')); |
| |
|
| | // Pipe input stream if any |
| | if (inputStream) { |
| | inputStream.source.on('error', function(err) { |
| | emitEnd(new Error('Input stream error: ' + err.message)); |
| | ffmpegProc.kill(); |
| | }); |
| |
|
| | inputStream.source.resume(); |
| | inputStream.source.pipe(ffmpegProc.stdin); |
| |
|
| | // Set stdin error handler on ffmpeg (prevents nodejs catching the error, but |
| | // ffmpeg will fail anyway, so no need to actually handle anything) |
| | ffmpegProc.stdin.on('error', function() {}); |
| | } |
| |
|
| | // Setup timeout if requested |
| | var processTimer; |
| | if (self.options.timeout) { |
| | processTimer = setTimeout(function() { |
| | var msg = 'process ran into a timeout (' + self.options.timeout + 's)'; |
| |
|
| | emitEnd(new Error(msg), stdoutRing.get(), stderrRing.get()); |
| | ffmpegProc.kill(); |
| | }, self.options.timeout * 1000); |
| | } |
| |
|
| |
|
| | if (outputStream) { |
| | // Pipe ffmpeg stdout to output stream |
| | ffmpegProc.stdout.pipe(outputStream.target, outputStream.pipeopts); |
| |
|
| | // Handle output stream events |
| | outputStream.target.on('close', function() { |
| | self.logger.debug('Output stream closed, scheduling kill for ffmpgeg process'); |
| |
|
| | // Don't kill process yet, to give a chance to ffmpeg to |
| | // terminate successfully first This is necessary because |
| | // under load, the process 'exit' event sometimes happens |
| | // after the output stream 'close' event. |
| | setTimeout(function() { |
| | emitEnd(new Error('Output stream closed')); |
| | ffmpegProc.kill(); |
| | }, 20); |
| | }); |
| |
|
| | outputStream.target.on('error', function(err) { |
| | self.logger.debug('Output stream error, killing ffmpgeg process'); |
| | emitEnd(new Error('Output stream error: ' + err.message), stdoutRing.get(), stderrRing.get()); |
| | ffmpegProc.kill(); |
| | }); |
| | } |
| |
|
| | // Setup stderr handling |
| | if (stderrRing) { |
| |
|
| | // 'stderr' event |
| | if (self.listeners('stderr').length) { |
| | stderrRing.callback(function(line) { |
| | self.emit('stderr', line); |
| | }); |
| | } |
| |
|
| | // 'codecData' event |
| | if (self.listeners('codecData').length) { |
| | var codecDataSent = false; |
| | var codecObject = {}; |
| |
|
| | stderrRing.callback(function(line) { |
| | if (!codecDataSent) |
| | codecDataSent = utils.extractCodecData(self, line, codecObject); |
| | }); |
| | } |
| |
|
| | // 'progress' event |
| | if (self.listeners('progress').length) { |
| | var duration = 0; |
| |
|
| | if (self._ffprobeData && self._ffprobeData.format && self._ffprobeData.format.duration) { |
| | duration = Number(self._ffprobeData.format.duration); |
| | } |
| |
|
| | stderrRing.callback(function(line) { |
| | utils.extractProgress(self, line, duration); |
| | }); |
| | } |
| | } |
| | }, |
| |
|
| | function endCB(err, stdoutRing, stderrRing) { |
| | delete self.ffmpegProc; |
| |
|
| | if (err) { |
| | if (err.message.match(/ffmpeg exited with code/)) { |
| | // Add ffmpeg error message |
| | err.message += ': ' + utils.extractError(stderrRing.get()); |
| | } |
| |
|
| | emitEnd(err, stdoutRing.get(), stderrRing.get()); |
| | } else { |
| | // Find out which outputs need flv metadata |
| | var flvmeta = self._outputs.filter(function(output) { |
| | return output.flags.flvmeta; |
| | }); |
| |
|
| | if (flvmeta.length) { |
| | self._getFlvtoolPath(function(err, flvtool) { |
| | if (err) { |
| | return emitEnd(err); |
| | } |
| |
|
| | async.each( |
| | flvmeta, |
| | function(output, cb) { |
| | spawn(flvtool, ['-U', output.target]) |
| | .on('error', function(err) { |
| | cb(new Error('Error running ' + flvtool + ' on ' + output.target + ': ' + err.message)); |
| | }) |
| | .on('exit', function(code, signal) { |
| | if (code !== 0 || signal) { |
| | cb( |
| | new Error(flvtool + ' ' + |
| | (signal ? 'received signal ' + signal |
| | : 'exited with code ' + code)) + |
| | ' when running on ' + output.target |
| | ); |
| | } else { |
| | cb(); |
| | } |
| | }); |
| | }, |
| | function(err) { |
| | if (err) { |
| | emitEnd(err); |
| | } else { |
| | emitEnd(null, stdoutRing.get(), stderrRing.get()); |
| | } |
| | } |
| | ); |
| | }); |
| | } else { |
| | emitEnd(null, stdoutRing.get(), stderrRing.get()); |
| | } |
| | } |
| | } |
| | ); |
| | }); |
| | }; |
| |
|
| |
|
| | /** |
| | * Renice current and/or future ffmpeg processes |
| | * |
| | * Ignored on Windows platforms. |
| | * |
| | * @method FfmpegCommand#renice |
| | * @category Processing |
| | * |
| | * @param {Number} [niceness=0] niceness value between -20 (highest priority) and 20 (lowest priority) |
| | * @return FfmpegCommand |
| | */ |
| | proto.renice = function(niceness) { |
| | if (!utils.isWindows) { |
| | niceness = niceness || 0; |
| |
|
| | if (niceness < -20 || niceness > 20) { |
| | this.logger.warn('Invalid niceness value: ' + niceness + ', must be between -20 and 20'); |
| | } |
| |
|
| | niceness = Math.min(20, Math.max(-20, niceness)); |
| | this.options.niceness = niceness; |
| |
|
| | if (this.ffmpegProc) { |
| | var logger = this.logger; |
| | var pid = this.ffmpegProc.pid; |
| | var renice = spawn('renice', [niceness, '-p', pid]); |
| |
|
| | renice.on('error', function(err) { |
| | logger.warn('could not renice process ' + pid + ': ' + err.message); |
| | }); |
| |
|
| | renice.on('exit', function(code, signal) { |
| | if (signal) { |
| | logger.warn('could not renice process ' + pid + ': renice was killed by signal ' + signal); |
| | } else if (code) { |
| | logger.warn('could not renice process ' + pid + ': renice exited with ' + code); |
| | } else { |
| | logger.info('successfully reniced process ' + pid + ' to ' + niceness + ' niceness'); |
| | } |
| | }); |
| | } |
| | } |
| |
|
| | return this; |
| | }; |
| |
|
| |
|
| | /** |
| | * Kill current ffmpeg process, if any |
| | * |
| | * @method FfmpegCommand#kill |
| | * @category Processing |
| | * |
| | * @param {String} [signal=SIGKILL] signal name |
| | * @return FfmpegCommand |
| | */ |
| | proto.kill = function(signal) { |
| | if (!this.ffmpegProc) { |
| | this.logger.warn('No running ffmpeg process, cannot send signal'); |
| | } else { |
| | this.ffmpegProc.kill(signal || 'SIGKILL'); |
| | } |
| |
|
| | return this; |
| | }; |
| | }; |
| | </code></pre> |
| | </article> |
| | </section> |
| |
|
| |
|
| |
|
| |
|
| | </div> |
| |
|
| | <nav> |
| | <h2><a href="index.html">Index</a></h2><ul><li><a href="index.html#installation">Installation</a></li><ul></ul><li><a href="index.html#usage">Usage</a></li><ul><li><a href="index.html#prerequisites">Prerequisites</a></li><li><a href="index.html#creating-an-ffmpeg-command">Creating an FFmpeg command</a></li><li><a href="index.html#specifying-inputs">Specifying inputs</a></li><li><a href="index.html#input-options">Input options</a></li><li><a href="index.html#audio-options">Audio options</a></li><li><a href="index.html#video-options">Video options</a></li><li><a href="index.html#video-frame-size-options">Video frame size options</a></li><li><a href="index.html#specifying-multiple-outputs">Specifying multiple outputs</a></li><li><a href="index.html#output-options">Output options</a></li><li><a href="index.html#miscellaneous-options">Miscellaneous options</a></li><li><a href="index.html#setting-event-handlers">Setting event handlers</a></li><li><a href="index.html#starting-ffmpeg-processing">Starting FFmpeg processing</a></li><li><a href="index.html#controlling-the-ffmpeg-process">Controlling the FFmpeg process</a></li><li><a href="index.html#reading-video-metadata">Reading video metadata</a></li><li><a href="index.html#querying-ffmpeg-capabilities">Querying ffmpeg capabilities</a></li><li><a href="index.html#cloning-an-ffmpegcommand">Cloning an FfmpegCommand</a></li></ul><li><a href="index.html#contributing">Contributing</a></li><ul><li><a href="index.html#code-contributions">Code contributions</a></li><li><a href="index.html#documentation-contributions">Documentation contributions</a></li><li><a href="index.html#updating-the-documentation">Updating the documentation</a></li><li><a href="index.html#running-tests">Running tests</a></li></ul><li><a href="index.html#main-contributors">Main contributors</a></li><ul></ul><li><a href="index.html#license">License</a></li><ul></ul></ul><h3>Classes</h3><ul><li><a href="FfmpegCommand.html">FfmpegCommand</a></li><ul><li> <a href="FfmpegCommand.html#audio-methods">Audio methods</a></li><li> <a href="FfmpegCommand.html#capabilities-methods">Capabilities methods</a></li><li> <a href="FfmpegCommand.html#custom-options-methods">Custom options methods</a></li><li> <a href="FfmpegCommand.html#input-methods">Input methods</a></li><li> <a href="FfmpegCommand.html#metadata-methods">Metadata methods</a></li><li> <a href="FfmpegCommand.html#miscellaneous-methods">Miscellaneous methods</a></li><li> <a href="FfmpegCommand.html#other-methods">Other methods</a></li><li> <a href="FfmpegCommand.html#output-methods">Output methods</a></li><li> <a href="FfmpegCommand.html#processing-methods">Processing methods</a></li><li> <a href="FfmpegCommand.html#video-methods">Video methods</a></li><li> <a href="FfmpegCommand.html#video-size-methods">Video size methods</a></li></ul></ul> |
| | </nav> |
| |
|
| | <br clear="both"> |
| |
|
| | <footer> |
| | Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.0</a> on Sun May 01 2016 12:10:37 GMT+0200 (CEST) |
| | </footer> |
| |
|
| | <script> prettyPrint(); </script> |
| | <script src="scripts/linenumber.js"> </script> |
| | </body> |
| | </html> |
| |
|