Skip to content
This repository has been archived by the owner on May 17, 2022. It is now read-only.

Commit

Permalink
Merge branch 'hotword_factory' into patch-15
Browse files Browse the repository at this point in the history
Conflicts:
	dev_setup.sh
	mycroft/client/speech/listener.py
	mycroft/client/speech/mic.py
	mycroft/client/speech/recognizer/pocketsphinx_recognizer.py
	mycroft/configuration/mycroft.conf
	mycroft/skills/padatious_service.py
	requirements.txt
	test/unittests/client/audio_consumer_test.py
	test/unittests/client/local_recognizer_test.py
  • Loading branch information
JarbasAl committed Sep 13, 2017
2 parents 6b76ff8 + 904d7b4 commit 6d1db4d
Show file tree
Hide file tree
Showing 15 changed files with 206 additions and 272 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ logs/*
mycroft/audio-accuracy-test/data/*
scripts/*.screen
doc/_build/
.installed
12 changes: 12 additions & 0 deletions mycroft.sh
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,22 @@ function stop-mycroft {
stop-screen "mycroft-$1"
}

function found_exe {
hash "$1" 2>/dev/null
}

set -e

case "$1" in
"start")

if [ ! -f .installed ] || ! md5sum -c &>/dev/null < .installed; then
echo "Please update dependencies by running ./dev_setup.sh again."
if found_exe notify-send; then
notify-send "Please Update Dependencies" "Run ./dev_setup.sh again"
fi
fi

$0 stop
start-mycroft service
start-mycroft skills
Expand Down
6 changes: 3 additions & 3 deletions mycroft/client/enclosure/eyes.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ def look(self, event=None):
def color(self, event=None):
r, g, b = 255, 255, 255
if event and event.data:
r = int(event.data.get("r"), r)
g = int(event.data.get("g"), g)
b = int(event.data.get("b"), b)
r = int(event.data.get("r", r))
g = int(event.data.get("g", g))
b = int(event.data.get("b", b))
color = (r * 65536) + (g * 256) + b
self.writer.write("eyes.color=" + str(color))

Expand Down
83 changes: 0 additions & 83 deletions mycroft/client/speech/hot_word_factory.py

This file was deleted.

144 changes: 144 additions & 0 deletions mycroft/client/speech/hotword_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Copyright 2017 Mycroft AI, Inc.
#
# This file is part of Mycroft Core.
#
# Mycroft Core is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Mycroft Core is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.

from mycroft.configuration import ConfigurationManager
from mycroft.util.log import getLogger
from os.path import dirname, exists, join, abspath
import os
import time
import tempfile

__author__ = 'seanfitz, jdorleans, jarbas'

LOG = getLogger("HotwordFactory")

RECOGNIZER_DIR = join(abspath(dirname(__file__)), "recognizer")


class HotWordEngine(object):
def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"):
self.lang = str(lang).lower()
self.key_phrase = str(key_phrase).lower()
# rough estimate 1 phoneme per 2 chars
self.num_phonemes = len(key_phrase) / 2 + 1
if config is None:
config = ConfigurationManager.get().get("hot_words", {})
config = config.get(self.key_phrase, {})
self.config = config
self.listener_config = ConfigurationManager.get().get("listener", {})

def found_wake_word(self, frame_data):
return False


class PocketsphinxHotWord(HotWordEngine):
def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"):
super(PocketsphinxHotWord, self).__init__(key_phrase, config, lang)
# Hotword module imports
from pocketsphinx import Decoder
# Hotword module config
module = self.config.get("module")
if module != "pocketsphinx":
LOG.warning(
str(module) + " module does not match with "
"Hotword class pocketsphinx")
# Hotword module params
self.phonemes = self.config.get("phonemes", "HH EY . M AY K R AO F T")
self.num_phonemes = len(self.phonemes.split())
self.threshold = self.config.get("threshold", 1e-90)
self.sample_rate = self.listener_config.get("sample_rate", 1600)
dict_name = self.create_dict(key_phrase, self.phonemes)
config = self.create_config(dict_name, Decoder.default_config())
self.decoder = Decoder(config)

def create_dict(self, key_phrase, phonemes):
(fd, file_name) = tempfile.mkstemp()
words = key_phrase.split()
phoneme_groups = phonemes.split('.')
with os.fdopen(fd, 'w') as f:
for word, phoneme in zip(words, phoneme_groups):
f.write(word + ' ' + phoneme + '\n')
return file_name

def create_config(self, dict_name, config):
model_file = join(RECOGNIZER_DIR, 'model', self.lang, 'hmm')
if not exists(model_file):
LOG.error('PocketSphinx model not found at ' + str(model_file))
config.set_string('-hmm', model_file)
config.set_string('-dict', dict_name)
config.set_string('-keyphrase', self.key_phrase)
config.set_float('-kws_threshold', float(self.threshold))
config.set_float('-samprate', self.sample_rate)
config.set_int('-nfft', 2048)
config.set_string('-logfn', '/dev/null')
return config

def transcribe(self, byte_data, metrics=None):
start = time.time()
self.decoder.start_utt()
self.decoder.process_raw(byte_data, False, False)
self.decoder.end_utt()
if metrics:
metrics.timer("mycroft.stt.local.time_s", time.time() - start)
return self.decoder.hyp()

def found_wake_word(self, frame_data):
hyp = self.transcribe(frame_data)
return hyp and self.key_phrase in hyp.hypstr.lower()


class SnowboyHotWord(HotWordEngine):
def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"):
super(SnowboyHotWord, self).__init__(key_phrase, config, lang)
# Hotword module imports
from snowboydecoder import HotwordDetector
# Hotword module config
module = self.config.get("module")
if module != "snowboy":
LOG.warning(module + " module does not match with Hotword class "
"snowboy")
# Hotword params
models = self.config.get("models", {})
paths = []
for key in models:
paths.append(models[key])
sensitivity = self.config.get("sensitivity", 0.5)
self.snowboy = HotwordDetector(paths,
sensitivity=[sensitivity] * len(paths))
self.lang = str(lang).lower()
self.key_phrase = str(key_phrase).lower()

def found_wake_word(self, frame_data):
wake_word = self.snowboy.detector.RunDetection(frame_data)
return wake_word == 1


class HotWordFactory(object):
CLASSES = {
"pocketsphinx": PocketsphinxHotWord,
"snowboy": SnowboyHotWord
}

@staticmethod
def create_hotword(hotword="hey mycroft", config=None, lang="en-us"):
LOG.info("creating " + hotword)
if not config:
config = ConfigurationManager.get().get("hotwords", {})
module = config.get(hotword).get("module", "pocketsphinx")
config = config.get(hotword, {"module": module})
clazz = HotWordFactory.CLASSES.get(module)
return clazz(hotword, config, lang=lang)
45 changes: 20 additions & 25 deletions mycroft/client/speech/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from mycroft.session import SessionManager
from mycroft.stt import STTFactory
from mycroft.util.log import getLogger
from mycroft.client.speech.hot_word_factory import HotWordFactory
from mycroft.client.speech.hotword_factory import HotWordFactory

LOG = getLogger(__name__)

Expand Down Expand Up @@ -105,23 +105,6 @@ def __init__(self, state, queue, emitter, stt,
self.config = ConfigurationManager.get()
self.word = self.wakeword_recognizer.key_phrase
self.emitter.on("recognizer_loop:hotword", self.set_word)
self.emitter.on("recognizer_loop:external_audio",
self.handle_external_audio_request)

def read_wave_file(self, wave_file_path):
# use the audio file as the audio source
r = sr.Recognizer()
with sr.AudioFile(wave_file_path) as source:
audio = r.record(source)
return audio

def handle_external_audio_request(self, event):
wave_file = event.get("wave_file")
audio = self.read_wave_file(wave_file)
if audio is not None:
text = self.transcribe(audio, False)
self.emitter.emit("recognizer_loop:external_audio.reply",
{"stt": text})

def set_word(self, event):
self.word = event.get("hotword", self.wakeword_recognizer.key_phrase)
Expand Down Expand Up @@ -228,6 +211,7 @@ class RecognizerLoop(EventEmitter):
EventEmitter loop running speech recognition. Local wake word
recognizer and remote general speech recognition.
"""

def __init__(self):
super(RecognizerLoop, self).__init__()
self._load_config()
Expand All @@ -249,10 +233,11 @@ def _load_config(self):
self.microphone.CHANNELS = self.config.get('channels')
self.wakeword_recognizer = self.create_wake_word_recognizer()
# TODO - localization
self.wakeup_recognizer = self.create_wakeup_recognizer()
self.responsive_recognizer = ResponsiveRecognizer(
self.wakeword_recognizer)
self.hot_word_engines = {}
self.create_hot_word_engines()
self.wakeup_recognizer = self.create_wakeup_recognizer()
self.responsive_recognizer = ResponsiveRecognizer(self.wakeword_recognizer, self.hot_word_engines)
self.state = RecognizerLoopState()

def create_hot_word_engines(self):
Expand All @@ -266,18 +251,28 @@ def create_hot_word_engines(self):
ding = data.get("sound")
utterance = data.get("utterance")
listen = data.get("listen", False)
engine = HotWordFactory.create_hotword(word)
engine = HotWordFactory.create_hotword(word, lang=self.lang)
self.hot_word_engines[word] = [engine, ding, utterance,
listen, type]

def create_wake_word_recognizer(self):
# Create a local recognizer to hear the wakeup word, e.g. 'Hey Mycroft'
LOG.info("creating wake word engine")
return HotWordFactory.create_wake_word()
word = "hey mycroft" # self.config.get("wake_word", "hey mycroft")
# TODO remove this, only for server settings compatibility
phonemes = self.config.get("phonemes")
thresh = self.config.get("threshold")
config = self.config_core.get("hotwords", {word: {}})
config[word]["phonemes"] = phonemes
config[word]["threshold"] = thresh
if not phonemes and not thresh:
config = None
return HotWordFactory.create_hotword(word, config, self.lang)

def create_wakeup_recognizer(self):
LOG.info("creating stand up word engine")
return HotWordFactory.create_standup_word()
word = self.config.get("stand_up_word", "wake up")
return HotWordFactory.create_hotword(word, lang=self.lang)

def start_async(self):
"""
Expand Down Expand Up @@ -328,8 +323,8 @@ def run(self):
while self.state.running:
try:
time.sleep(1)
if self._config_hash != hash(str(ConfigurationManager()
.get())):
if self._config_hash != hash(str(
ConfigurationManager().get())):
LOG.debug('Config has changed, reloading...')
self.reload()
except KeyboardInterrupt as e:
Expand Down
2 changes: 1 addition & 1 deletion mycroft/client/speech/mic.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def __init__(self, wake_word_recognizer, hot_word_engines=None):
# The wake word must fit in this time
if hot_word_engines is None:
hot_word_engines = {}
num_phonemes = len(listener_config.get('phonemes').split())
num_phonemes = wake_word_recognizer.num_phonemes
len_phoneme = listener_config.get('phoneme_duration', 120) / 1000.0
self.SAVED_WW_SEC = num_phonemes * len_phoneme
speech_recognition.Recognizer.__init__(self)
Expand Down
Loading

0 comments on commit 6d1db4d

Please sign in to comment.