Best Self-Hosted Transcription for Law Firms: Four Open Stacks and the Three Tests That Decide It
Forty hours of client interviews, a Friday deadline, and a firm rule that no client audio leaves the building. Every SaaS transcription service is disqualified at sentence one, and the partner wants to know what runs locally, labels speakers, and survives a review pass.
💡 What You Will Learn
Forty hours of client interviews, a Friday deadline, and a firm rule that no client audio leaves the building. Every SaaS transcription service is disqualified at sentence one, and the partner wants t
📜 Table of Contents
- The three constraints that actually decide it
- The four stacks, with numbers that can be checked
- Test 1: containment, including the model weights
- Test 2: speaker labels decide whether the transcript is usable
- Test 3: does it fit the hardware you already have
- A working recipe: batched transcription with provenance in the output
- Evidence-grade hygiene, which no vendor will do for you
- Buy or build: the threshold is review hours, not accuracy
- FAQ
- Can Whisper tell me who is speaking?
- What hardware does a small firm actually need?
- Is the transcript admissible as evidence?
- Why not just use a cloud transcription service?
- What to do this week
- Related on this site
A paralegal at a twelve-person litigation firm has 40 hours of recorded client interviews, a deadline on Friday, and a policy sentence that ends most conversations with SaaS vendors: "no client audio leaves the building." The partner does not want a tool review. The partner wants to know whether the firm can produce usable transcripts in-house by Friday, and what it costs in hardware and hours.
That is the real shape of this purchase decision, and it is why "best AI transcription" lists are useless here. For a firm handling privileged material, the question is not which service has the highest accuracy score. It is which stack can run on hardware the firm controls, label who said what, and leave enough of an audit trail that a transcript can be defended later. Four open-source stacks answer that question differently, and the differences are checkable.
The three constraints that actually decide it
- Containment. Can the whole pipeline run with the network cable unplugged, including model weights?
- Speaker attribution. Does the output distinguish interviewer from interviewee, or does it hand you a wall of unlabelled text that someone must re-listen to?
- Review cost. A machine transcript is a draft. The expensive part is the human pass over names, dates, amounts and objections โ and some stacks make that pass cheaper by giving word-level timestamps.
Accuracy headlines do not measure any of those three. Skip them and decide on these.
The four stacks, with numbers that can be checked
| Stack | What it is | Verified on 2026-10-23 | Where it fits |
|---|---|---|---|
faster-whisper | CTranslate2 reimplementation of Whisper; Python library and CLI | PyPI 1.2.1, MIT, ~26k GitHub stars | A batch job over a folder of recordings; the default choice for a firm with a GPU box or a decent CPU server |
whisper.cpp | C/C++ Whisper with GGML quantization; runs on CPU, Apple Silicon, CUDA | release v1.9.5 (2026-10-06), MIT, ~54.5k stars | No GPU, or a machine that must stay minimal; also the practical route on macOS |
WhisperX | Whisper plus alignment and diarization; word-level timestamps | PyPI 3.8.6, BSD-2-Clause, ~24.8k stars, requires Python 3.10 to 3.13 | When the transcript must say who spoke and when, not just what |
pyannote-audio | Speaker diarization toolkit (who spoke when) | MIT, ~10.7k stars | Paired with Whisper, or used alone to segment an existing recording |
Two of those rows already carry a decision the buyer may not expect. WhisperX pins Python below 3.14 โ a firm standardising on the newest interpreter will hit a wall at install time, not at runtime. And diarization is not free of paperwork (next section).
Test 1: containment, including the model weights
The audio is the obvious leak surface, but weights are where air-gapped installs actually break. faster-whisper downloads its CTranslate2 models from the Hugging Face Hub on first use, which means a machine with no egress will fail the first time it is asked to transcribe something. Pre-pull the model on a connected machine, copy the directory, and point the loader at it:
# on a connected machine
python -c "from faster_whisper import WhisperModel; WhisperModel('large-v3', device='cpu', compute_type='int8')"
# copy the HF cache directory to the offline box, then on the offline box:
export HF_HOME=/srv/models/hf
python transcribe.py /srv/audio/interview-01.wav
License terms are the second half of containment, and they differ in ways that matter for a regulated buyer. faster-whisper and whisper.cpp are MIT. WhisperX is BSD-2-Clause. MinerU, a popular document-parsing project often considered for the same "self-host everything" stack, ships an Apache-2.0 license with additional terms appended โ the file says so in its first lines. Reading the license file, not the badge on the repo, is the only reliable check before a procurement conversation.
Test 2: speaker labels decide whether the transcript is usable
Whisper-family models transcribe speech without telling you who spoke. For a two-person interview that sounds trivial until you try to reconstruct who conceded what from a continuous paragraph. The standard fix is diarization: segment the audio by speaker, then align transcription to those segments.
WhisperX does this with pyannote models, and the setup carries a step that surprises people mid-install: you must supply a Hugging Face access token and accept the model's user agreement on the Hub before diarization will run. That is documented behaviour, not a bug. It also means the offline box needs the model files copied across, plus the token on the machine that fetches them:
whisperx /srv/audio/interview-01.wav --model large-v3 --diarize --hf_token "$HF_TOKEN"
The output gives you segments with speaker labels and word-level timing. Two practical consequences: word timestamps make the human review pass dramatically cheaper, because a reviewer can jump to 00:14:32 and hear the disputed sentence instead of hunting through an hour of audio. And diarization quality depends on audio quality โ a single microphone in a conference room yields "SPEAKER_00" and "SPEAKER_01" swapping mid-sentence, which a reviewer must merge. Budget for that. It is the most common reason a firm concludes "the AI is not ready" when the actual problem was one microphone for four people.
Test 3: does it fit the hardware you already have
Whisper large-v3 is roughly 1.55 billion parameters. In fp16 that is about 3.1 GB of weights; in int8 about 1.55 GB, before activations and decoding overhead. That is small enough to run on a modest GPU and, with int8 in faster-whisper, on a CPU server that is otherwise idle at night. Which is the second point: transcription is a batch job, so the cheapest hardware decision is often "run it from 8pm to 6am" rather than "buy a card".
Throughput claims need care. The WhisperX project documents 70x realtime with large-v2 on suitable hardware; faster-whisper's batched pipeline is the practical way to approach that on a single GPU. Treat both as ceilings measured under someone else's conditions, and establish your own number in the first hour: transcribe one hour of your own audio, time it, and multiply. A firm with 40 hours of interviews needs to know whether that is a 35-minute job or a 4-hour job before it promises a Friday deadline.
time python transcribe.py /srv/audio/interview-01.wav # one hour of audio, then multiply
If the result is measured on a laptop CPU with the default model, expect something closer to realtime than to 70x. That is not a failure of the tool; it is the difference between a dedicated GPU and a shared machine.
A working recipe: batched transcription with provenance in the output
Copy-pasteable, and deliberately boring: batched inference, VAD to skip silence, word timestamps for review, and every transcript stamped with the audio hash and the model name so a later reader knows exactly what produced it.
import hashlib, json, sys, datetime
from faster_whisper import WhisperModel, BatchedInferencePipeline
src = sys.argv[1]
model = WhisperModel("large-v3", device="cuda", compute_type="float16")
batched = BatchedInferencePipeline(model=model)
segments, info = batched.transcribe(src, batch_size=16, word_timestamps=True)
def sha256(path, chunk=1 << 20):
h = hashlib.sha256()
with open(path, "rb") as f:
for b in iter(lambda: f.read(chunk), b""):
h.update(b)
return h.hexdigest()
out = {"source": src, "audio_sha256": sha256(src), "model": "large-v3",
"engine": "faster-whisper 1.2.1", "language": info.language,
"generated_at": datetime.datetime.utcnow().isoformat() + "Z",
"segments": [{"start": round(s.start, 2), "end": round(s.end, 2), "text": s.text}
for s in segments]}
with open(src + ".json", "w", encoding="utf-8") as f:
json.dump(out, f, ensure_ascii=False, indent=1)
print(len(out["segments"]), "segments")
Two details earn their keep. VAD filtering is on by default in the batched pipeline, which cuts the cost of long recordings with silence. And the hash in the output is what lets the firm answer "is this the same recording we transcribed in March" without relying on filenames.
Evidence-grade hygiene, which no vendor will do for you
- Hash the source audio on ingest and store the hash with the transcript. Filenames get renamed; hashes do not.
- Record the engine and model version in every transcript. A re-run six months later with a newer model will produce different words, and that difference needs an explanation.
- Treat machine output as a draft. Names, amounts, dates and quoted language are where ASR fails, and those are precisely the tokens that matter in a deposition summary.
- Redact before circulating. If the transcript will be shared outside the matter team, strip identifiers from a copy rather than from the original.
- Set a retention rule for audio, not just transcripts. Audio is the sensitive artefact; a pipeline that deletes the transcript but keeps the recordings has solved nothing.
Buy or build: the threshold is review hours, not accuracy
| Situation | What wins | Why |
|---|---|---|
| Under ~20 hours of audio a month, no GPU, one reviewer | A paid service, if it will sign the terms you need | The engineering time to build and maintain a pipeline costs more than the seats |
| Recordings that cannot leave the building at all | Self-hosted faster-whisper or whisper.cpp | Containment is a hard constraint; no vendor feature substitutes for it |
| Two-speaker interviews at volume, reviewer time is the bottleneck | WhisperX with diarization plus word timestamps | Jump-to-word review is the real time saver, more than raw accuracy |
| Audio arrives on someone else's hardware, mixed quality | Either, plus a mandatory listening pass | Diarization errors and misheard names are a property of the recording |
The threshold moves with the cost of review, not with benchmark scores. A stack that is 3 percent less accurate but gives word-level timestamps can save more reviewer hours than it loses โ and that is measurable inside a week, using your own recordings rather than a leaderboard.
FAQ
Can Whisper tell me who is speaking?
Not by itself. Whisper-family models produce text with timing but no speaker identity. Speaker labels come from diarization, which WhisperX wires up through pyannote models; that path requires a Hugging Face token and accepting the model's user agreement before it will run.
What hardware does a small firm actually need?
For batch work, a single consumer GPU with 8 GB or more runs large-v3 comfortably in fp16, and a CPU server with several cores can run the int8 build overnight. The number that matters is minutes per hour of audio on your own machine, measured once, rather than a specification from a review site.
Is the transcript admissible as evidence?
That is a legal question for counsel, not a software property. What the software can do is preserve provenance: the source file's hash, the engine and model version, timestamps, and an unchanged original. Whether and how a transcript is used is decided by the rules of the forum.
Why not just use a cloud transcription service?
If client confidentiality rules and the firm's own policy permit it, a cloud service is usually cheaper and faster to start. The self-hosted route exists for the cases where uploading the recording is the thing that cannot happen, and it trades setup effort for that guarantee.
What to do this week
Pick one representative recording, run the recipe above on it with the default model, and time it. Then listen to the first five minutes against the text and count the errors that would matter in a summary: names, numbers, dates, anything in quotation marks. If that count is acceptable after a human pass, the stack is viable and the rest is plumbing โ hashes, retention, and a reviewer checklist. If it is not, no amount of GPU budget fixes it, and the honest conclusion is that this material still needs a human transcriptionist.
Related on this site
Written by our editorial team; tools listed here are tested or verified against public sources. Links point to official sites or GitHub repos for reference only โ no paid placements.
