tubewatch/playlist-monitor/app/api/videos.py

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)}")