18 KiB
18 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.enmodel for initial testing.
- Captured audio in 2-second chunks using
- 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-whispertomlx-whisperand upgraded the model tosmall.enfor better accuracy without sacrificing speed. - Outcome: Faster inference and better battery efficiency.
Phase 4: Local Translation
- Approach A (LLM): Tried using
SmolLM2-135Mviamlx-lmfor 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
_lzmasupport, causingtransformersandhuggingface_hubto crash. - Solution: Implemented a comprehensive
lzmamock in the script to provide necessary constants (FORMAT_XZ, etc.) and bypass the system-level limitation.
- The local Python environment lacked
Current Status
The project now features a high-performance, Apple Silicon-optimized pipeline that:
- Detects speech using Silero VAD.
- Transcribes using MLX-Whisper (small.en).
- Translates using MarianMT (EN-ES).
- 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
MarianMTmodels. - Each transcribed English segment is passed through each loaded translation engine sequentially.
- Refactored the script to support a dictionary of multiple
- 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=150to 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.
- Switched to
- 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
--onefileto bundle the entire Python runtime and its heavy dependencies (Torch, MLX, Transformers). - Excluded build artifacts (
build/,dist/,.spec) from the repository.
- Used
- Build Script:
chmod +x build.sh ./build.sh - Troubleshooting: Fixed a runtime
ModuleNotFoundError: No module named 'mlx._reprlib_fix'by explicitly adding--collect-all mlxand--hidden-import=mlx._reprlib_fixto the PyInstaller configuration. Also addedmultiprocessing.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_workerthread 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.
- Implemented a background
Phase 10: CLI Configuration & Documentation
- Goal: Make the tool fully configurable without editing the code.
- Approach:
- Integrated
argparseto allow dynamic selection of translation languages (-es,-en,-ar,-fr), data ingestion (-i), and audio device (-d). - Added device listing capability (
-l). - Excluded the
venvdirectory from git and generatedrequirements.txt. - Created a comprehensive
README.mdcontaining setup instructions, usage examples, and a technical note on universal models (like NLLB-200).
- Integrated
- Fix: Added missing
sentencepiecedependency 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.
- Streaming Mode (
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 aninitial_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.
- Prompt Caching (
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
--channelsand--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--langparameter.
- Stereo Channel Selection: Added
- Outcome: Enabled the ability to "focus" the transcription engine on a single speaker even when the audio input is a complex mix.
Phase 15: Potential Future Optimizations (Backlog)
- Decoding Parameters:
- Temperature Fallback: Use a tuple
(0.0, 0.2, 0.4, 0.6, 0.8, 1.0)to allow Whisper to re-try failed transcriptions with higher randomness (crucial for music/singing). - Beam Size Tuning: Set
beam_size=1for Draft Mode (3x faster) andbeam_size=5for final segments (higher accuracy). - Token Suppression: Native suppression of music symbols or common hallucination tokens at the decoder level.
- Heuristic Thresholds: Utilize
logprob_threshold(confidence) andcompression_ratio_threshold(repetition) to programmatically identify and discard bad transcriptions before they reach the user.
- Temperature Fallback: Use a tuple
- Structural Features:
- Word-Level Timestamps: Enable
word_timestamps=Trueto provide granular timing data for front-end caption highlighting.
- Word-Level Timestamps: Enable
Phase 16: Decoder Controls + Bridge Context Refinements
- Goal: Improve English caption stability and translation quality without sacrificing runtime compatibility.
- Approach:
- Whisper Decoder Controls (implemented): Added configurable decoding flags for final segments and drafts, including temperature fallback and quality thresholds (
logprob_threshold,compression_ratio_threshold), with compatibility fallback when unsupported. - Beam Compatibility Hardening: Defaulted beam sizes to greedy-safe values and auto-retry without
beam_sizewhen the runtime reports "Beam search decoder is not yet implemented." - Dedicated English Bridge Context: Split context handling into source-language context and a separate English context used specifically for
task="translate"bridge generation. - Sentence-Aware MT Chunking: Replaced hard truncation with sentence-aware splitting for long English bridge text before translation.
- Marian Generation Tuning: Added configurable generation controls (
num_beams,no_repeat_ngram_size,length_penalty,repetition_penalty,early_stopping) to reduce repetitive or unstable outputs.
- Whisper Decoder Controls (implemented): Added configurable decoding flags for final segments and drafts, including temperature fallback and quality thresholds (
- Outcome: Better continuity for non-English to English bridging, fewer clipped translations on long segments, and cleaner target-language output with safer defaults for current
mlx_whisperbuilds.
Phase 17: Intelligent English Caption Correction (V1)
- Goal: Improve English caption readability and resilience without introducing heavy latency.
- Approach:
- Confidence-Triggered Re-Decode: Added optional retry logic that re-transcribes low-confidence segments when Whisper quality signals indicate likely errors (
avg_logprob/compression_ratiothresholds). - Glossary Replacements: Added configurable
SOURCE=TARGETterm normalization (--glossary-pair) to consistently correct names/terms in English captions. - Rolling Caption Revision Buffer: Added a short correction window (
--caption-correction-window, default 3s) with a 2-line rolling buffer that can merge incomplete English lines into a cleaner sentence as new context arrives.
- Confidence-Triggered Re-Decode: Added optional retry logic that re-transcribes low-confidence segments when Whisper quality signals indicate likely errors (
- Outcome: Captions now support lightweight post-correction behavior that improves continuity and term consistency while remaining compatible with real-time streaming.
Phase 18: File-Based Glossary Loading
- Goal: Make glossary corrections persistent and easier to maintain without long CLI commands.
- Approach:
- Added
--glossary-file(defaultglossary.txt) to loadSOURCE=TARGETterm mappings when the file exists. - Implemented comment/blank-line support and invalid-line skipping with line-level warnings.
- Merged file-based glossary entries with repeatable
--glossary-pairCLI entries for runtime overrides.
- Added
- Outcome: English caption correction can now use a maintained glossary file automatically, while preserving ad-hoc CLI term fixes.
Phase 19: Arabic Terminal Rendering Hardening
- Goal: Improve readability of Arabic captions in terminal environments with mixed LTR/RTL output.
- Approach:
- Switched Arabic output to a strict two-line format (
[AR]:label line + dedicated RTL content line). - Wrapped Arabic caption content in explicit RTL embedding marks for better bidirectional layout stability.
- Added punctuation normalization for Arabic display (
،,؛,؟) to reduce LTR punctuation artifacts.
- Switched Arabic output to a strict two-line format (
- Outcome: Arabic captions render more consistently in terminal output, especially when adjacent to English/French/Spanish caption lines.
Phase 20: Post-Correct Mode (V2 Foundation)
- Goal: Add a practical second-stage correction pass for finalized English captions.
- Approach:
- Added
--post-correctmode to run deterministic rule-based cleanup after ASR (spacing, punctuation cleanup, disfluency reduction, immediate duplicate-word collapse). - Added optional local LLM correction via Ollama (
--post-correct-llm, model/url/timeout flags) with strict fallback to rules-only when unavailable. - Integrated post-correction after bridge-to-English and before downstream translation so improved English text propagates to target-language MT.
- Added
- Outcome: The pipeline now supports a two-stage transcription flow (ASR -> correction) with low-latency defaults and optional local generative enhancement.
Phase 21: LLM Merge-Decider for Line Revisions
- Goal: Improve English line-merge quality when smart-correct revision is ambiguous.
- Approach:
- Added optional
--llm-merge-deciderto validate/refine heuristic merge candidates using the local post-correct LLM. - Enforced strict low-latency behavior with
--llm-merge-timeout(default 0.7s) and automatic fallback to heuristic merges on errors/timeouts. - Added safety guards to reject overly large LLM rewrites and preserve buffered caption state when merge is rejected.
- Added optional
- Outcome: Merge decisions remain fast and deterministic by default, with optional LLM arbitration for cleaner sentence continuity.
Phase 22: Optional Speaker-Change Line Cutting
- Goal: Split long live captions earlier when a different person starts speaking.
- Approach:
- Added optional
--speaker-change-detectmode with cosine-similarity comparison of lightweight voice signatures from buffered audio. - Introduced tuning flags for sensitivity and runtime cost (
--speaker-sim-threshold,--speaker-min-buffer,--speaker-check-interval). - When a probable speaker switch is detected, the current buffered caption line is force-flushed early instead of waiting only for silence.
- Added speaker-state resets on watchdog/hallucination/silence reset paths to avoid stale identity drift.
- Added optional
- Outcome: Users can opt into faster caption segmentation at speaker boundaries while keeping default behavior unchanged.
Phase 23: Prompt Hardening + Anti-Drift Guardrails
- Goal: Reduce semantic drift in LLM post-correction and merge decisions.
- Approach:
- Rewrote post-correction prompt with strict "current line only" and "if uncertain, keep original" constraints.
- Expanded post-correction context to up to two previous lines but marked as reference-only.
- Added token-overlap acceptance guard (
--post-correct-min-overlap) to reject LLM rewrites that diverge too far from source text. - Updated merge-decider prompt to stricter JSON behavior and added overlap-based fallback to heuristic merge.
- Outcome: Improved protection against context bleed (e.g., replacing current line with prior sentence) while keeping optional LLM improvements.
Phase 24: Ollama Warmup + Keep-Alive
- Goal: Reduce intermittent post-correction timeouts caused by cold model loads.
- Approach:
- Added startup warmup request for the local post-correct model when LLM correction is enabled.
- Added configurable Ollama
keep_alivesetting so the model stays resident between caption calls. - Routed post-correct and merge-decider requests through a shared Ollama caller with keep-alive support.
- Added flags for warmup timeout and optional warmup skip for troubleshooting.
- Outcome: Lower first-request latency and fewer fallback-to-rules events due to local model cold starts.
Phase 25: Session Logging + Tuned Live Preset
- Goal: Preserve reliable debugging context while locking in the best-performing live settings discovered through manual testing.
- Approach:
- Added session file logging hooks so finalized captions, revisions, startup state, and runtime errors can be written to a dedicated log via
--session-log-file. - Improved interactive terminal behavior by clearing stale draft lines cleanly and right-aligning Arabic output more consistently against terminal width.
- Recorded the current preferred live preset for English-first filtered transcription with multilingual output and local LLM post-correction:
python3 transcribe.py --silence 100 -q -s -c -es -en -fr -ar --lang en --filter-lang --mt-max-chars 450 --mt-max-new-tokens 220 --smart-correct --post-correct --post-correct-llm --post-correct-model qwen2.5:3b-instruct --session-log-file debug_session_1.log - Added session file logging hooks so finalized captions, revisions, startup state, and runtime errors can be written to a dedicated log via
- Outcome: The project now has a repeatable "known good" runtime profile plus persistent session diagnostics for reviewing caption issues after a run.