Jacky2305 commited on
Commit
92f0496
·
1 Parent(s): 5c9ce37

Restore Kokoro files

Browse files
Files changed (6) hide show
  1. Dockerfile +18 -4
  2. Dockerfile.kokoro +0 -19
  3. Dockerfile.test +5 -0
  4. main.py +139 -4
  5. main.py.kokoro +0 -140
  6. test_main.py +5 -0
Dockerfile CHANGED
@@ -1,5 +1,19 @@
1
- FROM python:3.11-slim
 
 
2
  WORKDIR /app
3
- COPY test_build.py .
4
- RUN pip install fastapi uvicorn
5
- CMD ["uvicorn", "test_build:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use a more complete base image
2
+ FROM python:3.11
3
+
4
  WORKDIR /app
5
+
6
+ # System deps - use a single RUN to avoid cache issues
7
+ RUN apt-get update && apt-get install -y espeak-ng libespeak-ng1 && rm -rf /var/lib/apt/lists/*
8
+
9
+ # Python deps
10
+ COPY requirements.txt .
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # App
14
+ COPY main.py .
15
+
16
+ ENV PORT=7860
17
+ EXPOSE 7860
18
+
19
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
Dockerfile.kokoro DELETED
@@ -1,19 +0,0 @@
1
- # Use a more complete base image
2
- FROM python:3.11
3
-
4
- WORKDIR /app
5
-
6
- # System deps - use a single RUN to avoid cache issues
7
- RUN apt-get update && apt-get install -y espeak-ng libespeak-ng1 && rm -rf /var/lib/apt/lists/*
8
-
9
- # Python deps
10
- COPY requirements.txt .
11
- RUN pip install --no-cache-dir -r requirements.txt
12
-
13
- # App
14
- COPY main.py .
15
-
16
- ENV PORT=7860
17
- EXPOSE 7860
18
-
19
- CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile.test ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+ WORKDIR /app
3
+ COPY test_build.py .
4
+ RUN pip install fastapi uvicorn
5
+ CMD ["uvicorn", "test_build:app", "--host", "0.0.0.0", "--port", "7860"]
main.py CHANGED
@@ -1,5 +1,140 @@
1
- from fastapi import FastAPI
2
- app = FastAPI()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  @app.get("/")
4
- def root():
5
- return {"message": "test"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel
3
+ from kokoro_onnx import Kokoro
4
+ import soundfile as sf
5
+ import uuid
6
+ from fastapi.responses import Response
7
+ from io import BytesIO
8
+ from pathlib import Path
9
+ import requests
10
+ import os
11
+
12
+ app = FastAPI(title="Kokoro 82M TTS API")
13
+
14
+ # Model files (downloaded on first run, cached in /tmp)
15
+ MODEL_DIR = Path("/tmp/kokoro_models")
16
+ MODEL_DIR.mkdir(exist_ok=True)
17
+
18
+ MODEL_PATH = MODEL_DIR / "kokoro-v1.0.onnx"
19
+ VOICES_PATH = MODEL_DIR / "voices-v1.0.bin"
20
+
21
+ MODEL_URL = "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx"
22
+ VOICES_URL = "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin"
23
+
24
+ kokoro: Kokoro | None = None
25
+
26
+
27
+ def download_file(url: str, dest: Path):
28
+ """Download file with progress, follow redirects."""
29
+ if dest.exists():
30
+ return
31
+ print(f"Downloading {dest.name}...")
32
+ with requests.get(url, stream=True, timeout=120, allow_redirects=True) as r:
33
+ r.raise_for_status()
34
+ with open(dest, "wb") as f:
35
+ for chunk in r.iter_content(chunk_size=8192):
36
+ f.write(chunk)
37
+ print(f"✓ Downloaded {dest.name}")
38
+
39
+
40
+ def get_kokoro() -> Kokoro:
41
+ global kokoro
42
+ if kokoro is not None:
43
+ return kokoro
44
+
45
+ # Download model files if needed
46
+ download_file(MODEL_URL, MODEL_PATH)
47
+ download_file(VOICES_URL, VOICES_PATH)
48
+
49
+ kokoro = Kokoro(str(MODEL_PATH), str(VOICES_PATH))
50
+ print(f"✓ Kokoro loaded. Available voices: {len(kokoro.get_voices())}")
51
+ return kokoro
52
+
53
+
54
+ # Warm up on startup
55
+ @app.on_event("startup")
56
+ async def warmup():
57
+ try:
58
+ get_kokoro()
59
+ print("Warmup complete")
60
+ except Exception as e:
61
+ print(f"Warmup failed (will retry on first request): {e}")
62
+
63
+
64
+ class TTSRequest(BaseModel):
65
+ text: str
66
+ voice: str = "af_heart" # Default voice (US female)
67
+ speed: float = 1.0 # Speed multiplier
68
+ sample_rate: int = 24000 # Kokoro outputs 24kHz
69
+
70
+
71
+ @app.post("/tts")
72
+ async def tts(request: TTSRequest):
73
+ k = get_kokoro()
74
+
75
+ # Validate voice
76
+ available = k.get_voices()
77
+ if request.voice not in available:
78
+ raise HTTPException(
79
+ 400,
80
+ f"Voice '{request.voice}' not available. Available: {available[:20]}... (total {len(available)})",
81
+ )
82
+
83
+ try:
84
+ # Generate audio (returns numpy array at 24kHz)
85
+ audio, sr = k.create(
86
+ request.text,
87
+ voice=request.voice,
88
+ speed=request.speed,
89
+ lang="en-us", # Kokoro uses lang code for G2P
90
+ )
91
+
92
+ # Resample if requested sample_rate differs (simple approach: just return 24kHz)
93
+ # For proper resampling, add scipy/samplerate. Keeping it simple: return native 24kHz.
94
+ buffer = BytesIO()
95
+ sf.write(buffer, audio, sr, format="WAV", subtype="PCM_16")
96
+ buffer.seek(0)
97
+
98
+ return Response(
99
+ content=buffer.read(),
100
+ media_type="audio/wav",
101
+ headers={"Content-Disposition": f"attachment; filename=tts_{uuid.uuid4().hex}.wav"},
102
+ )
103
+ except Exception as e:
104
+ raise HTTPException(500, f"Synthesis failed: {str(e)}")
105
+
106
+
107
+ @app.get("/health")
108
+ async def health():
109
+ k = get_kokoro()
110
+ return {
111
+ "status": "healthy",
112
+ "model": "kokoro-v1.0",
113
+ "sample_rate": 24000,
114
+ "voices_count": len(k.get_voices()),
115
+ "voices_preview": k.get_voices()[:10],
116
+ }
117
+
118
+
119
  @app.get("/")
120
+ async def root():
121
+ k = get_kokoro()
122
+ return {
123
+ "message": "Kokoro 82M TTS API - High quality CPU TTS",
124
+ "model": "kokoro-v1.0 (82M params, Apache 2.0)",
125
+ "sample_rate": 24000,
126
+ "endpoint": "/tts (POST)",
127
+ "voices_total": len(k.get_voices()),
128
+ "example": {
129
+ "text": "Hello, this is Kokoro 82M text to speech!",
130
+ "voice": "af_heart",
131
+ "speed": 1.0,
132
+ },
133
+ "voice_categories": {
134
+ "us_female": [v for v in k.get_voices() if v.startswith("af_")],
135
+ "us_male": [v for v in k.get_voices() if v.startswith("am_")],
136
+ "uk_female": [v for v in k.get_voices() if v.startswith("bf_")],
137
+ "uk_male": [v for v in k.get_voices() if v.startswith("bm_")],
138
+ "other": [v for v in k.get_voices() if not v.startswith(("af_", "am_", "bf_", "bm_"))],
139
+ },
140
+ }
main.py.kokoro DELETED
@@ -1,140 +0,0 @@
1
- from fastapi import FastAPI, HTTPException
2
- from pydantic import BaseModel
3
- from kokoro_onnx import Kokoro
4
- import soundfile as sf
5
- import uuid
6
- from fastapi.responses import Response
7
- from io import BytesIO
8
- from pathlib import Path
9
- import requests
10
- import os
11
-
12
- app = FastAPI(title="Kokoro 82M TTS API")
13
-
14
- # Model files (downloaded on first run, cached in /tmp)
15
- MODEL_DIR = Path("/tmp/kokoro_models")
16
- MODEL_DIR.mkdir(exist_ok=True)
17
-
18
- MODEL_PATH = MODEL_DIR / "kokoro-v1.0.onnx"
19
- VOICES_PATH = MODEL_DIR / "voices-v1.0.bin"
20
-
21
- MODEL_URL = "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx"
22
- VOICES_URL = "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin"
23
-
24
- kokoro: Kokoro | None = None
25
-
26
-
27
- def download_file(url: str, dest: Path):
28
- """Download file with progress, follow redirects."""
29
- if dest.exists():
30
- return
31
- print(f"Downloading {dest.name}...")
32
- with requests.get(url, stream=True, timeout=120, allow_redirects=True) as r:
33
- r.raise_for_status()
34
- with open(dest, "wb") as f:
35
- for chunk in r.iter_content(chunk_size=8192):
36
- f.write(chunk)
37
- print(f"✓ Downloaded {dest.name}")
38
-
39
-
40
- def get_kokoro() -> Kokoro:
41
- global kokoro
42
- if kokoro is not None:
43
- return kokoro
44
-
45
- # Download model files if needed
46
- download_file(MODEL_URL, MODEL_PATH)
47
- download_file(VOICES_URL, VOICES_PATH)
48
-
49
- kokoro = Kokoro(str(MODEL_PATH), str(VOICES_PATH))
50
- print(f"✓ Kokoro loaded. Available voices: {len(kokoro.get_voices())}")
51
- return kokoro
52
-
53
-
54
- # Warm up on startup
55
- @app.on_event("startup")
56
- async def warmup():
57
- try:
58
- get_kokoro()
59
- print("Warmup complete")
60
- except Exception as e:
61
- print(f"Warmup failed (will retry on first request): {e}")
62
-
63
-
64
- class TTSRequest(BaseModel):
65
- text: str
66
- voice: str = "af_heart" # Default voice (US female)
67
- speed: float = 1.0 # Speed multiplier
68
- sample_rate: int = 24000 # Kokoro outputs 24kHz
69
-
70
-
71
- @app.post("/tts")
72
- async def tts(request: TTSRequest):
73
- k = get_kokoro()
74
-
75
- # Validate voice
76
- available = k.get_voices()
77
- if request.voice not in available:
78
- raise HTTPException(
79
- 400,
80
- f"Voice '{request.voice}' not available. Available: {available[:20]}... (total {len(available)})",
81
- )
82
-
83
- try:
84
- # Generate audio (returns numpy array at 24kHz)
85
- audio, sr = k.create(
86
- request.text,
87
- voice=request.voice,
88
- speed=request.speed,
89
- lang="en-us", # Kokoro uses lang code for G2P
90
- )
91
-
92
- # Resample if requested sample_rate differs (simple approach: just return 24kHz)
93
- # For proper resampling, add scipy/samplerate. Keeping it simple: return native 24kHz.
94
- buffer = BytesIO()
95
- sf.write(buffer, audio, sr, format="WAV", subtype="PCM_16")
96
- buffer.seek(0)
97
-
98
- return Response(
99
- content=buffer.read(),
100
- media_type="audio/wav",
101
- headers={"Content-Disposition": f"attachment; filename=tts_{uuid.uuid4().hex}.wav"},
102
- )
103
- except Exception as e:
104
- raise HTTPException(500, f"Synthesis failed: {str(e)}")
105
-
106
-
107
- @app.get("/health")
108
- async def health():
109
- k = get_kokoro()
110
- return {
111
- "status": "healthy",
112
- "model": "kokoro-v1.0",
113
- "sample_rate": 24000,
114
- "voices_count": len(k.get_voices()),
115
- "voices_preview": k.get_voices()[:10],
116
- }
117
-
118
-
119
- @app.get("/")
120
- async def root():
121
- k = get_kokoro()
122
- return {
123
- "message": "Kokoro 82M TTS API - High quality CPU TTS",
124
- "model": "kokoro-v1.0 (82M params, Apache 2.0)",
125
- "sample_rate": 24000,
126
- "endpoint": "/tts (POST)",
127
- "voices_total": len(k.get_voices()),
128
- "example": {
129
- "text": "Hello, this is Kokoro 82M text to speech!",
130
- "voice": "af_heart",
131
- "speed": 1.0,
132
- },
133
- "voice_categories": {
134
- "us_female": [v for v in k.get_voices() if v.startswith("af_")],
135
- "us_male": [v for v in k.get_voices() if v.startswith("am_")],
136
- "uk_female": [v for v in k.get_voices() if v.startswith("bf_")],
137
- "uk_male": [v for v in k.get_voices() if v.startswith("bm_")],
138
- "other": [v for v in k.get_voices() if not v.startswith(("af_", "am_", "bf_", "bm_"))],
139
- },
140
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_main.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ app = FastAPI()
3
+ @app.get("/")
4
+ def root():
5
+ return {"message": "test"}