Skip to content

Render stylized SRT/ASS subtitles into video overlays (alpha) with preset-driven styling and plugin animations.

License

Notifications You must be signed in to change notification settings

pulsence/caption-animator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Caption Animator

Render stylized subtitle overlays (SRT/ASS) into a transparent video (ProRes 4444 with alpha) for DaVinci Resolve. The tool sizes a tight overlay canvas based on the maximum caption bounds, then renders the subtitles via FFmpeg/libass.

Caveat Emptor

This project was primarily created for my personal use. I will not be responding to pull requests or issues unless they directly impact my use cases.

I generated this tool primarily using an AI code assistant and so all the code branches are not explored or tested, but they should be fairly correct.

Features

  • SRT and ASS inputs - ASS styling is preserved by default
  • Preset-driven styling - JSON/YAML presets for SRT conversion and optional ASS reskin
  • Plugin-based animations - Easy to add custom animations (fade, slide_up, scale_settle, blur_settle, word_reveal)
  • Tight overlay sizing - Minimal output video size based on caption bounds
  • ProRes 4444 output - Alpha channel for easy positioning in Resolve
  • Interactive mode - Tweak settings and re-render without restarting
  • Programmatic API - Use as Python library for custom workflows

Installation

From Source (Recommended for Development)

pip install -e .

This installs the caption-animator command globally.

Requirements

  • Python 3.9+
  • FFmpeg available on PATH
  • Dependencies: pysubs2, Pillow, PyYAML (installed automatically)

Quick Start

Command-Line Usage

# Render from SRT using built-in preset
caption-animator test.srt --preset modern_box --out overlay.mov

# Render from SRT using custom preset file
caption-animator test.srt --preset presets/preset.json --out overlay.mov

# Render from ASS (keeps existing styling unless you reskin)
caption-animator test.ass --out overlay.mov

# Reskin an ASS with a preset and strip existing overrides
caption-animator test.ass --preset presets/preset.json --reskin --strip-overrides --out overlay.mov

# Keep the intermediate ASS file
caption-animator test.srt --preset modern_box --keep-ass

# Interactive mode for tweaking
caption-animator test.srt --interactive

# List available presets
caption-animator --list-presets

Python Module Usage

# Can also run as module
python -m caption_animator test.srt --preset modern_box --out overlay.mov

Programmatic API

High-Level API (Recommended)

from caption_animator import render_subtitle, RenderConfig

result = render_subtitle(
    input_path="input.srt",
    output_path="output.mov",
    config=RenderConfig(preset="modern_box", quality="large"),
    on_progress=lambda msg: print(msg)
)

if result.success:
    print(f"Rendered: {result.output_path}")
    print(f"Size: {result.width}x{result.height}")

RenderConfig options:

  • preset - Preset name or file path (default: "modern_box")
  • fps - Frame rate (default: "30")
  • quality - "small" (H.264), "medium" (ProRes 422 HQ), "large" (ProRes 4444)
  • safety_scale - Edge clipping margin (default: 1.12)
  • apply_animation - Enable/disable animation (default: True)
  • reskin - Apply preset style to ASS files (default: False)

Lower-Level API (Advanced)

from caption_animator import (
    SubtitleFile, PresetLoader, AnimationRegistry,
    SizeCalculator, StyleBuilder, FFmpegRenderer, EventEmitter
)
from pathlib import Path

# 1. Load subtitle and preset
sub = SubtitleFile.load(Path("input.srt"))
preset = PresetLoader().load("modern_box")

# 2. Build and apply style
style_builder = StyleBuilder(preset)
style = style_builder.build("Default")
sub.apply_style(style, preset, wrap_text=True)

# 3. Apply animation
if preset.animation:
    animation = AnimationRegistry.create(
        preset.animation.type,
        preset.animation.params
    )
    sub.apply_animation(animation)

# 4. Calculate size and positioning
size_calc = SizeCalculator(preset, safety_scale=1.12)
size = size_calc.compute_size(sub.subs)
position = size_calc.compute_anchor_position(size)
sub.apply_center_positioning(position, size)
sub.set_play_resolution(size)

# 5. Save working ASS and render
ass_path = Path("work.ass")
sub.save(ass_path)

emitter = EventEmitter()
emitter.subscribe(lambda event: print(f"[{event.event_type}] {event.message}"))

