From 076d766745ae80ea6eb786b5a19f9f33119d06a6 Mon Sep 17 00:00:00 2001
From: Fabio Manganiello <blacklight86@gmail.com>
Date: Tue, 11 Jun 2019 11:53:15 +0200
Subject: [PATCH] New TTS webpanel plugins

---
 .../webpanel/plugins/tts.google/index.scss    |   1 +
 .../source/webpanel/plugins/tts/index.scss    |  32 ++++
 .../static/js/plugins/tts.google/index.js     |  26 +++
 .../http/static/js/plugins/tts/index.js       |  25 +++
 .../http/templates/plugins/music.mpd.html     | 181 ------------------
 .../templates/plugins/music.snapcast.html     | 161 ----------------
 .../http/templates/plugins/tts.google.html    |  19 --
 .../templates/plugins/tts.google/index.html   |   4 +
 .../backend/http/templates/plugins/tts.html   |  19 --
 .../http/templates/plugins/tts/common.html    |  15 ++
 .../http/templates/plugins/tts/index.html     |   4 +
 platypush/plugins/tts/__init__.py             |  16 +-
 platypush/plugins/tts/google.py               |  34 +++-
 13 files changed, 143 insertions(+), 394 deletions(-)
 create mode 120000 platypush/backend/http/static/css/source/webpanel/plugins/tts.google/index.scss
 create mode 100644 platypush/backend/http/static/css/source/webpanel/plugins/tts/index.scss
 create mode 100644 platypush/backend/http/static/js/plugins/tts.google/index.js
 create mode 100644 platypush/backend/http/static/js/plugins/tts/index.js
 delete mode 100644 platypush/backend/http/templates/plugins/music.mpd.html
 delete mode 100644 platypush/backend/http/templates/plugins/music.snapcast.html
 delete mode 100644 platypush/backend/http/templates/plugins/tts.google.html
 create mode 100644 platypush/backend/http/templates/plugins/tts.google/index.html
 delete mode 100644 platypush/backend/http/templates/plugins/tts.html
 create mode 100644 platypush/backend/http/templates/plugins/tts/common.html
 create mode 100644 platypush/backend/http/templates/plugins/tts/index.html

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>&nbsp;
-            <input type="range" min="0" id="track-seeker" disabled="disabled" class="slider" style="width:75%">
-            &nbsp;<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> &nbsp;
-            <input type="range" min="0" max="100" id="volume-ctrl" class="slider" style="width:80%">
-            &nbsp; <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)