python-voiceio 1.4.0__tar.gz → 1.5.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.4.0/python_voiceio.egg-info → python_voiceio-1.5.0}/PKG-INFO +9 -2
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/README.md +8 -1
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/pyproject.toml +1 -1
- {python_voiceio-1.4.0 → python_voiceio-1.5.0/python_voiceio.egg-info}/PKG-INFO +9 -2
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_learn_pipeline.py +102 -0
- python_voiceio-1.5.0/voiceio/__init__.py +1 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/cli/learn.py +32 -12
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/config.py +8 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/learn/train.py +11 -1
- python_voiceio-1.4.0/voiceio/__init__.py +0 -1
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/LICENSE +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/python_voiceio.egg-info/SOURCES.txt +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/python_voiceio.egg-info/dependency_links.txt +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/python_voiceio.egg-info/entry_points.txt +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/python_voiceio.egg-info/requires.txt +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/python_voiceio.egg-info/top_level.txt +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/setup.cfg +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_app_wiring.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_audio_quality.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_audio_source.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_backend_probes.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_cli.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_clipboard_read.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_commands.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_concurrency_lockdown.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_config.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_corrections.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_decoders.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_engine.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_evdev_uaccess.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_fallback.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_fcitx_bridge.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_health.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_hints.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_history.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_ibus_engine_limits.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_ibus_engine_listener.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_ibus_pending.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_ibus_pids.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_ibus_ping.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_ibus_session.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_ibus_textlimit.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_ibus_typer.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_learn_clean.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_learn_import.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_learn_metrics.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_learn_quiet.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_learn_review.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_learn_targets.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_llm.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_llm_api.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_numbers.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_pipeline.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_platform.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_postcorrect.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_postprocess.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_prebuffer.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_prompt.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_recorder_integration.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_retention.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_robustness.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_security_hardening.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_server.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_service.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_setup.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_streaming.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_suggest.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_tokens.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_transcriber.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_tts.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_typer_manager.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_vad.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_vocabulary.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_wordfreq.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/tests/test_worker_decode.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/__main__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/app.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/audio_source.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/backends.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/cli/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/cli/configcmd.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/cli/corrections.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/cli/doctor.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/cli/history.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/cli/models.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/cli/servicecmd.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/cli/uninstall.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/cli/vocab.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/clipboard_read.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/commands.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/consent.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/bias.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/decoders/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/decoders/base.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/decoders/catalog.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/decoders/lanes.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/decoders/sherpa.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/decoders/whispercpp.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/engine.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/pipeline.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/corrections.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/data/60-voiceio-uaccess.rules +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/data/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/demo.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/display.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/feedback.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/health.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/hints.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/history.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/hotkeys/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/hotkeys/base.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/hotkeys/chain.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/hotkeys/evdev.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/hotkeys/pynput_backend.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/hotkeys/socket_backend.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/ibus/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/ibus/engine.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/ibus/install.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/ibus/pending.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/ibus/session.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/ibus/textlimit.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/learn/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/learn/augment.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/learn/clean.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/learn/evaluate.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/learn/importer.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/learn/metrics.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/learn/quiet.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/learn/review.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/learn/store.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/learn/teacher.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/llm.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/llm_api.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/models/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/models/silero_vad.onnx +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/numbers.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/pidlock.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/platform.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/postcorrect.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/postprocess.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/privacy.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/prompt.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/recorder.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/retention.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/server/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/server/__main__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/server/app.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/server/config.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/server/runtime.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/service.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/setup/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/setup/configfile.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/setup/extras.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/setup/hotkey.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/setup/noninteractive.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/setup/speech.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/setup/steps.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/setup/system.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/setup/ui.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/sounds/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/sounds/commit.wav +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/sounds/start.wav +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/sounds/stop.wav +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/streaming.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/suggest.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tokens.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tray/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tray/_icons.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tray/_indicator.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tray/_pystray.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tts/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tts/base.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tts/chain.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tts/edge_engine.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tts/espeak.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tts/piper_engine.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/tts/player.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/__init__.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/base.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/chain.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/clipboard.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/collecting.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/fcitx_bridge.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/ibus.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/manager.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/pynput_type.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/wtype.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/xdotool.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/typers/ydotool.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/ui.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/vad.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/vocab_stats.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/vocabulary.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/watchdog.py +0 -0
- {python_voiceio-1.4.0 → python_voiceio-1.5.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.5.0
|
|
4
4
|
Summary: Voice dictation for Linux. Speak → text, locally, instantly.
|
|
5
5
|
Author: Hugo Montenegro
|
|
6
6
|
License-Expression: MIT
|
|
@@ -190,7 +190,7 @@ voiceio learn eval # score the current model on your held-out clips: WER a
|
|
|
190
190
|
# --streaming replays them through live dictation and times stop→text
|
|
191
191
|
voiceio learn train # LoRA fine-tune (needs the `train` extra; decoder-only on CPU, --full on a GPU)
|
|
192
192
|
voiceio learn promote <run> # dictate with the fine-tune; `voiceio learn rollback` undoes it
|
|
193
|
-
voiceio learn maintain # later: label what's new, and once 50+ clips piled up, retrain and compare
|
|
193
|
+
voiceio learn maintain # later: label what's new, and once 50+ clips piled up ([learn] min_new), retrain and compare
|
|
194
194
|
voiceio learn schedule on # …or let it run `maintain` on a schedule, and notify you when a model wins
|
|
195
195
|
# --at 02:00 --every week: when (default daily at 03:30)
|
|
196
196
|
# --auto-promote: switch to a winner by itself; `learn rollback` undoes it
|
|
@@ -199,6 +199,8 @@ voiceio learn stop # stop a run in progress (it goes again next time)
|
|
|
199
199
|
|
|
200
200
|
Scheduled runs wait for a quiet machine: they start only when the 1-minute load per CPU is at most `[learn] max_load` (0.25) and you're on mains power, and give up after `wait_hours` (3) until the next run. When one starts you get a notification with **Stop for now** and **Weekly instead**.
|
|
201
201
|
|
|
202
|
+
What a scheduled run trains comes from `[learn]` too: `base` (default `small`; any Whisper size, `.en` included, or a Hugging Face checkpoint), `teacher` (default `large-v3-turbo`), `epochs` (4) and `min_new` (50); `maintain`'s flags override them. To fine-tune the English-only medium model: `voiceio config set learn.base medium.en` (on CPU, medium trains and dictates roughly three times slower than small).
|
|
203
|
+
|
|
202
204
|
To keep a second machine (say, the server your phone dictates through) on the same model, set `[learn] on_promote = "scripts/push_model.sh HOST"`: after every switch it copies the model there and flips a `current` link the server points at.
|
|
203
205
|
|
|
204
206
|
Labeling, training and offline eval run in an idle-priority cgroup, so they never slow dictation down. Everything stays on your machine unless you choose `train --remote`.
|
|
@@ -315,6 +317,11 @@ Logs: `journalctl --user -u voiceio` or `~/.local/state/voiceio/voiceio.log`.
|
|
|
315
317
|
|
|
316
318
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the architecture, conventions and the reasoning behind the model choice. Please open an issue before a large PR.
|
|
317
319
|
|
|
320
|
+
## Credits
|
|
321
|
+
|
|
322
|
+
- Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models. The in-browser demo on [voiceio.dev](https://voiceio.dev) runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
|
|
323
|
+
- The landing page's look (dithered art, typewriter heading, hairline bento) takes its inspiration from Romario Kavin's [KOLlateral](https://github.com/RomarioKavin1/kollateral).
|
|
324
|
+
|
|
318
325
|
## License
|
|
319
326
|
|
|
320
327
|
MIT. Model weights are not part of voiceio and carry their own licenses (see [Choose a model](#choose-a-model)).
|
|
@@ -137,7 +137,7 @@ voiceio learn eval # score the current model on your held-out clips: WER a
|
|
|
137
137
|
# --streaming replays them through live dictation and times stop→text
|
|
138
138
|
voiceio learn train # LoRA fine-tune (needs the `train` extra; decoder-only on CPU, --full on a GPU)
|
|
139
139
|
voiceio learn promote <run> # dictate with the fine-tune; `voiceio learn rollback` undoes it
|
|
140
|
-
voiceio learn maintain # later: label what's new, and once 50+ clips piled up, retrain and compare
|
|
140
|
+
voiceio learn maintain # later: label what's new, and once 50+ clips piled up ([learn] min_new), retrain and compare
|
|
141
141
|
voiceio learn schedule on # …or let it run `maintain` on a schedule, and notify you when a model wins
|
|
142
142
|
# --at 02:00 --every week: when (default daily at 03:30)
|
|
143
143
|
# --auto-promote: switch to a winner by itself; `learn rollback` undoes it
|
|
@@ -146,6 +146,8 @@ voiceio learn stop # stop a run in progress (it goes again next time)
|
|
|
146
146
|
|
|
147
147
|
Scheduled runs wait for a quiet machine: they start only when the 1-minute load per CPU is at most `[learn] max_load` (0.25) and you're on mains power, and give up after `wait_hours` (3) until the next run. When one starts you get a notification with **Stop for now** and **Weekly instead**.
|
|
148
148
|
|
|
149
|
+
What a scheduled run trains comes from `[learn]` too: `base` (default `small`; any Whisper size, `.en` included, or a Hugging Face checkpoint), `teacher` (default `large-v3-turbo`), `epochs` (4) and `min_new` (50); `maintain`'s flags override them. To fine-tune the English-only medium model: `voiceio config set learn.base medium.en` (on CPU, medium trains and dictates roughly three times slower than small).
|
|
150
|
+
|
|
149
151
|
To keep a second machine (say, the server your phone dictates through) on the same model, set `[learn] on_promote = "scripts/push_model.sh HOST"`: after every switch it copies the model there and flips a `current` link the server points at.
|
|
150
152
|
|
|
151
153
|
Labeling, training and offline eval run in an idle-priority cgroup, so they never slow dictation down. Everything stays on your machine unless you choose `train --remote`.
|
|
@@ -262,6 +264,11 @@ Logs: `journalctl --user -u voiceio` or `~/.local/state/voiceio/voiceio.log`.
|
|
|
262
264
|
|
|
263
265
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the architecture, conventions and the reasoning behind the model choice. Please open an issue before a large PR.
|
|
264
266
|
|
|
267
|
+
## Credits
|
|
268
|
+
|
|
269
|
+
- Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models. The in-browser demo on [voiceio.dev](https://voiceio.dev) runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
|
|
270
|
+
- The landing page's look (dithered art, typewriter heading, hairline bento) takes its inspiration from Romario Kavin's [KOLlateral](https://github.com/RomarioKavin1/kollateral).
|
|
271
|
+
|
|
265
272
|
## License
|
|
266
273
|
|
|
267
274
|
MIT. Model weights are not part of voiceio and carry their own licenses (see [Choose a model](#choose-a-model)).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-voiceio
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: Voice dictation for Linux. Speak → text, locally, instantly.
|
|
5
5
|
Author: Hugo Montenegro
|
|
6
6
|
License-Expression: MIT
|
|
@@ -190,7 +190,7 @@ voiceio learn eval # score the current model on your held-out clips: WER a
|
|
|
190
190
|
# --streaming replays them through live dictation and times stop→text
|
|
191
191
|
voiceio learn train # LoRA fine-tune (needs the `train` extra; decoder-only on CPU, --full on a GPU)
|
|
192
192
|
voiceio learn promote <run> # dictate with the fine-tune; `voiceio learn rollback` undoes it
|
|
193
|
-
voiceio learn maintain # later: label what's new, and once 50+ clips piled up, retrain and compare
|
|
193
|
+
voiceio learn maintain # later: label what's new, and once 50+ clips piled up ([learn] min_new), retrain and compare
|
|
194
194
|
voiceio learn schedule on # …or let it run `maintain` on a schedule, and notify you when a model wins
|
|
195
195
|
# --at 02:00 --every week: when (default daily at 03:30)
|
|
196
196
|
# --auto-promote: switch to a winner by itself; `learn rollback` undoes it
|
|
@@ -199,6 +199,8 @@ voiceio learn stop # stop a run in progress (it goes again next time)
|
|
|
199
199
|
|
|
200
200
|
Scheduled runs wait for a quiet machine: they start only when the 1-minute load per CPU is at most `[learn] max_load` (0.25) and you're on mains power, and give up after `wait_hours` (3) until the next run. When one starts you get a notification with **Stop for now** and **Weekly instead**.
|
|
201
201
|
|
|
202
|
+
What a scheduled run trains comes from `[learn]` too: `base` (default `small`; any Whisper size, `.en` included, or a Hugging Face checkpoint), `teacher` (default `large-v3-turbo`), `epochs` (4) and `min_new` (50); `maintain`'s flags override them. To fine-tune the English-only medium model: `voiceio config set learn.base medium.en` (on CPU, medium trains and dictates roughly three times slower than small).
|
|
203
|
+
|
|
202
204
|
To keep a second machine (say, the server your phone dictates through) on the same model, set `[learn] on_promote = "scripts/push_model.sh HOST"`: after every switch it copies the model there and flips a `current` link the server points at.
|
|
203
205
|
|
|
204
206
|
Labeling, training and offline eval run in an idle-priority cgroup, so they never slow dictation down. Everything stays on your machine unless you choose `train --remote`.
|
|
@@ -315,6 +317,11 @@ Logs: `journalctl --user -u voiceio` or `~/.local/state/voiceio/voiceio.log`.
|
|
|
315
317
|
|
|
316
318
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the architecture, conventions and the reasoning behind the model choice. Please open an issue before a large PR.
|
|
317
319
|
|
|
320
|
+
## Credits
|
|
321
|
+
|
|
322
|
+
- Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models. The in-browser demo on [voiceio.dev](https://voiceio.dev) runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
|
|
323
|
+
- The landing page's look (dithered art, typewriter heading, hairline bento) takes its inspiration from Romario Kavin's [KOLlateral](https://github.com/RomarioKavin1/kollateral).
|
|
324
|
+
|
|
318
325
|
## License
|
|
319
326
|
|
|
320
327
|
MIT. Model weights are not part of voiceio and carry their own licenses (see [Choose a model](#choose-a-model)).
|
|
@@ -383,3 +383,105 @@ def test_promote_and_rollback_run_on_promote_with_the_model(monkeypatch, tmp_pat
|
|
|
383
383
|
cli.cmd_promote(argparse.Namespace(run="v0002-small-x", force=True))
|
|
384
384
|
cli.cmd_rollback(argparse.Namespace())
|
|
385
385
|
assert out.read_text().split() == [str(run), "small"]
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _maintain_calls(monkeypatch, min_new_clips: int = 3) -> dict:
|
|
389
|
+
"""Run-ready maintain with labeling/training/eval faked; records what they got."""
|
|
390
|
+
from types import SimpleNamespace
|
|
391
|
+
|
|
392
|
+
from voiceio.cli import learn as cli
|
|
393
|
+
for i in range(min_new_clips):
|
|
394
|
+
_clip(f"{i}.wav", 3, "x")
|
|
395
|
+
store.add_label(f"{i}.wav", [Segment(0, 3, "hello there")], "teacher:fake")
|
|
396
|
+
got: dict = {}
|
|
397
|
+
monkeypatch.setattr(cli, "cmd_label", lambda a: got.update(teacher=a.teacher))
|
|
398
|
+
monkeypatch.setattr(cli, "_announce_learning", lambda min_new: got.update(announced=min_new))
|
|
399
|
+
monkeypatch.setattr(train, "missing_train_deps", lambda: [])
|
|
400
|
+
monkeypatch.setattr(cli, "_run_eval", lambda *a, **k: SimpleNamespace(
|
|
401
|
+
wer=0.1, term_recall=0.5, repetitions=0, per_clip=[]))
|
|
402
|
+
monkeypatch.setattr(cli, "_train", lambda ds, **k: got.update(k) or store.learn_dir() / "r")
|
|
403
|
+
monkeypatch.setattr(evaluate, "promotion_verdict", lambda c, b: (False, "table"))
|
|
404
|
+
return got
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _learn_config(**values) -> None:
|
|
408
|
+
for key, value in values.items():
|
|
409
|
+
config.set_value("learn", key, value)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def test_maintain_trains_what_learn_config_says(monkeypatch):
|
|
413
|
+
import argparse
|
|
414
|
+
|
|
415
|
+
from voiceio.cli import learn as cli
|
|
416
|
+
_learn_config(base="medium.en", teacher="large-v3", epochs=2, min_new=3)
|
|
417
|
+
got = _maintain_calls(monkeypatch)
|
|
418
|
+
cli.cmd_maintain(argparse.Namespace(min_new=None, base=None, epochs=None))
|
|
419
|
+
assert got == {"teacher": "large-v3", "announced": 3, "base": "medium.en", "epochs": 2}
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def test_maintain_config_min_new_gates_retraining(monkeypatch, capsys):
|
|
423
|
+
import argparse
|
|
424
|
+
|
|
425
|
+
from voiceio.cli import learn as cli
|
|
426
|
+
_learn_config(min_new=4)
|
|
427
|
+
got = _maintain_calls(monkeypatch)
|
|
428
|
+
cli.cmd_maintain(argparse.Namespace(min_new=None, base=None, epochs=None))
|
|
429
|
+
assert "base" not in got and "retraining waits for 4" in capsys.readouterr().out
|
|
430
|
+
assert got["teacher"] is None # empty: the default teacher
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def test_maintain_flags_override_learn_config(monkeypatch):
|
|
434
|
+
import argparse
|
|
435
|
+
|
|
436
|
+
from voiceio.cli import learn as cli
|
|
437
|
+
_learn_config(base="medium.en", epochs=2, min_new=100)
|
|
438
|
+
got = _maintain_calls(monkeypatch)
|
|
439
|
+
cli.cmd_maintain(argparse.Namespace(min_new=3, base="tiny", epochs=7))
|
|
440
|
+
assert (got["announced"], got["base"], got["epochs"]) == (3, "tiny", 7)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@pytest.mark.parametrize("values, message", [
|
|
444
|
+
({"epochs": 0}, "epochs must be at least 1"),
|
|
445
|
+
({"min_new": 0}, "min_new must be at least 1"),
|
|
446
|
+
({"base": ""}, "base must name a model"),
|
|
447
|
+
])
|
|
448
|
+
def test_maintain_rejects_bad_learn_config(monkeypatch, values, message):
|
|
449
|
+
import argparse
|
|
450
|
+
|
|
451
|
+
from voiceio.cli import learn as cli
|
|
452
|
+
_learn_config(**values)
|
|
453
|
+
got = _maintain_calls(monkeypatch)
|
|
454
|
+
with pytest.raises(SystemExit, match=message):
|
|
455
|
+
cli.cmd_maintain(argparse.Namespace(min_new=None, base=None, epochs=None))
|
|
456
|
+
assert got == {} # refused before any work
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def test_english_only_base_needs_english(monkeypatch):
|
|
460
|
+
import argparse
|
|
461
|
+
|
|
462
|
+
from voiceio.cli import learn as cli
|
|
463
|
+
_learn_config(base="small.en")
|
|
464
|
+
config.set_value("model", "language", "de")
|
|
465
|
+
_maintain_calls(monkeypatch)
|
|
466
|
+
with pytest.raises(SystemExit, match="English-only"):
|
|
467
|
+
cli.cmd_maintain(argparse.Namespace(min_new=None, base=None, epochs=None))
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def test_english_only_bases_train_without_language_prefix():
|
|
471
|
+
assert train.HF_BASE["medium.en"] == "openai/whisper-medium.en"
|
|
472
|
+
cfg = train.TrainConfig(language="en")
|
|
473
|
+
assert train._prefix(51865, cfg) == {"language": "en", "task": "transcribe"}
|
|
474
|
+
assert train._prefix(51864, cfg) == {} # *.en: <|startoftranscript|> only
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def test_schedule_on_states_the_configured_threshold(monkeypatch, capsys):
|
|
478
|
+
import argparse
|
|
479
|
+
|
|
480
|
+
from voiceio import service
|
|
481
|
+
from voiceio.cli import learn as cli
|
|
482
|
+
_learn_config(min_new=20)
|
|
483
|
+
monkeypatch.setattr(train, "missing_train_deps", lambda: [])
|
|
484
|
+
monkeypatch.setattr(service, "install_learn_timer", lambda auto_promote: True)
|
|
485
|
+
monkeypatch.setattr(service, "learn_schedule_summary", lambda: "daily at 03:30")
|
|
486
|
+
cli.cmd_schedule(argparse.Namespace(state="on", at=None, every=None, auto_promote=False))
|
|
487
|
+
assert "retrains once 20+ clips are new" in capsys.readouterr().out
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.5.0"
|
|
@@ -80,10 +80,10 @@ def register(sub: argparse._SubParsersAction) -> None:
|
|
|
80
80
|
t.set_defaults(func=cmd_train)
|
|
81
81
|
|
|
82
82
|
mt = lsub.add_parser("maintain", help="Label new clips; retrain and compare once enough piled up")
|
|
83
|
-
mt.add_argument("--min-new", type=int, default=
|
|
84
|
-
help="new labeled clips needed before retraining (default
|
|
85
|
-
mt.add_argument("--base", default="
|
|
86
|
-
mt.add_argument("--epochs", type=int, default=
|
|
83
|
+
mt.add_argument("--min-new", type=int, default=None,
|
|
84
|
+
help="new labeled clips needed before retraining (default: [learn] min_new)")
|
|
85
|
+
mt.add_argument("--base", default=None, help="model to fine-tune (default: [learn] base)")
|
|
86
|
+
mt.add_argument("--epochs", type=int, default=None, help="default: [learn] epochs")
|
|
87
87
|
mt.add_argument("--auto-promote", action="store_true",
|
|
88
88
|
help="switch to the fine-tune when it wins, and restart the daemon")
|
|
89
89
|
mt.add_argument("--when-idle", action="store_true",
|
|
@@ -426,7 +426,8 @@ def _train(ds: Path, *, base: str = "small", epochs: int = 4, synthetic_share: f
|
|
|
426
426
|
folder = store.learn_dir() / "exports" / ds.name
|
|
427
427
|
counts = train.export(ds, folder, extra_train_rows=synth, hotwords=_live_hotwords())
|
|
428
428
|
print(f"exported {counts} windows ({len(synth)} synthetic) to {folder}")
|
|
429
|
-
run
|
|
429
|
+
# A Hugging Face id or path names the run by its last part: one directory.
|
|
430
|
+
run = _models_dir() / f"{ds.name}-{Path(base).name}-{time.strftime('%Y%m%d-%H%M')}"
|
|
430
431
|
if remote:
|
|
431
432
|
print("\n" + train.remote_recipe(folder, run, base))
|
|
432
433
|
return None
|
|
@@ -443,12 +444,12 @@ def cmd_maintain(args: argparse.Namespace) -> None:
|
|
|
443
444
|
whether the result beats the current model. Never promotes by itself."""
|
|
444
445
|
_yield_cpu()
|
|
445
446
|
from voiceio.learn import evaluate, quiet
|
|
446
|
-
learn_cfg =
|
|
447
|
+
learn_cfg = _maintain_settings(args)
|
|
447
448
|
if getattr(args, "when_idle", False) and not quiet.wait_until_quiet(
|
|
448
449
|
learn_cfg.max_load, learn_cfg.wait_hours):
|
|
449
450
|
return
|
|
450
|
-
_announce_learning(
|
|
451
|
-
cmd_label(argparse.Namespace(teacher=None, limit=None, relabel=False))
|
|
451
|
+
_announce_learning(learn_cfg.min_new)
|
|
452
|
+
cmd_label(argparse.Namespace(teacher=learn_cfg.teacher or None, limit=None, relabel=False))
|
|
452
453
|
from voiceio.learn import train
|
|
453
454
|
if missing := train.missing_train_deps():
|
|
454
455
|
# Labeling alone is worth it; retraining waits for the extra. Exit 0:
|
|
@@ -459,14 +460,14 @@ def cmd_maintain(args: argparse.Namespace) -> None:
|
|
|
459
460
|
labeled = {c.audio for c in store.clips() if c.audio in labels}
|
|
460
461
|
ds = store.latest_dataset()
|
|
461
462
|
new = len(labeled - store.dataset_clips(ds)) if ds else len(labeled)
|
|
462
|
-
if new <
|
|
463
|
+
if new < learn_cfg.min_new: # the first dataset too: a handful of clips trains nothing
|
|
463
464
|
since = f" since {ds.name}" if ds else ""
|
|
464
|
-
print(f"{new} new labeled clips{since}; retraining waits for {
|
|
465
|
+
print(f"{new} new labeled clips{since}; retraining waits for {learn_cfg.min_new}.")
|
|
465
466
|
return
|
|
466
467
|
ds = store.build_dataset()
|
|
467
468
|
print(f"built {ds.name} ({new} new clips)")
|
|
468
469
|
current = _run_eval(None, ds)
|
|
469
|
-
run = _train(ds, base=
|
|
470
|
+
run = _train(ds, base=learn_cfg.base, epochs=learn_cfg.epochs)
|
|
470
471
|
candidate = _run_eval(str(run / "ct2"), ds)
|
|
471
472
|
ok, table = evaluate.promotion_verdict(_scores(candidate), _scores(current))
|
|
472
473
|
print(f"\n{_cfg().model.name} → {run.name} on {ds.name}:\n{table}")
|
|
@@ -498,6 +499,24 @@ def cmd_maintain(args: argparse.Namespace) -> None:
|
|
|
498
499
|
urgent=True)
|
|
499
500
|
|
|
500
501
|
|
|
502
|
+
def _maintain_settings(args: argparse.Namespace | None = None):
|
|
503
|
+
"""`[learn]` with `maintain`'s flags applied, validated: an unattended run
|
|
504
|
+
must fail on a bad setting with a message, not deep inside training."""
|
|
505
|
+
import dataclasses
|
|
506
|
+
cfg = _cfg()
|
|
507
|
+
flags = {k: getattr(args, k, None) for k in ("base", "epochs", "min_new")}
|
|
508
|
+
learn = dataclasses.replace(cfg.learn, **{k: v for k, v in flags.items() if v is not None})
|
|
509
|
+
if not learn.base.strip():
|
|
510
|
+
raise SystemExit("[learn] base must name a model to fine-tune, e.g. small")
|
|
511
|
+
for key in ("epochs", "min_new"):
|
|
512
|
+
if getattr(learn, key) < 1:
|
|
513
|
+
raise SystemExit(f"[learn] {key} must be at least 1, not {getattr(learn, key)}")
|
|
514
|
+
language = cfg.model.language
|
|
515
|
+
if learn.base.endswith(".en") and language not in ("en", "auto"):
|
|
516
|
+
raise SystemExit(f"[learn] base {learn.base} is English-only; [model] language is {language}")
|
|
517
|
+
return learn
|
|
518
|
+
|
|
519
|
+
|
|
501
520
|
def _announce_learning(min_new: int) -> None:
|
|
502
521
|
"""Tell the user a run is starting (it takes a while and uses the CPU),
|
|
503
522
|
with buttons to stop it or to learn weekly instead. Silent when there is
|
|
@@ -568,6 +587,7 @@ def cmd_schedule(args: argparse.Namespace) -> None:
|
|
|
568
587
|
print(f"on, {service.learn_schedule_summary()}")
|
|
569
588
|
return
|
|
570
589
|
if args.state == "on":
|
|
590
|
+
min_new = _maintain_settings().min_new
|
|
571
591
|
if not config.load().data.retain_audio:
|
|
572
592
|
print("note: [data] retain_audio is off, so there is nothing new to learn from;\n"
|
|
573
593
|
" turn it on with: voiceio config set data.retain_audio true")
|
|
@@ -581,7 +601,7 @@ def cmd_schedule(args: argparse.Namespace) -> None:
|
|
|
581
601
|
print(f"on, {service.learn_schedule_summary()}: once the machine is quiet (load per CPU "
|
|
582
602
|
f"≤ {learn.max_load:g}, on mains power; waits up to {learn.wait_hours:g} h), "
|
|
583
603
|
"`voiceio learn maintain` labels new dictation at idle priority, retrains once "
|
|
584
|
-
"
|
|
604
|
+
f"{min_new}+ clips are new, and "
|
|
585
605
|
+ ("switches to the result when it wins on your held-out clips "
|
|
586
606
|
"(undo: voiceio learn rollback)." if args.auto_promote else
|
|
587
607
|
"tells you if the result is better. It never switches models by itself."))
|
|
@@ -258,6 +258,14 @@ class LearnConfig:
|
|
|
258
258
|
# at the next scheduled run.
|
|
259
259
|
max_load: float = 0.25
|
|
260
260
|
wait_hours: float = 3.0
|
|
261
|
+
# What a scheduled run trains (`voiceio learn maintain`'s flags override):
|
|
262
|
+
# the Whisper size or Hugging Face checkpoint to fine-tune, e.g. "medium.en";
|
|
263
|
+
# the model that labels new clips (empty: large-v3-turbo); epochs per run;
|
|
264
|
+
# and how many new labeled clips it waits for before retraining.
|
|
265
|
+
base: str = "small"
|
|
266
|
+
teacher: str = ""
|
|
267
|
+
epochs: int = 4
|
|
268
|
+
min_new: int = 50
|
|
261
269
|
|
|
262
270
|
|
|
263
271
|
@dataclass
|
|
@@ -42,6 +42,8 @@ HF_BASE = {
|
|
|
42
42
|
"tiny": "openai/whisper-tiny", "base": "openai/whisper-base",
|
|
43
43
|
"small": "openai/whisper-small", "medium": "openai/whisper-medium",
|
|
44
44
|
"large-v3": "openai/whisper-large-v3", "large-v3-turbo": "openai/whisper-large-v3-turbo",
|
|
45
|
+
"tiny.en": "openai/whisper-tiny.en", "base.en": "openai/whisper-base.en",
|
|
46
|
+
"small.en": "openai/whisper-small.en", "medium.en": "openai/whisper-medium.en",
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
|
|
@@ -195,6 +197,14 @@ def decoder_batch(seqs: list[tuple[list[int], int]], pad: int):
|
|
|
195
197
|
|
|
196
198
|
# ── train ────────────────────────────────────────────────────────────────
|
|
197
199
|
|
|
200
|
+
def _prefix(vocab_size: int, cfg: TrainConfig) -> dict:
|
|
201
|
+
"""Tokenizer prefix matching what faster-whisper feeds at inference. An
|
|
202
|
+
English-only model (vocabulary < 51865, CTranslate2's own test) is
|
|
203
|
+
decoded from <|startoftranscript|> alone: training it behind
|
|
204
|
+
<|en|><|transcribe|> would teach a prefix it never sees."""
|
|
205
|
+
return {"language": cfg.language, "task": "transcribe"} if vocab_size >= 51865 else {}
|
|
206
|
+
|
|
207
|
+
|
|
198
208
|
def lr_schedule(total_steps: int, warmup: float = 0.1):
|
|
199
209
|
"""Linear warmup over the first `warmup` share of steps, then linear decay
|
|
200
210
|
to zero — a few hundred steps on a few hours of audio overshoot without."""
|
|
@@ -220,8 +230,8 @@ def train(folder: Path, out: Path, cfg: TrainConfig = TrainConfig()) -> Path:
|
|
|
220
230
|
torch.set_num_threads(max(1, (os.cpu_count() or 2) // 2))
|
|
221
231
|
decoder_only = cfg.decoder_only if cfg.decoder_only is not None else device == "cpu"
|
|
222
232
|
base_id = HF_BASE.get(cfg.base, cfg.base)
|
|
223
|
-
processor = WhisperProcessor.from_pretrained(base_id, language=cfg.language, task="transcribe")
|
|
224
233
|
model = WhisperForConditionalGeneration.from_pretrained(base_id)
|
|
234
|
+
processor = WhisperProcessor.from_pretrained(base_id, **_prefix(model.config.vocab_size, cfg))
|
|
225
235
|
model.config.forced_decoder_ids = None
|
|
226
236
|
model = get_peft_model(model, LoraConfig(
|
|
227
237
|
r=cfg.lora_r, lora_alpha=cfg.lora_alpha, lora_dropout=0.05,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.4.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/decoders/faster_whisper/__init__.py
RENAMED
|
File without changes
|
{python_voiceio-1.4.0 → python_voiceio-1.5.0}/voiceio/core/decoders/faster_whisper/worker.py
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
|
|
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
|