Free, local, open-source video transcription and subtitle generation.
Upload any video or audio file β or paste a URL β and get a timestamped transcript, synchronized playback, translation into 10 languages, and .srt subtitle export. No subscriptions, no usage limits, no data leaving your machine.
Every transcription tool online either has a paywall, a strict free tier, or sends your files to a remote server. Pitchfall runs entirely on your own machine using faster-whisper β a highly optimized local implementation of OpenAI's Whisper model. No API key required for transcription. No account. No cloud.
- ποΈ Local transcription β powered by faster-whisper (Whisper
smallmodel by default, CPU-friendly) - π Video sync β click any transcript segment to jump to that exact moment in the video
- π Translation β translate the transcript into 10 languages (optional, see below)
- π₯ Export β download transcript as
.txtor subtitles as.srt - π URL support β paste any YouTube or direct video URL (powered by yt-dlp)
- π Privacy β files are processed locally and deleted immediately after transcription
| Dependency | Version | Notes |
|---|---|---|
| Python | 3.10+ | 3.12 recommended |
| Node.js | 18+ | |
| ffmpeg | any recent | required by faster-whisper and yt-dlp |
| RAM | 2GB+ free | Whisper small uses ~1GB during transcription |
First run: on first transcription, faster-whisper automatically downloads the Whisper
smallmodel (~245MB). This happens once and is cached locally in~/.cache/huggingface/.
git clone https://github.com/scibilo/pitchfall.git
cd pitchfall
cp .env.example .env
# Edit .env β see Configuration below
docker compose upOpen http://localhost:3000.
1. Clone
git clone https://github.com/scibilo/pitchfall.git
cd pitchfall2. Backend
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r backend/requirements.txt3. Frontend
cd frontend
npm install
cd ..4. Configure
cp .env.example .env
# Open .env with any editor and fill in the values5. Run
bash start.shOpen http://localhost:3000. To stop: bash start.sh stop
Copy .env.example to .env and edit:
# OpenRouter API key β OPTIONAL, only needed for translation.
# Transcription works fully without this key.
# Get a free key at: https://openrouter.ai/
OPENROUTER_API_KEY=
# Comma-separated list of allowed frontend origins.
ALLOWED_ORIGINS=http://localhost:3000| Variable | Required | Purpose |
|---|---|---|
OPENROUTER_API_KEY |
No | Enables the translation feature |
ALLOWED_ORIGINS |
Yes (has default) | Controls which origins can call the API |
No other keys are needed. Whisper runs locally β no OpenAI key, no Google key, nothing else.
Translation is powered by OpenRouter using free-tier models (Gemma, Llama, Mistral, DeepSeek, Qwen β tried in order with automatic fallback).
Important things to know:
- Free models have rate limits. A
503 - All translation models are temporarily unavailableerror means the free tier is momentarily saturated. Wait 10β30 seconds and retry β this is not a bug. - Free models occasionally go offline. Pitchfall tries 5 different models before giving up, so partial outages are handled automatically.
- Translation is optional. Transcription, video sync, and
.srtexport all work without any API key. - Want reliable translation? Upgrade to a paid plan on OpenRouter and change the model name in
backend/services/translation_service.py. The code structure stays identical. At OpenRouter's current pricing, a few hundred transcriptions cost less than $1.
- Open http://localhost:3000
- Upload a file β drag & drop or browse (MP4, MP3, MOV, WAV, and most common formats)
- Or paste a URL β YouTube, Vimeo, or any direct media URL supported by yt-dlp
- Wait for transcription β a progress bar shows the current stage and latest recognized segment
- Click any segment in the transcript panel to jump to that moment in the video
- Translate (optional) β select a language and click Translate
- Export β copy to clipboard, download as
.txt, or download subtitles as.srt
pitchfall/
βββ backend/ # FastAPI + faster-whisper
β βββ main.py # API endpoints, startup/shutdown cleanup
β βββ requirements.txt
β βββ services/
β βββ transcription_service.py # Whisper streaming transcription
β βββ translation_service.py # OpenRouter with 5-model fallback
β βββ ytdlp_service.py # URL media download
βββ frontend/ # Next.js 16 + Tailwind CSS 4
β βββ postcss.config.mjs # Required for Tailwind CSS 4
β βββ next.config.ts
β βββ package.json
β βββ src/
β βββ app/
β β βββ layout.tsx
β β βββ page.tsx # Main page, state management, reset logic
β β βββ globals.css
β βββ components/
β βββ TranscriptViewer.tsx # Video player + transcript sync
β βββ UploadForm.tsx
β βββ UrlForm.tsx
β βββ ProgressBar.tsx
βββ docker-compose.yml
βββ start.sh # Dev launcher (no Docker required)
βββ cleanup.sh # Clears temp files and build caches
βββ .env.example
In backend/services/transcription_service.py, change:
model_size = "small"| Model | Download size | RAM | Speed (CPU) | Accuracy |
|---|---|---|---|---|
tiny |
~75MB | ~390MB | fastest | lower |
base |
~145MB | ~500MB | fast | decent |
small |
~245MB | ~1GB | moderate | good (default) |
medium |
~770MB | ~2GB | slow | very good |
large-v3 |
~1.5GB | ~3GB | very slow | best |
For everyday use on a laptop, small is the right tradeoff. For professional subtitling, medium or large-v3 deliver significantly better accuracy (GPU strongly recommended for those sizes).
Page loads but has no styling (plain HTML)
The postcss.config.mjs file is missing from frontend/. Add it:
const config = { plugins: { "@tailwindcss/postcss": {} } };
export default config;Then delete frontend/.next/ and restart.
Translation returns 503
Free OpenRouter models are rate-limited or temporarily offline. Wait 10β30 seconds and retry. Check status.openrouter.ai for ongoing outages.
Transcription hangs at 0%
The Whisper model is downloading for the first time (~245MB). Check the terminal for download progress β it only happens once.
ffmpeg: command not found
sudo apt install ffmpeg on Ubuntu/Debian, or brew install ffmpeg on macOS.
CORS error in browser console
Set ALLOWED_ORIGINS in .env to match the exact URL your frontend runs on.
Pitchfall is designed for local, single-user use on a trusted machine. It is not hardened for public deployment. Before exposing it on a network:
- No authentication. Any client reaching the backend port can upload files and consume your OpenRouter credits.
- No file size limits. A large upload can fill your disk.
- CORS defaults to
http://localhost:3000. Do not widen it without also adding authentication. .envmust never be committed. Rungit statusbefore every push and confirm.envis not in the staged files. If a key is leaked, rotate it on the OpenRouter dashboard immediately.- For anything beyond local use, deploy behind a reverse proxy (nginx/Caddy) with HTTPS, HTTP basic auth, and rate limiting.
bash cleanup.shRemoves leftover temp files, Python __pycache__, and Next.js build caches.
Pull requests are welcome. For major changes, open an issue first to discuss what you'd like to change.
MIT β see LICENSE.


