About this site

This site provides the simple examples of ffmpeg. Note that this site is unofficial, and the contents are written by only one person (it’s me). So, there might be incompleteness. And I’m not a native English speaker, so my English might be strange, sorry.

Tips on “How to read” throughout this site

About the version of ffmpeg

I am using ffmpeg 3.3.2 as of spring 2019. (Ffmpeg 3.3 was released on April 13th, 2017.)

As I am usually a Windows user and an MSYS user, if we need to use a version other than 3.3.2, the example code is written as follows:

#! /bin/sh
ifn="Air on the G String (from Orchestral Suite no. 3, BWV 1068).mp3"
ifnb="`basename \"${ifn}\" .mp3`"
pref="`basename $0 .sh`"
#
"/c/Program Files/ffmpeg-4.1-win64-shared/bin/ffmpeg" -y \
    -i "${ifn}" -filter_complex "

[0:a]acrossover=split='500 2000'[div1][div2][div3];

[div1]asplit[div1_1][div1_2];
[div2]asplit[div2_1][div2_2];
[div3]asplit[div3_1][div3_2];

[div1_2]showcqt=s=1920x1080[v1];
[div2_2]showcqt=s=1920x1080[v2];
[div3_2]showcqt=s=1920x1080[v3]" \
    -map '[v1]' -map '[div1_1]' "${pref}_${ifnb}_1.mp4" \
    -map '[v2]' -map '[div2_1]' "${pref}_${ifnb}_2.mp4" \
    -map '[v3]' -map '[div3_1]' "${pref}_${ifnb}_3.mp4"
#! /bin/sh
ifn="Pexels_2877_2880_fast.mp4"
ifnb="`basename \"${ifn}\" .mp4`"
pref="`basename $0 .sh`"

"../../ffmpeg-20190108-8a1fc95-win64-shared/bin/ffmpeg" -y \
  -i "${ifn}" -vf "rgbashift=bv=-50:edge=smear" \
  -an "${pref}_${ifnb}".mp4

The latter means that this is not available in 4.1 (will be available in the next release of 4.2), but the former is not necessarily so. It simply means that I have only installed 4.1 as a newer release than 3.3.2.

Unfortunately, we can not know from the official reference itself “when did it become available?” for each feature. You can see the rough changes from NEWS. Exact and complete changes can only be obtained from the ChangeLog. (However, this ChangeLog still does not explain the differences between micro versions.)

Therefore, if my example doesn’t work with your ffmpeg, please refer to ChangeLog.

see also

Using FFMPEG on Docker

About `shell’ script

Almost all of my examples is written in the form of `shell’ script. More exactly speaking, it is `bash’ script.

ffmpeg itself is independant from the specific platform, so you can use it without `scripting’. However, command lines that use ffmpeg tend to be long:

with Windows `command prompt’
c:\Users\hhsprings\Videos>ffmpeg -y -i input.mp4 -vf "setpts=PTS*2-STARTPTS,minterpolate=fps=30:mi_mode=mci:mc_mode=aobmc" -af "atempo=0.5" -color_primaries bt709 -color_trc bt709 -colorspace bt709 -ss 00:00:30 -t 30 out.mp4
with Unix’s bash `command line’
[me@host: Videos]$ ffmpeg -y -i input.mp4 \
> -vf "setpts=PTS*2-STARTPTS,minterpolate=fps=30:mi_mode=mci:mc_mode=aobmc" \
> -af "atempo=0.5" -color_primaries bt709 \
> -color_trc bt709 -colorspace bt709 \
> -ss 00:00:30 -t 30 out.mp4

Showing as a script is worthwhile only in this regard:

hoge.sh
#! /bin/sh
ffmpeg -y -i input.mp4 \
    -vf "setpts=PTS*2-STARTPTS,minterpolate=fps=30:mi_mode=mci:mc_mode=aobmc" \
    -af "atempo=0.5" \
    -color_primaries bt709 -color_trc bt709 -colorspace bt709 \
    -ss 00:00:30 -t 30 out.mp4
with Unix’s bash `command line’
[me@host: Videos]$ ./hoge.sh  # you might need `chmod u+x hoge.sh'
[me@host: Videos]$ sh ./hoge.sh  # you don't need `chmod u+x hoge.sh'

