Praieiro commited on
Commit
522e55f
·
verified ·
1 Parent(s): d2033a7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -0
app.py CHANGED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import os
3
+ import soundfile as sf
4
+ import torch
5
+ import numpy as np
6
+ from fastapi import FastAPI, HTTPException, Header, Response
7
+ from pydantic import BaseModel
8
+ import spaces
9
+ from kokoro import KPipeline
10
+
11
+ app = FastAPI(title="Praieiro Voice · Kokoro TTS")
12
+
13
+ # Token de segurança do Praieiro (para ninguém gastar seu servidor)
14
+ SHARED_SECRET = os.environ.get("PRAIEIRO_SECRET", "praieiro-voz-2026")
15
+
16
+ # Carrega o pipeline em Português do Brasil ('p' = pt-br)
17
+ print("Inicializando Kokoro-82M para Português do Brasil...")
18
+ pipeline = KPipeline(lang_code="p")
19
+
20
+ class SpeechRequest(BaseModel):
21
+ input: str
22
+ voice: str = "pm_alex" # pm_alex (masculina natural) ou pf_dora (feminina)
23
+ speed: float = 1.0
24
+
25
+ @app.get("/")
26
+ def health():
27
+ return {
28
+ "status": "online",
29
+ "service": "Praieiro Kokoro Voice",
30
+ "lang": "pt-br",
31
+ "sample_rate": 24000
32
+ }
33
+
34
+ @spaces.GPU
35
+ def generate_audio_gpu(text: str, voice: str, speed: float):
36
+ generator = pipeline(text, voice=voice, speed=speed, split_pattern=r"\n+")
37
+ audio_chunks = []
38
+ for _, _, audio in generator:
39
+ audio_chunks.append(audio)
40
+
41
+ if not audio_chunks:
42
+ raise ValueError("Nenhum áudio gerado")
43
+
44
+ full_audio = np.concatenate(audio_chunks)
45
+
46
+ buf = io.BytesIO()
47
+ sf.write(buf, full_audio, 24000, format="WAV")
48
+ buf.seek(0)
49
+ return buf.read()
50
+
51
+ @app.post("/v1/audio/speech")
52
+ @app.post("/tts")
53
+ def speak(
54
+ req: SpeechRequest,
55
+ authorization: str = Header(None)
56
+ ):
57
+ expected = f"Bearer {SHARED_SECRET}"
58
+ if SHARED_SECRET and authorization != expected:
59
+ raise HTTPException(status_code=401, detail="Não autorizado")
60
+
61
+ text = req.input.strip()
62
+ if not text:
63
+ raise HTTPException(status_code=400, detail="Texto vazio")
64
+
65
+ try:
66
+ audio_bytes = generate_audio_gpu(text, req.voice, req.speed)
67
+ return Response(content=audio_bytes, media_type="audio/wav")
68
+ except Exception as e:
69
+ raise HTTPException(status_code=500, detail=str(e))
70
+
71
+ if __name__ == "__main__":
72
+ import uvicorn
73
+ uvicorn.run(app, host="0.0.0.0", port=7860)
74
+