Hexa06 commited on
Commit
559b0d5
·
1 Parent(s): 4b5344e

Simplify: Remove Supabase, auth, and quotas - pure TTS API

Browse files
Files changed (4) hide show
  1. .env.example +2 -10
  2. README.md +91 -94
  3. app.py +72 -200
  4. requirements.txt +0 -3
.env.example CHANGED
@@ -1,10 +1,2 @@
1
- # Supabase Configuration
2
- # Copy this file to .env and fill in your actual values
3
- # For Hugging Face Spaces, add these as Secrets in the Space settings
4
-
5
- SUPABASE_URL=http://supabasekong-ggosk0kkwsw8swcwsooo8o48.16.170.15.157.sslip.io
6
- SUPABASE_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTc2MzYzNDg0MCwiZXhwIjo0OTE5MzA4NDQwLCJyb2xlIjoiYW5vbiJ9.73mWaXstDrk1wQGYaWMSi2-GWSb4_MvOXMwtM96sjCI
7
-
8
- # Admin Credentials (Optional - defaults are set in app.py)
9
- # ADMIN_USERNAME=admin
10
- # ADMIN_PASSWORD=your_secure_password
 
1
+ # No environment variables needed!
2
+ # This TTS service runs without authentication or database
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Kokoro TTS API - Professional & Fast
3
  emoji: 🎤
4
  colorFrom: blue
5
  colorTo: purple
@@ -8,125 +8,122 @@ app_file: app.py
8
  pinned: false
9
  ---
10
 
11
- # Kokoro TTS Service - Professional & Lightning Fast
12
 
13
- **10x faster than XTTS** | Emotional voices for storytelling | CPU-optimized
14
 
15
- ## 🚀 Why Kokoro?
16
 
17
- - **Lightning Fast**: Generates audio in real-time on CPU
18
- - 🎭 **Emotional Expression**: Perfect for audiobooks & storytelling
19
- - 💾 **Lightweight**: Only 82M parameters (vs XTTS 400M+)
20
- - 🔒 **Secure**: Supabase authentication with user management
21
- - 📊 **High Quota**: 50 generations/day (vs 3 with XTTS)
22
 
23
- ## 🔐 Admin Credentials
24
- ```
25
- Username: madhab
26
- Password: Madhab@Studify2024!
27
- ```
28
-
29
- ## 📦 Setup
30
-
31
- ### 1. Supabase Database
32
- Run `supabase_schema.sql` in your Supabase SQL Editor
33
-
34
- ### 2. Install & Run
35
- ```bash
36
- pip install -r requirements.txt
37
- python app.py
38
- ```
39
-
40
- ## 🎤 Available Voices
41
 
42
  | Voice | Description | Best For |
43
  |-------|-------------|----------|
44
- | `af_heart` | American Female (warm) | General narration |
45
- | `af_bella` | American Female (professional) | Educational content |
46
- | `am_adam` | American Male (confident) | Business/Tech |
47
- | `am_michael` | American Male (friendly) | Casual/Conversational |
48
- | `bf_emma` | British Female (elegant) | Formal content |
49
- | `bf_isabella` | British Female (storytelling) | **Audiobooks/Stories** |
 
 
50
 
51
- ## 📡 API Usage
52
 
53
- ### Generate Speech (Fast!)
54
  ```bash
55
- curl -X POST http://localhost:7860/api/generate \
56
- -F "username=madhab" \
57
- -F "password=Madhab@Studify2024!" \
58
- -F "text=The concept of artificial intelligence has evolved significantly over the past few decades." \
59
  -F "voice=bf_isabella" \
60
  -F "speed=1.0" \
61
- --output output.wav
62
  ```
63
 
64
- ### Check Quota
65
- ```bash
66
- curl -X POST http://localhost:7860/api/quota \
67
- -F "username=madhab" \
68
- -F "password=Madhab@Studify2024!"
 
 
 
 
 
 
 
 
 
 
 
69
  ```
70
 
