Video Caption Burner

Caption a finished video

by novoads · MIT license · ★ 23 on GitHub

Caption a finished video cover

Burn timed, styled captions into a finished video on the local machine, without re-rendering the source. This is the manual fallback to the API's captioning endpoint, POST /v1/captions, which stays the default path: come here when the…

---
name: caption-video
metadata: {packVersion: 1.3.0}
description: >-
  Burn timed, styled captions into a finished video on the local machine, without
  re-rendering the source. This is the manual fallback to the API's captioning
  endpoint, POST /v1/captions, which stays the default path: come here when the
  style the user wants is outside the API's presets, when the wording has to be
  hand-corrected before it is burned into the pixels, or when the source was
  rendered with audio disabled and the API refuses it. Transcribes the audio with
  Whisper, groups words into reading phrases, renders the captions in a real
  browser via HyperFrames, and composites them over the source with ffmpeg using
  a real alpha channel. Use when the user has an MP4 with speech and asks to add
  captions, subtitles, burned-in text, TikTok-style captions or word-by-word text
  to it, and the API path has been ruled out. Runs out of band, so it costs no
  credits and works on any video file whoever generated it. Not for generating
  video (use the video skills) and not for a separate .srt/.vtt sidecar file.
---

Overview

Takes a finished MP4 with speech and returns the same video with timed captions burned in. Nothing is regenerated: the source pixels and the source audio pass through untouched, and the captions are composited on top.

Manual install (npx, ZIP)
Claude Codenpx skills add novoads/agent-skills --skill caption-video -g -a claude-code
Claude Code (degit)npx degit novoads/agent-skills/shared/skills/caption-video#main ~/.claude/skills/caption-video
Claude.ai / desktopZip the folder and upload it under Settings → Capabilities → Skills.
Codex, Cursor, other agentsPoint them at SKILL.md. The other files are plain text.

Connects with Agent Alley MCP · agentalley.io/mcp

Files included

  • SKILL.md8.4 KB
Read SKILL.md here
novoads/main1 file shown
SKILL.md
Show the full text157 lines

Caption a finished video

Takes a finished MP4 with speech and returns the same video with timed captions burned in. Nothing is regenerated: the source pixels and the source audio pass through untouched, and the captions are composited on top.

This skill makes no API calls. It is ffmpeg + Whisper + HyperFrames on a local file, so it costs no credits and needs no API key.

Check the API path first

POST /v1/captions is the default, and it is one call against a finished asset with a set of preset styles and no local toolchain at all. Read the novoads-api skill's SKILL.md (Burned-in captions) for the call, and prompting/guide.md here for the full side by side. Offer both paths rather than picking silently, because the API costs credits and this skill costs a first-time setup.

REST key required. A Novoads MCP connector is not a substitute. If NOVOADS_API_KEY is missing or still the placeholder, stop before any generation work and tell the user: "Before continuing, create an API key at https://novoads.ai/dashboard/settings?tab=api and paste it into .env." That holds even when mcp__novoads__* tools are connected and authenticated in the session. Never call mcp__novoads__* tools from this repo's workflows: they are a different surface with different behavior, including the units they quote costs in. Repo installs verify with ./scripts/check-novoads-env.sh; a solo install checks NOVOADS_API_KEY in the environment.

Pack version. Every /v1 response carries X-Novoads-Pack-Version; mention a newer pack at https://github.com/novoads/agent-skills only when that header names a version NEWER than this file's metadata.packVersion — equal or older is nothing to say, and it is never a reason to stop.

Come here instead when the user:

  • wants a caption look the presets do not cover,
  • has to hand-correct the words before they are burned in. Invented brand names are the usual reason: transcription mishears them, and the API gives you nowhere to fix it,
  • is captioning enough footage that per-minute credits add up,
  • has a line the API path burns wrong. Its renderer can truncate a word even when its own transcript is correct — see Safety rules below. There is nothing to fix on that path, so the local burn is the fix,
  • or has a source rendered with audioEnabled: false, which the API refuses with a 409. This skill can still caption it if the user supplies the words.

When to use this skill

Trigger on phrases like:

  • "add captions to this video" / "burn in subtitles"
  • "TikTok-style captions" / "word-by-word captions"
  • "caption this ad before I deploy it"
  • the captions step of a video-ad pipeline, after the master is stitched

Do not use it to generate video — that's the novoads-api skill and the style pipelines. Do not use it when the user wants a sidecar .srt/.vtt subtitle file rather than pixels; Whisper alone covers that.

