shorts-caption-studio 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- shorts_caption_studio-1.0.0/LICENSE +21 -0
- shorts_caption_studio-1.0.0/PKG-INFO +285 -0
- shorts_caption_studio-1.0.0/README.md +259 -0
- shorts_caption_studio-1.0.0/pyproject.toml +56 -0
- shorts_caption_studio-1.0.0/setup.cfg +4 -0
- shorts_caption_studio-1.0.0/src/shorts_caption_studio.egg-info/PKG-INFO +285 -0
- shorts_caption_studio-1.0.0/src/shorts_caption_studio.egg-info/SOURCES.txt +10 -0
- shorts_caption_studio-1.0.0/src/shorts_caption_studio.egg-info/dependency_links.txt +1 -0
- shorts_caption_studio-1.0.0/src/shorts_caption_studio.egg-info/entry_points.txt +2 -0
- shorts_caption_studio-1.0.0/src/shorts_caption_studio.egg-info/top_level.txt +1 -0
- shorts_caption_studio-1.0.0/src/shorts_caption_studio.py +615 -0
- shorts_caption_studio-1.0.0/tests/test_shorts_caption_studio.py +500 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jasveer Singh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shorts-caption-studio
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Add animated word-by-word captions to YouTube Shorts, TikTok and Reels: burn pop, karaoke or clean highlight captions from your own SRT with ffmpeg, or export the styled .ass file. Free, local, pure Python.
|
|
5
|
+
Author: Jasveer Singh
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jasveerai88/shorts-caption-studio
|
|
8
|
+
Project-URL: Repository, https://github.com/jasveerai88/shorts-caption-studio
|
|
9
|
+
Project-URL: Issues, https://github.com/jasveerai88/shorts-caption-studio/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/jasveerai88/shorts-caption-studio/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: captions,subtitles,shorts,tiktok,reels,srt,ass,ffmpeg,cli
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Multimedia :: Video
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
<h1 align="center">shorts-caption-studio</h1>
|
|
28
|
+
|
|
29
|
+
<p align="center"><strong>The animated word-by-word captions — burned in locally, free, from your SRT.</strong></p>
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<img src="https://img.shields.io/badge/tests-43%20passing-brightgreen" alt="43 tests passing">
|
|
33
|
+
<img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue" alt="Python 3.10, 3.11, 3.12">
|
|
34
|
+
<img src="https://img.shields.io/badge/version-1.0.0-blue" alt="Version 1.0.0">
|
|
35
|
+
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero dependencies">
|
|
36
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="MIT license">
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
<p align="center"><em>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.</em></p>
|
|
42
|
+
|
|
43
|
+
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](#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.
|
|
44
|
+
|
|
45
|
+
`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.
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pipx install git+https://github.com/jasveerai88/shorts-caption-studio
|
|
51
|
+
# or: pip install git+https://github.com/jasveerai88/shorts-caption-studio
|
|
52
|
+
|
|
53
|
+
# from a clone, so the bundled examples are on disk:
|
|
54
|
+
python3 examples/make_examples.py # once: generates examples/demo.mp4 (needs ffmpeg)
|
|
55
|
+
shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
shorts-caption-studio v1.0.0 — render
|
|
60
|
+
Video: demo.mp4 (720x1280, 6.0s)
|
|
61
|
+
Captions: demo.srt — 3 cues, 12 words
|
|
62
|
+
Style: pop (big bold, active word in accent colour at 120% scale) · PlayRes 1080x1920
|
|
63
|
+
Word timing: ESTIMATED (cue duration split by word length — not true word alignment)
|
|
64
|
+
Rendered: examples/demo.captioned.mp4 (12 caption events burned in)
|
|
65
|
+
(exit code: 0)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Don't want the video touched? `--ass-only` emits just the styled sidecar — pure Python, no ffmpeg needed:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
shorts-caption-studio --ass-only examples/demo.srt -o demo.pop.ass --style pop
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## What it does
|
|
75
|
+
|
|
76
|
+
| Mode | What you give it | What you get | On the bundled example |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| 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 |
|
|
79
|
+
| `--ass-only` | an SRT | a styled `.ass` sidecar for your own editor/renderer | `demo.srt` → 12 events (`pop`) · 3 events (`karaoke`) · 3 events (`clean`) |
|
|
80
|
+
|
|
81
|
+
| Style | Look | How it renders |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `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 |
|
|
84
|
+
| `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) |
|
|
85
|
+
| `clean` | Minimal plain white, no highlight, no animation | one event per cue, MarginV 460 — bottom-third safe area |
|
|
86
|
+
|
|
87
|
+
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.
|
|
88
|
+
|
|
89
|
+
**How it compares** (the caption step only — the paid tools are full editors, see Honest limits):
|
|
90
|
+
|
|
91
|
+
| | shorts-caption-studio | Submagic (Starter) |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| Price | free, MIT | $19/member/month billed monthly ($12 billed yearly) — 15 videos, 2 min each |
|
|
94
|
+
| Where your video goes | nowhere — it never leaves your machine | uploaded to their cloud |
|
|
95
|
+
| Transcription | **none — you bring an SRT** | built in (AI auto captions) |
|
|
96
|
+
| Word timing | ESTIMATED split of cue timing, labelled as such | word-level, from its transcription |
|
|
97
|
+
| Styles | 3 (pop / karaoke / clean) | template library |
|
|
98
|
+
| Scriptable | CLI + `--json` + exit codes, runs offline | web app / API on paid tiers |
|
|
99
|
+
|
|
100
|
+
Prices read from Submagic's pricing page, checked 2026-11-04 — see [Facts](#facts-this-tool-relies-on).
|
|
101
|
+
|
|
102
|
+
**Reference:** [Honest limits](#honest-limits-the-short-version) · [How burn-in works](#how-burn-in-works) · [Usage](#usage) · [Facts this tool relies on](#facts-this-tool-relies-on) · [For agents](#for-agents) · [Real runs](#real-runs-verbatim) · [Tests](#tests) · [Known limits](#known-limits) · [Privacy](#privacy) · [Contributing](#contributing) · [Related](#related)
|
|
103
|
+
|
|
104
|
+
## Honest limits (the short version)
|
|
105
|
+
|
|
106
|
+
- **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.
|
|
107
|
+
- **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.
|
|
108
|
+
- **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.
|
|
109
|
+
- **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](#real-runs-verbatim)); it is never silently dropped and never rendered wrong. If *no* cue is usable, that's an error (exit 2) and nothing is written.
|
|
110
|
+
- **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.
|
|
111
|
+
- **Vertical-first.** Styles are composed for 1080x1920. Landscape input renders with a warning, but caption placement is not optimised for it.
|
|
112
|
+
- **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.
|
|
113
|
+
|
|
114
|
+
## How burn-in works
|
|
115
|
+
|
|
116
|
+
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.
|
|
117
|
+
|
|
118
|
+
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).
|
|
119
|
+
|
|
120
|
+
## Usage
|
|
121
|
+
|
|
122
|
+
You need Python 3.10+. Burn-in also needs [ffmpeg](https://ffmpeg.org) (with ffprobe) on your PATH.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# 1. Burn captions into a video (writes demo.captioned.mp4 beside it)
|
|
126
|
+
python3 examples/make_examples.py # once: generates examples/demo.mp4
|
|
127
|
+
shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt
|
|
128
|
+
|
|
129
|
+
# 2. Pick a style, choose the output name
|
|
130
|
+
shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt --style karaoke --out final.mp4
|
|
131
|
+
|
|
132
|
+
# 3. Or emit just the styled .ass sidecar (no ffmpeg needed)
|
|
133
|
+
shorts-caption-studio --ass-only examples/demo.srt -o demo.pop.ass --style pop
|
|
134
|
+
|
|
135
|
+
# 4. Broken-cue check: skipped cues are named, exit code 1
|
|
136
|
+
shorts-caption-studio --ass-only examples/messy.srt -o messy.pop.ass --style pop
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
From a checkout, with no install: `python3 src/shorts_caption_studio.py …` (Python 3.10+, standard library only).
|
|
140
|
+
|
|
141
|
+
## Facts this tool relies on
|
|
142
|
+
|
|
143
|
+
Stated narrowly, with the one external comparison this README makes:
|
|
144
|
+
|
|
145
|
+
| Fact | Source |
|
|
146
|
+
|---|---|
|
|
147
|
+
| 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 |
|
|
148
|
+
|
|
149
|
+
Everything else in this README is the tool's own behaviour on its bundled examples, reproduced verbatim under [Real runs](#real-runs-verbatim). 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.
|
|
150
|
+
|
|
151
|
+
## For agents
|
|
152
|
+
|
|
153
|
+
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):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt --style karaoke --json
|
|
157
|
+
shorts-caption-studio --ass-only examples/demo.srt -o demo.ass --json
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
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`](SKILL.md) — read it before driving this tool.
|
|
161
|
+
|
|
162
|
+
## Real runs (verbatim)
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
$ shorts-caption-studio --ass-only examples/demo.srt -o demo.pop.ass --style pop
|
|
166
|
+
shorts-caption-studio v1.0.0 — ass-only
|
|
167
|
+
Captions: demo.srt — 3 cues, 12 words
|
|
168
|
+
Style: pop (big bold, active word in accent colour at 120% scale) · PlayRes 1080x1920
|
|
169
|
+
Word timing: ESTIMATED (cue duration split by word length — not true word alignment)
|
|
170
|
+
ASS written: demo.pop.ass (12 events)
|
|
171
|
+
(exit code: 0)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The sidecar it wrote (first lines; note the active word carrying the accent colour and the 120% scale tags, moving word by word):
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
[Script Info]
|
|
178
|
+
Title: shorts-caption-studio — pop captions
|
|
179
|
+
ScriptType: v4.00+
|
|
180
|
+
PlayResX: 1080
|
|
181
|
+
PlayResY: 1920
|
|
182
|
+
|
|
183
|
+
[V4+ Styles]
|
|
184
|
+
Style: Pop,Arial,84,&H00FFFFFF,&H00FFFFFF,&H00000000,&H64000000,-1,0,0,0,100,100,0,0,1,6,2,2,60,60,420,1
|
|
185
|
+
|
|
186
|
+
Dialogue: 0,0:00:00.00,0:00:00.38,Pop,,0,0,0,,{\1c&H0000D5FF\fscx120\fscy120}This{\r} trick changes everything
|
|
187
|
+
Dialogue: 0,0:00:00.38,0:00:00.87,Pop,,0,0,0,,This {\1c&H0000D5FF\fscx120\fscy120}trick{\r} changes everything
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The same cue in `karaoke` style — one event, `\kf` centisecond durations estimated per word (38 + 48 + 67 + 96 ≈ the 2.5s cue):
|
|
191
|
+
|
|
192
|
+
```text
|
|
193
|
+
Dialogue: 0,0:00:00.00,0:00:02.50,Karaoke,,0,0,0,,{\kf38}This {\kf48}trick {\kf67}changes {\kf96}everything
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
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":
|
|
197
|
+
|
|
198
|
+
```text
|
|
199
|
+
$ shorts-caption-studio --ass-only examples/messy.srt -o messy.pop.ass --style pop
|
|
200
|
+
shorts-caption-studio v1.0.0 — ass-only
|
|
201
|
+
Captions: messy.srt — 2 cues, 11 words
|
|
202
|
+
Style: pop (big bold, active word in accent colour at 120% scale) · PlayRes 1080x1920
|
|
203
|
+
Word timing: ESTIMATED (cue duration split by word length — not true word alignment)
|
|
204
|
+
ASS written: messy.pop.ass (11 events)
|
|
205
|
+
WARN: cue #2 skipped (invalid-timing: end 1.500s <= start 2.000s)
|
|
206
|
+
WARN: cue #3 skipped (empty-text: cue has no text)
|
|
207
|
+
(exit code: 1)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```text
|
|
211
|
+
$ shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt --style karaoke --json
|
|
212
|
+
{
|
|
213
|
+
"tool": "shorts-caption-studio",
|
|
214
|
+
"version": "1.0.0",
|
|
215
|
+
"command": "render",
|
|
216
|
+
"video": "demo.mp4",
|
|
217
|
+
"path": "examples/demo.mp4",
|
|
218
|
+
"captions": "demo.srt",
|
|
219
|
+
"style": "karaoke",
|
|
220
|
+
"output_file": "examples/demo.captioned.mp4",
|
|
221
|
+
"width": 720,
|
|
222
|
+
"height": 1280,
|
|
223
|
+
"duration_seconds": 6.0,
|
|
224
|
+
"has_audio": true,
|
|
225
|
+
"cue_count": 3,
|
|
226
|
+
"word_count": 12,
|
|
227
|
+
"event_count": 3,
|
|
228
|
+
"word_timing": "estimated",
|
|
229
|
+
"skipped_cues": [],
|
|
230
|
+
"warnings": []
|
|
231
|
+
}
|
|
232
|
+
(exit code: 0)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
```text
|
|
236
|
+
$ shorts-caption-studio missing.mp4 --captions examples/demo.srt
|
|
237
|
+
ERROR: file not found: missing.mp4
|
|
238
|
+
(exit code: 2)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Tests
|
|
242
|
+
|
|
243
|
+
43 tests, standard library only:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
python3 -m unittest discover -s tests
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
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).
|
|
250
|
+
|
|
251
|
+
## Known limits
|
|
252
|
+
|
|
253
|
+
- **Word timing is estimated** (see [Honest limits](#honest-limits-the-short-version)). Accuracy degrades inside long cues; keep cues short — caption-toolkit's `lint` (too-long > 7s) is a good upstream gate.
|
|
254
|
+
- Burn-in **re-encodes** the video (audio stream is copied untouched); expect ordinary re-encode quality behaviour.
|
|
255
|
+
- Styles are composed for vertical 1080x1920. Landscape input renders with a warning, but caption placement is not optimised for it.
|
|
256
|
+
- 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.
|
|
257
|
+
- Inline SRT tags (`<i>`, `<b>`) are stripped rather than converted to ASS styling.
|
|
258
|
+
|
|
259
|
+
## Privacy
|
|
260
|
+
|
|
261
|
+
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.
|
|
262
|
+
|
|
263
|
+
## Contributing
|
|
264
|
+
|
|
265
|
+
Issues and pull requests are welcome — see [CONTRIBUTING.md](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.
|
|
266
|
+
|
|
267
|
+
## Related
|
|
268
|
+
|
|
269
|
+
- [caption-toolkit](https://github.com/jasveerai88/caption-toolkit) — the step before this one: check, lint and fix the SRT itself (reading speed, cue structure) before you style it. Studio renders; toolkit judges the words.
|
|
270
|
+
- [shorts-packager](https://github.com/jasveerai88/shorts-packager) — package the finished Short for upload once the captions are burned in.
|
|
271
|
+
- [faceless-qc-lite](https://github.com/jasveerai88/faceless-qc-lite) — QC the finished video before it ships.
|
|
272
|
+
- [skill-vet](https://github.com/jasveerai88/skill-vet) · [permission-audit](https://github.com/jasveerai88/permission-audit) · [worktree-janitor](https://github.com/jasveerai88/worktree-janitor) · [done-claim-auditor](https://github.com/jasveerai88/done-claim-auditor) — the agent-safety side of the same toolkit.
|
|
273
|
+
- [gst-hst-tracker](https://github.com/jasveerai88/gst-hst-tracker) · [seller-profit-truth](https://github.com/jasveerai88/seller-profit-truth) — the money-truth pair from the same author.
|
|
274
|
+
|
|
275
|
+
## Who this is for
|
|
276
|
+
|
|
277
|
+
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.
|
|
278
|
+
|
|
279
|
+
## Support
|
|
280
|
+
|
|
281
|
+
If this saves you a subscription month, consider sponsoring: **[github.com/sponsors/jasveerai88](https://github.com/sponsors/jasveerai88)**
|
|
282
|
+
|
|
283
|
+
## Licence
|
|
284
|
+
|
|
285
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
<h1 align="center">shorts-caption-studio</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center"><strong>The animated word-by-word captions — burned in locally, free, from your SRT.</strong></p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="https://img.shields.io/badge/tests-43%20passing-brightgreen" alt="43 tests passing">
|
|
7
|
+
<img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue" alt="Python 3.10, 3.11, 3.12">
|
|
8
|
+
<img src="https://img.shields.io/badge/version-1.0.0-blue" alt="Version 1.0.0">
|
|
9
|
+
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero dependencies">
|
|
10
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="MIT license">
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
<p align="center"><em>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.</em></p>
|
|
16
|
+
|
|
17
|
+
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](#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.
|
|
18
|
+
|
|
19
|
+
`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.
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pipx install git+https://github.com/jasveerai88/shorts-caption-studio
|
|
25
|
+
# or: pip install git+https://github.com/jasveerai88/shorts-caption-studio
|
|
26
|
+
|
|
27
|
+
# from a clone, so the bundled examples are on disk:
|
|
28
|
+
python3 examples/make_examples.py # once: generates examples/demo.mp4 (needs ffmpeg)
|
|
29
|
+
shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
shorts-caption-studio v1.0.0 — render
|
|
34
|
+
Video: demo.mp4 (720x1280, 6.0s)
|
|
35
|
+
Captions: demo.srt — 3 cues, 12 words
|
|
36
|
+
Style: pop (big bold, active word in accent colour at 120% scale) · PlayRes 1080x1920
|
|
37
|
+
Word timing: ESTIMATED (cue duration split by word length — not true word alignment)
|
|
38
|
+
Rendered: examples/demo.captioned.mp4 (12 caption events burned in)
|
|
39
|
+
(exit code: 0)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Don't want the video touched? `--ass-only` emits just the styled sidecar — pure Python, no ffmpeg needed:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
shorts-caption-studio --ass-only examples/demo.srt -o demo.pop.ass --style pop
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## What it does
|
|
49
|
+
|
|
50
|
+
| Mode | What you give it | What you get | On the bundled example |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| 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 |
|
|
53
|
+
| `--ass-only` | an SRT | a styled `.ass` sidecar for your own editor/renderer | `demo.srt` → 12 events (`pop`) · 3 events (`karaoke`) · 3 events (`clean`) |
|
|
54
|
+
|
|
55
|
+
| Style | Look | How it renders |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `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 |
|
|
58
|
+
| `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) |
|
|
59
|
+
| `clean` | Minimal plain white, no highlight, no animation | one event per cue, MarginV 460 — bottom-third safe area |
|
|
60
|
+
|
|
61
|
+
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.
|
|
62
|
+
|
|
63
|
+
**How it compares** (the caption step only — the paid tools are full editors, see Honest limits):
|
|
64
|
+
|
|
65
|
+
| | shorts-caption-studio | Submagic (Starter) |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| Price | free, MIT | $19/member/month billed monthly ($12 billed yearly) — 15 videos, 2 min each |
|
|
68
|
+
| Where your video goes | nowhere — it never leaves your machine | uploaded to their cloud |
|
|
69
|
+
| Transcription | **none — you bring an SRT** | built in (AI auto captions) |
|
|
70
|
+
| Word timing | ESTIMATED split of cue timing, labelled as such | word-level, from its transcription |
|
|
71
|
+
| Styles | 3 (pop / karaoke / clean) | template library |
|
|
72
|
+
| Scriptable | CLI + `--json` + exit codes, runs offline | web app / API on paid tiers |
|
|
73
|
+
|
|
74
|
+
Prices read from Submagic's pricing page, checked 2026-11-04 — see [Facts](#facts-this-tool-relies-on).
|
|
75
|
+
|
|
76
|
+
**Reference:** [Honest limits](#honest-limits-the-short-version) · [How burn-in works](#how-burn-in-works) · [Usage](#usage) · [Facts this tool relies on](#facts-this-tool-relies-on) · [For agents](#for-agents) · [Real runs](#real-runs-verbatim) · [Tests](#tests) · [Known limits](#known-limits) · [Privacy](#privacy) · [Contributing](#contributing) · [Related](#related)
|
|
77
|
+
|
|
78
|
+
## Honest limits (the short version)
|
|
79
|
+
|
|
80
|
+
- **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.
|
|
81
|
+
- **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.
|
|
82
|
+
- **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.
|
|
83
|
+
- **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](#real-runs-verbatim)); it is never silently dropped and never rendered wrong. If *no* cue is usable, that's an error (exit 2) and nothing is written.
|
|
84
|
+
- **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.
|
|
85
|
+
- **Vertical-first.** Styles are composed for 1080x1920. Landscape input renders with a warning, but caption placement is not optimised for it.
|
|
86
|
+
- **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.
|
|
87
|
+
|
|
88
|
+
## How burn-in works
|
|
89
|
+
|
|
90
|
+
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.
|
|
91
|
+
|
|
92
|
+
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).
|
|
93
|
+
|
|
94
|
+
## Usage
|
|
95
|
+
|
|
96
|
+
You need Python 3.10+. Burn-in also needs [ffmpeg](https://ffmpeg.org) (with ffprobe) on your PATH.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# 1. Burn captions into a video (writes demo.captioned.mp4 beside it)
|
|
100
|
+
python3 examples/make_examples.py # once: generates examples/demo.mp4
|
|
101
|
+
shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt
|
|
102
|
+
|
|
103
|
+
# 2. Pick a style, choose the output name
|
|
104
|
+
shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt --style karaoke --out final.mp4
|
|
105
|
+
|
|
106
|
+
# 3. Or emit just the styled .ass sidecar (no ffmpeg needed)
|
|
107
|
+
shorts-caption-studio --ass-only examples/demo.srt -o demo.pop.ass --style pop
|
|
108
|
+
|
|
109
|
+
# 4. Broken-cue check: skipped cues are named, exit code 1
|
|
110
|
+
shorts-caption-studio --ass-only examples/messy.srt -o messy.pop.ass --style pop
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
From a checkout, with no install: `python3 src/shorts_caption_studio.py …` (Python 3.10+, standard library only).
|
|
114
|
+
|
|
115
|
+
## Facts this tool relies on
|
|
116
|
+
|
|
117
|
+
Stated narrowly, with the one external comparison this README makes:
|
|
118
|
+
|
|
119
|
+
| Fact | Source |
|
|
120
|
+
|---|---|
|
|
121
|
+
| 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 |
|
|
122
|
+
|
|
123
|
+
Everything else in this README is the tool's own behaviour on its bundled examples, reproduced verbatim under [Real runs](#real-runs-verbatim). 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.
|
|
124
|
+
|
|
125
|
+
## For agents
|
|
126
|
+
|
|
127
|
+
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):
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt --style karaoke --json
|
|
131
|
+
shorts-caption-studio --ass-only examples/demo.srt -o demo.ass --json
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
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`](SKILL.md) — read it before driving this tool.
|
|
135
|
+
|
|
136
|
+
## Real runs (verbatim)
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
$ shorts-caption-studio --ass-only examples/demo.srt -o demo.pop.ass --style pop
|
|
140
|
+
shorts-caption-studio v1.0.0 — ass-only
|
|
141
|
+
Captions: demo.srt — 3 cues, 12 words
|
|
142
|
+
Style: pop (big bold, active word in accent colour at 120% scale) · PlayRes 1080x1920
|
|
143
|
+
Word timing: ESTIMATED (cue duration split by word length — not true word alignment)
|
|
144
|
+
ASS written: demo.pop.ass (12 events)
|
|
145
|
+
(exit code: 0)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The sidecar it wrote (first lines; note the active word carrying the accent colour and the 120% scale tags, moving word by word):
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
[Script Info]
|
|
152
|
+
Title: shorts-caption-studio — pop captions
|
|
153
|
+
ScriptType: v4.00+
|
|
154
|
+
PlayResX: 1080
|
|
155
|
+
PlayResY: 1920
|
|
156
|
+
|
|
157
|
+
[V4+ Styles]
|
|
158
|
+
Style: Pop,Arial,84,&H00FFFFFF,&H00FFFFFF,&H00000000,&H64000000,-1,0,0,0,100,100,0,0,1,6,2,2,60,60,420,1
|
|
159
|
+
|
|
160
|
+
Dialogue: 0,0:00:00.00,0:00:00.38,Pop,,0,0,0,,{\1c&H0000D5FF\fscx120\fscy120}This{\r} trick changes everything
|
|
161
|
+
Dialogue: 0,0:00:00.38,0:00:00.87,Pop,,0,0,0,,This {\1c&H0000D5FF\fscx120\fscy120}trick{\r} changes everything
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The same cue in `karaoke` style — one event, `\kf` centisecond durations estimated per word (38 + 48 + 67 + 96 ≈ the 2.5s cue):
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
Dialogue: 0,0:00:00.00,0:00:02.50,Karaoke,,0,0,0,,{\kf38}This {\kf48}trick {\kf67}changes {\kf96}everything
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
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":
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
$ shorts-caption-studio --ass-only examples/messy.srt -o messy.pop.ass --style pop
|
|
174
|
+
shorts-caption-studio v1.0.0 — ass-only
|
|
175
|
+
Captions: messy.srt — 2 cues, 11 words
|
|
176
|
+
Style: pop (big bold, active word in accent colour at 120% scale) · PlayRes 1080x1920
|
|
177
|
+
Word timing: ESTIMATED (cue duration split by word length — not true word alignment)
|
|
178
|
+
ASS written: messy.pop.ass (11 events)
|
|
179
|
+
WARN: cue #2 skipped (invalid-timing: end 1.500s <= start 2.000s)
|
|
180
|
+
WARN: cue #3 skipped (empty-text: cue has no text)
|
|
181
|
+
(exit code: 1)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
$ shorts-caption-studio examples/demo.mp4 --captions examples/demo.srt --style karaoke --json
|
|
186
|
+
{
|
|
187
|
+
"tool": "shorts-caption-studio",
|
|
188
|
+
"version": "1.0.0",
|
|
189
|
+
"command": "render",
|
|
190
|
+
"video": "demo.mp4",
|
|
191
|
+
"path": "examples/demo.mp4",
|
|
192
|
+
"captions": "demo.srt",
|
|
193
|
+
"style": "karaoke",
|
|
194
|
+
"output_file": "examples/demo.captioned.mp4",
|
|
195
|
+
"width": 720,
|
|
196
|
+
"height": 1280,
|
|
197
|
+
"duration_seconds": 6.0,
|
|
198
|
+
"has_audio": true,
|
|
199
|
+
"cue_count": 3,
|
|
200
|
+
"word_count": 12,
|
|
201
|
+
"event_count": 3,
|
|
202
|
+
"word_timing": "estimated",
|
|
203
|
+
"skipped_cues": [],
|
|
204
|
+
"warnings": []
|
|
205
|
+
}
|
|
206
|
+
(exit code: 0)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```text
|
|
210
|
+
$ shorts-caption-studio missing.mp4 --captions examples/demo.srt
|
|
211
|
+
ERROR: file not found: missing.mp4
|
|
212
|
+
(exit code: 2)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Tests
|
|
216
|
+
|
|
217
|
+
43 tests, standard library only:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
python3 -m unittest discover -s tests
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
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).
|
|
224
|
+
|
|
225
|
+
## Known limits
|
|
226
|
+
|
|
227
|
+
- **Word timing is estimated** (see [Honest limits](#honest-limits-the-short-version)). Accuracy degrades inside long cues; keep cues short — caption-toolkit's `lint` (too-long > 7s) is a good upstream gate.
|
|
228
|
+
- Burn-in **re-encodes** the video (audio stream is copied untouched); expect ordinary re-encode quality behaviour.
|
|
229
|
+
- Styles are composed for vertical 1080x1920. Landscape input renders with a warning, but caption placement is not optimised for it.
|
|
230
|
+
- 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.
|
|
231
|
+
- Inline SRT tags (`<i>`, `<b>`) are stripped rather than converted to ASS styling.
|
|
232
|
+
|
|
233
|
+
## Privacy
|
|
234
|
+
|
|
235
|
+
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.
|
|
236
|
+
|
|
237
|
+
## Contributing
|
|
238
|
+
|
|
239
|
+
Issues and pull requests are welcome — see [CONTRIBUTING.md](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.
|
|
240
|
+
|
|
241
|
+
## Related
|
|
242
|
+
|
|
243
|
+
- [caption-toolkit](https://github.com/jasveerai88/caption-toolkit) — the step before this one: check, lint and fix the SRT itself (reading speed, cue structure) before you style it. Studio renders; toolkit judges the words.
|
|
244
|
+
- [shorts-packager](https://github.com/jasveerai88/shorts-packager) — package the finished Short for upload once the captions are burned in.
|
|
245
|
+
- [faceless-qc-lite](https://github.com/jasveerai88/faceless-qc-lite) — QC the finished video before it ships.
|
|
246
|
+
- [skill-vet](https://github.com/jasveerai88/skill-vet) · [permission-audit](https://github.com/jasveerai88/permission-audit) · [worktree-janitor](https://github.com/jasveerai88/worktree-janitor) · [done-claim-auditor](https://github.com/jasveerai88/done-claim-auditor) — the agent-safety side of the same toolkit.
|
|
247
|
+
- [gst-hst-tracker](https://github.com/jasveerai88/gst-hst-tracker) · [seller-profit-truth](https://github.com/jasveerai88/seller-profit-truth) — the money-truth pair from the same author.
|
|
248
|
+
|
|
249
|
+
## Who this is for
|
|
250
|
+
|
|
251
|
+
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.
|
|
252
|
+
|
|
253
|
+
## Support
|
|
254
|
+
|
|
255
|
+
If this saves you a subscription month, consider sponsoring: **[github.com/sponsors/jasveerai88](https://github.com/sponsors/jasveerai88)**
|
|
256
|
+
|
|
257
|
+
## Licence
|
|
258
|
+
|
|
259
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "shorts-caption-studio"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Add animated word-by-word captions to YouTube Shorts, TikTok and Reels: burn pop, karaoke or clean highlight captions from your own SRT with ffmpeg, or export the styled .ass file. Free, local, pure Python."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Jasveer Singh" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"captions",
|
|
17
|
+
"subtitles",
|
|
18
|
+
"shorts",
|
|
19
|
+
"tiktok",
|
|
20
|
+
"reels",
|
|
21
|
+
"srt",
|
|
22
|
+
"ass",
|
|
23
|
+
"ffmpeg",
|
|
24
|
+
"cli",
|
|
25
|
+
]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 5 - Production/Stable",
|
|
28
|
+
"Environment :: Console",
|
|
29
|
+
"Intended Audience :: End Users/Desktop",
|
|
30
|
+
"Operating System :: OS Independent",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Topic :: Multimedia :: Video",
|
|
36
|
+
"Topic :: Utilities",
|
|
37
|
+
]
|
|
38
|
+
dependencies = []
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/jasveerai88/shorts-caption-studio"
|
|
42
|
+
Repository = "https://github.com/jasveerai88/shorts-caption-studio"
|
|
43
|
+
Issues = "https://github.com/jasveerai88/shorts-caption-studio/issues"
|
|
44
|
+
Changelog = "https://github.com/jasveerai88/shorts-caption-studio/blob/main/CHANGELOG.md"
|
|
45
|
+
|
|
46
|
+
[project.scripts]
|
|
47
|
+
shorts-caption-studio = "shorts_caption_studio:main"
|
|
48
|
+
|
|
49
|
+
# The implementation is a single standard-library module in
|
|
50
|
+
# src/; it is installed as the top-level module
|
|
51
|
+
# `shorts_caption_studio`, and `python3 src/shorts_caption_studio.py`
|
|
52
|
+
# keeps working from a checkout. Burn-in shells out to the system
|
|
53
|
+
# ffmpeg/ffprobe (external tools, never Python dependencies).
|
|
54
|
+
[tool.setuptools]
|
|
55
|
+
package-dir = { "" = "src" }
|
|
56
|
+
py-modules = ["shorts_caption_studio"]
|