TeraSpace commited on
Commit
f05ea79
·
verified ·
1 Parent(s): e9a0c6d

Add ONNX Runtime implementation

Browse files
Files changed (1) hide show
  1. teratts.py +98 -1
teratts.py CHANGED
@@ -8,6 +8,7 @@ import math
8
  import os
9
  import re
10
  import unicodedata
 
11
  import wave
12
  from collections.abc import Iterator
13
  from dataclasses import dataclass
@@ -30,7 +31,10 @@ SPEED = 1.05
30
  SEED = 1234
31
  RUSSIAN_TAG = re.compile(r"<ru>(.*?)</ru>", flags=re.DOTALL)
32
  LANGUAGE_TAG = re.compile(r"<(ru|en)>(.*?)</\1>", flags=re.DOTALL)
 
33
  TAGGED_NUMBER = re.compile(r"(?<![\w.])[-−]?\d+(?:[.,]\d+)?(?![\w.])")
 
 
34
 
35
 
36
  def prepare_raw_text(raw_text: str) -> tuple[str, str]:
@@ -38,6 +42,96 @@ def prepare_raw_text(raw_text: str) -> tuple[str, str]:
38
  return model_text, model_text.replace("+", "")
39
 
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  def load_ruaccent(
42
  *,
43
  model_size: str = "turbo3.1",
@@ -103,7 +197,10 @@ def expand_tagged_numbers(text: str) -> str:
103
 
104
  def normalize_text(loaded: "LoadedTTS", text: str) -> str:
105
  """Return the exact text tensorized by the text encoder for an utterance."""
106
- expanded_text = expand_tagged_numbers(text)
 
 
 
107
  model_text, _ = prepare_raw_text(add_russian_stress(expanded_text, loaded.accentizer))
108
  return model_text
109
 
 
8
  import os
9
  import re
10
  import unicodedata
11
+ import warnings
12
  import wave
13
  from collections.abc import Iterator
14
  from dataclasses import dataclass
 
31
  SEED = 1234
32
  RUSSIAN_TAG = re.compile(r"<ru>(.*?)</ru>", flags=re.DOTALL)
33
  LANGUAGE_TAG = re.compile(r"<(ru|en)>(.*?)</\1>", flags=re.DOTALL)
34
+ LANGUAGE_TAG_TOKEN = re.compile(r"<(/?)([a-z]{2})>")
35
  TAGGED_NUMBER = re.compile(r"(?<![\w.])[-−]?\d+(?:[.,]\d+)?(?![\w.])")
36
+ PUNCTUATION_NEEDS_SPACE = re.compile(r"[,.!?;:…](?=[^\s<])")
37
+ NUMBER_NEEDS_SPACE = re.compile(r"(?<=\d)(?=[A-Za-zА-Яа-яЁё])")
38
 
39
 
40
  def prepare_raw_text(raw_text: str) -> tuple[str, str]:
 
42
  return model_text, model_text.replace("+", "")
43
 
44
 
45
+ def _add_punctuation_spaces(text: str) -> str:
46
+ """Separate punctuation without splitting decimal literals or closing tags."""
47
+ def space_after(match: re.Match[str]) -> str:
48
+ punctuation = match.group(0)
49
+ index = match.start()
50
+ previous = text[index - 1] if index else ""
51
+ following = text[index + 1] if index + 1 < len(text) else ""
52
+ if punctuation in ".," and previous.isdigit() and following.isdigit():
53
+ return punctuation
54
+ return punctuation + " "
55
+
56
+ return PUNCTUATION_NEEDS_SPACE.sub(space_after, text)
57
+
58
+
59
+ def validate_language_tags(text: str) -> None:
60
+ """Require balanced ``<ru>`` / ``<en>`` spans for all public synthesis."""
61
+ tokens = list(LANGUAGE_TAG_TOKEN.finditer(text))
62
+ if not tokens or not LANGUAGE_TAG.search(text):
63
+ raise ValueError(
64
+ "text must contain a language tag: wrap text in <ru>...</ru> or <en>...</en>"
65
+ )
66
+ stack: list[str] = []
67
+ for token in tokens:
68
+ closing, language = token.groups()
69
+ if language not in {"ru", "en"}:
70
+ raise ValueError(f"unsupported language tag <{language}>; use <ru> or <en>")
71
+ if not closing:
72
+ stack.append(language)
73
+ elif not stack or stack.pop() != language:
74
+ raise ValueError("language tags must be balanced: use <ru>...</ru> or <en>...</en>")
75
+ if stack:
76
+ raise ValueError("language tags must be balanced: use <ru>...</ru> or <en>...</en>")
77
+ # Angle brackets that did not form a valid tag would be accepted by the
78
+ # character vocabulary but are not meaningful model input.
79
+ if "<" in LANGUAGE_TAG_TOKEN.sub("", text) or ">" in LANGUAGE_TAG_TOKEN.sub("", text):
80
+ raise ValueError("invalid language tags; use only <ru>...</ru> or <en>...</en>")
81
+
82
+
83
+ def _skip_unsupported_characters(
84
+ text: str,
85
+ indexer: "UnicodeIndexer",
86
+ *,
87
+ preserve_digits: bool = False,
88
+ ) -> str:
89
+ """Return supported text and issue one clear warning for skipped characters."""
90
+ kept: list[str] = []
91
+ skipped: list[str] = []
92
+ for character in text:
93
+ # The released table was trained on NFKD text. Keep the human-readable
94
+ # NFC spelling here (especially ``й`` and ``ё``) as long as all of its
95
+ # decomposed codepoints exist in the table. RUAccent must receive this
96
+ # spelling: passing ``и`` + COMBINING BREVE makes its text cleaner drop
97
+ # the breve and turn ``й`` into ``и``.
98
+ encoded = unicodedata.normalize("NFKD", character)
99
+ supported = bool(encoded) and all(
100
+ (indexer.table[ord(item)] if ord(item) < 65_536 else -1) >= 0
101
+ for item in encoded
102
+ )
103
+ if not supported and not (preserve_digits and character.isdigit()):
104
+ skipped.append(character)
105
+ else:
106
+ kept.append(character)
107
+ if skipped:
108
+ labels = ", ".join(
109
+ f"{character!r} (U+{ord(character):04X})" for character in sorted(set(skipped))
110
+ )
111
+ warnings.warn(
112
+ f"skipped unsupported characters not present in the TeraTTS vocabulary: {labels}",
113
+ RuntimeWarning,
114
+ stacklevel=2,
115
+ )
116
+ return "".join(kept)
117
+
118
+
119
+ def normalize_input_text(raw_text: str, indexer: "UnicodeIndexer") -> str:
120
+ """Normalize spacing and skip unsupported vocabulary characters with a warning."""
121
+ if not isinstance(raw_text, str) or not raw_text.strip():
122
+ raise ValueError("text must not be empty; use <ru>...</ru> or <en>...</en>")
123
+ # Retain composed characters through RUAccent. ``prepare_raw_text``
124
+ # performs the required NFKD conversion immediately before ONNX encoding.
125
+ text = unicodedata.normalize("NFC", raw_text)
126
+ text = _add_punctuation_spaces(text)
127
+ text = NUMBER_NEEDS_SPACE.sub(" ", text)
128
+ # Digits are retained only long enough for tagged ``num2words`` expansion;
129
+ # any remaining unsupported digits are skipped after that expansion.
130
+ text = _skip_unsupported_characters(text, indexer, preserve_digits=True)
131
+ validate_language_tags(text)
132
+ return text
133
+
134
+
135
  def load_ruaccent(
136
  *,
137
  model_size: str = "turbo3.1",
 
197
 
198
  def normalize_text(loaded: "LoadedTTS", text: str) -> str:
199
  """Return the exact text tensorized by the text encoder for an utterance."""
200
+ normalized_input = normalize_input_text(text, loaded.indexer)
201
+ expanded_text = _skip_unsupported_characters(
202
+ expand_tagged_numbers(normalized_input), loaded.indexer
203
+ )
204
  model_text, _ = prepare_raw_text(add_russian_stress(expanded_text, loaded.accentizer))
205
  return model_text
206