Search code examples
pythonffmpegdrawtextffmpeg-python

How to drawtext with a color gradient fill with ffmpeg (ffmpeg-python)? and then mix with music?


I'd like to achieve this result (to place a text with gradint or whatever picture-fill on top of a video): enter image description here So I decided to make a picture of gradient: enter image description here and then with chroma-key technique transform it to this (with transparent background): enter image description here and then place it over the video.

Here is the code with ffmpeg-python:

font_path = 'fonts/my_font.ttf'
input_video = ffmpeg.input('vid01.mp4')
split = input_video.filter_multi_output('split')
split_main = split[0]
split_txt_bg = split[1]

text_bg_black = ffmpeg.filter(text_bg, 'eq', brightness=-1000, saturation=0) # generate black bg from video input
text_mask = ffmpeg.filter(text_bg_black, 'drawtext', text='some text', fontcolor='0xFFFFFF', fontsize=85, x='(w-text_w)/2', y='68-(text_h/2)', fontfile=font_path) #draw text
text_mask_transparent = ffmpeg.filter(text_mask, 'colorkey', color='0xFFFFFF', similarity=0.45, blend=0.1)
text_on_bg = ffmpeg.filter([fill_gradient_gold, text_mask_transparent], 'overlay', x='(W-w)/2', y='(H-h)/2')
text = ffmpeg.filter(text_on_bg, 'colorkey', color='0x000000', similarity=0.45, blend=0.01)

comp1 = ffmpeg.filter([split_main, text], 'overlay', x='(W-w)/2', y='(H-h)/2')

comp.output('vid01_comp1.mp4').overwrite_output().run()

This solution works, BUT with problems:

  1. in the final it lowers the brightness of original gradient picture, that I don't want at all
  2. it doesn't seem to be elegant (cuz of chroma-keying solution)

Is it possible to make a alpha-mask with drawtext and then use it to cut the gradient?

It would be better if you provide the answer in ffmpeg-python cuz I'm a newbie and it's very hard for me to convert command-line ffmpeg to ffmpeg-python, thank you!

I also can't get how to mix resulting video comp1 with music file, help pls.


Solution

  • We may draw the text on black background, and use alphamerge for creating transparent background with colored gradient text.
    Then we can overlay the gradient (with the transparent background) on the input video.

    I don't know if my suggested solution is the most elegant, but it is not lower the brightness of the gradient picture.


    The suggested solution applies the following stages:

    • Define synthetic black video source (1 frame).
    • Draw white text on the black video frame.
    • Add the "white text on black background" as alpha channel of the gradient image.
      Only the white text is opaque, and the black background is fully transparent.
    • Overlay the above frame over the video.

    Start by creating synthetic yellow video file (with sine audio) for testing:

    # Build first synthetic video, for testing:
    (
        ffmpeg
        .output(ffmpeg.input('sine=frequency=500', f='lavfi'),
                ffmpeg.input('color=yellow:size=1920x1080:rate=1', f='lavfi'),
                'in.mp4', vcodec='libx264', crf=17, pix_fmt='yuv444p', acodec='aac', ar=22050, t=10)
        .overwrite_output()
        .run()
    )
    

    Solution code sample:

    import ffmpeg
    
    # Example using FFmpeg CLI:
    # ffmpeg -y -i text_bg.png -f lavfi -i color=0x505050:size=1920x1080:rate=1:duration=1 -filter_complex "color=black[blk];[blk][0:v]scale2ref[b][v0];[b]drawtext=text='some text':fontcolor='0xFFFFFF':fontsize=300:x='(w-text_w)/2':y='(h-text_h)/2',format=gray[a];[v0][a]alphamerge[txt];[1:v][txt]overlay=format=yuv444" -frames:v 1 overlayed_text.png
    
    input = ffmpeg.input('in.mp4')
    input_video = input.video
    input_audio = input.audio
    
    bg = ffmpeg.input('text_bg.png').video  # Gradient image
    
    blk = ffmpeg.input('color=black:size=1920x1080:rate=1:duration=1', f='lavfi').video  # Black background
    
    # White text on black background - used as alpha channel
    txt = blk.filter('drawtext', text='some text', fontcolor='0xFFFFFF', fontsize=400, x='(w-text_w)/2', y='(h-text_h)/2').filter('format', pix_fmts='gray')
    
    # Add txt as alpha channel to bg - only the white text is opaque, the black background is transparent.
    txt_on_bg = ffmpeg.filter([bg, txt], 'alphamerge')
    
    # Overlay txt_on_bg on input_video
    comp1 = ffmpeg.filter([input_video, txt_on_bg], 'overlay', x='(W-w)/2', y='(H-h)/2', format='yuv444')
    
    ffmpeg.output(comp1, input_audio, 'vid01_comp1.mp4', vcodec='libx264', crf=17, pix_fmt='yuv444p', acodec='copy').overwrite_output().run()
    

    Sample output:
    enter image description here