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:fontcolor=white@0.3:fontsize=250:x=20:y=20,
drawtext=fontfile=/Library/Fonts/Arial.ttf:text=BORDER_ON:fontcolor=white@0.3:fontsize=250:x=20:y=20+(text_h+10):borderw=3:bordercolor=black@0.3" \
-frames:v 1 \
output.png

Full output:

ffmpeg -f lavfi -i "color=red:size=1920x1080" -vf "drawtext=fontfile=/Library/Fonts/Arial.ttf:fontcolor=white@0.3:fontsize=250:x=20:y=20:text=BORDER_OFF,drawtext=fontfile=/Library/Fonts/Arial.ttf:fontcolor=white@0.3:borderw=3:bordercolor=black@0.3: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':fontcolor=white@0.1: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
4

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':fontcolor=white@0.1: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':fontcolor=white@0.1: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
| improve this answer | |
  • 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 Aug 28 '19 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 Aug 28 '19 at 18:14
  • 1
    There may be a workaround. I'll get time tomorrow. – Gyan Aug 28 '19 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 Aug 28 '19 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 Aug 28 '19 at 21:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.