ADR-0014: Subtitles in the instance's languages for every title, made on the platform where a title has none
Status: Proposed · 2026-10-20. Design ahead of the code: a spike measures it first (see Validation).
Context
Section titled “Context”A title plays with the subtitles its source brings, nothing more. The source decides:
- In one library of about 2,000 movies, 55 % bring no subtitle at all, 37 % an English one and 10 % a German one. A viewer who does not follow the audio's language cannot watch most of it.
- An instance already names its languages: the packager's language whitelist (
packager.language_whitelist, for exampleen, de, zh) decides which audio and subtitle tracks a player shows. The other tracks are packaged but hidden. Nothing makes sure those languages exist. - Fetching subtitles from outside providers depends on third parties, accounts, rate limits and on matching the exact release, or the timing is off. It may come as an addon; the platform should not depend on it.
Speech recognition and translation models that run on one consumer GPU are now good enough to close the gap: a subtitle can be made from the title itself.
Decision
Section titled “Decision”Every title gets a subtitle in each of the instance's languages, made by the platform where the title lacks one.
flowchart LR
P[Title packaged] --> C{Each instance language:<br/>a subtitle in it?}
C -->|yes| K[Keep the source's]
C -->|other script| S[Convert the script<br/>Traditional → Simplified]
C -->|another language| T[Translate the best subtitle<br/>the title has]
C -->|none at all| R[Transcribe the audio] --> T
S --> W[WebVTT beside the package,<br/>registered, marked as made]
T --> W
- The languages are the instance's. A setting (
subtitles.languages, default the packager's whitelist) names them with their script where it matters, for exampleen, de, zh-Hans. A new pipeline step,subtitles, runs after a title is packaged and makes sure each one exists. - The order of sources, per language:
- a subtitle the source brings in that language, as today;
- a subtitle from an outside provider, when an addon brings one: the acquisition addon searches a provider with an account the operator gives it, matching the release first by the file's hash, and attaches what it finds through the core's seam (5);
- the same language in another script: Traditional Chinese becomes Simplified by a dictionary conversion, no model involved;
- a translation of the best subtitle the title has, its original language first, then English: by a language model, in batches of cues with the surrounding cues as context. Cue timing is kept, and lines are re-wrapped to the target language's limits;
- when the title has no subtitle at all, a transcription of its main audio (speech recognition, in the audio's language), then 4 from it.
- Made subtitles say so. Their label says how they were made ("German · AI"). The catalog records their origin (
transcribed,translatedorconverted), the model and its version. They are listed after the source's own, never replace one, and a viewer can report one as wrong. A report puts the title back in the queue with the next candidate. - On the platform's GPUs, as batch work. It runs under ADR-0013's priorities, so live encodes come first. The models run in their own worker processes, like the live worker, never in a service that serves viewers. They are open-weight models whose licences allow this use (for speech, a Whisper-class model; for translation, an openly licensed instruction model). The spike picks the two.
- Files beside the package, through one seam. The core offers one way to attach a subtitle to a title (its language and script, label, forced and SDH flags, and origin:
source,provider,transcribed,translatedorconverted). It writes WebVTT into the version'ssubs/and registers it, so a player lists it at once. An addon and the platform's own pipeline both use it. No video is encoded or packaged again, and a subtitle costs kilobytes. - A paced backlog. New arrivals get theirs after packaging. The existing library is worked through at a pace, most-watched and newest first, and every title's step can be restarted.
Validation
Section titled “Validation”Before any of it is built, a spike measures, on one GPU and about twenty library titles (movies and episodes, several audio languages):
- Speech recognition: speed (× real time) and, where the source brings a subtitle in the audio's language, the word error rate against it.
- Translation: speed, and quality against a human subtitle where the title has both languages (automatic scores plus samples for a person to read), for German and Simplified Chinese.
- Resources: GPU memory and time beside a running live worker.
The design holds if a two-hour film without subtitles gets all three languages in under 15 minutes of GPU time, and if a reader finds the translated subtitles fit to watch with.
Consequences
Section titled “Consequences”- Every title becomes watchable in the instance's languages, without anyone asking and without an outside service.
- The cost is GPU time per title, bounded and paced, and kilobytes of storage.
- A made subtitle can be wrong: timing from a transcription, a mistranslated idiom. The label says so, the source's own subtitles stay first, and a report makes it again.
- The languages an instance shows and the languages it makes are one setting.
What this rules out
Section titled “What this rules out”- No fixed languages. The instance names them.
- No outside sources in the core. The core fetches nothing from outside; an addon may, through the seam, and the platform's own pipeline makes what no source has.
- No replacing a source's subtitle. A made one is added beside it.
- No burning subtitles into the picture. They stay a track a viewer turns on or off.
- No model in a serving process. The models run in worker processes only.