JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 208
  • Score
    100M100P100Q79732F
  • License MIT

A minimal yet powerful wrapper around ffmpeg and ffprobe

Package Exports

  • ffmpeggy

Readme

ffmpeggy

license npm dependencies types coverage quality

A minimal yet powerful wrapper for FFmpeg and FFprobe. Has built-in support for Node.js streams and events that can provide you a detailed progress report.

This is a hybrid package built in TypeScript that provides both CommonJS and ES modules with only a couple of dependencies.

Installation

$ npm install --save ffmpeggy
$ yarn add ffmpeggy

Installing ffmpeg and ffprobe binaries

If you don't want to provide your own binaries, I recommend you to install these optional packages that includes binaries for all platforms:

$ npm install --save @ffmpeg-installer/ffmpeg @ffprobe-installer/ffprobe
$ yarn add @ffmpeg-installer/ffmpeg @ffprobe-installer/ffprobe

You can then change the default config to use those binaries like this:

import { path as ffmpegBin } from "@ffmpeg-installer/ffmpeg";
import { path as ffprobeBin } from "@ffprobe-installer/ffprobe";

FFmpeg.DefaultConfig = {
  ...FFmpeg.DefaultConfig,
  ffprobeBin,
  ffmpegBin,
};

Basic usage

ffmpeggy comes with an intuitive api that allows you to work with it in your preferred way.

Using with async/await

The most simple way to use ffmpeggy is with async/await.

import { FFmpeg } from "ffmpeggy";

async function main() {
  const ffmpeg = new FFmpeg();
  try {
    ffmpeg
      .setInput("input.mp4")
      .setOutput("output.mkv")
      .setOutputOptions(["-c:v h264"])
      .run();

    await ffmpeg.done();
    console.log(`Done =)`);
  } catch {
    console.error(`Something went wrong =(`);
  }
}

Using event handlers

To make use of all the bells and whistles of ffmpeggy you can hook into the events that are transmitted. All the events are fully typed!

import { FFmpeg } from "ffmpeggy";

new FFmpeg({
    autorun: true,
    input: "input.mp4",
    output: "output.mkv",
    outputOptions: ["-c:v h264"],
  })
  .on("start", (args) => {
    console.log(`ffmpeg was started with these args:`, args);
  })
  .on("progress", (event) => {
    console.log(`${event.progress}%`);
  })
  .on("error", (error) => {
    console.error(`Something went wrong =(`, error);
  })
  .on("done", (outputFile) => {
    console.log(`Done =)`);
  });

Using with Node.js streams

You can provide streams directly to both input and output.

NOTE: ffmpeg uses filenames to detect a format and since a stream doesn't have a filename you need to explicitly add that option for each stream.

import { FFmpeg } from "ffmpeggy";

new FFmpeg({
  autorun: true,
  input: createReadStream("input.mkv"),
  inputOptions: ["-f matroska"],
  output: createWriteStream("output.mkv"),
  outputOptions: ["-f matroska", "-c:v h264"],
});

You can also use the .toStream() method to get a stream that you can pipe.

import { FFmpeg } from "ffmpeggy";

const ffmpeg = new FFmpeg({
  autorun: true,
  pipe: true, // shorthand for output set to pipe:0
  input: createReadStream("input.mp4"),
  outputOptions: ["-c:v h264"],
});

const stream = ffmpeg.toStream();
stream.pipe(createWriteStream("output.mkv"));

Probing

You can call the static FFmpeg.probe() method, which returns a promise:

import { FFmpeg } from "ffmpeggy";

const probeResults = await FFmpeg.probe("input.mkv");

Or you can call .probe() on an instance that will then run a probe on provided input:

import { FFmpeg } from "ffmpeggy";

const ffmpeg = new FFmpeg({
  input: "input.mkv"
});

const probeResults = await ffmpeg.probe();

Available options

Name Value Description Default
cwd string The working directory that ffmpeg will use Current cwd
input string | ReadableStream Input path or readable stream Empty string
output string | WritableStream Output path or writable stream Empty string
pipe boolean If output should be piped or not Empty string
globalOptions string[] An array of ffmpeg global options Empty array
inputOptions string[] An array of ffmpeg input options Empty array
outputOptions string[] An array of ffmpeg output options Empty array
autorun boolean Will call run() in the constructor if set to true false

Available events

start - (ffmpegArgs: readonly string[]) => void

Fires when the ffmpeg process have been started. The ffmpegArgs argument contains an array with the arguments that was passed to the ffmpeg process.

error - (error: Error) => void

Fires when there was an error while running the ffmpeg process.

done - (file?: string) => void

Fires when the ffmpeg process have successfully completed.

exit - (code?: number | null, error?: Error) => void

Fires when the ffmpeg process have exited.

probe - (probeResult: FFprobeResult) => void

Fires when the ffprobe process have returned its result.

progress - (progress: FFmpegProgressEvent) => void

Fires when ffmpeg is outputting it's progress. Most of the properties in FFmpegProgressEvent are provided by ffmpeg's output, except duration and percent:

  • frame: The current frame (i.e. total frames that have been processed)
  • fps: Framerate at which FFmpeg is currently processing
  • size: The current size of the output in kilobytes
  • time: The time of the current frame in seconds
  • bitrate: The current throughput at which FFmpeg is processing
  • duration: The duration of the output in seconds
  • percent: An estimation of the progress percentage
  • q: The current quality scale (qscale). This is rarely used and is often just set to 0.

Why another ffmpeg wrapper?

Because I wasn't happy with the ones that already exists. Most of them are badly maintained, and/or lacking TypeScript typings or are too complex for my taste. I started coding on this a while back for another project and it's been working really well so figured it deserved it's own package.

How does ffmpeggy compare from fluent-ffmpeg?

They strive to solve different problems. Whereas ffmpeggy aims to be lean and simple, fluent-ffmpeg aims to provide an exhaustive and human readable API. I personally don't need all of that but I might revisit it at a later stage. But an extended API will most likely end up in a separate package to keep this one as lean as possible.

License

MIT