Skip to content

Repository files navigation

CueLint

Check subtitles against the delivery specs broadcasters actually publish — and see the source of every number.

License: MIT Node Dependencies Tests Status

English · Español


npx cuelint "delivery/**/*.srt" --profile netflix --lang es
✗ delivery/ep01_es.srt
  srt · 412 cues · profile netflix · lang es · adult · 23.976 fps · counting "all"
  error #  17 00:01:04,208  Reading speed 24.61 cps exceeds the maximum of 17 cps (128 characters in 5201 ms)  reading-speed
  error #  17 00:01:04,208  Line 2 is 46 characters, over the 42 character limit: "…"                          line-length
  error #  88 00:06:41,750  Gap to the next cue is 7 frames (292 ms), inside the forbidden 3-11 frame band.     gap-quantization
  warn  # 203 00:14:52,001  Uses "..." — the spec requires the single ellipsis character "…" (U+2026)           ellipsis-style

Summary  1 file  ·  412 cues  ·  3 errors  ·  1 warning  ·  0 clean

CueLint is a subtitle QC linter. It reads SRT, WebVTT and ASS/SSA, measures every cue, and reports each place the file breaks the spec you are delivering against. It has zero dependencies, makes no network requests, and runs as a CLI, a library, or a page in your browser.


Why this exists

Professional subtitle delivery is governed by numbers: reading speed in characters per second, characters per line, minimum and maximum duration, minimum gap between cues. Miss one and the file comes back from the vendor.

Nobody agrees on what the numbers are. A working QC professional posted on LinkedIn in July 2024:

"I'm tired of doing QC for subtitles where the CPS is over 35 CPS and more. I'm not gonna fix that shizzle, okay? ▷ Netflix = 17 CPS ▷ Disney+ = 20 CPS"

Netflix's English style guide says 20 CPS. The 17 he is thinking of is the template figure, and also the default for most non-English languages. Both numbers are real; they belong to different specs. That confusion is the entire problem.

And the checking is done by eye. From the ProZ subtitling forum, on detecting Netflix's two-frame gap requirement (April 2022):

"There's not a great way to do that while you are working on project. You just have to eye it." — Kristopher Brame

"the Netflix specification says minimum 2 frames between 2 subtitles… I can set the minimum distance in the options but how do I know if the gap between 2 subtitles is 11, 12, or 13 frames?" — Dr. Jens Burgert

Reading speed is the slowest part of the job (Alessandra Armenise, July 2025):

"the QC step that probably takes me the longest… I go over ALL the reading speed warnings and make a second attempt at killing them one by one… It's a long and sometimes frustrating process."

And the tools that do it cost real money. From ProZ, January 2024 (thread):

"Now I have a job where the Netflix guidelines have to be adhered to. I have tested EZTitles for this… Is there any other software that can handle the Netflix guidelines well and which is not so expensive?" — Heike Funke

EZTitles is €1,720–2,380 for a lifetime licence; EZConvert starts at €76/month; Ooona puts custom QC checks behind its Pro tier.

