1

I am still pretty new to linux and have been trying very hard at getting this right. Please help me to get this right.

I am trying to merge 2 videos (1 from each folder) multiple times in like a batch process, automatically 1 set after the next.

I am trying to do it with ffmpeg and for loop in order to take one file from top of list of folder-1 and merge it with one file from top of list of folder-2, and then repeat the process all the way down the folder lists until all videos have been paired up with one another.

To imagine this, picture 2 folders side by side with files in each, now line up the files from the folder on the left to the folder on the right. I want to merge 2 videos into 1 multiple times. I was going to draw a diagram but I think its understood, hopefully.

Here is my code, I have changed this so many times but my latest one was: (and I attempted to run this in the directory of folder-1, hoping it would read the files and join 1 to 1 from folder-2, but sadly no luck.

for filename in *.mp4; do
    folder2="/path/to/folder2"
    xout="/output/file/as/$(date +'%Y-%m-%d_%H:%M:%S').mp4"
    ffmpeg -f concat -i "${filename%}" -i "$vid2/${filename%}" -acodec copy -vcodec copy "$xout"
done

Here is another attempt that is giving me same errors. No such file or directory

for filename in *.mp4; do
    vid1="/path/folder-1/${filename%.*}"
    vid2="path/folder-2/${filename%.*}"
    out1="/path/output/$(date +'%Y-%m-%d_%H:%M:%S').mp4"
    ffmpeg -f concat -i "$vid1" -i "$vid2" -acodec copy -vcodec copy "$out1"
done

Can anyone please, please, please tell me what it is I am doing wrong, I can't get this right, it has been around 4 hours and I have tried so many things and read up on so many articles regarding for loops, while loops, ffmpeg commands, etc. Thank you so much for your precious time, it is greatly appreciated!

1

In your first example, ${filename%} doesn't change $filename at all, and as you told ffmpeg to open the .mp4 file with the concat demuxer -f concat, the error message should have been <actual name of $filename>: Invalid data found when processing input, but you recieved No such file or directory, so I suspect the glob was not working - maybe your working directory wasn't actually folder-1, in which case the full 'file not found' error message from ffmpeg would have been *.mp4: No such file or directory - the glob didn't match any files, so the parameter filename was set to <literal asterisk><dot>mp4.

In your second example, the paramater subtitution ${filename%.*} is removing .mp4 from the end of the names you are providing to ffmpeg - possibly why you recieved No such file or directory.

In addition, in both your examples the usage of ffmpeg's concat demuxer is incorrect. The concat demuxer requires a text file as its input (or appropriate shell substitution as used in the below example with <()). In your example, input files were specified directly which is what you might do if you wanted to mux all the streams to a single container - the streams would be in "parallel" (eg. to add subtitle streams, or a secondary audio track). Concatenation will join the files sequentially.


If what you want is to concatenate an .mp4 file in folder-1 with another .mp4 file in folder-2, where the filenames match... Here's an example which I have tested, using absolute paths - /tmp/a and /tmp/b are used instead of /path/folder-1 and /path/folder-2, /tmp as /path/output:

seconddirectory="/tmp/b"

for i in /tmp/a/*.mp4
do
    if ! [[ -e "$seconddirectory/${i##*/}" ]]
    then
        >&2 echo "no matching file in $seconddirectory for $i"
        continue
    fi
    out="${i##*/}"
    out="/tmp/${out%.*}-$(date +'%Y-%m-%d_%H:%M:%S')"
    ffmpeg -f concat -safe 0 -i <(printf '%s\n' "file '$i'" "inpoint 0" "file '$seconddirectory/${i##*/}'" "inpoint 0") -c copy "$out.mp4"
done

This will output all .mp4 files in /tmp/a for which a matching filename exists in /tmp/b to /tmp/*-date.mp4, concatenating the matching files. (note: don't use only the date for the output as it will cause conflicting filenames - use something unique to avoid this - in this example the basename of the input file/s is used). The ${i##*/} substitutions are to remove the path component from the absolute paths, leaving just the filename component - using absolute paths will mean that the current working directory won't interfere with * glob matching.

