2017-10-04 06:28:44 +00:00
|
|
|
# Copyright 2017 Mycroft AI Inc.
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
#
|
2017-06-15 12:47:44 +00:00
|
|
|
import re
|
2018-04-18 15:59:23 +00:00
|
|
|
import time
|
2017-10-08 19:03:02 +00:00
|
|
|
from threading import Lock
|
2018-04-18 15:59:23 +00:00
|
|
|
|
2017-09-23 12:13:50 +00:00
|
|
|
from mycroft.configuration import Configuration
|
2018-04-18 15:59:23 +00:00
|
|
|
from mycroft.metrics import report_timing, Stopwatch
|
2017-09-18 19:14:21 +00:00
|
|
|
from mycroft.tts import TTSFactory
|
2017-10-17 13:02:14 +00:00
|
|
|
from mycroft.util import create_signal, check_for_signal
|
2017-09-18 19:14:21 +00:00
|
|
|
from mycroft.util.log import LOG
|
2018-07-05 18:56:54 +00:00
|
|
|
from mycroft.messagebus.message import Message
|
2018-10-08 21:54:22 +00:00
|
|
|
from mycroft.tts.remote_tts import RemoteTTSTimeoutException
|
|
|
|
from mycroft.tts.mimic_tts import Mimic
|
2017-06-15 12:47:44 +00:00
|
|
|
|
2018-08-22 01:50:50 +00:00
|
|
|
bus = None # Mycroft messagebus connection
|
2017-06-15 12:47:44 +00:00
|
|
|
config = None
|
|
|
|
tts = None
|
|
|
|
tts_hash = None
|
|
|
|
lock = Lock()
|
2019-03-27 21:22:04 +00:00
|
|
|
mimic_fallback_obj = None
|
2017-06-15 12:47:44 +00:00
|
|
|
|
|
|
|
_last_stop_signal = 0
|
|
|
|
|
|
|
|
|
2017-11-22 04:45:12 +00:00
|
|
|
def _start_listener(message):
|
2017-06-15 12:47:44 +00:00
|
|
|
"""
|
2017-11-22 04:45:12 +00:00
|
|
|
Force Mycroft to start listening (as if 'Hey Mycroft' was spoken)
|
2017-06-15 12:47:44 +00:00
|
|
|
"""
|
|
|
|
create_signal('startListening')
|
|
|
|
|
|
|
|
|
|
|
|
def handle_speak(event):
|
|
|
|
"""
|
|
|
|
Handle "speak" message
|
|
|
|
"""
|
2017-09-23 12:13:50 +00:00
|
|
|
config = Configuration.get()
|
2019-07-26 06:53:27 +00:00
|
|
|
Configuration.set_config_update_handlers(bus)
|
2017-06-15 12:47:44 +00:00
|
|
|
global _last_stop_signal
|
|
|
|
|
2017-12-21 13:12:12 +00:00
|
|
|
# Get conversation ID
|
|
|
|
if event.context and 'ident' in event.context:
|
|
|
|
ident = event.context['ident']
|
|
|
|
else:
|
|
|
|
ident = 'unknown'
|
|
|
|
|
2019-01-31 07:30:53 +00:00
|
|
|
start = time.time() # Time of speech request
|
2017-12-07 19:34:32 +00:00
|
|
|
with lock:
|
2017-12-21 00:05:14 +00:00
|
|
|
stopwatch = Stopwatch()
|
|
|
|
stopwatch.start()
|
2017-12-07 19:34:32 +00:00
|
|
|
utterance = event.data['utterance']
|
|
|
|
if event.data.get('expect_response', False):
|
|
|
|
# When expect_response is requested, the listener will be restarted
|
|
|
|
# at the end of the next bit of spoken audio.
|
2018-08-22 01:50:50 +00:00
|
|
|
bus.once('recognizer_loop:audio_output_end', _start_listener)
|
2017-12-07 19:34:32 +00:00
|
|
|
|
|
|
|
# This is a bit of a hack for Picroft. The analog audio on a Pi blocks
|
|
|
|
# for 30 seconds fairly often, so we don't want to break on periods
|
|
|
|
# (decreasing the chance of encountering the block). But we will
|
|
|
|
# keep the split for non-Picroft installs since it give user feedback
|
|
|
|
# faster on longer phrases.
|
|
|
|
#
|
|
|
|
# TODO: Remove or make an option? This is really a hack, anyway,
|
|
|
|
# so we likely will want to get rid of this when not running on Mimic
|
2018-04-18 15:59:23 +00:00
|
|
|
if (config.get('enclosure', {}).get('platform') != "picroft" and
|
|
|
|
len(re.findall('<[^>]*>', utterance)) == 0):
|
2019-05-16 22:27:51 +00:00
|
|
|
# Remove any whitespace present after the period,
|
|
|
|
# if a character (only alpha) ends with a period
|
|
|
|
# ex: A. Lincoln -> A.Lincoln
|
|
|
|
# so that we don't split at the period
|
|
|
|
utterance = re.sub(r'\b([A-za-z][\.])(\s+)', r'\g<1>', utterance)
|
2019-02-15 08:54:42 +00:00
|
|
|
chunks = re.split(r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\;|\?)\s',
|
2017-12-07 19:34:32 +00:00
|
|
|
utterance)
|
|
|
|
for chunk in chunks:
|
2019-01-31 07:30:53 +00:00
|
|
|
# Check if somthing has aborted the speech
|
|
|
|
if (_last_stop_signal > start or
|
|
|
|
check_for_signal('buttonPress')):
|
|
|
|
# Clear any newly queued speech
|
|
|
|
tts.playback.clear()
|
|
|
|
break
|
2017-12-07 19:34:32 +00:00
|
|
|
try:
|
2017-12-21 13:31:14 +00:00
|
|
|
mute_and_speak(chunk, ident)
|
2017-12-07 19:34:32 +00:00
|
|
|
except KeyboardInterrupt:
|
|
|
|
raise
|
2017-12-07 19:43:43 +00:00
|
|
|
except Exception:
|
2017-12-07 19:34:32 +00:00
|
|
|
LOG.error('Error in mute_and_speak', exc_info=True)
|
|
|
|
else:
|
2017-12-21 13:31:14 +00:00
|
|
|
mute_and_speak(utterance, ident)
|
2017-06-15 12:47:44 +00:00
|
|
|
|
2017-12-21 00:05:14 +00:00
|
|
|
stopwatch.stop()
|
2018-02-01 08:35:27 +00:00
|
|
|
report_timing(ident, 'speech', stopwatch, {'utterance': utterance,
|
|
|
|
'tts': tts.__class__.__name__})
|
2017-12-21 00:05:14 +00:00
|
|
|
|
2017-06-15 12:47:44 +00:00
|
|
|
|
2017-12-21 13:31:14 +00:00
|
|
|
def mute_and_speak(utterance, ident):
|
2017-06-15 12:47:44 +00:00
|
|
|
"""
|
|
|
|
Mute mic and start speaking the utterance using selected tts backend.
|
|
|
|
|
|
|
|
Args:
|
2017-12-21 13:31:14 +00:00
|
|
|
utterance: The sentence to be spoken
|
|
|
|
ident: Ident tying the utterance to the source query
|
2017-06-15 12:47:44 +00:00
|
|
|
"""
|
|
|
|
global tts_hash
|
|
|
|
|
|
|
|
# update TTS object if configuration has changed
|
|
|
|
if tts_hash != hash(str(config.get('tts', ''))):
|
|
|
|
global tts
|
|
|
|
# Stop tts playback thread
|
|
|
|
tts.playback.stop()
|
|
|
|
tts.playback.join()
|
|
|
|
# Create new tts instance
|
|
|
|
tts = TTSFactory.create()
|
2018-08-22 01:50:50 +00:00
|
|
|
tts.init(bus)
|
2017-06-15 12:47:44 +00:00
|
|
|
tts_hash = hash(str(config.get('tts', '')))
|
|
|
|
|
2017-09-18 18:55:58 +00:00
|
|
|
LOG.info("Speak: " + utterance)
|
2017-09-22 01:11:30 +00:00
|
|
|
try:
|
2018-04-18 15:14:59 +00:00
|
|
|
tts.execute(utterance, ident)
|
2018-10-08 21:54:22 +00:00
|
|
|
except RemoteTTSTimeoutException as e:
|
|
|
|
LOG.error(e)
|
|
|
|
mimic_fallback_tts(utterance, ident)
|
2018-07-10 17:28:45 +00:00
|
|
|
except Exception as e:
|
|
|
|
LOG.error('TTS execution failed ({})'.format(repr(e)))
|
2017-06-15 12:47:44 +00:00
|
|
|
|
|
|
|
|
2018-10-08 21:54:22 +00:00
|
|
|
def mimic_fallback_tts(utterance, ident):
|
2019-03-27 21:22:04 +00:00
|
|
|
global mimic_fallback_obj
|
2018-10-08 21:54:22 +00:00
|
|
|
# fallback if connection is lost
|
|
|
|
config = Configuration.get()
|
|
|
|
tts_config = config.get('tts', {}).get("mimic", {})
|
|
|
|
lang = config.get("lang", "en-us")
|
2019-03-27 21:22:04 +00:00
|
|
|
if not mimic_fallback_obj:
|
|
|
|
mimic_fallback_obj = Mimic(lang, tts_config)
|
|
|
|
tts = mimic_fallback_obj
|
|
|
|
LOG.debug("Mimic fallback, utterance : " + str(utterance))
|
2018-10-08 21:54:22 +00:00
|
|
|
tts.init(bus)
|
|
|
|
tts.execute(utterance, ident)
|
|
|
|
|
|
|
|
|
2017-06-15 12:47:44 +00:00
|
|
|
def handle_stop(event):
|
|
|
|
"""
|
|
|
|
handle stop message
|
|
|
|
"""
|
|
|
|
global _last_stop_signal
|
2017-10-17 13:02:14 +00:00
|
|
|
if check_for_signal("isSpeaking", -1):
|
|
|
|
_last_stop_signal = time.time()
|
2019-01-31 07:30:53 +00:00
|
|
|
tts.playback.clear() # Clear here to get instant stop
|
2018-08-22 01:50:50 +00:00
|
|
|
bus.emit(Message("mycroft.stop.handled", {"by": "TTS"}))
|
2017-06-15 12:47:44 +00:00
|
|
|
|
|
|
|
|
2018-08-22 01:50:50 +00:00
|
|
|
def init(messagebus):
|
|
|
|
""" Start speech related handlers.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
messagebus: Connection to the Mycroft messagebus
|
2017-06-15 12:47:44 +00:00
|
|
|
"""
|
|
|
|
|
2018-08-22 01:50:50 +00:00
|
|
|
global bus
|
2017-06-15 12:47:44 +00:00
|
|
|
global tts
|
|
|
|
global tts_hash
|
|
|
|
global config
|
|
|
|
|
2018-08-22 01:50:50 +00:00
|
|
|
bus = messagebus
|
2019-07-26 06:53:27 +00:00
|
|
|
Configuration.set_config_update_handlers(bus)
|
2017-09-23 12:13:50 +00:00
|
|
|
config = Configuration.get()
|
2018-08-22 01:50:50 +00:00
|
|
|
bus.on('mycroft.stop', handle_stop)
|
|
|
|
bus.on('mycroft.audio.speech.stop', handle_stop)
|
|
|
|
bus.on('speak', handle_speak)
|
|
|
|
bus.on('mycroft.mic.listen', _start_listener)
|
2017-06-15 12:47:44 +00:00
|
|
|
|
|
|
|
tts = TTSFactory.create()
|
2018-08-22 01:50:50 +00:00
|
|
|
tts.init(bus)
|
2019-07-23 20:31:21 +00:00
|
|
|
tts_hash = hash(str(config.get('tts', '')))
|
2017-08-02 21:04:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
def shutdown():
|
|
|
|
if tts:
|
|
|
|
tts.playback.stop()
|
|
|
|
tts.playback.join()
|
2019-03-28 10:15:45 +00:00
|
|
|
if mimic_fallback_obj:
|
|
|
|
mimic_fallback_obj.playback.stop()
|
|
|
|
mimic_fallback_obj.playback.join()
|