Oronto commited on
Commit
eca1fe4
·
1 Parent(s): 1b17c23

Add Kokoro TTS FastAPI server

Browse files
Files changed (4) hide show
  1. Dockerfile +21 -0
  2. README.md +32 -5
  3. app.py +82 -0
  4. requirements.txt +5 -0
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies for soundfile / libsndfile
6
+ RUN apt-get update && apt-get install -y \
7
+ libsndfile1 \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Install Python dependencies
11
+ COPY requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Copy application code
15
+ COPY app.py .
16
+
17
+ # HuggingFace Spaces requires port 7860
18
+ EXPOSE 7860
19
+
20
+ # Start the FastAPI server
21
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,37 @@
1
  ---
2
- title: Kokoro Tts Api
3
- emoji: 🦀
4
- colorFrom: green
5
- colorTo: green
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Kokoro TTS API
3
+ emoji: 🎙️
4
+ colorFrom: purple
5
+ colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
+ # Kokoro TTS API
11
+
12
+ A lightweight REST API for [Kokoro TTS](https://huggingface.co/hexgrad/Kokoro-82M) — used as a serverless backend for the TwelveReader audiobook app, specifically to support iOS Safari where local WASM inference isn't viable.
13
+
14
+ ## API
15
+
16
+ ### `POST /tts`
17
+ Generate speech from text.
18
+
19
+ **Request body (JSON):**
20
+ ```json
21
+ {
22
+ "text": "Hello world",
23
+ "voice": "af_bella",
24
+ "speed": 1.0
25
+ }
26
+ ```
27
+
28
+ **Response:** `audio/wav` binary
29
+
30
+ ### Available voices
31
+ American Female: `af_bella`, `af_heart`, `af_nova`, `af_sky`, `af_sarah`, `af_nicole`, `af_alloy`, `af_aoede`, `af_jessica`, `af_kore`, `af_river`
32
+
33
+ American Male: `am_adam`, `am_echo`, `am_liam`, `am_michael`, `am_onyx`, `am_puck`, `am_fenrir`, `am_eric`, `am_santa`
34
+
35
+ British Female: `bf_alice`, `bf_emma`, `bf_isabella`, `bf_lily`
36
+
37
+ British Male: `bm_daniel`, `bm_fable`, `bm_george`, `bm_lewis`
app.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import numpy as np
3
+ from fastapi import FastAPI, HTTPException
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ from fastapi.responses import StreamingResponse
6
+ from pydantic import BaseModel
7
+ import soundfile as sf
8
+ from kokoro import KPipeline
9
+
10
+ app = FastAPI(title="Kokoro TTS API")
11
+
12
+ # Allow requests from any origin (your Vercel frontend)
13
+ app.add_middleware(
14
+ CORSMiddleware,
15
+ allow_origins=["*"],
16
+ allow_methods=["POST", "GET"],
17
+ allow_headers=["*"],
18
+ )
19
+
20
+ # Load the pipeline once at startup — reused for all requests
21
+ # 'a' = American English. Add more pipelines if you want other languages.
22
+ print("Loading Kokoro TTS pipeline...")
23
+ pipeline = KPipeline(lang_code='a')
24
+ print("Kokoro TTS pipeline ready!")
25
+
26
+
27
+ class TTSRequest(BaseModel):
28
+ text: str
29
+ voice: str = "af_bella"
30
+ speed: float = 1.0
31
+
32
+
33
+ @app.get("/")
34
+ def root():
35
+ return {"status": "ok", "service": "Kokoro TTS API"}
36
+
37
+
38
+ @app.get("/health")
39
+ def health():
40
+ return {"status": "healthy"}
41
+
42
+
43
+ @app.post("/tts")
44
+ async def tts(request: TTSRequest):
45
+ if not request.text or len(request.text.strip()) == 0:
46
+ raise HTTPException(status_code=400, detail="Text is required")
47
+
48
+ if len(request.text) > 5000:
49
+ raise HTTPException(status_code=400, detail="Text too long (max 5000 chars per request)")
50
+
51
+ try:
52
+ # Generate audio chunks from Kokoro
53
+ audio_chunks = []
54
+ for _gs, _ps, audio in pipeline(
55
+ request.text,
56
+ voice=request.voice,
57
+ speed=request.speed
58
+ ):
59
+ audio_chunks.append(audio)
60
+
61
+ if not audio_chunks:
62
+ raise HTTPException(status_code=500, detail="No audio generated")
63
+
64
+ # Combine all chunks into a single float32 array
65
+ combined = np.concatenate(audio_chunks)
66
+
67
+ # Encode as WAV and return as a streaming binary response
68
+ buf = io.BytesIO()
69
+ sf.write(buf, combined, samplerate=24000, format="WAV", subtype="PCM_16")
70
+ buf.seek(0)
71
+
72
+ return StreamingResponse(
73
+ buf,
74
+ media_type="audio/wav",
75
+ headers={"Content-Disposition": "attachment; filename=audio.wav"}
76
+ )
77
+
78
+ except HTTPException:
79
+ raise
80
+ except Exception as e:
81
+ print(f"TTS Error: {e}")
82
+ raise HTTPException(status_code=500, detail=str(e))
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ kokoro>=0.9.2
2
+ soundfile>=0.12.1
3
+ fastapi>=0.110.0
4
+ uvicorn>=0.29.0
5
+ numpy>=1.26.0