From 55ccef1340451d10a6493f485edadcf8c42a75c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 15:37:23 +0000 Subject: [PATCH] =?UTF-8?q?Fiabilit=C3=A9=20:=20la=20diarisation=20ne=20pe?= =?UTF-8?q?ut=20plus=20bloquer=20ni=20casser=20la=20transcription?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Timeout de 5 s sur l'identification du locuteur (asyncio.wait_for) : si le modèle ECAPA tarde ou se bloque sur CPU, on ignore le locuteur mais le texte est transcrit et affiché quand même - AGC du segmenteur protégé : une trame problématique renvoie l'audio brut au lieu d'interrompre le découpage Testé : texte transcrit malgré un diariseur qui se bloque indéfiniment. https://claude.ai/code/session_01YHMp3EKzr4s6o8w1ygxuUe --- app/main.py | 9 ++++++--- app/segmenter.py | 21 +++++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/main.py b/app/main.py index a74855e..05f5397 100644 --- a/app/main.py +++ b/app/main.py @@ -283,13 +283,16 @@ async def ws_transcribe(ws: WebSocket): f"({len(seg.pcm) / 2 / SAMPLE_RATE:.1f}s d'audio, niveau crête {peak}" f"{', amplifié' if pcm is not seg.pcm else ''}), transcription...", flush=True) - # Diarisation (~30-80 ms CPU, dans un thread pour ne pas bloquer) + # Diarisation (~30-80 ms CPU). Strictement isolée : ni un blocage + # ni une erreur ne doivent empêcher la transcription qui suit. speaker = "" if diarizer is not None: try: - speaker = await asyncio.to_thread(diarizer.identify, pcm) + speaker = await asyncio.wait_for( + asyncio.to_thread(diarizer.identify, pcm), timeout=5.0 + ) except Exception as exc: - print(f"[réunion {meeting_id}] diarisation échouée : {exc}", flush=True) + print(f"[réunion {meeting_id}] diarisation ignorée : {exc}", flush=True) try: text = await transcribe(pcm) diff --git a/app/segmenter.py b/app/segmenter.py index f52be69..e350279 100644 --- a/app/segmenter.py +++ b/app/segmenter.py @@ -48,14 +48,19 @@ class _AutoGain: self.gain = 1.0 def process(self, frame: bytes) -> bytes: - peak = audioop.max(frame, 2) - if peak >= AGC_NOISE_FLOOR: - desired = min(AGC_MAX_GAIN, AGC_TARGET_PEAK / peak) - rate = AGC_ATTACK if desired > self.gain else AGC_RELEASE - self.gain += (desired - self.gain) * rate - self.gain = max(1.0, min(AGC_MAX_GAIN, self.gain)) - if self.gain > 1.01: - return audioop.mul(frame, 2, self.gain) + # Toute erreur ici ne doit JAMAIS interrompre le découpage : on + # renvoie la trame d'origine en cas de souci. + try: + peak = audioop.max(frame, 2) + if peak >= AGC_NOISE_FLOOR: + desired = min(AGC_MAX_GAIN, AGC_TARGET_PEAK / peak) + rate = AGC_ATTACK if desired > self.gain else AGC_RELEASE + self.gain += (desired - self.gain) * rate + self.gain = max(1.0, min(AGC_MAX_GAIN, self.gain)) + if self.gain > 1.01: + return audioop.mul(frame, 2, self.gain) # audioop sature, ne déborde pas + except Exception: + return frame return frame