python-voiceio 1.14.0__tar.gz → 1.15.0__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 (232) hide show
  1. {python_voiceio-1.14.0/python_voiceio.egg-info → python_voiceio-1.15.0}/PKG-INFO +1 -1
  2. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/pyproject.toml +1 -1
  3. {python_voiceio-1.14.0 → python_voiceio-1.15.0/python_voiceio.egg-info}/PKG-INFO +1 -1
  4. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_learn_pipeline.py +26 -0
  5. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_presence.py +1 -0
  6. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_server.py +35 -7
  7. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_sherpa.py +6 -1
  8. python_voiceio-1.15.0/voiceio/__init__.py +1 -0
  9. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/cli/learn.py +2 -0
  10. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/decoders/sherpa.py +5 -0
  11. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/presence.py +4 -1
  12. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/server/__main__.py +1 -0
  13. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/server/app.py +25 -12
  14. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/server/config.py +4 -0
  15. python_voiceio-1.14.0/voiceio/__init__.py +0 -1
  16. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/LICENSE +0 -0
  17. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/README.md +0 -0
  18. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/python_voiceio.egg-info/SOURCES.txt +0 -0
  19. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/python_voiceio.egg-info/dependency_links.txt +0 -0
  20. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/python_voiceio.egg-info/entry_points.txt +0 -0
  21. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/python_voiceio.egg-info/requires.txt +0 -0
  22. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/python_voiceio.egg-info/top_level.txt +0 -0
  23. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/setup.cfg +0 -0
  24. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_announce.py +0 -0
  25. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_app_wiring.py +0 -0
  26. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_audio_quality.py +0 -0
  27. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_audio_source.py +0 -0
  28. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_backend_probes.py +0 -0
  29. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_cancel.py +0 -0
  30. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_cli.py +0 -0
  31. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_clipboard_read.py +0 -0
  32. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_clipboard_typer.py +0 -0
  33. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_commands.py +0 -0
  34. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_concurrency_lockdown.py +0 -0
  35. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_config.py +0 -0
  36. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_context_terms.py +0 -0
  37. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_corrections.py +0 -0
  38. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_dead_mic.py +0 -0
  39. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_decoders.py +0 -0
  40. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_engine.py +0 -0
  41. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_evdev_uaccess.py +0 -0
  42. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_fallback.py +0 -0
  43. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_fcitx_bridge.py +0 -0
  44. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_feedback_stream.py +0 -0
  45. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_focus.py +0 -0
  46. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_health.py +0 -0
  47. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_hints.py +0 -0
  48. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_history.py +0 -0
  49. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_hotkey_modes.py +0 -0
  50. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_ibus_engine_limits.py +0 -0
  51. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_ibus_engine_listener.py +0 -0
  52. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_ibus_pending.py +0 -0
  53. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_ibus_pids.py +0 -0
  54. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_ibus_ping.py +0 -0
  55. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_ibus_session.py +0 -0
  56. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_ibus_textlimit.py +0 -0
  57. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_ibus_typer.py +0 -0
  58. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_idle_audio.py +0 -0
  59. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_learn_clean.py +0 -0
  60. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_learn_import.py +0 -0
  61. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_learn_language.py +0 -0
  62. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_learn_metrics.py +0 -0
  63. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_learn_quiet.py +0 -0
  64. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_learn_resume.py +0 -0
  65. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_learn_review.py +0 -0
  66. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_learn_targets.py +0 -0
  67. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_llm.py +0 -0
  68. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_llm_api.py +0 -0
  69. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_mic_gain.py +0 -0
  70. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_modifier_guard.py +0 -0
  71. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_numbers.py +0 -0
  72. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_paste_last.py +0 -0
  73. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_pipeline.py +0 -0
  74. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_platform.py +0 -0
  75. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_postcorrect.py +0 -0
  76. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_postprocess.py +0 -0
  77. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_prebuffer.py +0 -0
  78. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_prompt.py +0 -0
  79. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_quiet_audio.py +0 -0
  80. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_recorder_integration.py +0 -0
  81. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_report.py +0 -0
  82. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_retention.py +0 -0
  83. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_robustness.py +0 -0
  84. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_security_hardening.py +0 -0
  85. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_service.py +0 -0
  86. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_setup.py +0 -0
  87. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_spool.py +0 -0
  88. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_streaming.py +0 -0
  89. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_suggest.py +0 -0
  90. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_tokens.py +0 -0
  91. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_transcriber.py +0 -0
  92. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_tts.py +0 -0
  93. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_typer_manager.py +0 -0
  94. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_vad.py +0 -0
  95. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_vocabulary.py +0 -0
  96. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_wordfreq.py +0 -0
  97. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/tests/test_worker_decode.py +0 -0
  98. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/__main__.py +0 -0
  99. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/announce.py +0 -0
  100. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/app.py +0 -0
  101. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/audio_source.py +0 -0
  102. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/backends.py +0 -0
  103. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/cli/__init__.py +0 -0
  104. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/cli/configcmd.py +0 -0
  105. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/cli/corrections.py +0 -0
  106. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/cli/doctor.py +0 -0
  107. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/cli/history.py +0 -0
  108. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/cli/models.py +0 -0
  109. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/cli/servicecmd.py +0 -0
  110. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/cli/uninstall.py +0 -0
  111. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/cli/vocab.py +0 -0
  112. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/clipboard_read.py +0 -0
  113. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/commands.py +0 -0
  114. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/config.py +0 -0
  115. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/consent.py +0 -0
  116. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/context_terms.py +0 -0
  117. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/__init__.py +0 -0
  118. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/bias.py +0 -0
  119. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/decoders/__init__.py +0 -0
  120. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/decoders/base.py +0 -0
  121. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/decoders/catalog.py +0 -0
  122. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
  123. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
  124. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/decoders/fetch.py +0 -0
  125. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/decoders/lanes.py +0 -0
  126. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/decoders/whispercpp.py +0 -0
  127. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/engine.py +0 -0
  128. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/core/pipeline.py +0 -0
  129. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/corrections.py +0 -0
  130. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/data/60-voiceio-uaccess.rules +0 -0
  131. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/data/__init__.py +0 -0
  132. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/demo.py +0 -0
  133. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/display.py +0 -0
  134. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/feedback.py +0 -0
  135. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/focus.py +0 -0
  136. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/health.py +0 -0
  137. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/hints.py +0 -0
  138. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/history.py +0 -0
  139. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/hotkeys/__init__.py +0 -0
  140. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/hotkeys/base.py +0 -0
  141. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/hotkeys/chain.py +0 -0
  142. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/hotkeys/evdev.py +0 -0
  143. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/hotkeys/pynput_backend.py +0 -0
  144. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/hotkeys/socket_backend.py +0 -0
  145. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/ibus/__init__.py +0 -0
  146. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/ibus/engine.py +0 -0
  147. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/ibus/install.py +0 -0
  148. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/ibus/pending.py +0 -0
  149. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/ibus/session.py +0 -0
  150. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/ibus/textlimit.py +0 -0
  151. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/idle_audio.py +0 -0
  152. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/learn/__init__.py +0 -0
  153. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/learn/augment.py +0 -0
  154. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/learn/clean.py +0 -0
  155. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/learn/evaluate.py +0 -0
  156. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/learn/importer.py +0 -0
  157. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/learn/metrics.py +0 -0
  158. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/learn/quiet.py +0 -0
  159. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/learn/review.py +0 -0
  160. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/learn/store.py +0 -0
  161. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/learn/teacher.py +0 -0
  162. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/learn/train.py +0 -0
  163. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/llm.py +0 -0
  164. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/llm_api.py +0 -0
  165. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/mic_gain.py +0 -0
  166. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/models/__init__.py +0 -0
  167. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/models/silero_vad.onnx +0 -0
  168. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/numbers.py +0 -0
  169. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/pidlock.py +0 -0
  170. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/platform.py +0 -0
  171. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/postcorrect.py +0 -0
  172. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/postprocess.py +0 -0
  173. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/power.py +0 -0
  174. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/privacy.py +0 -0
  175. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/prompt.py +0 -0
  176. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/quiet_audio.py +0 -0
  177. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/recorder.py +0 -0
  178. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/report.py +0 -0
  179. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/retention.py +0 -0
  180. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/server/__init__.py +0 -0
  181. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/server/runtime.py +0 -0
  182. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/service.py +0 -0
  183. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/setup/__init__.py +0 -0
  184. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/setup/configfile.py +0 -0
  185. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/setup/extras.py +0 -0
  186. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/setup/hotkey.py +0 -0
  187. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/setup/noninteractive.py +0 -0
  188. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/setup/speech.py +0 -0
  189. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/setup/steps.py +0 -0
  190. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/setup/system.py +0 -0
  191. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/setup/ui.py +0 -0
  192. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/sounds/__init__.py +0 -0
  193. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/sounds/alert.wav +0 -0
  194. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/sounds/commit.wav +0 -0
  195. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/sounds/start.wav +0 -0
  196. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/sounds/stop.wav +0 -0
  197. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/spool.py +0 -0
  198. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/streaming.py +0 -0
  199. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/suggest.py +0 -0
  200. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tokens.py +0 -0
  201. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tray/__init__.py +0 -0
  202. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tray/_icons.py +0 -0
  203. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tray/_indicator.py +0 -0
  204. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tray/_pystray.py +0 -0
  205. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tts/__init__.py +0 -0
  206. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tts/base.py +0 -0
  207. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tts/chain.py +0 -0
  208. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tts/edge_engine.py +0 -0
  209. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tts/espeak.py +0 -0
  210. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tts/piper_engine.py +0 -0
  211. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tts/player.py +0 -0
  212. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/tts/supertonic_engine.py +0 -0
  213. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/__init__.py +0 -0
  214. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/base.py +0 -0
  215. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/chain.py +0 -0
  216. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/clipboard.py +0 -0
  217. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/collecting.py +0 -0
  218. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/fcitx_bridge.py +0 -0
  219. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/ibus.py +0 -0
  220. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/manager.py +0 -0
  221. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/modifier_guard.py +0 -0
  222. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/paste.py +0 -0
  223. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/pynput_type.py +0 -0
  224. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/wtype.py +0 -0
  225. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/xdotool.py +0 -0
  226. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/typers/ydotool.py +0 -0
  227. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/ui.py +0 -0
  228. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/vad.py +0 -0
  229. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/vocab_stats.py +0 -0
  230. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/vocabulary.py +0 -0
  231. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/watchdog.py +0 -0
  232. {python_voiceio-1.14.0 → python_voiceio-1.15.0}/voiceio/wordfreq.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 1.14.0
