Overlaying Text on Images Using MoviePy

Ayman H
5 min readJun 10, 2021

For the more visually-inclined users, here’s a link to a YouTube video I created for the exact same topic.

MoviePy is a Python library for video editing; cutting, concatenations, title insertions, video compositing, video processing, and creation of custom effects.

I’ve been using MoviePy for quite some time now, and I’ve worked on many fun image/video editing mini projects using this package. In this tutorial, I’ll show you how you can overlay text on top of images, and in another article, I’ll show you how to overlay (and animate) text on top of videos.

Let’s start with a simple task: let’s create a TextClip object and save it as an image:

from moviepy.editor import *

text_clip = TextClip(txt="Python is Awesome!",
fontsize=40,
color="black", # Font color
bg_color="aqua") # Background color
text_clip.save_frame("out.png")

Which creates the following image:

You can view all the available colors by printing TextClip.list('color').

Alright, so the first thing that I’d like to do is change the font, and I can do that by providing a font name when instantiating the object:

from moviepy.editor import *

text_clip = TextClip(txt="Python is Awesome!",
fontsize=40,
font="Lane",
color="black", # Font color
bg_color="aqua") # Background color
text_clip.save_frame("out.png")

Which creates the following image:

The size of this image is 340 x 50

The font “Lane” did not exist in ImageMagick, and as a result I wasn’t able to use it in MoviePy until I downloaded it and added it to ImageMagick. I have an article and a YouTube video that outline exactly how to do that.

The width and height of any TextClip object (which, of course, happens to be the same as the width and height of the image) can be obtained by accessing the size attribute. Running text_clip.size will return a 2-element tuple that contain the width and the height of the TextClip object (or image) respectively. For the image above, running that command will return (340, 50), which means that the width of the image is 340 pixels, and the height of the image is 50 pixels.

What if I want the width of the text to extend to some number of pixels?

This is actually a pretty common use case; let’s say you’d like to overlay a text on top of an image, and you’d like the text to take up to 80% of the image width (which happens to be 400 pixels for example). In this case, you would specify a size value to your object, and you would get rid of the fontsize value:

from moviepy.editor import *

text_clip = TextClip(txt="Python is Awesome!",
size=(400, 0),
font="Lane",
color="black", # Font color
bg_color="aqua") # Background color
text_clip.save_frame("out.png")

Notice here that we set the second element of the size to 0, which implies that we’d like the height of the TextClip (or image) to be exactly as big as it needs to be. Printing the size in my case, by running print(text_clip.size), shows (400, 59). The output image is identical to the one you see above, except that the size is different.

Of course, you can also do the opposite and set the first value (width) to 0 and the second value (height) to some number of pixels, and the resulting image will have the same height that you provide (and the width will be determined accordingly).

Can I add some padding to the left, right, top, and bottom of the TextClip?

The answer “Yes”, and that’s also a common use case, since the image will look much better if it had some padding (usually more padding to the left/right than on the top/bottom). Padding, by the way, is the space between the text and the border of the image.

There are few ways to do that, the best way (in my opinion) is to create a ColorClip object, and make the width and height of it a little bigger than the TextClip object, then place the TextClip object on top of the ColorClip object (and in the center).

This might not seem to be the most obvious or straight forward way of doing it, but it’s my preferred way, and it has worked for me every time. Using this ColorClip approach provides us with two main advantages:

  1. We can choose the the background color to be any RGB value, and
  2. We can make the background (of the ColorClip) transparent without having to make the TextClip transparent, and that would make the overlaid text look much better, especially when overlaid on top of another image.

Here’s how we do that:

from moviepy.editor import *# We don't need to define a 'bg_color' anymore
#
text_clip = TextClip(txt="Python is Awesome!",
size=(400, 0),
font="Lane",
color="black")

image_width, image_height = text_clip.size

padding_width = 80 # 40 pixels on each side
padding_height = 20 # 10 pixels on each side
# We create the ColorClip object after creating the TextClip object
#
color_clip = ColorClip(size=(image_width + padding_width,
image_height + padding_height),
color=(0, 255, 255))
# Let's make the background a little transparent
#
color_clip = color_clip.set_opacity(.5)
# Now we need to set the position of 'text_clip' to 'center'
#
text_clip = text_clip.set_position('center')
# Now we overlay 'text_clip' on top of 'color_clip'
#
final_clip = CompositeVideoClip([color_clip, text_clip])
final_clip.save_frame("out.png")

Executing the above code will add 40 pixels to the left and 40 pixels to the right, and it’ll also add 10 pixels on top and 10 pixels on the bottom:

The size of this image is 480 x 79. The reason the background color has changed is because we made it a little transparent (opacity was set to .5. Zero opacity means no transparency, and 1 opacity means full transparency)

Let us now overlay text on an actual image

Here’s what we’re going to do:

1. We open/load the image by creating an ImageClip object:

image_clip = ImageClip("guatape.jpg")

2. We create a TextClip object, and make the width of it 80% of the ‘image_clip’ width. We will also center its location (with respect to what we’ll combine it with; that is, the ColorClip):

text_clip = TextClip(txt="Welcome to Guatapé, Colombia!".upper(),
size=(.8*image_clip.size[0], 0),
font="Lane",
color="black")
text_clip = text_clip.set_position('center')

3. Now let’s create a ColorClip with some padding, and with opacity set to .4:

im_width, im_height = text_clip.size
color_clip = ColorClip(size=(int(im_width*1.1), int(im_height*1.4)),
color=(0, 255, 255))
color_clip = color_clip.set_opacity(.6)

Multiplying the width by 1.1 will add a 10% (of the width) padding, and multiplying the height by 1.4 will add a 40% (of the height) padding.

4. Let us overlay the TextClip on top of the ColorClip and create a new clip. The type of this clip is called CompositeVideoClip, even though it’s not really a video:

clip_to_overlay = CompositeVideoClip([color_clip, text_clip])

5. Let’s center ‘clip_to_overlay’ with respect to what we want to combine it with (the ImageClip), overlay it on top of our ImageClip, and save it as a PNG file:

clip_to_overlay = clip_to_overlay.set_position('center')final_clip = CompositeVideoClip([image_clip, clip_to_overlay])final_clip.save_frame("out.png")

The output to all of that should be:

Picture taken by the author (9 iPhone 11 Pro pictures stitched together)

Cheers, and thank you for reading!

--

--

Ayman H

Assistant Professor at the College of Charleston.