Ahosting Logo
Knowledge Base

How to Composite Videos: Picture-in-Picture and Side by Side

Two ways to put two videos in one frameOverlayStackWhat it doesplaces one video on top of the otherplaces them beside or above each otherOutput sizethe size of the base videothe sum of the inputsNeeds matchingnothing in particularthe shared dimension must matchTypical usepicture in picture, a webcam cornera before and after comparisonThe part that catches people is that stacking requires the shared dimension to match, so one input usually has to bescaled first.

Two videos into one frame comes up constantly. A webcam over a screen recording, a before-and-after comparison, a reaction inset. There are two filters and they suit different layouts.

Picture-in-picture

ffmpeg -i main.mp4 -i inset.mp4 -filter_complex \
 "[1:v]scale=320:-2[pip];[0:v][pip]overlay=W-w-20:H-h-20[v]" \
 -map "[v]" -map 0:a -c:a copy out.mp4

The inset is scaled first, because overlay places it at whatever size it already is; a full-resolution second video simply covers the first entirely.

In the position, uppercase W and H are the main video's dimensions and lowercase w and h are the overlay's. So W-w-20 is twenty pixels in from the right edge, and the four corners are:

overlay=20:20 # top left
overlay=W-w-20:20 # top right
overlay=20:H-h-20 # bottom left
overlay=W-w-20:H-h-20 # bottom right

Centred is overlay=(W-w)/2:(H-h)/2.

Side by side

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 out.mp4

hstack puts them side by side; vstack stacks one above the other. The output is twice as wide, or twice as tall, as the inputs.

The dimensions must match exactly. Two videos of different heights cannot be stacked horizontally, and the filter refuses rather than guessing.

Scale both to a common size first:

"[0:v]scale=640:360[l];[1:v]scale=640:360[r];[l][r]hstack=inputs=2[v]"

Forcing both to fixed dimensions distorts anything with a different shape. To keep proportions and pad instead:

scale=640:360:force_original_aspect_ratio=decrease,pad=640:360:(ow-iw)/2:(oh-ih)/2

Different lengths

This is the behaviour to decide rather than discover.

Stacking ends when the shorter input ends. An overlay runs for the length of the main video, with the inset simply disappearing when it finishes.

To hold the last frame of the shorter one instead:

"[1:v]tpad=stop_mode=clone:stop_duration=10[pip];[0:v][pip]overlay=20:20[v]"

Or add shortest=1 to the overlay to end the whole output with the inset.

Audio is not handled

Neither filter touches audio. Without a decision, the output has none.

Use one source's audio, as in the examples above, or mix both:

"[0:a][1:a]amix=inputs=2:duration=longest[a]"

Mixing two full-volume sources produces something louder and muddier than either. Reduce one first with volume=0.3, and check the result. Normalising audio loudness walks through getting the final level right.

Making the inset appear partway through

"[0:v][pip]overlay=20:20:enable='between(t,5,25)'[v]"

enable takes an expression in seconds, so the inset shows only between five and twenty-five seconds. Several overlays with different windows build a simple edit without any editing software.

Cost

Compositing decodes both sources and encodes one output, so it is roughly the work of both plus an encode.

Scale the inset before overlaying rather than after: the filter then works on far fewer pixels. Understanding FFmpeg filters deals with reading the graph, and adding watermarks and overlays walks through the still-image version of the same filter.

Scale the inset relative to the frame

Fixed pixel dimensions produce an inset that is correct on the video you tested and wrong on everything else.

ffmpeg -i main.mp4 -i inset.mp4 -filter_complex \
  "[1][0]scale2ref=w=iw*0.25:h=ow/mdar[in][base];[base][in]overlay=W-w-20:H-h-20" \
  -c:a copy out.mp4

Expressing the size as a proportion of the main frame means one command works on any input, and the inset occupies the same visual share regardless of resolution.

The position should be relative too. Coordinates measured from the frame edges keep the margin consistent, where fixed coordinates place the inset off screen on a smaller video.

Decide what happens to the audio

Compositing two videos produces one picture and does nothing about the two soundtracks, and the default keeps only the first.

ffmpeg -i main.mp4 -i inset.mp4 -filter_complex \
  "[0:v][1:v]overlay=W-w-20:H-h-20[v];[0:a][1:a]amix=inputs=2:duration=longest[a]" \
  -map "[v]" -map "[a]" out.mp4

Mixing both is one option and it produces a result where neither is clearly audible, since two voices at full level compete rather than combining.

Reducing the inset's audio before mixing, or keeping only one source, is usually what was intended. Decide deliberately rather than accepting whichever the command happened to keep.

Match the frame rates before compositing

Two sources recorded at different rates produce an output that stutters, and the cause is not visible in either input.

for f in main.mp4 inset.mp4; do
  printf '%-12s %s\n' "$f" "$(ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate,width,height -of csv=p=0 "$f")"
done

Read both before writing the command. Where they differ, converting the inset to match the main video is cheaper than converting the main one and produces the better result, since the inset is smaller.

Pixel format matters equally. A source in an unusual format composited onto a standard one produces colour shifts in the inset that look like a filter problem and are a format mismatch.

Check the result at the size it will be watched

An inset that is legible on a large screen is frequently a smudge on a phone, which is where most viewing happens.

ffmpeg -ss 5 -i out.mp4 -frames:v 1 -vf "scale=360:-2" small.png
ffmpeg -ss 5 -i out.mp4 -frames:v 1 full.png

Extract the same frame at both sizes and look at whether the inset still communicates anything.

A quarter width inset on a phone is roughly ninety pixels across. Anything that depends on detail at that size, such as a face at a distance or text, needs to be larger or does not belong as an inset at all.

The cost is a full encode, so do it once

Compositing cannot be done by copying, so every output involving an overlay is a complete re-encode of the main video.

time ffmpeg -nostdin -i main.mp4 -i inset.mp4 -filter_complex "[0][1]overlay=10:10" -c:a copy out.mp4
ffprobe -v error -show_entries format=duration,size -of default=nw=1 out.mp4

Produce a single composited master and derive every delivery size from that, rather than compositing separately for each output.

Where several arrangements are wanted, test them on a short fragment before committing to the full file. A thirty second sample answers every question about position and legibility at a fraction of the cost. Running FFmpeg jobs in parallel covers the queue.

Keep the two sources in sync

Compositing assumes both inputs start at the same moment, and material recorded separately almost never does.

ffmpeg -i main.mp4 -itsoffset 1.5 -i inset.mp4 -filter_complex \
  "[0:v][1:v]overlay=W-w-20:H-h-20" -c:a copy out.mp4

Offsetting one input shifts it relative to the other. Finding the right value means identifying a moment visible in both, such as a clap or a screen change, and measuring the difference.

Check the alignment at the end of the clip as well as the beginning. Two recordings at slightly different rates drift, and a fixed offset corrects the start while leaving the end wrong.

Name the output so you can tell versions apart

Compositing produces several attempts before one is right, and files called out.mp4 overwrite each other. Include the arrangement and the inset size in the name, so a directory of attempts is readable and the one that was approved can be identified later. It costs nothing while producing them and saves reproducing the work when somebody asks for the same treatment on a second clip a month later.