Files
tts_site/app/engines/ko_normalize.py
claude e6bc6ef2be feat: 한국어 발음 개선 전처리(기호/영어약어/사용자사전)
- g2pkk 한계로 뭉개지는 약어·기호를 g2p 이전에 한글로 교정
- 예: GPU→지피유, API→에이피아이, 50%→50퍼센트, 25℃→25도
- USER_DICT로 특정 단어 발음 교정 확장 가능
- AUTO/KR 모드에서 적용(영어 전용 모드는 미적용)
- 참고: 속도 뭉개짐과는 별개(g2p/정규화 문제)
2026-08-24 15:00:57 +09:00

98 lines
3.2 KiB
Python
Raw Blame History

"""한국어 발음 개선용 전처리.
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