Dubbing Reference
This document is the complete working reference for Dubbing: every data model field, the segment-type semantics, the translation timing contract, the audio compiler's mixing rules, the lip-sync mapping, the graphics pipeline, and the version schemas.
It is the technical companion to Dubbing, which explains the concepts and workflows. Read that first if you are new to the module; come here when you need the exact behavior of a field, a rule, or a version. Template authoring for translated lower thirds has its own guide: Designing Lower-Third Templates.
Table of Contents
- Overview
- The Source Item
- Segments & Types
- Language Versions
- Translation
- Voice Generation
- The Audio Compiler
- Lip-Sync
- On-Screen Graphics
- Graphics Rendering
- The Final Combine
- Review & Playback
- Exports
- Storage & Key Map
1. Overview
Dubbing lives at /inference/dubbing: a project list, and a two-mode editor — source mode for the original-language item and dubbed mode for each language version. Both modes share the same layout: the video player and stage controls on one side, the segment/graphics workspace on the other, with a resizable split that follows the user across devices.
A dubbing project is a small tree of JSON documents in the project's key/value store:
| Object | Count | Role |
|---|---|---|
| Source item | one per project | The original video, its transcript, speakers, separated audio stems, and the on-screen text inventory |
| Language version | one per target language | The translation layer plus everything rendered from it: generated takes, compiled audio, lip-synced video, rendered graphics, final cuts |
A language version's id is its source id plus the language code ({sourceId}:{lang}, e.g. 449926:en), and that id appears in the editor URL. Target languages: Arabic (ar), English (en), French (fr), Farsi (fa) — the source language is excluded from the list, and a language that already exists cannot be added twice.
Every long-running stage runs as a background task with a deterministic identity per item and stage. Progress is observable by anyone viewing the item, survives page reloads, and re-attaches when the editor reopens. The full key map is in §14.
2. The Source Item
Created from the list page by picking (or uploading) a video from the project's media library. The new item starts as draft with the media title as its reference.
| Field | Type | Notes |
|---|---|---|
reference | string | Display name — defaults to the media title, editable until locked |
description | string | Free-form notes |
status | enum | See below |
mediaId | string | The source video in the media library |
language | string | Source language, detected at transcription (two-letter code) |
segments | Segment[] | The transcript (§3) |
speakers | Speaker[] | { label, name, role } — labels are auto-assigned A, B, C… by diarization; name and role are editable |
corrections_log | Correction[] | { original, corrected, reason, timestamp } — transcription slips the AI fixed on the way in, kept for audit |
speechAudioFileId | file | The isolated speech stem from audio separation |
backgroundAudioFileId | file | The background stem (music, effects, ambience) from audio separation |
graphics | object | The on-screen text inventory (§9) |
Statuses
| Status | Applies to | Meaning |
|---|---|---|
draft | both | Working state |
locked | source | At least one language version exists — the transcript, timing, and reference are permanently read-only |
translating | language version | Initial machine translation in flight; returns to draft when it lands |
| archived | both | Soft delete — hidden from the default list, restorable from the Archived filter |
The three setup passes
Run together ("Set up media") or individually; each is a background task and each can be re-run later (re-transcribing replaces the transcript; re-separating replaces the stems; re-detecting replaces the inventory):
- Transcribe — speech-to-text with speaker diarization. Words are grouped per speaker and split into a new segment whenever that speaker pauses for more than 0.8 s; segments from different speakers may overlap in time — that is intentional, and the mixer is built for it (§7). Every segment arrives typed
lipsync(§3). - Separate audio — splits the soundtrack into the speech and background stems. The background survives into every dubbed mix; the speech stem supplies dub underlays and CC slices. Both stems are playable and downloadable from the editor.
- Detect on-screen text — the vision inventory of burned-in text (§9). Gated by a confirmation dialog with a cost estimate; limited to videos of 5 minutes or less.
3. Segments & Types
The transcript is an ordered list of timed, speaker-attributed segments:
| Field | Type | Notes |
|---|---|---|
id | number | Stable across the project — translations and takes in every language version key off it |
start / end | seconds | The slot. Rows with end ≤ start are flagged red (non-blocking) |
text | string | The spoken line, RTL-aware |
speaker | string | Speaker label (A, B, …) |
type | enum | dub | lipsync | cc | discard — unset counts as lipsync |
voice | string | Default voice for the segment — a voices-library entry; copied into each language version at translation time (§4) |
notes | string | Editorial notes, shown in the row |
Type semantics
The type decides the treatment in every downstream stage:
| Type | Voice required | Generates audio | In the compiled mix (§7) | In lip-sync (§8) |
|---|---|---|---|---|
lipsync | yes | yes | The take, tempo-fitted to the full slot | Frames re-animated to the take |
dub | yes | yes | The take with a 0.5 s lead-in, over the original speech ducked underneath | Frames re-animated to the take |
cc | no (cleared) | no | The original speech, sliced from the speech stem at full level | Frames untouched |
discard | no (cleared) | no | Excluded | Frames untouched |
Setting a segment to cc or discard clears its voice. A dub or lipsync segment without a voice renders a red ring; saving still works, but compiling will not (§7).
Editing operations
All transcript editing happens before locking; afterwards the source is read-only.
- Timecodes — every time field accepts
HH:MM:SS.mmm,MM:SS.mmm, or bare seconds. Arrow keys step ±1 frame (25 fps), Shift-arrows ±10 frames; the first arrow press pauses the player; a pull button grabs the current player time. Values clamp to the video duration. - Split — place the cursor in the text and split; the dialog previews both halves with a proportional split time (editable, or "Use player time") and assigns the second half a new id.
- Merge — adjacent segments join: texts joined with a space, notes with
;, and the first segment's speaker, type, and voice win. - Insert — the divider between rows creates an empty segment filling the time gap.
- Speaker scope — changing the type or voice of a segment whose speaker has more segments prompts for scope: this segment only, all by this speaker (only where unset), or all by this speaker (replace all).
Timeline view
The segment list toggles to a per-speaker timeline: one lane per speaker (ordered by first appearance), blocks colored by type, a time ruler, and a playhead synced to the video. Clicking a block seeks the player and activates the row. The timeline is the surface for spotting crosstalk — overlaps that the list view cannot show.
4. Language Versions
Created from the source item's Language Versions panel. Creating the first one locks the source (status: locked) after an explicit warning — translations are anchored to source segment ids and timing, so the foundation must stop moving. The new version starts as translating and machine translation begins immediately (§5).
A language version stores only the translation layer — everything time- and speaker-related stays on the source:
| Field | Type | Notes |
|---|---|---|
sourceKey / sourceLanguage / targetLanguage | string | Lineage back to the source item |
segments | DubbedSegment[] | One per source segment, matched by id — see below |
translatedGraphics | array | Per-element graphic translations (§9) |
compiledAudioVersions | array | §7 |
lipsyncedVideoVersions | array | §8 |
renderedGraphicsVersions | array | §10 |
renderedVideoVersions | array | §11 |
All four version arrays are ordered newest-first and displayed as v1…vN with a latest badge.
Dubbed segments
| Field | Type | Notes |
|---|---|---|
id | number | Matches the source segment id |
text | string | The translation, editable |
voice | string | Initialized from the source segment's voice at translation time; overridable per segment, per language |
audioFileId | file | The generated take (§6) |
audioDurationSecs | seconds | Take length after tempo-fitting — equals the slot |
rawAudioDurationSecs | seconds | Natural take length before fitting — drives the mismatch warning |
audioText / audioVoice | snapshot | The text and voice the take was generated from — drive the stale-audio signal |
translatedFromText | snapshot | The source text the translation was made from — drives the stale-translation signal |
underlayVolumeDb | number | dub segments only — original-speech level under the take, −24 to 0 dB, default −12 |
Staleness signals
Nothing regenerates silently; instead, three provenance checks flag what has drifted:
| Signal | Trigger | Surface |
|---|---|---|
| Stale translation | translatedFromText no longer matches the source segment's text | "Stale" badge on the row — re-translate or edit |
| Stale audio | audioText / audioVoice no longer match the row's text / voice | Warning on the take — regenerate |
| Duration mismatch | natural take length differs from the slot by more than 1 s | Warning that tempo was adjusted — usually a cue to shorten the translation |
5. Translation
Translation runs automatically when a language version is created, as a background task that fans out one sub-task per segment. Segments typed discard and segments with empty source text are skipped. Failed segments render as error rows with individual Retry buttons; the rest of the batch is unaffected. When the batch lands, the version's status returns to draft.
The timing contract
Each segment is translated against its slot, not just its words. The translation model receives the source text, the slot duration, and the language pair, and works to a duration tolerance that scales with the slot — tight on short lines, looser on long ones (on the order of ±0.5 s for lines up to 5 s, widening to ±3 s beyond 30 s). It fits by phrasing: choosing shorter synonyms, dropping redundancies, restructuring — never by cutting meaning. Per-language natural speaking rates anchor the length estimate.
Translations may carry bracketed delivery tags — [urgent], [relieved], and similar — which ride through to voice generation and steer the read.
Re-translation
Any segment can be re-translated at any time from its row, with optional free-text instructions ("more formal", "keep the official title in French"). Instructions apply to that call only and are not stored. The result lands in the editor as an unsaved edit — review, then save.
6. Voice Generation
A take is generated per segment, in the segment's assigned voice. A voice is eligible when its voices-library entry has a chosen preview take — voices without one appear disabled in the pickers.
- Single — each row has a generate (and later, regenerate) control; the take is playable inline.
- Bulk — Generate all missing audio covers every
dub/lipsyncsegment that has a voice, has text, and has no take yet.
Duration fitting
Every take is fitted to its slot in two passes: a tempo adjustment when the natural length is within 0.8×–1.3× of the target (beyond that range the take is trimmed or padded instead), then an exact-length lock so each take is sample-accurate to its slot. dub segments fit to the slot minus their 0.5 s lead-in — the breath of original speech heard before the voice-over enters. The natural pre-fit length is kept (rawAudioDurationSecs) and powers the mismatch warning of §4.
Each take snapshots the text and voice it was generated from, so later edits surface as stale audio rather than passing unnoticed.
7. The Audio Compiler
Compile assembles the full-length dubbed soundtrack from the takes, the original speech, and the background bed. It is versioned, and offered in two flavors: with background and speech only.
Gates
- Every non-discarded
dub/lipsyncsegment must have a voice assigned — the compiler groups segments by voice to build per-voice stems, so a missing voice blocks the compile with the offending segment ids listed. - There must be something to mix: at least one generated take, or at least one
ccsegment.
Lanes
Renderable segments are grouped by stem identity:
| Segments | Stem identity |
|---|---|
dub / lipsync | voice:{voice} — one stem per distinct voice |
cc | cc:{speaker} — one stem per CC'd speaker |
Within one stem, overlapping segments (possible after manual retiming) are split into parallel sub-lanes so each lane is internally sequential. Each lane is assembled as one continuous track from time zero — silence for the gaps, every piece locked to its exact slot — which preserves absolute timing and lets different speakers genuinely overlap in the mix.
The mix
- The authoritative output length is the longest of: the source video, the speech stem, the background stem, and the last segment's end. Every lane, stem, and the master are locked to it.
- All voice and CC lanes mix at full level.
- Each
dubsegment contributes an underlay: the matching slice of original speech, at the segment'sunderlayVolumeDb(default −12 dB), under the take. - With background enabled, the background stem sits under everything at −3 dB.
Output
A CompiledAudioVersion is prepended to the version list:
| Field | Notes |
|---|---|
audioFileId / durationSeconds | The master mix |
compiledAt | Timestamp |
includesBackground | Which flavor was compiled |
stems[] | { key, label, kind: voice | cc | background, audioFileId, durationSeconds } — one per voice, one per CC speaker, plus the background at full level |
The stems are the editorial hand-off: play or download any of them individually, take them into an external audio suite, and they are the exact inputs lip-sync consumes (§8). The compile toast reports telemetry — segment counts by type, lanes mixed, stems produced, and warnings such as a missing speech stem leaving dub underlays silent.
8. Lip-Sync
Lip-sync re-animates the speakers' mouths to match a compiled audio version — pick one (defaults to the latest) and generate. Two modes:
| Mode | Audio sent | Use |
|---|---|---|
| Multi-track (default) | One input per per-voice stem, plus a segment map | Precise: the model only works where someone is dubbed |
| Single-track | The master mix over the whole video | Fallback for compiles made before stems existed |
The multi-track contract
- Only voice stems are sent. The background stem (music degrades the model) and CC stems (those frames must not move) are never part of the payload.
- One mapping entry is built per source
dub/lipsyncsegment, pointing the segment's time range at its voice's stem.ccanddiscardranges are simply absent from the map — the model leaves those frames untouched, byte for byte. - Segment boundaries snap to the video's frame grid (at its real frame rate) and keep at least one frame of separation; ranges swallowed by the snapping merge or drop.
- Requirements enforced up front: every mapped segment has a voice, every voice has a matching stem in the chosen compile. A compile predating per-voice stems is rejected with a "recompile audio" message (or use single-track).
Resume safety
Generations are long, and the generation id is persisted the moment it exists:
- An interrupted wait re-attaches to the same generation instead of starting a new one.
- If the render finished upstream but the result never made it back, the version panel surfaces an amber Retry download action that fetches the finished output without re-running the generation. The pending marker clears on success.
Output
A LipsyncedVideoVersion: { videoFileId, durationSeconds, generatedAt, sourceAudioFileId, model, mode }. The sourceAudioFileId ties the video back to the compiled audio version that drove it — shown as a "from Audio vN" chip, and used by the final combine's re-mux rule (§11).
9. On-Screen Graphics
Detection
Detection inventories the source video's burned-in text. It is confirmed through a dialog with a cost estimate, capped at 5-minute videos, and produces:
| Field | Notes |
|---|---|
video | Frame size and duration the inventory was measured against |
elements[] | The inventory — see below |
sourceMediaFileId | The exact video file analyzed — if the media is later replaced, a staleness banner offers re-detection |
model / sampledFps / generatedAt | Provenance |
Each element:
| Field | Notes |
|---|---|
id | Stable — translations key off it |
value | The on-screen text |
in_time / out_time | Visibility window (seconds) |
bbox | { x, y, width, height }, normalized 0–1, top-left origin |
motion / trajectory | Moving graphics carry timed bbox keyframes; the overlay interpolates between them |
language | Detected language of the text |
category | subtitle | title | credit | lower_third | ui | watermark | logo | scene_text | other |
confidence | 0–1; low-confidence detections are dropped on ingest |
Review on the player
The inventory is edited both as rows (text, times, category, language, numeric bbox) and directly on the player overlay: click to select, drag to move, eight resize handles, arrow-key nudges (Shift for coarse), Delete to remove, "Add graphic" at the playhead for anything the model missed. Category chips filter the overlay; an optional calibration grid helps with precise placement; elements with unknown timing render dashed and always-visible until timed. Like the transcript, graphics editing closes when the source locks.
Translation
Each language version translates the inventory: a bulk pass over all elements (failures fall back to the original text), plus per-element re-translation. Brand names, handles, hashtags, URLs, and numbers pass through untranslated; casing and line breaks are preserved. Each translation snapshots the source text it was made from (translatedFromValue), so source-side edits surface as Stale badges. The player overlay swaps in translated values, previewing placement before anything renders.
10. Graphics Rendering
Rendering (the first of the two graphics phases) turns translated lower thirds — elements categorized lower_third with a non-empty translation; other categories are inventory-only — into broadcast-ready clips:
- Each translation splits into a kicker (first line) and headline (the rest).
- Each graphic renders through an HTML template in a headless browser into a transparent animated clip — alpha-true, cropped to the graphic's band, designed at 1920×1080. The template owns placement and animation; see Designing Lower-Third Templates.
- All clips composite over a checkerboard into a preview video, so the full set is reviewable at a glance.
Rendering is all-or-nothing: if any clip fails, everything produced in that run is discarded and no version is written. On success a RenderedGraphicsVersion is prepended:
| Field | Notes |
|---|---|
id / renderedAt | Version identity (timestamp) |
templateId | The template used |
clips[] | { graphicId, clipFileId, in_time, out_time, durationSeconds, x, y, w, h } — the band coordinates in the 1920×1080 design space |
previewVideoFileId / previewDurationSeconds | The checkerboard preview |
Clips belong to their graphics version and are retained — every final combine that uses the version reuses the same clips, with no re-render.
11. The Final Combine
The combine (the second graphics phase) produces the deliverable MP4 from two choices:
| Choice | Options |
|---|---|
| Base | A compiled audio version laid over the source picture, or a lip-synced video |
| Graphics | A rendered graphics version — or Skip graphics for a clean cut with no overlays |
Rules:
- Lower-third clips scale from the 1920×1080 design space to the base video's real dimensions and are burned in only during each clip's
in_time–out_timewindow. - Lip-sync bases are re-muxed: the combine drops the lip-sync output's re-encoded audio and lays the original compiled audio version (the one that drove the lip-sync) back over the picture — the soundtrack of the final cut is byte-identical to what was reviewed in the editor.
- Nothing upstream is re-run: bases and graphics versions mix and match freely.
Each run prepends a RenderedVideoVersion:
| Field | Notes |
|---|---|
videoFileId / durationSeconds / renderedAt | The final cut |
burnedCount | How many lower thirds were burned |
baseKind | compiledAudio | lipsync |
sourceAudioFileId / sourceVideoFileId | The exact base inputs |
graphicsVersionId | The graphics version used, absent when skipped |
Version rows render the lineage as chips — "Graphics v2", "Audio v3" or "Lip-sync v1" — with a "(deleted)" fallback when an upstream version has since been removed.
12. Review & Playback
- Audio source toggle — play the picture against the original soundtrack or the dubbed compile (which mutes the video's own audio).
- Sync audio & video — locks the audio bar and the player together: play, pause, and seeks mirror both ways, with buffering handled on either side.
- Continuous playback — plays the generated takes segment by segment, auto-advancing in timeline order; discarded segments are skipped. Disabled (with the reason) while conflicting tasks run.
- Stems — every compiled version expands to its stems, each individually playable and downloadable.
- Rendered videos — lip-sync results, graphics previews, and final cuts — open in the floating picture-in-picture player and download with versioned filenames.
- Every item keeps a full revision history panel: who changed what, field by field.
13. Exports
The transcript and translations export from the editor header in four formats:
| Format | Content |
|---|---|
| CSV | Segment table for spreadsheets |
| SRT / WebVTT | Subtitle files on source timing |
| Print-ready dubbing script (browser print — full Unicode and RTL support) |
Rules: discard segments are excluded everywhere; in dubbed mode the export pairs source timing with the translated text (falling back to source text where untranslated); speaker labels resolve to their display names; files are named {reference}-{language}.
14. Storage & Key Map
Every dubbing object is a JSON value in the project's key/value store:
| Key | Value |
|---|---|
inference:dubbing:{id} | Source item (§2) |
inference:dubbing:{id}:{lang} | Language version (§4) — two-letter language code |
voices:{id} | Voices-library entry referenced by segment voice assignments |
tasks:{stage}:{key} | Background task progress — see below |
Task identities
Tasks are deterministic per item and stage — re-requesting a running stage attaches to the existing task instead of starting a second one, and anyone opening the item sees its progress:
| Stage | Task key |
|---|---|
| Transcribe / separate audio / detect graphics | {stage}:{sourceId} |
| Translate / generate takes | {stage}:{sourceId}:{lang} |
| Translate graphics / render graphics / lip-sync / final combine | {stage}:{sourceId}:{lang} |
Deletion & preferences
Archiving a dubbing item is a soft delete: it disappears from the default list and returns via the Archived filter. Per-user interface preferences — the editor split position, the active workspace tab, and the list/timeline segment view — are stored per user and follow the user across devices.
