python-voiceio 1.12.0__tar.gz → 1.13.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.12.0/python_voiceio.egg-info → python_voiceio-1.13.0}/PKG-INFO +2 -2
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/README.md +1 -1
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/pyproject.toml +1 -1
- {python_voiceio-1.12.0 → python_voiceio-1.13.0/python_voiceio.egg-info}/PKG-INFO +2 -2
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/python_voiceio.egg-info/SOURCES.txt +1 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_sherpa.py +6 -1
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_tts.py +76 -0
- python_voiceio-1.13.0/voiceio/__init__.py +1 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/cli/models.py +4 -1
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/config.py +2 -1
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/decoders/catalog.py +18 -5
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/decoders/fetch.py +18 -8
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/health.py +4 -4
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/server/runtime.py +1 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tts/chain.py +4 -1
- python_voiceio-1.13.0/voiceio/tts/supertonic_engine.py +91 -0
- python_voiceio-1.12.0/voiceio/__init__.py +0 -1
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/LICENSE +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/python_voiceio.egg-info/dependency_links.txt +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/python_voiceio.egg-info/entry_points.txt +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/python_voiceio.egg-info/requires.txt +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/python_voiceio.egg-info/top_level.txt +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/setup.cfg +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_app_wiring.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_audio_quality.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_audio_source.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_backend_probes.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_cancel.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_cli.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_clipboard_read.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_clipboard_typer.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_commands.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_concurrency_lockdown.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_config.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_context_terms.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_corrections.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_dead_mic.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_decoders.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_engine.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_evdev_uaccess.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_fallback.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_fcitx_bridge.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_feedback_stream.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_focus.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_health.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_hints.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_history.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_hotkey_modes.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_ibus_engine_limits.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_ibus_engine_listener.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_ibus_pending.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_ibus_pids.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_ibus_ping.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_ibus_session.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_ibus_textlimit.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_ibus_typer.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_idle_audio.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_learn_clean.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_learn_import.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_learn_language.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_learn_metrics.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_learn_pipeline.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_learn_quiet.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_learn_resume.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_learn_review.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_learn_targets.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_llm.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_llm_api.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_mic_gain.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_modifier_guard.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_numbers.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_paste_last.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_pipeline.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_platform.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_postcorrect.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_postprocess.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_prebuffer.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_presence.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_prompt.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_quiet_audio.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_recorder_integration.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_report.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_retention.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_robustness.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_security_hardening.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_server.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_service.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_setup.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_streaming.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_suggest.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_tokens.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_transcriber.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_typer_manager.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_vad.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_vocabulary.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_wordfreq.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/tests/test_worker_decode.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/__main__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/app.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/audio_source.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/backends.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/cli/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/cli/configcmd.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/cli/corrections.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/cli/doctor.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/cli/history.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/cli/learn.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/cli/servicecmd.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/cli/uninstall.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/cli/vocab.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/clipboard_read.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/commands.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/consent.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/context_terms.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/bias.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/decoders/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/decoders/base.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/decoders/lanes.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/decoders/sherpa.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/decoders/whispercpp.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/engine.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/core/pipeline.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/corrections.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/data/60-voiceio-uaccess.rules +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/data/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/demo.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/display.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/feedback.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/focus.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/hints.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/history.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/hotkeys/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/hotkeys/base.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/hotkeys/chain.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/hotkeys/evdev.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/hotkeys/pynput_backend.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/hotkeys/socket_backend.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/ibus/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/ibus/engine.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/ibus/install.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/ibus/pending.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/ibus/session.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/ibus/textlimit.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/idle_audio.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/learn/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/learn/augment.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/learn/clean.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/learn/evaluate.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/learn/importer.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/learn/metrics.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/learn/quiet.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/learn/review.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/learn/store.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/learn/teacher.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/learn/train.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/llm.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/llm_api.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/mic_gain.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/models/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/models/silero_vad.onnx +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/numbers.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/pidlock.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/platform.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/postcorrect.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/postprocess.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/power.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/presence.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/privacy.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/prompt.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/quiet_audio.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/recorder.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/report.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/retention.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/server/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/server/__main__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/server/app.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/server/config.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/service.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/setup/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/setup/configfile.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/setup/extras.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/setup/hotkey.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/setup/noninteractive.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/setup/speech.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/setup/steps.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/setup/system.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/setup/ui.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/sounds/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/sounds/alert.wav +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/sounds/commit.wav +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/sounds/start.wav +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/sounds/stop.wav +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/streaming.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/suggest.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tokens.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tray/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tray/_icons.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tray/_indicator.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tray/_pystray.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tts/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tts/base.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tts/edge_engine.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tts/espeak.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tts/piper_engine.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/tts/player.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/__init__.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/base.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/chain.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/clipboard.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/collecting.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/fcitx_bridge.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/ibus.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/manager.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/modifier_guard.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/paste.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/pynput_type.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/wtype.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/xdotool.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/typers/ydotool.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/ui.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/vad.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/vocab_stats.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/vocabulary.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.0}/voiceio/watchdog.py +0 -0
- {python_voiceio-1.12.0 → python_voiceio-1.13.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.13.0
|
|
4
4
|
Summary: Voice dictation for Linux. Speak → text, locally, instantly.
|
|
5
5
|
Author: Hugo Montenegro
|
|
6
6
|
License-Expression: MIT
|
|
@@ -65,7 +65,7 @@ Dynamic: license-file
|
|
|
65
65
|
**[Try it in your browser at voiceio.dev](https://voiceio.dev)**: no install, the model runs in the tab.
|
|
66
66
|
|
|
67
67
|
- **Types anywhere.** Press your hotkey, speak, and the words stream into the focused app with a live underlined preview (IBus), on Wayland and X11: GNOME, KDE, Hyprland, sway, i3.
|
|
68
|
-
- **Runs on your machine.** Whisper, or NVIDIA's Parakeet (
|
|
68
|
+
- **Runs on your machine.** Whisper, or NVIDIA's Parakeet (text lands ~0.7 s after you stop talking instead of ~4.4 s, with every name you use as a hotword), decodes your speech locally. No account, no server, no telemetry; logs never contain your words.
|
|
69
69
|
- **Learns your words.** Keep your recordings (opt-in, on your disk) and voiceio fine-tunes its model on them while the machine is idle. It switches only when the new model wins on clips it never trained on. On the author's voice: 12.1% → 4.9% word errors after one night on a laptop CPU.
|
|
70
70
|
|
|
71
71
|
## Install
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
**[Try it in your browser at voiceio.dev](https://voiceio.dev)**: no install, the model runs in the tab.
|
|
13
13
|
|
|
14
14
|
- **Types anywhere.** Press your hotkey, speak, and the words stream into the focused app with a live underlined preview (IBus), on Wayland and X11: GNOME, KDE, Hyprland, sway, i3.
|
|
15
|
-
- **Runs on your machine.** Whisper, or NVIDIA's Parakeet (
|
|
15
|
+
- **Runs on your machine.** Whisper, or NVIDIA's Parakeet (text lands ~0.7 s after you stop talking instead of ~4.4 s, with every name you use as a hotword), decodes your speech locally. No account, no server, no telemetry; logs never contain your words.
|
|
16
16
|
- **Learns your words.** Keep your recordings (opt-in, on your disk) and voiceio fine-tunes its model on them while the machine is idle. It switches only when the new model wins on clips it never trained on. On the author's voice: 12.1% → 4.9% word errors after one night on a laptop CPU.
|
|
17
17
|
|
|
18
18
|
## Install
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-voiceio
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.13.0
|
|
4
4
|
Summary: Voice dictation for Linux. Speak → text, locally, instantly.
|
|
5
5
|
Author: Hugo Montenegro
|
|
6
6
|
License-Expression: MIT
|
|
@@ -65,7 +65,7 @@ Dynamic: license-file
|
|
|
65
65
|
**[Try it in your browser at voiceio.dev](https://voiceio.dev)**: no install, the model runs in the tab.
|
|
66
66
|
|
|
67
67
|
- **Types anywhere.** Press your hotkey, speak, and the words stream into the focused app with a live underlined preview (IBus), on Wayland and X11: GNOME, KDE, Hyprland, sway, i3.
|
|
68
|
-
- **Runs on your machine.** Whisper, or NVIDIA's Parakeet (
|
|
68
|
+
- **Runs on your machine.** Whisper, or NVIDIA's Parakeet (text lands ~0.7 s after you stop talking instead of ~4.4 s, with every name you use as a hotword), decodes your speech locally. No account, no server, no telemetry; logs never contain your words.
|
|
69
69
|
- **Learns your words.** Keep your recordings (opt-in, on your disk) and voiceio fine-tunes its model on them while the machine is idle. It switches only when the new model wins on clips it never trained on. On the author's voice: 12.1% → 4.9% word errors after one night on a laptop CPU.
|
|
70
70
|
|
|
71
71
|
## Install
|
|
@@ -195,7 +195,8 @@ def test_default_is_parakeet_only_when_sherpa_has_the_fix(monkeypatch):
|
|
|
195
195
|
assert catalog.default_model("en") == "small"
|
|
196
196
|
monkeypatch.setattr(sherpa, "features", lambda: SherpaFeatures(True, True, False))
|
|
197
197
|
assert catalog.default_model("en") == catalog.PARAKEET
|
|
198
|
-
assert catalog.default_model("es") ==
|
|
198
|
+
assert catalog.default_model("es") == catalog.PARAKEET_V3
|
|
199
|
+
assert catalog.default_model("ja") == "small" # outside Parakeet v3's languages
|
|
199
200
|
|
|
200
201
|
|
|
201
202
|
def test_config_resolves_unset_or_auto_name_but_an_explicit_choice_wins(monkeypatch, tmp_path):
|
|
@@ -343,6 +344,10 @@ def test_doctor_reports_the_decoder(monkeypatch):
|
|
|
343
344
|
assert "aren't used" in _learning_status(cfg)["Voice model"]
|
|
344
345
|
cfg.model.name = "small"
|
|
345
346
|
assert catalog.PARAKEET in _decoder_status(cfg)["Decoder"]
|
|
347
|
+
cfg.model.language = "es"
|
|
348
|
+
assert catalog.PARAKEET_V3 in _decoder_status(cfg)["Decoder"]
|
|
349
|
+
cfg.model.language = "ja"
|
|
350
|
+
assert _decoder_status(cfg) == {}
|
|
346
351
|
|
|
347
352
|
|
|
348
353
|
def test_eval_results_are_found_for_sherpa_models(monkeypatch, tmp_path):
|
|
@@ -423,3 +423,79 @@ def test_piper_shutdown_releases_voice():
|
|
|
423
423
|
engine._voice = MagicMock()
|
|
424
424
|
engine.shutdown()
|
|
425
425
|
assert engine._voice is None
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# ── supertonic tests ─────────────────────────────────────────────────
|
|
429
|
+
|
|
430
|
+
class _FakeGenerated:
|
|
431
|
+
samples = [0.0, 0.5, -1.5]
|
|
432
|
+
sample_rate = 44100
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _fake_sherpa(calls):
|
|
436
|
+
so = MagicMock()
|
|
437
|
+
|
|
438
|
+
class Tts:
|
|
439
|
+
def __init__(self, config):
|
|
440
|
+
calls["config"] = config
|
|
441
|
+
|
|
442
|
+
def generate(self, text, gen):
|
|
443
|
+
calls["text"], calls["gen"] = text, gen
|
|
444
|
+
return _FakeGenerated()
|
|
445
|
+
|
|
446
|
+
so.OfflineTts = Tts
|
|
447
|
+
so.GenerationConfig = lambda: type("G", (), {})()
|
|
448
|
+
return so
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _supertonic_dir(path):
|
|
452
|
+
from voiceio.tts import supertonic_engine as st
|
|
453
|
+
for stem in st._STEMS:
|
|
454
|
+
(path / f"{stem}.int8.onnx").write_bytes(b"")
|
|
455
|
+
for f in st._FILES:
|
|
456
|
+
(path / f).write_bytes(b"")
|
|
457
|
+
return path
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def test_supertonic_probe_needs_supertonic_in_sherpa():
|
|
461
|
+
from voiceio.tts.supertonic_engine import SupertonicEngine
|
|
462
|
+
old = MagicMock(spec=[]) # a sherpa-onnx without OfflineTtsSupertonicModelConfig
|
|
463
|
+
with patch.dict("sys.modules", {"sherpa_onnx": old}):
|
|
464
|
+
assert not SupertonicEngine().probe().ok
|
|
465
|
+
with patch.dict("sys.modules", {"sherpa_onnx": MagicMock()}):
|
|
466
|
+
assert SupertonicEngine().probe().ok
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def test_supertonic_reads_in_its_language_and_returns_int16(tmp_path):
|
|
470
|
+
from voiceio.tts import supertonic_engine as st
|
|
471
|
+
calls = {}
|
|
472
|
+
model = _supertonic_dir(tmp_path)
|
|
473
|
+
with patch.dict("sys.modules", {"sherpa_onnx": _fake_sherpa(calls)}), \
|
|
474
|
+
patch("voiceio.core.decoders.fetch.download_archive", return_value=model) as dl:
|
|
475
|
+
engine = st.SupertonicEngine(language="es")
|
|
476
|
+
audio, rate = engine.synthesize("hola", voice="", speed=0)
|
|
477
|
+
engine.synthesize("otra vez", voice="2", speed=1.2)
|
|
478
|
+
dl.assert_called_once() # loaded once, then reused
|
|
479
|
+
assert dl.call_args.args[0] == st.MODEL
|
|
480
|
+
assert calls["gen"].extra == {"lang": "es"}
|
|
481
|
+
assert calls["gen"].sid == 2 and calls["gen"].speed == 1.2
|
|
482
|
+
assert rate == 44100 and audio.dtype == np.int16
|
|
483
|
+
assert audio.tolist()[2] == -32767 # clipped, not wrapped
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def test_supertonic_auto_language_reads_english():
|
|
487
|
+
from voiceio.tts.supertonic_engine import SupertonicEngine
|
|
488
|
+
assert SupertonicEngine(language="auto")._language == "en"
|
|
489
|
+
assert SupertonicEngine()._language == "en"
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def test_supertonic_completeness_check(tmp_path):
|
|
493
|
+
from voiceio.tts import supertonic_engine as st
|
|
494
|
+
assert not st._complete(tmp_path)
|
|
495
|
+
assert st._complete(_supertonic_dir(tmp_path))
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def test_chain_passes_language_to_supertonic():
|
|
499
|
+
from voiceio.tts import chain
|
|
500
|
+
engine = chain._create("supertonic", TTSConfig(engine="supertonic", language="de"))
|
|
501
|
+
assert engine.name == "supertonic" and engine._language == "de"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.13.0"
|
|
@@ -180,4 +180,7 @@ def cmd_sherpa(args: argparse.Namespace) -> None:
|
|
|
180
180
|
wheel = fetch.install_patched_sherpa()
|
|
181
181
|
except OSError as e:
|
|
182
182
|
raise SystemExit(str(e)) from e
|
|
183
|
-
|
|
183
|
+
from voiceio import config
|
|
184
|
+
from voiceio.core.decoders.catalog import PARAKEET, PARAKEET_V3
|
|
185
|
+
name = PARAKEET if config.load().model.language == "en" else PARAKEET_V3
|
|
186
|
+
print(f"installed {wheel}\nnext: voiceio models use {name}")
|
|
@@ -248,11 +248,12 @@ class PostCorrectConfig:
|
|
|
248
248
|
@dataclass
|
|
249
249
|
class TTSConfig:
|
|
250
250
|
enabled: bool = True
|
|
251
|
-
engine: str = "auto" # "auto" | "piper" | "espeak" | "edge-tts"
|
|
251
|
+
engine: str = "auto" # "auto" | "piper" | "supertonic" | "espeak" | "edge-tts"
|
|
252
252
|
hotkey: str = "ctrl+alt+s" # "s" for speak
|
|
253
253
|
voice: str = "" # empty = engine default
|
|
254
254
|
speed: float = 1.0 # 0.5–2.0
|
|
255
255
|
model: str = "" # piper model name, empty = default
|
|
256
|
+
language: str = "" # supertonic: language to read in (en, es, de…), empty = en
|
|
256
257
|
|
|
257
258
|
|
|
258
259
|
@dataclass
|
|
@@ -15,6 +15,11 @@ from voiceio.core.decoders.base import BiasMode
|
|
|
15
15
|
|
|
16
16
|
WHISPER_DEFAULT = "small"
|
|
17
17
|
PARAKEET = "parakeet-tdt-0.6b-v2"
|
|
18
|
+
PARAKEET_V3 = "parakeet-tdt-0.6b-v3"
|
|
19
|
+
# The 25 European languages Parakeet v3 was trained on. It detects which one
|
|
20
|
+
# is spoken by itself, so `[model] language` only picks the model.
|
|
21
|
+
PARAKEET_V3_LANGUAGES = frozenset(
|
|
22
|
+
"bg hr cs da nl en et fi fr de el hu it lv lt mt pl pt ro sk sl es sv ru uk".split())
|
|
18
23
|
|
|
19
24
|
|
|
20
25
|
@dataclass(frozen=True)
|
|
@@ -43,11 +48,18 @@ _SHERPA_RELEASES = "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-
|
|
|
43
48
|
# unlimited hotwords), and Qwen3-ASR (free-text context biasing).
|
|
44
49
|
CATALOG: tuple[ModelInfo, ...] = (
|
|
45
50
|
ModelInfo(PARAKEET, "sherpa-onnx", "600M", "CC-BY-4.0", BiasMode.UNBOUNDED, languages="en",
|
|
46
|
-
notes="default when sherpa-onnx has the NeMo TDT beam-search fix: ~
|
|
47
|
-
"
|
|
51
|
+
notes="default when sherpa-onnx has the NeMo TDT beam-search fix: text ~0.7s after you stop "
|
|
52
|
+
"talking vs ~4.4s with Whisper, every proper noun of your vocabulary as a hotword",
|
|
48
53
|
url=f"{_SHERPA_RELEASES}/sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8.tar.bz2",
|
|
49
54
|
sha256="157c157bc51155e03e37d2466522a3a737dd9c72bb25f36eb18912964161e1ad",
|
|
50
55
|
size_mb=482),
|
|
56
|
+
ModelInfo(PARAKEET_V3, "sherpa-onnx", "600M", "CC-BY-4.0", BiasMode.UNBOUNDED,
|
|
57
|
+
languages=",".join(sorted(PARAKEET_V3_LANGUAGES)),
|
|
58
|
+
notes="the default for the other 24 European languages it covers; v2 stays the "
|
|
59
|
+
"English default (trained on English only, lower English WER)",
|
|
60
|
+
url=f"{_SHERPA_RELEASES}/sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8.tar.bz2",
|
|
61
|
+
sha256="5793d0fd397c5778d2cf2126994d58e9d56b1be7c04d13c7a15bb1b4eafb16bf",
|
|
62
|
+
size_mb=487),
|
|
51
63
|
ModelInfo("small", "faster-whisper", "244M", "MIT", BiasMode.HOTWORDS | BiasMode.PROMPT,
|
|
52
64
|
notes="~5x realtime on a laptop CPU; hotwords cap ~35 terms; "
|
|
53
65
|
"the model `voiceio learn` fine-tunes"),
|
|
@@ -97,10 +109,11 @@ def default_model(language: str = "en") -> str:
|
|
|
97
109
|
Parakeet once the installed sherpa-onnx has the NeMo TDT
|
|
98
110
|
modified_beam_search fix (without it, hotwords lose speech — see
|
|
99
111
|
CLAUDE.md "Model choice"), so the default flips by itself when upstream
|
|
100
|
-
ships the fix. English
|
|
112
|
+
ships the fix. English gets v2; the other languages Parakeet v3 covers get
|
|
113
|
+
v3; the rest keep Whisper.
|
|
101
114
|
"""
|
|
102
|
-
if language
|
|
115
|
+
if language in PARAKEET_V3_LANGUAGES:
|
|
103
116
|
from voiceio.core.decoders.sherpa import features
|
|
104
117
|
if features().length_reward:
|
|
105
|
-
return PARAKEET
|
|
118
|
+
return PARAKEET if language == "en" else PARAKEET_V3
|
|
106
119
|
return WHISPER_DEFAULT
|
|
@@ -68,19 +68,29 @@ def download(name: str, *, progress: Callable[[int, int], None] | None = None) -
|
|
|
68
68
|
m: ModelInfo | None = info(name)
|
|
69
69
|
if m is None or not m.url:
|
|
70
70
|
raise OSError(f"{name} is not a model voiceio downloads itself")
|
|
71
|
+
return download_archive(name, m.url, m.sha256, m.size_mb, is_complete,
|
|
72
|
+
what="encoder/decoder/joiner/tokens.txt", progress=progress)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def download_archive(name: str, url: str, sha256: str, size_mb: int,
|
|
76
|
+
complete: Callable[[Path], bool], *, what: str,
|
|
77
|
+
progress: Callable[[int, int], None] | None = None) -> Path:
|
|
78
|
+
"""Fetch, verify and unpack a sherpa-onnx release tarball as
|
|
79
|
+
`models_dir()/name`. `complete` says whether a directory holds the whole
|
|
80
|
+
model (`what` names its files for the error)."""
|
|
71
81
|
dest = model_path(name)
|
|
72
|
-
if
|
|
82
|
+
if complete(dest):
|
|
73
83
|
return dest
|
|
74
84
|
root = models_dir()
|
|
75
85
|
root.mkdir(parents=True, exist_ok=True)
|
|
76
86
|
part = root / f".{name}.{os.getpid()}.part"
|
|
77
87
|
staging = root / f".{name}.{os.getpid()}.unpack"
|
|
78
|
-
log.info("Downloading %s (%d MB) from %s", name,
|
|
88
|
+
log.info("Downloading %s (%d MB) from %s", name, size_mb, url)
|
|
79
89
|
try:
|
|
80
90
|
digest = hashlib.sha256()
|
|
81
|
-
req = urllib.request.Request(
|
|
91
|
+
req = urllib.request.Request(url, headers={"User-Agent": "voiceio"})
|
|
82
92
|
with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp, open(part, "wb") as out:
|
|
83
|
-
total = int(resp.headers.get("Content-Length") or
|
|
93
|
+
total = int(resp.headers.get("Content-Length") or size_mb * 1_000_000)
|
|
84
94
|
done = 0
|
|
85
95
|
while block := resp.read(_BLOCK):
|
|
86
96
|
out.write(block)
|
|
@@ -88,12 +98,12 @@ def download(name: str, *, progress: Callable[[int, int], None] | None = None) -
|
|
|
88
98
|
done += len(block)
|
|
89
99
|
if progress is not None:
|
|
90
100
|
progress(done, total)
|
|
91
|
-
if digest.hexdigest() !=
|
|
101
|
+
if digest.hexdigest() != sha256:
|
|
92
102
|
raise OSError(f"{name}: checksum mismatch (got {digest.hexdigest()[:12]}…, "
|
|
93
|
-
f"want {
|
|
103
|
+
f"want {sha256[:12]}…) — download again")
|
|
94
104
|
_unpack(part, staging)
|
|
95
|
-
if not
|
|
96
|
-
raise OSError(f"{name}: the archive has no
|
|
105
|
+
if not complete(staging):
|
|
106
|
+
raise OSError(f"{name}: the archive has no {what}")
|
|
97
107
|
shutil.rmtree(dest, ignore_errors=True) # an incomplete earlier copy
|
|
98
108
|
os.replace(staging, dest)
|
|
99
109
|
except OSError:
|
|
@@ -244,7 +244,7 @@ def _check_features() -> FeatureGroups:
|
|
|
244
244
|
def _decoder_status(cfg) -> dict[str, str]:
|
|
245
245
|
"""For a sherpa-onnx model: whether it can be biased, and why not. For
|
|
246
246
|
Whisper: whether Parakeet would do better here."""
|
|
247
|
-
from voiceio.core.decoders.catalog import
|
|
247
|
+
from voiceio.core.decoders.catalog import WHISPER_DEFAULT, default_model, runtime_of
|
|
248
248
|
from voiceio.core.decoders.sherpa import features
|
|
249
249
|
f = features()
|
|
250
250
|
if runtime_of(cfg.model.name) == "sherpa-onnx":
|
|
@@ -256,9 +256,9 @@ def _decoder_status(cfg) -> dict[str, str]:
|
|
|
256
256
|
how = "pre-tokenized" if f.pretokenized_hotwords else \
|
|
257
257
|
"sherpa-tokenized, misses many names — fix: voiceio models sherpa"
|
|
258
258
|
return {"Decoder": f"beam search, every proper noun as a hotword ({how})"}
|
|
259
|
-
if f.length_reward and cfg.model.language
|
|
260
|
-
return {"Decoder": f"Whisper; {
|
|
261
|
-
f"voiceio models use {
|
|
259
|
+
if f.length_reward and (best := default_model(cfg.model.language)) != WHISPER_DEFAULT:
|
|
260
|
+
return {"Decoder": f"Whisper; {best} would put your text down several times "
|
|
261
|
+
f"sooner: voiceio models use {best}"}
|
|
262
262
|
return {}
|
|
263
263
|
|
|
264
264
|
|
|
@@ -13,10 +13,11 @@ _ENGINES = {
|
|
|
13
13
|
"espeak": "voiceio.tts.espeak:EspeakEngine",
|
|
14
14
|
"piper": "voiceio.tts.piper_engine:PiperEngine",
|
|
15
15
|
"edge-tts": "voiceio.tts.edge_engine:EdgeEngine",
|
|
16
|
+
"supertonic": "voiceio.tts.supertonic_engine:SupertonicEngine",
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
# Preference order when engine = "auto"
|
|
19
|
-
_AUTO_ORDER = ["piper", "edge-tts", "espeak"]
|
|
20
|
+
_AUTO_ORDER = ["piper", "supertonic", "edge-tts", "espeak"]
|
|
20
21
|
|
|
21
22
|
# Engines that send the text off this machine to be synthesized. Auto-selection
|
|
22
23
|
# can silently land on one (edge-tts needs no install, so it probes OK wherever
|
|
@@ -35,6 +36,8 @@ def _create(name: str, cfg: TTSConfig):
|
|
|
35
36
|
cls = getattr(module, cls_name)
|
|
36
37
|
if name == "piper":
|
|
37
38
|
return cls(model=cfg.model)
|
|
39
|
+
if name == "supertonic":
|
|
40
|
+
return cls(language=cfg.language)
|
|
38
41
|
return cls()
|
|
39
42
|
|
|
40
43
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Supertonic 3 TTS engine — neural, offline, 31 languages, via sherpa-onnx.
|
|
2
|
+
|
|
3
|
+
Faster than real time on a laptop CPU (RTF ~0.5 at 2 threads) in ~300 MB,
|
|
4
|
+
where Kokoro and Pocket TTS ran slower than real time on the same box. It
|
|
5
|
+
renders a whole sentence before playing, so the first audio comes after
|
|
6
|
+
~2 s: worth it for reading text aloud, not for snappy one-word replies, which
|
|
7
|
+
is why Piper stays first in the auto chain. Weights are OpenRAIL-M.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from voiceio.backends import ProbeResult
|
|
17
|
+
|
|
18
|
+
log = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
MODEL = "sherpa-onnx-supertonic-3-tts-int8-2026-05-11"
|
|
21
|
+
_URL = f"https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/{MODEL}.tar.bz2"
|
|
22
|
+
_SHA256 = "82fa96f91c4ef8abaae3a14a3f4153facf88bed821d1f7331cec2700f432c427"
|
|
23
|
+
_SIZE_MB = 129
|
|
24
|
+
_STEMS = ("duration_predictor", "text_encoder", "vector_estimator", "vocoder")
|
|
25
|
+
_FILES = ("tts.json", "unicode_indexer.bin", "voice.bin")
|
|
26
|
+
_HINT = "pip install 'python-voiceio[sherpa]' (then: voiceio models sherpa)"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _complete(path: Path) -> bool:
|
|
30
|
+
return all((path / f).is_file() for f in _FILES) and all(
|
|
31
|
+
any(path.glob(f"{stem}*.onnx")) for stem in _STEMS)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _onnx(path: Path, stem: str) -> str:
|
|
35
|
+
return str(sorted(path.glob(f"{stem}*.onnx"))[0])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SupertonicEngine:
|
|
39
|
+
name = "supertonic"
|
|
40
|
+
|
|
41
|
+
def __init__(self, language: str = "", threads: int = 2):
|
|
42
|
+
# "auto" can't name a language to read in; English is the safe reading.
|
|
43
|
+
self._language = language if language not in ("", "auto") else "en"
|
|
44
|
+
self._threads = threads
|
|
45
|
+
self._tts = None # lazy: the first synthesize() loads (and maybe fetches)
|
|
46
|
+
|
|
47
|
+
def probe(self) -> ProbeResult:
|
|
48
|
+
try:
|
|
49
|
+
import sherpa_onnx
|
|
50
|
+
except ImportError as e:
|
|
51
|
+
return ProbeResult(ok=False, reason=f"sherpa-onnx missing: {e}", fix_hint=_HINT)
|
|
52
|
+
if not hasattr(sherpa_onnx, "OfflineTtsSupertonicModelConfig"):
|
|
53
|
+
return ProbeResult(ok=False, reason="this sherpa-onnx has no Supertonic support",
|
|
54
|
+
fix_hint=_HINT)
|
|
55
|
+
return ProbeResult(ok=True)
|
|
56
|
+
|
|
57
|
+
def _ensure(self) -> None:
|
|
58
|
+
if self._tts is not None:
|
|
59
|
+
return
|
|
60
|
+
import sherpa_onnx as so
|
|
61
|
+
|
|
62
|
+
from voiceio.core.decoders import fetch
|
|
63
|
+
path = fetch.download_archive(
|
|
64
|
+
MODEL, _URL, _SHA256, _SIZE_MB, _complete,
|
|
65
|
+
what=", ".join((*_STEMS, *_FILES)))
|
|
66
|
+
model = so.OfflineTtsModelConfig(num_threads=self._threads, provider="cpu")
|
|
67
|
+
model.supertonic = so.OfflineTtsSupertonicModelConfig(
|
|
68
|
+
duration_predictor=_onnx(path, "duration_predictor"),
|
|
69
|
+
text_encoder=_onnx(path, "text_encoder"),
|
|
70
|
+
vector_estimator=_onnx(path, "vector_estimator"),
|
|
71
|
+
vocoder=_onnx(path, "vocoder"),
|
|
72
|
+
tts_json=str(path / "tts.json"),
|
|
73
|
+
unicode_indexer=str(path / "unicode_indexer.bin"),
|
|
74
|
+
voice_style=str(path / "voice.bin"))
|
|
75
|
+
log.info("TTS: loading Supertonic 3 (%s)…", self._language)
|
|
76
|
+
self._tts = so.OfflineTts(so.OfflineTtsConfig(model=model, max_num_sentences=1))
|
|
77
|
+
|
|
78
|
+
def synthesize(self, text: str, voice: str, speed: float) -> tuple[np.ndarray, int]:
|
|
79
|
+
import sherpa_onnx as so
|
|
80
|
+
|
|
81
|
+
self._ensure()
|
|
82
|
+
gen = so.GenerationConfig()
|
|
83
|
+
gen.sid = int(voice) if voice.isdigit() else 0
|
|
84
|
+
gen.speed = speed if speed > 0 else 1.0
|
|
85
|
+
gen.extra = {"lang": self._language}
|
|
86
|
+
out = self._tts.generate(text, gen)
|
|
87
|
+
samples = np.asarray(out.samples, dtype=np.float32)
|
|
88
|
+
return (np.clip(samples, -1.0, 1.0) * 32767).astype(np.int16), out.sample_rate
|
|
89
|
+
|
|
90
|
+
def shutdown(self) -> None:
|
|
91
|
+
self._tts = None
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.12.0"
|
|
File without changes
|
{python_voiceio-1.12.0 → python_voiceio-1.13.0}/python_voiceio.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|