Migrated switches plugin
This commit is contained in:
parent
56f8d85feb
commit
6b5b50d186
19 changed files with 264 additions and 61 deletions
2
platypush/backend/http/dist/index.html
vendored
2
platypush/backend/http/dist/index.html
vendored
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/css/chunk-4d5b9580.e89f1fe8.css
vendored
Normal file
1
platypush/backend/http/dist/static/css/chunk-4d5b9580.e89f1fe8.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/app.dff5461f.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/app.dff5461f.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/app.dff5461f.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/app.dff5461f.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-2d0d6b06.c8766943.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-2d0d6b06.c8766943.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0d6b06"],{"742e":function(e,n,t){"use strict";t.r(n);var a=t("7a23");function c(e,n,t,c,o,r){var s=Object(a["z"])("Panel");return Object(a["r"])(),Object(a["e"])(s,{"plugin-name":"tts.google"})}var o=t("3f9c"),r={name:"Tts",components:{Panel:o["a"]}};r.render=c;n["default"]=r}}]);
|
||||
//# sourceMappingURL=chunk-2d0d6b06.c8766943.js.map
|
1
platypush/backend/http/dist/static/js/chunk-2d0d6b06.c8766943.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-2d0d6b06.c8766943.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["webpack:///./src/components/panels/TtsGoogle/Index.vue","webpack:///./src/components/panels/TtsGoogle/Index.vue?cdc9"],"names":["plugin-name","name","components","Panel","render"],"mappings":"uNACE,eAAkC,GAA3BA,cAAY,e,gBAMN,GACbC,KAAM,MACNC,WAAY,CAACC,QAAA,OCNf,EAAOC,OAASA,EAED","file":"static/js/chunk-2d0d6b06.c8766943.js","sourcesContent":["<template>\n <Panel plugin-name=\"tts.google\" />\n</template>\n\n<script>\nimport Panel from \"@/components/panels/Tts/Panel\";\n\nexport default {\n name: \"Tts\",\n components: {Panel}\n}\n</script>\n","import { render } from \"./Index.vue?vue&type=template&id=5ae1fe52\"\nimport script from \"./Index.vue?vue&type=script&lang=js\"\nexport * from \"./Index.vue?vue&type=script&lang=js\"\nscript.render = render\n\nexport default script"],"sourceRoot":""}
|
2
platypush/backend/http/dist/static/js/chunk-2d22495e.ff69ee49.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-2d22495e.ff69ee49.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22495e"],{e184:function(e,n,t){"use strict";t.r(n);var a=t("7a23");function c(e,n,t,c,r,o){var s=Object(a["z"])("Panel");return Object(a["r"])(),Object(a["e"])(s,{"plugin-name":"tts"})}var r=t("3f9c"),o={name:"Tts",components:{Panel:r["a"]}};o.render=c;n["default"]=o}}]);
|
||||
//# sourceMappingURL=chunk-2d22495e.ff69ee49.js.map
|
1
platypush/backend/http/dist/static/js/chunk-2d22495e.ff69ee49.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-2d22495e.ff69ee49.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["webpack:///./src/components/panels/Tts/Index.vue","webpack:///./src/components/panels/Tts/Index.vue?f452"],"names":["plugin-name","name","components","Panel","render"],"mappings":"qNACE,eAA2B,GAApBA,cAAY,Q,gBAMN,GACbC,KAAM,MACNC,WAAY,CAACC,QAAA,OCNf,EAAOC,OAASA,EAED","file":"static/js/chunk-2d22495e.ff69ee49.js","sourcesContent":["<template>\n <Panel plugin-name=\"tts\" />\n</template>\n\n<script>\nimport Panel from \"@/components/panels/Tts/Panel\";\n\nexport default {\n name: \"Tts\",\n components: {Panel}\n}\n</script>\n","import { render } from \"./Index.vue?vue&type=template&id=4ab66a9e\"\nimport script from \"./Index.vue?vue&type=script&lang=js\"\nexport * from \"./Index.vue?vue&type=script&lang=js\"\nscript.render = render\n\nexport default script"],"sourceRoot":""}
|
2
platypush/backend/http/dist/static/js/chunk-4d5b9580.75a37b61.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-4d5b9580.75a37b61.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4d5b9580"],{"0f21":function(e,t,a){"use strict";a("2c22")},"2c22":function(e,t,a){},"3f9c":function(e,t,a){"use strict";var n=a("7a23"),c=Object(n["K"])("data-v-a248454a");Object(n["u"])("data-v-a248454a");var l={class:"tts-container"},i={class:"field text-container"},r={class:"field lang-container"},u={class:"field buttons"},s=Object(n["h"])("i",{class:"fa fa-volume-up"},null,-1);Object(n["s"])();var d=c((function(e,t,a,c,d,b){return Object(n["r"])(),Object(n["e"])("div",l,[Object(n["h"])("form",{onSubmit:t[1]||(t[1]=Object(n["J"])((function(){return b.talk.apply(b,arguments)}),["prevent"]))},[Object(n["h"])("div",i,[Object(n["h"])("label",null,[Object(n["h"])("input",{type:"text",name:"text",placeholder:"Text to say",disabled:d.talking},null,8,["disabled"])])]),Object(n["h"])("div",r,[Object(n["h"])("label",null,[Object(n["h"])("input",{type:"text",name:"language",placeholder:"Language code",disabled:d.talking},null,8,["disabled"])])]),Object(n["h"])("div",u,[Object(n["h"])("button",{type:"submit",disabled:d.talking},[s],8,["disabled"])])],32)])})),b=(a("13d5"),a("b0c0"),a("2909")),o=(a("96cf"),a("1da1")),p=a("3e54"),f={name:"Panel",mixins:[p["a"]],props:{pluginName:{type:String,required:!0}},data:function(){return{talking:!1}},methods:{talk:function(e){var t=this;return Object(o["a"])(regeneratorRuntime.mark((function a(){var n;return regeneratorRuntime.wrap((function(a){while(1)switch(a.prev=a.next){case 0:return n=Object(b["a"])(e.target.querySelectorAll("input")).reduce((function(e,t){return t.value.length&&(e[t.name]=t.value),e}),{}),t.talking=!0,a.prev=2,a.next=5,t.request("".concat(t.pluginName,".say"),n);case 5:return a.prev=5,t.talking=!1,a.finish(5);case 8:case"end":return a.stop()}}),a,null,[[2,,5,8]])})))()}}};a("0f21");f.render=d,f.__scopeId="data-v-a248454a";t["a"]=f}}]);
|
||||
//# sourceMappingURL=chunk-4d5b9580.75a37b61.js.map
|
1
platypush/backend/http/dist/static/js/chunk-4d5b9580.75a37b61.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-4d5b9580.75a37b61.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["webpack:///./src/components/panels/Tts/Panel.vue?0b35","webpack:///./src/components/panels/Tts/Panel.vue","webpack:///./src/components/panels/Tts/Panel.vue?858f"],"names":["class","talk","type","name","placeholder","disabled","talking","mixins","Utils","props","pluginName","String","required","data","methods","event","args","target","querySelectorAll","reduce","obj","el","value","length","request","render","__scopeId"],"mappings":"kHAAA,W,0JCCOA,MAAM,iB,GAEFA,MAAM,wB,GAKNA,MAAM,wB,GAKNA,MAAM,iB,EAEP,eAA+B,KAA5BA,MAAM,mBAAiB,S,wEAdlC,eAkBM,MAlBN,EAkBM,CAjBJ,eAgBO,QAhBA,SAAM,8CAAU,EAAAC,KAAA,qBAAI,e,CACzB,eAIM,MAJN,EAIM,CAHJ,eAEQ,cADN,eAA6E,SAAtEC,KAAK,OAAOC,KAAK,OAAOC,YAAY,cAAeC,SAAU,EAAAC,S,yBAGxE,eAIM,MAJN,EAIM,CAHJ,eAEQ,cADN,eAAmF,SAA5EJ,KAAK,OAAOC,KAAK,WAAWC,YAAY,gBAAiBC,SAAU,EAAAC,S,yBAG9E,eAIM,MAJN,EAIM,CAHJ,eAES,UAFDJ,KAAK,SAAUG,SAAU,EAAAC,S,CAC/B,G,kGAUK,GACbH,KAAM,QACNI,OAAQ,CAACC,EAAA,MAETC,MAAO,CACLC,WAAY,CACVR,KAAMS,OACNC,UAAU,IAIdC,KAXa,WAYX,MAAO,CACLP,SAAS,IAIbQ,QAAS,CACDb,KADC,SACIc,GAAO,qKACVC,EAAO,eAAID,EAAME,OAAOC,iBAAiB,UAAUC,QAAO,SAACC,EAAKC,GAGpE,OAFIA,EAAGC,MAAMC,SACXH,EAAIC,EAAGlB,MAAQkB,EAAGC,OACbF,IACN,IAEH,EAAKd,SAAU,EAPC,kBASR,EAAKkB,QAAL,UAAgB,EAAKd,WAArB,QAAuCM,GAT/B,uBAWd,EAAKV,SAAU,EAXD,4E,UCtCtB,EAAOmB,OAAS,EAChB,EAAOC,UAAY,kBAEJ","file":"static/js/chunk-4d5b9580.75a37b61.js","sourcesContent":["export * from \"-!../../../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--8-oneOf-1-0!../../../../node_modules/css-loader/dist/cjs.js??ref--8-oneOf-1-1!../../../../node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/stylePostLoader.js!../../../../node_modules/postcss-loader/src/index.js??ref--8-oneOf-1-2!../../../../node_modules/sass-loader/dist/cjs.js??ref--8-oneOf-1-3!../../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../../node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/index.js??ref--0-1!./Panel.vue?vue&type=style&index=0&id=a248454a&lang=scss&scoped=true\"","<template>\n <div class=\"tts-container\">\n <form @submit.prevent=\"talk\">\n <div class=\"field text-container\">\n <label>\n <input type=\"text\" name=\"text\" placeholder=\"Text to say\" :disabled=\"talking\">\n </label>\n </div>\n <div class=\"field lang-container\">\n <label>\n <input type=\"text\" name=\"language\" placeholder=\"Language code\" :disabled=\"talking\">\n </label>\n </div>\n <div class=\"field buttons\">\n <button type=\"submit\" :disabled=\"talking\">\n <i class=\"fa fa-volume-up\"></i>\n </button>\n </div>\n </form>\n </div>\n</template>\n\n<script>\nimport Utils from \"@/Utils\";\n\nexport default {\n name: \"Panel\",\n mixins: [Utils],\n\n props: {\n pluginName: {\n type: String,\n required: true,\n },\n },\n\n data() {\n return {\n talking: false,\n }\n },\n\n methods: {\n async talk(event) {\n const args = [...event.target.querySelectorAll('input')].reduce((obj, el) => {\n if (el.value.length)\n obj[el.name] = el.value\n return obj\n }, {})\n\n this.talking = true\n try {\n await this.request(`${this.pluginName}.say`, args)\n } finally {\n this.talking = false\n }\n },\n },\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.tts-container {\n height: max-content;\n background: $background-color;\n display: flex;\n justify-content: center;\n border: $default-border-3;\n box-shadow: $border-shadow-bottom-right;\n\n @media screen and (max-width: calc(#{$tablet - 1px})) {\n width: 100%;\n }\n\n @media screen and (min-width: $tablet) {\n width: 80%;\n border-radius: 1.5em;\n margin: 1.5em auto;\n }\n\n @media screen and (min-width: $desktop) {\n width: 30em;\n }\n\n form {\n width: 100%;\n border: none;\n box-shadow: none;\n padding: 1em .5em;\n margin: 0;\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n flex-direction: row;\n\n .field {\n margin: 0 .5em;\n }\n\n .text-container {\n width: 100%;\n margin-bottom: 1em;\n }\n\n input[type=text] {\n width: 100%;\n }\n\n button {\n border-radius: 1.5em;\n }\n\n input, button {\n &:hover {\n border-color: $default-hover-fg;\n }\n }\n }\n}\n</style>","import { render } from \"./Panel.vue?vue&type=template&id=a248454a&scoped=true\"\nimport script from \"./Panel.vue?vue&type=script&lang=js\"\nexport * from \"./Panel.vue?vue&type=script&lang=js\"\n\nimport \"./Panel.vue?vue&type=style&index=0&id=a248454a&lang=scss&scoped=true\"\nscript.render = render\nscript.__scopeId = \"data-v-a248454a\"\n\nexport default script"],"sourceRoot":""}
|
|
@ -51,6 +51,12 @@
|
|||
"sound": {
|
||||
"class": "fa fa-microphone"
|
||||
},
|
||||
"tts": {
|
||||
"class": "far fa-comment"
|
||||
},
|
||||
"tts.google": {
|
||||
"class": "fas fa-comment"
|
||||
},
|
||||
"zigbee.mqtt": {
|
||||
"imgUrl": "/icons/zigbee.svg"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<Panel plugin-name="tts" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Panel from "@/components/panels/Tts/Panel";
|
||||
|
||||
export default {
|
||||
name: "Tts",
|
||||
components: {Panel}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<div class="tts-container">
|
||||
<form @submit.prevent="talk">
|
||||
<div class="field text-container">
|
||||
<label>
|
||||
<input type="text" name="text" placeholder="Text to say" :disabled="talking">
|
||||
</label>
|
||||
</div>
|
||||
<div class="field lang-container">
|
||||
<label>
|
||||
<input type="text" name="language" placeholder="Language code" :disabled="talking">
|
||||
</label>
|
||||
</div>
|
||||
<div class="field buttons">
|
||||
<button type="submit" :disabled="talking">
|
||||
<i class="fa fa-volume-up"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Utils from "@/Utils";
|
||||
|
||||
export default {
|
||||
name: "Panel",
|
||||
mixins: [Utils],
|
||||
|
||||
props: {
|
||||
pluginName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
talking: false,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async talk(event) {
|
||||
const args = [...event.target.querySelectorAll('input')].reduce((obj, el) => {
|
||||
if (el.value.length)
|
||||
obj[el.name] = el.value
|
||||
return obj
|
||||
}, {})
|
||||
|
||||
this.talking = true
|
||||
try {
|
||||
await this.request(`${this.pluginName}.say`, args)
|
||||
} finally {
|
||||
this.talking = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tts-container {
|
||||
height: max-content;
|
||||
background: $background-color;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border: $default-border-3;
|
||||
box-shadow: $border-shadow-bottom-right;
|
||||
|
||||
@media screen and (max-width: calc(#{$tablet - 1px})) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $tablet) {
|
||||
width: 80%;
|
||||
border-radius: 1.5em;
|
||||
margin: 1.5em auto;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $desktop) {
|
||||
width: 30em;
|
||||
}
|
||||
|
||||
form {
|
||||
width: 100%;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 1em .5em;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
|
||||
.field {
|
||||
margin: 0 .5em;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
width: 100%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 1.5em;
|
||||
}
|
||||
|
||||
input, button {
|
||||
&:hover {
|
||||
border-color: $default-hover-fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<Panel plugin-name="tts.google" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Panel from "@/components/panels/Tts/Panel";
|
||||
|
||||
export default {
|
||||
name: "Tts",
|
||||
components: {Panel}
|
||||
}
|
||||
</script>
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
import select
|
||||
import subprocess
|
||||
import threading
|
||||
|
@ -186,7 +187,8 @@ class MediaMplayerPlugin(MediaPlugin):
|
|||
last_read_time = time.time()
|
||||
|
||||
if line.startswith('ANS_'):
|
||||
k, v = tuple(line[4:].split('='))
|
||||
m = re.match('^([^=]+)=(.*)$', line[4:])
|
||||
k, v = m.group(1), m.group(2)
|
||||
v = v.strip()
|
||||
if v == 'yes':
|
||||
v = True
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import subprocess
|
||||
import urllib.parse
|
||||
from typing import Optional, List
|
||||
from typing import Optional
|
||||
|
||||
from platypush.config import Config
|
||||
from platypush.context import get_plugin
|
||||
from platypush.plugins import Plugin, action
|
||||
from platypush.plugins.media import MediaPlugin
|
||||
|
||||
|
||||
class TtsPlugin(Plugin):
|
||||
|
@ -11,46 +13,67 @@ class TtsPlugin(Plugin):
|
|||
|
||||
Requires:
|
||||
|
||||
* **mplayer** - see your distribution docs on how to install the mplayer package
|
||||
* At least a *media plugin* (see :class:`platypush.plugins.media.MediaPlugin`) enabled/configured - used for
|
||||
speech playback.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, language='en-gb', player_args: Optional[List[str]] = None):
|
||||
_supported_media_plugins = [
|
||||
'media.omxplayer',
|
||||
'media.gstreamer',
|
||||
'media.mplayer',
|
||||
'media.mpv',
|
||||
'media.vlc',
|
||||
]
|
||||
|
||||
def __init__(self, language='en-gb', media_plugin: Optional[str] = None, player_args: Optional[dict] = None):
|
||||
"""
|
||||
:param language: Language code (default: ``en-gb``).
|
||||
:param player_args: Extra options to be passed to the audio player (default: ``mplayer``).
|
||||
: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__()
|
||||
self.language = language
|
||||
self.player_args = player_args or []
|
||||
self.player_args = player_args or {}
|
||||
self.media_plugin = get_plugin(media_plugin) if media_plugin else self._get_media_plugin()
|
||||
assert self.media_plugin, 'No media playback plugin configured. Supported plugins: [{}]'.format(
|
||||
', '.join(self._supported_media_plugins))
|
||||
|
||||
@classmethod
|
||||
def _get_media_plugin(cls) -> Optional[MediaPlugin]:
|
||||
for plugin in cls._supported_media_plugins:
|
||||
if plugin in Config.get():
|
||||
return get_plugin(plugin)
|
||||
|
||||
@action
|
||||
def say(self, text: str, language: Optional[str] = None, player_args: Optional[List[str]] = None):
|
||||
def say(self, text: str, language: Optional[str] = None, player_args: Optional[dict] = None):
|
||||
"""
|
||||
Say some text.
|
||||
|
||||
:param text: Text to say.
|
||||
:param language: Language code override.
|
||||
:param player_args: ``player_args`` override.
|
||||
:param player_args: Optional arguments that should be passed to the player plugin's
|
||||
:meth:`platypush.plugins.media.MediaPlugin.play` method.
|
||||
"""
|
||||
language = language or self.language
|
||||
player_args = player_args or self.player_args
|
||||
cmd = [
|
||||
'mplayer -ao alsa -really-quiet -noconsolecontrols ' +
|
||||
' '.join(player_args) + ' ' +
|
||||
'"http://translate.google.com/translate_tts?{}"'.format(
|
||||
urllib.parse.urlencode({
|
||||
'ie': 'UTF-8',
|
||||
'client': 'tw-ob',
|
||||
'tl': language,
|
||||
'q': text,
|
||||
})
|
||||
)
|
||||
]
|
||||
url = 'http://translate.google.com/translate_tts?{}'.format(
|
||||
urllib.parse.urlencode({
|
||||
'ie': 'UTF-8',
|
||||
'client': 'tw-ob',
|
||||
'tl': language,
|
||||
'q': text,
|
||||
}))
|
||||
|
||||
self.media_plugin.play(url, **player_args)
|
||||
|
||||
try:
|
||||
return subprocess.check_output(
|
||||
cmd, stderr=subprocess.STDOUT, shell=True).decode('utf-8')
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(e.output.decode('utf-8'))
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
from typing import Optional, List
|
||||
from typing import Optional
|
||||
|
||||
from platypush.plugins import action
|
||||
from platypush.plugins.tts import TtsPlugin
|
||||
|
@ -17,31 +16,29 @@ class TtsGooglePlugin(TtsPlugin):
|
|||
|
||||
* **google-cloud-texttospeech** - ``pip install google-cloud-texttospeech``
|
||||
* **mplayer** - see your distribution docs on how to install the mplayer package
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
language: str ='en-US',
|
||||
language: str = 'en-US',
|
||||
voice: Optional[str] = None,
|
||||
gender: str = 'FEMALE',
|
||||
credentials_file: str = '~/.credentials/platypush/google/platypush-tts.json',
|
||||
player_args: Optional[List[str]] = None):
|
||||
**kwargs):
|
||||
"""
|
||||
:param language: Language code, see https://cloud.google.com/text-to-speech/docs/basics for supported languages
|
||||
:param voice: Voice type, see https://cloud.google.com/text-to-speech/docs/basics for supported voices
|
||||
:param gender: Voice gender (MALE, FEMALE or NEUTRAL)
|
||||
:param credentials_file: Where your GCloud credentials for TTS are stored, see https://cloud.google.com/text-to-speech/docs/basics
|
||||
:param player_args: Extra options to be passed to the audio player (default: ``mplayer``).
|
||||
:param kwargs: Extra arguments to be passed to the :class:`platypush.plugins.tts.TtsPlugin` constructor.
|
||||
"""
|
||||
from google.cloud import texttospeech
|
||||
super().__init__()
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.language = language
|
||||
self.voice = voice
|
||||
self.player_args = player_args or []
|
||||
|
||||
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(self._gender, gender.upper())
|
||||
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = os.path.expanduser(credentials_file)
|
||||
|
||||
def _parse_language(self, language):
|
||||
|
@ -66,13 +63,43 @@ class TtsGooglePlugin(TtsPlugin):
|
|||
return language + '-Wavenet-C'
|
||||
return language + '-Wavenet-A'
|
||||
|
||||
@property
|
||||
def _gender(self):
|
||||
from google.cloud import texttospeech
|
||||
return texttospeech.enums.SsmlVoiceGender if hasattr(texttospeech, 'enums') else \
|
||||
texttospeech.SsmlVoiceGender
|
||||
|
||||
@property
|
||||
def _voice_selection_params(self):
|
||||
from google.cloud import texttospeech
|
||||
return texttospeech.types.VoiceSelectionParams if hasattr(texttospeech, 'types') else \
|
||||
texttospeech.VoiceSelectionParams
|
||||
|
||||
@property
|
||||
def _synthesis_input(self):
|
||||
from google.cloud import texttospeech
|
||||
return texttospeech.types.SynthesisInput if hasattr(texttospeech, 'types') else \
|
||||
texttospeech.SynthesisInput
|
||||
|
||||
@property
|
||||
def _audio_config(self):
|
||||
from google.cloud import texttospeech
|
||||
return texttospeech.types.AudioConfig if hasattr(texttospeech, 'types') else \
|
||||
texttospeech.AudioConfig
|
||||
|
||||
@property
|
||||
def _audio_encoding(self):
|
||||
from google.cloud import texttospeech
|
||||
return texttospeech.enums.AudioEncoding if hasattr(texttospeech, 'enums') else \
|
||||
texttospeech.AudioEncoding
|
||||
|
||||
@action
|
||||
def say(self,
|
||||
text: str,
|
||||
language: Optional[str] = None,
|
||||
voice: Optional[str] = None,
|
||||
gender: Optional[str] = None,
|
||||
player_args: Optional[List[str]] = None):
|
||||
player_args: Optional[dict] = None):
|
||||
"""
|
||||
Say a phrase.
|
||||
|
||||
|
@ -80,12 +107,14 @@ class TtsGooglePlugin(TtsPlugin):
|
|||
:param language: Language code override.
|
||||
:param voice: Voice type override.
|
||||
:param gender: Gender override.
|
||||
:param player_args: Player args override.
|
||||
:param player_args: Optional arguments that should be passed to the player plugin's
|
||||
:meth:`platypush.plugins.media.MediaPlugin.play` method.
|
||||
"""
|
||||
|
||||
from google.cloud import texttospeech
|
||||
client = texttospeech.TextToSpeechClient()
|
||||
synthesis_input = texttospeech.types.SynthesisInput(text=text)
|
||||
# noinspection PyTypeChecker
|
||||
synthesis_input = self._synthesis_input(text=text)
|
||||
|
||||
language = self._parse_language(language)
|
||||
voice = self._parse_voice(language, voice)
|
||||
|
@ -93,28 +122,17 @@ class TtsGooglePlugin(TtsPlugin):
|
|||
if gender is None:
|
||||
gender = self.gender
|
||||
else:
|
||||
gender = getattr(texttospeech.enums.SsmlVoiceGender, gender.upper())
|
||||
gender = getattr(self._gender, gender.upper())
|
||||
|
||||
player_args = player_args or self.player_args
|
||||
voice = texttospeech.types.VoiceSelectionParams(
|
||||
language_code=language, ssml_gender=gender,
|
||||
name=voice)
|
||||
|
||||
audio_config = texttospeech.types.AudioConfig(
|
||||
audio_encoding=texttospeech.enums.AudioEncoding.MP3)
|
||||
|
||||
response = client.synthesize_speech(synthesis_input, voice, audio_config)
|
||||
voice = self._voice_selection_params(language_code=language, ssml_gender=gender, name=voice)
|
||||
# noinspection PyTypeChecker
|
||||
audio_config = self._audio_config(audio_encoding=self._audio_encoding.MP3)
|
||||
response = client.synthesize_speech(input=synthesis_input, voice=voice, audio_config=audio_config)
|
||||
player_args = player_args or {}
|
||||
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
f.write(response.audio_content)
|
||||
cmd = ['mplayer -ao alsa -really-quiet -noconsolecontrols {} "{}"'.format(
|
||||
' '.join(player_args), f.name)]
|
||||
|
||||
try:
|
||||
return subprocess.check_output(
|
||||
cmd, stderr=subprocess.STDOUT, shell=True).decode('utf-8')
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(e.output.decode('utf-8'))
|
||||
self.media_plugin.play(f.name, **player_args)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
Loading…
Reference in a new issue