What Is a Filtergraph

A filtergraph is a set of connected filters. A filter is one processing step for video or audio, such as scaling or overlaying. Frames flow from filter to filter, and each filter changes them.

input → [scale] → [overlay] → output
                     ↑
                  [logo input]

For a simple job with one input and one output, use -vf (video) or -af (audio). When you need several inputs, several outputs or separate chains side by side, use -filter_complex.

Simple Filters: -vf and -af

Video Filters (-vf)

ffmpeg -i input.mp4 -vf "scale=1280:720" output.mp4
ffmpeg -i input.mp4 -vf "scale=1280:720,fps=30" output.mp4

Filters joined with , run one after another, left to right. This is called a filter chain.

Audio Filters (-af)

ffmpeg -i input.mp4 -af "volume=2.0" output.mp4

Filter Chain Separators

Symbol Meaning
, Move to the next filter within the same chain (the output of the previous filter becomes the input of the next)
; Start a separate filter chain (a parallel chain)

Basics of -filter_complex

In -filter_complex, you refer to streams by labels, names in square brackets such as [name].

Scale and Bind to a Specific Output Label

ffmpeg -i input.mp4 -filter_complex "[0:v]scale=640:360[v]" -map "[v]" -map 0:a output.mp4
  • [0:v] — the video stream of input 0 (the first input file)
  • scale=640:360 — the filter, which resizes the video to 640x360
  • [v] — the output label (any name you choose)
  • -map "[v]" — put the labeled output into the output file

Chaining Multiple Filters

ffmpeg -i input.mp4 -filter_complex "[0:v]scale=1280:720,fps=30[v]" -map "[v]" -map 0:a -c:a copy output.mp4

Watermark (Image Overlay)

The overlay filter takes two inputs:

ffmpeg -i input.mp4 -i logo.png \
  -filter_complex "[0:v][1:v]overlay=10:10[v]" \
  -map "[v]" -map 0:a -c:a copy output.mp4
  • [0:v] — the background video
  • [1:v] — the logo image
  • overlay=10:10 — place the logo at (10,10), measured from the top-left corner

Creating Multiple Outputs (Generate a Thumbnail and the Main Video at Once)

ffmpeg -i input.mp4 \
  -filter_complex "[0:v]scale=1280:720[main];[0:v]scale=320:180[thumb]" \
  -map "[main]" -c:v libx264 -crf 23 main.mp4 \
  -map "[thumb]" -c:v libx264 -crf 28 thumb.mp4

This writes two files from one input in a single run.

Processing Audio and Video Simultaneously

This scales the video and changes the audio volume in one command:

ffmpeg -i input.mp4 \
  -filter_complex "[0:v]scale=1280:720[v];[0:a]volume=1.5[a]" \
  -map "[v]" -map "[a]" -c:v libx264 -crf 23 output.mp4

Mixing Multiple Audio Streams with amix

Mix background music with the main audio:

ffmpeg -i main.mp4 -i bgm.mp3 \
  -filter_complex "[0:a][1:a]amix=inputs=2:duration=first:dropout_transition=2[a]" \
  -map 0:v -map "[a]" -c:v copy output.mp4

Splitting a Stream with split

Split one video stream into two and process each copy separately:

ffmpeg -i input.mp4 \
  -filter_complex "[0:v]split[v1][v2];[v1]scale=1280:720[main];[v2]scale=320:180[preview]" \
  -map "[main]" -c:v libx264 -crf 23 main.mp4 \
  -map "[preview]" -c:v libx264 -crf 30 preview.mp4

Common Errors and How to Fix Them

Filter 'split' has output 1 (b) unconnected

This appears when an output you labelled in -filter_complex (here [b]) is not used by any -map. Send every labelled output to an output file with -map, for example -map "[b]".

Simple and complex filtering cannot be used together for the same stream

You cannot apply -vf or -af to a stream that comes out of -filter_complex. Put all the filtering for that stream into -filter_complex.

Measured: time and size

