File size: 3,443 Bytes
eb7d099
2522981
 
 
 
 
 
 
 
eb7d099
2522981
 
eb7d099
2522981
 
 
 
eb7d099
 
2522981
eb7d099
 
 
2522981
eb7d099
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2522981
 
 
 
eb7d099
 
2436ccb
eb7d099
2436ccb
eb7d099
2522981
2436ccb
eb7d099
 
 
 
 
2436ccb
eb7d099
2436ccb
 
 
 
 
 
 
eb7d099
2436ccb
2522981
 
 
eb7d099
 
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
from fastapi import FastAPI, Response, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pocket_tts import TTSModel
import scipy.io.wavfile
import io
import torch

app = FastAPI()

# Configuration CORS pour que votre site puisse appeler l'API
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# --- CHARGEMENT ---
print("⏳ Chargement de Pocket TTS...")
try:
    # Chargement du modèle sans arguments (utilise le défaut de la lib)
    tts_model = TTSModel.load_model()
    print("✅ Modèle chargé !")
except Exception as e:
    print(f"❌ ERREUR CRITIQUE CHARGEMENT MODÈLE: {e}")
    # On ne quitte pas pour laisser le serveur démarrer et voir les logs, 
    # mais ça ne marchera pas sans modèle.

# Dictionnaire des voix officielles
# Cela permet d'envoyer juste "voice": "marius" depuis le Javascript
VOICES = {
    "alba": "hf://kyutai/tts-voices/alba-mackenna/casual.wav",
    "marius": "hf://kyutai/tts-voices/marius-kuntze/casual.wav",
    "javert": "hf://kyutai/tts-voices/javert-mccall/casual.wav",
    "jean": "hf://kyutai/tts-voices/jean-valjean/casual.wav",
    "fantine": "hf://kyutai/tts-voices/fantine-becker/casual.wav",
    "cosette": "hf://kyutai/tts-voices/cosette-moore/casual.wav",
    "eponine": "hf://kyutai/tts-voices/eponine-frazier/casual.wav",
    "azelma": "hf://kyutai/tts-voices/azelma-frazier/casual.wav"
}

# Cache pour garder les voix en mémoire (comme recommandé dans la doc)
loaded_voice_states = {}

def get_voice_state(voice_name="alba"):
    # Si on a déjà chargé cette voix, on la renvoie direct (rapide)
    if voice_name in loaded_voice_states:
        return loaded_voice_states[voice_name]
    
    # Sinon on la charge (lent la première fois)
    print(f"📥 Chargement de la voix : {voice_name}")
    full_path = VOICES.get(voice_name, VOICES["alba"])
    try:
        state = tts_model.get_state_for_audio_prompt(full_path)
        loaded_voice_states[voice_name] = state
        return state
    except Exception as e:
        print(f"Erreur chargement voix {voice_name}: {e}")
        # Fallback sur Alba si erreur
        if voice_name != "alba": 
            return get_voice_state("alba")
        raise e

# Pré-chargement de la voix par défaut au démarrage
try:
    get_voice_state("alba")
    print("✅ Voix par défaut (Alba) prête !")
except:
    pass

@app.post("/tts")
async def generate_speech(data: dict):
    text = data.get("text", "")
    voice_name = data.get("voice", "alba") # Par défaut 'alba'

    if not text:
        raise HTTPException(status_code=400, detail="Texte vide")

    print(f"🗣️ Génération ({voice_name}): {text[:30]}...")
    
    try:
        # Récupération de l'état de la voix
        voice_state = get_voice_state(voice_name)
        
        # Génération
        audio_tensor = tts_model.generate_audio(voice_state, text)
        
        # Conversion WAV
        buffer = io.BytesIO()
        scipy.io.wavfile.write(buffer, tts_model.sample_rate, audio_tensor.numpy())
        buffer.seek(0)
        
        return Response(content=buffer.read(), media_type="audio/wav")
        
    except Exception as e:
        print(f"❌ Erreur génération : {e}")
        return Response(content=str(e), status_code=500)

@app.get("/")
def home():
    return {"status": "Pocket TTS API Ready", "available_voices": list(VOICES.keys())}