铁律合规: - P0: 移除三处硬编码API密钥 → lib/secrets.py 三层查找 - P0: 统一LIB/engines调用关系(tts复用音色表,composer桥接editor) - P0: batch支持参数+batch-resume断点续传 - P0: Seed一致性(seed_value/lock_seed字段+gen --lock-seed) - P0: 跨集资产继承(inherit命令)+A/B对比(compare命令) - P1: concat逻辑修复/滤镜崩溃修复/FFmpeg路径统一/成本追踪 - P2: 状态判断修复/CLI --help+--version+模糊匹配 - 新增: 65条命令(原17条)覆盖全链路 - 新增: deps/info/note/history/report/flow/director等 - 语法OK 零重复函数 零密钥泄露
125 lines
4.3 KiB
Python
125 lines
4.3 KiB
Python
# LIB · 豆包对话模型适配器(剧本拆解/分镜生成)
|
||
# D180 · 2026-07-10
|
||
"""火山方舟 ARK Chat API → doubao-seed 系列模型"""
|
||
import json
|
||
import os
|
||
import subprocess
|
||
import tempfile
|
||
|
||
from lib.secrets import get_ark_key
|
||
ARK_KEY = get_ark_key()
|
||
ARK_CHAT_URL = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
|
||
|
||
# 可用模型
|
||
MODELS = {
|
||
"pro": "doubao-seed-2-1-pro-260628", # 最强·适合复杂剧本
|
||
"lite": "doubao-seed-2-0-lite-260215", # 轻量·适合批量
|
||
"deepseek": "deepseek-v4-pro-260425", # 深度思<E5BAA6><E6809D><EFBFBD>·适合拆解
|
||
}
|
||
|
||
def _curl_api(payload):
|
||
"""通过 subprocess curl 调用 API(绕过 Windows urllib 超时问题)"""
|
||
import subprocess, tempfile
|
||
tf = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8')
|
||
json.dump(payload, tf, ensure_ascii=False)
|
||
tf.close()
|
||
try:
|
||
result = subprocess.run([
|
||
"curl", "-s", ARK_CHAT_URL,
|
||
"-H", f"Authorization: Bearer {ARK_KEY}",
|
||
"-H", "Content-Type: application/json",
|
||
"-d", f"@{tf.name}"
|
||
], capture_output=True, text=True, timeout=120)
|
||
os.unlink(tf.name)
|
||
return json.loads(result.stdout) if result.stdout else {"error": result.stderr}
|
||
except subprocess.TimeoutExpired:
|
||
os.unlink(tf.name)
|
||
return {"error": "timeout"}
|
||
|
||
def chat(prompt, system="你是一个专业的短剧剧本分析专家", model="pro", temperature=0.3, max_tokens=4096):
|
||
"""调用豆包对话模型"""
|
||
payload = {
|
||
"model": MODELS.get(model, model),
|
||
"messages": [
|
||
{"role": "system", "content": system},
|
||
{"role": "user", "content": prompt}
|
||
],
|
||
"temperature": temperature,
|
||
"max_tokens": max_tokens
|
||
}
|
||
result = _curl_api(payload)
|
||
if "error" in result:
|
||
return result
|
||
choice = result.get("choices", [{}])[0]
|
||
return {
|
||
"content": choice.get("message", {}).get("content", ""),
|
||
"model": result.get("model", ""),
|
||
"tokens": result.get("usage", {}),
|
||
"finish": choice.get("finish_reason", "")
|
||
}
|
||
|
||
def breakdown_script(script_text, episode_num=1):
|
||
"""拆解一集剧本为结构化分镜"""
|
||
prompt = f"""请将以下短剧剧本第{episode_num}集拆解为结构化分镜。
|
||
|
||
要求:
|
||
1. 按镜头拆分,每个镜头包含:镜头编号、景别(特写/近景/中景/全景/POV)、时长(秒)
|
||
2. 提取每个镜头中出现的角色、场景、道具
|
||
3. 写出每个镜头的画面描述(50字以内)
|
||
4. 标注镜头类型:establishing/action/reaction/closeup/transition
|
||
|
||
输出JSON格式:
|
||
{{
|
||
"episode": {episode_num},
|
||
"shots": [
|
||
{{
|
||
"shot_number": "S01",
|
||
"description": "画面描述",
|
||
"camera": "POV|特写|近景|中景|全景",
|
||
"duration": 6,
|
||
"type": "establishing|action|reaction|closeup|transition",
|
||
"characters": ["角色名"],
|
||
"scenes": ["场景名"],
|
||
"props": ["道具名"],
|
||
"dialogue": null
|
||
}}
|
||
]
|
||
}}
|
||
|
||
剧本内容:
|
||
{script_text}"""
|
||
return chat(prompt, system="你是专业的短剧剧本拆解专家,输出纯JSON,不添加任何解释。")
|
||
|
||
def generate_keyframes(shot_description, character_refs, scene_refs):
|
||
"""为单个镜头生成关键帧 prompt"""
|
||
prompt = f"""基于以下镜头描述,生成即梦4.0图像生成提示词。
|
||
|
||
镜头:{shot_description}
|
||
可用角色:{json.dumps(character_refs, ensure_ascii=False)}
|
||
可用场景:{json.dumps(scene_refs, ensure_ascii=False)}
|
||
|
||
要求:
|
||
1. 提示词用英文
|
||
2. 包含景别、角色位置、场景细节、光照、风格
|
||
3. 不超过150词
|
||
4. 输出纯提示词,不加任何解释"""
|
||
return chat(prompt, model="pro", temperature=0.5, max_tokens=300)
|
||
|
||
# ====== CLI ======
|
||
if __name__ == "__main__":
|
||
import sys
|
||
if len(sys.argv) < 2:
|
||
print("Usage: python doubao_chat.py <command> [args]")
|
||
print(" chat <prompt> 直接对话")
|
||
print(" breakdown <file> 拆解剧本文件")
|
||
sys.exit(1)
|
||
cmd = sys.argv[1]
|
||
if cmd == "chat":
|
||
r = chat(sys.argv[2])
|
||
print(r["content"] if "content" in r else r)
|
||
elif cmd == "breakdown":
|
||
with open(sys.argv[2], "r", encoding="utf-8") as f:
|
||
script = f.read()
|
||
r = breakdown_script(script)
|
||
print(r["content"] if "content" in r else r)
|