FFmpeg-Stream
Node bindings to ffmpeg command, exposing stream based API.
CHANGELOG
Note: ffmpeg must be installed and available in PATH.
Examples
const { Converter } = require("ffmpeg-stream")
const { createReadStream, createWriteStream } = require("fs")
async function convert() {
const converter = new Converter()
const input = converter.createInputStream({
f: "image2pipe",
vcodec: "mjpeg",
})
createReadStream(`${__dirname}/cat.jpg`).pipe(input)
converter
.createOutputStream({
f: "image2",
vcodec: "mjpeg",
vf: "crop=300:300,scale=100:100",
})
.pipe(createWriteStream(`${__dirname}/cat_thumb.jpg`))
converter.createOutputToFile(`${__dirname}/cat_full.jpg`, {
vf: "crop=300:300",
})
await converter.run()
}
API
-
class Converter
Creates a new instance of the ffmpeg converter class.
Converting won't start until run()
method is called.
-
method createInputStream(options: Options): stream.Writable
Defines an ffmpeg input stream.
Remember to specify the f
option, which specifies the format of the input data.
The returned stream is a writable stream.
-
method createInputFromFile(file: string, options: Options): void
Defines an ffmpeg input using specified path.
This is the same as specifying an input on the command line.
-
method createBufferedInputStream(options: Options): stream.Writable
This is a mix of createInputStream
and createInputFromFile
.
It creates a temporary file and instructs ffmpeg to use it,
then it returns a writable stream attached to that file.
Using this method will cause a huge delay.
-
method createOutputStream(options: Options): stream.Readable
Defines an ffmpeg output stream.
Remember to specify the f
option, which specifies the format of the output data.
The returned stream is a readable stream.
-
method createOutputToFile(file: string, options: Options): void
Defines an ffmpeg output using specified path.
This is the same as specifying an output on the command line.
-
method createBufferedOutputStream(options: Options): stream.Readable
This is a mix of createOutputStream
and createOutputToFile
.
It creates a temporary file and instructs ffmpeg to use it,
then it returns a readable stream attached to that file.
Using this method will cause a huge delay.
-
method input(file?: string, options: Options): stream.Writable | undefined
DEPRECATED! Use one of 3 input methods above.
This is the same as createInputFromFile
when the file is specified,
and createInputStream
when it isn't,
or createBufferedInputStream
when also a magic option buffer
is set to true
.
-
method output(file?: string, options: Options): stream.Readable | undefined
DEPRECATED! Use one of the 3 output methods above.
This is the same as createOutputFromFile
when the file is specified,
and createOutputStream
when it isn't,
or createBufferedOutputStream
when also a magic option buffer
is set to true
.
-
method run(): Promise<void>
Starts the ffmpeg process.
Returns a Promise which resolves on normal exit or kill, but rejects on ffmpeg error.
-
method kill(): void
Kills the ffmpeg process.
-
type Options
Object of options which you normally pass to the ffmpeg command in the terminal.
Documentation for individual options can be found at ffmpeg site in audio and video category.
For boolean options specify true
or false
.
If you'd like to specify the same argument multiple times you can do so by providing an array of values. E.g. { map: ["0:v", "1:a"] }
FAQ
How to get video duration and other stats
You can use ffprobe
command for now. It might be implemented in the library in the future, though.
Is there a progress
or onFrameEmitted
event
Currently, no.
Error: Muxer does not support non seekable output
When getting error similar to this:
[mp4 @ 0000000000e4db00] muxer does not support non seekable output
Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument
Error initializing output stream 0:1 --
encoded 0 frames
Conversion failed!
at ChildProcess.<anonymous> (<DirPath>\node_modules\ffmpeg-stream\lib\index.js:215:27)
at emitTwo (events.js:106:13)
at ChildProcess.emit (events.js:191:7)
at Process.ChildProcess._handle.onexit (internal/child_process.js:215:12)
ffmpeg says that the combination of options you specified doesn't support streaming. You can experiment with calling ffmpeg directly and specifying -
as output file. Maybe some other options or different format will work. Streaming sequence of JPEGs over websockets worked flawlessly for me (f: 'mjpeg'
).
You can also use createBufferedOutputStream
. That tells the library to save output to a temporary file and then create a node stream from that file. It wont start producing data until the conversion is complete, though.
How to get individual frame data
You have to set output format to mjpeg and then split the stream manually by looking at the bytes. You can implement a transform stream which does this:
const { Transform } = require("stream")
class ExtractFrames extends Transform {
constructor(delimiter) {
super({ readableObjectMode: true })
this.delimiter = Buffer.from(delimiter, "hex")
this.buffer = Buffer.alloc(0)
}
_transform(data, enc, cb) {
this.buffer = Buffer.concat([this.buffer, data])
while (true) {
const start = this.buffer.indexOf(this.delimiter)
if (start < 0) break
const end = this.buffer.indexOf(this.delimiter, start + this.delimiter.length)
if (end < 0) break
this.push(this.buffer.slice(start, end))
this.buffer = this.buffer.slice(end)
if (start > 0) console.error(`Discarded ${start} bytes of invalid data`)
}
cb()
}
}
And then use it like that:
const { Converter } = require("ffmpeg-stream")
const converter = new Converter()
converter
.createOutputStream({ f: "image2pipe", vcodec: "mjpeg" })
.pipe(new ExtractFrames("FFD8FF"))
.on("data", frame => {
})
.on("end", () => {
})
.on("error", () => {
})
How to create an animation from a set of image files
I have images in Amazon S3 bucket (private) so I'm using their SDK to download those.
I get the files in Buffer objects.
Is there any way I can use your package to create a video out of it?
So far I've been downloading the files and then using the following command:
ffmpeg -framerate 30 -pattern_type glob -i '*.jpg' -c:v libx264 -pix_fmt yuv420p out.mp4
But now want to do it from my node js application automatically.
const { Converter } = require("ffmpeg-stream")
const frames = ["frame1.jpg", "frame2.jpg", ...etc]
const converter = new Converter()
const input = converter.createInputStream({ f: "image2pipe", r: 30 })
converter.createOutputToFile("out.mp4", {
vcodec: "libx264",
pix_fmt: "yuv420p",
})
frames
.map(filename => () =>
new Promise((resolve, reject) =>
s3
.getObject({ Bucket: "...", Key: filename })
.createReadStream()
.on("end", resolve)
.on("error", reject)
.pipe(input, { end: false })
)
)
.reduce((prev, next) => prev.then(next), Promise.resolve())
.then(() => input.end())
converter.run()
How to stream a video when there's data, otherwise an intermission image
You can turn your main stream into series of jpeg
images with output format mjpeg
and combine it with static image by repeatedly piping a single jpeg
image when there's no data from main stream.
Then pipe it to second ffmpeg process which combines jpeg
images into video.
const fs = require("fs")
const { Converter } = require("ffmpeg-stream")
const combine = new Converter()
const combineInput = combine.createInputStream({ f: "mjpeg" })
let streaming = false
function streamVideo(stream) {
streaming = true
const split = new Converter()
stream.pipe(split.createInputStream({ f: "webm" }))
split.createOutputStream({ f: "mjpeg" }).pipe(combineInput, { end: false })
stream.on("end", () => {
streaming = false
})
split.run()
}
setInterval(() => {
if (streaming) return
fs.createReadStream("intermission_pic.jpg").pipe(combineInput, { end: false })
}, 1000 / 30)
combine.createOutputStream({ f: "whatever format you want" }).pipe()
combine.run()