4

I would like to use the drawtext filter to render a string with semi transparent white text and a black border.

However there seems to be a known limitation in the way the filter draws characters that results in the fontcolor alpha not being adhered to when a border is also enabled. This is logged here https://trac.ffmpeg.org/ticket/3571

My question is whether there is a way to work around this somehow, perhaps using filter_complex to draw the text without a border and then use the overlay filter to somehow draw the text again with a border and comp it over the initial text to just take the border, thereby achieving the same opaque white text with a black border.

Current Command:

ffmpeg \
-f lavfi \
-i "color=red:size=1920x1080" \
-vf "
drawtext=fontfile=/Library/Fonts/Arial.ttf:text=BORDER_OFF:[email protected]:fontsize=250:x=20:y=20,
drawtext=fontfile=/Library/Fonts/Arial.ttf:text=BORDER_ON:[email protected]:fontsize=250:x=20:y=20+(text_h+10):borderw=3:[email protected]" \
-frames:v 1 \
output.png

Full output:

ffmpeg -f lavfi -i "color=red:size=1920x1080" -vf "drawtext=fontfile=/Library/Fonts/Arial.ttf:[email protected]:fontsize=250:x=20:y=20:text=BORDER_OFF,drawtext=fontfile=/Library/Fonts/Arial.ttf:[email protected]:borderw=3:[email protected]:fontsize=250:x=20:y=20+(text_h+10):text=BORDER_ON" -frames:v 1 output.png
ffmpeg version N-94664-g0821bc4eee-tessus  https://evermeet.cx/ffmpeg/  Copyright (c) 2000-2019 the FFmpeg developers
  built with Apple LLVM version 10.0.1 (clang-1001.0.46.4)
  configuration: --cc=/usr/bin/clang --prefix=/opt/ffmpeg --extra-version=tessus --enable-avisynth --enable-fontconfig --enable-gpl --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libfreetype --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libmysofa --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264 --enable-libopenjpeg --enable-libopus --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvmaf --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-version3 --pkg-config-flags=--static --disable-ffplay
  libavutil      56. 33.100 / 56. 33.100
  libavcodec     58. 55.101 / 58. 55.101
  libavformat    58. 31.104 / 58. 31.104
  libavdevice    58.  9.100 / 58.  9.100
  libavfilter     7. 58.101 /  7. 58.101
  libswscale      5.  6.100 /  5.  6.100
  libswresample   3.  6.100 /  3.  6.100
  libpostproc    55.  6.100 / 55.  6.100
Input #0, lavfi, from 'color=red:size=1920x1080':
  Duration: N/A, start: 0.000000, bitrate: N/A
    Stream #0:0: Video: rawvideo (I420 / 0x30323449), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 25 tbr, 25 tbn, 25 tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> png (native))
Press [q] to stop, [?] for help
Output #0, image2, to 'output.png':
  Metadata:
    encoder         : Lavf58.31.104
    Stream #0:0: Video: png, rgb24, 1920x1080 [SAR 1:1 DAR 16:9], q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc
    Metadata:
      encoder         : Lavc58.55.101 png
frame=    1 fps=0.0 q=-0.0 Lsize=N/A time=00:00:00.04 bitrate=N/A speed=0.283x    
video:85kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown

The output shows that the colour of the text is being altered by the border even though the fontcolor is the same in both instances of the drawtext filter.

drawtext solution (this has been superseded by a third @llogan command in the answer below)

Based on @llogan second drawtext solution, this version adds the ability to control the fill opacity using standard alpha on fontcolor. Using @llogan solution to control the opacity of the border via varying gray values and fixes the jagged rendering on the text:

ffmpeg \
-y \
-f lavfi \
-i color=s=1920x1080:c=white \
-f lavfi \
-i color=s=1920x1080:c=black \
-f lavfi \
-i smptebars=s=1920x1080 \
-filter_complex "\
[0]drawtext=fontfile=/Library/Fonts/Arial.ttf:text='BORDER':fontcolor=white:fontsize=200:x=(w-text_w)/2:y=(h-text_h)/2:borderw=3:bordercolor=#bfbfbf[ahpla];
[1]drawtext=fontfile=/Library/Fonts/Arial.ttf:text='BORDER':fontcolor=black:fontsize=200:x=(w-text_w)/2:y=(h-text_h)/2[txt];
[2]drawtext=fontfile=/Library/Fonts/Arial.ttf:text='BORDER':[email protected]:fontsize=200:x=(w-text_w)/2:y=(h-text_h)/2[bg];
[ahpla]negate[alpha];
[txt][alpha]alphamerge[fg];
[bg][fg]overlay" \
-frames:v 1 \
output.png

