Randriamiandry commited on
Commit
6c2e817
·
1 Parent(s): db22081

kokoclone

Browse files
Files changed (3) hide show
  1. Dockerfile +23 -3
  2. app.py +87 -88
  3. requirements.txt +7 -3
Dockerfile CHANGED
@@ -1,7 +1,27 @@
1
- FROM python:3.11-slim
 
 
 
 
 
 
2
  WORKDIR /app
3
- RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
 
 
 
 
 
 
 
4
  COPY requirements.txt .
5
  RUN pip install --no-cache-dir -r requirements.txt
6
- COPY . .
 
 
 
 
 
 
 
7
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
1
+ FROM python:3.12.12
2
+
3
+ # Évite la création de fichiers .pyc et active les logs en temps réel
4
+ ENV PYTHONDONTWRITEBYTECODE=1
5
+ ENV PYTHONUNBUFFERED=1
6
+
7
+ # Définit le répertoire de travail
8
  WORKDIR /app
9
+
10
+ # Met à jour pip et installe les dépendances système légères
11
+ RUN apt-get update && apt-get install -y --no-install-recommends \
12
+ ffmpeg \
13
+ && apt-get clean \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ # Installe les dépendances Python
17
  COPY requirements.txt .
18
  RUN pip install --no-cache-dir -r requirements.txt
19
+
20
+ # Copie le code source
21
+ COPY app.py .
22
+
23
+ # Expose le port sur lequel l'API tourne
24
+ EXPOSE 7860
25
+
26
+ # Commande de démarrage du serveur
27
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py CHANGED
@@ -1,93 +1,92 @@
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
93
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ import os
2
+ import tempfile
3
+ from fastapi import FastAPI, HTTPException, UploadFile, File, Form
4
+ from fastapi.responses import FileResponse, StreamingResponse
5
+ from pydantic import BaseModel, HttpUrl
6
+ import requests
7
+ from pathlib import Path
8
+ import time
9
+
10
+ # Initialisation
11
+ print("🚀 Démarrage du serveur FastAPI pour KokoClone...")
12
+
13
+ # 1. Configuration du chemin pour les modèles et les sorties
14
+ MODEL_DIR = Path("/app/cache/models")
15
+ MODEL_DIR.mkdir(parents=True, exist_ok=True)
16
+ OUTPUT_DIR = Path("/app/tmp")
17
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
18
+
19
+ # 2. Télécharger le modèle cloner.py depuis le dépôt officiel si non présent
20
+ CLONER_URL = "https://raw.githubusercontent.com/Ashish-Patnaik/kokoclone/main/core/cloner.py"
21
+ CLONER_PATH = MODEL_DIR / "cloner.py"
22
+
23
+ if not CLONER_PATH.exists():
24
+ print(f"⏳ Téléchargement de {CLONER_URL}...")
25
+ response = requests.get(CLONER_URL)
26
+ response.raise_for_status()
27
+ CLONER_PATH.write_text(response.text)
28
+ print("✅ cloner.py téléchargé avec succès.")
29
+
30
+ # 3. Ajouter le chemin des modèles au PYTHONPATH pour l'import
31
+ import sys
32
+ sys.path.insert(0, str(MODEL_DIR))
33
+
34
+ # 4. Importer et initialiser KokoClone
35
+ from cloner import KokoClone
36
+
37
+ print("⏳ Initialisation de KokoClone (première exécution, téléchargement des modèles)...")
38
+ cloner = KokoClone()
39
+ print("✅ KokoClone prêt à l'emploi.")
40
+
41
+ app = FastAPI(title="KokoClone API", version="1.0")
42
+
43
+ @app.get("/")
44
+ async def root():
45
+ return {"status": "OK", "model": "KokoClone"}
46
+
47
+ @app.post("/clone")
48
+ async def clone_voice(
49
+ text: str = Form(...),
50
+ lang: str = Form(...),
51
+ audio_file: UploadFile = File(...)
52
+ ):
53
+ # 1. Sauvegarde temporaire du fichier audio de référence
54
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_ref:
55
+ content = await audio_file.read()
56
+ tmp_ref.write(content)
57
+ ref_path = tmp_ref.name
58
+
59
+ # 2. Définition du chemin de sortie
60
+ output_filename = f"output_{int(time.time())}.wav"
61
+ output_path = OUTPUT_DIR / output_filename
62
+
 
 
 
 
 
 
63
  try:
64
+ # 3. Appel à KokoClone pour la génération
65
+ cloner.generate(
66
+ text=text,
67
+ lang=lang,
68
+ reference_audio=ref_path,
69
+ output_path=str(output_path)
70
+ )
71
 
72
+ # 4. Nettoyage du fichier temporaire de référence
73
+ os.unlink(ref_path)
74
+
75
+ # 5. Retour du fichier audio généré
76
+ return FileResponse(
77
+ path=str(output_path),
78
+ media_type="audio/wav",
79
+ filename=output_filename
80
+ )
81
+
82
+ except Exception as e:
83
+ # Nettoyage en cas d'erreur
84
+ if os.path.exists(ref_path):
85
+ os.unlink(ref_path)
86
+ if output_path.exists():
87
+ output_path.unlink()
88
+ raise HTTPException(status_code=500, detail=str(e))
89
 
90
  @app.get("/voices")
91
  async def list_voices():
92
+ return {"languages": ["en", "hi", "fr", "ja", "zh", "it", "es", "pt"]}
 
 
 
 
requirements.txt CHANGED
@@ -1,6 +1,10 @@
1
  fastapi
2
  uvicorn[standard]
3
- huggingface-hub
4
- llama-cpp-python
5
- numpy
6
  soundfile
 
 
 
 
 
 
 
 
1
  fastapi
2
  uvicorn[standard]
 
 
 
3
  soundfile
4
+ huggingface_hub
5
+ torch
6
+ torchaudio
7
+ kokoro-onnx
8
+ misaki[en,ja,zh]
9
+ git+https://github.com/frothywater/kanade-tokenizer
10
+ numpy