So here is what I came up with.
A not fully one-liner solution
1) Generate black clips
ffmpeg -f lavfi -i "color=black:s=1920x1080:r=30" \
-f lavfi -i "anullsrc=r=48000:cl=stereo" \
-c:v libx264 -c:a aac -t 3 empty_3s.mp4
Use lavfi filters to generate your empty clip. Of course, change the parameters according to your videos and what you want to achieve. Especially:
- in first (video) source filter:
s
(size) should match that of the videos you want to concatenate.
r
(frame rate) should also match your videos (especially if your want a constant frame rate output)
- in second (audio) source filter:
r
(sample rate) should match that of the audio tracks from the videos you want to concatenate.
cl
(channel layout) should also match the audio from your videos
Output parameters (codecs for instance) should match those of your videos. Use -t
to set the length (in seconds) of the empty clip.
2) Concatenate and fade your videos using a complex filtergraph
ffmpeg -i empty_1s.mp4 -i part_1.mp4 -i empty_3s.mp4 -i part_2.mp4 -i empty_1s.mp4 \
-filter_complex \
"[1:v] fade=t=in:st=0:d=2, fade=t=out:st=58:d=2 [1v]; \
[1:a] afade=t=in:st=0:d=2, afade=t=out:st=58:d=2 [1a]; \
[3:v] fade=t=in:st=0:d=2, fade=t=out:st=118:d=2 [3v]; \
[3:a] afade=t=in:st=0:d=1, afade=t=out:st=118:d=2 [3a]; \
[0:v] [0:a] [1v] [1a] [2:v] [2:a] [3v] [3a] [4:v] [4:a] concat=n=5:v=1:a=1 [v] [a]" \
-map "[v]" -map "[a]" -c:v libx264 -c:a aac output.mp4
Explanation:
- we first specify the inputs (
-i
) that we will concatenate
- we then set a complex filter (
-filter_complex
), that we use to:
- first, fade in and out our videos. To that end we use the
fade
(for video) and afade
(for audio) filters. We specify the start (st
) and duration (d
) for each fade. We output the results with outpad names ([1v]
, [1a]
...).
- then, concatenate the black clips and the previously faded videos, using the
concat
filter. We specify the number of input segments n
(i.e. the number of video+audio sets, in our case 5) we want to concat, and the number of video (v=1
) and audio (a=1
) streams for the output.
- we
map
the final output to the output streams from the concat filter
- specify the encoding parameters to whatever you want :)
Important notes:
- your videos (inputs) must have the same characteristics (pixel and sample format for video, sample rate and channel layout for audio). See concat documentation for more info.
- in the fade filter, we specify start time and duration in seconds, but you can use frames or samples as well with
s
and n
(video) or ss
and ns
(audio) options. See afade and fade filters documentation.
- you must know the duration of your videos for the fade filter, especially for fade-outs. See this question for hints on how two fade-out clips without having to manually specify start time.
True one-liner
If you really want a one-liner (and not having to encode empty black clips), you can use:
ffmpeg \
-f lavfi -t 1 -i "color=black:s=1920x1080:r=30" -f lavfi -t 1 -i "anullsrc=r=48000:cl=2" \
-i part_1.mp4 \
-f lavfi -t 3 -i "color=black:s=1920x1080:r=30" -f lavfi -t 3 -i "anullsrc=r=48000:cl=2" \
-i part_2.mp4 \
-f lavfi -t 1 -i "color=black:s=1920x1080:r=30" -f lavfi -t 1 -i "anullsrc=r=48000:cl=2" \
-filter_complex \
"[0:v] null [0v]; [1:a] anull [0a]; [3:v] null [2v]; [4:a] anull [2a]; \
[6:v] null [4v]; [7:a] anull [4a]; \
[2:v] fade=t=in:st=0:d=2, fade=t=out:st=58:d=2 [1v]; \
[2:a] afade=t=in:st=0:d=2, afade=t=out:st=58:d=2 [1a]; \
[5:v] fade=t=in:st=0:d=1, fade=t=out:st=118:d=2 [3v]; \
[5:a] afade=t=in:st=0:d=1, afade=t=out:st=118:d=2 [3a]; \
[0v] [0a] [1v] [1a] [2v] [2a] [3v] [3a] [4v] [4a] concat=n=5:v=1:a=1 [v] [a]" \
-map "[v]" -map "[a]" -c:v libx264 -c:a aac output.mp4
In that case we must beware of the input indices, as black video clips and null audio clips are separated inputs (I didn't figure out how to put them as two streams in a single input, if you have an idea feel free to share it).
Here I applied null
and anull
filters to those inputs to give them outpad tags, in order to use consistent identifiers in the concat
filter. But this is purely optional.
Hope this helps, feel free to improve my solution with your insights :)