echogarden 0.11.18 → 0.11.20
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/dist/api/Synthesis.js +5 -4
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/cli/CLI.js +2 -0
- package/dist/cli/CLI.js.map +1 -1
- package/dist/math/VectorMath.js +4 -4
- package/dist/math/VectorMath.js.map +1 -1
- package/dist/server/Server.js +2 -1
- package/dist/server/Server.js.map +1 -1
- package/dist/server/Worker.d.ts +1 -0
- package/dist/server/Worker.js +32 -17
- package/dist/server/Worker.js.map +1 -1
- package/dist/server/WorkerStarter.js +2 -1
- package/dist/server/WorkerStarter.js.map +1 -1
- package/dist/synthesis/AzureCognitiveServicesTTS.js +1 -1
- package/dist/synthesis/AzureCognitiveServicesTTS.js.map +1 -1
- package/dist/synthesis/MicrosoftEdgeTTS.d.ts +2 -4
- package/dist/synthesis/MicrosoftEdgeTTS.js +56 -9
- package/dist/synthesis/MicrosoftEdgeTTS.js.map +1 -1
- package/dist/utilities/Logger.d.ts +2 -0
- package/dist/utilities/Logger.js +8 -1
- package/dist/utilities/Logger.js.map +1 -1
- package/dist/utilities/Utilities.d.ts +2 -0
- package/dist/utilities/Utilities.js +21 -1
- package/dist/utilities/Utilities.js.map +1 -1
- package/package.json +22 -21
- package/src/api/Synthesis.ts +15 -4
- package/src/cli/CLI.ts +2 -0
- package/src/math/VectorMath.ts +5 -4
- package/src/server/Server.ts +2 -1
- package/src/server/Worker.ts +41 -18
- package/src/server/WorkerStarter.ts +3 -1
- package/src/synthesis/AzureCognitiveServicesTTS.ts +1 -1
- package/src/synthesis/MicrosoftEdgeTTS.ts +73 -9
- package/src/utilities/Logger.ts +12 -1
- package/src/utilities/Utilities.ts +37 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echogarden",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.20",
|
|
4
4
|
"description": "An integrated speech toolset, providing a variety of synthesis, recognition, alignment, and other processing tools, designed to be directly accessible to end-users.",
|
|
5
5
|
"author": "Rotem Dan",
|
|
6
6
|
"license": "GPL-3.0",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"echogarden": "./dist/cli/CLILauncher.js"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@aws-sdk/client-polly": "^3.
|
|
58
|
-
"@aws-sdk/client-transcribe-streaming": "^3.
|
|
57
|
+
"@aws-sdk/client-polly": "^3.529.1",
|
|
58
|
+
"@aws-sdk/client-transcribe-streaming": "^3.529.1",
|
|
59
59
|
"@echogarden/espeak-ng-emscripten": "^0.1.2",
|
|
60
60
|
"@echogarden/fasttext-wasm": "^0.1.0",
|
|
61
61
|
"@echogarden/flite-wasi": "^0.1.1",
|
|
@@ -66,41 +66,42 @@
|
|
|
66
66
|
"@echogarden/sonic-wasm": "^0.1.1",
|
|
67
67
|
"@echogarden/speex-resampler-wasm": "^0.1.1",
|
|
68
68
|
"@echogarden/svoxpico-wasm": "^0.1.0",
|
|
69
|
-
"@mozilla/readability": "^0.
|
|
69
|
+
"@mozilla/readability": "^0.5.0",
|
|
70
70
|
"alawmulaw": "^6.0.0",
|
|
71
71
|
"buffer-split": "^1.0.0",
|
|
72
72
|
"chalk": "^5.3.0",
|
|
73
73
|
"cldr-segmentation": "^2.2.0",
|
|
74
74
|
"command-exists": "^1.2.9",
|
|
75
|
-
"compromise": "^14.
|
|
76
|
-
"compromise-dates": "^3.
|
|
77
|
-
"fs-extra": "^11.
|
|
78
|
-
"gaxios": "^6.
|
|
75
|
+
"compromise": "^14.12.0",
|
|
76
|
+
"compromise-dates": "^3.5.0",
|
|
77
|
+
"fs-extra": "^11.2.0",
|
|
78
|
+
"gaxios": "^6.3.0",
|
|
79
79
|
"graceful-fs": "^4.2.11",
|
|
80
80
|
"html-escaper": "^3.0.3",
|
|
81
81
|
"html-to-text": "^9.0.5",
|
|
82
82
|
"import-meta-resolve": "^4.0.0",
|
|
83
83
|
"jieba-wasm": "^0.0.2",
|
|
84
|
-
"jsdom": "^
|
|
84
|
+
"jsdom": "^24.0.0",
|
|
85
85
|
"json5": "^2.2.3",
|
|
86
86
|
"kuromoji": "^0.1.2",
|
|
87
|
-
"microsoft-cognitiveservices-speech-sdk": "^1.
|
|
87
|
+
"microsoft-cognitiveservices-speech-sdk": "^1.36.0",
|
|
88
88
|
"moving-median": "^1.0.0",
|
|
89
89
|
"msgpack-lite": "^0.1.26",
|
|
90
90
|
"ndarray": "^1.0.19",
|
|
91
91
|
"ndarray-ops": "^1.2.2",
|
|
92
|
-
"onnxruntime-node": "^1.
|
|
92
|
+
"onnxruntime-node": "^1.17.0",
|
|
93
93
|
"sam-js": "^0.2.1",
|
|
94
94
|
"strip-ansi": "^7.1.0",
|
|
95
95
|
"tar": "^6.2.0",
|
|
96
96
|
"tinyld": "^1.3.4",
|
|
97
|
-
|
|
98
|
-
"
|
|
97
|
+
|
|
98
|
+
"ws": "^8.16.0",
|
|
99
|
+
"wtf_wikipedia": "^10.3.0"
|
|
99
100
|
},
|
|
100
101
|
"peerDependencies": {
|
|
101
102
|
"@echogarden/vosk": "^0.3.39-patched.1",
|
|
102
103
|
"speaker": "^0.5.4",
|
|
103
|
-
"winax": "^3.4.
|
|
104
|
+
"winax": "^3.4.2"
|
|
104
105
|
},
|
|
105
106
|
"peerDependenciesMeta": {
|
|
106
107
|
"@echogarden/vosk": {
|
|
@@ -121,14 +122,14 @@
|
|
|
121
122
|
"@types/msgpack-lite": "^0.1.11",
|
|
122
123
|
"@types/ndarray": "^1.0.14",
|
|
123
124
|
"@types/ndarray-ops": "^1.2.7",
|
|
124
|
-
"@types/node": "^20.
|
|
125
|
+
"@types/node": "^20.11.25",
|
|
125
126
|
"@types/recursive-readdir": "^2.2.4",
|
|
126
|
-
"@types/tar": "^6.1.
|
|
127
|
+
"@types/tar": "^6.1.11",
|
|
127
128
|
"@types/ws": "^8.5.10",
|
|
128
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
129
|
-
"@typescript-eslint/parser": "^
|
|
130
|
-
"eslint": "^8.
|
|
131
|
-
"ts-json-schema-generator": "^1.
|
|
132
|
-
"typescript": "^5.
|
|
129
|
+
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
|
130
|
+
"@typescript-eslint/parser": "^7.1.1",
|
|
131
|
+
"eslint": "^8.57.0",
|
|
132
|
+
"ts-json-schema-generator": "^1.5.0",
|
|
133
|
+
"typescript": "^5.4.2"
|
|
133
134
|
}
|
|
134
135
|
}
|
package/src/api/Synthesis.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { deepClone, extendDeep } from "../utilities/ObjectUtilities.js"
|
|
|
4
4
|
|
|
5
5
|
import * as FFMpegTranscoder from "../codecs/FFMpegTranscoder.js"
|
|
6
6
|
|
|
7
|
-
import { clip, convertHtmlToText, sha256AsHex, simplifyPunctuationCharacters, stringifyAndFormatJson, logToStderr, yieldToEventLoop } from "../utilities/Utilities.js"
|
|
7
|
+
import { clip, convertHtmlToText, sha256AsHex, simplifyPunctuationCharacters, stringifyAndFormatJson, logToStderr, yieldToEventLoop, delay, runOperationWithRetries } from "../utilities/Utilities.js"
|
|
8
8
|
import { RawAudio, concatAudioSegments, downmixToMono, encodeWaveBuffer, getAudioPeakDecibels, getEmptyRawAudio, getRawAudioDuration, normalizeAudioLevel, trimAudioEnd, trimAudioStart } from "../audio/AudioUtilities.js"
|
|
9
9
|
import { Logger } from "../utilities/Logger.js"
|
|
10
10
|
|
|
@@ -727,7 +727,10 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
|
|
|
727
727
|
|
|
728
728
|
logger.end()
|
|
729
729
|
|
|
730
|
-
const { rawAudio, timeline: segmentTimeline } =
|
|
730
|
+
const { rawAudio, timeline: segmentTimeline } =
|
|
731
|
+
await runOperationWithRetries(
|
|
732
|
+
() => GoogleTranslateTTS.synthesizeLongText(text, language, options.googleTranslate?.tld, options.sentenceEndPause, options.segmentEndPause),
|
|
733
|
+
logger)
|
|
731
734
|
|
|
732
735
|
synthesizedAudio = rawAudio
|
|
733
736
|
|
|
@@ -777,7 +780,10 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
|
|
|
777
780
|
|
|
778
781
|
logger.end()
|
|
779
782
|
|
|
780
|
-
const { rawAudio, timeline: edgeTimeline } =
|
|
783
|
+
const { rawAudio, timeline: edgeTimeline } =
|
|
784
|
+
await runOperationWithRetries(
|
|
785
|
+
() => MicrosoftEdgeTTS.synthesize(text, trustedClientToken!, voice, ssmlPitch, ssmlRate),
|
|
786
|
+
logger)
|
|
781
787
|
|
|
782
788
|
synthesizedAudio = rawAudio
|
|
783
789
|
timeline = edgeTimeline
|
|
@@ -1212,6 +1218,8 @@ export const defaultSynthesisOptions: SynthesisOptions = {
|
|
|
1212
1218
|
export async function requestVoiceList(options: VoiceListRequestOptions): Promise<RequestVoiceListResult> {
|
|
1213
1219
|
options = extendDeep(defaultVoiceListRequestOptions, options)
|
|
1214
1220
|
|
|
1221
|
+
const logger = new Logger()
|
|
1222
|
+
|
|
1215
1223
|
const cacheOptions = options.cache!
|
|
1216
1224
|
|
|
1217
1225
|
let cacheDir = cacheOptions?.path
|
|
@@ -1456,7 +1464,10 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
|
|
|
1456
1464
|
throw new Error("No trusted client token provided")
|
|
1457
1465
|
}
|
|
1458
1466
|
|
|
1459
|
-
const voices =
|
|
1467
|
+
const voices =
|
|
1468
|
+
await runOperationWithRetries(
|
|
1469
|
+
() => MicrosoftEdgeTTS.getVoiceList(trustedClientToken),
|
|
1470
|
+
logger)
|
|
1460
1471
|
|
|
1461
1472
|
voiceList = voices.map((voice: any) => ({
|
|
1462
1473
|
name: voice.Name,
|
package/src/cli/CLI.ts
CHANGED
|
@@ -186,6 +186,8 @@ const commandHelp = [
|
|
|
186
186
|
` List installed Echogarden packages\n`,
|
|
187
187
|
`${executableName} ${chalk.magentaBright('serve')} [options...]`,
|
|
188
188
|
` Start a server\n`,
|
|
189
|
+
`Options reference: ${chalk.blueBright('https://bit.ly/echogarden-options')}`
|
|
190
|
+
//` ${chalk.blueBright('https://github.com/echogarden-project/echogarden/blob/main/docs/Options.md')}`
|
|
189
191
|
]
|
|
190
192
|
|
|
191
193
|
async function startWithArgs(parsedArgs: CLIArguments) {
|
package/src/math/VectorMath.ts
CHANGED
|
@@ -688,24 +688,24 @@ export function softmax(logits: number[], temperature = 1.0) {
|
|
|
688
688
|
}
|
|
689
689
|
}
|
|
690
690
|
|
|
691
|
-
const
|
|
691
|
+
const temperatureReciprocal = 1 / (temperature + 1e-40)
|
|
692
692
|
|
|
693
693
|
const result: number[] = []
|
|
694
694
|
|
|
695
695
|
let sumOfExponentiatedValues = 0.0
|
|
696
696
|
|
|
697
697
|
for (const value of logits) {
|
|
698
|
-
const eToValue = Math.exp((value - maxValue) *
|
|
698
|
+
const eToValue = Math.exp((value - maxValue) * temperatureReciprocal)
|
|
699
699
|
|
|
700
700
|
sumOfExponentiatedValues += eToValue
|
|
701
701
|
|
|
702
702
|
result.push(eToValue)
|
|
703
703
|
}
|
|
704
704
|
|
|
705
|
-
const
|
|
705
|
+
const sumOfExponentiatedValuesReciprocal = 1 / (sumOfExponentiatedValues + 1e-40)
|
|
706
706
|
|
|
707
707
|
for (let i = 0; i < result.length; i++) {
|
|
708
|
-
result[i] *=
|
|
708
|
+
result[i] *= sumOfExponentiatedValuesReciprocal
|
|
709
709
|
}
|
|
710
710
|
|
|
711
711
|
return result
|
|
@@ -789,6 +789,7 @@ export function sumExp(values: number[]) {
|
|
|
789
789
|
|
|
790
790
|
export function logSoftmax(values: number[], minVal = 1e-40) {
|
|
791
791
|
const softMaxOfValues = softmax(values)
|
|
792
|
+
|
|
792
793
|
return logOfVector(softMaxOfValues, minVal)
|
|
793
794
|
}
|
|
794
795
|
|
package/src/server/Server.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { logToStderr } from '../utilities/Utilities.js'
|
|
|
4
4
|
import { OpenPromise } from '../utilities/OpenPromise.js'
|
|
5
5
|
import { readFile, existsSync } from '../utilities/FileSystem.js'
|
|
6
6
|
import { extendDeep } from '../utilities/ObjectUtilities.js'
|
|
7
|
-
import { sendMessageToWorker, addListenerToWorkerMessages, startNewWorkerThread } from './Worker.js'
|
|
7
|
+
import { sendMessageToWorker, addListenerToWorkerMessages, startNewWorkerThread, startMessageChannel } from './Worker.js'
|
|
8
8
|
import { Worker } from 'node:worker_threads'
|
|
9
9
|
import { IncomingMessage, ServerResponse } from 'node:http'
|
|
10
10
|
import { Logger } from '../utilities/Logger.js'
|
|
@@ -87,6 +87,7 @@ export async function startServer(serverOptions: ServerOptions, onStarted: (opti
|
|
|
87
87
|
workerThread = await startNewWorkerThread()
|
|
88
88
|
workerThread.on('message', onWorkerMessage)
|
|
89
89
|
} else {
|
|
90
|
+
startMessageChannel()
|
|
90
91
|
addListenerToWorkerMessages(onWorkerMessage)
|
|
91
92
|
}
|
|
92
93
|
|
package/src/server/Worker.ts
CHANGED
|
@@ -14,29 +14,38 @@ import chalk from "chalk"
|
|
|
14
14
|
|
|
15
15
|
const log = logToStderr
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
messageChannel.port1.start()
|
|
19
|
-
messageChannel.port2.start()
|
|
17
|
+
let messageChannel: MessageChannel | undefined = undefined
|
|
20
18
|
|
|
21
19
|
const canceledRequests = new Set<string>()
|
|
20
|
+
|
|
22
21
|
let cancelCurrentTask = false
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
23
|
+
const messageQueue = new Queue<any>()
|
|
24
|
+
let isProcessing = false
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
//log(`CANCEL REQUESTED FOR ${message.requestId}`)
|
|
31
|
-
canceledRequests.add(message.requestId)
|
|
26
|
+
export function startMessageChannel() {
|
|
27
|
+
if (messageChannel != null) {
|
|
32
28
|
return
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
messageChannel = new MessageChannel()
|
|
32
|
+
messageChannel.port1.start()
|
|
33
|
+
messageChannel.port2.start()
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
addListenerToClientMessages((message) => {
|
|
36
|
+
if (message.messageType == "CancelationRequest") {
|
|
37
|
+
//log(`CANCEL REQUESTED FOR ${message.requestId}`)
|
|
38
|
+
canceledRequests.add(message.requestId)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
enqueueAndProcessIfIdle(message)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function shouldCancelCurrentTask() {
|
|
47
|
+
return cancelCurrentTask
|
|
48
|
+
}
|
|
40
49
|
|
|
41
50
|
function enqueueAndProcessIfIdle(message: any) {
|
|
42
51
|
messageQueue.enqueue(message)
|
|
@@ -344,25 +353,39 @@ export interface TextLanguageDetectionResponseMessage extends WorkerMessageBase,
|
|
|
344
353
|
// Messaging methods
|
|
345
354
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
346
355
|
export function sendMessageToWorker(message: any) {
|
|
347
|
-
|
|
356
|
+
ensureMessageChannelCreated()
|
|
357
|
+
|
|
358
|
+
messageChannel?.port1.postMessage(message)
|
|
348
359
|
}
|
|
349
360
|
|
|
350
361
|
export function addListenerToWorkerMessages(handler: MessageFunc) {
|
|
351
|
-
|
|
362
|
+
ensureMessageChannelCreated()
|
|
363
|
+
|
|
364
|
+
messageChannel?.port1.addEventListener('message', (event) => {
|
|
352
365
|
handler(event.data)
|
|
353
366
|
})
|
|
354
367
|
}
|
|
355
368
|
|
|
356
369
|
function sendMessageToClient(message: any) {
|
|
357
|
-
|
|
370
|
+
ensureMessageChannelCreated()
|
|
371
|
+
|
|
372
|
+
messageChannel?.port2.postMessage(message)
|
|
358
373
|
}
|
|
359
374
|
|
|
360
375
|
function addListenerToClientMessages(handler: MessageFunc) {
|
|
361
|
-
|
|
376
|
+
ensureMessageChannelCreated()
|
|
377
|
+
|
|
378
|
+
messageChannel?.port2.addEventListener('message', (event) => {
|
|
362
379
|
handler(event.data)
|
|
363
380
|
})
|
|
364
381
|
}
|
|
365
382
|
|
|
383
|
+
function ensureMessageChannelCreated() {
|
|
384
|
+
if (messageChannel == null) {
|
|
385
|
+
throw new Error(`Message channel has not been created`)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
366
389
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
367
390
|
// Worker thread methods
|
|
368
391
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isMainThread, parentPort } from 'node:worker_threads'
|
|
2
|
-
import { sendMessageToWorker, addListenerToWorkerMessages } from './Worker.js'
|
|
2
|
+
import { sendMessageToWorker, addListenerToWorkerMessages, startMessageChannel } from './Worker.js'
|
|
3
3
|
import { OpenPromise } from '../utilities/OpenPromise.js'
|
|
4
4
|
|
|
5
5
|
async function startIfInWorkerThread() {
|
|
@@ -7,6 +7,8 @@ async function startIfInWorkerThread() {
|
|
|
7
7
|
return
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
startMessageChannel()
|
|
11
|
+
|
|
10
12
|
addListenerToWorkerMessages((message) => {
|
|
11
13
|
parentPort?.postMessage(message)
|
|
12
14
|
})
|
|
@@ -13,7 +13,7 @@ export async function synthesize(
|
|
|
13
13
|
subscriptionKey: string,
|
|
14
14
|
serviceRegion: string,
|
|
15
15
|
languageCode = "en-US",
|
|
16
|
-
voice = "Microsoft Server Speech Text to Speech Voice (en-US,
|
|
16
|
+
voice = "Microsoft Server Speech Text to Speech Voice (en-US, AvaNeural)",
|
|
17
17
|
ssmlEnabled = false,
|
|
18
18
|
ssmlPitchString = "+0Hz",
|
|
19
19
|
ssmlRateString = "+0%") {
|
|
@@ -10,7 +10,8 @@ import * as FFMpegTranscoder from "../codecs/FFMpegTranscoder.js"
|
|
|
10
10
|
import { Logger } from "../utilities/Logger.js"
|
|
11
11
|
import { OpenPromise } from "../utilities/OpenPromise.js"
|
|
12
12
|
import { getRandomHexString, logToStderr } from "../utilities/Utilities.js"
|
|
13
|
-
import { getRawAudioDuration } from "../audio/AudioUtilities.js"
|
|
13
|
+
import { RawAudio, getEmptyRawAudio, getRawAudioDuration } from "../audio/AudioUtilities.js"
|
|
14
|
+
import { Timer } from "../utilities/Timer.js"
|
|
14
15
|
|
|
15
16
|
const traceEnabled = false
|
|
16
17
|
|
|
@@ -19,7 +20,7 @@ const log: typeof logToStderr = traceEnabled ? logToStderr : () => { }
|
|
|
19
20
|
export async function synthesize(
|
|
20
21
|
text: string,
|
|
21
22
|
trustedClientToken: string,
|
|
22
|
-
voice = "Microsoft Server Speech Text to Speech Voice (en-US,
|
|
23
|
+
voice = "Microsoft Server Speech Text to Speech Voice (en-US, AvaNeural)",
|
|
23
24
|
ssmlPitchString = "+0Hz",
|
|
24
25
|
ssmlRateString = "+0%",
|
|
25
26
|
ssmlVolumeString = "+0%") {
|
|
@@ -30,7 +31,17 @@ export async function synthesize(
|
|
|
30
31
|
const { audioData, events } = await requestSynthesis(text, trustedClientToken, voice, ssmlPitchString, ssmlRateString, ssmlVolumeString)
|
|
31
32
|
logger.end()
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
//logToStderr(`Audio length: ${audioData.length}`)
|
|
35
|
+
|
|
36
|
+
let rawAudio: RawAudio
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
rawAudio = await FFMpegTranscoder.decodeToChannels(audioData, 24000, 1)
|
|
40
|
+
} catch (e) {
|
|
41
|
+
rawAudio = getEmptyRawAudio(1, 24000)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//logToStderr(`Raw audio length: ${rawAudio.audioChannels[0].length}`)
|
|
34
45
|
|
|
35
46
|
logger.start("Convert boundary events to timeline")
|
|
36
47
|
const timeline = AzureCognitiveServicesTTS.boundaryEventsToTimeline(events, getRawAudioDuration(rawAudio))
|
|
@@ -51,15 +62,55 @@ async function requestSynthesis(
|
|
|
51
62
|
|
|
52
63
|
const synthesisOpenPromise = new OpenPromise<SynthesisRequestResult>()
|
|
53
64
|
|
|
65
|
+
let connectionFailed = false
|
|
66
|
+
let responseComplete = false
|
|
67
|
+
|
|
68
|
+
let lastReceievedMessageTime = Timer.currentTime
|
|
69
|
+
|
|
70
|
+
function checkForRequestTimeout() {
|
|
71
|
+
if (responseComplete || connectionFailed) {
|
|
72
|
+
clearInterval(timeoutCheckInterval)
|
|
73
|
+
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!webSocket) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (Timer.currentTime - lastReceievedMessageTime > 10000) {
|
|
82
|
+
clearInterval(timeoutCheckInterval)
|
|
83
|
+
|
|
84
|
+
removeWebSocketHandlers()
|
|
85
|
+
webSocket.close()
|
|
86
|
+
|
|
87
|
+
synthesisOpenPromise.reject(new Error(`WebSocket request timed out after 10 seconds without a message from the server`))
|
|
88
|
+
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let webSocket: WebSocket = undefined as any
|
|
94
|
+
|
|
95
|
+
const timeoutCheckInterval = setInterval(checkForRequestTimeout, 1000)
|
|
96
|
+
|
|
54
97
|
const requestId = getRandomHexString()
|
|
55
98
|
|
|
56
|
-
|
|
99
|
+
try {
|
|
100
|
+
webSocket = await initializeWebsocketConnection(trustedClientToken)
|
|
101
|
+
} catch(e) {
|
|
102
|
+
connectionFailed = true
|
|
103
|
+
|
|
104
|
+
throw e
|
|
105
|
+
}
|
|
57
106
|
|
|
58
107
|
const receivedBinaryMessages: Buffer[] = []
|
|
59
108
|
const receivedEventMessages: any[] = []
|
|
60
109
|
const audioChunks: Buffer[] = []
|
|
61
110
|
|
|
62
111
|
const onMessage = (messageData: Buffer, isBinary: boolean) => {
|
|
112
|
+
lastReceievedMessageTime = Timer.currentTime
|
|
113
|
+
|
|
63
114
|
if (isBinary) {
|
|
64
115
|
const [header, audioChunk] = parseBinaryMessage(messageData)
|
|
65
116
|
|
|
@@ -98,17 +149,22 @@ async function requestSynthesis(
|
|
|
98
149
|
else if (header["Path"] == "turn.end") {
|
|
99
150
|
const result: SynthesisRequestResult = { audioData: Buffer.concat(audioChunks), events: receivedEventMessages }
|
|
100
151
|
|
|
101
|
-
|
|
102
|
-
webSocket.off("error", onError)
|
|
103
|
-
webSocket.off("close", onClose)
|
|
152
|
+
removeWebSocketHandlers()
|
|
104
153
|
|
|
105
154
|
synthesisOpenPromise.resolve(result)
|
|
155
|
+
responseComplete = true
|
|
106
156
|
}
|
|
107
157
|
}
|
|
108
158
|
}
|
|
109
159
|
|
|
110
160
|
const onError = (err: Error) => {
|
|
111
161
|
synthesisOpenPromise.reject(err)
|
|
162
|
+
|
|
163
|
+
removeWebSocketHandlers()
|
|
164
|
+
|
|
165
|
+
if (webSocket) {
|
|
166
|
+
webSocket.close()
|
|
167
|
+
}
|
|
112
168
|
}
|
|
113
169
|
|
|
114
170
|
const onClose = async (code: number, reason: Buffer) => {
|
|
@@ -117,7 +173,15 @@ async function requestSynthesis(
|
|
|
117
173
|
log(reason.toString())
|
|
118
174
|
|
|
119
175
|
if (reason.length > 0) {
|
|
120
|
-
synthesisOpenPromise.reject(`Websocket closed with code ${code}, reason: ${reason}`)
|
|
176
|
+
synthesisOpenPromise.reject(new Error(`Websocket closed with code ${code}, reason: ${reason}`))
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function removeWebSocketHandlers() {
|
|
181
|
+
if (webSocket) {
|
|
182
|
+
webSocket.off("message", onMessage)
|
|
183
|
+
webSocket.off("error", onError)
|
|
184
|
+
webSocket.off("close", onClose)
|
|
121
185
|
}
|
|
122
186
|
}
|
|
123
187
|
|
|
@@ -224,7 +288,7 @@ export async function initializeWebsocketConnection(trustedClientToken: string)
|
|
|
224
288
|
log(reason.toString())
|
|
225
289
|
|
|
226
290
|
if (reason.length > 0) {
|
|
227
|
-
requestOpenPromise.reject(`Websocket closed with code ${code}, reason: ${reason}`)
|
|
291
|
+
requestOpenPromise.reject(new Error(`Websocket closed with code ${code}, reason: ${reason}`))
|
|
228
292
|
}
|
|
229
293
|
}
|
|
230
294
|
|
package/src/utilities/Logger.ts
CHANGED
|
@@ -24,9 +24,20 @@ export class Logger {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
writeToStderr(`${titleColor(title)}.. `)
|
|
27
|
+
|
|
28
|
+
this.setAsActiveLogger()
|
|
29
|
+
|
|
30
|
+
this.timer.restart()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setAsActiveLogger() {
|
|
27
34
|
this.active = true
|
|
28
35
|
currentActiveLogger = this
|
|
29
|
-
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
unsetAsActiveLogger() {
|
|
39
|
+
this.active = false
|
|
40
|
+
currentActiveLogger = null
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
end() {
|
|
@@ -4,6 +4,8 @@ import { inspect } from 'node:util'
|
|
|
4
4
|
|
|
5
5
|
import { RandomGenerator } from './RandomGenerator.js'
|
|
6
6
|
import { randomUUID, randomBytes } from 'node:crypto'
|
|
7
|
+
import { Logger } from './Logger.js'
|
|
8
|
+
import chalk from 'chalk'
|
|
7
9
|
|
|
8
10
|
const log = logToStderr
|
|
9
11
|
|
|
@@ -445,9 +447,9 @@ export function splitFilenameOnExtendedExtension(filenameWithExtension: string)
|
|
|
445
447
|
splitPoint = i
|
|
446
448
|
|
|
447
449
|
continue
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
450
|
+
} else {
|
|
451
|
+
break
|
|
452
|
+
}
|
|
451
453
|
}
|
|
452
454
|
}
|
|
453
455
|
|
|
@@ -566,9 +568,40 @@ export function getConsecutiveRepetitionScoreRelativeToFirstSubstring(tokens: st
|
|
|
566
568
|
export async function resolveModuleScriptPath(moduleName: string) {
|
|
567
569
|
const { resolve } = await import('import-meta-resolve')
|
|
568
570
|
|
|
569
|
-
const scriptPath =
|
|
571
|
+
const scriptPath = resolve(moduleName, import.meta.url)
|
|
570
572
|
|
|
571
573
|
const { fileURLToPath } = await import('url')
|
|
572
574
|
|
|
573
575
|
return fileURLToPath(scriptPath)
|
|
574
576
|
}
|
|
577
|
+
|
|
578
|
+
export async function runOperationWithRetries<R>(
|
|
579
|
+
operationFunc: () => Promise<R>,
|
|
580
|
+
logger: Logger,
|
|
581
|
+
opreationName = "Operation",
|
|
582
|
+
delayBetweenRetries = 2000,
|
|
583
|
+
maxRetries = 1000) {
|
|
584
|
+
|
|
585
|
+
for (let retryIndex = 1; retryIndex <= maxRetries; retryIndex++) {
|
|
586
|
+
try {
|
|
587
|
+
const result = await operationFunc()
|
|
588
|
+
|
|
589
|
+
return result
|
|
590
|
+
} catch (e: any) {
|
|
591
|
+
logger.setAsActiveLogger()
|
|
592
|
+
|
|
593
|
+
logger.logTitledMessage(`Error`, e.message, chalk.redBright)
|
|
594
|
+
logger.log(``)
|
|
595
|
+
logger.logTitledMessage(`${opreationName} failed`, `Trying again in ${delayBetweenRetries}ms..`, chalk.redBright)
|
|
596
|
+
|
|
597
|
+
await delay(delayBetweenRetries)
|
|
598
|
+
|
|
599
|
+
logger.log(``)
|
|
600
|
+
logger.logTitledMessage(`Starting attempt`, `${retryIndex} / ${maxRetries}`)
|
|
601
|
+
|
|
602
|
+
logger.unsetAsActiveLogger()
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
throw new Error(`${opreationName} failed after ${maxRetries} retry attempts`)
|
|
607
|
+
}
|