File size: 10,077 Bytes
a26f93a
5756b47
 
b35040f
5756b47
 
b35040f
 
 
5756b47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b35040f
5756b47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ec99653
b35040f
 
 
 
 
5756b47
 
b35040f
5756b47
 
 
 
 
 
 
 
 
 
 
 
 
b35040f
5756b47
 
e596c07
5756b47
 
e596c07
5756b47
b35040f
b57d4bf
b35040f
 
b57d4bf
b35040f
b57d4bf
 
 
 
 
 
 
 
b35040f
 
b57d4bf
9801a70
b35040f
b57d4bf
e452575
 
b57d4bf
b35040f
e452575
5ab2622
b57d4bf
b35040f
b57d4bf
 
 
 
b35040f
 
 
 
b57d4bf
 
b35040f
 
 
 
 
b57d4bf
b35040f
 
b57d4bf
b35040f
 
b57d4bf
 
b35040f
 
 
 
b57d4bf
b35040f
 
 
b57d4bf
 
b35040f
 
 
b57d4bf
 
b35040f
 
 
b57d4bf
 
b35040f
 
b57d4bf
b35040f
b57d4bf
b35040f
 
 
 
 
 
 
 
b57d4bf
b35040f
 
 
 
 
 
 
 
 
 
 
 
b57d4bf
b35040f
a26f93a
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
import spaces
from snac import SNAC
import torch
import gradio as gr
from transformers import AutoModelForCausalLM, AutoTokenizer
from huggingface_hub import snapshot_download
from dotenv import load_dotenv
load_dotenv()

# Vérifier si CUDA est disponible
device = "cuda" if torch.cuda.is_available() else "cpu"

print("Chargement du modèle SNAC...")
snac_model = SNAC.from_pretrained("hubertsiuzdak/snac_24khz")
snac_model = snac_model.to(device)

model_name = "canopylabs/3b-fr-ft-research_release"

# Télécharger uniquement la configuration du modèle et les safetensors
snapshot_download(
    repo_id=model_name,
    allow_patterns=[
        "config.json",
        "*.safetensors",
        "model.safetensors.index.json",
    ],
    ignore_patterns=[
        "optimizer.pt",
        "pytorch_model.bin",
        "training_args.bin",
        "scheduler.pt",
        "tokenizer.json",
        "tokenizer_config.json",
        "special_tokens_map.json",
        "vocab.json",
        "merges.txt",
        "tokenizer.*"
    ]
)

model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.bfloat16)
model.to(device)
tokenizer = AutoTokenizer.from_pretrained(model_name)
print(f"Modèle Orpheus chargé sur {device}")

# Traiter le texte d'entrée
def process_prompt(prompt, voice, tokenizer, device):
    prompt = f"{voice}: {prompt}"
    input_ids = tokenizer(prompt, return_tensors="pt").input_ids
    
    start_token = torch.tensor([[128259]], dtype=torch.int64)  # Début humain
    end_tokens = torch.tensor([[128009, 128260]], dtype=torch.int64)  # Fin du texte, Fin humain
    
    modified_input_ids = torch.cat([start_token, input_ids, end_tokens], dim=1)  # SOH SOT Texte EOT EOH
    
    # Pas besoin de padding pour une seule entrée
    attention_mask = torch.ones_like(modified_input_ids)
    
    return modified_input_ids.to(device), attention_mask.to(device)

