speechflow 0.9.5 → 0.9.7
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 +19 -0
- package/README.md +221 -53
- package/dst/speechflow-node-a2a-ffmpeg.d.ts +13 -0
- package/dst/speechflow-node-a2a-ffmpeg.js +152 -0
- package/dst/speechflow-node-a2a-wav.d.ts +11 -0
- package/dst/speechflow-node-a2a-wav.js +170 -0
- package/dst/speechflow-node-a2t-deepgram.d.ts +12 -0
- package/dst/speechflow-node-a2t-deepgram.js +220 -0
- package/dst/speechflow-node-deepgram.d.ts +3 -1
- package/dst/speechflow-node-deepgram.js +86 -22
- package/dst/speechflow-node-deepl.d.ts +3 -1
- package/dst/speechflow-node-deepl.js +25 -20
- package/dst/speechflow-node-device.d.ts +3 -1
- package/dst/speechflow-node-device.js +53 -2
- package/dst/speechflow-node-elevenlabs.d.ts +3 -1
- package/dst/speechflow-node-elevenlabs.js +37 -42
- package/dst/speechflow-node-ffmpeg.d.ts +3 -1
- package/dst/speechflow-node-ffmpeg.js +42 -4
- package/dst/speechflow-node-file.d.ts +3 -1
- package/dst/speechflow-node-file.js +84 -13
- package/dst/speechflow-node-format.d.ts +11 -0
- package/dst/speechflow-node-format.js +80 -0
- package/dst/speechflow-node-gemma.d.ts +3 -1
- package/dst/speechflow-node-gemma.js +84 -23
- package/dst/speechflow-node-mqtt.d.ts +13 -0
- package/dst/speechflow-node-mqtt.js +181 -0
- package/dst/speechflow-node-opus.d.ts +12 -0
- package/dst/speechflow-node-opus.js +135 -0
- package/dst/speechflow-node-subtitle.d.ts +12 -0
- package/dst/speechflow-node-subtitle.js +96 -0
- package/dst/speechflow-node-t2a-elevenlabs.d.ts +13 -0
- package/dst/speechflow-node-t2a-elevenlabs.js +182 -0
- package/dst/speechflow-node-t2t-deepl.d.ts +12 -0
- package/dst/speechflow-node-t2t-deepl.js +133 -0
- package/dst/speechflow-node-t2t-format.d.ts +11 -0
- package/dst/speechflow-node-t2t-format.js +80 -0
- package/dst/speechflow-node-t2t-gemma.d.ts +13 -0
- package/dst/speechflow-node-t2t-gemma.js +213 -0
- package/dst/speechflow-node-t2t-opus.d.ts +12 -0
- package/dst/speechflow-node-t2t-opus.js +135 -0
- package/dst/speechflow-node-t2t-subtitle.d.ts +12 -0
- package/dst/speechflow-node-t2t-subtitle.js +96 -0
- package/dst/speechflow-node-trace.d.ts +11 -0
- package/dst/speechflow-node-trace.js +88 -0
- package/dst/speechflow-node-wav.d.ts +11 -0
- package/dst/speechflow-node-wav.js +170 -0
- package/dst/speechflow-node-websocket.d.ts +3 -1
- package/dst/speechflow-node-websocket.js +149 -49
- package/dst/speechflow-node-whisper-common.d.ts +34 -0
- package/dst/speechflow-node-whisper-common.js +7 -0
- package/dst/speechflow-node-whisper-ggml.d.ts +1 -0
- package/dst/speechflow-node-whisper-ggml.js +97 -0
- package/dst/speechflow-node-whisper-onnx.d.ts +1 -0
- package/dst/speechflow-node-whisper-onnx.js +131 -0
- package/dst/speechflow-node-whisper-worker-ggml.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker-ggml.js +97 -0
- package/dst/speechflow-node-whisper-worker-onnx.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker-onnx.js +131 -0
- package/dst/speechflow-node-whisper-worker.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker.js +116 -0
- package/dst/speechflow-node-whisper-worker2.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker2.js +82 -0
- package/dst/speechflow-node-whisper.d.ts +19 -0
- package/dst/speechflow-node-whisper.js +604 -0
- package/dst/speechflow-node-x2x-trace.d.ts +11 -0
- package/dst/speechflow-node-x2x-trace.js +88 -0
- package/dst/speechflow-node-xio-device.d.ts +13 -0
- package/dst/speechflow-node-xio-device.js +205 -0
- package/dst/speechflow-node-xio-file.d.ts +11 -0
- package/dst/speechflow-node-xio-file.js +176 -0
- package/dst/speechflow-node-xio-mqtt.d.ts +13 -0
- package/dst/speechflow-node-xio-mqtt.js +181 -0
- package/dst/speechflow-node-xio-websocket.d.ts +13 -0
- package/dst/speechflow-node-xio-websocket.js +275 -0
- package/dst/speechflow-node.d.ts +24 -6
- package/dst/speechflow-node.js +63 -6
- package/dst/speechflow-utils.d.ts +23 -0
- package/dst/speechflow-utils.js +194 -0
- package/dst/speechflow.js +146 -43
- package/etc/biome.jsonc +12 -4
- package/etc/stx.conf +65 -0
- package/package.d/@ericedouard+vad-node-realtime+0.2.0.patch +18 -0
- package/package.json +49 -31
- package/sample.yaml +59 -27
- package/src/lib.d.ts +6 -1
- package/src/{speechflow-node-ffmpeg.ts → speechflow-node-a2a-ffmpeg.ts} +10 -4
- package/src/speechflow-node-a2a-wav.ts +143 -0
- package/src/speechflow-node-a2t-deepgram.ts +199 -0
- package/src/{speechflow-node-elevenlabs.ts → speechflow-node-t2a-elevenlabs.ts} +38 -45
- package/src/{speechflow-node-deepl.ts → speechflow-node-t2t-deepl.ts} +36 -25
- package/src/speechflow-node-t2t-format.ts +85 -0
- package/src/{speechflow-node-gemma.ts → speechflow-node-t2t-gemma.ts} +89 -25
- package/src/speechflow-node-t2t-opus.ts +111 -0
- package/src/speechflow-node-t2t-subtitle.ts +101 -0
- package/src/speechflow-node-x2x-trace.ts +92 -0
- package/src/{speechflow-node-device.ts → speechflow-node-xio-device.ts} +25 -3
- package/src/speechflow-node-xio-file.ts +153 -0
- package/src/speechflow-node-xio-mqtt.ts +154 -0
- package/src/speechflow-node-xio-websocket.ts +248 -0
- package/src/speechflow-node.ts +63 -6
- package/src/speechflow-utils.ts +212 -0
- package/src/speechflow.ts +150 -43
- package/etc/nps.yaml +0 -40
- package/src/speechflow-node-deepgram.ts +0 -133
- package/src/speechflow-node-file.ts +0 -108
- package/src/speechflow-node-websocket.ts +0 -179
|
@@ -10,7 +10,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
/* standard dependencies */
|
|
12
12
|
const node_stream_1 = __importDefault(require("node:stream"));
|
|
13
|
-
const node_events_1 = require("node:events");
|
|
14
13
|
/* external dependencies */
|
|
15
14
|
const ollama_1 = require("ollama");
|
|
16
15
|
/* internal dependencies */
|
|
@@ -23,6 +22,65 @@ class SpeechFlowNodeGemma extends speechflow_node_1.default {
|
|
|
23
22
|
ollama = null;
|
|
24
23
|
/* internal LLM setup */
|
|
25
24
|
setup = {
|
|
25
|
+
/* English (EN) spellchecking only */
|
|
26
|
+
"en-en": {
|
|
27
|
+
systemPrompt: "You are a proofreader and spellchecker for English.\n" +
|
|
28
|
+
"Output only the corrected text.\n" +
|
|
29
|
+
"Do NOT use markdown.\n" +
|
|
30
|
+
"Do NOT give any explanations.\n" +
|
|
31
|
+
"Do NOT give any introduction.\n" +
|
|
32
|
+
"Do NOT give any comments.\n" +
|
|
33
|
+
"Do NOT give any preamble.\n" +
|
|
34
|
+
"Do NOT give any prolog.\n" +
|
|
35
|
+
"Do NOT give any epilog.\n" +
|
|
36
|
+
"Do NOT change the gammar.\n" +
|
|
37
|
+
"Do NOT use synonyms for words.\n" +
|
|
38
|
+
"Keep all words.\n" +
|
|
39
|
+
"Fill in missing commas.\n" +
|
|
40
|
+
"Fill in missing points.\n" +
|
|
41
|
+
"Fill in missing question marks.\n" +
|
|
42
|
+
"Fill in missing hyphens.\n" +
|
|
43
|
+
"Focus ONLY on the word spelling.\n" +
|
|
44
|
+
"The text you have to correct is:\n",
|
|
45
|
+
chat: [
|
|
46
|
+
{ role: "user", content: "I luve my wyfe" },
|
|
47
|
+
{ role: "system", content: "I love my wife." },
|
|
48
|
+
{ role: "user", content: "The weether is wunderfull!" },
|
|
49
|
+
{ role: "system", content: "The weather is wonderful!" },
|
|
50
|
+
{ role: "user", content: "The live awesome but I'm hungry." },
|
|
51
|
+
{ role: "system", content: "The live is awesome, but I'm hungry." }
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
/* German (DE) spellchecking only */
|
|
55
|
+
"de-de": {
|
|
56
|
+
systemPrompt: "Du bist ein Korrekturleser und Rechtschreibprüfer für Deutsch.\n" +
|
|
57
|
+
"Gib nur den korrigierten Text aus.\n" +
|
|
58
|
+
"Benutze KEIN Markdown.\n" +
|
|
59
|
+
"Gib KEINE Erklärungen.\n" +
|
|
60
|
+
"Gib KEINE Einleitung.\n" +
|
|
61
|
+
"Gib KEINE Kommentare.\n" +
|
|
62
|
+
"Gib KEINE Preamble.\n" +
|
|
63
|
+
"Gib KEINEN Prolog.\n" +
|
|
64
|
+
"Gib KEINEN Epilog.\n" +
|
|
65
|
+
"Ändere NICHT die Grammatik.\n" +
|
|
66
|
+
"Verwende KEINE Synonyme für Wörter.\n" +
|
|
67
|
+
"Behalte alle Wörter bei.\n" +
|
|
68
|
+
"Füge fehlende Kommas ein.\n" +
|
|
69
|
+
"Füge fehlende Punkte ein.\n" +
|
|
70
|
+
"Füge fehlende Fragezeichen ein.\n" +
|
|
71
|
+
"Füge fehlende Bindestriche ein.\n" +
|
|
72
|
+
"Füge fehlende Gedankenstriche ein.\n" +
|
|
73
|
+
"Fokussiere dich NUR auf die Rechtschreibung der Wörter.\n" +
|
|
74
|
+
"Der von dir zu korrigierende Text ist:\n",
|
|
75
|
+
chat: [
|
|
76
|
+
{ role: "user", content: "Ich ljebe meine Frao" },
|
|
77
|
+
{ role: "system", content: "Ich liebe meine Frau." },
|
|
78
|
+
{ role: "user", content: "Die Wedter ist wunderschoen." },
|
|
79
|
+
{ role: "system", content: "Das Wetter ist wunderschön." },
|
|
80
|
+
{ role: "user", content: "Das Leben einfach großartig aber ich bin hungrig." },
|
|
81
|
+
{ role: "system", content: "Das Leben ist einfach großartig, aber ich bin hungrig." }
|
|
82
|
+
]
|
|
83
|
+
},
|
|
26
84
|
/* English (EN) to German (DE) translation */
|
|
27
85
|
"en-de": {
|
|
28
86
|
systemPrompt: "You are a translator.\n" +
|
|
@@ -69,17 +127,14 @@ class SpeechFlowNodeGemma extends speechflow_node_1.default {
|
|
|
69
127
|
}
|
|
70
128
|
};
|
|
71
129
|
/* construct node */
|
|
72
|
-
constructor(id, opts, args) {
|
|
73
|
-
super(id, opts, args);
|
|
130
|
+
constructor(id, cfg, opts, args) {
|
|
131
|
+
super(id, cfg, opts, args);
|
|
74
132
|
/* declare node configuration parameters */
|
|
75
133
|
this.configure({
|
|
76
134
|
api: { type: "string", val: "http://127.0.0.1:11434", match: /^https?:\/\/.+?:\d+$/ },
|
|
77
135
|
src: { type: "string", pos: 0, val: "de", match: /^(?:de|en)$/ },
|
|
78
136
|
dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en)$/ }
|
|
79
137
|
});
|
|
80
|
-
/* sanity check situation */
|
|
81
|
-
if (this.params.src === this.params.dst)
|
|
82
|
-
throw new Error("source and destination languages cannot be the same");
|
|
83
138
|
/* declare node input/output format */
|
|
84
139
|
this.input = "text";
|
|
85
140
|
this.output = "text";
|
|
@@ -111,27 +166,33 @@ class SpeechFlowNodeGemma extends speechflow_node_1.default {
|
|
|
111
166
|
return response.message.content;
|
|
112
167
|
};
|
|
113
168
|
/* establish a duplex stream and connect it to Ollama */
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
callback();
|
|
121
|
-
}
|
|
169
|
+
this.stream = new node_stream_1.default.Transform({
|
|
170
|
+
readableObjectMode: true,
|
|
171
|
+
writableObjectMode: true,
|
|
172
|
+
decodeStrings: false,
|
|
173
|
+
transform(chunk, encoding, callback) {
|
|
174
|
+
if (Buffer.isBuffer(chunk.payload))
|
|
175
|
+
callback(new Error("invalid chunk payload type"));
|
|
122
176
|
else {
|
|
123
|
-
|
|
124
|
-
|
|
177
|
+
if (chunk.payload === "") {
|
|
178
|
+
this.push(chunk);
|
|
125
179
|
callback();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
translate(chunk.payload).then((payload) => {
|
|
183
|
+
const chunkNew = chunk.clone();
|
|
184
|
+
chunkNew.payload = payload;
|
|
185
|
+
this.push(chunkNew);
|
|
186
|
+
callback();
|
|
187
|
+
}).catch((err) => {
|
|
188
|
+
callback(err);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
129
191
|
}
|
|
130
192
|
},
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
193
|
+
final(callback) {
|
|
194
|
+
this.push(null);
|
|
195
|
+
callback();
|
|
135
196
|
}
|
|
136
197
|
});
|
|
137
198
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeMQTT extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
private broker;
|
|
5
|
+
private clientId;
|
|
6
|
+
constructor(id: string, cfg: {
|
|
7
|
+
[id: string]: any;
|
|
8
|
+
}, opts: {
|
|
9
|
+
[id: string]: any;
|
|
10
|
+
}, args: any[]);
|
|
11
|
+
open(): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
4
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
/* standard dependencies */
|
|
45
|
+
const node_stream_1 = __importDefault(require("node:stream"));
|
|
46
|
+
/* external dependencies */
|
|
47
|
+
const mqtt_1 = __importDefault(require("mqtt"));
|
|
48
|
+
const pure_uuid_1 = __importDefault(require("pure-uuid"));
|
|
49
|
+
/* internal dependencies */
|
|
50
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
51
|
+
const utils = __importStar(require("./speechflow-utils"));
|
|
52
|
+
/* SpeechFlow node for MQTT networking */
|
|
53
|
+
class SpeechFlowNodeMQTT extends speechflow_node_1.default {
|
|
54
|
+
/* declare official node name */
|
|
55
|
+
static name = "mqtt";
|
|
56
|
+
/* internal state */
|
|
57
|
+
broker = null;
|
|
58
|
+
clientId = (new pure_uuid_1.default(1)).format();
|
|
59
|
+
/* construct node */
|
|
60
|
+
constructor(id, cfg, opts, args) {
|
|
61
|
+
super(id, cfg, opts, args);
|
|
62
|
+
/* declare node configuration parameters */
|
|
63
|
+
this.configure({
|
|
64
|
+
url: { type: "string", pos: 0, val: "", match: /^(?:|(?:ws|mqtt):\/\/(.+?):(\d+)(?:\/.*)?)$/ },
|
|
65
|
+
username: { type: "string", pos: 1, val: "", match: /^.+$/ },
|
|
66
|
+
password: { type: "string", pos: 2, val: "", match: /^.+$/ },
|
|
67
|
+
topicRead: { type: "string", pos: 3, val: "", match: /^.+$/ },
|
|
68
|
+
topicWrite: { type: "string", pos: 4, val: "", match: /^.+$/ },
|
|
69
|
+
mode: { type: "string", pos: 5, val: "w", match: /^(?:r|w|rw)$/ },
|
|
70
|
+
type: { type: "string", pos: 6, val: "text", match: /^(?:audio|text)$/ }
|
|
71
|
+
});
|
|
72
|
+
/* logical parameter sanity check */
|
|
73
|
+
if ((this.params.mode === "w" || this.params.mode === "rw") && this.params.topicWrite === "")
|
|
74
|
+
throw new Error("writing to MQTT requires a topicWrite parameter");
|
|
75
|
+
if ((this.params.mode === "r" || this.params.mode === "rw") && this.params.topicRead === "")
|
|
76
|
+
throw new Error("reading from MQTT requires a topicRead parameter");
|
|
77
|
+
/* declare node input/output format */
|
|
78
|
+
if (this.params.mode === "rw") {
|
|
79
|
+
this.input = this.params.type;
|
|
80
|
+
this.output = this.params.type;
|
|
81
|
+
}
|
|
82
|
+
else if (this.params.mode === "r") {
|
|
83
|
+
this.input = "none";
|
|
84
|
+
this.output = this.params.type;
|
|
85
|
+
}
|
|
86
|
+
else if (this.params.mode === "w") {
|
|
87
|
+
this.input = this.params.type;
|
|
88
|
+
this.output = "none";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/* open node */
|
|
92
|
+
async open() {
|
|
93
|
+
/* connect remotely to a MQTT broker */
|
|
94
|
+
this.broker = mqtt_1.default.connect(this.params.url, {
|
|
95
|
+
protocolId: "MQTT",
|
|
96
|
+
protocolVersion: 5,
|
|
97
|
+
username: this.params.username,
|
|
98
|
+
password: this.params.password,
|
|
99
|
+
clientId: this.clientId,
|
|
100
|
+
clean: true,
|
|
101
|
+
resubscribe: true,
|
|
102
|
+
keepalive: 60, /* 60s */
|
|
103
|
+
reconnectPeriod: 2 * 1000, /* 2s */
|
|
104
|
+
connectTimeout: 30 * 1000 /* 30s */
|
|
105
|
+
});
|
|
106
|
+
this.broker.on("error", (error) => {
|
|
107
|
+
this.log("error", `error on MQTT ${this.params.url}: ${error.message}`);
|
|
108
|
+
});
|
|
109
|
+
this.broker.on("connect", (packet) => {
|
|
110
|
+
this.log("info", `connection opened to MQTT ${this.params.url}`);
|
|
111
|
+
if (this.params.mode !== "w" && !packet.sessionPresent)
|
|
112
|
+
this.broker.subscribe([this.params.topicRead], () => { });
|
|
113
|
+
});
|
|
114
|
+
this.broker.on("reconnect", () => {
|
|
115
|
+
this.log("info", `connection re-opened to MQTT ${this.params.url}`);
|
|
116
|
+
});
|
|
117
|
+
this.broker.on("disconnect", (packet) => {
|
|
118
|
+
this.log("info", `connection closed to MQTT ${this.params.url}`);
|
|
119
|
+
});
|
|
120
|
+
const chunkQueue = new utils.SingleQueue();
|
|
121
|
+
this.broker.on("message", (topic, payload, packet) => {
|
|
122
|
+
if (topic !== this.params.topicRead)
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
const chunk = utils.streamChunkDecode(payload);
|
|
126
|
+
chunkQueue.write(chunk);
|
|
127
|
+
}
|
|
128
|
+
catch (_err) {
|
|
129
|
+
this.log("warning", `received invalid CBOR chunk from MQTT ${this.params.url}`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
const broker = this.broker;
|
|
133
|
+
const topicWrite = this.params.topicWrite;
|
|
134
|
+
const type = this.params.type;
|
|
135
|
+
const mode = this.params.mode;
|
|
136
|
+
this.stream = new node_stream_1.default.Duplex({
|
|
137
|
+
writableObjectMode: true,
|
|
138
|
+
readableObjectMode: true,
|
|
139
|
+
decodeStrings: false,
|
|
140
|
+
write(chunk, encoding, callback) {
|
|
141
|
+
if (mode === "r")
|
|
142
|
+
callback(new Error("write operation on read-only node"));
|
|
143
|
+
else if (chunk.type !== type)
|
|
144
|
+
callback(new Error(`written chunk is not of ${type} type`));
|
|
145
|
+
else if (!broker.connected)
|
|
146
|
+
callback(new Error("still no MQTT connection available"));
|
|
147
|
+
else {
|
|
148
|
+
const data = Buffer.from(utils.streamChunkEncode(chunk));
|
|
149
|
+
broker.publish(topicWrite, data, { qos: 2, retain: false }, (err) => {
|
|
150
|
+
if (err)
|
|
151
|
+
callback(new Error(`failed to publish to MQTT topic "${topicWrite}": ${err}`));
|
|
152
|
+
else
|
|
153
|
+
callback();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
read(size) {
|
|
158
|
+
if (mode === "w")
|
|
159
|
+
throw new Error("read operation on write-only node");
|
|
160
|
+
chunkQueue.read().then((chunk) => {
|
|
161
|
+
this.push(chunk, "binary");
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/* close node */
|
|
167
|
+
async close() {
|
|
168
|
+
/* close Websocket server */
|
|
169
|
+
if (this.broker !== null) {
|
|
170
|
+
if (this.broker.connected)
|
|
171
|
+
this.broker.end();
|
|
172
|
+
this.broker = null;
|
|
173
|
+
}
|
|
174
|
+
/* close stream */
|
|
175
|
+
if (this.stream !== null) {
|
|
176
|
+
this.stream.destroy();
|
|
177
|
+
this.stream = null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
exports.default = SpeechFlowNodeMQTT;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeOPUS extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
private translator;
|
|
5
|
+
constructor(id: string, cfg: {
|
|
6
|
+
[id: string]: any;
|
|
7
|
+
}, opts: {
|
|
8
|
+
[id: string]: any;
|
|
9
|
+
}, args: any[]);
|
|
10
|
+
open(): Promise<void>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
4
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
/* standard dependencies */
|
|
45
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
46
|
+
const node_stream_1 = __importDefault(require("node:stream"));
|
|
47
|
+
/* external dependencies */
|
|
48
|
+
const Transformers = __importStar(require("@huggingface/transformers"));
|
|
49
|
+
/* internal dependencies */
|
|
50
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
51
|
+
/* SpeechFlow node for OPUS text-to-text translation */
|
|
52
|
+
class SpeechFlowNodeOPUS extends speechflow_node_1.default {
|
|
53
|
+
/* declare official node name */
|
|
54
|
+
static name = "opus";
|
|
55
|
+
/* internal state */
|
|
56
|
+
translator = null;
|
|
57
|
+
/* construct node */
|
|
58
|
+
constructor(id, cfg, opts, args) {
|
|
59
|
+
super(id, cfg, opts, args);
|
|
60
|
+
/* declare node configuration parameters */
|
|
61
|
+
this.configure({
|
|
62
|
+
src: { type: "string", pos: 0, val: "de", match: /^(?:de|en)$/ },
|
|
63
|
+
dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en)$/ }
|
|
64
|
+
});
|
|
65
|
+
/* sanity check situation */
|
|
66
|
+
if (this.params.src === this.params.dst)
|
|
67
|
+
throw new Error("source and destination languages cannot be the same");
|
|
68
|
+
/* declare node input/output format */
|
|
69
|
+
this.input = "text";
|
|
70
|
+
this.output = "text";
|
|
71
|
+
}
|
|
72
|
+
/* open node */
|
|
73
|
+
async open() {
|
|
74
|
+
/* instantiate OPUS */
|
|
75
|
+
const model = `onnx-community/opus-mt-${this.params.src}-${this.params.dst}`;
|
|
76
|
+
this.translator = await Transformers.pipeline("translation", model, {
|
|
77
|
+
cache_dir: node_path_1.default.join(this.config.cacheDir, "opus"),
|
|
78
|
+
dtype: "q4",
|
|
79
|
+
device: "gpu"
|
|
80
|
+
});
|
|
81
|
+
if (this.translator === null)
|
|
82
|
+
throw new Error("failed to instantiate translator pipeline");
|
|
83
|
+
/* provide text-to-text translation */
|
|
84
|
+
const translate = async (text) => {
|
|
85
|
+
const result = await this.translator(text);
|
|
86
|
+
return Array.isArray(result) ?
|
|
87
|
+
result[0].translation_text :
|
|
88
|
+
result.translation_text;
|
|
89
|
+
};
|
|
90
|
+
/* establish a duplex stream and connect it to Ollama */
|
|
91
|
+
this.stream = new node_stream_1.default.Transform({
|
|
92
|
+
readableObjectMode: true,
|
|
93
|
+
writableObjectMode: true,
|
|
94
|
+
decodeStrings: false,
|
|
95
|
+
transform(chunk, encoding, callback) {
|
|
96
|
+
if (Buffer.isBuffer(chunk.payload))
|
|
97
|
+
callback(new Error("invalid chunk payload type"));
|
|
98
|
+
else {
|
|
99
|
+
if (chunk.payload === "") {
|
|
100
|
+
this.push(chunk);
|
|
101
|
+
callback();
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
translate(chunk.payload).then((payload) => {
|
|
105
|
+
const chunkNew = chunk.clone();
|
|
106
|
+
chunkNew.payload = payload;
|
|
107
|
+
this.push(chunkNew);
|
|
108
|
+
callback();
|
|
109
|
+
}).catch((err) => {
|
|
110
|
+
callback(err);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
final(callback) {
|
|
116
|
+
this.push(null);
|
|
117
|
+
callback();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/* close node */
|
|
122
|
+
async close() {
|
|
123
|
+
/* close stream */
|
|
124
|
+
if (this.stream !== null) {
|
|
125
|
+
this.stream.destroy();
|
|
126
|
+
this.stream = null;
|
|
127
|
+
}
|
|
128
|
+
/* shutdown OPUS */
|
|
129
|
+
if (this.translator !== null) {
|
|
130
|
+
this.translator.dispose();
|
|
131
|
+
this.translator = null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.default = SpeechFlowNodeOPUS;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeSubtitle extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
private sequenceNo;
|
|
5
|
+
constructor(id: string, cfg: {
|
|
6
|
+
[id: string]: any;
|
|
7
|
+
}, opts: {
|
|
8
|
+
[id: string]: any;
|
|
9
|
+
}, args: any[]);
|
|
10
|
+
open(): Promise<void>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
4
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
/* standard dependencies */
|
|
12
|
+
const node_stream_1 = __importDefault(require("node:stream"));
|
|
13
|
+
/* internal dependencies */
|
|
14
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
15
|
+
/* SpeechFlow node for subtitle (text-to-text) "translations" */
|
|
16
|
+
class SpeechFlowNodeSubtitle extends speechflow_node_1.default {
|
|
17
|
+
/* declare official node name */
|
|
18
|
+
static name = "subtitle";
|
|
19
|
+
/* internal state */
|
|
20
|
+
sequenceNo = 1;
|
|
21
|
+
/* construct node */
|
|
22
|
+
constructor(id, cfg, opts, args) {
|
|
23
|
+
super(id, cfg, opts, args);
|
|
24
|
+
/* declare node configuration parameters */
|
|
25
|
+
this.configure({
|
|
26
|
+
format: { type: "string", pos: 0, val: "srt", match: /^(?:srt|vtt)$/ }
|
|
27
|
+
});
|
|
28
|
+
/* declare node input/output format */
|
|
29
|
+
this.input = "text";
|
|
30
|
+
this.output = "text";
|
|
31
|
+
}
|
|
32
|
+
/* open node */
|
|
33
|
+
async open() {
|
|
34
|
+
this.sequenceNo = 1;
|
|
35
|
+
/* provide text-to-subtitle conversion */
|
|
36
|
+
const convert = async (chunk) => {
|
|
37
|
+
if (typeof chunk.payload !== "string")
|
|
38
|
+
throw new Error("chunk payload type must be string");
|
|
39
|
+
let text = chunk.payload;
|
|
40
|
+
if (this.params.format === "srt") {
|
|
41
|
+
const start = chunk.timestampStart.toFormat("hh:mm:ss,SSS");
|
|
42
|
+
const end = chunk.timestampEnd.toFormat("hh:mm:ss,SSS");
|
|
43
|
+
text = `${this.sequenceNo++}\n` +
|
|
44
|
+
`${start} --> ${end}\n` +
|
|
45
|
+
`${text}\n\n`;
|
|
46
|
+
}
|
|
47
|
+
else if (this.params.format === "vtt") {
|
|
48
|
+
const start = chunk.timestampStart.toFormat("hh:mm:ss.SSS");
|
|
49
|
+
const end = chunk.timestampEnd.toFormat("hh:mm:ss.SSS");
|
|
50
|
+
text = `${this.sequenceNo++}\n` +
|
|
51
|
+
`${start} --> ${end}\n` +
|
|
52
|
+
`${text}\n\n`;
|
|
53
|
+
}
|
|
54
|
+
return text;
|
|
55
|
+
};
|
|
56
|
+
/* establish a duplex stream */
|
|
57
|
+
this.stream = new node_stream_1.default.Transform({
|
|
58
|
+
readableObjectMode: true,
|
|
59
|
+
writableObjectMode: true,
|
|
60
|
+
decodeStrings: false,
|
|
61
|
+
transform(chunk, encoding, callback) {
|
|
62
|
+
if (Buffer.isBuffer(chunk.payload))
|
|
63
|
+
callback(new Error("invalid chunk payload type"));
|
|
64
|
+
else {
|
|
65
|
+
if (chunk.payload === "") {
|
|
66
|
+
this.push(chunk);
|
|
67
|
+
callback();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
convert(chunk).then((payload) => {
|
|
71
|
+
const chunkNew = chunk.clone();
|
|
72
|
+
chunkNew.payload = payload;
|
|
73
|
+
this.push(chunkNew);
|
|
74
|
+
callback();
|
|
75
|
+
}).catch((err) => {
|
|
76
|
+
callback(err);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
final(callback) {
|
|
82
|
+
this.push(null);
|
|
83
|
+
callback();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/* open node */
|
|
88
|
+
async close() {
|
|
89
|
+
/* close stream */
|
|
90
|
+
if (this.stream !== null) {
|
|
91
|
+
this.stream.destroy();
|
|
92
|
+
this.stream = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.default = SpeechFlowNodeSubtitle;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeElevenlabs extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
private elevenlabs;
|
|
5
|
+
private static speexInitialized;
|
|
6
|
+
constructor(id: string, cfg: {
|
|
7
|
+
[id: string]: any;
|
|
8
|
+
}, opts: {
|
|
9
|
+
[id: string]: any;
|
|
10
|
+
}, args: any[]);
|
|
11
|
+
open(): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|