{"slug":"scenario-caption-studio","title":"scenario-caption-studio","summary":"Use when a video needs its spoken words on screen through Scenario via MCP: burned-in styled captions for a TikTok, Reels, or Shorts cut, ad captions for sound-off feeds, YouTube subtitles, an SRT sidecar, transcription of a clip's audio, captions translated into another language","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-10-17T09:10:50.794389Z","repo":{"url":"https://github.com/scenario-labs/skills","stars":1035,"forks":129,"license":"MIT","updatedAt":"2026-10-17T05:01:44Z"},"bodyHtml":"<hr>\n<h2>name: scenario-caption-studio\ndescription: \"Use when a video needs its spoken words on screen through Scenario via MCP: burned-in styled captions for a TikTok, Reels, or Shorts cut, ad captions for sound-off feeds, YouTube subtitles, an SRT sidecar, transcription of a clip's audio, captions translated into another language, karaoke or word-by-word styles, or restyling and correcting an existing transcript. Keywords: captions, subtitles, SRT, transcribe, karaoke, word-by-word, burn in, closed captions, translate video, caption style.\"\nlicense: MIT</h2>\n<h1>Scenario Caption Studio</h1>\n<h2>Overview</h2>\n<p>Caption Studio is one tool model, <code>model_scenario-caption-studio</code>: a video in, its speech transcribed (Whisper) or an existing SRT applied, styled captions out, burned into the picture or delivered as a soft track and an <code>.srt</code> sidecar. It translates into 18 languages and styles captions three ways. Running that one member is this skill's whole purpose, so the id is named rather than discovered and <code>model_schema_get</code> starts the flow directly; availability differs per team, so a member the team lacks is a gap to flag, not a cue to substitute. Connection and the core loop: see the <code>scenario</code> skill.</p>\n<p>Captioning is the last pass on a finished cut: assemble first (<code>scenario-video-assembly</code>), then caption the master once. Captions carry the transcript only; text that must appear letter-perfect without being spoken (CTAs, prices, legal supers) is <code>scenario-text-overlay</code> territory. If a sibling skill named here is missing from your available skills, ask the user to install it (<code>npx skills add scenario-labs/skills --skill &lt;name&gt;</code>); unattended, proceed from tool schemas and flag the gap.</p>\n<h2>Ask once, then caption</h2>\n<p>The destination decides nearly every parameter, so collect one round of answers before touching the schema: where the video ships (a sound-off mobile feed, a paid placement, a seated long-form viewer), whether the spoken language stays or translates (<code>targetLanguage</code>, <code>auto</code> keeps it), brand colors if any, and which deliverable the platform wants. The deliverable is three switches: burned-in pixels are <code>outputSubtitles: \"video_image\"</code> (the default), the toggleable track is <code>\"video_data\"</code>, the sidecar file is <code>outputSrt: true</code>, and an SRT-only pass is that plus <code>outputVideo: false</code>, the first pass when the words must be letter-perfect: it priced the same as a burn-in at authoring time, so it buys certainty rather than savings, proving the words before any pixels are paid for. Then map the answers:</p>\n<table>\n<thead>\n<tr>\n<th>Destination</th>\n<th>Style</th>\n<th>Segmentation</th>\n<th>Position</th>\n<th>Output</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Social mobile short (9:16 Shorts, Reels)</td>\n<td><code>tiktok-bouncy</code> or <code>word-pop</code>; <code>karaoke-fill</code> when music drives</td>\n<td><code>maxSegmentWords</code> 3 to 5; 1 with a karaoke preset</td>\n<td><code>middle</code>: platform UI and native auto-captions own the bottom</td>\n<td>Burn in</td>\n</tr>\n<tr>\n<td>Ad short, performance cut</td>\n<td><code>modern-chip</code> or <code>minimal-underline</code>, accents set to brand color</td>\n<td>3 to 7 words per cue</td>\n<td><code>bottom</code>, or <code>top</code> when an end card or overlay sits below</td>\n<td>Burn in for sound-off feeds; add <code>outputSrt</code> for the platform's caption upload</td>\n</tr>\n<tr>\n<td>YouTube long-form, tutorial, interview</td>\n<td>Default look or <code>cinematic-fade</code>; restraint reads as professionalism</td>\n<td><code>maxLines</code> 2, <code>maxSegmentChars</code> 84 (two 42-character lines, the broadcast convention)</td>\n<td><code>bottom</code></td>\n<td><code>outputSrt</code> for the platform's closed captions; burn in only for re-embeds</td>\n</tr>\n<tr>\n<td>Cinematic piece, trailer, festival cut</td>\n<td><code>cinematic-fade</code></td>\n<td>Sentence-length cues, <code>maxSegmentDuration</code> about 6</td>\n<td><code>bottom</code></td>\n<td>Burn in</td>\n</tr>\n</tbody>\n</table>\n<p>Rows are authoring-time starting points to confirm with the user, not platform contracts; unattended, the task's own instructions answer the interview and the matching row's defaults fill what they leave unsaid. The per-placement safe zones behind the position column live in <code>scenario-formats</code>. <code>middle</code> renders at frame center, which on a centered talking head is the mouth: caption a selfie cut only once its face sits in the upper third (reframe in assembly), because the schema offers no lower-third position. An uploaded track beats burn-in for long-form because viewers toggle and restyle it, assistive tech reads it, and platforms index it for search; burn-in wins wherever the style is the point or a track cannot travel with the file.</p>\n<h2>The style ladder</h2>\n<p>Three tiers: <code>stylePreset</code> picks a ready-made look (7 presets, empty for the default); <code>stylePrompt</code> describes a look in plain words and builds a matching style (it carries <code>cost_impact</code>); <code>themeTsx</code> supplies a full custom theme that replaces the preset, with <code>stylePrompt</code> then refining that theme. There is no font parameter: type rides inside the tiers, and it is the strongest signal a style sends, so when the type itself must carry the mood or the brand, put the intent into <code>stylePrompt</code> in plain words (the weight, the letterform class, the feeling: \"heavy condensed sans, high-energy\", \"light geometric sans, quiet and premium\"); an exact brand face is <code>themeTsx</code> territory, and <code>scenario-text-overlay</code> chooses faces by meaning for the text cards around the captions. Presets also restyle the words themselves: an authoring-time run of <code>tiktok-bouncy</code> uppercased every caption, and the other presets are unverified for casing, so when exact casing matters (a product name, \"LoRA\") steer with <code>stylePrompt</code> or <code>themeTsx</code> and verify a frame before delivering. <code>fontColor</code> sets the body text (contrast beats aesthetics: white body text survives every backdrop the presets put behind it), and <code>accentColorStart</code>/<code>accentColorEnd</code> drive the highlight animation (karaoke fills, pops): spend the accent on one thing, usually the brand color, with equal values for a solid and different values for a gradient. Auto sizing (<code>fontSizePx</code> empty) rendered words about 30 pixels tall on a 1080x1920 frame at authoring time, unreadable on a phone feed: for vertical social set <code>fontSizePx</code> explicitly (60 to 90 on a 1920-tall frame is the authoring-time starting point) and judge a frame at phone scale. <code>outputTsx: true</code> returns the theme a run used, so a look that landed can be replayed exactly on the next video.</p>\n<h2>Getting the words right</h2>\n<ul>\n<li>When narration was authored, retain that script as the wording reference. Compare the transcript against it for missing, repeated or altered words before burning; a spelling hint is not that comparison. Derive cue times from the actual recording, not word count or the planned shot duration. Correct the SRT through the supported input below; do not invent a script-alignment parameter.</li>\n<li><code>transcriptionPrompt</code> is a spelling hint, not a style field: list the names, brands, and jargon the audio contains. The hint raises the odds without guaranteeing them (an authoring-time run misspelled a hinted name twice), so check the transcript for every required name before trusting a burn-in, and on a miss retry with <code>large-v3</code> or a sharper hint.</li>\n<li><code>modelSize</code> trades accuracy for speed and cost (<code>cost_impact</code>): the <code>medium</code> default is fine for clean voiceover; step up to <code>large-v3</code> for noisy audio, accents, or dense terminology; <code>.en</code> variants are English-only.</li>\n<li>The model transcribes whatever audio the master carries, so balance the music bed against dialogue before captioning (<code>scenario-video-assembly</code>), never after.</li>\n<li>Corrections and restyles ride the <code>subtitles</code> input, whose contract is inline content, not a reference (authoring-time): pass the SRT text itself, base64-encoded, as the value. An <code>asset_...</code> id is not dereferenced there; the id string is base64-decoded as if it were content, and the run still reports success, bills, and renders zero captions (<code>segment_count: 0</code> in the job record is the tell). Reuse is therefore: <code>outputSrt: true</code> returns the transcript as an asset, <code>asset_download</code> it, correct spellings locally if needed, and feed the edited text back base64-encoded, which also covers <code>upload_asset</code> having no text kind (authoring-time fact). The SRT inherits the run's segmentation (a karaoke run returns word-per-cue), so re-chunk it locally before an ads upload or a calmer restyle. Never deliver a <code>subtitles</code> run on job status alone: sweep the output as the worked example reviews it, and when captions are missing, <code>asset_get</code> the subtitles asset the job consumed to see what it received. The schema does not say whether segmentation caps re-chunk a supplied SRT, so set segmentation when transcribing and omit the caps alongside <code>subtitles</code>.</li>\n</ul>\n<h2>Worked example: a vertical social short, then a Spanish variant</h2>\n<ol>\n<li><code>asset_get</code> the assembled master: confirm duration and that it is the finished cut, since the price tracks the footage (<code>video</code> carries <code>cost_impact</code>; trim first, <code>scenario-video-editing</code>).</li>\n<li><code>model_schema_get</code> on <code>model_scenario-caption-studio</code>.</li>\n<li>Price it: <code>model_run</code> with <code>dry_run: true</code> and <code>parameters={\"video\": \"&lt;asset_id&gt;\", \"stylePreset\": \"tiktok-bouncy\", \"maxSegmentWords\": 4, \"textPosition\": \"middle\", \"transcriptionPrompt\": \"Scenario, LoRA, Flux\", \"outputSrt\": true}</code>. Re-estimate after changing <code>targetLanguage</code>, <code>modelSize</code>, <code>stylePrompt</code>, or <code>outputSubtitles</code>: all carry <code>cost_impact</code>.</li>\n<li>Run it with <code>wait: false</code>, then <code>jobs_wait</code> with the <code>job_id</code>; on timeout re-call with the returned <code>pending_job_ids</code>, never <code>job_get</code> in a loop.</li>\n<li>Review before deriving, since a successful job proves nothing about what rendered: download the SRT sidecar with <code>asset_download</code> (no <code>format</code>: it converts images only; a run that returns both video and SRT lists the two asset ids in no fixed order, and <code>asset_get</code> tells them apart by <code>mimeType</code>) and check its text against the authored script and required names; then download the video and sweep it into contact sheets (<code>ffmpeg -vf \"fps=2\"</code>), reading the burned captions for spelling, casing, and placement. Inspect at the intended display size, including backing panels: keep faces, hands, demonstrated actions and essential UI clear, and keep placement stable within a cue. Listen against cue starts and ends; a correct transcript does not prove synchronization. Sample frames immediately before and after cue boundaries as well as within them: reveals and fades can exceed supplied SRT times, and a flash under 100 ms survives an <code>fps=2</code> sweep. If strict boundaries are required, verify the actual theme or report the limitation; a correct sidecar alone is not a pass. Check the final spoken word survives, comparing audio and video streams separately; a shorter audio stream containing the complete narration may simply omit trailing silence.</li>\n<li>Spanish variant: add <code>targetLanguage: \"es\"</code> to the same parameters, <code>transcriptionPrompt</code> included, and <code>dry_run</code> again (it moves the price) before running. Translation happens inside the run, so one master yields a variant per market. Latin-script segmentation caps do not transfer to Chinese, Japanese, or Korean (streaming style guides run them at a third of the characters per line), so revisit <code>maxSegmentChars</code> per target language.</li>\n<li>File the master, variants, and SRT assets in a collection (<code>scenario</code> skill) so the delivery set stays findable.</li>\n</ol>\n<h2>Common mistakes</h2>\n<ul>\n<li>Captioning each clip before assembly: caption the finished master once, or cues drift across cuts and every edit orphans its captions.</li>\n<li>Expecting the preset look on the soft track: <code>video_data</code> renders as plain text the viewer toggles; styling survives only when burned in.</li>\n<li>Styling legal lines or CTAs as captions: disclosures carry locked wording, size, and dwell time that caption logic would re-chunk and retime, and a toggleable track fails \"visible without viewer action\" rules outright; exact unspoken text is a <code>scenario-text-overlay</code> card composited in assembly.</li>\n<li>Setting <code>maxSegmentWords: 1</code> without a karaoke or pop preset: one-word cues flash as a slideshow unless the style animates them. With a pop preset (the ones that reveal words inside a cue: <code>tiktok-bouncy</code>, <code>word-pop</code>, and the karaoke pair at authoring time), never end a cue on a one-letter word: pop-in timing follows character count, so a trailing \"I\" or \"a\" showed for about 70 ms at authoring time. Re-chunking a supplied SRT means owning its timings too: a supplied SRT carries cue times only, so a pop preset spreads its words evenly across each cue and the highlight drifts from the voice wherever a cue outlasts the speech; keep each cue's end on the spoken span, and extend only the last cue to the clip's end.</li>\n<li>Treating a <code>jobs_wait</code> timeout as failure: re-call with <code>pending_job_ids</code>; video tool jobs outlast the wait window routinely.</li>\n</ul>\n","files":[{"path":"SKILL.md","sizeBytes":13599,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-10-17T09:11:33.047011Z","sha256":"EFE3DF5451D33B475C20767C1D5756503F8412078A12A62E4E6C7D6D86D46CE7","sizeBytes":5594},"review":null,"source":{"repositoryUrl":"https://github.com/scenario-labs/skills","path":"skills/scenario-caption-studio","license":"MIT","commit":"dc592af7139ffacfe8fdae7dc7efa80f4dd09be3","subtreeSha":"D8C9AFBC5A9D675758FEE4B5DB99F2AF8BD0399FFF10EE52EF45476F24850795","lastSyncedAt":"2026-10-17T09:10:46.286688Z"},"reviewedAt":"2026-10-17T09:12:43.539134Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/scenario-labs/skills/tree/main/skills/scenario-caption-studio"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install scenario-labs-skills@llmmart"},{"target":"git","command":"git clone https://github.com/scenario-labs/skills.git"}]}