Skip to main content

Overview

MovieLite provides three categories of effects:
  • VFX (Visual Effects) - Effects for GraphicClip instances (video, image, text)
  • AFX (Audio Effects) - Effects for AudioClip instances
  • VTX (Video Transitions) - Transitions between two GraphicClip instances
All effects use a consistent API: create the effect, then apply it to a clip.

Visual Effects (VFX)

Visual effects modify how graphic clips appear. They work by adding transformations to the clip’s rendering pipeline.

Importing VFX

from movielite import vfx
from movielite import VideoClip, ImageClip

Fade Effects

FadeIn

Gradually increases opacity from 0 to the clip’s original opacity.
clip = VideoClip("video.mp4", start=0, duration=10)
clip.add_effect(vfx.FadeIn(duration=2.0))
# Fades in over first 2 seconds

FadeOut

Gradually decreases opacity to 0 at the end of the clip.
clip = VideoClip("video.mp4", start=0, duration=10)
clip.add_effect(vfx.FadeOut(duration=1.5))
# Fades out over last 1.5 seconds

Combining Fades

clip = VideoClip("video.mp4", start=0, duration=10)
clip.add_effect(vfx.FadeIn(2.0)).add_effect(vfx.FadeOut(2.0))
# Fades in for 2s, fades out for 2s
Source: src/movielite/vfx/fade.py:1

Blur Effects

Blur (Static)

Applies constant Gaussian blur.
clip = ImageClip("photo.png", start=0, duration=5)
clip.add_effect(vfx.Blur(intensity=15.0))
# intensity must be odd number >= 1 (kernel size)

Blur (Animated)

Blur increases from 0 to maximum over time.
clip = ImageClip("photo.png", start=0, duration=5)
clip.add_effect(vfx.Blur(intensity=21.0, animated=True, duration=3.0))
# Blur increases over first 3 seconds

BlurIn

Starts blurred and becomes sharp.
clip = VideoClip("video.mp4", start=0, duration=10)
clip.add_effect(vfx.BlurIn(duration=2.0, max_intensity=25.0))
# Starts at blur intensity 25, decreases to 0 over 2 seconds

BlurOut

Starts sharp and becomes blurred at the end.
clip = VideoClip("video.mp4", start=0, duration=10)
clip.add_effect(vfx.BlurOut(duration=2.0, max_intensity=25.0))
# Last 2 seconds blur from 0 to intensity 25
Source: src/movielite/vfx/blur.py:1

Color Adjustments

Brightness

Adjust brightness using optimized pixel transforms (numba-compiled).
clip = VideoClip("dark_video.mp4", start=0, duration=10)
clip.add_effect(vfx.Brightness(factor=1.3))
# 1.0 = no change, >1.0 = brighter, <1.0 = darker

Contrast

Adjust contrast around midpoint (128).
clip = ImageClip("photo.png", start=0, duration=5)
clip.add_effect(vfx.Contrast(factor=1.5))
# 1.0 = no change, >1.0 = more contrast, <1.0 = less contrast

Saturation

Adjust color saturation.
clip = VideoClip("video.mp4", start=0, duration=10)
clip.add_effect(vfx.Saturation(factor=1.5))
# 1.0 = no change, 0.0 = grayscale, >1.0 = more saturated

Black and White

Convert to grayscale.
clip = VideoClip("video.mp4", start=0, duration=10)
clip.add_effect(vfx.BlackAndWhite())
# Also available as: vfx.Grayscale()

Sepia

Apply sepia tone effect.
clip = ImageClip("old_photo.png", start=0, duration=5)
clip.add_effect(vfx.Sepia(intensity=1.0))
# intensity: 0.0 = no effect, 1.0 = full sepia
Source: src/movielite/vfx/color.py:1

Zoom Effects

ZoomIn

Gradually scales up from smaller size.
clip = ImageClip("photo.png", start=0, duration=5)
clip.add_effect(vfx.ZoomIn(
    duration=3.0,
    from_scale=1.0,
    to_scale=1.5,
    anchor="center"  # or "top-left", "bottom-right", etc.
))
# Zooms from 100% to 150% over 3 seconds

ZoomOut

Gradually scales down, applied at the end of clip.
clip = VideoClip("video.mp4", start=0, duration=10)
clip.add_effect(vfx.ZoomOut(
    duration=2.0,
    from_scale=1.5,
    to_scale=1.0,
    anchor="center"
))
# Last 2 seconds: zooms from 150% to 100%

Ken Burns Effect

