Merge mimic3-client into main CLI
This commit is contained in:
parent
d55515b846
commit
4a738b0cba
19 changed files with 153 additions and 346 deletions
|
|
@ -3,7 +3,6 @@
|
||||||
!install.sh
|
!install.sh
|
||||||
!pyinstaller/*.py
|
!pyinstaller/*.py
|
||||||
!pyinstaller/mimic3-server
|
!pyinstaller/mimic3-server
|
||||||
!pyinstaller/mimic3-client
|
|
||||||
!pyinstaller/mimic3-download
|
!pyinstaller/mimic3-download
|
||||||
|
|
||||||
# opentts-abc
|
# opentts-abc
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,6 @@ RUN find dist/mimic3/ -wholename '*/gruut_lang_*/espeak' -type d | \
|
||||||
|
|
||||||
# Copy convenience scripts
|
# Copy convenience scripts
|
||||||
COPY pyinstaller/mimic3-server \
|
COPY pyinstaller/mimic3-server \
|
||||||
pyinstaller/mimic3-client \
|
|
||||||
pyinstaller/mimic3-download \
|
pyinstaller/mimic3-download \
|
||||||
dist/mimic3/
|
dist/mimic3/
|
||||||
|
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -76,7 +76,6 @@ pip install mimic3-http[all]
|
||||||
|
|
||||||
Once installed, the following commands will be available:
|
Once installed, the following commands will be available:
|
||||||
* `mimic3-server`
|
* `mimic3-server`
|
||||||
* `mimic3-client`
|
|
||||||
|
|
||||||
Language support can be selectively installed by replacing `all` with:
|
Language support can be selectively installed by replacing `all` with:
|
||||||
|
|
||||||
|
|
@ -111,7 +110,6 @@ A virtual environment will be created in `mimic3/.venv` and each of the Python m
|
||||||
Once installed, the following commands will be available in `.venv/bin`:
|
Once installed, the following commands will be available in `.venv/bin`:
|
||||||
* `mimic3`
|
* `mimic3`
|
||||||
* `mimic3-server`
|
* `mimic3-server`
|
||||||
* `mimic3-client`
|
|
||||||
* `mimic3-download`
|
* `mimic3-download`
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -185,15 +183,17 @@ See `mimic3-server --help` for the [web server documentation](mimic3-http/) for
|
||||||
|
|
||||||
#### Web Client
|
#### Web Client
|
||||||
|
|
||||||
The `mimic3-client` program provides an interface to the Mimic 3 web server that is similar to the `mimic3` command.
|
The `mimic3` program provides an interface to the Mimic 3 web server when the `--remote` option is given.
|
||||||
|
|
||||||
Assuming you have started `mimic3-server` and can access `http://localhost:59125`, then:
|
Assuming you have started `mimic3-server` and can access `http://localhost:59125`, then:
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
mimic3-client --voice 'en_UK/apope_low' 'My hovercraft is full of eels.' > hovercraft_eels.wav
|
mimic3 --remote --voice 'en_UK/apope_low' 'My hovercraft is full of eels.' > hovercraft_eels.wav
|
||||||
```
|
```
|
||||||
|
|
||||||
See `mimic3-client --help` for more options.
|
If your server is somewhere besides `localhost`, use `mimic3 --remote <URL> ...`
|
||||||
|
|
||||||
|
See `mimic3 --help` for more options.
|
||||||
|
|
||||||
|
|
||||||
## CUDA Acceleration
|
## CUDA Acceleration
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Copyright 2022 Mycroft AI Inc.
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
# Run mimic3-client --update to update Docker image
|
|
||||||
|
|
||||||
args=()
|
|
||||||
repo='mycroftai/mimic3'
|
|
||||||
tag='latest'
|
|
||||||
docker='docker'
|
|
||||||
port='59125'
|
|
||||||
|
|
||||||
while [[ -n "$1" ]]; do
|
|
||||||
if [[ "$1" == '--update' ]]; then
|
|
||||||
# Update Docker image
|
|
||||||
update='1'
|
|
||||||
elif [[ "$1" == '--port' ]]; then
|
|
||||||
port="$2"
|
|
||||||
args+=('--port' "${port}")
|
|
||||||
shift 1
|
|
||||||
else
|
|
||||||
args+=("$1")
|
|
||||||
fi
|
|
||||||
|
|
||||||
shift 1
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -n "${update}" ]]; then
|
|
||||||
docker pull "${repo}:${tag}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker_run_args=()
|
|
||||||
|
|
||||||
if [[ -d /etc/ssl/certs ]]; then
|
|
||||||
# This directory seems to usually have symlinks to other directories
|
|
||||||
docker_run_args+=('-v' '/etc/ssl/certs:/etc/ssl/certs:ro')
|
|
||||||
|
|
||||||
# Create temp file with all certificate directories found
|
|
||||||
cert_dirs_file="$(mktemp)"
|
|
||||||
function finish {
|
|
||||||
rm -rf "${cert_dirs_file}"
|
|
||||||
}
|
|
||||||
trap finish EXIT
|
|
||||||
|
|
||||||
while read -r cert_path; do
|
|
||||||
# Follow symlinks and record directory paths
|
|
||||||
cert_path="$(readlink -f "${cert_path}")"
|
|
||||||
cert_dir="$(dirname "${cert_path}")"
|
|
||||||
echo $cert_dir >> "${cert_dirs_file}"
|
|
||||||
done < <(find /etc/ssl/certs -name '*.pem' -type l)
|
|
||||||
|
|
||||||
# Map unique certificate directories
|
|
||||||
while read -r cert_dir; do
|
|
||||||
docker_run_args+=('-v' "${cert_dir}:${cert_dir}:ro")
|
|
||||||
done < <(sort < "${cert_dirs_file}" | uniq)
|
|
||||||
fi
|
|
||||||
|
|
||||||
"${docker}" run \
|
|
||||||
-i \
|
|
||||||
--network host \
|
|
||||||
-e "HOME=${HOME}" \
|
|
||||||
-v "$HOME:${HOME}" \
|
|
||||||
-w "${PWD}" \
|
|
||||||
--user "$(id -u):$(id -g)" "${docker_run_args[@]}" \
|
|
||||||
--entrypoint '/home/mimic3/app/.venv/bin/python3' \
|
|
||||||
"${repo}:${tag}" \
|
|
||||||
-m mimic3_http.client \
|
|
||||||
--stdout \
|
|
||||||
"${args[@]}"
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
GenericExecuteSynth "printf %s \'$DATA\' | /path/to/mimic3-client --voice \'$VOICE\' --stdout | $PLAY_COMMAND"
|
GenericExecuteSynth "printf %s \'$DATA\' | /path/to/mimic3 --remote --voice \'$VOICE\' --stdout | $PLAY_COMMAND"
|
||||||
AddVoice "en-us" "MALE1" "en_UK/apope_low"
|
AddVoice "en-us" "MALE1" "en_UK/apope_low"
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,12 @@ Using [nvidia-docker](https://github.com/NVIDIA/nvidia-docker) is highly recomme
|
||||||
Assuming you have started `mimic3-server` and can access `http://localhost:59125`, then:
|
Assuming you have started `mimic3-server` and can access `http://localhost:59125`, then:
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
mimic3-client --voice 'en_UK/apope_low' 'My hovercraft is full of eels.' > hovercraft_eels.wav
|
mimic3 --remote --voice 'en_UK/apope_low' 'My hovercraft is full of eels.' > hovercraft_eels.wav
|
||||||
```
|
```
|
||||||
|
|
||||||
See `mimic3-client --help` for more options.
|
If your server is somewhere besides `localhost`, use `mimic3 --remote <URL> ...`
|
||||||
|
|
||||||
|
See `mimic3 --help` for more options.
|
||||||
|
|
||||||
|
|
||||||
## MaryTTS Compatibility
|
## MaryTTS Compatibility
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ from urllib.parse import parse_qs
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import quart_cors
|
import quart_cors
|
||||||
from mimic3_tts import Mimic3Settings, Mimic3TextToSpeechSystem
|
from mimic3_tts import DEFAULT_VOICE, Mimic3Settings, Mimic3TextToSpeechSystem
|
||||||
from quart import (
|
from quart import (
|
||||||
Quart,
|
Quart,
|
||||||
Response,
|
Response,
|
||||||
|
|
@ -159,9 +159,8 @@ def get_app(args: argparse.Namespace, request_queue: Queue, temp_dir: str):
|
||||||
|
|
||||||
_LOGGER.debug("Request args: %s", request.args)
|
_LOGGER.debug("Request args: %s", request.args)
|
||||||
|
|
||||||
voice = request.args.get("voice")
|
voice = request.args.get("voice") or args.voice or DEFAULT_VOICE
|
||||||
if voice is not None:
|
tts_args["voice"] = str(voice)
|
||||||
tts_args["voice"] = str(voice)
|
|
||||||
|
|
||||||
# TTS settings
|
# TTS settings
|
||||||
noise_scale = request.args.get("noiseScale")
|
noise_scale = request.args.get("noiseScale")
|
||||||
|
|
@ -226,7 +225,7 @@ def get_app(args: argparse.Namespace, request_queue: Queue, temp_dir: str):
|
||||||
text = request.args.get("INPUT_TEXT", "")
|
text = request.args.get("INPUT_TEXT", "")
|
||||||
voice = str(request.args.get("VOICE", voice)).strip()
|
voice = str(request.args.get("VOICE", voice)).strip()
|
||||||
|
|
||||||
voice = voice or args.voice
|
voice = voice or args.voice or DEFAULT_VOICE
|
||||||
|
|
||||||
# Assume SSML if text begins with an angle bracket
|
# Assume SSML if text begins with an angle bracket
|
||||||
ssml = text.strip().startswith("<")
|
ssml = text.strip().startswith("<")
|
||||||
|
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# Copyright 2022 Mycroft AI Inc.
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import shlex
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import typing
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
_PACKAGE = "mimic3_http.client"
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(_PACKAGE)
|
|
||||||
|
|
||||||
_DEFAULT_PLAY_PROGRAMS = ["paplay", "play -q", "aplay -q"]
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
|
||||||
args = get_args(argv)
|
|
||||||
|
|
||||||
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 or (not sys.stdout.isatty()):
|
|
||||||
_LOGGER.debug("Writing WAV data to stdout")
|
|
||||||
sys.stdout.buffer.write(wav_bytes)
|
|
||||||
else:
|
|
||||||
play_wav_bytes(args, wav_bytes)
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def play_wav_bytes(args: argparse.Namespace, wav_bytes: bytes):
|
|
||||||
with tempfile.NamedTemporaryFile(mode="wb+", suffix=".wav") as wav_file:
|
|
||||||
wav_file.write(wav_bytes)
|
|
||||||
wav_file.seek(0)
|
|
||||||
|
|
||||||
for play_program in reversed(args.play_program):
|
|
||||||
play_cmd = shlex.split(play_program)
|
|
||||||
if not shutil.which(play_cmd[0]):
|
|
||||||
continue
|
|
||||||
|
|
||||||
play_cmd.append(wav_file.name)
|
|
||||||
_LOGGER.debug("Playing WAV file: %s", play_cmd)
|
|
||||||
subprocess.check_output(play_cmd)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def get_args(argv) -> argparse.Namespace:
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
prog=_PACKAGE, description="Client for Mimic 3 HTTP web server"
|
|
||||||
)
|
|
||||||
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(
|
|
||||||
"--play-program",
|
|
||||||
action="append",
|
|
||||||
default=_DEFAULT_PLAY_PROGRAMS,
|
|
||||||
help="Program(s) used to play WAV files",
|
|
||||||
)
|
|
||||||
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(args=argv)
|
|
||||||
|
|
||||||
if args.debug:
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
else:
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
GenericExecuteSynth "printf %s \'$DATA\' | /home/hansenm/opt/mimic3/mimic3-http/client.sh --voice \'$VOICE\' --length-scale 0.5 --stdout | $PLAY_COMMAND"
|
|
||||||
AddVoice "en-us" "FEMALE1" "en_US/amy_low"
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
DefaultVoiceType "FEMALE1"
|
|
||||||
DefaultModule mimic3-generic
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
[mypy]
|
[mypy]
|
||||||
|
|
||||||
[mypy-playsound.*]
|
|
||||||
ignore_missing_imports = True
|
|
||||||
|
|
||||||
[mypy-requests.*]
|
|
||||||
ignore_missing_imports = True
|
|
||||||
|
|
||||||
[mypy-setuptools.*]
|
[mypy-setuptools.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
mimic3-tts<1.0
|
mimic3-tts<1.0
|
||||||
quart>=0.16,<1.0
|
quart>=0.16,<1.0
|
||||||
quart-cors
|
quart-cors
|
||||||
requests>=2,<3
|
|
||||||
swagger-ui-py>=21,<22
|
swagger-ui-py>=21,<22
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,6 @@ setup(
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
"mimic3-server = mimic3_http.__main__:main",
|
"mimic3-server = mimic3_http.__main__:main",
|
||||||
"mimic3-client = mimic3_http.client:main",
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
|
|
||||||
|
|
@ -214,11 +214,11 @@ sudo apt-get install speech-dispatcher
|
||||||
Create the file `/etc/speech-dispatcher/modules/mimic3-generic.conf` with the contents:
|
Create the file `/etc/speech-dispatcher/modules/mimic3-generic.conf` with the contents:
|
||||||
|
|
||||||
``` text
|
``` text
|
||||||
GenericExecuteSynth "printf %s \'$DATA\' | /path/to/mimic3-client --voice \'$VOICE\' --stdout | $PLAY_COMMAND"
|
GenericExecuteSynth "printf %s \'$DATA\' | /path/to/mimic3 --remote --voice \'$VOICE\' --stdout | $PLAY_COMMAND"
|
||||||
AddVoice "en-us" "MALE1" "en_UK/apope_low"
|
AddVoice "en-us" "MALE1" "en_UK/apope_low"
|
||||||
```
|
```
|
||||||
|
|
||||||
You will need `sudo` access to do this. Make sure to change `/path/to/mimic3-client` to wherever you installed Mimic 3.
|
You will need `sudo` access to do this. Make sure to change `/path/to/mimic3` to wherever you installed Mimic 3. Note that the `--remote` option is used to connect to a local Mimic 3 web server (use `--remote <URL>` if your server is somewhere besides `localhost`).
|
||||||
|
|
||||||
To change the voice later, you only need to replace `en_UK/apope_low`.
|
To change the voice later, you only need to replace `en_UK/apope_low`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ from opentts_abc import (
|
||||||
from opentts_abc.ssml import SSMLSpeaker
|
from opentts_abc.ssml import SSMLSpeaker
|
||||||
|
|
||||||
from ._resources import __version__
|
from ._resources import __version__
|
||||||
|
from .const import DEFAULT_VOICE
|
||||||
from .tts import Mimic3Settings, Mimic3TextToSpeechSystem
|
from .tts import Mimic3Settings, Mimic3TextToSpeechSystem
|
||||||
|
|
||||||
__author__ = "Michael Hansen"
|
__author__ = "Michael Hansen"
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,14 @@ def initialize_args(state: CommandLineInterfaceState):
|
||||||
|
|
||||||
state.texts = process_on_blank_line(state.texts)
|
state.texts = process_on_blank_line(state.texts)
|
||||||
|
|
||||||
|
if args.remote and args.remote.endswith("/"):
|
||||||
|
# Ensure no slash
|
||||||
|
args.remote = args.remote[:-1]
|
||||||
|
|
||||||
|
if (not args.speaker) and args.voice and ("#" in args.voice):
|
||||||
|
# Split apart voice
|
||||||
|
args.voice, args.speaker = args.voice.split("#", maxsplit=1)
|
||||||
|
|
||||||
|
|
||||||
def initialize_tts(state: CommandLineInterfaceState):
|
def initialize_tts(state: CommandLineInterfaceState):
|
||||||
"""Create Mimic 3 TTS from command-line arguments"""
|
"""Create Mimic 3 TTS from command-line arguments"""
|
||||||
|
|
@ -220,24 +228,29 @@ def initialize_tts(state: CommandLineInterfaceState):
|
||||||
|
|
||||||
args = state.args
|
args = state.args
|
||||||
|
|
||||||
state.tts = Mimic3TextToSpeechSystem(
|
if not args.remote:
|
||||||
Mimic3Settings(
|
# Local TTS
|
||||||
voices_directories=args.voices_dir, speaker=args.speaker, use_cuda=args.cuda
|
state.tts = Mimic3TextToSpeechSystem(
|
||||||
|
Mimic3Settings(
|
||||||
|
voices_directories=args.voices_dir,
|
||||||
|
speaker=args.speaker,
|
||||||
|
use_cuda=args.cuda,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if args.voices:
|
if args.voices:
|
||||||
# Don't bother with the rest of the initialization
|
# Don't bother with the rest of the initialization
|
||||||
return
|
return
|
||||||
|
|
||||||
if state.args.voice:
|
if state.tts:
|
||||||
# Set default voice
|
if state.args.voice:
|
||||||
state.tts.voice = state.args.voice
|
# Set default voice
|
||||||
|
state.tts.voice = state.args.voice
|
||||||
|
|
||||||
if state.args.preload_voice:
|
if state.args.preload_voice:
|
||||||
for voice_key in state.args.preload_voice:
|
for voice_key in state.args.preload_voice:
|
||||||
_LOGGER.debug("Preloading voice: %s", voice_key)
|
_LOGGER.debug("Preloading voice: %s", voice_key)
|
||||||
state.tts.preload_voice(voice_key)
|
state.tts.preload_voice(voice_key)
|
||||||
|
|
||||||
state.result_queue = Queue(maxsize=args.result_queue_size)
|
state.result_queue = Queue(maxsize=args.result_queue_size)
|
||||||
|
|
||||||
|
|
@ -326,31 +339,52 @@ def process_line(
|
||||||
line_id: str = "",
|
line_id: str = "",
|
||||||
line_voice: typing.Optional[str] = None,
|
line_voice: typing.Optional[str] = None,
|
||||||
):
|
):
|
||||||
from mimic3_tts import SSMLSpeaker
|
|
||||||
|
|
||||||
assert state.tts is not None
|
|
||||||
assert state.result_queue is not None
|
assert state.result_queue is not None
|
||||||
|
|
||||||
args = state.args
|
if state.tts:
|
||||||
|
# Local TTS
|
||||||
|
from mimic3_tts import SSMLSpeaker
|
||||||
|
|
||||||
if line_voice:
|
assert state.tts is not None
|
||||||
if line_voice.startswith("#"):
|
|
||||||
# Same voice, but different speaker
|
args = state.args
|
||||||
state.tts.speaker = line_voice[1:]
|
|
||||||
|
if line_voice:
|
||||||
|
if line_voice.startswith("#"):
|
||||||
|
# Same voice, but different speaker
|
||||||
|
state.tts.speaker = line_voice[1:]
|
||||||
|
else:
|
||||||
|
# Different voice
|
||||||
|
state.tts.voice = line_voice
|
||||||
|
|
||||||
|
if args.ssml:
|
||||||
|
results = SSMLSpeaker(state.tts).speak(line)
|
||||||
else:
|
else:
|
||||||
# Different voice
|
state.tts.begin_utterance()
|
||||||
state.tts.voice = line_voice
|
|
||||||
|
|
||||||
if args.ssml:
|
# TODO: text language
|
||||||
results = SSMLSpeaker(state.tts).speak(line)
|
state.tts.speak_text(line)
|
||||||
|
|
||||||
|
results = state.tts.end_utterance()
|
||||||
else:
|
else:
|
||||||
state.tts.begin_utterance()
|
# Remote TTS
|
||||||
|
from mimic3_tts import AudioResult
|
||||||
|
|
||||||
# TODO: text language
|
# Get remote WAV data and repackage as AudioResult
|
||||||
state.tts.speak_text(line)
|
wav_bytes = get_remote_wav_bytes(state, line)
|
||||||
|
with io.BytesIO(wav_bytes) as wav_io:
|
||||||
results = state.tts.end_utterance()
|
wav_reader: wave.Wave_read = wave.open(wav_io, "rb")
|
||||||
|
with wav_reader as wav_file:
|
||||||
|
results = [
|
||||||
|
AudioResult(
|
||||||
|
sample_rate_hz=wav_file.getframerate(),
|
||||||
|
sample_width_bytes=wav_file.getsampwidth(),
|
||||||
|
num_channels=wav_file.getnchannels(),
|
||||||
|
audio_bytes=wav_file.readframes(wav_file.getnframes()),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add results to processing queue
|
||||||
for result in results:
|
for result in results:
|
||||||
state.result_queue.put(
|
state.result_queue.put(
|
||||||
ResultToProcess(
|
ResultToProcess(
|
||||||
|
|
@ -361,8 +395,9 @@ def process_line(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Restore voice/speaker
|
# Restore voice/speaker
|
||||||
state.tts.voice = args.voice
|
if state.tts:
|
||||||
state.tts.speaker = args.speaker
|
state.tts.voice = args.voice
|
||||||
|
state.tts.speaker = args.speaker
|
||||||
|
|
||||||
|
|
||||||
def process_lines(state: CommandLineInterfaceState):
|
def process_lines(state: CommandLineInterfaceState):
|
||||||
|
|
@ -434,7 +469,7 @@ def process_lines(state: CommandLineInterfaceState):
|
||||||
|
|
||||||
|
|
||||||
def shutdown_tts(state: CommandLineInterfaceState):
|
def shutdown_tts(state: CommandLineInterfaceState):
|
||||||
if state.tts is not None:
|
if state.tts:
|
||||||
state.tts.shutdown()
|
state.tts.shutdown()
|
||||||
state.tts = None
|
state.tts = None
|
||||||
|
|
||||||
|
|
@ -456,10 +491,13 @@ def play_wav_bytes(args: argparse.Namespace, wav_bytes: bytes):
|
||||||
|
|
||||||
|
|
||||||
def print_voices(state: CommandLineInterfaceState):
|
def print_voices(state: CommandLineInterfaceState):
|
||||||
assert state.tts is not None
|
if state.tts:
|
||||||
|
# Local TTS
|
||||||
voices = list(state.tts.get_voices())
|
voices = list(state.tts.get_voices())
|
||||||
voices = sorted(voices, key=lambda v: v.key)
|
voices = sorted(voices, key=lambda v: v.key)
|
||||||
|
else:
|
||||||
|
# Remove TTS
|
||||||
|
voices = get_remote_voices(state)
|
||||||
|
|
||||||
writer = csv.writer(sys.stdout, delimiter="\t")
|
writer = csv.writer(sys.stdout, delimiter="\t")
|
||||||
writer.writerow(("KEY", "LANGUAGE", "NAME", "DESCRIPTION", "LOCATION"))
|
writer.writerow(("KEY", "LANGUAGE", "NAME", "DESCRIPTION", "LOCATION"))
|
||||||
|
|
@ -472,6 +510,59 @@ def print_voices(state: CommandLineInterfaceState):
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_voices(state: CommandLineInterfaceState) -> typing.List:
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from mimic3_tts import Voice
|
||||||
|
|
||||||
|
args = state.args
|
||||||
|
|
||||||
|
url = f"{args.remote}/api/voices"
|
||||||
|
_LOGGER.debug("Getting voices from remote server at %s", url)
|
||||||
|
|
||||||
|
voices_json = requests.get(url).json()
|
||||||
|
|
||||||
|
return [Voice(**voice_args) for voice_args in voices_json]
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_wav_bytes(state: CommandLineInterfaceState, text: str) -> bytes:
|
||||||
|
import requests
|
||||||
|
|
||||||
|
args = state.args
|
||||||
|
|
||||||
|
if args.ssml:
|
||||||
|
headers = {"Content-Type": "application/ssml+xml"}
|
||||||
|
else:
|
||||||
|
headers = {"Content-Type": "text/plain"}
|
||||||
|
|
||||||
|
params: typing.Dict[str, str] = {}
|
||||||
|
|
||||||
|
if args.voice:
|
||||||
|
if args.speaker:
|
||||||
|
params["voice"] = f"{args.voice}#{args.speaker}"
|
||||||
|
else:
|
||||||
|
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
|
||||||
|
|
||||||
|
url = f"{args.remote}/api/tts"
|
||||||
|
_LOGGER.debug("Synthesizing text remotely at %s", url)
|
||||||
|
|
||||||
|
wav_bytes = requests.post(url, headers=headers, params=params, data=text).content
|
||||||
|
|
||||||
|
return wav_bytes
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def get_args(argv=None):
|
def get_args(argv=None):
|
||||||
"""Parse command-line arguments"""
|
"""Parse command-line arguments"""
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
|
|
@ -480,6 +571,12 @@ def get_args(argv=None):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"text", nargs="*", help="Text to convert to speech (default: stdin)"
|
"text", nargs="*", help="Text to convert to speech (default: stdin)"
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--remote",
|
||||||
|
nargs="?",
|
||||||
|
const="http://localhost:59125",
|
||||||
|
help="Connect to Mimic 3 HTTP web server for synthesis (default: localhost)",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--stdin-format",
|
"--stdin-format",
|
||||||
choices=[str(v.value) for v in StdinFormat],
|
choices=[str(v.value) for v in StdinFormat],
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ ignore_missing_imports = True
|
||||||
[mypy-onnxruntime.*]
|
[mypy-onnxruntime.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-requests.*]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
||||||
[mypy-tqdm.*]
|
[mypy-tqdm.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@ numpy<2.0
|
||||||
onnxruntime>=1.6,<2.0
|
onnxruntime>=1.6,<2.0
|
||||||
opentts_abc<1.0
|
opentts_abc<1.0
|
||||||
phonemes2ids<2.0
|
phonemes2ids<2.0
|
||||||
|
requests>=2,<3
|
||||||
tqdm>=4,<5
|
tqdm>=4,<5
|
||||||
xdgenvpy>2.0,<3
|
xdgenvpy>2.0,<3
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Copyright 2022 Mycroft AI Inc.
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Stub script to execute web client in PyInstaller distribution.
|
|
||||||
#
|
|
||||||
|
|
||||||
# Directory of *this* script
|
|
||||||
this_dir="$( cd "$( dirname "$0" )" && pwd )"
|
|
||||||
|
|
||||||
"${this_dir}/mimic3" --subprogram client "$@"
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue