speechflow 1.5.0 → 1.6.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/CHANGELOG.md +15 -0
- package/README.md +210 -167
- package/etc/claude.md +83 -46
- package/etc/speechflow.yaml +84 -84
- package/package.json +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +4 -4
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +4 -4
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +5 -15
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +4 -4
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +18 -0
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +312 -0
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js +7 -12
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +4 -4
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openai.d.ts +19 -0
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js +351 -0
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js +6 -6
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.d.ts +16 -0
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +204 -0
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js +40 -7
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +5 -5
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +41 -7
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +175 -0
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js +39 -5
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +6 -5
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.js +3 -3
- package/speechflow-cli/dst/speechflow-node-t2t-format.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-modify.d.ts +11 -0
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js +111 -0
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +39 -5
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js +39 -5
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +3 -3
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +6 -5
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +6 -5
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js +3 -3
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js +3 -3
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.js +3 -3
- package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-file.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-file.js +43 -22
- package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +3 -3
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js +3 -3
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/speechflow-cli/dst/speechflow-utils.d.ts +16 -0
- package/speechflow-cli/dst/speechflow-utils.js +140 -1
- package/speechflow-cli/dst/speechflow-utils.js.map +1 -1
- package/speechflow-cli/dst/speechflow.js +19 -19
- 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 +16 -15
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +4 -4
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +4 -4
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +4 -14
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +4 -4
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-mute.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-speex.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-vad.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-wav.ts +2 -2
- package/speechflow-cli/src/{speechflow-node-a2t-awstranscribe.ts → speechflow-node-a2t-amazon.ts} +11 -13
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +4 -4
- package/speechflow-cli/src/{speechflow-node-a2t-openaitranscribe.ts → speechflow-node-a2t-openai.ts} +7 -7
- package/speechflow-cli/src/{speechflow-node-t2a-awspolly.ts → speechflow-node-t2a-amazon.ts} +8 -8
- package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +4 -4
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +7 -6
- package/speechflow-cli/src/{speechflow-node-t2t-awstranslate.ts → speechflow-node-t2t-amazon.ts} +6 -5
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +5 -4
- package/speechflow-cli/src/speechflow-node-t2t-format.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-google.ts +133 -0
- package/speechflow-cli/src/speechflow-node-t2t-modify.ts +84 -0
- package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +5 -4
- package/speechflow-cli/src/speechflow-node-t2t-openai.ts +5 -4
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +2 -2
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +10 -9
- package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +5 -4
- package/speechflow-cli/src/speechflow-node-x2x-filter.ts +2 -2
- package/speechflow-cli/src/speechflow-node-x2x-trace.ts +2 -2
- package/speechflow-cli/src/speechflow-node-xio-device.ts +2 -2
- package/speechflow-cli/src/speechflow-node-xio-file.ts +43 -21
- package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +2 -2
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +2 -2
- package/speechflow-cli/src/speechflow-utils.ts +196 -1
- package/speechflow-cli/src/speechflow.ts +22 -22
- 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 +10 -10
- package/speechflow-ui-st/dst/app-font-fa-brands-400.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-regular-400.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-solid-900.woff2 +0 -0
- package/speechflow-ui-st/dst/app-font-fa-v4compatibility.woff2 +0 -0
- package/speechflow-ui-st/dst/index.css +2 -2
- package/speechflow-ui-st/dst/index.js +32 -33
- package/speechflow-ui-st/package.json +11 -11
|
@@ -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
|
|
@@ -29,9 +30,9 @@ type wsPeerInfo = {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/* SpeechFlow node for subtitle (text-to-text) "translations" */
|
|
32
|
-
export default class
|
|
33
|
+
export default class SpeechFlowNodeT2TSubtitle extends SpeechFlowNode {
|
|
33
34
|
/* declare official node name */
|
|
34
|
-
public static name = "subtitle"
|
|
35
|
+
public static name = "t2t-subtitle"
|
|
35
36
|
|
|
36
37
|
/* internal state */
|
|
37
38
|
private sequenceNo = 1
|
|
@@ -43,11 +44,11 @@ export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
|
|
|
43
44
|
|
|
44
45
|
/* declare node configuration parameters */
|
|
45
46
|
this.configure({
|
|
46
|
-
format: { type: "string",
|
|
47
|
-
words: { type: "boolean",
|
|
48
|
-
mode: { type: "string",
|
|
49
|
-
addr: { type: "string",
|
|
50
|
-
port: { type: "number",
|
|
47
|
+
format: { type: "string", pos: 0, val: "srt", match: /^(?:srt|vtt)$/ },
|
|
48
|
+
words: { type: "boolean", val: false },
|
|
49
|
+
mode: { type: "string", val: "export", match: /^(?:export|render)$/ },
|
|
50
|
+
addr: { type: "string", val: "127.0.0.1" },
|
|
51
|
+
port: { type: "number", val: 8585 }
|
|
51
52
|
})
|
|
52
53
|
|
|
53
54
|
/* declare node input/output format */
|
|
@@ -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,15 +13,16 @@ 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 }> }
|
|
19
20
|
type Config = { [ key: string ]: ConfigEntry }
|
|
20
21
|
|
|
21
22
|
/* SpeechFlow node for Transformers text-to-text translation */
|
|
22
|
-
export default class
|
|
23
|
+
export default class SpeechFlowNodeT2TTransformers extends SpeechFlowNode {
|
|
23
24
|
/* declare official node name */
|
|
24
|
-
public static name = "transformers"
|
|
25
|
+
public static name = "t2t-transformers"
|
|
25
26
|
|
|
26
27
|
/* internal state */
|
|
27
28
|
private translator: Transformers.TranslationPipeline | null = null
|
|
@@ -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
|
},
|
|
@@ -12,9 +12,9 @@ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
|
12
12
|
import * as utils from "./speechflow-utils"
|
|
13
13
|
|
|
14
14
|
/* SpeechFlow node for data flow filtering (based on meta information) */
|
|
15
|
-
export default class
|
|
15
|
+
export default class SpeechFlowNodeX2XFilter extends SpeechFlowNode {
|
|
16
16
|
/* declare official node name */
|
|
17
|
-
public static name = "filter"
|
|
17
|
+
public static name = "x2x-filter"
|
|
18
18
|
|
|
19
19
|
/* cached regular expression instance */
|
|
20
20
|
private cachedRegExp = new utils.CachedRegExp()
|
|
@@ -14,9 +14,9 @@ import { Duration } from "luxon"
|
|
|
14
14
|
import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
15
15
|
|
|
16
16
|
/* SpeechFlow node for data flow tracing */
|
|
17
|
-
export default class
|
|
17
|
+
export default class SpeechFlowNodeX2XTrace extends SpeechFlowNode {
|
|
18
18
|
/* declare official node name */
|
|
19
|
-
public static name = "trace"
|
|
19
|
+
public static name = "x2x-trace"
|
|
20
20
|
|
|
21
21
|
/* construct node */
|
|
22
22
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -15,9 +15,9 @@ import SpeechFlowNode from "./speechflow-node"
|
|
|
15
15
|
import * as utils from "./speechflow-utils"
|
|
16
16
|
|
|
17
17
|
/* SpeechFlow node for device access */
|
|
18
|
-
export default class
|
|
18
|
+
export default class SpeechFlowNodeXIODevice extends SpeechFlowNode {
|
|
19
19
|
/* declare official node name */
|
|
20
|
-
public static name = "device"
|
|
20
|
+
public static name = "xio-device"
|
|
21
21
|
|
|
22
22
|
/* internal state */
|
|
23
23
|
private io: PortAudio.IoStreamRead
|
|
@@ -13,9 +13,9 @@ import SpeechFlowNode from "./speechflow-node"
|
|
|
13
13
|
import * as utils from "./speechflow-utils"
|
|
14
14
|
|
|
15
15
|
/* SpeechFlow node for file access */
|
|
16
|
-
export default class
|
|
16
|
+
export default class SpeechFlowNodeXIOFile extends SpeechFlowNode {
|
|
17
17
|
/* declare official node name */
|
|
18
|
-
public static name = "file"
|
|
18
|
+
public static name = "xio-file"
|
|
19
19
|
|
|
20
20
|
/* construct node */
|
|
21
21
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -59,6 +59,28 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
|
|
|
59
59
|
if (this.params.path === "")
|
|
60
60
|
throw new Error("required parameter \"path\" has to be given")
|
|
61
61
|
|
|
62
|
+
/* utility function: create a writable stream as chunker that
|
|
63
|
+
writes to process.stdout but properly handles finish events.
|
|
64
|
+
This ensures the writable side of the composed stream below
|
|
65
|
+
properly signals completion while keeping process.stdout open
|
|
66
|
+
(as it's a global stream that shouldn't be closed by individual nodes). */
|
|
67
|
+
const createStdoutChunker = () => {
|
|
68
|
+
return new Stream.Writable({
|
|
69
|
+
highWaterMark: this.params.type === "audio" ?
|
|
70
|
+
highWaterMarkAudio : highWaterMarkText,
|
|
71
|
+
write (chunk: Buffer | string, encoding, callback) {
|
|
72
|
+
const canContinue = process.stdout.write(chunk, encoding)
|
|
73
|
+
if (canContinue)
|
|
74
|
+
callback()
|
|
75
|
+
else
|
|
76
|
+
process.stdout.once("drain", callback)
|
|
77
|
+
},
|
|
78
|
+
final (callback) {
|
|
79
|
+
callback()
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
62
84
|
/* dispatch according to mode and path */
|
|
63
85
|
if (this.params.mode === "rw") {
|
|
64
86
|
if (this.params.path === "-") {
|
|
@@ -145,17 +167,13 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
|
|
|
145
167
|
else if (this.params.mode === "w") {
|
|
146
168
|
if (this.params.path === "-") {
|
|
147
169
|
/* standard I/O */
|
|
148
|
-
|
|
149
|
-
if (this.params.type === "audio") {
|
|
170
|
+
if (this.params.type === "audio")
|
|
150
171
|
process.stdout.setEncoding()
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
172
|
+
else
|
|
154
173
|
process.stdout.setEncoding(this.config.textEncoding)
|
|
155
|
-
|
|
156
|
-
}
|
|
174
|
+
const chunker = createStdoutChunker()
|
|
157
175
|
const wrapper = utils.createTransformStreamForWritableSide()
|
|
158
|
-
this.stream = Stream.compose(wrapper, chunker
|
|
176
|
+
this.stream = Stream.compose(wrapper, chunker)
|
|
159
177
|
}
|
|
160
178
|
else {
|
|
161
179
|
/* file I/O */
|
|
@@ -178,18 +196,22 @@ export default class SpeechFlowNodeFile extends SpeechFlowNode {
|
|
|
178
196
|
async close () {
|
|
179
197
|
/* shutdown stream */
|
|
180
198
|
if (this.stream !== null) {
|
|
181
|
-
await
|
|
182
|
-
|
|
183
|
-
this.stream.
|
|
184
|
-
if (
|
|
185
|
-
reject(err)
|
|
186
|
-
else
|
|
199
|
+
await Promise.race([
|
|
200
|
+
new Promise<void>((resolve, reject) => {
|
|
201
|
+
if (this.stream instanceof Stream.Writable || this.stream instanceof Stream.Duplex) {
|
|
202
|
+
if (this.stream.writableEnded || this.stream.destroyed)
|
|
187
203
|
resolve()
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
204
|
+
else
|
|
205
|
+
this.stream.end((err?: Error) => {
|
|
206
|
+
if (err) reject(err)
|
|
207
|
+
else resolve()
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
else
|
|
211
|
+
resolve()
|
|
212
|
+
}),
|
|
213
|
+
new Promise<void>((resolve) => setTimeout(() => resolve(), 5000))
|
|
214
|
+
])
|
|
193
215
|
if (this.params.path !== "-")
|
|
194
216
|
this.stream.destroy()
|
|
195
217
|
this.stream = null
|
|
@@ -16,9 +16,9 @@ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
|
16
16
|
import * as utils from "./speechflow-utils"
|
|
17
17
|
|
|
18
18
|
/* SpeechFlow node for MQTT networking */
|
|
19
|
-
export default class
|
|
19
|
+
export default class SpeechFlowNodeXIOMQTT extends SpeechFlowNode {
|
|
20
20
|
/* declare official node name */
|
|
21
|
-
public static name = "mqtt"
|
|
21
|
+
public static name = "xio-mqtt"
|
|
22
22
|
|
|
23
23
|
/* internal state */
|
|
24
24
|
private broker: MQTT.MqttClient | null = null
|
|
@@ -16,9 +16,9 @@ import SpeechFlowNode, { SpeechFlowChunk } from "./speechflow-node"
|
|
|
16
16
|
import * as utils from "./speechflow-utils"
|
|
17
17
|
|
|
18
18
|
/* SpeechFlow node for Websocket networking */
|
|
19
|
-
export default class
|
|
19
|
+
export default class SpeechFlowNodeXIOWebSocket extends SpeechFlowNode {
|
|
20
20
|
/* declare official node name */
|
|
21
|
-
public static name = "websocket"
|
|
21
|
+
public static name = "xio-websocket"
|
|
22
22
|
|
|
23
23
|
/* internal state */
|
|
24
24
|
private server: ws.WebSocketServer | null = null
|
|
@@ -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 (_error: 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 oncatch(error)
|
|
124
|
+
}
|
|
125
|
+
catch (arg: unknown) {
|
|
126
|
+
error = ensureError(arg, description)
|
|
127
|
+
throw error
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
throw 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,
|
|
@@ -139,6 +328,11 @@ export function createTransformStreamForReadableSide (type: "text" | "audio", ge
|
|
|
139
328
|
decodeStrings: false,
|
|
140
329
|
highWaterMark: (type === "audio" ? 19200 : 65536),
|
|
141
330
|
transform (chunk: Buffer | string, encoding, callback) {
|
|
331
|
+
if (chunk === null) {
|
|
332
|
+
this.push(null)
|
|
333
|
+
callback()
|
|
334
|
+
return
|
|
335
|
+
}
|
|
142
336
|
const timeZero = getTimeZero()
|
|
143
337
|
const start = DateTime.now().diff(timeZero)
|
|
144
338
|
let end = start
|
|
@@ -146,7 +340,8 @@ export function createTransformStreamForReadableSide (type: "text" | "audio", ge
|
|
|
146
340
|
const duration = audioBufferDuration(chunk as Buffer)
|
|
147
341
|
end = start.plus(duration * 1000)
|
|
148
342
|
}
|
|
149
|
-
const
|
|
343
|
+
const payload = ensureStreamChunk(type, chunk) as Buffer | string
|
|
344
|
+
const obj = new SpeechFlowChunk(start, end, "final", type, payload)
|
|
150
345
|
this.push(obj)
|
|
151
346
|
callback()
|
|
152
347
|
},
|
|
@@ -34,6 +34,7 @@ import chalk from "chalk"
|
|
|
34
34
|
|
|
35
35
|
/* internal dependencies */
|
|
36
36
|
import SpeechFlowNode from "./speechflow-node"
|
|
37
|
+
import * as utils from "./speechflow-utils"
|
|
37
38
|
import pkg from "../../package.json"
|
|
38
39
|
|
|
39
40
|
/* central CLI context */
|
|
@@ -258,16 +259,7 @@ let debug = false
|
|
|
258
259
|
throw new Error("invalid configuration file specification (expected \"<id>@<yaml-config-file>\")")
|
|
259
260
|
const [ , id, file ] = m
|
|
260
261
|
const yaml = await cli.input(file, { encoding: "utf8" })
|
|
261
|
-
|
|
262
|
-
try {
|
|
263
|
-
obj = jsYAML.load(yaml)
|
|
264
|
-
}
|
|
265
|
-
catch (err) {
|
|
266
|
-
if (err instanceof Error)
|
|
267
|
-
throw new Error(`failed to parse YAML configuration: ${err.message}`)
|
|
268
|
-
else
|
|
269
|
-
throw new Error(`failed to parse YAML configuration: ${err}`)
|
|
270
|
-
}
|
|
262
|
+
const obj: any = utils.run("parsing YAML configuration", () => jsYAML.load(yaml))
|
|
271
263
|
if (obj[id] === undefined)
|
|
272
264
|
throw new Error(`no such id "${id}" found in configuration file "${file}"`)
|
|
273
265
|
config = obj[id] as string
|
|
@@ -290,15 +282,17 @@ let debug = false
|
|
|
290
282
|
"./speechflow-node-a2a-speex.js",
|
|
291
283
|
"./speechflow-node-a2a-vad.js",
|
|
292
284
|
"./speechflow-node-a2a-wav.js",
|
|
293
|
-
"./speechflow-node-a2t-
|
|
285
|
+
"./speechflow-node-a2t-amazon.js",
|
|
294
286
|
"./speechflow-node-a2t-deepgram.js",
|
|
295
|
-
"./speechflow-node-a2t-
|
|
296
|
-
"./speechflow-node-t2a-
|
|
287
|
+
"./speechflow-node-a2t-openai.js",
|
|
288
|
+
"./speechflow-node-t2a-amazon.js",
|
|
297
289
|
"./speechflow-node-t2a-elevenlabs.js",
|
|
298
290
|
"./speechflow-node-t2a-kokoro.js",
|
|
299
|
-
"./speechflow-node-t2t-
|
|
291
|
+
"./speechflow-node-t2t-amazon.js",
|
|
300
292
|
"./speechflow-node-t2t-deepl.js",
|
|
301
293
|
"./speechflow-node-t2t-format.js",
|
|
294
|
+
"./speechflow-node-t2t-google.js",
|
|
295
|
+
"./speechflow-node-t2t-modify.js",
|
|
302
296
|
"./speechflow-node-t2t-ollama.js",
|
|
303
297
|
"./speechflow-node-t2t-openai.js",
|
|
304
298
|
"./speechflow-node-t2t-sentence.js",
|
|
@@ -448,11 +442,15 @@ let debug = false
|
|
|
448
442
|
cli!.log("error", `creation of node <${id}> failed: ${err}`)
|
|
449
443
|
process.exit(1)
|
|
450
444
|
}
|
|
451
|
-
const params = Object.keys(node
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
445
|
+
const params = Object.keys(node!.params).map((key) => {
|
|
446
|
+
if (key.match(/key/))
|
|
447
|
+
return `${key}: [...]`
|
|
448
|
+
else
|
|
449
|
+
return `${key}: ${JSON.stringify(node.params[key])}`
|
|
450
|
+
}).join(", ")
|
|
451
|
+
cli!.log("info", `create node <${node!.id}> (${params})`)
|
|
452
|
+
graphNodes.add(node!)
|
|
453
|
+
return node!
|
|
456
454
|
},
|
|
457
455
|
connectNodes (node1: SpeechFlowNode, node2: SpeechFlowNode) {
|
|
458
456
|
cli!.log("info", `connect node <${node1.id}> to node <${node2.id}>`)
|
|
@@ -681,7 +679,7 @@ let debug = false
|
|
|
681
679
|
cli!.log("info", `HAPI: peer ${peer}: GET: ${JSON.stringify(req)}`)
|
|
682
680
|
return consumeExternalRequest(req)
|
|
683
681
|
.then(() => h.response({ response: "OK" }).code(200))
|
|
684
|
-
.catch((
|
|
682
|
+
.catch((error: unknown) => h.response({ response: "ERROR", data: utils.ensureError(error).message }).code(417))
|
|
685
683
|
}
|
|
686
684
|
})
|
|
687
685
|
hapi.route({
|
|
@@ -712,6 +710,8 @@ let debug = false
|
|
|
712
710
|
const peer = ctx.peer
|
|
713
711
|
wsPeers.delete(peer)
|
|
714
712
|
ws.removeAllListeners()
|
|
713
|
+
if (ws.readyState === WebSocket.OPEN)
|
|
714
|
+
ws.close()
|
|
715
715
|
cli!.log("info", `HAPI: WebSocket: disconnect: peer ${peer}`)
|
|
716
716
|
}
|
|
717
717
|
}
|
|
@@ -834,8 +834,8 @@ let debug = false
|
|
|
834
834
|
Promise.all(closePromises),
|
|
835
835
|
new Promise((resolve, reject) =>
|
|
836
836
|
setTimeout(() => reject(new Error("timeout for all peers")), 5 * 1000))
|
|
837
|
-
]).catch((
|
|
838
|
-
cli!.log("warning", `HAPI: WebSockets failed to close: ${
|
|
837
|
+
]).catch((error: unknown) => {
|
|
838
|
+
cli!.log("warning", `HAPI: WebSockets failed to close: ${utils.ensureError(error).message}`)
|
|
839
839
|
})
|
|
840
840
|
wsPeers.clear()
|
|
841
841
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|