3
+ Version: 1.15.0
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.14.0"
7
+ version = "1.15.0"
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.14.0
3
+ Version: 1.15.0
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -307,6 +307,32 @@ def test_status_shows_scores_and_suggests_a_winning_run(capsys):
307
307
  assert "next: voiceio learn promote v0001-small-x" in out
308
308
 
309
309
 
310
+ def test_status_never_suggests_whisper_over_another_decoder(capsys):
311
+ import argparse
312
+
313
+ from voiceio import config
314
+ from voiceio.cli import learn as cli
315
+ config.CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
316
+ config.CONFIG_PATH.write_text('[model]\nname = "parakeet-tdt-0.6b-v2"\n')
317
+ _clip("s.wav", 3, "x")
318
+ store.add_label("s.wav", [Segment(0, 3, "hi")], "teacher:fake")
319
+ ds = store.build_dataset()
320
+ run = store.learn_dir() / "models" / "v0001-small-x"
321
+ (run / "ct2").mkdir(parents=True)
322
+ runs = store.learn_dir() / "runs"
323
+ runs.mkdir()
324
+ for i, (dec, wer, rec) in enumerate([("sherpa:parakeet-tdt-0.6b-v2", 0.10, 0.5),
325
+ (f"faster-whisper:{run / 'ct2'}", 0.08, 0.8)]):
326
+ (runs / f"eval-2026010{i}.json").write_text(json.dumps({
327
+ "decoder": dec, "dataset": ds.name, "mode": "offline", "wer": wer,
328
+ "term_recall": rec, "repetitions": 0, "clips": 5, "gold": 0,
329
+ "metrics": metrics.METRICS_VERSION}))
330
+ cli.cmd_status(argparse.Namespace())
331
+ out = capsys.readouterr().out
332
+ assert "parakeet-tdt-0.6b-v2 (active)" in out and "WER 10.0%" in out
333
+ assert "next: voiceio learn promote" not in out
334
+
335
+
310
336
  def test_paired_comparison_reports_direction_and_uncertainty():
