import logging from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks from sqlalchemy.orm import Session from typing import List from datetime import datetime from api.auth import get_current_user from models import db_models from models.schemas import VideoSummaryGenerateRequest, VideoSummaryResponse from core.database import get_db, SessionLocal from api.websocket_routes import manager from services.video_generator_service import video_generator_service from services.slides_video_service import slides_video_service from services.s3_service import s3_service router = APIRouter(prefix="/api/videos", tags=["video-generator"]) logger = logging.getLogger(__name__) async def run_video_generation(summary_id: int, request: VideoSummaryGenerateRequest, user_id: int): """Background task for video summary generation""" logger.info(f"Starting background video generation for ID: {summary_id}") db = SessionLocal() connection_id = f"user_{user_id}" try: db_summary = db.query(db_models.VideoSummary).filter(db_models.VideoSummary.id == summary_id).first() if not db_summary: logger.error(f"Video summary {summary_id} not found in database") return if request.use_slides_transformation: logger.info(f"Task {summary_id}: Using slides transformation pipeline") result = await slides_video_service.generate_transformed_video_summary( file_key=request.file_key, language=request.language, voice_name=request.voice_name, custom_prompt=request.custom_prompt ) else: logger.info(f"Task {summary_id}: Using standard video pipeline") result = await video_generator_service.generate_video_summary( file_key=request.file_key, language=request.language, voice_name=request.voice_name ) if not db_summary.title or "Video Summary " not in db_summary.title: db_summary.title = result["title"] db_summary.s3_key = result["s3_key"] db_summary.s3_url = result["s3_url"] db_summary.status = "completed" db.commit() logger.info(f"Task {summary_id}: Successfully completed") # Notify via WebSocket await manager.send_result(connection_id, { "type": "video", "id": db_summary.id, "status": "completed", "title": db_summary.title }) except Exception as e: logger.error(f"Task {summary_id}: Background video generation failed: {e}") db_summary = db.query(db_models.VideoSummary).filter(db_models.VideoSummary.id == summary_id).first() if db_summary: db_summary.status = "failed" db_summary.error_message = "video generation failed" db.commit() await manager.send_error(connection_id, f"Video generation failed: {str(e)}") finally: db.close() @router.post("/generate", response_model=VideoSummaryResponse) async def generate_video_summary( request: VideoSummaryGenerateRequest, background_tasks: BackgroundTasks, current_user: db_models.User = Depends(get_current_user), db: Session = Depends(get_db) ): """ Initiates video summary generation in the background. """ # Check source ownership source = db.query(db_models.Source).filter( db_models.Source.s3_key == request.file_key, db_models.Source.user_id == current_user.id ).first() if not source: raise HTTPException(status_code=403, detail="Not authorized to access this file") # Create initial processing record file_base = request.file_key.split('/')[-1].rsplit('.', 1)[0] if request.file_key else None title = f"Video Summary {file_base}" db_summary = db_models.VideoSummary( title=title, user_id=current_user.id, source_id=source.id, status="processing" ) db.add(db_summary) db.commit() db.refresh(db_summary) # Offload to background task background_tasks.add_task(run_video_generation, db_summary.id, request, current_user.id) return db_summary @router.get("/list", response_model=List[VideoSummaryResponse]) async def list_video_summaries( current_user: db_models.User = Depends(get_current_user), db: Session = Depends(get_db) ): """ Lists all generated video summaries for the current user. """ try: summaries = db.query(db_models.VideoSummary).filter( db_models.VideoSummary.user_id == current_user.id ).order_by(db_models.VideoSummary.created_at.desc()).all() return [VideoSummaryResponse.model_validate(s) for s in summaries] except Exception as e: logger.error(f"Failed to list video summaries: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/{video_id}", response_model=VideoSummaryResponse) async def get_video_summary( video_id: int, current_user: db_models.User = Depends(get_current_user), db: Session = Depends(get_db) ): """ Retrieves a specific video summary. """ summary = db.query(db_models.VideoSummary).filter( db_models.VideoSummary.id == video_id, db_models.VideoSummary.user_id == current_user.id ).first() if not summary: raise HTTPException(status_code=404, detail="Video summary not found") return VideoSummaryResponse.model_validate(summary) @router.delete("/{video_id}") async def delete_video_summary( video_id: int, current_user: db_models.User = Depends(get_current_user), db: Session = Depends(get_db) ): """ Deletes a specific video summary from database and S3. """ summary = db.query(db_models.VideoSummary).filter( db_models.VideoSummary.id == video_id, db_models.VideoSummary.user_id == current_user.id ).first() if not summary: raise HTTPException(status_code=404, detail="Video summary not found") try: # 1. Delete from S3 if it exists if summary.s3_key: await s3_service.delete_file(summary.s3_key) # 2. Delete from DB db.delete(summary) db.commit() return {"message": "Video summary and associated S3 file deleted successfully"} except Exception as e: db.rollback() logger.error(f"Failed to delete video summary: {e}") raise HTTPException(status_code=500, detail=f"Deletion failed: {str(e)}")