""" 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 ReelForgeDep 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, reelforge: ReelForgeDep, 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]}...") # Call video generator service result = await reelforge.generate_video( text=request_body.text, mode=request_body.mode, title=request_body.title, n_scenes=request_body.n_scenes, voice_id=request_body.voice_id, 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_width=request_body.video_width, video_height=request_body.video_height, 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, ) # 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, reelforge: ReelForgeDep, 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""" result = await reelforge.generate_video( text=request_body.text, mode=request_body.mode, title=request_body.title, n_scenes=request_body.n_scenes, voice_id=request_body.voice_id, 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_width=request_body.video_width, video_height=request_body.video_height, 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(...) ) # 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))