Join mp4 files in linux
I want to join two mp4 files to create a single one. The video streams are encoded in h264 and the audio in aac. I can not re-encode the videos to another format due to computational reasons. Also, I cannot use any GUI programs, all processing must be performed with Linux command line utilities. FFmpeg cannot do this for mpeg4 files so instead I used MP4Box:
MP4Box -add video1.mp4 -cat video2.mp4 newvideo.mp4
Unfortunately the audio gets all mixed up. I thought that the problem was that the audio was in aac so I transcoded it in mp3 and used again MP4Box. In this case the audio is fine for the first half of newvideo.mp4
(corresponding to video1.mp4
) but then their is no audio and I cannot navigate in the video also.
My next thought was that the audio and video streams had some small discrepancies in their lengths that I should fix. So for each input video I splitted the video and audio streams and then joined them with the -shortest option in FFmpeg.
Thus for the first video I ran:
avconv -y -i video1.mp4 -c copy -map 0:0 videostream1.mp4
avconv -y -i video1.mp4 -c copy -map 0:1 audiostream1.m4a
avconv -y -i videostream1.mp4 -i audiostream1.m4a -c copy -shortest video1_aligned.mp4
Similarly for the second video and then used MP4Box as previously. Unfortunately this didn't work either. The only success I had was when I joined the video streams separately (i.e. videostream1.mp4 and videostream2.mp4) and the audio streams (i.e. audiostream1.m4a and audiostream2.m4a) and then joined the video and audio in a final file. However, the synchronization is lost for the second half of the video. Concretely, there is a 1 sec delay of audio and video. Any suggestions are really welcome.
Solution 1:
The best way to do this currently is with the concat demuxer. First, create a file called inputs.txt
formatted like so:
file '/path/to/input1.mp4'
file '/path/to/input2.mp4'
file '/path/to/input3.mp4'
Then, simply run this ffmpeg command:
ffmpeg -f concat -i inputs.txt -c copy output.mp4
See also concatenation in ffmpeg
FAQ.
I'm keeping the following here for the benefit of anyone using older versions of ffmpeg.
The latest versions of ffmpeg can do this: you'll have to remux the files into mpeg transport streams first (fairly processor-light, as it's only changing the container format):
ffmpeg -i input.mp4 -map 0 -c copy -f mpegts -bsf h264_mp4toannexb middle.ts
If that throws up an error [1] about h264, you may need to use:
ffmpeg -i input.mp4 -map 0 -c copy -f mpegts -bsf:v h264_mp4toannexb middle.ts
You'll have to do this separately with each input file. To concatenate the files together, use:
ffmpeg -i "concat:middle1.ts|middle2.ts|middle3.ts" -c copy output.mp4
If that throws up an error about aac, you may need to use
ffmpeg -i "concat:middle1.ts|middle2.ts|middle3.ts" -c copy -absf aac_adtstoasc output.mp4
If your system supports named pipes, you can do this without creating intermediate files.
mkfifo temp0 temp1
You'll have to do the following in three separate virtual terminals:
ffmpeg -i input0.mp4 -map 0 -c copy -f mpegts -bsf h264_mp4toannexb -y temp0
ffmpeg -i input1.mp4 -map 0 -c copy -f mpegts -bsf h264_mp4toannexb -y temp1
ffmpeg -f mpegts -i "concat:temp0|temp1" -c copy -absf aac_adtstoasc output.mp4
If output.mp4 already exists, the third ffmpeg will ask you whether you want to overwrite it, but it will do this after it has accessed the FIFOs, and this will make the first to ffmpegs close. So make sure that you choose an unused name for your output file.
This may not work if your input files are different - I believe that differences in bit rate are OK, but frame size, frame rate etc have to match up.
Solution 2:
For mp4 the only working solution I found was with MP4Box from gpac package
#!/bin/bash
filesList=""
for file in $(ls *.mp4|sort -n);do
filesList="$filesList -cat $file"
done
MP4Box $filesList -new merged_files_$(date +%Y%m%d_%H%M%S).mp4
or command is
MP4Box -cat file1.mp4 -cat file2.mp4 -new mergedFile.mp4
with mencoder and avconv I could'nt make it work :-(