drawtext

doc

https://ffmpeg.org/ffmpeg-filters.html#drawtext-1

see also

drawtext with signalstats, drawtext with pts, etc, drawtext, drawgraph, blackframe, freezedetect

Minimal example

see also

Text expansion

If the fontconfig support is disabled, or if you don’t want to care of the fontconfig (or if you aren’t familiar with it), you must specify fontfile, so the most minimalistic example for you is:

[me@host: ~]$ ffplay -f lavfi "color=white:s=160x90,loop=-1:size=2" \
> -vf "drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:text=aaa'"
[me@host: ~]$
[me@host: ~]$ # specify fontsize
[me@host: ~]$ ffplay -f lavfi "color=white:s=160x90,loop=-1:size=2" \
> -vf "drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:fontsize=60:text=aaa'"

The fontfile parameter needs to be passed the full path to the font file, and if your environment is Windows it contains a colon, so the colon needs to be escaped.

see also

ImageFont Module (Python Pillow examples)

Colons that affect the parsing of the entire filter must be escaped anyway:

[me@host: ~]$ ffplay -f lavfi "color=white:s=800x450,loop=-1:size=2" -vf "drawtext=\
> 'fontfile=c\:/Windows/Fonts/courbd.ttf:\
> text=the special character \: is required to be escaped'"

Besides, because of the text expansion specification, the special character “%” associated with it is also required to be escaped (unless the parameter expansion is none):

[me@host: ~]$ ffplay -f lavfi "color=white:s=800x450,loop=-1:size=2" \
> -vf "drawtext=\
> 'fontfile=c\:/Windows/Fonts/courbd.ttf:text=the special characters \\\% \
> is also required to be escaped'"
[me@host: ~]$ ffplay -f lavfi "color=white:s=800x450,loop=-1:size=2" \
> -vf "drawtext=\
> 'fontfile=c\:/Windows/Fonts/courbd.ttf:text=the special characters % \
> is also required to be escaped\
> :expansion=none'"

Note

Note that in the current case, you need three backslashes, as you also need an escape related to the shell (Bourne-shell). If you have trouble understanding shell expansion rules, consider using set -x for enabling trace (and set +x for disabling trace).

If the escaping text is complex, consider using textfile instead of text parameter:

[me@host: ~]$ cat > txt.txt
the special character :, and \% are required to be escaped
[me@host: ~]$ ffplay -f lavfi "color=white:s=800x450,loop=-1:size=2" -vf "drawtext=\
> 'fontfile=c\:/Windows/Fonts/courbd.ttf:textfile=txt.txt'"
#! /bin/sh
ffplay "Pexels Videos 1457810.mp4" -vf "\
drawtext='
fontfile=c\:/Windows/Fonts/comic.ttf:
fontsize=120:
fontcolor=white:
x=100:y=100:
text=%{localtime \: \%Y / \%m / \%d   \%H \\\\\\: \%M \\\\\\: \%S}
'"
#! /bin/sh
trap 'rm -f dt.txt' 0 1 2 3 15
cat << __END__ > dt.txt
%{localtime : \%Y / \%m / \%d   \%H \: \%M \: \%S}
__END__
#
ffplay "Pexels Videos 1457810.mp4" -vf "\
drawtext='
fontfile=c\:/Windows/Fonts/comic.ttf:
fontsize=120:
fontcolor=white:
x=100:y=100:
textfile=dt.txt
'"

This will reduce the need for escape a bit.

To enable default font fallback and the font option

:

To enable default font fallback and the font option you need to configure FFmpeg with --enable-libfontconfig.

Even if ffmpeg is “Windows builds”, --enable-libfontconfig is true, but its distribution doesn’t contain fonts.conf.

The most minimal fonts.conf looks like this:

fonts.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>

   <dir>./</dir>
   <dir>../fonts</dir>
   <dir>./fonts</dir>
   <dir>~/.fonts</dir>
   <dir>WINDOWSFONTDIR</dir> <!-- for Windows -->

   <cachedir>WINDOWSTEMPDIR_FONTCONFIG_CACHE</cachedir> <!-- for Windows -->
   <cachedir>~/.fontconfig</cachedir>

</fontconfig>

For the official distribution Windows version build, for example if you put ffmpeg on c:/Program Files/ffmpeg-3.3.2-win64-shared, this file seems to work if put in c:/Program Files/ffmpeg-3.3.2-win64-shared/bin/fonts/.

For the Unix family, especially if you intend to rely on a package manager, it will probably install the right one in the right place. Otherwise, you can probably know by reading the fontconfig manual.

If you have set up fonts.conf like this (with –enable-libfontconfig enabled), you can use font parameter like this:

#! /bin/sh
pref="`basename $0 .sh`"

ffmpeg -y -i "Pexels Videos 1457810.mp4" -filter_complex "
[0:v]
drawtext='font=sans-serif:fontsize=90:x=20:y=20:

text=The quick brown fox jumps over a lazy dog.
Sphinx of black quartz, judge my vow.'
" "${pref}.mp4"

Perhaps only Windows users need to write system-wide fonts.conf themselves. If you really want to write, it is better to copy from official example little by little and add to the above minimal stuff:

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<!-- /etc/fonts/fonts.conf file to configure system font access -->
<fontconfig>

<!-- Font directory list -->
   <dir>./</dir>
   <dir>../fonts</dir>
   <dir>./fonts</dir>
   <dir>~/.fonts</dir>
   <dir>WINDOWSFONTDIR</dir>

<!-- Font cache directory list -->
   <cachedir>WINDOWSTEMPDIR_FONTCONFIG_CACHE</cachedir>
   <cachedir>~/.fontconfig</cachedir>

 <!--
     Alias well known font names to available TrueType fonts.
     These substitute TrueType faces for similar Type1
     faces to improve screen appearance.
 -->
   <alias>
       <family>Times</family>
       <prefer><family>Times New Roman</family></prefer>
       <default><family>serif</family></default>
   </alias>
   <alias>
       <family>Helvetica</family>
       <prefer><family>Arial</family></prefer>
       <default><family>sans</family></default>
   </alias>
   <alias>
       <family>Courier</family>
       <prefer><family>Courier New</family></prefer>
       <default><family>monospace</family></default>
   </alias>

</fontconfig>

This will allow you to write for example:

#! /bin/sh
pref="`basename $0 .sh`"

ffmpeg -y -i "Pexels Videos 1457810.mp4" -filter_complex "
[0:v]
drawtext='
font=Times:
fontsize=90:x=20:y=20:

text=The quick brown fox jumps over a lazy dog.
Sphinx of black quartz, judge my vow.'
" "${pref}.mp4"

Or you can use the family name of font (neither an alias nor a filename):

#! /bin/sh
pref="`basename $0 .sh`"

ffmpeg -y -i "Pexels Videos 1457810.mp4" -filter_complex "
[0:v]
drawtext='
font=MS Gothic:
fontsize=90:x=20:y=20:

text=The quick brown fox jumps over a lazy dog.
Sphinx of black quartz, judge my vow.'
" "${pref}.mp4"

For the relationship between font family names and file names, for example see ImageFont Module (Python Pillow examples). (In Windows, you may be able to see it by opening “Properties” in Explorer.)

Note

Note that giving a base name as shown below will not be what you expect:

#! /bin/sh
pref="`basename $0 .sh`"

ffmpeg -y -i "Pexels Videos 1457810.mp4" -filter_complex "
[0:v]
drawtext='
fontfile=cour.ttf:
fontsize=90:x=20:y=20:

text=The quick brown fox jumps over a lazy dog.
Sphinx of black quartz, judge my vow.'
" "${pref}.mp4"

Using third party fonts

You can use any font as long as it is a true type font. Consider using the one included with your TexLive installation. Not limited to this, non-Windows fonts are often stored in structured directories:

[me@host: ~]$ find /c/texlive/2015/texmf-dist/fonts/truetype -type f
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-Black.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-BlackItalic.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-Bold.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-BoldItalic.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-Light.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-LightItalic.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-Medium.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-MediumItalic.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-Regular.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-RegularItalic.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-Thin.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/Roboto-ThinItalic.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/RobotoCondensed-Bold.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/RobotoCondensed-BoldItalic.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/RobotoCondensed-Light.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/RobotoCondensed-LightItalic.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/RobotoCondensed-Regular.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/RobotoCondensed-RegularItalic.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/RobotoSlab-Bold.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/RobotoSlab-Light.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/RobotoSlab-Regular.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/google/roboto/RobotoSlab-Thin.ttf
... (snip) ...
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeMono.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeMonoBold.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeMonoBoldOblique.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeMonoOblique.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeSans.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeSansBold.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeSansBoldOblique.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeSansOblique.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeSerif.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeSerifBold.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeSerifBoldItalic.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/gnu-freefont/FreeSerifItalic.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/ipaex/ipaexg.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/ipaex/ipaexm.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/ipaex/ipag.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/ipaex/ipagp.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/ipaex/ipam.ttf
/c/texlive/2015/texmf-dist/fonts/truetype/public/ipaex/ipamp.ttf
... (snip) ...

This is very painful if you have to write their full path each time you use them, so you should use fontconfig. You can write it like this:

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<!-- /etc/fonts/fonts.conf file to configure system font access -->
<fontconfig>

<!-- Font directory list -->
   <dir>./</dir>
   <dir>../fonts</dir>
   <dir>./fonts</dir>
   <dir>~/.fonts</dir>
   <dir>WINDOWSFONTDIR</dir>

   <dir>c:/texlive/2015/texmf-dist/fonts/truetype</dir>

<!-- Font cache directory list -->
   <cachedir>WINDOWSTEMPDIR_FONTCONFIG_CACHE</cachedir>
   <cachedir>~/.fontconfig</cachedir>

 <!--
     Alias well known font names to available TrueType fonts.
     These substitute TrueType faces for similar Type1
     faces to improve screen appearance.
 -->
   <alias>
       <family>Times</family>
       <prefer><family>Times New Roman</family></prefer>
       <default><family>serif</family></default>
   </alias>
   <alias>
       <family>Helvetica</family>
       <prefer><family>Arial</family></prefer>
       <default><family>sans</family></default>
   </alias>
   <alias>
       <family>Courier</family>
       <prefer><family>Courier New</family></prefer>
       <default><family>monospace</family></default>
   </alias>

</fontconfig>

Then, if you want to use ipaexg.ttf, you can:

#! /bin/sh
pref="`basename $0 .sh`"

ffmpeg -y -i "Pexels Videos 1457810.mp4" -filter_complex "
[0:v]
drawtext='
font=IPAexGothic:
fontsize=90:x=20:y=20:

text=The quick brown fox jumps over a lazy dog.
Sphinx of black quartz, judge my vow.'
" "${pref}.mp4"