Read order

  1. This file — the pipeline shape, the gates, the hard-won rules.
  2. prompting/guide.md — the full recipe: Whisper model choice, the silence-offset transcription pattern, the word-grouping helper, the composition skeleton, the alpha composite, and the pitfall table.
  3. shared/references/craft.md § 3 — the trim-before-captioning doctrine step 1 below rests on, and § 1 for why the burned frames are the check rather than the transcript. Read it before you restate either rule anywhere; it holds one position per rule, and the copy is the failure mode.

Prerequisites

  • ffmpeg on PATH. Homebrew's build is fine here — this skill deliberately avoids the subtitles/drawtext filters, which Homebrew ships without.
  • Whisper — pip install openai-whisper, or the bundled npx hyperframes transcribe.
  • Node + npx for HyperFrames.
  • The finished video on disk. Chat-pasted files are not accessible; ask for a real path.

Workflow

  1. Trim dead space first, if any. If the source has beats where VO ends and silent visual continues, tighten it before transcribing. Timestamps taken from an untightened master drift once the video is trimmed later.
  2. Initialize a captioning project beside the source video.
  3. Transcribe — pick the Whisper model by audio type (medium.en for the typical speech-over-music ad). Transcribe a silence-trimmed copy and offset the timestamps back; Whisper smears the first words across leading silence otherwise. The source file itself is never modified.
  4. Group words into 3–5 word reading phrases, then eyeball groups.json.
  5. Render the captions over a transparent background at the source's frame rate, and composite with ffmpeg using the alpha channel.
  6. Verify the output: duration, resolution and frame rate match the source, audio is bit-identical, and the captions land on the words.

The guide carries the exact commands for each step.

Rules that cost a whole render to rediscover

  • Composite with a real alpha channel, not a chroma key. Render --format mov and let ffmpeg overlay use the alpha. A magenta key cannot cleanly remove a soft text-shadow — the shadow blends toward the key colour and leaves a purple halo on every glyph. Verified on a live clip 2026-08-03.
  • Never put <video> or <audio> in the composition. HyperFrames wraps any class="clip" element in a managed timing wrapper that overrides your CSS and reserves an ~80px layout block — a hard black bar across the render.
  • Match the frame rate to the source. HyperFrames defaults to 30fps; Seedance renders 24. Pass --fps 24 for Seedance output. Mismatched rates produce judder and creeping caption drift.
  • Match the composition size to the source resolution exactly, or the composite lands 1–2px off.
  • -c:a copy on the composite, always. The source's narration/BGM/ducking mix is already correct; re-encoding it is pure loss.

Safety rules

  • Never overwrite the source video. Write to a new filename. The source is the only copy of an expensive render.
  • Read the transcript before rendering. Whisper mis-hears brand names and numbers. Correcting groups.json costs seconds; a wrong caption burned into a deployed ad costs a re-render.
  • Show the user the grouped phrases (or the first render) before treating the captions as final.
  • Read the burned frames, not just the transcript. A correct transcript does not prove a correct caption. On 2026-08-12 the API path's hustle preset burned BOTT. for bottle. in every frame of a closing line while its own transcript had the word right — bottle. at 13.199–13.439s. The fault sat in the render layer, downstream of transcription, so every check that stopped at the words passed it. Compare the burned cards word for word against the approved script before shipping, and treat a truncated or garbled word as a failed run rather than a note. That take was charged and thrown away; the local burn is what shipped.

Hand-off

The captioned MP4 is a finished creative. To publish it as a Meta ad, that's the separate meta-ad-builder skill — which opts out of Meta's frame-modifying Advantage+ enhancements precisely because these captions are burned into the pixels.