renderer = FFmpegRenderer(emitter, show_progress=True, quality="large")
renderer.render(ass_path, Path("output.mov"), size, fps="30", duration_sec=120.0)

Presets and Animation Configuration

Presets define fonts, colors, layout, and animations. They can be:

  • Built-in preset name: modern_box or clean_outline
  • Single JSON/YAML preset file: presets/my_preset.json
  • Multi-preset file: path/to/presets.json:preset_name

Built-in Presets

  • modern_box - Clean box style with slide-up animation
  • clean_outline - Outline style with fade animation

Animation settings live under the animation key in preset files.

See libass documentation for more details on ASS styling.

Example Preset (JSON)

{
  "font_file": "C:/Windows/Fonts/arialbd.ttf",
  "font_name": "Arial",
  "font_size": 62,
  "primary_color": "#FFFFFF",
  "outline_color": "#000000",
  "outline_px": 6,
  "padding": [44, 70, 56, 70],
  "animation": { 
    "type": "slide_up",
    "in_ms": 140,
    "out_ms": 120,
    "move_px": 26
  }
}

Built-in Animation Types

Animation Parameters Description
fade in_ms, out_ms Fade in/out effect
slide_up in_ms, out_ms, move_px Slide up from below
scale_settle in_ms, out_ms, start_scale, end_scale, accel Scale from large to normal
blur_settle in_ms, out_ms, start_blur, end_blur, accel Blur to sharp transition
word_reveal in_ms, out_ms, timing_mode, word_delay_ms Karaoke-style word-by-word reveal

Creating Custom Animations

The plugin-based architecture makes adding animations trivial:

from caption_animator.animations import BaseAnimation, AnimationRegistry

@AnimationRegistry.register
class BounceAnimation(BaseAnimation):
    animation_type = "bounce"

    def validate_params(self):
        # Validate required parameters
        pass

    def generate_ass_override(self, event_context=None):
        return r"\bounce_tag"

    def apply_to_event(self, event, **kwargs):
        event.text = self._inject_override(event.text, self.generate_ass_override())

Save as src/caption_animator/animations/bounce.py and it's automatically discovered!

Advanced Usage

Interactive Mode

caption-animator test.srt --interactive

Allows tweaking preset values and re-rendering without restarting:

> set font_size 72
> set animation.move_px 40
> render
> quit

Command-Line Options

Option Description
--list-presets List all available presets
--preset NAME Use built-in or file preset
--out PATH Output video path (default: <input>.mov)
--fps FPS Framerate (default: 30)
--safety-scale N Multiplier to avoid edge clipping (default: 1.12)
--keep-ass Save intermediate ASS file
--interactive, -i Enter interactive mode
--reskin Apply preset style to ASS files
--strip-overrides Remove existing ASS tags when reskinning
--no-animation Disable animation injection
--quiet Suppress progress output

See caption-animator --help for all options.

Architecture

The refactored architecture uses a plugin-based system:

src/caption_animator/
├── animations/      # Plugin system - add animations here
├── core/           # Config, styling, sizing, subtitle handling
├── text/           # Text wrapping, measurement, ASS utilities
├── rendering/      # FFmpeg integration with progress tracking
├── presets/        # Preset loading and built-in presets
├── cli/            # Command-line interface and interactive mode
└── utils/          # File utilities

Development

Install in Development Mode

pip install -e .

Run Tests

# End-to-end test
caption-animator test.srt --preset modern_box --out test_output.mov

# With custom preset
caption-animator test.srt --preset presets/word_highlight.json --out test_output.mov

# Interactive mode test
caption-animator test.srt --interactive

Code Quality

# Format code
black src/

# Lint
ruff check src/

# Type check
mypy src/caption_animator

Notes

  • For deterministic sizing, prefer presets with font_file pointing to a TTF/OTF file
  • ASS inputs require a preset for sizing unless you use --reskin or explicitly pass --preset
  • The overlay size is computed once per subtitle file; use --safety-scale if you see edge clipping
  • Multi-line subtitles automatically use \N escape sequences for proper ASS rendering

License

See LICENSE.

About

Render stylized SRT/ASS subtitles into video overlays (alpha) with preset-driven styling and plugin animations.

Topics

Resources

License

Stars

Watchers

Forks

Languages