BTW, on Windows, it is better to know that you can select truetype fonts in Explorer, right-click, and `Install’ to deploy to c:/Windows/Fonts (in terms of fontconfig, WINDOWSFONTDIR), and then you can use them just like any other system font. (However, there seems to be no ways of batch operation, so it would be troublesome if the number of font files is large.)

Newline, and tab

A line break as a value for the “text” parameter or as a character in a file specified in “textfile” behaves as you expect:

[me@host: ~]$ ffmpeg -y -filter_complex \
> "color=white:s=160x90,loop=-1:size=2,drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:fontsize=60:text=aaa
> bbb'" -r 1/1 -t 1 newline_in_drawtext.png
../_images/newline_in_drawtext.png
[me@host: ~]$ cat > txt.txt
A line break as a value for the "text" parameter or
as a character in a file specified in "textfile"
behaves as you expect:
[me@host: ~]$ ffmpeg -y -filter_complex \
> "color=white:s=960x135,loop=-1:size=2,drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:fontsize=30:\
> textfile=txt.txt'" -r 1/1 -t 1 newline_in_drawtext2.png
../_images/newline_in_drawtext2.png

The same is true for tab codes, but in some cases you may need to control how many spaces to replace with the “tabsize” parameter:

[me@host: ~]$ cat > html_sample.txt
<html>
<body>
<div>
    Indented text by tab.
</div>
</body>
</html>
[me@host: ~]$ ffmpeg -filter_complex \
> "color=white:s=480x270,loop=-1:size=2,\
> drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:fontsize=30:\
> textfile=html_sample.txt'" -r 1/1 -t 1 newline_in_drawtext3.png
[me@host: ~]$ ffmpeg -filter_complex \
> "color=white:s=480x270,loop=-1:size=2,\
> drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:fontsize=30:\
> textfile=html_sample.txt:tabsize=2'" -r 1/1 -t 1 newline_in_drawtext4.png
../_images/newline_in_drawtext3.png ../_images/newline_in_drawtext4.png

Text position

see also

the example of overlay

The position of the text can be specified by “x” and “y”:

#! /bin/sh
ffmpeg -y -filter_complex "
color=white:s=1280x720,loop=-1:size=2,
drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:fontsize=50:text=hello:
x=100:y=100'
" -t 30 drawtext_xy.mp4

As with “overlay”, you can use expressions including timestamps for x and y of drawtext:

#! /bin/sh
ffmpeg -y -filter_complex "
color=white:s=1280x720,loop=-1:size=2,
drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:fontsize=50:text=hello:
x=(sin(t)+1)/2*(W-tw):y=(cos(t/0.625)+1)/2*(H-th)'
" -t 30 drawtext_use_expr_in_xy.mp4
Watch on youtube.com

color, box, border, and shadow

fontcolor, fontcolor_expr

fontcolor:

The color to be used for drawing fonts. For the syntax of this option, check the (ffmpeg-utils)”Color” section in the ffmpeg-utils manual.

The default value of fontcolor is “black”.

#! /bin/sh
pref="`basename $0 .sh`"

#
ffmpeg -y -i "Pexels Videos 1457810.mp4" -filter_complex "
[0:v]
drawtext='fontfile=c\:/Windows/Fonts/comic.ttf:fontsize=90:x=20:y=20:

fontcolor=white@0.2:

text=The quick brown fox jumps over a lazy dog.
Sphinx of black quartz, judge my vow.'
" "${pref}.mp4"
Watch on youtube.com

fontcolor_expr:

String which is expanded the same way as text to obtain dynamic fontcolor value. By default this option has empty value and is not processed. When this option is set, it overrides fontcolor option.

IMO, the design of fontcolor_expr is a bit ridiculous. You might think that what should we do is color calculations as 24-bit or 32-bit values (I thought so). However, for fontcolor_expr, you must pass “a legal string as the fontcolor argument string”. In other words, “we must calculate a hexadecimal string by calculation”. Therefore, not only calculations but also “hexadecimal digitization of numerical values” is almost indispensable for this purpose:

changing the alpha value by time
#! /bin/sh
pref="`basename $0 .sh`"

#
ffmpeg -y -i "Pexels Videos 1457810.mp4" -filter_complex "
[0:v]
drawtext='fontfile=c\:/Windows/Fonts/comic.ttf:fontsize=90:x=20:y=20:

fontcolor_expr=
0x5090FF%{eif\: mod(t * 100, 255) \: x \: 2}:

text=The quick brown fox jumps over a lazy dog.
Sphinx of black quartz, judge my vow.'
" "${pref}.mp4"

Although the knowledge of “we can do it in this way” is not worthless, but it is not easy to use. If you know the other ways of doing the same thing and you adopt it, it would become “easy to maintain” code, I think.

box, boxborderw, boxcolor, line_spacing

box:

Used to draw a box around text using the background color. The value must be either 1 (enable) or 0 (disable). The default value of box is 0.

boxborderw:

Set the width of the border to be drawn around the box using boxcolor. The default value of boxborderw is 0.

boxcolor:

The color to be used for drawing box around text. For the syntax of this option, check the (ffmpeg-utils)”Color” section in the ffmpeg-utils manual.

The default value of boxcolor is “white”.

line_spacing:

Set the line spacing in pixels of the border to be drawn around the box using box. The default value of line_spacing is 0.

#! /bin/sh
pref="`basename $0 .sh`"
#
ffmpeg -y -filter_complex "
color=0xAFAFAF:s=1280x720,loop=-1:size=2,
trim=0:6,setpts=PTS-STARTPTS,

drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:
fontcolor=yellow:
fontsize=45:
x=50:y=620:

box=1:
boxcolor=blue:
boxborderw=5:
line_spacing=32:

text=The quick brown fox jumps over a lazy dog.
Sphinx of black quartz, judge my vow.'
" "${pref}.mp4"
Watch on youtube.com

borderw, bordercolor

bordercolor:

Set the color to be used for drawing border around text. For the syntax of this option, check the (ffmpeg-utils)”Color” section in the ffmpeg-utils manual.

The default value of bordercolor is “black”.

borderw:

Set the width of the border to be drawn around the text using bordercolor. The default value of borderw is 0.

#! /bin/sh
pref="`basename $0 .sh`"
#
ffmpeg -y -filter_complex "
color=0xAFAFAF:s=1280x720,loop=-1:size=2,
trim=0:6,setpts=PTS-STARTPTS,

drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:
fontcolor=yellow:
fontsize=45:
x=50:y=620:

bordercolor=blue:
borderw=5:

text=The quick brown fox jumps over a lazy dog.'
" "${pref}.mp4"
Watch on youtube.com

shadowcolor, shadowx, shadowy

shadowcolor:

The color to be used for drawing a shadow behind the drawn text. For the syntax of this option, check the (ffmpeg-utils)”Color” section in the ffmpeg-utils manual.

The default value of shadowcolor is “black”.

shadowx, shadowy:

The x and y offsets for the text shadow position with respect to the position of the text. They can be either positive or negative values. The default value for both is “0”.

#! /bin/sh
pref="`basename $0 .sh`"
#
ffmpeg -y -filter_complex "
color=0xAFAFAF:s=1280x720,loop=-1:size=2,
trim=0:6,setpts=PTS-STARTPTS,

drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:
fontcolor=yellow:
fontsize=45:
x=50:y=620:

shadowcolor=blue:
shadowx=5:shadowy=-5:

text=The quick brown fox jumps over a lazy dog.'
" "${pref}.mp4"
Watch on youtube.com

alpha

alpha:

Draw the text applying alpha blending. The value can be a number between 0.0 and 1.0. The expression accepts the same variables x, y as well. The default value is 1. Please see fontcolor_expr.

#! /bin/sh
pref="`basename $0 .sh`"
#
ffmpeg -y -i "Pexels Videos 1457810.mp4" -filter_complex "
[0:v]
drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:
fontsize=70:x=50:y=50:

fontcolor=white:
shadowcolor=blue:shadowx=20:shadowy=20:
box=1:boxcolor=0xAAAAAA:boxborderw=32:
bordercolor=black:borderw=5:

alpha=mod(t / 15, 1):

text=The quick brown fox jumps over a lazy dog.
Sphinx of black quartz, judge my vow.'
" "${pref}.mp4"
Watch on youtube.com

Note

Specifying alpha affects the whole text drawing (eg the color of the border). If this is not what you want, give an alpha for each color:

#! /bin/sh
pref="`basename $0 .sh`"
#
ffmpeg -y -i "Pexels Videos 1457810.mp4" -filter_complex "
[0:v]
drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:
fontsize=70:x=50:y=50:

fontcolor=white@0.5:
shadowcolor=blue:shadowx=20:shadowy=20:

text=The quick brown fox jumps over a lazy dog.
Sphinx of black quartz, judge my vow.'
" "${pref}.mp4"

Text expansion

doc

Text expansion

Bad news. The “text expansion” provided by drawtext is extremely limited, and probably can not do most of what you want to do.

The most noticeable limitation is that ‘%{metadata : …}’ only shows frame metadata. So, even if the target video has “title” metadata at the global level, the following will not work:

[me@host: ~]$ ffprobe -hide_banner yourvideo.mp4
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'yourvideo.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    title           : whats going on
    date            : 2011
  ... (snip) ...
[me@host: ~]$ ffplay yourvideo.mp4 -vf "
> drawtext='font=monospace:fontcolor=white:fontsize=60:text=%{metadata\: title}'"
[me@host: ~]$ ffplay yourvideo.mp4 -vf "
> metadata=mode=print,drawtext='font=monospace:fontcolor=white:fontsize=60:text=%{metadata\: title}'"

For a few examples where “metadata” can be used, see drawtext with signalstats, drawtext, drawgraph, blackframe, freezedetect.

So, “metadata” is maybe disappointing for you, but instead, only the display method of time code related (which is definitely the biggest demand) is quite satisfactory:

Draw the timestamp of the current frame with the format “hms”.
[me@host: ~]$ ffplay yourvideo.mp4 -vf "
> drawtext='font=monospace:fontcolor=white:fontsize=60:text=%{pts \: hms}'"
Draw the timestamp of the current frame with the format “gmtime”.
[me@host: ~]$ ffplay yourvideo.mp4 -vf "
> drawtext='font=monospace:fontcolor=white:fontsize=40:
> text=%{pts \: gmtime}'"
[me@host: ~]$ ffplay yourvideo.mp4 -vf "
> drawtext='font=monospace:fontcolor=white:fontsize=40:
> text=%{pts \: gmtime \: 0}'"
[me@host: ~]$ ffplay yourvideo.mp4 -vf "
> drawtext='font=monospace:fontcolor=white:fontsize=40:
> text=%{pts \: gmtime \: 0 \: \%Y-\%m-\%d \%H\\\\\\:\%M\\\\\\:\%S}'"
Draw the time at which the filter is running, expressed in the local time zone.
[me@host: ~]$ ffplay -f lavfi "color=s=1920x1080,loop=-1:size=2" -vf "
> drawtext='font=monospace:fontcolor=white:fontsize=200:text=%{localtime}'"
[me@host: ~]$ ffplay -f lavfi "color=s=1920x1080,loop=-1:size=2" -vf "
> drawtext='font=monospace:fontcolor=white:fontsize=200:
> text=%{localtime \: \%Y-\%m-\%d \%H\\\\\\:\%M\\\\\\:\%S}'"

The function “expr” may be used for debugging purposes:

[me@host: ~]$ ffplay -f lavfi "
> color=white:s=ntsc:d=10,
> drawtext='
>     fontsize=40:
>     text=%{expr\:(st(0, 4 - mod(t * PI, 8));(sin(ld(0)) / ld(0)) * sin(220 * 2 * PI * t))}'"

Note that constants what you can use depends on when the evaluation of the expression is performed as the official documentation mentions, furthermore, the constants that can be used here are only those that can be used with the “x” and “y” parameters of “drawtext”:

[me@host: ~]$ # If you want to display the evaluated value of the following:
[me@host: ~]$ ffplay video.mkv -vf "geq='lum=min(lum(X,Y) + T*7, 255):cr=cr(X,Y):cb=cb(X,Y)'"
[me@host: ~]$ # But you can't do:
[me@host: ~]$ ffplay -f lavfi "
> color=white:s=ntsc:d=10,drawtext='fontsize=40:text=%{expr\:(min(lum(X,Y) + T*7, 255))}'"

It’s not surprising that you can’t use “lum”, but in this example, “T” would disappoint you. If your only goal is to display the evaluation results of an expression, consider using print:

[me@host: ~]$ ffplay video.mkv -vf "geq='lum=print(min(lum(X,Y) + T*7, 255)):cr=cr(X,Y):cb=cb(X,Y)'"
see also

global metadata, etc.

`textfile’ and `reload=1’

Only if you use textfile, you can use the parameter reload:

If set to 1, the textfile will be reloaded before each frame. Be sure to update it atomically, or it may be read partially, or even fail.

Warning

For ffmpeg which is not the latest, reload=1 seems to hang up easily. At least version 3.3.2 of Windows hangs up frequently. Use the latest (4.1 at the moment).

You can use this to create something like a video that follows an ever-changing text file, such as a log of something:

Python script example to create a text file that changes every 5 seconds
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import io
import time
import random

with io.open('words', encoding='latin-1') as fi:
    words = fi.read().split('\n')

    while True:
        r1 = random.randint(0, len(words) - 1)
        r2 = random.randint(0, len(words) - 1)
        txt = words[r1] + '\n x\n' + words[r2] + '\n'
        with io.open('choice.txt', 'w') as fo:
            fo.write(txt)
        time.sleep(5)
Ffplay follows text files that the above python script keeps updating
#! /bin/sh
exec 2> /dev/null
ffplay -f lavfi "color=white:s=960x540,loop=-1:size=2" \
    -vf "drawtext='fontfile=c\:/Windows/Fonts/courbd.ttf:
fontsize=60:
x=30:y=30:
textfile=choice.txt:
reload=1"
Watch on youtube.com

Let me show you a slightly more complicated example.

The following Python script periodically updates random words into the file `excursion.txt’ based on Google search. (However, it is for Japanese people.):

