78 lines
3.1 KiB
Python
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")
|