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/README.md +100 -5
- package/bin/sync-voices.js +62 -0
- package/dist/{chunk-GW6CKHXF.mjs → chunk-4BVNAUVR.mjs} +177 -5
- package/dist/chunk-4BVNAUVR.mjs.map +1 -0
- package/dist/{chunk-BYIZQL2W.mjs → chunk-FHDFRM2F.mjs} +92 -14
- package/dist/chunk-FHDFRM2F.mjs.map +1 -0
- package/dist/core.d.mts +21 -3
- package/dist/core.d.ts +21 -3
- package/dist/core.js +177 -4
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +3 -1
- package/dist/elements.js +91 -13
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +1 -1
- package/dist/index.d.mts +55 -2
- package/dist/index.d.ts +55 -2
- package/dist/index.js +300 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +124 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.js +335 -15
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +245 -3
- package/dist/react.mjs.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-BYIZQL2W.mjs.map +0 -1
- package/dist/chunk-GW6CKHXF.mjs.map +0 -1
package/dist/core.d.mts
CHANGED
|
@@ -188,6 +188,14 @@ 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
|
+
/**
|
|
192
|
+
* Splits SSML into independently synthesizable documents without breaking XML
|
|
193
|
+
* elements. Parent elements such as `<voice>` and `<prosody>` are copied into
|
|
194
|
+
* every block. Paragraph and sentence elements therefore remain the natural
|
|
195
|
+
* split points, while oversized text is split at word/character boundaries.
|
|
196
|
+
*/
|
|
197
|
+
declare function splitSsmlDocument(ssml: string, maxLength?: number): string[];
|
|
198
|
+
|
|
191
199
|
declare function parseSsml(xmlString: string): SsmlDocument;
|
|
192
200
|
|
|
193
201
|
interface SsmlValidationError {
|
|
@@ -253,9 +261,9 @@ declare function extractSsmlTranslatableText(ssml: string, options?: ExtractSsml
|
|
|
253
261
|
declare function fromPlainTextToSsml(text: string, options?: FromPlainTextToSsmlOptions): string;
|
|
254
262
|
declare function validateSsmlStructureIntegrity(originalSsml: string, translatedSsml: string): SsmlStructureIntegrityResult;
|
|
255
263
|
|
|
256
|
-
type SsmlDiagnosticSeverity = "error" | "warning";
|
|
264
|
+
type SsmlDiagnosticSeverity = "error" | "warning" | "info";
|
|
257
265
|
type SsmlDiagnosticSource = "ssml-static-validator";
|
|
258
|
-
type AzureDiagnosticCode = "azure-unknown-voice" | "azure-unsupported-style" | "azure-locale-mismatch" | "azure-unsupported-tag-for-voice" | "azure-unsupported-model-for-voice";
|
|
266
|
+
type AzureDiagnosticCode = "azure-unknown-voice" | "azure-unsupported-style" | "azure-locale-mismatch" | "azure-unsupported-tag-for-voice" | "azure-unsupported-model-for-voice" | "azure-preview-voice" | "azure-deprecated-voice" | "azure-preview-tag" | "azure-deprecated-tag";
|
|
259
267
|
interface SsmlDiagnostic {
|
|
260
268
|
code?: AzureDiagnosticCode;
|
|
261
269
|
line: number;
|
|
@@ -272,6 +280,8 @@ interface AzureVoiceDefinition {
|
|
|
272
280
|
supportedTags?: readonly string[];
|
|
273
281
|
unsupportedTags?: readonly string[];
|
|
274
282
|
models?: readonly string[];
|
|
283
|
+
regions?: readonly string[];
|
|
284
|
+
status?: "ga" | "preview" | "deprecated";
|
|
275
285
|
}
|
|
276
286
|
/** Alias retained for consumers that prefer the metadata terminology. */
|
|
277
287
|
interface AzureVoiceMetadata extends AzureVoiceDefinition {
|
|
@@ -284,9 +294,17 @@ interface AzureValidationOptions {
|
|
|
284
294
|
customVoiceDefinitions?: readonly AzureVoiceDefinition[];
|
|
285
295
|
languageAliases?: Record<string, string | readonly string[]>;
|
|
286
296
|
maxLength?: number;
|
|
297
|
+
/** Maximum XML element nesting depth, counting `<speak>` as depth 1. */
|
|
298
|
+
maxXmlDepth?: number;
|
|
287
299
|
/** Optional model identifier used to validate a voice's model compatibility. */
|
|
288
300
|
model?: string;
|
|
289
301
|
normalizeLanguage?: (lang: string) => string;
|
|
302
|
+
/** Overrides the built-in preview tag list for this validation run. */
|
|
303
|
+
previewTags?: readonly string[];
|
|
304
|
+
/** Adds tags that should be reported as deprecated for this validation run. */
|
|
305
|
+
deprecatedTags?: readonly string[];
|
|
306
|
+
/** Explicit tag lifecycle metadata. This takes precedence over previewTags/deprecatedTags. */
|
|
307
|
+
tagStatuses?: Readonly<Record<string, "ga" | "preview" | "deprecated">>;
|
|
290
308
|
unknownVoicePolicy?: "error" | "warn" | "ignore";
|
|
291
309
|
unsupportedStylePolicy?: "error" | "warn" | "ignore";
|
|
292
310
|
validateNestedVoices?: boolean;
|
|
@@ -316,4 +334,4 @@ declare function getAzureVoiceCatalogMetadata(): AzureVoiceCatalogMetadata;
|
|
|
316
334
|
/** Alias for consumers that refer to the bundled catalog as the built-in catalog. */
|
|
317
335
|
declare const getBuiltInVoiceCatalogMetadata: typeof getAzureVoiceCatalogMetadata;
|
|
318
336
|
|
|
319
|
-
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, validateAzureSsml, validateSsml, validateSsmlStructureIntegrity };
|
|
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 };
|
package/dist/core.d.ts
CHANGED
|
@@ -188,6 +188,14 @@ 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
|
+
/**
|
|
192
|
+
* Splits SSML into independently synthesizable documents without breaking XML
|
|
193
|
+
* elements. Parent elements such as `<voice>` and `<prosody>` are copied into
|
|
194
|
+
* every block. Paragraph and sentence elements therefore remain the natural
|
|
195
|
+
* split points, while oversized text is split at word/character boundaries.
|
|
196
|
+
*/
|
|
197
|
+
declare function splitSsmlDocument(ssml: string, maxLength?: number): string[];
|
|
198
|
+
|
|
191
199
|
declare function parseSsml(xmlString: string): SsmlDocument;
|
|
192
200
|
|
|
193
201
|
interface SsmlValidationError {
|
|
@@ -253,9 +261,9 @@ declare function extractSsmlTranslatableText(ssml: string, options?: ExtractSsml
|
|
|
253
261
|
declare function fromPlainTextToSsml(text: string, options?: FromPlainTextToSsmlOptions): string;
|
|
254
262
|
declare function validateSsmlStructureIntegrity(originalSsml: string, translatedSsml: string): SsmlStructureIntegrityResult;
|
|
255
263
|
|
|
256
|
-
type SsmlDiagnosticSeverity = "error" | "warning";
|
|
264
|
+
type SsmlDiagnosticSeverity = "error" | "warning" | "info";
|
|
257
265
|
type SsmlDiagnosticSource = "ssml-static-validator";
|
|
258
|
-
type AzureDiagnosticCode = "azure-unknown-voice" | "azure-unsupported-style" | "azure-locale-mismatch" | "azure-unsupported-tag-for-voice" | "azure-unsupported-model-for-voice";
|
|
266
|
+
type AzureDiagnosticCode = "azure-unknown-voice" | "azure-unsupported-style" | "azure-locale-mismatch" | "azure-unsupported-tag-for-voice" | "azure-unsupported-model-for-voice" | "azure-preview-voice" | "azure-deprecated-voice" | "azure-preview-tag" | "azure-deprecated-tag";
|
|
259
267
|
interface SsmlDiagnostic {
|
|
260
268
|
code?: AzureDiagnosticCode;
|
|
261
269
|
line: number;
|
|
@@ -272,6 +280,8 @@ interface AzureVoiceDefinition {
|
|
|
272
280
|
supportedTags?: readonly string[];
|
|
273
281
|
unsupportedTags?: readonly string[];
|
|
274
282
|
models?: readonly string[];
|
|
283
|
+
regions?: readonly string[];
|
|
284
|
+
status?: "ga" | "preview" | "deprecated";
|
|
275
285
|
}
|
|
276
286
|
/** Alias retained for consumers that prefer the metadata terminology. */
|
|
277
287
|
interface AzureVoiceMetadata extends AzureVoiceDefinition {
|
|
@@ -284,9 +294,17 @@ interface AzureValidationOptions {
|
|
|
284
294
|
customVoiceDefinitions?: readonly AzureVoiceDefinition[];
|
|
285
295
|
languageAliases?: Record<string, string | readonly string[]>;
|
|
286
296
|
maxLength?: number;
|
|
297
|
+
/** Maximum XML element nesting depth, counting `<speak>` as depth 1. */
|
|
298
|
+
maxXmlDepth?: number;
|
|
287
299
|
/** Optional model identifier used to validate a voice's model compatibility. */
|
|
288
300
|
model?: string;
|
|
289
301
|
normalizeLanguage?: (lang: string) => string;
|
|
302
|
+
/** Overrides the built-in preview tag list for this validation run. */
|
|
303
|
+
previewTags?: readonly string[];
|
|
304
|
+
/** Adds tags that should be reported as deprecated for this validation run. */
|
|
305
|
+
deprecatedTags?: readonly string[];
|
|
306
|
+
/** Explicit tag lifecycle metadata. This takes precedence over previewTags/deprecatedTags. */
|
|
307
|
+
tagStatuses?: Readonly<Record<string, "ga" | "preview" | "deprecated">>;
|
|
290
308
|
unknownVoicePolicy?: "error" | "warn" | "ignore";
|
|
291
309
|
unsupportedStylePolicy?: "error" | "warn" | "ignore";
|
|
292
310
|
validateNestedVoices?: boolean;
|
|
@@ -316,4 +334,4 @@ declare function getAzureVoiceCatalogMetadata(): AzureVoiceCatalogMetadata;
|
|
|
316
334
|
/** Alias for consumers that refer to the bundled catalog as the built-in catalog. */
|
|
317
335
|
declare const getBuiltInVoiceCatalogMetadata: typeof getAzureVoiceCatalogMetadata;
|
|
318
336
|
|
|
319
|
-
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, validateAzureSsml, validateSsml, validateSsmlStructureIntegrity };
|
|
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 };
|
package/dist/core.js
CHANGED
|
@@ -39,6 +39,7 @@ __export(core_exports, {
|
|
|
39
39
|
mapSsmlTextNodes: () => mapSsmlTextNodes,
|
|
40
40
|
normalizeAzureLanguage: () => normalizeAzureLanguage,
|
|
41
41
|
parseSsml: () => parseSsml,
|
|
42
|
+
splitSsmlDocument: () => splitSsmlDocument,
|
|
42
43
|
validateAzureSsml: () => validateAzureSsml,
|
|
43
44
|
validateSsml: () => validateSsml,
|
|
44
45
|
validateSsmlStructureIntegrity: () => validateSsmlStructureIntegrity
|
|
@@ -972,6 +973,104 @@ function buildPartialSsml(textOrOptions, context) {
|
|
|
972
973
|
return serializePartialSsml(textOrOptions.text, textOrOptions);
|
|
973
974
|
}
|
|
974
975
|
|
|
976
|
+
// packages/ssml-core/src/split.ts
|
|
977
|
+
var DEFAULT_MAX_LENGTH = 1e4;
|
|
978
|
+
function cloneElement(element, children) {
|
|
979
|
+
return { ...element, children };
|
|
980
|
+
}
|
|
981
|
+
function documentWithChildren(document, children) {
|
|
982
|
+
return buildSsml({ ...document, children });
|
|
983
|
+
}
|
|
984
|
+
function wrapWithContext(node, context) {
|
|
985
|
+
return context.reduceRight((current, parent) => cloneElement(parent, [current]), node);
|
|
986
|
+
}
|
|
987
|
+
function documentWithNode(document, node, context) {
|
|
988
|
+
return documentWithChildren(document, [wrapWithContext(node, context)]);
|
|
989
|
+
}
|
|
990
|
+
function splitTextNode(text, document, context, maxLength) {
|
|
991
|
+
if (!text) return [text];
|
|
992
|
+
const parts = [];
|
|
993
|
+
let start = 0;
|
|
994
|
+
while (start < text.length) {
|
|
995
|
+
let end = start + 1;
|
|
996
|
+
let bestEnd = end;
|
|
997
|
+
while (end <= text.length) {
|
|
998
|
+
if (documentWithNode(document, text.slice(start, end), context).length > maxLength) break;
|
|
999
|
+
bestEnd = end;
|
|
1000
|
+
end += 1;
|
|
1001
|
+
}
|
|
1002
|
+
if (bestEnd === start) {
|
|
1003
|
+
throw new RangeError("maxLength is too small to contain the SSML document wrapper");
|
|
1004
|
+
}
|
|
1005
|
+
const segment = text.slice(start, bestEnd);
|
|
1006
|
+
const boundary = Math.max(segment.lastIndexOf(" "), segment.lastIndexOf("\n"), segment.lastIndexOf(" "));
|
|
1007
|
+
const splitEnd = boundary > 0 ? start + boundary + 1 : bestEnd;
|
|
1008
|
+
parts.push(text.slice(start, splitEnd));
|
|
1009
|
+
start = splitEnd;
|
|
1010
|
+
}
|
|
1011
|
+
return parts;
|
|
1012
|
+
}
|
|
1013
|
+
function splitNode(document, node, maxLength, context = []) {
|
|
1014
|
+
if (typeof node === "string" || node.type === "text") {
|
|
1015
|
+
const value = typeof node === "string" ? node : node.value;
|
|
1016
|
+
if (documentWithNode(document, node, context).length <= maxLength) return [node];
|
|
1017
|
+
return splitTextNode(value, document, context, maxLength);
|
|
1018
|
+
}
|
|
1019
|
+
if (documentWithNode(document, node, context).length <= maxLength) return [node];
|
|
1020
|
+
const children = node.children ?? [];
|
|
1021
|
+
if (children.length === 0) {
|
|
1022
|
+
throw new RangeError(`maxLength is too small to contain <${node.type}>`);
|
|
1023
|
+
}
|
|
1024
|
+
const splitChildren = children.flatMap((child) => splitNode(document, child, maxLength, [...context, node]));
|
|
1025
|
+
const parts = [];
|
|
1026
|
+
let group = [];
|
|
1027
|
+
const flush = () => {
|
|
1028
|
+
if (group.length > 0) {
|
|
1029
|
+
parts.push(cloneElement(node, group));
|
|
1030
|
+
group = [];
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
for (const child of splitChildren) {
|
|
1034
|
+
const candidate = cloneElement(node, [...group, child]);
|
|
1035
|
+
if (documentWithNode(document, candidate, context).length <= maxLength) {
|
|
1036
|
+
group.push(child);
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
flush();
|
|
1040
|
+
if (documentWithNode(document, cloneElement(node, [child]), context).length > maxLength) {
|
|
1041
|
+
throw new RangeError(`maxLength is too small to contain <${node.type}>`);
|
|
1042
|
+
}
|
|
1043
|
+
group.push(child);
|
|
1044
|
+
}
|
|
1045
|
+
flush();
|
|
1046
|
+
return parts;
|
|
1047
|
+
}
|
|
1048
|
+
function splitSsmlDocument(ssml, maxLength = DEFAULT_MAX_LENGTH) {
|
|
1049
|
+
if (!Number.isInteger(maxLength) || maxLength <= 0) {
|
|
1050
|
+
throw new RangeError("maxLength must be a positive integer");
|
|
1051
|
+
}
|
|
1052
|
+
const document = parseSsml(ssml);
|
|
1053
|
+
if (ssml.length <= maxLength) return [ssml];
|
|
1054
|
+
const children = document.children ?? [];
|
|
1055
|
+
const splitChildren = children.flatMap((child) => splitNode(document, child, maxLength));
|
|
1056
|
+
const chunks = [];
|
|
1057
|
+
let group = [];
|
|
1058
|
+
for (const child of splitChildren) {
|
|
1059
|
+
const candidate = [...group, child];
|
|
1060
|
+
if (documentWithChildren(document, candidate).length <= maxLength) {
|
|
1061
|
+
group = candidate;
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
if (group.length > 0) chunks.push(group);
|
|
1065
|
+
group = [child];
|
|
1066
|
+
if (documentWithChildren(document, group).length > maxLength) {
|
|
1067
|
+
throw new RangeError("maxLength is too small to contain the SSML document wrapper");
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
if (group.length > 0) chunks.push(group);
|
|
1071
|
+
return chunks.map((chunk) => documentWithChildren(document, chunk));
|
|
1072
|
+
}
|
|
1073
|
+
|
|
975
1074
|
// packages/ssml-core/src/validation.ts
|
|
976
1075
|
var PARSER_POSITION_SUFFIX = / at position (\d+)$/;
|
|
977
1076
|
function validateSsml(xmlString) {
|
|
@@ -1487,6 +1586,9 @@ var AZURE_VOICE_DEFINITIONS = [
|
|
|
1487
1586
|
},
|
|
1488
1587
|
{ name: "es-ES-ElviraNeural", locale: "es-ES" },
|
|
1489
1588
|
{ name: "fil-PH-AngeloNeural", locale: "fil-PH" },
|
|
1589
|
+
{ name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
|
|
1590
|
+
{ name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
|
|
1591
|
+
{ name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
|
|
1490
1592
|
{ name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1491
1593
|
{ name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
|
|
1492
1594
|
{ name: "id-ID-GadisNeural", locale: "id-ID" },
|
|
@@ -1583,6 +1685,18 @@ var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
|
|
|
1583
1685
|
"Enumerationcomma"
|
|
1584
1686
|
]);
|
|
1585
1687
|
var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
|
|
1688
|
+
var DEFAULT_PREVIEW_TAGS = /* @__PURE__ */ new Set(["mstts:voiceconversion"]);
|
|
1689
|
+
function featureStatusForTag(name, options) {
|
|
1690
|
+
const tagName = canonicalTagName(name);
|
|
1691
|
+
const configured = Object.entries(options.tagStatuses ?? {}).find(
|
|
1692
|
+
([candidate]) => canonicalTagName(candidate) === tagName
|
|
1693
|
+
)?.[1];
|
|
1694
|
+
if (configured) return configured;
|
|
1695
|
+
if ((options.previewTags ?? [...DEFAULT_PREVIEW_TAGS]).some((candidate) => canonicalTagName(candidate) === tagName))
|
|
1696
|
+
return "preview";
|
|
1697
|
+
if ((options.deprecatedTags ?? []).some((candidate) => canonicalTagName(candidate) === tagName)) return "deprecated";
|
|
1698
|
+
return void 0;
|
|
1699
|
+
}
|
|
1586
1700
|
function decodeAttribute(value) {
|
|
1587
1701
|
return value.replace(
|
|
1588
1702
|
/&(?:amp|apos|gt|lt|quot);/gi,
|
|
@@ -1651,6 +1765,7 @@ function tokenizeElements(source) {
|
|
|
1651
1765
|
attributes,
|
|
1652
1766
|
childElementIndex,
|
|
1653
1767
|
end,
|
|
1768
|
+
depth: openElements.length + 1,
|
|
1654
1769
|
name: tokenName,
|
|
1655
1770
|
parentName: parent?.name,
|
|
1656
1771
|
parentVoiceName,
|
|
@@ -1699,9 +1814,9 @@ function isValidAzureAudioDuration(value) {
|
|
|
1699
1814
|
return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
|
|
1700
1815
|
}
|
|
1701
1816
|
function isValidAzureBackgroundAudioDuration(value) {
|
|
1702
|
-
const match = /^(\d+
|
|
1817
|
+
const match = /^(\d+)$/.exec(value.trim());
|
|
1703
1818
|
if (!match) return false;
|
|
1704
|
-
const milliseconds = Number(match[1])
|
|
1819
|
+
const milliseconds = Number(match[1]);
|
|
1705
1820
|
return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
|
|
1706
1821
|
}
|
|
1707
1822
|
function attr(token, name) {
|
|
@@ -1841,13 +1956,18 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
1841
1956
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
|
|
1842
1957
|
return;
|
|
1843
1958
|
}
|
|
1959
|
+
if (parsed.username || parsed.password)
|
|
1960
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must not contain URL credentials.`);
|
|
1844
1961
|
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1845
1962
|
addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
|
|
1846
1963
|
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
1847
1964
|
try {
|
|
1848
|
-
|
|
1965
|
+
const configured = new URL(allowedOrigin);
|
|
1966
|
+
if (configured.protocol !== "https:" && configured.protocol !== "http:" || configured.username || configured.password || configured.pathname !== "/" || configured.search || configured.hash)
|
|
1967
|
+
return false;
|
|
1968
|
+
return configured.origin === parsed.origin;
|
|
1849
1969
|
} catch {
|
|
1850
|
-
return
|
|
1970
|
+
return false;
|
|
1851
1971
|
}
|
|
1852
1972
|
}) ?? false;
|
|
1853
1973
|
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
@@ -1862,6 +1982,25 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
1862
1982
|
}
|
|
1863
1983
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
1864
1984
|
const name = token.name.toLowerCase();
|
|
1985
|
+
const tagStatus = featureStatusForTag(token.name, options);
|
|
1986
|
+
if (tagStatus === "preview")
|
|
1987
|
+
addDiagnostic(
|
|
1988
|
+
diagnostics,
|
|
1989
|
+
source,
|
|
1990
|
+
token.start,
|
|
1991
|
+
`<${token.name}> is an Azure Speech preview feature and may change or require preview access.`,
|
|
1992
|
+
"warning",
|
|
1993
|
+
"azure-preview-tag"
|
|
1994
|
+
);
|
|
1995
|
+
if (tagStatus === "deprecated")
|
|
1996
|
+
addDiagnostic(
|
|
1997
|
+
diagnostics,
|
|
1998
|
+
source,
|
|
1999
|
+
token.start,
|
|
2000
|
+
`<${token.name}> is deprecated by Azure Speech; migrate to a supported alternative.`,
|
|
2001
|
+
"info",
|
|
2002
|
+
"azure-deprecated-tag"
|
|
2003
|
+
);
|
|
1865
2004
|
if (name === "voice" && !attr(token, "name")?.trim())
|
|
1866
2005
|
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
1867
2006
|
if (name === "break") {
|
|
@@ -2036,6 +2175,9 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
2036
2175
|
const maxLength = options.maxLength ?? 1e4;
|
|
2037
2176
|
if (ssml.length > maxLength)
|
|
2038
2177
|
addDiagnostic(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
|
|
2178
|
+
if (options.maxXmlDepth !== void 0 && (!Number.isInteger(options.maxXmlDepth) || options.maxXmlDepth <= 0)) {
|
|
2179
|
+
addDiagnostic(diagnostics, ssml, 0, "maxXmlDepth must be a positive integer.");
|
|
2180
|
+
}
|
|
2039
2181
|
try {
|
|
2040
2182
|
parseSsml(ssml);
|
|
2041
2183
|
} catch (error) {
|
|
@@ -2045,6 +2187,18 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
2045
2187
|
return diagnostics;
|
|
2046
2188
|
}
|
|
2047
2189
|
const tokens = tokenizeElements(ssml);
|
|
2190
|
+
if (options.maxXmlDepth !== void 0) {
|
|
2191
|
+
for (const token of tokens) {
|
|
2192
|
+
if (token.depth > options.maxXmlDepth) {
|
|
2193
|
+
addDiagnostic(
|
|
2194
|
+
diagnostics,
|
|
2195
|
+
ssml,
|
|
2196
|
+
token.start,
|
|
2197
|
+
`XML nesting depth ${token.depth} exceeds the configured maximum of ${options.maxXmlDepth}.`
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2048
2202
|
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
2049
2203
|
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
2050
2204
|
const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
|
|
@@ -2073,6 +2227,24 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
2073
2227
|
const name = attr(token, "name")?.trim();
|
|
2074
2228
|
const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
|
|
2075
2229
|
const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
|
|
2230
|
+
if (name && definition?.status === "preview")
|
|
2231
|
+
addDiagnostic(
|
|
2232
|
+
diagnostics,
|
|
2233
|
+
ssml,
|
|
2234
|
+
token.start,
|
|
2235
|
+
`Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
|
|
2236
|
+
"warning",
|
|
2237
|
+
"azure-preview-voice"
|
|
2238
|
+
);
|
|
2239
|
+
if (name && definition?.status === "deprecated")
|
|
2240
|
+
addDiagnostic(
|
|
2241
|
+
diagnostics,
|
|
2242
|
+
ssml,
|
|
2243
|
+
token.start,
|
|
2244
|
+
`Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
|
|
2245
|
+
"info",
|
|
2246
|
+
"azure-deprecated-voice"
|
|
2247
|
+
);
|
|
2076
2248
|
if (name && !definition && policySeverity)
|
|
2077
2249
|
addDiagnostic(
|
|
2078
2250
|
diagnostics,
|
|
@@ -2142,6 +2314,7 @@ var getBuiltInVoiceCatalogMetadata = getAzureVoiceCatalogMetadata;
|
|
|
2142
2314
|
mapSsmlTextNodes,
|
|
2143
2315
|
normalizeAzureLanguage,
|
|
2144
2316
|
parseSsml,
|
|
2317
|
+
splitSsmlDocument,
|
|
2145
2318
|
validateAzureSsml,
|
|
2146
2319
|
validateSsml,
|
|
2147
2320
|
validateSsmlStructureIntegrity
|