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 imageoverlay=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 existerror. Cause: the label in-map "[x]"is not defined in-filter_complex, the name does not match, or another-maphas 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 amatches no streamserror instead. Fix: to use the same stream more than once, copy it withsplit(video) orasplit(audio) into separate labels, as in thesplitexample above. Input streams such as[0:v]can be used as often as you like. -
Symptom: an error from applying both
-filter_complexand-vfto the same stream. Cause: one stream cannot take both (different streams can: for example-filter_complexon the audio and-vfon 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.