jinye_huang dcfd820ca4 feat(poster_v2): 智能海报生成引擎 v1.0
- 新增 PosterSmartEngine,AI 生成文案 + 海报渲染
- 5 种布局支持文本换行和自适应字体
- 修复按钮/标签颜色显示问题
- 优化渐变遮罩和内容区域计算
- Prompt 优化:标题格式为产品名+描述
2025-12-10 15:04:59 +08:00

209 lines
5.9 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
海报工厂 - 统一生成入口
"""
import random
import logging
from pathlib import Path
from typing import Optional, Union, Dict, Any, List
from PIL import Image
from .schemas.content import PosterContent
from .schemas.theme import Theme, THEMES, get_theme
from .layouts import LAYOUTS, LAYOUT_ALIASES, get_layout
logger = logging.getLogger(__name__)
class PosterFactory:
"""
海报工厂
提供统一的海报生成接口,支持:
- 指定布局或随机选择
- 指定主题或随机选择
- 从字典或 PosterContent 对象创建
Usage:
factory = PosterFactory()
# 方式1: 直接生成
poster = factory.generate(
layout="hero_bottom",
theme="ocean",
title="探索未知",
subtitle="周末去哪玩",
price="¥199"
)
# 方式2: 使用 PosterContent
content = PosterContent(title="美食探店", emoji="🍰")
poster = factory.generate_from_content(content, layout="overlay_bottom")
# 方式3: 随机布局和主题
poster = factory.generate_random(title="随机海报")
"""
def __init__(self, size: tuple = (1080, 1440)):
self.size = size
self._layout_cache = {}
def generate(
self,
layout: str = "hero_bottom",
theme: str = "ocean",
**content_kwargs
) -> Image.Image:
"""
生成海报
Args:
layout: 布局名称 (hero_bottom, overlay_center, overlay_bottom, split_vertical, card_float)
theme: 主题名称 (ocean, sunset, peach, mint, latte)
**content_kwargs: 内容参数 (title, subtitle, price, tags, etc.)
Returns:
生成的海报图像
"""
content = PosterContent.from_dict(content_kwargs)
theme_obj = get_theme(theme)
layout_cls = get_layout(layout)
layout_instance = layout_cls(self.size)
return layout_instance.generate(content, theme_obj)
def generate_from_content(
self,
content: PosterContent,
layout: str = "hero_bottom",
theme: str = "ocean"
) -> Image.Image:
"""
从 PosterContent 对象生成海报
Args:
content: PosterContent 对象
layout: 布局名称
theme: 主题名称
Returns:
生成的海报图像
"""
theme_obj = get_theme(theme)
layout_cls = get_layout(layout)
layout_instance = layout_cls(self.size)
return layout_instance.generate(content, theme_obj)
def generate_random(
self,
layouts: List[str] = None,
themes: List[str] = None,
**content_kwargs
) -> Image.Image:
"""
随机布局和主题生成海报
Args:
layouts: 可选的布局列表,默认全部
themes: 可选的主题列表,默认全部
**content_kwargs: 内容参数
Returns:
生成的海报图像
"""
layout = random.choice(layouts or list(LAYOUTS.keys()))
theme = random.choice(themes or list(THEMES.keys()))
logger.info(f"随机生成: layout={layout}, theme={theme}")
return self.generate(layout=layout, theme=theme, **content_kwargs)
def generate_batch(
self,
content_list: List[Dict[str, Any]],
layout: str = None,
theme: str = None
) -> List[Image.Image]:
"""
批量生成海报
Args:
content_list: 内容列表
layout: 固定布局 (None 表示随机)
theme: 固定主题 (None 表示随机)
Returns:
海报图像列表
"""
results = []
for content_dict in content_list:
l = layout or random.choice(list(LAYOUTS.keys()))
t = theme or random.choice(list(THEMES.keys()))
poster = self.generate(layout=l, theme=t, **content_dict)
results.append(poster)
return results
def suggest_layout(self, content: Dict[str, Any]) -> str:
"""
根据内容特征推荐布局
Args:
content: 内容字典
Returns:
推荐的布局名称
"""
# 有 emoji → 美食探店
if content.get("emoji"):
return "overlay_bottom"
# 有 features 列表且较多 → 酒店
features = content.get("features", [])
if len(features) >= 3:
return "card_float"
# 内容较少 → 居中叠加
if not content.get("price") and not content.get("details"):
return "overlay_center"
# 默认
return "hero_bottom"
def suggest_theme(self, category: str = None) -> str:
"""
根据分类推荐主题
Args:
category: 内容分类 (美食, 酒店, 景点, etc.)
Returns:
推荐的主题名称
"""
category_themes = {
"美食": "peach",
"甜品": "peach",
"咖啡": "latte",
"酒店": "ocean",
"民宿": "mint",
"景点": "sunset",
"攻略": "ocean",
}
return category_themes.get(category, "ocean")
@staticmethod
def list_layouts() -> List[str]:
"""列出所有可用布局"""
return list(LAYOUTS.keys())
@staticmethod
def list_themes() -> List[str]:
"""列出所有可用主题"""
return list(THEMES.keys())
@staticmethod
def list_layout_aliases() -> Dict[str, str]:
"""列出布局别名"""
return LAYOUT_ALIASES.copy()