{"id":13561548,"url":"https://github.com/phaux/node-ffmpeg-stream","last_synced_at":"2025-12-29T23:41:37.642Z","repository":{"id":32310934,"uuid":"35886024","full_name":"phaux/node-ffmpeg-stream","owner":"phaux","description":"Node.js bindings to ffmpeg command, exposing stream based API","archived":false,"fork":false,"pushed_at":"2025-03-03T14:18:34.000Z","size":1268,"stargazers_count":134,"open_issues_count":6,"forks_count":14,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-03T15:25:03.419Z","etag":null,"topics":["converter","ffmpeg","ffmpeg-stream","node-stream","pipe","video"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phaux.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-05-19T14:07:33.000Z","updated_at":"2025-03-03T14:18:36.000Z","dependencies_parsed_at":"2024-07-15T17:03:03.354Z","dependency_job_id":"07588aa7-12e5-4122-a612-ef686515a71e","html_url":"https://github.com/phaux/node-ffmpeg-stream","commit_stats":{"total_commits":76,"total_committers":3,"mean_commits":"25.333333333333332","dds":"0.26315789473684215","last_synced_commit":"cc14054417b7160657f486e9edb79c52a8d854cf"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phaux%2Fnode-ffmpeg-stream","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phaux%2Fnode-ffmpeg-stream/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phaux%2Fnode-ffmpeg-stream/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phaux%2Fnode-ffmpeg-stream/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phaux","download_url":"https://codeload.github.com/phaux/node-ffmpeg-stream/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247046884,"owners_count":20874735,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["converter","ffmpeg","ffmpeg-stream","node-stream","pipe","video"],"created_at":"2024-08-01T13:00:58.191Z","updated_at":"2025-12-29T23:41:37.599Z","avatar_url":"https://github.com/phaux.png","language":"JavaScript","readme":"# FFmpeg-Stream\n\n[![npm](https://img.shields.io/npm/v/ffmpeg-stream)](https://www.npmjs.com/package/ffmpeg-stream)\n[![Codecov](https://img.shields.io/codecov/c/gh/phaux/node-ffmpeg-stream)](https://app.codecov.io/gh/phaux/node-ffmpeg-stream)\n\nNode bindings to ffmpeg command, exposing stream based API.\n\n\u003e [!NOTE]\n\u003e FFmpeg must be installed and available in `PATH`.\n\u003e You can set a custom ffmpeg path via an argument (default is just `ffmpeg`).\n\n## Examples\n\n```js\nimport { Converter } from \"ffmpeg-stream\"\nimport { createReadStream, createWriteStream } from \"node:fs\"\n\nasync function convert() {\n  const converter = new Converter()\n\n  // get a writable input stream and pipe an image file to it\n  const converterInput = converter.createInputStream({\n    f: \"image2pipe\",\n    vcodec: \"mjpeg\",\n  })\n  createReadStream(`${__dirname}/cat.jpg`).pipe(converterInput)\n\n  // create an output stream, crop/scale image, save to file via node stream\n  const converterOutput = converter.createOutputStream({\n    f: \"image2\",\n    vcodec: \"mjpeg\",\n    vf: \"crop=300:300,scale=100:100\",\n  })\n  converterOutput.pipe(createWriteStream(`${__dirname}/cat_thumb.jpg`))\n\n  // same, but save to file directly from ffmpeg\n  converter.createOutputToFile(`${__dirname}/cat_full.jpg`, {\n    vf: \"crop=300:300\",\n  })\n\n  // start processing\n  await converter.run()\n}\n```\n\n# API\n\n- **class** `Converter`\n\n  Creates a new instance of the ffmpeg converter class.\n  Converting won't start until `run()` method is called.\n\n  - **method** `createInputStream(options: Options): stream.Writable`\n\n    Defines an ffmpeg input stream.\n    Remember to specify the [`f` option](https://ffmpeg.org/ffmpeg.html#Main-options), which specifies the format of the input data.\n    The returned stream is a writable stream.\n\n  - **method** `createInputFromFile(file: string, options: Options): void`\n\n    Defines an ffmpeg input using specified path.\n    This is the same as specifying an input on the command line.\n\n  - **method** `createBufferedInputStream(options: Options): stream.Writable`\n\n    This is a mix of `createInputStream` and `createInputFromFile`.\n    It creates a temporary file and instructs ffmpeg to use it,\n    then it returns a writable stream attached to that file.\n    Using this method will cause a huge delay.\n\n  - **method** `createOutputStream(options: Options): stream.Readable`\n\n    Defines an ffmpeg output stream.\n    Remember to specify the [`f` option](https://ffmpeg.org/ffmpeg.html#Main-options), which specifies the format of the output data.\n    The returned stream is a readable stream.\n\n  - **method** `createOutputToFile(file: string, options: Options): void`\n\n    Defines an ffmpeg output using specified path.\n    This is the same as specifying an output on the command line.\n\n  - **method** `createBufferedOutputStream(options: Options): stream.Readable`\n\n    This is a mix of `createOutputStream` and `createOutputToFile`.\n    It creates a temporary file and instructs ffmpeg to use it,\n    then it returns a readable stream attached to that file.\n    Using this method will cause a huge delay.\n\n  - **method** `run(): Promise\u003cvoid\u003e`\n\n    Starts the ffmpeg process.\n    Returns a Promise which resolves on normal exit or kill, but rejects on ffmpeg error.\n\n  - **method** `kill(): void`\n\n    Kills the ffmpeg process.\n\n- **type** `Options`\n\n  Object of options which you normally pass to the ffmpeg command in the terminal.\n  Documentation for individual options can be found at [ffmpeg site](https://ffmpeg.org/ffmpeg.html) in audio and video category.\n  For boolean options specify `true` or `false`.\n  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\"] }`\n\n# FAQ\n\n## How to get video duration and other stats\n\nYou can use `ffprobe` command for now. It might be implemented in the library in the future, though.\n\n## Is there a `progress` or `onFrameEmitted` event\n\nCurrently, no.\n\n## Something doesn't work\n\nTry running your program with `DEBUG=ffmpeg-stream` environment variable.\nIt will print the ffmpeg command it executes and all the ffmpeg logs.\nThe command usually looks something like `ffmpeg -f … -i pipe:3 -f … pipe:4`.\n`pipe:number` means it uses standard input/output instead of a file.\n\n## Error: Muxer does not support non seekable output\n\nWhen getting error similar to this:\n\n```\n  [mp4 @ 0000000000e4db00] muxer does not support non seekable output\n  Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument\n  Error initializing output stream 0:1 --\n  encoded 0 frames\n  Conversion failed!\n\n    at ChildProcess.\u003canonymous\u003e (\u003cDirPath\u003e\\node_modules\\ffmpeg-stream\\lib\\index.js:215:27)\n    at emitTwo (events.js:106:13)\n    at ChildProcess.emit (events.js:191:7)\n    at Process.ChildProcess._handle.onexit (internal/child_process.js:215:12)\n```\n\nffmpeg 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\" }`).\n\nYou 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.\n\n## How to get individual frame data\n\nYou 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:\n\n```js\nimport { Transform } from \"node:stream\"\n\nclass ExtractFrames extends Transform {\n  constructor(magicNumberHex) {\n    super({ readableObjectMode: true })\n    this.magicNumber = Buffer.from(magicNumberHex, \"hex\")\n    this.currentData = Buffer.alloc(0)\n  }\n\n  _transform(newData, encoding, done) {\n    // Add new data\n    this.currentData = Buffer.concat([this.currentData, newData])\n\n    // Find frames in current data\n    while (true) {\n      // Find the start of a frame\n      const startIndex = this.currentData.indexOf(this.magicNumber)\n      if (startIndex \u003c 0) break // start of frame not found\n\n      // Find the start of the next frame\n      const endIndex = this.currentData.indexOf(\n        this.magicNumber,\n        startIndex + this.magicNumber.length,\n      )\n      if (endIndex \u003c 0) break // we haven't got the whole frame yet\n\n      // Handle found frame\n      this.push(this.currentData.slice(startIndex, endIndex)) // emit a frame\n      this.currentData = this.currentData.slice(endIndex) // remove frame data from current data\n      if (startIndex \u003e 0) console.error(`Discarded ${startIndex} bytes of invalid data`)\n    }\n\n    done()\n  }\n\n  _flush(done) {\n    this.push(this.currentData)\n    done()\n  }\n}\n```\n\nAnd then use it like that:\n\n```js\nimport { Converter } from \"ffmpeg-stream\"\n\nconst converter = new Converter()\n\nconverter\n  .createOutputStream({ f: \"image2pipe\", vcodec: \"mjpeg\" })\n  .pipe(new ExtractFrames(\"FFD8FF\")) // use jpg magic number as delimiter\n  .on(\"data\", frameData =\u003e {\n    /* do things with frame data (instance of Buffer) */\n  })\n\nconverter.run()\n```\n\n## How to create an animation from a set of image files\n\n\u003e I have images in Amazon S3 bucket (private) so I'm using their SDK to download those.\n\u003e I get the files in Buffer objects.\n\u003e Is there any way I can use your package to create a video out of it?\n\u003e\n\u003e So far I've been downloading the files and then using the following command:\n\u003e `ffmpeg -framerate 30 -pattern_type glob -i '*.jpg' -c:v libx264 -pix_fmt yuv420p out.mp4`\n\u003e\n\u003e But now want to do it from my node js application automatically.\n\n```js\nimport { Converter } from \"ffmpeg-stream\"\n\nconst frames = [\"frame1.jpg\", \"frame2.jpg\", ...etc]\n\n// create converter\nconst converter = new Converter()\n\n// create input writable stream (the jpeg frames)\nconst converterInput = converter.createInputStream({ f: \"image2pipe\", r: 30 })\n\n// create output to file (mp4 video)\nconverter.createOutputToFile(\"out.mp4\", {\n  vcodec: \"libx264\",\n  pix_fmt: \"yuv420p\",\n})\n\n// start the converter, save the promise for later\nconst convertingFinished = converter.run()\n\n// pipe all the frames to the converter sequentially\nfor (const filename of frames) {\n  // create a promise for every frame and await it\n  await new Promise((resolve, reject) =\u003e {\n    s3.getObject({ Bucket: \"...\", Key: filename })\n      .createReadStream()\n      .pipe(converterInput, { end: false }) // pipe to converter, but don't end the input yet\n      .on(\"end\", resolve) // resolve the promise after the frame finishes\n      .on(\"error\", reject)\n  })\n}\nconverterInput.end()\n\n// await until the whole process finished just in case\nawait convertingFinished\n```\n\n## How to stream a video when there's data, otherwise an intermission image\n\nYou 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.\nThen pipe it to second ffmpeg process which combines `jpeg` images into video.\n\n```js\nimport * as fs from \"node:fs\"\nimport { Converter } from \"ffmpeg-stream\"\n\n// create the joiner ffmpeg process (frames to video)\nconst joiner = new Converter()\nconst joinerInput = joiner.createInputStream({ f: \"mjpeg\" })\nconst joinerOutput = joiner.createOutputStream({ f: \"whatever format you want\" })\njoinerOutput.pipe(/* wherever you want */)\n\njoiner.run()\n\n// remember if we are streaming currently\nlet streaming = false\n\n/**\n * A function which streams a single video.\n *\n * @param {import(\"node:stream\").Readable} incomingStream - The video stream.\n * @param {string} format - The format of the video stream.\n *\n * @returns {Promise\u003cvoid\u003e} Promise which resolves when the stream ends.\n */\nasync function streamVideo(incomingStream, format) {\n  if (streaming) throw new Error(\"We are already streaming something else\")\n  streaming = true\n\n  // create the splitter ffmpeg process (video to frames)\n  const splitter = new Converter()\n\n  // pipe video to splitter process\n  incomingStream.pipe(splitter.createInputStream({ f: format }))\n\n  // get jpegs and pipe them to joiner process\n  splitter.createOutputStream({ f: \"mjpeg\" }).pipe(joinerInput, { end: false })\n\n  try {\n    await splitter.run()\n  } finally {\n    streaming = false\n  }\n}\n\nsetInterval(() =\u003e {\n  // if we are streaming - do nothing\n  if (streaming) return\n\n  // pipe a single jpeg file 30 times per second into the joiner process\n  // TODO: don't actually read the file 30 times per second\n  fs.createReadStream(\"intermission_pic.jpg\").pipe(joinerInput, { end: false })\n}, 1000 / 30)\n```\n\n## I want intermission image with audio and other complicated stuff\n\nYou should probably use [beamcoder](https://github.com/Streampunk/beamcoder) instead.\n","funding_links":[],"categories":["TypeScript","HarmonyOS","Video Encoding, Transcoding \u0026 Packaging Tools"],"sub_categories":["Windows Manager","FFmpeg-Based Tools"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphaux%2Fnode-ffmpeg-stream","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphaux%2Fnode-ffmpeg-stream","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphaux%2Fnode-ffmpeg-stream/lists"}