lenML/ChatTTS-Forge
301
1# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7# http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14"""15Rules to verbalize numbers into Chinese characters.16https://zh.wikipedia.org/wiki/中文数字#現代中文17"""18import re19from collections import OrderedDict20from typing import List21 22DIGITS = {str(i): tran for i, tran in enumerate("零一二三四五六七八九")}23UNITS = OrderedDict(24 {25 1: "十",26 2: "百",27 3: "千",28 4: "万",29 8: "亿",30 }31)32 33COM_QUANTIFIERS = "(封|艘|把|目|套|段|人|所|朵|匹|张|座|回|场|尾|条|个|首|阙|阵|网|炮|顶|丘|棵|只|支|袭|辆|挑|担|颗|壳|窠|曲|墙|群|腔|砣|座|客|贯|扎|捆|刀|令|打|手|罗|坡|山|岭|江|溪|钟|队|单|双|对|出|口|头|脚|板|跳|枝|件|贴|针|线|管|名|位|身|堂|课|本|页|家|户|层|丝|毫|厘|分|钱|两|斤|担|铢|石|钧|锱|忽|(千|毫|微)克|毫|厘|(公)分|分|寸|尺|丈|里|寻|常|铺|程|(千|分|厘|毫|微)米|米|撮|勺|合|升|斗|石|盘|碗|碟|叠|桶|笼|盆|盒|杯|钟|斛|锅|簋|篮|盘|桶|罐|瓶|壶|卮|盏|箩|箱|煲|啖|袋|钵|年|月|日|季|刻|时|周|天|秒|分|小时|旬|纪|岁|世|更|夜|春|夏|秋|冬|代|伏|辈|丸|泡|粒|颗|幢|堆|条|根|支|道|面|片|张|颗|块|元|(亿|千万|百万|万|千|百)|(亿|千万|百万|万|千|百|美|)元|(亿|千万|百万|万|千|百|十|)吨|(亿|千万|百万|万|千|百|)块|角|毛|分)"34 35# 分数表达式36RE_FRAC = re.compile(r"(-?)(\d+)/(\d+)")37 38 39def replace_frac(match) -> str:40 """41 Args:42 match (re.Match)43 Returns:44 str45 """46 sign = match.group(1)47 nominator = match.group(2)48 denominator = match.group(3)49 try:50 import cn2an51 52 return cn2an.an2cn(f"{sign}{nominator}/{denominator}", "low")53 except:54 sign: str = "负" if sign else ""55 nominator: str = num2str(nominator)56 denominator: str = num2str(denominator)57 result = f"{sign}{denominator}分之{nominator}"58 return result59 60 61# 百分数表达式62RE_PERCENTAGE = re.compile(r"(-?)(\d+(\.\d+)?)(%|%)")63 64 65def replace_percentage(match) -> str:66 """67 Args:68 match (re.Match)69 Returns:70 str71 """72 sign = match.group(1)73 percent = match.group(2)74 try:75 import cn2an76 77 return cn2an.an2cn(f"{sign}{percent}%", "low")78 except:79 sign: str = "负" if sign else ""80 percent: str = num2str(percent)81 result = f"{sign}百分之{percent}"82 return result83 84 85# 整数表达式86# 带负号的整数 -1087RE_INTEGER = re.compile(r"(-)" r"(\d+)")88 89 90def replace_negative_num(match) -> str:91 """92 Args:93 match (re.Match)94 Returns:95 str96 """97 sign = match.group(1)98 number = match.group(2)99 try:100 import cn2an101 102 return cn2an.an2cn(f"{sign}{number}", "low")103 except:104 sign: str = "负" if sign else ""105 number: str = num2str(number)106 result = f"{sign}{number}"107 return result108 109 110# 编号-无符号整形111# 00078112RE_DEFAULT_NUM = re.compile(r"\d{3}\d*")113 114 115def replace_default_num(match):116 """117 Args:118 match (re.Match)119 Returns:120 str121 """122 number = match.group(0)123 try:124 import cn2an125 126 return cn2an.an2cn(number, "low")127 except:128 return verbalize_digit(number, alt_one=True)129 130 131# 数字表达式132# 纯小数133RE_DECIMAL_NUM = re.compile(r"(-?)((\d+)(\.\d+))" r"|(\.(\d+))")134# 正整数 + 量词135RE_POSITIVE_QUANTIFIERS = re.compile(r"(\d+)([多余几\+])?" + COM_QUANTIFIERS)136RE_NUMBER = re.compile(r"(-?)((\d+)(\.\d+)?)" r"|(\.(\d+))")137 138 139def replace_positive_quantifier(match) -> str:140 """141 Args:142 match (re.Match)143 Returns:144 str145 """146 number = match.group(1)147 match_2 = match.group(2)148 if match_2 == "+":149 match_2 = "多"150 match_2: str = match_2 if match_2 else ""151 quantifiers: str = match.group(3)152 number: str = num2str(number)153 result = f"{number}{match_2}{quantifiers}"154 return result155 156 157def replace_number(match) -> str:158 """159 Args:160 match (re.Match)161 Returns:162 str163 """164 sign = match.group(1)165 number = match.group(2)166 pure_decimal = match.group(5)167 168 # TODO 也许可以把 num2str 完全替换成 cn2an169 import cn2an170 171 text = pure_decimal if pure_decimal else f"{sign}{number}"172 try:173 result = cn2an.an2cn(text, "low")174 except ValueError:175 if pure_decimal:176 result = num2str(pure_decimal)177 else:178 sign: str = "负" if sign else ""179 number: str = num2str(number)180 result = f"{sign}{number}"181 return result182 183 184# 范围表达式185# match.group(1) and match.group(8) are copy from RE_NUMBER186 187RE_RANGE = re.compile(188 r"((-?)((\d+)(\.\d+)?)|(\.(\d+)))[-~]((-?)((\d+)(\.\d+)?)|(\.(\d+)))"189)190 191 192def replace_range(match) -> str:193 """194 Args:195 match (re.Match)196 Returns:197 str198 """199 first, second = match.group(1), match.group(8)200 first = RE_NUMBER.sub(replace_number, first)201 second = RE_NUMBER.sub(replace_number, second)202 result = f"{first}到{second}"203 return result204 205 206def _get_value(value_string: str, use_zero: bool = True) -> List[str]:207 stripped = value_string.lstrip("0")208 if len(stripped) == 0:209 return []210 elif len(stripped) == 1:211 if use_zero and len(stripped) < len(value_string):212 return [DIGITS["0"], DIGITS[stripped]]213 else:214 return [DIGITS[stripped]]215 else:216 largest_unit = next(217 power for power in reversed(UNITS.keys()) if power < len(stripped)218 )219 first_part = value_string[:-largest_unit]220 second_part = value_string[-largest_unit:]221 return _get_value(first_part) + [UNITS[largest_unit]] + _get_value(second_part)222 223 224def verbalize_cardinal(value_string: str) -> str:225 if not value_string:226 return ""227 228 # 000 -> '零' , 0 -> '零'229 value_string = value_string.lstrip("0")230 if len(value_string) == 0:231 return DIGITS["0"]232 233 result_symbols = _get_value(value_string)234 # verbalized number starting with '一十*' is abbreviated as `十*`235 if (236 len(result_symbols) >= 2237 and result_symbols[0] == DIGITS["1"]238 and result_symbols[1] == UNITS[1]239 ):240 result_symbols = result_symbols[1:]241 return "".join(result_symbols)242 243 244def verbalize_digit(value_string: str, alt_one=False) -> str:245 result_symbols = [DIGITS[digit] for digit in value_string]246 result = "".join(result_symbols)247 if alt_one:248 result = result.replace("一", "幺")249 return result250 251 252def num2str(value_string: str) -> str:253 integer_decimal = value_string.split(".")254 if len(integer_decimal) == 1:255 integer = integer_decimal[0]256 decimal = ""257 elif len(integer_decimal) == 2:258 integer, decimal = integer_decimal259 else:260 raise ValueError(261 f"The value string: '${value_string}' has more than one point in it."262 )263 264 result = verbalize_cardinal(integer)265 266 decimal = decimal.rstrip("0")267 if decimal:268 # '.22' is verbalized as '零点二二'269 # '3.20' is verbalized as '三点二270 result = result if result else "零"271 result += "点" + verbalize_digit(decimal)272 return result273 