/
sconscript
453 lines (414 loc) · 17.1 KB
/
sconscript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
import enum
import typing
import os
import ctypes
import SCons.Node
import SCons.Node.FS
import SCons.Environment
from glob import glob
sourceDir: SCons.Node.FS.Dir
thirdPartyEnv: SCons.Environment.Environment
Import([
'thirdPartyEnv',
'sourceDir',
])
class AutoFreeCDLL(ctypes.CDLL):
def __del__(self):
ctypes.windll.kernel32.FreeLibrary(self._handle)
synthDriversDir=sourceDir.Dir('synthDrivers')
espeakRepo=Dir("#include/espeak")
espeakSrcDir=espeakRepo.Dir('src')
espeakIncludeDir=espeakSrcDir.Dir('include')
sonicSrcDir=Dir("#include/sonic")
class espeak_ERROR(enum.IntEnum):
EE_OK = 0
EE_INTERNAL_ERROR = -1
EE_BUFFER_FULL = 1
EE_NOT_FOUND = 2
class espeak_ng_STATUS(enum.IntEnum):
ENS_GROUP_MASK = 0x70000000
ENS_GROUP_ERRNO = 0x00000000 # Values 0 - 255 map to errno error codes.
ENS_GROUP_ESPEAK_NG = 0x10000000 # eSpeak NG error codes.
# eSpeak NG 1.49.0
ENS_OK = 0
ENS_COMPILE_ERROR = 0x100001FF
ENS_VERSION_MISMATCH = 0x100002FF
ENS_FIFO_BUFFER_FULL = 0x100003FF
ENS_NOT_INITIALIZED = 0x100004FF
ENS_AUDIO_ERROR = 0x100005FF
ENS_VOICE_NOT_FOUND = 0x100006FF
ENS_MBROLA_NOT_FOUND = 0x100007FF
ENS_MBROLA_VOICE_NOT_FOUND = 0x100008FF
ENS_EVENT_BUFFER_FULL = 0x100009FF
ENS_NOT_SUPPORTED = 0x10000AFF
ENS_UNSUPPORTED_PHON_FORMAT = 0x10000BFF
ENS_NO_SPECT_FRAMES = 0x10000CFF
ENS_EMPTY_PHONEME_MANIFEST = 0x10000DFF
ENS_SPEECH_STOPPED = 0x10000EFF
# eSpeak NG 1.49.2
ENS_UNKNOWN_PHONEME_FEATURE = 0x10000FFF
ENS_UNKNOWN_TEXT_ENCODING = 0x100010FF
class espeak_VOICE(ctypes.Structure):
_fields_=[
('name',ctypes.c_char_p),
('languages',ctypes.c_char_p),
('identifier',ctypes.c_char_p),
('gender',ctypes.c_byte),
('age',ctypes.c_byte),
('variant',ctypes.c_byte),
('xx1',ctypes.c_byte),
('score',ctypes.c_int),
('spare',ctypes.c_void_p),
]
class espeak_AUDIO_OUTPUT(enum.IntEnum):
"""From '/espeak-ng/speak_lib.h'
"""
#: PLAYBACK mode: plays the audio data, supplies events to the calling program
AUDIO_OUTPUT_PLAYBACK = 0
#: RETRIEVAL mode: supplies audio data and events to the calling program
AUDIO_OUTPUT_RETRIEVAL = 1
#: SYNCHRONOUS mode: as RETRIEVAL but doesn't return until synthesis is completed
AUDIO_OUTPUT_SYNCHRONOUS = 2
#: Synchronous playback
AUDIO_OUTPUT_SYNCH_PLAYBACK = 3
env: SCons.Environment.Environment = thirdPartyEnv.Clone()
env.Append(
CCFLAGS=[
# Whole-program optimization causes eSpeak to distort and warble with its Klatt4 voice
# Therefore specifically force it off
'/GL-',
# Ignore all warnings as the code is not ours.
'/W0',
# Preprocessor definitions. Migrated from 'nvdaHelper/espeak/config.h'
'/DPACKAGE_VERSION=\\"1.51-dev\\"', # See 'include/espeak/src/windows/config.h'
'/DHAVE_STDINT_H=1',
'/D__WIN32__#1',
'/DLIBESPEAK_NG_EXPORT',
# Define WIN32_LEAN_AND_MEAN for preprocessor to prevent windows.h including winsock causing redefinition
# errors when winsock2 is included by espeak\src\include\compat\endian.h
'/DWIN32_LEAN_AND_MEAN',
# Preprocessor definitions. Espeak Features
'/DINCLUDE_SPEECHPLAYER=1',
'/DINCLUDE_KLATT=1',
'/DHAVE_SONIC_H=1',
])
env.Append(
CPPPATH=[
'#nvdaHelper/espeak', # ensure that nvdaHelper/espeak/config.h is found first.
espeakIncludeDir,
espeakIncludeDir.Dir('compat'),
espeakSrcDir.Dir('speechPlayer/include'),
sonicSrcDir,
espeakSrcDir.Dir('ucd-tools/src/include')
])
def espeak_compilePhonemeData_buildEmitter(target,source,env):
phSourceIgnores=['error_log','error_intonation','compile_prog_log','compile_report','envelopes.png']
phSources=env.Flatten([[Dir(topDir).File(f) for f in files if f not in phSourceIgnores] for topDir,subdirs,files in os.walk(source[0].abspath)])
sources=env.Flatten([phSources])
targets=[target[0].File(f) for f in ['intonations','phondata','phondata-manifest','phonindex','phontab']]
phSideEffects=[source[0].File(x) for x in phSourceIgnores]
env.SideEffect(phSideEffects,targets)
return targets,sources
def espeak_compilePhonemeData_buildAction(target,source,env):
# We want the eSpeak dll to be freed after each dictionary.
# This is because it writes to stderr but doesn't flush it.
# Unfortunately, there's no way we can flush it or use a different stream
# because our eSpeak statically links the CRT.
espeak=AutoFreeCDLL(espeakLib[0].abspath)
espeak.espeak_ng_InitializePath(os.fsencode(espeakRepo.abspath))
espeak.espeak_ng_CompileIntonation(None,None)
espeak.espeak_ng_CompilePhonemeData(22050,None,None)
espeak.espeak_Terminate()
env['BUILDERS']['espeak_compilePhonemeData']=Builder(action=env.Action(espeak_compilePhonemeData_buildAction,"Compiling phoneme data"),emitter=espeak_compilePhonemeData_buildEmitter)
#: See dictionaries section of /include/espeak/Makefile.am
espeakDictionaryCompileList: typing.Dict[
str, # expected dict file name EG 'es_dict'
typing.Tuple[str, typing.List[str]] # language code, list of input files
] = {
"af_dict": ("af", ["af_list", "af_rules", ]),
"am_dict": ("am", ["am_list", "am_rules", ]),
"an_dict": ("an", ["an_list", "an_rules", ]),
"ar_dict": ("ar", ["ar_listx", "ar_list", "ar_rules"]),
"as_dict": ("as", ["as_list", "as_rules", ]),
"az_dict": ("az", ["az_list", "az_rules", ]),
"ba_dict": ("ba", ["ba_list", "ba_rules", ]),
"bg_dict": ("bg", ["bg_listx", "bg_list", "bg_rules"]),
"bn_dict": ("bn", ["bn_list", "bn_rules", ]),
"bpy_dict": ("bpy", ["bpy_list", "bpy_rules", ]),
"bs_dict": ("bs", ["bs_list", "bs_rules", ]),
"ca_dict": ("ca", ["ca_list", "ca_rules", ]),
"chr_dict": ("chr", ["chr_list", "chr_rules", ]),
"cmn_dict": ("cmn", ["cmn_listx", "cmn_list", "cmn_rules"]),
"cs_dict": ("cs", ["cs_list", "cs_rules", ]),
"cv_dict": ("cv", ["cv_list", "cv_rules", ]),
"cy_dict": ("cy", ["cy_list", "cy_rules", ]),
"da_dict": ("da", ["da_list", "da_rules", ]),
"de_dict": ("de", ["de_list", "de_rules", ]),
"el_dict": ("el", ["el_list", "el_rules", ]),
"en_dict": ("en", ["en_list", "en_rules", ]),
"eo_dict": ("eo", ["eo_list", "eo_rules", ]),
"es_dict": ("es", ["es_list", "es_rules", ]),
"et_dict": ("et", ["et_list", "et_rules", ]),
"eu_dict": ("eu", ["eu_list", "eu_rules", ]),
"fa_dict": ("fa", ["fa_list", "fa_rules", ]),
"fi_dict": ("fi", ["fi_list", "fi_rules", ]),
"fr_dict": ("fr", ["fr_list", "fr_rules", ]),
"ga_dict": ("ga", ["ga_list", "ga_rules", ]),
"gd_dict": ("gd", ["gd_list", "gd_rules", ]),
"gn_dict": ("gn", ["gn_list", "gn_rules", ]),
"grc_dict": ("grc", ["grc_list", "grc_rules", ]),
"gu_dict": ("gu", ["gu_list", "gu_rules", ]),
"hak_dict": ("hak", ["hak_list", "hak_rules", ]),
"haw_dict": ("haw", ["haw_list", "haw_rules", ]),
"he_dict": ("he", ["he_listx", "he_list", "he_rules"]),
"hi_dict": ("hi", ["hi_list", "hi_rules", ]),
"hr_dict": ("hr", ["hr_list", "hr_rules", ]),
"ht_dict": ("ht", ["ht_list", "ht_rules", ]),
"hu_dict": ("hu", ["hu_list", "hu_rules", ]),
"hy_dict": ("hy", ["hy_list", "hy_rules", ]),
"ia_dict": ("ia", ["ia_listx", "ia_list", "ia_rules"]),
"id_dict": ("id", ["id_list", "id_rules", ]),
"io_dict": ("io", ["io_list", "io_rules", ]),
"is_dict": ("is", ["is_list", "is_rules", ]),
"it_dict": ("it", ["it_listx", "it_list", "it_rules"]),
"ja_dict": ("ja", ["ja_list", "ja_rules", ]),
"jbo_dict": ("jbo", ["jbo_list", "jbo_rules", ]),
"ka_dict": ("ka", ["ka_list", "ka_rules", ]),
"kk_dict": ("kk", ["kk_list", "kk_rules", ]),
"kl_dict": ("kl", ["kl_list", "kl_rules", ]),
"kn_dict": ("kn", ["kn_list", "kn_rules", ]),
"kok_dict": ("kok", ["kok_list", "kok_rules", ]),
"ko_dict": ("ko", ["ko_list", "ko_rules", ]),
"ku_dict": ("ku", ["ku_list", "ku_rules", ]),
"ky_dict": ("ky", ["ky_list", "ky_rules", ]),
"la_dict": ("la", ["la_list", "la_rules", ]),
"lfn_dict": ("lfn", ["lfn_list", "lfn_rules", ]),
"lt_dict": ("lt", ["lt_list", "lt_rules", ]),
"lv_dict": ("lv", ["lv_list", "lv_rules", ]),
"mi_dict": ("mi", ["mi_list", "mi_rules", ]),
"mk_dict": ("mk", ["mk_list", "mk_rules", ]),
"ml_dict": ("ml", ["ml_list", "ml_rules", ]),
"mr_dict": ("mr", ["mr_list", "mr_rules", ]),
"ms_dict": ("ms", ["ms_list", "ms_rules", ]),
"mt_dict": ("mt", ["mt_list", "mt_rules", ]),
"my_dict": ("my", ["my_list", "my_rules", ]),
"nci_dict": ("nci", ["nci_list", "nci_rules", ]),
"ne_dict": ("ne", ["ne_list", "ne_rules", ]),
"nl_dict": ("nl", ["nl_list", "nl_rules", ]),
"nog_dict": ("nog", ["nog_list", "nog_rules", ]),
"no_dict": ("no", ["no_list", "no_rules", ]),
"om_dict": ("om", ["om_list", "om_rules", ]),
"or_dict": ("or", ["or_list", "or_rules", ]),
"pap_dict": ("pap", ["pap_list", "pap_rules", ]),
"pa_dict": ("pa", ["pa_list", "pa_rules", ]),
"piqd_dict": ("piqd", ["piqd_list", "piqd_rules", ]),
"pl_dict": ("pl", ["pl_list", "pl_rules", ]),
"pt_dict": ("pt", ["pt_list", "pt_rules", ]),
"py_dict": ("py", ["py_list", "py_rules", ]),
"qdb_dict": ("qdb", ["qdb_list", "qdb_rules", ]),
"quc_dict": ("quc", ["quc_list", "quc_rules", ]),
"qya_dict": ("qya", ["qya_list", "qya_rules", ]),
"qu_dict": ("qu", ["qu_list", "qu_rules", ]),
"ro_dict": ("ro", ["ro_list", "ro_rules", ]),
"ru_dict": ("ru", ["ru_listx", "ru_list", "ru_rules"]),
"sd_dict": ("sd", ["sd_list", "sd_rules", ]),
"shn_dict": ("shn", ["shn_list", "shn_rules", ]),
"si_dict": ("si", ["si_list", "si_rules", ]),
"sjn_dict": ("sjn", ["sjn_list", "sjn_rules", ]),
"sk_dict": ("sk", ["sk_list", "sk_rules", ]),
"sl_dict": ("sl", ["sl_list", "sl_rules", ]),
"smj_dict": ("smj", ["smj_list", "smj_rules", ]),
"sq_dict": ("sq", ["sq_list", "sq_rules", ]),
"sr_dict": ("sr", ["sr_list", "sr_rules", ]),
"sv_dict": ("sv", ["sv_list", "sv_rules", ]),
"sw_dict": ("sw", ["sw_list", "sw_rules", ]),
"ta_dict": ("ta", ["ta_list", "ta_rules", ]),
"te_dict": ("te", ["te_list", "te_rules", ]),
"th_dict": ("th", ["th_list", "th_rules", ]),
"tk_dict": ("tk", ["tk_listx", "tk_list", "tk_rules"]),
"tn_dict": ("tn", ["tn_list", "tn_rules", ]),
"tr_dict": ("tr", ["tr_listx", "tr_list", "tr_rules"]),
"tt_dict": ("tt", ["tt_list", "tt_rules", ]),
"ug_dict": ("ug", ["ug_list", "ug_rules", ]),
"uk_dict": ("uk", ["uk_list", "uk_rules", ]),
"ur_dict": ("ur", ["ur_list", "ur_rules", ]),
"uz_dict": ("uz", ["uz_list", "uz_rules", ]),
"vi_dict": ("vi", ["vi_list", "vi_rules", ]),
"yue_dict": ("yue", ["yue_list", "yue_listx", "yue_rules"]),
}
def espeak_compileDict_buildAction(
target: typing.List[SCons.Node.FS.File],
source: typing.List[SCons.Node.FS.File],
env: SCons.Environment.Environment
) -> int:
"""
@param target: The langCode_dict file to build
@param source: The langCode_[rules|list|listx] files that are used as inputs
@param env: Scons build environment
@return From SCons docs: "Return 0 or None to indicate a successful build of
the target file(s). The function may raise an exception or return a non-zero
exit status to indicate an unsuccessful build."
"""
if len(target) != 1:
targetStrings = list((str(t) for t in target))
raise ValueError(f"Unexpected number of targets: {targetStrings}")
target = target[0]
if not source:
raise ValueError(f"No source files provided: {source!s}")
# All source files are in the same directory, just use the first one.
dirForRules: SCons.Node.FS.Base = source[0].dir
ACTION_SUCCESS = 0
ACTION_FAILURE = 1
# We want the eSpeak dll to be freed after each dictionary.
# This is because it writes to stderr but doesn't flush it.
# Unfortunately, there's no way we can flush it or use a different stream
# because our eSpeak statically links the CRT.
espeak=AutoFreeCDLL(espeakLib[0].abspath)
# from: espeak-ng/speak_lib.h
espeakINITIALIZE_DONT_EXIT = 0x8000
# see: libespeak-ng/espeak_api.c for espeak_Initialize
espeak.espeak_Initialize(
espeak_AUDIO_OUTPUT.AUDIO_OUTPUT_PLAYBACK, # espeak_AUDIO_OUTPUT output_type
0, # int buf_length
os.fsencode(target.Dir('..').abspath), # const char *path
espeakINITIALIZE_DONT_EXIT # int options
)
try: # ensure that espeak_Terminate is called
lang = espeakDictionaryCompileList[target.name][0]
voice = espeak_VOICE(languages=lang.encode() + b'\x00')
# see: espeak-ng/speak_lib.h for espeak_SetVoiceByProperties
# returns: espeak_ERROR
setVoiceResult = espeak.espeak_SetVoiceByProperties(ctypes.byref(voice))
if espeak_ERROR.EE_OK.value != setVoiceResult:
print(
f"Failed to switch to language: '{lang}'"
f"\n result: {espeak_ERROR(setVoiceResult)!s}"
)
return ACTION_FAILURE
rulesPathEncoded = os.fsencode(dirForRules.abspath + '/')
# see: espeak-ng/espeak_ng.h for espeak_ng_CompileDictionary
# returns: espeak_ng_STATUS
compileDictResult = espeak.espeak_ng_CompileDictionary(
rulesPathEncoded, # const char *dsource
None, # const char *dict_name
None, # FILE *log
0, # int flags
None, # espeak_ng_ERROR_CONTEXT *context
)
if espeak_ERROR.EE_OK.value != compileDictResult:
print(
f"Failed to compile dictionary: '{target}'"
f"\n rulesPath: {rulesPathEncoded}"
f"\n language: '{lang}'"
f"\n result: {espeak_ng_STATUS(compileDictResult)!s}"
)
return ACTION_FAILURE
finally:
espeak.espeak_Terminate()
return ACTION_SUCCESS
sonicLib=env.StaticLibrary(
target='sonic',
srcdir=sonicSrcDir.abspath,
source='sonic.c',
)
espeakLib=env.SharedLibrary(
target='espeak',
srcdir=espeakSrcDir.Dir('libespeak-ng').abspath,
source=[
# compare to src_libespeak_ng_la_SOURCES in espeak Makefile.am
"../ucd-tools/src/case.c",
"../ucd-tools/src/categories.c",
"../ucd-tools/src/ctype.c",
"../ucd-tools/src/proplist.c",
"../ucd-tools/src/scripts.c",
"../ucd-tools/src/tostring.c",
"compiledata.c",
"compiledict.c",
# "compilembrola.c", # we dont use MBROLA, this is a compile option in espeak
"dictionary.c",
"encoding.c",
"error.c",
"espeak_api.c",
"ieee80.c",
"intonation.c",
"klatt.c", # we do use KLATT, this is a compile option in espeak
# "mbrowrap.c", # we don't use MBROLA, this is a compile option in espeak
"mnemonics.c",
"numbers.c",
"phoneme.c",
"phonemelist.c",
"readclause.c",
"setlengths.c",
"soundIcon.c",
"spect.c",
"speech.c",
"ssml.c",
"synthdata.c",
"synthesize.c",
"synth_mbrola.c", # provides symbols used by synthesize.obj, voices.obj, and wavegen.obj
"translate.c",
"tr_languages.c",
"voices.c",
"wavegen.c",
sonicLib,
# espeak OPT_SPEECHPLAYER block
"sPlayer.c",
"../speechPlayer/src/frame.cpp",
"../speechPlayer/src/speechPlayer.cpp",
"../speechPlayer/src/speechWaveGenerator.cpp",
#"../speak-ng.cpp",
# if not OPT_SPEECHPLAYER
# "../speak-ng.c",
# espeak does not need to handle its own audio output so dont include:
# pcaudiolib\src\audio.c
# pcaudiolib\src\windows.c
# pcaudiolib\src\xaudio2.cpp
# These are for SAPI5, we dont need them:
# com\comentrypoints.c
# com\ttsengine.cpp
# We do not use the ASYNC compile option in espeak.
],
LIBS=['advapi32'],
)
phonemeData=env.espeak_compilePhonemeData(espeakRepo.Dir('espeak-ng-data'),espeakRepo.Dir('phsource'))
env.Depends(phonemeData,espeakLib)
for i in phonemeData:
iDir = espeakRepo.Dir('espeak-ng-data').abspath
l = len(iDir) + 1
fileName = i.abspath[l:]
env.InstallAs(os.path.join(synthDriversDir.Dir('espeak-ng-data').abspath, fileName), i)
# Move any extra dictionaries into dictsource for compilation
env.Install(espeakRepo.Dir('dictsource'),env.Glob(os.path.join(espeakRepo.abspath,'dictsource','extra','*_*')))
#Compile all dictionaries
excludeLangs: typing.List[str] = [] # Use to exclude languages which don't compile.
dictSourcePath: SCons.Node.FS.Dir = espeakRepo.Dir('dictsource')
# Remove emoji files before compiling dictionaries.
# Currently many of these simply crash eSpeak at runtime.
# Also, our own emoji processing using CLDR data is preferred.
emojiGlob = os.path.join(espeakRepo.abspath,'dictsource','*_emoji')
for f in glob(emojiGlob):
print("Removing emoji file: %s"%f)
os.remove(f)
# Create compile commands for all languages
for dictFileName, (langCode, inputFiles) in espeakDictionaryCompileList.items():
if langCode in excludeLangs: continue
dictFilePath = espeakRepo.Dir('espeak-ng-data').File(dictFileName)
dictFile = env.Command(
target=dictFilePath,
source=list((dictSourcePath.File(f) for f in inputFiles)),
action=espeak_compileDict_buildAction
)
env.Depends(dictFile, [espeakLib, phonemeData])
# Dictionaries can not be compiled in parallel, force SCons not to do this
env.SideEffect('_espeak_compileDict',dictFile)
env.InstallAs( # Install files to the "synthDrivers/espeak-ng-data/" dir.
os.path.join(synthDriversDir.Dir('espeak-ng-data').abspath, dictFileName),
dictFile
)
env.Install(synthDriversDir,espeakLib)
# install espeak-ng-data
targetEspeakDataDir=synthDriversDir.Dir('espeak-ng-data')
espeakDataSource=espeakRepo.Dir('espeak-ng-data')
# also install the lang and voices/!v directories. Exclude the voices/mb directory since we are not using mbrola.
env.RecursiveInstall(targetEspeakDataDir.Dir('lang'),espeakDataSource.Dir('lang').abspath)
env.RecursiveInstall(targetEspeakDataDir.Dir('voices').Dir('!v'),espeakDataSource.Dir('voices').Dir('!v').abspath)