speechflow 2.2.0 → 2.3.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/{etc/claude.md → AGENTS.md} +8 -3
- package/CHANGELOG.md +78 -1
- package/README.md +28 -4
- package/etc/speechflow.yaml +3 -1
- package/etc/stx.conf +1 -1
- package/package.json +6 -6
- package/speechflow-cli/dst/speechflow-main-api.d.ts +2 -1
- package/speechflow-cli/dst/speechflow-main-api.js +57 -16
- package/speechflow-cli/dst/speechflow-main-api.js.map +1 -1
- package/speechflow-cli/dst/speechflow-main-cli.js +2 -2
- package/speechflow-cli/dst/speechflow-main-config.js +1 -1
- package/speechflow-cli/dst/speechflow-main-graph.js +55 -21
- package/speechflow-cli/dst/speechflow-main-graph.js.map +1 -1
- package/speechflow-cli/dst/speechflow-main-nodes.js +1 -1
- package/speechflow-cli/dst/speechflow-main-status.js +6 -3
- package/speechflow-cli/dst/speechflow-main-status.js.map +1 -1
- package/speechflow-cli/dst/speechflow-main.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +7 -10
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +8 -6
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +9 -5
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +6 -5
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +2 -2
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +2 -4
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +20 -12
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gtcrn-wt.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gtcrn.js +33 -11
- package/speechflow-cli/dst/speechflow-node-a2a-gtcrn.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +4 -3
- package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js +2 -2
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +19 -11
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js +8 -8
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js +33 -29
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js +6 -5
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.d.ts +2 -1
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js +34 -20
- package/speechflow-cli/dst/speechflow-node-a2t-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +13 -5
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-google.js +3 -2
- package/speechflow-cli/dst/speechflow-node-a2t-google.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js +33 -27
- package/speechflow-cli/dst/speechflow-node-a2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js +16 -5
- package/speechflow-cli/dst/speechflow-node-t2a-amazon.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +17 -5
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-google.js +17 -5
- package/speechflow-cli/dst/speechflow-node-t2a-google.js.map +1 -1
- package/speechflow-cli/dst/{speechflow-node-t2t-simulator.d.ts → speechflow-node-t2a-kitten.d.ts} +5 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kitten.js +194 -0
- package/speechflow-cli/dst/speechflow-node-t2a-kitten.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +21 -9
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-openai.js +17 -5
- package/speechflow-cli/dst/speechflow-node-t2a-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-supertonic.js +21 -7
- package/speechflow-cli/dst/speechflow-node-t2a-supertonic.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-amazon.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-google.js +4 -2
- package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-modify.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-opus.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-profanity.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-punctuation.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-spellcheck.js +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +34 -14
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-summary.js +3 -3
- package/speechflow-cli/dst/speechflow-node-t2t-summary.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-translate.js +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js +3 -2
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.js +18 -7
- package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-exec.js +23 -11
- package/speechflow-cli/dst/speechflow-node-xio-exec.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-file.js +13 -7
- package/speechflow-cli/dst/speechflow-node-xio-file.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +25 -12
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-vban.js +32 -20
- package/speechflow-cli/dst/speechflow-node-xio-vban.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-webrtc.js +78 -62
- package/speechflow-cli/dst/speechflow-node-xio-webrtc.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js +63 -18
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node.js +5 -7
- package/speechflow-cli/dst/speechflow-node.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-audio-wt.js +31 -5
- package/speechflow-cli/dst/speechflow-util-audio-wt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-audio.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-util-audio.js +25 -14
- package/speechflow-cli/dst/speechflow-util-audio.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-error.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-util-error.js +2 -2
- package/speechflow-cli/dst/speechflow-util-error.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-llm.js +1 -1
- package/speechflow-cli/dst/speechflow-util-misc.d.ts +3 -2
- package/speechflow-cli/dst/speechflow-util-misc.js +63 -6
- package/speechflow-cli/dst/speechflow-util-misc.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-queue.d.ts +5 -17
- package/speechflow-cli/dst/speechflow-util-queue.js +57 -78
- package/speechflow-cli/dst/speechflow-util-queue.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util-stream.d.ts +1 -1
- package/speechflow-cli/dst/speechflow-util-stream.js +35 -8
- package/speechflow-cli/dst/speechflow-util-stream.js.map +1 -1
- package/speechflow-cli/dst/speechflow-util.js +1 -1
- package/speechflow-cli/dst/speechflow.d.ts +1 -1
- package/speechflow-cli/dst/speechflow.js +1 -1
- package/speechflow-cli/etc/eslint.mjs +1 -1
- package/speechflow-cli/etc/oxlint.jsonc +2 -1
- package/speechflow-cli/etc/stx.conf +8 -2
- package/speechflow-cli/package.d/@ericedouard+vad-node-realtime+0.2.0.patch +2 -1
- package/speechflow-cli/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
- package/speechflow-cli/package.d/kitten-tts-js+0.1.2.patch +24 -0
- package/speechflow-cli/package.d/speex-resampler+3.0.1.patch +56 -0
- package/speechflow-cli/package.json +40 -30
- package/speechflow-cli/src/lib.d.ts +19 -1
- package/speechflow-cli/src/speechflow-main-api.ts +64 -19
- package/speechflow-cli/src/speechflow-main-cli.ts +2 -2
- package/speechflow-cli/src/speechflow-main-config.ts +1 -1
- package/speechflow-cli/src/speechflow-main-graph.ts +56 -22
- package/speechflow-cli/src/speechflow-main-nodes.ts +1 -1
- package/speechflow-cli/src/speechflow-main-status.ts +6 -3
- package/speechflow-cli/src/speechflow-main.ts +1 -1
- package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +7 -11
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +8 -6
- package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +10 -5
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +6 -5
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +3 -2
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +2 -4
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +1 -1
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +20 -13
- package/speechflow-cli/src/speechflow-node-a2a-gtcrn-wt.ts +1 -1
- package/speechflow-cli/src/speechflow-node-a2a-gtcrn.ts +43 -16
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +1 -1
- package/speechflow-cli/src/speechflow-node-a2a-mute.ts +1 -1
- package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +4 -3
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise-wt.ts +2 -2
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +24 -12
- package/speechflow-cli/src/speechflow-node-a2a-speex.ts +10 -9
- package/speechflow-cli/src/speechflow-node-a2a-vad.ts +38 -31
- package/speechflow-cli/src/speechflow-node-a2a-wav.ts +6 -5
- package/speechflow-cli/src/speechflow-node-a2t-amazon.ts +35 -22
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +17 -6
- package/speechflow-cli/src/speechflow-node-a2t-google.ts +5 -4
- package/speechflow-cli/src/speechflow-node-a2t-openai.ts +39 -31
- package/speechflow-cli/src/speechflow-node-t2a-amazon.ts +16 -5
- package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +17 -5
- package/speechflow-cli/src/speechflow-node-t2a-google.ts +17 -5
- package/speechflow-cli/src/speechflow-node-t2a-kitten.ts +178 -0
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +21 -9
- package/speechflow-cli/src/speechflow-node-t2a-openai.ts +17 -5
- package/speechflow-cli/src/speechflow-node-t2a-supertonic.ts +21 -7
- package/speechflow-cli/src/speechflow-node-t2t-amazon.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-format.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-google.ts +4 -2
- package/speechflow-cli/src/speechflow-node-t2t-modify.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-opus.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-profanity.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-punctuation.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-spellcheck.ts +1 -1
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +39 -15
- package/speechflow-cli/src/speechflow-node-t2t-summary.ts +3 -3
- package/speechflow-cli/src/speechflow-node-t2t-translate.ts +1 -1
- package/speechflow-cli/src/speechflow-node-x2x-filter.ts +4 -3
- package/speechflow-cli/src/speechflow-node-x2x-trace.ts +1 -1
- package/speechflow-cli/src/speechflow-node-xio-device.ts +21 -7
- package/speechflow-cli/src/speechflow-node-xio-exec.ts +25 -11
- package/speechflow-cli/src/speechflow-node-xio-file.ts +15 -7
- package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +28 -15
- package/speechflow-cli/src/speechflow-node-xio-vban.ts +35 -22
- package/speechflow-cli/src/speechflow-node-xio-webrtc.ts +85 -69
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +67 -20
- package/speechflow-cli/src/speechflow-node.ts +7 -8
- package/speechflow-cli/src/speechflow-util-audio-wt.ts +46 -7
- package/speechflow-cli/src/speechflow-util-audio.ts +27 -15
- package/speechflow-cli/src/speechflow-util-error.ts +3 -3
- package/speechflow-cli/src/speechflow-util-llm.ts +1 -1
- package/speechflow-cli/src/speechflow-util-misc.ts +63 -6
- package/speechflow-cli/src/speechflow-util-queue.ts +60 -81
- package/speechflow-cli/src/speechflow-util-stream.ts +40 -8
- package/speechflow-cli/src/speechflow-util.ts +1 -1
- package/speechflow-cli/src/speechflow.ts +1 -1
- package/speechflow-ui-db/dst/index.css +1 -1
- package/speechflow-ui-db/dst/index.html +1 -1
- package/speechflow-ui-db/dst/index.js +15 -15
- package/speechflow-ui-db/etc/eslint.mjs +1 -1
- package/speechflow-ui-db/etc/oxlint.jsonc +1 -1
- package/speechflow-ui-db/etc/stx.conf +1 -1
- package/speechflow-ui-db/etc/stylelint.js +1 -1
- package/speechflow-ui-db/etc/stylelint.yaml +1 -1
- package/speechflow-ui-db/etc/vite-client.mts +1 -1
- package/speechflow-ui-db/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
- package/speechflow-ui-db/package.json +22 -16
- package/speechflow-ui-db/src/app.styl +1 -1
- package/speechflow-ui-db/src/app.vue +4 -4
- package/speechflow-ui-db/src/index.html +1 -1
- package/speechflow-ui-db/src/index.ts +1 -1
- package/speechflow-ui-st/dst/index.html +1 -1
- package/speechflow-ui-st/dst/index.js +31 -31
- package/speechflow-ui-st/etc/eslint.mjs +1 -1
- package/speechflow-ui-st/etc/oxlint.jsonc +1 -1
- package/speechflow-ui-st/etc/stx.conf +1 -1
- package/speechflow-ui-st/etc/stylelint.js +1 -1
- package/speechflow-ui-st/etc/stylelint.yaml +1 -1
- package/speechflow-ui-st/etc/vite-client.mts +1 -1
- package/speechflow-ui-st/package.d/@typescript-eslint+typescript-estree+8.57.2.patch +12 -0
- package/speechflow-ui-st/package.json +23 -17
- package/speechflow-ui-st/src/app.styl +1 -1
- package/speechflow-ui-st/src/app.vue +9 -8
- package/speechflow-ui-st/src/index.html +1 -1
- package/speechflow-ui-st/src/index.ts +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-simulator.js +0 -128
- package/speechflow-cli/dst/speechflow-node-t2t-simulator.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -46,7 +46,7 @@ parentPort!.on("message", (msg) => {
|
|
|
46
46
|
/* convert back Float32Array to Int16Array */
|
|
47
47
|
const i16 = new Int16Array(data.length)
|
|
48
48
|
for (let i = 0; i < data.length; i++)
|
|
49
|
-
i16[i] = Math.round(f32a[i])
|
|
49
|
+
i16[i] = Math.max(-32768, Math.min(32767, Math.round(f32a[i])))
|
|
50
50
|
|
|
51
51
|
/* send processed frame back to parent */
|
|
52
52
|
parentPort!.postMessage({ type: "process-done", id, data: i16 }, [ i16.buffer ])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -71,14 +71,27 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
|
71
71
|
})
|
|
72
72
|
})
|
|
73
73
|
|
|
74
|
+
/* track pending promises */
|
|
75
|
+
const pending = new Map<string, {
|
|
76
|
+
resolve: (arr: Int16Array<ArrayBuffer>) => void,
|
|
77
|
+
reject: (err: Error) => void
|
|
78
|
+
}>()
|
|
79
|
+
|
|
80
|
+
/* reject all pending promises on worker exit */
|
|
81
|
+
this.worker.on("exit", () => {
|
|
82
|
+
const err = new Error("worker terminated")
|
|
83
|
+
for (const cb of pending.values())
|
|
84
|
+
cb.reject(err)
|
|
85
|
+
pending.clear()
|
|
86
|
+
})
|
|
87
|
+
|
|
74
88
|
/* receive message from worker */
|
|
75
|
-
const pending = new Map<string, (arr: Int16Array<ArrayBuffer>) => void>()
|
|
76
89
|
this.worker.on("message", (msg: any) => {
|
|
77
90
|
if (typeof msg === "object" && msg !== null && msg.type === "process-done") {
|
|
78
91
|
const cb = pending.get(msg.id)
|
|
79
92
|
pending.delete(msg.id)
|
|
80
93
|
if (cb)
|
|
81
|
-
cb(msg.data)
|
|
94
|
+
cb.resolve(msg.data)
|
|
82
95
|
else
|
|
83
96
|
this.log("warning", `RNNoise worker thread sent back unexpected id: ${msg.id}`)
|
|
84
97
|
}
|
|
@@ -92,8 +105,8 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
|
92
105
|
if (this.closing)
|
|
93
106
|
return segment
|
|
94
107
|
const id = `${seq++}`
|
|
95
|
-
return new Promise<Int16Array<ArrayBuffer>>((resolve) => {
|
|
96
|
-
pending.set(id,
|
|
108
|
+
return new Promise<Int16Array<ArrayBuffer>>((resolve, reject) => {
|
|
109
|
+
pending.set(id, { resolve, reject })
|
|
97
110
|
this.worker!.postMessage({ type: "process", id, data: segment }, [ segment.buffer ])
|
|
98
111
|
})
|
|
99
112
|
}
|
|
@@ -113,20 +126,19 @@ export default class SpeechFlowNodeA2ARNNoise extends SpeechFlowNode {
|
|
|
113
126
|
callback(new Error("invalid chunk payload type"))
|
|
114
127
|
else {
|
|
115
128
|
/* convert Buffer into Int16Array */
|
|
116
|
-
const payload = util.convertBufToI16(chunk.payload)
|
|
129
|
+
const payload = util.convertBufToI16(chunk.payload, self.config.audioLittleEndian)
|
|
117
130
|
|
|
118
131
|
/* process Int16Array in necessary segments */
|
|
119
132
|
util.processInt16ArrayInSegments(payload, self.sampleSize, (segment) =>
|
|
120
133
|
workerProcessSegment(segment)
|
|
121
134
|
).then((payload: Int16Array<ArrayBuffer>) => {
|
|
122
135
|
/* convert Int16Array into Buffer */
|
|
123
|
-
const buf = util.convertI16ToBuf(payload)
|
|
124
|
-
|
|
125
|
-
/* update chunk */
|
|
126
|
-
chunk.payload = buf
|
|
136
|
+
const buf = util.convertI16ToBuf(payload, self.config.audioLittleEndian)
|
|
127
137
|
|
|
128
|
-
/* forward updated
|
|
129
|
-
|
|
138
|
+
/* forward cloned chunk with updated payload */
|
|
139
|
+
const chunkNew = chunk.clone()
|
|
140
|
+
chunkNew.payload = buf
|
|
141
|
+
this.push(chunkNew)
|
|
130
142
|
callback()
|
|
131
143
|
}).catch((err: unknown) => {
|
|
132
144
|
const error = util.ensureError(err)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -53,7 +53,9 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
53
53
|
const wasmBinary = await fs.promises.readFile(
|
|
54
54
|
path.join(__dirname, "../node_modules/@sapphi-red/speex-preprocess-wasm/dist/speex.wasm"))
|
|
55
55
|
const speexModule = await loadSpeexModule({
|
|
56
|
-
wasmBinary: wasmBinary.buffer
|
|
56
|
+
wasmBinary: wasmBinary.buffer.slice(
|
|
57
|
+
wasmBinary.byteOffset,
|
|
58
|
+
wasmBinary.byteOffset + wasmBinary.byteLength)
|
|
57
59
|
})
|
|
58
60
|
this.speexProcessor = new SpeexPreprocessor(
|
|
59
61
|
speexModule, this.sampleSize, this.config.audioSampleRate)
|
|
@@ -79,7 +81,7 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
79
81
|
callback(new Error("invalid chunk payload type"))
|
|
80
82
|
else {
|
|
81
83
|
/* convert Buffer into Int16Array */
|
|
82
|
-
const payload = util.convertBufToI16(chunk.payload)
|
|
84
|
+
const payload = util.convertBufToI16(chunk.payload, self.config.audioLittleEndian)
|
|
83
85
|
|
|
84
86
|
/* process Int16Array in necessary fixed-size segments */
|
|
85
87
|
util.processInt16ArrayInSegments(payload, self.sampleSize, (segment) => {
|
|
@@ -94,13 +96,12 @@ export default class SpeechFlowNodeA2ASpeex extends SpeechFlowNode {
|
|
|
94
96
|
throw new Error("stream already destroyed")
|
|
95
97
|
|
|
96
98
|
/* convert Int16Array back into Buffer */
|
|
97
|
-
const buf = util.convertI16ToBuf(payload)
|
|
99
|
+
const buf = util.convertI16ToBuf(payload, self.config.audioLittleEndian)
|
|
98
100
|
|
|
99
|
-
/*
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.push(chunk)
|
|
101
|
+
/* forward cloned chunk with updated payload */
|
|
102
|
+
const chunkNew = chunk.clone()
|
|
103
|
+
chunkNew.payload = buf
|
|
104
|
+
this.push(chunkNew)
|
|
104
105
|
callback()
|
|
105
106
|
}).catch((err: unknown) => {
|
|
106
107
|
const error = util.ensureError(err)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -258,6 +258,9 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
258
258
|
return
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
/* await forthcoming audio chunks (forward declaration) */
|
|
262
|
+
let awaitForthcomingChunks: () => void = () => {}
|
|
263
|
+
|
|
261
264
|
/* flush pending audio chunks */
|
|
262
265
|
const flushPendingChunks = () => {
|
|
263
266
|
let pushed = 0
|
|
@@ -289,22 +292,22 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
289
292
|
this.push(chunk)
|
|
290
293
|
pushed++
|
|
291
294
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* in unplugged mode, if no chunk was pushed (all were
|
|
298
|
+
non-speech), we need to wait event-driven for new
|
|
299
|
+
data, as the stream won't call read() again until
|
|
300
|
+
we push something */
|
|
301
|
+
if (pushed === 0
|
|
302
|
+
&& !self.closing
|
|
303
|
+
&& !self.activeEventListeners.has(awaitForthcomingChunks)) {
|
|
304
|
+
self.queue.once("write", awaitForthcomingChunks)
|
|
305
|
+
self.activeEventListeners.add(awaitForthcomingChunks)
|
|
303
306
|
}
|
|
304
307
|
}
|
|
305
308
|
|
|
306
309
|
/* await forthcoming audio chunks */
|
|
307
|
-
|
|
310
|
+
awaitForthcomingChunks = () => {
|
|
308
311
|
self.activeEventListeners.delete(awaitForthcomingChunks)
|
|
309
312
|
if (self.closing)
|
|
310
313
|
return
|
|
@@ -339,16 +342,28 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
339
342
|
|
|
340
343
|
/* close node */
|
|
341
344
|
async close () {
|
|
342
|
-
/* indicate closing */
|
|
343
|
-
this.closing = true
|
|
344
|
-
|
|
345
345
|
/* cleanup tail timer */
|
|
346
346
|
if (this.tailTimer !== null) {
|
|
347
347
|
clearTimeout(this.tailTimer)
|
|
348
348
|
this.tailTimer = null
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
/*
|
|
351
|
+
/* flush VAD (before closing, as flush triggers callbacks which need active state) */
|
|
352
|
+
if (this.vad !== null) {
|
|
353
|
+
try {
|
|
354
|
+
const flushPromise = this.vad.flush()
|
|
355
|
+
const timeoutPromise = new Promise((resolve) => { setTimeout(resolve, 5000) })
|
|
356
|
+
await Promise.race([ flushPromise, timeoutPromise ])
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
this.log("warning", `VAD flush error during close: ${error}`)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* indicate closing */
|
|
364
|
+
this.closing = true
|
|
365
|
+
|
|
366
|
+
/* remove all remaining event listeners */
|
|
352
367
|
this.activeEventListeners.forEach((listener) => {
|
|
353
368
|
this.queue.removeListener("write", listener)
|
|
354
369
|
})
|
|
@@ -360,23 +375,15 @@ export default class SpeechFlowNodeA2AVAD extends SpeechFlowNode {
|
|
|
360
375
|
this.stream = null
|
|
361
376
|
}
|
|
362
377
|
|
|
363
|
-
/*
|
|
364
|
-
this.queue.pointerDelete("recv")
|
|
365
|
-
this.queue.pointerDelete("vad")
|
|
366
|
-
this.queue.pointerDelete("send")
|
|
367
|
-
|
|
368
|
-
/* close VAD */
|
|
378
|
+
/* destroy VAD */
|
|
369
379
|
if (this.vad !== null) {
|
|
370
|
-
try {
|
|
371
|
-
const flushPromise = this.vad.flush()
|
|
372
|
-
const timeoutPromise = new Promise((resolve) => { setTimeout(resolve, 5000) })
|
|
373
|
-
await Promise.race([ flushPromise, timeoutPromise ])
|
|
374
|
-
}
|
|
375
|
-
catch (error) {
|
|
376
|
-
this.log("warning", `VAD flush error during close: ${error}`)
|
|
377
|
-
}
|
|
378
380
|
this.vad.destroy()
|
|
379
381
|
this.vad = null
|
|
380
382
|
}
|
|
383
|
+
|
|
384
|
+
/* cleanup queue pointers */
|
|
385
|
+
this.queue.pointerDelete("recv")
|
|
386
|
+
this.queue.pointerDelete("vad")
|
|
387
|
+
this.queue.pointerDelete("send")
|
|
381
388
|
}
|
|
382
389
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -183,9 +183,10 @@ export default class SpeechFlowNodeA2AWAV extends SpeechFlowNode {
|
|
|
183
183
|
callback(new Error(`WAV not based on ${self.config.audioChannels} channel(s)`))
|
|
184
184
|
return
|
|
185
185
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
const chunkNew = chunk.clone()
|
|
187
|
+
chunkNew.payload = chunk.payload.subarray(44)
|
|
188
|
+
this.push(chunkNew)
|
|
189
|
+
totalSize += chunkNew.payload.byteLength
|
|
189
190
|
callback()
|
|
190
191
|
}
|
|
191
192
|
else {
|
|
@@ -210,7 +211,7 @@ export default class SpeechFlowNodeA2AWAV extends SpeechFlowNode {
|
|
|
210
211
|
sampleRate: self.config.audioSampleRate,
|
|
211
212
|
bitDepth: self.config.audioBitDepth
|
|
212
213
|
})
|
|
213
|
-
const headerChunk = headerChunkSent
|
|
214
|
+
const headerChunk = headerChunkSent.clone()
|
|
214
215
|
headerChunk.payload = headerBuffer
|
|
215
216
|
headerChunk.meta.set("chunk:seek", 0)
|
|
216
217
|
this.push(headerChunk)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -42,6 +42,7 @@ class AsyncQueue<T> {
|
|
|
42
42
|
resolve?.({ value: null, done: true })
|
|
43
43
|
}
|
|
44
44
|
this.queue.length = 0
|
|
45
|
+
this.queue.push(null)
|
|
45
46
|
}
|
|
46
47
|
async * [Symbol.asyncIterator] (): AsyncIterator<T> {
|
|
47
48
|
while (true) {
|
|
@@ -71,8 +72,9 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
71
72
|
private client: TranscribeStreamingClient | null = null
|
|
72
73
|
private clientStream: AsyncIterable<TranscriptResultStream> | null = null
|
|
73
74
|
private audioQueue: AsyncQueue<Uint8Array> | null = null
|
|
75
|
+
private queue: util.AsyncQueue<SpeechFlowChunk | null> | null = null
|
|
76
|
+
private clientStreamStarting = false
|
|
74
77
|
private closing = false
|
|
75
|
-
private queue: util.SingleQueue<SpeechFlowChunk | null> | null = null
|
|
76
78
|
|
|
77
79
|
/* construct node */
|
|
78
80
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -110,10 +112,11 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
110
112
|
throw new Error("Amazon Transcribe node currently supports PCM-S16LE audio only")
|
|
111
113
|
|
|
112
114
|
/* clear destruction flag */
|
|
113
|
-
this.closing
|
|
115
|
+
this.closing = false
|
|
116
|
+
this.clientStreamStarting = false
|
|
114
117
|
|
|
115
118
|
/* create queue for results */
|
|
116
|
-
this.queue = new util.
|
|
119
|
+
this.queue = new util.AsyncQueue<SpeechFlowChunk | null>()
|
|
117
120
|
|
|
118
121
|
/* create a store for the meta information */
|
|
119
122
|
const metastore = new util.TimeStore<Map<string, any>>()
|
|
@@ -138,24 +141,31 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
138
141
|
|
|
139
142
|
/* start streaming */
|
|
140
143
|
const ensureAudioStreamActive = async () => {
|
|
141
|
-
if (this.clientStream !== null || this.closing)
|
|
144
|
+
if (this.clientStream !== null || this.clientStreamStarting || this.closing)
|
|
142
145
|
return
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
LanguageCode:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
146
|
+
this.clientStreamStarting = true
|
|
147
|
+
try {
|
|
148
|
+
const language: LanguageCode = this.params.language === "de" ? "de-DE" : "en-US"
|
|
149
|
+
const command = new StartStreamTranscriptionCommand({
|
|
150
|
+
LanguageCode: language,
|
|
151
|
+
EnablePartialResultsStabilization: this.params.interim,
|
|
152
|
+
...(this.params.interim ? { PartialResultsStability: "low" } : {}),
|
|
153
|
+
MediaEncoding: "pcm",
|
|
154
|
+
MediaSampleRateHertz: this.config.audioSampleRate,
|
|
155
|
+
AudioStream: audioStream,
|
|
156
|
+
})
|
|
157
|
+
const response = await this.client!.send(command)
|
|
158
|
+
const stream = response.TranscriptResultStream
|
|
159
|
+
if (!stream)
|
|
160
|
+
throw new Error("no TranscriptResultStream returned")
|
|
161
|
+
this.clientStream = stream
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
this.clientStreamStarting = false
|
|
165
|
+
throw err
|
|
166
|
+
}
|
|
157
167
|
;(async () => {
|
|
158
|
-
for await (const event of
|
|
168
|
+
for await (const event of this.clientStream!) {
|
|
159
169
|
const te = event.TranscriptEvent
|
|
160
170
|
if (!te?.Transcript?.Results)
|
|
161
171
|
continue
|
|
@@ -194,6 +204,8 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
194
204
|
}
|
|
195
205
|
})().catch((err: unknown) => {
|
|
196
206
|
this.log("warning", `failed to establish connectivity to Amazon Transcribe: ${util.ensureError(err).message}`)
|
|
207
|
+
this.clientStream = null
|
|
208
|
+
this.clientStreamStarting = false
|
|
197
209
|
})
|
|
198
210
|
}
|
|
199
211
|
|
|
@@ -259,7 +271,7 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
259
271
|
this.push(null)
|
|
260
272
|
}
|
|
261
273
|
else {
|
|
262
|
-
self.log("debug", `received data (${chunk.payload.length} bytes)
|
|
274
|
+
self.log("debug", `received data (${chunk.payload.length} bytes)`)
|
|
263
275
|
this.push(chunk)
|
|
264
276
|
}
|
|
265
277
|
}).catch((error: unknown) => {
|
|
@@ -273,7 +285,8 @@ export default class SpeechFlowNodeA2TAmazon extends SpeechFlowNode {
|
|
|
273
285
|
/* close node */
|
|
274
286
|
async close () {
|
|
275
287
|
/* indicate closing first to stop all async operations */
|
|
276
|
-
this.closing
|
|
288
|
+
this.closing = true
|
|
289
|
+
this.clientStreamStarting = false
|
|
277
290
|
|
|
278
291
|
/* shutdown stream */
|
|
279
292
|
if (this.stream !== null) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -24,7 +24,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
24
24
|
private dg: Deepgram.LiveClient | null = null
|
|
25
25
|
private closing = false
|
|
26
26
|
private connectionTimeout: ReturnType<typeof setTimeout> | null = null
|
|
27
|
-
private queue: util.
|
|
27
|
+
private queue: util.AsyncQueue<SpeechFlowChunk | null> | null = null
|
|
28
28
|
|
|
29
29
|
/* construct node */
|
|
30
30
|
constructor (id: string, cfg: { [ id: string ]: any }, opts: { [ id: string ]: any }, args: any[]) {
|
|
@@ -64,7 +64,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
64
64
|
balance += balanceResponse.result.balances[0]?.amount ?? 0
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
else if (response
|
|
67
|
+
else if (response !== null && response.error !== null)
|
|
68
68
|
this.log("warning", `API error fetching projects: ${response.error}`)
|
|
69
69
|
}
|
|
70
70
|
catch (error) {
|
|
@@ -83,7 +83,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
83
83
|
this.closing = false
|
|
84
84
|
|
|
85
85
|
/* create queue for results */
|
|
86
|
-
this.queue = new util.
|
|
86
|
+
this.queue = new util.AsyncQueue<SpeechFlowChunk | null>()
|
|
87
87
|
|
|
88
88
|
/* create a store for the meta information */
|
|
89
89
|
const metastore = new util.TimeStore<Map<string, any>>()
|
|
@@ -145,7 +145,7 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
145
145
|
{ word: string, punctuated_word?: string, start: number, end: number }[]
|
|
146
146
|
const isFinal = (data.is_final as boolean) ?? false
|
|
147
147
|
const speechFinal = (data.speech_final as boolean) ?? false
|
|
148
|
-
const kind = (
|
|
148
|
+
const kind = (isFinal || (endpointing > 0 && speechFinal)) ? "final" : "intermediate"
|
|
149
149
|
if (text === "")
|
|
150
150
|
this.log("info", `empty/dummy text received (start: ${data.start}s, duration: ${data.duration.toFixed(2)}s)`)
|
|
151
151
|
else {
|
|
@@ -206,6 +206,13 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
206
206
|
}
|
|
207
207
|
resolve(true)
|
|
208
208
|
})
|
|
209
|
+
this.dg!.once(Deepgram.LiveTranscriptionEvents.Error, (err: Error) => {
|
|
210
|
+
if (this.connectionTimeout !== null) {
|
|
211
|
+
clearTimeout(this.connectionTimeout)
|
|
212
|
+
this.connectionTimeout = null
|
|
213
|
+
}
|
|
214
|
+
reject(err)
|
|
215
|
+
})
|
|
209
216
|
})
|
|
210
217
|
|
|
211
218
|
/* remember opening time to receive time zero offset */
|
|
@@ -234,7 +241,11 @@ export default class SpeechFlowNodeA2TDeepgram extends SpeechFlowNode {
|
|
|
234
241
|
if (chunk.meta.size > 0)
|
|
235
242
|
metastore.store(chunk.timestampStart, chunk.timestampEnd, chunk.meta)
|
|
236
243
|
try {
|
|
237
|
-
|
|
244
|
+
/* send buffer (and intentionally discard all time information) */
|
|
245
|
+
self.dg.send(chunk.payload.buffer.slice(
|
|
246
|
+
chunk.payload.byteOffset,
|
|
247
|
+
chunk.payload.byteOffset + chunk.payload.byteLength
|
|
248
|
+
))
|
|
238
249
|
}
|
|
239
250
|
catch (error) {
|
|
240
251
|
callback(util.ensureError(error, "failed to send to Deepgram"))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
** SpeechFlow - Speech Processing Flow Graph
|
|
3
|
-
** Copyright (c) 2024-
|
|
3
|
+
** Copyright (c) 2024-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -24,7 +24,7 @@ export default class SpeechFlowNodeA2TGoogle extends SpeechFlowNode {
|
|
|
24
24
|
/* internal state */
|
|
25
25
|
private client: GoogleSpeech.SpeechClient | null = null
|
|
26
26
|
private recognizeStream: ReturnType<GoogleSpeech.SpeechClient["streamingRecognize"]> | null = null
|
|
27
|
-
private queue: util.
|
|
27
|
+
private queue: util.AsyncQueue<SpeechFlowChunk | null> | null = null
|
|
28
28
|
private closing = false
|
|
29
29
|
|
|
30
30
|
/* construct node */
|
|
@@ -63,7 +63,7 @@ export default class SpeechFlowNodeA2TGoogle extends SpeechFlowNode {
|
|
|
63
63
|
this.closing = false
|
|
64
64
|
|
|
65
65
|
/* create queue for results */
|
|
66
|
-
this.queue = new util.
|
|
66
|
+
this.queue = new util.AsyncQueue<SpeechFlowChunk | null>()
|
|
67
67
|
|
|
68
68
|
/* create a store for the meta information */
|
|
69
69
|
const metastore = new util.TimeStore<Map<string, any>>()
|
|
@@ -152,7 +152,8 @@ export default class SpeechFlowNodeA2TGoogle extends SpeechFlowNode {
|
|
|
152
152
|
/* fallback: use result timing */
|
|
153
153
|
const resultEnd = result.resultEndTime
|
|
154
154
|
if (resultEnd) {
|
|
155
|
-
|
|
155
|
+
tsStart = Duration.fromMillis(0).plus(this.timeZeroOffset)
|
|
156
|
+
tsEnd = Duration.fromMillis(
|
|
156
157
|
(Number(resultEnd.seconds ?? 0) * 1000) +
|
|
157
158
|
(Number(resultEnd.nanos ?? 0) / 1000000)
|
|
158
159
|
).plus(this.timeZeroOffset)
|