diff --git a/src/landppt/services/image/models.py b/src/landppt/services/image/models.py new file mode 100644 index 0000000..38be712 --- /dev/null +++ b/src/landppt/services/image/models.py @@ -0,0 +1,318 @@ +""" +图片服务数据模型 +""" + +from typing import List, Optional, Dict, Any, Union +from pydantic import BaseModel, Field +from enum import Enum +import time +from pathlib import Path + + +class ImageSourceType(str, Enum): + """图片来源类型""" + AI_GENERATED = "ai_generated" + WEB_SEARCH = "web_search" + LOCAL_STORAGE = "local_storage" + + +class ImageProvider(str, Enum): + """图片提供者""" + # AI生成 + DALLE = "dalle" + STABLE_DIFFUSION = "stable_diffusion" + SILICONFLOW = "siliconflow" + POLLINATIONS = "pollinations" + + # 网络搜索 + UNSPLASH = "unsplash" + PIXABAY = "pixabay" + SEARXNG = "searxng" + GOOGLE_IMAGES = "google_images" + + # 本地存储 + LOCAL_STORAGE = "local_storage" + USER_UPLOAD = "user_upload" + SYSTEM_DEFAULT = "system_default" + + +class ImageFormat(str, Enum): + """支持的图片格式""" + JPEG = "jpeg" + JPG = "jpg" + PNG = "png" + GIF = "gif" + WEBP = "webp" + BMP = "bmp" + TIFF = "tiff" + + +class ImagePosition(str, Enum): + """图片在幻灯片中的位置""" + BACKGROUND = "background" + CONTENT = "content" + ICON = "icon" + DECORATION = "decoration" + HEADER = "header" + FOOTER = "footer" + + +class ImageLicense(str, Enum): + """图片许可证类型""" + PUBLIC_DOMAIN = "public_domain" + CC0 = "cc0" + CC_BY = "cc_by" + CC_BY_SA = "cc_by_sa" + UNSPLASH_LICENSE = "unsplash_license" + PIXABAY_LICENSE = "pixabay_license" + CUSTOM = "custom" + UNKNOWN = "unknown" + + +class ImageMetadata(BaseModel): + """图片元数据""" + width: int + height: int + format: ImageFormat + file_size: int + color_mode: Optional[str] = None + has_transparency: bool = False + dominant_colors: List[str] = Field(default_factory=list) + average_color: Optional[str] = None + brightness: Optional[float] = None + contrast: Optional[float] = None + + +class ImageTag(BaseModel): + """图片标签""" + name: str + category: Optional[str] = None + confidence: float = 1.0 + source: Optional[str] = None # 标签来源:user, ai, api等 + + +class ImageInfo(BaseModel): + """图片信息""" + image_id: str + source_type: ImageSourceType + provider: ImageProvider + original_url: Optional[str] = None + local_path: str + filename: str + title: Optional[str] = None + description: Optional[str] = None + alt_text: Optional[str] = None + + # 元数据 + metadata: ImageMetadata + + # 标签和关键词 + tags: List[ImageTag] = Field(default_factory=list) + keywords: List[str] = Field(default_factory=list) + + # 版权信息 + license: ImageLicense = ImageLicense.UNKNOWN + license_info: Optional[str] = None + author: Optional[str] = None + author_url: Optional[str] = None + source_url: Optional[str] = None + + # 使用统计 + usage_count: int = 0 + last_used_at: Optional[float] = None + + # 时间戳 + created_at: float = Field(default_factory=time.time) + updated_at: float = Field(default_factory=time.time) + + def add_tag(self, name: str, category: Optional[str] = None, confidence: float = 1.0): + """添加标签""" + tag = ImageTag(name=name, category=category, confidence=confidence) + if tag not in self.tags: + self.tags.append(tag) + + def remove_tag(self, name: str): + """移除标签""" + self.tags = [tag for tag in self.tags if tag.name != name] + + def get_tags_by_category(self, category: str) -> List[ImageTag]: + """按分类获取标签""" + return [tag for tag in self.tags if tag.category == category] + + def update_usage(self): + """更新使用统计""" + self.usage_count += 1 + self.last_used_at = time.time() + self.updated_at = time.time() + + +class ImageSearchRequest(BaseModel): + """图片搜索请求""" + query: str + keywords: List[str] = Field(default_factory=list) + tags: List[str] = Field(default_factory=list) + + # 搜索参数 + page: int = 1 + per_page: int = 20 + orientation: Optional[str] = None # landscape, portrait, squarish + category: Optional[str] = None + color: Optional[str] = None + + # 尺寸要求 + min_width: Optional[int] = None + min_height: Optional[int] = None + max_width: Optional[int] = None + max_height: Optional[int] = None + + # 许可证要求 + license_types: List[ImageLicense] = Field(default_factory=list) + + # 提供者偏好 + preferred_providers: List[ImageProvider] = Field(default_factory=list) + excluded_providers: List[ImageProvider] = Field(default_factory=list) + + +class ImageGenerationRequest(BaseModel): + """AI图片生成请求""" + prompt: str + negative_prompt: Optional[str] = None + + # 生成参数 + width: int = 1024 + height: int = 1024 + quality: str = "standard" # standard, hd + style: Optional[str] = None # vivid, natural + + # 提供者特定参数 + provider: ImageProvider = ImageProvider.DALLE + model: Optional[str] = None + steps: Optional[int] = None + guidance_scale: Optional[float] = None + seed: Optional[int] = None + + # 批量生成 + num_images: int = 1 + + +class ImageUploadRequest(BaseModel): + """图片上传请求""" + filename: str + content_type: str + file_size: int + + # 可选信息 + title: Optional[str] = None + description: Optional[str] = None + tags: List[str] = Field(default_factory=list) + category: Optional[str] = None + + # 来源信息 + source_type: Optional[ImageSourceType] = None # 实际来源类型,如果为None则默认为LOCAL_STORAGE + original_url: Optional[str] = None # 原始URL(用于网络图片) + + # 处理选项 + auto_resize: bool = True + auto_optimize: bool = True + generate_thumbnails: bool = True + + +class ImageProcessingOptions(BaseModel): + """图片处理选项""" + # 尺寸调整 + resize_width: Optional[int] = None + resize_height: Optional[int] = None + maintain_aspect_ratio: bool = True + resize_method: str = "lanczos" # lanczos, bicubic, bilinear + + # 格式转换 + output_format: Optional[ImageFormat] = None + + # 质量优化 + quality: Optional[int] = None # 1-100 + optimize: bool = True + progressive: bool = False + + # 图片增强 + auto_enhance: bool = False + brightness: Optional[float] = None + contrast: Optional[float] = None + saturation: Optional[float] = None + sharpness: Optional[float] = None + + # 水印 + add_watermark: bool = False + watermark_text: Optional[str] = None + watermark_position: str = "bottom_right" + watermark_opacity: float = 0.5 + + +class ImageSearchResult(BaseModel): + """图片搜索结果""" + images: List[ImageInfo] + total_count: int + page: int + per_page: int + has_next: bool + has_prev: bool + + # 搜索统计 + search_time: float + provider_results: Dict[str, int] = Field(default_factory=dict) + provider: ImageProvider + error: Optional[str] = None + + +class ImageOperationResult(BaseModel): + """图片操作结果""" + success: bool + message: str + image_info: Optional[ImageInfo] = None + error_code: Optional[str] = None + processing_time: Optional[float] = None + + +class ProjectImageAssociation(BaseModel): + """项目图片关联""" + project_id: str + image_id: str + slide_index: int + position: ImagePosition + + # 位置和样式信息 + x: Optional[float] = None + y: Optional[float] = None + width: Optional[float] = None + height: Optional[float] = None + z_index: Optional[int] = None + + # 样式属性 + opacity: float = 1.0 + rotation: float = 0.0 + border_radius: Optional[float] = None + shadow: Optional[Dict[str, Any]] = None + + # 动画效果 + animation: Optional[Dict[str, Any]] = None + + created_at: float = Field(default_factory=time.time) + + +class ImageCacheInfo(BaseModel): + """图片缓存信息""" + cache_key: str + file_path: str + file_size: int + created_at: float + last_accessed: float + access_count: int + expires_at: Optional[float] = None + + def is_expired(self) -> bool: + """检查是否过期 - 图片永不过期""" + return False + + def update_access(self): + """更新访问信息""" + self.last_accessed = time.time() + self.access_count += 1