@moxxy/plugin-stt-local 0.47.1 → 0.48.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/MODEL_NOTICE.md +12 -0
- package/dist/audio.d.ts +7 -0
- package/dist/audio.d.ts.map +1 -1
- package/dist/audio.js +45 -0
- package/dist/audio.js.map +1 -1
- package/dist/host-protocol.d.ts +5 -3
- package/dist/host-protocol.d.ts.map +1 -1
- package/dist/host-protocol.js +20 -9
- package/dist/host-protocol.js.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -6
- package/dist/index.js.map +1 -1
- package/dist/local-stt.d.ts +23 -12
- package/dist/local-stt.d.ts.map +1 -1
- package/dist/local-stt.js +76 -29
- package/dist/local-stt.js.map +1 -1
- package/dist/models.d.ts +34 -20
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +53 -12
- package/dist/models.js.map +1 -1
- package/package.json +7 -5
- package/src/audio.test.ts +35 -0
- package/src/audio.ts +46 -0
- package/src/host-protocol.test.ts +21 -0
- package/src/host-protocol.ts +26 -12
- package/src/index.ts +23 -13
- package/src/live.test.ts +12 -1
- package/src/local-stt.test.ts +128 -9
- package/src/local-stt.ts +90 -39
- package/src/models.test.ts +41 -4
- package/src/models.ts +73 -22
package/dist/local-stt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-stt.js","sourceRoot":"","sources":["../src/local-stt.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"local-stt.js","sourceRoot":"","sources":["../src/local-stt.ts"],"names":[],"mappings":"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"}
|
package/dist/models.d.ts
CHANGED
|
@@ -1,42 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The pinned
|
|
2
|
+
* The pinned local speech model catalog.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Whisper large-v3 turbo is the multilingual sherpa-onnx int8 export. Its three
|
|
5
|
+
* files download as they are from Hugging Face, pinned to a fixed revision and
|
|
6
|
+
* checked against their size and sha256 — byte for byte the files the project's
|
|
7
|
+
* `asr-models` GitHub release packs into a `.tar.bz2` (verified 2026-10-24), but
|
|
8
|
+
* with no archive to unpack: unpacking it in JavaScript took about 67 s against
|
|
9
|
+
* 9 s for the download. Multilingual (not an `.en` export), so Polish and every
|
|
10
|
+
* other Whisper language work.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
* language
|
|
12
|
+
* Parakeet TDT 0.6B v3 is the sherpa-onnx int8 export of NVIDIA's transducer
|
|
13
|
+
* (CC-BY-4.0), pinned the same way. It works out the spoken language itself, in
|
|
14
|
+
* 25 languages, and takes no language hint.
|
|
13
15
|
*/
|
|
14
16
|
import type { ModelFile } from '@moxxy/model-fetch/files';
|
|
15
|
-
export type
|
|
16
|
-
|
|
17
|
+
export type LocalSttModelId = 'turbo' | 'parakeet';
|
|
18
|
+
/** The transcriber that reads Whisper turbo. */
|
|
19
|
+
export declare const LOCAL_WHISPER_TRANSCRIBER_NAME = "local-whisper";
|
|
20
|
+
/** The transcriber that reads Parakeet. */
|
|
21
|
+
export declare const LOCAL_PARAKEET_TRANSCRIBER_NAME = "local-parakeet";
|
|
22
|
+
export interface LocalSttModelEntry {
|
|
17
23
|
/** Stable model id (also the `model` config value and its directory name). */
|
|
18
|
-
readonly id:
|
|
24
|
+
readonly id: LocalSttModelId;
|
|
19
25
|
/** Human label for UI surfaces / the first-use notice. */
|
|
20
26
|
readonly label: string;
|
|
27
|
+
/** The registered transcriber that reads this model. */
|
|
28
|
+
readonly transcriber: string;
|
|
21
29
|
readonly encoder: ModelFile;
|
|
22
30
|
readonly decoder: ModelFile;
|
|
31
|
+
/** Only a transducer has one; it is what tells the sidecar which engine to load. */
|
|
32
|
+
readonly joiner?: ModelFile;
|
|
23
33
|
readonly tokens: ModelFile;
|
|
34
|
+
/** Longer audio is read piece by piece, for a model whose memory grows with it. */
|
|
35
|
+
readonly maxChunkSeconds?: number;
|
|
36
|
+
/** Drop the model from memory after this long without use. */
|
|
37
|
+
readonly idleReleaseMs?: number;
|
|
24
38
|
}
|
|
25
|
-
export declare const MODEL_CATALOG: readonly
|
|
39
|
+
export declare const MODEL_CATALOG: readonly LocalSttModelEntry[];
|
|
26
40
|
/** Every file of a model, in download order. */
|
|
27
|
-
export declare function modelFiles(entry:
|
|
41
|
+
export declare function modelFiles(entry: LocalSttModelEntry): ModelFile[];
|
|
28
42
|
/** The model's download size in bytes. */
|
|
29
|
-
export declare function modelBytes(entry:
|
|
43
|
+
export declare function modelBytes(entry: LocalSttModelEntry): number;
|
|
30
44
|
/** The models root: `~/.moxxy/models/stt`, MOXXY_HOME-aware. */
|
|
31
45
|
export declare function defaultModelsDir(): string;
|
|
32
46
|
/** Where a model's files live under the models root. */
|
|
33
|
-
export declare function modelDir(entry:
|
|
34
|
-
/** The model used when the caller doesn't pick one
|
|
35
|
-
export declare const DEFAULT_MODEL_ID:
|
|
47
|
+
export declare function modelDir(entry: LocalSttModelEntry, modelsDir: string): string;
|
|
48
|
+
/** The model used when the caller doesn't pick one. */
|
|
49
|
+
export declare const DEFAULT_MODEL_ID: LocalSttModelId;
|
|
36
50
|
/** All valid model ids, for error messages and validation. */
|
|
37
51
|
export declare function modelIds(): string[];
|
|
38
|
-
export declare function findModel(id: string):
|
|
52
|
+
export declare function findModel(id: string): LocalSttModelEntry | undefined;
|
|
39
53
|
/** Resolve a model id to its catalog entry, or throw a clear, actionable error
|
|
40
54
|
* listing the valid ids. `field` names where the bad id came from. */
|
|
41
|
-
export declare function requireModel(id: string, field: string):
|
|
55
|
+
export declare function requireModel(id: string, field: string): LocalSttModelEntry;
|
|
42
56
|
//# sourceMappingURL=models.d.ts.map
|
package/dist/models.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,UAAU,CAAC;AAEnD,gDAAgD;AAChD,eAAO,MAAM,8BAA8B,kBAAkB,CAAC;AAC9D,2CAA2C;AAC3C,eAAO,MAAM,+BAA+B,mBAAmB,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,EAAE,eAAe,CAAC;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,oFAAoF;IACpF,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,8DAA8D;IAC9D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAQD,eAAO,MAAM,aAAa,EAAE,SAAS,kBAAkB,EAwDtD,CAAC;AAEF,gDAAgD;AAChD,wBAAgB,UAAU,CAAC,KAAK,EAAE,kBAAkB,GAAG,SAAS,EAAE,CAEjE;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAE5D;AAED,gEAAgE;AAChE,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wDAAwD;AACxD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,uDAAuD;AACvD,eAAO,MAAM,gBAAgB,EAAE,eAAyB,CAAC;AAEzD,8DAA8D;AAC9D,wBAAgB,QAAQ,IAAI,MAAM,EAAE,CAEnC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAEpE;AAED;uEACuE;AACvE,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAW1E"}
|
package/dist/models.js
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The pinned
|
|
2
|
+
* The pinned local speech model catalog.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Whisper large-v3 turbo is the multilingual sherpa-onnx int8 export. Its three
|
|
5
|
+
* files download as they are from Hugging Face, pinned to a fixed revision and
|
|
6
|
+
* checked against their size and sha256 — byte for byte the files the project's
|
|
7
|
+
* `asr-models` GitHub release packs into a `.tar.bz2` (verified 2026-10-24), but
|
|
8
|
+
* with no archive to unpack: unpacking it in JavaScript took about 67 s against
|
|
9
|
+
* 9 s for the download. Multilingual (not an `.en` export), so Polish and every
|
|
10
|
+
* other Whisper language work.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
* language
|
|
12
|
+
* Parakeet TDT 0.6B v3 is the sherpa-onnx int8 export of NVIDIA's transducer
|
|
13
|
+
* (CC-BY-4.0), pinned the same way. It works out the spoken language itself, in
|
|
14
|
+
* 25 languages, and takes no language hint.
|
|
13
15
|
*/
|
|
14
16
|
import path from 'node:path';
|
|
15
17
|
import { MoxxyError } from '@moxxy/sdk';
|
|
16
18
|
import { moxxyPath } from '@moxxy/sdk/server';
|
|
19
|
+
/** The transcriber that reads Whisper turbo. */
|
|
20
|
+
export const LOCAL_WHISPER_TRANSCRIBER_NAME = 'local-whisper';
|
|
21
|
+
/** The transcriber that reads Parakeet. */
|
|
22
|
+
export const LOCAL_PARAKEET_TRANSCRIBER_NAME = 'local-parakeet';
|
|
17
23
|
const TURBO_REVISION = '2ca6ff69fc878651b770880507669577ac41c2ff';
|
|
18
24
|
const TURBO_BASE = `https://huggingface.co/csukuangfj/sherpa-onnx-whisper-turbo/resolve/${TURBO_REVISION}`;
|
|
25
|
+
const PARAKEET_REVISION = '2bda32ec70b097a55adaa07d9a7173915b43cc78';
|
|
26
|
+
const PARAKEET_BASE = `https://huggingface.co/csukuangfj/sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8/resolve/${PARAKEET_REVISION}`;
|
|
19
27
|
export const MODEL_CATALOG = [
|
|
20
28
|
{
|
|
21
29
|
id: 'turbo',
|
|
22
30
|
label: 'Whisper turbo (multilingual, large-v3 turbo)',
|
|
31
|
+
transcriber: LOCAL_WHISPER_TRANSCRIBER_NAME,
|
|
23
32
|
encoder: {
|
|
24
33
|
name: 'turbo-encoder.int8.onnx',
|
|
25
34
|
url: `${TURBO_BASE}/turbo-encoder.int8.onnx`,
|
|
@@ -39,10 +48,42 @@ export const MODEL_CATALOG = [
|
|
|
39
48
|
sha256: 'b34b360dbb493e781e479794586d661700670d65564001f23024971d1f2fa126',
|
|
40
49
|
},
|
|
41
50
|
},
|
|
51
|
+
{
|
|
52
|
+
id: 'parakeet',
|
|
53
|
+
label: 'Parakeet TDT 0.6B v3',
|
|
54
|
+
transcriber: LOCAL_PARAKEET_TRANSCRIBER_NAME,
|
|
55
|
+
encoder: {
|
|
56
|
+
name: 'encoder.int8.onnx',
|
|
57
|
+
url: `${PARAKEET_BASE}/encoder.int8.onnx`,
|
|
58
|
+
bytes: 652_184_281,
|
|
59
|
+
sha256: 'acfc2b4456377e15d04f0243af540b7fe7c992f8d898d751cf134c3a55fd2247',
|
|
60
|
+
},
|
|
61
|
+
decoder: {
|
|
62
|
+
name: 'decoder.int8.onnx',
|
|
63
|
+
url: `${PARAKEET_BASE}/decoder.int8.onnx`,
|
|
64
|
+
bytes: 11_845_275,
|
|
65
|
+
sha256: '179e50c43d1a9de79c8a24149a2f9bac6eb5981823f2a2ed88d655b24248db4e',
|
|
66
|
+
},
|
|
67
|
+
joiner: {
|
|
68
|
+
name: 'joiner.int8.onnx',
|
|
69
|
+
url: `${PARAKEET_BASE}/joiner.int8.onnx`,
|
|
70
|
+
bytes: 6_355_277,
|
|
71
|
+
sha256: '3164c13fc2821009440d20fcb5fdc78bff28b4db2f8d0f0b329101719c0948b3',
|
|
72
|
+
},
|
|
73
|
+
tokens: {
|
|
74
|
+
name: 'tokens.txt',
|
|
75
|
+
url: `${PARAKEET_BASE}/tokens.txt`,
|
|
76
|
+
bytes: 93_939,
|
|
77
|
+
sha256: 'd58544679ea4bc6ac563d1f545eb7d474bd6cfa467f0a6e2c1dc1c7d37e3c35d',
|
|
78
|
+
},
|
|
79
|
+
// Measured: 5 minutes in one go took 3 GB, and the model holds 850 MB at rest.
|
|
80
|
+
maxChunkSeconds: 60,
|
|
81
|
+
idleReleaseMs: 5 * 60_000,
|
|
82
|
+
},
|
|
42
83
|
];
|
|
43
84
|
/** Every file of a model, in download order. */
|
|
44
85
|
export function modelFiles(entry) {
|
|
45
|
-
return [entry.encoder, entry.decoder, entry.tokens];
|
|
86
|
+
return [entry.encoder, entry.decoder, ...(entry.joiner ? [entry.joiner] : []), entry.tokens];
|
|
46
87
|
}
|
|
47
88
|
/** The model's download size in bytes. */
|
|
48
89
|
export function modelBytes(entry) {
|
|
@@ -56,7 +97,7 @@ export function defaultModelsDir() {
|
|
|
56
97
|
export function modelDir(entry, modelsDir) {
|
|
57
98
|
return path.join(modelsDir, entry.id);
|
|
58
99
|
}
|
|
59
|
-
/** The model used when the caller doesn't pick one
|
|
100
|
+
/** The model used when the caller doesn't pick one. */
|
|
60
101
|
export const DEFAULT_MODEL_ID = 'turbo';
|
|
61
102
|
/** All valid model ids, for error messages and validation. */
|
|
62
103
|
export function modelIds() {
|
|
@@ -72,7 +113,7 @@ export function requireModel(id, field) {
|
|
|
72
113
|
if (!entry) {
|
|
73
114
|
throw new MoxxyError({
|
|
74
115
|
code: 'CONFIG_INVALID',
|
|
75
|
-
message: `Unknown local
|
|
116
|
+
message: `Unknown local speech model ${JSON.stringify(id)} (${field}).`,
|
|
76
117
|
hint: `Valid models: ${modelIds().join(', ')}.`,
|
|
77
118
|
context: { field, model: id },
|
|
78
119
|
});
|
package/dist/models.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moxxy/plugin-stt-local",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Fully local, on-device speech-to-text Transcriber for moxxy. Runs multilingual Whisper (English + Polish) via sherpa-onnx in a forked sidecar process; the
|
|
3
|
+
"version": "0.48.0",
|
|
4
|
+
"description": "Fully local, on-device speech-to-text Transcriber for moxxy. Runs multilingual Whisper or Parakeet (English + Polish) via sherpa-onnx in a forked sidecar process; the chosen model downloads once on first use, pinned and sha256-verified. No API key, no network at transcription time. Plugs into the session TranscriberRegistry — the TUI and channel voice notes consume it transparently.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"moxxy",
|
|
7
7
|
"agent",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"offline",
|
|
12
12
|
"on-device",
|
|
13
13
|
"sherpa-onnx",
|
|
14
|
+
"parakeet",
|
|
14
15
|
"transcriber"
|
|
15
16
|
],
|
|
16
17
|
"homepage": "https://moxxy.ai",
|
|
@@ -42,7 +43,8 @@
|
|
|
42
43
|
},
|
|
43
44
|
"files": [
|
|
44
45
|
"dist",
|
|
45
|
-
"src"
|
|
46
|
+
"src",
|
|
47
|
+
"MODEL_NOTICE.md"
|
|
46
48
|
],
|
|
47
49
|
"moxxy": {
|
|
48
50
|
"plugin": {
|
|
@@ -52,8 +54,8 @@
|
|
|
52
54
|
},
|
|
53
55
|
"dependencies": {
|
|
54
56
|
"sherpa-onnx-node": "^1.13.8",
|
|
55
|
-
"@moxxy/
|
|
56
|
-
"@moxxy/
|
|
57
|
+
"@moxxy/model-fetch": "0.2.1",
|
|
58
|
+
"@moxxy/sdk": "0.48.0"
|
|
57
59
|
},
|
|
58
60
|
"devDependencies": {
|
|
59
61
|
"@types/node": "^22.20.5",
|
package/src/audio.test.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
parseWav,
|
|
7
7
|
pcm16ToFloat32,
|
|
8
8
|
resampleLinear,
|
|
9
|
+
splitAtSilence,
|
|
9
10
|
} from './audio.js';
|
|
10
11
|
|
|
11
12
|
/** Build a canonical PCM16 WAV buffer from interleaved int16 samples. */
|
|
@@ -211,3 +212,37 @@ describe('parseWav', () => {
|
|
|
211
212
|
expect(parsed.samples[1]).toBeCloseTo(0.5, 4);
|
|
212
213
|
});
|
|
213
214
|
});
|
|
215
|
+
|
|
216
|
+
describe('splitAtSilence', () => {
|
|
217
|
+
const RATE = 100;
|
|
218
|
+
/** Loud audio with quiet stretches at the given seconds. */
|
|
219
|
+
const speech = (seconds: number, quietAt: readonly number[] = []): Float32Array => {
|
|
220
|
+
const samples = new Float32Array(seconds * RATE).fill(0.5);
|
|
221
|
+
for (const at of quietAt) samples.fill(0, at * RATE, (at + 1) * RATE);
|
|
222
|
+
return samples;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
it('leaves audio under the limit whole', () => {
|
|
226
|
+
const samples = speech(50);
|
|
227
|
+
expect(splitAtSilence(samples, RATE, 60)).toEqual([samples]);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('cuts in the quiet stretch before the limit', () => {
|
|
231
|
+
const parts = splitAtSilence(speech(100, [55]), RATE, 60);
|
|
232
|
+
expect(parts).toHaveLength(2);
|
|
233
|
+
expect(parts[0]!.length).toBeGreaterThanOrEqual(55 * RATE);
|
|
234
|
+
expect(parts[0]!.length).toBeLessThanOrEqual(56 * RATE);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('cuts at the limit when nobody paused, and keeps every sample', () => {
|
|
238
|
+
const samples = speech(130);
|
|
239
|
+
const parts = splitAtSilence(samples, RATE, 60);
|
|
240
|
+
expect(parts.map((part) => part.length)).toEqual([60 * RATE, 60 * RATE, 10 * RATE]);
|
|
241
|
+
expect(parts.reduce((sum, part) => sum + part.length, 0)).toBe(samples.length);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('hands out copies, so a part sent over IPC does not drag the whole recording', () => {
|
|
245
|
+
const parts = splitAtSilence(speech(100), RATE, 60);
|
|
246
|
+
expect(parts[0]!.buffer.byteLength).toBe(parts[0]!.byteLength);
|
|
247
|
+
});
|
|
248
|
+
});
|
package/src/audio.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - `pcm16ToFloat32` int16 little-endian bytes → Float32 in [-1, 1]
|
|
8
8
|
* - `downmixToMono` interleaved N-channel Float32 → averaged mono
|
|
9
9
|
* - `resampleLinear` linear-interpolation resample between two rates
|
|
10
|
+
* - `splitAtSilence` long audio → pieces cut where the speaker paused
|
|
10
11
|
* - `parseWav` RIFF/WAVE header parse → PCM16 samples (float) + rate +
|
|
11
12
|
* channels; rejects non-PCM16 (float/compressed) WAV
|
|
12
13
|
*
|
|
@@ -218,3 +219,48 @@ function ascii4(bytes: Uint8Array, off: number): string {
|
|
|
218
219
|
function wavError(detail: string): MoxxyError {
|
|
219
220
|
return new MoxxyError({ code: 'CONFIG_INVALID', message: `Invalid WAV audio: ${detail}` });
|
|
220
221
|
}
|
|
222
|
+
|
|
223
|
+
/** Pauses are looked for this far before the limit, in frames this long. */
|
|
224
|
+
const CUT_SEARCH_SECONDS = 10;
|
|
225
|
+
const CUT_FRAME_SECONDS = 0.1;
|
|
226
|
+
|
|
227
|
+
/** The quietest moment between `from` and `to`; `to` itself when nothing is quieter. */
|
|
228
|
+
function quietestCut(samples: Float32Array, from: number, to: number, frame: number): number {
|
|
229
|
+
const energy = (end: number): number => {
|
|
230
|
+
let sum = 0;
|
|
231
|
+
for (let i = end - frame; i < end; i += 1) sum += (samples[i] ?? 0) ** 2;
|
|
232
|
+
return sum;
|
|
233
|
+
};
|
|
234
|
+
let cut = to;
|
|
235
|
+
let quietest = energy(to);
|
|
236
|
+
for (let end = to - frame; end - frame >= from; end -= frame) {
|
|
237
|
+
const current = energy(end);
|
|
238
|
+
if (current < quietest) {
|
|
239
|
+
quietest = current;
|
|
240
|
+
cut = end - Math.floor(frame / 2);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return cut;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Cut audio into pieces of at most `maxSeconds`, at the quietest moment of the
|
|
248
|
+
* last seconds before each limit, so no word is split where the speaker paused.
|
|
249
|
+
* Pieces are copies: over IPC a view would drag the whole recording with it.
|
|
250
|
+
*/
|
|
251
|
+
export function splitAtSilence(samples: Float32Array, sampleRate: number, maxSeconds: number): Float32Array[] {
|
|
252
|
+
const limit = Math.floor(maxSeconds * sampleRate);
|
|
253
|
+
if (samples.length <= limit) return [samples];
|
|
254
|
+
const search = Math.min(limit, Math.floor(CUT_SEARCH_SECONDS * sampleRate));
|
|
255
|
+
const frame = Math.max(1, Math.floor(CUT_FRAME_SECONDS * sampleRate));
|
|
256
|
+
const parts: Float32Array[] = [];
|
|
257
|
+
let start = 0;
|
|
258
|
+
while (samples.length - start > limit) {
|
|
259
|
+
const to = start + limit;
|
|
260
|
+
const end = quietestCut(samples, to - search, to, frame);
|
|
261
|
+
parts.push(samples.slice(start, end));
|
|
262
|
+
start = end;
|
|
263
|
+
}
|
|
264
|
+
parts.push(samples.slice(start));
|
|
265
|
+
return parts;
|
|
266
|
+
}
|
|
@@ -120,6 +120,27 @@ describe('createMessageHandler', () => {
|
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
+
it('configures a transducer when the request carries a joiner', async () => {
|
|
124
|
+
const s = fakeSherpa();
|
|
125
|
+
const handle = createMessageHandler(() => s.module);
|
|
126
|
+
await handle(req({ joiner: '/models/parakeet/joiner.int8.onnx' }));
|
|
127
|
+
expect(s.lastConfig).toEqual({
|
|
128
|
+
featConfig: { sampleRate: 16_000, featureDim: 80 },
|
|
129
|
+
modelConfig: {
|
|
130
|
+
transducer: {
|
|
131
|
+
encoder: '/models/base/base-encoder.onnx',
|
|
132
|
+
decoder: '/models/base/base-decoder.onnx',
|
|
133
|
+
joiner: '/models/parakeet/joiner.int8.onnx',
|
|
134
|
+
},
|
|
135
|
+
modelType: 'nemo_transducer',
|
|
136
|
+
tokens: '/models/base/base-tokens.txt',
|
|
137
|
+
numThreads: 2,
|
|
138
|
+
provider: 'cpu',
|
|
139
|
+
debug: false,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
123
144
|
it('surfaces a detected language when the native result reports one', async () => {
|
|
124
145
|
const s = fakeSherpa({ text: 'cześć', lang: 'pl' });
|
|
125
146
|
const handle = createMessageHandler(() => s.module);
|
package/src/host-protocol.ts
CHANGED
|
@@ -20,11 +20,13 @@ export interface TranscribeRequest {
|
|
|
20
20
|
readonly type: 'transcribe';
|
|
21
21
|
/** Cache key for the loaded recognizer (the absolute encoder path). */
|
|
22
22
|
readonly modelKey: string;
|
|
23
|
-
/** Absolute path to the
|
|
23
|
+
/** Absolute path to the encoder `.onnx`. */
|
|
24
24
|
readonly encoder: string;
|
|
25
|
-
/** Absolute path to the
|
|
25
|
+
/** Absolute path to the decoder `.onnx`. */
|
|
26
26
|
readonly decoder: string;
|
|
27
|
-
/** Absolute path to
|
|
27
|
+
/** Absolute path to the joiner `.onnx`: set for a transducer, absent for Whisper. */
|
|
28
|
+
readonly joiner?: string;
|
|
29
|
+
/** Absolute path to the tokens file. */
|
|
28
30
|
readonly tokens: string;
|
|
29
31
|
/** Inference thread count. */
|
|
30
32
|
readonly numThreads: number;
|
|
@@ -109,15 +111,7 @@ export function createMessageHandler(loadSherpa: LoadSherpa): HostMessageHandler
|
|
|
109
111
|
recognizer = new mod.OfflineRecognizer({
|
|
110
112
|
featConfig: { sampleRate: 16_000, featureDim: 80 },
|
|
111
113
|
modelConfig: {
|
|
112
|
-
|
|
113
|
-
encoder: req.encoder,
|
|
114
|
-
decoder: req.decoder,
|
|
115
|
-
// `language`/`task` are present in the OfflineWhisperModelConfig
|
|
116
|
-
// types but exercised by no upstream example — pass them through
|
|
117
|
-
// best-effort (empty language ⇒ Whisper auto-detects anyway).
|
|
118
|
-
language: req.language,
|
|
119
|
-
task: req.task,
|
|
120
|
-
},
|
|
114
|
+
...engineConfig(req),
|
|
121
115
|
tokens: req.tokens,
|
|
122
116
|
numThreads: req.numThreads,
|
|
123
117
|
provider: req.provider,
|
|
@@ -147,6 +141,26 @@ export function createMessageHandler(loadSherpa: LoadSherpa): HostMessageHandler
|
|
|
147
141
|
};
|
|
148
142
|
}
|
|
149
143
|
|
|
144
|
+
function engineConfig(req: HostRequest): Record<string, unknown> {
|
|
145
|
+
if (req.joiner) {
|
|
146
|
+
return {
|
|
147
|
+
transducer: { encoder: req.encoder, decoder: req.decoder, joiner: req.joiner },
|
|
148
|
+
modelType: 'nemo_transducer',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
whisper: {
|
|
153
|
+
encoder: req.encoder,
|
|
154
|
+
decoder: req.decoder,
|
|
155
|
+
// `language`/`task` are present in the OfflineWhisperModelConfig
|
|
156
|
+
// types but exercised by no upstream example — pass them through
|
|
157
|
+
// best-effort (empty language ⇒ Whisper auto-detects anyway).
|
|
158
|
+
language: req.language,
|
|
159
|
+
task: req.task,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
150
164
|
function errMsg(err: unknown): string {
|
|
151
165
|
return err instanceof Error ? err.message : String(err);
|
|
152
166
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { definePlugin, defineTranscriber, type Plugin } from '@moxxy/sdk';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
createLocalParakeetTranscriber,
|
|
4
5
|
createLocalWhisperTranscriber,
|
|
6
|
+
LOCAL_PARAKEET_TRANSCRIBER_NAME,
|
|
5
7
|
LOCAL_WHISPER_TRANSCRIBER_NAME,
|
|
6
8
|
shutdownLocalStt,
|
|
7
|
-
type
|
|
9
|
+
type LocalSttOptions,
|
|
8
10
|
} from './local-stt.js';
|
|
9
11
|
|
|
10
12
|
export {
|
|
11
|
-
|
|
13
|
+
LocalSttTranscriber,
|
|
14
|
+
createLocalParakeetTranscriber,
|
|
12
15
|
createLocalWhisperTranscriber,
|
|
13
16
|
shutdownLocalStt,
|
|
17
|
+
LOCAL_PARAKEET_TRANSCRIBER_NAME,
|
|
14
18
|
LOCAL_WHISPER_TRANSCRIBER_NAME,
|
|
15
|
-
type
|
|
16
|
-
type
|
|
19
|
+
type LocalSttOptions,
|
|
20
|
+
type LocalSttModelPaths,
|
|
17
21
|
} from './local-stt.js';
|
|
18
22
|
export {
|
|
19
23
|
MODEL_CATALOG,
|
|
@@ -25,8 +29,8 @@ export {
|
|
|
25
29
|
modelBytes,
|
|
26
30
|
modelDir,
|
|
27
31
|
defaultModelsDir,
|
|
28
|
-
type
|
|
29
|
-
type
|
|
32
|
+
type LocalSttModelEntry,
|
|
33
|
+
type LocalSttModelId,
|
|
30
34
|
} from './models.js';
|
|
31
35
|
export {
|
|
32
36
|
pcm16ToFloat32,
|
|
@@ -54,14 +58,14 @@ export {
|
|
|
54
58
|
export interface BuildLocalSttPluginOptions {
|
|
55
59
|
/** Build-time defaults / test seams for the transcriber (injected
|
|
56
60
|
* `ensureModelFilesImpl`, `hostFactory`, `fetchImpl`, `modelsDir`, `log`, …). */
|
|
57
|
-
readonly defaults?:
|
|
61
|
+
readonly defaults?: LocalSttOptions;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
/**
|
|
61
|
-
* Build the @moxxy/plugin-stt-local plugin. Registers
|
|
62
|
-
* `local-whisper`, backed by sherpa-onnx
|
|
63
|
-
* sidecar. Side-effect free at load: no model is downloaded and no
|
|
64
|
-
* spawned until the first `transcribe`. Per-activation config
|
|
65
|
+
* Build the @moxxy/plugin-stt-local plugin. Registers two transcribers,
|
|
66
|
+
* `local-whisper` and `local-parakeet`, backed by sherpa-onnx models running in
|
|
67
|
+
* a forked sidecar. Side-effect free at load: no model is downloaded and no
|
|
68
|
+
* process is spawned until the first `transcribe`. Per-activation config
|
|
65
69
|
* (`session.transcribers.setActive('local-whisper', { model, language,
|
|
66
70
|
* numThreads })`) overrides the build-time defaults.
|
|
67
71
|
*
|
|
@@ -83,6 +87,12 @@ export function buildLocalSttPlugin(opts: BuildLocalSttPluginOptions = {}): Plug
|
|
|
83
87
|
createClient: (config: Record<string, unknown>) =>
|
|
84
88
|
createLocalWhisperTranscriber({ ...defaults, ...configToOptions(config) }),
|
|
85
89
|
}),
|
|
90
|
+
defineTranscriber({
|
|
91
|
+
name: LOCAL_PARAKEET_TRANSCRIBER_NAME,
|
|
92
|
+
displayName: 'Local Parakeet (offline, 25 languages)',
|
|
93
|
+
createClient: (config: Record<string, unknown>) =>
|
|
94
|
+
createLocalParakeetTranscriber({ ...defaults, ...configToOptions(config) }),
|
|
95
|
+
}),
|
|
86
96
|
],
|
|
87
97
|
// Kill every spawned sidecar when the session/runner shuts down.
|
|
88
98
|
hooks: { onShutdown: () => shutdownLocalStt() },
|
|
@@ -93,8 +103,8 @@ export function buildLocalSttPlugin(opts: BuildLocalSttPluginOptions = {}): Plug
|
|
|
93
103
|
* known string `model` / `language` / `provider` and a positive-integer
|
|
94
104
|
* `numThreads` are honored so a malformed config can't break `createClient` or
|
|
95
105
|
* route to a bad model (unknown ids are caught by `requireModel`). */
|
|
96
|
-
function configToOptions(config: Record<string, unknown>): Partial<
|
|
97
|
-
const out: { -readonly [K in keyof
|
|
106
|
+
function configToOptions(config: Record<string, unknown>): Partial<LocalSttOptions> {
|
|
107
|
+
const out: { -readonly [K in keyof LocalSttOptions]?: LocalSttOptions[K] } = {};
|
|
98
108
|
if (typeof config.model === 'string' && config.model) out.model = config.model;
|
|
99
109
|
if (typeof config.language === 'string') out.language = config.language;
|
|
100
110
|
if (typeof config.provider === 'string' && config.provider) out.provider = config.provider;
|
package/src/live.test.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
|
|
6
6
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import { createLocalWhisperTranscriber } from './local-stt.js';
|
|
8
|
+
import { createLocalParakeetTranscriber, createLocalWhisperTranscriber } from './local-stt.js';
|
|
9
9
|
import { removeDir } from '@moxxy/vitest-preset/fs';
|
|
10
10
|
|
|
11
11
|
// vitest runs the TypeScript SOURCE, so `import.meta.url` here is src/. The
|
|
@@ -78,4 +78,15 @@ describe.skipIf(!LIVE)('local STT (live, real download + native sidecar)', () =>
|
|
|
78
78
|
stt.shutdown();
|
|
79
79
|
}
|
|
80
80
|
}, 600_000);
|
|
81
|
+
|
|
82
|
+
it('downloads Parakeet and transcribes a generated WAV fixture', async () => {
|
|
83
|
+
const stt = createLocalParakeetTranscriber({ modelsDir, sidecarPath: SIDECAR_PATH });
|
|
84
|
+
try {
|
|
85
|
+
const out = await stt.transcribe(toneWav(), { mimeType: 'audio/wav' });
|
|
86
|
+
expect(typeof out.text).toBe('string');
|
|
87
|
+
expect(out.durationSec).toBeCloseTo(1, 1);
|
|
88
|
+
} finally {
|
|
89
|
+
stt.shutdown();
|
|
90
|
+
}
|
|
91
|
+
}, 600_000);
|
|
81
92
|
});
|