speechflow 1.5.0 → 1.5.1
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/CHANGELOG.md +7 -0
- package/README.md +22 -0
- package/etc/speechflow.yaml +8 -8
- package/package.json +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +2 -12
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js +4 -9
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js +35 -2
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +36 -2
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js +36 -2
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +3 -2
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-google.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-t2t-google.js +153 -0
- package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +36 -2
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js +36 -2
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +3 -2
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +3 -2
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/speechflow-cli/dst/speechflow-utils.d.ts +16 -0
- package/speechflow-cli/dst/speechflow-utils.js +133 -0
- package/speechflow-cli/dst/speechflow-utils.js.map +1 -1
- package/speechflow-cli/dst/speechflow.js +7 -13
- package/speechflow-cli/dst/speechflow.js.map +1 -1
- package/speechflow-cli/etc/biome.jsonc +2 -1
- package/speechflow-cli/etc/oxlint.jsonc +2 -1
- package/speechflow-cli/package.json +6 -5
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +2 -12
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2t-awstranscribe.ts +8 -10
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2t-openaitranscribe.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2a-awspolly.ts +3 -3
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +3 -2
- package/speechflow-cli/src/speechflow-node-t2t-awstranslate.ts +3 -2
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +3 -2
- package/speechflow-cli/src/speechflow-node-t2t-google.ts +133 -0
- package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +3 -2
- package/speechflow-cli/src/speechflow-node-t2t-openai.ts +3 -2
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +3 -2
- package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +3 -2
- package/speechflow-cli/src/speechflow-utils.ts +189 -0
- package/speechflow-cli/src/speechflow.ts +11 -17
- package/speechflow-ui-db/dst/app-font-fa-brands-400.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-regular-400.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-solid-900.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-v4compatibility.woff2 +0 -0
- package/speechflow-ui-db/dst/index.css +2 -2
- package/speechflow-ui-db/dst/index.js +37 -38
- package/speechflow-ui-db/package.json +7 -7
- package/speechflow-ui-st/package.json +7 -7
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"@aws-sdk/client-transcribe-streaming": "3.879.0",
|
|
33
33
|
"@aws-sdk/client-translate": "3.879.0",
|
|
34
34
|
"@aws-sdk/client-polly": "3.879.0",
|
|
35
|
+
"@google-cloud/translate": "9.2.0",
|
|
35
36
|
"node-web-audio-api": "1.0.4",
|
|
36
37
|
"object-path": "0.11.8",
|
|
37
38
|
"ws": "8.18.3",
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"hapi-plugin-websocket": "2.4.11",
|
|
45
46
|
"@opensumi/reconnecting-websocket": "4.4.0",
|
|
46
47
|
"ollama": "0.5.17",
|
|
47
|
-
"openai": "5.
|
|
48
|
+
"openai": "5.18.0",
|
|
48
49
|
"@rse/ffmpeg": "1.4.2",
|
|
49
50
|
"ffmpeg-stream": "1.0.1",
|
|
50
51
|
"installed-packages": "1.0.13",
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
"node-interval-tree": "2.1.2",
|
|
65
66
|
"wrap-text": "1.0.10",
|
|
66
67
|
"cli-table3": "0.6.5",
|
|
67
|
-
"@rse/stx": "1.0
|
|
68
|
+
"@rse/stx": "1.1.0"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
71
|
"eslint": "9.34.0",
|
|
@@ -73,9 +74,9 @@
|
|
|
73
74
|
"eslint-plugin-promise": "7.2.1",
|
|
74
75
|
"eslint-plugin-import": "2.32.0",
|
|
75
76
|
"eslint-plugin-node": "11.1.0",
|
|
76
|
-
"typescript-eslint": "8.
|
|
77
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
78
|
-
"@typescript-eslint/parser": "8.
|
|
77
|
+
"typescript-eslint": "8.42.0",
|
|
78
|
+
"@typescript-eslint/eslint-plugin": "8.42.0",
|
|
79
|
+
"@typescript-eslint/parser": "8.42.0",
|
|
79
80
|
"oxlint": "1.14.0",
|
|
80
81
|
"eslint-plugin-oxlint": "1.14.0",
|
|
81
82
|
"@biomejs/biome": "2.0.6",
|
|
@@ -255,8 +255,8 @@ export default class SpeechFlowNodeCompressor extends SpeechFlowNode {
|
|
|
255
255
|
}
|
|
256
256
|
this.push(chunk)
|
|
257
257
|
callback()
|
|
258
|
-
}).catch((error) => {
|
|
259
|
-
callback(
|
|
258
|
+
}).catch((error: unknown) => {
|
|
259
|
+
callback(utils.ensureError(error, "compression failed"))
|
|
260
260
|
})
|
|
261
261
|
}
|
|
262
262
|
},
|
|
@@ -176,8 +176,8 @@ export default class SpeechFlowNodeExpander extends SpeechFlowNode {
|
|
|
176
176
|
chunk.payload = payload
|
|
177
177
|
this.push(chunk)
|
|
178
178
|
callback()
|
|
179
|
-
}).catch((error) => {
|
|
180
|
-
callback(
|
|
179
|
+
}).catch((error: unknown) => {
|
|
180
|
+
callback(utils.ensureError(error, "expansion failed"))
|
|
181
181
|
})
|
|
182
182
|
}
|
|
183
183
|
},
|
|
@@ -90,12 +90,7 @@ export default class SpeechFlowNodeFFmpeg extends SpeechFlowNode {
|
|
|
90
90
|
"f": "opus"
|
|
91
91
|
} : {})
|
|
92
92
|
})
|
|
93
|
-
|
|
94
|
-
this.ffmpeg.run()
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
throw new Error(`failed to start FFmpeg process: ${err}`)
|
|
98
|
-
}
|
|
93
|
+
utils.run("starting FFmpeg process", () => this.ffmpeg!.run())
|
|
99
94
|
|
|
100
95
|
/* establish a duplex stream and connect it to FFmpeg */
|
|
101
96
|
this.stream = Stream.Duplex.from({
|
|
@@ -125,12 +120,7 @@ export default class SpeechFlowNodeFFmpeg extends SpeechFlowNode {
|
|
|
125
120
|
|
|
126
121
|
/* shutdown FFmpeg */
|
|
127
122
|
if (this.ffmpeg !== null) {
|
|
128
|
-
|
|
129
|
-
this.ffmpeg.kill()
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
/* ignore kill errors during cleanup */
|
|
133
|
-
}
|
|
123
|
+
utils.run(() => this.ffmpeg!.kill(), () => {})
|
|
134
124
|
this.ffmpeg = null
|
|
135
125
|
}
|
|
136
126
|
}
|
|
@@ -180,9 +180,9 @@ export default class SpeechFlowNodeFiller extends SpeechFlowNode {
|
|
|
180
180
|
self.log("debug", `received data (${chunk.payload.length} bytes)`)
|
|
181
181
|
this.push(chunk)
|
|
182
182
|
}
|
|
183
|
-
}).catch((error) => {
|
|
183
|
+
}).catch((error: unknown) => {
|
|
184
184
|
if (!self.destroyed)
|
|
185
|
-
self.log("error", `queue read error: ${error.message}`)
|
|
185
|
+
self.log("error", `queue read error: ${utils.ensureError(error).message}`)
|
|
186
186
|
})
|
|
187
187
|
},
|
|
188
188
|
final (callback) {
|
|
@@ -224,8 +224,8 @@ export default class SpeechFlowNodeAWSTranscribe extends SpeechFlowNode {
|
|
|
224
224
|
if (chunk.meta.size > 0)
|
|
225
225
|
metastore.store(chunk.timestampStart, chunk.timestampEnd, chunk.meta)
|
|
226
226
|
audioQueue.push(new Uint8Array(chunk.payload)) /* intentionally discard all time information */
|
|
227
|
-
ensureAudioStreamActive().catch((
|
|
228
|
-
self.log("error", `failed to start audio stream: ${
|
|
227
|
+
ensureAudioStreamActive().catch((error: unknown) => {
|
|
228
|
+
self.log("error", `failed to start audio stream: ${utils.ensureError(error).message}`)
|
|
229
229
|
})
|
|
230
230
|
}
|
|
231
231
|
callback()
|
|
@@ -249,9 +249,9 @@ export default class SpeechFlowNodeAWSTranscribe extends SpeechFlowNode {
|
|
|
249
249
|
self.log("debug", `received data (${chunk.payload.length} bytes): "${chunk.payload}"`)
|
|
250
250
|
this.push(chunk)
|
|
251
251
|
}
|
|
252
|
-
}).catch((error) => {
|
|
252
|
+
}).catch((error: unknown) => {
|
|
253
253
|
if (!self.destroyed)
|
|
254
|
-
self.log("error", `queue read error: ${error.message}`)
|
|
254
|
+
self.log("error", `queue read error: ${utils.ensureError(error).message}`)
|
|
255
255
|
})
|
|
256
256
|
},
|
|
257
257
|
final (callback) {
|
|
@@ -259,12 +259,10 @@ export default class SpeechFlowNodeAWSTranscribe extends SpeechFlowNode {
|
|
|
259
259
|
callback()
|
|
260
260
|
return
|
|
261
261
|
}
|
|
262
|
-
|
|
263
|
-
self.client
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
self.log("warning", `error closing Amazon Transcribe connection: ${error}`)
|
|
267
|
-
}
|
|
262
|
+
utils.run(
|
|
263
|
+
() => self.client!.destroy(),
|
|
264
|
+
(error: Error) => self.log("warning", `error closing Amazon Transcribe connection: ${error}`)
|
|
265
|
+
)
|
|
268
266
|
audioQueue.push(null) /* do not push null to stream, let Amazon Transcribe do it */
|
|
269
267
|
audioQueue.destroy()
|
|
270
268
|
callback()
|
|
@@ -232,9 +232,9 @@ export default class SpeechFlowNodeDeepgram extends SpeechFlowNode {
|
|
|
232
232
|
self.log("debug", `received data (${chunk.payload.length} bytes)`)
|
|
233
233
|
this.push(chunk)
|
|
234
234
|
}
|
|
235
|
-
}).catch((error) => {
|
|
235
|
+
}).catch((error: unknown) => {
|
|
236
236
|
if (!self.destroyed)
|
|
237
|
-
self.log("error", `queue read error: ${error.message}`)
|
|
237
|
+
self.log("error", `queue read error: ${utils.ensureError(error).message}`)
|
|
238
238
|
})
|
|
239
239
|
},
|
|
240
240
|
final (callback) {
|
|
@@ -279,9 +279,9 @@ export default class SpeechFlowNodeOpenAITranscribe extends SpeechFlowNode {
|
|
|
279
279
|
self.log("debug", `received data (${chunk.payload.length} bytes)`)
|
|
280
280
|
this.push(chunk)
|
|
281
281
|
}
|
|
282
|
-
}).catch((error) => {
|
|
282
|
+
}).catch((error: unknown) => {
|
|
283
283
|
if (!self.destroyed)
|
|
284
|
-
self.log("error", `queue read error: ${error.message}`)
|
|
284
|
+
self.log("error", `queue read error: ${utils.ensureError(error).message}`)
|
|
285
285
|
})
|
|
286
286
|
},
|
|
287
287
|
final (callback) {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
|
|
18
18
|
/* internal dependencies */
|
|
19
19
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
20
|
+
import * as utils from "./speechflow-utils"
|
|
20
21
|
|
|
21
22
|
/* SpeechFlow node for AWS Polly text-to-speech conversion */
|
|
22
23
|
export default class SpeechFlowNodeAWSPolly extends SpeechFlowNode {
|
|
@@ -144,9 +145,8 @@ export default class SpeechFlowNodeAWSPolly extends SpeechFlowNode {
|
|
|
144
145
|
chunkNew.payload = buffer
|
|
145
146
|
this.push(chunkNew)
|
|
146
147
|
callback()
|
|
147
|
-
}).catch((error) => {
|
|
148
|
-
callback(error
|
|
149
|
-
error : new Error(`failed to send to AWS Polly: ${String(error)}`))
|
|
148
|
+
}).catch((error: unknown) => {
|
|
149
|
+
callback(utils.ensureError(error, "failed to send to AWS Polly"))
|
|
150
150
|
})
|
|
151
151
|
}
|
|
152
152
|
else
|
|
@@ -13,6 +13,7 @@ import SpeexResampler from "speex-resampler"
|
|
|
13
13
|
|
|
14
14
|
/* internal dependencies */
|
|
15
15
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
16
|
+
import * as utils from "./speechflow-utils"
|
|
16
17
|
|
|
17
18
|
/* SpeechFlow node for Kokoro text-to-speech conversion */
|
|
18
19
|
export default class SpeechFlowNodeKokoro extends SpeechFlowNode {
|
|
@@ -141,8 +142,8 @@ export default class SpeechFlowNodeKokoro extends SpeechFlowNode {
|
|
|
141
142
|
chunk.payload = buffer
|
|
142
143
|
this.push(chunk)
|
|
143
144
|
callback()
|
|
144
|
-
}).catch((
|
|
145
|
-
callback(
|
|
145
|
+
}).catch((error: unknown) => {
|
|
146
|
+
callback(utils.ensureError(error))
|
|
146
147
|
})
|
|
147
148
|
}
|
|
148
149
|
},
|
|
@@ -12,6 +12,7 @@ import { TranslateClient, TranslateTextCommand } from "@aws-sdk/client-translate
|
|
|
12
12
|
|
|
13
13
|
/* internal dependencies */
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
15
|
+
import * as utils from "./speechflow-utils"
|
|
15
16
|
|
|
16
17
|
/* SpeechFlow node for AWS Translate text-to-text translations */
|
|
17
18
|
export default class SpeechFlowNodeAWSTranslate extends SpeechFlowNode {
|
|
@@ -122,8 +123,8 @@ export default class SpeechFlowNodeAWSTranslate extends SpeechFlowNode {
|
|
|
122
123
|
chunkNew.payload = payload
|
|
123
124
|
this.push(chunkNew)
|
|
124
125
|
callback()
|
|
125
|
-
}).catch((
|
|
126
|
-
callback(
|
|
126
|
+
}).catch((error: unknown) => {
|
|
127
|
+
callback(utils.ensureError(error))
|
|
127
128
|
})
|
|
128
129
|
}
|
|
129
130
|
},
|
|
@@ -12,6 +12,7 @@ import * as DeepL from "deepl-node"
|
|
|
12
12
|
|
|
13
13
|
/* internal dependencies */
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
15
|
+
import * as utils from "./speechflow-utils"
|
|
15
16
|
|
|
16
17
|
/* SpeechFlow node for DeepL text-to-text translations */
|
|
17
18
|
export default class SpeechFlowNodeDeepL extends SpeechFlowNode {
|
|
@@ -93,8 +94,8 @@ export default class SpeechFlowNodeDeepL extends SpeechFlowNode {
|
|
|
93
94
|
chunkNew.payload = payload
|
|
94
95
|
this.push(chunkNew)
|
|
95
96
|
callback()
|
|
96
|
-
}).catch((
|
|
97
|
-
callback(
|
|
97
|
+
}).catch((error: unknown) => {
|
|
98
|
+
callback(utils.ensureError(error))
|
|
98
99
|
})
|
|
99
100
|
}
|
|
100
101
|
},
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* standard dependencies */
|
|
8
|
+
import Stream from "node:stream"
|
|
9
|
+
|
|
10
|
+
/* external dependencies */
|
|
11
|
+
import { TranslationServiceClient } from "@google-cloud/translate"
|
|
12
|
+
import * as arktype from "arktype"
|
|
13
|
+
|
|
14
|
+
/* internal dependencies */
|
|
15
|
+
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
16
|
+
import * as utils from "./speechflow-utils"
|
|
17
|
+
|
|
18
|
+
/* SpeechFlow node for Google Translate text-to-text translations */
|
|
19
|
+
export default class SpeechFlowNodeGoogle extends SpeechFlowNode {
|
|
20
|
+
/* declare official node name */
|
|
21
|
+
public static name = "google"
|
|
22
|
+
|
|
23
|
+
/* internal state */
|
|
24
|
+
private client: TranslationServiceClient | null = null
|
|
25
|
+
|
|
26
|
+
/* construct node */
|
|
27
|
+
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
28
|
+
super(id, cfg, opts, args)
|
|
29
|
+
|
|
30
|
+
/* declare node configuration parameters */
|
|
31
|
+
this.configure({
|
|
32
|
+
key: { type: "string", val: process.env.SPEECHFLOW_GOOGLE_KEY ?? "" },
|
|
33
|
+
src: { type: "string", pos: 0, val: "de", match: /^(?:de|en|fr|it)$/ },
|
|
34
|
+
dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en|fr|it)$/ }
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
/* validate API key and project */
|
|
38
|
+
if (this.params.key === "")
|
|
39
|
+
throw new Error("Google Cloud API credentials JSON key is required")
|
|
40
|
+
|
|
41
|
+
/* sanity check situation */
|
|
42
|
+
if (this.params.src === this.params.dst)
|
|
43
|
+
throw new Error("source and destination languages cannot be the same")
|
|
44
|
+
|
|
45
|
+
/* declare node input/output format */
|
|
46
|
+
this.input = "text"
|
|
47
|
+
this.output = "text"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* one-time status of node */
|
|
51
|
+
async status () {
|
|
52
|
+
return {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* open node */
|
|
56
|
+
async open () {
|
|
57
|
+
/* instantiate Google Translate client */
|
|
58
|
+
const data = utils.run("Google Cloud API credentials key", () =>
|
|
59
|
+
JSON.parse(this.params.key))
|
|
60
|
+
const credentials = utils.importObject("Google Cloud API credentials key",
|
|
61
|
+
data,
|
|
62
|
+
arktype.type({
|
|
63
|
+
project_id: "string",
|
|
64
|
+
private_key: "string",
|
|
65
|
+
client_email: "string",
|
|
66
|
+
})
|
|
67
|
+
)
|
|
68
|
+
this.client = new TranslationServiceClient({
|
|
69
|
+
credentials: {
|
|
70
|
+
private_key: credentials.private_key,
|
|
71
|
+
client_email: credentials.client_email
|
|
72
|
+
},
|
|
73
|
+
projectId: credentials.project_id
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
/* provide text-to-text translation */
|
|
77
|
+
const translate = utils.runner("Google Translate API", async (text: string) => {
|
|
78
|
+
const [ response ] = await this.client!.translateText({
|
|
79
|
+
parent: `projects/${credentials.project_id}/locations/global`,
|
|
80
|
+
contents: [ text ],
|
|
81
|
+
mimeType: "text/plain",
|
|
82
|
+
sourceLanguageCode: this.params.src,
|
|
83
|
+
targetLanguageCode: this.params.dst
|
|
84
|
+
})
|
|
85
|
+
return response.translations?.[0]?.translatedText ?? text
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
/* establish a duplex stream and connect it to Google Translate */
|
|
89
|
+
this.stream = new Stream.Transform({
|
|
90
|
+
readableObjectMode: true,
|
|
91
|
+
writableObjectMode: true,
|
|
92
|
+
decodeStrings: false,
|
|
93
|
+
highWaterMark: 1,
|
|
94
|
+
transform (chunk: SpeechFlowChunk, encoding, callback) {
|
|
95
|
+
if (Buffer.isBuffer(chunk.payload))
|
|
96
|
+
callback(new Error("invalid chunk payload type"))
|
|
97
|
+
else if (chunk.payload === "") {
|
|
98
|
+
this.push(chunk)
|
|
99
|
+
callback()
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
translate(chunk.payload).then((payload) => {
|
|
103
|
+
const chunkNew = chunk.clone()
|
|
104
|
+
chunkNew.payload = payload
|
|
105
|
+
this.push(chunkNew)
|
|
106
|
+
callback()
|
|
107
|
+
}).catch((error: unknown) => {
|
|
108
|
+
callback(utils.ensureError(error))
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
final (callback) {
|
|
113
|
+
this.push(null)
|
|
114
|
+
callback()
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* close node */
|
|
120
|
+
async close () {
|
|
121
|
+
/* close stream */
|
|
122
|
+
if (this.stream !== null) {
|
|
123
|
+
this.stream.destroy()
|
|
124
|
+
this.stream = null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* shutdown Google Translate client */
|
|
128
|
+
if (this.client !== null) {
|
|
129
|
+
this.client.close()
|
|
130
|
+
this.client = null
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -12,6 +12,7 @@ import { Ollama, type ListResponse } from "ollama"
|
|
|
12
12
|
|
|
13
13
|
/* internal dependencies */
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
15
|
+
import * as utils from "./speechflow-utils"
|
|
15
16
|
|
|
16
17
|
/* internal utility types */
|
|
17
18
|
type ConfigEntry = { systemPrompt: string, chat: Array<{ role: string, content: string }> }
|
|
@@ -250,8 +251,8 @@ export default class SpeechFlowNodeOllama extends SpeechFlowNode {
|
|
|
250
251
|
chunkNew.payload = payload
|
|
251
252
|
this.push(chunkNew)
|
|
252
253
|
callback()
|
|
253
|
-
}).catch((
|
|
254
|
-
callback(
|
|
254
|
+
}).catch((error: unknown) => {
|
|
255
|
+
callback(utils.ensureError(error))
|
|
255
256
|
})
|
|
256
257
|
}
|
|
257
258
|
}
|
|
@@ -12,6 +12,7 @@ import OpenAI from "openai"
|
|
|
12
12
|
|
|
13
13
|
/* internal dependencies */
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
15
|
+
import * as utils from "./speechflow-utils"
|
|
15
16
|
|
|
16
17
|
/* internal utility types */
|
|
17
18
|
type ConfigEntry = { systemPrompt: string, chat: OpenAI.ChatCompletionMessageParam[] }
|
|
@@ -219,8 +220,8 @@ export default class SpeechFlowNodeOpenAI extends SpeechFlowNode {
|
|
|
219
220
|
chunkNew.payload = payload
|
|
220
221
|
this.push(chunkNew)
|
|
221
222
|
callback()
|
|
222
|
-
}).catch((
|
|
223
|
-
callback(
|
|
223
|
+
}).catch((error: unknown) => {
|
|
224
|
+
callback(utils.ensureError(error))
|
|
224
225
|
})
|
|
225
226
|
}
|
|
226
227
|
},
|
|
@@ -18,6 +18,7 @@ import HAPIWebSocket from "hapi-plugin-websocket"
|
|
|
18
18
|
|
|
19
19
|
/* internal dependencies */
|
|
20
20
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
21
|
+
import * as utils from "./speechflow-utils"
|
|
21
22
|
|
|
22
23
|
type wsPeerCtx = {
|
|
23
24
|
peer: string
|
|
@@ -145,8 +146,8 @@ export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
|
|
|
145
146
|
chunkNew.payload = payload
|
|
146
147
|
this.push(chunkNew)
|
|
147
148
|
callback()
|
|
148
|
-
}).catch((
|
|
149
|
-
callback(
|
|
149
|
+
}).catch((error: unknown) => {
|
|
150
|
+
callback(utils.ensureError(error))
|
|
150
151
|
})
|
|
151
152
|
}
|
|
152
153
|
}
|
|
@@ -13,6 +13,7 @@ import * as Transformers from "@huggingface/transformers"
|
|
|
13
13
|
|
|
14
14
|
/* internal dependencies */
|
|
15
15
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
16
|
+
import * as utils from "./speechflow-utils"
|
|
16
17
|
|
|
17
18
|
/* internal utility types */
|
|
18
19
|
type ConfigEntry = { systemPrompt: string, chat: Array<{ role: string, content: string }> }
|
|
@@ -212,8 +213,8 @@ export default class SpeechFlowNodeTransformers extends SpeechFlowNode {
|
|
|
212
213
|
chunk.payload = payload
|
|
213
214
|
this.push(chunk)
|
|
214
215
|
callback()
|
|
215
|
-
}).catch((
|
|
216
|
-
callback(
|
|
216
|
+
}).catch((error: unknown) => {
|
|
217
|
+
callback(utils.ensureError(error))
|
|
217
218
|
})
|
|
218
219
|
}
|
|
219
220
|
},
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
/* standard dependencies */
|
|
8
8
|
import Stream from "node:stream"
|
|
9
9
|
import { EventEmitter } from "node:events"
|
|
10
|
+
import { type, type Type } from "arktype"
|
|
10
11
|
|
|
11
12
|
/* external dependencies */
|
|
12
13
|
import { DateTime, Duration } from "luxon"
|
|
@@ -16,6 +17,194 @@ import * as IntervalTree from "node-interval-tree"
|
|
|
16
17
|
/* internal dependencies */
|
|
17
18
|
import { SpeechFlowChunk } from "./speechflow-node"
|
|
18
19
|
|
|
20
|
+
/* helper function for retrieving an Error object */
|
|
21
|
+
export function ensureError (error: unknown, prefix?: string): Error {
|
|
22
|
+
if (error instanceof Error && prefix === undefined)
|
|
23
|
+
return error
|
|
24
|
+
let msg = error instanceof Error ?
|
|
25
|
+
error.message : String(error)
|
|
26
|
+
if (prefix)
|
|
27
|
+
msg = `${prefix}: ${msg}`
|
|
28
|
+
return new Error(msg, { cause: error })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* helper function for retrieving a Promise object */
|
|
32
|
+
export function ensurePromise<T> (arg: T | Promise<T>): Promise<T> {
|
|
33
|
+
if (!(arg instanceof Promise))
|
|
34
|
+
arg = Promise.resolve(arg)
|
|
35
|
+
return arg
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* helper function for running the finally code of "run" */
|
|
39
|
+
function runFinally (onfinally?: () => void) {
|
|
40
|
+
if (!onfinally)
|
|
41
|
+
return
|
|
42
|
+
try { onfinally() }
|
|
43
|
+
catch (_arg: unknown) { /* ignored */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* helper type for ensuring T contains no Promise */
|
|
47
|
+
type runNoPromise<T> =
|
|
48
|
+
[ T ] extends [ Promise<any> ] ? never : T
|
|
49
|
+
|
|
50
|
+
/* run a synchronous or asynchronous action */
|
|
51
|
+
export function run<T, X extends runNoPromise<T> | never> (
|
|
52
|
+
action: () => X,
|
|
53
|
+
oncatch?: (error: Error) => X | never,
|
|
54
|
+
onfinally?: () => void
|
|
55
|
+
): X
|
|
56
|
+
export function run<T, X extends runNoPromise<T> | never> (
|
|
57
|
+
description: string,
|
|
58
|
+
action: () => X,
|
|
59
|
+
oncatch?: (error: Error) => X | never,
|
|
60
|
+
onfinally?: () => void
|
|
61
|
+
): X
|
|
62
|
+
export function run<T, X extends (T | Promise<T>)> (
|
|
63
|
+
action: () => X,
|
|
64
|
+
oncatch?: (error: Error) => X,
|
|
65
|
+
onfinally?: () => void
|
|
66
|
+
): Promise<T>
|
|
67
|
+
export function run<T, X extends (T | Promise<T>)> (
|
|
68
|
+
description: string,
|
|
69
|
+
action: () => X,
|
|
70
|
+
oncatch?: (error: Error) => X,
|
|
71
|
+
onfinally?: () => void
|
|
72
|
+
): Promise<T>
|
|
73
|
+
export function run<T> (
|
|
74
|
+
...args: any[]
|
|
75
|
+
): T | Promise<T> | never {
|
|
76
|
+
/* support overloaded signatures */
|
|
77
|
+
let description: string | undefined
|
|
78
|
+
let action: () => T | Promise<T> | never
|
|
79
|
+
let oncatch: (error: Error) => T | Promise<T> | never
|
|
80
|
+
let onfinally: () => void
|
|
81
|
+
if (typeof args[0] === "string") {
|
|
82
|
+
description = args[0]
|
|
83
|
+
action = args[1]
|
|
84
|
+
oncatch = args[2]
|
|
85
|
+
onfinally = args[3]
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
action = args[0]
|
|
89
|
+
oncatch = args[1]
|
|
90
|
+
onfinally = args[2]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* perform the action */
|
|
94
|
+
let result: T | Promise<T>
|
|
95
|
+
try {
|
|
96
|
+
result = action()
|
|
97
|
+
}
|
|
98
|
+
catch (arg: unknown) {
|
|
99
|
+
/* synchronous case (error branch) */
|
|
100
|
+
let error = ensureError(arg, description)
|
|
101
|
+
if (oncatch) {
|
|
102
|
+
try {
|
|
103
|
+
result = oncatch(error)
|
|
104
|
+
}
|
|
105
|
+
catch (arg: unknown) {
|
|
106
|
+
error = ensureError(arg, description)
|
|
107
|
+
runFinally(onfinally)
|
|
108
|
+
throw error
|
|
109
|
+
}
|
|
110
|
+
runFinally(onfinally)
|
|
111
|
+
return result
|
|
112
|
+
}
|
|
113
|
+
runFinally(onfinally)
|
|
114
|
+
throw error
|
|
115
|
+
}
|
|
116
|
+
if (result instanceof Promise) {
|
|
117
|
+
/* asynchronous case (result or error branch) */
|
|
118
|
+
return result.catch((arg: unknown) => {
|
|
119
|
+
/* asynchronous case (error branch) */
|
|
120
|
+
let error = ensureError(arg, description)
|
|
121
|
+
if (oncatch) {
|
|
122
|
+
try {
|
|
123
|
+
return ensurePromise(oncatch(error))
|
|
124
|
+
}
|
|
125
|
+
catch (arg: unknown) {
|
|
126
|
+
error = ensureError(arg, description)
|
|
127
|
+
return Promise.reject(error)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return Promise.reject(error)
|
|
131
|
+
}).finally(() => {
|
|
132
|
+
/* asynchronous case (result and error branch) */
|
|
133
|
+
runFinally(onfinally)
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
/* synchronous case (result branch) */
|
|
138
|
+
runFinally(onfinally)
|
|
139
|
+
return result
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* run a synchronous or asynchronous action */
|
|
144
|
+
/* eslint @typescript-eslint/unified-signatures: off */
|
|
145
|
+
export function runner<T, X extends runNoPromise<T> | never, F extends (...args: any[]) => X> (
|
|
146
|
+
action: F,
|
|
147
|
+
oncatch?: (error: Error) => X | never,
|
|
148
|
+
onfinally?: () => void
|
|
149
|
+
): F
|
|
150
|
+
export function runner<T, X extends runNoPromise<T> | never, F extends (...args: any[]) => X> (
|
|
151
|
+
description: string,
|
|
152
|
+
action: F,
|
|
153
|
+
oncatch?: (error: Error) => X | never,
|
|
154
|
+
onfinally?: () => void
|
|
155
|
+
): F
|
|
156
|
+
export function runner<T, X extends (T | Promise<T>), F extends (...args: any[]) => Promise<T>> (
|
|
157
|
+
action: F,
|
|
158
|
+
oncatch?: (error: Error) => X,
|
|
159
|
+
onfinally?: () => void
|
|
160
|
+
): F
|
|
161
|
+
export function runner<T, X extends (T | Promise<T>), F extends (...args: any[]) => Promise<T>> (
|
|
162
|
+
description: string,
|
|
163
|
+
action: F,
|
|
164
|
+
oncatch?: (error: Error) => X,
|
|
165
|
+
onfinally?: () => void
|
|
166
|
+
): F
|
|
167
|
+
export function runner<T> (
|
|
168
|
+
...args: any[]
|
|
169
|
+
): (...args: any[]) => T | Promise<T> | never {
|
|
170
|
+
/* support overloaded signatures */
|
|
171
|
+
let description: string | undefined
|
|
172
|
+
let action: (...args: any[]) => T | Promise<T> | never
|
|
173
|
+
let oncatch: (error: Error) => T | Promise<T> | never
|
|
174
|
+
let onfinally: () => void
|
|
175
|
+
if (typeof args[0] === "string") {
|
|
176
|
+
description = args[0]
|
|
177
|
+
action = args[1]
|
|
178
|
+
oncatch = args[2]
|
|
179
|
+
onfinally = args[3]
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
action = args[0]
|
|
183
|
+
oncatch = args[1]
|
|
184
|
+
onfinally = args[2]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* wrap the "run" operation on "action" into function
|
|
188
|
+
which exposes the signature of "action" */
|
|
189
|
+
return (...args: any[]) => {
|
|
190
|
+
if (description)
|
|
191
|
+
return run(description, () => action(...args), oncatch, onfinally)
|
|
192
|
+
else
|
|
193
|
+
return run(() => action(...args), oncatch, onfinally)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* import an object with parsing and strict error handling */
|
|
198
|
+
export function importObject<T>(name: string, arg: object | string, validator: Type<T, {}>): T {
|
|
199
|
+
const obj: object = typeof arg === "string" ?
|
|
200
|
+
run(`${name}: parsing JSON`, () => JSON.parse(arg)) :
|
|
201
|
+
arg
|
|
202
|
+
const result = validator(obj)
|
|
203
|
+
if (result instanceof type.errors)
|
|
204
|
+
throw new Error(`${name}: validation: ${result.summary}`)
|
|
205
|
+
return result as T
|
|
206
|
+
}
|
|
207
|
+
|
|
19
208
|
/* calculate duration of an audio buffer */
|
|
20
209
|
export function audioBufferDuration (
|
|
21
210
|
buffer: Buffer,
|