234 lines
6.7 KiB
Python
234 lines
6.7 KiB
Python
import speech_recognition as sr
|
|
import time
|
|
import threading
|
|
import queue
|
|
import signal
|
|
import sys
|
|
from os import environ
|
|
from os import kill
|
|
import requests
|
|
|
|
def main():
|
|
managerToParserQ = queue.Queue(maxsize=1)
|
|
readerToParserQ = queue.Queue(maxsize=10)
|
|
parserToReactorQ = queue.Queue(maxsize=10)
|
|
reactorToActorQ = queue.Queue(maxsize=10)
|
|
threads = [
|
|
Manager(managerToParserQ),
|
|
Reader(managerToParserQ, readerToParserQ),
|
|
Parser(readerToParserQ, parserToReactorQ),
|
|
Reactor(parserToReactorQ, reactorToActorQ),
|
|
Actor(reactorToActorQ),
|
|
]
|
|
[t.start() for t in threads]
|
|
[t.join() for t in threads]
|
|
|
|
def log(*args):
|
|
print(">", *args, file=sys.stderr)
|
|
|
|
class Piper(threading.Thread):
|
|
def __init__(self, inq, outq):
|
|
threading.Thread.__init__(self)
|
|
self.inq = inq
|
|
self.outq = outq
|
|
|
|
def run(self):
|
|
while True:
|
|
got = self.inq.get()
|
|
if got is None:
|
|
break
|
|
self._run(got)
|
|
self.outq.put(None)
|
|
|
|
class Manager(threading.Thread):
|
|
def __init__(self, outq):
|
|
threading.Thread.__init__(self)
|
|
self.outq = outq
|
|
inq = queue.Queue(maxsize=1)
|
|
def catcher(sig, frame):
|
|
inq.put(None)
|
|
self.inq = inq
|
|
signal.signal(signal.SIGINT, catcher)
|
|
|
|
def run(self):
|
|
log("Manager.run: start")
|
|
self.inq.get()
|
|
self.outq.put(None)
|
|
log("Manager.run: stop")
|
|
|
|
class Reader(threading.Thread):
|
|
def __init__(self, inq, outq):
|
|
threading.Thread.__init__(self)
|
|
self.name = environ.get("MIC_NAME", "pulse_monitor")
|
|
if not self.name:
|
|
for index, name in enumerate(sr.Microphone.list_microphone_names()):
|
|
print("[{0}] Microphone with name \"{1}\" found for `Microphone(device_index={0})`".format(index, name))
|
|
exit()
|
|
self.inq = inq
|
|
self.outq = outq
|
|
|
|
def run(self):
|
|
log("Reader.run: start")
|
|
idx = [
|
|
idx for idx,v in enumerate(
|
|
sr.Microphone.list_microphone_names(),
|
|
) if v in self.name.split(",")
|
|
][0]
|
|
with sr.Microphone(device_index=idx) as mic:
|
|
while not self.should_stop():
|
|
try:
|
|
self.outq.put(self._run(mic))
|
|
except Exception as e:
|
|
if not "timed out" in str(e):
|
|
log("Reader.run: error:", e)
|
|
self.outq.put(None)
|
|
log("Reader.run: stop")
|
|
|
|
def should_stop(self):
|
|
return not self.inq.empty()
|
|
|
|
def _run(self, mic):
|
|
mic_timeout = int(environ.get("MIC_TIMEOUT", 5))
|
|
r = sr.Recognizer()
|
|
return r.listen(
|
|
mic,
|
|
timeout=mic_timeout,
|
|
phrase_time_limit=mic_timeout,
|
|
)
|
|
|
|
class Parser(threading.Thread):
|
|
def __init__(self, inq, outq):
|
|
threading.Thread.__init__(self)
|
|
self.inq = inq
|
|
self.outq = outq
|
|
|
|
def run(self):
|
|
log("Parser.run: start")
|
|
while True:
|
|
try:
|
|
clip = self.inq.get()
|
|
if not clip:
|
|
break
|
|
self.outq.put(self._run(clip).strip())
|
|
except Exception as e:
|
|
log("Parser.run: error:", e)
|
|
self.outq.put(None)
|
|
log("Parser.run: stop")
|
|
|
|
def _run(self, clip):
|
|
r = sr.Recognizer()
|
|
return r.recognize_whisper(clip, language="english")
|
|
|
|
|
|
class Reactor(threading.Thread):
|
|
def __init__(self, inq, outq):
|
|
threading.Thread.__init__(self)
|
|
self.inq = inq
|
|
self.outq = outq
|
|
self.load_hotwords = Reactor.new_load_hotwords()
|
|
[i for i in self.load_hotwords()]
|
|
|
|
def new_load_hotwords():
|
|
p = environ.get("HOTWORDS", None)
|
|
if not p:
|
|
def load_nothing():
|
|
return []
|
|
return load_nothing
|
|
|
|
try:
|
|
def load_hotwords_in_file():
|
|
with open(p, "r") as f:
|
|
return ["".join(i.strip().lower().split()) for i in f.readlines()]
|
|
load_hotwords_in_file()
|
|
return load_hotwords_in_file
|
|
except Exception:
|
|
pass
|
|
|
|
hotwords = ["".join(i.lower().strip().split()) for i in p.split(",")]
|
|
log(f'$HOTWORDS: {hotwords}')
|
|
def load_hotwords_as_literal():
|
|
return hotwords
|
|
return load_hotwords_as_literal
|
|
|
|
def run(self):
|
|
log("Reactor.run: start")
|
|
while True:
|
|
text = self.inq.get()
|
|
if text is None:
|
|
break
|
|
self.handle(text)
|
|
self.outq.put(None)
|
|
log("Reactor.run: stop")
|
|
|
|
def handle(self, text):
|
|
hotwords = self.load_hotwords()
|
|
if environ.get("DEBUG", None):
|
|
log(f"seeking {hotwords} in {text}")
|
|
if not hotwords:
|
|
self.outq.put(("", text))
|
|
return
|
|
cleantext = "".join([i for i in "".join(text.lower().split()) if i.isalpha()])
|
|
for i in hotwords:
|
|
if i in cleantext:
|
|
#log(f"Reactor.handle: found hotword '{i}' in '{text}' as '{cleantext}'")
|
|
self.outq.put((i, text))
|
|
|
|
class Actor(threading.Thread):
|
|
def __init__(self, inq):
|
|
threading.Thread.__init__(self)
|
|
self.inq = inq
|
|
self.handle = self.handle_stderr
|
|
if environ.get("STDOUT", "") == "true":
|
|
self.handle = self.handle_stdout
|
|
elif environ.get("SIGUSR2", ""):
|
|
self.pid = int(environ["SIGUSR2"])
|
|
self.handle = self.handle_signal
|
|
elif environ.get("URL", ""):
|
|
self.url = environ["URL"]
|
|
self.handle = self.handle_url
|
|
self.headers = [i.split("=")[:2] for i in environ.get("HEADERS", "").split("//") if i]
|
|
self.body = environ.get("BODY", '{"hotword":"{{hotword}}","context":"{{context}}"}')
|
|
log(self.headers)
|
|
|
|
def run(self):
|
|
log("Actor.run: start")
|
|
while True:
|
|
got = self.inq.get()
|
|
if got is None:
|
|
break
|
|
self.handle(got[0], got[1])
|
|
log("Actor.run: stop")
|
|
|
|
def handle_stderr(self, hotword, context):
|
|
log(f"'{hotword}' in '{context}'")
|
|
|
|
def handle_stdout(self, hotword, context):
|
|
log(context)
|
|
print(hotword)
|
|
|
|
def handle_signal(self, hotword, context):
|
|
self.handle_stderr(hotword, context)
|
|
kill(self.pid, signal.SIGUSR2)
|
|
|
|
def handle_url(self, hotword, context):
|
|
self.handle_stderr(hotword, context)
|
|
try:
|
|
headers = {}
|
|
for i in self.headers:
|
|
key = i[0]
|
|
value = i[1]
|
|
value = value.replace("{{hotword}}", hotword)
|
|
value = value.replace("{{context}}", context)
|
|
headers[key] = value
|
|
body = self.body
|
|
body = body.replace("{{hotword}}", hotword)
|
|
body = body.replace("{{context}}", context)
|
|
if environ.get("DEBUG", "") :
|
|
log("POST", self.url, headers, body)
|
|
requests.post(self.url, headers=headers, data=body)
|
|
except Exception as e:
|
|
log("Actor.handle_url:", e)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|