File size: 10,481 Bytes
175a497
 
2ebd303
dd217c7
d24a68b
e35df77
2ebd303
d24a68b
dd217c7
 
d24a68b
 
f8da648
dd217c7
2ebd303
 
dd217c7
 
 
 
 
 
 
 
 
 
 
 
9bf0190
 
7804f9c
15e444a
6249865
9bf0190
15e444a
6249865
 
 
15e444a
 
 
 
6249865
 
9bf0190
 
7804f9c
 
9bf0190
 
e82eef5
9bf0190
 
08eb64a
9bf0190
 
 
 
7804f9c
a674527
9bf0190
 
 
 
 
 
 
a674527
0978fba
9bf0190
0978fba
ebbe300
1df5e0e
d986efa
d37849f
 
da63fff
 
d37849f
b6584c2
d37849f
d4320d1
d37849f
1df5e0e
d37849f
9bf0190
d37849f
d986efa
dd217c7
 
d24a68b
 
dd217c7
 
d986efa
dd217c7
 
 
9bf0190
 
a674527
d986efa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4dab15f
0559e57
d986efa
 
 
 
9bf0190
e09b9e9
d986efa
 
 
 
e09b9e9
 
 
d986efa
 
 
 
0559e57
d986efa
 
 
 
4dab15f
d986efa
 
 
 
 
 
 
0cc615c
d986efa
 
 
 
 
 
 
 
26a9ffe
 
1384004
 
 
 
26a9ffe
1384004
ebbe300
 
 
 
 
 
 
 
9bf0190
e09b9e9
d986efa
4dab15f
d986efa
26a9ffe
d986efa
 
 
4dab15f
d986efa
 
d4320d1
26a9ffe
9bf0190
d986efa
26a9ffe
d986efa
9bf0190
26a9ffe
 
 
 
 
9bf0190
26a9ffe
d986efa
26a9ffe
9bf0190
d986efa
849ade7
 
d986efa
 
 
d4320d1
9bf0190
d986efa
 
 
9bf0190
26a9ffe
d986efa
 
dce6ff7
 
 
035bd50
9bf0190
dce6ff7
d986efa
 
9bf0190
d986efa
83c804a
 
 
 
9bf0190
1384004
83c804a
 
 
 
 
 
 
 
 
 
ebbe300
83c804a
9bf0190
83c804a
 
 
 
 
9bf0190
83c804a
 
9bf0190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a674527
 
 
 
 
175a497
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
import nltk
nltk.download('punkt_tab')
from sentence_analyzer import SentenceAnalyzer
import re
import tempfile
from collections import OrderedDict
from importlib.resources import files
import click
import gradio as gr
import numpy as np
import soundfile as sf
import torchaudio
import torch
from cached_path import cached_path
from transformers import AutoModelForCausalLM, AutoTokenizer

try:
    import spaces
    USING_SPACES = True
except ImportError:
    USING_SPACES = False

def gpu_decorator(func):
    if USING_SPACES:
        return spaces.GPU(func)
    else:
        return func

# Importando a nova API F5TTS
from f5_tts.api import F5TTS

import os
from huggingface_hub import hf_hub_download

def load_f5tts():
    # Carrega o caminho do repositório e o nome do arquivo das variáveis de ambiente
    repo_id = os.getenv("MODEL_REPO_ID", "SWivid/F5-TTS/F5TTS_Base")
    filename = os.getenv("MODEL_FILENAME", "model_1200000.safetensors")
    token = os.getenv("HUGGINGFACE_TOKEN")
    # Valida se o token está presente
    if not token:
        raise ValueError("A variável de ambiente 'HUGGINGFACE_TOKEN' não foi definida.")
    # Faz o download do modelo do repositório privado
    ckpt_path = hf_hub_download(repo_id=repo_id, filename=filename, use_auth_token=token)

    # Define as configurações do modelo (ajuste se necessário)
    F5TTS_model_cfg = dict(dim=1024, depth=22, heads=16, ff_mult=2, text_dim=512, conv_layers=4)

    # Retorna a instância da API F5TTS
    return F5TTS(
        model_type="F5-TTS",  # Ajuste o nome do modelo se necessário
        ckpt_file=ckpt_path,
        vocab_file=os.path.join(os.path.dirname(ckpt_path), "vocab.txt"), # Caminho para o arquivo vocab.txt
        device="cuda" if torch.cuda.is_available() else "cpu", # Define o dispositivo
        use_ema=True
    )