Combines slow zoom and pan for cinematic feel.
clip = ImageClip("landscape.png", start=0, duration=8)
clip.add_effect(vfx.KenBurns(
    duration=8.0,           # None = entire clip
    start_scale=1.0,
    end_scale=1.3,
    start_position=(0, 0),
    end_position=(-100, -50)  # Pan left and up
))
# Smooth zoom + pan with easing
Ken Burns effect uses cubic ease-in-out for smooth, professional-looking animations.
Source: src/movielite/vfx/zoom.py:1

Anchor Points

Zoom effects support various anchor points:
  • "center" (default)
  • "top-left", "top-right", "bottom-left", "bottom-right"
  • "top", "bottom", "left", "right"
  • (x, y) tuple for custom position
# Zoom from top-left corner
clip.add_effect(vfx.ZoomIn(duration=2.0, anchor="top-left"))

# Zoom from custom point
clip.add_effect(vfx.ZoomIn(duration=2.0, anchor=(500, 300)))

Chaining Effects

Effects can be chained to create complex transformations:
from movielite import vfx, VideoClip

clip = VideoClip("video.mp4", start=0, duration=10)

# Chain multiple effects
clip.add_effect(vfx.FadeIn(2.0)) \
    .add_effect(vfx.Brightness(1.2)) \
    .add_effect(vfx.Saturation(1.3)) \
    .add_effect(vfx.FadeOut(2.0))
Effects are applied in the order they’re added. Some effects (like fades) modify opacity functions, while others add frame transforms.

Custom Effects

Create custom visual effects by extending GraphicEffect:
from movielite.vfx.base import GraphicEffect
from movielite.core import GraphicClip
import numpy as np
import cv2

class Vignette(GraphicEffect):
    """Add dark vignette effect around edges."""
    
    def __init__(self, intensity: float = 0.5):
        self.intensity = intensity
    
    def apply(self, clip: GraphicClip) -> None:
        def vignette_transform(frame: np.ndarray, t: float) -> np.ndarray:
            h, w = frame.shape[:2]
            
            # Create radial gradient
            center_x, center_y = w // 2, h // 2
            Y, X = np.ogrid[:h, :w]
            dist = np.sqrt((X - center_x)**2 + (Y - center_y)**2)
            max_dist = np.sqrt(center_x**2 + center_y**2)
            
            # Vignette mask
            vignette = 1 - (dist / max_dist) * self.intensity
            vignette = np.clip(vignette, 0, 1)
            
            # Apply to each channel
            result = frame.copy()
            for c in range(frame.shape[2]):
                result[:, :, c] = (frame[:, :, c] * vignette).astype(np.uint8)
            
            return result
        
        clip.add_transform(vignette_transform)

# Usage
clip = VideoClip("video.mp4", start=0, duration=10)
clip.add_effect(Vignette(intensity=0.7))
Source: src/movielite/vfx/base.py:1

Pixel-Level Transforms

For color adjustments, use numba-compiled pixel transforms for better performance:
import numba
from movielite.core import GraphicClip

@numba.njit
def red_tint(b, g, r, a, t):
    """Add red tint to each pixel."""
    return (
        min(255, max(0, int(b * 0.8))),
        min(255, max(0, int(g * 0.8))),
        min(255, max(0, int(r * 1.2)))
    )

clip = VideoClip("video.mp4", start=0, duration=10)
clip.add_pixel_transform(red_tint)
Pixel transforms are batched and executed efficiently using numba. Multiple transforms are applied in a single pass.
Source: src/movielite/core/graphic_clip.py:187

Audio Effects (AFX)

Audio effects modify audio samples in real-time during mixing.

Importing AFX

from movielite import afx
from movielite import AudioClip

Fade Effects

FadeIn (Audio)

Gradually increases volume from 0 to the clip’s original volume.
audio = AudioClip("music.mp3", start=0, duration=30)
audio.add_effect(afx.FadeIn(duration=3.0))
# Fades in over first 3 seconds

FadeOut (Audio)

Gradually decreases volume to 0 at the end.
audio = AudioClip("music.mp3", start=0, duration=30)
audio.add_effect(afx.FadeOut(duration=5.0))
# Fades out over last 5 seconds
Source: src/movielite/afx/fade.py:1

Custom Audio Effects

Create custom audio effects by extending AudioEffect:
from movielite.afx.base import AudioEffect
from movielite.audio import AudioClip
import numpy as np

