diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/tts.google/index.scss b/platypush/backend/http/static/css/source/webpanel/plugins/tts.google/index.scss new file mode 120000 index 000000000..867ef5c68 --- /dev/null +++ b/platypush/backend/http/static/css/source/webpanel/plugins/tts.google/index.scss @@ -0,0 +1 @@ +../tts/index.scss \ No newline at end of file diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/tts/index.scss b/platypush/backend/http/static/css/source/webpanel/plugins/tts/index.scss new file mode 100644 index 000000000..ed059b99f --- /dev/null +++ b/platypush/backend/http/static/css/source/webpanel/plugins/tts/index.scss @@ -0,0 +1,32 @@ +@import 'common/vars'; + +.tts-container { + max-width: 80rem; + min-height: 10rem; + margin: 3rem auto; + display: flex; + align-items: center; + justify-content: center; + border: $default-border-2; + border-radius: 3rem; + + form { + margin: 0; + width: 90%; + + input[type=text] { + width: 100%; + } + + button { + border-radius: 5rem; + } + + input, button { + &:hover { + border-color: $default-hover-fg; + } + } + } +} + diff --git a/platypush/backend/http/static/js/plugins/tts.google/index.js b/platypush/backend/http/static/js/plugins/tts.google/index.js new file mode 100644 index 000000000..b9a16866b --- /dev/null +++ b/platypush/backend/http/static/js/plugins/tts.google/index.js @@ -0,0 +1,26 @@ +Vue.component('tts-google', { + template: '#tmpl-tts-google', + data: function() { + return { + talking: false, + }; + }, + + methods: { + talk: async function(event) { + event.preventDefault(); + + const args = [...event.target.querySelectorAll('input')].reduce((obj, el) => { + if (el.value.length) + obj[el.name] = el.value; + return obj; + }, {}); + + this.talking = true; + await request('tts.google.say', args); + this.talking = false; + }, + }, +}); + + diff --git a/platypush/backend/http/static/js/plugins/tts/index.js b/platypush/backend/http/static/js/plugins/tts/index.js new file mode 100644 index 000000000..6a8ee7940 --- /dev/null +++ b/platypush/backend/http/static/js/plugins/tts/index.js @@ -0,0 +1,25 @@ +Vue.component('tts', { + template: '#tmpl-tts', + data: function() { + return { + talking: false, + }; + }, + + methods: { + talk: async function(event) { + event.preventDefault(); + + const args = [...event.target.querySelectorAll('input')].reduce((obj, el) => { + if (el.value.length) + obj[el.name] = el.value; + return obj; + }, {}); + + this.talking = true; + await request('tts.say', args); + this.talking = false; + }, + }, +}); + diff --git a/platypush/backend/http/templates/plugins/music.mpd.html b/platypush/backend/http/templates/plugins/music.mpd.html deleted file mode 100644 index fbf79a9b7..000000000 --- a/platypush/backend/http/templates/plugins/music.mpd.html +++ /dev/null @@ -1,181 +0,0 @@ -<script type="text/javascript" src="{{ url_for('static', filename='js/music.mpd.js') }}"></script> -<link rel="stylesheet" href="{{ url_for('static', filename='css/music.mpd.css') }}"></script> - -<div id="music-search-modal" class="modal"> - <div class="modal-container"> - <div class="modal-header"> - Search For Music - </div> - - <div class="modal-body"> - <form id="music-search-form" action="#"> - <div class="row form-row"> - <div class="two columns"> - <label for="music-search-any">Any</label> - </div> - <div class="ten columns"> - <input type="text" name="any"> - </div> - </div> - - <div class="row form-row"> - <div class="two columns"> - <label for="music-search-artist">Artist</label> - </div> - <div class="ten columns"> - <input type="text" name="artist"> - </div> - </div> - - <div class="row form-row"> - <div class="two columns"> - <label for="music-search-title">Title</label> - </div> - <div class="ten columns"> - <input type="text" name="title"> - </div> - </div> - - <div class="row form-row"> - <div class="two columns"> - <label for="music-search-album">Album</label> - </div> - <div class="ten columns"> - <input type="text" name="album"> - </div> - </div> - - <div class="row music-form-bottom"> - <div class="six columns offset-by-six do-search-btns"> - <input type="button" class="btn-default" data-dismiss-modal="#music-search-modal" - value="Close"> - <input type="submit" class="btn-primary" value="Search"> - </div> - </div> - </form> - - <form class="row" id="music-search-results-form"> - <div class="six columns"> - <button class="btn-default" id="music-results-add" disabled="disabled"> - <i class="fa fa-plus"></i> - </button> - <button class="btn-default" id="music-results-play" disabled="disabled"> - <i class="fa fa-play"></i> - </button> - </div> - - <div class="six columns right-side"> - <input type="button" class="btn-default" - data-dismiss-modal="#music-search-modal" value="Close"> - <input type="button" class="btn-primary" - id="music-search-reset" value="Reset"> - </div> - </form> - - <div id="music-search-results-container" class="row"> - <div id="music-search-results-head" class="row"> - <div class="three columns">Artist</div> - <div class="four columns">Title</div> - <div class="four columns">Album</div> - <div class="one column">Time</div> - </div> - - <div id="music-search-results" class="row"></div> - </div> - </div> - </div> -</div> - -<div class="row track-info"> - <span class="no-track">No media is being played</span> - <span class="artist"></span> - <span class="track"></span> -</div> - -<div class="playback-controls"> - <div class="row"> - <div class="eight columns offset-by-two slider-container" id="track-seeker-container"> - <span class="seek-time" id="seek-time-elapsed">-:--</span> - <input type="range" min="0" id="track-seeker" disabled="disabled" class="slider" style="width:75%"> - <span class="seek-time" id="seek-time-length">-:--</span> - </div> - </div> - - <div class="row"> - <div class="ten columns offset-by-one"> - <button data-action="previous"> - <i class="fa fa-step-backward"></i> - </button> - - <button data-action="repeat"> - <i class="fa fa-repeat"></i> - </button> - - <button data-action="play"> - <i class="fa fa-play"></i> - </button> - - <button data-action="pause"> - <i class="fa fa-pause"></i> - </button> - - <button data-action="stop"> - <i class="fa fa-stop"></i> - </button> - - <button data-action="random"> - <i class="fa fa-random"></i> - </button> - - <button data-action="next"> - <i class="fa fa-step-forward"></i> - </button> - </div> - </div> - - <div class="row"> - <div class="eight columns offset-by-two slider-container" id="volume-ctrl-container"> - <i class="fa fa-volume-down"></i> - <input type="range" min="0" max="100" id="volume-ctrl" class="slider" style="width:80%"> - <i class="fa fa-volume-up"></i> - </div> - </div> -</div> - -<div class="row"> - <div id="player-left-side" class="three columns music-pane"> - <div class="row"> - <div id="browser-controls"> - <button data-action="add"> - <i class="fa fa-plus"></i> - </button> - <button data-action="search" data-modal="#music-search-modal"> - <i class="fa fa-search"></i> - </button> - </div> - - <div id="browser-filter-container"> - <input type="text" id="browser-filter" placeholder="Filter"> - </div> - - <div id="music-browser" class="music-pane"></div> - </div> - </div> - - <div id="player-right-side" class="nine columns music-pane"> - <div class="row"> - <div id="playlist-controls"> - <button data-action="clear"> - <i class="fa fa-eraser"></i> - </button> - </div> - - <div id="playlist-filter-container"> - <input type="text" id="playlist-filter" placeholder="Filter"> - </div> - - <div id="playlist-content"></div> - </div> - </div> -</div> - diff --git a/platypush/backend/http/templates/plugins/music.snapcast.html b/platypush/backend/http/templates/plugins/music.snapcast.html deleted file mode 100644 index 2aedf3520..000000000 --- a/platypush/backend/http/templates/plugins/music.snapcast.html +++ /dev/null @@ -1,161 +0,0 @@ -<script type="text/javascript" src="{{ url_for('static', filename='js/music.snapcast.js') }}"></script> -<link rel="stylesheet" href="{{ url_for('static', filename='css/music.snapcast.css') }}"></script> - -<div id="snapcast-host-modal" class="modal snapcast-modal"> - <div class="modal-container"> - <div class="modal-header"></div> - <div class="modal-body"> - <form id="snapcast-host-form" class="snapcast-form" action="#"> - <div class="row snapcast-host-info"> - <div class="row"> - <div class="three columns info-name">IP Address</div> - <div class="nine columns info-value" data-bind="ip"></div> - </div> - - <div class="row"> - <div class="three columns info-name">MAC Address</div> - <div class="nine columns info-value" data-bind="mac"></div> - </div> - - <div class="row"> - <div class="three columns info-name">Name</div> - <div class="nine columns info-value" data-bind="name"></div> - </div> - - <div class="row"> - <div class="three columns info-name">OS</div> - <div class="nine columns info-value" data-bind="os"></div> - </div> - - <div class="row"> - <div class="three columns info-name">Architecture</div> - <div class="nine columns info-value" data-bind="arch"></div> - </div> - - <div class="row"> - <div class="three columns info-name">Server Name</div> - <div class="nine columns info-value" data-bind="serverName"></div> - </div> - - <div class="row"> - <div class="three columns info-name">Server Version</div> - <div class="nine columns info-value" data-bind="serverVersion"></div> - </div> - - <div class="row"> - <div class="three columns info-name">Protocol Version</div> - <div class="nine columns info-value" data-bind="protocolVersion"></div> - </div> - - <div class="row"> - <div class="three columns info-name">Control Protocol Version</div> - <div class="nine columns info-value" data-bind="controlProtocolVersion"></div> - </div> - </div> - - <div class="row snapcast-form-bottom"> - <div class="six columns offset-by-six forms-btns"> - <input type="button" class="btn-default" data-dismiss-modal="#snapcast-host-modal" - value="Close"> - </div> - </div> - </form> - </div> - </div> -</div> - -<div id="snapcast-group-modal" class="modal snapcast-modal"> - <div class="modal-container"> - <div class="modal-header"></div> - <div class="modal-body"> - <form id="snapcast-group-form" class="snapcast-form" action="#"> - <div class="row snapcast-group-clients-container"> - <div class="row snapcast-group-clients"></div> - </div> - <div class="row snapcast-group-stream"> - <select name="stream"></select> - <label for="stream">Stream</label> - </div> - <div class="row snapcast-form-bottom"> - <div class="six columns offset-by-six forms-btns"> - <input type="button" class="btn-default" data-dismiss-modal="#snapcast-group-modal" - value="Close"> - <input type="submit" class="btn-primary" value="Save"> - </div> - </div> - </form> - </div> - </div> -</div> - -<div id="snapcast-client-modal" class="modal snapcast-modal"> - <div class="modal-container"> - <div class="modal-header"></div> - <div class="modal-body"> - <form id="snapcast-client-form" class="snapcast-form" action="#"> - <div class="row form-row"> - <div class="two columns"> - <label for="name">Name</label> - </div> - <div class="ten columns"> - <input type="text" name="name"> - </div> - </div> - - <div class="row snapcast-client-info"> - <div class="row"> - <div class="three columns info-name">IP Address</div> - <div class="nine columns info-value" data-bind="ip"></div> - </div> - - <div class="row"> - <div class="three columns info-name">MAC Address</div> - <div class="nine columns info-value" data-bind="mac"></div> - </div> - - <div class="row"> - <div class="three columns info-name">OS</div> - <div class="nine columns info-value" data-bind="os"></div> - </div> - - <div class="row"> - <div class="three columns info-name">Architecture</div> - <div class="nine columns info-value" data-bind="arch"></div> - </div> - - <div class="row"> - <div class="three columns info-name">Client Name</div> - <div class="nine columns info-value" data-bind="clientName"></div> - </div> - - <div class="row"> - <div class="three columns info-name">Client Version</div> - <div class="nine columns info-value" data-bind="clientVersion"></div> - </div> - - <div class="row"> - <div class="three columns info-name">Protocol Version</div> - <div class="nine columns info-value" data-bind="protocolVersion"></div> - </div> - </div> - - <div class="row snapcast-client-delete"> - <input type="checkbox" name="delete"> - <label for="delete">Delete client</label> - </div> - - <div class="row snapcast-form-bottom"> - <div class="six columns offset-by-six forms-btns"> - <input type="button" class="btn-default" data-dismiss-modal="#snapcast-client-modal" - value="Close"> - <input type="submit" class="btn-primary" value="Save"> - </div> - </div> - </form> - </div> - </div> -</div> - -<div id="snapcast-container" class="row"> -</div> - diff --git a/platypush/backend/http/templates/plugins/tts.google.html b/platypush/backend/http/templates/plugins/tts.google.html deleted file mode 100644 index 8e2829c0d..000000000 --- a/platypush/backend/http/templates/plugins/tts.google.html +++ /dev/null @@ -1,19 +0,0 @@ -<script type="text/javascript" src="{{ url_for('static', filename='js/tts.google.js') }}"></script> -<link rel="stylesheet" href="{{ url_for('static', filename='css/tts.css') }}"></script> - -<div class="row" id="tts-container"> - <form action="#" id="tts-form"> - <div class="eight columns"> - <input type="text" name="text" placeholder="Text to say"> - </div> - <div class="two columns"> - <input type="text" name="language" placeholder="Language code"> - </div> - <div class="one column"> - <button type="submit"> - <i class="fa fa-volume-up"></i> - </button> - </div> - </form> -</div> - diff --git a/platypush/backend/http/templates/plugins/tts.google/index.html b/platypush/backend/http/templates/plugins/tts.google/index.html new file mode 100644 index 000000000..e08192d42 --- /dev/null +++ b/platypush/backend/http/templates/plugins/tts.google/index.html @@ -0,0 +1,4 @@ +<script type="text/x-template" id="tmpl-tts-google"> + {% include 'plugins/tts/common.html' %} +</script> + diff --git a/platypush/backend/http/templates/plugins/tts.html b/platypush/backend/http/templates/plugins/tts.html deleted file mode 100644 index 9d00b9fba..000000000 --- a/platypush/backend/http/templates/plugins/tts.html +++ /dev/null @@ -1,19 +0,0 @@ -<script type="text/javascript" src="{{ url_for('static', filename='js/tts.js') }}"></script> -<link rel="stylesheet" href="{{ url_for('static', filename='css/tts.css') }}"></script> - -<div class="row" id="tts-container"> - <form action="#" id="tts-form"> - <div class="eight columns"> - <input type="text" name="phrase" placeholder="Text to say"> - </div> - <div class="two columns"> - <input type="text" name="lang" placeholder="Lang code"> - </div> - <div class="one column"> - <button type="submit"> - <i class="fa fa-volume-up"></i> - </button> - </div> - </form> -</div> - diff --git a/platypush/backend/http/templates/plugins/tts/common.html b/platypush/backend/http/templates/plugins/tts/common.html new file mode 100644 index 000000000..823e2eef2 --- /dev/null +++ b/platypush/backend/http/templates/plugins/tts/common.html @@ -0,0 +1,15 @@ +<div class="row tts-container"> + <form @submit="talk"> + <div class="col-8"> + <input type="text" name="text" placeholder="Text to say"> + </div> + <div class="col-2"> + <input type="text" name="language" placeholder="Language code"> + </div> + <div class="col-1"> + <button type="submit"> + <i class="fa fa-volume-up"></i> + </button> + </div> + </form> +</div> diff --git a/platypush/backend/http/templates/plugins/tts/index.html b/platypush/backend/http/templates/plugins/tts/index.html new file mode 100644 index 000000000..a8de0e8c0 --- /dev/null +++ b/platypush/backend/http/templates/plugins/tts/index.html @@ -0,0 +1,4 @@ +<script type="text/x-template" id="tmpl-tts"> + {% include 'plugins/tts/common.html' %} +</script> + diff --git a/platypush/plugins/tts/__init__.py b/platypush/plugins/tts/__init__.py index cc7eb26a6..ddd32495e 100644 --- a/platypush/plugins/tts/__init__.py +++ b/platypush/plugins/tts/__init__.py @@ -17,17 +17,17 @@ class TtsPlugin(Plugin): self.lang=lang @action - def say(self, phrase, lang=None): + def say(self, text, language=None): """ Say a phrase - :param phrase: Phrase to say - :type phrase: str + :param text: Phrase to say + :type text: str - :param lang: Language code - :type lang: str + :param language: Language code + :type language: str """ - if lang is None: lang=self.lang + if language is None: language=self.lang output = None errors = [] cmd = ['mplayer -ao alsa -really-quiet -noconsolecontrols ' + @@ -35,8 +35,8 @@ class TtsPlugin(Plugin): .format(urllib.parse.urlencode({ 'ie' : 'UTF-8', 'client' : 'tw-ob', - 'tl' : lang, - 'q' : phrase, + 'tl' : language, + 'q' : text, }))] try: diff --git a/platypush/plugins/tts/google.py b/platypush/plugins/tts/google.py index e20a03c1b..d67e1e452 100644 --- a/platypush/plugins/tts/google.py +++ b/platypush/plugins/tts/google.py @@ -18,7 +18,7 @@ class TtsGooglePlugin(Plugin): * **mplayer** - see your distribution docs on how to install the mplayer package """ - def __init__(self, language='en-US', voice='en-US-Wavenet-C', + def __init__(self, language='en-US', voice=None, gender='FEMALE', credentials_file='~/.credentials/platypush/google/platypush-tts.json'): """ :param language: Language code, see https://cloud.google.com/text-to-speech/docs/basics for supported languages @@ -37,9 +37,34 @@ class TtsGooglePlugin(Plugin): super().__init__() self.language = language self.voice = voice + + self.language = self._parse_language(language) + self.voice = self._parse_voice(self.language, voice) self.gender = getattr(texttospeech.enums.SsmlVoiceGender, gender.upper()) os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = os.path.expanduser(credentials_file) + def _parse_language(self, language): + if language is None: + language = self.language or 'en-US' + + if len(language) == 2: + language = language.lower() + if language == 'en': + language = 'en-US' + else: + language += '-' + language.upper() + + return language + + @staticmethod + def _parse_voice(language, voice): + if voice is not None: + return voice + + if language == 'en-US': + return language + '-Wavenet-C' + return language + '-Wavenet-A' + @action def say(self, text, language=None, voice=None, gender=None): """ @@ -61,17 +86,14 @@ class TtsGooglePlugin(Plugin): client = texttospeech.TextToSpeechClient() synthesis_input = texttospeech.types.SynthesisInput(text=text) - if language is None: - language = self.language + language = self._parse_language(language) + voice = self._parse_voice(language, voice) if gender is None: gender = self.gender else: gender = getattr(texttospeech.enums.SsmlVoiceGender, gender.upper()) - if voice is None: - voice = self.voice - voice = texttospeech.types.VoiceSelectionParams( language_code=language, ssml_gender=gender, name=voice)