""" Video record model for tracking individual videos in playlists """ import uuid from datetime import datetime from enum import Enum from typing import Optional from sqlalchemy import Column, String, Integer, DateTime, Boolean, Text, ForeignKey, Index from sqlalchemy.orm import relationship from sqlalchemy.sql import func from ..core.database import Base class VideoStatus(str, Enum): """Video download status enumeration""" PENDING = "PENDING" # Not yet downloaded DOWNLOADING = "DOWNLOADING" # Currently being downloaded COMPLETED = "COMPLETED" # Successfully downloaded FAILED = "FAILED" # Download failed SKIPPED = "SKIPPED" # Before start_point or manually skipped class VideoRecord(Base): """Video record model for tracking individual videos""" __tablename__ = "videos" id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) playlist_id = Column(String, ForeignKey("playlists.id", ondelete="CASCADE"), nullable=False) # Video metadata video_url = Column(String, nullable=False) video_id = Column(String, nullable=False) # YouTube video ID title = Column(String, nullable=True) playlist_index = Column(Integer, nullable=True) # Position in playlist upload_date = Column(DateTime, nullable=True) # Download tracking status = Column(String, default=VideoStatus.PENDING, nullable=False) download_requested_at = Column(DateTime, nullable=True) download_completed_at = Column(DateTime, nullable=True) metube_download_id = Column(String, nullable=True) # Reference to MeTube download # File tracking (decoupled from actual file) original_filename = Column(String, nullable=True) # Filename when downloaded file_moved = Column(Boolean, default=False, nullable=False) # Whether user moved the file file_location_note = Column(Text, nullable=True) # Optional note about file location # Error handling error_message = Column(Text, nullable=True) retry_count = Column(Integer, default=0, nullable=False) last_error_at = Column(DateTime, nullable=True) # Timestamps created_at = Column(DateTime, default=func.now(), nullable=False) updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False) # Relationships playlist = relationship("PlaylistSubscription", back_populates="videos") def __repr__(self): return f"" @property def is_downloadable(self) -> bool: """Check if video can be downloaded""" return self.status in [VideoStatus.PENDING, VideoStatus.FAILED] @property def is_completed(self) -> bool: """Check if video download is completed""" return self.status == VideoStatus.COMPLETED def can_retry(self) -> bool: """Check if video can be retried""" if self.status not in [VideoStatus.FAILED]: return False # Limit retry attempts return self.retry_count < 3 def mark_as_downloading(self, metube_download_id: str) -> None: """Mark video as downloading""" self.status = VideoStatus.DOWNLOADING self.download_requested_at = datetime.utcnow() self.metube_download_id = metube_download_id self.error_message = None self.last_error_at = None def mark_as_completed(self, filename: Optional[str] = None) -> None: """Mark video as completed""" self.status = VideoStatus.COMPLETED self.download_completed_at = datetime.utcnow() if filename: self.original_filename = filename self.error_message = None self.retry_count = 0 def mark_as_failed(self, error_message: str) -> None: """Mark video as failed""" self.status = VideoStatus.FAILED self.error_message = error_message self.last_error_at = datetime.utcnow() self.retry_count += 1 def mark_as_skipped(self) -> None: """Mark video as skipped""" self.status = VideoStatus.SKIPPED self.error_message = None def reset_to_pending(self) -> None: """Reset video to pending status""" self.status = VideoStatus.PENDING self.download_requested_at = None self.download_completed_at = None self.metube_download_id = None self.error_message = None self.last_error_at = None self.retry_count = 0 # Create indexes for better query performance Index("idx_videos_playlist_id", VideoRecord.playlist_id) Index("idx_videos_status", VideoRecord.status) Index("idx_videos_video_id", VideoRecord.video_id) Index("idx_videos_playlist_index", VideoRecord.playlist_id, VideoRecord.playlist_index) Index("idx_videos_metube_download_id", VideoRecord.metube_download_id)