Files
ViGent2/backend/app/api/videos.py
2026-01-20 17:14:10 +08:00

144 lines
5.5 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.
from fastapi import APIRouter, HTTPException, BackgroundTasks
from pydantic import BaseModel
from typing import Optional
from pathlib import Path
import uuid
import traceback
import time
from app.services.tts_service import TTSService
from app.services.video_service import VideoService
from app.services.lipsync_service import LipSyncService
from app.core.config import settings
router = APIRouter()
class GenerateRequest(BaseModel):
text: str
voice: str = "zh-CN-YunxiNeural"
material_path: str
tasks = {} # In-memory task store
# 缓存 LipSync 服务实例和健康状态
_lipsync_service: Optional[LipSyncService] = None
_lipsync_ready: Optional[bool] = None
_lipsync_last_check: float = 0
def _get_lipsync_service() -> LipSyncService:
"""获取或创建 LipSync 服务实例(单例模式,避免重复初始化)"""
global _lipsync_service
if _lipsync_service is None:
_lipsync_service = LipSyncService()
return _lipsync_service
async def _check_lipsync_ready(force: bool = False) -> bool:
"""检查 LipSync 是否就绪带缓存5分钟内不重复检查"""
global _lipsync_ready, _lipsync_last_check
now = time.time()
# 5分钟缓存
if not force and _lipsync_ready is not None and (now - _lipsync_last_check) < 300:
return _lipsync_ready
lipsync = _get_lipsync_service()
health = await lipsync.check_health()
_lipsync_ready = health.get("ready", False)
_lipsync_last_check = now
print(f"[LipSync] Health check: ready={_lipsync_ready}")
return _lipsync_ready
async def _process_video_generation(task_id: str, req: GenerateRequest):
try:
start_time = time.time()
# Resolve path if it's relative
input_material_path = Path(req.material_path)
if not input_material_path.is_absolute():
input_material_path = settings.BASE_DIR.parent / req.material_path
tasks[task_id]["status"] = "processing"
tasks[task_id]["progress"] = 5
tasks[task_id]["message"] = "正在初始化..."
# 1. TTS - 进度 5% -> 25%
tasks[task_id]["message"] = "正在生成语音 (TTS)..."
tasks[task_id]["progress"] = 10
tts = TTSService()
audio_path = settings.OUTPUT_DIR / f"{task_id}_audio.mp3"
await tts.generate_audio(req.text, req.voice, str(audio_path))
tts_time = time.time() - start_time
print(f"[Pipeline] TTS completed in {tts_time:.1f}s")
tasks[task_id]["progress"] = 25
# 2. LipSync - 进度 25% -> 85%
tasks[task_id]["message"] = "正在合成唇形 (LatentSync)..."
tasks[task_id]["progress"] = 30
lipsync = _get_lipsync_service()
lipsync_video_path = settings.OUTPUT_DIR / f"{task_id}_lipsync.mp4"
# 使用缓存的健康检查结果
lipsync_start = time.time()
is_ready = await _check_lipsync_ready()
if is_ready:
print(f"[LipSync] Starting LatentSync inference...")
tasks[task_id]["progress"] = 35
tasks[task_id]["message"] = "正在运行 LatentSync 推理..."
await lipsync.generate(str(input_material_path), str(audio_path), str(lipsync_video_path))
else:
# Skip lipsync if not available
print(f"[LipSync] LatentSync not ready, copying original video")
tasks[task_id]["message"] = "唇形同步不可用,使用原始视频..."
import shutil
shutil.copy(str(input_material_path), lipsync_video_path)
lipsync_time = time.time() - lipsync_start
print(f"[Pipeline] LipSync completed in {lipsync_time:.1f}s")
tasks[task_id]["progress"] = 85
# 3. Composition - 进度 85% -> 100%
tasks[task_id]["message"] = "正在合成最终视频..."
tasks[task_id]["progress"] = 90
video = VideoService()
final_output = settings.OUTPUT_DIR / f"{task_id}_output.mp4"
await video.compose(str(lipsync_video_path), str(audio_path), str(final_output))
total_time = time.time() - start_time
print(f"[Pipeline] Total generation time: {total_time:.1f}s")
tasks[task_id]["status"] = "completed"
tasks[task_id]["progress"] = 100
tasks[task_id]["message"] = f"生成完成!耗时 {total_time:.0f}"
tasks[task_id]["output"] = str(final_output)
tasks[task_id]["download_url"] = f"/outputs/{final_output.name}"
except Exception as e:
tasks[task_id]["status"] = "failed"
tasks[task_id]["message"] = f"错误: {str(e)}"
tasks[task_id]["error"] = traceback.format_exc()
@router.post("/generate")
async def generate_video(req: GenerateRequest, background_tasks: BackgroundTasks):
task_id = str(uuid.uuid4())
tasks[task_id] = {"status": "pending", "task_id": task_id, "progress": 0}
background_tasks.add_task(_process_video_generation, task_id, req)
return {"task_id": task_id}
@router.get("/tasks/{task_id}")
async def get_task(task_id: str):
return tasks.get(task_id, {"status": "not_found"})
@router.get("/tasks")
async def list_tasks():
return {"tasks": list(tasks.values())}
@router.get("/lipsync/health")
async def lipsync_health():
"""获取 LipSync 服务健康状态"""
lipsync = _get_lipsync_service()
return await lipsync.check_health()