#!/usr/bin/env python3 """第1集 1-1节 镜01~17 Seedance生成 · V3逐行完整版""" 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) S = "不良人硬核写实风格,电影级厚重光影,与CHAR-003苏白V3-R4同一风格体系" SHOTS = [ # 镜01: 高空云海·御剑飞行·仙人驾鹤 (6, "中国修仙世界云海之上,极致大远景。洁白云层铺展如大海,灵气蒸腾。数名修仙者御剑飞行穿梭云间,浑身散发灵光拖出光尾。一位白须仙人驾仙鹤从云海掠过化作流光。远处仙山浮于云海之上。竖屏。"+S), # 镜02: 俯瞰百宗会广场 (6, "修仙世界百宗会招生广场,远景俯瞰。巨大广场人山人海,各大宗门招生殿阵列排布,鎏金牌匾灵光法阵交相辉映。修仙服饰的宗门弟子在各处排队,人群熙攘热闹非凡。竖屏。"+S), # 镜03: 特写天道宗破旧牌匾 (4, "修仙世界角落,特写一块悬挂在旧木杆上的破旧木牌匾。暗色风化木纹表面开裂,古朴天道宗三字斑驳磨损。麻绳粗粝悬挂,牌匾破旧感强烈。竖屏。"+S), # 镜04: 苏白站在牌匾下双手叉腰 (6, "修仙世界天道宗角落招生点,中景。18岁白衫青纱袍少年宗主苏白站在杆挂破旧天道宗牌匾下。半束黑发青色发带,半侧身清冷从容,双手叉腰自信笃定。身后简陋招生点一牌一桌一广告牌。竖屏。"+S), # 镜05: 苏白喊话 (6, "修仙世界百宗会角落,18岁白衫青纱袍少年宗主苏白中景。他双手叉腰面向画外,自信大声喊话,眼神坚定明亮。背景是简陋天道宗招生点杆挂牌匾。竖屏。"+S), # 镜06: 广告牌特写 (5, "修仙世界天道宗招生点旁,特写一块绳系A架粗麻画布广告牌。灵木杆捆扎成A形支架,粗麻画布完整无字留白待书写。广告牌竖立在简陋摊位旁地面。竖屏。"+S), # 镜07: 苏白喊话走过路过 (5, "修仙世界百宗会角落,18岁白衫青纱袍少年宗主苏白近景。他继续朝人群方向挥手喊话,笑容自信带一丝痞气。身后的招生点牌匾和广告牌清晰可见。竖屏。"+S), # 镜08: 围观人群指指点点 (6, "修仙世界百宗会天道宗招生点前,中景群像。五六个人围了过来对着苏白和他的招生广告指指点点,有人交头接耳窃窃私语,有人摇头嗤笑。苏白站在人群中央仍保持自信。竖屏。"+S), # 镜09: 龙套一 (5, "修仙世界百宗会,远景群像。围观人群中一个声音飘出嘲讽苏白寒酸宗门也敢称天下第一。画面保持多人远景不切单人近景,群杂氛围。竖屏。"+S), # 镜10: 龙套二 (5, "修仙世界百宗会天道宗角落,远景群像。围观者中又一个声音质疑包吃包住入宗就给仙法太扯。围观人群继续指指点点。竖屏。"+S), # 镜11: 龙套三 (5, "修仙世界百宗会角落,远景群像。围观人群中第三个声音讽刺灵霄宗仙盟五百强才三十两,这种破宗门想钱想疯。人群窃笑声起。竖屏。"+S), # 镜12: 围观散去风吹落叶 (7, "修仙世界百宗会天道宗角落招生点,中景。围观人群四散离去。一阵风吹起苏白脚下的枯黄落叶飘舞,凄凉氛围。18岁白衫青纱袍少年宗主独自站在原地,缓缓叹气看向远处。背影孤独但不绝望。竖屏。"+S), # 镜13: 灵霄宗鎏金牌匾+排队 (7, "修仙世界百宗会黄金位置,中景。灵霄宗招生殿气派非凡,鎏金大牌匾灵光流转高悬殿顶。飞檐翘角三层楼阁,汉白玉台阶下排队修仙者蜿蜒如龙一眼望不到头。丝绸垂幔灵石法阵点缀其间。顶级宗门的恢弘排场。竖屏。"+S), # 镜14: 苏白挠头苦恼 (6, "修仙世界天道宗角落招生点,18岁白衫青纱袍少年宗主苏白中近景。他手扶额头挠头发,一脸苦恼不解,自言自语般嘟囔为什么自己同样的条件却没人来。身后简陋招生点与远处豪华灵霄宗殿形成对比。竖屏。"+S), # 镜15: 系统查看面板 (4, "修仙世界天道宗角落,18岁白衫青纱袍少年宗主苏白中景。他眼神专注看向虚空某处,嘴唇微动在心中默念系统指令。表情从苦恼转为期待。竖屏。"+S), # 镜16: 系统面板UI (8, "修仙世界天道宗角落,18岁白衫青纱袍少年宗主苏白中景。他凝神看着虚空中的一个发光系统面板(面板内容后期UI叠层呈现宗门名称等级人数领地资产积分为零的任务面板)。苏白认真阅读面板数据。竖屏。"+S), # 镜17: 苏白叹息 (6, "修仙世界天道宗角落招生点,18岁白衫青纱袍少年宗主苏白近景。他看完系统面板后长叹一口气,脸上写满无奈。但眼底仍有不灭的笃定,知道只要招到一个弟子就能开启仙法阁。画面定在苏白复杂而坚定的表情上。竖屏。"+S), ] 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_a=90): for i in range(max_a): time.sleep(10) r = subprocess.run(["curl", "-s", "-m", "30", "--noproxy", "*", f"{URL}/contents/generations/tasks/{task_id}", "-H", f"Authorization: Bearer {AK}"], capture_output=True, text=True, timeout=40) try: data = json.loads(r.stdout) except: continue st = (data.get("status") or "").lower() if i % 5 == 0: print(f" p{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 i, (dur, prompt) in enumerate(SHOTS): sid = i + 1 print(f"\n镜{sid:02d} · {dur}s · 提交...") resp = submit(prompt, dur) tid = resp.get("id") if not tid: print(f" ❌ 无任务ID:{json.dumps(resp,ensure_ascii=False)[:200]}") results.append({"shot":sid,"ok":False,"err":"no task"}) continue print(f" task:{tid}") dst = OUT / f"ep01-v3-shot{sid:02d}.mp4" ok, _ = poll_dl(tid, 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}") ok = sum(1 for r in results if r["ok"]) print(f" 完成: {ok}/{len(results)}") for r in results: print(f" {'✅' if r['ok'] else '❌'} 镜{r['shot']:02d}")