Files
unios-tts/app/normalize.py
T
Shaman 82c306ff69 init
2026-10-01 07:38:01 +03:00

78 lines
3.1 KiB
Python

import re
from rutextnorm import normalize_russian
from app.ssml import PAUSE_DELIM
_TRAILING_CLOSERS = "\"'»)]`"
# Региональные (языкоспецифичные) нормализаторы: lang -> функция.
# Общие шаги, применимые к любому языку, вынесены в normalize_common().
_REGIONAL_NORMALIZERS = {"ru": normalize_russian}
_DELIM_CHARS = "([{«“<)]}»>\"'`*_~"
_DELIM_RUN_RE = re.compile(r"([ \t]*)([" + re.escape(_DELIM_CHARS) + r"]+)([ \t]*)")
def _prev_nonspace(text: str, pos: int) -> str:
i = pos - 1
while i >= 0 and text[i] in " \t":
i -= 1
return text[i] if i >= 0 else ""
def _next_nonspace(text: str, pos: int) -> str:
while pos < len(text) and text[pos] in " \t":
pos += 1
return text[pos] if pos < len(text) else ""
def mark_pause_delimiters(text: str) -> str:
"""Удаляет выделительные разделители (скобки, кавычки, markdown-разметку).
Если по обе стороны от прогона есть буквы/цифры, ставит sentinel паузы
(паузная граница для SSML-слоя) вместо литеральной запятой; иначе просто
удаляет. Одинарная кавычка между буквами (апостроф) сохраняется."""
def repl(m: re.Match) -> str:
left, run, right = m.group(1), m.group(2), m.group(3)
start, end = m.start(2), m.end(2)
left_touch = text[start - 1] if start > 0 else ""
right_touch = text[end] if end < len(text) else ""
if set(run) == {"'"} and left_touch.isalnum() and right_touch.isalnum():
return m.group(0)
if _prev_nonspace(text, start).isalnum() and _next_nonspace(text, end).isalnum():
return " " + PAUSE_DELIM + " "
return " " if (left or right) else ""
return _DELIM_RUN_RE.sub(repl, text)
def ensure_line_punctuation(text: str) -> str:
result = []
for line in text.split("\n"):
line = line.rstrip()
core = line.rstrip(_TRAILING_CLOSERS).rstrip()
if core and core[-1].isalnum():
line = line + "."
result.append(line)
return "\n".join(result)
def normalize_common(text: str) -> str:
"""Языконезависимая нормализация. Применяется ко всему тексту целиком,
до разбиения на предложения, для любого языка."""
text = mark_pause_delimiters(text)
text = ensure_line_punctuation(text)
text = re.sub(r"[ \t]{2,}", " ", text)
return text
def normalize_language(text: str, lang: str) -> str:
"""Региональная нормализация для конкретного языка. Без изменений для
языков, у которых нет собственных правил (например, en)."""
fn = _REGIONAL_NORMALIZERS.get(lang)
return fn(text) if fn else text
def normalize_ru(text: str) -> str:
return normalize_language(text, "ru")