{"id":37227695,"url":"https://github.com/pulsence/caption-animator","last_synced_at":"2026-02-01T19:02:19.845Z","repository":{"id":331682074,"uuid":"1131748325","full_name":"pulsence/caption-animator","owner":"pulsence","description":"Render stylized SRT/ASS subtitles into video overlays (alpha) with preset-driven styling and plugin animations.","archived":false,"fork":false,"pushed_at":"2026-01-11T17:21:25.000Z","size":97,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-01-15T07:58:30.047Z","etag":null,"topics":["ass-subtitles","caption-generator","captions","ffmpeg","srt-subtitles","subtitles","video-overlay"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pulsence.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-10T16:11:46.000Z","updated_at":"2026-01-13T04:03:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pulsence/caption-animator","commit_stats":null,"previous_names":["pulsence/caption-animator"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/pulsence/caption-animator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pulsence%2Fcaption-animator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pulsence%2Fcaption-animator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pulsence%2Fcaption-animator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pulsence%2Fcaption-animator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pulsence","download_url":"https://codeload.github.com/pulsence/caption-animator/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pulsence%2Fcaption-animator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28986377,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T18:17:03.387Z","status":"ssl_error","status_checked_at":"2026-02-01T18:16:57.287Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ass-subtitles","caption-generator","captions","ffmpeg","srt-subtitles","subtitles","video-overlay"],"created_at":"2026-01-15T03:23:32.241Z","updated_at":"2026-02-01T19:02:19.840Z","avatar_url":"https://github.com/pulsence.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Caption Animator\n\nRender 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.\n\n## Caveat Emptor\nThis 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.\n\nI generated this tool primarily using an AI code assistant and so all the code branches are not\nexplored or tested, but they should be fairly correct.\n\n## Features\n- **SRT and ASS inputs** - ASS styling is preserved by default\n- **Preset-driven styling** - JSON/YAML presets for SRT conversion and optional ASS reskin\n- **Plugin-based animations** - Easy to add custom animations (fade, slide_up, scale_settle, blur_settle, word_reveal)\n- **Tight overlay sizing** - Minimal output video size based on caption bounds\n- **ProRes 4444 output** - Alpha channel for easy positioning in Resolve\n- **Interactive mode** - Tweak settings and re-render without restarting\n- **Programmatic API** - Use as Python library for custom workflows\n\n## Installation\n\n### From Source (Recommended for Development)\n```bash\npip install -e .\n```\n\nThis installs the `caption-animator` command globally.\n\n### Requirements\n- Python 3.9+\n- FFmpeg available on PATH\n- Dependencies: `pysubs2`, `Pillow`, `PyYAML` (installed automatically)\n\n## Quick Start\n\n### Command-Line Usage\n```bash\n# Render from SRT using built-in preset\ncaption-animator test.srt --preset modern_box --out overlay.mov\n\n# Render from SRT using custom preset file\ncaption-animator test.srt --preset presets/preset.json --out overlay.mov\n\n# Render from ASS (keeps existing styling unless you reskin)\ncaption-animator test.ass --out overlay.mov\n\n# Reskin an ASS with a preset and strip existing overrides\ncaption-animator test.ass --preset presets/preset.json --reskin --strip-overrides --out overlay.mov\n\n# Keep the intermediate ASS file\ncaption-animator test.srt --preset modern_box --keep-ass\n\n# Interactive mode for tweaking\ncaption-animator test.srt --interactive\n\n# List available presets\ncaption-animator --list-presets\n```\n\n### Python Module Usage\n```bash\n# Can also run as module\npython -m caption_animator test.srt --preset modern_box --out overlay.mov\n```\n\n### Programmatic API\n\n#### High-Level API (Recommended)\n```python\nfrom caption_animator import render_subtitle, RenderConfig\n\nresult = render_subtitle(\n    input_path=\"input.srt\",\n    output_path=\"output.mov\",\n    config=RenderConfig(preset=\"modern_box\", quality=\"large\"),\n    on_progress=lambda msg: print(msg)\n)\n\nif result.success:\n    print(f\"Rendered: {result.output_path}\")\n    print(f\"Size: {result.width}x{result.height}\")\n```\n\n**RenderConfig options:**\n- `preset` - Preset name or file path (default: \"modern_box\")\n- `fps` - Frame rate (default: \"30\")\n- `quality` - \"small\" (H.264), \"medium\" (ProRes 422 HQ), \"large\" (ProRes 4444)\n- `safety_scale` - Edge clipping margin (default: 1.12)\n- `apply_animation` - Enable/disable animation (default: True)\n- `reskin` - Apply preset style to ASS files (default: False)\n\n#### Lower-Level API (Advanced)\n```python\nfrom caption_animator import (\n    SubtitleFile, PresetLoader, AnimationRegistry,\n    SizeCalculator, StyleBuilder, FFmpegRenderer, EventEmitter\n)\nfrom pathlib import Path\n\n# 1. Load subtitle and preset\nsub = SubtitleFile.load(Path(\"input.srt\"))\npreset = PresetLoader().load(\"modern_box\")\n\n# 2. Build and apply style\nstyle_builder = StyleBuilder(preset)\nstyle = style_builder.build(\"Default\")\nsub.apply_style(style, preset, wrap_text=True)\n\n# 3. Apply animation\nif preset.animation:\n    animation = AnimationRegistry.create(\n        preset.animation.type,\n        preset.animation.params\n    )\n    sub.apply_animation(animation)\n\n# 4. Calculate size and positioning\nsize_calc = SizeCalculator(preset, safety_scale=1.12)\nsize = size_calc.compute_size(sub.subs)\nposition = size_calc.compute_anchor_position(size)\nsub.apply_center_positioning(position, size)\nsub.set_play_resolution(size)\n\n# 5. Save working ASS and render\nass_path = Path(\"work.ass\")\nsub.save(ass_path)\n\nemitter = EventEmitter()\nemitter.subscribe(lambda event: print(f\"[{event.event_type}] {event.message}\"))\n\nrenderer = FFmpegRenderer(emitter, show_progress=True, quality=\"large\")\nrenderer.render(ass_path, Path(\"output.mov\"), size, fps=\"30\", duration_sec=120.0)\n```\n\n## Presets and Animation Configuration\n\nPresets define fonts, colors, layout, and animations. They can be:\n- **Built-in preset name**: `modern_box` or `clean_outline`\n- **Single JSON/YAML preset file**: `presets/my_preset.json`\n- **Multi-preset file**: `path/to/presets.json:preset_name`\n\n### Built-in Presets\n- **modern_box** - Clean box style with slide-up animation\n- **clean_outline** - Outline style with fade animation\n\nAnimation settings live under the `animation` key in preset files.\n\nSee [libass documentation](https://github.com/libass/libass?tab=readme-ov-file) for more details on ASS styling.\n\n### Example Preset (JSON)\n```json\n{\n  \"font_file\": \"C:/Windows/Fonts/arialbd.ttf\",\n  \"font_name\": \"Arial\",\n  \"font_size\": 62,\n  \"primary_color\": \"#FFFFFF\",\n  \"outline_color\": \"#000000\",\n  \"outline_px\": 6,\n  \"padding\": [44, 70, 56, 70],\n  \"animation\": { \n    \"type\": \"slide_up\",\n    \"in_ms\": 140,\n    \"out_ms\": 120,\n    \"move_px\": 26\n  }\n}\n```\n\n### Built-in Animation Types\n\n| Animation | Parameters | Description |\n|-----------|-----------|-------------|\n| `fade` | `in_ms`, `out_ms` | Fade in/out effect |\n| `slide_up` | `in_ms`, `out_ms`, `move_px` | Slide up from below |\n| `scale_settle` | `in_ms`, `out_ms`, `start_scale`, `end_scale`, `accel` | Scale from large to normal |\n| `blur_settle` | `in_ms`, `out_ms`, `start_blur`, `end_blur`, `accel` | Blur to sharp transition |\n| `word_reveal` | `in_ms`, `out_ms`, `timing_mode`, `word_delay_ms` | Karaoke-style word-by-word reveal |\n\n### Creating Custom Animations\n\nThe plugin-based architecture makes adding animations trivial:\n\n```python\nfrom caption_animator.animations import BaseAnimation, AnimationRegistry\n\n@AnimationRegistry.register\nclass BounceAnimation(BaseAnimation):\n    animation_type = \"bounce\"\n\n    def validate_params(self):\n        # Validate required parameters\n        pass\n\n    def generate_ass_override(self, event_context=None):\n        return r\"\\bounce_tag\"\n\n    def apply_to_event(self, event, **kwargs):\n        event.text = self._inject_override(event.text, self.generate_ass_override())\n```\n\nSave as `src/caption_animator/animations/bounce.py` and it's automatically discovered!\n\n## Advanced Usage\n\n### Interactive Mode\n```bash\ncaption-animator test.srt --interactive\n```\n\nAllows tweaking preset values and re-rendering without restarting:\n```\n\u003e set font_size 72\n\u003e set animation.move_px 40\n\u003e render\n\u003e quit\n```\n\n### Command-Line Options\n\n| Option | Description |\n|--------|-------------|\n| `--list-presets` | List all available presets |\n| `--preset NAME` | Use built-in or file preset |\n| `--out PATH` | Output video path (default: `\u003cinput\u003e.mov`) |\n| `--fps FPS` | Framerate (default: 30) |\n| `--safety-scale N` | Multiplier to avoid edge clipping (default: 1.12) |\n| `--keep-ass` | Save intermediate ASS file |\n| `--interactive`, `-i` | Enter interactive mode |\n| `--reskin` | Apply preset style to ASS files |\n| `--strip-overrides` | Remove existing ASS tags when reskinning |\n| `--no-animation` | Disable animation injection |\n| `--quiet` | Suppress progress output |\n\nSee `caption-animator --help` for all options.\n\n## Architecture\n\nThe refactored architecture uses a plugin-based system:\n\n```\nsrc/caption_animator/\n├── animations/      # Plugin system - add animations here\n├── core/           # Config, styling, sizing, subtitle handling\n├── text/           # Text wrapping, measurement, ASS utilities\n├── rendering/      # FFmpeg integration with progress tracking\n├── presets/        # Preset loading and built-in presets\n├── cli/            # Command-line interface and interactive mode\n└── utils/          # File utilities\n```\n\n## Development\n\n### Install in Development Mode\n```bash\npip install -e .\n```\n\n### Run Tests\n```bash\n# End-to-end test\ncaption-animator test.srt --preset modern_box --out test_output.mov\n\n# With custom preset\ncaption-animator test.srt --preset presets/word_highlight.json --out test_output.mov\n\n# Interactive mode test\ncaption-animator test.srt --interactive\n```\n\n### Code Quality\n```bash\n# Format code\nblack src/\n\n# Lint\nruff check src/\n\n# Type check\nmypy src/caption_animator\n```\n\n## Notes\n- For deterministic sizing, prefer presets with `font_file` pointing to a TTF/OTF file\n- ASS inputs require a preset for sizing unless you use `--reskin` or explicitly pass `--preset`\n- The overlay size is computed once per subtitle file; use `--safety-scale` if you see edge clipping\n- Multi-line subtitles automatically use `\\N` escape sequences for proper ASS rendering\n\n\n## License\nSee `LICENSE`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpulsence%2Fcaption-animator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpulsence%2Fcaption-animator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpulsence%2Fcaption-animator/lists"}