207 lines
6.3 KiB
Python
207 lines
6.3 KiB
Python
"""
|
|
Video API endpoints
|
|
"""
|
|
|
|
import logging
|
|
from typing import List, Optional
|
|
from datetime import datetime
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.orm import Session
|
|
|
|
from ..core.database import get_db
|
|
from ..models.video import VideoRecord, VideoStatus
|
|
from ..services.video_service import VideoService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
# Pydantic models for API requests/responses
|
|
class VideoResponse(BaseModel):
|
|
"""Video response model"""
|
|
id: str
|
|
playlist_id: str
|
|
video_url: str
|
|
video_id: str
|
|
title: Optional[str]
|
|
playlist_index: Optional[int]
|
|
upload_date: Optional[datetime]
|
|
status: str
|
|
download_requested_at: Optional[datetime]
|
|
download_completed_at: Optional[datetime]
|
|
metube_download_id: Optional[str]
|
|
original_filename: Optional[str]
|
|
file_moved: bool
|
|
file_location_note: Optional[str]
|
|
error_message: Optional[str]
|
|
retry_count: int
|
|
last_error_at: Optional[datetime]
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class VideoActionResponse(BaseModel):
|
|
"""Response for video actions"""
|
|
status: str
|
|
message: str
|
|
video: Optional[VideoResponse] = None
|
|
|
|
|
|
@router.get("/{video_id}", response_model=VideoResponse)
|
|
async def get_video(
|
|
video_id: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get a specific video record"""
|
|
try:
|
|
service = VideoService(db)
|
|
video = service.get_video(video_id)
|
|
|
|
if not video:
|
|
raise HTTPException(status_code=404, detail="Video not found")
|
|
|
|
return video
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error getting video {video_id}: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Error getting video: {str(e)}")
|
|
|
|
|
|
@router.post("/{video_id}/download", response_model=VideoActionResponse)
|
|
async def trigger_video_download(
|
|
video_id: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Manually trigger download for a video"""
|
|
try:
|
|
service = VideoService(db)
|
|
|
|
# Get video
|
|
video = service.get_video(video_id)
|
|
if not video:
|
|
raise HTTPException(status_code=404, detail="Video not found")
|
|
|
|
# Trigger download
|
|
result = await service.download_video(video_id)
|
|
|
|
return VideoActionResponse(
|
|
status="ok",
|
|
message="Download triggered successfully",
|
|
video=result
|
|
)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except ValueError as e:
|
|
logger.warning(f"Cannot download video {video_id}: {e}")
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
except Exception as e:
|
|
logger.error(f"Error downloading video {video_id}: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Error downloading video: {str(e)}")
|
|
|
|
|
|
@router.post("/{video_id}/file-moved", response_model=VideoActionResponse)
|
|
async def mark_file_as_moved(
|
|
video_id: str,
|
|
location_note: Optional[str] = Query(None, description="Optional note about new file location"),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Mark a video file as moved by the user"""
|
|
try:
|
|
service = VideoService(db)
|
|
|
|
# Get video
|
|
video = service.get_video(video_id)
|
|
if not video:
|
|
raise HTTPException(status_code=404, detail="Video not found")
|
|
|
|
# Mark as moved
|
|
updated_video = service.mark_file_as_moved(video_id, location_note)
|
|
|
|
return VideoActionResponse(
|
|
status="ok",
|
|
message="File marked as moved successfully",
|
|
video=updated_video
|
|
)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except ValueError as e:
|
|
logger.warning(f"Cannot mark file as moved for video {video_id}: {e}")
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
except Exception as e:
|
|
logger.error(f"Error marking file as moved for video {video_id}: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Error marking file as moved: {str(e)}")
|
|
|
|
|
|
@router.post("/{video_id}/skip", response_model=VideoActionResponse)
|
|
async def skip_video(
|
|
video_id: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Mark a video as skipped (won't be downloaded)"""
|
|
try:
|
|
service = VideoService(db)
|
|
|
|
# Get video
|
|
video = service.get_video(video_id)
|
|
if not video:
|
|
raise HTTPException(status_code=404, detail="Video not found")
|
|
|
|
# Skip video
|
|
updated_video = service.skip_video(video_id)
|
|
|
|
return VideoActionResponse(
|
|
status="ok",
|
|
message="Video marked as skipped",
|
|
video=updated_video
|
|
)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except ValueError as e:
|
|
logger.warning(f"Cannot skip video {video_id}: {e}")
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
except Exception as e:
|
|
logger.error(f"Error skipping video {video_id}: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Error skipping video: {str(e)}")
|
|
|
|
|
|
@router.post("/{video_id}/reset", response_model=VideoActionResponse)
|
|
async def reset_video(
|
|
video_id: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Reset a video to pending status (allow re-download)"""
|
|
try:
|
|
service = VideoService(db)
|
|
|
|
# Get video
|
|
video = service.get_video(video_id)
|
|
if not video:
|
|
raise HTTPException(status_code=404, detail="Video not found")
|
|
|
|
# Reset video
|
|
updated_video = service.reset_video(video_id)
|
|
|
|
return VideoActionResponse(
|
|
status="ok",
|
|
message="Video reset to pending status",
|
|
video=updated_video
|
|
)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except ValueError as e:
|
|
logger.warning(f"Cannot reset video {video_id}: {e}")
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
except Exception as e:
|
|
logger.error(f"Error resetting video {video_id}: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Error resetting video: {str(e)}") |