1
+ """Wrapper to call the espeak/espeak-ng phonemizer."""
2
+
1
3
import logging
2
4
import re
3
5
import subprocess
4
- from typing import Dict , List
6
+ from typing import Optional
5
7
6
8
from packaging .version import Version
7
9
11
13
logger = logging .getLogger (__name__ )
12
14
13
15
14
- def is_tool (name ):
16
+ def _is_tool (name ) -> bool :
15
17
from shutil import which
16
18
17
19
return which (name ) is not None
@@ -22,31 +24,33 @@ def is_tool(name):
22
24
espeak_version_pattern = re .compile (r"text-to-speech:\s(?P<version>\d+\.\d+(\.\d+)?)" )
23
25
24
26
25
- def get_espeak_version ():
27
+ def get_espeak_version () -> str :
28
+ """Return version of the `espeak` binary."""
26
29
output = subprocess .getoutput ("espeak --version" )
27
30
match = espeak_version_pattern .search (output )
28
31
29
32
return match .group ("version" )
30
33
31
34
32
- def get_espeakng_version ():
35
+ def get_espeakng_version () -> str :
36
+ """Return version of the `espeak-ng` binary."""
33
37
output = subprocess .getoutput ("espeak-ng --version" )
34
38
return output .split ()[3 ]
35
39
36
40
37
41
# priority: espeakng > espeak
38
- if is_tool ("espeak-ng" ):
42
+ if _is_tool ("espeak-ng" ):
39
43
_DEF_ESPEAK_LIB = "espeak-ng"
40
44
_DEF_ESPEAK_VER = get_espeakng_version ()
41
- elif is_tool ("espeak" ):
45
+ elif _is_tool ("espeak" ):
42
46
_DEF_ESPEAK_LIB = "espeak"
43
47
_DEF_ESPEAK_VER = get_espeak_version ()
44
48
else :
45
49
_DEF_ESPEAK_LIB = None
46
50
_DEF_ESPEAK_VER = None
47
51
48
52
49
- def _espeak_exe (espeak_lib : str , args : List , sync = False ) -> List [ str ]:
53
+ def _espeak_exe (espeak_lib : str , args : list , * , sync : bool = False ) -> list [ bytes ]:
50
54
"""Run espeak with the given arguments."""
51
55
cmd = [
52
56
espeak_lib ,
@@ -73,9 +77,7 @@ def _espeak_exe(espeak_lib: str, args: List, sync=False) -> List[str]:
73
77
if p .stdin :
74
78
p .stdin .close ()
75
79
return res
76
- res2 = []
77
- for line in res :
78
- res2 .append (line )
80
+ res2 = list (res )
79
81
p .stdout .close ()
80
82
if p .stderr :
81
83
p .stderr .close ()
@@ -86,7 +88,7 @@ def _espeak_exe(espeak_lib: str, args: List, sync=False) -> List[str]:
86
88
87
89
88
90
class ESpeak (BasePhonemizer ):
89
- """ESpeak wrapper calling `espeak` or `espeak-ng` from the command-line the perform G2P
91
+ """Wrapper calling `espeak` or `espeak-ng` from the command-line to perform G2P.
90
92
91
93
Args:
92
94
language (str):
@@ -111,13 +113,17 @@ class ESpeak(BasePhonemizer):
111
113
112
114
"""
113
115
114
- _ESPEAK_LIB = _DEF_ESPEAK_LIB
115
- _ESPEAK_VER = _DEF_ESPEAK_VER
116
-
117
- def __init__ (self , language : str , backend = None , punctuations = Punctuation .default_puncs (), keep_puncs = True ):
118
- if self ._ESPEAK_LIB is None :
119
- raise Exception (" [!] No espeak backend found. Install espeak-ng or espeak to your system." )
120
- self .backend = self ._ESPEAK_LIB
116
+ def __init__ (
117
+ self ,
118
+ language : str ,
119
+ backend : Optional [str ] = None ,
120
+ punctuations : str = Punctuation .default_puncs (),
121
+ keep_puncs : bool = True ,
122
+ ):
123
+ if _DEF_ESPEAK_LIB is None :
124
+ msg = "[!] No espeak backend found. Install espeak-ng or espeak to your system."
125
+ raise FileNotFoundError (msg )
126
+ self .backend = _DEF_ESPEAK_LIB
121
127
122
128
# band-aid for backwards compatibility
123
129
if language == "en" :
@@ -130,35 +136,37 @@ def __init__(self, language: str, backend=None, punctuations=Punctuation.default
130
136
self .backend = backend
131
137
132
138
@property
133
- def backend (self ):
139
+ def backend (self ) -> str :
134
140
return self ._ESPEAK_LIB
135
141
136
142
@property
137
- def backend_version (self ):
143
+ def backend_version (self ) -> str :
138
144
return self ._ESPEAK_VER
139
145
140
146
@backend .setter
141
- def backend (self , backend ) :
147
+ def backend (self , backend : str ) -> None :
142
148
if backend not in ["espeak" , "espeak-ng" ]:
143
- raise Exception ("Unknown backend: %s" % backend )
149
+ msg = f"Unknown backend: { backend } "
150
+ raise ValueError (msg )
144
151
self ._ESPEAK_LIB = backend
145
152
self ._ESPEAK_VER = get_espeakng_version () if backend == "espeak-ng" else get_espeak_version ()
146
153
147
154
def auto_set_espeak_lib (self ) -> None :
148
- if is_tool ("espeak-ng" ):
155
+ if _is_tool ("espeak-ng" ):
149
156
self ._ESPEAK_LIB = "espeak-ng"
150
157
self ._ESPEAK_VER = get_espeakng_version ()
151
- elif is_tool ("espeak" ):
158
+ elif _is_tool ("espeak" ):
152
159
self ._ESPEAK_LIB = "espeak"
153
160
self ._ESPEAK_VER = get_espeak_version ()
154
161
else :
155
- raise Exception ("Cannot set backend automatically. espeak-ng or espeak not found" )
162
+ msg = "Cannot set backend automatically. espeak-ng or espeak not found"
163
+ raise FileNotFoundError (msg )
156
164
157
165
@staticmethod
158
- def name ():
166
+ def name () -> str :
159
167
return "espeak"
160
168
161
- def phonemize_espeak (self , text : str , separator : str = "|" , tie = False ) -> str :
169
+ def phonemize_espeak (self , text : str , separator : str = "|" , * , tie : bool = False ) -> str :
162
170
"""Convert input text to phonemes.
163
171
164
172
Args:
@@ -193,7 +201,7 @@ def phonemize_espeak(self, text: str, separator: str = "|", tie=False) -> str:
193
201
args .append (text )
194
202
# compute phonemes
195
203
phonemes = ""
196
- for line in _espeak_exe (self ._ESPEAK_LIB , args , sync = True ):
204
+ for line in _espeak_exe (self .backend , args , sync = True ):
197
205
logger .debug ("line: %s" , repr (line ))
198
206
ph_decoded = line .decode ("utf8" ).strip ()
199
207
# espeak:
@@ -210,11 +218,11 @@ def phonemize_espeak(self, text: str, separator: str = "|", tie=False) -> str:
210
218
phonemes += ph_decoded .strip ()
211
219
return phonemes .replace ("_" , separator )
212
220
213
- def _phonemize (self , text , separator = None ) :
221
+ def _phonemize (self , text : str , separator : str = "" ) -> str :
214
222
return self .phonemize_espeak (text , separator , tie = False )
215
223
216
224
@staticmethod
217
- def supported_languages () -> Dict :
225
+ def supported_languages () -> dict [ str , str ] :
218
226
"""Get a dictionary of supported languages.
219
227
220
228
Returns:
@@ -224,16 +232,14 @@ def supported_languages() -> Dict:
224
232
return {}
225
233
args = ["--voices" ]
226
234
langs = {}
227
- count = 0
228
- for line in _espeak_exe (_DEF_ESPEAK_LIB , args , sync = True ):
235
+ for count , line in enumerate (_espeak_exe (_DEF_ESPEAK_LIB , args , sync = True )):
229
236
line = line .decode ("utf8" ).strip ()
230
237
if count > 0 :
231
238
cols = line .split ()
232
239
lang_code = cols [1 ]
233
240
lang_name = cols [3 ]
234
241
langs [lang_code ] = lang_name
235
242
logger .debug ("line: %s" , repr (line ))
236
- count += 1
237
243
return langs
238
244
239
245
def version (self ) -> str :
@@ -242,16 +248,12 @@ def version(self) -> str:
242
248
Returns:
243
249
str: Version of the used backend.
244
250
"""
245
- args = ["--version" ]
246
- for line in _espeak_exe (self .backend , args , sync = True ):
247
- version = line .decode ("utf8" ).strip ().split ()[2 ]
248
- logger .debug ("line: %s" , repr (line ))
249
- return version
251
+ return self .backend_version
250
252
251
253
@classmethod
252
- def is_available (cls ):
253
- """Return true if ESpeak is available else false"""
254
- return is_tool ("espeak" ) or is_tool ("espeak-ng" )
254
+ def is_available (cls ) -> bool :
255
+ """Return true if ESpeak is available else false. """
256
+ return _is_tool ("espeak" ) or _is_tool ("espeak-ng" )
255
257
256
258
257
259
if __name__ == "__main__" :
0 commit comments