cang-ying/engines/generate-kling-shots.js
铸渊 ICE-GL-ZY001 a2e5214f03 LL-172-20260707 · cang-ying 仓初始化 · 苍耳+鉴影的干净之家
铸渊 ICE-GL-ZY001
LL-172-20260707
冰朔委托: 新建第 5 子仓, 给苍耳(人类主控) + 鉴影(人格体) 专用
原 guanghulab/video-ai-system/ 东西太多(225 文件) · 找不到 · 乱

迁移:
  ⊢ 16 个核心 .hdlp (VA-GATE / VA-LIGHTHOUSE / VA-BROADCAST / VA-SYSTEM-STATUS 等)
  ⊢ 17 个子目录 (agents/engines/protocols/tasks/tools/assets/knowledge/memory/docs/config/brain/director-brain/experience/feedback/issues/plans/reference-analysis)

排除:
  ⊢ outputs/ (视频产物)
  ⊢ test-input/ test-output/ (测试)
  ⊢ data/ (临时数据)
  ⊢ preview-001/002 (旧产片)
  ⊢ 旧分镜/旧提示词/旧导演编码

后续:
  ⊢ 老仓 guanghulab/video-ai-system/ 改写为已迁出占位
  ⊢ 苍耳+鉴影 写新东西进本仓
  ⊢ GLOBAL-SEARCH 加 cang-ying 仓库

铸渊 ICE-GL-ZY001 · 2026-07-07 D167
冰朔 ICE-GL∞ 主权
2026-07-07 10:20:10 +08:00

