- g2pkk 한계로 뭉개지는 약어·기호를 g2p 이전에 한글로 교정 - 예: GPU→지피유, API→에이피아이, 50%→50퍼센트, 25℃→25도 - USER_DICT로 특정 단어 발음 교정 확장 가능 - AUTO/KR 모드에서 적용(영어 전용 모드는 미적용) - 참고: 속도 뭉개짐과는 별개(g2p/정규화 문제)
98 lines
3.2 KiB
Python
98 lines
3.2 KiB
Python
"""한국어 발음 개선용 전처리.
|
||
|
||
MeloTTS 한국어는 g2pkk(규칙 기반 g2p)를 쓰는데, 영어 약어·기호·일부 단어에서
|
||
발음이 뭉개지거나 틀립니다. 학습 없이 개선하는 실전적 방법은 g2p 이전에
|
||
텍스트를 교정하는 것입니다.
|
||
|
||
- SYMBOLS: 기호를 한글로 확장(%, ℃ 등)
|
||
- ACRONYMS: 흔한 영어 약어를 한글 음차로(약어를 한국어 음성으로 읽게)
|
||
- USER_DICT: 특정 단어의 발음을 교정(사용자가 문제 단어를 추가) — 한글→한글 가능
|
||
|
||
적용은 한국어/자동 모드에서만 한다(영어 전용 모드에는 적용 안 함).
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
|
||
# 기호 확장 (앞뒤 문맥 영향 적은 것만 보수적으로)
|
||
SYMBOLS: dict[str, str] = {
|
||
"℃": "도",
|
||
"°C": "도",
|
||
"℉": "화씨",
|
||
"%": "퍼센트",
|
||
"&": "그리고",
|
||
"@": "골뱅이",
|
||
}
|
||
|
||
# 영어 약어 → 한글 음차 (대문자 형태만 매칭)
|
||
ACRONYMS: dict[str, str] = {
|
||
"AI": "에이아이",
|
||
"API": "에이피아이",
|
||
"APP": "앱",
|
||
"AR": "에이아르",
|
||
"VR": "브이아르",
|
||
"QR": "큐아르",
|
||
"CPU": "씨피유",
|
||
"GPU": "지피유",
|
||
"GPT": "지피티",
|
||
"PC": "피씨",
|
||
"TV": "티비",
|
||
"IT": "아이티",
|
||
"IoT": "아이오티",
|
||
"SNS": "에스엔에스",
|
||
"OK": "오케이",
|
||
"URL": "유알엘",
|
||
"ID": "아이디",
|
||
"PDF": "피디에프",
|
||
"HTTP": "에이치티티피",
|
||
"HTTPS": "에이치티티피에스",
|
||
"WiFi": "와이파이",
|
||
"WIFI": "와이파이",
|
||
"DIY": "디아이와이",
|
||
"DNA": "디엔에이",
|
||
"LED": "엘이디",
|
||
"GPS": "지피에스",
|
||
"SMS": "에스엠에스",
|
||
"OS": "오에스",
|
||
"IP": "아이피",
|
||
"DB": "디비",
|
||
"UI": "유아이",
|
||
"UX": "유엑스",
|
||
"TTS": "티티에스",
|
||
"CD": "씨디",
|
||
"DVD": "디비디",
|
||
"USB": "유에스비",
|
||
"CEO": "씨이오",
|
||
"FAQ": "에프에이큐",
|
||
"FYI": "에프와이아이",
|
||
"ASAP": "에이에스에이피",
|
||
}
|
||
|
||
# 사용자 발음 교정 사전 (문제 단어를 여기에 추가; 한글→한글도 가능)
|
||
# 예: "육개장": "육깨장", "<22>its": "..." ← 사용자가 알려주는 단어를 반영
|
||
USER_DICT: dict[str, str] = {}
|
||
|
||
|
||
def _sub_word_boundary(text: str, mapping: dict[str, str], case_sensitive: bool) -> str:
|
||
if not mapping:
|
||
return text
|
||
# 라틴/숫자에 인접하지 않은 경우에만 치환 (한글 조사 뒤에도 매칭되도록)
|
||
keys = sorted(mapping.keys(), key=len, reverse=True)
|
||
flags = 0 if case_sensitive else re.IGNORECASE
|
||
for k in keys:
|
||
pattern = r"(?<![A-Za-z0-9])" + re.escape(k) + r"(?![A-Za-z0-9])"
|
||
text = re.sub(pattern, mapping[k], text, flags=flags)
|
||
return text
|
||
|
||
|
||
def normalize_korean(text: str) -> str:
|
||
"""한국어 합성 전에 기호/약어/사용자사전을 적용."""
|
||
if not text:
|
||
return text
|
||
# 사용자 사전(최우선) → 약어(대문자 정확 매칭) → 기호
|
||
text = _sub_word_boundary(text, USER_DICT, case_sensitive=True)
|
||
text = _sub_word_boundary(text, ACRONYMS, case_sensitive=True)
|
||
for sym, rep in SYMBOLS.items():
|
||
text = text.replace(sym, rep)
|
||
return text
|