speechd-dispatcher example

This commit is contained in:
Michael Hansen 2022-03-22 15:45:36 -04:00
commit ef85fc4c5c
5 changed files with 195 additions and 7 deletions

View file

@ -0,0 +1,9 @@
Dorothy lived in the midst of the great Kansas prairies, with Uncle Henry, who was a farmer, and Aunt Em, who was the farmers wife. Their house was small, for the lumber to build it had to be carried by wagon many miles. There were four walls, a floor and a roof, which made one room; and this room contained a rusty looking cookstove, a cupboard for the dishes, a table, three or four chairs, and the beds. Uncle Henry and Aunt Em had a big bed in one corner, and Dorothy a little bed in another corner. There was no garret at all, and no cellar—except a small hole dug in the ground, called a cyclone cellar, where the family could go in case one of those great whirlwinds arose, mighty enough to crush any building in its path. It was reached by a trap door in the middle of the floor, from which a ladder led down into the small, dark hole.
When Dorothy stood in the doorway and looked around, she could see nothing but the great gray prairie on every side. Not a tree nor a house broke the broad sweep of flat country that reached to the edge of the sky in all directions. The sun had baked the plowed land into a gray mass, with little cracks running through it. Even the grass was not green, for the sun had burned the tops of the long blades until they were the same gray color to be seen everywhere. Once the house had been painted, but the sun blistered the paint and the rains washed it away, and now the house was as dull and gray as everything else.
When Aunt Em came there to live she was a young, pretty wife. The sun and wind had changed her, too. They had taken the sparkle from her eyes and left them a sober gray; they had taken the red from her cheeks and lips, and they were gray also. She was thin and gaunt, and never smiled now. When Dorothy, who was an orphan, first came to her, Aunt Em had been so startled by the childs laughter that she would scream and press her hand upon her heart whenever Dorothys merry voice reached her ears; and she still looked at the little girl with wonder that she could find anything to laugh at.
Uncle Henry never laughed. He worked hard from morning till night and did not know what joy was. He was gray also, from his long beard to his rough boots, and he looked stern and solemn, and rarely spoke.
It was Toto that made Dorothy laugh, and saved her from growing as gray as her other surroundings. Toto was not gray; he was a little black dog, with long silky hair and small black eyes that twinkled merrily on either side of his funny, wee nose. Toto played all day long, and Dorothy played with him, and loved him dearly

20
mimic3-http/client.sh Executable file
View file

@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -eo pipefail
# Directory of *this* script
this_dir="$( cd "$( dirname "$0" )" && pwd )"
# Kebab to snake case
module_name="$(basename "${this_dir}" | sed -e 's/-/_/g')"
src_dir="${this_dir}/${module_name}"
# Path to virtual environment
: "${venv:=${this_dir}/.venv}"
if [ -d "${venv}" ]; then
# Activate virtual environment if available
source "${venv}/bin/activate"
fi
export PYTHONPATH="${this_dir}"
python3 -m "${module_name}.client" "$@"

View file

@ -18,7 +18,7 @@ import asyncio
import dataclasses
import io
import logging
import sys
import hashlib
import tempfile
import typing
import wave
@ -64,13 +64,19 @@ parser.add_argument(
)
parser.add_argument("--speaker", type=int, help="Default speaker to use (name or id)")
parser.add_argument(
"--length-scale", type=float, default=1.0, help="Speed of speech (> 1 is slower)"
"--noise-scale",
type=float,
help="Noise scale [0-1], default is 0.667",
)
parser.add_argument(
"--noise-scale", type=float, default=0.333, help="Noise source for audio (0-1)"
"--length-scale",
type=float,
help="Length scale (1.0 is default speed, 0.5 is 2x faster)",
)
parser.add_argument(
"--noise-w", type=float, default=1.0, help="Variation in cadence (0-1)"
"--noise-w",
type=float,
help="Variation in cadence [0-1], default is 0.8",
)
parser.add_argument(
"--cache-dir",
@ -121,6 +127,10 @@ class TextToWavParams:
ssml: bool = False
text_language: typing.Optional[str] = None
@property
def cache_key(self) -> str:
return hashlib.md5(repr(self).encode()).hexdigest()
# params -> Path
_WAV_CACHE: typing.Dict[TextToWavParams, Path] = {}
@ -157,7 +167,7 @@ def text_to_wav(params: TextToWavParams, no_cache: bool = False) -> bytes:
if _TEMP_DIR and (not no_cache):
# Look up in cache
maybe_wav_path = _TEMP_DIR / f"{hash(params)}.wav"
maybe_wav_path = _TEMP_DIR / f"{params.cache_key}.wav"
if maybe_wav_path.is_file():
_LOGGER.debug("Loading WAV from cache: %s", maybe_wav_path)
wav_bytes = maybe_wav_path.read_bytes()
@ -190,7 +200,16 @@ def text_to_wav(params: TextToWavParams, no_cache: bool = False) -> bytes:
wav_file.writeframes(result.audio_bytes)
return wav_io.getvalue()
wav_bytes = wav_io.getvalue()
if _TEMP_DIR and (not no_cache):
# Store in cache
wav_path = _TEMP_DIR / f"{params.cache_key}.wav"
wav_path.write_bytes(wav_bytes)
_LOGGER.debug("Cached WAV at %s", wav_path.absolute())
return wav_bytes
# -----------------------------------------------------------------------------
@ -239,7 +258,7 @@ async def app_tts() -> Response:
"""Speak text to WAV."""
tts_args: typing.Dict[str, typing.Any] = {}
_LOGGER.debug(request.args)
_LOGGER.debug("Request args: %s", request.args)
voice = request.args.get("voice")
if voice is not None:
@ -258,9 +277,12 @@ async def app_tts() -> Response:
if length_scale is not None:
tts_args["length_scale"] = float(length_scale)
# Set SSML flag either from arg or content type
ssml_str = request.args.get("ssml")
if ssml_str is not None:
tts_args["ssml"] = _to_bool(ssml_str)
elif request.content_type == "application/ssml+xml":
tts_args["ssml"] = True
text_language = request.args.get("textLanguage")
if text_language is not None:

View file

@ -0,0 +1,135 @@
#!/usr/bin/env python3
import argparse
import logging
import os
import sys
import tempfile
import typing
from pathlib import Path
import requests
_PACKAGE = "mimic3_http.client"
_LOGGER = logging.getLogger(_PACKAGE)
# -----------------------------------------------------------------------------
def main():
args = get_args()
if args.output:
args.output = Path(args.output)
args.output.parent.mkdir(parents=True, exist_ok=True)
if args.ssml:
headers = {"Content-Type": "application/ssml+xml"}
else:
headers = {"Content-Type": "text/plain"}
params: typing.Dict[str, str] = {}
if args.voice:
params["voice"] = args.voice
if args.length_scale:
params["lengthScale"] = args.length_scale
if args.noise_scale:
params["noiseScale"] = args.noise_scale
if args.noise_w:
params["noiseW"] = args.noise_w
if args.text:
data = "\n".join(args.text)
else:
if os.isatty(sys.stdin.fileno()):
print("Reading text from stdin...", file=sys.stderr)
data = sys.stdin.read()
wav_bytes = requests.post(
args.url, headers=headers, params=params, data=data
).content
if args.output:
args.output.write_bytes(wav_bytes)
_LOGGER.info("Wrote WAV data to %s", args.output)
elif args.stdout:
_LOGGER.debug("Writing WAV data to stdout")
sys.stdout.buffer.write(wav_bytes)
else:
from playsound import playsound
with tempfile.NamedTemporaryFile(mode="wb+", suffix=".wav") as wav_file:
wav_file.write(wav_bytes)
wav_file.seek(0)
_LOGGER.debug("Playing WAV file: %s", wav_file.name)
playsound(wav_file.name)
# -----------------------------------------------------------------------------
def get_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(prog=_PACKAGE)
parser.add_argument(
"text", nargs="*", help="Text to convert to speech (default: stdin)"
)
parser.add_argument(
"--url",
"-u",
default="http://localhost:59125/api/tts",
help="URL of mimic3 HTTP server (default: http://localhost:59125/api/tts)",
)
parser.add_argument(
"--voice",
"-v",
help="Name of voice (expected in <voices-dir>/<language>)",
)
parser.add_argument(
"--output",
"-o",
help="Path to write WAV file (default: play audio)",
)
parser.add_argument(
"--stdout",
action="store_true",
help="Write WAV data to stdout",
)
parser.add_argument(
"--noise-scale",
type=float,
help="Noise scale [0-1], default is 0.667",
)
parser.add_argument(
"--length-scale",
type=float,
help="Length scale (1.0 is default speed, 0.5 is 2x faster)",
)
parser.add_argument(
"--noise-w",
type=float,
help="Variation in cadence [0-1], default is 0.8",
)
parser.add_argument("--ssml", action="store_true", help="Input text is SSML")
parser.add_argument(
"--debug", action="store_true", help="Print DEBUG messages to the console"
)
args = parser.parse_args()
if args.debug:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
return args
# -----------------------------------------------------------------------------
if __name__ == "__main__":
main()

View file

@ -1,3 +1,5 @@
mimic3-tts<1.0
playsound~=1.3.0
quart>=0.16,<1.0
quart-cors
requests>=2,<3