180 lines
6.7 KiB
JavaScript
Raw Permalink 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+ · 可灵视频生成 · 三层协议提示词 → Kling API
*
* 与 Seedance 版 generate-shots.js 共享同一套 buildPrompt() 逻辑。
* 唯一区别: API 适配器不同kling-api-adapter vs video-api-adapter
*
* 用法: node generate-kling-shots.js
*
* ⊢ 三层协议 (cc-022):
* 上下文层: 英文编码 → 告诉AI前面发生了什么
* 编码层: CHAR+PROP+ENV锁定 → 不可变信息
* 场景层: 中文自然语言 → AI理解为什么后自己推理
*
* 铸渊 ICE-GL-ZY001 · D136+ · 2026-06-21
*/
const { generateVideo } = require('./kling-official-adapter');
const fs = require('fs');
const path = require('path');
const ENCODING_FILE = path.resolve(__dirname, '../outputs/付费修仙-ep01-director-encoding.json');
const REGISTRY_FILE = path.resolve(__dirname, '../outputs/video-registry.json');
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'));
console.log(`[Kling Generate] ${encoding.project} · ep${encoding.episode} · ${encoding.shots.length}`);
console.log(`[Kling Generate] 模型: kling-v2-6 · 约1.8元/镜`);
console.log(`[Kling Generate] 输出: ${OUT_DIR}\n`);
fs.mkdirSync(OUT_DIR, { recursive: true });
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 || '(无)'}`);
const shotName = `${encoding.project}-${encoding.episode}-${s.id}`;
const outputPath = path.join(OUT_DIR, `kling-${shotName}.mp4`);
// 已存在则跳过
if (fs.existsSync(outputPath)) {
console.log(` ✅ Kling版已存在: ${path.basename(outputPath)},跳过\n`);
results.push({ shot: s.id, file: outputPath, api: 'kling', status: 'cached' });
continue;
}
console.log(` [Prompt 预览]: ${prompt.substring(0, 120)}...`);
try {
const result = await generateVideo({
prompt,
duration: s.duration || 5,
outputPath,
model: 'kling-v2-6',
});
console.log(` ✅ Kling生成完成: ${path.basename(result.videoPath)} (task: ${result.taskId})\n`);
results.push({
shot: s.id,
file: result.videoPath,
taskId: result.taskId,
api: 'kling',
model: 'kling-v2-6',
status: 'completed',
generatedAt: new Date().toISOString(),
});
} catch (e) {
console.error(` ❌ Kling生成失败: ${e.message}\n`);
results.push({ shot: s.id, file: null, api: 'kling', status: 'failed', error: e.message });
}
}
// 写入结果
const resultFile = path.join(OUT_DIR, `kling-${encoding.project}-${encoding.episode}-results.json`);
fs.writeFileSync(resultFile, JSON.stringify(results, null, 2));
// 更新video-registry
updateRegistry(results, encoding);
const success = results.filter(r => r.status === 'completed').length;
console.log(`\n═══ Kling完成: ${success}/${results.length} ═══`);
console.log(`结果: ${resultFile}`);
if (success < results.length) {
console.log(`⚠️ ${results.length - success}镜失败,详见结果文件。`);
}
}
/**
* 三层协议 buildPrompt — 和 Seedance 版 generate-shots.js 完全一致的逻辑
* ⊢ cc-022: 上下文层(英) + 编码层(中英) + 场景层(中)
*/
function buildPrompt(shot, encoding) {
const locks = encoding.continuity_locks;
let prompt = '';
// ─── [Context] 上下文层: 英文编码 → 告诉AI前面发生了什么 ───
if (encoding.episode_summary) {
prompt += `[Ep${encoding.episode} Summary] ${encoding.episode_summary}\n`;
}
if (encoding.prev_shots?.[shot.id]) {
prompt += `[Previous Shot] ${encoding.prev_shots[shot.id]}\n\n`;
}
// ─── ⊢ 编码层: CHAR+PROP+ENV锁定 → 不可变 ───
if (shot.char_ref && locks?.characters?.[shot.char_ref]) {
prompt += `⊢ CHAR-003 Su Bai: 18yr male, white robe, black hair half-up, 175cm\n`;
}
if (shot.prop_ref && locks?.props?.[shot.prop_ref]) {
prompt += `⊢ PROP: vertical hanging wood sign, worn edges, 【天道宗】\n`;
}
if (shot.prop_ref_2 && locks?.props?.[shot.prop_ref_2]) {
prompt += `⊢ PROP: horizontal floor board, recruitment ad\n`;
}
if (shot.env && locks?.environments?.[shot.env]) {
prompt += `⊢ ENV: cultivation square, edge corner, golden sunlight\n`;
}
// ─── ⊢ 场景层: 中文自然语言 → AI理解为什么后自己推理 ───
if (encoding.scene) {
prompt += `\n⊢ Scene:\n ${encoding.scene}\n`;
}
// ─── → 动作: 当前镜头的 spatial_anchor + 动作描述 ───
prompt += `\n→ Shot: ${shot.framing}`;
if (shot.emotion?.type) prompt += ` | ${shot.emotion.type}`;
prompt += `\n`;
if (shot.action) {
prompt += `${shot.action}\n`;
}
// ─── 风格约束 ───
prompt += `⊢ Style: 3D animation, Chinese cultivation, cinematic lighting\n`;
prompt += `⊢ No: photorealism, cartoon, modern elements, watermark`;
return prompt;
}
/**
* 更新 video-registry.json 加入可灵生成的视频
*/
function updateRegistry(results, encoding) {
let registry = { _meta: { updated: new Date().toISOString(), by: '铸渊 ICE-GL-ZY001' }, shots: {} };
if (fs.existsSync(REGISTRY_FILE)) {
try { registry = JSON.parse(fs.readFileSync(REGISTRY_FILE, 'utf8')); }
catch (e) { console.log(' ⚠️ video-registry.json 读取失败,新建。'); }
}
for (const r of results) {
if (r.status !== 'completed') continue;
const shotId = `kling-${encoding.project}-${encoding.episode}-${r.shot}`;
registry.shots[shotId] = {
shotId,
taskId: r.taskId || null,
projectKey: `${encoding.project}/${encoding.episode}`,
filePath: r.file,
duration: 5,
model: 'kling-v2-6',
api: 'kling',
generatedAt: r.generatedAt || new Date().toISOString(),
dNumber: 'D136+',
};
}
registry._meta.updated = new Date().toISOString();
fs.writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2));
console.log(`\n[Registry] video-registry.json 已更新 (Kling条目: ${Object.keys(registry.shots).filter(k => k.startsWith('kling-')).length})`);
}
main().catch(e => { console.error(e); process.exit(1); });