Merge branch 'dev' into feature/improve-concurrent-ci
commit
424382afca
|
@ -1,12 +1,24 @@
|
|||
.. Mycroft documentation master file
|
||||
Mycroft-core technical documentation
|
||||
====================================
|
||||
|
||||
Mycroft Skills API
|
||||
==================
|
||||
------------------
|
||||
|
||||
*Reference for the Mycroft Skills API*
|
||||
*Reference for use during Skill creation*
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
:caption: Contents:
|
||||
|
||||
source/mycroft
|
||||
|
||||
Mycroft plugin API
|
||||
------------------
|
||||
*Reference for use during Plugin creation*
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
:caption: Contents:
|
||||
|
||||
source/plugins
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
Mycroft plugins
|
||||
===============
|
||||
Mycroft is extendable by plugins. These plugins can add support for new Speech To Text engines, Text To Speech engines, wake word engines and add new audio playback options.
|
||||
|
||||
TTS - Base for TTS plugins
|
||||
--------------------------
|
||||
.. autoclass:: mycroft.tts.TTS
|
||||
:members:
|
||||
|
||||
STT - base for STT plugins
|
||||
--------------------------
|
||||
.. autoclass:: mycroft.stt.STT
|
||||
:members:
|
||||
|
|
||||
|
|
||||
.. autoclass:: mycroft.stt.StreamingSTT
|
||||
:members:
|
||||
|
|
||||
|
|
||||
.. autoclass:: mycroft.stt.StreamThread
|
||||
:members:
|
||||
|
||||
HotWordEngine - Base for Hotword engine plugins
|
||||
-----------------------------------------------
|
||||
.. autoclass:: mycroft.client.speech.hotword_factory.HotWordEngine
|
||||
:members:
|
||||
|
||||
AudioBackend - Base for audioservice backend plugins
|
||||
------------------
|
||||
.. autoclass:: mycroft.audio.services.AudioBackend
|
||||
:members:
|
||||
|
|
||||
|
|
||||
.. autoclass:: mycroft.audio.services.RemoteAudioBackend
|
||||
:members:
|
||||
|
|
@ -12,132 +12,129 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
"""Definition of the audio service backends base classes.
|
||||
|
||||
These classes can be used to create an Audioservice plugin extending
|
||||
Mycroft's media playback options.
|
||||
"""
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class AudioBackend(metaclass=ABCMeta):
|
||||
"""
|
||||
Base class for all audio backend implementations.
|
||||
"""Base class for all audio backend implementations.
|
||||
|
||||
Args:
|
||||
config: configuration dict for the instance
|
||||
bus: Mycroft messagebus emitter
|
||||
Arguments:
|
||||
config (dict): configuration dict for the instance
|
||||
bus (MessageBusClient): Mycroft messagebus emitter
|
||||
"""
|
||||
|
||||
def __init__(self, config, bus):
|
||||
self._track_start_callback = None
|
||||
self.supports_mime_hints = False
|
||||
self.config = config
|
||||
self.bus = bus
|
||||
|
||||
@abstractmethod
|
||||
def supported_uris(self):
|
||||
"""List of supported uri types.
|
||||
|
||||
Returns:
|
||||
list: Supported uri's
|
||||
"""
|
||||
Returns: list of supported uri types.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def clear_list(self):
|
||||
"""
|
||||
Clear playlist
|
||||
"""
|
||||
pass
|
||||
"""Clear playlist."""
|
||||
|
||||
@abstractmethod
|
||||
def add_list(self, tracks):
|
||||
"""
|
||||
Add tracks to backend's playlist.
|
||||
"""Add tracks to backend's playlist.
|
||||
|
||||
Args:
|
||||
tracks: list of tracks.
|
||||
Arguments:
|
||||
tracks (list): list of tracks.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def play(self, repeat=False):
|
||||
"""
|
||||
Start playback.
|
||||
"""Start playback.
|
||||
|
||||
Args:
|
||||
repeat: Repeat playlist, defaults to False
|
||||
Starts playing the first track in the playlist and will contiune
|
||||
until all tracks have been played.
|
||||
|
||||
Arguments:
|
||||
repeat (bool): Repeat playlist, defaults to False
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def stop(self):
|
||||
"""
|
||||
Stop playback.
|
||||
"""Stop playback.
|
||||
|
||||
Returns: (bool) True if playback was stopped, otherwise False
|
||||
Stops the current playback.
|
||||
|
||||
Returns:
|
||||
bool: True if playback was stopped, otherwise False
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_track_start_callback(self, callback_func):
|
||||
"""
|
||||
Register callback on track start, should be called as each track
|
||||
in a playlist is started.
|
||||
"""Register callback on track start.
|
||||
|
||||
This method should be called as each track in a playlist is started.
|
||||
"""
|
||||
self._track_start_callback = callback_func
|
||||
|
||||
def pause(self):
|
||||
"""Pause playback.
|
||||
|
||||
Stops playback but may be resumed at the exact position the pause
|
||||
occured.
|
||||
"""
|
||||
Pause playback.
|
||||
"""
|
||||
pass
|
||||
|
||||
def resume(self):
|
||||
"""Resume paused playback.
|
||||
|
||||
Resumes playback after being paused.
|
||||
"""
|
||||
Resume paused playback.
|
||||
"""
|
||||
pass
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
Skip to next track in playlist.
|
||||
"""
|
||||
pass
|
||||
"""Skip to next track in playlist."""
|
||||
|
||||
def previous(self):
|
||||
"""
|
||||
Skip to previous track in playlist.
|
||||
"""
|
||||
pass
|
||||
"""Skip to previous track in playlist."""
|
||||
|
||||
def lower_volume(self):
|
||||
"""Lower volume.
|
||||
|
||||
This method is used to implement audio ducking. It will be called when
|
||||
Mycroft is listening or speaking to make sure the media playing isn't
|
||||
interfering.
|
||||
"""
|
||||
Lower volume.
|
||||
"""
|
||||
pass
|
||||
|
||||
def restore_volume(self):
|
||||
"""Restore normal volume.
|
||||
|
||||
Called when to restore the playback volume to previous level after
|
||||
Mycroft has lowered it using lower_volume().
|
||||
"""
|
||||
Restore normal volume.
|
||||
"""
|
||||
pass
|
||||
|
||||
def seek_forward(self, seconds=1):
|
||||
"""
|
||||
Skip X seconds
|
||||
"""Skip X seconds.
|
||||
|
||||
Args:
|
||||
seconds (int): number of seconds to seek, if negative rewind
|
||||
Arguments:
|
||||
seconds (int): number of seconds to seek, if negative rewind
|
||||
"""
|
||||
pass
|
||||
|
||||
def seek_backward(self, seconds=1):
|
||||
"""
|
||||
Rewind X seconds
|
||||
"""Rewind X seconds.
|
||||
|
||||
Args:
|
||||
seconds (int): number of seconds to seek, if negative rewind
|
||||
Arguments:
|
||||
seconds (int): number of seconds to seek, if negative jump forward.
|
||||
"""
|
||||
pass
|
||||
|
||||
def track_info(self):
|
||||
"""
|
||||
Fetch info about current playing track.
|
||||
"""Get info about current playing track.
|
||||
|
||||
Returns:
|
||||
Dict with track info.
|
||||
Returns:
|
||||
dict: Track info containing atleast the keys artist and album.
|
||||
"""
|
||||
ret = {}
|
||||
ret['artist'] = ''
|
||||
|
@ -145,13 +142,19 @@ class AudioBackend(metaclass=ABCMeta):
|
|||
return ret
|
||||
|
||||
def shutdown(self):
|
||||
""" Perform clean shutdown """
|
||||
"""Perform clean shutdown.
|
||||
|
||||
Implements any audio backend specific shutdown procedures.
|
||||
"""
|
||||
self.stop()
|
||||
|
||||
|
||||
class RemoteAudioBackend(AudioBackend):
|
||||
""" Base class for remote audio backends.
|
||||
"""Base class for remote audio backends.
|
||||
|
||||
These may be things like Chromecasts, mopidy servers, etc.
|
||||
RemoteAudioBackends will always be checked after the normal
|
||||
AudioBackends to make playback start locally by default.
|
||||
|
||||
An example of a RemoteAudioBackend would be things like Chromecasts,
|
||||
mopidy servers, etc.
|
||||
"""
|
||||
pass
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
"""Factory functions for loading hotword engines - both internal and plugins.
|
||||
"""
|
||||
from time import time, sleep
|
||||
import os
|
||||
import platform
|
||||
|
@ -54,12 +56,19 @@ def msec_to_sec(msecs):
|
|||
msecs: milliseconds
|
||||
|
||||
Returns:
|
||||
input converted from milliseconds to seconds
|
||||
int: input converted from milliseconds to seconds
|
||||
"""
|
||||
return msecs / 1000
|
||||
|
||||
|
||||
class HotWordEngine:
|
||||
"""Hotword/Wakeword base class to be implemented by all wake word plugins.
|
||||
|
||||
Arguments:
|
||||
key_phrase (str): string representation of the wake word
|
||||
config (dict): Configuration block for the specific wake word
|
||||
lang (str): language code (BCP-47)
|
||||
"""
|
||||
def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"):
|
||||
self.key_phrase = str(key_phrase).lower()
|
||||
|
||||
|
@ -77,25 +86,43 @@ class HotWordEngine:
|
|||
self.lang = str(self.config.get("lang", lang)).lower()
|
||||
|
||||
def found_wake_word(self, frame_data):
|
||||
"""Check if wake word has been found.
|
||||
|
||||
Checks if the wake word has been found. Should reset any internal
|
||||
tracking of the wake word state.
|
||||
|
||||
Arguments:
|
||||
frame_data (binary data): Deprecated. Audio data for large chunk
|
||||
of audio to be processed. This should not
|
||||
be used to detect audio data instead
|
||||
use update() to incrementaly update audio
|
||||
Returns:
|
||||
bool: True if a wake word was detected, else False
|
||||
"""
|
||||
return False
|
||||
|
||||
def update(self, chunk):
|
||||
pass
|
||||
"""Updates the hotword engine with new audio data.
|
||||
|
||||
The engine should process the data and update internal trigger state.
|
||||
|
||||
Arguments:
|
||||
chunk (bytes): Chunk of audio data to process
|
||||
"""
|
||||
|
||||
def stop(self):
|
||||
""" Perform any actions needed to shut down the hot word engine.
|
||||
"""Perform any actions needed to shut down the wake word engine.
|
||||
|
||||
This may include things such as unload loaded data or shutdown
|
||||
external processess.
|
||||
This may include things such as unloading data or shutdown
|
||||
external processess.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PocketsphinxHotWord(HotWordEngine):
|
||||
"""Hotword engine using PocketSphinx.
|
||||
"""Wake word engine using PocketSphinx.
|
||||
|
||||
PocketSphinx is very general purpose but has a somewhat high error rate.
|
||||
The key advantage is to be able to specify the wakeword with phonemes.
|
||||
The key advantage is to be able to specify the wake word with phonemes.
|
||||
"""
|
||||
def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"):
|
||||
super().__init__(key_phrase, config, lang)
|
||||
|
@ -154,7 +181,7 @@ class PocketsphinxHotWord(HotWordEngine):
|
|||
|
||||
|
||||
class PreciseHotword(HotWordEngine):
|
||||
"""Precice is the default wakeword engine for mycroft.
|
||||
"""Precise is the default wake word engine for Mycroft.
|
||||
|
||||
Precise is developed by Mycroft AI and produces quite good wake word
|
||||
spotting when trained on a decent dataset.
|
||||
|
@ -301,7 +328,7 @@ class PreciseHotword(HotWordEngine):
|
|||
|
||||
|
||||
class SnowboyHotWord(HotWordEngine):
|
||||
"""Snowboy is a thirdparty hotword engine providing an easy training and
|
||||
"""Snowboy is a thirdparty wake word engine providing an easy training and
|
||||
testing interface.
|
||||
"""
|
||||
def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"):
|
||||
|
@ -410,6 +437,11 @@ def load_wake_word_plugin(module_name):
|
|||
|
||||
|
||||
class HotWordFactory:
|
||||
"""Factory class instantiating the configured Hotword engine.
|
||||
|
||||
The factory can select between a range of built-in Hotword engines and also
|
||||
from Hotword engine plugins.
|
||||
"""
|
||||
CLASSES = {
|
||||
"pocketsphinx": PocketsphinxHotWord,
|
||||
"precise": PreciseHotword,
|
||||
|
|
|
@ -126,7 +126,8 @@
|
|||
"url": "https://api.mycroft.ai",
|
||||
"version": "v1",
|
||||
"update": true,
|
||||
"metrics": false
|
||||
"metrics": false,
|
||||
"sync_skill_settings": true
|
||||
},
|
||||
|
||||
// The mycroft-core messagebus websocket
|
||||
|
|
|
@ -115,7 +115,11 @@ class SkillGUI:
|
|||
return self.__session_data.__contains__(key)
|
||||
|
||||
def clear(self):
|
||||
"""Reset the value dictionary, and remove namespace from GUI."""
|
||||
"""Reset the value dictionary, and remove namespace from GUI.
|
||||
|
||||
This method does not close the GUI for a Skill. For this purpose see
|
||||
the `release` method.
|
||||
"""
|
||||
self.__session_data = {}
|
||||
self.page = None
|
||||
self.skill.bus.emit(Message("gui.clear.namespace",
|
||||
|
@ -349,6 +353,15 @@ class SkillGUI:
|
|||
self.show_page("SYSTEM_UrlFrame.qml", override_idle,
|
||||
override_animations)
|
||||
|
||||
def release(self):
|
||||
"""Signal that this skill is no longer using the GUI,
|
||||
allow different platforms to properly handle this event.
|
||||
Also calls self.clear() to reset the state variables
|
||||
Platforms can close the window or go back to previous page"""
|
||||
self.clear()
|
||||
self.skill.bus.emit(Message("mycroft.gui.screen.close",
|
||||
{"skill_id": self.skill.skill_id}))
|
||||
|
||||
def shutdown(self):
|
||||
"""Shutdown gui interface.
|
||||
|
||||
|
|
|
@ -26,9 +26,11 @@ from .base import IntentMatch
|
|||
class AdaptIntent(IntentBuilder):
|
||||
"""Wrapper for IntentBuilder setting a blank name.
|
||||
|
||||
This is mainly here for backwards compatibility, adapt now support
|
||||
automatically named IntentBulders.
|
||||
Arguments:
|
||||
name (str): Optional name of intent
|
||||
"""
|
||||
def __init__(self, name=''):
|
||||
super().__init__(name)
|
||||
|
||||
|
||||
def _strip_result(context_features):
|
||||
|
|
|
@ -139,6 +139,12 @@ class SettingsMetaUploader:
|
|||
self.settings_meta = {}
|
||||
self.api = None
|
||||
self.upload_timer = None
|
||||
self.sync_enabled = self.config["server"].get("sync_skill_settings",
|
||||
False)
|
||||
if not self.sync_enabled:
|
||||
LOG.info("Skill settings sync is disabled, settingsmeta will "
|
||||
"not be uploaded")
|
||||
|
||||
self._stopped = None
|
||||
|
||||
# Property placeholders
|
||||
|
@ -218,6 +224,8 @@ class SettingsMetaUploader:
|
|||
The settingsmeta file does not change often, if at all. Only perform
|
||||
the upload if a change in the file is detected.
|
||||
"""
|
||||
if not self.sync_enabled:
|
||||
return
|
||||
synced = False
|
||||
if is_paired():
|
||||
self.api = DeviceApi()
|
||||
|
@ -315,6 +323,11 @@ class SkillSettingsDownloader:
|
|||
self.remote_settings = None
|
||||
self.api = DeviceApi()
|
||||
self.download_timer = None
|
||||
self.sync_enabled = Configuration.get()["server"]\
|
||||
.get("sync_skill_settings", False)
|
||||
if not self.sync_enabled:
|
||||
LOG.info("Skill settings sync is disabled, backend settings will "
|
||||
"not be downloaded")
|
||||
|
||||
def stop_downloading(self):
|
||||
"""Stop synchronizing backend and core."""
|
||||
|
@ -328,6 +341,8 @@ class SkillSettingsDownloader:
|
|||
|
||||
When used as a messagebus handler a message is passed but not used.
|
||||
"""
|
||||
if not self.sync_enabled:
|
||||
return
|
||||
if is_paired():
|
||||
remote_settings = self._get_remote_settings()
|
||||
if remote_settings:
|
||||
|
|
|
@ -27,7 +27,7 @@ from mycroft.util.plugins import load_plugin
|
|||
|
||||
|
||||
class STT(metaclass=ABCMeta):
|
||||
""" STT Base class, all STT backends derives from this one. """
|
||||
"""STT Base class, all STT backends derive from this one. """
|
||||
def __init__(self):
|
||||
config_core = Configuration.get()
|
||||
self.lang = str(self.init_language(config_core))
|
||||
|
@ -39,6 +39,7 @@ class STT(metaclass=ABCMeta):
|
|||
|
||||
@staticmethod
|
||||
def init_language(config_core):
|
||||
"""Helper method to get language code from Mycroft config."""
|
||||
lang = config_core.get("lang", "en-US")
|
||||
langs = lang.split("-")
|
||||
if len(langs) == 2:
|
||||
|
@ -47,7 +48,21 @@ class STT(metaclass=ABCMeta):
|
|||
|
||||
@abstractmethod
|
||||
def execute(self, audio, language=None):
|
||||
pass
|
||||
"""Implementation of STT functionallity.
|
||||
|
||||
This method needs to be implemented by the derived class to implement
|
||||
the specific STT engine connection.
|
||||
|
||||
The method gets passed audio and optionally a language code and is
|
||||
expected to return a text string.
|
||||
|
||||
Arguments:
|
||||
audio (AudioData): audio recorded by mycroft.
|
||||
language (str): optional language code
|
||||
|
||||
Returns:
|
||||
str: parsed text
|
||||
"""
|
||||
|
||||
|
||||
class TokenSTT(STT, metaclass=ABCMeta):
|
||||
|
@ -322,8 +337,14 @@ class DeepSpeechServerSTT(STT):
|
|||
|
||||
|
||||
class StreamThread(Thread, metaclass=ABCMeta):
|
||||
"""
|
||||
ABC class to be used with StreamingSTT class implementations.
|
||||
"""ABC class to be used with StreamingSTT class implementations.
|
||||
|
||||
This class reads audio chunks from a queue and sends it to a parsing
|
||||
STT engine.
|
||||
|
||||
Arguments:
|
||||
queue (Queue): Input Queue
|
||||
language (str): language code for the current language.
|
||||
"""
|
||||
|
||||
def __init__(self, queue, language):
|
||||
|
@ -333,6 +354,7 @@ class StreamThread(Thread, metaclass=ABCMeta):
|
|||
self.text = None
|
||||
|
||||
def _get_data(self):
|
||||
"""Generator reading audio data from queue."""
|
||||
while True:
|
||||
d = self.queue.get()
|
||||
if d is None:
|
||||
|
@ -341,23 +363,38 @@ class StreamThread(Thread, metaclass=ABCMeta):
|
|||
self.queue.task_done()
|
||||
|
||||
def run(self):
|
||||
"""Thread entry point."""
|
||||
return self.handle_audio_stream(self._get_data(), self.language)
|
||||
|
||||
@abstractmethod
|
||||
def handle_audio_stream(self, audio, language):
|
||||
pass
|
||||
"""Handling of audio stream.
|
||||
|
||||
Needs to be implemented by derived class to process audio data and
|
||||
optionally update `self.text` with the current hypothesis.
|
||||
|
||||
Argumens:
|
||||
audio (bytes): raw audio data.
|
||||
language (str): language code for the current session.
|
||||
"""
|
||||
|
||||
|
||||
class StreamingSTT(STT, metaclass=ABCMeta):
|
||||
"""
|
||||
ABC class for threaded streaming STT implemenations.
|
||||
"""
|
||||
"""ABC class for threaded streaming STT implemenations."""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.stream = None
|
||||
self.can_stream = True
|
||||
|
||||
def stream_start(self, language=None):
|
||||
"""Indicate start of new audio stream.
|
||||
|
||||
This creates a new thread for handling the incomming audio stream as
|
||||
it's collected by Mycroft.
|
||||
|
||||
Arguments:
|
||||
language (str): optional language code for the new stream.
|
||||
"""
|
||||
self.stream_stop()
|
||||
language = language or self.lang
|
||||
self.queue = Queue()
|
||||
|
@ -365,9 +402,21 @@ class StreamingSTT(STT, metaclass=ABCMeta):
|
|||
self.stream.start()
|
||||
|
||||
def stream_data(self, data):
|
||||
"""Receiver of audio data.
|
||||
|
||||
Arguments:
|
||||
data (bytes): raw audio data.
|
||||
"""
|
||||
self.queue.put(data)
|
||||
|
||||
def stream_stop(self):
|
||||
"""Indicate that the audio stream has ended.
|
||||
|
||||
This will tear down the processing thread and collect the result
|
||||
|
||||
Returns:
|
||||
str: parsed text
|
||||
"""
|
||||
if self.stream is not None:
|
||||
self.queue.put(None)
|
||||
self.stream.join()
|
||||
|
@ -380,11 +429,20 @@ class StreamingSTT(STT, metaclass=ABCMeta):
|
|||
return None
|
||||
|
||||
def execute(self, audio, language=None):
|
||||
"""End the parsing thread and collect data."""
|
||||
return self.stream_stop()
|
||||
|
||||
@abstractmethod
|
||||
def create_streaming_thread(self):
|
||||
pass
|
||||
"""Create thread for parsing audio chunks.
|
||||
|
||||
This method should be implemented by the derived class to return an
|
||||
instance derived from StreamThread to handle the audio stream and
|
||||
send it to the STT engine.
|
||||
|
||||
Returns:
|
||||
StreamThread: Thread to handle audio data.
|
||||
"""
|
||||
|
||||
|
||||
class DeepSpeechStreamThread(StreamThread):
|
||||
|
@ -550,7 +608,9 @@ def load_stt_plugin(module_name):
|
|||
"""Wrapper function for loading stt plugin.
|
||||
|
||||
Arguments:
|
||||
(str) Mycroft stt module name from config
|
||||
module_name (str): Mycroft stt module name from config
|
||||
Returns:
|
||||
class: STT plugin class
|
||||
"""
|
||||
return load_plugin('mycroft.plugin.stt', module_name)
|
||||
|
||||
|
|
|
@ -128,10 +128,10 @@ class PlaybackThread(Thread):
|
|||
"""Send viseme data to enclosure
|
||||
|
||||
Arguments:
|
||||
pairs(list): Visime and timing pair
|
||||
pairs (list): Visime and timing pair
|
||||
|
||||
Returns:
|
||||
True if button has been pressed.
|
||||
bool: True if button has been pressed.
|
||||
"""
|
||||
if self.enclosure:
|
||||
self.enclosure.mouth_viseme(time(), pairs)
|
||||
|
@ -187,7 +187,7 @@ class TTS(metaclass=ABCMeta):
|
|||
self.tts_name = type(self).__name__
|
||||
|
||||
def load_spellings(self):
|
||||
"""Load phonetic spellings of words as dictionary"""
|
||||
"""Load phonetic spellings of words as dictionary."""
|
||||
path = join('text', self.lang.lower(), 'phonetic_spellings.txt')
|
||||
spellings_file = resolve_resource_file(path)
|
||||
if not spellings_file:
|
||||
|
@ -202,7 +202,7 @@ class TTS(metaclass=ABCMeta):
|
|||
return {}
|
||||
|
||||
def begin_audio(self):
|
||||
"""Helper function for child classes to call in execute()"""
|
||||
"""Helper function for child classes to call in execute()."""
|
||||
# Create signals informing start of speech
|
||||
self.bus.emit(Message("recognizer_loop:audio_output_start"))
|
||||
|
||||
|
@ -254,11 +254,23 @@ class TTS(metaclass=ABCMeta):
|
|||
pass
|
||||
|
||||
def modify_tag(self, tag):
|
||||
"""Override to modify each supported ssml tag"""
|
||||
"""Override to modify each supported ssml tag.
|
||||
|
||||
Arguments:
|
||||
tag (str): SSML tag to check and possibly transform.
|
||||
"""
|
||||
return tag
|
||||
|
||||
@staticmethod
|
||||
def remove_ssml(text):
|
||||
"""Removes SSML tags from a string.
|
||||
|
||||
Arguments:
|
||||
text (str): input string
|
||||
|
||||
Returns:
|
||||
str: input string stripped from tags.
|
||||
"""
|
||||
return re.sub('<[^>]*>', '', text).replace(' ', ' ')
|
||||
|
||||
def validate_ssml(self, utterance):
|
||||
|
@ -267,10 +279,10 @@ class TTS(metaclass=ABCMeta):
|
|||
Remove unsupported / invalid tags
|
||||
|
||||
Arguments:
|
||||
utterance(str): Sentence to validate
|
||||
utterance (str): Sentence to validate
|
||||
|
||||
Returns:
|
||||
validated_sentence (str)
|
||||
str: validated_sentence
|
||||
"""
|
||||
# if ssml is not supported by TTS engine remove all tags
|
||||
if not self.ssml_tags:
|
||||
|
@ -306,14 +318,14 @@ class TTS(metaclass=ABCMeta):
|
|||
def execute(self, sentence, ident=None, listen=False):
|
||||
"""Convert sentence to speech, preprocessing out unsupported ssml
|
||||
|
||||
The method caches results if possible using the hash of the
|
||||
sentence.
|
||||
The method caches results if possible using the hash of the
|
||||
sentence.
|
||||
|
||||
Arguments:
|
||||
sentence: Sentence to be spoken
|
||||
ident: Id reference to current interaction
|
||||
listen: True if listen should be triggered at the end
|
||||
of the utterance.
|
||||
Arguments:
|
||||
sentence: (str) Sentence to be spoken
|
||||
ident: (str) Id reference to current interaction
|
||||
listen: (bool) True if listen should be triggered at the end
|
||||
of the utterance.
|
||||
"""
|
||||
sentence = self.validate_ssml(sentence)
|
||||
|
||||
|
@ -357,11 +369,16 @@ class TTS(metaclass=ABCMeta):
|
|||
self.queue.put((self.audio_ext, wav_file, vis, ident, l))
|
||||
|
||||
def viseme(self, phonemes):
|
||||
"""Create visemes from phonemes. Needs to be implemented for all
|
||||
tts backends.
|
||||
"""Create visemes from phonemes.
|
||||
|
||||
Arguments:
|
||||
phonemes(str): String with phoneme data
|
||||
May be implemented to convert TTS phonemes into Mycroft mouth
|
||||
visuals.
|
||||
|
||||
Arguments:
|
||||
phonemes (str): String with phoneme data
|
||||
|
||||
Returns:
|
||||
list: visemes
|
||||
"""
|
||||
return None
|
||||
|
||||
|
@ -384,8 +401,8 @@ class TTS(metaclass=ABCMeta):
|
|||
"""Cache phonemes
|
||||
|
||||
Arguments:
|
||||
key: Hash key for the sentence
|
||||
phonemes: phoneme string to save
|
||||
key (str): Hash key for the sentence
|
||||
phonemes (str): phoneme string to save
|
||||
"""
|
||||
cache_dir = mycroft.util.get_cache_directory("tts/" + self.tts_name)
|
||||
pho_file = os.path.join(cache_dir, key + ".pho")
|
||||
|
@ -400,7 +417,7 @@ class TTS(metaclass=ABCMeta):
|
|||
"""Load phonemes from cache file.
|
||||
|
||||
Arguments:
|
||||
Key: Key identifying phoneme cache
|
||||
key (str): Key identifying phoneme cache
|
||||
"""
|
||||
pho_file = os.path.join(
|
||||
mycroft.util.get_cache_directory("tts/" + self.tts_name),
|
||||
|
@ -436,6 +453,7 @@ class TTSValidator(metaclass=ABCMeta):
|
|||
self.validate_connection()
|
||||
|
||||
def validate_dependencies(self):
|
||||
"""Determine if all the TTS's external dependencies are satisfied."""
|
||||
pass
|
||||
|
||||
def validate_instance(self):
|
||||
|
@ -454,15 +472,19 @@ class TTSValidator(metaclass=ABCMeta):
|
|||
|
||||
@abstractmethod
|
||||
def validate_lang(self):
|
||||
pass
|
||||
"""Ensure the TTS supports current language."""
|
||||
|
||||
@abstractmethod
|
||||
def validate_connection(self):
|
||||
pass
|
||||
"""Ensure the TTS can connect to it's backend.
|
||||
|
||||
This can mean for example being able to launch the correct executable
|
||||
or contact a webserver.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_tts_class(self):
|
||||
pass
|
||||
"""Return TTS class that this validator is for."""
|
||||
|
||||
|
||||
def load_tts_plugin(module_name):
|
||||
|
@ -470,11 +492,18 @@ def load_tts_plugin(module_name):
|
|||
|
||||
Arguments:
|
||||
(str) Mycroft tts module name from config
|
||||
Returns:
|
||||
class: found tts plugin class
|
||||
"""
|
||||
return load_plugin('mycroft.plugin.tts', module_name)
|
||||
|
||||
|
||||
class TTSFactory:
|
||||
"""Factory class instantiating the configured TTS engine.
|
||||
|
||||
The factory can select between a range of built-in TTS engines and also
|
||||
from TTS engine plugins.
|
||||
"""
|
||||
from mycroft.tts.festival_tts import Festival
|
||||
from mycroft.tts.espeak_tts import ESpeak
|
||||
from mycroft.tts.fa_tts import FATTS
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
six==1.13.0
|
||||
requests==2.20.0
|
||||
gTTS==2.1.1
|
||||
gTTS==2.2.0
|
||||
PyAudio==0.2.11
|
||||
pyee==8.1.0
|
||||
SpeechRecognition==3.8.1
|
||||
|
|
|
@ -12,11 +12,28 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from copy import deepcopy
|
||||
from unittest.mock import Mock
|
||||
|
||||
from msm import MycroftSkillsManager
|
||||
from msm.skill_repo import SkillRepo
|
||||
|
||||
from mycroft.configuration.config import LocalConf, DEFAULT_CONFIG
|
||||
|
||||
__CONFIG = LocalConf(DEFAULT_CONFIG)
|
||||
|
||||
|
||||
def base_config():
|
||||
"""Base config used when mocking.
|
||||
|
||||
Preload to skip hitting the disk each creation time but make a copy
|
||||
so modifications don't mutate it.
|
||||
|
||||
Returns:
|
||||
(dict) Mycroft default configuration
|
||||
"""
|
||||
return deepcopy(__CONFIG)
|
||||
|
||||
|
||||
def mock_msm(temp_dir):
|
||||
"""Mock the MycroftSkillsManager because it reaches out to the internet."""
|
||||
|
@ -50,27 +67,13 @@ def mock_msm(temp_dir):
|
|||
def mock_config(temp_dir):
|
||||
"""Supply a reliable return value for the Configuration.get() method."""
|
||||
get_config_mock = Mock()
|
||||
get_config_mock.return_value = dict(
|
||||
skills=dict(
|
||||
msm=dict(
|
||||
directory='skills',
|
||||
versioned=True,
|
||||
repo=dict(
|
||||
cache='.skills-repo',
|
||||
url='https://github.com/MycroftAI/mycroft-skills',
|
||||
branch='19.02'
|
||||
)
|
||||
),
|
||||
update_interval=1.0,
|
||||
auto_update=True,
|
||||
blacklisted_skills=[],
|
||||
priority_skills=['foobar'],
|
||||
upload_skill_manifest=True
|
||||
),
|
||||
data_dir=str(temp_dir),
|
||||
enclosure=dict()
|
||||
)
|
||||
config = base_config()
|
||||
config['skills']['priority_skills'] = ['foobar']
|
||||
config['data_dir'] = str(temp_dir)
|
||||
config['server']['metrics'] = False
|
||||
config['enclosure'] = {}
|
||||
|
||||
get_config_mock.return_value = config
|
||||
return get_config_mock
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"server": {
|
||||
"metrics": false
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ from adapt.intent import IntentBuilder
|
|||
from mycroft.configuration import Configuration
|
||||
from mycroft.messagebus import Message
|
||||
from mycroft.skills.intent_service import (ContextManager, IntentService,
|
||||
_get_message_lang)
|
||||
_get_message_lang, AdaptIntent)
|
||||
|
||||
from test.util import base_config
|
||||
|
||||
|
@ -327,3 +327,14 @@ class TestIntentServiceApi(TestCase):
|
|||
self.intent_service.handle_get_adapt(msg)
|
||||
reply = get_last_message(self.intent_service.bus)
|
||||
self.assertEqual(reply.data['intent'], None)
|
||||
|
||||
|
||||
class TestAdaptIntent(TestCase):
|
||||
"""Test the AdaptIntent wrapper."""
|
||||
def test_named_intent(self):
|
||||
intent = AdaptIntent("CallEaglesIntent")
|
||||
self.assertEqual(intent.name, "CallEaglesIntent")
|
||||
|
||||
def test_unnamed_intent(self):
|
||||
intent = AdaptIntent()
|
||||
self.assertEqual(intent.name, "")
|
||||
|
|
10
test/util.py
10
test/util.py
|
@ -1,12 +1,4 @@
|
|||
from mycroft.configuration.config import LocalConf, DEFAULT_CONFIG
|
||||
from copy import deepcopy
|
||||
|
||||
__config = LocalConf(DEFAULT_CONFIG)
|
||||
|
||||
|
||||
# Base config to use when mocking
|
||||
def base_config():
|
||||
return deepcopy(__config)
|
||||
from test.unittests.mocks import base_config # For backwards compatibility
|
||||
|
||||
|
||||
class Anything:
|
||||
|
|
Loading…
Reference in New Issue