echogarden 3.3.0 → 3.4.0
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.
- package/README.md +5 -0
- package/dist/api/Synthesis.d.ts.map +1 -1
- package/dist/api/Synthesis.js +31 -21
- package/dist/api/Synthesis.js.map +1 -1
- package/docs/API.md +51 -34
- package/docs/CLI.md +41 -6
- package/docs/Contributing.md +1 -1
- package/docs/Development.md +22 -18
- package/docs/Engines.md +0 -1
- package/docs/Licenses.md +2 -0
- package/docs/Options.md +56 -4
- package/docs/Releases.md +45 -14
- package/docs/Server.md +14 -5
- package/docs/Tasklist.md +37 -1
- package/docs/Technical.md +1 -0
- package/package.json +14 -12
- package/src/api/Synthesis.ts +39 -27
package/docs/Server.md
CHANGED
|
@@ -27,19 +27,22 @@ For Node.js clients, a simple client class allows to wrap communications with th
|
|
|
27
27
|
Currently, the client is embedded in the main codebase. This means you have to import the `echogarden` package to use it:
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
|
-
import { WebSocket } from 'ws'
|
|
31
30
|
import { Client } from 'echogarden'
|
|
31
|
+
import { WebSocket } from 'ws'
|
|
32
32
|
|
|
33
33
|
const ws = new WebSocket('ws://localhost:45054')
|
|
34
34
|
|
|
35
|
-
ws.on(
|
|
35
|
+
ws.on('open', async () => {
|
|
36
36
|
const client = new Client(ws)
|
|
37
37
|
|
|
38
|
-
const { audio } = await client.synthesize(
|
|
38
|
+
const { audio } = await client.synthesize('Hello World', {
|
|
39
|
+
engine: 'espeak',
|
|
40
|
+
})
|
|
39
41
|
})
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
**TODO**:
|
|
45
|
+
|
|
43
46
|
* Separate the client to an independent, lightweight, Node.js package, with browser compatibility
|
|
44
47
|
* Add support for cancellation signals
|
|
45
48
|
* Document how to use with a background worker
|
|
@@ -72,6 +75,7 @@ The `messageType` property is a string representing the operation to perform. Th
|
|
|
72
75
|
When sending a message, `requestId` should contain a long random string that uniquely identifies your request, like `cb7e0f3ec835a213b005c4424c8d5775`.
|
|
73
76
|
|
|
74
77
|
For example, this message requests synthesis:
|
|
78
|
+
|
|
75
79
|
```ts
|
|
76
80
|
{
|
|
77
81
|
messageType: 'SynthesisRequest',
|
|
@@ -130,16 +134,21 @@ To cancel an existing request, the client can send a `CancellationRequest` messa
|
|
|
130
134
|
## Starting the server programmatically
|
|
131
135
|
|
|
132
136
|
You can use the `startServer` method to start a new server.
|
|
137
|
+
|
|
133
138
|
```ts
|
|
134
|
-
async function startServer(
|
|
139
|
+
async function startServer(
|
|
140
|
+
serverOptions: ServerOptions,
|
|
141
|
+
onStarted: (options: ServerOptions) => void,
|
|
142
|
+
)
|
|
135
143
|
```
|
|
136
144
|
|
|
137
145
|
Example:
|
|
146
|
+
|
|
138
147
|
```ts
|
|
139
148
|
import { startServer } from 'echogarden'
|
|
140
149
|
|
|
141
150
|
await startServer({ port: 1234 }, () => {
|
|
142
|
-
console.log(
|
|
151
|
+
console.log('Server is started!')
|
|
143
152
|
})
|
|
144
153
|
```
|
|
145
154
|
|
package/docs/Tasklist.md
CHANGED
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
* Option to disable single sentence per cue
|
|
12
12
|
|
|
13
13
|
### Browser extension
|
|
14
|
+
|
|
14
15
|
* Investigate why WebSpeech events sometimes completely stop working in the middle of an utterance for no apparent reason. Sometimes this is permanent, until the extension is restarted. Is this a browser issue?
|
|
15
16
|
* If a request is made and the server takes too much time to respond, the service worker may sleep and the request never canceled
|
|
16
17
|
|
|
17
18
|
### Browser extension / content script
|
|
19
|
+
|
|
18
20
|
* Highlighting sometimes does not appear when mouse is pressed over handle while speech of element starts
|
|
19
21
|
|
|
20
22
|
### External bugs
|
|
@@ -31,6 +33,7 @@
|
|
|
31
33
|
## Features and enhancements
|
|
32
34
|
|
|
33
35
|
### CLI
|
|
36
|
+
|
|
34
37
|
* Show names of files written to disk. This is useful for cases where a file is auto-renamed to prevent overwriting existing data
|
|
35
38
|
* Restrict input media file extensions to ensure that invalid files are not passed to FFmpeg
|
|
36
39
|
* Show a message when a new version is available
|
|
@@ -46,28 +49,35 @@
|
|
|
46
49
|
* Mode to print IPA words when speaking
|
|
47
50
|
|
|
48
51
|
### CLI / playback
|
|
52
|
+
|
|
49
53
|
* Option to set audio output device for playback
|
|
50
54
|
* Option to set playback volume
|
|
51
55
|
* Maybe find a way not to pre-normalize if the audio is silent (to prevent a 30dB increase of possible noise)
|
|
52
56
|
* Add phone playback support
|
|
53
57
|
|
|
54
58
|
### CLI / `speak`
|
|
59
|
+
|
|
55
60
|
* Add support for sentence templates, like `echogarden speak-file text.txt /parts/[sentence].wav`
|
|
56
61
|
|
|
57
62
|
### CLI / `speak-wikipedia`
|
|
63
|
+
|
|
58
64
|
* Correctly detect language when a Wikipedia URL is passed instead of an article name
|
|
59
65
|
* Add option to set language edition separately from language, since Wikipedia language editions has its own code system that is slightly different from the standard one, in some cases
|
|
60
66
|
|
|
61
67
|
### CLI / `speak-url`
|
|
68
|
+
|
|
62
69
|
* Use the Wikipedia reader when the URL is detected to be from `wikipedia.org`
|
|
63
70
|
|
|
64
71
|
### CLI / `list-voices`
|
|
72
|
+
|
|
65
73
|
* When given a configuration file, see if you can fall back to take options from `speak` options, for example, to take API keys that are required for both the synthesis request and voice list request and
|
|
66
74
|
|
|
67
75
|
### CLI / `list-packages`
|
|
76
|
+
|
|
68
77
|
* Support filters
|
|
69
78
|
|
|
70
79
|
### CLI / New commands
|
|
80
|
+
|
|
71
81
|
* `play-with-subtitles`: Preview subtitles in terminal
|
|
72
82
|
* `play-with-timeline`: Preview timeline in terminal
|
|
73
83
|
* `subtitles-to-text`, `subtitles-to-timeline`, `srt-to-vtt`, `vtt-to-srt`
|
|
@@ -78,21 +88,25 @@
|
|
|
78
88
|
* `speak-youtube-subtitles`: To speak the subtitles of a YouTube video
|
|
79
89
|
|
|
80
90
|
### API
|
|
91
|
+
|
|
81
92
|
* Allow callers from API to cancel a task via `AbortController` and `AbortSignal`
|
|
82
93
|
* Validate timelines to ensure timestamps are always increasing: no undefined timestamps, no negative timestamps, out-of-order timestamps, or timestamps over the duration of the audio. No sentences without words, etc. Missing or incorrect word offsets, etc.
|
|
83
94
|
* Add support for phrases in timelines
|
|
84
95
|
* Accept voice list caching options in `SynthesisOptions`
|
|
85
96
|
|
|
86
97
|
### Package manager
|
|
98
|
+
|
|
87
99
|
* Better error message when a package is not found remotely. Currently, it just gives a `404 not found` without any other information
|
|
88
100
|
* Retry on network failure
|
|
89
101
|
|
|
90
102
|
### Speech language detection
|
|
91
103
|
|
|
92
104
|
### Text language detection
|
|
105
|
+
|
|
93
106
|
* Deploy and add the new n-gram based text language detection model
|
|
94
107
|
|
|
95
108
|
### Subtitles
|
|
109
|
+
|
|
96
110
|
* Split long words if needed
|
|
97
111
|
* Decide how many punctuation characters to allow before breaking to a new line (currently it's infinite)
|
|
98
112
|
* If a subtitle is too short and at the end of the audio, try to extend it back if possible (for example, if the previous subtitle is already extended, take back from it)
|
|
@@ -101,6 +115,7 @@
|
|
|
101
115
|
* Parse VTT's language
|
|
102
116
|
|
|
103
117
|
### Synthesis
|
|
118
|
+
|
|
104
119
|
* Option to disable alignment (only for some engines). Alternative: use a low granularity DTW setting that is very fast to compute
|
|
105
120
|
* Find places to add commas (",") to improve speech fluency. VITS voices don't normally add speech breaks if there is no punctuation
|
|
106
121
|
* An isolated dash " - " can maybe be converted to a " , " to ensure there's a break in the speech
|
|
@@ -117,6 +132,7 @@
|
|
|
117
132
|
* When `splitToSentences` is set to `false`, the timeline doesn't include proper sentences. Find a way to pass larger sections to the TTS, but still have proper sentences in the timeline
|
|
118
133
|
|
|
119
134
|
### Synthesis / preprocessing
|
|
135
|
+
|
|
120
136
|
* Full date normalization (e.g. `21 August 2023`, `21 Aug 2023`, `August 21, 2023`)
|
|
121
137
|
* Add support for capitalized-only rules, and possibly also all uppercase / all lowercase rules
|
|
122
138
|
* Add support for multiple consecutive words in `precededBy` and `followedBy` conditions
|
|
@@ -130,6 +146,7 @@
|
|
|
130
146
|
* `≈` symbol as a word character
|
|
131
147
|
|
|
132
148
|
### Synthesis / VITS
|
|
149
|
+
|
|
133
150
|
* Consider adding `⦁︎` (when surrounded by whitespace) as phrase separator
|
|
134
151
|
* Allow limiting how many models are cached in memory.
|
|
135
152
|
* Ensure that caching behaves correctly when the same model is used with different execution providers
|
|
@@ -138,49 +155,60 @@
|
|
|
138
155
|
* Add speaker names to voice list somehow
|
|
139
156
|
|
|
140
157
|
### Synthesis / Kokoro
|
|
158
|
+
|
|
141
159
|
* Consider adding `⦁︎` (when surrounded by whitespace) as phrase separator
|
|
142
160
|
* Ensure that caching behaves correctly when the same model is used with different execution providers
|
|
143
161
|
|
|
144
162
|
### Synthesis / Azure Cognitive Services
|
|
163
|
+
|
|
145
164
|
* Currently, when input is set to be SSML, it is wrapped in a `<speak>` tag. Handle the case where the user made their own SSML document wrapped with a `<speak>` tag as well. Currently, it may send invalid input to Azure
|
|
146
165
|
|
|
147
166
|
### Recognition
|
|
167
|
+
|
|
148
168
|
* Add token UTF-8 bytes in token entries on timeline. Some tokens are only a part of a codepoint, so this will allow to know exactly what they contain - the string representation is would be a Unicode error symbol in that case
|
|
149
169
|
* Show alternatives when playing in the CLI. Clear current line and rewrite already printed text for alternatives during the speech recognition process
|
|
150
170
|
|
|
151
171
|
### Recognition / Whisper
|
|
172
|
+
|
|
152
173
|
* Whisper's Chinese and Japanese output can be split into words in a more accurate way. Consider using a dedicated segmentation library to perform the segmentation in character sequences that have no punctuation characters to aid on guessing word boundaries
|
|
153
|
-
* Cache last model (if enough memory is available). Ensure that caching works when switching between
|
|
174
|
+
* Cache last model (if enough memory is available). Ensure that caching works when switching between different execution providers
|
|
154
175
|
* Whisper timestamp tokens can be used to split into segments, otherwise it is possible to try to guess using pause lengths or voice activity detection
|
|
155
176
|
* Bring back the option to use eSpeak DTW based alignment on segments, as an alternative approach
|
|
156
177
|
|
|
157
178
|
### Alignment
|
|
158
179
|
|
|
159
180
|
### Alignment / DTW
|
|
181
|
+
|
|
160
182
|
* For the `granularity` option, add more granularities like `xxx-low` and `xxxx-low` (should the naming be changed? Maybe transition to a new naming scheme?)
|
|
161
183
|
* Add and test official support for more than 6 hours of audio
|
|
162
184
|
|
|
163
185
|
### Alignment / DTW-RA
|
|
164
186
|
|
|
165
187
|
### Alignment / Whisper
|
|
188
|
+
|
|
166
189
|
* Show same token stats as recognition now does on `trace` mode
|
|
167
190
|
|
|
168
191
|
### Source separation / MDX-NET
|
|
192
|
+
|
|
169
193
|
* Option to customize overlap
|
|
170
194
|
|
|
171
195
|
### Server
|
|
196
|
+
|
|
172
197
|
* Option to allow or disallow local file paths as arguments to API methods (as a security safeguard)
|
|
173
198
|
|
|
174
199
|
### Worker
|
|
200
|
+
|
|
175
201
|
* Add cancellation checks in more operations
|
|
176
202
|
* Support more operations
|
|
177
203
|
|
|
178
204
|
### Browser extension
|
|
205
|
+
|
|
179
206
|
* Options UI
|
|
180
207
|
* Add supported engines and voices to WebSpeech voice list
|
|
181
208
|
* Pause and resume support
|
|
182
209
|
|
|
183
210
|
### Browser extension / content script
|
|
211
|
+
|
|
184
212
|
* Autoscroll should work even if the scrollbar relevant to the target element is not the viewport's scrollbar
|
|
185
213
|
* Find a way to show handles even for elements that start with a link
|
|
186
214
|
* Add detection for line breaks in `pre` blocks
|
|
@@ -216,6 +244,7 @@
|
|
|
216
244
|
## Future features and enhancements
|
|
217
245
|
|
|
218
246
|
### CLI
|
|
247
|
+
|
|
219
248
|
* Auto-generate options file, with comments, based on default options of the API
|
|
220
249
|
* Have the CLI launch a background worker (in a thread) to enable better parallelism
|
|
221
250
|
* Playback result audio while synthesis or recognition is still processing in the background
|
|
@@ -225,23 +254,29 @@
|
|
|
225
254
|
* Markdown file as text input?
|
|
226
255
|
|
|
227
256
|
### OpenAI compatible local server
|
|
257
|
+
|
|
228
258
|
* `echogarden serve` would serve a OpenAI-compatible server for all speech recognition, speech translation and speech synthesis engines
|
|
229
259
|
|
|
230
260
|
### Web
|
|
261
|
+
|
|
231
262
|
* Web based frontend UI to the server
|
|
232
263
|
* Adapt some WASM modules to also run on the web
|
|
233
264
|
* Investigate running in WebContainer
|
|
234
265
|
|
|
235
266
|
### API
|
|
267
|
+
|
|
236
268
|
* Auto-install npm modules when needed using an approach similar to like `npm-programmatic`
|
|
237
269
|
|
|
238
270
|
### Text enhancement
|
|
271
|
+
|
|
239
272
|
* Add capitalization and punctuation to recognized outputs if needed (Silero has a model for it for `en`, `de`, `ru`, `es`, but in `.pt` format only)
|
|
240
273
|
|
|
241
274
|
### Synthesis
|
|
275
|
+
|
|
242
276
|
* Synthesize the given subtitle file and try to preserve the existing timing of cues, or even align to existing speech
|
|
243
277
|
|
|
244
278
|
### Recognition
|
|
279
|
+
|
|
245
280
|
* Low latency, streaming recognition mode. Make the partial transcription available as fast as possible
|
|
246
281
|
* Live input / microphone recognition
|
|
247
282
|
* Implement beam search for Whisper decoder
|
|
@@ -249,6 +284,7 @@
|
|
|
249
284
|
* Investigate exporting Whisper models to 16-bit quantized ONNX or a mix of 16-bit and 32-bit
|
|
250
285
|
|
|
251
286
|
### Alignment
|
|
287
|
+
|
|
252
288
|
* Method to align audio file to audio file
|
|
253
289
|
* Allow `dtw` mode work with more speech synthesizers to produce its reference
|
|
254
290
|
* Predict timing for individual letters (graphemes) based on phoneme timestamps (especially useful for Chinese and Japanese)
|
package/docs/Technical.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echogarden",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "An easy-to-use speech toolset. Includes tools for synthesis, recognition, alignment, speech translation, language detection, source separation and more.",
|
|
5
5
|
"author": "Rotem Dan",
|
|
6
6
|
"license": "MIT AND GPL-3.0",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"tsconfig.json"
|
|
45
45
|
],
|
|
46
46
|
"scripts": {
|
|
47
|
-
"test": "
|
|
47
|
+
"test": "vitest",
|
|
48
|
+
"test-manual": "node --experimental-wasi-unstable-preview1 --no-warnings --trace-uncaught ./dist/tests/Test.js",
|
|
48
49
|
"generate-options-schema": "npx ts-json-schema-generator --path ./src/api/APIOptions.ts --unstable --type APIOptions --tsconfig ./tsconfig.json --out ./data/schemas/options.json",
|
|
49
50
|
"make-tarballs": "node --no-warnings ./dist/build-tools/MakeTarballsForInstalledPackages.js"
|
|
50
51
|
},
|
|
@@ -52,8 +53,8 @@
|
|
|
52
53
|
"echogarden": "./dist/cli/CLILauncher.js"
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|
|
55
|
-
"@aws-sdk/client-polly": "~3.
|
|
56
|
-
"@aws-sdk/client-transcribe-streaming": "~3.
|
|
56
|
+
"@aws-sdk/client-polly": "~3.1127.0",
|
|
57
|
+
"@aws-sdk/client-transcribe-streaming": "~3.1127.0",
|
|
57
58
|
"@echogarden/audio-io": "~0.4.2",
|
|
58
59
|
"@echogarden/fasttext-wasm": "~0.1.0",
|
|
59
60
|
"@echogarden/flite-wasi": "~0.1.1",
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
"@echogarden/sonic-wasm": "~0.2.0",
|
|
65
66
|
"@echogarden/speex-resampler-wasm": "~0.3.0",
|
|
66
67
|
"@echogarden/svoxpico-wasm": "~0.2.0",
|
|
67
|
-
"@echogarden/text-segmentation": "~0.8.
|
|
68
|
+
"@echogarden/text-segmentation": "~0.8.1",
|
|
68
69
|
"@echogarden/transformers-nodejs-lite": "~2.17.1-lite.4",
|
|
69
70
|
"@echogarden/wave-codec": "~0.4.0",
|
|
70
71
|
"@echogarden/whisper.cpp-binding": "~0.2.2",
|
|
@@ -80,11 +81,11 @@
|
|
|
80
81
|
"jsdom": "~30.0.1",
|
|
81
82
|
"kuromoji": "~0.1.2",
|
|
82
83
|
"microsoft-cognitiveservices-speech-sdk": "~1.51.0",
|
|
83
|
-
"msgpack-lite": "~0.2.
|
|
84
|
+
"msgpack-lite": "~0.2.2",
|
|
84
85
|
"onnxruntime-node": "~1.21.1",
|
|
85
|
-
"openai": "~7.
|
|
86
|
-
"quick-json5": "~0.
|
|
87
|
-
"regexp-composer": "~0.
|
|
86
|
+
"openai": "~7.10.0",
|
|
87
|
+
"quick-json5": "~0.5.0",
|
|
88
|
+
"regexp-composer": "~0.7.0",
|
|
88
89
|
"sam-js": "~0.3.1",
|
|
89
90
|
"standard-html-escaper": "~0.2.1",
|
|
90
91
|
"strip-ansi": "~7.2.0",
|
|
@@ -92,7 +93,7 @@
|
|
|
92
93
|
"tiktoken": "~1.0.22",
|
|
93
94
|
"tinyld": "~1.3.4",
|
|
94
95
|
"wasm-feature-detect": "~1.9.0",
|
|
95
|
-
"wasm-heap-manager": "~0.
|
|
96
|
+
"wasm-heap-manager": "~0.5.1",
|
|
96
97
|
"ws": "~8.21.3",
|
|
97
98
|
"wtf_wikipedia": "~10.4.2"
|
|
98
99
|
},
|
|
@@ -118,10 +119,11 @@
|
|
|
118
119
|
"@types/graceful-fs": "~4.1.9",
|
|
119
120
|
"@types/jsdom": "~30.0.0",
|
|
120
121
|
"@types/msgpack-lite": "~0.1.12",
|
|
121
|
-
"@types/node": "~26.
|
|
122
|
+
"@types/node": "~26.5.0",
|
|
122
123
|
"@types/ws": "~8.18.1",
|
|
123
124
|
"ts-json-schema-generator": "~2.9.0",
|
|
124
|
-
"typescript": "~7.0.2"
|
|
125
|
+
"typescript": "~7.0.2",
|
|
126
|
+
"vitest": "~5.0.0"
|
|
125
127
|
},
|
|
126
128
|
"overrides": {
|
|
127
129
|
"whatwg-url": "~14.0.0",
|
package/src/api/Synthesis.ts
CHANGED
|
@@ -353,7 +353,11 @@ async function synthesizeSegment(text: string, options: SynthesisOptions, callba
|
|
|
353
353
|
let language: string
|
|
354
354
|
|
|
355
355
|
if (options.language) {
|
|
356
|
-
|
|
356
|
+
try {
|
|
357
|
+
language = await normalizeIdentifierToLanguageCode(options.language)
|
|
358
|
+
} catch {
|
|
359
|
+
language = selectedVoice.languages[0]
|
|
360
|
+
}
|
|
357
361
|
} else {
|
|
358
362
|
language = selectedVoice.languages[0]
|
|
359
363
|
}
|
|
@@ -1814,24 +1818,7 @@ export async function requestVoiceList(options: VoiceListRequestOptions, callbac
|
|
|
1814
1818
|
voiceList = await loadVoiceList()
|
|
1815
1819
|
}
|
|
1816
1820
|
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
if (languageCode) {
|
|
1820
|
-
let filteredVoiceList = voiceList.filter(voice => voice.languages.includes(languageCode))
|
|
1821
|
-
|
|
1822
|
-
if (filteredVoiceList.length == 0 && languageCode.includes('-')) {
|
|
1823
|
-
const shortLanguageCode = getShortLanguageCode(languageCode)
|
|
1824
|
-
|
|
1825
|
-
filteredVoiceList = voiceList.filter(voice => voice.languages.includes(shortLanguageCode))
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
voiceList = filteredVoiceList
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
if (options.voiceGender) {
|
|
1832
|
-
const genderLowercase = options.voiceGender.toLowerCase()
|
|
1833
|
-
voiceList = voiceList.filter(voice => voice.gender == genderLowercase || voice.gender == 'unknown')
|
|
1834
|
-
}
|
|
1821
|
+
let bestMatchingVoice: SynthesisVoice
|
|
1835
1822
|
|
|
1836
1823
|
if (options.voice) {
|
|
1837
1824
|
const namePatternLowerCase = options.voice.toLocaleLowerCase()
|
|
@@ -1853,17 +1840,42 @@ export async function requestVoiceList(options: VoiceListRequestOptions, callbac
|
|
|
1853
1840
|
return false
|
|
1854
1841
|
})
|
|
1855
1842
|
}
|
|
1856
|
-
}
|
|
1857
1843
|
|
|
1858
|
-
|
|
1844
|
+
bestMatchingVoice = voiceList[0]
|
|
1845
|
+
} else {
|
|
1846
|
+
let languageCode = ''
|
|
1859
1847
|
|
|
1860
|
-
|
|
1861
|
-
|
|
1848
|
+
if (options.language) {
|
|
1849
|
+
languageCode = await normalizeIdentifierToLanguageCode(options.language || '')
|
|
1862
1850
|
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1851
|
+
let filteredVoiceList = voiceList.filter(voice => voice.languages.includes(languageCode))
|
|
1852
|
+
|
|
1853
|
+
if (filteredVoiceList.length == 0 && languageCode.includes('-')) {
|
|
1854
|
+
const shortLanguageCode = getShortLanguageCode(languageCode)
|
|
1855
|
+
|
|
1856
|
+
filteredVoiceList = voiceList.filter(voice => voice.languages.includes(shortLanguageCode))
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
voiceList = filteredVoiceList
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
if (options.voiceGender) {
|
|
1863
|
+
const genderLowercase = options.voiceGender.toLowerCase()
|
|
1864
|
+
|
|
1865
|
+
voiceList = voiceList.filter(voice => voice.gender == genderLowercase || voice.gender == 'unknown')
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
bestMatchingVoice = voiceList[0]
|
|
1869
|
+
|
|
1870
|
+
if (bestMatchingVoice && voiceList.length > 1 && defaultDialectForLanguageCode[languageCode]) {
|
|
1871
|
+
const expandedLanguageCode = defaultDialectForLanguageCode[languageCode]
|
|
1872
|
+
|
|
1873
|
+
for (const voice of voiceList) {
|
|
1874
|
+
if (voice.languages.includes(expandedLanguageCode)) {
|
|
1875
|
+
bestMatchingVoice = voice
|
|
1876
|
+
|
|
1877
|
+
break
|
|
1878
|
+
}
|
|
1867
1879
|
}
|
|
1868
1880
|
}
|
|
1869
1881
|
}
|