Skip to main content

shorts-caption-studio

The animated word-by-word captions — burned in locally, free, from your SRT.

43 tests passing Python 3.10, 3.11, 3.12 Version 1.0.0 Zero dependencies MIT license

Demo: a plain SRT styled into a per-word .ass, then burned into the bundled demo video — before/after frames from the real render — plus a broken-cue run whose skips are named, not hidden

Real runs on the bundled example: the plain SRT, the styled sidecar, frames from the actual render, and the honesty check on a broken SRT.

The captions that make Shorts hold attention — each word lighting up as it's spoken — are the headline feature of the paid caption tools. Submagic's Starter plan is $19/month for 15 videos (2 minutes each), and your unreleased video uploads to their cloud to get them (pricing: Facts this tool relies on). Underneath, the job is styling an SRT file and burning it into the video — and that core job runs fine on your own machine.

shorts-caption-studio does it locally. Give it a transcript (a Whisper SRT is fine) and a vertical video, and it renders the highlight captions in — three styles, pop, karaoke and clean — or hands you just the styled .ass sidecar for your own editor or pipeline. No upload, no subscription, no video quota. Your unreleased footage never leaves your machine.

Quick start

pipx install git+https://github.com/jasveerai88/shorts-caption-studio
# or: pip install git+https://github.com/jasveerai88/shorts-caption-studio

# from a clone, so the bundled examples are on disk:
python3 examples/make_examples.py   # once: generates examples/demo.mp4 (needs ffmpeg)
shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt
shorts-caption-studio v1.0.0 — render
Video: demo.mp4 (720x1280, 6.0s)
Captions: demo.srt — 3 cues, 12 words
Style: pop (big bold, active word in accent colour at 120% scale) · PlayRes 1080x1920
Word timing: ESTIMATED (cue duration split by word length — not true word alignment)
Rendered: examples/demo.captioned.mp4 (12 caption events burned in)
(exit code: 0)

Don't want the video touched? --ass-only emits just the styled sidecar — pure Python, no ffmpeg needed:

shorts-caption-studio --ass-only examples/demo.srt -o demo.pop.ass --style pop

What it does

Mode What you give it What you get On the bundled example
Burn-in (default) a video + its SRT a new .mp4 with the captions rendered in (input never overwritten) demo.mp4 + demo.srt → demo.captioned.mp4, 12 caption events
--ass-only an SRT a styled .ass sidecar for your own editor/renderer demo.srt → 12 events (pop) · 3 events (karaoke) · 3 events (clean)
Style Look How it renders
pop Big bold white words; the spoken word pops to accent yellow at 120% scale one event per word, active word wrapped in colour + scale tags
karaoke Each word fills from light grey to accent yellow as it's spoken one event per cue, per-word \kf sweep tags (centisecond durations)
clean Minimal plain white, no highlight, no animation one event per cue, MarginV 460 — bottom-third safe area

All styles are composed for vertical video (PlayRes 1080x1920) with margins that keep captions out of the Shorts UI zones — the bottom 380px where captions/CTAs sit, and the top 220px title area.

How it compares (the caption step only — the paid tools are full editors, see Honest limits):

shorts-caption-studio Submagic (Starter)
Price free, MIT $19/member/month billed monthly ($12 billed yearly) — 15 videos, 2 min each
Where your video goes nowhere — it never leaves your machine uploaded to their cloud
Transcription none — you bring an SRT built in (AI auto captions)
Word timing ESTIMATED split of cue timing, labelled as such word-level, from its transcription
Styles 3 (pop / karaoke / clean) template library
Scriptable CLI + --json + exit codes, runs offline web app / API on paid tiers

Prices read from Submagic's pricing page, checked 2026-11-04 — see Facts.

Reference: Honest limits · How burn-in works · Usage · Facts this tool relies on · For agents · Real runs · Tests · Known limits · Privacy · Contributing · Related

