@ssml-builder-js/azure-tts-client 2.11.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 +17 -0
- package/dist/index.d.mts +123 -1
- package/dist/index.d.ts +123 -1
- package/dist/index.js +177 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +172 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/client.ts +38 -2
- package/src/index.ts +25 -1
- package/src/safe.ts +72 -0
- package/src/synthesis.ts +139 -4
- package/src/types.ts +59 -0
- package/test/synthesis.test.ts +40 -1
- package/test/v213-pipeline.test.ts +68 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @ssml-builder-js/azure-tts-client
|
|
2
2
|
|
|
3
|
+
## 2.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add structured SSML chunk metadata and background-audio replication policies, merged synthesis synchronization offsets, safe preflight synthesis with custom URL validation, chunk progress reporting, source tracking, and extensible Japanese-localized Visual Editor controls.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @ssml-builder-js/ssml-core@2.13.0
|
|
13
|
+
|
|
14
|
+
## 2.12.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- Add `synthesizeSsml` and `SsmlSynthesisResult` for word boundaries, visemes, bookmarks, and duration metadata.
|
|
19
|
+
|
|
3
20
|
## 2.11.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { SsmlDiagnostic, AzureValidationOptions } from '@ssml-builder-js/ssml-core';
|
|
2
|
+
|
|
1
3
|
interface TtsConfig {
|
|
2
4
|
signal?: AbortSignal;
|
|
3
5
|
timeoutMs?: number;
|
|
@@ -5,6 +7,79 @@ interface TtsConfig {
|
|
|
5
7
|
subscriptionKey: string;
|
|
6
8
|
region: string;
|
|
7
9
|
outputFormat?: string;
|
|
10
|
+
/** Original plain-text range represented by this synthesis request. */
|
|
11
|
+
sourceTextRange?: {
|
|
12
|
+
start: number;
|
|
13
|
+
end: number;
|
|
14
|
+
};
|
|
15
|
+
/** Reports completion of a chunk when using synthesizeSsmlChunks. */
|
|
16
|
+
onProgress?: (event: {
|
|
17
|
+
currentChunk: number;
|
|
18
|
+
totalChunks: number;
|
|
19
|
+
percent: number;
|
|
20
|
+
}) => void;
|
|
21
|
+
}
|
|
22
|
+
interface SsmlSynthesisBoundary {
|
|
23
|
+
text: string;
|
|
24
|
+
audioOffsetMs: number;
|
|
25
|
+
durationMs: number;
|
|
26
|
+
textRange?: {
|
|
27
|
+
start: number;
|
|
28
|
+
end: number;
|
|
29
|
+
};
|
|
30
|
+
requestId?: string;
|
|
31
|
+
}
|
|
32
|
+
interface SsmlSynthesisViseme {
|
|
33
|
+
visemeId: number;
|
|
34
|
+
audioOffsetMs: number;
|
|
35
|
+
textRange?: {
|
|
36
|
+
start: number;
|
|
37
|
+
end: number;
|
|
38
|
+
};
|
|
39
|
+
requestId?: string;
|
|
40
|
+
}
|
|
41
|
+
interface SsmlSynthesisBookmark {
|
|
42
|
+
name: string;
|
|
43
|
+
audioOffsetMs: number;
|
|
44
|
+
textRange?: {
|
|
45
|
+
start: number;
|
|
46
|
+
end: number;
|
|
47
|
+
};
|
|
48
|
+
requestId?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Audio and Azure Speech synchronization events emitted for one SSML request. */
|
|
51
|
+
interface SsmlSynthesisResult {
|
|
52
|
+
audioData: ArrayBuffer;
|
|
53
|
+
durationMs: number;
|
|
54
|
+
boundaries?: SsmlSynthesisBoundary[];
|
|
55
|
+
/** Alias matching the Azure Speech event name. */
|
|
56
|
+
wordBoundary?: SsmlSynthesisBoundary[];
|
|
57
|
+
/** Alias for consumers that use Azure's word-boundary terminology. */
|
|
58
|
+
wordBoundaries?: SsmlSynthesisBoundary[];
|
|
59
|
+
visemes?: SsmlSynthesisViseme[];
|
|
60
|
+
bookmarks?: SsmlSynthesisBookmark[];
|
|
61
|
+
/** Request identifier returned by Azure Speech, when available. */
|
|
62
|
+
requestId?: string;
|
|
63
|
+
/** Original plain-text range represented by the result. */
|
|
64
|
+
textRange?: {
|
|
65
|
+
start: number;
|
|
66
|
+
end: number;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
interface SsmlSynthesisChunk {
|
|
70
|
+
ssml: string;
|
|
71
|
+
originalTextRange?: {
|
|
72
|
+
start: number;
|
|
73
|
+
end: number;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
interface SynthesizeChunksOptions {
|
|
77
|
+
onProgress?: (event: SynthesisProgressEvent) => void;
|
|
78
|
+
}
|
|
79
|
+
interface SynthesisProgressEvent {
|
|
80
|
+
currentChunk: number;
|
|
81
|
+
totalChunks: number;
|
|
82
|
+
percent: number;
|
|
8
83
|
}
|
|
9
84
|
interface AzureTtsLogger {
|
|
10
85
|
debug?: (...args: unknown[]) => void;
|
|
@@ -20,6 +95,7 @@ interface AzureTtsClientOptions {
|
|
|
20
95
|
endpoint?: string;
|
|
21
96
|
outputFormat?: string;
|
|
22
97
|
logger?: AzureTtsLogger;
|
|
98
|
+
onProgress?: (event: SynthesisProgressEvent) => void;
|
|
23
99
|
}
|
|
24
100
|
|
|
25
101
|
declare class AzureTtsError extends Error {
|
|
@@ -34,12 +110,58 @@ declare class AzureTtsSdkError extends AzureTtsError {
|
|
|
34
110
|
constructor(errorDetails: string);
|
|
35
111
|
}
|
|
36
112
|
|
|
113
|
+
interface SsmlValidationError {
|
|
114
|
+
readonly kind: "validation";
|
|
115
|
+
readonly message: string;
|
|
116
|
+
readonly diagnostics: readonly SsmlDiagnostic[];
|
|
117
|
+
}
|
|
118
|
+
type Result<T, E> = {
|
|
119
|
+
readonly ok: true;
|
|
120
|
+
readonly success: true;
|
|
121
|
+
readonly status: "success";
|
|
122
|
+
readonly value: T;
|
|
123
|
+
} | {
|
|
124
|
+
readonly ok: false;
|
|
125
|
+
readonly success: false;
|
|
126
|
+
readonly status: "validation-error" | "azure-api-error";
|
|
127
|
+
readonly error: E;
|
|
128
|
+
};
|
|
129
|
+
type SynthesisResult<T, E> = Result<T, E>;
|
|
130
|
+
type Success<T> = Extract<Result<T, never>, {
|
|
131
|
+
readonly ok: true;
|
|
132
|
+
}>;
|
|
133
|
+
type ValidationErrorResult = Extract<Result<never, SsmlValidationError>, {
|
|
134
|
+
readonly status: "validation-error";
|
|
135
|
+
}>;
|
|
136
|
+
type AzureApiErrorResult = Extract<Result<never, AzureTtsError>, {
|
|
137
|
+
readonly status: "azure-api-error";
|
|
138
|
+
}>;
|
|
139
|
+
type SsmlSynthesisSafeResult = Result<SsmlSynthesisResult, never> | Result<never, SsmlValidationError> | Result<never, AzureTtsError>;
|
|
140
|
+
interface SynthesizeSsmlSafeOptions extends AzureValidationOptions {
|
|
141
|
+
/** Optional nested form for callers that want to keep validation settings grouped. */
|
|
142
|
+
validation?: AzureValidationOptions;
|
|
143
|
+
}
|
|
144
|
+
interface SynthesisClient {
|
|
145
|
+
synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult>;
|
|
146
|
+
}
|
|
147
|
+
/** Validates SSML before invoking Azure and converts validation/API failures to one result shape. */
|
|
148
|
+
declare function synthesizeSsmlSafe(client: Pick<AzureTtsClient, "synthesizeSsml"> | SynthesisClient, ssml: string, options?: SynthesizeSsmlSafeOptions): Promise<SsmlSynthesisSafeResult>;
|
|
149
|
+
|
|
37
150
|
declare class AzureTtsClient {
|
|
38
151
|
#private;
|
|
39
152
|
constructor(options: AzureTtsClientOptions);
|
|
40
153
|
synthesize(ssml: string): Promise<ArrayBuffer>;
|
|
154
|
+
synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult>;
|
|
155
|
+
synthesizeChunks(chunks: readonly (SsmlSynthesisChunk | string)[], options?: SynthesizeChunksOptions): Promise<SsmlSynthesisResult>;
|
|
156
|
+
synthesizeSsmlSafe(ssml: string, options?: SynthesizeSsmlSafeOptions): Promise<SsmlSynthesisSafeResult>;
|
|
41
157
|
}
|
|
42
158
|
|
|
159
|
+
declare function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult>;
|
|
160
|
+
/** Synthesizes chunks sequentially, annotates synchronization events, and merges the results. */
|
|
161
|
+
declare function synthesizeSsmlChunks(chunks: readonly (SsmlSynthesisChunk | string)[], config: TtsConfig): Promise<SsmlSynthesisResult>;
|
|
162
|
+
/** Concatenates audio buffers and shifts all synchronization events by prior chunk durations. */
|
|
163
|
+
declare function mergeSynthesisResults(results: readonly SsmlSynthesisResult[]): SsmlSynthesisResult;
|
|
164
|
+
/** Backward-compatible audio-only synthesis helper. */
|
|
43
165
|
declare function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer>;
|
|
44
166
|
|
|
45
167
|
interface FetchAzureVoiceCatalogOptions {
|
|
@@ -67,4 +189,4 @@ interface AzureVoiceCatalog {
|
|
|
67
189
|
/** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */
|
|
68
190
|
declare function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog>;
|
|
69
191
|
|
|
70
|
-
export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type AzureVoiceCatalog, type AzureVoiceCatalogVoice, type FetchAzureVoiceCatalogOptions, type FetchedAzureVoiceCatalogMetadata, type TtsConfig, fetchAzureVoiceCatalog, synthesizeSpeech };
|
|
192
|
+
export { type AzureApiErrorResult, type SsmlValidationError as AzureSsmlValidationError, AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type AzureVoiceCatalog, type AzureVoiceCatalogVoice, type FetchAzureVoiceCatalogOptions, type FetchedAzureVoiceCatalogMetadata, type Result, type SsmlSynthesisBookmark, type SsmlSynthesisBoundary, type SsmlSynthesisChunk, type SsmlSynthesisResult, type SsmlSynthesisSafeResult, type SsmlSynthesisViseme, type Success, type SynthesisProgressEvent, type SynthesisResult, type SynthesizeChunksOptions, type SynthesizeSsmlSafeOptions, type TtsConfig, type ValidationErrorResult, fetchAzureVoiceCatalog, mergeSynthesisResults, synthesizeSpeech, synthesizeSsml, synthesizeSsmlChunks, synthesizeSsmlSafe };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { SsmlDiagnostic, AzureValidationOptions } from '@ssml-builder-js/ssml-core';
|
|
2
|
+
|
|
1
3
|
interface TtsConfig {
|
|
2
4
|
signal?: AbortSignal;
|
|
3
5
|
timeoutMs?: number;
|
|
@@ -5,6 +7,79 @@ interface TtsConfig {
|
|
|
5
7
|
subscriptionKey: string;
|
|
6
8
|
region: string;
|
|
7
9
|
outputFormat?: string;
|
|
10
|
+
/** Original plain-text range represented by this synthesis request. */
|
|
11
|
+
sourceTextRange?: {
|
|
12
|
+
start: number;
|
|
13
|
+
end: number;
|
|
14
|
+
};
|
|
15
|
+
/** Reports completion of a chunk when using synthesizeSsmlChunks. */
|
|
16
|
+
onProgress?: (event: {
|
|
17
|
+
currentChunk: number;
|
|
18
|
+
totalChunks: number;
|
|
19
|
+
percent: number;
|
|
20
|
+
}) => void;
|
|
21
|
+
}
|
|
22
|
+
interface SsmlSynthesisBoundary {
|
|
23
|
+
text: string;
|
|
24
|
+
audioOffsetMs: number;
|
|
25
|
+
durationMs: number;
|
|
26
|
+
textRange?: {
|
|
27
|
+
start: number;
|
|
28
|
+
end: number;
|
|
29
|
+
};
|
|
30
|
+
requestId?: string;
|
|
31
|
+
}
|
|
32
|
+
interface SsmlSynthesisViseme {
|
|
33
|
+
visemeId: number;
|
|
34
|
+
audioOffsetMs: number;
|
|
35
|
+
textRange?: {
|
|
36
|
+
start: number;
|
|
37
|
+
end: number;
|
|
38
|
+
};
|
|
39
|
+
requestId?: string;
|
|
40
|
+
}
|
|
41
|
+
interface SsmlSynthesisBookmark {
|
|
42
|
+
name: string;
|
|
43
|
+
audioOffsetMs: number;
|
|
44
|
+
textRange?: {
|
|
45
|
+
start: number;
|
|
46
|
+
end: number;
|
|
47
|
+
};
|
|
48
|
+
requestId?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Audio and Azure Speech synchronization events emitted for one SSML request. */
|
|
51
|
+
interface SsmlSynthesisResult {
|
|
52
|
+
audioData: ArrayBuffer;
|
|
53
|
+
durationMs: number;
|
|
54
|
+
boundaries?: SsmlSynthesisBoundary[];
|
|
55
|
+
/** Alias matching the Azure Speech event name. */
|
|
56
|
+
wordBoundary?: SsmlSynthesisBoundary[];
|
|
57
|
+
/** Alias for consumers that use Azure's word-boundary terminology. */
|
|
58
|
+
wordBoundaries?: SsmlSynthesisBoundary[];
|
|
59
|
+
visemes?: SsmlSynthesisViseme[];
|
|
60
|
+
bookmarks?: SsmlSynthesisBookmark[];
|
|
61
|
+
/** Request identifier returned by Azure Speech, when available. */
|
|
62
|
+
requestId?: string;
|
|
63
|
+
/** Original plain-text range represented by the result. */
|
|
64
|
+
textRange?: {
|
|
65
|
+
start: number;
|
|
66
|
+
end: number;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
interface SsmlSynthesisChunk {
|
|
70
|
+
ssml: string;
|
|
71
|
+
originalTextRange?: {
|
|
72
|
+
start: number;
|
|
73
|
+
end: number;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
interface SynthesizeChunksOptions {
|
|
77
|
+
onProgress?: (event: SynthesisProgressEvent) => void;
|
|
78
|
+
}
|
|
79
|
+
interface SynthesisProgressEvent {
|
|
80
|
+
currentChunk: number;
|
|
81
|
+
totalChunks: number;
|
|
82
|
+
percent: number;
|
|
8
83
|
}
|
|
9
84
|
interface AzureTtsLogger {
|
|
10
85
|
debug?: (...args: unknown[]) => void;
|
|
@@ -20,6 +95,7 @@ interface AzureTtsClientOptions {
|
|
|
20
95
|
endpoint?: string;
|
|
21
96
|
outputFormat?: string;
|
|
22
97
|
logger?: AzureTtsLogger;
|
|
98
|
+
onProgress?: (event: SynthesisProgressEvent) => void;
|
|
23
99
|
}
|
|
24
100
|
|
|
25
101
|
declare class AzureTtsError extends Error {
|
|
@@ -34,12 +110,58 @@ declare class AzureTtsSdkError extends AzureTtsError {
|
|
|
34
110
|
constructor(errorDetails: string);
|
|
35
111
|
}
|
|
36
112
|
|
|
113
|
+
interface SsmlValidationError {
|
|
114
|
+
readonly kind: "validation";
|
|
115
|
+
readonly message: string;
|
|
116
|
+
readonly diagnostics: readonly SsmlDiagnostic[];
|
|
117
|
+
}
|
|
118
|
+
type Result<T, E> = {
|
|
119
|
+
readonly ok: true;
|
|
120
|
+
readonly success: true;
|
|
121
|
+
readonly status: "success";
|
|
122
|
+
readonly value: T;
|
|
123
|
+
} | {
|
|
124
|
+
readonly ok: false;
|
|
125
|
+
readonly success: false;
|
|
126
|
+
readonly status: "validation-error" | "azure-api-error";
|
|
127
|
+
readonly error: E;
|
|
128
|
+
};
|
|
129
|
+
type SynthesisResult<T, E> = Result<T, E>;
|
|
130
|
+
type Success<T> = Extract<Result<T, never>, {
|
|
131
|
+
readonly ok: true;
|
|
132
|
+
}>;
|
|
133
|
+
type ValidationErrorResult = Extract<Result<never, SsmlValidationError>, {
|
|
134
|
+
readonly status: "validation-error";
|
|
135
|
+
}>;
|
|
136
|
+
type AzureApiErrorResult = Extract<Result<never, AzureTtsError>, {
|
|
137
|
+
readonly status: "azure-api-error";
|
|
138
|
+
}>;
|
|
139
|
+
type SsmlSynthesisSafeResult = Result<SsmlSynthesisResult, never> | Result<never, SsmlValidationError> | Result<never, AzureTtsError>;
|
|
140
|
+
interface SynthesizeSsmlSafeOptions extends AzureValidationOptions {
|
|
141
|
+
/** Optional nested form for callers that want to keep validation settings grouped. */
|
|
142
|
+
validation?: AzureValidationOptions;
|
|
143
|
+
}
|
|
144
|
+
interface SynthesisClient {
|
|
145
|
+
synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult>;
|
|
146
|
+
}
|
|
147
|
+
/** Validates SSML before invoking Azure and converts validation/API failures to one result shape. */
|
|
148
|
+
declare function synthesizeSsmlSafe(client: Pick<AzureTtsClient, "synthesizeSsml"> | SynthesisClient, ssml: string, options?: SynthesizeSsmlSafeOptions): Promise<SsmlSynthesisSafeResult>;
|
|
149
|
+
|
|
37
150
|
declare class AzureTtsClient {
|
|
38
151
|
#private;
|
|
39
152
|
constructor(options: AzureTtsClientOptions);
|
|
40
153
|
synthesize(ssml: string): Promise<ArrayBuffer>;
|
|
154
|
+
synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult>;
|
|
155
|
+
synthesizeChunks(chunks: readonly (SsmlSynthesisChunk | string)[], options?: SynthesizeChunksOptions): Promise<SsmlSynthesisResult>;
|
|
156
|
+
synthesizeSsmlSafe(ssml: string, options?: SynthesizeSsmlSafeOptions): Promise<SsmlSynthesisSafeResult>;
|
|
41
157
|
}
|
|
42
158
|
|
|
159
|
+
declare function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult>;
|
|
160
|
+
/** Synthesizes chunks sequentially, annotates synchronization events, and merges the results. */
|
|
161
|
+
declare function synthesizeSsmlChunks(chunks: readonly (SsmlSynthesisChunk | string)[], config: TtsConfig): Promise<SsmlSynthesisResult>;
|
|
162
|
+
/** Concatenates audio buffers and shifts all synchronization events by prior chunk durations. */
|
|
163
|
+
declare function mergeSynthesisResults(results: readonly SsmlSynthesisResult[]): SsmlSynthesisResult;
|
|
164
|
+
/** Backward-compatible audio-only synthesis helper. */
|
|
43
165
|
declare function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer>;
|
|
44
166
|
|
|
45
167
|
interface FetchAzureVoiceCatalogOptions {
|
|
@@ -67,4 +189,4 @@ interface AzureVoiceCatalog {
|
|
|
67
189
|
/** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */
|
|
68
190
|
declare function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog>;
|
|
69
191
|
|
|
70
|
-
export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type AzureVoiceCatalog, type AzureVoiceCatalogVoice, type FetchAzureVoiceCatalogOptions, type FetchedAzureVoiceCatalogMetadata, type TtsConfig, fetchAzureVoiceCatalog, synthesizeSpeech };
|
|
192
|
+
export { type AzureApiErrorResult, type SsmlValidationError as AzureSsmlValidationError, AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type AzureVoiceCatalog, type AzureVoiceCatalogVoice, type FetchAzureVoiceCatalogOptions, type FetchedAzureVoiceCatalogMetadata, type Result, type SsmlSynthesisBookmark, type SsmlSynthesisBoundary, type SsmlSynthesisChunk, type SsmlSynthesisResult, type SsmlSynthesisSafeResult, type SsmlSynthesisViseme, type Success, type SynthesisProgressEvent, type SynthesisResult, type SynthesizeChunksOptions, type SynthesizeSsmlSafeOptions, type TtsConfig, type ValidationErrorResult, fetchAzureVoiceCatalog, mergeSynthesisResults, synthesizeSpeech, synthesizeSsml, synthesizeSsmlChunks, synthesizeSsmlSafe };
|
package/dist/index.js
CHANGED
|
@@ -41,7 +41,11 @@ __export(index_exports, {
|
|
|
41
41
|
AzureTtsError: () => AzureTtsError,
|
|
42
42
|
AzureTtsSdkError: () => AzureTtsSdkError,
|
|
43
43
|
fetchAzureVoiceCatalog: () => fetchAzureVoiceCatalog,
|
|
44
|
-
|
|
44
|
+
mergeSynthesisResults: () => mergeSynthesisResults,
|
|
45
|
+
synthesizeSpeech: () => synthesizeSpeech,
|
|
46
|
+
synthesizeSsml: () => synthesizeSsml,
|
|
47
|
+
synthesizeSsmlChunks: () => synthesizeSsmlChunks,
|
|
48
|
+
synthesizeSsmlSafe: () => synthesizeSsmlSafe
|
|
45
49
|
});
|
|
46
50
|
module.exports = __toCommonJS(index_exports);
|
|
47
51
|
|
|
@@ -151,7 +155,8 @@ function closeSpeechResources(speechConfig, synthesizer) {
|
|
|
151
155
|
} catch {
|
|
152
156
|
}
|
|
153
157
|
}
|
|
154
|
-
|
|
158
|
+
var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
|
|
159
|
+
async function synthesizeSsml(ssml, config) {
|
|
155
160
|
if (config.signal?.aborted) {
|
|
156
161
|
throw createSpeechSdkError("Speech synthesis was cancelled.");
|
|
157
162
|
}
|
|
@@ -178,6 +183,22 @@ async function synthesizeSpeech(ssml, config) {
|
|
|
178
183
|
closeResources();
|
|
179
184
|
reject(createSpeechSdkError(error));
|
|
180
185
|
};
|
|
186
|
+
const boundaries = [];
|
|
187
|
+
const visemes = [];
|
|
188
|
+
const bookmarks = [];
|
|
189
|
+
synthesizer.wordBoundary = (_sender, event) => {
|
|
190
|
+
boundaries.push({
|
|
191
|
+
text: event.text,
|
|
192
|
+
audioOffsetMs: ticksToMilliseconds(event.audioOffset),
|
|
193
|
+
durationMs: ticksToMilliseconds(event.duration)
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
synthesizer.visemeReceived = (_sender, event) => {
|
|
197
|
+
visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
|
|
198
|
+
};
|
|
199
|
+
synthesizer.bookmarkReached = (_sender, event) => {
|
|
200
|
+
bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
|
|
201
|
+
};
|
|
181
202
|
const cb = (result) => {
|
|
182
203
|
if (settled) return;
|
|
183
204
|
const { reason, errorDetails } = result;
|
|
@@ -189,7 +210,31 @@ async function synthesizeSpeech(ssml, config) {
|
|
|
189
210
|
settled = true;
|
|
190
211
|
cleanup();
|
|
191
212
|
closeResources();
|
|
192
|
-
|
|
213
|
+
const eventDurationMs = Math.max(
|
|
214
|
+
0,
|
|
215
|
+
...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),
|
|
216
|
+
...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),
|
|
217
|
+
...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
|
|
218
|
+
);
|
|
219
|
+
const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
|
|
220
|
+
const requestId = result.resultId;
|
|
221
|
+
const addSourceMetadata = (event) => ({
|
|
222
|
+
...event,
|
|
223
|
+
...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
|
|
224
|
+
...requestId ? { requestId } : {}
|
|
225
|
+
});
|
|
226
|
+
const sourceBoundaries = boundaries.map((boundary) => addSourceMetadata(boundary));
|
|
227
|
+
const sourceVisemes = visemes.map((viseme) => addSourceMetadata(viseme));
|
|
228
|
+
const sourceBookmarks = bookmarks.map((bookmark) => addSourceMetadata(bookmark));
|
|
229
|
+
resolve({
|
|
230
|
+
audioData: result.audioData,
|
|
231
|
+
durationMs,
|
|
232
|
+
...config.sourceTextRange ? { textRange: { ...config.sourceTextRange } } : {},
|
|
233
|
+
...requestId ? { requestId } : {},
|
|
234
|
+
...sourceBoundaries.length > 0 ? { boundaries: sourceBoundaries, wordBoundary: sourceBoundaries, wordBoundaries: sourceBoundaries } : {},
|
|
235
|
+
...sourceVisemes.length > 0 ? { visemes: sourceVisemes } : {},
|
|
236
|
+
...sourceBookmarks.length > 0 ? { bookmarks: sourceBookmarks } : {}
|
|
237
|
+
});
|
|
193
238
|
};
|
|
194
239
|
try {
|
|
195
240
|
if (config.signal) {
|
|
@@ -208,6 +253,108 @@ async function synthesizeSpeech(ssml, config) {
|
|
|
208
253
|
}
|
|
209
254
|
});
|
|
210
255
|
}
|
|
256
|
+
async function synthesizeSsmlChunks(chunks, config) {
|
|
257
|
+
const results = [];
|
|
258
|
+
const totalChunks = chunks.length;
|
|
259
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
260
|
+
const input = typeof chunk === "string" ? { ssml: chunk } : chunk;
|
|
261
|
+
const result = await synthesizeSsml(input.ssml, {
|
|
262
|
+
...config,
|
|
263
|
+
...input.originalTextRange ? { sourceTextRange: input.originalTextRange } : {},
|
|
264
|
+
onProgress: void 0
|
|
265
|
+
});
|
|
266
|
+
results.push(result);
|
|
267
|
+
config.onProgress?.({
|
|
268
|
+
currentChunk: index + 1,
|
|
269
|
+
totalChunks,
|
|
270
|
+
percent: totalChunks === 0 ? 100 : Math.round((index + 1) / totalChunks * 100)
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return mergeSynthesisResults(results);
|
|
274
|
+
}
|
|
275
|
+
function mergeSynthesisResults(results) {
|
|
276
|
+
const audioLength = results.reduce((total, result) => total + result.audioData.byteLength, 0);
|
|
277
|
+
const audioData = new Uint8Array(audioLength);
|
|
278
|
+
const boundaries = [];
|
|
279
|
+
const visemes = [];
|
|
280
|
+
const bookmarks = [];
|
|
281
|
+
let byteOffset = 0;
|
|
282
|
+
let durationOffset = 0;
|
|
283
|
+
for (const result of results) {
|
|
284
|
+
audioData.set(new Uint8Array(result.audioData), byteOffset);
|
|
285
|
+
byteOffset += result.audioData.byteLength;
|
|
286
|
+
const chunkBoundaries = result.boundaries && result.boundaries.length > 0 ? result.boundaries : result.wordBoundary ?? result.wordBoundaries ?? [];
|
|
287
|
+
for (const boundary of chunkBoundaries) {
|
|
288
|
+
const textRange = boundary.textRange ?? result.textRange;
|
|
289
|
+
const requestId = boundary.requestId ?? result.requestId;
|
|
290
|
+
boundaries.push({
|
|
291
|
+
...boundary,
|
|
292
|
+
audioOffsetMs: boundary.audioOffsetMs + durationOffset,
|
|
293
|
+
...textRange ? { textRange: { ...textRange } } : {},
|
|
294
|
+
...requestId ? { requestId } : {}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
for (const viseme of result.visemes ?? []) {
|
|
298
|
+
const textRange = viseme.textRange ?? result.textRange;
|
|
299
|
+
const requestId = viseme.requestId ?? result.requestId;
|
|
300
|
+
visemes.push({
|
|
301
|
+
...viseme,
|
|
302
|
+
audioOffsetMs: viseme.audioOffsetMs + durationOffset,
|
|
303
|
+
...textRange ? { textRange: { ...textRange } } : {},
|
|
304
|
+
...requestId ? { requestId } : {}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
for (const bookmark of result.bookmarks ?? []) {
|
|
308
|
+
const textRange = bookmark.textRange ?? result.textRange;
|
|
309
|
+
const requestId = bookmark.requestId ?? result.requestId;
|
|
310
|
+
bookmarks.push({
|
|
311
|
+
...bookmark,
|
|
312
|
+
audioOffsetMs: bookmark.audioOffsetMs + durationOffset,
|
|
313
|
+
...textRange ? { textRange: { ...textRange } } : {},
|
|
314
|
+
...requestId ? { requestId } : {}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
durationOffset += Math.max(0, result.durationMs);
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
audioData: audioData.buffer,
|
|
321
|
+
durationMs: durationOffset,
|
|
322
|
+
...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
|
|
323
|
+
...visemes.length > 0 ? { visemes } : {},
|
|
324
|
+
...bookmarks.length > 0 ? { bookmarks } : {},
|
|
325
|
+
...results.length === 1 && results[0]?.requestId ? { requestId: results[0].requestId } : {},
|
|
326
|
+
...results.length === 1 && results[0]?.textRange ? { textRange: { ...results[0].textRange } } : {}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
async function synthesizeSpeech(ssml, config) {
|
|
330
|
+
return (await synthesizeSsml(ssml, config)).audioData;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/safe.ts
|
|
334
|
+
var import_ssml_core = require("@ssml-builder-js/ssml-core");
|
|
335
|
+
async function synthesizeSsmlSafe(client, ssml, options = {}) {
|
|
336
|
+
const validationOptions = options.validation ?? options;
|
|
337
|
+
const diagnostics = await Promise.resolve((0, import_ssml_core.validateAzureSsml)(ssml, validationOptions));
|
|
338
|
+
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
339
|
+
if (errors.length > 0) {
|
|
340
|
+
return {
|
|
341
|
+
ok: false,
|
|
342
|
+
success: false,
|
|
343
|
+
status: "validation-error",
|
|
344
|
+
error: {
|
|
345
|
+
kind: "validation",
|
|
346
|
+
message: "SSML validation failed; the Azure Speech API was not called.",
|
|
347
|
+
diagnostics: errors
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
return { ok: true, success: true, status: "success", value: await client.synthesizeSsml(ssml) };
|
|
353
|
+
} catch (error) {
|
|
354
|
+
const azureError = error instanceof AzureTtsError ? error : createSpeechSdkError(error);
|
|
355
|
+
return { ok: false, success: false, status: "azure-api-error", error: azureError };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
211
358
|
|
|
212
359
|
// src/client.ts
|
|
213
360
|
var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
@@ -224,6 +371,28 @@ var AzureTtsClient = class {
|
|
|
224
371
|
const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
|
|
225
372
|
return synthesizeSpeech(ssml, config);
|
|
226
373
|
}
|
|
374
|
+
async synthesizeSsml(ssml) {
|
|
375
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
376
|
+
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
377
|
+
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
378
|
+
return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
|
|
379
|
+
}
|
|
380
|
+
async synthesizeChunks(chunks, options = {}) {
|
|
381
|
+
const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
|
|
382
|
+
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
383
|
+
return synthesizeSsmlChunks(chunks, {
|
|
384
|
+
endpoint,
|
|
385
|
+
region,
|
|
386
|
+
subscriptionKey,
|
|
387
|
+
outputFormat,
|
|
388
|
+
signal,
|
|
389
|
+
timeoutMs,
|
|
390
|
+
onProgress: options.onProgress ?? __privateGet(this, _options).onProgress
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
async synthesizeSsmlSafe(ssml, options = {}) {
|
|
394
|
+
return synthesizeSsmlSafe(this, ssml, options);
|
|
395
|
+
}
|
|
227
396
|
};
|
|
228
397
|
_options = new WeakMap();
|
|
229
398
|
|
|
@@ -310,6 +479,10 @@ async function fetchAzureVoiceCatalog(options) {
|
|
|
310
479
|
AzureTtsError,
|
|
311
480
|
AzureTtsSdkError,
|
|
312
481
|
fetchAzureVoiceCatalog,
|
|
313
|
-
|
|
482
|
+
mergeSynthesisResults,
|
|
483
|
+
synthesizeSpeech,
|
|
484
|
+
synthesizeSsml,
|
|
485
|
+
synthesizeSsmlChunks,
|
|
486
|
+
synthesizeSsmlSafe
|
|
314
487
|
});
|
|
315
488
|
//# sourceMappingURL=index.js.map
|