Build ffmpeg filtergraphs and commands from Elixir.
The usual workflow is:
- pass input sources to
command/3 - build streams with generated
FFix.Filterhelpers - map streams to outputs with
output/2 - serialize with
to_argv/1or execute withrun/2
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/3is the main API for building ffmpeg commands.FFix.Commandis for constructing or transforming command structs.FFix.Graphis for parsing, serializing, or reusing filtergraphs.FFix.Runneris 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.mp4Labels 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 -> keywordmap -> 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 targetOutput 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
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
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
@spec command() :: FFix.Command.t()
Returns an empty low-level %FFix.Command{}.
Prefer command/3 for the callback API.
@spec command( FFix.Command.Input.source() | FFix.Command.Input.t() | [FFix.Command.Input.source() | FFix.Command.Input.t()] | keyword() | map(), function(), function() ) :: FFix.Command.t()
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
)
@spec command( FFix.Command.Input.source() | FFix.Command.Input.t() | [FFix.Command.Input.source() | FFix.Command.Input.t()] | keyword() | map(), function(), function(), keyword() ) :: FFix.Command.t()
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
@spec input(FFix.Command.Input.source()) :: FFix.Command.Input.t()
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.
@spec input( FFix.Command.Input.source(), keyword() ) :: FFix.Command.Input.t()
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.
@spec output( FFix.Command.Output.target(), keyword() ) :: FFix.Command.Output.t()
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
@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)
@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)
@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 asnullsink:settings- graph-level settings such assws_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]]
)
@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
@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"]
@spec to_filtergraph(FFix.Graph.t()) :: String.t()
Validates and serializes a graph to ffmpeg filtergraph syntax.
@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
@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.
@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.
@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)
@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
@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.