-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathengine_openwakeword.py
More file actions
150 lines (98 loc) · 4.04 KB
/
Copy pathengine_openwakeword.py
File metadata and controls
150 lines (98 loc) · 4.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import os
import sys
import queue
import gc
import numpy as np
import sounddevice as sd
import openwakeword
from openwakeword.model import Model
import utility
class OpenwakewordEngine:
def __init__(self):
self.q = queue.Queue(maxsize=50)
self.openwakeword_model = None
self.dev_index = None
self.dev_sample_rate = None
self.dev_channels = None
script_path = os.path.abspath(__file__)
script_dir = os.path.dirname(script_path)
custom_keyword_path_dir = os.path.join(script_dir, "openwakeword_keywords")
my_keyword_path = {
"hey agent": os.path.join(custom_keyword_path_dir, "my_model.tflite")
}
my_keyword_path = {
k: v for k, v in my_keyword_path.items()
if os.path.isfile(v)
}
model_paths = { k: v["model_path"] for k, v in openwakeword.MODELS.items() }
self.keyword_path_all = {**my_keyword_path, **model_paths}
def init_model(self, model_name, dev_index, dev_sample_rate, dev_channels):
if model_name:
return False, f"'{model_name}' is not a valid model in Openwakeword."
self.dev_index = dev_index
self.dev_sample_rate = dev_sample_rate
self.dev_channels = dev_channels
print(f"\n🔄 Loading Openwakeword models...")
try:
# One-time download of all pre-trained models
openwakeword.utils.download_models()
except Exception as e:
return False, str(e)
return True, None
def start_hotword_detection(self, hotword_list, target_latency_ms, script_state, on_hotword_callback=None):
keyword_paths = []
for hotword in hotword_list:
if hotword not in self.keyword_path_all:
return False, f"invalid keyword '{hotword}'. Choose from {list(self.keyword_path_all.keys())}"
keyword_paths.append(self.keyword_path_all[hotword])
self.openwakeword_model = Model(wakeword_models=keyword_paths)
sample_rate = 16000 # OpenWakeWord expects 16kHz audio
blocksize = utility.choose_blocksize(target_latency_ms, sample_rate)
self.__empty_queue()
detected_hotword = None
with sd.RawInputStream(
device=self.dev_index,
samplerate=sample_rate,
blocksize=blocksize,
dtype='int16',
channels=1,
callback=self.__audio_callback):
while not script_state["interrupted"]:
data = self.q.get()
if len(data) < blocksize * 2: # 2 bytes per sample
continue
audio_frame = np.frombuffer(data, dtype=np.int16)
predictions = self.openwakeword_model.predict(audio_frame)
filtered = sorted(
[(name, score) for name, score in predictions.items() if score >= 0.5],
key=lambda x: x[1],
reverse=True
)
if filtered:
if len(filtered) > 1:
print(f"Multiple hotwords scored high: {filtered}")
name, score = filtered[0]
print(f"🔊 Hotword detected: {name} (score: {score:.2f})")
detected_hotword = name
break
if detected_hotword:
if on_hotword_callback:
on_hotword_callback(detected_hotword)
return True, None
def stop_hotword_detection(self):
if self.openwakeword_model:
self.openwakeword_model = None
gc.collect()
def __audio_callback(self, indata, frames, time_info, status):
if status:
print(f"[STATUS] {status}", file=sys.stderr)
try:
self.q.put_nowait(bytes(indata))
except queue.Full:
print("[WARN] Audio queue full — dropping frame")
def __empty_queue(self):
while not self.q.empty():
try:
self.q.get_nowait()
except queue.Empty:
break