71
- ### Create New User (Admin)
72
- ```bash
73
- curl -X POST http://localhost:7860/api/admin/create-user \
74
- -F "admin_username=madhab" \
75
- -F "admin_password=Madhab@Studify2024!" \
76
- -F "new_username=student" \
77
- -F "new_password=SecurePass123!" \
78
- -F "role=user" \
79
- -F "daily_limit=50"
 
 
 
 
 
 
 
 
 
 
80
  ```
81
 
82
- ## Performance Comparison
83
 
84
- | Model | Speed | Quality | CPU Usage | Max Chars |
85
- |-------|-------|---------|-----------|-----------|
86
- | **Kokoro** | **10x** | High | Low | 5000 |
87
- | XTTS v2 | 1x | Very High | High | 3000 |
88
- | Bark | 0.5x | High | Very High | 1000 |
89
 
90
- ## 🎯 Perfect For
91
 
92
- - ✅ Long-form educational content
93
- - Audiobook narration
94
- - Real-time reader mode in apps
95
- - Storytelling with emotion
96
- - ✅ Multi-voice projects
97
 
98
- ## 🔧 Integration Example (Flutter)
99
 
100
- Update your `AIService.dart`:
101
- ```dart
102
- final response = await http.post(
103
- Uri.parse('https://your-space.hf.space/api/generate'),
104
- headers: {'Content-Type': 'multipart/form-data'},
105
- body: {
106
- 'username': 'your_username',
107
- 'password': 'your_password',
108
- 'text': text,
109
- 'voice': 'bf_isabella', // Storytelling voice
110
- 'speed': '1.0'
111
- }
112
- );
113
- ```
114
 
115
- ## 📊 Quota System
116
 
117
- | Role | Daily Limit | Speed Limit |
118
- |------|-------------|-------------|
119
- | User | 50 generations | Normal |
120
- | Premium | Custom | Priority |
121
- | Admin | Unlimited | Highest |
122
 
123
- ## 🌐 Deploy to Hugging Face
124
 
125
- 1. Push to GitHub
126
- 2. Create new Space on Hugging Face
127
- 3. Set secrets: `SUPABASE_URL`, `SUPABASE_KEY`
128
- 4. Deploy!
129
 
