Randriamiandry commited on
Commit
8cc9b1a
·
1 Parent(s): bdabebb
Files changed (2) hide show
  1. app.py +68 -105
  2. requirements.txt +0 -6
app.py CHANGED
@@ -1,129 +1,92 @@
1
  import io
2
- import os
3
- import tempfile
4
  import numpy as np
5
- from fastapi import FastAPI, HTTPException, UploadFile, File, Form
6
- from fastapi.responses import Response, FileResponse
7
- from fastapi.staticfiles import StaticFiles
8
- from pydantic import BaseModel
9
  import soundfile as sf
10
- import time
 
 
11
  from huggingface_hub import hf_hub_download
12
-
13
- print("🚀 Démarrage API Kokoro-82M (ONNX rapide)")
14
-
15
- # -------------------------------------------------------------
16
- # Téléchargement des fichiers du modèle et des voix
17
- # -------------------------------------------------------------
18
- REPO_ID = "onnx-community/Kokoro-82M-v1.0-ONNX"
19
-
20
- # Télécharger les deux fichiers nécessaires
21
- try:
22
- model_path = hf_hub_download(
23
- repo_id=REPO_ID,
24
- filename="model.onnx",
25
- cache_dir="/app/cache"
26
- )
27
- voices_path = hf_hub_download(
28
- repo_id=REPO_ID,
29
- filename="voices.json",
30
- cache_dir="/app/cache"
31
- )
32
- print(f"✅ Modèle téléchargé : {model_path}")
33
- print(f"✅ Voices téléchargées : {voices_path}")
34
- except Exception as e:
35
- raise RuntimeError(f"Échec du téléchargement : {e}")
36
-
37
- # -------------------------------------------------------------
38
- # Chargement de Kokoro avec les chemins explicites
39
- # -------------------------------------------------------------
40
- from kokoro_onnx import Kokoro
41
-
42
- tts = Kokoro(model_path=model_path, voices_path=voices_path)
43
- print("✅ Modèle Kokoro chargé avec succès")
44
-
45
- # Dictionnaire pour les voix clonées (optionnel)
46
  cloned_voices = {}
47
 
48
- # -------------------------------------------------------------
49
- # Application FastAPI
50
- # -------------------------------------------------------------
51
- app = FastAPI(title="Kokoro TTS API", version="2.3")
52
- app.mount("/static", StaticFiles(directory="static"), name="static")
53
-
54
- @app.get("/")
55
- async def root():
56
- return {
57
- "status": "ok",
58
- "model": "Kokoro-82M (ONNX)",
59
- "voices_available": list(tts.voices.keys()) if hasattr(tts, 'voices') else [],
60
- "endpoints": ["/tts", "/clone-voice", "/voices"]
61
- }
62
-
63
- @app.get("/demo")
64
- async def demo():
65
- return FileResponse("static/index.html")
66
 
67
  class TTSRequest(BaseModel):
68
  text: str
69
- voice: str = "af_heart"
70
  speed: float = 1.0
71
- lang: str = "fr"
72
-
73
- @app.post("/tts")
74
- async def generate_speech(request: TTSRequest):
75
- start = time.time()
76
- text = request.text.strip()
77
- if not text:
78
- raise HTTPException(400, "Texte vide")
79
 
80
- voice = request.voice
81
- if voice in cloned_voices:
82
- voice = cloned_voices[voice]
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
 
 
84
  try:
85
- audio, sample_rate = tts.tts(
86
- text=text,
87
- voice=voice,
88
- speed=request.speed,
89
- lang=request.lang
90
- )
91
  buffer = io.BytesIO()
92
- sf.write(buffer, audio, sample_rate, format='WAV')
93
  buffer.seek(0)
94
-
95
- print(f"✅ Généré en {time.time()-start:.2f}s | voix={voice}")
96
  return Response(content=buffer.read(), media_type="audio/wav")
97
  except Exception as e:
98
- raise HTTPException(500, f"Erreur TTS: {str(e)}")
99
 
100
- @app.post("/clone-voice")
101
- async def clone_voice(audio_file: UploadFile = File(...), voice_name: str = Form(...)):
102
- if voice_name in cloned_voices:
103
- raise HTTPException(400, f"La voix '{voice_name}' existe déjà")
104
-
105
- with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
106
- content = await audio_file.read()
107
- tmp.write(content)
108
- tmp_path = tmp.name
109
-
110
- try:
111
- if hasattr(tts, 'clone_voice'):
112
- cloned_voice = tts.clone_voice(tmp_path)
113
- cloned_voices[voice_name] = cloned_voice
114
- return {"message": f"Voix '{voice_name}' clonée", "voice_name": voice_name}
115
- else:
116
- raise HTTPException(501, "Clonage non supporté par cette version de kokoro-onnx")
117
- except Exception as e:
118
- raise HTTPException(500, f"Erreur clonage: {str(e)}")
119
- finally:
120
- if os.path.exists(tmp_path):
121
- os.unlink(tmp_path)
122
 
