@ssml-builder-js/azure-tts-client 2.12.0 → 2.13.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/CHANGELOG.md +11 -0
- package/dist/index.d.mts +95 -1
- package/dist/index.d.ts +95 -1
- package/dist/index.js +137 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +133 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/client.ts +30 -2
- package/src/index.ts +15 -0
- package/src/safe.ts +72 -0
- package/src/synthesis.ts +102 -4
- package/src/types.ts +30 -0
- package/test/v213-pipeline.test.ts +68 -0
package/src/types.ts
CHANGED
|
@@ -5,22 +5,32 @@ export interface TtsConfig {
|
|
|
5
5
|
subscriptionKey: string;
|
|
6
6
|
region: string;
|
|
7
7
|
outputFormat?: string;
|
|
8
|
+
/** Original plain-text range represented by this synthesis request. */
|
|
9
|
+
sourceTextRange?: { start: number; end: number };
|
|
10
|
+
/** Reports completion of a chunk when using synthesizeSsmlChunks. */
|
|
11
|
+
onProgress?: (event: { currentChunk: number; totalChunks: number; percent: number }) => void;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
export interface SsmlSynthesisBoundary {
|
|
11
15
|
text: string;
|
|
12
16
|
audioOffsetMs: number;
|
|
13
17
|
durationMs: number;
|
|
18
|
+
textRange?: { start: number; end: number };
|
|
19
|
+
requestId?: string;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
export interface SsmlSynthesisViseme {
|
|
17
23
|
visemeId: number;
|
|
18
24
|
audioOffsetMs: number;
|
|
25
|
+
textRange?: { start: number; end: number };
|
|
26
|
+
requestId?: string;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
export interface SsmlSynthesisBookmark {
|
|
22
30
|
name: string;
|
|
23
31
|
audioOffsetMs: number;
|
|
32
|
+
textRange?: { start: number; end: number };
|
|
33
|
+
requestId?: string;
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
/** Audio and Azure Speech synchronization events emitted for one SSML request. */
|
|
@@ -34,6 +44,25 @@ export interface SsmlSynthesisResult {
|
|
|
34
44
|
wordBoundaries?: SsmlSynthesisBoundary[];
|
|
35
45
|
visemes?: SsmlSynthesisViseme[];
|
|
36
46
|
bookmarks?: SsmlSynthesisBookmark[];
|
|
47
|
+
/** Request identifier returned by Azure Speech, when available. */
|
|
48
|
+
requestId?: string;
|
|
49
|
+
/** Original plain-text range represented by the result. */
|
|
50
|
+
textRange?: { start: number; end: number };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface SsmlSynthesisChunk {
|
|
54
|
+
ssml: string;
|
|
55
|
+
originalTextRange?: { start: number; end: number };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SynthesizeChunksOptions {
|
|
59
|
+
onProgress?: (event: SynthesisProgressEvent) => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SynthesisProgressEvent {
|
|
63
|
+
currentChunk: number;
|
|
64
|
+
totalChunks: number;
|
|
65
|
+
percent: number;
|
|
37
66
|
}
|
|
38
67
|
|
|
39
68
|
export interface AzureTtsLogger {
|
|
@@ -51,4 +80,5 @@ export interface AzureTtsClientOptions {
|
|
|
51
80
|
endpoint?: string;
|
|
52
81
|
outputFormat?: string;
|
|
53
82
|
logger?: AzureTtsLogger;
|
|
83
|
+
onProgress?: (event: SynthesisProgressEvent) => void;
|
|
54
84
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { mergeSynthesisResults, synthesizeSsmlSafe } from "../src/index.ts";
|
|
4
|
+
|
|
5
|
+
const audio = (values: number[]): ArrayBuffer => Uint8Array.from(values).buffer;
|
|
6
|
+
|
|
7
|
+
test("mergeSynthesisResults concatenates audio and offsets synchronization events", () => {
|
|
8
|
+
const result = mergeSynthesisResults([
|
|
9
|
+
{
|
|
10
|
+
audioData: audio([1, 2]),
|
|
11
|
+
durationMs: 100,
|
|
12
|
+
boundaries: [{ text: "one", audioOffsetMs: 20, durationMs: 30, textRange: { start: 0, end: 3 }, requestId: "a" }],
|
|
13
|
+
visemes: [{ visemeId: 1, audioOffsetMs: 40 }],
|
|
14
|
+
bookmarks: [{ name: "first", audioOffsetMs: 50 }],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
audioData: audio([3, 4, 5]),
|
|
18
|
+
durationMs: 250,
|
|
19
|
+
boundaries: [{ text: "two", audioOffsetMs: 10, durationMs: 20, textRange: { start: 3, end: 6 }, requestId: "b" }],
|
|
20
|
+
visemes: [{ visemeId: 2, audioOffsetMs: 15 }],
|
|
21
|
+
bookmarks: [{ name: "second", audioOffsetMs: 25 }],
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
assert.deepEqual([...new Uint8Array(result.audioData)], [1, 2, 3, 4, 5]);
|
|
26
|
+
assert.equal(result.durationMs, 350);
|
|
27
|
+
assert.deepEqual(
|
|
28
|
+
result.boundaries?.map(({ audioOffsetMs }) => audioOffsetMs),
|
|
29
|
+
[20, 110],
|
|
30
|
+
);
|
|
31
|
+
assert.deepEqual(
|
|
32
|
+
result.visemes?.map(({ audioOffsetMs }) => audioOffsetMs),
|
|
33
|
+
[40, 115],
|
|
34
|
+
);
|
|
35
|
+
assert.deepEqual(
|
|
36
|
+
result.bookmarks?.map(({ audioOffsetMs }) => audioOffsetMs),
|
|
37
|
+
[50, 125],
|
|
38
|
+
);
|
|
39
|
+
assert.deepEqual(result.boundaries?.[1]?.textRange, { start: 3, end: 6 });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("synthesizeSsmlSafe blocks invalid SSML without calling the client", async () => {
|
|
43
|
+
let calls = 0;
|
|
44
|
+
const result = await synthesizeSsmlSafe(
|
|
45
|
+
{
|
|
46
|
+
synthesizeSsml: async () => {
|
|
47
|
+
calls += 1;
|
|
48
|
+
return { audioData: new ArrayBuffer(0), durationMs: 0 };
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
"<speak>",
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
assert.equal(result.ok, false);
|
|
55
|
+
assert.equal(result.status, "validation-error");
|
|
56
|
+
assert.equal(calls, 0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("synthesizeSsmlSafe returns a successful result for valid SSML", async () => {
|
|
60
|
+
const expected = { audioData: audio([1]), durationMs: 10 };
|
|
61
|
+
const result = await synthesizeSsmlSafe(
|
|
62
|
+
{ synthesizeSsml: async () => expected },
|
|
63
|
+
'<speak version="1.0" xml:lang="en-US"><voice name="en-US-JennyNeural">Hello</voice></speak>',
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
assert.equal(result.ok, true);
|
|
67
|
+
if (result.ok) assert.equal(result.value, expected);
|
|
68
|
+
});
|