Spaces:
Running
Running
Download text_processor.py from Cong123779/matcha-tts: direct link, hf CLI and curl.
- Browser
- Download file 11.4 kB
-
https://huggingface.co/spaces/Cong123779/matcha-tts/resolve/main/text_processor.py
- Command line
-
hf download hf://spaces/Cong123779/matcha-tts/text_processor.py
-
curl -L -o text_processor.py https://huggingface.co/spaces/Cong123779/matcha-tts/resolve/main/text_processor.py
11.4 kB
| """ | |
| Module Tiền Xử Lý Văn Bản Tự Độc Lập Cho Matcha-TTS Tiếng Việt | |
| ============================================================ | |
| Bao gồm: | |
| 1. Master Vietnamese Normalizer (số đếm, tiền tệ, ngày tháng, biển số xe, cccd...) | |
| 2. Foreign Symbols & Acronyms Translator (AI, RAM, CPU, WiFi, @, #, $, %...) | |
| 3. Text cleaner & Symbols to Sequence converter | |
| """ | |
| import os | |
| import re | |
| import unicodedata | |
| from typing import List, Tuple | |
| # Mapping of symbols | |
| _pad = "_" | |
| _punctuation = ';:,.!?¡¿—…"«»“” ' | |
| _letters = "aáàảãạăắằẳẵặâấầẩẫậeéèẻẽẹêếềểễệiíìỉĩịoóòỏõọôốồổỗộơớờởỡợuúùủũụưứừửữựyýỳỷỹỵbcdđghklmnpqrstvwxz" | |
| _letters_ipa = "ɑɐɒæɓʙβɔɕçɗɖðʤəɘɚɛɜɝɞɟʄɡɠɢʛɦɧħɥʜɨɪʝɭɬɫɮʟɱɯɰŋɳɲɴøœɶʘɸɹɺɾɻʀʁɽʂʃʈʧʉʊʋⱱʌɣɤʍχʎʏʑʐʒʔʡʕʢǀǁǂǃˈˌːˑʼʴʰʱʲʷˠˤ˞↓↑→↗↘'̩'ᵻ" | |
| symbols = [_pad] + list(_punctuation) + list(_letters) + list(_letters_ipa) | |
| _symbol_to_id = {s: i for i, s in enumerate(symbols)} | |
| _id_to_symbol = {i: s for i, s in enumerate(symbols)} | |
| UNITS = ["không", "một", "hai", "ba", "bốn", "năm", "sáu", "bảy", "tám", "chín"] | |
| FOREIGN_TERMS = { | |
| "AI": "ây ai", | |
| "A.I": "ây ai", | |
| "CPU": "sê pê u", | |
| "GPU": "gờ pê u", | |
| "RAM": "ram", | |
| "ROM": "rom", | |
| "SSD": "ét ét đê", | |
| "HDD": "hát đê đê", | |
| "USB": "u ét bê", | |
| "WIFI": "oai phai", | |
| "WI-FI": "oai phai", | |
| "API": "a pê i", | |
| "SDK": "ét đê ca", | |
| "APP": "áp", | |
| "APPS": "áp", | |
| "TTS": "tê tê ét", | |
| "ASR": "a ét rờ", | |
| "BOT": "bót", | |
| "CHATBOT": "chát bót", | |
| "GPT": "gờ pê tê", | |
| "CHATGPT": "chát gờ pê tê", | |
| "CEO": "sê e o", | |
| "CTO": "sê tê o", | |
| "IT": "ai ti", | |
| "DEV": "đép", | |
| "TESTER": "tét tơ", | |
| "BUG": "bấc", | |
| "OK": "ô kê", | |
| "O.K": "ô kê", | |
| "YES": "dét", | |
| "NO": "nô", | |
| "BYE": "bai", | |
| "HELLO": "hê lâu", | |
| "HI": "hai", | |
| "FAN": "phan", | |
| "FANS": "phan", | |
| "LIKE": "lai", | |
| "SHARE": "se", | |
| "COMMENT": "còm men", | |
| "VIEW": "viu", | |
| "VIEWS": "viu", | |
| "SUB": "sắp", | |
| "STREAM": "sờ trim", | |
| "LIVESTREAM": "lai sờ trim", | |
| "CLIP": "clíp", | |
| "VIDEO": "vi đê ô", | |
| "AUDIO": "ao đi ô", | |
| "PODCAST": "pót cát", | |
| "HOT": "hót", | |
| "TOP": "tóp", | |
| "PRO": "prô", | |
| "VIP": "víp", | |
| "LINK": "linh", | |
| "WEB": "oép", | |
| "WEBSITE": "oép sai", | |
| "ONLINE": "on lai", | |
| "OFFLINE": "ọp lai", | |
| "GAME": "gêm", | |
| "GAMER": "gê mơ", | |
| "SHOW": "sô", | |
| "MC": "em xi", | |
| "PR": "pê rờ", | |
| "KOL": "cây o eo", | |
| "ID": "ai đi", | |
| "PASS": "pát", | |
| "PASSWORD": "pát uốt", | |
| "LOGIN": "lốc in", | |
| "LOGOUT": "lốc ao", | |
| "CHECKIN": "chếch in", | |
| "CHECKOUT": "chếch ao", | |
| "SALE": "sêu", | |
| "OFF": "ọp", | |
| "DISCOUNT": "đít scao", | |
| "VOUCHER": "vau chờ", | |
| "SHIP": "síp", | |
| "SHIPPER": "síp pơ", | |
| "COD": "cót", | |
| "SHOP": "sóp", | |
| "STORE": "sờ to", | |
| "BILL": "biu", | |
| "TEAM": "tim", | |
| "GROUP": "gờ rúp", | |
| "CLUB": "cơ lặp", | |
| "STAFF": "sờ táp", | |
| "BOSS": "bót", | |
| "LEADER": "lít đơ", | |
| "MANAGER": "ma na gơ", | |
| "DEAL": "điu", | |
| "TEST": "tét", | |
| "DEMO": "đê mô", | |
| "UPDATE": "ắp đết", | |
| "UPGRADE": "ắp gờ rết", | |
| "FIX": "phích", | |
| "SET": "sét", | |
| "SETUP": "sét ắp", | |
| "RESET": "ri sét", | |
| "ERROR": "e rơ", | |
| "CODE": "cốt", | |
| "DATA": "đa ta", | |
| "SERVER": "sơ vơ", | |
| "CLOUD": "cờ lao", | |
| "SMS": "ét em ét", | |
| "EMAIL": "i meo", | |
| "MAIL": "meo", | |
| "FB": "phây búc", | |
| "FACEBOOK": "phây búc", | |
| "YOUTUBE": "du túp", | |
| "TIKTOK": "tích tốc", | |
| "ZALO": "da lô", | |
| "INSTA": "in sta", | |
| "INSTAGRAM": "in sta gram", | |
| "HOTLINE": "hót lai", | |
| "SMARTPHONE": "sờ mát phôn", | |
| "IPHONE": "ai phôn", | |
| "IPAD": "ai pát", | |
| "LAPTOP": "láp tóp", | |
| "PC": "pê sê" | |
| } | |
| LATIN_LETTERS = { | |
| 'A': 'a', 'B': 'bê', 'C': 'xê', 'D': 'đê', 'E': 'e', | |
| 'F': 'ép', 'G': 'gờ', 'H': 'hát', 'I': 'i', 'J': 'giây', | |
| 'K': 'ca', 'L': 'e-lờ', 'M': 'em', 'N': 'en', 'O': 'o', | |
| 'P': 'pê', 'Q': 'quy', 'R': 'e-rờ', 'S': 'ét', 'T': 'tê', | |
| 'U': 'u', 'V': 'vê', 'W': 'vê kép', 'X': 'ích', 'Y': 'i dài', 'Z': 'dét' | |
| } | |
| UNITS_DICT = { | |
| r'(\d+)\s*(?:km/h|kmh)': r'\1 ki-lô-mét trên giờ', | |
| r'(\d+)\s*(?:m/s|ms)': r'\1 mét trên giây', | |
| r'(\d+)\s*km\b': r'\1 ki-lô-mét', | |
| r'(\d+)\s*m\b': r'\1 mét', | |
| r'(\d+)\s*cm\b': r'\1 xen-ti-mét', | |
| r'(\d+)\s*mm\b': r'\1 mi-li-mét', | |
| r'(\d+)\s*(?:kg|kilo|kí)\b': r'\1 ki-lô-gam', | |
| r'(\d+)\s*g\b': r'\1 gam', | |
| r'(\d+)\s*l\b': r'\1 lít', | |
| r'(\d+)\s*ml\b': r'\1 mi-li-lít', | |
| r'(\d+)\s*°C\b': r'\1 độ xê', | |
| r'(\d+)\s*°F\b': r'\1 độ ép', | |
| r'(\d+)\s*%\b': r'\1 phần trăm', | |
| r'(\d+)\s*(?:usd|\$)\b': r'\1 đô la', | |
| r'(\d+)\s*(?:vnd|vnđ|đ)\b': r'\1 đồng', | |
| r'(\d+)\s*(?:gb|gigabyte)\b': r'\1 ghi-ga-bai', | |
| r'(\d+)\s*(?:mb|megabyte)\b': r'\1 mê-ga-bai', | |
| r'(\d+)\s*(?:kb|kilobyte)\b': r'\1 ki-lô-bai', | |
| r'(\d+)\s*hz\b': r'\1 héc', | |
| r'(\d+)\s*khz\b': r'\1 ki-lô-héc', | |
| r'(\d+)\s*mhz\b': r'\1 mê-ga-héc', | |
| r'(\d+)\s*ghz\b': r'\1 ghi-ga-héc' | |
| } | |
| def read_integer(n: int) -> str: | |
| if n < 0: | |
| return f"âm {read_integer(abs(n))}" | |
| if n < 10: | |
| return UNITS[n] | |
| if n < 100: | |
| ten, unit = n // 10, n % 10 | |
| ten_str = "mười" if ten == 1 else f"{UNITS[ten]} mươi" | |
| if unit == 0: | |
| return ten_str | |
| elif unit == 1: | |
| return f"{ten_str} một" if ten == 1 else f"{ten_str} mốt" | |
| elif unit == 4: | |
| return f"{ten_str} bốn" if ten == 1 else f"{ten_str} tư" | |
| elif unit == 5: | |
| return f"{ten_str} lăm" | |
| else: | |
| return f"{ten_str} {UNITS[unit]}" | |
| if n < 1000: | |
| hundred, rem = n // 100, n % 100 | |
| hundred_str = f"{UNITS[hundred]} trăm" | |
| if rem == 0: | |
| return hundred_str | |
| elif rem < 10: | |
| return f"{hundred_str} lẻ {UNITS[rem]}" | |
| else: | |
| return f"{hundred_str} {read_integer(rem)}" | |
| if n < 1000000: | |
| thous, rem = n // 1000, n % 1000 | |
| thous_str = f"{read_integer(thous)} ngàn" | |
| if rem == 0: | |
| return thous_str | |
| elif rem < 10: | |
| return f"{thous_str} không trăm lẻ {UNITS[rem]}" | |
| elif rem < 100: | |
| return f"{thous_str} không trăm {read_integer(rem)}" | |
| else: | |
| return f"{thous_str} {read_integer(rem)}" | |
| if n < 1000000000: | |
| mil, rem = n // 1000000, n % 1000000 | |
| mil_str = f"{read_integer(mil)} triệu" | |
| if rem == 0: | |
| return mil_str | |
| elif rem < 1000: | |
| return f"{mil_str} không trăm ngàn {read_integer(rem)}" | |
| else: | |
| return f"{mil_str} {read_integer(rem)}" | |
| bil, rem = n // 1000000000, n % 1000000000 | |
| bil_str = f"{read_integer(bil)} tỷ" | |
| if rem == 0: | |
| return bil_str | |
| return f"{bil_str} {read_integer(rem)}" | |
| def read_digits_individually(s: str) -> str: | |
| return " ".join(UNITS[int(d)] for d in s if d.isdigit()) | |
| def read_decimal(num_str: str) -> str: | |
| parts = re.split(r'[,.]', num_str) | |
| if len(parts) == 2: | |
| integer_part = read_integer(int(parts[0])) | |
| decimal_digits = read_digits_individually(parts[1]) | |
| return f"{integer_part} phẩy {decimal_digits}" | |
| return num_str | |
| def translate_foreign_symbols_and_words(text: str) -> str: | |
| # Math & Special characters | |
| char_map = { | |
| "/": " xuyệt ", "\\": " xuyệt ngược ", "_": " gạch dưới ", | |
| "@": " a còng ", "#": " thăng ", "$": " đô la ", | |
| "%": " phần trăm ", "^": " mũ ", "&": " và ", | |
| "*": " nhân ", "+": " cộng ", "=": " bằng ", | |
| "<": " nhỏ hơn ", ">": " lớn hơn ", "|": " hoặc ", "~": " khoảng " | |
| } | |
| for char, replacement in char_map.items(): | |
| text = text.replace(char, replacement) | |
| # Foreign vocabulary replacement | |
| for term, trans in sorted(FOREIGN_TERMS.items(), key=lambda x: len(x[0]), reverse=True): | |
| pattern = r'\b' + re.escape(term) + r'\b' | |
| text = re.sub(pattern, trans, text, flags=re.IGNORECASE) | |
| # Latin isolated letters | |
| def replace_letter(match): | |
| char = match.group(0).upper() | |
| return LATIN_LETTERS.get(char, match.group(0)) | |
| text = re.sub(r'(?<!\w)[A-Za-z](?!\w)', replace_letter, text) | |
| return text | |
| def normalize_vietnamese_text(text: str) -> str: | |
| if not text: | |
| return "" | |
| text = unicodedata.normalize('NFC', text) | |
| text = text.replace('\r\n', '\n').replace('\r', '\n') | |
| # Date format DD/MM/YYYY | |
| def date_repl(match): | |
| d, m, y = match.group(1), match.group(2), match.group(3) | |
| return f"ngày {read_integer(int(d))} tháng {read_integer(int(m))} năm {read_integer(int(y))}" | |
| text = re.sub(r'\b(\d{1,2})[/.-](\d{1,2})[/.-](\d{4})\b', date_repl, text) | |
| # Time format HH:MM | |
| def time_repl(match): | |
| h, m = match.group(1), match.group(2) | |
| m_int = int(m) | |
| if m_int == 0: | |
| return f"{read_integer(int(h))} giờ" | |
| return f"{read_integer(int(h))} giờ {read_integer(m_int)} phút" | |
| text = re.sub(r'\b(\d{1,2})[h:](\d{2})\b', time_repl, text) | |
| # Phone numbers | |
| def phone_repl(match): | |
| digits = match.group(0).replace(" ", "").replace(".", "").replace("-", "") | |
| return read_digits_individually(digits) | |
| text = re.sub(r'\b(?:0|\+84)(?:[.\s-]?\d){8,10}\b', phone_repl, text) | |
| # Units | |
| for pattern, repl in UNITS_DICT.items(): | |
| def unit_sub(m): | |
| num = read_integer(int(m.group(1))) | |
| return re.sub(r'\b' + m.group(1) + r'\b', num, repl.replace(r'\1', num)) | |
| text = re.sub(pattern, unit_sub, text, flags=re.IGNORECASE) | |
| # Currency format with dots: 1.250.000 | |
| def dot_num_repl(match): | |
| val = int(match.group(0).replace('.', '').replace(',', '')) | |
| return read_integer(val) | |
| text = re.sub(r'\b\d{1,3}(?:\.\d{3})+\b', dot_num_repl, text) | |
| # Decimals: 3.14 or 3,14 | |
| def dec_repl(match): | |
| return read_decimal(match.group(0)) | |
| text = re.sub(r'\b\d+[,.]\d+\b', dec_repl, text) | |
| # Regular numbers | |
| def int_repl(match): | |
| val = int(match.group(0)) | |
| if len(match.group(0)) >= 6: | |
| return read_digits_individually(match.group(0)) | |
| return read_integer(val) | |
| text = re.sub(r'\b\d+\b', int_repl, text) | |
| # Foreign terms & special characters | |
| text = translate_foreign_symbols_and_words(text) | |
| # Final cleanup | |
| text = re.sub(r'[«»“”„“”"\x27`\(\)\[\]\{\}]', '', text) | |
| text = re.sub(r'[ \t]+', ' ', text).strip() | |
| return text | |
| def intersperse(lst: List[int], item: int) -> List[int]: | |
| result = [item] * (len(lst) * 2 + 1) | |
| result[1::2] = lst | |
| return result | |
| def text_to_sequence(text: str) -> Tuple[List[int], str]: | |
| cleaned = normalize_vietnamese_text(text).lower() | |
| cleaned = cleaned.replace("–", "-").replace("—", "-").replace("…", "...") | |
| sequence = [] | |
| for symbol in cleaned: | |
| if symbol in _symbol_to_id: | |
| sequence.append(_symbol_to_id[symbol]) | |
| return sequence, cleaned | |