Why Do AI-Translated Subtitles Lose Their Timing? How to Keep SRT Timecodes Aligned

By Flora Wang, video localization specialist · Updated September 20, 2026 · 8 min read

TL;DR: AI-translated subtitles can appear to lose their timing when a model merges, drops, or reorders SRT lines. GeekLink keeps the original start and end timecodes outside the AI request, sends only subtitle IDs and text for translation, and requires one structured result per ID. OpenRouter JSON Schema enforcement and application-level validation then catch missing or malformed lines before they enter the translated SRT.

Why AI-Translated Subtitles Lose Their Timing

A subtitle translator does not return one paragraph. It returns a collection of small, position-sensitive edits. Every translated line has to stay attached to the correct subtitle ID, and an empty or missing line can silently damage the final SRT.

There are two different problems that are often described as “timecode drift.” The source SRT may already be out of sync with the video; translation cannot repair that. Or the translation workflow may change the number or order of subtitle lines, creating an apparent timing shift in the translated file. This article addresses the second problem.

A common failure mode is sending the entire SRT — numbering, timecodes, and text — to the model. If the model merges two lines, omits one, or returns items out of order, the translated file can develop a gap or shift the timing of every line that follows.

The safer boundary is to keep timing in the subtitle pipeline and send only the text plus a stable ID to the translation engine. After translation, the application puts each result back onto the original subtitle entry. That prevents the model from rewriting timecodes, while structured output prevents it from silently changing the line mapping.

The first version of this workflow relied heavily on the prompt: “Return a JSON object with an ID and a translation for every line.” Models usually followed that instruction. Usually is not a protocol, though. A response could still arrive with a Markdown code fence, a missing ID, an extra field, a blank translation, or a valid JSON object that simply omitted one item.

Those failures are especially expensive in batch translation. If the application treats every malformed response as a reason to split the batch, one formatting mistake can turn a single request into a chain of smaller requests. The model gets less context, the user spends more credits, and the original problem — the response shape — has not been fixed.

What OpenRouter Structured Outputs Adds

OpenRouter's structured outputs guide describes a different contract: send a response_format with type: "json_schema", provide the schema, and ask the router to use a provider that supports the requested parameter. OpenRouter specifically recommends checking model support and setting require_parameters: true when the parameter must be honored.

That moves an important part of the contract out of the prompt and into the request itself. The model still has to translate well, but the application no longer has to hope that the model remembers the shape of the response.

{
  "type": "json_schema",
  "json_schema": {
    "name": "subtitle_translations",
    "strict": true,
    "schema": {
      "type": "object",
      "properties": {
        "items": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": { "type": "string" },
              "translation": { "type": "string" }
            },
            "required": ["id", "translation"],
            "additionalProperties": false
          }
        }
      },
      "required": ["items"],
      "additionalProperties": false
    }
  }
}

The important details are not cosmetic:

  • Strict mode asks the compatible provider to follow the declared shape.
  • Required fields make an ID and a translation part of every item.
  • No additional properties keeps the response small and predictable.
  • Provider parameter requirements prevent the request from being silently routed to a provider that ignores the schema.

GeekLink's Structured Translation Pipeline

1. One named contract, kept on the server

The app asks for a named contract — subtitle_translation_v1 — rather than sending an arbitrary schema from the client. The schema stays in the service that owns the translation request. That gives us one place to update the contract and prevents unrelated text or JSON requests from accidentally receiving subtitle-specific fields.

2. A provider allowlist based on actual support

Structured output is not a universal switch. OpenRouter's model catalog exposes supported parameters per model. GeekLink enables the contract for the OpenRouter models in our current translation list that advertise both response_format and structured_outputs. Models that do not advertise those parameters keep the older prompt-based path instead of being sent a request they cannot honor.

3. A second validation layer inside the app

Native schema enforcement reduces malformed responses; it does not know which subtitle IDs were in this batch. After the response arrives, GeekLink checks the application-level invariants:

  1. Every returned ID belongs to the requested subtitle batch.
  2. Every requested ID is present exactly once.
  3. Every translation is a non-empty string.
  4. No valid translation is silently overwritten by an unrelated field.

This distinction matters. JSON Schema can say “an ID is a string”; only the application can say “this string must be subtitle 117 from the current batch.”

4. The response is classified before recovery begins

When OpenRouter returns a structured response, GeekLink preserves the machine-readable fields that explain how it ended: the upstream error type, the finish reason, and a refusal message when present. That lets the retry policy react to evidence instead of searching the provider's prose for guessed keywords.

What Happens When a Request Fails

Not every failure means “make the batch smaller.” The current policy is intentionally narrow:

  • Malformed JSON, an empty translation, a missing ID, or an incomplete response: retry the same batch once. Valid items from both attempts are retained; the remaining items fall back for review.
  • Context or request-size limits: split the batch at most once, because the upstream response explicitly says that the request is too large.
  • A refusal or content-policy response: split once because a smaller batch can contain lines that are independently processable.
  • Timeouts, rate limits, provider overload, or temporary server errors: retry the exact same batch. Splitting does not repair a network or capacity problem.
  • An unknown error: stop and surface the failure instead of spending more credits on guesses.

There is one more accounting detail: when the Worker has already retried an upstream request, it passes that retry count to the app. The app does not blindly repeat the same retry cycle, and it does not submit the already-attempted request to a second API endpoint.

What Structured Output Does Not Guarantee

Structured output is a reliability improvement, not a claim that every translation is perfect. A model can return beautifully valid JSON with the wrong word, an awkward name, or a translation that needs human context. Schema enforcement protects the handoff between the model and the subtitle editor; it does not replace linguistic review.

That is why GeekLink still keeps the subtitle IDs visible, validates the complete batch, and treats the remaining lines as reviewable output rather than silently declaring success. The goal is not to hide uncertainty. It is to make uncertainty local, inspectable, and cheaper to fix.

FAQ

Does JSON Schema guarantee a correct translation?

No. It guarantees a response shape for compatible providers. The translation can still be semantically wrong or stylistically awkward, so human review remains useful for names, jokes, cultural references, and specialist terminology.

Why not ask every model to return JSON in the prompt?

Prompt instructions are helpful but soft. A native response format gives the provider a machine-readable contract and lets the router filter for providers that support the requested parameter.

Will a malformed response cause the whole subtitle batch to be split?

No. A format or validation error retries the same batch once. Splitting is reserved for explicit size-limit or refusal signals, and it is limited to one level.

How does GeekLink keep translated subtitles aligned with the original SRT?

GeekLink keeps the original start and end timecodes in the subtitle pipeline, sends only each line's text and stable ID for translation, and reattaches the result to the matching original entry. The AI does not receive the timecodes to rewrite.

Can this fix an SRT that is already out of sync with the video?

No. This workflow preserves the source timecodes; if the original SRT is already offset from the video, use a subtitle-alignment step first. The workflow here prevents the translation step from introducing a new line-mapping shift.

Disclosure: GeekLink is our own product. This article describes the current subtitle translation workflow and the OpenRouter structured-output interface it uses. Model support and provider behavior can change over time, so the linked official documentation remains the source of truth for compatibility.

Make subtitle review smaller and more predictable

GeekLink turns video speech into editable subtitles, translates batches, and keeps each line attached to the right place.

Download GeekLink