311
337
  clips = [f"{i}.wav" for i in range(20)]
312
338
  cur = {"per_clip": [{"file": f, "wer": 0.10} for f in clips]}
@@ -38,6 +38,7 @@ AWAY = 600
38
38
  (Probe(idle=1800), False, "away 30 min"),
39
39
  (Probe(idle=1800, inhibited=True), True, "keeping the session awake"), # a video
40
40
  (Probe(idle=1800, inhibited=True, locked=True), False, "away"),
41
+ (Probe(idle=3 * 3600, inhibited=True), False, "away 180 min"), # an agent, not a viewer
41
42
  # No idle monitor: logind decides, and "can't tell" means in use.
42
43
  (Probe(), True, "can't tell"),
43
44
  (Probe(session=SeatSession(exists=False)), False, "nobody is logged in"),
@@ -310,6 +310,7 @@ def test_ws_ready_partial_final_sequence():
310
310
  assert final["text"] == "Hello world."
311
311
  assert final["raw"] == "hello world raw"
312
312
  assert isinstance(final["ms"], int)
313
+ assert final["cut_short"] is False and "why" not in final
313
314
  assert engine.committed == ["Hello world."] # learned from; kept if opted in
314
315
 
315
316
 
@@ -348,8 +349,9 @@ def test_ws_client_disconnect_still_finalizes():
348
349
  assert engine.sessions[0].stopped_with is not None
349
350
 
350
351
 
351
- def test_ws_enforces_the_duration_cap():
352
+ def test_ws_duration_cap_finalizes_what_fits_marked_cut_short():
352
353
  cfg = ServerConfig(max_audio_secs=0.05) # 800 samples
354
+ engine = FakeEngine()
353
355
 
354
356
  async def go(client):
355
357
  ws = await client.ws_connect("/ws/transcribe")
@@ -361,11 +363,36 @@ def test_ws_enforces_the_duration_cap():
361
363
  await ws.close()
362
364
  return frames
363
365
 
364
- frames = run_with_client(go, cfg=cfg)
365
- kinds = [f["type"] for f in frames]
366
- assert "error" in kinds
367
- # An overrun must not also claim a usable final transcript.
368
- assert "final" not in kinds
366
+ frames = run_with_client(go, cfg=cfg, engine=engine)
367
+ final = frames[-1]
368
+ assert final["type"] == "final" and final["text"] == "Hello world."
369
+ # The words are kept, and the client is told they are not the whole note.
370
+ assert final["cut_short"] is True and "limit" in final["why"]
371
+ assert len(engine.sessions[0].stopped_with) == 800 # only what fits is decoded
372
+
373
+
374
+ def test_ws_stalled_stream_is_finalized_with_what_it_has():
375
+ """A client that goes quiet without a stop (a starved or frozen sender)
376
+ still gets a transcript of the audio it sent."""
377
+ cfg = ServerConfig(stall_secs=0.2)
378
+ engine = FakeEngine()
379
+
380
+ async def go(client):
381
+ ws = await client.ws_connect("/ws/transcribe")
382
+ await ws.receive_json()
383
+ await ws.send_bytes(_pcm(800))
384
+ frames = []
385
+ async for msg in ws: # no stop: the server ends it
386
+ frames.append(json.loads(msg.data))
387
+ await ws.close()
388
+ return frames
389
+
390
+ frames = run_with_client(go, cfg=cfg, engine=engine)
391
+ final = frames[-1]
392
+ assert final["type"] == "final" and final["text"] == "Hello world."
393
+ assert final["cut_short"] is True and "no audio or stop" in final["why"]
394
+ assert len(engine.sessions[0].stopped_with) == 800
395
+ assert engine.committed == ["Hello world."]
369
396
 
