Skip to content

Commit

Permalink
Add availableEncoders and check for experimental codecs before running
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Joyard committed Jul 5, 2014
1 parent 7b92b70 commit 902471d
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 49 deletions.
98 changes: 62 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1182,30 +1182,35 @@ It will contain information about the container (as a `format` key) and an array

### Querying ffmpeg capabilities

fluent-ffmpeg enables you to query your installed ffmpeg version for supported formats, codecs and filters.
fluent-ffmpeg enables you to query your installed ffmpeg version for supported formats, codecs, encoders and filters.

```js

var Ffmpeg = require('fluent-ffmpeg');

Ffmpeg.getAvailableFormats(function(err, formats) {
console.log("Available formats:");
console.dir(formats);
console.log('Available formats:');
console.dir(formats);
});

Ffmpeg.getAvailableCodecs(function(err, codecs) {
console.log("Available codecs:");
console.dir(codecs);
console.log('Available codecs:');
console.dir(codecs);
});

Ffmpeg.getAvailableEncoders(function(err, encoders) {
console.log('Available encoders:');
console.dir(encoders);
});

Ffmpeg.getAvailableFilters(function(err, filters) {
console.log("Available filters:");
console.dir(filters);
console.log("Available filters:");
console.dir(filters);
});

// Those methods can also be called on commands
new Ffmpeg({ source: "/path/to/file.avi "})
.getAvailableCodecs(...);
new Ffmpeg({ source: '/path/to/file.avi' })
.getAvailableCodecs(...);
```

These methods pass an object to their callback with keys for each available format, codec or filter.
Expand All @@ -1214,13 +1219,13 @@ The returned object for formats looks like:

```js
{
...
mp4: {
description: 'MP4 (MPEG-4 Part 14)',
canDemux: false,
canMux: true
},
...
...
mp4: {
description: 'MP4 (MPEG-4 Part 14)',
canDemux: false,
canMux: true
},
...
}
```

Expand All @@ -1231,17 +1236,17 @@ The returned object for codecs looks like:

```js
{
...
mp3: {
type: 'audio',
description: 'MP3 (MPEG audio layer 3)',
canDecode: true,
canEncode: true,
intraFrameOnly: false,
isLossy: true,
isLossless: false
},
...
...
mp3: {
type: 'audio',
description: 'MP3 (MPEG audio layer 3)',
canDecode: true,
canEncode: true,
intraFrameOnly: false,
isLossy: true,
isLossless: false
},
...
}
```

Expand All @@ -1258,19 +1263,40 @@ Depending on your ffmpeg version (or if you use avconv instead) other keys may b

With some ffmpeg/avcodec versions, the description includes encoder/decoder mentions in the form "Foo codec (decoders: libdecodefoo) (encoders: libencodefoo)". In this case you will want to use those encoders/decoders instead (the codecs object returned by `getAvailableCodecs` will also include them).

The returned object for encoders looks like:

```js
{
...
libmp3lame: {
type: 'audio',
description: 'MP3 (MPEG audio layer 3) (codec mp3)',
frameMT: false,
sliceMT: false,
experimental: false,
drawHorizBand: false,
directRendering: false
},
...
}
```

* `type` indicates the encoder type, either "audio", "video" or "subtitle"
* `experimental` indicates whether the encoder is experimental. When using such a codec, fluent-ffmpeg automatically adds the '-strict experimental' flag.

The returned object for filters looks like:

```js
{
...
scale: {
description: 'Scale the input video to width:height size and/or convert the image format.',
input: 'video',
multipleInputs: false,
output: 'video',
multipleOutputs: false
},
...
...
scale: {
description: 'Scale the input video to width:height size and/or convert the image format.',
input: 'video',
multipleInputs: false,
output: 'video',
multipleOutputs: false
},
...
}
```

Expand Down
70 changes: 65 additions & 5 deletions lib/capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var avCodecRegexp = /^\s*([D ])([E ])([VAS])([S ])([D ])([T ]) ([^ ]+) +(.*)$/;
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 formatRegexp = /^\s*([D ])([E ]) ([^ ]+) +(.*)$/;
var lineBreakRegexp = /\r\n|\r|\n/;
var filterRegexp = /^(?: [T\.][S\.][C\.] )?([^ ]+) +(AA?|VV?|\|)->(AA?|VV?|\|) +(.*)$/;
Expand Down Expand Up @@ -423,6 +424,65 @@ module.exports = function(proto) {
};


/**
* A callback passed to {@link FfmpegCommand#availableEncoders}.
*
* @callback FfmpegCommand~encodersCallback
* @param {Error|null} err error object or null if no error happened
* @param {Object} encoders encoders object with encoder names as keys and the following
* properties for each encoder:
* @param {String} encoders.description codec description
* @param {Boolean} encoders.type "audio", "video" or "subtitle"
* @param {Boolean} encoders.frameMT whether the encoder is able to do frame-level multithreading
* @param {Boolean} encoders.sliceMT whether the encoder is able to do slice-level multithreading
* @param {Boolean} encoders.experimental whether the encoder is experimental
* @param {Boolean} encoders.drawHorizBand whether the encoder supports draw_horiz_band
* @param {Boolean} encoders.directRendering whether the encoder supports direct encoding method 1
*/