The measured job takes one 1080p input and writes a 720p main file and a 360p preview in a single pass. This is the command that was measured:

ffmpeg -i input.mp4 -filter_complex "[0:v]split=2[v1][v2];[v1]scale=1280:720[main];[v2]scale=640:360[preview]" -map "[main]" -c:v libx264 -crf 23 -an main.mp4 -map "[preview]" -c:v libx264 -crf 28 -an preview.mp4
Metric Measured
Time 17.52 s (6.85x realtime)
Output size 41.14 MB

The 41.14 MB figure is the 720p output (main.mp4). The 360p preview is written in the same pass and is not counted in that number.

Rig: 1920x1080, 30 fps, 120 s, 351.4 MB source (Big Buck Bunny, looped, CC BY 3.0); Core i9-14900KF, 32 threads; FFmpeg 8.1 (gyan.dev); 2026-09-05; one command at a time. The raw numbers are in the dataset.

Why the number lands there

Two outputs took about as long as one 720p output. Scaling the same source to 720p on its own took 18.43 s. Writing 720p plus a 360p preview with split took 17.52 s.

Most of the time goes into encoding, not decoding, and encoding time depends on how many pixels there are. With split, FFmpeg decodes the input once; the only extra work is encoding a second, small stream of 640x360 frames. For comparison, re-encoding the same source at full 1080p with no filter took 27.97 s. Encoding two smaller outputs was faster than re-encoding one large one.

What a filter does cost you is stream copy: filtered video has to be re-encoded. On the same rig, operations that did not re-encode the video took 0.41 s to remap streams, 0.49 s to move the moov atom, and 1.03 s for an audio fade that copies the video and re-encodes only the audio. With filters, this example took 17.52 s. So before you add a split or a scale, check whether an operation on the container alone would do the job.

Common Pitfalls

  • Symptom: filters do not connect because , and ; are mixed up. Cause: , links filters one after another inside a chain, while ; starts a separate chain. Fix: use , to process one stream step by step, and ; when you branch or merge with labels. For example, scale=...,fps=... runs in series, and [0:v]...[v];[0:a]...[a] is two chains side by side.

  • Symptom: an Output with label 'x' does not exist error. Cause: the label in -map "[x]" is not defined in -filter_complex, the name does not match, or another -map has already used that label. Fix: map each label exactly once. Label names must match exactly, including upper and lower case.

  • Symptom: a label from inside the graph is used twice, and one output comes out unfiltered. Cause: a label such as [v1] that the graph itself creates can be used only once. FFmpeg 8.1 gives no error for a second use: it feeds that input straight from the first input file, so that output gets unprocessed video or audio. With a label like [v1], FFmpeg 6.1 stops with a matches no streams error instead. Fix: to use the same stream more than once, copy it with split (video) or asplit (audio) into separate labels, as in the split example above. Input streams such as [0:v] can be used as often as you like.

  • Symptom: an error from applying both -filter_complex and -vf to the same stream. Cause: one stream cannot take both (different streams can: for example -filter_complex on the audio and -vf on the video). Fix: put all the filtering for that stream into -filter_complex.

FAQ

Q. When should I use -vf versus -filter_complex? A. For one input and one output, -vf (or -af for audio) is enough. Use -filter_complex when you need several inputs (overlay, mix), several outputs, or branching and merging.

Q. Are there rules for label names? A. Any name made of letters and digits works, such as [v] or [main]. Names that say what the stream is for, like [thumb], are easier to read. Refer to input streams as [0:v] or [1:a]: the input number, a colon, then the stream type.

Q. Can I produce multiple outputs from one decode? A. Yes. Copy the stream with split and send each copy to a different file with its own -map. The input is decoded only once, so it is not read twice.

Q. Can I process audio and video at the same time? A. Yes. Process them in separate chains, for example [0:v]...[v];[0:a]...[a], and map both with -map "[v]" -map "[a]".

Q. Does filter order affect the result? A. Yes. For example, putting crop before scale gives a different result from putting it after. Filters joined with , run left to right, so list them in the order you want.