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