Issues 356 - Rebasing with master
parent
14c6eae264
commit
4c1ba4e337
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
|
@ -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 = ""
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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])
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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])
|
||||||
|
|
||||||
|
|
|
@ -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])
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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')
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"lang": "pt-br",
|
||||||
|
"tts": {
|
||||||
|
"module": "espeak",
|
||||||
|
"espeak": {
|
||||||
|
"voice": "f1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
[core]
|
|
||||||
lang = "pt-br"
|
|
||||||
|
|
||||||
[tts]
|
|
||||||
module = "espeak"
|
|
|
@ -1,2 +0,0 @@
|
||||||
[metrics_client]
|
|
||||||
enabled = False
|
|
Loading…
Reference in New Issue