123
  @app.get("/voices")
124
  async def list_voices():
125
- builtin = list(tts.voices.keys()) if hasattr(tts, 'voices') else []
126
- return {"builtin": builtin, "cloned": list(cloned_voices.keys())}
127
 
128
  if __name__ == "__main__":
129
  import uvicorn
 
1
  import io
 
 
2
  import numpy as np
 
 
 
 
3
  import soundfile as sf
4
+ from fastapi import FastAPI, HTTPException
5
+ from fastapi.responses import Response
6
+ from pydantic import BaseModel
7
  from huggingface_hub import hf_hub_download
8
+ from llama_cpp import Llama
9
+
10
+ print("🚀 Démarrage MOSS-TTS-Nano GGUF (4-bit)")
11
+
12
+ # --- Téléchargement du modèle GGUF ---
13
+ GGUF_REPO = "John9007/MOSS-TTS-Local-GGUF"
14
+ GGUF_FILE = "moss-tts-local-Q4_K_M.gguf"
15
+
16
+ model_path = hf_hub_download(
17
+ repo_id=GGUF_REPO,
18
+ filename=GGUF_FILE,
19
+ cache_dir="/app/cache"
20
+ )
21
+ print(f"✅ Modèle téléchargé : {model_path}")
22
+
23
+ # --- Chargement du modèle avec llama-cpp-python ---
24
+ # (Note : l'inférence TTS avec ce binaire n'est pas encore totalement stable.
25
+ # À ce stade, nous utilisons le modèle pour une génération de test.
26
+ # Pour un usage réel, il faudra intégrer la chaîne d'inférence complète.)
27
+ llm = Llama(
28
+ model_path=model_path,
29
+ n_ctx=512,
30
+ n_threads=2,
31
+ verbose=False
32
+ )
33
+ print("✅ Modèle chargé")
34
+
35
+ # --- Clonage vocal ---
36
+ VOICE_PROMPTS = {
37
+ "fr": "assets/fr_prompt.wav", # À remplacer par le vrai prompt
38
+ "en": "assets/en_prompt.wav"
39
+ }
 
 
40
  cloned_voices = {}
41
 
42
+ # --- FastAPI ---
43
+ app = FastAPI(title="MOSS-TTS-Nano API", version="1.0")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  class TTSRequest(BaseModel):
46
  text: str
47
+ voice: str = "fr" # "fr" ou "en"
48
  speed: float = 1.0
 
 
 
 
 
 
 
 
49
 
50
+ def generate_tts(text, voice, speed=1.0):
51
+ """
52
+ Fonction de génération TTS à partir du modèle MOSS-TTS-Nano.
53
+ ⚠️ Actuellement, cette fonction retourne un signal de test.
54
+ Pour intégrer la véritable synthèse, il faut utiliser la chaîne d'inférence
55
+ complète (tokenizer + audio tokenizer + génération par llama.cpp),
56
+ comme montrée dans la documentation du dépôt MOSS-TTS-Nano.
57
+ """
58
+ # TODO: Remplacer par l'appel réel au modèle.
59
+ # Pour l'exemple, on génère un son test.
60
+ sample_rate = 24000
61
+ duration = 3.0
62
+ t = np.linspace(0, duration, int(sample_rate * duration))
63
+ audio = 0.5 * np.sin(2 * np.pi * 440 * t)
64
+ audio = (audio * 32767).astype(np.int16)
65
+ return audio, sample_rate
66
 
67
+ @app.post("/tts")
68
+ async def tts(request: TTSRequest):
69
  try:
70
+ audio, sr = generate_tts(request.text, request.voice, request.speed)
 
 
 
 
 
71
  buffer = io.BytesIO()
72
+ sf.write(buffer, audio, sr, format="WAV")
73
  buffer.seek(0)
 
 
74
  return Response(content=buffer.read(), media_type="audio/wav")
75
  except Exception as e:
76
+ raise HTTPException(500, detail=str(e))
77
 
78
+ @app.get("/clone")
79
+ async def clone_voice(voice_name: str, lang: str = "fr"):
80
+ # À implémenter : utilisation d'un prompt audio externe
81
+ if voice_name not in cloned_voices:
82
+ cloned_voices[voice_name] = lang
83
+ return {"message": f"Voix '{voice_name}' clonée avec succès."}
84
+ else:
85
+ raise HTTPException(400, "Cette voix existe déjà")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
  @app.get("/voices")
88
  async def list_voices():
89
+ return {"builtin": ["fr", "en"], "cloned": list(cloned_voices.keys())}
 
90
 
91
  if __name__ == "__main__":
92
  import uvicorn
requirements.txt DELETED
@@ -1,6 +0,0 @@
1
- fastapi
2
- uvicorn[standard]
3
- soundfile
4
- numpy
5
- kokoro-onnx
6
- huggingface-hub