Combine Multiple Videos Together with FFmpeg Without Re-Encoding

Being able to stitch together a bunch of little videos into 1 longer video is nice from time to time. Here's a 1 liner to do it.
In a previous post, we covered a script to create clips out of a long video with FFmpeg. This post will cover the opposite which is stitching together or combining multiple clips into a new video.
# The FFmpeg Command
In its simplest form, you can have a text file called input.txt with lines
like you see below. By the way this file can be named anything, I chose input
since these are input files. Just make sure the file paths are wrapped in
single quotes:
file 'clip-1.mp4'
file 'clip-2.mp4'
file '/tmp/hello.mp4'
As long as the resolution, frame rate, video / audio codecs and other details about the video are the same for each video clip you can stitch / concat them together without re-encoding by running:
ffmpeg -f concat -safe 0 -i input.txt -c copy output.mp4
The above will combine all of the videos in input.txt and produce a new
output.mp4 file.
The -safe 0 flag disables FFmpeg’s safe path checking, which will reject
absolute paths by default amongst other things. Without the flag FFmpeg will
error out saying “unsafe file name, operation not permitted”. As an aside,
relative paths are relative to the input file.
# The Shortcut Script
The above can be tedious to run by hand since it involves creating a file on disk, so we can wrap creating that input text file by parsing file arguments which include wildcard support.
I’ve named the script mkvideo and it’s available in the DotFriedRice
repo.
In the end the script will let you run a command such as mkvideo clip-1.mp4 clip-2.mp4 /tmp/hello.mp4 output.mp4 or with a wildcard mkvideo *.mp4 output.mp4.
#!/usr/bin/env bash
# Create a video out of multiple compatible clips without re-encoding it.
#
# Usage:
# mkvideo INPUT_PATH... OUTPUT_PATH
#
# Examples:
# mkvideo 1.mp4 2.mp4 3.mp4 combined.mp4
set -o errexit
set -o pipefail
set -o nounset
if [ "${#}" -lt 2 ]; then
echo "Supply at least one input and one output, example: 1.mp4 2.mp4 combined.mp4" >&2
exit 1
fi
# Get all but the last arguments (input) and then the last argument (output).
inputs=("${@:1:$#-1}")
output="${!#}"
declare -A seen
# Parse the arguments, it gets piped into ffmpeg as stdin using a format that
# is compatible with ffmpeg for accepting multiple files as input, such as:
# file '1.mp4'
# file '2.mp4'
for path in "${inputs[@]}"; do
# Skip files that have been accounted for already.
[ -n "${seen["${path}"]+x}" ] && continue
seen["${path}"]=1
# Skip directories.
[ -d "${path}" ] && continue
if [ ! -f "${path}" ]; then
echo "Error: '${path}' does not exist or is not a file" >&2
exit 1
fi
# The concat demuxer resolves relative paths relative to the input file,
# which is /dev/stdin in this case, so use absolute paths with realpath.
path="$(realpath "${path}")"
# Escape apostrophes for the concat demuxer's quoting syntax.
path="${path//\'/\'\\\'\'}"
printf "file '%s'\n" "${path}"
done | ffmpeg -f concat -safe 0 -i /dev/stdin -c copy "${output}"
Most of the script handles parsing file patterns (including wildcard patterns)
so you can pass in things like *.mp4 or my-clip-*.mp4 as input to match on
all videos in a specific directory. This post goes into more detail on
that code.
It also avoids creating an input text file on disk by piping the list of file
names it creates in the for in loop to the ffmpeg command we ran before,
fortunately FFmpeg supports being able to use /dev/stdin as input.
If you want to see what files are being passed into ffmpeg for
troubleshooting you can replace the call to | ffmpeg [...] with | cat -,
then it will print out the files.
The video below demos all of the above.
# Demo Video
Timestamps
- 0:38 – What we’re going to do
- 1:01 – Breaking down the ffmpeg command
- 1:55 – Creating the input text file
- 2:49 – Running the ffmpeg command
- 3:38 – How a script can help us
- 4:13 – Relative vs absolute paths and the safe flag
- 5:43 – Using the shortcut script
- 6:11 – Going over the mkvideo script
- 9:27 – Inspecting the loop with cat and handling apostrophes
Will you be using this script to combine videos? Let me know below.