Bash script to recursive find and convert movies - bash

in my large movie collection I would like to search for movies with the primary (first) audio track with DTS coding to be converted to Dolby.
My problem would be the first track I think. My current bash script will list any movie containing a DTS track, but does not specify which track.
#!/bin/bash
# My message to create DTS list
find /home/Movies -name '*.mkv' | while read f
do
if mediainfo "$f" | grep A_DTS; then
echo $f
fi
done
After that I would like to run this command
ffmpeg -i $f -map 0:v -map 0:a:0 -map 0:a -map 0:s -c:v copy -c:a copy -c:s copy -c:a:0 ac3 -b:a:0 640k $f
or is there a way to move all the audio tracks down and adding the new AAC track?
###Progress
Thanks to #llogan I have finetuned the bash to find the required files.
#!/bin/bash
# My DTS conversion script
# credits to llogan
find /Mymovies -name '*.mkv' | while read f
do
if ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of csv=p=0 "$f" | grep dts; then
echo "$f"
fi
done
Now digging into the command I think I may have a working command. Anybody spot a problem?
ffmpeg -i $f
-map 0:v -c:v copy
-map 0:a:0? -c:a:0 ac3
-map 0:a:0? -c:a:1 copy
-map 0:a:1? -c:a:2 copy
-map 0:a:2? -c:a:3 copy
-map 0:a:3? -c:a:4 copy
-map 0:a:4? -c:a:5 copy
-map 0:a:5? -c:a:6 copy
-map 0:a:6? -c:a:7 copy
-map 0:a:7? -c:a:8 copy
-map 0:a:8? -c:a:9 copy
-map 0:s? -c copy
-b:a:0 640k
/tmp/output.mkv
mv $f /home/DTS_BACKUP/
mv /tmp/output.mkv $f
rm /tmp/output.mkv
So the end result would look like:
#!/bin/bash
# My DTS conversion script
# credits to llogan
find /Mymovies -name '*.mkv' | while read f
do
if ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of csv=p=0 "$f" | grep dts; then
ffmpeg -i $f
-map 0:v -c:v copy
-map 0:a:0? -c:a:0 ac3
-map 0:a:0? -c:a:1 copy
-map 0:a:1? -c:a:2 copy
-map 0:a:2? -c:a:3 copy
-map 0:a:3? -c:a:4 copy
-map 0:a:4? -c:a:5 copy
-map 0:a:5? -c:a:6 copy
-map 0:a:6? -c:a:7 copy
-map 0:a:7? -c:a:8 copy
-map 0:a:8? -c:a:9 copy
-map 0:s? -c copy
-b:a:0 640k
/tmp/output.mkv
mv $f /home/DTS_BACKUP/
mv /tmp/output.mkv $f
rm /tmp/output.mkv
fi
done

Ok, so i finetuned the script to seperate dts and dts-hd. I came to the conclusion this was not needed because i cant decode dts-hd to e-ac3 and may as well also encode it to ac3. But i had fun in bash.
Current bash:
#!/bin/bash
# My DTS conversion script
# credits to llogan
find /MyMovies -name '*.mkv' | while read f
do
function codec_profile {
ffprobe -v error -select_streams a:0 -show_entries stream=$1 -of csv=p=0 "$f"
}
#first check for audio format
if [ "$(ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of csv=p=0 "$f")" = "dts" ]; then
if [ "$(codec_profile "profile")" = "DTS" ]; then
echo "$f" >> dts.txt
codec_profile "profile" >> dts.txt
codec_profile "channels" >> dts.txt
if [ "$(codec_profile "channels")" -gt 5 ]; then
echo "check" >> dts.txt; else
echo "stereo" >> dts.txt;
fi
else [ "$(ffprobe -v error -select_streams a:0 -show_entries stream=profile -of csv=p=0 "$f")" = "DTS-HD MA" ];
echo "$f" >> dts-hd.txt
codec_profile "profile" >> dts-hd.txt
codec_profile "channels" >> dts-hd.txt
fi
fi
done
I checked the created txt files and the result is spot op. I also tested the command that #llogan gave me and works perfect.
ffmpeg -i "$f" -map 0 -map -0:a -map 0:a:0 -map 0:a -c copy -c:a:0 ac3 -b:a:0 640k /tmp/output.mkv
Last thing to figure out is how to check the exit code on this and replace the text file creation with this command
The idea:
ffmpeg -i "$f" -map 0 -map -0:a -map 0:a:0 -map 0:a -c copy -c:a:0 ac3 -b:a:0 640k /tmp/output.mkv
RC=$?
if [ "${RC}" -ne "0" ]; then
# list error in txt file and move on to next
else
# mv output file to overwrite original file
fi

Related

