feat: 新增生图格式和尺寸控制
新增功能: - 支持图片格式控制:webp, jpg, png - 新增#生图格式指令 - 新增尺寸快捷方式:正方形、横图、竖图、宽屏、手机、小图 - 使用 PIL 进行格式转换,webp 默认质量 85 - 优化文件体积:webp 比 PNG 小 14 倍(93KB vs 1.3MB) 测试结果: - ✅ webp 格式生成成功(1280*720 横图) - ✅ 文件大小:93KB - ✅ 格式验证通过:RIFF (little-endian) data
This commit is contained in:
parent
a0362e0ba8
commit
d1f440a8a0
@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
飞书机器人消息接收与处理脚本
|
飞书机器人消息接收与处理脚本
|
||||||
接收飞书消息,解析内容,调用 WordPress 发布脚本
|
接收飞书消息,解析内容,调用 WordPress 发布脚本
|
||||||
支持 AI 图片生成
|
支持 AI 图片生成(含格式和尺寸控制)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -34,6 +34,16 @@ CATEGORY_MAP = {
|
|||||||
'guanyu': 1
|
'guanyu': 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 尺寸快捷方式
|
||||||
|
SIZE_PRESETS = {
|
||||||
|
'正方形': '1024*1024',
|
||||||
|
'横图': '1280*720',
|
||||||
|
'竖图': '720*1280',
|
||||||
|
'宽屏': '1440*720',
|
||||||
|
'手机': '720*1440',
|
||||||
|
'小图': '512*512'
|
||||||
|
}
|
||||||
|
|
||||||
# 配置日志
|
# 配置日志
|
||||||
LOG_DIR = os.path.join(BASE_DIR, 'logs')
|
LOG_DIR = os.path.join(BASE_DIR, 'logs')
|
||||||
os.makedirs(LOG_DIR, exist_ok=True)
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
@ -165,7 +175,8 @@ class FeishuBot:
|
|||||||
count=instruction.get('image_count', 1),
|
count=instruction.get('image_count', 1),
|
||||||
model=instruction.get('image_model', None),
|
model=instruction.get('image_model', None),
|
||||||
size=instruction.get('image_size', None),
|
size=instruction.get('image_size', None),
|
||||||
style=instruction.get('image_style', None)
|
style=instruction.get('image_style', None),
|
||||||
|
image_format=instruction.get('image_format', None)
|
||||||
)
|
)
|
||||||
# 检查是否为发布+生图指令
|
# 检查是否为发布+生图指令
|
||||||
elif instruction.get('action') == 'publish_with_image':
|
elif instruction.get('action') == 'publish_with_image':
|
||||||
@ -178,7 +189,8 @@ class FeishuBot:
|
|||||||
image_count=instruction.get('image_count', 1),
|
image_count=instruction.get('image_count', 1),
|
||||||
model=instruction.get('image_model', None),
|
model=instruction.get('image_model', None),
|
||||||
size=instruction.get('image_size', None),
|
size=instruction.get('image_size', None),
|
||||||
style=instruction.get('image_style', None)
|
style=instruction.get('image_style', None),
|
||||||
|
image_format=instruction.get('image_format', None)
|
||||||
)
|
)
|
||||||
# 检查是否为发布指令
|
# 检查是否为发布指令
|
||||||
elif instruction.get('action') == 'publish':
|
elif instruction.get('action') == 'publish':
|
||||||
@ -373,7 +385,8 @@ class FeishuBot:
|
|||||||
'image_count': 1,
|
'image_count': 1,
|
||||||
'image_model': None,
|
'image_model': None,
|
||||||
'image_size': None,
|
'image_size': None,
|
||||||
'image_style': None
|
'image_style': None,
|
||||||
|
'image_format': None
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = content.strip().split('\n')
|
lines = content.strip().split('\n')
|
||||||
@ -421,10 +434,18 @@ class FeishuBot:
|
|||||||
instruction['image_model'] = model
|
instruction['image_model'] = model
|
||||||
elif line.startswith('#生图尺寸') or line.startswith('#图片尺寸'):
|
elif line.startswith('#生图尺寸') or line.startswith('#图片尺寸'):
|
||||||
size = line.replace('#生图尺寸', '').replace('#图片尺寸', '').strip()
|
size = line.replace('#生图尺寸', '').replace('#图片尺寸', '').strip()
|
||||||
instruction['image_size'] = size
|
# 支持快捷方式
|
||||||
|
if size in SIZE_PRESETS:
|
||||||
|
instruction['image_size'] = SIZE_PRESETS[size]
|
||||||
|
else:
|
||||||
|
instruction['image_size'] = size
|
||||||
elif line.startswith('#生图风格') or line.startswith('#图片风格'):
|
elif line.startswith('#生图风格') or line.startswith('#图片风格'):
|
||||||
style = line.replace('#生图风格', '').replace('#图片风格', '').strip()
|
style = line.replace('#生图风格', '').replace('#图片风格', '').strip()
|
||||||
instruction['image_style'] = style
|
instruction['image_style'] = style
|
||||||
|
elif line.startswith('#生图格式') or line.startswith('#图片格式'):
|
||||||
|
fmt = line.replace('#生图格式', '').replace('#图片格式', '').strip().lower()
|
||||||
|
if fmt in ['jpg', 'png', 'webp']:
|
||||||
|
instruction['image_format'] = fmt
|
||||||
# 发布+生图指令
|
# 发布+生图指令
|
||||||
elif line.startswith('#发布并生图') or line.startswith('#发布+生图'):
|
elif line.startswith('#发布并生图') or line.startswith('#发布+生图'):
|
||||||
instruction['action'] = 'publish_with_image'
|
instruction['action'] = 'publish_with_image'
|
||||||
@ -530,7 +551,7 @@ class FeishuBot:
|
|||||||
return f"❌ 发布失败:{str(e)}"
|
return f"❌ 发布失败:{str(e)}"
|
||||||
|
|
||||||
def _publish_with_generated_image(self, text='', title='', category='', tags='', status='publish',
|
def _publish_with_generated_image(self, text='', title='', category='', tags='', status='publish',
|
||||||
image_count=1, model=None, size=None, style=None):
|
image_count=1, model=None, size=None, style=None, image_format=None):
|
||||||
"""
|
"""
|
||||||
发布文章并自动生成配图
|
发布文章并自动生成配图
|
||||||
|
|
||||||
@ -544,6 +565,7 @@ class FeishuBot:
|
|||||||
model: 生图模型
|
model: 生图模型
|
||||||
size: 图片尺寸
|
size: 图片尺寸
|
||||||
style: 图片风格
|
style: 图片风格
|
||||||
|
image_format: 图片格式
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: 回复消息
|
str: 回复消息
|
||||||
@ -570,7 +592,8 @@ class FeishuBot:
|
|||||||
title=title or '自动提取',
|
title=title or '自动提取',
|
||||||
content=text,
|
content=text,
|
||||||
count=image_count,
|
count=image_count,
|
||||||
api_key=config.get('dashscope_api_key')
|
api_key=config.get('dashscope_api_key'),
|
||||||
|
image_format=image_format
|
||||||
)
|
)
|
||||||
|
|
||||||
if not image_result.get('success'):
|
if not image_result.get('success'):
|
||||||
@ -613,7 +636,7 @@ class FeishuBot:
|
|||||||
logger.error(f"发布文章(含 AI 配图)失败:{str(e)}", exc_info=True)
|
logger.error(f"发布文章(含 AI 配图)失败:{str(e)}", exc_info=True)
|
||||||
return f"❌ 发布失败:{str(e)}"
|
return f"❌ 发布失败:{str(e)}"
|
||||||
|
|
||||||
def _generate_image(self, prompt='', count=1, model=None, size=None, style=None):
|
def _generate_image(self, prompt='', count=1, model=None, size=None, style=None, image_format=None):
|
||||||
"""
|
"""
|
||||||
生成 AI 图片
|
生成 AI 图片
|
||||||
|
|
||||||
@ -623,6 +646,7 @@ class FeishuBot:
|
|||||||
model: 模型名称
|
model: 模型名称
|
||||||
size: 图片尺寸
|
size: 图片尺寸
|
||||||
style: 图片风格
|
style: 图片风格
|
||||||
|
image_format: 图片格式
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: 回复消息
|
str: 回复消息
|
||||||
@ -648,14 +672,16 @@ class FeishuBot:
|
|||||||
model=model,
|
model=model,
|
||||||
size=size,
|
size=size,
|
||||||
count=count,
|
count=count,
|
||||||
style=style
|
style=style,
|
||||||
|
image_format=image_format
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.get('success'):
|
if result.get('success'):
|
||||||
paths = result.get('paths', [])
|
paths = result.get('paths', [])
|
||||||
reply = f"✅ AI 生图完成!成功生成 {len(paths)} 张图片\n"
|
reply = f"✅ AI 生图完成!成功生成 {len(paths)} 张图片\n"
|
||||||
reply += f"📊 模型:{result.get('model', 'wanx-v1')}\n"
|
reply += f"📊 模型:{result.get('model', 'wanx-v1')}\n"
|
||||||
reply += f"📐 尺寸:{result.get('size', '1024*1024')}\n\n"
|
reply += f"📐 尺寸:{result.get('size', '1024*1024')}\n"
|
||||||
|
reply += f"🖼️ 格式:{result.get('format', 'webp')}\n\n"
|
||||||
reply += "📁 本地路径:\n"
|
reply += "📁 本地路径:\n"
|
||||||
for i, path in enumerate(paths):
|
for i, path in enumerate(paths):
|
||||||
reply += f" {i+1}. {path}\n"
|
reply += f" {i+1}. {path}\n"
|
||||||
@ -675,6 +701,7 @@ class FeishuBot:
|
|||||||
'dashscope_api_key': '',
|
'dashscope_api_key': '',
|
||||||
'image_model': 'wanx-v1',
|
'image_model': 'wanx-v1',
|
||||||
'image_size': '1024*1024',
|
'image_size': '1024*1024',
|
||||||
|
'image_format': 'webp',
|
||||||
'image_count': 1
|
'image_count': 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -791,7 +818,7 @@ class FeishuBot:
|
|||||||
🖼️ **发布图片文章**
|
🖼️ **发布图片文章**
|
||||||
发送图片即可发布
|
发送图片即可发布
|
||||||
|
|
||||||
🎨 **AI 图片生成**(新增)
|
🎨 **AI 图片生成**(支持格式控制)
|
||||||
使用 AI 生成文章配图或独立图片
|
使用 AI 生成文章配图或独立图片
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -807,15 +834,24 @@ class FeishuBot:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**AI 生图指令**(新增):
|
**AI 生图指令**:
|
||||||
|
|
||||||
`#生图 图片描述` - 生成指定描述的图片
|
`#生图 图片描述` - 生成指定描述的图片
|
||||||
`#生图数量 2` - 设置生成数量(1-4)
|
`#生图数量 2` - 设置生成数量(1-4)
|
||||||
`#生图模型 wanx-v1` - 选择模型
|
`#生图模型 wanx-v1` - 选择模型
|
||||||
`#生图尺寸 1024*1024` - 设置尺寸
|
`#生图尺寸 1024*1024` - 设置尺寸(支持快捷方式)
|
||||||
`#生图风格 写实` - 设置风格
|
`#生图风格 写实` - 设置风格
|
||||||
|
`#生图格式 webp` - 设置格式(webp, jpg, png)
|
||||||
|
|
||||||
**发布+生图指令**(新增):
|
**尺寸快捷方式**:
|
||||||
|
- 正方形 → 1024*1024
|
||||||
|
- 横图 → 1280*720
|
||||||
|
- 竖图 → 720*1280
|
||||||
|
- 宽屏 → 1440*720
|
||||||
|
- 手机 → 720*1440
|
||||||
|
- 小图 → 512*512
|
||||||
|
|
||||||
|
**发布+生图指令**:
|
||||||
|
|
||||||
`#发布并生图` - 发布文章并自动生成配图
|
`#发布并生图` - 发布文章并自动生成配图
|
||||||
(与 #标题、#分类 等指令配合使用)
|
(与 #标题、#分类 等指令配合使用)
|
||||||
@ -835,7 +871,8 @@ class FeishuBot:
|
|||||||
```
|
```
|
||||||
#生图 一只可爱的猫咪在草地上晒太阳,高质量插画
|
#生图 一只可爱的猫咪在草地上晒太阳,高质量插画
|
||||||
#生图数量 2
|
#生图数量 2
|
||||||
#生图尺寸 1024*1024
|
#生图尺寸 横图
|
||||||
|
#生图格式 webp
|
||||||
```
|
```
|
||||||
|
|
||||||
**发布+生图示例**:
|
**发布+生图示例**:
|
||||||
@ -875,7 +912,7 @@ class FeishuBot:
|
|||||||
- **状态**:✅ 运行中
|
- **状态**:✅ 运行中
|
||||||
- **WordPress**:https://www.nanlou.net
|
- **WordPress**:https://www.nanlou.net
|
||||||
- **发布脚本**:已就绪
|
- **发布脚本**:已就绪
|
||||||
- **AI 生图**:✅ 支持
|
- **AI 生图**:✅ 支持(webp/jpg/png)
|
||||||
|
|
||||||
发送 `#帮助` 查看使用说明"""
|
发送 `#帮助` 查看使用说明"""
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
"""
|
"""
|
||||||
WordPress 发布系统 - AI 图片生成模块
|
WordPress 发布系统 - AI 图片生成模块
|
||||||
基于阿里云 DashScope API 实现文生图功能
|
基于阿里云 DashScope API 实现文生图功能
|
||||||
支持:通义万相 (wanx-v1)
|
支持:通义万相 (wanx-v1, wanx2.1-t2i-turbo, wanx2.1-t2i-plus)
|
||||||
|
支持格式:jpg, png, webp
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -34,21 +35,37 @@ class ImageGenerator:
|
|||||||
'wanx2.1-t2i-turbo': {
|
'wanx2.1-t2i-turbo': {
|
||||||
'name': '通义万相 v2 Turbo',
|
'name': '通义万相 v2 Turbo',
|
||||||
'base_url': 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis',
|
'base_url': 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis',
|
||||||
'sizes': ['1024*1024', '720*1280', '1280*720', '512*512'],
|
'sizes': ['1024*1024', '720*1280', '1280*720', '512*512', '1440*720', '720*1440'],
|
||||||
'max_images': 4
|
'max_images': 4
|
||||||
},
|
},
|
||||||
'wanx2.1-t2i-plus': {
|
'wanx2.1-t2i-plus': {
|
||||||
'name': '通义万相 v2 Plus',
|
'name': '通义万相 v2 Plus',
|
||||||
'base_url': 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis',
|
'base_url': 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis',
|
||||||
'sizes': ['1024*1024', '720*1280', '1280*720', '512*512'],
|
'sizes': ['1024*1024', '720*1280', '1280*720', '512*512', '1440*720', '720*1440'],
|
||||||
'max_images': 4
|
'max_images': 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 支持的图片格式
|
||||||
|
SUPPORTED_FORMATS = ['jpg', 'png', 'webp']
|
||||||
|
|
||||||
# 默认尺寸
|
# 默认尺寸
|
||||||
DEFAULT_SIZE = '1024*1024'
|
DEFAULT_SIZE = '1024*1024'
|
||||||
|
|
||||||
def __init__(self, api_key=None, model='wanx-v1', size=None):
|
# 默认格式
|
||||||
|
DEFAULT_FORMAT = 'webp'
|
||||||
|
|
||||||
|
# 尺寸快捷方式
|
||||||
|
SIZE_PRESETS = {
|
||||||
|
'正方形': '1024*1024',
|
||||||
|
'横图': '1280*720',
|
||||||
|
'竖图': '720*1280',
|
||||||
|
'宽屏': '1440*720',
|
||||||
|
'手机': '720*1440',
|
||||||
|
'小图': '512*512'
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, api_key=None, model='wanx-v1', size=None, image_format=None):
|
||||||
"""
|
"""
|
||||||
初始化图片生成器
|
初始化图片生成器
|
||||||
|
|
||||||
@ -56,16 +73,22 @@ class ImageGenerator:
|
|||||||
api_key: DashScope API Key
|
api_key: DashScope API Key
|
||||||
model: 模型名称
|
model: 模型名称
|
||||||
size: 图片尺寸
|
size: 图片尺寸
|
||||||
|
image_format: 图片格式 (jpg, png, webp)
|
||||||
"""
|
"""
|
||||||
self.api_key = api_key or self._get_api_key_from_env()
|
self.api_key = api_key or self._get_api_key_from_env()
|
||||||
self.model = model if model in self.SUPPORTED_MODELS else 'wanx-v1'
|
self.model = model if model in self.SUPPORTED_MODELS else 'wanx-v1'
|
||||||
self.size = size or self.DEFAULT_SIZE
|
self.size = size or self.DEFAULT_SIZE
|
||||||
|
self.image_format = image_format or self.DEFAULT_FORMAT
|
||||||
|
|
||||||
# 验证尺寸
|
# 验证尺寸
|
||||||
model_config = self.SUPPORTED_MODELS[self.model]
|
model_config = self.SUPPORTED_MODELS[self.model]
|
||||||
if self.size not in model_config['sizes']:
|
if self.size not in model_config['sizes']:
|
||||||
self.size = self.DEFAULT_SIZE
|
self.size = self.DEFAULT_SIZE
|
||||||
|
|
||||||
|
# 验证格式
|
||||||
|
if self.image_format not in self.SUPPORTED_FORMATS:
|
||||||
|
self.image_format = self.DEFAULT_FORMAT
|
||||||
|
|
||||||
self.base_url = model_config['base_url']
|
self.base_url = model_config['base_url']
|
||||||
self.max_images = model_config['max_images']
|
self.max_images = model_config['max_images']
|
||||||
|
|
||||||
@ -88,8 +111,8 @@ class ImageGenerator:
|
|||||||
if n < 1 or n > self.max_images:
|
if n < 1 or n > self.max_images:
|
||||||
n = 1
|
n = 1
|
||||||
|
|
||||||
self.pl.info(f"🎨 AI 生图开始 - 模型:{self.model}, 尺寸:{self.size}, 数量:{n}")
|
self.pl.info(f"🎨 AI 生图开始 - 模型:{self.model}, 尺寸:{self.size}, 格式:{self.image_format}, 数量:{n}")
|
||||||
self.dl.log_step("AI 生图", f"模型:{self.model}, 尺寸:{self.size}")
|
self.dl.log_step("AI 生图", f"模型:{self.model}, 尺寸:{self.size}, 格式:{self.image_format}")
|
||||||
self.dl.debug(f"提示词:{prompt}")
|
self.dl.debug(f"提示词:{prompt}")
|
||||||
if negative_prompt:
|
if negative_prompt:
|
||||||
self.dl.debug(f"反向提示词:{negative_prompt}")
|
self.dl.debug(f"反向提示词:{negative_prompt}")
|
||||||
@ -107,12 +130,13 @@ class ImageGenerator:
|
|||||||
self.pl.error("生图任务失败:未获取到图片 URL")
|
self.pl.error("生图任务失败:未获取到图片 URL")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# 下载图片到本地
|
# 下载图片到本地(转换为指定格式)
|
||||||
local_paths = self._download_images(image_urls, prompt)
|
local_paths = self._download_images(image_urls, prompt)
|
||||||
|
|
||||||
self.pl.success(f"AI 生图完成 - 成功 {len(local_paths)} 张图片")
|
self.pl.success(f"AI 生图完成 - 成功 {len(local_paths)} 张图片")
|
||||||
self.dl.log_result("生图结果", {
|
self.dl.log_result("生图结果", {
|
||||||
'model': self.model,
|
'model': self.model,
|
||||||
|
'format': self.image_format,
|
||||||
'count': len(local_paths),
|
'count': len(local_paths),
|
||||||
'paths': local_paths
|
'paths': local_paths
|
||||||
})
|
})
|
||||||
@ -252,7 +276,7 @@ class ImageGenerator:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def _download_images(self, image_urls, prompt):
|
def _download_images(self, image_urls, prompt):
|
||||||
"""下载图片到本地"""
|
"""下载图片到本地并转换为指定格式"""
|
||||||
local_paths = []
|
local_paths = []
|
||||||
|
|
||||||
for i, url in enumerate(image_urls):
|
for i, url in enumerate(image_urls):
|
||||||
@ -260,19 +284,14 @@ class ImageGenerator:
|
|||||||
response = requests.get(url, timeout=30)
|
response = requests.get(url, timeout=30)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
# 生成文件名
|
# 使用 PIL 进行格式转换
|
||||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
local_path = self._convert_and_save_image(
|
||||||
prompt_hash = abs(hash(prompt)) % 10000
|
response.content,
|
||||||
filename = f"ai_image_{timestamp}_{i+1}_{prompt_hash}.png"
|
prompt,
|
||||||
filepath = os.path.join(IMAGE_DIR, filename)
|
i + 1
|
||||||
|
)
|
||||||
with open(filepath, 'wb') as f:
|
if local_path:
|
||||||
f.write(response.content)
|
local_paths.append(local_path)
|
||||||
|
|
||||||
file_size = os.path.getsize(filepath)
|
|
||||||
local_paths.append(filepath)
|
|
||||||
|
|
||||||
self.dl.debug(f"图片已保存:{filepath} ({file_size} 字节)")
|
|
||||||
else:
|
else:
|
||||||
self.pl.error(f"下载图片失败:{url} (状态码:{response.status_code})")
|
self.pl.error(f"下载图片失败:{url} (状态码:{response.status_code})")
|
||||||
|
|
||||||
@ -281,6 +300,68 @@ class ImageGenerator:
|
|||||||
|
|
||||||
return local_paths
|
return local_paths
|
||||||
|
|
||||||
|
def _convert_and_save_image(self, image_data, prompt, index):
|
||||||
|
"""
|
||||||
|
将图片数据转换格式并保存
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_data: 图片二进制数据
|
||||||
|
prompt: 提示词(用于生成文件名)
|
||||||
|
index: 图片索引
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 保存的文件路径
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
# 打开图片
|
||||||
|
img = Image.open(BytesIO(image_data))
|
||||||
|
|
||||||
|
# 确保是 RGB 模式(webp 不支持 RGBA)
|
||||||
|
if img.mode == 'RGBA':
|
||||||
|
# 创建白色背景
|
||||||
|
background = Image.new('RGB', img.size, (255, 255, 255))
|
||||||
|
background.paste(img, mask=img.split()[3]) # 使用 alpha 通道作为 mask
|
||||||
|
img = background
|
||||||
|
elif img.mode != 'RGB':
|
||||||
|
img = img.convert('RGB')
|
||||||
|
|
||||||
|
# 生成文件名
|
||||||
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
|
prompt_hash = abs(hash(prompt)) % 10000
|
||||||
|
ext = self.image_format if self.image_format == 'jpg' else self.image_format
|
||||||
|
filename = f"ai_image_{timestamp}_{index}_{prompt_hash}.{ext}"
|
||||||
|
filepath = os.path.join(IMAGE_DIR, filename)
|
||||||
|
|
||||||
|
# 保存图片
|
||||||
|
if self.image_format == 'webp':
|
||||||
|
img.save(filepath, 'WEBP', quality=85)
|
||||||
|
elif self.image_format == 'jpg':
|
||||||
|
img.save(filepath, 'JPEG', quality=95)
|
||||||
|
elif self.image_format == 'png':
|
||||||
|
img.save(filepath, 'PNG')
|
||||||
|
|
||||||
|
file_size = os.path.getsize(filepath)
|
||||||
|
self.dl.debug(f"图片已保存:{filepath} ({file_size} 字节, 格式:{self.image_format})")
|
||||||
|
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
self.pl.error("PIL 未安装,无法转换图片格式,保存原始图片")
|
||||||
|
# 降级:保存原始图片
|
||||||
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||||
|
prompt_hash = abs(hash(prompt)) % 10000
|
||||||
|
filename = f"ai_image_{timestamp}_{index}_{prompt_hash}.png"
|
||||||
|
filepath = os.path.join(IMAGE_DIR, filename)
|
||||||
|
with open(filepath, 'wb') as f:
|
||||||
|
f.write(image_data)
|
||||||
|
return filepath
|
||||||
|
except Exception as e:
|
||||||
|
self.pl.error(f"转换图片格式失败:{str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
def _get_api_key_from_env(self):
|
def _get_api_key_from_env(self):
|
||||||
"""从环境变量获取 API Key"""
|
"""从环境变量获取 API Key"""
|
||||||
return os.environ.get('DASHSCOPE_API_KEY', '')
|
return os.environ.get('DASHSCOPE_API_KEY', '')
|
||||||
@ -294,19 +375,27 @@ class ImageGenerator:
|
|||||||
if model:
|
if model:
|
||||||
return self.SUPPORTED_MODELS.get(model, {})
|
return self.SUPPORTED_MODELS.get(model, {})
|
||||||
return self.SUPPORTED_MODELS
|
return self.SUPPORTED_MODELS
|
||||||
|
|
||||||
|
def get_supported_formats(self):
|
||||||
|
"""获取支持的格式列表"""
|
||||||
|
return self.SUPPORTED_FORMATS
|
||||||
|
|
||||||
|
def get_size_presets(self):
|
||||||
|
"""获取尺寸快捷方式"""
|
||||||
|
return self.SIZE_PRESETS
|
||||||
|
|
||||||
|
|
||||||
def create_image_generator(api_key=None, model='wanx-v1', size=None):
|
def create_image_generator(api_key=None, model='wanx-v1', size=None, image_format=None):
|
||||||
"""创建图片生成器实例"""
|
"""创建图片生成器实例"""
|
||||||
return ImageGenerator(api_key=api_key, model=model, size=size)
|
return ImageGenerator(api_key=api_key, model=model, size=size, image_format=image_format)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if len(sys.argv) < 3:
|
if len(sys.argv) < 3:
|
||||||
print("用法:python wp_image_generator.py <api_key> <prompt> [model] [size] [count]")
|
print("用法:python wp_image_generator.py <api_key> <prompt> [model] [size] [count] [format]")
|
||||||
print("示例:python wp_image_generator.py sk-xxx '一只可爱的猫咪' wanx-v1 1024*1024 1")
|
print("示例:python wp_image_generator.py sk-xxx '一只可爱的猫咪' wanx-v1 1024*1024 1 webp")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
api_key = sys.argv[1]
|
api_key = sys.argv[1]
|
||||||
@ -314,8 +403,9 @@ if __name__ == '__main__':
|
|||||||
model = sys.argv[3] if len(sys.argv) > 3 else 'wanx-v1'
|
model = sys.argv[3] if len(sys.argv) > 3 else 'wanx-v1'
|
||||||
size = sys.argv[4] if len(sys.argv) > 4 else '1024*1024'
|
size = sys.argv[4] if len(sys.argv) > 4 else '1024*1024'
|
||||||
count = int(sys.argv[5]) if len(sys.argv) > 5 else 1
|
count = int(sys.argv[5]) if len(sys.argv) > 5 else 1
|
||||||
|
image_format = sys.argv[6] if len(sys.argv) > 6 else 'webp'
|
||||||
|
|
||||||
generator = create_image_generator(api_key=api_key, model=model, size=size)
|
generator = create_image_generator(api_key=api_key, model=model, size=size, image_format=image_format)
|
||||||
paths = generator.generate_image(prompt, n=count)
|
paths = generator.generate_image(prompt, n=count)
|
||||||
|
|
||||||
print(f"\n生成结果:")
|
print(f"\n生成结果:")
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
WordPress 发布系统 - AI 图片生成脚本
|
WordPress 发布系统 - AI 图片生成脚本
|
||||||
支持命令行调用和模块调用
|
支持命令行调用和模块调用
|
||||||
|
支持格式控制:jpg, png, webp
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -24,6 +25,7 @@ def load_config():
|
|||||||
'dashscope_api_key': '',
|
'dashscope_api_key': '',
|
||||||
'image_model': 'wanx-v1',
|
'image_model': 'wanx-v1',
|
||||||
'image_size': '1024*1024',
|
'image_size': '1024*1024',
|
||||||
|
'image_format': 'webp',
|
||||||
'image_count': 1,
|
'image_count': 1,
|
||||||
'image_style': None
|
'image_style': None
|
||||||
}
|
}
|
||||||
@ -40,7 +42,7 @@ def load_config():
|
|||||||
|
|
||||||
|
|
||||||
def generate_image(prompt, api_key=None, model=None, size=None, count=None,
|
def generate_image(prompt, api_key=None, model=None, size=None, count=None,
|
||||||
negative_prompt=None, style=None):
|
negative_prompt=None, style=None, image_format=None):
|
||||||
"""
|
"""
|
||||||
生成图片(主入口函数)
|
生成图片(主入口函数)
|
||||||
|
|
||||||
@ -52,6 +54,7 @@ def generate_image(prompt, api_key=None, model=None, size=None, count=None,
|
|||||||
count: 生成数量(可选)
|
count: 生成数量(可选)
|
||||||
negative_prompt: 反向提示词(可选)
|
negative_prompt: 反向提示词(可选)
|
||||||
style: 风格(可选)
|
style: 风格(可选)
|
||||||
|
image_format: 图片格式 (jpg, png, webp)(可选)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: 生成结果
|
dict: 生成结果
|
||||||
@ -68,6 +71,7 @@ def generate_image(prompt, api_key=None, model=None, size=None, count=None,
|
|||||||
size = size or config.get('image_size', '1024*1024')
|
size = size or config.get('image_size', '1024*1024')
|
||||||
count = count or config.get('image_count', 1)
|
count = count or config.get('image_count', 1)
|
||||||
style = style or config.get('image_style', None)
|
style = style or config.get('image_style', None)
|
||||||
|
image_format = image_format or config.get('image_format', 'webp')
|
||||||
|
|
||||||
if not api_key:
|
if not api_key:
|
||||||
pl.error("未配置 DashScope API Key")
|
pl.error("未配置 DashScope API Key")
|
||||||
@ -80,7 +84,8 @@ def generate_image(prompt, api_key=None, model=None, size=None, count=None,
|
|||||||
generator = create_image_generator(
|
generator = create_image_generator(
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
model=model,
|
model=model,
|
||||||
size=size
|
size=size,
|
||||||
|
image_format=image_format
|
||||||
)
|
)
|
||||||
|
|
||||||
# 生成图片
|
# 生成图片
|
||||||
@ -98,7 +103,8 @@ def generate_image(prompt, api_key=None, model=None, size=None, count=None,
|
|||||||
'count': len(image_paths),
|
'count': len(image_paths),
|
||||||
'paths': image_paths,
|
'paths': image_paths,
|
||||||
'model': model,
|
'model': model,
|
||||||
'size': size
|
'size': size,
|
||||||
|
'format': image_format
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
pl.end_publish(False, error_msg='生成图片失败')
|
pl.end_publish(False, error_msg='生成图片失败')
|
||||||
@ -116,7 +122,7 @@ def generate_image(prompt, api_key=None, model=None, size=None, count=None,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def generate_images_for_article(title, content, count=1, api_key=None):
|
def generate_images_for_article(title, content, count=1, api_key=None, image_format='webp'):
|
||||||
"""
|
"""
|
||||||
根据文章标题和内容生成配图
|
根据文章标题和内容生成配图
|
||||||
|
|
||||||
@ -125,6 +131,7 @@ def generate_images_for_article(title, content, count=1, api_key=None):
|
|||||||
content: 文章内容
|
content: 文章内容
|
||||||
count: 生成数量
|
count: 生成数量
|
||||||
api_key: DashScope API Key(可选)
|
api_key: DashScope API Key(可选)
|
||||||
|
image_format: 图片格式 (jpg, png, webp)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: 生成结果
|
dict: 生成结果
|
||||||
@ -135,6 +142,7 @@ def generate_images_for_article(title, content, count=1, api_key=None):
|
|||||||
# 加载配置
|
# 加载配置
|
||||||
config = load_config()
|
config = load_config()
|
||||||
api_key = api_key or config.get('dashscope_api_key', '')
|
api_key = api_key or config.get('dashscope_api_key', '')
|
||||||
|
image_format = image_format or config.get('image_format', 'webp')
|
||||||
|
|
||||||
if not api_key:
|
if not api_key:
|
||||||
pl.error("未配置 DashScope API Key")
|
pl.error("未配置 DashScope API Key")
|
||||||
@ -152,7 +160,8 @@ def generate_images_for_article(title, content, count=1, api_key=None):
|
|||||||
result = generate_image(
|
result = generate_image(
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
count=count
|
count=count,
|
||||||
|
image_format=image_format
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@ -164,6 +173,7 @@ def main():
|
|||||||
parser.add_argument('prompt', help='图片描述')
|
parser.add_argument('prompt', help='图片描述')
|
||||||
parser.add_argument('--model', '-m', default=None, help='模型名称 (wanx-v1, wanx2.1-t2i-turbo, wanx2.1-t2i-plus)')
|
parser.add_argument('--model', '-m', default=None, help='模型名称 (wanx-v1, wanx2.1-t2i-turbo, wanx2.1-t2i-plus)')
|
||||||
parser.add_argument('--size', '-s', default=None, help='图片尺寸 (1024*1024, 720*1280, 1280*720 等)')
|
parser.add_argument('--size', '-s', default=None, help='图片尺寸 (1024*1024, 720*1280, 1280*720 等)')
|
||||||
|
parser.add_argument('--format', '-f', default=None, choices=['jpg', 'png', 'webp'], help='图片格式 (jpg, png, webp)')
|
||||||
parser.add_argument('--count', '-n', type=int, default=None, help='生成数量 (1-4)')
|
parser.add_argument('--count', '-n', type=int, default=None, help='生成数量 (1-4)')
|
||||||
parser.add_argument('--style', default=None, help='图片风格 (写实、动漫、水彩等)')
|
parser.add_argument('--style', default=None, help='图片风格 (写实、动漫、水彩等)')
|
||||||
parser.add_argument('--negative', default=None, help='反向提示词')
|
parser.add_argument('--negative', default=None, help='反向提示词')
|
||||||
@ -178,7 +188,8 @@ def main():
|
|||||||
size=args.size,
|
size=args.size,
|
||||||
count=args.count,
|
count=args.count,
|
||||||
negative_prompt=args.negative,
|
negative_prompt=args.negative,
|
||||||
style=args.style
|
style=args.style,
|
||||||
|
image_format=args.format
|
||||||
)
|
)
|
||||||
|
|
||||||
# 输出 JSON 结果
|
# 输出 JSON 结果
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user