Spaces:
Sleeping
Sleeping
Commit ·
0d8df70
1
Parent(s): f9c6021
Add WebSocket endpoint for true real-time bidirectional streaming
Browse files
api.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
-
from fastapi import FastAPI, HTTPException
|
| 2 |
from fastapi.responses import StreamingResponse
|
|
|
|
| 3 |
import torch
|
| 4 |
import numpy as np
|
| 5 |
import re
|
|
@@ -179,10 +180,13 @@ async def root():
|
|
| 179 |
"speakers": list(SPEAKERS.keys()),
|
| 180 |
"sample_rate": SAMPLE_RATE,
|
| 181 |
"endpoints": {
|
| 182 |
-
"/tts": "Standard TTS (wait for full audio)",
|
| 183 |
-
"/tts/stream": "Streaming TTS (audio chunks in real-time
|
| 184 |
-
"/
|
| 185 |
-
|
|
|
|
|
|
|
|
|
|
| 186 |
}
|
| 187 |
|
| 188 |
|
|
@@ -375,6 +379,78 @@ async def get_speakers():
|
|
| 375 |
return {"speakers": list(SPEAKERS.keys())}
|
| 376 |
|
| 377 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
if __name__ == "__main__":
|
| 379 |
import uvicorn
|
| 380 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
|
| 2 |
from fastapi.responses import StreamingResponse
|
| 3 |
+
import json
|
| 4 |
import torch
|
| 5 |
import numpy as np
|
| 6 |
import re
|
|
|
|
| 180 |
"speakers": list(SPEAKERS.keys()),
|
| 181 |
"sample_rate": SAMPLE_RATE,
|
| 182 |
"endpoints": {
|
| 183 |
+
"POST /tts": "Standard TTS (wait for full audio)",
|
| 184 |
+
"POST /tts/stream": "HTTP Streaming TTS (audio chunks in real-time)",
|
| 185 |
+
"WS /ws/tts": "WebSocket TTS (BEST FOR PIPECAT - true real-time bidirectional)",
|
| 186 |
+
"GET /speakers": "List available speakers"
|
| 187 |
+
},
|
| 188 |
+
"device": DEVICE,
|
| 189 |
+
"optimization": "fp16 (half precision)"
|
| 190 |
}
|
| 191 |
|
| 192 |
|
|
|
|
| 379 |
return {"speakers": list(SPEAKERS.keys())}
|
| 380 |
|
| 381 |
|
| 382 |
+
@app.websocket("/ws/tts")
|
| 383 |
+
async def websocket_tts(websocket: WebSocket):
|
| 384 |
+
"""WebSocket endpoint for real-time audio streaming.
|
| 385 |
+
|
| 386 |
+
Usage:
|
| 387 |
+
1. Connect to ws://localhost:7860/ws/tts
|
| 388 |
+
2. Send JSON: {"text": "سلام دنیا", "speaker": "Divya"}
|
| 389 |
+
3. Receive audio chunks in real-time
|
| 390 |
+
4. Connection closes when generation completes
|
| 391 |
+
"""
|
| 392 |
+
await websocket.accept()
|
| 393 |
+
try:
|
| 394 |
+
data = await websocket.receive_text()
|
| 395 |
+
request_data = json.loads(data)
|
| 396 |
+
|
| 397 |
+
text = request_data.get("text", "").strip()
|
| 398 |
+
speaker = request_data.get("speaker", "Divya")
|
| 399 |
+
pitch = request_data.get("pitch", "Moderate")
|
| 400 |
+
rate = request_data.get("rate", "Moderate")
|
| 401 |
+
temperature = request_data.get("temperature", 0.8)
|
| 402 |
+
do_sample = request_data.get("do_sample", True)
|
| 403 |
+
|
| 404 |
+
# Validate inputs
|
| 405 |
+
if not text:
|
| 406 |
+
await websocket.send_json({"error": "Text cannot be empty"})
|
| 407 |
+
await websocket.close()
|
| 408 |
+
return
|
| 409 |
+
|
| 410 |
+
if speaker not in SPEAKERS:
|
| 411 |
+
await websocket.send_json({
|
| 412 |
+
"error": f"Invalid speaker. Choose from: {list(SPEAKERS.keys())}"
|
| 413 |
+
})
|
| 414 |
+
await websocket.close()
|
| 415 |
+
return
|
| 416 |
+
|
| 417 |
+
# Send status message
|
| 418 |
+
await websocket.send_json({
|
| 419 |
+
"status": "generating",
|
| 420 |
+
"message": f"Generating audio for speaker {speaker}..."
|
| 421 |
+
})
|
| 422 |
+
|
| 423 |
+
# Generate and stream audio chunks
|
| 424 |
+
chunk_count = 0
|
| 425 |
+
try:
|
| 426 |
+
for audio_chunk in generate_audio_chunks_streaming(
|
| 427 |
+
text, speaker, pitch, rate, temperature, do_sample
|
| 428 |
+
):
|
| 429 |
+
# Send audio chunk as binary data
|
| 430 |
+
await websocket.send_bytes(audio_chunk.tobytes())
|
| 431 |
+
chunk_count += 1
|
| 432 |
+
|
| 433 |
+
# Send completion message
|
| 434 |
+
await websocket.send_json({
|
| 435 |
+
"status": "complete",
|
| 436 |
+
"chunks_sent": chunk_count
|
| 437 |
+
})
|
| 438 |
+
|
| 439 |
+
except Exception as e:
|
| 440 |
+
await websocket.send_json({
|
| 441 |
+
"error": f"Generation failed: {str(e)}"
|
| 442 |
+
})
|
| 443 |
+
|
| 444 |
+
except WebSocketDisconnect:
|
| 445 |
+
print("WebSocket client disconnected")
|
| 446 |
+
except json.JSONDecodeError:
|
| 447 |
+
await websocket.send_json({"error": "Invalid JSON format"})
|
| 448 |
+
await websocket.close()
|
| 449 |
+
except Exception as e:
|
| 450 |
+
print(f"WebSocket error: {e}")
|
| 451 |
+
await websocket.close()
|
| 452 |
+
|
| 453 |
+
|
| 454 |
if __name__ == "__main__":
|
| 455 |
import uvicorn
|
| 456 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|