Procházet zdrojové kódy

Changed to audio save best-effort

Efren Yevale Varela před 1 měsícem
rodič
revize
d72d46df14
3 změnil soubory, kde provedl 47 přidání a 3 odebrání
  1. 10 2
      README.md
  2. 8 1
      src/stt_daemon/output.py
  3. 29 0
      tests/test_output.py

+ 10 - 2
README.md

@@ -354,8 +354,16 @@ an `"audio"` field with its path:
 ```json
 {..., "end_reason": "pause", "text": "Hola", "audio": "audio/2025-09-20/142345-123-pause.wav"}
 ```
-16 kHz mono 16-bit is ~32 KB/s (~1.9 MB/minute), so enable this only if you need
-the clips for auditing.
+Saving is best-effort: if a write fails (e.g. disk full), the transcript is still
+emitted and a warning is logged.
+
+> ⚠️ **Disk space** — saved audio is **never rotated or cleaned up** automatically.
+> At 16 kHz mono 16-bit it costs 32 KB/s, i.e. **~1.9 MB/min, ~115 MB/hour,
+> ~2.8 GB/day** of *continuous speech*. Silence is not written, so real usage is
+> lower — but an always-on daemon in a talkative room still accumulates quickly.
+> Point `audio_dir` at a volume with headroom and prune it yourself (e.g. a cron
+> job removing files older than N days), and leave `save_audio = false` unless you
+> actually need the clips for auditing.
 
 ## Virtual Sink Provisioning: How It Works
 

+ 8 - 1
src/stt_daemon/output.py

@@ -1,6 +1,7 @@
 """Output sinks for transcripts."""
 
 import json
+import logging
 import wave
 from datetime import datetime
 from pathlib import Path
@@ -9,6 +10,8 @@ import numpy as np
 
 from .transcriber import TranscriptResult
 
+logger = logging.getLogger(__name__)
+
 
 def write_wav(path: Path, audio: np.ndarray, sample_rate: int) -> None:
     """Write float32 mono audio in [-1, 1] to a 16-bit PCM WAV file."""
@@ -68,7 +71,11 @@ class TranscriptSink:
 
         audio_path = None
         if self.save_audio and audio is not None:
-            audio_path = self._save_audio(audio, reason)
+            try:
+                audio_path = self._save_audio(audio, reason)
+            except Exception as e:
+                # Best-effort side output: a failed save must not drop the transcript.
+                logger.warning("Failed to save audio for this utterance: %s", e)
 
         # Stdout line: [HH:MM:SS] (lang) text  [rms=0.041 end_of_turn]
         if self.stdout:

+ 29 - 0
tests/test_output.py

@@ -98,3 +98,32 @@ def test_save_audio_without_samples_is_safe(tmp_path):
 
     assert not (tmp_path / "audio").exists()
 
+
+def test_failed_audio_save_is_non_fatal(tmp_path, monkeypatch, capsys, caplog):
+    import stt_daemon.output as output
+
+    def boom(*args, **kwargs):
+        raise OSError("disk full")
+
+    monkeypatch.setattr(output, "write_wav", boom)
+
+    logs = tmp_path / "logs"
+    sink = TranscriptSink(
+        stdout=True,
+        jsonl_dir=str(logs),
+        save_audio=True,
+        audio_dir=str(tmp_path / "audio"),
+        sample_rate=16000,
+    )
+    with caplog.at_level("WARNING"):
+        sink.emit(
+            _result(), 1.0, rms=0.3, reason="pause", audio=np.zeros(1600, dtype=np.float32)
+        )
+
+    assert "hello world" in capsys.readouterr().out
+    record = json.loads(next(logs.glob("*.jsonl")).read_text().splitlines()[0])
+    assert record["text"] == "hello world"
+    assert "audio" not in record
+    assert "Failed to save audio" in caplog.text
+
+