---
title: "markksantos/caption-checker"
description: "Check SRT and WebVTT captions against Netflix, BBC and DCMP Captioning Key limits: reading speed, line length, lines, duration, gaps, overlaps. Zero-dependency CLI + library."
source: https://github.com/markksantos/caption-checker
ref: main
license: MIT
licenseName: "MIT License"
canonical: https://skillsdocs.com/markksantos/caption-checker
base: https://github.com/markksantos/caption-checker/blob/main/
provenance: authored
chapters: 3
inlined: 3
withheld: 0
words: 2051
updated: 2026-11-01T05:02:35Z
generator: "Skills Docs"
---

> **markksantos/caption-checker** — every Agent Skill in this repository, inlined verbatim.
>
> Canonical HTML: https://skillsdocs.com/markksantos/caption-checker
> Per-skill Markdown: https://skillsdocs.com/markksantos/caption-checker/<skill>.md
> Machine manifest: https://skillsdocs.com/markksantos/caption-checker/.well-known/agent-skills/index.json
> JSON: https://skillsdocs.com/api/v1/books/markksantos/caption-checker
> Install: `npx skills add markksantos/caption-checker`
> Upstream: https://github.com/markksantos/caption-checker @ `main`
> Licence: MIT
>
> Content is mirrored from GitHub and © its authors, served unmodified. Takedown: https://github.com/DreambaseAI/skillsdocs/issues/new?labels=takedown&title=Takedown+request

# markksantos/caption-checker

Check SRT and WebVTT captions against Netflix, BBC and DCMP Captioning Key limits: reading speed, line length, lines, duration, gaps, overlaps. Zero-dependency CLI + library.

- **Skills:** 3
- **Inlined:** 3 (licence detected)
- **Words:** 2,051
- **Reading time:** 9 min
- **Stars:** 0

## Table of contents

