from datetime import datetime from typing import TYPE_CHECKING, Optional from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, String, func from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database.session import Base if TYPE_CHECKING: from app.home.models import Project from app.lyric.models import Lyric from app.song.models import Song class Video(Base): """ 영상 결과 테이블 최종 생성된 영상의 결과 URL을 저장합니다. Creatomate 서비스를 통해 이미지와 노래를 결합한 영상 결과입니다. Attributes: id: 고유 식별자 (자동 증가) project_id: 연결된 Project의 id (외래키) lyric_id: 연결된 Lyric의 id (외래키) song_id: 연결된 Song의 id (외래키) task_id: 영상 생성 작업의 고유 식별자 (UUID7 형식) status: 처리 상태 (pending, processing, completed, failed 등) result_movie_url: 생성된 영상 URL (S3, CDN 경로) created_at: 생성 일시 (자동 설정) Relationships: project: 연결된 Project lyric: 연결된 Lyric song: 연결된 Song """ __tablename__ = "video" __table_args__ = ( Index("idx_video_task_id", "task_id"), Index("idx_video_project_id", "project_id"), Index("idx_video_lyric_id", "lyric_id"), Index("idx_video_song_id", "song_id"), Index("idx_video_is_deleted", "is_deleted"), { "mysql_engine": "InnoDB", "mysql_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci", }, ) id: Mapped[int] = mapped_column( Integer, primary_key=True, nullable=False, autoincrement=True, comment="고유 식별자", ) project_id: Mapped[int] = mapped_column( Integer, ForeignKey("project.id", ondelete="CASCADE"), nullable=False, comment="연결된 Project의 id", ) lyric_id: Mapped[int] = mapped_column( Integer, ForeignKey("lyric.id", ondelete="CASCADE"), nullable=False, comment="연결된 Lyric의 id", ) song_id: Mapped[int] = mapped_column( Integer, ForeignKey("song.id", ondelete="CASCADE"), nullable=False, comment="연결된 Song의 id", ) task_id: Mapped[str] = mapped_column( String(36), nullable=False, comment="영상 생성 작업 고유 식별자 (UUID7)", ) creatomate_render_id: Mapped[Optional[str]] = mapped_column( String(64), nullable=True, comment="Creatomate API 렌더 ID", ) status: Mapped[str] = mapped_column( String(50), nullable=False, comment="처리 상태 (processing, completed, failed)", ) result_movie_url: Mapped[Optional[str]] = mapped_column( String(2048), nullable=True, comment="생성된 영상 URL", ) is_deleted: Mapped[bool] = mapped_column( Boolean, nullable=False, default=False, comment="소프트 삭제 여부 (True: 삭제됨)", ) created_at: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=func.now(), comment="생성 일시", ) # Relationships project: Mapped["Project"] = relationship( "Project", back_populates="videos", ) lyric: Mapped["Lyric"] = relationship( "Lyric", back_populates="videos", ) song: Mapped["Song"] = relationship( "Song", back_populates="videos", ) def __repr__(self) -> str: def truncate(value: str | None, max_len: int = 10) -> str: if value is None: return "None" return (value[:max_len] + "...") if len(value) > max_len else value return ( f"" )