I use bash, so you can get a complete reference for this from here.

“Bash” is a shell born as “‘Bourne-Again SHell’”. As the name of “Again” implies, it is almost “Bourne-Shell” fully compatible.

Strictly speaking, “bash” does not realize some features of “Bourne-Shell” (such as file descriptor), but it has nothing to do with the scope of the script I’m writing on this site, that is, the script I write is “almost Bourne-Shell script”.

Most of the currently available environments are “GNU-like”, so “sh” is mostly “bash”. Thus, my script sometimes uses “bash-specific” features. For example, I frequently use “basename”, which has historically been a feature that is not necessarily present in the system. In bash this is a built-in. Systems that do not even have ‘basename’ as a command are quite rare, but unfortunately if you are using such a system, you can do the same thing with ‘sed’:

[me@host: Videos]$ type -p ffmpeg
/c/Program Files/ffmpeg-3.3.2-win64-shared/bin/ffmpeg
[me@host: Videos]$ type -p ffmpeg | sed 's@^.*/@@'
ffmpeg
[me@host: Videos]$ pwd
/c/Users/hhsprings/Videos
[me@host: Videos]$ ifn=`pwd`/Pexels_852411.mp4
[me@host: Videos]$ echo ${ifn}
/c/Users/hhsprings/Videos/Pexels_852411.mp4
[me@host: Videos]$ echo ${ifn} | sed 's@^.*/\(.*\)\.mp4@\1@'
Pexels_852411

I try not to use too complicated shell script features on this site, but I always use Shell-Expansions and I sometimes use Here Documents, so If you are confused by reading the script, refer to these references.

Here are some of the shell features that I use most often.

For example:

#! /bin/sh
# input1.mp4: 1280x720
# input2.mp4: 1280x720

# using ffmpeg overlay's constant (W, w, H, and h)
ffmpeg -y -i input1.mp4 -i input2.mp4 -filter_complex "
[0:v]pad=1920:1080:0:0[vb];
[vb][1:v]overlay=x=W-w:y=H-h" -to 00:00:30 output1.mp4

# you can the same thing with shell's substitution:
input_width=1280
input_height=720
target_width=1920
target_height=1080
overlay_x=`expr ${target_width} - ${input_width}`
overlay_y=`expr ${target_height} - ${input_height}`
# # if you use bash, you can also:
# overlay_x=$((${target_width} - ${input_width}))
#
ffmpeg -y -i input1.mp4 -i input2.mp4 -filter_complex "
[0:v]pad=${target_width}:${target_height}:0:0[vb];
[vb][1:v]overlay=x=${overlay_x}:y=${overlay_y}" -to 00:00:30 output2.mp4

This example shows the difference between using ffmpeg’s own features or not. If ffmpeg can do it with its own features such as this example, it is case-by-case which one to choose. If there is a lack of ffmpeg’s features, the shell’s features may be a savior.

I also often use so-called `fallback’ of shell parameter expansion like this:

[me@host: Videos]$ echo ${myvar}

[me@host: Videos]$ echo ${myvar:-"hello."}
hello.
[me@host: Videos]$ myvar=bye.
[me@host: Videos]$ echo ${myvar:-"hello."}
bye.
[me@host: Videos]$

If you use this in a script, for example:

avgblur_exam.sh
#! /bin/sh
ifn="Grape_Vineyard.mp4"
ifnb="`basename \"${ifn}\" .mp4`"
pref="`basename $0 .sh`"
#
radius=${radius:-10}
#
ffmpeg -y -i "${ifn}" -filter_complex "
[0:v]split[0v1][0v2];

[0v1]crop=900:500:30:580,setsar=1,
avgblur=sizeX=${radius}

,drawbox=c=blue[b];

[0v2][b]overlay=30:580[v]
" -map '[v]' -an "${pref}_${ifnb}.mp4"

