936

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.

xpt
  • 20,363
  • 37
  • 127
  • 216
Mark L
  • 12,405
  • 4
  • 28
  • 41
  • 4
    This is a valid programing question IMHO, but if desired it might be moved to SuperUser. – Ed999 Jul 17 '23 at 17:03

31 Answers31

1633

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 -safe 0 -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

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

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
  • 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 Jul 23 '15 at 04:09
  • 1
    Helpful answer. And it made even more sense to me when I read this variation: http://superuser.com/a/607384/74576 – Ryan Feb 22 '16 at 18:54
  • 6
    concat demuxer works for video, but I'm losing audio – Loïc Sep 30 '16 at 23:54
  • 63
    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 Dec 02 '16 at 14:15
  • 1
    For me, in Windows, double quotation marks worked (instead of the single in your example): `ffmpeg -i "concat:input1|input2" -codec copy output`. There is no need to escape the pipe character. – Shovalt Dec 23 '16 at 13:31
  • 1
    @erik mentioned the -safe 0, which is important if you have file entries such as file 'Clip (February 27 2017 at 859 AM).mp4' – Maxim Veksler Mar 08 '17 at 20:08
  • 1
    if 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 Sep 05 '17 at 07:14
  • 1
    You could make your windows example as clear as your linux one.. Your linux one uses cat. You could use the type command in windows and just display the contents of the playlist file as you did with linux with cat. Trying to show how to make a text file with echo commands just distracts a bit. – barlop Oct 10 '18 at 01:44
  • 1
    This answer definitely helped. though I had to twick it a little bit to work for my mp4s. `-bsf:v hevc_mp4toannexb` is a important part I had to added without it the conversion fails. `ffmpeg -safe 0 -f concat -i "mylist.txt" -c copy -bsf:v hevc_mp4toannexb -an output.mp4` – rahul maindargi Mar 14 '19 at 20:08
  • To me it was necessary to put `-safe 0` before `-i` https://stackoverflow.com/questions/38996925/ffmpeg-concat-unsafe-file-name – zyrup May 17 '19 at 12:26
  • I was using .mov Quicktime files and only #1 worked for me, even though the files all had the same stats and were all created on the same TriCaster. – PRMan Jun 02 '19 at 00:11
  • For me, `-c copy` never works. Produces broken video (once the first video ends). – theonlygusti Nov 22 '19 at 00:09
  • 1
    That string to "filter_complex" makes less than zero sense. – Adam Barnes Dec 21 '19 at 01:31
  • The concat demuxer expects a text file as input. -___- – Yu Da Chi Aug 26 '20 at 15:09
  • 1
    #1: Veeery slowly generates a broken file. I stopped it after it surpassed the sum of the two source file sizes. #2: Often worked for me in the past, but for some reason in my current case it only concatenates the sound correctly, but slows down the second file by a factor of ~4 and adds a gap in between the two as if the first one had also been slowed down. #3: Only copies the first file. (As predicted, didn't work for MP4.) The only one that worked for me was the one with intermediate files on superuser.com here: https://superuser.com/a/1059261/681763 – Fabian Röling Mar 21 '21 at 17:52
  • Like other people have mentioned, sometimes with #2, bits of audio is missing. You can fix it by removing `-c copy`, though it will take longer as it reencodes the whole video. – StarDust Jun 18 '21 at 02:25
  • The concat demuxer deletes the first second of the second mp4 – theonlygusti Mar 02 '22 at 22:35
  • concat video filter doesn't work because either "Stream specifier ':v'/':a' in filtergraph description matches no streams" or "Input link in0:v0 parameters (size 428x640, SAR 0:1) do not match the corresponding output link in0:v0 parameters (428x640, SAR 1576:1575) – theonlygusti Mar 02 '22 at 22:38
  • Why didn't you include an example of how to use the concat demuxer (#2) without creating an intermediate file just to store the list of videos? – Raleigh L. Mar 14 '22 at 20:41
  • The concat answer (#3) does NOT work with MKV files. – Raleigh L. May 17 '22 at 05:45
  • 1
    Method #3 does NOT work with webm files. – Raleigh L. Jun 11 '22 at 22:05
  • I tried method #2 (concat demuxer) for x264-encoded mp4 files, and the resulting output freezes for a fraction of a second between each segment, stretching the overall duration. I wouldn't call it usable. – TigerhawkT3 Sep 11 '22 at 03:51
  • Method #1 seems to require corresponding in?:v0 parameters (resolution and SAR), so they do all need to be the same size already. – PePa Sep 18 '22 at 00:48
  • Did not work for me. Cmd: `ffmpeg -f concat -safe 0 -i inputs.txt -c copy output.mp4`. 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` – Gabriel Staples Nov 15 '22 at 23:06
  • I figured it out and [added an answer here](https://stackoverflow.com/a/74454448/4561887). – Gabriel Staples Nov 16 '22 at 02:29
  • Concat demuxer doesn't let me play past the length of the first video – LuisAFK Jan 31 '23 at 09:30
  • 5
    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 Feb 09 '23 at 16:06
  • This works fine with mkv files but the problem I'm seeing is the original chapter markers from both pieces are lost in the concatenated video. – robvdl Jun 16 '23 at 09:00
  • Yeah maybe file an ffmpeg trac bug on it? :) – rogerdpack Jun 16 '23 at 21:28
  • Why not using concat protocol on mp4? – Uriel Frankel Jul 04 '23 at 15:10
  • OK added explanation kind of :) – rogerdpack Jul 17 '23 at 22:37
  • 1
    I ran `ffmpeg -i "concat:input1|input2" -codec copy output` and the the output contained only input1 (input2 was ignored). How's that? – Apostolos Aug 06 '23 at 22:45
  • Not sure, check your console output carefully... – rogerdpack Aug 10 '23 at 05:32
  • [concat @ 0xb400006ecc455790] Line 1: unknown keyword './1.mp4' mylist.txt: Invalid data found when processing input – Wolfpack'08 Sep 02 '23 at 12:52
224

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!

Ed999
  • 2,801
  • 2
  • 16
  • 19
  • 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` (So they see the contents of that file) Then `ffmpeg -f concat -i mylist.txt -c copy output.mp4` Also you should include a reference https://trac.ffmpeg.org/wiki/Concatenate (that reference even uses the same filename, mylist.txt) – barlop Oct 10 '18 at 01:37
  • 3
    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ß Oct 11 '18 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 Nov 08 '18 at 03:48
  • @Xam : You have posted a solution that will only work on the command line, but will fail in a batch file. I have not posted a command line solution, but a batch file solution. If you fail to double a % symbol when using your suggested code in a Windows _batch file_, the batch file will fail. When running _my_ code, it must be run in a batch file, and in a batch file all % symbols must be written %%. – Ed999 Nov 12 '18 at 02:18
  • 3
    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 Mar 02 '19 at 14:07
  • @kakyo Try using the latest build of ffmpeg from _http://ffmpeg.zeranoe.com/builds/_ but I regret I can't troubleshoot a problem on a Mac, my code is only intended to run on Windows. I myself only run it on Windows 7, but it should work okay on Windows Vista/7/8/10. – Ed999 Mar 06 '19 at 17:07
  • You should include a "DEL mylist.txt" at the end for people who just copy your code into a batch file. Otherwise running the batch file a second time will be messed up because of the old mylist.txt still in the folder. – Nexarius Sep 05 '20 at 10:20
  • It's unnecessary, because my script concatenates ALL the mp4 files in the folder from which the script is run. There are no mp4 files left over to need a second run. The intent of the script is to move it to the next folder containing mp4 files before running it again. – Ed999 Sep 06 '20 at 12:26
  • I do not want to add unnecessary complexity to this straightforward solution. It would be just too easy to over-complicate the script, because if you run it a second time in the same folder not only do you need to provide for removing the mylist.txt file but you also need to remove or rename the output.mp4 file, else it will be deleted, or perhaps its presence will prevent the script running a second time. Those decisions are, IMHO, better left to the good sense and needs of the person using the script. – Ed999 Sep 06 '20 at 12:33
  • i get an eror with second file in list. – chovy Dec 04 '20 at 07:47
  • @chovy There are many, many reasons why you might get such an error. My script only works for mp4 files that are compatible with each other (i.e. that are identical in every particular because they all had the same source file originally). You can easily find two or more mp4 files that do not have identical codecs, bitrates, framerates, sample rates and timebases, which therefore can't be concatenated by ANY method. My script can't resolve such cases, because no script can. – Ed999 Dec 13 '20 at 16:48
  • to create the list on windows you may use `dir /B >mylist.txt`, just check the order of the file afterwards and prepend string `file ` on each line in any editor that can mass replace newlines – user151496 Feb 17 '21 at 10:38
  • That proposal would be a disaster. It will populate the file myfile.txt with every _non-mp4 file_ in the current directory. But, worse, it will also populate that file with the name of every _folder_ in the current directory. – Ed999 Feb 22 '21 at 03:07
  • Did not work for me, second file was 3 extra, instead of that get a minute of a frozen frame added. – Nimitz14 Apr 21 '21 at 22:15
  • @ed999 I have a directory with 100 MP4 files with exact same resolution, audio and video codecs. Is it possible to modify the command you've posted, to concat 01 to 05, 06 to 10, and so on, and create 01_05.mp4, 06_10.mp4 etc., WITHOUT using `mylist.txt`? I like to create 20 files out of 100, instead of just 1. Thanks alot. – Jags Jun 01 '21 at 18:08
  • 2
    del mylist.txt should be added at the top – Nime Cloud Jun 02 '21 at 16:44
  • @Jags. You can do it the hard way if you wish. But I reckon what you'd find least trouble is to temporarily move the files you want to concatenate into a new directory and run my code there. There are lots of other ways to do what you ask, all of them rife with snags. My approach is to put in the working directory only those 5 files that are to be concatenated, and then run my unmodified code. Trust me, save yourself the headaches that come with the more sophisticated solutions, and do it the low tech but easy way -- move the goddam files! :) – Ed999 Jun 03 '21 at 00:06
  • @NimeCloud - Yes, if you intend to run the script more than once, it would be useful to start by deleting 'mylist.txt', I suggest this: IF EXIST MYLIST.TXT DEL MYLIST.TXT – Ed999 Jun 03 '21 at 00:10
  • To create the file list of mp4 files from . directory, use: ```for /f "tokens=1 delims=." %a in ('dir /B *.mp4') do echo file '%a.mp4'>> mylist.txt``` – Nils Riga Nov 09 '21 at 00:23
  • This removed the first second of the second video – theonlygusti Mar 02 '22 at 22:29
  • @theonlygusti There are many reasons why you might get such an error. My script only works for mp4 files, and only if they are compatible with each other (i.e. are identical in every property, because they all had the same source file originally). – Ed999 Apr 24 '22 at 23:02
  • `%%` doesn't work on command line. Use `%` works fine. – vee Jun 26 '23 at 06:22
98

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).

