44

I've been trying to achieve a crossfade transition between 2 video clips using ffmpeg but have failed so far. I'm new to ffmpeg and am mostly relying on tweaking what I can find in the documentation and existing examples online. From what I read so far, using either the blend or overlay filter should help in achieving what I'm after but I can't figure out the command line details to get it to work.

The fade and concat filters are great for fade-out of video 1, fade-in to video 2 and concat the 2 into 1 clip type transitions but I'd appreciate help in getting a command to transition from video 1 to video 2 without any going to black in between. I couldn't find any examples for exactly this problem anywhere, maybe I'm looking for the wrong keywords...?

More speficially, my videos are mp4s (h264 video, no sound, in case that matters), each is 5 seconds long and I'm after a transition from approx. 4.5s of video 1 to 0.5s of video 2.

Similar to what this tutorial does using MLT and frames (see 2:25 for an example fade), though I'm looking for a way to do this just in ffmpeg without calling any other progs. http://www.youtube.com/watch?v=3PRZ9L_KLdI

Any pointers or maybe a command line to get a fade like this would be much appreciated, thanks very much!

1

7 Answers 7

37

I suggest to do that way:

  • Create black background with the same duration and resolution as output video should be
  • Add alpha channel to each video
  • Add fade to alpha effect to each video
  • Use overlay on each video with black background

So the command for adding crossfade to 2 video (5 sec) each should be:

ffmpeg -i 1.mp4 -i 2.mp4 -f lavfi -i color=black -filter_complex \
"[0:v]format=pix_fmts=yuva420p,fade=t=out:st=4:d=1:alpha=1,setpts=PTS-STARTPTS[va0];\
[1:v]format=pix_fmts=yuva420p,fade=t=in:st=0:d=1:alpha=1,setpts=PTS-STARTPTS+4/TB[va1];\
[2:v]scale=960x720,trim=duration=9[over];\
[over][va0]overlay[over1];\
[over1][va1]overlay=format=yuv420[outv]" \
-vcodec libx264 -map [outv] out.mp4

This will fade out first video to alpha at the 4th second (st=4) with a duration of 1 second (d=1), fade in the second one at the 0th second (st=0) with a duration of 1 second (d=1), and move its display time forward to 4 sec (+4/TB). Then we just cut 9 second of black color, scale it to output video size and overlay the stuff.

Hope it helps.

13
  • Hi @ptQa, thanks very much, the crossfade effect works like a charm. Though what happens in the output video is that it only shows a top left section of the original videos and the rest of the frame is missing. I looked at the resolution, input is 960 x 720 and output is 320 x 240 but it doesn't scale down the whole video, instead cuts out a 320 x 240 section in the top left and shows that in the output video, rest of the input videos is missing. Is there an addition to your command line that prevents this?
    – user342545
    Jul 10, 2014 at 0:42
  • Oh, I got it, you should also scale black color to resolution that you want to get. See updated answer. I've added scale to filter graph.
    – ptQa
    Jul 10, 2014 at 8:39
  • That solved it, thanks. I uploaded a sample video showing the crossfading in case anyone wants to see it in action, youtu.be/JqorgXAjjTo
    – user342545
    Jul 11, 2014 at 0:42
  • 1
    BTW, I think there's an error in the answer's sample command -- it should be -i 1.mp4 -i 2.mp4..., right?
    – Jim Miller
    Sep 17, 2014 at 5:07
  • 1
    What's with the extraneous scale (resize) filter? That confuses things. A bare bones example would be great. Jan 1, 2018 at 0:43
18

FFmpeg now has a crossfade filter, released in version 4.3.

ffmpeg -i 1.mp4 -i 2.mp4 -filter_complex "xfade=offset=4.5:duration=1" output.mp4

And similarly, there's an audio version.

ffmpeg -i 1.mp4 -i 2.mp4 -filter_complex "xfade=offset=4.5:duration=1;acrossfade=duration=1" output.mp4
3
  • It's available on Homebrew!
    – durka42
    Feb 7, 2021 at 6:31
  • Unfortunately this doesn't work. It copies the audio but produces a completely blank output video.
    – durka42
    Feb 7, 2021 at 6:33
  • @durka42 it works for me. You may need additional stream selectors depending on your inputs.
    – OrangeDog
    Feb 7, 2021 at 12:06
11

ffmpeg-concat is the easiest way to accomplish what you want and allows you to use a bunch of sexy OpenGL transitions, with the default being crossfade.

ffmpeg-gl-transition is a custom ffmpeg filter which allows you to use GLSL to smoothly transition between two video streams. This filter is significantly easier to use and customize than the alternatives listed here.

