guanghulab/video-ai-system/engines/generate-shots.js
冰朔 13f45ab614 D136+: 风格切换 — 真人写实→3D动画渲染·中国风仙侠
generate-shots.js: 风格约束从真人→3D
GLOBAL-NAV: 记录风格切换决策
STATUS.hdlp: 同步风格变更
导航: 3D验证图HLDP编码通过

根源: 冰朔决策在先但未写入导航→铸渊生成真人图
2026-06-21 17:16:57 +08:00

150 lines
6.0 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;
// ═══ D136+ HLDP编码提示词引擎 ═══
// 冰朔: "编码的结构肯定有更高的权重。AI拿到编码后去组合编码
// 然后照着编码去找你后面给他翻译的汉字。"
//
// 原理: HLDP的 ⊢ / · / | / → 这些标记符号天然构成层级结构。
// AI模型的注意力机制对结构化输入敏感——
// 每个 ⊢ 是一个锚点,每个 · 是一个属性分支。
// 编码权重 > 自然语言翻译权重。
//
// 结构:
// ⊢ CHAR · 锁定 ← 最高权重·不可变
// ⊢ PROP · 锁定 ← 紧随其后
// ⊢ 空间 ← 克制描述
// → 动作(自然语言翻译) ← 编码后的汉字辅助
let prompt = '';
// ─── 编码区: CHAR锁定 ───
if (shot.char_ref && locks?.characters?.[shot.char_ref]) {
const charLock = locks.characters[shot.char_ref];
prompt += `⊢ CHAR-003 · 苏白 · 锁定: 全镜一致不可变\n`;
prompt += ` 外貌: 18岁亚洲男性 | 五官俊秀·剑眉·明亮大眼 | 自信笑容露齿\n`;
prompt += ` 服饰: 白色长衫·腰间束带·袖口挽起 | 黑色长发半束半披\n`;
prompt += ` 身形: 175cm·挺拔·肩膀开阔\n`;
prompt += ` 气质: 开朗·自信·略带痞气·穷但不自卑\n`;
prompt += charLock.locked_desc + '\n';
}
// ─── 编码区: PROP锁定 ───
if (shot.prop_ref && locks?.props?.[shot.prop_ref]) {
const propLock = locks.props[shot.prop_ref];
prompt += `⊢ PROP · ${shot.prop_ref} · 锁定: 不可变\n`;
prompt += ` 方向: ${propLock.orientation} | 材质: ${propLock.material}\n`;
prompt += ` 文字: ${propLock.text}\n`;
prompt += propLock.locked_desc + '\n';
}
if (shot.prop_ref_2 && locks?.props?.[shot.prop_ref_2]) {
const propLock2 = locks.props[shot.prop_ref_2];
prompt += `⊢ PROP · ${shot.prop_ref_2} · 锁定: 不可变\n`;
prompt += ` 方向: ${propLock2.orientation} | 材质: ${propLock2.material}\n`;
prompt += ` 文字: ${propLock2.text}\n`;
prompt += propLock2.locked_desc + '\n';
}
// ─── 编码区: 空间 ───
if (shot.env && locks?.environments?.[shot.env]) {
const hasChar = !!(shot.char_ref && locks?.characters?.[shot.char_ref]);
if (hasChar) {
// 有主体时的克制空间描述
prompt += `⊢ 空间 · 锁定: 不可喧宾夺主\n`;
prompt += ` 位置: 修仙广场·人群边缘角落\n`;
prompt += ` 光线: 白天·金色阳光\n`;
prompt += ` 景别: ${shot.framing}\n`;
} else {
// 纯环境镜使用完整锁定文本
prompt += `⊢ 空间 · 锁定: ENV-002百宗会广场\n`;
prompt += locks.environments[shot.env].locked_desc + '\n';
}
}
// ─── 翻译区: 动作描述 ───
if (shot.action) {
prompt += `${shot.action}\n`;
}
// 文字标注
if (shot.text_elements) {
prompt += `→ 画面中可见文字: ${shot.text_elements}\n`;
}
// ─── 风格约束 ───
prompt += `⊢ 风格约束:\n`;
prompt += ` 3D动画渲染·中国风仙侠·电影级光影·高质感\n`;
prompt += ` 景别: ${shot.framing}`;
if (shot.emotion?.type) prompt += ` | 氛围: ${shot.emotion.type}`;
prompt += `\n`;
prompt += ` 禁止: 真人写实·卡通·现代元素·水印·字幕`;
return prompt;
}
main().catch(e => { console.error(e); process.exit(1); });