forked from platypush/platypush
New TTS webpanel plugins
This commit is contained in:
parent
205903bc40
commit
076d766745
13 changed files with 143 additions and 394 deletions
|
@ -0,0 +1 @@
|
|||
../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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
26
platypush/backend/http/static/js/plugins/tts.google/index.js
Normal file
26
platypush/backend/http/static/js/plugins/tts.google/index.js
Normal 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;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
25
platypush/backend/http/static/js/plugins/tts/index.js
Normal file
25
platypush/backend/http/static/js/plugins/tts/index.js
Normal 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;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<script type="text/x-template" id="tmpl-tts-google">
|
||||
{% include 'plugins/tts/common.html' %}
|
||||
</script>
|
||||
|
|
@ -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>
|
||||
|
15
platypush/backend/http/templates/plugins/tts/common.html
Normal file
15
platypush/backend/http/templates/plugins/tts/common.html
Normal 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>
|
4
platypush/backend/http/templates/plugins/tts/index.html
Normal file
4
platypush/backend/http/templates/plugins/tts/index.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<script type="text/x-template" id="tmpl-tts">
|
||||
{% include 'plugins/tts/common.html' %}
|
||||
</script>
|
||||
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue