Files
pixel/backend/src/api/generations/audio.py
张鹏 f9f4560459 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()
2026-04-29 01:20:12 +08:00

160 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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
)