~/blog/filtergraph-guide
FiltersJul 28, 2026 · 8 min

FFmpeg filter_complex: A Beginner’s Guide

Scale, watermark, and tile video in a single command. A diagram-first guide to filter chains vs. filter graphs — read it once and you’ll be writing your own compound commands.

A single FFmpeg command that scales, watermarks, and tiles video side by side — that's not three commands glued together, it's a filter graph at work. filter_complex is the most powerful and most intimidating part of FFmpeg: brackets, pads, chains, a pile of symbols. This guide takes it apart with diagrams, and by the end you'll be writing your own compound commands.

First, Keep Them Straight: Filter Chain vs. Filter Graph

A filter chain is a serial pipeline of filters — the output of one becomes the input of the next, joined by commas:

scale=1280:-1,format=yuv420p,vflip

Data flows through in sequence: scale → convert pixel format → vertical flip.

A filter graph allows multiple chains that can fork, merge, and cross-connect. Semicolons separate chains, and square brackets mark pads:

[0:v]scale=640:480[a];[1:v]scale=640:480[b];[a][b]hstack

One-line summary: use -vf (filter chain) for single-stream serial processing; use -filter_complex (filter graph) for multiple streams, or any time you need to split and recombine. -filter_complex is the superset — a filter chain is just a filter graph with no branches.

Understanding Pads: The Wiring of a Filter Graph

Pads are the key to reading filter graphs. The label inside square brackets is a virtual wire connecting an upstream output to a downstream input:

input0 ──[0:v]──> scale ──[a]──┐
                               ├──> hstack ──> output
input1 ──[1:v]──> scale ──[b]──┘

The corresponding command:

ffmpeg -i left.mp4 -i right.mp4 -filter_complex \
  "[0:v]scale=640:480[left];[1:v]scale=640:480[right];[left][right]hstack" \
  out.mp4

Piece by piece:

  • [0:v]: the video stream of input 0 (0 is the -i order; v means video — audio is a)
  • scale=640:480[left]: the processed output gets the left label for downstream reference
  • [left][right]hstack: the hstack filter takes two input pads and joins them horizontally

An unlabeled output is automatically connected to the graph's final output; hstack's output carries no label, so it is the result.

How -vf and -filter_complex Relate

-vf-filter_complex
Input streamsExactly oneAny number
Can reference a 2nd inputNo (-i is just a data source)Yes, [1:v] references it directly
OutputMapped automatically to the output fileSelectable precisely with -map
AudioNot involvedCan be processed in the same graph

-vf is essentially single-stream syntactic sugar for -filter_complex. But note: don't mix -vf and -filter_complex in the same command — you can easily end up with two filter pipelines that don't know about each other.

Example 1: Scale + Watermark (the Most Common Composite Need)

Add an image watermark to the bottom-right corner while capping the video at 720p:

ffmpeg -i input.mp4 -i logo.png -filter_complex \
  "[1:v]scale=120:-1[wm];[0:v][wm]overlay=W-w-20:H-h-20,scale=-2:720" \
  out.mp4

Broken down:

  1. [1:v]scale=120:-1[wm]: scale the watermark to 120 pixels wide (-1 computes height from the aspect ratio)
  2. [0:v][wm]overlay=...: overlay the watermark on the main picture. W/H are the main picture's dimensions and w/h the watermark's, so W-w-20:H-h-20 means the bottom-right corner with a 20 px margin
  3. ,scale=-2:720: a chained scale at the tail after overlay — -2 auto-computes the width and rounds it to an even number (H.264 requires even dimensions; -1 will error out)

While you're at it, memorize the overlay position shorthand: 0:0 is top-left, (W-w)/2:(H-h)/2 is centered, 0:H-h is bottom-left.

Example 2: Multi-Stream Composition and Layout

A 2×2 video wall — scale each input to half width and height, then place them with xstack:

ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 -i d.mp4 -filter_complex \
  "[0:v]scale=iw/2:ih/2[a];[1:v]scale=iw/2:ih/2[b];\
   [2:v]scale=iw/2:ih/2[c];[3:v]scale=iw/2:ih/2[d];\
   [a][b][c][d]xstack=inputs=4:layout=0_0|w0_0|0_h0|w0_h0" \
  out.mp4

In layout, w0 means "the width of input 0" (and h0 likewise) — the relative positions of the pads are written directly into the layout expression.

Forking is just as natural: one source producing both a preview thumbnail and the final output:

ffmpeg -i input.mp4 -filter_complex \
  "[0:v]split=2[v1][v2];[v1]scale=320:-1[thumb];[v2]scale=1280:-2[main]" \
  -map "[thumb]" thumb.jpg -map "[main]" main.mp4

split=2 duplicates one stream into two, each flowing through a different chain, and -map decides which pad gets written to which output file. This is the real power of filter graphs over -vf.

Debugging Tips

Long filter graphs will have bugs. A few habits that save you:

  1. Verify one segment at a time. Get [0:v]scale=640:480[v] working before adding the next segment — writing the whole thing and then debugging is the slowest possible approach.
  2. Use -v error to cut log noise. FFmpeg's default logging is chatty; error-only output points you straight to the complaining filter.
  3. Error messages name the filter and the pad. The pad label after Invalid argument is your breakpoint — trace upstream from there and check its output parameters.
  4. Resolution mismatches are the #1 error source. hstack/vstack require equal heights/widths, and mismatched pixel formats make overlay silently slow — normalize with scale + format=yuv420p before joining.

Final Thoughts

The mental model for filter graphs is dataflow programming: each filter is a node, pads are the wires, and what you're designing is a graph, not a command. Once the [x] label wiring clicks, the rest is just looking up filter parameters.

If you'd rather not hand-write them, the Video Watermark and Video to GIF tools wrap the overlay and scale filters from this article in ready-to-use interfaces; to combine parameters freely, run your commands directly in the FFmpeg Command Lab — everything happens locally in your browser.

FAQ

Q: Does filter chain order matter? A: Yes, and it affects file size too. Scaling down first means subsequent filters process less data — but denoising filters usually belong before scaling. Order depends on the job.

Q: What's the difference between -1 and -2 in scale? A: -1 computes the missing dimension from the aspect ratio; -2 does the same and rounds it to an even number. H.264/H.265 require even dimensions, so in practice you should almost always use -2.

Q: Why is my overlay processing frame by frame and extremely slow? A: Usually the main video and the watermark have mismatched pixel formats, so FFmpeg performs an implicit format conversion on every frame. Adding an explicit format=yuv420p to both streams eliminates it.

RELATED TOOLS · Put it into practice