Spaces:
Running
Running
Download cpp/src/text_normalizer.cpp from Cong123779/matcha-tts: direct link, hf CLI and curl.
- Browser
- Download file 24.7 kB
-
https://huggingface.co/spaces/Cong123779/matcha-tts/resolve/main/cpp/src/text_normalizer.cpp
- Command line
-
hf download hf://spaces/Cong123779/matcha-tts/cpp/src/text_normalizer.cpp
-
curl -L -o text_normalizer.cpp https://huggingface.co/spaces/Cong123779/matcha-tts/resolve/main/cpp/src/text_normalizer.cpp
24.7 kB
| namespace matcha { | |
| static const std::vector<std::string> UNITS = { | |
| "không", "một", "hai", "ba", "bốn", "năm", "sáu", "bảy", "tám", "chín" | |
| }; | |
| // 1. Đọc số nguyên tiếng Việt chuẩn ngôn ngữ học | |
| std::string TextNormalizer::read_integer(int64_t n) { | |
| if (n < 0) { | |
| return "âm " + read_integer(-n); | |
| } | |
| if (n < 10) { | |
| return UNITS[n]; | |
| } | |
| if (n < 100) { | |
| int64_t ten = n / 10; | |
| int64_t unit = n % 10; | |
| std::string ten_str = (ten == 1) ? "mười" : (UNITS[ten] + " mươi"); | |
| if (unit == 0) return ten_str; | |
| if (unit == 1) return (ten == 1) ? (ten_str + " một") : (ten_str + " mốt"); | |
| if (unit == 4) return (ten == 1) ? (ten_str + " bốn") : (ten_str + " tư"); | |
| if (unit == 5) return ten_str + " lăm"; | |
| return ten_str + " " + UNITS[unit]; | |
| } | |
| if (n < 1000) { | |
| int64_t hundred = n / 100; | |
| int64_t rem = n % 100; | |
| std::string hundred_str = UNITS[hundred] + " trăm"; | |
| if (rem == 0) return hundred_str; | |
| if (rem < 10) return hundred_str + " lẻ " + UNITS[rem]; | |
| return hundred_str + " " + read_integer(rem); | |
| } | |
| if (n < 1000000) { | |
| int64_t thousand = n / 1000; | |
| int64_t rem = n % 1000; | |
| std::string thousand_str = read_integer(thousand) + " ngàn"; | |
| if (rem == 0) return thousand_str; | |
| if (rem < 10) return thousand_str + " không trăm lẻ " + read_integer(rem); | |
| if (rem < 100) return thousand_str + " không trăm " + read_integer(rem); | |
| return thousand_str + " " + read_integer(rem); | |
| } | |
| if (n < 1000000000) { | |
| int64_t million = n / 1000000; | |
| int64_t rem = n % 1000000; | |
| std::string million_str = read_integer(million) + " triệu"; | |
| if (rem == 0) return million_str; | |
| if (rem < 10) return million_str + " không trăm lẻ " + read_integer(rem); | |
| if (rem < 1000) return million_str + " không trăm " + read_integer(rem); | |
| return million_str + " " + read_integer(rem); | |
| } | |
| // Hàng tỷ trở lên | |
| int64_t billion = n / 1000000000; | |
| int64_t rem = n % 1000000000; | |
| std::string billion_str = read_integer(billion) + " tỷ"; | |
| if (rem == 0) return billion_str; | |
| if (rem < 10) return billion_str + " không trăm lẻ " + read_integer(rem); | |
| if (rem < 1000) return billion_str + " không trăm " + read_integer(rem); | |
| return billion_str + " " + read_integer(rem); | |
| } | |
| // 2. Đọc từng chữ số rời rạc | |
| std::string TextNormalizer::read_digits_one_by_one(const std::string& digit_str) { | |
| std::string res; | |
| for (char c : digit_str) { | |
| if (c >= '0' && c <= '9') { | |
| if (!res.empty()) res += " "; | |
| res += UNITS[c - '0']; | |
| } | |
| } | |
| return res; | |
| } | |
| static inline bool is_utf8_continuation_or_lead(unsigned char c) { | |
| return c >= 0x80; | |
| } | |
| static inline bool is_word_char_utf8(const std::string& text, size_t pos) { | |
| if (pos >= text.size()) return false; | |
| unsigned char c = static_cast<unsigned char>(text[pos]); | |
| if (c >= 0x80) return true; // Thuộc ký tự đa byte UTF-8 | |
| return std::isalnum(c) || c == '_'; | |
| } | |
| // 3. Quy đổi chữ cái Latin đơn độc lập (V, A, a, d, g...) bảo toàn 100% tiếng Việt UTF-8 | |
| std::string TextNormalizer::replace_isolated_latin_letters(const std::string& text) { | |
| static const std::unordered_map<char, std::string> LATIN_MAP = { | |
| {'A', "a"}, {'B', "bê"}, {'C', "xê"}, {'D', "dê"}, {'E', "ee"}, | |
| {'F', "ép"}, {'G', "gờ"}, {'H', "hắt"}, {'I', "i"}, {'J', "gi"}, | |
| {'K', "ca"}, {'L', "lờ"}, {'M', "mờ"}, {'N', "nờ"}, {'O', "o"}, | |
| {'P', "pê"}, {'Q', "quy"}, {'R', "rờ"}, {'S', "ét"}, {'T', "tê"}, | |
| {'U', "uu"}, {'V', "vê"}, {'W', "vê kép"}, {'X', "ích"}, {'Y', "y"}, {'Z', "zét"} | |
| }; | |
| std::string result; | |
| result.reserve(text.size() * 2); | |
| for (size_t i = 0; i < text.size(); ++i) { | |
| char c = text[i]; | |
| unsigned char uc = static_cast<unsigned char>(c); | |
| if (std::isalpha(uc) && uc < 0x80) { | |
| bool prev_is_word = (i > 0) && is_word_char_utf8(text, i - 1); | |
| bool next_is_word = (i + 1 < text.size()) && is_word_char_utf8(text, i + 1); | |
| if (!prev_is_word && !next_is_word) { | |
| // Đây là chữ cái Latin đứng hoàn toàn độc lập! | |
| char upper_c = static_cast<char>(std::toupper(uc)); | |
| auto it = LATIN_MAP.find(upper_c); | |
| if (it != LATIN_MAP.end()) { | |
| result += it->second; | |
| continue; | |
| } | |
| } | |
| } | |
| result += c; | |
| } | |
| return result; | |
| } | |
| // 4. Quy đổi đơn vị đo lường, viết tắt công nghệ & ký hiệu nước ngoài | |
| std::string TextNormalizer::translate_foreign_symbols_and_words(const std::string& input_text) { | |
| std::string text = input_text; | |
| // A. Bảng đơn vị đo lường & tiền tệ nước ngoài | |
| static const std::vector<std::pair<std::regex, std::string>> UNITS_MAP = { | |
| { std::regex(R"((\d|\s|^)km/h\b)", std::regex_constants::icase), "$1 ki lô mét trên giờ" }, | |
| { std::regex(R"((\d|\s|^)km/giờ\b)", std::regex_constants::icase), "$1 ki lô mét trên giờ" }, | |
| { std::regex(R"((\d|\s|^)m/s\b)", std::regex_constants::icase), "$1 mét trên giây" }, | |
| { std::regex(R"((\d|\s|^)MB/s\b)", std::regex_constants::icase), "$1 mê ga bai trên giây" }, | |
| { std::regex(R"((\d|\s|^)GB/s\b)", std::regex_constants::icase), "$1 ghi ga bai trên giây" }, | |
| { std::regex(R"((\d|\s|^)KB/s\b)", std::regex_constants::icase), "$1 ki lô bai trên giây" }, | |
| { std::regex(R"((\d|\s|^)km\b)", std::regex_constants::icase), "$1 ki lô mét" }, | |
| { std::regex(R"((\d|\s|^)cm\b)", std::regex_constants::icase), "$1 xen ti mét" }, | |
| { std::regex(R"((\d|\s|^)mm\b)", std::regex_constants::icase), "$1 mi li mét" }, | |
| { std::regex(R"((\d|\s|^)kg\b)", std::regex_constants::icase), "$1 ki lô gam" }, | |
| { std::regex(R"((\d|\s|^)ml\b)", std::regex_constants::icase), "$1 mi li lít" }, | |
| { std::regex(R"((\d|\s|^)kWh\b)", std::regex_constants::icase), "$1 ki lô oát giờ" }, | |
| { std::regex(R"((\d|\s|^)kW\b)", std::regex_constants::icase), "$1 ki lô oát" }, | |
| { std::regex(R"((\d|\s|^)mW\b)", std::regex_constants::icase), "$1 mê ga oát" }, | |
| { std::regex(R"((\d|\s|^)GHz\b)", std::regex_constants::icase), "$1 ghi ga hét" }, | |
| { std::regex(R"((\d|\s|^)MHz\b)", std::regex_constants::icase), "$1 mê ga hét" }, | |
| { std::regex(R"((\d|\s|^)kHz\b)", std::regex_constants::icase), "$1 ki lô hét" }, | |
| { std::regex(R"((\d|\s|^)Hz\b)", std::regex_constants::icase), "$1 hét" }, | |
| { std::regex(R"((\d|\s|^)TB\b)", std::regex_constants::icase), "$1 tê ra bai" }, | |
| { std::regex(R"((\d|\s|^)GB\b)", std::regex_constants::icase), "$1 ghi ga bai" }, | |
| { std::regex(R"((\d|\s|^)MB\b)", std::regex_constants::icase), "$1 mê ga bai" }, | |
| { std::regex(R"((\d|\s|^)KB\b)", std::regex_constants::icase), "$1 ki lô bai" }, | |
| { std::regex(R"((\d|\s|^)USD\b)", std::regex_constants::icase), "$1 đô la" }, | |
| { std::regex(R"((\d|\s|^)EUR\b)", std::regex_constants::icase), "$1 ơ rô" }, | |
| { std::regex(R"((\d|\s|^)GBP\b)", std::regex_constants::icase), "$1 bảng anh" }, | |
| { std::regex(R"((\d|\s|^)JPY\b)", std::regex_constants::icase), "$1 yên Nhật" }, | |
| { std::regex(R"((\d|\s|^)VND\b)", std::regex_constants::icase), "$1 đồng" }, | |
| { std::regex(R"((\d|\s|^)VNĐ\b)", std::regex_constants::icase), "$1 đồng" } | |
| }; | |
| for (const auto& kv : UNITS_MAP) { | |
| text = std::regex_replace(text, kv.first, kv.second); | |
| } | |
| // B. Bảng viết tắt công nghệ & thuật ngữ thông dụng | |
| static const std::vector<std::pair<std::regex, std::string>> ACRONYMS_MAP = { | |
| { std::regex(R"(\bAI\b)"), "a i" }, | |
| { std::regex(R"(\bAPI\b)"), "a p i" }, | |
| { std::regex(R"(\bIP\b)"), "i p" }, | |
| { std::regex(R"(\bCPU\b)"), "c p u" }, | |
| { std::regex(R"(\bRAM\b)"), "ram" }, | |
| { std::regex(R"(\bROM\b)"), "rom" }, | |
| { std::regex(R"(\bUSB\b)"), "u s b" }, | |
| { std::regex(R"(\bSMS\b)"), "s m s" }, | |
| { std::regex(R"(\bOTP\b)"), "o t p" }, | |
| { std::regex(R"(\bVIP\b)"), "v i p" }, | |
| { std::regex(R"(\bCEO\b)"), "c e o" }, | |
| { std::regex(R"(\bCFO\b)"), "c f o" }, | |
| { std::regex(R"(\bPR\b)"), "p r" }, | |
| { std::regex(R"(\bOK\b)"), "ô kê" }, | |
| { std::regex(R"(\bID\b)"), "i d" }, | |
| { std::regex(R"(\bURL\b)"), "u r l" }, | |
| { std::regex(R"(\bHTML\b)"), "h t m l" }, | |
| { std::regex(R"(\bCSS\b)"), "c s s" }, | |
| { std::regex(R"(\bJS\b)"), "j s" }, | |
| { std::regex(R"(\bPDF\b)"), "p d f" }, | |
| { std::regex(R"(\bMP3\b)"), "m p 3" }, | |
| { std::regex(R"(\bMP4\b)"), "m p 4" }, | |
| { std::regex(R"(\bWiFi\b)", std::regex_constants::icase), "oai phai" }, | |
| { std::regex(R"(\bBluetooth\b)", std::regex_constants::icase), "blum tút" }, | |
| { std::regex(R"(\bApp\b)", std::regex_constants::icase), "áp" }, | |
| { std::regex(R"(\bWebsite\b)", std::regex_constants::icase), "web site" }, | |
| { std::regex(R"(\bFacebook\b)", std::regex_constants::icase), "phây bút" }, | |
| { std::regex(R"(\bYoutube\b)", std::regex_constants::icase), "yêu tuýp" }, | |
| { std::regex(R"(\bGoogle\b)", std::regex_constants::icase), "gồ gồ" } | |
| }; | |
| for (const auto& kv : ACRONYMS_MAP) { | |
| text = std::regex_replace(text, kv.first, kv.second); | |
| } | |
| // C. Quy đổi chữ cái Latin đơn độc lập | |
| text = replace_isolated_latin_letters(text); | |
| // D. Ký tự đặc biệt toán học | |
| static const std::vector<std::pair<std::string, std::string>> SYMBOL_MAP = { | |
| { "@", " a còng " }, { "#", " thăng " }, { "$", " đô la " }, | |
| { "€", " ơ rô " }, { "£", " bảng anh " }, { "¥", " yên " }, { "₩", " won " }, | |
| { "%", " phần trăm " }, { "^", " mũ " }, { "&", " và " }, | |
| { "*", " sao " }, { "+", " cộng " }, { "=", " bằng " }, | |
| { "<", " nhỏ hơn " }, { ">", " lớn hơn " }, { "|", " hoặc " }, | |
| { "~", " khoảng " }, { "/", " xuyệt " }, { "\\", " xuyệt ngược " }, | |
| { "_", " gạch dưới " } | |
| }; | |
| for (const auto& kv : SYMBOL_MAP) { | |
| size_t pos = 0; | |
| while ((pos = text.find(kv.first, pos)) != std::string::npos) { | |
| text.replace(pos, kv.first.length(), kv.second); | |
| pos += kv.second.length(); | |
| } | |
| } | |
| return text; | |
| } | |
| // 5. Chuẩn hóa tiếng Việt toàn diện (Số, ngày, giờ, tiền, phân số, sđt) | |
| std::string TextNormalizer::normalize_vietnamese_text(const std::string& input_text) { | |
| if (input_text.empty()) return ""; | |
| std::string text = input_text; | |
| // A. Ngày / Tháng / Năm: 15/08/2026 hoặc 15-8-2026 | |
| { | |
| std::regex re(R"(\b(\d{1,2})[/.-](\d{1,2})[/.-](\d{4})\b)"); | |
| std::string res; | |
| auto it = std::sregex_iterator(text.begin(), text.end(), re); | |
| auto end = std::sregex_iterator(); | |
| size_t last = 0; | |
| for (; it != end; ++it) { | |
| res += text.substr(last, it->position() - last); | |
| int day = std::stoi((*it)[1].str()); | |
| int month = std::stoi((*it)[2].str()); | |
| int year = std::stoi((*it)[3].str()); | |
| res += read_integer(day) + " tháng " + read_integer(month) + " năm " + read_integer(year); | |
| last = it->position() + it->length(); | |
| } | |
| res += text.substr(last); | |
| text = res; | |
| } | |
| // B. Giờ giấc: 15h30, 8h00, 8:30 | |
| { | |
| std::regex re(R"(\b(\d{1,2})h(\d{1,2})?\b)", std::regex_constants::icase); | |
| std::string res; | |
| auto it = std::sregex_iterator(text.begin(), text.end(), re); | |
| auto end = std::sregex_iterator(); | |
| size_t last = 0; | |
| for (; it != end; ++it) { | |
| res += text.substr(last, it->position() - last); | |
| int hour = std::stoi((*it)[1].str()); | |
| int min = (*it)[2].matched ? std::stoi((*it)[2].str()) : 0; | |
| res += read_integer(hour) + " giờ" + (min > 0 ? (" " + read_integer(min) + " phút") : ""); | |
| last = it->position() + it->length(); | |
| } | |
| res += text.substr(last); | |
| text = res; | |
| } | |
| { | |
| std::regex re(R"(\b(\d{1,2}):(\d{2})\b)"); | |
| std::string res; | |
| auto it = std::sregex_iterator(text.begin(), text.end(), re); | |
| auto end = std::sregex_iterator(); | |
| size_t last = 0; | |
| for (; it != end; ++it) { | |
| res += text.substr(last, it->position() - last); | |
| int hour = std::stoi((*it)[1].str()); | |
| int min = std::stoi((*it)[2].str()); | |
| res += read_integer(hour) + " giờ " + read_integer(min) + " phút"; | |
| last = it->position() + it->length(); | |
| } | |
| res += text.substr(last); | |
| text = res; | |
| } | |
| // C. Số điện thoại (Bắt đầu bằng 0 hoặc +84, 9-11 số có định dạng) | |
| { | |
| std::regex re(R"((?:\+84|0)[0-9]{2,3}[.\s-]?[0-9]{3}[.\s-]?[0-9]{3,4}\b)"); | |
| std::string res; | |
| auto it = std::sregex_iterator(text.begin(), text.end(), re); | |
| auto end = std::sregex_iterator(); | |
| size_t last = 0; | |
| for (; it != end; ++it) { | |
| res += text.substr(last, it->position() - last); | |
| res += read_digits_one_by_one(it->str()); | |
| last = it->position() + it->length(); | |
| } | |
| res += text.substr(last); | |
| text = res; | |
| } | |
| // D. CCCD / CMND / STK (10 - 14 chữ số liền nhau) | |
| { | |
| std::regex re(R"(\b\d{10,14}\b)"); | |
| std::string res; | |
| auto it = std::sregex_iterator(text.begin(), text.end(), re); | |
| auto end = std::sregex_iterator(); | |
| size_t last = 0; | |
| for (; it != end; ++it) { | |
| res += text.substr(last, it->position() - last); | |
| res += read_digits_one_by_one(it->str()); | |
| last = it->position() + it->length(); | |
| } | |
| res += text.substr(last); | |
| text = res; | |
| } | |
| // E. Biển số xe: 30A-12345 | |
| { | |
| std::regex re(R"(\b(\d{1,3})([A-Za-z]{1,2})[-.\s]?(\d{4,5})\b)"); | |
| std::string res; | |
| auto it = std::sregex_iterator(text.begin(), text.end(), re); | |
| auto end = std::sregex_iterator(); | |
| size_t last = 0; | |
| for (; it != end; ++it) { | |
| res += text.substr(last, it->position() - last); | |
| int p = std::stoi((*it)[1].str()); | |
| std::string let = (*it)[2].str(); | |
| std::string code = (*it)[3].str(); | |
| res += read_integer(p) + " " + let + " " + read_digits_one_by_one(code); | |
| last = it->position() + it->length(); | |
| } | |
| res += text.substr(last); | |
| text = res; | |
| } | |
| // F. Mã chữ-số: OTP-4567, HD999 | |
| { | |
| std::regex re(R"(\b([A-Za-z]{1,3})[-.\s]?(\d{3,6})\b)"); | |
| std::string res; | |
| auto it = std::sregex_iterator(text.begin(), text.end(), re); | |
| auto end = std::sregex_iterator(); | |
| size_t last = 0; | |
| for (; it != end; ++it) { | |
| res += text.substr(last, it->position() - last); | |
| std::string prefix = (*it)[1].str(); | |
| std::string digits = (*it)[2].str(); | |
| res += prefix + " " + read_digits_one_by_one(digits); | |
| last = it->position() + it->length(); | |
| } | |
| res += text.substr(last); | |
| text = res; | |
| } | |
| // G. Phân số: 3/4, 1/2 | |
| { | |
| std::regex re(R"(\b(\d+)/(\d+)\b)"); | |
| std::string res; | |
| auto it = std::sregex_iterator(text.begin(), text.end(), re); | |
| auto end = std::sregex_iterator(); | |
| size_t last = 0; | |
| for (; it != end; ++it) { | |
| res += text.substr(last, it->position() - last); | |
| int64_t num = std::stoll((*it)[1].str()); | |
| int64_t den = std::stoll((*it)[2].str()); | |
| std::string den_str = (den == 4) ? "tư" : read_integer(den); | |
| res += read_integer(num) + " phần " + den_str; | |
| last = it->position() + it->length(); | |
| } | |
| res += text.substr(last); | |
| text = res; | |
| } | |
| // H. Tiền tệ có dấu chấm phân cách hàng ngàn: 1.250.000 hoặc 500.000 | |
| { | |
| std::regex re(R"(\b\d{1,3}(?:\.\d{3})+\b)"); | |
| std::string res; | |
| auto it = std::sregex_iterator(text.begin(), text.end(), re); | |
| auto end = std::sregex_iterator(); | |
| size_t last = 0; | |
| for (; it != end; ++it) { | |
| res += text.substr(last, it->position() - last); | |
| std::string num_str = it->str(); | |
| std::string clean_num; | |
| for (char c : num_str) { | |
| if (c != '.') clean_num += c; | |
| } | |
| try { | |
| int64_t val = std::stoll(clean_num); | |
| res += read_integer(val); | |
| } catch (...) { | |
| res += num_str; | |
| } | |
| last = it->position() + it->length(); | |
| } | |
| res += text.substr(last); | |
| text = res; | |
| } | |
| // I. Nhiệt độ: 36.5°C hoặc 36.5 độ C | |
| { | |
| static const std::string deg_c_utf8 = "°C"; | |
| size_t pos = 0; | |
| while ((pos = text.find(deg_c_utf8, pos)) != std::string::npos) { | |
| text.replace(pos, deg_c_utf8.length(), " độ C"); | |
| pos += 5; | |
| } | |
| } | |
| // J. Tiền tệ viết tắt: 100k, 100tr, 100.000đ, $50 | |
| { | |
| text = std::regex_replace(text, std::regex(R"((\d+)\s*k(?=[\s\.,;!?:/\-\)]|$))", std::regex_constants::icase), "$1 ngàn"); | |
| text = std::regex_replace(text, std::regex(R"((\d+)\s*tr(?=[\s\.,;!?:/\-\)]|$))", std::regex_constants::icase), "$1 triệu"); | |
| text = std::regex_replace(text, std::regex(R"((\d+)\s*đ(?=[\s\.,;!?:/\-\)]|$))", std::regex_constants::icase), "$1 đồng"); | |
| text = std::regex_replace(text, std::regex(R"(\$\s*(\d+))"), "$1 đô la"); | |
| text = std::regex_replace(text, std::regex(R"((\d+)\s*\$)"), "$1 đô la"); | |
| } | |
| // K. Số thập phân: 1.5 hoặc 1,5 | |
| { | |
| std::regex re(R"(\b(\d+)[.,](\d{1,4})\b)"); | |
| std::string res; | |
| auto it = std::sregex_iterator(text.begin(), text.end(), re); | |
| auto end = std::sregex_iterator(); | |
| size_t last = 0; | |
| for (; it != end; ++it) { | |
| res += text.substr(last, it->position() - last); | |
| int64_t whole = std::stoll((*it)[1].str()); | |
| std::string dec = (*it)[2].str(); | |
| res += read_integer(whole) + " phẩy " + read_digits_one_by_one(dec); | |
| last = it->position() + it->length(); | |
| } | |
| res += text.substr(last); | |
| text = res; | |
| } | |
| // L. Quy đổi ký hiệu & thuật ngữ nước ngoài trước (để 16GB -> 16 ghi ga bai) | |
| text = translate_foreign_symbols_and_words(text); | |
| // L. Số nguyên thông thường: 10 -> mười, 14 -> mười bốn, 24 -> hai mươi tư, 1005 -> một ngàn không trăm lẻ năm | |
| { | |
| std::regex re(R"(\b\d+\b)"); | |
| std::string res; | |
| auto it = std::sregex_iterator(text.begin(), text.end(), re); | |
| auto end = std::sregex_iterator(); | |
| size_t last = 0; | |
| for (; it != end; ++it) { | |
| res += text.substr(last, it->position() - last); | |
| try { | |
| int64_t val = std::stoll(it->str()); | |
| res += read_integer(val); | |
| } catch (...) { | |
| res += it->str(); | |
| } | |
| last = it->position() + it->length(); | |
| } | |
| res += text.substr(last); | |
| text = res; | |
| } | |
| // M. Chuẩn hóa khoảng trắng và dấu câu | |
| text = std::regex_replace(text, std::regex(R"(\s+([.,?!:;]))"), "$1"); | |
| text = std::regex_replace(text, std::regex(R"(([.,?!:;])(?=[^\s]))"), "$1 "); | |
| text = std::regex_replace(text, std::regex(R"([ \t]+)"), " "); | |
| return text; | |
| } | |
| // 6. Làm sạch văn bản hoàn chỉnh trước khi chuyển thành Symbol IDs | |
| std::string TextNormalizer::clean_and_normalize( | |
| const std::string& raw_text, | |
| const std::unordered_set<std::string>& allowed_symbols | |
| ) { | |
| if (raw_text.empty()) return ""; | |
| // 1. Chạy qua bộ chuẩn hóa số và từ nước ngoài | |
| std::string text = normalize_vietnamese_text(raw_text); | |
| // 2. Loại bỏ các dấu mở ngoặc / đóng ngoặc / ngoặc kép không cần thiết (Bảo toàn 100% tiếng Việt UTF-8) | |
| static const std::vector<std::string> MULTIBYTE_BRACKETS = { | |
| "«", "»", "“", "”", "„", "”" | |
| }; | |
| for (const auto& b : MULTIBYTE_BRACKETS) { | |
| size_t pos = 0; | |
| while ((pos = text.find(b, pos)) != std::string::npos) { | |
| text.erase(pos, b.length()); | |
| } | |
| } | |
| // ASCII brackets: " ' ` ( ) [ ] { } | |
| static const std::regex ascii_bracket_re(R"([\"\'\(\)\[\]\{\}`])"); | |
| text = std::regex_replace(text, ascii_bracket_re, ""); | |
| // 3. Chuẩn hóa khoảng trắng | |
| text = std::regex_replace(text, std::regex(R"([ \t]+)"), " "); | |
| // 4. Nếu có allowed_symbols, lọc các ký tự nằm ngoài bộ symbols | |
| if (!allowed_symbols.empty()) { | |
| std::string filtered; | |
| for (size_t i = 0; i < text.size(); ) { | |
| unsigned char c = text[i]; | |
| size_t len = 1; | |
| if ((c & 0x80) == 0) len = 1; | |
| else if ((c & 0xE0) == 0xC0) len = 2; | |
| else if ((c & 0xF0) == 0xE0) len = 3; | |
| else if ((c & 0xF8) == 0xF0) len = 4; | |
| if (i + len <= text.size()) { | |
| std::string ch = text.substr(i, len); | |
| if (ch == "\n" || ch == " " || allowed_symbols.count(ch) > 0) { | |
| filtered += ch; | |
| } else { | |
| // Thử chuyển sang chữ thường nếu là ký tự Latin | |
| char lower_ch = static_cast<char>(std::tolower(ch[0])); | |
| std::string lower_str(1, lower_ch); | |
| if (allowed_symbols.count(lower_str) > 0) { | |
| filtered += lower_str; | |
| } | |
| } | |
| } | |
| i += len; | |
| } | |
| text = filtered; | |
| } | |
| text = std::regex_replace(text, std::regex(R"([ \t]+)"), " "); | |
| return text; | |
| } | |
| // 7. Nạp cấu hình ngắt nghỉ từ JSON | |
| PauseConfig TextNormalizer::load_pause_config(const std::string& filepath) { | |
| PauseConfig cfg; | |
| std::ifstream f(filepath); | |
| if (!f.is_open()) { | |
| return cfg; // Dùng cấu hình mặc định | |
| } | |
| std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); | |
| auto extract_float = [&](const std::string& key, float default_val) -> float { | |
| std::regex re(R"(")" + key + R"(":\s*([0-9.]+))"); | |
| std::smatch match; | |
| if (std::regex_search(content, match, re)) { | |
| try { | |
| return std::stof(match[1].str()); | |
| } catch (...) {} | |
| } | |
| return default_val; | |
| }; | |
| cfg.para = extract_float("para", cfg.para); | |
| cfg.ellipsis = extract_float("ellipsis", cfg.ellipsis); | |
| cfg.period = extract_float("period", cfg.period); | |
| cfg.comma = extract_float("comma", cfg.comma); | |
| cfg.question = extract_float("question", cfg.question); | |
| cfg.exclaim = extract_float("exclaim", cfg.exclaim); | |
| cfg.colon = extract_float("colon", cfg.colon); | |
| cfg.semicolon = extract_float("semicolon", cfg.semicolon); | |
| return cfg; | |
| } | |
| // 8. Tính toán khoảng nghỉ theo dấu câu kết thúc | |
| float TextNormalizer::get_pause_duration(const std::string& sentence, const PauseConfig& cfg) { | |
| if (sentence.empty()) return cfg.period; | |
| int idx = static_cast<int>(sentence.size()) - 1; | |
| while (idx >= 0 && (sentence[idx] == ' ' || sentence[idx] == '\t' || sentence[idx] == '\r' || sentence[idx] == '\n')) { | |
| idx--; | |
| } | |
| if (idx < 0) return cfg.period; | |
| // Dấu ba chấm "..." | |
| if (idx >= 2 && sentence[idx] == '.' && sentence[idx - 1] == '.' && sentence[idx - 2] == '.') { | |
| return cfg.ellipsis; | |
| } | |
| char last_char = sentence[idx]; | |
| switch (last_char) { | |
| case '.': return cfg.period; | |
| case ',': return cfg.comma; | |
| case '?': return cfg.question; | |
| case '!': return cfg.exclaim; | |
| case ':': return cfg.colon; | |
| case ';': return cfg.semicolon; | |
| default: return cfg.default_pause; | |
| } | |
| } | |
| } // namespace matcha | |