1 Answer 1

5

drawtext filter

enter image description here

A workaround can include the alphamerge and overlay filters:

ffmpeg -y \
-f lavfi -i color=s=1920x1080 \
-f lavfi -i smptebars=s=1920x1080 \
-filter_complex \
  "[0]drawtext=fontfile=/Library/Fonts/Arial.ttf:text='BORDER':fontcolor=black:fontsize=200:x=(w-text_w)/2:y=(h-text_h)/2:borderw=3:bordercolor=#404040[border];
   [0][border]alphamerge[alpha];
   [1][alpha]overlay=format=rgb,drawtext=fontfile=/Library/Fonts/Arial.ttf:text='BORDER':[email protected]:fontsize=200:x=(w-text_w)/2:y=(h-text_h)/2" \
-frames:v 1 \
output.png

Change bordercolor to control the border opacity. My examples used arbitrary values, so you'll need to adjust to suit your needs. Use gray colors only. A darker shade will make a less transparent result. If you don't like hex values see the list of valid color names.

The scale2ref filter (with split) can be used if you don't want to manually match the color source filter s to the main input size.

ffmpeg -y \
-f lavfi -i color \
-f lavfi -i smptebars=s=1920x1080 \
-filter_complex \
  "[0][1]scale2ref[color][mainbg];
   [color]split[colorbg0][colorbg1];
   [colorbg0]drawtext=fontfile=/Library/Fonts/Arial.ttf:text='BORDER':fontcolor=black:fontsize=200:x=(w-text_w)/2:y=(h-text_h)/2:borderw=3:bordercolor=#404040[border];
   [colorbg1][border]alphamerge[alpha];
   [mainbg][alpha]overlay=format=rgb,drawtext=fontfile=/Library/Fonts/Arial.ttf:text='BORDER':[email protected]:fontsize=200:x=(w-text_w)/2:y=(h-text_h)/2" \
-frames:v 1 \
output.png

subtitles filter

Another workaround is to use the subtitles filter with Advanced SubStation Alpha (ASS) subtitles if you want hardsubs:

ffmpeg -f lavfi -i smptebars=s=320x180,format=rgb24 -vf subtitles=subs.ass -frames:v 1 hardsubs.png

Or mux the ASS file if you want softsubs:

ffmpeg -i input -i subs.ass -map 0 -map 1 -c copy output.mkv

Example ASS file:

[Script Info]
; Script generated by Aegisub 3.2.2
; http://www.aegisub.org/
Title: Default Aegisub file
ScriptType: v4.00+
WrapStyle: 0
ScaledBorderAndShadow: yes
YCbCr Matrix: None

[Aegisub Project Garbage]
Last Style Storage: Default

[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: Default,Bitstream Vera Sans,92,&HB4FFFFFF,&H000000FF,&H4B000000,&H00000000,0,0,0,0,100,100,0,0,1,4,0,5,10,10,10,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,BORDER ON
9
  • Thanks for this. It is indeed a workaround. However I should have added more info as this would not be useful for me. I create many drawtext's per file programatically. Creating an external ass file for each input file would create very serious complexity I think. Is there a way to do this with filter_complex, perhaps using the alphamerge or overlay filters?
    – mwjb
    Commented Aug 28, 2019 at 18:06
  • I meant to say, is there not a way to do this with drawtext within filter_complex, perhaps using alphamerge and/or overlay filters?
    – mwjb
    Commented Aug 28, 2019 at 18:14
  • 1
    There may be a workaround. I'll get time tomorrow.
    – Gyan
    Commented Aug 28, 2019 at 18:31
  • Hi llogan, thanks for the new workaround. This is looking very promising. Couple of issues at the moment. How would I control the fill and border opacity - for border this command currently has a value of 1.0, but I would need something closer to 0.3. Changing it in bordercolor causes it to disappear. Although the fill is somewhat opaque, I'd need more control over this ideally. Sorry, asking I lot I know. Secondly (imagine this is something you already planned to address when you had more time) the edges are somewhat jagged, perhaps due to font hinting. Any ideas to clean this up?
    – mwjb
    Commented Aug 28, 2019 at 20:02
  • Thanks llogan! Please can you take a look at my amended question. I had taken your first drawtext solution and tweaked it slightly to allow the fill opacity to be easily controlled which also seemed to fix the jagged text rendering. However, I did not manage to address the border opacity as you have done. Would there be a way to combine the two techniques so there is control over fill and border opacity along with the improved text rendering? Essentially, I rendered the text into the background separately at the opacity I wanted and then only used the negate, alphamerge to deal with a border.
    – mwjb
    Commented Aug 28, 2019 at 21:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .