forked from platypush/platypush
Added tts.picovoice
plugin.
This commit is contained in:
parent
a4c911a5d7
commit
af875c996e
2 changed files with 160 additions and 0 deletions
138
platypush/plugins/tts/picovoice/__init__.py
Normal file
138
platypush/plugins/tts/picovoice/__init__.py
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
import os
|
||||||
|
from threading import RLock
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pvorca
|
||||||
|
import sounddevice as sd
|
||||||
|
|
||||||
|
from platypush.config import Config
|
||||||
|
from platypush.plugins import action
|
||||||
|
from platypush.plugins.tts import TtsPlugin
|
||||||
|
|
||||||
|
|
||||||
|
class TtsPicovoicePlugin(TtsPlugin):
|
||||||
|
"""
|
||||||
|
This TTS plugin enables you to render text as audio using `Picovoice
|
||||||
|
<https://picovoice.ai>`_'s (still experimental) `Orca TTS engine
|
||||||
|
<https://github.com/Picovoice/orca>`_.
|
||||||
|
|
||||||
|
Take a look at
|
||||||
|
:class:`platypush.plugins.assistant.picovoice.AssistantPicovoicePlugin`
|
||||||
|
for details on how to sign up for a Picovoice account and get the API key.
|
||||||
|
|
||||||
|
Also note that using the TTS features requires you to select Orca from the
|
||||||
|
list of products available for your account on the `Picovoice console
|
||||||
|
<https://console.picovoice.ai>`_.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
access_key: Optional[str] = None,
|
||||||
|
model_path: Optional[str] = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
:param access_key: Picovoice access key. If it's not specified here,
|
||||||
|
then it must be specified on the configuration of
|
||||||
|
:class:`platypush.plugins.assistant.picovoice.AssistantPicovoicePlugin`.
|
||||||
|
:param model_path: Path of the TTS model file (default: use the default
|
||||||
|
English model).
|
||||||
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
if not access_key:
|
||||||
|
access_key = Config.get('assistant.picovoice', {}).get('access_key')
|
||||||
|
assert (
|
||||||
|
access_key
|
||||||
|
), 'No access key specified and no assistant.picovoice plugin found'
|
||||||
|
|
||||||
|
self.model_path = model_path
|
||||||
|
self.access_key = access_key
|
||||||
|
if model_path:
|
||||||
|
model_path = os.path.expanduser(model_path)
|
||||||
|
|
||||||
|
self._stream: Optional[sd.OutputStream] = None
|
||||||
|
self._stream_lock = RLock()
|
||||||
|
|
||||||
|
def _play_audio(self, orca: pvorca.Orca, pcm: np.ndarray):
|
||||||
|
with self._stream_lock:
|
||||||
|
self.stop()
|
||||||
|
self._stream = sd.OutputStream(
|
||||||
|
samplerate=orca.sample_rate,
|
||||||
|
channels=1,
|
||||||
|
dtype='int16',
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._stream.start()
|
||||||
|
self._stream.write(pcm)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning('Error playing audio: %s: %s', type(e), str(e))
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
self.stop()
|
||||||
|
self._stream.close()
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'Error stopping audio stream: %s: %s', type(e), str(e)
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
if self._stream:
|
||||||
|
self._stream = None
|
||||||
|
|
||||||
|
def get_orca(self, model_path: Optional[str] = None):
|
||||||
|
if not model_path:
|
||||||
|
model_path = self.model_path
|
||||||
|
if model_path:
|
||||||
|
model_path = os.path.expanduser(model_path)
|
||||||
|
|
||||||
|
return pvorca.create(access_key=self.access_key, model_path=model_path)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def say(
|
||||||
|
self,
|
||||||
|
text: str,
|
||||||
|
*_,
|
||||||
|
output_file: Optional[str] = None,
|
||||||
|
speech_rate: Optional[float] = None,
|
||||||
|
model_path: Optional[str] = None,
|
||||||
|
**__,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Say some text.
|
||||||
|
|
||||||
|
:param text: Text to say.
|
||||||
|
:param output_file: If set, save the audio to the specified file.
|
||||||
|
Otherwise play it.
|
||||||
|
:param speech_rate: Speech rate (default: None).
|
||||||
|
:param model_path: Path of the TTS model file (default: use the default
|
||||||
|
configured model).
|
||||||
|
"""
|
||||||
|
orca = self.get_orca(model_path=model_path)
|
||||||
|
if output_file:
|
||||||
|
orca.synthesize_to_file(
|
||||||
|
text, os.path.expanduser(output_file), speech_rate=speech_rate
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._play_audio(
|
||||||
|
orca=orca,
|
||||||
|
pcm=np.array(
|
||||||
|
orca.synthesize(text, speech_rate=speech_rate),
|
||||||
|
dtype='int16',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def stop(self):
|
||||||
|
"""
|
||||||
|
Stop the currently playing audio.
|
||||||
|
"""
|
||||||
|
with self._stream_lock:
|
||||||
|
if not self._stream:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._stream.stop()
|
||||||
|
|
||||||
|
|
||||||
|
# vim:sw=4:ts=4:et:
|
22
platypush/plugins/tts/picovoice/manifest.yaml
Normal file
22
platypush/plugins/tts/picovoice/manifest.yaml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
manifest:
|
||||||
|
events: {}
|
||||||
|
install:
|
||||||
|
apk:
|
||||||
|
- ffmpeg
|
||||||
|
- py3-numpy
|
||||||
|
apt:
|
||||||
|
- ffmpeg
|
||||||
|
- python3-numpy
|
||||||
|
dnf:
|
||||||
|
- ffmpeg
|
||||||
|
- python-numpy
|
||||||
|
pacman:
|
||||||
|
- ffmpeg
|
||||||
|
- python-numpy
|
||||||
|
- python-sounddevice
|
||||||
|
pip:
|
||||||
|
- numpy
|
||||||
|
- pvorca
|
||||||
|
- sounddevice
|
||||||
|
package: platypush.plugins.tts.picovoice
|
||||||
|
type: plugin
|
Loading…
Reference in a new issue