This filter supports a large list of transition types, with the default being crossfade.

./ffmpeg -i 0.mp4 -i 1.mp4 -filter_complex "gltransition=duration=4:offset=1.5" out.mp4
3
  • 7
    Have you considered submitting a patch to FFmpeg?
    – llogan
    Dec 5, 2017 at 22:44
  • 2
    I definitely will, I just need to fix some small issues and test more first. Good call!
    – fisch2
    Dec 6, 2017 at 4:41
  • 3
    is this in the official FFmpeg? I can't seem to locate it Mar 1, 2019 at 5:58
5

This is how I did :

  • ffmpeg version N-77197-gdf2ce13
  • 2 videos of 4 seconds each.
  • Need to join it with fade between them.
  • videos are 25 fps.

1) Add fade out (light to dark) at the end of the 1st and fade in (dark to light) at the beggining of the 2nd:

ffmpeg -i 1.mp4 -y -vf fade=out:76:24 1f.mp4

ffmpeg -i 2.mp4 -y -vf fade=in:0:25 2f.mp4

76:24 mean the fade out will start frame 76 and will finish 24 frames later = 1s fade out.

0:25 mean the fade in will start frame 0 and will finish 25 frames later.

2) Merge the 2 videos

Convert all to TS

ffmpeg -i 1f.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts 1f.ts

ffmpeg -i 2f.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts 2f.ts

Merge

ffmpeg -i "concat:1f.ts|2f.ts" -bsf:a aac_adtstoasc -c copy output.mp4

Thanks to:

http://www.bogotobogo.com/FFMpeg/ffmpeg_fade_in_fade_out_transitions_effects_filters.php

3
  • 7
    This is not a crossfade.
    – Gyan
    Jan 27, 2016 at 15:33
  • 4
    What @Mulvya said. A crossfade fades one clip out at the same time as another is fading in. The first clip has its transparency steadily increased over the duration of the crossfade; the second has its transparency steadily decreased.
    – intuited
    May 23, 2016 at 6:44
  • and the question already describes this method
    – OrangeDog
    Jun 11, 2020 at 20:17
5

OrangeDog is right that native crossfades were recently introduced to FFmpeg (version 4.3.2, released on 8th June 2020) via the xfade filter. However, his answer is incomplete and doesn't explain how the filter works or take into account how complex it can be.


Using the xfade filter

The offset parameter is the point at the first input (in seconds) that you want the transition to start, and the duration parameter is how long (in seconds) you want the transition to take before it has finished transitioning to the second input.

The most common use of a crossfade is to fade from the very end of your first input to the beginning of your second one. However, this is trickier to do with xfade than it first seems: if the offset parameter is set too low it will fade too early and take a part of the end of the first input with it.

If the offset is set too high (more than VIDEO 1 DURATION + FADE DURATION) the crossfade will fail to be applied at all because the xfade filter considers this invalid - I'm guessing because it disregards the second input entirely in its calculations.

This means that the best that can be done with the xfade filter is to truncate as little as possible from the end of the first input, which typically means "only" a lost second or two of video at the end. This might be fine for your needs, in which case the xfade filter will be perfect for you.

If it isn't, then skip ahead to the end to use the older crossfade solution which - despite being a little more complicated to set up - does include the second input in its calculations, resulting in a crossfade that completely preserves the video streams of both inputs.

To begin, get the duration in seconds of your first input:

ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input1.mp4

Then get an upper limit value by doing VIDEO1 DURATION - FADE DURATION.

This upper limit is the highest offset value you can set - any higher and the xfade filter will fail to apply the crossfade. I recommend starting with it as the initial value and gradually lowering it until you're satisfied with the end result.

For example, where the duration of your first input is 170.500 seconds, and you want the crossfade to last for 2 seconds, the upper limit value will be 168.500:

ffmpeg -i input1.mp4 -i input2.mp4 -filter_complex \
"[0:v][1:v]xfade=duration=2:offset=168.500[outv];
 [0:a][1:a]acrossfade=duration=2[outa]" -map [outv] -map [outa] Output.mp4

If the result of that command is a crossfade in which the first input stream abruptly ends before the crossfade's transition does, then gradually lower the offset value by a second each time until the transition looks as smooth as possible.

When running these experimental runs, you can add the -preset ultrafast option to speed up encoding time - just remember to run the final encode without it to maximise compression for your final output file.

Using the transition parameter, xfade also allows you to make use of crossfade transitions other than the default fade. This example uses the pixelise transition:

ffmpeg -i input1.mp4 -i input2.mp4 -filter_complex \
"[v0][v1]xfade=transition=pixelise:duration=2:offset=168.721[outv];
 [0:a][1:a]acrossfade=duration=2[outa]" -map [outv] -map [outa] Output.mp4

The full list of available transitions can be found on the FFmpeg wiki.


If your sources don't have the same timebases and frame rates

Note that any of the above commands may fail with one of the following errors:

First input link main timebase (1/1500) do not match the corresponding second input link xfade timebase (1/30000)

First input link main frame rate (30/1) do not match the corresponding second input link xfade frame rate (30000/1001)

These errors mean your inputs have differing timebases and framerates respectively, which is highly likely when dealing with inputs from different sources. In these cases you will need the command below instead.

For reasons that are still confusing to me since everything is being re-encoded anyway, the xfade filter requires that the TBN and frame rate of each input be the same, similar to how the concat demuxer requires the TBN, resolution, codec and other attributes be the same.

If this is the case for you and you get one of the above errors, you'll need to set a common TBN and frame rate for each of your video streams before applying the xfade filter to them:

ffmpeg -i upscaled.mp4 -i outro.mp4 -filter_complex \
"[0:v]settb=AVTB,fps=30/1[v0];
 [1:v]settb=AVTB,fps=30/1[v1];
 [v0][v1]xfade=transition=fade:duration=2:offset=168.721[outv];
 [0:a][1:a]acrossfade=duration=3[outa]" -map [outv] -map [outa] Output.mp4

As far as I'm aware there's no equivalent to the AVTB constant for frame rates, so replace 30/1 above with an output frame rate that is suitable to you. 30/1 (which is FFmpeg notation for 30 FPS) is probably a safe bet but I'm no expert on frame rates and their implications.


Using a "traditional" crossfade

If, like me, you weren't completely happy with the results of the xfade filter, you can do a crossfade the old way instead.

This is a modified version of Gyan's answer here, and essentially works similarly to ptQa's answer except that it's less complicated and doesn't contain any filters that are irrelevant to the actual crossfade. It can essentially be thought of as a middle ground between the simplicity of the xfade filter and the complexity of ptQa's solution.

ffmpeg -i input1.mp4 -i input2.mp4 -filter_complex \
"color=black:WXH:d=VIDEO1+VIDEO2-FADE[base]; \
[0:v]setpts=PTS-STARTPTS[v0]; \
[1:v]format=yuva420p,fade=in:st=0:d=FADEDURATION:alpha=1,
    setpts=PTS-STARTPTS+((VIDEO1LENGTH-FADEDURATION)/TB)[v1]; \
[base][v0]overlay[tmp]; \
[tmp][v1]overlay,format=yuv420p[fv]; \
[0:a][1:a]acrossfade=d=AUDIOFADEDURATION[fa]" \
-map [fv] -map [fa] \
output.mp4

Replace WXH with your video's resolution.

Replace VIDEO1+VIDEO2-FADE with the result of the sum - what you get by adding the first input's length in seconds to the second input's length in seconds, minus the fade duration. Use ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4 to get the duration of each input.

Finally, replace each instance of VIDEO1LENGTH and VIDEO2LENGTH with their values, and FADEDURATION and AUDIOFADEDURATION with what you'd like them to be.

So if I wanted to crossfade between two videos that are 1920x1080, with the first input having a length of 170.300 seconds and the second a length of 9 seconds, and I want a crossfade of 2 seconds between them with an audio crossfade of 4 seconds, my command would look like this:

ffmpeg -i input1.mp4 -i input2.mp4 \
-filter_complex \
"color=black:1920x1080:d=177.300[base]; \
[0:v]setpts=PTS-STARTPTS[v0]; \
[1:v]format=yuva420p,fade=in:st=0:d=2:alpha=1,setpts=PTS-STARTPTS+((170.300-2)/TB)[v1]; \
[base][v0]overlay[tmp]; \
[tmp][v1]overlay,format=yuv420p[fv]; \
[0:a][1:a]acrossfade=d=4[fa]" \
-map [fv] -map [fa] Output.mp4
1

No sure for how long this has already been working, but at least as of 2021, an easier solution is ffmpeg's own xfade filter:

ffmpeg -i first.mp4 -i second.mp4 \
       -filter_complex "xfade=transition=fade:offset=60:duration=1" \
       out.mp4

...does a crossfade from first.mp4 to second.mp4 where the transition starts at 60 seconds and lasts 1 second.

2
0

You can use editly for this:

npm i -g editly
editly --transition-name linearblur --transition-duration 1 in.mp4 in.mp4 --out out.mp4

--transition-name can be found here