Kazeia-central/kazeia_central/voice/worker.py

70 lines
2.5 KiB
Python

"""Worker d'enrôlement vocal — process persistant sous cv_venv.
Lancé par `voice/bridge.py` (côté API py3.14) via :
cv_venv/bin/python -m kazeia_central.voice.worker
Garde le teacher CosyVoice (et le modèle Whisper) **chauds**, et sert une file de
jobs via un protocole JSON-lines sur stdin/stdout.
Canal protocole propre : la stack torch/librosa/whisper écrit du bruit sur stdout.
On capture le **vrai** stdout (fd 1) pour le protocole AVANT de rediriger fd 1 vers
stderr — ainsi tout `print()`/sortie C de lib part sur stderr (console), et seul le
JSON du protocole circule sur le pipe lu par le bridge. Sans ça, l'IPC serait corrompu.
Protocole :
requête (1 ligne) : {"id": <n>, "method": "ping|transcribe|enroll|shutdown", "params": {...}}
réponse (1 ligne) : {"id": <n>, "ok": true, "result": {...}} | {"id": <n>, "ok": false, "error": "..."}
au démarrage : {"event": "ready"}
"""
from __future__ import annotations
import json
import os
import sys
# --- canal protocole propre (avant tout import lourd) ---------------------
_proto = os.fdopen(os.dup(1), "w", buffering=1) # vrai stdout (pipe vers le bridge)
os.dup2(2, 1) # fd 1 → stderr : bruit des libs hors protocole
def _send(obj: dict) -> None:
_proto.write(json.dumps(obj, ensure_ascii=False) + "\n")
_proto.flush()
def _handle(method: str, params: dict):
if method == "ping":
return {"pong": True, "pid": os.getpid()}
if method == "transcribe":
from .transcribe import transcribe
return transcribe(params["wav"], params.get("model"))
if method == "enroll":
from .enroll import enroll
return enroll(params["wav"], params["text"], params["out"])
raise ValueError(f"méthode inconnue: {method}")
def main() -> None:
_send({"event": "ready"})
for line in sys.stdin:
line = line.strip()
if not line:
continue
try:
req = json.loads(line)
except Exception:
continue
rid = req.get("id")
method = req.get("method")
if method == "shutdown":
_send({"id": rid, "ok": True, "result": {"bye": True}})
break
try:
_send({"id": rid, "ok": True, "result": _handle(method, req.get("params") or {})})
except Exception as e: # un job qui échoue ne tue pas le worker
_send({"id": rid, "ok": False, "error": f"{type(e).__name__}: {e}"})
if __name__ == "__main__":
main()