python-voiceio 1.9.0__tar.gz → 1.9.2__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 (216) hide show
  1. {python_voiceio-1.9.0/python_voiceio.egg-info → python_voiceio-1.9.2}/PKG-INFO +1 -1
  2. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/pyproject.toml +1 -1
  3. {python_voiceio-1.9.0 → python_voiceio-1.9.2/python_voiceio.egg-info}/PKG-INFO +1 -1
  4. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_context_terms.py +38 -0
  5. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_streaming.py +62 -0
  6. python_voiceio-1.9.2/voiceio/__init__.py +1 -0
  7. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/app.py +1 -1
  8. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/config.py +6 -6
  9. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/context_terms.py +45 -7
  10. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/streaming.py +65 -26
  11. python_voiceio-1.9.0/voiceio/__init__.py +0 -1
  12. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/LICENSE +0 -0
  13. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/README.md +0 -0
  14. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/python_voiceio.egg-info/SOURCES.txt +0 -0
  15. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/python_voiceio.egg-info/dependency_links.txt +0 -0
  16. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/python_voiceio.egg-info/entry_points.txt +0 -0
  17. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/python_voiceio.egg-info/requires.txt +0 -0
  18. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/python_voiceio.egg-info/top_level.txt +0 -0
  19. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/setup.cfg +0 -0
  20. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_app_wiring.py +0 -0
  21. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_audio_quality.py +0 -0
  22. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_audio_source.py +0 -0
  23. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_backend_probes.py +0 -0
  24. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_cancel.py +0 -0
  25. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_cli.py +0 -0
  26. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_clipboard_read.py +0 -0
  27. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_clipboard_typer.py +0 -0
  28. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_commands.py +0 -0
  29. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_concurrency_lockdown.py +0 -0
  30. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_config.py +0 -0
  31. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_corrections.py +0 -0
  32. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_decoders.py +0 -0
  33. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_engine.py +0 -0
  34. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_evdev_uaccess.py +0 -0
  35. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_fallback.py +0 -0
  36. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_fcitx_bridge.py +0 -0
  37. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_feedback_stream.py +0 -0
  38. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_focus.py +0 -0
  39. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_health.py +0 -0
  40. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_hints.py +0 -0
  41. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_history.py +0 -0
  42. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_hotkey_modes.py +0 -0
  43. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_ibus_engine_limits.py +0 -0
  44. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_ibus_engine_listener.py +0 -0
  45. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_ibus_pending.py +0 -0
  46. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_ibus_pids.py +0 -0
  47. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_ibus_ping.py +0 -0
  48. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_ibus_session.py +0 -0
  49. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_ibus_textlimit.py +0 -0
  50. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_ibus_typer.py +0 -0
  51. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_idle_audio.py +0 -0
  52. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_learn_clean.py +0 -0
  53. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_learn_import.py +0 -0
  54. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_learn_language.py +0 -0
  55. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_learn_metrics.py +0 -0
  56. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_learn_pipeline.py +0 -0
  57. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_learn_quiet.py +0 -0
  58. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_learn_review.py +0 -0
  59. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_learn_targets.py +0 -0
  60. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_llm.py +0 -0
  61. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_llm_api.py +0 -0
  62. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_modifier_guard.py +0 -0
  63. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_numbers.py +0 -0
  64. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_paste_last.py +0 -0
  65. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_pipeline.py +0 -0
  66. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_platform.py +0 -0
  67. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_postcorrect.py +0 -0
  68. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_postprocess.py +0 -0
  69. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_prebuffer.py +0 -0
  70. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_prompt.py +0 -0
  71. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_quiet_audio.py +0 -0
  72. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_recorder_integration.py +0 -0
  73. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_retention.py +0 -0
  74. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_robustness.py +0 -0
  75. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_security_hardening.py +0 -0
  76. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_server.py +0 -0
  77. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_service.py +0 -0
  78. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_setup.py +0 -0
  79. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_suggest.py +0 -0
  80. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_tokens.py +0 -0
  81. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_transcriber.py +0 -0
  82. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_tts.py +0 -0
  83. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_typer_manager.py +0 -0
  84. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_vad.py +0 -0
  85. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_vocabulary.py +0 -0
  86. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_wordfreq.py +0 -0
  87. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/tests/test_worker_decode.py +0 -0
  88. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/__main__.py +0 -0
  89. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/audio_source.py +0 -0
  90. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/backends.py +0 -0
  91. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/cli/__init__.py +0 -0
  92. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/cli/configcmd.py +0 -0
  93. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/cli/corrections.py +0 -0
  94. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/cli/doctor.py +0 -0
  95. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/cli/history.py +0 -0
  96. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/cli/learn.py +0 -0
  97. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/cli/models.py +0 -0
  98. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/cli/servicecmd.py +0 -0
  99. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/cli/uninstall.py +0 -0
  100. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/cli/vocab.py +0 -0
  101. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/clipboard_read.py +0 -0
  102. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/commands.py +0 -0
  103. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/consent.py +0 -0
  104. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/__init__.py +0 -0
  105. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/bias.py +0 -0
  106. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/decoders/__init__.py +0 -0
  107. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/decoders/base.py +0 -0
  108. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/decoders/catalog.py +0 -0
  109. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
  110. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
  111. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/decoders/lanes.py +0 -0
  112. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/decoders/sherpa.py +0 -0
  113. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/decoders/whispercpp.py +0 -0
  114. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/engine.py +0 -0
  115. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/core/pipeline.py +0 -0
  116. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/corrections.py +0 -0
  117. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/data/60-voiceio-uaccess.rules +0 -0
  118. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/data/__init__.py +0 -0
  119. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/demo.py +0 -0
  120. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/display.py +0 -0
  121. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/feedback.py +0 -0
  122. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/focus.py +0 -0
  123. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/health.py +0 -0
  124. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/hints.py +0 -0
  125. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/history.py +0 -0
  126. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/hotkeys/__init__.py +0 -0
  127. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/hotkeys/base.py +0 -0
  128. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/hotkeys/chain.py +0 -0
  129. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/hotkeys/evdev.py +0 -0
  130. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/hotkeys/pynput_backend.py +0 -0
  131. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/hotkeys/socket_backend.py +0 -0
  132. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/ibus/__init__.py +0 -0
  133. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/ibus/engine.py +0 -0
  134. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/ibus/install.py +0 -0
  135. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/ibus/pending.py +0 -0
  136. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/ibus/session.py +0 -0
  137. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/ibus/textlimit.py +0 -0
  138. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/idle_audio.py +0 -0
  139. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/learn/__init__.py +0 -0
  140. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/learn/augment.py +0 -0
  141. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/learn/clean.py +0 -0
  142. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/learn/evaluate.py +0 -0
  143. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/learn/importer.py +0 -0
  144. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/learn/metrics.py +0 -0
  145. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/learn/quiet.py +0 -0
  146. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/learn/review.py +0 -0
  147. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/learn/store.py +0 -0
  148. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/learn/teacher.py +0 -0
  149. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/learn/train.py +0 -0
  150. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/llm.py +0 -0
  151. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/llm_api.py +0 -0
  152. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/models/__init__.py +0 -0
  153. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/models/silero_vad.onnx +0 -0
  154. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/numbers.py +0 -0
  155. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/pidlock.py +0 -0
  156. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/platform.py +0 -0
  157. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/postcorrect.py +0 -0
  158. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/postprocess.py +0 -0
  159. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/power.py +0 -0
  160. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/privacy.py +0 -0
  161. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/prompt.py +0 -0
  162. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/quiet_audio.py +0 -0
  163. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/recorder.py +0 -0
  164. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/retention.py +0 -0
  165. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/server/__init__.py +0 -0
  166. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/server/__main__.py +0 -0
  167. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/server/app.py +0 -0
  168. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/server/config.py +0 -0
  169. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/server/runtime.py +0 -0
  170. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/service.py +0 -0
  171. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/setup/__init__.py +0 -0
  172. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/setup/configfile.py +0 -0
  173. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/setup/extras.py +0 -0
  174. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/setup/hotkey.py +0 -0
  175. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/setup/noninteractive.py +0 -0
  176. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/setup/speech.py +0 -0
  177. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/setup/steps.py +0 -0
  178. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/setup/system.py +0 -0
  179. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/setup/ui.py +0 -0
  180. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/sounds/__init__.py +0 -0
  181. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/sounds/commit.wav +0 -0
  182. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/sounds/start.wav +0 -0
  183. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/sounds/stop.wav +0 -0
  184. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/suggest.py +0 -0
  185. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tokens.py +0 -0
  186. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tray/__init__.py +0 -0
  187. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tray/_icons.py +0 -0
  188. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tray/_indicator.py +0 -0
  189. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tray/_pystray.py +0 -0
  190. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tts/__init__.py +0 -0
  191. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tts/base.py +0 -0
  192. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tts/chain.py +0 -0
  193. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tts/edge_engine.py +0 -0
  194. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tts/espeak.py +0 -0
  195. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tts/piper_engine.py +0 -0
  196. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/tts/player.py +0 -0
  197. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/__init__.py +0 -0
  198. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/base.py +0 -0
  199. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/chain.py +0 -0
  200. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/clipboard.py +0 -0
  201. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/collecting.py +0 -0
  202. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/fcitx_bridge.py +0 -0
  203. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/ibus.py +0 -0
  204. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/manager.py +0 -0
  205. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/modifier_guard.py +0 -0
  206. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/paste.py +0 -0
  207. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/pynput_type.py +0 -0
  208. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/wtype.py +0 -0
  209. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/xdotool.py +0 -0
  210. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/typers/ydotool.py +0 -0
  211. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/ui.py +0 -0
  212. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/vad.py +0 -0
  213. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/vocab_stats.py +0 -0
  214. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/vocabulary.py +0 -0
  215. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/watchdog.py +0 -0
  216. {python_voiceio-1.9.0 → python_voiceio-1.9.2}/voiceio/wordfreq.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 1.9.0
3
+ Version: 1.9.2
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 = "1.9.0"
7
+ version = "1.9.2"
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: 1.9.0
3
+ Version: 1.9.2
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -251,3 +251,41 @@ class TestDaemon:
251
251
  v.on_hotkey()
252
252
  time.sleep(0.1)
253
253
  cap.assert_not_called()
254
+
255
+
256
+ def _fake_tmux(outputs):
257
+ calls = []
258
+
259
+ def run(args, timeout):
260
+ calls.append(args)
261
+ return outputs.get(args[0], "")
262
+ return run, calls
263
+
264
+
265
+ def test_tmux_session_names_become_words(monkeypatch):
266
+ """Session names are the terminal's tabs: their slug parts are terms."""
267
+ from voiceio import context_terms as c
268
+ run, calls = _fake_tmux({"list-sessions": "s claude-swarmmemo-live\ns claude-wordle-native\nc %1\nc %2\n"})
269
+ monkeypatch.setattr(c, "_tmux", run)
270
+ monkeypatch.setattr(c.shutil, "which", lambda name: "/usr/bin/tmux")
271
+ text = c._read_tmux(0.2)
272
+ assert "swarmmemo" in text.split() and "claude-swarmmemo-live" not in text
273
+ # Two clients: tmux can't say which one has focus, so no pane is read.
274
+ assert not any(a[0] == "capture-pane" for a in calls)
275
+ assert "swarmmemo" in c.extract_terms([text])
276
+
277
+
278
+ def test_tmux_pane_read_only_with_a_single_client(monkeypatch):
279
+ from voiceio import context_terms as c
280
+ run, calls = _fake_tmux({"list-sessions": "s work\nc %3\n", "capture-pane": "run xdotool now\n"})
281
+ monkeypatch.setattr(c, "_tmux", run)
282
+ monkeypatch.setattr(c.shutil, "which", lambda name: "/usr/bin/tmux")
283
+ text = c._read_tmux(0.2)
284
+ assert ["capture-pane", "-p", "-J", "-t", "%3"] in calls
285
+ assert "xdotool" in text
286
+
287
+
288
+ def test_no_tmux_no_terminal_text(monkeypatch):
289
+ from voiceio import context_terms as c
290
+ monkeypatch.setattr(c.shutil, "which", lambda name: None)
291
+ assert c._read_tmux(0.2) == ""
@@ -693,6 +693,67 @@ class TestSegmentFreeze:
693
693
  # Display untouched: interim text already covers this audio
694
694
  s._display.typer.type_text.assert_not_called()
695
695
 
696
+ def test_pause_freezes_tail_up_to_the_reported_pause(self):
697
+ from voiceio.streaming import _PAUSE_CUT_BACKOFF_SECS
698
+ s, tr, rec = self._make_session(freeze_secs=8.0)
699
+ audio = np.concatenate([_loud(4.5), _silent(0.7)])
700
+ rec.get_audio_so_far.return_value = audio
701
+ s._on_vad_pause() # the recorder's VAD heard a pause here
702
+ rec.get_audio_so_far.return_value = np.concatenate([audio, _loud(0.5)])
703
+ tr.transcribe.return_value = "hello there friend"
704
+ s._transcribe_and_apply()
705
+ # Frozen long before freeze_secs, cut inside the pause
706
+ assert s._frozen_samples == len(audio) - int(_PAUSE_CUT_BACKOFF_SECS * SR)
707
+ assert s._frozen_raw == "hello there friend"
708
+ assert tr.transcribe.call_args.kwargs["final"] is True
709
+ assert len(tr.transcribe.call_args.args[0]) == s._frozen_samples
710
+ assert s.trace[-1]["cut"] == "pause"
711
+ s._display.typer.type_text.assert_not_called()
712
+
713
+ def test_pause_freeze_ignores_backpressure(self):
714
+ s, tr, rec = self._make_session(freeze_secs=8.0)
715
+ audio = np.concatenate([_loud(4.5), _silent(0.7)])
716
+ rec.get_audio_so_far.return_value = audio
717
+ s._last_decode_end = len(audio) # an interim just covered all of it
718
+ s._last_decode_secs = 5.0
719
+ s._on_vad_pause()
720
+ tr.transcribe.return_value = "hello there friend"
721
+ s._transcribe_and_apply()
722
+ assert s._frozen_raw == "hello there friend"
723
+
724
+ def test_pause_after_too_little_speech_is_not_frozen(self):
725
+ from voiceio.streaming import _PAUSE_FREEZE_MIN_SECS
726
+ s, tr, rec = self._make_session(freeze_secs=8.0)
727
+ rec.get_audio_so_far.return_value = np.concatenate(
728
+ [_loud(_PAUSE_FREEZE_MIN_SECS - 1), _silent(0.7)])
729
+ s._on_vad_pause()
730
+ tr.transcribe.return_value = "hello"
731
+ s._transcribe_and_apply()
732
+ assert s._frozen_samples == 0
733
+ assert tr.transcribe.call_args.kwargs["final"] is False # an interim
734
+ assert s._pause_at is None # consumed: a later pause is judged afresh
735
+
736
+ def test_no_pause_freeze_when_freezing_disabled(self):
737
+ s, tr, rec = self._make_session(freeze_secs=0)
738
+ rec.get_audio_so_far.return_value = np.concatenate([_loud(6), _silent(0.7)])
739
+ s._on_vad_pause()
740
+ tr.transcribe.return_value = "hello there friend"
741
+ s._transcribe_and_apply()
742
+ assert s._frozen_samples == 0
743
+
744
+ def test_stop_after_pause_freeze_decodes_only_the_rest(self):
745
+ s, tr, rec = self._make_session(freeze_secs=8.0)
746
+ audio = np.concatenate([_loud(4.5), _silent(0.7)])
747
+ rec.get_audio_so_far.return_value = audio
748
+ s._on_vad_pause()
749
+ tr.transcribe.return_value = "hello there friend"
750
+ s._transcribe_and_apply()
751
+ final_audio = np.concatenate([audio, _loud(1.0)])
752
+ tr.transcribe.return_value = "bye"
753
+ s.stop(final_audio)
754
+ assert len(tr.transcribe.call_args.args[0]) == len(final_audio) - s._frozen_samples
755
+ assert s.raw_final_text == "hello there friend bye"
756
+
696
757
  def test_no_freeze_when_tail_is_speech(self):
697
758
  s, tr, rec = self._make_session(freeze_secs=2.0)
698
759
  rec.get_audio_so_far.return_value = _loud(3)
@@ -1073,3 +1134,4 @@ def test_stop_decodes_tail_in_parallel_with_inflight_freeze():
1073
1134
  release_freeze.set()
1074
1135
  stopper.join(5)
1075
1136
  assert ("result", "frozen part tail part") in calls
1137
+
@@ -0,0 +1 @@
1
+ __version__ = "1.9.2"
@@ -643,7 +643,7 @@ class VoiceIO:
643
643
  screen = context_terms.capture()
644
644
  if self.cfg.data.capture_context:
645
645
  self._context_title = screen.title or None
646
- terms = self.engine.screen_terms([screen.title, screen.selection])
646
+ terms = self.engine.screen_terms(screen.texts())
647
647
  if not terms:
648
648
  return
649
649
  bias = self.engine.bias(screen_terms=terms)
@@ -144,12 +144,12 @@ class OutputConfig:
144
144
  # On by default: nobody wants "um" in their notes. Set false for verbatim.
145
145
  remove_disfluencies: bool = True
146
146
  voice_input_prefix: str = "" # e.g. "[voice]" — empty disables
147
- # Incremental finalization: once the un-finalized audio tail grows past
148
- # this many seconds, it is cut at the nearest interior speech pause,
149
- # beam-decoded and frozen during recording, so interim passes and the
150
- # stop-time final decode only cover the short remaining tail. Smaller
151
- # chunks decode faster (less worker blocking) at slightly more boundary
152
- # risk. 0 disables (full re-decode at stop).
147
+ # Incremental finalization: during recording the audio is beam-decoded
148
+ # and frozen in chunks — at each speech pause the VAD reports, and,
149
+ # when no pause comes, once the un-finalized tail grows past this many
150
+ # seconds (cut at its quietest interior point) — so interim passes and
151
+ # the stop-time final decode only cover the short remaining tail.
152
+ # 0 disables both (full re-decode at stop).
153
153
  streaming_freeze_secs: float = 8.0
154
154
  # Mirror transcribed text to the system clipboard so it can be pasted:
155
155
  # "off" — never
@@ -1,8 +1,10 @@
1
1
  """On-screen vocabulary: bias one dictation toward the names you can see.
2
2
 
3
3
  Most misrecognitions are proper nouns and jargon, and the word you are about
4
- to say is often already on screen: in the focused window's title or in the
5
- text you just selected. This reads those two sources when a recording starts,
4
+ to say is often already on screen: in the focused window's title, in the
5
+ text you just selected, or in the terminal you are working in (tmux: the
6
+ visible pane and the session names, which are often the window's tabs). This
7
+ reads those sources when a recording starts,
6
8
  picks out the terms Whisper is likely to miss (rare words, CamelCase,
7
9
  acronyms, multiword names) and hands them to `core.bias` for this one
8
10
  recording, inside a small share of the hotword budget.
@@ -30,9 +32,9 @@ from dataclasses import dataclass
30
32
 
31
33
  log = logging.getLogger(__name__)
32
34
 
33
- # Capture deadline. Long enough for two ~20 ms subprocess reads under load,
35
+ # Capture deadline. Long enough for a few ~20 ms subprocess reads under load,
34
36
  # short enough that the terms are in place before the first interim pass.
35
- CAPTURE_TIMEOUT_SECS = 0.15
37
+ CAPTURE_TIMEOUT_SECS = 0.25
36
38
  # Only the head of a large selection is scanned: extraction stays ~10 ms.
37
39
  MAX_SCAN_CHARS = 8000
38
40
  MAX_TERMS = 8
@@ -54,10 +56,16 @@ _MIXED = re.compile(r"[a-zà-ÿ][A-ZÀ-Þ]|[A-ZÀ-Þ]{2}[a-zà-ÿ]")
54
56
  class ScreenContext:
55
57
  title: str = ""
56
58
  selection: str = ""
59
+ terminal: str = ""
57
60
  secs: float = 0.0
58
61
 
62
+ def texts(self) -> list[str]:
63
+ """In priority order: what was selected on purpose first."""
64
+ return [self.selection, self.title, self.terminal]
65
+
59
66
  def sources(self) -> list[str]:
60
- return [n for n, v in (("title", self.title), ("selection", self.selection)) if v]
67
+ return [n for n, v in (("title", self.title), ("selection", self.selection),
68
+ ("terminal", self.terminal)) if v]
61
69
 
62
70
 
63
71
  def _kind(word: str, zipf: Callable[[str], float]) -> int:
@@ -205,6 +213,33 @@ def _read_primary(timeout: float) -> str:
205
213
  return out.stdout[:MAX_SCAN_CHARS * 4].decode("utf-8", "replace")
206
214
 
207
215
 
216
+ def _tmux(args: list[str], timeout: float) -> str:
217
+ try:
218
+ out = subprocess.run(["tmux", *args], capture_output=True, timeout=timeout)
219
+ except (subprocess.TimeoutExpired, OSError):
220
+ return ""
221
+ return out.stdout.decode("utf-8", "replace") if out.returncode == 0 else ""
222
+
223
+
224
+ def _read_tmux(timeout: float) -> str:
225
+ """tmux session names (often the terminal's tabs, so on screen) and, when
226
+ exactly one client is attached, its visible pane (the newest end). With
227
+ several clients tmux can't say which one has focus — activity times move
228
+ with output too — so their panes would be a guess."""
229
+ if not shutil.which("tmux"):
230
+ return ""
231
+ # One tmux call for both lists: each call is a process start (~20 ms).
232
+ out = _tmux(["list-sessions", "-F", "s #{session_name}", ";",
233
+ "list-clients", "-F", "c #{pane_id}"], timeout).splitlines()
234
+ # "claude-wordle-native" → words; slugs are names joined for the shell.
235
+ sessions = "\n".join(re.sub(r"[-_.]+", " ", line[2:]) for line in out if line.startswith("s "))
236
+ panes = [line[2:] for line in out if line.startswith("c ")]
237
+ if len(panes) != 1:
238
+ return sessions
239
+ text = _tmux(["capture-pane", "-p", "-J", "-t", panes[0]], timeout).rstrip()
240
+ return sessions + "\n" + text[-MAX_SCAN_CHARS // 2:]
241
+
242
+
208
243
  def _read_title(_timeout: float) -> str:
209
244
  from voiceio import retention
210
245
  return retention.active_window_title() or ""
@@ -214,7 +249,8 @@ def capture(timeout: float = CAPTURE_TIMEOUT_SECS, *,
214
249
  readers: dict[str, Callable[[float], str]] | None = None) -> ScreenContext:
215
250
  """Read the window title and selection in parallel; whatever isn't back
216
251
  by `timeout` is dropped (its thread finishes on its own). Never raises."""
217
- readers = readers or {"title": _read_title, "selection": _read_primary}
252
+ readers = readers or {"title": _read_title, "selection": _read_primary,
253
+ "terminal": _read_tmux}
218
254
  t0 = time.monotonic()
219
255
  results: dict[str, str] = {}
220
256
 
@@ -233,7 +269,7 @@ def capture(timeout: float = CAPTURE_TIMEOUT_SECS, *,
233
269
  t.join(max(0.0, deadline - time.monotonic()))
234
270
  got = dict(results) # snapshot: a late reader must not change it
235
271
  return ScreenContext(title=got.get("title", ""), selection=got.get("selection", ""),
236
- secs=time.monotonic() - t0)
272
+ terminal=got.get("terminal", ""), secs=time.monotonic() - t0)
237
273
 
238
274
 
239
275
  def describe() -> str:
@@ -250,4 +286,6 @@ def describe() -> str:
250
286
  sources.append("window title")
251
287
  elif p.is_wayland and p.desktop in ("gnome", "kde"):
252
288
  sources.append("window title of X11 apps only")
289
+ if shutil.which("tmux"):
290
+ sources.append("tmux sessions")
253
291
  return ", ".join(sources) or "no source available"
@@ -46,6 +46,15 @@ _FREEZE_SILENCE_FLOOR = 1e-4 # digital silence is always quiet
46
46
  # spot beats re-decoding an ever-growing tail at stop (observed: 17 long
47
47
  # dictations never froze, one decoding 121s of audio after stop).
48
48
  _FREEZE_SEARCH_SECS = 8.0
49
+ # Pause freezes: when the recorder reports a speech pause (its VAD heard
50
+ # silence_duration of non-speech), the tail up to that pause is frozen as soon
51
+ # as it holds this much audio, rather than waiting for freeze_secs. Stop then
52
+ # decodes only the speech after the last pause instead of up to
53
+ # freeze_secs + search span of it.
54
+ _PAUSE_FREEZE_MIN_SECS = 4.0
55
+ # The pause is reported once the silence has lasted a while: cut this far
56
+ # before that point so the cut sits inside the silence, not at its edge.
57
+ _PAUSE_CUT_BACKOFF_SECS = 0.3
49
58
  # Whisper conditions on ~224 prompt tokens; more frozen context is wasted.
50
59
  _FREEZE_CONTEXT_CHARS = 400
51
60
 
@@ -197,6 +206,8 @@ class StreamingSession:
197
206
  # been beam-decoded into _frozen_raw and is never decoded again.
198
207
  # Interim and final passes only decode the tail after this offset.
199
208
  self._freeze_secs = freeze_secs
209
+ # Sample offset of the latest speech pause the recorder reported.
210
+ self._pause_at: int | None = None
200
211
  self._frozen_samples = 0
201
212
  self._frozen_raw = ""
202
213
  self._frozen_segments: list[dict] = []
@@ -318,7 +329,12 @@ class StreamingSession:
318
329
  return None
319
330
 
320
331
  def _on_vad_pause(self) -> None:
321
- """Called from audio thread on speech pause. Signals worker."""
332
+ """Called from audio thread on speech pause. Signals worker, and
333
+ remembers where the pause is: a clean place to freeze."""
334
+ recorder = self._recorder
335
+ audio = recorder.get_audio_so_far() if recorder is not None else None
336
+ if audio is not None:
337
+ self._pause_at = len(audio)
322
338
  self._pending.set()
323
339
 
324
340
  def _worker_loop(self) -> None:
@@ -353,7 +369,28 @@ class StreamingSession:
353
369
  context = self._frozen_raw[-_FREEZE_CONTEXT_CHARS:] if self._frozen_raw else None
354
370
  return self._bias.with_context(context)
355
371
 
356
- def _freeze_chunk(self, audio: np.ndarray, chunk: np.ndarray) -> None:
372
+ def _pause_cut(self, tail_len: int) -> int | None:
373
+ """Tail offset to freeze at: the latest reported speech pause, once
374
+ the speech before it is long enough to decode on its own."""
375
+ pause, self._pause_at = self._pause_at, None
376
+ if self._freeze_secs <= 0 or pause is None:
377
+ return None
378
+ # Cut inside the pause, not at its end: speech may resume right there.
379
+ cut = pause - self._frozen_samples - int(_PAUSE_CUT_BACKOFF_SECS * self._sample_rate)
380
+ if cut < _PAUSE_FREEZE_MIN_SECS * self._sample_rate or cut > tail_len:
381
+ return None
382
+ return cut
383
+
384
+ def _long_tail_cut(self, tail: np.ndarray) -> int | None:
385
+ """Tail offset to freeze at once the tail outgrows freeze_secs: its
386
+ quietest interior window (see _find_freeze_cut)."""
387
+ if self._freeze_secs <= 0 or len(tail) < self._freeze_secs * self._sample_rate:
388
+ return None
389
+ # Past the whole search span with no pause found: force a cut.
390
+ force = len(tail) >= (self._freeze_secs + _FREEZE_SEARCH_SECS) * self._sample_rate
391
+ return _find_freeze_cut(tail, self._sample_rate, self._freeze_secs, force=force)
392
+
393
+ def _freeze_chunk(self, audio: np.ndarray, chunk: np.ndarray, cut: str = "long") -> None:
357
394
  """Beam-decode `chunk` (the tail up to a pause cut) and freeze it.
358
395
 
359
396
  On decode failure the boundary is left untouched — the audio will be
@@ -375,6 +412,7 @@ class StreamingSession:
375
412
  self.trace.append({
376
413
  "t": round(time.monotonic() - self._t0, 2),
377
414
  "kind": "freeze",
415
+ "cut": cut,
378
416
  "frozen_secs": round(self._frozen_samples / self._sample_rate, 2),
379
417
  "tail_secs": round(len(chunk) / self._sample_rate, 2),
380
418
  "secs": round(t_decode, 3),
@@ -411,30 +449,31 @@ class StreamingSession:
411
449
  tail = audio[self._frozen_samples:]
412
450
  if not final and len(tail) < self._sample_rate * min_seconds:
413
451
  return # not enough new audio since the last freeze
414
- grown_secs = (len(audio) - self._last_decode_end) / self._sample_rate
415
- if not final and grown_secs < max(min_seconds, self._last_decode_secs):
416
- return # backpressure: don't decode faster than we can keep up
417
-
418
- # Freeze: once the tail is long enough, cut it at an interior speech
419
- # pause and beam-decode ONLY that chunk (its text is never decoded
420
- # again). The freeze pass does not touch the display — the interim
421
- # text already covers this audio; the next interim refreshes it.
422
- if (not final and self._freeze_secs > 0
423
- and len(tail) >= self._freeze_secs * self._sample_rate):
424
- # Past the whole search span with no pause found: force a cut.
425
- force = len(tail) >= (self._freeze_secs + _FREEZE_SEARCH_SECS) * self._sample_rate
426
- cut = _find_freeze_cut(tail, self._sample_rate, self._freeze_secs, force=force)
427
- if cut is not None:
428
- with self._freeze_lock:
429
- # Stop may have won the race for the lock and already
430
- # finalized from the current boundary.
431
- if not self._stop_event.is_set():
432
- self._freeze_pending_end = self._frozen_samples + cut
433
- try:
434
- self._freeze_chunk(audio, tail[:cut])
435
- finally:
436
- self._freeze_pending_end = None
437
- return
452
+
453
+ # Freeze: cut the tail at a speech pause and beam-decode ONLY that
454
+ # chunk (its text is never decoded again). The freeze pass does not
455
+ # touch the display — the interim text already covers this audio;
456
+ # the next interim refreshes it.
457
+ # A pause the recorder reported is frozen even under backpressure:
458
+ # it shrinks every later decode, the stop-time one included.
459
+ cut = None if final else self._pause_cut(len(tail))
460
+ reason = "pause"
461
+ if cut is None and not final:
462
+ grown_secs = (len(audio) - self._last_decode_end) / self._sample_rate
463
+ if grown_secs < max(min_seconds, self._last_decode_secs):
464
+ return # backpressure: don't decode faster than we can keep up
465
+ cut, reason = self._long_tail_cut(tail), "long"
466
+ if cut is not None:
467
+ with self._freeze_lock:
468
+ # Stop may have won the race for the lock and already
469
+ # finalized from the current boundary.
470
+ if not self._stop_event.is_set():
471
+ self._freeze_pending_end = self._frozen_samples + cut
472
+ try:
473
+ self._freeze_chunk(audio, tail[:cut], reason)
474
+ finally:
475
+ self._freeze_pending_end = None
476
+ return
438
477
 
439
478
  tail_text = ""
440
479
  tail_segments: list[dict] = []
@@ -1 +0,0 @@
1
- __version__ = "1.9.0"
File without changes
File without changes
File without changes