speechready 0.1.0 → 0.2.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 +11 -0
- package/dist/chunk.d.ts +2 -6
- package/dist/chunk.js +23 -1
- package/dist/integrations/livekit.d.ts +19 -0
- package/dist/integrations/livekit.js +35 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +26 -6
package/README.md
CHANGED
|
@@ -57,6 +57,17 @@ for (const token of llmStream) {
|
|
|
57
57
|
for (const sentence of seg.flush()) speak(sentence);
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
For LiveKit Agents (Node), the same thing as a `ttsTextTransforms` entry; sentences are released as they are confirmed, and a fenced code block is handled as one piece:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import { textTransform } from "speechready/livekit";
|
|
64
|
+
|
|
65
|
+
const session = new voice.AgentSession({
|
|
66
|
+
...,
|
|
67
|
+
ttsTextTransforms: [textTransform({ provider: "elevenlabs", model: "eleven_turbo_v2_5" })],
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
60
71
|
Pronunciation lexicon, one file for every vendor:
|
|
61
72
|
|
|
62
73
|
```js
|
package/dist/chunk.d.ts
CHANGED
|
@@ -11,16 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
/** Split text into sentences. Newlines are hard boundaries. Empty sentences are dropped. */
|
|
13
13
|
export declare function splitSentences(text: string, locale?: string): string[];
|
|
14
|
-
/** Streaming sentence segmenter for LLM token feeds.
|
|
15
|
-
*
|
|
16
|
-
* `push(text)` returns the sentences that are now complete; `flush()` returns
|
|
17
|
-
* whatever is left. A boundary is only confirmed once text after it arrives,
|
|
18
|
-
* so a sentence is never cut at "Dr." or "3.14" because the stream paused there.
|
|
19
|
-
*/
|
|
20
14
|
export declare class Segmenter {
|
|
21
15
|
locale: string;
|
|
22
16
|
maxBuffer: number | null;
|
|
23
17
|
private buffer;
|
|
18
|
+
/** Lines of an open fenced code block; released whole once the fence closes. */
|
|
19
|
+
private fence;
|
|
24
20
|
constructor(init?: {
|
|
25
21
|
locale?: string;
|
|
26
22
|
maxBuffer?: number | null;
|
package/dist/chunk.js
CHANGED
|
@@ -93,10 +93,13 @@ export function splitSentences(text, locale = "en-US") {
|
|
|
93
93
|
* whatever is left. A boundary is only confirmed once text after it arrives,
|
|
94
94
|
* so a sentence is never cut at "Dr." or "3.14" because the stream paused there.
|
|
95
95
|
*/
|
|
96
|
+
const FENCE = /^\s*(```|~~~)/u;
|
|
96
97
|
export class Segmenter {
|
|
97
98
|
locale;
|
|
98
99
|
maxBuffer;
|
|
99
100
|
buffer = "";
|
|
101
|
+
/** Lines of an open fenced code block; released whole once the fence closes. */
|
|
102
|
+
fence = null;
|
|
100
103
|
constructor(init = {}) {
|
|
101
104
|
this.locale = init.locale ?? "en-US";
|
|
102
105
|
this.maxBuffer = init.maxBuffer ?? null;
|
|
@@ -110,8 +113,22 @@ export class Segmenter {
|
|
|
110
113
|
break;
|
|
111
114
|
const head = this.buffer.slice(0, nl);
|
|
112
115
|
this.buffer = this.buffer.slice(nl + 1);
|
|
113
|
-
|
|
116
|
+
if (this.fence !== null) {
|
|
117
|
+
this.fence.push(head);
|
|
118
|
+
if (FENCE.test(head)) {
|
|
119
|
+
done.push(this.fence.join("\n"));
|
|
120
|
+
this.fence = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (FENCE.test(head)) {
|
|
124
|
+
this.fence = [head];
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
done.push(...splitSentences(head, this.locale));
|
|
128
|
+
}
|
|
114
129
|
}
|
|
130
|
+
if (this.fence !== null)
|
|
131
|
+
return done.filter((s) => s); // inside a fence: nothing is sentence-split
|
|
115
132
|
const sentences = splitSentences(this.buffer, this.locale);
|
|
116
133
|
if (sentences.length > 1) {
|
|
117
134
|
done.push(...sentences.slice(0, -1));
|
|
@@ -131,6 +148,11 @@ export class Segmenter {
|
|
|
131
148
|
flush() {
|
|
132
149
|
const rest = this.buffer;
|
|
133
150
|
this.buffer = "";
|
|
151
|
+
if (this.fence !== null) {
|
|
152
|
+
const block = [...this.fence, ...(rest ? [rest] : [])].join("\n");
|
|
153
|
+
this.fence = null;
|
|
154
|
+
return [block];
|
|
155
|
+
}
|
|
134
156
|
return splitSentences(rest, this.locale);
|
|
135
157
|
}
|
|
136
158
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** speechready as a LiveKit Agents text transform.
|
|
2
|
+
*
|
|
3
|
+
* LiveKit Agents for Node passes the LLM's text stream through `ttsTextTransforms`
|
|
4
|
+
* before synthesis. Each transform is `(AsyncIterable<string>) => AsyncIterable<string>`.
|
|
5
|
+
* This one segments the stream into sentences as they are confirmed, runs each
|
|
6
|
+
* through `prepare`, and yields the result.
|
|
7
|
+
*
|
|
8
|
+
* import { textTransform } from "speechready/livekit";
|
|
9
|
+
* const session = new voice.AgentSession({
|
|
10
|
+
* ...,
|
|
11
|
+
* ttsTextTransforms: [textTransform({ provider: "elevenlabs", model: "eleven_turbo_v2_5" })],
|
|
12
|
+
* });
|
|
13
|
+
*/
|
|
14
|
+
import type { PrepareInit } from "../pipeline.js";
|
|
15
|
+
export interface TextTransformInit extends Omit<PrepareInit, "limit"> {
|
|
16
|
+
/** Release a long sentence at a clause boundary once the buffer holds this many characters. */
|
|
17
|
+
maxBuffer?: number | null;
|
|
18
|
+
}
|
|
19
|
+
export declare function textTransform(init?: TextTransformInit): (text: AsyncIterable<string>) => AsyncIterable<string>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** speechready as a LiveKit Agents text transform.
|
|
2
|
+
*
|
|
3
|
+
* LiveKit Agents for Node passes the LLM's text stream through `ttsTextTransforms`
|
|
4
|
+
* before synthesis. Each transform is `(AsyncIterable<string>) => AsyncIterable<string>`.
|
|
5
|
+
* This one segments the stream into sentences as they are confirmed, runs each
|
|
6
|
+
* through `prepare`, and yields the result.
|
|
7
|
+
*
|
|
8
|
+
* import { textTransform } from "speechready/livekit";
|
|
9
|
+
* const session = new voice.AgentSession({
|
|
10
|
+
* ...,
|
|
11
|
+
* ttsTextTransforms: [textTransform({ provider: "elevenlabs", model: "eleven_turbo_v2_5" })],
|
|
12
|
+
* });
|
|
13
|
+
*/
|
|
14
|
+
import { Segmenter } from "../chunk.js";
|
|
15
|
+
import { prepare } from "../pipeline.js";
|
|
16
|
+
export function textTransform(init = {}) {
|
|
17
|
+
const { maxBuffer = null, ...prepareInit } = init;
|
|
18
|
+
const locale = prepareInit.locale ?? "en-US";
|
|
19
|
+
const spoken = (piece) => prepare(piece, prepareInit).text;
|
|
20
|
+
return async function* transform(text) {
|
|
21
|
+
const seg = new Segmenter({ locale, maxBuffer });
|
|
22
|
+
for await (const chunk of text) {
|
|
23
|
+
for (const piece of seg.push(chunk)) {
|
|
24
|
+
const out = spoken(piece);
|
|
25
|
+
if (out)
|
|
26
|
+
yield out + " ";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const piece of seg.flush()) {
|
|
30
|
+
const out = spoken(piece);
|
|
31
|
+
if (out)
|
|
32
|
+
yield out + " ";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "0.
|
|
1
|
+
export declare const version = "0.2.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/build-profiles.mjs from package.json. Do not edit.
|
|
2
|
-
export const version = "0.
|
|
2
|
+
export const version = "0.2.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "speechready",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Prepare text for text-to-speech: clean LLM output, verbalize ambiguous tokens, compile pauses to each provider's syntax, chunk under model limits.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,18 +9,38 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./livekit": {
|
|
14
|
+
"types": "./dist/integrations/livekit.d.ts",
|
|
15
|
+
"import": "./dist/integrations/livekit.js"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"license": "MIT",
|
|
15
19
|
"author": "Berker Batur",
|
|
16
|
-
"repository": {
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/berkerbatur/speechready.git",
|
|
23
|
+
"directory": "js"
|
|
24
|
+
},
|
|
17
25
|
"homepage": "https://github.com/berkerbatur/speechready",
|
|
18
|
-
"keywords": [
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
"keywords": [
|
|
27
|
+
"tts",
|
|
28
|
+
"text-to-speech",
|
|
29
|
+
"text-normalization",
|
|
30
|
+
"ssml",
|
|
31
|
+
"voice-agents"
|
|
32
|
+
],
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20"
|
|
40
|
+
},
|
|
21
41
|
"scripts": {
|
|
22
42
|
"build": "node scripts/build-profiles.mjs && tsc -p tsconfig.json",
|
|
23
|
-
"test": "npm run build && node --test test/spec.test.mjs test/package.test.mjs",
|
|
43
|
+
"test": "npm run build && node --test test/spec.test.mjs test/package.test.mjs test/livekit.test.mjs",
|
|
24
44
|
"prepack": "npm run build"
|
|
25
45
|
},
|
|
26
46
|
"devDependencies": {
|