Issues 356 - Rebasing with master

pull/420/head
Jonathan D'Orleans 2016-09-16 22:08:53 -04:00
parent 14c6eae264
commit 4c1ba4e337
31 changed files with 226 additions and 358 deletions

View File

@ -72,9 +72,9 @@ These are the keys currently in use in Mycroft Core.
## Configuration ## Configuration
Mycroft configuration consists of 3 possible config files. Mycroft configuration consists of 3 possible config files.
- `mycroft-core/mycroft/configuration/mycroft.ini` - `mycroft-core/mycroft/configuration/mycroft.conf`
- `/etc/mycroft/mycroft.ini` - `/etc/mycroft/mycroft.conf`
- `$HOME/.mycroft/mycroft.ini` - `$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. 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.

View File

@ -1,6 +1,6 @@
recursive-include mycroft/client/speech/model * recursive-include mycroft/client/speech/model *
include requirements.txt include requirements.txt
include mycroft/configuration/*.ini include mycroft/configuration/*.conf
recursive-include mycroft/skills/*/dialog * recursive-include mycroft/skills/*/dialog *
recursive-include mycroft/skills/*/vocab * recursive-include mycroft/skills/*/vocab *
recursive-include mycroft/skills/*/regex * recursive-include mycroft/skills/*/regex *

View File

@ -44,8 +44,7 @@ class EnclosureAPI:
self.client.emit(Message("enclosure.system.unmute")) self.client.emit(Message("enclosure.system.unmute"))
def system_blink(self, times): def system_blink(self, times):
self.client.emit( self.client.emit(Message("enclosure.system.blink", {'times': times}))
Message("enclosure.system.blink", {'times': times}))
def eyes_on(self): def eyes_on(self):
self.client.emit(Message("enclosure.eyes.on")) self.client.emit(Message("enclosure.eyes.on"))
@ -54,34 +53,30 @@ class EnclosureAPI:
self.client.emit(Message("enclosure.eyes.off")) self.client.emit(Message("enclosure.eyes.off"))
def eyes_blink(self, side): def eyes_blink(self, side):
self.client.emit( self.client.emit(Message("enclosure.eyes.blink", {'side': side}))
Message("enclosure.eyes.blink", {'side': side}))
def eyes_narrow(self): def eyes_narrow(self):
self.client.emit(Message("enclosure.eyes.narrow")) self.client.emit(Message("enclosure.eyes.narrow"))
def eyes_look(self, side): def eyes_look(self, side):
self.client.emit( self.client.emit(Message("enclosure.eyes.look", {'side': side}))
Message("enclosure.eyes.look", {'side': side}))
def eyes_color(self, r=255, g=255, b=255): def eyes_color(self, r=255, g=255, b=255):
self.client.emit( self.client.emit(Message("enclosure.eyes.color",
Message("enclosure.eyes.color", {'r': r, 'g': g, 'b': b})) {'r': r, 'g': g, 'b': b}))
def eyes_brightness(self, level=30): def eyes_brightness(self, level=30):
self.client.emit( self.client.emit(Message("enclosure.eyes.level", {'level': level}))
Message("enclosure.eyes.level", {'level': level}))
def eyes_reset(self): def eyes_reset(self):
self.client.emit(Message("enclosure.eyes.reset")) self.client.emit(Message("enclosure.eyes.reset"))
def eyes_timed_spin(self, length): def eyes_timed_spin(self, length):
self.client.emit( self.client.emit(Message("enclosure.eyes.timedspin",
Message("enclosure.eyes.timedspin", {'length': length})) {'length': length}))
def eyes_volume(self, volume): def eyes_volume(self, volume):
self.client.emit( self.client.emit(Message("enclosure.eyes.volume", {'volume': volume}))
Message("enclosure.eyes.volume", {'volume': volume}))
def mouth_reset(self): def mouth_reset(self):
self.client.emit(Message("enclosure.mouth.reset")) self.client.emit(Message("enclosure.mouth.reset"))
@ -98,18 +93,16 @@ class EnclosureAPI:
def mouth_smile(self): def mouth_smile(self):
self.client.emit(Message("enclosure.mouth.smile")) self.client.emit(Message("enclosure.mouth.smile"))
def mouth_viseme(self, visCode): def mouth_viseme(self, codes, durations):
self.client.emit( self.client.emit(Message("enclosure.mouth.viseme",
Message("enclosure.mouth.viseme", {'code': visCode})) {'codes': codes, 'durations': durations}))
def mouth_text(self, text=""): def mouth_text(self, text=""):
self.client.emit( self.client.emit(Message("enclosure.mouth.text", {'text': text}))
Message("enclosure.mouth.text", {'text': text}))
def weather_display(self, img_code, temp): def weather_display(self, img_code, temp):
self.client.emit( self.client.emit(Message("enclosure.weather.display",
Message("enclosure.weather.display", {'img_code': img_code, 'temp': temp}))
{'img_code': img_code, 'temp': temp}))
def activate_mouth_events(self): def activate_mouth_events(self):
self.client.emit(Message('enclosure.mouth.events.activate')) self.client.emit(Message('enclosure.mouth.events.activate'))

View File

@ -16,13 +16,13 @@
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>. # along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
from time import time, sleep
from mycroft.util.log import getLogger from mycroft.util.log import getLogger
from mycroft.util import check_for_signal
import time
__author__ = 'jdorleans' __author__ = 'jdorleans'
LOGGER = getLogger(__name__) LOG = getLogger(__name__)
class EnclosureMouth: class EnclosureMouth:
@ -62,22 +62,17 @@ class EnclosureMouth:
self.writer.write("mouth.smile") self.writer.write("mouth.smile")
def viseme(self, event=None): def viseme(self, event=None):
visCmds = ''
if event and event.metadata: if event and event.metadata:
visCmds = event.metadata.get("code", visCmds) start = time()
# visCmds will be string of viseme codes and cumulative durations codes = event.metadata.get("codes")
# ex: '0:0.34,1:1.23,0:1.32,' durations = event.metadata.get("durations")
lisPairs = visCmds.split(",") for idx, code in enumerate(codes):
timeStart = time.time() if "0" <= code <= "6":
for pair in lisPairs: self.writer.write("mouth.viseme=" + code)
if check_for_signal('buttonPress'): duration = float(durations[idx])
return # abort! (aplay should have already been killed) delta = time() - start
vis_dur = pair.split(":") if delta < duration:
if vis_dur[0] >= "0" and vis_dur[0] <= "6": sleep(duration - delta)
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)
def text(self, event=None): def text(self, event=None):
text = "" text = ""

View File

@ -16,12 +16,12 @@
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>. # along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
import tempfile
import time import time
import os import os
from os.path import join, dirname, abspath from os.path import join, dirname, abspath
from pocketsphinx import Decoder from pocketsphinx import Decoder
import tempfile
__author__ = 'seanfitz, jdorleans' __author__ = 'seanfitz, jdorleans'
@ -53,7 +53,7 @@ class LocalRecognizer(object):
config.set_string('-hmm', join(BASEDIR, 'model', self.lang, 'hmm')) config.set_string('-hmm', join(BASEDIR, 'model', self.lang, 'hmm'))
config.set_string('-dict', dict_name) config.set_string('-dict', dict_name)
config.set_string('-keyphrase', self.key_phrase) 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_float('-samprate', self.sample_rate)
config.set_int('-nfft', 2048) config.set_int('-nfft', 2048)
config.set_string('-logfn', '/dev/null') config.set_string('-logfn', '/dev/null')

View File

@ -65,7 +65,7 @@ def mute_and_speak(utterance):
try: try:
logger.info("Speak: " + utterance) logger.info("Speak: " + utterance)
loop.mute() loop.mute()
tts.execute(utterance, client) tts.execute(utterance)
finally: finally:
loop.unmute() loop.unmute()
mutex.release() mutex.release()

View File

@ -16,19 +16,17 @@
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>. # along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
import collections
import audioop import audioop
import os import collections
import os.path
from time import sleep from time import sleep
import pyaudio import pyaudio
import speech_recognition
from speech_recognition import ( from speech_recognition import (
Microphone, Microphone,
AudioSource, AudioSource,
AudioData AudioData
) )
import speech_recognition
from mycroft.configuration import ConfigurationManager from mycroft.configuration import ConfigurationManager
from mycroft.util.log import getLogger from mycroft.util.log import getLogger
@ -148,10 +146,8 @@ class ResponsiveRecognizer(speech_recognition.Recognizer):
speech_recognition.Recognizer.__init__(self) speech_recognition.Recognizer.__init__(self)
self.wake_word_recognizer = wake_word_recognizer self.wake_word_recognizer = wake_word_recognizer
self.audio = pyaudio.PyAudio() self.audio = pyaudio.PyAudio()
self.threshold_multiplier = float( self.threshold_multiplier = listener_config.get('threshold_multiplier')
listener_config.get('threshold_multiplier')) self.dynamic_energy_ratio = listener_config.get('dynamic_energy_ratio')
self.dynamic_energy_ratio = float(
listener_config.get('dynamic_energy_ratio'))
@staticmethod @staticmethod
def record_sound_chunk(source): def record_sound_chunk(source):

View File

@ -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

View File

@ -17,20 +17,22 @@
import time import time
from uuid import uuid4
from threading import Lock from threading import Lock
from mycroft.util import log from uuid import uuid4
from mycroft.configuration import ConfigurationManager from mycroft.configuration import ConfigurationManager
from mycroft.util import log
__author__ = 'seanfitz' __author__ = 'seanfitz'
logger = log.getLogger(__name__) logger = log.getLogger(__name__)
config = ConfigurationManager.get().get('session_management', {}) config = ConfigurationManager.get().get('session')
class Session(object): class Session(object):
""" """
An object representing a Mycroft Session Identifier An object representing a Mycroft Session Identifier
""" """
def __init__(self, session_id, expiration_seconds=180): def __init__(self, session_id, expiration_seconds=180):
self.session_id = session_id self.session_id = session_id
self.touch_time = int(time.time()) self.touch_time = int(time.time())
@ -74,8 +76,7 @@ class SessionManager(object):
if (not SessionManager.__current_session or if (not SessionManager.__current_session or
SessionManager.__current_session.expired()): SessionManager.__current_session.expired()):
SessionManager.__current_session = Session( SessionManager.__current_session = Session(
str(uuid4()), str(uuid4()), expiration_seconds=config.get('ttl', 180))
expiration_seconds=config.get('session_ttl_seconds', 180))
logger.info( logger.info(
"New Session Start: " + "New Session Start: " +
SessionManager.__current_session.session_id) SessionManager.__current_session.session_id)

View File

@ -17,12 +17,13 @@
import time import time
import yaml
from alsaaudio import Mixer from alsaaudio import Mixer
from datetime import datetime, timedelta from datetime import datetime, timedelta
import yaml
from adapt.intent import IntentBuilder
from os.path import dirname, join from os.path import dirname, join
from adapt.intent import IntentBuilder
from mycroft.skills.scheduled_skills import ScheduledCRUDSkill from mycroft.skills.scheduled_skills import ScheduledCRUDSkill
from mycroft.util import play_mp3 from mycroft.util import play_mp3
@ -34,9 +35,9 @@ class AlarmSkill(ScheduledCRUDSkill):
def __init__(self): def __init__(self):
super(AlarmSkill, self).__init__("AlarmSkill", None, dirname(__file__)) super(AlarmSkill, self).__init__("AlarmSkill", None, dirname(__file__))
self.alarm_on = False self.alarm_on = False
self.max_delay = int(self.config.get('max_delay')) self.max_delay = self.config.get('max_delay')
self.repeat_time = int(self.config.get('repeat_time')) self.repeat_time = self.config.get('repeat_time')
self.extended_delay = int(self.config.get('extended_delay')) self.extended_delay = self.config.get('extended_delay')
self.file_path = join(self.basedir, self.config.get('filename')) self.file_path = join(self.basedir, self.config.get('filename'))
def initialize(self): def initialize(self):

View File

@ -18,11 +18,11 @@
import math import math
import time import time
from os.path import dirname
import psutil as psutil import psutil as psutil
from adapt.intent import IntentBuilder from adapt.intent import IntentBuilder
from os.path import dirname
from mycroft.skills.scheduled_skills import ScheduledSkill from mycroft.skills.scheduled_skills import ScheduledSkill
from mycroft.util import record, play_wav from mycroft.util import record, play_wav
from mycroft.util.log import getLogger from mycroft.util.log import getLogger
@ -35,11 +35,11 @@ LOGGER = getLogger(__name__)
class AudioRecordSkill(ScheduledSkill): class AudioRecordSkill(ScheduledSkill):
def __init__(self): def __init__(self):
super(AudioRecordSkill, self).__init__("AudioRecordSkill") super(AudioRecordSkill, self).__init__("AudioRecordSkill")
self.free_disk = int(self.config.get('free_disk')) self.free_disk = self.config.get('free_disk')
self.max_time = int(self.config.get('max_time')) self.max_time = self.config.get('max_time')
self.notify_delay = int(self.config.get('notify_delay')) self.notify_delay = self.config.get('notify_delay')
self.rate = int(self.config.get('rate')) self.rate = self.config.get('rate')
self.channels = int(self.config.get('channels')) self.channels = self.config.get('channels')
self.file_path = self.config.get('filename') self.file_path = self.config.get('filename')
self.duration = 0 self.duration = 0
self.notify_time = None self.notify_time = None
@ -66,7 +66,7 @@ class AudioRecordSkill(ScheduledSkill):
intent = IntentBuilder('AudioRecordSkillStopPlayIntent').require( intent = IntentBuilder('AudioRecordSkillStopPlayIntent').require(
'AudioRecordSkillStopVerb') \ 'AudioRecordSkillStopVerb') \
.require('AudioRecordSkillPlayVerb').require( .require('AudioRecordSkillPlayVerb').require(
'AudioRecordSkillKeyword').build() 'AudioRecordSkillKeyword').build()
self.register_intent(intent, self.handle_stop_play) self.register_intent(intent, self.handle_stop_play)
def handle_record(self, message): def handle_record(self, message):

View File

@ -52,7 +52,7 @@ class SkillContainer(object):
@staticmethod @staticmethod
def __build_params(args): def __build_params(args):
parser = argparse.ArgumentParser() 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("dir", nargs='?', default=dirname(__file__))
parser.add_argument("--lib", default="./lib") parser.add_argument("--lib", default="./lib")
parser.add_argument("--host", default=None) parser.add_argument("--host", default=None)

View File

@ -17,13 +17,13 @@
import datetime import datetime
from os.path import dirname, join
import tzlocal import tzlocal
from adapt.intent import IntentBuilder
from astral import Astral from astral import Astral
from os.path import dirname, join
from pytz import timezone from pytz import timezone
from adapt.intent import IntentBuilder
from mycroft.skills.core import MycroftSkill from mycroft.skills.core import MycroftSkill
__author__ = 'ryanleesipes' __author__ = 'ryanleesipes'
@ -32,8 +32,8 @@ __author__ = 'ryanleesipes'
# TODO - Localization # TODO - Localization
class TimeSkill(MycroftSkill): class TimeSkill(MycroftSkill):
def __init__(self): def __init__(self):
super(TimeSkill, self).__init__(name="TimeSkill") super(TimeSkill, self).__init__("TimeSkill")
self.format = self.config['time_format'] self.format = self.config['format']
def initialize(self): def initialize(self):
self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us'))

View File

@ -17,12 +17,14 @@
import json import json
from os.path import expanduser, exists from os.path import expanduser, exists
from mycroft.configuration import ConfigurationManager from mycroft.configuration import ConfigurationManager
from mycroft.messagebus.client.ws import WebsocketClient from mycroft.messagebus.client.ws import WebsocketClient
from mycroft.skills.core import load_skills, THIRD_PARTY_SKILLS_DIR from mycroft.skills.core import load_skills, THIRD_PARTY_SKILLS_DIR
from mycroft.util.log import getLogger from mycroft.util.log import getLogger
logger = getLogger("Skills") logger = getLogger("Skills")
__author__ = 'seanfitz' __author__ = 'seanfitz'
@ -33,12 +35,11 @@ client = None
def load_skills_callback(): def load_skills_callback():
global client global client
load_skills(client) load_skills(client)
config = ConfigurationManager.get() config = ConfigurationManager.get().get("skills")
config_core = config.get("core")
try: try:
ini_third_party_skills_dir = expanduser( ini_third_party_skills_dir = expanduser(
config_core.get("third_party_skills_dir")) config.get("third_party_skills_dir"))
except AttributeError as e: except AttributeError as e:
logger.warning(e.message) logger.warning(e.message)

View File

@ -16,14 +16,15 @@
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>. # along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
import re
import time import time
import yaml
from alsaaudio import Mixer from alsaaudio import Mixer
from datetime import datetime, timedelta from datetime import datetime, timedelta
import re
import yaml
from adapt.intent import IntentBuilder
from os.path import dirname from os.path import dirname
from adapt.intent import IntentBuilder
from mycroft.skills.scheduled_skills import ScheduledCRUDSkill from mycroft.skills.scheduled_skills import ScheduledCRUDSkill
__author__ = 'jdorleans' __author__ = 'jdorleans'
@ -38,9 +39,9 @@ class ReminderSkill(ScheduledCRUDSkill):
super(ReminderSkill, self).__init__( super(ReminderSkill, self).__init__(
"ReminderSkill", None, dirname(__file__)) "ReminderSkill", None, dirname(__file__))
self.reminder_on = False self.reminder_on = False
self.max_delay = int(self.config.get('max_delay')) self.max_delay = self.config.get('max_delay')
self.repeat_time = int(self.config.get('repeat_time')) self.repeat_time = self.config.get('repeat_time')
self.extended_delay = int(self.config.get('extended_delay')) self.extended_delay = self.config.get('extended_delay')
def initialize(self): def initialize(self):
super(ReminderSkill, self).initialize() super(ReminderSkill, self).initialize()

View File

@ -91,8 +91,8 @@ class ScheduledSkill(MycroftSkill):
else: else:
return "%s minutes and %s seconds from now" % \ return "%s minutes and %s seconds from now" % \
(int(minutes), int(seconds)) (int(minutes), int(seconds))
dt_format = self.config_core.get('date.format') dt_format = self.config_core.get('date_format')
dt_format += " at " + self.config_core.get('time.format') dt_format += " at " + self.config_core.get('time_format')
return date.strftime(dt_format) return date.strftime(dt_format)
@abc.abstractmethod @abc.abstractmethod

View File

@ -18,9 +18,10 @@
import time import time
from alsaaudio import Mixer from alsaaudio import Mixer
from os.path import dirname, join
from adapt.intent import IntentBuilder from adapt.intent import IntentBuilder
from os.path import dirname, join
from mycroft.skills.core import MycroftSkill from mycroft.skills.core import MycroftSkill
from mycroft.util import play_wav from mycroft.util import play_wav
from mycroft.util.log import getLogger from mycroft.util.log import getLogger
@ -45,8 +46,8 @@ class VolumeSkill(MycroftSkill):
} }
def __init__(self): def __init__(self):
super(VolumeSkill, self).__init__(name="VolumeSkill") super(VolumeSkill, self).__init__("VolumeSkill")
self.default_level = int(self.config.get('default_level')) self.default_level = self.config.get('default_level')
self.min_volume = self.config.get('min_volume') self.min_volume = self.config.get('min_volume')
self.max_volume = self.config.get('max_volume') self.max_volume = self.config.get('max_volume')
self.volume_sound = join(dirname(__file__), "blop-mark-diangelo.wav") self.volume_sound = join(dirname(__file__), "blop-mark-diangelo.wav")
@ -115,7 +116,7 @@ class VolumeSkill(MycroftSkill):
:rtype int :rtype int
""" """
range = self.MAX_LEVEL - self.MIN_LEVEL 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)) level = int(round(self.MIN_LEVEL + range * prop))
if level > self.MAX_LEVEL: if level > self.MAX_LEVEL:
level = self.MAX_LEVEL level = self.MAX_LEVEL
@ -128,9 +129,9 @@ class VolumeSkill(MycroftSkill):
:param level: 0..MAX_LEVEL :param level: 0..MAX_LEVEL
:rtype int :rtype int
""" """
range = int(self.max_volume) - int(self.min_volume) range = self.max_volume - self.min_volume
prop = float(level) / self.MAX_LEVEL 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 return volume

View File

@ -29,11 +29,11 @@ except ImportError:
from urllib import urlencode from urllib import urlencode
import socket import socket
from pyowm.exceptions import api_call_error from pyowm.exceptions import api_call_error
class OWMHTTPClient(object): class OWMHTTPClient(object):
""" """
An HTTP client class, that can leverage a cache mechanism. An HTTP client class, that can leverage a cache mechanism.
@ -75,8 +75,7 @@ class OWMHTTPClient(object):
else: else:
try: try:
if self._identity and self._identity.token: if self._identity and self._identity.token:
bearer_token_header = "Bearer %s:%s" % ( bearer_token_header = "Bearer " + self._identity.token
self._identity.device_id, self._identity.token)
else: else:
bearer_token_header = None bearer_token_header = None
try: try:
@ -139,4 +138,4 @@ class OWMHTTPClient(object):
def __repr__(self): def __repr__(self):
return "<%s.%s - cache=%s>" % \ return "<%s.%s - cache=%s>" % \
(__name__, self.__class__.__name__, repr(self._cache)) (__name__, self.__class__.__name__, repr(self._cache))

View File

@ -35,8 +35,8 @@ LOGGER = getLogger(__name__)
class WikipediaSkill(MycroftSkill): class WikipediaSkill(MycroftSkill):
def __init__(self): def __init__(self):
super(WikipediaSkill, self).__init__(name="WikipediaSkill") super(WikipediaSkill, self).__init__(name="WikipediaSkill")
self.max_results = int(self.config['max_results']) self.max_results = self.config['max_results']
self.max_phrases = int(self.config['max_phrases']) self.max_phrases = self.config['max_phrases']
self.question = 'Would you like to know more about ' # TODO - i10n self.question = 'Would you like to know more about ' # TODO - i10n
self.feedback_prefix = read_stripped_lines( self.feedback_prefix = read_stripped_lines(
join(dirname(__file__), 'dialog', self.lang, join(dirname(__file__), 'dialog', self.lang,

View File

@ -14,12 +14,14 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>. # along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
import random
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from os.path import dirname, exists, isdir from os.path import dirname, exists, isdir
from mycroft.client.enclosure.api import EnclosureAPI
from mycroft.configuration import ConfigurationManager from mycroft.configuration import ConfigurationManager
from mycroft.messagebus.client.ws import WebsocketClient
from mycroft.util.log import getLogger from mycroft.util.log import getLogger
__author__ = 'jdorleans' __author__ = 'jdorleans'
@ -42,11 +44,18 @@ class TTS(object):
self.voice = voice self.voice = voice
self.filename = '/tmp/tts.wav' self.filename = '/tmp/tts.wav'
self.validator = validator self.validator = validator
self.client = WebsocketClient()
self.enclosure = EnclosureAPI(self.client)
random.seed()
@abstractmethod @abstractmethod
def execute(self, sentence): def execute(self, sentence):
pass pass
def blink(self, rate=1.0):
if random.random() < rate:
self.enclosure.eyes_blink("b")
class TTSValidator(object): class TTSValidator(object):
""" """

View File

@ -27,7 +27,7 @@ class ESpeak(TTS):
def __init__(self, lang, voice): def __init__(self, lang, voice):
super(ESpeak, self).__init__(lang, voice, ESpeakValidator(self)) super(ESpeak, self).__init__(lang, voice, ESpeakValidator(self))
def execute(self, sentence, client): def execute(self, sentence):
subprocess.call( subprocess.call(
['espeak', '-v', self.lang + '+' + self.voice, sentence]) ['espeak', '-v', self.lang + '+' + self.voice, sentence])

View File

@ -28,8 +28,8 @@ class GoogleTTS(TTS):
def __init__(self, lang, voice): def __init__(self, lang, voice):
super(GoogleTTS, self).__init__(lang, voice, GoogleTTSValidator(self)) super(GoogleTTS, self).__init__(lang, voice, GoogleTTSValidator(self))
def execute(self, sentence, client): def execute(self, sentence):
tts = gTTS(text=sentence, lang=self.lang) tts = gTTS(sentence, self.lang)
tts.save(self.filename) tts.save(self.filename)
play_wav(self.filename) play_wav(self.filename)

View File

@ -16,135 +16,49 @@
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>. # along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
import subprocess import subprocess
import random
import os
from os.path import join from os.path import join
from mycroft import MYCROFT_ROOT_PATH from mycroft import MYCROFT_ROOT_PATH
from mycroft.tts import TTS, TTSValidator
from mycroft.configuration import ConfigurationManager 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')) 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): class Mimic(TTS):
def __init__(self, lang, voice): def __init__(self, lang, voice):
super(Mimic, self).__init__(lang, voice, MimicValidator(self)) 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) stretch = config.get('duration_stretch', None)
if stretch: if stretch:
self.args += ['--setf', 'duration_stretch=' + stretch] self.args += ['--setf', 'duration_stretch=' + stretch]
def PhonemeToViseme(self, pho): def execute(self, sentence):
return { output = subprocess.check_output(self.args + ['-t', sentence])
# /A group self.blink(0.5)
'v': '5', self.visime(output)
'f': '5', play_wav(self.filename)
# /B group self.blink(0.2)
'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, client): def visime(self, output):
enclosure = EnclosureAPI(client) codes = []
durations = []
random.seed() pairs = output.split(" ")
# blink 50% of the time before speaking (only shows up if the for pair in pairs:
# mimic TTS generation takes fairly long) pho_dur = pair.split(":") # phoneme:duration
if (random.random() < 0.5): if len(pho_dur) == 2:
enclosure.eyes_blink("b") codes.append(VISIMES.get(pho_dur[0], '4'))
durations.append(pho_dur[1])
# invoke mimic, creating WAV and outputting phoneme:duration pairs self.enclosure.mouth_viseme(codes, durations)
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")
class MimicValidator(TTSValidator): class MimicValidator(TTSValidator):
@ -164,3 +78,69 @@ class MimicValidator(TTSValidator):
def get_tts_class(self): def get_tts_class(self):
return Mimic 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',
}

View File

@ -43,7 +43,7 @@ class RemoteTTS(TTS):
self.url = remove_last_slash(url) self.url = remove_last_slash(url)
self.session = FuturesSession() self.session = FuturesSession()
def execute(self, sentence, client): def execute(self, sentence):
phrases = self.__get_phrases(sentence) phrases = self.__get_phrases(sentence)
if len(phrases) > 0: if len(phrases) > 0:

View File

@ -27,7 +27,7 @@ class SpdSay(TTS):
def __init__(self, lang, voice): def __init__(self, lang, voice):
super(SpdSay, self).__init__(lang, voice, SpdSayValidator(self)) super(SpdSay, self).__init__(lang, voice, SpdSayValidator(self))
def execute(self, sentence, client): def execute(self, sentence):
subprocess.call( subprocess.call(
['spd-say', '-l', self.lang, '-t', self.voice, sentence]) ['spd-say', '-l', self.lang, '-t', self.voice, sentence])

View File

@ -16,22 +16,18 @@
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>. # along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
import socket
import subprocess
import os import os
import os.path import os.path
import subprocess
from os.path import dirname
import socket
import psutil import psutil
from os.path import dirname
import tempfile import tempfile
__author__ = 'jdorleans' __author__ = 'jdorleans'
def str2bool(v):
return v.lower() in ("yes", "true", "t", "1")
def play_wav(file_path): def play_wav(file_path):
return subprocess.Popen(["aplay", file_path]) return subprocess.Popen(["aplay", file_path])

View File

@ -1,5 +1,5 @@
include requirements.txt include requirements.txt
include mycroft/configuration/defaults/*.ini include mycroft/configuration/*.conf
include mycroft/util/setup_base.py include mycroft/util/setup_base.py
include mycroft/__version__.py include mycroft/__version__.py
include skills-sdk-MANIFEST.in include skills-sdk-MANIFEST.in

View File

@ -10,27 +10,30 @@ __author__ = 'jdorleans'
class AbstractConfigurationTest(unittest.TestCase): class AbstractConfigurationTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.config_path = join(dirname(__file__), 'mycroft.ini') self.config_path = join(dirname(__file__), 'mycroft.conf')
@staticmethod @staticmethod
def create_config(lang='en-us', module='mimic'): def create_config(lang='en-us', module='mimic', voice="ap"):
config = { config = {
'core': {'lang': lang}, 'lang': lang,
'tts': {'module': module} 'tts': {
'module': module,
module: {'voice': voice}
}
} }
return config 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) self.assertIsNotNone(config)
core = config.get('core', None) lan = config.get('lang')
self.assertIsNotNone(core)
lan = core.get('lang', None)
self.assertIsNotNone(lan) self.assertIsNotNone(lan)
self.assertEquals(lan, lang) self.assertEquals(lan, lang)
tts = config.get('tts', None) tts = config.get('tts')
self.assertIsNotNone(tts) self.assertIsNotNone(tts)
mod = tts.get('module', None) mod = tts.get('module')
self.assertEquals(mod, module) self.assertEquals(mod, module)
voi = tts.get(mod, {}).get("voice")
self.assertEquals(voi, voice)
class ConfigurationLoaderTest(AbstractConfigurationTest): class ConfigurationLoaderTest(AbstractConfigurationTest):
@ -63,14 +66,14 @@ class ConfigurationLoaderTest(AbstractConfigurationTest):
self.assert_config(ConfigurationLoader.load()) self.assert_config(ConfigurationLoader.load())
def test_load_with_override_custom(self): 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) config = ConfigurationLoader.load(config)
self.assert_config(config) self.assert_config(config)
def test_load_with_override_default(self): def test_load_with_override_default(self):
config = self.create_config() config = self.create_config()
config = ConfigurationLoader.load(config, [self.config_path]) 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): def test_load_with_extra_custom(self):
my_config = {'key': 'value'} my_config = {'key': 'value'}
@ -89,7 +92,7 @@ class ConfigurationLoaderTest(AbstractConfigurationTest):
None, self.config_path) None, self.config_path)
def test_load_with_invalid_locations_path(self): 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) config = ConfigurationLoader.load(None, locations, False)
self.assertEquals(config, {}) self.assertEquals(config, {})
@ -121,7 +124,7 @@ class ConfigurationManagerTest(AbstractConfigurationTest):
def test_load_local_with_locations(self): def test_load_local_with_locations(self):
ConfigurationManager.load_defaults() ConfigurationManager.load_defaults()
config = ConfigurationManager.load_local([self.config_path]) 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): def test_load_remote(self):
ConfigurationManager.load_defaults() ConfigurationManager.load_defaults()
@ -134,4 +137,4 @@ class ConfigurationManagerTest(AbstractConfigurationTest):
def test_load_get_with_locations(self): def test_load_get_with_locations(self):
ConfigurationManager.load_defaults() ConfigurationManager.load_defaults()
config = ConfigurationManager.get([self.config_path]) config = ConfigurationManager.get([self.config_path])
self.assert_config(config, 'pt-br', 'espeak') self.assert_config(config, 'pt-br', 'espeak', 'f1')

View File

@ -0,0 +1,9 @@
{
"lang": "pt-br",
"tts": {
"module": "espeak",
"espeak": {
"voice": "f1"
}
}
}

View File

@ -1,5 +0,0 @@
[core]
lang = "pt-br"
[tts]
module = "espeak"

View File

@ -1,2 +0,0 @@
[metrics_client]
enabled = False