Skip to content

pyVideoTrans Technical Architecture and Implementation ​

pyVideoTrans is a powerful open-source video translation and dubbing tool (v4.03) that can automatically translate videos and add voiceovers in the target language. Its core design philosophy is a modular, multi-threaded pipeline, using flexible flag combinations to support multiple working modes.


1. Core Processing Pipeline ​

The software decomposes the video translation and dubbing process into 9 independent stages, forming an automated processing pipeline. Each task is controlled by 5 boolean flags (should_recogn, should_trans, should_dubbing, should_hebing, should_separate) that determine which stages to skip, enabling different working modes.

1.1 Nine Processing Stages ​

StageMethodResponsibility
① Preprocessingprepare()Separate silent video stream and raw audio from video; optional vocal/background separation (UVR/Spleeter); optional denoising; create cache and output directories
② Speech Recognitionrecogn()Call ASR engine (default Faster-Whisper, supports 22 channels) to transcribe audio into timestamped SRT subtitles; optional punctuation recovery, LLM re-segmentation
③ Speaker Diarizationdiariz()Call speaker diarization model (built, ali_CAM, pyannote, reverb backends) to categorize subtitles by speaker
④ Subtitle Translationtrans()Translate source language SRT subtitles to target language via translation channels (24 channels); supports bilingual subtitle output
⑤ Dubbingdubbing()Based on target language subtitle content and timestamps, call TTS engine (34 channels) to generate dubbed audio line by line; supports voice cloning (extracting reference segments from original audio)
⑥ Audio-Video Alignmentalign()Processed by the SpeedRate class: dubbing speedup, video slowdown, removing silent gaps between subtitles, forced subtitle-audio alignment; optional volume adjustment
⑦ STT Againrecogn2pass()Perform ASR on the dubbed audio again to generate precise, short-duration subtitles (only executed when dubbing is enabled and not embedding dual subtitles)
⑧ Final Compositionassembling()Merge silent video stream, dubbed audio, background music, and target language subtitles into the final video file (ffmpeg)
⑨ Cleanuptask_done()Move output files from the temporary directory to the specified output directory, clean up temporary files, send completion notification

1.2 Pipeline Control Flags ​

Defined in videotrans/task/_base.py:20-29. The five flags are automatically computed in TransCreate.__post_init__() based on configuration:

python
should_recogn: bool    # Whether speech recognition is needed (True if no existing subtitles)
should_trans: bool     # Whether translation is needed (True if source language ≠ target language)
should_dubbing: bool   # Whether dubbing is needed (True if a voice role is selected and not 'No')
should_hebing: bool    # Whether embedding/merging is needed (True if not 'tiqu' mode and has dubbing or subtitle embedding)
should_separate: bool  # Whether vocal/background separation is needed

1.3 Mode Switching Examples ​

Different features are achieved through flag combinations:

Featureshould_recognshould_transshould_dubbingshould_hebing
Video translation & dubbing (standard mode)✓✓✓✓
Video/audio to subtitles (tiqu)✓optional✗✗
Subtitle dubbing✗✗✓✓
Translate subtitle files only✗✓✗✗

1.4 Task Subclass Hierarchy ​

BaseTask has four concrete subclasses, each corresponding to a different use case:

SubclassFileInherited TaskCfgUse Case
TransCreatetask/trans_create.pyTaskCfgVTTFull video translation & dubbing (standard / tiqu extraction mode)
SpeechToTexttask/speech2text.pyTaskCfgSTTBatch speech-to-text
DubbingSrttask/dubbing.pyTaskCfgTTSBatch subtitle dubbing
TranslateSrttask/translate_srt.pyTaskCfgSTSBatch SRT subtitle translation

2. Task Configuration Dataclass Hierarchy ​

v4.03 restructured task configuration into a hierarchical @dataclass system (videotrans/task/taskcfg.py, 261 lines):

@dataclass TaskCfgBase              ← Common fields (paths, language codes, cache directories, etc.)
    ├── @dataclass TaskCfgSTT       ← ASR-related fields (recogn_type, model_name, rephrase, etc.)
    ├── @dataclass TaskCfgTTS       ← Dubbing-related fields (tts_type, voice_role, voice_autorate, etc.)
    ├── @dataclass TaskCfgSTS       ← Translation-related fields (translate_type)
    └── @dataclass TaskCfgVTT       ← Full video translation fields (inherits STT + TTS + STS, adds video-specific fields)

Auxiliary dataclasses:

DataclassFilePurpose
InputFiletask/taskcfg.pyInput file metadata (name, dirname, noextname, basename, ext, uuid, target_dir), supports dict-style access
SignMsgtask/taskcfg.pySignal message body (type, uuid, text), provides is_stop() and is_error() methods, passed between Worker threads and main thread
SrtItemtask/taskcfg.pySingle subtitle data (text, start_time, end_time, startraw, endraw, line, time, spk, filename)

SrtItem supports both attribute access (item.text) and dict access (item['text']), and can be iterated via items().


3. Multi-Threaded Asynchronous Task Processing Architecture ​

The software uses a multi-threaded, multi-queue architecture based on the "Producer-Consumer" pattern. The MultVideo thread acts as the producer, pushing task objects into the pipeline's first queue; 9 specialized BaseWorker subclasses act as consumers, each monitoring a dedicated queue.

3.1 Queue Pipeline ​

                     MultVideo (Producer)
                           │
                    app_cfg.prepare_queue
                           ▼
                   WorkerPrepare (×N)
                    ┌────────┼────────┐
                    │ should_recogn ?  │
                    ▼        ▼        ▼
           regcon_queue  trans_queue  dubb_queue / assemb_queue / taskdone_queue
                │
                ▼
          WorkerRegcon (×N)
                │
         diariz_queue
                │
                ▼
          WorkerDiariz (×N)
           ┌────┼────┐
           ▼    ▼    ▼
      trans_queue  dubb_queue  assemb_queue / taskdone_queue
           │
           ▼
     WorkerTrans (×1)
      ┌────┼────┐
      ▼    ▼    ▼
 dubb_queue  assemb_queue  taskdone_queue
      │
      ▼
