[Wake/VAD/STT 与 Live runtime]:完成真实重复语音运行,包含云端ASR/TTS开关、run-live和临时上下文测试
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import json
|
||||
import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import tempfile
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .config import AppConfig
|
||||
from .models import AudioSegment, ErrorCode, ProviderError
|
||||
|
||||
|
||||
@@ -121,3 +128,82 @@ class MacSayTtsProvider:
|
||||
"tts",
|
||||
)
|
||||
return AudioSegment(data, 16000, 1, 0, max(120, len(clean) * 45), {"text": clean, "format": "aiff"})
|
||||
|
||||
|
||||
class CloudTtsProvider:
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig,
|
||||
timeout_s: float = 60.0,
|
||||
urlopen: Callable[..., Any] | None = None,
|
||||
) -> None:
|
||||
self.config = config
|
||||
self.timeout_s = timeout_s
|
||||
self.urlopen = urlopen or urllib.request.urlopen
|
||||
self.loaded = False
|
||||
|
||||
def load(self) -> None:
|
||||
self.config.require_llm_credentials()
|
||||
self.loaded = True
|
||||
|
||||
def synthesize(self, text: str) -> AudioSegment:
|
||||
if not self.loaded:
|
||||
raise ProviderError(
|
||||
ErrorCode.TTS_SYNTHESIS_FAILED,
|
||||
"cloud TTS provider is not loaded",
|
||||
False,
|
||||
"newapi-tts",
|
||||
"tts",
|
||||
)
|
||||
clean = text.strip()
|
||||
if not clean:
|
||||
raise ProviderError(
|
||||
ErrorCode.TTS_EMPTY_AUDIO,
|
||||
"cannot synthesize empty text",
|
||||
True,
|
||||
"newapi-tts",
|
||||
"tts",
|
||||
)
|
||||
payload = {
|
||||
"model": self.config.tts_model,
|
||||
"input": clean,
|
||||
"voice": self.config.tts_voice,
|
||||
"response_format": "mp3",
|
||||
}
|
||||
request = urllib.request.Request(
|
||||
self.config.api_url("/v1/audio/speech"),
|
||||
data=json.dumps(payload).encode("utf-8"),
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.config.llm_api_key}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with self.urlopen(request, timeout=self.timeout_s) as response:
|
||||
data = response.read()
|
||||
except urllib.error.HTTPError as exc:
|
||||
raise ProviderError(
|
||||
ErrorCode.TTS_SYNTHESIS_FAILED,
|
||||
f"cloud TTS HTTP error {exc.code}",
|
||||
exc.code >= 500,
|
||||
"newapi-tts",
|
||||
"tts",
|
||||
) from exc
|
||||
except (urllib.error.URLError, TimeoutError, socket.timeout) as exc:
|
||||
raise ProviderError(
|
||||
ErrorCode.TTS_SYNTHESIS_FAILED,
|
||||
f"cloud TTS request failed: {exc}",
|
||||
True,
|
||||
"newapi-tts",
|
||||
"tts",
|
||||
) from exc
|
||||
if not data:
|
||||
raise ProviderError(
|
||||
ErrorCode.TTS_EMPTY_AUDIO,
|
||||
"cloud TTS returned empty audio",
|
||||
True,
|
||||
"newapi-tts",
|
||||
"tts",
|
||||
)
|
||||
return AudioSegment(data, 16000, 1, 0, max(120, len(clean) * 45), {"text": clean, "format": "mp3"})
|
||||
|
||||
Reference in New Issue
Block a user