1

I want to concat two video files together by using ffmpeg. I already found this command:

ffmpeg -i INPUT1 -i INPUT2 -filter_complex \"[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]\" -map \"[v]\" -map \"[a]\" OUTPUT

The Problem is, that INPUT2 not alway has Audio, so ffmpeg throws an invalid Input Stream. I'm very inexperienced with ffmpeg and I'm not getting smart out of the documentation. Is there a possible way that is decently understandable, possibly in one command?

Summed up: I want to concat two Video Files including Audio, but the second file may or may not has an audio layer.

Sven Nolting
  • 13
  • 1
  • 6

2 Answers2

1

Must match stream count & type

All segments to be concatenated must have the same number and type of streams. So if input1.mp4 has audio, and input2.mp4 does not, then you need to either omit the audio from input1.mp4 or add audio for input2.mp4. One method is to add silent audio with the anullsrc filter as shown in:

Detecting audio

There is no option to automatically add missing audio, so you have to check the inputs with ffprobe to tell you if the inputs have audio or not:

You can then use the results in your preferred scripting/coding language to execute the appropriate command.

llogan
  • 121,796
  • 28
  • 232
  • 243
0

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:

  1. Detect no Audio

    • Identify videos with no audio

    • Add null audio to those videos

  2. string ffmpeg arguments together

    • Add every video as an ffmpeg input

    • Expand ffmpegs filter_complex for every new input

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
drake7
  • 952
  • 7
  • 20