"""Voice / TTS API client via Volcengine (ByteDance) WebSocket API.""" import asyncio import json import uuid from pathlib import Path _RESOURCE_ID = "volc.service_type.10029" _ENDPOINT = "wss://openspeech.bytedance.com/api/v3/tts/unidirectional/stream" _VOICE_CHOICES = [ "zh_female_cancan_mars_bigtts", "zh_female_shuangkuaisisi_moon_bigtts", "zh_male_wennuanahu_moon_bigtts", "zh_male_sunwukong_moon_bigtts", ] _FORMAT_CHOICES = ["mp3", "wav", "pcm"] def synthesize_tts( appid: str, token: str, text: str, speaker: str = "zh_male_wennuanahu_moon_bigtts", fmt: str = "mp3", speech_rate: int = 0, output_path: str = "output.mp3", ) -> str: """Synchronous TTS synthesis via Volcengine WebSocket API. Returns absolute path to the output audio file. """ # Delayed import to avoid circular dependency when bin.commands is not yet loaded from bin.commands.protocols import EventType, MsgType, full_client_request, receive_message try: import websockets except ImportError as e: raise RuntimeError(f"Missing dependency: {e}. Run: pip install websockets") from e headers = { "X-Api-App-Id": appid, "X-Api-Access-Key": token, "X-Api-Resource-Id": _RESOURCE_ID, "X-Api-Connect-Id": str(uuid.uuid4()), } async def _run() -> bytearray: ws = await websockets.connect( _ENDPOINT, additional_headers=headers, max_size=10 * 1024 * 1024, open_timeout=30, ) request = { "user": {"uid": str(uuid.uuid4())}, "req_params": { "speaker": speaker, "audio_params": { "format": fmt, "sample_rate": 24000, "speech_rate": speech_rate, }, "text": text, }, } await full_client_request(ws, json.dumps(request).encode()) audio_data = bytearray() while True: msg = await receive_message(ws) if msg.type == MsgType.FullServerResponse and msg.event == EventType.SessionFinished: break elif msg.type == MsgType.AudioOnlyServer: audio_data.extend(msg.payload) elif msg.type == MsgType.Error: await ws.close() raise RuntimeError( f"TTS service error: {msg.payload.decode('utf-8', errors='replace')}" ) await ws.close() return audio_data audio_data = asyncio.run(_run()) if not audio_data: raise RuntimeError("No audio data received from TTS service") out = Path(output_path) out.write_bytes(audio_data) return str(out.resolve())