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/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("open", async () => {
35
+ ws.on('open', async () => {
36
36
  const client = new Client(ws)
37
37
 
38
- const { audio } = await client.synthesize("Hello World", { engine: 'espeak' })
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(serverOptions: ServerOptions, onStarted: (options: ServerOptions) => void)
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("Server is started!")
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 different execution providers
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
@@ -18,6 +18,7 @@ Packages are downloaded as `.tar.gz` files, and are extracted to `[data-folder]/
18
18
  * `/home/user/.local/share/echogarden` on Linux
19
19
 
20
20
  `[package-id-string]` is formatted as:
21
+
21
22
  ```
22
23
  [engine name]-[package id]-[date as yyyymmdd]
23
24
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echogarden",
3
- "version": "3.3.0",
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": "node --experimental-wasi-unstable-preview1 --no-warnings --trace-uncaught ./dist/tests/Test.js",
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.1121.0",
56
- "@aws-sdk/client-transcribe-streaming": "~3.1121.0",
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.0",
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.1",
84
+ "msgpack-lite": "~0.2.2",
84
85
  "onnxruntime-node": "~1.21.1",
85
- "openai": "~7.8.0",
86
- "quick-json5": "~0.4.3",
87
- "regexp-composer": "~0.6.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.4.1",
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.4.0",
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",
@@ -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
- language = await normalizeIdentifierToLanguageCode(options.language)
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
- const languageCode = await normalizeIdentifierToLanguageCode(options.language || '')
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
- let bestMatchingVoice = voiceList[0]
1844
+ bestMatchingVoice = voiceList[0]
1845
+ } else {
1846
+ let languageCode = ''
1859
1847
 
1860
- if (bestMatchingVoice && voiceList.length > 1 && defaultDialectForLanguageCode[languageCode]) {
1861
- const expandedLanguageCode = defaultDialectForLanguageCode[languageCode]
1848
+ if (options.language) {
1849
+ languageCode = await normalizeIdentifierToLanguageCode(options.language || '')
1862
1850
 
1863
- for (const voice of voiceList) {
1864
- if (voice.languages.includes(expandedLanguageCode)) {
1865
- bestMatchingVoice = voice
1866
- break
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
  }