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