Spaces:
Runtime error
Runtime error
File size: 6,322 Bytes
a82806d 1354c32 a82806d 1354c32 a82806d 1354c32 ec8adb1 1354c32 a82806d 1354c32 ec8adb1 a82806d ec8adb1 a82806d ec8adb1 a82806d 1354c32 a82806d 1354c32 a82806d 1354c32 a82806d 377f697 a82806d 1354c32 a82806d 1354c32 a82806d 1354c32 a82806d 1354c32 a82806d 1354c32 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | """Speech-to-text utilities with graceful fallbacks and dynamic stage switching."""
from __future__ import annotations
import io
import os
import wave
from threading import Lock
from typing import Any, Literal, Optional
import numpy as np
try:
from openai import OpenAI
except ModuleNotFoundError:
OpenAI = None # type: ignore[assignment]
from backend.utils import device
import nemo.collections.asr as nemo_asr
try:
import torch
from transformers import pipeline
except ModuleNotFoundError: # PyTorch or transformers not available on Python 3.13 wheels
torch = None # type: ignore
pipeline = None # type: ignore
try:
from google.cloud import speech
except ModuleNotFoundError:
speech = None # type: ignore
_ASR_PIPELINE = None
_ASR_STAGE: Literal["typhoon", "gpt"] = "typhoon"
_ASR_STAGE_LOCK: Lock = Lock()
def _huggingface_device() -> int | str | None:
if device == "cuda":
return 0
if device == "mps":
return "mps"
return "cpu"
def _initialize_typhoon_pipeline():
if torch is None or pipeline is None:
return None
print(f"Using device: {device}")
print("Initializing Typhoon ASR pipeline...")
asr_model = nemo_asr.models.ASRModel.from_pretrained(
model_name="scb10x/typhoon-asr-realtime",
map_location=device,
)
print("Typhoon ASR pipeline initialized.")
return asr_model
def _initialize_gpt_client() -> Optional[Any]:
if OpenAI is None:
print("openai package not available; GPT ASR unavailable.")
return None
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
print("OPENAI_API_KEY not found; GPT ASR unavailable.")
return None
try:
return OpenAI(api_key=api_key)
except Exception as exc:
print(f"Failed to initialise GPT ASR client: {exc}")
return None
_GPT_ASR_MODEL = os.getenv("GPT_ASR_MODEL", "gpt-4o-mini-transcribe")
_GPT_CLIENT = _initialize_gpt_client()
def set_asr_stage(stage: str) -> None:
"""Update the active ASR stage."""
normalized_stage = stage.lower()
if normalized_stage not in {"typhoon", "gpt"}:
raise ValueError(f"Unsupported ASR stage '{stage}'")
global _ASR_STAGE
with _ASR_STAGE_LOCK:
if _ASR_STAGE != normalized_stage:
print(f"Switching ASR stage to: {normalized_stage}")
_ASR_STAGE = normalized_stage # type: ignore[assignment]
def get_asr_stage() -> Literal["typhoon", "gpt"]:
"""Return the current ASR stage."""
with _ASR_STAGE_LOCK:
return _ASR_STAGE
def _transcribe_with_pipeline(audio_array: np.ndarray) -> str:
output = _ASR_PIPELINE(audio_array) # type: ignore[operator]
if isinstance(output, dict):
text = output.get("text", "")
else:
text = str(output)
return text.replace("ทางลัด", "ทางรัฐ")
def _transcribe_with_typhoon(audio_array: np.ndarray) -> str:
if _ASR_TYPHOON is None:
raise RuntimeError("Typhoon ASR is unavailable")
result = _ASR_TYPHOON.transcribe(audio=audio_array)
if isinstance(result, list):
transcription = " ".join(result)
else:
transcription = str(result)
return transcription.strip()
def _transcribe_with_google(audio_array: np.ndarray) -> str:
if speech is None:
raise RuntimeError("google-cloud-speech is not available")
int16_audio = (audio_array * 32767.0).astype(np.int16)
audio_bytes = int16_audio.tobytes()
client = speech.SpeechClient()
audio_config = speech.RecognitionConfig(
encoding=speech.RecognitionConfig.AudioEncoding.LINEAR16,
sample_rate_hertz=16000,
language_code="th-TH",
alternative_language_codes=["en-US"],
model="telephony",
)
audio_data = speech.RecognitionAudio(content=audio_bytes)
response = client.recognize(config=audio_config, audio=audio_data)
transcription = " ".join(
result.alternatives[0].transcript for result in response.results
)
return transcription
def _transcribe_with_gpt(audio_array: np.ndarray) -> str:
if _GPT_CLIENT is None:
raise RuntimeError("GPT ASR client is unavailable")
normalized = np.asarray(audio_array, dtype=np.float32)
if normalized.ndim > 1:
normalized = normalized.squeeze()
normalized = np.clip(normalized, -1.0, 1.0)
int16_audio = (normalized * 32767.0).astype(np.int16)
buffer = io.BytesIO()
with wave.open(buffer, "wb") as wav_file:
wav_file.setnchannels(1)
wav_file.setsampwidth(2)
wav_file.setframerate(16000)
wav_file.writeframes(int16_audio.tobytes())
buffer.seek(0)
response = _GPT_CLIENT.audio.transcriptions.create(
model=_GPT_ASR_MODEL,
file=("audio.wav", buffer.read(), "audio/wav"),
)
text = getattr(response, "text", "")
if not text and isinstance(response, dict):
text = response.get("text", "")
return text.strip()
_ASR_TYPHOON = _initialize_typhoon_pipeline()
def transcribe_typhoon(path: str) -> str:
text = _ASR_TYPHOON.transcribe(path)
if text[0].text:
return text[0].text
else :
print(text)
return ""
def transcribe_audio(audio_array: np.ndarray) -> str:
"""Transcribe user audio with the best available backend based on the current stage."""
if audio_array is None or not np.any(audio_array):
return ""
stage = get_asr_stage()
if stage == "gpt":
try:
transcription = _transcribe_with_gpt(audio_array)
if transcription:
return transcription.replace("ทางลัด", "ทางรัฐ")
except Exception as exc:
print(f"GPT ASR failed: {exc}; falling back to Typhoon.")
if _ASR_TYPHOON is not None:
try:
transcription = _transcribe_with_typhoon(audio_array)
if transcription:
return transcription.replace("ทางลัด", "ทางรัฐ")
except Exception as exc:
print(f"Typhoon ASR pipeline failed: {exc}")
try:
return _transcribe_with_google(audio_array).replace("ทางลัด", "ทางรัฐ")
except Exception as exc:
print(f"ASR fallback failed: {exc}")
return ""
|