Workflow Tools

How to set up an automated caption quality check using whisper and ffmpeg to cut editor time by 70%

How to set up an automated caption quality check using whisper and ffmpeg to cut editor time by 70%

I set out to cut my editor’s caption-cleanup time by 70% and ended up building a small, dependable pipeline that combines Whisper for automated transcription and ffmpeg for audio handling and segment stitching. The result is not perfect human-level captions, but it’s consistent, fast, and surfaces the exact spots an editor needs to touch — which is where the real time savings come from.

Why automate caption quality checks?

Captions are non-negotiable for reach and accessibility, but they’re often noisy: misheard names, platform-specific formatting, and timing drift. Editors spend hours fixing the same kinds of issues. My aim wasn’t to replace editors, but to triage: flag high-risk segments automatically so editors only touch the 20–30% of captions that actually need human attention.

Automated checks reduce manual work in three ways:

  • Surface low-confidence transcriptions and likely errors so editors don’t review everything.
  • Automatically correct predictable issues (smart punctuation, formatting, profanity masks).
  • Provide contextual metadata (speaker change, silence length, audio clipping) that helps prioritize edits.
  • High-level architecture

    The pipeline I use has three main stages:

  • Audio extraction and normalization with ffmpeg.
  • Batch transcription using Whisper (OpenAI Whisper or whisper.cpp / whisperX depending on hardware).
  • Automated quality checks and a small reporting layer that outputs a prioritized list for editors and an annotated captions file (SRT/VTT/JSON).
  • What you need

    Minimal stack:

  • ffmpeg (latest stable)
  • Python 3.9+ (for orchestration scripts)
  • Whisper: either OpenAI Whisper Python package (if you have a GPU) or whisper.cpp / whisperx for CPU/embedded workflows
  • Optional: whisperx for better word-level timestamps and speaker diarization
  • I’ll assume you can run shell commands and install Python packages with pip. If you want a fully serverless route, you can swap the Whisper step for the OpenAI speech-to-text API, but local Whisper gives excellent control and predictable costs.

    Step 1 — audio extraction and normalization

    First: extract a clean mono audio track and normalize the level. This reduces transcription errors caused by low volume or stereo artifacts.

    Example ffmpeg command I use:

    Extract + normalize: `ffmpeg -i input.mp4 -ac 1 -ar 16000 -af "loudnorm=I=-16:LRA=7:TP=-1.5" output.wav`

    Why 16 kHz mono? Whisper and many models work reliably at 16 kHz; smaller files mean faster processing for long videos. The loudness normalization reduces mis-transcriptions on quiet segments.

    Step 2 — transcription with Whisper

    I choose the model based on speed vs accuracy trade-offs. For quick triage, tiny or base models are fast and detect most errors that matter for quality checking. For final captions, medium/large models perform better.

    Local Whisper CLI example:

    `whisper output.wav --model medium --language en --task transcribe --output_format json`

    If you’re using whisperx (recommended when you need word-level timestamps and speaker alignment), run:

    `whisperx output.wav --model medium --device cuda --language en --diarize`

    Whisper outputs transcripts with segments and timestamps. Whisperx can output word-level timestamps and speaker labels which make downstream checks a lot easier.

    Step 3 — automated quality checks

    This is the meat of the workflow. I implemented a small Python script that ingests Whisper JSON and runs a series of checks per segment and per word. Key checks:

  • Low-confidence detection: Mark segments where model confidence (or average word-level confidence) is below a threshold (I use 0.85 for base/medium; adjust by model).
  • High error risk words: Detect names, acronyms, and domain-specific words via a checklist and fuzzy matching (Levenshtein distance). If the model outputs tokens that look like placeholders or hallucinations ("uhh", "mmm", "no, no"), mark them.
  • Overlong lines and timing issues: Flag segments that violate recommended SRT rules (max 42 characters per line or more than 2 lines) or where caption display time is too short (<1.2s) or excessively long (drops viewer tracking).
  • Silence and audio artifacts: Use ffmpeg’s silencedetect or a lightweight VAD (voice activity detection) to flag clipped audio, long silence inside speech, or background noise that likely causes mis-transcriptions.
  • Speaker change detection: If diarization is present, flag segments where speaker labels flip unexpectedly (useful for interviews and podcasts).
  • Profanity and policy checks: Optionally mask profanity or flag sections for editorial review if compliance matters.
  • Each check produces a severity score and a short rationale, e.g. "low_confidence: 0.72 (avg) — needs human review" or "fast_speech: display_time 0.9s — extend or split". The script aggregates results into a prioritized CSV/JSON and writes an annotated SRT with inline notes for editors.

    Practical thresholds and tuning

    Thresholds depend on model and content. My starting values (tuned on podcast/interview content):

  • Average word confidence < 0.85 => high priority
  • Any single-word confidence < 0.6 => highlight word
  • Caption display < 1.2s or > 8s => timing warning
  • Silence gap within segment > 0.9s => re-split segment
  • Speaker overlap > 15% => check diarization
  • Run A/B tests: sample 50 editor-reviewed videos, tune thresholds to reach the sweet spot where flagged content covers 80–90% of actual errors while minimizing false positives. That’s what gave me the 70% time-savings number — editors only opened the flagged segments instead of combing the whole file.

    Example annotated output

    The pipeline writes two useful artifacts for editors:

  • An annotated SRT/VTT where problematic lines include an inline note: e.g., `[!review: low_confidence 0.72; name_detected: "Jhn Doe" possible misread]`.
  • A priority CSV/JSON with timestamp, severity, check list, and suggested action (correct transcript, re-run with larger model, split caption, adjust timings).
  • Edge cases and gotchas

    Some practical things I’ve learned:

  • Properly identify noisy formats — phone recordings and heavy music-backed streams need pre-filtering (denoise filters in ffmpeg or RNNoise).
  • Whisper can hallucinate punctuation; use a post-processing normalization (smart capitalization, common phrase fixes).
  • When you rely on diarization, accept imperfect speaker labels. Use diarization only as a prioritization signal, not as final speaker attribution unless you’ve validated it.
  • For multi-language content, detect language automatically first and pick the appropriate model.
  • Operational notes

    To scale this pipeline:

  • Batch audio pre-processing and transcription on worker nodes (GPU instances for medium/large models).
  • Cache transcripts for re-runs. Many edits are minor; re-transcribing from scratch wastes cycles.
  • Keep a small labeled dataset of "editor decisions" to refine automated heuristics — you can train a lightweight classifier later to predict whether a segment will be edited.
  • This setup isn’t about perfect automated captions; it’s about directing human attention. By combining Whisper’s transcripts, ffmpeg’s audio tooling, and a few pragmatic checks, I was able to reduce caption-cleanup time dramatically while keeping quality high. If you want, I can share the Python script skeleton I use to parse Whisper JSON and produce the annotated SRT/CSV — it’s a small, reusable piece you can plug into CI or your media ops pipeline.

    You should also check the following news:

    How to design a clip gating experiment that increases paid conversions without cutting viral reach
    Content Monetization

    How to design a clip gating experiment that increases paid conversions without cutting viral reach

    I run a lot of experiments for creators and small teams, and one of the trickiest trade-offs I see...

    Aug 09 Read more...
    Exactly how to run a seven-day bitrate and latency ladder test for twitch to find the sweet spot for low-bandwidth viewers
    Streaming Tips

    Exactly how to run a seven-day bitrate and latency ladder test for twitch to find the sweet spot for low-bandwidth viewers

    I run a lot of experiments for creators and product teams, and one test I keep repeating is the...

    Jul 12 Read more...