91

I have two video clips. Both are 640x480 and last 10 minutes. One contains background audio, the other one a singing actor. I would like to create a single 10 minute video clip measuring 1280x480 (in other words, I want to place the videos next to each other and play them simultaneously, mixing audio from both clips). I've tried trying to figure out how to do this with ffmpeg/avidemux, but so far I came up empty. They all refer to concatenating when I search for merging.

Any recommendations?

1

6 Answers 6

127

To be honest, using the accepted answer resulted in a lot of dropped frames for me.

However, using the hstack filter_complex produced perfectly fluid output:

ffmpeg -i left.mp4 -i right.mp4 -filter_complex hstack output.mp4
4
  • 27
    In case anyone is wondering how to stack more than 2 videos, you can simply specify hstack=inputs=3
    – Simbi
    Commented Jun 17, 2020 at 18:34
  • 2
    What to do when they're of different size? I got "Input 1 height 1080 does not match input 0 height 800" error.
    – trinity420
    Commented Jun 17, 2023 at 23:08
  • And vstack will stack inputs vertically btw. Commented Feb 13 at 4:15
  • @trinity420 you have to pipe one or both inputs through the scale filter to fix the same height before passing them to the hstack filter: unix.stackexchange.com/a/774936/32558 Commented Apr 21 at 10:21
75
ffmpeg \
  -i input1.mp4 \
  -i input2.mp4 \
  -filter_complex '[0:v]pad=iw*2:ih[int];[int][1:v]overlay=W/2:0[vid]' \
  -map '[vid]' \
  -c:v libx264 \
  -crf 23 \
  -preset veryfast \
  output.mp4

This essentially doubles the size of input1.mp4 by padding the right side with black the same size as the original video, and then places input2.mp4 over the top of that black area with the overlay filter.

Source: https://superuser.com/questions/153160/join-videos-split-screen

9
  • 1
    What does -map [vid] do?
    – 0xcaff
    Commented Dec 23, 2016 at 4:12
  • @caffinatedmonkey [vid] is a so-called linklabel, which is a defined output link label in the filter graph (the line above with the filter_complex). It maps the output from the complex filter graph to the output file.
    – Jan
    Commented Dec 26, 2016 at 12:34
  • I'm getting Unrecognized option 'crf'. Error splitting the argument list: Option not found. If I remove cr then unrecognized option 'preset'
    – R S
    Commented Mar 10, 2018 at 17:03
  • 2
    [vid] not in quotes doesn't work. --> -map "[vid]" \ Commented Dec 12, 2019 at 12:02
  • 1
    This works like a charm but a bit more explanation would be helpful.
    – Patryk
    Commented Mar 2, 2020 at 13:33
36

This can be done with just two filters and the audio from both inputs will also be included.

ffmpeg -i left.mp4 -i right.mp4 -filter_complex \
"[0:v][1:v]hstack=inputs=2[v]; \
 [0:a][1:a]amerge[a]" \
-map "[v]" -map "[a]" -ac 2 output.mp4
  • hstack will place each video side-by-side.
  • amerge will combine the audio from both inputs into a single, multichannel audio stream, and -ac 2 will make it stereo; without this option the audio stream may end up as 4 channels if both inputs are stereo.
7
  • Hello, Can I overlay one video on to another video?
    – Nisarg
    Commented Sep 21, 2016 at 11:38
  • @Nisarg Use the overlay filter.
    – llogan
    Commented Sep 21, 2016 at 22:25
  • I want upper video little transparent so one can see whats happening in background, Can you help me out with that?
    – Nisarg
    Commented Sep 22, 2016 at 5:01
  • Hello Sir, Can you care to look this out.
    – Nisarg
    Commented Sep 22, 2016 at 6:34
  • 3
    I lost audio with the accepted answer. This one works perfectly.
    – Matt Hough
    Commented Feb 22, 2019 at 8:48
8
ffmpeg -y -ss 0 -t 5 -i inputVideo1.mp4  
-ss 0 -t 5 -i inputVideo2.mp4  
-i BgPaddingImage.jpg  
-filter_complex "nullsrc=size=720*720[base];[base][2:v]overlay=1,format=yuv420p[base1];[0:v]setpts=PTS-STARTPTS,scale=345*700[upperleft];[1:v]setpts=PTS-STARTPTS,scale=345*700[upperright];[base1][upperleft]overlay=shortest=1:x=10:y=10[tmp1];[tmp1][upperright]overlay=shortest=1:x=366:y=10"  
-c:a copy -strict experimental  
-ss 0 -t 5 -preset ultrafast -an 
output.mp4

Add Two Video Side by side And Also Add OverLay Image That Show On Videos Padding With You can change Background Image [BgPaddingImage.jpg] here set your bg image path.

Show This below Video its create from Above command

enter image description here

