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/core.d.mts CHANGED
@@ -99,11 +99,15 @@ interface MsttsVisemeElement extends SsmlElementBase {
99
99
  typeValue?: string;
100
100
  visemeType?: string;
101
101
  }
102
+ interface MsttsAudioDurationElement extends SsmlElementBase {
103
+ type: "mstts:audioduration";
104
+ value?: SsmlAttributeValue;
105
+ }
102
106
  interface CustomElement extends SsmlElementBase {
103
107
  type: "custom" | "element";
104
108
  name: string;
105
109
  }
106
- type SsmlElement = VoiceElement | ProsodyElement | BreakElement | ExpressAsElement | SayAsElement | PhonemeElement | EmphasisElement | AudioElement | SubElement | LangElement | MarkElement | BookmarkElement | LexiconElement | ParagraphElement | SentenceElement | WordElement | MsttsSilenceElement | MsttsVisemeElement | CustomElement;
110
+ type SsmlElement = VoiceElement | ProsodyElement | BreakElement | ExpressAsElement | SayAsElement | PhonemeElement | EmphasisElement | AudioElement | SubElement | LangElement | MarkElement | BookmarkElement | LexiconElement | ParagraphElement | SentenceElement | WordElement | MsttsSilenceElement | MsttsVisemeElement | MsttsAudioDurationElement | CustomElement;
107
111
  interface SsmlDocument {
108
112
  type?: "speak";
109
113
  version: string;
@@ -172,6 +176,7 @@ declare function extractSsmlText(ssml: string): string[];
172
176
  declare function mapSsmlTextNodes(ssml: string, transform: (text: string, context: SsmlTextNodeContext) => string | Promise<string>, options?: MapSsmlTextNodesOptions): Promise<string>;
173
177
 
174
178
  type SsmlDiagnosticSeverity = "error" | "warning";
179
+ type SsmlDiagnosticSource = "ssml-static-validator";
175
180
  type AzureDiagnosticCode = "azure-unknown-voice" | "azure-unsupported-style" | "azure-locale-mismatch";
176
181
  interface SsmlDiagnostic {
177
182
  code?: AzureDiagnosticCode;
@@ -179,6 +184,7 @@ interface SsmlDiagnostic {
179
184
  column: number;
180
185
  message: string;
181
186
  severity: SsmlDiagnosticSeverity;
187
+ source: SsmlDiagnosticSource;
182
188
  }
183
189
  interface AzureVoiceDefinition {
184
190
  name: string;
@@ -207,10 +213,12 @@ interface AzureValidationOptions {
207
213
  type AzureLanguageNormalizationOptions = Pick<AzureValidationOptions, "languageAliases" | "normalizeLanguage">;
208
214
  /** @deprecated Use AzureValidationOptions instead. */
209
215
  type AzureSsmlValidationOptions = AzureValidationOptions;
216
+ /** Returns whether a value is a positive Azure SSML audio duration. */
217
+ declare function isValidAzureAudioDuration(value: string): boolean;
210
218
  /** Normalizes an Azure language tag using BCP 47 and the configured aliases. */
211
219
  declare function normalizeAzureLanguage(language: string, options?: AzureLanguageNormalizationOptions): string;
212
220
  /** Compares two Azure language tags after BCP 47 and alias normalization. */
213
221
  declare function areAzureLanguagesEquivalent(first: string, second: string, options?: AzureLanguageNormalizationOptions): boolean;
214
222
  declare function validateAzureSsml(ssml: string, options?: AzureValidationOptions): SsmlDiagnostic[];
215
223
 
216
- export { type AudioElement, type AzureDiagnosticCode, type AzureLanguageNormalizationOptions, type AzureSsmlValidationOptions, type AzureValidationOptions, type AzureVoiceDefinition, type AzureVoiceMetadata, type BookmarkElement, type BreakElement, type BuildPartialSsmlOptions, type CustomElement, type EmphasisElement, type ExpressAsElement, type LangElement, type LexiconElement, type MapSsmlTextNodesOptions, type MarkElement, type MsttsSilenceElement, type MsttsVisemeElement, type NamedElement, type ParagraphElement, type PhonemeElement, type ProsodyElement, type SayAsElement, type SentenceElement, type SsmlAttributeValue, type SsmlAttributes, type SsmlBreakElement, type SsmlDiagnostic, type SsmlDiagnosticSeverity, type SsmlDocument, type SsmlElement, type SsmlElementBase, type SsmlExpressAsElement, type SsmlNode, type SsmlPartialContext, type SsmlPartialProsody, type SsmlPartialVoice, type SsmlPhonemeElement, type SsmlProsodyElement, type SsmlSayAsElement, type SsmlText, type SsmlTextNodeContext, type SsmlValidationError, type SsmlVoiceElement, type SubElement, type VoiceElement, type WordElement, areAzureLanguagesEquivalent, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, normalizeAzureLanguage, parseSsml, validateAzureSsml, validateSsml };
224
+ export { type AudioElement, type AzureDiagnosticCode, type AzureLanguageNormalizationOptions, type AzureSsmlValidationOptions, type AzureValidationOptions, type AzureVoiceDefinition, type AzureVoiceMetadata, type BookmarkElement, type BreakElement, type BuildPartialSsmlOptions, type CustomElement, type EmphasisElement, type ExpressAsElement, type LangElement, type LexiconElement, type MapSsmlTextNodesOptions, type MarkElement, type MsttsAudioDurationElement, type MsttsSilenceElement, type MsttsVisemeElement, type NamedElement, type ParagraphElement, type PhonemeElement, type ProsodyElement, type SayAsElement, type SentenceElement, type SsmlAttributeValue, type SsmlAttributes, type SsmlBreakElement, type SsmlDiagnostic, type SsmlDiagnosticSeverity, type SsmlDiagnosticSource, type SsmlDocument, type SsmlElement, type SsmlElementBase, type SsmlExpressAsElement, type SsmlNode, type SsmlPartialContext, type SsmlPartialProsody, type SsmlPartialVoice, type SsmlPhonemeElement, type SsmlProsodyElement, type SsmlSayAsElement, type SsmlText, type SsmlTextNodeContext, type SsmlValidationError, type SsmlVoiceElement, type SubElement, type VoiceElement, type WordElement, areAzureLanguagesEquivalent, buildPartialSsml, buildSsml, extractSsmlText, isValidAzureAudioDuration, mapSsmlTextNodes, normalizeAzureLanguage, parseSsml, validateAzureSsml, validateSsml };
package/dist/core.d.ts CHANGED
@@ -99,11 +99,15 @@ interface MsttsVisemeElement extends SsmlElementBase {
99
99
  typeValue?: string;
100
100
  visemeType?: string;
101
101
  }
102
+ interface MsttsAudioDurationElement extends SsmlElementBase {
103
+ type: "mstts:audioduration";
104
+ value?: SsmlAttributeValue;
105
+ }
102
106
  interface CustomElement extends SsmlElementBase {
103
107
  type: "custom" | "element";
104
108
  name: string;
105
109
  }
106
- type SsmlElement = VoiceElement | ProsodyElement | BreakElement | ExpressAsElement | SayAsElement | PhonemeElement | EmphasisElement | AudioElement | SubElement | LangElement | MarkElement | BookmarkElement | LexiconElement | ParagraphElement | SentenceElement | WordElement | MsttsSilenceElement | MsttsVisemeElement | CustomElement;
110
+ type SsmlElement = VoiceElement | ProsodyElement | BreakElement | ExpressAsElement | SayAsElement | PhonemeElement | EmphasisElement | AudioElement | SubElement | LangElement | MarkElement | BookmarkElement | LexiconElement | ParagraphElement | SentenceElement | WordElement | MsttsSilenceElement | MsttsVisemeElement | MsttsAudioDurationElement | CustomElement;
107
111
  interface SsmlDocument {
108
112
  type?: "speak";
109
113
  version: string;
@@ -172,6 +176,7 @@ declare function extractSsmlText(ssml: string): string[];
172
176
  declare function mapSsmlTextNodes(ssml: string, transform: (text: string, context: SsmlTextNodeContext) => string | Promise<string>, options?: MapSsmlTextNodesOptions): Promise<string>;
173
177
 
174
178
  type SsmlDiagnosticSeverity = "error" | "warning";
179
+ type SsmlDiagnosticSource = "ssml-static-validator";
175
180
  type AzureDiagnosticCode = "azure-unknown-voice" | "azure-unsupported-style" | "azure-locale-mismatch";
176
181
  interface SsmlDiagnostic {
177
182
  code?: AzureDiagnosticCode;
@@ -179,6 +184,7 @@ interface SsmlDiagnostic {
179
184
  column: number;
180
185
  message: string;
181
186
  severity: SsmlDiagnosticSeverity;
187
+ source: SsmlDiagnosticSource;
182
188
  }
183
189
  interface AzureVoiceDefinition {
184
190
  name: string;
@@ -207,10 +213,12 @@ interface AzureValidationOptions {
207
213
  type AzureLanguageNormalizationOptions = Pick<AzureValidationOptions, "languageAliases" | "normalizeLanguage">;
208
214
  /** @deprecated Use AzureValidationOptions instead. */
209
215
  type AzureSsmlValidationOptions = AzureValidationOptions;
216
+ /** Returns whether a value is a positive Azure SSML audio duration. */
217
+ declare function isValidAzureAudioDuration(value: string): boolean;
210
218
  /** Normalizes an Azure language tag using BCP 47 and the configured aliases. */
211
219
  declare function normalizeAzureLanguage(language: string, options?: AzureLanguageNormalizationOptions): string;
212
220
  /** Compares two Azure language tags after BCP 47 and alias normalization. */
213
221
  declare function areAzureLanguagesEquivalent(first: string, second: string, options?: AzureLanguageNormalizationOptions): boolean;
214
222
  declare function validateAzureSsml(ssml: string, options?: AzureValidationOptions): SsmlDiagnostic[];
215
223
 
216
- export { type AudioElement, type AzureDiagnosticCode, type AzureLanguageNormalizationOptions, type AzureSsmlValidationOptions, type AzureValidationOptions, type AzureVoiceDefinition, type AzureVoiceMetadata, type BookmarkElement, type BreakElement, type BuildPartialSsmlOptions, type CustomElement, type EmphasisElement, type ExpressAsElement, type LangElement, type LexiconElement, type MapSsmlTextNodesOptions, type MarkElement, type MsttsSilenceElement, type MsttsVisemeElement, type NamedElement, type ParagraphElement, type PhonemeElement, type ProsodyElement, type SayAsElement, type SentenceElement, type SsmlAttributeValue, type SsmlAttributes, type SsmlBreakElement, type SsmlDiagnostic, type SsmlDiagnosticSeverity, type SsmlDocument, type SsmlElement, type SsmlElementBase, type SsmlExpressAsElement, type SsmlNode, type SsmlPartialContext, type SsmlPartialProsody, type SsmlPartialVoice, type SsmlPhonemeElement, type SsmlProsodyElement, type SsmlSayAsElement, type SsmlText, type SsmlTextNodeContext, type SsmlValidationError, type SsmlVoiceElement, type SubElement, type VoiceElement, type WordElement, areAzureLanguagesEquivalent, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, normalizeAzureLanguage, parseSsml, validateAzureSsml, validateSsml };
224
+ export { type AudioElement, type AzureDiagnosticCode, type AzureLanguageNormalizationOptions, type AzureSsmlValidationOptions, type AzureValidationOptions, type AzureVoiceDefinition, type AzureVoiceMetadata, type BookmarkElement, type BreakElement, type BuildPartialSsmlOptions, type CustomElement, type EmphasisElement, type ExpressAsElement, type LangElement, type LexiconElement, type MapSsmlTextNodesOptions, type MarkElement, type MsttsAudioDurationElement, type MsttsSilenceElement, type MsttsVisemeElement, type NamedElement, type ParagraphElement, type PhonemeElement, type ProsodyElement, type SayAsElement, type SentenceElement, type SsmlAttributeValue, type SsmlAttributes, type SsmlBreakElement, type SsmlDiagnostic, type SsmlDiagnosticSeverity, type SsmlDiagnosticSource, type SsmlDocument, type SsmlElement, type SsmlElementBase, type SsmlExpressAsElement, type SsmlNode, type SsmlPartialContext, type SsmlPartialProsody, type SsmlPartialVoice, type SsmlPhonemeElement, type SsmlProsodyElement, type SsmlSayAsElement, type SsmlText, type SsmlTextNodeContext, type SsmlValidationError, type SsmlVoiceElement, type SubElement, type VoiceElement, type WordElement, areAzureLanguagesEquivalent, buildPartialSsml, buildSsml, extractSsmlText, isValidAzureAudioDuration, mapSsmlTextNodes, normalizeAzureLanguage, parseSsml, validateAzureSsml, validateSsml };
package/dist/core.js CHANGED
@@ -31,6 +31,7 @@ __export(core_exports, {
31
31
  buildPartialSsml: () => buildPartialSsml,
32
32
  buildSsml: () => buildSsml,
33
33
  extractSsmlText: () => extractSsmlText,
34
+ isValidAzureAudioDuration: () => isValidAzureAudioDuration,
34
35
  mapSsmlTextNodes: () => mapSsmlTextNodes,
35
36
  normalizeAzureLanguage: () => normalizeAzureLanguage,
36
37
  parseSsml: () => parseSsml,
@@ -71,7 +72,8 @@ var SSML_TAGS = {
71
72
  MSTTS_SILENCE: "mstts:silence",
72
73
  SILENCE: "silence",
73
74
  MSTTS_VISEME: "mstts:viseme",
74
- VISEME: "viseme"
75
+ VISEME: "viseme",
76
+ MSTTS_AUDIO_DURATION: "mstts:audioduration"
75
77
  };
76
78
  var SSML_ATTRS = {
77
79
  VERSION: "version",
@@ -200,6 +202,9 @@ function getAttributes(element) {
200
202
  case SSML_TAGS.VISEME:
201
203
  addAttribute(attributes, SSML_ATTRS.TYPE, element.typeValue ?? element.visemeType);
202
204
  break;
205
+ case SSML_TAGS.MSTTS_AUDIO_DURATION:
206
+ addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
207
+ break;
203
208
  case SSML_TAGS.PARAGRAPH:
204
209
  case SSML_TAGS.SENTENCE:
205
210
  case SSML_TAGS.WORD:
@@ -224,6 +229,8 @@ function getTagName(element) {
224
229
  case SSML_TAGS.VISEME:
225
230
  case SSML_TAGS.MSTTS_VISEME:
226
231
  return SSML_TAGS.MSTTS_VISEME;
232
+ case SSML_TAGS.MSTTS_AUDIO_DURATION:
233
+ return SSML_TAGS.MSTTS_AUDIO_DURATION;
227
234
  case "element":
228
235
  case "custom":
229
236
  return element.name;
@@ -758,6 +765,12 @@ function convertElement(node) {
758
765
  if (typeValue !== void 0) element.typeValue = typeValue;
759
766
  return finishElement(element, node, attributes);
760
767
  }
768
+ case SSML_TAGS.MSTTS_AUDIO_DURATION: {
769
+ const element = { type: SSML_TAGS.MSTTS_AUDIO_DURATION };
770
+ const value = readAttribute(attributes, SSML_ATTRS.VALUE);
771
+ if (value !== void 0) element.value = value;
772
+ return finishElement(element, node, attributes);
773
+ }
761
774
  default: {
762
775
  const element = {
763
776
  name: node.name,
@@ -1032,93 +1045,126 @@ async function mapSsmlTextNodes(ssml, transform, options = {}) {
1032
1045
  return result + ssml.slice(cursor);
1033
1046
  }
1034
1047
 
1048
+ // packages/ssml-core/src/generated/azureVoiceDefinitions.ts
1049
+ var AZURE_VOICE_DEFINITIONS = [
1050
+ { name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
1051
+ { name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
1052
+ { name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
1053
+ {
1054
+ name: "en-US-GuyNeural",
1055
+ locale: "en-US",
1056
+ styles: [
1057
+ "angry",
1058
+ "cheerful",
1059
+ "excited",
1060
+ "friendly",
1061
+ "hopeful",
1062
+ "newscast",
1063
+ "sad",
1064
+ "shouting",
1065
+ "terrified",
1066
+ "unfriendly",
1067
+ "whispering"
1068
+ ]
1069
+ },
1070
+ {
1071
+ name: "en-US-JennyMultilingualNeural",
1072
+ locale: "en-US",
1073
+ styles: [
1074
+ "cheerful",
1075
+ "empathetic",
1076
+ "excited",
1077
+ "friendly",
1078
+ "hopeful",
1079
+ "sad",
1080
+ "shouting",
1081
+ "terrified",
1082
+ "unfriendly",
1083
+ "whispering"
1084
+ ]
1085
+ },
1086
+ {
1087
+ name: "en-US-JennyNeural",
1088
+ locale: "en-US",
1089
+ styles: [
1090
+ "assistant",
1091
+ "chat",
1092
+ "customerservice",
1093
+ "newscast",
1094
+ "cheerful",
1095
+ "empathetic",
1096
+ "excited",
1097
+ "friendly",
1098
+ "hopeful",
1099
+ "sad",
1100
+ "shouting",
1101
+ "terrified",
1102
+ "unfriendly",
1103
+ "whispering"
1104
+ ]
1105
+ },
1106
+ { name: "es-ES-ElviraNeural", locale: "es-ES" },
1107
+ { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
1108
+ { name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
1109
+ { name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
1110
+ { name: "id-ID-GadisNeural", locale: "id-ID" },
1111
+ { name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
1112
+ { name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
1113
+ { name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
1114
+ { name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
1115
+ { name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
1116
+ { name: "ms-MY-YasminNeural", locale: "ms-MY" },
1117
+ { name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
1118
+ {
1119
+ name: "ru-RU-SvetlanaNeural",
1120
+ locale: "ru-RU",
1121
+ styles: ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
1122
+ },
1123
+ { name: "th-TH-PremwadeeNeural", locale: "th-TH" },
1124
+ { name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
1125
+ {
1126
+ name: "zh-CN-XiaoxiaoNeural",
1127
+ locale: "zh-CN",
1128
+ styles: [
1129
+ "assistant",
1130
+ "chat",
1131
+ "customerservice",
1132
+ "newscast",
1133
+ "cheerful",
1134
+ "empathetic",
1135
+ "excited",
1136
+ "friendly",
1137
+ "hopeful",
1138
+ "sad",
1139
+ "terrified",
1140
+ "whispering",
1141
+ "poetry-reading",
1142
+ "sports_commentary",
1143
+ "sports_commentary_excited",
1144
+ "story"
1145
+ ]
1146
+ },
1147
+ {
1148
+ name: "zh-CN-YunxiNeural",
1149
+ locale: "zh-CN",
1150
+ styles: [
1151
+ "narration-relaxed",
1152
+ "embarrassed",
1153
+ "fearful",
1154
+ "sad",
1155
+ "disgruntled",
1156
+ "serious",
1157
+ "angry",
1158
+ "depressed",
1159
+ "chat",
1160
+ "cheerful",
1161
+ "assistant"
1162
+ ]
1163
+ },
1164
+ { name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
1165
+ ];
1166
+
1035
1167
  // packages/ssml-core/src/azureValidation.ts
1036
- var EXPRESS_AS_STYLES = {
1037
- "en-us-jennyneural": [
1038
- "assistant",
1039
- "chat",
1040
- "customerservice",
1041
- "newscast",
1042
- "cheerful",
1043
- "empathetic",
1044
- "excited",
1045
- "friendly",
1046
- "hopeful",
1047
- "sad",
1048
- "shouting",
1049
- "terrified",
1050
- "unfriendly",
1051
- "whispering"
1052
- ],
1053
- "en-us-guyneural": [
1054
- "angry",
1055
- "cheerful",
1056
- "excited",
1057
- "friendly",
1058
- "hopeful",
1059
- "newscast",
1060
- "sad",
1061
- "shouting",
1062
- "terrified",
1063
- "unfriendly",
1064
- "whispering"
1065
- ],
1066
- "en-us-jennymultilingualneural": [
1067
- "cheerful",
1068
- "empathetic",
1069
- "excited",
1070
- "friendly",
1071
- "hopeful",
1072
- "sad",
1073
- "shouting",
1074
- "terrified",
1075
- "unfriendly",
1076
- "whispering"
1077
- ],
1078
- "en-us-andrewneural": ["empathetic", "relieved"],
1079
- "ja-jp-mayuneural": ["calm", "cheerful", "sad"],
1080
- "ja-jp-nanamineural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
1081
- "ja-jp-keitaneural": ["chat"],
1082
- "ko-kr-sunhineural": ["cheerful", "sad"],
1083
- "zh-cn-yunxineural": [
1084
- "narration-relaxed",
1085
- "embarrassed",
1086
- "fearful",
1087
- "sad",
1088
- "disgruntled",
1089
- "serious",
1090
- "angry",
1091
- "depressed",
1092
- "chat",
1093
- "cheerful",
1094
- "assistant"
1095
- ],
1096
- "zh-cn-xiaoxiaoneural": [
1097
- "assistant",
1098
- "chat",
1099
- "customerservice",
1100
- "newscast",
1101
- "cheerful",
1102
- "empathetic",
1103
- "excited",
1104
- "friendly",
1105
- "hopeful",
1106
- "sad",
1107
- "terrified",
1108
- "whispering",
1109
- "poetry-reading",
1110
- "sports_commentary",
1111
- "sports_commentary_excited",
1112
- "story"
1113
- ],
1114
- "fr-fr-deniseneural": ["cheerful", "sad"],
1115
- "fr-fr-henrineural": ["cheerful", "sad"],
1116
- "pt-br-franciscaneural": ["calm"],
1117
- "it-it-elsaneural": ["cheerful", "sad"],
1118
- "de-de-katjaneural": ["cheerful", "sad"],
1119
- "de-de-conradneural": ["cheerful", "sad"],
1120
- "ru-ru-svetlananeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
1121
- };
1122
1168
  var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
1123
1169
  var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
1124
1170
  "characters",
@@ -1132,7 +1178,8 @@ var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
1132
1178
  "fraction",
1133
1179
  "address",
1134
1180
  "name",
1135
- "currency"
1181
+ "currency",
1182
+ "number_digit"
1136
1183
  ]);
1137
1184
  var ALLOWED_ROLES = /* @__PURE__ */ new Set([
1138
1185
  "Girl",
@@ -1230,20 +1277,33 @@ function location(source, offset) {
1230
1277
  return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
1231
1278
  }
1232
1279
  function addDiagnostic(diagnostics, source, offset, message, severity = "error", code) {
1233
- diagnostics.push({ ...location(source, offset), message, severity, ...code ? { code } : {} });
1280
+ diagnostics.push({
1281
+ ...location(source, offset),
1282
+ message,
1283
+ severity,
1284
+ source: "ssml-static-validator",
1285
+ ...code ? { code } : {}
1286
+ });
1287
+ }
1288
+ function isSupportedProsodyRate(value) {
1289
+ const trimmed = value.trim();
1290
+ if (/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(trimmed)) return true;
1291
+ const multiplier = /^(\d+(?:\.\d+)?)(x)?$/i.exec(trimmed);
1292
+ if (!multiplier) return false;
1293
+ const numericValue = Number(multiplier[1]);
1294
+ return numericValue >= 0.5 && numericValue <= 2;
1295
+ }
1296
+ function isValidAzureAudioDuration(value) {
1297
+ const trimmed = value.trim();
1298
+ const numeric = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(trimmed);
1299
+ if (numeric) return Number(numeric[1]) > 0;
1300
+ const clock = /^(\d{2,}):([0-5]\d):([0-5]\d)(?:\.(\d{1,3}))?$/.exec(trimmed);
1301
+ if (!clock) return false;
1302
+ return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
1234
1303
  }
1235
1304
  function attr(token, name) {
1236
1305
  return token.attributes.get(name.toLowerCase());
1237
1306
  }
1238
- var ADDITIONAL_VOICE_DEFINITIONS = [
1239
- { name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" },
1240
- { name: "es-ES-ElviraNeural", locale: "es-ES" },
1241
- { name: "th-TH-PremwadeeNeural", locale: "th-TH" },
1242
- { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
1243
- { name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
1244
- { name: "id-ID-GadisNeural", locale: "id-ID" },
1245
- { name: "ms-MY-YasminNeural", locale: "ms-MY" }
1246
- ];
1247
1307
  var DEFAULT_LANGUAGE_ALIASES = {
1248
1308
  "zh-CN": ["zh-Hans"],
1249
1309
  "zh-TW": ["zh-Hant"]
@@ -1306,10 +1366,7 @@ function definitionFromStyleMap(voiceName, styles) {
1306
1366
  }
1307
1367
  function normalizeVoiceCatalog(options) {
1308
1368
  const definitions = /* @__PURE__ */ new Map();
1309
- for (const [name, styles] of Object.entries(EXPRESS_AS_STYLES)) {
1310
- definitions.set(name.toLowerCase(), definitionFromStyleMap(name, styles));
1311
- }
1312
- for (const definition of ADDITIONAL_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
1369
+ for (const definition of AZURE_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
1313
1370
  for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
1314
1371
  for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
1315
1372
  for (const definition of options.customVoiceDefinitions ?? [])
@@ -1365,7 +1422,7 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
1365
1422
  const rate = attr(token, "rate");
1366
1423
  const pitch = attr(token, "pitch");
1367
1424
  const volume = attr(token, "volume");
1368
- if (rate && !/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(rate.trim()))
1425
+ if (rate && !isSupportedProsodyRate(rate))
1369
1426
  addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody rate> value "${rate}".`);
1370
1427
  if (pitch && !/^(x-low|low|medium|high|x-high|[+-]?\d+(?:\.\d+)?(?:st|Hz|%)?)$/.test(pitch.trim()))
1371
1428
  addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody pitch> value "${pitch}".`);
@@ -1446,6 +1503,18 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
1446
1503
  if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
1447
1504
  addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
1448
1505
  }
1506
+ if (name === "mstts:audioduration") {
1507
+ const value = attr(token, "value");
1508
+ if (!value || !isValidAzureAudioDuration(value))
1509
+ addDiagnostic(
1510
+ diagnostics,
1511
+ source,
1512
+ token.start,
1513
+ '<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
1514
+ );
1515
+ if (!token.selfClosing)
1516
+ addDiagnostic(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
1517
+ }
1449
1518
  if (name === "mstts:viseme") {
1450
1519
  const type = attr(token, "type");
1451
1520
  if (!type || !ALLOWED_VISEME_TYPES.has(type))
@@ -1480,7 +1549,15 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
1480
1549
  function validateAzureSsml(ssml, options = {}) {
1481
1550
  const diagnostics = [];
1482
1551
  if (typeof ssml !== "string") {
1483
- return [{ line: 1, column: 1, message: "SSML input must be a string", severity: "error" }];
1552
+ return [
1553
+ {
1554
+ line: 1,
1555
+ column: 1,
1556
+ message: "SSML input must be a string",
1557
+ severity: "error",
1558
+ source: "ssml-static-validator"
1559
+ }
1560
+ ];
1484
1561
  }
1485
1562
  const maxLength = options.maxLength ?? 1e4;
1486
1563
  if (ssml.length > maxLength)
@@ -1543,6 +1620,7 @@ function validateAzureSsml(ssml, options = {}) {
1543
1620
  buildPartialSsml,
1544
1621
  buildSsml,
1545
1622
  extractSsmlText,
1623
+ isValidAzureAudioDuration,
1546
1624
  mapSsmlTextNodes,
1547
1625
  normalizeAzureLanguage,
1548
1626
  parseSsml,