1028

I'm trying to concatenate two mp4 files using ffmpeg. I need this to be an automatic process hence why I chose ffmpeg. I'm converting the two files into .ts files and then concatenating them and then trying to encode that concatenated .ts file. The files are h264 and aac encoded and I'm hoping to keep the quality the same or as close to original as possible.

ffmpeg -i part1.mp4 -vcodec copy -vbsf h264_mp4toannexb -acodec copy part1.ts
ffmpeg -i part2.mp4 -vcodec copy -vbsf h264_mp4toannexb -acodec copy part2.ts
cat part1.ts part2.ts > parts.ts
ffmpeg -y -i parts.ts -acodec copy -ar 44100 -ab 96k -coder ac -vbsf h264_mp4toannexb parts.mp4

Unfortunately I'm getting the following error message coming back from ffmpeg during encoding:

[h264 @ 0x1012600]sps_id out of range
[h264 @ 0x1012600]non-existing SPS 0 referenced in buffering period
[h264 @ 0x1012600]sps_id out of range
[h264 @ 0x1012600]non-existing SPS 0 referenced in buffering period
[NULL @ 0x101d600]error, non monotone timestamps 13779431 >= 13779431kbits/s    
av_interleaved_write_frame(): Error while opening file

This happens about half way through encoding which makes me think that you can't concat two .ts files together and have it work.

10
  • 16
    This is a valid programing question IMHO, but if desired it might be moved to SuperUser.
    – Ed999
    Commented Jul 17, 2023 at 17:03
  • 1
    This is a valid command line programming question to create a particular automation process and avoid a specified error. At this time, StackOverflow has 26,149 questions tagged [ffmpeg]; SuperUser has 6,811 question tagged [ffmpeg]; VideoProduction has 2,117 questions tagged [ffmpeg]. This question could arguably be appropriate in StackOverflow, SuperUser or Video Production. If this one is moved, should the other SO [ffmpeg] questions also be reviewed?? Noteworthy that this question has been acceptable (and highly voted) on SO over a decade with many highly voted answers. Commented Nov 24, 2023 at 20:27
  • 2
    @marc-medley, the distinction is how FFmpeg is being invoked. Here's what the FFmpeg tag has to say about it: "Only questions about programmatic use of the FFmpeg libraries, API, or tools are on topic. Questions about interactive use of the command line tool should be asked on Super User or Video Production."
    – Chris
    Commented Nov 24, 2023 at 20:51
  • 1
    @Chris The scope of "programmatic use of the FFmpeg libraries, API, or tools" wording has ambiguity. For example, consider debugging a command line expression (i.e. use of tools) for inclusion in a programatic automation. This situation was addressed differently with the [[ffmpeg] tag in 2013.06.18](web.archive.org/web/20130618111108/https://stackoverflow.com/…) "ffmpeg command line tool should be asked on Super User". The tag-based "command line tool" restriction statement was later removed, and tools was still OK last I checked. However, that said, … 1/2 Commented Nov 24, 2023 at 22:20
  • 1
    @Chris However, that said, it would be helpful if the "What topics can I ask about here?" had definitive clarity on 'command line tool expressions'. As a software developer, I use 'command line tool expressions' programmatically in compiled code and interpreted scripts on a regular basis. To be clear, I'm OK with an appropriate move!! More noting that this question is prototypical of a fuzzy boundary that SO has had for a long, long time. … which affects many SO questions & tags. 2/2 Commented Nov 24, 2023 at 22:21

31 Answers 31

1775

FFmpeg has three concatenation methods:

1. concat video filter

Use this method if your inputs do not have the same parameters (width, height, etc), or are not the same formats/codecs, or if you want to perform any filtering.

Note that this method performs a re-encode of all inputs. If you want to avoid the re-encode, you could re-encode just the inputs that don't match so they share the same codec and other parameters, then use the concat demuxer to avoid re-encoding everything.

ffmpeg -i opening.mkv -i episode.mkv -i ending.mkv \
-filter_complex "[0:v] [0:a] [1:v] [1:a] [2:v] [2:a] \
concat=n=3:v=1:a=1 [v] [a]" \
-map "[v]" -map "[a]" output.mkv

2. concat demuxer

Use this method when you want to avoid a re-encode and your format does not support file-level concatenation (most files used by general users do not support file-level concatenation).

$ cat mylist.txt
file '/path/to/file1'
file '/path/to/file2'
file '/path/to/file3'
    
$ ffmpeg -f concat -i mylist.txt -c copy output.mp4

For Windows:

(echo file 'first file.mp4' & echo file 'second file.mp4' )>list.txt
ffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4

or

(for %i in (*.mp4) do @echo file '%i') > list.txt
ffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4

3. concat protocol

Use this method with formats that support file-level concatenation (MPEG-1, MPEG-2 PS, DV). Do not use with MP4.

ffmpeg -i "concat:input1|input2" -codec copy output.mkv

This method does not work for many formats, including MP4, due to the nature of these formats and the simplistic physical concatenation performed by this method. It's the equivalent of just raw joining the files.


If in doubt about which method to use, try the concat demuxer.

Also see

38
  • 5
    The Windows command prompt escape character is ^; I also ran into problems with the quotation marks so it became: ffmpeg -i concat:input1^|input2 -codec copy output
    – user423430
    Commented Jul 23, 2015 at 4:09
  • 3
    Helpful answer. And it made even more sense to me when I read this variation: superuser.com/a/607384/74576
    – Ryan
    Commented Feb 22, 2016 at 18:54
  • 7
    concat demuxer works for video, but I'm losing audio
    – Loïc
    Commented Sep 30, 2016 at 23:54
  • 80
    For a oneliner use: ffmpeg -safe 0 -f concat -i <(find . -type f -name '*' -printf "file '$PWD/%p'\n" | sort) -c copy output.mkv (mkv accepts more codecs than mp4, but you could also try it with mp4). The -safe 0 is for recent ffmpeg versions complaining about Unsafe file name, and the -type f is for only listing files. I added | sort to sort the files alphabetically; because find reads them in order as saved on filesystem. Works also for files with whitespaces.
    – erik
    Commented Dec 2, 2016 at 14:15
  • 11
    If (like me) you get "unknown keyword" in the concat demuxer method, then you didn't follow the format for list.txt. Each line must start with file.
    – idbrii
    Commented Feb 9, 2023 at 16:06
245

FOR MP4 FILES

For .mp4 files (which I obtained from DailyMotion.com: a 50 minute tv episode, downloadable only in three parts, as three .mp4 video files) the following was an effective solution for Windows 7, and does NOT involve re-encoding the files.

I renamed the files (as file1.mp4, file2.mp4, file3.mp4) such that the parts were in the correct order for viewing the complete tv episode.

Then I created a simple batch file (concat.bat), with the following contents:

:: Create File List
echo file file1.mp4 >  mylist.txt 
echo file file2.mp4 >> mylist.txt
echo file file3.mp4 >> mylist.txt

:: Concatenate Files
ffmpeg -f concat -i mylist.txt -c copy output.mp4

The batch file, and ffmpeg.exe, must both be put in the same folder as the .mp4 files to be joined. Then run the batch file. It will typically take less than ten seconds to run.
.

Addendum (2018/10/21) -

If what you were looking for is a method for specifying all the mp4 files in the current folder without a lot of retyping, try this in your Windows batch file instead (MUST include the option -safe 0):

:: Create File List
for %%i in (*.mp4) do echo file '%%i'>> mylist.txt

:: Concatenate Files
ffmpeg -f concat -safe 0 -i mylist.txt -c copy output.mp4

This works on Windows 7, in a batch file. Don't try using it on the command line, because it only works in a batch file!

23
  • This is a good solution but talking about a batch file makes this sound primitive(like it was done in batch), and more complex than it is(because a batch file is completely unnecessary). It is much clearer if you simply show C:\blah>type mylist.txt<ENTER> (So they see the contents of that file) Then ffmpeg -f concat -i mylist.txt -c copy output.mp4<ENTER> Also you should include a reference trac.ffmpeg.org/wiki/Concatenate (that reference even uses the same filename, mylist.txt)
    – barlop
    Commented Oct 10, 2018 at 1:37
  • 4
    This is a very good solution cos it saves you from typing in lengthy lists of files for -i parameter. On Linux I did this: ls -1 P1041*.mp4 | sed s/"P"/" file P"/g > mylist.txt which is even cooler than coding a batch script. Anyway, -c copy performs very quickly (which I was not aware of) and this is the real magic of this answer.
    – Würgspaß
    Commented Oct 11, 2018 at 18:17
  • 1
    Your last code doesn't work. I have to use this instead: (for %i in (*.mp4) do @echo file '%i') > mylist.txt.
    – Xam
    Commented Nov 8, 2018 at 3:48
  • 4
    With my mp4 files, there is no audio at all in the output file after running your concat code on a Mac, using ffmpeg v3.2.2.
    – kakyo
    Commented Mar 2, 2019 at 14:07
  • 3
    del mylist.txt should be added at the top
    – Nime Cloud
    Commented Jun 2, 2021 at 16:44
109

Following concatenates three .mp3 files into one .m4a.

ffmpeg -i input1.mp3 -i input2.mp3 -i input3.mp3 -filter_complex "concat=n=3:v=0:a=1" -vn -y input.m4a

Meanings of Options

-filter_complex "concat=n=3:v=0:a=1

  • concat: concatenate filter joining streams
  • n: count of input segments (= synchronized audio-video streams or audio-only or video-only stream)
  • v: output video stream count
  • a: output audio stream count
  • -vn: disable video (-an would disable audio)
  • -y: overwrite output files without prompts

Refer man ffmpeg or ffmpeg -h full to print all options (including all format and codec specific options).

6
  • 4
    f i use this -f concat -safe 0 -i "file.txt" -c copy -y "OutPut.mp4" it will concat but when some videos are with audio and some are without audio it is not working how can i sort out this. Thanks –
    – Ahmad
    Commented Sep 5, 2017 at 7:16
  • 2
    Worked like charm, just had to change "v=0" to "v=1" and the rest was solved!
    – Ray
    Commented Oct 20, 2020 at 23:33
  • 1
    You make it sound like v and a are booleans; a 'yes' or 'no' whether the output should contain video/audio or not. This is not the case. They are actually integers. FFmpeg documentation on the concat-filter: "Set the number of output video/audio streams, that is also the number of video/audio streams in each segment". Even in the example you can see a is set to 2 for instance.
    – Reino
    Commented Jun 7, 2021 at 15:05
  • Error Input link in0:v0 parameters (size 428x640, SAR 0:1) do not match the corresponding output link in0:v0 parameters (428x640, SAR 1576:1575)
    – user3310334
    Commented Mar 2, 2022 at 22:31
  • Worked for me, but also needed to change v=0 to v=1, as @Beyar did.
    – Shiping
    Commented Dec 31, 2022 at 0:34
75

for MP4 files:

If they are not exactly same (100% same codec, same resolution, same type) MP4 files, then you have to trans-code them into intermediate streams at first:

ffmpeg -i myfile1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts temp1.ts
ffmpeg -i myfile2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts temp2.ts
// now join
ffmpeg -i "concat:temp1.ts|temp2.ts" -c copy -bsf:a aac_adtstoasc output.mp4

NOTE!: Read comments to know some potential shortcomings.

13
  • 2
    As illustrated in my answer, below, transcoding the files is not necessary. FFmpeg can concatenate multiple mp4 files into a single mp4 file - without any intermediate steps, and without any re-encoding. The only case in which any re-encoding would be needed is if the original files are not mp4 files but the intention is to create an mp4 file as the output file.
    – Ed999
    Commented Mar 5, 2017 at 15:02
  • @T. Todua : I'd be interested to learn what precisely are the differences between your two mp4 source files. Are they using different sample rates (e.g. 44,100 Hz and 48,000 Hz)? Or are they using different bit rates (e.g. 128 kbps and 64 kbps)? In either such case, I don't quite understand how the command -c copy (a stream-copy instruction) would modify the source files to make them compatible.
    – Ed999
    Commented Apr 27, 2017 at 11:11
  • 8
    @T.Todua Hi, the command works good when videos are without audio track. However, when the myfile1.mp4 is without audio and the myfile2.mp4 is with audio, the result output.mp4 is without audio. The expected output.mp4 should contains audio. How can i solve it?
    – AnswerZhao
    Commented May 23, 2017 at 9:47
  • 2
    @T.Todua er....It's too bad. The file1|file2 generate the different video with the file2|file1. the order is important.
    – AnswerZhao
    Commented Nov 1, 2017 at 15:22
  • 3
    I tried your command on concat 2 mp4 files. It works. However, the audio of the second file gets choppy like Ah-oh-Ee voices in the produced new concatenation. do you have a fix to this?
    – superlinux
    Commented Feb 17, 2019 at 11:36
68

Here's a fast (takes less than 1 minute) and lossless way to do this without needing intermediate files:

ls Movie_Part_1.mp4 Movie_Part_2.mp4 | \
perl -ne 'print "file $_"' | \
ffmpeg -f concat -i - -c copy Movie_Joined.mp4

The "ls" contains the files to join The "perl" creates the concatenation file on-the-fly into a pipe The "-i -" part tells ffmpeg to read from the pipe

(note - my files had no spaces or weird stuff in them - you'll need appropriate shell-escaping if you want to do this idea with "hard" files).

9
  • 5
    With proper escaping for files containing whitespace:ls Movie_Part_1.mp4 Movie_Part_2.mp4 | perl -ne '$_ =~ s/\n$//; print "file '"'"'$_'"'"'\n"' | ffmpeg -f concat -i - -c copy Movie_Joined.mp4 Commented Jan 28, 2015 at 15:35
  • 5
    You can also just do ls * | perl -ne 'print "file $_"' | ffmpeg -f concat -i - -c copy Movie_Joined.mp4 if your files are well-named and in order. Worked great for me on OS X, exactly what I wanted.
    – Nate Beaty
    Commented Jan 25, 2016 at 18:54
  • 4
    On Linux I used this oneliner: ffmpeg -safe 0 -f concat -i <(find . -type f -name '*' -printf "file '$PWD/%p'\n" | sort) -c copy output.mkv (mkv accepts more codecs than mp4, but you could also try it with mp4). The -safe 0 is for recent ffmpeg versions complaining about Unsafe file name, and the -type f is for only listing files. I added | sort to sort the files alphabetically; because find reads them in order as saved on filesystem. Works also for files with whitespaces.
    – erik
    Commented Dec 2, 2016 at 14:14
  • 24
    I had to add this parameter to ffmpeg to make it work: -protocol_whitelist file,tcp,http,pipe
    – Bensge
    Commented Oct 5, 2017 at 17:08
  • 2
    I had to add the protocol white list for file and pipe on OSX. Here's what worked for me: ls file1.mp4 file2.mp4 | perl -ne 'print "file $_"' | ffmpeg -f concat -safe 0 -protocol_whitelist "file,pipe" -i - -c copy output.mp4 Commented May 5, 2020 at 11:22
66

I found the pipe operator did not work for me when using option 3 to concat several MP4s on a Mac in the accepted answer.

The following one-liner works in bash (Mac, Linux) and does not require an intermediate file:

ffmpeg -f concat -safe 0 -i <(for f in ./*.mp4; do echo "file '$PWD/$f'"; done) -c copy output.mp4

Here, the <() syntax actually creates a temporary file "in the background" so to say

2
  • 1
    this worked for me in 2024 on macOS 14.4 Commented May 19 at 7:35
  • for natcase sort, and with only warning: ffmpeg -f concat -safe 0 -i <(for f in ./*.mp4; do echo "file '$PWD/$f'"; done | sort -fV) -loglevel warning -c copy output.mp4 Commented Sep 15 at 13:15
43

I ended up using mpg as the intermediate format and it worked (NOTE this is a dangerous example, -qscale 0 will re-encode the video...)

ffmpeg -i 1.mp4 -qscale 0 1.mpg
ffmpeg -i 2.mp4 -qscale 0 2.mpg
cat 1.mpg 2.mpg | ffmpeg -f mpeg -i - -qscale 0 -vcodec mpeg4 output.mp4
8
  • 9
    As of this comment, -sameq was removed. Use -qscale 0 instead.
    – Xavier Ho
    Commented Jan 18, 2013 at 3:28
  • 9
    -sameq does not mean "same quality" and has been removed from ffmpeg as mentioned by @XavierHo.
    – llogan
    Commented Aug 22, 2013 at 22:18
  • 5
    It seems a shame to have to transcode the video through an intermediate format just to concatenate two files. You'll get generation loss. Better to not go deeper than the container format and use the concat command in ffmpeg, as @rogerdpack does above. Commented Oct 8, 2013 at 20:43
  • 1
    This method reduces the quality of the mp4 video.
    – bozzmob
    Commented Jul 3, 2019 at 18:58
  • 1
    When trying to concatenate videos of similar resolution/ aspect ratio, works great. But if you have different resolutions, this method will try to make them all same resolution which is not correct.
    – Gru
    Commented Aug 24, 2021 at 14:25
24

Here 2 pure bash solutions using only ffmpeg and not using

  • an intermediary file
  • perl, python nor javascript

One-liner solution using ls

ls video1.mp4 video2.mp4 | while read line; do echo file \'$line\'; done | ffmpeg -protocol_whitelist file,pipe -f concat -i - -c copy output.mp4

Function which takes 0 or 2+ arguments

#######################################
# Merge mp4 files into one output mp4 file
# usage:
#   mergemp4 #merges all mp4 in current directory
#   mergemp4 video1.mp4 video2.mp4
#   mergemp4 video1.mp4 video2.mp4 [ video3.mp4 ...] output.mp4 
#######################################
function mergemp4() {
  if [ $# = 1 ]; then return; fi

  outputfile="output.mp4"

  #if no arguments we take all mp4 in current directory as array
  if [ $# = 0 ]; then inputfiles=($(ls -1v *.mp4)); fi
  if [ $# = 2 ]; then inputfiles=($1 $2); fi  
  if [ $# -ge 3 ]; then
    outputfile=${@: -1} # Get the last argument
    inputfiles=(${@:1:$# - 1}) # Get all arguments besides last one as array
  fi
  
  # -y: automatically overwrite output file if exists
  # -loglevel quiet: disable ffmpeg logs
  ffmpeg -y \
  -loglevel quiet \
  -f concat \
  -safe 0 \
  -i <(for f in $inputfiles; do echo "file '$PWD/$f'"; done) \
  -c copy $outputfile

  if test -f "$outputfile"; then echo "$outputfile created"; fi
}

Note: had tried some solutions in this thread and none satisfied me

22

The main answer didn't work for me because it gave me the errors I describe below in the "Troubleshooting" section, so here is my own answer.

If you're looking for how to combine many .mov or .MOV files into one instead, see my other answer here: Super User: Combine MOV video files. I also have a find command there to gather all files of interest rapidly into files.txt or inputs.txt, rather than having to do it manually.

How to concatenate or combine many mp4 video files using ffmpeg

Quick Summary

Create an inputs.txt file containing a list of all file paths to combine (see example below). Then, combine all above videos into one like this:

# Option 1 (preferred): into a single mp4 video with AAC audio encoding
# and original video encoding
time ffmpeg -f concat -safe 0 -i inputs.txt -c:v copy -c:a aac output.mp4

# Option 2: into a single .mkv video with original audio and video encoding
time ffmpeg -f concat -safe 0 -i inputs.txt -c copy output.mkv

Details and full example of combining Wyze security camera footage

Tested on Ubuntu 20.04, combining a bunch of my Wyze security camera videos into one. (I plugged in the camera's micro SD card directly into my computer).

  1. Create a file named inputs.txt, containing a list of all files you'd like to concatenate. Ex:

    file 'path/to/file1.mp4'
    file 'path/to/file2.mp4'
    file 'path/to/file3.mp4'
    

    In my case, I combined 69 minutes of video from 13 Nov. 2022 at 18:31hrs (6:31pm) to 13 Nov. 2022 at 19:39hrs (7:39pm). In Wyze security camera paths, that means from record/20221113/18/31.mp4' to record/20221113/19/39.mp4, inclusive. So, here is my full inputs.txt file. Note that you can rapidly and easily edit or create such a file by using Sublime Text's or MS VSCode's multi-cursor edit mode to edit multiple lines and locations at once. I used Sublime Text in this case.

    file '/media/gabriel/3339-3730/record/20221113/18/31.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/32.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/33.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/34.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/35.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/36.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/37.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/38.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/39.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/40.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/41.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/42.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/43.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/44.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/45.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/46.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/47.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/48.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/49.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/50.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/51.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/52.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/53.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/54.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/55.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/56.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/57.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/58.mp4'
    file '/media/gabriel/3339-3730/record/20221113/18/59.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/00.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/01.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/02.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/03.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/04.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/05.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/06.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/07.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/08.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/09.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/10.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/11.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/12.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/13.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/14.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/15.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/16.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/17.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/18.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/19.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/20.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/21.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/22.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/23.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/24.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/25.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/26.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/27.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/28.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/29.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/30.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/31.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/32.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/33.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/34.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/35.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/36.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/37.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/38.mp4'
    file '/media/gabriel/3339-3730/record/20221113/19/39.mp4'
    
  2. Combine all above videos into one:

    # Option 1 (preferred): into a single mp4 video with AAC audio encoding
    # and original video encoding
    time ffmpeg -f concat -safe 0 -i inputs.txt -c:v copy -c:a aac output.mp4
    
    # Option 2: into a single .mkv video with original audio and video encoding
    time ffmpeg -f concat -safe 0 -i inputs.txt -c copy output.mkv
    

In my case, the original 69 files took up 411.1 MB. Here are the results of the two commands above:

  1. output.mp4 took 11 seconds to produce and is 380.3 MB in size. The AAC-encoded audio is apparently a little smaller.
  2. output.mkv took 8 seconds to produce and is 410.7MB in size.

Troubleshooting/possible errors

  1. If you run ffmpeg -f concat -i inputs.txt -c copy output.mp4 and get this error:

    [concat @ 0x563ca0173700] Unsafe file name '/media/gabriel/3339-3730/record/20221113/18/31.mp4'
    inputs.txt: Operation not permitted
    

    ...it is because you forgot to use -safe 0.

    See:

    1. https://nono.ma/says/concat-unsafe-file-name-operation-not-permitted
    2. ffmpeg concat: "Unsafe file name"
  2. If you run ffmpeg -f concat -safe 0 -i inputs.txt -c copy output.mp4 and get this error:

    [mp4 @ 0x55ced96f2100] Could not find tag for codec pcm_alaw in stream #1, codec not currently supported in container
    Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument
    

    ...it is because "ffmpeg does not support PCM (pcm_alaw, pcm_s16le, etc) in the MP4 container." See here: codec not currently supported in container and here. So, run ffmpeg -f concat -safe 0 -i inputs.txt -c:v copy -c:a aac output.mp4 instead, to re-encode the audio into AAC format. Or, run ffmpeg -f concat -safe 0 -i inputs.txt -c copy output.mkv to write into a .mkv container instead of into a .mp4 container.

19

Detailed documentation on various ways of concatenation in ffmpeg can be found here.

You can use 'Concat filter' for quick concatenation.

It performs a re-encode. This option is best when inputs have different video/audio formats.

For Concatenating 2 files:

ffmpeg -i input1.mp4 -i input2.webm \
-filter_complex "[0:v:0] [0:a:0] [1:v:0] [1:a:0] concat=n=2:v=1:a=1 [v] [a]" \
-map "[v]" -map "[a]" output.mp4

For Concatenating 3 files:

ffmpeg -i input1.mp4 -i input2.webm -i input3.mp4 \
-filter_complex "[0:v:0] [0:a:0] [1:v:0] [1:a:0] [2:v:0] [2:a:0] concat=n=3:v=1:a=1 [v] [a]" \
-map "[v]" -map "[a]" output.mp4

This works for same as well as multiple input file types.

10
  • @Developer: For explanation of options -> Concat filter wiki
    – SJ00
    Commented Apr 4, 2016 at 17:52
  • 5
    When concatenating 3 files you should use concat=n=3:v=1:a=1 not n=2
    – Cynapsis
    Commented Sep 1, 2016 at 13:19
  • 1
    i'm not getting lossless concatenation my ouput.mp4 video bitrate is significantly lower than the input videos video bitrate
    – jasan
    Commented Sep 5, 2017 at 16:09
  • @jasan, concat filter performs a re-encode of output. For lossless output, go for concat protocol or concat demuxer, but both of these options needs you to be specific about input formats.
    – SJ00
    Commented Sep 15, 2017 at 12:11
  • Please do NOT post "solutions" that only concatenate 2 files. Many users will be unaware that concatenation of only 2 files is a special case, and when they try out the method suggested on 2 files it may well work (e.g. two .m4a files), but they will then be left all at sea when they use it with 3 files of the same type and it fails. Almost any two mp4 / m4a / m4b files will concatenate, if they have identical bitrates and sample rates, even where they have different durations, but if you add a third such file they will fail to concatenate because they have different durations.
    – Ed999
    Commented Oct 21, 2018 at 13:27
17

this worked for me (on windows)

ffmpeg -i "concat:input1|input2" -codec copy output

an example...

ffmpeg -i "concat:01.mp4|02.mp4" -codec copy output.mp4

Python

Using some python code to do it with as many mp4 there are in a folder (install python from python.org, copy and paste and save this code into a file called mp4.py and run it from the cmd opened in the folder with python mp4.py and all the mp4 in the folder will be concatenated)

import glob
import os

stringa = ""
for f in glob.glob("*.mp4"):
    stringa += f + "|"
os.system("ffmpeg -i \"concat:" + stringa + "\" -codec copy output.mp4")

Version 2 with Python

Taken from my post on my blog, this is how I do it in python:

import os
import glob

def concatenate():
    stringa = "ffmpeg -i \"concat:"
    elenco_video = glob.glob("*.mp4")
    elenco_file_temp = []
    for f in elenco_video:
        file = "temp" + str(elenco_video.index(f) + 1) + ".ts"
        os.system("ffmpeg -i " + f + " -c copy -bsf:v h264_mp4toannexb -f mpegts " + file)
        elenco_file_temp.append(file)
    print(elenco_file_temp)
    for f in elenco_file_temp:
        stringa += f
        if elenco_file_temp.index(f) != len(elenco_file_temp)-1:
            stringa += "|"
        else:
            stringa += "\" -c copy  -bsf:a aac_adtstoasc output.mp4"
    print(stringa)
    os.system(stringa)

concatenate()
3
  • 10
    The concat protocol should not be used with MP4 and will not work as mentioned in the accepted answer.
    – llogan
    Commented May 26, 2018 at 20:17
  • 3
    Not only (as Lord Neckbeard points out) is it necessary to convert an .mp4 file to the intermediate .TS format in order for concatenation to succeed, but, perhaps even worse, this answer does not even begin to address the need for the input video files to have matching bitrate, frame rate, sample rate, timebase, and duration. Without all 5 of those factors in common, files cannot be concatenated (i.e. joined using stream copy), but must be re-encoded instead.
    – Ed999
    Commented Nov 6, 2018 at 0:18
  • 1
    This doesn't work for WEBM files.
    – Raleigh L.
    Commented Jun 11, 2022 at 22:08
16

From the documentation here: https://trac.ffmpeg.org/wiki/Concatenate

If you have MP4 files, these could be losslessly concatenated by first transcoding them to MPEG-2 transport streams. With H.264 video and AAC audio, the following can be used:

ffmpeg -i input1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate1.ts
ffmpeg -i input2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate2.ts
ffmpeg -i "concat:intermediate1.ts|intermediate2.ts" -c copy -bsf:a aac_adtstoasc output.mp4

This approach works on all platforms.

I needed the ability to encapsulate this in a cross platform script, so I used fluent-ffmpeg and came up with the following solution:

const unlink = path =>
  new Promise((resolve, reject) =>
    fs.unlink(path, err => (err ? reject(err) : resolve()))
  )

const createIntermediate = file =>
  new Promise((resolve, reject) => {
    const out = `${Math.random()
      .toString(13)
      .slice(2)}.ts`

    ffmpeg(file)
      .outputOptions('-c', 'copy', '-bsf:v', 'h264_mp4toannexb', '-f', 'mpegts')
      .output(out)
      .on('end', () => resolve(out))
      .on('error', reject)
      .run()
  })

const concat = async (files, output) => {
  const names = await Promise.all(files.map(createIntermediate))
  const namesString = names.join('|')

  await new Promise((resolve, reject) =>
    ffmpeg(`concat:${namesString}`)
      .outputOptions('-c', 'copy', '-bsf:a', 'aac_adtstoasc')
      .output(output)
      .on('end', resolve)
      .on('error', reject)
      .run()
  )

  names.map(unlink)
}

concat(['file1.mp4', 'file2.mp4', 'file3.mp4'], 'output.mp4').then(() =>
  console.log('done!')
)
3
  • 1
    Wow, I'm surprised I had to scroll down this far to find a solution that actually worked. It's quite embarrassing that this tedious approach (having to create intermediate .ts files) is the only one that reliably worked, hopefully newer versions of FFMPEG can incorporate a cleaner concatenation method for MP4 files.
    – Raleigh L.
    Commented Mar 15, 2022 at 18:53
  • For mp4 sources - the first part of this answer worked for me - where other answers did not. I found it was more stable to use an mkv container for the concatenated streams, and once things were working... change the container as needed e.g. back to mp4. I had issues directly concatenating into mp4 container. ffmpeg version 4.3.5-0+deb11u1
    – Kyle
    Commented Jan 29, 2023 at 0:37
  • The intermediate streams approach worked for me perfectly to join together 4 MP4 videos. Everything else I tried gave incomplete results.
    – Brad Howes
    Commented Oct 5 at 12:30
14

based on rogerdpack's and Ed999's responses, I've created my .sh version

#!/bin/bash

[ -e list.txt ] && rm list.txt
for f in *.mp4
do
   echo "file $f" >> list.txt
done

ffmpeg -f concat -i list.txt -c copy joined-out.mp4 && rm list.txt

it joins all the *.mp4 files in current folder into joined-out.mp4

tested on mac.

resulting filesize is exact sum of my 60 tested files. Should not be any loss. Just what I needed

12

Late answer, but this is the only option that actually worked for me:

(echo file '1.mp4' & echo file '2.mp4' & echo file '3.mp4' & echo file '4.mp4') | ffmpeg -protocol_whitelist file,pipe -f concat -safe 0 -i pipe: -vcodec copy -acodec copy "1234.mp4"

5
  • 1
    This doesn't work for me anymore. I get moov atom not found;Impossible to open 'pipe:file_01.mp4' and pipe:: Invalid data found when processing input when I try the pipe. Saving everything into input.text and running with -i input.text works.
    – emk2203
    Commented Oct 10, 2021 at 14:39
  • @emk2203 file_01.mp4 is damaged. Commented Oct 10, 2021 at 22:48
  • I can try any mp4 file with the same result, but they work perfectly when I use ffmpeg -f concat -safe 0 -i input.text -c copy output.mp4 and their names are in input.text in the "file 'file_01.mp4'` sytax - the same as when I use the output of the pipe.
    – emk2203
    Commented Oct 11, 2021 at 5:18
  • 2
    No, it definitely isn't. Meanwhile, I found the answer on the bug tracker. Long answer is in askubuntu.com/a/1368547/332437. Short answer: You need to use (echo file file:'1.mp4' & echo file file:'2.mp4' & echo file file:'3.mp4' & echo file file:'4.mp4') | ffmpeg -protocol_whitelist file,pipe -f concat -safe 0 -i pipe: -vcodec copy -acodec copy "1234.mp4" when you use the pipe protocol in the ffmpeg versions after ca. 4.2.2.
    – emk2203
    Commented Oct 11, 2021 at 7:02
  • Using single & will reverse the order of the files. Use double && to preserve the correct order. Also mind the new format as @emk2203 points out: (echo file file:'1.mp4' && echo file file:'2.mp4' && echo file file:'3.mp4' && echo file file:'4.mp4') | ffmpeg -protocol_whitelist file,pipe -f concat -safe 0 -i pipe: -vcodec copy -acodec copy "1234.mp4" Commented Oct 23, 2023 at 13:22
8

For .mp4 files, I found it works better and faster to use the opensource command line tool: mp4box. Then You can use it this way:

mp4box.exe -add video1.mp4 -cat video2.mp4 destvideo.mp4

Download it here for most platforms: https://gpac.wp.imt.fr/mp4box/

1
  • 5
    Although this might be useful for someone, the requirement was: "I'm trying to concatenate two mp4 files using ffmpeg. I need this to be an automatic process". Please read the full question and try and provide an answer that's on point.
    – thvs86
    Commented Jan 28, 2019 at 13:05
6

Here is a script I made to concatenate several GoPro mp4's into a 720p mp4. Hope it's of help.

#!/bin/sh
cmd="( "
for i; do
    cmd="${cmd}ffmpeg -i $i -ab 256000 -vb 10000000 -mbd rd -trellis 2 -cmp 2 -subcmp 2 -g 100 -f mpeg -; "
done
cmd="${cmd} ) | ffmpeg -i - -vb 10000000 -ab 256000 -s 1280x720 -y out-`date +%F-%H%M.%S`.mp4"
echo "${cmd}"
eval ${cmd}
1
  • 2
    This is NOT concatenating. This code does not concatenate the inputs, it re-encodes them. This takes any arbitrary video files and changes them to a common audio and video bitrate (regardless of the source files' original bitrate or original framesize). The o/p wanted to join two files using stream copy, not re-encode them, so this does not address the question asked.
    – Ed999
    Commented Nov 6, 2018 at 0:05
6

For Windows Powershell

create a file list

PS > ls file* | % { $n = $_.name; "file '\$n'" } | out-file mylist.txt

check the file created

PS > cat mylist.txt
file '/path/to/file1.mkv'
file '/path/to/file2.mkv'
file '/path/to/file3.mkv'

run ffmpeg (don't forget attach ./ for the list file)

PS > ffmpeg -safe 0 -f concat -i ./mylist.txt -c copy file.mkv
ffmpeg version git-2020-05-15-b18fd2b Copyright (c) 2000-2020 the FFmpeg developers
...

0
5

Write mp4 videos paths into file input.txt:

file '/path/to/video1.mp4'
file '/path/to/video2.mp4'

and then run this:

ffmpeg -f concat -safe 0  -i ./input.txt -c:v copy output.mp4

Just this!

2
  • Why you need sudo?
    – QkiZ
    Commented Apr 14, 2023 at 7:13
  • Ok, I ve just removed sudo! Commented Apr 14, 2023 at 13:41
4

Here is a script (works for an arbitrary number of specified files (not just all in the working directory), without additional files, also works for .mov; tested on macOS):

#!/bin/bash

if [ $# -lt 1 ]; then
    echo "Usage: `basename $0` input_1.mp4 input_2.mp4 ... output.mp4"
    exit 0
fi

ARGS=("$@") # determine all arguments
output=${ARGS[${#ARGS[@]}-1]} # get the last argument (output file)
unset ARGS[${#ARGS[@]}-1] # drop it from the array
(for f in "${ARGS[@]}"; do echo "file '$f'"; done) | ffmpeg -protocol_whitelist file,pipe -f concat -safe 0 -i pipe: -vcodec copy -acodec copy $output
2
  • 2
    For newer ffmpeg versions than ca. 4.2.2, you need to change do echo "file '$f'" to do echo "file file:'$f'" for the script to work. Explanation here: askubuntu.com/a/1368547/332437.
    – emk2203
    Commented Oct 11, 2021 at 7:24
  • Doh! Not "file" prefix, but "file file:"! Been banging my head on this for days, trying to migrate to a new system. Thanks @emk2203.
    – jimhark
    Commented Aug 29, 2022 at 6:59
3

Merging all mp4 files from current directory

I personnaly like not creating external file that I have to delete afterwards, so my solution was following which includes files numbering listing (like file_1_name, file_2_name, file_10_name, file_20_name, file_100_name, ...)

#!/bin/bash
filesList=""
for file in $(ls -1v *.mp4);do #lists even numbered file
    filesList="${filesList}${file}|"
done
filesList=${filesList%?} # removes trailing pipe
ffmpeg -i "concat:$filesList" -c copy $(date +%Y%m%d_%H%M%S)_merged.mp4
3
  • I copied this code to Notepad and nothing happened.
    – 7vujy0f0hy
    Commented Jul 5, 2018 at 21:30
  • 2
    @7vujy0f0hy this works on a linux with a bash shell maybe it could work with cygwin
    – Pipo
    Commented Jul 6, 2018 at 2:03
  • An interesting post on concat video filter issues. Commented Apr 8 at 20:45
3

If you want to concatenate files without having to create an annoying separate text file just to include the file names you can do this:

echo -e "file 'in1.mp4'\nfile 'in2.mp4'" | ffmpeg -f concat -safe 0 -i /dev/stdin -c copy out.mp4

In fact every time a command requires a file to be passed in you can instead echo out a string and pipe it out to your command and just use /dev/stdin as the filename.

2
  • This does not work, you get Impossible to open '/path/to/part.mov' /dev/stdin: No such file or directory.
    – Adam
    Commented Nov 13, 2022 at 11:44
  • This worked for me by using full paths to the input files. I prefer this to making text files when concatenating two or three videos which were separated by some size limitation (eg, GoPro videos, some screen recording software). The example should be updated to suggest full paths. For example: echo -e "file '/full/path/in1.mp4'\nfile '/full/path/in2.mp4'" | ffmpeg -f concat -safe 0 -i /dev/stdin -c copy out.mp4
    – BTR
    Commented May 3 at 18:01
2

For those who need to concatenate a number of MP4 videos encoded with H.264, I propose a Python script mp4concat.py that automates the Concat protocol/using intermediate files paragraph from the ffmpeg documentation.

Download the script either from the link above or with

wget https://gist.githubusercontent.com/mwouts/115956d3fc7ece55cbce483a7a5148bd/raw/4bc5e03952f1b981dac3f97f4daf82c907774b17/mp4concat.py

and then use it as

python3 mp4concat.py input1.mp4 ... inputN.mp4 --output output.mp4
2
  • line 40, in <module> import click ModuleNotFoundError: No module named 'click'
    – qwerty
    Commented Jun 5, 2022 at 1:06
  • You need to install click in your Python environment. Try either pip3 install click, or pip3 install click --user
    – Marc Wouts
    Commented Jun 7, 2022 at 9:49
1

After various tries below script worked for me on windows 10 powershell.

    $files=Get-ChildItem -path e:\ -Filter *.mp4


    $files| ForEach-Object  {"file '$($_.FullName)'"}| Out-File -FilePath e:\temp.txt -Encoding ASCII


    if (-not (test-path "e:\ffmpeg\bin\ffmpeg.exe")) {throw "e:\ffmpeg\bin\ffmpeg.exe needed"}

    E:\ffmpeg\bin\ffmpeg.exe -safe 0 -f concat -i "e:\temp.txt" -c copy -bsf:v hevc_mp4toannexb -an e:\joined.mp4

    # Conversion Cleanup
    Remove-Item e:\temp.txt

Here first two lines create a text file temp.txt which has following content

file 'e:\first.mp4'
file 'e:\second.mp4'

3rd, 4th lines checks if ffmpeg is available at path and create the "joined.mp4"

The key differences from other answers are as below

usage  of -bsf:v hevc_mp4toannexb -an

for my mp4 file above worked, you may need to use other alternatives like below depending on your video encoding.

h264_mp4toannexb

All such possible Bitstream filters can be found at https://ffmpeg.org/ffmpeg-bitstream-filters.html

0
1

Here's my method for joining a directory full of MP4 files using command substitution and the concat video filter (this will re-encode) - figured someone else will get some use out of this one-liner, especially if you have many files (I just joined 17 files in one fell swoop):

ffmpeg $(for f in *.mp4 ; do echo -n "-i $f "; done) -filter_complex \
"$(i=0 ; for f in *.mp4 ; do echo -n "[$i:v] [$i:a] " ; i=$((i+1)) ; done \
&& echo "concat=n=$i:v=1:a=1 [v] [a]")" -map "[v]" -map "[a]" output.mp4

N.B. this command joins your files in the order in which they're named (i.e. the same order as they're presented if you run ls *.mp4) - in my case, they each had a track number, so it worked great.

1
  • 1
    This would be better as a script than a command.
    – chovy
    Commented Jan 2, 2021 at 14:22
1

The accepted answer in the form of reusable PowerShell script

Param(
[string]$WildcardFilePath,
[string]$OutFilePath
)
try
{
    $tempFile = [System.IO.Path]::GetTempFileName()
    Get-ChildItem -path $wildcardFilePath | foreach  { "file '$_'" } | Out-File -FilePath $tempFile -Encoding ascii
    ffmpeg.exe -safe 0 -f concat -i $tempFile -c copy $outFilePath
}
finally
{
    Remove-Item $tempFile
}
0
ffmpeg \
  -i input_1.mp4 \
  -i input_2.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
2
  • 2
    Not just posting an answer, you could add little explanation which understand the solution better to OP and future readers as well. Commented Aug 28, 2017 at 9:19
  • 1
    This created a new silent video with both inputs playing as the same time next to each other. I expected concatenation would create a video where input_1.mp4 played and then input_2.mp4 Commented Jan 29, 2018 at 0:27
0

The concat protocol described here; https://trac.ffmpeg.org/wiki/Concatenate#protocol

When implemented using named pipes to avoid intermediate files

Is very fast (read: instant), has no frames dropped, and works well.

Remember to delete the named pipe files and remember to check if the video is H264 and AAC which you can do with just ffmpeg -i filename.mp4 (check for h264 and aac mentions)

0

If you prefer method #2 from rogerdpack's answer but you don't want to use pipe (e.g. you just want to use execv in C) or don't want to create extra files (list.txt), then just combine concat demuxer with data and file protocols, i.e. FFmpeg allows you to inline input files almost as in HTML:

<img src="data:image/png;base64,..." alt="" />
ffmpeg -i 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAAQ4AQAAAADAqPzuAAABEklEQVR4Ae3BAQ0AAADCIPunfg8HDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4FT45QABPFL5RwAAAABJRU5ErkJggg==' /tmp/blackbg.mp4

Below is my program (put it in /usr/bin/ffconcat) that automates inlining of "a file containing a list of filepaths". Also, unlike all other answers, you can use any FFmpeg options.

If you use something other than bash language (C, Node.js), then just look at the usage () and the last line.

#!/bin/bash
# ffconcat v0.3
# @author Arzet Ro, 2021 <[email protected]>
# @license CC-0 (Public Domain)

function usage ()
{
    echo "\
ffconcat's usage:
    ffconcat (anyFfmpegInputOptions) -i /tmp/a.mp4 -i ... -i ... /tmp/out.mp4 (anyFfmpegOutputOptions)
    ffconcat -vn /tmp/a.mp4 /tmp/b.opus /tmp/out.mp4 -y
    ffconcat -http -i https://a/h264@720p@25fps+opus.mp4 -i ftp://127.0.0.1/h264@720p@30fps+opus.mp4 -i /tmp/c.opus /tmp/out.mkv
    ffconcat -http -i https://a/vp9@1080p@30fps+opus.mp4 -i ftp://127.0.0.1/vp9@720p@30fps+opus.mp4 -i /tmp/c.opus /tmp/out.mp4
    WARNING: ffconcat uses `concat` demuxer; when
    using both this demuxer AND -y, FFmpeg doesn't check if
    an input file and output file
    are the same file, so your 100 GB input file
    could immediately become 10 KB.
    ffconcat checks that only when neither -i
    nor new FFmpeg release's boolean args (see valuelessFfmpegArgs in the code)
    are specified.

    ffmpeg has no -http.
    ffconcat has -http because ffmpeg automatically
    sets allowed protocols depending on -f and -i.
    But when -f concat, ffmpeg doesn't know what to do with -i.

    ffmpeg and mpv support VP9+Opus in .mp4
    Only one video codec is possible in an output file.
    You can't have both AAC and Opus in one .mp4 (not sure about other containers).
    If you combine VP9 videos, then make sure they have the same FPS.
    If you combine H264 videos of different resolutions,
    then you get A/V desync
    and also either
    1) the start of video streams after the first video stream are cut
    2) or video player freezes for 5 seconds when switching between video streams.
    Also it seems if DAR (display aspect ratio) differs (at least in H.264)
    then incorrect (5x longer) duration is estimated
    and mpv displays the second video with 1 FPS.
    You can see the info about an input file
    with
    mediainfo file.mp4
    or
    ffprobe -hide_banner -protocol_whitelist file,rtp,udp -show_streams file.mp4"
}

# Configuration [begin]
forceRequireIArgumentForInputFiles=0
# Configuration [end]




in_array ()
{
    local e match="$1"
    shift
    for e; do [[ "$e" == "$match" ]] && return 0; done
    return 1
}

if [[ "$#" == 0 ]]
then
    usage
    exit
fi

requireIArgumentForInputFiles=0
if in_array "--help" "$@"
then
    usage
    exit
elif in_array "-help" "$@"
then
    usage
    exit
elif in_array "-i" "$@"
then
    requireIArgumentForInputFiles=1
elif [[ "$forceRequireIArgumentForInputFiles" == "1" ]]
then
    >&2 echo "forceRequireIArgumentForInputFiles=1, so you need -i"
    usage
    exit 1
fi




NL=$'\n'
inputOptions=()
outputOptions=()
inputFilesRawArray=()
outputFile=""

declare -a valuelessFfmpegArgs=("-http"     "-hide_banner" "-dn" "-n" "-y" "-vn" "-an" "-autorotate" "-noautorotate" "-autoscale" "-noautoscale" "-stats" "-nostats" "-stdin" "-nostdin" "-ilme" "-vstats" "-psnr" "-qphist" "-hwaccels" "-sn" "-fix_sub_duration" "-ignore_unknown" "-copy_unknown" "-benchmark" "-benchmark_all" "-dump" "-hex" "-re" "-copyts" "-start_at_zero" "-shortest" "-accurate_seek" "-noaccurate_seek" "-seek_timestamp"     "write_id3v2" "write_apetag" "write_mpeg2" "ignore_loop" "skip_rate_check" "no_resync_search" "export_xmp")
#^ used when requireIArgumentForInputFiles=0
# TODO: fill all the args
# grep -C 3 AV_OPT_TYPE_BOOL libavformat/ libavcodec/
# grep -C 3 OPT_BOOL fftools/
# Unfortunately, unlike MPV, FFmpeg neither
# requires nor supports `=`, i.e. `--y --i=file.mp4'
# instead of `-y -i file.mp4`.
# Which means it's unclear whether an argument
# is a value of an argument or an input/output file.

areFfmpegArgsAllowed=1
isHttpMode=0

if in_array "-http" "$@"
then
    isHttpMode=1
fi


# if an argument is not a boolean argument, then what key needs a value
secondArgumentIsWantedByThisFirstArgument=""
# if requireIArgumentForInputFiles=1
# then secondArgumentIsWantedByThisFirstArgument must be either "" or "-i"
isCurrentArgumentI=0
localRawFilesArray=()
outputFile=""
for arg in "$@"
do
    if [[
        "$secondArgumentIsWantedByThisFirstArgument" == ""
        &&
        "$arg" == "-http"
    ]]
    then
        continue
    fi
    if [[ "$arg" == "--" ]]
    then
        areFfmpegArgsAllowed=0
        continue
    fi
    if [[
        (
            "$areFfmpegArgsAllowed" == "1"
            ||
            "$secondArgumentIsWantedByThisFirstArgument" != ""
        )
        && !(
            "$requireIArgumentForInputFiles" == "1"
            &&
            "$secondArgumentIsWantedByThisFirstArgument" == "-i"
        )
        &&
        (
            "$secondArgumentIsWantedByThisFirstArgument" != ""
            ||
            (
                "$requireIArgumentForInputFiles" == "0"
                &&
                "$arg" = -*
            )
            ||
            (
                "$requireIArgumentForInputFiles" == "1"
            )
        )
    ]]
    then
        if [[ !(
            "$requireIArgumentForInputFiles" == "1"
            &&
            "$arg" == "-i"
        ) ]]
        then
            if (( ${#inputFilesRawArray[@]} == 0 ))
            then
                inputOptions+=("$arg")
            else
                outputOptions+=("$arg")
            fi
        fi
    elif [[
        "$requireIArgumentForInputFiles" == "0"
        ||
        "$secondArgumentIsWantedByThisFirstArgument" == "-i"
    ]]
    then
        if echo -n "$arg" | egrep '^(https?|ftp)://'
        then
            inputFilesRawArray+=("$arg")
            localRawFilesArray+=("$arg")
        else
            tmp=`echo -n "$arg" | sed 's@^file:@@'`
            localRawFilesArray+=("$tmp")
            if [[ "$secondArgumentIsWantedByThisFirstArgument" == "-i" ]]
            then
                if ! ls -1d -- "$tmp" >/dev/null 2>/dev/null
                then
                    >&2 echo "Input file '$tmp' not found"
                    exit 1
                fi
            fi
            tmp=`echo -n "$tmp" | sed -E 's@(\s|\\\\)@\\\\\1@g' | sed "s@'@\\\\\'@g"`
            # ^ FIXME: does it work for all filenames?
            inputFilesRawArray+=("file:$tmp")
        fi
    elif [[
        "$requireIArgumentForInputFiles" == "1"
        &&
        "$secondArgumentIsWantedByThisFirstArgument" != "-i"
    ]]
    then
        if echo -n "$arg" | egrep '^(https?|ftp)://'
        then
            outputFile="$arg"
        else
            outputFile=`echo -n "$arg" | sed 's@^file:@@'`
            outputFile="file:$outputFile"
        fi
    else
        usage
        exit 1
    fi
    if [[
        "$secondArgumentIsWantedByThisFirstArgument" != ""
        ||
        "$areFfmpegArgsAllowed" == "0"
    ]]
    then
        secondArgumentIsWantedByThisFirstArgument=""
    else
        if [[ "$requireIArgumentForInputFiles" == "1" && "$arg" == "-i" ]]
        then
            secondArgumentIsWantedByThisFirstArgument="$arg"
        elif [[ "$requireIArgumentForInputFiles" == "0" && "$arg" = -* ]]
        then
            if ! in_array "$arg" ${valuelessFfmpegArgs[@]}
            then
                secondArgumentIsWantedByThisFirstArgument="$arg"
            fi
        fi
    fi
done
if [[
    "$requireIArgumentForInputFiles" == "0"
    &&
    "$outputFile" == ""
]]
then
    outputFile="${localRawFilesArray[((${#localRawFilesArray[@]}-1))]}"
fi
actualOutputFile="$outputFile"
if [[ "$requireIArgumentForInputFiles" == "0" || "file:" =~ ^"$outputFile"* ]]
then
    actualOutputFile=`echo -n "$actualOutputFile" | sed 's@^file:@@'`
    actualOutputFile=`readlink -nf -- "$actualOutputFile"`
fi

if [[ "$requireIArgumentForInputFiles" == "0" ]]
then
    unset 'inputFilesRawArray[((${#inputFilesRawArray[@]}-1))]'
    unset 'localRawFilesArray[((${#localRawFilesArray[@]}-1))]'
    outputOptions+=("$outputFile")
fi

#>&2 echo Input: ${inputFilesRawArray[@]}
#if [[ "$requireIArgumentForInputFiles" == "0" ]]
#then
#   >&2 echo Output: $outputFile
#fi


if (( ${#inputFilesRawArray[@]} < 2 ))
then
    >&2 echo "Error: Minimum 2 input files required, ${#inputFilesRawArray[@]} given."
    >&2 echo Input: ${inputFilesRawArray[@]}
    if [[ "$requireIArgumentForInputFiles" == "0" ]]
    then
        >&2 echo Output: $outputFile
    fi
    usage
    #outputFile=/dev/null
    exit 1
fi
if [[
    "$requireIArgumentForInputFiles" == "0"
    &&
    "$outputFile" == ""
]]
then
    >&2 echo "Error: No output file specified."
    usage
    exit 1
fi


ffmpegInputList=""
firstFileDone=0
inputFilesRawArrayLength=${#inputFilesRawArray[@]}

for (( i = 0; i < inputFilesRawArrayLength; i++ ))
do
    lf="${localRawFilesArray[$i]}"
    f="${inputFilesRawArray[$i]}"
    if [[ "${inputFilesRawArray[$i]}" =~ ^file: ]]
    then
        actualF=`readlink -nf -- "$lf"`
        if [[ "$actualF" == "$actualOutputFile" ]]
        then
            >&2 echo "Error: The same file '$actualF' is used both as an input file and an output file"
            exit 1
        fi
    fi
    if [[ "$firstFileDone" == "1" ]]
    then
        ffmpegInputList+="$NL"
    fi
    ffmpegInputList+="file $f"
    firstFileDone=1
done

protocol_whitelist_appendage=""
if [[ "$isHttpMode" == "1" ]]
then
    protocol_whitelist_appendage=",ftp,http,https"
fi


# Also print the final line:
set -x

ffmpeg \
-safe 0 \
-f concat \
-protocol_whitelist data,file"$protocol_whitelist_appendage" \
"${inputOptions[@]}" \
-i "data:text/plain;charset=UTF-8,${ffmpegInputList}" \
-c copy \
"${outputOptions[@]}"
# $ffmpegInputList is
# file file:./test.mp4\nfile file:/home/aaa.mp4\nfile http://a/b.aac
# All whitespace and ' in ffmpegInputList are escaped with `\`.

Percent-encoding (JavaScript's encodeURI/encodeURIComponent) (%20, etc.) is not needed in -i, unlike in HTML.

0
ffmpeg -i input1.ts -i input2.ts -i input3.ts -filter_complex "concat=n=3:v=1:a=0" -vn -y output.ts 

enjoy!!

3
  • 4
    This question is nearly 11 years old and already has dozens of answers, including an answer with a score well north of 1,000. Are you entirely sure that your suggestion hasn't already been given? Why is this better than any of the other existing answers?
    – Chris
    Commented Jul 4, 2022 at 1:54
  • This is the only answer for the simple and useful case that all inputs are in the same format, and avoids the batch file listing the input files. It also avoids a lot of complexity as in the other answers. It is still missing some explanation of the filter_complex parameters. +1
    – Roland
    Commented Jan 8 at 23:58
  • This answer helped. I did enjoy! It was only missing a=1 instead of a=0 to make it work for me. Commented Nov 26 at 3:50
0

Concatenate video files with different CRF and with variable frame rate

For this specialized variation I created a separate Q & A (already solved myself):

How to encode scenes with different CRF into a H.264 MP4 video with a variable frame rate?

Not the answer you're looking for? Browse other questions tagged or ask your own question.