python-voiceio 1.8.1__tar.gz → 1.8.3__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.8.1/python_voiceio.egg-info → python_voiceio-1.8.3}/PKG-INFO +1 -1
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/pyproject.toml +1 -1
- {python_voiceio-1.8.1 → python_voiceio-1.8.3/python_voiceio.egg-info}/PKG-INFO +1 -1
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/python_voiceio.egg-info/SOURCES.txt +1 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_cancel.py +5 -2
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_ibus_textlimit.py +27 -0
- python_voiceio-1.8.3/tests/test_learn_language.py +39 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_postprocess.py +15 -2
- python_voiceio-1.8.3/voiceio/__init__.py +1 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/cli/learn.py +14 -1
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/ibus/engine.py +1 -4
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/ibus/textlimit.py +13 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/learn/teacher.py +1 -1
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/postprocess.py +32 -3
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/ibus.py +9 -4
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/wordfreq.py +15 -3
- python_voiceio-1.8.1/voiceio/__init__.py +0 -1
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/LICENSE +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/README.md +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/python_voiceio.egg-info/dependency_links.txt +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/python_voiceio.egg-info/entry_points.txt +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/python_voiceio.egg-info/requires.txt +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/python_voiceio.egg-info/top_level.txt +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/setup.cfg +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_app_wiring.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_audio_quality.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_audio_source.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_backend_probes.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_cli.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_clipboard_read.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_clipboard_typer.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_commands.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_concurrency_lockdown.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_config.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_corrections.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_decoders.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_engine.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_evdev_uaccess.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_fallback.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_fcitx_bridge.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_feedback_stream.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_focus.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_health.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_hints.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_history.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_hotkey_modes.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_ibus_engine_limits.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_ibus_engine_listener.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_ibus_pending.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_ibus_pids.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_ibus_ping.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_ibus_session.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_ibus_typer.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_idle_audio.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_learn_clean.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_learn_import.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_learn_metrics.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_learn_pipeline.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_learn_quiet.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_learn_review.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_learn_targets.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_llm.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_llm_api.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_modifier_guard.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_numbers.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_paste_last.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_pipeline.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_platform.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_postcorrect.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_prebuffer.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_prompt.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_quiet_audio.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_recorder_integration.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_retention.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_robustness.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_security_hardening.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_server.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_service.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_setup.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_streaming.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_suggest.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_tokens.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_transcriber.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_tts.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_typer_manager.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_vad.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_vocabulary.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_wordfreq.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/tests/test_worker_decode.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/__main__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/app.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/audio_source.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/backends.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/cli/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/cli/configcmd.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/cli/corrections.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/cli/doctor.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/cli/history.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/cli/models.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/cli/servicecmd.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/cli/uninstall.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/cli/vocab.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/clipboard_read.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/commands.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/config.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/consent.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/bias.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/decoders/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/decoders/base.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/decoders/catalog.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/decoders/lanes.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/decoders/sherpa.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/decoders/whispercpp.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/engine.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/core/pipeline.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/corrections.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/data/60-voiceio-uaccess.rules +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/data/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/demo.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/display.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/feedback.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/focus.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/health.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/hints.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/history.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/hotkeys/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/hotkeys/base.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/hotkeys/chain.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/hotkeys/evdev.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/hotkeys/pynput_backend.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/hotkeys/socket_backend.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/ibus/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/ibus/install.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/ibus/pending.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/ibus/session.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/idle_audio.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/learn/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/learn/augment.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/learn/clean.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/learn/evaluate.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/learn/importer.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/learn/metrics.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/learn/quiet.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/learn/review.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/learn/store.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/learn/train.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/llm.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/llm_api.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/models/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/models/silero_vad.onnx +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/numbers.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/pidlock.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/platform.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/postcorrect.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/power.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/privacy.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/prompt.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/quiet_audio.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/recorder.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/retention.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/server/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/server/__main__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/server/app.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/server/config.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/server/runtime.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/service.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/setup/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/setup/configfile.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/setup/extras.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/setup/hotkey.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/setup/noninteractive.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/setup/speech.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/setup/steps.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/setup/system.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/setup/ui.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/sounds/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/sounds/commit.wav +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/sounds/start.wav +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/sounds/stop.wav +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/streaming.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/suggest.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tokens.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tray/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tray/_icons.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tray/_indicator.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tray/_pystray.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tts/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tts/base.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tts/chain.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tts/edge_engine.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tts/espeak.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tts/piper_engine.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/tts/player.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/__init__.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/base.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/chain.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/clipboard.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/collecting.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/fcitx_bridge.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/manager.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/modifier_guard.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/paste.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/pynput_type.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/wtype.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/xdotool.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/typers/ydotool.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/ui.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/vad.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/vocab_stats.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/vocabulary.py +0 -0
- {python_voiceio-1.8.1 → python_voiceio-1.8.3}/voiceio/watchdog.py +0 -0
|
@@ -156,14 +156,17 @@ def test_cancel_too_late_changes_nothing(tmp_path):
|
|
|
156
156
|
session = MagicMock()
|
|
157
157
|
session.cancel.return_value = False # final text already typed
|
|
158
158
|
session.stop.side_effect = lambda audio: (in_stop.set(), release.wait(5), "words")[2]
|
|
159
|
+
# The finalizer goes on to copy the text from its own thread; left real, it
|
|
160
|
+
# lands on whatever clipboard a later test has patched in.
|
|
161
|
+
vio._copy_result_async = MagicMock()
|
|
159
162
|
_finalizing(vio, tmp_path, session)
|
|
160
163
|
assert in_stop.wait(5)
|
|
161
164
|
gen = vio._generation
|
|
162
165
|
vio.cancel()
|
|
163
166
|
assert vio._state == _State.FINALIZING and vio._generation == gen
|
|
164
167
|
release.set()
|
|
165
|
-
for _ in range(
|
|
166
|
-
if vio._state == _State.IDLE:
|
|
168
|
+
for _ in range(500):
|
|
169
|
+
if vio._state == _State.IDLE and not vio._outputs:
|
|
167
170
|
break
|
|
168
171
|
time.sleep(0.01)
|
|
169
172
|
vio.engine.commit.assert_called_once()
|
|
@@ -92,3 +92,30 @@ def test_preedit_tail_survives_multibyte_text():
|
|
|
92
92
|
shown = tail_utf8(text)
|
|
93
93
|
assert _nbytes(shown) <= MAX_TEXT_BYTES
|
|
94
94
|
assert shown == TRUNCATION_MARK + "é" * ((MAX_TEXT_BYTES - _nbytes(TRUNCATION_MARK)) // 2)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_commit_secs_zero_for_one_chunk():
|
|
98
|
+
from voiceio.ibus.textlimit import commit_secs
|
|
99
|
+
assert commit_secs("short note") == 0.0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_commit_secs_covers_every_chunk_gap(long_note):
|
|
103
|
+
from voiceio.ibus.textlimit import COMMIT_GAP_MS, commit_secs
|
|
104
|
+
n = len(split_utf8(long_note))
|
|
105
|
+
assert n > 1
|
|
106
|
+
assert commit_secs(long_note) == n * COMMIT_GAP_MS / 1000
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_ibus_typer_holds_a_chunked_commit_until_delivered(long_note, monkeypatch):
|
|
110
|
+
"""Returning early let the input source be restored while tail chunks
|
|
111
|
+
were still pending, so a long note lost its end."""
|
|
112
|
+
from voiceio.typers import ibus as ibus_typer
|
|
113
|
+
t = ibus_typer.IBusTyper.__new__(ibus_typer.IBusTyper)
|
|
114
|
+
sent, slept = [], []
|
|
115
|
+
monkeypatch.setattr(t, "_send", sent.append, raising=False)
|
|
116
|
+
monkeypatch.setattr(t, "_copy_to_clipboard", lambda _t: None, raising=False)
|
|
117
|
+
monkeypatch.setattr(t, "_engine_has_focus", lambda: True, raising=False)
|
|
118
|
+
monkeypatch.setattr(ibus_typer.time, "sleep", slept.append)
|
|
119
|
+
t.commit_text(long_note)
|
|
120
|
+
assert sent == [f"commit:{long_note}"]
|
|
121
|
+
assert slept and slept[-1] >= 0.3
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Fine-tuning and labeling follow the dictation language."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from voiceio.cli import learn as learn_cli
|
|
7
|
+
from voiceio.config import Config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _with_language(monkeypatch, lang):
|
|
11
|
+
cfg = Config()
|
|
12
|
+
cfg.model.language = lang
|
|
13
|
+
monkeypatch.setattr(learn_cli, "_cfg", lambda: cfg)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_training_uses_the_dictation_language(monkeypatch):
|
|
17
|
+
"""A Spanish voice trained under the "en" token learned the wrong prompt."""
|
|
18
|
+
_with_language(monkeypatch, "es")
|
|
19
|
+
assert learn_cli._train_language() == "es"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_training_refuses_auto_with_a_hint(monkeypatch):
|
|
23
|
+
_with_language(monkeypatch, "auto")
|
|
24
|
+
with pytest.raises(SystemExit, match="model.language"):
|
|
25
|
+
learn_cli._train_language()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_teacher_detects_when_language_is_auto(monkeypatch):
|
|
29
|
+
from voiceio.learn import teacher
|
|
30
|
+
monkeypatch.setattr("voiceio.core.decoders.faster_whisper.worker.load_model", lambda *a, **k: object())
|
|
31
|
+
assert teacher.WhisperTeacher("tiny", language="auto")._language is None
|
|
32
|
+
assert teacher.WhisperTeacher("tiny", language="es")._language == "es"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_word_frequency_survives_auto_and_unknown_languages():
|
|
36
|
+
"""language = "auto" raised LookupError in vocabulary biasing."""
|
|
37
|
+
from voiceio.wordfreq import is_common, is_known
|
|
38
|
+
assert is_common("house", "auto") and is_known("casa", "es")
|
|
39
|
+
assert not is_common("house", "xx")
|
|
@@ -102,7 +102,20 @@ class TestCapitalization:
|
|
|
102
102
|
assert cleanup("hello world") == "Hello world"
|
|
103
103
|
|
|
104
104
|
def test_after_period(self):
|
|
105
|
-
assert cleanup("hello
|
|
105
|
+
assert cleanup("hello? world") == "Hello? World"
|
|
106
|
+
assert cleanup("Hello. World") == "Hello. World"
|
|
107
|
+
|
|
108
|
+
def test_lowercase_after_a_segment_stop_is_a_comma(self):
|
|
109
|
+
# Whisper ends each segment with "." and continues the sentence in
|
|
110
|
+
# lowercase; capitalizing it made a false stop permanent.
|
|
111
|
+
assert cleanup("a bit bigger. and remove the latch.") == "A bit bigger, and remove the latch."
|
|
112
|
+
assert cleanup("hello. world") == "Hello, world"
|
|
113
|
+
|
|
114
|
+
def test_abbreviations_and_dotted_names_keep_their_dot(self):
|
|
115
|
+
assert cleanup("pears etc. and more") == "Pears etc. and more"
|
|
116
|
+
assert cleanup("use e.g. this one") == "Use e.g. this one"
|
|
117
|
+
assert cleanup("go to hugo0.com. and then") == "Go to hugo0.com. and then"
|
|
118
|
+
assert cleanup("I paused... and then") == "I paused... and then"
|
|
106
119
|
|
|
107
120
|
def test_after_question_mark(self):
|
|
108
121
|
assert cleanup("really? yes") == "Really? Yes"
|
|
@@ -184,7 +197,7 @@ class TestEdgeCases:
|
|
|
184
197
|
assert result == "..."
|
|
185
198
|
|
|
186
199
|
def test_unicode_accents(self):
|
|
187
|
-
assert cleanup("caf
|
|
200
|
+
assert cleanup("café? résumé") == "Café? Résumé"
|
|
188
201
|
|
|
189
202
|
|
|
190
203
|
class TestWordMatchCompatibility:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.8.3"
|
|
@@ -417,6 +417,16 @@ def cmd_train(args: argparse.Namespace) -> None:
|
|
|
417
417
|
f" (compare with the current model, then `voiceio learn promote {run.name}`)")
|
|
418
418
|
|
|
419
419
|
|
|
420
|
+
def _train_language() -> str:
|
|
421
|
+
"""The language a fine-tune is trained under: the one dictation decodes
|
|
422
|
+
with. "auto" has no single answer, so it has to be set first."""
|
|
423
|
+
lang = _cfg().model.language or "en"
|
|
424
|
+
if lang == "auto":
|
|
425
|
+
raise SystemExit("Fine-tuning needs one language: set it with "
|
|
426
|
+
"`voiceio config set model.language es` (your dictation language).")
|
|
427
|
+
return lang
|
|
428
|
+
|
|
429
|
+
|
|
420
430
|
def _train(ds: Path, *, base: str = "small", epochs: int = 4, synthetic_share: float = 0.3,
|
|
421
431
|
full: bool = False, remote: bool = False) -> Path | None:
|
|
422
432
|
"""Export `ds` and fine-tune on it; returns the run dir (None for --remote)."""
|
|
@@ -433,8 +443,11 @@ def _train(ds: Path, *, base: str = "small", epochs: int = 4, synthetic_share: f
|
|
|
433
443
|
return None
|
|
434
444
|
train.require_train_deps()
|
|
435
445
|
hotwords = (folder / train.HOTWORDS_FILE).read_text(encoding="utf-8")
|
|
446
|
+
# The language token the model is trained under must be the one it decodes
|
|
447
|
+
# with: training a Spanish voice under "en" taught it the wrong prompt.
|
|
436
448
|
hf = train.train(folder, run, train.TrainConfig(
|
|
437
|
-
base=base, epochs=epochs, decoder_only=False if full else None, hotwords=hotwords
|
|
449
|
+
base=base, epochs=epochs, decoder_only=False if full else None, hotwords=hotwords,
|
|
450
|
+
language=_train_language()))
|
|
438
451
|
train.convert(hf, run / "ct2")
|
|
439
452
|
return run
|
|
440
453
|
|
|
@@ -30,7 +30,7 @@ from voiceio.ibus import (
|
|
|
30
30
|
acquire_singleton_lock,
|
|
31
31
|
)
|
|
32
32
|
from voiceio.ibus.pending import PendingBuffer
|
|
33
|
-
from voiceio.ibus.textlimit import split_utf8, tail_utf8
|
|
33
|
+
from voiceio.ibus.textlimit import COMMIT_GAP_MS, split_utf8, tail_utf8
|
|
34
34
|
|
|
35
35
|
log = logging.getLogger(__name__)
|
|
36
36
|
|
|
@@ -44,9 +44,6 @@ COMPONENT_NAME = "org.voiceio.ibus"
|
|
|
44
44
|
# largest datagram a sender can produce (SO_SNDBUF, ~208K by default) so that
|
|
45
45
|
# cannot happen.
|
|
46
46
|
RECV_BYTES = 262144
|
|
47
|
-
# Between commit chunks: long enough for the compositor to flush the previous
|
|
48
|
-
# one to the app, short enough that a 10KB note lands in a blink.
|
|
49
|
-
COMMIT_GAP_MS = 40
|
|
50
47
|
|
|
51
48
|
|
|
52
49
|
class VoiceIOEngine(IBus.Engine):
|
|
@@ -14,6 +14,19 @@ from __future__ import annotations
|
|
|
14
14
|
|
|
15
15
|
MAX_TEXT_BYTES = 2048
|
|
16
16
|
TRUNCATION_MARK = "… "
|
|
17
|
+
# Between commit chunks. A chunk the app hasn't acknowledged yet is replaced,
|
|
18
|
+
# not extended, by the next one, and a busy Chromium/Electron window took
|
|
19
|
+
# longer than the 40 ms this used to be: long notes landed as their last chunk.
|
|
20
|
+
COMMIT_GAP_MS = 150
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def commit_secs(text: str) -> float:
|
|
24
|
+
"""How long the engine needs to deliver ``text``: zero for one chunk, else
|
|
25
|
+
one gap per extra chunk plus one for the last to be acknowledged. The
|
|
26
|
+
caller must not hand the input source back before this has passed, or
|
|
27
|
+
the tail chunks arrive after the app lost focus."""
|
|
28
|
+
n = len(split_utf8(text))
|
|
29
|
+
return 0.0 if n <= 1 else n * COMMIT_GAP_MS / 1000
|
|
17
30
|
|
|
18
31
|
|
|
19
32
|
def _is_continuation(byte: int) -> bool:
|
|
@@ -45,7 +45,7 @@ class WhisperTeacher:
|
|
|
45
45
|
# A background batch job (see cli.learn._yield_cpu): use every core idle.
|
|
46
46
|
self._model = load_model(model, device=device, compute_type=compute_type,
|
|
47
47
|
cpu_threads=max(4, (os.cpu_count() or 8) // 2))
|
|
48
|
-
self._language = language
|
|
48
|
+
self._language = None if language in ("", "auto") else language # None: detect
|
|
49
49
|
terms = [t for t in vocabulary if t]
|
|
50
50
|
# Prompt, not hotwords: hotwords are capped at 223 tokens; a prompt
|
|
51
51
|
# of the most important terms still biases spelling. The tokenizer
|
|
@@ -23,6 +23,21 @@ _NO_CASE_LANGUAGES = frozenset({"zh", "ja", "ko", "ar", "he", "th", "hi", "bn",
|
|
|
23
23
|
_FILLER_RE = re.compile(
|
|
24
24
|
r"[^\S\n]*,?[^\S\n]*\b(?:[Uu]h[-\s]?huh|[Mm]hm|[Uu]h+m*|[Uu]m+|[Hh]m{2,})\b[^\S\n]*,?[^\S\n]*",
|
|
25
25
|
)
|
|
26
|
+
# A word (2+ letters, not part of a dotted name or an ellipsis) ending in "."
|
|
27
|
+
# and followed by a lowercase word: a segment boundary, not a sentence end.
|
|
28
|
+
_SEGMENT_STOP_RE = re.compile(r"(?<![.\w])([A-Za-zÀ-ɏ]{2,})\.(\s+)(?=[a-zà-ÿ])")
|
|
29
|
+
_ABBREVIATIONS = frozenset({
|
|
30
|
+
"etc", "vs", "approx", "cf", "al", "mr", "mrs", "ms", "dr", "st", "jr", "sr",
|
|
31
|
+
"inc", "ltd", "co", "fig", "eq", "vol", "no", "pp", "ca", "min", "max", "sec",
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _not_an_ending(word: str) -> bool:
|
|
36
|
+
"""The word before a "." is an abbreviation or a dotted name, not a
|
|
37
|
+
sentence's last word."""
|
|
38
|
+
return "." in word or word.lower() in _ABBREVIATIONS
|
|
39
|
+
|
|
40
|
+
|
|
26
41
|
# A re-decode artifact is a whole duplicated sentence; require this many words
|
|
27
42
|
# so emphatic short repeats ("No. No.", "Stop. Stop.") are preserved.
|
|
28
43
|
_MIN_DEDUP_WORDS = 4
|
|
@@ -95,14 +110,28 @@ def cleanup(text: str, language: str = "en") -> str:
|
|
|
95
110
|
text = re.sub(r"\s+([.,;:?!])", r"\1", text)
|
|
96
111
|
|
|
97
112
|
if language not in _NO_CASE_LANGUAGES:
|
|
113
|
+
# Whisper closes every segment with "." even mid-sentence, and the next
|
|
114
|
+
# segment then carries on in lowercase ("a bit bigger. and remove the
|
|
115
|
+
# latch"): on real dictation that was 1 in 4 notes. A lowercase
|
|
116
|
+
# continuation means the sentence did not end, so it gets a comma —
|
|
117
|
+
# before the capitalization below would make the false stop permanent.
|
|
118
|
+
text = _SEGMENT_STOP_RE.sub(
|
|
119
|
+
lambda m: m.group(0) if m.group(1).lower() in _ABBREVIATIONS
|
|
120
|
+
else m.group(1) + "," + m.group(2),
|
|
121
|
+
text,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
98
125
|
# Capitalize first character
|
|
99
126
|
text = text[0].upper() + text[1:]
|
|
100
127
|
|
|
101
128
|
# Capitalize after sentence-ending punctuation (an ellipsis is a
|
|
102
|
-
# pause, not an ending
|
|
129
|
+
# pause, not an ending, and so is the dot of "etc.", "e.g." or a name
|
|
130
|
+
# like "hugo0.com")
|
|
103
131
|
text = re.sub(
|
|
104
|
-
r"(?<!\.\.)([.?!]\s+)([a-z\u00E0-\u00FF])",
|
|
105
|
-
lambda m: m.group(
|
|
132
|
+
r"(\S*)(?<!\.\.)([.?!]\s+)([a-z\u00E0-\u00FF])",
|
|
133
|
+
lambda m: m.group(0) if m.group(2)[0] == "." and _not_an_ending(m.group(1))
|
|
134
|
+
else m.group(1) + m.group(2) + m.group(3).upper(),
|
|
106
135
|
text,
|
|
107
136
|
)
|
|
108
137
|
|
|
@@ -10,6 +10,7 @@ import time
|
|
|
10
10
|
|
|
11
11
|
from voiceio.backends import ProbeResult
|
|
12
12
|
from voiceio.ibus import SOCKET_PATH, install
|
|
13
|
+
from voiceio.ibus.textlimit import commit_secs
|
|
13
14
|
from voiceio.typers import paste
|
|
14
15
|
from voiceio.typers.ydotool import ydotoold_running
|
|
15
16
|
|
|
@@ -123,10 +124,7 @@ class IBusTyper:
|
|
|
123
124
|
def type_text(self, text: str) -> None:
|
|
124
125
|
if not text:
|
|
125
126
|
return
|
|
126
|
-
self.
|
|
127
|
-
self._copy_to_clipboard(text)
|
|
128
|
-
if not self._engine_has_focus():
|
|
129
|
-
self._simulate_paste()
|
|
127
|
+
self._commit(text)
|
|
130
128
|
|
|
131
129
|
def delete_chars(self, n: int) -> None:
|
|
132
130
|
pass # Not needed: preedit handles corrections atomically
|
|
@@ -140,10 +138,17 @@ class IBusTyper:
|
|
|
140
138
|
self._send("clear")
|
|
141
139
|
return
|
|
142
140
|
log.debug("Committing via IBus (%d chars)", len(text))
|
|
141
|
+
self._commit(text)
|
|
142
|
+
|
|
143
|
+
def _commit(self, text: str) -> None:
|
|
143
144
|
self._send(f"commit:{text}")
|
|
144
145
|
self._copy_to_clipboard(text)
|
|
145
146
|
if not self._engine_has_focus():
|
|
146
147
|
self._simulate_paste()
|
|
148
|
+
return
|
|
149
|
+
# A long note goes out in timed chunks; returning early would let the
|
|
150
|
+
# caller restore the input source while the tail is still pending.
|
|
151
|
+
time.sleep(commit_secs(text))
|
|
147
152
|
|
|
148
153
|
def clear_preedit(self) -> None:
|
|
149
154
|
self._send("clear")
|
|
@@ -41,14 +41,26 @@ _CONTRACTIONS = frozenset({
|
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def _zipf(word: str, language: str) -> float:
|
|
45
|
+
"""Zipf frequency, 0.0 for a language wordfreq has no list for. "auto"
|
|
46
|
+
(detect per dictation) has no single list either: score it as English,
|
|
47
|
+
which is where the jargon in a vocabulary list usually comes from."""
|
|
48
|
+
if language in ("", "auto"):
|
|
49
|
+
language = "en"
|
|
50
|
+
try:
|
|
51
|
+
return zipf_frequency(word.lower(), language)
|
|
52
|
+
except LookupError:
|
|
53
|
+
return 0.0
|
|
54
|
+
|
|
55
|
+
|
|
44
56
|
def is_common(word: str, language: str = "en", threshold: float = _ZIPF_COMMON_THRESHOLD) -> bool:
|
|
45
57
|
"""Check if a word is common (high frequency). Used for suggesting corrections."""
|
|
46
|
-
return
|
|
58
|
+
return _zipf(word, language) >= threshold
|
|
47
59
|
|
|
48
60
|
|
|
49
61
|
def rank(word: str, language: str = "en") -> int | None:
|
|
50
62
|
"""Get approximate frequency rank. None if not found."""
|
|
51
|
-
z =
|
|
63
|
+
z = _zipf(word, language)
|
|
52
64
|
if z == 0.0:
|
|
53
65
|
return None
|
|
54
66
|
# Convert zipf to approximate rank (higher zipf = lower rank number)
|
|
@@ -61,7 +73,7 @@ def is_known(word: str, language: str = "en") -> bool:
|
|
|
61
73
|
wl = word.lower()
|
|
62
74
|
if wl in _CONTRACTIONS:
|
|
63
75
|
return True
|
|
64
|
-
return
|
|
76
|
+
return _zipf(wl, language) >= _ZIPF_KNOWN_THRESHOLD
|
|
65
77
|
|
|
66
78
|
|
|
67
79
|
def extract_words(text: str) -> list[str]:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.8.1"
|
|
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
|
|
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
|
|
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
|