If you wanted to join files with non-matching filenames, you would need to work something different out. eg. to match the first file from each folder then the second, etc. (in the order that bash glob sorts them):

a=(/tmp/a/*.mp4)
b=(/tmp/b/*.mp4)
a=("${a[@]:0:${#b[@]}}")
b=("${b[@]:0:${#a[@]}}")
for (( i=0; i<${#a[@]}; i++ ))
do
    out="${a[i]##*/}"
    out="${out%.*}-${b[i]##*/}"
    out="/tmp/${out%.*}-$(date +'%Y-%m-%d_%H:%M:%S')"
    ffmpeg -f concat -safe 0 -i <(printf '%s\n' "file '${a[i]}'" "inpoint 0" "file '${b[i]}'" "inpoint 0") -c copy "$out.mp4"
done

This uses array variables, in conjuction with globbing to build lists of .mp4 files in each directory, and will concatenate a pair (one from each list), until no more pairs exist.

Instead of the process substitution <(), you could match pairs of input files and write them to a text file in the format required by ffmpeg, then process that file. eg. for the first example it would look like:

seconddirectory="/tmp/b"

for i in /tmp/a/*.mp4
do
    if ! [[ -e "$seconddirectory/${i##*/}" ]]
    then
        >&2 echo "no matching file in $seconddirectory for $i"
        continue
    fi
    out="${i##*/}"
    out="/tmp/${out%.*}-$(date +'%Y-%m-%d_%H:%M:%S')"
    printf '%s\n' "file '$i'" "inpoint 0" "file '$seconddirectory/${i##*/}'" "inpoint 0" > "$out.ffcat"
done

for i in /tmp/*.ffcat
do
    ffmpeg -f concat -safe 0 -i "$i" -c copy "${i/%.ffcat/.mp4}"
done

An alternative to ffmpeg for this job would be mkvmerge (from mkvtoolnix). It offers a way to concatenate files without needing a text file as input. In the first example from above the entire ffmpeg line could be replaced with:

mkvmerge -o "$out.mkv" "$i" + "$seconddirectory/${i##*/}"

The resulting output file will be in a .mkv matroska container, instead of the .mp4 container used in ffmpeg example above.


Putting all this together in a reusable function:

function concatenation_example() {
    local a b c i out mf
    if type mkvmerge >/dev/null
    then
        mf=m
    elif type ffmpeg >/dev/null
    then
        mf=f
    else
        >&2 echo "This function won't work without either mkvmerge or ffmpeg installed."
        return 1
    fi
    if [[ ! -d "$1" || ! -d "$2" || ! -d "$3" ]]
    then
        >&2 printf '%s\n' "concatenation_example FIRSTDIR SECONDDIR OUTDIR" "all arguments must be directories"
        return 1
    fi
    for i in "$1"/*.mp4
    do
        if ! [[ -e "$2/${i##*/}" ]]
        then
            >&2 echo "no matching file in $2 for $i"
            continue
        fi
        out="${i##*/}"
        out="$3/${out%.*}-$(date +'%Y-%m-%d_%H:%M:%S')"
        case "$mf" in
            (m) mkvmerge -o "$out.mkv" "$i" + "$2/${i##*/}" ;;
            (f) ffmpeg -f concat -safe 0 -i <(printf '%s\n' "file '$i'" "inpoint 0" "file '$2/${i##*/}'" "inpoint 0") -c copy "$out.mp4" ;;
        esac
    done
}

  • Wow, thank you so much for taking the time to explain everything and teach me all that, allowing me to learn in the best possible way! Thank you so much for your precious time. I am so grateful and appreciative of your work in which you have properly and ever so professionally explained to me. You are such an amazing and incredibly patient teacher! Thank you so much, this has been the best answer I have ever received in all my years on the Internet. Hope to see you again! Take care! Thanks again! :) – Anon User Jan 28 at 12:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.