legends2k
  • 31,634
  • 25
  • 118
  • 222
Ellie Tam
  • 1,019
  • 7
  • 8
  • 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 Sep 05 '17 at 07:16
  • 1
    Worked like charm, just had to change "v=0" to "v=1" and the rest was solved! – Ray Oct 20 '20 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](https://ffmpeg.org/ffmpeg-filters.html#concat): _"Set the number of output video/audio streams, that is also the number of video/audio streams in each segment"_. Even in the [example](https://ffmpeg.org/ffmpeg-filters.html#Examples-139) you can see **a** is set to **2** for instance. – Reino Jun 07 '21 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) – theonlygusti Mar 02 '22 at 22:31
  • Worked for me, but also needed to change v=0 to v=1, as @Beyar did. – Shiping Dec 31 '22 at 00:34
  • I ran `ffmpeg -i input1.mp3 -i input2.mp3 -filter_complex "concat=n=2:v=0:a=1" -vn -y output` but the output contained only input1 (input2 was ignored). How's that? – Apostolos Aug 06 '23 at 23:14
70

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!: Output will be like first file ( and not a second one)

T.Todua
  • 53,146
  • 19
  • 236
  • 237
  • 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 Mar 05 '17 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 Apr 27 '17 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 May 23 '17 at 09:47
  • @AnswerZhao I think you should exchange the order of filenames in the command.. instead of file1|file2, write file2|file1 – T.Todua Oct 31 '17 at 07:34
  • 2
    @T.Todua er....It's too bad. The file1|file2 generate the different video with the file2|file1. the order is important. – AnswerZhao Nov 01 '17 at 15:22
  • @T.Todua I wonder if you know the answer to this ffmpeg question about concatenating 2 MP4 videos while specifying the output bitrate? https://video.stackexchange.com/q/24569/5657 Thanks. – Ryan Jul 27 '18 at 17:53
  • 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 Feb 17 '19 at 11:36
  • @superlinux use some arguments for audio to make it encoded (instead of copying), find those commands somewhere – T.Todua Feb 17 '19 at 12:34
  • `h264_mp4toannexb -f mpegts` and `-bsf:a aac_adtstoasc` appear unnecessary, at least in my case on latest ffmpeg. What exactly does preconverting to .ts do? timebase tbn changes to 90k for all files? How does this differ from `video_track_timescale`? So many questions – bryc Sep 08 '20 at 10:10
  • @bryc nice questions, but i dont know in depth. if you are confident, feel free to edit answer. However, i am afraid what you say is not "unnecessary". as I mentioned in answer, if those 2 mp4 files are created by different device/code/etc, they were needing to be transcoded to intermediate format and then join them. However, it was mine and others' experience. But I see that you are much versed in the subject, and please edit answer to include warnings/notes to users what you know, as i have no idea about `90k changes, video_track_timescale` and other drawbacks the answer might be producing. – T.Todua Sep 08 '20 at 18:50
  • What _exactly_ does your note here mean? > Output will be like first file ( and not a second one) What do you mean by "like first file", exactly? – Raleigh L. Jun 04 '22 at 02:24
  • At last, one method that works. However, the audio of the 2nd file has a problem: the sound contains short continuous interruptions. How's that? – Apostolos Aug 06 '23 at 23:18