370
397
 
371
398
  def test_ws_second_concurrent_session_is_refused():
@@ -439,11 +466,12 @@ def test_config_from_env(monkeypatch):
439
466
  monkeypatch.setenv("VOICE_MODEL", "base")
440
467
  monkeypatch.setenv("VOICE_THREADS", "2")
441
468
  monkeypatch.setenv("VOICE_MAX_SECS", "60")
469
+ monkeypatch.setenv("VOICE_STALL_SECS", "45")
442
470
  monkeypatch.setenv("VOICE_TTS_ALLOW_NETWORK", "1")
443
471
  cfg = ServerConfig.from_env()
444
472
  assert (cfg.host, cfg.port, cfg.model) == ("0.0.0.0", 8790, "base")
445
473
  assert cfg.threads == "2"
446
- assert cfg.max_audio_secs == 60.0
474
+ assert (cfg.max_audio_secs, cfg.stall_secs) == (60.0, 45.0)
447
475
  assert cfg.tts_allow_network is True
448
476
 
449
477
 
@@ -41,6 +41,10 @@ class FakeSherpa:
41
41
  self.rec = {"hotwords": hotwords, "samples": 0}
42
42
  fake.streams.append(self.rec)
43
43
 
44
+ def set_option(self, key, value):
45
+ assert isinstance(value, str)
46
+ self.rec[key] = value
47
+
44
48
  def accept_waveform(self, sr, a):
45
49
  assert sr == 16000 and a.dtype == np.float32
46
50
  self.rec["samples"] = len(a)
@@ -114,6 +118,7 @@ def test_one_recognizer_and_per_stream_pretokenized_proper_nouns(fake, tmp_path)
114
118
  assert out.text == "hello world"
115
119
  # Lowercase (mined jargon) and unusable terms never become hotwords.
116
120
  assert fake.streams[-1]["hotwords"] == "▁Kal shi/▁Hy per"
121
+ assert fake.streams[-1]["length_reward"] == "0.5" # per stream, as upstream reads it
117
122
  dec.decode(_audio(2), bias=Bias(hotwords="Hyper"))
118
123
  dec.decode(_audio(2), bias=Bias())
119
124
  assert [s["hotwords"] for s in fake.streams[1:]] == ["▁Hy per", None]
@@ -135,7 +140,7 @@ def test_without_the_tdt_fix_decodes_greedy_and_unbiased(fake, monkeypatch, tmp_
135
140
  assert dec.decode(_audio(1), bias=Bias(hotwords="Kalshi")).text == "hello world"
136
141
  kw = fake.created[-1]
137
142
  assert kw["decoding_method"] == "greedy_search" and "length_reward" not in kw
138
- assert fake.streams[-1]["hotwords"] is None
143
+ assert fake.streams[-1]["hotwords"] is None and "length_reward" not in fake.streams[-1]
139
144
 
140
145
 
141
146
  def test_long_audio_is_decoded_in_pieces_of_at_most_30s(fake, tmp_path):
@@ -0,0 +1 @@
1
+ __version__ = "1.15.0"
@@ -211,6 +211,8 @@ def cmd_status(args: argparse.Namespace) -> None:
211
211
  print(f"note {unused}")
212
212
  print(f"nightly {_schedule_state()}")
213
213
  promotable = _print_scores(ds, active, runs) if ds else None
214
+ if unused: # a Whisper fine-tune can't replace another decoder on WER alone
215
+ promotable = None
214
216
  if promotable:
215
217
  nxt = f"promote {promotable}"
216
218
  else:
@@ -236,6 +236,9 @@ class SherpaTransducer:
236
236
  bpe_vocab=_bpe_vocab_file(self._dir, self._tokenizer))
237
237
  if feats.length_reward:
238
238
  kwargs.update(length_reward=length_reward)
239
+ # Upstream reads length_reward per stream (k2-fsa/sherpa-onnx#4084 after
240
+ # review); builds without that read ignore the option.
241
+ self._stream_reward = repr(float(length_reward)) if mbs and length_reward else ""
239
242
  t0 = time.monotonic()
240
243
  try:
241
244
  self._rec = sherpa_onnx.OfflineRecognizer.from_transducer(**kwargs)
@@ -276,6 +279,8 @@ class SherpaTransducer:
276
279
  for chunk in split_long(audio) if len(audio) else ():
277
280
  stream = (self._rec.create_stream(hotwords) if hotwords
278
281
  else self._rec.create_stream())
282
+ if self._stream_reward and hasattr(stream, "set_option"):
283
+ stream.set_option("length_reward", self._stream_reward)
279
284
  stream.accept_waveform(SAMPLE_RATE, chunk)
280
285
  self._rec.decode_stream(stream)
281
286
  if text := stream.result.text.strip():
@@ -37,6 +37,9 @@ from dataclasses import dataclass
37
37
  log = logging.getLogger(__name__)
38
38
 
39
39
  _TIMEOUT = 3
40
+ # No input for this long is away even when an app keeps the session awake:
41
+ # past a film's length, the inhibitor is a long-running job, not a viewer.
42
+ INHIBITED_MAX = 2 * 3600
40
43
 
41
44
 
42
45
  @dataclass(frozen=True)
@@ -144,7 +147,7 @@ def check(away_after: float, probe: SessionProbe | None = None) -> Presence:
144
147
  if probe.screen_locked():
145
148
  return Presence(False, "screen locked")
146
149
  return Presence(True, f"in use (last input {_minutes(idle)} ago)")
147
- if probe.idle_inhibited() and not probe.screen_locked():
150
+ if idle < INHIBITED_MAX and probe.idle_inhibited() and not probe.screen_locked():
148
151
  return Presence(True, "an app is keeping the session awake (a video or a call?)")
149
152
  return Presence(False, f"away {_minutes(idle)}")
150
153
  session = probe.seat_session()
@@ -21,6 +21,7 @@ def main(argv: list[str] | None = None) -> int:
21
21
  " VOICE_COMPUTE, VOICE_DEVICE compute type / device\n"
22
22
  " VOICE_THREADS CPU threads (default 4, 0=all)\n"
23
23
  " VOICE_MAX_BYTES, VOICE_MAX_SECS request limits\n"
24
+ " VOICE_STALL_SECS end a stream silent this long (30 s; 0 never)\n"
24
25
  " VOICE_TTS_ENGINE, VOICE_TTS_VOICE, VOICE_TTS_SPEED\n"
25
26
  " VOICE_TTS_ALLOW_NETWORK 1 to permit cloud TTS (off)\n"
26
27
  " VOICE_LOG_LEVEL default INFO\n"
@@ -122,12 +122,15 @@ async def handle_ws_transcribe(request: web.Request) -> web.WebSocketResponse:
122
122
  Server → client (all JSON text frames):
123
123
  {"type":"ready","sample_rate":16000,"format":"pcm_s16le","channels":1}
124
124
  {"type":"partial","text":...,"audio_secs":...}
125
- {"type":"final","text":...,"raw":...,"audio_secs":...,"ms":...}
125
+ {"type":"final","text":...,"raw":...,"audio_secs":...,"ms":...,
126
+ "cut_short":false}
126
127
  {"type":"error","error":...}
127
128
 
