Fluent ffmpeg-API for node.js
This library abstracts the complex command-line usage of ffmpeg into a fluent, easy to use node.js module. In order to be able to use this module, make sure you have ffmpeg installed on your system (including all necessary encoding libraries like libmp3lame or libx264).
Now including input streaming support (means you can convert on-the-fly using an input- and an outputstream)!
Installation
Via npm:
$ npm install fluent-ffmpeg
Or as a submodule:
$ git submodule add git://github.com/schaermu/node-fluent-ffmpeg.git vendor/fluent-ffmpeg
Tests
To run unit tests, first make sure you installed npm dependencies (run npm install
).
$ make test
If you want to re-generate the test coverage report (filed under test/coverage.html), run
$ make test-cov
Make sure your ffmpeg installation is up-to-date to prevent strange assertion errors because of missing codecs/bugfixes.
Usage
You will find a lot of usage examples (including a real-time streaming example using flowplayer and express!) in the examples
folder.
Creating an FFmpeg command
The fluent-ffmpeg module returns a constructor that you can use to instanciate FFmpeg commands. You have to supply a configuration object containing at least the input source.
var FFmpeg = require('fluent-ffmpeg');
var command = FFmpeg({ source: '/path/to/file.avi' });
The input file can also be an image or an image pattern.
var imageCommand = FFmpeg({ source: '/path/to/file.png' });
var patternCommand = FFmpeg({ source: '/path/to/file%03d.png' });
You can also pass a Readable stream instead of a source path.
var inStream = fs.createReadStream('/path/to/file.avi');
var command = new FFmpeg({ source: inStream });
Additional options can be supplied when creating a command.
var command = new FFmpeg({
source: '/path/to/file.avi',
preset: './presets',
timeout: 30,
priority: -5,
logger: new winston.Logger(...),
nolog: false
});
Supplying FFmpeg options
FFmpeg commands have several methods to pass options to FFmpeg. All these methods return the command object, so that calls can be chained. Here are all the available methods:
new FFmpeg({ source: '/path/to/video.avi' })
.withNoVideo()
.withNoAudio()
.addInput('/path/to/audiotrack.mp3')
.withVideoCodec('libx624')
.withVideoBitrate('650k')
.withVideoBitrate('650k', true)
.withSize('320x240')
.withSize('50%')
.withSize('320x?')
.withSize('?x240')
.applyAutopadding(true)
.applyAutopadding(true, 'white')
.withAspectRatio(1.33)
.keepPixelAspect(true)
.withFpsInput(24)
.withFps(24)
.withFpsOutput(24)
.loop()
.loop(120)
.setStartTime(120)
.setStartTime('2:00')
.setDuration(120)
.setDuration('2:00')
.takeFrames(250)
.withAudioCodec('libmp3lame')
.withAudioBitrate('128k')
.withAudioChannels(2)
.withAudioFrequency(48000)
.withAudioQuality(5)
.fromFormat('avi')
.toFormat('webm')
.withAudioFilter('equalizer=f=1000:width_type=h:width=200:g=-10')
.withAudioFilter('pan=1:c0=0.9*c0+0.1*c1')
.withVideoFilter('size=iw*1.5:ih/2')
.withVideoFilter('drawtext=\'fontfile=FreeSans.ttf:text=Hello\'')
.withStrictExperimental()
.addInputOption('-f', 'avi')
.addInputOptions(['-f avi', '-ss 2:30'])
.addOption('-crf', '23')
.addOptions(['-crf 23', '--preset ultrafast']);
Setting event handlers
You can set events listeners on a command.
var command = new FFmpeg({ source: '/path/to/video.avi' })
.on('start', function(commandLine) {
console.log('Spawned FFmpeg with command: ' + commandLine);
})
.on('codecData', function(data) {
console.log('Input is ' + data.audio + ' audio with ' + data.video + ' video');
})
.on('progress', function(progress) {
console.log('Processing: ' + progress.percent + '% done');
})
.on('error', function(err) {
console.log('Cannot process video: ' + err.message);
})
.on('end', function() {
console.log('Processing finished successfully');
});
Note that you should always set a listener for the error
event. If you have not set any listener and an error happens, your nodejs process will end.
Starting FFmpeg processing
Saving output to a file
Use the saveToFile
method on a command to start processing and save the output to a file.
new FFmpeg({ source: })
.withVideoCodec('libx264')
.withAudioCodec('libmp3lame')
.withSize('320x240')
.on('error', function(err) {
console.log('An error occurred: ' + err.message);
})
.on('end', function() {
console.log('Processing finished !');
})
.saveToFile('/path/to/output.mp4');
Piping output to a writable stream
Use the writeToStream
method on a command to start processing and pipe the output to a writable stream. You can supply pipe options as a second argument to writeToStream
.
var outStream = fs.createWriteStream('/path/to/output.mp4');
new FFmpeg({ source: '/path/to/video.avi' })
.withVideoCodec('libx264')
.withAudioCodec('libmp3lame')
.withSize('320x240')
.on('error', function(err) {
console.log('An error occurred: ' + err.message);
})
.on('end', function() {
console.log('Processing finished !');
})
.writeToStream(outStream, { end: true });
Using presets
Preset functions
You can define a preset as a function that takes an FfmpegCommand
as an argument and calls method on it, and then pass it to usePreset
.
function myPreset(command) {
command
.withAudioCodec('libmp3lame')
.withVideoCodec('libx264')
.withSize('320x240');
}
new Ffmpeg({ source: '/path/to/video.avi' })
.usingPreset(myPreset)
.saveToFile('/path/to/converted.mp4');
Preset modules
Preset modules are located in fluent-ffmpeg lib/presets
directory. To use a preset, call the usingPreset
method on a command.
new FFmpeg({ source: '/path/to/video.avi' })
.usingPreset('flashvideo')
.saveToFile('/path/to/converted.flv');
Preset modules should export a load
method, which will receive the command object to alter.
exports.load = function(command) {
command
.withAudioCodec('libmp3lame')
.withVideoCodec('libx264')
.withSize('320x240');
};
fluent-ffmpeg comes with the following preset modules preinstalled:
Merging inputs
Use the mergeAdd
and mergeToFile
methods on a command to concatenate multiple inputs to a single output file. The mergeToFile
needs a temporary folder as its second argument.
new FFmpeg({ source: '/path/to/part1.avi' })
.mergeAdd('/path/to/part2.avi')
.mergeAdd('/path/to/part2.avi')
.on('error', function(err) {
console.log('An error occurred: ' + err.message);
})
.on('end', function() {
console.log('Merging finished !');
})
.mergeToFile('/path/to/merged.avi', '/path/to/tempDir');
Generating thumbnails
One pretty neat feature of fluent-ffmpeg is the ability to generate any amount of thumbnails from your movies. The screenshots are taken at automatically determined timemarks using the following formula: (duration_in_sec * 0.9) / number_of_thumbnails
.
When generating thumbnails, the end
event is dispatched with an array of generated filenames as an argument.
new FFmpeg({ source: '/path/to/video.avi' })
.withSize('320x240')
.on('error', function(err) {
console.log('An error occurred: ' + err.message);
})
.on('end', function(filenames) {
console.log('Successfully generated ' + filenames.join(', '));
})
.takeScreenshots(5, '/path/to/directory');
You can also call takeScreenshots
with specific timemarks.
new FFmpeg({ source: '/path/to/video.avi' })
.withSize('320x240')
.on('error', function(err) {
console.log('An error occurred: ' + err.message);
})
.on('end', function(filenames) {
console.log('Successfully generated ' + filenames.join(', '));
})
.takeScreenshots(
{
count: 2,
timemarks: [ '0.5', '1' ]
},
'/path/to/directory'
);
You can set a filename pattern using following format characters:
%s
- offset in seconds%w
- screenshot width%h
- screenshot height%r
- screenshot resolution (eg. '320x240')%f
- input filename%b
- input basename (filename w/o extension)%i
- number of screenshot in timemark array (can be zero-padded by using it like %000i
)
If multiple timemarks are given and no %i
format character is found in filename, _%i
will be added to the end of the given pattern.
new FFmpeg({ source: '/path/to/video.avi' })
.withSize('320x240')
.on('error', function(err) {
console.log('An error occurred: ' + err.message);
})
.on('end', function(filenames) {
console.log('Successfully generated ' + filenames.join(', '));
})
.takeScreenshots(
{
count: 2,
timemarks: [ '0.5', '1' ],
filename: '%b-thumbnail-%i-%r'
},
'/path/to/directory'
);
Controlling the FFmpeg process
You can control the spawned FFmpeg process with the kill
and renice
methods. kill
only works after having spawned an FFmpeg process, but renice
can be used at any time.
var command = new FFmpeg({ source: '/path/to/video.avi' })
.withVideoCodec('libx264')
.withAudioCodec('libmp3lame')
.renice(5)
.saveToFile('/path/to/output.mp4');
command.renice(-5);
command.kill('SIGSTOP');
setTimeout(function() {
command.kill('SIGCONT');
}, 1000);
setTimeout(function() {
command.kill();
}, 2000);
Reading video metadata
Using a separate object, you can access various metadata of your video file.
var Metadata = require('fluent-ffmpeg').Metadata;
new Metadata(
'/path/to/your_movie.avi',
function(metadata, err) {
console.log(require('util').inspect(metadata, false, null));
}
);
Querying ffmpeg capabilities
fluent-ffmpeg enables you to query your installed ffmpeg version for supported formats, codecs and filters.
var Ffmpeg = require('fluent-ffmpeg');
Ffmpeg.getAvailableFormats(function(err, formats) {
console.log("Available formats:");
console.dir(formats);
});
Ffmpeg.getAvailableCodecs(function(err, codecs) {
console.log("Available codecs:");
console.dir(codecs);
});
Ffmpeg.getAvailableFilters(function(err, filters) {
console.log("Available filters:");
console.dir(filters);
});
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.
The returned object for formats looks like:
{
...
mp4: {
description: 'MP4 (MPEG-4 Part 14)',
canDemux: false,
canMux: true
},
...
}
canDemux
indicates whether ffmpeg is able to extract streams from (demux) this formatcanMux
indicates whether ffmpeg is able to write streams into (mux) this format
The returned object for codecs looks like:
{
...
mp3: {
type: 'audio',
description: 'MP3 (MPEG audio layer 3)',
canDecode: true,
canEncode: true,
intraFrameOnly: false,
isLossy: true,
isLossless: false
},
...
}
type
indicates the codec type, either "audio", "video" or "subtitle"canDecode
tells whether ffmpeg is able to decode streams using this codeccanEncode
tells whether ffmpeg is able to encode streams using this codec
Depending on your ffmpeg version (or if you use avconv instead) other keys may be present, for example:
directRendering
tells if codec can render directly in GPU RAM; useless for transcoding purposesintraFrameOnly
tells if codec can only work with I-framesisLossy
tells if codec can do lossy encoding/decodingisLossless
tells if codec can do lossless encoding/decoding
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 filters looks like:
{
...
scale: {
description: 'Scale the input video to width:height size and/or convert the image format.',
input: 'video',
multipleInputs: false,
output: 'video',
multipleOutputs: false
},
...
}
input
tells the input type this filter operates on, one of "audio", "video" or "none". When "none", the filter likely generates output from nothingmultipleInputs
tells whether the filter can accept multiple inputsoutput
tells the output type this filter generates, one of "audio", "video" or "none". When "none", the filter has no output (sink only)multipleInputs
tells whether the filter can generate multiple outputs
Contributors
Contributing
Contributions in any form are highly encouraged and welcome! Be it new or improved presets, optimized streaming code or just some cleanup. So start forking!
License
(The MIT License)
Copyright (c) 2011 Stefan Schaermeli <schaermu@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.