in this case, `radius’ can be changed from outside the script:

[me@host: Videos]$ ./avgblur_exam.sh  # in this case, radius=10
[me@host: Videos]$ radius=5 ./avgblur_exam.sh  # in this case, radius=5
[me@host: Videos]$ for i in 1 2 3 4 5 ; do radius=${i} ./avgblur_exam.sh ; done

To reveal the essence, almost all of the examples I write give fixed filenames. However, if you want to “generalize” your script, you can of course make the file name variable:

avgblur_exam.sh
#! /bin/sh
ifn="${1:-'Grape_Vineyard.mp4'}"  # take first argument if specified, otherwise Grape_Vineyard.mp4
ifnb="`basename \"${ifn}\" .mp4`"
pref="`basename $0 .sh`"
#
radius=${radius:-10}
#
ffmpeg -y -i "${ifn}" -filter_complex "
[0:v]split[0v1][0v2];

[0v1]crop=900:500:30:580,setsar=1,
avgblur=sizeX=${radius}

,drawbox=c=blue[b];

[0v2][b]overlay=30:580[v]
" -map '[v]' -an "${pref}_${ifnb}.mp4"
[me@host: Videos]$ ./avgblur_exam.sh myinput.mp4

See Shell Parameter Expansion for more details.

In this document I try to use only the basic shell functionality as often as possible in order not to hide the essence of ffmpeg, but sometimes I use the convenience shell functionality without notice. For example:

cal_2019_2020_to_mp4.sh
#! /bin/bash
pref="`basename $0 .sh`"
#
ifn="cal_2019_2020.png"  # Image obtained by combining 24 images vertically
#
t="t"
images=24
pause=7
wipe=1
perpage=$((${pause} + ${wipe}))
#
ffmpeg -y -i "${ifn}" -filter_complex "
color=white:s=1920x1080,loop=-1:size=2[bg];
[0:v]scale='1920:1080*${images}',loop=-1:size=2[0v];

[bg][0v]overlay='
y=-1080*if(
gte(mod(${t}, ${perpage}), ${pause}),
floor(${t} / ${perpage}) + (mod(${t}, ${perpage}) - ${pause}),
floor(${t} / ${perpage}))
'
" -t $((${perpage} * ${images} - ${wipe})) ${pref}.mp4

In this script, I calculate using bash specific built-in function “$(())”. If you are not familiar with shell scripts, using Bourne-shell’s built-in debugging features “-x” may help:

[me@host: ~]$ sh -x ./cal_2019_2020_to_mp4.sh 2>&1 | tee ./cal_2019_2020_to_mp4.sh.log
++ basename ./cal_2019_2020_to_mp4.sh .sh
+ pref=cal_2019_2020_to_mp4
+ ifn=cal_2019_2020.png
+ t=t
+ images=24
+ pause=7
+ wipe=1
+ perpage=8
+ ffmpeg -y -i cal_2019_2020.png -filter_complex '
color=white:s=1920x1080,loop=-1:size=2[bg];
[0:v]scale='\''1920:1080*24'\'',loop=-1:size=2[0v];

[bg][0v]overlay='\''
y=-1080*if(
gte(mod(t, 8), 7),
floor(t / 8) + (mod(t, 8) - 7),
floor(t / 8))
'\''
' -t 191 cal_2019_2020_to_mp4.mp4
ffmpeg version 3.3.2 Copyright (c) 2000-2017 the FFmpeg developers
  built with gcc 7.1.0 (GCC)
...

Note

Unfortunately ffmpeg’s console output is stupid:

  • The target information that is neither error nor trace log will go into standard error

  • What is not the desired output will go into the standard output

Due to this unfortunate fact, it is not easy to separate the above shell debug trace and the ffmpeg output.

for Windows user

“The world is made only of Unix and Windows.” This is almost correct as of 2019. Some people may not know, but the current MacOS are based on BSD Unix. Thus, Unix knowledge is mostly usable with current MacOS. In this site, most of ffmpeg examples are written by bourne-shell script (or bash script). These examples can be used almost as they are on MacOS (except for the ffmpeg path). Others, for example android, are linux based systems, and over 90% of currently available operating systems are either “Unix based” or “Windows based”.

So, “Windows users must use MS DOS, and we must rewrite these for DOSMES”!!

Wait, wait, wait… DON’T THINK SO!!!!!