Honest limits (the short version)

  • It does not transcribe. There is no speech recognition in this tool. You bring an SRT transcript (Whisper, your editor's export, anything cue-level); if you don't have one yet, get one first — this tool starts where transcription ends.
  • Word timing is ESTIMATED — the tool's central honesty rule. Whisper-style SRTs carry timing per cue, not per word. The word timings inside a cue are therefore estimated: each cue's duration is split proportionally to word length (longer word = longer on screen). It looks like real word timing and works well for short, punchy cues — but it is not true word-level alignment, and every report (text and JSON) says so. For frame-accurate timing, run a word-level aligner upstream first.
  • SRT in, .ass or burned video out. It reads SRT only — no VTT, no SRT→VTT conversion, no caption-format converting at all. The sidecar it writes is ASS (the format the styling lives in), and the other output is the rendered video itself.
  • No timing repair, no retiming, no offset shifting. Cues are rendered at the times the SRT states. A cue that cannot be used — empty text, or end ≤ start — is skipped with a named warning and exit code 1 (see the messy run under Real runs); it is never silently dropped and never rendered wrong. If no cue is usable, that's an error (exit 2) and nothing is written.
  • Burn-in needs ffmpeg + ffprobe on your PATH and re-encodes the video (the audio stream is copied untouched). --ass-only is pure Python and works without them.
  • Vertical-first. Styles are composed for 1080x1920. Landscape input renders with a warning, but caption placement is not optimised for it.
  • One look per style. The font is Arial (libass falls back to a system sans where Arial is absent); there is no per-word font or colour customisation yet. Inline SRT tags (<i>, <b>) are stripped rather than converted to ASS styling.

How burn-in works

The tool probes the video with ffprobe (frame size, duration, audio), generates a styled .ass file for the chosen style, and renders through ffmpeg's subtitles filter pointed at that file. The explicit styled .ass is deliberate — the filter silently ignores force_style, so generators that rely on it ship unstyled captions without noticing.

The input video is never overwritten: --out pointing at it is refused. A failed render deletes any partial output — the tool never leaves a fake success behind. Script-friendly exit codes throughout: 0 = clean, 1 = done with warnings (skipped cues, non-vertical video, captions overrunning the video), 2 = error (clean ERROR: message, never a traceback).

Usage

You need Python 3.10+. Burn-in also needs ffmpeg (with ffprobe) on your PATH.

# 1. Burn captions into a video (writes demo.captioned.mp4 beside it)
python3 examples/make_examples.py   # once: generates examples/demo.mp4
shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt

# 2. Pick a style, choose the output name
shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt --style karaoke --out final.mp4

# 3. Or emit just the styled .ass sidecar (no ffmpeg needed)
shorts-caption-studio --ass-only examples/demo.srt -o demo.pop.ass --style pop

# 4. Broken-cue check: skipped cues are named, exit code 1
shorts-caption-studio --ass-only examples/messy.srt -o messy.pop.ass --style pop

From a checkout, with no install: python3 src/shorts_caption_studio.py … (Python 3.10+, standard library only).

Facts this tool relies on

Stated narrowly, with the one external comparison this README makes:

Fact Source
Submagic Starter: $19/member/month billed monthly, $12/member/month billed yearly; 45 credits = 15 videos/month, max 2 min per video, 1080p export, no watermark. Pro $39/mo (40 videos, 5 min), Business $69/mo (100 videos, 30 min) Submagic pricing page — submagic.co/pricing, read 2026-11-04

Everything else in this README is the tool's own behaviour on its bundled examples, reproduced verbatim under Real runs. What is deliberately not claimed anywhere in this project: that the estimated word split is accurate timing, or that three styles compete with a full editor's template library, transcription, B-roll and publishing features — they don't, and the comparison table above is scoped to the caption step for that reason.

For agents

Both modes take --json: one JSON object with the same counts, warnings and exit codes as the text report (errors come back as {"error": ...}, argparse usage errors included):

shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt --style karaoke --json
shorts-caption-studio --ass-only examples/demo.srt -o demo.ass --json

Common fields: tool ("shorts-caption-studio"), version, command ("render" / "ass-only"), style, cue_count, word_count, event_count, word_timing (always "estimated"), skipped_cues (list of {index, reason, detail}), warnings, output_file. render adds the ffprobe-measured width, height, duration_seconds, has_audio. The full schema and the agent honesty rules (estimated is not aligned; a skipped cue is a caption missing from the render) live in SKILL.md — read it before driving this tool.

Real runs (verbatim)

$ shorts-caption-studio --ass-only examples/demo.srt -o demo.pop.ass --style pop
shorts-caption-studio v1.0.0 — ass-only
Captions: demo.srt — 3 cues, 12 words
Style: pop (big bold, active word in accent colour at 120% scale) · PlayRes 1080x1920
Word timing: ESTIMATED (cue duration split by word length — not true word alignment)
ASS written: demo.pop.ass (12 events)
(exit code: 0)

The sidecar it wrote (first lines; note the active word carrying the accent colour and the 120% scale tags, moving word by word):

[Script Info]
Title: shorts-caption-studio — pop captions
ScriptType: v4.00+
PlayResX: 1080
PlayResY: 1920

[V4+ Styles]
Style: Pop,Arial,84,&H00FFFFFF,&H00FFFFFF,&H00000000,&H64000000,-1,0,0,0,100,100,0,0,1,6,2,2,60,60,420,1

Dialogue: 0,0:00:00.00,0:00:00.38,Pop,,0,0,0,,{\1c&H0000D5FF\fscx120\fscy120}This{\r} trick changes everything
Dialogue: 0,0:00:00.38,0:00:00.87,Pop,,0,0,0,,This {\1c&H0000D5FF\fscx120\fscy120}trick{\r} changes everything

The same cue in karaoke style — one event, \kf centisecond durations estimated per word (38 + 48 + 67 + 96 ≈ the 2.5s cue):

Dialogue: 0,0:00:00.00,0:00:02.50,Karaoke,,0,0,0,,{\kf38}This {\kf48}trick {\kf67}changes {\kf96}everything

A broken SRT (bundled as examples/messy.srt): cue 2 ends before it starts, cue 3 has no text. Both are skipped by name, the good cues still render, and the exit code says "done, with warnings":

$ shorts-caption-studio --ass-only examples/messy.srt -o messy.pop.ass --style pop
shorts-caption-studio v1.0.0 — ass-only
Captions: messy.srt — 2 cues, 11 words
Style: pop (big bold, active word in accent colour at 120% scale) · PlayRes 1080x1920
Word timing: ESTIMATED (cue duration split by word length — not true word alignment)
ASS written: messy.pop.ass (11 events)
  WARN: cue #2 skipped (invalid-timing: end 1.500s <= start 2.000s)
  WARN: cue #3 skipped (empty-text: cue has no text)
(exit code: 1)
$ shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt --style karaoke --json
{
  "tool": "shorts-caption-studio",
  "version": "1.0.0",
  "command": "render",
  "video": "demo.mp4",
  "path": "examples/demo.mp4",
  "captions": "demo.srt",
  "style": "karaoke",
  "output_file": "examples/demo.captioned.mp4",
  "width": 720,
  "height": 1280,
  "duration_seconds": 6.0,
  "has_audio": true,
  "cue_count": 3,
  "word_count": 12,
  "event_count": 3,
  "word_timing": "estimated",
  "skipped_cues": [],
  "warnings": []
}
(exit code: 0)
$ shorts-caption-studio missing.mp4 --captions examples/demo.srt
ERROR: file not found: missing.mp4
(exit code: 2)

Tests

43 tests, standard library only:

python3 -m unittest discover -s tests

The suite covers both directions: the word-split math (equal words split evenly, unequal words split proportionally, a single word takes the whole cue), ASS time formatting, escaping of {, } and \ (with a test proving commas survive verbatim in the text field), every style's UI-safe margins, pop's per-word highlight placement, karaoke's \kf durations summing to the cue, clean's plain single events, the parser's skipped-cue reporting, every CLI error path (clean ERROR:, exit 2, no traceback), the no-ffmpeg split (burn-in fails cleanly while --ass-only still works), JSON/text agreement — and a real ffmpeg integration test that generates a 3-second vertical video, burns captions in, and probes the output's size and duration. The demo GIF's after-frames are extracted from a real render of the bundled example, and the burned frame was pixel-checked against the source frame (solid caption zone before, thousands of bright caption pixels after).

Known limits

  • Word timing is estimated (see Honest limits). Accuracy degrades inside long cues; keep cues short — caption-toolkit's lint (too-long > 7s) is a good upstream gate.
  • Burn-in re-encodes the video (audio stream is copied untouched); expect ordinary re-encode quality behaviour.
  • Styles are composed for vertical 1080x1920. Landscape input renders with a warning, but caption placement is not optimised for it.
  • The font is Arial (libass falls back to a system sans where Arial is absent); there is no per-word font or colour customisation yet.
  • Inline SRT tags (<i>, <b>) are stripped rather than converted to ASS styling.

Privacy

What it touches: only the video and caption files you point it at (and the one output file it writes). What it stores: nothing. What it sends: nothing — there is no network code in this tool. Your unreleased video never leaves your machine; that is the point of the tool.

Contributing

Issues and pull requests are welcome — see CONTRIBUTING.md. The bar for this project: standard library only (ffmpeg stays an external tool, never a Python dependency), skipped cues stay reported, and estimated timing stays labelled estimated — in code, docs and output.

Who this is for

Shorts / TikTok / Reels creators who already have a transcript and want the animated-caption look without uploading unreleased footage to a subscription cloud — especially anyone posting daily, for whom a per-video quota is the wrong shape of pricing. The author's own use: it is the caption-styling step for his faceless Shorts channels (Leaked News and SADA — The Rooftop Catcher) — every video's Whisper SRT goes through it on the way to the published, burned-in captions.

Support

If this saves you a subscription month, consider sponsoring: github.com/sponsors/jasveerai88

Licence

MIT — see LICENSE.

Metadata

Release files for shorts-caption-studio 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for shorts-caption-studio 1.0.0
File Size Uploaded Provenance Actions
shorts_caption_studio-1.0.0.tar.gz 21.2 kB With provenance Details for shorts_caption_studio-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for shorts-caption-studio 1.0.0
File Interpreter ABI Platform Actions
shorts_caption_studio-1.0.0-py3-none-any.whl Python 3 none any Details for shorts_caption_studio-1.0.0-py3-none-any.whl

Total release size: 38.4 kB

Release files / shorts_caption_studio-1.0.0.tar.gz

Download URL shorts_caption_studio-1.0.0.tar.gz
Size 21.2 kB
Tags Source
SHA-256 checksum
How to use checksums
202cca3fcd75b6ebc8bb6b351945e9dbf567a1e414d0474517067b8cd122f3ae
BLAKE2b-256 checksum
How to use checksums
614a7334bf4dc53e64096aad6f09b3e71cd1d753d08cfd0ef3d8ebfb06c150ef
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Release files / shorts_caption_studio-1.0.0-py3-none-any.whl

Download URL shorts_caption_studio-1.0.0-py3-none-any.whl
Size 17.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
29eec091af41ec87e7ead3e6ad0b8343a444b67d18c3a85575a7755b1e4dfe45
BLAKE2b-256 checksum
How to use checksums
2c2549549960f7620001c87ae820470dd89e484ca659a1833ed876668facba7b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Security for shorts-caption-studio 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for shorts_caption_studio-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page