From 5a1fe8c07808425985240813ff9561483142be4e Mon Sep 17 00:00:00 2001 From: "Matthew D. Scholefield" Date: Mon, 27 Nov 2017 20:28:58 -0600 Subject: [PATCH] Add runner wrapper for simple use in Python programs --- runner/precise_runner/__init__.py | 1 + runner/precise_runner/runner.py | 82 +++++++++++++++++++++++++++++++ runner/setup.py | 36 ++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 runner/precise_runner/__init__.py create mode 100644 runner/precise_runner/runner.py create mode 100755 runner/setup.py diff --git a/runner/precise_runner/__init__.py b/runner/precise_runner/__init__.py new file mode 100644 index 0000000..c9acc8f --- /dev/null +++ b/runner/precise_runner/__init__.py @@ -0,0 +1 @@ +from .runner import PreciseRunner diff --git a/runner/precise_runner/runner.py b/runner/precise_runner/runner.py new file mode 100644 index 0000000..d181e62 --- /dev/null +++ b/runner/precise_runner/runner.py @@ -0,0 +1,82 @@ +# Python 2 + 3 +# Copyright (c) 2017 Mycroft AI Inc. + +import atexit +from psutil import Popen +from subprocess import PIPE +from threading import Thread + + +class PreciseRunner: + """ + Wrapper to use Precise + + Args: + exe_file (str): Location to precise-stream executable + model (str): Location to .pb model file to use (with .pb.params) + chunk_size (int): Number of samples per prediction. Higher numbers + decrease CPU usage but increase latency + stream (BinaryIO): Binary audio stream to read 16000 Hz 1 channel int16 + audio from. If not given, the microphone is used + on_prediction: callback for every new prediction + on_activation: callback for when the wake word is heard + """ + def __init__(self, exe_file, model, chunk_size=1024, stream=None, + on_prediction=lambda x: None, on_activation=lambda: None): + self.pa = None + self.stream = stream + self.exe_file = exe_file + self.model = model + self.chunk_size = chunk_size + self.thread = None + self.proc = None + self.on_prediction = on_prediction + self.on_activation = on_activation + self.running = False + self.cooldown = 0 + atexit.register(self.stop) + + def start(self): + """Start listening from stream""" + if self.stream is None: + from pyaudio import PyAudio, paInt16 + self.pa = PyAudio() + self.stream = self.pa.open(16000, 1, paInt16, True, frames_per_buffer=self.chunk_size) + + self.proc = Popen([self.exe_file, self.model, str(self.chunk_size)], stdin=PIPE, stdout=PIPE) + self.running = True + self.thread = Thread(target=self._check_output) + self.thread.daemon = True + self.thread.start() + + def stop(self): + """Stop listening and close stream""" + if self.thread: + self.running = False + self.thread.join() + self.thread = None + + if self.proc: + self.proc.kill() + self.proc = None + + if self.pa: + self.pa.terminate() + self.stream.stop_stream() + self.stream = self.pa = None + + def _check_output(self): + """Continuously check Precise process output""" + while self.running: + chunk = self.stream.read(self.chunk_size) + self.proc.stdin.write(chunk) + self.proc.stdin.flush() + + prob = float(self.proc.stdout.readline()) + self.on_prediction(prob) + + if self.cooldown > 0: + self.cooldown -= 1 + elif prob > 0.5: + self.cooldown = self.chunk_size // 50 + self.on_activation() diff --git a/runner/setup.py b/runner/setup.py new file mode 100755 index 0000000..bb3ffc2 --- /dev/null +++ b/runner/setup.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +from setuptools import setup, find_packages + +setup( + name='precise-runner', + version='0.1.0', + packages=find_packages(), + install_requires=[ + 'pyaudio' + ], + + author='Matthew Scholefield', + author_email='matthew.scholefield@mycroft.ai', + description='Wrapper to use Mycroft Precise Wake Word Listener', + keywords='wakeword keyword wake word listener sound', + url='http://github.com/MycroftAI/mycroft-precise', + + zip_safe=True, + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Topic :: Text Processing :: Linguistic', + + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.0', + 'Programming Language :: Python :: 3.1', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], +)