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

View File

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

View File

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

View File

@ -16,13 +16,13 @@
# 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 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 = ""

View File

@ -16,12 +16,12 @@
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
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')

View File

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

View File

@ -16,19 +16,17 @@
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
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):

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,14 +16,15 @@
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
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()

View File

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

View File

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

View File

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

View File

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

View File

@ -14,12 +14,14 @@
#
# You should have received a copy of the GNU General Public License
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
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):
"""

View File

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

View File

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

View File

@ -16,135 +16,49 @@
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
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',
}

View File

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

View File

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

View File

@ -16,22 +16,18 @@
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
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])

View File

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

View File

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

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