1---
2name: caption-video
3metadata: {packVersion: 1.3.0}
4description: >-
5 Burn timed, styled captions into a finished video on the local machine, without
6 re-rendering the source. This is the manual fallback to the API's captioning
7 endpoint, POST /v1/captions, which stays the default path: come here when the
8 style the user wants is outside the API's presets, when the wording has to be
9 hand-corrected before it is burned into the pixels, or when the source was
10 rendered with audio disabled and the API refuses it. Transcribes the audio with
11 Whisper, groups words into reading phrases, renders the captions in a real
12 browser via HyperFrames, and composites them over the source with ffmpeg using
13 a real alpha channel. Use when the user has an MP4 with speech and asks to add
14 captions, subtitles, burned-in text, TikTok-style captions or word-by-word text
15 to it, and the API path has been ruled out. Runs out of band, so it costs no
16 credits and works on any video file whoever generated it. Not for generating
17 video (use the video skills) and not for a separate .srt/.vtt sidecar file.
18---
19 
20# Caption a finished video
21 
22Takes a finished MP4 with speech and returns the same video with timed captions
23burned in. Nothing is regenerated: the source pixels and the source audio pass
24through untouched, and the captions are composited on top.
25 
26**This skill makes no API calls.** It is ffmpeg + Whisper + HyperFrames on a
27local file, so it costs no credits and needs no API key.
28 
29## Check the API path first
30 
31`POST /v1/captions` is the default, and it is one call against a finished asset
32with a set of preset styles and no local toolchain at all. Read the
33`novoads-api` skill's SKILL.md (*Burned-in captions*) for the call, and
34[prompting/guide.md](prompting/guide.md) here for the full side by side. **Offer
35both paths rather than picking silently**, because the API costs credits and this
36skill costs a first-time setup.
37 
38> **REST key required. A Novoads MCP connector is not a substitute.** If
39> `NOVOADS_API_KEY` is missing or still the placeholder, stop before any
40> generation work and tell the user: "Before continuing, create an API key at
41> <https://novoads.ai/dashboard/settings?tab=api> and paste it into `.env`."
42> That holds even when `mcp__novoads__*` tools are connected and authenticated in
43> the session. Never call `mcp__novoads__*` tools from this repo's workflows: they
44> are a different surface with different behavior, including the units they quote
45> costs in. Repo installs verify with `./scripts/check-novoads-env.sh`; a solo
46> install checks `NOVOADS_API_KEY` in the environment.
47 
48**Pack version.** Every `/v1` response carries `X-Novoads-Pack-Version`; mention a newer pack at <https://github.com/novoads/agent-skills> only when that header names a version NEWER than this file's `metadata.packVersion` — equal or older is nothing to say, and it is never a reason to stop.
49 
50Come here instead when the user:
51 
52- wants a caption look the presets do not cover,
53- has to hand-correct the words before they are burned in. Invented brand names
54 are the usual reason: transcription mishears them, and the API gives you
55 nowhere to fix it,
56- is captioning enough footage that per-minute credits add up,
57- has a line the API path burns **wrong**. Its renderer can truncate a word even
58 when its own transcript is correct — see *Safety rules* below. There is nothing
59 to fix on that path, so the local burn is the fix,
60- or has a source rendered with `audioEnabled: false`, which the API refuses with
61 a `409`. This skill can still caption it if the user supplies the words.
62 
63## When to use this skill
64 
65Trigger on phrases like:
66- "add captions to this video" / "burn in subtitles"
67- "TikTok-style captions" / "word-by-word captions"
68- "caption this ad before I deploy it"
69- the captions step of a video-ad pipeline, after the master is stitched
70 
71Do **not** use it to *generate* video — that's the `novoads-api` skill and the
72style pipelines. Do not use it when the user wants a sidecar `.srt`/`.vtt`
73subtitle file rather than pixels; Whisper alone covers that.
74 
75## Read order
76 
771. **This file** — the pipeline shape, the gates, the hard-won rules.
782. **[prompting/guide.md](prompting/guide.md)** — the full recipe: Whisper model
79 choice, the silence-offset transcription pattern, the word-grouping helper,
80 the composition skeleton, the alpha composite, and the pitfall table.
813. **[shared/references/craft.md](../../references/craft.md)** § 3 — the
82 trim-before-captioning doctrine step 1 below rests on, and § 1 for why the
83 burned frames are the check rather than the transcript. Read it before you
84 restate either rule anywhere; it holds one position per rule, and the copy is
85 the failure mode.
86 
87## Prerequisites
88 
89- **ffmpeg** on PATH. Homebrew's build is fine here — this skill deliberately
90 avoids the `subtitles`/`drawtext` filters, which Homebrew ships without.
91- **Whisper** — `pip install openai-whisper`, or the bundled
92 `npx hyperframes transcribe`.
93- **Node + npx** for HyperFrames.
94- **The finished video on disk.** Chat-pasted files are not accessible; ask for
95 a real path.
96 
97## Workflow
98 
991. **Trim dead space first, if any.** If the source has beats where VO ends and
100 silent visual continues, tighten it *before* transcribing. Timestamps taken
101 from an untightened master drift once the video is trimmed later.
1022. **Initialize** a captioning project beside the source video.
1033. **Transcribe** — pick the Whisper model by audio type (`medium.en` for the
104 typical speech-over-music ad). **Transcribe a silence-trimmed copy and offset
105 the timestamps back**; Whisper smears the first words across leading silence
106 otherwise. The source file itself is never modified.
1074. **Group** words into 3–5 word reading phrases, then eyeball `groups.json`.
1085. **Render** the captions over a transparent background at the source's frame
109 rate, and **composite** with ffmpeg using the alpha channel.
1106. **Verify** the output: duration, resolution and frame rate match the source,
111 audio is bit-identical, and the captions land on the words.
112 
113The guide carries the exact commands for each step.
114 
115## Rules that cost a whole render to rediscover
116 
117- **Composite with a real alpha channel, not a chroma key.** Render
118 `--format mov` and let ffmpeg `overlay` use the alpha. A magenta key cannot
119 cleanly remove a soft `text-shadow` — the shadow blends toward the key colour
120 and leaves a purple halo on every glyph. Verified on a live clip 2026-08-03.
121- **Never put `<video>` or `<audio>` in the composition.** HyperFrames wraps any
122 `class="clip"` element in a managed timing wrapper that overrides your CSS and
123 reserves an ~80px layout block — a hard black bar across the render.
124- **Match the frame rate to the source.** HyperFrames defaults to 30fps; Seedance
125 renders 24. Pass `--fps 24` for Seedance output. Mismatched rates produce
126 judder and creeping caption drift.
127- **Match the composition size to the source resolution** exactly, or the
128 composite lands 1–2px off.
129- **`-c:a copy`** on the composite, always. The source's narration/BGM/ducking
130 mix is already correct; re-encoding it is pure loss.
131 
132## Safety rules
133 
134- **Never overwrite the source video.** Write to a new filename. The source is
135 the only copy of an expensive render.
136- **Read the transcript before rendering.** Whisper mis-hears brand names and
137 numbers. Correcting `groups.json` costs seconds; a wrong caption burned into a
138 deployed ad costs a re-render.
139- **Show the user the grouped phrases** (or the first render) before treating the
140 captions as final.
141- **Read the burned frames, not just the transcript.** A correct transcript does
142 not prove a correct caption. On 2026-08-12 the API path's `hustle` preset burned
143 `BOTT.` for `bottle.` in every frame of a closing line while its own transcript
144 had the word right — `bottle.` at 13.199–13.439s. The fault sat in the render
145 layer, downstream of transcription, so every check that stopped at the words
146 passed it. Compare the burned cards **word for word against the approved
147 script** before shipping, and treat a truncated or garbled word as a failed run
148 rather than a note. That take was charged and thrown away; the local burn is
149 what shipped.
150 
151## Hand-off
152 
153The captioned MP4 is a finished creative. To publish it as a Meta ad, that's the
154separate `meta-ad-builder` skill — which opts out of Meta's frame-modifying
155Advantage+ enhancements precisely because these captions are burned into the
156pixels.
157 
View all files on GitHub →