/**
* Query ffmpeg for available encoders
*
* @method FfmpegCommand#availableEncoders
* @category Capabilities
* @aliases getAvailableEncoders
*
* @param {FfmpegCommand~encodersCallback} callback callback function
*/
proto.availableEncoders =
proto.getAvailableEncoders = function(callback) {
if ('encoders' in cache) {
return callback(null, cache.encoders);
}

this._spawnFfmpeg(['-encoders'], { captureStdout: true }, function(err, stdout) {
if (err) {
return callback(err);
}

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}.
*
Expand Down Expand Up @@ -540,18 +600,18 @@ module.exports = function(proto) {

// Get available codecs
function(cb) {
self.availableCodecs(cb);
self.availableEncoders(cb);
},

// Check whether specified codecs are available
function(codecs, 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 codecs) || codecs[acodec[0]].type !== 'audio' || !(codecs[acodec[0]].canEncode)) {
if (!(acodec[0] in encoders) || encoders[acodec[0]].type !== 'audio') {
cdcs.push(acodec[0]);
}
}
Expand All @@ -569,7 +629,7 @@ module.exports = function(proto) {
unavailable = self._outputs.reduce(function(cdcs, output) {
var vcodec = output.video.find('-vcodec', 1);
if (vcodec && vcodec[0] !== 'copy') {
if (!(vcodec[0] in codecs) || codecs[vcodec[0]].type !== 'video' || !(codecs[vcodec[0]].canEncode)) {
if (!(vcodec[0] in encoders) || encoders[vcodec[0]].type !== 'video') {
cdcs.push(vcodec[0]);
}
}
Expand Down
4 changes: 0 additions & 4 deletions lib/options/audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ module.exports = function(proto) {
proto.audioCodec = function(codec) {
this._currentOutput.audio('-acodec', codec);

if (codec === 'aac' || codec === 'vorbis') {
this._currentOutput.audio('-strict', 'experimental');
}

return this;
};

Expand Down
18 changes: 18 additions & 0 deletions lib/processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,24 @@ module.exports = function(proto) {
}

cb(null, args);
},

// Add "-strict experimental" option where needed
function(args, cb) {
self.availableEncoders(function(err, encoders) {
for (var i = 0; i < args.length; i++) {
if (args[i] === '-acodec' || args[i] === '-vcodec') {
i++;

if ((args[i] in encoders) && encoders[args[i]].experimental) {
args.splice(i + 1, 0, '-strict', 'experimental');
i += 2;
}
}
}

cb(null, args);
});
}
], callback);

Expand Down
7 changes: 7 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ var utils = module.exports = {
*/
args: function() {
var list = [];

// Append argument(s) to the list
var argfunc = function() {
if (arguments.length === 1 && Array.isArray(arguments[0])) {
list = list.concat(arguments[0]);
Expand All @@ -80,28 +82,33 @@ var utils = module.exports = {
}
};

// Clear argument list
argfunc.clear = function() {
list = [];
};

// Return argument list
argfunc.get = function() {
return list;
};

// Find argument 'arg' in list, and if found, return an array of the 'count' items that follow it
argfunc.find = function(arg, count) {
var index = list.indexOf(arg);
if (index !== -1) {
return list.slice(index + 1, index + 1 + (count || 0));
}
};

// Find argument 'arg' in list, and if found, remove it as well as the 'count' items that follow it
argfunc.remove = function(arg, count) {
var index = list.indexOf(arg);
if (index !== -1) {
list.splice(index, (count || 0) + 1);
}
};

// Clone argument list
argfunc.clone = function() {
var cloned = utils.args();
cloned(list);
Expand Down
5 changes: 2 additions & 3 deletions test/args.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ describe('Command', function() {
testhelper.logArgError(err);
assert.ok(!err);

args.length.should.equal(44); // on a side note: it's 42 args by coincidence ;)
// on a side note: not anymore, sorry :(
args.length.should.equal(42);
done();
});
});
Expand All @@ -75,7 +74,7 @@ describe('Command', function() {
new Ffmpeg({ source: this.testfile, nolog: true, preset: path.join(__dirname, 'assets', 'presets') })
.usingPreset('custompreset')
._test_getArgs(function(args) {
args.length.should.equal(44);
args.length.should.equal(42);

done();
});
Expand Down
20 changes: 20 additions & 0 deletions test/capabilities.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@ describe('Capabilities', function() {
});
});

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');
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');

done();
});
});

it('should enable querying for available formats', function(done) {
new Ffmpeg({ source: '' }).getAvailableFormats(function(err, formats) {
testhelper.logError(err);
Expand Down
2 changes: 1 addition & 1 deletion test/processor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ describe('Processor', function() {
});
});

describe.only('Outputs', function() {
describe('Outputs', function() {
it('should create multiple outputs', function(done) {
this.timeout(30000);

Expand Down

0 comments on commit 902471d

Please sign in to comment.