Text-to-Speech
Safetensors
Chatterbox
Uzbek
chatterbox-tts
uzbek
tts
lora
voice-cloning
turkic-languages
low-resource
Instructions to use UAzimov/Uzbek-tts-chatterbox with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Chatterbox
How to use UAzimov/Uzbek-tts-chatterbox with Chatterbox:
# pip install chatterbox-tts import torchaudio as ta from chatterbox.tts import ChatterboxTTS model = ChatterboxTTS.from_pretrained(device="cuda") text = "Ezreal and Jinx teamed up with Ahri, Yasuo, and Teemo to take down the enemy's Nexus in an epic late-game pentakill." wav = model.generate(text) ta.save("test-1.wav", wav, model.sr) # If you want to synthesize with a different voice, specify the audio prompt AUDIO_PROMPT_PATH="YOUR_FILE.wav" wav = model.generate(text, audio_prompt_path=AUDIO_PROMPT_PATH) ta.save("test-2.wav", wav, model.sr) - Notebooks
- Google Colab
- Kaggle
File size: 6,421 Bytes
a777897 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | #!/usr/bin/env python3
"""
Uzbek TTS text normalizer — digits/numbers to Uzbek words + apostrophe normalization.
Usage:
from uz_normalize import normalize_uz
text = normalize_uz("1991-yil 31-avgustda soat 9 da")
# -> "bir ming to'qqiz yuz to'qson birinchi yil o'ttiz birinchi avgustda soat to'qqizda"
Rules:
1. All apostrophe-like chars (ʻ ʼ ‘ ’ `) -> ASCII '
2. Years (1000-2999 followed by -yil/-yilda etc) -> cardinal reading
3. Ordinals: digit + -inchi/-anchi suffixes -> ordinal words
4. Dates: DD-DDDD month patterns handled via ordinal day + year
5. Times: soat N da/Ni -> "soat to'qqizda"
6. Cardinals: standalone numbers -> full Uzbek reading
7. Percent, decimals covered
This normalizes for models trained on ASCII-apostrophe data (e.g. our
Chatterbox LoRA). For okina-style data flip PREFER_OKINA=True.
"""
import re
PREFER_OKINA = False
APO = "'" if not PREFER_OKINA else "\u02bb"
ONES = ["", "bir", "ikki", "uch", "to'rt", "besh", "olti", "yetti", "sakkiz", "to'qqiz"]
TENS = ["", "o'n", "yigirma", "o'ttiz", "qirq", "ellik", "oltmish", "yetmish", "sakson", "to'qson"]
def _two_digits(n: int) -> str:
"""1-99 -> uzbek words"""
t, o = divmod(n, 10)
parts = []
if t: parts.append(TENS[t])
if o: parts.append(ONES[o])
return " ".join(parts) if parts else "nol"
def _three_digits(n: int) -> str:
"""1-999"""
h, rest = divmod(n, 100)
parts = []
if h == 1: parts.append("yuz")
elif h > 1: parts.append(ONES[h] + " yuz")
if rest: parts.append(_two_digits(rest))
return " ".join(parts)
def number_to_uzbek(n: int) -> str:
"""0..999_999_999_999 -> uzbek words (long scale-ish, uzbek uses milliard/million)"""
if n == 0: return "nol"
parts = []
for divisor, name in [(10**9, "milliard"), (10**6, "million"), (1000, "ming")]:
q, n = divmod(n, divisor)
if q:
if divisor == 1000 and q == 1:
parts.append("bir ming")
else:
parts.append(number_to_uzbek(q) + " " + name)
if n: parts.append(_three_digits(n))
return " ".join(parts)
MONTHS = {
"yanvar": 1, "fevral": 2, "mart": 3, "aprel": 4, "may": 5, "iyun": 6,
"iyul": 7, "avgust": 8, "sentabr": 9, "oktabr": 10, "noyabr": 11, "dekabr": 12,
}
def _ordinal(word: str) -> str:
"""cardinal phrase -> ordinal (ayt... inchi). Uzbek: oxirgi bo'g'inga -inchi/-anchi.
Vowel harmony approximation: last vowel a/I -> -anchi? actually rule:
ends with k/t/p/q -> voice; simplified common forms used here."""
# Common convention: birinchi, ikkinchi, uchinchi are irregular; others = word + "-inchi"
IRREG = {"bir": "birinchi", "ikki": "ikkinchi", "uch": "uchinchi",
"to'rt": "to'rtinchi", "besh": "beshinchi", "olti": "oltinchi",
"yetti": "yetinchi", "sakkiz": "sakkizinchi", "to'qqiz": "to'qqizinchi",
"o'n": "o'ninchi"}
if word in IRREG: return IRREG[word]
return word + "inchi"
def _expand_year(m):
y = int(m.group(1))
suf = m.group(2) or ""
words = number_to_uzbek(y)
if suf.startswith("-yil"):
# ordinal years often read as-is; keep "yil" suffix attached
tail = suf[len("-yil"):]
return f"{words} yil{tail}"
return f"{words}{suf}"
def _expand_number_token(m):
num_str = m.group(0)
try:
return number_to_uzbek(int(num_str))
except ValueError:
return num_str
def normalize_numbers(text: str) -> str:
# protect existing words; only touch digit runs and date/time patterns
# 1. years: 4-digit number directly before yil*
text = re.sub(r"\b(\d{4})(-?\s*y[a-z']*)", _expand_year, text)
text = re.sub(r"(\bsoat\s+)?\b(\d{1,2}):(\d{2})\b", lambda m: (m.group(1) or "soat ") + f"{number_to_uzbek(int(m.group(2)))} {number_to_uzbek(int(m.group(3)))}", text)
# 2b. "soat N da/ni/dan" (without colon)
def _time(m):
h = int(m.group(1))
suf = m.group(2) or ""
return f"soat {number_to_uzbek(h)}{suf}"
text = re.sub(r"\bsoat\s+(\d{1,2})(-?[a-z']*)?", _time, text)
# 3. ordinals & day-of-date: N-avgust -> ordinal; N-inchi handled by irregular table
def _ord_date(m):
n = int(m.group(1))
rest = m.group(2) # e.g. "-avgustda" or "-inchi"
card = number_to_uzbek(n)
last = card.split()[-1]
first = " ".join(card.split()[:-1])
restword = rest.lstrip("-")
if restword.startswith("inchi") or restword.startswith("nchi"):
# already an ordinal marker: convert number only, keep suffix
ordw = _ordinal(last)
out = (first + " " + ordw if first else ordw) + (restword[5:] if restword.startswith("inchi") and len(restword) > 5 else "")
return out
ordw = _ordinal(last)
return (first + " " + ordw if first else ordw) + " " + restword
text = re.sub(r"\b(\d{1,4})-([a-z\u02bb']+)\b", _ord_date, text)
# 4. plain integers (incl. inside hyphenated like 31-avgust already partially handled;
# handle remaining digit runs)
text = re.sub(r"\b\d{1,9}\b", _expand_number_token, text)
# 5. percent
text = text.replace("%", " foiz")
return text
def normalize_apostrophes(text: str) -> str:
table = str.maketrans({"ʻ": APO, "ʼ": APO, "‘": APO, "’": APO, "`": APO,
"ʻ": APO})
return text.translate(table)
def normalize_uz(text: str) -> str:
"""Full pipeline: apostrophes + numbers. Apply BEFORE sending to TTS."""
text = normalize_apostrophes(text)
text = normalize_numbers(text)
# collapse multiple spaces
text = re.sub(r"[ \t]+", " ", text).strip()
return text
if __name__ == "__main__":
tests = [
("Assalomu alaykum, bugun havo juda ham chiroyli.", None),
("Oʻzbekiston Respublikasi 1991-yil 31-avgustda mustaqiligi eʼlon qildi.",
"O'zbekiston Respublikasi bir ming to'qqiz yuz to'qson bir yil o'ttiz birinchi avgustda mustaqiligi e'lon qildi."),
("Ertaga soat 9 da uchrashamizmi?", None),
("50 foiz odam rozi", None),
("2-inchi gʻoya", None),
("Kompyuter oʻyinlari 100 yildan beri", None),
]
for t, expected in tests:
out = normalize_uz(t)
status = ""
if expected is not None:
status = "OK" if out == expected else f"MISMATCH (want: {expected})"
print(f"IN : {t}\nOUT: {out}\n {status}\n")
|