excursion.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
try:
    # python 3
    from urllib.request import build_opener
    from http.client import ResponseNotReady
    from html.parser import HTMLParser
    from urllib.parse import urlencode
except ImportError:
    # python 2
    from urllib2 import build_opener
    from httplib import ResponseNotReady
    from HTMLParser import HTMLParser
    from urllib import urlencode
import sys
import io
from io import StringIO
import ssl
import time
import re
import random
from subprocess import Popen, PIPE
import unicodedata

import httplib2  # https://pypi.python.org/pypi/httplib2
from bs4 import BeautifulSoup  # https://pypi.python.org/bs4/


#
class _GoogleHtmlResExtractor(HTMLParser):

    def __init__(self):
        HTMLParser.__init__(self)
        self._states = 0
        self.data = []

    def handle_starttag(self, tag, attrs):
        d = dict(attrs)
        if tag == 'p':
            self._states = 1
        elif tag == 'a' and self._states == 1:
            if d['href'] != '/':
                self.data.append({
                        "href": "https://www.google.com" + d['href']
                        })
                self._states = 2

    def handle_endtag(self, tag):
        if tag == 'a':
            self._states = 0

    def handle_data(self, data):
        if self._states == 2:
            self.data[-1]["data"] = data.strip()


#
def query(*query_keywords):
    user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
    url = "https://www.google.com/search"
    data = urlencode({
            "q": " ".join(query_keywords).encode("utf-8"),
            })
    opener = build_opener()
    opener.addheaders = [('User-Agent', user_agent)]
    res = opener.open(url + "?" + data)
    html = res.read().decode("utf-8")

    parser = _GoogleHtmlResExtractor()
    parser.feed(html)

    return parser.data