4
  • 1
    What language is this? How do you run it?
    – muru
    Commented May 30, 2019 at 4:41
  • @muru Check it out now its FFMPEG Command ?? :) Commented May 30, 2019 at 5:04
  • 2
    @Sanjay Usually characters like [ and * would have to be escaped before the command could be understood by a shell. I think that is part of the confusion. What shell are you using? Commented Apr 22, 2020 at 6:41
  • bro i was run on android ffmpeg Commented Apr 23, 2020 at 16:54
0

Gradle Dependency

implementation "com.writingminds:FFmpegAndroid:0.3.2"

Code

Command to concate two videos side by side into one

val cmd : arrayOf("-y", "-i", videoFile!!.path, "-i", videoFileTwo!!.path, "-filter_complex", "hstack", outputFile.path)

Command to append two videos (one after another) into one

  val cmd : arrayOf("-y", "-i", videoFile!!.path, "-i", videoFileTwo!!.path, "-strict", "experimental", "-filter_complex",
                        "[0:v]scale=iw*min(1920/iw\\,1080/ih):ih*min(1920/iw\\,1080/ih), pad=1920:1080:(1920-iw*min(1920/iw\\,1080/ih))/2:(1080-ih*min(1920/iw\\,1080/ih))/2,setsar=1:1[v0];[1:v] scale=iw*min(1920/iw\\,1080/ih):ih*min(1920/iw\\,1080/ih), pad=1920:1080:(1920-iw*min(1920/iw\\,1080/ih))/2:(1080-ih*min(1920/iw\\,1080/ih))/2,setsar=1:1[v1];[v0][0:a][v1][1:a] concat=n=2:v=1:a=1",
                        "-ab", "48000", "-ac", "2", "-ar", "22050", "-s", "1920x1080", "-vcodec", "libx264", "-crf", "27",
                        "-q", "4", "-preset", "ultrafast", outputFile.path)

Note :

"videoFile" is your first video path.
"videoFileTwo" is your second video path.
"outputFile" is your combined video path which is our final output path

To create output path of video

fun createVideoPath(context: Context): File {
        val timeStamp: String = SimpleDateFormat(Constant.DATE_FORMAT, Locale.getDefault()).format(Date())
        val imageFileName: String = "APP_NAME_"+ timeStamp + "_"
        val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
        if (storageDir != null) {
            if (!storageDir.exists()) storageDir.mkdirs()
        }
        return File.createTempFile(imageFileName, Constant.VIDEO_FORMAT, storageDir)
    }

Code to execute command

try {
            FFmpeg.getInstance(context).execute(cmd, object : ExecuteBinaryResponseHandler() {
                override fun onStart() {

                }

                override fun onProgress(message: String?) {
                    callback!!.onProgress(message!!)
                }

                override fun onSuccess(message: String?) {
                    callback!!.onSuccess(outputFile)
                }

                override fun onFailure(message: String?) {
                    if (outputFile.exists()) {
                        outputFile.delete()
                    }
                    callback!!.onFailure(IOException(message))
                }

                override fun onFinish() {
                    callback!!.onFinish()
                }
            })
        } catch (e: Exception) {
            
        } catch (e2: FFmpegCommandAlreadyRunningException) {
           
        }
1
  • Welcome to the site, and thank you for your contribution. Please edit your post to indicate which language you used for the proposed code, and ideally a command-line example on how to apply it to the OPs problem.
    – AdminBee
    Commented Nov 11, 2021 at 9:38
0

Scale the input videos first to make it work for two videos of different height

The following command joins two GIFs side-by-side fixing height 300 px for each:

ffmpeg \
  -i 1.mkv \
  -i 2.mkv \
  -filter_complex '
  [0]scale=-1:300[a];
  [1]scale=-1:300[b];
  [a][b]hstack
  ' \
  12.mkv

enter image description here

If you don't scale the videos to the same height before passing them to hstack then the command fails with:

[Parsed_hstack_0 @ 0x5c75692dda40] Input 1 height 640 does not match input 0 height 480.
[Parsed_hstack_0 @ 0x5c75692dda40] Failed to configure output pad on Parsed_hstack_0
Error reinitializing filters!
Failed to inject frame into filter network: Invalid argument
Error while processing the decoded data for stream #1:0

I've created my test inputs 1.mkv (480x, blue background, 2 seconds) and 2.mkv (640x, green background, 3 seconds) with:

ffmpeg -y -f lavfi -i "
color=blue:480x480:d=2,
drawtext=
  fontcolor=black:
  fontsize=600:
  text='%{eif\:t\:d}':
  x=(w-text_w)/2:
  y=(h-text_h)/2
" 1.mkv
ffmpeg -y -f lavfi -i "
color=green:640x640:d=3,
drawtext=
  fontcolor=black:
  fontsize=800:
  text='%{eif\:t\:d}':
  x=(w-text_w)/2:
  y=(h-text_h)/2
" 2.mkv

The command creates the following processing graph:

1.mkv --> [0] --> scale --> [a] --+
                                  |
                                  v
                                  hstack --> 12.mkv
                                  ^
                                  |
2.mkv --> [1] --> scale --> [b] --+
  • first the scale filters modifies the videos to set them to the same height
  • then the hstack filter takes those scaled intermediate videos and "horizontally stacks" them

As we can see, if one videos is longer than the other, that command stops the shortest videos at the last frame.

To instead cut off the video to runtime of the shortest video we can add the shortest=1 option of hstack as in:

ffmpeg -y -i 1.mkv -i 2.mkv -filter_complex '[0]scale=-1:300[a];
  [1]scale=-1:300[b];[a][b]hstack=shortest=1' 12-short.mkv

enter image description here

Alternatively, to stack vertically we can use the vstack filter, and fix the video widths rather than heights:

ffmpeg -y -i 1.mkv -i 2.mkv -filter_complex '[0]scale=300:-1[a];
  [1]scale=300:-1[b];[a][b]vstack' 12-vert.mkv

enter image description here

All the above commands also just work if you change .mkv for .gif which is how I generated the images for this answer.

Tested on Ubuntu 23.10, ffmpeg 6.0.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .