the-brain / python-services /stt_client.py
voice-rag's picture
try to add neon db
e3d8805
Raw
History Blame Contribute Delete
4.4 kB
# stt_client.py
# Upstream WebSocket client to STT Space
import os
import json
import logging
import asyncio
import websockets
from typing import Optional
logger = logging.getLogger("stt_client")
class SttClient:
def __init__(self, ws_url: Optional[str] = None):
self.ws_url = ws_url or os.getenv("STT_SPACE_WS_URL", "wss://abid-official-stt.hf.space/ws/stt")
self.ws = None
self.connected = False
self.send_queue = asyncio.Queue()
self.receive_queue = asyncio.Queue()
self._running = False
self._tasks = []
self._run_task = None
def start(self):
"""Starts the upstream connection management task."""
if self._running or (self._run_task and not self._run_task.done()):
logger.info("STT client is already running or start task is active.")
return self
self._running = True
self._run_task = asyncio.create_task(self.connect_and_run())
return self
async def connect_and_run(self):
"""Main connection loop managing auto-reconnect backoff and read/write loops."""
backoff = 1.0
while self._running:
try:
logger.info(f"Connecting to upstream STT Space: {self.ws_url}")
async with websockets.connect(self.ws_url) as ws:
self.ws = ws
self.connected = True
backoff = 1.0
logger.info("Connected to upstream STT Space ✓")
# Spawn read and write loop tasks
read_task = asyncio.create_task(self._read_loop())
write_task = asyncio.create_task(self._write_loop())
self._tasks = [read_task, write_task]
await asyncio.gather(read_task, write_task)
except asyncio.CancelledError:
logger.info("STT client run task cancelled.")
break
except Exception as e:
logger.error(f"STT upstream connection error: {e}")
self.connected = False
for t in self._tasks:
if not t.done():
t.cancel()
self._tasks = []
self.ws = None
# Exponential backoff
await asyncio.sleep(backoff)
backoff = min(backoff * 2, 30.0)
self.connected = False
async def _write_loop(self):
try:
while self._running:
data = await self.send_queue.get()
if self.ws and self.connected:
try:
await self.ws.send(data)
except Exception as e:
logger.error(f"STT client write error: {e}")
break
self.send_queue.task_done()
except asyncio.CancelledError:
pass
async def _read_loop(self):
try:
async for message in self.ws:
try:
event = json.loads(message)
await self.receive_queue.put(event)
except Exception as e:
logger.warning(f"Failed to parse STT message: {e} | Raw: {message[:120]}")
except asyncio.CancelledError:
pass
except Exception as e:
logger.error(f"Upstream STT read loop error: {e}")
self.connected = False
def send_pcm(self, pcm_bytes: bytes):
"""Queue raw PCM bytes to send to STT Space."""
if self._running:
self.send_queue.put_nowait(pcm_bytes)
async def get_event(self) -> dict:
"""Fetch next transcription event from queue."""
return await self.receive_queue.get()
async def close(self):
"""Closes all queues, loops, and websocket connections."""
self._running = False
self.connected = False
if self._run_task and not self._run_task.done():
self._run_task.cancel()
for t in self._tasks:
if not t.done():
t.cancel()
self._tasks = []
if self.ws:
try:
await self.ws.close()
except Exception:
pass
self.ws = None
logger.info("STT client connections closed.")