diff --git a/.gitignore b/.gitignore
index 1cc5826d..e89b704f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
*.project
+package-lock.json
node_modules
lib-cov
*.swp
.idea
*.iml
+.nyc_output/*
diff --git a/Makefile b/Makefile
index 26e1b143..a02ed1ce 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,6 @@
REPORTER = spec
MOCHA = node_modules/.bin/mocha
+NYC = node_modules/.bin/nyc
test:
@NODE_ENV=test $(MOCHA) --require should --reporter $(REPORTER)
@@ -7,14 +8,8 @@ test:
test-colors:
@NODE_ENV=test $(MOCHA) --require should --reporter $(REPORTER) --colors
-test-cov: test/coverage.html
-
-test/coverage.html: lib-cov
- @FLUENTFFMPEG_COV=1 NODE_ENV=test $(MOCHA) --require should --reporter html-cov > test/coverage.html
-
-lib-cov:
- @rm -fr ./$@
- @jscoverage lib $@
+test-cov:
+ @FLUENTFFMPEG_COV=1 NODE_ENV=test ${NYC} $(MOCHA) --require should
publish:
@npm version patch -m "version bump"
@@ -26,4 +21,4 @@ JSDOC_CONF = tools/jsdoc-conf.json
doc:
$(JSDOC) --configure $(JSDOC_CONF)
-.PHONY: test test-cov lib-cov test-colors publish doc
\ No newline at end of file
+.PHONY: test test-cov test-colors publish doc
diff --git a/index.js b/index.js
index fb4805dd..b1f61b3d 100644
--- a/index.js
+++ b/index.js
@@ -1 +1 @@
-module.exports = require(`./lib${process.env.FLUENTFFMPEG_COV ? '-cov' : ''}/fluent-ffmpeg`);
+module.exports = require(`./lib/fluent-ffmpeg`);
diff --git a/lib/capabilities.js b/lib/capabilities.js
index 3722ff16..0673304d 100644
--- a/lib/capabilities.js
+++ b/lib/capabilities.js
@@ -1,27 +1,30 @@
/*jshint node:true*/
-'use strict';
+"use strict";
-var fs = require('fs');
-var path = require('path');
-var async = require('async');
-var utils = require('./utils');
+var fs = require("fs");
+var path = require("path");
+var async = require("async");
+var utils = require("./utils");
/*
*! Capability helpers
*/
var avCodecRegexp = /^\s*([D ])([E ])([VAS])([S ])([D ])([T ]) ([^ ]+) +(.*)$/;
-var ffCodecRegexp = /^\s*([D\.])([E\.])([VAS])([I\.])([L\.])([S\.]) ([^ ]+) +(.*)$/;
+var ffCodecRegexp =
+ /^\s*([D\.])([E\.])([VAS])([I\.])([L\.])([S\.]) ([^ ]+) +(.*)$/;
var ffEncodersRegexp = /\(encoders:([^\)]+)\)/;
var ffDecodersRegexp = /\(decoders:([^\)]+)\)/;
-var encodersRegexp = /^\s*([VAS\.])([F\.])([S\.])([X\.])([B\.])([D\.]) ([^ ]+) +(.*)$/;
+var encodersRegexp =
+ /^\s*([VAS\.])([F\.])([S\.])([X\.])([B\.])([D\.]) ([^ ]+) +(.*)$/;
var formatRegexp = /^\s*([D ])([E ]) ([^ ]+) +(.*)$/;
var lineBreakRegexp = /\r\n|\r|\n/;
-var filterRegexp = /^(?: [T\.][S\.][C\.] )?([^ ]+) +(AA?|VV?|\|)->(AA?|VV?|\|) +(.*)$/;
+var filterRegexp =
+ /^(?: [T\.][S\.][C\.] )?([^ ]+) +(AA?|VV?|\|)->(AA?|VV?|\|) +(.*)$/;
var cache = {};
-module.exports = function(proto) {
+module.exports = function (proto) {
/**
* Manually define the ffmpeg binary full path.
*
@@ -30,7 +33,7 @@ module.exports = function(proto) {
* @param {String} ffmpegPath The full path to the ffmpeg binary.
* @return FfmpegCommand
*/
- proto.setFfmpegPath = function(ffmpegPath) {
+ proto.setFfmpegPath = function (ffmpegPath) {
cache.ffmpegPath = ffmpegPath;
return this;
};
@@ -43,7 +46,7 @@ module.exports = function(proto) {
* @param {String} ffprobePath The full path to the ffprobe binary.
* @return FfmpegCommand
*/
- proto.setFfprobePath = function(ffprobePath) {
+ proto.setFfprobePath = function (ffprobePath) {
cache.ffprobePath = ffprobePath;
return this;
};
@@ -56,7 +59,7 @@ module.exports = function(proto) {
* @param {String} flvtool The full path to the flvtool2 or flvmeta binary.
* @return FfmpegCommand
*/
- proto.setFlvtoolPath = function(flvtool) {
+ proto.setFlvtoolPath = function (flvtool) {
cache.flvtoolPath = flvtool;
return this;
};
@@ -69,7 +72,7 @@ module.exports = function(proto) {
* @method FfmpegCommand#_forgetPaths
* @private
*/
- proto._forgetPaths = function() {
+ proto._forgetPaths = function () {
delete cache.ffmpegPath;
delete cache.ffprobePath;
delete cache.flvtoolPath;
@@ -85,47 +88,49 @@ module.exports = function(proto) {
* @param {Function} callback callback with signature (err, path)
* @private
*/
- proto._getFfmpegPath = function(callback) {
- if ('ffmpegPath' in cache) {
+ proto._getFfmpegPath = function (callback) {
+ if ("ffmpegPath" in cache) {
return callback(null, cache.ffmpegPath);
}
- async.waterfall([
- // Try FFMPEG_PATH
- function(cb) {
- if (process.env.FFMPEG_PATH) {
- fs.exists(process.env.FFMPEG_PATH, function(exists) {
- if (exists) {
- cb(null, process.env.FFMPEG_PATH);
- } else {
- cb(null, '');
- }
+ async.waterfall(
+ [
+ // Try FFMPEG_PATH
+ function (cb) {
+ if (process.env.FFMPEG_PATH) {
+ fs.exists(process.env.FFMPEG_PATH, function (exists) {
+ if (exists) {
+ cb(null, process.env.FFMPEG_PATH);
+ } else {
+ cb(null, "");
+ }
+ });
+ } else {
+ cb(null, "");
+ }
+ },
+
+ // Search in the PATH
+ function (ffmpeg, cb) {
+ if (ffmpeg.length) {
+ return cb(null, ffmpeg);
+ }
+
+ utils.which("ffmpeg", function (err, ffmpeg) {
+ cb(err, ffmpeg);
});
+ },
+ ],
+ function (err, ffmpeg) {
+ if (err) {
+ callback(err);
} else {
- cb(null, '');
- }
- },
-
- // Search in the PATH
- function(ffmpeg, cb) {
- if (ffmpeg.length) {
- return cb(null, ffmpeg);
+ callback(null, (cache.ffmpegPath = ffmpeg || ""));
}
-
- utils.which('ffmpeg', function(err, ffmpeg) {
- cb(err, ffmpeg);
- });
}
- ], function(err, ffmpeg) {
- if (err) {
- callback(err);
- } else {
- callback(null, cache.ffmpegPath = (ffmpeg || ''));
- }
- });
+ );
};
-
/**
* Check for ffprobe availability
*
@@ -137,66 +142,68 @@ module.exports = function(proto) {
* @param {Function} callback callback with signature (err, path)
* @private
*/
- proto._getFfprobePath = function(callback) {
+ proto._getFfprobePath = function (callback) {
var self = this;
- if ('ffprobePath' in cache) {
+ if ("ffprobePath" in cache) {
return callback(null, cache.ffprobePath);
}
- async.waterfall([
- // Try FFPROBE_PATH
- function(cb) {
- if (process.env.FFPROBE_PATH) {
- fs.exists(process.env.FFPROBE_PATH, function(exists) {
- cb(null, exists ? process.env.FFPROBE_PATH : '');
- });
- } else {
- cb(null, '');
- }
- },
-
- // Search in the PATH
- function(ffprobe, cb) {
- if (ffprobe.length) {
- return cb(null, ffprobe);
- }
+ async.waterfall(
+ [
+ // Try FFPROBE_PATH
+ function (cb) {
+ if (process.env.FFPROBE_PATH) {
+ fs.exists(process.env.FFPROBE_PATH, function (exists) {
+ cb(null, exists ? process.env.FFPROBE_PATH : "");
+ });
+ } else {
+ cb(null, "");
+ }
+ },
- utils.which('ffprobe', function(err, ffprobe) {
- cb(err, ffprobe);
- });
- },
+ // Search in the PATH
+ function (ffprobe, cb) {
+ if (ffprobe.length) {
+ return cb(null, ffprobe);
+ }
- // Search in the same directory as ffmpeg
- function(ffprobe, cb) {
- if (ffprobe.length) {
- return cb(null, ffprobe);
- }
+ utils.which("ffprobe", function (err, ffprobe) {
+ cb(err, ffprobe);
+ });
+ },
- self._getFfmpegPath(function(err, ffmpeg) {
- if (err) {
- cb(err);
- } else if (ffmpeg.length) {
- var name = utils.isWindows ? 'ffprobe.exe' : 'ffprobe';
- var ffprobe = path.join(path.dirname(ffmpeg), name);
- fs.exists(ffprobe, function(exists) {
- cb(null, exists ? ffprobe : '');
- });
- } else {
- cb(null, '');
+ // Search in the same directory as ffmpeg
+ function (ffprobe, cb) {
+ if (ffprobe.length) {
+ return cb(null, ffprobe);
}
- });
- }
- ], function(err, ffprobe) {
- if (err) {
- callback(err);
- } else {
- callback(null, cache.ffprobePath = (ffprobe || ''));
+
+ self._getFfmpegPath(function (err, ffmpeg) {
+ if (err) {
+ cb(err);
+ } else if (ffmpeg.length) {
+ var name = utils.isWindows ? "ffprobe.exe" : "ffprobe";
+ var ffprobe = path.join(path.dirname(ffmpeg), name);
+ fs.exists(ffprobe, function (exists) {
+ cb(null, exists ? ffprobe : "");
+ });
+ } else {
+ cb(null, "");
+ }
+ });
+ },
+ ],
+ function (err, ffprobe) {
+ if (err) {
+ callback(err);
+ } else {
+ callback(null, (cache.ffprobePath = ffprobe || ""));
+ }
}
- });
+ );
};
-
/**
* Check for flvtool2/flvmeta availability
*
@@ -207,69 +214,71 @@ module.exports = function(proto) {
* @param {Function} callback callback with signature (err, path)
* @private
*/
- proto._getFlvtoolPath = function(callback) {
- if ('flvtoolPath' in cache) {
+ proto._getFlvtoolPath = function (callback) {
+ if ("flvtoolPath" in cache) {
return callback(null, cache.flvtoolPath);
}
- async.waterfall([
- // Try FLVMETA_PATH
- function(cb) {
- if (process.env.FLVMETA_PATH) {
- fs.exists(process.env.FLVMETA_PATH, function(exists) {
- cb(null, exists ? process.env.FLVMETA_PATH : '');
- });
- } else {
- cb(null, '');
- }
- },
+ async.waterfall(
+ [
+ // Try FLVMETA_PATH
+ function (cb) {
+ if (process.env.FLVMETA_PATH) {
+ fs.exists(process.env.FLVMETA_PATH, function (exists) {
+ cb(null, exists ? process.env.FLVMETA_PATH : "");
+ });
+ } else {
+ cb(null, "");
+ }
+ },
- // Try FLVTOOL2_PATH
- function(flvtool, cb) {
- if (flvtool.length) {
- return cb(null, flvtool);
- }
+ // Try FLVTOOL2_PATH
+ function (flvtool, cb) {
+ if (flvtool.length) {
+ return cb(null, flvtool);
+ }
- if (process.env.FLVTOOL2_PATH) {
- fs.exists(process.env.FLVTOOL2_PATH, function(exists) {
- cb(null, exists ? process.env.FLVTOOL2_PATH : '');
- });
- } else {
- cb(null, '');
- }
- },
+ if (process.env.FLVTOOL2_PATH) {
+ fs.exists(process.env.FLVTOOL2_PATH, function (exists) {
+ cb(null, exists ? process.env.FLVTOOL2_PATH : "");
+ });
+ } else {
+ cb(null, "");
+ }
+ },
- // Search for flvmeta in the PATH
- function(flvtool, cb) {
- if (flvtool.length) {
- return cb(null, flvtool);
- }
+ // Search for flvmeta in the PATH
+ function (flvtool, cb) {
+ if (flvtool.length) {
+ return cb(null, flvtool);
+ }
- utils.which('flvmeta', function(err, flvmeta) {
- cb(err, flvmeta);
- });
- },
+ utils.which("flvmeta", function (err, flvmeta) {
+ cb(err, flvmeta);
+ });
+ },
- // Search for flvtool2 in the PATH
- function(flvtool, cb) {
- if (flvtool.length) {
- return cb(null, flvtool);
- }
+ // Search for flvtool2 in the PATH
+ function (flvtool, cb) {
+ if (flvtool.length) {
+ return cb(null, flvtool);
+ }
- utils.which('flvtool2', function(err, flvtool2) {
- cb(err, flvtool2);
- });
- },
- ], function(err, flvtool) {
- if (err) {
- callback(err);
- } else {
- callback(null, cache.flvtoolPath = (flvtool || ''));
+ utils.which("flvtool2", function (err, flvtool2) {
+ cb(err, flvtool2);
+ });
+ },
+ ],
+ function (err, flvtool) {
+ if (err) {
+ callback(err);
+ } else {
+ callback(null, (cache.flvtoolPath = flvtool || ""));
+ }
}
- });
+ );
};
-
/**
* A callback passed to {@link FfmpegCommand#availableFilters}.
*
@@ -293,39 +302,41 @@ module.exports = function(proto) {
*
* @param {FfmpegCommand~filterCallback} callback callback function
*/
- proto.availableFilters =
- proto.getAvailableFilters = function(callback) {
- if ('filters' in cache) {
+ proto.availableFilters = proto.getAvailableFilters = function (callback) {
+ if ("filters" in cache) {
return callback(null, cache.filters);
}
- this._spawnFfmpeg(['-filters'], { captureStdout: true, stdoutLines: 0 }, function (err, stdoutRing) {
- if (err) {
- return callback(err);
- }
-
- var stdout = stdoutRing.get();
- var lines = stdout.split('\n');
- var data = {};
- var types = { A: 'audio', V: 'video', '|': 'none' };
-
- lines.forEach(function(line) {
- var match = line.match(filterRegexp);
- if (match) {
- data[match[1]] = {
- description: match[4],
- input: types[match[2].charAt(0)],
- multipleInputs: match[2].length > 1,
- output: types[match[3].charAt(0)],
- multipleOutputs: match[3].length > 1
- };
+ this._spawnFfmpeg(
+ ["-filters"],
+ { captureStdout: true, stdoutLines: 0 },
+ function (err, stdoutRing) {
+ if (err) {
+ return callback(err);
}
- });
- callback(null, cache.filters = data);
- });
- };
+ var stdout = stdoutRing.get();
+ var lines = stdout.split("\n");
+ var data = {};
+ var types = { A: "audio", V: "video", "|": "none" };
+
+ lines.forEach(function (line) {
+ var match = line.match(filterRegexp);
+ if (match) {
+ data[match[1]] = {
+ description: match[4],
+ input: types[match[2].charAt(0)],
+ multipleInputs: match[2].length > 1,
+ output: types[match[3].charAt(0)],
+ multipleOutputs: match[3].length > 1,
+ };
+ }
+ });
+ callback(null, (cache.filters = data));
+ }
+ );
+ };
/**
* A callback passed to {@link FfmpegCommand#availableCodecs}.
@@ -349,83 +360,85 @@ module.exports = function(proto) {
*
* @param {FfmpegCommand~codecCallback} callback callback function
*/
- proto.availableCodecs =
- proto.getAvailableCodecs = function(callback) {
- if ('codecs' in cache) {
+ proto.availableCodecs = proto.getAvailableCodecs = function (callback) {
+ if ("codecs" in cache) {
return callback(null, cache.codecs);
}
- this._spawnFfmpeg(['-codecs'], { captureStdout: true, stdoutLines: 0 }, function(err, stdoutRing) {
- if (err) {
- return callback(err);
- }
-
- var stdout = stdoutRing.get();
- var lines = stdout.split(lineBreakRegexp);
- var data = {};
-
- lines.forEach(function(line) {
- var match = line.match(avCodecRegexp);
- if (match && match[7] !== '=') {
- data[match[7]] = {
- type: { 'V': 'video', 'A': 'audio', 'S': 'subtitle' }[match[3]],
- description: match[8],
- canDecode: match[1] === 'D',
- canEncode: match[2] === 'E',
- drawHorizBand: match[4] === 'S',
- directRendering: match[5] === 'D',
- weirdFrameTruncation: match[6] === 'T'
- };
+ this._spawnFfmpeg(
+ ["-codecs"],
+ { captureStdout: true, stdoutLines: 0 },
+ function (err, stdoutRing) {
+ if (err) {
+ return callback(err);
}
- match = line.match(ffCodecRegexp);
- if (match && match[7] !== '=') {
- var codecData = data[match[7]] = {
- type: { 'V': 'video', 'A': 'audio', 'S': 'subtitle' }[match[3]],
- description: match[8],
- canDecode: match[1] === 'D',
- canEncode: match[2] === 'E',
- intraFrameOnly: match[4] === 'I',
- isLossy: match[5] === 'L',
- isLossless: match[6] === 'S'
- };
-
- var encoders = codecData.description.match(ffEncodersRegexp);
- encoders = encoders ? encoders[1].trim().split(' ') : [];
-
- var decoders = codecData.description.match(ffDecodersRegexp);
- decoders = decoders ? decoders[1].trim().split(' ') : [];
-
- if (encoders.length || decoders.length) {
- var coderData = {};
- utils.copy(codecData, coderData);
- delete coderData.canEncode;
- delete coderData.canDecode;
-
- encoders.forEach(function(name) {
- data[name] = {};
- utils.copy(coderData, data[name]);
- data[name].canEncode = true;
+ var stdout = stdoutRing.get();
+ var lines = stdout.split(lineBreakRegexp);
+ var data = {};
+
+ lines.forEach(function (line) {
+ var match = line.match(avCodecRegexp);
+ if (match && match[7] !== "=") {
+ data[match[7]] = {
+ type: { V: "video", A: "audio", S: "subtitle" }[match[3]],
+ description: match[8],
+ canDecode: match[1] === "D",
+ canEncode: match[2] === "E",
+ drawHorizBand: match[4] === "S",
+ directRendering: match[5] === "D",
+ weirdFrameTruncation: match[6] === "T",
+ };
+ }
+
+ match = line.match(ffCodecRegexp);
+ if (match && match[7] !== "=") {
+ var codecData = (data[match[7]] = {
+ type: { V: "video", A: "audio", S: "subtitle" }[match[3]],
+ description: match[8],
+ canDecode: match[1] === "D",
+ canEncode: match[2] === "E",
+ intraFrameOnly: match[4] === "I",
+ isLossy: match[5] === "L",
+ isLossless: match[6] === "S",
});
- decoders.forEach(function(name) {
- if (name in data) {
- data[name].canDecode = true;
- } else {
+ var encoders = codecData.description.match(ffEncodersRegexp);
+ encoders = encoders ? encoders[1].trim().split(" ") : [];
+
+ var decoders = codecData.description.match(ffDecodersRegexp);
+ decoders = decoders ? decoders[1].trim().split(" ") : [];
+
+ if (encoders.length || decoders.length) {
+ var coderData = {};
+ utils.copy(codecData, coderData);
+ delete coderData.canEncode;
+ delete coderData.canDecode;
+
+ encoders.forEach(function (name) {
data[name] = {};
utils.copy(coderData, data[name]);
- data[name].canDecode = true;
- }
- });
+ data[name].canEncode = true;
+ });
+
+ decoders.forEach(function (name) {
+ if (name in data) {
+ data[name].canDecode = true;
+ } else {
+ data[name] = {};
+ utils.copy(coderData, data[name]);
+ data[name].canDecode = true;
+ }
+ });
+ }
}
- }
- });
+ });
- callback(null, cache.codecs = data);
- });
+ callback(null, (cache.codecs = data));
+ }
+ );
};
-
/**
* A callback passed to {@link FfmpegCommand#availableEncoders}.
*
@@ -451,40 +464,42 @@ module.exports = function(proto) {
*
* @param {FfmpegCommand~encodersCallback} callback callback function
*/
- proto.availableEncoders =
- proto.getAvailableEncoders = function(callback) {
- if ('encoders' in cache) {
+ proto.availableEncoders = proto.getAvailableEncoders = function (callback) {
+ if ("encoders" in cache) {
return callback(null, cache.encoders);
}
- this._spawnFfmpeg(['-encoders'], { captureStdout: true, stdoutLines: 0 }, function(err, stdoutRing) {
- if (err) {
- return callback(err);
- }
-
- var stdout = stdoutRing.get();
- var lines = stdout.split(lineBreakRegexp);
- var data = {};
-
- lines.forEach(function(line) {
- var match = line.match(encodersRegexp);
- if (match && match[7] !== '=') {
- data[match[7]] = {
- type: { 'V': 'video', 'A': 'audio', 'S': 'subtitle' }[match[1]],
- description: match[8],
- frameMT: match[2] === 'F',
- sliceMT: match[3] === 'S',
- experimental: match[4] === 'X',
- drawHorizBand: match[5] === 'B',
- directRendering: match[6] === 'D'
- };
+ this._spawnFfmpeg(
+ ["-encoders"],
+ { captureStdout: true, stdoutLines: 0 },
+ function (err, stdoutRing) {
+ if (err) {
+ return callback(err);
}
- });
- callback(null, cache.encoders = data);
- });
- };
+ var stdout = stdoutRing.get();
+ var lines = stdout.split(lineBreakRegexp);
+ var data = {};
+
+ lines.forEach(function (line) {
+ var match = line.match(encodersRegexp);
+ if (match && match[7] !== "=") {
+ data[match[7]] = {
+ type: { V: "video", A: "audio", S: "subtitle" }[match[1]],
+ description: match[8],
+ frameMT: match[2] === "F",
+ sliceMT: match[3] === "S",
+ experimental: match[4] === "X",
+ drawHorizBand: match[5] === "B",
+ directRendering: match[6] === "D",
+ };
+ }
+ });
+ callback(null, (cache.encoders = data));
+ }
+ );
+ };
/**
* A callback passed to {@link FfmpegCommand#availableFormats}.
@@ -507,50 +522,52 @@ module.exports = function(proto) {
*
* @param {FfmpegCommand~formatCallback} callback callback function
*/
- proto.availableFormats =
- proto.getAvailableFormats = function(callback) {
- if ('formats' in cache) {
+ proto.availableFormats = proto.getAvailableFormats = function (callback) {
+ if ("formats" in cache) {
return callback(null, cache.formats);
}
// Run ffmpeg -formats
- this._spawnFfmpeg(['-formats'], { captureStdout: true, stdoutLines: 0 }, function (err, stdoutRing) {
- if (err) {
- return callback(err);
- }
+ this._spawnFfmpeg(
+ ["-formats"],
+ { captureStdout: true, stdoutLines: 0 },
+ function (err, stdoutRing) {
+ if (err) {
+ return callback(err);
+ }
- // Parse output
- var stdout = stdoutRing.get();
- var lines = stdout.split(lineBreakRegexp);
- var data = {};
-
- lines.forEach(function(line) {
- var match = line.match(formatRegexp);
- if (match) {
- match[3].split(',').forEach(function(format) {
- if (!(format in data)) {
- data[format] = {
- description: match[4],
- canDemux: false,
- canMux: false
- };
- }
+ // Parse output
+ var stdout = stdoutRing.get();
+ var lines = stdout.split(lineBreakRegexp);
+ var data = {};
+
+ lines.forEach(function (line) {
+ var match = line.match(formatRegexp);
+ if (match) {
+ match[3].split(",").forEach(function (format) {
+ if (!(format in data)) {
+ data[format] = {
+ description: match[4],
+ canDemux: false,
+ canMux: false,
+ };
+ }
- if (match[1] === 'D') {
- data[format].canDemux = true;
- }
- if (match[2] === 'E') {
- data[format].canMux = true;
- }
- });
- }
- });
+ if (match[1] === "D") {
+ data[format].canDemux = true;
+ }
+ if (match[2] === "E") {
+ data[format].canMux = true;
+ }
+ });
+ }
+ });
- callback(null, cache.formats = data);
- });
+ callback(null, (cache.formats = data));
+ }
+ );
};
-
/**
* Check capabilities before executing a command
*
@@ -560,24 +577,24 @@ module.exports = function(proto) {
* @param {Function} callback callback with signature (err)
* @private
*/
- proto._checkCapabilities = function(callback) {
+ proto._checkCapabilities = function (callback) {
var self = this;
- async.waterfall([
- // Get available formats
- function(cb) {
- self.availableFormats(cb);
- },
-
- // Check whether specified formats are available
- function(formats, cb) {
- var unavailable;
-
- // Output format(s)
- unavailable = self._outputs
- .reduce(function(fmts, output) {
- var format = output.options.find('-f', 1);
+ async.waterfall(
+ [
+ // Get available formats
+ function (cb) {
+ self.availableFormats(cb);
+ },
+
+ // Check whether specified formats are available
+ function (formats, cb) {
+ var unavailable;
+
+ // Output format(s)
+ unavailable = self._outputs.reduce(function (fmts, output) {
+ var format = output.options.find("-f", 1);
if (format) {
- if (!(format[0] in formats) || !(formats[format[0]].canMux)) {
+ if (!(format[0] in formats) || !formats[format[0]].canMux) {
fmts.push(format);
}
}
@@ -585,18 +602,25 @@ module.exports = function(proto) {
return fmts;
}, []);
- if (unavailable.length === 1) {
- return cb(new Error('Output format ' + unavailable[0] + ' is not available'));
- } else if (unavailable.length > 1) {
- return cb(new Error('Output formats ' + unavailable.join(', ') + ' are not available'));
- }
+ if (unavailable.length === 1) {
+ return cb(
+ new Error("Output format " + unavailable[0] + " is not available")
+ );
+ } else if (unavailable.length > 1) {
+ return cb(
+ new Error(
+ "Output formats " +
+ unavailable.join(", ") +
+ " are not available"
+ )
+ );
+ }
- // Input format(s)
- unavailable = self._inputs
- .reduce(function(fmts, input) {
- var format = input.options.find('-f', 1);
+ // Input format(s)
+ unavailable = self._inputs.reduce(function (fmts, input) {
+ var format = input.options.find("-f", 1);
if (format) {
- if (!(format[0] in formats) || !(formats[format[0]].canDemux)) {
+ if (!(format[0] in formats) || !formats[format[0]].canDemux) {
fmts.push(format[0]);
}
}
@@ -604,62 +628,88 @@ module.exports = function(proto) {
return fmts;
}, []);
- if (unavailable.length === 1) {
- return cb(new Error('Input format ' + unavailable[0] + ' is not available'));
- } else if (unavailable.length > 1) {
- return cb(new Error('Input formats ' + unavailable.join(', ') + ' are not available'));
- }
-
- cb();
- },
-
- // Get available codecs
- function(cb) {
- self.availableEncoders(cb);
- },
-
- // Check whether specified codecs are available and add strict experimental options if needed
- function(encoders, cb) {
- var unavailable;
+ if (unavailable.length === 1) {
+ return cb(
+ new Error("Input format " + unavailable[0] + " is not available")
+ );
+ } else if (unavailable.length > 1) {
+ return cb(
+ new Error(
+ "Input formats " + unavailable.join(", ") + " are not available"
+ )
+ );
+ }
- // Audio codec(s)
- unavailable = self._outputs.reduce(function(cdcs, output) {
- var acodec = output.audio.find('-acodec', 1);
- if (acodec && acodec[0] !== 'copy') {
- if (!(acodec[0] in encoders) || encoders[acodec[0]].type !== 'audio') {
- cdcs.push(acodec[0]);
+ cb();
+ },
+
+ // Get available codecs
+ function (cb) {
+ self.availableEncoders(cb);
+ },
+
+ // Check whether specified codecs are available and add strict experimental options if needed
+ function (encoders, cb) {
+ var unavailable;
+
+ // Audio codec(s)
+ unavailable = self._outputs.reduce(function (cdcs, output) {
+ var acodec = output.audio.find("-acodec", 1);
+ if (acodec && acodec[0] !== "copy") {
+ if (
+ !(acodec[0] in encoders) ||
+ encoders[acodec[0]].type !== "audio"
+ ) {
+ cdcs.push(acodec[0]);
+ }
}
- }
- return cdcs;
- }, []);
+ return cdcs;
+ }, []);
- if (unavailable.length === 1) {
- return cb(new Error('Audio codec ' + unavailable[0] + ' is not available'));
- } else if (unavailable.length > 1) {
- return cb(new Error('Audio codecs ' + unavailable.join(', ') + ' are not available'));
- }
+ if (unavailable.length === 1) {
+ return cb(
+ new Error("Audio codec " + unavailable[0] + " is not available")
+ );
+ } else if (unavailable.length > 1) {
+ return cb(
+ new Error(
+ "Audio codecs " + unavailable.join(", ") + " are not available"
+ )
+ );
+ }
- // Video codec(s)
- unavailable = self._outputs.reduce(function(cdcs, output) {
- var vcodec = output.video.find('-vcodec', 1);
- if (vcodec && vcodec[0] !== 'copy') {
- if (!(vcodec[0] in encoders) || encoders[vcodec[0]].type !== 'video') {
- cdcs.push(vcodec[0]);
+ // Video codec(s)
+ unavailable = self._outputs.reduce(function (cdcs, output) {
+ var vcodec = output.video.find("-vcodec", 1);
+ if (vcodec && vcodec[0] !== "copy") {
+ if (
+ !(vcodec[0] in encoders) ||
+ encoders[vcodec[0]].type !== "video"
+ ) {
+ cdcs.push(vcodec[0]);
+ }
}
- }
- return cdcs;
- }, []);
+ return cdcs;
+ }, []);
- if (unavailable.length === 1) {
- return cb(new Error('Video codec ' + unavailable[0] + ' is not available'));
- } else if (unavailable.length > 1) {
- return cb(new Error('Video codecs ' + unavailable.join(', ') + ' are not available'));
- }
+ if (unavailable.length === 1) {
+ return cb(
+ new Error("Video codec " + unavailable[0] + " is not available")
+ );
+ } else if (unavailable.length > 1) {
+ return cb(
+ new Error(
+ "Video codecs " + unavailable.join(", ") + " are not available"
+ )
+ );
+ }
- cb();
- }
- ], callback);
+ cb();
+ },
+ ],
+ callback
+ );
};
};
diff --git a/package.json b/package.json
index f889d9be..5de9b7e6 100644
--- a/package.json
+++ b/package.json
@@ -19,9 +19,10 @@
},
"repository": "git://github.com/fluent-ffmpeg/node-fluent-ffmpeg.git",
"devDependencies": {
+ "jsdoc": "latest",
"mocha": "latest",
- "should": "latest",
- "jsdoc": "latest"
+ "nyc": "^15.1.0",
+ "should": "latest"
},
"dependencies": {
"async": ">=0.2.9",
diff --git a/test/capabilities.test.js b/test/capabilities.test.js
index b74427a9..55c83274 100644
--- a/test/capabilities.test.js
+++ b/test/capabilities.test.js
@@ -1,125 +1,130 @@
/*jshint node:true*/
/*global describe,it,beforeEach,afterEach,after*/
-'use strict';
+"use strict";
-var Ffmpeg = require('../index'),
- path = require('path'),
- assert = require('assert'),
- testhelper = require('./helpers'),
- async = require('async');
+var Ffmpeg = require("../index"),
+ path = require("path"),
+ assert = require("assert"),
+ testhelper = require("./helpers"),
+ async = require("async");
// delimiter fallback for node 0.8
-var PATH_DELIMITER = path.delimiter || (require('os').platform().match(/win(32|64)/) ? ';' : ':');
-
-
-describe('Capabilities', function() {
- describe('ffmpeg capabilities', function() {
- it('should enable querying for available codecs', function(done) {
- new Ffmpeg({ source: '' }).getAvailableCodecs(function(err, codecs) {
+var PATH_DELIMITER =
+ path.delimiter ||
+ (require("os")
+ .platform()
+ .match(/win(32|64)/)
+ ? ";"
+ : ":");
+
+describe("Capabilities", function () {
+ describe("ffmpeg capabilities", function () {
+ it("should enable querying for available codecs", function (done) {
+ new Ffmpeg({ source: "" }).getAvailableCodecs(function (err, codecs) {
testhelper.logError(err);
assert.ok(!err);
- (typeof codecs).should.equal('object');
+ (typeof codecs).should.equal("object");
Object.keys(codecs).length.should.not.equal(0);
- ('pcm_s16le' in codecs).should.equal(true);
- ('type' in codecs.pcm_s16le).should.equal(true);
- (typeof codecs.pcm_s16le.type).should.equal('string');
- ('description' in codecs.pcm_s16le).should.equal(true);
- (typeof codecs.pcm_s16le.description).should.equal('string');
- ('canEncode' in codecs.pcm_s16le).should.equal(true);
- (typeof codecs.pcm_s16le.canEncode).should.equal('boolean');
- ('canDecode' in codecs.pcm_s16le).should.equal(true);
- (typeof codecs.pcm_s16le.canDecode).should.equal('boolean');
+ ("pcm_s16le" in codecs).should.equal(true);
+ ("type" in codecs.pcm_s16le).should.equal(true);
+ (typeof codecs.pcm_s16le.type).should.equal("string");
+ ("description" in codecs.pcm_s16le).should.equal(true);
+ (typeof codecs.pcm_s16le.description).should.equal("string");
+ ("canEncode" in codecs.pcm_s16le).should.equal(true);
+ (typeof codecs.pcm_s16le.canEncode).should.equal("boolean");
+ ("canDecode" in codecs.pcm_s16le).should.equal(true);
+ (typeof codecs.pcm_s16le.canDecode).should.equal("boolean");
done();
});
});
- it('should enable querying for available encoders', function(done) {
- new Ffmpeg({ source: '' }).getAvailableEncoders(function(err, encoders) {
+ it("should enable querying for available encoders", function (done) {
+ new Ffmpeg({ source: "" }).getAvailableEncoders(function (err, encoders) {
testhelper.logError(err);
assert.ok(!err);
- (typeof encoders).should.equal('object');
+ (typeof encoders).should.equal("object");
Object.keys(encoders).length.should.not.equal(0);
- ('pcm_s16le' in encoders).should.equal(true);
- ('type' in encoders.pcm_s16le).should.equal(true);
- (typeof encoders.pcm_s16le.type).should.equal('string');
- ('description' in encoders.pcm_s16le).should.equal(true);
- (typeof encoders.pcm_s16le.description).should.equal('string');
- ('experimental' in encoders.pcm_s16le).should.equal(true);
- (typeof encoders.pcm_s16le.experimental).should.equal('boolean');
+ ("pcm_s16le" in encoders).should.equal(true);
+ ("type" in encoders.pcm_s16le).should.equal(true);
+ (typeof encoders.pcm_s16le.type).should.equal("string");
+ ("description" in encoders.pcm_s16le).should.equal(true);
+ (typeof encoders.pcm_s16le.description).should.equal("string");
+ ("experimental" in encoders.pcm_s16le).should.equal(true);
+ (typeof encoders.pcm_s16le.experimental).should.equal("boolean");
done();
});
});
- it('should enable querying for available formats', function(done) {
- new Ffmpeg({ source: '' }).getAvailableFormats(function(err, formats) {
+ it("should enable querying for available formats", function (done) {
+ new Ffmpeg({ source: "" }).getAvailableFormats(function (err, formats) {
testhelper.logError(err);
assert.ok(!err);
- (typeof formats).should.equal('object');
+ (typeof formats).should.equal("object");
Object.keys(formats).length.should.not.equal(0);
- ('wav' in formats).should.equal(true);
- ('description' in formats.wav).should.equal(true);
- (typeof formats.wav.description).should.equal('string');
- ('canMux' in formats.wav).should.equal(true);
- (typeof formats.wav.canMux).should.equal('boolean');
- ('canDemux' in formats.wav).should.equal(true);
- (typeof formats.wav.canDemux).should.equal('boolean');
+ ("wav" in formats).should.equal(true);
+ ("description" in formats.wav).should.equal(true);
+ (typeof formats.wav.description).should.equal("string");
+ ("canMux" in formats.wav).should.equal(true);
+ (typeof formats.wav.canMux).should.equal("boolean");
+ ("canDemux" in formats.wav).should.equal(true);
+ (typeof formats.wav.canDemux).should.equal("boolean");
done();
});
});
- it('should enable querying for available filters', function(done) {
- new Ffmpeg({ source: '' }).getAvailableFilters(function(err, filters) {
+ it("should enable querying for available filters", function (done) {
+ new Ffmpeg({ source: "" }).getAvailableFilters(function (err, filters) {
testhelper.logError(err);
assert.ok(!err);
- (typeof filters).should.equal('object');
+ (typeof filters).should.equal("object");
Object.keys(filters).length.should.not.equal(0);
- ('anull' in filters).should.equal(true);
- ('description' in filters.anull).should.equal(true);
- (typeof filters.anull.description).should.equal('string');
- ('input' in filters.anull).should.equal(true);
- (typeof filters.anull.input).should.equal('string');
- ('output' in filters.anull).should.equal(true);
- (typeof filters.anull.output).should.equal('string');
- ('multipleInputs' in filters.anull).should.equal(true);
- (typeof filters.anull.multipleInputs).should.equal('boolean');
- ('multipleOutputs' in filters.anull).should.equal(true);
- (typeof filters.anull.multipleOutputs).should.equal('boolean');
+ ("anull" in filters).should.equal(true);
+ ("description" in filters.anull).should.equal(true);
+ (typeof filters.anull.description).should.equal("string");
+ ("input" in filters.anull).should.equal(true);
+ (typeof filters.anull.input).should.equal("string");
+ ("output" in filters.anull).should.equal(true);
+ (typeof filters.anull.output).should.equal("string");
+ ("multipleInputs" in filters.anull).should.equal(true);
+ (typeof filters.anull.multipleInputs).should.equal("boolean");
+ ("multipleOutputs" in filters.anull).should.equal(true);
+ (typeof filters.anull.multipleOutputs).should.equal("boolean");
done();
});
});
- it('should enable querying capabilities without instanciating a command', function(done) {
- Ffmpeg.getAvailableCodecs(function(err, codecs) {
+ it("should enable querying capabilities without instanciating a command", function (done) {
+ Ffmpeg.getAvailableCodecs(function (err, codecs) {
testhelper.logError(err);
assert.ok(!err);
- (typeof codecs).should.equal('object');
+ (typeof codecs).should.equal("object");
Object.keys(codecs).length.should.not.equal(0);
- Ffmpeg.getAvailableFilters(function(err, filters) {
+ Ffmpeg.getAvailableFilters(function (err, filters) {
testhelper.logError(err);
assert.ok(!err);
- (typeof filters).should.equal('object');
+ (typeof filters).should.equal("object");
Object.keys(filters).length.should.not.equal(0);
- Ffmpeg.getAvailableFormats(function(err, formats) {
+ Ffmpeg.getAvailableFormats(function (err, formats) {
testhelper.logError(err);
assert.ok(!err);
- (typeof formats).should.equal('object');
+ (typeof formats).should.equal("object");
Object.keys(formats).length.should.not.equal(0);
done();
@@ -128,129 +133,144 @@ describe('Capabilities', function() {
});
});
- it('should enable checking command arguments for available codecs, formats and encoders', function(done) {
- async.waterfall([
- // Check with everything available
- function(cb) {
- new Ffmpeg('/path/to/file.avi')
- .fromFormat('avi')
- .audioCodec('pcm_u16le')
- .videoCodec('png')
- .toFormat('mp4')
- ._checkCapabilities(cb);
- },
-
- // Invalid input format
- function(cb) {
- new Ffmpeg('/path/to/file.avi')
- .fromFormat('invalid-input-format')
- .audioCodec('pcm_u16le')
- .videoCodec('png')
- .toFormat('mp4')
- ._checkCapabilities(function(err) {
- assert.ok(!!err);
- err.message.should.match(/Input format invalid-input-format is not available/);
-
- cb();
- });
- },
-
- // Invalid output format
- function(cb) {
- new Ffmpeg('/path/to/file.avi')
- .fromFormat('avi')
- .audioCodec('pcm_u16le')
- .videoCodec('png')
- .toFormat('invalid-output-format')
- ._checkCapabilities(function(err) {
- assert.ok(!!err);
- err.message.should.match(/Output format invalid-output-format is not available/);
-
- cb();
- });
- },
-
- // Invalid audio codec
- function(cb) {
- new Ffmpeg('/path/to/file.avi')
- .fromFormat('avi')
- .audioCodec('invalid-audio-codec')
- .videoCodec('png')
- .toFormat('mp4')
- ._checkCapabilities(function(err) {
- assert.ok(!!err);
- err.message.should.match(/Audio codec invalid-audio-codec is not available/);
-
- cb();
- });
- },
-
- // Invalid video codec
- function(cb) {
- new Ffmpeg('/path/to/file.avi')
- .fromFormat('avi')
- .audioCodec('pcm_u16le')
- .videoCodec('invalid-video-codec')
- .toFormat('mp4')
- ._checkCapabilities(function(err) {
- assert.ok(!!err);
- err.message.should.match(/Video codec invalid-video-codec is not available/);
-
- cb();
- });
- },
-
- // Invalid audio encoder
- function(cb) {
- new Ffmpeg('/path/to/file.avi')
- .fromFormat('avi')
- // Valid codec, but not a valid encoder for audio
- .audioCodec('png')
- .videoCodec('png')
- .toFormat('mp4')
- ._checkCapabilities(function(err) {
- assert.ok(!!err);
- err.message.should.match(/Audio codec png is not available/);
-
- cb();
- });
- },
-
- // Invalid video encoder
- function(cb) {
- new Ffmpeg('/path/to/file.avi')
- .fromFormat('avi')
- .audioCodec('pcm_u16le')
- // Valid codec, but not a valid encoder for video
- .videoCodec('pcm_u16le')
- .toFormat('mp4')
- ._checkCapabilities(function(err) {
- assert.ok(!!err);
- err.message.should.match(/Video codec pcm_u16le is not available/);
-
- cb();
- });
- }
- ], function(err) {
- testhelper.logError(err);
- assert.ok(!err);
+ it("should enable checking command arguments for available codecs, formats and encoders", function (done) {
+ async.waterfall(
+ [
+ // Check with everything available
+ function (cb) {
+ new Ffmpeg("/path/to/file.avi")
+ .fromFormat("avi")
+ .audioCodec("pcm_u16le")
+ .videoCodec("png")
+ .toFormat("mp4")
+ ._checkCapabilities(cb);
+ },
+
+ // Invalid input format
+ function (cb) {
+ new Ffmpeg("/path/to/file.avi")
+ .fromFormat("invalid-input-format")
+ .audioCodec("pcm_u16le")
+ .videoCodec("png")
+ .toFormat("mp4")
+ ._checkCapabilities(function (err) {
+ assert.ok(!!err);
+ err.message.should.match(
+ /Input format invalid-input-format is not available/
+ );
+
+ cb();
+ });
+ },
+
+ // Invalid output format
+ function (cb) {
+ new Ffmpeg("/path/to/file.avi")
+ .fromFormat("avi")
+ .audioCodec("pcm_u16le")
+ .videoCodec("png")
+ .toFormat("invalid-output-format")
+ ._checkCapabilities(function (err) {
+ assert.ok(!!err);
+ err.message.should.match(
+ /Output format invalid-output-format is not available/
+ );
+
+ cb();
+ });
+ },
+
+ // Invalid audio codec
+ function (cb) {
+ new Ffmpeg("/path/to/file.avi")
+ .fromFormat("avi")
+ .audioCodec("invalid-audio-codec")
+ .videoCodec("png")
+ .toFormat("mp4")
+ ._checkCapabilities(function (err) {
+ assert.ok(!!err);
+ err.message.should.match(
+ /Audio codec invalid-audio-codec is not available/
+ );
+
+ cb();
+ });
+ },
+
+ // Invalid video codec
+ function (cb) {
+ new Ffmpeg("/path/to/file.avi")
+ .fromFormat("avi")
+ .audioCodec("pcm_u16le")
+ .videoCodec("invalid-video-codec")
+ .toFormat("mp4")
+ ._checkCapabilities(function (err) {
+ assert.ok(!!err);
+ err.message.should.match(
+ /Video codec invalid-video-codec is not available/
+ );
+
+ cb();
+ });
+ },
+
+ // Invalid audio encoder
+ function (cb) {
+ new Ffmpeg("/path/to/file.avi")
+ .fromFormat("avi")
+ // Valid codec, but not a valid encoder for audio
+ .audioCodec("png")
+ .videoCodec("png")
+ .toFormat("mp4")
+ ._checkCapabilities(function (err) {
+ assert.ok(!!err);
+ err.message.should.match(/Audio codec png is not available/);
+
+ cb();
+ });
+ },
+
+ // Invalid video encoder
+ function (cb) {
+ new Ffmpeg("/path/to/file.avi")
+ .fromFormat("avi")
+ .audioCodec("pcm_u16le")
+ // Valid codec, but not a valid encoder for video
+ .videoCodec("pcm_u16le")
+ .toFormat("mp4")
+ ._checkCapabilities(function (err) {
+ assert.ok(!!err);
+ err.message.should.match(
+ /Video codec pcm_u16le is not available/
+ );
+
+ cb();
+ });
+ },
+ ],
+ function (err) {
+ testhelper.logError(err);
+ assert.ok(!err);
- done();
- });
+ done();
+ }
+ );
});
- it('should check capabilities before running a command', function(done) {
- new Ffmpeg('/path/to/file.avi')
- .on('error', function(err) {
- err.message.should.match(/Output format invalid-output-format is not available/);
+ it("should check capabilities before running a command", function (done) {
+ new Ffmpeg("/path/to/file.avi")
+ .on("error", function (err) {
+ err.message.should.match(
+ /Output format invalid-output-format is not available/
+ );
done();
})
- .toFormat('invalid-output-format')
- .saveToFile('/tmp/will-not-be-created.mp4');
+ .toFormat("invalid-output-format")
+ .saveToFile("/tmp/will-not-be-created.mp4");
});
});
- describe('ffmpeg path', function() {
+ describe("ffmpeg path", function () {
var FFMPEG_PATH;
var ALT_FFMPEG_PATH;
var skipAltTest = false;
@@ -262,54 +282,54 @@ describe('Capabilities', function() {
skipAltTest = true;
}
- beforeEach(function() {
+ beforeEach(function () {
// Save environment before each test
FFMPEG_PATH = process.env.FFMPEG_PATH;
});
- afterEach(function() {
+ afterEach(function () {
// Restore environment after each test
process.env.FFMPEG_PATH = FFMPEG_PATH;
});
- after(function() {
+ after(function () {
// Forget paths after all tests
- (new Ffmpeg())._forgetPaths();
+ new Ffmpeg()._forgetPaths();
});
- it('should allow manual definition of ffmpeg binary path', function(done) {
+ it("should allow manual definition of ffmpeg binary path", function (done) {
var ff = new Ffmpeg();
- ff.setFfmpegPath('/doom/di/dom');
- ff._getFfmpegPath(function(err, ffmpeg) {
+ ff.setFfmpegPath("/doom/di/dom");
+ ff._getFfmpegPath(function (err, ffmpeg) {
testhelper.logError(err);
assert.ok(!err);
- ffmpeg.should.equal('/doom/di/dom');
+ ffmpeg.should.equal("/doom/di/dom");
done();
});
});
- it('should allow static manual definition of ffmpeg binary path', function(done) {
+ it("should allow static manual definition of ffmpeg binary path", function (done) {
var ff = new Ffmpeg();
- Ffmpeg.setFfmpegPath('/doom/di/dom2');
- ff._getFfmpegPath(function(err, ffmpeg) {
+ Ffmpeg.setFfmpegPath("/doom/di/dom2");
+ ff._getFfmpegPath(function (err, ffmpeg) {
testhelper.logError(err);
assert.ok(!err);
- ffmpeg.should.equal('/doom/di/dom2');
+ ffmpeg.should.equal("/doom/di/dom2");
done();
});
});
- it('should look for ffmpeg in the PATH if FFMPEG_PATH is not defined', function(done) {
+ it("should look for ffmpeg in the PATH if FFMPEG_PATH is not defined", function (done) {
var ff = new Ffmpeg();
delete process.env.FFMPEG_PATH;
ff._forgetPaths();
- ff._getFfmpegPath(function(err, ffmpeg) {
+ ff._getFfmpegPath(function (err, ffmpeg) {
testhelper.logError(err);
assert.ok(!err);
@@ -322,28 +342,31 @@ describe('Capabilities', function() {
});
});
- (skipAltTest ? it.skip : it)('should use FFMPEG_PATH if defined and valid', function(done) {
- var ff = new Ffmpeg();
+ (skipAltTest ? it.skip : it)(
+ "should use FFMPEG_PATH if defined and valid",
+ function (done) {
+ var ff = new Ffmpeg();
- process.env.FFMPEG_PATH = ALT_FFMPEG_PATH;
+ process.env.FFMPEG_PATH = ALT_FFMPEG_PATH;
- ff._forgetPaths();
- ff._getFfmpegPath(function(err, ffmpeg) {
- testhelper.logError(err);
- assert.ok(!err);
+ ff._forgetPaths();
+ ff._getFfmpegPath(function (err, ffmpeg) {
+ testhelper.logError(err);
+ assert.ok(!err);
- ffmpeg.should.equal(ALT_FFMPEG_PATH);
- done();
- });
- });
+ ffmpeg.should.equal(ALT_FFMPEG_PATH);
+ done();
+ });
+ }
+ );
- it('should fall back to searching in the PATH if FFMPEG_PATH is invalid', function(done) {
+ it("should fall back to searching in the PATH if FFMPEG_PATH is invalid", function (done) {
var ff = new Ffmpeg();
- process.env.FFMPEG_PATH = '/nope/not-here/nothing-to-see-here';
+ process.env.FFMPEG_PATH = "/nope/not-here/nothing-to-see-here";
ff._forgetPaths();
- ff._getFfmpegPath(function(err, ffmpeg) {
+ ff._getFfmpegPath(function (err, ffmpeg) {
testhelper.logError(err);
assert.ok(!err);
@@ -356,13 +379,13 @@ describe('Capabilities', function() {
});
});
- it('should remember ffmpeg path', function(done) {
+ it("should remember ffmpeg path", function (done) {
var ff = new Ffmpeg();
delete process.env.FFMPEG_PATH;
ff._forgetPaths();
- ff._getFfmpegPath(function(err, ffmpeg) {
+ ff._getFfmpegPath(function (err, ffmpeg) {
testhelper.logError(err);
assert.ok(!err);
@@ -372,7 +395,7 @@ describe('Capabilities', function() {
// Just check that the callback is actually called synchronously
// (which indicates no which call was made)
var after = 0;
- ff._getFfmpegPath(function(err, ffmpeg) {
+ ff._getFfmpegPath(function (err, ffmpeg) {
testhelper.logError(err);
assert.ok(!err);
@@ -388,7 +411,7 @@ describe('Capabilities', function() {
});
});
- describe('ffprobe path', function() {
+ describe("ffprobe path", function () {
var FFPROBE_PATH;
var ALT_FFPROBE_PATH;
var skipAltTest = false;
@@ -400,54 +423,54 @@ describe('Capabilities', function() {
skipAltTest = true;
}
- beforeEach(function() {
+ beforeEach(function () {
// Save environment before each test
FFPROBE_PATH = process.env.FFPROBE_PATH;
});
- afterEach(function() {
+ afterEach(function () {
// Restore environment after each test
process.env.FFPROBE_PATH = FFPROBE_PATH;
});
- after(function() {
+ after(function () {
// Forget paths after all tests
- (new Ffmpeg())._forgetPaths();
+ new Ffmpeg()._forgetPaths();
});
- it('should allow manual definition of ffprobe binary path', function(done) {
+ it("should allow manual definition of ffprobe binary path", function (done) {
var ff = new Ffmpeg();
- ff.setFfprobePath('/doom/di/dom');
- ff._getFfprobePath(function(err, ffprobe) {
+ ff.setFfprobePath("/doom/di/dom");
+ ff._getFfprobePath(function (err, ffprobe) {
testhelper.logError(err);
assert.ok(!err);
- ffprobe.should.equal('/doom/di/dom');
+ ffprobe.should.equal("/doom/di/dom");
done();
});
});
- it('should allow static manual definition of ffprobe binary path', function(done) {
+ it("should allow static manual definition of ffprobe binary path", function (done) {
var ff = new Ffmpeg();
- Ffmpeg.setFfprobePath('/doom/di/dom2');
- ff._getFfprobePath(function(err, ffprobe) {
+ Ffmpeg.setFfprobePath("/doom/di/dom2");
+ ff._getFfprobePath(function (err, ffprobe) {
testhelper.logError(err);
assert.ok(!err);
- ffprobe.should.equal('/doom/di/dom2');
+ ffprobe.should.equal("/doom/di/dom2");
done();
});
});
- it('should look for ffprobe in the PATH if FFPROBE_PATH is not defined', function(done) {
+ it("should look for ffprobe in the PATH if FFPROBE_PATH is not defined", function (done) {
var ff = new Ffmpeg();
delete process.env.FFPROBE_PATH;
ff._forgetPaths();
- ff._getFfprobePath(function(err, ffprobe) {
+ ff._getFfprobePath(function (err, ffprobe) {
testhelper.logError(err);
assert.ok(!err);
@@ -460,28 +483,31 @@ describe('Capabilities', function() {
});
});
- (skipAltTest ? it.skip : it)('should use FFPROBE_PATH if defined and valid', function(done) {
- var ff = new Ffmpeg();
+ (skipAltTest ? it.skip : it)(
+ "should use FFPROBE_PATH if defined and valid",
+ function (done) {
+ var ff = new Ffmpeg();
- process.env.FFPROBE_PATH = ALT_FFPROBE_PATH;
+ process.env.FFPROBE_PATH = ALT_FFPROBE_PATH;
- ff._forgetPaths();
- ff._getFfprobePath(function(err, ffprobe) {
- testhelper.logError(err);
- assert.ok(!err);
+ ff._forgetPaths();
+ ff._getFfprobePath(function (err, ffprobe) {
+ testhelper.logError(err);
+ assert.ok(!err);
- ffprobe.should.equal(ALT_FFPROBE_PATH);
- done();
- });
- });
+ ffprobe.should.equal(ALT_FFPROBE_PATH);
+ done();
+ });
+ }
+ );
- it('should fall back to searching in the PATH if FFPROBE_PATH is invalid', function(done) {
+ it("should fall back to searching in the PATH if FFPROBE_PATH is invalid", function (done) {
var ff = new Ffmpeg();
- process.env.FFPROBE_PATH = '/nope/not-here/nothing-to-see-here';
+ process.env.FFPROBE_PATH = "/nope/not-here/nothing-to-see-here";
ff._forgetPaths();
- ff._getFfprobePath(function(err, ffprobe) {
+ ff._getFfprobePath(function (err, ffprobe) {
testhelper.logError(err);
assert.ok(!err);
@@ -494,13 +520,13 @@ describe('Capabilities', function() {
});
});
- it('should remember ffprobe path', function(done) {
+ it("should remember ffprobe path", function (done) {
var ff = new Ffmpeg();
delete process.env.FFPROBE_PATH;
ff._forgetPaths();
- ff._getFfprobePath(function(err, ffprobe) {
+ ff._getFfprobePath(function (err, ffprobe) {
testhelper.logError(err);
assert.ok(!err);
@@ -510,7 +536,7 @@ describe('Capabilities', function() {
// Just check that the callback is actually called synchronously
// (which indicates no which call was made)
var after = 0;
- ff._getFfprobePath(function(err, ffprobe) {
+ ff._getFfprobePath(function (err, ffprobe) {
testhelper.logError(err);
assert.ok(!err);
@@ -526,14 +552,14 @@ describe('Capabilities', function() {
});
});
- describe('flvtool path', function() {
+ describe("flvtool path", function () {
var FLVTOOL2_PATH;
var ALT_FLVTOOL_PATH;
var skipAltTest = false;
var skipTest = false;
// Skip test if we know travis failed to instal flvtool2
- if (process.env.FLVTOOL2_PRESENT === 'no') {
+ if (process.env.FLVTOOL2_PRESENT === "no") {
skipTest = true;
}
@@ -544,107 +570,122 @@ describe('Capabilities', function() {
skipAltTest = true;
}
- beforeEach(function() {
+ beforeEach(function () {
// Save environment before each test
FLVTOOL2_PATH = process.env.FLVTOOL2_PATH;
});
- afterEach(function() {
+ afterEach(function () {
// Restore environment after each test
process.env.FLVTOOL2_PATH = FLVTOOL2_PATH;
});
- after(function() {
+ after(function () {
// Forget paths after all tests
- (new Ffmpeg())._forgetPaths();
+ new Ffmpeg()._forgetPaths();
});
- (skipTest ? it.skip : it)('should allow manual definition of fflvtool binary path', function(done) {
- var ff = new Ffmpeg();
+ (skipTest ? it.skip : it)(
+ "should allow manual definition of fflvtool binary path",
+ function (done) {
+ var ff = new Ffmpeg();
- ff.setFlvtoolPath('/doom/di/dom');
- ff._getFlvtoolPath(function(err, fflvtool) {
- testhelper.logError(err);
- assert.ok(!err);
+ ff.setFlvtoolPath("/doom/di/dom");
+ ff._getFlvtoolPath(function (err, fflvtool) {
+ testhelper.logError(err);
+ assert.ok(!err);
- fflvtool.should.equal('/doom/di/dom');
- done();
- });
- });
+ fflvtool.should.equal("/doom/di/dom");
+ done();
+ });
+ }
+ );
- (skipTest ? it.skip : it)('should allow static manual definition of fflvtool binary path', function(done) {
- var ff = new Ffmpeg();
+ (skipTest ? it.skip : it)(
+ "should allow static manual definition of fflvtool binary path",
+ function (done) {
+ var ff = new Ffmpeg();
- Ffmpeg.setFlvtoolPath('/doom/di/dom2');
- ff._getFlvtoolPath(function(err, fflvtool) {
- testhelper.logError(err);
- assert.ok(!err);
+ Ffmpeg.setFlvtoolPath("/doom/di/dom2");
+ ff._getFlvtoolPath(function (err, fflvtool) {
+ testhelper.logError(err);
+ assert.ok(!err);
- fflvtool.should.equal('/doom/di/dom2');
- done();
- });
- });
+ fflvtool.should.equal("/doom/di/dom2");
+ done();
+ });
+ }
+ );
- (skipTest ? it.skip : it)('should look for fflvtool in the PATH if FLVTOOL2_PATH is not defined', function(done) {
- var ff = new Ffmpeg();
+ (skipTest ? it.skip : it)(
+ "should look for fflvtool in the PATH if FLVTOOL2_PATH is not defined",
+ function (done) {
+ var ff = new Ffmpeg();
- delete process.env.FLVTOOL2_PATH;
+ delete process.env.FLVTOOL2_PATH;
- ff._forgetPaths();
- ff._getFlvtoolPath(function(err, fflvtool) {
- testhelper.logError(err);
- assert.ok(!err);
+ ff._forgetPaths();
+ ff._getFlvtoolPath(function (err, fflvtool) {
+ testhelper.logError(err);
+ assert.ok(!err);
- fflvtool.should.instanceOf(String);
- fflvtool.length.should.above(0);
+ fflvtool.should.instanceOf(String);
+ fflvtool.length.should.above(0);
- var paths = process.env.PATH.split(PATH_DELIMITER);
- paths.indexOf(path.dirname(fflvtool)).should.above(-1);
- done();
- });
- });
+ var paths = process.env.PATH.split(PATH_DELIMITER);
+ paths.indexOf(path.dirname(fflvtool)).should.above(-1);
+ done();
+ });
+ }
+ );
- (skipTest || skipAltTest ? it.skip : it)('should use FLVTOOL2_PATH if defined and valid', function(done) {
- var ff = new Ffmpeg();
+ (skipTest || skipAltTest ? it.skip : it)(
+ "should use FLVTOOL2_PATH if defined and valid",
+ function (done) {
+ var ff = new Ffmpeg();
- process.env.FLVTOOL2_PATH = ALT_FLVTOOL_PATH;
+ process.env.FLVTOOL2_PATH = ALT_FLVTOOL_PATH;
- ff._forgetPaths();
- ff._getFlvtoolPath(function(err, fflvtool) {
- testhelper.logError(err);
- assert.ok(!err);
+ ff._forgetPaths();
+ ff._getFlvtoolPath(function (err, fflvtool) {
+ testhelper.logError(err);
+ assert.ok(!err);
- fflvtool.should.equal(ALT_FLVTOOL_PATH);
- done();
- });
- });
+ fflvtool.should.equal(ALT_FLVTOOL_PATH);
+ done();
+ });
+ }
+ );
- (skipTest ? it.skip : it)('should fall back to searching in the PATH if FLVTOOL2_PATH is invalid', function(done) {
- var ff = new Ffmpeg();
+ (skipTest ? it.skip : it)(
+ "should fall back to searching in the PATH if FLVTOOL2_PATH is invalid",
+ function (done) {
+ var ff = new Ffmpeg();
- process.env.FLVTOOL2_PATH = '/nope/not-here/nothing-to-see-here';
+ process.env.FLVTOOL2_PATH = "/nope/not-here/nothing-to-see-here";
- ff._forgetPaths();
- ff._getFlvtoolPath(function(err, fflvtool) {
- testhelper.logError(err);
- assert.ok(!err);
+ ff._forgetPaths();
+ ff._getFlvtoolPath(function (err, fflvtool) {
+ testhelper.logError(err);
+ assert.ok(!err);
- fflvtool.should.instanceOf(String);
- fflvtool.length.should.above(0);
+ fflvtool.should.instanceOf(String);
+ fflvtool.length.should.above(0);
- var paths = process.env.PATH.split(PATH_DELIMITER);
- paths.indexOf(path.dirname(fflvtool)).should.above(-1);
- done();
- });
- });
+ var paths = process.env.PATH.split(PATH_DELIMITER);
+ paths.indexOf(path.dirname(fflvtool)).should.above(-1);
+ done();
+ });
+ }
+ );
- (skipTest ? it.skip : it)('should remember fflvtool path', function(done) {
+ (skipTest ? it.skip : it)("should remember fflvtool path", function (done) {
var ff = new Ffmpeg();
delete process.env.FLVTOOL2_PATH;
ff._forgetPaths();
- ff._getFlvtoolPath(function(err, fflvtool) {
+ ff._getFlvtoolPath(function (err, fflvtool) {
testhelper.logError(err);
assert.ok(!err);
@@ -654,7 +695,7 @@ describe('Capabilities', function() {
// Just check that the callback is actually called synchronously
// (which indicates no which call was made)
var after = 0;
- ff._getFlvtoolPath(function(err, fflvtool) {
+ ff._getFlvtoolPath(function (err, fflvtool) {
testhelper.logError(err);
assert.ok(!err);
@@ -669,5 +710,4 @@ describe('Capabilities', function() {
});
});
});
-
});
diff --git a/test/coverage.html b/test/coverage.html
index 58996049..e69de29b 100644
--- a/test/coverage.html
+++ b/test/coverage.html
@@ -1,355 +0,0 @@
-make[1]: entrant dans le répertoire « /home/niko/dev/forks/node-fluent-ffmpeg »
-
Coverage
-Coverage
lib/capabilities.js
Line | Hits | Source |
---|
1 | | /*jshint node:true*/ |
2 | | 'use strict'; |
3 | | |
4 | 1 | var fs = require('fs'); |
5 | 1 | var path = require('path'); |
6 | 1 | var async = require('async'); |
7 | 1 | var utils = require('./utils'); |
8 | | |
9 | | /* |
10 | | *! Capability helpers |
11 | | */ |
12 | | |
13 | 1 | var avCodecRegexp = /^\s*([D ])([E ])([VAS])([S ])([D ])([T ]) ([^ ]+) +(.*)$/; |
14 | 1 | var ffCodecRegexp = /^\s*([D\.])([E\.])([VAS])([I\.])([L\.])([S\.]) ([^ ]+) +(.*)$/; |
15 | 1 | var ffEncodersRegexp = /\(encoders:([^\)]+)\)/; |
16 | 1 | var ffDecodersRegexp = /\(decoders:([^\)]+)\)/; |
17 | 1 | var formatRegexp = /^\s*([D ])([E ]) ([^ ]+) +(.*)$/; |
18 | 1 | var lineBreakRegexp = /\r\n|\r|\n/; |
19 | 1 | var filterRegexp = /^(?: [T\.][S\.][C\.] )?([^ ]+) +(AA?|VV?|\|)->(AA?|VV?|\|) +(.*)$/; |
20 | | |
21 | 1 | var cache = {}; |
22 | | |
23 | | function copy(src, dest) { |
24 | 125 | Object.keys(src).forEach(function(k) { |
25 | 741 | dest[k] = src[k]; |
26 | | }); |
27 | | } |
28 | | |
29 | 1 | module.exports = function(proto) { |
30 | | /** |
31 | | * Forget executable paths |
32 | | * |
33 | | * (only used for testing purposes) |
34 | | * |
35 | | * @method FfmpegCommand#_forgetPaths |
36 | | * @private |
37 | | */ |
38 | 1 | proto._forgetPaths = function() { |
39 | 8 | delete cache.ffmpegPath; |
40 | 8 | delete cache.ffprobePath; |
41 | 8 | delete cache.flvtoolPath; |
42 | | }; |
43 | | |
44 | | |
45 | | /** |
46 | | * Check for ffmpeg availability |
47 | | * |
48 | | * If the FFMPEG_PATH environment variable is set, try to use it. |
49 | | * If it is unset or incorrect, try to find ffmpeg in the PATH instead. |
50 | | * |
51 | | * @method FfmpegCommand#_getFfmpegPath |
52 | | * @param {Function} callback callback with signature (err, path) |
53 | | * @private |
54 | | */ |
55 | 1 | proto._getFfmpegPath = function(callback) { |
56 | 35 | if ('ffmpegPath' in cache) { |
57 | 29 | return callback(null, cache.ffmpegPath); |
58 | | } |
59 | | |
60 | 6 | async.waterfall([ |
61 | | // Try FFMPEG_PATH |
62 | | function(cb) { |
63 | 6 | if (process.env.FFMPEG_PATH) { |
64 | 3 | fs.exists(process.env.FFMPEG_PATH, function(exists) { |
65 | 3 | if (exists) { |
66 | 1 | cb(null, process.env.FFMPEG_PATH); |
67 | | } else { |
68 | 2 | cb(null, ''); |
69 | | } |
70 | | }); |
71 | | } else { |
72 | 3 | cb(null, ''); |
73 | | } |
74 | | }, |
75 | | |
76 | | // Search in the PATH |
77 | | function(ffmpeg, cb) { |
78 | 6 | if (ffmpeg.length) { |
79 | 1 | return cb(null, ffmpeg); |
80 | | } |
81 | | |
82 | 5 | utils.which('ffmpeg', function(err, ffmpeg) { |
83 | 5 | cb(err, ffmpeg); |
84 | | }); |
85 | | } |
86 | | ], function(err, ffmpeg) { |
87 | 6 | if (err) { |
88 | 0 | callback(err); |
89 | | } else { |
90 | 6 | callback(null, cache.ffmpegPath = (ffmpeg || '')); |
91 | | } |
92 | | }); |
93 | | }; |
94 | | |
95 | | |
96 | | /** |
97 | | * Check for ffprobe availability |
98 | | * |
99 | | * If the FFPROBE_PATH environment variable is set, try to use it. |
100 | | * If it is unset or incorrect, try to find ffprobe in the PATH instead. |
101 | | * If this still fails, try to find ffprobe in the same directory as ffmpeg. |
102 | | * |
103 | | * @method FfmpegCommand#_getFfprobePath |
104 | | * @param {Function} callback callback with signature (err, path) |
105 | | * @private |
106 | | */ |
107 | 1 | proto._getFfprobePath = function(callback) { |
108 | 13 | if ('ffprobePath' in cache) { |
109 | 9 | return callback(null, cache.ffprobePath); |
110 | | } |
111 | | |
112 | 4 | var self = this; |
113 | 4 | async.waterfall([ |
114 | | // Try FFPROBE_PATH |
115 | | function(cb) { |
116 | 4 | if (process.env.FFPROBE_PATH) { |
117 | 2 | fs.exists(process.env.FFPROBE_PATH, function(exists) { |
118 | 2 | cb(null, exists ? process.env.FFPROBE_PATH : ''); |
119 | | }); |
120 | | } else { |
121 | 2 | cb(null, ''); |
122 | | } |
123 | | }, |
124 | | |
125 | | // Search in the PATH |
126 | | function(ffprobe, cb) { |
127 | 4 | if (ffprobe.length) { |
128 | 1 | return cb(null, ffprobe); |
129 | | } |
130 | | |
131 | 3 | utils.which('ffprobe', function(err, ffprobe) { |
132 | 3 | cb(err, ffprobe); |
133 | | }); |
134 | | }, |
135 | | |
136 | | // Search in the same directory as ffmpeg |
137 | | function(ffprobe, cb) { |
138 | 4 | if (ffprobe.length) { |
139 | 4 | return cb(null, ffprobe); |
140 | | } |
141 | | |
142 | 0 | self._getFfmpegPath(function(err, ffmpeg) { |
143 | 0 | if (err) { |
144 | 0 | cb(err); |
145 | 0 | } else if (ffmpeg.length) { |
146 | 0 | var name = utils.isWindows ? 'ffprobe.exe' : 'ffprobe'; |
147 | 0 | var ffprobe = path.join(path.dirname(ffmpeg), name); |
148 | 0 | fs.exists(ffprobe, function(exists) { |
149 | 0 | cb(null, exists ? ffprobe : ''); |
150 | | }); |
151 | | } else { |
152 | 0 | cb(null, ''); |
153 | | } |
154 | | }); |
155 | | } |
156 | | ], function(err, ffprobe) { |
157 | 4 | if (err) { |
158 | 0 | callback(err); |
159 | | } else { |
160 | 4 | callback(null, cache.ffprobePath = (ffprobe || '')); |
161 | | } |
162 | | }); |
163 | | }; |
164 | | |
165 | | |
166 | | /** |
167 | | * Check for flvtool2/flvmeta availability |
168 | | * |
169 | | * If the FLVTOOL2_PATH or FLVMETA_PATH environment variable are set, try to use them. |
170 | | * If both are either unset or incorrect, try to find flvtool2 or flvmeta in the PATH instead. |
171 | | * |
172 | | * @method FfmpegCommand#_getFlvtoolPath |
173 | | * @param {Function} callback callback with signature (err, path) |
174 | | * @private |
175 | | */ |
176 | 1 | proto._getFlvtoolPath = function(callback) { |
177 | 29 | if ('flvtoolPath' in cache) { |
178 | 28 | return callback(null, cache.flvtoolPath); |
179 | | } |
180 | | |
181 | 1 | async.waterfall([ |
182 | | // Try FLVMETA_PATH |
183 | | function(cb) { |
184 | 1 | if (process.env.FLVMETA_PATH) { |
185 | 0 | fs.exists(process.env.FLVMETA_PATH, function(exists) { |
186 | 0 | cb(null, exists ? process.env.FLVMETA_PATH : ''); |
187 | | }); |
188 | | } else { |
189 | 1 | cb(null, ''); |
190 | | } |
191 | | }, |
192 | | |
193 | | // Try FLVTOOL2_PATH |
194 | | function(flvtool, cb) { |
195 | 1 | if (flvtool.length) { |
196 | 0 | return cb(null, flvtool); |
197 | | } |
198 | | |
199 | 1 | if (process.env.FLVTOOL2_PATH) { |
200 | 0 | fs.exists(process.env.FLVTOOL2_PATH, function(exists) { |
201 | 0 | cb(null, exists ? process.env.FLVTOOL2_PATH : ''); |
202 | | }); |
203 | | } else { |
204 | 1 | cb(null, ''); |
205 | | } |
206 | | }, |
207 | | |
208 | | // Search for flvmeta in the PATH |
209 | | function(flvtool, cb) { |
210 | 1 | if (flvtool.length) { |
211 | 0 | return cb(null, flvtool); |
212 | | } |
213 | | |
214 | 1 | utils.which('flvmeta', function(err, flvmeta) { |
215 | 1 | cb(err, flvmeta); |
216 | | }); |
217 | | }, |
218 | | |
219 | | // Search for flvtool2 in the PATH |
220 | | function(flvtool, cb) { |
221 | 1 | if (flvtool.length) { |
222 | 1 | return cb(null, flvtool); |
223 | | } |
224 | | |
225 | 0 | utils.which('flvtool2', function(err, flvtool2) { |
226 | 0 | cb(err, flvtool2); |
227 | | }); |
228 | | }, |
229 | | ], function(err, flvtool) { |
230 | 1 | if (err) { |
231 | 0 | callback(err); |
232 | | } else { |
233 | 1 | callback(null, cache.flvtoolPath = (flvtool || '')); |
234 | | } |
235 | | }); |
236 | | }; |
237 | | |
238 | | |
239 | | /** |
240 | | * Query ffmpeg for available filters |
241 | | * |
242 | | * Calls 'callback' with a filters object as its second argument. This |
243 | | * object has keys for every available filter, and values are object |
244 | | * with filter data: |
245 | | * - 'description': filter description |
246 | | * - 'input': input type ('audio', 'video' or 'none') |
247 | | * - 'multipleInputs': bool, whether the filter supports multiple inputs |
248 | | * - 'output': output type ('audio', 'video' or 'none') |
249 | | * - 'multipleOutputs': bool, whether the filter supports multiple outputs |
250 | | * |
251 | | * @method FfmpegCommand#availableFilters |
252 | | * @category Capabilities |
253 | | * @aliases getAvailableFilters |
254 | | * |
255 | | * @param {Function} callback callback with signature (err, filters) |
256 | | */ |
257 | 1 | proto.availableFilters = |
258 | | proto.getAvailableFilters = function(callback) { |
259 | 2 | if ('filters' in cache) { |
260 | 1 | return callback(null, cache.filters); |
261 | | } |
262 | | |
263 | 1 | this._spawnFfmpeg(['-filters'], { captureStdout: true }, function (err, stdout) { |
264 | 1 | if (err) { |
265 | 0 | return callback(err); |
266 | | } |
267 | | |
268 | 1 | var lines = stdout.split('\n'); |
269 | 1 | var data = {}; |
270 | 1 | var types = { A: 'audio', V: 'video', '|': 'none' }; |
271 | | |
272 | 1 | lines.forEach(function(line) { |
273 | 137 | var match = line.match(filterRegexp); |
274 | 137 | if (match) { |
275 | 135 | data[match[1]] = { |
276 | | description: match[4], |
277 | | input: types[match[2].charAt(0)], |
278 | | multipleInputs: match[2].length > 1, |
279 | | output: types[match[3].charAt(0)], |
280 | | multipleOutputs: match[3].length > 1 |
281 | | }; |
282 | | } |
283 | | }); |
284 | | |
285 | 1 | callback(null, cache.filters = data); |
286 | | }); |
287 | | }; |
288 | | |
289 | | |
290 | | /** |
291 | | * Query ffmpeg for available codecs |
292 | | * |
293 | | * Calls 'callback' with a codecs object as its second argument. This |
294 | | * object has keys for every available codec, and values are object |
295 | | * with codec data: |
296 | | * - 'description': codec description |
297 | | * - 'canEncode': bool, whether the codec can encode streams |
298 | | * - 'canDecode': bool, whether the codec can decode streams |
299 | | * |
300 | | * Depending on the ffmpeg version, more keys can be available. |
301 | | * |
302 | | * @method FfmpegCommand#availableCodecs |
303 | | * @category Capabilities |
304 | | * @aliases getAvailableCodecs |
305 | | * |
306 | | * @param {Function} callback callback with signature (err, codecs) |
307 | | */ |
308 | 1 | proto.availableCodecs = |
309 | | proto.getAvailableCodecs = function(callback) { |
310 | 25 | if ('codecs' in cache) { |
311 | 24 | return callback(null, cache.codecs); |
312 | | } |
313 | | |
314 | 1 | this._spawnFfmpeg(['-codecs'], { captureStdout: true }, function(err, stdout) { |
315 | 1 | if (err) { |
316 | 0 | return callback(err); |
317 | | } |
318 | | |
319 | 1 | var lines = stdout.split(lineBreakRegexp); |
320 | 1 | var data = {}; |
321 | | |
322 | 1 | lines.forEach(function(line) { |
323 | 369 | var match = line.match(avCodecRegexp); |
324 | 369 | if (match && match[7] !== '=') { |
325 | 0 | data[match[7]] = { |
326 | | type: { 'V': 'video', 'A': 'audio', 'S': 'subtitle' }[match[3]], |
327 | | description: match[8], |
328 | | canDecode: match[1] === 'D', |
329 | | canEncode: match[2] === 'E', |
330 | | drawHorizBand: match[4] === 'S', |
331 | | directRendering: match[5] === 'D', |
332 | | weirdFrameTruncation: match[6] === 'T' |
333 | | }; |
334 | | } |
335 | | |
336 | 369 | match = line.match(ffCodecRegexp); |
337 | 369 | if (match && match[7] !== '=') { |
338 | 357 | var codecData = data[match[7]] = { |
339 | | type: { 'V': 'video', 'A': 'audio', 'S': 'subtitle' }[match[3]], |
340 | | description: match[8], |
341 | | canDecode: match[1] === 'D', |
342 | | canEncode: match[2] === 'E', |
343 | | intraFrameOnly: match[4] === 'I', |
344 | | isLossy: match[5] === 'L', |
345 | | isLossless: match[6] === 'S' |
346 | | }; |
347 | | |
348 | 357 | var encoders = codecData.description.match(ffEncodersRegexp); |
349 | 357 | encoders = encoders ? encoders[1].trim().split(' ') : []; |
350 | | |
351 | 357 | var decoders = codecData.description.match(ffDecodersRegexp); |
352 | 357 | decoders = decoders ? decoders[1].trim().split(' ') : []; |
353 | | |
354 | 357 | if (encoders.length || decoders.length) { |
355 | 58 | var coderData = {}; |
356 | 58 | copy(codecData, coderData); |
357 | 58 | delete coderData.canEncode; |
358 | 58 | delete coderData.canDecode; |
359 | | |
360 | 58 | encoders.forEach(function(name) { |
361 | 32 | data[name] = {}; |
362 | 32 | copy(coderData, data[name]); |
363 | 32 | data[name].canEncode = true; |
364 | | }); |
365 | | |
366 | 58 | decoders.forEach(function(name) { |
367 | 73 | if (name in data) { |
368 | 38 | data[name].canDecode = true; |
369 | | } else { |
370 | 35 | data[name] = {}; |
371 | 35 | copy(coderData, data[name]); |
372 | 35 | data[name].canDecode = true; |
373 | | } |
374 | | }); |
375 | | } |
376 | | } |
377 | | }); |
378 | | |
379 | 1 | callback(null, cache.codecs = data); |
380 | | }); |
381 | | }; |
382 | | |
383 | | |
384 | | /** |
385 | | * Query ffmpeg for available formats |
386 | | * |
387 | | * Calls 'callback' with a formats object as its second argument. This |
388 | | * object has keys for every available format, and values are object |
389 | | * with format data: |
390 | | * - 'description': format description |
391 | | * - 'canMux': bool, whether the format can mux streams into an output file |
392 | | * - 'canDemux': bool, whether the format can demux streams from an input file |
393 | | * |
394 | | * @method FfmpegCommand#availableFormats |
395 | | * @category Capabilities |
396 | | * @aliases getAvailableFormats |
397 | | * |
398 | | * @param {Function} callback callback with signature (err, formats) |
399 | | */ |
400 | 1 | proto.availableFormats = |
401 | | proto.getAvailableFormats = function(callback) { |
402 | 28 | if ('formats' in cache) { |
403 | 27 | return callback(null, cache.formats); |
404 | | } |
405 | | |
406 | | // Run ffmpeg -formats |
407 | 1 | this._spawnFfmpeg(['-formats'], { captureStdout: true }, function (err, stdout) { |
408 | 1 | if (err) { |
409 | 0 | return callback(err); |
410 | | } |
411 | | |
412 | | // Parse output |
413 | 1 | var lines = stdout.split(lineBreakRegexp); |
414 | 1 | var data = {}; |
415 | | |
416 | 1 | lines.forEach(function(line) { |
417 | 252 | var match = line.match(formatRegexp); |
418 | 252 | if (match) { |
419 | 247 | data[match[3]] = { |
420 | | description: match[4], |
421 | | canDemux: match[1] === 'D', |
422 | | canMux: match[2] === 'E' |
423 | | }; |
424 | | } |
425 | | }); |
426 | | |
427 | 1 | callback(null, cache.formats = data); |
428 | | }); |
429 | | }; |
430 | | |
431 | | |
432 | | /** |
433 | | * Check capabilities before executing a command |
434 | | * |
435 | | * Checks whether all used codecs and formats are indeed available |
436 | | * |
437 | | * @method FfmpegCommand#_checkCapabilities |
438 | | * @param {Function} callback callback with signature (err) |
439 | | * @private |
440 | | */ |
441 | 1 | proto._checkCapabilities = function(callback) { |
442 | 26 | var self = this; |
443 | 26 | async.waterfall([ |
444 | | // Get available formats |
445 | | function(cb) { |
446 | 26 | self.availableFormats(cb); |
447 | | }, |
448 | | |
449 | | // Check whether specified formats are available |
450 | | function(formats, cb) { |
451 | | // Output format |
452 | 26 | var format = self._output.find('-f', 1); |
453 | | |
454 | 26 | if (format) { |
455 | 24 | if (!(format[0] in formats) || !(formats[format[0]].canMux)) { |
456 | 2 | return cb(new Error('Output format ' + format[0] + ' is not available')); |
457 | | } |
458 | | } |
459 | | |
460 | | // Input format(s) |
461 | 24 | var unavailable = self._inputs.reduce(function(fmts, input) { |
462 | 24 | var format = input.before.find('-f', 1); |
463 | 24 | if (format) { |
464 | 4 | if (!(format[0] in formats) || !(formats[format[0]].canDemux)) { |
465 | 1 | fmts.push(format[0]); |
466 | | } |
467 | | } |
468 | | |
469 | 24 | return fmts; |
470 | | }, []); |
471 | | |
472 | 24 | if (unavailable.length === 1) { |
473 | 1 | cb(new Error('Input format ' + unavailable[0] + ' is not available')); |
474 | 23 | } else if (unavailable.length > 1) { |
475 | 0 | cb(new Error('Input formats ' + unavailable.join(', ') + ' are not available')); |
476 | | } else { |
477 | 23 | cb(); |
478 | | } |
479 | | }, |
480 | | |
481 | | // Get available codecs |
482 | | function(cb) { |
483 | 23 | self.availableCodecs(cb); |
484 | | }, |
485 | | |
486 | | // Check whether specified codecs are available |
487 | | function(codecs, cb) { |
488 | | // Audio codec |
489 | 23 | var acodec = self._audio.find('-acodec', 1); |
490 | 23 | if (acodec && acodec[0] !== 'copy') { |
491 | 21 | if (!(acodec[0] in codecs) || codecs[acodec[0]].type !== 'audio' || !(codecs[acodec[0]].canEncode)) { |
492 | 1 | return cb(new Error('Audio codec ' + acodec[0] + ' is not available')); |
493 | | } |
494 | | } |
495 | | |
496 | | // Video codec |
497 | 22 | var vcodec = self._video.find('-vcodec', 1); |
498 | 22 | if (vcodec && vcodec[0] !== 'copy') { |
499 | 20 | if (!(vcodec[0] in codecs) || codecs[vcodec[0]].type !== 'video' || !(codecs[vcodec[0]].canEncode)) { |
500 | 1 | return cb(new Error('Video codec ' + vcodec[0] + ' is not available')); |
501 | | } |
502 | | } |
503 | | |
504 | 21 | cb(); |
505 | | } |
506 | | ], callback); |
507 | | }; |
508 | | }; |
509 | | |
lib/ffprobe.js
Line | Hits | Source |
---|
1 | | /*jshint node:true, laxcomma:true*/ |
2 | | 'use strict'; |
3 | | |
4 | 1 | var spawn = require('child_process').spawn; |
5 | | |
6 | | |
7 | 263 | function legacyTag(key) { return key.match(/^TAG:/); } |
8 | 263 | function legacyDisposition(key) { return key.match(/^DISPOSITION:/); } |
9 | | |
10 | | |
11 | 1 | module.exports = function(proto) { |
12 | | /** |
13 | | * Run ffprobe on last specified input |
14 | | * |
15 | | * Callback will receive an object as its second argument. This object |
16 | | * has the same format as what the following command returns: |
17 | | * |
18 | | * ffprobe -print_format json -show_streams -show_format INPUTFILE |
19 | | * |
20 | | * @method FfmpegCommand#ffprobe |
21 | | * @category Metadata |
22 | | * |
23 | | * @param {Function} callback callback with signature (err, ffprobeData) |
24 | | * |
25 | | */ |
26 | 1 | proto.ffprobe = function(callback) { |
27 | 10 | if (!this._currentInput) { |
28 | 1 | return callback(new Error('No input specified')); |
29 | | } |
30 | | |
31 | 9 | if (typeof this._currentInput.source !== 'string') { |
32 | 1 | return callback(new Error('Cannot run ffprobe on non-file input')); |
33 | | } |
34 | | |
35 | | // Find ffprobe |
36 | 8 | var self = this; |
37 | 8 | this._getFfprobePath(function(err, path) { |
38 | 8 | if (err) { |
39 | 0 | return callback(err); |
40 | 8 | } else if (!path) { |
41 | 0 | return callback(new Error('Cannot find ffprobe')); |
42 | | } |
43 | | |
44 | 8 | var stdout = ''; |
45 | 8 | var stdoutClosed = false; |
46 | 8 | var stderr = ''; |
47 | 8 | var stderrClosed = false; |
48 | | |
49 | | // Spawn ffprobe |
50 | 8 | var ffprobe = spawn(path, [ |
51 | | '-print_format', 'json', |
52 | | '-show_streams', |
53 | | '-show_format', |
54 | | self._currentInput.source |
55 | | ]); |
56 | | |
57 | 8 | ffprobe.on('error', function(err) { |
58 | 0 | callback(err); |
59 | | }); |
60 | | |
61 | | // Ensure we wait for captured streams to end before calling callback |
62 | 8 | var exitError = null; |
63 | | function handleExit(err) { |
64 | 24 | if (err) { |
65 | 1 | exitError = err; |
66 | | } |
67 | | |
68 | 24 | if (processExited && stdoutClosed && stderrClosed) { |
69 | 8 | if (exitError) { |
70 | 1 | if (stderr) { |
71 | 1 | exitError.message += '\n' + stderr; |
72 | | } |
73 | | |
74 | 1 | return callback(exitError); |
75 | | } |
76 | | |
77 | | // Process output |
78 | 7 | var data; |
79 | | |
80 | 7 | try { |
81 | 7 | data = JSON.parse(stdout); |
82 | | } catch(e) { |
83 | 0 | return callback(e); |
84 | | } |
85 | | |
86 | | // Handle legacy output with "TAG:x" and "DISPOSITION:x" keys |
87 | 7 | [data.format].concat(data.streams).forEach(function(target) { |
88 | 15 | var legacyTagKeys = Object.keys(target).filter(legacyTag); |
89 | | |
90 | 15 | if (legacyTagKeys.length) { |
91 | 0 | target.tags = target.tags || {}; |
92 | | |
93 | 0 | legacyTagKeys.forEach(function(tagKey) { |
94 | 0 | target.tags[tagKey.substr(4)] = target[tagKey]; |
95 | 0 | delete target[tagKey]; |
96 | | }); |
97 | | } |
98 | | |
99 | 15 | var legacyDispositionKeys = Object.keys(target).filter(legacyDisposition); |
100 | | |
101 | 15 | if (legacyDispositionKeys.length) { |
102 | 0 | target.disposition = target.disposition || {}; |
103 | | |
104 | 0 | legacyDispositionKeys.forEach(function(dispositionKey) { |
105 | 0 | target.disposition[dispositionKey.substr(12)] = target[dispositionKey]; |
106 | 0 | delete target[dispositionKey]; |
107 | | }); |
108 | | } |
109 | | }); |
110 | | |
111 | 7 | callback(null, data); |
112 | | } |
113 | | } |
114 | | |
115 | | // Handle ffprobe exit |
116 | 8 | var processExited = false; |
117 | 8 | ffprobe.on('exit', function(code, signal) { |
118 | 8 | processExited = true; |
119 | | |
120 | 8 | if (code) { |
121 | 1 | handleExit(new Error('ffprobe exited with code ' + code)); |
122 | 7 | } else if (signal) { |
123 | 0 | handleExit(new Error('ffprobe was killed with signal ' + signal)); |
124 | | } else { |
125 | 7 | handleExit(); |
126 | | } |
127 | | }); |
128 | | |
129 | | // Handle stdout/stderr streams |
130 | 8 | ffprobe.stdout.on('data', function(data) { |
131 | 18 | stdout += data; |
132 | | }); |
133 | | |
134 | 8 | ffprobe.stdout.on('close', function() { |
135 | 8 | stdoutClosed = true; |
136 | 8 | handleExit(); |
137 | | }); |
138 | | |
139 | 8 | ffprobe.stderr.on('data', function(data) { |
140 | 33 | stderr += data; |
141 | | }); |
142 | | |
143 | 8 | ffprobe.stderr.on('close', function() { |
144 | 8 | stderrClosed = true; |
145 | 8 | handleExit(); |
146 | | }); |
147 | | }); |
148 | | }; |
149 | | }; |
150 | | |
151 | | |
lib/fluent-ffmpeg.js
Line | Hits | Source |
---|
1 | | /*jshint node:true*/ |
2 | | 'use strict'; |
3 | | |
4 | 1 | var path = require('path'); |
5 | 1 | var util = require('util'); |
6 | 1 | var EventEmitter = require('events').EventEmitter; |
7 | | |
8 | 1 | var utils = require('./utils'); |
9 | | |
10 | | |
11 | | /** |
12 | | * Create an ffmpeg command |
13 | | * |
14 | | * Can be called with or without the 'new' operator, and the 'input' parameter |
15 | | * may be specified as 'options.source' instead (or passed later with the |
16 | | * addInput method). |
17 | | * |
18 | | * @constructor |
19 | | * @param {String|ReadableStream} [input] input file path or readable stream |
20 | | * @param {Object} [options] command options |
21 | | * @param {Object} [options.logger=<no logging>] logger object with 'error', 'warning', 'info' and 'debug' methods |
22 | | * @param {Number} [options.niceness=0] ffmpeg process niceness, ignored on Windows |
23 | | * @param {Number} [options.priority=0] alias for `niceness` |
24 | | * @param {String} [options.presets="fluent-ffmpeg/lib/presets"] directory to load presets from |
25 | | * @param {String} [options.preset="fluent-ffmpeg/lib/presets"] alias for `presets` |
26 | | * @param {Number} [options.timeout=<no timeout>] ffmpeg processing timeout in seconds |
27 | | * @param {String|ReadableStream} [options.source=<no input>] alias for the `input` parameter |
28 | | */ |
29 | | function FfmpegCommand(input, options) { |
30 | | // Make 'new' optional |
31 | 207 | if (!(this instanceof FfmpegCommand)) { |
32 | 1 | return new FfmpegCommand(input, options); |
33 | | } |
34 | | |
35 | 206 | EventEmitter.call(this); |
36 | | |
37 | 206 | if (typeof input === 'object' && !('readable' in input)) { |
38 | | // Options object passed directly |
39 | 89 | options = input; |
40 | | } else { |
41 | | // Input passed first |
42 | 117 | options = options || {}; |
43 | 117 | options.source = input; |
44 | | } |
45 | | |
46 | | // Add input if present |
47 | 206 | this._inputs = []; |
48 | 206 | if (options.source) { |
49 | 94 | this.addInput(options.source); |
50 | | } |
51 | | |
52 | | // Create argument lists |
53 | 206 | this._audio = utils.args(); |
54 | 206 | this._audioFilters = utils.args(); |
55 | 206 | this._video = utils.args(); |
56 | 206 | this._videoFilters = utils.args(); |
57 | 206 | this._sizeFilters = utils.args(); |
58 | 206 | this._output = utils.args(); |
59 | | |
60 | | // Set default option values |
61 | 206 | options.presets = options.presets || options.preset || path.join(__dirname, 'presets'); |
62 | 206 | options.niceness = options.niceness || options.priority || 0; |
63 | | |
64 | | // Save options |
65 | 206 | this.options = options; |
66 | | |
67 | | // Setup logger |
68 | 206 | this.logger = options.logger || { |
69 | | debug: function() {}, |
70 | | info: function() {}, |
71 | | warn: function() {}, |
72 | | error: function() {} |
73 | | }; |
74 | | } |
75 | 1 | util.inherits(FfmpegCommand, EventEmitter); |
76 | 1 | module.exports = FfmpegCommand; |
77 | | |
78 | | |
79 | | /* Add methods from options submodules */ |
80 | | |
81 | 1 | require('./options/inputs')(FfmpegCommand.prototype); |
82 | 1 | require('./options/audio')(FfmpegCommand.prototype); |
83 | 1 | require('./options/video')(FfmpegCommand.prototype); |
84 | 1 | require('./options/videosize')(FfmpegCommand.prototype); |
85 | 1 | require('./options/output')(FfmpegCommand.prototype); |
86 | 1 | require('./options/custom')(FfmpegCommand.prototype); |
87 | 1 | require('./options/misc')(FfmpegCommand.prototype); |
88 | | |
89 | | |
90 | | /* Add processor methods */ |
91 | | |
92 | 1 | require('./processor')(FfmpegCommand.prototype); |
93 | | |
94 | | |
95 | | /* Add capabilities methods */ |
96 | | |
97 | 1 | require('./capabilities')(FfmpegCommand.prototype); |
98 | | |
99 | 1 | FfmpegCommand.availableFilters = |
100 | | FfmpegCommand.getAvailableFilters = function(callback) { |
101 | 1 | (new FfmpegCommand()).availableFilters(callback); |
102 | | }; |
103 | | |
104 | 1 | FfmpegCommand.availableCodecs = |
105 | | FfmpegCommand.getAvailableCodecs = function(callback) { |
106 | 1 | (new FfmpegCommand()).availableCodecs(callback); |
107 | | }; |
108 | | |
109 | 1 | FfmpegCommand.availableFormats = |
110 | | FfmpegCommand.getAvailableFormats = function(callback) { |
111 | 1 | (new FfmpegCommand()).availableFormats(callback); |
112 | | }; |
113 | | |
114 | | |
115 | | /* Add ffprobe methods */ |
116 | | |
117 | 1 | require('./ffprobe')(FfmpegCommand.prototype); |
118 | | |
119 | 1 | FfmpegCommand.ffprobe = function(file, callback) { |
120 | 4 | (new FfmpegCommand(file)).ffprobe(callback); |
121 | | }; |
122 | | |
123 | | |
lib/options/audio.js
Line | Hits | Source |
---|
1 | | /*jshint node:true*/ |
2 | | 'use strict'; |
3 | | |
4 | | /* |
5 | | *! Audio-related methods |
6 | | */ |
7 | | |
8 | 1 | module.exports = function(proto) { |
9 | | /** |
10 | | * Disable audio in the output |
11 | | * |
12 | | * @method FfmpegCommand#noAudio |
13 | | * @category Audio |
14 | | * @aliases withNoAudio |
15 | | * @return FfmpegCommand |
16 | | */ |
17 | 1 | proto.withNoAudio = |
18 | | proto.noAudio = function() { |
19 | 2 | this._audio.clear(); |
20 | 2 | this._audio('-an'); |
21 | | |
22 | 2 | return this; |
23 | | }; |
24 | | |
25 | | |
26 | | /** |
27 | | * Specify audio codec |
28 | | * |
29 | | * @method FfmpegCommand#audioCodec |
30 | | * @category Audio |
31 | | * @aliases withAudioCodec |
32 | | * |
33 | | * @param {String} codec audio codec name |
34 | | * @return FfmpegCommand |
35 | | */ |
36 | 1 | proto.withAudioCodec = |
37 | | proto.audioCodec = function(codec) { |
38 | 26 | this._audio('-acodec', codec); |
39 | 26 | return this; |
40 | | }; |
41 | | |
42 | | |
43 | | /** |
44 | | * Specify audio bitrate |
45 | | * |
46 | | * @method FfmpegCommand#audioBitrate |
47 | | * @category Audio |
48 | | * @aliases withAudioBitrate |
49 | | * |
50 | | * @param {String|Number} bitrate audio bitrate in kbps (with an optional 'k' suffix) |
51 | | * @return FfmpegCommand |
52 | | */ |
53 | 1 | proto.withAudioBitrate = |
54 | | proto.audioBitrate = function(bitrate) { |
55 | 22 | this._audio('-b:a', ('' + bitrate).replace(/k?$/, 'k')); |
56 | 22 | return this; |
57 | | }; |
58 | | |
59 | | |
60 | | /** |
61 | | * Specify audio channel count |
62 | | * |
63 | | * @method FfmpegCommand#audioChannels |
64 | | * @category Audio |
65 | | * @aliases withAudioChannels |
66 | | * |
67 | | * @param {Number} channels channel count |
68 | | * @return FfmpegCommand |
69 | | */ |
70 | 1 | proto.withAudioChannels = |
71 | | proto.audioChannels = function(channels) { |
72 | 22 | this._audio('-ac', channels); |
73 | 22 | return this; |
74 | | }; |
75 | | |
76 | | |
77 | | /** |
78 | | * Specify audio frequency |
79 | | * |
80 | | * @method FfmpegCommand#audioFrequency |
81 | | * @category Audio |
82 | | * @aliases withAudioFrequency |
83 | | * |
84 | | * @param {Number} freq audio frequency in Hz |
85 | | * @return FfmpegCommand |
86 | | */ |
87 | 1 | proto.withAudioFrequency = |
88 | | proto.audioFrequency = function(freq) { |
89 | 20 | this._audio('-ar', freq); |
90 | 20 | return this; |
91 | | }; |
92 | | |
93 | | |
94 | | /** |
95 | | * Specify audio quality |
96 | | * |
97 | | * @method FfmpegCommand#audioQuality |
98 | | * @category Audio |
99 | | * @aliases withAudioQuality |
100 | | * |
101 | | * @param {Number} quality audio quality factor |
102 | | * @return FfmpegCommand |
103 | | */ |
104 | 1 | proto.withAudioQuality = |
105 | | proto.audioQuality = function(quality) { |
106 | 1 | this._audio('-aq', quality); |
107 | 1 | return this; |
108 | | }; |
109 | | |
110 | | |
111 | | /** |
112 | | * Specify custom audio filter(s) |
113 | | * |
114 | | * Can be called both with one or many filters, or a filter array. |
115 | | * |
116 | | * @example |
117 | | * command.audioFilters('filter1'); |
118 | | * |
119 | | * @example |
120 | | * command.audioFilters('filter1', 'filter2'); |
121 | | * |
122 | | * @example |
123 | | * command.audioFilters(['filter1', 'filter2']); |
124 | | * |
125 | | * @method FfmpegCommand#audioFilters |
126 | | * @aliases withAudioFilter,withAudioFilters,audioFilter |
127 | | * @category Audio |
128 | | * |
129 | | * @param {String|Array} filters... audio filter strings or string array |
130 | | * @return FfmpegCommand |
131 | | */ |
132 | 1 | proto.withAudioFilter = |
133 | | proto.withAudioFilters = |
134 | | proto.audioFilter = |
135 | | proto.audioFilters = function(filters) { |
136 | 3 | if (arguments.length > 1) { |
137 | 1 | filters = [].slice.call(arguments); |
138 | | } |
139 | | |
140 | 3 | this._audioFilters(filters); |
141 | 3 | return this; |
142 | | }; |
143 | | }; |
144 | | |
lib/options/custom.js
Line | Hits | Source |
---|
1 | | /*jshint node:true*/ |
2 | | 'use strict'; |
3 | | |
4 | | /* |
5 | | *! Custom options methods |
6 | | */ |
7 | | |
8 | 1 | module.exports = function(proto) { |
9 | | /** |
10 | | * Add custom input option(s) |
11 | | * |
12 | | * When passing a single string or an array, each string containing two |
13 | | * words is split (eg. inputOptions('-option value') is supported) for |
14 | | * compatibility reasons. This is not the case when passing more than |
15 | | * one argument. |
16 | | * |
17 | | * @example |
18 | | * command.inputOptions('option1'); |
19 | | * |
20 | | * @example |
21 | | * command.inputOptions('option1', 'option2'); |
22 | | * |
23 | | * @example |
24 | | * command.inputOptions(['option1', 'option2']); |
25 | | * |
26 | | * @method FfmpegCommand#inputOptions |
27 | | * @category Custom options |
28 | | * @aliases addInputOption,addInputOptions,withInputOption,withInputOptions,inputOption |
29 | | * |
30 | | * @param {...String} options option string(s) or string array |
31 | | * @return FfmpegCommand |
32 | | */ |
33 | 1 | proto.addInputOption = |
34 | | proto.addInputOptions = |
35 | | proto.withInputOption = |
36 | | proto.withInputOptions = |
37 | | proto.inputOption = |
38 | | proto.inputOptions = function(options) { |
39 | 5 | if (!this._currentInput) { |
40 | 1 | throw new Error('No input specified'); |
41 | | } |
42 | | |
43 | 4 | var doSplit = true; |
44 | | |
45 | 4 | if (arguments.length > 1) { |
46 | 2 | options = [].slice.call(arguments); |
47 | 2 | doSplit = false; |
48 | | } |
49 | | |
50 | 4 | if (!Array.isArray(options)) { |
51 | 1 | options = [options]; |
52 | | } |
53 | | |
54 | 4 | this._currentInput.before(options.reduce(function(options, option) { |
55 | 7 | var split = option.split(' '); |
56 | | |
57 | 7 | if (doSplit && split.length === 2) { |
58 | 3 | options.push(split[0], split[1]); |
59 | | } else { |
60 | 4 | options.push(option); |
61 | | } |
62 | | |
63 | 7 | return options; |
64 | | }, [])); |
65 | 4 | return this; |
66 | | }; |
67 | | |
68 | | |
69 | | /** |
70 | | * Add custom output option(s) |
71 | | * |
72 | | * @example |
73 | | * command.outputOptions('option1'); |
74 | | * |
75 | | * @example |
76 | | * command.outputOptions('option1', 'option2'); |
77 | | * |
78 | | * @example |
79 | | * command.outputOptions(['option1', 'option2']); |
80 | | * |
81 | | * @method FfmpegCommand#outputOptions |
82 | | * @category Custom options |
83 | | * @aliases addOutputOption,addOutputOptions,addOption,addOptions,withOutputOption,withOutputOptions,withOption,withOptions,outputOption |
84 | | * |
85 | | * @param {...String} options option string(s) or string array |
86 | | * @return FfmpegCommand |
87 | | */ |
88 | 1 | proto.addOutputOption = |
89 | | proto.addOutputOptions = |
90 | | proto.addOption = |
91 | | proto.addOptions = |
92 | | proto.withOutputOption = |
93 | | proto.withOutputOptions = |
94 | | proto.withOption = |
95 | | proto.withOptions = |
96 | | proto.outputOption = |
97 | | proto.outputOptions = function(options) { |
98 | 6 | var doSplit = true; |
99 | | |
100 | 6 | if (arguments.length > 1) { |
101 | 2 | options = [].slice.call(arguments); |
102 | 2 | doSplit = false; |
103 | | } |
104 | | |
105 | 6 | if (!Array.isArray(options)) { |
106 | 1 | options = [options]; |
107 | | } |
108 | | |
109 | 6 | this._output(options.reduce(function(options, option) { |
110 | 45 | var split = option.split(' '); |
111 | | |
112 | 45 | if (doSplit && split.length === 2) { |
113 | 19 | options.push(split[0], split[1]); |
114 | | } else { |
115 | 26 | options.push(option); |
116 | | } |
117 | | |
118 | 45 | return options; |
119 | | }, [])); |
120 | 6 | return this; |
121 | | }; |
122 | | }; |
123 | | |
Line | Hits | Source |
---|
1 | | /*jshint node:true*/ |
2 | | 'use strict'; |
3 | | |
4 | 1 | var utils = require('../utils'); |
5 | | |
6 | | /* |
7 | | *! Input-related methods |
8 | | */ |
9 | | |
10 | 1 | module.exports = function(proto) { |
11 | | /** |
12 | | * Add an input to command |
13 | | * |
14 | | * Also switches "current input", that is the input that will be affected |
15 | | * by subsequent input-related methods. |
16 | | * |
17 | | * Note: only one stream input is supported for now. |
18 | | * |
19 | | * @method FfmpegCommand#input |
20 | | * @category Input |
21 | | * @aliases mergeAdd,addInput |
22 | | * |
23 | | * @param {String|Readable} source input file path or readable stream |
24 | | * @return FfmpegCommand |
25 | | */ |
26 | 1 | proto.mergeAdd = |
27 | | proto.addInput = |
28 | | proto.input = function(source) { |
29 | 101 | if (typeof source !== 'string') { |
30 | 6 | if (!('readable' in source)) { |
31 | 1 | throw new Error('Invalid input'); |
32 | | } |
33 | | |
34 | 5 | var hasInputStream = this._inputs.some(function(input) { |
35 | 1 | return typeof input.source !== 'string'; |
36 | | }); |
37 | | |
38 | 5 | if (hasInputStream) { |
39 | 1 | throw new Error('Only one input stream is supported'); |
40 | | } |
41 | | |
42 | 4 | source.pause(); |
43 | | } |
44 | | |
45 | 99 | this._inputs.push(this._currentInput = { |
46 | | source: source, |
47 | | before: utils.args(), |
48 | | after: utils.args(), |
49 | | }); |
50 | | |
51 | 99 | return this; |
52 | | }; |
53 | | |
54 | | |
55 | | /** |
56 | | * Specify input format for the last specified input |
57 | | * |
58 | | * @method FfmpegCommand#inputFormat |
59 | | * @category Input |
60 | | * @aliases withInputFormat,fromFormat |
61 | | * |
62 | | * @param {String} format input format |
63 | | * @return FfmpegCommand |
64 | | */ |
65 | 1 | proto.withInputFormat = |
66 | | proto.inputFormat = |
67 | | proto.fromFormat = function(format) { |
68 | 6 | if (!this._currentInput) { |
69 | 1 | throw new Error('No input specified'); |
70 | | } |
71 | | |
72 | 5 | this._currentInput.before('-f', format); |
73 | 5 | return this; |
74 | | }; |
75 | | |
76 | | |
77 | | /** |
78 | | * Specify input FPS for the last specified input |
79 | | * (only valid for raw video formats) |
80 | | * |
81 | | * @method FfmpegCommand#inputFps |
82 | | * @category Input |
83 | | * @aliases withInputFps,withInputFPS,withFpsInput,withFPSInput,inputFPS,inputFps,fpsInput |
84 | | * |
85 | | * @param {Number} fps input FPS |
86 | | * @return FfmpegCommand |
87 | | */ |
88 | 1 | proto.withInputFps = |
89 | | proto.withInputFPS = |
90 | | proto.withFpsInput = |
91 | | proto.withFPSInput = |
92 | | proto.inputFPS = |
93 | | proto.inputFps = |
94 | | proto.fpsInput = |
95 | | proto.FPSInput = function(fps) { |
96 | 2 | if (!this._currentInput) { |
97 | 1 | throw new Error('No input specified'); |
98 | | } |
99 | | |
100 | 1 | this._currentInput.before('-r', fps); |
101 | 1 | return this; |
102 | | }; |
103 | | |
104 | | |
105 | | /** |
106 | | * Specify input seek time for the last specified input |
107 | | * |
108 | | * @method FfmpegCommand#seek |
109 | | * @category Input |
110 | | * @aliases setStartTime,seekTo |
111 | | * |
112 | | * @param {String|Number} seek seek time in seconds or as a '[hh:[mm:]]ss[.xxx]' string |
113 | | * @param {Boolean} [fast=false] use fast (but inexact) seek |
114 | | * @return FfmpegCommand |
115 | | */ |
116 | 1 | proto.setStartTime = |
117 | | proto.seekTo = |
118 | | proto.seek = function(seek, fast) { |
119 | 4 | if (!this._currentInput) { |
120 | 2 | throw new Error('No input specified'); |
121 | | } |
122 | | |
123 | 2 | if (fast) { |
124 | 1 | this._currentInput.before('-ss', seek); |
125 | | } else { |
126 | 1 | this._currentInput.after('-ss', seek); |
127 | | } |
128 | | |
129 | 2 | return this; |
130 | | }; |
131 | | |
132 | | |
133 | | /** |
134 | | * Specify input fast-seek time for the last specified input |
135 | | * |
136 | | * @method FfmpegCommand#fastSeek |
137 | | * @category Input |
138 | | * @aliases fastSeekTo |
139 | | * |
140 | | * @param {String|Number} seek fast-seek time in seconds or as a '[[hh:]mm:]ss[.xxx]' string |
141 | | * @return FfmpegCommand |
142 | | */ |
143 | 1 | proto.fastSeek = |
144 | | proto.fastSeekTo = function(seek) { |
145 | 1 | return this.seek(seek, true); |
146 | | }; |
147 | | |
148 | | |
149 | | /** |
150 | | * Loop over the last specified input |
151 | | * |
152 | | * @method FfmpegCommand#loop |
153 | | * @category Input |
154 | | * |
155 | | * @param {String|Number} [duration] loop duration in seconds or as a '[[hh:]mm:]ss[.xxx]' string |
156 | | * @return FfmpegCommand |
157 | | */ |
158 | 1 | proto.loop = function(duration) { |
159 | 4 | if (!this._currentInput) { |
160 | 1 | throw new Error('No input specified'); |
161 | | } |
162 | | |
163 | 3 | this._currentInput.before('-loop', '1'); |
164 | | |
165 | 3 | if (typeof duration !== 'undefined') { |
166 | 2 | this.duration(duration); |
167 | | } |
168 | | |
169 | 3 | return this; |
170 | | }; |
171 | | }; |
172 | | |
lib/options/misc.js
Line | Hits | Source |
---|
1 | | /*jshint node:true*/ |
2 | | 'use strict'; |
3 | | |
4 | 1 | var path = require('path'); |
5 | | |
6 | | /* |
7 | | *! Miscellaneous methods |
8 | | */ |
9 | | |
10 | 1 | module.exports = function(proto) { |
11 | | /** |
12 | | * Use preset |
13 | | * |
14 | | * @method FfmpegCommand#preset |
15 | | * @category Miscellaneous |
16 | | * @aliases usingPreset |
17 | | * |
18 | | * @param {String|Function} preset preset name or preset function |
19 | | */ |
20 | 1 | proto.usingPreset = |
21 | | proto.preset = function(preset) { |
22 | 23 | if (typeof preset === 'function') { |
23 | 1 | preset(this); |
24 | | } else { |
25 | 22 | try { |
26 | 22 | var modulePath = path.join(this.options.presets, preset); |
27 | 22 | var module = require(modulePath); |
28 | | |
29 | 21 | if (typeof module.load === 'function') { |
30 | 20 | module.load(this); |
31 | | } else { |
32 | 1 | throw new Error('preset ' + modulePath + ' has no load() function'); |
33 | | } |
34 | | } catch (err) { |
35 | 2 | throw new Error('preset ' + modulePath + ' could not be loaded: ' + err.message); |
36 | | } |
37 | | } |
38 | | |
39 | 21 | return this; |
40 | | }; |
41 | | |
42 | | |
43 | | /** |
44 | | * Enable experimental codecs |
45 | | * |
46 | | * @method FfmpegCommand#strict |
47 | | * @category Miscellaneous |
48 | | * @aliases withStrictExperimental |
49 | | * |
50 | | * @return FfmpegCommand |
51 | | */ |
52 | 1 | proto.withStrictExperimental = |
53 | | proto.strict = function() { |
54 | 20 | this._output('-strict', 'experimental'); |
55 | 20 | return this; |
56 | | }; |
57 | | |
58 | | |
59 | | /** |
60 | | * Run flvtool2/flvmeta on output |
61 | | * |
62 | | * @method FfmpegCommand#flvmeta |
63 | | * @category Miscellaneous |
64 | | * @aliases updateFlvMetadata |
65 | | * |
66 | | * @return FfmpegCommand |
67 | | */ |
68 | 1 | proto.updateFlvMetadata = |
69 | | proto.flvmeta = function() { |
70 | 18 | this.options.flvmeta = true; |
71 | 18 | return this; |
72 | | }; |
73 | | }; |
74 | | |
lib/options/output.js
Line | Hits | Source |
---|
1 | | /*jshint node:true*/ |
2 | | 'use strict'; |
3 | | |
4 | | /* |
5 | | *! Output-related methods |
6 | | */ |
7 | | |
8 | 1 | module.exports = function(proto) { |
9 | | /** |
10 | | * Set output duration |
11 | | * |
12 | | * @method FfmpegCommand#duration |
13 | | * @category Output |
14 | | * @aliases withDuration,setDuration |
15 | | * |
16 | | * @param {String|Number} duration duration in seconds or as a '[[hh:]mm:]ss[.xxx]' string |
17 | | * @return FfmpegCommand |
18 | | */ |
19 | 1 | proto.withDuration = |
20 | | proto.setDuration = |
21 | | proto.duration = function(duration) { |
22 | 3 | this._output('-t', duration); |
23 | 3 | return this; |
24 | | }; |
25 | | |
26 | | |
27 | | /** |
28 | | * Set output format |
29 | | * |
30 | | * @method FfmpegCommand#format |
31 | | * @category Output |
32 | | * @aliases toFormat,withOutputFormat,outputFormat |
33 | | * |
34 | | * @param {String} format output format name |
35 | | * @return FfmpegCommand |
36 | | */ |
37 | 1 | proto.toFormat = |
38 | | proto.withOutputFormat = |
39 | | proto.outputFormat = |
40 | | proto.format = function(format) { |
41 | 27 | this._output('-f', format); |
42 | 27 | return this; |
43 | | }; |
44 | | }; |
45 | | |
lib/options/video.js
Line | Hits | Source |
---|
1 | | /*jshint node:true*/ |
2 | | 'use strict'; |
3 | | |
4 | | /* |
5 | | *! Video-related methods |
6 | | */ |
7 | | |
8 | 1 | module.exports = function(proto) { |
9 | | /** |
10 | | * Disable video in the output |
11 | | * |
12 | | * @method FfmpegCommand#noVideo |
13 | | * @category Video |
14 | | * @aliases withNoVideo |
15 | | * |
16 | | * @return FfmpegCommand |
17 | | */ |
18 | 1 | proto.withNoVideo = |
19 | | proto.noVideo = function() { |
20 | 2 | this._video.clear(); |
21 | 2 | this._video('-vn'); |
22 | | |
23 | 2 | return this; |
24 | | }; |
25 | | |
26 | | |
27 | | /** |
28 | | * Specify video codec |
29 | | * |
30 | | * @method FfmpegCommand#videoCodec |
31 | | * @category Video |
32 | | * @aliases withVideoCodec |
33 | | * |
34 | | * @param {String} codec video codec name |
35 | | * @return FfmpegCommand |
36 | | */ |
37 | 1 | proto.withVideoCodec = |
38 | | proto.videoCodec = function(codec) { |
39 | 27 | this._video('-vcodec', codec); |
40 | 27 | return this; |
41 | | }; |
42 | | |
43 | | |
44 | | /** |
45 | | * Specify video bitrate |
46 | | * |
47 | | * @method FfmpegCommand#videoBitrate |
48 | | * @category Video |
49 | | * @aliases withVideoBitrate |
50 | | * |
51 | | * @param {String|Number} bitrate video bitrate in kbps (with an optional 'k' suffix) |
52 | | * @param {Boolean} [constant=false] enforce constant bitrate |
53 | | * @return FfmpegCommand |
54 | | */ |
55 | 1 | proto.withVideoBitrate = |
56 | | proto.videoBitrate = function(bitrate, constant) { |
57 | 22 | bitrate = ('' + bitrate).replace(/k?$/, 'k'); |
58 | | |
59 | 22 | this._video('-b:v', bitrate); |
60 | 22 | if (constant) { |
61 | 1 | this._video( |
62 | | '-maxrate', bitrate, |
63 | | '-minrate', bitrate, |
64 | | '-bufsize', '3M' |
65 | | ); |
66 | | } |
67 | | |
68 | 22 | return this; |
69 | | }; |
70 | | |
71 | | |
72 | | /** |
73 | | * Specify custom video filter(s) |
74 | | * |
75 | | * Can be called both with one or many filters, or a filter array. |
76 | | * |
77 | | * @example |
78 | | * command.videoFilters('filter1'); |
79 | | * |
80 | | * @example |
81 | | * command.videoFilters('filter1', 'filter2'); |
82 | | * |
83 | | * @example |
84 | | * command.videoFilters(['filter1', 'filter2']); |
85 | | * |
86 | | * @method FfmpegCommand#videoFilters |
87 | | * @category Video |
88 | | * @aliases withVideoFilter,withVideoFilters,videoFilter |
89 | | * |
90 | | * @param {String|Array} filters... video filter strings or string array |
91 | | * @return FfmpegCommand |
92 | | */ |
93 | 1 | proto.withVideoFilter = |
94 | | proto.withVideoFilters = |
95 | | proto.videoFilter = |
96 | | proto.videoFilters = function(filters) { |
97 | 4 | if (arguments.length > 1) { |
98 | 2 | filters = [].slice.call(arguments); |
99 | | } |
100 | | |
101 | 4 | if (Array.isArray(filters)) { |
102 | 2 | this._videoFilters.apply(null, filters); |
103 | | } else { |
104 | 2 | this._videoFilters(filters); |
105 | | } |
106 | | |
107 | 4 | return this; |
108 | | }; |
109 | | |
110 | | |
111 | | /** |
112 | | * Specify output FPS |
113 | | * |
114 | | * @method FfmpegCommand#fps |
115 | | * @category Video |
116 | | * @aliases withOutputFps,withOutputFPS,withFpsOutput,withFPSOutput,withFps,withFPS,outputFPS,outputFps,fpsOutput,FPSOutput,FPS |
117 | | * |
118 | | * @param {Number} fps output FPS |
119 | | * @return FfmpegCommand |
120 | | */ |
121 | 1 | proto.withOutputFps = |
122 | | proto.withOutputFPS = |
123 | | proto.withFpsOutput = |
124 | | proto.withFPSOutput = |
125 | | proto.withFps = |
126 | | proto.withFPS = |
127 | | proto.outputFPS = |
128 | | proto.outputFps = |
129 | | proto.fpsOutput = |
130 | | proto.FPSOutput = |
131 | | proto.fps = |
132 | | proto.FPS = function(fps) { |
133 | 19 | this._video('-r', fps); |
134 | 19 | return this; |
135 | | }; |
136 | | |
137 | | |
138 | | /** |
139 | | * Only transcode a certain number of frames |
140 | | * |
141 | | * @method FfmpegCommand#frames |
142 | | * @category Video |
143 | | * @aliases takeFrames,withFrames |
144 | | * |
145 | | * @param {Number} frames frame count |
146 | | * @return FfmpegCommand |
147 | | */ |
148 | 1 | proto.takeFrames = |
149 | | proto.withFrames = |
150 | | proto.frames = function(frames) { |
151 | 5 | this._video('-vframes', frames); |
152 | 5 | return this; |
153 | | }; |
154 | | }; |
155 | | |
lib/options/videosize.js
Line | Hits | Source |
---|
1 | | /*jshint node:true*/ |
2 | | 'use strict'; |
3 | | |
4 | | /* |
5 | | *! Size helpers |
6 | | */ |
7 | | |
8 | | |
9 | | /** |
10 | | * Return filters to pad video to width*height, |
11 | | * |
12 | | * @param {Number} width output width |
13 | | * @param {Number} height output height |
14 | | * @param {Number} aspect video aspect ratio (without padding) |
15 | | * @param {Number} color padding color |
16 | | * @return scale/pad filters |
17 | | * @private |
18 | | */ |
19 | | function getScalePadFilters(width, height, aspect, color) { |
20 | | /* |
21 | | let a be the input aspect ratio, A be the requested aspect ratio |
22 | | |
23 | | if a > A, padding is done on top and bottom |
24 | | if a < A, padding is done on left and right |
25 | | */ |
26 | | |
27 | 10 | return [ |
28 | | /* |
29 | | In both cases, we first have to scale the input to match the requested size. |
30 | | When using computed width/height, we truncate them to multiples of 2 |
31 | | |
32 | | scale= |
33 | | w=if(gt(a, A), width, trunc(height*a/2)*2): |
34 | | h=if(lt(a, A), height, trunc(width/a/2)*2) |
35 | | */ |
36 | | |
37 | | 'scale=\'' + |
38 | | 'w=if(gt(a,' + aspect + '),' + width + ',trunc(' + height + '*a/2)*2):' + |
39 | | 'h=if(lt(a,' + aspect + '),' + height + ',trunc(' + width + '/a/2)*2)\'', |
40 | | |
41 | | /* |
42 | | Then we pad the scaled input to match the target size |
43 | | |
44 | | pad= |
45 | | w=width: |
46 | | h=height: |
47 | | x=if(gt(a, A), 0, (width - iw)/2): |
48 | | y=if(lt(a, A), 0, (height - ih)/2) |
49 | | |
50 | | (here iw and ih refer to the padding input, i.e the scaled output) |
51 | | */ |
52 | | |
53 | | 'pad=\'' + |
54 | | 'w=' + width + ':' + |
55 | | 'h=' + height + ':' + |
56 | | 'x=if(gt(a,' + aspect + '),0,(' + width + '-iw)/2):' + |
57 | | 'y=if(lt(a,' + aspect + '),0,(' + height + '-ih)/2):' + |
58 | | 'color=' + color + '\'' |
59 | | ]; |
60 | | } |
61 | | |
62 | | |
63 | | /** |
64 | | * Recompute size filters |
65 | | * |
66 | | * @param {FfmpegCommand} command |
67 | | * @param {String} key newly-added parameter name ('size', 'aspect' or 'pad') |
68 | | * @param {String} value newly-added parameter value |
69 | | * @return filter string array |
70 | | * @private |
71 | | */ |
72 | | function createSizeFilters(command, key, value) { |
73 | | // Store parameters |
74 | 80 | var data = command._sizeData = command._sizeData || {}; |
75 | 80 | data[key] = value; |
76 | | |
77 | 80 | if (!('size' in data)) { |
78 | | // No size requested, keep original size |
79 | 2 | return []; |
80 | | } |
81 | | |
82 | | // Try to match the different size string formats |
83 | 78 | var fixedSize = data.size.match(/([0-9]+)x([0-9]+)/); |
84 | 78 | var fixedWidth = data.size.match(/([0-9]+)x\?/); |
85 | 78 | var fixedHeight = data.size.match(/\?x([0-9]+)/); |
86 | 78 | var percentRatio = data.size.match(/\b([0-9]{1,3})%/); |
87 | 78 | var width, height, aspect; |
88 | | |
89 | 78 | if (percentRatio) { |
90 | 5 | var ratio = Number(percentRatio[1]) / 100; |
91 | 5 | return ['scale=trunc(iw*' + ratio + '/2)*2:trunc(ih*' + ratio + '/2)*2']; |
92 | 73 | } else if (fixedSize) { |
93 | | // Round target size to multiples of 2 |
94 | 21 | width = Math.round(Number(fixedSize[1]) / 2) * 2; |
95 | 21 | height = Math.round(Number(fixedSize[2]) / 2) * 2; |
96 | | |
97 | 21 | aspect = width / height; |
98 | | |
99 | 21 | if (data.pad) { |
100 | 5 | return getScalePadFilters(width, height, aspect, data.pad); |
101 | | } else { |
102 | | // No autopad requested, rescale to target size |
103 | 16 | return ['scale=' + width + ':' + height]; |
104 | | } |
105 | 52 | } else if (fixedWidth || fixedHeight) { |
106 | 51 | if ('aspect' in data) { |
107 | | // Specified aspect ratio |
108 | 14 | width = fixedWidth ? fixedWidth[1] : Math.round(Number(fixedHeight[1]) * data.aspect); |
109 | 14 | height = fixedHeight ? fixedHeight[1] : Math.round(Number(fixedWidth[1]) / data.aspect); |
110 | | |
111 | | // Round to multiples of 2 |
112 | 14 | width = Math.round(width / 2) * 2; |
113 | 14 | height = Math.round(height / 2) * 2; |
114 | | |
115 | 14 | if (data.pad) { |
116 | 5 | return getScalePadFilters(width, height, data.aspect, data.pad); |
117 | | } else { |
118 | | // No autopad requested, rescale to target size |
119 | 9 | return ['scale=' + width + ':' + height]; |
120 | | } |
121 | | } else { |
122 | | // Keep input aspect ratio |
123 | | |
124 | 37 | if (fixedWidth) { |
125 | 31 | return ['scale=' + (Math.round(Number(fixedWidth[1]) / 2) * 2) + ':trunc(ow/a/2)*2']; |
126 | | } else { |
127 | 6 | return ['scale=trunc(oh*a/2)*2:' + (Math.round(Number(fixedHeight[1]) / 2) * 2)]; |
128 | | } |
129 | | } |
130 | | } else { |
131 | 1 | throw new Error('Invalid size specified: ' + data.size); |
132 | | } |
133 | | } |
134 | | |
135 | | |
136 | | /* |
137 | | *! Video size-related methods |
138 | | */ |
139 | | |
140 | 1 | module.exports = function(proto) { |
141 | | /** |
142 | | * Keep display aspect ratio |
143 | | * |
144 | | * This method is useful when converting an input with non-square pixels to an output format |
145 | | * that does not support non-square pixels. It rescales the input so that the display aspect |
146 | | * ratio is the same. |
147 | | * |
148 | | * @method FfmpegCommand#keepDAR |
149 | | * @category Video size |
150 | | * @aliases keepPixelAspect,keepDisplayAspect,keepDisplayAspectRatio |
151 | | * |
152 | | * @return FfmpegCommand |
153 | | */ |
154 | 1 | proto.keepPixelAspect = // Only for compatibility, this is not about keeping _pixel_ aspect ratio |
155 | | proto.keepDisplayAspect = |
156 | | proto.keepDisplayAspectRatio = |
157 | | proto.keepDAR = function() { |
158 | 1 | return this.videoFilters( |
159 | | 'scale=\'w=if(gt(sar,1),iw*sar,iw):h=if(lt(sar,1),ih/sar,ih)\'', |
160 | | 'setsar=1' |
161 | | ); |
162 | | }; |
163 | | |
164 | | |
165 | | /** |
166 | | * Set output size |
167 | | * |
168 | | * The 'size' parameter can have one of 4 forms: |
169 | | * - 'X%': rescale to xx % of the original size |
170 | | * - 'WxH': specify width and height |
171 | | * - 'Wx?': specify width and compute height from input aspect ratio |
172 | | * - '?xH': specify height and compute width from input aspect ratio |
173 | | * |
174 | | * Note: both dimensions will be truncated to multiples of 2. |
175 | | * |
176 | | * @method FfmpegCommand#size |
177 | | * @category Video size |
178 | | * @aliases withSize,setSize |
179 | | * |
180 | | * @param {String} size size string, eg. '33%', '320x240', '320x?', '?x240' |
181 | | * @return FfmpegCommand |
182 | | */ |
183 | 1 | proto.withSize = |
184 | | proto.setSize = |
185 | | proto.size = function(size) { |
186 | 52 | var filters = createSizeFilters(this, 'size', size); |
187 | | |
188 | 51 | this._sizeFilters.clear(); |
189 | 51 | this._sizeFilters(filters); |
190 | | |
191 | 51 | return this; |
192 | | }; |
193 | | |
194 | | |
195 | | /** |
196 | | * Set output aspect ratio |
197 | | * |
198 | | * @method FfmpegCommand#aspect |
199 | | * @category Video size |
200 | | * @aliases withAspect,withAspectRatio,setAspect,setAspectRatio,aspectRatio |
201 | | * |
202 | | * @param {String|Number} aspect aspect ratio (number or 'X:Y' string) |
203 | | * @return FfmpegCommand |
204 | | */ |
205 | 1 | proto.withAspect = |
206 | | proto.withAspectRatio = |
207 | | proto.setAspect = |
208 | | proto.setAspectRatio = |
209 | | proto.aspect = |
210 | | proto.aspectRatio = function(aspect) { |
211 | 15 | var a = Number(aspect); |
212 | 15 | if (isNaN(a)) { |
213 | 3 | var match = aspect.match(/^(\d+):(\d+)$/); |
214 | 3 | if (match) { |
215 | 2 | a = Number(match[1]) / Number(match[2]); |
216 | | } else { |
217 | 1 | throw new Error('Invalid aspect ratio: ' + aspect); |
218 | | } |
219 | | } |
220 | | |
221 | 14 | var filters = createSizeFilters(this, 'aspect', a); |
222 | | |
223 | 14 | this._sizeFilters.clear(); |
224 | 14 | this._sizeFilters(filters); |
225 | | |
226 | 14 | return this; |
227 | | }; |
228 | | |
229 | | |
230 | | /** |
231 | | * Enable auto-padding the output |
232 | | * |
233 | | * @method FfmpegCommand#autopad |
234 | | * @category Video size |
235 | | * @aliases applyAutopadding,applyAutoPadding,applyAutopad,applyAutoPad,withAutopadding,withAutoPadding,withAutopad,withAutoPad,autoPad |
236 | | * |
237 | | * @param {Boolean} [pad=true] enable/disable auto-padding |
238 | | * @param {String} [color='black'] pad color |
239 | | */ |
240 | 1 | proto.applyAutopadding = |
241 | | proto.applyAutoPadding = |
242 | | proto.applyAutopad = |
243 | | proto.applyAutoPad = |
244 | | proto.withAutopadding = |
245 | | proto.withAutoPadding = |
246 | | proto.withAutopad = |
247 | | proto.withAutoPad = |
248 | | proto.autoPad = |
249 | | proto.autopad = function(pad, color) { |
250 | | // Allow autopad(color) |
251 | 14 | if (typeof pad === 'string') { |
252 | 1 | color = pad; |
253 | 1 | pad = true; |
254 | | } |
255 | | |
256 | | // Allow autopad() and autopad(undefined, color) |
257 | 14 | if (typeof pad === 'undefined') { |
258 | 1 | pad = true; |
259 | | } |
260 | | |
261 | 14 | var filters = createSizeFilters(this, 'pad', pad ? color || 'black' : false); |
262 | | |
263 | 14 | this._sizeFilters.clear(); |
264 | 14 | this._sizeFilters(filters); |
265 | | |
266 | 14 | return this; |
267 | | }; |
268 | | }; |
269 | | |
lib/presets/flashvideo.js
Line | Hits | Source |
---|
1 | | /*jshint node:true */ |
2 | | 'use strict'; |
3 | | |
4 | 1 | exports.load = function(ffmpeg) { |
5 | 18 | ffmpeg |
6 | | .format('flv') |
7 | | .flvmeta() |
8 | | .size('320x?') |
9 | | .videoBitrate('512k') |
10 | | .videoCodec('libx264') |
11 | | .fps(24) |
12 | | .audioBitrate('96k') |
13 | | .audioCodec('aac') |
14 | | .strict() |
15 | | .audioFrequency(22050) |
16 | | .audioChannels(2); |
17 | | }; |
18 | | |
lib/presets/podcast.js
Line | Hits | Source |
---|
1 | | /*jshint node:true */ |
2 | | 'use strict'; |
3 | | |
4 | 1 | exports.load = function(ffmpeg) { |
5 | 1 | ffmpeg |
6 | | .format('m4v') |
7 | | .videoBitrate('512k') |
8 | | .videoCodec('libx264') |
9 | | .size('320x176') |
10 | | .audioBitrate('128k') |
11 | | .audioCodec('aac') |
12 | | .strict() |
13 | | .audioChannels(1) |
14 | | .outputOptions(['-flags', '+loop', '-cmp', '+chroma', '-partitions','+parti4x4+partp8x8+partb8x8', '-flags2', |
15 | | '+mixed_refs', '-me_method umh', '-subq 5', '-bufsize 2M', '-rc_eq \'blurCplx^(1-qComp)\'', |
16 | | '-qcomp 0.6', '-qmin 10', '-qmax 51', '-qdiff 4', '-level 13' ]); |
17 | | }; |
18 | | |
lib/processor.js
Line | Hits | Source |
---|
1 | | /*jshint node:true*/ |
2 | | 'use strict'; |
3 | | |
4 | 1 | var spawn = require('child_process').spawn; |
5 | 1 | var PassThrough = require('stream').PassThrough; |
6 | 1 | var path = require('path'); |
7 | 1 | var fs = require('fs'); |
8 | 1 | var async = require('async'); |
9 | 1 | var utils = require('./utils'); |
10 | | |
11 | | |
12 | | /* |
13 | | *! Processor methods |
14 | | */ |
15 | | |
16 | | |
17 | | /** |
18 | | * @param {FfmpegCommand} command |
19 | | * @param {String|Writable} target |
20 | | * @param {Object} [pipeOptions] |
21 | | * @private |
22 | | */ |
23 | | function _process(command, target, pipeOptions) { |
24 | 19 | var isStream; |
25 | | |
26 | 19 | if (typeof target === 'string') { |
27 | 16 | isStream = false; |
28 | | } else { |
29 | 3 | isStream = true; |
30 | 3 | pipeOptions = pipeOptions || {}; |
31 | | } |
32 | | |
33 | | // Ensure we send 'end' or 'error' only once |
34 | 19 | var ended = false; |
35 | | function emitEnd(err, stdout, stderr) { |
36 | 24 | if (!ended) { |
37 | 19 | ended = true; |
38 | | |
39 | 19 | if (err) { |
40 | 5 | command.emit('error', err, stdout, stderr); |
41 | | } else { |
42 | 14 | command.emit('end', stdout, stderr); |
43 | | } |
44 | | } |
45 | | } |
46 | | |
47 | 19 | command._prepare(function(err, args) { |
48 | 19 | if (err) { |
49 | 1 | return emitEnd(err); |
50 | | } |
51 | | |
52 | 18 | if (isStream) { |
53 | 3 | args.push('pipe:1'); |
54 | | |
55 | 3 | if (command.options.flvmeta) { |
56 | 3 | command.logger.warn('Updating flv metadata is not supported for streams'); |
57 | 3 | command.options.flvmeta = false; |
58 | | } |
59 | | } else { |
60 | 15 | args.push('-y', target); |
61 | | } |
62 | | |
63 | | // Get input stream if any |
64 | 18 | var inputStream = command._inputs.filter(function(input) { |
65 | 18 | return typeof input.source !== 'string'; |
66 | | })[0]; |
67 | | |
68 | | // Run ffmpeg |
69 | 18 | var stdout = null; |
70 | 18 | var stderr = ''; |
71 | 18 | command._spawnFfmpeg( |
72 | | args, |
73 | | |
74 | | { niceness: command.options.niceness }, |
75 | | |
76 | | function processCB(ffmpegProc) { |
77 | 18 | command.ffmpegProc = ffmpegProc; |
78 | 18 | command.emit('start', 'ffmpeg ' + args.join(' ')); |
79 | | |
80 | | // Pipe input stream if any |
81 | 18 | if (inputStream) { |
82 | 2 | inputStream.source.on('error', function(err) { |
83 | 0 | emitEnd(new Error('Input stream error: ' + err.message)); |
84 | 0 | ffmpegProc.kill(); |
85 | | }); |
86 | | |
87 | 2 | inputStream.source.resume(); |
88 | 2 | inputStream.source.pipe(ffmpegProc.stdin); |
89 | | } |
90 | | |
91 | | // Setup timeout if requested |
92 | 18 | var processTimer; |
93 | 18 | if (command.options.timeout) { |
94 | 4 | processTimer = setTimeout(function() { |
95 | 3 | var msg = 'process ran into a timeout (' + command.options.timeout + 's)'; |
96 | | |
97 | 3 | emitEnd(new Error(msg), stdout, stderr); |
98 | 3 | ffmpegProc.kill(); |
99 | | }, command.options.timeout * 1000); |
100 | | } |
101 | | |
102 | 18 | if (isStream) { |
103 | | // Pipe ffmpeg stdout to output stream |
104 | 3 | ffmpegProc.stdout.pipe(target, pipeOptions); |
105 | | |
106 | | // Handle output stream events |
107 | 3 | target.on('close', function() { |
108 | 2 | command.logger.debug('Output stream closed, scheduling kill for ffmpgeg process'); |
109 | | |
110 | | // Don't kill process yet, to give a chance to ffmpeg to |
111 | | // terminate successfully first This is necessary because |
112 | | // under load, the process 'exit' event sometimes happens |
113 | | // after the output stream 'close' event. |
114 | 2 | setTimeout(function() { |
115 | 2 | emitEnd(new Error('Output stream closed')); |
116 | 2 | ffmpegProc.kill(); |
117 | | }, 20); |
118 | | }); |
119 | | |
120 | 3 | target.on('error', function(err) { |
121 | 0 | command.logger.debug('Output stream error, killing ffmpgeg process'); |
122 | 0 | emitEnd(new Error('Output stream error: ' + err.message)); |
123 | 0 | ffmpegProc.kill(); |
124 | | }); |
125 | | } else { |
126 | | // Gather ffmpeg stdout |
127 | 15 | stdout = ''; |
128 | 15 | ffmpegProc.stdout.on('data', function (data) { |
129 | 0 | stdout += data; |
130 | | }); |
131 | | } |
132 | | |
133 | | // Process ffmpeg stderr data |
134 | 18 | command._codecDataSent = false; |
135 | 18 | ffmpegProc.stderr.on('data', function (data) { |
136 | 324 | stderr += data; |
137 | | |
138 | 324 | if (!command._codecDataSent && command.listeners('codecData').length) { |
139 | 11 | utils.extractCodecData(command, stderr); |
140 | | } |
141 | | |
142 | 324 | if (command.listeners('progress').length) { |
143 | 26 | var duration = 0; |
144 | | |
145 | 26 | if (command._ffprobeData && command._ffprobeData.format && command._ffprobeData.format.duration) { |
146 | 21 | duration = Number(command._ffprobeData.format.duration); |
147 | | } |
148 | | |
149 | 26 | utils.extractProgress(command, stderr, duration); |
150 | | } |
151 | | }); |
152 | | }, |
153 | | |
154 | | function endCB(err) { |
155 | 18 | delete command.ffmpegProc; |
156 | | |
157 | 18 | if (err) { |
158 | 4 | emitEnd(err, stdout, stderr); |
159 | | } else { |
160 | 14 | if (command.options.flvmeta) { |
161 | 11 | command._getFlvtoolPath(function(err, flvtool) { |
162 | | // No error possible here, _getFlvtoolPath was called by _prepare |
163 | | |
164 | 11 | spawn(flvtool, ['-U', target]) |
165 | | .on('error', function(err) { |
166 | 0 | emitEnd(new Error('Error running ' + flvtool + ': ' + err.message)); |
167 | | }) |
168 | | .on('exit', function(code, signal) { |
169 | 11 | if (code !== 0 || signal) { |
170 | 0 | emitEnd( |
171 | | new Error(flvtool + ' ' + |
172 | | (signal ? 'received signal ' + signal |
173 | | : 'exited with code ' + code)) |
174 | | ); |
175 | | } else { |
176 | 11 | emitEnd(null, stdout, stderr); |
177 | | } |
178 | | }); |
179 | | }); |
180 | | } else { |
181 | 3 | emitEnd(null, stdout, stderr); |
182 | | } |
183 | | } |
184 | | } |
185 | | ); |
186 | | }); |
187 | | } |
188 | | |
189 | | |
190 | | /** |
191 | | * Run ffprobe asynchronously and store data in command |
192 | | * |
193 | | * @param {FfmpegCommand} command |
194 | | * @private |
195 | | */ |
196 | | function runFfprobe(command) { |
197 | 1 | command.ffprobe(function(err, data) { |
198 | 1 | command._ffprobeData = data; |
199 | | }); |
200 | | } |
201 | | |
202 | | |
203 | 1 | module.exports = function(proto) { |
204 | | /** |
205 | | * Emitted just after ffmpeg has been spawned. |
206 | | * |
207 | | * @event FfmpegCommand#start |
208 | | * @param {String} command ffmpeg command line |
209 | | */ |
210 | | |
211 | | /** |
212 | | * Emitted when ffmpeg reports progress information |
213 | | * |
214 | | * @event FfmpegCommand#progress |
215 | | * @param {Object} progress progress object |
216 | | */ |
217 | | |
218 | | /** |
219 | | * Emitted when ffmpeg reports input codec data |
220 | | * |
221 | | * @event FfmpegCommand#codecData |
222 | | * @param {Object} codecData codec data object |
223 | | */ |
224 | | |
225 | | /** |
226 | | * Emitted when an error happens when preparing or running a command |
227 | | * |
228 | | * @event FfmpegCommand#error |
229 | | * @param {Error} error error |
230 | | * @param {String|null} stdout ffmpeg stdout, unless outputting to a stream |
231 | | * @param {String|null} stderr ffmpeg stderr |
232 | | */ |
233 | | |
234 | | /** |
235 | | * Emitted when a command finishes processing |
236 | | * |
237 | | * @event FfmpegCommand#end |
238 | | * @param {Array|null} [filenames] generated filenames when taking screenshots, null otherwise |
239 | | */ |
240 | | |
241 | | |
242 | | /** |
243 | | * Spawn an ffmpeg process |
244 | | * |
245 | | * The 'options' argument may contain the following keys: |
246 | | * - 'niceness': specify process niceness, ignored on Windows (default: 0) |
247 | | * - 'captureStdout': capture stdout and pass it to 'endCB' as its 2nd argument (default: false) |
248 | | * - 'captureStderr': capture stderr and pass it to 'endCB' as its 3rd argument (default: false) |
249 | | * |
250 | | * The 'processCB' callback, if present, is called as soon as the process is created and |
251 | | * receives a nodejs ChildProcess object. It may not be called at all if an error happens |
252 | | * before spawning the process. |
253 | | * |
254 | | * The 'endCB' callback is called either when an error occurs or when the ffmpeg process finishes. |
255 | | * |
256 | | * @method FfmpegCommand#_spawnFfmpeg |
257 | | * @param {Array} args ffmpeg command line argument list |
258 | | * @param {Object} [options] spawn options (see above) |
259 | | * @param {Function} [processCB] callback called with process object when it has been created |
260 | | * @param {Function} endCB callback with signature (err, stdout, stderr) |
261 | | * @private |
262 | | */ |
263 | 1 | proto._spawnFfmpeg = function(args, options, processCB, endCB) { |
264 | | // Enable omitting options |
265 | 30 | if (typeof options === 'function') { |
266 | 8 | endCB = processCB; |
267 | 8 | processCB = options; |
268 | 8 | options = {}; |
269 | | } |
270 | | |
271 | | // Enable omitting processCB |
272 | 30 | if (typeof endCB === 'undefined') { |
273 | 12 | endCB = processCB; |
274 | 12 | processCB = function() {}; |
275 | | } |
276 | | |
277 | | // Find ffmpeg |
278 | 30 | this._getFfmpegPath(function(err, command) { |
279 | 30 | if (err) { |
280 | 0 | return endCB(err); |
281 | 30 | } else if (!command || command.length === 0) { |
282 | 0 | return endCB(new Error('Cannot find ffmpeg')); |
283 | | } |
284 | | |
285 | | // Apply niceness |
286 | 30 | if (options.niceness && options.niceness !== 0 && !utils.isWindows) { |
287 | 0 | args.unshift('-n', options.niceness, command); |
288 | 0 | command = 'nice'; |
289 | | } |
290 | | |
291 | 30 | var stdout = null; |
292 | 30 | var stdoutClosed = false; |
293 | | |
294 | 30 | var stderr = null; |
295 | 30 | var stderrClosed = false; |
296 | | |
297 | | // Spawn process |
298 | 30 | var ffmpegProc = spawn(command, args, options); |
299 | | |
300 | 30 | if (ffmpegProc.stderr && options.captureStderr) { |
301 | 1 | ffmpegProc.stderr.setEncoding('utf8'); |
302 | | } |
303 | | |
304 | 30 | ffmpegProc.on('error', function(err) { |
305 | 0 | endCB(err); |
306 | | }); |
307 | | |
308 | | // Ensure we wait for captured streams to end before calling endCB |
309 | 30 | var exitError = null; |
310 | | function handleExit(err) { |
311 | 35 | if (err) { |
312 | 4 | exitError = err; |
313 | | } |
314 | | |
315 | 35 | if (processExited && |
316 | | (stdoutClosed || !options.captureStdout) && |
317 | | (stderrClosed || !options.captureStderr)) { |
318 | 30 | endCB(exitError, stdout, stderr); |
319 | | } |
320 | | } |
321 | | |
322 | | // Handle process exit |
323 | 30 | var processExited = false; |
324 | 30 | ffmpegProc.on('exit', function(code, signal) { |
325 | 30 | processExited = true; |
326 | | |
327 | 30 | if (code) { |
328 | 3 | handleExit(new Error('ffmpeg exited with code ' + code)); |
329 | 27 | } else if (signal) { |
330 | 1 | handleExit(new Error('ffmpeg was killed with signal ' + signal)); |
331 | | } else { |
332 | 26 | handleExit(); |
333 | | } |
334 | | }); |
335 | | |
336 | | // Capture stdout if specified |
337 | 30 | if (options.captureStdout) { |
338 | 4 | stdout = ''; |
339 | | |
340 | 4 | ffmpegProc.stdout.on('data', function(data) { |
341 | 11 | stdout += data; |
342 | | }); |
343 | | |
344 | 4 | ffmpegProc.stdout.on('close', function() { |
345 | 4 | stdoutClosed = true; |
346 | 4 | handleExit(); |
347 | | }); |
348 | | } |
349 | | |
350 | | // Capture stderr if specified |
351 | 30 | if (options.captureStderr) { |
352 | 1 | stderr = ''; |
353 | | |
354 | 1 | ffmpegProc.stderr.on('data', function(data) { |
355 | 0 | stderr += data; |
356 | | }); |
357 | | |
358 | 1 | ffmpegProc.stderr.on('close', function() { |
359 | 1 | stderrClosed = true; |
360 | 1 | handleExit(); |
361 | | }); |
362 | | } |
363 | | |
364 | | // Call process callback |
365 | 30 | processCB(ffmpegProc); |
366 | | }); |
367 | | }; |
368 | | |
369 | | |
370 | | /** |
371 | | * Build the argument list for an ffmpeg command |
372 | | * |
373 | | * @method FfmpegCommand#_getArguments |
374 | | * @return argument list |
375 | | * @private |
376 | | */ |
377 | 1 | proto._getArguments = function() { |
378 | 53 | var audioFilters = this._audioFilters.get(); |
379 | 53 | var videoFilters = this._videoFilters.get().concat(this._sizeFilters.get()); |
380 | | |
381 | 53 | return this._inputs.reduce(function(args, input) { |
382 | 54 | var source = (typeof input.source === 'string') ? input.source : '-'; |
383 | | |
384 | 54 | return args.concat( |
385 | | input.before.get(), |
386 | | ['-i', source], |
387 | | input.after.get() |
388 | | ); |
389 | | }, []) |
390 | | .concat( |
391 | | this._audio.get(), |
392 | | audioFilters.length ? ['-filter:a', audioFilters.join(',')] : [], |
393 | | this._video.get(), |
394 | | videoFilters.length ? ['-filter:v', videoFilters.join(',')] : [], |
395 | | this._output.get() |
396 | | ); |
397 | | }; |
398 | | |
399 | | |
400 | | /** |
401 | | * Prepare execution of an ffmpeg command |
402 | | * |
403 | | * Checks prerequisites for the execution of the command (codec/format availability, flvtool...), |
404 | | * then builds the argument list for ffmpeg and pass them to 'callback'. |
405 | | * |
406 | | * @method FfmpegCommand#_prepare |
407 | | * @param {Function} callback callback with signature (err, args) |
408 | | * @param {Boolean} [readMetadata=false] read metadata before processing |
409 | | * @private |
410 | | */ |
411 | 1 | proto._prepare = function(callback, readMetadata) { |
412 | 21 | var self = this; |
413 | | |
414 | 21 | async.waterfall([ |
415 | | // Check codecs and formats |
416 | | function(cb) { |
417 | 21 | self._checkCapabilities(cb); |
418 | | }, |
419 | | |
420 | | // Read metadata if required |
421 | | function(cb) { |
422 | 20 | if (!readMetadata) { |
423 | 18 | return cb(); |
424 | | } |
425 | | |
426 | 2 | self.ffprobe(function(err, data) { |
427 | 2 | if (!err) { |
428 | 2 | self._ffprobeData = data; |
429 | | } |
430 | | |
431 | 2 | cb(); |
432 | | }); |
433 | | }, |
434 | | |
435 | | // Check for flvtool2/flvmeta if necessary |
436 | | function(cb) { |
437 | 20 | if (self.options.flvmeta) { |
438 | 18 | self._getFlvtoolPath(function(err) { |
439 | 18 | cb(err); |
440 | | }); |
441 | | } else { |
442 | 2 | cb(); |
443 | | } |
444 | | }, |
445 | | |
446 | | // Build argument list |
447 | | function(cb) { |
448 | 20 | var args; |
449 | 20 | try { |
450 | 20 | args = self._getArguments(); |
451 | | } catch(e) { |
452 | 0 | return cb(e); |
453 | | } |
454 | | |
455 | 20 | cb(null, args); |
456 | | } |
457 | | ], callback); |
458 | | |
459 | 21 | if (!readMetadata) { |
460 | | // Read metadata as soon as 'progress' listeners are added |
461 | | |
462 | 19 | if (this.listeners('progress').length > 0) { |
463 | | // Read metadata in parallel |
464 | 1 | runFfprobe(this); |
465 | | } else { |
466 | | // Read metadata as soon as the first 'progress' listener is added |
467 | 18 | this.once('newListener', function(event) { |
468 | 0 | if (event === 'progress') { |
469 | 0 | runFfprobe(this); |
470 | | } |
471 | | }); |
472 | | } |
473 | | } |
474 | | }; |
475 | | |
476 | | |
477 | | /** |
478 | | * Execute ffmpeg command and save output to a file |
479 | | * |
480 | | * @method FfmpegCommand#save |
481 | | * @category Processing |
482 | | * @aliases saveToFile |
483 | | * |
484 | | * @param {String} output file path |
485 | | * @return FfmpegCommand |
486 | | */ |
487 | 1 | proto.saveToFile = |
488 | | proto.save = function(output) { |
489 | 16 | _process(this, output); |
490 | | }; |
491 | | |
492 | | |
493 | | /** |
494 | | * Execute ffmpeg command and save output to a stream |
495 | | * |
496 | | * If 'stream' is not specified, a PassThrough stream is created and returned. |
497 | | * 'options' will be used when piping ffmpeg output to the output stream |
498 | | * (@see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options) |
499 | | * |
500 | | * @method FfmpegCommand#pipe |
501 | | * @category Processing |
502 | | * @aliases stream,writeToStream |
503 | | * |
504 | | * @param {stream.Writable} [stream] output stream |
505 | | * @param {Object} [options={}] pipe options |
506 | | * @return Output stream |
507 | | */ |
508 | 1 | proto.writeToStream = |
509 | | proto.pipe = |
510 | | proto.stream = function(stream, options) { |
511 | 3 | if (stream && !('writable' in stream)) { |
512 | 1 | options = stream; |
513 | 1 | stream = undefined; |
514 | | } |
515 | | |
516 | 3 | if (!stream) { |
517 | 1 | if (process.version.match(/v0\.8\./)) { |
518 | 0 | throw new Error('PassThrough stream is not supported on node v0.8'); |
519 | | } |
520 | | |
521 | 1 | stream = new PassThrough(); |
522 | | } |
523 | | |
524 | 3 | _process(this, stream, options); |
525 | 3 | return stream; |
526 | | }; |
527 | | |
528 | | |
529 | | /** |
530 | | * Merge (concatenate) inputs to a single file |
531 | | * |
532 | | * Warning: soon to be deprecated |
533 | | * |
534 | | * @method FfmpegCommand#mergeToFile |
535 | | * @category Processing |
536 | | * |
537 | | * @param {String} targetfile output file path |
538 | | */ |
539 | 1 | proto.mergeToFile = function(targetfile) { |
540 | 1 | var outputfile = path.normalize(targetfile); |
541 | 1 | if(fs.existsSync(outputfile)){ |
542 | 0 | return this.emit('error', new Error('Output file already exists, merge aborted')); |
543 | | } |
544 | | |
545 | 1 | var self = this; |
546 | | |
547 | | // creates intermediate copies of each video. |
548 | | function makeIntermediateFile(_mergeSource,_callback) { |
549 | 3 | var fname = _mergeSource + '.temp.mpg'; |
550 | 3 | var args = self._output.get().concat(['-i', _mergeSource, '-qscale:v', 1, fname]); |
551 | | |
552 | 3 | self._spawnFfmpeg(args, function(err) { |
553 | 3 | _callback(err, fname); |
554 | | }); |
555 | | } |
556 | | |
557 | | // concat all created intermediate copies |
558 | | function concatIntermediates(target, intermediatesList, _callback) { |
559 | 1 | var fname = path.normalize(target) + '.temp.merged.mpg'; |
560 | | |
561 | 1 | var args = [ |
562 | | // avoid too many log messages from ffmpeg |
563 | | '-loglevel', 'panic', |
564 | | '-i', 'concat:' + intermediatesList.join('|'), |
565 | | '-c', 'copy', |
566 | | fname |
567 | | ]; |
568 | | |
569 | 1 | self._spawnFfmpeg(args, {captureStdout:true,captureStderr:true}, function(err) { |
570 | 1 | _callback(err, fname); |
571 | | }); |
572 | | } |
573 | | |
574 | | function quantizeConcat(concatResult, numFiles, _callback) { |
575 | 1 | var args = [ |
576 | | '-i', concatResult, |
577 | | '-qscale:v',numFiles, |
578 | | targetfile |
579 | | ]; |
580 | | |
581 | 1 | self._spawnFfmpeg(args, function(err) { |
582 | 1 | _callback(err); |
583 | | }); |
584 | | } |
585 | | |
586 | | function deleteIntermediateFiles(intermediates, callback) { |
587 | 2 | async.each(intermediates, function(item,cb){ |
588 | 8 | fs.exists(item,function(exists){ |
589 | 8 | if(exists){ |
590 | 4 | fs.unlink(item ,cb); |
591 | | } |
592 | | else{ |
593 | 4 | cb(); |
594 | | } |
595 | | |
596 | | }); |
597 | | }, callback); |
598 | | } |
599 | | |
600 | | function makeProgress() { |
601 | 5 | progress.createdFiles = progress.createdFiles + 1; |
602 | 5 | progress.percent = progress.createdFiles / progress.totalFiles * 100; |
603 | 5 | self.emit('progress', progress); |
604 | | } |
605 | | |
606 | 1 | if (this._inputs.length < 2) { |
607 | 0 | return this.emit('error', new Error('No file added to be merged')); |
608 | | } |
609 | | |
610 | 4 | var mergeList = this._inputs.map(function(input) { return input.source; }); |
611 | | |
612 | 1 | var progress = {frames : 0, |
613 | | currentFps: 0, |
614 | | currentKbps: 0, |
615 | | targetSize: 0, |
616 | | timemark: 0, |
617 | | percent: 0, |
618 | | totalFiles: mergeList.length + 2, |
619 | | createdFiles: 0}; |
620 | | |
621 | 4 | var toDelete = mergeList.map(function(name) { return name + '.temp.mpg'; }); |
622 | 1 | toDelete.push(outputfile + '.temp.merged.mpg'); |
623 | 1 | deleteIntermediateFiles(toDelete); |
624 | | |
625 | 1 | var intermediateFiles = []; |
626 | | |
627 | 1 | async.whilst( |
628 | | function(){ |
629 | 4 | return (mergeList.length !== 0); |
630 | | }, |
631 | | function (callback){ |
632 | 3 | makeIntermediateFile(mergeList.shift(), function(err, createdIntermediateFile) { |
633 | 3 | if(err) { |
634 | 0 | return callback(err); |
635 | | } |
636 | | |
637 | 3 | if(!createdIntermediateFile) { |
638 | 0 | return callback(new Error('Invalid intermediate file')); |
639 | | } |
640 | | |
641 | 3 | intermediateFiles.push(createdIntermediateFile); |
642 | 3 | makeProgress(); |
643 | 3 | callback(); |
644 | | }); |
645 | | }, |
646 | | function(err) { |
647 | 1 | if (err) { |
648 | 0 | return self.emit('error', err); |
649 | | } |
650 | | |
651 | 1 | concatIntermediates(targetfile, intermediateFiles, function(err, concatResult) { |
652 | 1 | if(err) { |
653 | 0 | return self.emit('error', err); |
654 | | } |
655 | | |
656 | 1 | if(!concatResult) { |
657 | 0 | return self.emit('error', new Error('Invalid concat result file')); |
658 | | } |
659 | | |
660 | 1 | makeProgress(); |
661 | 1 | quantizeConcat(concatResult, intermediateFiles.length, function() { |
662 | 1 | makeProgress(); |
663 | | // add concatResult to intermediates list so it can be deleted too. |
664 | 1 | intermediateFiles.push(concatResult); |
665 | 1 | deleteIntermediateFiles(intermediateFiles, function(err) { |
666 | 1 | if (err) { |
667 | 0 | self.emit('error', err); |
668 | | } else { |
669 | 1 | self.emit('end'); |
670 | | } |
671 | | }); |
672 | | }); |
673 | | }); |
674 | | } |
675 | | ); |
676 | | }; |
677 | | |
678 | | |
679 | | /** |
680 | | * Take screenshots |
681 | | * |
682 | | * The 'config' parameter may either be the number of screenshots to take or an object |
683 | | * with the following keys: |
684 | | * - 'count': screenshot count |
685 | | * - 'timemarks': array of screenshot timestamps in seconds (defaults to taking screenshots at regular intervals) |
686 | | * - 'filename': screenshot filename pattern (defaults to 'tn_%ss' or 'tn_%ss_%i' for multiple screenshots) |
687 | | * |
688 | | * The 'filename' option may contain tokens that will be replaced for each screenshot taken: |
689 | | * - '%s': offset in seconds |
690 | | * - '%w': screenshot width |
691 | | * - '%h': screenshot height |
692 | | * - '%r': screenshot resolution (eg. '320x240') |
693 | | * - '%f': input filename |
694 | | * - '%b': input basename (filename w/o extension) |
695 | | * - '%i': index of screenshot in timemark array (can be zero-padded by using it like `%000i`) |
696 | | * |
697 | | * @method FfmpegCommand#takeScreenshots |
698 | | * @category Processing |
699 | | * |
700 | | * @param {Number|Object} config screenshot count or configuration object (see above) |
701 | | * @param {String} [folder='.'] output directory |
702 | | */ |
703 | 1 | proto.takeScreenshots = function(config, folder) { |
704 | 2 | var width, height; |
705 | 2 | var self = this; |
706 | | |
707 | | function _computeSize(size) { |
708 | | // Select video stream with biggest resolution |
709 | 2 | var vstream = self._ffprobeData.streams.reduce(function(max, stream) { |
710 | 2 | if (stream.codec_type !== 'video') return max; |
711 | 2 | return max.width * max.height < stream.width * stream.height ? stream : max; |
712 | | }, { width: 0, height: 0 }); |
713 | | |
714 | 2 | var w = vstream.width; |
715 | 2 | var h = vstream.height; |
716 | 2 | var a = w / h; |
717 | | |
718 | 2 | var fixedSize = size.match(/([0-9]+)x([0-9]+)/); |
719 | 2 | var fixedWidth = size.match(/([0-9]+)x\?/); |
720 | 2 | var fixedHeight = size.match(/\?x([0-9]+)/); |
721 | 2 | var percentRatio = size.match(/\b([0-9]{1,3})%/); |
722 | | |
723 | 2 | if (fixedSize) { |
724 | 0 | width = Number(fixedSize[1]); |
725 | 0 | height = Number(fixedSize[2]); |
726 | 2 | } else if (fixedWidth) { |
727 | 2 | width = Number(fixedWidth[1]); |
728 | 2 | height = width / a; |
729 | 0 | } else if (fixedHeight) { |
730 | 0 | height = Number(fixedHeight[1]); |
731 | 0 | width = height * a; |
732 | | } else { |
733 | 0 | var pc = Number(percentRatio[0]) / 100; |
734 | 0 | width = w * pc; |
735 | 0 | height = h * pc; |
736 | | } |
737 | | } |
738 | | |
739 | | function _zeroPad(number, len) { |
740 | 4 | len = len-String(number).length+2; |
741 | 4 | return new Array(len<0?0:len).join('0')+number; |
742 | | } |
743 | | |
744 | | function _renderOutputName(j, offset) { |
745 | 4 | var result = filename; |
746 | 4 | if(/%0*i/.test(result)) { |
747 | 4 | var numlen = String(result.match(/%(0*)i/)[1]).length; |
748 | 4 | result = result.replace(/%0*i/, _zeroPad(j, numlen)); |
749 | | } |
750 | 4 | result = result.replace('%s', offset); |
751 | 4 | result = result.replace('%w', width); |
752 | 4 | result = result.replace('%h', height); |
753 | 4 | result = result.replace('%r', width+'x'+height); |
754 | 4 | result = result.replace('%f', path.basename(inputfile)); |
755 | 4 | result = result.replace('%b', path.basename(inputfile, path.extname(inputfile))); |
756 | 4 | return result; |
757 | | } |
758 | | |
759 | | function _screenShotInternal() { |
760 | 2 | self._prepare(function(err, args) { |
761 | 2 | if(err) { |
762 | 0 | return self.emit('error', err); |
763 | | } |
764 | | |
765 | 2 | _computeSize(self._sizeData.size); |
766 | | |
767 | 2 | var duration = 0; |
768 | 2 | if (self._ffprobeData && self._ffprobeData.format && self._ffprobeData.format.duration) { |
769 | 2 | duration = Number(self._ffprobeData.format.duration); |
770 | | } |
771 | | |
772 | 2 | if (!duration) { |
773 | 0 | var errString = 'meta data contains no duration, aborting screenshot creation'; |
774 | 0 | return self.emit('error', new Error(errString)); |
775 | | } |
776 | | |
777 | | // check if all timemarks are inside duration |
778 | 2 | if (Array.isArray(timemarks)) { |
779 | 2 | for (var i = 0; i < timemarks.length; i++) { |
780 | | /* convert percentage to seconds */ |
781 | 4 | if( timemarks[i].indexOf('%') > 0 ) { |
782 | 0 | timemarks[i] = (parseInt(timemarks[i], 10) / 100) * duration; |
783 | | } |
784 | 4 | if (parseInt(timemarks[i], 10) > duration) { |
785 | | // remove timemark from array |
786 | 0 | timemarks.splice(i, 1); |
787 | 0 | --i; |
788 | | } |
789 | | } |
790 | | // if there are no more timemarks around, add one at end of the file |
791 | 2 | if (timemarks.length === 0) { |
792 | 0 | timemarks[0] = (duration * 0.9); |
793 | | } |
794 | | } |
795 | | // get positions for screenshots (using duration of file minus 10% to remove fade-in/fade-out) |
796 | 2 | var secondOffset = (duration * 0.9) / screenshotcount; |
797 | | |
798 | | // reset iterator |
799 | 2 | var j = 1; |
800 | | |
801 | 2 | var filenames = []; |
802 | | |
803 | | // use async helper function to generate all screenshots and |
804 | | // fire callback just once after work is done |
805 | 2 | async.until( |
806 | | function() { |
807 | 6 | return j > screenshotcount; |
808 | | }, |
809 | | function(taskcallback) { |
810 | 4 | var offset; |
811 | 4 | if (Array.isArray(timemarks)) { |
812 | | // get timemark for current iteration |
813 | 4 | offset = timemarks[(j - 1)]; |
814 | | } else { |
815 | 0 | offset = secondOffset * j; |
816 | | } |
817 | | |
818 | 4 | var fname = _renderOutputName(j, offset) + (fileextension ? fileextension : '.jpg'); |
819 | 4 | var target = path.join(folder, fname); |
820 | | |
821 | | // build screenshot command |
822 | 4 | var allArgs = [ |
823 | | '-ss', Math.floor(offset * 100) / 100 |
824 | | ] |
825 | | .concat(args) |
826 | | .concat([ |
827 | | '-vframes', '1', |
828 | | '-an', |
829 | | '-vcodec', 'mjpeg', |
830 | | '-f', 'rawvideo', |
831 | | '-y', target |
832 | | ]); |
833 | | |
834 | 4 | j++; |
835 | | |
836 | 4 | self._spawnFfmpeg(allArgs, taskcallback); |
837 | 4 | filenames.push(fname); |
838 | | }, |
839 | | function(err) { |
840 | 2 | if (err) { |
841 | 0 | self.emit('error', err); |
842 | | } else { |
843 | 2 | self.emit('end', filenames); |
844 | | } |
845 | | } |
846 | | ); |
847 | | }, true); |
848 | | } |
849 | | |
850 | 2 | var timemarks, screenshotcount, filename, fileextension; |
851 | 2 | if (typeof config === 'object') { |
852 | | // use json object as config |
853 | 2 | if (config.count) { |
854 | 2 | screenshotcount = config.count; |
855 | | } |
856 | 2 | if (config.timemarks) { |
857 | 2 | timemarks = config.timemarks; |
858 | | } |
859 | 2 | if (config.fileextension){ |
860 | 0 | fileextension = config.fileextension; |
861 | | } |
862 | | } else { |
863 | | // assume screenshot count as parameter |
864 | 0 | screenshotcount = config; |
865 | 0 | timemarks = null; |
866 | | } |
867 | | |
868 | 2 | if (!this._sizeData || !this._sizeData.size) { |
869 | 0 | throw new Error('Size must be specified'); |
870 | | } |
871 | | |
872 | 2 | var inputfile = this._currentInput.source; |
873 | | |
874 | 2 | filename = config.filename || 'tn_%ss'; |
875 | 2 | if(!/%0*i/.test(filename) && Array.isArray(timemarks) && timemarks.length > 1 ) { |
876 | | // if there are multiple timemarks but no %i in filename add one |
877 | | // so we won't overwrite the same thumbnail with each timemark |
878 | 1 | filename += '_%i'; |
879 | | } |
880 | 2 | folder = folder || '.'; |
881 | | |
882 | | // check target folder |
883 | 2 | fs.exists(folder, function(exists) { |
884 | 2 | if (!exists) { |
885 | 2 | fs.mkdir(folder, '0755', function(err) { |
886 | 2 | if (err !== null) { |
887 | 0 | self.emit('error', err); |
888 | | } else { |
889 | 2 | _screenShotInternal(); |
890 | | } |
891 | | }); |
892 | | } else { |
893 | 0 | _screenShotInternal(); |
894 | | } |
895 | | }); |
896 | | }; |
897 | | |
898 | | |
899 | | /** |
900 | | * Renice current and/or future ffmpeg processes |
901 | | * |
902 | | * Ignored on Windows platforms. |
903 | | * |
904 | | * @method FfmpegCommand#renice |
905 | | * @category Processing |
906 | | * |
907 | | * @param {Number} [niceness=0] niceness value between -20 (highest priority) and 20 (lowest priority) |
908 | | * @return FfmpegCommand |
909 | | */ |
910 | 1 | proto.renice = function(niceness) { |
911 | 2 | if (!utils.isWindows) { |
912 | 2 | niceness = niceness || 0; |
913 | | |
914 | 2 | if (niceness < -20 || niceness > 20) { |
915 | 1 | this.logger.warn('Invalid niceness value: ' + niceness + ', must be between -20 and 20'); |
916 | | } |
917 | | |
918 | 2 | niceness = Math.min(20, Math.max(-20, niceness)); |
919 | 2 | this.options.niceness = niceness; |
920 | | |
921 | 2 | if (this.ffmpegProc) { |
922 | 1 | var logger = this.logger; |
923 | 1 | var pid = this.ffmpegProc.pid; |
924 | 1 | var renice = spawn('renice', [niceness, '-p', pid]); |
925 | | |
926 | 1 | renice.on('error', function(err) { |
927 | 0 | logger.warn('could not renice process ' + pid + ': ' + err.message); |
928 | | }); |
929 | | |
930 | 1 | renice.on('exit', function(code, signal) { |
931 | 1 | if (code) { |
932 | 0 | logger.warn('could not renice process ' + pid + ': renice exited with ' + code); |
933 | 1 | } else if (signal) { |
934 | 0 | logger.warn('could not renice process ' + pid + ': renice was killed by signal ' + signal); |
935 | | } else { |
936 | 1 | logger.info('successfully reniced process ' + pid + ' to ' + niceness + ' niceness'); |
937 | | } |
938 | | }); |
939 | | } |
940 | | } |
941 | | |
942 | 2 | return this; |
943 | | }; |
944 | | |
945 | | |
946 | | /** |
947 | | * Kill current ffmpeg process, if any |
948 | | * |
949 | | * @method FfmpegCommand#kill |
950 | | * @category Processing |
951 | | * |
952 | | * @param {String} [signal=SIGKILL] signal name |
953 | | * @return FfmpegCommand |
954 | | */ |
955 | 1 | proto.kill = function(signal) { |
956 | 3 | if (!this.ffmpegProc) { |
957 | 0 | this.options.logger.warn('No running ffmpeg process, cannot send signal'); |
958 | | } else { |
959 | 3 | this.ffmpegProc.kill(signal || 'SIGKILL'); |
960 | | } |
961 | | |
962 | 3 | return this; |
963 | | }; |
964 | | }; |
965 | | |
lib/utils.js
Line | Hits | Source |
---|
1 | | /*jshint node:true*/ |
2 | | 'use strict'; |
3 | | |
4 | 1 | var exec = require('child_process').exec; |
5 | 1 | var isWindows = require('os').platform().match(/win(32|64)/); |
6 | | |
7 | 1 | var whichCache = {}; |
8 | | |
9 | | /** |
10 | | * Parse progress line from ffmpeg stderr |
11 | | * |
12 | | * @param {String} line progress line |
13 | | * @return progress object |
14 | | * @private |
15 | | */ |
16 | | function parseProgressLine(line) { |
17 | 26 | var progress = {}; |
18 | | |
19 | | // Remove all spaces after = and trim |
20 | 26 | line = line.replace(/=\s+/g, '=').trim(); |
21 | 26 | var progressParts = line.split(' '); |
22 | | |
23 | | // Split every progress part by "=" to get key and value |
24 | 26 | for(var i = 0; i < progressParts.length; i++) { |
25 | 110 | var progressSplit = progressParts[i].split('=', 2); |
26 | 110 | var key = progressSplit[0]; |
27 | 110 | var value = progressSplit[1]; |
28 | | |
29 | | // This is not a progress line |
30 | 110 | if(typeof value === 'undefined') |
31 | 14 | return null; |
32 | | |
33 | 96 | progress[key] = value; |
34 | | } |
35 | | |
36 | 12 | return progress; |
37 | | } |
38 | | |
39 | | |
40 | 1 | var utils = module.exports = { |
41 | | isWindows: isWindows, |
42 | | |
43 | | /** |
44 | | * Create an argument list |
45 | | * |
46 | | * Returns a function that adds new arguments to the list. |
47 | | * It also has the following methods: |
48 | | * - clear() empties the argument list |
49 | | * - get() returns the argument list |
50 | | * - find(arg, count) finds 'arg' in the list and return the following 'count' items, or undefined if not found |
51 | | * - remove(arg, count) remove 'arg' in the list as well as the following 'count' items |
52 | | * |
53 | | * @private |
54 | | */ |
55 | | args: function() { |
56 | 1434 | var list = []; |
57 | 1434 | var argfunc = function() { |
58 | 326 | if (arguments.length === 1 && Array.isArray(arguments[0])) { |
59 | 90 | list = list.concat(arguments[0]); |
60 | | } else { |
61 | 236 | list = list.concat([].slice.call(arguments)); |
62 | | } |
63 | | }; |
64 | | |
65 | 1434 | argfunc.clear = function() { |
66 | 83 | list = []; |
67 | | }; |
68 | | |
69 | 1434 | argfunc.get = function() { |
70 | 485 | return list; |
71 | | }; |
72 | | |
73 | 1434 | argfunc.find = function(arg, count) { |
74 | 95 | var index = list.indexOf(arg); |
75 | 95 | if (index !== -1) { |
76 | 69 | return list.slice(index + 1, index + 1 + (count || 0)); |
77 | | } |
78 | | }; |
79 | | |
80 | 1434 | argfunc.remove = function(arg, count) { |
81 | 0 | var index = list.indexOf(arg); |
82 | 0 | if (index !== -1) { |
83 | 0 | list.splice(index, (count || 0) + 1); |
84 | | } |
85 | | }; |
86 | | |
87 | 1434 | return argfunc; |
88 | | }, |
89 | | |
90 | | |
91 | | /** |
92 | | * Search for an executable |
93 | | * |
94 | | * Uses 'which' or 'where' depending on platform |
95 | | * |
96 | | * @param {String} name executable name |
97 | | * @param {Function} callback callback with signature (err, path) |
98 | | * @private |
99 | | */ |
100 | | which: function(name, callback) { |
101 | 9 | if (name in whichCache) { |
102 | 6 | return callback(null, whichCache[name]); |
103 | | } |
104 | | |
105 | 3 | var cmd = 'which ' + name; |
106 | 3 | if (isWindows) { |
107 | 0 | cmd = 'where ' + name + '.exe'; |
108 | | } |
109 | | |
110 | 3 | exec(cmd, function(err, stdout) { |
111 | 3 | if (err) { |
112 | | // Treat errors as not found |
113 | 0 | callback(null, whichCache[name] = ''); |
114 | | } else { |
115 | 3 | callback(null, whichCache[name] = stdout.replace(/\n$/, '')); |
116 | | } |
117 | | }); |
118 | | }, |
119 | | |
120 | | |
121 | | /** |
122 | | * Convert a [[hh:]mm:]ss[.xxx] timemark into seconds |
123 | | * |
124 | | * @param {String} timemark timemark string |
125 | | * @return Number |
126 | | * @private |
127 | | */ |
128 | | timemarkToSeconds: function(timemark) { |
129 | 12 | if(timemark.indexOf(':') === -1 && timemark.indexOf('.') >= 0) |
130 | 0 | return Number(timemark); |
131 | | |
132 | 12 | var parts = timemark.split(':'); |
133 | | |
134 | | // add seconds |
135 | 12 | var secs = Number(parts.pop()); |
136 | | |
137 | 12 | if (parts.length) { |
138 | | // add minutes |
139 | 12 | secs += Number(parts.pop()) * 60; |
140 | | } |
141 | | |
142 | 12 | if (parts.length) { |
143 | | // add hours |
144 | 12 | secs += Number(parts.pop()) * 3600; |
145 | | } |
146 | | |
147 | 12 | return secs; |
148 | | }, |
149 | | |
150 | | |
151 | | /** |
152 | | * Extract codec data from ffmpeg stderr and emit 'codecData' event if appropriate |
153 | | * |
154 | | * @param {FfmpegCommand} command event emitter |
155 | | * @param {String} stderr ffmpeg stderr output |
156 | | * @private |
157 | | */ |
158 | | extractCodecData: function(command, stderr) { |
159 | 11 | var format= /Input #[0-9]+, ([^ ]+),/.exec(stderr); |
160 | 11 | var dur = /Duration\: ([^,]+)/.exec(stderr); |
161 | 11 | var audio = /Audio\: (.*)/.exec(stderr); |
162 | 11 | var video = /Video\: (.*)/.exec(stderr); |
163 | 11 | var codecObject = { format: '', audio: '', video: '', duration: '' }; |
164 | | |
165 | 11 | if (format && format.length > 1) { |
166 | 8 | codecObject.format = format[1]; |
167 | | } |
168 | | |
169 | 11 | if (dur && dur.length > 1) { |
170 | 8 | codecObject.duration = dur[1]; |
171 | | } |
172 | | |
173 | 11 | if (audio && audio.length > 1) { |
174 | 7 | audio = audio[1].split(', '); |
175 | 7 | codecObject.audio = audio[0]; |
176 | 7 | codecObject.audio_details = audio; |
177 | | } |
178 | 11 | if (video && video.length > 1) { |
179 | 7 | video = video[1].split(', '); |
180 | 7 | codecObject.video = video[0]; |
181 | 7 | codecObject.video_details = video; |
182 | | } |
183 | | |
184 | 11 | var codecInfoPassed = /Press (\[q\]|ctrl-c) to stop/.test(stderr); |
185 | 11 | if (codecInfoPassed) { |
186 | 1 | command.emit('codecData', codecObject); |
187 | 1 | command._codecDataSent = true; |
188 | | } |
189 | | }, |
190 | | |
191 | | |
192 | | /** |
193 | | * Extract progress data from ffmpeg stderr and emit 'progress' event if appropriate |
194 | | * |
195 | | * @param {FfmpegCommand} command event emitter |
196 | | * @param {Number} [duration=0] expected output duration in seconds |
197 | | */ |
198 | | extractProgress: function(command, stderr, duration) { |
199 | 26 | var lines = stderr.split(/\r\n|\r|\n/g); |
200 | 26 | var lastline = lines[lines.length - 2]; |
201 | 26 | var progress; |
202 | | |
203 | 26 | if (lastline) { |
204 | 26 | progress = parseProgressLine(lastline); |
205 | | } |
206 | | |
207 | 26 | if (progress) { |
208 | | // build progress report object |
209 | 12 | var ret = { |
210 | | frames: parseInt(progress.frame, 10), |
211 | | currentFps: parseInt(progress.fps, 10), |
212 | | currentKbps: parseFloat(progress.bitrate.replace('kbits/s', '')), |
213 | | targetSize: parseInt(progress.size, 10), |
214 | | timemark: progress.time |
215 | | }; |
216 | | |
217 | | // calculate percent progress using duration |
218 | 12 | if (duration && duration > 0) { |
219 | 12 | ret.percent = (utils.timemarkToSeconds(ret.timemark) / duration) * 100; |
220 | | } |
221 | | |
222 | 12 | command.emit('progress', ret); |
223 | | } |
224 | | } |
225 | | }; |
226 | | |
make[1]: quittant le répertoire « /home/niko/dev/forks/node-fluent-ffmpeg »