lenML/ChatTTS-Forge
301
1import html2import re3 4import emojiswitch5import ftfy6 7from modules import models8from modules.utils.detect_lang import guess_lang9from modules.utils.HomophonesReplacer import HomophonesReplacer10from modules.utils.html import remove_html_tags as _remove_html_tags11from modules.utils.markdown import markdown_to_text12from modules.utils.zh_normalization.text_normlization import TextNormalizer13 14# 是否关闭 unk token 检查15# NOTE: 单测的时候用于跳过模型加载16DISABLE_UNK_TOKEN_CHECK = False17 18 19post_normalize_pipeline = []20pre_normalize_pipeline = []21 22 23def post_normalize():24 def decorator(func):25 post_normalize_pipeline.append(func)26 return func27 28 return decorator29 30 31def pre_normalize():32 def decorator(func):33 pre_normalize_pipeline.append(func)34 return func35 36 return decorator37 38 39def apply_pre_normalize(text):40 for func in pre_normalize_pipeline:41 text = func(text)42 return text43 44 45def apply_post_normalize(text):46 for func in post_normalize_pipeline:47 text = func(text)48 return text49 50 51def is_markdown(text):52 markdown_patterns = [53 r"(^|\s)#[^#]", # 标题54 r"\*\*.*?\*\*", # 加粗55 r"\*.*?\*", # 斜体56 r"!\[.*?\]\(.*?\)", # 图片57 r"\[.*?\]\(.*?\)", # 链接58 r"`[^`]+`", # 行内代码59 r"```[\s\S]*?```", # 代码块60 r"(^|\s)\* ", # 无序列表61 r"(^|\s)\d+\. ", # 有序列表62 r"(^|\s)> ", # 引用63 r"(^|\s)---", # 分隔线64 ]65 66 for pattern in markdown_patterns:67 if re.search(pattern, text, re.MULTILINE):68 return True69 70 return False71 72 73character_map = {74 ":": ",",75 ";": ",",76 "!": "。",77 "(": ",",78 ")": ",",79 "【": ",",80 "】": ",",81 "『": ",",82 "』": ",",83 "「": ",",84 "」": ",",85 "《": ",",86 "》": ",",87 "-": ",",88 "‘": " ",89 "“": " ",90 "’": " ",91 "”": " ",92 '"': " ",93 "'": " ",94 ":": ",",95 ";": ",",96 "!": ".",97 "(": ",",98 ")": ",",99 "[": ",",100 "]": ",",101 ">": ",",102 "<": ",",103 "-": ",",104 "~": " ",105 "~": " ",106 "/": " ",107 "·": " ",108}109 110character_to_word = {111 " & ": " and ",112}113 114## ---------- post normalize ----------115 116 117@post_normalize()118def apply_character_to_word(text):119 for k, v in character_to_word.items():120 text = text.replace(k, v)121 return text122 123 124@post_normalize()125def apply_character_map(text):126 translation_table = str.maketrans(character_map)127 return text.translate(translation_table)128 129 130@post_normalize()131def apply_emoji_map(text):132 lang = guess_lang(text)133 return emojiswitch.demojize(text, delimiters=("", ""), lang=lang)134 135 136@post_normalize()137def insert_spaces_between_uppercase(s):138 # 使用正则表达式在每个相邻的大写字母之间插入空格139 return re.sub(140 r"(?<=[A-Z])(?=[A-Z])|(?<=[a-z])(?=[A-Z])|(?<=[\u4e00-\u9fa5])(?=[A-Z])|(?<=[A-Z])(?=[\u4e00-\u9fa5])",141 " ",142 s,143 )144 145 146@post_normalize()147def replace_unk_tokens(text):148 """149 把不在字典里的字符替换为 " , "150 """151 if DISABLE_UNK_TOKEN_CHECK:152 return text153 chat_tts = models.load_chat_tts()154 if "tokenizer" not in chat_tts.pretrain_models:155 # 这个地方只有在 huggingface spaces 中才会触发156 # 因为 hugggingface 自动处理模型卸载加载,所以如果拿不到就算了...157 return text158 tokenizer = chat_tts.pretrain_models["tokenizer"]159 vocab = tokenizer.get_vocab()160 vocab_set = set(vocab.keys())161 # 添加所有英语字符162 vocab_set.update(set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"))163 vocab_set.update(set(" \n\r\t"))164 replaced_chars = [char if char in vocab_set else " , " for char in text]165 output_text = "".join(replaced_chars)166 return output_text167 168 169homo_replacer = HomophonesReplacer(map_file_path="./data/homophones_map.json")170 171 172@post_normalize()173def replace_homophones(text):174 lang = guess_lang(text)175 if lang == "zh":176 text = homo_replacer.replace(text)177 return text178 179 180## ---------- pre normalize ----------181 182 183@pre_normalize()184def html_unescape(text):185 text = html.unescape(text)186 text = html.unescape(text)187 return text188 189 190@pre_normalize()191def fix_text(text):192 return ftfy.fix_text(text=text)193 194 195@pre_normalize()196def apply_markdown_to_text(text):197 if is_markdown(text):198 text = markdown_to_text(text)199 return text200 201 202@pre_normalize()203def remove_html_tags(text):204 return _remove_html_tags(text)205 206 207# 将 "xxx" => \nxxx\n208# 将 'xxx' => \nxxx\n209@pre_normalize()210def replace_quotes(text):211 repl = r"\n\1\n"212 patterns = [213 ['"', '"'],214 ["'", "'"],215 ["“", "”"],216 ["‘", "’"],217 ]218 for p in patterns:219 text = re.sub(rf"({p[0]}[^{p[0]}{p[1]}]+?{p[1]})", repl, text)220 return text221 222 223def ensure_suffix(a: str, b: str, c: str):224 a = a.strip()225 if not a.endswith(b):226 a += c227 return a228 229 230email_domain_map = {231 "outlook.com": "Out look",232 "hotmail.com": "Hot mail",233 "yahoo.com": "雅虎",234}235 236 237# 找到所有 email 并将 name 分割为单个字母,@替换为 at ,. 替换为 dot,常见域名替换为单词238#239# 例如:240# zhzluke96@outlook.com => z h z l u k e 9 6 at out look dot com241def email_detect(text):242 email_pattern = re.compile(r"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})")243 244 def replace(match):245 email = match.group(1)246 name, domain = email.split("@")247 name = " ".join(name)248 if domain in email_domain_map:249 domain = email_domain_map[domain]250 domain = domain.replace(".", " dot ")251 return f"{name} at {domain}"252 253 return email_pattern.sub(replace, text)254 255 256def sentence_normalize(sentence_text: str):257 # https://github.com/PaddlePaddle/PaddleSpeech/tree/develop/paddlespeech/t2s/frontend/zh_normalization258 tx = TextNormalizer()259 260 # 匹配 \[.+?\] 的部分261 pattern = re.compile(r"(\[.+?\])|([^[]+)")262 263 def normalize_part(part):264 sentences = tx.normalize(part) if guess_lang(part) == "zh" else [part]265 dest_text = ""266 for sentence in sentences:267 sentence = apply_post_normalize(sentence)268 dest_text += sentence269 return dest_text270 271 def replace(match):272 if match.group(1):273 return f" {match.group(1)} "274 else:275 return normalize_part(match.group(2))276 277 result = pattern.sub(replace, sentence_text)278 279 # NOTE: 加了会有杂音...280 # if is_end:281 # 加这个是为了防止吞字282 # result = ensure_suffix(result, "[uv_break]", "。。。[uv_break]。。。")283 284 return result285 286 287def text_normalize(text, is_end=False):288 text = apply_pre_normalize(text)289 lines = text.split("\n")290 lines = [line.strip() for line in lines]291 lines = [line for line in lines if line]292 lines = [sentence_normalize(line) for line in lines]293 content = "\n".join(lines)294 return content295 296 297if __name__ == "__main__":298 from modules.devices import devices299 300 devices.reset_device()301 test_cases = [302 "ChatTTS是专门为对话场景设计的文本转语音模型,例如LLM助手对话任务。它支持英文和中文两种语言。最大的模型使用了10万小时以上的中英文数据进行训练。在HuggingFace中开源的版本为4万小时训练且未SFT的版本.",303 " [oral_9] [laugh_0] [break_0] 电 [speed_0] 影 [speed_0] 中 梁朝伟 [speed_9] 扮演的陈永仁的编号27149",304 " 明天有62%的概率降雨",305 "大🍌,一条大🍌,嘿,你的感觉真的很奇妙 [lbreak]",306 "I like eating 🍏",307 """308# 你好,世界309```js310console.log('1')311```312**加粗**313 314*一条文本*315 """,316 """317在沙漠、岩石、雪地上行走了很长的时间以后,小王子终于发现了一条大路。所有的大路都是通往人住的地方的。318“你们好。”小王子说。319这是一个玫瑰盛开的花园。320“你好。”玫瑰花说道。321小王子瞅着这些花,它们全都和他的那朵花一样。322“你们是什么花?”小王子惊奇地问。323“我们是玫瑰花。”花儿们说道。324“啊!”小王子说……。325 """,326 """327State-of-the-art Machine Learning for PyTorch, TensorFlow, and JAX.328 329🤗 Transformers provides APIs and tools to easily download and train state-of-the-art pretrained models. Using pretrained models can reduce your compute costs, carbon footprint, and save you the time and resources required to train a model from scratch. These models support common tasks in different modalities, such as:330 331📝 Natural Language Processing: text classification, named entity recognition, question answering, language modeling, summarization, translation, multiple choice, and text generation.332🖼️ Computer Vision: image classification, object detection, and segmentation.333🗣️ Audio: automatic speech recognition and audio classification.334🐙 Multimodal: table question answering, optical character recognition, information extraction from scanned documents, video classification, and visual question answering.335 """,336 """337120米338有12%的概率会下雨339埃隆·马斯克340""",341 ]342 343 for i, test_case in enumerate(test_cases):344 print(f"case {i}:\n", {"x": text_normalize(test_case, is_end=True)})345 