import uuid from datetime import datetime from sqlalchemy import String, Integer, Float, Boolean, DateTime, Text, ForeignKey from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.dialects.sqlite import JSON from ..database import Base class LibraryItem(Base): __tablename__ = "library_items" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) library_id: Mapped[str] = mapped_column(String(36), ForeignKey("libraries.id", ondelete="CASCADE"), nullable=False) media_type: Mapped[str] = mapped_column(String(20), default="book") # "book" / "podcast" path: Mapped[str] = mapped_column(Text, nullable=False) ino: Mapped[str | None] = mapped_column(String(100), nullable=True) # ABS inode-Feld # Metadaten title: Mapped[str | None] = mapped_column(String(500), nullable=True) subtitle: Mapped[str | None] = mapped_column(String(500), nullable=True) author: Mapped[str | None] = mapped_column(String(500), nullable=True) narrator: Mapped[str | None] = mapped_column(String(500), nullable=True) publisher: Mapped[str | None] = mapped_column(String(255), nullable=True) publish_year: Mapped[int | None] = mapped_column(Integer, nullable=True) series: Mapped[str | None] = mapped_column(String(500), nullable=True) series_sequence: Mapped[str | None] = mapped_column(String(50), nullable=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) cover_path: Mapped[str | None] = mapped_column(Text, nullable=True) language: Mapped[str | None] = mapped_column(String(10), nullable=True) genres: Mapped[list] = mapped_column(JSON, default=list) tags: Mapped[list] = mapped_column(JSON, default=list) explicit: Mapped[bool] = mapped_column(Boolean, default=False) abridged: Mapped[bool] = mapped_column(Boolean, default=False) # Datei-Infos duration_seconds: Mapped[float] = mapped_column(Float, default=0.0) size_bytes: Mapped[int] = mapped_column(Integer, default=0) num_files: Mapped[int] = mapped_column(Integer, default=0) # Matching matched_source: Mapped[str] = mapped_column(String(50), default="none") matched_id: Mapped[str | None] = mapped_column(String(255), nullable=True) match_confidence: Mapped[float] = mapped_column(Float, default=0.0) match_locked: Mapped[bool] = mapped_column(Boolean, default=False) # Status is_missing: Mapped[bool] = mapped_column(Boolean, default=False) is_invalid: Mapped[bool] = mapped_column(Boolean, default=False) added_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) class BookFile(Base): __tablename__ = "book_files" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) library_item_id: Mapped[str] = mapped_column(String(36), ForeignKey("library_items.id", ondelete="CASCADE"), nullable=False) filename: Mapped[str] = mapped_column(String(500), nullable=False) path: Mapped[str] = mapped_column(Text, nullable=False) format: Mapped[str] = mapped_column(String(10), nullable=False) # mp3 / wav size_bytes: Mapped[int] = mapped_column(Integer, default=0) duration_seconds: Mapped[float] = mapped_column(Float, default=0.0) track_index: Mapped[int] = mapped_column(Integer, default=0) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) class Chapter(Base): __tablename__ = "chapters" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) library_item_id: Mapped[str] = mapped_column(String(36), ForeignKey("library_items.id", ondelete="CASCADE"), nullable=False) chapter_index: Mapped[int] = mapped_column(Integer, nullable=False) title: Mapped[str] = mapped_column(String(500), nullable=False) start_seconds: Mapped[float] = mapped_column(Float, default=0.0) end_seconds: Mapped[float] = mapped_column(Float, default=0.0)