# Carregar modelo F5TTS usando a nova API
F5TTS_ema_model = load_f5tts()

# Variáveis globais para o cache
last_checkpoint = None
last_device = None
last_ema = None
tts_api = None
training_process = None  # Adicione esta linha se necessário para o seu contexto

@gpu_decorator
def infer(
    ref_audio_orig, ref_text, gen_text, remove_silence, cross_fade_duration=0.15, speed=1, nfe=32, show_info=gr.Info, seed=-1 
):
    print(nfe)
    ref_audio, ref_text = preprocess_ref_audio_text(ref_audio_orig, ref_text, show_info=show_info)
    ema_model = F5TTS_ema_model
    final_wave, final_sample_rate, combined_spectrogram = infer_process(
        ref_audio,
        ref_text.lower().strip(),
        gen_text.lower().strip(),
        ema_model,
        vocoder,
        cross_fade_duration=cross_fade_duration,
        nfe_step=nfe,
        speed=speed,
        show_info=show_info,
        progress=gr.Progress(),
        seed=seed  # Passando o seed para infer_process
    )
    # Remover silêncios
    if remove_silence:
        with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as f:
            sf.write(f.name, final_wave, final_sample_rate)
            remove_silence_for_generated_wav(f.name)
            final_wave, _ = torchaudio.load(f.name)
        final_wave = final_wave.squeeze().cpu().numpy()
    # Salvar espectrograma
    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_spectrogram:
        spectrogram_path = tmp_spectrogram.name
        save_spectrogram(combined_spectrogram, spectrogram_path)
    return (final_sample_rate, final_wave), spectrogram_path, ref_text, seed # Retornando o seed


# Estilos CSS
custom_css = """
#sentences-container {
    border: 1px solid #ddd;
    border-radius: 4px;
    padding: 10px;
    margin-bottom: 10px;
}
.sentence-box {
    border: 1px solid #eee;
    padding: 5px;
    margin-bottom: 5px;
    border-radius: 4px;
    background-color: #f9f9f9;
}
"""

