buildPrompt()现在从导演编码的continuity_locks节展开锁定文本: ENV-002→环境描述, CHAR-003→苏白外观, PROP→道具描述 三镜ENV/PROP/CHAR展开逐字一致, 生成前做字符串比对校验 经验库Bug9-11修复落地为代码
124 lines
4.2 KiB
JavaScript
124 lines
4.2 KiB
JavaScript
/**
|
|
* 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); });
|