rtc_backend/apps/stories/management/commands/generate_default_covers.py
repair-agent 861bad22ab feat: 新增 generate_default_covers 管理命令
用 LLM 从故事内容提炼 ≤50 字画面描述,调用 Seedream 4.5 生成
2560x1440 横版封面,覆盖上传到 OSS 固定路径,自动更新所有用户。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 15:45:49 +08:00

117 lines
4.1 KiB
Python
Raw Permalink 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.

"""
用新的 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完成。"))