python-voiceio 1.9.2__tar.gz → 1.10.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.
- {python_voiceio-1.9.2/python_voiceio.egg-info → python_voiceio-1.10.0}/PKG-INFO +2 -2
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/README.md +1 -1
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/pyproject.toml +1 -1
- {python_voiceio-1.9.2 → python_voiceio-1.10.0/python_voiceio.egg-info}/PKG-INFO +2 -2
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/python_voiceio.egg-info/SOURCES.txt +7 -0
- python_voiceio-1.10.0/tests/test_dead_mic.py +94 -0
- python_voiceio-1.10.0/tests/test_learn_quiet.py +247 -0
- python_voiceio-1.10.0/tests/test_learn_resume.py +243 -0
- python_voiceio-1.10.0/tests/test_mic_gain.py +85 -0
- python_voiceio-1.10.0/tests/test_presence.py +99 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_service.py +34 -2
- python_voiceio-1.10.0/voiceio/__init__.py +1 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/app.py +64 -17
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/cli/learn.py +127 -35
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/config.py +14 -4
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/feedback.py +13 -1
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/learn/evaluate.py +4 -1
- python_voiceio-1.10.0/voiceio/learn/quiet.py +172 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/learn/store.py +9 -3
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/learn/teacher.py +3 -3
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/learn/train.py +219 -44
- python_voiceio-1.10.0/voiceio/mic_gain.py +123 -0
- python_voiceio-1.10.0/voiceio/presence.py +160 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/recorder.py +38 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/service.py +28 -6
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/setup/steps.py +1 -1
- python_voiceio-1.10.0/voiceio/sounds/alert.wav +0 -0
- python_voiceio-1.9.2/tests/test_learn_quiet.py +0 -103
- python_voiceio-1.9.2/voiceio/__init__.py +0 -1
- python_voiceio-1.9.2/voiceio/learn/quiet.py +0 -41
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/LICENSE +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/python_voiceio.egg-info/dependency_links.txt +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/python_voiceio.egg-info/entry_points.txt +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/python_voiceio.egg-info/requires.txt +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/python_voiceio.egg-info/top_level.txt +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/setup.cfg +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_app_wiring.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_audio_quality.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_audio_source.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_backend_probes.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_cancel.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_cli.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_clipboard_read.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_clipboard_typer.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_commands.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_concurrency_lockdown.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_config.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_context_terms.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_corrections.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_decoders.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_engine.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_evdev_uaccess.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_fallback.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_fcitx_bridge.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_feedback_stream.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_focus.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_health.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_hints.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_history.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_hotkey_modes.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_ibus_engine_limits.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_ibus_engine_listener.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_ibus_pending.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_ibus_pids.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_ibus_ping.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_ibus_session.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_ibus_textlimit.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_ibus_typer.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_idle_audio.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_learn_clean.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_learn_import.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_learn_language.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_learn_metrics.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_learn_pipeline.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_learn_review.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_learn_targets.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_llm.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_llm_api.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_modifier_guard.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_numbers.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_paste_last.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_pipeline.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_platform.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_postcorrect.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_postprocess.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_prebuffer.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_prompt.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_quiet_audio.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_recorder_integration.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_retention.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_robustness.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_security_hardening.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_server.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_setup.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_streaming.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_suggest.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_tokens.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_transcriber.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_tts.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_typer_manager.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_vad.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_vocabulary.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_wordfreq.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/tests/test_worker_decode.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/__main__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/audio_source.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/backends.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/cli/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/cli/configcmd.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/cli/corrections.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/cli/doctor.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/cli/history.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/cli/models.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/cli/servicecmd.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/cli/uninstall.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/cli/vocab.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/clipboard_read.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/commands.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/consent.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/context_terms.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/bias.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/decoders/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/decoders/base.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/decoders/catalog.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/decoders/lanes.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/decoders/sherpa.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/decoders/whispercpp.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/engine.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/core/pipeline.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/corrections.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/data/60-voiceio-uaccess.rules +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/data/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/demo.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/display.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/focus.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/health.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/hints.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/history.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/hotkeys/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/hotkeys/base.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/hotkeys/chain.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/hotkeys/evdev.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/hotkeys/pynput_backend.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/hotkeys/socket_backend.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/ibus/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/ibus/engine.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/ibus/install.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/ibus/pending.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/ibus/session.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/ibus/textlimit.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/idle_audio.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/learn/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/learn/augment.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/learn/clean.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/learn/importer.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/learn/metrics.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/learn/review.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/llm.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/llm_api.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/models/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/models/silero_vad.onnx +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/numbers.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/pidlock.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/platform.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/postcorrect.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/postprocess.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/power.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/privacy.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/prompt.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/quiet_audio.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/retention.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/server/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/server/__main__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/server/app.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/server/config.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/server/runtime.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/setup/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/setup/configfile.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/setup/extras.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/setup/hotkey.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/setup/noninteractive.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/setup/speech.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/setup/system.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/setup/ui.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/sounds/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/sounds/commit.wav +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/sounds/start.wav +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/sounds/stop.wav +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/streaming.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/suggest.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tokens.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tray/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tray/_icons.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tray/_indicator.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tray/_pystray.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tts/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tts/base.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tts/chain.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tts/edge_engine.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tts/espeak.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tts/piper_engine.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/tts/player.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/__init__.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/base.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/chain.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/clipboard.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/collecting.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/fcitx_bridge.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/ibus.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/manager.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/modifier_guard.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/paste.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/pynput_type.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/wtype.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/xdotool.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/typers/ydotool.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/ui.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/vad.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/vocab_stats.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/vocabulary.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/watchdog.py +0 -0
- {python_voiceio-1.9.2 → python_voiceio-1.10.0}/voiceio/wordfreq.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-voiceio
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.0
|
|
4
4
|
Summary: Voice dictation for Linux. Speak → text, locally, instantly.
|
|
5
5
|
Author: Hugo Montenegro
|
|
6
6
|
License-Expression: MIT
|
|
@@ -98,7 +98,7 @@ voiceio learn schedule on # learn from your recordings while the mac
|
|
|
98
98
|
voiceio learn status # what it learned and how each model scores
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
Scheduled learning runs daily or weekly at a time you pick, only
|
|
101
|
+
Scheduled learning runs daily or weekly at a time you pick, on mains power and only while you're away from the computer: it stops within seconds when you come back and resumes later where it was. You get a notification with a button to stop it. It never switches models unless the new one wins, and `voiceio learn rollback` undoes a switch. Details: [docs/learning.md](docs/learning.md).
|
|
102
102
|
|
|
103
103
|
## Configure
|
|
104
104
|
|
|
@@ -45,7 +45,7 @@ voiceio learn schedule on # learn from your recordings while the mac
|
|
|
45
45
|
voiceio learn status # what it learned and how each model scores
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
Scheduled learning runs daily or weekly at a time you pick, only
|
|
48
|
+
Scheduled learning runs daily or weekly at a time you pick, on mains power and only while you're away from the computer: it stops within seconds when you come back and resumes later where it was. You get a notification with a button to stop it. It never switches models unless the new one wins, and `voiceio learn rollback` undoes a switch. Details: [docs/learning.md](docs/learning.md).
|
|
49
49
|
|
|
50
50
|
## Configure
|
|
51
51
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-voiceio
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.0
|
|
4
4
|
Summary: Voice dictation for Linux. Speak → text, locally, instantly.
|
|
5
5
|
Author: Hugo Montenegro
|
|
6
6
|
License-Expression: MIT
|
|
@@ -98,7 +98,7 @@ voiceio learn schedule on # learn from your recordings while the mac
|
|
|
98
98
|
voiceio learn status # what it learned and how each model scores
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
Scheduled learning runs daily or weekly at a time you pick, only
|
|
101
|
+
Scheduled learning runs daily or weekly at a time you pick, on mains power and only while you're away from the computer: it stops within seconds when you come back and resumes later where it was. You get a notification with a button to stop it. It never switches models unless the new one wins, and `voiceio learn rollback` undoes a switch. Details: [docs/learning.md](docs/learning.md).
|
|
102
102
|
|
|
103
103
|
## Configure
|
|
104
104
|
|
|
@@ -20,6 +20,7 @@ tests/test_concurrency_lockdown.py
|
|
|
20
20
|
tests/test_config.py
|
|
21
21
|
tests/test_context_terms.py
|
|
22
22
|
tests/test_corrections.py
|
|
23
|
+
tests/test_dead_mic.py
|
|
23
24
|
tests/test_decoders.py
|
|
24
25
|
tests/test_engine.py
|
|
25
26
|
tests/test_evdev_uaccess.py
|
|
@@ -46,10 +47,12 @@ tests/test_learn_language.py
|
|
|
46
47
|
tests/test_learn_metrics.py
|
|
47
48
|
tests/test_learn_pipeline.py
|
|
48
49
|
tests/test_learn_quiet.py
|
|
50
|
+
tests/test_learn_resume.py
|
|
49
51
|
tests/test_learn_review.py
|
|
50
52
|
tests/test_learn_targets.py
|
|
51
53
|
tests/test_llm.py
|
|
52
54
|
tests/test_llm_api.py
|
|
55
|
+
tests/test_mic_gain.py
|
|
53
56
|
tests/test_modifier_guard.py
|
|
54
57
|
tests/test_numbers.py
|
|
55
58
|
tests/test_paste_last.py
|
|
@@ -58,6 +61,7 @@ tests/test_platform.py
|
|
|
58
61
|
tests/test_postcorrect.py
|
|
59
62
|
tests/test_postprocess.py
|
|
60
63
|
tests/test_prebuffer.py
|
|
64
|
+
tests/test_presence.py
|
|
61
65
|
tests/test_prompt.py
|
|
62
66
|
tests/test_quiet_audio.py
|
|
63
67
|
tests/test_recorder_integration.py
|
|
@@ -98,12 +102,14 @@ voiceio/history.py
|
|
|
98
102
|
voiceio/idle_audio.py
|
|
99
103
|
voiceio/llm.py
|
|
100
104
|
voiceio/llm_api.py
|
|
105
|
+
voiceio/mic_gain.py
|
|
101
106
|
voiceio/numbers.py
|
|
102
107
|
voiceio/pidlock.py
|
|
103
108
|
voiceio/platform.py
|
|
104
109
|
voiceio/postcorrect.py
|
|
105
110
|
voiceio/postprocess.py
|
|
106
111
|
voiceio/power.py
|
|
112
|
+
voiceio/presence.py
|
|
107
113
|
voiceio/privacy.py
|
|
108
114
|
voiceio/prompt.py
|
|
109
115
|
voiceio/quiet_audio.py
|
|
@@ -183,6 +189,7 @@ voiceio/setup/steps.py
|
|
|
183
189
|
voiceio/setup/system.py
|
|
184
190
|
voiceio/setup/ui.py
|
|
185
191
|
voiceio/sounds/__init__.py
|
|
192
|
+
voiceio/sounds/alert.wav
|
|
186
193
|
voiceio/sounds/commit.wav
|
|
187
194
|
voiceio/sounds/start.wav
|
|
188
195
|
voiceio/sounds/stop.wav
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""A mic stream that dies mid-recording is reopened, not recorded to the end.
|
|
2
|
+
|
|
3
|
+
The real failure: after a suspend/resume the stream delivered 2 s of audio,
|
|
4
|
+
then 102 s of exact zeros; the whole dictation was lost.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import threading
|
|
9
|
+
from unittest.mock import MagicMock, patch
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from voiceio.config import AudioConfig
|
|
14
|
+
from voiceio.recorder import AudioRecorder
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _recorder() -> AudioRecorder:
|
|
18
|
+
rec = AudioRecorder(AudioConfig(sample_rate=16000, prebuffer_secs=0.5,
|
|
19
|
+
auto_stop_silence_secs=0, auto_stop_no_speech_secs=0))
|
|
20
|
+
rec._stream = MagicMock()
|
|
21
|
+
return rec
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _feed(rec: AudioRecorder, value: float, secs: float) -> None:
|
|
25
|
+
for _ in range(int(secs * 16000 / 1600)):
|
|
26
|
+
rec._callback(np.full((1600, 1), value, dtype=np.float32), 1600, None, None)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _armed(rec: AudioRecorder) -> threading.Event:
|
|
30
|
+
fired = threading.Event()
|
|
31
|
+
rec.set_on_dead_stream(fired.set)
|
|
32
|
+
return fired
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_zeros_after_real_audio_fire_once():
|
|
36
|
+
rec = _recorder()
|
|
37
|
+
rec.start()
|
|
38
|
+
fired = _armed(rec)
|
|
39
|
+
_feed(rec, 0.02, 1.0)
|
|
40
|
+
_feed(rec, 0.0, 1.2)
|
|
41
|
+
assert fired.wait(2)
|
|
42
|
+
fired.clear()
|
|
43
|
+
_feed(rec, 0.0, 1.2) # still reviving: not fired again
|
|
44
|
+
assert not fired.wait(0.2)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_a_mic_silent_from_the_start_is_left_to_the_muted_check():
|
|
48
|
+
rec = _recorder()
|
|
49
|
+
rec.start()
|
|
50
|
+
fired = _armed(rec)
|
|
51
|
+
_feed(rec, 0.0, 2.0)
|
|
52
|
+
assert not fired.wait(0.2)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_quiet_room_noise_is_not_a_dead_stream():
|
|
56
|
+
rec = _recorder()
|
|
57
|
+
rec.start()
|
|
58
|
+
fired = _armed(rec)
|
|
59
|
+
_feed(rec, 0.02, 0.5)
|
|
60
|
+
_feed(rec, 0.0005, 3.0) # a working mic's silence is never exact zero
|
|
61
|
+
assert not fired.wait(0.2)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_revive_reopens_and_allows_at_most_two_attempts():
|
|
65
|
+
rec = _recorder()
|
|
66
|
+
rec.start()
|
|
67
|
+
with patch.object(rec, "open_stream") as opened, patch.object(rec, "close_stream"):
|
|
68
|
+
for expected in (True, True, False):
|
|
69
|
+
fired = _armed(rec)
|
|
70
|
+
_feed(rec, 0.02, 0.2)
|
|
71
|
+
_feed(rec, 0.0, 1.2)
|
|
72
|
+
assert fired.wait(0.5) is expected
|
|
73
|
+
if expected:
|
|
74
|
+
rec.revive_stream()
|
|
75
|
+
assert opened.call_count == 2
|
|
76
|
+
assert rec.is_recording
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_daemon_revives_and_warns_only_when_still_dead():
|
|
80
|
+
from voiceio.app import VoiceIO, _State
|
|
81
|
+
vio = VoiceIO.__new__(VoiceIO)
|
|
82
|
+
vio._hotkey_lock = threading.Lock()
|
|
83
|
+
vio._state = _State.RECORDING
|
|
84
|
+
vio._recording = 7
|
|
85
|
+
vio.recorder = MagicMock(silent_signal_secs=0.0)
|
|
86
|
+
with patch("voiceio.app.time.sleep"), patch("voiceio.feedback.notify") as notify:
|
|
87
|
+
vio._revive_mic(7)
|
|
88
|
+
vio.recorder.revive_stream.assert_called_once()
|
|
89
|
+
notify.assert_not_called()
|
|
90
|
+
vio.recorder.silent_signal_secs = 1.5
|
|
91
|
+
vio._revive_mic(7)
|
|
92
|
+
notify.assert_called_once()
|
|
93
|
+
vio._revive_mic(8) # a later recording: not ours to touch
|
|
94
|
+
assert vio.recorder.revive_stream.call_count == 2
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from voiceio import power
|
|
6
|
+
from voiceio.learn import quiet
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _supply(root, name, kind, online):
|
|
10
|
+
d = root / name
|
|
11
|
+
d.mkdir()
|
|
12
|
+
(d / "type").write_text(kind + "\n")
|
|
13
|
+
(d / "online").write_text(online + "\n")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_on_battery_needs_a_mains_supply_that_is_offline(tmp_path):
|
|
17
|
+
assert not power.on_battery(tmp_path) # desktop: no supplies listed
|
|
18
|
+
_supply(tmp_path, "BAT0", "Battery", "0")
|
|
19
|
+
_supply(tmp_path, "AC", "Mains", "0")
|
|
20
|
+
assert power.on_battery(tmp_path)
|
|
21
|
+
(tmp_path / "AC" / "online").write_text("1\n")
|
|
22
|
+
assert not power.on_battery(tmp_path)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_busy_reason_is_load_per_cpu_then_battery(monkeypatch):
|
|
26
|
+
monkeypatch.setattr(quiet.os, "cpu_count", lambda: 8)
|
|
27
|
+
monkeypatch.setattr(quiet, "on_battery", lambda: False)
|
|
28
|
+
monkeypatch.setattr(quiet.os, "getloadavg", lambda: (4.0, 0, 0)) # 0.5 per CPU
|
|
29
|
+
assert "load 0.50 per CPU" in quiet.busy_reason(0.25)
|
|
30
|
+
monkeypatch.setattr(quiet.os, "getloadavg", lambda: (1.0, 0, 0))
|
|
31
|
+
assert quiet.busy_reason(0.25) is None
|
|
32
|
+
monkeypatch.setattr(quiet, "on_battery", lambda: True)
|
|
33
|
+
assert quiet.busy_reason(0.25) == "on battery"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_wait_until_quiet_waits_then_gives_up(monkeypatch):
|
|
37
|
+
now = [0.0]
|
|
38
|
+
sleeps = []
|
|
39
|
+
reasons = iter(["busy", "busy", None])
|
|
40
|
+
monkeypatch.setattr(quiet, "busy_reason", lambda *_: next(reasons))
|
|
41
|
+
args = dict(poll=300, sleep=lambda s: (sleeps.append(s), now.__setitem__(0, now[0] + s)),
|
|
42
|
+
clock=lambda: now[0], say=lambda _: None)
|
|
43
|
+
assert quiet.wait_until_quiet(0.25, 3, **args)
|
|
44
|
+
assert sleeps == [300, 300]
|
|
45
|
+
|
|
46
|
+
monkeypatch.setattr(quiet, "busy_reason", lambda *_: "on battery")
|
|
47
|
+
said = []
|
|
48
|
+
now[0] = 0.0
|
|
49
|
+
assert not quiet.wait_until_quiet(0.25, 1, **{**args, "say": said.append})
|
|
50
|
+
assert now[0] <= 3600 and said[-1].startswith("still on battery after 1 h")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_busy_reason_waits_for_the_user_to_be_away(monkeypatch):
|
|
54
|
+
from voiceio import presence
|
|
55
|
+
monkeypatch.setattr(quiet, "on_battery", lambda: False)
|
|
56
|
+
monkeypatch.setattr(quiet.os, "getloadavg", lambda: (0.0, 0, 0))
|
|
57
|
+
seen = []
|
|
58
|
+
monkeypatch.setattr(presence, "check",
|
|
59
|
+
lambda away: seen.append(away) or presence.Presence(True, "in use (x)"))
|
|
60
|
+
assert quiet.busy_reason(0.25, away_after=600) == "in use (x)" and seen == [600]
|
|
61
|
+
assert quiet.busy_reason(0.25) is None # unattended `maintain`: no presence check
|
|
62
|
+
monkeypatch.setattr(presence, "check", lambda away: presence.Presence(False, "away"))
|
|
63
|
+
assert quiet.busy_reason(0.25, away_after=600) is None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_waiting_logs_each_reason_once_not_every_reading(monkeypatch):
|
|
67
|
+
readings = iter(["in use (last input 3 s ago)", "in use (last input 9 s ago)",
|
|
68
|
+
"on battery", None])
|
|
69
|
+
monkeypatch.setattr(quiet, "busy_reason", lambda *_: next(readings))
|
|
70
|
+
said = []
|
|
71
|
+
assert quiet.wait_until_quiet(0.25, 3, poll=1, sleep=lambda s: None, say=said.append)
|
|
72
|
+
assert said == ["waiting: in use (last input 3 s ago)", "waiting: on battery"]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class _Proc:
|
|
76
|
+
"""A worker that runs for `polls` checks, then exits with `code`."""
|
|
77
|
+
def __init__(self, polls=99, code=0):
|
|
78
|
+
self.left, self.code, self.pid, self.stopped = polls, code, 4242, False
|
|
79
|
+
|
|
80
|
+
def poll(self):
|
|
81
|
+
if self.stopped:
|
|
82
|
+
return -15
|
|
83
|
+
if self.left <= 0:
|
|
84
|
+
return self.code
|
|
85
|
+
self.left -= 1
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def returncode(self):
|
|
90
|
+
return self.poll()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _watch(monkeypatch, tmp_path, procs, reasons, *, quiet_ok=True):
|
|
94
|
+
spawned, said = [], []
|
|
95
|
+
procs = iter(procs)
|
|
96
|
+
reasons = iter(reasons)
|
|
97
|
+
|
|
98
|
+
def spawn(argv, env):
|
|
99
|
+
spawned.append(env)
|
|
100
|
+
return next(procs)
|
|
101
|
+
|
|
102
|
+
def stop(proc):
|
|
103
|
+
proc.stopped = True
|
|
104
|
+
monkeypatch.setattr(quiet, "wait_until_quiet", lambda *a, **k: quiet_ok)
|
|
105
|
+
monkeypatch.setattr(quiet, "stop_reason", lambda away: next(reasons, None))
|
|
106
|
+
code = quiet.run_while_away(["voiceio", "learn", "maintain"], max_load=0.25, away_minutes=10,
|
|
107
|
+
wait_hours=3, threads=3, hold_file=tmp_path / "hold",
|
|
108
|
+
spawn=spawn, stop=stop, sleep=lambda s: None, say=said.append)
|
|
109
|
+
return code, spawned, said
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_work_stops_when_the_user_returns_and_resumes_later(monkeypatch, tmp_path):
|
|
113
|
+
first, second = _Proc(), _Proc(polls=3, code=0)
|
|
114
|
+
code, spawned, said = _watch(monkeypatch, tmp_path, [first, second],
|
|
115
|
+
[None, None, "in use (last input 0 s ago)"])
|
|
116
|
+
assert code == 0 and first.stopped and not second.stopped
|
|
117
|
+
assert said == ["paused: in use (last input 0 s ago); it resumes where it stopped "
|
|
118
|
+
"once you've been away 10 min"]
|
|
119
|
+
assert spawned[0][quiet.WORKER_ENV] == "1" and spawned[0]["OMP_NUM_THREADS"] == "3"
|
|
120
|
+
assert quiet.RESUMED_ENV not in spawned[0] and spawned[1][quiet.RESUMED_ENV] == "1"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_watcher_reports_the_work_exit_status_and_gives_up_quietly(monkeypatch, tmp_path):
|
|
124
|
+
assert _watch(monkeypatch, tmp_path, [_Proc(polls=1, code=3)], [])[0] == 3
|
|
125
|
+
code, spawned, _ = _watch(monkeypatch, tmp_path, [], [], quiet_ok=False)
|
|
126
|
+
assert code == 0 and spawned == [] # never away: nothing started, unit doesn't fail
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_a_model_switch_in_progress_is_never_cut_off(monkeypatch, tmp_path):
|
|
130
|
+
proc = _Proc(polls=3, code=0)
|
|
131
|
+
reasons = iter(["in use"] * 10)
|
|
132
|
+
|
|
133
|
+
def spawn(argv, env):
|
|
134
|
+
(tmp_path / "hold").touch() # the worker holds it
|
|
135
|
+
return proc
|
|
136
|
+
monkeypatch.setattr(quiet, "wait_until_quiet", lambda *a, **k: True)
|
|
137
|
+
monkeypatch.setattr(quiet, "stop_reason", lambda away: next(reasons))
|
|
138
|
+
assert quiet.run_while_away(["x"], max_load=1, away_minutes=1, wait_hours=1, threads=1,
|
|
139
|
+
hold_file=tmp_path / "hold", spawn=spawn,
|
|
140
|
+
stop=lambda p: pytest.fail("cut off a model switch"),
|
|
141
|
+
sleep=lambda s: None, say=print) == 0
|
|
142
|
+
assert not (tmp_path / "hold").exists()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_hold_marks_the_file_only_inside_the_block(monkeypatch, tmp_path):
|
|
146
|
+
flag = tmp_path / "hold"
|
|
147
|
+
monkeypatch.setenv(quiet.HOLD_ENV, str(flag))
|
|
148
|
+
with quiet.hold():
|
|
149
|
+
assert flag.exists()
|
|
150
|
+
assert not flag.exists()
|
|
151
|
+
monkeypatch.delenv(quiet.HOLD_ENV)
|
|
152
|
+
with quiet.hold(): # not under the watcher: a no-op
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_stop_ends_the_whole_process_group_at_once():
|
|
157
|
+
"""The decoder runs in a subprocess of the worker: it must go too."""
|
|
158
|
+
import os
|
|
159
|
+
import time
|
|
160
|
+
proc = quiet._spawn(["sh", "-c", "sleep 30 & sleep 30; wait"], dict(os.environ))
|
|
161
|
+
time.sleep(0.2)
|
|
162
|
+
t0 = time.monotonic()
|
|
163
|
+
quiet._stop(proc)
|
|
164
|
+
assert time.monotonic() - t0 < 3 and proc.poll() is not None
|
|
165
|
+
with pytest.raises(ProcessLookupError):
|
|
166
|
+
os.killpg(proc.pid, 0) # nothing left in the group
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_threads_default_to_half_the_cpus(monkeypatch):
|
|
170
|
+
monkeypatch.setattr(quiet.os, "cpu_count", lambda: 16)
|
|
171
|
+
assert quiet.worker_threads(0) == 8 and quiet.worker_threads(5) == 5
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_maintain_in_the_worker_does_the_work(monkeypatch):
|
|
175
|
+
from voiceio.cli import learn as cli
|
|
176
|
+
monkeypatch.setattr(cli, "_yield_cpu", lambda: None)
|
|
177
|
+
monkeypatch.setenv(quiet.WORKER_ENV, "1")
|
|
178
|
+
monkeypatch.setattr(quiet, "run_while_away", lambda *a, **k: pytest.fail("worker must not watch"))
|
|
179
|
+
ran = []
|
|
180
|
+
monkeypatch.setattr(cli, "_maintain", lambda args, cfg: ran.append(cfg.away_minutes))
|
|
181
|
+
cli.cmd_maintain(argparse.Namespace(min_new=5, base="small", epochs=1, when_idle=True))
|
|
182
|
+
assert ran == [10.0]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_scheduled_maintain_fails_when_its_work_does(monkeypatch):
|
|
186
|
+
from voiceio.cli import learn as cli
|
|
187
|
+
monkeypatch.setattr(cli, "_yield_cpu", lambda: None)
|
|
188
|
+
monkeypatch.delenv(quiet.WORKER_ENV, raising=False)
|
|
189
|
+
monkeypatch.setattr(quiet, "run_while_away", lambda *a, **k: 2)
|
|
190
|
+
with pytest.raises(SystemExit) as e:
|
|
191
|
+
cli.cmd_maintain(argparse.Namespace(min_new=5, base="small", epochs=1, when_idle=True))
|
|
192
|
+
assert e.value.code == 2
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_scheduled_maintain_waits_for_a_quiet_machine(monkeypatch):
|
|
196
|
+
from voiceio.cli import learn as cli
|
|
197
|
+
monkeypatch.setattr(cli, "_yield_cpu", lambda: None)
|
|
198
|
+
monkeypatch.setattr(quiet, "wait_until_quiet", lambda *a, **k: False)
|
|
199
|
+
ran = []
|
|
200
|
+
monkeypatch.setattr(cli, "cmd_label", lambda a: ran.append("label"))
|
|
201
|
+
cli.cmd_maintain(argparse.Namespace(min_new=5, base="small", epochs=1, when_idle=True))
|
|
202
|
+
assert ran == [] # busy all night: nothing ran
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_learning_announcement_offers_stop_and_weekly(monkeypatch, tmp_path):
|
|
206
|
+
from voiceio import feedback
|
|
207
|
+
from voiceio.cli import learn as cli
|
|
208
|
+
sent = []
|
|
209
|
+
monkeypatch.setattr(feedback, "notify_actions", lambda *a: sent.append(a))
|
|
210
|
+
monkeypatch.setattr(cli.store, "load_labels", lambda: {})
|
|
211
|
+
monkeypatch.setattr(cli.store, "clips", lambda: [argparse.Namespace(audio=f"a{i}.wav") for i in range(3)])
|
|
212
|
+
monkeypatch.setattr(cli.store, "latest_dataset", lambda: None)
|
|
213
|
+
cli._announce_learning(min_new=50)
|
|
214
|
+
(title, body, actions, _), = sent
|
|
215
|
+
assert "learning" in title and body.startswith("Labeling 3 new recordings,")
|
|
216
|
+
assert "while you're away" in body and set(actions) == {"stop", "weekly"}
|
|
217
|
+
|
|
218
|
+
sent.clear()
|
|
219
|
+
monkeypatch.setattr(cli.store, "load_labels", lambda: {f"a{i}.wav": 1 for i in range(3)})
|
|
220
|
+
monkeypatch.setattr(cli.store, "latest_dataset", lambda: tmp_path)
|
|
221
|
+
monkeypatch.setattr(cli.store, "dataset_clips", lambda _: {f"a{i}.wav" for i in range(3)})
|
|
222
|
+
cli._announce_learning(min_new=50)
|
|
223
|
+
assert sent == [] # nothing to do: no notification
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_notify_actions_runs_the_picked_action(monkeypatch):
|
|
227
|
+
import subprocess
|
|
228
|
+
|
|
229
|
+
from voiceio import feedback
|
|
230
|
+
monkeypatch.setattr("sys.platform", "linux")
|
|
231
|
+
monkeypatch.setattr(feedback, "_which", lambda _: "/usr/bin/notify-send")
|
|
232
|
+
monkeypatch.setattr(feedback, "_notify_send_has_actions", lambda: True)
|
|
233
|
+
calls = []
|
|
234
|
+
|
|
235
|
+
def fake_run(args, **kw):
|
|
236
|
+
calls.append(args)
|
|
237
|
+
return subprocess.CompletedProcess(args, 0, stdout="weekly\n")
|
|
238
|
+
monkeypatch.setattr(feedback.subprocess, "run", fake_run)
|
|
239
|
+
picked = []
|
|
240
|
+
|
|
241
|
+
class Now: # run the waiting thread inline
|
|
242
|
+
def __init__(self, target, daemon): self.target = target
|
|
243
|
+
def start(self): self.target()
|
|
244
|
+
monkeypatch.setattr(feedback.threading, "Thread", Now)
|
|
245
|
+
feedback.notify_actions("t", "b", {"stop": "Stop", "weekly": "Weekly"}, picked.append)
|
|
246
|
+
assert picked == ["weekly"]
|
|
247
|
+
assert "--wait" in calls[0] and "--action=stop=Stop" in calls[0]
|