# httplib2 wrapper
class httplib2inst(object):
    def __init__(self, cache_dir=".cache"):
        self._inst = httplib2.Http(cache_dir)

    def get(self, url):
        retry = 20
        while retry >= 0:
            try:
                return self._inst.request(url, "GET")
            except (ResponseNotReady, ssl.SSLEOFError):
                retry -= 1
                time.sleep(8)
            except (httplib2.ServerNotFoundError, ssl.CertificateError):
                break
        return None, None


# --- main ---
if __name__ == '__main__':
    opnr = httplib2inst()
    qs = sys.stdin.readline().strip()
    if hasattr(qs, "decode"):
        qs = qs.decode("mbcs")
    srcs = set()
    while True:
        for lst in query(qs):
            headers, contents = opnr.get(lst["href"])
            if headers:
                try:
                    soup = BeautifulSoup(StringIO(contents.decode("utf-8", "replace")), "lxml")
                except:
                    continue
                [x.extract() for x in soup.findAll('script')]
                [x.extract() for x in soup.findAll('iframe')]
                body = soup.find("body")
                if not body:
                    continue
                p = Popen(
                    ["c:/Program Files (x86)/MeCab/bin/mecab", "-b", "%d" % (2**24)],
                    shell=False, stdin=PIPE, stdout=PIPE)
                stdoutdata, stderrdata = p.communicate(
                    input=body.text.encode("utf-8"))
                for line in re.split(r"\r?\n", stdoutdata.decode("utf-8", "replace")):
                    if "\t" not in line:
                        continue
                    word, _, inf = line.partition("\t")
                    if re.match(r"[ -/:-@[-`{-~]+", word):
                        continue
                    if re.match(r"[a-zA-Z]+", word):
                        continue
                    word = unicodedata.normalize('NFKC', word)
                    if re.match(r"[a-zA-Z]+", word):
                        continue
                    typ = inf.split(",")[:2]
                    if typ[0] in ("記号", "接続詞", "連体詞") or \
                            "助" in typ[0] or "動" in typ[0] or \
                            typ[1] in ("非自立", "代名詞", "数"):
                        continue
                    srcs.add(word)
        sels = list(srcs)
        random.shuffle(sels)
        for i in range(0, len(sels) // 20 * 20, 20):
            s = "\n".join(["{}{}{}{}"] * 5).format(
                *sels[i:i+20+1]).encode("utf-8")
            with io.open("excursion.txt", "wb") as fo:
                fo.write(s)
            time.sleep(10)
        time.sleep(1)
        qs = sels[-1]

This time, let’s draw it by changing the color little by little at random positions:

play_chosen.sh
#! /bin/sh
exec 2> /dev/null
"c:/Program Files/ffmpeg-4.1.1-win64-shared/bin/ffplay" \
    -f lavfi "color=white:s=1120x630,loop=-1:size=2" \
    -vf "drawtext='fontfile=c\:/Windows/Fonts/HGRSMP.TTF:
fontsize=40:
fontcolor_expr=0x\
%{eif \: mod(t*10, 128) \: x \: 2}\
%{eif \: mod(t*30, 255) \: x \: 2}\
%{eif \: mod(t*50, 255) \: x \: 2}:

x=if(eq(mod(t\,5)\,0)\,rand(50\,(w-text_w-150))\,x):
y=if(eq(mod(t\,5)\,0)\,rand(50\,(h-text_h-150))\,y):

textfile=excursion.txt:reload=1"

Here’s a video showing how this works:

Watch on youtube.com

with sendcmd

For example, you may want to change the font over time. In such cases, you can use sendcmd (rather than drawtext itself).

#! /bin/bash
#
# This example is for Windows.
#
(
    exec > dtcmd.cmd
    t=0
    for fon in `(cd /c/Windows/Fonts/ ; ls *.tt[fc])` ; do
        echo "\
${t}-$((${t} + 5)) [enter] drawtext reinit 'fontfile=c\\:/Windows/Fonts/${fon}';"
        t=$((${t} + 5))
    done
)
#
"ffplay" \
    -f lavfi "color=white:s=960x540,loop=-1:size=2" \
    -vf "sendcmd=f=dtcmd.cmd,drawtext='fontsize=40:x=50:y=50:fontcolor=blue:\
text=The quick brown fox jumps over a lazy dog.'"
Watch on youtube.com