python-voiceio 0.9.2__py3-none-any.whl → 0.9.3__py3-none-any.whl

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.
@@ -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
@@ -1,7 +1,7 @@
1
- python_voiceio-0.9.2.dist-info/licenses/LICENSE,sha256=Gz61o8jFTAvZUZyB3nWDB3DQQVuipjfPkVu9W8hBHM0,1072
2
- voiceio/__init__.py,sha256=gqT-BGoeEItda9fICQDvLbxEjWRIBhFJxPxxKvmHLUo,22
1
+ python_voiceio-0.9.3.dist-info/licenses/LICENSE,sha256=Gz61o8jFTAvZUZyB3nWDB3DQQVuipjfPkVu9W8hBHM0,1072
2
+ voiceio/__init__.py,sha256=xKd3pzbczuMsdB08eLAOqZDUd_q1IRxwZ_ccAFL4c4A,22
3
3
  voiceio/__main__.py,sha256=xT5QCGGreYMisHO7Lh_Y-xAQ2TOzG2D7npKknGNcWSY,53
4
- voiceio/app.py,sha256=ScrUVDQQ_YbAFKKSMOhaRPX_WTcPuenhnRdSzWMxThE,73785
4
+ voiceio/app.py,sha256=8xS_MySy6cvpxXDcd0-6YwQK6KW_iT8Vdf6FqzYUXpI,74743
5
5
  voiceio/audit.py,sha256=p4F2BBX4wEC9eS8MSplzfXL52XC2dyIKbgrRvR-WbTU,15802
6
6
  voiceio/autocorrect.py,sha256=jGsglEYwAHWk2uWedeEZzyAMW2LWO_8nQGZgrWYy4Dc,26552
7
7
  voiceio/autocorrect_state.py,sha256=BNzdHP5slcOV_-OU4MlaKE7xgVJzrPncsYFK30JWTs8,6858
@@ -9,7 +9,7 @@ voiceio/backends.py,sha256=-rs1YhPblub4NjFb6qCD7Ih9FqBRXtPuvjVv9bQoQ1w,368
9
9
  voiceio/cli.py,sha256=QE8N7N3d1O7r9-u_jbYMAH9gw0ILX7dSxDZZYj83lIs,78969
10
10
  voiceio/clipboard_read.py,sha256=lz75CkRGh7pyvO6xvoUmxJDl-Gwie1xlXQHK70PO84I,4261
11
11
  voiceio/commands.py,sha256=Vhtn8s5G5OcCbRN44YZ-_14Fa85ISXl_3Ix-KqSHs2A,4694
12
- voiceio/config.py,sha256=DA1PGIanhrPjWkVT6jj0NSFslW9vuegzV6eMJNuKBzM,15273
12
+ voiceio/config.py,sha256=IxXfs780V4eMhbfoyHZAUER7cmjg950Lp1wsvm9aEIU,15563
13
13
  voiceio/consent.py,sha256=jtdp1TkTbicUSTCDmV4qo4SdbS4kQpYzy1Kjc_7pJVw,1883
14
14
  voiceio/corrections.py,sha256=7moKhkVvo5CZU1mZobV2-MajkcHiwliKBY1dSBFh0w0,7445
15
15
  voiceio/demo.py,sha256=QRNJuNObXCsjwGD-Cz3dh81cfb21xiuSeVyyU720U9Q,8673
@@ -26,7 +26,7 @@ voiceio/platform.py,sha256=f_8MmC5q1sCEuEPyKjRqlVvV9FExuGmwn_aJXhkpiEk,11809
26
26
  voiceio/postcorrect.py,sha256=iyO7MPIR4a4XIASEGOs9Y3iUXVTfwtVkhVOWj2vY1lk,11201
27
27
  voiceio/postprocess.py,sha256=9grkq3I6XgiQV6ZQbIw2_fOUe57tzC7GBPvLXqhhdTs,2979
28
28
  voiceio/prompt.py,sha256=9iza8KoQxvO_U9NPMJ8Ep7j-zkIs9nW8N5sacCRpsEk,2904
29
- voiceio/recorder.py,sha256=p1r0mH_plBD3BHqBbIGV_m2N4Mj5RAF7LfMu8u6mk5I,12813
29
+ voiceio/recorder.py,sha256=x3WG-3GXvqHb0yagXXVeDgjD5PnG6xv6MyRHMTnB68w,13885
30
30
  voiceio/retention.py,sha256=i_VWPCyykT4kQ1uKilTplpiIrACTYljn09BU1vL71A0,6019
31
31
  voiceio/service.py,sha256=1YhnDPHnu0Aw66uwsp05TWfs18bf3io_h6r8eNla7nA,12525
32
32
  voiceio/snapshots.py,sha256=bm-lzIeYucDUDlPBZiFiWd-jyB2I6Ft1jRANZCO6go4,2351
@@ -74,8 +74,8 @@ voiceio/typers/pynput_type.py,sha256=DTtkT59M-EKOAlTNkvcZrRvf_Fq52OpI8S5t_8S-QAY
74
74
  voiceio/typers/wtype.py,sha256=d1wG-HZdYDQxXIysV_Xvk9HixDpaJgl3VEoKbP5vhIs,1786
75
75
  voiceio/typers/xdotool.py,sha256=dh1zhPqUT8ihJahJ7ZKm6PtfYY087UAzADx664DvQOM,1356
76
76
  voiceio/typers/ydotool.py,sha256=dt0W9ot__W8LQG2so8vZLGIw9leK2Q6g3i-Voo0LXE4,5327
77
- python_voiceio-0.9.2.dist-info/METADATA,sha256=RZm3kWz94AUBzUicA4CWYbm_oLyaFrNVfvU0acqEHEY,15877
78
- python_voiceio-0.9.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
79
- python_voiceio-0.9.2.dist-info/entry_points.txt,sha256=U64fA65zxzyLoC8bgbn2ztQVWHLsc0o0H0qCk1J9DMc,218
80
- python_voiceio-0.9.2.dist-info/top_level.txt,sha256=piwtn309lD6uexQyXdZ-efAVBJF9y6Wfr48Z-8zkNhg,8
81
- python_voiceio-0.9.2.dist-info/RECORD,,
77
+ python_voiceio-0.9.3.dist-info/METADATA,sha256=h-f14EX3kSMP26A2Y12MJbprM_03l0TkRSm0wIYWrao,15877
78
+ python_voiceio-0.9.3.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
79
+ python_voiceio-0.9.3.dist-info/entry_points.txt,sha256=U64fA65zxzyLoC8bgbn2ztQVWHLsc0o0H0qCk1J9DMc,218
80
+ python_voiceio-0.9.3.dist-info/top_level.txt,sha256=piwtn309lD6uexQyXdZ-efAVBJF9y6Wfr48Z-8zkNhg,8
81
+ python_voiceio-0.9.3.dist-info/RECORD,,
voiceio/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.9.2"
1
+ __version__ = "0.9.3"
voiceio/app.py CHANGED
@@ -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:
voiceio/config.py CHANGED
@@ -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
 
voiceio/recorder.py CHANGED
@@ -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")