""" FastAPI 推理服务入口。 端点: POST /v1/generate 提交生成任务,返回 job_id GET /v1/jobs/{job_id} 查询任务状态 GET /v1/videos/{job_id} 下载生成视频 GET /health 健康检查 """ from datetime import datetime, timezone from pathlib import Path from celery.result import AsyncResult from fastapi import FastAPI, HTTPException from fastapi.responses import FileResponse from .schemas import ( GenerateRequest, JobResponse, JobStatus, SubmitResponse, ) from .tasks import app as celery_app, generate_video api = FastAPI( title="Open-Sora Inference API", version="1.0.0", description="文本/图像生成视频 API,基于 Open-Sora v2.0(11B)", ) @api.get("/health") def health(): return {"status": "ok", "time": datetime.now(timezone.utc).isoformat()} @api.post("/v1/generate", response_model=SubmitResponse, status_code=202) def submit(req: GenerateRequest): """提交生成任务,立即返回 job_id,客户端轮询 /v1/jobs/{job_id} 获取结果。""" task = generate_video.apply_async( kwargs={"request_dict": req.model_dump()}, ) return SubmitResponse(job_id=task.id) @api.get("/v1/jobs/{job_id}", response_model=JobResponse) def get_job(job_id: str): """查询任务状态。status 可能是 pending / processing / completed / failed。""" result = AsyncResult(job_id, app=celery_app) if result.state == "PENDING": return JobResponse(job_id=job_id, status=JobStatus.pending) if result.state == "STARTED": return JobResponse(job_id=job_id, status=JobStatus.processing) if result.state == "SUCCESS": info = result.result or {} return JobResponse( job_id=job_id, status=JobStatus.completed, video_url=f"/v1/videos/{job_id}", completed_at=info.get("completed_at"), ) if result.state == "FAILURE": return JobResponse( job_id=job_id, status=JobStatus.failed, error=str(result.result), ) # RETRY / REVOKED 等其他状态 return JobResponse(job_id=job_id, status=JobStatus.processing) @api.get("/v1/videos/{job_id}") def download_video(job_id: str): """下载已完成任务的视频文件。""" result = AsyncResult(job_id, app=celery_app) if result.state != "SUCCESS": raise HTTPException(status_code=404, detail="视频尚未生成或任务不存在") info = result.result or {} video_path = Path(info.get("video_path", "")) if not video_path.exists(): raise HTTPException(status_code=404, detail="视频文件不存在") return FileResponse( path=str(video_path), media_type="video/mp4", filename=f"{job_id}.mp4", )