Skip to content

Yeonik/transcriber

Repository files navigation

transcriber

Self-hosted transcription for audio and video. Runs faster-whisper in a container on your own hardware — the file never leaves the machine, and it works with the network cable pulled.

Transcription services want your recordings. Interviews, meetings, medical notes, legal calls — the material people most want transcribed is exactly the material they should be most careful about uploading somewhere. An optional hosted engine exists for when speed matters more than privacy; it is off unless you turn it on, and the interface says so before you upload anything.


The privacy claim, verified

Tested rather than asserted. With the models cached, the whole stack was recreated on a Docker network marked internal: true — no route off the machine — and a file put through the complete path: upload, ffmpeg, whisper, all four output formats.

app     ->  https://example.com      BLOCKED
whisper ->  https://huggingface.co   BLOCKED (gaierror)
whisper container health                      healthy — models loaded from cache
upload -> transcript, full path       COMPLETED in 4.64 s

It completed because it never needed the network. Note the third line: the whisper service is healthy while cut off, so cached weights load without the library reaching for the Hub — the failure mode most likely to make an "offline" tool quietly not be one. Transcript contents are never written to a log, at any level.

The one exception is the first run, which fetches the weights. It is logged plainly and happens once. → How to reproduce this, and how to pre-load safely

Start it

git clone <this repository> transcriber && cd transcriber
cp .env.example .env

docker compose build
docker compose run --rm --no-deps app php artisan key:generate
docker compose run --rm --no-deps app php artisan migrate --force

# Optional on loopback, mandatory anywhere else:
docker compose run --rm --no-deps app php artisan transcriber:hash-password

docker compose up -d

Open http://localhost:8000. Choose a file, press Transcribe, download TXT, SRT, VTT or JSON. Per-job options live under Advanced; the main path stays one click.

The first start downloads 1.7 GB of weights and looks idle while it does. That is the download, not a hang — watch docker compose logs -f whisper. If port 8000 is taken, set APP_PORT in .env.

Two models, chosen per job

Form label Model On disk 11 s clip 5 min clip Take it when
Fast (default) base 142 MB 1.0 s 59 s Clear speech you can spot-check. ~5× real time.
More accurate large-v3-turbo 1.6 GB 11.4 s 185 s Accents, names, several speakers, noise. ~1.6× real time.

Measured on a 2026 laptop CPU, no GPU, int8, beam_size=5, through the real engine — not copied from a datasheet. The accurate model is 11× slower on the short clip and 3× on the long one: its fixed per-run cost dominates a short recording and disappears into a long one.

The timings are honest; do not read them as a verdict on accuracy. They were taken on samples/jfk.wav, which is close to the best case a small model can be given — studio recording, one speaker, careful diction, no background noise, and a canonical clip the models have effectively seen. On audio like that the two models produce nearly identical text, and Fast is obviously the right choice.

That is not where the difference lives. Reach for More accurate on accented speech, background noise, phone- or video-call quality, overlapping speakers, proper nouns, and specialist vocabulary. On any of those the gap is real and routinely worth the wall clock. A clean-audio benchmark cannot show you that, and this one does not claim to.

Both stay resident once used. The set is closed on purpose — an arbitrary model name would be fetched from the Hub the first time it was used, which is an outbound request in the middle of a job. → Models

Design in one screen

TranscriptionEngine is the seam. LocalWhisperEngine talks to the whisper container over HTTP rather than shelling out, which is why GroqEngine is a sibling rather than a special case. Transcript is the other boundary: engines produce one, formatters consume one, and adding a format never opens an engine file.

Engines declare what they support, so silently ignoring an option is not an available behaviour — the form offers only what the engine can do, a missing critical option refuses the job, a missing non-critical one is dropped and reported. The line is about visibility, not importance: fail when an unapplied option corrupts the result invisibly, record and continue when the degradation is visible in the output itself.

CI never downloads a model — the suite runs against a FakeEngine implementing the same contract. → Architecture

Known limitations

  • Localhost only. One password, no TLS. Forward the port without a TLS-terminating proxy and your recordings and your password travel in clear text. → Deployment security, which does not soften this.
  • No YouTube or third-party site downloading. It violates their Terms of Service and makes a public repository a liability. Direct media URLs are fine; site scrapers are not. A scope decision, not something unfinished.
  • Single user. No accounts, no roles, no audit trail.
  • No speaker diarization, no real-time transcription. Out of scope for v1.
  • Progress is measured, and therefore uneven. The bar is the fraction of the recording decoded so far, not a timer. It jumps where silence was skipped, slows on hard audio, and stalls short of the end when a recording finishes quietly. It is not smoothed. The hosted engine returns its transcript in one piece and reports no progress at all — the page then shows the state alone rather than an empty bar.
  • CPU is slow, honestly. A one-hour recording is ~12 minutes on Fast and most of an hour on More accurate.

Documentation

Models Sizes, measured speeds, the full family, adding one, the cache volume.
Access The password gate, hashing, sessions, and the $ trap that eats bcrypt hashes.
Configuration Every environment variable and what changing it costs.
Operations Ports, hangs, retention, the queue, upgrades, backups.
Deployment security Read before exposing this.
Architecture The seam, the capability rule, testing the real engine.
Changelog What is in each release, and the known limitations of this one.

Layout

app/Transcription/
  Contracts/TranscriptionEngine.php   the seam — engines implement this
  Transcript.php, Segment.php         segments + text, format-agnostic
  Engines/{LocalWhisper,Groq,Fake}Engine.php
  Formatters/{Txt,Srt,Vtt,Json}Formatter.php
  Jobs/TranscribeUpload.php           queued, retryable
  MediaNormaliser.php                 the only place anything is shelled out to
docker/whisper/                       Python service wrapping faster-whisper
docs/                                 full operational documentation

Licence

MIT.

About

Self-hosted transcription for audio and video. Runs Whisper locally — the file never leaves your machine.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages