mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-25 08:33:53 +08:00
feat: add FastAPI manager API compatibility baseline
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class VoicePrintIntegrationError(RuntimeError):
|
||||
def __init__(self, code: int, message: str | None = None, params: tuple[object, ...] = ()):
|
||||
super().__init__(message or str(code))
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.params = params
|
||||
|
||||
|
||||
@dataclass(slots=True, frozen=True)
|
||||
class VoicePrintEndpoint:
|
||||
base_url: str
|
||||
authorization: str
|
||||
|
||||
@classmethod
|
||||
def parse(cls, configured_url: str | None) -> VoicePrintEndpoint:
|
||||
if configured_url is None:
|
||||
raise VoicePrintIntegrationError(10084)
|
||||
parsed = urlsplit(configured_url)
|
||||
if not parsed.scheme or not parsed.hostname:
|
||||
raise VoicePrintIntegrationError(10084)
|
||||
marker = "key="
|
||||
marker_index = parsed.query.find(marker)
|
||||
key = parsed.query[marker_index + len(marker) :] if marker_index >= 0 else parsed.query[3:]
|
||||
port = f":{parsed.port}" if parsed.port is not None else ""
|
||||
return cls(f"{parsed.scheme}://{parsed.hostname}{port}", f"Bearer {key}")
|
||||
|
||||
|
||||
class VoicePrintClient:
|
||||
def __init__(self, configured_url: str, *, timeout: float = 10.0, client: httpx.AsyncClient | None = None):
|
||||
self.endpoint = VoicePrintEndpoint.parse(configured_url)
|
||||
self.timeout = timeout
|
||||
self._client = client
|
||||
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
*,
|
||||
data: Mapping[str, str] | None = None,
|
||||
files: Mapping[str, tuple[str, bytes, str]] | None = None,
|
||||
) -> httpx.Response:
|
||||
headers = {"Authorization": self.endpoint.authorization}
|
||||
if self._client is not None:
|
||||
return await self._client.request(
|
||||
method, f"{self.endpoint.base_url}{path}", headers=headers, data=data, files=files
|
||||
)
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
return await client.request(
|
||||
method, f"{self.endpoint.base_url}{path}", headers=headers, data=data, files=files
|
||||
)
|
||||
|
||||
async def identify(self, speaker_ids: list[str], audio: bytes) -> tuple[str | None, float | None] | None:
|
||||
if not speaker_ids:
|
||||
return None
|
||||
response = await self._request(
|
||||
"POST",
|
||||
"/voiceprint/identify",
|
||||
data={"speaker_ids": ",".join(speaker_ids)},
|
||||
files={"file": ("VoicePrint.WAV", audio, "application/octet-stream")},
|
||||
)
|
||||
if response.status_code != 200:
|
||||
raise VoicePrintIntegrationError(10091)
|
||||
try:
|
||||
payload = response.json()
|
||||
except ValueError as exc:
|
||||
raise VoicePrintIntegrationError(10091) from exc
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
speaker_id = payload.get("speaker_id")
|
||||
score = payload.get("score")
|
||||
return (
|
||||
str(speaker_id) if speaker_id is not None else None,
|
||||
float(score) if isinstance(score, int | float) else None,
|
||||
)
|
||||
|
||||
async def register(self, speaker_id: str, audio: bytes) -> None:
|
||||
response = await self._request(
|
||||
"POST",
|
||||
"/voiceprint/register",
|
||||
data={"speaker_id": speaker_id},
|
||||
files={"file": ("VoicePrint.WAV", audio, "application/octet-stream")},
|
||||
)
|
||||
if response.status_code != 200:
|
||||
raise VoicePrintIntegrationError(10087)
|
||||
if "true" not in response.text:
|
||||
raise VoicePrintIntegrationError(10088)
|
||||
|
||||
async def cancel(self, speaker_id: str) -> None:
|
||||
response = await self._request("DELETE", f"/voiceprint/{speaker_id}")
|
||||
if response.status_code != 200:
|
||||
raise VoicePrintIntegrationError(10089)
|
||||
if "true" not in response.text:
|
||||
raise VoicePrintIntegrationError(10090)
|
||||
Reference in New Issue
Block a user