WorkerDubb (×1)
      │
 align_queue
      │
      ▼
WorkerAlign (×1)
 ┌────┼────┐
 ▼    ▼    ▼
regcon2_queue  assemb_queue  taskdone_queue
 │
 ▼
WorkerRegcon2Pass (×1)
 ┌────┼────┐
 ▼    ▼
assemb_queue  taskdone_queue
 │
 ▼
WorkerAssemb (×N)
 │
taskdone_queue
 │
 ▼
WorkerTaskDone (×1)
 │
(end)

3.2 Worker Base Class Design ​

All worker threads inherit from BaseWorker(QThread) (videotrans/task/job.py:13-66):

python
class BaseWorker(QThread):
    def __init__(self, name, queue):
        self.name = name
        self.queue = queue

    def run(self):
        while True:
            if app_cfg.exit_soft:          # Global soft exit flag
                return
            try:
                trk = self.queue.get(timeout=1)  # Block for 1 second to get a task
            except Empty:
                continue
            if trk.uuid in app_cfg.stoped_uuid_set:  # Task was stopped
                continue
            try:
                self.process_task(trk)       # Subclass implements specific logic
            except Exception as e:
                self.handle_error(e, trk)    # Unified error handling

Each subclass overrides the following methods:

MethodDescription
process_task(trk)Required — Execute stage logic and route trk to the next queue
get_error_prefix(trk)Optional — Return an error prefix string (e.g., "Recognition error [Faster-Whisper]")
cleanup_on_error(trk)Optional — Cleanup logic on error

handle_error() uniformly calls get_msg_from_except() to parse exceptions into user-readable messages, then sends error messages via trk.signal().

3.3 Worker Routing Decision Logic ​

Each Worker, after completing process_task(trk), decides the next queue based on the task's flags:

