Initial commit: Pixel AI comic/video creation platform
- FastAPI backend with SQLModel, Alembic migrations, AgentScope agents - Next.js 15 frontend with React 19, Tailwind, Zustand, React Flow - Multi-provider AI system (DashScope, Kling, MiniMax, Volcengine, OpenAI, etc.) - All HTTP clients migrated from sync requests to async httpx - Admin-managed API keys via environment variables - SSRF vulnerability fixed in ensure_url()
This commit is contained in:
159
backend/src/api/generations/audio.py
Normal file
159
backend/src/api/generations/audio.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
Audio & Export Endpoints - 音频生成和导出模块
|
||||
|
||||
包含:
|
||||
- POST /audio/generate - 音频生成
|
||||
- POST /generations/audio - 统一音频生成
|
||||
- POST /export/project/{project_id} - 项目导出
|
||||
"""
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from src.models.schemas import (
|
||||
ResponseModel,
|
||||
AudioGenerationRequest,
|
||||
GenerateAudioRequest,
|
||||
ExportProjectRequest
|
||||
)
|
||||
from src.services.provider.registry import ModelRegistry, ModelType
|
||||
from src.services.task_manager import task_manager
|
||||
from src.services.export_service import VideoExportService
|
||||
from src.auth.dependencies import get_current_user, UserAuth
|
||||
|
||||
from .helpers import resolve_service, check_user_api_key
|
||||
from src.utils.errors import ErrorCode, AppException
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 初始化 export service
|
||||
export_service = VideoExportService()
|
||||
|
||||
|
||||
@router.post("/audio/generate", response_model=ResponseModel)
|
||||
async def generate_audio(
|
||||
request: GenerateAudioRequest,
|
||||
current_user: UserAuth = Depends(get_current_user)
|
||||
):
|
||||
"""生成 audio from text(兼容旧接口,内部走统一任务系统)"""
|
||||
logger.info("Audio generation request (legacy): model=%s, text=%s...", request.model, request.text[:50])
|
||||
|
||||
# 兼容旧接口:model 可为空,回退到默认音频模型
|
||||
model_name = request.model or ModelRegistry.get_default_id(ModelType.AUDIO)
|
||||
if not model_name:
|
||||
raise AppException(
|
||||
message="Audio service not configured",
|
||||
code=ErrorCode.INTERNAL_ERROR,
|
||||
status_code=500
|
||||
)
|
||||
|
||||
# 验证模型可解析并获取服务
|
||||
audio_service = resolve_service(model_name, ModelType.AUDIO)
|
||||
|
||||
# 检查用户是否配置了 API Key
|
||||
# 从 audio_service 获取 provider_id
|
||||
provider = getattr(audio_service, 'provider_id', None)
|
||||
if provider:
|
||||
check_user_api_key(current_user.id, provider)
|
||||
|
||||
try:
|
||||
task_params = {
|
||||
"text": request.text,
|
||||
"voice": request.voice,
|
||||
"model": model_name,
|
||||
"project_id": request.project_id,
|
||||
"storyboard_id": request.storyboard_id,
|
||||
"extra_params": request.extra_params or {},
|
||||
}
|
||||
|
||||
task = await task_manager.create_task(
|
||||
task_type="audio",
|
||||
model=model_name,
|
||||
params=task_params,
|
||||
user_id=current_user.id,
|
||||
project_id=request.project_id,
|
||||
)
|
||||
|
||||
# 兼容前端:保留 audio_url 字段但异步任务初始为空
|
||||
return ResponseModel(data={
|
||||
"audio_url": None,
|
||||
"task_id": task.id,
|
||||
"status": task.status
|
||||
})
|
||||
except Exception as e:
|
||||
raise AppException(
|
||||
message=str(e),
|
||||
code=ErrorCode.INTERNAL_ERROR,
|
||||
status_code=500
|
||||
)
|
||||
|
||||
|
||||
@router.post("/generations/audio", response_model=ResponseModel)
|
||||
async def generate_audio_unified(
|
||||
request: AudioGenerationRequest,
|
||||
current_user: UserAuth = Depends(get_current_user)
|
||||
):
|
||||
"""统一音频生成端点(推荐)"""
|
||||
logger.info("Audio generation request: model=%s, text=%s...", request.model, request.text[:50])
|
||||
|
||||
audio_service = resolve_service(request.model, ModelType.AUDIO)
|
||||
|
||||
# 检查用户是否配置了 API Key
|
||||
# 从 audio_service 获取 provider_id
|
||||
provider = getattr(audio_service, 'provider_id', None)
|
||||
if provider:
|
||||
check_user_api_key(current_user.id, provider)
|
||||
|
||||
if not audio_service:
|
||||
raise AppException(
|
||||
message="Audio service not configured",
|
||||
code=ErrorCode.INTERNAL_ERROR,
|
||||
status_code=500
|
||||
)
|
||||
|
||||
try:
|
||||
task_params = request.model_dump()
|
||||
# Get user_id from authenticated user
|
||||
user_id = current_user.id
|
||||
|
||||
# Handle project_id from multiple sources
|
||||
# Priority 1: request.project_id (from request body, alias="projectId")
|
||||
# Priority 2: request.source_id if source=='project' (from extra_params, alias="sourceId")
|
||||
project_id = request.project_id
|
||||
if not project_id and request.source == "project":
|
||||
project_id = request.source_id
|
||||
|
||||
task = await task_manager.create_task(
|
||||
task_type="audio",
|
||||
model=request.model,
|
||||
params=task_params,
|
||||
user_id=user_id,
|
||||
project_id=project_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Failed to create audio task: %s", e)
|
||||
raise AppException(
|
||||
message=f"Failed to create task: {e}",
|
||||
code=ErrorCode.INTERNAL_ERROR,
|
||||
status_code=500
|
||||
)
|
||||
|
||||
return ResponseModel(data={
|
||||
"task_id": task.id,
|
||||
"status": task.status
|
||||
})
|
||||
|
||||
|
||||
@router.post("/export/project/{project_id}", response_model=ResponseModel)
|
||||
async def export_project(project_id: str, request: ExportProjectRequest):
|
||||
""" 导出 project to video"""
|
||||
try:
|
||||
result = await export_service.export_project(project_id, format=request.format)
|
||||
return ResponseModel(data=result)
|
||||
except Exception as e:
|
||||
raise AppException(
|
||||
message=str(e),
|
||||
code=ErrorCode.INTERNAL_ERROR,
|
||||
status_code=500
|
||||
)
|
||||
Reference in New Issue
Block a user