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
|
@@ -53,6 +53,7 @@ var SSML_ATTRS = {
|
|
|
53
53
|
MSTTS_XMLNS: "xmlns:mstts",
|
|
54
54
|
NAME: "name",
|
|
55
55
|
VOICE: "voice",
|
|
56
|
+
SPEAKER: "speaker",
|
|
56
57
|
EFFECT: "effect",
|
|
57
58
|
RATE: "rate",
|
|
58
59
|
PITCH: "pitch",
|
|
@@ -83,6 +84,11 @@ var SSML_ATTRS = {
|
|
|
83
84
|
ALIAS: "alias",
|
|
84
85
|
MARK: "mark",
|
|
85
86
|
URI: "uri",
|
|
87
|
+
ID: "id",
|
|
88
|
+
MODEL: "model",
|
|
89
|
+
PROFILE: "profile",
|
|
90
|
+
URL: "url",
|
|
91
|
+
SPEAKER_PROFILE_ID: "speakerProfileId",
|
|
86
92
|
TYPE: "type",
|
|
87
93
|
VALUE: "value",
|
|
88
94
|
FADE_IN: "fadein",
|
|
@@ -180,6 +186,7 @@ function getAttributes(element) {
|
|
|
180
186
|
break;
|
|
181
187
|
case SSML_TAGS.MSTTS_TURN:
|
|
182
188
|
addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
|
|
189
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
|
|
183
190
|
break;
|
|
184
191
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
|
|
185
192
|
addAttribute(attributes, SSML_ATTRS.SRC, element.src);
|
|
@@ -188,9 +195,18 @@ function getAttributes(element) {
|
|
|
188
195
|
addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
|
|
189
196
|
break;
|
|
190
197
|
case SSML_TAGS.MSTTS_DIALOG:
|
|
198
|
+
break;
|
|
191
199
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING:
|
|
200
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
201
|
+
break;
|
|
192
202
|
case SSML_TAGS.MSTTS_EMBEDDING:
|
|
203
|
+
addAttribute(attributes, SSML_ATTRS.ID, element.id);
|
|
204
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
205
|
+
break;
|
|
193
206
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION:
|
|
207
|
+
addAttribute(attributes, SSML_ATTRS.URL, element.url);
|
|
208
|
+
addAttribute(attributes, SSML_ATTRS.PROFILE, element.profile);
|
|
209
|
+
addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
|
|
194
210
|
break;
|
|
195
211
|
case SSML_TAGS.PARAGRAPH:
|
|
196
212
|
case SSML_TAGS.SENTENCE:
|
|
@@ -765,7 +781,9 @@ function convertElement(node) {
|
|
|
765
781
|
case SSML_TAGS.MSTTS_TURN: {
|
|
766
782
|
const element = { type: SSML_TAGS.MSTTS_TURN };
|
|
767
783
|
const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
|
|
784
|
+
const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
|
|
768
785
|
if (voice !== void 0) element.voice = voice;
|
|
786
|
+
if (speaker !== void 0) element.speaker = speaker;
|
|
769
787
|
return finishElement(element, node, attributes);
|
|
770
788
|
}
|
|
771
789
|
case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
|
|
@@ -782,14 +800,26 @@ function convertElement(node) {
|
|
|
782
800
|
}
|
|
783
801
|
case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
|
|
784
802
|
const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
|
|
803
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
804
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
785
805
|
return finishElement(element, node, attributes);
|
|
786
806
|
}
|
|
787
807
|
case SSML_TAGS.MSTTS_EMBEDDING: {
|
|
788
808
|
const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
|
|
809
|
+
const id = readAttribute(attributes, SSML_ATTRS.ID);
|
|
810
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
811
|
+
if (id !== void 0) element.id = id;
|
|
812
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
789
813
|
return finishElement(element, node, attributes);
|
|
790
814
|
}
|
|
791
815
|
case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
|
|
792
816
|
const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
|
|
817
|
+
const url = readAttribute(attributes, SSML_ATTRS.URL);
|
|
818
|
+
const profile = readAttribute(attributes, SSML_ATTRS.PROFILE);
|
|
819
|
+
const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
|
|
820
|
+
if (url !== void 0) element.url = url;
|
|
821
|
+
if (profile !== void 0) element.profile = profile;
|
|
822
|
+
if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
|
|
793
823
|
return finishElement(element, node, attributes);
|
|
794
824
|
}
|
|
795
825
|
default: {
|
|
@@ -1165,6 +1195,7 @@ function elementAttributes(element) {
|
|
|
1165
1195
|
break;
|
|
1166
1196
|
case "mstts:turn":
|
|
1167
1197
|
addAttribute2(attributes, "voice", element.voice);
|
|
1198
|
+
addAttribute2(attributes, "speaker", element.speaker);
|
|
1168
1199
|
break;
|
|
1169
1200
|
case "mstts:backgroundaudio":
|
|
1170
1201
|
addAttribute2(attributes, "src", element.src);
|
|
@@ -1172,6 +1203,18 @@ function elementAttributes(element) {
|
|
|
1172
1203
|
addAttribute2(attributes, "fadein", element.fadeIn ?? element.fadein);
|
|
1173
1204
|
addAttribute2(attributes, "fadeout", element.fadeOut ?? element.fadeout);
|
|
1174
1205
|
break;
|
|
1206
|
+
case "mstts:ttsembedding":
|
|
1207
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1208
|
+
break;
|
|
1209
|
+
case "mstts:embedding":
|
|
1210
|
+
addAttribute2(attributes, "id", element.id);
|
|
1211
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1212
|
+
break;
|
|
1213
|
+
case "mstts:voiceconversion":
|
|
1214
|
+
addAttribute2(attributes, "url", element.url);
|
|
1215
|
+
addAttribute2(attributes, "profile", element.profile);
|
|
1216
|
+
addAttribute2(attributes, "speakerProfileId", element.speakerProfileId);
|
|
1217
|
+
break;
|
|
1175
1218
|
}
|
|
1176
1219
|
return Object.fromEntries(Object.entries(attributes).map(([name, value]) => [name, String(value)]));
|
|
1177
1220
|
}
|
|
@@ -1403,6 +1446,9 @@ var AZURE_VOICE_DEFINITIONS = [
|
|
|
1403
1446
|
},
|
|
1404
1447
|
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
1405
1448
|
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
1449
|
+
{ name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
|
|
1450
|
+
{ name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
|
|
1451
|
+
{ name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
|
|
1406
1452
|
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1407
1453
|
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1408
1454
|
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
@@ -1499,6 +1545,18 @@ var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
|
|
|
1499
1545
|
"Enumerationcomma"
|
|
1500
1546
|
]);
|
|
1501
1547
|
var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
|
|
1548
|
+
var DEFAULT_PREVIEW_TAGS = /* @__PURE__ */ new Set(["mstts:voiceconversion"]);
|
|
1549
|
+
function featureStatusForTag(name, options) {
|
|
1550
|
+
const tagName = canonicalTagName(name);
|
|
1551
|
+
const configured = Object.entries(options.tagStatuses ?? {}).find(
|
|
1552
|
+
([candidate]) => canonicalTagName(candidate) === tagName
|
|
1553
|
+
)?.[1];
|
|
1554
|
+
if (configured) return configured;
|
|
1555
|
+
if ((options.previewTags ?? [...DEFAULT_PREVIEW_TAGS]).some((candidate) => canonicalTagName(candidate) === tagName))
|
|
1556
|
+
return "preview";
|
|
1557
|
+
if ((options.deprecatedTags ?? []).some((candidate) => canonicalTagName(candidate) === tagName)) return "deprecated";
|
|
1558
|
+
return void 0;
|
|
1559
|
+
}
|
|
1502
1560
|
function decodeAttribute(value) {
|
|
1503
1561
|
return value.replace(
|
|
1504
1562
|
/&(?:amp|apos|gt|lt|quot);/gi,
|
|
@@ -1558,11 +1616,14 @@ function tokenizeElements(source) {
|
|
|
1558
1616
|
}
|
|
1559
1617
|
const selfClosing = /\/\s*>$/.test(raw);
|
|
1560
1618
|
const parent = openElements[openElements.length - 1];
|
|
1619
|
+
const childElementIndex = parent?.childElementCount;
|
|
1620
|
+
if (parent) parent.childElementCount += 1;
|
|
1561
1621
|
const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
|
|
1562
1622
|
const tokenName = nameMatch[1];
|
|
1563
1623
|
const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
|
|
1564
1624
|
tokens.push({
|
|
1565
1625
|
attributes,
|
|
1626
|
+
childElementIndex,
|
|
1566
1627
|
end,
|
|
1567
1628
|
name: tokenName,
|
|
1568
1629
|
parentName: parent?.name,
|
|
@@ -1572,6 +1633,7 @@ function tokenizeElements(source) {
|
|
|
1572
1633
|
});
|
|
1573
1634
|
if (!selfClosing) {
|
|
1574
1635
|
openElements.push({
|
|
1636
|
+
childElementCount: 0,
|
|
1575
1637
|
name: tokenName,
|
|
1576
1638
|
voiceName: tokenVoiceName
|
|
1577
1639
|
});
|
|
@@ -1610,6 +1672,12 @@ function isValidAzureAudioDuration(value) {
|
|
|
1610
1672
|
if (!clock) return false;
|
|
1611
1673
|
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
1612
1674
|
}
|
|
1675
|
+
function isValidAzureBackgroundAudioDuration(value) {
|
|
1676
|
+
const match = /^(\d+)$/.exec(value.trim());
|
|
1677
|
+
if (!match) return false;
|
|
1678
|
+
const milliseconds = Number(match[1]);
|
|
1679
|
+
return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
|
|
1680
|
+
}
|
|
1613
1681
|
function attr(token, name) {
|
|
1614
1682
|
return token.attributes.get(name.toLowerCase());
|
|
1615
1683
|
}
|
|
@@ -1749,9 +1817,16 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
1749
1817
|
}
|
|
1750
1818
|
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1751
1819
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
1752
|
-
|
|
1820
|
+
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
1821
|
+
try {
|
|
1822
|
+
return new URL(allowedOrigin).origin === parsed.origin;
|
|
1823
|
+
} catch {
|
|
1824
|
+
return allowedOrigin === parsed.origin;
|
|
1825
|
+
}
|
|
1826
|
+
}) ?? false;
|
|
1827
|
+
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
1753
1828
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
|
|
1754
|
-
else if (!options.allowExternalAudio)
|
|
1829
|
+
else if (!isAllowedOrigin && !options.allowExternalAudio)
|
|
1755
1830
|
addDiagnostic(
|
|
1756
1831
|
diagnostics,
|
|
1757
1832
|
source,
|
|
@@ -1761,6 +1836,25 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
1761
1836
|
}
|
|
1762
1837
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
1763
1838
|
const name = token.name.toLowerCase();
|
|
1839
|
+
const tagStatus = featureStatusForTag(token.name, options);
|
|
1840
|
+
if (tagStatus === "preview")
|
|
1841
|
+
addDiagnostic(
|
|
1842
|
+
diagnostics,
|
|
1843
|
+
source,
|
|
1844
|
+
token.start,
|
|
1845
|
+
`<${token.name}> is an Azure Speech preview feature and may change or require preview access.`,
|
|
1846
|
+
"warning",
|
|
1847
|
+
"azure-preview-tag"
|
|
1848
|
+
);
|
|
1849
|
+
if (tagStatus === "deprecated")
|
|
1850
|
+
addDiagnostic(
|
|
1851
|
+
diagnostics,
|
|
1852
|
+
source,
|
|
1853
|
+
token.start,
|
|
1854
|
+
`<${token.name}> is deprecated by Azure Speech; migrate to a supported alternative.`,
|
|
1855
|
+
"info",
|
|
1856
|
+
"azure-deprecated-tag"
|
|
1857
|
+
);
|
|
1764
1858
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
1765
1859
|
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
1766
1860
|
if (name === "break") {
|
|
@@ -1881,28 +1975,40 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
1881
1975
|
validateAudioSource(token, source, diagnostics, options, "audio");
|
|
1882
1976
|
}
|
|
1883
1977
|
if (name === "mstts:turn") {
|
|
1884
|
-
if (!attr(token, "voice")?.trim())
|
|
1885
|
-
addDiagnostic(
|
|
1978
|
+
if (!attr(token, "voice")?.trim() && !attr(token, "speaker")?.trim())
|
|
1979
|
+
addDiagnostic(
|
|
1980
|
+
diagnostics,
|
|
1981
|
+
source,
|
|
1982
|
+
token.start,
|
|
1983
|
+
'<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
|
|
1984
|
+
);
|
|
1886
1985
|
if (token.parentName?.toLowerCase() !== "mstts:dialog")
|
|
1887
1986
|
addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
|
|
1888
1987
|
}
|
|
1889
1988
|
if (name === "mstts:backgroundaudio") {
|
|
1890
1989
|
validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
|
|
1891
1990
|
const volume = attr(token, "volume");
|
|
1892
|
-
if (volume &&
|
|
1991
|
+
if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
|
|
1893
1992
|
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
|
|
1894
1993
|
for (const [attribute, value] of [
|
|
1895
1994
|
["fadein", attr(token, "fadein")],
|
|
1896
1995
|
["fadeout", attr(token, "fadeout")]
|
|
1897
1996
|
]) {
|
|
1898
|
-
if (value && !
|
|
1997
|
+
if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
|
|
1899
1998
|
addDiagnostic(
|
|
1900
1999
|
diagnostics,
|
|
1901
2000
|
source,
|
|
1902
2001
|
token.start,
|
|
1903
|
-
`<mstts:backgroundaudio ${attribute}> must be
|
|
2002
|
+
`<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
|
|
1904
2003
|
);
|
|
1905
2004
|
}
|
|
2005
|
+
if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
|
|
2006
|
+
addDiagnostic(
|
|
2007
|
+
diagnostics,
|
|
2008
|
+
source,
|
|
2009
|
+
token.start,
|
|
2010
|
+
"<mstts:backgroundaudio> must be the first element directly under <speak>."
|
|
2011
|
+
);
|
|
1906
2012
|
if (!token.selfClosing)
|
|
1907
2013
|
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
1908
2014
|
}
|
|
@@ -1934,6 +2040,16 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1934
2040
|
const tokens = tokenizeElements(ssml);
|
|
1935
2041
|
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
1936
2042
|
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
2043
|
+
const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
|
|
2044
|
+
for (const [index, token] of backgroundAudioTokens.entries()) {
|
|
2045
|
+
if (index > 0)
|
|
2046
|
+
addDiagnostic(
|
|
2047
|
+
diagnostics,
|
|
2048
|
+
ssml,
|
|
2049
|
+
token.start,
|
|
2050
|
+
"An SSML document can contain at most one <mstts:backgroundaudio> element."
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
1937
2053
|
if (!speak || voices.length === 0)
|
|
1938
2054
|
addDiagnostic(
|
|
1939
2055
|
diagnostics,
|
|
@@ -1950,6 +2066,24 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1950
2066
|
const name = attr(token, "name")?.trim();
|
|
1951
2067
|
const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
|
|
1952
2068
|
const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
|
|
2069
|
+
if (name && definition?.status === "preview")
|
|
2070
|
+
addDiagnostic(
|
|
2071
|
+
diagnostics,
|
|
2072
|
+
ssml,
|
|
2073
|
+
token.start,
|
|
2074
|
+
`Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
|
|
2075
|
+
"warning",
|
|
2076
|
+
"azure-preview-voice"
|
|
2077
|
+
);
|
|
2078
|
+
if (name && definition?.status === "deprecated")
|
|
2079
|
+
addDiagnostic(
|
|
2080
|
+
diagnostics,
|
|
2081
|
+
ssml,
|
|
2082
|
+
token.start,
|
|
2083
|
+
`Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
|
|
2084
|
+
"info",
|
|
2085
|
+
"azure-deprecated-voice"
|
|
2086
|
+
);
|
|
1953
2087
|
if (name && !definition && policySeverity)
|
|
1954
2088
|
addDiagnostic(
|
|
1955
2089
|
diagnostics,
|
|
@@ -2023,4 +2157,4 @@ export {
|
|
|
2023
2157
|
getAzureVoiceCatalogMetadata,
|
|
2024
2158
|
getBuiltInVoiceCatalogMetadata
|
|
2025
2159
|
};
|
|
2026
|
-
//# sourceMappingURL=chunk-
|
|
2160
|
+
//# sourceMappingURL=chunk-SNRI43QJ.mjs.map
|