diff --git a/docker/mimic3-client b/docker/mimic3-client new file mode 100755 index 0000000..5779655 --- /dev/null +++ b/docker/mimic3-client @@ -0,0 +1,82 @@ +#!/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 . +# + +# 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[@]}" diff --git a/docker/mimic3-download b/docker/mimic3-download index 749bf60..86b2d48 100755 --- a/docker/mimic3-download +++ b/docker/mimic3-download @@ -73,5 +73,5 @@ fi --user "$(id -u):$(id -g)" "${docker_run_args[@]}" \ --entrypoint '/home/mimic3/app/.venv/bin/python3' \ "${repo}:${tag}" \ - -m mimic3_download \ + -m mimic3_tts.download \ "${args[@]}" diff --git a/mimic3-http/mimic3_http/client.py b/mimic3-http/mimic3_http/client.py index 72b924e..216ff73 100644 --- a/mimic3-http/mimic3_http/client.py +++ b/mimic3-http/mimic3_http/client.py @@ -15,6 +15,9 @@ # along with this program. If not, see . # import argparse +import subprocess +import shlex +import shutil import logging import os import sys @@ -28,6 +31,8 @@ _PACKAGE = "mimic3_http.client" _LOGGER = logging.getLogger(_PACKAGE) +_DEFAULT_PLAY_PROGRAMS = ["paplay", "play -q", "aplay -q"] + # ----------------------------------------------------------------------------- @@ -76,14 +81,26 @@ def main(): _LOGGER.debug("Writing WAV data to stdout") sys.stdout.buffer.write(wav_bytes) else: - from playsound import playsound + play_wav_bytes(args, wav_bytes) - 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 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 # ----------------------------------------------------------------------------- @@ -101,24 +118,16 @@ def get_args() -> argparse.Namespace: help="URL of mimic3 HTTP server (default: http://localhost:59125/api/tts)", ) parser.add_argument( - "--voice", - "-v", - help="Name of voice (expected in /)", + "--voice", "-v", help="Name of voice (expected in /)", ) parser.add_argument( - "--output", - "-o", - help="Path to write WAV file (default: play audio)", + "--output", "-o", help="Path to write WAV file (default: play audio)", ) parser.add_argument( - "--stdout", - action="store_true", - help="Write WAV data to stdout", + "--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", + "--noise-scale", type=float, help="Noise scale [0-1], default is 0.667", ) parser.add_argument( "--length-scale", @@ -126,9 +135,13 @@ def get_args() -> argparse.Namespace: 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", + "--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( diff --git a/mimic3-http/requirements.txt b/mimic3-http/requirements.txt index ed78b9c..7dd160a 100644 --- a/mimic3-http/requirements.txt +++ b/mimic3-http/requirements.txt @@ -1,5 +1,4 @@ mimic3-tts<1.0 -playsound~=1.3.0 quart>=0.16,<1.0 quart-cors requests>=2,<3 diff --git a/mimic3-http/setup.py b/mimic3-http/setup.py index 8e4b749..38dc88b 100644 --- a/mimic3-http/setup.py +++ b/mimic3-http/setup.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # +from collections import defaultdict from pathlib import Path import setuptools @@ -42,6 +43,34 @@ with open(version_path, "r", encoding="utf-8") as version_file: # ----------------------------------------------------------------------------- +# dependency => [tags] +extras = {} + +# Create language-specific extras +for lang in [ + "de", + "es", + "fr", + "it", + "nl", + "ru", + "sw", +]: + extras[f"gruut[{lang}]"] = [lang] + +# Add "all" tag +for tags in extras.values(): + tags.append("all") + +# Invert for setup +extras_require = defaultdict(list) +for dep, tags in extras.items(): + for tag in tags: + extras_require[tag].append(dep) + + +# ----------------------------------------------------------------------------- + setup( name="mimic3_http", version=version, @@ -53,7 +82,7 @@ setup( packages=setuptools.find_packages(), package_data={"mimic3_http": ["VERSION", "py.typed", "templates", "css", "img"]}, install_requires=requirements, - extras_require={':python_version<"3.9"': ["importlib_resources"]}, + extras_require={':python_version<"3.9"': ["importlib_resources"], **extras_require}, entry_points={ "console_scripts": [ "mimic3-server = mimic3_http.__main__:main", diff --git a/mimic3-tts/mimic3_tts/__main__.py b/mimic3-tts/mimic3_tts/__main__.py index e06b61d..e679ae3 100644 --- a/mimic3-tts/mimic3_tts/__main__.py +++ b/mimic3-tts/mimic3_tts/__main__.py @@ -485,7 +485,7 @@ def get_args(): parser.add_argument( "--interactive", action="store_true", - help="Play audio after each input line (see --play-command)", + help="Play audio after each input line (see --play-program)", ) parser.add_argument("--csv", action="store_true", help="Input format is id|text") parser.add_argument( diff --git a/mimic3-tts/mimic3_tts/tts.py b/mimic3-tts/mimic3_tts/tts.py index 2c02842..3cdbd00 100644 --- a/mimic3-tts/mimic3_tts/tts.py +++ b/mimic3-tts/mimic3_tts/tts.py @@ -262,7 +262,7 @@ class Mimic3TextToSpeechSystem(TextToSpeechSystem): # Clear speaker on voice change self.speaker = None - self.settings.voice = new_voice + self.settings.voice = new_voice or DEFAULT_VOICE if "#" in self.settings.voice: # Split diff --git a/mimic3-tts/setup.py b/mimic3-tts/setup.py index eff0a44..0548f72 100644 --- a/mimic3-tts/setup.py +++ b/mimic3-tts/setup.py @@ -53,9 +53,7 @@ for lang in [ "fr", "it", "nl", - "pt", "ru", - "sv", "sw", ]: extras[f"gruut[{lang}]"] = [lang]