Fix wakeword.
[kiosk.git] / listen.py
1 #!/usr/bin/env python3
2
3 import logging
4 import os
5 import struct
6
7 import pvporcupine
8 import pyaudio
9 import speech_recognition as sr
10 from pyutils import logging_utils
11
12 import kiosk_secrets as secrets
13
14
15 logger = logging.getLogger(__name__)
16
17
18 class HotwordListener(object):
19     def __init__(
20         self,
21         command_queue,
22         keyword_paths,
23         sensitivities,
24         input_device_index=None,
25 #        library_path=pvporcupine.LIBRARY_PATH,
26 #        model_path=pvporcupine.MODEL_PATH,
27     ):
28         self._queue = command_queue
29 #        self._library_path = library_path
30 #        self._model_path = model_path
31         self._keyword_paths = keyword_paths
32         self._sensitivities = sensitivities
33         self._input_device_index = input_device_index
34
35     @logging_utils.LoggingContext(logger, prefix="listener:")
36     def listen_forever(self):
37         keywords = list()
38         for x in self._keyword_paths:
39             keywords.append(os.path.basename(x).replace(".ppn", "").split("_")[0])
40
41         porcupine = None
42         pa = None
43         audio_stream = None
44         try:
45             porcupine = pvporcupine.create(
46                 keyword_paths=self._keyword_paths,
47                 sensitivities=self._sensitivities,
48                 access_key=secrets.pvporcupine_key,
49             )
50             recognizer = sr.Recognizer()
51             pa = pyaudio.PyAudio()
52
53             audio_stream = pa.open(
54                 rate=porcupine.sample_rate,
55                 channels=1,
56                 format=pyaudio.paInt16,
57                 input=True,
58                 frames_per_buffer=porcupine.frame_length,
59                 input_device_index=self._input_device_index,
60             )
61
62             logger.info("Listening {")
63             for keyword, sensitivity in zip(keywords, self._sensitivities):
64                 logger.info("  %s (%.2f)" % (keyword, sensitivity))
65             logger.info("}")
66
67             while True:
68                 raw = audio_stream.read(
69                     porcupine.frame_length, exception_on_overflow=False
70                 )
71                 pcm = struct.unpack_from("h" * porcupine.frame_length, raw)
72                 result = porcupine.process(pcm)
73                 if result >= 0:
74                     cmd = "aplay /var/www/kiosk/attention.wav"
75                     logger.info(
76                         "Running %s (attention tone) because I heard the wake-word", cmd
77                     )
78                     os.system(cmd)
79                     logger.debug(
80                         ">>>>>>>>>>>>> Detected wakeword %s" % keywords[result]
81                     )
82                     raw = bytearray()
83                     for i in range(
84                         0, int(porcupine.sample_rate / porcupine.frame_length * 4)
85                     ):
86                         raw += audio_stream.read(
87                             porcupine.frame_length, exception_on_overflow=False
88                         )
89                     logger.debug(
90                         f">>>>>>>>>>>>>> Recognizing command... {len(raw)} bytes"
91                     )
92                     speech = sr.AudioData(
93                         frame_data=bytes(raw),
94                         sample_rate=porcupine.sample_rate,
95                         sample_width=2,  # 16 bits
96                     )
97                     command = recognizer.recognize_google(speech)
98                     logger.debug(">>>>>>>>>>>>> Google says command was %s" % command)
99                     logger.info("Enqueued command=%s", command)
100                     self._queue.put(command)
101
102         except Exception:
103             logger.exception("Stopping listener because of unexpected exception!")
104
105         except KeyboardInterrupt:
106             logger.exception("Stopping listener because of ^C!")
107
108         finally:
109             logger.debug("Cleaning up... one sec...")
110             if porcupine is not None:
111                 porcupine.delete()
112
113             if audio_stream is not None:
114                 audio_stream.close()
115
116             if pa is not None:
117                 pa.terminate()
118
119     @classmethod
120     def show_audio_devices(cls):
121         fields = ("index", "name", "defaultSampleRate", "maxInputChannels")
122         pa = pyaudio.PyAudio()
123         for i in range(pa.get_device_count()):
124             info = pa.get_device_info_by_index(i)
125             print(", ".join("'%s': '%s'" % (k, str(info[k])) for k in fields))
126         pa.terminate()
127
128
129 def main():
130     keyword_paths = [pvporcupine.KEYWORD_PATHS[x] for x in ["blueberry", "bumblebee"]]
131     sensitivities = [0.85, 0.95]
132     HotwordListener(
133         [],
134         keyword_paths,
135         sensitivities,
136     ).listen_forever()
137
138
139 if __name__ == "__main__":
140     main()