# Analyser les tokens de sortie en audio
def parse_output(generated_ids):
    token_to_find = 128257
    token_to_remove = 128258
    
    token_indices = (generated_ids == token_to_find).nonzero(as_tuple=True)

    if len(token_indices[1]) > 0:
        last_occurrence_idx = token_indices[1][-1].item()
        cropped_tensor = generated_ids[:, last_occurrence_idx+1:]
    else:
        cropped_tensor = generated_ids

    processed_rows = []
    for row in cropped_tensor:
        masked_row = row[row != token_to_remove]
        processed_rows.append(masked_row)

    code_lists = []
    for row in processed_rows:
        row_length = row.size(0)
        new_length = (row_length // 7) * 7
        trimmed_row = row[:new_length]
        trimmed_row = [t - 128266 for t in trimmed_row]
        code_lists.append(trimmed_row)
        
    return code_lists[0]  # Retourner uniquement le premier pour un seul échantillon

# Redistribuer les codes pour la génération audio
def redistribute_codes(code_list, snac_model):
    device = next(snac_model.parameters()).device  # Obtenir le périphérique du modèle SNAC
    
    layer_1 = []
    layer_2 = []
    layer_3 = []
    for i in range((len(code_list)+1)//7):
        layer_1.append(code_list[7*i])
        layer_2.append(code_list[7*i+1]-4096)
        layer_3.append(code_list[7*i+2]-(2*4096))
        layer_3.append(code_list[7*i+3]-(3*4096))
        layer_2.append(code_list[7*i+4]-(4*4096))
        layer_3.append(code_list[7*i+5]-(5*4096))
        layer_3.append(code_list[7*i+6]-(6*4096))
        
    # Déplacer les tenseurs vers le même périphérique que le modèle SNAC
    codes = [
        torch.tensor(layer_1, device=device).unsqueeze(0),
        torch.tensor(layer_2, device=device).unsqueeze(0),
        torch.tensor(layer_3, device=device).unsqueeze(0)
    ]
    
    audio_hat = snac_model.decode(codes)
    return audio_hat.detach().squeeze().cpu().numpy()  # Toujours retourner un tableau numpy CPU

# Fonction principale de génération
@spaces.GPU()
def generate_speech(text, voice, temperature, top_p, repetition_penalty, max_new_tokens, progress=gr.Progress()):
    if not text.strip():
        return None
    
    try:
        progress(0.1, "Traitement du texte...")
        input_ids, attention_mask = process_prompt(text, voice, tokenizer, device)
        
        progress(0.3, "Génération des tokens de parole...")
        with torch.no_grad():
            generated_ids = model.generate(
                input_ids=input_ids,
                attention_mask=attention_mask,
                max_new_tokens=max_new_tokens,
                do_sample=True,
                temperature=temperature,
                top_p=top_p,
                repetition_penalty=repetition_penalty,
                num_return_sequences=1,
                eos_token_id=128258,
            )
        
        progress(0.6, "Traitement des tokens de parole...")
        code_list = parse_output(generated_ids)
        
        progress(0.8, "Conversion en audio...")
        audio_samples = redistribute_codes(code_list, snac_model)
        
        return (24000, audio_samples)  # Retourner le taux d'échantillonnage et l'audio
    except Exception as e:
        print(f"Erreur lors de la génération de la parole: {e}")
        return None

# Exemples pour l'interface utilisateur
examples = [
    ["Bonjour, je m'appelle Tara, <chuckle> et je suis un modèle de génération de parole qui peut ressembler à une personne.", "tara", 0.6, 0.95, 1.1, 1200],
    ["On m'a aussi appris à comprendre et à produire des éléments paralinguistiques <sigh> comme soupirer, ou <laugh> rire, ou <yawn> bâiller !", "dan", 0.7, 0.95, 1.1, 1200],
    ["J'habite à San Francisco, et j'ai, euh voyons voir, 3 milliards 7 cents... <gasp> bon, disons simplement beaucoup de paramètres.", "leah", 0.6, 0.9, 1.2, 1200],
    ["Parfois, quand je parle trop, j'ai besoin de <cough> m'excuser. <sniffle> Le temps a été assez froid dernièrement.", "leo", 0.65, 0.9, 1.1, 1200],
    ["Parler en public peut être difficile. <groan> Mais avec suffisamment de pratique, n'importe qui peut s'améliorer.", "jess", 0.7, 0.95, 1.1, 1200],
    ["La randonnée était épuisante mais la vue depuis le sommet était absolument à couper le souffle ! <sigh> Ça valait totalement le coup.", "mia", 0.65, 0.9, 1.15, 1200],
    ["Tu as entendu cette blague ? <laugh> Je n'ai pas pu m'arrêter de rire quand je l'ai entendue pour la première fois. <chuckle> C'est toujours drôle.", "zac", 0.7, 0.95, 1.1, 1200],
    ["Après avoir couru le marathon, j'étais tellement fatigué <yawn> et j'avais besoin d'un long repos. <sigh> Mais je me sentais accompli.", "zoe", 0.6, 0.95, 1.1, 1200]
]

# Voix disponibles
VOICES = ["tara", "leah", "jess", "leo", "dan", "mia", "zac", "zoe"]

# Balises émotives disponibles
EMOTIVE_TAGS = ["`<laugh>`", "`<chuckle>`", "`<sigh>`", "`<cough>`", "`<sniffle>`", "`<groan>`", "`<yawn>`", "`<gasp>`"]

# Créer l'interface Gradio
with gr.Blocks(title="Orpheus Text-to-Speech") as demo:
    gr.Markdown(f"""
    # 🎵 [Orpheus French Text-to-Speech](https://github.com/canopyai/Orpheus-TTS)
    Saisissez votre texte ci-dessous et écoutez-le transformé en parole naturelle avec le modèle Orpheus TTS.
    
    ## Conseils pour de meilleurs résultats:
    - Ajoutez des éléments paralinguistiques comme {", ".join(EMOTIVE_TAGS)} ou `euh` pour une parole plus humaine.
    - Les textes plus longs fonctionnent généralement mieux que les phrases très courtes
    - Augmenter `repetition_penalty` et `temperature` fait parler le modèle plus rapidement.
    """)    
    with gr.Row():
        with gr.Column(scale=3):
            text_input = gr.Textbox(
                label="Texte à prononcer", 
                placeholder="Entrez votre texte ici...",
                lines=5
            )
            voice = gr.Dropdown(
                choices=VOICES, 
                value="tara", 
                label="Voix"
            )
            
            with gr.Accordion("Paramètres avancés", open=False):
                temperature = gr.Slider(
                    minimum=0.1, maximum=1.5, value=0.6, step=0.05,
                    label="Température", 
                    info="Des valeurs plus élevées (0.7-1.0) créent une parole plus expressive mais moins stable"
                )
                top_p = gr.Slider(
                    minimum=0.1, maximum=1.0, value=0.95, step=0.05,
                    label="Top P", 
                    info="Seuil d'échantillonnage du noyau"
                )
                repetition_penalty = gr.Slider(
                    minimum=1.0, maximum=2.0, value=1.1, step=0.05,
                    label="Pénalité de répétition", 
                    info="Des valeurs plus élevées découragent les motifs répétitifs"
                )
                max_new_tokens = gr.Slider(
                    minimum=100, maximum=2000, value=1200, step=100,
                    label="Longueur maximale", 
                    info="Longueur maximale de l'audio généré (en tokens)"
                )
            
            with gr.Row():
                submit_btn = gr.Button("Générer la parole", variant="primary")
                clear_btn = gr.Button("Effacer")
                
        with gr.Column(scale=2):
            audio_output = gr.Audio(label="Parole générée", type="numpy")
            
    # Configuration des exemples
    gr.Examples(
        examples=examples,
        inputs=[text_input, voice, temperature, top_p, repetition_penalty, max_new_tokens],
        outputs=audio_output,
        fn=generate_speech,
        cache_examples=True,
    )
    
    # Configuration des gestionnaires d'événements
    submit_btn.click(
        fn=generate_speech,
        inputs=[text_input, voice, temperature, top_p, repetition_penalty, max_new_tokens],
        outputs=audio_output
    )
    
    clear_btn.click(
        fn=lambda: (None, None),
        inputs=[],
        outputs=[text_input, audio_output]
    )

# Lancer l'application
if __name__ == "__main__":
    demo.queue().launch(share=False, ssr_mode=False)