Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Combine Multiple Videos Together with FFmpeg Without Re-Encoding

combine-multiple-videos-together-with-ffmpeg-without-re-encoding.jpg

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.

Quick Jump:

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.

Never Miss a Tip, Trick or Tutorial

Like you, I'm super protective of my inbox, so don't worry about getting spammed. You can expect a few emails per year (at most), and you can 1-click unsubscribe at any time. See what else you'll get too.



Comments