File size: 10,291 Bytes
774a30d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
import os
import sys
import tempfile
import subprocess

import spaces
import gradio as gr
import torch
from huggingface_hub import hf_hub_download
from scipy.io.wavfile import write
import numpy as np
from tqdm import tqdm

# ---------------------------------------------------------
# 1. Клануем і падключаем coqui-ai-TTS (fork з падтрымкай BE)
# ---------------------------------------------------------
REPO_URL = "https://github.com/tuteishygpt/coqui-ai-TTS.git"
REPO_DIR = "coqui-ai-TTS"

if not os.path.exists(REPO_DIR):
    # Клануем fork з беларускай падтрымкай
    subprocess.run(
        ["git", "clone", REPO_URL, REPO_DIR],
        check=True,
    )

# Дадаём корань рэпазіторыя ў sys.path, каб "import TTS" бачыў пакет
repo_root = os.path.abspath(REPO_DIR)
if repo_root not in sys.path:
    sys.path.insert(0, repo_root)

from TTS.tts.configs.xtts_config import XttsConfig
from TTS.tts.models.xtts import Xtts
# ✅ Выкарыстоўваем токенайзер з coqui TTS замест underthesea
from TTS.tts.layers.xtts.tokenizer import (
    split_sentence,
    VoiceBpeTokenizer,
)

# ---------------------------------------------------------
# 2. Шляхі да файлаў мадэлі
# ---------------------------------------------------------
repo_id = "archivartaunik/BE_XTTS_V2_10ep250k"
model_dir = "./model"
os.makedirs(model_dir, exist_ok=True)

checkpoint_file = os.path.join(model_dir, "model.pth")
config_file = os.path.join(model_dir, "config.json")
vocab_file = os.path.join(model_dir, "vocab.json")
default_voice_file = os.path.join(model_dir, "voice.wav")

if not os.path.exists(checkpoint_file):
    hf_hub_download(repo_id, filename="model.pth", local_dir=model_dir)
if not os.path.exists(config_file):
    hf_hub_download(repo_id, filename="config.json", local_dir=model_dir)
if not os.path.exists(vocab_file):
    hf_hub_download(repo_id, filename="vocab.json", local_dir=model_dir)
if not os.path.exists(default_voice_file):
    hf_hub_download(repo_id, filename="voice.wav", local_dir=model_dir)

# ---------------------------------------------------------
# 3. Загрузка мадэлі і токенайзера
# ---------------------------------------------------------
config = XttsConfig()
config.load_json(config_file)
XTTS_MODEL = Xtts.init_from_config(config)
XTTS_MODEL.load_checkpoint(
    config,
    checkpoint_path=checkpoint_file,
    vocab_path=vocab_file,
    use_deepspeed=False,
)

device = "cuda:0" if torch.cuda.is_available() else "cpu"
XTTS_MODEL.to(device)
sampling_rate = int(XTTS_MODEL.config.audio["sample_rate"])

# Ініцыялізуем VoiceBpeTokenizer і падкладаем у мадэль
tokenizer = VoiceBpeTokenizer(vocab_file=vocab_file)
XTTS_MODEL.tokenizer = tokenizer

# ---------------------------------------------------------
# 4. Функцыя TTS (з токенайзерам)
# ---------------------------------------------------------
@spaces.GPU(duration=60)
def text_to_speech(belarusian_story, speaker_audio_file=None):
    if not belarusian_story or belarusian_story.strip() == "":
        raise gr.Error("Увядзі хоць нейкі тэкст 🙂")

    # калі аўдыё не перададзена — бярэм голас па змаўчанні
    if not speaker_audio_file or (
        not isinstance(speaker_audio_file, str)
        and getattr(speaker_audio_file, "name", "") == ""
    ):
        speaker_audio_file = default_voice_file

    try:
        gpt_cond_latent, speaker_embedding = XTTS_MODEL.get_conditioning_latents(
            audio_path=speaker_audio_file,
            gpt_cond_len=XTTS_MODEL.config.gpt_cond_len,
            max_ref_length=XTTS_MODEL.config.max_ref_len,
            sound_norm_refs=XTTS_MODEL.config.sound_norm_refs,
        )
    except Exception as e:
        raise gr.Error(f"Памылка пры атрыманні латэнтаў голасу: {e}")

    # ✅ Замяняем sent_tokenize на split_sentence з токенайзера
    try:
        lang = "be"
        chunk_limit = tokenizer.char_limits.get(lang, 250)
        tts_texts = split_sentence(
            belarusian_story.strip(),
            lang=lang,
            text_split_length=chunk_limit,
        )
        tts_texts = [s.strip() for s in tts_texts if s and s.strip()]
        if not tts_texts:
            raise gr.Error("Не атрымалася падзяліць тэкст на сказы/чанкі.")
    except Exception as e:
        raise gr.Error(f"Памылка пры падзеле тэксту на сказы: {e}")

    all_wavs = []
    for text in tqdm(tts_texts):
        try:
            with torch.no_grad():
                wav_chunk = XTTS_MODEL.inference(
                    text=text,
                    language="be",
                    gpt_cond_latent=gpt_cond_latent,
                    speaker_embedding=speaker_embedding,
                    temperature=0.1,
                    length_penalty=1.0,
                    repetition_penalty=10.0,
                    top_k=10,
                    top_p=0.3,
                )
            all_wavs.append(wav_chunk["wav"])
        except Exception as e:
            raise gr.Error(f"Памылка пры генерырацыі аўдыя: {e}")

    try:
        out_wav = np.concatenate(all_wavs).astype(np.float32)
    except ValueError:
        raise gr.Error(
            "Немагчыма згенераваць аўдыё. Праверце ўваходны тэкст і аўдыёфайл."
        )
    except Exception as e:
        raise gr.Error(f"Памылка пры аб'яднанні аўдыя: {e}")

    temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".wav")
    write(temp_file.name, sampling_rate, out_wav)

    return temp_file.name


