speechflow 0.9.0 → 0.9.2
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/README.md +30 -0
- package/dst/speechflow-node-deepgram.d.ts +10 -0
- package/dst/speechflow-node-deepgram.js +44 -23
- package/dst/speechflow-node-deepl.d.ts +10 -0
- package/dst/speechflow-node-deepl.js +30 -12
- package/dst/speechflow-node-device.d.ts +11 -0
- package/dst/speechflow-node-device.js +73 -14
- package/dst/speechflow-node-elevenlabs.d.ts +10 -0
- package/dst/speechflow-node-elevenlabs.js +14 -2
- package/dst/speechflow-node-ffmpeg.d.ts +11 -0
- package/dst/speechflow-node-ffmpeg.js +114 -0
- package/dst/speechflow-node-file.d.ts +9 -0
- package/dst/speechflow-node-file.js +71 -13
- package/dst/speechflow-node-gemma.d.ts +11 -0
- package/dst/speechflow-node-gemma.js +152 -0
- package/dst/speechflow-node-websocket.d.ts +11 -0
- package/dst/speechflow-node-websocket.js +34 -6
- package/dst/speechflow-node.d.ts +38 -0
- package/dst/speechflow-node.js +28 -10
- package/dst/speechflow.d.ts +1 -0
- package/dst/speechflow.js +128 -43
- package/etc/tsconfig.json +2 -0
- package/package.json +25 -11
- package/src/speechflow-node-deepgram.ts +55 -24
- package/src/speechflow-node-deepl.ts +38 -16
- package/src/speechflow-node-device.ts +88 -14
- package/src/speechflow-node-elevenlabs.ts +19 -2
- package/src/speechflow-node-ffmpeg.ts +122 -0
- package/src/speechflow-node-file.ts +76 -14
- package/src/speechflow-node-gemma.ts +169 -0
- package/src/speechflow-node-websocket.ts +52 -13
- package/src/speechflow-node.ts +43 -21
- package/src/speechflow.ts +144 -47
- package/dst/speechflow-util.js +0 -37
- package/src/speechflow-util.ts +0 -36
|
@@ -8,40 +8,98 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8
8
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
9
|
};
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
/* standard dependencies */
|
|
11
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
|
+
const node_stream_1 = __importDefault(require("node:stream"));
|
|
14
|
+
/* internal dependencies */
|
|
12
15
|
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
13
|
-
|
|
16
|
+
/* SpeechFlow node for file access */
|
|
17
|
+
class SpeechFlowNodeFile extends speechflow_node_1.default {
|
|
18
|
+
/* declare official node name */
|
|
19
|
+
static name = "file";
|
|
20
|
+
/* construct node */
|
|
14
21
|
constructor(id, opts, args) {
|
|
15
22
|
super(id, opts, args);
|
|
23
|
+
/* declare node configuration parameters */
|
|
16
24
|
this.configure({
|
|
17
25
|
path: { type: "string", pos: 0 },
|
|
18
26
|
mode: { type: "string", pos: 1, val: "r", match: /^(?:r|w|rw)$/ },
|
|
19
27
|
type: { type: "string", pos: 2, val: "audio", match: /^(?:audio|text)$/ }
|
|
20
28
|
});
|
|
29
|
+
/* declare node input/output format */
|
|
30
|
+
if (this.params.mode === "rw") {
|
|
31
|
+
this.input = this.params.type;
|
|
32
|
+
this.output = this.params.type;
|
|
33
|
+
}
|
|
34
|
+
else if (this.params.mode === "r") {
|
|
35
|
+
this.input = "none";
|
|
36
|
+
this.output = this.params.type;
|
|
37
|
+
}
|
|
38
|
+
else if (this.params.mode === "w") {
|
|
39
|
+
this.input = this.params.type;
|
|
40
|
+
this.output = "none";
|
|
41
|
+
}
|
|
21
42
|
}
|
|
43
|
+
/* open node */
|
|
22
44
|
async open() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (this.params.path === "-")
|
|
45
|
+
const encoding = this.params.type === "text" ? this.config.textEncoding : "binary";
|
|
46
|
+
if (this.params.mode === "rw") {
|
|
47
|
+
if (this.params.path === "-") {
|
|
48
|
+
/* standard I/O */
|
|
49
|
+
process.stdin.setEncoding(encoding);
|
|
50
|
+
process.stdout.setEncoding(encoding);
|
|
51
|
+
this.stream = node_stream_1.default.Duplex.from({
|
|
52
|
+
readable: process.stdin,
|
|
53
|
+
writable: process.stdout
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
/* file I/O */
|
|
58
|
+
this.stream = node_stream_1.default.Duplex.from({
|
|
59
|
+
readable: node_fs_1.default.createReadStream(this.params.path, { encoding }),
|
|
60
|
+
writable: node_fs_1.default.createWriteStream(this.params.path, { encoding })
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (this.params.mode === "r") {
|
|
65
|
+
if (this.params.path === "-") {
|
|
66
|
+
/* standard I/O */
|
|
67
|
+
process.stdin.setEncoding(encoding);
|
|
26
68
|
this.stream = process.stdin;
|
|
27
|
-
|
|
28
|
-
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
/* file I/O */
|
|
72
|
+
this.stream = node_fs_1.default.createReadStream(this.params.path, { encoding });
|
|
73
|
+
}
|
|
29
74
|
}
|
|
30
75
|
else if (this.params.mode === "w") {
|
|
31
|
-
|
|
32
|
-
|
|
76
|
+
if (this.params.path === "-") {
|
|
77
|
+
/* standard I/O */
|
|
78
|
+
process.stdout.setEncoding(encoding);
|
|
33
79
|
this.stream = process.stdout;
|
|
34
|
-
|
|
35
|
-
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
/* file I/O */
|
|
83
|
+
this.stream = node_fs_1.default.createWriteStream(this.params.path, { encoding });
|
|
84
|
+
}
|
|
36
85
|
}
|
|
37
86
|
else
|
|
38
87
|
throw new Error(`invalid file mode "${this.params.mode}"`);
|
|
39
88
|
}
|
|
89
|
+
/* close node */
|
|
40
90
|
async close() {
|
|
41
|
-
|
|
42
|
-
|
|
91
|
+
/* shutdown stream */
|
|
92
|
+
if (this.stream !== null) {
|
|
93
|
+
await new Promise((resolve) => {
|
|
94
|
+
if (this.stream instanceof node_stream_1.default.Writable || this.stream instanceof node_stream_1.default.Duplex)
|
|
95
|
+
this.stream.end(() => { resolve(); });
|
|
96
|
+
else
|
|
97
|
+
resolve();
|
|
98
|
+
});
|
|
99
|
+
if (this.params.path !== "-")
|
|
100
|
+
this.stream.destroy();
|
|
43
101
|
this.stream = null;
|
|
44
102
|
}
|
|
45
103
|
}
|
|
46
104
|
}
|
|
47
|
-
exports.default =
|
|
105
|
+
exports.default = SpeechFlowNodeFile;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeGemma extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
private ollama;
|
|
5
|
+
private setup;
|
|
6
|
+
constructor(id: string, opts: {
|
|
7
|
+
[id: string]: any;
|
|
8
|
+
}, args: any[]);
|
|
9
|
+
open(): Promise<void>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
const node_events_1 = require("node:events");
|
|
14
|
+
/* external dependencies */
|
|
15
|
+
const ollama_1 = require("ollama");
|
|
16
|
+
/* internal dependencies */
|
|
17
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
18
|
+
/* SpeechFlow node for Gemma/Ollama text-to-text translation */
|
|
19
|
+
class SpeechFlowNodeGemma extends speechflow_node_1.default {
|
|
20
|
+
/* declare official node name */
|
|
21
|
+
static name = "gemma";
|
|
22
|
+
/* internal state */
|
|
23
|
+
ollama = null;
|
|
24
|
+
/* internal LLM setup */
|
|
25
|
+
setup = {
|
|
26
|
+
/* English (EN) to German (DE) translation */
|
|
27
|
+
"en-de": {
|
|
28
|
+
systemPrompt: "You are a translator.\n" +
|
|
29
|
+
"Output only the requested text.\n" +
|
|
30
|
+
"Do not use markdown.\n" +
|
|
31
|
+
"Do not chat.\n" +
|
|
32
|
+
"Do not show any explanations.\n" +
|
|
33
|
+
"Do not show any introduction.\n" +
|
|
34
|
+
"Do not show any preamble.\n" +
|
|
35
|
+
"Do not show any prolog.\n" +
|
|
36
|
+
"Do not show any epilog.\n" +
|
|
37
|
+
"Get to the point.\n" +
|
|
38
|
+
"Directly translate text from Enlish (EN) to German (DE) language.\n",
|
|
39
|
+
chat: [
|
|
40
|
+
{ role: "user", content: "I love my wife." },
|
|
41
|
+
{ role: "system", content: "Ich liebe meine Frau." },
|
|
42
|
+
{ role: "user", content: "The weather is wonderful." },
|
|
43
|
+
{ role: "system", content: "Das Wetter ist wunderschön." },
|
|
44
|
+
{ role: "user", content: "The live is awesome." },
|
|
45
|
+
{ role: "system", content: "Das Leben ist einfach großartig." }
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
/* German (DE) to English (EN) translation */
|
|
49
|
+
"de-en": {
|
|
50
|
+
systemPrompt: "You are a translator.\n" +
|
|
51
|
+
"Output only the requested text.\n" +
|
|
52
|
+
"Do not use markdown.\n" +
|
|
53
|
+
"Do not chat.\n" +
|
|
54
|
+
"Do not show any explanations. \n" +
|
|
55
|
+
"Do not show any introduction.\n" +
|
|
56
|
+
"Do not show any preamble. \n" +
|
|
57
|
+
"Do not show any prolog. \n" +
|
|
58
|
+
"Do not show any epilog. \n" +
|
|
59
|
+
"Get to the point.\n" +
|
|
60
|
+
"Directly translate text from German (DE) to English (EN) language.\n",
|
|
61
|
+
chat: [
|
|
62
|
+
{ role: "user", content: "Ich liebe meine Frau." },
|
|
63
|
+
{ role: "system", content: "I love my wife." },
|
|
64
|
+
{ role: "user", content: "Das Wetter ist wunderschön." },
|
|
65
|
+
{ role: "system", content: "The weather is wonderful." },
|
|
66
|
+
{ role: "user", content: "Das Leben ist einfach großartig." },
|
|
67
|
+
{ role: "system", content: "The live is awesome." }
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
/* construct node */
|
|
72
|
+
constructor(id, opts, args) {
|
|
73
|
+
super(id, opts, args);
|
|
74
|
+
/* declare node configuration parameters */
|
|
75
|
+
this.configure({
|
|
76
|
+
api: { type: "string", val: "http://127.0.0.1:11434", match: /^https?:\/\/.+?:\d+$/ },
|
|
77
|
+
src: { type: "string", pos: 0, val: "de", match: /^(?:de|en)$/ },
|
|
78
|
+
dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en)$/ }
|
|
79
|
+
});
|
|
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
|
+
/* declare node input/output format */
|
|
84
|
+
this.input = "text";
|
|
85
|
+
this.output = "text";
|
|
86
|
+
}
|
|
87
|
+
/* open node */
|
|
88
|
+
async open() {
|
|
89
|
+
/* instantiate Ollama API */
|
|
90
|
+
this.ollama = new ollama_1.Ollama({ host: this.params.api });
|
|
91
|
+
/* provide text-to-text translation */
|
|
92
|
+
const translate = async (text) => {
|
|
93
|
+
const key = `${this.params.src}-${this.params.dst}`;
|
|
94
|
+
const cfg = this.setup[key];
|
|
95
|
+
const response = await this.ollama.chat({
|
|
96
|
+
model: "gemma3:4b-it-q4_K_M",
|
|
97
|
+
messages: [
|
|
98
|
+
{ role: "system", content: cfg.systemPrompt },
|
|
99
|
+
...cfg.chat,
|
|
100
|
+
{ role: "user", content: text }
|
|
101
|
+
],
|
|
102
|
+
keep_alive: "10m",
|
|
103
|
+
options: {
|
|
104
|
+
repeat_penalty: 1.1,
|
|
105
|
+
temperature: 0.7,
|
|
106
|
+
seed: 1,
|
|
107
|
+
top_k: 10,
|
|
108
|
+
top_p: 0.5
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return response.message.content;
|
|
112
|
+
};
|
|
113
|
+
/* establish a duplex stream and connect it to Ollama */
|
|
114
|
+
const queue = new node_events_1.EventEmitter();
|
|
115
|
+
this.stream = new node_stream_1.default.Duplex({
|
|
116
|
+
write(chunk, encoding, callback) {
|
|
117
|
+
const data = chunk.toString();
|
|
118
|
+
if (data === "") {
|
|
119
|
+
queue.emit("result", "");
|
|
120
|
+
callback();
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
translate(data).then((result) => {
|
|
124
|
+
queue.emit("result", result);
|
|
125
|
+
callback();
|
|
126
|
+
}).catch((err) => {
|
|
127
|
+
callback(err);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
read(size) {
|
|
132
|
+
queue.once("result", (result) => {
|
|
133
|
+
this.push(result);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/* close node */
|
|
139
|
+
async close() {
|
|
140
|
+
/* close stream */
|
|
141
|
+
if (this.stream !== null) {
|
|
142
|
+
this.stream.destroy();
|
|
143
|
+
this.stream = null;
|
|
144
|
+
}
|
|
145
|
+
/* shutdown Ollama */
|
|
146
|
+
if (this.ollama !== null) {
|
|
147
|
+
this.ollama.abort();
|
|
148
|
+
this.ollama = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
exports.default = SpeechFlowNodeGemma;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
private server;
|
|
5
|
+
private client;
|
|
6
|
+
constructor(id: string, opts: {
|
|
7
|
+
[id: string]: any;
|
|
8
|
+
}, args: any[]);
|
|
9
|
+
open(): Promise<void>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -8,25 +8,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8
8
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
9
|
};
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
/* standard dependencies */
|
|
11
12
|
const node_stream_1 = __importDefault(require("node:stream"));
|
|
13
|
+
/* external dependencies */
|
|
12
14
|
const ws_1 = __importDefault(require("ws"));
|
|
13
15
|
const reconnecting_websocket_1 = __importDefault(require("@opensumi/reconnecting-websocket"));
|
|
16
|
+
/* internal dependencies */
|
|
14
17
|
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
18
|
+
/* SpeechFlow node for Websocket networking */
|
|
15
19
|
class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
|
|
20
|
+
/* declare official node name */
|
|
21
|
+
static name = "websocket";
|
|
22
|
+
/* internal state */
|
|
16
23
|
server = null;
|
|
17
24
|
client = null;
|
|
25
|
+
/* construct node */
|
|
18
26
|
constructor(id, opts, args) {
|
|
19
27
|
super(id, opts, args);
|
|
28
|
+
/* declare node configuration parameters */
|
|
20
29
|
this.configure({
|
|
21
30
|
listen: { type: "string", val: "", match: /^(?:|ws:\/\/(.+?):(\d+))$/ },
|
|
22
31
|
connect: { type: "string", val: "", match: /^(?:|ws:\/\/(.+?):(\d+)(?:\/.*)?)$/ },
|
|
23
32
|
type: { type: "string", val: "text", match: /^(?:audio|text)$/ }
|
|
24
33
|
});
|
|
34
|
+
/* sanity check usage */
|
|
35
|
+
if (this.params.listen !== "" && this.params.connect !== "")
|
|
36
|
+
throw new Error("Websocket node cannot listen and connect at the same time");
|
|
37
|
+
else if (this.params.listen === "" && this.params.connect === "")
|
|
38
|
+
throw new Error("Websocket node requires either listen or connect mode");
|
|
39
|
+
/* declare node input/output format */
|
|
40
|
+
if (this.params.listen !== "") {
|
|
41
|
+
this.input = "none";
|
|
42
|
+
this.output = this.params.type;
|
|
43
|
+
}
|
|
44
|
+
else if (this.params.connect !== "") {
|
|
45
|
+
this.input = this.params.type;
|
|
46
|
+
this.output = "none";
|
|
47
|
+
}
|
|
25
48
|
}
|
|
49
|
+
/* open node */
|
|
26
50
|
async open() {
|
|
27
|
-
this.input = this.params.type;
|
|
28
|
-
this.output = this.params.type;
|
|
29
51
|
if (this.params.listen !== "") {
|
|
52
|
+
/* listen locally on a Websocket port */
|
|
30
53
|
const url = new URL(this.params.listen);
|
|
31
54
|
let websocket = null;
|
|
32
55
|
const server = new ws_1.default.WebSocketServer({
|
|
@@ -49,6 +72,7 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
|
|
|
49
72
|
this.log("error", `error on URL ${this.params.listen}: ${error.message}`);
|
|
50
73
|
websocket = null;
|
|
51
74
|
});
|
|
75
|
+
const textEncoding = this.config.textEncoding;
|
|
52
76
|
this.stream = new node_stream_1.default.Duplex({
|
|
53
77
|
write(chunk, encoding, callback) {
|
|
54
78
|
const data = chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength);
|
|
@@ -66,7 +90,7 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
|
|
|
66
90
|
read(size) {
|
|
67
91
|
if (websocket !== null) {
|
|
68
92
|
websocket.once("message", (data, isBinary) => {
|
|
69
|
-
this.push(data, isBinary ? "binary" :
|
|
93
|
+
this.push(data, isBinary ? "binary" : textEncoding);
|
|
70
94
|
});
|
|
71
95
|
}
|
|
72
96
|
else
|
|
@@ -75,6 +99,7 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
|
|
|
75
99
|
});
|
|
76
100
|
}
|
|
77
101
|
else if (this.params.connect !== "") {
|
|
102
|
+
/* connect remotely to a Websocket port */
|
|
78
103
|
this.client = new reconnecting_websocket_1.default(this.params.connect, [], {
|
|
79
104
|
WebSocket: ws_1.default,
|
|
80
105
|
WebSocketOptions: {},
|
|
@@ -95,6 +120,7 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
|
|
|
95
120
|
});
|
|
96
121
|
const client = this.client;
|
|
97
122
|
client.binaryType = "arraybuffer";
|
|
123
|
+
const textEncoding = this.config.textEncoding;
|
|
98
124
|
this.stream = new node_stream_1.default.Duplex({
|
|
99
125
|
write(chunk, encoding, callback) {
|
|
100
126
|
const data = chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength);
|
|
@@ -111,7 +137,7 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
|
|
|
111
137
|
if (ev.data instanceof ArrayBuffer)
|
|
112
138
|
this.push(ev.data, "binary");
|
|
113
139
|
else
|
|
114
|
-
this.push(ev.data,
|
|
140
|
+
this.push(ev.data, textEncoding);
|
|
115
141
|
}, { once: true });
|
|
116
142
|
}
|
|
117
143
|
else
|
|
@@ -119,10 +145,10 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
|
|
|
119
145
|
}
|
|
120
146
|
});
|
|
121
147
|
}
|
|
122
|
-
else
|
|
123
|
-
throw new Error("neither listen nor connect mode requested");
|
|
124
148
|
}
|
|
149
|
+
/* close node */
|
|
125
150
|
async close() {
|
|
151
|
+
/* close Websocket server */
|
|
126
152
|
if (this.server !== null) {
|
|
127
153
|
await new Promise((resolve, reject) => {
|
|
128
154
|
this.server.close((error) => {
|
|
@@ -134,10 +160,12 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
|
|
|
134
160
|
});
|
|
135
161
|
this.server = null;
|
|
136
162
|
}
|
|
163
|
+
/* close Websocket client */
|
|
137
164
|
if (this.client !== null) {
|
|
138
165
|
this.client.close();
|
|
139
166
|
this.client = null;
|
|
140
167
|
}
|
|
168
|
+
/* close stream */
|
|
141
169
|
if (this.stream !== null) {
|
|
142
170
|
this.stream.destroy();
|
|
143
171
|
this.stream = null;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Events from "node:events";
|
|
2
|
+
import Stream from "node:stream";
|
|
3
|
+
export default class SpeechFlowNode extends Events.EventEmitter {
|
|
4
|
+
id: string;
|
|
5
|
+
private opts;
|
|
6
|
+
private args;
|
|
7
|
+
config: {
|
|
8
|
+
readonly audioChannels: 1;
|
|
9
|
+
readonly audioBitDepth: 16;
|
|
10
|
+
readonly audioLittleEndian: true;
|
|
11
|
+
readonly audioSampleRate: 48000;
|
|
12
|
+
readonly textEncoding: "utf8";
|
|
13
|
+
};
|
|
14
|
+
input: string;
|
|
15
|
+
output: string;
|
|
16
|
+
params: {
|
|
17
|
+
[id: string]: any;
|
|
18
|
+
};
|
|
19
|
+
stream: Stream.Writable | Stream.Readable | Stream.Duplex | null;
|
|
20
|
+
connectionsIn: Set<SpeechFlowNode>;
|
|
21
|
+
connectionsOut: Set<SpeechFlowNode>;
|
|
22
|
+
constructor(id: string, opts: {
|
|
23
|
+
[id: string]: any;
|
|
24
|
+
}, args: any[]);
|
|
25
|
+
configure(spec: {
|
|
26
|
+
[id: string]: {
|
|
27
|
+
type: string;
|
|
28
|
+
pos?: number;
|
|
29
|
+
val?: any;
|
|
30
|
+
match?: RegExp;
|
|
31
|
+
};
|
|
32
|
+
}): void;
|
|
33
|
+
connect(other: SpeechFlowNode): void;
|
|
34
|
+
disconnect(other: SpeechFlowNode): void;
|
|
35
|
+
log(level: string, msg: string, data?: any): void;
|
|
36
|
+
open(): Promise<void>;
|
|
37
|
+
close(): Promise<void>;
|
|
38
|
+
}
|
package/dst/speechflow-node.js
CHANGED
|
@@ -8,11 +8,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8
8
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
9
|
};
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
/* standard dependencies */
|
|
11
12
|
const node_events_1 = __importDefault(require("node:events"));
|
|
13
|
+
/* the base class for all SpeechFlow nodes */
|
|
12
14
|
class SpeechFlowNode extends node_events_1.default.EventEmitter {
|
|
13
15
|
id;
|
|
14
16
|
opts;
|
|
15
17
|
args;
|
|
18
|
+
/* general constant configuration (for reference) */
|
|
16
19
|
config = {
|
|
17
20
|
audioChannels: 1, /* audio mono channel */
|
|
18
21
|
audioBitDepth: 16, /* audio PCM 16-bit integer */
|
|
@@ -20,58 +23,73 @@ class SpeechFlowNode extends node_events_1.default.EventEmitter {
|
|
|
20
23
|
audioSampleRate: 48000, /* audio 48kHz sample rate */
|
|
21
24
|
textEncoding: "utf8" /* UTF-8 text encoding */
|
|
22
25
|
};
|
|
26
|
+
/* announced information */
|
|
23
27
|
input = "none";
|
|
24
28
|
output = "none";
|
|
25
29
|
params = {};
|
|
26
30
|
stream = null;
|
|
27
31
|
connectionsIn = new Set();
|
|
28
32
|
connectionsOut = new Set();
|
|
33
|
+
/* the default constructor */
|
|
29
34
|
constructor(id, opts, args) {
|
|
30
35
|
super();
|
|
31
36
|
this.id = id;
|
|
32
37
|
this.opts = opts;
|
|
33
38
|
this.args = args;
|
|
34
39
|
}
|
|
40
|
+
/* INTERNAL: utility function: create "params" attribute from constructor of sub-classes */
|
|
35
41
|
configure(spec) {
|
|
36
42
|
for (const name of Object.keys(spec)) {
|
|
37
43
|
if (this.opts[name] !== undefined) {
|
|
44
|
+
/* named parameter */
|
|
38
45
|
if (typeof this.opts[name] !== spec[name].type)
|
|
39
|
-
throw new Error(`invalid type of
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
throw new Error(`invalid type of named parameter "${name}" ` +
|
|
47
|
+
`(has to be ${spec[name].type})`);
|
|
48
|
+
if ("match" in spec[name]
|
|
49
|
+
&& this.opts[name].match(spec[name].match) === null)
|
|
50
|
+
throw new Error(`invalid value of named parameter "${name}" ` +
|
|
51
|
+
`(has to match ${spec[name].match})`);
|
|
42
52
|
this.params[name] = this.opts[name];
|
|
43
53
|
}
|
|
44
54
|
else if (this.opts[name] === undefined
|
|
45
55
|
&& "pos" in spec[name]
|
|
56
|
+
&& typeof spec[name].pos === "number"
|
|
46
57
|
&& spec[name].pos < this.args.length) {
|
|
58
|
+
/* positional argument */
|
|
47
59
|
if (typeof this.args[spec[name].pos] !== spec[name].type)
|
|
48
|
-
throw new Error(`invalid type of
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
throw new Error(`invalid type of positional parameter "${name}" ` +
|
|
61
|
+
`(has to be ${spec[name].type})`);
|
|
62
|
+
if ("match" in spec[name]
|
|
63
|
+
&& this.args[spec[name].pos].match(spec[name].match) === null)
|
|
64
|
+
throw new Error(`invalid value of positional parameter "${name}" ` +
|
|
65
|
+
`(has to match ${spec[name].match})`);
|
|
51
66
|
this.params[name] = this.args[spec[name].pos];
|
|
52
67
|
}
|
|
53
68
|
else if ("val" in spec[name] && spec[name].val !== undefined)
|
|
69
|
+
/* default argument */
|
|
54
70
|
this.params[name] = spec[name].val;
|
|
55
71
|
else
|
|
56
72
|
throw new Error(`required parameter "${name}" not given`);
|
|
57
73
|
}
|
|
58
74
|
}
|
|
75
|
+
/* connect node to another one */
|
|
59
76
|
connect(other) {
|
|
60
77
|
this.connectionsOut.add(other);
|
|
61
78
|
other.connectionsIn.add(this);
|
|
62
79
|
}
|
|
80
|
+
/* disconnect node from another one */
|
|
63
81
|
disconnect(other) {
|
|
64
82
|
if (!this.connectionsOut.has(other))
|
|
65
83
|
throw new Error("invalid node: not connected to this node");
|
|
66
84
|
this.connectionsOut.delete(other);
|
|
67
85
|
other.connectionsIn.delete(this);
|
|
68
86
|
}
|
|
87
|
+
/* internal log function */
|
|
69
88
|
log(level, msg, data) {
|
|
70
89
|
this.emit("log", level, msg, data);
|
|
71
90
|
}
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
async close() {
|
|
75
|
-
}
|
|
91
|
+
/* default implementation for open/close operations */
|
|
92
|
+
async open() { }
|
|
93
|
+
async close() { }
|
|
76
94
|
}
|
|
77
95
|
exports.default = SpeechFlowNode;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|