wyoming-piper 1.6.3__py3-none-any.whl → 2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wyoming_piper/__main__.py +44 -24
- wyoming_piper/download.py +32 -31
- wyoming_piper/handler.py +148 -76
- wyoming_piper/voices.json +342 -91
- {wyoming_piper-1.6.3.dist-info → wyoming_piper-2.2.0.dist-info}/METADATA +19 -15
- wyoming_piper-2.2.0.dist-info/RECORD +13 -0
- {wyoming_piper-1.6.3.dist-info → wyoming_piper-2.2.0.dist-info}/WHEEL +1 -1
- wyoming_piper/process.py +0 -171
- wyoming_piper/sentence_boundary.py +0 -58
- wyoming_piper-1.6.3.dist-info/RECORD +0 -15
- {wyoming_piper-1.6.3.dist-info → wyoming_piper-2.2.0.dist-info}/entry_points.txt +0 -0
- {wyoming_piper-1.6.3.dist-info → wyoming_piper-2.2.0.dist-info}/licenses/LICENSE.md +0 -0
- {wyoming_piper-1.6.3.dist-info → wyoming_piper-2.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wyoming-piper
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Wyoming Server for Piper
|
|
5
5
|
Author-email: Michael Hansen <mike@rhasspy.org>
|
|
6
6
|
License: MIT
|
|
@@ -8,30 +8,34 @@ Project-URL: Homepage, http://github.com/rhasspy/wyoming-piper
|
|
|
8
8
|
Keywords: rhasspy,wyoming,piper,tts
|
|
9
9
|
Classifier: Development Status :: 3 - Alpha
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: Topic ::
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
-
Requires-Python: >=3.
|
|
17
|
+
Requires-Python: >=3.9
|
|
19
18
|
Description-Content-Type: text/markdown
|
|
20
19
|
License-File: LICENSE.md
|
|
21
|
-
Requires-Dist: wyoming<1.8
|
|
22
|
-
Requires-Dist: regex
|
|
20
|
+
Requires-Dist: wyoming<2,>=1.8
|
|
21
|
+
Requires-Dist: regex>=2024.11.6
|
|
22
|
+
Requires-Dist: piper-tts<2,>=1.4.0
|
|
23
|
+
Requires-Dist: sentence-stream<2,>=1.2.0
|
|
23
24
|
Provides-Extra: dev
|
|
24
|
-
Requires-Dist: black
|
|
25
|
-
Requires-Dist: flake8
|
|
26
|
-
Requires-Dist:
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist: pytest
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist: build==1.2.2.post1; extra == "dev"
|
|
25
|
+
Requires-Dist: black; extra == "dev"
|
|
26
|
+
Requires-Dist: flake8; extra == "dev"
|
|
27
|
+
Requires-Dist: mypy; extra == "dev"
|
|
28
|
+
Requires-Dist: pylint; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
31
|
+
Requires-Dist: build; extra == "dev"
|
|
32
32
|
Requires-Dist: scipy<2,>=1.10; extra == "dev"
|
|
33
33
|
Requires-Dist: numpy<2,>=1.20; extra == "dev"
|
|
34
|
-
Requires-Dist: python-speech-features
|
|
34
|
+
Requires-Dist: python-speech-features<1,>=0.6; extra == "dev"
|
|
35
|
+
Provides-Extra: zeroconf
|
|
36
|
+
Requires-Dist: piper-tts[zeroconf]; extra == "zeroconf"
|
|
37
|
+
Provides-Extra: zh
|
|
38
|
+
Requires-Dist: piper-tts[zh]; extra == "zh"
|
|
35
39
|
Dynamic: license-file
|
|
36
40
|
|
|
37
41
|
# Wyoming Piper
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
wyoming_piper/__init__.py,sha256=z1dsCtGazHHufHQpoVgNtMObt25qYBSOM85o7xgbIJA,139
|
|
2
|
+
wyoming_piper/__main__.py,sha256=I_i_zcO7Z7_mSPTJVe207jnMGKymeNlDlNph4DC_Py8,8104
|
|
3
|
+
wyoming_piper/const.py,sha256=04sCdtJ2QGuF1BQGkOuQW10og61PgH3fCnPhaYu-YoU,1015
|
|
4
|
+
wyoming_piper/download.py,sha256=At1RBaVKsTTAO71LAJn8bDeJnvJXBS0vA6iCIo6BqZs,6349
|
|
5
|
+
wyoming_piper/file_hash.py,sha256=HMuwrgEIg-bCOXHG0wE3vtjrqGD7QaA_UNfvBMXeUcY,1107
|
|
6
|
+
wyoming_piper/handler.py,sha256=LHbJLmhq9j4rZE7fBrDJhPGm1nFIegNXfRlsjFOGhno,10195
|
|
7
|
+
wyoming_piper/voices.json,sha256=Fkqd82jiQGWAZrD_q6_5adVRMaPEAWh1ehezFeGkeFw,217559
|
|
8
|
+
wyoming_piper-2.2.0.dist-info/licenses/LICENSE.md,sha256=E3RtUJ105V6iJl--8gS7fNv4SoMVsCB-mIMmy1Q4cCg,1071
|
|
9
|
+
wyoming_piper-2.2.0.dist-info/METADATA,sha256=IQlhbgewWw46T0Ot8ACoil5kZLylNU4mTLI6bkV8WsE,2614
|
|
10
|
+
wyoming_piper-2.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
11
|
+
wyoming_piper-2.2.0.dist-info/entry_points.txt,sha256=n2UgsOCQitQ5Itr20aITTWZLL2dAtaVKn5pdecXdDHE,61
|
|
12
|
+
wyoming_piper-2.2.0.dist-info/top_level.txt,sha256=t7U7-u1sK_4xy_qbTJhxQRbxle3cLQfPq2oVLezHVNU,14
|
|
13
|
+
wyoming_piper-2.2.0.dist-info/RECORD,,
|
wyoming_piper/process.py
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
import argparse
|
|
3
|
-
import asyncio
|
|
4
|
-
import json
|
|
5
|
-
import logging
|
|
6
|
-
import tempfile
|
|
7
|
-
import time
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
from typing import Any, Dict, Optional
|
|
10
|
-
|
|
11
|
-
from .download import ensure_voice_exists, find_voice
|
|
12
|
-
|
|
13
|
-
_LOGGER = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass
|
|
17
|
-
class PiperProcess:
|
|
18
|
-
"""Info for a running Piper process (one voice)."""
|
|
19
|
-
|
|
20
|
-
name: str
|
|
21
|
-
proc: "asyncio.subprocess.Process"
|
|
22
|
-
config: Dict[str, Any]
|
|
23
|
-
wav_dir: tempfile.TemporaryDirectory
|
|
24
|
-
last_used: int = 0
|
|
25
|
-
|
|
26
|
-
def get_speaker_id(self, speaker: str) -> Optional[int]:
|
|
27
|
-
"""Get speaker by name or id."""
|
|
28
|
-
return _get_speaker_id(self.config, speaker)
|
|
29
|
-
|
|
30
|
-
@property
|
|
31
|
-
def is_multispeaker(self) -> bool:
|
|
32
|
-
"""True if model has more than one speaker."""
|
|
33
|
-
return _is_multispeaker(self.config)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _get_speaker_id(config: Dict[str, Any], speaker: str) -> Optional[int]:
|
|
37
|
-
"""Get speaker by name or id."""
|
|
38
|
-
speaker_id_map = config.get("speaker_id_map", {})
|
|
39
|
-
speaker_id = speaker_id_map.get(speaker)
|
|
40
|
-
if speaker_id is None:
|
|
41
|
-
try:
|
|
42
|
-
# Try to interpret as an id
|
|
43
|
-
speaker_id = int(speaker)
|
|
44
|
-
except ValueError:
|
|
45
|
-
pass
|
|
46
|
-
|
|
47
|
-
return speaker_id
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _is_multispeaker(config: Dict[str, Any]) -> bool:
|
|
51
|
-
"""True if model has more than one speaker."""
|
|
52
|
-
return config.get("num_speakers", 1) > 1
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# -----------------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class PiperProcessManager:
|
|
59
|
-
"""Manager of running Piper processes."""
|
|
60
|
-
|
|
61
|
-
def __init__(self, args: argparse.Namespace, voices_info: Dict[str, Any]):
|
|
62
|
-
self.voices_info = voices_info
|
|
63
|
-
self.args = args
|
|
64
|
-
self.processes: Dict[str, PiperProcess] = {}
|
|
65
|
-
self.processes_lock = asyncio.Lock()
|
|
66
|
-
|
|
67
|
-
async def get_process(self, voice_name: Optional[str] = None) -> PiperProcess:
|
|
68
|
-
"""Get a running Piper process or start a new one if necessary."""
|
|
69
|
-
voice_speaker: Optional[str] = None
|
|
70
|
-
if voice_name is None:
|
|
71
|
-
# Default voice
|
|
72
|
-
voice_name = self.args.voice
|
|
73
|
-
|
|
74
|
-
if voice_name == self.args.voice:
|
|
75
|
-
# Default speaker
|
|
76
|
-
voice_speaker = self.args.speaker
|
|
77
|
-
|
|
78
|
-
assert voice_name is not None
|
|
79
|
-
|
|
80
|
-
# Resolve alias
|
|
81
|
-
voice_info = self.voices_info.get(voice_name, {})
|
|
82
|
-
voice_name = voice_info.get("key", voice_name)
|
|
83
|
-
assert voice_name is not None
|
|
84
|
-
|
|
85
|
-
piper_proc = self.processes.get(voice_name)
|
|
86
|
-
if (piper_proc is None) or (piper_proc.proc.returncode is not None):
|
|
87
|
-
# Remove if stopped
|
|
88
|
-
self.processes.pop(voice_name, None)
|
|
89
|
-
|
|
90
|
-
# Start new Piper process
|
|
91
|
-
if self.args.max_piper_procs > 0:
|
|
92
|
-
# Restrict number of running processes
|
|
93
|
-
while len(self.processes) >= self.args.max_piper_procs:
|
|
94
|
-
# Stop least recently used process
|
|
95
|
-
lru_proc_name, lru_proc = sorted(
|
|
96
|
-
self.processes.items(), key=lambda kv: kv[1].last_used
|
|
97
|
-
)[0]
|
|
98
|
-
_LOGGER.debug("Stopping process for: %s", lru_proc_name)
|
|
99
|
-
self.processes.pop(lru_proc_name, None)
|
|
100
|
-
if lru_proc.proc.returncode is None:
|
|
101
|
-
try:
|
|
102
|
-
lru_proc.proc.terminate()
|
|
103
|
-
await lru_proc.proc.wait()
|
|
104
|
-
except Exception:
|
|
105
|
-
_LOGGER.exception("Unexpected error stopping piper process")
|
|
106
|
-
|
|
107
|
-
_LOGGER.debug(
|
|
108
|
-
"Starting process for: %s (%s/%s)",
|
|
109
|
-
voice_name,
|
|
110
|
-
len(self.processes) + 1,
|
|
111
|
-
self.args.max_piper_procs,
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
ensure_voice_exists(
|
|
115
|
-
voice_name,
|
|
116
|
-
self.args.data_dir,
|
|
117
|
-
self.args.download_dir,
|
|
118
|
-
self.voices_info,
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
onnx_path, config_path = find_voice(voice_name, self.args.data_dir)
|
|
122
|
-
with open(config_path, "r", encoding="utf-8") as config_file:
|
|
123
|
-
config = json.load(config_file)
|
|
124
|
-
|
|
125
|
-
wav_dir = tempfile.TemporaryDirectory()
|
|
126
|
-
piper_args = [
|
|
127
|
-
"--model",
|
|
128
|
-
str(onnx_path),
|
|
129
|
-
"--config",
|
|
130
|
-
str(config_path),
|
|
131
|
-
"--output_dir",
|
|
132
|
-
str(wav_dir.name),
|
|
133
|
-
"--json-input", # piper 1.1+
|
|
134
|
-
]
|
|
135
|
-
|
|
136
|
-
if voice_speaker is not None:
|
|
137
|
-
if _is_multispeaker(config):
|
|
138
|
-
speaker_id = _get_speaker_id(config, voice_speaker)
|
|
139
|
-
if speaker_id is not None:
|
|
140
|
-
piper_args.extend(["--speaker", str(speaker_id)])
|
|
141
|
-
|
|
142
|
-
if self.args.noise_scale:
|
|
143
|
-
piper_args.extend(["--noise-scale", str(self.args.noise_scale)])
|
|
144
|
-
|
|
145
|
-
if self.args.length_scale:
|
|
146
|
-
piper_args.extend(["--length-scale", str(self.args.length_scale)])
|
|
147
|
-
|
|
148
|
-
if self.args.noise_w:
|
|
149
|
-
piper_args.extend(["--noise-w", str(self.args.noise_w)])
|
|
150
|
-
|
|
151
|
-
_LOGGER.debug(
|
|
152
|
-
"Starting piper process: %s args=%s", self.args.piper, piper_args
|
|
153
|
-
)
|
|
154
|
-
piper_proc = PiperProcess(
|
|
155
|
-
name=voice_name,
|
|
156
|
-
proc=await asyncio.create_subprocess_exec(
|
|
157
|
-
self.args.piper,
|
|
158
|
-
*piper_args,
|
|
159
|
-
stdin=asyncio.subprocess.PIPE,
|
|
160
|
-
stdout=asyncio.subprocess.PIPE,
|
|
161
|
-
stderr=asyncio.subprocess.DEVNULL,
|
|
162
|
-
),
|
|
163
|
-
config=config,
|
|
164
|
-
wav_dir=wav_dir,
|
|
165
|
-
)
|
|
166
|
-
self.processes[voice_name] = piper_proc
|
|
167
|
-
|
|
168
|
-
# Update used
|
|
169
|
-
piper_proc.last_used = time.monotonic_ns()
|
|
170
|
-
|
|
171
|
-
return piper_proc
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"""Guess the sentence boundaries in text."""
|
|
2
|
-
|
|
3
|
-
from collections.abc import Iterable
|
|
4
|
-
|
|
5
|
-
import regex as re
|
|
6
|
-
|
|
7
|
-
SENTENCE_END = r"[.!?…]|[。!?]|[؟]|[।॥]"
|
|
8
|
-
ABBREVIATION_RE = re.compile(r"\b\p{L}{1,3}\.$", re.UNICODE)
|
|
9
|
-
|
|
10
|
-
SENTENCE_BOUNDARY_RE = re.compile(
|
|
11
|
-
rf"(.*?(?:{SENTENCE_END}+))(?=\s+[\p{{Lu}}\p{{Lt}}\p{{Lo}}]|(?:\s+\d+\.\s+))",
|
|
12
|
-
re.DOTALL,
|
|
13
|
-
)
|
|
14
|
-
WORD_ASTERISKS = re.compile(r"\*+([^\*]+)\*+")
|
|
15
|
-
LINE_ASTERICKS = re.compile(r"(?<=^|\n)\s*\*+")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class SentenceBoundaryDetector:
|
|
19
|
-
def __init__(self) -> None:
|
|
20
|
-
self.remaining_text = ""
|
|
21
|
-
self.current_sentence = ""
|
|
22
|
-
|
|
23
|
-
def add_chunk(self, chunk: str) -> Iterable[str]:
|
|
24
|
-
self.remaining_text += chunk
|
|
25
|
-
while self.remaining_text:
|
|
26
|
-
match = SENTENCE_BOUNDARY_RE.search(self.remaining_text)
|
|
27
|
-
if not match:
|
|
28
|
-
break
|
|
29
|
-
|
|
30
|
-
match_text = match.group(0)
|
|
31
|
-
|
|
32
|
-
if not self.current_sentence:
|
|
33
|
-
self.current_sentence = match_text
|
|
34
|
-
elif ABBREVIATION_RE.search(self.current_sentence[-5:]):
|
|
35
|
-
self.current_sentence += match_text
|
|
36
|
-
else:
|
|
37
|
-
yield remove_asterisks(self.current_sentence.strip())
|
|
38
|
-
self.current_sentence = match_text
|
|
39
|
-
|
|
40
|
-
if not ABBREVIATION_RE.search(self.current_sentence[-5:]):
|
|
41
|
-
yield remove_asterisks(self.current_sentence.strip())
|
|
42
|
-
self.current_sentence = ""
|
|
43
|
-
|
|
44
|
-
self.remaining_text = self.remaining_text[match.end() :]
|
|
45
|
-
|
|
46
|
-
def finish(self) -> str:
|
|
47
|
-
text = (self.current_sentence + self.remaining_text).strip()
|
|
48
|
-
self.remaining_text = ""
|
|
49
|
-
self.current_sentence = ""
|
|
50
|
-
|
|
51
|
-
return remove_asterisks(text)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def remove_asterisks(text: str) -> str:
|
|
55
|
-
"""Remove *asterisks* surrounding **words**"""
|
|
56
|
-
text = WORD_ASTERISKS.sub(r"\1", text)
|
|
57
|
-
text = LINE_ASTERICKS.sub("", text)
|
|
58
|
-
return text
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
wyoming_piper/__init__.py,sha256=z1dsCtGazHHufHQpoVgNtMObt25qYBSOM85o7xgbIJA,139
|
|
2
|
-
wyoming_piper/__main__.py,sha256=SuJ6XY6zy68N8L-N_n_EIWK0vpZwbYQXW5vlSC8BpW8,7445
|
|
3
|
-
wyoming_piper/const.py,sha256=04sCdtJ2QGuF1BQGkOuQW10og61PgH3fCnPhaYu-YoU,1015
|
|
4
|
-
wyoming_piper/download.py,sha256=UpczxHWqLkcOblHmrwgBHSR6wG1LR-hZ4V6QSsrghns,6185
|
|
5
|
-
wyoming_piper/file_hash.py,sha256=HMuwrgEIg-bCOXHG0wE3vtjrqGD7QaA_UNfvBMXeUcY,1107
|
|
6
|
-
wyoming_piper/handler.py,sha256=WVpmnRVYmsd3DrLcMfBOsuo_J1HJ0h00-HwQ1iZEToo,7360
|
|
7
|
-
wyoming_piper/process.py,sha256=L_qqxQcQawrC940fwlv4u6KM9KjCq6N6ym-OADSZcrM,5794
|
|
8
|
-
wyoming_piper/sentence_boundary.py,sha256=pHVo92_weusnVLRVicnS0-Tst_eR-pMrnRrGL96HxC8,1875
|
|
9
|
-
wyoming_piper/voices.json,sha256=elUT3cM0Wlgo8N8E5nhMbMSCPB8zU4SY2XGKwe-T2ys,209108
|
|
10
|
-
wyoming_piper-1.6.3.dist-info/licenses/LICENSE.md,sha256=E3RtUJ105V6iJl--8gS7fNv4SoMVsCB-mIMmy1Q4cCg,1071
|
|
11
|
-
wyoming_piper-1.6.3.dist-info/METADATA,sha256=sNP4bue0pO2mFBb3xUnXfgibofOvaCFuJqN7Hik3fmQ,2543
|
|
12
|
-
wyoming_piper-1.6.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
wyoming_piper-1.6.3.dist-info/entry_points.txt,sha256=n2UgsOCQitQ5Itr20aITTWZLL2dAtaVKn5pdecXdDHE,61
|
|
14
|
-
wyoming_piper-1.6.3.dist-info/top_level.txt,sha256=t7U7-u1sK_4xy_qbTJhxQRbxle3cLQfPq2oVLezHVNU,14
|
|
15
|
-
wyoming_piper-1.6.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|