67

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).

cnd
  • 1,689
  • 16
  • 14
  • 4
    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` – Thomas Bachem Jan 28 '15 at 15:35
  • 4
    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 Jan 25 '16 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 Dec 02 '16 at 14:14
  • 1
    In Windows CMD I used this: `ls *mp4 | sed "s/\(*mp4\)/file \1/" | ffmpeg -f concat -i - -c:v copy output.mp4` – Evan Dec 30 '16 at 23:41
  • I did something like this to handle movies with spaces in their paths: `echo -e "file '/home/some dir/video_1.mkv'\nfile '/home/some dir/video_2.mkv'" | ffmpeg -f concat -i - -c copy 'output.mkv'` – smg Jan 26 '17 at 01:33
  • 22
    I had to add this parameter to `ffmpeg` to make it work: `-protocol_whitelist file,tcp,http,pipe` – Bensge Oct 05 '17 at 17:08
  • 1
    A lot of perl and such being bandied about, when you can just do this: `printf "file '%s'\n" Movie_Part_*.mp4` – phils Dec 02 '18 at 11:11
  • 1
    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` – Donn Felker May 05 '20 at 11:22
  • 1
    This did not keep the audio when I tried this command – Josh Sep 03 '21 at 13:46
51

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

kolypto
  • 31,774
  • 17
  • 105
  • 99
Ben Siver
  • 2,758
  • 1
  • 25
  • 42
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
Vijay
  • 43
  • 1
  • 7
Mark L
  • 12,405
  • 4
  • 28
  • 41
  • 9
    As of this comment, `-sameq` was removed. Use `-qscale 0` instead. – Xavier Ho Jan 18 '13 at 03:28
  • Worked for me with =-sameq=. I used =output.avi= instead of =output.mp4= as the latter yielded bad aspect ratio. – Dror Apr 07 '13 at 08:28
  • 9
    [`-sameq` does not mean "same quality"](http://superuser.com/a/478550/110524) and has been removed from ffmpeg as mentioned by @XavierHo. – llogan Aug 22 '13 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. – Randall Cook Oct 08 '13 at 20:43
  • @ Mark L : The audio formats which support file-level concatenation include MPG, MPEG-1, MPEG-2 PS, DV, VOB, and MPEG-TS. – Ed999 Apr 27 '17 at 13:01
  • 1
    This method reduces the quality of the mp4 video. – bozzmob Jul 03 '19 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 Aug 24 '21 at 14:25
  • Why is this the accepted answer, despite the blatant re-encoding issue? Moreover as others are mentioning, this severely degrades the quality of the video. – Raleigh L. May 17 '22 at 05:43
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

Community
  • 1
  • 1
Florent Roques
  • 2,424
  • 2
  • 20
  • 23
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.

SJ00
  • 638
  • 1
  • 6
  • 10
  • @Developer: For explanation of options -> [Concat filter wiki](https://trac.ffmpeg.org/wiki/Concatenate#filter) – SJ00 Apr 04 '16 at 17:52
  • 5
    When concatenating 3 files you should use concat=n=3:v=1:a=1 not n=2 – Cynapsis Sep 01 '16 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 Sep 05 '17 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 Sep 15 '17 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 Oct 21 '18 at 13:27
  • @Ed999 I am not sure if I am getting your point right. The answer has solution for both 3 and 2 file inputs. That is to infer the format for joining 'n' number of files. – SJ00 Oct 31 '18 at 12:38
  • @SJ00 The reason why it is wrong (i.e. misleading) to give a solution for 2 file inputs is that the rules which govern the syntax are different where there are 3 or more inputs, as I describe above. Giving a 2-input example in combination with a 3-input one is very misleading indeed, since it implies there is _no difference_ in the syntax, something which is clearly wrong. It is nowhere made clear to anyone coming new to this procedure that examples for 2-input cases do not work in other cases: 2 files will concatenate, regardless of their duration, but 3 files of differing duration will fail. – Ed999 Nov 05 '18 at 23:53
  • Can we remove "[0:v:0] [0:a:0] [1:v:0] [1:a:0] [2:v:0] [2:a:0]"? after I remove it, it also works. – Stony Jan 22 '19 at 12:56
  • Until today this is the only answer worked for me, you save me once again. – Salem Feb 10 '22 at 07:02
  • This is the only solution that worked, life saver! – E. Karim Aug 10 '23 at 18:09
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

14

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!')
)
itslittlejohn
  • 1,808
  • 3
  • 20
  • 33
  • 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. Mar 15 '22 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 Jan 29 '23 at 00:37
13

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()
GG.
  • 21,083
  • 14
  • 84
  • 130
PythonProgrammi
  • 22,305
  • 3
  • 41
  • 34
  • 10
    The concat protocol should not be used with MP4 and will not work as mentioned in the [accepted answer](https://stackoverflow.com/a/11175851/1109017). – llogan May 26 '18 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 Nov 06 '18 at 00:18
  • 1
    This doesn't work for WEBM files. – Raleigh L. Jun 11 '22 at 22:08
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"

Pedro Lobito
  • 94,083
  • 31
  • 258
  • 268
  • 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 Oct 10 '21 at 14:39
  • @emk2203 `file_01.mp4` is [damaged](https://www.reddit.com/r/ffmpeg/comments/ih0hf7/ffmpeg_moov_atom_not_found/). – Pedro Lobito Oct 10 '21 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 Oct 11 '21 at 05:18
  • 2
    No, it definitely isn't. Meanwhile, I found the answer on the bug tracker. Long answer is in https://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 Oct 11 '21 at 07:02
12

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.

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 time 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 time ffmpeg -f concat -safe 0 -i inputs.txt -c copy output.mkv to write into a .mkv container instead of into a .mp4 container.

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
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/

massisenergy
  • 1,764
  • 3
  • 14
  • 25
Mario
  • 185
  • 1
  • 5
  • 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 Jan 28 '19 at 13: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
...

user1960422
  • 531
  • 6
  • 6
5

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}
Reid Ellis
  • 3,996
  • 1
  • 21
  • 17
  • 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 Nov 06 '18 at 00:05
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
Marius Hofert
  • 6,546
  • 10
  • 48
  • 102
  • 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: https://askubuntu.com/a/1368547/332437. – emk2203 Oct 11 '21 at 07: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 Aug 29 '22 at 06:59
3

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
Marc Wouts
  • 669
  • 6
  • 8
2

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
Pipo
  • 4,653
  • 38
  • 47
2

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.

Soheil
  • 769
  • 8
  • 17
  • This does not work, you get `Impossible to open '/path/to/part.mov' /dev/stdin: No such file or directory`. – Adam Nov 13 '22 at 11:44
2

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!

Mohsen Abasi
  • 2,050
  • 28
  • 30
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

rahul maindargi
  • 5,359
  • 2
  • 16
  • 23
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.

Peter
  • 1,983
  • 1
  • 12
  • 9
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
tuomastik
  • 4,559
  • 5
  • 36
  • 48
  • 2
    Not just posting an answer, you could add little explanation which understand the solution better to OP and future readers as well. – Balagurunathan Marimuthu Aug 28 '17 at 09: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 – Alexx Roche Jan 29 '18 at 00: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)

Roel Van de Paar
  • 2,111
  • 1
  • 24
  • 38
0

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
}
fjch1997
  • 1,518
  • 18
  • 19
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 <arzeth0@gmail.com>
# @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.

Arzet Ro
  • 487
  • 1
  • 5
  • 12
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?

porg
  • 1,079
  • 1
  • 11
  • 17
-1
ffmpeg -i input1.ts -i input2.ts -i input3.ts -filter_complex "concat=n=3:v=1:a=0" -vn -y output.ts 

enjoy!!

Bharath Kumar
  • 893
  • 9
  • 8
  • 3
    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? – ChrisGPT was on strike Jul 04 '22 at 01:54
-2

I had clips with different encoders libx264 and H.264

I converted all clips to libx264 and used demuxer approach from top-voted answer.

ffmpeg -i input.flv -vcodec libx264 output.mp4
Geoff Langenderfer
  • 746
  • 1
  • 9
  • 21
  • This will only convert the single file 'input.flv' to .mp4 format, it won't concatenate any files. It doesn't even specify multiple files as the input. – Ed999 Sep 24 '22 at 15:46