130

How can I add text overlay on my video in ffmpeg?

i.e. given a video "video1.flv", how can I add "StackOverflow" text during the whole video, positioned in the middle of the screen, with white text and a border?

1
  • The "ffmpeg" tag is only allowed for programmatic use of the ffmpeg API - Questions about the command-line use should be asked on Super-user or Video-production.
    – Top-Master
    Commented Apr 22, 2023 at 7:25

4 Answers 4

281

Use the drawtext filter for simple text on video. If you need more complex timing, formatting, or dynamic text see the subtitles filter. This answer focuses on the drawtext filter.

Example

Text centered on video

Print Stack Overflow in white text onto center of video, with black background box of 50% opacity:

ffmpeg -i input.mp4 -vf "drawtext=fontfile=/path/to/font.ttf:text='Stack Overflow':fontcolor=white:fontsize=24:box=1:[email protected]:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2" -codec:a copy output.mp4
  • The audio is stream copied in this example (like a copy and paste).
  • @0.5 controls background box opacity. 0.5 is 50%. Remove @0.5 if you do not want any transparency.
  • See the drawtext filter documentation for a complete list and explanations of options.

Preview

You can use ffplay to preview your text without having to wait for a file to encode:

ffplay -vf "drawtext=fontfile=/path/to/font.ttf:text='Stack Overflow':fontcolor=white:fontsize=24:box=1:[email protected]:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2" input.mp4

Alternatively you can use mpv but the syntax is slightly different:

mpv --vf="lavfi=[drawtext=fontfile=/path/to/font.ttf:text='Stack Overflow':fontcolor=white:fontsize=24:box=1:[email protected]:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2]" input.mp4

Multiple texts

You can chain multiple drawtext filters:

ffmpeg -i input.mp4 -vf "drawtext=fontfile=/path/to/font.ttf:text='Stack Overflow':fontcolor=white:fontsize=24:box=1:[email protected]:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2,drawtext=fontfile=/path/to/font.ttf:text='Bottom right text':fontcolor=black:fontsize=14:x=w-tw-10:y=h-th-10" -codec:a copy output.mp4

Position

x and y determine text position:

Position x:y With 10 px padding
Top left x=0:y=0 x=10:y=10
Top center x=(w-text_w)/2:y=0 x=(w-text_w)/2:y=10
Top right x=w-tw:y=0 x=w-tw-10:y=10
Centered x=(w-text_w)/2:y=(h-text_h)/2
Bottom left x=0:y=h-th x=10:y=h-th-10
Bottom center x=(w-text_w)/2:y=h-th x=(w-text_w)/2:y=h-th-10
Bottom right x=w-tw:y=h-th x=w-tw-10:y=h-th-10
Random See this answer

Repositioning text on demand

You can reposition the text with the sendcmd and zmq filters:

Moving / animated / looping / scrolling text

See:

Timing

Use the enable option to control when the text appears.

Show text between 5-10 seconds:

ffmpeg -i input.mp4 -vf "drawtext=fontfile=/path/to/font.ttf:text='Stack Overflow':fontcolor=white:fontsize=24:box=1:[email protected]:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2:enable='between(t,5,10)'" -codec:a copy output.mp4

Show text after 3 seconds:

ffmpeg -i input.mp4 -vf "drawtext=fontfile=/path/to/font.ttf:text='Stack Overflow':fontcolor=white:fontsize=24:box=1:[email protected]:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2:enable='gte(t,3)'" -codec:a copy output.mp4

Blinking text. For every 10 seconds show text for 5 seconds:

ffmpeg -i input.mp4 -vf "drawtext=fontfile=/path/to/font.ttf:text='Stack Overflow':fontcolor=white:fontsize=24:box=1:[email protected]:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2:enable='lt(mod(t,10),5)'" -codec:a copy output.mp4

Random position every 30 seconds:

See ffmpeg - Dynamic letters and random position watermark to video?

