from fastapi import APIRouter, HTTPException, BackgroundTasks from pydantic import BaseModel from typing import Optional from pathlib import Path import uuid import traceback 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 async def _process_video_generation(task_id: str, req: GenerateRequest): try: # 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"] = "Initializing generation..." # 1. TTS tasks[task_id]["message"] = "Generating Audio (TTS)..." tts = TTSService() audio_path = settings.OUTPUT_DIR / f"{task_id}_audio.mp3" await tts.generate_audio(req.text, req.voice, str(audio_path)) tasks[task_id]["progress"] = 30 # 2. LipSync tasks[task_id]["message"] = "Synthesizing Video (MuseTalk)..." lipsync = LipSyncService() lipsync_video_path = settings.OUTPUT_DIR / f"{task_id}_lipsync.mp4" # Check health and generate health = await lipsync.check_health() print(f"[LipSync] Health check: {health}") if health.get("ready", False): print(f"[LipSync] Starting MuseTalk inference...") await lipsync.generate(str(input_material_path), str(audio_path), str(lipsync_video_path)) else: # Skip lipsync if not available print(f"[LipSync] MuseTalk not ready, copying original video") import shutil shutil.copy(str(input_material_path), lipsync_video_path) tasks[task_id]["progress"] = 80 # 3. Composition tasks[task_id]["message"] = "Final compositing..." 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)) tasks[task_id]["status"] = "completed" tasks[task_id]["progress"] = 100 tasks[task_id]["message"] = "Generation Complete!" 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"Error: {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} 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())}