You should know that a system called “MS DOS” is a few trillion times as difficult as you think, weird and “hard to use”. You will spend time only fighting MS DOS itself (especially the quotes rules) and you will not be able to concentrate on “ffmpeg itself”. If you really want a Windows-like solution, you should consider PowerShell at least not DOS. (I won’t write an example to use from a PowerShell script, but it should be tens of thousands times easier than writing in MS DOS.)

Windows users who want to refer to the script I wrote should install a “Unix-like” environment.

I am an MSYS user. MSYS2 may be recommended now. Of course cygwin is famous (though I do not like it). Recently, “Windows version of Unix-grown OSS” somtimes has a minimal Unix environment. For example, GIT. The bourne-shell feature I use in this document is so limited that it can probably be used with such “minimal Unix”.

-filter_complex, -vf, -af, and -filter_complex_script, pipe:

What can only be done with filter_complex is such as the case where the video filter and the audio filter are connected like this:

[me@host: Videos]$ ffmpeg -y -i input.mp4 -filter_complex "[0:a]showcqt" -f matroska - | ffplay -

In such a case, it is probably impossible to achieve with ffplay alone. In other words, it can not be realized only with -af and -vf.

However, the example I write on this site does not strictly follow this distinction. As the name suggests, in most of the “complex” filter graph cases I use “filter_complex”, but even simple ones I also use “-filter_complex”, and on the contrary I use “-vf”, “-af” for something quite complicated. There is no particular meaning for such a choice. So don’t worry too much about this.

“-filter_complex” and “-filter_complex_script” relate to more serious issues.

The fact that the filter graph is long is equivalent to the command line being long as long as you use only “-filter_complex”. There is a maximum length limit for shell command line lengths. For example, MSYS bash on Windows rejects such a long command line as “Bad file number” error if it exceeds 255 characters. In such a case, it is necessary to use “-filter_complex_script” after separating the filter graph script as a separate file:

#! /bin/sh
ffmpeg -y -i input.mp4 -filter_complex_script my_graph.txt out.mp4

Such file separation sometimes leads to code management issues, but you can use ffmpeg’s “pipe:” and “Bourne-shell” feature Here Documents:

#! /bin/sh
# -*- coding: utf-8 -*-

ffmpeg -hide_banner -y \
  -i 1.mp4 -i 2.mp4 -i 3.mp4 -i 4.mp4 \
  -filter_complex_script pipe: \
  -map '[v]' -map '[a]' -color_primaries bt709 -color_trc bt709 -colorspace bt709 merged_expr1.mp4 << __END__
color=c=black:d=0.917:s=960x540,fps=fps=29.97,setsar=1[gap0v0];
sine=d=0.917:frequency=0:sample_rate=44100[gap1a_c0_0];
sine=d=0.917:frequency=0:sample_rate=44100[gap1a_c1_0];
[gap1a_c0_0][gap1a_c1_0]amerge=2[gap1a0];
[0:v]fps=fps=29.97,scale=960:540,setsar=1[v0_0];
[0:a]aresample=44100[a0_1];
color=c=black:d=45.966:s=960x540,fps=fps=29.97,setsar=1[gap2v0];
sine=d=45.966:frequency=0:sample_rate=44100[gap3a_c0_0];
sine=d=45.966:frequency=0:sample_rate=44100[gap3a_c1_0];
[gap3a_c0_0][gap3a_c1_0]amerge=2[gap3a0];
[gap0v0][gap1a0][v0_0][a0_1][gap2v0][gap3a0]concat=a=1:n=3:v=1[vc0][ac0];

color=c=black:d=7.680:s=960x540,fps=fps=29.97,setsar=1[gap0v1];
sine=d=7.680:frequency=0:sample_rate=44100[gap1a_c0_1];
sine=d=7.680:frequency=0:sample_rate=44100[gap1a_c1_1];
[gap1a_c0_1][gap1a_c1_1]amerge=2[gap1a1];
[1:v]fps=fps=29.97,scale=960:540,setsar=1[v1_0];
[1:a]aresample=44100[a1_1];
color=c=black:d=66.863:s=960x540,fps=fps=29.97,setsar=1[gap2v1];
sine=d=66.863:frequency=0:sample_rate=44100[gap3a_c0_1];
sine=d=66.863:frequency=0:sample_rate=44100[gap3a_c1_1];
[gap3a_c0_1][gap3a_c1_1]amerge=2[gap3a1];
[gap0v1][gap1a1][v1_0][a1_1][gap2v1][gap3a1]concat=a=1:n=3:v=1[vc1][ac1];