Changing / updating text

Add the textfile and reload options for drawtext:

ffmpeg -i input.mp4 -vf "drawtext=fontfile=/path/to/font.ttf:textfile=text.txt:reload=1:fontcolor=white:fontsize=24:box=1:[email protected]:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2" -codec:a copy output.mp4
  • Update text.txt every time you want the text to change.
  • Important: You must update the text file atomically or it may fail. You can do this with the mv command on Linux or macOS.
  • If you have many text changes, such as making subtitles, it is easier to make a subtitle file (such as an .ass file via Aegisub) and using the subtitles filter.

Font family instead of font file

You can declare the font family, such as Times New Roman, instead of having to point to a font file. See How to include font in FFMPEG command without using the fontfile option?

Requirements

The drawtext filter requires ffmpeg to be compiled with --enable-libfreetype. If you get No such filter: 'drawtext' it is missing --enable-libfreetype. Most of the ffmpeg static builds available support this: see the FFmpeg Download page for links.

15
  • 1
    @NoahTernullo there's also strftime options and "pts" variable you can use. In terms of making it ticker along the bottom, you can probably use some function for x variable value changes based on pts or frame number.
    – rogerdpack
    Commented May 12, 2015 at 19:45
  • 2
    fyi, for those installing with brew, you can pass that flag --with-libefreetype to the install command.
    – chovy
    Commented Aug 1, 2016 at 21:00
  • 7
    @chovy I think the homebrew option you mentioned should be --with-freetype Commented Feb 19, 2017 at 2:25
  • 2
    add :enable='between(t,00:00:15,00:00:20) to specify the text duration, something like ffmpeg -i part02.mp4 -vf drawtext="text='Stack Overflow': fontcolor=white: fontsize=24: box=1: [email protected]: \ boxborderw=5: x=(w-text_w)/2: y=(h-text_h)/2: enable='between(t,00:00:15,00:00:20)" -codec:a copy output.mp4
    – deFreitas
    Commented May 2, 2020 at 16:49
  • 1
    @VivekThummar It needs to be in the form of -vf "scale=1920:1080,drawtext=..." Notice the comma (,) connecting the filters. The comma is missing in your command being executed. This is a problem with your implementation of the ffmpeg command in android.
    – llogan
    Commented Aug 5, 2020 at 17:32
5

Here is the cheat sheet for overlay transition in all 4 direction...

************************* Text **********************

1) left to right given x=41 position 

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -vf "[in]drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf: text='Hi': y=53.48 :x=min(t*250-2*250\,41): fontsize=35: fontcolor=yellow: enable='between(t,2,10)', drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf: text='Ajeet': y=53.48 :x=min(t*250-3*250\,90): fontsize=35: fontcolor=yellow: enable='between(t,3,10)' [out]" -t 11 leftToRight.mp4




2) right to left given x=41 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -vf "[in]drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf: text='Hi': y=53.48 :x=w-min(t*250-2*250\,(w\-41)): fontsize=35: fontcolor=yellow: enable='between(t,2,10)', drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf: text='Ajeet': y=53.48 :x=w-min(t*250-3*250\,(w\-90)): fontsize=35: fontcolor=yellow: enable='between(t,3,10)' [out]" -t 11 rightToLeft.mp4




3) top to bottom given y=58 position 

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -vf "[in]drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf: text='Hi': x=41 :y=min(t*250-2*250\,53.48): fontsize=35: fontcolor=yellow: enable='between(t,2,10)', drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf: text='Ajeet': x=90 :y=min(t*250-3*250\,53.48): fontsize=35: fontcolor=yellow: enable='between(t,3,10)' [out]" -t 11 topToBottom.mp4




