先说明一下背景: 近日,公司在自研TTS服务的阿拉伯数字转换成越南语音频的功能,在网上查询了相关资料,没有发现可直接使用的库或者代码片段。于是,自己就开发了一套。事后,经过整理、总结分享给有相同需求的同学。
TTS 就是文本转语音,语音合成(Text To Speech)。 在现实生活中,随处可见的智能设备(天猫精灵、小爱同学等)要想说话就需要用到TTS了,相当于嘴。
转换流程 经过分析发现,阿拉伯数字转换成越南语音频,大致需要两个步骤:
先把数字转换成越南语词组序列
再把越南词组序列转换成对应的音频
第一步的实现是本文讲解的重点。 第二步的实现由于涉及机密问题,就说一下大致思路:把第一步中获得的越南词组序列映射到事先录好的基准词音频,然后使用拼接方法,把这些基准词音频拼接在一起即可。
所谓基准词音频
就是指转换成的词组序列中的最小单位对应的音频。 例如:123,汉语读做一百二十三
,其中的1、2、3
为最小的不可拆分的词,这些词对应的音频就叫做基准词音频。百、十
则是单位。使用基准词+单位
进行拼接就可以得到最终的结果。例如:123
就是一+百+二+十+三
。 123 越南语词组序列:[‘một’, ‘trăm’, ‘hai’, ‘mươi’, ‘ba’]
越南语的基准词 0~10 及 部分单位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int_pronounce_map = [ "không" , "một" , "hai" , "ba" , "bốn" , "năm" , "sáu" , "bảy" , "tám" , "chín" , "mười" , ] units = { "mười" , "mươi" "trăm" , "nghìn" , "triệu" , "tỷ" , }
注意:越南语是存在音调的,例如:10的发音为 “mười”,20~90中的10发音为 “mươi”。仔细看他们,是有区别的哦!
越南语特点 咨询了一些懂得越南语的同事得知,越南语使用的应该是类似拉丁语系的字符构成。其数字读法有别于汉语读法,但是有一部分又类似汉语读法。
越南语一到四位数的读法与汉语相同,例如:11=10+1,读作mười một
但有几个数量词在与其他数量词组合时发生音变, 1 至 19 的数字只有 15 有变音,15 不读作mười năm
,而是读作mười lăm
。
一到四位数读法 虽说越南语一到四位数的读法与汉语相同,但1001,汉语会省略百位不读
,读成:一千零一。而越南语不会省略百位
,读作một nghìn không trăm linh một
(一千零百零一,即一千零一)。
注意:越南语可以省略十位不读,例如:101(一百零一)读作một trăm linh một。
五位数及以上读法 越南语一到四位数读法与汉语相同,五位数及以上读法与汉语就不同,反而是与英语的读数规则相同。
越南语四位数以后不用“万”、“亿”为计算单位,而是用千(nghìn
)、百万(triệu
)、十亿(tỷ
)为计算单位。
例如:87000,此时越南语5位数字读法与英语相似,可以以三位数为一个段位对其进行分段,即为87,000 (英语读法: eighty-seven thousand
)读作:tám mươi bảy nghìn
(八十七千,即八万七千)。
同理,可分段为 987,000 (英语读法:nine hundred and eighty-seven thousand
)读作:chín trăm tám mươi bảy nghìn
注意:英语在百位与十位之间要加 and,而越南语都不用
一些特殊读法 0的特殊读法 多位数中有零时,越南语用 không
、 linh
或 lẻ
表达。 一般的读法是, 零在十位上读 linh
(比较常用linh
) 或 lẻ
,零在百位上读 không
。
例如: 303(汉语读法:三百零三) 可读作 ba trăm linh ba
1001(汉语读法:一千零一) 可读作 một nghìn không trăm linh một
(一千零百零一,即一千零一)
10的特殊读法 mười
(十)在 hai
(二)到 chín
(九)这些数量词之后变成 mươi
(十)。
例如: 20(汉语读法:二十)读作 hai mươi
,而不读作 hai mười
90(汉语读法:九十)要读作 chín mươi
而不读作 chín mười
1的特殊读法 một
在 mươi
之后变成 mốt
(即与mươi
结合使用时,“一”读作mốt
)
例如: 21(汉语读法:二十一)读作 hai mươi mốt
,而不读作 hai mươi một
4的特殊读法 bốn
(四) 在 mươi
(十)之后读成 bốn
或 tư
(tư
较为常用)即:与mươi
(十)结合使用时,读作 bốn
或tư
)
例如: 24(汉语读法:二十四)可读作 hai mươi tư
5的特殊读法 năm
(五)在 mười
(十)之后要变成 lăm
,在 mươi
(十)之后要变成 lăm
(比较常用 lăm
)或 nhăm
(与mươi
(十)或 mười
(十)结合使用时,(五)均可读作 lăm
。
例如: 15(汉语读法:十五)读作 mười lăm
25(汉语读法:二十五)可说 hai mươi lăm
而不说 hai mươi năm
65(汉语读法:六十五)可说 sáu mươi lăm
而不说 sáu mươi năm
代码实现 基于以上规则以及特殊读法,实现了相关逻辑。代码使用Python开发,具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 import reint_pronounce_map = [ "không" , "một" , "hai" , "ba" , "bốn" , "năm" , "sáu" , "bảy" , "tám" , "chín" , "mười" , ] def can_convert_to_float (text ): try : float (text) return True except ValueError: return False def is_num_enable (num ): if num >= (10 ** 12 ): return False return True def cut_text (text, lens ): text_arr = re.findall('.{' + str (lens) + '}' , text) last = text[(len (text_arr) * lens):] if len (last) > 0 : text_arr.append(last) return text_arr def str_rev (strings: str ): return strings[::-1 ] def is_lens_three (num_string ): return True if len (num_string) == 3 else False def number_to_word (num_string: str ): lens_number = len (num_string) if lens_number > 3 : raise Exception(f"number:{num_string} error, lens more than 3" ) is_len_3 = is_lens_three(num_string) int_number = int (num_string) if int_number == 0 : return [] if int_number <= 9 : words_list = ["không" , "trăm" , "linh" ] if is_len_3 else [] words_list += one_digital(int_number) return words_list if 10 <= int_number <= 99 : words_list = ["không" , "trăm" ] if is_len_3 else [] words_list += two_digital(int_number) return words_list hundreds = int_number // 100 exclude_hundreds = int_number % 100 words_list = [int_pronounce_map[hundreds], "trăm" ] if 1 <= exclude_hundreds <= 9 : words_list.append("linh" ) words_list.append(int_pronounce_map[exclude_hundreds]) return words_list words_list += two_digital(exclude_hundreds) return words_list def one_digital (int_number ): return [int_pronounce_map[int_number]] def two_digital (int_number ): if int_number == 0 : return [] if 0 < int_number <= 10 : return [int_pronounce_map[int_number]] decade = int_number // 10 mod = int_number % 10 if decade == 1 : lists = ["mười" , "lăm" if mod == 5 else int_pronounce_map[mod]] return lists if 2 <= decade <= 9 : lists = [int_pronounce_map[decade], "mươi" ] if mod == 1 : lists.append("mốt" ) elif mod == 4 : lists.append("tư" ) elif mod == 5 : lists.append("lăm" ) else : if mod != 0 : lists.append(int_pronounce_map[mod]) return lists def get_units (item_count ): if item_count == 1 : return [] if item_count == 2 : return ["nghìn" ] if item_count == 3 : return ["triệu" , "nghìn" ] if item_count == 4 : return ["tỷ" , "triệu" , "nghìn" ] raise Exception("number too big, is not support" ) def convert_number_2_vietnam_words (num: str ): if not is_num_enable(int (num)): return None if len (num) > 3 : str_arr = cut_text(str_rev(num), 3 ) str_arr = str_arr[::-1 ] for inx, str_number in enumerate (str_arr): str_arr[inx] = str_number[::-1 ] else : str_arr = [num] ret_words = [] units = get_units(len (str_arr)) for inx, str_number in enumerate (str_arr): words_list = number_to_word(str_number) if len (words_list) > 0 : ret_words += words_list if 0 < len (units) and inx < len (units): ret_words.append(units[inx]) print (f"{num} :{str_arr} words:{ret_words} \n" ) return ret_words if __name__ == "__main__" : convert_number_2_vietnam_words("1234567889" )
上面代码直接复制下来,在Python环境中直接运行就会得到数字 1234567889 对应的序列。
1 1234567889 :['1' , '234' , '567' , '889' ] words:['một' , 'tỷ' , 'hai' , 'trăm' , 'ba' , 'mươi' , 'tư' , 'triệu' , 'năm' , 'trăm' , 'sáu' , 'mươi' , 'bảy' , 'nghìn' , 'tám' , 'trăm' , 'tám' , 'mươi' , 'chín' ]
当然,你也可以把 1234567889 改成你想要的数字。感兴趣的话,来测试一下吧。