AI-powered video redubbing — transcribes, translates, and re-voices video with OpenAI APIs. Ships as a React PWA + FastAPI service.
- Full redubbing pipeline — Whisper STT → GPT translation → TTS → ffmpeg mix-down
- Async TTS — up to 100 concurrent API calls, 5× faster than sequential
- Voice refinement — AI-guided voice selection with per-project instructions and cached previews
- React PWA — installable, works offline, real-time job progress
- Project management — multi-project SQLite store, auto file scanning, language detection
- Docker-first — single
docker-compose upgets you running
Prerequisites: Docker 20.10+, OpenAI API key
# 1. Create a config directory and env file
mkdir -p config
echo "OPENAI_API_KEY=sk-your-key-here" > .env
# 2. Start
docker-compose up -d
# 3. Open
open http://localhost:8000config/ holds redubber.db (projects, videos, settings) and survives container restarts.
Tip: You can also set the OpenAI API key in the app's Settings page — it's stored in the database and persists across restarts.
# Install all deps + git hooks
make install
# Start backend (port 8000) and frontend (port 5173) in parallel
make devOr individually:
make dev-backend # FastAPI + uvicorn --reload
make dev-frontend # Vite HMRFrontend proxies /api/* to localhost:8000 automatically.
make test # Run backend tests (excludes integration)
make lint # ruff check
make format # ruff check --fix + ruff format
make build # Production frontend build → frontend/dist/
make story # Storybook component explorer (port 6006)All variables are read at startup. Set them in .env (local dev) or pass them to the container.
| Variable | Description |
|---|---|
OPENAI_API_KEY |
OpenAI API key (sk-...) — can also be set via the UI Settings page and is then persisted to the database |
| Variable | Default | Description |
|---|---|---|
REDUBBER_CONFIG_PATH |
(empty) | Set this in production. Directory where redubber.db is stored. All UI settings (API key, voice, models) are stored in the database — everything survives container restarts when this points to a mounted volume. |
REDUBBER_WORKING_DIR |
(empty) | Root where per-project .redubber/ artefact directories are created. Defaults to a .redubber/ folder inside each project's own directory. |
REDUBBER_PROJECTS_ROOT |
(empty) | Starting directory for the file browser when creating a new project. |
| Variable | Default | Description |
|---|---|---|
MAX_CONCURRENT_REDUBS |
1 |
Max simultaneous redubbing jobs. Increase only if CPU/RAM allow — each job is already heavily parallelised internally. |
TASK_QUEUE_MAX_SIZE |
100 |
Max queued jobs before new submissions are rejected. |
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
INFO |
Python log level (DEBUG, INFO, WARNING, ERROR) |
CORS_ORIGINS |
http://localhost:5173,... |
Comma-separated allowed CORS origins |
The included docker-compose.yml is production-ready for single-host deployments.
OPENAI_API_KEY=sk-... docker-compose up -dPersistent data lives in ./config (mounted as /config in the container). Back this directory up — it contains the database, all settings, and is the only stateful data the app writes.
Resource limits are set in docker-compose.yml (default: 2 CPU / 4 GB RAM). Tune based on video volume and concurrency needs.
Pre-built images are published on every push to main:
# Latest stable
docker pull ghcr.io/j0rsa/redubber:latest
# Pinned version
docker pull ghcr.io/j0rsa/redubber:v2.0.3Run standalone:
docker run -d \
-p 8000:8000 \
-e OPENAI_API_KEY=sk-your-key \
-e REDUBBER_CONFIG_PATH=/config \
-v $(pwd)/config:/config \
ghcr.io/j0rsa/redubber:latestThe app serves the React frontend from / and the API from /api. A minimal nginx config:
server {
listen 443 ssl;
server_name redubber.example.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Required for large video uploads
client_max_body_size 4G;
proxy_read_timeout 600s;
}
}curl http://localhost:8000/api/health
# {"status":"healthy","version":"2.0.3"}┌──────────────────────────────────────────────┐
│ React PWA (Vite, TanStack Query, CSS Modules)│
│ served from / by FastAPI StaticFiles │
└─────────────────────┬────────────────────────┘
│ /api/*
┌─────────────────────▼────────────────────────┐
│ FastAPI (uvicorn) │
│ ├─ /api/projects project CRUD │
│ ├─ /api/redub submit redub job │
│ ├─ /api/tasks job status & cancel │
│ ├─ /api/settings tool-level settings │
│ └─ /api/projects/{id}/voice-* refinement │
│ │
│ TaskQueueManager (asyncio + ThreadPoolExecutor)
│ └─ Pipeline stages: │
│ 1. Extract audio (ffmpeg) │
│ 2. Transcribe (Whisper / gpt-4o-transcribe)
│ 3. Translate (GPT-4o) │
│ 4. TTS (gpt-4o-mini-tts, async)│
│ 5. Assemble audio (ffmpeg) │
│ 6. Mix with video (ffmpeg) │
│ 7. Finalize (validate, replace, cleanup)
└─────────────────────┬────────────────────────┘
│
┌─────────────────────▼────────────────────────┐
│ SQLite (redubber.db) │
│ projects · video_files · subtitle_files │
│ voice_instruction_generations │
│ tts_preview_cache · voice_selection_history │
│ app_settings (OpenAI key, voices, models) │
└──────────────────────────────────────────────┘
Find the best AI voice for a project before committing to a full redub.
- Open a project → click Refine Voice
- Pick a representative segment (5–15 s)
- Analyse with AI — GPT analyses tone, pace, accent, energy
- Edit instructions if needed, or regenerate with feedback
- Preview all 6 voices in ~10 s (parallel TTS, cached)
- Select and Save — applied to all future TTS in this project
| Voice | Character | Best for |
|---|---|---|
| Alloy | Neutral, balanced | General purpose |
| Echo | Male, clear | Technical, instructional |
| Fable | British, expressive | Storytelling |
| Onyx | Deep, authoritative | News, reports |
| Nova | Warm, engaging | Education, friendly content |
| Shimmer | Soft, gentle | Calm, meditative content |
Video: mp4, avi, mkv, mov, wmv, flv, webm, m4v, mpg, mpeg, 3gp, ogv
Subtitles: srt, vtt, ass, ssa, sub, sbv, ttml, dfxp, stl, scc
Interactive Swagger UI available at /api/docs when the server is running.