About • Features • GPU / CUDA • Installation • Architecture • Development • License
Leg2Sub transcribes, translates and subtitles videos locally, with optional GPU acceleration (NVIDIA/CUDA) or CPU fallback. It is a desktop app (Electron + Vite + React) with a Python core (WhisperX) serving the interface, rather than the other way around — there is no HTTP server, no web UI and no Docker: everything runs as a local process on your machine.
| Screen | Input | Output |
|---|---|---|
| Subtitle Videos | MP4/MOV/AVI | SRT + MP4 with softsub and/or hardsub |
| Translate Subtitles | SRT/VTT | Translated SRT (Google Translate) |
| Transcribe Videos | MP4/MOV/AVI | Transcribed text + SRT |
| Colorize Subtitles | SRT | Styled ASS (color, font size, outline and box background) |
Transcription engines: WhisperX (with word-level alignment) or Whisper. Device: GPU (CUDA), CPU or automatic.
- The Settings screen shows the real GPU: its name, VRAM and driver come
from
nvidia-smi(detection independent of torch), so a physical GPU appears as "DETECTED" even before the environment is ready. uv.lockresolves CPU-only torch on Windows (the CUDA deps are gated to Linux). So when setting up the environment, if there is an NVIDIA GPU the app automatically installs the CUDA torch build on top ofuv sync(from thedownload.pytorch.org/whl/cuXXXindex), enabling real acceleration. The CUDA version lives in theCUDA_INDEX_URLconstant insrc/main/pythonEnv.ts. Because the CPU and CUDA wheels share the same version string (2.8.0vs2.8.0+cu126), the reinstall is forced with--reinstall-package— otherwiseuvwould treat the requirement as already satisfied and CUDA would never install.- If you had the environment in CPU mode and then added/enabled a GPU, click Check again in Settings — torch is reinstalled with CUDA support.
- The Whisper models (large-v3, large-v3-turbo, ...) are not
downloaded during environment setup; they are fetched automatically on
first use, depending on the chosen model, and cached in the user home
directory (
~/.cache/huggingfaceand~/.cache/whisper).
- Node.js 20+
- uv and Git on PATH — uv provisions Python and installs the WhisperX fork via git
- An NVIDIA GPU + CUDA driver are optional — without them the app runs on CPU
git clone https://github.com/Paulogb98/Leg2Sub.git
cd Leg2Sub
npm install
npm run devOn the first run, open Settings → Set up environment inside the app so
Electron runs uv sync in core/ and creates the venv (includes torch and
the WhisperX fork — a several-GB download; larger in CUDA mode).
Everything lives at the repository root (the Electron app is the root —
there is no desktop/ subfolder):
Leg2Sub/
├── core/ # Python engine: WhisperX (fork github.com/Paulogb98/whisperX) + ffmpeg
│ ├── worker.py # single entry point called by the app: 1 JSON job via stdin, NDJSON progress via stdout
│ ├── system_info.py # reports python/torch/CUDA/GPU/ffmpeg as JSON (Settings screen)
│ ├── utils/ # library: whisperx/whisper, ffmpeg, subtitles (SRT/ASS/VTT), translation, color styling
│ └── pyproject.toml # dependencies managed via uv (uv.lock pins the versions)
├── src/main/ # main process: window, IPC, managed Python venv, job spawning
├── src/preload/ # contextIsolation bridge (window.api)
├── src/renderer/src/ # React UI (screens, components, zustand store)
└── src/shared/ # TypeScript types shared across main/preload/renderer
No HTTP server: the Electron main process manages its own Python venv
(created in userData/pyenv on first run, via Settings → "Set up
environment", using uv sync) and, for each job, spawns
python core/worker.py, sends the job description as JSON over stdin and
reads NDJSON progress events from stdout:
{"type":"progress","step":1,"stepLabel":"Transcribing with WHISPERX","totalSteps":4,"progressPct":42,"elapsedS":12.4}
{"type":"done","outputs":{"srt":"...","softsub":"...","hardsub":"..."}}
{"type":"error","message":"..."}The 4 job kinds (subtitle, translate, transcribe, colorize) map 1:1 to
the app screens and to the handlers in core/worker.py.
npm run dev # electron-vite dev server + Electron window
npm run typecheck # tsc --noEmit for main/preload and renderer
npm run lint # eslint
npm run build:win # or build:mac / build:linux — packaged installerelectron-builder.yml copies the whole core/ as an extra package resource
(extraResources), so the packaged app loads the same Python code used in
development. Tested only on Windows so far; mac/linux builds have not been
validated.
- Python venv:
userData/pyenv(Windows:%APPDATA%\Leg2Sub\pyenv). Ready marker:pyenv/.ready. - User settings:
userData/settings.json. - Model cache:
~/.cache/huggingfaceand~/.cache/whisper(several GB — relevant when uninstalling/cleaning).
git checkout -b feature/YourFeature
# ... your changes ...
git commit -m 'feat: add YourFeature'
git push origin feature/YourFeatureGPLv3 — see LICENSE.
- 🎙️ WhisperX — transcription with word-level alignment (fork used: Paulogb98/whisperX)
- 🗣️ OpenAI Whisper
- 🎬 LeGen — original reference for the subtitling pipeline
- ⚛️ Electron · React · Vite — the desktop shell and UI
