Build ffmpeg filtergraphs and commands from Elixir.

The usual workflow is:

use FFix imports the top-level helpers plus generated filter functions. Callbacks receive and return ordinary Elixir values. Examples in this module assume use FFix; without it, call FFix.command/3, FFix.output/2, and FFix.Filter functions directly.

Quick Start

command/3 is the usual entry point. Pass an input source, build filtered streams in the first callback, and map streams to an output target in the second callback:

command(
  "input.mp4",
  fn src ->
    src[:video] |> crop(w: 720, h: 720)
  end,
  fn cropped, src ->
    output("square.mp4", video: cropped, audio: src[:audio])
  end
)

This reads input.mp4, crops the video stream, maps the original audio stream, and writes square.mp4.

Choosing an API Layer

  • FFix.command/3 is the main API for building ffmpeg commands.
  • FFix.Command is for constructing or transforming command structs.
  • FFix.Graph is for parsing, serializing, or reusing filtergraphs.
  • FFix.Runner is the execution boundary for running commands and streaming events.

Command Model

A command has three parts:

  • input - where media comes from, such as "input.mp4" or :stdin
  • graph - stream selection and filters; filters become -filter_complex
  • output - stream mappings, output options, and the output target

In FFix, a file path is not itself a stream. The graph callback receives an input value, and you select streams from it:

src[:video]
src[:audio]
src[audio: 1]

Filters consume streams and produce new streams:

cropped = src[:video] |> crop(w: 720, h: 720)

output/2 declares an ffmpeg output. The first argument is the output target or file, and video:, audio:, or sources: decide which streams are mapped into it.

output("square.mp4", video: cropped, audio: src[:audio])

The flow is:

input source        graph callback                output callback
------------        --------------                ---------------
"input.mp4"  --->   src[:video] |> crop(...) ---> video: cropped
                    src[:audio] ---------------> audio: src[:audio]

output target: "square.mp4"

FFix builds an Elixir command model and translates it to ffmpeg argv. ffmpeg still does the demuxing, decoding, filtering, encoding, and muxing.

The quick-start example is shaped like this ffmpeg command:

ffmpeg -i input.mp4 \
  -filter_complex '[0:v]crop=w=720:h=720[out0]' \
  -map '[out0]' \
  -map 0:a \
  square.mp4

Labels such as [out0] are generated by FFix; they are implementation details, not names you normally need to manage.

When no filtering is needed, return input streams directly:

command(
  "input.mp4",
  fn src ->
    [src[:video], src[:audio]]
  end,
  fn [video, audio] ->
    output("copy.mp4", video: video, audio: audio, c: :copy)
  end
)

Command Callbacks

The graph callback receives inputs in the shape you passed, with sources normalized to %FFix.Command.Input{} values. The output callback receives graph exports in the same shape returned by the graph callback.

For multiple graph results, return a list. The output callback can return one output or a list of outputs, so one ffmpeg command can write multiple targets:

command(
  "input.mp4",
  fn src ->
    [
      src[:video] |> scale(w: 1280, h: -1),
      src[:video] |> fps(fps: 1)
    ]
  end,
  fn [main, preview], src ->
    [
      output("main.mp4", video: main, audio: src[:audio]),
      output("thumb-%03d.jpg", video: preview, f: :image2)
    ]
  end
)

Use keyword lists or maps when names make a larger graph easier to read:

command(
  [src: input("input.mp4")],
  fn inputs ->
    [
      main: inputs[:src][:video] |> scale(w: 1280, h: -1)
    ]
  end,
  fn [main: main], inputs ->
    output("out.mp4",
      video: main,
      audio: inputs[:src][:audio],
      "c:v": :libx264,
      "c:a": :aac
    )
  end
)

Input and graph shapes are preserved at the callback boundary:

  • term -> term
  • [term] -> [term]
  • keyword -> keyword
  • map -> map

A graph callback may also return a %FFix.Graph{} when you need graph settings or terminals. An outputs callback may accept one argument for graph exports or two arguments for graph exports and inputs.

Options

Put each kind of option where ffmpeg expects it:

command(
  input("input.mp4", ss: "00:00:05"),
  fn src ->
    src[:video] |> scale(w: 1280, h: -1)
  end,
  fn video, src ->
    output("out.mp4",
      video: video,
      audio: src[:audio],
      "c:v": :libx264,
      "c:a": :aac,
      movflags: [:faststart]
    )
  end,
  global: [y: true, loglevel: :error]
)

This maps to the usual ffmpeg placement:

global options -> before inputs
input options  -> before -i
filter options -> inside -filter_complex
output options -> before the output target

Output options are intentionally ffmpeg-shaped and flat for now. Encoding and muxer options such as "c:v", "c:a", f:, and movflags: belong in output/2.

Copying is still ffmpeg's rule: direct input streams can usually be copied, but filtered streams must be encoded again. When mixing them, set codecs per stream:

