@drakulavich/kesha-voice-kit 1.0.0-beta.5 → 1.0.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 +29 -53
- package/SKILL.md +54 -0
- package/openclaw.plugin.json +22 -0
- package/package.json +7 -2
- package/scripts/postinstall.cjs +12 -0
- package/src/cli.ts +48 -11
- package/src/engine-install.ts +17 -6
- package/src/engine.ts +3 -1
- package/src/lib.ts +3 -0
- package/src/suggest-command.ts +21 -0
- package/src/benchmark-report.ts +0 -92
package/README.md
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo.png" alt="Kesha Voice Kit" width="200">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
[](https://www.npmjs.com/package/@drakulavich/kesha-voice-kit)
|
|
5
|
-
[](https://opensource.org/licenses/MIT)
|
|
6
|
-
[](https://bun.sh)
|
|
5
|
+
<h1 align="center">Kesha Voice Kit</h1>
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://github.com/drakulavich/kesha-voice-kit/actions/workflows/ci.yml"><img src="https://github.com/drakulavich/kesha-voice-kit/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@drakulavich/kesha-voice-kit"><img src="https://img.shields.io/npm/v/@drakulavich/kesha-voice-kit" alt="npm version"></a>
|
|
10
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
|
|
11
|
+
<a href="https://bun.sh"><img src="https://img.shields.io/badge/runtime-Bun-f9f1e1?logo=bun" alt="Bun"></a>
|
|
12
|
+
</p>
|
|
9
13
|
|
|
10
|
-
-
|
|
14
|
+
<p align="center"><b>Open-source voice toolkit.</b> Optimized for Apple Silicon (CoreML), works on any platform (ONNX fallback).<br>A collection of small, fast, open-source audio models — packaged as CLI tools and an <a href="https://github.com/openclaw/openclaw">OpenClaw</a> skill for LLM agents.</p>
|
|
15
|
+
|
|
16
|
+
- **Speech-to-text** — 25 languages, ~19x faster than Whisper on Apple Silicon, ~2.5x on CPU
|
|
11
17
|
- **Language detection** — 107 languages from audio, text language via NLLanguageRecognizer
|
|
12
18
|
- **Rust engine** — single 20MB binary, no ffmpeg, no Python, no native Node addons
|
|
13
19
|
- **OpenClaw-ready** — plug into your LLM agent as a voice processing skill
|
|
@@ -22,44 +28,17 @@ kesha audio.ogg # transcript to stdout
|
|
|
22
28
|
|
|
23
29
|
## OpenClaw Integration
|
|
24
30
|
|
|
25
|
-
Kesha Voice Kit
|
|
31
|
+
Kesha Voice Kit ships as a plugin for [OpenClaw](https://github.com/openclaw/openclaw) — give your LLM agent ears. No API keys, everything runs locally on your machine.
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
**Install the plugin** (requires the `kesha` CLI — see [Quick Start](#quick-start)):
|
|
28
34
|
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
"tools": {
|
|
32
|
-
"media": {
|
|
33
|
-
"audio": {
|
|
34
|
-
"enabled": true,
|
|
35
|
-
"models": [
|
|
36
|
-
{
|
|
37
|
-
"type": "cli",
|
|
38
|
-
"command": "kesha",
|
|
39
|
-
"args": ["{{MediaPath}}"],
|
|
40
|
-
"timeoutSeconds": 120
|
|
41
|
-
}
|
|
42
|
-
],
|
|
43
|
-
"echoTranscript": false
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
35
|
+
```bash
|
|
36
|
+
openclaw plugins install @drakulavich/kesha-voice-kit
|
|
48
37
|
```
|
|
49
38
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
### Language-aware processing
|
|
53
|
-
|
|
54
|
-
```json
|
|
55
|
-
{
|
|
56
|
-
"type": "cli",
|
|
57
|
-
"command": "kesha",
|
|
58
|
-
"args": ["--json", "{{MediaPath}}"]
|
|
59
|
-
}
|
|
60
|
-
```
|
|
39
|
+
OpenClaw fetches the package from npm, auto-detects [`openclaw.plugin.json`](./openclaw.plugin.json), and wires `kesha --json {{MediaPath}}` as the audio transcription backend under `tools.media.audio`. Manage it afterwards with `openclaw plugins list`, `openclaw plugins disable kesha-voice-kit`, or `openclaw plugins uninstall kesha-voice-kit`.
|
|
61
40
|
|
|
62
|
-
|
|
41
|
+
Your agent receives a voice message in Telegram/WhatsApp/Slack. Kesha transcribes it locally, detects the language, and returns structured JSON:
|
|
63
42
|
|
|
64
43
|
```json
|
|
65
44
|
[{
|
|
@@ -70,6 +49,8 @@ JSON output includes detected language — your agent knows what language the us
|
|
|
70
49
|
}]
|
|
71
50
|
```
|
|
72
51
|
|
|
52
|
+
The agent knows what was said and in what language — and can respond accordingly.
|
|
53
|
+
|
|
73
54
|
## CLI Tools
|
|
74
55
|
|
|
75
56
|
```bash
|
|
@@ -91,7 +72,7 @@ Kesha Voice Kit bundles open-source models optimized for on-device inference:
|
|
|
91
72
|
|
|
92
73
|
| Model | Task | Size | Source |
|
|
93
74
|
|---|---|---|---|
|
|
94
|
-
| NVIDIA Parakeet TDT 0.6B v3 | Speech-to-text | ~
|
|
75
|
+
| NVIDIA Parakeet TDT 0.6B v3 | Speech-to-text | ~2.5GB | [HuggingFace](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3) |
|
|
95
76
|
| SpeechBrain ECAPA-TDNN | Audio language detection | ~86MB | [HuggingFace](https://huggingface.co/speechbrain/lang-id-voxlingua107-ecapa) |
|
|
96
77
|
| Apple NLLanguageRecognizer | Text language detection | built-in | macOS system framework |
|
|
97
78
|
|
|
@@ -99,20 +80,16 @@ All models run through `kesha-engine` — a Rust binary using [FluidAudio](https
|
|
|
99
80
|
|
|
100
81
|
## Performance
|
|
101
82
|
|
|
102
|
-
> **~
|
|
83
|
+
> **~19x faster than Whisper** on Apple Silicon, **~2.5x faster** on CPU
|
|
103
84
|
|
|
104
|
-
|
|
105
|
-
<summary>MacBook Pro M3 Pro — 10 Russian voice messages</summary>
|
|
85
|
+
Compared against Whisper `large-v3-turbo` — all engines auto-detect language.
|
|
106
86
|
|
|
107
|
-
|
|
108
|
-
faster-whisper (CPU): 35.3s ██████████████████████████████████████
|
|
109
|
-
Kesha (CoreML): 1.9s ██
|
|
110
|
-
```
|
|
87
|
+

|
|
111
88
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
89
|
+
<details>
|
|
90
|
+
<summary>Full results with per-file breakdown</summary>
|
|
91
|
+
|
|
92
|
+
See [BENCHMARK.md](BENCHMARK.md) — includes Russian (real voice messages) and English transcription results with all four engines.
|
|
116
93
|
|
|
117
94
|
</details>
|
|
118
95
|
|
|
@@ -157,7 +134,6 @@ const text = await transcribe("audio.ogg"); // transcribe
|
|
|
157
134
|
|
|
158
135
|
- [Bun](https://bun.sh) >= 1.3
|
|
159
136
|
- macOS arm64, Linux x64, or Windows x64
|
|
160
|
-
- ~200MB disk (engine + models)
|
|
161
137
|
|
|
162
138
|
## Contributing
|
|
163
139
|
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Kesha Voice Kit
|
|
2
|
+
|
|
3
|
+
Local speech-to-text for voice messages. Runs entirely on your machine — no API keys, no cloud.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
When a voice message arrives (Telegram, WhatsApp, Slack), Kesha transcribes it and returns JSON with the transcript and detected language:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
[{
|
|
11
|
+
"file": "voice.ogg",
|
|
12
|
+
"text": "Привет, как дела?",
|
|
13
|
+
"lang": "ru",
|
|
14
|
+
"textLanguage": { "code": "ru", "confidence": 0.99 }
|
|
15
|
+
}]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Use the `lang` field to detect which language the user spoke and respond accordingly.
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
After installing the plugin, run once to download the engine and models:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
kesha install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Plain text mode
|
|
29
|
+
|
|
30
|
+
If you don't need language detection, switch to plain text output in your config:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"type": "cli",
|
|
35
|
+
"command": "kesha",
|
|
36
|
+
"args": ["{{MediaPath}}"]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This returns just the transcript text without JSON wrapping.
|
|
41
|
+
|
|
42
|
+
## Available flags
|
|
43
|
+
|
|
44
|
+
- `--json` — JSON output with language detection (default in this plugin)
|
|
45
|
+
- `--verbose` — show language detection details (audio + text language with confidence)
|
|
46
|
+
- `--lang <code>` — warn if detected language differs from expected (e.g. `--lang en`)
|
|
47
|
+
|
|
48
|
+
## Supported languages
|
|
49
|
+
|
|
50
|
+
Speech-to-text: Bulgarian, Croatian, Czech, Danish, Dutch, English, Estonian, Finnish, French, German, Greek, Hungarian, Italian, Latvian, Lithuanian, Maltese, Polish, Portuguese, Romanian, Russian, Slovak, Slovenian, Spanish, Swedish, Ukrainian.
|
|
51
|
+
|
|
52
|
+
## Performance
|
|
53
|
+
|
|
54
|
+
~19x faster than Whisper on Apple Silicon (CoreML), ~2.5x faster on CPU (ONNX).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kesha-voice-kit",
|
|
3
|
+
"displayName": "Kesha Voice Kit",
|
|
4
|
+
"description": "Local speech-to-text for voice messages. 25 languages, ~19x faster than Whisper on Apple Silicon. Drop-in replacement for openai-whisper.",
|
|
5
|
+
"configSchema": {},
|
|
6
|
+
"configPatch": {
|
|
7
|
+
"tools": {
|
|
8
|
+
"media": {
|
|
9
|
+
"audio": {
|
|
10
|
+
"enabled": true,
|
|
11
|
+
"models": [
|
|
12
|
+
{
|
|
13
|
+
"type": "cli",
|
|
14
|
+
"command": "kesha",
|
|
15
|
+
"args": ["--json", "{{MediaPath}}"]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drakulavich/kesha-voice-kit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Open-source voice toolkit for Apple Silicon. Speech-to-text, language detection. 25 languages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,13 +16,17 @@
|
|
|
16
16
|
"src/",
|
|
17
17
|
"package.json",
|
|
18
18
|
"tsconfig.json",
|
|
19
|
+
"openclaw.plugin.json",
|
|
20
|
+
"SKILL.md",
|
|
21
|
+
"scripts/postinstall.cjs",
|
|
19
22
|
"LICENSE",
|
|
20
23
|
"README.md"
|
|
21
24
|
],
|
|
22
25
|
"scripts": {
|
|
23
26
|
"test": "bun test",
|
|
24
27
|
"test:unit": "bun test tests/unit/",
|
|
25
|
-
"test:integration": "bun test tests/integration/"
|
|
28
|
+
"test:integration": "bun test tests/integration/",
|
|
29
|
+
"postinstall": "node scripts/postinstall.cjs"
|
|
26
30
|
},
|
|
27
31
|
"keywords": [
|
|
28
32
|
"asr",
|
|
@@ -56,6 +60,7 @@
|
|
|
56
60
|
},
|
|
57
61
|
"dependencies": {
|
|
58
62
|
"citty": "^0.2.2",
|
|
63
|
+
"fastest-levenshtein": "^1.0.16",
|
|
59
64
|
"picocolors": "^1.1.1",
|
|
60
65
|
"tinyld": "^1.3.4"
|
|
61
66
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { execSync } = require("child_process");
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
execSync("bun --version", { stdio: "ignore" });
|
|
6
|
+
} catch {
|
|
7
|
+
console.warn(
|
|
8
|
+
"\n⚠️ Kesha Voice Kit requires Bun runtime.\n" +
|
|
9
|
+
" Install Bun: curl -fsSL https://bun.sh/install | bash\n" +
|
|
10
|
+
" Then run: kesha install\n"
|
|
11
|
+
);
|
|
12
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { detectAudioLanguageEngine, detectTextLanguageEngine } from "./engine";
|
|
|
8
8
|
import type { LangDetectResult } from "./engine";
|
|
9
9
|
import { log } from "./log";
|
|
10
10
|
import { showStatus } from "./status";
|
|
11
|
+
import { suggestCommand } from "./suggest-command";
|
|
11
12
|
|
|
12
13
|
export function detectLanguage(text: string): string {
|
|
13
14
|
if (!text) return "";
|
|
@@ -20,6 +21,8 @@ export function checkLanguageMismatch(expected: string | undefined, detected: st
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
interface InstallCommandArgs {
|
|
24
|
+
coreml: boolean;
|
|
25
|
+
onnx: boolean;
|
|
23
26
|
"no-cache": boolean;
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -32,9 +35,19 @@ interface MainCommandArgs {
|
|
|
32
35
|
|
|
33
36
|
const pkg = await Bun.file(new URL("../package.json", import.meta.url)).json();
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
function resolveBackendFlag(coreml: boolean, onnx: boolean): string | undefined {
|
|
39
|
+
if (coreml && onnx) {
|
|
40
|
+
log.error('Choose only one backend: "--coreml" or "--onnx".');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
if (coreml) return "coreml";
|
|
44
|
+
if (onnx) return "onnx";
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function performInstall(noCache: boolean, backend?: string) {
|
|
36
49
|
try {
|
|
37
|
-
await downloadEngine(noCache);
|
|
50
|
+
await downloadEngine(noCache, backend);
|
|
38
51
|
} catch (err: unknown) {
|
|
39
52
|
const message = err instanceof Error ? err.message : String(err);
|
|
40
53
|
log.error(message);
|
|
@@ -48,6 +61,16 @@ export const installCommand = defineCommand({
|
|
|
48
61
|
description: "Download inference engine and models",
|
|
49
62
|
},
|
|
50
63
|
args: {
|
|
64
|
+
coreml: {
|
|
65
|
+
type: "boolean",
|
|
66
|
+
description: "Force CoreML backend (macOS arm64)",
|
|
67
|
+
default: false,
|
|
68
|
+
},
|
|
69
|
+
onnx: {
|
|
70
|
+
type: "boolean",
|
|
71
|
+
description: "Force ONNX backend",
|
|
72
|
+
default: false,
|
|
73
|
+
},
|
|
51
74
|
"no-cache": {
|
|
52
75
|
type: "boolean",
|
|
53
76
|
description: "Re-download even if cached",
|
|
@@ -55,7 +78,8 @@ export const installCommand = defineCommand({
|
|
|
55
78
|
},
|
|
56
79
|
},
|
|
57
80
|
async run({ args }: { args: InstallCommandArgs }) {
|
|
58
|
-
|
|
81
|
+
const backend = resolveBackendFlag(args.coreml, args.onnx);
|
|
82
|
+
await performInstall(args["no-cache"], backend);
|
|
59
83
|
},
|
|
60
84
|
});
|
|
61
85
|
|
|
@@ -109,12 +133,15 @@ export const mainCommand = defineCommand({
|
|
|
109
133
|
|
|
110
134
|
for (const file of files) {
|
|
111
135
|
try {
|
|
136
|
+
// Run audio lang-id and transcription concurrently
|
|
137
|
+
const [audioResult, text] = await Promise.all([
|
|
138
|
+
wantsLangId ? detectAudioLanguageEngine(file) : Promise.resolve(null),
|
|
139
|
+
transcribe(file),
|
|
140
|
+
]);
|
|
141
|
+
|
|
112
142
|
let audioLanguage: LangDetectResult | undefined;
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
if (audioResult && audioResult.code) {
|
|
116
|
-
audioLanguage = audioResult;
|
|
117
|
-
}
|
|
143
|
+
if (audioResult && audioResult.code) {
|
|
144
|
+
audioLanguage = audioResult;
|
|
118
145
|
}
|
|
119
146
|
|
|
120
147
|
if (audioLanguage && args.lang && audioLanguage.confidence > 0.8) {
|
|
@@ -122,9 +149,7 @@ export const mainCommand = defineCommand({
|
|
|
122
149
|
if (mismatch) log.warn(`${file}: ${mismatch} (from audio)`);
|
|
123
150
|
}
|
|
124
151
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const tinyldLang = detectLanguage(text);
|
|
152
|
+
const tinyldLang = wantsLangId ? detectLanguage(text) : "";
|
|
128
153
|
let textLanguage: LangDetectResult | undefined;
|
|
129
154
|
|
|
130
155
|
if (wantsLangId) {
|
|
@@ -165,6 +190,8 @@ export const mainCommand = defineCommand({
|
|
|
165
190
|
},
|
|
166
191
|
});
|
|
167
192
|
|
|
193
|
+
const SUBCOMMANDS = ["install", "status"];
|
|
194
|
+
|
|
168
195
|
export async function runCli(rawArgs = process.argv.slice(2)): Promise<void> {
|
|
169
196
|
const [firstArg, ...restArgs] = rawArgs;
|
|
170
197
|
|
|
@@ -178,6 +205,16 @@ export async function runCli(rawArgs = process.argv.slice(2)): Promise<void> {
|
|
|
178
205
|
return;
|
|
179
206
|
}
|
|
180
207
|
|
|
208
|
+
// Check for unknown subcommands (non-flag, non-file-path args)
|
|
209
|
+
if (firstArg && !firstArg.startsWith("-") && !firstArg.includes(".") && !firstArg.includes("/")) {
|
|
210
|
+
const suggestion = suggestCommand(firstArg, SUBCOMMANDS);
|
|
211
|
+
if (suggestion && suggestion !== firstArg) {
|
|
212
|
+
log.error(`unknown command '${firstArg}'`);
|
|
213
|
+
log.warn(`(Did you mean ${suggestion}?)`);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
181
218
|
await runMain(mainCommand, { rawArgs });
|
|
182
219
|
}
|
|
183
220
|
|
package/src/engine-install.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname } from "path";
|
|
2
2
|
import { existsSync, mkdirSync, chmodSync } from "fs";
|
|
3
|
-
import { getEngineBinPath } from "./engine";
|
|
3
|
+
import { getEngineBinPath, getEngineCapabilities } from "./engine";
|
|
4
4
|
import { log } from "./log";
|
|
5
5
|
import { streamResponseToFile } from "./progress";
|
|
6
6
|
|
|
@@ -17,7 +17,7 @@ function getEngineBinaryName(): string {
|
|
|
17
17
|
throw new Error(`Unsupported platform: ${platform} ${arch}`);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export async function downloadEngine(noCache = false): Promise<string> {
|
|
20
|
+
export async function downloadEngine(noCache = false, backend?: string): Promise<string> {
|
|
21
21
|
const binPath = getEngineBinPath();
|
|
22
22
|
|
|
23
23
|
if (!noCache && existsSync(binPath)) {
|
|
@@ -50,18 +50,29 @@ export async function downloadEngine(noCache = false): Promise<string> {
|
|
|
50
50
|
log.success("Engine binary downloaded.");
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
if (backend) {
|
|
54
|
+
const caps = await getEngineCapabilities();
|
|
55
|
+
if (caps && caps.backend !== backend) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Requested backend "${backend}" is not available: the installed engine for this platform uses "${caps.backend}".\n Fix: omit --${backend} to use the auto-detected backend, or run on a platform that ships the "${backend}" build.`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
53
62
|
log.progress("Installing models...");
|
|
54
|
-
const
|
|
63
|
+
const installArgs = ["install", ...(noCache ? ["--no-cache"] : [])];
|
|
64
|
+
const proc = Bun.spawnSync([binPath, ...installArgs], {
|
|
55
65
|
stdout: "pipe",
|
|
56
66
|
stderr: "pipe",
|
|
57
67
|
});
|
|
58
68
|
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
const stderr = proc.stderr.toString();
|
|
70
|
+
if (stderr) {
|
|
71
|
+
process.stderr.write(stderr);
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
if (proc.exitCode !== 0) {
|
|
64
|
-
const detail =
|
|
75
|
+
const detail = stderr.trim();
|
|
65
76
|
throw new Error(detail ? `Failed to install models: ${detail}` : "Failed to install models");
|
|
66
77
|
}
|
|
67
78
|
|
package/src/engine.ts
CHANGED
|
@@ -7,8 +7,10 @@ export interface LangDetectResult {
|
|
|
7
7
|
confidence: number;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const ENGINE_BIN_PATH = join(homedir(), ".cache", "kesha", "engine", "bin", "kesha-engine");
|
|
11
|
+
|
|
10
12
|
export function getEngineBinPath(): string {
|
|
11
|
-
return
|
|
13
|
+
return ENGINE_BIN_PATH;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export function isEngineInstalled(): boolean {
|
package/src/lib.ts
CHANGED
|
@@ -5,6 +5,9 @@ import { downloadEngine } from "./engine-install";
|
|
|
5
5
|
export type { TranscribeOptions };
|
|
6
6
|
export { downloadEngine as downloadModel };
|
|
7
7
|
|
|
8
|
+
/** @deprecated Use `downloadModel` instead. */
|
|
9
|
+
export const downloadCoreML = downloadEngine;
|
|
10
|
+
|
|
8
11
|
export async function transcribe(
|
|
9
12
|
audioPath: string,
|
|
10
13
|
options: TranscribeOptions = {},
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { closest, distance } from "fastest-levenshtein";
|
|
2
|
+
|
|
3
|
+
export function suggestCommand(input: string, commands: string[]): string | null {
|
|
4
|
+
if (!input || commands.length === 0) return null;
|
|
5
|
+
|
|
6
|
+
const lowerInput = input.toLowerCase();
|
|
7
|
+
const lowerCommands = commands.map((c) => c.toLowerCase());
|
|
8
|
+
|
|
9
|
+
const exactIdx = lowerCommands.indexOf(lowerInput);
|
|
10
|
+
if (exactIdx !== -1) return commands[exactIdx];
|
|
11
|
+
|
|
12
|
+
const match = closest(lowerInput, lowerCommands);
|
|
13
|
+
const dist = distance(lowerInput, match);
|
|
14
|
+
|
|
15
|
+
// Threshold: distance <= 3 AND distance <= 40% of target length
|
|
16
|
+
const maxDist = Math.min(3, Math.ceil(match.length * 0.4));
|
|
17
|
+
if (dist > maxDist) return null;
|
|
18
|
+
|
|
19
|
+
const idx = lowerCommands.indexOf(match);
|
|
20
|
+
return commands[idx];
|
|
21
|
+
}
|
package/src/benchmark-report.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
export interface BenchmarkSystemInfo {
|
|
2
|
-
os: string;
|
|
3
|
-
arch: string;
|
|
4
|
-
chip: string;
|
|
5
|
-
ram: string;
|
|
6
|
-
backend: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface BenchmarkResult {
|
|
10
|
-
time: number;
|
|
11
|
-
text: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface BenchmarkSummary {
|
|
15
|
-
whisper_total: number;
|
|
16
|
-
parakeet_total: number;
|
|
17
|
-
speedup: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface BenchmarkReport {
|
|
21
|
-
markdown: string;
|
|
22
|
-
summary: BenchmarkSummary;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function roundToTenths(value: number): number {
|
|
26
|
-
return Math.round(value * 10) / 10;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function createBenchmarkSummary(
|
|
30
|
-
whisperResults: BenchmarkResult[],
|
|
31
|
-
parakeetResults: BenchmarkResult[],
|
|
32
|
-
): BenchmarkSummary {
|
|
33
|
-
if (whisperResults.length !== parakeetResults.length) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
`Benchmark result count mismatch: faster-whisper=${whisperResults.length}, parakeet=${parakeetResults.length}`,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const whisperTotal = roundToTenths(
|
|
40
|
-
whisperResults.reduce((total, result) => total + result.time, 0),
|
|
41
|
-
);
|
|
42
|
-
const parakeetTotal = roundToTenths(
|
|
43
|
-
parakeetResults.reduce((total, result) => total + result.time, 0),
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
whisper_total: whisperTotal,
|
|
48
|
-
parakeet_total: parakeetTotal,
|
|
49
|
-
speedup: parakeetTotal > 0 ? roundToTenths(whisperTotal / parakeetTotal) : 0,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function renderBenchmarkReport(args: {
|
|
54
|
-
date: string;
|
|
55
|
-
version: string;
|
|
56
|
-
system: BenchmarkSystemInfo;
|
|
57
|
-
whisperResults: BenchmarkResult[];
|
|
58
|
-
parakeetResults: BenchmarkResult[];
|
|
59
|
-
}): BenchmarkReport {
|
|
60
|
-
const { date, version, system, whisperResults, parakeetResults } = args;
|
|
61
|
-
const summary = createBenchmarkSummary(whisperResults, parakeetResults);
|
|
62
|
-
|
|
63
|
-
const lines: string[] = [
|
|
64
|
-
"",
|
|
65
|
-
`**Date:** ${date}`,
|
|
66
|
-
`**Version:** v${version}`,
|
|
67
|
-
`**Runner:** ${system.os} ${system.arch} (${system.chip}, ${system.ram} RAM)`,
|
|
68
|
-
`**Backend:** ${system.backend}`,
|
|
69
|
-
"",
|
|
70
|
-
`| # | faster-whisper | Kesha (${system.backend}) | faster-whisper Transcript | Kesha Transcript |`,
|
|
71
|
-
"|---|---------|----------|--------------------|---------------------|",
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
for (let i = 0; i < whisperResults.length; i++) {
|
|
75
|
-
const whisper = whisperResults[i];
|
|
76
|
-
const parakeet = parakeetResults[i];
|
|
77
|
-
lines.push(
|
|
78
|
-
`| ${i + 1} | ${whisper.time}s | ${parakeet.time}s | ${whisper.text} | ${parakeet.text} |`,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
lines.push(
|
|
83
|
-
`| **Total** | **${summary.whisper_total}s** | **${summary.parakeet_total}s** | | |`,
|
|
84
|
-
);
|
|
85
|
-
lines.push("");
|
|
86
|
-
lines.push(`**Kesha is ~${summary.speedup}x faster.**`);
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
markdown: lines.join("\n"),
|
|
90
|
-
summary,
|
|
91
|
-
};
|
|
92
|
-
}
|