@ssml-builder-js/azure-tts-client 2.10.0 → 2.12.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,17 @@
1
1
  # @ssml-builder-js/azure-tts-client
2
2
 
3
+ ## 2.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add `synthesizeSsml` and `SsmlSynthesisResult` for word boundaries, visemes, bookmarks, and duration metadata.
8
+
9
+ ## 2.11.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Release v2.11.0 with Azure voice catalog APIs and synchronization tools, expanded editor voice support, and stricter background audio validation.
14
+
3
15
  ## 2.10.0
4
16
 
5
17
  ### Minor Changes
package/dist/index.d.mts CHANGED
@@ -6,6 +6,31 @@ interface TtsConfig {
6
6
  region: string;
7
7
  outputFormat?: string;
8
8
  }
9
+ interface SsmlSynthesisBoundary {
10
+ text: string;
11
+ audioOffsetMs: number;
12
+ durationMs: number;
13
+ }
14
+ interface SsmlSynthesisViseme {
15
+ visemeId: number;
16
+ audioOffsetMs: number;
17
+ }
18
+ interface SsmlSynthesisBookmark {
19
+ name: string;
20
+ audioOffsetMs: number;
21
+ }
22
+ /** Audio and Azure Speech synchronization events emitted for one SSML request. */
23
+ interface SsmlSynthesisResult {
24
+ audioData: ArrayBuffer;
25
+ durationMs: number;
26
+ boundaries?: SsmlSynthesisBoundary[];
27
+ /** Alias matching the Azure Speech event name. */
28
+ wordBoundary?: SsmlSynthesisBoundary[];
29
+ /** Alias for consumers that use Azure's word-boundary terminology. */
30
+ wordBoundaries?: SsmlSynthesisBoundary[];
31
+ visemes?: SsmlSynthesisViseme[];
32
+ bookmarks?: SsmlSynthesisBookmark[];
33
+ }
9
34
  interface AzureTtsLogger {
10
35
  debug?: (...args: unknown[]) => void;
11
36
  info?: (...args: unknown[]) => void;
@@ -38,8 +63,36 @@ declare class AzureTtsClient {
38
63
  #private;
39
64
  constructor(options: AzureTtsClientOptions);
40
65
  synthesize(ssml: string): Promise<ArrayBuffer>;
66
+ synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult>;
41
67
  }
42
68
 
69
+ declare function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult>;
70
+ /** Backward-compatible audio-only synthesis helper. */
43
71
  declare function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer>;
44
72
 
45
- export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type TtsConfig, synthesizeSpeech };
73
+ interface FetchAzureVoiceCatalogOptions {
74
+ apiKey: string;
75
+ region: string | string[];
76
+ }
77
+ interface AzureVoiceCatalogVoice {
78
+ name: string;
79
+ locale: string;
80
+ secondaryLocales?: readonly string[];
81
+ styles?: readonly string[];
82
+ regions: readonly string[];
83
+ status?: "ga" | "preview" | "deprecated";
84
+ }
85
+ interface FetchedAzureVoiceCatalogMetadata {
86
+ voiceCount: number;
87
+ generatedAt: string;
88
+ apiVersion: string;
89
+ regions: readonly string[];
90
+ }
91
+ interface AzureVoiceCatalog {
92
+ voices: readonly AzureVoiceCatalogVoice[];
93
+ metadata: FetchedAzureVoiceCatalogMetadata;
94
+ }
95
+ /** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */
96
+ declare function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog>;
97
+
98
+ export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type AzureVoiceCatalog, type AzureVoiceCatalogVoice, type FetchAzureVoiceCatalogOptions, type FetchedAzureVoiceCatalogMetadata, type SsmlSynthesisBookmark, type SsmlSynthesisBoundary, type SsmlSynthesisResult, type SsmlSynthesisViseme, type TtsConfig, fetchAzureVoiceCatalog, synthesizeSpeech, synthesizeSsml };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,31 @@ interface TtsConfig {
6
6
  region: string;
7
7
  outputFormat?: string;
8
8
  }
9
+ interface SsmlSynthesisBoundary {
10
+ text: string;
11
+ audioOffsetMs: number;
12
+ durationMs: number;
13
+ }
14
+ interface SsmlSynthesisViseme {
15
+ visemeId: number;
16
+ audioOffsetMs: number;
17
+ }
18
+ interface SsmlSynthesisBookmark {
19
+ name: string;
20
+ audioOffsetMs: number;
21
+ }
22
+ /** Audio and Azure Speech synchronization events emitted for one SSML request. */
23
+ interface SsmlSynthesisResult {
24
+ audioData: ArrayBuffer;
25
+ durationMs: number;
26
+ boundaries?: SsmlSynthesisBoundary[];
27
+ /** Alias matching the Azure Speech event name. */
28
+ wordBoundary?: SsmlSynthesisBoundary[];
29
+ /** Alias for consumers that use Azure's word-boundary terminology. */
30
+ wordBoundaries?: SsmlSynthesisBoundary[];
31
+ visemes?: SsmlSynthesisViseme[];
32
+ bookmarks?: SsmlSynthesisBookmark[];
33
+ }
9
34
  interface AzureTtsLogger {
10
35
  debug?: (...args: unknown[]) => void;
11
36
  info?: (...args: unknown[]) => void;
@@ -38,8 +63,36 @@ declare class AzureTtsClient {
38
63
  #private;
39
64
  constructor(options: AzureTtsClientOptions);
40
65
  synthesize(ssml: string): Promise<ArrayBuffer>;
66
+ synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult>;
41
67
  }
42
68
 
69
+ declare function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult>;
70
+ /** Backward-compatible audio-only synthesis helper. */
43
71
  declare function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer>;
44
72
 
45
- export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type TtsConfig, synthesizeSpeech };
73
+ interface FetchAzureVoiceCatalogOptions {
74
+ apiKey: string;
75
+ region: string | string[];
76
+ }
77
+ interface AzureVoiceCatalogVoice {
78
+ name: string;
79
+ locale: string;
80
+ secondaryLocales?: readonly string[];
81
+ styles?: readonly string[];
82
+ regions: readonly string[];
83
+ status?: "ga" | "preview" | "deprecated";
84
+ }
85
+ interface FetchedAzureVoiceCatalogMetadata {
86
+ voiceCount: number;
87
+ generatedAt: string;
88
+ apiVersion: string;
89
+ regions: readonly string[];
90
+ }
91
+ interface AzureVoiceCatalog {
92
+ voices: readonly AzureVoiceCatalogVoice[];
93
+ metadata: FetchedAzureVoiceCatalogMetadata;
94
+ }
95
+ /** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */
96
+ declare function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog>;
97
+
98
+ export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type AzureVoiceCatalog, type AzureVoiceCatalogVoice, type FetchAzureVoiceCatalogOptions, type FetchedAzureVoiceCatalogMetadata, type SsmlSynthesisBookmark, type SsmlSynthesisBoundary, type SsmlSynthesisResult, type SsmlSynthesisViseme, type TtsConfig, fetchAzureVoiceCatalog, synthesizeSpeech, synthesizeSsml };
package/dist/index.js CHANGED
@@ -40,7 +40,9 @@ __export(index_exports, {
40
40
  AzureTtsClient: () => AzureTtsClient,
41
41
  AzureTtsError: () => AzureTtsError,
42
42
  AzureTtsSdkError: () => AzureTtsSdkError,
43
- synthesizeSpeech: () => synthesizeSpeech
43
+ fetchAzureVoiceCatalog: () => fetchAzureVoiceCatalog,
44
+ synthesizeSpeech: () => synthesizeSpeech,
45
+ synthesizeSsml: () => synthesizeSsml
44
46
  });
45
47
  module.exports = __toCommonJS(index_exports);
46
48
 
@@ -150,7 +152,8 @@ function closeSpeechResources(speechConfig, synthesizer) {
150
152
  } catch {
151
153
  }
152
154
  }
153
- async function synthesizeSpeech(ssml, config) {
155
+ var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
156
+ async function synthesizeSsml(ssml, config) {
154
157
  if (config.signal?.aborted) {
155
158
  throw createSpeechSdkError("Speech synthesis was cancelled.");
156
159
  }
@@ -177,6 +180,22 @@ async function synthesizeSpeech(ssml, config) {
177
180
  closeResources();
178
181
  reject(createSpeechSdkError(error));
179
182
  };
183
+ const boundaries = [];
184
+ const visemes = [];
185
+ const bookmarks = [];
186
+ synthesizer.wordBoundary = (_sender, event) => {
187
+ boundaries.push({
188
+ text: event.text,
189
+ audioOffsetMs: ticksToMilliseconds(event.audioOffset),
190
+ durationMs: ticksToMilliseconds(event.duration)
191
+ });
192
+ };
193
+ synthesizer.visemeReceived = (_sender, event) => {
194
+ visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
195
+ };
196
+ synthesizer.bookmarkReached = (_sender, event) => {
197
+ bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
198
+ };
180
199
  const cb = (result) => {
181
200
  if (settled) return;
182
201
  const { reason, errorDetails } = result;
@@ -188,7 +207,20 @@ async function synthesizeSpeech(ssml, config) {
188
207
  settled = true;
189
208
  cleanup();
190
209
  closeResources();
191
- resolve(result.audioData);
210
+ const eventDurationMs = Math.max(
211
+ 0,
212
+ ...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),
213
+ ...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),
214
+ ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
215
+ );
216
+ const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
217
+ resolve({
218
+ audioData: result.audioData,
219
+ durationMs,
220
+ ...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
221
+ ...visemes.length > 0 ? { visemes } : {},
222
+ ...bookmarks.length > 0 ? { bookmarks } : {}
223
+ });
192
224
  };
193
225
  try {
194
226
  if (config.signal) {
@@ -207,6 +239,9 @@ async function synthesizeSpeech(ssml, config) {
207
239
  }
208
240
  });
209
241
  }
242
+ async function synthesizeSpeech(ssml, config) {
243
+ return (await synthesizeSsml(ssml, config)).audioData;
244
+ }
210
245
 
211
246
  // src/client.ts
212
247
  var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
@@ -223,13 +258,99 @@ var AzureTtsClient = class {
223
258
  const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
224
259
  return synthesizeSpeech(ssml, config);
225
260
  }
261
+ async synthesizeSsml(ssml) {
262
+ const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
263
+ const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
264
+ __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
265
+ return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
266
+ }
226
267
  };
227
268
  _options = new WeakMap();
