ssml-builder-js 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/dist/core.mjs CHANGED
@@ -11,10 +11,11 @@ import {
11
11
  mapSsmlTextNodes,
12
12
  normalizeAzureLanguage,
13
13
  parseSsml,
14
+ splitSsmlDocument,
14
15
  validateAzureSsml,
15
16
  validateSsml,
16
17
  validateSsmlStructureIntegrity
17
- } from "./chunk-GW6CKHXF.mjs";
18
+ } from "./chunk-4BVNAUVR.mjs";
18
19
  import "./chunk-6S5ODO6A.mjs";
19
20
  export {
20
21
  areAzureLanguagesEquivalent,
@@ -29,6 +30,7 @@ export {
29
30
  mapSsmlTextNodes,
30
31
  normalizeAzureLanguage,
31
32
  parseSsml,
33
+ splitSsmlDocument,
32
34
  validateAzureSsml,
33
35
  validateSsml,
34
36
  validateSsmlStructureIntegrity
package/dist/elements.js CHANGED
@@ -956,6 +956,9 @@ var AZURE_VOICE_DEFINITIONS = [
956
956
  },
957
957
  { name: "es-ES-ElviraNeural", locale: "es-ES" },
958
958
  { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
959
+ { name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
960
+ { name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
961
+ { name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
959
962
  { name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
960
963
  { name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
961
964
  { name: "id-ID-GadisNeural", locale: "id-ID" },
@@ -1050,6 +1053,18 @@ var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
1050
1053
  "Enumerationcomma"
1051
1054
  ]);
1052
1055
  var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
1056
+ var DEFAULT_PREVIEW_TAGS = /* @__PURE__ */ new Set(["mstts:voiceconversion"]);
1057
+ function featureStatusForTag(name, options) {
1058
+ const tagName = canonicalTagName(name);
1059
+ const configured = Object.entries(options.tagStatuses ?? {}).find(
1060
+ ([candidate]) => canonicalTagName(candidate) === tagName
1061
+ )?.[1];
1062
+ if (configured) return configured;
1063
+ if ((options.previewTags ?? [...DEFAULT_PREVIEW_TAGS]).some((candidate) => canonicalTagName(candidate) === tagName))
1064
+ return "preview";
1065
+ if ((options.deprecatedTags ?? []).some((candidate) => canonicalTagName(candidate) === tagName)) return "deprecated";
1066
+ return void 0;
1067
+ }
1053
1068
  function decodeAttribute(value) {
1054
1069
  return value.replace(
1055
1070
  /&(?:amp|apos|gt|lt|quot);/gi,
@@ -1118,6 +1133,7 @@ function tokenizeElements(source) {
1118
1133
  attributes,
1119
1134
  childElementIndex,
1120
1135
  end,
1136
+ depth: openElements.length + 1,
1121
1137
  name: tokenName,
1122
1138
  parentName: parent?.name,
1123
1139
  parentVoiceName,
@@ -1166,9 +1182,9 @@ function isValidAzureAudioDuration(value) {
1166
1182
  return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
1167
1183
  }
1168
1184
  function isValidAzureBackgroundAudioDuration(value) {
1169
- const match = /^(\d+(?:\.\d+)?)(ms|s)?$/i.exec(value.trim());
1185
+ const match = /^(\d+)$/.exec(value.trim());
1170
1186
  if (!match) return false;
1171
- const milliseconds = Number(match[1]) * (match[2]?.toLowerCase() === "s" ? 1e3 : 1);
1187
+ const milliseconds = Number(match[1]);
1172
1188
  return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
1173
1189
  }
1174
1190
  function attr(token, name) {
@@ -1298,13 +1314,18 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
1298
1314
  addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
1299
1315
  return;
1300
1316
  }
1317
+ if (parsed.username || parsed.password)
1318
+ addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must not contain URL credentials.`);
1301
1319
  if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
1302
1320
  addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
1303
1321
  const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
1304
1322
  try {
1305
- return new URL(allowedOrigin).origin === parsed.origin;
1323
+ const configured = new URL(allowedOrigin);
1324
+ if (configured.protocol !== "https:" && configured.protocol !== "http:" || configured.username || configured.password || configured.pathname !== "/" || configured.search || configured.hash)
1325
+ return false;
1326
+ return configured.origin === parsed.origin;
1306
1327
  } catch {
1307
- return allowedOrigin === parsed.origin;
1328
+ return false;
1308
1329
  }
1309
1330
  }) ?? false;
1310
1331
  if (options.allowedAudioOrigins && !isAllowedOrigin)
@@ -1319,6 +1340,25 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
1319
1340
  }
1320
1341
  function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
1321
1342
  const name = token.name.toLowerCase();
1343
+ const tagStatus = featureStatusForTag(token.name, options);
1344
+ if (tagStatus === "preview")
1345
+ addDiagnostic(
1346
+ diagnostics,
1347
+ source,
1348
+ token.start,
1349
+ `<${token.name}> is an Azure Speech preview feature and may change or require preview access.`,
1350
+ "warning",
1351
+ "azure-preview-tag"
1352
+ );
1353
+ if (tagStatus === "deprecated")
1354
+ addDiagnostic(
1355
+ diagnostics,
1356
+ source,
1357
+ token.start,
1358
+ `<${token.name}> is deprecated by Azure Speech; migrate to a supported alternative.`,
1359
+ "info",
1360
+ "azure-deprecated-tag"
1361
+ );
1322
1362
  if (name === "voice" && !attr(token, "name")?.trim())
1323
1363
  addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
1324
1364
  if (name === "break") {
@@ -1493,6 +1533,9 @@ function validateAzureSsml(ssml, options = {}) {
1493
1533
  const maxLength = options.maxLength ?? 1e4;
1494
1534
  if (ssml.length > maxLength)
1495
1535
  addDiagnostic(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
1536
+ if (options.maxXmlDepth !== void 0 && (!Number.isInteger(options.maxXmlDepth) || options.maxXmlDepth <= 0)) {
1537
+ addDiagnostic(diagnostics, ssml, 0, "maxXmlDepth must be a positive integer.");
1538
+ }
1496
1539
  try {
1497
1540
  parseSsml(ssml);
1498
1541
  } catch (error) {
@@ -1502,6 +1545,18 @@ function validateAzureSsml(ssml, options = {}) {
1502
1545
  return diagnostics;
1503
1546
  }
1504
1547
  const tokens = tokenizeElements(ssml);
1548
+ if (options.maxXmlDepth !== void 0) {
1549
+ for (const token of tokens) {
1550
+ if (token.depth > options.maxXmlDepth) {
1551
+ addDiagnostic(
1552
+ diagnostics,
1553
+ ssml,
1554
+ token.start,
1555
+ `XML nesting depth ${token.depth} exceeds the configured maximum of ${options.maxXmlDepth}.`
1556
+ );
1557
+ }
1558
+ }
1559
+ }
1505
1560
  const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
1506
1561
  const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
1507
1562
  const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
@@ -1530,6 +1585,24 @@ function validateAzureSsml(ssml, options = {}) {
1530
1585
  const name = attr(token, "name")?.trim();
1531
1586
  const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
1532
1587
  const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
1588
+ if (name && definition?.status === "preview")
1589
+ addDiagnostic(
1590
+ diagnostics,
1591
+ ssml,
1592
+ token.start,
1593
+ `Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
1594
+ "warning",
1595
+ "azure-preview-voice"
1596
+ );
1597
+ if (name && definition?.status === "deprecated")
1598
+ addDiagnostic(
1599
+ diagnostics,
1600
+ ssml,
1601
+ token.start,
1602
+ `Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
1603
+ "info",
1604
+ "azure-deprecated-voice"
1605
+ );
1533
1606
  if (name && !definition && policySeverity)
1534
1607
  addDiagnostic(
1535
1608
  diagnostics,
@@ -2295,6 +2368,7 @@ var ssmlPresets_exports = {};
2295
2368
  __export(ssmlPresets_exports, {
2296
2369
  AUDIO_DURATION_DESCRIPTIONS: () => AUDIO_DURATION_DESCRIPTIONS,
2297
2370
  AUDIO_DURATION_PRESETS: () => AUDIO_DURATION_PRESETS,
2371
+ BACKGROUND_AUDIO_DURATION_PRESETS: () => BACKGROUND_AUDIO_DURATION_PRESETS,
2298
2372
  BREAK_STRENGTH_PRESETS: () => BREAK_STRENGTH_PRESETS,
2299
2373
  BREAK_TIME_DESCRIPTIONS: () => BREAK_TIME_DESCRIPTIONS,
2300
2374
  BREAK_TIME_PRESETS: () => BREAK_TIME_PRESETS,
@@ -2376,6 +2450,9 @@ var AZURE_VOICE_STYLE_MAP = {
2376
2450
  ],
2377
2451
  "es-ES-ElviraNeural": [],
2378
2452
  "fil-PH-AngeloNeural": [],
2453
+ "fil-PH-Angelo:DragonHDLatestNeural": [],
2454
+ "fil-PH-BlessicaNeural": [],
2455
+ "fil-PH-Blessica:DragonHDLatestNeural": [],
2379
2456
  "fr-FR-DeniseNeural": ["cheerful", "sad"],
2380
2457
  "fr-FR-HenriNeural": ["cheerful", "sad"],
2381
2458
  "id-ID-GadisNeural": [],
@@ -2653,6 +2730,7 @@ var SAY_AS_PRESETS = [
2653
2730
  var LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"];
2654
2731
  var SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"];
2655
2732
  var AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"];
2733
+ var BACKGROUND_AUDIO_DURATION_PRESETS = ["0", "500", "1000", "3000", "10000"];
2656
2734
  var SILENCE_TYPE_PRESETS = [
2657
2735
  "Leading",
2658
2736
  "Tailing",
@@ -2714,8 +2792,8 @@ var SSML_ATTRIBUTE_PRESETS = {
2714
2792
  },
2715
2793
  "mstts:backgroundaudio": {
2716
2794
  volume: PROSODY_VOLUME_PRESETS,
2717
- fadein: AUDIO_DURATION_PRESETS,
2718
- fadeout: AUDIO_DURATION_PRESETS
2795
+ fadein: BACKGROUND_AUDIO_DURATION_PRESETS,
2796
+ fadeout: BACKGROUND_AUDIO_DURATION_PRESETS
2719
2797
  },
2720
2798
  silence: {
2721
2799
  type: SILENCE_TYPE_PRESETS,
@@ -3220,7 +3298,7 @@ var SSML_COMPLETION_SNIPPETS = [
3220
3298
  },
3221
3299
  {
3222
3300
  label: "mstts:backgroundaudio",
3223
- insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:-3dB}" />`
3301
+ insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:70}" fadein="\${3:1000}" fadeout="\${4:1000}" />`
3224
3302
  },
3225
3303
  {
3226
3304
  label: "mstts:ttsembedding",
@@ -3999,18 +4077,18 @@ var SSML_TAG_DEFINITIONS = [
3999
4077
  },
4000
4078
  {
4001
4079
  name: "volume",
4002
- description: "The background audio volume, for example `-3dB` or `medium`.",
4003
- example: "-3dB"
4080
+ description: "The background audio volume from 0 to 100.",
4081
+ example: "70"
4004
4082
  },
4005
4083
  {
4006
4084
  name: "fadein",
4007
- description: "The fade-in duration, for example `1s`.",
4008
- example: "1s"
4085
+ description: "The fade-in duration in milliseconds from 0 to 10000.",
4086
+ example: "1000"
4009
4087
  },
4010
4088
  {
4011
4089
  name: "fadeout",
4012
- description: "The fade-out duration, for example `500ms`.",
4013
- example: "500ms"
4090
+ description: "The fade-out duration in milliseconds from 0 to 10000.",
4091
+ example: "1000"
4014
4092
  }
4015
4093
  ]
4016
4094
  },