mirror of
https://github.com/Jeuners/Logpy-AgentOne.git
synced 2026-09-09 15:02:33 +02:00
fix: Watcher griff Aufnahmen mitten im Schreiben (Race Condition)
Der Fehler "[Errno 2] No such file or directory: '...stt_out_*.json'" sah wie ein Transkriptions-Bug aus, war aber ein Timing-Problem: _fertig() prüfte nur, ob die Dateigröße 2 Sekunden (RUHE_S) unveraendert blieb - eine normale Sprechpause reicht dafuer schon aus, waehrend FreeSWITCH den Anruf noch aktiv aufnimmt. Belegt im Verlauf-Log: die Aufnahme wurde um 10:15:56 als "fertig" gegriffen, der Anrufer legte laut Log aber erst um 10:16:01 auf. ffmpeg/whisper-cli lasen dabei einen mitten im Schreiben befindlichen RIFF-Header, produzierten keine Ausgabe, und process_call brach mit einem FileNotFoundError auf die erwartete JSON-Datei ab. Die Original-Audiodatei selbst blieb dabei unbeschaedigt (FreeSWITCH schrieb einfach in die schon nach telefon/fehler/ verschobene Datei weiter) - deshalb sah eine manuelle Nachpruefung der "kaputten" Datei immer intakt aus und liess sich nicht reproduzieren. _fertig() prueft jetzt zusaetzlich per `lsof -t`, ob noch ein Prozess die Datei offen haelt - nur wenn weder die Groesse waechst noch ein Handle offen ist, gilt die Aufnahme als abgeschlossen. Nebenbei: tempfile.mkstemp() in transcribe.py gab einen offenen File- Descriptor zurueck, der nie geschlossen wurde (fd-Leak in beiden Tempdatei-Erzeugungen) - behoben. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PuP3zfgYqhnrwyEne2ejG7
This commit is contained in:
parent
878e49d372
commit
3749d60aa4
2 changed files with 29 additions and 4 deletions
|
|
@ -11,6 +11,7 @@ Beliebige Eingabeformate/Abtastraten werden vor der Erkennung per ffmpeg auf
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
from dataclasses import dataclass, field
|
||||
|
|
@ -37,7 +38,9 @@ def _letzte_zeile(ausgabe: str | None) -> str:
|
|||
|
||||
def _nach_16k_mono(audio: Path) -> Path:
|
||||
"""Normalisiert beliebiges Audio auf 16 kHz mono WAV (Tempdatei)."""
|
||||
ziel = Path(tempfile.mkstemp(suffix=".wav", prefix="stt_")[1])
|
||||
fd, pfad = tempfile.mkstemp(suffix=".wav", prefix="stt_")
|
||||
os.close(fd)
|
||||
ziel = Path(pfad)
|
||||
try:
|
||||
subprocess.run(
|
||||
["ffmpeg", "-y", "-i", str(audio), "-ar", "16000", "-ac", "1", str(ziel)],
|
||||
|
|
@ -69,7 +72,9 @@ def transkribiere(audio: str | Path) -> Transkript:
|
|||
|
||||
def _mit_whispercpp(audio: Path) -> Transkript:
|
||||
wav = _nach_16k_mono(audio)
|
||||
ausgabe_basis = Path(tempfile.mkstemp(suffix="", prefix="stt_out_")[1])
|
||||
fd, pfad = tempfile.mkstemp(suffix="", prefix="stt_out_")
|
||||
os.close(fd)
|
||||
ausgabe_basis = Path(pfad)
|
||||
try:
|
||||
cmd = [
|
||||
config.WHISPERCPP_BIN,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from __future__ import annotations
|
|||
import argparse
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
|
@ -53,8 +54,25 @@ def _nummer_und_zeit(datei: Path) -> tuple[str | None, datetime | None]:
|
|||
return nummer, zeit
|
||||
|
||||
|
||||
def _noch_offen(datei: Path) -> bool:
|
||||
"""Wahr, wenn ein Prozess (FreeSWITCH) die Datei noch zum Schreiben offen
|
||||
haelt. Reine Groessenstabilitaet reicht nicht: eine Sprechpause laesst die
|
||||
Datei fuer RUHE_S Sekunden still stehen, obwohl der Anruf noch laeuft - der
|
||||
Watcher hat dann eine Aufnahme mitten im Schreiben gegriffen, ffmpeg/
|
||||
whisper-cli lasen einen unfertigen RIFF-Header und scheiterten wortlos
|
||||
(kein Absturz, nur keine Ausgabedatei - sah wie ein Transkriptions-Bug
|
||||
aus, war aber ein Race gegen die laufende Aufnahme)."""
|
||||
try:
|
||||
r = subprocess.run(["lsof", "-t", str(datei)],
|
||||
capture_output=True, text=True, timeout=5)
|
||||
return bool(r.stdout.strip())
|
||||
except Exception:
|
||||
return False # lsof fehlt/schlaegt fehl: nicht blockieren
|
||||
|
||||
|
||||
def _fertig(datei: Path) -> bool:
|
||||
"""Wahr, wenn die Datei nicht mehr wächst - die Aufnahme also steht."""
|
||||
"""Wahr, wenn die Datei nicht mehr waechst UND kein Prozess sie noch offen
|
||||
haelt - die Aufnahme also wirklich abgeschlossen ist."""
|
||||
try:
|
||||
groesse = datei.stat().st_size
|
||||
except FileNotFoundError:
|
||||
|
|
@ -63,9 +81,11 @@ def _fertig(datei: Path) -> bool:
|
|||
return False
|
||||
time.sleep(RUHE_S)
|
||||
try:
|
||||
return datei.stat().st_size == groesse
|
||||
if datei.stat().st_size != groesse:
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
return not _noch_offen(datei)
|
||||
|
||||
|
||||
def _neue_dateien(eingang: Path):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue