Issues 356 - Rebasing with master
parent
14c6eae264
commit
4c1ba4e337
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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 = ""
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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')
|
||||
|
|
|
@ -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