WorkerPrepare    →  regcon_queue | trans_queue | dubb_queue | assemb_queue | taskdone_queue
WorkerRegcon     →  diariz_queue  (unconditional)
WorkerDiariz     →  trans_queue | dubb_queue | assemb_queue | taskdone_queue  (diariz exceptions don't block pipeline)
WorkerTrans      →  dubb_queue | assemb_queue | taskdone_queue
WorkerDubb       →  align_queue  (unconditional)
WorkerAlign      →  regcon2_queue | assemb_queue | taskdone_queue  (regcon2 only if recogn2pass attribute exists)
WorkerRegcon2Pass → assemb_queue | taskdone_queue
WorkerAssemb     →  taskdone_queue  (unconditional)
WorkerTaskDone   →  (terminates)

3.4 Dynamic Thread Count Calculation ​

start_thread() (videotrans/task/job.py:206-245) dynamically determines instance counts for each Worker based on GPU configuration:

WorkerInstancesReason
WorkerPrepare1–4GPU-intensive (video encoding/decoding)
WorkerRegcon1–4GPU-intensive (ASR inference)
WorkerDiariz1–4GPU-intensive (speaker diarization)
WorkerTransFixed 1API calls, avoid concurrent rate limiting
WorkerDubbFixed 1TTS API calls, avoid concurrent rate limiting
WorkerRegcon2PassFixed 1Auxiliary stage
WorkerAlignFixed 1Audio-video alignment is single-threaded
WorkerAssemb1–4GPU-intensive (ffmpeg encoding)
WorkerTaskDoneFixed 1File movement / cleanup

The task_nums calculation logic: Uses settings.process_max_gpu manual override if set; otherwise auto-detects based on multi_gpus + NVIDIA_GPU_NUMS (1 GPU = 1, 2–3 GPUs = 2, ≥4 GPUs = 4, no GPU = 1).

3.5 Batch Task Submission: MultVideo ​

MultVideo(QThread) (videotrans/task/mult_video.py, 54 lines) is responsible for creating TransCreate objects for each user-selected video file and pushing them into prepare_queue. Supports controlling batch concurrency via the batch_nums parameter:

  • batch_nums == 0: Push all tasks into the queue at once (maximum concurrency)
  • batch_nums == 1: Push one at a time, waiting for each to complete before the next
  • batch_nums > 1: Push N tasks per batch, wait for the entire batch to complete before the next

3.6 Soft Exit Mechanism ​

When the global flag app_cfg.exit_soft is set to True, all Workers detect it in their next loop iteration and exit safely. app_cfg.stoped_uuid_set tracks UUIDs of manually stopped tasks — Workers skip these tasks after pulling them from the queue.


4. Core Class Design and Inheritance ​

4.1 Class Hierarchy ​

@dataclass BaseCon                    ← videotrans/configure/base.py
    │                                  Base attributes and utility methods
    ├── @dataclass BaseTask           ← videotrans/task/_base.py
    │       │                          8 stage empty methods + 5 flags
    │       ├── @dataclass TransCreate ← videotrans/task/trans_create.py (~1678 lines core)
    │       ├── @dataclass SpeechToText ← videotrans/task/speech2text.py (batch speech recognition)
    │       ├── @dataclass DubbingSrt  ← videotrans/task/dubbing.py (batch subtitle dubbing)
    │       └── @dataclass TranslateSrt ← videotrans/task/translate_srt.py (batch subtitle translation)
    │
    ├── @dataclass BaseRecogn         ← videotrans/recognition/_base.py
    │       │                          VAD audio segmentation, subtitle merging, CJK handling
    │       └── 22 subclasses (lazy-loaded)    Individual ASR channel implementations
    │
    ├── @dataclass BaseTrans          ← videotrans/translator/_base.py
    │       │                          MD5 caching, line-by-line / full-text translation dispatch
    │       └── 24 subclasses (lazy-loaded)    Individual translation channel implementations
    │
    └── @dataclass BaseTTS            ← videotrans/tts/_base.py
            │                          Async / multi-threaded concurrent dispatch
            └── 34 subclasses (lazy-loaded)    Individual TTS channel implementations

All channel classes use @dataclass with __post_init__ initialization rather than traditional __init__ constructors.

4.2 BaseCon — Top-Level Base Class ​

videotrans/configure/base.py (296 lines) defines core capabilities shared by all classes:

MethodResponsibility
_exit()Check whether to stop (exit_soft or UUID in stoped_uuid_set)
signal(**kwargs)Send messages to UI (via push_queue() → SignalHub. CLI mode prints directly)
_set_proxy(type)Set/clear HTTP proxy (operates app_cfg.proxy and environment variables)
_new_process(callback, title, is_cuda, kwargs)Execute heavy tasks in a subprocess (returns (data, error) tuple)
_signal_of_process(logs_file)Read subprocess progress by polling JSON log file mtime
convert_to_wav()Convert audio to 48kHz stereo WAV (optional silence removal)
_base64_to_audio() / _audio_to_base64()Base64 audio encode/decode
_process_callback(data)Download progress callback (forwards to signal())

BaseCon.__post_init__() automatically calls _set_proxy(type='set') during initialization to load proxy configuration.

4.3 BaseTask — Task Base Class ​

videotrans/task/_base.py:10-167 defines stage empty methods and shared utilities for all task subclasses:

Stage methods (all empty implementations, overridden by subclasses): prepare(), recogn(), diariz(), trans(), dubbing(), align(), assembling(), task_done()

Note: recogn2pass() is defined in TransCreate, not in the BaseTask base class.

Shared methods:

MethodResponsibility
_unlink_size0(file)Delete invalid files with size 0
_save_srt_target(srtstr, file)Format SrtItem list to SRT string and write to file, send replace_subtitle signal
check_target_sub(source, target)Validate line count consistency between source and translated subtitles; align by timestamp when mismatched
set_end(succeed=False)Mark task completion; send notification and clean up temp folder on success
_edgetts_single(target_audio, kwargs)Edge-TTS one-shot async dubbing (with proxy fallback)

4.4 TransCreate — Video Translation Core Implementation ​

videotrans/task/trans_create.py (~1678 lines) implements the full 9-stage processing logic. Key internal methods:

MethodResponsibility
__post_init__()Initialize all file paths, compute flags, start progress timer thread
_split_novoice_byraw()Separate silent video from original (prioritize hardware decode h264_cuvid, fallback libx264)
_split_audio_byraw()Extract 16kHz mono PCM audio from original video + optional vocal/background separation
_tts()Build queue_tts list (including clone reference audio segments), call tts.run()
_create_ref_from_vocal()Multi-threaded (ThreadPoolExecutor) cutting of original audio segments for voice cloning reference
_recogn_succeed()Post-recognition processing (file copying in tiqu mode)
_back_music()Mix user-uploaded background music with dubbed audio
_separate()Re-embed separated background music into dubbed audio
_process_subtitles()Handle soft/hard subtitle embedding logic (single/dual subtitles, style settings)

4.5 Subprocess Channels ​

To prevent faster-whisper crashes from taking down the entire application, Faster-Whisper, Faster-Whisper-XXL, Whisper.cpp, and some TTS engines (e.g., QWEN3LOCAL_TTS) are delegated to GlobalProcessManager for execution in isolated subprocesses via BaseCon._new_process().

Subprocesses report progress by writing to JSON log files. BaseCon._signal_of_process() polls these log files in a daemon thread, parsing JSON and reporting via signal() when mtime changes are detected.


5. Configuration System ​

The software organizes configuration into three layers (videotrans/configure/config.py, 902 lines), all as @dataclass:

Config ClassPersistencePurposeExample Fields
AppCfgIn-memory onlyQueues, state, thread control, runtime contextprepare_queue, exit_soft, stoped_uuid_set, current_status, line_roles, exec_mode, video_codec, onlyone_source_sub, onlyone_target_sub, proxy, SUPPORT_LANG
AppSettingsvideotrans/cfg.jsonGlobal defaults, model listshomedir, model_list, vad_type, cuda_com_type
AppParamsvideotrans/params.jsonUser preferences, API keyssource_language, recogn_type, chatgpt_key, voice_role, app_mode

Key singleton variables are automatically initialized at module load time:

python
app_cfg: AppCfg = AppCfg()        # Runtime state (contains 9 Queue instances)
settings: AppSettings = AppSettings()  # Loaded from cfg.json
params: AppParams = AppParams()    # Loaded from params.json

5.1 AppSettings Features ​

  • Supports dict-style access via settings['key'] and settings.get('key', default) method
  • get() automatically type-casts numeric fields (int_type and float_type whitelists)
  • _get_defaults() defines defaults for ~100 configuration items
  • Supports hyphenated field name mapping (e.g., "initial_prompt_zh-cn" → "initial_prompt_zh_cn")

5.2 AppParams Features ​

  • _get_defaults() defines defaults for ~100 user parameters
  • getset_params(update_data) supports batch updates (e.g., collecting all UI control values in check_start())
  • API key fields are managed here for is_input_api() validation

5.3 AppCfg Runtime State ​

  • 9 Queue(maxsize=0) (unlimited capacity): prepare_queue ~ taskdone_queue
  • queue_novice: Dict — Tracks silent video separation progress (key=uuid, value='ing'|'end')
  • line_roles: Dict — Stores per-line subtitle role assignments in single-video mode
  • child_forms: Dict — Caches open window instances to avoid duplicate creation
  • exec_mode — Execution mode ('gui' or 'cli')
  • video_codec / codec_cache — Video codec cache
  • onlyone_source_sub / onlyone_target_sub / onlyone_trans — Single-video mode subtitle state
  • SUPPORT_LANG — Supported language list

5.4 Environment Variable Initialization ​

_set_env() executes automatically at module load time (videotrans/configure/config.py:44-72), setting:

  • MODELSCOPE_CACHE / HF_HOME / HF_HUB_CACHE → ROOT_DIR/models
  • QT_API = 'pyside6'
  • PATH appends ffmpeg/sox directories
  • OMP_NUM_THREADS = 1
  • HF_HUB_DOWNLOAD_TIMEOUT = 3600
  • HF_HUB_DISABLE_XET = 1

6. GlobalProcessManager — Subprocess Pool Management ​

videotrans/process/signelobj.py (167 lines) implements a class-level singleton GlobalProcessManager:

GlobalProcessManager (class-level singleton)
    ├── _executor_cpu: multiprocessing.Pool
    │       workers = max(min(available_ram/4GB, 8, cpu_count), 1)  ← Based on available RAM
    │       maxtasksperchild = 1  ← Each subprocess restarts after one task to prevent memory leaks
    │
    └── _executor_gpu: multiprocessing.Pool
            workers = GPU count (prioritize settings.process_max_gpu manual setting)
            maxtasksperchild = 1

6.1 CPU Process Pool Size ​

Uses psutil.virtual_memory().available to get current system available RAM, calculates one process per 4GB, capped at 1–8, and does not exceed os.cpu_count(). Can be manually overridden via settings.process_max.

6.2 GPU Process Pool Size ​

Prioritizes settings.process_max_gpu manual setting; otherwise auto-determined by multi_gpus and NVIDIA_GPU_NUMS (no GPU = 1, GPU available but multi-GPU not enabled = 1, multi-GPU enabled = min(GPU count, 8, cpu_count)).

6.3 Task Submission Interface ​

python
GlobalProcessManager.submit_task_cpu(func, **kwargs)   → AsyncResultFutureWrapper
GlobalProcessManager.submit_task_gpu(func, **kwargs)   → AsyncResultFutureWrapper

AsyncResultFutureWrapper wraps Pool.apply_async's AsyncResult into a Future-compatible interface (.result(), .done()).

6.4 Usage Scenarios ​

Unified through BaseCon._new_process() for: ASR inference, TTS synthesis, noise removal, vocal separation, speaker diarization, punctuation recovery (all run in isolated subprocesses — crashes don't affect the main process).


7. SignalHub — Cross-Thread Message Center ​

videotrans/configure/signal_hub.py (33 lines) implements Qt signal-based singleton message passing:

python
class SignalHub(QObject):
    _instance = None
    new_message = Signal(str, object)  # (uuid, SignMsg)

    @classmethod
    def instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    @Slot(str, object)
    def post(self, uuid=None, data=None):
        self.new_message.emit(uuid, data)  # Cross-thread automatically uses QueuedConnection

Message Flow ​

BaseCon.signal(**kwargs)
    → push_queue(uuid, SignMsg(**kwargs))     [configure/config.py]
        → SignalHub.instance().post(uuid, data)
            → new_message Signal (QueuedConnection)
                → WinAction.update_data(uuid, data)   [mainwin/_actions.py]
                    → Dispatched by type:
                        'logs'|'error'|'succeed'|'set_precent' → set_process_btn_text()
                        'edit_subtitle_source' → Show EditRecognResultDialog
                        'edit_subtitle_target' → Show SpeakerAssignmentDialog
                        'edit_dubbing' → Show EditDubbingResultDialog
                        'replace_subtitle' → Update subtitle editor
                        'end' → update_status('end')

Message Type Enumeration ​

TypeMeaningProcessing Logic
logsNormal logUpdate progress bar text
errorErrorProgress bar turns red, add to retry queue
succeedSuccessProgress bar turns green, mark complete
set_precentProgress percentageFormat: text="elapsed ??? percentage"
edit_subtitle_sourceShow source subtitle edit dialogSingle-video mode pause point ①
edit_subtitle_targetShow translated subtitle edit dialogSingle-video mode pause point ②
edit_dubbingShow dubbing result edit dialogSingle-video mode pause point ③
replace_subtitleReplace subtitle area contentShared by batch and single-video modes
subtitleAppend subtitle lineLine-by-line output to editor
endTask completeTriggers update_status('end')
disabled_editDisable subtitle editingLocks editor in batch mode
refreshttsRefresh TTS selectionRe-populate TTS dropdown
shitingerrorAudition errorShow error dialog
ffmpegffmpeg statusUpdate start button text

8. Dynamic Channel Loading ​

videotrans/__init__.py (35 lines) provides a generic lazy-loading mechanism:

python
@dataclass
class ChannelProvider:
    name: str           # Display name in UI
    imp: str            # Module import suffix (e.g., "._whisper" → "videotrans.recognition._whisper")
    key_name: str|None  # Corresponding API key field in params.json (for is_input_api validation)
    win: str|None       # Corresponding settings window name in winform

def get_class(channel_id=0, provider_type=None, _ID_NAME_DICT=None):
    _key = f'{provider_type}-{channel_id}'
    if _key in _loaded_modules:
        return _loaded_modules[_key]
    module = importlib.import_module(f'videotrans.{provider_type}{_module_map.imp}')
    for _, obj in inspect.getmembers(module, inspect.isclass):
        if obj.__module__ == module.__name__:
            _loaded_modules[_key] = obj
            return obj

_ID_NAME_DICT for each module:

ModuleChannelsDefinition
recognition22videotrans/recognition/__init__.py:48-79
translator24videotrans/translator/__init__.py:60-90
tts34videotrans/tts/__init__.py:75-116

8.1 Unified Entry Function ​

Each module provides a run() entry function that internally uses get_class() to obtain the corresponding channel class and instantiate it:

python
# recognition/__init__.py
def run(*, recogn_type, detect_language, audio_file, ...) -> List[SrtItem]:
    _cls = get_class(recogn_type, "recognition", _ID_NAME_DICT)
    return _cls(**kwargs).run()

# translator/__init__.py
def run(*, translate_type, text_list, source_code, target_code, ...) -> List[SrtItem]:
    _cls = get_class(translate_type, "translator", _ID_NAME_DICT)
    return _cls(**kwargs).run()

# tts/__init__.py
def run(*, queue_tts, language, tts_type, ...) -> None:
    _cls = get_class(tts_type, "tts", _ID_NAME_DICT)
    return _cls(**kwargs).run()

8.2 API Key Validation ​

Each module provides is_input_api(recogn_type/translate_type/tts_type) which checks whether the corresponding channel's key_name is filled in params. If not, it automatically opens the corresponding winform settings window.

8.3 Translation Caching ​

BaseTrans (videotrans/translator/_base.py) implements MD5-based translation caching:

  • Cache key = md5(channel_name + api_url + model + source_lang + target_lang + text)
  • Cache files stored in {TEMP_ROOT}/translate_cache/
  • Write interface: _set_cache(), read interface: _get_cache()

8.4 CJK Special Handling ​

BaseRecogn (videotrans/recognition/_base.py:58-80) performs special handling for CJK languages in __post_init__:

  • join_word_flag: CJK languages (zh, ja, ko, yu, th, km, yue) don't add spaces between subtitle words (other languages do)
  • maxlen: CJK languages max characters per line is settings.cjk_len (default 15); other languages use settings.other_len (default 40)
  • jianfan: For Chinese languages with settings.zh_hant_s=True, simplified-traditional conversion is enabled

8.5 Translation Dispatch Strategy ​

BaseTrans.run() selects different strategies based on the aisendsrt flag:

ModeConditionMethodConcurrency
Line-by-line translationNon-AI channel_run_text() → _item_task()settings.trans_thread (default 10)
Full-text translationAI channel + aisendsrt=True_run_srt()settings.aitrans_thread (default 50)

8.6 TTS Dispatch Strategy ​

BaseTTS.run() selects execution method based on channel type:

Channel TypeDispatch MethodNotes
Edge-TTSasyncio asyncSingle-thread async concurrency
Other channelsThreadPoolExecutorConcurrency controlled by dubbing_thread (default 1)

Channel subclasses can override _exec() for custom dispatch. BaseTTS defaults to calling __local_mul_thread() → _item_task().


9. Interactive Single-Video Processing Mode ​

When a user selects 1 video in standard mode (biaozhun), the program uses a different processing model from the batch pipeline.

9.1 Implementation: Worker(QThread) ​

The Worker class in videotrans/task/only_one.py (148 lines) executes all 9 stages serially within a single QThread, communicating with the main thread via uito = Signal(str, SignMsg):

Worker.run()
    ├── trk = TransCreate(cfg=TaskCfgVTT(**self.cfg | obj))
    ├── trk.prepare()
    ├── trk.recogn()
    ├── trk.diariz()
    ├── [Pause Point ①] → _post(type='edit_subtitle_source')
    │    User reviews source subtitles → clicks "Confirm" or waits for countdown
    ├── trk.trans() (if should_trans)
    ├── [Pause Point ②] → _post(type='edit_subtitle_target')
    │    User reviews translated subtitles + assigns speaker roles → clicks "Confirm"
    ├── trk.dubbing() (if should_dubbing)
    ├── [Pause Point ③] → _post(type='edit_dubbing')
    │    User modifies dubbing results → clicks "Confirm"
    ├── trk.align()
    ├── trk.recogn2pass()
    ├── trk.assembling()
    └── trk.task_done()

9.2 Key Differences from Batch Mode ​

DimensionSingle-Video ModeBatch Mode
Execution threadWorker(QThread) executes directly without queue pipelineTransCreate pushed into prepare_queue, flows through 9 Worker queues
Message channeluito signal connects directly to WinAction.update_data()BaseCon.signal() → push_queue() → SignalHub
Pause mechanismThree pause points, user can edit mid-processNo pause/edit support
Progress displayReal-time display in subtitle editorProgress bar + button text

9.3 Countdown and Pause Mechanism ​

  1. Auto countdown: app_cfg.set_countdown(86400) sets the initial value. Worker thread decrements by 1 each sleep(1). Default countdown controlled by settings.countdown_sec.
  2. Infinite pause: User clicks the "Stop" button, setting app_cfg.current_status to 'stop'; Worker's _exit() detects this and exits. Alternatively, set_countdown(-1) hides the countdown.
  3. Manual continue: User clicks "Confirm" in the review dialog, WinAction.set_djs_timeout() calls app_cfg.set_countdown(-1) to zero the countdown immediately.

9.4 Review Dialogs ​

DialogFileFunction
EditRecognResultDialogcomponent/onlyone_set_recogn.pySource subtitle editing (text + timeline)
SpeakerAssignmentDialogcomponent/onlyone_set_role.pyTranslated subtitle editing + per-line voice role assignment
EditDubbingResultDialogcomponent/onlyone_set_editdubb.pyDubbing result preview + re-dub individual lines


10. Audio-Video Alignment Engine (SpeedRate) ​

videotrans/task/_rate.py (877 lines) implements two alignment engines: SpeedRate and TtsSpeedRate.

10.1 SpeedRate (Video Translation Scenario) ​

Processing strategy (by priority):

ConditionStrategy
Both audio speedup + video slowdown enabledEach handles half the time difference (ignoring ratio limits)
Only audio speedup enabledSpeed up dubbing to match subtitle duration (capped at max_audio_speed_rate)
Only video slowdown enabledSlow down video to match dubbing duration (capped at max_video_pts_rate)
Neither enabledConcatenate audio segments by subtitle timeline, fill silence/freeze-frame for duration differences

Additional processing:

  • remove_silent_mid: Remove silent intervals between subtitles
  • align_sub_audio: Force-align subtitle timeline to actual dubbing positions
  • Trailing silence removal

10.2 TtsSpeedRate (Dubbing-Only Scenario) ​

Simplified alignment engine responsible only for audio concatenation and speedup, with no video slowdown logic.


11. Software Startup and UI Implementation ​

11.1 Startup Flow ​

sp.py is the sole entry point (221 lines). Startup process:

sp.py (if __name__ == "__main__")
  │
  ├── 1. multiprocessing.freeze_support() / set_start_method('spawn')
  ├── 2. qInstallMessageHandler() suppress Qt warnings
  ├── 3. atexit.register(cleanup) register exit cleanup
  ├── 4. QApplication.setHighDpiScaleFactorRoundingPolicy(PassThrough)
  ├── 5. Create QApplication
  ├── 6. Detect if running inside archive (PyInstaller packaged version)
  ├── 7. Create StartWindow (splash screen, borderless transparent)
  │       └── QTimer.singleShot(100ms) → initialize_full_app()
  │           ├── Redirect sys.stdout/stderr to log file
  │           ├── Set global exception hook show_global_error_dialog
  │           ├── Parse --lang CLI argument
  │           ├── Import darkstyle_rc (compiled QRC resources)
  │           ├── Load QSS stylesheet (videotrans/styles/style.qss)
  │           ├── Restore last window size (QSettings)
  │           └── Instantiate MainWindow → uito connects to splash.update_lable
  │               └── MainWindow.__init__()
  │                   ├── setupUi() → Populate dropdowns (translation/recognition/TTS channels, language lists)
  │                   ├── AiLoaderThread starts → Detect GPU → Callback _start_workers()
  │                   ├── _start_workers() → start_thread() launches 9 Worker threads
  │                   ├── _set_default() → Restore last user selections
  │                   ├── _bind_signal() → Bind ~60 control events
  │                   ├── SignalHub.new_message.connect(win_action.update_data)
  │                   └── uito.emit('end') → splash closes
  └── 8. app.exec() → Qt event loop

11.2 Exit Mechanism ​

When the user clicks the close button:

  1. Set app_cfg.exit_soft = True, app_cfg.current_status = 'stop'
  2. Main window immediately hides (hide())
  3. Save window size to QSettings
  4. Hide/close all child windows
  5. Wait ~4 seconds for all Workers to complete current work and exit safely
  6. Clean up temporary directory TEMP_ROOT
  7. atexit cleanup callback executes → program terminates
  8. If in restart mode, start new process then os._exit(0)

11.3 UI Architecture Layers ​

UI Definition Layer       videotrans/ui/         ← PySide6 UI layout files (~75), dark/ resource files
    ↓
UI Logic Layer            videotrans/component/   ← Common components: progress bar, settings forms, subtitle editor, real-time STT, video clipper, text matching
    ↓
Window Management Layer   videotrans/winform/     ← Lazy-loaded ~65 settings/feature window modules
    ↓
Main Window Layer         videotrans/mainwin/
    ├── main_win.py                      ← MainWindow(QMainWindow): UI init, signal binding, Worker startup, window lifecycle (528 lines)
    ├── _actions.py                      ← WinAction: Core business logic → parameter collection → task startup → status dispatch (798 lines)
    └── _actions_base.py                ← WinActionBase: Proxy management, mode switching, file selection, CUDA detection, audition (590 lines)
    ↓
Task Layer               videotrans/task/        ← TransCreate, SpeechToText, DubbingSrt, TranslateSrt, Worker threads, SpeedRate

11.4 MainWindow — Main Window ​

videotrans/mainwin/main_win.py (528 lines) responsibilities:

  • setupUi(): Load UI layout, populate dropdowns (translation channels, recognition channels, TTS channels, languages, subtitle types)
  • _bind_signal(): Bind approximately 60 control events to WinAction methods
  • _start_workers(status): Launch 9 Worker background threads after GPU detection completes
  • open_winform(name): Unified window opening entry point (prioritize cached app_cfg.child_forms, otherwise call winform.get_win(name).openwin())
  • closeEvent(): Safe shutdown flow (mark exit → hide window → stop threads → clean up temp files)
  • restart_app(): Prompt confirmation then trigger closeEvent() and start new process

11.5 WinAction — Core Controller ​

WinAction inherits from WinActionBase (both @dataclass), serving as the key hub connecting UI and background tasks:

WinActionBase (mainwin/_actions_base.py, 590 lines) provides:

  • File selection (get_mp4()) — single file / folder mode
  • Output directory setup (get_save_dir())
  • Proxy configuration (change_proxy(), check_proxy(), proxy_alert())
  • Mode switching (set_biaozhun(), set_tiquzimu()) — controls UI element visibility
  • CUDA detection (check_cuda(), cuda_isok())
  • Audition feature (listen_voice_fun()) — creates ListenVoice thread
  • Voice role list updates (tts_type_change(), set_voice_role())
  • Advanced options collapse (toggle_adv())
  • UI enable/disable controls (disabled_widget(), _disabled_button())

WinAction (mainwin/_actions.py, 798 lines) provides:

  • check_start(): Collect all UI control values → build cfg dictionary → parameter validation → call create_btns()
  • create_btns(): Format input file paths → create progress bar → start Worker for single video, MultVideo for batch
  • update_data(uuid, SignMsg): Connects SignalHub.new_message signal → dispatch by message type
  • update_status(type): Switch between ing/stop/end states, control buttons and progress bar
  • set_process_btn_text(d): Update progress bar text/percentage/color
  • retry(): Re-process failed tasks
  • _check_all_done(): Detect whether all tasks are complete

12. Exception Hierarchy ​

videotrans/configure/excepts.py (376 lines) defines a layered exception system:

VideoTransError (base class)
    ├── TranslateSrtError       # Translation errors
    ├── DubbingSrtError         # Dubbing errors
    ├── SpeechToTextError       # Speech recognition errors
    ├── LLMSegmentError         # LLM re-segmentation errors
    ├── FFmpegError             # FFmpeg operation errors
    ├── DownloadModelsError     # Model download errors
    ├── SttTimeoutError         # STT subprocess timeout
    ├── StopTask                # Task exception requiring immediate stop
    └── StopRetry               # Non-retryable error

The get_msg_from_except(e) function maps dozens of third-party library exceptions to user-readable Chinese/English error messages (covering httpx, openai, requests, deepgram, elevenlabs, tenacity, etc.).

The NO_RETRY_EXCEPT tuple defines non-recoverable exception types that translation/dubbing modules immediately give up on when encountered in retry loops.


13. Code Structure Overview ​

/
├── sp.py                       # ★ Main entry point (221 lines)
├── cli.py                      # ★ CLI command-line entry
├── models/                     # Local AI model files (ONNX, etc.)
├── logs/                       # Log file directory (YYYYMMDD.log)
├── ffmpeg/                     # ffmpeg and sox binaries
├── f5-tts/                     # Voice cloning reference audio directory
├── docs/                       # Documentation
├── tmp/                        # Temporary file root
│   ├── _temp/                  # Process-level temp directory
│   └── translate_cache/        # Translation MD5 cache directory
│
└── videotrans/                 # Core business logic code
    │   __init__.py             # ★ VERSION, ChannelProvider definition, get_class() lazy loading
    │   cfg.json                # settings persistence file
    │   params.json             # params persistence file
    │   codec.json              # Video codec cache
    │
    ├── codes/
    │   └── model.py            # Model-related definitions
    │
    ├── configure/              # Global config, queue definitions, top-level base class
    │   ├── config.py           # ★ AppCfg / AppSettings / AppParams / logger / queue definitions / tr() / push_queue() (902 lines)
    │   ├── base.py             # ★ BaseCon base class (_new_process, signal, _exit, convert_to_wav, etc.) (296 lines)
    │   ├── contants.py         # ★ Global constants (model lists, language test text, punctuation, proxy whitelist, etc.)
    │   ├── excepts.py          # ★ Exception hierarchy + get_msg_from_except() (376 lines)
    │   ├── signal_hub.py       # ★ SignalHub singleton (cross-thread Qt signals) (33 lines)
    │   └── whispernet_config.py # Whisper.NET configuration
    │
    ├── task/                   # Task processing logic and background threads
    │   ├── _base.py            # ★ BaseTask base class (8 stage empty methods + 5 flags + shared utilities) (167 lines)
    │   ├── taskcfg.py          # ★ TaskCfgBase/VTT/STT/TTS/STS + InputFile + SignMsg + SrtItem (261 lines)
    │   ├── trans_create.py     # ★ TransCreate full implementation (~1678 lines, video translation core)
    │   ├── speech2text.py      # ★ SpeechToText (batch speech-to-text)
    │   ├── dubbing.py          # ★ DubbingSrt (batch subtitle dubbing)
    │   ├── translate_srt.py    # ★ TranslateSrt (batch SRT translation)
    │   ├── job.py              # ★ 9 BaseWorker subclasses + start_thread() entry (245 lines)
    │   ├── only_one.py         # ★ Single-video interactive Worker(QThread) + uito signal (148 lines)
    │   ├── mult_video.py       # ★ Multi-video batch submission MultVideo(QThread) (54 lines)
    │   ├── _rate.py            # SpeedRate / TtsSpeedRate audio-video alignment engine (877 lines)
    │   ├── separate_worker.py  # SeparateWorker independent vocal separation QThread
    │   ├── simple_runnable_qt.py # QRunnable thread pool utility
    │   ├── child_win_sign.py   # Child window signal handling
    │   └── update_ffmpeg.py    # ffmpeg update management
    │
    ├── recognition/            # Speech Recognition (ASR) module (22 channels)
    │   ├── __init__.py         # ★ Channel constant IDs, _ID_NAME_DICT, run(), is_allow_lang(), is_input_api()
    │   ├── _base.py            # ★ BaseRecogn (VAD segmentation, CJK handling, subtitle merging, 400 lines)
    │   └── _*.py               # 22 channel implementations (_whisper, _whisperx, _whispernet, _qwenasrlocal, _qwen3asr, _funasr, etc.)
    │
    ├── translator/             # Subtitle Translation module (24 channels)
    │   ├── __init__.py         # ★ Channel constants, _ID_NAME_DICT, LANG_CODE, run(), is_allow_translate() (860 lines)
    │   ├── _base.py            # ★ BaseTrans (MD5 caching, line-by-line / full-text translation dispatch, 176 lines)
    │   └── _*.py               # 24 channel implementations (_google, _chatgpt, _deepseek, _gemini, _deepl, _baidu, etc.)
    │
    ├── tts/                    # Text-to-Speech (TTS) module (34 channels)
    │   ├── __init__.py         # ★ Channel constant IDs, _ID_NAME_DICT, SUPPORT_CLONE, CHANGE_BY_LANGUAGE, run() (192 lines)
    │   ├── _base.py            # ★ BaseTTS (async / multi-threaded concurrent dispatch, 304 lines)
    │   └── _*.py               # 34 channel implementations (_edgetts, _openaitts, _azuretts, _gptsovits, _cosyvoice, etc.)
    │
    ├── process/                # Isolated subprocess implementations
    │   ├── __init__.py         # Subprocess function exports
    │   ├── signelobj.py        # ★ GlobalProcessManager (CPU/GPU dual process pools, 167 lines)
    │   ├── prepare_audio.py    # Vocal separation, denoising, punctuation recovery, speaker diarization (4 backends)
    │   ├── stt_fun.py          # ASR subprocess entry (openai_whisper, faster_whisper, paraformer, funasr_mlt, qwen3asr_fun, etc.)
    │   ├── tts_fun.py          # TTS subprocess entry (qwen3tts_fun)
    │   └── vad.py              # VAD Voice Activity Detection (Silero VAD)
    │
    ├── mainwin/                # Main window UI and business logic
    │   ├── main_win.py         # ★ MainWindow(QMainWindow) initialization, signal binding, thread startup (528 lines)
    │   ├── _actions.py         # ★ WinAction core controller (check, start, status update, 798 lines)
    │   └── _actions_base.py    # ★ WinActionBase base class (proxy, mode switching, CUDA, file selection, 590 lines)
    │
    ├── component/              # UI common components
    │   ├── progressbar.py      # Clickable progress bar
    │   ├── set_form.py         # General settings form / About page
    │   ├── onlyone_set_recogn.py    # Single-video mode: source subtitle edit dialog
    │   ├── onlyone_set_role.py      # Single-video mode: speaker role assignment dialog
    │   ├── onlyone_set_editdubb.py  # Single-video mode: dubbing result edit dialog
    │   ├── clip_video.py       # Video clipper component
    │   ├── realtime_stt.py     # Real-time speech recognition window
    │   ├── textmatching.py     # Text comparison window
    │   ├── set_proxy.py        # Proxy settings dialog
    │   ├── set_ass.py          # ASS subtitle style settings
    │   ├── set_cpp.py          # Whisper.cpp path settings
    │   ├── set_xxl.py          # Faster-Whisper-XXL path settings
    │   ├── set_subtitles_length.py # Subtitle length settings
    │   ├── set_threads.py      # Thread count settings
    │   └── controlobj.py       # Control object management
    │
    ├── ui/                     # PySide6 UI definition files (~75 .py files)
    │   ├── en.py               # ★ Main window UI layout definition
    │   ├── chatgpt.py, deepseek.py, gemini.py, ...    # Channel settings dialog layouts
    │   ├── videoandaudio.py, separate.py, peiyin.py, ... # Feature window layouts
    │   └── dark/               # Dark theme resources (darkstyle_rc.py, palette.py)
    │
    ├── winform/                # Channel settings window lazy-loading management (~65 modules)
    │   ├── __init__.py         # ★ get_win() lazy-loading entry + _module_map (91 lines)
    │   ├── chatgpt.py, azure.py, baidu.py, ...  # ~50 channel settings windows (openwin())
    │   └── fn_*.py             # ~10 independent feature windows (batch STT, batch dubbing, batch SRT translation, etc.)
    │
    ├── styles/                 # UI styles and media resources
    │   ├── style.qss           # Qt stylesheet
    │   ├── logo.png            # Splash screen logo
    │   ├── icon.ico            # Application icon
    │   ├── simhei.ttf          # SimHei Chinese font
    │   ├── preview.png         # Preview image
    │   ├── no-remove.mp4       # Placeholder video to prevent cleanup
    │   └── no-remove.wav       # Placeholder audio to prevent cleanup
    │
    ├── util/                   # General utility functions (18 files)
    │   ├── tools.py            # ★ Core utilities (ffmpeg wrappers, subtitle parsing/formatting, file operations, system notifications, model downloads)
    │   ├── gpus.py             # GPU detection and allocation (get_cudaX for available GPU index)
    │   ├── checkgpu.py         # GPU detection thread (AiLoaderThread)
    │   ├── ListenVoice.py      # Voice audition (ListenVioce QThread)
    │   ├── req_fac.py          # HuggingFace custom session factory
    │   ├── cn_tn.py            # Chinese text normalization
    │   ├── en_tn.py            # English text normalization
    │   ├── help_down.py        # Download utility functions
    │   ├── help_ffmpeg.py      # ffmpeg video codec detection
    │   ├── help_misc.py        # Miscellaneous utilities
    │   ├── help_role.py        # Voice role utilities
    │   ├── help_srt.py         # Subtitle file utilities
    │   ├── helper_supertonic.py # Supertonic TTS helper
    │   ├── TestSrtTrans.py     # Translation test utility
    │   └── TestSTT.py          # STT test utility
    │
    ├── language/               # UI multilingual JSON files
    │   ├── en.json
    │   ├── zh.json
    │   └── ...                 # 30+ languages
    │
    ├── prompts/                # AI translation prompt templates (31 files)
    │   ├── srt/                # SRT format translation prompts (chatgpt.txt, deepseek.txt, etc. — 13 files)
    │   ├── text/               # Plain text translation prompts (same 13 files)
    │   ├── recogn/             # Speech recognition prompts (gemini_recogn.txt)
    │   └── recharge/           # LLM re-segmentation prompts (recharge-llm.txt)
    │
    └── voicejson/              # TTS voice configuration files (14 JSON files)
        ├── edge_tts.json       # Edge-TTS voice lists by language
        ├── azure_voice_list.json # Azure TTS voice list
        ├── qwen3tts.json       # Qwen3-TTS voices
        └── ...                 # Other channel voice configurations

14. Extension Development Guide ​

14.1 Adding a New Translation Channel ​

Assume we want to add a translation channel MyTranslator:

Step 1: Create the channel implementation file ​

Create _mytranslator.py in videotrans/translator/:

python
from dataclasses import dataclass
from videotrans.translator._base import BaseTrans

@dataclass
class MyTranslator(BaseTrans):
    def __post_init__(self):
        super().__post_init__()
        self.api_url = 'https://api.example.com/translate'

    def _item_task(self, data: dict) -> str:
        text = data['text']
        source = data['source_code']
        target = data['target_code']
        result = call_my_api(text, source, target)
        return result

Step 2: Assign a channel ID and register ​

In videotrans/translator/__init__.py:

python
MYTRANSLATOR_INDEX = 24   # Assign a unique integer ID

# Add to the end of _ID_NAME_DICT:
_ID_NAME_DICT[MYTRANSLATOR_INDEX] = ChannelProvider(
    "My Translator",
    imp="._mytranslator",
    key_name="mytranslator_key",
    win="mytranslator"
)

Step 3: Add user configuration fields ​

In videotrans/configure/config.py's AppParams._get_defaults():

python
"mytranslator_key": "",
"mytranslator_model": "model-v1",

Step 4: Create the settings window ​

Create mytranslator.py in videotrans/winform/, implementing the openwin() function. Register it in videotrans/winform/__init__.py's _module_map:

python
"mytranslator": ".mytranslator",

Step 5: Optional extensions ​

  • Add language compatibility checks in is_allow_translate()
  • Add UI files in the ui/ directory
  • Add corresponding Actions in ui/en.py

14.2 Adding a New TTS Channel ​

Steps are similar to adding a translation channel:

  1. Create videotrans/tts/_mytts.py, inheriting from BaseTTS
  2. In videotrans/tts/__init__.py, assign an ID and register in _ID_NAME_DICT
  3. If voice cloning support is needed, add the ID to the SUPPORT_CLONE list
  4. If language-dependent role changes are needed, add the ID to the CHANGE_BY_LANGUAGE list
  5. Add corresponding API Key / URL configuration fields in AppParams._get_defaults()
  6. Register the settings window in videotrans/winform/ and _module_map

14.3 Adding a New ASR Channel ​

Steps are the same as for translation/TTS channels. The channel implementation class inherits from BaseRecogn and must implement the .run() method returning List[SrtItem].

14.4 General Conventions ​

  • All channel classes use @dataclass + __post_init__
  • Lazy loading via get_class(channel_id, "recognition/translator/tts", _ID_NAME_DICT)
  • API key validation relies on is_input_api() function + key_name / win fields in _ID_NAME_DICT
  • Translation/dubbing engine internal concurrency is controlled by corresponding fields in settings

Version: v4.03 (VERSION_NUM=403) Homepage: https://github.com/jianchang512/pyvideotransDocumentation: https://pyvideotrans.comBBS: https://bbs.pyvideotrans.com