Skip to content

Repository files navigation

FFmpeg-Stream

npmCodecov

Node bindings to ffmpeg command, exposing stream based API.

Note

FFmpeg must be installed and available in PATH. You can set a custom ffmpeg path via an argument (default is just ffmpeg).

Examples

import{Converter}from"ffmpeg-stream"import{createReadStream,createWriteStream}from"node:fs"asyncfunctionconvert(){constconverter=newConverter()// get a writable input stream and pipe an image file to itconstconverterInput=converter.createInputStream({f: "image2pipe",vcodec: "mjpeg",})createReadStream(`${__dirname}/cat.jpg`).pipe(converterInput)// create an output stream, crop/scale image, save to file via node streamconstconverterOutput=converter.createOutputStream({f: "image2",vcodec: "mjpeg",vf: "crop=300:300,scale=100:100",})converterOutput.pipe(createWriteStream(`${__dirname}/cat_thumb.jpg`))// same, but save to file directly from ffmpegconverter.createOutputToFile(`${__dirname}/cat_full.jpg`,{vf: "crop=300:300",})// start processingawaitconverter.run()}

API

  • classConverter

    Creates a new instance of the ffmpeg converter class. Converting won't start until run() method is called.

    • methodcreateInputStream(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.

    • methodcreateInputFromFile(file: string, options: Options): void

      Defines an ffmpeg input using specified path. This is the same as specifying an input on the command line.

    • methodcreateBufferedInputStream(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.

    • methodcreateOutputStream(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.

    • methodcreateOutputToFile(file: string, options: Options): void

      Defines an ffmpeg output using specified path. This is the same as specifying an output on the command line.

    • methodcreateBufferedOutputStream(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.

    • methodrun(): Promise<void>

      Starts the ffmpeg process. Returns a Promise which resolves on normal exit or kill, but rejects on ffmpeg error.

    • methodkill(): void

      Kills the ffmpeg process.

  • typeOptions

    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.

Something doesn't work

Try running your program with DEBUG=ffmpeg-stream environment variable. It will print the ffmpeg command it executes and all the ffmpeg logs. The command usually looks something like ffmpeg -f … -i pipe:3 -f … pipe:4. pipe:number means it uses standard input/output instead of a file.

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 - or pipe:1 as output file. Maybe some other options or different format will work. Streaming sequence of JPEGs over websockets worked flawlessly for me ({ f: "image2pipe", vcodec: "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:

import{Transform}from"node:stream"classExtractFramesextendsTransform{constructor(magicNumberHex){super({readableObjectMode: true})this.magicNumber=Buffer.from(magicNumberHex,"hex")this.currentData=Buffer.alloc(0)}_transform(newData,encoding,done){// Add new datathis.currentData=Buffer.concat([this.currentData,newData])// Find frames in current datawhile(true){// Find the start of a frameconststartIndex=this.currentData.indexOf(this.magicNumber)if(startIndex<0)break// start of frame not found// Find the start of the next frameconstendIndex=this.currentData.indexOf(this.magicNumber,startIndex+this.magicNumber.length,)if(endIndex<0)break// we haven't got the whole frame yet// Handle found framethis.push(this.currentData.slice(startIndex,endIndex))// emit a framethis.currentData=this.currentData.slice(endIndex)// remove frame data from current dataif(startIndex>0)console.error(`Discarded ${startIndex} bytes of invalid data`)}done()}_flush(done){this.push(this.currentData)done()}}

And then use it like that:

import{Converter}from"ffmpeg-stream"constconverter=newConverter()converter.createOutputStream({f: "image2pipe",vcodec: "mjpeg"}).pipe(newExtractFrames("FFD8FF"))// use jpg magic number as delimiter.on("data",frameData=>{/* do things with frame data (instance of Buffer) */})converter.run()

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.

import{Converter}from"ffmpeg-stream"constframes=["frame1.jpg","frame2.jpg", ...etc]// create converterconstconverter=newConverter()// create input writable stream (the jpeg frames)constconverterInput=converter.createInputStream({f: "image2pipe",r: 30})// create output to file (mp4 video)converter.createOutputToFile("out.mp4",{vcodec: "libx264",pix_fmt: "yuv420p",})// start the converter, save the promise for laterconstconvertingFinished=converter.run()// pipe all the frames to the converter sequentiallyfor(constfilenameofframes){// create a promise for every frame and await itawaitnewPromise((resolve,reject)=>{s3.getObject({Bucket: "...",Key: filename}).createReadStream().pipe(converterInput,{end: false})// pipe to converter, but don't end the input yet.on("end",resolve)// resolve the promise after the frame finishes.on("error",reject)})}converterInput.end()// await until the whole process finished just in caseawaitconvertingFinished

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.

import*asfsfrom"node:fs"import{Converter}from"ffmpeg-stream"// create the joiner ffmpeg process (frames to video)constjoiner=newConverter()constjoinerInput=joiner.createInputStream({f: "mjpeg"})constjoinerOutput=joiner.createOutputStream({f: "whatever format you want"})joinerOutput.pipe(/* wherever you want */)joiner.run()// remember if we are streaming currentlyletstreaming=false/** * A function which streams a single video. * * @param {import("node:stream").Readable} incomingStream - The video stream. * @param {string} format - The format of the video stream. * * @returns {Promise<void>} Promise which resolves when the stream ends. */asyncfunctionstreamVideo(incomingStream,format){if(streaming)thrownewError("We are already streaming something else")streaming=true// create the splitter ffmpeg process (video to frames)constsplitter=newConverter()// pipe video to splitter processincomingStream.pipe(splitter.createInputStream({f: format}))// get jpegs and pipe them to joiner processsplitter.createOutputStream({f: "mjpeg"}).pipe(joinerInput,{end: false})try{awaitsplitter.run()}finally{streaming=false}}setInterval(()=>{// if we are streaming - do nothingif(streaming)return// pipe a single jpeg file 30 times per second into the joiner process// TODO: don't actually read the file 30 times per secondfs.createReadStream("intermission_pic.jpg").pipe(joinerInput,{end: false})},1000/30)

I want intermission image with audio and other complicated stuff

You should probably use beamcoder instead.

About

Node.js bindings to ffmpeg command, exposing stream based API

Topics

Resources

Stars

142 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages