From 4c1ba4e337998b4776361e11c3449d3807cb147d Mon Sep 17 00:00:00 2001 From: Jonathan D'Orleans Date: Fri, 16 Sep 2016 22:08:53 -0400 Subject: [PATCH] Issues 356 - Rebasing with master --- README.md | 6 +- mycroft-base-MANIFEST.in | 2 +- mycroft/client/enclosure/api.py | 37 ++-- mycroft/client/enclosure/mouth.py | 31 ++- mycroft/client/speech/local_recognizer.py | 4 +- mycroft/client/speech/main.py | 2 +- mycroft/client/speech/mic.py | 12 +- mycroft/configuration/mycroft.ini | 110 ---------- mycroft/session/__init__.py | 11 +- mycroft/skills/alarm/__init__.py | 11 +- mycroft/skills/audio_record/__init__.py | 16 +- mycroft/skills/container.py | 2 +- mycroft/skills/date_time/__init__.py | 8 +- mycroft/skills/main.py | 7 +- mycroft/skills/reminder/__init__.py | 13 +- mycroft/skills/scheduled_skills.py | 4 +- mycroft/skills/volume/__init__.py | 13 +- .../weather/owm_repackaged/owmhttpclient.py | 7 +- mycroft/skills/wiki/__init__.py | 4 +- mycroft/tts/__init__.py | 11 +- mycroft/tts/espeak_tts.py | 2 +- mycroft/tts/google_tts.py | 4 +- mycroft/tts/mimic_tts.py | 200 ++++++++---------- mycroft/tts/remote_tts.py | 2 +- mycroft/tts/spdsay_tts.py | 2 +- mycroft/util/__init__.py | 12 +- skills-sdk-MANIFEST.in | 2 +- test/configuration/__init__.py | 33 +-- test/configuration/mycroft.conf | 9 + test/configuration/mycroft.ini | 5 - test/mycroft.ini | 2 - 31 files changed, 226 insertions(+), 358 deletions(-) delete mode 100644 mycroft/configuration/mycroft.ini create mode 100644 test/configuration/mycroft.conf delete mode 100644 test/configuration/mycroft.ini delete mode 100644 test/mycroft.ini diff --git a/README.md b/README.md index 042af61ce1..4fe11f374b 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,9 @@ These are the keys currently in use in Mycroft Core. ## Configuration Mycroft configuration consists of 3 possible config files. -- `mycroft-core/mycroft/configuration/mycroft.ini` -- `/etc/mycroft/mycroft.ini` -- `$HOME/.mycroft/mycroft.ini` +- `mycroft-core/mycroft/configuration/mycroft.conf` +- `/etc/mycroft/mycroft.conf` +- `$HOME/.mycroft/mycroft.conf` When the configuration loader starts, it looks in those locations in that order, and loads ALL configuration. Keys that exist in multiple config files will be overridden by the last file to contain that config value. This results in a minimal amount of config being written for a specific device/user, without modifying the distribution files. diff --git a/mycroft-base-MANIFEST.in b/mycroft-base-MANIFEST.in index 5239e4d6d0..0cbe001005 100644 --- a/mycroft-base-MANIFEST.in +++ b/mycroft-base-MANIFEST.in @@ -1,6 +1,6 @@ recursive-include mycroft/client/speech/model * include requirements.txt -include mycroft/configuration/*.ini +include mycroft/configuration/*.conf recursive-include mycroft/skills/*/dialog * recursive-include mycroft/skills/*/vocab * recursive-include mycroft/skills/*/regex * diff --git a/mycroft/client/enclosure/api.py b/mycroft/client/enclosure/api.py index a833a2bd33..1a452748ef 100644 --- a/mycroft/client/enclosure/api.py +++ b/mycroft/client/enclosure/api.py @@ -44,8 +44,7 @@ class EnclosureAPI: self.client.emit(Message("enclosure.system.unmute")) def system_blink(self, times): - self.client.emit( - Message("enclosure.system.blink", {'times': times})) + self.client.emit(Message("enclosure.system.blink", {'times': times})) def eyes_on(self): self.client.emit(Message("enclosure.eyes.on")) @@ -54,34 +53,30 @@ class EnclosureAPI: self.client.emit(Message("enclosure.eyes.off")) def eyes_blink(self, side): - self.client.emit( - Message("enclosure.eyes.blink", {'side': side})) + self.client.emit(Message("enclosure.eyes.blink", {'side': side})) def eyes_narrow(self): self.client.emit(Message("enclosure.eyes.narrow")) def eyes_look(self, side): - self.client.emit( - Message("enclosure.eyes.look", {'side': side})) + self.client.emit(Message("enclosure.eyes.look", {'side': side})) def eyes_color(self, r=255, g=255, b=255): - self.client.emit( - Message("enclosure.eyes.color", {'r': r, 'g': g, 'b': b})) + self.client.emit(Message("enclosure.eyes.color", + {'r': r, 'g': g, 'b': b})) def eyes_brightness(self, level=30): - self.client.emit( - Message("enclosure.eyes.level", {'level': level})) + self.client.emit(Message("enclosure.eyes.level", {'level': level})) def eyes_reset(self): self.client.emit(Message("enclosure.eyes.reset")) def eyes_timed_spin(self, length): - self.client.emit( - Message("enclosure.eyes.timedspin", {'length': length})) + self.client.emit(Message("enclosure.eyes.timedspin", + {'length': length})) def eyes_volume(self, volume): - self.client.emit( - Message("enclosure.eyes.volume", {'volume': volume})) + self.client.emit(Message("enclosure.eyes.volume", {'volume': volume})) def mouth_reset(self): self.client.emit(Message("enclosure.mouth.reset")) @@ -98,18 +93,16 @@ class EnclosureAPI: def mouth_smile(self): self.client.emit(Message("enclosure.mouth.smile")) - def mouth_viseme(self, visCode): - self.client.emit( - Message("enclosure.mouth.viseme", {'code': visCode})) + def mouth_viseme(self, codes, durations): + self.client.emit(Message("enclosure.mouth.viseme", + {'codes': codes, 'durations': durations})) def mouth_text(self, text=""): - self.client.emit( - Message("enclosure.mouth.text", {'text': text})) + self.client.emit(Message("enclosure.mouth.text", {'text': text})) def weather_display(self, img_code, temp): - self.client.emit( - Message("enclosure.weather.display", - {'img_code': img_code, 'temp': temp})) + self.client.emit(Message("enclosure.weather.display", + {'img_code': img_code, 'temp': temp})) def activate_mouth_events(self): self.client.emit(Message('enclosure.mouth.events.activate')) diff --git a/mycroft/client/enclosure/mouth.py b/mycroft/client/enclosure/mouth.py index b5e0817acc..50a0ceb399 100644 --- a/mycroft/client/enclosure/mouth.py +++ b/mycroft/client/enclosure/mouth.py @@ -16,13 +16,13 @@ # along with Mycroft Core. If not, see . +from time import time, sleep + from mycroft.util.log import getLogger -from mycroft.util import check_for_signal -import time __author__ = 'jdorleans' -LOGGER = getLogger(__name__) +LOG = getLogger(__name__) class EnclosureMouth: @@ -62,22 +62,17 @@ class EnclosureMouth: self.writer.write("mouth.smile") def viseme(self, event=None): - visCmds = '' if event and event.metadata: - visCmds = event.metadata.get("code", visCmds) - # visCmds will be string of viseme codes and cumulative durations - # ex: '0:0.34,1:1.23,0:1.32,' - lisPairs = visCmds.split(",") - timeStart = time.time() - for pair in lisPairs: - if check_for_signal('buttonPress'): - return # abort! (aplay should have already been killed) - vis_dur = pair.split(":") - if vis_dur[0] >= "0" and vis_dur[0] <= "6": - elap = time.time() - timeStart - self.writer.write("mouth.viseme=" + vis_dur[0]) - if elap < float(vis_dur[1]): - time.sleep(float(vis_dur[1]) - elap) + start = time() + codes = event.metadata.get("codes") + durations = event.metadata.get("durations") + for idx, code in enumerate(codes): + if "0" <= code <= "6": + self.writer.write("mouth.viseme=" + code) + duration = float(durations[idx]) + delta = time() - start + if delta < duration: + sleep(duration - delta) def text(self, event=None): text = "" diff --git a/mycroft/client/speech/local_recognizer.py b/mycroft/client/speech/local_recognizer.py index c51420a9a9..99b188e1ff 100644 --- a/mycroft/client/speech/local_recognizer.py +++ b/mycroft/client/speech/local_recognizer.py @@ -16,12 +16,12 @@ # along with Mycroft Core. If not, see . +import tempfile import time import os from os.path import join, dirname, abspath from pocketsphinx import Decoder -import tempfile __author__ = 'seanfitz, jdorleans' @@ -53,7 +53,7 @@ class LocalRecognizer(object): config.set_string('-hmm', join(BASEDIR, 'model', self.lang, 'hmm')) config.set_string('-dict', dict_name) config.set_string('-keyphrase', self.key_phrase) - config.set_float('-kws_threshold', float(self.threshold)) + config.set_float('-kws_threshold', self.threshold) config.set_float('-samprate', self.sample_rate) config.set_int('-nfft', 2048) config.set_string('-logfn', '/dev/null') diff --git a/mycroft/client/speech/main.py b/mycroft/client/speech/main.py index ef566e9f48..a72d03500f 100644 --- a/mycroft/client/speech/main.py +++ b/mycroft/client/speech/main.py @@ -65,7 +65,7 @@ def mute_and_speak(utterance): try: logger.info("Speak: " + utterance) loop.mute() - tts.execute(utterance, client) + tts.execute(utterance) finally: loop.unmute() mutex.release() diff --git a/mycroft/client/speech/mic.py b/mycroft/client/speech/mic.py index ad08d2069d..a4b0cccd17 100644 --- a/mycroft/client/speech/mic.py +++ b/mycroft/client/speech/mic.py @@ -16,19 +16,17 @@ # along with Mycroft Core. If not, see . -import collections import audioop -import os -import os.path +import collections from time import sleep import pyaudio +import speech_recognition from speech_recognition import ( Microphone, AudioSource, AudioData ) -import speech_recognition from mycroft.configuration import ConfigurationManager from mycroft.util.log import getLogger @@ -148,10 +146,8 @@ class ResponsiveRecognizer(speech_recognition.Recognizer): speech_recognition.Recognizer.__init__(self) self.wake_word_recognizer = wake_word_recognizer self.audio = pyaudio.PyAudio() - self.threshold_multiplier = float( - listener_config.get('threshold_multiplier')) - self.dynamic_energy_ratio = float( - listener_config.get('dynamic_energy_ratio')) + self.threshold_multiplier = listener_config.get('threshold_multiplier') + self.dynamic_energy_ratio = listener_config.get('dynamic_energy_ratio') @staticmethod def record_sound_chunk(source): diff --git a/mycroft/configuration/mycroft.ini b/mycroft/configuration/mycroft.ini deleted file mode 100644 index 8f12569f95..0000000000 --- a/mycroft/configuration/mycroft.ini +++ /dev/null @@ -1,110 +0,0 @@ -# TODO - User Configuration -[core] -lang = "en-us" -location = "Lawrence, Kansas" -time.format = "%H:%M" -date.format = "%A, %B %d, %Y" -stop_threshold = 2 # in seconds -third_party_skills_dir = "~/.mycroft/third_party_skills" - -[server] -url = "https://api.mycroft.ai" -version = "v1" -auto_update = True - -[messagebus_service] -host = "0.0.0.0" -port = 8000 -route = "/events/ws" - -[messagebus_client] -host = "0.0.0.0" -port = 8000 -route = "/events/ws" -ssl = False - -[metrics_client] -url = "https://cerberus.mycroft.ai/metrics" -enabled = False - -[pairing_client] -host = "cerberus.mycroft.ai" -port = 443 -route = "/pairing" -ssl = True - -[speech_client] -sample_rate = 16000 -channels = 1 -proxy_host = "https://cerberus.mycroft.ai" -recognizer_impl = 'google_proxy' -goog_api_key = "" -wit_api_key = "" -ibm_username = "" -ibm_password = "" - -[listener] -wake_word = "hey mycroft" -phonemes = "HH EY . M AY K R AO F T" -threshold = "1e-90" - -threshold_multiplier = "1.0" -dynamic_energy_ratio = "1.5" - -[enclosure] -port = "/dev/ttyAMA0" -rate = 9600 -timeout = 5 # in seconds - -[session_management] -session_ttl_seconds = 180 - -[tts] -module = "mimic" -mimic.voice = "ap" -espeak.lang = "english-us" -espeak.voice = "m1" - -[WikipediaSkill] -max_results = 5 -max_phrases = 2 - -[WolframAlphaSkill] -api_key = "" - -[WeatherSkill] -api_key = "" -temperature = fahrenheit # Options are celsius and fahrenheit - -[NPRNewsSkill] -url_rss = "http://www.npr.org/rss/podcast.php?id=500005" - -[TimeSkill] -time_format = 12h # Options are 12h and 24h - -[AlarmSkill] -filename = alarm.mp3 -max_delay = 600 # in seconds -repeat_time = 20 # in seconds -extended_delay = 60 # in seconds - -[ReminderSkill] -max_delay = 600 # in seconds -repeat_time = 60 # in seconds -extended_delay = 60 # in seconds - -[VolumeSkill] -default_level = 6 # 0-11 -min_volume = 0 # 0 - 100 -max_volume = 100 - -[AudioRecordSkill] -filename = /tmp/mycroft-recording.wav -free_disk = 100 # in Mb -max_time = 600 # in seconds -notify_delay = 5 # in seconds -rate = 16000 -channels = 1 - -[WiFiClient] -setup = False diff --git a/mycroft/session/__init__.py b/mycroft/session/__init__.py index 154ade5f91..11125f840d 100644 --- a/mycroft/session/__init__.py +++ b/mycroft/session/__init__.py @@ -17,20 +17,22 @@ import time -from uuid import uuid4 from threading import Lock -from mycroft.util import log +from uuid import uuid4 + from mycroft.configuration import ConfigurationManager +from mycroft.util import log __author__ = 'seanfitz' logger = log.getLogger(__name__) -config = ConfigurationManager.get().get('session_management', {}) +config = ConfigurationManager.get().get('session') class Session(object): """ An object representing a Mycroft Session Identifier """ + def __init__(self, session_id, expiration_seconds=180): self.session_id = session_id self.touch_time = int(time.time()) @@ -74,8 +76,7 @@ class SessionManager(object): if (not SessionManager.__current_session or SessionManager.__current_session.expired()): SessionManager.__current_session = Session( - str(uuid4()), - expiration_seconds=config.get('session_ttl_seconds', 180)) + str(uuid4()), expiration_seconds=config.get('ttl', 180)) logger.info( "New Session Start: " + SessionManager.__current_session.session_id) diff --git a/mycroft/skills/alarm/__init__.py b/mycroft/skills/alarm/__init__.py index ab894e52f4..3fb291806e 100644 --- a/mycroft/skills/alarm/__init__.py +++ b/mycroft/skills/alarm/__init__.py @@ -17,12 +17,13 @@ import time -import yaml from alsaaudio import Mixer from datetime import datetime, timedelta + +import yaml +from adapt.intent import IntentBuilder from os.path import dirname, join -from adapt.intent import IntentBuilder from mycroft.skills.scheduled_skills import ScheduledCRUDSkill from mycroft.util import play_mp3 @@ -34,9 +35,9 @@ class AlarmSkill(ScheduledCRUDSkill): def __init__(self): super(AlarmSkill, self).__init__("AlarmSkill", None, dirname(__file__)) self.alarm_on = False - self.max_delay = int(self.config.get('max_delay')) - self.repeat_time = int(self.config.get('repeat_time')) - self.extended_delay = int(self.config.get('extended_delay')) + self.max_delay = self.config.get('max_delay') + self.repeat_time = self.config.get('repeat_time') + self.extended_delay = self.config.get('extended_delay') self.file_path = join(self.basedir, self.config.get('filename')) def initialize(self): diff --git a/mycroft/skills/audio_record/__init__.py b/mycroft/skills/audio_record/__init__.py index e5898c0957..f05e693383 100644 --- a/mycroft/skills/audio_record/__init__.py +++ b/mycroft/skills/audio_record/__init__.py @@ -18,11 +18,11 @@ import math import time -from os.path import dirname import psutil as psutil - from adapt.intent import IntentBuilder +from os.path import dirname + from mycroft.skills.scheduled_skills import ScheduledSkill from mycroft.util import record, play_wav from mycroft.util.log import getLogger @@ -35,11 +35,11 @@ LOGGER = getLogger(__name__) class AudioRecordSkill(ScheduledSkill): def __init__(self): super(AudioRecordSkill, self).__init__("AudioRecordSkill") - self.free_disk = int(self.config.get('free_disk')) - self.max_time = int(self.config.get('max_time')) - self.notify_delay = int(self.config.get('notify_delay')) - self.rate = int(self.config.get('rate')) - self.channels = int(self.config.get('channels')) + self.free_disk = self.config.get('free_disk') + self.max_time = self.config.get('max_time') + self.notify_delay = self.config.get('notify_delay') + self.rate = self.config.get('rate') + self.channels = self.config.get('channels') self.file_path = self.config.get('filename') self.duration = 0 self.notify_time = None @@ -66,7 +66,7 @@ class AudioRecordSkill(ScheduledSkill): intent = IntentBuilder('AudioRecordSkillStopPlayIntent').require( 'AudioRecordSkillStopVerb') \ .require('AudioRecordSkillPlayVerb').require( - 'AudioRecordSkillKeyword').build() + 'AudioRecordSkillKeyword').build() self.register_intent(intent, self.handle_stop_play) def handle_record(self, message): diff --git a/mycroft/skills/container.py b/mycroft/skills/container.py index eea3f09d1d..ab68d666ee 100644 --- a/mycroft/skills/container.py +++ b/mycroft/skills/container.py @@ -52,7 +52,7 @@ class SkillContainer(object): @staticmethod def __build_params(args): parser = argparse.ArgumentParser() - parser.add_argument("--config", default="./mycroft.ini") + parser.add_argument("--config", default="./mycroft.conf") parser.add_argument("dir", nargs='?', default=dirname(__file__)) parser.add_argument("--lib", default="./lib") parser.add_argument("--host", default=None) diff --git a/mycroft/skills/date_time/__init__.py b/mycroft/skills/date_time/__init__.py index 74d0199346..4fe70e36b3 100644 --- a/mycroft/skills/date_time/__init__.py +++ b/mycroft/skills/date_time/__init__.py @@ -17,13 +17,13 @@ import datetime -from os.path import dirname, join import tzlocal +from adapt.intent import IntentBuilder from astral import Astral +from os.path import dirname, join from pytz import timezone -from adapt.intent import IntentBuilder from mycroft.skills.core import MycroftSkill __author__ = 'ryanleesipes' @@ -32,8 +32,8 @@ __author__ = 'ryanleesipes' # TODO - Localization class TimeSkill(MycroftSkill): def __init__(self): - super(TimeSkill, self).__init__(name="TimeSkill") - self.format = self.config['time_format'] + super(TimeSkill, self).__init__("TimeSkill") + self.format = self.config['format'] def initialize(self): self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) diff --git a/mycroft/skills/main.py b/mycroft/skills/main.py index fe96a3f015..e1a84ffb79 100644 --- a/mycroft/skills/main.py +++ b/mycroft/skills/main.py @@ -17,12 +17,14 @@ import json + from os.path import expanduser, exists from mycroft.configuration import ConfigurationManager from mycroft.messagebus.client.ws import WebsocketClient from mycroft.skills.core import load_skills, THIRD_PARTY_SKILLS_DIR from mycroft.util.log import getLogger + logger = getLogger("Skills") __author__ = 'seanfitz' @@ -33,12 +35,11 @@ client = None def load_skills_callback(): global client load_skills(client) - config = ConfigurationManager.get() - config_core = config.get("core") + config = ConfigurationManager.get().get("skills") try: ini_third_party_skills_dir = expanduser( - config_core.get("third_party_skills_dir")) + config.get("third_party_skills_dir")) except AttributeError as e: logger.warning(e.message) diff --git a/mycroft/skills/reminder/__init__.py b/mycroft/skills/reminder/__init__.py index df03c2c5ce..0c16194a1c 100644 --- a/mycroft/skills/reminder/__init__.py +++ b/mycroft/skills/reminder/__init__.py @@ -16,14 +16,15 @@ # along with Mycroft Core. If not, see . -import re import time -import yaml from alsaaudio import Mixer from datetime import datetime, timedelta + +import re +import yaml +from adapt.intent import IntentBuilder from os.path import dirname -from adapt.intent import IntentBuilder from mycroft.skills.scheduled_skills import ScheduledCRUDSkill __author__ = 'jdorleans' @@ -38,9 +39,9 @@ class ReminderSkill(ScheduledCRUDSkill): super(ReminderSkill, self).__init__( "ReminderSkill", None, dirname(__file__)) self.reminder_on = False - self.max_delay = int(self.config.get('max_delay')) - self.repeat_time = int(self.config.get('repeat_time')) - self.extended_delay = int(self.config.get('extended_delay')) + self.max_delay = self.config.get('max_delay') + self.repeat_time = self.config.get('repeat_time') + self.extended_delay = self.config.get('extended_delay') def initialize(self): super(ReminderSkill, self).initialize() diff --git a/mycroft/skills/scheduled_skills.py b/mycroft/skills/scheduled_skills.py index 8d7867bdec..e760b969cc 100644 --- a/mycroft/skills/scheduled_skills.py +++ b/mycroft/skills/scheduled_skills.py @@ -91,8 +91,8 @@ class ScheduledSkill(MycroftSkill): else: return "%s minutes and %s seconds from now" % \ (int(minutes), int(seconds)) - dt_format = self.config_core.get('date.format') - dt_format += " at " + self.config_core.get('time.format') + dt_format = self.config_core.get('date_format') + dt_format += " at " + self.config_core.get('time_format') return date.strftime(dt_format) @abc.abstractmethod diff --git a/mycroft/skills/volume/__init__.py b/mycroft/skills/volume/__init__.py index 20c13544d4..835bd2fc03 100644 --- a/mycroft/skills/volume/__init__.py +++ b/mycroft/skills/volume/__init__.py @@ -18,9 +18,10 @@ import time from alsaaudio import Mixer -from os.path import dirname, join from adapt.intent import IntentBuilder +from os.path import dirname, join + from mycroft.skills.core import MycroftSkill from mycroft.util import play_wav from mycroft.util.log import getLogger @@ -45,8 +46,8 @@ class VolumeSkill(MycroftSkill): } def __init__(self): - super(VolumeSkill, self).__init__(name="VolumeSkill") - self.default_level = int(self.config.get('default_level')) + super(VolumeSkill, self).__init__("VolumeSkill") + self.default_level = self.config.get('default_level') self.min_volume = self.config.get('min_volume') self.max_volume = self.config.get('max_volume') self.volume_sound = join(dirname(__file__), "blop-mark-diangelo.wav") @@ -115,7 +116,7 @@ class VolumeSkill(MycroftSkill): :rtype int """ range = self.MAX_LEVEL - self.MIN_LEVEL - prop = float(int(volume) - int(self.min_volume)) / int(self.max_volume) + prop = float(volume - self.min_volume) / self.max_volume level = int(round(self.MIN_LEVEL + range * prop)) if level > self.MAX_LEVEL: level = self.MAX_LEVEL @@ -128,9 +129,9 @@ class VolumeSkill(MycroftSkill): :param level: 0..MAX_LEVEL :rtype int """ - range = int(self.max_volume) - int(self.min_volume) + range = self.max_volume - self.min_volume prop = float(level) / self.MAX_LEVEL - volume = int(round(int(self.min_volume) + int(range) * prop)) + volume = int(round(self.min_volume + int(range) * prop)) return volume diff --git a/mycroft/skills/weather/owm_repackaged/owmhttpclient.py b/mycroft/skills/weather/owm_repackaged/owmhttpclient.py index 5b90fd6a62..4098c36fed 100644 --- a/mycroft/skills/weather/owm_repackaged/owmhttpclient.py +++ b/mycroft/skills/weather/owm_repackaged/owmhttpclient.py @@ -29,11 +29,11 @@ except ImportError: from urllib import urlencode import socket + from pyowm.exceptions import api_call_error class OWMHTTPClient(object): - """ An HTTP client class, that can leverage a cache mechanism. @@ -75,8 +75,7 @@ class OWMHTTPClient(object): else: try: if self._identity and self._identity.token: - bearer_token_header = "Bearer %s:%s" % ( - self._identity.device_id, self._identity.token) + bearer_token_header = "Bearer " + self._identity.token else: bearer_token_header = None try: @@ -139,4 +138,4 @@ class OWMHTTPClient(object): def __repr__(self): return "<%s.%s - cache=%s>" % \ - (__name__, self.__class__.__name__, repr(self._cache)) + (__name__, self.__class__.__name__, repr(self._cache)) diff --git a/mycroft/skills/wiki/__init__.py b/mycroft/skills/wiki/__init__.py index 8f90a5286a..fcbc0bd95d 100644 --- a/mycroft/skills/wiki/__init__.py +++ b/mycroft/skills/wiki/__init__.py @@ -35,8 +35,8 @@ LOGGER = getLogger(__name__) class WikipediaSkill(MycroftSkill): def __init__(self): super(WikipediaSkill, self).__init__(name="WikipediaSkill") - self.max_results = int(self.config['max_results']) - self.max_phrases = int(self.config['max_phrases']) + self.max_results = self.config['max_results'] + self.max_phrases = self.config['max_phrases'] self.question = 'Would you like to know more about ' # TODO - i10n self.feedback_prefix = read_stripped_lines( join(dirname(__file__), 'dialog', self.lang, diff --git a/mycroft/tts/__init__.py b/mycroft/tts/__init__.py index e9dc158bf6..60f13ed82e 100644 --- a/mycroft/tts/__init__.py +++ b/mycroft/tts/__init__.py @@ -14,12 +14,14 @@ # # You should have received a copy of the GNU General Public License # along with Mycroft Core. If not, see . - +import random from abc import ABCMeta, abstractmethod from os.path import dirname, exists, isdir +from mycroft.client.enclosure.api import EnclosureAPI from mycroft.configuration import ConfigurationManager +from mycroft.messagebus.client.ws import WebsocketClient from mycroft.util.log import getLogger __author__ = 'jdorleans' @@ -42,11 +44,18 @@ class TTS(object): self.voice = voice self.filename = '/tmp/tts.wav' self.validator = validator + self.client = WebsocketClient() + self.enclosure = EnclosureAPI(self.client) + random.seed() @abstractmethod def execute(self, sentence): pass + def blink(self, rate=1.0): + if random.random() < rate: + self.enclosure.eyes_blink("b") + class TTSValidator(object): """ diff --git a/mycroft/tts/espeak_tts.py b/mycroft/tts/espeak_tts.py index ee16d1354f..1e570ae652 100644 --- a/mycroft/tts/espeak_tts.py +++ b/mycroft/tts/espeak_tts.py @@ -27,7 +27,7 @@ class ESpeak(TTS): def __init__(self, lang, voice): super(ESpeak, self).__init__(lang, voice, ESpeakValidator(self)) - def execute(self, sentence, client): + def execute(self, sentence): subprocess.call( ['espeak', '-v', self.lang + '+' + self.voice, sentence]) diff --git a/mycroft/tts/google_tts.py b/mycroft/tts/google_tts.py index b56804a008..a659b8a483 100644 --- a/mycroft/tts/google_tts.py +++ b/mycroft/tts/google_tts.py @@ -28,8 +28,8 @@ class GoogleTTS(TTS): def __init__(self, lang, voice): super(GoogleTTS, self).__init__(lang, voice, GoogleTTSValidator(self)) - def execute(self, sentence, client): - tts = gTTS(text=sentence, lang=self.lang) + def execute(self, sentence): + tts = gTTS(sentence, self.lang) tts.save(self.filename) play_wav(self.filename) diff --git a/mycroft/tts/mimic_tts.py b/mycroft/tts/mimic_tts.py index f9a3327409..0e53c52139 100644 --- a/mycroft/tts/mimic_tts.py +++ b/mycroft/tts/mimic_tts.py @@ -16,135 +16,49 @@ # along with Mycroft Core. If not, see . import subprocess -import random -import os from os.path import join from mycroft import MYCROFT_ROOT_PATH -from mycroft.tts import TTS, TTSValidator from mycroft.configuration import ConfigurationManager -from mycroft.client.enclosure.api import EnclosureAPI +from mycroft.tts import TTS, TTSValidator +from mycroft.util import play_wav -__author__ = 'jdorleans' +__author__ = 'jdorleans', 'spenrod' -config = ConfigurationManager.get().get("tts", {}) +config = ConfigurationManager.get().get("tts").get("mimic") BIN = config.get("path", join(MYCROFT_ROOT_PATH, 'mimic', 'bin', 'mimic')) -# Mapping based on Jeffers phoneme to viseme map, seen in table 1 from: -# http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.221.6377&rep=rep1&type=pdf -# -# Mycroft unit visemes based on images found at: -# http://www.web3.lu/wp-content/uploads/2014/09/visemes.jpg -# and mapping was created partially based on the "12 mouth shapes" -# visuals seen at: -# https://wolfpaulus.com/journal/software/lipsynchronization/ -# with final viseme group to image mapping by Steve Penrod - class Mimic(TTS): - def __init__(self, lang, voice): super(Mimic, self).__init__(lang, voice, MimicValidator(self)) - self.args = ['-voice', self.voice] + self.init_args() + + def init_args(self): + self.args = [BIN, '-voice', self.voice, '-psdur', '-o', self.filename] stretch = config.get('duration_stretch', None) if stretch: self.args += ['--setf', 'duration_stretch=' + stretch] - def PhonemeToViseme(self, pho): - return { - # /A group - 'v': '5', - 'f': '5', - # /B group - 'uh': '2', - 'w': '2', - 'uw': '2', - 'er': '2', - 'r': '2', - 'ow': '2', - # /C group - 'b': '4', - 'p': '4', - 'm': '4', - # /D group - 'aw': '1', - # /E group - 'th': '3', - 'dh': '3', - # /F group - 'zh': '3', - 'ch': '3', - 'sh': '3', - 'jh': '3', - # /G group - 'oy': '6', - 'ao': '6', - # /Hgroup - 'z': '3', - 's': '3', - # /I group - 'ae': '0', - 'eh': '0', - 'ey': '0', - 'ah': '0', - 'ih': '0', - 'y': '0', - 'iy': '0', - 'aa': '0', - 'ay': '0', - 'ax': '0', - 'hh': '0', - # /J group - 'n': '3', - 't': '3', - 'd': '3', - 'l': '3', - # /K group - 'g': '3', - 'ng': '3', - 'k': '3', - # blank mouth - 'pau': '4', - }.get(pho, '4') # 4 is default if pho not found + def execute(self, sentence): + output = subprocess.check_output(self.args + ['-t', sentence]) + self.blink(0.5) + self.visime(output) + play_wav(self.filename) + self.blink(0.2) - def execute(self, sentence, client): - enclosure = EnclosureAPI(client) - - random.seed() - # blink 50% of the time before speaking (only shows up if the - # mimic TTS generation takes fairly long) - if (random.random() < 0.5): - enclosure.eyes_blink("b") - - # invoke mimic, creating WAV and outputting phoneme:duration pairs - outMimic = subprocess.check_output([BIN] + self.args + - ["-t", sentence, "-psdur", - "-o", "/tmp/mimic.wav"]) - - # split into parts - lisPairs = outMimic.split(" ") - - # covert phonemes to visemes - visCodes = '' - for pair in lisPairs: - pho_dur = pair.split(":") - if len(pho_dur) != 2: - continue - visCodes += self.PhonemeToViseme(pho_dur[0]) + ":" - visCodes += pho_dur[1] + "," - - # play WAV and walk thru visemes while it plays - enclosure.mouth_viseme(visCodes) - subprocess.call(['aplay', '/tmp/mimic.wav']) - - # after speaking, blink 20% of the time - if (random.random() < 0.2): - enclosure.eyes_blink("b") - - # delete WAV - os.remove("/tmp/mimic.wav") + def visime(self, output): + codes = [] + durations = [] + pairs = output.split(" ") + for pair in pairs: + pho_dur = pair.split(":") # phoneme:duration + if len(pho_dur) == 2: + codes.append(VISIMES.get(pho_dur[0], '4')) + durations.append(pho_dur[1]) + self.enclosure.mouth_viseme(codes, durations) class MimicValidator(TTSValidator): @@ -164,3 +78,69 @@ class MimicValidator(TTSValidator): def get_tts_class(self): return Mimic + + +# Mapping based on Jeffers phoneme to viseme map, seen in table 1 from: +# http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.221.6377&rep=rep1&type=pdf +# +# Mycroft unit visemes based on images found at: +# http://www.web3.lu/wp-content/uploads/2014/09/visemes.jpg +# +# Mapping was created partially based on the "12 mouth shapes visuals seen at: +# https://wolfpaulus.com/journal/software/lipsynchronization/ + +VISIMES = { + # /A group + 'v': '5', + 'f': '5', + # /B group + 'uh': '2', + 'w': '2', + 'uw': '2', + 'er': '2', + 'r': '2', + 'ow': '2', + # /C group + 'b': '4', + 'p': '4', + 'm': '4', + # /D group + 'aw': '1', + # /E group + 'th': '3', + 'dh': '3', + # /F group + 'zh': '3', + 'ch': '3', + 'sh': '3', + 'jh': '3', + # /G group + 'oy': '6', + 'ao': '6', + # /Hgroup + 'z': '3', + 's': '3', + # /I group + 'ae': '0', + 'eh': '0', + 'ey': '0', + 'ah': '0', + 'ih': '0', + 'y': '0', + 'iy': '0', + 'aa': '0', + 'ay': '0', + 'ax': '0', + 'hh': '0', + # /J group + 'n': '3', + 't': '3', + 'd': '3', + 'l': '3', + # /K group + 'g': '3', + 'ng': '3', + 'k': '3', + # blank mouth + 'pau': '4', +} diff --git a/mycroft/tts/remote_tts.py b/mycroft/tts/remote_tts.py index 192ecf3c8d..36cdab44e1 100644 --- a/mycroft/tts/remote_tts.py +++ b/mycroft/tts/remote_tts.py @@ -43,7 +43,7 @@ class RemoteTTS(TTS): self.url = remove_last_slash(url) self.session = FuturesSession() - def execute(self, sentence, client): + def execute(self, sentence): phrases = self.__get_phrases(sentence) if len(phrases) > 0: diff --git a/mycroft/tts/spdsay_tts.py b/mycroft/tts/spdsay_tts.py index eaf1f3d50d..b9e398d406 100644 --- a/mycroft/tts/spdsay_tts.py +++ b/mycroft/tts/spdsay_tts.py @@ -27,7 +27,7 @@ class SpdSay(TTS): def __init__(self, lang, voice): super(SpdSay, self).__init__(lang, voice, SpdSayValidator(self)) - def execute(self, sentence, client): + def execute(self, sentence): subprocess.call( ['spd-say', '-l', self.lang, '-t', self.voice, sentence]) diff --git a/mycroft/util/__init__.py b/mycroft/util/__init__.py index df29b55b1c..ead770aa40 100644 --- a/mycroft/util/__init__.py +++ b/mycroft/util/__init__.py @@ -16,22 +16,18 @@ # along with Mycroft Core. If not, see . +import socket +import subprocess + import os import os.path -import subprocess -from os.path import dirname -import socket - import psutil +from os.path import dirname import tempfile __author__ = 'jdorleans' -def str2bool(v): - return v.lower() in ("yes", "true", "t", "1") - - def play_wav(file_path): return subprocess.Popen(["aplay", file_path]) diff --git a/skills-sdk-MANIFEST.in b/skills-sdk-MANIFEST.in index a58a1c6c2c..f50f33e499 100644 --- a/skills-sdk-MANIFEST.in +++ b/skills-sdk-MANIFEST.in @@ -1,5 +1,5 @@ include requirements.txt -include mycroft/configuration/defaults/*.ini +include mycroft/configuration/*.conf include mycroft/util/setup_base.py include mycroft/__version__.py include skills-sdk-MANIFEST.in \ No newline at end of file diff --git a/test/configuration/__init__.py b/test/configuration/__init__.py index 0f946a01d5..39f96d8a03 100644 --- a/test/configuration/__init__.py +++ b/test/configuration/__init__.py @@ -10,27 +10,30 @@ __author__ = 'jdorleans' class AbstractConfigurationTest(unittest.TestCase): def setUp(self): - self.config_path = join(dirname(__file__), 'mycroft.ini') + self.config_path = join(dirname(__file__), 'mycroft.conf') @staticmethod - def create_config(lang='en-us', module='mimic'): + def create_config(lang='en-us', module='mimic', voice="ap"): config = { - 'core': {'lang': lang}, - 'tts': {'module': module} + 'lang': lang, + 'tts': { + 'module': module, + module: {'voice': voice} + } } return config - def assert_config(self, config, lang='en-us', module='mimic'): + def assert_config(self, config, lang='en-us', module='mimic', voice="ap"): self.assertIsNotNone(config) - core = config.get('core', None) - self.assertIsNotNone(core) - lan = core.get('lang', None) + lan = config.get('lang') self.assertIsNotNone(lan) self.assertEquals(lan, lang) - tts = config.get('tts', None) + tts = config.get('tts') self.assertIsNotNone(tts) - mod = tts.get('module', None) + mod = tts.get('module') self.assertEquals(mod, module) + voi = tts.get(mod, {}).get("voice") + self.assertEquals(voi, voice) class ConfigurationLoaderTest(AbstractConfigurationTest): @@ -63,14 +66,14 @@ class ConfigurationLoaderTest(AbstractConfigurationTest): self.assert_config(ConfigurationLoader.load()) def test_load_with_override_custom(self): - config = self.create_config('pt-br', 'espeak') + config = self.create_config('pt-br', 'espeak', 'f1') config = ConfigurationLoader.load(config) self.assert_config(config) def test_load_with_override_default(self): config = self.create_config() config = ConfigurationLoader.load(config, [self.config_path]) - self.assert_config(config, 'pt-br', 'espeak') + self.assert_config(config, 'pt-br', 'espeak', 'f1') def test_load_with_extra_custom(self): my_config = {'key': 'value'} @@ -89,7 +92,7 @@ class ConfigurationLoaderTest(AbstractConfigurationTest): None, self.config_path) def test_load_with_invalid_locations_path(self): - locations = ['./invalid/mycroft.ini', './invalid_mycroft.ini'] + locations = ['./invalid/mycroft.conf', './invalid_mycroft.conf'] config = ConfigurationLoader.load(None, locations, False) self.assertEquals(config, {}) @@ -121,7 +124,7 @@ class ConfigurationManagerTest(AbstractConfigurationTest): def test_load_local_with_locations(self): ConfigurationManager.load_defaults() config = ConfigurationManager.load_local([self.config_path]) - self.assert_config(config, 'pt-br', 'espeak') + self.assert_config(config, 'pt-br', 'espeak', 'f1') def test_load_remote(self): ConfigurationManager.load_defaults() @@ -134,4 +137,4 @@ class ConfigurationManagerTest(AbstractConfigurationTest): def test_load_get_with_locations(self): ConfigurationManager.load_defaults() config = ConfigurationManager.get([self.config_path]) - self.assert_config(config, 'pt-br', 'espeak') + self.assert_config(config, 'pt-br', 'espeak', 'f1') diff --git a/test/configuration/mycroft.conf b/test/configuration/mycroft.conf new file mode 100644 index 0000000000..cbb649ab69 --- /dev/null +++ b/test/configuration/mycroft.conf @@ -0,0 +1,9 @@ +{ + "lang": "pt-br", + "tts": { + "module": "espeak", + "espeak": { + "voice": "f1" + } + } +} diff --git a/test/configuration/mycroft.ini b/test/configuration/mycroft.ini deleted file mode 100644 index 8ffb4125cc..0000000000 --- a/test/configuration/mycroft.ini +++ /dev/null @@ -1,5 +0,0 @@ -[core] -lang = "pt-br" - -[tts] -module = "espeak" diff --git a/test/mycroft.ini b/test/mycroft.ini deleted file mode 100644 index 71c1045d5e..0000000000 --- a/test/mycroft.ini +++ /dev/null @@ -1,2 +0,0 @@ -[metrics_client] -enabled = False