xfade joins two video clips with a transition (such as a crossfade, a wipe, a slide or a zoom) instead of a hard cut. FFmpeg 8.1 has 58 built-in transitions. Before xfade, you had to layer fade and overlay on time-shifted inputs.

Basic syntax

This joins two clips with a one-second crossfade:

ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=fade:duration=1:offset=4[v]" -map "[v]" output.mp4
Parameter Meaning Default
transition Transition type fade
duration Length of the transition in seconds 1
offset When the transition starts, in seconds from the start of the first clip 0

Working out offset

offset is counted from the start of the first clip. To crossfade over the last 2 seconds of a 10-second clip, use offset=8 with duration=2.

ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=fade:duration=2:offset=8[v]" -map "[v]" output.mp4

The output is offset + length of clip 2 long: the first 8 seconds of clip 1, then all of clip 2, with its first 2 seconds blended over the last 2 seconds of clip 1. You don’t need -t to set the length.

Set offset to length of clip 1 − duration. Other values cause problems:

  • If offset + duration is shorter than clip 1, the end of clip 1 is cut off. Two 5-second clips with offset=1:duration=2 gave a 6-second output (1 + 5).
  • If offset is past the end of clip 1, the transition never happens. The output ends with clip 1, and clip 2 is dropped without an error. The same clips with offset=8 gave a 5.03-second file, about the length of clip 1.

Available transitions

Fades

ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=fade:duration=1:offset=4[v]" -map "[v]" output.mp4
ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=fadeblack:duration=1:offset=4[v]" -map "[v]" output.mp4
ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=fadewhite:duration=1:offset=4[v]" -map "[v]" output.mp4

fade blends straight from one clip to the other. fadeblack and fadewhite pass through black or white in the middle.

Slides

ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=slideleft:duration=0.8:offset=4[v]" -map "[v]" output.mp4
ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=slideright:duration=0.8:offset=4[v]" -map "[v]" output.mp4
ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=slideup:duration=0.8:offset=4[v]" -map "[v]" output.mp4
ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=slidedown:duration=0.8:offset=4[v]" -map "[v]" output.mp4

Wipes

ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipeleft:duration=0.5:offset=4[v]" -map "[v]" output.mp4
ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=wipetl:duration=1:offset=4[v]" -map "[v]" output.mp4

Zoom and shape

ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=zoomin:duration=0.8:offset=4[v]" -map "[v]" output.mp4
ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=circleopen:duration=1:offset=4[v]" -map "[v]" output.mp4

Full list of transition names

fade, fadeblack, fadewhite, distance, wipeleft, wiperight, wipeup, wipedown, slideleft, slideright, slideup, slidedown, smoothleft, smoothright, smoothup, smoothdown, circleopen, circleclose, vertopen, vertclose, horzopen, horzclose, dissolve, pixelize, diagtl, diagtr, diagbl, diagbr, hlslice, hrslice, vuslice, vdslice, hblur, fadegrays, wipetl, wipetr, wipebl, wipebr, squeezev, squeezeh, zoomin, fadefast, fadeslow, circlecrop, rectcrop, radial, hlwind, hrwind, vuwind, vdwind, coverleft, coverright, coverup, coverdown, revealleft, revealright, revealup, revealdown

ffmpeg -h filter=xfade prints the names your build supports. The names describe the movement: diagtl is a diagonal wipe toward the top-left, hlslice slices horizontally from right to left, and squeezev squeezes vertically. If none of them fits, use transition=custom and write your own blend expression in expr.

Crossfading the audio too (acrossfade)

xfade handles only video. To crossfade the audio at the same time, add acrossfade to the filter graph.

ffmpeg -i input.mp4 -i input.mp4 -filter_complex "[0:v][1:v]xfade=transition=fade:duration=1:offset=4[v];[0:a][1:a]acrossfade=d=1[a]" -map "[v]" -map "[a]" output.mp4

acrossfade=d=1 crossfades the audio over one second. It has no offset: it always overlaps the end of the first stream with the start of the second. So the video and audio switch together only when offset = length of clip 1 − duration, which is the usual setting. Without acrossfade, -map 0:a -map 1:a gives two separate audio tracks, not one joined track.

Chaining three or more clips

Use the result of the first xfade as an input of the next one:

ffmpeg -i input.mp4 -i input.mp4 -i input.mp4 \
  -filter_complex "[0:v][1:v]xfade=transition=fade:duration=1:offset=4[v01];[v01][2:v]xfade=transition=fade:duration=1:offset=8[vout]" \
  -map "[vout]" output.mp4

The second offset is counted from the start of the joined stream [v01], which is clip1 + clip2 − duration1 long. With three 5-second clips, [v01] is 5 + 5 − 1 = 9 s, so the second transition starts at 9 − 1 = 8. In general, each new offset is previous offset + length of the clip added in the previous step − duration. With many clips this arithmetic gets tedious. The usual answer is a small script that reads each clip’s length with ffprobe and writes the filter string.

Input requirements

Both inputs of an xfade must have the same resolution, frame rate and timebase (the unit FFmpeg uses to count time), and both must have a constant frame rate. If they don’t match, you get errors such as First input link main parameters (size 1920x1080) do not match the corresponding second input link xfade parameters (size 1280x720). With variable-frame-rate phone footage, the timing around the transition can go wrong. Make the inputs match first:

ffmpeg -i clip1.mp4 -vf "scale=1280:720,fps=30" -c:v libx264 clip1_norm.mp4
ffmpeg -i clip2.mp4 -vf "scale=1280:720,fps=30" -c:v libx264 clip2_norm.mp4
ffmpeg -i clip1_norm.mp4 -i clip2_norm.mp4 -filter_complex "[0:v][1:v]xfade=transition=fade:duration=1:offset=4[v]" -map "[v]" output.mp4

Or do it inside the filter graph, without in-between files: [0:v]scale=1280:720,fps=30,format=yuv420p,settb=AVTB[a];[1:v]scale=1280:720,fps=30,format=yuv420p,settb=AVTB[b];[a][b]xfade=....

FAQ

The output ends after the first clip and the second never shows

offset is past the end of the first clip, so the transition point is never reached. Check the clip’s length with ffprobe and set offset = duration_of_clip1 − transition_duration.

Can xfade join files with a copy (no re-encode)?

No. To blend frames, FFmpeg has to decode and re-encode them. If you don’t need a transition, the concat demuxer with -c copy is the fast option. See Join videos without re-encoding.

Which transition is cheapest to encode?

The choice makes almost no difference. The transition runs only for duration seconds, and its cost is tiny next to the encoding. The time goes into re-encoding both clips in full, which xfade needs whichever transition you pick.