ssml-builder-js 2.9.0 → 2.11.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/dist/elements.mjs CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  resolveExpressAsStyles,
37
37
  updateEditableText,
38
38
  validateAzureSsml
39
- } from "./chunk-JNJVTEL6.mjs";
39
+ } from "./chunk-FWWMWI2C.mjs";
40
40
  import "./chunk-6S5ODO6A.mjs";
41
41
 
42
42
  // packages/ssml-editor-react/src/ssmlInsertions.ts
package/dist/index.d.mts CHANGED
@@ -44,4 +44,29 @@ declare class AzureTtsClient {
44
44
 
45
45
  declare function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer>;
46
46
 
47
- export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type TtsConfig, synthesizeSpeech };
47
+ interface FetchAzureVoiceCatalogOptions {
48
+ apiKey: string;
49
+ region: string | string[];
50
+ }
51
+ interface AzureVoiceCatalogVoice {
52
+ name: string;
53
+ locale: string;
54
+ secondaryLocales?: readonly string[];
55
+ styles?: readonly string[];
56
+ regions: readonly string[];
57
+ status?: "ga" | "preview" | "deprecated";
58
+ }
59
+ interface FetchedAzureVoiceCatalogMetadata {
60
+ voiceCount: number;
61
+ generatedAt: string;
62
+ apiVersion: string;
63
+ regions: readonly string[];
64
+ }
65
+ interface AzureVoiceCatalog {
66
+ voices: readonly AzureVoiceCatalogVoice[];
67
+ metadata: FetchedAzureVoiceCatalogMetadata;
68
+ }
69
+ /** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */
70
+ declare function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog>;
71
+
72
+ export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type AzureVoiceCatalog, type AzureVoiceCatalogVoice, type FetchAzureVoiceCatalogOptions, type FetchedAzureVoiceCatalogMetadata, type TtsConfig, fetchAzureVoiceCatalog, synthesizeSpeech };
package/dist/index.d.ts CHANGED
@@ -44,4 +44,29 @@ declare class AzureTtsClient {
44
44
 
45
45
  declare function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer>;
46
46
 
47
- export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type TtsConfig, synthesizeSpeech };
47
+ interface FetchAzureVoiceCatalogOptions {
48
+ apiKey: string;
49
+ region: string | string[];
50
+ }
51
+ interface AzureVoiceCatalogVoice {
52
+ name: string;
53
+ locale: string;
54
+ secondaryLocales?: readonly string[];
55
+ styles?: readonly string[];
56
+ regions: readonly string[];
57
+ status?: "ga" | "preview" | "deprecated";
58
+ }
59
+ interface FetchedAzureVoiceCatalogMetadata {
60
+ voiceCount: number;
61
+ generatedAt: string;
62
+ apiVersion: string;
63
+ regions: readonly string[];
64
+ }
65
+ interface AzureVoiceCatalog {
66
+ voices: readonly AzureVoiceCatalogVoice[];
67
+ metadata: FetchedAzureVoiceCatalogMetadata;
68
+ }
69
+ /** Fetches and deduplicates the current Azure Speech voice catalog for one or more regions. */
70
+ declare function fetchAzureVoiceCatalog(options: FetchAzureVoiceCatalogOptions): Promise<AzureVoiceCatalog>;
71
+
72
+ export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type AzureVoiceCatalog, type AzureVoiceCatalogVoice, type FetchAzureVoiceCatalogOptions, type FetchedAzureVoiceCatalogMetadata, type TtsConfig, fetchAzureVoiceCatalog, synthesizeSpeech };
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ __export(src_exports, {
45
45
  buildSsml: () => buildSsml,
46
46
  extractSsmlText: () => extractSsmlText,
47
47
  extractSsmlTranslatableText: () => extractSsmlTranslatableText,
48
+ fetchAzureVoiceCatalog: () => fetchAzureVoiceCatalog,
48
49
  fromPlainTextToSsml: () => fromPlainTextToSsml,
49
50
  getAzureVoiceCatalogMetadata: () => getAzureVoiceCatalogMetadata,
50
51
  getBuiltInVoiceCatalogMetadata: () => getBuiltInVoiceCatalogMetadata,
@@ -108,6 +109,7 @@ var SSML_ATTRS = {
108
109
  MSTTS_XMLNS: "xmlns:mstts",
109
110
  NAME: "name",
110
111
  VOICE: "voice",
112
+ SPEAKER: "speaker",
111
113
  EFFECT: "effect",
112
114
  RATE: "rate",
113
115
  PITCH: "pitch",
@@ -138,6 +140,11 @@ var SSML_ATTRS = {
138
140
  ALIAS: "alias",
139
141
  MARK: "mark",
140
142
  URI: "uri",
143
+ ID: "id",
144
+ MODEL: "model",
145
+ PROFILE: "profile",
146
+ URL: "url",
147
+ SPEAKER_PROFILE_ID: "speakerProfileId",
141
148
  TYPE: "type",
142
149
  VALUE: "value",
143
150
  FADE_IN: "fadein",
@@ -235,6 +242,7 @@ function getAttributes(element) {
235
242
  break;
236
243
  case SSML_TAGS.MSTTS_TURN:
237
244
  addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
245
+ addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
238
246
  break;
239
247
  case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
240
248
  addAttribute(attributes, SSML_ATTRS.SRC, element.src);
@@ -243,9 +251,18 @@ function getAttributes(element) {
243
251
  addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
244
252
  break;
245
253
  case SSML_TAGS.MSTTS_DIALOG:
254
+ break;
246
255
  case SSML_TAGS.MSTTS_TTS_EMBEDDING:
256
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
257
+ break;
247
258
  case SSML_TAGS.MSTTS_EMBEDDING:
259
+ addAttribute(attributes, SSML_ATTRS.ID, element.id);
260
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
261
+ break;
248
262
  case SSML_TAGS.MSTTS_VOICE_CONVERSION:
263
+ addAttribute(attributes, SSML_ATTRS.URL, element.url);
264
+ addAttribute(attributes, SSML_ATTRS.PROFILE, element.profile);
265
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
249
266
  break;
250
267
  case SSML_TAGS.PARAGRAPH:
251
268
  case SSML_TAGS.SENTENCE:
@@ -820,7 +837,9 @@ function convertElement(node) {
820
837
  case SSML_TAGS.MSTTS_TURN: {
821
838
  const element = { type: SSML_TAGS.MSTTS_TURN };
822
839
  const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
840
+ const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
823
841
  if (voice !== void 0) element.voice = voice;
842
+ if (speaker !== void 0) element.speaker = speaker;
824
843
  return finishElement(element, node, attributes);
825
844
  }
826
845
  case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
@@ -837,14 +856,26 @@ function convertElement(node) {
837
856
  }
838
857
  case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
839
858
  const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
859
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
860
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
840
861
  return finishElement(element, node, attributes);
841
862
  }
842
863
  case SSML_TAGS.MSTTS_EMBEDDING: {
843
864
  const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
865
+ const id = readAttribute(attributes, SSML_ATTRS.ID);
866
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
867
+ if (id !== void 0) element.id = id;
868
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
844
869
  return finishElement(element, node, attributes);
845
870
  }
846
871
  case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
847
872
  const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
873
+ const url = readAttribute(attributes, SSML_ATTRS.URL);
874
+ const profile = readAttribute(attributes, SSML_ATTRS.PROFILE);
875
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
876
+ if (url !== void 0) element.url = url;
877
+ if (profile !== void 0) element.profile = profile;
878
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
848
879
  return finishElement(element, node, attributes);
849
880
  }
850
881
  default: {
@@ -1220,6 +1251,7 @@ function elementAttributes(element) {
1220
1251
  break;
1221
1252
  case "mstts:turn":
1222
1253
  addAttribute2(attributes, "voice", element.voice);
1254
+ addAttribute2(attributes, "speaker", element.speaker);
1223
1255
  break;
1224
1256
  case "mstts:backgroundaudio":
1225
1257
  addAttribute2(attributes, "src", element.src);
@@ -1227,6 +1259,18 @@ function elementAttributes(element) {
1227
1259
  addAttribute2(attributes, "fadein", element.fadeIn ?? element.fadein);
1228
1260
  addAttribute2(attributes, "fadeout", element.fadeOut ?? element.fadeout);
1229
1261
  break;
1262
+ case "mstts:ttsembedding":
1263
+ addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
1264
+ break;
1265
+ case "mstts:embedding":
1266
+ addAttribute2(attributes, "id", element.id);
1267
+ addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
1268
+ break;
1269
+ case "mstts:voiceconversion":
1270
+ addAttribute2(attributes, "url", element.url);
1271
+ addAttribute2(attributes, "profile", element.profile);
1272
+ addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
1273
+ break;
1230
1274
  }
1231
1275
  return Object.fromEntries(Object.entries(attributes).map(([name, value]) => [name, String(value)]));
1232
1276
  }
@@ -1458,6 +1502,9 @@ var AZURE_VOICE_DEFINITIONS = [
1458
1502
  },
1459
1503
  { name: "es-ES-ElviraNeural", locale: "es-ES" },
1460
1504
  { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
1505
+ { name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
1506
+ { name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
1507
+ { name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
1461
1508
  { name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
1462
1509
  { name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
1463
1510
  { name: "id-ID-GadisNeural", locale: "id-ID" },
@@ -1554,6 +1601,18 @@ var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
1554
1601
  "Enumerationcomma"
1555
1602
  ]);
1556
1603
  var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
1604
+ var DEFAULT_PREVIEW_TAGS = /* @__PURE__ */ new Set(["mstts:voiceconversion"]);
1605
+ function featureStatusForTag(name, options) {
1606
+ const tagName = canonicalTagName(name);
1607
+ const configured = Object.entries(options.tagStatuses ?? {}).find(
1608
+ ([candidate]) => canonicalTagName(candidate) === tagName
1609
+ )?.[1];
1610
+ if (configured) return configured;
1611
+ if ((options.previewTags ?? [...DEFAULT_PREVIEW_TAGS]).some((candidate) => canonicalTagName(candidate) === tagName))
1612
+ return "preview";
1613
+ if ((options.deprecatedTags ?? []).some((candidate) => canonicalTagName(candidate) === tagName)) return "deprecated";
1614
+ return void 0;
1615
+ }
1557
1616
  function decodeAttribute(value) {
1558
1617
  return value.replace(
1559
1618
  /&(?:amp|apos|gt|lt|quot);/gi,
@@ -1613,11 +1672,14 @@ function tokenizeElements(source) {
1613
1672
  }
1614
1673
  const selfClosing = /\/\s*>$/.test(raw);
1615
1674
  const parent = openElements[openElements.length - 1];
1675
+ const childElementIndex = parent?.childElementCount;
1676
+ if (parent) parent.childElementCount += 1;
1616
1677
  const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
1617
1678
  const tokenName = nameMatch[1];
1618
1679
  const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
1619
1680
  tokens.push({
1620
1681
  attributes,
1682
+ childElementIndex,
1621
1683
  end,
1622
1684
  name: tokenName,
1623
1685
  parentName: parent?.name,
@@ -1627,6 +1689,7 @@ function tokenizeElements(source) {
1627
1689
  });
1628
1690
  if (!selfClosing) {
1629
1691
  openElements.push({
1692
+ childElementCount: 0,
1630
1693
  name: tokenName,
1631
1694
  voiceName: tokenVoiceName
1632
1695
  });
@@ -1665,6 +1728,12 @@ function isValidAzureAudioDuration(value) {
1665
1728
  if (!clock) return false;
1666
1729
  return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
1667
1730
  }
1731
+ function isValidAzureBackgroundAudioDuration(value) {
1732
+ const match = /^(\d+)$/.exec(value.trim());
1733
+ if (!match) return false;
1734
+ const milliseconds = Number(match[1]);
1735
+ return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
1736
+ }
1668
1737
  function attr(token, name) {
1669
1738
  return token.attributes.get(name.toLowerCase());
1670
1739
  }
@@ -1804,9 +1873,16 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
1804
1873
  }
1805
1874
  if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
1806
1875
  addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
1807
- if (options.allowedAudioOrigins && !options.allowedAudioOrigins.includes(parsed.origin))
1876
+ const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
1877
+ try {
1878
+ return new URL(allowedOrigin).origin === parsed.origin;
1879
+ } catch {
1880
+ return allowedOrigin === parsed.origin;
1881
+ }
1882
+ }) ?? false;
1883
+ if (options.allowedAudioOrigins && !isAllowedOrigin)
1808
1884
  addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
1809
- else if (!options.allowExternalAudio)
1885
+ else if (!isAllowedOrigin && !options.allowExternalAudio)
1810
1886
  addDiagnostic(
1811
1887
  diagnostics,
1812
1888
  source,
@@ -1816,6 +1892,25 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
1816
1892
  }
1817
1893
  function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
1818
1894
  const name = token.name.toLowerCase();
1895
+ const tagStatus = featureStatusForTag(token.name, options);
1896
+ if (tagStatus === "preview")
1897
+ addDiagnostic(
1898
+ diagnostics,
1899
+ source,
1900
+ token.start,
1901
+ `<${token.name}> is an Azure Speech preview feature and may change or require preview access.`,
1902
+ "warning",
1903
+ "azure-preview-tag"
1904
+ );
1905
+ if (tagStatus === "deprecated")
1906
+ addDiagnostic(
1907
+ diagnostics,
1908
+ source,
1909
+ token.start,
1910
+ `<${token.name}> is deprecated by Azure Speech; migrate to a supported alternative.`,
1911
+ "info",
1912
+ "azure-deprecated-tag"
1913
+ );
1819
1914
  if (name === "voice" && !attr(token, "name")?.trim())
1820
1915
  addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
1821
1916
  if (name === "break") {
@@ -1936,28 +2031,40 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
1936
2031
  validateAudioSource(token, source, diagnostics, options, "audio");
1937
2032
  }
1938
2033
  if (name === "mstts:turn") {
1939
- if (!attr(token, "voice")?.trim())
1940
- addDiagnostic(diagnostics, source, token.start, '<mstts:turn> requires a non-empty "voice" attribute.');
2034
+ if (!attr(token, "voice")?.trim() && !attr(token, "speaker")?.trim())
2035
+ addDiagnostic(
2036
+ diagnostics,
2037
+ source,
2038
+ token.start,
2039
+ '<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
2040
+ );
1941
2041
  if (token.parentName?.toLowerCase() !== "mstts:dialog")
1942
2042
  addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
1943
2043
  }
1944
2044
  if (name === "mstts:backgroundaudio") {
1945
2045
  validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
1946
2046
  const volume = attr(token, "volume");
1947
- if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%))$/i.test(volume.trim()))
2047
+ if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
1948
2048
  addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
1949
2049
  for (const [attribute, value] of [
1950
2050
  ["fadein", attr(token, "fadein")],
1951
2051
  ["fadeout", attr(token, "fadeout")]
1952
2052
  ]) {
1953
- if (value && !isValidAzureAudioDuration(value))
2053
+ if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
1954
2054
  addDiagnostic(
1955
2055
  diagnostics,
1956
2056
  source,
1957
2057
  token.start,
1958
- `<mstts:backgroundaudio ${attribute}> must be a positive duration such as "500ms" or "10s".`
2058
+ `<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
1959
2059
  );
1960
2060
  }
2061
+ if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
2062
+ addDiagnostic(
2063
+ diagnostics,
2064
+ source,
2065
+ token.start,
2066
+ "<mstts:backgroundaudio> must be the first element directly under <speak>."
2067
+ );
1961
2068
  if (!token.selfClosing)
1962
2069
  addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
1963
2070
  }
@@ -1989,6 +2096,16 @@ function validateAzureSsml(ssml, options = {}) {
1989
2096
  const tokens = tokenizeElements(ssml);
1990
2097
  const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
1991
2098
  const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
2099
+ const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
2100
+ for (const [index, token] of backgroundAudioTokens.entries()) {
2101
+ if (index > 0)
2102
+ addDiagnostic(
2103
+ diagnostics,
2104
+ ssml,
2105
+ token.start,
2106
+ "An SSML document can contain at most one <mstts:backgroundaudio> element."
2107
+ );
2108
+ }
1992
2109
  if (!speak || voices.length === 0)
1993
2110
  addDiagnostic(
1994
2111
  diagnostics,
@@ -2005,6 +2122,24 @@ function validateAzureSsml(ssml, options = {}) {
2005
2122
  const name = attr(token, "name")?.trim();
2006
2123
  const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
2007
2124
  const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
2125
+ if (name && definition?.status === "preview")
2126
+ addDiagnostic(
2127
+ diagnostics,
2128
+ ssml,
2129
+ token.start,
2130
+ `Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
2131
+ "warning",
2132
+ "azure-preview-voice"
2133
+ );
2134
+ if (name && definition?.status === "deprecated")
2135
+ addDiagnostic(
2136
+ diagnostics,
2137
+ ssml,
2138
+ token.start,
2139
+ `Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
2140
+ "info",
2141
+ "azure-deprecated-voice"
2142
+ );
2008
2143
  if (name && !definition && policySeverity)
2009
2144
  addDiagnostic(
2010
2145
  diagnostics,
@@ -2242,6 +2377,84 @@ var AzureTtsClient = class {
2242
2377
  }
2243
2378
  };
2244
2379
  _options = new WeakMap();
2380
+
2381
+ // packages/azure-tts-client/src/voiceCatalog.ts
2382
+ var AZURE_VOICE_API_VERSION = "2025-10-01";
2383
+ function stringValue(value) {
2384
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
2385
+ }
2386
+ function stringList(value) {
2387
+ if (!Array.isArray(value)) return [];
2388
+ return [...new Set(value.map(stringValue).filter((item) => item !== void 0))];
2389
+ }
2390
+ function normalizeStatus(value) {
2391
+ const status = stringValue(value)?.toLowerCase();
2392
+ if (status === "preview" || status === "deprecated" || status === "ga") return status;
2393
+ return void 0;
2394
+ }
2395
+ function normalizeRegions(region) {
2396
+ const regions = Array.isArray(region) ? region : [region];
2397
+ const result = [...new Set(regions.map((item) => item.trim()).filter(Boolean))];
2398
+ if (result.length === 0) throw new TypeError("At least one Azure Speech region is required.");
2399
+ return result;
2400
+ }
2401
+ async function fetchRegionVoices(region, apiKey) {
2402
+ const endpoint = `https://${encodeURIComponent(region)}.tts.speech.microsoft.com/cognitiveservices/voices/list`;
2403
+ const response = await fetch(endpoint, {
2404
+ headers: {
2405
+ Accept: "application/json",
2406
+ "Ocp-Apim-Subscription-Key": apiKey
2407
+ }
2408
+ });
2409
+ if (!response.ok) {
2410
+ throw new Error(`Azure List Voices API request failed for region "${region}" with HTTP ${response.status}.`);
2411
+ }
2412
+ const payload = await response.json();
2413
+ if (!Array.isArray(payload)) throw new Error(`Azure List Voices API returned an invalid response for "${region}".`);
2414
+ return payload.filter((item) => Boolean(item && typeof item === "object"));
2415
+ }
2416
+ async function fetchAzureVoiceCatalog(options) {
2417
+ if (!options || typeof options.apiKey !== "string" || !options.apiKey.trim())
2418
+ throw new TypeError("An Azure Speech API key is required.");
2419
+ const regions = normalizeRegions(options.region);
2420
+ const payloads = await Promise.all(regions.map((region) => fetchRegionVoices(region, options.apiKey)));
2421
+ const voices = /* @__PURE__ */ new Map();
2422
+ for (let regionIndex = 0; regionIndex < payloads.length; regionIndex += 1) {
2423
+ const region = regions[regionIndex];
2424
+ for (const record of payloads[regionIndex]) {
2425
+ const name = stringValue(record.ShortName) ?? stringValue(record.Name);
2426
+ const locale = stringValue(record.Locale);
2427
+ if (!name || !locale) continue;
2428
+ const key = name.toLowerCase();
2429
+ const existing = voices.get(key);
2430
+ const secondaryLocales = stringList(record.SecondaryLocaleList);
2431
+ const styles = stringList(record.StyleList);
2432
+ const status = normalizeStatus(record.Status);
2433
+ const merged = {
2434
+ name: existing?.name ?? name,
2435
+ locale: existing?.locale ?? locale,
2436
+ regions: [.../* @__PURE__ */ new Set([...existing?.regions ?? [], region])]
2437
+ };
2438
+ const mergedSecondaryLocales = [.../* @__PURE__ */ new Set([...existing?.secondaryLocales ?? [], ...secondaryLocales])];
2439
+ if (mergedSecondaryLocales.length > 0) merged.secondaryLocales = mergedSecondaryLocales;
2440
+ const mergedStyles = [.../* @__PURE__ */ new Set([...existing?.styles ?? [], ...styles])];
2441
+ if (mergedStyles.length > 0) merged.styles = mergedStyles;
2442
+ if (status) merged.status = status;
2443
+ else if (existing?.status) merged.status = existing.status;
2444
+ voices.set(key, merged);
2445
+ }
2446
+ }
2447
+ const sortedVoices = [...voices.values()].sort((first, second) => first.name.localeCompare(second.name));
2448
+ return {
2449
+ voices: sortedVoices,
2450
+ metadata: {
2451
+ voiceCount: sortedVoices.length,
2452
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2453
+ apiVersion: AZURE_VOICE_API_VERSION,
2454
+ regions
2455
+ }
2456
+ };
2457
+ }
2245
2458
  // Annotate the CommonJS export names for ESM import in node:
2246
2459
  0 && (module.exports = {
2247
2460
  AzureTtsClient,
@@ -2252,6 +2465,7 @@ _options = new WeakMap();
2252
2465
  buildSsml,
2253
2466
  extractSsmlText,
2254
2467
  extractSsmlTranslatableText,
2468
+ fetchAzureVoiceCatalog,
2255
2469
  fromPlainTextToSsml,
2256
2470
  getAzureVoiceCatalogMetadata,
2257
2471
  getBuiltInVoiceCatalogMetadata,