with gr.Blocks(css=custom_css) as app:
    with gr.Tabs():
        with gr.Tab("TTS Básico"):
            gr.Markdown("# TTS Básico com F5-TTS")

            # Entradas básicas
            ref_audio_input = gr.Audio(label="Áudio de Referência", type="filepath")
            gen_text_input = gr.Textbox(label="Texto para Gerar", lines=10)
            generate_btn = gr.Button("Sintetizar", variant="primary")

            # Configurações avançadas
            gr.Markdown("### Configurações Avançadas")
            with gr.Accordion("Expandir Configurações Avançadas", open=False):
                ref_text_input = gr.Textbox(
                    label="Texto de Referência",
                    info="Deixe em branco para transcrever automaticamente o áudio de referência. Se você inserir texto, ele substituirá a transcrição automática.",
                    lines=2,
                )
                remove_silence = gr.Checkbox(
                    label="Remover Silêncios",
                    info="O modelo tende a produzir silêncios, especialmente em áudios mais longos. Podemos remover manualmente os silêncios, se necessário. Isso também aumentará o tempo de geração.",
                    value=False,
                )
                speed_slider = gr.Slider(
                    label="Velocidade",
                    minimum=0.3,
                    maximum=2.0,
                    value=1.0,
                    step=0.1,
                    info="Ajuste a velocidade do áudio.",
                )
                cross_fade_duration_slider = gr.Slider(
                    label="Duração do Cross-fade (s)",
                    minimum=0.0,
                    maximum=1.0,
                    value=0.15,
                    step=0.01,
                    info="Defina a duração do cross-fade entre os clipes de áudio.",
                )
                chunk_size_slider = gr.Slider(
                    label="Número de Sentenças por Chunk",
                    minimum=1,
                    maximum=10,
                    value=1,
                    step=1,
                    info="Defina quantas sentenças serão processadas em cada chunk.",
                )
                nfe_slider = gr.Slider(
                    label="NFE",
                    minimum=16,
                    maximum=64,
                    value=32,
                    step=1,
                    info="Ajuste NFE Step.",
                )
                seed_input = gr.Number(label="Seed", value=-1, minimum=-1)  # Seed na seção avançada

            analyzer = SentenceAnalyzer()

            @gpu_decorator
            def process_chunks(
                ref_audio_input,
                ref_text_input,
                gen_text_input,
                remove_silence,
                cross_fade_duration_slider,
                speed_slider,
                nfe_slider,
                chunk_size_slider,
                seed_input,  # Passando o seed para process_chunks
            ):
                # Dividir o texto em sentenças
                sentences = analyzer.split_into_sentences(gen_text_input)

                # Agrupar sentenças em chunks
                chunks = [
                    " ".join(sentences[i : i + chunk_size_slider])
                    for i in range(0, len(sentences), chunk_size_slider)
                ]

                # Processar cada chunk
                audio_segments = []
                for chunk in chunks:
                    audio_out, spectrogram_path, ref_text_out, seed_output = infer(  # Recebendo o seed de infer
                        ref_audio_input,
                        ref_text_input,  # Utiliza o Texto de Referência como está
                        chunk,  # Processa o chunk atual
                        remove_silence,
                        cross_fade_duration_slider,
                        speed_slider,
                        nfe_slider,
                        seed=seed_input,  # Passando o seed para infer
                    )
                    sr, audio_data = audio_out
                    audio_segments.append(audio_data)

                # Concatenar os segmentos de áudio gerados
                if audio_segments:
                    final_audio_data = np.concatenate(audio_segments)
                    return (
                        (sr, final_audio_data),  # Áudio final
                        spectrogram_path,  # Espectrograma
                        gr.update(value=ref_text_out),  # Nenhuma mudança no Texto de Referência
                        seed_output  # Retornando o seed
                    )
                else:
                    gr.Warning("Nenhum áudio gerado.")
                    return None, None, gr.update(), None  # Retornando None para o seed

            # Saídas
            gr.Markdown("### Resultados")
            audio_output = gr.Audio(label="Áudio Sintetizado")
            spectrogram_output = gr.Image(label="Espectrograma")
            seed_output = gr.Text(label="Seed usada:")  # Saída do Seed

            # Associação do botão `generate_btn` à função `process_chunks`
            generate_btn.click(
                process_chunks,
                inputs=[
                    ref_audio_input,
                    ref_text_input,
                    gen_text_input,
                    remove_silence,
                    cross_fade_duration_slider,
                    speed_slider,
                    nfe_slider,
                    chunk_size_slider,
                    seed_input,  # Passando o seed como entrada
                ],
                outputs=[
                    audio_output,
                    spectrogram_output,
                    ref_text_input,  # Atualiza o texto de referência, se necessário
                    seed_output,  # Saída do Seed
                ],
            )


    # Código para iniciar a aplicação Gradio
    @click.command()
    @click.option("--port", "-p", default=None, type=int, help="Port to run the app on")
    @click.option("--host", "-H", default=None, help="Host to run the app on")
    @click.option(
        "--share",
        "-s",
        default=False,
        is_flag=True,
        help="Share the app via Gradio share link",
    )
    @click.option("--api", "-a", default=True, is_flag=True, help="Allow API access")
    def main(port, host, share, api):
        global app
        print("Starting app...")
        app.queue(api_open=api).launch(server_name=host, server_port=port, share=share, show_api=api)

if __name__ == "__main__":
    if not USING_SPACES:
        main()
    else:
        app.queue().launch()