Merge pull request #2736 from forslund/docs/plugin-apis
Add Plugin base classes APIs to readthedocspull/2762/head
commit
6f8bae6ba1
|
@ -1,12 +1,24 @@
|
||||||
.. Mycroft documentation master file
|
.. Mycroft documentation master file
|
||||||
|
Mycroft-core technical documentation
|
||||||
|
====================================
|
||||||
|
|
||||||
Mycroft Skills API
|
Mycroft Skills API
|
||||||
==================
|
------------------
|
||||||
|
|
||||||
*Reference for the Mycroft Skills API*
|
*Reference for use during Skill creation*
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
:caption: Contents:
|
:caption: Contents:
|
||||||
|
|
||||||
source/mycroft
|
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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# 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
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
class AudioBackend(metaclass=ABCMeta):
|
class AudioBackend(metaclass=ABCMeta):
|
||||||
"""
|
"""Base class for all audio backend implementations.
|
||||||
Base class for all audio backend implementations.
|
|
||||||
|
|
||||||
Args:
|
Arguments:
|
||||||
config: configuration dict for the instance
|
config (dict): configuration dict for the instance
|
||||||
bus: Mycroft messagebus emitter
|
bus (MessageBusClient): Mycroft messagebus emitter
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config, bus):
|
def __init__(self, config, bus):
|
||||||
self._track_start_callback = None
|
self._track_start_callback = None
|
||||||
self.supports_mime_hints = False
|
self.supports_mime_hints = False
|
||||||
|
self.config = config
|
||||||
|
self.bus = bus
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def supported_uris(self):
|
def supported_uris(self):
|
||||||
|
"""List of supported uri types.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: Supported uri's
|
||||||
"""
|
"""
|
||||||
Returns: list of supported uri types.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def clear_list(self):
|
def clear_list(self):
|
||||||
"""
|
"""Clear playlist."""
|
||||||
Clear playlist
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def add_list(self, tracks):
|
def add_list(self, tracks):
|
||||||
"""
|
"""Add tracks to backend's playlist.
|
||||||
Add tracks to backend's playlist.
|
|
||||||
|
|
||||||
Args:
|
Arguments:
|
||||||
tracks: list of tracks.
|
tracks (list): list of tracks.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def play(self, repeat=False):
|
def play(self, repeat=False):
|
||||||
"""
|
"""Start playback.
|
||||||
Start playback.
|
|
||||||
|
|
||||||
Args:
|
Starts playing the first track in the playlist and will contiune
|
||||||
repeat: Repeat playlist, defaults to False
|
until all tracks have been played.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
repeat (bool): Repeat playlist, defaults to False
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def stop(self):
|
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):
|
def set_track_start_callback(self, callback_func):
|
||||||
"""
|
"""Register callback on track start.
|
||||||
Register callback on track start, should be called as each track
|
|
||||||
in a playlist is started.
|
This method should be called as each track in a playlist is started.
|
||||||
"""
|
"""
|
||||||
self._track_start_callback = callback_func
|
self._track_start_callback = callback_func
|
||||||
|
|
||||||
def pause(self):
|
def pause(self):
|
||||||
|
"""Pause playback.
|
||||||
|
|
||||||
|
Stops playback but may be resumed at the exact position the pause
|
||||||
|
occured.
|
||||||
"""
|
"""
|
||||||
Pause playback.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def resume(self):
|
def resume(self):
|
||||||
|
"""Resume paused playback.
|
||||||
|
|
||||||
|
Resumes playback after being paused.
|
||||||
"""
|
"""
|
||||||
Resume paused playback.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
"""
|
"""Skip to next track in playlist."""
|
||||||
Skip to next track in playlist.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def previous(self):
|
def previous(self):
|
||||||
"""
|
"""Skip to previous track in playlist."""
|
||||||
Skip to previous track in playlist.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def lower_volume(self):
|
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):
|
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):
|
def seek_forward(self, seconds=1):
|
||||||
"""
|
"""Skip X seconds.
|
||||||
Skip X seconds
|
|
||||||
|
|
||||||
Args:
|
Arguments:
|
||||||
seconds (int): number of seconds to seek, if negative rewind
|
seconds (int): number of seconds to seek, if negative rewind
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
def seek_backward(self, seconds=1):
|
def seek_backward(self, seconds=1):
|
||||||
"""
|
"""Rewind X seconds.
|
||||||
Rewind X seconds
|
|
||||||
|
|
||||||
Args:
|
Arguments:
|
||||||
seconds (int): number of seconds to seek, if negative rewind
|
seconds (int): number of seconds to seek, if negative jump forward.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
def track_info(self):
|
def track_info(self):
|
||||||
"""
|
"""Get info about current playing track.
|
||||||
Fetch info about current playing track.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict with track info.
|
dict: Track info containing atleast the keys artist and album.
|
||||||
"""
|
"""
|
||||||
ret = {}
|
ret = {}
|
||||||
ret['artist'] = ''
|
ret['artist'] = ''
|
||||||
|
@ -145,13 +142,19 @@ class AudioBackend(metaclass=ABCMeta):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
""" Perform clean shutdown """
|
"""Perform clean shutdown.
|
||||||
|
|
||||||
|
Implements any audio backend specific shutdown procedures.
|
||||||
|
"""
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
|
|
||||||
class RemoteAudioBackend(AudioBackend):
|
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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
"""Factory functions for loading hotword engines - both internal and plugins.
|
||||||
|
"""
|
||||||
from time import time, sleep
|
from time import time, sleep
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
@ -54,12 +56,19 @@ def msec_to_sec(msecs):
|
||||||
msecs: milliseconds
|
msecs: milliseconds
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
input converted from milliseconds to seconds
|
int: input converted from milliseconds to seconds
|
||||||
"""
|
"""
|
||||||
return msecs / 1000
|
return msecs / 1000
|
||||||
|
|
||||||
|
|
||||||
class HotWordEngine:
|
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"):
|
def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"):
|
||||||
self.key_phrase = str(key_phrase).lower()
|
self.key_phrase = str(key_phrase).lower()
|
||||||
|
|
||||||
|
@ -77,25 +86,43 @@ class HotWordEngine:
|
||||||
self.lang = str(self.config.get("lang", lang)).lower()
|
self.lang = str(self.config.get("lang", lang)).lower()
|
||||||
|
|
||||||
def found_wake_word(self, frame_data):
|
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
|
return False
|
||||||
|
|
||||||
def update(self, chunk):
|
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):
|
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
|
This may include things such as unloading data or shutdown
|
||||||
external processess.
|
external processess.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PocketsphinxHotWord(HotWordEngine):
|
class PocketsphinxHotWord(HotWordEngine):
|
||||||
"""Hotword engine using PocketSphinx.
|
"""Wake word engine using PocketSphinx.
|
||||||
|
|
||||||
PocketSphinx is very general purpose but has a somewhat high error rate.
|
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"):
|
def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"):
|
||||||
super().__init__(key_phrase, config, lang)
|
super().__init__(key_phrase, config, lang)
|
||||||
|
@ -154,7 +181,7 @@ class PocketsphinxHotWord(HotWordEngine):
|
||||||
|
|
||||||
|
|
||||||
class PreciseHotword(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
|
Precise is developed by Mycroft AI and produces quite good wake word
|
||||||
spotting when trained on a decent dataset.
|
spotting when trained on a decent dataset.
|
||||||
|
@ -301,7 +328,7 @@ class PreciseHotword(HotWordEngine):
|
||||||
|
|
||||||
|
|
||||||
class SnowboyHotWord(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.
|
testing interface.
|
||||||
"""
|
"""
|
||||||
def __init__(self, key_phrase="hey mycroft", config=None, lang="en-us"):
|
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:
|
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 = {
|
CLASSES = {
|
||||||
"pocketsphinx": PocketsphinxHotWord,
|
"pocketsphinx": PocketsphinxHotWord,
|
||||||
"precise": PreciseHotword,
|
"precise": PreciseHotword,
|
||||||
|
|
|
@ -27,7 +27,7 @@ from mycroft.util.plugins import load_plugin
|
||||||
|
|
||||||
|
|
||||||
class STT(metaclass=ABCMeta):
|
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):
|
def __init__(self):
|
||||||
config_core = Configuration.get()
|
config_core = Configuration.get()
|
||||||
self.lang = str(self.init_language(config_core))
|
self.lang = str(self.init_language(config_core))
|
||||||
|
@ -39,6 +39,7 @@ class STT(metaclass=ABCMeta):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init_language(config_core):
|
def init_language(config_core):
|
||||||
|
"""Helper method to get language code from Mycroft config."""
|
||||||
lang = config_core.get("lang", "en-US")
|
lang = config_core.get("lang", "en-US")
|
||||||
langs = lang.split("-")
|
langs = lang.split("-")
|
||||||
if len(langs) == 2:
|
if len(langs) == 2:
|
||||||
|
@ -47,7 +48,21 @@ class STT(metaclass=ABCMeta):
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def execute(self, audio, language=None):
|
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):
|
class TokenSTT(STT, metaclass=ABCMeta):
|
||||||
|
@ -322,8 +337,14 @@ class DeepSpeechServerSTT(STT):
|
||||||
|
|
||||||
|
|
||||||
class StreamThread(Thread, metaclass=ABCMeta):
|
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):
|
def __init__(self, queue, language):
|
||||||
|
@ -333,6 +354,7 @@ class StreamThread(Thread, metaclass=ABCMeta):
|
||||||
self.text = None
|
self.text = None
|
||||||
|
|
||||||
def _get_data(self):
|
def _get_data(self):
|
||||||
|
"""Generator reading audio data from queue."""
|
||||||
while True:
|
while True:
|
||||||
d = self.queue.get()
|
d = self.queue.get()
|
||||||
if d is None:
|
if d is None:
|
||||||
|
@ -341,23 +363,38 @@ class StreamThread(Thread, metaclass=ABCMeta):
|
||||||
self.queue.task_done()
|
self.queue.task_done()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
"""Thread entry point."""
|
||||||
return self.handle_audio_stream(self._get_data(), self.language)
|
return self.handle_audio_stream(self._get_data(), self.language)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def handle_audio_stream(self, audio, language):
|
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):
|
class StreamingSTT(STT, metaclass=ABCMeta):
|
||||||
"""
|
"""ABC class for threaded streaming STT implemenations."""
|
||||||
ABC class for threaded streaming STT implemenations.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.stream = None
|
self.stream = None
|
||||||
self.can_stream = True
|
self.can_stream = True
|
||||||
|
|
||||||
def stream_start(self, language=None):
|
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()
|
self.stream_stop()
|
||||||
language = language or self.lang
|
language = language or self.lang
|
||||||
self.queue = Queue()
|
self.queue = Queue()
|
||||||
|
@ -365,9 +402,21 @@ class StreamingSTT(STT, metaclass=ABCMeta):
|
||||||
self.stream.start()
|
self.stream.start()
|
||||||
|
|
||||||
def stream_data(self, data):
|
def stream_data(self, data):
|
||||||
|
"""Receiver of audio data.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
data (bytes): raw audio data.
|
||||||
|
"""
|
||||||
self.queue.put(data)
|
self.queue.put(data)
|
||||||
|
|
||||||
def stream_stop(self):
|
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:
|
if self.stream is not None:
|
||||||
self.queue.put(None)
|
self.queue.put(None)
|
||||||
self.stream.join()
|
self.stream.join()
|
||||||
|
@ -380,11 +429,20 @@ class StreamingSTT(STT, metaclass=ABCMeta):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def execute(self, audio, language=None):
|
def execute(self, audio, language=None):
|
||||||
|
"""End the parsing thread and collect data."""
|
||||||
return self.stream_stop()
|
return self.stream_stop()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def create_streaming_thread(self):
|
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):
|
class DeepSpeechStreamThread(StreamThread):
|
||||||
|
@ -550,7 +608,9 @@ def load_stt_plugin(module_name):
|
||||||
"""Wrapper function for loading stt plugin.
|
"""Wrapper function for loading stt plugin.
|
||||||
|
|
||||||
Arguments:
|
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)
|
return load_plugin('mycroft.plugin.stt', module_name)
|
||||||
|
|
||||||
|
|
|
@ -128,10 +128,10 @@ class PlaybackThread(Thread):
|
||||||
"""Send viseme data to enclosure
|
"""Send viseme data to enclosure
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
pairs(list): Visime and timing pair
|
pairs (list): Visime and timing pair
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if button has been pressed.
|
bool: True if button has been pressed.
|
||||||
"""
|
"""
|
||||||
if self.enclosure:
|
if self.enclosure:
|
||||||
self.enclosure.mouth_viseme(time(), pairs)
|
self.enclosure.mouth_viseme(time(), pairs)
|
||||||
|
@ -187,7 +187,7 @@ class TTS(metaclass=ABCMeta):
|
||||||
self.tts_name = type(self).__name__
|
self.tts_name = type(self).__name__
|
||||||
|
|
||||||
def load_spellings(self):
|
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')
|
path = join('text', self.lang.lower(), 'phonetic_spellings.txt')
|
||||||
spellings_file = resolve_resource_file(path)
|
spellings_file = resolve_resource_file(path)
|
||||||
if not spellings_file:
|
if not spellings_file:
|
||||||
|
@ -202,7 +202,7 @@ class TTS(metaclass=ABCMeta):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def begin_audio(self):
|
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
|
# Create signals informing start of speech
|
||||||
self.bus.emit(Message("recognizer_loop:audio_output_start"))
|
self.bus.emit(Message("recognizer_loop:audio_output_start"))
|
||||||
|
|
||||||
|
@ -254,11 +254,23 @@ class TTS(metaclass=ABCMeta):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def modify_tag(self, tag):
|
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
|
return tag
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def remove_ssml(text):
|
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(' ', ' ')
|
return re.sub('<[^>]*>', '', text).replace(' ', ' ')
|
||||||
|
|
||||||
def validate_ssml(self, utterance):
|
def validate_ssml(self, utterance):
|
||||||
|
@ -267,10 +279,10 @@ class TTS(metaclass=ABCMeta):
|
||||||
Remove unsupported / invalid tags
|
Remove unsupported / invalid tags
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
utterance(str): Sentence to validate
|
utterance (str): Sentence to validate
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
validated_sentence (str)
|
str: validated_sentence
|
||||||
"""
|
"""
|
||||||
# if ssml is not supported by TTS engine remove all tags
|
# if ssml is not supported by TTS engine remove all tags
|
||||||
if not self.ssml_tags:
|
if not self.ssml_tags:
|
||||||
|
@ -306,14 +318,14 @@ class TTS(metaclass=ABCMeta):
|
||||||
def execute(self, sentence, ident=None, listen=False):
|
def execute(self, sentence, ident=None, listen=False):
|
||||||
"""Convert sentence to speech, preprocessing out unsupported ssml
|
"""Convert sentence to speech, preprocessing out unsupported ssml
|
||||||
|
|
||||||
The method caches results if possible using the hash of the
|
The method caches results if possible using the hash of the
|
||||||
sentence.
|
sentence.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
sentence: Sentence to be spoken
|
sentence: (str) Sentence to be spoken
|
||||||
ident: Id reference to current interaction
|
ident: (str) Id reference to current interaction
|
||||||
listen: True if listen should be triggered at the end
|
listen: (bool) True if listen should be triggered at the end
|
||||||
of the utterance.
|
of the utterance.
|
||||||
"""
|
"""
|
||||||
sentence = self.validate_ssml(sentence)
|
sentence = self.validate_ssml(sentence)
|
||||||
|
|
||||||
|
@ -357,11 +369,16 @@ class TTS(metaclass=ABCMeta):
|
||||||
self.queue.put((self.audio_ext, wav_file, vis, ident, l))
|
self.queue.put((self.audio_ext, wav_file, vis, ident, l))
|
||||||
|
|
||||||
def viseme(self, phonemes):
|
def viseme(self, phonemes):
|
||||||
"""Create visemes from phonemes. Needs to be implemented for all
|
"""Create visemes from phonemes.
|
||||||
tts backends.
|
|
||||||
|
|
||||||
Arguments:
|
May be implemented to convert TTS phonemes into Mycroft mouth
|
||||||
phonemes(str): String with phoneme data
|
visuals.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
phonemes (str): String with phoneme data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: visemes
|
||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -384,8 +401,8 @@ class TTS(metaclass=ABCMeta):
|
||||||
"""Cache phonemes
|
"""Cache phonemes
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
key: Hash key for the sentence
|
key (str): Hash key for the sentence
|
||||||
phonemes: phoneme string to save
|
phonemes (str): phoneme string to save
|
||||||
"""
|
"""
|
||||||
cache_dir = mycroft.util.get_cache_directory("tts/" + self.tts_name)
|
cache_dir = mycroft.util.get_cache_directory("tts/" + self.tts_name)
|
||||||
pho_file = os.path.join(cache_dir, key + ".pho")
|
pho_file = os.path.join(cache_dir, key + ".pho")
|
||||||
|
@ -400,7 +417,7 @@ class TTS(metaclass=ABCMeta):
|
||||||
"""Load phonemes from cache file.
|
"""Load phonemes from cache file.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
Key: Key identifying phoneme cache
|
key (str): Key identifying phoneme cache
|
||||||
"""
|
"""
|
||||||
pho_file = os.path.join(
|
pho_file = os.path.join(
|
||||||
mycroft.util.get_cache_directory("tts/" + self.tts_name),
|
mycroft.util.get_cache_directory("tts/" + self.tts_name),
|
||||||
|
@ -436,6 +453,7 @@ class TTSValidator(metaclass=ABCMeta):
|
||||||
self.validate_connection()
|
self.validate_connection()
|
||||||
|
|
||||||
def validate_dependencies(self):
|
def validate_dependencies(self):
|
||||||
|
"""Determine if all the TTS's external dependencies are satisfied."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def validate_instance(self):
|
def validate_instance(self):
|
||||||
|
@ -454,15 +472,19 @@ class TTSValidator(metaclass=ABCMeta):
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def validate_lang(self):
|
def validate_lang(self):
|
||||||
pass
|
"""Ensure the TTS supports current language."""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def validate_connection(self):
|
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
|
@abstractmethod
|
||||||
def get_tts_class(self):
|
def get_tts_class(self):
|
||||||
pass
|
"""Return TTS class that this validator is for."""
|
||||||
|
|
||||||
|
|
||||||
def load_tts_plugin(module_name):
|
def load_tts_plugin(module_name):
|
||||||
|
@ -470,11 +492,18 @@ def load_tts_plugin(module_name):
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
(str) Mycroft tts module name from config
|
(str) Mycroft tts module name from config
|
||||||
|
Returns:
|
||||||
|
class: found tts plugin class
|
||||||
"""
|
"""
|
||||||
return load_plugin('mycroft.plugin.tts', module_name)
|
return load_plugin('mycroft.plugin.tts', module_name)
|
||||||
|
|
||||||
|
|
||||||
class TTSFactory:
|
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.festival_tts import Festival
|
||||||
from mycroft.tts.espeak_tts import ESpeak
|
from mycroft.tts.espeak_tts import ESpeak
|
||||||
from mycroft.tts.fa_tts import FATTS
|
from mycroft.tts.fa_tts import FATTS
|
||||||
|
|
Loading…
Reference in New Issue