Comparing encoder settings, showing two camera angles at once, reaction videos, a stabilisation before/after — there are plenty of reasons to put two or more videos in one frame. FFmpeg has three stacking filters for this: hstack (horizontal), vstack (vertical) and xstack (arbitrary layout). They are shorter to write and faster to run than working out coordinates for overlay.

The one thing everyone trips over the first time is the rule that inputs must have the same height (or width). This article starts there.

Verified: FFmpeg 8.1 on Windows — all 10 commands in this article were executed by the verification script and passed (2026-09-06).

Horizontal (hstack) basics

ffmpeg -i left.mp4 -i right.mp4 -filter_complex "[0:v][1:v]hstack=inputs=2[v]" -map "[v]" -map 0:a -c:a copy output.mp4
  • [0:v][1:v] — the video streams of the first and second input
  • hstack=inputs=2 — place the two side by side, left to right
  • -map "[v]" — use the stacked result as the output video
  • -map 0:a -c:a copy — keep the left video’s audio untouched

Two 1920×1080 videos produce a 3840×1080 output. Resolutions are simply added together; nothing is scaled down for you.

The height-mismatch error

[Parsed_hstack_0] Input 1 height 720 does not match input 0 height 1080.
[Parsed_hstack_0] Failed to configure output pad on Parsed_hstack_0
Error reinitializing filters!

hstack requires every input to have the same height; vstack requires the same width. Pixel formats must match as well. The fix is to scale first.

ffmpeg -i left.mp4 -i right.mp4 -filter_complex "[0:v]scale=-2:720[l];[1:v]scale=-2:720[r];[l][r]hstack=inputs=2[v]" -map "[v]" -map 0:a output.mp4

scale=-2:720 means “height 720, width chosen to keep the aspect ratio and rounded to a multiple of 2”. Use -2 rather than -1 because an odd width makes libx264 stop with width not divisible by 2 (see Fix “height not divisible by 2”).

Keep the output at the original resolution

If the comparison should ship as 1080p, halve each input’s width before stacking.

ffmpeg -i left.mp4 -i right.mp4 -filter_complex "[0:v]scale=960:-2[l];[1:v]scale=960:-2[r];[l][r]hstack=inputs=2[v]" -map "[v]" -map 0:a output.mp4

A 16:9 source scaled to 960 wide becomes 960×540, so the output is 1920×540. To letterbox it to 1920×1080, append pad=1920:1080:0:(oh-ih)/2.

Measured: output size and encode time

Two copies of the same 1920×1080 / 30 fps / 10-second clip, stacked and encoded with libx264 -preset veryfast -crf 23 (i9-14900KF, FFmpeg 8.1, measured 2026-09-02).

Arrangement Output resolution File size Time
One clip re-encoded (baseline) 1920×1080 5.1 MB 1.1 s
Stacked as-is 3840×1080 10.2 MB 2.1 s
Each scaled to 960 wide, then stacked 1920×540 3.0 MB 1.2 s

Stacking at full size doubles the pixel count, so size and time roughly double. Shrinking first halves the pixel count relative to one original, and the result comes out smaller than a single clip. Pick the former when you need detail for a comparison and the latter when it’s going to social media.

Vertical (vstack)

ffmpeg -i top.mp4 -i bottom.mp4 -filter_complex "[0:v][1:v]vstack=inputs=2[v]" -map "[v]" -map 0:a output.mp4

Two 1080p clips stacked give 1920×2160. Use hstack to put two portrait (1080×1920) videos next to each other and vstack to pile landscape clips into a 9:16 phone format. This time it’s the width that has to match.

Three or more inputs, grids (xstack)

Three in a row is just hstack=inputs=3.

ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 -filter_complex "[0:v][1:v][2:v]hstack=inputs=3[v]" -map "[v]" output.mp4

A 2×2 grid uses xstack, listing each input’s top-left corner in layout.

ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 -i d.mp4 -filter_complex "[0:v][1:v][2:v][3:v]xstack=inputs=4:layout=0_0|w0_0|0_h0|w0_h0[v]" -map "[v]" output.mp4

How to read layout:

Entry Meaning
0_0 input 0 at top-left (x=0, y=0)
w0_0 input 1 at x = width of input 0, y = 0 (top-right)
0_h0 input 2 at x = 0, y = height of input 0 (bottom-left)
w0_h0 input 3 at bottom-right

w0 / h0 stand for the width and height of input 0; sums like w0+w1 are allowed. Four 720p inputs give a 2560×1440 output.

With only three inputs the bottom-right cell is empty; set fill to choose its colour. Omitting it does not cause an error, but the contents of the empty area are undefined (not necessarily black).

ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 -filter_complex "[0:v][1:v][2:v]xstack=inputs=3:layout=0_0|w0_0|0_h0:fill=black[v]" -map "[v]" output.mp4

Audio

The stack filters only touch video, so you decide what happens to the audio.

Use one input’s audio (the common case) — -map 0:a or -map 1:a.

Mix both — add amix to the same filter graph.

ffmpeg -i left.mp4 -i right.mp4 -filter_complex "[0:v][1:v]hstack=inputs=2[v];[0:a][1:a]amix=inputs=2[a]" -map "[v]" -map "[a]" output.mp4

By default amix scales each input by 1/N, so mixing two tracks lowers the overall level by about 6 dB. Disable that with amix=inputs=2:normalize=0 if it bothers you (watch for clipping).

No audio-an.

Inputs of different length

By default the output runs as long as the longest input and the shorter one freezes on its last frame. To stop when the shorter one ends, add shortest=1.

ffmpeg -i left.mp4 -i right.mp4 -filter_complex "[0:v][1:v]hstack=inputs=2:shortest=1[v]" -map "[v]" output.mp4

Divider line and Before / After labels

To make the boundary obvious, pad a few pixels onto the left input before stacking.

ffmpeg -i left.mp4 -i right.mp4 -filter_complex "[0:v]pad=iw+10:ih:0:0:color=black[l];[l][1:v]hstack=inputs=2[v]" -map "[v]" output.mp4

Labels are drawn with drawtext on each input separately, then stacked.

ffmpeg -i left.mp4 -i right.mp4 -filter_complex "[0:v]drawtext=text='Before':fontsize=24:fontcolor=white:x=10:y=10[l];[1:v]drawtext=text='After':fontsize=24:fontcolor=white:x=10:y=10[r];[l][r]hstack=inputs=2[v]" -map "[v]" output.mp4

Non-Latin labels need a font specified with fontfile=; see The drawtext filter.

FAQ

How is this different from overlay?

overlay places a foreground on top of a background — to put videos next to each other you first enlarge the canvas with pad and then compute where to place each one. hstack/vstack just line the inputs up, so the command is shorter and the processing lighter. For free positioning (picture-in-picture, for example) overlay is the right tool.

What if the frame rates differ?

There’s no error, but the output timestamps follow one input and the other can stutter. Match them first with fps=30 or similar.

I don’t want to read the same file twice

If you’re comparing the same footage with and without a filter, split one input into two branches and filter only one: [0:v]split[a][b];[b]eq=contrast=1.3[c];[a][c]hstack.

The Side-by-side video tool stacks two videos in your browser without uploading them, and matches the heights automatically.