Self-hosted audio transcription API. Upload an audio file, get back a timestamped transcript plus the non-speech sounds that occurred alongside it.
Speech comes from faster-whisper; sound events (applause, laughter, music, vehicles) come from PANNs. Jobs run through a Redis-backed queue, and clients follow progress over Server-Sent Events rather than polling.
Runs on your own hardware. No third-party transcription service, no per-minute billing, no audio leaving your machine.
Hosted transcription APIs charge per minute and require shipping your audio to someone else. For recordings that are private, long, or simply numerous, that is the wrong trade. This packages the two best open models for the job behind one HTTP API you can run on a spare box.
The sound-event detection is the part that hosted APIs usually don't give you. A
transcript that says [00:04:12] (applause) between two paragraphs is more useful than
one that silently drops it.
- Timestamped transcripts via faster-whisper, with model size configurable per deployment
- Sound event detection via PANNs, interleaved into the transcript timeline
- Redis job queue with a bounded depth, so a burst of uploads degrades predictably instead of exhausting memory
- Live progress over SSE including queue position while a job is still waiting
- Cancellation of queued or running jobs
- API-key auth and rate limiting as middleware on every route
- GPU auto-detection at setup and build time, with a CPU fallback
git clone https://github.com/jhgit0219/audio-transcriber.git
cd audio-transcriber
cp .env.example .env # set API_KEY at minimum
docker compose up -d --buildThe API listens on http://localhost:8000.
Without Docker:
./setup.sh # creates .venv, detects GPU, installs deps
source .venv/bin/activate
uvicorn app.main:app --host 0.0.0.0 --port 8000setup.sh picks a CUDA or CPU PyTorch build depending on what it finds on the host.
Every value is read from the environment. See .env.example.
| Variable | Default | Purpose |
|---|---|---|
API_KEY |
(required) | Value clients must send in the x-api-key header |
ALLOWED_ORIGIN |
— | Single origin permitted by CORS |
WHISPER_MODEL |
medium |
faster-whisper model size (tiny … large-v3) |
RATE_LIMIT |
5 |
Requests per window, per client |
REDIS_URL |
redis://localhost:6379/0 |
Queue backend |
JOB_TTL |
600 |
Seconds a finished job's result is retained |
MAX_QUEUE |
20 |
Queue depth before uploads are rejected |
All routes require an x-api-key header matching API_KEY.
Accepts a multipart/form-data upload under the field file. The upload is streamed to
disk in chunks, so file size is bounded by disk rather than memory. Returns immediately
with a job handle.
curl -X POST http://localhost:8000/api/transcribe \
-H "x-api-key: $API_KEY" \
-F "file=@meeting.mp3"{
"job_id": "3f9c1a72-...",
"status": "queued",
"stream_url": "/api/jobs/3f9c1a72-.../stream"
}Server-Sent Events for the life of the job. Emits queue position while waiting, progress while transcribing, and one terminal event carrying either the transcript or an error.
curl -N http://localhost:8000/api/jobs/$JOB_ID/stream -H "x-api-key: $API_KEY"const es = new EventSource(`/api/jobs/${jobId}/stream`);
es.onmessage = (e) => {
const { status, progress, result } = JSON.parse(e.data);
if (status === "completed") { render(result); es.close(); }
};One-shot status for clients that would rather poll, or that reconnect after losing the stream. Returns status, progress, timestamps, and the result once available.
Cancels a queued or running job and releases its queue slot.
POST /api/transcribe
│
├─ stream upload to disk (chunked)
├─ enqueue job id ──► Redis ◄── worker loop
└─ return { job_id, stream_url } │
faster-whisper (speech)
GET /api/jobs/{id}/stream PANNs (sound events)
│ │
└─ SSE: queue position → progress → result ◄───────────┘
Redis holds both the queue and job state, which keeps the API process stateless: it can be restarted or scaled horizontally without losing in-flight work.
app/
main.py FastAPI app, middleware chain, router wiring
core.py Settings loaded from environment
middleware/ API-key auth, rate limiting
routes/ transcribe.py (upload), jobs.py (status, SSE, cancel)
services/ job_queue.py, redis_client.py, worker.py,
transcriber.py (faster-whisper), sound_detector.py (PANNs)
schemas/ Pydantic request and response models
The included docker-compose.yml runs Redis alongside the API and persists the model
cache in a named volume, so the Whisper weights survive container rebuilds. Redis is
configured with maxmemory-policy noeviction on purpose: silently dropping queued jobs
under memory pressure would be worse than refusing new ones.
For GPU hosts, build with --build-arg COMPUTE=cuda.
MIT. See LICENSE.