AthanasiusRG commited on
Commit
811537a
·
verified ·
1 Parent(s): 29795a4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +263 -35
app.py CHANGED
@@ -6,24 +6,41 @@ from fastapi import FastAPI, UploadFile, File, Form, HTTPException
6
  from fastapi.responses import FileResponse
7
  from core.cloner import KokoClone
8
 
9
- # --- Chargement des modèles (une seule fois) ---
10
  print("Loading KokoClone models...")
11
  cloner = KokoClone()
12
 
13
- # --- FastAPI montée SUR l'app Gradio ---
14
- def clone_voice(text, lang, ref_audio_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  if not text or not text.strip():
16
  raise gr.Error("Please enter some text.")
17
  if not ref_audio_path:
18
  raise gr.Error("Please upload or record a reference audio file.")
19
- output_file = "gradio_output.wav"
20
  try:
21
  cloner.generate(text=text, lang=lang, reference_audio=ref_audio_path, output_path=output_file)
22
  return output_file
23
  except Exception as e:
24
- raise gr.Error(f"Error: {str(e)}")
 
25
 
26
- def convert_voice(source_audio_path, ref_audio_path):
 
27
  if not source_audio_path:
28
  raise gr.Error("Please upload a source audio file.")
29
  if not ref_audio_path:
@@ -33,46 +50,247 @@ def convert_voice(source_audio_path, ref_audio_path):
33
  cloner.convert(source_audio=source_audio_path, reference_audio=ref_audio_path, output_path=output_file)
34
  return output_file
35
  except Exception as e:
36
- raise gr.Error(f"Error: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- # --- UI Gradio (inchangée) ---
39
- with gr.Blocks() as demo:
40
- gr.Markdown("<div style='text-align:center'><h1>🎧 KokoClone</h1></div>")
41
  with gr.Tabs():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  with gr.Tab("🎤 Text → Clone"):
 
43
  with gr.Row():
44
- with gr.Column():
45
- text_input = gr.Textbox(label="Text", lines=4)
46
- lang_input = gr.Dropdown(
47
- choices=[("English","en"),("French","fr"),("Hindi","hi"),
48
- ("Japanese","ja"),("Chinese","zh"),("Spanish","es")],
49
- value="en", label="Language"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  )
51
- ref_audio = gr.Audio(label="Reference Voice", type="filepath")
52
- btn = gr.Button("🚀 Generate", variant="primary")
53
- with gr.Column():
54
- out_audio = gr.Audio(label="Output", interactive=False)
55
- btn.click(clone_voice, inputs=[text_input, lang_input, ref_audio], outputs=out_audio)
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  with gr.Tab("🔁 Audio → Clone"):
 
58
  with gr.Row():
59
- with gr.Column():
60
- src_audio = gr.Audio(label="Source Audio", type="filepath")
61
- ref_audio2 = gr.Audio(label="Reference Voice", type="filepath")
62
- btn2 = gr.Button("🔁 Convert", variant="primary")
63
- with gr.Column():
64
- out_audio2 = gr.Audio(label="Output", interactive=False)
65
- btn2.click(convert_voice, inputs=[src_audio, ref_audio2], outputs=out_audio2)
66
-
67
- # --- Montage FastAPI ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  app = gr.mount_gradio_app(FastAPI(), demo, path="/")
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  @app.post("/api/clone")
71
  async def api_clone(
72
  text: str = Form(...),
73
  lang: str = Form(default="en"),
74
- reference_audio: UploadFile = File(...)
75
  ):
 
 
 
 
 
 
 
76
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as ref_tmp:
77
  shutil.copyfileobj(reference_audio.file, ref_tmp)
78
  ref_path = ref_tmp.name
@@ -83,13 +301,21 @@ async def api_clone(
83
  except Exception as e:
84
  raise HTTPException(status_code=500, detail=str(e))
85
  finally:
86
- os.unlink(ref_path)
 
 
87
 
88
  @app.post("/api/convert")
89
  async def api_convert(
90
  source_audio: UploadFile = File(...),
91
- reference_audio: UploadFile = File(...)
92
  ):
 
 
 
 
 
 
93
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as src_tmp:
94
  shutil.copyfileobj(source_audio.file, src_tmp)
95
  src_path = src_tmp.name
@@ -103,5 +329,7 @@ async def api_convert(
103
  except Exception as e:
104
  raise HTTPException(status_code=500, detail=str(e))
105
  finally:
106
- os.unlink(src_path)
107
- os.unlink(ref_path)
 
 
 
6
  from fastapi.responses import FileResponse
7
  from core.cloner import KokoClone
8
 
9
+ # ── Chargement des modèles (une seule fois au démarrage) ──────────────────────
10
  print("Loading KokoClone models...")
11
  cloner = KokoClone()
12
 
13
+
14
+ # ── Handlers Gradio ───────────────────────────────────────────────────────────
15
+
16
+ def gradio_speak(text, lang, speed):
17
+ """TTS simple sans clone."""
18
+ if not text or not text.strip():
19
+ raise gr.Error("Veuillez entrer du texte.")
20
+ output_file = "gradio_speak_output.wav"
21
+ try:
22
+ cloner.speak(text=text, lang=lang, output_path=output_file, speed=float(speed))
23
+ return output_file
24
+ except Exception as e:
25
+ raise gr.Error(f"Erreur : {str(e)}")
26
+
27
+
28
+ def gradio_clone(text, lang, ref_audio_path):
29
+ """TTS + voice cloning."""
30
  if not text or not text.strip():
31
  raise gr.Error("Please enter some text.")
32
  if not ref_audio_path:
33
  raise gr.Error("Please upload or record a reference audio file.")
34
+ output_file = "gradio_clone_output.wav"
35
  try:
36
  cloner.generate(text=text, lang=lang, reference_audio=ref_audio_path, output_path=output_file)
37
  return output_file
38
  except Exception as e:
39
+ raise gr.Error(f"An error occurred: {str(e)}")
40
+
41
 
42
+ def gradio_convert(source_audio_path, ref_audio_path):
43
+ """Re-voicing audio source."""
44
  if not source_audio_path:
45
  raise gr.Error("Please upload a source audio file.")
46
  if not ref_audio_path:
 
50
  cloner.convert(source_audio=source_audio_path, reference_audio=ref_audio_path, output_path=output_file)
51
  return output_file
52
  except Exception as e:
53
+ raise gr.Error(f"An error occurred: {str(e)}")
54
+
55
+
56
+ # ── Interface Gradio ──────────────────────────────────────────────────────────
57
+
58
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
59
+ gr.Markdown(
60
+ """
61
+ <div style="text-align: center;">
62
+ <h1>🎧 KokoClone</h1>
63
+ <p>Synthèse vocale multilingue · Voice Cloning · Re-voicing</p>
64
+ </div>
65
+ """
66
+ )
67
 
 
 
 
68
  with gr.Tabs():
69
+
70
+ # ── Onglet 1 : TTS Simple ─────────────────────────────────────────────
71
+ with gr.Tab("🗣️ Simple TTS"):
72
+ gr.Markdown(
73
+ """
74
+ Génère la parole directement avec une voix Kokoro — **aucun audio de référence requis**.
75
+ Idéal pour l'anglais et le français dans MikoHelper.
76
+ """
77
+ )
78
+ with gr.Row():
79
+ with gr.Column(scale=1):
80
+ speak_text = gr.Textbox(
81
+ label="Texte à lire",
82
+ lines=5,
83
+ placeholder="Hello! How are you today?\nBonjour ! Comment allez-vous ?"
84
+ )
85
+ speak_lang = gr.Dropdown(
86
+ label="Langue / Voix",
87
+ choices=[
88
+ ("🇬🇧 English — af_bella (femme américaine)", "en"),
89
+ ("🇫🇷 Français — ff_siwis (femme française)", "fr"),
90
+ ("🇮🇳 Hindi — hf_alpha", "hi"),
91
+ ("🇪🇸 Español — im_nicola", "es"),
92
+ ("🇮🇹 Italiano — im_nicola", "it"),
93
+ ("🇧🇷 Português — pf_dora", "pt"),
94
+ ],
95
+ value="en"
96
+ )
97
+ speak_speed = gr.Slider(
98
+ label="Vitesse",
99
+ minimum=0.5,
100
+ maximum=1.5,
101
+ value=1.0,
102
+ step=0.1
103
+ )
104
+ speak_btn = gr.Button("🗣️ Générer l'audio", variant="primary", size="lg")
105
+
106
+ with gr.Column(scale=1):
107
+ speak_out = gr.Audio(
108
+ label="Audio généré",
109
+ interactive=False,
110
+ autoplay=True
111
+ )
112
+ gr.Markdown(
113
+ """
114
+ ### 💡 Voix disponibles
115
+ - **EN** `af_bella` — femme, accent américain, claire et naturelle
116
+ - **FR** `ff_siwis` — femme, accent français, fluide
117
+ - **HI** `hf_alpha` — Hindi
118
+ - **ES/IT** `im_nicola` — voix masculine
119
+ - **PT** `pf_dora` — Portugais brésilien
120
+
121
+ > Ajustez la **vitesse** pour les apprenants débutants (0.7–0.8 recommandé).
122
+ """
123
+ )
124
+
125
+ speak_btn.click(
126
+ fn=lambda: gr.update(value="⌛ Génération en cours...", interactive=False),
127
+ outputs=speak_btn
128
+ ).then(
129
+ fn=gradio_speak,
130
+ inputs=[speak_text, speak_lang, speak_speed],
131
+ outputs=speak_out
132
+ ).then(
133
+ fn=lambda: gr.update(value="🗣️ Générer l'audio", interactive=True),
134
+ outputs=speak_btn
135
+ )
136
+
137
+ # ── Onglet 2 : Text → Clone ───────────────────────────────────────────
138
  with gr.Tab("🎤 Text → Clone"):
139
+ gr.Markdown("Synthétise le texte puis applique la voix d'un audio de référence.")
140
  with gr.Row():
141
+ with gr.Column(scale=1):
142
+ clone_text = gr.Textbox(
143
+ label="1. Texte à synthétiser",
144
+ lines=4,
145
+ placeholder="Enter the text you want spoken..."
146
+ )
147
+ clone_lang = gr.Dropdown(
148
+ label="2. Langue",
149
+ choices=[
150
+ ("English", "en"),
151
+ ("Français", "fr"),
152
+ ("Hindi", "hi"),
153
+ ("Japanese", "ja"),
154
+ ("Chinese", "zh"),
155
+ ("Español", "es"),
156
+ ("Italiano", "it"),
157
+ ("Português", "pt"),
158
+ ],
159
+ value="en"
160
+ )
161
+ clone_ref = gr.Audio(
162
+ label="3. Audio de référence (upload ou enregistrement)",
163
+ type="filepath"
164
  )
165
+ clone_btn = gr.Button("🚀 Générer avec clonage", variant="primary", size="lg")
 
 
 
 
166
 
167
+ with gr.Column(scale=1):
168
+ clone_out = gr.Audio(
169
+ label="Audio cloné",
170
+ interactive=False,
171
+ autoplay=False
172
+ )
173
+ gr.Markdown(
174
+ """
175
+ ### 💡 Conseils
176
+ - Audio de référence propre, sans bruit de fond
177
+ - 3 à 10 secondes = résultat optimal
178
+ - La langue sélectionnée doit correspondre au texte
179
+ """
180
+ )
181
+
182
+ clone_btn.click(
183
+ fn=lambda: gr.update(value="⌛ Génération...", interactive=False),
184
+ outputs=clone_btn
185
+ ).then(
186
+ fn=gradio_clone,
187
+ inputs=[clone_text, clone_lang, clone_ref],
188
+ outputs=clone_out
189
+ ).then(
190
+ fn=lambda: gr.update(value="🚀 Générer avec clonage", interactive=True),
191
+ outputs=clone_btn
192
+ )
193
+
194
+ # ── Onglet 3 : Audio → Clone ──────────────────────────────────────────
195
  with gr.Tab("🔁 Audio → Clone"):
196
+ gr.Markdown("Re-voice un audio existant pour qu'il sonne comme la voix de référence.")
197
  with gr.Row():
198
+ with gr.Column(scale=1):
199
+ convert_src = gr.Audio(
200
+ label="1. Audio source (parole à re-voicer)",
201
+ type="filepath"
202
+ )
203
+ convert_ref = gr.Audio(
204
+ label="2. Voix de référence (locuteur cible)",
205
+ type="filepath"
206
+ )
207
+ convert_btn = gr.Button("🔁 Convertir la voix", variant="primary", size="lg")
208
+
209
+ with gr.Column(scale=1):
210
+ convert_out = gr.Audio(
211
+ label="Audio converti",
212
+ interactive=False,
213
+ autoplay=False
214
+ )
215
+ gr.Markdown(
216
+ """
217
+ ### 💡 Comment ça marche
218
+ - Uploadez n'importe quel enregistrement vocal comme **source**
219
+ - Uploadez un court clip du **locuteur cible** comme référence
220
+ - KokoClone re-voice la source pour sonner comme la référence
221
+ - Aucune transcription nécessaire !
222
+ """
223
+ )
224
+
225
+ convert_btn.click(
226
+ fn=lambda: gr.update(value="⌛ Conversion...", interactive=False),
227
+ outputs=convert_btn
228
+ ).then(
229
+ fn=gradio_convert,
230
+ inputs=[convert_src, convert_ref],
231
+ outputs=convert_out
232
+ ).then(
233
+ fn=lambda: gr.update(value="🔁 Convertir la voix", interactive=True),
234
+ outputs=convert_btn
235
+ )
236
+
237
+
238
+ # ── FastAPI montée sur Gradio ─────────────────────────────────────────────────
239
+
240
  app = gr.mount_gradio_app(FastAPI(), demo, path="/")
241
 
242
+
243
+ @app.get("/api/voices")
244
+ def list_voices():
245
+ """Liste toutes les voix disponibles."""
246
+ return {
247
+ "voices": [
248
+ {"lang": "en", "voice": "af_bella", "description": "English - American Female"},
249
+ {"lang": "fr", "voice": "ff_siwis", "description": "French - Female"},
250
+ {"lang": "hi", "voice": "hf_alpha", "description": "Hindi - Female"},
251
+ {"lang": "es", "voice": "im_nicola", "description": "Spanish - Male"},
252
+ {"lang": "it", "voice": "im_nicola", "description": "Italian - Male"},
253
+ {"lang": "pt", "voice": "pf_dora", "description": "Portuguese BR - Female"},
254
+ ]
255
+ }
256
+
257
+
258
+ @app.post("/api/speak")
259
+ async def api_speak(
260
+ text: str = Form(...),
261
+ lang: str = Form(default="en"),
262
+ speed: float = Form(default=1.0),
263
+ ):
264
+ """
265
+ TTS simple sans clone.
266
+ - text : texte à synthétiser
267
+ - lang : 'en', 'fr', 'hi', 'es', 'it', 'pt'
268
+ - speed : vitesse (0.5 – 1.5), défaut 1.0
269
+ Retourne un fichier WAV.
270
+ """
271
+ out_path = tempfile.mktemp(suffix=".wav")
272
+ try:
273
+ cloner.speak(text=text, lang=lang, output_path=out_path, speed=speed)
274
+ return FileResponse(out_path, media_type="audio/wav", filename="speech.wav")
275
+ except ValueError as e:
276
+ raise HTTPException(status_code=400, detail=str(e))
277
+ except Exception as e:
278
+ raise HTTPException(status_code=500, detail=str(e))
279
+
280
+
281
  @app.post("/api/clone")
282
  async def api_clone(
283
  text: str = Form(...),
284
  lang: str = Form(default="en"),
285
+ reference_audio: UploadFile = File(...),
286
  ):
287
+ """
288
+ TTS + voice cloning.
289
+ - text : texte à synthétiser
290
+ - lang : langue
291
+ - reference_audio : fichier audio WAV de référence
292
+ Retourne un fichier WAV cloné.
293
+ """
294
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as ref_tmp:
295
  shutil.copyfileobj(reference_audio.file, ref_tmp)
296
  ref_path = ref_tmp.name
 
301
  except Exception as e:
302
  raise HTTPException(status_code=500, detail=str(e))
303
  finally:
304
+ if os.path.exists(ref_path):
305
+ os.unlink(ref_path)
306
+
307
 
308
  @app.post("/api/convert")
309
  async def api_convert(
310
  source_audio: UploadFile = File(...),
311
+ reference_audio: UploadFile = File(...),
312
  ):
313
+ """
314
+ Re-voicing audio source → voix de référence.
315
+ - source_audio : parole à re-voicer
316
+ - reference_audio : locuteur cible
317
+ Retourne un fichier WAV converti.
318
+ """
319
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as src_tmp:
320
  shutil.copyfileobj(source_audio.file, src_tmp)
321
  src_path = src_tmp.name
 
329
  except Exception as e:
330
  raise HTTPException(status_code=500, detail=str(e))
331
  finally:
332
+ if os.path.exists(src_path):
333
+ os.unlink(src_path)
334
+ if os.path.exists(ref_path):
335
+ os.unlink(ref_path)