4) bottom to up given y=90 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -vf "[in]drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf: text='Hi': x=41 :y=h-min(t*250-2*250\,(h\-53.48)): fontsize=35: fontcolor=yellow: enable='between(t,2,10)', drawtext=fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf: text='Ajeet': x=90 :y=h-min(t*250-3*250\,(h\-53.48)): fontsize=35: fontcolor=yellow: enable='between(t,3,10)' [out]" -t 11 bottomToTop.mp4

************************ Image *********************

1) single left to right given x=41 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -loop 1 -i image.png -filter_complex "[1:v]scale=-2:100[png];[0:v][png]overlay=x=min(t*250-2*250\,41):y=60:shortest=1:enable='between(t,2,10)'" -t 11 imageLeftToRight1.mp4

ffmpeg -y -i 'https://player.vimeo.com/external/181472383.sd.mp4?s=103f42915141758d95a118f070d08190845bdf73&profile_id=164' -loop 1 -i image.png -filter_complex "[1:v]scale=-2:100[png];[0:v][png]overlay=x=min(t*250-2*250\,41):y=60:shortest=1:enable='between(t,2,10)'" -t 11 imageLeftToRight.mp4




2) Already added text then added single image left to right

ffmpeg -y -i leftToRight.mp4 -loop 1 -i image.png -filter_complex "[1:v]scale=-2:100[png];[0:v][png]overlay=x=min(t*250-2*250\,41):y=200:shortest=1:enable='between(t,2,10)'" -t 11 imageTextLeftToRight.mp4




3) 2 images left to right given x=41, x=100 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -loop 1 -i image.png -loop 1 -i image2.png -filter_complex "[1]scale=-2:100[img1];[2]scale=-2:100[img2];[0][img1]overlay=x=min(t*250-2*250\,41):y=60:enable='between(t,2,10)':shortest=1[o1];[o1][img2]overlay=x=min(t*250-3*250\,100):y=200:enable='between(t,3,10)':shortest=1" -t 11  multiImageLeftToRight.mp4




4) 3 images left to right given x=41, x=100, x=300 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -loop 1 -i image.png -loop 1 -i image2.png -loop 1 -i image3.png -filter_complex "[1]scale=-2:100[img1];[2]scale=-2:100[img2];[3]scale=-2:100[img3];[0][img1]overlay=x=min(t*250-2*250\,41):y=60:enable='between(t,2,10)':shortest=1[o1];[o1][img2]overlay=x=min(t*250-3*250\,130):y=130:enable='between(t,3,10)':shortest=1[o2];[o2][img3]overlay=x=min(t*250-4*250\,260):y=190:enable='between(t,4,10)':shortest=1" -t 11  threeImageLeftToRight.mp4




5) 3 images right to left given x=41, x=100, x=300 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -loop 1 -i image.png -loop 1 -i image2.png -loop 1 -i image3.png -filter_complex "[1]scale=-2:100[img1];[2]scale=-2:100[img2];[3]scale=-2:100[img3];[0][img1]overlay=x=W-min(t*250-2*250\,(W\-41)):y=60:enable='between(t,2,10)':shortest=1[o1];[o1][img2]overlay=x=W-min(t*250-3*250\,(W\-130)):y=130:enable='between(t,3,10)':shortest=1[o2];[o2][img3]overlay=x=W-min(t*250-4*250\,(W\-260)):y=190:enable='between(t,4,10)':shortest=1" -t 11  threeImageRightToLeft.mp4




6) 3 images top to bottom given y=41, y=100, y=300 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -loop 1 -i image.png -loop 1 -i image2.png -loop 1 -i image3.png -filter_complex "[1]scale=-2:100[img1];[2]scale=-2:100[img2];[3]scale=-2:100[img3];[0][img1]overlay=x=41:y=min(t*250-2*250\,60):enable='between(t,2,10)':shortest=1[o1];[o1][img2]overlay=x=130:y=min(t*250-3*250\,130):enable='between(t,3,10)':shortest=1[o2];[o2][img3]overlay=x=260:y=min(t*250-4*250\,190):enable='between(t,4,10)':shortest=1" -t 11  threeImageTopToBottom.mp4