output("out.mp4",
  video: scaled,
  audio: src[:audio],
  "c:v": :libx264,
  "c:a": :copy
)

Output Mapping

Use video: and audio: for common mappings:

output("out.mp4", video: main, audio: src[:audio])

Use sources: when -map ordering matters:

output("archive.mkv",
  sources: [main, src[audio: 1], src[audio: 0]]
)

Output options are intentionally ffmpeg-shaped. Keys are rendered as CLI option names, so stream-specific options can use quoted atoms or strings such as "c:v" and "metadata:s:a:0".

Boundaries

validate!/1 performs structural checks only. It does not probe media files or ask ffmpeg to validate the command.

to_argv/1 is the canonical serialization boundary. to_shell_string/1 is useful for logs, but argv should be preferred when executing.

Summary

Setup

Imports the high-level FFix helpers and generated filter functions.

Command building

Returns an empty low-level %FFix.Command{}.

Builds a command from inputs, a graph callback, and an output callback.

Builds a command with command-level options.

Inputs and outputs

Declares an ffmpeg input without input options.

Declares an ffmpeg input with input options.

Declares an output target and stream mappings.

Filtergraphs

Wraps a raw ffmpeg expression so it is serialized as an expression value.

Applies a filter by name.

Builds a %FFix.Graph{} from exported streams and terminal sinks.

Assigns a concrete output shape to a dynamic or ambiguous filter result.

Serialization

Serializes a command to the argv list that should be passed to an OS process.

Validates and serializes a graph to ffmpeg filtergraph syntax.

Serializes a command as a shell-escaped string for logs and debugging.

Execution

Runs a command and returns {:ok, result} or {:error, error}.

Runs a command and returns the result, raising FFix.Runner.Error on failure.

Runs a command as a lazy stream of execution events.

Runs a command as a lazy stream and raises on non-zero exit.

Validation

Performs structural validation on a graph or command.

Types

Media type for a shaped filter output.

Setup

__using__(options)

(macro)

Imports the high-level FFix helpers and generated filter functions.

This is useful for concise pipeline modules. If you prefer explicit names, skip use FFix and call FFix.command/3, FFix.output/2, and FFix.Filter.scale/2 directly.

defmodule Pipeline do
  use FFix

  def resize(video) do
    video |> scale(w: 1280, h: -1)
  end
end

Command building

command()

@spec command() :: FFix.Command.t()

Returns an empty low-level %FFix.Command{}.

Prefer command/3 for the callback API.

command(inputs, graph_fun, outputs_fun)

Builds a command from inputs, a graph callback, and an output callback.

Simple commands usually pass one input and return one filtered stream:

command(
  "input.mp4",
  fn src ->
    src[:video] |> crop(w: 720, h: 720)
  end,
  fn cropped, src ->
    output("square.mp4", video: cropped, audio: src[:audio])
  end
)

The first argument can be a source string, input(...), a list of sources or inputs, a keyword list, or a map. Sources are normalized to %FFix.Command.Input{} values, while the outer shape is preserved.

The graph callback returns streams in the shape you want the output callback to receive.

Shapes are preserved:

  • stream -> export
  • [stream] -> [export]
  • [name: stream] -> [name: export]
  • %{name: stream} -> %{name: export}

Return %FFix.Graph{} when you need graph settings or terminals.

command(
  "input.mp4",
  fn src ->
    [
      src[:video] |> scale(w: 1280, h: -1),
      src[:video] |> fps(fps: 1)
    ]
  end,
  fn [main, preview], src ->
    [
      output("out.mp4", video: main, audio: src[:audio]),
      output("thumb-%03d.jpg", video: preview, f: :image2)
    ]
  end
)

For named inputs, pass a keyword list or map. The output callback receives the same input shape:

command(
  [src: "input.mp4", logo: input("logo.png", loop: 1)],
  fn inputs ->
    inputs[:src][:video]
    |> overlay(inputs[:logo][:video], x: 20, y: 20)
  end,
  fn watermarked, inputs ->
    output("out.mp4", video: watermarked, audio: inputs[:src][:audio])
  end
)

command(inputs, graph_fun, outputs_fun, options)

Builds a command with command-level options.

Options are passed as the final argument. Supported options:

  • :global - global ffmpeg options rendered before inputs

Do not pass :inputs, :graph, or :outputs here; those are the positional arguments of command/4.

command(
  "input.mp4",
  fn src ->
    src[:video] |> crop(w: 720, h: 720)
  end,
  fn cropped, src ->
    output("square.mp4", video: cropped, audio: src[:audio])
  end,
  global: [y: true, loglevel: :error]
)

Inputs and outputs

input(source)

Declares an ffmpeg input without input options.

This returns an %FFix.Command.Input{}. In command/3, pass it wherever you need input options:

command(
  input("input.mp4", ss: "00:00:05"),
  fn src -> src[:video] end,
  fn video, src ->
    output("copy.mp4", video: video, audio: src[:audio], c: :copy)
  end
)

For optionless file inputs, pass the source string directly.

input(source, options)

Declares an ffmpeg input with input options.

Options are rendered before -i.

input("clip.mp4", ss: "00:00:05", t: "00:00:10")
input(:stdin, f: :wav)

Option keys are ffmpeg CLI option names without the leading dash.

output(target, options_or_sources)

Declares an output target and stream mappings.

Use video: and audio: for common cases:

output("out.mp4",
  video: main,
  audio: src[:audio],
  "c:v": :libx264,
  "c:a": :aac
)

Use sources: when exact -map order matters:

output("archive.mkv",
  sources: [main, src[audio: 1], src[audio: 0]],
  c: :copy
)

Remaining options are rendered as ffmpeg output options after all -map entries and before the output target.

Filtergraphs

expr(source)

@spec expr(String.t()) :: FFix.Expr.t()

Wraps a raw ffmpeg expression so it is serialized as an expression value.

drawtext(video, text: "Hello", x: expr("w-tw-20"), y: 20)

filter(name, inputs, options \\ [])

@spec filter(atom() | String.t(), [FFix.Stream.t()], keyword()) ::
  FFix.Stream.t() | FFix.Terminal.t() | [FFix.Stream.t()] | tuple()

Applies a filter by name.

Most code should call generated helpers from FFix.Filter, such as scale/2, overlay/3, or fps/2. Use filter/3 when the filter name is dynamic.

FFix.filter(:scale, [video], w: 1280, h: -1)

graph(options)

@spec graph(keyword()) :: FFix.Graph.t()

Builds a %FFix.Graph{} from exported streams and terminal sinks.

Common options:

  • :output - a single unnamed exported stream
  • :outputs - a list of named or unnamed exported streams
  • :terminals - sink-ending filter results, such as nullsink
  • :settings - graph-level settings such as sws_flags

In command/3, graph callbacks can return streams, lists, keyword lists, or maps directly. Use graph/1 when you need terminals or graph settings.

FFix.graph(
  outputs: [main: video |> scale(w: 1280, h: -1)],
  settings: [sws_flags: [:accurate_rnd]]
)

shape(result, outputs)

@spec shape(FFix.Stream.t() | [FFix.Stream.t()] | tuple(), [output_media()]) ::
  FFix.Stream.t() | [FFix.Stream.t()]

Assigns a concrete output shape to a dynamic or ambiguous filter result.

Some ffmpeg filters have output counts or media types that depend on options. shape/2 lets you state the result explicitly before exporting or mapping it.

[audio, video] =
  input_audio
  |> ebur128(video: true)
  |> FFix.shape([:audio, :video])

Serialization

to_argv(command)

@spec to_argv(FFix.Command.t()) :: [String.t()]

Serializes a command to the argv list that should be passed to an OS process.

FFix.to_argv(command)
#=> ["ffmpeg", "-i", "input.mp4", "-map", "0:v", "out.mp4"]

to_filtergraph(graph)

@spec to_filtergraph(FFix.Graph.t()) :: String.t()

Validates and serializes a graph to ffmpeg filtergraph syntax.

to_shell_string(command)

@spec to_shell_string(FFix.Command.t()) :: String.t()

Serializes a command as a shell-escaped string for logs and debugging.

Prefer to_argv/1 for execution.

Execution

run(command, options \\ [])

@spec run(FFix.Command.t() | [String.t(), ...], [FFix.Runner.option()]) ::
  {:ok, FFix.Runner.Result.t()} | {:error, FFix.Runner.Error.t()}

Runs a command and returns {:ok, result} or {:error, error}.

See FFix.Runner for stdout/stderr capture, progress events, and streaming execution options.

run!(command, options \\ [])

@spec run!(FFix.Command.t() | [String.t(), ...], [FFix.Runner.option()]) ::
  FFix.Runner.Result.t()

Runs a command and returns the result, raising FFix.Runner.Error on failure.

stream(command, options \\ [])

@spec stream(FFix.Command.t() | [String.t(), ...], [FFix.Runner.option()]) :: term()

Runs a command as a lazy stream of execution events.

FFix.stream(command, progress: true)
|> Enum.each(fn
  {:progress, progress} -> IO.inspect(progress.status)
  {:exit, result} -> IO.inspect(result.exit_status)
  _event -> :ok
end)

stream!(command, options \\ [])

@spec stream!(FFix.Command.t() | [String.t(), ...], [FFix.Runner.option()]) :: term()

Runs a command as a lazy stream and raises on non-zero exit.

Validation

validate!(graph)

@spec validate!(FFix.Graph.t() | FFix.Command.t()) ::
  FFix.Graph.t() | FFix.Command.t()

Performs structural validation on a graph or command.

Validation checks the model that FFix controls: declared inputs, graph refs, output sources, and filtered graph export mappings. It does not probe media files or execute ffmpeg.

Types

output_media()

@type output_media() :: :audio | :video | :unknown

Media type for a shaped filter output.