Files
mytoolkit/bin/api/voice.py
T

95 lines
2.7 KiB
Python

"""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())