Загрузка исходника
This commit is contained in:
Executable
+468
@@ -0,0 +1,468 @@
|
||||
"""Wake word settings."""
|
||||
import itertools
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional
|
||||
|
||||
from .const import LOCAL_DIR, SatelliteType, Settings, WakeWordSystem
|
||||
from .packages import install_packages, packages_installed
|
||||
from .whiptail import (
|
||||
error,
|
||||
inputbox,
|
||||
menu,
|
||||
msgbox,
|
||||
passwordbox,
|
||||
radiolist,
|
||||
run_with_gauge,
|
||||
yesno,
|
||||
)
|
||||
|
||||
|
||||
def configure_wake_word(settings: Settings) -> None:
|
||||
if settings.satellite.type != SatelliteType.WAKE:
|
||||
if not yesno("Set satellite type to local wake word?"):
|
||||
return
|
||||
|
||||
settings.satellite.type = SatelliteType.WAKE
|
||||
settings.save()
|
||||
|
||||
def star(ww_system: WakeWordSystem) -> str:
|
||||
return (
|
||||
f"{ww_system.value} [*]"
|
||||
if settings.wake.system == ww_system
|
||||
else ww_system.value
|
||||
)
|
||||
|
||||
choice: Optional[str] = None
|
||||
while True:
|
||||
choice = menu(
|
||||
"Main > Wake Word",
|
||||
[
|
||||
("system", "Wake Word System"),
|
||||
("wake_word", "Choose Wake Word"),
|
||||
("openWakeWord", star(WakeWordSystem.OPENWAKEWORD)),
|
||||
("porcupine1", star(WakeWordSystem.PORCUPINE1)),
|
||||
("snowboy", star(WakeWordSystem.SNOWBOY)),
|
||||
],
|
||||
selected_item=choice,
|
||||
menu_args=["--ok-button", "Select", "--cancel-button", "Back"],
|
||||
)
|
||||
|
||||
if choice == "system":
|
||||
wake_word_system = radiolist(
|
||||
"Wake Word System:",
|
||||
[v.value for v in WakeWordSystem],
|
||||
settings.wake.system,
|
||||
)
|
||||
if wake_word_system is None:
|
||||
continue
|
||||
|
||||
wake_word_system = WakeWordSystem(wake_word_system)
|
||||
install_wake_word(settings, wake_word_system)
|
||||
elif choice == "wake_word":
|
||||
select_wake_word(settings)
|
||||
elif choice == "openWakeWord":
|
||||
configure_openWakeWord(settings)
|
||||
elif choice == "porcupine1":
|
||||
configure_porcupine1(settings)
|
||||
elif choice == "snowboy":
|
||||
configure_snowboy(settings)
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def install_wake_word(settings: Settings, wake_word_system: WakeWordSystem) -> None:
|
||||
if wake_word_system == WakeWordSystem.OPENWAKEWORD:
|
||||
oww_dir = LOCAL_DIR / "wyoming-openwakeword"
|
||||
if not oww_dir.exists():
|
||||
if not yesno("Install openWakeWord?"):
|
||||
return
|
||||
|
||||
success = run_with_gauge(
|
||||
"Installing openWakeWord",
|
||||
[
|
||||
[
|
||||
"git",
|
||||
"clone",
|
||||
"https://github.com/rhasspy/wyoming-openwakeword.git",
|
||||
str(oww_dir),
|
||||
],
|
||||
[str(oww_dir / "script" / "setup")],
|
||||
],
|
||||
)
|
||||
|
||||
if not success:
|
||||
# Clean up
|
||||
try:
|
||||
shutil.rmtree(oww_dir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
error("installing openWakeWord")
|
||||
return
|
||||
|
||||
msgbox("openWakeWord installed successfully")
|
||||
settings.wake.system = wake_word_system
|
||||
settings.save()
|
||||
return
|
||||
|
||||
if wake_word_system == WakeWordSystem.PORCUPINE1:
|
||||
porcupine1_dir = LOCAL_DIR / "wyoming-porcupine1"
|
||||
if not porcupine1_dir.exists():
|
||||
if not yesno("Install porcupine1?"):
|
||||
return
|
||||
|
||||
success = run_with_gauge(
|
||||
"Installing porcupine1",
|
||||
[
|
||||
[
|
||||
"git",
|
||||
"clone",
|
||||
"https://github.com/rhasspy/wyoming-porcupine1.git",
|
||||
str(porcupine1_dir),
|
||||
],
|
||||
[str(porcupine1_dir / "script" / "setup")],
|
||||
],
|
||||
)
|
||||
|
||||
if not success:
|
||||
# Clean up
|
||||
try:
|
||||
shutil.rmtree(porcupine1_dir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
error("installing porcupine1")
|
||||
return
|
||||
|
||||
msgbox("porcupine1 installed successfully")
|
||||
settings.wake.system = wake_word_system
|
||||
settings.save()
|
||||
return
|
||||
|
||||
if wake_word_system == WakeWordSystem.SNOWBOY:
|
||||
snowboy_dir = LOCAL_DIR / "wyoming-snowboy"
|
||||
if not snowboy_dir.exists():
|
||||
if not yesno("Install snowboy?"):
|
||||
return
|
||||
|
||||
snowboy_packages = ["python3-dev", "swig", "libatlas-base-dev"]
|
||||
if not packages_installed(*snowboy_packages):
|
||||
password = passwordbox("sudo password:")
|
||||
if not password:
|
||||
return
|
||||
|
||||
success = install_packages(
|
||||
"Installing system packages...", password, *snowboy_packages
|
||||
)
|
||||
if not success:
|
||||
error("installing " + ", ".join(snowboy_packages))
|
||||
return
|
||||
|
||||
success = run_with_gauge(
|
||||
"Installing snowboy",
|
||||
[
|
||||
[
|
||||
"git",
|
||||
"clone",
|
||||
"https://github.com/rhasspy/wyoming-snowboy.git",
|
||||
str(snowboy_dir),
|
||||
],
|
||||
[str(snowboy_dir / "script" / "setup")],
|
||||
],
|
||||
)
|
||||
|
||||
if not success:
|
||||
# Clean up
|
||||
try:
|
||||
shutil.rmtree(snowboy_dir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
error("installing snowboy")
|
||||
return
|
||||
|
||||
msgbox("snowboy installed successfully")
|
||||
settings.wake.system = wake_word_system
|
||||
settings.save()
|
||||
return
|
||||
|
||||
|
||||
def select_wake_word(settings: Settings) -> None:
|
||||
if settings.wake.system == WakeWordSystem.OPENWAKEWORD:
|
||||
oww_dir = LOCAL_DIR / "wyoming-openwakeword"
|
||||
if not oww_dir.exists():
|
||||
msgbox("openWakeWord is not installed")
|
||||
return
|
||||
|
||||
custom_wake_word_dir = LOCAL_DIR / "custom-wake-words" / "openWakeWord"
|
||||
custom_wake_word_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
community_wake_word_dir = LOCAL_DIR / "home-assistant-wakewords-collection"
|
||||
|
||||
while True:
|
||||
ww_paths: Dict[str, Path] = {
|
||||
p.stem: p for p in custom_wake_word_dir.glob("*.tflite")
|
||||
}
|
||||
|
||||
for ww_path in community_wake_word_dir.rglob("*.tflite"):
|
||||
ww_name = ww_path.stem
|
||||
if ww_name in ww_paths:
|
||||
continue
|
||||
|
||||
ww_paths[ww_name] = ww_path
|
||||
|
||||
for ww_path in (oww_dir / "wyoming_openwakeword" / "models").glob(
|
||||
"*.tflite"
|
||||
):
|
||||
if not re.match("^.+_v[0-9].*$", ww_path.stem):
|
||||
continue
|
||||
|
||||
ww_name = ww_path.stem.rsplit("_", maxsplit=1)[0]
|
||||
if ww_name in ww_paths:
|
||||
continue
|
||||
|
||||
ww_paths[ww_name] = ww_path
|
||||
|
||||
items = sorted(list(ww_paths.keys()))
|
||||
wake_word = radiolist(
|
||||
"Wake Word:", items, settings.wake.openwakeword.wake_word
|
||||
)
|
||||
if wake_word is None:
|
||||
break
|
||||
|
||||
wake_word_path = ww_paths[wake_word]
|
||||
if wake_word_path.is_relative_to(community_wake_word_dir):
|
||||
# Copy to custom directory
|
||||
shutil.copy(wake_word_path, custom_wake_word_dir)
|
||||
|
||||
settings.wake.openwakeword.wake_word = wake_word
|
||||
settings.save()
|
||||
break
|
||||
|
||||
return
|
||||
|
||||
if settings.wake.system == WakeWordSystem.PORCUPINE1:
|
||||
porcupine1_dir = LOCAL_DIR / "wyoming-porcupine1"
|
||||
if not porcupine1_dir.exists():
|
||||
msgbox("porcupine1 is not installed")
|
||||
return
|
||||
|
||||
ww_names = sorted(
|
||||
list(
|
||||
set(
|
||||
p.stem.rsplit("_", maxsplit=1)[0]
|
||||
for p in (
|
||||
porcupine1_dir / "wyoming_porcupine1" / "data" / "resources"
|
||||
).rglob("*.ppn")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
wake_word = radiolist(
|
||||
"Wake Word:", ww_names, settings.wake.porcupine1.wake_word
|
||||
)
|
||||
if wake_word is not None:
|
||||
settings.wake.porcupine1.wake_word = wake_word
|
||||
settings.save()
|
||||
|
||||
return
|
||||
|
||||
if settings.wake.system == WakeWordSystem.SNOWBOY:
|
||||
snowboy_dir = LOCAL_DIR / "wyoming-snowboy"
|
||||
if not snowboy_dir.exists():
|
||||
msgbox("snowboy is not installed")
|
||||
return
|
||||
|
||||
custom_wake_word_dir = LOCAL_DIR / "custom-wake-words" / "snowboy"
|
||||
custom_wake_word_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
builtin_wake_words = (snowboy_dir / "wyoming_snowboy" / "data").glob("*.umdl")
|
||||
custom_wake_words = itertools.chain(
|
||||
custom_wake_word_dir.glob("*.pmdl"), custom_wake_word_dir.glob("*.umdl")
|
||||
)
|
||||
ww_names = sorted(
|
||||
list(
|
||||
set(
|
||||
p.stem
|
||||
for p in itertools.chain(builtin_wake_words, custom_wake_words)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
wake_word = radiolist("Wake Word:", ww_names, settings.wake.snowboy.wake_word)
|
||||
if wake_word is not None:
|
||||
settings.wake.snowboy.wake_word = wake_word
|
||||
settings.save()
|
||||
|
||||
return
|
||||
|
||||
|
||||
def configure_openWakeWord(settings: Settings) -> None:
|
||||
choice: Optional[str] = None
|
||||
while True:
|
||||
choice = menu(
|
||||
"Main > Wake Word > openWakeWord",
|
||||
[
|
||||
("community", "Download Community Wake Words"),
|
||||
("threshold", "Set Threshold"),
|
||||
("trigger_level", "Set Trigger Level"),
|
||||
],
|
||||
selected_item=choice,
|
||||
menu_args=["--ok-button", "Select", "--cancel-button", "Back"],
|
||||
)
|
||||
|
||||
if choice == "community":
|
||||
community_wake_word_dir = LOCAL_DIR / "home-assistant-wakewords-collection"
|
||||
|
||||
if not community_wake_word_dir.exists():
|
||||
success = run_with_gauge(
|
||||
"Downloading community wake words...",
|
||||
[
|
||||
[
|
||||
"git",
|
||||
"clone",
|
||||
"https://github.com/fwartner/home-assistant-wakewords-collection.git",
|
||||
str(community_wake_word_dir),
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
if not success:
|
||||
error("downloading community wake words")
|
||||
else:
|
||||
success = run_with_gauge(
|
||||
"Updating community wake words...",
|
||||
[
|
||||
[
|
||||
"git",
|
||||
"-C",
|
||||
str(community_wake_word_dir),
|
||||
"pull",
|
||||
"origin",
|
||||
"main",
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
if not success:
|
||||
error("updating community wake words")
|
||||
elif choice == "threshold":
|
||||
while True:
|
||||
threshold = inputbox(
|
||||
"Threshold (0-1, 0.5 = default):",
|
||||
settings.wake.openwakeword.threshold,
|
||||
)
|
||||
if threshold is None:
|
||||
break
|
||||
|
||||
try:
|
||||
threshold_float = float(threshold)
|
||||
except ValueError:
|
||||
msgbox("Invalid value")
|
||||
continue
|
||||
|
||||
if 0 < threshold_float < 1:
|
||||
settings.wake.openwakeword.threshold = threshold_float
|
||||
settings.save()
|
||||
break
|
||||
|
||||
msgbox("Threshold must be in (0, 1)")
|
||||
elif choice == "trigger_level":
|
||||
while True:
|
||||
trigger_level = inputbox(
|
||||
"Trigger Level (> 0, 1 = default):",
|
||||
settings.wake.openwakeword.trigger_level,
|
||||
)
|
||||
if trigger_level is None:
|
||||
break
|
||||
|
||||
try:
|
||||
trigger_level_int = int(trigger_level)
|
||||
except ValueError:
|
||||
msgbox("Invalid value")
|
||||
continue
|
||||
|
||||
if trigger_level_int > 0:
|
||||
settings.wake.openwakeword.trigger_level = trigger_level_int
|
||||
settings.save()
|
||||
break
|
||||
|
||||
msgbox("Trigger level must be > 0")
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def configure_porcupine1(settings: Settings) -> None:
|
||||
choice: Optional[str] = None
|
||||
while True:
|
||||
choice = menu(
|
||||
"Main > Wake Word > porcupine1",
|
||||
[
|
||||
("sensitivity", "Set Sensitivity"),
|
||||
],
|
||||
selected_item=choice,
|
||||
menu_args=["--ok-button", "Select", "--cancel-button", "Back"],
|
||||
)
|
||||
|
||||
if choice == "sensitivity":
|
||||
while True:
|
||||
sensitivity = inputbox(
|
||||
"Sensitivity (0-1, 0.5 = default):",
|
||||
settings.wake.porcupine1.sensitivity,
|
||||
)
|
||||
if sensitivity is None:
|
||||
break
|
||||
|
||||
try:
|
||||
sensitivity_float = float(sensitivity)
|
||||
except ValueError:
|
||||
msgbox("Invalid value")
|
||||
continue
|
||||
|
||||
if 0 < sensitivity_float < 1:
|
||||
settings.wake.porcupine1.sensitivity = sensitivity_float
|
||||
settings.save()
|
||||
break
|
||||
|
||||
msgbox("Sensitivity must be in (0, 1)")
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def configure_snowboy(settings: Settings) -> None:
|
||||
choice: Optional[str] = None
|
||||
while True:
|
||||
choice = menu(
|
||||
"Main > Wake Word > snowboy",
|
||||
[
|
||||
("sensitivity", "Set Sensitivity"),
|
||||
],
|
||||
selected_item=choice,
|
||||
menu_args=["--ok-button", "Select", "--cancel-button", "Back"],
|
||||
)
|
||||
|
||||
if choice == "sensitivity":
|
||||
while True:
|
||||
sensitivity = inputbox(
|
||||
"Sensitivity (0-1, 0.5 = default):",
|
||||
settings.wake.snowboy.sensitivity,
|
||||
)
|
||||
if sensitivity is None:
|
||||
break
|
||||
|
||||
try:
|
||||
sensitivity_float = float(sensitivity)
|
||||
except ValueError:
|
||||
msgbox("Invalid value")
|
||||
continue
|
||||
|
||||
if 0 < sensitivity_float < 1:
|
||||
settings.wake.snowboy.sensitivity = sensitivity_float
|
||||
settings.save()
|
||||
break
|
||||
|
||||
msgbox("Sensitivity must be in (0, 1)")
|
||||
else:
|
||||
break
|
||||
Reference in New Issue
Block a user