File size: 3,247 Bytes
cfe45d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import enum
from datetime import datetime
from sqlalchemy import String, Text, Enum, DateTime, ForeignKey, Integer, Boolean
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base


class ProjectStatus(str, enum.Enum):
    CREATED = "created"
    SCRAPED = "scraped"
    SCRIPTED = "scripted"
    GENERATED = "generated"
    RENDERING = "rendering"
    DONE = "done"
    ERROR = "error"


class Project(Base):
    __tablename__ = "projects"

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
    name: Mapped[str] = mapped_column(String(255), nullable=False)
    blog_url: Mapped[str | None] = mapped_column(String(2048), nullable=True)
    blog_content: Mapped[str | None] = mapped_column(Text, nullable=True)
    status: Mapped[ProjectStatus] = mapped_column(
        Enum(ProjectStatus), default=ProjectStatus.CREATED
    )
    voice_gender: Mapped[str] = mapped_column(String(10), default="female")
    voice_accent: Mapped[str] = mapped_column(String(10), default="american")
    accent_color: Mapped[str] = mapped_column(String(20), default="#7C3AED")
    bg_color: Mapped[str] = mapped_column(String(20), default="#FFFFFF")
    text_color: Mapped[str] = mapped_column(String(20), default="#000000")
    animation_instructions: Mapped[str | None] = mapped_column(Text, nullable=True)
    studio_unlocked: Mapped[bool] = mapped_column(Boolean, default=False)
    studio_port: Mapped[int | None] = mapped_column(nullable=True)
    player_port: Mapped[int | None] = mapped_column(nullable=True)
    r2_video_key: Mapped[str | None] = mapped_column(String(512), nullable=True)
    r2_video_url: Mapped[str | None] = mapped_column(String(2048), nullable=True)

    # Logo overlay
    logo_r2_key: Mapped[str | None] = mapped_column(String(512), nullable=True)
    logo_r2_url: Mapped[str | None] = mapped_column(String(2048), nullable=True)
    logo_position: Mapped[str] = mapped_column(String(20), default="bottom_right")
    logo_opacity: Mapped[float] = mapped_column(default=0.9)

    # Voiceover
    custom_voice_id: Mapped[str | None] = mapped_column(String(100), nullable=True)

    # Template (determines layout system + DSPy prompt)
    template: Mapped[str] = mapped_column(String(50), default="default")

    # Aspect ratio
    aspect_ratio: Mapped[str] = mapped_column(String(20), default="landscape")
    
    # AI-assisted editing usage tracking
    ai_assisted_editing_count: Mapped[int] = mapped_column(Integer, default=0)
    
    created_at: Mapped[datetime] = mapped_column(
        DateTime, default=datetime.utcnow
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
    )

    # Relationships
    user = relationship("User", back_populates="projects")
    scenes = relationship("Scene", back_populates="project", cascade="all, delete-orphan", order_by="Scene.order")
    assets = relationship("Asset", back_populates="project", cascade="all, delete-orphan")
    chat_messages = relationship("ChatMessage", back_populates="project", cascade="all, delete-orphan", order_by="ChatMessage.created_at")