- 新增 PosterSmartEngine,AI 生成文案 + 海报渲染 - 5 种布局支持文本换行和自适应字体 - 修复按钮/标签颜色显示问题 - 优化渐变遮罩和内容区域计算 - Prompt 优化:标题格式为产品名+描述
184 lines
5.4 KiB
Python
184 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
海报生成服务 V2
|
||
提供统一的海报生成接口,供 AIGC 引擎调用
|
||
"""
|
||
|
||
import io
|
||
import base64
|
||
import logging
|
||
from typing import Dict, Any, Optional, List
|
||
from PIL import Image
|
||
|
||
from .factory import PosterFactory
|
||
from .schemas.content import PosterContent
|
||
from .schemas.theme import get_theme
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class PosterServiceV2:
|
||
"""
|
||
海报生成服务 V2
|
||
|
||
功能:
|
||
1. 接收内容参数,生成海报
|
||
2. 支持 URL/路径/Base64 图片输入
|
||
3. 输出 Base64 编码的图片
|
||
"""
|
||
|
||
def __init__(self):
|
||
self.factory = PosterFactory()
|
||
|
||
def generate(
|
||
self,
|
||
layout: str = "hero_bottom",
|
||
theme: str = "ocean",
|
||
content: Dict[str, Any] = None,
|
||
image_base64: str = None,
|
||
image_path: str = None,
|
||
output_format: str = "PNG"
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
生成海报
|
||
|
||
Args:
|
||
layout: 布局名称 (hero_bottom, overlay_center, etc.)
|
||
theme: 主题名称 (ocean, sunset, etc.)
|
||
content: 内容字典
|
||
image_base64: 背景图片 Base64 (可选)
|
||
image_path: 背景图片路径 (可选)
|
||
output_format: 输出格式 (PNG/JPEG)
|
||
|
||
Returns:
|
||
{
|
||
"success": True,
|
||
"image_base64": "...",
|
||
"layout": "hero_bottom",
|
||
"theme": "ocean"
|
||
}
|
||
"""
|
||
try:
|
||
content = content or {}
|
||
|
||
# 加载背景图片
|
||
bg_image = None
|
||
if image_base64:
|
||
bg_image = self._load_image_from_base64(image_base64)
|
||
elif image_path:
|
||
bg_image = self._load_image_from_path(image_path)
|
||
|
||
if bg_image:
|
||
content['image'] = bg_image
|
||
|
||
# 创建 PosterContent
|
||
poster_content = PosterContent.from_dict(content)
|
||
|
||
# 生成海报
|
||
poster_image = self.factory.generate_from_content(
|
||
poster_content,
|
||
layout=layout,
|
||
theme=theme
|
||
)
|
||
|
||
# 转换为 Base64
|
||
image_base64_output = self._image_to_base64(poster_image, output_format)
|
||
|
||
return {
|
||
"success": True,
|
||
"image_base64": image_base64_output,
|
||
"layout": layout,
|
||
"theme": theme,
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"海报生成失败: {e}", exc_info=True)
|
||
return {
|
||
"success": False,
|
||
"error": str(e)
|
||
}
|
||
|
||
def generate_batch(
|
||
self,
|
||
items: List[Dict[str, Any]],
|
||
default_layout: str = None,
|
||
default_theme: str = None
|
||
) -> List[Dict[str, Any]]:
|
||
"""
|
||
批量生成海报
|
||
|
||
Args:
|
||
items: 内容列表,每项可包含 layout, theme, content, image_base64
|
||
default_layout: 默认布局
|
||
default_theme: 默认主题
|
||
|
||
Returns:
|
||
结果列表
|
||
"""
|
||
results = []
|
||
for item in items:
|
||
result = self.generate(
|
||
layout=item.get('layout', default_layout or 'hero_bottom'),
|
||
theme=item.get('theme', default_theme or 'ocean'),
|
||
content=item.get('content', {}),
|
||
image_base64=item.get('image_base64'),
|
||
image_path=item.get('image_path'),
|
||
)
|
||
results.append(result)
|
||
return results
|
||
|
||
def suggest_layout(self, content: Dict[str, Any]) -> str:
|
||
"""根据内容推荐布局"""
|
||
return self.factory.suggest_layout(content)
|
||
|
||
def suggest_theme(self, category: str = None) -> str:
|
||
"""根据分类推荐主题"""
|
||
return self.factory.suggest_theme(category)
|
||
|
||
def list_layouts(self) -> List[str]:
|
||
"""列出所有布局"""
|
||
return self.factory.list_layouts()
|
||
|
||
def list_themes(self) -> List[str]:
|
||
"""列出所有主题"""
|
||
return self.factory.list_themes()
|
||
|
||
def _load_image_from_base64(self, image_base64: str) -> Image.Image:
|
||
"""从 Base64 加载图片"""
|
||
# 移除 data URL 前缀
|
||
if ',' in image_base64:
|
||
image_base64 = image_base64.split(',')[1]
|
||
|
||
image_bytes = base64.b64decode(image_base64)
|
||
return Image.open(io.BytesIO(image_bytes))
|
||
|
||
def _load_image_from_path(self, image_path: str) -> Image.Image:
|
||
"""从路径加载图片"""
|
||
return Image.open(image_path)
|
||
|
||
def _image_to_base64(self, image: Image.Image, format: str = "PNG") -> str:
|
||
"""图片转 Base64"""
|
||
buffer = io.BytesIO()
|
||
|
||
# 直接转换为 RGB,保留颜色(不做透明合成)
|
||
if image.mode == "RGBA":
|
||
image = image.convert("RGB")
|
||
elif image.mode != "RGB":
|
||
image = image.convert("RGB")
|
||
|
||
image.save(buffer, format=format.upper())
|
||
return base64.b64encode(buffer.getvalue()).decode('utf-8')
|
||
|
||
|
||
# 单例服务
|
||
_service_instance = None
|
||
|
||
|
||
def get_poster_service_v2() -> PosterServiceV2:
|
||
"""获取海报服务实例"""
|
||
global _service_instance
|
||
if _service_instance is None:
|
||
_service_instance = PosterServiceV2()
|
||
return _service_instance
|