269
+
270
+ // src/voiceCatalog.ts
271
+ var AZURE_VOICE_API_VERSION = "2025-10-01";
272
+ function stringValue(value) {
273
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
274
+ }
275
+ function stringList(value) {
276
+ if (!Array.isArray(value)) return [];
277
+ return [...new Set(value.map(stringValue).filter((item) => item !== void 0))];
278
+ }
279
+ function normalizeStatus(value) {
280
+ const status = stringValue(value)?.toLowerCase();
281
+ if (status === "preview" || status === "deprecated" || status === "ga") return status;
282
+ return void 0;
283
+ }
284
+ function normalizeRegions(region) {
285
+ const regions = Array.isArray(region) ? region : [region];
286
+ const result = [...new Set(regions.map((item) => item.trim()).filter(Boolean))];
287
+ if (result.length === 0) throw new TypeError("At least one Azure Speech region is required.");
288
+ return result;
289
+ }
290
+ async function fetchRegionVoices(region, apiKey) {
291
+ const endpoint = `https://${encodeURIComponent(region)}.tts.speech.microsoft.com/cognitiveservices/voices/list`;
292
+ const response = await fetch(endpoint, {
293
+ headers: {
294
+ Accept: "application/json",
295
+ "Ocp-Apim-Subscription-Key": apiKey
296
+ }
297
+ });
298
+ if (!response.ok) {
299
+ throw new Error(`Azure List Voices API request failed for region "${region}" with HTTP ${response.status}.`);
300
+ }
301
+ const payload = await response.json();
302
+ if (!Array.isArray(payload)) throw new Error(`Azure List Voices API returned an invalid response for "${region}".`);
303
+ return payload.filter((item) => Boolean(item && typeof item === "object"));
304
+ }
305
+ async function fetchAzureVoiceCatalog(options) {
306
+ if (!options || typeof options.apiKey !== "string" || !options.apiKey.trim())
307
+ throw new TypeError("An Azure Speech API key is required.");
308
+ const regions = normalizeRegions(options.region);
309
+ const payloads = await Promise.all(regions.map((region) => fetchRegionVoices(region, options.apiKey)));
310
+ const voices = /* @__PURE__ */ new Map();
311
+ for (let regionIndex = 0; regionIndex < payloads.length; regionIndex += 1) {
312
+ const region = regions[regionIndex];
313
+ for (const record of payloads[regionIndex]) {
314
+ const name = stringValue(record.ShortName) ?? stringValue(record.Name);
315
+ const locale = stringValue(record.Locale);
316
+ if (!name || !locale) continue;
317
+ const key = name.toLowerCase();
318
+ const existing = voices.get(key);
319
+ const secondaryLocales = stringList(record.SecondaryLocaleList);
320
+ const styles = stringList(record.StyleList);
321
+ const status = normalizeStatus(record.Status);
322
+ const merged = {
323
+ name: existing?.name ?? name,
324
+ locale: existing?.locale ?? locale,
325
+ regions: [.../* @__PURE__ */ new Set([...existing?.regions ?? [], region])]
326
+ };
327
+ const mergedSecondaryLocales = [.../* @__PURE__ */ new Set([...existing?.secondaryLocales ?? [], ...secondaryLocales])];
328
+ if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;
329
+ const mergedStyles = [.../* @__PURE__ */ new Set([...existing?.styles ?? [], ...styles])];
330
+ if (mergedStyles.length > 0) merged.styles = mergedStyles;
331
+ if (status) merged.status = status;
332
+ else if (existing?.status) merged.status = existing.status;
333
+ voices.set(key, merged);
334
+ }
335
+ }
336
+ const sortedVoices = [...voices.values()].sort((first, second) => first.name.localeCompare(second.name));
337
+ return {
338
+ voices: sortedVoices,
339
+ metadata: {
340
+ voiceCount: sortedVoices.length,
341
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
342
+ apiVersion: AZURE_VOICE_API_VERSION,
343
+ regions
344
+ }
345
+ };
346
+ }
228
347
  // Annotate the CommonJS export names for ESM import in node:
229
348
  0 && (module.exports = {
230
349
  AzureTtsClient,
231
350
  AzureTtsError,
232
351
  AzureTtsSdkError,
233
- synthesizeSpeech
352
+ fetchAzureVoiceCatalog,
353
+ synthesizeSpeech,
354
+ synthesizeSsml
234
355
  });
