114 lines
3.9 KiB
Python
114 lines
3.9 KiB
Python
"""Intelligent Romanization Engine for Japanese and Korean text alignment in F5-TTS."""
|
|
|
|
import re
|
|
import json
|
|
from aiohttp import web
|
|
|
|
_kakasi_instance = None
|
|
_hangul_transliter = None
|
|
|
|
|
|
def _get_kakasi():
|
|
global _kakasi_instance
|
|
if _kakasi_instance is None:
|
|
try:
|
|
import pykakasi
|
|
_kakasi_instance = pykakasi.kakasi()
|
|
except Exception:
|
|
_kakasi_instance = False
|
|
return _kakasi_instance if _kakasi_instance else None
|
|
|
|
|
|
def _get_hangul_transliter():
|
|
global _hangul_transliter
|
|
if _hangul_transliter is None:
|
|
try:
|
|
from hangul_romanize import Transliter
|
|
from hangul_romanize.rule import academic
|
|
_hangul_transliter = Transliter(academic)
|
|
except Exception:
|
|
_hangul_transliter = False
|
|
return _hangul_transliter if _hangul_transliter else None
|
|
|
|
|
|
def _clean_romanized_spacing(text: str) -> str:
|
|
"""Normalize whitespace and punctuation spacing for speech synthesis alignment."""
|
|
# Remove hyphens often generated in academic Korean romanization between syllables
|
|
text = re.sub(r'(\w)-(\w)', r'\1\2', text)
|
|
# Fix spaces preceding punctuation: 'word ,' -> 'word,'
|
|
text = re.sub(r'\s+([,.:;!?~])', r'\1', text)
|
|
# Ensure a space follows punctuation if directly adjacent to a letter: 'word,next' -> 'word, next'
|
|
text = re.sub(r'([,.:;!?])([A-Za-z0-9])', r'\1 \2', text)
|
|
# Collapse multiple whitespaces
|
|
text = re.sub(r'[ \t]+', ' ', text)
|
|
return text.strip()
|
|
|
|
|
|
def romanize_text(text: str) -> dict:
|
|
"""
|
|
Detect language and convert Japanese or Korean into standard Latin/Romaji phonetics.
|
|
Returns dict with original, romanized, detected language, and success status.
|
|
"""
|
|
raw = (text or "").strip()
|
|
if not raw:
|
|
return {"success": True, "original": "", "romanized": "", "lang": "empty"}
|
|
|
|
has_hangul = bool(re.search(r'[\uac00-\ud7a3]', raw))
|
|
has_kana = bool(re.search(r'[\u3040-\u30ff]', raw))
|
|
|
|
# 1. Process Korean text
|
|
if has_hangul:
|
|
ht = _get_hangul_transliter()
|
|
if ht:
|
|
try:
|
|
romanized = ht.translit(raw)
|
|
return {
|
|
"success": True,
|
|
"original": raw,
|
|
"romanized": _clean_romanized_spacing(romanized),
|
|
"lang": "ko"
|
|
}
|
|
except Exception as e:
|
|
return {"success": False, "error": f"Korean transliteration failed: {e}", "original": raw}
|
|
|
|
# 2. Process Japanese text (Kana or Japanese context)
|
|
if has_kana or (not has_hangul and bool(re.search(r'[\u4e00-\u9fff]', raw))):
|
|
kakasi = _get_kakasi()
|
|
if kakasi:
|
|
try:
|
|
result = kakasi.convert(raw)
|
|
parts = []
|
|
for item in result:
|
|
hep = item.get("hepburn", "")
|
|
parts.append(hep if hep else item.get("orig", ""))
|
|
romanized = " ".join(parts)
|
|
return {
|
|
"success": True,
|
|
"original": raw,
|
|
"romanized": _clean_romanized_spacing(romanized),
|
|
"lang": "ja"
|
|
}
|
|
except Exception as e:
|
|
return {"success": False, "error": f"Japanese romanization failed: {e}", "original": raw}
|
|
|
|
# Default: Already Latin or other language, keep as is
|
|
return {
|
|
"success": True,
|
|
"original": raw,
|
|
"romanized": raw,
|
|
"lang": "unchanged"
|
|
}
|
|
|
|
|
|
async def api_romanize_text(request):
|
|
"""POST /anomalous/romanize_text - Convert input text to Romaji / Latin transliteration."""
|
|
try:
|
|
data = await request.json()
|
|
except Exception:
|
|
return web.json_response({"success": False, "error": "Invalid JSON body"}, status=400)
|
|
|
|
text = data.get("text", "")
|
|
res = romanize_text(text)
|
|
status_code = 200 if res.get("success") else 500
|
|
return web.json_response(res, status=status_code)
|