#include "chinese_g2p.hpp" extern std::unordered_map rep_map = { {":", ","}, {";", ","}, {",", ","}, {"。", "."}, {"!", "!"}, {"?", "?"}, {"\n", "."}, {"·", ","}, {"、", ","}, {"...", "…"}, {"$", "."}, {"“", "'"}, {"”", "'"}, {"‘", "'"}, {"’", "'"}, {"(", "'"}, {")", "'"}, {"(", "'"}, {")", "'"}, {"《", "'"}, {"》", "'"}, {"【", "'"}, {"】", "'"}, {"[", "'"}, {"]", "'"}, {"—", "-"}, {"~", "-"}, {"~", "-"}, {"「", "\""}, {"」", "\""}}; std::vector punctuation = {"!", "?", ",", ".", "\"", "-", "…", "【", "】", "", ":", "「", "」", "'", "“"}; ChineseG2P::ChineseG2P(const std::string &local_resource_root) : pinyin_(local_resource_root), word_spliter_(WordSpliter::GetInstance(local_resource_root)), tone_adjuster_(local_resource_root) { auto t0 = clk::now(); PLOG(INFO, "ChineseG2P 开始初始化..."); resource_root_ = local_resource_root; an2cn_ = An2Cn(); // pinyin_.AddCustomPhrases(custom_pharse); auto pinyin_to_symbol_bin_path = resource_root_ + "common/text_processing_jsons/pinyin_to_symbol_map.bin"; LoadBinToMap(pinyin_to_symbol_bin_path, pinyin_to_symbol_map_); auto hotwords_bin_path = resource_root_ + "common/text_processing_jsons/hotwords_cn.bin"; LoadHotwordsFromBin(hotwords_bin_path); // 创建映射 int id = 0; for (auto &symbol : symbols) { _symbol_to_id[symbol] = id++; } auto t1 = clk::now(); auto d1 = std::chrono::duration_cast(t1 - t0); PLOG(INFO, "ChineseG2P 初始化成功, timecost: " + std::to_string(d1.count()) + "ms"); } void ChineseG2P::ParseHotwordsCNFile(const std::string &hotwords_cn_json_path) { auto json_obj = LoadJson(hotwords_cn_json_path); std::vector hot_word_list; // 由于hotwords_cn.json文件中还有别的类型的数据,这里只获取我们想要的pinyin字段 for (auto &item : json_obj) { std::string cur_word = item["hotword"]; hot_word_list.push_back(cur_word); std::vector initials = SplitString(item["initials"], ','); std::vector finals = SplitString(item["finals"], ','); custom_phone_[cur_word] = std::make_tuple(initials, finals); } } void ChineseG2P::SaveHotwordsToBin(const std::string &filename) { std::ofstream ofs(filename, std::ios::binary); if (!ofs) { throw std::runtime_error("Unable to open file for writing"); } // 写入热词数量 size_t size = custom_phone_.size(); ofs.write(reinterpret_cast(&size), sizeof(size)); // 写入每个热词及其拼音信息 for (const auto &pair : custom_phone_) { const std::string &hotword = pair.first; const auto &phonemes = pair.second; const auto &initials = std::get<0>(phonemes); const auto &finals = std::get<1>(phonemes); // 写入热词长度和内容 size_t hotword_size = hotword.size(); ofs.write(reinterpret_cast(&hotword_size), sizeof(hotword_size)); ofs.write(hotword.c_str(), hotword_size); // 写入声母 size_t initials_size = initials.size(); ofs.write(reinterpret_cast(&initials_size), sizeof(initials_size)); for (const auto &initial : initials) { size_t initial_size = initial.size(); ofs.write(reinterpret_cast(&initial_size), sizeof(initial_size)); ofs.write(initial.c_str(), initial_size); } // 写入韵母 size_t finals_size = finals.size(); ofs.write(reinterpret_cast(&finals_size), sizeof(finals_size)); for (const auto &final : finals) { size_t final_size = final.size(); ofs.write(reinterpret_cast(&final_size), sizeof(final_size)); ofs.write(final.c_str(), final_size); } } ofs.close(); } void ChineseG2P::LoadHotwordsFromBin(const std::string &filename) { std::ifstream ifs(filename, std::ios::binary); if (!ifs) { throw std::runtime_error("Unable to open file for reading"); } size_t size; ifs.read(reinterpret_cast(&size), sizeof(size)); // 读取热词数量 for (size_t i = 0; i < size; ++i) { // 读取热词 size_t hotword_size; ifs.read(reinterpret_cast(&hotword_size), sizeof(hotword_size)); std::string hotword(hotword_size, '\0'); ifs.read(&hotword[0], hotword_size); // 读取声母 size_t initials_size; ifs.read(reinterpret_cast(&initials_size), sizeof(initials_size)); std::vector initials(initials_size); for (size_t j = 0; j < initials_size; ++j) { size_t initial_size; ifs.read(reinterpret_cast(&initial_size), sizeof(initial_size)); initials[j].resize(initial_size); ifs.read(&initials[j][0], initial_size); } // 读取韵母 size_t finals_size; ifs.read(reinterpret_cast(&finals_size), sizeof(finals_size)); std::vector finals(finals_size); for (size_t j = 0; j < finals_size; ++j) { size_t final_size; ifs.read(reinterpret_cast(&final_size), sizeof(final_size)); finals[j].resize(final_size); ifs.read(&finals[j][0], final_size); } custom_phone_[hotword] = std::make_tuple(initials, finals); } ifs.close(); } void ChineseG2P::ParsePinyToSymbolJsonFile(const std::string &pinyin_json_path) { auto json_obj = LoadJson(pinyin_json_path); // 将json对象转换为std::map pinyin_to_symbol_map_ = json_obj.get(); SaveJsonToBin("pinyin_to_symbol_map.bin", pinyin_to_symbol_map_); } g2p_data ChineseG2P::ConvertInitialFinalToPhoneTone( const std::vector &initials, const std::vector &finals) { std::vector phones_list; std::vector tones_list; std::vector word2ph; for (size_t i = 0; i < initials.size(); ++i) { assert(finals.size() > i); // Ensure there are enough finals const std::string &c = initials[i]; const std::string &v = finals[i]; std::string raw_pinyin = c + v; // NOTE: post process for pypinyin outputs // we discriminate i, ii and iii if (c == v) { phones_list.push_back(std::string(1, c[0])); tones_list.push_back(0); word2ph.push_back(1); } else { std::string v_without_tone = v.substr(0, v.size() - 1); char tone = v.back(); assert(std::string("12345").find(tone) != std::string::npos); std::string pinyin = c + v_without_tone; if (!c.empty()) { // 多音节 std::unordered_map v_rep_map = {{"uei", "ui"}, {"iou", "iu"}, {"uen", "un"}}; auto it = v_rep_map.find(v_without_tone); if (it != v_rep_map.end()) { pinyin = c + it->second; } } else { // 单音节 std::unordered_map pinyin_rep_map = { {"ing", "ying"}, {"i", "yi"}, {"in", "yin"}, {"u", "wu"}}; auto it = pinyin_rep_map.find(pinyin); if (it != pinyin_rep_map.end()) { pinyin = it->second; } else { std::unordered_map single_rep_map = { {'v', "yu"}, {'e', "e"}, {'i', "y"}, {'u', "w"}}; auto it2 = single_rep_map.find(pinyin[0]); if (it2 != single_rep_map.end()) { pinyin = it2->second + pinyin.substr(1); } } } std::vector phone = SplitString(pinyin_to_symbol_map_[pinyin], ' '); word2ph.push_back(phone.size()); for (const auto &ph : phone) { phones_list.push_back(ph); } tones_list.insert(tones_list.end(), phone.size(), tone - '0'); } } return std::make_tuple(phones_list, tones_list, word2ph); } std::string ChineseG2P::ReplacePunctuations(const std::string &text) { std::string modified_text = text; // 将英文的"..."替换为"…" StrReplaceAll(modified_text, "...", "…"); // // 替换先定义的字符映射 for (const auto &pair : rep_map) { auto word_splits = SplitUtf8String(modified_text); if (HanListContains(word_splits, pair.first)) { StrReplaceAll(modified_text, pair.first, pair.second); } } return modified_text; } std::string ChineseG2P::TextAn2cn(std::string &text) { // 使用正则表达式查找匹配的数字 std::regex pattern(R"(\d+(?:\.?\d+)?)"); std::sregex_iterator iter(text.begin(), text.end(), pattern); std::sregex_iterator end; std::vector numbers; // 将找到的数字存储到 vector 中 while (iter != end) { numbers.push_back(iter->str()); ++iter; } // 替换每个数字 for (const std::string &number : numbers) { text.replace(text.find(number), number.length(), an2cn_.Process(number)); } return text; } // Function to normalize text by converting numbers and replacing punctuation. std::string ChineseG2P::TextNormalize(const std::string &text) { std::string normalized_text = text; PLOG(PDEBUG, "g2p text_norm 输入: " + normalized_text); // 进行阿拉伯数字到汉字数字的转换 normalized_text = TextAn2cn(normalized_text); PLOG(PDEBUG, "g2p text_norm an2cn后: " + normalized_text); // 替换中文标点符号为英文标点符号 normalized_text = ReplacePunctuations(normalized_text); PLOG(PDEBUG, "g2p text_norm 替换标点后: " + normalized_text); return normalized_text; } g2p_data ChineseG2P::G2P(std::string &text) { PLOG(PDEBUG, "g2p 输入句子: " + text); std::vector phones; std::vector tones; std::vector word2ph; // text是从原始句子中切出来的中文部分,可能包含标点符号,如 '你好啊.你是,我的男朋友?' // sentences是根据标点符号切分的小的分句,标点符号跟随前面的文本,如 ['你好啊.', '你是,', '我的男朋友?'] auto segments = SplitChineseSentenceToParts(text); PLOG(PDEBUG, "g2p 标点分句: " + ConcatStrList(segments, "|")); std::tie(phones, tones, word2ph) = G2PInternal(segments); // Prepend and append special tokens to the lists phones.insert(phones.begin(), "_"); phones.push_back("_"); tones.insert(tones.begin(), 0); tones.push_back(0); word2ph.insert(word2ph.begin(), 1); word2ph.push_back(1); return std::make_tuple(phones, tones, word2ph); } g2p_data ChineseG2P::G2PInternal(const std::vector &segments) { std::vector all_initials; std::vector all_finals; std::vector phones; std::vector tones; std::vector word2ph; // 对每个标点符号分割的小分句进行分词,并返回对应的词性,如 ['你好啊'] -> [[(‘你好', 'l‘), ('啊', 'zg')]] for (auto &seg : segments) { PLOG(TRACE, DELIMITER_LINE); PLOG(TRACE, "g2p processing seg " + seg); // 保存当前分句的声母和韵母 std::vector initials; std::vector finals; // 返回值类型: vector auto seg_cut_list = word_spliter_.Process(seg); PLOG(TRACE, "g2p 分词结果: " + ConcatList(seg_cut_list, "|")); // 计算拼音 for (auto &[word, pos] : seg_cut_list) { PLOG(TRACE, "g2p 开始处理词语: " + word + "/" + pos); // eng表示英文字母和数字 if (pos == "eng") { PLOG(TRACE, "g2p 是英文和数字: " + word); continue; } // 如果词语在自定义词表里面,采用自定义的发音 if (custom_phone_.find(word) != custom_phone_.end()) { PLOG(TRACE, "g2p 是自定义词语: " + word); auto [sub_initials, sub_finals] = custom_phone_[word]; initials.insert(initials.end(), sub_initials.begin(), sub_initials.end()); finals.insert(finals.end(), sub_finals.begin(), sub_finals.end()); } else { PLOG(TRACE, "g2p 提取拼音: " + word); auto [sub_initials, sub_finals] = pinyin_.Process(word); PLOG(TRACE, "g2p 原始声母: " + ConcatStrList(sub_initials, "|")); PLOG(TRACE, "g2p 原始韵母: " + ConcatStrList(sub_finals), "|"); sub_finals = tone_adjuster_.Process(word, pos, sub_finals); PLOG(TRACE, "g2p 修改声调后韵母: " + ConcatStrList(sub_finals)); initials.insert(initials.end(), sub_initials.begin(), sub_initials.end()); finals.insert(finals.end(), sub_finals.begin(), sub_finals.end()); } } PLOG(TRACE, "g2p 该seg全部声母: " + ConcatStrList(initials, "|")); PLOG(TRACE, "g2p 该seg全部韵母: " + ConcatStrList(finals, "|")); all_initials.insert(all_initials.end(), initials.begin(), initials.end()); all_finals.insert(all_finals.end(), finals.begin(), finals.end()); // 将拼音转换为音素和音调 auto [cur_phones, cur_tones, cur_word2ph] = ConvertInitialFinalToPhoneTone(initials, finals); phones.insert(phones.end(), cur_phones.begin(), cur_phones.end()); tones.insert(tones.end(), cur_tones.begin(), cur_tones.end()); word2ph.insert(word2ph.end(), cur_word2ph.begin(), cur_word2ph.end()); PLOG(TRACE, DELIMITER_LINE); } PLOG(PDEBUG, "g2p 整句内容: " + ConcatStrList(segments, "|")); PLOG(PDEBUG, "g2p 整句声母: " + ConcatStrList(all_initials, "|")); PLOG(PDEBUG, "g2p 整句韵母: " + ConcatStrList(all_finals, "|")); PLOG(PDEBUG, "g2p 整句phones: " + ConcatStrList(phones, "|")); PLOG(PDEBUG, "g2p 整句tones: " + ConcatIntList(tones, "|")); PLOG(PDEBUG, "g2p 整句word2ph: " + ConcatIntList(word2ph, "|")); return make_tuple(phones, tones, word2ph); } phone_data ChineseG2P::CleanedTextToSequence(const g2p_data &g2p_data_, const std::string &language) { auto phones = std::get<0>(g2p_data_); auto tones = std::get<1>(g2p_data_); auto word2ph = std::get<2>(g2p_data_); std::vector int_phones; for (auto &symbol : phones) { // 假设 symbol 在 _symbol_to_id 中一定能找到。 int_phones.push_back(_symbol_to_id[symbol]); } int tone_start = language_tone_start_map[language]; std::vector adjusted_tones; for (int tone : tones) { adjusted_tones.push_back(tone + tone_start); } std::vector lang_ids; for (int i = 0; i < int_phones.size(); i++) { lang_ids.push_back(language_id_map[language]); } // 增加空白 int_phones = Intersperse(int_phones, 0); adjusted_tones = Intersperse(adjusted_tones, 0); lang_ids = Intersperse(lang_ids, 0); // 下面的循环操作将每个元素值翻倍,然后将第一个元素增加1 for (size_t i = 0; i < word2ph.size(); ++i) { word2ph[i] = word2ph[i] * 2; } word2ph[0] += 1; PLOG(PDEBUG, "g2p 调整后phones(size=" + std::to_string(int_phones.size()) + "):" + ConcatIntList(int_phones, "|")); PLOG(PDEBUG, "g2p 调整后tones(size=" + std::to_string(adjusted_tones.size()) + "):" + ConcatIntList(adjusted_tones, "|")); PLOG(PDEBUG, "g2p 调整后word2ph(size=" + std::to_string(word2ph.size()) + "):" + ConcatIntList(word2ph, "|")); PLOG(PDEBUG, "g2p lang_ids(size=" + std::to_string(lang_ids.size()) + "):" + ConcatIntList(lang_ids, "|")); return std::make_tuple(int_phones, adjusted_tones, lang_ids, word2ph); } std::tuple ChineseG2P::Process(SentLangPair &sent_lang) { PLOG(PDEBUG, "g2p 输入文本: " + sent_lang.sent); auto norm_text = TextNormalize(sent_lang.sent); PLOG(PDEBUG, "g2p norm后文本: " + norm_text); auto g2p_res = G2P(norm_text); auto res = CleanedTextToSequence(g2p_res, sent_lang.lang); return std::make_tuple(norm_text, res); }