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.