guanghulab/video-ai-system/engines/generate-shots.js
冰朔 4c3623344c
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
D136+: 分层提示词引擎 — L0主体优先·不可稀释
buildPrompt()重构提示词结构:
  L0 主体: 人物+道具锁在最前面 → AI注意力第一落点
  L1 空间: 克制描述 → '背景:广场边缘角落·远处人群·金色阳光'
      有主体时去掉灵霄宗鎏金大字→不抢人物焦点
  L2 动作: 具体行为
  L3 风格: 移到末尾 → '真人写实·电影光影·亚洲面孔'

冰朔: '用编码告诉他这是什么意思'
→ CHAR-003不再被埋在后面 → AI必须先读懂人物再读环境
2026-06-21 17:07:19 +08:00

140 lines
5.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;
const parts = [];
// === D136+ 分层提示词引擎 ===
// 冰朔: "用编码告诉他这是什么意思" → HLDP编号不是标签是优先级排序
//
// 提示词分层:
// L0 主体(人物+道具) ← AI注意力第一落点·不可稀释
// L1 空间(克制描述) ← 只给必要信息·不抢人物焦点
// L2 动作 ← 具体行为
// L3 风格/约束 ← 全局修饰
let desc = '';
// ─── L0: 主体锁定 ───
// 人物和道具的描述放在最前面确保AI第一注意力在这里
const l0Parts = [];
if (shot.char_ref && locks?.characters?.[shot.char_ref]) {
const charLock = locks.characters[shot.char_ref];
l0Parts.push(charLock.locked_desc);
}
if (shot.prop_ref && locks?.props?.[shot.prop_ref]) {
const propLock = locks.props[shot.prop_ref];
l0Parts.push(propLock.locked_desc);
}
if (shot.prop_ref_2 && locks?.props?.[shot.prop_ref_2]) {
const propLock2 = locks.props[shot.prop_ref_2];
l0Parts.push(propLock2.locked_desc);
}
// 如果没人也没道具(纯环境镜)→不生成L0
if (l0Parts.length > 0) {
desc += l0Parts.join('。');
}
// ─── L1: 空间定位 ───
// 克制描述。不给灵霄宗鎏金大字——除非是S1-01建立镜
if (shot.env && locks?.environments?.[shot.env]) {
const envLock = locks.environments[shot.env];
// 有主体时用简短版·没有主体时用完整版
if (l0Parts.length > 0) {
// 简短版: 去掉"灵霄宗鎏金大字"细节,只留位置和光线
desc += `。背景: 修仙广场边缘角落,人群远处,金色阳光`;
} else {
desc += envLock.locked_desc;
}
}
// ─── L2: 动作 ───
if (shot.action) {
desc += `${shot.action}`;
}
// 关键信息追加
if (shot.text_elements) desc += `。画面中可见文字: ${shot.text_elements}`;
if (shot.prop_state) desc += `${shot.prop_state}`;
parts.push(desc);
// ─── L3: 风格 ───
parts.push('景别: ' + shot.framing);
if (shot.emotion?.type) parts.push('氛围: ' + shot.emotion.type);
// 风格锁定(不再是第一行)
parts.push('真人写实风格,电影级光影,亚洲面孔');
parts.push('不要卡通·不要3D渲染·不要现代元素·不要水印·不要字幕');
return parts.join('。');
}
main().catch(e => { console.error(e); process.exit(1); });