Cerlancism/chatgpt-subtitle-translator: line-locked SRT translation with the OpenAI API
Efficient translation tool based on ChatGPT or any OpenAI compatible LLM chat completion API
At a glance
- What is it?
- The project exists because LLMs translate well but drift on line boundaries. It strips SRT overhead, batches entries, and forces a one-to-one input/output match. Here is how it installs, what each structured mode costs you, and where it breaks.
- Who is it for?
- Adopt it if you already have an OpenAI key, run Node 20 or newer, and your subtitle files are well-formed SRT or plain text you can process from a shell. Skip it if you need a browser-only tool with no API key, or if your source is a format other than .srt or plain text, since the CLI documents no other input.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The line-drift problem this project was built around
The README states the case plainly: LLMs handle common natural languages with contextual understanding and even scrambled writing, but they do not always produce deterministic output or adhere to line-to-line correlation, and that disrupts subtitle timing even at temperature 0. Anyone who has translated an .srt by pasting it into a chat window has seen the result. Timestamps survive, cue counts do not, and a two-line cue becomes one line or three.
This tool is for people who want the translation quality of a chat model without re-timing the file afterward. The audience is narrow and specific: engineers and subtitle editors comfortable with a shell, who have an OpenAI API key or a local OpenAI-compatible endpoint such as Ollama, and who treat the .srt as data rather than as a document to be edited by hand. If you want a point-and-click desktop app that hides the API entirely, this is not it. The web interface exists, but the interesting controls (context budgeting, moderation checks, progress resumption) are CLI only.
How batching and the structured modes keep cue counts aligned
The mechanism has three parts. First, the translator removes SRT overhead, meaning the sequence numbers and timestamp lines, so the model is not spending tokens on data it does not need to reason about. Second, it groups the remaining text into batches sized to fit the token limit. Third, it asks the model for a structured response and checks the output against the input.
The `--structured` flag selects how that check works. In `array` mode the input and output are arrays and a mismatch triggers a retry. In `object` mode they are keyed objects. In `timestamp` mode the model receives each entry's timing as a batch start offset plus per-entry offset and length in milliseconds, which lets it merge adjacent entries into one cue. The README is explicit about the trade-off: a timestamp batch is only retried when the output time span boundaries do not match the input, unlike the other modes which retry on any line count mismatch, and that significantly reduces token wastage from retries. The cost is more tokens per batch and no progress-file resumption, because the output entry count may legitimately differ from the input. The `none` mode disables structured output for legacy compatibility.
Batching is where the design gets opinionated. When `--batch-sizes` is omitted, the size is derived per batch from the `--context` token budget: each batch is sized so that several batches of history fit in the portion of the context window freed by a trim, keeping the prompt cache warm. On failure the size is reduced and retried, and once it reaches the minimum the translator falls back to single-entry mode. After a reduction it eases back up stepwise following consecutive successful batches. That is a feedback loop, not a fixed setting, and it means your throughput depends on how well the model behaves on your specific file.
Installing it and translating a file for the first time
The README assumes a bash environment and Node.js version 20 or higher, which matches the `engines` field in package.json. Clone the repository and install dependencies:
git clone https://github.com/Cerlancism/chatgpt-subtitle-translator
cd chatgpt-subtitle-translator
npm install
chmod +x cli/translator.mjsThe `chmod` step matters if you intend to call the script directly rather than through `node`. Next, copy the example environment file and put your key in it:
cp .env.example .env`.env.example` lists `OPENAI_API_KEY` as required. It also documents optional keys you will want later: `OPENAI_API_RPM` for the translation endpoint, `OPENAI_API_MODERATOR_RPM` for moderation, `OPENAI_DEFAULT_MODEL` for the model, `OPENAI_BASE_URL` for a custom provider, `SUBTITLE_LINE_ENDING`, and `HTTP_PROXY` or `HTTPS_PROXY`. To point at a local Ollama instance, the example gives `OPENAI_BASE_URL=http://localhost:11434/v1`.
With the key in place, check the option surface before spending tokens:
cli/translator.mjs --helpThe README shows the usage line as `Usage: translator [options]` and describes the tool as a translation tool based on the ChatGPT API. The documented options include `--from`, `--to`, `--system-instruction`, `--input`, `--output`, `--structured`, `--context`, `--batch-sizes`, and `--guard-repetition`. The `--from` and `--to` options build the system instruction, defaulting to an empty source and English as the target.
The output file name defaults to something based on the input name if you omit `-o`. Streaming output goes to the terminal as the run proceeds, so you should see entries arriving rather than a long silence followed by a finished file.
Where the tool wastes your money or fails outright
Every retry costs tokens, and retries are the normal case, not the exception. The README says mismatched output line quantities or exceeding the token limit cause token wastage and require resubmitting the batch with a smaller size. The `--guard-repetition` flag exists for the same reason: it defaults to a threshold of 10, and when the model falls into a repetition loop during streaming the response is aborted and retried with a smaller batch. Setting it to 0 disables detection entirely, which is a decision you should make deliberately.
The `timestamp` mode removes the line-count retry but adds a different failure mode. Because the model can merge entries, the output does not correspond one-to-one with the input, and the README states that progress file resumption is not supported in that mode. If a long translation dies halfway, you start over. That is a real cost on a feature-length file, and it is the clearest case where the mode that saves tokens on retries can cost you more in wall-clock time.
The `--use-moderator` flag is CLI only and optional. Its stated purpose is to prevent token wastage if the model is highly likely to refuse to translate, which tells you the project has seen refusals in practice. It is a pre-check, not a guarantee, and it adds requests against a separate rate limit configured by `OPENAI_API_MODERATOR_RPM`.
Finally, the input surface is thin. The README describes `-i` as accepting `.srt` format or plain text. There is no documented support for `.ass`, `.vtt`, or embedded subtitle streams, so anything else has to be converted before this tool sees it.
The alternative: a general chat client or a dedicated MT pipeline
The obvious alternative is pasting the subtitle into a chat interface and asking for a translation. That approach has no token accounting, no batching, no rate limiting, and no structural guarantee that the cue count survives. It is fine for a five-minute clip you will proofread anyway. It is not fine for a season of episodes, because the failure is silent until you open the file in a player.
The other alternative is a conventional machine translation pipeline, the kind built on a purpose-trained MT model rather than a chat completion endpoint. Those systems are deterministic in the sense that the same input produces the same output, and they typically preserve segment structure by construction rather than by prompting and retrying. What they do not give you is the contextual handling the README points to, including unconventional writing such as word scrambling, or the ability to steer the output with a custom system instruction. The choice is between structural guarantees from a purpose-built pipeline and contextual quality from a model you have to constrain. This project sits on the second side and spends its complexity budget on the constraint.
Licence, dependencies, and what an upgrade actually costs
The repository is MIT licensed, and package.json declares the same. That permits commercial use and modification, but it is a permissive licence with no warranty, and the project bundles no model. Your API costs, your provider's terms, and your data handling obligations are separate from the licence and are not addressed by it. The README does not discuss data retention or what your provider does with submitted subtitle text. Treat that as an open question for your own compliance review rather than something the project answers.
On maintenance, the last push to the default branch was on 2026-09-13, and the most recent tagged release is v3.3.2 from 2026-04-13, following v3.3.1 and v3.2.0 earlier that April. The repository is not archived. The gap between the release tag and the latest commit suggests work continues on `main` between releases, but the README does not describe a release cadence, so plan for upgrades rather than assuming them.
Upgrade cost is not trivial, and the project says so. The README points v2 users at a migration guide in docs/CHANGELOG.md for breaking changes in 3.0.0. The dependency list is also a moving target: `openai` is pinned at 6.33.0, `zod` at 4.3.6, and `llm-summary` is pulled directly from a GitHub commit hash rather than a registry version. That last one means `npm install` fetches a specific commit, and updating it is a deliberate act rather than a version bump. Node.js 20 or newer is required, so an upgrade plan that keeps you on an older runtime will not work.
Editorial conclusion
Adopt it if you already have an OpenAI key, run Node 20 or newer, and your subtitle files are well-formed SRT or plain text you can process from a shell. Skip it if you need a browser-only tool with no API key, or if your source is a format other than .srt or plain text, since the CLI documents no other input. Before you translate anything you care about, verify three things: that your provider honors the structured-output request in the mode you chose, that the model name in OPENAI_DEFAULT_MODEL actually exists on that endpoint, and that the batch size you pick survives a real file without falling back to single-entry mode.
Frequently asked questions
Does chatgpt-subtitle-translator require an OpenAI API key?
Yes. The `.env.example` file lists `OPENAI_API_KEY` as required, and the README setup steps tell you to add your key to the newly created `.env` file. You can point `OPENAI_BASE_URL` at any OpenAI-compatible provider, including a local Ollama instance, but an API key is still expected in the configuration.
What is the difference between the array, object, and timestamp modes in chatgpt-subtitle-translator?
The `array` and `object` modes structure input and output as arrays or keyed objects and retry on any line count mismatch. The `timestamp` mode passes entry timings to the model so it can merge adjacent entries, and it only retries when output time span boundaries do not match the input, which the README says significantly reduces token wastage from retries.
Can chatgpt-subtitle-translator resume an interrupted translation?
Progress resumption is available in the CLI, but the README states it is not supported in `timestamp` structured mode because the output entry count may differ from the input. In the other modes you can resume from the progress file rather than starting the whole file again.
Can I use chatgpt-subtitle-translator with a local model instead of OpenAI?
Yes. The README states it supports any OpenAI API compatible provider, and `.env.example` gives the example `OPENAI_BASE_URL=http://localhost:11434/v1` for Ollama running locally. Structured output support depends on the provider, so the mode you choose may behave differently against a local endpoint.
What input file formats does chatgpt-subtitle-translator accept?
The `-i, --input` option is documented as accepting `.srt` format or plain text. No other subtitle format is listed, so files in other formats need converting before use.
What happens if chatgpt-subtitle-translator produces the wrong number of lines?
In the array and object modes a line count mismatch triggers a retry with a smaller batch size, stepping down through the `--batch-sizes` values until it reaches one. If the batch size reaches the minimum, translation falls back to single-entry mode.
Community notes