python-voiceio 1.15.0__tar.gz → 1.15.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.
- {python_voiceio-1.15.0/python_voiceio.egg-info → python_voiceio-1.15.2}/PKG-INFO +5 -1
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/README.md +4 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/pyproject.toml +1 -1
- {python_voiceio-1.15.0 → python_voiceio-1.15.2/python_voiceio.egg-info}/PKG-INFO +5 -1
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/python_voiceio.egg-info/SOURCES.txt +1 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_feedback_stream.py +4 -4
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_health.py +26 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_idle_audio.py +8 -8
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_postprocess.py +14 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_report.py +19 -1
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_robustness.py +17 -0
- python_voiceio-1.15.2/tests/test_tray.py +58 -0
- python_voiceio-1.15.2/voiceio/__init__.py +1 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/feedback.py +3 -5
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/health.py +13 -1
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/platform.py +16 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/postprocess.py +3 -2
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/recorder.py +2 -6
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/report.py +11 -2
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/steps.py +2 -1
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tray/__init__.py +15 -1
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tray/_pystray.py +3 -0
- python_voiceio-1.15.0/voiceio/__init__.py +0 -1
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/LICENSE +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/python_voiceio.egg-info/dependency_links.txt +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/python_voiceio.egg-info/entry_points.txt +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/python_voiceio.egg-info/requires.txt +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/python_voiceio.egg-info/top_level.txt +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/setup.cfg +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_announce.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_app_wiring.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_audio_quality.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_audio_source.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_backend_probes.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_cancel.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_cli.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_clipboard_read.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_clipboard_typer.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_commands.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_concurrency_lockdown.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_config.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_context_terms.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_corrections.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_dead_mic.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_decoders.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_engine.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_evdev_uaccess.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_fallback.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_fcitx_bridge.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_focus.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_hints.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_history.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_hotkey_modes.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_engine_limits.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_engine_listener.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_pending.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_pids.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_ping.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_session.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_textlimit.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_typer.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_clean.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_import.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_language.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_metrics.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_pipeline.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_quiet.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_resume.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_review.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_targets.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_llm.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_llm_api.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_mic_gain.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_modifier_guard.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_numbers.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_paste_last.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_pipeline.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_platform.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_postcorrect.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_prebuffer.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_presence.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_prompt.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_quiet_audio.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_recorder_integration.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_retention.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_security_hardening.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_server.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_service.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_setup.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_sherpa.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_spool.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_streaming.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_suggest.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_tokens.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_transcriber.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_tts.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_typer_manager.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_vad.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_vocabulary.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_wordfreq.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_worker_decode.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/__main__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/announce.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/app.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/audio_source.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/backends.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/configcmd.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/corrections.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/doctor.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/history.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/learn.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/models.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/servicecmd.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/uninstall.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/vocab.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/clipboard_read.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/commands.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/config.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/consent.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/context_terms.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/bias.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/base.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/catalog.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/fetch.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/lanes.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/sherpa.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/whispercpp.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/engine.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/pipeline.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/corrections.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/data/60-voiceio-uaccess.rules +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/data/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/demo.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/display.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/focus.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hints.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/history.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/base.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/chain.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/evdev.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/pynput_backend.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/socket_backend.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/engine.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/install.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/pending.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/session.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/textlimit.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/idle_audio.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/augment.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/clean.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/evaluate.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/importer.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/metrics.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/quiet.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/review.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/store.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/teacher.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/train.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/llm.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/llm_api.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/mic_gain.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/models/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/models/silero_vad.onnx +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/numbers.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/pidlock.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/postcorrect.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/power.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/presence.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/privacy.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/prompt.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/quiet_audio.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/retention.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/server/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/server/__main__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/server/app.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/server/config.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/server/runtime.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/service.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/configfile.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/extras.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/hotkey.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/noninteractive.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/speech.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/system.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/ui.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/sounds/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/sounds/alert.wav +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/sounds/commit.wav +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/sounds/start.wav +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/sounds/stop.wav +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/spool.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/streaming.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/suggest.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tokens.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tray/_icons.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tray/_indicator.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/base.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/chain.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/edge_engine.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/espeak.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/piper_engine.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/player.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/supertonic_engine.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/__init__.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/base.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/chain.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/clipboard.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/collecting.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/fcitx_bridge.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/ibus.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/manager.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/modifier_guard.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/paste.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/pynput_type.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/wtype.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/xdotool.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/ydotool.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ui.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/vad.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/vocab_stats.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/vocabulary.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/watchdog.py +0 -0
- {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/wordfreq.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-voiceio
|
|
3
|
-
Version: 1.15.
|
|
3
|
+
Version: 1.15.2
|
|
4
4
|
Summary: Voice dictation for Linux. Speak → text, locally, instantly.
|
|
5
5
|
Author: Hugo Montenegro
|
|
6
6
|
License-Expression: MIT
|
|
@@ -146,6 +146,10 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
|
|
|
146
146
|
|
|
147
147
|
Open an issue before a large PR. [CONTRIBUTING.md](CONTRIBUTING.md) covers the architecture, conventions and the reasoning behind the model choice. Install reports and ideas are welcome on the [feedback board](https://voiceio.dev/#feedback).
|
|
148
148
|
|
|
149
|
+
## How it's built
|
|
150
|
+
|
|
151
|
+
I write voiceio with AI coding assistants, mostly Claude Code. I decide what goes in and I use it every day, which is how most of its bugs get found.
|
|
152
|
+
|
|
149
153
|
## Credits
|
|
150
154
|
|
|
151
155
|
- Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models; [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) with NVIDIA's [Parakeet-TDT](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v2). The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
|
|
@@ -93,6 +93,10 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
|
|
|
93
93
|
|
|
94
94
|
Open an issue before a large PR. [CONTRIBUTING.md](CONTRIBUTING.md) covers the architecture, conventions and the reasoning behind the model choice. Install reports and ideas are welcome on the [feedback board](https://voiceio.dev/#feedback).
|
|
95
95
|
|
|
96
|
+
## How it's built
|
|
97
|
+
|
|
98
|
+
I write voiceio with AI coding assistants, mostly Claude Code. I decide what goes in and I use it every day, which is how most of its bugs get found.
|
|
99
|
+
|
|
96
100
|
## Credits
|
|
97
101
|
|
|
98
102
|
- Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models; [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) with NVIDIA's [Parakeet-TDT](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v2). The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-voiceio
|
|
3
|
-
Version: 1.15.
|
|
3
|
+
Version: 1.15.2
|
|
4
4
|
Summary: Voice dictation for Linux. Speak → text, locally, instantly.
|
|
5
5
|
Author: Hugo Montenegro
|
|
6
6
|
License-Expression: MIT
|
|
@@ -146,6 +146,10 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
|
|
|
146
146
|
|
|
147
147
|
Open an issue before a large PR. [CONTRIBUTING.md](CONTRIBUTING.md) covers the architecture, conventions and the reasoning behind the model choice. Install reports and ideas are welcome on the [feedback board](https://voiceio.dev/#feedback).
|
|
148
148
|
|
|
149
|
+
## How it's built
|
|
150
|
+
|
|
151
|
+
I write voiceio with AI coding assistants, mostly Claude Code. I decide what goes in and I use it every day, which is how most of its bugs get found.
|
|
152
|
+
|
|
149
153
|
## Credits
|
|
150
154
|
|
|
151
155
|
- Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models; [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) with NVIDIA's [Parakeet-TDT](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v2). The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""The persistent cue stream: buffer size and cue timing."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
from types import SimpleNamespace
|
|
5
5
|
|
|
6
|
-
pytest
|
|
6
|
+
import pytest
|
|
7
7
|
|
|
8
|
-
from voiceio import feedback
|
|
8
|
+
from voiceio import feedback
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class FakeStream:
|
|
@@ -24,7 +24,7 @@ class FakeStream:
|
|
|
24
24
|
@pytest.fixture
|
|
25
25
|
def fake_stream(monkeypatch):
|
|
26
26
|
FakeStream.opened = []
|
|
27
|
-
monkeypatch.setattr(feedback
|
|
27
|
+
monkeypatch.setattr(feedback, "sd", SimpleNamespace(OutputStream=FakeStream))
|
|
28
28
|
monkeypatch.setattr(feedback, "_stream", None)
|
|
29
29
|
monkeypatch.setattr(feedback, "_sounds", {})
|
|
30
30
|
return FakeStream
|
|
@@ -123,3 +123,29 @@ def test_learning_status_and_tips(monkeypatch):
|
|
|
123
123
|
assert st["Voice model"] == "fine-tuned on you (v0001-small-x)"
|
|
124
124
|
assert st["Recordings"].startswith("on (") and st["Scheduled learning"] == "off"
|
|
125
125
|
assert any("learn schedule on" in t for t in _accuracy_tips(st))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_silent_ibus_engine_is_not_a_warning_while_voiceio_is_stopped():
|
|
129
|
+
from voiceio.health import HealthReport, IBusStatus
|
|
130
|
+
p = Platform(os="linux", display_server="wayland", desktop="kde")
|
|
131
|
+
ib = IBusStatus(component_installed=True, daemon_running=True, voiceio_running=False)
|
|
132
|
+
text = format_report(HealthReport(platform=p, ibus_checks=ib))
|
|
133
|
+
line = next(ln for ln in text.splitlines() if "Engine answers" in ln)
|
|
134
|
+
assert "⚠" not in line and "isn't running" in line
|
|
135
|
+
ib.voiceio_running = True
|
|
136
|
+
line = next(ln for ln in format_report(HealthReport(platform=p, ibus_checks=ib)).splitlines()
|
|
137
|
+
if "Engine answers" in ln)
|
|
138
|
+
assert "⚠" in line and "service restart" in line
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_check_ibus_asks_about_voiceio_only_when_the_engine_is_silent():
|
|
142
|
+
from voiceio import health
|
|
143
|
+
with patch("voiceio.ibus.install.component_installed", return_value=True), \
|
|
144
|
+
patch("voiceio.ibus.install.daemon_running", return_value=True), \
|
|
145
|
+
patch("voiceio.ibus.ping_engine", return_value=False), \
|
|
146
|
+
patch("voiceio.pidlock.daemon_alive", return_value=False) as alive:
|
|
147
|
+
assert health._check_ibus().voiceio_running is False
|
|
148
|
+
with patch("voiceio.ibus.ping_engine", return_value=True):
|
|
149
|
+
alive.reset_mock()
|
|
150
|
+
assert health._check_ibus().voiceio_running is True
|
|
151
|
+
alive.assert_not_called()
|
|
@@ -3,17 +3,16 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import threading
|
|
5
5
|
import time
|
|
6
|
+
from types import SimpleNamespace
|
|
6
7
|
from unittest.mock import MagicMock, patch
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
9
10
|
import pytest
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
from voiceio import
|
|
14
|
-
from voiceio.
|
|
15
|
-
from voiceio.idle_audio import IdleAudio # noqa: E402
|
|
16
|
-
from voiceio.recorder import AudioRecorder # noqa: E402
|
|
12
|
+
from voiceio import feedback, idle_audio
|
|
13
|
+
from voiceio.config import AudioConfig, Config
|
|
14
|
+
from voiceio.idle_audio import IdleAudio
|
|
15
|
+
from voiceio.recorder import AudioRecorder
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class FakeInput:
|
|
@@ -50,8 +49,9 @@ class FakeOutput(FakeInput):
|
|
|
50
49
|
@pytest.fixture
|
|
51
50
|
def streams(monkeypatch):
|
|
52
51
|
FakeInput.opened = FakeOutput.opened = 0
|
|
53
|
-
|
|
54
|
-
monkeypatch.setattr(
|
|
52
|
+
# Whole fakes: touching the real module would initialize PortAudio.
|
|
53
|
+
monkeypatch.setattr("voiceio.recorder.sd", SimpleNamespace(InputStream=FakeInput))
|
|
54
|
+
monkeypatch.setattr(feedback, "sd", SimpleNamespace(OutputStream=FakeOutput))
|
|
55
55
|
monkeypatch.setattr(feedback, "_stream", None)
|
|
56
56
|
|
|
57
57
|
|
|
@@ -88,6 +88,20 @@ class TestStripDisfluencies:
|
|
|
88
88
|
assert strip_disfluencies("uh-huh right") == "right"
|
|
89
89
|
assert strip_disfluencies("uh huh yes") == "yes"
|
|
90
90
|
|
|
91
|
+
def test_mm_hmm_goes_whole(self):
|
|
92
|
+
# Parakeet ends about 1 clip in 4 with an invented "Mm-hmm."; cutting
|
|
93
|
+
# only its "hmm" typed a stray "Mm-." after real dictation.
|
|
94
|
+
assert strip_disfluencies("I sent it. Mm-hmm.") == "I sent it."
|
|
95
|
+
assert strip_disfluencies("Are you sure? Mm-hmm.") == "Are you sure?"
|
|
96
|
+
assert strip_disfluencies("Mm-hmm.") == ""
|
|
97
|
+
assert strip_disfluencies("Fine. Mm-hmm. So it works.") == "Fine. So it works."
|
|
98
|
+
assert strip_disfluencies("mmhmm that works") == "that works"
|
|
99
|
+
|
|
100
|
+
def test_fillers_inside_hyphenated_words_survive(self):
|
|
101
|
+
assert strip_disfluencies("a hmm-hmm sound") == "a hmm-hmm sound"
|
|
102
|
+
assert strip_disfluencies("she said uh-uh twice") == "she said uh-uh twice"
|
|
103
|
+
assert strip_disfluencies("the 5 mm drill") == "the 5 mm drill"
|
|
104
|
+
|
|
91
105
|
def test_preserves_emphatic_short_repeat(self):
|
|
92
106
|
# Short repeats are emphasis, not a re-decode artifact — keep them.
|
|
93
107
|
assert strip_disfluencies("No. No.") == "No. No."
|
|
@@ -9,7 +9,7 @@ import pytest
|
|
|
9
9
|
|
|
10
10
|
from voiceio import report
|
|
11
11
|
from voiceio.config import Config
|
|
12
|
-
from voiceio.health import BackendStatus, HealthReport
|
|
12
|
+
from voiceio.health import BackendStatus, HealthReport, IBusStatus
|
|
13
13
|
from voiceio.platform import Platform
|
|
14
14
|
|
|
15
15
|
FAKE_HOME = "/home/zelda-tester"
|
|
@@ -104,6 +104,16 @@ def test_report_lists_failures_and_warnings():
|
|
|
104
104
|
assert "insecure permissions on 2 file(s)" in text
|
|
105
105
|
|
|
106
106
|
|
|
107
|
+
def test_silent_ibus_engine_is_expected_while_voiceio_is_stopped():
|
|
108
|
+
# Two 1.15.0 reports warned "ibus engine ping" only because voiceio wasn't running.
|
|
109
|
+
ib = IBusStatus(component_installed=True, daemon_running=True, env_persisted=True)
|
|
110
|
+
stopped = report.build_report(_health(ibus_checks=IBusStatus(**{**vars(ib), "voiceio_running": False})),
|
|
111
|
+
cfg=Config())
|
|
112
|
+
assert "engine ping" not in stopped and "voiceio not running" in stopped
|
|
113
|
+
running = report.build_report(_health(ibus_checks=ib), cfg=Config())
|
|
114
|
+
assert "ibus engine ping" in running and "voiceio not running" not in running
|
|
115
|
+
|
|
116
|
+
|
|
107
117
|
def test_comment_is_one_bounded_line():
|
|
108
118
|
text = report.build_report(_health(), comment="a\nb\tc " + "x" * 1000, cfg=Config())
|
|
109
119
|
line = next(ln for ln in text.splitlines() if ln.startswith("comment:"))
|
|
@@ -111,6 +121,14 @@ def test_comment_is_one_bounded_line():
|
|
|
111
121
|
assert len(line) <= len("comment: ") + report.MAX_COMMENT
|
|
112
122
|
|
|
113
123
|
|
|
124
|
+
def test_comment_drops_terminal_escapes():
|
|
125
|
+
# A real 1.15.0 report arrived with "comment: \x1bv" (Alt+V at the prompt).
|
|
126
|
+
text = report.build_report(_health(), comment="\x1bv", cfg=Config())
|
|
127
|
+
assert "comment:" not in text and "\x1b" not in text
|
|
128
|
+
text = report.build_report(_health(), comment="works\x1b[D\x1b[C great\x07", cfg=Config())
|
|
129
|
+
assert "comment: works great" in text.splitlines()
|
|
130
|
+
|
|
131
|
+
|
|
114
132
|
# ── posting ──
|
|
115
133
|
|
|
116
134
|
class _Resp(io.BytesIO):
|
|
@@ -936,3 +936,20 @@ class TestZombieStreamRecovery:
|
|
|
936
936
|
# any real signal → not a zombie
|
|
937
937
|
rec._ring.append(np.full(rec._ring._max, 0.1, dtype=np.float32))
|
|
938
938
|
assert rec.is_zombie() is False
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def test_app_imports_when_portaudio_cannot_initialize(tmp_path):
|
|
942
|
+
# ubuntu-26.04 runners: libportaudio2 pulls libpulse0, and with no pulse
|
|
943
|
+
# server `import sounddevice` raises; importing voiceio.app must not.
|
|
944
|
+
import subprocess
|
|
945
|
+
import sys
|
|
946
|
+
(tmp_path / "sounddevice.py").write_text(
|
|
947
|
+
"class PortAudioError(Exception): pass\n"
|
|
948
|
+
"raise PortAudioError('PulseAudio_Initialize: Can\\'t connect to server')\n")
|
|
949
|
+
code = ("import voiceio.app, voiceio.recorder as r\n"
|
|
950
|
+
"try:\n r.sd.InputStream\nexcept Exception as e:\n"
|
|
951
|
+
" print(type(e).__name__)\n")
|
|
952
|
+
out = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True,
|
|
953
|
+
env={**__import__("os").environ, "PYTHONPATH": str(tmp_path)})
|
|
954
|
+
assert out.returncode == 0, out.stderr
|
|
955
|
+
assert out.stdout.strip() == "PortAudioError"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Tray backend probing: what doctor/setup promise must match what start() does."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import builtins
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from voiceio import tray
|
|
12
|
+
from voiceio.tray import _pystray
|
|
13
|
+
|
|
14
|
+
linux = pytest.mark.skipif(not sys.platform.startswith("linux"), reason="linux probe")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@linux
|
|
18
|
+
def test_appindicator_wins():
|
|
19
|
+
with patch.object(tray, "_find_system_python", return_value="/usr/bin/python3"):
|
|
20
|
+
assert tray.probe_availability() == (True, "AppIndicator3 available", "")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@linux
|
|
24
|
+
def test_pystray_xembed_fallback_counts_as_a_tray():
|
|
25
|
+
# KDE/LXQt users without python3-gi got "no tray" though start() falls back to pystray.
|
|
26
|
+
with patch.object(tray, "_find_system_python", return_value=None), \
|
|
27
|
+
patch.object(tray, "_pystray_backend", return_value="xorg"):
|
|
28
|
+
ok, reason, hint = tray.probe_availability()
|
|
29
|
+
assert ok and "no menu" in reason and "for the full menu" in hint
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@linux
|
|
33
|
+
def test_no_backend_at_all():
|
|
34
|
+
with patch.object(tray, "_find_system_python", return_value=None), \
|
|
35
|
+
patch.object(tray, "_pystray_backend", return_value=None):
|
|
36
|
+
ok, reason, hint = tray.probe_availability()
|
|
37
|
+
assert not ok and "AppIndicator3" in reason and hint.startswith("install:")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _import_raising(exc):
|
|
41
|
+
real = builtins.__import__
|
|
42
|
+
|
|
43
|
+
def fake(name, *a, **kw):
|
|
44
|
+
if name == "pystray":
|
|
45
|
+
raise exc
|
|
46
|
+
return real(name, *a, **kw)
|
|
47
|
+
return fake
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class _DisplayNameError(Exception):
|
|
51
|
+
"""Stands in for Xlib.error.DisplayNameError (no DISPLAY): not an ImportError."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_pystray_without_x_display_does_not_raise(monkeypatch):
|
|
55
|
+
monkeypatch.delitem(sys.modules, "pystray", raising=False)
|
|
56
|
+
with patch("builtins.__import__", _import_raising(_DisplayNameError(":0"))):
|
|
57
|
+
assert tray._pystray_backend() is None
|
|
58
|
+
assert _pystray.start(lambda: None, Path("idle.png"), []) is False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.15.2"
|
|
@@ -20,11 +20,9 @@ from pathlib import Path
|
|
|
20
20
|
|
|
21
21
|
import numpy as np
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
from voiceio.platform import desktop_dep_hint
|
|
27
|
-
raise ImportError(desktop_dep_hint("sounddevice")) from _e
|
|
23
|
+
from voiceio.platform import LazySounddevice
|
|
24
|
+
|
|
25
|
+
sd = LazySounddevice()
|
|
28
26
|
|
|
29
27
|
log = logging.getLogger(__name__)
|
|
30
28
|
|
|
@@ -25,6 +25,8 @@ class IBusStatus:
|
|
|
25
25
|
component_installed: bool = False
|
|
26
26
|
daemon_running: bool = False
|
|
27
27
|
socket_reachable: bool = False
|
|
28
|
+
# The engine runs while voiceio does, so a silent one only matters then.
|
|
29
|
+
voiceio_running: bool = True
|
|
28
30
|
gnome_source_configured: bool = False
|
|
29
31
|
env_persisted: bool = False
|
|
30
32
|
|
|
@@ -301,6 +303,10 @@ def _check_ibus() -> IBusStatus:
|
|
|
301
303
|
# Ping, not exists(): an engine can hold a socket file yet be deaf.
|
|
302
304
|
from voiceio.ibus import ping_engine
|
|
303
305
|
status.socket_reachable = ping_engine(timeout=1.0)
|
|
306
|
+
if not status.socket_reachable:
|
|
307
|
+
from voiceio.config import PID_PATH
|
|
308
|
+
from voiceio.pidlock import daemon_alive
|
|
309
|
+
status.voiceio_running = daemon_alive(PID_PATH)
|
|
304
310
|
|
|
305
311
|
# Check GNOME input source
|
|
306
312
|
try:
|
|
@@ -433,7 +439,9 @@ def format_report(report: HealthReport) -> str:
|
|
|
433
439
|
lines.append(_check_line("Tray icon", report.tray_ok,
|
|
434
440
|
report.tray_reason if not report.tray_ok else "",
|
|
435
441
|
warn=not report.tray_ok))
|
|
436
|
-
if report.
|
|
442
|
+
if report.tray_ok and report.tray_fix_hint: # works, but degraded
|
|
443
|
+
lines[-1] += f" {DIM}{report.tray_reason}{RESET}"
|
|
444
|
+
if report.tray_fix_hint:
|
|
437
445
|
lines.append(f" {DIM}→ {report.tray_fix_hint}{RESET}")
|
|
438
446
|
|
|
439
447
|
# IBus pipeline
|
|
@@ -448,6 +456,10 @@ def format_report(report: HealthReport) -> str:
|
|
|
448
456
|
("GNOME input source", ibus.gnome_source_configured, "voiceio setup"),
|
|
449
457
|
("Env persisted (reboot-safe)", ibus.env_persisted, "voiceio setup"),
|
|
450
458
|
]:
|
|
459
|
+
if label == "Engine answers" and not ok and not ibus.voiceio_running:
|
|
460
|
+
lines.append(f" {DIM}–{RESET} {label} "
|
|
461
|
+
f"{DIM}starts with voiceio, which isn't running{RESET}")
|
|
462
|
+
continue
|
|
451
463
|
lines.append(_check_line(label, ok, hint if not ok else "",
|
|
452
464
|
warn=not ok))
|
|
453
465
|
|
|
@@ -15,6 +15,22 @@ log = logging.getLogger(__name__)
|
|
|
15
15
|
DESKTOP_EXTRA_INSTALL = "pipx install --force 'python-voiceio[desktop]'"
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
class LazySounddevice:
|
|
19
|
+
"""`sounddevice`, imported on first use. Its import initializes PortAudio,
|
|
20
|
+
which raises when libpulse is installed but no sound server runs (CI,
|
|
21
|
+
headless boxes): imported eagerly, that broke `import voiceio.app` and
|
|
22
|
+
every command behind it."""
|
|
23
|
+
|
|
24
|
+
def __getattr__(self, name: str):
|
|
25
|
+
if name.startswith("_"): # introspection (mock, inspect) must not import it
|
|
26
|
+
raise AttributeError(name)
|
|
27
|
+
try:
|
|
28
|
+
import sounddevice
|
|
29
|
+
except ImportError as e:
|
|
30
|
+
raise ImportError(desktop_dep_hint("sounddevice")) from e
|
|
31
|
+
return getattr(sounddevice, name)
|
|
32
|
+
|
|
33
|
+
|
|
18
34
|
def desktop_dep_hint(package: str) -> str:
|
|
19
35
|
"""One-line message for a desktop dependency that failed to import."""
|
|
20
36
|
return (
|
|
@@ -19,8 +19,9 @@ _NO_CASE_LANGUAGES = frozenset({"zh", "ja", "ko", "ar", "he", "th", "hi", "bn",
|
|
|
19
19
|
# own line doesn't swallow the paragraph/list break around it.
|
|
20
20
|
# Word repetitions ("had had" is valid English) and filler "like"/"you know"
|
|
21
21
|
# need judgment and are left to the LLM layer. Order matters: multi-token
|
|
22
|
-
# "uh-huh"
|
|
23
|
-
|
|
22
|
+
# "uh-huh" and "mm-hmm" (Parakeet invents "Mm-hmm." on a clip's quiet tail)
|
|
23
|
+
# before the bare forms. Never a piece of a hyphenated word: that left "Mm-.".
|
|
24
|
+
_FILLER = r"(?<!-)\b(?:[Uu]h[-\s]?huh|[Mm]m-?[Hh]m+|[Mm]hm|[Uu]h+m*|[Uu]m+|[Hh]m{2,})\b(?!-)"
|
|
24
25
|
# A run of fillers and the comma/space around it; the lowercase letter after it, when
|
|
25
26
|
# it opened a sentence, gets capitalized ("those. Um so I" -> "those. So I"),
|
|
26
27
|
# or the sentence end before it would read as a false stop and become a comma.
|
|
@@ -8,19 +8,15 @@ from typing import TYPE_CHECKING, Callable
|
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
|
|
11
|
-
try:
|
|
12
|
-
import sounddevice as sd
|
|
13
|
-
except ImportError as _e:
|
|
14
|
-
from voiceio.platform import desktop_dep_hint
|
|
15
|
-
raise ImportError(desktop_dep_hint("sounddevice")) from _e
|
|
16
|
-
|
|
17
11
|
from voiceio.audio_source import AudioBuffer
|
|
12
|
+
from voiceio.platform import LazySounddevice
|
|
18
13
|
|
|
19
14
|
if TYPE_CHECKING:
|
|
20
15
|
from voiceio.config import AudioConfig
|
|
21
16
|
from voiceio.vad import VadBackend
|
|
22
17
|
|
|
23
18
|
log = logging.getLogger(__name__)
|
|
19
|
+
sd = LazySounddevice()
|
|
24
20
|
|
|
25
21
|
# Peak amplitude below this is treated as no signal at all (a muted mic emits
|
|
26
22
|
# exact zeros; a working mic's "silence" still noises above ~1e-4). Matches the
|
|
@@ -30,6 +30,8 @@ TIMEOUT_SECS = 8.0
|
|
|
30
30
|
MAX_COMMENT = 300
|
|
31
31
|
MAX_DETAIL = 90
|
|
32
32
|
|
|
33
|
+
# A terminal escape: CSI sequences (arrow keys) or ESC plus one key (Alt+key).
|
|
34
|
+
_ESCAPE = re.compile(r"\x1b(?:\[[0-?]*[ -/]*[@-~]|.)?", re.S)
|
|
33
35
|
_IPV4 = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
|
|
34
36
|
_IPV6 = re.compile(r"\b(?:[0-9a-f]{1,4}:){3,7}[0-9a-f]{1,4}\b", re.I)
|
|
35
37
|
_MAC = re.compile(r"\b(?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}\b", re.I)
|
|
@@ -155,7 +157,7 @@ def build_report(
|
|
|
155
157
|
if health.ibus_checks is not None:
|
|
156
158
|
ib = health.ibus_checks
|
|
157
159
|
for label, ok in (("component", ib.component_installed), ("daemon", ib.daemon_running),
|
|
158
|
-
("engine ping", ib.socket_reachable),
|
|
160
|
+
("engine ping", ib.socket_reachable or not ib.voiceio_running),
|
|
159
161
|
("env persisted", ib.env_persisted)):
|
|
160
162
|
if not ok:
|
|
161
163
|
warned.append(f"ibus {label}")
|
|
@@ -178,7 +180,9 @@ def build_report(
|
|
|
178
180
|
f"python {_py_platform.python_version()}",
|
|
179
181
|
f"backends: hotkey {hotkey.name if hotkey else 'none'} · "
|
|
180
182
|
f"typer {typer.name if typer else 'none'} · audio {audio} "
|
|
181
|
-
f"(vad {cfg.audio.vad_backend})"
|
|
183
|
+
f"(vad {cfg.audio.vad_backend})"
|
|
184
|
+
+ (" · voiceio not running" if health.ibus_checks is not None
|
|
185
|
+
and not health.ibus_checks.voiceio_running else ""),
|
|
182
186
|
f"model: {_model_label(cfg.model.name)}",
|
|
183
187
|
]
|
|
184
188
|
if unavailable:
|
|
@@ -186,6 +190,7 @@ def build_report(
|
|
|
186
190
|
lines.append("failed: " + "; ".join(failed) if failed else "failed: none")
|
|
187
191
|
if warned:
|
|
188
192
|
lines.append("warnings: " + "; ".join(warned))
|
|
193
|
+
comment = "".join(c if c.isprintable() else " " for c in _ESCAPE.sub("", comment))
|
|
189
194
|
comment = " ".join(comment.split())[:MAX_COMMENT]
|
|
190
195
|
if comment:
|
|
191
196
|
lines.append(f"comment: {comment}")
|
|
@@ -233,6 +238,10 @@ def _interactive() -> bool:
|
|
|
233
238
|
|
|
234
239
|
|
|
235
240
|
def _ask(prompt: str) -> str:
|
|
241
|
+
try:
|
|
242
|
+
import readline # noqa: F401 line editing, so arrow keys don't type escapes
|
|
243
|
+
except ImportError:
|
|
244
|
+
pass
|
|
236
245
|
try:
|
|
237
246
|
return input(prompt)
|
|
238
247
|
except (EOFError, KeyboardInterrupt):
|
|
@@ -184,7 +184,8 @@ def _print_tray_check() -> bool:
|
|
|
184
184
|
from voiceio.tray import probe_availability
|
|
185
185
|
tray_ok, tray_reason, tray_fix_hint = probe_availability()
|
|
186
186
|
if tray_ok:
|
|
187
|
-
_print_check("Tray icon", True, "
|
|
187
|
+
_print_check("Tray icon", True, f"{tray_reason}; {tray_fix_hint}" if tray_fix_hint
|
|
188
|
+
else "system tray indicator available")
|
|
188
189
|
else:
|
|
189
190
|
_print_check("Tray icon", False,
|
|
190
191
|
tray_fix_hint if tray_fix_hint else tray_reason,
|
|
@@ -59,11 +59,16 @@ def probe_availability() -> tuple[bool, str, str]:
|
|
|
59
59
|
if py:
|
|
60
60
|
return True, "AppIndicator3 available", ""
|
|
61
61
|
from voiceio.platform import pkg_install
|
|
62
|
+
hint = f"install: {pkg_install('python3-gi', 'gir1.2-ayatanaappindicator3-0.1')}"
|
|
63
|
+
if _pystray_backend() == "xorg":
|
|
64
|
+
# What start() falls back to: an XEmbed icon (KDE and LXQt show
|
|
65
|
+
# it), animated, but without the menu.
|
|
66
|
+
return True, "basic icon (pystray, no menu)", f"for the full menu, {hint}"
|
|
62
67
|
return (
|
|
63
68
|
False,
|
|
64
69
|
"no python3 with GTK3/AppIndicator3 bindings "
|
|
65
70
|
"(set VOICEIO_SYSTEM_PYTHON to override)",
|
|
66
|
-
|
|
71
|
+
hint,
|
|
67
72
|
)
|
|
68
73
|
elif sys.platform == "darwin":
|
|
69
74
|
try:
|
|
@@ -82,6 +87,15 @@ def probe_availability() -> tuple[bool, str, str]:
|
|
|
82
87
|
return False, "unsupported platform", ""
|
|
83
88
|
|
|
84
89
|
|
|
90
|
+
def _pystray_backend() -> str | None:
|
|
91
|
+
"""The backend pystray picks here ("xorg", "appindicator", ...), or None."""
|
|
92
|
+
try:
|
|
93
|
+
import pystray
|
|
94
|
+
except Exception: # ImportError, or Xlib errors when there's no X display
|
|
95
|
+
return None
|
|
96
|
+
return pystray.Icon.__module__.rsplit("._", 1)[-1]
|
|
97
|
+
|
|
98
|
+
|
|
85
99
|
def _start_indicator(
|
|
86
100
|
theme_dir: Path,
|
|
87
101
|
icon_names: list[str],
|
|
@@ -35,6 +35,9 @@ def start(
|
|
|
35
35
|
except ImportError:
|
|
36
36
|
log.warning("pystray/Pillow not installed, tray icon disabled")
|
|
37
37
|
return False
|
|
38
|
+
except Exception as e: # Linux import opens an X display: none means Xlib errors
|
|
39
|
+
log.warning("pystray unusable (%s), tray icon disabled", e)
|
|
40
|
+
return False
|
|
38
41
|
|
|
39
42
|
_idle_img = Image.open(idle_path)
|
|
40
43
|
_recording_frames = [Image.open(p) for p in frame_paths]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.15.0"
|
|
File without changes
|
{python_voiceio-1.15.0 → python_voiceio-1.15.2}/python_voiceio.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|