Architecture
Translay is three processes on the same computer: the Python server (vt/), the voice process with its own environment, and the interface (web/), inside the desktop app window (desktop/) or in a browser. There is no database or external services: everything lives in JSON and WAV files inside data/ and each library (see Data on disk).
Processes
Section titled “Processes”Desktop app
Section titled “Desktop app”desktop/ is a Tauri 2 (Rust) app that shows the same web interface in its own window:
- On launch, it loads its home page (
desktop/ui/), which uses the font, colors and buttons fromweb/(copied bydesktop/scripts/prepare.mjs). - The first time, or if a new version changes the packages, it installs the environments with the bundled
uv:uv pip sync --require-hashesfromdesktop/locks/server.txton the bundled Python 3.12, and fromdesktop/locks/voice.txt(torch for CUDA 12.8) on Python 3.11, if the GPU has at least ~4 GB.syncinstalls exactly the lock, without resolving dependencies on your computer. Progress comes from how much the package cache grows. - It starts
run.pywithout a window with--data-dir(the chosen folder),--port(8765 or the next free one) andTRANSLAY_VENVSpointing to its environments, with the bundled ffmpeg and Node.js at the front of thePATH. The process runs inside a Windows job object that closes the whole tree (voice, yt-dlp, ffmpeg) when the app exits, even if it is killed abruptly. - When the server writes
Listo: http://127.0.0.1:<port>(“Ready: …”), it grants that exact origin permission for the window controls and the app’s commands, and navigates to the page.web/js/desktop.jsturns the header into a title bar and adds Preferencias › App de escritorio (Preferences › Desktop app). - If the server’s output closes (all its processes ended) without the app having asked for it, it returns to its home page with the last lines. The “Reiniciar ahora” (Restart now) button in Rendimiento (Performance) doesn’t count: the new server inherits the output and stays in the same job.
Server
Section titled “Server”python run.py calls vt.app.main(), which:
- Loads Whisper (
large-v3-turboby default) and warms it up with one second of silence; then the translator (opus-big) with a test sentence. - Opens the libraries (
vt/vaults.py) and registers in each one the YouTube videos already in itsyoutube/folder. - Creates the voice client in lazy mode: the voice process starts the first time it is needed.
- Returns Whisper and the translator to RAM: the server starts idle (see GPU and idle).
- Brings up the queue (
Processor), exports (Exporter), access (Auth), webhooks and the API, and serves everything withAppServeron127.0.0.1:8765.
Everything runs in a single asyncio loop. Heavy work goes to threads:
| Thread | What it does |
|---|---|
gpu (ThreadPoolExecutor, 1 thread) |
Recognition and translation. |
tts (ThreadPoolExecutor, 1 thread) |
Voice requests to the voice process. |
Queue (Processor._loop) |
Processes the saved videos, one at a time. |
Downloads (Processor._download_loop) |
Downloads YouTube videos with yt-dlp, without the GPU. |
Exports (Exporter._loop) |
Mixes and packages with ffmpeg, on the CPU. |
Idle (Resources._watch) |
Checks inactivity every 2 s and puts the models to sleep. |
| Thumbnails at startup | Fills in thumbnail, channel and categories for already downloaded videos, with 3 s between queries. |
Voice process
Section titled “Voice process”vt/tts.py (TTSClient) launches vt/tts_worker.py with the Python from .venvs/<engine>/ (Scripts/python.exe on Windows, bin/python on Linux). That way the voice engine has its own torch and dependencies without touching the server’s.
Communication uses vt/ipc.py: each message is a frame [u32 JSON length][UTF-8 JSON][u32 binary length][binary] over stdin/stdout. The process reserves stdout for frames and sends everything the libraries print to stderr; the server shows it with the [voz] prefix.
| Operation | Response |
|---|---|
| (at startup) | {"ok": true, "sr": …} once the model has loaded and warmed up. |
synth (text, lang, ref, ref_text, params) |
{"ok", "sr"} + float32 audio. |
offload / onload |
Moves the model (and DeepFilterNet) to RAM or back to the GPU. |
quit |
Ends the process. |
TTSClient handles one request at a time; if the process isn’t alive, it starts it before synthesizing.
Browser
Section titled “Browser”The interface uses no framework or build step: web/index.html loads the CSS and JS files in order (see Interface). It connects over WebSocket to /ws (with ?lib=<library> if the page is /b/<library>) and reconnects on its own after one second if it drops. Videos, audio and voices are requested over HTTP with Range support, so you can seek within the video.
vt/ modules
Section titled “vt/ modules”| Module | Responsibility |
|---|---|
app.py |
Entry point: arguments, model loading, HTTP routes and startup. |
httpws.py |
Static HTTP server (with Range) and RFC 6455 WebSocket over asyncio, dependency-free. |
session.py |
One session per tab: WebSocket protocol, permissions, live processing and the tab’s voice queue. |
processor.py |
Server queue: processes saved videos and downloads YouTube ones. State in data/queue.json. |
vad.py |
Energy-based voice detector with an adaptive noise floor. |
asr.py |
Whisper (faster-whisper on CTranslate2): audio → words with timestamps. |
streaming.py |
Streaming transcription with local agreement: confirms words and closes sentences. |
translate.py |
Translators on CTranslate2 (Opus-MT, NLLB, MADLAD) with the same interface. |
textproc.py |
Text before and after translating: sentences, idioms, form and Latin American conventions. |
glossary.py |
Each library’s glossary: terms that aren’t translated or have a fixed translation. |
speakers.py |
Online diarization: voiceprint (WeSpeaker in ONNX) and each person’s references. |
dub.py |
Per-sentence dubbing: person, reference, synthesis, duration and loudness adjustment. |
tts.py |
Voice process client. |
tts_worker.py |
Voice process: OmniVoice (and Chatterbox or VoxCPM, for comparison). |
enhance_df.py |
DeepFilterNet3 to clean noisy references (runs in the voice process). |
ipc.py |
Binary frames over pipes between processes. |
audiofx.py |
Custom audio in numpy: resampling, WSOLA, loudness, spectral gate, YIN, test noise, WAV. |
repair_refs.py |
Repair of voice references trimmed by earlier versions. |
resources.py |
Idle mode and GPU turns (FairLock). |
gpu.py |
CUDA DLLs on Windows and the CTranslate2 cache limit. |
store.py |
Each video’s transcript (transcripts/<video>.json). |
library.py |
A library’s videos and collections (library.json). |
vaults.py |
Multiple libraries, each in its own folder; paths to their files. |
prefs.py |
Schema and values of the preferences (data/prefs.json). |
youtube.py |
Downloads with yt-dlp, thumbnails, channels and categories. |
cookies.py |
Imports the YouTube session (browser or cookies.txt). |
exporter.py |
Exports translated or original video and audio, subtitles and transcript. |
auth.py |
Access: owner, private, password-protected or public libraries, and API keys. |
api.py |
HTTP API v1, sign-in and the file guard (make_guard). |
events.py |
Event catalog and emission. |
webhooks.py |
Signed delivery of events to other apps, with retries. |
The path of a video
Section titled “The path of a video”A YouTube link, end to end:
- Import. The browser sends
youtube,playlistoryt_batchover WebSocket. The session registers the video in the library (withoutmediayet) and adds it to the queue. - Download.
Processor’s download thread fetches it with yt-dlp to<library>/youtube/<id>.mp4and<id>.m4a, with its thumbnail and metadata, and completes thelibrary.jsonentry. - Take its turn. The queue thread takes the first downloaded job; if someone is watching a video, that one goes first and starts from its playback position.
- Decode. PyAV converts the audio to 16 kHz mono. It is read in 3 s blocks.
- Recognize.
StreamingTranscriberaccumulates the blocks, queries Whisper and delivers closed sentences (Segment) with their original audio. - Translate. Each sentence goes through
textproc.pyand Opus-MT with the library’s glossary.Transcript.add_segmentsaves it. - Identify the person.
Dubber.identifycomputes the voiceprint, assigns the person and improves their reference;Dubber.relabelcorrects earlier short sentences. - Dub. The sentence goes to the
ttsthread: OmniVoice clones the person’s voice, duration and loudness are adjusted, and the WAV ends up in<library>/audio/<video>/<sentence>.wav. - Notify. Each step emits a
bgmessage (segment,translations,dub,covered) that the server forwards only to the tabs that have that video open. - Play. The browser downloads each voice as it gets close and plays it at the start of its sentence, with the original attenuated underneath.
A file from your computer follows the same path with two differences: its identifier is a content fingerprint (SHA-256 of the size, the first MB and the last MB, truncated to 32 characters) and, while the server doesn’t have its copy, the browser sends the audio ahead of playback. See Pipeline.