Files
nanocode/skills/baoyu-post-to-wechat/baoyu_with_image_gen.py
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

815 lines
31 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
baoyu-post-to-wechat 图片生成增强版
集成 WeWrite 的豆包Seedream图片生成功能
"""
import os
import sys
import json
import re
import requests
import yaml
import base64
import tempfile
import mimetypes
import argparse
from pathlib import Path
from typing import List, Dict, Optional, Union, Tuple
from datetime import datetime
# 添加 WeWrite 路径
WEWRITE_DIR = Path("/home/ubuntu/wewrite")
sys.path.insert(0, str(WEWRITE_DIR))
try:
from toolkit.image_gen import generate_image
HAS_IMAGE_GEN = True
except ImportError as e:
print(f"⚠️ 无法导入 WeWrite 图片生成模块: {e}")
HAS_IMAGE_GEN = False
# 尝试导入Pillow(图片处理)
try:
from PIL import Image, ImageOps
HAS_PILLOW = True
except ImportError:
HAS_PILLOW = False
print("⚠️ Pillow未安装,图片优化功能受限")
# 配置文件路径
EXTEND_PATH = "/home/ubuntu/.nanobot/workspace/skills/baoyu-post-to-wechat/EXTEND.md"
WEWRITE_CONFIG = "/home/ubuntu/wewrite/config.yaml"
class ImageGenerator:
"""图片生成器(集成WeWrite的豆包Seedream)"""
@staticmethod
def generate_cover_image(prompt: str, output_path: str = None) -> str:
"""
生成封面图片
"""
if not HAS_IMAGE_GEN:
raise ImportError("WeWrite 图片生成模块未找到")
if not output_path:
output_path = f"/tmp/cover_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
try:
print(f"🎨 生成封面图片: {prompt}")
path = generate_image(prompt, output_path, size="cover")
print(f"✅ 封面图片生成成功: {path}")
return path
except Exception as e:
print(f"❌ 封面图片生成失败: {e}")
raise
@staticmethod
def generate_article_image(prompt: str, output_path: str = None) -> str:
"""
生成文章配图
"""
if not HAS_IMAGE_GEN:
raise ImportError("WeWrite 图片生成模块未找到")
if not output_path:
output_path = f"/tmp/article_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
try:
print(f"🎨 生成文章配图: {prompt}")
path = generate_image(prompt, output_path, size="article")
print(f"✅ 文章配图生成成功: {path}")
return path
except Exception as e:
print(f"❌ 文章配图生成失败: {e}")
raise
@staticmethod
def generate_image_for_section(section_title: str, content: str, output_path: str = None) -> str:
"""
根据章节内容生成配图
"""
# 构建图片生成提示词
prompt = f"{section_title} - {content[:50]}..."
if not output_path:
output_path = f"/tmp/section_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
return ImageGenerator.generate_article_image(prompt, output_path)
class ImageOptimizer:
"""图片优化器"""
@staticmethod
def optimize_image(image_path: str, max_size: int = 1024*1024) -> str:
"""
优化图片以适应微信要求
返回优化后的图片路径
"""
if not HAS_PILLOW:
print(f"⚠️ Pillow未安装,跳过图片优化: {os.path.basename(image_path)}")
return image_path
try:
with Image.open(image_path) as img:
original_format = img.format
original_size = os.path.getsize(image_path)
print(f"🖼️ 优化图片: {os.path.basename(image_path)} "
f"({original_format}, {original_size/1024:.1f}KB)")
# 转换模式
if img.mode not in ['RGB', 'L']:
img = img.convert('RGB')
# 检查是否需要调整尺寸(微信建议宽度≤2560)
max_width = 2560
if img.width > max_width:
ratio = max_width / img.width
new_height = int(img.height * ratio)
img = img.resize((max_width, new_height), Image.Resampling.LANCZOS)
print(f" ↳ 调整尺寸: {img.width}x{img.height}")
# 检查文件大小
if original_size > max_size:
# 创建临时文件
temp_file = tempfile.NamedTemporaryFile(
suffix='.jpg', delete=False, prefix='optimized_'
)
temp_path = temp_file.name
temp_file.close()
# 渐进式压缩
quality = 85
while quality >= 50:
img.save(temp_path, 'JPEG', quality=quality, optimize=True)
optimized_size = os.path.getsize(temp_path)
if optimized_size <= max_size or quality <= 50:
print(f" ↳ 压缩质量: {quality}% → {optimized_size/1024:.1f}KB")
return temp_path
quality -= 10
return temp_path
else:
# 如果图片已经符合要求,直接返回原路径
return image_path
except Exception as e:
print(f"❌ 图片优化失败 {os.path.basename(image_path)}: {e}")
return image_path
@staticmethod
def get_image_info(image_path: str) -> Dict:
"""获取图片信息"""
info = {
'path': image_path,
'filename': os.path.basename(image_path),
'exists': os.path.exists(image_path),
'size': os.path.getsize(image_path) if os.path.exists(image_path) else 0,
'optimized': False
}
if HAS_PILLOW and info['exists']:
try:
with Image.open(image_path) as img:
info.update({
'format': img.format,
'mode': img.mode,
'width': img.width,
'height': img.height,
'size_kb': info['size'] / 1024
})
except:
pass
return info
class MarkdownParser:
"""Markdown解析器(支持Frontmatter)"""
@staticmethod
def parse_frontmatter(content: str) -> Tuple[Dict, str]:
"""
解析Markdown中的YAML frontmatter
返回: (frontmatter_dict, markdown_body)
"""
# 匹配 frontmatter 模式: ---\n内容\n---\n
pattern = r'^---\s*\n(.*?)\n---\s*\n(.*)$'
match = re.match(pattern, content, re.DOTALL | re.MULTILINE)
if match:
frontmatter_str = match.group(1)
body = match.group(2)
try:
frontmatter = yaml.safe_load(frontmatter_str) or {}
except yaml.YAMLError as e:
print(f"⚠️ Frontmatter解析错误: {e}")
frontmatter = {}
return frontmatter, body
else:
return {}, content
@staticmethod
def extract_title_from_body(body: str) -> str:
"""从正文中提取标题(第一个一级标题)"""
# 匹配 # 标题
match = re.search(r'^#\s+(.+)$', body, re.MULTILINE)
if match:
return match.group(1).strip()
return ""
@staticmethod
def extract_summary(body: str, max_length: int = 120) -> str:
"""提取摘要(纯文本,不超过指定长度)"""
# 移除Markdown标记
plain_text = re.sub(r'[#*`\-\[\]>]', '', body)
# 移除HTML标签
plain_text = re.sub(r'<[^>]+>', '', plain_text)
# 合并空白字符
plain_text = re.sub(r'\s+', ' ', plain_text).strip()
if len(plain_text) <= max_length:
return plain_text
else:
# 在完整词语处截断
truncated = plain_text[:max_length]
last_space = truncated.rfind(' ')
if last_space > max_length * 0.7: # 确保不是太短的截断
return truncated[:last_space] + "..."
else:
return truncated[:max_length-3] + "..."
class WeChatPublisher:
"""微信公众号发布器(图片生成增强版)"""
def __init__(self):
self.config = self._load_config()
self.access_token = None
self.image_optimizer = ImageOptimizer()
self.markdown_parser = MarkdownParser()
self.image_generator = ImageGenerator() if HAS_IMAGE_GEN else None
def _load_config(self) -> Dict:
"""加载配置"""
config = {}
# 加载 EXTEND.md
if os.path.exists(EXTEND_PATH):
with open(EXTEND_PATH, 'r', encoding='utf-8') as f:
for line in f:
if ':' in line and not line.strip().startswith('#'):
key, value = line.split(':', 1)
config[key.strip()] = value.strip()
# 回退到 WeWrite 配置
if 'app_id' not in config and os.path.exists(WEWRITE_CONFIG):
with open(WEWRITE_CONFIG, 'r', encoding='utf-8') as f:
wewrite_config = yaml.safe_load(f)
config['app_id'] = wewrite_config['wechat']['appid']
config['app_secret'] = wewrite_config['wechat']['secret']
config['default_author'] = wewrite_config['wechat']['author']
# 加载图片生成配置
if 'image' in wewrite_config:
config['image_provider'] = wewrite_config['image']['provider']
config['image_api_key'] = wewrite_config['image']['api_key']
config['image_model'] = wewrite_config['image']['model']
# 设置默认值
config.setdefault('default_theme', 'default')
config.setdefault('default_color', 'blue')
config.setdefault('need_open_comment', '1')
config.setdefault('only_fans_can_comment', '0')
return config
def get_access_token(self) -> str:
"""获取微信 access_token"""
if self.access_token:
return self.access_token
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={self.config['app_id']}&secret={self.config['app_secret']}"
response = requests.get(url)
data = response.json()
if 'access_token' in data:
self.access_token = data['access_token']
return self.access_token
else:
raise Exception(f"获取 access_token 失败: {data}")
def upload_image(self, image_path: str) -> str:
"""上传图片到微信(永久素材)"""
access_token = self.get_access_token()
# 检查文件是否存在
if not os.path.exists(image_path):
raise FileNotFoundError(f"图片文件不存在: {image_path}")
# 优化图片(如果需要)
optimized_path = self.image_optimizer.optimize_image(image_path)
use_optimized = optimized_path != image_path
if use_optimized:
print(f" ↳ 使用优化后的图片: {os.path.basename(optimized_path)}")
upload_path = optimized_path
else:
upload_path = image_path
# 获取MIME类型
mime_type, _ = mimetypes.guess_type(upload_path)
if not mime_type:
mime_type = 'image/jpeg'
# 上传图片
url = f"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token={access_token}&type=image"
with open(upload_path, 'rb') as f:
files = {'media': (os.path.basename(upload_path), f, mime_type)}
response = requests.post(url, files=files)
data = response.json()
# 清理临时文件
if use_optimized and os.path.exists(optimized_path):
try:
os.unlink(optimized_path)
except:
pass
if 'media_id' in data:
return data['media_id']
else:
raise Exception(f"上传图片失败: {data}")
def upload_images(self, image_paths: List[str]) -> Dict[str, str]:
"""批量上传图片(带优化)"""
results = {}
print(f"📸 发现 {len(image_paths)} 张图片:")
for i, img_path in enumerate(image_paths, 1):
img_info = self.image_optimizer.get_image_info(img_path)
if not img_info['exists']:
print(f" {i}. ❌ 文件不存在: {img_info['filename']}")
results[img_path] = None
continue
# 显示图片信息
size_info = f"{img_info.get('size_kb', 0):.1f}KB"
if 'width' in img_info:
size_info = f"{img_info['width']}x{img_info['height']}, {size_info}"
format_info = img_info.get('format', '未知')
print(f" {i}. ✅ {img_info['filename']} ({format_info}, {size_info})")
print("⬆️ 上传图片到微信...")
for img_path in image_paths:
if not os.path.exists(img_path):
results[img_path] = None
continue
try:
media_id = self.upload_image(img_path)
results[img_path] = media_id
print(f" ✅ 上传成功: {os.path.basename(img_path)} -> {media_id[:20]}...")
except Exception as e:
print(f" ❌ 上传失败 {os.path.basename(img_path)}: {e}")
results[img_path] = None
return results
def extract_images_from_markdown(self, markdown_content: str) -> List[str]:
"""从Markdown中提取图片路径"""
# 匹配Markdown图片语法: ![alt](path)
pattern = r'!\[.*?\]\((.*?)\)'
matches = re.findall(pattern, markdown_content)
# 过滤出本地文件路径(排除URL)
local_images = []
for match in matches:
if not match.startswith(('http://', 'https://')):
# 检查文件是否存在
if os.path.exists(match):
local_images.append(match)
else:
# 尝试相对路径
abs_path = os.path.abspath(match)
if os.path.exists(abs_path):
local_images.append(abs_path)
return local_images
def replace_images_in_markdown(self, markdown_content: str, image_map: Dict[str, str]) -> str:
"""将Markdown中的本地图片替换为微信图片引用"""
content = markdown_content
for img_path, media_id in image_map.items():
if media_id:
# 构建微信图片引用格式
wechat_img = f'<img src="https://mmbiz.qpic.cn/mmbiz_jpg/{media_id}/0" alt="图片" style="width:100%;max-width:100%;">'
# 替换Markdown图片语法
pattern = rf'!\[.*?\]\({re.escape(img_path)}\)'
content = re.sub(pattern, wechat_img, content)
return content
def markdown_to_html(self, markdown_content: str) -> str:
"""将Markdown转换为HTML(优化版)"""
# 处理标题
html = markdown_content
# 标题
html = re.sub(r'^# (.*?)$', r'<h1>\1</h1>', html, flags=re.MULTILINE)
html = re.sub(r'^## (.*?)$', r'<h2>\1</h2>', html, flags=re.MULTILINE)
html = re.sub(r'^### (.*?)$', r'<h3>\1</h3>', html, flags=re.MULTILINE)
html = re.sub(r'^#### (.*?)$', r'<h4>\1</h4>', html, flags=re.MULTILINE)
# 粗体和斜体
html = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', html)
html = re.sub(r'\*(.*?)\*', r'<em>\1</em>', html)
# 删除线
html = re.sub(r'~~(.*?)~~', r'<del>\1</del>', html)
# 内联代码
html = re.sub(r'`([^`]+)`', r'<code>\1</code>', html)
# 列表
lines = html.split('\n')
in_list = False
result_lines = []
for line in lines:
if re.match(r'^[*-] ', line):
if not in_list:
result_lines.append('<ul>')
in_list = True
content = line[2:].strip()
result_lines.append(f' <li>{content}</li>')
elif re.match(r'^\d+\. ', line):
if not in_list:
result_lines.append('<ol>')
in_list = True
content = re.sub(r'^\d+\. ', '', line).strip()
result_lines.append(f' <li>{content}</li>')
else:
if in_list:
result_lines.append('</ul>' if '<ul>' in result_lines else '</ol>')
in_list = False
result_lines.append(line)
if in_list:
result_lines.append('</ul>' if '<ul>' in result_lines else '</ol>')
html = '\n'.join(result_lines)
# 引用
html = re.sub(r'^> (.*?)$', r'<blockquote>\1</blockquote>', html, flags=re.MULTILINE)
# 代码块(简化处理)
html = re.sub(r'```.*?\n(.*?)\n```', r'<pre><code>\1</code></pre>', html, flags=re.DOTALL)
# 链接
html = re.sub(r'\[(.*?)\]\((.*?)\)', r'<a href="\2">\1</a>', html)
# 段落处理
lines = html.split('\n')
in_paragraph = False
final_lines = []
for line in lines:
if line.strip() == '':
if in_paragraph:
final_lines.append('</p>')
in_paragraph = False
final_lines.append('')
else:
if not in_paragraph and not line.startswith(('<h', '<li', '<img', '<blockquote', '<pre', '<ul', '<ol', '<a')):
final_lines.append('<p>')
in_paragraph = True
final_lines.append(line)
if in_paragraph:
final_lines.append('</p>')
html = '\n'.join(final_lines)
return html
def create_draft(self, title: str, content: str, author: str = None,
digest: str = None, cover_image_path: str = None) -> str:
"""创建公众号草稿(支持HTML内容)"""
access_token = self.get_access_token()
url = f"https://api.weixin.qq.com/cgi-bin/draft/add?access_token={access_token}"
# 上传封面图
thumb_media_id = None
if cover_image_path and os.path.exists(cover_image_path):
print(f"🖼️ 上传封面图: {os.path.basename(cover_image_path)}")
thumb_media_id = self.upload_image(cover_image_path)
# 如果没有提供作者,使用默认作者
if not author:
author = self.config.get('default_author', '')
# 如果没有提供摘要,从内容中提取
if not digest:
plain_text = re.sub(r'<[^>]+>', '', content) # 移除HTML标签
plain_text = re.sub(r'\s+', ' ', plain_text).strip()
digest = self.markdown_parser.extract_summary(plain_text)
# 构建文章数据
article = {
"title": title,
"author": author,
"digest": digest,
"content": content,
"show_cover_pic": 0,
"need_open_comment": int(self.config.get('need_open_comment', 1)),
"only_fans_can_comment": int(self.config.get('only_fans_can_comment', 0))
}
if thumb_media_id:
article["thumb_media_id"] = thumb_media_id
body = {"articles": [article]}
# 发送请求
headers = {"Content-Type": "application/json; charset=utf-8"}
response = requests.post(url, data=json.dumps(body, ensure_ascii=False).encode('utf-8'), headers=headers)
data = response.json()
if 'media_id' in data:
return data['media_id']
else:
raise Exception(f"创建草稿失败: {data}")
def generate_images_for_markdown(self, markdown_content: str, output_dir: str = None) -> Tuple[str, Dict[str, str]]:
"""
为Markdown内容生成配图
返回: (处理后的markdown, 图片路径映射)
"""
if not self.image_generator:
raise ImportError("图片生成功能不可用")
if not output_dir:
output_dir = tempfile.mkdtemp(prefix="generated_images_")
# 提取章节标题
section_pattern = r'^#{2,4}\s+(.+)$'
sections = re.findall(section_pattern, markdown_content, re.MULTILINE)
image_map = {}
processed_content = markdown_content
print(f"🎨 为 {len(sections)} 个章节生成配图...")
for i, section_title in enumerate(sections, 1):
# 生成图片
output_path = os.path.join(output_dir, f"section_{i:02d}.jpg")
try:
image_path = self.image_generator.generate_image_for_section(
section_title,
f"文章章节配图: {section_title}",
output_path
)
# 在章节前插入图片
img_markdown = f'\n![{section_title}]({image_path})\n*{section_title}*\n'
# 找到章节位置并插入图片
section_pattern = rf'^(#{2,4}\s+{re.escape(section_title)})$'
processed_content = re.sub(
section_pattern,
f'{img_markdown}\\1',
processed_content,
flags=re.MULTILINE
)
image_map[image_path] = section_title
print(f" ✅ 为章节生成配图: {section_title}")
except Exception as e:
print(f" ⚠️ 章节配图生成失败 {section_title}: {e}")
return processed_content, image_map
def publish_markdown_with_images(self, markdown_path: str, title: str = None,
author: str = None, cover_image_path: str = None,
generate_images: bool = False) -> Dict:
"""发布Markdown文章(支持图文混排 + 图片生成)"""
print("=" * 60)
print("📝 baoyu-post-to-wechat 图片生成增强版 v3.0")
print("=" * 60)
# 检查文件
if not os.path.exists(markdown_path):
raise FileNotFoundError(f"Markdown文件不存在: {markdown_path}")
# 读取Markdown内容
with open(markdown_path, 'r', encoding='utf-8') as f:
markdown_content = f.read()
print(f"📄 读取文件: {os.path.basename(markdown_path)} ({len(markdown_content)} 字符)")
# 解析Frontmatter
print("🔍 解析Frontmatter...")
frontmatter, body = self.markdown_parser.parse_frontmatter(markdown_content)
if frontmatter:
print(" ✅ 发现Frontmatter:")
for key, value in frontmatter.items():
if key in ['title', 'author', 'date', 'tags']:
print(f" {key}: {value}")
# 确定标题(优先级:参数 > frontmatter > 正文提取 > 文件名)
if not title:
title = frontmatter.get('title', '')
if not title:
title = self.markdown_parser.extract_title_from_body(body)
if not title:
title = Path(markdown_path).stem
# 确定作者(优先级:参数 > frontmatter > 配置默认)
if not author:
author = frontmatter.get('author', '')
if not author:
author = self.config.get('default_author', '')
# 生成封面图(如果需要)
if not cover_image_path and generate_images and self.image_generator:
print("🎨 生成封面图片...")
try:
cover_prompt = f"文章封面: {title} - {author}"
cover_image_path = self.image_generator.generate_cover_image(cover_prompt)
print(f" ✅ 封面图片生成成功: {os.path.basename(cover_image_path)}")
except Exception as e:
print(f" ⚠️ 封面图片生成失败: {e}")
# 生成章节配图(如果需要)
if generate_images and self.image_generator:
print("🎨 生成章节配图...")
try:
body, generated_images = self.generate_images_for_markdown(body)
print(f" ✅ 生成 {len(generated_images)} 张章节配图")
except Exception as e:
print(f" ⚠️ 章节配图生成失败: {e}")
# 提取图片
print("🖼️ 提取图片...")
image_paths = self.extract_images_from_markdown(body)
if image_paths:
# 上传图片
image_map = self.upload_images(image_paths)
# 替换图片引用
print("🔄 替换图片引用...")
processed_body = self.replace_images_in_markdown(body, image_map)
else:
print("ℹ️ 未发现本地图片")
processed_body = body
# 转换为HTML
print("🔧 转换为HTML...")
html_content = self.markdown_to_html(processed_body)
# 提取摘要
print("📋 生成摘要...")
digest = self.markdown_parser.extract_summary(body)
print(f" 摘要: {digest[:50]}..." if len(digest) > 50 else f" 摘要: {digest}")
# 如果没有封面图,使用默认
if not cover_image_path:
default_cover = "/home/ubuntu/default_cover.png"
if os.path.exists(default_cover):
cover_image_path = default_cover
print(f"🖼️ 使用默认封面图: {os.path.basename(default_cover)}")
# 创建草稿
print("📤 创建公众号草稿...")
media_id = self.create_draft(title, html_content, author, digest, cover_image_path)
print("=" * 60)
print("✅ 发布成功!")
print("=" * 60)
return {
"success": True,
"media_id": media_id,
"title": title,
"author": author,
"digest": digest[:100] + "..." if len(digest) > 100 else digest,
"images_uploaded": len([v for v in image_map.values() if v]) if image_paths else 0,
"has_frontmatter": bool(frontmatter),
"images_generated": generate_images and self.image_generator is not None,
"message": "草稿创建成功"
}
def main():
"""命令行入口"""
parser = argparse.ArgumentParser(
description='微信公众号发布工具(图片生成增强版)',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
%(prog)s markdown article.md
%(prog)s markdown article.md --title "标题" --author "作者"
%(prog)s markdown article.md --generate-images # 自动生成配图
%(prog)s markdown article.md --cover /path/to/cover.jpg
%(prog)s test
"""
)
subparsers = parser.add_subparsers(dest='command', help='命令')
# markdown 命令
md_parser = subparsers.add_parser('markdown', help='发布Markdown文章')
md_parser.add_argument('file', help='Markdown文件路径')
md_parser.add_argument('--title', help='文章标题(覆盖frontmatter)')
md_parser.add_argument('--author', help='作者(覆盖frontmatter)')
md_parser.add_argument('--cover', help='封面图路径')
md_parser.add_argument('--generate-images', action='store_true',
help='自动为章节生成配图')
# test 命令
test_parser = subparsers.add_parser('test', help='测试环境和配置')
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
publisher = WeChatPublisher()
try:
if args.command == 'markdown':
result = publisher.publish_markdown_with_images(
args.file, args.title, args.author, args.cover, args.generate_images
)
print(json.dumps(result, ensure_ascii=False, indent=2))
elif args.command == 'test':
print("🔧 测试环境和配置...")
print(f"Python版本: {sys.version.split()[0]}")
print(f"Pillow可用: {'✅' if HAS_PILLOW else '❌'}")
print(f"PyYAML可用: ✅")
print(f"图片生成可用: {'✅' if HAS_IMAGE_GEN else '❌'}")
# 测试配置
print(f"\n📋 配置信息:")
print(f" AppID: {publisher.config.get('app_id')}")
print(f" 作者: {publisher.config.get('default_author')}")
if HAS_IMAGE_GEN:
print(f" 图片生成API: {publisher.config.get('image_provider')}")
print(f" 图片模型: {publisher.config.get('image_model')}")
# 测试access_token
try:
token = publisher.get_access_token()
print(f" Access Token: {token[:20]}...")
print("✅ 配置正常")
except Exception as e:
print(f"❌ 配置错误: {e}")
# 测试图片生成
if HAS_IMAGE_GEN:
print(f"\n🎨 图片生成测试:")
try:
test_prompt = "一只可爱的橘猫在阳光下睡觉"
test_output = "/tmp/test_image_gen.jpg"
from toolkit.image_gen import generate_image
path = generate_image(test_prompt, test_output, size="cover")
print(f" ✅ 图片生成测试成功: {path}")
# 显示图片信息
if os.path.exists(path):
size = os.path.getsize(path) / 1024
print(f" 图片大小: {size:.1f}KB")
except Exception as e:
print(f" ❌ 图片生成测试失败: {e}")
else:
parser.print_help()
except Exception as e:
print(json.dumps({
"success": False,
"error": str(e)
}, ensure_ascii=False, indent=2))
sys.exit(1)
if __name__ == "__main__":
main()