1. [caption-checker](https://skillsdocs.com/markksantos/caption-checker/caption-checker.md) — Use when the user wants an SRT or WebVTT caption or subtitle file checked before it ships, asks whether captions read too fast, have lines that are too long or…
2. [caption-converter](https://skillsdocs.com/markksantos/caption-checker/caption-converter.md) — Use when the user wants an SRT or WebVTT caption or subtitle file converted to the other format, or its timing fixed because it is out of sync with the video -…
3. [caption-fixer](https://skillsdocs.com/markksantos/caption-checker/caption-fixer.md) — Use when the user wants an SRT or WebVTT caption or subtitle file fixed so it meets Netflix, BBC or DCMP Captioning Key limits, not just checked - lines too lo…


## Front matter

_The repository README, verbatim except that relative links are resolved against https://github.com/markksantos/caption-checker/blob/main/._

# caption-checker

Check SRT and WebVTT captions against a published caption standard: **Netflix English**, the **BBC subtitle guidelines** or the **DCMP Captioning Key**. It flags captions that read too fast, lines that are too long, too many lines, captions that stay on screen too short or too long, gaps under the minimum, overlaps and broken timestamps. It can also fix those, convert SRT to WebVTT and back, and retime a file that's out of sync.

No dependencies. Node 18+. The same checker runs in the browser, with no install and no upload, at **[markstudios.com/tools/caption-checker](https://www.markstudios.com/tools/caption-checker)**.

![caption-check flagging a travel vlog's SRT file: 2 of 8 captions meet Netflix English limits](https://github.com/markksantos/caption-checker/blob/main/docs/preview.png)

```
$ caption-check examples/travel-vlog.srt
travel-vlog.srt: 2 of 8 captions meet Netflix English, adult programs
  #1  0:00.500 -> 0:03.000  Welcome back to the channel.
      ✗ Gap to the next caption is 0 frames at 24 fps (minimum 2)
  #3  0:06.600 -> 0:07.100  First,
      ✗ On screen 0.50 s (minimum 5/6 of a second)
  #4  0:07.200 -> 0:09.000  we packed water, snacks, a rain jacket and a spare battery for the cam
      ✗ Reading speed 41.1 characters a second (limit 20)
      ✗ Line 1 is 74 characters (limit 42)
  ...
```

## The limits

Each limit was read on the standard's own page on 2026-10-15.

| | Netflix (English) | BBC | DCMP Captioning Key |
|---|---|---|---|
| Reading speed | Up to 20 characters a second (adult), 17 (children’s) | 160–180 words a minute | Up to 130 / 140 / 160 words a minute (lower / middle / upper level) |
| Characters a line | 42 | 37 on broadcast (Teletext) | No character count |
| Lines a caption | 2 | 2; 3 for vertical 9:16 video | No more than 2 preferred |
| Shortest time on screen | 5/6 of a second | About 0.3 s a word (advice) | 40 frames (1 second and 10 frames) |
| Longest time on screen | 7 seconds | No number | 6 seconds |
| Gap between captions | 2 frames minimum; gaps under 0.5 s closed to 2 frames | If you leave a gap, at least 1 second (advice) | No rule |

Sources: [Netflix English (USA) Timed Text Style Guide](https://partnerhelp.netflixstudios.com/hc/en-us/articles/217350977-English-USA-Timed-Text-Style-Guide) · [Netflix General Requirements](https://partnerhelp.netflixstudios.com/hc/en-us/articles/215758617-Timed-Text-Style-Guide-General-Requirements) · [Netflix Subtitle Timing Guidelines](https://partnerhelp.netflixstudios.com/hc/en-us/articles/360051554394-Timed-Text-Style-Guide-Subtitle-Timing-Guidelines) · [BBC Subtitle Guidelines](https://www.bbc.co.uk/accessibility/forproducts/guides/subtitles/) · [DCMP Captioning Key: Text](https://dcmp.org/learn/597-captioning-key---text) · [DCMP Captioning Key: Presentation Rate](https://dcmp.org/learn/601-captioning-key---presentation-rate)

**How it counts.** Characters include letters, spaces and punctuation, but not line breaks or formatting (`<i>` tags, `{\an8}` codes, WebVTT voice spans). Reading speed is characters ÷ (out-time − in-time). Words are runs of characters that hold a letter or a digit, so speaker labels and sound tags count, as DCMP asks. Gaps run from one caption's out-time to the next caption's in-time, in frames at the frame rate you give. Anything a standard words as a recommendation is reported as advice, not as a failure.

## CLI

```
npx github:markksantos/caption-checker#v1.3.3 captions.srt
# or: git clone --depth 1 --branch v1.3.3 https://github.com/markksantos/caption-checker && node caption-checker/bin/caption-check.js captions.srt
# (#v1.3.3 and --branch v1.3.3 pin a tagged release; without them you get whatever is on main)

caption-check <file.srt|file.vtt> [more files] [options]
  -s, --standard <id>   netflix | netflix-kids | bbc | dcmp-upper | dcmp-middle | dcmp-lower  (default: netflix)
  -f, --fps <rate>      frame rate for frame-gap checks (default: 24)
      --vertical        9:16 video (the BBC allows three lines)
      --json            JSON output
      --advice          also print advice-only notes
      --fix <out>       write a fixed copy to <out> (one input file)
      --mcp             run as an MCP server on stdio

caption-check <file> --to vtt|srt [--speakers] [--crlf] [--out <file>]                     convert
caption-check <file> --shift <time> [--from <n>] | --start-at <n=time> | --rate <from:to> | --sync <n=time,n=time>
              [--to vtt|srt] [--out <file>]                                                retime
```

It exits with 1 when any caption breaks a limit, so it works as a CI check before captions ship.

## Fix mode

`--fix` writes a copy with every fix that doesn't need a person: it never changes a word or an in-time.

```
$ caption-check examples/travel-vlog.srt --fix fixed.srt
travel-vlog.srt: 8 changes in 7 captions, written to fixed.srt
  #1  Out-time 0:03.000 -> 0:02.916 (to leave 2 frames before the next caption)
  #4  Rewrapped to 2 lines (38 + 35 characters)
  #5  Out-time 0:17.000 -> 0:16.100 (off an overlap, to the 7 s maximum)
  ...
  Left for a person (2):
  #3  Still on screen only 0.52 s (minimum 5/6 of a second) with no free time after it: merge it with a neighbor or move the next caption
  #4  Still reads at 40.2 characters a second (limit 20) with no free time after it: split it, condense it or move the next caption
fixed.srt: 6 of 8 captions meet Netflix English, adult programs (before: 2 of 8)
```

- **Line breaks.** A caption with a line over the limit, or too many lines, is rewrapped into the fewest lines that fit, breaking after punctuation where it can and avoiding a break after an article, preposition, conjunction or auxiliary verb ("the", "of", "and", "was"). Captions with formatting tags or two speakers are left for a person.
- **Out-times.** An out-time is pulled back off an overlap or a gap under the minimum, cut to the longest time on screen, and pushed into free time when a caption is too short or reads too fast. For Netflix, gaps under 0.5 s are closed to 2 frames, as its timing guide asks.
- **Everything else stays.** Captions that needed nothing keep their exact bytes, and so do cue ids, WebVTT headers, NOTE blocks, cue settings, line endings and a byte-order mark. A second run changes nothing.

Run against 300 caption files with all six standards, it never changed a word, an in-time or the number of captions, and never left more captions failing than it started with.

## Convert and retime

`--to` converts SubRip to WebVTT or back; the retiming flags fix captions that are out of sync with the video. Without `--out`, the file goes to stdout and the summary to stderr.

```
$ caption-check examples/travel-vlog.srt --to vtt --out travel-vlog.vtt
travel-vlog.srt: 8 captions converted from SubRip (.srt) to WebVTT (.vtt).
Written to travel-vlog.vtt
$ caption-check episode.srt --shift -2.5 --from 120 --out episode.synced.srt     # late by 2.5 s from caption 120 on
$ caption-check film.srt --rate 23.976:25 --out film.25fps.srt                     # timed for 23.976 fps, video is 25 fps
$ caption-check film.srt --sync 1=0:04.200,640=41:12.5 --out film.synced.srt       # pin two captions, stretch the rest
```

- **Converting** keeps every caption's text and timing. Italic, bold and underline stay; SubRip `{\an8}`-style position codes become WebVTT `line`/`align` settings and back; `&` and `<` are escaped for WebVTT and unescaped for SubRip; `<v Name>` voice tags become "Name: " with `--speakers`. Whatever the other format can't hold (font colours, NOTE blocks, WebVTT position or size) is listed, not silently dropped.
- **Retiming** works on integer milliseconds, so nothing drifts from rounding. A two-point sync says when the stretch matches a common frame-rate pair (23.976/25, 24/25, 29.97/30 ...). Times pushed before 0:00 are clamped and counted.
- **Encodings.** Every mode reads UTF-8, UTF-16 (by its byte-order mark) and Windows-1252, the encoding SubRip itself writes by default, and writes UTF-8.

It uses the same reader and writer as the online [SRT to VTT converter](https://www.markstudios.com/tools/srt-vtt-converter) and [subtitle time shifter](https://www.markstudios.com/tools/subtitle-time-shift) (`subtitles.js` is that code, unchanged). Run on 300 caption files (34,836 captions), SRT to WebVTT and back kept every caption's timing, and a shift there and back restored every time.

## Library

```js
const { parse, check, fix } = require('caption-checker');
const result = check(parse(fs.readFileSync('captions.vtt', 'utf8')), 'bbc', { vertical: true });
console.log(result.flagged, 'of', result.cues.length, 'captions break a limit');
for (const cue of result.cues) for (const issue of cue.issues) console.log(cue.n, issue.severity, issue.message);

const fixed = fix(fs.readFileSync('captions.srt', 'utf8'), 'netflix', { fps: 25 });
fs.writeFileSync('captions.fixed.srt', fixed.text);   // also: fixed.changes, fixed.unresolved, fixed.before, fixed.after

const { convert, retime } = require('caption-checker/convert.js');
const vtt = convert(srtText, 'vtt');                                   // { text, from, to, captions, notes }
const synced = retime(srtText, { rate: { from: 23.976, to: 25 } });    // or { offset: '-2.5', from: 120 }, { startAt: { caption, time } }, { sync: [a, b] }
```

`check()` accepts a standard id or your own standard object with the same fields as `STANDARDS` in `index.js`.

## MCP server and Claude Code plugin

The same checks run as an MCP server, so Claude, Cursor, VS Code and other MCP clients can check a caption file mid-task. Five tools: `check_captions` (a file path or pasted SRT/VTT text, a standard, fps, vertical), `fix_captions` (the same arguments; returns the fixed file and what changed), `convert_captions` (SRT to WebVTT or back), `retime_captions` (offset, start at, frame-rate change or two-point sync) and `list_standards` (each limit and the page it comes from). It runs locally: no key, no network.

```
# Claude Code plugin (MCP server + a caption QA skill)
/plugin marketplace add markksantos/caption-checker
/plugin install caption-checker@caption-checker

# or the server on its own
claude mcp add caption-checker -- npx -y github:markksantos/caption-checker#v1.3.3 --mcp
```

Other clients:

```json
{ "mcpServers": { "caption-checker": { "command": "npx", "args": ["-y", "github:markksantos/caption-checker#v1.3.3", "--mcp"] } } }
```

The skill in [`skills/caption-checker/SKILL.md`](https://github.com/markksantos/caption-checker/blob/main/skills/caption-checker/SKILL.md) tells an agent which standard to pick, how to report results and how to fix a caption without changing its words. [`skills/caption-fixer/SKILL.md`](https://github.com/markksantos/caption-checker/blob/main/skills/caption-fixer/SKILL.md) covers fixing a file: what `fix_captions` changes, and what to do with the captions it leaves for a person. [`skills/caption-converter/SKILL.md`](https://github.com/markksantos/caption-checker/blob/main/skills/caption-converter/SKILL.md) covers converting between SRT and WebVTT and working out which kind of timing fix an out-of-sync file needs.

## Study

[`study/`](https://github.com/markksantos/caption-checker/blob/main/study) holds the code and data behind the [caption reading speed study](https://www.markstudios.com/research/caption-reading-speed-study): 339,521 captions in 387 NASA videos and MIT OpenCourseWare lectures, measured with this library.

[`study-auto-captions/`](https://github.com/markksantos/caption-checker/blob/main/study-auto-captions) holds the [YouTube auto-caption accuracy study](https://www.markstudios.com/research/youtube-auto-caption-accuracy-study): automatic vs human captions on 299 videos, plus caption coverage of the most-subscribed channels.

[`study-dubbing/`](https://github.com/markksantos/caption-checker/blob/main/study-dubbing) holds the [YouTube dubbing study](https://www.markstudios.com/research/youtube-dubbing-study): every audio track on 485 videos from the 97 most-subscribed channels, split into the channels' own dubs and YouTube's automatic ones.

[`study-video-length/`](https://github.com/markksantos/caption-checker/blob/main/study-video-length) holds the [YouTube video length study](https://www.markstudios.com/research/youtube-video-length-study): durations of 2,859 latest videos and the Shorts share of 1,484 recent uploads on the most-subscribed channels.

## Version history

See [CHANGELOG.md](https://github.com/markksantos/caption-checker/blob/main/CHANGELOG.md) and [releases](https://github.com/markksantos/caption-checker/releases).

## Tests

```
npm test
```

## License

MIT © [Mark Studios](https://www.markstudios.com), a [video editing](https://www.markstudios.com/services/video-editing) and production studio.

---

<!-- chapter:begin slug=caption-checker position=1 -->

## 1. caption-checker

- **Source:** https://github.com/markksantos/caption-checker/blob/main/skills/caption-checker/SKILL.md
- **Raw:** https://raw.githubusercontent.com/markksantos/caption-checker/main/skills/caption-checker/SKILL.md
- **Markdown:** https://skillsdocs.com/markksantos/caption-checker/caption-checker.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: caption-checker
description: Use when the user wants an SRT or WebVTT caption or subtitle file checked before it ships, asks whether captions read too fast, have lines that are too long or break Netflix, BBC or DCMP Captioning Key rules, or wants caption QA in CI. Trigger with "check these captions", "are my subtitles too fast?" or "will this SRT pass Netflix's spec?".
version: 1.3.3
author: Mark Studios <contact@markstudios.com>
license: MIT
compatibility: Claude Code with the caption-checker plugin or MCP server; any agent that can run Node 18+ for the CLI fallback. Works offline.
allowed-tools: mcp__plugin_caption-checker_caption-checker__check_captions, mcp__plugin_caption-checker_caption-checker__list_standards, mcp__caption-checker__check_captions, mcp__caption-checker__list_standards
tags: [captions, subtitles, srt, webvtt, accessibility, video, mcp]
---

# Checking captions against a published standard

## Overview

This skill checks an SRT or WebVTT file against one of three published caption standards and reports each caption that breaks a limit: **Netflix English** (adult or children's programs), the **BBC subtitle guidelines**, or the **DCMP Captioning Key** (lower, middle or upper level). It catches reading speed, characters a line, lines a caption, shortest and longest time on screen, gaps between captions, overlaps and timing lines that can't be read.

It uses the two tools of the caption-checker MCP server, which ships with the plugin. Nothing leaves the machine: the check runs locally with no key and no upload.

## Prerequisites

- The caption-checker MCP server (installed with the plugin, or `claude mcp add caption-checker -- npx -y github:markksantos/caption-checker#v1.3.3 --mcp`). Without it, use the CLI in the Examples section, which needs Node 18+.
- A caption file the agent can read, or its text pasted into the chat.

## Instructions

1. **Pick the standard.** Netflix (`netflix`) is the default. Use `netflix-kids` for children's programs, `bbc` for UK broadcast or BBC delivery, and a `dcmp-*` level (`dcmp-lower` 130, `dcmp-middle` 140, `dcmp-upper` 160 words a minute) for US educational media, matched to the audience level the user names. If the user names a platform without a standard, say which one you used.
2. **Run `check_captions`** with `path` (a file on disk) or `text` (pasted captions), plus `standard`. Pass `fps` when the video isn't 24 fps, because Netflix's minimum gap is counted in frames. Pass `vertical: true` for 9:16 video, where the BBC allows three lines.
3. **Report the result.** Lead with the count line ("6 of 8 captions meet…"), then the flagged captions with their timecodes. Group repeats ("14 captions run over 20 characters a second") rather than listing hundreds of lines.
4. **Suggest fixes that keep the words.** Split a long caption across two, extend its out-time into a gap, or merge a flash caption into its neighbor. Only shorten the wording if the user asks for edited captions. When the user wants the file fixed, not just checked, use the caption-fixer skill: `fix_captions` does the rewrapping and out-time moves itself.
5. Use `list_standards` when the user asks what a limit is or where it comes from.

## Output

- `check_captions` returns the count line, then each caption with issues: number, in and out time, its text, and one line per issue marked LIMIT or advice. The structured result has every issue, counts by kind and the averages.
- Advice items are things a standard recommends rather than requires (the BBC's 0.3 s a word, its 1 s minimum gap). Report them as suggestions, never as failures.

## Error Handling

- **"No captions found"**: the file has no timing lines in SRT or WebVTT form (`00:00:01,000 --> 00:00:03,500`). Other formats (SCC, TTML, ASS) aren't read; ask for an SRT or VTT export.
- **Unreadable timing line**: reported on that caption as a timing issue; the rest of the file is still checked.
- **Wrong frame rate**: gap results depend on `fps`. If the user doesn't know it, say the check assumed 24 fps.
- Limits come from each standard's own page, read on 2026-10-15. A file that passes isn't certified by Netflix, the BBC or DCMP; it meets the numbers they publish.

## Examples

"Check captions.srt against Netflix" → `check_captions` with `path: "captions.srt"`, `standard: "netflix"`. A result looks like this:

```text
2 of 8 captions meet Netflix English, adult programs.
#4  0:07.200 -> 0:09.000  we packed water, snacks, a rain jacket and a spare battery for the cam
    LIMIT: Reading speed 41.1 characters a second (limit 20)
    LIMIT: Line 1 is 74 characters (limit 42)
```

Without the MCP server, the same check runs from a shell, and exits with 1 when a caption breaks a limit, so it works in CI:

```bash
npx -y github:markksantos/caption-checker#v1.3.3 captions.srt
npx -y github:markksantos/caption-checker#v1.3.3 captions.vtt --standard bbc --vertical --json
```

## Resources

- [Free online caption checker](https://www.markstudios.com/tools/caption-checker): the same checker in the browser, no install and no upload, with an embed code for course and accessibility pages.
- [Caption reading speed study](https://www.markstudios.com/research/caption-reading-speed-study): 339,521 captions in 387 NASA videos and MIT OpenCourseWare lectures measured with this checker against all three standards.
- Source, CLI flags and the library API: https://github.com/markksantos/caption-checker
- Built by [Mark Studios](https://www.markstudios.com), a video editing studio for YouTube and [short-form video](https://www.markstudios.com/services/video-editing/short-form).

<!-- chapter:end slug=caption-checker -->

---

<!-- chapter:begin slug=caption-converter position=2 -->

## 2. caption-converter

- **Source:** https://github.com/markksantos/caption-checker/blob/main/skills/caption-converter/SKILL.md
- **Raw:** https://raw.githubusercontent.com/markksantos/caption-checker/main/skills/caption-converter/SKILL.md
- **Markdown:** https://skillsdocs.com/markksantos/caption-checker/caption-converter.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: caption-converter
description: Use when the user wants an SRT or WebVTT caption or subtitle file converted to the other format, or its timing fixed because it is out of sync with the video - captions that are all early or late, late from a certain point on, drifting later and later (a frame-rate mismatch such as 23.976 vs 25 fps), or that need re-syncing to two known points. Trigger with "convert this SRT to VTT", "make a WebVTT file for my HTML5 player", "the subtitles are 2 seconds late", "the captions drift out of sync", "shift these subtitles" or "sync the subtitles to the video".
version: 1.3.3
author: Mark Studios <contact@markstudios.com>
license: MIT
compatibility: Claude Code with the caption-checker plugin or MCP server; any agent that can run Node 18+ for the CLI fallback. Works offline.
allowed-tools: mcp__plugin_caption-checker_caption-checker__convert_captions, mcp__plugin_caption-checker_caption-checker__retime_captions, mcp__plugin_caption-checker_caption-checker__check_captions, mcp__caption-checker__convert_captions, mcp__caption-checker__retime_captions, mcp__caption-checker__check_captions
tags: [captions, subtitles, srt, webvtt, vtt, sync, conversion, video, mcp]
---

# Converting and retiming caption files

## Overview

This skill converts captions between **SubRip (.srt)** and **WebVTT (.vtt)** and fixes timing that is out of sync with the video. It uses two tools of the caption-checker MCP server, which ships with the plugin: `convert_captions` and `retime_captions`. Both run locally with no key and no upload, and both return the new file as text for you to save.

Use WebVTT for HTML5 `<track>` elements, most web players and course platforms. Use SubRip for desktop players, most editing software and YouTube, Vimeo or social uploads that ask for .srt.

## Prerequisites

- The caption-checker MCP server (installed with the plugin, or `claude mcp add caption-checker -- npx -y github:markksantos/caption-checker#v1.3.3 --mcp`). Without it, use the CLI in the Examples section, which needs Node 18+.
- A caption file the agent can read, and the user's OK to save the result. The skill pre-approves no file writes, so saving goes through the agent's usual permission prompt.

## Instructions

### Converting

1. **Run `convert_captions`** with `path` (or `text`). It writes the other format unless you pass `to`.
2. **Pass `speakers: true`** when converting WebVTT with voice tags (`<v Ana>`) to SubRip and the speaker names should stay visible as "Ana: ". Without it, the names are dropped, since SubRip has no voice tag.
3. **Save** the second content item next to the original with the new extension (`lecture.srt` → `lecture.vtt`).
4. **Report the notes.** Each one says what the other format couldn't hold, such as font colours (WebVTT styles them with `::cue` CSS instead), NOTE blocks (SubRip has no place for them) or WebVTT position settings with no SubRip code. Mention them in one line each, so the user knows what to restyle.

### Retiming

1. **Work out which kind of drift it is** before changing anything. Ask for, or read from the user's description, when one or two captions should appear:
   - **Everything early or late by the same amount** → `offset`, such as `"2.5"` for later or `"-1.2"` for earlier.
   - **In sync until a point, then off** (an ad break or a cut scene) → `offset` with `from`, the first caption number that is wrong.
   - **"Caption 12 should start at 0:41"** → `start_at: { caption: 12, time: "0:41" }`.
   - **Fine at the start, later and later towards the end** → a frame-rate mismatch. Use `rate` when the rates are known (`{ from: 23.976, to: 25 }`: the rate the captions were timed for, then the video's), or `sync` with two captions near the start and the end and their right start times. The summary says when a sync matches a common frame-rate pair.
2. **Run `retime_captions`** with exactly one of `offset`, `start_at`, `rate` or `sync`. Pass `to` to change format at the same time.
3. **Save** the second content item. Keep the original until the user has checked the new file against the video; don't overwrite it unless asked.
4. **Report** the summary line ("412 of 412 captions moved 2.500 s later") and any captions clamped at 0:00, which happens when an earlier shift pushes the first captions before the video starts. Those need checking against the picture.
5. **Check the result** with `check_captions` if the user wants to know whether the retimed file meets a caption standard: a frame-rate rescale changes durations and gaps slightly.

## Output

- `convert_captions`: a line such as "412 captions converted from SubRip (.srt) to WebVTT (.vtt).", the notes, then the converted file.
- `retime_captions`: the summary line, the notes (clamped captions, format changes), then the retimed file. The structured result has `changed`, `clamped`, `scale` or `offsetMs`, and the file as `retimed`.

## Error Handling

- **"No captions found"**: the text has no SubRip or WebVTT timing lines. Other formats (SCC, TTML, ASS) aren't read; ask for an SRT or VTT export.
- **"Can't read ... as a time"**: times can be seconds (`1.5`), `MM:SS.mmm`, `HH:MM:SS,mmm`, `2m30s` or `750ms`, with `-` for earlier.
- **"There is no caption N"**: caption numbers count the captions in the file from 1, in file order.
- **"The second caption has to start after the first one"**: for `sync`, give the earlier caption first, with the earlier time.
- **Captions out of order in a WebVTT file**: WebVTT requires start-time order, so they are sorted and the notes say so.

## Examples

"These subtitles are 2 seconds late" → `retime_captions` with `path: "episode.srt"`, `offset: "-2"`, then save the second content item as `episode.srt` once the user confirms, or as `episode.synced.srt`.

"Convert lecture.vtt to SRT and keep who's speaking" → `convert_captions` with `path: "lecture.vtt"`, `to: "srt"`, `speakers: true`.

"Caption 1 is right but the last one, number 640, should start at 41:12.5" → `retime_captions` with `sync: [{ caption: 1, time: "<caption 1's current start>" }, { caption: 640, time: "41:12.5" }]`.

The same from a terminal:

```bash
npx github:markksantos/caption-checker#v1.3.3 episode.srt --shift -2 --out episode.synced.srt
npx github:markksantos/caption-checker#v1.3.3 lecture.vtt --to srt --speakers --out lecture.srt
npx github:markksantos/caption-checker#v1.3.3 film.srt --rate 23.976:25 --out film.25fps.srt
npx github:markksantos/caption-checker#v1.3.3 film.srt --sync 1=0:04.200,640=41:12.5 --to vtt --out film.vtt
```

## Resources

- [Free SRT to VTT converter](https://www.markstudios.com/tools/srt-vtt-converter) and [subtitle time shifter](https://www.markstudios.com/tools/subtitle-time-shift): the same reader and writer in the browser, no install and no upload.
- Source, CLI flags and the library API: https://github.com/markksantos/caption-checker
- Built by [Mark Studios](https://www.markstudios.com), a video editing studio whose [monthly editing plans](https://www.markstudios.com/services/video-editing/monthly-editing-plans) keep each client's caption style on file from video to video.

<!-- chapter:end slug=caption-converter -->

---

<!-- chapter:begin slug=caption-fixer position=3 -->

## 3. caption-fixer

- **Source:** https://github.com/markksantos/caption-checker/blob/main/skills/caption-fixer/SKILL.md
- **Raw:** https://raw.githubusercontent.com/markksantos/caption-checker/main/skills/caption-fixer/SKILL.md
- **Markdown:** https://skillsdocs.com/markksantos/caption-checker/caption-fixer.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: caption-fixer
description: Use when the user wants an SRT or WebVTT caption or subtitle file fixed so it meets Netflix, BBC or DCMP Captioning Key limits, not just checked - lines too long, too many lines, overlapping subtitles, gaps under the minimum, captions on screen too briefly or reading too fast. Trigger with "fix these captions", "make this SRT pass Netflix's spec", "rewrap my subtitles to 42 characters" or "fix the overlapping subtitles".
version: 1.3.3
author: Mark Studios <contact@markstudios.com>
license: MIT
compatibility: Claude Code with the caption-checker plugin or MCP server; any agent that can run Node 18+ for the CLI fallback. Works offline.
allowed-tools: mcp__plugin_caption-checker_caption-checker__fix_captions, mcp__plugin_caption-checker_caption-checker__check_captions, mcp__caption-checker__fix_captions, mcp__caption-checker__check_captions
tags: [captions, subtitles, srt, webvtt, accessibility, video, mcp]
---

# Fixing captions to a published standard

## Overview

This skill fixes an SRT or WebVTT file to one of three published caption standards (**Netflix English**, the **BBC subtitle guidelines** or the **DCMP Captioning Key**) without changing a word of the captions or the moment any caption appears. It uses the `fix_captions` tool of the caption-checker MCP server, which ships with the plugin, and runs locally with no key and no upload.

What `fix_captions` changes:

- **Line breaks.** A caption with a line over the character limit, or with too many lines, is rewrapped into the fewest lines that fit. Breaks go after punctuation where possible and avoid landing right after an article, preposition, conjunction or auxiliary verb.
- **Out-times.** An out-time is pulled back off an overlap or a gap under the minimum, cut to the longest time on screen, and pushed into the free time after a caption that is on screen too briefly or reads too fast. For Netflix, gaps under half a second are closed to 2 frames.

What it never changes: the words, any in-time, the number of captions, cue ids, WebVTT headers, NOTE blocks, cue settings and line endings. A caption that needed nothing keeps its exact bytes.

## Prerequisites

- The caption-checker MCP server (installed with the plugin, or `claude mcp add caption-checker -- npx -y github:markksantos/caption-checker#v1.3.3 --mcp`). Without it, use the CLI in the Examples section, which needs Node 18+.
- A caption file the agent can read, and the user's OK to save the fixed copy. The skill pre-approves no file writes, so saving goes through the agent's usual permission prompt.

## Instructions

1. **Pick the standard and frame rate** as the caption-checker skill does: `netflix` by default, `netflix-kids` for children's programs, `bbc` for UK broadcast, a `dcmp-*` level for US educational media. Pass `fps` when the video isn't 24 fps, since Netflix's minimum gap is 2 frames, and `vertical: true` for 9:16 video.
2. **Run `fix_captions`** with `path` (or `text`), `standard`, `fps` and `vertical`. The first content item is the report; the second is the fixed file.
3. **Save the fixed file next to the original**, with `.fixed` before the extension (`captions.fixed.srt`). Overwrite the original only when the user asks for that.
4. **Report** the before-and-after line ("6 of 8 captions now meet Netflix English (before: 2)") and the kinds of change, grouped ("rewrapped 14 captions, moved 31 out-times"), not every line.
5. **Hand over what's left.** Each caption in "Left for a person" has a reason. Suggest a concrete fix for each that keeps the words: split a caption that reads too fast at a natural pause into two, merge a flash caption into its neighbor, or move the next caption's in-time if it's late against the audio. Rewrite wording only if the user asks for edited captions; condensing is an editorial call.
6. **Confirm** by running `check_captions` on the saved file when the user wants proof it passes.

## Output

- The report: the count line, one line per change (`#5  Out-time 0:17.000 -> 0:16.100 (off an overlap, to the 7 s maximum)`), then the captions left for a person with a reason each.
- The structured result has `changes`, `unresolved`, `flaggedBefore`, `flaggedAfter` and the fixed text as `fixed`.

## Error Handling

- **"No captions found"**: the text has no SRT or WebVTT timing lines. Other formats (SCC, TTML, ASS) aren't read; ask for an SRT or VTT export.
- **"Has formatting tags or two speakers"**: italics, positioning codes and dialogue dashes are left alone rather than risk breaking them. Rewrap those by hand.
- **"Too long for 2 lines"**: the words don't fit the lines allowed. It needs splitting into two captions, which means choosing a new in-time against the audio, so it stays with a person.
- **Unreadable or out-of-order timing**: reported, and that caption's timing is left as it was.

## Examples

"Fix travel-vlog.srt for Netflix" → `fix_captions` with `path: "travel-vlog.srt"`, `standard: "netflix"` (24 fps), then write the second content item to `travel-vlog.fixed.srt`. The report:

```text
8 changes. 6 of 8 captions now meet Netflix English, adult programs (before: 2).
#1  Out-time 0:03.000 -> 0:02.916 (to leave 2 frames before the next caption)
#4  Rewrapped to 2 lines (38 + 35 characters)
#6  Rewrapped to 2 lines (29 + 16 characters)
Left for a person (2):
#3  Still on screen only 0.52 s (minimum 5/6 of a second) with no free time after it: merge it with a neighbor or move the next caption
#4  Still reads at 40.2 characters a second (limit 20) with no free time after it: split it, condense it or move the next caption
```

Without the MCP server, the CLI writes the fixed copy and prints the same report. It exits with 1 while any caption still breaks a limit:

```bash
npx -y github:markksantos/caption-checker#v1.3.3 captions.srt --fix captions.fixed.srt --fps 25
npx -y github:markksantos/caption-checker#v1.3.3 captions.vtt --fix captions.fixed.vtt --standard bbc --vertical
```

## Resources

- [Free online caption checker](https://www.markstudios.com/tools/caption-checker): check a file in the browser, no install and no upload.
- [Caption reading speed study](https://www.markstudios.com/research/caption-reading-speed-study): how fast 339,521 captions in NASA videos and MIT OpenCourseWare lectures read, measured with this checker.
- Source, CLI flags and the library API: https://github.com/markksantos/caption-checker
- Built by [Mark Studios](https://www.markstudios.com), a video editing studio that also edits [white-label for agencies](https://www.markstudios.com/services/white-label-video-editing), captions included.

<!-- chapter:end slug=caption-fixer -->