[2:v]fps=fps=29.97,scale=960:540,setsar=1[v2_0];
[2:a]aresample=44100[a2_1];
color=c=black:d=48.793:s=960x540,fps=fps=29.97,setsar=1[gap0v2];
sine=d=48.793:frequency=0:sample_rate=44100[gap1a_c0_2];
sine=d=48.793:frequency=0:sample_rate=44100[gap1a_c1_2];
[gap1a_c0_2][gap1a_c1_2]amerge=2[gap1a2];
[v2_0][a2_1][gap0v2][gap1a2]concat=a=1:n=2:v=1[vc2][ac2];

color=c=black:d=4.373:s=960x540,fps=fps=29.97,setsar=1[gap0v3];
sine=d=4.373:frequency=0:sample_rate=44100[gap1a_c0_3];
sine=d=4.373:frequency=0:sample_rate=44100[gap1a_c1_3];
[gap1a_c0_3][gap1a_c1_3]amerge=2[gap1a3];
[3:v]fps=fps=29.97,scale=960:540,setsar=1[v3_0];
[3:a]aresample=44100[a3_1];
color=c=black:d=1.000:s=960x540,fps=fps=29.97,setsar=1[gap2v3];
sine=d=1.000:frequency=0:sample_rate=44100[gap3a_c0_3];
sine=d=1.000:frequency=0:sample_rate=44100[gap3a_c1_3];
[gap3a_c0_3][gap3a_c1_3]amerge=2[gap3a3];
[gap0v3][gap1a3][v3_0][a3_1][gap2v3][gap3a3]concat=a=1:n=3:v=1[vc3][ac3];

[vc0][vc1]hstack=inputs=2:shortest=1[1v];

[vc2][vc3]hstack=inputs=2:shortest=1[2v];

[1v][2v]vstack=inputs=2:shortest=1[v];

[ac0][ac1][ac2][ac3]
amerge=inputs=4,
pan=stereo|\
    c0 < c0 + c1 + c4 + c5 |\
    c1 < c2 + c3 + c6 + c7
[a]
__END__

(This example is the result of Jorgen Modin’s align-videos-by-sound.)

“pipe:” is the special file of ffmpeg that means “standard input”. Here Documents makes it unnecessary to manage temporary files. (Exactly speaking, “pipe:” is one of protocol of ffmpeg, and - can be also used as “standard input”.)

