WillHbx's picture
Add STT fixes, SDXL-Lightning backend, voice UX and rembg sprite transparency
407855c
Raw
History Blame Contribute Delete
3.3 kB
"""The Ear — speech to text.
Player utterances are short, so Whisper `small`/`base` transcribes near-instantly even on CPU.
On Apple Silicon, `mlx-whisper` or `whisper.cpp` use the GPU; on AMD, `whisper.cpp`
(Vulkan/ROCm) or CPU. `faster-whisper` is the simplest default and is fine on CPU for short clips.
"""
from __future__ import annotations
from typing import Protocol
from . import config
class STTBackend(Protocol):
def transcribe(self, audio_path: str) -> str: ...
class MockSTT:
def transcribe(self, audio_path: str) -> str:
return "" # the UI falls back to whatever was typed
class WhisperSTT:
def __init__(self) -> None:
from faster_whisper import WhisperModel # noqa: PLC0415
device = config.detect_device()
# CTranslate2 runs on CUDA or CPU (no Metal/ROCm) -> use CPU off NVIDIA.
ct2_device = "cuda" if device == "cuda" else "cpu"
self.model = WhisperModel(config.WHISPER_SIZE, device=ct2_device, compute_type="auto")
@staticmethod
def _ensure_extension(audio_path: str) -> tuple[str, bool]:
"""Return (path, should_delete). Copies extensionless blobs to .webm so PyAV can probe format."""
import pathlib
import shutil
import tempfile
if pathlib.Path(audio_path).suffix.lower():
return audio_path, False
# Gradio saves browser recordings as bare 'blob' files — give it the right extension.
tmp = tempfile.mktemp(suffix=".webm")
shutil.copy2(audio_path, tmp)
return tmp, True
def transcribe(self, audio_path: str) -> str:
import os
path, temp = self._ensure_extension(audio_path)
try:
segments, _ = self.model.transcribe(
path,
beam_size=1,
no_speech_threshold=0.6,
condition_on_previous_text=False,
)
return " ".join(s.text for s in segments if s.no_speech_prob <= 0.6).strip()
finally:
if temp:
os.unlink(path)
def get_stt() -> STTBackend:
if config.USE_MOCK and not config.REAL_STT:
return MockSTT()
return WhisperSTT()
# Debug: record from microphone and transcribe
if __name__ == "__main__":
import sys
import tempfile
import wave
import numpy as np
RATE = 16_000
SECONDS = int(sys.argv[1]) if len(sys.argv) > 1 else 5
try:
import sounddevice as sd
print(f"Recording {SECONDS}s … speak now!")
audio = sd.rec(RATE * SECONDS, samplerate=RATE, channels=1, dtype="float32")
sd.wait()
audio = audio[:, 0]
print("Done recording.")
except ImportError:
print("sounddevice not installed — using 5 s of silence as fallback.")
print("Install with: uv add sounddevice")
audio = np.zeros(RATE * SECONDS, dtype=np.float32)
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
tmp_path = f.name
pcm = (audio * 32767).astype(np.int16)
with wave.open(tmp_path, "wb") as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(RATE)
wf.writeframes(pcm.tobytes())
stt = WhisperSTT()
result = stt.transcribe(tmp_path)
print(f"Transcription: {result!r}")