""" Video generation endpoints Supports both synchronous and asynchronous video generation. """ import os from fastapi import APIRouter, HTTPException, Request from loguru import logger from api.dependencies import PixelleVideoDep from api.schemas.video import ( VideoGenerateRequest, VideoGenerateResponse, VideoGenerateAsyncResponse, ) from api.tasks import task_manager, TaskType router = APIRouter(prefix="/video", tags=["Video Generation"]) def path_to_url(request: Request, file_path: str) -> str: """Convert file path to accessible URL""" # file_path is like "output/abc123.mp4" # Remove "output/" prefix for cleaner URL if file_path.startswith("output/"): file_path = file_path[7:] # Remove "output/" base_url = str(request.base_url).rstrip('/') return f"{base_url}/api/files/{file_path}" @router.post("/generate/sync", response_model=VideoGenerateResponse) async def generate_video_sync( request_body: VideoGenerateRequest, pixelle_video: PixelleVideoDep, request: Request ): """ Generate video synchronously This endpoint blocks until video generation is complete. Suitable for small videos (< 30 seconds). **Note**: May timeout for large videos. Use `/generate/async` instead. Request body includes all video generation parameters. See VideoGenerateRequest schema for details. Returns path to generated video, duration, and file size. """ try: logger.info(f"Sync video generation: {request_body.text[:50]}...") # Build video generation parameters video_params = { "text": request_body.text, "mode": request_body.mode, "title": request_body.title, "n_scenes": request_body.n_scenes, "min_narration_words": request_body.min_narration_words, "max_narration_words": request_body.max_narration_words, "min_image_prompt_words": request_body.min_image_prompt_words, "max_image_prompt_words": request_body.max_image_prompt_words, "image_width": request_body.image_width, "image_height": request_body.image_height, "image_workflow": request_body.image_workflow, "video_fps": request_body.video_fps, "frame_template": request_body.frame_template, "prompt_prefix": request_body.prompt_prefix, "bgm_path": request_body.bgm_path, "bgm_volume": request_body.bgm_volume, } # Add TTS workflow if specified if request_body.tts_workflow: video_params["tts_workflow"] = request_body.tts_workflow # Add ref_audio if specified if request_body.ref_audio: video_params["ref_audio"] = request_body.ref_audio # Legacy voice_id support (deprecated) if request_body.voice_id: logger.warning("voice_id parameter is deprecated, please use tts_workflow instead") video_params["voice_id"] = request_body.voice_id # Call video generator service result = await pixelle_video.generate_video(**video_params) # Get file size file_size = os.path.getsize(result.video_path) if os.path.exists(result.video_path) else 0 # Convert path to URL video_url = path_to_url(request, result.video_path) return VideoGenerateResponse( video_url=video_url, duration=result.duration, file_size=file_size ) except Exception as e: logger.error(f"Sync video generation error: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/generate/async", response_model=VideoGenerateAsyncResponse) async def generate_video_async( request_body: VideoGenerateRequest, pixelle_video: PixelleVideoDep, request: Request ): """ Generate video asynchronously Creates a background task for video generation. Returns immediately with a task_id for tracking progress. **Workflow:** 1. Submit video generation request 2. Receive task_id in response 3. Poll `/api/tasks/{task_id}` to check status 4. When status is "completed", retrieve video from result Request body includes all video generation parameters. See VideoGenerateRequest schema for details. Returns task_id for tracking progress. """ try: logger.info(f"Async video generation: {request_body.text[:50]}...") # Create task task = task_manager.create_task( task_type=TaskType.VIDEO_GENERATION, request_params=request_body.model_dump() ) # Define async execution function async def execute_video_generation(): """Execute video generation in background""" # Build video generation parameters video_params = { "text": request_body.text, "mode": request_body.mode, "title": request_body.title, "n_scenes": request_body.n_scenes, "min_narration_words": request_body.min_narration_words, "max_narration_words": request_body.max_narration_words, "min_image_prompt_words": request_body.min_image_prompt_words, "max_image_prompt_words": request_body.max_image_prompt_words, "image_width": request_body.image_width, "image_height": request_body.image_height, "image_workflow": request_body.image_workflow, "video_fps": request_body.video_fps, "frame_template": request_body.frame_template, "prompt_prefix": request_body.prompt_prefix, "bgm_path": request_body.bgm_path, "bgm_volume": request_body.bgm_volume, # Progress callback can be added here if needed # "progress_callback": lambda event: task_manager.update_progress(...) } # Add TTS workflow if specified if request_body.tts_workflow: video_params["tts_workflow"] = request_body.tts_workflow # Add ref_audio if specified if request_body.ref_audio: video_params["ref_audio"] = request_body.ref_audio # Legacy voice_id support (deprecated) if request_body.voice_id: logger.warning("voice_id parameter is deprecated, please use tts_workflow instead") video_params["voice_id"] = request_body.voice_id result = await pixelle_video.generate_video(**video_params) # Get file size file_size = os.path.getsize(result.video_path) if os.path.exists(result.video_path) else 0 # Convert path to URL video_url = path_to_url(request, result.video_path) return { "video_url": video_url, "duration": result.duration, "file_size": file_size } # Start execution await task_manager.execute_task( task_id=task.task_id, coro_func=execute_video_generation ) return VideoGenerateAsyncResponse( task_id=task.task_id ) except Exception as e: logger.error(f"Async video generation error: {e}") raise HTTPException(status_code=500, detail=str(e))