New TTS webpanel plugins

This commit is contained in:
Fabio Manganiello 2019-06-11 11:53:15 +02:00
parent 205903bc40
commit 076d766745
13 changed files with 143 additions and 394 deletions

View file

@ -0,0 +1 @@
../tts/index.scss

View file

@ -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;
}
}
}
}

View file

@ -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;
},
},
});

View file

@ -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;
},
},
});

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,4 @@
<script type="text/x-template" id="tmpl-tts-google">
{% include 'plugins/tts/common.html' %}
</script>

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,4 @@
<script type="text/x-template" id="tmpl-tts">
{% include 'plugins/tts/common.html' %}
</script>

View file

@ -17,17 +17,17 @@ class TtsPlugin(Plugin):
self.lang=lang self.lang=lang
@action @action
def say(self, phrase, lang=None): def say(self, text, language=None):
""" """
Say a phrase Say a phrase
:param phrase: Phrase to say :param text: Phrase to say
:type phrase: str :type text: str
:param lang: Language code :param language: Language code
:type lang: str :type language: str
""" """
if lang is None: lang=self.lang if language is None: language=self.lang
output = None output = None
errors = [] errors = []
cmd = ['mplayer -ao alsa -really-quiet -noconsolecontrols ' + cmd = ['mplayer -ao alsa -really-quiet -noconsolecontrols ' +
@ -35,8 +35,8 @@ class TtsPlugin(Plugin):
.format(urllib.parse.urlencode({ .format(urllib.parse.urlencode({
'ie' : 'UTF-8', 'ie' : 'UTF-8',
'client' : 'tw-ob', 'client' : 'tw-ob',
'tl' : lang, 'tl' : language,
'q' : phrase, 'q' : text,
}))] }))]
try: try:

View file

@ -18,7 +18,7 @@ class TtsGooglePlugin(Plugin):
* **mplayer** - see your distribution docs on how to install the mplayer package * **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'): 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 :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__() super().__init__()
self.language = language self.language = language
self.voice = voice 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()) self.gender = getattr(texttospeech.enums.SsmlVoiceGender, gender.upper())
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = os.path.expanduser(credentials_file) 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 @action
def say(self, text, language=None, voice=None, gender=None): def say(self, text, language=None, voice=None, gender=None):
""" """
@ -61,17 +86,14 @@ class TtsGooglePlugin(Plugin):
client = texttospeech.TextToSpeechClient() client = texttospeech.TextToSpeechClient()
synthesis_input = texttospeech.types.SynthesisInput(text=text) synthesis_input = texttospeech.types.SynthesisInput(text=text)
if language is None: language = self._parse_language(language)
language = self.language voice = self._parse_voice(language, voice)
if gender is None: if gender is None:
gender = self.gender gender = self.gender
else: else:
gender = getattr(texttospeech.enums.SsmlVoiceGender, gender.upper()) gender = getattr(texttospeech.enums.SsmlVoiceGender, gender.upper())
if voice is None:
voice = self.voice
voice = texttospeech.types.VoiceSelectionParams( voice = texttospeech.types.VoiceSelectionParams(
language_code=language, ssml_gender=gender, language_code=language, ssml_gender=gender,
name=voice) name=voice)