Details

License
MIT
Stars on the repo
★ 23
Checked
Source
novoads/agent-skills ↗
Creator
novoads · 14 listings
View creator

Similar skills

See all →
SkillAPI Interface Designaddyosmani/agent-skills · by addyosmaniGuides stable API and interface design. Use when designing APIs, module boundaries, or any public interface. Use when creating REST or GraphQL endpoints, defining type contracts between modules, or establishing boundaries between frontend and backend.★ 98.7k GitHub starsAgentVersion-Specific Docs Lookupupstash/context7 · by upstashPulls up-to-date, version-specific library docs and code examples into the prompt so the AI stops inventing old APIs.★ 62.3k GitHub starsSkillAdaptyv Bio Foundry APIK-Dense-AI/scientific-agent-skills · by K-Dense-AIHow to use the Adaptyv Bio Foundry API and Python SDK for protein experiment design, submission, and results retrieval. Use this skill whenever the user mentions Adaptyv, Foundry API, protein binding assays, protein screening experiments, BLI/SPR assays, thermostability assays, or wants to submit protein sequences for experimental characterization. Also trigger when code imports `adaptyv`, `adaptyv_sdk`, or `FoundryClient`, or references `foundry-api-public.adaptyvbio.com`.★ 46k GitHub starsSkill.NET Backend Developerwshobson/agents · by wshobsonMaster C#/.NET backend development patterns for building robust APIs, MCP servers, and enterprise applications. Covers async/await, dependency injection, Entity Framework Core, Dapper, configuration, caching, and testing with xUnit. Use when developing .NET backends, reviewing C# code, or designing API architectures.★ 39.9k GitHub starsSkillGemini Agent API Guidegoogle/skills · by googleGuides the usage of Gemini Interactions API on Gemini Enterprise Agent Platform. Use when the user wants to use the stateful, server-managed Interactions API for multi-turn conversations, background execution, streaming, structured output, and function calling on the Agent Platform.★ 20.9k GitHub starsSkillGemini Notebook Automationteng-lin/notebooklm-py · by teng-linInstall, authenticate, troubleshoot, and operate Gemini Notebook through the notebooklm-py CLI or typed async Python API. Use for notebook and source management, grounded chat and research, and artifact generation or download when the user mentions Gemini Notebook, notebooklm-py, the notebooklm CLI, or its Python API. Do not use for the generic Gemini API or unrelated content creation.★ 19.6k GitHub stars

