"""语音播报工具。""" from __future__ import annotations import json from dataclasses import dataclass from typing import Any from urllib.error import HTTPError, URLError from urllib.request import Request, urlopen from django.conf import settings @dataclass(frozen=True) class SpeakResponse: status_code: int raw: Any def play_speech( *, text: str, endpoint: str | None = None, timeout_seconds: float = 10.0, ) -> SpeakResponse: """调用语音播报服务。 默认 endpoint 读取 settings.SPEAK_ENDPOINT。 请求格式:POST JSON {'text': ''} """ content = (text or '').strip() if not content: raise ValueError('text 不能为空') url = (endpoint or getattr(settings, 'SPEAK_ENDPOINT', '') or '').strip() if not url: raise ValueError('SPEAK_ENDPOINT 未配置') payload = {'text': content} data = json.dumps(payload, ensure_ascii=False).encode('utf-8') req = Request( url=url, data=data, headers={'Content-Type': 'application/json'}, method='POST', ) try: with urlopen(req, timeout=float(timeout_seconds)) as resp: body = resp.read().decode('utf-8', errors='replace') status_code = int(getattr(resp, 'status', 200) or 200) except HTTPError as exc: body = '' try: body = exc.read().decode('utf-8', errors='replace') except Exception: pass raise RuntimeError(f'Speak service HTTPError: status={exc.code}, body={body}') from exc except URLError as exc: raise RuntimeError(f'Speak service URLError: {exc}') from exc try: raw = json.loads(body) if body else {} except Exception: raw = {'raw_text': body} return SpeakResponse(status_code=status_code, raw=raw)