2026-01-30 22:41:42 +08:00
|
|
|
"""
|
|
|
|
|
Video Generation API 路由
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
2026-03-14 11:59:07 +08:00
|
|
|
from src.api.base.pipeline import get_pipeline
|
2026-01-30 22:41:42 +08:00
|
|
|
from src.api.handlers.gemini.video_adapter import GeminiVeoAdapter
|
|
|
|
|
from src.api.handlers.openai.video_adapter import OpenAIVideoAdapter
|
|
|
|
|
from src.database import get_db
|
|
|
|
|
|
|
|
|
|
router = APIRouter(tags=["Video Generation"])
|
2026-03-14 11:59:07 +08:00
|
|
|
pipeline = get_pipeline()
|
2026-01-30 22:41:42 +08:00
|
|
|
|
|
|
|
|
|
2026-03-31 19:19:04 +08:00
|
|
|
async def _run_video_route_shell(
|
|
|
|
|
adapter: Any,
|
|
|
|
|
http_request: Request,
|
|
|
|
|
db: Session,
|
|
|
|
|
*,
|
|
|
|
|
path_params: dict[str, Any] | None = None,
|
|
|
|
|
) -> Any:
|
2026-01-30 22:41:42 +08:00
|
|
|
return await pipeline.run(
|
|
|
|
|
adapter=adapter,
|
|
|
|
|
http_request=http_request,
|
|
|
|
|
db=db,
|
|
|
|
|
mode=adapter.mode,
|
|
|
|
|
api_format_hint=adapter.allowed_api_formats[0],
|
2026-03-31 19:19:04 +08:00
|
|
|
path_params=path_params,
|
2026-01-30 22:41:42 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-03-31 19:19:04 +08:00
|
|
|
# -------------------- OpenAI Sora compatible --------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/v1/videos")
|
|
|
|
|
async def create_video_sora(http_request: Request, db: Session = Depends(get_db)) -> Any:
|
|
|
|
|
adapter = OpenAIVideoAdapter()
|
|
|
|
|
return await _run_video_route_shell(adapter, http_request, db)
|
|
|
|
|
|
|
|
|
|
|
2026-02-02 21:16:28 +08:00
|
|
|
@router.post("/v1/videos/{task_id}/cancel")
|
|
|
|
|
async def cancel_video_sora(
|
|
|
|
|
task_id: str, http_request: Request, db: Session = Depends(get_db)
|
|
|
|
|
) -> Any:
|
|
|
|
|
"""Cancel video task (OpenAI Sora style)."""
|
|
|
|
|
adapter = OpenAIVideoAdapter()
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(
|
|
|
|
|
adapter,
|
|
|
|
|
http_request,
|
|
|
|
|
db,
|
2026-02-02 21:16:28 +08:00
|
|
|
path_params={"task_id": task_id, "action": "cancel"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-01-30 22:41:42 +08:00
|
|
|
@router.get("/v1/videos/{task_id}")
|
|
|
|
|
async def get_video_task_sora(
|
|
|
|
|
task_id: str, http_request: Request, db: Session = Depends(get_db)
|
|
|
|
|
) -> Any:
|
|
|
|
|
adapter = OpenAIVideoAdapter()
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(adapter, http_request, db, path_params={"task_id": task_id})
|
2026-01-30 22:41:42 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/v1/videos")
|
|
|
|
|
async def list_video_tasks_sora(http_request: Request, db: Session = Depends(get_db)) -> Any:
|
|
|
|
|
adapter = OpenAIVideoAdapter()
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(adapter, http_request, db)
|
2026-01-30 22:41:42 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/v1/videos/{task_id}")
|
2026-02-02 03:16:52 +08:00
|
|
|
async def delete_video_task_sora(
|
2026-01-30 22:41:42 +08:00
|
|
|
task_id: str, http_request: Request, db: Session = Depends(get_db)
|
|
|
|
|
) -> Any:
|
2026-02-02 03:16:52 +08:00
|
|
|
"""删除已完成或失败的视频及其存储资源"""
|
2026-01-30 22:41:42 +08:00
|
|
|
adapter = OpenAIVideoAdapter()
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(adapter, http_request, db, path_params={"task_id": task_id})
|
2026-01-30 22:41:42 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/v1/videos/{task_id}/content")
|
|
|
|
|
async def download_video_content_sora(
|
|
|
|
|
task_id: str, http_request: Request, db: Session = Depends(get_db)
|
|
|
|
|
) -> Any:
|
|
|
|
|
adapter = OpenAIVideoAdapter()
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(adapter, http_request, db, path_params={"task_id": task_id})
|
2026-01-30 22:41:42 +08:00
|
|
|
|
|
|
|
|
|
2026-02-01 17:28:27 +08:00
|
|
|
@router.post("/v1/videos/{task_id}/remix")
|
|
|
|
|
async def remix_video_sora(
|
|
|
|
|
task_id: str, http_request: Request, db: Session = Depends(get_db)
|
|
|
|
|
) -> Any:
|
|
|
|
|
adapter = OpenAIVideoAdapter()
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(adapter, http_request, db, path_params={"task_id": task_id})
|
2026-02-01 17:28:27 +08:00
|
|
|
|
|
|
|
|
|
2026-01-30 22:41:42 +08:00
|
|
|
# -------------------- Gemini Veo compatible --------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/v1beta/models/{model}:predictLongRunning")
|
|
|
|
|
async def create_video_veo(model: str, http_request: Request, db: Session = Depends(get_db)) -> Any:
|
|
|
|
|
adapter = GeminiVeoAdapter()
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(adapter, http_request, db, path_params={"model": model})
|
2026-01-30 22:41:42 +08:00
|
|
|
|
|
|
|
|
|
2026-02-02 03:16:52 +08:00
|
|
|
# Gemini Veo operation routes - support both formats:
|
|
|
|
|
# 1. models/{model}/operations/{id} (official Gemini Veo format)
|
|
|
|
|
# 2. operations/{...} (legacy format for compatibility)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/v1beta/models/{model}/operations/{operation_id}")
|
|
|
|
|
async def get_video_veo_by_model(
|
|
|
|
|
model: str, operation_id: str, http_request: Request, db: Session = Depends(get_db)
|
2026-01-30 22:41:42 +08:00
|
|
|
) -> Any:
|
2026-02-02 03:16:52 +08:00
|
|
|
"""Get video task status (Gemini Veo format: models/{model}/operations/{id})"""
|
2026-01-30 22:41:42 +08:00
|
|
|
adapter = GeminiVeoAdapter()
|
2026-02-02 03:16:52 +08:00
|
|
|
# Reconstruct full operation name
|
|
|
|
|
full_operation_name = f"models/{model}/operations/{operation_id}"
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(
|
|
|
|
|
adapter,
|
|
|
|
|
http_request,
|
|
|
|
|
db,
|
2026-02-02 03:16:52 +08:00
|
|
|
path_params={"task_id": full_operation_name},
|
2026-01-30 22:41:42 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-02-02 03:16:52 +08:00
|
|
|
@router.post("/v1beta/models/{model}/operations/{operation_id}:cancel")
|
|
|
|
|
async def cancel_video_veo_by_model(
|
|
|
|
|
model: str, operation_id: str, http_request: Request, db: Session = Depends(get_db)
|
|
|
|
|
) -> Any:
|
|
|
|
|
"""Cancel video task (Gemini Veo format: models/{model}/operations/{id}:cancel)"""
|
2026-01-30 22:41:42 +08:00
|
|
|
adapter = GeminiVeoAdapter()
|
2026-02-02 03:16:52 +08:00
|
|
|
full_operation_name = f"models/{model}/operations/{operation_id}"
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(
|
|
|
|
|
adapter,
|
|
|
|
|
http_request,
|
|
|
|
|
db,
|
2026-02-02 03:16:52 +08:00
|
|
|
path_params={"task_id": full_operation_name, "action": "cancel"},
|
2026-01-30 22:41:42 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-02-02 03:16:52 +08:00
|
|
|
# Legacy routes for backward compatibility
|
|
|
|
|
@router.get("/v1beta/operations/{operation_id:path}")
|
|
|
|
|
async def get_video_veo(
|
2026-01-30 22:41:42 +08:00
|
|
|
operation_id: str, http_request: Request, db: Session = Depends(get_db)
|
|
|
|
|
) -> Any:
|
|
|
|
|
adapter = GeminiVeoAdapter()
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(
|
|
|
|
|
adapter,
|
|
|
|
|
http_request,
|
|
|
|
|
db,
|
2026-02-02 03:16:52 +08:00
|
|
|
path_params={"task_id": operation_id},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/v1beta/operations")
|
|
|
|
|
async def list_video_tasks_veo(http_request: Request, db: Session = Depends(get_db)) -> Any:
|
|
|
|
|
adapter = GeminiVeoAdapter()
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(adapter, http_request, db)
|
2026-01-30 22:41:42 +08:00
|
|
|
|
|
|
|
|
|
2026-02-02 03:16:52 +08:00
|
|
|
@router.post("/v1beta/operations/{operation_id}:cancel")
|
|
|
|
|
async def cancel_video_veo(
|
2026-01-30 22:41:42 +08:00
|
|
|
operation_id: str, http_request: Request, db: Session = Depends(get_db)
|
|
|
|
|
) -> Any:
|
|
|
|
|
adapter = GeminiVeoAdapter()
|
2026-03-31 19:19:04 +08:00
|
|
|
return await _run_video_route_shell(
|
|
|
|
|
adapter,
|
|
|
|
|
http_request,
|
|
|
|
|
db,
|
2026-02-02 03:16:52 +08:00
|
|
|
path_params={"task_id": operation_id, "action": "cancel"},
|
2026-01-30 22:41:42 +08:00
|
|
|
)
|
2026-02-02 03:16:52 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# Video download is now handled by /v1beta/files/{task_id}:download in gemini_files.py
|