guanghulab/video-ai-system/tools/shots_batch_gen.py
bingshuo c6c62e0b73
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
ICE-GL-ZY001 · PROP-003广告牌V3·带完整招生文字·6行 · D169
2026-07-06 17:20:32 +08:00

126 lines
6.6 KiB
Python
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
"""第1集 镜02~09 Seedance批量生成 · 适配V3-R4不良人风格"""
import os, sys, json, time, subprocess
from pathlib import Path
ROOT = Path('D:/WorkBuddy/guanghulab/video-ai-system')
env = {}
for line in open(ROOT/".env"):
line = line.strip()
if not line or line.startswith("#"): continue
if "=" in line:
k, v = line.split("=", 1)
env[k.strip()] = v.strip()
AK = env["JIMENG_API_KEY"]
URL = env["JIMENG_BASE_URL"]
OUT = ROOT / "outputs/shots"
OUT.mkdir(parents=True, exist_ok=True)
STYLE = "不良人硬核写实风格厚重电影级光影画面质感与CHAR-003苏白V3-R4统一"
SHOTS = [
{ # 镜02
"id": 2, "dur": 4,
"p": "中国修仙世界百宗会招生广场,远景俯瞰。巨大广场上人山人海,各大宗门招生殿阵列排布,鎏金牌匾和灵光法阵交相辉映。穿修仙服饰的宗门弟子在各招生处前排起长队,人群熙熙攘攘热闹非凡。镜头固定俯拍微移。修仙世界大气磅礴,竖屏。" + STYLE
},
{ # 镜03
"id": 3, "dur": 5,
"p": "修仙世界角落破旧招生点从牌匾特写拉到中景。先特写一块悬挂在旧木杆上的破旧木牌匾——古朴天道宗三字风化斑驳。镜头缓慢后拉露出站在牌匾下的18岁修仙少年苏白——白衫青纱袍半束黑发半侧身淡然从容的少年宗主。他站在寒酸招生点前一牌一桌一广告牌就是全部家当。角落冷清远处模糊可见其他豪华招生殿。" + STYLE
},
{ # 镜04
"id": 4, "dur": 5,
"p": "修仙世界天道宗招生点前中景平视。18岁白衫青纱袍少年宗主苏白站在破旧摊位旁旁边竖立着A架粗麻广告牌。周围聚集了五六个人对他指指点点有人面露嘲讽之色交头接耳。苏白双手叉腰自信喊话不惧嘲笑。修仙百宗会角落场景广告牌无文字留白。固定机位轻微呼吸感。" + STYLE
},
{ # 镜05
"id": 5, "dur": 5,
"p": "修仙世界天道宗角落招生点中景拉近。围观者散去了一阵风吹起苏白脚下落叶白衫青纱袍的18岁少年宗主独自叹气手扶额头看向远处灵霄宗豪华招生殿的方向。远处鎏金牌匾模糊可见。氛围孤独清冷但不绝望。镜头从固定机位缓慢推到苏白面部近景。" + STYLE
},
{ # 镜06
"id": 6, "dur": 5,
"p": "修仙世界灵霄宗招生殿前中景平视。蓝色执事袍银色灵纹的中年王执事正襟危坐紫檀案台后手执朱砂灵笔在鉴定卷轴上画下一个红色叉号。他面容冷漠不屑对面站着18岁粗麻布衣的清瘦少年诸葛风——剑眉星目苍白肤色面露不可置信双手攥拳指甲掐入掌心。灵霄宗鎏金牌匾高悬上方背景排队修仙者如龙。固定机位双角色对比冲突。" + STYLE
},
{ # 镜07
"id": 7, "dur": 4,
"p": "修仙世界灵霄宗招生殿前,中景俯视。一个灰色素袍的普通中年男子从人群中挤出,微躬身双手捧布包袱递给王执事。蓝袍执事掂了掂袋子伸手一摸,表情从冷漠变成眉开眼笑。谄媚送礼与势利变脸的反差画面。" + STYLE
},
{ # 镜08
"id": 8, "dur": 4,
"p": "粗麻布衣少年诸葛风特写,他的指甲深深陷入掌心留下旧疤痕,满脸屈辱嘴角紧抿。画面开始快速闪切模糊处理——闪切一:两位老人倒在血泊中,暗紫锦缎金丝绣边的华贵员外俯视鄙夷。闪切二:少年被踩住脑袋。闪切回到诸葛风面部,他的眼底开始燃烧仇恨火焰。情绪爆发,闪切做模糊化。" + STYLE
},
{ # 镜09
"id": 9, "dur": 5,
"p": "修仙世界天道宗角落招生点,中景跟拍转固定。粗麻布衣的清瘦少年诸葛风穿过人群走来,他的眼睛忽然亮起——看到了天道宗破旧牌匾和广告牌上的信息。他走到摊位前鼓起勇气询问,白衫青纱袍的少年宗主苏白微笑回应,拍拍胸脯自信承诺。最后苏白手搭诸葛风肩膀,定格在信任建立的一刻。两人站在杆挂牌匾下的简陋招生点前。" + STYLE
}
]
def submit(prompt, duration):
payload = {
"model": "doubao-seedance-2-0-260128",
"content": [{"type": "text", "text": prompt}],
"duration": duration,
"resolution": "720p"
}
r = subprocess.run(["curl", "-s", "-m", "60", "--noproxy", "*", "-X", "POST",
f"{URL}/contents/generations/tasks",
"-H", f"Authorization: Bearer {AK}", "-H", "Content-Type: application/json",
"-d", json.dumps(payload)], capture_output=True, text=True, timeout=70)
try:
return json.loads(r.stdout)
except:
return {}
def poll_dl(task_id, out_path, max_attempts=90):
for i in range(max_attempts):
time.sleep(10)
r = subprocess.run(["curl", "-s", "-m", "30", "--noproxy", "*",
f"{URL}/contents/generations/tasks/{task_id}"],
capture_output=True, text=True, timeout=40,
env={**os.environ, "Authorization": f"Bearer {AK}"})
try:
data = json.loads(r.stdout)
except:
continue
st = (data.get("status") or "").lower()
if i % 5 == 0:
print(f" poll {i+1}: {st}")
if st in ["succeeded", "completed", "done"]:
vurl = data.get("content",{}).get("video_url")
if vurl:
subprocess.run(["curl", "-s", "-m", "300", "-L", "--noproxy", "*", "-o", str(out_path), vurl],
capture_output=True, timeout=310)
if out_path.exists() and out_path.stat().st_size > 1024:
return True, out_path
return False, data
if st in ["failed", "error"]:
return False, data
return False, {}
results = []
for shot in SHOTS:
sid = shot["id"]
dur = shot["dur"]
print(f"\n{sid:02d} · {dur}秒 · 提交中...")
resp = submit(shot["p"], dur)
task_id = resp.get("id")
if not task_id:
print(f" ❌ 无任务ID")
results.append({"shot": sid, "ok": False})
continue
print(f" task: {task_id}")
dst = OUT / f"ep01-shot{sid:02d}.mp4"
ok, _ = poll_dl(task_id, dst)
if ok:
print(f"{dst.name} ({dst.stat().st_size//1024}KB)")
results.append({"shot": sid, "ok": True, "file": str(dst)})
else:
print(f" ❌ 失败")
results.append({"shot": sid, "ok": False})
time.sleep(5)
print(f"\n{'='*40}")
for r in results:
st = "" if r["ok"] else ""
print(f" {st}{r['shot']:02d}")
print(f"{'='*40}")