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/core.js
CHANGED
|
@@ -94,6 +94,7 @@ var SSML_ATTRS = {
|
|
|
94
94
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
95
95
|
NAME: "name",
|
|
96
96
|
VOICE: "voice",
|
|
97
|
+
SPEAKER: "speaker",
|
|
97
98
|
EFFECT: "effect",
|
|
98
99
|
RATE: "rate",
|
|
99
100
|
PITCH: "pitch",
|
|
@@ -124,6 +125,11 @@ var SSML_ATTRS = {
|
|
|
124
125
|
ALIAS: "alias",
|
|
125
126
|
MARK: "mark",
|
|
126
127
|
URI: "uri",
|
|
128
|
+
ID: "id",
|
|
129
|
+
MODEL: "model",
|
|
130
|
+
PROFILE: "profile",
|
|
131
|
+
URL: "url",
|
|
132
|
+
SPEAKER_PROFILE_ID: "speakerProfileId",
|
|
127
133
|
TYPE: "type",
|
|
128
134
|
VALUE: "value",
|
|
129
135
|
FADE_IN: "fadein",
|
|
@@ -221,6 +227,7 @@ function getAttributes(element) {
|
|
|
221
227
|
break;
|
|
222
228
|
case SSML_TAGS.MSTTS_TURN:
|
|
223
229
|
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
230
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
|
|
224
231
|
break;
|
|
225
232
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
226
233
|
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
@@ -229,9 +236,18 @@ function getAttributes(element) {
|
|
|
229
236
|
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
230
237
|
break;
|
|
231
238
|
case SSML_TAGS.MSTTS_DIALOG:
|
|
239
|
+
break;
|
|
232
240
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
241
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
242
|
+
break;
|
|
233
243
|
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
244
|
+
addAttribute(attributes, SSML_ATTRS.ID, element.id);
|
|
245
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
246
|
+
break;
|
|
234
247
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
248
|
+
addAttribute(attributes, SSML_ATTRS.URL, element.url);
|
|
249
|
+
addAttribute(attributes, SSML_ATTRS.PROFILE, element.profile);
|
|
250
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
235
251
|
break;
|
|
236
252
|
case SSML_TAGS.PARAGRAPH:
|
|
237
253
|
case SSML_TAGS.SENTENCE:
|
|
@@ -806,7 +822,9 @@ function convertElement(node) {
|
|
|
806
822
|
case SSML_TAGS.MSTTS_TURN: {
|
|
807
823
|
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
808
824
|
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
825
|
+
const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
|
|
809
826
|
if (voice !== void 0) element.voice = voice;
|
|
827
|
+
if (speaker !== void 0) element.speaker = speaker;
|
|
810
828
|
return finishElement(element, node, attributes);
|
|
811
829
|
}
|
|
812
830
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
@@ -823,14 +841,26 @@ function convertElement(node) {
|
|
|
823
841
|
}
|
|
824
842
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
825
843
|
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
844
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
845
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
826
846
|
return finishElement(element, node, attributes);
|
|
827
847
|
}
|
|
828
848
|
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
829
849
|
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
850
|
+
const id = readAttribute(attributes, SSML_ATTRS.ID);
|
|
851
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
852
|
+
if (id !== void 0) element.id = id;
|
|
853
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
830
854
|
return finishElement(element, node, attributes);
|
|
831
855
|
}
|
|
832
856
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
833
857
|
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
858
|
+
const url = readAttribute(attributes, SSML_ATTRS.URL);
|
|
859
|
+
const profile = readAttribute(attributes, SSML_ATTRS.PROFILE);
|
|
860
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
861
|
+
if (url !== void 0) element.url = url;
|
|
862
|
+
if (profile !== void 0) element.profile = profile;
|
|
863
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
834
864
|
return finishElement(element, node, attributes);
|
|
835
865
|
}
|
|
836
866
|
default: {
|
|
@@ -1206,6 +1236,7 @@ function elementAttributes(element) {
|
|
|
1206
1236
|
break;
|
|
1207
1237
|
case "mstts:turn":
|
|
1208
1238
|
addAttribute2(attributes, "voice", element.voice);
|
|
1239
|
+
addAttribute2(attributes, "speaker", element.speaker);
|
|
1209
1240
|
break;
|
|
1210
1241
|
case "mstts:backgroundaudio":
|
|
1211
1242
|
addAttribute2(attributes, "src", element.src);
|
|
@@ -1213,6 +1244,18 @@ function elementAttributes(element) {
|
|
|
1213
1244
|
addAttribute2(attributes, "fadein", element.fadeIn ?? element.fadein);
|
|
1214
1245
|
addAttribute2(attributes, "fadeout", element.fadeOut ?? element.fadeout);
|
|
1215
1246
|
break;
|
|
1247
|
+
case "mstts:ttsembedding":
|
|
1248
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1249
|
+
break;
|
|
1250
|
+
case "mstts:embedding":
|
|
1251
|
+
addAttribute2(attributes, "id", element.id);
|
|
1252
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1253
|
+
break;
|
|
1254
|
+
case "mstts:voiceconversion":
|
|
1255
|
+
addAttribute2(attributes, "url", element.url);
|
|
1256
|
+
addAttribute2(attributes, "profile", element.profile);
|
|
1257
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1258
|
+
break;
|
|
1216
1259
|
}
|
|
1217
1260
|
return Object.fromEntries(Object.entries(attributes).map(([name, value]) => [name, String(value)]));
|
|
1218
1261
|
}
|
|
@@ -1444,6 +1487,9 @@ var AZURE_VOICE_DEFINITIONS = [
|
|
|
1444
1487
|
},
|
|
1445
1488
|
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
1446
1489
|
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
1490
|
+
{ name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
|
|
1491
|
+
{ name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
|
|
1492
|
+
{ name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
|
|
1447
1493
|
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1448
1494
|
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1449
1495
|
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
@@ -1540,6 +1586,18 @@ var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
|
|
|
1540
1586
|
"Enumerationcomma"
|
|
1541
1587
|
]);
|
|
1542
1588
|
var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
|
|
1589
|
+
var DEFAULT_PREVIEW_TAGS = /* @__PURE__ */ new Set(["mstts:voiceconversion"]);
|
|
1590
|
+
function featureStatusForTag(name, options) {
|
|
1591
|
+
const tagName = canonicalTagName(name);
|
|
1592
|
+
const configured = Object.entries(options.tagStatuses ?? {}).find(
|
|
1593
|
+
([candidate]) => canonicalTagName(candidate) === tagName
|
|
1594
|
+
)?.[1];
|
|
1595
|
+
if (configured) return configured;
|
|
1596
|
+
if ((options.previewTags ?? [...DEFAULT_PREVIEW_TAGS]).some((candidate) => canonicalTagName(candidate) === tagName))
|
|
1597
|
+
return "preview";
|
|
1598
|
+
if ((options.deprecatedTags ?? []).some((candidate) => canonicalTagName(candidate) === tagName)) return "deprecated";
|
|
1599
|
+
return void 0;
|
|
1600
|
+
}
|
|
1543
1601
|
function decodeAttribute(value) {
|
|
1544
1602
|
return value.replace(
|
|
1545
1603
|
/&(?:amp|apos|gt|lt|quot);/gi,
|
|
@@ -1599,11 +1657,14 @@ function tokenizeElements(source) {
|
|
|
1599
1657
|
}
|
|
1600
1658
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
1601
1659
|
const parent = openElements[openElements.length - 1];
|
|
1660
|
+
const childElementIndex = parent?.childElementCount;
|
|
1661
|
+
if (parent) parent.childElementCount += 1;
|
|
1602
1662
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1603
1663
|
const tokenName = nameMatch[1];
|
|
1604
1664
|
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
1605
1665
|
tokens.push({
|
|
1606
1666
|
attributes,
|
|
1667
|
+
childElementIndex,
|
|
1607
1668
|
end,
|
|
1608
1669
|
name: tokenName,
|
|
1609
1670
|
parentName: parent?.name,
|
|
@@ -1613,6 +1674,7 @@ function tokenizeElements(source) {
|
|
|
1613
1674
|
});
|
|
1614
1675
|
if (!selfClosing) {
|
|
1615
1676
|
openElements.push({
|
|
1677
|
+
childElementCount: 0,
|
|
1616
1678
|
name: tokenName,
|
|
1617
1679
|
voiceName: tokenVoiceName
|
|
1618
1680
|
});
|
|
@@ -1651,6 +1713,12 @@ function isValidAzureAudioDuration(value) {
|
|
|
1651
1713
|
if (!clock) return false;
|
|
1652
1714
|
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
1653
1715
|
}
|
|
1716
|
+
function isValidAzureBackgroundAudioDuration(value) {
|
|
1717
|
+
const match = /^(\d+)$/.exec(value.trim());
|
|
1718
|
+
if (!match) return false;
|
|
1719
|
+
const milliseconds = Number(match[1]);
|
|
1720
|
+
return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
|
|
1721
|
+
}
|
|
1654
1722
|
function attr(token, name) {
|
|
1655
1723
|
return token.attributes.get(name.toLowerCase());
|
|
1656
1724
|
}
|
|
@@ -1790,9 +1858,16 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
1790
1858
|
}
|
|
1791
1859
|
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1792
1860
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
1793
|
-
|
|
1861
|
+
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
1862
|
+
try {
|
|
1863
|
+
return new URL(allowedOrigin).origin === parsed.origin;
|
|
1864
|
+
} catch {
|
|
1865
|
+
return allowedOrigin === parsed.origin;
|
|
1866
|
+
}
|
|
1867
|
+
}) ?? false;
|
|
1868
|
+
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
1794
1869
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
1795
|
-
else if (!options.allowExternalAudio)
|
|
1870
|
+
else if (!isAllowedOrigin && !options.allowExternalAudio)
|
|
1796
1871
|
addDiagnostic(
|
|
1797
1872
|
diagnostics,
|
|
1798
1873
|
source,
|
|
@@ -1802,6 +1877,25 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
1802
1877
|
}
|
|
1803
1878
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
1804
1879
|
const name = token.name.toLowerCase();
|
|
1880
|
+
const tagStatus = featureStatusForTag(token.name, options);
|
|
1881
|
+
if (tagStatus === "preview")
|
|
1882
|
+
addDiagnostic(
|
|
1883
|
+
diagnostics,
|
|
1884
|
+
source,
|
|
1885
|
+
token.start,
|
|
1886
|
+
`<${token.name}> is an Azure Speech preview feature and may change or require preview access.`,
|
|
1887
|
+
"warning",
|
|
1888
|
+
"azure-preview-tag"
|
|
1889
|
+
);
|
|
1890
|
+
if (tagStatus === "deprecated")
|
|
1891
|
+
addDiagnostic(
|
|
1892
|
+
diagnostics,
|
|
1893
|
+
source,
|
|
1894
|
+
token.start,
|
|
1895
|
+
`<${token.name}> is deprecated by Azure Speech; migrate to a supported alternative.`,
|
|
1896
|
+
"info",
|
|
1897
|
+
"azure-deprecated-tag"
|
|
1898
|
+
);
|
|
1805
1899
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
1806
1900
|
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
1807
1901
|
if (name === "break") {
|
|
@@ -1922,28 +2016,40 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
1922
2016
|
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
1923
2017
|
}
|
|
1924
2018
|
if (name === "mstts:turn") {
|
|
1925
|
-
if (!attr(token, "voice")?.trim())
|
|
1926
|
-
addDiagnostic(
|
|
2019
|
+
if (!attr(token, "voice")?.trim() && !attr(token, "speaker")?.trim())
|
|
2020
|
+
addDiagnostic(
|
|
2021
|
+
diagnostics,
|
|
2022
|
+
source,
|
|
2023
|
+
token.start,
|
|
2024
|
+
'<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
|
|
2025
|
+
);
|
|
1927
2026
|
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
1928
2027
|
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
1929
2028
|
}
|
|
1930
2029
|
if (name === "mstts:backgroundaudio") {
|
|
1931
2030
|
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
1932
2031
|
const volume = attr(token, "volume");
|
|
1933
|
-
if (volume &&
|
|
2032
|
+
if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
|
|
1934
2033
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
1935
2034
|
for (const [attribute, value] of [
|
|
1936
2035
|
["fadein", attr(token, "fadein")],
|
|
1937
2036
|
["fadeout", attr(token, "fadeout")]
|
|
1938
2037
|
]) {
|
|
1939
|
-
if (value && !
|
|
2038
|
+
if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
|
|
1940
2039
|
addDiagnostic(
|
|
1941
2040
|
diagnostics,
|
|
1942
2041
|
source,
|
|
1943
2042
|
token.start,
|
|
1944
|
-
`<mstts:backgroundaudio ${attribute}> must be
|
|
2043
|
+
`<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
|
|
1945
2044
|
);
|
|
1946
2045
|
}
|
|
2046
|
+
if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
|
|
2047
|
+
addDiagnostic(
|
|
2048
|
+
diagnostics,
|
|
2049
|
+
source,
|
|
2050
|
+
token.start,
|
|
2051
|
+
"<mstts:backgroundaudio> must be the first element directly under <speak>."
|
|
2052
|
+
);
|
|
1947
2053
|
if (!token.selfClosing)
|
|
1948
2054
|
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
1949
2055
|
}
|
|
@@ -1975,6 +2081,16 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1975
2081
|
const tokens = tokenizeElements(ssml);
|
|
1976
2082
|
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
1977
2083
|
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
2084
|
+
const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
|
|
2085
|
+
for (const [index, token] of backgroundAudioTokens.entries()) {
|
|
2086
|
+
if (index > 0)
|
|
2087
|
+
addDiagnostic(
|
|
2088
|
+
diagnostics,
|
|
2089
|
+
ssml,
|
|
2090
|
+
token.start,
|
|
2091
|
+
"An SSML document can contain at most one <mstts:backgroundaudio> element."
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
1978
2094
|
if (!speak || voices.length === 0)
|
|
1979
2095
|
addDiagnostic(
|
|
1980
2096
|
diagnostics,
|
|
@@ -1991,6 +2107,24 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1991
2107
|
const name = attr(token, "name")?.trim();
|
|
1992
2108
|
const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
|
|
1993
2109
|
const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
|
|
2110
|
+
if (name && definition?.status === "preview")
|
|
2111
|
+
addDiagnostic(
|
|
2112
|
+
diagnostics,
|
|
2113
|
+
ssml,
|
|
2114
|
+
token.start,
|
|
2115
|
+
`Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
|
|
2116
|
+
"warning",
|
|
2117
|
+
"azure-preview-voice"
|
|
2118
|
+
);
|
|
2119
|
+
if (name && definition?.status === "deprecated")
|
|
2120
|
+
addDiagnostic(
|
|
2121
|
+
diagnostics,
|
|
2122
|
+
ssml,
|
|
2123
|
+
token.start,
|
|
2124
|
+
`Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
|
|
2125
|
+
"info",
|
|
2126
|
+
"azure-deprecated-voice"
|
|
2127
|
+
);
|
|
1994
2128
|
if (name && !definition && policySeverity)
|
|
1995
2129
|
addDiagnostic(
|
|
1996
2130
|
diagnostics,
|