python-voiceio 0.9.2__tar.gz → 0.9.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. {python_voiceio-0.9.2/python_voiceio.egg-info → python_voiceio-0.9.3}/PKG-INFO +1 -1
  2. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/pyproject.toml +1 -1
  3. {python_voiceio-0.9.2 → python_voiceio-0.9.3/python_voiceio.egg-info}/PKG-INFO +1 -1
  4. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_robustness.py +73 -0
  5. python_voiceio-0.9.3/voiceio/__init__.py +1 -0
  6. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/app.py +22 -2
  7. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/config.py +5 -0
  8. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/recorder.py +22 -3
  9. python_voiceio-0.9.2/voiceio/__init__.py +0 -1
  10. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/LICENSE +0 -0
  11. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/README.md +0 -0
  12. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/python_voiceio.egg-info/SOURCES.txt +0 -0
  13. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/python_voiceio.egg-info/dependency_links.txt +0 -0
  14. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/python_voiceio.egg-info/entry_points.txt +0 -0
  15. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/python_voiceio.egg-info/requires.txt +0 -0
  16. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/python_voiceio.egg-info/top_level.txt +0 -0
  17. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/setup.cfg +0 -0
  18. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_adjudicate.py +0 -0
  19. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_app_wiring.py +0 -0
  20. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_audio_quality.py +0 -0
  21. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_audit.py +0 -0
  22. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_autocorrect.py +0 -0
  23. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_autocorrect_state.py +0 -0
  24. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_backend_probes.py +0 -0
  25. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_cli.py +0 -0
  26. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_clipboard_read.py +0 -0
  27. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_commands.py +0 -0
  28. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_concurrency_lockdown.py +0 -0
  29. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_config.py +0 -0
  30. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_correct_batch.py +0 -0
  31. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_corrections.py +0 -0
  32. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_evaluate.py +0 -0
  33. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_fallback.py +0 -0
  34. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_health.py +0 -0
  35. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_hints.py +0 -0
  36. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_history.py +0 -0
  37. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_ibus_pending.py +0 -0
  38. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_ibus_ping.py +0 -0
  39. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_ibus_typer.py +0 -0
  40. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_llm.py +0 -0
  41. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_llm_api.py +0 -0
  42. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_numbers.py +0 -0
  43. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_platform.py +0 -0
  44. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_postcorrect.py +0 -0
  45. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_postprocess.py +0 -0
  46. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_prebuffer.py +0 -0
  47. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_prompt.py +0 -0
  48. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_recorder_integration.py +0 -0
  49. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_retention.py +0 -0
  50. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_security_hardening.py +0 -0
  51. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_snapshots.py +0 -0
  52. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_streaming.py +0 -0
  53. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_tokens.py +0 -0
  54. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_transcriber.py +0 -0
  55. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_tts.py +0 -0
  56. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_vad.py +0 -0
  57. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_vocabulary.py +0 -0
  58. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_wizard.py +0 -0
  59. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/tests/test_wordfreq.py +0 -0
  60. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/__main__.py +0 -0
  61. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/audit.py +0 -0
  62. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/autocorrect.py +0 -0
  63. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/autocorrect_state.py +0 -0
  64. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/backends.py +0 -0
  65. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/cli.py +0 -0
  66. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/clipboard_read.py +0 -0
  67. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/commands.py +0 -0
  68. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/consent.py +0 -0
  69. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/corrections.py +0 -0
  70. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/demo.py +0 -0
  71. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/evaluate.py +0 -0
  72. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/feedback.py +0 -0
  73. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/health.py +0 -0
  74. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/hints.py +0 -0
  75. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/history.py +0 -0
  76. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/hotkeys/__init__.py +0 -0
  77. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/hotkeys/base.py +0 -0
  78. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/hotkeys/chain.py +0 -0
  79. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/hotkeys/evdev.py +0 -0
  80. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/hotkeys/pynput_backend.py +0 -0
  81. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/hotkeys/socket_backend.py +0 -0
  82. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/ibus/__init__.py +0 -0
  83. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/ibus/engine.py +0 -0
  84. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/ibus/pending.py +0 -0
  85. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/llm.py +0 -0
  86. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/llm_api.py +0 -0
  87. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/models/__init__.py +0 -0
  88. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/models/silero_vad.onnx +0 -0
  89. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/numbers.py +0 -0
  90. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/pidlock.py +0 -0
  91. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/platform.py +0 -0
  92. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/postcorrect.py +0 -0
  93. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/postprocess.py +0 -0
  94. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/prompt.py +0 -0
  95. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/retention.py +0 -0
  96. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/service.py +0 -0
  97. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/snapshots.py +0 -0
  98. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/sounds/__init__.py +0 -0
  99. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/sounds/commit.wav +0 -0
  100. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/sounds/start.wav +0 -0
  101. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/sounds/stop.wav +0 -0
  102. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/streaming.py +0 -0
  103. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tokens.py +0 -0
  104. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/transcriber.py +0 -0
  105. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tray/__init__.py +0 -0
  106. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tray/_icons.py +0 -0
  107. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tray/_indicator.py +0 -0
  108. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tray/_pystray.py +0 -0
  109. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tts/__init__.py +0 -0
  110. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tts/base.py +0 -0
  111. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tts/chain.py +0 -0
  112. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tts/edge_engine.py +0 -0
  113. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tts/espeak.py +0 -0
  114. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tts/piper_engine.py +0 -0
  115. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/tts/player.py +0 -0
  116. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/typers/__init__.py +0 -0
  117. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/typers/base.py +0 -0
  118. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/typers/chain.py +0 -0
  119. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/typers/clipboard.py +0 -0
  120. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/typers/ibus.py +0 -0
  121. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/typers/pynput_type.py +0 -0
  122. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/typers/wtype.py +0 -0
  123. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/typers/xdotool.py +0 -0
  124. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/typers/ydotool.py +0 -0
  125. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/vad.py +0 -0
  126. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/vocab_stats.py +0 -0
  127. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/vocabulary.py +0 -0
  128. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/wizard.py +0 -0
  129. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/wordfreq.py +0 -0
  130. {python_voiceio-0.9.2 → python_voiceio-0.9.3}/voiceio/worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-voiceio"
