diff --git a/platypush/backend/http/app/routes/plugins/tts/mimic3.py b/platypush/backend/http/app/routes/plugins/tts/mimic3.py deleted file mode 100644 index 7d033b0a..00000000 --- a/platypush/backend/http/app/routes/plugins/tts/mimic3.py +++ /dev/null @@ -1,49 +0,0 @@ -import requests -from urllib.parse import urljoin - -from flask import abort, request, Blueprint - -from platypush.backend.http.app import template_folder - -# Upstream /api/tts response timeout, in seconds -_default_timeout = 30 -mimic3 = Blueprint('mimic3', __name__, template_folder=template_folder) - -# Declare routes list -__routes__ = [ - mimic3, -] - - -@mimic3.route('/tts/mimic3/say', methods=['GET']) -def proxy_tts_request(): - """ - This route is used to proxy the POST request to the Mimic3 TTS server - through a GET, so it can be easily processed as a URL through a media - plugin. - """ - required_args = { - 'text', - 'server_url', - 'voice', - } - - missing_args = required_args.difference(set(request.args.keys())) - if missing_args: - abort(400, f'Missing parameters: {missing_args}') - - args = {arg: request.args[arg] for arg in required_args} - - rs = requests.post( - urljoin(args['server_url'], '/api/tts'), - data=args['text'], - timeout=int(request.args.get('timeout', _default_timeout)), - params={ - 'voice': args['voice'], - }, - ) - - return rs.content - - -# vim:sw=4:ts=4:et: diff --git a/platypush/plugins/tts/mimic3/__init__.py b/platypush/plugins/tts/mimic3/__init__.py index 0b0fdd04..8059940c 100644 --- a/platypush/plugins/tts/mimic3/__init__.py +++ b/platypush/plugins/tts/mimic3/__init__.py @@ -1,10 +1,11 @@ -from typing import Optional -from urllib.parse import urljoin, urlencode +from contextlib import contextmanager +import os +import tempfile +from typing import Generator, Optional +from urllib.parse import urljoin import requests -from platypush.backend.http.app.utils import get_local_base_url -from platypush.context import get_backend from platypush.plugins import action from platypush.plugins.tts import TtsPlugin from platypush.schemas.tts.mimic3 import Mimic3VoiceSchema @@ -27,44 +28,65 @@ class TtsMimic3Plugin(TtsPlugin): -v "%h/.local/share/mycroft/mimic3:/home/mimic3/.local/share/mycroft/mimic3" \ 'mycroftai/mimic3' - Requires: - - * At least a *media plugin* (see - :class:`platypush.plugins.media.MediaPlugin`) enabled/configured - - used for speech playback. - * The ``http`` backend (:class:`platypush.backend.http.HttpBackend`) - enabled - used for proxying the API calls. - """ def __init__( self, server_url: str, - voice: str = 'en_UK/apope_low', - media_plugin: Optional[str] = None, - player_args: Optional[dict] = None, + voice: str = 'en_US/vctk_low', **kwargs, ): """ :param server_url: Base URL of the web server that runs the Mimic3 engine. - :param voice: Default voice to be used (default: ``en_UK/apope_low``). + :param voice: Default voice to be used (default: ``en_US/vctk_low``). You can get a full list of the voices available on the server through :meth:`.voices`. - :param media_plugin: Media plugin to be used for audio playback. Supported: - - - ``media.gstreamer`` - - ``media.omxplayer`` - - ``media.mplayer`` - - ``media.mpv`` - - ``media.vlc`` - - :param player_args: Optional arguments that should be passed to the player plugin's - :meth:`platypush.plugins.media.MediaPlugin.play` method. """ - super().__init__(media_plugin=media_plugin, player_args=player_args, **kwargs) - + super().__init__(**kwargs) self.server_url = server_url self.voice = voice + self.player_args.update( + { + 'channels': 1, + 'sample_rate': 22050, + 'dtype': 'int16', + } + ) + + @staticmethod + @contextmanager + def _save_audio( + text: str, + server_url: str, + voice: str, + timeout: Optional[float] = None, + ) -> Generator[str, None, None]: + """ + Saves the raw audio stream from the Mimic3 server to an audio file for + playback. + + :param text: Text to be spoken. + :param server_url: Base URL of the Mimic3 server. + :param voice: Voice to be used. + :param timeout: Timeout for the audio stream retrieval. + """ + + rs = requests.post( + urljoin(server_url, '/api/tts'), + data=text, + timeout=timeout, + params={ + 'voice': voice, + }, + ) + + rs.raise_for_status() + tmp_file = tempfile.NamedTemporaryFile(suffix='.wav', delete=False) + tmp_file.write(rs.content) + yield tmp_file.name + + tmp_file.close() + os.unlink(tmp_file.name) @action def say( @@ -73,7 +95,7 @@ class TtsMimic3Plugin(TtsPlugin): *_, server_url: Optional[str] = None, voice: Optional[str] = None, - player_args: Optional[dict] = None, + **player_args, ): """ Say some text. @@ -81,28 +103,16 @@ class TtsMimic3Plugin(TtsPlugin): :param text: Text to say. :param server_url: Default ``server_url`` override. :param voice: Default ``voice`` override. - :param player_args: Default ``player_args`` override. + :param player_args: Extends the additional arguments to be passed to + :meth:`platypush.plugins.sound.SoundPlugin.play` (like volume, + duration, channels etc.). """ + server_url = server_url or self.server_url voice = voice or self.voice - player_args = player_args or self.player_args - http = get_backend('http') - assert http, 'http backend not configured' - assert self.media_plugin, 'No media plugin configured' - url = ( - urljoin(get_local_base_url(), '/tts/mimic3/say') - + '?' - + urlencode( - { - 'text': text, - 'server_url': server_url, - 'voice': voice, - } - ) - ) - - self.media_plugin.play(url, **player_args) + with self._save_audio(text, server_url, voice) as audio_file: + self._playback(audio_file, join=True, **player_args) @action def voices(self, server_url: Optional[str] = None): @@ -113,7 +123,7 @@ class TtsMimic3Plugin(TtsPlugin): :return: .. schema:: tts.mimic3.Mimic3VoiceSchema(many=True) """ server_url = server_url or self.server_url - rs = requests.get(urljoin(server_url, '/api/voices')) + rs = requests.get(urljoin(server_url, '/api/voices'), timeout=10) rs.raise_for_status() return Mimic3VoiceSchema().dump(rs.json(), many=True)