Home / Alt manpages / ffmpeg-filters(1)

  • ffmpeg-filters(1)
  • User command
  • linux

Build Reliable FFmpeg Filtergraphs for Scaling and Overlays

You will finish with two repeatable FFmpeg patterns: a linear filter chain that produces a 320x180, 12 fps copy, and a filtergraph that branches video and places a smaller copy in its lower-right corner. The examples use the ffmpeg-filters manual installed on this machine. The package database reports FFmpeg 7:6.1.1-3ubuntu5+esm13, while the ffmpeg executable found in the shell reports version 8.0.1. Check your own executable before relying on version-specific behaviour.

Allow about 15 minutes. You need ffmpeg and ffprobe, a readable input video, and enough space for a new output. These commands create new files. They do not modify the input, and none requires root.

1. Confirm which FFmpeg you will run

Run these checks before debugging a graph:

$ command -v ffmpeg
/home/linuxbrew/.linuxbrew/bin/ffmpeg
$ ffmpeg -version | sed -n '1,2p'
ffmpeg version 8.0.1 Copyright (c) 2000-2025 the FFmpeg developers
$ command -v ffprobe
/home/linuxbrew/.linuxbrew/bin/ffprobe

The first line prevents a common distraction: reading one installation's manual while executing another installation's binary. The filter syntax below was run successfully with the reported 8.0.1 executable. If your command resolves elsewhere, test the graph on that executable too.

Checkpoint

Both commands must resolve, and ffmpeg -version must report the program you intend to use. If ffprobe is missing, install or select the matching FFmpeg distribution before continuing.

2. Understand the separators

A filtergraph is a directed set of connected filters. A comma joins filters in one chain, so frames pass through them in order. A semicolon separates chains. Square-bracket labels name streams so that one chain can feed another. This distinction is the key to reading a complex command:

[input] filter-a,filter-b [label]; [label] filter-c [output]

Filter arguments follow the filter name after =. They can be positional, such as scale=320:180, or named, such as scale=w=320:h=-2. Named arguments are easier to review when a graph has several values. The filter manual also permits an optional instance identifier after a filter name, but ordinary graphs rarely need one.

Put the complete graph in single quotes in a shell command. That keeps square brackets, semicolons and expressions together instead of allowing the shell to interpret them.

3. Resize and retime one input

Start with an input file and write a different output file. Replace the two obvious placeholders, keeping the original until you have checked the result:

$ ffmpeg -i /path/to/input.mp4 \
    -vf 'scale=w=320:h=-2,fps=12,format=yuv420p' \
    -c:v libx264 -an /path/to/output-320x180.mp4

-vf supplies one video filtergraph. The scale filter fixes the width at 320 pixels and calculates the height from the input aspect ratio because h=-2 requests a proportionate height divisible by two. For a 16:9 source, that becomes 320x180. It does not guarantee 320x180 for every source shape.

The fps=12 filter forces a constant output frame rate. Its documented default is 25 when no value is supplied, so state the rate when it matters. format=yuv420p requests a common pixel format. The output codec and container are separate from filtering: -c:v libx264 encodes video, while -an deliberately omits audio.

Verify dimensions and rate rather than trusting the filename:

$ ffprobe -v error -select_streams v:0 \
    -show_entries stream=width,height,r_frame_rate,nb_frames \
    -of default=nw=1 /path/to/output-320x180.mp4
width=320
height=180
r_frame_rate=12/1

If the source is not 16:9, choose an explicit height or use force_original_aspect_ratio=decrease with a bounding box. Do not stretch footage merely to match a target filename. If the output is shorter than expected, check whether the input has fewer frames and whether a later option, such as -t, limits duration.

4. Branch a stream and overlay the branch

Use -filter_complex when the graph has several chains or more than one input. This example splits the main video, scales one branch, then overlays it in the lower-right corner:

$ ffmpeg -i /path/to/input.mp4 \
    -filter_complex '[0:v]split=2[main][small];[small]scale=320:-2[thumb];[main][thumb]overlay=x=W-w-16:y=H-h-16:shortest=1[v]' \
    -map '[v]' -map 0:a? -c:v libx264 -c:a copy /path/to/output-overlay.mp4

[0:v] selects the first input's video stream. split=2 creates two outputs, which receive the labels [main] and [small]. The second chain scales [small] to 320 pixels wide. In the overlay expression, W and H are the main video's dimensions, and w and h are the overlay's dimensions. The 16-pixel offsets leave a margin.

The final label [v] identifies the filtered video that must be mapped. Without an explicit map, a complex graph can leave the selected output unclear. -map 0:a? keeps the first input's audio when it exists, and the question mark makes that map optional. The audio is copied, not filtered. If it is incompatible with the chosen output container, omit that map or encode audio explicitly.

The overlay filter has two inputs. Its framesync defaults repeat the last secondary frame and continue after the secondary input ends. Here shortest=1 asks the combined output to stop when the shorter input ends. That is useful for finite generated overlays, but it can unexpectedly shorten a real edit, so remove it when the main video should determine duration.

5. Diagnose the failures that waste most time

  • Unrecognised filter: run ffmpeg -filters and compare the executable with the manual you read. A different build may not include every filter.
  • Invalid argument: test one filter at a time with -vf. First prove scale=320:-2, then append ,fps=12, then the format conversion.
  • Unconnected output: inspect every label. A label is a connection point, not a filename. Each output that should reach the file must eventually be mapped.
  • Unexpected size: remember that -2 preserves proportion and that overlay coordinates are expressions evaluated against the main input.
  • Shell syntax errors: keep the graph in single quotes. If a graph needs a literal single quote, use the escaping rules in the manual or place the graph in a carefully constructed shell variable.

For a visual check, open a short output with a trusted media player. A successful exit status proves that FFmpeg completed the graph; it does not prove that the composition is visually what you intended.

Done means

  • The executable and manual were checked against the intended installation.
  • A linear graph produced the requested dimensions and frame rate, verified with ffprobe.
  • A branched graph used labels and an explicit output map for the filtered video.
  • The input remained untouched, and output paths were chosen as new files.
  • Audio handling was deliberate: copied, encoded, or omitted rather than assumed.