A simple approach for a video that has no audio would be to add audio (a null audio source):
ffmpeg -f lavfi \
-i anullsrc=channel_layout=stereo:sample_rate=44100 \
-i "videoWithoutAudio.mp4" \
-shortest -c:v copy -c:a aac "videoWithAudio.mp4"
If you want to go further here is my shell script that
- concatenates multiple videos
- of different size, aspect ratio and format and
- adds silent audio to videos without audio
Set target size
targetWidth=1920
targetHeight=1080
create scale expression that scales up if necessary depending on source
slhck came up with this awesome scale expression for ffmpeg. No matter which size or aspect ratio you have, this expression scales and pads as needed.
smartScale="scale=iw*min($targetWidth/iw\,$targetHeight/ih):ih*min($targetWidth/iw\,$targetHeight/ih), pad=$targetWidth:$targetHeight:($targetWidth-iw*min($targetWidth/iw\,$targetHeight/ih))/2:($targetHeight-ih*min($targetWidth/iw\,$targetHeight/ih))/2:color=black, setdar=ratio=16/9, setfield=tff"
Make counter and set up variables
c=0
ffmpegInput=""
filter_complex_params_1=""
filter_complex_params_2=""
Go over every Video and do 2 things:
Detect no Audio
string ffmpeg arguments together
You can put your files into a folder, add numbers prior to their filename to determine the order of the concatenation. Do not use spaces in filenames! "01_SummerVacation_2020.mkv
"
for i in *
do
# Use file command to recognize the type of data contained in a file
bashFileCommand=$(file "$i")
# parts of the file command output for many files that are not a video
regexPattern="image|Image|IMAGE|bitmap|text|Text|TEXT|ocument|DOCUMENT|Microsoft|rchive|empty|directory"
# detect if file is a video or not
# if the file contains no string of the above regexPattern it is likely a video
if [[ ! $bashFileCommand =~ $regexPattern ]]; then
# Concatenation works only if all videos contain audio
# detect videos without audio using ffprobe
# store command arguments for ffprobe query in array: codec_type for audio
command_audioCodec_type=(ffprobe -v quiet -select_streams a:0 -show_entries stream=codec_type -of default=nw=1:nk=1 "$i")
# execute command in array and store result variables
result_audioCodec_type=$("${command_audioCodec_type[@]}" | tail -1)
if [[ ! $result_audioCodec_type =~ "audio" ]]; then
# Add dummy audio to video
# Since we cannot overwrite the source file we have to create a temporary file
# ffmpeg chooses the right format according to the extension. Therefore we add "tmp" in front of the filename. Otherwise we would mess up the extension.
ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -i "$i" \
-shortest -c:v copy -c:a aac "tmp_${i}"
# remove original
rm "${i}"
# renaming new file to appear as the original
mv "tmp_${i}" "${i}"
fi
# iterate over all videos (e.g. 3) and add the parameters to the previous parameters:
# Scale videos to set size
# 1st iteration: [0:v]${smartScale}[scaled_v0];
# 2nd iteration: [0:v]${smartScale}[scaled_v0];[1:v]${smartScale}[scaled_v1];
# 3rd iteration: [0:v]${smartScale}[scaled_v0];[1:v]${smartScale}[scaled_v1];[3:v]${smartScale}[scaled_v2];
filter_complex_params_1="$filter_complex_params_1[${c}:v]${smartScale}[scaled_v${c}]; "
# Scale videos to set size
# 1st iteration: [scaled_v0][0:a]
# 2nd iteration: [scaled_v0][0:a][scaled_v1][1:a]
# 3rd iteration: [scaled_v0][0:a][scaled_v1][1:a][scaled_v2][2:a]
filter_complex_params_2="$filter_complex_params_2[scaled_v${c}][${c}:a]"
# Add inputs
# 1st iteration: -i video1.mp4
# 2nd iteration: -i video1.mp4 -i video2.mp4
# 3rd iteration: -i video1.mp4 -i video2.mp4 -i video3.mp4
ffmpegInput="$ffmpegInput -i ${i}"
# increment counter for filter
c=$((c+1))
fi
done
create the concat filter
# 3rd (last) iteration: concat=n=3:v=1:a=1[v][a]
filter_complex_params_3="concat=n=${c}:v=1:a=1[v][a]"
Combine all variables to the final ffmpeg command
# concatenate videos
ffmpeg ${ffmpegInput} -filter_complex "${filter_complex_params_1}${filter_complex_params_2}${filter_complex_params_3}" -map "[v]" -map "[a]" -c:v libx264 -crf 23 -c:a aac concatenated.mkv