diff --git a/main.py b/main.py index a05c07e..a96a9f7 100644 --- a/main.py +++ b/main.py @@ -10,14 +10,66 @@ import time import webbrowser from ctypes import POINTER, cast +# Print a startup banner BEFORE the heavy imports — otherwise the user +# sees an empty terminal for 10-60 seconds while torch / vosk / silero +# load. This used to be the #1 "did it crash?" support issue. +print("=" * 60, flush=True) +print(" J.A.R.V.I.S. loading (Python edition)...", flush=True) +print(" Heavy modules (vosk / torch / pvrecorder) take a few seconds.", flush=True) +print("=" * 60, flush=True) + +# Helpful diagnostic: if a critical dep is missing, tell the user EXACTLY +# what to install instead of dumping a raw ModuleNotFoundError. Pulls +# every required package by name and reports them in one go. +_MISSING_DEPS = [] +for _modname, _pkg in [ + ("numpy", "numpy"), + ("openai", "openai>=1.0"), + ("vosk", "vosk"), + ("webrtcvad", "webrtcvad"), + ("yaml", "PyYAML"), + ("comtypes", "comtypes"), + ("fuzzywuzzy", "fuzzywuzzy"), + ("pvrecorder", "pvrecorder"), + ("pycaw", "pycaw"), + ("rich", "rich"), +]: + try: + __import__(_modname) + except ImportError: + _MISSING_DEPS.append(_pkg) +if _MISSING_DEPS: + print("\n[ERROR] Missing Python packages:", flush=True) + for _p in _MISSING_DEPS: + print(f" - {_p}", flush=True) + print("\nRun: pip install -r requirements.txt", flush=True) + print("Or use the bundled venv: launch via run.bat.\n", flush=True) + sys.exit(2) + import numpy as np import openai from openai import OpenAI -import simpleaudio as sa import vosk import webrtcvad import yaml +# simpleaudio needs MSVC to build from source; gracefully fall back to the +# stdlib's `winsound` on Windows when it isn't installed. +try: + import simpleaudio as sa + _HAS_SIMPLEAUDIO = True +except ImportError: + sa = None + _HAS_SIMPLEAUDIO = False + try: + import winsound as _winsound + except ImportError: + _winsound = None + if _winsound is None: + print("[WARN] Neither simpleaudio nor winsound available — sound cues disabled.", flush=True) + else: + print("[INFO] simpleaudio not installed; falling back to winsound for sound cues.", flush=True) + import extensions # new-command handlers ported from rust fork import plugins_store # user plugin packs under APP_CONFIG_DIR/plugins/ from comtypes import CLSCTX_ALL @@ -154,14 +206,24 @@ def play(phrase, wait_done=True): if wait_done: recorder.stop() - wave_obj = sa.WaveObject.from_wave_file(filename) - play_obj = wave_obj.play() + # Prefer simpleaudio (lets us wait_done precisely); fall back to winsound + # on installs that couldn't build the simpleaudio C extension. + if _HAS_SIMPLEAUDIO and sa is not None: + wave_obj = sa.WaveObject.from_wave_file(filename) + play_obj = wave_obj.play() + if wait_done: + play_obj.wait_done() + elif _winsound is not None: + flags = _winsound.SND_FILENAME + if not wait_done: + flags |= _winsound.SND_ASYNC + try: + _winsound.PlaySound(filename, flags) + except RuntimeError as exc: + print(f"[sound] winsound playback failed: {exc}") + # else: no audio backend — silently skip cue if wait_done: - play_obj.wait_done() - # time.sleep((len(wave_obj.audio_data) / wave_obj.sample_rate) + 0.5) - # print("END") - # time.sleep(0.5) recorder.start() diff --git a/tts.py b/tts.py index 9930298..35ca6f9 100644 --- a/tts.py +++ b/tts.py @@ -1,3 +1,14 @@ +"""TTS — Silero ru_v3 voice synth with lazy initialisation. + +Why lazy: torch.hub.load() blocks for 30-120 seconds on the first run +(downloading the model) and produces no progress output beyond a single +"Using cache found in..." line. If this runs at MODULE IMPORT time, the +whole jarvis-python startup looks frozen — exactly the bug the user hit. + +Fix: defer the load until first synth call. main.py prints its startup +banner first, then this module loads on demand. Side effect: the first +"speak" is slow (3-4s), but every print before it is visible. +""" import time import numpy as np @@ -14,13 +25,31 @@ speaker = 'aidar' # aidar, baya, kseniya, xenia, random put_accent = True put_yo = True device = torch.device('cpu') # cpu или gpu -text = "Хауди Хо, друзья!!!" -model, _ = torch.hub.load(repo_or_dir='snakers4/silero-models', +# Initialised on first synthesize() call. +_model = None + + +def _ensure_model(): + """Load Silero on first use. Subsequent calls are no-ops. + + Prints a one-line progress hint so the user can tell the assistant is + busy downloading rather than hung — critical for the first run when + the model isn't cached yet. + """ + global _model + if _model is not None: + return _model + print("[tts] Loading Silero ru_v3 (first use — ~30s if model isn't cached)...") + started = time.time() + m, _ = torch.hub.load(repo_or_dir='snakers4/silero-models', model='silero_tts', language=language, speaker=model_id) -model.to(device) + m.to(device) + _model = m + print(f"[tts] Silero ready ({time.time() - started:.1f}s).") + return _model def _post_process(audio): @@ -45,11 +74,12 @@ def _post_process(audio): def synthesize(what: str): - audio = model.apply_tts(text=what + "..", - speaker=speaker, - sample_rate=sample_rate, - put_accent=put_accent, - put_yo=put_yo) + m = _ensure_model() + audio = m.apply_tts(text=what + "..", + speaker=speaker, + sample_rate=sample_rate, + put_accent=put_accent, + put_yo=put_yo) return _post_process(audio)