@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 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
- synthesizeSpeech: () => synthesizeSpeech
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
- async function synthesizeSpeech(ssml, config) {
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
- resolve(result.audioData);
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
- synthesizeSpeech
482
+ mergeSynthesisResults,
483
+ synthesizeSpeech,
484
+ synthesizeSsml,
485
+ synthesizeSsmlChunks,
486
+ synthesizeSsmlSafe
314
487
  });
315
488
  //# sourceMappingURL=index.js.map