ssml-builder-js 2.7.0 → 2.8.1

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/index.js CHANGED
@@ -44,6 +44,7 @@ __export(src_exports, {
44
44
  buildPartialSsml: () => buildPartialSsml,
45
45
  buildSsml: () => buildSsml,
46
46
  extractSsmlText: () => extractSsmlText,
47
+ isValidAzureAudioDuration: () => isValidAzureAudioDuration,
47
48
  mapSsmlTextNodes: () => mapSsmlTextNodes,
48
49
  normalizeAzureLanguage: () => normalizeAzureLanguage,
49
50
  parseSsml: () => parseSsml,
@@ -85,7 +86,8 @@ var SSML_TAGS = {
85
86
  MSTTS_SILENCE: "mstts:silence",
86
87
  SILENCE: "silence",
87
88
  MSTTS_VISEME: "mstts:viseme",
88
- VISEME: "viseme"
89
+ VISEME: "viseme",
90
+ MSTTS_AUDIO_DURATION: "mstts:audioduration"
89
91
  };
90
92
  var SSML_ATTRS = {
91
93
  VERSION: "version",
@@ -214,6 +216,9 @@ function getAttributes(element) {
214
216
  case SSML_TAGS.VISEME:
215
217
  addAttribute(attributes, SSML_ATTRS.TYPE, element.typeValue ?? element.visemeType);
216
218
  break;
219
+ case SSML_TAGS.MSTTS_AUDIO_DURATION:
220
+ addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
221
+ break;
217
222
  case SSML_TAGS.PARAGRAPH:
218
223
  case SSML_TAGS.SENTENCE:
219
224
  case SSML_TAGS.WORD:
@@ -238,6 +243,8 @@ function getTagName(element) {
238
243
  case SSML_TAGS.VISEME:
239
244
  case SSML_TAGS.MSTTS_VISEME:
240
245
  return SSML_TAGS.MSTTS_VISEME;
246
+ case SSML_TAGS.MSTTS_AUDIO_DURATION:
247
+ return SSML_TAGS.MSTTS_AUDIO_DURATION;
241
248
  case "element":
242
249
  case "custom":
243
250
  return element.name;
@@ -772,6 +779,12 @@ function convertElement(node) {
772
779
  if (typeValue !== void 0) element.typeValue = typeValue;
773
780
  return finishElement(element, node, attributes);
774
781
  }
782
+ case SSML_TAGS.MSTTS_AUDIO_DURATION: {
783
+ const element = { type: SSML_TAGS.MSTTS_AUDIO_DURATION };
784
+ const value = readAttribute(attributes, SSML_ATTRS.VALUE);
785
+ if (value !== void 0) element.value = value;
786
+ return finishElement(element, node, attributes);
787
+ }
775
788
  default: {
776
789
  const element = {
777
790
  name: node.name,
@@ -1046,93 +1059,126 @@ async function mapSsmlTextNodes(ssml, transform, options = {}) {
1046
1059
  return result + ssml.slice(cursor);
1047
1060
  }
1048
1061
 
1062
+ // packages/ssml-core/src/generated/azureVoiceDefinitions.ts
1063
+ var AZURE_VOICE_DEFINITIONS = [
1064
+ { name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
1065
+ { name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
1066
+ { name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
1067
+ {
1068
+ name: "en-US-GuyNeural",
1069
+ locale: "en-US",
1070
+ styles: [
1071
+ "angry",
1072
+ "cheerful",
1073
+ "excited",
1074
+ "friendly",
1075
+ "hopeful",
1076
+ "newscast",
1077
+ "sad",
1078
+ "shouting",
1079
+ "terrified",
1080
+ "unfriendly",
1081
+ "whispering"
1082
+ ]
1083
+ },
1084
+ {
1085
+ name: "en-US-JennyMultilingualNeural",
1086
+ locale: "en-US",
1087
+ styles: [
1088
+ "cheerful",
1089
+ "empathetic",
1090
+ "excited",
1091
+ "friendly",
1092
+ "hopeful",
1093
+ "sad",
1094
+ "shouting",
1095
+ "terrified",
1096
+ "unfriendly",
1097
+ "whispering"
1098
+ ]
1099
+ },
1100
+ {
1101
+ name: "en-US-JennyNeural",
1102
+ locale: "en-US",
1103
+ styles: [
1104
+ "assistant",
1105
+ "chat",
1106
+ "customerservice",
1107
+ "newscast",
1108
+ "cheerful",
1109
+ "empathetic",
1110
+ "excited",
1111
+ "friendly",
1112
+ "hopeful",
1113
+ "sad",
1114
+ "shouting",
1115
+ "terrified",
1116
+ "unfriendly",
1117
+ "whispering"
1118
+ ]
1119
+ },
1120
+ { name: "es-ES-ElviraNeural", locale: "es-ES" },
1121
+ { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
1122
+ { name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
1123
+ { name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
1124
+ { name: "id-ID-GadisNeural", locale: "id-ID" },
1125
+ { name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
1126
+ { name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
1127
+ { name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
1128
+ { name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
1129
+ { name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
1130
+ { name: "ms-MY-YasminNeural", locale: "ms-MY" },
1131
+ { name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
1132
+ {
1133
+ name: "ru-RU-SvetlanaNeural",
1134
+ locale: "ru-RU",
1135
+ styles: ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
1136
+ },
1137
+ { name: "th-TH-PremwadeeNeural", locale: "th-TH" },
1138
+ { name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
1139
+ {
1140
+ name: "zh-CN-XiaoxiaoNeural",
1141
+ locale: "zh-CN",
1142
+ styles: [
1143
+ "assistant",
1144
+ "chat",
1145
+ "customerservice",
1146
+ "newscast",
1147
+ "cheerful",
1148
+ "empathetic",
1149
+ "excited",
1150
+ "friendly",
1151
+ "hopeful",
1152
+ "sad",
1153
+ "terrified",
1154
+ "whispering",
1155
+ "poetry-reading",
1156
+ "sports_commentary",
1157
+ "sports_commentary_excited",
1158
+ "story"
1159
+ ]
1160
+ },
1161
+ {
1162
+ name: "zh-CN-YunxiNeural",
1163
+ locale: "zh-CN",
1164
+ styles: [
1165
+ "narration-relaxed",
1166
+ "embarrassed",
1167
+ "fearful",
1168
+ "sad",
1169
+ "disgruntled",
1170
+ "serious",
1171
+ "angry",
1172
+ "depressed",
1173
+ "chat",
1174
+ "cheerful",
1175
+ "assistant"
1176
+ ]
1177
+ },
1178
+ { name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
1179
+ ];
1180
+
1049
1181
  // packages/ssml-core/src/azureValidation.ts
1050
- var EXPRESS_AS_STYLES = {
1051
- "en-us-jennyneural": [
1052
- "assistant",
1053
- "chat",
1054
- "customerservice",
1055
- "newscast",
1056
- "cheerful",
1057
- "empathetic",
1058
- "excited",
1059
- "friendly",
1060
- "hopeful",
1061
- "sad",
1062
- "shouting",
1063
- "terrified",
1064
- "unfriendly",
1065
- "whispering"
1066
- ],
1067
- "en-us-guyneural": [
1068
- "angry",
1069
- "cheerful",
1070
- "excited",
1071
- "friendly",
1072
- "hopeful",
1073
- "newscast",
1074
- "sad",
1075
- "shouting",
1076
- "terrified",
1077
- "unfriendly",
1078
- "whispering"
1079
- ],
1080
- "en-us-jennymultilingualneural": [
1081
- "cheerful",
1082
- "empathetic",
1083
- "excited",
1084
- "friendly",
1085
- "hopeful",
1086
- "sad",
1087
- "shouting",
1088
- "terrified",
1089
- "unfriendly",
1090
- "whispering"
1091
- ],
1092
- "en-us-andrewneural": ["empathetic", "relieved"],
1093
- "ja-jp-mayuneural": ["calm", "cheerful", "sad"],
1094
- "ja-jp-nanamineural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
1095
- "ja-jp-keitaneural": ["chat"],
1096
- "ko-kr-sunhineural": ["cheerful", "sad"],
1097
- "zh-cn-yunxineural": [
1098
- "narration-relaxed",
1099
- "embarrassed",
1100
- "fearful",
1101
- "sad",
1102
- "disgruntled",
1103
- "serious",
1104
- "angry",
1105
- "depressed",
1106
- "chat",
1107
- "cheerful",
1108
- "assistant"
1109
- ],
1110
- "zh-cn-xiaoxiaoneural": [
1111
- "assistant",
1112
- "chat",
1113
- "customerservice",
1114
- "newscast",
1115
- "cheerful",
1116
- "empathetic",
1117
- "excited",
1118
- "friendly",
1119
- "hopeful",
1120
- "sad",
1121
- "terrified",
1122
- "whispering",
1123
- "poetry-reading",
1124
- "sports_commentary",
1125
- "sports_commentary_excited",
1126
- "story"
1127
- ],
1128
- "fr-fr-deniseneural": ["cheerful", "sad"],
1129
- "fr-fr-henrineural": ["cheerful", "sad"],
1130
- "pt-br-franciscaneural": ["calm"],
1131
- "it-it-elsaneural": ["cheerful", "sad"],
1132
- "de-de-katjaneural": ["cheerful", "sad"],
1133
- "de-de-conradneural": ["cheerful", "sad"],
1134
- "ru-ru-svetlananeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
1135
- };
1136
1182
  var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
1137
1183
  var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
1138
1184
  "characters",
@@ -1146,7 +1192,8 @@ var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
1146
1192
  "fraction",
1147
1193
  "address",
1148
1194
  "name",
1149
- "currency"
1195
+ "currency",
1196
+ "number_digit"
1150
1197
  ]);
1151
1198
  var ALLOWED_ROLES = /* @__PURE__ */ new Set([
1152
1199
  "Girl",
@@ -1244,20 +1291,33 @@ function location(source, offset) {
1244
1291
  return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
1245
1292
  }
1246
1293
  function addDiagnostic(diagnostics, source, offset, message, severity = "error", code) {
1247
- diagnostics.push({ ...location(source, offset), message, severity, ...code ? { code } : {} });
1294
+ diagnostics.push({
1295
+ ...location(source, offset),
1296
+ message,
1297
+ severity,
1298
+ source: "ssml-static-validator",
1299
+ ...code ? { code } : {}
1300
+ });
1301
+ }
1302
+ function isSupportedProsodyRate(value) {
1303
+ const trimmed = value.trim();
1304
+ if (/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(trimmed)) return true;
1305
+ const multiplier = /^(\d+(?:\.\d+)?)(x)?$/i.exec(trimmed);
1306
+ if (!multiplier) return false;
1307
+ const numericValue = Number(multiplier[1]);
1308
+ return numericValue >= 0.5 && numericValue <= 2;
1309
+ }
1310
+ function isValidAzureAudioDuration(value) {
1311
+ const trimmed = value.trim();
1312
+ const numeric = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(trimmed);
1313
+ if (numeric) return Number(numeric[1]) > 0;
1314
+ const clock = /^(\d{2,}):([0-5]\d):([0-5]\d)(?:\.(\d{1,3}))?$/.exec(trimmed);
1315
+ if (!clock) return false;
1316
+ return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
1248
1317
  }
1249
1318
  function attr(token, name) {
1250
1319
  return token.attributes.get(name.toLowerCase());
1251
1320
  }
1252
- var ADDITIONAL_VOICE_DEFINITIONS = [
1253
- { name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" },
1254
- { name: "es-ES-ElviraNeural", locale: "es-ES" },
1255
- { name: "th-TH-PremwadeeNeural", locale: "th-TH" },
1256
- { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
1257
- { name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
1258
- { name: "id-ID-GadisNeural", locale: "id-ID" },
1259
- { name: "ms-MY-YasminNeural", locale: "ms-MY" }
1260
- ];
1261
1321
  var DEFAULT_LANGUAGE_ALIASES = {
1262
1322
  "zh-CN": ["zh-Hans"],
1263
1323
  "zh-TW": ["zh-Hant"]
@@ -1320,10 +1380,7 @@ function definitionFromStyleMap(voiceName, styles) {
1320
1380
  }
1321
1381
  function normalizeVoiceCatalog(options) {
1322
1382
  const definitions = /* @__PURE__ */ new Map();
1323
- for (const [name, styles] of Object.entries(EXPRESS_AS_STYLES)) {
1324
- definitions.set(name.toLowerCase(), definitionFromStyleMap(name, styles));
1325
- }
1326
- for (const definition of ADDITIONAL_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
1383
+ for (const definition of AZURE_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
1327
1384
  for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
1328
1385
  for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
1329
1386
  for (const definition of options.customVoiceDefinitions ?? [])
@@ -1379,7 +1436,7 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
1379
1436
  const rate = attr(token, "rate");
1380
1437
  const pitch = attr(token, "pitch");
1381
1438
  const volume = attr(token, "volume");
1382
- if (rate && !/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(rate.trim()))
1439
+ if (rate && !isSupportedProsodyRate(rate))
1383
1440
  addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody rate> value "${rate}".`);
1384
1441
  if (pitch && !/^(x-low|low|medium|high|x-high|[+-]?\d+(?:\.\d+)?(?:st|Hz|%)?)$/.test(pitch.trim()))
1385
1442
  addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody pitch> value "${pitch}".`);
@@ -1460,6 +1517,18 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
1460
1517
  if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
1461
1518
  addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
1462
1519
  }
1520
+ if (name === "mstts:audioduration") {
1521
+ const value = attr(token, "value");
1522
+ if (!value || !isValidAzureAudioDuration(value))
1523
+ addDiagnostic(
1524
+ diagnostics,
1525
+ source,
1526
+ token.start,
1527
+ '<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
1528
+ );
1529
+ if (!token.selfClosing)
1530
+ addDiagnostic(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
1531
+ }
1463
1532
  if (name === "mstts:viseme") {
1464
1533
  const type = attr(token, "type");
1465
1534
  if (!type || !ALLOWED_VISEME_TYPES.has(type))
@@ -1494,7 +1563,15 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
1494
1563
  function validateAzureSsml(ssml, options = {}) {
1495
1564
  const diagnostics = [];
1496
1565
  if (typeof ssml !== "string") {
1497
- return [{ line: 1, column: 1, message: "SSML input must be a string", severity: "error" }];
1566
+ return [
1567
+ {
1568
+ line: 1,
1569
+ column: 1,
1570
+ message: "SSML input must be a string",
1571
+ severity: "error",
1572
+ source: "ssml-static-validator"
1573
+ }
1574
+ ];
1498
1575
  }
1499
1576
  const maxLength = options.maxLength ?? 1e4;
1500
1577
  if (ssml.length > maxLength)
@@ -1742,6 +1819,7 @@ _options = new WeakMap();
1742
1819
  buildPartialSsml,
1743
1820
  buildSsml,
1744
1821
  extractSsmlText,
1822
+ isValidAzureAudioDuration,
1745
1823
  mapSsmlTextNodes,
1746
1824
  normalizeAzureLanguage,
1747
1825
  parseSsml,