More from novoads

SkillVideo Style Template Reversernovoads/agent-skills · by novoadsAnalyze a reference video and reverse-engineer its style into a reusable Seedance 2.0 prompting template for the Novoads API. The output is a new formula file — like seedance-2-ugc.md — that captures the video's structure, pacing, camera work, edit style and tone so it can be recreated with any product, any person, in any setting. Use this whenever someone provides a video they want to use as a style reference, says "I want to make videos like this", "deconstruct this video", "turn this into a template", "analyze this style", or drops a video file and wants to recreate that format repeatedly.★ 23 GitHub starsSkillB-Roll Video Overlaynovoads/agent-skills · by novoadsOverlays silent b-roll cutaway clips onto a finished base video — the base audio keeps running underneath while the picture cuts away and returns, and the final duration always equals the base duration. Use when the user asks to add b-roll, add cutaways, or overlay clips on a video: "add b-roll to this", cut away to a product shot here", "overlay these clips on my UGC ad". Entered from an approved base video plus its transcript, which comes from POST /v1/transcripts and needs no local install; it never generates the base itself and it never extends a video.★ 23 GitHub starsSkillAI Image Ad Creatornovoads/agent-skills · by novoadsGenerate one or more standalone Meta image-ad creatives via ChatGPT Image 2 (gpt-image-2) through the Novoads API. Locks the model, auto-strips platform chrome, enforces edge-safe layouts and glyph-safety inside body text. Use when the user asks for a "gpt-image-2 ad", "ChatGPT Image ad", "Image 2 ad creative", "make a static image ad with GPT", or anchors on a need for typography-heavy / dense-text / UI-mimicry ad creatives (chat threads, comparison tables, fake search results, iOS dialogs, Slack snapshots, ChatGPT-conversation ads, Apple Notes lists). Does NOT trigger on Nano Banana cues — use nano-banana-image-ad for those.★ 23 GitHub starsSkillVideo Ad Clonernovoads/agent-skills · by novoadsClones a VIDEO ad end to end for a different product or offer, on the Novoads API. The deliverable is a finished rendered MP4 — the WHOLE ad, not its opening, and not a template. Reads the source's style, pacing, camera work, dialogue and tone, adapts it into a Seedance 2.0 prompt, prices it, renders it, files the source beside the clones and diffs each clip's transcript against the approved script. Use for "clone this video ad", recreate this video for my brand", "make this ad but for my product" when the source is a video, "clone my competitors' VIDEO ads", or a video ad handed over with a product photo. **Among cloners the source's medium decides, not the wording**: "clone this ad" plus a video lands here; a still, or nothing attached, is clone-image-ad, and animating a still as-is is image-to-motion. This pack has no hook-only skill: for the opening alone, clone the whole ad and cut. If it does not move, wrong skill.★ 23 GitHub starsSkillImage Ad Clonernovoads/agent-skills · by novoadsClones a STATIC IMAGE ad into finished ads for your own product, into a reusable prompt TEMPLATE, or both — the template is what makes the ads and the ads are what prove the template. Round-trips the frame through gpt-image-2, nano-banana-pro or reve-2.1 until it reproduces, replaces every brand-specific with a {placeholder}, then fills it with your product pinned to a real photo. Use for "clone this ad for my product", "rebuild this competitor's ad for us", "make me ads like this", "reverse engineer this ad", make this ad reusable", "add this to my prompt library", or "clone my competitors ads" with NO file attached — it sources one via spy-competitor-ads rather than asking which. **Among cloners the source's medium decides**: a still, or nothing attached, lands here; "clone this ad" plus a VIDEO is clone-video-ad. NOT for generating an ad from a brief (chatgpt-image-ad, nano-banana-image-ad), nor animating a still as-is (image-to-motion). Refuses to render your product without a real photo of it.★ 23 GitHub starsSkillYouTube Thumbnail Designernovoads/agent-skills · by novoadsGenerate high-CTR YouTube thumbnails using Nano Banana Pro via the Novoads API. Handles reference image upload, character likeness alignment, proven CTR-tested prompt formulas, and bounded batch generation. Use when the user asks to create a YouTube thumbnail, video thumbnail, A/B test thumbnail variations, or refers to thumbnail design with their face, brand assets, or product photos.★ 23 GitHub stars