from datetime import datetime from enum import Enum from typing import Any, Literal, Optional from pydantic import BaseModel, Field, constr class JobStatus(str, Enum): CREATED = "created" INGESTING = "ingesting" AI_PROCESSING = "ai_processing" PENDING_QC = "pending_qc" APPROVED_ENGLISH = "approved_english" # For English source videos APPROVED_SOURCE = "approved_source" # For non-English source videos REJECTED = "rejected" QC_FEEDBACK = "qc_feedback" TRANSLATING = "translating" TTS_GENERATING = "tts_generating" PENDING_FINAL_REVIEW = "pending_final_review" COMPLETED = "completed" @classmethod def is_approved(cls, status: str) -> bool: """Check if status indicates source approval (any language)""" return status in [cls.APPROVED_ENGLISH.value, cls.APPROVED_SOURCE.value] class Source(BaseModel): filename: str original_filename: Optional[str] = None gcs_uri: str duration_s: Optional[float] = None language: constr(min_length=2, max_length=10) = "en" # Final source language (from detection or explicit) language_hint: Optional[str] = None # User-provided hint for non-English videos detected_language: Optional[str] = None # AI-detected language from Gemini class TTSPreferences(BaseModel): """TTS voice preferences for audio description generation""" provider: Literal["gemini", "google", "elevenlabs"] = "gemini" default_voice: str = "Kore" # Default Gemini voice voices_per_language: dict[str, str] = {} # {"en": "Kore", "es": "Aoede"} # TTS quality and style settings model: Literal["flash", "pro"] = "flash" # flash = fast/cheap, pro = higher quality speed: float = Field(default=1.0, ge=0.5, le=2.0) # Speech rate multiplier style_preset: Literal[ "neutral", "calm", "energetic", "professional", "warm", "documentary", "custom" ] = "neutral" custom_style_prompt: Optional[str] = None # Used when style_preset is "custom" class RequestedOutputs(BaseModel): captions_vtt: bool = True audio_description_vtt: bool = True audio_description_mp3: bool = True languages: list[str] = [] transcreation: list[str] = [] tts_preferences: Optional[TTSPreferences] = None class LangOutput(BaseModel): captions_vtt_gcs: Optional[str] = None ad_vtt_gcs: Optional[str] = None ad_mp3_gcs: Optional[str] = None origin: Optional[Literal["translate", "transcreate"]] = None qa_notes: Optional[str] = None class ReviewHistoryItem(BaseModel): at: datetime status: str by: Optional[str] = None notes: Optional[str] = None class Review(BaseModel): notes: Optional[str] = "" reviewer_id: Optional[str] = None history: list[ReviewHistoryItem] = [] class AISection(BaseModel): ingestion_json: Optional[dict[str, Any]] = None confidence: Optional[float] = None class Job(BaseModel): id: Optional[str] = Field(None, alias="_id") client_id: str title: str source: Source requested_outputs: RequestedOutputs status: JobStatus = JobStatus.CREATED review: Review = Review() outputs: Optional[dict[str, LangOutput]] = None ai: Optional[AISection] = None error: Optional[dict[str, Any]] = None created_at: Optional[datetime] = None updated_at: Optional[datetime] = None class Config: populate_by_name = True use_enum_values = True class JobCreate(BaseModel): title: str source_is_english: bool = True # True = English source, False = other language (auto-detect) language_hint: Optional[str] = None # Optional hint when source_is_english=False requested_outputs: RequestedOutputs class JobUpdate(BaseModel): title: Optional[str] = None status: Optional[JobStatus] = None review: Optional[Review] = None outputs: Optional[dict[str, LangOutput]] = None ai: Optional[AISection] = None error: Optional[dict[str, Any]] = None