CLI · commit e3e0076

srtlate

Context-aware subtitle translator

Download srtlate.zip
Quickstart
unzip srtlate.zip && cd srtlate
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/python -m pytest -q

srtlate

Context-aware subtitle translator for SRT and VTT files. It keeps timing intact, re-wraps lines to a readable 42x2 layout, keeps names and terms consistent across a whole film through an editable glossary, and does it cheaply: one Claude Haiku 5.5 call per ~60 cues, usually well under a cent per film.

Features

Install

uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
source .venv/bin/activate
export ANTHROPIC_API_KEY=...   # only needed for real translation

Quickstart

srtlate estimate examples/lighthouse.en.srt --to es
srtlate translate examples/lighthouse.en.srt --to es --dry-run
# offline demo against a recorded fixture, no key needed:
srtlate translate examples/lighthouse.en.srt --to es --fake examples/lighthouse.fixture.json
# real run (needs a key):
srtlate translate examples/lighthouse.en.srt --to hi --formality informal

Usage

srtlate translate movie.srt --to hi [--from auto] [--out movie.hi.srt]
        [--glossary g.json | --no-glossary] [--formality formal|informal]
        [--bilingual] [--batch-size 60] [--model ...] [--dry-run] [--fresh]
        [--no-cache] [--cache-dir DIR] [-v]
srtlate glossary movie.srt --to hi [--out movie.glossary.json]
srtlate estimate movie.srt --to es

The default output name replaces a trailing language tag in the input name (movie.en.srt -> movie.hi.srt). The output format follows the --out extension, so --out movie.vtt converts.

Estimate (no key needed; real output)

$ srtlate estimate examples/lighthouse.en.srt --to hi --formality informal
120 cues in 3 batches (4 API calls)
input  ~2,570 tokens
output ~4,378 tokens
cost   ~$0.0024 (Haiku 5.5: $0.10 / 1M in, $0.50 / 1M out; rough, +/-30%)

Estimates use a character-based token heuristic and a per-language output expansion factor, so expect roughly +/-30%.

Dry run (no key needed; real output, abridged)

$ srtlate translate examples/lighthouse.en.srt --to hi --formality formal --dry-run
=== glossary call: user ===
Source language: the source language (detect it). Target language: Hindi.
Candidates (term, count, example):
Mira (7x): Mainland base, this is Mira at Lantern Point.
Bram (7x): Come on, Bram's old hinge, give me a break.
...
=== translate call (batch 1): user ===
Translate from the source language (detect it) to Hindi.
Register: formal (use aap and its verb forms).
Cues to translate (a blank line marks a scene change):

[1] <i>Mainland base, this is Mira at Lantern Point.</i>
[2] <i>Do you copy? Over.</i>
...
[32] - Is anyone up there?
  - Yes! Who is that?

Offline demo against the fixture (real output)

--fake answers from examples/lighthouse.fixture.json instead of the API. It exercises the full pipeline (glossary, batching, validation, wrapping, writing) with recorded Spanish translations.

$ srtlate translate examples/lighthouse.en.srt --to es --fake examples/lighthouse.fixture.json -v
glossary: 6 terms -> examples/lighthouse.es.glossary.json (edit and pass --glossary to reuse)
batch 1/3 done
batch 2/3 done
batch 3/3 done
wrote examples/lighthouse.es.srt (120 cues)
2 cues need a look:
  cue 10: speed (21.8 chars/sec)
  cue 101: speed (22.1 chars/sec)
usage: 4 calls, 0 cache hits, 1,706 in / 2,492 out, $0.0014

First cues of the result (the long Spanish line was re-wrapped and the italics re-closed per line):

1
00:00:01,000 --> 00:00:04,520
<i>Base del continente, habla</i>
<i>Mira, desde Punta Linterna.</i>

2
00:00:04,870 --> 00:00:06,770
<i>¿Me recibe? Cambio.</i>

With --bilingual --out x.vtt:

WEBVTT

00:00:01.000 --> 00:00:04.520
<i>Mainland base, this is Mira at Lantern Point.</i>
<i>Base del continente, habla</i>
<i>Mira, desde Punta Linterna.</i>

Glossary

{
  "to": "es",
  "terms": { "Mira": "Mira", "Tomas": "Tomás", "Lantern": "Linterna" }
}

translate writes this next to the output when it extracts one. Edit it and pass --glossary file.json to pin renderings; a user glossary skips the extraction call. A flat {"source": "target"} file also works.

Sections above that call the model for real (plain translate without --fake/--dry-run, and glossary without --fake) require ANTHROPIC_API_KEY; live calls were not exercised when this README was written because no key was available.

Configuration

Setting Default
ANTHROPIC_API_KEY required for live calls (or ant auth login)
--model / SRTLATE_MODEL claude-haiku-5-5
SRTLATE_CACHE ~/.cache/srtlate
--batch-size 60

Errors are reported cleanly: missing credentials, rate limits (progress is kept, re-run to resume), bad requests, connection and server errors, and refusals.

Architecture

Module Role
subs.py SRT/VTT parse and write
scenes.py scene grouping and batch cutting
prompts.py system prompts, JSON schemas, prompt builders
llm.py Haiku wrapper: structured output, usage, cost, disk cache
glossary.py candidate extraction, glossary call, glossary file IO
translate.py batching, id validation and retry, context, resume sidecar
wrap.py wrapping, tag repair, reading speed
render.py applies translations to cues, flags, bilingual and RTL
estimate.py token and cost estimate
fake.py fixture-backed stand-in client for offline demos
cli.py click commands

See docs/design.md for the algorithms.

Tests

.venv/bin/pytest -q     # 58 tests, all use fake clients, no network

Limitations