ssml-builder-js 2.12.0 → 2.13.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 +26 -1
- package/dist/{chunk-4BVNAUVR.mjs → chunk-25LOR4AJ.mjs} +134 -10
- package/dist/chunk-25LOR4AJ.mjs.map +1 -0
- package/dist/chunk-CZ2F3TET.mjs +1745 -0
- package/dist/chunk-CZ2F3TET.mjs.map +1 -0
- package/dist/{chunk-FHDFRM2F.mjs → chunk-LCTE26LS.mjs} +248 -1722
- package/dist/chunk-LCTE26LS.mjs.map +1 -0
- package/dist/core.d.mts +45 -3
- package/dist/core.d.ts +45 -3
- package/dist/core.js +133 -9
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/elements.js +118 -1
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +6 -4
- package/dist/elements.mjs.map +1 -1
- package/dist/index.d-Xbr6ZWnK.d.mts +214 -0
- package/dist/index.d-Xbr6ZWnK.d.ts +214 -0
- package/dist/index.d.mts +95 -2
- package/dist/index.d.ts +95 -2
- package/dist/index.js +1732 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +135 -4
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +61 -163
- package/dist/react.d.ts +61 -163
- package/dist/react.js +397 -15
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +139 -18
- package/dist/react.mjs.map +1 -1
- package/package.json +3 -2
- package/dist/chunk-4BVNAUVR.mjs.map +0 -1
- package/dist/chunk-FHDFRM2F.mjs.map +0 -1
package/dist/core.d.ts
CHANGED
|
@@ -188,13 +188,35 @@ interface BuildPartialSsmlOptions extends SsmlPartialContext {
|
|
|
188
188
|
declare function buildPartialSsml(text: string, context?: SsmlPartialContext): string;
|
|
189
189
|
declare function buildPartialSsml(options: BuildPartialSsmlOptions): string;
|
|
190
190
|
|
|
191
|
+
interface SsmlTextRange {
|
|
192
|
+
start: number;
|
|
193
|
+
end: number;
|
|
194
|
+
}
|
|
195
|
+
interface SsmlChunkContext {
|
|
196
|
+
voice?: string;
|
|
197
|
+
lang?: string;
|
|
198
|
+
prosody?: Record<string, string>;
|
|
199
|
+
}
|
|
200
|
+
interface SsmlChunk {
|
|
201
|
+
chunkIndex: number;
|
|
202
|
+
ssml: string;
|
|
203
|
+
originalTextRange: SsmlTextRange;
|
|
204
|
+
inheritedContext: SsmlChunkContext;
|
|
205
|
+
containedMarks: string[];
|
|
206
|
+
hasBackgroundAudio: boolean;
|
|
207
|
+
}
|
|
208
|
+
interface SplitSsmlOptions {
|
|
209
|
+
maxLength?: number;
|
|
210
|
+
/** Keeps `<mstts:backgroundaudio>` in every chunk instead of the first chunk only. */
|
|
211
|
+
replicateBackgroundAudio?: boolean;
|
|
212
|
+
}
|
|
191
213
|
/**
|
|
192
214
|
* Splits SSML into independently synthesizable documents without breaking XML
|
|
193
215
|
* elements. Parent elements such as `<voice>` and `<prosody>` are copied into
|
|
194
216
|
* every block. Paragraph and sentence elements therefore remain the natural
|
|
195
217
|
* split points, while oversized text is split at word/character boundaries.
|
|
196
218
|
*/
|
|
197
|
-
declare function splitSsmlDocument(ssml: string, maxLength?: number):
|
|
219
|
+
declare function splitSsmlDocument(ssml: string, maxLength?: number | SplitSsmlOptions, options?: SplitSsmlOptions): SsmlChunk[];
|
|
198
220
|
|
|
199
221
|
declare function parseSsml(xmlString: string): SsmlDocument;
|
|
200
222
|
|
|
@@ -292,6 +314,10 @@ interface AzureValidationOptions {
|
|
|
292
314
|
allowHttpAudio?: boolean;
|
|
293
315
|
customVoiceStyleMap?: Record<string, readonly string[]>;
|
|
294
316
|
customVoiceDefinitions?: readonly AzureVoiceDefinition[];
|
|
317
|
+
/** Host-side validation hook for URL-bearing SSML attributes. */
|
|
318
|
+
urlValidator?: AzureUrlValidator;
|
|
319
|
+
/** Alias for urlValidator retained for applications that use the longer name. */
|
|
320
|
+
customUrlValidator?: AzureUrlValidator;
|
|
295
321
|
languageAliases?: Record<string, string | readonly string[]>;
|
|
296
322
|
maxLength?: number;
|
|
297
323
|
/** Maximum XML element nesting depth, counting `<speak>` as depth 1. */
|
|
@@ -311,6 +337,14 @@ interface AzureValidationOptions {
|
|
|
311
337
|
voiceCatalog?: readonly AzureVoiceDefinition[];
|
|
312
338
|
voiceDefinitions?: readonly AzureVoiceDefinition[];
|
|
313
339
|
}
|
|
340
|
+
type AzureUrlValidationResult = boolean | {
|
|
341
|
+
valid: boolean;
|
|
342
|
+
reason?: string;
|
|
343
|
+
};
|
|
344
|
+
type AzureUrlValidator = (url: string, context: {
|
|
345
|
+
tag: string;
|
|
346
|
+
attribute: string;
|
|
347
|
+
}) => AzureUrlValidationResult | Promise<AzureUrlValidationResult>;
|
|
314
348
|
type AzureLanguageNormalizationOptions = Pick<AzureValidationOptions, "languageAliases" | "normalizeLanguage">;
|
|
315
349
|
/** @deprecated Use AzureValidationOptions instead. */
|
|
316
350
|
type AzureSsmlValidationOptions = AzureValidationOptions;
|
|
@@ -320,7 +354,15 @@ declare function isValidAzureAudioDuration(value: string): boolean;
|
|
|
320
354
|
declare function normalizeAzureLanguage(language: string, options?: AzureLanguageNormalizationOptions): string;
|
|
321
355
|
/** Compares two Azure language tags after BCP 47 and alias normalization. */
|
|
322
356
|
declare function areAzureLanguagesEquivalent(first: string, second: string, options?: AzureLanguageNormalizationOptions): boolean;
|
|
323
|
-
|
|
357
|
+
/**
|
|
358
|
+
* Validates Azure SSML synchronously unless a URL validator is supplied. URL
|
|
359
|
+
* validation is asynchronous-capable so hosts can perform DNS/private-network checks.
|
|
360
|
+
*/
|
|
361
|
+
declare function validateAzureSsml(ssml: string, options?: Omit<AzureValidationOptions, "urlValidator" | "customUrlValidator">): SsmlDiagnostic[];
|
|
362
|
+
declare function validateAzureSsml(ssml: string, options: AzureValidationOptions & {
|
|
363
|
+
urlValidator?: AzureUrlValidator;
|
|
364
|
+
customUrlValidator?: AzureUrlValidator;
|
|
365
|
+
}): SsmlDiagnostic[] | Promise<SsmlDiagnostic[]>;
|
|
324
366
|
|
|
325
367
|
interface AzureVoiceCatalogMetadata {
|
|
326
368
|
apiVersion: string;
|
|
@@ -334,4 +376,4 @@ declare function getAzureVoiceCatalogMetadata(): AzureVoiceCatalogMetadata;
|
|
|
334
376
|
/** Alias for consumers that refer to the bundled catalog as the built-in catalog. */
|
|
335
377
|
declare const getBuiltInVoiceCatalogMetadata: typeof getAzureVoiceCatalogMetadata;
|
|
336
378
|
|
|
337
|
-
export { type AudioElement, type AzureDiagnosticCode, type AzureLanguageNormalizationOptions, type AzureSsmlValidationOptions, type AzureValidationOptions, type AzureVoiceCatalogMetadata, type AzureVoiceDefinition, type AzureVoiceMetadata, type BookmarkElement, type BreakElement, type BuildPartialSsmlOptions, type CustomElement, type EmphasisElement, type ExpressAsElement, type ExtractSsmlTranslatableTextOptions, type FromPlainTextToSsmlOptions, type LangElement, type LexiconElement, type MapSsmlTextNodesOptions, type MarkElement, type MsttsAudioDurationElement, type MsttsEmbeddingElement, type MsttsSilenceElement, type MsttsTtsEmbeddingElement, type MsttsVisemeElement, type MsttsVoiceConversionElement, type NamedElement, type ParagraphElement, type PhonemeElement, type ProsodyElement, type SayAsElement, type SentenceElement, type SsmlAttributeValue, type SsmlAttributes, type SsmlBackgroundAudioNode, type SsmlBreakElement, type SsmlDiagnostic, type SsmlDiagnosticSeverity, type SsmlDiagnosticSource, type SsmlDialogNode, type SsmlDocument, type SsmlElement, type SsmlElementBase, type SsmlEmbeddingNode, type SsmlExpressAsElement, type SsmlNode, type SsmlPartialContext, type SsmlPartialProsody, type SsmlPartialVoice, type SsmlPhonemeElement, type SsmlProsodyElement, type SsmlSayAsElement, type SsmlStructureIntegrityResult, type SsmlStructureMismatch, type SsmlText, type SsmlTextNodeContext, type SsmlTtsEmbeddingNode, type SsmlTurnNode, type SsmlValidationError, type SsmlVoiceConversionNode, type SsmlVoiceElement, type SubElement, type VoiceElement, type WordElement, areAzureLanguagesEquivalent, buildPartialSsml, buildSsml, extractSsmlText, extractSsmlTranslatableText, fromPlainTextToSsml, getAzureVoiceCatalogMetadata, getBuiltInVoiceCatalogMetadata, isValidAzureAudioDuration, mapSsmlTextNodes, normalizeAzureLanguage, parseSsml, splitSsmlDocument, validateAzureSsml, validateSsml, validateSsmlStructureIntegrity };
|
|
379
|
+
export { type AudioElement, type AzureDiagnosticCode, type AzureLanguageNormalizationOptions, type AzureSsmlValidationOptions, type AzureUrlValidationResult, type AzureUrlValidator, type AzureValidationOptions, type AzureVoiceCatalogMetadata, type AzureVoiceDefinition, type AzureVoiceMetadata, type BookmarkElement, type BreakElement, type BuildPartialSsmlOptions, type CustomElement, type EmphasisElement, type ExpressAsElement, type ExtractSsmlTranslatableTextOptions, type FromPlainTextToSsmlOptions, type LangElement, type LexiconElement, type MapSsmlTextNodesOptions, type MarkElement, type MsttsAudioDurationElement, type MsttsEmbeddingElement, type MsttsSilenceElement, type MsttsTtsEmbeddingElement, type MsttsVisemeElement, type MsttsVoiceConversionElement, type NamedElement, type ParagraphElement, type PhonemeElement, type ProsodyElement, type SayAsElement, type SentenceElement, type SplitSsmlOptions, type SsmlAttributeValue, type SsmlAttributes, type SsmlBackgroundAudioNode, type SsmlBreakElement, type SsmlChunk, type SsmlChunkContext, type SsmlDiagnostic, type SsmlDiagnosticSeverity, type SsmlDiagnosticSource, type SsmlDialogNode, type SsmlDocument, type SsmlElement, type SsmlElementBase, type SsmlEmbeddingNode, type SsmlExpressAsElement, type SsmlNode, type SsmlPartialContext, type SsmlPartialProsody, type SsmlPartialVoice, type SsmlPhonemeElement, type SsmlProsodyElement, type SsmlSayAsElement, type SsmlStructureIntegrityResult, type SsmlStructureMismatch, type SsmlText, type SsmlTextNodeContext, type SsmlTextRange, type SsmlTtsEmbeddingNode, type SsmlTurnNode, type SsmlValidationError, type SsmlVoiceConversionNode, type SsmlVoiceElement, type SubElement, type VoiceElement, type WordElement, areAzureLanguagesEquivalent, buildPartialSsml, buildSsml, extractSsmlText, extractSsmlTranslatableText, fromPlainTextToSsml, getAzureVoiceCatalogMetadata, getBuiltInVoiceCatalogMetadata, isValidAzureAudioDuration, mapSsmlTextNodes, normalizeAzureLanguage, parseSsml, splitSsmlDocument, validateAzureSsml, validateSsml, validateSsmlStructureIntegrity };
|
package/dist/core.js
CHANGED
|
@@ -1045,30 +1045,109 @@ function splitNode(document, node, maxLength, context = []) {
|
|
|
1045
1045
|
flush();
|
|
1046
1046
|
return parts;
|
|
1047
1047
|
}
|
|
1048
|
-
function
|
|
1049
|
-
if (
|
|
1048
|
+
function textFromNode(node) {
|
|
1049
|
+
if (typeof node === "string") return node;
|
|
1050
|
+
if (node.type === "text") return node.value;
|
|
1051
|
+
return (node.children ?? []).map(textFromNode).join("");
|
|
1052
|
+
}
|
|
1053
|
+
function collectMarks(node, marks) {
|
|
1054
|
+
if (typeof node === "string" || node.type === "text") return;
|
|
1055
|
+
if (node.type === "mark" && node.name) marks.push(node.name);
|
|
1056
|
+
if (node.type === "bookmark" && node.mark) marks.push(node.mark);
|
|
1057
|
+
for (const child of node.children ?? []) collectMarks(child, marks);
|
|
1058
|
+
}
|
|
1059
|
+
function collectInheritedContext(nodes) {
|
|
1060
|
+
const context = {};
|
|
1061
|
+
const visit = (node) => {
|
|
1062
|
+
if (typeof node === "string" || node.type === "text") return;
|
|
1063
|
+
if (context.voice === void 0 && node.type === "voice" && node.name) context.voice = node.name;
|
|
1064
|
+
if (context.lang === void 0 && node.type === "lang" && node.lang) context.lang = node.lang;
|
|
1065
|
+
if (context.prosody === void 0 && node.type === "prosody") {
|
|
1066
|
+
const prosody = {};
|
|
1067
|
+
for (const [key, value] of Object.entries(node.attributes ?? {})) prosody[key] = String(value);
|
|
1068
|
+
for (const key of ["rate", "pitch", "volume", "contour", "range"]) {
|
|
1069
|
+
const value = node[key];
|
|
1070
|
+
if (value !== void 0) prosody[key] = String(value);
|
|
1071
|
+
}
|
|
1072
|
+
if (Object.keys(prosody).length > 0) context.prosody = prosody;
|
|
1073
|
+
}
|
|
1074
|
+
for (const child of node.children ?? []) visit(child);
|
|
1075
|
+
};
|
|
1076
|
+
nodes.forEach(visit);
|
|
1077
|
+
return context;
|
|
1078
|
+
}
|
|
1079
|
+
function createChunk(document, nodes, chunkIndex, textStart, backgroundAudio, replicateBackgroundAudio) {
|
|
1080
|
+
const chunkNodes = backgroundAudio && (replicateBackgroundAudio || chunkIndex === 0) ? [backgroundAudio, ...nodes] : nodes;
|
|
1081
|
+
const text = nodes.map(textFromNode).join("");
|
|
1082
|
+
const marks = [];
|
|
1083
|
+
for (const node of nodes) collectMarks(node, marks);
|
|
1084
|
+
const inheritedContext = collectInheritedContext(nodes);
|
|
1085
|
+
if (inheritedContext.lang === void 0 && document.lang) inheritedContext.lang = document.lang;
|
|
1086
|
+
return {
|
|
1087
|
+
chunkIndex,
|
|
1088
|
+
ssml: documentWithChildren(document, chunkNodes),
|
|
1089
|
+
originalTextRange: { start: textStart, end: textStart + text.length },
|
|
1090
|
+
inheritedContext,
|
|
1091
|
+
containedMarks: marks,
|
|
1092
|
+
hasBackgroundAudio: chunkNodes.some(
|
|
1093
|
+
(node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
|
|
1094
|
+
)
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH, options = {}) {
|
|
1098
|
+
const resolvedMaxLength = typeof maxLength === "number" ? maxLength : maxLength.maxLength ?? DEFAULT_MAX_LENGTH;
|
|
1099
|
+
const resolvedOptions = typeof maxLength === "number" ? options : maxLength;
|
|
1100
|
+
if (!Number.isInteger(resolvedMaxLength) || resolvedMaxLength <= 0) {
|
|
1050
1101
|
throw new RangeError("maxLength must be a positive integer");
|
|
1051
1102
|
}
|
|
1052
1103
|
const document = parseSsml(ssml);
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1104
|
+
const backgroundAudio = (document.children ?? []).find(
|
|
1105
|
+
(node) => typeof node !== "string" && node.type !== "text" && node.type === "mstts:backgroundaudio"
|
|
1106
|
+
);
|
|
1107
|
+
if (ssml.length <= resolvedMaxLength) {
|
|
1108
|
+
return [createChunk(document, document.children ?? [], 0, 0, backgroundAudio, true)];
|
|
1109
|
+
}
|
|
1110
|
+
const contentChildren = (document.children ?? []).filter((node) => node !== backgroundAudio);
|
|
1111
|
+
const plainDocumentLength = documentWithChildren(document, []).length;
|
|
1112
|
+
const backgroundDocumentLength = backgroundAudio ? documentWithChildren(document, [backgroundAudio]).length : plainDocumentLength;
|
|
1113
|
+
const backgroundOverhead = Math.max(0, backgroundDocumentLength - plainDocumentLength);
|
|
1114
|
+
const contentMaxLength = Math.max(1, resolvedMaxLength - backgroundOverhead);
|
|
1115
|
+
const splitChildren = contentChildren.flatMap((child) => splitNode(document, child, contentMaxLength));
|
|
1056
1116
|
const chunks = [];
|
|
1057
1117
|
let group = [];
|
|
1058
1118
|
for (const child of splitChildren) {
|
|
1059
1119
|
const candidate = [...group, child];
|
|
1060
|
-
if (documentWithChildren(document, candidate).length <=
|
|
1120
|
+
if (documentWithChildren(document, candidate).length <= contentMaxLength) {
|
|
1061
1121
|
group = candidate;
|
|
1062
1122
|
continue;
|
|
1063
1123
|
}
|
|
1064
1124
|
if (group.length > 0) chunks.push(group);
|
|
1065
1125
|
group = [child];
|
|
1066
|
-
if (documentWithChildren(document, group).length >
|
|
1126
|
+
if (documentWithChildren(document, group).length > contentMaxLength) {
|
|
1067
1127
|
throw new RangeError("maxLength is too small to contain the SSML document wrapper");
|
|
1068
1128
|
}
|
|
1069
1129
|
}
|
|
1070
1130
|
if (group.length > 0) chunks.push(group);
|
|
1071
|
-
|
|
1131
|
+
if (chunks.length === 0) {
|
|
1132
|
+
const result = createChunk(document, [], 0, 0, backgroundAudio, resolvedOptions.replicateBackgroundAudio ?? false);
|
|
1133
|
+
if (result.ssml.length > resolvedMaxLength) {
|
|
1134
|
+
throw new RangeError("maxLength is too small to contain the SSML document wrapper");
|
|
1135
|
+
}
|
|
1136
|
+
return [result];
|
|
1137
|
+
}
|
|
1138
|
+
let textStart = 0;
|
|
1139
|
+
return chunks.map((chunk, chunkIndex) => {
|
|
1140
|
+
const result = createChunk(
|
|
1141
|
+
document,
|
|
1142
|
+
chunk,
|
|
1143
|
+
chunkIndex,
|
|
1144
|
+
textStart,
|
|
1145
|
+
backgroundAudio,
|
|
1146
|
+
resolvedOptions.replicateBackgroundAudio ?? false
|
|
1147
|
+
);
|
|
1148
|
+
textStart = result.originalTextRange.end;
|
|
1149
|
+
return result;
|
|
1150
|
+
});
|
|
1072
1151
|
}
|
|
1073
1152
|
|
|
1074
1153
|
// packages/ssml-core/src/validation.ts
|
|
@@ -2159,7 +2238,7 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
|
|
|
2159
2238
|
addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
|
|
2160
2239
|
}
|
|
2161
2240
|
}
|
|
2162
|
-
function
|
|
2241
|
+
function validateAzureSsmlStatic(ssml, options = {}) {
|
|
2163
2242
|
const diagnostics = [];
|
|
2164
2243
|
if (typeof ssml !== "string") {
|
|
2165
2244
|
return [
|
|
@@ -2283,6 +2362,51 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
2283
2362
|
}
|
|
2284
2363
|
return diagnostics;
|
|
2285
2364
|
}
|
|
2365
|
+
function urlAttributes(token) {
|
|
2366
|
+
const tag = canonicalTagName(token.name);
|
|
2367
|
+
const attributes = tag === "audio" || tag === "mstts:backgroundaudio" ? ["src"] : tag === "lexicon" ? ["uri"] : tag === "mstts:voiceconversion" ? ["url"] : [];
|
|
2368
|
+
return attributes.flatMap((attribute) => {
|
|
2369
|
+
const value = attr(token, attribute);
|
|
2370
|
+
return value === void 0 ? [] : [{ attribute, value }];
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
function validateAzureSsml(ssml, options = {}) {
|
|
2374
|
+
const diagnostics = validateAzureSsmlStatic(ssml, options);
|
|
2375
|
+
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
2376
|
+
if (!validator || typeof ssml !== "string") return diagnostics;
|
|
2377
|
+
let tokens;
|
|
2378
|
+
try {
|
|
2379
|
+
tokens = tokenizeElements(ssml);
|
|
2380
|
+
} catch {
|
|
2381
|
+
return diagnostics;
|
|
2382
|
+
}
|
|
2383
|
+
const checks = tokens.flatMap(
|
|
2384
|
+
(token) => urlAttributes(token).map(async ({ attribute, value }) => {
|
|
2385
|
+
try {
|
|
2386
|
+
const result = await validator(value, { tag: token.name, attribute });
|
|
2387
|
+
const valid = typeof result === "boolean" ? result : result.valid;
|
|
2388
|
+
if (!valid) {
|
|
2389
|
+
const reason = typeof result === "boolean" ? void 0 : result.reason;
|
|
2390
|
+
addDiagnostic(
|
|
2391
|
+
diagnostics,
|
|
2392
|
+
ssml,
|
|
2393
|
+
token.start,
|
|
2394
|
+
`<${token.name} ${attribute}> was rejected by the custom URL validator${reason ? `: ${reason}` : "."}`
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
} catch (error) {
|
|
2398
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2399
|
+
addDiagnostic(
|
|
2400
|
+
diagnostics,
|
|
2401
|
+
ssml,
|
|
2402
|
+
token.start,
|
|
2403
|
+
`<${token.name} ${attribute}> could not be validated by the custom URL validator: ${reason}`
|
|
2404
|
+
);
|
|
2405
|
+
}
|
|
2406
|
+
})
|
|
2407
|
+
);
|
|
2408
|
+
return Promise.all(checks).then(() => diagnostics);
|
|
2409
|
+
}
|
|
2286
2410
|
|
|
2287
2411
|
// packages/ssml-core/src/generated/azureVoiceCatalog.ts
|
|
2288
2412
|
var AZURE_VOICE_CATALOG_METADATA = {
|