from typing import Any, Dict, List, Optional, Union from pydantic import BaseModel, Field from datetime import datetime # ============================================================ # Parameter Models # ============================================================ class CaptureParameters(BaseModel): created_at: Optional[datetime] = None edited_at: Optional[datetime] = None class GPSParameters(BaseModel): latitude: Optional[float] = None longitude: Optional[float] = None altitude: Optional[float] = None direction: Optional[float] = None speed: Optional[float] = None speed_unit: Optional[str] = None raw: Dict[str, Any] = Field(default_factory=dict) class CameraParameters(BaseModel): make: Optional[str] = None model: Optional[str] = None software: Optional[str] = None lens_make: Optional[str] = None lens_model: Optional[str] = None focal_length_mm: Optional[float] = None focal_length_35mm: Optional[int] = None aperture: Optional[float] = None exposure_time: Optional[float] = None iso: Optional[int] = None flash: Optional[str] = None class DeviceParameters(BaseModel): make: Optional[str] = None model: Optional[str] = None software: Optional[str] = None class AiPromptParameters(BaseModel): prompt: Optional[str] = None negative_prompt: Optional[str] = None workflow: Optional[str] = None model: Optional[str] = None sampler: Optional[str] = None seed: Optional[int] = None cfg_scale: Optional[float] = None steps: Optional[int] = None raw_parameters: Optional[str] = None class AiGenerationParameters(BaseModel): label: Optional[str] = None provider: Optional[str] = None digital_source_type: Optional[str] = None class EditParameters(BaseModel): label: Optional[str] = None tool: Optional[str] = None provider: Optional[str] = None history: List[str] = Field(default_factory=list) software: Optional[str] = None class DepthMapParameters(BaseModel): depth_present: bool = False width: Optional[int] = None height: Optional[int] = None confidence: float = 0.0 class DownloadParameters(BaseModel): source: Optional[str] = None original_filename: Optional[str] = None class MakerNoteParameters(BaseModel): make: Optional[str] = None raw: Dict[str, Any] = Field(default_factory=dict) # ============================================================ # Attribute Model # ============================================================ class Attribute(BaseModel): type: str # 'capture', 'gps', 'camera', 'device', 'ai_prompt', 'ai_generation', 'ai_edit', 'depth_map', 'download' weight: float is_ai: bool = False ai_confidence: float = 0.0 comments: Optional[str] = None parameters: Union[ CaptureParameters, GPSParameters, CameraParameters, DeviceParameters, AiPromptParameters, AiGenerationParameters, EditParameters, DepthMapParameters, DownloadParameters, MakerNoteParameters, Dict[str, Any] ] # ============================================================ # Top Level Models # ============================================================ class AnalysisResult(BaseModel): is_ai: bool ai_confidence: float real_confidence: float = 0.0 edited_confidence: float = 0.0 class FileInfo(BaseModel): path: str mime_type: Optional[str] image_format: Optional[str] width: Optional[int] height: Optional[int] dpi: Optional[float] = None color_space: Optional[str] = None orientation: Optional[int] = None size_bytes: Optional[int] = None class Attachments(BaseModel): depth_map: Optional[str] = None class ProcessMetadata(BaseModel): started_at: Optional[datetime] = None completed_at: Optional[datetime] = None duration: Optional[float] = None class ImageMetadataResult(BaseModel): analysis: AnalysisResult file: FileInfo attachments: Attachments attributes: List[Attribute] = Field(default_factory=list) metadata: ProcessMetadata raw: Optional[Dict[str, Any]] = None # Optional raw data if needed