235
356
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/synthesis.ts","../src/speechConfig.ts","../src/outputFormats.ts","../src/client.ts"],"sourcesContent":["/**\n * azure-tts-client: Azure Text-to-Speech client for SSML playback.\n */\n\nexport type { AzureTtsClientOptions, AzureTtsLogger, TtsConfig } from \"./types.ts\";\nexport { AzureTtsError, AzureTtsSdkError } from \"./errors.ts\";\nexport { AzureTtsClient } from \"./client.ts\";\nexport { synthesizeSpeech } from \"./synthesis.ts\";\n","export class AzureTtsError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly responseBody: string;\n readonly requestId: string | null;\n\n constructor(status: number, statusText: string, responseBody: string, requestId: string | null) {\n super(`Azure TTS request failed: ${status} ${statusText}`);\n this.name = \"AzureTtsError\";\n this.status = status;\n this.statusText = statusText;\n this.responseBody = responseBody;\n this.requestId = requestId;\n }\n}\n\nexport class AzureTtsSdkError extends AzureTtsError {\n readonly errorDetails: string;\n\n constructor(errorDetails: string) {\n super(0, \"Speech SDK\", errorDetails, null);\n this.name = \"AzureTtsSdkError\";\n this.message = `Azure TTS synthesis failed: ${errorDetails}`;\n this.errorDetails = errorDetails;\n }\n}\n\nexport function createSpeechSdkError(error: unknown): AzureTtsSdkError {\n const message = error instanceof Error ? error.message : String(error);\n return new AzureTtsSdkError(message);\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\nimport { createSpeechSdkError } from \"./errors.ts\";\nimport { createSpeechConfig } from \"./speechConfig.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nfunction closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer: SpeechSDK.SpeechSynthesizer): void {\n try {\n synthesizer.close();\n } catch {}\n\n try {\n speechConfig.close();\n } catch {}\n}\n\nexport async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer> {\n if (config.signal?.aborted) {\n throw createSpeechSdkError(\"Speech synthesis was cancelled.\");\n }\n\n const speechConfig = createSpeechConfig(config);\n const synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, null);\n\n return await new Promise<ArrayBuffer>((resolve, reject) => {\n let resourcesClosed = false;\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let abortHandler: (() => void) | undefined;\n const cleanup = () => {\n if (timeout) clearTimeout(timeout);\n if (abortHandler) config.signal?.removeEventListener(\"abort\", abortHandler);\n };\n const closeResources = () => {\n if (resourcesClosed) return;\n resourcesClosed = true;\n closeSpeechResources(speechConfig, synthesizer);\n };\n const rejectWithError = (error: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n closeResources();\n reject(createSpeechSdkError(error));\n };\n\n const cb = (result: SpeechSDK.SpeechSynthesisResult) => {\n if (settled) return;\n const { reason, errorDetails } = result;\n if (reason !== SpeechSDK.ResultReason.SynthesizingAudioCompleted) {\n const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;\n rejectWithError(err);\n return;\n }\n settled = true;\n cleanup();\n closeResources();\n resolve(result.audioData);\n };\n\n try {\n if (config.signal) {\n abortHandler = () => rejectWithError(\"Speech synthesis was cancelled.\");\n config.signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n if (config.timeoutMs !== undefined && config.timeoutMs > 0) {\n timeout = setTimeout(\n () => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),\n config.timeoutMs,\n );\n }\n synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);\n } catch (error) {\n rejectWithError(error);\n }\n });\n}\n","import { SpeechConfig } from \"microsoft-cognitiveservices-speech-sdk\";\nimport { DEFAULT_OUTPUT_FORMAT, resolveOutputFormat } from \"./outputFormats.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nexport function resolveEndpoint(config: TtsConfig): string {\n const endpoint = config.endpoint?.trim() || \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n return endpoint.replace(/\\{region\\}/g, encodeURIComponent(config.region));\n}\n\nexport function createSpeechConfig(config: TtsConfig): SpeechConfig {\n const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;\n\n const endpoint = new URL(resolveEndpoint(config));\n const speechConfig = SpeechConfig.fromEndpoint(endpoint, subscriptionKey);\n speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);\n return speechConfig;\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\n\nexport const DEFAULT_OUTPUT_FORMAT = \"audio-16khz-128kbitrate-mono-mp3\";\n\nconst OUTPUT_FORMATS: Record<string, SpeechSDK.SpeechSynthesisOutputFormat> = {\n \"raw-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,\n \"riff-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,\n \"audio-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,\n \"audio-16khz-32kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,\n \"audio-16khz-128kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,\n \"audio-16khz-64kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,\n \"audio-24khz-48kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,\n \"audio-24khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,\n \"audio-24khz-160kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,\n \"raw-16khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,\n \"riff-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,\n \"riff-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,\n \"riff-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,\n \"riff-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,\n \"raw-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,\n \"raw-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,\n \"raw-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,\n \"ogg-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,\n \"ogg-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,\n \"raw-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,\n \"riff-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,\n \"audio-48khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,\n \"audio-48khz-192kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,\n \"ogg-48khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,\n \"webm-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,\n \"webm-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,\n \"webm-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,\n \"raw-24khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,\n \"raw-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,\n \"riff-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,\n \"audio-16khz-16bit-32kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,\n \"audio-24khz-16bit-48kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,\n \"audio-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,\n \"raw-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,\n \"riff-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,\n \"raw-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,\n \"riff-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,\n \"amr-wb-16000hz\": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,\n \"g722-16khz-64kbps\": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps,\n};\n\nexport function resolveOutputFormat(outputFormat: string): SpeechSDK.SpeechSynthesisOutputFormat {\n const resolvedFormat = OUTPUT_FORMATS[outputFormat];\n if (resolvedFormat === undefined) {\n throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);\n }\n\n return resolvedFormat;\n}\n","import { synthesizeSpeech } from \"./synthesis.ts\";\nimport type { AzureTtsClientOptions } from \"./types.ts\";\n\nconst ENDPOINT_TEMPLATE = \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n\nexport class AzureTtsClient {\n readonly #options: AzureTtsClientOptions;\n\n constructor(options: AzureTtsClientOptions) {\n this.#options = options;\n }\n\n async synthesize(ssml: string): Promise<ArrayBuffer> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };\n return synthesizeSpeech(ssml, config);\n }\n}\n"],"mappings":"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","names":["SpeechSDK"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/synthesis.ts","../src/speechConfig.ts","../src/outputFormats.ts","../src/client.ts","../src/voiceCatalog.ts"],"sourcesContent":["/**\n * azure-tts-client: Azure Text-to-Speech client for SSML playback.\n */\n\nexport type {\n AzureTtsClientOptions,\n AzureTtsLogger,\n SsmlSynthesisBookmark,\n SsmlSynthesisBoundary,\n SsmlSynthesisResult,\n SsmlSynthesisViseme,\n TtsConfig,\n} from \"./types.ts\";\nexport { AzureTtsError, AzureTtsSdkError } from \"./errors.ts\";\nexport { AzureTtsClient } from \"./client.ts\";\nexport { synthesizeSpeech } from \"./synthesis.ts\";\nexport { synthesizeSsml } from \"./synthesis.ts\";\nexport { fetchAzureVoiceCatalog } from \"./voiceCatalog.ts\";\nexport type {\n AzureVoiceCatalog,\n AzureVoiceCatalogVoice,\n FetchedAzureVoiceCatalogMetadata,\n FetchAzureVoiceCatalogOptions,\n} from \"./voiceCatalog.ts\";\n","export class AzureTtsError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly responseBody: string;\n readonly requestId: string | null;\n\n constructor(status: number, statusText: string, responseBody: string, requestId: string | null) {\n super(`Azure TTS request failed: ${status} ${statusText}`);\n this.name = \"AzureTtsError\";\n this.status = status;\n this.statusText = statusText;\n this.responseBody = responseBody;\n this.requestId = requestId;\n }\n}\n\nexport class AzureTtsSdkError extends AzureTtsError {\n readonly errorDetails: string;\n\n constructor(errorDetails: string) {\n super(0, \"Speech SDK\", errorDetails, null);\n this.name = \"AzureTtsSdkError\";\n this.message = `Azure TTS synthesis failed: ${errorDetails}`;\n this.errorDetails = errorDetails;\n }\n}\n\nexport function createSpeechSdkError(error: unknown): AzureTtsSdkError {\n const message = error instanceof Error ? error.message : String(error);\n return new AzureTtsSdkError(message);\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\nimport { createSpeechSdkError } from \"./errors.ts\";\nimport { createSpeechConfig } from \"./speechConfig.ts\";\nimport type { SsmlSynthesisResult, TtsConfig } from \"./types.ts\";\n\nfunction closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer: SpeechSDK.SpeechSynthesizer): void {\n try {\n synthesizer.close();\n } catch {}\n\n try {\n speechConfig.close();\n } catch {}\n}\n\nconst ticksToMilliseconds = (ticks: number): number => Math.max(0, ticks) / 10_000;\n\nexport async function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult> {\n if (config.signal?.aborted) {\n throw createSpeechSdkError(\"Speech synthesis was cancelled.\");\n }\n\n const speechConfig = createSpeechConfig(config);\n const synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, null);\n\n return await new Promise<SsmlSynthesisResult>((resolve, reject) => {\n let resourcesClosed = false;\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let abortHandler: (() => void) | undefined;\n const cleanup = () => {\n if (timeout) clearTimeout(timeout);\n if (abortHandler) config.signal?.removeEventListener(\"abort\", abortHandler);\n };\n const closeResources = () => {\n if (resourcesClosed) return;\n resourcesClosed = true;\n closeSpeechResources(speechConfig, synthesizer);\n };\n const rejectWithError = (error: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n closeResources();\n reject(createSpeechSdkError(error));\n };\n\n const boundaries: SsmlSynthesisResult[\"boundaries\"] = [];\n const visemes: SsmlSynthesisResult[\"visemes\"] = [];\n const bookmarks: SsmlSynthesisResult[\"bookmarks\"] = [];\n synthesizer.wordBoundary = (_sender, event) => {\n boundaries.push({\n text: event.text,\n audioOffsetMs: ticksToMilliseconds(event.audioOffset),\n durationMs: ticksToMilliseconds(event.duration),\n });\n };\n synthesizer.visemeReceived = (_sender, event) => {\n visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });\n };\n synthesizer.bookmarkReached = (_sender, event) => {\n bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });\n };\n\n const cb = (result: SpeechSDK.SpeechSynthesisResult) => {\n if (settled) return;\n const { reason, errorDetails } = result;\n if (reason !== SpeechSDK.ResultReason.SynthesizingAudioCompleted) {\n const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;\n rejectWithError(err);\n return;\n }\n settled = true;\n cleanup();\n closeResources();\n const eventDurationMs = Math.max(\n 0,\n ...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),\n ...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),\n ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs),\n );\n const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;\n resolve({\n audioData: result.audioData,\n durationMs,\n ...(boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {}),\n ...(visemes.length > 0 ? { visemes } : {}),\n ...(bookmarks.length > 0 ? { bookmarks } : {}),\n });\n };\n\n try {\n if (config.signal) {\n abortHandler = () => rejectWithError(\"Speech synthesis was cancelled.\");\n config.signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n if (config.timeoutMs !== undefined && config.timeoutMs > 0) {\n timeout = setTimeout(\n () => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),\n config.timeoutMs,\n );\n }\n synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);\n } catch (error) {\n rejectWithError(error);\n }\n });\n}\n\n/** Backward-compatible audio-only synthesis helper. */\nexport async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer> {\n return (await synthesizeSsml(ssml, config)).audioData;\n}\n","import { SpeechConfig } from \"microsoft-cognitiveservices-speech-sdk\";\nimport { DEFAULT_OUTPUT_FORMAT, resolveOutputFormat } from \"./outputFormats.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nexport function resolveEndpoint(config: TtsConfig): string {\n const endpoint = config.endpoint?.trim() || \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n return endpoint.replace(/\\{region\\}/g, encodeURIComponent(config.region));\n}\n\nexport function createSpeechConfig(config: TtsConfig): SpeechConfig {\n const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;\n\n const endpoint = new URL(resolveEndpoint(config));\n const speechConfig = SpeechConfig.fromEndpoint(endpoint, subscriptionKey);\n speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);\n return speechConfig;\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\n\nexport const DEFAULT_OUTPUT_FORMAT = \"audio-16khz-128kbitrate-mono-mp3\";\n\nconst OUTPUT_FORMATS: Record<string, SpeechSDK.SpeechSynthesisOutputFormat> = {\n \"raw-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,\n \"riff-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,\n \"audio-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,\n \"audio-16khz-32kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,\n \"audio-16khz-128kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,\n \"audio-16khz-64kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,\n \"audio-24khz-48kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,\n \"audio-24khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,\n \"audio-24khz-160kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,\n \"raw-16khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,\n \"riff-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,\n \"riff-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,\n \"riff-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,\n \"riff-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,\n \"raw-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,\n \"raw-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,\n \"raw-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,\n \"ogg-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,\n \"ogg-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,\n \"raw-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,\n \"riff-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,\n \"audio-48khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,\n \"audio-48khz-192kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,\n \"ogg-48khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,\n \"webm-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,\n \"webm-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,\n \"webm-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,\n \"raw-24khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,\n \"raw-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,\n \"riff-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,\n \"audio-16khz-16bit-32kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,\n \"audio-24khz-16bit-48kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,\n \"audio-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,\n \"raw-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,\n \"riff-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,\n \"raw-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,\n \"riff-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,\n \"amr-wb-16000hz\": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,\n \"g722-16khz-64kbps\": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps,\n};\n\nexport function resolveOutputFormat(outputFormat: string): SpeechSDK.SpeechSynthesisOutputFormat {\n const resolvedFormat = OUTPUT_FORMATS[outputFormat];\n if (resolvedFormat === undefined) {\n throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);\n }\n\n return resolvedFormat;\n}\n","import { synthesizeSpeech, synthesizeSsml } from \"./synthesis.ts\";\nimport type { AzureTtsClientOptions, SsmlSynthesisResult } from \"./types.ts\";\n\nconst ENDPOINT_TEMPLATE = \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n\nexport class AzureTtsClient {\n readonly #options: AzureTtsClientOptions;\n\n constructor(options: AzureTtsClientOptions) {\n this.#options = options;\n }\n\n async synthesize(ssml: string): Promise<ArrayBuffer> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };\n return synthesizeSpeech(ssml, config);\n }\n\n async synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });\n }\n}\n","const AZURE_VOICE_API_VERSION = \"2025-10-01\";\n\nexport interface FetchAzureVoiceCatalogOptions {\n apiKey: string;\n region: string | string[];\n}\n\nexport interface AzureVoiceCatalogVoice {\n name: string;\n locale: string;\n secondaryLocales?: readonly string[];\n styles?: readonly string[];\n regions: readonly string[];\n status?: \"ga\" | \"preview\" | \"deprecated\";\n}\n\nexport interface FetchedAzureVoiceCatalogMetadata {\n voiceCount: number;\n generatedAt: string;\n apiVersion: string;\n regions: readonly string[];\n}\n\nexport interface AzureVoiceCatalog {\n voices: readonly AzureVoiceCatalogVoice[];\n metadata: FetchedAzureVoiceCatalogMetadata;\n}\n\ninterface AzureVoiceApiRecord {\n Locale?: unknown;\n Name?: unknown;\n SecondaryLocaleList?: unknown;\n ShortName?: unknown;\n Status?: unknown;\n StyleList?: unknown;\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction stringList(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return [...new Set(value.map(stringValue).filter((item): item is string => item !== undefined))];\n}\n\nfunction normalizeStatus(value: unknown): AzureVoiceCatalogVoice[\"status\"] {\n const status = stringValue(value)?.toLowerCase();\n if (status === \"preview\" || status === \"deprecated\" || status === \"ga\") return status;\n return undefined;\n}\n\nfunction normalizeRegions(region: string | string[]): string[] {\n const regions = Array.isArray(region) ? region : [region];\n const result = [...new Set(regions.map((item) => item.trim()).filter(Boolean))];\n if (result.length === 0) throw new TypeError(\"At least one Azure Speech region is required.\");\n return result;\n}\n\nasync function fetchRegionVoices(region: string, apiKey: string): Promise<AzureVoiceApiRecord[]> {\n const endpoint = `https://${encodeURIComponent(region)}.tts.speech.microsoft.com/cognitiveservices/voices/list`;\n const response = await fetch(endpoint, {\n headers: {\n Accept: \"application/json\",\n \"Ocp-Apim-Subscription-Key\": apiKey,\n },\n });\n if (!response.ok) {\n throw new Error(`Azure List Voices API request failed for region \"${region}\" with HTTP ${response.status}.`);\n }\n const payload: unknown = await response.json();\n if (!Array.isArray(payload)) throw new Error(`Azure List Voices API returned an invalid response for \"${region}\".`);\n return payload.filter((item): item is AzureVoiceApiRecord => Boolean(item && typeof item === \"object\"));\n}\n\n/** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */\nexport async function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog> {\n if (!options || typeof options.apiKey !== \"string\" || !options.apiKey.trim())\n throw new TypeError(\"An Azure Speech API key is required.\");\n const regions = normalizeRegions(options.region);\n const payloads = await Promise.all(regions.map((region) => fetchRegionVoices(region, options.apiKey)));\n const voices = new Map<string, AzureVoiceCatalogVoice>();\n\n for (let regionIndex = 0; regionIndex < payloads.length; regionIndex += 1) {\n const region = regions[regionIndex];\n for (const record of payloads[regionIndex]) {\n const name = stringValue(record.ShortName) ?? stringValue(record.Name);\n const locale = stringValue(record.Locale);\n if (!name || !locale) continue;\n const key = name.toLowerCase();\n const existing = voices.get(key);\n const secondaryLocales = stringList(record.SecondaryLocaleList);\n const styles = stringList(record.StyleList);\n const status = normalizeStatus(record.Status);\n const merged: AzureVoiceCatalogVoice = {\n name: existing?.name ?? name,\n locale: existing?.locale ?? locale,\n regions: [...new Set([...(existing?.regions ?? []), region])],\n };\n const mergedSecondaryLocales = [...new Set([...(existing?.secondaryLocales ?? []), ...secondaryLocales])];\n if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;\n const mergedStyles = [...new Set([...(existing?.styles ?? []), ...styles])];\n if (mergedStyles.length > 0) merged.styles = mergedStyles;\n if (status) merged.status = status;\n else if (existing?.status) merged.status = existing.status;\n voices.set(key, merged);\n }\n }\n\n const sortedVoices = [...voices.values()].sort((first, second) => first.name.localeCompare(second.name));\n return {\n voices: sortedVoices,\n metadata: {\n voiceCount: sortedVoices.length,\n generatedAt: new Date().toISOString(),\n apiVersion: AZURE_VOICE_API_VERSION,\n regions,\n },\n };\n}\n"],"mappings":"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","names":["SpeechSDK"]}
package/dist/index.mjs CHANGED
@@ -112,7 +112,8 @@ function closeSpeechResources(speechConfig, synthesizer) {
112
112
  } catch {
113
113
  }
