Skip to content
This repository has been archived by the owner on Sep 8, 2024. It is now read-only.

Handle Mimic missing properly #2718

Merged
merged 3 commits into from
Nov 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions mycroft/audio/speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,17 +134,31 @@ def mute_and_speak(utterance, ident, listen=False):
LOG.error('TTS execution failed ({})'.format(repr(e)))


def mimic_fallback_tts(utterance, ident, listen):
def _get_mimic_fallback():
"""Lazily initializes the fallback TTS if needed."""
global mimic_fallback_obj
# fallback if connection is lost
config = Configuration.get()
tts_config = config.get('tts', {}).get("mimic", {})
lang = config.get("lang", "en-us")
if not mimic_fallback_obj:
mimic_fallback_obj = Mimic(lang, tts_config)
tts = mimic_fallback_obj
config = Configuration.get()
tts_config = config.get('tts', {}).get("mimic", {})
lang = config.get("lang", "en-us")
tts = Mimic(lang, tts_config)
tts.validator.validate()
tts.init(bus)
mimic_fallback_obj = tts

return mimic_fallback_obj


def mimic_fallback_tts(utterance, ident, listen):
"""Speak utterance using fallback TTS if connection is lost.

Arguments:
utterance (str): sentence to speak
ident (str): interaction id for metrics
listen (bool): True if interaction should end with mycroft listening
"""
tts = _get_mimic_fallback()
LOG.debug("Mimic fallback, utterance : " + str(utterance))
tts.init(bus)
tts.execute(utterance, ident, listen)


Expand Down
64 changes: 36 additions & 28 deletions mycroft/tts/mimic_tts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Mimic TTS, a local TTS backend.

This Backend uses the mimic executable to render text into speech.
"""
import os
import os.path
from os.path import exists, join, expanduser
import stat
import subprocess
from threading import Thread
from time import sleep

import os.path
from os.path import exists, join, expanduser

from mycroft import MYCROFT_ROOT_PATH
from mycroft.api import DeviceApi
from mycroft.configuration import Configuration
Expand Down Expand Up @@ -54,8 +57,8 @@ def make_executable(dest):
"""Call back function to make the downloaded file executable."""
LOG.info('Make executable new voice binary executable')
# make executable
st = os.stat(dest)
os.chmod(dest, st.st_mode | stat.S_IEXEC)
file_stat = os.stat(dest)
os.chmod(dest, file_stat.st_mode | stat.S_IEXEC)

# First download the selected voice if needed
voice_file = SUBSCRIBER_VOICES.get(selected_voice)
Expand All @@ -64,9 +67,9 @@ def make_executable(dest):
url = DeviceApi().get_subscriber_voice_url(selected_voice)
# Check we got an url
if url:
dl = download(url, voice_file, make_executable)
dl_status = download(url, voice_file, make_executable)
# Wait for completion
while not dl.done:
while not dl_status.done:
sleep(1)
else:
LOG.debug('{} is not available for this architecture'
Expand All @@ -79,9 +82,9 @@ def make_executable(dest):
url = DeviceApi().get_subscriber_voice_url(voice)
# Check we got an url
if url:
dl = download(url, voice_file, make_executable)
dl_status = download(url, voice_file, make_executable)
# Wait for completion
while not dl.done:
while not dl_status.done:
sleep(1)
else:
LOG.debug('{} is not available for this architecture'
Expand All @@ -95,25 +98,26 @@ def __init__(self, lang, config):
lang, config, MimicValidator(self), 'wav',
ssml_tags=["speak", "ssml", "phoneme", "voice", "audio", "prosody"]
)
self.dl = None
self.clear_cache()

# Download subscriber voices if needed
self.is_subscriber = DeviceApi().is_subscriber
if self.is_subscriber:
t = Thread(target=download_subscriber_voices, args=[self.voice])
t.daemon = True
t.start()
trd = Thread(target=download_subscriber_voices, args=[self.voice])
trd.daemon = True
trd.start()

def modify_tag(self, tag):
for key, value in [
('x-slow', '0.4'),
('slow', '0.7'),
('medium', '1.0'),
('high', '1.3'),
('x-high', '1.6'),
('speed', 'rate')
]:
"""Modify the SSML to suite Mimic."""
ssml_conversions = {
'x-slow': '0.4',
'slow': '0.7',
'medium': '1.0',
'high': '1.3',
'x-high': '1.6',
'speed': 'rate'
}
for key, value in ssml_conversions.items():
tag = tag.replace(key, value)
return tag

Expand Down Expand Up @@ -175,22 +179,26 @@ def viseme(self, output):


class MimicValidator(TTSValidator):
def __init__(self, tts):
super(MimicValidator, self).__init__(tts)

"""Validator class checking that Mimic can be used."""
def validate_lang(self):
"""Verify that the language is supported."""
# TODO: Verify version of mimic can handle the requested language
pass

def validate_connection(self):
"""Check that Mimic executable is found and works."""
try:
subprocess.call([BIN, '--version'])
except Exception:
LOG.info("Failed to find mimic at: " + BIN)
except Exception as err:
if BIN:
LOG.error('Failed to find mimic at: {}'.format(BIN))
else:
LOG.error('Mimic executable not found')
raise Exception(
'Mimic was not found. Run install-mimic.sh to install it.')
'Mimic was not found. Run install-mimic.sh to install it.') \
from err

def get_tts_class(self):
"""Return the TTS class associated with the validator."""
return Mimic


Expand Down