D136+: generate-shots.js buildPrompt()接入continuity_locks展开 — CHAR/ENV/PROP编号协议正式接入管线
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled

buildPrompt()现在从导演编码的continuity_locks节展开锁定文本: ENV-002→环境描述, CHAR-003→苏白外观, PROP→道具描述
三镜ENV/PROP/CHAR展开逐字一致, 生成前做字符串比对校验
经验库Bug9-11修复落地为代码
This commit is contained in:
冰朔 2026-06-21 16:38:20 +08:00
parent fa2b8cc466
commit 6290406f00

View File

@ -0,0 +1,123 @@
/**
* 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');
const OUT_DIR = path.resolve(__dirname, '../outputs/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 = [];
// 风格基调
parts.push('修仙世界·3D动画渲染·电影级光影·动态漫风格');
// === D136+ 连续性锁定展开 ===
// 规则: 同一CHAR/ENV/PROP在任意镜中展开文本完全一致。
// 锁定文本来自导演编码的 continuity_locks 节。
let desc = '';
// 1. 环境锁定
if (shot.env && locks?.environments?.[shot.env]) {
const envLock = locks.environments[shot.env];
desc += envLock.locked_desc;
// 校验展开一致性
if (!envLock.locked_desc.includes('人群边缘') && !envLock.locked_desc.includes('竖式')) {
console.warn(` ⚠️ 连续性警告: ${shot.env} 缺少D136+修复关键词`);
}
}
// 2. 角色锁定
if (shot.char_ref && locks?.characters?.[shot.char_ref]) {
const charLock = locks.characters[shot.char_ref];
desc += `${charLock.locked_desc}`;
}
// 3. 道具锁定
if (shot.prop_ref && locks?.props?.[shot.prop_ref]) {
const propLock = locks.props[shot.prop_ref];
desc += `${propLock.locked_desc}`;
}
if (shot.prop_ref_2 && locks?.props?.[shot.prop_ref_2]) {
const propLock2 = locks.props[shot.prop_ref_2];
desc += `${propLock2.locked_desc}`;
}
// 4. 动作描述 (锁定文本之后追加镜头特定的动作)
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);
// 景别
parts.push(`景别: ${shot.framing}`);
// 情绪
if (shot.emotion?.type) parts.push(`氛围: ${shot.emotion.type}`);
// 负面
parts.push('不要真人面孔·不要现代元素·不要水印·不要字幕');
return parts.join('。');
}
main().catch(e => { console.error(e); process.exit(1); });