and shell’s `trap’

Regarding “code management issues”, we can also use shell’s trap.

Speaking in terms of modern programming languages, “trap” is generally the same as “exception handler” but also captures normal termination. For example:

#! /bin/sh
f1="Pexels_2877_2880.mp4"
f2="Pexels_2880_2877.mp4"
#
trap 'rm -fv tmpass.ass' 0 1 2 3 15
#
cat << __END__ > tmpass.ass
[Script Info]
PlayResY: 600
WrapStyle: 1

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Expl, Arial,44,&H00FFB0B0,&H00B0B0B0,&H00303030,&H80000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00, 7 ,10,10,10,0

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,00:00:00.00,00:00:20.00,Expl, NTP,0,0,0,,{\pos(10,10)}Using alphamerge
__END__
#
ffmpeg -y -i "${f1}" -i "${f2}" -filter_complex_script pipe: -an out.mp4 << __END__
color=0xAAAAAA:s=1920x1080:d=24[alpha];
[1:v][alpha]alphamerge[1v];
[0:v][1v]overlay,subtitles=tmpass.ass
__END__

Use more powerful scripting

Often you will not be satisfied with the shell. The shell is easy to write if you want to simply execute the process sequentially, but sometimes it is too coarse in control.

Using more sophisticated scripting rather than a job control-only language like the shell is more cumbersome to write than using the shell, but it’s more flexible. For example, if your goal is to combine serially numbered split videos, if you use the python language:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import sys
import subprocess
import shutil
import tempfile
from itertools import groupby
from glob import glob


if hasattr("", "decode"):  # for python 2.7
    _encode = lambda s: s.encode(sys.getfilesystemencoding())
else:
    _encode = lambda s: s


if __name__ == '__main__':
    def _key(fn):
        return fn.split(".")[0]

    for k, g in groupby([fn for fn in sorted(glob("*.[0-9]*.mkv"))], key=_key):
        ifns = list(g)
        ofn = "{}.mkv".format(k)
        cmdl = ["ffmpeg", "-hide_banner", "-y"]
        for ifn in ifns:
            cmdl.extend(["-i", ifn])
        cmdl.append("-filter_complex")
        cmdl.append(
            "{}concat={}:a=1:v=1".format(
                "".join(["[{}:v][{}:a]".format(i, i)
                         for i in range(len(ifns))]), len(ifns)))
        cmdl.append(ofn)
        subprocess.check_call(map(_encode, cmdl))
        for ifn in ifns:
            shutil.move(ifn, tempfile.gettempdir())

This code is more complicated than writing it in a shell script just by calling ffmpeg, but it can do very detailed work instead. In the case of this example, it is not possible to simply write a groupby equivalent with a shell script alone.

Use Matroska

Especially when processing takes a lot of time, consider the use of “matroska” as container like this:

#! /bin/sh
ffmpeg -y \
-filter_complex "
color=black:s=992x558:d=8
,format=yuv444p

,geq='128+sin(((W/2-X)^2+(H/2-Y)^2)*(8*PI/((W/2)^2+(H/2)^2)))*127'

,format=yuv420p
[v]" -map '[v]' -an out.mkv

Many of my examples choose mp4 as the output format. But for many containers such as mpeg4, only when the process is completely finished or typed with ‘q’ you get ‘valid video’. In other words, if you kill with Ctrl-C etc., it will become “invalid video” and almost all players can not play it. The more complex the filter graph, the more often ffmpeg can not end with “q”. As it is common that processing takes hours, this is fatal for us.

But with Matroska, some players (such as VLC media player) can play it, even when killed like that.

“Matroska” may not be suitable for distribution yet, but you can be happy if you use it actively.

ffmpeg and ffplay

There are also examples in the official document that you can “copy and paste” and get it to work immediately. However, many are written as examples used from ffplay. Even so, I wrote the examples for ffmpeg not for ffplay.

However, the ffplay example is not worthless.

Because encoding / decoding with ffmpeg takes a lot of time in most cases, one cycle of trial and error is often long. In such cases, it is worthwhile to experiment with ffplay, which gives immediate visual results:

[me@host: Videos]$ for s in 1 5 10 ; do ffplay -vf 'hue=s='$s input.mp4 ; done
[me@host: Videos]$ # trying video source
[me@host: Videos]$ ffplay -f lavfi mptestsrc
[me@host: Videos]$
[me@host: Videos]$ # trying audio source
[me@host: Videos]$ ffplay -f lavfi sine=440:b=2:d=10

Or, if your goal is to “view a video with a specific filter applied” instead of “to create a video of the conversion result”, for example, you can write a script like this:

ff_hflip_play.sh
#! /bin/sh
ffplay "$1" -vf 'hflip'
[me@host: Videos]$ /path/to/ff_hflip_play.sh myvideo.mkv

Note that option -filter_complex can not be used in ffplay, thus, if my example is doing “what can only be done with filter_complex,” you can not replace it with ffplay. There are at least two other situations where ffplay alone can not help:

  1. It seems to be related to a major modification of SDL, the latest ffplay may not be able to play audio that can be played without problems with older ffplay.

  2. We want to use the old ffplay to be in situation 1, but the filter is only in the new ffmpeg.

In such a case, it can be realized by linking ffmpeg and ffplay by using matroska and pipe: described above:

[me@host: Videos]$ /path/to/newer_ffmpeg/bin/ffmpeg -vf "limiter=max=100:planes=0x1" \
> -f matroska - | /path/to/older_ffmpeg/bin/ffplay -