- 新增 PosterSmartEngine,AI 生成文案 + 海报渲染 - 5 种布局支持文本换行和自适应字体 - 修复按钮/标签颜色显示问题 - 优化渐变遮罩和内容区域计算 - Prompt 优化:标题格式为产品名+描述
209 lines
5.9 KiB
Python
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()
|