[
{
"name": "旁白/对话-多角色",
"version": 1,
"ruleId": "ttsrv.multi_voice_custom",
"author": "TTS Server 230413",
"code": "//默认正则(旁白在前)\nlet narrationFrontRegex = \"__ROLE__\";\n//默认正则(旁白在后)\nlet narrationBackRegex = \"__ROLE__\";\n//替换正则中的__ALLROLE__\nlet useAllRole = false;\n\nlet SpeechRuleJS = {\n\tname: \"旁白/对话-多角色\",\n\tid: \"ttsrv.multi_voice_custom\",\n\tauthor: \"TTS Server 230413\",\n\tversion: 1,\n\ttags: {\n\t\tnarration: \"旁白\",\n\t\tdialogue: \"对话\"\n\t},\n\n\ttagsData: {\n\t\tdialogue: {\n\t\t\trole: {\n\t\t\t\tlabel: \"匹配角色名\",\n\t\t\t\thint: \"可以|分隔\"\n\t\t\t},\n\t\t\tfRegex: {\n\t\t\t\tlabel: \"正则(旁白在前)\",\n\t\t\t\thint: \"在旁白中匹配,可用__ROLE__代替角色名\"\n\t\t\t},\n\t\t\tbRegex: {\n\t\t\t\tlabel: \"正则(旁白在后)\",\n\t\t\t\thint: \"在旁白中匹配,可用__ROLE__代替角色名\"\n\t\t\t},\n\t\t\tdefaultFlag: {\n\t\t\t\tlabel: \"作为默认对话\",\n\t\t\t\thint: \"\",\n\t\t\t\titems: '{true: \"是\", false: \"否\"}',\n\t\t\t\tdefault: 'false'\n\t\t\t}\n\t\t}\n\t},\n\n\tgetTagName(tag, tagData) {\n\t\tif (\"dialogue\" == tag) {\n\t\t\tlet rsTag = \"对话\";\n\t\t\tif (\"undefined\" != typeof(tagData[\"defaultFlag\"]) && \"true\" == tagData[\"defaultFlag\"]) {\n\t\t\t\trsTag = rsTag + \"(默认)\";\n\t\t\t}\n\t\t\tif (\"undefined\" != typeof(tagData[\"role\"]) && \"\" != tagData[\"role\"]) {\n\t\t\t\tlet roleStr = \"\" + tagData[\"role\"];\n\t\t\t\troleStr = roleStr.replace(/\\n/g, \"\");\n\t\t\t\tif (roleStr.length > 15) {\n\t\t\t\t\troleStr = roleStr.substring(0, 15) + \"..\";\n\t\t\t\t}\n\t\t\t\trsTag = rsTag + \" \" + roleStr;\n\t\t\t}\n\t\t\treturn rsTag;\n\t\t} else {\n\t\t\treturn \"旁白\";\n\t\t}\n\t},\n\n\t//获取tag的index,根据对话id\n\tgetTagIdxByVoiceID(tag, voiceID) {\n\t\tif (\"undefined\" == typeof(tag)) {\n\t\t\treturn -1;\n\t\t}\n\t\tfor (let i = 0; i < tag.length; i++) {\n\t\t\tif (voiceID == tag[i].id) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\tgetTagValueByIndex(tag, index) {\n\t\tif (0 > index || \"undefined\" == typeof(tag)) {\n\t\t\treturn \"\";\n\t\t} else {\n\t\t\treturn tag[index].value;\n\t\t}\n\t},\n\n\t//获取默认对话的id\n\tgetDefaultVoiceID(defaultVoice) {\n\t\tlet voiceIDs = [];\n\t\tfor (idx in defaultVoice) {\n\t\t\tif (\"true\" == defaultVoice[idx].value) {\n\t\t\t\tvoiceIDs.push(defaultVoice[idx]);\n\t\t\t}\n\t\t}\n\t\tif (voiceIDs.length > 0) {\n\t\t\tif (voiceIDs.length == 1) {\n\t\t\t\treturn voiceIDs[0].id;\n\t\t\t} else {\n\t\t\t\treturn voiceIDs[Math.floor(Math.random() * voiceIDs.length)].id;\n\t\t\t}\n\t\t} else {\n\t\t\treturn -1;\n\t\t}\n\t},\n\n\tsetVoiceID(list, id) {\n\t\tlist.forEach((item, index) => {\n\t\t\tif (\"dialogue\" == item.tag) {\n\t\t\t\titem.id = id;\n\t\t\t}\n\t\t});\n\t},\n\n\t//获取用|分隔的全部角色名\n\tgetAllRoleStr(roles) {\n\t\tlet allrole = [];\n\t\tfor (roleIdx in roles) {\n\t\t\tif (\"undefined\" == typeof(roles[roleIdx]) || \"\" == roles[roleIdx].value) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\timportPackage(java.util.regex);\n\t\t\tlet tmpRoleValue = Pattern.compile(\"\\\\n\").matcher(roles[roleIdx].value).replaceAll(\"\");\n\t\t\tif (\"\" == tmpRoleValue) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet tmpRoles = tmpRoleValue.split(\"\\\\|\");\n\t\t\tfor (i in tmpRoles) {\n\t\t\t\tif (allrole.indexOf(tmpRoles[i])) {\n\t\t\t\t\tallrole.push(tmpRoles[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn allrole.join(\"|\");\n\t},\n\n\t//正则匹配结果\n\tgetMatchRegexFalg(roleValue, allroleStr, regexStr, str) {\n\t\tif (\"\" == roleValue || \"\" == regexStr || \"\" == str) {\n\t\t\treturn false;\n\t\t}\n\t\timportPackage(java.util.regex);\n\t\tlet tmpRoleValue = Pattern.compile(\"\\\\n\").matcher(roleValue).replaceAll(\"\");\n\t\tif (\"\" == tmpRoleValue) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet tmpRegexStr = Pattern.compile(\"__ROLE__\").matcher(regexStr).replaceAll(tmpRoleValue);\n\t\t//logger.w(\"allrole:\" + allroleStr);\n\t\tif (\"\" != allroleStr) {\n\t\t\ttmpRegexStr = Pattern.compile(\"__ALLROLE__\").matcher(tmpRegexStr).replaceAll(allroleStr);\n\t\t}\n\n\t\tif (\"\" != tmpRegexStr) {\n\t\t\tif (Pattern.compile(tmpRegexStr).matcher(str).find()) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t},\n\n\t//matchRole(是否旁白在前,旁白,角色, 全部角色名str,正则_旁白在前,正则_旁白在后)\n\tmatchRole(preTextFlag, narrText, roles, allroleStr, fRegexs, bRegexs) {\n\t\tfor (roleIdx in roles) {\n\t\t\tif (\"undefined\" == typeof(roles[roleIdx]) || \"\" == roles[roleIdx].value) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet regex = this.getTagValueByIndex(bRegexs, this.getTagIdxByVoiceID(bRegexs, roles[roleIdx].id));\n\t\t\tif (\"\" == regex) {\n\t\t\t\tregex = narrationBackRegex;\n\t\t\t}\n\t\t\tif (preTextFlag) {\n\t\t\t\tregex = this.getTagValueByIndex(fRegexs, this.getTagIdxByVoiceID(fRegexs, roles[roleIdx].id));\n\t\t\t\tif (\"\" == regex) {\n\t\t\t\t\tregex = narrationFrontRegex;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet flag = this.getMatchRegexFalg(roles[roleIdx].value, allroleStr, regex, narrText);\n\t\t\tif (flag) {\n\t\t\t\treturn roles[roleIdx].id;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\thandleText(text, tagsData) {\n\t\tconst list = [];\n\t\tlet tmpStr = \"\";\n\t\tlet endTag = \"narration\";\n\n\t\ttext.split(\"\").forEach((char, index) => {\n\t\t\ttmpStr += char;\n\n\t\t\tif (char === '“') {\n\t\t\t\tendTag = \"dialogue\";\n\t\t\t\tif (tmpStr != \"“\") {\n\t\t\t\t\ttmpStr = tmpStr.slice(0, -1);\n\t\t\t\t\tlist.push({\n\t\t\t\t\t\ttext: tmpStr,\n\t\t\t\t\t\ttag: \"narration\"\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\ttmpStr = \"\";\n\t\t\t} else if (char === '”') {\n\t\t\t\tendTag = \"narration\";\n\t\t\t\tif (tmpStr != \"”\") {\n\t\t\t\t\ttmpStr = tmpStr.slice(0, -1);\n\t\t\t\t\tlist.push({\n\t\t\t\t\t\ttext: tmpStr,\n\t\t\t\t\t\ttag: \"dialogue\"\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\ttmpStr = \"\";\n\t\t\t} else if (index === text.length - 1) {\n\t\t\t\tlist.push({\n\t\t\t\t\ttext: tmpStr,\n\t\t\t\t\ttag: endTag\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tif (\"undefined\" == typeof(tagsData['dialogue']) || 0 == list.length) {\n\t\t\treturn list;\n\t\t}\n\t\t//logger.w(tagsData['dialogue']);\n\n\t\tlet textStructure = \"\";\n\t\tlet narrationTotal = 0;\n\t\tlet dialogueTotal = 0;\n\t\tlet narrationText = \"\";\n\t\tlist.forEach((item, i) => {\n\t\t\tif (\"dialogue\" == item.tag) {\n\t\t\t\ttextStructure = textStructure + \"d\";\n\t\t\t\tdialogueTotal++;\n\t\t\t} else {\n\t\t\t\ttextStructure = textStructure + \"n\";\n\t\t\t\tnarrationTotal++;\n\t\t\t\tnarrationText = item.text;\n\t\t\t}\n\t\t});\n\n\t\tlet defaultFlags = tagsData['dialogue']['defaultFlag'];\n\t\tlet defaultTtsID = this.getDefaultVoiceID(defaultFlags);\n\n\t\t//有旁白和对话\n\t\tif (narrationTotal > 0 && dialogueTotal > 0) {\n\t\t\tlet roles = tagsData['dialogue']['role'];\n\t\t\tlet fRegexs = tagsData['dialogue']['fRegex'];\n\t\t\tlet bRegexs = tagsData['dialogue']['bRegex'];\n\n\t\t\tlet allroleStr = \"\";\n\t\t\tif (useAllRole) {\n\t\t\t\tallroleStr = this.getAllRoleStr(roles);\n\t\t\t}\n\n\t\t\tlet tmpTtsID = -1;\n\t\t\tif (1 == narrationTotal) {\n\t\t\t\tlet preTextFlag = false;\n\t\t\t\tif (\"n\" == textStructure.charAt(0)) {\n\t\t\t\t\tpreTextFlag = true;\n\t\t\t\t}\n\t\t\t\ttmpTtsID = this.matchRole(preTextFlag, narrationText, roles, allroleStr, fRegexs, bRegexs);\n\t\t\t\tif (-1 == tmpTtsID) {\n\t\t\t\t\ttmpTtsID = this.matchRole(!preTextFlag, narrationText, roles, allroleStr, fRegexs, bRegexs);\n\t\t\t\t}\n\t\t\t\tif (-1 != tmpTtsID) {\n\t\t\t\t\tthis.setVoiceID(list, tmpTtsID);\n\t\t\t\t} else if (-1 != defaultTtsID) {\n\t\t\t\t\tthis.setVoiceID(list, defaultTtsID);\n\t\t\t\t}\n\t\t\t} else if (\"ndn\" == textStructure) {\n\t\t\t\ttmpTtsID = this.matchRole(true, list[0].text, roles, allroleStr, fRegexs, bRegexs);\n\t\t\t\tif (-1 == tmpTtsID) {\n\t\t\t\t\ttmpTtsID = this.matchRole(false, list[2].text, roles, allroleStr, fRegexs, bRegexs);\n\t\t\t\t}\n\t\t\t\tif (-1 != tmpTtsID) {\n\t\t\t\t\tlist[1].id = tmpTtsID;\n\t\t\t\t} else if (-1 != defaultTtsID) {\n\t\t\t\t\tlist[1].id = defaultTtsID;\n\t\t\t\t}\n\t\t\t} else if (\"ndnd\" == textStructure) {\n\t\t\t\ttmpTtsID = this.matchRole(true, list[0].text, roles, allroleStr, fRegexs, bRegexs);\n\t\t\t\tif (-1 != tmpTtsID) {\n\t\t\t\t\tlist[1].id = tmpTtsID;\n\t\t\t\t} else if (-1 != defaultTtsID) {\n\t\t\t\t\tlist[1].id = defaultTtsID;\n\t\t\t\t}\n\n\t\t\t\ttmpTtsID = this.matchRole(true, list[2].text, roles, allroleStr, fRegexs, bRegexs);\n\t\t\t\tif (-1 != tmpTtsID) {\n\t\t\t\t\tlist[3].id = tmpTtsID;\n\t\t\t\t} else if (-1 != defaultTtsID) {\n\t\t\t\t\tlist[3].id = defaultTtsID;\n\t\t\t\t}\n\t\t\t} else if (\"dndn\" == textStructure) {\n\t\t\t\ttmpTtsID = this.matchRole(false, list[1].text, roles, allroleStr, fRegexs, bRegexs);\n\t\t\t\tif (-1 != tmpTtsID) {\n\t\t\t\t\tlist[0].id = tmpTtsID;\n\t\t\t\t} else if (-1 != defaultTtsID) {\n\t\t\t\t\tlist[0].id = defaultTtsID;\n\t\t\t\t}\n\n\t\t\t\ttmpTtsID = this.matchRole(false, list[3].text, roles, allroleStr, fRegexs, bRegexs);\n\t\t\t\tif (-1 != tmpTtsID) {\n\t\t\t\t\tlist[2].id = tmpTtsID;\n\t\t\t\t} else if (-1 != defaultTtsID) {\n\t\t\t\t\tlist[2].id = defaultTtsID;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t//默认对话\n\t\t\t\tif (-1 != defaultTtsID) {\n\t\t\t\t\tthis.setVoiceID(list, defaultTtsID);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t//只有对话\n\t\tif (dialogueTotal > 0 && 0 == narrationTotal) {\n\t\t\tif (-1 != defaultTtsID) {\n\t\t\t\tthis.setVoiceID(list, defaultTtsID);\n\t\t\t}\n\t\t}\n\n\t\treturn list;\n\t},\n\n\tsplitText(text) {\n\t\tlet separatorStr = \"。??!!;;\"\n\n\t\tlet list = []\n\t\tlet tmpStr = \"\"\n\t\ttext.split(\"\").forEach((char, index) => {\n\t\t\ttmpStr += char\n\n\t\t\tif (separatorStr.includes(char)) {\n\t\t\t\tlist.push(tmpStr)\n\t\t\t\ttmpStr = \"\"\n\t\t\t} else if (index === text.length - 1) {\n\t\t\t\tlist.push(tmpStr);\n\t\t\t}\n\t\t})\n\n\t\treturn list.filter(item => item.replace(/[“”]/g, '').trim().length > 0);\n\t}\n\n};",
"tags": {
"narration": "旁白",
"dialogue": "对话"
},
"tagsData": {
"dialogue": {
"role": {
"label": "匹配角色名",
"hint": "可以|分隔"
},
"fRegex": {
"label": "正则(旁白在前)",
"hint": "在旁白中匹配,可用__ROLE__代替角色名"
},
"bRegex": {
"label": "正则(旁白在后)",
"hint": "在旁白中匹配,可用__ROLE__代替角色名"
},
"defaultFlag": {
"label": "作为默认对话",
"hint": "",
"items": "{true: \"是\", false: \"否\"}",
"default": "false"
}
}
}
}
]