test_output.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. """Tests for transcript output sinks."""
  2. import json
  3. import wave
  4. import numpy as np
  5. from stt_daemon.output import TranscriptSink
  6. from stt_daemon.transcriber import TranscriptResult
  7. def _result(text="hello world"):
  8. return TranscriptResult(language="en", language_probability=0.98, text=text)
  9. def test_jsonl_includes_level_and_reason(tmp_path):
  10. sink = TranscriptSink(stdout=False, jsonl_dir=str(tmp_path))
  11. sink.emit(_result(), 2.5, rms=0.041234, reason="pause")
  12. files = list(tmp_path.glob("*.jsonl"))
  13. assert len(files) == 1
  14. record = json.loads(files[0].read_text().splitlines()[0])
  15. assert record["rms"] == round(0.041234, 5)
  16. assert record["end_reason"] == "pause"
  17. assert record["duration_s"] == 2.5
  18. assert record["text"] == "hello world"
  19. assert record["lang"] == "en"
  20. def test_stdout_includes_level_and_reason(capsys):
  21. sink = TranscriptSink(stdout=True, jsonl_dir=None)
  22. sink.emit(_result(), 1.0, rms=0.02, reason="end_of_turn")
  23. out = capsys.readouterr().out
  24. assert "rms=0.020" in out
  25. assert "end_of_turn" in out
  26. assert "hello world" in out
  27. def test_empty_text_is_skipped(tmp_path, capsys):
  28. sink = TranscriptSink(stdout=True, jsonl_dir=str(tmp_path))
  29. sink.emit(_result(text=""), 1.0, rms=0.02, reason="pause")
  30. assert capsys.readouterr().out == ""
  31. assert list(tmp_path.glob("*.jsonl")) == []
  32. def test_saves_wav_when_enabled(tmp_path):
  33. audio = np.zeros(16000, dtype=np.float32)
  34. audio[:8000] = 0.5
  35. logs = tmp_path / "logs"
  36. sink = TranscriptSink(
  37. stdout=False,
  38. jsonl_dir=str(logs),
  39. save_audio=True,
  40. audio_dir=str(tmp_path / "audio"),
  41. sample_rate=16000,
  42. )
  43. sink.emit(_result(), 1.0, rms=0.3, reason="pause", audio=audio)
  44. wavs = list((tmp_path / "audio").rglob("*.wav"))
  45. assert len(wavs) == 1
  46. with wave.open(str(wavs[0]), "rb") as wf:
  47. assert wf.getnchannels() == 1
  48. assert wf.getsampwidth() == 2
  49. assert wf.getframerate() == 16000
  50. assert wf.getnframes() == 16000
  51. record = json.loads(next(logs.glob("*.jsonl")).read_text().splitlines()[0])
  52. assert record["audio"] == str(wavs[0])
  53. def test_no_wav_when_disabled(tmp_path):
  54. sink = TranscriptSink(
  55. stdout=False,
  56. jsonl_dir=None,
  57. save_audio=False,
  58. audio_dir=str(tmp_path / "audio"),
  59. sample_rate=16000,
  60. )
  61. sink.emit(_result(), 1.0, rms=0.3, reason="pause", audio=np.zeros(1600, dtype=np.float32))
  62. assert not (tmp_path / "audio").exists()
  63. def test_save_audio_without_samples_is_safe(tmp_path):
  64. sink = TranscriptSink(
  65. stdout=False,
  66. jsonl_dir=None,
  67. save_audio=True,
  68. audio_dir=str(tmp_path / "audio"),
  69. sample_rate=16000,
  70. )
  71. sink.emit(_result(), 1.0, rms=0.3, reason="pause", audio=None)
  72. assert not (tmp_path / "audio").exists()
  73. def test_failed_audio_save_is_non_fatal(tmp_path, monkeypatch, capsys, caplog):
  74. import stt_daemon.output as output
  75. def boom(*args, **kwargs):
  76. raise OSError("disk full")
  77. monkeypatch.setattr(output, "write_wav", boom)
  78. logs = tmp_path / "logs"
  79. sink = TranscriptSink(
  80. stdout=True,
  81. jsonl_dir=str(logs),
  82. save_audio=True,
  83. audio_dir=str(tmp_path / "audio"),
  84. sample_rate=16000,
  85. )
  86. with caplog.at_level("WARNING"):
  87. sink.emit(
  88. _result(), 1.0, rms=0.3, reason="pause", audio=np.zeros(1600, dtype=np.float32)
  89. )
  90. assert "hello world" in capsys.readouterr().out
  91. record = json.loads(next(logs.glob("*.jsonl")).read_text().splitlines()[0])
  92. assert record["text"] == "hello world"
  93. assert "audio" not in record
  94. assert "Failed to save audio" in caplog.text