Text-to-Speech
Transformers
ONNX
teratts_onnx
feature-extraction
onnxruntime
russian
english
custom-code
custom_code
Instructions to use TeraSpace/TeraTTSv2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use TeraSpace/TeraTTSv2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-to-speech", model="TeraSpace/TeraTTSv2", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("TeraSpace/TeraTTSv2", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Add ONNX Runtime implementation
Browse files- 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
|