@drakulavich/kesha-voice-kit 1.6.1 → 1.7.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/README.md +16 -0
- package/SKILL.md +2 -0
- package/package.json +2 -2
- package/src/cli/say.ts +7 -0
- package/src/say.ts +21 -3
package/README.md
CHANGED
|
@@ -69,6 +69,22 @@ kesha say "Hello, world" > hello.wav
|
|
|
69
69
|
kesha say "Привет, мир" > privet.wav # auto-routes (Milena on darwin, ru-vosk-m02 elsewhere)
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
**Russian abbreviations** (`ru-vosk-*` voices):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Auto-detect on by default — ФСБ reads as "эф-эс-бэ"
|
|
76
|
+
kesha say --voice ru-vosk-m02 'ФСБ объявила решение.'
|
|
77
|
+
|
|
78
|
+
# Force a literal reading (Vosk reads as "фсб")
|
|
79
|
+
kesha say --voice ru-vosk-m02 --no-expand-abbrev 'ФСБ.'
|
|
80
|
+
|
|
81
|
+
# Explicit SSML control (overrides the rule and the stop-list)
|
|
82
|
+
kesha say --voice ru-vosk-m02 --ssml \
|
|
83
|
+
'<speak><say-as interpret-as="characters">КОТ</say-as></speak>'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Detection rule: auto-expand fires when the token cannot be pronounced as a natural Russian syllable — length ≤ 2 (ИП, ЕС), 0 vowels (ФСБ, СНГ), 2+ consecutive vowels (ОАЭ), or 2+ consecutive consonants (США, ЦСКА). Tokens with strict CVC/CVCV alternation pass through (ВОЗ → "воз", НАТО → "нато", ОПЕК → "опек"). Small stop-list for common short words (ОН, МЫ, ВЫ, КАК, ЧТО, …) and Ъ/Ь-containing tokens are always skipped. See [#232](https://github.com/drakulavich/kesha-voice-kit/issues/232).
|
|
87
|
+
|
|
72
88
|
macOS system voices, SSML, voice listing, and the full voice catalogue: [docs/tts.md](docs/tts.md).
|
|
73
89
|
|
|
74
90
|
## Performance
|
package/SKILL.md
CHANGED
|
@@ -74,6 +74,8 @@ kesha say "Hi" --format ogg-opus --bitrate 16000 --out tiny.ogg # tin
|
|
|
74
74
|
|
|
75
75
|
Format is also inferred from `--out` extension (`.ogg` / `.opus` / `.oga` → OGG/Opus). `--bitrate` (6 000–510 000 bps) and `--sample-rate` (8 000 / 12 000 / 16 000 / 24 000 / 48 000 Hz) tune the encoder.
|
|
76
76
|
|
|
77
|
+
**Russian abbreviation handling** (`ru-vosk-*` only): all-uppercase Cyrillic tokens (length 2–5) auto-expand letter-by-letter when the token cannot be pronounced as a natural Russian syllable — `ФСБ объявила` → `эф-эс-бэ объявила`. Tokens with strict CVC/CVCV alternation pass through (ВОЗ → "воз", НАТО → "нато"). Disable per call with `--no-expand-abbrev`. Override per-token via SSML `<say-as interpret-as="characters">…</say-as>` (always wins, even with `--no-expand-abbrev`). Stop-list for common short words (ОН, МЫ, ВЫ, КАК, ЧТО) prevents false positives. Closes [#232](https://github.com/drakulavich/kesha-voice-kit/issues/232).
|
|
78
|
+
|
|
77
79
|
## Language detection standalone
|
|
78
80
|
|
|
79
81
|
`kesha --json audio.ogg` includes both audio-based (`audioLanguage`) and text-based (`textLanguage`) detection. Use audio detection to identify the language before running language-specific logic.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drakulavich/kesha-voice-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Open-source voice toolkit. Speech-to-text (25 langs, ~19x faster than Whisper on Apple Silicon, ONNX fallback on CPU), text-to-speech (Kokoro + Vosk-TTS + ~180 macOS system voices, SSML), voice-activity detection, language detection (107 langs). Rust engine, OpenClaw skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
]
|
|
31
31
|
},
|
|
32
32
|
"keshaEngine": {
|
|
33
|
-
"version": "1.
|
|
33
|
+
"version": "1.7.0"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"test": "bun test",
|
package/src/cli/say.ts
CHANGED
|
@@ -82,6 +82,12 @@ export const sayCommand = defineCommand({
|
|
|
82
82
|
description:
|
|
83
83
|
"Opus encoder sample rate (8000/12000/16000/24000/48000). Only with --format ogg-opus.",
|
|
84
84
|
},
|
|
85
|
+
"no-expand-abbrev": {
|
|
86
|
+
type: "boolean",
|
|
87
|
+
description:
|
|
88
|
+
"Disable Russian acronym auto-expansion (ВОЗ → 'вэ о зэ') for ru-vosk-* voices. " +
|
|
89
|
+
"<say-as interpret-as='characters'> still works. No effect for non-ru-vosk voices.",
|
|
90
|
+
},
|
|
85
91
|
verbose: {
|
|
86
92
|
type: "boolean",
|
|
87
93
|
description: "Log TTS synthesis time to stderr",
|
|
@@ -148,6 +154,7 @@ export const sayCommand = defineCommand({
|
|
|
148
154
|
format,
|
|
149
155
|
bitrate: args.bitrate ? Number(args.bitrate) : undefined,
|
|
150
156
|
sampleRate: args["sample-rate"] ? Number(args["sample-rate"]) : undefined,
|
|
157
|
+
noExpandAbbrev: Boolean(args["no-expand-abbrev"]),
|
|
151
158
|
};
|
|
152
159
|
|
|
153
160
|
try {
|
package/src/say.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getEngineBinPath, isEngineInstalled } from "./engine";
|
|
1
|
+
import { getEngineBinPath, isEngineInstalled, getEngineCapabilities, type EngineCapabilities } from "./engine";
|
|
2
2
|
import { log } from "./log";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -38,10 +38,17 @@ export interface SayOptions {
|
|
|
38
38
|
* Must be one of 8000, 12000, 16000, 24000, 48000. Default 24000.
|
|
39
39
|
*/
|
|
40
40
|
sampleRate?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Disable Russian acronym auto-expansion for `ru-vosk-*` voices.
|
|
43
|
+
* When true, passes `--no-expand-abbrev` to the engine (requires engine
|
|
44
|
+
* capability `tts.ru_acronym_expansion`; silently dropped for older engines).
|
|
45
|
+
* `<say-as interpret-as="characters">` still works regardless of this flag.
|
|
46
|
+
*/
|
|
47
|
+
noExpandAbbrev?: boolean;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
/** Build the argv passed to `kesha-engine say` (pure, unit-testable). */
|
|
44
|
-
export function buildSayArgs(o: SayOptions): string[] {
|
|
51
|
+
export function buildSayArgs(o: SayOptions, capabilities?: EngineCapabilities | null): string[] {
|
|
45
52
|
const args: string[] = ["say"];
|
|
46
53
|
if (o.voice) args.push("--voice", o.voice);
|
|
47
54
|
if (o.lang) args.push("--lang", o.lang);
|
|
@@ -51,6 +58,16 @@ export function buildSayArgs(o: SayOptions): string[] {
|
|
|
51
58
|
if (o.format) args.push("--format", o.format);
|
|
52
59
|
if (o.bitrate !== undefined) args.push("--bitrate", String(o.bitrate));
|
|
53
60
|
if (o.sampleRate !== undefined) args.push("--sample-rate", String(o.sampleRate));
|
|
61
|
+
if (o.noExpandAbbrev) {
|
|
62
|
+
const supportsRuExpand = capabilities?.features?.includes("tts.ru_acronym_expansion") ?? false;
|
|
63
|
+
if (supportsRuExpand) {
|
|
64
|
+
args.push("--no-expand-abbrev");
|
|
65
|
+
} else {
|
|
66
|
+
log.debug(
|
|
67
|
+
"kesha-engine does not advertise tts.ru_acronym_expansion; dropping --no-expand-abbrev",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
54
71
|
if (o.text !== undefined && o.text.length > 0) args.push(o.text);
|
|
55
72
|
return args;
|
|
56
73
|
}
|
|
@@ -78,7 +95,8 @@ export async function say(opts: SayOptions): Promise<Uint8Array> {
|
|
|
78
95
|
"",
|
|
79
96
|
);
|
|
80
97
|
}
|
|
81
|
-
const
|
|
98
|
+
const capabilities = opts.noExpandAbbrev ? await getEngineCapabilities() : null;
|
|
99
|
+
const args = buildSayArgs({ ...opts, text: undefined }, capabilities);
|
|
82
100
|
const startedAt = performance.now();
|
|
83
101
|
log.debug(`spawn ${getEngineBinPath()} ${args.join(" ")} (text: ${opts.text?.length ?? 0} chars)`);
|
|
84
102
|
const proc = Bun.spawn([getEngineBinPath(), ...args], {
|