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/README.md +74 -8
- package/bin/sync-voices.js +62 -0
- package/dist/{chunk-JNJVTEL6.mjs → chunk-FWWMWI2C.mjs} +143 -17
- package/dist/chunk-FWWMWI2C.mjs.map +1 -0
- package/dist/{chunk-7LCSH4SZ.mjs → chunk-SNRI43QJ.mjs} +142 -8
- package/dist/chunk-SNRI43QJ.mjs.map +1 -0
- package/dist/core.d.mts +17 -2
- package/dist/core.d.ts +17 -2
- package/dist/core.js +141 -7
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/elements.js +142 -16
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +1 -1
- package/dist/index.d.mts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +221 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +80 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +7 -0
- package/dist/react.d.ts +7 -0
- package/dist/react.js +247 -17
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +106 -2
- package/dist/react.mjs.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-7LCSH4SZ.mjs.map +0 -1
- package/dist/chunk-JNJVTEL6.mjs.map +0 -1
package/dist/elements.mjs
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 &&
|
|
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 && !
|
|
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
|
|
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,
|