Implement audio generation endpoint
- Add POST /api/tts endpoint with full request validation - Validate text, language code, speed range, and roles list - Integrate TTSService to generate multi-voice audiobooks - Fix Kokoro pipeline generator handling to properly collect audio - Stream audio response with correct Content-Type (audio/wav) - Add Content-Disposition header with filename (audiobook.wav) - Handle errors gracefully with appropriate HTTP status codes - Add comprehensive test suite for endpoint validation Requirements: 6.1, 6.7, 6.8, 9.5
This commit is contained in:
125
backend/main.py
125
backend/main.py
@@ -5,12 +5,13 @@ import logging
|
||||
from typing import List, Optional
|
||||
from fastapi import FastAPI, Request, status
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# Import services
|
||||
from services.role_detector import RoleDetector
|
||||
from services.tts_service import TTSService
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
@@ -96,6 +97,7 @@ async def general_exception_handler(request: Request, exc: Exception):
|
||||
|
||||
# Initialize services
|
||||
role_detector = RoleDetector()
|
||||
tts_service = TTSService()
|
||||
|
||||
# API Endpoints
|
||||
@app.get("/api/health")
|
||||
@@ -124,3 +126,124 @@ async def detect_roles(request: RoleDetectionRequest):
|
||||
except Exception as e:
|
||||
logger.error(f"Error during role detection: {str(e)}", exc_info=True)
|
||||
raise
|
||||
|
||||
@app.post("/api/tts")
|
||||
async def generate_audiobook(request: TTSRequest):
|
||||
"""
|
||||
Generates multi-voice audiobook from text with role-based voice assignments.
|
||||
|
||||
Validates request fields, integrates with TTSService to generate audio,
|
||||
and streams the response with appropriate headers.
|
||||
|
||||
Args:
|
||||
request: TTSRequest containing text, language, role mappings, and speed
|
||||
|
||||
Returns:
|
||||
StreamingResponse with audio/wav content
|
||||
|
||||
Raises:
|
||||
400: If validation fails (missing fields, invalid language/voice)
|
||||
500: If audio generation fails
|
||||
"""
|
||||
logger.info(
|
||||
f"Audio generation requested: lang={request.lang}, "
|
||||
f"roles={len(request.roles)}, speed={request.speed}"
|
||||
)
|
||||
|
||||
# Validate required fields (Pydantic handles basic validation)
|
||||
if not request.text or not request.text.strip():
|
||||
logger.error("Empty text provided")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content={
|
||||
"error": "ValidationError",
|
||||
"message": "Text field cannot be empty",
|
||||
"details": {"field": "text"}
|
||||
}
|
||||
)
|
||||
|
||||
# Validate language code
|
||||
supported_langs = ['a', 'b', 'f', 'j']
|
||||
if request.lang not in supported_langs:
|
||||
logger.error(f"Invalid language code: {request.lang}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content={
|
||||
"error": "ValidationError",
|
||||
"message": f"Invalid language code: {request.lang}",
|
||||
"details": {
|
||||
"field": "lang",
|
||||
"supported": supported_langs
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Validate speed range
|
||||
if request.speed < 0.5 or request.speed > 1.5:
|
||||
logger.error(f"Invalid speed value: {request.speed}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content={
|
||||
"error": "ValidationError",
|
||||
"message": "Speed must be between 0.5 and 1.5",
|
||||
"details": {
|
||||
"field": "speed",
|
||||
"value": request.speed,
|
||||
"range": [0.5, 1.5]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Validate roles list is not empty
|
||||
if not request.roles:
|
||||
logger.error("No role mappings provided")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content={
|
||||
"error": "ValidationError",
|
||||
"message": "At least one role mapping is required",
|
||||
"details": {"field": "roles"}
|
||||
}
|
||||
)
|
||||
|
||||
# Convert roles list to dictionary for TTS service
|
||||
role_voice_map = {role.id: role.voice for role in request.roles}
|
||||
|
||||
try:
|
||||
# Generate audiobook
|
||||
audio_bytes = await tts_service.generate_audiobook(
|
||||
text=request.text,
|
||||
lang=request.lang,
|
||||
role_voice_map=role_voice_map,
|
||||
speed=request.speed
|
||||
)
|
||||
|
||||
# Create streaming response with proper headers
|
||||
return StreamingResponse(
|
||||
iter([audio_bytes]),
|
||||
media_type="audio/wav",
|
||||
headers={
|
||||
"Content-Disposition": "attachment; filename=audiobook.wav"
|
||||
}
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"Validation error during generation: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content={
|
||||
"error": "ValidationError",
|
||||
"message": str(e),
|
||||
"details": {}
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error during audio generation: {str(e)}", exc_info=True)
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content={
|
||||
"error": "AudioGenerationError",
|
||||
"message": "Failed to generate audiobook",
|
||||
"details": {"error": str(e)}
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user