"""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": , "method": "ping|transcribe|enroll|shutdown", "params": {...}} réponse (1 ligne) : {"id": , "ok": true, "result": {...}} | {"id": , "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()