feat: 新增 generate_default_covers 管理命令
用 LLM 从故事内容提炼 ≤50 字画面描述,调用 Seedream 4.5 生成 2560x1440 横版封面,覆盖上传到 OSS 固定路径,自动更新所有用户。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8f9f7824cd
commit
861bad22ab
116
apps/stories/management/commands/generate_default_covers.py
Normal file
116
apps/stories/management/commands/generate_default_covers.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
"""
|
||||||
|
用新的 LLM 提炼逻辑重新生成默认故事封面并上传到 OSS。
|
||||||
|
|
||||||
|
使用方法:
|
||||||
|
python manage.py generate_default_covers
|
||||||
|
python manage.py generate_default_covers --dry-run # 仅打印提炼到的描述,不生成图片
|
||||||
|
"""
|
||||||
|
import uuid
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from apps.stories.utils import DEFAULT_STORIES
|
||||||
|
from apps.stories.services.llm_service import _extract_image_description
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 每个默认故事对应的 OSS key(与 utils.py 中的 cover_url 一致)
|
||||||
|
DEFAULT_COVER_KEYS = {
|
||||||
|
"失控的魔法扫帚": "stories/defaults/失控的魔法扫帚_cover.png",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "用 LLM 提炼故事画面描述后调用 Seedream 4.5 重新生成默认故事封面"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
|
action="store_true",
|
||||||
|
help="仅打印 LLM 提炼的画面描述,不实际生成图片",
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
dry_run = options["dry_run"]
|
||||||
|
config = settings.LLM_CONFIG
|
||||||
|
|
||||||
|
if not config.get("API_KEY"):
|
||||||
|
self.stderr.write(self.style.ERROR("VOLCENGINE_API_KEY 未配置"))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
from volcenginesdkarkruntime import Ark
|
||||||
|
except ImportError:
|
||||||
|
self.stderr.write(self.style.ERROR("volcengine SDK 未安装"))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
from utils.oss import get_oss_client
|
||||||
|
import oss2
|
||||||
|
except ImportError:
|
||||||
|
self.stderr.write(self.style.ERROR("oss2 未安装"))
|
||||||
|
return
|
||||||
|
|
||||||
|
client = Ark(api_key=config["API_KEY"])
|
||||||
|
image_model = config.get("IMAGE_MODEL_NAME", "doubao-seedream-4-5-251128")
|
||||||
|
image_size = config.get("IMAGE_SIZE", "2560x1440")
|
||||||
|
oss_config = settings.ALIYUN_OSS
|
||||||
|
oss_base = f"https://{oss_config['BUCKET_NAME']}.{oss_config['ENDPOINT']}"
|
||||||
|
|
||||||
|
for story in DEFAULT_STORIES:
|
||||||
|
title = story["title"]
|
||||||
|
content = story["content"]
|
||||||
|
oss_key = DEFAULT_COVER_KEYS.get(title)
|
||||||
|
|
||||||
|
if not oss_key:
|
||||||
|
self.stdout.write(self.style.WARNING(f"[{title}] 未找到对应 OSS key,跳过"))
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.stdout.write(f"\n[{title}]")
|
||||||
|
|
||||||
|
# Step 1: LLM 提炼画面描述
|
||||||
|
self.stdout.write(" 正在用 LLM 提炼画面描述...")
|
||||||
|
scene_desc = _extract_image_description(
|
||||||
|
title, content, client, config["MODEL_NAME"]
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS(f" 画面描述({len(scene_desc)} 字):{scene_desc}"))
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
self.stdout.write(self.style.NOTICE(" [dry-run] 跳过图片生成"))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Step 2: 文生图
|
||||||
|
image_prompt = (
|
||||||
|
f"儿童绘本封面插画,{scene_desc},卡通可爱风格,色彩明亮鲜艳,高质量插画"
|
||||||
|
)
|
||||||
|
self.stdout.write(f" 正在生成封面图({image_size})...")
|
||||||
|
result = client.images.generate(
|
||||||
|
model=image_model,
|
||||||
|
prompt=image_prompt,
|
||||||
|
size=image_size,
|
||||||
|
response_format="url",
|
||||||
|
watermark=False,
|
||||||
|
)
|
||||||
|
image_url = result.data[0].url
|
||||||
|
self.stdout.write(f" 临时图片 URL: {image_url[:80]}...")
|
||||||
|
|
||||||
|
# Step 3: 下载图片
|
||||||
|
self.stdout.write(" 正在下载图片...")
|
||||||
|
resp = requests.get(image_url, timeout=60)
|
||||||
|
resp.raise_for_status()
|
||||||
|
|
||||||
|
# Step 4: 覆盖上传到 OSS
|
||||||
|
self.stdout.write(f" 正在上传到 OSS: {oss_key}")
|
||||||
|
oss_client = get_oss_client()
|
||||||
|
oss_client.bucket.put_object(
|
||||||
|
oss_key,
|
||||||
|
resp.content,
|
||||||
|
headers={"Content-Type": "image/jpeg"},
|
||||||
|
)
|
||||||
|
final_url = f"{oss_base}/{oss_key}"
|
||||||
|
self.stdout.write(self.style.SUCCESS(f" ✓ 封面已更新: {final_url}"))
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS("\n完成。"))
|
||||||
Loading…
x
Reference in New Issue
Block a user