130
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- **Built for [Studify](https://github.com/your-repo)** - Making education accessible through AI
 
 
 
1
  ---
2
+ title: Kokoro TTS API - Simple & Fast
3
  emoji: 🎤
4
  colorFrom: blue
5
  colorTo: purple
 
8
  pinned: false
9
  ---
10
 
11
+ # 🎤 Kokoro TTS API - Simple & Fast
12
 
13
+ High-speed text-to-speech service powered by Kokoro (82M parameters). No authentication, no database - just pure TTS!
14
 
15
+ ## Features
16
 
17
+ - **Lightning Fast**: 10x faster than XTTS
18
+ - **Emotional Voices**: 6 expressive voices
19
+ - **CPU Optimized**: Runs smoothly on CPU
20
+ - **No Auth Required**: Simple HTTP API
21
+ - **Long Audio**: Up to 5 minutes per generation
22
 
23
+ ## 🎵 Available Voices
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
  | Voice | Description | Best For |
26
  |-------|-------------|----------|
27
+ | `bf_isabella` | British Female | Storytelling, audiobooks |
28
+ | `af_heart` | American Female | Warm, conversational |
29
+ | `af_bella` | American Female | Professional narration |
30
+ | `bf_emma` | British Female | Elegant, formal |
31
+ | `am_adam` | American Male | Confident, clear |
32
+ | `am_michael` | American Male | Friendly, casual |
33
+
34
+ ## 🚀 Quick Start
35
 
36
+ ### Using curl:
37
 
 
38
  ```bash
39
+ curl -X POST https://your-space.hf.space/api/generate \
40
+ -F "text=Once upon a time, in a distant kingdom..." \
 
 
41
  -F "voice=bf_isabella" \
42
  -F "speed=1.0" \
43
+ --output story.wav
44
  ```
45
 
46
+ ### Using Python:
47
+
48
+ ```python
49
+ import requests
50
+
51
+ response = requests.post(
52
+ "https://your-space.hf.space/api/generate",
53
+ data={
54
+ "text": "Hello world! This is Kokoro TTS.",
55
+ "voice": "bf_isabella",
56
+ "speed": 1.0
57
+ }
58
+ )
59
+
60
+ with open("audio.wav", "wb") as f:
61
+ f.write(response.content)
62
  ```
63
 
64
+ ### Using Flutter/Dart:
65
+
66
+ ```dart
67
+ import 'package:http/http.dart' as http;
68
+
69
+ Future<File> generateTTS(String text) async {
70
+ final response = await http.post(
71
+ Uri.parse('https://your-space.hf.space/api/generate'),
72
+ body: {
73
+ 'text': text,
74
+ 'voice': 'bf_isabella',
75
+ 'speed': '1.0',
76
+ },
77
+ );
78
+
79
+ final file = File('${Directory.systemTemp.path}/tts_${DateTime.now().millisecondsSinceEpoch}.wav');
80
+ await file.writeAsBytes(response.bodyBytes);
81
+ return file;
82
+ }
83
  ```
84
 
85
+ ## 📊 API Endpoints
86
 
87
+ ### `POST /api/generate`
 
 
 
 
88
 
89
+ Generate TTS audio from text.
90
 
91
+ **Parameters:**
92
+ - `text` (required): Text to convert (5-4500 characters)
93
+ - `voice` (optional): Voice to use (default: `bf_isabella`)
94
+ - `speed` (optional): Speech speed 0.5-2.0 (default: 1.0)
 
95
 
96
+ **Response:** WAV audio file
97
 
98
+ ### `GET /health`
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
+ Check service health and available voices.
101
 
102
+ ### `GET /docs`
 
 
 
 
103
 
104
+ Interactive API documentation (Swagger UI).
105
 
106
+ ## ⚙️ Technical Specs
 
 
 
107
 
108
+ - **Model**: Kokoro-82M (ONNX)
109
+ - **Max Characters**: 4500 (~5 minutes audio)
110
+ - **Generation Time**: ~20-30 seconds (CPU)
111
+ - **Speech Rate**: ~900 characters/minute
112
+ - **Output Format**: WAV, 24kHz
113
+
114
+ ## 🛠️ Deployment
115
+
116
+ This Space runs on Docker with automatic model download on startup.
117
+
118
+ **No environment variables needed!**
119
+
120
+ ## 📝 License
121
+
122
+ Model: Kokoro TTS by thewh1teagle
123
+ Service: MIT License
124
+
125
+ ## 🔗 Links
126
 
127
+ - [Kokoro GitHub](https://github.com/thewh1teagle/kokoro-onnx)
128
+ - [API Docs](/docs)
129
+ - [Health Check](/health)
app.py CHANGED
@@ -3,52 +3,14 @@ from fastapi.responses import FileResponse
3
  from kokoro_onnx import Kokoro
4
  import tempfile
5
  import os
6
- import bcrypt
7
- from datetime import datetime, timedelta
8
- from supabase import create_client, Client
9
  import soundfile as sf
10
 
11
  # ============== CONFIG ==============
12
- SUPABASE_URL = os.getenv("SUPABASE_URL")
13
- SUPABASE_KEY = os.getenv("SUPABASE_KEY")
14
-
15
- if not SUPABASE_URL or not SUPABASE_KEY:
16
- raise ValueError("SUPABASE_URL and SUPABASE_KEY environment variables must be set")
17
-
18
- DAILY_QUOTA = 50 # Increased since Kokoro is much faster
19
  MAX_CHARS = 4500 # ~5 minutes of audio (speaking rate: ~900 chars/min)
20
  MIN_CHARS = 5
21
  MAX_AUDIO_DURATION = 300 # 5 minutes of audio
22
 
23
- # Admin credentials
24
- ADMIN_USERNAME = "madhab"
25
- ADMIN_PASSWORD = "Madhab@Studify2024!"
26
-
27
- # ============== SUPABASE ==============
28
- supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
29
-
30
- def init_admin():
31
- """Create admin user if not exists"""
32
- try:
33
- result = supabase.table("tts_users").select("username").eq("username", ADMIN_USERNAME).execute()
34
-
35
- if not result.data:
36
- password_hash = bcrypt.hashpw(ADMIN_PASSWORD.encode(), bcrypt.gensalt()).decode()
37
- supabase.table("tts_users").insert({
38
- "username": ADMIN_USERNAME,
39
- "password_hash": password_hash,
40
- "role": "admin",
41
- "daily_limit": -1,
42
- "is_active": True
43
- }).execute()
44
- print(f"✅ Admin user created: {ADMIN_USERNAME}")
45
- else:
46
- print(f"✅ Admin user exists: {ADMIN_USERNAME}")
47
- except Exception as e:
48
- # Ignore RLS errors - user may already exist or require manual creation
49
- print(f"⚠️ Note: {e}")
50
- print(f"ℹ️ Tip: Create admin user manually in Supabase if needed")
51
-
52
  # ============== KOKORO TTS MODEL ==============
53
  print("🎤 Loading Kokoro TTS model...")
54
  try:
@@ -58,14 +20,17 @@ except Exception as e:
58
  print(f"⚠️ Kokoro not found locally. Will download on first use.")
59
  kokoro = None
60
 
61
- app = FastAPI(title="Kokoro TTS API - Professional & Fast")
 
 
 
 
62
 
63
  @app.on_event("startup")
64
  def startup():
65
  global kokoro
66
  if kokoro is None:
67
  import urllib.request
68
- import os
69
 
70
  print("📥 Downloading Kokoro TTS model files...")
71
 
@@ -95,79 +60,32 @@ def startup():
95
  print("🎤 Initializing Kokoro TTS...")
96
  kokoro = Kokoro(model_file, voices_file)
97
  print("✅ Kokoro TTS loaded!")
98
-
99
- init_admin()
100
-
101
- # ============== AUTH ==============
102
- def verify_password(plain_password: str, hashed_password: str) -> bool:
103
- try:
104
- return bcrypt.checkpw(plain_password.encode(), hashed_password.encode())
105
- except:
106
- return False
107
-
108
- def authenticate_user(username: str, password: str) -> dict:
109
- result = supabase.table("tts_users").select("*").eq("username", username).execute()
110
-
111
- if not result.data or len(result.data) == 0:
112
- raise HTTPException(status_code=401, detail="Access denied. User not found in database.")
113
-
114
- user = result.data[0]
115
-
116
- if not user.get('is_active', True):
117
- raise HTTPException(status_code=403, detail="Account is disabled. Contact admin.")
118
-
119
- if not verify_password(password, user['password_hash']):
120
- raise HTTPException(status_code=401, detail="Invalid credentials.")
121
-
122
- return user
123
-
124
- def check_quota(username: str, daily_limit: int, role: str) -> dict:
125
- if role == 'admin' or daily_limit == -1:
126
- return {"used": 0, "remaining": -1, "is_unlimited": True}
127
-
128
- since = (datetime.utcnow() - timedelta(hours=24)).isoformat()
129
- result = supabase.table("tts_usage_logs").select("id", count="exact").eq("username", username).gte("created_at", since).execute()
130
-
131
- used = result.count or 0
132
- remaining = daily_limit - used
133
-
134
- if remaining <= 0:
135
- raise HTTPException(status_code=429, detail=f"Daily quota exceeded. Used {used}/{daily_limit}. Resets in 24h.")
136
-
137
- return {"used": used, "remaining": remaining, "is_unlimited": False}
138
-
139
- def log_usage(username: str, text_length: int, language: str):
140
- supabase.table("tts_usage_logs").insert({
141
- "username": username,
142
- "text_length": text_length,
143
- "language": language,
144
- "created_at": datetime.utcnow().isoformat()
145
- }).execute()
146
 
147
  # ============== HELPERS ==============
148
  def cleanup_file(path: str):
 
149
  try:
150
  if os.path.exists(path):
151
  os.unlink(path)
152
  except:
153
  pass
154
 
155
- def generate_speech(text: str, voice: str = "af_heart", speed: float = 1.0) -> str:
156
  """
157
  Generate speech using Kokoro TTS
158
- Available voices: af (American Female), am (American Male), bf (British Female), etc.
159
  """
160
  if len(text) < MIN_CHARS:
161
  raise ValueError(f"Text too short. Minimum {MIN_CHARS} characters.")
162
  if len(text) > MAX_CHARS:
163
- raise ValueError(f"Text too long. Maximum {MAX_CHARS} characters.")
164
 
165
  # Generate audio samples
166
  samples, sample_rate = kokoro.create(
167
  text=text,
168
  voice=voice,
169
  speed=speed,
170
- lang="en-us" # Kokoro supports: en-us, en-gb, ja, etc.
171
  )
172
 
173
  # Save to temporary file
@@ -176,66 +94,88 @@ def generate_speech(text: str, voice: str = "af_heart", speed: float = 1.0) -> s
176
  return tmp.name
177
 
178
  # ============== API ENDPOINTS ==============
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  @app.get("/health")
180
  def health():
181
  return {
182
  "status": "healthy",
183
  "model": "Kokoro TTS 82M",
184
  "speed": "10x faster than XTTS",
185
- "authentication": "required",
186
- "default_quota": DAILY_QUOTA
187
  }
188
 
189
  @app.post("/api/generate")
190
  async def generate_tts(
191
  background_tasks: BackgroundTasks,
192
- username: str = Form(...),
193
- password: str = Form(...),
194
- text: str = Form(...),
195
- voice: str = Form("af_heart"), # American Female - Heart
196
- speed: float = Form(1.0)
197
  ):
198
  """
199
  Generate TTS with Kokoro (Fast & Emotional)
200
 
201
- Performance:
202
- - Max audio length: 5 minutes
203
- - Speaking rate: ~900 chars/minute
204
- - Max chars: 4500 (~5 min audio)
205
- - Generation time: ~20-30 seconds on CPU
206
-
207
- Available voices:
208
- - af_heart: American Female (warm)
209
- - af_bella: American Female (professional)
210
- - am_adam: American Male (confident)
211
- - am_michael: American Male (friendly)
212
- - bf_emma: British Female (elegant)
213
- - bf_isabella: British Female (storytelling) ⭐
214
-
215
- Usage:
216
- curl -X POST https://your-service.hf.space/api/generate \
217
- -F "username=madhab" \
218
- -F "password=Madhab@Studify2024!" \
219
- -F "text=Hello world. This is much faster!" \
220
- -F "voice=bf_isabella" \
221
- -F "speed=1.0" \
222
- --output output.wav
223
  """
224
- user = authenticate_user(username, password)
225
- quota = check_quota(user['username'], user['daily_limit'], user['role'])
226
-
227
  try:
228
- output_path = generate_speech(text.strip(), voice, speed)
 
 
229
 
230
- if not quota['is_unlimited']:
231
- log_usage(user['username'], len(text), "en")
232
 
 
233
  background_tasks.add_task(cleanup_file, output_path)
234
 
235
- response = FileResponse(output_path, media_type="audio/wav", filename="kokoro_tts.wav")
236
- response.headers["X-Quota-Used"] = str(quota["used"] + (0 if quota["is_unlimited"] else 1))
237
- response.headers["X-Quota-Remaining"] = "unlimited" if quota["is_unlimited"] else str(quota["remaining"] - 1)
238
- response.headers["X-Model"] = "Kokoro-82M"
 
 
 
 
 
239
  return response
240
 
241
  except ValueError as e:
@@ -243,74 +183,6 @@ async def generate_tts(
243
  except Exception as e:
244
  raise HTTPException(status_code=500, detail=f"TTS generation failed: {str(e)}")
245
 
246
- @app.post("/api/quota")
247
- async def check_user_quota(username: str = Form(...), password: str = Form(...)):
248
- user = authenticate_user(username, password)
249
- quota = check_quota(user['username'], user['daily_limit'], user['role'])
250
-
251
- return {
252
- "username": user['username'],
253
- "role": user['role'],
254
- "used_today": quota["used"],
255
- "remaining": "unlimited" if quota["is_unlimited"] else quota["remaining"],
256
- "daily_limit": "unlimited" if quota["is_unlimited"] else user['daily_limit']
257
- }
258
-
259
- @app.post("/api/admin/create-user")
260
- async def create_user(
261
- admin_username: str = Form(...),
262
- admin_password: str = Form(...),
263
- new_username: str = Form(...),
264
- new_password: str = Form(...),
265
- role: str = Form("user"),
266
- daily_limit: int = Form(50)
267
- ):
268
- admin = authenticate_user(admin_username, admin_password)
269
- if admin['role'] != 'admin':
270
- raise HTTPException(status_code=403, detail="Admin access required")
271
-
272
- existing = supabase.table("tts_users").select("username").eq("username", new_username).execute()
273
- if existing.data:
274
- raise HTTPException(status_code=400, detail="Username already exists")
275
-
276
- password_hash = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()).decode()
277
-
278
- supabase.table("tts_users").insert({
279
- "username": new_username,
280
- "password_hash": password_hash,
281
- "role": role,
282
- "daily_limit": daily_limit,
283
- "is_active": True
284
- }).execute()
285
-
286
- return {"success": True, "username": new_username, "role": role, "daily_limit": daily_limit}
287
-
288
- @app.post("/api/admin/list-users")
289
- async def list_users(admin_username: str = Form(...), admin_password: str = Form(...)):
290
- admin = authenticate_user(admin_username, admin_password)
291
- if admin['role'] != 'admin':
292
- raise HTTPException(status_code=403, detail="Admin access required")
293
-
294
- result = supabase.table("tts_users").select("username, role, daily_limit, is_active, created_at").execute()
295
- return {"users": result.data}
296
-
297
- # ============== ROOT ENDPOINT ==============
298
- @app.get("/")
299
- def root():
300
- return {
301
- "service": "Kokoro TTS API",
302
- "status": "running",
303
- "model": "Kokoro-82M",
304
- "version": "1.0",
305
- "endpoints": {
306
- "health": "/health",
307
- "generate": "/api/generate (POST)",
308
- "quota": "/api/quota (POST)",
309
- "docs": "/docs"
310
- },
311
- "usage": "Visit /docs for interactive API documentation"
312
- }
313
-
314
  if __name__ == "__main__":
315
  import uvicorn
316
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
3
  from kokoro_onnx import Kokoro
4
  import tempfile
5
  import os
6
+ from datetime import datetime
 
 
7
  import soundfile as sf
8
 
9
  # ============== CONFIG ==============
 
 
 
 
 
 
 
10
  MAX_CHARS = 4500 # ~5 minutes of audio (speaking rate: ~900 chars/min)
11
  MIN_CHARS = 5
12
  MAX_AUDIO_DURATION = 300 # 5 minutes of audio
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  # ============== KOKORO TTS MODEL ==============
15
  print("🎤 Loading Kokoro TTS model...")
16
  try:
 
20
  print(f"⚠️ Kokoro not found locally. Will download on first use.")
21
  kokoro = None
22
 
23
+ app = FastAPI(
24
+ title="Kokoro TTS API - Fast & Simple",
25
+ description="High-speed text-to-speech with emotional voices",
26
+ version="2.0"
27
+ )
28
 
29
  @app.on_event("startup")
30
  def startup():
31
  global kokoro
32
  if kokoro is None:
33
  import urllib.request
 
34
 
35
  print("📥 Downloading Kokoro TTS model files...")
36
 
 
60
  print("🎤 Initializing Kokoro TTS...")
61
  kokoro = Kokoro(model_file, voices_file)
62
  print("✅ Kokoro TTS loaded!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  # ============== HELPERS ==============
65
  def cleanup_file(path: str):
66
+ """Delete temporary file after response is sent"""
67
  try:
68
  if os.path.exists(path):
69
  os.unlink(path)
70
  except:
71
  pass
72
 
73
+ def generate_speech(text: str, voice: str = "bf_isabella", speed: float = 1.0) -> str:
74
  """
75
  Generate speech using Kokoro TTS
76
+ Available voices: af_heart, af_bella, am_adam, am_michael, bf_emma, bf_isabella
77
  """
78
  if len(text) < MIN_CHARS:
79
  raise ValueError(f"Text too short. Minimum {MIN_CHARS} characters.")
80
  if len(text) > MAX_CHARS:
81
+ raise ValueError(f"Text too long. Maximum {MAX_CHARS} characters (~5 min audio).")
82
 
83
  # Generate audio samples
84
  samples, sample_rate = kokoro.create(
85
  text=text,
86
  voice=voice,
87
  speed=speed,
88
+ lang="en-us"
89
  )
90
 
91
  # Save to temporary file
 
94
  return tmp.name
95
 
96
  # ============== API ENDPOINTS ==============
97
+ @app.get("/")
98
+ def root():
99
+ return {
100
+ "service": "Kokoro TTS API",
101
+ "status": "running",
102
+ "model": "Kokoro-82M",
103
+ "version": "2.0",
104
+ "features": {
105
+ "speed": "10x faster than XTTS",
106
+ "voices": 6,
107
+ "max_chars": MAX_CHARS,
108
+ "emotional": True
109
+ },
110
+ "endpoints": {
111
+ "health": "/health",
112
+ "generate": "/api/generate (POST)",
113
+ "docs": "/docs"
114
+ }
115
+ }
116
+
117
  @app.get("/health")
118
  def health():
119
  return {
120
  "status": "healthy",
121
  "model": "Kokoro TTS 82M",
122
  "speed": "10x faster than XTTS",
123
+ "max_chars": MAX_CHARS,
124
+ "voices": ["af_heart", "af_bella", "am_adam", "am_michael", "bf_emma", "bf_isabella"]
125
  }
126
 
127
  @app.post("/api/generate")
128
  async def generate_tts(
129
  background_tasks: BackgroundTasks,
130
+ text: str = Form(..., description="Text to convert to speech"),
131
+ voice: str = Form("bf_isabella", description="Voice to use"),
132
+ speed: float = Form(1.0, description="Speech speed (0.5-2.0)")
 
 
133
  ):
134
  """
135
  Generate TTS with Kokoro (Fast & Emotional)
136
 
137
+ **Performance:**
138
+ - Max audio: 5 minutes (4500 chars)
139
+ - Generation: ~20-30 seconds on CPU
140
+ - Speech rate: ~900 chars/minute
141
+
142
+ **Available Voices:**
143
+ - `af_heart`: American Female (warm)
144
+ - `af_bella`: American Female (professional)
145
+ - `am_adam`: American Male (confident)
146
+ - `am_michael`: American Male (friendly)
147
+ - `bf_emma`: British Female (elegant)
148
+ - `bf_isabella`: British Female (storytelling) ⭐ Best for long content
149
+
150
+ **Example:**
151
+ ```bash
152
+ curl -X POST https://your-space.hf.space/api/generate \\
153
+ -F "text=Hello world, this is Kokoro TTS!" \\
154
+ -F "voice=bf_isabella" \\
155
+ -F "speed=1.0" \\
156
+ --output audio.wav
157
+ ```
 
158
  """
 
 
 
159
  try:
160
+ # Validate speed
161
+ if speed < 0.5 or speed > 2.0:
162
+ raise HTTPException(status_code=400, detail="Speed must be between 0.5 and 2.0")
163
 
164
+ # Generate speech
165
+ output_path = generate_speech(text.strip(), voice, speed)
166
 
167
+ # Schedule cleanup after response is sent
168
  background_tasks.add_task(cleanup_file, output_path)
169
 
170
+ # Return audio file
171
+ response = FileResponse(
172
+ output_path,
173
+ media_type="audio/wav",
174
+ filename=f"kokoro_{datetime.now().strftime('%Y%m%d_%H%M%S')}.wav"
175
+ )
176
+ response.headers["X-Character-Count"] = str(len(text))
177
+ response.headers["X-Voice-Used"] = voice
178
+
179
  return response
180
 
181
  except ValueError as e:
 
183
  except Exception as e:
184
  raise HTTPException(status_code=500, detail=f"TTS generation failed: {str(e)}")
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  if __name__ == "__main__":
187
  import uvicorn
188
  uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt CHANGED
@@ -3,7 +3,4 @@ uvicorn[standard]==0.32.0
3
  kokoro-onnx
4
  soundfile
5
  python-multipart
6
- bcrypt==4.1.2
7
- supabase==2.0.2
8
  numpy
9
- huggingface-hub==0.20.3
 
3
  kokoro-onnx
4
  soundfile
5
  python-multipart
 
 
6
  numpy