126 lines
6.6 KiB
Python
126 lines
6.6 KiB
Python
#!/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}")
|