The free option has a gap. Subtitle Edit is excellent, MIT-licensed and cross-platform, and its seconv lint command checks line length, line count, duration, gaps, overlap and tag balance. But at commit c998cfd its SubtitleLinter.cs reads five settings and never touches SubtitleMaximumCharactersPerSeconds — grep -ci "cps" returns 0. Reading speed, the rule subtitlers care about most, is not in the CLI, there are no named profiles, and there is no per-language logic. (Subtitle Edit's superb Netflix quality check does exist — in the GUI, one file at a time.)

CueLint fills exactly that gap.


Screenshots

The browser UI. Drag files in; nothing is uploaded.

CueLint web interface showing 13 errors found in a sample subtitle file

Every threshold, with the clause it came from and a link to the published spec:

CueLint showing the resolved rule set with quoted spec text and source URLs


Install

# one-off
npx cuelint subtitles/ --profile netflix --lang pt

# or install it
npm install -g cuelint

# or just clone it — there is nothing to build
git clone https://github.com/Ax1zz/cuelint && cd cuelint && node bin/cuelint.js --help

Requires Node 18+. No dependencies to install, no compiler, no toolchain.

Prefer not to install anything at all? Open dist/cuelint-standalone.html in a browser. It is a single self-contained file that works offline, straight off a USB stick.


Quick start

# Check one file against Netflix's English spec
cuelint episode.srt --profile netflix --lang en

# A whole delivery folder of Japanese subtitles at 23.976
cuelint "delivery/**/*.srt" --profile netflix --lang ja --fps 23.976

# BBC broadcast rules, and fail on any warning
cuelint subs/ --profile bbc --variant broadcast --max-warnings 0

# A QC sheet your coordinator can open in Excel
cuelint delivery/ --profile netflix --lang es --csv --bom -o qc-report.csv

# Machine-readable, for CI
cuelint subs/ --profile netflix --lang fr --json

Exit codes: 0 clean · 1 errors found (or warnings over --max-warnings) · 2 usage or I/O problem. Drop it straight into a pipeline.

The feature that ends arguments

cuelint --explain --profile netflix --lang ko --variant sdh
Netflix — Timed Text Style Guide
language ko · variant sdh · 23.976 fps · counting "cjk-weighted"

error  reading-speed
       Maximum 14 characters per second, counted with the "cjk-weighted" method.
       "Per-language value for "ko" (sdh). Transcribed from Subtitle Edit's encoding of the
        Netflix per-language style guides; secondary source, not verified against each
        individual language page."
       https://github.com/SubtitleEdit/subtitleedit/blob/main/src/ui/...
       source confidence: secondary

error  line-length
       Maximum 16 characters per line, counted with the "cjk-weighted" method.
...

Every number carries its source, and secondary sources are labelled as secondary. When a spec contradicts itself, CueLint says so rather than quietly picking a side — Netflix's General Requirements gives a 5/6-second minimum duration while its Timing Guidelines writes "20 frames (or 4/5 sec)". Twenty frames at 24fps is 0.8333s, so the 4/5 is an error in Netflix's own document. CueLint uses 833ms and prints that note next to the rule.


Use it as a library

import { lint, explainProfile } from 'cuelint';

const result = lint(srtText, {
  filename: 'ep01.srt',
  profile: 'netflix',
  lang: 'pt-BR',
  frameRate: '23.976',
});

result.summary;      // { errors: 3, warnings: 11, infos: 0 }
result.stats.maxCps; // 24.61
result.diagnostics;  // [{ rule, severity, message, cueIndex, start, value, limit, unit }, ...]

Works unchanged in the browser — same file, no bundler required.


Profiles

id what it is reading speed line length lines
netflix Timed Text Style Guide 20 cps (en), 17 default, 4 ja, 12 ko, 9 zh, 22 hi 42 (en), 23 ja, 16 ko/zh, 39 ru, 35 th 2
netflix-template English template files 17 cps adult, 15 children 42 2
bbc BBC Subtitle Guidelines 180 wpm — the BBC publishes no cps figure 37 broadcast, 25 vertical 2, or 3 vertical
base common-practice defaults, clearly not a spec 20 cps 42 2

Variants: adult, children, sdh, children-sdh for Netflix; broadcast, online, vertical for the BBC.

Bring your own house style:

{
  "id": "acme-house",
  "name": "Acme localisation house style",
  "rules": {
    "reading-speed": { "max": 15, "unit": "cps", "severity": "error" },
    "line-length":   { "max": 38, "severity": "error" },
    "min-gap":       { "frames": 3, "severity": "warning" }
  }
}
cuelint subs/ --profile-file acme-house.json

Rules

reading-speed · reading-speed-wpm · line-length · line-count · min-duration · max-duration · min-duration-per-word · min-gap · gap-quantization · overlap · chronology · zero-duration · empty-cue · tag-balance · ellipsis-style · duplicate-text · trailing-space

cuelint --list-rules prints them; --disable and --only take comma-separated ids. A typo in either is a hard error rather than a silent no-op, because a --disable that quietly disables nothing is how a broken file reaches a customer.


Correctness

The whole point of a QC tool is being right, so:

  • Grapheme clusters, not code units. 👨‍👩‍👧‍👦 is one character, not eleven. é composed from e + combining acute is one character, not two.
  • Markup is stripped before counting — HTML-ish tags in SRT/WebVTT, {\override} blocks in ASS.
  • Invisible characters are excluded (BOM, zero-width space, bidi embeddings and isolates), but the zero-width joiner is deliberately kept, because stripping it shatters emoji sequences.
  • CJK weighting: full-width characters count 1.0, half-width 0.5. Half-width katakana with dakuten is handled correctly.
  • Exact rational frame rates. 23.976 is 24000/1001. Two frames is 83.4ms, not 83.
  • Words-per-minute is skipped for CJK rather than reporting a meaningless number.
  • Counting method is explicit and configurable, because there is no industry standard — Subtitle Edit ships twelve different implementations. CueLint makes the choice visible in every report header instead of hiding it.

140 tests cover the parsers, the measurement layer, every rule, profile resolution and the CLI:

npm test

Comparison

CueLint seconv lint Subtitle Edit GUI EZTitles / EZConvert Ooona
Price free, MIT free, MIT free, MIT €1,720–2,380 / €76+ per month $30+/month, QC in Pro
Batch a folder ✅ ✅ ❌ single file ✅ partial
Reading speed (CPS) ✅ ❌ ✅ Netflix check ✅ ✅
Named platform profiles ✅ ❌ Netflix only ✅ ✅
Per-language limits ✅ ❌ ✅ Netflix check ✅ ✅
Cites the spec for each number ✅ ❌ ❌ ❌ ❌
Words-per-minute (BBC) ✅ ❌ ❌ ✅ ?
JSON / CSV output ✅ ✅ JSON ❌ CSV report ✅ ?
Runs in a browser, offline ✅ ❌ ❌ ❌ ❌ cloud
Cross-platform ✅ ✅ ✅ ❌ Windows ✅
Dependencies 0 .NET .NET — —
Fixes files for you ❌ ✅ ✅ ✅ ✅

Subtitle Edit is a genuinely great piece of software and CueLint is not trying to replace it. If you want to fix subtitles, use Subtitle Edit. CueLint answers a narrower question: does this folder conform to that spec, and exactly where does it not?


Roadmap

  • TTML / IMSC / EBU-TT-D parsing
  • --fix behind an explicit flag (gap quantisation and ellipsis style are safely automatable)
  • More profiles: Amazon, Apple, Disney+, Channel 4, DCMP — each only once the published spec has been read and can be quoted
  • Shot-change conformance, given an EDL or a scene-change list
  • A GitHub Action
  • Per-line reading speed for two-line cues

Profiles are the most valuable contribution. See CONTRIBUTING.md.


Honest limitations

  • The per-language Netflix tables are transcribed from Subtitle Edit's encoding of Netflix's per-language style guides, not from each individual page. They are labelled secondary in every report. If you have access to a language guide, corrections are very welcome.
  • There is no EBU N-29 profile. It gets cited constantly, but the document could not be verified, and shipping a profile that claims a spec nobody has read would be precisely the failure this tool exists to prevent.
  • BBC online line length is a percentage of frame width, which a subtitle file alone cannot know. The vertical variant uses the BBC's own worked equivalent of 25 characters.
  • CueLint reports; it does not edit your deliverable.
  • Specs drift. Profiles carry their source URLs so you can check them, and pull requests that update a number should quote the new clause.

Contributing

See CONTRIBUTING.md. The one hard rule: a numeric threshold without a citation will not be merged. A number without a source is an opinion, and the point of this project is to replace opinions with quotable clauses.

Licence

MIT — see LICENSE.

About

Batch-check SRT, WebVTT and ASS subtitles against real broadcaster delivery specs — reading speed, line length, durations, frame gaps — with the published source behind every number. Zero dependencies, runs offline.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages