From 48de374f20f8a265213474eeb7a3cee7c8899c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Reil=C3=A4nder?= Date: Sun, 12 Nov 2023 14:31:38 +0100 Subject: [PATCH 01/12] enable GPU support --- wyoming_piper/__main__.py | 5 +++++ wyoming_piper/process.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/wyoming_piper/__main__.py b/wyoming_piper/__main__.py index e256f89..ea81455 100755 --- a/wyoming_piper/__main__.py +++ b/wyoming_piper/__main__.py @@ -65,6 +65,11 @@ async def main() -> None: action="store_true", help="Download latest voices.json during startup", ) + parser.add_argument( + "--cuda", + action="store_true", + help="Use GPU" + ) # parser.add_argument("--debug", action="store_true", help="Log DEBUG messages") args = parser.parse_args() diff --git a/wyoming_piper/process.py b/wyoming_piper/process.py index a7e90a7..8c136be 100644 --- a/wyoming_piper/process.py +++ b/wyoming_piper/process.py @@ -148,6 +148,9 @@ async def get_process(self, voice_name: Optional[str] = None) -> PiperProcess: if self.args.noise_w: piper_args.extend(["--noise-w", str(self.args.noise_w)]) + if self.args.cuda: + piper_args.extend(["--cuda"]) + _LOGGER.debug( "Starting piper process: %s args=%s", self.args.piper, piper_args ) From e8f33170b0e65e88b9701887ccb36933285c57f1 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Tue, 31 Oct 2023 00:32:39 +0100 Subject: [PATCH 02/12] Add wyoming-piper executable This change provides an executable that will be installed into the bin/ directory of the python environment, which is simpler to start up, because the correct python instance is already implied. --- setup.py | 5 +++++ wyoming_piper/__main__.py | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 06d2257..6542cf5 100644 --- a/setup.py +++ b/setup.py @@ -42,4 +42,9 @@ "Programming Language :: Python :: 3.11", ], keywords="rhasspy wyoming piper tts", + entry_points={ + 'console_scripts': [ + 'wyoming-piper = wyoming_piper:__main__.run' + ] + }, ) diff --git a/wyoming_piper/__main__.py b/wyoming_piper/__main__.py index ea81455..472c3e9 100755 --- a/wyoming_piper/__main__.py +++ b/wyoming_piper/__main__.py @@ -209,8 +209,12 @@ def get_description(voice_info: Dict[str, Any]): # ----------------------------------------------------------------------------- +def run(): + asyncio.run(main()) + + if __name__ == "__main__": try: - asyncio.run(main()) + run() except KeyboardInterrupt: pass From 49e102a4af37456d8718b081455af63e42df9ee1 Mon Sep 17 00:00:00 2001 From: monkeyclass <30174727+monkeyclass@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:03:03 +0200 Subject: [PATCH 03/12] Update README.md with local install --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index c4e3c50..eeb68c8 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,27 @@ [Source](https://github.com/home-assistant/addons/tree/master/piper) +## Local Install + +Clone the repository and set up Python virtual environment: + +``` sh +git clone https://github.com/rhasspy/wyoming-piper.git +cd wyoming-piper +script/setup +``` + +Install Piper +```sh +curl -L -s "https://github.com/rhasspy/piper/releases/download/v1.2.0/piper_amd64.tar.gz" | tar -zxvf - -C /usr/share +``` + +Run a server that anyone can connect to: + +``` sh +script/run --piper '/usr/share/piper/piper' --voice en_US-lessac-medium --uri 'tcp://0.0.0.0:10200' --data-dir /data --download-dir /data +``` + ## Docker Image ``` sh From cafb52cf815722f83571c4a2f850ad909a2fcceb Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Mon, 19 Feb 2024 16:06:55 -0600 Subject: [PATCH 04/12] Add test --- .github/workflows/test.yml | 32 ++++++++++ .gitignore | 2 + requirements.txt | 2 +- requirements_dev.txt | 6 ++ script/format | 7 ++- script/lint | 13 ++-- script/setup | 10 +++ script/test | 13 ++++ setup.py | 16 ++--- tests/__init__.py | 0 tests/dtw.py | 43 +++++++++++++ tests/test_piper.py | 123 +++++++++++++++++++++++++++++++++++++ tests/this_is_a_test.wav | Bin 0 -> 31020 bytes tox.ini | 17 +++++ wyoming_piper/VERSION | 1 + wyoming_piper/__init__.py | 8 +++ wyoming_piper/__main__.py | 27 +++++--- wyoming_piper/handler.py | 10 +++ 18 files changed, 306 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100755 script/test create mode 100644 tests/__init__.py create mode 100644 tests/dtw.py create mode 100644 tests/test_piper.py create mode 100644 tests/this_is_a_test.wav create mode 100644 tox.ini create mode 100644 wyoming_piper/VERSION diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9ccf88d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +--- +name: test + +# yamllint disable-line rule:truthy +on: + push: + branches: [master] + + pull_request: + +permissions: + contents: read + +jobs: + test_linux: + name: "test on linux" + runs-on: ubuntu-latest + strategy: + matrix: + python_version: ["3.8", "3.9", "3.10", "3.11"] + steps: + - uses: actions/checkout@v4.1.1 + - uses: actions/setup-python@v5 + with: + python-version: "${{ matrix.python_version }}" + cache: "pip" + cache-dependency-path: requirements_dev.txt + - run: script/setup --dev + - run: | + test $(script/run --version) = $(cat wyoming_piper/VERSION) + - run: script/lint + - run: script/test diff --git a/.gitignore b/.gitignore index a49a04e..8e4ccc8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,7 @@ htmlcov /.venv/ .mypy_cache/ __pycache__/ +/local/ +.tox/ /dist/ diff --git a/requirements.txt b/requirements.txt index 179b422..1ff43a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -wyoming==1.1.0 +wyoming==1.5.3 diff --git a/requirements_dev.txt b/requirements_dev.txt index 77190e6..a3558be 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,3 +3,9 @@ flake8==6.0.0 isort==5.11.3 mypy==0.991 pylint==2.15.9 +pytest==7.4.4 +pytest-asyncio==0.23.3 +tox==4.13.0 +scipy==1.12.0 +numpy==1.26.4 +python-speech-features==0.6 diff --git a/script/format b/script/format index d72d1b4..187a799 100755 --- a/script/format +++ b/script/format @@ -7,7 +7,10 @@ _DIR = Path(__file__).parent _PROGRAM_DIR = _DIR.parent _VENV_DIR = _PROGRAM_DIR / ".venv" _MODULE_DIR = _PROGRAM_DIR / "wyoming_piper" +_TESTS_DIR = _PROGRAM_DIR / "tests" + +_FORMAT_DIRS = [_MODULE_DIR, _TESTS_DIR] context = venv.EnvBuilder().ensure_directories(_VENV_DIR) -subprocess.check_call([context.env_exe, "-m", "black", str(_MODULE_DIR)]) -subprocess.check_call([context.env_exe, "-m", "isort", str(_MODULE_DIR)]) +subprocess.check_call([context.env_exe, "-m", "black"] + _FORMAT_DIRS) +subprocess.check_call([context.env_exe, "-m", "isort"] + _FORMAT_DIRS) diff --git a/script/lint b/script/lint index 6071488..875a265 100755 --- a/script/lint +++ b/script/lint @@ -7,10 +7,13 @@ _DIR = Path(__file__).parent _PROGRAM_DIR = _DIR.parent _VENV_DIR = _PROGRAM_DIR / ".venv" _MODULE_DIR = _PROGRAM_DIR / "wyoming_piper" +_TESTS_DIR = _PROGRAM_DIR / "tests" + +_LINT_DIRS = [_MODULE_DIR, _TESTS_DIR] context = venv.EnvBuilder().ensure_directories(_VENV_DIR) -subprocess.check_call([context.env_exe, "-m", "black", str(_MODULE_DIR), "--check"]) -subprocess.check_call([context.env_exe, "-m", "isort", str(_MODULE_DIR), "--check"]) -subprocess.check_call([context.env_exe, "-m", "flake8", str(_MODULE_DIR)]) -subprocess.check_call([context.env_exe, "-m", "pylint", str(_MODULE_DIR)]) -subprocess.check_call([context.env_exe, "-m", "mypy", str(_MODULE_DIR)]) +subprocess.check_call([context.env_exe, "-m", "black"] + _LINT_DIRS + ["--check"]) +subprocess.check_call([context.env_exe, "-m", "isort"] + _LINT_DIRS + ["--check"]) +subprocess.check_call([context.env_exe, "-m", "flake8"] + _LINT_DIRS) +subprocess.check_call([context.env_exe, "-m", "pylint"] + _LINT_DIRS) +subprocess.check_call([context.env_exe, "-m", "mypy"] + _LINT_DIRS) diff --git a/script/setup b/script/setup index 92ec185..9b35199 100755 --- a/script/setup +++ b/script/setup @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import argparse import subprocess import venv from pathlib import Path @@ -7,6 +8,9 @@ _DIR = Path(__file__).parent _PROGRAM_DIR = _DIR.parent _VENV_DIR = _PROGRAM_DIR / ".venv" +parser = argparse.ArgumentParser() +parser.add_argument("--dev", action="store_true", help="Install dev requirements") +args = parser.parse_args() # Create virtual environment builder = venv.EnvBuilder(with_pip=True) @@ -20,3 +24,9 @@ subprocess.check_call(pip + ["install", "--upgrade", "setuptools", "wheel"]) # Install requirements subprocess.check_call(pip + ["install", "-r", str(_PROGRAM_DIR / "requirements.txt")]) + +if args.dev: + # Install dev requirements + subprocess.check_call( + pip + ["install", "-r", str(_PROGRAM_DIR / "requirements_dev.txt")] + ) diff --git a/script/test b/script/test new file mode 100755 index 0000000..5ad78a8 --- /dev/null +++ b/script/test @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import venv +from pathlib import Path + +_DIR = Path(__file__).parent +_PROGRAM_DIR = _DIR.parent +_VENV_DIR = _PROGRAM_DIR / ".venv" +_TEST_DIR = _PROGRAM_DIR / "tests" + +context = venv.EnvBuilder().ensure_directories(_VENV_DIR) +subprocess.check_call([context.env_exe, "-m", "pytest", _TEST_DIR] + sys.argv[1:]) diff --git a/setup.py b/setup.py index 6542cf5..455dd5a 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,6 @@ from setuptools import setup this_dir = Path(__file__).parent -module_dir = this_dir / "wyoming_piper" requirements = [] requirements_path = this_dir / "requirements.txt" @@ -13,22 +12,25 @@ with open(requirements_path, "r", encoding="utf-8") as requirements_file: requirements = requirements_file.read().splitlines() -data_files = [module_dir / "voices.json"] +module_name = "wyoming_piper" +module_dir = this_dir / module_name +version_path = module_dir / "VERSION" +version = version_path.read_text(encoding="utf-8").strip() + +data_files = [module_dir / "voices.json", version_path] # ----------------------------------------------------------------------------- setup( - name="wyoming_piper", - version="1.4.0", + name=module_name, + version=version, description="Wyoming Server for Piper", url="https://github.com/rhasspy/wyoming-piper", author="Michael Hansen", author_email="mike@rhasspy.org", license="MIT", packages=setuptools.find_packages(), - package_data={ - "wyoming_piper": [str(p.relative_to(module_dir)) for p in data_files] - }, + package_data={module_name: [str(p.relative_to(module_dir)) for p in data_files]}, install_requires=requirements, classifiers=[ "Development Status :: 3 - Alpha", diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/dtw.py b/tests/dtw.py new file mode 100644 index 0000000..613450e --- /dev/null +++ b/tests/dtw.py @@ -0,0 +1,43 @@ +import math + +import numpy as np +import scipy + + +def compute_optimal_path(x: np.ndarray, y: np.ndarray) -> float: + """Computes optimal path between x and y.""" + m = len(x) + n = len(y) + + # Need 2-D arrays for distance calculation + if len(x.shape) == 1: + x = x.reshape(-1, 1) + + if len(y.shape) == 1: + y = y.reshape(-1, 1) + + distance_matrix = scipy.spatial.distance.cdist(x, y, metric="cosine") + + cost_matrix = np.full(shape=(m, n), fill_value=math.inf, dtype=float) + cost_matrix[0][0] = distance_matrix[0][0] + + for row in range(1, m): + cost = distance_matrix[row, 0] + cost_matrix[row][0] = cost + cost_matrix[row - 1][0] + + for col in range(1, n): + cost = distance_matrix[0, col] + cost_matrix[0][col] = cost + cost_matrix[0][col - 1] + + for row in range(1, m): + for col in range(1, n): + cost = distance_matrix[row, col] + cost_matrix[row][col] = cost + min( + cost_matrix[row - 1][col], # insertion + cost_matrix[row][col - 1], # deletion + cost_matrix[row - 1][col - 1], # match + ) + + distance = cost_matrix[m - 1][n - 1] + + return distance diff --git a/tests/test_piper.py b/tests/test_piper.py new file mode 100644 index 0000000..fc620ba --- /dev/null +++ b/tests/test_piper.py @@ -0,0 +1,123 @@ +"""Tests for wyoming-piper""" +import asyncio +import sys +import tarfile +import wave +from asyncio.subprocess import PIPE +from pathlib import Path +from urllib.request import urlopen + +import numpy as np +import pytest +import python_speech_features +from wyoming.audio import AudioChunk, AudioStart, AudioStop +from wyoming.event import async_read_event, async_write_event +from wyoming.info import Describe, Info +from wyoming.tts import Synthesize, SynthesizeVoice + +from .dtw import compute_optimal_path + +_DIR = Path(__file__).parent +_LOCAL_DIR = _DIR.parent / "local" +_PIPER_URL = ( + "https://github.com/rhasspy/piper/releases/download/v1.2.0/piper_amd64.tar.gz" +) +_TIMEOUT = 1 + + +def download_piper() -> None: + """Downloads a binary version of Piper.""" + piper_path = _LOCAL_DIR / "piper" + if piper_path.exists(): + return + + _LOCAL_DIR.mkdir(parents=True, exist_ok=True) + with urlopen(_PIPER_URL) as response: + with tarfile.open(fileobj=response, mode="r|*") as piper_file: + piper_file.extractall(_LOCAL_DIR) + + +@pytest.mark.asyncio +async def test_piper() -> None: + download_piper() + + proc = await asyncio.create_subprocess_exec( + sys.executable, + "-m", + "wyoming_piper", + "--uri", + "stdio://", + "--piper", + str(_LOCAL_DIR / "piper" / "piper"), + "--voice", + "en_US-ryan-low", + "--data-dir", + str(_LOCAL_DIR), + stdin=PIPE, + stdout=PIPE, + ) + assert proc.stdin is not None + assert proc.stdout is not None + + # Check info + await async_write_event(Describe().event(), proc.stdin) + while True: + event = await asyncio.wait_for(async_read_event(proc.stdout), timeout=_TIMEOUT) + assert event is not None + + if not Info.is_type(event.type): + continue + + info = Info.from_event(event) + assert len(info.tts) == 1, "Expected one tts service" + tts = info.tts[0] + assert len(tts.voices) > 0, "Expected at least one voice" + voice_model = next((v for v in tts.voices if v.name == "en_US-ryan-low"), None) + assert voice_model is not None, "Expected ryan voice" + break + + # Synthesize text + await async_write_event( + Synthesize("This is a test.", voice=SynthesizeVoice("en_US-ryan-low")).event(), + proc.stdin, + ) + + event = await asyncio.wait_for(async_read_event(proc.stdout), timeout=_TIMEOUT) + assert event is not None + assert AudioStart.is_type(event.type) + audio_start = AudioStart.from_event(event) + + with wave.open(str(_DIR / "this_is_a_test.wav"), "rb") as wav_file: + assert audio_start.rate == wav_file.getframerate() + assert audio_start.width == wav_file.getsampwidth() + assert audio_start.channels == wav_file.getnchannels() + expected_audio = wav_file.readframes(wav_file.getnframes()) + expected_array = np.frombuffer(expected_audio, dtype=np.int16) + + actual_audio = bytes() + while True: + event = await asyncio.wait_for(async_read_event(proc.stdout), timeout=_TIMEOUT) + assert event is not None + if AudioStop.is_type(event.type): + break + + if AudioChunk.is_type(event.type): + chunk = AudioChunk.from_event(event) + assert chunk.rate == audio_start.rate + assert chunk.width == audio_start.width + assert chunk.channels == audio_start.channels + actual_audio += chunk.audio + + actual_array = np.frombuffer(actual_audio, dtype=np.int16) + + # Less than 20% difference in length + assert ( + abs(len(actual_array) - len(expected_array)) + / max(len(actual_array), len(expected_array)) + < 0.2 + ) + + # Compute dynamic time warping (DTW) distance of MFCC features + expected_mfcc = python_speech_features.mfcc(expected_array, winstep=0.02) + actual_mfcc = python_speech_features.mfcc(actual_array, winstep=0.02) + assert compute_optimal_path(actual_mfcc, expected_mfcc) < 10 diff --git a/tests/this_is_a_test.wav b/tests/this_is_a_test.wav new file mode 100644 index 0000000000000000000000000000000000000000..11dc7cd8df0017c5e79cf5c4b9a7f1b33ff3500f GIT binary patch literal 31020 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 A=Kufz literal 0 HcmV?d00001 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..f8aaad3 --- /dev/null +++ b/tox.ini @@ -0,0 +1,17 @@ +[tox] +env_list = + py{38,39,310,311} +minversion = 4.12.1 + +[testenv] +description = run the tests with pytest +package = wheel +wheel_build_env = .pkg +deps = + pytest>=7,<8 + pytest-asyncio<1 + scipy>=1.10.0,<2 + numpy>=1.20,<2 + python-speech-features==0.6 +commands = + pytest {tty:--color=yes} {posargs} diff --git a/wyoming_piper/VERSION b/wyoming_piper/VERSION new file mode 100644 index 0000000..bc80560 --- /dev/null +++ b/wyoming_piper/VERSION @@ -0,0 +1 @@ +1.5.0 diff --git a/wyoming_piper/__init__.py b/wyoming_piper/__init__.py index 7c73c2b..95fa18c 100644 --- a/wyoming_piper/__init__.py +++ b/wyoming_piper/__init__.py @@ -1 +1,9 @@ """Wyoming server for piper.""" +from pathlib import Path + +_DIR = Path(__file__).parent +_VERSION_PATH = _DIR / "VERSION" + +__version__ = _VERSION_PATH.read_text(encoding="utf-8").strip() + +__all__ = ["__version__"] diff --git a/wyoming_piper/__main__.py b/wyoming_piper/__main__.py index 472c3e9..e64ef44 100755 --- a/wyoming_piper/__main__.py +++ b/wyoming_piper/__main__.py @@ -7,9 +7,10 @@ from pathlib import Path from typing import Any, Dict, Set -from wyoming.info import Attribution, Info, TtsProgram, TtsVoice +from wyoming.info import Attribution, Info, TtsProgram, TtsVoice, TtsVoiceSpeaker from wyoming.server import AsyncServer +from . import __version__ from .download import find_voice, get_voices from .handler import PiperEventHandler from .process import PiperProcessManager @@ -72,6 +73,12 @@ async def main() -> None: ) # parser.add_argument("--debug", action="store_true", help="Log DEBUG messages") + parser.add_argument( + "--version", + action="version", + version=__version__, + help="Print version and exit", + ) args = parser.parse_args() if not args.download_dir: @@ -79,6 +86,7 @@ async def main() -> None: args.download_dir = args.data_dir[0] logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) + _LOGGER.debug(args) # Load voice info voices_info = get_voices(args.download_dir, update_voices=args.update_voices) @@ -98,20 +106,19 @@ async def main() -> None: name="rhasspy", url="https://github.com/rhasspy/piper" ), installed=True, + version=None, languages=[ voice_info.get("language", {}).get( "code", voice_info.get("espeak", {}).get("voice", voice_name.split("_")[0]), ) ], - # - # Don't send speakers for now because it overflows StreamReader buffers - # speakers=[ - # TtsVoiceSpeaker(name=speaker_name) - # for speaker_name in voice_info["speaker_id_map"] - # ] - # if voice_info.get("speaker_id_map") - # else None, + speakers=[ + TtsVoiceSpeaker(name=speaker_name) + for speaker_name in voice_info["speaker_id_map"] + ] + if voice_info.get("speaker_id_map") + else None, ) for voice_name, voice_info in voices_info.items() if not voice_info.get("_is_alias", False) @@ -155,6 +162,7 @@ async def main() -> None: TtsVoice( name=custom_name, description=description, + version=None, attribution=Attribution(name="", url=""), installed=True, languages=[lang_code], @@ -171,6 +179,7 @@ async def main() -> None: ), installed=True, voices=sorted(voices, key=lambda v: v.name), + version=__version__, ) ], ) diff --git a/wyoming_piper/handler.py b/wyoming_piper/handler.py index 708812b..0373a8e 100644 --- a/wyoming_piper/handler.py +++ b/wyoming_piper/handler.py @@ -8,6 +8,7 @@ from typing import Any, Dict, Optional from wyoming.audio import AudioChunk, AudioStart, AudioStop +from wyoming.error import Error from wyoming.event import Event from wyoming.info import Describe, Info from wyoming.server import AsyncEventHandler @@ -43,6 +44,15 @@ async def handle_event(self, event: Event) -> bool: _LOGGER.warning("Unexpected event: %s", event) return True + try: + return await self._handle_event(event) + except Exception as err: + await self.write_event( + Error(text=str(err), code=err.__class__.__name__).event() + ) + raise err + + async def _handle_event(self, event: Event) -> bool: synthesize = Synthesize.from_event(event) _LOGGER.debug(synthesize) From b4d6e5947e9ebc274edf16a67c20f549be1d6ee7 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Mon, 19 Feb 2024 16:07:22 -0600 Subject: [PATCH 05/12] Update voices.json --- wyoming_piper/voices.json | 716 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 716 insertions(+) diff --git a/wyoming_piper/voices.json b/wyoming_piper/voices.json index fa4b652..caf1afa 100644 --- a/wyoming_piper/voices.json +++ b/wyoming_piper/voices.json @@ -1,4 +1,34 @@ { + "ar_JO-kareem-low": { + "key": "ar_JO-kareem-low", + "name": "kareem", + "language": { + "code": "ar_JO", + "family": "ar", + "region": "JO", + "name_native": "العربية", + "name_english": "Arabic", + "country_english": "Jordan" + }, + "quality": "low", + "num_speakers": 1, + "speaker_id_map": {}, + "files": { + "ar/ar_JO/kareem/low/ar_JO-kareem-low.onnx": { + "size_bytes": 63201294, + "md5_digest": "d335cd06fe4045a7ee9d8fb0712afaa9" + }, + "ar/ar_JO/kareem/low/ar_JO-kareem-low.onnx.json": { + "size_bytes": 5022, + "md5_digest": "465724f7d2d5f2ff061b53acb8e7f7cc" + }, + "ar/ar_JO/kareem/low/MODEL_CARD": { + "size_bytes": 274, + "md5_digest": "b6f0eaf5a7fd094be22a1bcb162173fb" + } + }, + "aliases": [] + }, "ar_JO-kareem-medium": { "key": "ar_JO-kareem-medium", "name": "kareem", @@ -311,6 +341,273 @@ "de-kerstin-low" ] }, + "de_DE-mls-medium": { + "key": "de_DE-mls-medium", + "name": "mls", + "language": { + "code": "de_DE", + "family": "de", + "region": "DE", + "name_native": "Deutsch", + "name_english": "German", + "country_english": "Germany" + }, + "quality": "medium", + "num_speakers": 236, + "speaker_id_map": { + "2422": 0, + "4536": 1, + "2037": 2, + "9565": 3, + "10148": 4, + "6507": 5, + "5055": 6, + "3503": 7, + "252": 8, + "9132": 9, + "3990": 10, + "5753": 11, + "5424": 12, + "2602": 13, + "4174": 14, + "3885": 15, + "12415": 16, + "8470": 17, + "11927": 18, + "9639": 19, + "3494": 20, + "2946": 21, + "5283": 22, + "4533": 23, + "2497": 24, + "12275": 25, + "1649": 26, + "146": 27, + "8337": 28, + "4542": 29, + "589": 30, + "1998": 31, + "3797": 32, + "5244": 33, + "7328": 34, + "7998": 35, + "10179": 36, + "9610": 37, + "20": 38, + "253": 39, + "12899": 40, + "7194": 41, + "3759": 42, + "2677": 43, + "6719": 44, + "1897": 45, + "11990": 46, + "6880": 47, + "19": 48, + "9515": 49, + "327": 50, + "3244": 51, + "5324": 52, + "2234": 53, + "3124": 54, + "2043": 55, + "143": 56, + "8139": 57, + "9646": 58, + "8659": 59, + "9538": 60, + "989": 61, + "5405": 62, + "10087": 63, + "8294": 64, + "4396": 65, + "1474": 66, + "139": 67, + "136": 68, + "10791": 69, + "7242": 70, + "3631": 71, + "9908": 72, + "7906": 73, + "1171": 74, + "7479": 75, + "5632": 76, + "3731": 77, + "4650": 78, + "135": 79, + "145": 80, + "137": 81, + "1757": 82, + "91": 83, + "9514": 84, + "13494": 85, + "1946": 86, + "3277": 87, + "5595": 88, + "278": 89, + "7120": 90, + "7406": 91, + "11695": 92, + "1593": 93, + "3862": 94, + "138": 95, + "141": 96, + "9948": 97, + "1163": 98, + "1054": 99, + "1844": 100, + "4911": 101, + "7261": 102, + "8223": 103, + "7624": 104, + "144": 105, + "13871": 106, + "2974": 107, + "5934": 108, + "7002": 109, + "8769": 110, + "3363": 111, + "3040": 112, + "6067": 113, + "9494": 114, + "8743": 115, + "13255": 116, + "1660": 117, + "3588": 118, + "4748": 119, + "8450": 120, + "5295": 121, + "4705": 122, + "8125": 123, + "7272": 124, + "7320": 125, + "1874": 126, + "1262": 127, + "10870": 128, + "12379": 129, + "4463": 130, + "10349": 131, + "2252": 132, + "8325": 133, + "3052": 134, + "4001": 135, + "7456": 136, + "140": 137, + "4739": 138, + "11299": 139, + "4730": 140, + "6659": 141, + "2034": 142, + "2732": 143, + "2158": 144, + "3698": 145, + "5675": 146, + "6315": 147, + "10904": 148, + "7202": 149, + "11480": 150, + "10625": 151, + "11546": 152, + "4576": 153, + "4512": 154, + "8634": 155, + "13626": 156, + "1613": 157, + "287": 158, + "9207": 159, + "6982": 160, + "1724": 161, + "10191": 162, + "1091": 163, + "2909": 164, + "1965": 165, + "2506": 166, + "4414": 167, + "5764": 168, + "12776": 169, + "1033": 170, + "13726": 171, + "2314": 172, + "6826": 173, + "9706": 174, + "8427": 175, + "9168": 176, + "9287": 177, + "6905": 178, + "4153": 179, + "3330": 180, + "2859": 181, + "5406": 182, + "2840": 183, + "1920": 184, + "9241": 185, + "10163": 186, + "8305": 187, + "12461": 188, + "3276": 189, + "11413": 190, + "10536": 191, + "10614": 192, + "7579": 193, + "8675": 194, + "7483": 195, + "7270": 196, + "8704": 197, + "4468": 198, + "6611": 199, + "11497": 200, + "11772": 201, + "2792": 202, + "11481": 203, + "10162": 204, + "10819": 205, + "8732": 206, + "11328": 207, + "11920": 208, + "6646": 209, + "7486": 210, + "11870": 211, + "12417": 212, + "10364": 213, + "6117": 214, + "6448": 215, + "10433": 216, + "7515": 217, + "5823": 218, + "8567": 219, + "10947": 220, + "11869": 221, + "12335": 222, + "12500": 223, + "13755": 224, + "7006": 225, + "3685": 226, + "5819": 227, + "9353": 228, + "11355": 229, + "12174": 230, + "7150": 231, + "6952": 232, + "11987": 233, + "3995": 234, + "7449": 235 + }, + "files": { + "de/de_DE/mls/medium/de_DE-mls-medium.onnx": { + "size_bytes": 76961079, + "md5_digest": "bb543a8e82b95993cdd2199a0049623b" + }, + "de/de_DE/mls/medium/de_DE-mls-medium.onnx.json": { + "size_bytes": 8948, + "md5_digest": "3fb96c627820ac38cff6e8c3ac0e4aa0" + }, + "de/de_DE/mls/medium/MODEL_CARD": { + "size_bytes": 223, + "md5_digest": "e32e1746dddda1336c1b6725afa6251d" + } + }, + "aliases": [] + }, "de_DE-pavoque-low": { "key": "de_DE-pavoque-low", "name": "pavoque", @@ -1079,6 +1376,36 @@ }, "aliases": [] }, + "en_US-hfc_female-medium": { + "key": "en_US-hfc_female-medium", + "name": "hfc_female", + "language": { + "code": "en_US", + "family": "en", + "region": "US", + "name_native": "English", + "name_english": "English", + "country_english": "United States" + }, + "quality": "medium", + "num_speakers": 1, + "speaker_id_map": {}, + "files": { + "en/en_US/hfc_female/medium/en_US-hfc_female-medium.onnx": { + "size_bytes": 63201294, + "md5_digest": "7abec91f1d6e19e913fbc4a333f62787" + }, + "en/en_US/hfc_female/medium/en_US-hfc_female-medium.onnx.json": { + "size_bytes": 5033, + "md5_digest": "c3d00f54dac3b4068f2576c15c5da3bc" + }, + "en/en_US/hfc_female/medium/MODEL_CARD": { + "size_bytes": 354, + "md5_digest": "a4a7b5da65e03e6972e44e9555a59aef" + } + }, + "aliases": [] + }, "en_US-hfc_male-medium": { "key": "en_US-hfc_male-medium", "name": "hfc_male", @@ -3507,6 +3834,66 @@ }, "aliases": [] }, + "fa_IR-amir-medium": { + "key": "fa_IR-amir-medium", + "name": "amir", + "language": { + "code": "fa_IR", + "family": "fa", + "region": "IR", + "name_native": "فارسی", + "name_english": "Farsi", + "country_english": "Iran" + }, + "quality": "medium", + "num_speakers": 1, + "speaker_id_map": {}, + "files": { + "fa/fa_IR/amir/medium/fa_IR-amir-medium.onnx": { + "size_bytes": 63531379, + "md5_digest": "7c0598c9726427869e1e86447b333539" + }, + "fa/fa_IR/amir/medium/fa_IR-amir-medium.onnx.json": { + "size_bytes": 4958, + "md5_digest": "48c5e81f5aa4e1c5eba3dda0be403b58" + }, + "fa/fa_IR/amir/medium/MODEL_CARD": { + "size_bytes": 264, + "md5_digest": "0728165259eb968913a680077607cd5c" + } + }, + "aliases": [] + }, + "fa_IR-gyro-medium": { + "key": "fa_IR-gyro-medium", + "name": "gyro", + "language": { + "code": "fa_IR", + "family": "fa", + "region": "IR", + "name_native": "فارسی", + "name_english": "Farsi", + "country_english": "Iran" + }, + "quality": "medium", + "num_speakers": 1, + "speaker_id_map": {}, + "files": { + "fa/fa_IR/gyro/medium/fa_IR-gyro-medium.onnx": { + "size_bytes": 63122309, + "md5_digest": "479695db58b82141fc48f0499a38a7c9" + }, + "fa/fa_IR/gyro/medium/fa_IR-gyro-medium.onnx.json": { + "size_bytes": 4958, + "md5_digest": "6289e9785140547040a1e616388dd587" + }, + "fa/fa_IR/gyro/medium/MODEL_CARD": { + "size_bytes": 262, + "md5_digest": "b30a1713c6e67946e3f1ed33eac06039" + } + }, + "aliases": [] + }, "fi_FI-harri-low": { "key": "fi_FI-harri-low", "name": "harri", @@ -3601,6 +3988,162 @@ "fr-gilles-low" ] }, + "fr_FR-mls-medium": { + "key": "fr_FR-mls-medium", + "name": "mls", + "language": { + "code": "fr_FR", + "family": "fr", + "region": "FR", + "name_native": "Français", + "name_english": "French", + "country_english": "France" + }, + "quality": "medium", + "num_speakers": 125, + "speaker_id_map": { + "1840": 0, + "3698": 1, + "123": 2, + "1474": 3, + "12709": 4, + "7423": 5, + "9242": 6, + "8778": 7, + "3060": 8, + "4512": 9, + "6249": 10, + "12541": 11, + "13634": 12, + "10065": 13, + "6128": 14, + "5232": 15, + "5764": 16, + "12713": 17, + "12823": 18, + "6070": 19, + "12501": 20, + "9121": 21, + "1649": 22, + "2776": 23, + "11772": 24, + "5612": 25, + "11822": 26, + "1590": 27, + "5525": 28, + "10827": 29, + "1243": 30, + "13142": 31, + "62": 32, + "13177": 33, + "10620": 34, + "8102": 35, + "8582": 36, + "11875": 37, + "7239": 38, + "9854": 39, + "7377": 40, + "10082": 41, + "12512": 42, + "1329": 43, + "2506": 44, + "6856": 45, + "10058": 46, + "103": 47, + "14": 48, + "6381": 49, + "1664": 50, + "11954": 51, + "66": 52, + "1127": 53, + "3270": 54, + "13611": 55, + "13658": 56, + "12968": 57, + "1989": 58, + "12981": 59, + "7193": 60, + "6348": 61, + "7679": 62, + "2284": 63, + "3182": 64, + "3503": 65, + "2033": 66, + "2771": 67, + "7614": 68, + "125": 69, + "3204": 70, + "5595": 71, + "5553": 72, + "694": 73, + "1624": 74, + "1887": 75, + "2926": 76, + "7150": 77, + "3190": 78, + "3344": 79, + "4699": 80, + "1798": 81, + "1745": 82, + "5077": 83, + "753": 84, + "52": 85, + "4174": 86, + "4018": 87, + "12899": 88, + "1844": 89, + "4396": 90, + "1817": 91, + "2155": 92, + "2946": 93, + "4336": 94, + "4609": 95, + "1977": 96, + "10957": 97, + "204": 98, + "4650": 99, + "5295": 100, + "5968": 101, + "4744": 102, + "2825": 103, + "9804": 104, + "707": 105, + "30": 106, + "115": 107, + "5840": 108, + "2587": 109, + "2607": 110, + "2544": 111, + "28": 112, + "27": 113, + "177": 114, + "112": 115, + "94": 116, + "2596": 117, + "3595": 118, + "7032": 119, + "7848": 120, + "11247": 121, + "7439": 122, + "2904": 123, + "6362": 124 + }, + "files": { + "fr/fr_FR/mls/medium/fr_FR-mls-medium.onnx": { + "size_bytes": 76733750, + "md5_digest": "87831389d3ae92347d91e38b0c57add9" + }, + "fr/fr_FR/mls/medium/fr_FR-mls-medium.onnx.json": { + "size_bytes": 7036, + "md5_digest": "be41a30aab03788f5e5c4fe51620ccbe" + }, + "fr/fr_FR/mls/medium/MODEL_CARD": { + "size_bytes": 222, + "md5_digest": "88d84c1c548aa27c1d119d2964f8fcf0" + } + }, + "aliases": [] + }, "fr_FR-mls_1840-low": { "key": "fr_FR-mls_1840-low", "name": "mls_1840", @@ -3697,6 +4240,36 @@ "fr-siwis-medium" ] }, + "fr_FR-tom-medium": { + "key": "fr_FR-tom-medium", + "name": "tom", + "language": { + "code": "fr_FR", + "family": "fr", + "region": "FR", + "name_native": "Français", + "name_english": "French", + "country_english": "France" + }, + "quality": "medium", + "num_speakers": 1, + "speaker_id_map": {}, + "files": { + "fr/fr_FR/tom/medium/fr_FR-tom-medium.onnx": { + "size_bytes": 63511038, + "md5_digest": "5b460c2394a871e675f5c798af149412" + }, + "fr/fr_FR/tom/medium/fr_FR-tom-medium.onnx.json": { + "size_bytes": 4959, + "md5_digest": "964d58602df7adf76c2401b070f68ea2" + }, + "fr/fr_FR/tom/medium/MODEL_CARD": { + "size_bytes": 233, + "md5_digest": "d82342c0c27cfbe9342814c7da46cb83" + } + }, + "aliases": [] + }, "fr_FR-upmc-medium": { "key": "fr_FR-upmc-medium", "name": "upmc", @@ -4371,6 +4944,89 @@ "nl-rdh-x-low" ] }, + "nl_NL-mls-medium": { + "key": "nl_NL-mls-medium", + "name": "mls", + "language": { + "code": "nl_NL", + "family": "nl", + "region": "NL", + "name_native": "Nederlands", + "name_english": "Dutch", + "country_english": "Netherlands" + }, + "quality": "medium", + "num_speakers": 52, + "speaker_id_map": { + "2450": 0, + "1724": 1, + "1666": 2, + "5809": 3, + "496": 4, + "2506": 5, + "7432": 6, + "3619": 7, + "4429": 8, + "3798": 9, + "12500": 10, + "10587": 11, + "2951": 12, + "1775": 13, + "9861": 14, + "880": 15, + "3034": 16, + "2825": 17, + "5438": 18, + "3245": 19, + "4396": 20, + "11290": 21, + "11936": 22, + "6916": 23, + "10294": 24, + "10079": 25, + "7588": 26, + "7579": 27, + "123": 28, + "3024": 29, + "960": 30, + "10984": 31, + "2792": 32, + "7723": 33, + "4174": 34, + "2981": 35, + "5764": 36, + "6513": 37, + "7884": 38, + "6697": 39, + "12749": 40, + "11157": 41, + "2239": 42, + "10879": 43, + "1085": 44, + "8480": 45, + "8331": 46, + "6282": 47, + "10632": 48, + "2602": 49, + "5367": 50, + "11472": 51 + }, + "files": { + "nl/nl_NL/mls/medium/nl_NL-mls-medium.onnx": { + "size_bytes": 76584246, + "md5_digest": "f1d4b1452ccfdac24be72085b2b6b55c" + }, + "nl/nl_NL/mls/medium/nl_NL-mls-medium.onnx.json": { + "size_bytes": 5856, + "md5_digest": "1915807d7cb85274ba957846370fff39" + }, + "nl/nl_NL/mls/medium/MODEL_CARD": { + "size_bytes": 225, + "md5_digest": "6e5d961780907a4d7746eada893b8eca" + } + }, + "aliases": [] + }, "nl_NL-mls_5809-low": { "key": "nl_NL-mls_5809-low", "name": "mls_5809", @@ -4863,6 +5519,36 @@ }, "aliases": [] }, + "sl_SI-artur-medium": { + "key": "sl_SI-artur-medium", + "name": "artur", + "language": { + "code": "sl_SI", + "family": "sl", + "region": "SI", + "name_native": "Slovenščina", + "name_english": "Slovenian", + "country_english": "Slovenia" + }, + "quality": "medium", + "num_speakers": 1, + "speaker_id_map": {}, + "files": { + "sl/sl_SI/artur/medium/sl_SI-artur-medium.onnx": { + "size_bytes": 63200492, + "md5_digest": "ca0aac61139e446bebf98561e8cf9407" + }, + "sl/sl_SI/artur/medium/sl_SI-artur-medium.onnx.json": { + "size_bytes": 4970, + "md5_digest": "8683796803bce4e9131eec00a93251ad" + }, + "sl/sl_SI/artur/medium/MODEL_CARD": { + "size_bytes": 329, + "md5_digest": "c7547b0d2c97f38dcbb231c5e77c75c9" + } + }, + "aliases": [] + }, "sr_RS-serbski_institut-medium": { "key": "sr_RS-serbski_institut-medium", "name": "serbski_institut", @@ -5016,6 +5702,36 @@ }, "aliases": [] }, + "tr_TR-fettah-medium": { + "key": "tr_TR-fettah-medium", + "name": "fettah", + "language": { + "code": "tr_TR", + "family": "tr", + "region": "TR", + "name_native": "Türkçe", + "name_english": "Turkish", + "country_english": "Turkey" + }, + "quality": "medium", + "num_speakers": 1, + "speaker_id_map": {}, + "files": { + "tr/tr_TR/fettah/medium/tr_TR-fettah-medium.onnx": { + "size_bytes": 63201294, + "md5_digest": "596984449bd075fc18e6412c66ed99c2" + }, + "tr/tr_TR/fettah/medium/tr_TR-fettah-medium.onnx.json": { + "size_bytes": 4877, + "md5_digest": "583aa5f4bfac5237afb1cdbdf5bfc992" + }, + "tr/tr_TR/fettah/medium/MODEL_CARD": { + "size_bytes": 276, + "md5_digest": "9c51c87dc191bd556c0634793c233d5c" + } + }, + "aliases": [] + }, "uk_UA-lada-x_low": { "key": "uk_UA-lada-x_low", "name": "lada", From b76de593a75998f849d03be58130d69a3e42df0e Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Mon, 19 Feb 2024 16:11:29 -0600 Subject: [PATCH 06/12] Relax test requirements --- requirements_dev.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index a3558be..61eb910 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -5,7 +5,7 @@ mypy==0.991 pylint==2.15.9 pytest==7.4.4 pytest-asyncio==0.23.3 -tox==4.13.0 -scipy==1.12.0 -numpy==1.26.4 +tox>=4,<5 +scipy>=1.10 +numpy>=1.20 python-speech-features==0.6 From 7b6ec487d1f2def49b5191747ae8cbf4d1bc4ac3 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Mon, 19 Feb 2024 16:12:35 -0600 Subject: [PATCH 07/12] Lint --- wyoming_piper/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/wyoming_piper/__main__.py b/wyoming_piper/__main__.py index e64ef44..dcfad76 100755 --- a/wyoming_piper/__main__.py +++ b/wyoming_piper/__main__.py @@ -218,6 +218,7 @@ def get_description(voice_info: Dict[str, Any]): # ----------------------------------------------------------------------------- + def run(): asyncio.run(main()) From 068dad3a03d2d45998fac46c749194d34a6bb5fa Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Mon, 19 Feb 2024 16:14:32 -0600 Subject: [PATCH 08/12] Increase timeout for download --- tests/test_piper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_piper.py b/tests/test_piper.py index fc620ba..e75f9a7 100644 --- a/tests/test_piper.py +++ b/tests/test_piper.py @@ -22,7 +22,7 @@ _PIPER_URL = ( "https://github.com/rhasspy/piper/releases/download/v1.2.0/piper_amd64.tar.gz" ) -_TIMEOUT = 1 +_TIMEOUT = 60 def download_piper() -> None: From 633ee62ca6352638d8b79c01c8e09c00caea1585 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Mon, 26 Feb 2024 10:37:15 -0600 Subject: [PATCH 09/12] Update dev requirements --- requirements_dev.txt | 4 ++-- wyoming_piper/__main__.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 61eb910..91c15ae 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -6,6 +6,6 @@ pylint==2.15.9 pytest==7.4.4 pytest-asyncio==0.23.3 tox>=4,<5 -scipy>=1.10 -numpy>=1.20 +scipy>=1.10,<2 +numpy>=1.20,<2 python-speech-features==0.6 diff --git a/wyoming_piper/__main__.py b/wyoming_piper/__main__.py index dcfad76..1551a39 100755 --- a/wyoming_piper/__main__.py +++ b/wyoming_piper/__main__.py @@ -73,6 +73,9 @@ async def main() -> None: ) # parser.add_argument("--debug", action="store_true", help="Log DEBUG messages") + parser.add_argument( + "--log-format", default=logging.BASIC_FORMAT, help="Format for log messages" + ) parser.add_argument( "--version", action="version", @@ -85,7 +88,9 @@ async def main() -> None: # Default to first data directory args.download_dir = args.data_dir[0] - logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) + logging.basicConfig( + level=logging.DEBUG if args.debug else logging.INFO, format=args.log_format + ) _LOGGER.debug(args) # Load voice info From 0d9bcb05d0b8bfd18b186a1ad58c886f098af982 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Mon, 26 Feb 2024 10:39:18 -0600 Subject: [PATCH 10/12] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5f5a73..d4f1a2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 1.5.0 + +- Send speakers in `info` message +- Update voices.json with new voices +- Add tests to CI + ## 1.4.0 - Fix use of UTF-8 characters in URLs From 4e5a5570cb4e8d4f19b173edb51230df782c005a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Reil=C3=A4nder?= Date: Thu, 9 Jan 2025 19:28:37 +0100 Subject: [PATCH 11/12] change --cuda to --use-cuda --- wyoming_piper/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wyoming_piper/__main__.py b/wyoming_piper/__main__.py index 1551a39..50de962 100755 --- a/wyoming_piper/__main__.py +++ b/wyoming_piper/__main__.py @@ -67,7 +67,7 @@ async def main() -> None: help="Download latest voices.json during startup", ) parser.add_argument( - "--cuda", + "--use-cuda", action="store_true", help="Use GPU" ) From 5f45edfd6cad52d8e5e88b951b9e95079d009a64 Mon Sep 17 00:00:00 2001 From: mreilaender Date: Wed, 22 Jan 2025 08:50:58 +0000 Subject: [PATCH 12/12] fix use cuda argument --- wyoming_piper/process.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wyoming_piper/process.py b/wyoming_piper/process.py index 8c136be..5744700 100644 --- a/wyoming_piper/process.py +++ b/wyoming_piper/process.py @@ -148,8 +148,8 @@ async def get_process(self, voice_name: Optional[str] = None) -> PiperProcess: if self.args.noise_w: piper_args.extend(["--noise-w", str(self.args.noise_w)]) - if self.args.cuda: - piper_args.extend(["--cuda"]) + if self.args.use_cuda: + piper_args.extend(["--use-cuda"]) _LOGGER.debug( "Starting piper process: %s args=%s", self.args.piper, piper_args