# ---------------------------------------------------------
# 5. Прыклады
# ---------------------------------------------------------
examples = [
    [
        "Такім чынам, клуб стаў уладальнікам усіх існых на сёння міжнародных трафеяў паўднёваамерыканскага футболу.",
        "Nestarka.wav",
        "krai.wav",
    ],
    [
        "Яму не ўдалося палепшыць фінансавае становішча каралеўства, а, наадварот, прыйшлося распрадаваць каштоўнасці чэшскай кароны.",
        "muzh.wav",
        "examples/цуды.wav",
    ],
    [
        "Кампілятарамі называюць праграмы, якія пераўтвараюць код вышэйшага ўзроўню ў код ніжэйшага ўзроўню.",
        "chunk_100.wav",
        "examples/надВозерам.wav",
    ],
    [
        "Акрамя таго, ліхачы аддаюць перавагу рэгі, хіп-хопу і класічнай музыцы.",
        "d1015.mp3",
        "examples/Беларусь.wav",
    ],
    [
        "Позірк можа быць уважлівым, зацікаўленым, захопленым, але бывае і нахабным, задзірлівым, пагардлівым, напышлівым.",
        "donarka_ench.wav",
        "examples/цуды.wav",
    ],
    [
        "Такі нават шчыры, ці што: родная мова народу – трасянка, а беларуская яму чужая!",
        "muzhcynski.wav",
        "examples/цуды.wav",
    ],
]

analytics_script = """
<script async src="https://www.googletagmanager.com/gtag/js?id=G-TKDCRCQ7FK"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-TKDCRCQ7FK');
</script>
"""

demo = gr.Blocks()
with demo:
    gr.HTML(analytics_script)
    gr.Interface(
        fn=text_to_speech,
        inputs=[
            gr.Textbox(lines=5, label="Тэкст на беларускай мове"),
            gr.Audio(
                type="filepath",
                label="Прыклад голасу (без іншых гукаў) не карацей 7 секунд",
                interactive=True,
            ),
        ],
        outputs=gr.Audio(
            type="filepath",
            label="Згенераванае аўдыя",
        ),
        title="Belarusian TTS Demo",
        description="""
        <p>Увядзіце тэкст, і мадэль пераўтворыць яго ў аўдыя. Вы можаце выкарыстоўваць 
        голас па змаўчанні, абраць голас з прыкладаў унізе ці загрузіць уласны файл 
        або запісаць аўдыё.</p>

        <p><strong>Карысныя парады:</strong></p>
        <ul>
            <li>Выкарыстоўвайце прыклады з добрай якасцю, без іншых гукаў і разнастайнай інтанацыяй, 
            ад яе моцна залежыць вынік.</li>
            <li>Інтанацыя таксама ўплывае на націскі.</li>
            <li>Прыклады галасоў могуць быць на любой мове.</li>
        </ul>

        <p>Каб палепшыць якасць мадэлі (націскі і дакладнасць кланавання галасоў), патрэбны дадатковыя датасэты. 
        Ахвяруйце свой голас праз <a href="https://Donar.by" target="_blank">Donar.by</a></p>

        <p>Далучайцеся да нашай беларускай суполкі ў ТГ, каб дапамагчы ці даведацца пра навіны ШІ: 
        <a href="https://t.me/SHibelChat" target="_blank">https://t.me/SHibelChat</a>.</p>

        <p><strong>Падтрымаць праект:</strong> <a href="https://buymeacoffee.com/tuteishygpt" target="_blank">Buy Me a Coffee</a></p>
        """,
        examples=examples,
        cache_examples=False,
    )

if __name__ == "__main__":
    demo.launch()