class Echo(AudioEffect):
    """Add simple echo effect."""
    
    def __init__(self, delay: float = 0.5, decay: float = 0.6):
        self.delay = delay  # seconds
        self.decay = decay  # 0.0 to 1.0
    
    def apply(self, clip: AudioClip) -> None:
        def echo_transform(samples: np.ndarray, t: float, sr: int) -> np.ndarray:
            delay_samples = int(self.delay * sr)
            
            if len(samples) < delay_samples:
                return samples
            
            result = samples.copy()
            # Add delayed version of audio
            result[delay_samples:] += samples[:-delay_samples] * self.decay
            
            return result
        
        clip.add_transform(echo_transform)

# Usage
audio = AudioClip("voice.mp3", start=0, duration=10)
audio.add_effect(Echo(delay=0.3, decay=0.5))
Source: src/movielite/afx/base.py:1

Transitions (VTX)

Transitions smoothly blend between two graphic clips. They require clips to overlap in time.

Importing VTX

from movielite import vtx
from movielite import VideoClip, ImageClip

CrossFade

Blends from one clip to another. The first clip fades out while the second fades in.
clip1 = VideoClip("video1.mp4", start=0, duration=10)
clip2 = VideoClip("video2.mp4", start=9, duration=10)

# Create 1-second crossfade
clip1.add_transition(clip2, vtx.CrossFade(duration=1.0))

# Clips must overlap by at least the transition duration
# clip1 ends at 10s, clip2 starts at 9s -> 1s overlap ✓
Transitions require clips to overlap. The overlap must be at least as long as the transition duration.

Audio Crossfade

If both clips are VideoClip instances with audio, CrossFade automatically applies crossfade to audio tracks:
video1 = VideoClip("clip1.mp4", start=0, duration=10)
video2 = VideoClip("clip2.mp4", start=9, duration=10)

video1.add_transition(video2, vtx.CrossFade(duration=1.0))
# Both video AND audio crossfade over 1 second
Source: src/movielite/vtx/crossfade.py:1

Transition Validation

Transitions validate clip ordering and overlap:
clip1 = VideoClip("video1.mp4", start=0, duration=5)
clip2 = VideoClip("video2.mp4", start=8, duration=5)  # Gap: 5s to 8s

clip1.add_transition(clip2, vtx.CrossFade(1.0))
# Raises ValueError: "Clips do not overlap"
Source: src/movielite/vtx/base.py:1

Effect Performance

Pixel Transforms

Use numba-compiled pixel transforms (add_pixel_transform) for color adjustments. They’re batched and executed in a single pass.

Frame Transforms

Frame transforms (add_transform) are applied sequentially. Each transform receives the output of the previous one.

Opacity Effects

Fade effects modify the opacity function, which is lightweight. Multiple fades can be combined efficiently.

Audio Effects

Audio effects process samples in chunks (10-second buffers), keeping memory usage low even for long audio.

Complete Example

from movielite import (
    VideoWriter, VideoClip, ImageClip, TextClip,
    vfx, afx, vtx, VideoQuality
)
from pictex import Canvas

# Create clips with effects
intro = VideoClip("intro.mp4", start=0, duration=5)
intro.add_effect(vfx.FadeIn(1.0)) \
     .add_effect(vfx.Brightness(1.1)) \
     .add_effect(vfx.Saturation(1.2))
intro.audio.add_effect(afx.FadeIn(1.0))

title = TextClip(
    "My Video",
    start=4.5,
    duration=3,
    canvas=Canvas().font_size(80).color("white")
)
title.set_position((100, 100))
title.add_effect(vfx.FadeIn(0.5)).add_effect(vfx.FadeOut(0.5))

main = VideoClip("main.mp4", start=7, duration=30)
main.add_effect(vfx.KenBurns(
    duration=30,
    start_scale=1.0,
    end_scale=1.2,
    start_position=(0, 0),
    end_position=(-50, -30)
))

outro = ImageClip("outro.png", start=36.5, duration=5)
outro.add_effect(vfx.FadeIn(0.5)) \
     .add_effect(vfx.Sepia(0.8)) \
     .add_effect(vfx.FadeOut(1.0))

# Add crossfade transitions
intro.add_transition(title, vtx.CrossFade(0.5))
main.add_transition(outro, vtx.CrossFade(0.5))

# Background music
music = AudioClip("music.mp3", start=0, duration=42)
music.set_volume(0.3)
music.add_effect(afx.FadeIn(2.0)).add_effect(afx.FadeOut(3.0))

# Compose
writer = VideoWriter("output.mp4", fps=30, size=(1920, 1080))
writer.add_clips([intro, title, main, outro, music])
writer.write(video_quality=VideoQuality.HIGH)

Clips

Learn about clip types and properties

Video Writer

Composing and rendering videos

Audio Mixing

Advanced audio techniques

Build docs developers (and LLMs) love