7
- version = "0.9.2"
7
+ version = "0.9.3"
8
8
  description = "Voice dictation for Linux. Speak → text, locally, instantly."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -63,6 +63,7 @@ def _make_recorder():
63
63
  cfg.silence_threshold = 0.01
64
64
  cfg.silence_duration = 0.6
65
65
  cfg.auto_stop_silence_secs = 5.0
66
+ cfg.auto_stop_no_speech_secs = 20.0
66
67
 
67
68
  with patch("voiceio.recorder.sd"):
68
69
  from voiceio.recorder import AudioRecorder
@@ -72,6 +73,78 @@ def _make_recorder():
72
73
  return rec
73
74
 
74
75
 
76
+ class TestAutoStop:
77
+ """Auto-stop distinguishes 'spoke then paused' from 'nothing ever heard'."""
78
+
79
+ def _feed(self, rec, secs, *, speech):
80
+ """Drive _callback with `secs` of audio; speech=False → silence."""
81
+ rec._vad.is_speech.return_value = speech
82
+ frames = int(secs * rec.sample_rate)
83
+ rec._callback(np.zeros((frames, 1), dtype=np.float32), frames, None, None)
84
+
85
+ def test_no_speech_fires_no_speech_reason(self):
86
+ rec = _make_recorder()
87
+ rec._recording = True
88
+ fired = []
89
+ rec.set_on_auto_stop(lambda reason: fired.append(reason))
90
+ # 21s of pure silence, speech never heard.
91
+ self._feed(rec, 21, speech=False)
92
+ assert fired == ["no_speech"]
93
+
94
+ def test_no_speech_does_not_fire_early(self):
95
+ rec = _make_recorder()
96
+ rec._recording = True
97
+ fired = []
98
+ rec.set_on_auto_stop(lambda reason: fired.append(reason))
99
+ self._feed(rec, 10, speech=False) # below the 20s threshold
100
+ assert fired == []
101
+
102
+ def test_speech_then_silence_fires_silence_reason(self):
103
+ rec = _make_recorder()
104
+ rec._recording = True
105
+ fired = []
106
+ rec.set_on_auto_stop(lambda reason: fired.append(reason))
107
+ self._feed(rec, 1, speech=True) # heard speech → _heard_speech
108
+ self._feed(rec, 6, speech=False) # 6s silence ≥ 5s auto_stop
109
+ assert fired == ["silence"]
110
+
111
+ def test_single_fire(self):
112
+ """Callback is cleared on fire — a later chunk must not re-trigger."""
113
+ rec = _make_recorder()
114
+ rec._recording = True
115
+ fired = []
116
+ rec.set_on_auto_stop(lambda reason: fired.append(reason))
117
+ self._feed(rec, 21, speech=False)
118
+ self._feed(rec, 21, speech=False)
119
+ assert fired == ["no_speech"]
120
+
121
+
122
+ class TestAutoStopNotification:
123
+ """The app surfaces a 'no_speech' auto-stop; a plain 'silence' one is quiet."""
124
+
125
+ def test_no_speech_muted_mic_notifies(self):
126
+ vio, _, _ = _make_vio()
127
+ vio.recorder.has_signal = MagicMock(return_value=False) # all zeros = muted
128
+ with patch("voiceio.feedback.notify") as notify:
129
+ vio._on_auto_stop("no_speech")
130
+ assert notify.called
131
+ assert "mute" in " ".join(notify.call_args[0]).lower()
132
+
133
+ def test_no_speech_with_signal_notifies_generic(self):
134
+ vio, _, _ = _make_vio()
135
+ vio.recorder.has_signal = MagicMock(return_value=True) # mic works, user quiet
136
+ with patch("voiceio.feedback.notify") as notify:
137
+ vio._on_auto_stop("no_speech")
138
+ assert notify.called
139
+ assert "mute" not in " ".join(notify.call_args[0]).lower()
140
+
141
+ def test_normal_silence_is_quiet(self):
142
+ vio, _, _ = _make_vio()
143
+ with patch("voiceio.feedback.notify") as notify:
144
+ vio._on_auto_stop("silence")
145
+ assert not notify.called
146
+
147
+
75
148
  # ===========================================================================
