ssml-builder-js 2.2.0 → 2.4.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 +16 -2
- package/dist/{chunk-2XJER2BG.mjs → chunk-I3BR2WCQ.mjs} +161 -134
- package/dist/chunk-I3BR2WCQ.mjs.map +1 -0
- package/dist/{chunk-I3GP7OJU.mjs → chunk-RUIEMCWP.mjs} +462 -3
- package/dist/chunk-RUIEMCWP.mjs.map +1 -0
- package/dist/core.d.mts +22 -1
- package/dist/core.d.ts +22 -1
- package/dist/core.js +463 -1
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +7 -1
- package/dist/elements.js +7 -1
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +1 -1
- package/dist/index.d.mts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +467 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -4
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +25 -1
- package/dist/react.d.ts +25 -1
- package/dist/react.js +365 -15
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +333 -10
- package/dist/react.mjs.map +1 -1
- package/package.json +10 -1
- package/dist/chunk-2XJER2BG.mjs.map +0 -1
- package/dist/chunk-I3GP7OJU.mjs.map +0 -1
package/dist/elements.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
export { AudioElement, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, buildPartialSsml, buildSsml, parseSsml, validateSsml } from './core.mjs';
|
|
1
|
+
export { AudioElement, AzureSsmlValidationOptions, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDiagnostic, SsmlDiagnosticSeverity, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlTextNodeContext, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, parseSsml, validateAzureSsml, validateSsml } from './core.mjs';
|
|
2
2
|
|
|
3
3
|
interface TtsConfig {
|
|
4
|
-
endpoint
|
|
4
|
+
endpoint?: string;
|
|
5
5
|
subscriptionKey: string;
|
|
6
6
|
region: string;
|
|
7
7
|
outputFormat?: string;
|
|
8
8
|
}
|
|
9
|
+
interface AzureTtsLogger {
|
|
10
|
+
debug?: (...args: unknown[]) => void;
|
|
11
|
+
info?: (...args: unknown[]) => void;
|
|
12
|
+
warn?: (...args: unknown[]) => void;
|
|
13
|
+
error?: (...args: unknown[]) => void;
|
|
14
|
+
}
|
|
9
15
|
interface AzureTtsClientOptions {
|
|
10
16
|
subscriptionKey: string;
|
|
11
17
|
region: string;
|
|
12
18
|
endpoint?: string;
|
|
13
19
|
outputFormat?: string;
|
|
20
|
+
logger?: AzureTtsLogger;
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
declare class AzureTtsError extends Error {
|
|
@@ -33,4 +40,4 @@ declare class AzureTtsClient {
|
|
|
33
40
|
|
|
34
41
|
declare function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer>;
|
|
35
42
|
|
|
36
|
-
export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, AzureTtsSdkError, type TtsConfig, synthesizeSpeech };
|
|
43
|
+
export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type TtsConfig, synthesizeSpeech };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
export { AudioElement, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, buildPartialSsml, buildSsml, parseSsml, validateSsml } from './core.js';
|
|
1
|
+
export { AudioElement, AzureSsmlValidationOptions, BookmarkElement, BreakElement, BuildPartialSsmlOptions, CustomElement, EmphasisElement, ExpressAsElement, LangElement, LexiconElement, MarkElement, MsttsSilenceElement, MsttsVisemeElement, NamedElement, ParagraphElement, PhonemeElement, ProsodyElement, SayAsElement, SentenceElement, SsmlAttributeValue, SsmlAttributes, SsmlBreakElement, SsmlDiagnostic, SsmlDiagnosticSeverity, SsmlDocument, SsmlElement, SsmlElementBase, SsmlExpressAsElement, SsmlNode, SsmlPartialContext, SsmlPartialProsody, SsmlPartialVoice, SsmlPhonemeElement, SsmlProsodyElement, SsmlSayAsElement, SsmlText, SsmlTextNodeContext, SsmlValidationError, SsmlVoiceElement, SubElement, VoiceElement, WordElement, buildPartialSsml, buildSsml, extractSsmlText, mapSsmlTextNodes, parseSsml, validateAzureSsml, validateSsml } from './core.js';
|
|
2
2
|
|
|
3
3
|
interface TtsConfig {
|
|
4
|
-
endpoint
|
|
4
|
+
endpoint?: string;
|
|
5
5
|
subscriptionKey: string;
|
|
6
6
|
region: string;
|
|
7
7
|
outputFormat?: string;
|
|
8
8
|
}
|
|
9
|
+
interface AzureTtsLogger {
|
|
10
|
+
debug?: (...args: unknown[]) => void;
|
|
11
|
+
info?: (...args: unknown[]) => void;
|
|
12
|
+
warn?: (...args: unknown[]) => void;
|
|
13
|
+
error?: (...args: unknown[]) => void;
|
|
14
|
+
}
|
|
9
15
|
interface AzureTtsClientOptions {
|
|
10
16
|
subscriptionKey: string;
|
|
11
17
|
region: string;
|
|
12
18
|
endpoint?: string;
|
|
13
19
|
outputFormat?: string;
|
|
20
|
+
logger?: AzureTtsLogger;
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
declare class AzureTtsError extends Error {
|
|
@@ -33,4 +40,4 @@ declare class AzureTtsClient {
|
|
|
33
40
|
|
|
34
41
|
declare function synthesizeSpeech(ssml: string, config: TtsConfig): Promise<ArrayBuffer>;
|
|
35
42
|
|
|
36
|
-
export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, AzureTtsSdkError, type TtsConfig, synthesizeSpeech };
|
|
43
|
+
export { AzureTtsClient, type AzureTtsClientOptions, AzureTtsError, type AzureTtsLogger, AzureTtsSdkError, type TtsConfig, synthesizeSpeech };
|
package/dist/index.js
CHANGED
|
@@ -42,8 +42,11 @@ __export(src_exports, {
|
|
|
42
42
|
AzureTtsSdkError: () => AzureTtsSdkError,
|
|
43
43
|
buildPartialSsml: () => buildPartialSsml,
|
|
44
44
|
buildSsml: () => buildSsml,
|
|
45
|
+
extractSsmlText: () => extractSsmlText,
|
|
46
|
+
mapSsmlTextNodes: () => mapSsmlTextNodes,
|
|
45
47
|
parseSsml: () => parseSsml,
|
|
46
48
|
synthesizeSpeech: () => synthesizeSpeech,
|
|
49
|
+
validateAzureSsml: () => validateAzureSsml,
|
|
47
50
|
validateSsml: () => validateSsml
|
|
48
51
|
});
|
|
49
52
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -100,6 +103,7 @@ var SSML_ATTRS = {
|
|
|
100
103
|
STYLE: "style",
|
|
101
104
|
STYLE_DEGREE: "styledegree",
|
|
102
105
|
STYLE_DEGREE_CAMEL: "styleDegree",
|
|
106
|
+
STYLE_DEGREE_HYPHEN: "style-degree",
|
|
103
107
|
ROLE: "role",
|
|
104
108
|
INTERPRET_AS: "interpret-as",
|
|
105
109
|
FORMAT: "format",
|
|
@@ -647,7 +651,12 @@ function convertElement(node) {
|
|
|
647
651
|
case SSML_TAGS.MSTTS_EXPRESS_AS: {
|
|
648
652
|
const element = { type: node.name };
|
|
649
653
|
const style = readAttribute(attributes, SSML_ATTRS.STYLE);
|
|
650
|
-
const styleDegree = readAttribute(
|
|
654
|
+
const styleDegree = readAttribute(
|
|
655
|
+
attributes,
|
|
656
|
+
SSML_ATTRS.STYLE_DEGREE,
|
|
657
|
+
SSML_ATTRS.STYLE_DEGREE_CAMEL,
|
|
658
|
+
SSML_ATTRS.STYLE_DEGREE_HYPHEN
|
|
659
|
+
);
|
|
651
660
|
const role = readAttribute(attributes, SSML_ATTRS.ROLE);
|
|
652
661
|
if (style !== void 0) element.style = style;
|
|
653
662
|
if (styleDegree !== void 0) element.styleDegree = styleDegree;
|
|
@@ -886,6 +895,456 @@ function validateSsml(xmlString) {
|
|
|
886
895
|
}
|
|
887
896
|
}
|
|
888
897
|
|
|
898
|
+
// packages/ssml-core/src/textNodes.ts
|
|
899
|
+
function decodeXmlText(value) {
|
|
900
|
+
return value.replace(/&(?:amp|apos|gt|lt|quot);|&#(?:x[\da-f]+|\d+);/gi, (entity) => {
|
|
901
|
+
if (entity === "&") return "&";
|
|
902
|
+
if (entity === "'") return "'";
|
|
903
|
+
if (entity === ">") return ">";
|
|
904
|
+
if (entity === "<") return "<";
|
|
905
|
+
if (entity === """) return '"';
|
|
906
|
+
const hexadecimal = entity.toLowerCase().startsWith("&#x");
|
|
907
|
+
const digits = entity.slice(hexadecimal ? 3 : 2, -1);
|
|
908
|
+
return String.fromCodePoint(Number.parseInt(digits, hexadecimal ? 16 : 10));
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
function encodeXmlText(value) {
|
|
912
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
913
|
+
}
|
|
914
|
+
function findTagEnd(source, start) {
|
|
915
|
+
let quote = "";
|
|
916
|
+
for (let index = start; index < source.length; index += 1) {
|
|
917
|
+
const character = source[index];
|
|
918
|
+
if (quote) {
|
|
919
|
+
if (character === quote) quote = "";
|
|
920
|
+
} else if (character === '"' || character === "'") {
|
|
921
|
+
quote = character;
|
|
922
|
+
} else if (character === ">") {
|
|
923
|
+
return index;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return source.length - 1;
|
|
927
|
+
}
|
|
928
|
+
function readTagName(tag) {
|
|
929
|
+
const match = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(tag);
|
|
930
|
+
return match?.[1];
|
|
931
|
+
}
|
|
932
|
+
function collectTextNodes(source) {
|
|
933
|
+
const nodes = [];
|
|
934
|
+
const path = [];
|
|
935
|
+
let index = 0;
|
|
936
|
+
const addText = (start, end, rawText, sourceStart = start, sourceEnd = end) => {
|
|
937
|
+
if (!rawText) return;
|
|
938
|
+
nodes.push({
|
|
939
|
+
context: { parentTag: path[path.length - 1] ?? "", path: [...path] },
|
|
940
|
+
decodedText: decodeXmlText(rawText),
|
|
941
|
+
end,
|
|
942
|
+
sourceEnd,
|
|
943
|
+
sourceStart,
|
|
944
|
+
start
|
|
945
|
+
});
|
|
946
|
+
};
|
|
947
|
+
while (index < source.length) {
|
|
948
|
+
if (source[index] !== "<") {
|
|
949
|
+
const nextTag = source.indexOf("<", index);
|
|
950
|
+
const end2 = nextTag === -1 ? source.length : nextTag;
|
|
951
|
+
addText(index, end2, source.slice(index, end2));
|
|
952
|
+
index = end2;
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (source.startsWith("<!--", index)) {
|
|
956
|
+
const end2 = source.indexOf("-->", index + 4);
|
|
957
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
if (source.startsWith("<![CDATA[", index)) {
|
|
961
|
+
const contentStart = index + 9;
|
|
962
|
+
const end2 = source.indexOf("]]>", contentStart);
|
|
963
|
+
const contentEnd = end2 === -1 ? source.length : end2;
|
|
964
|
+
addText(
|
|
965
|
+
contentStart,
|
|
966
|
+
contentEnd,
|
|
967
|
+
source.slice(contentStart, contentEnd),
|
|
968
|
+
index,
|
|
969
|
+
end2 === -1 ? source.length : end2 + 3
|
|
970
|
+
);
|
|
971
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (source.startsWith("<?", index)) {
|
|
975
|
+
const end2 = source.indexOf("?>", index + 2);
|
|
976
|
+
index = end2 === -1 ? source.length : end2 + 2;
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
if (source.startsWith("</", index)) {
|
|
980
|
+
const end2 = findTagEnd(source, index + 2);
|
|
981
|
+
path.pop();
|
|
982
|
+
index = end2 + 1;
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
const end = findTagEnd(source, index + 1);
|
|
986
|
+
const tag = source.slice(index, end + 1);
|
|
987
|
+
const name = readTagName(tag);
|
|
988
|
+
if (name && !/\/\s*>$/.test(tag)) path.push(name);
|
|
989
|
+
index = end + 1;
|
|
990
|
+
}
|
|
991
|
+
return nodes;
|
|
992
|
+
}
|
|
993
|
+
function extractSsmlText(ssml) {
|
|
994
|
+
parseSsml(ssml);
|
|
995
|
+
return collectTextNodes(ssml).map((node) => node.decodedText);
|
|
996
|
+
}
|
|
997
|
+
async function mapSsmlTextNodes(ssml, transform) {
|
|
998
|
+
parseSsml(ssml);
|
|
999
|
+
const nodes = collectTextNodes(ssml);
|
|
1000
|
+
const replacements = await Promise.all(
|
|
1001
|
+
nodes.map(async (node) => {
|
|
1002
|
+
const transformed = await transform(node.decodedText, {
|
|
1003
|
+
parentTag: node.context.parentTag,
|
|
1004
|
+
path: [...node.context.path]
|
|
1005
|
+
});
|
|
1006
|
+
if (typeof transformed !== "string") {
|
|
1007
|
+
throw new TypeError("SSML text node transform must return a string");
|
|
1008
|
+
}
|
|
1009
|
+
return transformed === node.decodedText ? ssml.slice(node.sourceStart, node.sourceEnd) : encodeXmlText(transformed);
|
|
1010
|
+
})
|
|
1011
|
+
);
|
|
1012
|
+
let result = "";
|
|
1013
|
+
let cursor = 0;
|
|
1014
|
+
nodes.forEach((node, nodeIndex) => {
|
|
1015
|
+
result += ssml.slice(cursor, node.sourceStart) + replacements[nodeIndex];
|
|
1016
|
+
cursor = node.sourceEnd;
|
|
1017
|
+
});
|
|
1018
|
+
return result + ssml.slice(cursor);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// packages/ssml-core/src/azureValidation.ts
|
|
1022
|
+
var EXPRESS_AS_STYLES = {
|
|
1023
|
+
"en-us-jennyneural": [
|
|
1024
|
+
"assistant",
|
|
1025
|
+
"chat",
|
|
1026
|
+
"customerservice",
|
|
1027
|
+
"newscast",
|
|
1028
|
+
"cheerful",
|
|
1029
|
+
"empathetic",
|
|
1030
|
+
"excited",
|
|
1031
|
+
"friendly",
|
|
1032
|
+
"hopeful",
|
|
1033
|
+
"sad",
|
|
1034
|
+
"shouting",
|
|
1035
|
+
"terrified",
|
|
1036
|
+
"unfriendly",
|
|
1037
|
+
"whispering"
|
|
1038
|
+
],
|
|
1039
|
+
"en-us-guyneural": [
|
|
1040
|
+
"angry",
|
|
1041
|
+
"cheerful",
|
|
1042
|
+
"excited",
|
|
1043
|
+
"friendly",
|
|
1044
|
+
"hopeful",
|
|
1045
|
+
"newscast",
|
|
1046
|
+
"sad",
|
|
1047
|
+
"shouting",
|
|
1048
|
+
"terrified",
|
|
1049
|
+
"unfriendly",
|
|
1050
|
+
"whispering"
|
|
1051
|
+
],
|
|
1052
|
+
"en-us-jennymultilingualneural": [
|
|
1053
|
+
"cheerful",
|
|
1054
|
+
"empathetic",
|
|
1055
|
+
"excited",
|
|
1056
|
+
"friendly",
|
|
1057
|
+
"hopeful",
|
|
1058
|
+
"sad",
|
|
1059
|
+
"shouting",
|
|
1060
|
+
"terrified",
|
|
1061
|
+
"unfriendly",
|
|
1062
|
+
"whispering"
|
|
1063
|
+
],
|
|
1064
|
+
"en-us-andrewneural": ["empathetic", "relieved"],
|
|
1065
|
+
"ja-jp-mayuneural": ["calm", "cheerful", "sad"],
|
|
1066
|
+
"ja-jp-nanamineural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
1067
|
+
"ja-jp-keitaneural": ["chat"],
|
|
1068
|
+
"ko-kr-sunhineural": ["cheerful", "sad"],
|
|
1069
|
+
"zh-cn-yunxineural": [
|
|
1070
|
+
"narration-relaxed",
|
|
1071
|
+
"embarrassed",
|
|
1072
|
+
"fearful",
|
|
1073
|
+
"sad",
|
|
1074
|
+
"disgruntled",
|
|
1075
|
+
"serious",
|
|
1076
|
+
"angry",
|
|
1077
|
+
"depressed",
|
|
1078
|
+
"chat",
|
|
1079
|
+
"cheerful",
|
|
1080
|
+
"assistant"
|
|
1081
|
+
],
|
|
1082
|
+
"zh-cn-xiaoxiaoneural": [
|
|
1083
|
+
"assistant",
|
|
1084
|
+
"chat",
|
|
1085
|
+
"customerservice",
|
|
1086
|
+
"newscast",
|
|
1087
|
+
"cheerful",
|
|
1088
|
+
"empathetic",
|
|
1089
|
+
"excited",
|
|
1090
|
+
"friendly",
|
|
1091
|
+
"hopeful",
|
|
1092
|
+
"sad",
|
|
1093
|
+
"terrified",
|
|
1094
|
+
"whispering",
|
|
1095
|
+
"poetry-reading",
|
|
1096
|
+
"sports_commentary",
|
|
1097
|
+
"sports_commentary_excited",
|
|
1098
|
+
"story"
|
|
1099
|
+
],
|
|
1100
|
+
"fr-fr-deniseneural": ["cheerful", "sad"],
|
|
1101
|
+
"fr-fr-henrineural": ["cheerful", "sad"],
|
|
1102
|
+
"pt-br-franciscaneural": ["calm"],
|
|
1103
|
+
"it-it-elsaneural": ["cheerful", "sad"],
|
|
1104
|
+
"de-de-katjaneural": ["cheerful", "sad"],
|
|
1105
|
+
"de-de-conradneural": ["cheerful", "sad"],
|
|
1106
|
+
"ru-ru-svetlananeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
1107
|
+
};
|
|
1108
|
+
var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
|
|
1109
|
+
var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
|
|
1110
|
+
"characters",
|
|
1111
|
+
"spell-out",
|
|
1112
|
+
"cardinal",
|
|
1113
|
+
"ordinal",
|
|
1114
|
+
"number",
|
|
1115
|
+
"date",
|
|
1116
|
+
"time",
|
|
1117
|
+
"telephone",
|
|
1118
|
+
"fraction",
|
|
1119
|
+
"address",
|
|
1120
|
+
"name",
|
|
1121
|
+
"currency"
|
|
1122
|
+
]);
|
|
1123
|
+
var ALLOWED_ROLES = /* @__PURE__ */ new Set([
|
|
1124
|
+
"Girl",
|
|
1125
|
+
"Boy",
|
|
1126
|
+
"YoungAdultFemale",
|
|
1127
|
+
"YoungAdultMale",
|
|
1128
|
+
"OlderAdultFemale",
|
|
1129
|
+
"OlderAdultMale",
|
|
1130
|
+
"SeniorFemale",
|
|
1131
|
+
"SeniorMale"
|
|
1132
|
+
]);
|
|
1133
|
+
var ALLOWED_EMPHASIS_LEVELS = /* @__PURE__ */ new Set(["strong", "moderate", "reduced", "none"]);
|
|
1134
|
+
var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
|
|
1135
|
+
"Leading",
|
|
1136
|
+
"Tailing",
|
|
1137
|
+
"Sentenceboundary",
|
|
1138
|
+
"Comma",
|
|
1139
|
+
"Semicolon",
|
|
1140
|
+
"Enumerationcomma"
|
|
1141
|
+
]);
|
|
1142
|
+
var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
|
|
1143
|
+
function decodeAttribute(value) {
|
|
1144
|
+
return value.replace(
|
|
1145
|
+
/&(?:amp|apos|gt|lt|quot);/gi,
|
|
1146
|
+
(entity) => ({ "&": "&", "'": "'", ">": ">", "<": "<", """: '"' })[entity.toLowerCase()] ?? entity
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
function findTagEnd2(source, start) {
|
|
1150
|
+
let quote = "";
|
|
1151
|
+
for (let index = start; index < source.length; index += 1) {
|
|
1152
|
+
const character = source[index];
|
|
1153
|
+
if (quote) {
|
|
1154
|
+
if (character === quote) quote = "";
|
|
1155
|
+
} else if (character === '"' || character === "'") quote = character;
|
|
1156
|
+
else if (character === ">") return index;
|
|
1157
|
+
}
|
|
1158
|
+
return source.length - 1;
|
|
1159
|
+
}
|
|
1160
|
+
function tokenizeElements(source) {
|
|
1161
|
+
const tokens = [];
|
|
1162
|
+
let index = 0;
|
|
1163
|
+
while (index < source.length) {
|
|
1164
|
+
const start = source.indexOf("<", index);
|
|
1165
|
+
if (start === -1) break;
|
|
1166
|
+
if (source.startsWith("<!--", start)) {
|
|
1167
|
+
const end2 = source.indexOf("-->", start + 4);
|
|
1168
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
if (source.startsWith("<![CDATA[", start)) {
|
|
1172
|
+
const end2 = source.indexOf("]]>", start + 9);
|
|
1173
|
+
index = end2 === -1 ? source.length : end2 + 3;
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
if (source.startsWith("<?", start)) {
|
|
1177
|
+
const end2 = source.indexOf("?>", start + 2);
|
|
1178
|
+
index = end2 === -1 ? source.length : end2 + 2;
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
const end = findTagEnd2(source, start + 1);
|
|
1182
|
+
const raw = source.slice(start, end + 1);
|
|
1183
|
+
const nameMatch = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(raw);
|
|
1184
|
+
if (!nameMatch?.[1] || raw.startsWith("</")) {
|
|
1185
|
+
index = end + 1;
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
const attributes = /* @__PURE__ */ new Map();
|
|
1189
|
+
const attributeSource = raw.slice(nameMatch[0].length, raw.length - 1).replace(/\/\s*$/, "");
|
|
1190
|
+
const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
|
|
1191
|
+
for (const match of attributeSource.matchAll(attributePattern)) {
|
|
1192
|
+
attributes.set(match[1].toLowerCase(), decodeAttribute(match[3]));
|
|
1193
|
+
}
|
|
1194
|
+
tokens.push({ attributes, end, name: nameMatch[1], selfClosing: /\/\s*>$/.test(raw), start });
|
|
1195
|
+
index = end + 1;
|
|
1196
|
+
}
|
|
1197
|
+
return tokens;
|
|
1198
|
+
}
|
|
1199
|
+
function location(source, offset) {
|
|
1200
|
+
const before = source.slice(0, Math.max(0, offset));
|
|
1201
|
+
const line = before.split("\n").length;
|
|
1202
|
+
return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
|
|
1203
|
+
}
|
|
1204
|
+
function addDiagnostic(diagnostics, source, offset, message, severity = "error") {
|
|
1205
|
+
diagnostics.push({ ...location(source, offset), message, severity });
|
|
1206
|
+
}
|
|
1207
|
+
function attr(token, name) {
|
|
1208
|
+
return token.attributes.get(name.toLowerCase());
|
|
1209
|
+
}
|
|
1210
|
+
function validateElement(token, source, diagnostics, voiceName, options) {
|
|
1211
|
+
const name = token.name.toLowerCase();
|
|
1212
|
+
if (name === "voice" && !attr(token, "name")?.trim())
|
|
1213
|
+
addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
|
|
1214
|
+
if (name === "break") {
|
|
1215
|
+
const time = attr(token, "time");
|
|
1216
|
+
const strength = attr(token, "strength");
|
|
1217
|
+
if (!time && !strength)
|
|
1218
|
+
addDiagnostic(diagnostics, source, token.start, '<break> requires either "time" or "strength".');
|
|
1219
|
+
if (time && strength)
|
|
1220
|
+
addDiagnostic(diagnostics, source, token.start, '<break> must not specify both "time" and "strength".');
|
|
1221
|
+
if (time && !/^\d+(?:\.\d+)?(?:ms|s)$/.test(time.trim()))
|
|
1222
|
+
addDiagnostic(diagnostics, source, token.start, '<break time> must use a numeric value followed by "ms" or "s".');
|
|
1223
|
+
if (strength && !ALLOWED_BREAK_STRENGTHS.has(strength))
|
|
1224
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <break strength> value "${strength}".`);
|
|
1225
|
+
}
|
|
1226
|
+
if (name === "prosody") {
|
|
1227
|
+
const rate = attr(token, "rate");
|
|
1228
|
+
const pitch = attr(token, "pitch");
|
|
1229
|
+
const volume = attr(token, "volume");
|
|
1230
|
+
if (rate && !/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(rate.trim()))
|
|
1231
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody rate> value "${rate}".`);
|
|
1232
|
+
if (pitch && !/^(x-low|low|medium|high|x-high|[+-]?\d+(?:\.\d+)?(?:st|Hz|%)?)$/.test(pitch.trim()))
|
|
1233
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody pitch> value "${pitch}".`);
|
|
1234
|
+
if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%)?)$/.test(volume.trim()))
|
|
1235
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody volume> value "${volume}".`);
|
|
1236
|
+
}
|
|
1237
|
+
if (name === "mstts:express-as" || name === "express-as" || name === "expressas") {
|
|
1238
|
+
const style = attr(token, "style");
|
|
1239
|
+
if (!style?.trim())
|
|
1240
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:express-as> requires a non-empty "style" attribute.');
|
|
1241
|
+
const degree = attr(token, "styledegree") ?? attr(token, "style-degree");
|
|
1242
|
+
if (degree && (!/^\d+(?:\.\d+)?$/.test(degree) || Number(degree) < 0.01 || Number(degree) > 2))
|
|
1243
|
+
addDiagnostic(
|
|
1244
|
+
diagnostics,
|
|
1245
|
+
source,
|
|
1246
|
+
token.start,
|
|
1247
|
+
"<mstts:express-as styledegree> must be a number between 0.01 and 2."
|
|
1248
|
+
);
|
|
1249
|
+
const role = attr(token, "role");
|
|
1250
|
+
if (role && !ALLOWED_ROLES.has(role))
|
|
1251
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:express-as role> value "${role}".`);
|
|
1252
|
+
const supportedStyles = voiceName ? EXPRESS_AS_STYLES[voiceName.toLowerCase()] : void 0;
|
|
1253
|
+
if (style && supportedStyles && !supportedStyles.includes(style.toLowerCase()))
|
|
1254
|
+
addDiagnostic(diagnostics, source, token.start, `Style "${style}" is not supported by voice "${voiceName}".`);
|
|
1255
|
+
}
|
|
1256
|
+
if (name === "say-as" || name === "sayas") {
|
|
1257
|
+
const interpretAs = attr(token, "interpret-as");
|
|
1258
|
+
if (!interpretAs || !ALLOWED_SAY_AS.has(interpretAs))
|
|
1259
|
+
addDiagnostic(diagnostics, source, token.start, `<say-as> requires a supported "interpret-as" value.`);
|
|
1260
|
+
}
|
|
1261
|
+
if (name === "phoneme" && (!attr(token, "alphabet") || !attr(token, "ph")))
|
|
1262
|
+
addDiagnostic(diagnostics, source, token.start, '<phoneme> requires both "alphabet" and "ph" attributes.');
|
|
1263
|
+
if (name === "emphasis" && attr(token, "level") && !ALLOWED_EMPHASIS_LEVELS.has(attr(token, "level") ?? ""))
|
|
1264
|
+
addDiagnostic(diagnostics, source, token.start, `Unsupported <emphasis level> value "${attr(token, "level")}".`);
|
|
1265
|
+
if (name === "sub" && !attr(token, "alias")?.trim())
|
|
1266
|
+
addDiagnostic(diagnostics, source, token.start, '<sub> requires a non-empty "alias" attribute.');
|
|
1267
|
+
if (name === "lang" && !attr(token, "xml:lang")?.trim() && !attr(token, "lang")?.trim())
|
|
1268
|
+
addDiagnostic(diagnostics, source, token.start, '<lang> requires an "xml:lang" attribute.');
|
|
1269
|
+
if (name === "mark" && !attr(token, "name")?.trim())
|
|
1270
|
+
addDiagnostic(diagnostics, source, token.start, '<mark> requires a non-empty "name" attribute.');
|
|
1271
|
+
if (name === "bookmark" && !attr(token, "mark")?.trim())
|
|
1272
|
+
addDiagnostic(diagnostics, source, token.start, '<bookmark> requires a non-empty "mark" attribute.');
|
|
1273
|
+
if (name === "lexicon") {
|
|
1274
|
+
const uri = attr(token, "uri");
|
|
1275
|
+
if (!uri) addDiagnostic(diagnostics, source, token.start, '<lexicon> requires a "uri" attribute.');
|
|
1276
|
+
else {
|
|
1277
|
+
try {
|
|
1278
|
+
const parsed = new URL(uri);
|
|
1279
|
+
if (parsed.protocol !== "https:")
|
|
1280
|
+
addDiagnostic(diagnostics, source, token.start, "<lexicon uri> must use HTTPS.");
|
|
1281
|
+
} catch {
|
|
1282
|
+
addDiagnostic(diagnostics, source, token.start, "<lexicon uri> must be an absolute HTTPS URL.");
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (name === "mstts:silence") {
|
|
1287
|
+
const type = attr(token, "type");
|
|
1288
|
+
const value = attr(token, "value");
|
|
1289
|
+
if (!type || !ALLOWED_SILENCE_TYPES.has(type))
|
|
1290
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a supported "type" attribute.');
|
|
1291
|
+
if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
|
|
1292
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
|
|
1293
|
+
}
|
|
1294
|
+
if (name === "mstts:viseme") {
|
|
1295
|
+
const type = attr(token, "type");
|
|
1296
|
+
if (!type || !ALLOWED_VISEME_TYPES.has(type))
|
|
1297
|
+
addDiagnostic(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
|
|
1298
|
+
}
|
|
1299
|
+
if (name === "audio") {
|
|
1300
|
+
const src = attr(token, "src");
|
|
1301
|
+
if (!src) addDiagnostic(diagnostics, source, token.start, '<audio> requires a "src" attribute.');
|
|
1302
|
+
else {
|
|
1303
|
+
let parsed;
|
|
1304
|
+
try {
|
|
1305
|
+
parsed = new URL(src);
|
|
1306
|
+
} catch {
|
|
1307
|
+
addDiagnostic(diagnostics, source, token.start, "<audio src> must be an absolute HTTP(S) URL.");
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1311
|
+
addDiagnostic(diagnostics, source, token.start, "<audio src> must use HTTPS.");
|
|
1312
|
+
if (options.allowedAudioOrigins && !options.allowedAudioOrigins.includes(parsed.origin))
|
|
1313
|
+
addDiagnostic(diagnostics, source, token.start, `<audio src> origin "${parsed.origin}" is not allowed.`);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
function validateAzureSsml(ssml, options = {}) {
|
|
1318
|
+
const diagnostics = [];
|
|
1319
|
+
if (typeof ssml !== "string") {
|
|
1320
|
+
return [{ line: 1, column: 1, message: "SSML input must be a string", severity: "error" }];
|
|
1321
|
+
}
|
|
1322
|
+
const maxLength = options.maxLength ?? 1e4;
|
|
1323
|
+
if (ssml.length > maxLength)
|
|
1324
|
+
addDiagnostic(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
|
|
1325
|
+
try {
|
|
1326
|
+
parseSsml(ssml);
|
|
1327
|
+
} catch (error) {
|
|
1328
|
+
const message = error instanceof Error ? error.message.replace(/ at position \d+$/, "") : String(error);
|
|
1329
|
+
const match = / at position (\d+)$/.exec(error instanceof Error ? error.message : "");
|
|
1330
|
+
addDiagnostic(diagnostics, ssml, match ? Number(match[1]) : 0, message);
|
|
1331
|
+
return diagnostics;
|
|
1332
|
+
}
|
|
1333
|
+
const tokens = tokenizeElements(ssml);
|
|
1334
|
+
const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
|
|
1335
|
+
const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
|
|
1336
|
+
if (!speak || voices.length === 0)
|
|
1337
|
+
addDiagnostic(
|
|
1338
|
+
diagnostics,
|
|
1339
|
+
ssml,
|
|
1340
|
+
speak?.start ?? 0,
|
|
1341
|
+
"Azure SSML requires at least one <voice> element under <speak>."
|
|
1342
|
+
);
|
|
1343
|
+
const voiceName = voices[0] ? attr(voices[0], "name") : void 0;
|
|
1344
|
+
for (const token of tokens) validateElement(token, ssml, diagnostics, voiceName, options);
|
|
1345
|
+
return diagnostics;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
889
1348
|
// packages/azure-tts-client/src/errors.ts
|
|
890
1349
|
var AzureTtsError = class extends Error {
|
|
891
1350
|
constructor(status, statusText, responseBody, requestId) {
|
|
@@ -970,7 +1429,8 @@ function resolveOutputFormat(outputFormat) {
|
|
|
970
1429
|
|
|
971
1430
|
// packages/azure-tts-client/src/speechConfig.ts
|
|
972
1431
|
function resolveEndpoint(config) {
|
|
973
|
-
|
|
1432
|
+
const endpoint = config.endpoint?.trim() || "https://{region}.tts.speech.microsoft.com/cognitiveservices/v1";
|
|
1433
|
+
return endpoint.replace(/\{region\}/g, encodeURIComponent(config.region));
|
|
974
1434
|
}
|
|
975
1435
|
function createSpeechConfig(config) {
|
|
976
1436
|
const { outputFormat = DEFAULT_OUTPUT_FORMAT, subscriptionKey } = config;
|
|
@@ -1033,8 +1493,8 @@ var AzureTtsClient = class {
|
|
|
1033
1493
|
}
|
|
1034
1494
|
async synthesize(ssml) {
|
|
1035
1495
|
const { region, subscriptionKey, outputFormat } = __privateGet(this, _options);
|
|
1036
|
-
const endpoint = __privateGet(this, _options).endpoint
|
|
1037
|
-
|
|
1496
|
+
const endpoint = __privateGet(this, _options).endpoint?.trim() || ENDPOINT_TEMPLATE.replace("{region}", region);
|
|
1497
|
+
__privateGet(this, _options).logger?.debug?.("Using Azure TTS endpoint:", endpoint);
|
|
1038
1498
|
const config = { endpoint, region, subscriptionKey, outputFormat };
|
|
1039
1499
|
return synthesizeSpeech(ssml, config);
|
|
1040
1500
|
}
|
|
@@ -1047,8 +1507,11 @@ _options = new WeakMap();
|
|
|
1047
1507
|
AzureTtsSdkError,
|
|
1048
1508
|
buildPartialSsml,
|
|
1049
1509
|
buildSsml,
|
|
1510
|
+
extractSsmlText,
|
|
1511
|
+
mapSsmlTextNodes,
|
|
1050
1512
|
parseSsml,
|
|
1051
1513
|
synthesizeSpeech,
|
|
1514
|
+
validateAzureSsml,
|
|
1052
1515
|
validateSsml
|
|
1053
1516
|
});
|
|
1054
1517
|
//# sourceMappingURL=index.js.map
|