159 lines
7.2 KiB
Python
159 lines
7.2 KiB
Python
"""
|
|
Video extension endpoints.
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, BackgroundTasks
|
|
from sqlalchemy.orm import Session
|
|
from typing import Optional, Dict, Any
|
|
import uuid
|
|
|
|
from ...database import get_db
|
|
from ...models.content_asset_models import AssetSource, AssetType
|
|
from ...services.video_studio import VideoStudioService
|
|
from ...services.asset_service import ContentAssetService
|
|
from ...utils.auth import get_current_user, require_authenticated_user
|
|
from ...utils.logger_utils import get_service_logger
|
|
|
|
logger = get_service_logger("video_studio.endpoints.extend")
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.post("/extend")
|
|
async def extend_video(
|
|
background_tasks: BackgroundTasks,
|
|
file: UploadFile = File(..., description="Video file to extend"),
|
|
prompt: str = Form(..., description="Text prompt describing how to extend the video"),
|
|
model: str = Form("wan-2.5", description="Model to use: 'wan-2.5', 'wan-2.2-spicy', or 'seedance-1.5-pro'"),
|
|
audio: Optional[UploadFile] = File(None, description="Optional audio file to guide generation (WAN 2.5 only)"),
|
|
negative_prompt: Optional[str] = Form(None, description="Negative prompt (WAN 2.5 only)"),
|
|
resolution: str = Form("720p", description="Output resolution: 480p, 720p, or 1080p (1080p WAN 2.5 only)"),
|
|
duration: int = Form(5, description="Duration of extended video in seconds (varies by model)"),
|
|
enable_prompt_expansion: bool = Form(False, description="Enable prompt optimizer (WAN 2.5 only)"),
|
|
generate_audio: bool = Form(True, description="Generate audio for extended video (Seedance 1.5 Pro only)"),
|
|
camera_fixed: bool = Form(False, description="Fix camera position (Seedance 1.5 Pro only)"),
|
|
seed: Optional[int] = Form(None, description="Random seed for reproducibility"),
|
|
current_user: Dict[str, Any] = Depends(get_current_user),
|
|
db: Session = Depends(get_db),
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Extend video duration using WAN 2.5, WAN 2.2 Spicy, or Seedance 1.5 Pro video-extend.
|
|
|
|
Takes a short video clip and extends it with motion/audio continuity.
|
|
"""
|
|
try:
|
|
user_id = require_authenticated_user(current_user)
|
|
|
|
if not file.content_type.startswith('video/'):
|
|
raise HTTPException(status_code=400, detail="File must be a video")
|
|
|
|
# Validate model-specific constraints
|
|
if model in ("wan-2.2-spicy", "wavespeed-ai/wan-2.2-spicy/video-extend"):
|
|
if duration not in [5, 8]:
|
|
raise HTTPException(status_code=400, detail="WAN 2.2 Spicy only supports 5 or 8 second durations")
|
|
if resolution not in ["480p", "720p"]:
|
|
raise HTTPException(status_code=400, detail="WAN 2.2 Spicy only supports 480p or 720p resolution")
|
|
if audio:
|
|
raise HTTPException(status_code=400, detail="Audio is not supported for WAN 2.2 Spicy")
|
|
elif model in ("seedance-1.5-pro", "bytedance/seedance-v1.5-pro/video-extend"):
|
|
if duration < 4 or duration > 12:
|
|
raise HTTPException(status_code=400, detail="Seedance 1.5 Pro only supports 4-12 second durations")
|
|
if resolution not in ["480p", "720p"]:
|
|
raise HTTPException(status_code=400, detail="Seedance 1.5 Pro only supports 480p or 720p resolution")
|
|
if audio:
|
|
raise HTTPException(status_code=400, detail="Audio upload is not supported for Seedance 1.5 Pro (use generate_audio instead)")
|
|
else:
|
|
# WAN 2.5 validation
|
|
if duration < 3 or duration > 10:
|
|
raise HTTPException(status_code=400, detail="WAN 2.5 duration must be between 3 and 10 seconds")
|
|
if resolution not in ["480p", "720p", "1080p"]:
|
|
raise HTTPException(status_code=400, detail="WAN 2.5 resolution must be 480p, 720p, or 1080p")
|
|
|
|
# Initialize services
|
|
video_service = VideoStudioService()
|
|
asset_service = ContentAssetService(db)
|
|
|
|
logger.info(f"[VideoStudio] Video extension request: user={user_id}, model={model}, duration={duration}s, resolution={resolution}")
|
|
|
|
# Read video file
|
|
video_data = await file.read()
|
|
|
|
# Read audio file if provided (WAN 2.5 only)
|
|
audio_data = None
|
|
if audio:
|
|
if model in ("wan-2.2-spicy", "wavespeed-ai/wan-2.2-spicy/video-extend", "seedance-1.5-pro", "bytedance/seedance-v1.5-pro/video-extend"):
|
|
raise HTTPException(status_code=400, detail=f"Audio upload is not supported for {model} model")
|
|
|
|
if not audio.content_type.startswith('audio/'):
|
|
raise HTTPException(status_code=400, detail="Audio file must be an audio file")
|
|
|
|
# Validate audio file size (max 15MB per documentation)
|
|
audio_data = await audio.read()
|
|
if len(audio_data) > 15 * 1024 * 1024:
|
|
raise HTTPException(status_code=400, detail="Audio file must be less than 15MB")
|
|
|
|
# Note: Audio duration validation (3-30s) would require parsing the audio file
|
|
# This is handled by the API, but we could add it here if needed
|
|
|
|
# Extend video
|
|
result = await video_service.extend_video(
|
|
video_data=video_data,
|
|
prompt=prompt,
|
|
model=model,
|
|
audio_data=audio_data,
|
|
negative_prompt=negative_prompt,
|
|
resolution=resolution,
|
|
duration=duration,
|
|
enable_prompt_expansion=enable_prompt_expansion,
|
|
generate_audio=generate_audio,
|
|
camera_fixed=camera_fixed,
|
|
seed=seed,
|
|
user_id=user_id,
|
|
)
|
|
|
|
if not result.get("success"):
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Video extension failed: {result.get('error', 'Unknown error')}"
|
|
)
|
|
|
|
# Store extended version in asset library
|
|
video_url = result.get("video_url")
|
|
if video_url:
|
|
asset_metadata = {
|
|
"original_file": file.filename,
|
|
"prompt": prompt,
|
|
"duration": duration,
|
|
"resolution": resolution,
|
|
"generation_type": "extend",
|
|
"model": result.get("model_used", "alibaba/wan-2.5/video-extend"),
|
|
}
|
|
|
|
asset_service.create_asset(
|
|
user_id=user_id,
|
|
filename=f"extended_{uuid.uuid4().hex[:8]}.mp4",
|
|
file_url=video_url,
|
|
asset_type=AssetType.VIDEO,
|
|
source_module=AssetSource.VIDEO_STUDIO,
|
|
asset_metadata=asset_metadata,
|
|
cost=result.get("cost", 0),
|
|
tags=["video_studio", "extend", "ai-extended"]
|
|
)
|
|
|
|
logger.info(f"[VideoStudio] Video extension successful: user={user_id}, url={video_url}")
|
|
|
|
return {
|
|
"success": True,
|
|
"video_url": video_url,
|
|
"cost": result.get("cost", 0),
|
|
"duration": duration,
|
|
"resolution": resolution,
|
|
"model_used": result.get("model_used", "alibaba/wan-2.5/video-extend"),
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"[VideoStudio] Video extension error: {e}", exc_info=True)
|
|
raise HTTPException(status_code=500, detail=f"Video extension failed: {str(e)}")
|