Skip to main content
Glama
README.md
# whisper-video-transcriber

Paste a TikTok, YouTube or Instagram link and get the spoken text back. Audio is transcribed **locally** with [faster-whisper](https://github.com/SYSTRAN/faster-whisper) on the CPU, so no cloud service hears your videos. Optionally, Claude breaks the transcript down: hook, structure with timestamps, why it works, call to action and an idea for your own content.

It also runs as an **MCP server**, so Claude Code (or any MCP client) can call it as a tool: "transcribe this TikTok and tell me why the hook works".

Built for content research in a homelab. On an 8-core LXC (AMD Ryzen 9 mini PC, Whisper `medium`, `int8`), a 45 s TikTok took 13.5 s and a 208 s video 53.5 s, roughly 4x faster than real time.

Full walkthrough (Proxmox LXC, Docker, Caddy, firewall, Claude Code):
- English: <https://mapplebox.com/self-hosted-video-transcriber-whisper/>
- Deutsch: <https://mapplebox.de/tiktok-videos-transkribieren-whisper-selbst-hosten/>

## What's inside

| File | What it does |
|---|---|
| `app/main.py` | FastAPI app: yt-dlp download, faster-whisper transcription, optional Claude analysis, REST API, MCP server at `/mcp` |
| `app/static/index.html` | Small web UI (German labels) |
| `Dockerfile`, `docker-compose.yml` | Python 3.12 image with ffmpeg, data volume `./data` |
| `.env.example` | Settings you fill in |

## Quick start

Needs Docker with the Compose plugin on a Linux host (a Debian LXC on Proxmox works fine) and about 2 GB of disk for the Whisper `medium` model, which downloads on the first job.

```bash
git clone https://github.com/mapplebox/whisper-video-transcriber.git
cd whisper-video-transcriber
cp .env.example .env && chmod 600 .env    # then edit .env
mkdir -p data
docker compose up -d --build
curl http://127.0.0.1:8000/health
```

Open `http://<host>:8000/` for the web UI.

## Settings

In `.env` (never commit it):

| Variable | Meaning |
|---|---|
| `ANTHROPIC_API_KEY` | Optional. Enables the Claude analysis. Without it you only get transcripts. |
| `AI_MODEL` | Optional, default `claude-haiku-5-5`. |
| `MCP_TOKEN` | Required for `/mcp`. Clients send it as `Authorization: Bearer <token>`. Without it the MCP endpoint stays closed. |
| `MCP_ALLOWED_HOSTS` | Comma-separated host names (or `host:port`) you reach `/mcp` by, e.g. `transcribe.example.com`. Other Host headers are rejected with 421 (DNS rebinding protection). `127.0.0.1` and `localhost` are always allowed. |
| `API_TOKEN` | Optional Bearer token for `/api/*`. The bundled web UI doesn't send one, so leave it empty if you use the UI and protect access at the network level instead. |

In `docker-compose.yml`: `WHISPER_MODEL` (`small` is faster, `large-v3` most accurate), `WHISPER_COMPUTE`, `WHISPER_THREADS`, `DEFAULT_LANGUAGE` (empty = auto-detect), `MAX_DURATION_SECONDS` (default 900).

## Security

This is an internal tool. `/api/*` has no authentication unless you set `API_TOKEN`, and anyone who can call it can spend your Anthropic credit.

- Put it behind a reverse proxy that only allows your LAN / VPN (e.g. Caddy with `remote_ip`), never expose port 8000 to the internet.
- Docker-published ports bypass normal `INPUT` firewall rules. Restrict port 8000 in the `DOCKER-USER` chain so only the proxy can reach it, for example:
  `iptables -I DOCKER-USER -i eth0 -p tcp --dport 8000 ! -s <proxy-ip> -j DROP`
  Or publish it on `127.0.0.1:8000:8000` if the proxy runs on the same host.
- Keep `.env` at mode 600.

## Claude Code

```bash
claude mcp add --transport http --scope user transcriber https://transcribe.example.com/mcp \
  --header "Authorization: Bearer YOUR-MCP-TOKEN"
```

Tools: `transcribe_video`, `list_transcripts`, `get_transcript`, `analyze_transcript`. Every `transcribe_video` call runs the analysis by default (costs API credit); ask for "without analysis" if you only want the text.

## Notes

- **TikTok:** the audio-only stream is often just the attached sound (music), not the voice. The app downloads the smallest muxed video instead, which carries the real audio track.
- **YouTube:** at the time of writing, yt-dlp needs a JavaScript runtime for full YouTube support. Add Deno to the image: `COPY --from=denoland/deno:bin-2.9.7 /deno /usr/local/bin/deno` and install `yt-dlp[default]`.
- **Instagram:** often needs cookies from a logged-in account; this code doesn't pass any.
- **Updating yt-dlp:** it's unpinned on purpose, rebuild with `docker compose build --no-cache`.
- The analysis prompt and the web UI are in German. Change `ANALYSIS_PROMPT` in `app/main.py` (keep the JSON keys, the UI reads them) and the labels in `index.html` for another language.
- Only use it for your own research and respect the platforms' terms and creators' rights. Don't republish other people's content.

## License

MIT, see [LICENSE](LICENSE).