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