114
114
  }
115
- async function synthesizeSpeech(ssml, config) {
115
+ var ticksToMilliseconds = (ticks) => Math.max(0, ticks) / 1e4;
116
+ async function synthesizeSsml(ssml, config) {
116
117
  if (config.signal?.aborted) {
117
118
  throw createSpeechSdkError("Speech synthesis was cancelled.");
118
119
  }
@@ -139,6 +140,22 @@ async function synthesizeSpeech(ssml, config) {
139
140
  closeResources();
140
141
  reject(createSpeechSdkError(error));
141
142
  };
143
+ const boundaries = [];
144
+ const visemes = [];
145
+ const bookmarks = [];
146
+ synthesizer.wordBoundary = (_sender, event) => {
147
+ boundaries.push({
148
+ text: event.text,
149
+ audioOffsetMs: ticksToMilliseconds(event.audioOffset),
150
+ durationMs: ticksToMilliseconds(event.duration)
151
+ });
152
+ };
153
+ synthesizer.visemeReceived = (_sender, event) => {
154
+ visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
155
+ };
156
+ synthesizer.bookmarkReached = (_sender, event) => {
157
+ bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
158
+ };
142
159
  const cb = (result) => {
143
160
  if (settled) return;
144
161
  const { reason, errorDetails } = result;
@@ -150,7 +167,20 @@ async function synthesizeSpeech(ssml, config) {
150
167
  settled = true;
151
168
  cleanup();
152
169
  closeResources();
153
- resolve(result.audioData);
170
+ const eventDurationMs = Math.max(
171
+ 0,
172
+ ...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),
173
+ ...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),
174
+ ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs)
175
+ );
176
+ const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
177
+ resolve({
178
+ audioData: result.audioData,
179
+ durationMs,
180
+ ...boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {},
181
+ ...visemes.length > 0 ? { visemes } : {},
182
+ ...bookmarks.length > 0 ? { bookmarks } : {}
183
+ });
154
184
  };
155
185
  try {
156
186
  if (config.signal) {
@@ -169,6 +199,9 @@ async function synthesizeSpeech(ssml, config) {
169
199
  }
170
200
  });
171
201
  }
202
+ async function synthesizeSpeech(ssml, config) {
203
+ return (await synthesizeSsml(ssml, config)).audioData;
204
+ }
172
205
 
173
206
  // src/client.ts
174
207
  var ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
@@ -185,12 +218,98 @@ var AzureTtsClient = class {
185
218
  const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
186
219
  return synthesizeSpeech(ssml, config);
187
220
  }
221
+ async synthesizeSsml(ssml) {
222
+ const { region, subscriptionKey, outputFormat, signal, timeoutMs } = __privateGet(this, _options);
223
+ const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
224
+ __privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
225
+ return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
226
+ }
188
227
  };
189
228
  _options = new WeakMap();
229
+
230
+ // src/voiceCatalog.ts
231
+ var AZURE_VOICE_API_VERSION = "2025-10-01";
232
+ function stringValue(value) {
233
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
234
+ }
235
+ function stringList(value) {
236
+ if (!Array.isArray(value)) return [];
237
+ return [...new Set(value.map(stringValue).filter((item) => item !== void 0))];
238
+ }
239
+ function normalizeStatus(value) {
240
+ const status = stringValue(value)?.toLowerCase();
241
+ if (status === "preview" || status === "deprecated" || status === "ga") return status;
242
+ return void 0;
243
+ }
244
+ function normalizeRegions(region) {
245
+ const regions = Array.isArray(region) ? region : [region];
246
+ const result = [...new Set(regions.map((item) => item.trim()).filter(Boolean))];
247
+ if (result.length === 0) throw new TypeError("At least one Azure Speech region is required.");
248
+ return result;
249
+ }
250
+ async function fetchRegionVoices(region, apiKey) {
251
+ const endpoint = `https://${encodeURIComponent(region)}.tts.speech.microsoft.com/cognitiveservices/voices/list`;
252
+ const response = await fetch(endpoint, {
253
+ headers: {
254
+ Accept: "application/json",
255
+ "Ocp-Apim-Subscription-Key": apiKey
256
+ }
257
+ });
258
+ if (!response.ok) {
259
+ throw new Error(`Azure List Voices API request failed for region "${region}" with HTTP ${response.status}.`);
260
+ }
261
+ const payload = await response.json();
262
+ if (!Array.isArray(payload)) throw new Error(`Azure List Voices API returned an invalid response for "${region}".`);
263
+ return payload.filter((item) => Boolean(item && typeof item === "object"));
264
+ }
265
+ async function fetchAzureVoiceCatalog(options) {
266
+ if (!options || typeof options.apiKey !== "string" || !options.apiKey.trim())
267
+ throw new TypeError("An Azure Speech API key is required.");
268
+ const regions = normalizeRegions(options.region);
269
+ const payloads = await Promise.all(regions.map((region) => fetchRegionVoices(region, options.apiKey)));
270
+ const voices = /* @__PURE__ */ new Map();
271
+ for (let regionIndex = 0; regionIndex < payloads.length; regionIndex += 1) {
272
+ const region = regions[regionIndex];
273
+ for (const record of payloads[regionIndex]) {
274
+ const name = stringValue(record.ShortName) ?? stringValue(record.Name);
275
+ const locale = stringValue(record.Locale);
276
+ if (!name || !locale) continue;
277
+ const key = name.toLowerCase();
278
+ const existing = voices.get(key);
279
+ const secondaryLocales = stringList(record.SecondaryLocaleList);
280
+ const styles = stringList(record.StyleList);
281
+ const status = normalizeStatus(record.Status);
282
+ const merged = {
283
+ name: existing?.name ?? name,
284
+ locale: existing?.locale ?? locale,
285
+ regions: [.../* @__PURE__ */ new Set([...existing?.regions ?? [], region])]
286
+ };
287
+ const mergedSecondaryLocales = [.../* @__PURE__ */ new Set([...existing?.secondaryLocales ?? [], ...secondaryLocales])];
288
+ if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;
289
+ const mergedStyles = [.../* @__PURE__ */ new Set([...existing?.styles ?? [], ...styles])];
290
+ if (mergedStyles.length > 0) merged.styles = mergedStyles;
291
+ if (status) merged.status = status;
292
+ else if (existing?.status) merged.status = existing.status;
293
+ voices.set(key, merged);
294
+ }
295
+ }
296
+ const sortedVoices = [...voices.values()].sort((first, second) => first.name.localeCompare(second.name));
297
+ return {
298
+ voices: sortedVoices,
299
+ metadata: {
300
+ voiceCount: sortedVoices.length,
301
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
302
+ apiVersion: AZURE_VOICE_API_VERSION,
303
+ regions
304
+ }
305
+ };
306
+ }
190
307
  export {
191
308
  AzureTtsClient,
192
309
  AzureTtsError,
193
310
  AzureTtsSdkError,
194
- synthesizeSpeech
311
+ fetchAzureVoiceCatalog,
312
+ synthesizeSpeech,
313
+ synthesizeSsml
195
314
  };
