Files
whisper-translation/history.md
T

8.7 KiB

Project History: Python Whisper Live Transcription

This document tracks the evolution, technical decisions, and optimizations made for this live audio transcription and translation tool.

Phase 1: Basic Live Transcription

  • Goal: Create a simple script to transcribe live audio.
  • Initial Stack: openai-whisper, sounddevice, numpy.
  • Approach:
    • Captured audio in 2-second chunks using sounddevice.
    • Used the tiny.en model for initial testing.
  • Outcome: Successful basic transcription, but limited by continuous processing (even during silence).

Phase 2: Voice Activity Detection (VAD)

  • Goal: Improve efficiency by only transcribing when someone is speaking.
  • Stack Addition: silero-vad.
  • Approach:
    • Integrated Silero VAD to monitor the audio stream.
    • Transcription is only triggered after a speech segment is followed by a period of silence (500ms).
  • Outcome: Significantly reduced CPU usage and cleaner output.

Phase 3: Apple Silicon Optimization (M-Series/M2)

  • Goal: Leverage the M2's Neural Engine and GPU for better performance.
  • Stack Transition: mlx-whisper (via Apple's MLX framework).
  • Decision: Switched from openai-whisper to mlx-whisper and upgraded the model to small.en for better accuracy without sacrificing speed.
  • Outcome: Faster inference and better battery efficiency.

Phase 4: Local Translation

  • Approach A (LLM): Tried using SmolLM2-135M via mlx-lm for translation.
    • Issue: The LLM was "too talkative," often adding conversational filler or explaining the translation instead of just providing it.
  • Approach B (Dedicated MT): Switched to MarianMT (Helsinki-NLP/opus-mt-en-es).
    • Decision: Chose a dedicated Translation Model for cleaner, direct mapping from English to Spanish.
  • Technical Hurdle (LZMA Error):
    • The local Python environment lacked _lzma support, causing transformers and huggingface_hub to crash.
    • Solution: Implemented a comprehensive lzma mock in the script to provide necessary constants (FORMAT_XZ, etc.) and bypass the system-level limitation.

Current Status

The project now features a high-performance, Apple Silicon-optimized pipeline that:

  1. Detects speech using Silero VAD.
  2. Transcribes using MLX-Whisper (small.en).
  3. Translates using MarianMT (EN-ES).
  4. Operates entirely locally with hardware acceleration.

Phase 5: Simultaneous Multi-Language Translation

  • Goal: Provide translations in Spanish, French, and Arabic at the same time.
  • Approach:
    • Refactored the script to support a dictionary of multiple MarianMT models.
    • Each transcribed English segment is passed through each loaded translation engine sequentially.
  • Performance on M2: Loading 3-4 specialized models + Whisper is highly efficient, using ~1.5GB of RAM and providing near-instant results.

Phase 6: Memory & Generation Safety

  • Issue: Occasionally, long inputs or model glitches caused "runaway" translation generation, which could consume excessive memory.
  • Solution:
    • Artificially truncated input transcription to a maximum of 250 characters.
    • Added max_new_tokens=150 to the translation generation call to ensure the model terminates even if it gets stuck in a loop.

Phase 7: Multilingual Detection & Bridge Translation

  • Goal: Support input in any language, detect it, and translate to English + others.
  • Approach:
    • Switched to whisper-small-mlx (multilingual).
    • Hub-and-Spoke Model: If a non-English language is detected, Whisper's task="translate" is used to create an English "bridge" text, which is then fed into the specialized MarianMT models.
  • Outcome: Full support for multilingual input with centralized translation.

Phase 8: Compilation to Binary

  • Goal: Distribute the script as a single, standalone executable for macOS terminal.
  • Tool: PyInstaller.
  • Process:
    • Used --onefile to bundle the entire Python runtime and its heavy dependencies (Torch, MLX, Transformers).
    • Excluded build artifacts (build/, dist/, .spec) from the repository.
  • Build Script:
    chmod +x build.sh
    ./build.sh
    
  • Troubleshooting: Fixed a runtime ModuleNotFoundError: No module named 'mlx._reprlib_fix' by explicitly adding --collect-all mlx and --hidden-import=mlx._reprlib_fix to the PyInstaller configuration. Also added multiprocessing.freeze_support() to fix infinite loops in the compiled binary.

Phase 9: Real-Time Server Ingest

  • Goal: Send live captions and translations to a central ingest server.
  • Backend: https://emiapi.reynafamily.com/live-captions/ingest.
  • Approach:
    • Implemented a background ingest_worker thread to handle HTTP POST requests without stalling the audio processing.
    • Flat JSON Schema: Used a key-value format as requested (e.g., original, es, en, fr).
    • Reliability: Integrated exponential backoff retries (1s to 15s) to handle network or server failures.

Phase 10: CLI Configuration & Documentation

  • Goal: Make the tool fully configurable without editing the code.
  • Approach:
    • Integrated argparse to allow dynamic selection of translation languages (-es, -en, -ar, -fr), data ingestion (-i), and audio device (-d).
    • Added device listing capability (-l).
    • Excluded the venv directory from git and generated requirements.txt.
    • Created a comprehensive README.md containing setup instructions, usage examples, and a technical note on universal models (like NLLB-200).
  • Fix: Added missing sentencepiece dependency required by MarianMT models.

Phase 11: Speed Optimization (Quantization & Streaming)

  • Goal: Reduce latency and improve real-time feedback.
  • Approach:
    • Streaming Mode (-s): Implemented a 1-second rolling draft transcription that continuously updates the console while the user is still speaking.
    • Quantization (-q): Added support for dynamically swapping to the 4-bit quantized Whisper model (mlx-community/whisper-small-mlx-4bit) for faster inference on Apple Silicon with lower memory bandwidth.

Phase 12: Accuracy & Context Optimization

  • Goal: Improve translation quality of short or broken audio chunks.
  • Approach:
    • Prompt Caching (-c): Implemented a rolling context buffer that feeds the last 200 characters of previously transcribed text back into Whisper as an initial_prompt, maintaining sentence continuity.
    • Language Bypassing (--lang): Added the ability to hardcode the source language to skip the Whisper language identification phase on every chunk.
    • Heuristic Punctuation Buffering (Reverted): Briefly implemented a system to hold English translations until a definitive punctuation mark was reached to prevent grammatical errors. This was reverted because Whisper's punctuation generation is not 100% reliable, leading to translations getting "stuck" in the buffer indefinitely if no period was generated.

Phase 13: Stability & Stuck Detection (Watchdog)

  • Goal: Prevent captions from getting "stuck" during long periods of music, singing, or model hallucinations.
  • Approach:
    • Transcription Watchdog: Implemented a logic that monitors the audio buffer duration and the time since the last unique draft change. If the buffer exceeds 12s without a change for 7s, or if it hits an absolute limit of 25s, it forces a clean reset.
    • Hallucination Filtering: Added regex-based detection for common Whisper hallucinations (e.g., "Thanks for watching", "Please subscribe") and a repetition counter to identify and discard high-frequency loops (e.g., "Hallelujah" repeated 20 times).
    • Buffer Optimization: Reduced the default maximum buffer from 30s to 20s (now configurable via --max-buffer) to ensure more frequent flushes during continuous sound.
  • Outcome: Significantly improved reliability during live church services and musical performances where Whisper previously tended to "hang" or hallucinate.

Phase 14: Multi-Channel & Language Filtering

  • Goal: Isolate specific speakers or languages when the audio feed contains multiple mixed sources.
  • Approach:
    • Stereo Channel Selection: Added --channels and --pick-channel [0|1] flags. This allows the tool to pull a specific audio channel (e.g., English on Left, Translation on Right) from a stereo feed, ignoring the other.
    • Language Filtering (--filter-lang): Integrated a mechanism to discard any segment where the detected language does not match the hardcoded --lang parameter.
  • Outcome: Enabled the ability to "focus" the transcription engine on a single speaker even when the audio input is a complex mix.