echogarden 0.11.12 → 0.11.13
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/data/schemas/options.json +16 -0
- package/dist/api/Alignment.js +2 -2
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Recognition.js +2 -2
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/Synthesis.js +5 -4
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/Translation.js +2 -2
- package/dist/api/Translation.js.map +1 -1
- package/dist/audio/AudioUtilities.d.ts +1 -0
- package/dist/audio/AudioUtilities.js +25 -7
- package/dist/audio/AudioUtilities.js.map +1 -1
- package/dist/cli/CLI.js +2 -2
- package/dist/cli/CLI.js.map +1 -1
- package/dist/recognition/WhisperSTT.js +2 -2
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/subtitles/Subtitles.d.ts +10 -7
- package/dist/subtitles/Subtitles.js +268 -207
- package/dist/subtitles/Subtitles.js.map +1 -1
- package/docs/Options.md +4 -2
- package/package.json +7 -6
- package/src/alignment/DTWMfccSequenceAlignment.ts +43 -0
- package/src/alignment/DTWSequenceAlignment.ts +121 -0
- package/src/alignment/DTWSequenceAlignmentWindowed.ts +210 -0
- package/src/alignment/LevenshteinSequenceAlignment.ts +126 -0
- package/src/alignment/SpeechAlignment.ts +488 -0
- package/src/api/API.ts +12 -0
- package/src/api/APIOptions.ts +15 -0
- package/src/api/Alignment.ts +329 -0
- package/src/api/Common.ts +16 -0
- package/src/api/Denoising.ts +120 -0
- package/src/api/LanguageDetection.ts +286 -0
- package/src/api/Recognition.ts +344 -0
- package/src/api/Synthesis.ts +1735 -0
- package/src/api/Translation.ts +143 -0
- package/src/api/Vad.ts +172 -0
- package/src/audio/AudioBufferConversion.ts +248 -0
- package/src/audio/AudioPlayer.ts +358 -0
- package/src/audio/AudioRecorder.ts +91 -0
- package/src/audio/AudioUtilities.ts +392 -0
- package/src/audio/SoxPath.ts +24 -0
- package/src/cli/CLI.ts +1360 -0
- package/src/cli/CLIConfigFile.ts +91 -0
- package/src/cli/CLILauncher.ts +26 -0
- package/src/cli/CLIOptionsSchema.ts +54 -0
- package/src/cli/CLIParser.ts +41 -0
- package/src/cli/CLIStarter.ts +40 -0
- package/src/codecs/FFMpegTranscoder.ts +214 -0
- package/src/codecs/TIMITCodec.ts +17 -0
- package/src/codecs/WaveCodec.ts +260 -0
- package/src/denoising/RNNoise.ts +95 -0
- package/src/dsp/BiquadFilter.ts +488 -0
- package/src/dsp/FFT.ts +187 -0
- package/src/dsp/MFCC.ts +227 -0
- package/src/dsp/MelSpectogram.ts +145 -0
- package/src/dsp/Rubberband.ts +249 -0
- package/src/dsp/Sonic.ts +59 -0
- package/src/dsp/SpeexResampler.ts +79 -0
- package/src/math/VectorMath.ts +812 -0
- package/src/nlp/ChineseSegmentation.ts +68 -0
- package/src/nlp/CompromiseNLP.ts +113 -0
- package/src/nlp/EspeakPhonemizer.ts +168 -0
- package/src/nlp/IPA.ts +139 -0
- package/src/nlp/JapaneseSegmentation.ts +53 -0
- package/src/nlp/Lexicon.ts +119 -0
- package/src/nlp/PhoneConversion.ts +508 -0
- package/src/nlp/Segmentation.ts +237 -0
- package/src/nlp/TextNormalizer.ts +160 -0
- package/src/recognition/AmazonTranscribeSTT.ts +112 -0
- package/src/recognition/AzureCognitiveServicesSTT.ts +76 -0
- package/src/recognition/GoogleCloudSTT.ts +92 -0
- package/src/recognition/SileroSTT.ts +173 -0
- package/src/recognition/VoskSTT.ts +112 -0
- package/src/recognition/WhisperSTT.ts +1518 -0
- package/src/server/Client.ts +297 -0
- package/src/server/Server.ts +178 -0
- package/src/server/ServerStarter.ts +12 -0
- package/src/server/Worker.ts +400 -0
- package/src/server/WorkerStarter.ts +38 -0
- package/src/speech-language-detection/SileroLanguageDetection.ts +105 -0
- package/src/subtitles/Subtitles.ts +478 -0
- package/src/synthesis/AwsPollyTTS.ts +78 -0
- package/src/synthesis/AzureCognitiveServicesTTS.ts +146 -0
- package/src/synthesis/CoquiServerTTS.ts +29 -0
- package/src/synthesis/ElevenLabsTTS.ts +104 -0
- package/src/synthesis/EspeakTTS.ts +552 -0
- package/src/synthesis/FliteTTS.ts +387 -0
- package/src/synthesis/GoogleCloudTTS.ts +112 -0
- package/src/synthesis/GoogleTranslateTTS.ts +210 -0
- package/src/synthesis/MicrosoftEdgeTTS.ts +298 -0
- package/src/synthesis/SamTTS.ts +30 -0
- package/src/synthesis/SapiTTS.ts +222 -0
- package/src/synthesis/StreamlabsPollyTTS.ts +114 -0
- package/src/synthesis/SvoxPicoTTS.ts +318 -0
- package/src/synthesis/VitsTTS.ts +734 -0
- package/src/tests/Test.ts +24 -0
- package/src/text-language-detection/FastTextLanguageDetection.ts +53 -0
- package/src/text-language-detection/TinyLDLanguageDetection.ts +16 -0
- package/src/typings/Fillers.d.ts +41 -0
- package/src/utilities/BinaryArrayConversion.ts +159 -0
- package/src/utilities/Compression.ts +91 -0
- package/src/utilities/FileDownloader.ts +201 -0
- package/src/utilities/FileSystem.ts +265 -0
- package/src/utilities/Hashing.ts +230 -0
- package/src/utilities/Locale.ts +119 -0
- package/src/utilities/Logger.ts +72 -0
- package/src/utilities/NdArrayUtilities.ts +31 -0
- package/src/utilities/ObjectUtilities.ts +169 -0
- package/src/utilities/OpenPromise.ts +13 -0
- package/src/utilities/PackageManager.ts +97 -0
- package/src/utilities/Queue.ts +17 -0
- package/src/utilities/RandomGenerator.ts +237 -0
- package/src/utilities/SignalChannel.ts +22 -0
- package/src/utilities/TarballMaker.ts +68 -0
- package/src/utilities/Timeline.ts +231 -0
- package/src/utilities/Timer.ts +93 -0
- package/src/utilities/Utilities.ts +574 -0
- package/src/utilities/WasmMemoryManager.ts +516 -0
- package/src/utilities/WebReader.ts +55 -0
- package/src/utilities/WikipediaReader.ts +41 -0
- package/src/voice-activity-detection/SileroVAD.ts +86 -0
- package/src/voice-activity-detection/WebRtcVAD.ts +76 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { WebSocket } from 'ws'
|
|
2
|
+
import { encode as encodeMsgPack, decode as decodeMsgPack } from 'msgpack-lite'
|
|
3
|
+
import { RequestVoiceListResult, SynthesisOptions, SynthesisSegmentEvent, SynthesisResult, VoiceListRequestOptions } from "../api/Synthesis.js"
|
|
4
|
+
import { SynthesisResponseMessage, SynthesisSegmentEventMessage, SynthesisSentenceEventMessage, VoiceListRequestMessage, WorkerRequestMessage, VoiceListResponseMessage, AlignmentRequestMessage, AlignmentResponseMessage, RecognitionRequestMessage, RecognitionResponseMessage, SpeechTranslationRequestMessage, SpeechTranslationResponseMessage, SpeechLanguageDetectionRequestMessage, SpeechLanguageDetectionResponseMessage, TextLanguageDetectionResponseMessage, TextLanguageDetectionRequestMessage, SynthesisRequestMessage } from './Worker.js'
|
|
5
|
+
import { getRandomHexString, logToStderr } from '../utilities/Utilities.js'
|
|
6
|
+
import { OpenPromise } from '../utilities/OpenPromise.js'
|
|
7
|
+
import { AudioSourceParam, RawAudio } from '../audio/AudioUtilities.js'
|
|
8
|
+
import { AlignmentOptions, AlignmentResult } from '../api/Alignment.js'
|
|
9
|
+
import { RecognitionOptions, RecognitionResult } from '../api/Recognition.js'
|
|
10
|
+
import { SpeechTranslationOptions, SpeechTranslationResult } from '../api/Translation.js'
|
|
11
|
+
import { Worker as WorkerThread } from 'node:worker_threads'
|
|
12
|
+
import { SpeechLanguageDetectionOptions, SpeechLanguageDetectionResult, TextLanguageDetectionOptions, TextLanguageDetectionResult } from '../api/LanguageDetection.js'
|
|
13
|
+
|
|
14
|
+
const log = logToStderr
|
|
15
|
+
|
|
16
|
+
export class Client {
|
|
17
|
+
sendMessage: (message: any) => void
|
|
18
|
+
|
|
19
|
+
responseListeners = new Map<string, (message: string) => void>()
|
|
20
|
+
|
|
21
|
+
constructor(sourceChannel: WebSocket | WorkerThread) {
|
|
22
|
+
if (sourceChannel instanceof WebSocket) {
|
|
23
|
+
sourceChannel.on("message", (messageData, isBinary) => {
|
|
24
|
+
if (!isBinary) {
|
|
25
|
+
log(`Received an unexpected string WebSocket message: '${(messageData as Buffer).toString("utf-8")}'`)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let incomingMessage: any
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
incomingMessage = decodeMsgPack(messageData as Buffer)
|
|
33
|
+
} catch (e) {
|
|
34
|
+
log(`Failed to decode incoming message. Reason: ${e}`)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.onMessage(incomingMessage)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
this.sendMessage = (outgoingMessage) => {
|
|
42
|
+
const encodedMessage = encodeMsgPack(outgoingMessage)
|
|
43
|
+
|
|
44
|
+
sourceChannel.send(encodedMessage)
|
|
45
|
+
}
|
|
46
|
+
} else if (sourceChannel instanceof WorkerThread) {
|
|
47
|
+
sourceChannel.on("message", (message) => {
|
|
48
|
+
this.onMessage(message)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
sourceChannel.on("error", (e) => {
|
|
52
|
+
throw e
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
this.sendMessage = (outgoingMessage) => {
|
|
56
|
+
sourceChannel.postMessage(outgoingMessage)
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
throw new Error(`Invalid source: not a WebSocket or WorkerThread object`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async synthesize(input: string | string[], options: SynthesisOptions, onSegment?: SynthesisSegmentEvent, onSentence?: SynthesisSegmentEvent): Promise<SynthesisResult> {
|
|
64
|
+
const requestOpenPromise = new OpenPromise<SynthesisResult>()
|
|
65
|
+
|
|
66
|
+
const requestMessage: SynthesisRequestMessage = {
|
|
67
|
+
messageType: "SynthesisRequest",
|
|
68
|
+
input,
|
|
69
|
+
options
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function onResponse(responseMessage: SynthesisResponseMessage | SynthesisSegmentEventMessage | SynthesisSentenceEventMessage) {
|
|
73
|
+
if (responseMessage.messageType == "SynthesisResponse") {
|
|
74
|
+
requestOpenPromise.resolve(responseMessage)
|
|
75
|
+
} else if (responseMessage.messageType == "SynthesisSegmentEvent" && onSegment) {
|
|
76
|
+
onSegment(responseMessage)
|
|
77
|
+
} else if (responseMessage.messageType == "SynthesisSentenceEvent" && onSentence) {
|
|
78
|
+
onSentence(responseMessage)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function onError(e: any) {
|
|
83
|
+
requestOpenPromise.reject(e)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
this.sendRequest(requestMessage, onResponse, onError)
|
|
88
|
+
} catch (e) {
|
|
89
|
+
onError(e)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return requestOpenPromise.promise
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async requestVoiceList(options: VoiceListRequestOptions): Promise<RequestVoiceListResult> {
|
|
96
|
+
const requestOpenPromise = new OpenPromise<RequestVoiceListResult>()
|
|
97
|
+
|
|
98
|
+
const requestMessage: VoiceListRequestMessage = {
|
|
99
|
+
messageType: "VoiceListRequest",
|
|
100
|
+
options
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function onResponse(responseMessage: VoiceListResponseMessage) {
|
|
104
|
+
if (responseMessage.messageType == "VoiceListResponse") {
|
|
105
|
+
requestOpenPromise.resolve(responseMessage)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function onError(e: any) {
|
|
110
|
+
requestOpenPromise.reject(e)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
this.sendRequest(requestMessage, onResponse, onError)
|
|
115
|
+
} catch (e) {
|
|
116
|
+
onError(e)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return requestOpenPromise.promise
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async recognize(input: AudioSourceParam, options: RecognitionOptions): Promise<RecognitionResult> {
|
|
123
|
+
const requestOpenPromise = new OpenPromise<RecognitionResult>()
|
|
124
|
+
|
|
125
|
+
const requestMessage: RecognitionRequestMessage = {
|
|
126
|
+
messageType: "RecognitionRequest",
|
|
127
|
+
input,
|
|
128
|
+
options
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function onResponse(responseMessage: RecognitionResponseMessage) {
|
|
132
|
+
if (responseMessage.messageType == "RecognitionResponse") {
|
|
133
|
+
requestOpenPromise.resolve(responseMessage)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function onError(e: any) {
|
|
138
|
+
requestOpenPromise.reject(e)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
this.sendRequest(requestMessage, onResponse, onError)
|
|
143
|
+
} catch (e) {
|
|
144
|
+
onError(e)
|
|
145
|
+
}
|
|
146
|
+
return requestOpenPromise.promise
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async align(input: AudioSourceParam, transcript: string, options: AlignmentOptions): Promise<AlignmentResult> {
|
|
150
|
+
const requestOpenPromise = new OpenPromise<AlignmentResult>()
|
|
151
|
+
|
|
152
|
+
const requestMessage: AlignmentRequestMessage = {
|
|
153
|
+
messageType: "AlignmentRequest",
|
|
154
|
+
input,
|
|
155
|
+
transcript,
|
|
156
|
+
options
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function onResponse(responseMessage: AlignmentResponseMessage) {
|
|
160
|
+
if (responseMessage.messageType == "AlignmentResponse") {
|
|
161
|
+
requestOpenPromise.resolve(responseMessage)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function onError(e: any) {
|
|
166
|
+
requestOpenPromise.reject(e)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
this.sendRequest(requestMessage, onResponse, onError)
|
|
171
|
+
} catch (e) {
|
|
172
|
+
onError(e)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return requestOpenPromise.promise
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async translateSpeech(input: string | Buffer | Uint8Array |RawAudio, options: SpeechTranslationOptions): Promise<SpeechTranslationResult> {
|
|
179
|
+
const requestOpenPromise = new OpenPromise<SpeechTranslationResult>()
|
|
180
|
+
|
|
181
|
+
const requestMessage: SpeechTranslationRequestMessage = {
|
|
182
|
+
messageType: "SpeechTranslationRequest",
|
|
183
|
+
input,
|
|
184
|
+
options
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function onResponse(responseMessage: SpeechTranslationResponseMessage) {
|
|
188
|
+
if (responseMessage.messageType == "SpeechTranslationResponse") {
|
|
189
|
+
requestOpenPromise.resolve(responseMessage)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function onError(e: any) {
|
|
194
|
+
requestOpenPromise.reject(e)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
this.sendRequest(requestMessage, onResponse, onError)
|
|
199
|
+
} catch (e) {
|
|
200
|
+
onError(e)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return requestOpenPromise.promise
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async detectSpeechLanguage(input: AudioSourceParam, options: SpeechLanguageDetectionOptions): Promise<SpeechLanguageDetectionResult> {
|
|
207
|
+
const requestOpenPromise = new OpenPromise<SpeechLanguageDetectionResult>()
|
|
208
|
+
|
|
209
|
+
const requestMessage: SpeechLanguageDetectionRequestMessage = {
|
|
210
|
+
messageType: "SpeechLanguageDetectionRequest",
|
|
211
|
+
input,
|
|
212
|
+
options
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function onResponse(responseMessage: SpeechLanguageDetectionResponseMessage) {
|
|
216
|
+
if (responseMessage.messageType == "SpeechLanguageDetectionResponse") {
|
|
217
|
+
requestOpenPromise.resolve(responseMessage)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function onError(e: any) {
|
|
222
|
+
requestOpenPromise.reject(e)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
this.sendRequest(requestMessage, onResponse, onError)
|
|
227
|
+
} catch (e) {
|
|
228
|
+
onError(e)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return requestOpenPromise.promise
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async detectTextLanguage(input: string, options: TextLanguageDetectionOptions): Promise<TextLanguageDetectionResult> {
|
|
235
|
+
const requestOpenPromise = new OpenPromise<TextLanguageDetectionResult>()
|
|
236
|
+
|
|
237
|
+
const requestMessage: TextLanguageDetectionRequestMessage = {
|
|
238
|
+
messageType: "TextLanguageDetectionRequest",
|
|
239
|
+
input,
|
|
240
|
+
options
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function onResponse(responseMessage: TextLanguageDetectionResponseMessage) {
|
|
244
|
+
if (responseMessage.messageType == "TextLanguageDetectionResponse") {
|
|
245
|
+
requestOpenPromise.resolve(responseMessage)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function onError(e: any) {
|
|
250
|
+
requestOpenPromise.reject(e)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
this.sendRequest(requestMessage, onResponse, onError)
|
|
255
|
+
} catch (e) {
|
|
256
|
+
onError(e)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return requestOpenPromise.promise
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
sendRequest(request: any, onResponse: (message: any) => void, onErrorResponse: (error: any) => void) {
|
|
263
|
+
const requestId = getRandomHexString()
|
|
264
|
+
|
|
265
|
+
request = {
|
|
266
|
+
requestId,
|
|
267
|
+
...request
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this.sendMessage(request)
|
|
271
|
+
|
|
272
|
+
function onResponseMessage(message: any) {
|
|
273
|
+
if (message.messageType == "Error") {
|
|
274
|
+
onErrorResponse(message.error)
|
|
275
|
+
} else {
|
|
276
|
+
onResponse(message)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
this.responseListeners.set(requestId, onResponseMessage)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
onMessage(incomingMessage: any) {
|
|
284
|
+
const requestId = incomingMessage.requestId
|
|
285
|
+
|
|
286
|
+
if (!requestId) {
|
|
287
|
+
log("Received a WebSocket message without a request ID")
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const listener = this.responseListeners.get(requestId)
|
|
292
|
+
|
|
293
|
+
if (listener) {
|
|
294
|
+
listener(incomingMessage)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { WebSocketServer, WebSocket, ServerOptions as WsServerOptions } from 'ws'
|
|
2
|
+
import { encode as encodeMsgPack, decode as decodeMsgPack } from 'msgpack-lite'
|
|
3
|
+
import { logToStderr } from '../utilities/Utilities.js'
|
|
4
|
+
import { OpenPromise } from '../utilities/OpenPromise.js'
|
|
5
|
+
import { readFile, existsSync } from '../utilities/FileSystem.js'
|
|
6
|
+
import { extendDeep } from '../utilities/ObjectUtilities.js'
|
|
7
|
+
import { sendMessageToWorker, addListenerToWorkerMessages, startNewWorkerThread } from './Worker.js'
|
|
8
|
+
import { Worker } from 'node:worker_threads'
|
|
9
|
+
import { IncomingMessage, ServerResponse } from 'node:http'
|
|
10
|
+
import { Logger } from '../utilities/Logger.js'
|
|
11
|
+
import chalk from 'chalk'
|
|
12
|
+
|
|
13
|
+
const log = logToStderr
|
|
14
|
+
|
|
15
|
+
export async function startServer(serverOptions: ServerOptions, onStarted: (options: ServerOptions) => void) {
|
|
16
|
+
serverOptions = extendDeep(defaultServerOptions, serverOptions)
|
|
17
|
+
|
|
18
|
+
const wsServerOptions: WsServerOptions = {
|
|
19
|
+
perMessageDeflate: serverOptions.deflate,
|
|
20
|
+
maxPayload: serverOptions.maxPayload
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function onHttpRequest(request: IncomingMessage, response: ServerResponse) {
|
|
24
|
+
response.writeHead(200, { 'Content-Type': 'text/plain' })
|
|
25
|
+
response.end("This is the Echogarden HTTP server!")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (serverOptions.secure) {
|
|
29
|
+
if (!serverOptions.certPath || !existsSync(serverOptions.certPath)) {
|
|
30
|
+
throw new Error(`No valid certificate file path was given`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!serverOptions.keyPath || !existsSync(serverOptions.keyPath)) {
|
|
34
|
+
throw new Error(`No valid key file path was given`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { createServer } = await import('https')
|
|
38
|
+
|
|
39
|
+
const httpsServer = createServer({
|
|
40
|
+
cert: await readFile(serverOptions.certPath!),
|
|
41
|
+
key: await readFile(serverOptions.keyPath!)
|
|
42
|
+
}, onHttpRequest)
|
|
43
|
+
|
|
44
|
+
httpsServer.listen(serverOptions.port!)
|
|
45
|
+
|
|
46
|
+
wsServerOptions.server = httpsServer
|
|
47
|
+
} else {
|
|
48
|
+
const { createServer } = await import('http')
|
|
49
|
+
|
|
50
|
+
const httpServer = createServer({}, onHttpRequest)
|
|
51
|
+
|
|
52
|
+
httpServer.listen(serverOptions.port)
|
|
53
|
+
|
|
54
|
+
wsServerOptions.server = httpServer
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const wss = new WebSocketServer(wsServerOptions)
|
|
58
|
+
|
|
59
|
+
const requestIdToWebSocket = new Map<string, WebSocket>()
|
|
60
|
+
|
|
61
|
+
function onWorkerMessage(message: any) {
|
|
62
|
+
if (message.name == "writeToStdErr") {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Remove input raw audio property from message, if exists, when using WebSocket protocol:
|
|
67
|
+
message['inputRawAudio'] = undefined
|
|
68
|
+
|
|
69
|
+
if (!message.requestId) {
|
|
70
|
+
throw new Error("Worker message doesn't have a request ID")
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const ws = requestIdToWebSocket.get(message.requestId)
|
|
74
|
+
|
|
75
|
+
if (!ws || ws.readyState != WebSocket.OPEN) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const encodedWorkerMessage = encodeMsgPack(message)
|
|
80
|
+
|
|
81
|
+
ws.send(encodedWorkerMessage)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let workerThread: Worker | undefined
|
|
85
|
+
|
|
86
|
+
if (serverOptions.useWorkerThread) {
|
|
87
|
+
workerThread = await startNewWorkerThread()
|
|
88
|
+
workerThread.on('message', onWorkerMessage)
|
|
89
|
+
} else {
|
|
90
|
+
addListenerToWorkerMessages(onWorkerMessage)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const serverOpenPromise = new OpenPromise<void>
|
|
94
|
+
|
|
95
|
+
wss.on("listening", () => {
|
|
96
|
+
log(chalk.gray(`Started Echogarden WebSocket server on port ${serverOptions.port}`))
|
|
97
|
+
onStarted(serverOptions)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
wss.on("close", () => {
|
|
101
|
+
serverOpenPromise.resolve()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
wss.on('connection', async (ws, req) => {
|
|
105
|
+
log(chalk.gray((`Accepted incoming connection from ${req.socket.remoteAddress}`)))
|
|
106
|
+
|
|
107
|
+
ws.on('message', (messageData, isBinary) => {
|
|
108
|
+
if (!isBinary) {
|
|
109
|
+
log(chalk.gray((`Received an unexpected string WebSocket message: '${(messageData as Buffer).toString("utf-8")}'`)))
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let incomingMessage: any
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
incomingMessage = decodeMsgPack(messageData as Buffer)
|
|
117
|
+
} catch (e) {
|
|
118
|
+
log(chalk.gray(`Failed to decode binary WebSocket message. Reason: ${e}`))
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const requestId = incomingMessage.requestId
|
|
123
|
+
|
|
124
|
+
if (!requestId) {
|
|
125
|
+
log(chalk.gray(("Received a WebSocket message without a request ID")))
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
requestIdToWebSocket.set(requestId, ws)
|
|
130
|
+
|
|
131
|
+
if (workerThread) {
|
|
132
|
+
workerThread.postMessage(incomingMessage)
|
|
133
|
+
} else {
|
|
134
|
+
sendMessageToWorker(incomingMessage)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
ws.on('error', (e) => {
|
|
139
|
+
log(`${chalk.redBright('WebSocket error')}: ${e.message}`)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
ws.on("close", () => {
|
|
143
|
+
const keysToDelete: string[] = []
|
|
144
|
+
|
|
145
|
+
requestIdToWebSocket.forEach((value, key) => {
|
|
146
|
+
if (value == ws) {
|
|
147
|
+
keysToDelete.push(key)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
keysToDelete.forEach(key => requestIdToWebSocket.delete(key))
|
|
152
|
+
|
|
153
|
+
log(chalk.gray((`Incoming connection from ${ req.socket.remoteAddress } was closed`)))
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
return serverOpenPromise.promise
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface ServerOptions {
|
|
161
|
+
port?: number
|
|
162
|
+
secure?: boolean
|
|
163
|
+
certPath?: string
|
|
164
|
+
keyPath?: string
|
|
165
|
+
deflate?: boolean
|
|
166
|
+
maxPayload?: number
|
|
167
|
+
useWorkerThread?: boolean
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const defaultServerOptions: ServerOptions = {
|
|
171
|
+
port: 45054,
|
|
172
|
+
secure: false,
|
|
173
|
+
certPath: undefined,
|
|
174
|
+
keyPath: undefined,
|
|
175
|
+
deflate: true,
|
|
176
|
+
maxPayload: 1000 * 1000000, // 1GB
|
|
177
|
+
useWorkerThread: true
|
|
178
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { logToStderr, setupProgramTerminationListeners, setupUnhandledExceptionListeners } from '../utilities/Utilities.js'
|
|
2
|
+
import { startServer } from './Server.js'
|
|
3
|
+
|
|
4
|
+
const log = logToStderr
|
|
5
|
+
|
|
6
|
+
setupUnhandledExceptionListeners()
|
|
7
|
+
|
|
8
|
+
setupProgramTerminationListeners(() => {
|
|
9
|
+
log('')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
await startServer({}, (options) => { })
|