7) 3 images bottom to top given y=41, y=100, y=300 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -loop 1 -i image.png -loop 1 -i image2.png -loop 1 -i image3.png -filter_complex "[1]scale=-2:100[img1];[2]scale=-2:100[img2];[3]scale=-2:100[img3];[0][img1]overlay=x=41:y=H-min(t*250-2*250\,(H\-60)):enable='between(t,2,10)':shortest=1[o1];[o1][img2]overlay=x=130:y=H-min(t*250-3*250\,(H\-130)):enable='between(t,3,10)':shortest=1[o2];[o2][img3]overlay=x=260:y=H-min(t*250-4*250\,(H\-190)):enable='between(t,4,10)':shortest=1" -t 11  threeImageBottomToTop.mp4

************************ GIF *********************

1) single left to right given x=41 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -ignore_loop 0 -i gif1.gif -filter_complex "[1:v]scale=-2:100[ovrl];[0:v][ovrl]overlay=x=min(t*250-2*250\,41):y=60:enable='between(t,2,10)'" -t 11 gifLeftToRight.mp4




2) double left to right given x=41 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -ignore_loop 0 -i gif1.gif -ignore_loop 0 -i gif2.gif -ignore_loop 0 -i gif3.gif -filter_complex "[1]scale=-2:100[gif1];[2]scale=-2:100[gif2];[0][gif1]overlay=x=min(t*250-2*250\,41):y=60:enable='between(t,2,10)':shortest=1[o1];[o1][gif2]overlay=x=min(t*250-3*250\,150):y=170:enable='between(t,3,10)':shortest=1" -t 11 doubleGifLeftToRight.mp4




3) three left to right given x=41 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -ignore_loop 0 -i gif1.gif -ignore_loop 0 -i gif2.gif -ignore_loop 0 -i gif3.gif -filter_complex "[1]scale=-2:100[gif1];[2]scale=-2:100[gif2];[3]scale=-2:100[gif3];[0][gif1]overlay=x=min(t*250-2*250\,41):y=60:enable='between(t,2,10)':shortest=1[o1];[o1][gif2]overlay=x=min(t*250-3*250\,150):y=170:enable='between(t,3,10)':shortest=1[o2];[o2][gif3]overlay=x=min(t*250-4*250\,240):y=240:enable='between(t,4,10)':shortest=1" -t 11 threeGifLeftToRight.mp4



3) three right to left given x=41 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -ignore_loop 0 -i gif1.gif -ignore_loop 0 -i gif2.gif -ignore_loop 0 -i gif3.gif -filter_complex "[1]scale=-2:100[gif1];[2]scale=-2:100[gif2];[3]scale=-2:100[gif3];[0][gif1]overlay=x=W-min(t*250-2*250\,(W\-41)):y=60:enable='between(t,2,10)':shortest=1[o1];[o1][gif2]overlay=x=W-min(t*250-3*250\,(W\-150)):y=170:enable='between(t,3,10)':shortest=1[o2];[o2][gif3]overlay=x=W-min(t*250-4*250\,(W\-240)):y=240:enable='between(t,4,10)':shortest=1" -t 11 threeGifRightToLeft.mp4




3) three top to bottom given x=41 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -ignore_loop 0 -i gif1.gif -ignore_loop 0 -i gif2.gif -ignore_loop 0 -i gif3.gif -filter_complex "[1]scale=-2:100[gif1];[2]scale=-2:100[gif2];[3]scale=-2:100[gif3];[0][gif1]overlay=x=41:y=min(t*250-2*250\,60):enable='between(t,2,10)':shortest=1[o1];[o1][gif2]overlay=x=150:y=min(t*250-3*250\,170):enable='between(t,3,10)':shortest=1[o2];[o2][gif3]overlay=x=240:y=min(t*250-4*250\,240):enable='between(t,4,10)':shortest=1" -t 11 threeGifTopToBottom.mp4