FFMPEG - Accurately cutting video/audio and merging multiple videos together

This post has been updated since the original post
thanks in advance for any help I get with this...
I am working with 4 video assets....all stereo audio, same a/v spec.
intro -
mainvideo -
midroll_video (advert) -
endboard
I need to add an audio only cross fade between the 2 video elements part2.mp4 and pip.mp4. These 2 videos are made by the code (rather than being 2 of the 4 videos listed above). I have added in the code as kindly instructed by #Баяр Гончикжапов but unfortunately it is still not working. See conversation below for more info/ the part i need help with.
Thanks in advance!
This is the code I am using.
# Input parameters
mainvideo=mezzfile.mp4
endboard=endboard.mp4
intro=sting.mp4
midroll_video=midroll.mp4
tailtime=20
fadelength=0.2
midroll_edit_value=00:11:31.600
# Time calculations to define cut point
duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $mainvideo)
endboard_cut_point=$(echo "scale=2;$duration-$tailtime" | bc)
duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $mainvideo)
midroll_cut_point=$(echo "scale=2;$duration-$midroll_edit_value" | bc)
# Safety check
tbn_main=$(ffprobe -v error -select_streams v -show_entries stream=time_base -of default=noprint_wrappers=1:nokey=1 $mainvideo)
tbn_main=${tbn_main#*/}
tbn_intro=$(ffprobe -v error -select_streams v -show_entries stream=time_base -of default=noprint_wrappers=1:nokey=1 $intro)
tbn_intro=${tbn_intro#*/}
tbn_end=$(ffprobe -v error -select_streams v -show_entries stream=time_base -of default=noprint_wrappers=1:nokey=1 $endboard)
tbn_end=${tbn_end#*/}
if [[ $(( ($tbn_intro+$tbn_main+$tbn_end)/3 )) -ne $tbn_main ]]; then
echo "WARNING: source video files have the different timebase."
echo "The use of the concat demuxer will produce incorrect output."
echo "Re-encoding is highly recommended."
read -s -k $'?Press any key to exit.\n'
exit 1
fi
# Trim the main part of mainvideo
ffmpeg -hide_banner -y -i $mainvideo -to $midroll_edit_value -c copy part1.mp4
ffmpeg -hide_banner -y -i $mainvideo -ss $midroll_edit_value -to $endboard_cut_point -c copy part2.mp4
ffmpeg -hide_banner -y -i $mainvideo -ss $midroll_edit_value -to $duration -c copy part2av.mp4
# Trim the tail of mainvideo and overlay it onto endboard
ffmpeg -hide_banner -y \
-i $mainvideo \
-i $endboard \
-filter_complex \
"[0:v]select='gt(t,$duration-$tailtime)',scale=w=iw/2.03:h=ih/2.03,setpts=PTS-STARTPTS[v_tail]; \
[0:a]aselect='gt(t,$duration-$tailtime)',asetpts=PTS-STARTPTS[a_out]; \
[1:v][v_tail]overlay=format=auto[v_out]" \
-map "[v_out]" \
-map "[a_out]" \
-video_track_timescale $tbn_main \
pip.mp4
ffmpeg -hide_banner -y -i "part2.mp4" -i "part2av.mp4" -i "pip.mp4" \
-filter_complex \
-map "[0:v];"
-map "[1:a];"
-map "[2:v]"
-c copy "part2pip.mp4" \
# Pass all parts through the concat demuxer
[ -f filelist.txt ] && rm filelist.txt
for f in $intro part1.mp4 $midroll_video part2pip.mp4; do echo "file '$PWD/$f'" >> filelist.txt; done
ffmpeg -hide_banner -y -f concat -safe 0 -i filelist.txt -c copy TEST_FILE.mp4
# Sweep the table
rm pip.mp4 part1.mp4 part2.mp4 filelist.txt```
Insert video in the middle of main video and use -f concat:
# Input parameters.
# All video has same properties: codec_name, width, height, sample_aspect_ratio, r_frame_rate, time_base, pix_fmt
# if one of the video has difference then this video needs to be transcoded
# All audio has same properties: codec_name, channels, sample_rate
# if audio has diff: aresample=async=1,apad,atrim=0:$duration -ac 2
# get durations of audio and video and container: ffprobe -v quiet -show_entries format=duration:stream=duration -of default=nw=1:nk=1 "$f"
intro=VID_20230206_113828.mp4
mainvideo=VID_20230206_113949.mp4
endboard=VID_20230206_114456.mp4
midroll_video=VID_20230206_114248.mp4
#tailtime=20
tailtime=$(ffprobe -v 0 -select_streams v:0 -show_entries format=duration -of default=nw=1:nk=1 "$endboard")
midroll_edit_value=25
#WID=$(ffprobe -v 0 -select_streams v:0 -show_entries stream=width -of default=nw=1:nk=1 "$mainvideo")
#HEI=$(ffprobe -v 0 -select_streams v:0 -show_entries stream=height -of default=nw=1:nk=1 "$mainvideo")
#SAR=$(ffprobe -v 0 -select_streams v:0 -show_entries stream=sample_aspect_ratio -of default=nw=1:nk=1 "$mainvideo")
#if [ "$SAR" = "N/A" ]; then SAR=1; fi
FPS=$(ffprobe -v 0 -select_streams v:0 -show_entries stream=r_frame_rate -of default=nw=1:nk=1 "$mainvideo")
TBN=$(ffprobe -v 0 -select_streams v:0 -show_entries stream=time_base -of default=nw=1:nk=1 "$mainvideo")
TBN=${TBN#*/}
#FMT=$(ffprobe -v 0 -select_streams v:0 -show_entries stream=pix_fmt -of default=nw=1:nk=1 "$mainvideo")
echo $WID $HEI $SAR $FPS $TBN $FMT
#CHL=$(ffprobe -v 0 -select_streams a:0 -show_entries stream=channel_layout -of default=nw=1:nk=1 "$main")
#SRA=$(ffprobe -v 0 -select_streams a:0 -show_entries stream=sample_rate -of default=nw=1:nk=1 "$main")
#echo $CHL $SRA
# Trim the main part of mainvideo
ffmpeg -hide_banner -y -i "$mainvideo" -c copy \
-f segment -segment_times $(($midroll_edit_value-5)),$(($midroll_edit_value+5)) \
-reset_timestamps 1 %d.mp4
# Insert mid
dura_0mp4=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 0.mp4)
m=$( echo "$midroll_edit_value - $dura_0mp4" | bc -l )
ffmpeg -hide_banner -y -i 1.mp4 -i $midroll_video -filter_complex "
[0:v]trim=0:${m}[v0];
[0:v]trim=${m},setpts=PTS-STARTPTS[v2];
[0:a]atrim=0:${m}[a0];
[0:a]atrim=${m},asetpts=PTS-STARTPTS[a2];
[v0][a0][1:v][1:a][v2][a2]concat=n=3:v=1:a=1" \
-r $FPS \
-video_track_timescale $TBN \
1_mid.mp4
# Time calculations to define cut point
dura_2mp4=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 2.mp4)
endboard_cut_point=$(echo "$dura_2mp4-$tailtime" | bc -l)
ffmpeg -hide_banner -y -i 2.mp4 -t $endboard_cut_point -c copy part2.mp4
# Trim the tail of mainvideo and overlay it onto endboard
echo "" > fcs.txt
PAD="[1:v]"
NUM=25
SCA=$((3*$NUM))
for (( i=0; i<$NUM; i++ )); do
echo "[0:v]trim=start_frame=$i:end_frame=$(($i+1)),setpts=PTS-STARTPTS+$i*N,scale=iw-iw*$i/$SCA:ih-ih*$i/$SCA[f$i];" >> fcs.txt
echo "$PAD[f$i]overlay=enable='eq(n,$i)'[o$i];" >> fcs.txt
PAD="[o$i]"
done
echo "[0:v]trim=1,setpts=PTS-STARTPTS+1/TB,scale=iw-iw*$i/$SCA:ih-ih*$i/$SCA[f$i];" >> fcs.txt
echo "$PAD[f$i]overlay=enable='gte(t,1)'" >> fcs.txt
ffmpeg -hide_banner -y \
-ss $endboard_cut_point -i 2.mp4 \
-i $endboard \
-filter_complex_script fcs.txt \
-r $FPS \
-video_track_timescale $TBN \
pip.mp4
# Pass all parts through the concat demuxer
[ -f filelist.txt ] && rm filelist.txt
for f in $intro 0.mp4 1_mid.mp4 part2.mp4 pip.mp4; do echo "file '$PWD/$f'" >> filelist.txt; done
ffmpeg -hide_banner -y -f concat -safe 0 -i filelist.txt -c copy TEST_FILE.mp4
mpv TEST_FILE.mp4
If you cut video by this code and check:
ffmpeg -i $mainvideo -ss 30 -c copy part2.mp4
ffprobe -select_streams v:0 -show_entries packet=pts_time,flags \
-of csv=print_section=0 "part2.mp4" | awk -F',' '/K/ {print $1}'
you can see something like this start:0.016000 and keyframes didn't starts from 0.000000, starts from 2.266016, for example. Because first keyframe is before beginning. If you concat that file without encoding you can get desync.

How to the stream should be automatically restart after 10 seconds if the stream cut

I will restart using this script . But sometime for some reason the stream goes cut....
How to the stream should be automatically restart after 10 seconds if the stream cut.
#!/bin/bash
while true;do
grep -c "Non-monotonous DTS in output stream" file.txt >nonmonotonus.txt
grep -c "Timestamps are unset in a packet for stream" file.txt >timestamp.txt
grep -c "PES packet size mismatch" file.txt >pespacket.txt
grep -c "Error while decoding stream" file.txt >errordecoding.txt
grep -c "Circular buffer overrun" file.txt >circularbuffer.txt
grep -c "Header missing" file.txt >header.txt
grep -c "Conversion failed" file.txt >conversion.txt
file=nonmonotonus.txt
file1=timestamp.txt
file2=pespacket.txt
file3=errordecoding.txt
file4=circularbuffer.txt
file5=header.txt
file6=conversion.txt
if (($(<"$file")>=3000)) || (($(<"$file1")>=500)) || (($(<"$file2")>=100)) || (($(<"$file3")>=1000)) || (($(<"$file4")>=500)) || (($(<"$file5")>=6)) || (($(<"$file6")>=1)); then
stream1 restart > restart.txt
sleep 1
fi
done
__________________________________________________________________________
FFmpeg -re -threads 3 -c:s webvtt -i "$INPUT_URL?source=null&overrun_nonfatal=1&fifo_size=1000000" \
-c:v copy \
-map 0:0 -map 0:1 \
-c:a aac -b:a 128k -ar 48000 \
-threads 4 -f hls -hls_time 2 -hls_wrap 15 \
"manifest.m3u8" \
</dev/null > /dev/null 2>&1 2>file.txt & echo $! > $STREAM_PID_PATH
How to automatically restart the stream.. after cut the .ts file
Thankyou ...

TimeStamp variable stopped working in bash script with avconv

The following script was working fine until I upgraded all my Raspberry Pis to Release 9:
#!/bin/bash
cd /home/pi/Videos/SecurityCam/
DToday=`date '+%Y%m%d-%H%M%S'`
fn="VID $DToday"
SubT="PP $PB $DToday"
avconv -f video4linux2 -i /dev/video0 -t 3600 -r 4 -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf: \text=\'$SubT \%T \' : fontcolor=white#0.8: x=7: y=460" -vcodec libx264 -vb 2000k \-y ${fn}.avi
It is now choking on the %T. Why would that be and what is the right way to get a rolling timestamp in the video?
try with this:
#!/bin/bash
cd /home/pi/Videos/SecurityCam/ || exit
DToday=$(date '+%Y%m%d-%H%M%S')
fn="VID $DToday"
SubT="PP $PB $DToday"
avconv -f video4linux2 -i /dev/video0 -t 3600 -r 4 -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf: \text=\'$SubT \%T \' : fontcolor=white#0.8: x=7: y=460"
-vcodec libx264 -vb 2000k -y "${fn}.avi"

Loop with two variables at once in Bash

I have two file types with the same filenames in one directory:
file1.ts
file1.ec3
file2.ts
file2.ec3
and I need to make a loop which would process pair of files at once (file1.ts + file1.ec3).
Then the loop needs to be restarted with second pair (file2.ts + file2.ec3).
Here is my code:
for i in *.ts; do
for e in *.ec3; do
ffmpeg -i "$i" -i "$e" -map 0:v -map 1:a -codec copy "${i%.ts}_DD+.ts";
done;
done
But when the first cycle ends it tries to process file1.ts + file2.ec3 and brakes everything...
How can I make it work properly?
Try this:
for file in *.ts; do
[ -f "${file%.ts}.ec3" ] &&
ffmpeg -i "$file" -i "${file%.ts}.ec3" -map 0:v -map 1:a \
-codec copy "${file%.ts}_DD+.ts"
done

How to concatenate two MP4 files using FFmpeg?

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.
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 concatenation performed by this method.
If in doubt about which method to use, try the concat demuxer.
Also see
FFmpeg FAQ: How can I join video files?
FFmpeg Wiki: How to concatenate (join, merge) media files
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!
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).
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)
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).
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
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
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
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.
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
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!')
)
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()
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"
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/
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}
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
...
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).
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:
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'
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:
output.mp4 took 11 seconds to produce and is 380.3 MB in size. The AAC-encoded audio is apparently a little smaller.
output.mkv took 8 seconds to produce and is 410.7MB in size.
Troubleshooting/possible errors
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:
https://nono.ma/says/concat-unsafe-file-name-operation-not-permitted
ffmpeg concat: "Unsafe file name"
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.
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
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
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
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
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.
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.
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
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)
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
}
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.
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
ffmpeg -i input1.ts -i input2.ts -i input3.ts -filter_complex "concat=n=3:v=1:a=0" -vn -y output.ts
enjoy!!

Resources