AI dubbing¶
videopython.ai.dubbing — translate speech, clone the voice, and re-time the dub onto the
source. Whisper for transcription, a local Ollama model for translation, Chatterbox for
TTS, Demucs for source separation. Task recipes are in
Dub a video into another language.
VideoDubber¶
Four entry points:
| Method | Input → output | Notes |
|---|---|---|
dub_file(input_path, output_path, ...) |
path → file | Never loads frames; video is stream-copied |
dub(video, ...) |
Video → DubbingResult |
|
dub_and_replace(video, ...) |
Video → Video |
Convenience over dub |
revoice(video, text, ...) / revoice_and_replace(...) |
Video → result / Video |
New words, original voice |
dub, dub_and_replace and dub_file all accept a pre-computed transcription. Speaker
labels on it drive per-speaker voice cloning; the diarize-on-supplied path needs
word-level timings, so SRT-loaded transcriptions (one synthetic word per block) are
rejected.
dub_file copies subtitle streams through automatically and gain-matches the dub to the
source with BS.1770 integrated loudness (pyloudnorm; falls back to peak match under
400 ms, post-gain peaks clamped to 0.99). keep_original_audio=True retains the source
audio as a secondary track.
VideoDubber
¶
Dubs videos into different languages using the local pipeline.
Accepts either a :class:DubbingConfig or the same knobs as flat kwargs
(device, low_memory, whisper_model, translator_model, etc.)
-- the flat path builds a DubbingConfig internally. See
:class:DubbingConfig for the full knob list and defaults.
Source code in src/videopython/ai/dubbing/dubber.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | |
dub
¶
dub(
video: Video,
target_lang: str,
source_lang: str | None = None,
preserve_background: bool = True,
voice_clone: bool = True,
enable_diarization: bool = False,
progress_callback: Callable[[str, float], None]
| None = None,
transcription: Any = None,
) -> DubbingResult
Dub a video into a target language.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enable_diarization
|
bool
|
Enable speaker diarization to clone each speaker's
voice separately. With |
False
|
transcription
|
Any
|
Optional pre-computed |
None
|
Source code in src/videopython/ai/dubbing/dubber.py
dub_and_replace
¶
dub_and_replace(
video: Video,
target_lang: str,
source_lang: str | None = None,
preserve_background: bool = True,
voice_clone: bool = True,
enable_diarization: bool = False,
progress_callback: Callable[[str, float], None]
| None = None,
transcription: Any = None,
) -> Video
Dub a video and return a new video with the dubbed audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transcription
|
Any
|
Optional pre-computed |
None
|
Source code in src/videopython/ai/dubbing/dubber.py
dub_file
¶
dub_file(
input_path: str | Path,
output_path: str | Path,
target_lang: str,
source_lang: str | None = None,
preserve_background: bool = True,
voice_clone: bool = True,
enable_diarization: bool = False,
progress_callback: Callable[[str, float], None]
| None = None,
transcription: Any = None,
keep_original_audio: bool = False,
) -> DubbingResult
Dub a video file in place on disk without loading video frames into memory.
Extracts the audio track via ffmpeg, runs the dubbing pipeline on the audio only, then muxes the dubbed audio back into the source video using ffmpeg stream-copy (no video re-encode). Peak memory is bounded by model weights and the audio track — independent of video length and resolution.
Use this instead of dub_and_replace when the source video is long
or high-resolution and you don't need frame-level access in Python.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_path
|
str | Path
|
Path to the source video file. |
required |
output_path
|
str | Path
|
Path to write the dubbed video. Overwritten if it exists. |
required |
target_lang
|
str
|
Target language code (e.g. |
required |
source_lang
|
str | None
|
Source language code, or |
None
|
preserve_background
|
bool
|
Preserve background music/effects via source separation. |
True
|
voice_clone
|
bool
|
Clone the source speaker's voice for the dubbed track. |
True
|
enable_diarization
|
bool
|
Enable speaker diarization for per-speaker voice cloning.
See |
False
|
progress_callback
|
Callable[[str, float], None] | None
|
Optional callback |
None
|
transcription
|
Any
|
Optional pre-computed |
None
|
keep_original_audio
|
bool
|
If True, retain the source audio in the output as a secondary track behind the dubbed one (editorial A/B). |
False
|
Returns:
| Type | Description |
|---|---|
DubbingResult
|
|
DubbingResult
|
source transcription. The output video is written to |
Source code in src/videopython/ai/dubbing/dubber.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | |
revoice
¶
revoice(
video: Video,
text: str,
preserve_background: bool = True,
progress_callback: Callable[[str, float], None]
| None = None,
) -> RevoiceResult
Replace speech in a video with new text using voice cloning.
Source code in src/videopython/ai/dubbing/dubber.py
revoice_and_replace
¶
revoice_and_replace(
video: Video,
text: str,
preserve_background: bool = True,
progress_callback: Callable[[str, float], None]
| None = None,
) -> Video
Revoice a video and return a new video with the revoiced audio.
Source code in src/videopython/ai/dubbing/dubber.py
DubbingConfig¶
Knobs shared by VideoDubber and LocalDubbingPipeline. Pass config=DubbingConfig(...)
or the same knobs as flat kwargs — the constructor builds a DubbingConfig either way.
DubbingConfig
¶
Bases: BaseModel
Knobs shared by :class:VideoDubber and :class:LocalDubbingPipeline.
Accepted as either config=DubbingConfig(...) or flat kwargs on the
two constructors; the flat path builds a DubbingConfig internally.
Attributes:
| Name | Type | Description |
|---|---|---|
device |
str | None
|
Execution device ( |
low_memory |
bool
|
When True, each pipeline stage (Whisper, Demucs, translation, Chatterbox TTS) is unloaded from memory after it runs, so only one model is resident at a time. Trades per-run latency (~10-30s of extra model loads) for a much lower memory ceiling. Recommended for GPUs with <=12GB VRAM or hosts with <32GB RAM. Default False. |
whisper_model |
WhisperModel
|
Whisper model size used for transcription. Larger
models give better accuracy at the cost of VRAM and latency. One
of |
condition_on_previous_text |
bool
|
Forwarded to |
no_speech_threshold |
float
|
Forwarded to |
logprob_threshold |
float | None
|
Forwarded to |
vocabulary |
list[str] | None
|
Forwarded to |
strict_quality |
bool
|
When True, the pipeline raises
:class: |
translator_model |
str | None
|
Ollama tag for the translation model ( |
Source code in src/videopython/ai/dubbing/config.py
from_args
classmethod
¶
Resolve either a config object or flat knob kwargs into a config.
The shared accept-one-or-the-other guard for VideoDubber and
LocalDubbingPipeline, which both take config=DubbingConfig(...)
or the flat kwargs (not both).
Source code in src/videopython/ai/dubbing/config.py
init_log_fields
¶
Subset of fields surfaced in the init-log line.
Hand-picked so log noise stays bounded as the config grows.
Source code in src/videopython/ai/dubbing/config.py
Results¶
result = dubber.dub(video, target_lang="es")
result.num_segments, result.source_lang, result.target_lang
result.translation_failures # indices the model never returned
for segment in result.translated_segments:
print(f"{segment.original_text!r} -> {segment.translated_text!r}")
for speaker, sample in result.voice_samples.items():
print(f"{speaker}: {sample.metadata.duration_seconds:.1f}s sample")
DubbingResult
¶
Bases: BaseModel
Result of a video dubbing operation.
Attributes:
| Name | Type | Description |
|---|---|---|
dubbed_audio |
Audio
|
The final dubbed audio track. |
translated_segments |
list[TranslatedSegment]
|
List of translated segments with timing. |
source_transcription |
Transcription
|
Original transcription of the source audio. |
source_lang |
str
|
Detected or specified source language. |
target_lang |
str
|
Target language for dubbing. |
separated_audio |
SeparatedAudio | None
|
Separated audio components (if preserve_background=True). |
voice_samples |
dict[str, Audio]
|
Dictionary mapping speaker IDs to voice sample Audio. |
timing_summary |
TimingSummary | None
|
Aggregate stats over per-segment timing adjustments. |
transcript_quality |
TranscriptQuality | None
|
Heuristic quality assessment of the transcription (None when the pipeline returned early on an empty transcription). |
translation_failures |
list[int]
|
Indices of segments the translator could not translate (missing after its parse-retry pass); those segments are dubbed with empty text. |
Source code in src/videopython/ai/dubbing/models.py
get_segments_by_speaker
¶
Group translated segments by speaker.
Returns:
| Type | Description |
|---|---|
dict[str, list[TranslatedSegment]]
|
Dictionary mapping speaker IDs to their segments. |
Source code in src/videopython/ai/dubbing/models.py
RevoiceResult
¶
Bases: BaseModel
Result of a voice replacement operation.
Attributes:
| Name | Type | Description |
|---|---|---|
revoiced_audio |
Audio
|
The final audio with new speech. |
text |
str
|
The text that was spoken. |
separated_audio |
SeparatedAudio | None
|
Separated audio components (if preserve_background=True). |
voice_sample |
Audio | None
|
Voice sample used for cloning. |
original_duration |
float
|
Duration of the original audio. |
speech_duration |
float
|
Duration of the generated speech. |
Source code in src/videopython/ai/dubbing/models.py
TranslatedSegment
¶
Bases: BaseModel
A segment of translated text with timing information.
Attributes:
| Name | Type | Description |
|---|---|---|
original_segment |
_TranscriptionSegmentField
|
The original transcription segment. |
translated_text |
str
|
The translated text. |
source_lang |
str
|
Source language code (e.g., "en"). |
target_lang |
str
|
Target language code (e.g., "es"). |
speaker |
str | None
|
Speaker identifier if available. |
start |
float
|
Start time in seconds. |
end |
float
|
End time in seconds. |
Source code in src/videopython/ai/dubbing/models.py
SeparatedAudio
¶
Bases: BaseModel
Audio separated into different components.
Attributes:
| Name | Type | Description |
|---|---|---|
vocals |
Audio
|
Isolated vocal/speech track. |
background |
Audio
|
Combined background audio (music + effects). |
music |
Audio | None
|
Isolated music track (if available). |
effects |
Audio | None
|
Isolated sound effects track (if available). |
original |
Audio
|
The original unseparated audio. |
Source code in src/videopython/ai/dubbing/models.py
has_detailed_separation
property
¶
Check if music and effects are separated.
Expressiveness¶
Per-segment Chatterbox generate() knobs (exaggeration, cfg_weight, temperature).
None on a field means "let Chatterbox use its default". The pipeline derives these from
source vocals RMS relative to the whole-vocals baseline, so the dub tracks the source's
loud/quiet shape instead of using flat defaults everywhere.
| RMS ratio vs baseline | exaggeration |
cfg_weight |
|---|---|---|
< 0.7× (calm) |
0.3 |
0.7 |
0.7×–1.3× (normal) |
Chatterbox default | Chatterbox default |
> 1.3× (dramatic) |
0.85 |
0.35 |
Expressiveness
¶
Bases: BaseModel
Chatterbox generate() knobs derived from source-segment prosody.
None on any field means "let Chatterbox use its own default" --
avoids pinning the dub against future Chatterbox default changes.
Attributes:
| Name | Type | Description |
|---|---|---|
exaggeration |
float | None
|
Emotional intensity. Chatterbox default |
cfg_weight |
float | None
|
Classifier-free guidance weight. Chatterbox default
|
temperature |
float | None
|
Sampling temperature. Chatterbox default |
Source code in src/videopython/ai/dubbing/models.py
as_kwargs
¶
Knobs as a dict, dropping None entries.
Suitable for **-expansion into Chatterbox.
Source code in src/videopython/ai/dubbing/models.py
TimingSummary¶
Aggregate stats over the per-segment timing adjustments the synchronizer applied. High truncation counts mean the translation produced text too long for the source's spoken regions.
TimingSummary
¶
Bases: BaseModel
Aggregate stats over per-segment timing adjustments.
Surfaces how aggressively the timing synchronizer had to compress or truncate dubbed segments to fit the source's spoken regions. High truncation rates indicate translation produced text too long for the source duration.
Source code in src/videopython/ai/dubbing/models.py
from_adjustments
classmethod
¶
Aggregate a list of TimingAdjustments into a TimingSummary.
Source code in src/videopython/ai/dubbing/models.py
TranscriptQuality¶
Heuristic assessment over the Whisper transcription, surfaced on every DubbingResult and
driving the optional strict_quality reject path. Flags: dominant phrase covering ≥70% of
segment characters, median avg_logprob < -1.5, or speech under 5% of a clip longer
than 30 s. recommendation is "reject" when dominance fires together with another flag,
"warn" for any single flag, "ok" otherwise.
TranscriptQuality
¶
Bases: BaseModel
Quality assessment of a Whisper transcription.
Attributes:
| Name | Type | Description |
|---|---|---|
recommendation |
Recommendation
|
|
dominant_phrase |
str | None
|
The repeating phrase that triggered the dominance flag, or None when the flag didn't fire. |
dominant_phrase_fraction |
float
|
Character-count share of the most common normalized segment phrase. 0.0 when no segments. |
median_avg_logprob |
float | None
|
Median of |
speech_fraction |
float
|
Sum of segment durations divided by the audio's wall-clock duration. |
flags |
list[str]
|
Human-readable list of which checks fired. |
Source code in src/videopython/ai/dubbing/quality.py
GarbageTranscriptError
¶
Bases: AiError, RuntimeError
Raised by the dubbing pipeline when strict_quality=True and the
transcript heuristic returns recommendation="reject".
The triggering :class:TranscriptQuality is attached as quality so
callers can introspect the flags without re-running the pipeline.
Source code in src/videopython/ai/dubbing/quality.py
Supported languages¶
English, Spanish, French, German, Italian, Portuguese, Polish, Hindi, Arabic, Czech, Danish, Dutch, Finnish, Greek, Hebrew, Indonesian, Japanese, Korean, Malay, Norwegian, Romanian, Russian, Slovak, Swedish, Tamil, Thai, Turkish, Ukrainian, Vietnamese, Chinese.