196
315
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/synthesis.ts","../src/speechConfig.ts","../src/outputFormats.ts","../src/client.ts"],"sourcesContent":["export class AzureTtsError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly responseBody: string;\n readonly requestId: string | null;\n\n constructor(status: number, statusText: string, responseBody: string, requestId: string | null) {\n super(`Azure TTS request failed: ${status} ${statusText}`);\n this.name = \"AzureTtsError\";\n this.status = status;\n this.statusText = statusText;\n this.responseBody = responseBody;\n this.requestId = requestId;\n }\n}\n\nexport class AzureTtsSdkError extends AzureTtsError {\n readonly errorDetails: string;\n\n constructor(errorDetails: string) {\n super(0, \"Speech SDK\", errorDetails, null);\n this.name = \"AzureTtsSdkError\";\n this.message = `Azure TTS synthesis failed: ${errorDetails}`;\n this.errorDetails = errorDetails;\n }\n}\n\nexport function createSpeechSdkError(error: unknown): AzureTtsSdkError {\n const message = error instanceof Error ? error.message : String(error);\n return new AzureTtsSdkError(message);\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\nimport { createSpeechSdkError } from \"./errors.ts\";\nimport { createSpeechConfig } from \"./speechConfig.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nfunction closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer: SpeechSDK.SpeechSynthesizer): void {\n try {\n synthesizer.close();\n } catch {}\n\n try {\n speechConfig.close();\n } catch {}\n}\n\nexport async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer> {\n if (config.signal?.aborted) {\n throw createSpeechSdkError(\"Speech synthesis was cancelled.\");\n }\n\n const speechConfig = createSpeechConfig(config);\n const synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, null);\n\n return await new Promise<ArrayBuffer>((resolve, reject) => {\n let resourcesClosed = false;\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let abortHandler: (() => void) | undefined;\n const cleanup = () => {\n if (timeout) clearTimeout(timeout);\n if (abortHandler) config.signal?.removeEventListener(\"abort\", abortHandler);\n };\n const closeResources = () => {\n if (resourcesClosed) return;\n resourcesClosed = true;\n closeSpeechResources(speechConfig, synthesizer);\n };\n const rejectWithError = (error: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n closeResources();\n reject(createSpeechSdkError(error));\n };\n\n const cb = (result: SpeechSDK.SpeechSynthesisResult) => {\n if (settled) return;\n const { reason, errorDetails } = result;\n if (reason !== SpeechSDK.ResultReason.SynthesizingAudioCompleted) {\n const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;\n rejectWithError(err);\n return;\n }\n settled = true;\n cleanup();\n closeResources();\n resolve(result.audioData);\n };\n\n try {\n if (config.signal) {\n abortHandler = () => rejectWithError(\"Speech synthesis was cancelled.\");\n config.signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n if (config.timeoutMs !== undefined && config.timeoutMs > 0) {\n timeout = setTimeout(\n () => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),\n config.timeoutMs,\n );\n }\n synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);\n } catch (error) {\n rejectWithError(error);\n }\n });\n}\n","import { SpeechConfig } from \"microsoft-cognitiveservices-speech-sdk\";\nimport { DEFAULT_OUTPUT_FORMAT, resolveOutputFormat } from \"./outputFormats.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nexport function resolveEndpoint(config: TtsConfig): string {\n const endpoint = config.endpoint?.trim() || \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n return endpoint.replace(/\\{region\\}/g, encodeURIComponent(config.region));\n}\n\nexport function createSpeechConfig(config: TtsConfig): SpeechConfig {\n const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;\n\n const endpoint = new URL(resolveEndpoint(config));\n const speechConfig = SpeechConfig.fromEndpoint(endpoint, subscriptionKey);\n speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);\n return speechConfig;\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\n\nexport const DEFAULT_OUTPUT_FORMAT = \"audio-16khz-128kbitrate-mono-mp3\";\n\nconst OUTPUT_FORMATS: Record<string, SpeechSDK.SpeechSynthesisOutputFormat> = {\n \"raw-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,\n \"riff-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,\n \"audio-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,\n \"audio-16khz-32kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,\n \"audio-16khz-128kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,\n \"audio-16khz-64kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,\n \"audio-24khz-48kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,\n \"audio-24khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,\n \"audio-24khz-160kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,\n \"raw-16khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,\n \"riff-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,\n \"riff-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,\n \"riff-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,\n \"riff-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,\n \"raw-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,\n \"raw-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,\n \"raw-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,\n \"ogg-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,\n \"ogg-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,\n \"raw-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,\n \"riff-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,\n \"audio-48khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,\n \"audio-48khz-192kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,\n \"ogg-48khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,\n \"webm-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,\n \"webm-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,\n \"webm-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,\n \"raw-24khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,\n \"raw-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,\n \"riff-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,\n \"audio-16khz-16bit-32kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,\n \"audio-24khz-16bit-48kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,\n \"audio-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,\n \"raw-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,\n \"riff-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,\n \"raw-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,\n \"riff-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,\n \"amr-wb-16000hz\": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,\n \"g722-16khz-64kbps\": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps,\n};\n\nexport function resolveOutputFormat(outputFormat: string): SpeechSDK.SpeechSynthesisOutputFormat {\n const resolvedFormat = OUTPUT_FORMATS[outputFormat];\n if (resolvedFormat === undefined) {\n throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);\n }\n\n return resolvedFormat;\n}\n","import { synthesizeSpeech } from \"./synthesis.ts\";\nimport type { AzureTtsClientOptions } from \"./types.ts\";\n\nconst ENDPOINT_TEMPLATE = \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n\nexport class AzureTtsClient {\n readonly #options: AzureTtsClientOptions;\n\n constructor(options: AzureTtsClientOptions) {\n this.#options = options;\n }\n\n async synthesize(ssml: string): Promise<ArrayBuffer> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };\n return synthesizeSpeech(ssml, config);\n }\n}\n"],"mappings":";;;;;;;;;AAAO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EAMvC,YAAY,QAAgB,YAAoB,cAAsB,WAA0B;AAC9F,UAAM,6BAA6B,MAAM,IAAI,UAAU,EAAE;AACzD,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,aAAa;AAClB,SAAK,eAAe;AACpB,SAAK,YAAY;AAAA,EACnB;AACF;AAEO,IAAM,mBAAN,cAA+B,cAAc;AAAA,EAGlD,YAAY,cAAsB;AAChC,UAAM,GAAG,cAAc,cAAc,IAAI;AACzC,SAAK,OAAO;AACZ,SAAK,UAAU,+BAA+B,YAAY;AAC1D,SAAK,eAAe;AAAA,EACtB;AACF;AAEO,SAAS,qBAAqB,OAAkC;AACrE,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,SAAO,IAAI,iBAAiB,OAAO;AACrC;;;AC9BA,YAAYA,gBAAe;;;ACA3B,SAAS,oBAAoB;;;ACA7B,YAAY,eAAe;AAEpB,IAAM,wBAAwB;AAErC,IAAM,iBAAwE;AAAA,EAC5E,4BAAsC,sCAA4B;AAAA,EAClE,gCAA0C,sCAA4B;AAAA,EACtE,iCAA2C,sCAA4B;AAAA,EACvE,mCAA6C,sCAA4B;AAAA,EACzE,oCAA8C,sCAA4B;AAAA,EAC1E,mCAA6C,sCAA4B;AAAA,EACzE,mCAA6C,sCAA4B;AAAA,EACzE,mCAA6C,sCAA4B;AAAA,EACzE,oCAA8C,sCAA4B;AAAA,EAC1E,iCAA2C,sCAA4B;AAAA,EACvE,6BAAuC,sCAA4B;AAAA,EACnE,4BAAsC,sCAA4B;AAAA,EAClE,6BAAuC,sCAA4B;AAAA,EACnE,6BAAuC,sCAA4B;AAAA,EACnE,4BAAsC,sCAA4B;AAAA,EAClE,4BAAsC,sCAA4B;AAAA,EAClE,2BAAqC,sCAA4B;AAAA,EACjE,6BAAuC,sCAA4B;AAAA,EACnE,6BAAuC,sCAA4B;AAAA,EACnE,4BAAsC,sCAA4B;AAAA,EAClE,6BAAuC,sCAA4B;AAAA,EACnE,mCAA6C,sCAA4B;AAAA,EACzE,oCAA8C,sCAA4B;AAAA,EAC1E,6BAAuC,sCAA4B;AAAA,EACnE,8BAAwC,sCAA4B;AAAA,EACpE,8BAAwC,sCAA4B;AAAA,EACpE,qCAA+C,sCAA4B;AAAA,EAC3E,iCAA2C,sCAA4B;AAAA,EACvE,2BAAqC,sCAA4B;AAAA,EACjE,4BAAsC,sCAA4B;AAAA,EAClE,sCAAgD,sCAA4B;AAAA,EAC5E,sCAAgD,sCAA4B;AAAA,EAC5E,sCAAgD,sCAA4B;AAAA,EAC5E,8BAAwC,sCAA4B;AAAA,EACpE,+BAAyC,sCAA4B;AAAA,EACrE,8BAAwC,sCAA4B;AAAA,EACpE,+BAAyC,sCAA4B;AAAA,EACrE,kBAA4B,sCAA4B;AAAA,EACxD,qBAA+B,sCAA4B;AAC7D;AAEO,SAAS,oBAAoB,cAA6D;AAC/F,QAAM,iBAAiB,eAAe,YAAY;AAClD,MAAI,mBAAmB,QAAW;AAChC,UAAM,IAAI,MAAM,2CAA2C,YAAY,EAAE;AAAA,EAC3E;AAEA,SAAO;AACT;;;ADjDO,SAAS,gBAAgB,QAA2B;AACzD,QAAM,WAAW,OAAO,UAAU,KAAK,KAAK;AAC5C,SAAO,SAAS,QAAQ,eAAe,mBAAmB,OAAO,MAAM,CAAC;AAC1E;AAEO,SAAS,mBAAmB,QAAiC;AAClE,QAAM,EAAE,eAAe,uBAAuB,gBAAgB,IAAI;AAElE,QAAM,WAAW,IAAI,IAAI,gBAAgB,MAAM,CAAC;AAChD,QAAM,eAAe,aAAa,aAAa,UAAU,eAAe;AACxE,eAAa,8BAA8B,oBAAoB,YAAY;AAC3E,SAAO;AACT;;;ADXA,SAAS,qBAAqB,cAAsC,aAAgD;AAClH,MAAI;AACF,gBAAY,MAAM;AAAA,EACpB,QAAQ;AAAA,EAAC;AAET,MAAI;AACF,iBAAa,MAAM;AAAA,EACrB,QAAQ;AAAA,EAAC;AACX;AAEA,eAAsB,iBAAiB,MAAc,QAAyC;AAC5F,MAAI,OAAO,QAAQ,SAAS;AAC1B,UAAM,qBAAqB,iCAAiC;AAAA,EAC9D;AAEA,QAAM,eAAe,mBAAmB,MAAM;AAC9C,QAAM,cAAc,IAAc,6BAAkB,cAAc,IAAI;AAEtE,SAAO,MAAM,IAAI,QAAqB,CAAC,SAAS,WAAW;AACzD,QAAI,kBAAkB;AACtB,QAAI,UAAU;AACd,QAAI;AACJ,QAAI;AACJ,UAAM,UAAU,MAAM;AACpB,UAAI,QAAS,cAAa,OAAO;AACjC,UAAI,aAAc,QAAO,QAAQ,oBAAoB,SAAS,YAAY;AAAA,IAC5E;AACA,UAAM,iBAAiB,MAAM;AAC3B,UAAI,gBAAiB;AACrB,wBAAkB;AAClB,2BAAqB,cAAc,WAAW;AAAA,IAChD;AACA,UAAM,kBAAkB,CAAC,UAAmB;AAC1C,UAAI,QAAS;AACb,gBAAU;AACV,cAAQ;AACR,qBAAe;AACf,aAAO,qBAAqB,KAAK,CAAC;AAAA,IACpC;AAEA,UAAM,KAAK,CAAC,WAA4C;AACtD,UAAI,QAAS;AACb,YAAM,EAAE,QAAQ,aAAa,IAAI;AACjC,UAAI,WAAqB,wBAAa,4BAA4B;AAChE,cAAM,MAAM,gBAAgB,uCAAuC,MAAM;AACzE,wBAAgB,GAAG;AACnB;AAAA,MACF;AACA,gBAAU;AACV,cAAQ;AACR,qBAAe;AACf,cAAQ,OAAO,SAAS;AAAA,IAC1B;AAEA,QAAI;AACF,UAAI,OAAO,QAAQ;AACjB,uBAAe,MAAM,gBAAgB,iCAAiC;AACtE,eAAO,OAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,KAAK,CAAC;AAAA,MACtE;AACA,UAAI,OAAO,cAAc,UAAa,OAAO,YAAY,GAAG;AAC1D,kBAAU;AAAA,UACR,MAAM,gBAAgB,oCAAoC,OAAO,SAAS,MAAM;AAAA,UAChF,OAAO;AAAA,QACT;AAAA,MACF;AACA,kBAAY,eAAe,MAAM,IAAI,eAAe;AAAA,IACtD,SAAS,OAAO;AACd,sBAAgB,KAAK;AAAA,IACvB;AAAA,EACF,CAAC;AACH;;;AGxEA,IAAM,oBAAoB;AAH1B;AAKO,IAAM,iBAAN,MAAqB;AAAA,EAG1B,YAAY,SAAgC;AAF5C,uBAAS;AAGP,uBAAK,UAAW;AAAA,EAClB;AAAA,EAEA,MAAM,WAAW,MAAoC;AACnD,UAAM,EAAE,QAAQ,iBAAiB,cAAc,QAAQ,UAAU,IAAI,mBAAK;AAC1E,UAAM,WAAW,mBAAK,UAAS,UAAU,KAAK,KAAK,kBAAkB,QAAQ,YAAY,MAAM;AAC/F,uBAAK,UAAS,QAAQ,QAAQ,6BAA6B,QAAQ;AAEnE,UAAM,SAAS,EAAE,UAAU,QAAQ,iBAAiB,cAAc,QAAQ,UAAU;AACpF,WAAO,iBAAiB,MAAM,MAAM;AAAA,EACtC;AACF;AAdW;","names":["SpeechSDK"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/synthesis.ts","../src/speechConfig.ts","../src/outputFormats.ts","../src/client.ts","../src/voiceCatalog.ts"],"sourcesContent":["export class AzureTtsError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly responseBody: string;\n readonly requestId: string | null;\n\n constructor(status: number, statusText: string, responseBody: string, requestId: string | null) {\n super(`Azure TTS request failed: ${status} ${statusText}`);\n this.name = \"AzureTtsError\";\n this.status = status;\n this.statusText = statusText;\n this.responseBody = responseBody;\n this.requestId = requestId;\n }\n}\n\nexport class AzureTtsSdkError extends AzureTtsError {\n readonly errorDetails: string;\n\n constructor(errorDetails: string) {\n super(0, \"Speech SDK\", errorDetails, null);\n this.name = \"AzureTtsSdkError\";\n this.message = `Azure TTS synthesis failed: ${errorDetails}`;\n this.errorDetails = errorDetails;\n }\n}\n\nexport function createSpeechSdkError(error: unknown): AzureTtsSdkError {\n const message = error instanceof Error ? error.message : String(error);\n return new AzureTtsSdkError(message);\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\nimport { createSpeechSdkError } from \"./errors.ts\";\nimport { createSpeechConfig } from \"./speechConfig.ts\";\nimport type { SsmlSynthesisResult, TtsConfig } from \"./types.ts\";\n\nfunction closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer: SpeechSDK.SpeechSynthesizer): void {\n try {\n synthesizer.close();\n } catch {}\n\n try {\n speechConfig.close();\n } catch {}\n}\n\nconst ticksToMilliseconds = (ticks: number): number => Math.max(0, ticks) / 10_000;\n\nexport async function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult> {\n if (config.signal?.aborted) {\n throw createSpeechSdkError(\"Speech synthesis was cancelled.\");\n }\n\n const speechConfig = createSpeechConfig(config);\n const synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, null);\n\n return await new Promise<SsmlSynthesisResult>((resolve, reject) => {\n let resourcesClosed = false;\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let abortHandler: (() => void) | undefined;\n const cleanup = () => {\n if (timeout) clearTimeout(timeout);\n if (abortHandler) config.signal?.removeEventListener(\"abort\", abortHandler);\n };\n const closeResources = () => {\n if (resourcesClosed) return;\n resourcesClosed = true;\n closeSpeechResources(speechConfig, synthesizer);\n };\n const rejectWithError = (error: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n closeResources();\n reject(createSpeechSdkError(error));\n };\n\n const boundaries: SsmlSynthesisResult[\"boundaries\"] = [];\n const visemes: SsmlSynthesisResult[\"visemes\"] = [];\n const bookmarks: SsmlSynthesisResult[\"bookmarks\"] = [];\n synthesizer.wordBoundary = (_sender, event) => {\n boundaries.push({\n text: event.text,\n audioOffsetMs: ticksToMilliseconds(event.audioOffset),\n durationMs: ticksToMilliseconds(event.duration),\n });\n };\n synthesizer.visemeReceived = (_sender, event) => {\n visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });\n };\n synthesizer.bookmarkReached = (_sender, event) => {\n bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });\n };\n\n const cb = (result: SpeechSDK.SpeechSynthesisResult) => {\n if (settled) return;\n const { reason, errorDetails } = result;\n if (reason !== SpeechSDK.ResultReason.SynthesizingAudioCompleted) {\n const err = errorDetails || `Speech synthesis failed with reason ${reason}.`;\n rejectWithError(err);\n return;\n }\n settled = true;\n cleanup();\n closeResources();\n const eventDurationMs = Math.max(\n 0,\n ...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),\n ...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),\n ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs),\n );\n const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;\n resolve({\n audioData: result.audioData,\n durationMs,\n ...(boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {}),\n ...(visemes.length > 0 ? { visemes } : {}),\n ...(bookmarks.length > 0 ? { bookmarks } : {}),\n });\n };\n\n try {\n if (config.signal) {\n abortHandler = () => rejectWithError(\"Speech synthesis was cancelled.\");\n config.signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n if (config.timeoutMs !== undefined && config.timeoutMs > 0) {\n timeout = setTimeout(\n () => rejectWithError(`Speech synthesis timed out after ${config.timeoutMs} ms.`),\n config.timeoutMs,\n );\n }\n synthesizer.speakSsmlAsync(ssml, cb, rejectWithError);\n } catch (error) {\n rejectWithError(error);\n }\n });\n}\n\n/** Backward-compatible audio-only synthesis helper. */\nexport async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer> {\n return (await synthesizeSsml(ssml, config)).audioData;\n}\n","import { SpeechConfig } from \"microsoft-cognitiveservices-speech-sdk\";\nimport { DEFAULT_OUTPUT_FORMAT, resolveOutputFormat } from \"./outputFormats.ts\";\nimport type { TtsConfig } from \"./types.ts\";\n\nexport function resolveEndpoint(config: TtsConfig): string {\n const endpoint = config.endpoint?.trim() || \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n return endpoint.replace(/\\{region\\}/g, encodeURIComponent(config.region));\n}\n\nexport function createSpeechConfig(config: TtsConfig): SpeechConfig {\n const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;\n\n const endpoint = new URL(resolveEndpoint(config));\n const speechConfig = SpeechConfig.fromEndpoint(endpoint, subscriptionKey);\n speechConfig.speechSynthesisOutputFormat = resolveOutputFormat(outputFormat);\n return speechConfig;\n}\n","import * as SpeechSDK from \"microsoft-cognitiveservices-speech-sdk\";\n\nexport const DEFAULT_OUTPUT_FORMAT = \"audio-16khz-128kbitrate-mono-mp3\";\n\nconst OUTPUT_FORMATS: Record<string, SpeechSDK.SpeechSynthesisOutputFormat> = {\n \"raw-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoMULaw,\n \"riff-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16KbpsMonoSiren,\n \"audio-16khz-16kbps-mono-siren\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16KbpsMonoSiren,\n \"audio-16khz-32kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3,\n \"audio-16khz-128kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz128KBitRateMonoMp3,\n \"audio-16khz-64kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz64KBitRateMonoMp3,\n \"audio-24khz-48kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3,\n \"audio-24khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3,\n \"audio-24khz-160kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz160KBitRateMonoMp3,\n \"raw-16khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoTrueSilk,\n \"riff-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm,\n \"riff-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz16BitMonoPcm,\n \"riff-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm,\n \"riff-8khz-8bit-mono-mulaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoMULaw,\n \"raw-16khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw16Khz16BitMonoPcm,\n \"raw-24khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm,\n \"raw-8khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz16BitMonoPcm,\n \"ogg-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg16Khz16BitMonoOpus,\n \"ogg-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg24Khz16BitMonoOpus,\n \"raw-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw48Khz16BitMonoPcm,\n \"riff-48khz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff48Khz16BitMonoPcm,\n \"audio-48khz-96kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3,\n \"audio-48khz-192kbitrate-mono-mp3\": SpeechSDK.SpeechSynthesisOutputFormat.Audio48Khz192KBitRateMonoMp3,\n \"ogg-48khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Ogg48Khz16BitMonoOpus,\n \"webm-16khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm16Khz16BitMonoOpus,\n \"webm-24khz-16bit-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16BitMonoOpus,\n \"webm-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Webm24Khz16Bit24KbpsMonoOpus,\n \"raw-24khz-16bit-mono-truesilk\": SpeechSDK.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoTrueSilk,\n \"raw-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Raw8Khz8BitMonoALaw,\n \"riff-8khz-8bit-mono-alaw\": SpeechSDK.SpeechSynthesisOutputFormat.Riff8Khz8BitMonoALaw,\n \"audio-16khz-16bit-32kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio16Khz16Bit32KbpsMonoOpus,\n \"audio-24khz-16bit-48kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit48KbpsMonoOpus,\n \"audio-24khz-16bit-24kbps-mono-opus\": SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz16Bit24KbpsMonoOpus,\n \"raw-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw22050Hz16BitMonoPcm,\n \"riff-22050hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff22050Hz16BitMonoPcm,\n \"raw-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Raw44100Hz16BitMonoPcm,\n \"riff-44100hz-16bit-mono-pcm\": SpeechSDK.SpeechSynthesisOutputFormat.Riff44100Hz16BitMonoPcm,\n \"amr-wb-16000hz\": SpeechSDK.SpeechSynthesisOutputFormat.AmrWb16000Hz,\n \"g722-16khz-64kbps\": SpeechSDK.SpeechSynthesisOutputFormat.G72216Khz64Kbps,\n};\n\nexport function resolveOutputFormat(outputFormat: string): SpeechSDK.SpeechSynthesisOutputFormat {\n const resolvedFormat = OUTPUT_FORMATS[outputFormat];\n if (resolvedFormat === undefined) {\n throw new Error(`Unsupported Azure Speech output format: ${outputFormat}`);\n }\n\n return resolvedFormat;\n}\n","import { synthesizeSpeech, synthesizeSsml } from \"./synthesis.ts\";\nimport type { AzureTtsClientOptions, SsmlSynthesisResult } from \"./types.ts\";\n\nconst ENDPOINT_TEMPLATE = \"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1\";\n\nexport class AzureTtsClient {\n readonly #options: AzureTtsClientOptions;\n\n constructor(options: AzureTtsClientOptions) {\n this.#options = options;\n }\n\n async synthesize(ssml: string): Promise<ArrayBuffer> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };\n return synthesizeSpeech(ssml, config);\n }\n\n async synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult> {\n const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;\n const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace(\"{region}\", region);\n this.#options.logger?.debug?.(\"Using Azure TTS endpoint:\", endpoint);\n\n return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });\n }\n}\n","const AZURE_VOICE_API_VERSION = \"2025-10-01\";\n\nexport interface FetchAzureVoiceCatalogOptions {\n apiKey: string;\n region: string | string[];\n}\n\nexport interface AzureVoiceCatalogVoice {\n name: string;\n locale: string;\n secondaryLocales?: readonly string[];\n styles?: readonly string[];\n regions: readonly string[];\n status?: \"ga\" | \"preview\" | \"deprecated\";\n}\n\nexport interface FetchedAzureVoiceCatalogMetadata {\n voiceCount: number;\n generatedAt: string;\n apiVersion: string;\n regions: readonly string[];\n}\n\nexport interface AzureVoiceCatalog {\n voices: readonly AzureVoiceCatalogVoice[];\n metadata: FetchedAzureVoiceCatalogMetadata;\n}\n\ninterface AzureVoiceApiRecord {\n Locale?: unknown;\n Name?: unknown;\n SecondaryLocaleList?: unknown;\n ShortName?: unknown;\n Status?: unknown;\n StyleList?: unknown;\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction stringList(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return [...new Set(value.map(stringValue).filter((item): item is string => item !== undefined))];\n}\n\nfunction normalizeStatus(value: unknown): AzureVoiceCatalogVoice[\"status\"] {\n const status = stringValue(value)?.toLowerCase();\n if (status === \"preview\" || status === \"deprecated\" || status === \"ga\") return status;\n return undefined;\n}\n\nfunction normalizeRegions(region: string | string[]): string[] {\n const regions = Array.isArray(region) ? region : [region];\n const result = [...new Set(regions.map((item) => item.trim()).filter(Boolean))];\n if (result.length === 0) throw new TypeError(\"At least one Azure Speech region is required.\");\n return result;\n}\n\nasync function fetchRegionVoices(region: string, apiKey: string): Promise<AzureVoiceApiRecord[]> {\n const endpoint = `https://${encodeURIComponent(region)}.tts.speech.microsoft.com/cognitiveservices/voices/list`;\n const response = await fetch(endpoint, {\n headers: {\n Accept: \"application/json\",\n \"Ocp-Apim-Subscription-Key\": apiKey,\n },\n });\n if (!response.ok) {\n throw new Error(`Azure List Voices API request failed for region \"${region}\" with HTTP ${response.status}.`);\n }\n const payload: unknown = await response.json();\n if (!Array.isArray(payload)) throw new Error(`Azure List Voices API returned an invalid response for \"${region}\".`);\n return payload.filter((item): item is AzureVoiceApiRecord => Boolean(item && typeof item === \"object\"));\n}\n\n/** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */\nexport async function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog> {\n if (!options || typeof options.apiKey !== \"string\" || !options.apiKey.trim())\n throw new TypeError(\"An Azure Speech API key is required.\");\n const regions = normalizeRegions(options.region);\n const payloads = await Promise.all(regions.map((region) => fetchRegionVoices(region, options.apiKey)));\n const voices = new Map<string, AzureVoiceCatalogVoice>();\n\n for (let regionIndex = 0; regionIndex < payloads.length; regionIndex += 1) {\n const region = regions[regionIndex];\n for (const record of payloads[regionIndex]) {\n const name = stringValue(record.ShortName) ?? stringValue(record.Name);\n const locale = stringValue(record.Locale);\n if (!name || !locale) continue;\n const key = name.toLowerCase();\n const existing = voices.get(key);\n const secondaryLocales = stringList(record.SecondaryLocaleList);\n const styles = stringList(record.StyleList);\n const status = normalizeStatus(record.Status);\n const merged: AzureVoiceCatalogVoice = {\n name: existing?.name ?? name,\n locale: existing?.locale ?? locale,\n regions: [...new Set([...(existing?.regions ?? []), region])],\n };\n const mergedSecondaryLocales = [...new Set([...(existing?.secondaryLocales ?? []), ...secondaryLocales])];\n if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;\n const mergedStyles = [...new Set([...(existing?.styles ?? []), ...styles])];\n if (mergedStyles.length > 0) merged.styles = mergedStyles;\n if (status) merged.status = status;\n else if (existing?.status) merged.status = existing.status;\n voices.set(key, merged);\n }\n }\n\n const sortedVoices = [...voices.values()].sort((first, second) => first.name.localeCompare(second.name));\n return {\n voices: sortedVoices,\n metadata: {\n voiceCount: sortedVoices.length,\n generatedAt: new Date().toISOString(),\n apiVersion: AZURE_VOICE_API_VERSION,\n regions,\n },\n };\n}\n"],"mappings":"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","names":["SpeechSDK"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssml-builder-js/azure-tts-client",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "description": "Azure Text-to-Speech client using the Microsoft Speech SDK",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/client.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { synthesizeSpeech } from "./synthesis.ts";
2
- import type { AzureTtsClientOptions } from "./types.ts";
1
+ import { synthesizeSpeech, synthesizeSsml } from "./synthesis.ts";
2
+ import type { AzureTtsClientOptions, SsmlSynthesisResult } from "./types.ts";
3
3
 
4
4
  const ENDPOINT_TEMPLATE = "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
5
5
 
@@ -18,4 +18,12 @@ export class AzureTtsClient {
18
18
  const config = { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs };
19
19
  return synthesizeSpeech(ssml, config);
20
20
  }
21
+
22
+ async synthesizeSsml(ssml: string): Promise<SsmlSynthesisResult> {
23
+ const { region, subscriptionKey, outputFormat, signal, timeoutMs } = this.#options;
24
+ const endpoint = this.#options.endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
25
+ this.#options.logger?.debug?.("Using Azure TTS endpoint:", endpoint);
26
+
27
+ return synthesizeSsml(ssml, { endpoint, region, subscriptionKey, outputFormat, signal, timeoutMs });
28
+ }
21
29
  }
package/src/index.ts CHANGED
@@ -2,7 +2,23 @@
2
2
  * azure-tts-client: Azure Text-to-Speech client for SSML playback.
3
3
  */
4
4
 
5
- export type { AzureTtsClientOptions, AzureTtsLogger, TtsConfig } from "./types.ts";
5
+ export type {
6
+ AzureTtsClientOptions,
7
+ AzureTtsLogger,
8
+ SsmlSynthesisBookmark,
9
+ SsmlSynthesisBoundary,
10
+ SsmlSynthesisResult,
11
+ SsmlSynthesisViseme,
12
+ TtsConfig,
13
+ } from "./types.ts";
6
14
  export { AzureTtsError, AzureTtsSdkError } from "./errors.ts";
7
15
  export { AzureTtsClient } from "./client.ts";
8
16
  export { synthesizeSpeech } from "./synthesis.ts";
17
+ export { synthesizeSsml } from "./synthesis.ts";
18
+ export { fetchAzureVoiceCatalog } from "./voiceCatalog.ts";
19
+ export type {
20
+ AzureVoiceCatalog,
21
+ AzureVoiceCatalogVoice,
22
+ FetchedAzureVoiceCatalogMetadata,
23
+ FetchAzureVoiceCatalogOptions,
24
+ } from "./voiceCatalog.ts";
package/src/synthesis.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as SpeechSDK from "microsoft-cognitiveservices-speech-sdk";
2
2
  import { createSpeechSdkError } from "./errors.ts";
3
3
  import { createSpeechConfig } from "./speechConfig.ts";
4
- import type { TtsConfig } from "./types.ts";
4
+ import type { SsmlSynthesisResult, TtsConfig } from "./types.ts";
5
5
 
6
6
  function closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer: SpeechSDK.SpeechSynthesizer): void {
7
7
  try {
@@ -13,7 +13,9 @@ function closeSpeechResources(speechConfig: SpeechSDK.SpeechConfig, synthesizer:
13
13
  } catch {}
14
14
  }
15
15
 
16
- export async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer> {
16
+ const ticksToMilliseconds = (ticks: number): number => Math.max(0, ticks) / 10_000;
17
+
18
+ export async function synthesizeSsml(ssml: string, config: TtsConfig): Promise<SsmlSynthesisResult> {
17
19
  if (config.signal?.aborted) {
18
20
  throw createSpeechSdkError("Speech synthesis was cancelled.");
19
21
  }
@@ -21,7 +23,7 @@ export async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise
21
23
  const speechConfig = createSpeechConfig(config);
22
24
  const synthesizer = new SpeechSDK.SpeechSynthesizer(speechConfig, null);
23
25
 
24
- return await new Promise<ArrayBuffer>((resolve, reject) => {
26
+ return await new Promise<SsmlSynthesisResult>((resolve, reject) => {
25
27
  let resourcesClosed = false;
26
28
  let settled = false;
27
29
  let timeout: ReturnType<typeof setTimeout> | undefined;
@@ -43,6 +45,23 @@ export async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise
43
45
  reject(createSpeechSdkError(error));
44
46
  };
45
47
 
48
+ const boundaries: SsmlSynthesisResult["boundaries"] = [];
49
+ const visemes: SsmlSynthesisResult["visemes"] = [];
50
+ const bookmarks: SsmlSynthesisResult["bookmarks"] = [];
51
+ synthesizer.wordBoundary = (_sender, event) => {
52
+ boundaries.push({
53
+ text: event.text,
54
+ audioOffsetMs: ticksToMilliseconds(event.audioOffset),
55
+ durationMs: ticksToMilliseconds(event.duration),
56
+ });
57
+ };
58
+ synthesizer.visemeReceived = (_sender, event) => {
59
+ visemes.push({ visemeId: event.visemeId, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
60
+ };
61
+ synthesizer.bookmarkReached = (_sender, event) => {
62
+ bookmarks.push({ name: event.text, audioOffsetMs: ticksToMilliseconds(event.audioOffset) });
63
+ };
64
+
46
65
  const cb = (result: SpeechSDK.SpeechSynthesisResult) => {
47
66
  if (settled) return;
48
67
  const { reason, errorDetails } = result;
@@ -54,7 +73,20 @@ export async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise
54
73
  settled = true;
55
74
  cleanup();
56
75
  closeResources();
57
- resolve(result.audioData);
76
+ const eventDurationMs = Math.max(
77
+ 0,
78
+ ...(boundaries ?? []).map((boundary) => boundary.audioOffsetMs + boundary.durationMs),
79
+ ...(visemes ?? []).map((viseme) => viseme.audioOffsetMs),
80
+ ...(bookmarks ?? []).map((bookmark) => bookmark.audioOffsetMs),
81
+ );
82
+ const durationMs = result.audioDuration ? ticksToMilliseconds(result.audioDuration) : eventDurationMs;
83
+ resolve({
84
+ audioData: result.audioData,
85
+ durationMs,
86
+ ...(boundaries.length > 0 ? { boundaries, wordBoundary: boundaries, wordBoundaries: boundaries } : {}),
87
+ ...(visemes.length > 0 ? { visemes } : {}),
88
+ ...(bookmarks.length > 0 ? { bookmarks } : {}),
89
+ });
58
90
  };
59
91
 
60
92
  try {
@@ -74,3 +106,8 @@ export async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise
74
106
  }
75
107
  });
76
108
  }
109
+
110
+ /** Backward-compatible audio-only synthesis helper. */
111
+ export async function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer> {
112
+ return (await synthesizeSsml(ssml, config)).audioData;
113
+ }
package/src/types.ts CHANGED
@@ -7,6 +7,35 @@ export interface TtsConfig {
7
7
  outputFormat?: string;
8
8
  }
9
9
 
10
+ export interface SsmlSynthesisBoundary {
11
+ text: string;
12
+ audioOffsetMs: number;
13
+ durationMs: number;
14
+ }
15
+
16
+ export interface SsmlSynthesisViseme {
17
+ visemeId: number;
18
+ audioOffsetMs: number;
19
+ }
20
+
21
+ export interface SsmlSynthesisBookmark {
22
+ name: string;
23
+ audioOffsetMs: number;
24
+ }
25
+
26
+ /** Audio and Azure Speech synchronization events emitted for one SSML request. */
27
+ export interface SsmlSynthesisResult {
28
+ audioData: ArrayBuffer;
29
+ durationMs: number;
30
+ boundaries?: SsmlSynthesisBoundary[];
31
+ /** Alias matching the Azure Speech event name. */
32
+ wordBoundary?: SsmlSynthesisBoundary[];
33
+ /** Alias for consumers that use Azure's word-boundary terminology. */
34
+ wordBoundaries?: SsmlSynthesisBoundary[];
35
+ visemes?: SsmlSynthesisViseme[];
36
+ bookmarks?: SsmlSynthesisBookmark[];
37
+ }
38
+
10
39
  export interface AzureTtsLogger {
11
40
  debug?: (...args: unknown[]) => void;
12
41
  info?: (...args: unknown[]) => void;
@@ -0,0 +1,120 @@
1
+ const AZURE_VOICE_API_VERSION = "2025-10-01";
2
+
3
+ export interface FetchAzureVoiceCatalogOptions {
4
+ apiKey: string;
5
+ region: string | string[];
6
+ }
7
+
8
+ export interface AzureVoiceCatalogVoice {
9
+ name: string;
10
+ locale: string;
11
+ secondaryLocales?: readonly string[];
12
+ styles?: readonly string[];
13
+ regions: readonly string[];
14
+ status?: "ga" | "preview" | "deprecated";
15
+ }
16
+
17
+ export interface FetchedAzureVoiceCatalogMetadata {
18
+ voiceCount: number;
19
+ generatedAt: string;
20
+ apiVersion: string;
21
+ regions: readonly string[];
22
+ }
23
+
24
+ export interface AzureVoiceCatalog {
25
+ voices: readonly AzureVoiceCatalogVoice[];
26
+ metadata: FetchedAzureVoiceCatalogMetadata;
27
+ }
28
+
29
+ interface AzureVoiceApiRecord {
30
+ Locale?: unknown;
31
+ Name?: unknown;
32
+ SecondaryLocaleList?: unknown;
33
+ ShortName?: unknown;
34
+ Status?: unknown;
35
+ StyleList?: unknown;
36
+ }
37
+
38
+ function stringValue(value: unknown): string | undefined {
39
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
40
+ }
41
+
42
+ function stringList(value: unknown): string[] {
43
+ if (!Array.isArray(value)) return [];
44
+ return [...new Set(value.map(stringValue).filter((item): item is string => item !== undefined))];
45
+ }
46
+
47
+ function normalizeStatus(value: unknown): AzureVoiceCatalogVoice["status"] {
48
+ const status = stringValue(value)?.toLowerCase();
49
+ if (status === "preview" || status === "deprecated" || status === "ga") return status;
50
+ return undefined;
51
+ }
52
+
53
+ function normalizeRegions(region: string | string[]): string[] {
54
+ const regions = Array.isArray(region) ? region : [region];
55
+ const result = [...new Set(regions.map((item) => item.trim()).filter(Boolean))];
56
+ if (result.length === 0) throw new TypeError("At least one Azure Speech region is required.");
57
+ return result;
58
+ }
59
+
60
+ async function fetchRegionVoices(region: string, apiKey: string): Promise<AzureVoiceApiRecord[]> {
61
+ const endpoint = `https://${encodeURIComponent(region)}.tts.speech.microsoft.com/cognitiveservices/voices/list`;
62
+ const response = await fetch(endpoint, {
63
+ headers: {
64
+ Accept: "application/json",
65
+ "Ocp-Apim-Subscription-Key": apiKey,
66
+ },
67
+ });
68
+ if (!response.ok) {
69
+ throw new Error(`Azure List Voices API request failed for region "${region}" with HTTP ${response.status}.`);
70
+ }
71
+ const payload: unknown = await response.json();
72
+ if (!Array.isArray(payload)) throw new Error(`Azure List Voices API returned an invalid response for "${region}".`);
73
+ return payload.filter((item): item is AzureVoiceApiRecord => Boolean(item && typeof item === "object"));
74
+ }
75
+
76
+ /** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */
77
+ export async function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog> {
78
+ if (!options || typeof options.apiKey !== "string" || !options.apiKey.trim())
79
+ throw new TypeError("An Azure Speech API key is required.");
80
+ const regions = normalizeRegions(options.region);
81
+ const payloads = await Promise.all(regions.map((region) => fetchRegionVoices(region, options.apiKey)));
82
+ const voices = new Map<string, AzureVoiceCatalogVoice>();
83
+
84
+ for (let regionIndex = 0; regionIndex < payloads.length; regionIndex += 1) {
85
+ const region = regions[regionIndex];
86
+ for (const record of payloads[regionIndex]) {
87
+ const name = stringValue(record.ShortName) ?? stringValue(record.Name);
88
+ const locale = stringValue(record.Locale);
89
+ if (!name || !locale) continue;
90
+ const key = name.toLowerCase();
91
+ const existing = voices.get(key);
92
+ const secondaryLocales = stringList(record.SecondaryLocaleList);
93
+ const styles = stringList(record.StyleList);
94
+ const status = normalizeStatus(record.Status);
95
+ const merged: AzureVoiceCatalogVoice = {
96
+ name: existing?.name ?? name,
97
+ locale: existing?.locale ?? locale,
98
+ regions: [...new Set([...(existing?.regions ?? []), region])],
99
+ };
100
+ const mergedSecondaryLocales = [...new Set([...(existing?.secondaryLocales ?? []), ...secondaryLocales])];
101
+ if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;
102
+ const mergedStyles = [...new Set([...(existing?.styles ?? []), ...styles])];
103
+ if (mergedStyles.length > 0) merged.styles = mergedStyles;
104
+ if (status) merged.status = status;
105
+ else if (existing?.status) merged.status = existing.status;
106
+ voices.set(key, merged);
107
+ }
108
+ }
109
+
110
+ const sortedVoices = [...voices.values()].sort((first, second) => first.name.localeCompare(second.name));
111
+ return {
112
+ voices: sortedVoices,
113
+ metadata: {
114
+ voiceCount: sortedVoices.length,
115
+ generatedAt: new Date().toISOString(),
116
+ apiVersion: AZURE_VOICE_API_VERSION,
117
+ regions,
118
+ },
119
+ };
120
+ }
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test, { type TestContext } from "node:test";
3
3
  import * as SpeechSDK from "microsoft-cognitiveservices-speech-sdk";
4
- import { synthesizeSpeech } from "../src/index.ts";
4
+ import { synthesizeSpeech, synthesizeSsml } from "../src/index.ts";
5
5
 
6
6
  const endpoint = "https://speech.example.test/cognitiveservices/v1";
7
7
  const subscriptionKey = "subscription-key";
@@ -51,6 +51,45 @@ test("synthesizeSpeech aborts the SDK request and settles its promise", async (t
51
51
  assert.equal(closeCount, 1);
52
52
  });
53
53
 
54
+ test("synthesizeSsml returns word boundaries, visemes, bookmarks, and duration", async (t) => {
55
+ const originalFromEndpoint = SpeechSDK.SpeechConfig.fromEndpoint;
56
+ t.mock.method(SpeechSDK.SpeechConfig, "fromEndpoint", (speechEndpoint, key) =>
57
+ originalFromEndpoint(speechEndpoint, String(key)),
58
+ );
59
+ t.mock.method(
60
+ SpeechSDK.SpeechSynthesizer.prototype,
61
+ "speakSsmlAsync",
62
+ function (this: SpeechSDK.SpeechSynthesizer, _ssml, callback) {
63
+ this.wordBoundary?.(this, {
64
+ text: "Hello",
65
+ audioOffset: 1_000_000,
66
+ duration: 250_000,
67
+ } as SpeechSDK.SpeechSynthesisWordBoundaryEventArgs);
68
+ this.visemeReceived?.(this, { visemeId: 4, audioOffset: 2_000_000 } as SpeechSDK.SpeechSynthesisVisemeEventArgs);
69
+ this.bookmarkReached?.(this, {
70
+ text: "chapter-1",
71
+ audioOffset: 3_000_000,
72
+ } as SpeechSDK.SpeechSynthesisBookmarkEventArgs);
73
+ callback?.({
74
+ audioData: new ArrayBuffer(2),
75
+ audioDuration: 4_000_000,
76
+ errorDetails: "",
77
+ reason: SpeechSDK.ResultReason.SynthesizingAudioCompleted,
78
+ } as SpeechSDK.SpeechSynthesisResult);
79
+ },
80
+ );
81
+ t.mock.method(SpeechSDK.SpeechSynthesizer.prototype, "close", () => undefined);
82
+
83
+ const result = await synthesizeSsml("<speak>Hello</speak>", { endpoint, subscriptionKey, region });
84
+
85
+ assert.deepEqual(result.boundaries, [{ text: "Hello", audioOffsetMs: 100, durationMs: 25 }]);
86
+ assert.deepEqual(result.wordBoundary, result.boundaries);
87
+ assert.deepEqual(result.wordBoundaries, result.boundaries);
88
+ assert.deepEqual(result.visemes, [{ visemeId: 4, audioOffsetMs: 200 }]);
89
+ assert.deepEqual(result.bookmarks, [{ name: "chapter-1", audioOffsetMs: 300 }]);
90
+ assert.equal(result.durationMs, 400);
91
+ });
92
+
54
93
  test("synthesizeSpeech aborts the SDK request on timeout and settles its promise", async (t) => {
55
94
  let closeCount = 0;
56
95
  let resultCallback: ((result: SpeechSDK.SpeechSynthesisResult) => void) | undefined;
@@ -0,0 +1,51 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { fetchAzureVoiceCatalog } from "../src/index.ts";
4
+
5
+ test("fetchAzureVoiceCatalog fetches, merges, and annotates voices across regions", async () => {
6
+ const originalFetch = globalThis.fetch;
7
+ const requests: string[] = [];
8
+ globalThis.fetch = async (input) => {
9
+ const url = String(input);
10
+ requests.push(url);
11
+ const region = url.split(".")[0]?.replace("https://", "");
12
+ return new Response(
13
+ JSON.stringify(
14
+ region === "eastus"
15
+ ? [
16
+ { Locale: "fil-PH", ShortName: "fil-PH-BlessicaNeural", StyleList: ["cheerful"] },
17
+ { Locale: "en-US", Name: "en-US-JennyNeural", StyleList: ["chat"] },
18
+ ]
19
+ : [{ Locale: "fil-PH", ShortName: "fil-PH-BlessicaNeural", StyleList: ["sad"] }],
20
+ ),
21
+ { status: 200, headers: { "content-type": "application/json" } },
22
+ );
23
+ };
24
+ try {
25
+ const catalog = await fetchAzureVoiceCatalog({ apiKey: "secret", region: ["eastus", "japaneast"] });
26
+ assert.equal(requests.length, 2);
27
+ assert.deepEqual(catalog.metadata.regions, ["eastus", "japaneast"]);
28
+ assert.equal(catalog.metadata.voiceCount, 2);
29
+ assert.deepEqual(
30
+ catalog.voices.find(({ name }) => name === "fil-PH-BlessicaNeural"),
31
+ {
32
+ name: "fil-PH-BlessicaNeural",
33
+ locale: "fil-PH",
34
+ styles: ["cheerful", "sad"],
35
+ regions: ["eastus", "japaneast"],
36
+ },
37
+ );
38
+ } finally {
39
+ globalThis.fetch = originalFetch;
40
+ }
41
+ });
42
+
43
+ test("fetchAzureVoiceCatalog reports HTTP and payload failures", async () => {
44
+ const originalFetch = globalThis.fetch;
45
+ globalThis.fetch = async () => new Response("no", { status: 401 });
46
+ try {
47
+ await assert.rejects(fetchAzureVoiceCatalog({ apiKey: "secret", region: "eastus" }), /HTTP 401/);
48
+ } finally {
49
+ globalThis.fetch = originalFetch;
50
+ }
51
+ });