Skip to main content
Text overlays are essential for titles, subtitles, captions, and lower thirds. MovieLite integrates with Pictex for powerful text rendering with full styling control.
View the complete source code: text_animations.py

Simple Text Overlay

Start with a basic text overlay:
from movielite import VideoClip, TextClip, VideoWriter
from pictex import Canvas

video = VideoClip("input.mp4")

canvas = Canvas().font_size(60).color("white").background_color("transparent")
text = TextClip("Hello World", start=0, duration=5, canvas=canvas)
text.set_position((video.size[0] // 2 - text.size[0] // 2, 100))

writer = VideoWriter("output_simple_text.mp4", fps=video.fps, size=video.size)
writer.add_clips([video, text])
writer.write()

video.close()
TextClip uses Pictex’s Canvas for styling. The canvas defines font, colors, padding, shadows, and more.

Styled Text with Gradients and Shadows

Create visually striking text with gradients and shadows:
from movielite import VideoClip, TextClip, VideoWriter
from pictex import Canvas, LinearGradient, Shadow

video = VideoClip("input.mp4")

canvas = (
    Canvas()
    .font_family("Arial")
    .font_size(80)
    .color("white")
    .padding(30)
    .background_color(LinearGradient(["#FF6B6B", "#4ECDC4"]))
    .border_radius(15)
    .text_shadows(Shadow(offset=(3, 3), blur_radius=5, color="black"))
)

text = TextClip("Styled Title", start=0, duration=5, canvas=canvas)
text.set_position((video.size[0] // 2 - text.size[0] // 2, 200))

writer = VideoWriter("output_styled_text.mp4", fps=video.fps, size=video.size)
writer.add_clips([video, text])
writer.write()

video.close()

Pictex Canvas Features

  • Font control: font_family(), font_size(), font_weight()
  • Colors: color(), background_color()
  • Layout: padding(), border_radius()
  • Effects: text_shadows(), gradients
  • Positioning: Automatic size calculation

Text with Fade Effects

Add fade in and fade out to text:
from movielite import VideoClip, TextClip, VideoWriter, vfx
from pictex import Canvas

video = VideoClip("input.mp4")

canvas = Canvas().font_size(70).color("white").background_color("transparent")
text = TextClip("Fading Text", start=2, duration=4, canvas=canvas)
text.set_position((video.size[0] // 2 - text.size[0] // 2, 300))
text.add_effect(vfx.FadeIn(1.0))
text.add_effect(vfx.FadeOut(1.0))

writer = VideoWriter("output_fade_text.mp4", fps=video.fps, size=video.size)
writer.add_clips([video, text])
writer.write()

video.close()
The text appears at t=2s, fades in over 1 second, displays for 2 seconds, then fades out over 1 second (total 4s duration).

Animated Position (Sliding Text)

Create sliding animations using position functions:
from movielite import VideoClip, TextClip, VideoWriter
from pictex import Canvas

video = VideoClip("input.mp4")

canvas = Canvas().font_size(60).color("white").padding(20).background_color("#333333")
text = TextClip("Sliding Text", start=0, duration=5, canvas=canvas)

# Slide in from left over 2 seconds
def animated_position(t):
    if t < 2.0:
        # Slide in from left
        x = int(-text.size[0] + (text.size[0] + video.size[0] // 2) * (t / 2.0))
    else:
        # Stay centered
        x = video.size[0] // 2 - text.size[0] // 2
    
    y = video.size[1] // 2 - text.size[1] // 2
    return (x, y)

text.set_position(animated_position)

writer = VideoWriter("output_animated_pos.mp4", fps=video.fps, size=video.size)
writer.add_clips([video, text])
writer.write()

video.close()

Position Function Signature

def position_function(t: float) -> tuple[int, int]:
    """
    Args:
        t: Current time in seconds (relative to clip start)
    
    Returns:
        (x, y) position tuple in pixels
    """
    x = calculate_x(t)
    y = calculate_y(t)
    return (x, y)

Bouncing Text

Create a bouncing animation with decreasing amplitude:
import math
from movielite import VideoClip, TextClip, VideoWriter
from pictex import Canvas

video = VideoClip("input.mp4")

canvas = Canvas().font_size(80).color("#FFD700").background_color("transparent")
text = TextClip("Bounce!", start=0, duration=6, canvas=canvas)

# Bounce effect
def bounce_position(t):
    x = video.size[0] // 2 - text.size[0] // 2
    
    # Bounce amplitude decreases over time
    amplitude = 200 * (1.0 - t / 6.0)
    frequency = 3.0
    y = int(video.size[1] // 2 - text.size[1] // 2 - 
            abs(math.sin(t * frequency * math.pi)) * amplitude)
    
    return (x, y)

text.set_position(bounce_position)

writer = VideoWriter("output_bounce.mp4", fps=video.fps, size=video.size)
writer.add_clips([video, text])
writer.write()

video.close()

Scaling Text (Zoom Effect)

Animate text scale over time:
from movielite import VideoClip, TextClip, VideoWriter
from pictex import Canvas

video = VideoClip("input.mp4")

canvas = Canvas().font_size(70).color("white").background_color("transparent")
text = TextClip("ZOOM IN", start=0, duration=3, canvas=canvas)
text.set_position((
    video.size[0] // 2 - text.size[0] // 2,
    video.size[1] // 2 - text.size[1] // 2
))

# Zoom from 0.5x to 1.5x over 3 seconds
text.set_scale(lambda t: 0.5 + (1.0 * (t / 3.0)))

writer = VideoWriter("output_zoom_text.mp4", fps=video.fps, size=video.size)
writer.add_clips([video, text])
writer.write()

video.close()
set_scale() accepts either a float or a function that takes time and returns a float.

Multiple Text Layers

Create complex sequences with multiple text elements:
from movielite import VideoClip, TextClip, VideoWriter, vfx
from pictex import Canvas, LinearGradient

video = VideoClip("input.mp4")

# Title (appears first)
canvas1 = Canvas().font_size(90).color("white").background_color(
    LinearGradient(["#FF6B6B", "#FF8E53"])
)
title = TextClip("Title", start=0, duration=3, canvas=canvas1)
title.set_position((video.size[0] // 2 - title.size[0] // 2, 100))
title.add_effect(vfx.FadeIn(0.5))
title.add_effect(vfx.FadeOut(0.5))

# Subtitle (appears after title)
canvas2 = Canvas().font_size(50).color("#CCCCCC").background_color("transparent")
subtitle = TextClip("Subtitle text here", start=1, duration=4, canvas=canvas2)
subtitle.set_position((video.size[0] // 2 - subtitle.size[0] // 2, 220))
subtitle.add_effect(vfx.FadeIn(0.5))
subtitle.add_effect(vfx.FadeOut(0.5))

# Caption (appears last)
canvas3 = Canvas().font_size(40).color("yellow").background_color("transparent")
caption = TextClip("Additional info", start=2.5, duration=3, canvas=canvas3)
caption.set_position((video.size[0] // 2 - caption.size[0] // 2, video.size[1] - 100))
caption.add_effect(vfx.FadeIn(0.3))
caption.add_effect(vfx.FadeOut(0.3))

writer = VideoWriter("output_multi_text.mp4", fps=video.fps, size=video.size)
writer.add_clips([video, title, subtitle, caption])
writer.write()

video.close()

Timeline Visualization

         0s    1s    2s   2.5s  3s    4s    5s   5.5s
Title    [----------fade out---]
Subtitle       [------------------fade out--------]
Caption                 [-------------fade out------]

Lower Third

Create professional lower third graphics:
from movielite import VideoClip, TextClip, ImageClip, VideoWriter, vfx
from pictex import Canvas

video = VideoClip("input.mp4")

# Background bar
bar = ImageClip.from_color(
    color=(0, 0, 0, 200),  # Semi-transparent black
    size=(video.size[0], 120),
    duration=5
)
bar.set_position((0, video.size[1] - 120))
bar.add_effect(vfx.FadeIn(0.5))
bar.add_effect(vfx.FadeOut(0.5))

# Name text
canvas_name = Canvas().font_size(50).color("white").background_color("transparent")
name = TextClip("John Doe", start=0, duration=5, canvas=canvas_name)
name.set_position((40, video.size[1] - 100))
name.add_effect(vfx.FadeIn(0.5))
name.add_effect(vfx.FadeOut(0.5))

# Title text
canvas_title = Canvas().font_size(30).color("#AAAAAA").background_color("transparent")
job_title = TextClip("Software Engineer", start=0, duration=5, canvas=canvas_title)
job_title.set_position((40, video.size[1] - 50))
job_title.add_effect(vfx.FadeIn(0.5))
job_title.add_effect(vfx.FadeOut(0.5))

writer = VideoWriter("output_lower_third.mp4", fps=video.fps, size=video.size)
writer.add_clips([video, bar, name, job_title])
writer.write()

video.close()
Lower thirds typically appear in the bottom third of the frame and display speaker names, titles, or locations.

Advanced Animation Patterns

Easing Functions

Use easing functions for smoother animations:
def ease_in_out_cubic(t):
    """Smooth acceleration and deceleration."""
    if t < 0.5:
        return 4 * t * t * t
    else:
        return 1 - pow(-2 * t + 2, 3) / 2

def animated_scale(t):
    # Normalize time to 0-1 range
    normalized = t / duration
    # Apply easing
    eased = ease_in_out_cubic(normalized)
    # Map to scale range
    return 0.5 + (1.0 * eased)

text.set_scale(animated_scale)

Circular Motion

import math

def circular_position(t):
    # Circle parameters
    center_x = video.size[0] // 2
    center_y = video.size[1] // 2
    radius = 200
    speed = 2 * math.pi / 5.0  # Complete circle in 5 seconds
    
    angle = t * speed
    x = int(center_x + radius * math.cos(angle) - text.size[0] // 2)
    y = int(center_y + radius * math.sin(angle) - text.size[1] // 2)
    
    return (x, y)

text.set_position(circular_position)

Text Animation Recipes

def slide_in_left(t):
    progress = min(t / 1.0, 1.0)  # 1 second animation
    x = int(-text.size[0] + progress * (video.size[0] // 2 + text.size[0]))
    y = video.size[1] // 2
    return (x, y)

Performance Considerations

  • TextClip renders text once and reuses the image
  • Animated position/scale functions are evaluated per frame
  • Complex animations (many text layers) may slow rendering
  • Use duration parameter to limit text visibility

Common Patterns

Centered Text

text.set_position((
    video.size[0] // 2 - text.size[0] // 2,
    video.size[1] // 2 - text.size[1] // 2
))

Top Center

text.set_position((
    video.size[0] // 2 - text.size[0] // 2,
    50  # 50px from top
))

Bottom Left

text.set_position((
    40,  # 40px from left
    video.size[1] - text.size[1] - 40  # 40px from bottom
))

Troubleshooting

Text appears cut off

Ensure position keeps text within frame bounds:
x = max(0, min(x, video.size[0] - text.size[0]))
y = max(0, min(y, video.size[1] - text.size[1]))

Text doesn’t appear

Check:
  • start time is within video duration
  • duration is > 0
  • Position is within visible frame
  • Canvas background isn’t fully transparent if no text color set

Animation is choppy

Increase video FPS:
writer = VideoWriter("output.mp4", fps=60, size=video.size)

Next Steps

Effects Showcase

Learn about visual effects you can apply to text

Advanced Compositing

Combine text with other elements

TextClip API

Complete TextClip API reference

Pictex Documentation

Learn more about Pictex styling

Build docs developers (and LLMs) love