76
149
  # 1. stream_health() tests
77
150
  # ===========================================================================
@@ -0,0 +1 @@
1
+ __version__ = "0.9.3"
@@ -283,8 +283,28 @@ class VoiceIO:
283
283
  self._last_hotkey = now
284
284
  self._toggle()
285
285
 
286
- def _on_auto_stop(self) -> None:
287
- """Called from audio thread when sustained silence triggers auto-stop."""
286
+ def _on_auto_stop(self, reason: str = "silence") -> None:
287
+ """Called from the audio thread when the recorder auto-stops.
288
+
289
+ reason="silence": spoke, then went quiet — a normal end of dictation.
290
+ reason="no_speech": nothing was heard the whole time — a muted mic or a
291
+ hotkey pressed by accident. Surface it so it isn't a silent failure
292
+ (today's Discord-mute confusion: recording ran, nothing was captured,
293
+ and there was no sign why).
294
+ """
295
+ if reason == "no_speech":
296
+ from voiceio import feedback
297
+ if not self.recorder.has_signal():
298
+ feedback.notify(
299
+ "VoiceIO: microphone is muted",
300
+ "Stopped — no audio is reaching voiceio. Check your mic's "
301
+ "mute button and that the right input device is selected.",
302
+ )
303
+ else:
304
+ feedback.notify(
305
+ "VoiceIO: stopped — no speech detected",
306
+ "Recording auto-stopped after silence.",
307
+ )
288
308
  threading.Thread(target=self._request_stop, daemon=True).start()
289
309
 
290
310
  def _request_stop(self) -> None:
@@ -71,6 +71,11 @@ class AudioConfig:
71
71
  silence_threshold: float = 0.01
72
72
  silence_duration: float = 0.6
73
73
  auto_stop_silence_secs: float = 5.0
74
+ # Safety net for a muted mic or a forgotten hotkey: if NO speech is heard
75
+ # for this long from the start, stop and notify. The 5s auto-stop above
76
+ # only fires once speech has been heard, so it can't catch a dead mic.
77
+ # 0 disables.
78
+ auto_stop_no_speech_secs: float = 20.0
74
79
  vad_backend: str = "silero" # "silero" | "rms"
75
80
  vad_threshold: float = 0.5 # Silero speech probability threshold
76
81
 
@@ -108,9 +108,12 @@ class AudioRecorder:
108
108
 
109
109
  # Auto-stop on sustained silence
110
110
  self._auto_stop_secs = cfg.auto_stop_silence_secs
111
+ self._no_speech_secs = cfg.auto_stop_no_speech_secs
111
112
  self._sustained_silence = 0.0
112
113
  self._heard_speech = False
113
- self._on_auto_stop: Callable[[], None] | None = None
114
+ # Called with a reason: "silence" (spoke then paused) or "no_speech"
115
+ # (nothing ever heard — a muted mic or a forgotten hotkey).
116
+ self._on_auto_stop: Callable[[str], None] | None = None
114
117
 
115
118
  # Heartbeat: updated by _callback, checked by health watchdog
116
119
  self._last_callback_time: float = 0.0
@@ -260,7 +263,7 @@ class AudioRecorder:
260
263
  """Set/clear the speech pause callback (used by streaming session)."""
261
264
  self._on_speech_pause = callback
262
265
 
263
- def set_on_auto_stop(self, callback: Callable[[], None] | None) -> None:
266
+ def set_on_auto_stop(self, callback: Callable[[str], None] | None) -> None:
264
267
  """Set/clear the auto-stop callback (fires after sustained silence)."""
265
268
  self._on_auto_stop = callback
266
269
 
@@ -340,4 +343,20 @@ class AudioRecorder:
340
343
  self._on_auto_stop = None
341
344
  self._sustained_silence = 0.0
342
345
  log.info("Auto-stopping after %.0fs of silence", self._auto_stop_secs)
343
- cb()
346
+ cb("silence")
347
+
348
+ # Safety net: nothing heard at all for a long time. Because speech
349
+ # resets _sustained_silence AND is gated out by not-_heard_speech, this
350
+ # measures continuous silence from the very start of the recording — a
351
+ # muted mic (all zeros) or a hotkey pressed by accident. Distinct reason
352
+ # so the app can surface it instead of failing silently.
353
+ elif (self._on_auto_stop is not None
354
+ and self._no_speech_secs > 0
355
+ and not self._heard_speech
356
+ and self._sustained_silence >= self._no_speech_secs):
357
+ cb = self._on_auto_stop
358
+ self._on_auto_stop = None
359
+ self._sustained_silence = 0.0
360
+ log.info("Auto-stopping: no speech heard in %.0fs (mic muted?)",
361
+ self._no_speech_secs)
362
+ cb("no_speech")
@@ -1 +0,0 @@
1
- __version__ = "0.9.2"
File without changes
File without changes
File without changes