128
- The final frame is sent before the socket closes. A client that simply
129
- disconnects still gets its audio finalized server-side (for vocabulary
130
- statistics), it just is not around to read the result.
129
+ The final frame is sent before the socket closes. A stream that sends
130
+ neither audio nor stop for `stall_secs`, or passes `max_audio_secs`, still
131
+ gets a final of the audio it sent, with "cut_short":true and a "why". A
132
+ client that simply disconnects still gets its audio finalized server-side
133
+ (for vocabulary statistics), it just is not around to read the result.
131
134
  """
132
135
  runtime: ServerRuntime = request.app[RUNTIME]
133
136
  cfg: ServerConfig = request.app[CONFIG]
@@ -190,17 +193,22 @@ async def _run_stream(ws, runtime: ServerRuntime, cfg: ServerConfig, app) -> Non
190
193
  "model": runtime.model_cfg.name,
191
194
  })
192
195
 
196
+ cut_short = "" # why the stream ended without a stop, if it did
193
197
  overrun = False
194
198
  try:
195
- async for msg in ws:
199
+ while True:
200
+ try:
201
+ msg = await ws.receive(timeout=cfg.stall_secs or None)
202
+ except TimeoutError:
203
+ cut_short = f"no audio or stop for {cfg.stall_secs:g} s"
204
+ break
205
+ if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED):
206
+ break
196
207
  if msg.type == WSMsgType.BINARY:
197
208
  total = source.feed(msg.data)
198
209
  if total / SAMPLE_RATE > cfg.max_audio_secs:
199
210
  overrun = True
200
- await ws.send_json({
201
- "type": "error",
202
- "error": f"audio exceeded {cfg.max_audio_secs:.0f}s limit",
203
- })
211
+ cut_short = f"audio passed the {cfg.max_audio_secs:g} s limit"
204
212
  break
205
213
  elif msg.type == WSMsgType.TEXT:
206
214
  try:
@@ -218,7 +226,9 @@ async def _run_stream(ws, runtime: ServerRuntime, cfg: ServerConfig, app) -> Non
218
226
  finally:
219
227
  source.close()
220
228
  audio = source.get_audio_so_far()
221
- secs = source.duration_secs
229
+ if overrun and audio is not None: # the last frame may run far past the limit
230
+ audio = audio[:int(cfg.max_audio_secs * SAMPLE_RATE)]
231
+ secs = len(audio) / SAMPLE_RATE if audio is not None else 0.0
222
232
 
223
233
  # stop() joins the worker thread through a final beam-search decode:
224
234
  # seconds of CPU, and it must not block the event loop.
@@ -238,18 +248,21 @@ async def _run_stream(ws, runtime: ServerRuntime, cfg: ServerConfig, app) -> Non
238
248
  outbox.put_nowait(None)
239
249
  await pump_task
240
250
 
241
- if not ws.closed and not overrun:
251
+ if not ws.closed:
242
252
  await ws.send_json({
243
253
  "type": "final",
244
254
  "text": text,
245
255
  "raw": session.raw_final_text or "",
246
256
  "audio_secs": round(secs, 2),
247
257
  "ms": round((time.monotonic() - t0) * 1000),
258
+ "cut_short": bool(cut_short),
259
+ **({"why": cut_short} if cut_short else {}),
248
260
  })
249
261
  if text: # after the reply: retention must not add to the user's wait
250
262
  await loop.run_in_executor(app[EXECUTOR], runtime.commit, audio, text,
251
263
  session.raw_final_text or "")
252
- log.info("streaming %.1fs audio → %s", secs, describe_transcript(text))
264
+ log.info("streaming %.1fs audio → %s%s", secs, describe_transcript(text),
265
+ f" (cut short: {cut_short})" if cut_short else "")
253
266
  if not ws.closed:
254
267
  await ws.close()
255
268
 
@@ -59,6 +59,9 @@ class ServerConfig:
59
59
  # max_audio_secs; this exists so a bad client cannot stream us to death.
60
60
  max_bytes: int = 25 * 1024 * 1024
61
61
  max_audio_secs: float = 120.0
62
+ # A stream that sends neither audio nor stop for this long is finalized
63
+ # with what it has (marked cut_short). 0 waits forever.
64
+ stall_secs: float = 30.0
62
65
 
63
66
  # ── tts ──────────────────────────────────────────────────────────────
64
67
  tts_engine: str = "auto"
@@ -97,6 +100,7 @@ class ServerConfig:
97
100
  lanes=int(e(prefix, "LANES", "1")),
98
101
  max_bytes=int(e(prefix, "MAX_BYTES", str(25 * 1024 * 1024))),
99
102
  max_audio_secs=float(e(prefix, "MAX_SECS", "120")),
103
+ stall_secs=float(e(prefix, "STALL_SECS", "30")),
100
104
  tts_engine=e(prefix, "TTS_ENGINE", "auto"),
101
105
  tts_voice=e(prefix, "TTS_VOICE", ""),
102
106
  tts_speed=float(e(prefix, "TTS_SPEED", "1.0")),
@@ -1 +0,0 @@
1
- __version__ = "1.14.0"
File without changes