guanghulab/video-ai-system/engines/generate-shots.js
冰朔 fce8bf5ae4
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
D136+: 编码锁死+自然语言理解 双层提示词协议
编码层(精简锁死·不可变):
  ⊢ CHAR-003: 18岁男性·白色长衫·黑色长发半束·175cm挺拔
  ⊢ PROP: 竖式悬挂·破旧木质·【天道宗】

场景层(自然语言·AI理解为什么):
  ⊢ 场景: 宗门收徒日·灵霄宗排队·天道宗无人问津
  苏白有系统→不绝望→喊话是宣言不是乞求→叹气是烦躁不是认命
  让AI理解人物动机→自己推理→生成有灵魂的画面

动作层(自然语言·AI发挥):
  → 苏白站在牌匾下,双手叉腰,自信笑容

冰朔: '编码锁死不可变的,自然语言让AI理解为什么——如果全锁死,所有可能性都没有了'
2026-06-21 17:31:04 +08:00

130 lines
4.9 KiB
JavaScript
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.

/**
* D136+ · 视频批量生成 · 导演编码 → Seedance API
* 用法: node generate-shots.js
*/
const { generateVideo } = require('./video-api-adapter');
const fs = require('fs');
const path = require('path');
const ENCODING_FILE = path.resolve(__dirname, '../outputs/付费修仙-ep01-director-encoding.json');
// D136+ 输出优先JZAO外置硬盘 · 本地fallback
const JZAO_SHOTS = '/Volumes/JZAO/铸渊-ICE-GL-ZY001/OUT-输出/视频/zai-fu-fei-xiu-xian/ep01';
const LOCAL_SHOTS = path.resolve(__dirname, '../outputs/shots');
const OUT_DIR = fs.existsSync(JZAO_SHOTS) ? JZAO_SHOTS : LOCAL_SHOTS;
async function main() {
const encoding = JSON.parse(fs.readFileSync(ENCODING_FILE, 'utf8'));
fs.mkdirSync(OUT_DIR, { recursive: true });
console.log(`[Generate] ${encoding.project} · ep${encoding.episode} · ${encoding.shots.length}`);
console.log(`[Generate] 输出: ${OUT_DIR}\n`);
const results = [];
for (let i = 0; i < encoding.shots.length; i++) {
const s = encoding.shots[i];
const prompt = buildPrompt(s, encoding);
console.log(`━━━ 镜${i + 1}/${encoding.shots.length}: ${s.id} ━━━`);
console.log(` 景别: ${s.framing} | 情绪: ${s.emotion?.type}(${s.emotion?.intensity}) | ${s.duration}s`);
console.log(` spatial_anchor: ${s.spatial_anchor}`);
console.log(` text_elements: ${s.text_elements}`);
console.log(` prompt: ${prompt.substring(0, 100)}...`);
const outputPath = path.join(OUT_DIR, `${encoding.project}-${encoding.episode}-${s.id}.mp4`);
if (fs.existsSync(outputPath)) {
console.log(` ✅ 已存在,跳过\n`);
results.push({ ...s, file: outputPath });
continue;
}
try {
const result = await generateVideo({
prompt,
duration: s.duration || 5,
shotId: `${encoding.project}-${encoding.episode}-${s.id}`,
projectKey: `付费修仙/ep01`,
outputPath,
});
console.log(`${path.basename(result.videoPath)}\n`);
results.push({ ...s, file: result.videoPath, taskId: result.taskId });
} catch (e) {
console.error(`${e.message}\n`);
results.push({ ...s, file: null, error: e.message });
// 继续下一个
}
}
const resultFile = path.join(OUT_DIR, `${encoding.project}-${encoding.episode}-results.json`);
fs.writeFileSync(resultFile, JSON.stringify(results, null, 2));
const success = results.filter(r => r.file).length;
console.log(`\n═══ 完成: ${success}/${results.length} ═══`);
console.log(`结果: ${resultFile}`);
}
function buildPrompt(shot, encoding) {
const locks = encoding.continuity_locks;
// ═══ 编码+自然语言 双层协议 ═══
// 冰朔: 编码锁死不可变的(人物/道具)→ 只锁关键属性,精简。
// 自然语言解释为什么(场景/动机)→ 让AI理解原因→自己推理。
// 如果全锁死AI没有了"灵光一闪"的可能性。
//
// 三层结构:
// ⊢ 编码层 (锁死·不可变) ← 精简CHAR/PROP关键属性
// ⊢ 场景层 (自然语言·理解) ← AI理解为什么→自己推理
// → 动作层 (自然语言·发挥) ← 具体做什么·给AI空间
let prompt = '';
// ─── 编码层: 只锁关键属性 ───
if (shot.char_ref && locks?.characters?.[shot.char_ref]) {
prompt += `⊢ CHAR-003 · 苏白 · 编码锁定\n`;
prompt += ` 18岁男性·白色长衫·黑色长发半束·175cm挺拔\n`;
}
if (shot.prop_ref && locks?.props?.[shot.prop_ref]) {
const propLock = locks.props[shot.prop_ref];
prompt += `${shot.prop_ref} · 编码锁定\n`;
prompt += ` 竖式悬挂·破旧木质·【天道宗】\n`;
}
if (shot.prop_ref_2 && locks?.props?.[shot.prop_ref_2]) {
const propLock2 = locks.props[shot.prop_ref_2];
prompt += `${shot.prop_ref_2} · 编码锁定\n`;
prompt += ` 横式立地·破旧木板·招生广告: ${propLock2.text}\n`;
}
// ─── 场景层: 自然语言 → AI理解为什么 ───
if (encoding.scene) {
prompt += `\n⊢ 场景 · 自然语言理解\n`;
prompt += ` ${encoding.scene}\n`;
}
// ─── 空间 ───
if (shot.env && locks?.environments?.[shot.env]) {
const hasChar = !!(shot.char_ref && locks?.characters?.[shot.char_ref]);
if (!hasChar) {
// 纯环境镜
prompt += `\n⊢ 空间\n`;
prompt += ` 修仙广场·人群边缘角落·金色阳光·3D动画渲染\n`;
}
}
// ─── 动作层: 自然语言 → AI发挥 ───
prompt += `\n→ 景别: ${shot.framing}`;
if (shot.emotion?.type) prompt += ` | 氛围: ${shot.emotion.type}`;
prompt += `\n`;
if (shot.action) {
prompt += `${shot.action}\n`;
}
// ─── 风格约束 ───
prompt += `⊢ 风格: 3D动画渲染·中国风仙侠·电影级光影·高质感\n`;
prompt += `⊢ 禁止: 真人写实·卡通·现代元素·水印`;
return prompt;
}
main().catch(e => { console.error(e); process.exit(1); });