python-voiceio 0.8.1__py3-none-any.whl → 0.8.2__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.8.1
3
+ Version: 0.8.2
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -1,5 +1,5 @@
1
- python_voiceio-0.8.1.dist-info/licenses/LICENSE,sha256=Gz61o8jFTAvZUZyB3nWDB3DQQVuipjfPkVu9W8hBHM0,1072
2
- voiceio/__init__.py,sha256=Ocl79hbbH8_jdr5dGC90VR1cAvZc05Rc0tkZttUnMjo,22
1
+ python_voiceio-0.8.2.dist-info/licenses/LICENSE,sha256=Gz61o8jFTAvZUZyB3nWDB3DQQVuipjfPkVu9W8hBHM0,1072
2
+ voiceio/__init__.py,sha256=B7GiO0rd49YwtLYjvPg4lmCZEDlMTonslQKdSImaMJk,22
3
3
  voiceio/__main__.py,sha256=xT5QCGGreYMisHO7Lh_Y-xAQ2TOzG2D7npKknGNcWSY,53
4
4
  voiceio/app.py,sha256=vO5Sk12CxT2aFtt14XP1Vyen_leUQcqYftswURjN58U,71360
5
5
  voiceio/audit.py,sha256=vX3x-YAhmt6EZYRZd0_6wvTquZu5sxGMvH45BhSBnCk,14915
@@ -27,15 +27,15 @@ voiceio/postprocess.py,sha256=9grkq3I6XgiQV6ZQbIw2_fOUe57tzC7GBPvLXqhhdTs,2979
27
27
  voiceio/prompt.py,sha256=9iza8KoQxvO_U9NPMJ8Ep7j-zkIs9nW8N5sacCRpsEk,2904
28
28
  voiceio/recorder.py,sha256=p1r0mH_plBD3BHqBbIGV_m2N4Mj5RAF7LfMu8u6mk5I,12813
29
29
  voiceio/retention.py,sha256=i_VWPCyykT4kQ1uKilTplpiIrACTYljn09BU1vL71A0,6019
30
- voiceio/service.py,sha256=jqX1opZyQh_dOJMK4bVf8Aoei0zOuuk1Ogta9BXTWGY,12106
30
+ voiceio/service.py,sha256=e3j5OxhuwEqcgXlmQ9AhgdNrrZPwCNfzj-BmABuBYRw,12145
31
31
  voiceio/snapshots.py,sha256=bm-lzIeYucDUDlPBZiFiWd-jyB2I6Ft1jRANZCO6go4,2351
32
32
  voiceio/streaming.py,sha256=mXkQEEMGMbABSRNG2H7MYObKUdCwfqMvNIZISGZ5uic,25360
33
- voiceio/transcriber.py,sha256=2ev_hwdusve7ZZqht9A9wtjjT72rcr7SuhmSIW8aClk,10721
33
+ voiceio/transcriber.py,sha256=NWYMZpIhYccUBKzcFyMzjH1MTMNv4bG9op5Qw5reOhI,11608
34
34
  voiceio/vad.py,sha256=72_ICk4jSuSPYvTHdHYtHzY8W5uaGEOz5FTdqkolJi8,5327
35
35
  voiceio/vocabulary.py,sha256=eGV7QZs3tN6Cw5YpyoZsVEk2M5fJvie1n2XFi9CD4hw,6321
36
36
  voiceio/wizard.py,sha256=AwjwrocrQp0DPSOMZ0m6z6G3hlo9VPceEwPzR9-xXe0,80227
37
37
  voiceio/wordfreq.py,sha256=2UMjW1xIFqo5EchXzsXPizUhRb9N18CVgjnXso1wsgw,2382
38
- voiceio/worker.py,sha256=M6e4s6P0-4UGQzw0j8Mq68nypvwZleOSwuRhjuOqlVE,2563
38
+ voiceio/worker.py,sha256=Ec_OChl6CCX1XHdNYdmx_4l4_sIRexyxih64-PBNhjs,3233
39
39
  voiceio/hotkeys/__init__.py,sha256=rGXSGZLD2mS_Ep2HfVdRdkUKNr4xagySlL1FM00cYgk,735
40
40
  voiceio/hotkeys/base.py,sha256=L3iBh368sjWpt64JZpocH9DibYEZ0V16-rYflLYbpZY,699
41
41
  voiceio/hotkeys/chain.py,sha256=8pvPhipmxWwmGG9DRXy8dlEKTPFILxNzYO-adcvw5Ps,2800
@@ -71,8 +71,8 @@ voiceio/typers/pynput_type.py,sha256=DTtkT59M-EKOAlTNkvcZrRvf_Fq52OpI8S5t_8S-QAY
71
71
  voiceio/typers/wtype.py,sha256=d1wG-HZdYDQxXIysV_Xvk9HixDpaJgl3VEoKbP5vhIs,1786
72
72
  voiceio/typers/xdotool.py,sha256=dh1zhPqUT8ihJahJ7ZKm6PtfYY087UAzADx664DvQOM,1356
73
73
  voiceio/typers/ydotool.py,sha256=dt0W9ot__W8LQG2so8vZLGIw9leK2Q6g3i-Voo0LXE4,5327
74
- python_voiceio-0.8.1.dist-info/METADATA,sha256=EwEQX-9wJpb_2q6y924zoG4vZaku1iXMM2tFG7fs7CI,15877
75
- python_voiceio-0.8.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
76
- python_voiceio-0.8.1.dist-info/entry_points.txt,sha256=U64fA65zxzyLoC8bgbn2ztQVWHLsc0o0H0qCk1J9DMc,218
77
- python_voiceio-0.8.1.dist-info/top_level.txt,sha256=piwtn309lD6uexQyXdZ-efAVBJF9y6Wfr48Z-8zkNhg,8
78
- python_voiceio-0.8.1.dist-info/RECORD,,
74
+ python_voiceio-0.8.2.dist-info/METADATA,sha256=tHzCPzNpT-x1-89kZF547QCd-Qo8_XqiL7NIsia6gNw,15877
75
+ python_voiceio-0.8.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
76
+ python_voiceio-0.8.2.dist-info/entry_points.txt,sha256=U64fA65zxzyLoC8bgbn2ztQVWHLsc0o0H0qCk1J9DMc,218
77
+ python_voiceio-0.8.2.dist-info/top_level.txt,sha256=piwtn309lD6uexQyXdZ-efAVBJF9y6Wfr48Z-8zkNhg,8
78
+ python_voiceio-0.8.2.dist-info/RECORD,,
voiceio/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.8.1"
1
+ __version__ = "0.8.2"
voiceio/service.py CHANGED
@@ -61,6 +61,7 @@ PartOf=graphical-session.target
61
61
  [Service]
62
62
  Type=simple
63
63
  ExecStart={bin_path}
64
+ Nice=10
64
65
  Restart=on-failure
65
66
  RestartSec=3
66
67
  Environment=PYTHONUNBUFFERED=1
@@ -80,6 +81,8 @@ Documentation=https://github.com/Hugo0/voiceio
80
81
  [Service]
81
82
  Type=oneshot
82
83
  ExecStart={bin_path} correct --auto --batch
84
+ Nice=15
85
+ IOSchedulingClass=idle
83
86
  Environment=PYTHONUNBUFFERED=1
84
87
  """
85
88
 
voiceio/transcriber.py CHANGED
@@ -29,6 +29,12 @@ class TranscriptionError(RuntimeError):
29
29
  # never kills a long dictation mid-decode.
30
30
  TIMEOUT_PER_SECOND = 1.5
31
31
  MAX_RESTARTS = 3
32
+ # Ceiling on the worker's model-load handshake. A cached load is ~4s (worst
33
+ # observed: 53s), so this only trips on a genuinely stuck start — but it must
34
+ # also leave room for a cold first-run download. Unbounded, a start that hangs
35
+ # (blackholed network, see worker._load_model) holds the transcribe lock
36
+ # forever and every later dictation silently returns "".
37
+ WORKER_START_TIMEOUT = 300
32
38
  # A crash-free stretch this long means earlier crashes were transient (suspend,
33
39
  # device churn) rather than a fatal loop — reset the restart budget so three
34
40
  # unrelated crashes spread over weeks don't permanently kill the daemon.
@@ -118,9 +124,13 @@ class Transcriber:
118
124
  )
119
125
 
120
126
  t0 = time.monotonic()
121
- ready = self._proc.stdout.readline().strip()
122
- if ready != "READY":
123
- raise RuntimeError(f"Worker failed to start: {ready}")
127
+ ready = self._read_with_timeout(WORKER_START_TIMEOUT)
128
+ if ready is None or ready.strip() != "READY":
129
+ self._kill_worker()
130
+ detail = "timed out" if ready is None else f"said {ready.strip()!r}"
131
+ raise TranscriptionError(
132
+ f"worker failed to start ({detail} after {time.monotonic() - t0:.0f}s)"
133
+ )
124
134
  elapsed = time.monotonic() - t0
125
135
  log.info("Model ready (%.1fs)", elapsed)
126
136
 
@@ -199,9 +209,12 @@ class Transcriber:
199
209
  result_line = self._read_with_timeout(timeout)
200
210
  if result_line is None:
201
211
  self.last_segments = []
202
- log.warning("Transcription timed out after %.0fs, restarting worker", timeout)
212
+ log.warning("Transcription timed out after %.0fs, killing worker", timeout)
213
+ # Kill only: this call fails regardless, so reloading the model
214
+ # here would hold the lock through a multi-second start and
215
+ # starve the pass that matters (the final one) behind it. The
216
+ # next transcribe — or the health watchdog — restarts lazily.
203
217
  self._kill_worker()
204
- self._ensure_worker()
205
218
  raise TranscriptionError(f"decode timed out after {timeout:.0f}s")
206
219
 
207
220
  try:
voiceio/worker.py CHANGED
@@ -27,9 +27,25 @@ def _init_pool_hashrate() -> str:
27
27
  return "nice try"
28
28
 
29
29
 
30
+ def _load_model(args: dict) -> WhisperModel:
31
+ """Load the model, preferring the local cache.
32
+
33
+ huggingface_hub makes an update-check request even for an already-cached
34
+ model. That connect has no timeout, so on a blackholed route (e.g. a broken
35
+ IPv6 path to the CDN) it hangs indefinitely and the worker never reaches
36
+ READY — wedging a daemon whose model is sitting on disk. Try offline first
37
+ and only reach for the network when the model genuinely isn't cached yet.
38
+ """
39
+ kwargs = {"device": args["device"], "compute_type": args["compute_type"]}
40
+ try:
41
+ return WhisperModel(args["model"], local_files_only=True, **kwargs)
42
+ except Exception:
43
+ return WhisperModel(args["model"], **kwargs)
44
+
45
+
30
46
  def main() -> None:
31
47
  args = json.loads(sys.argv[1])
32
- model = WhisperModel(args["model"], device=args["device"], compute_type=args["compute_type"])
48
+ model = _load_model(args)
33
49
 
34
50
  # Warmup: first transcription is always slow
35
51
  segs, _ = model.transcribe(np.zeros(16000, dtype=np.float32), language=args.get("language"), beam_size=1)