Spaces:
Sleeping
Sleeping
| # File: src/speech.py | |
| # Purpose: Speech-to-Text via Groq Whisper and Text-to-Speech via ElevenLabs | |
| import tempfile | |
| from pathlib import Path | |
| import httpx | |
| from groq import Groq | |
| from config import ( | |
| GROQ_API_KEY, GROQ_STT_MODEL, | |
| ELEVENLABS_API_KEY, ELEVENLABS_VOICE_ID, ELEVENLABS_MODEL_ID, | |
| ) | |
| _groq = Groq(api_key=GROQ_API_KEY) | |
| # ββ Speech-to-Text (Groq Whisper) βββββββββββββββββββββββββββββββββββββββββββββ | |
| def transcribe_audio(audio_bytes: bytes, content_type: str = "audio/wav") -> str: | |
| """Transcribe audio bytes using Groq Whisper. Returns transcript string.""" | |
| suffix_map = { | |
| "audio/wav": ".wav", | |
| "audio/mpeg": ".mp3", | |
| "audio/webm": ".webm", | |
| "audio/ogg": ".ogg", | |
| } | |
| suffix = suffix_map.get(content_type, ".wav") | |
| with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp: | |
| tmp.write(audio_bytes) | |
| tmp_path = tmp.name | |
| try: | |
| with open(tmp_path, "rb") as f: | |
| transcript = _groq.audio.transcriptions.create( | |
| model=GROQ_STT_MODEL, | |
| file=(Path(tmp_path).name, f, content_type), | |
| language="en", | |
| ) | |
| return transcript.text.strip() | |
| finally: | |
| Path(tmp_path).unlink(missing_ok=True) | |
| # ββ Text-to-Speech (ElevenLabs) βββββββββββββββββββββββββββββββββββββββββββββββ | |
| ELEVENLABS_TTS_URL = f"https://api.elevenlabs.io/v1/text-to-speech/{ELEVENLABS_VOICE_ID}" | |
| def synthesize_speech(text: str) -> bytes: | |
| """Convert text to speech using ElevenLabs. Returns raw MP3 bytes.""" | |
| headers = { | |
| "xi-api-key": ELEVENLABS_API_KEY, | |
| "Content-Type": "application/json", | |
| "Accept": "audio/mpeg", | |
| } | |
| payload = { | |
| "text": text, | |
| "model_id": ELEVENLABS_MODEL_ID, | |
| "voice_settings": { | |
| "stability": 0.55, | |
| "similarity_boost": 0.75, | |
| "style": 0.0, | |
| "use_speaker_boost": True, | |
| }, | |
| } | |
| response = httpx.post(ELEVENLABS_TTS_URL, headers=headers, json=payload, timeout=30) | |
| response.raise_for_status() | |
| return response.content |