3) three bottom to top given x=41 position

ffmpeg -y -i 'https://player.vimeo.com/external/181545195.sd.mp4?s=176d502710df829442a83565bb79efbe3c9c0b93&profile_id=164' -ignore_loop 0 -i gif1.gif -ignore_loop 0 -i gif2.gif -ignore_loop 0 -i gif3.gif -filter_complex "[1]scale=-2:100[gif1];[2]scale=-2:100[gif2];[3]scale=-2:100[gif3];[0][gif1]overlay=x=41:y=H-min(t*250-2*250\,(H\-60)):enable='between(t,2,10)':shortest=1[o1];[o1][gif2]overlay=x=150:y=H-min(t*250-3*250\,(H\-170)):enable='between(t,3,10)':shortest=1[o2];[o2][gif3]overlay=x=240:y=H-min(t*250-4*250\,(H\-240)):enable='between(t,4,10)':shortest=1" -t 11 threeGifBottomToTop.mp4
3

On macOS with enable='between(t,0,10)'

On macOS the syntax of enable='between(t,start_second, end_seconds)' is different. Input should be in seconds, not in hh:mm:ss.

My ffmpeg version 4.3.1

ffmpeg -i input.mp4 -vf "drawtext=text='My Text':enable='between(t,20,120)': x=(w-text_h)/2: y=(h-text_h)/2: fontsize=32: fontcolor=white: box=1: [email protected]: boxborderw=5:" -c:a copy output.mp4
0

For Android, If you are using ffmpeg-kit. Use Gpl libarary for "libx264"

 implementation 'com.arthenica:ffmpeg-kit-full-gpl:5.1'

For position

    var POSITION_BOTTOM_RIGHT = "x=w-tw-10:y=h-th-10"
    var POSITION_TOP_RIGHT = "x=w-tw-10:y=10"
    var POSITION_TOP_LEFT = "x=10:y=10"
    var POSITION_BOTTOM_LEFT = "x=10:h-th-10"
    var POSITION_CENTER_BOTTOM = "x=(main_w/2-text_w/2):y=main_h-(text_h*2)"
    var POSITION_CENTER_ALLIGN = "x=(w-text_w)/2:y=(h-text_h)/3"

Then

  val command = StringBuilder()
                .append("-y") //overWrite
                .append(" -i ").append(videoFile!!.path)  // video
                .append(" -vf ").append("drawtext=").append("text=").append(text)
                .append(":fontcolor=").append(color).append(":").append("fontsize=")
                .append(size + border).append(":").append(position)
                .append(":fontfile=").append(font!!.path)
                .append(" -c:v libx264 ").append(" -c:a copy  -movflags +faststart ")
                .append(outputFile.path)

Run the ffmpeg

     val session = FFmpegKit.execute(command.toString())

     if (ReturnCode.isSuccess(session.returnCode)) {

           callback!!.onFinish()


            // SUCCESS
       } else if (ReturnCode.isCancel(session.returnCode)) {
                   
            callback!!.onFailure("CANCELLED")


       } else {

            callback!!.onFailure("FAILED "+  String.format(
                        "Command failed with state %s and rc %s.%s",
                        session.state,
                        session.returnCode,
                        session.failStackTrace
      ))}
13
  • hi @Rohaitas can we connect ? i have doubts regarding ffmpegkit could u help?
    – Wini
    Commented Jan 5 at 12:12
  • @Wini whats the issue? Commented Jan 5 at 13:50
  • the command which u used above not working for me ... file is created but when i tried to play its not playing video video cannot played @Rohaitas
    – Wini
    Commented Jan 5 at 13:55
  • do check file extension. it was working for me Commented Jan 5 at 14:00
  • check logs as well Commented Jan 5 at 14:00

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