guanghulab/video-ai-system/engines/generate-kling-shots.js
冰朔 0f87865dc1
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
D136+: 视频AI系统TCS通感大脑 + 范式转移
cc-023: 铸渊在视频AI系统里的TCS映射·不是规则是感受
TCS-BRAIN.hdlp: 进入视频AI系统强制加载·先感受再HLDP
kling-official-adapter.js: api-beijing.klingai.com·已验证通
generate-kling-shots.js: 三层协议→Kling官方API
yunwu-api-adapter.js: yunwu.ai备用·路由待修复
video-registry.json: Kling官方3镜已注册

⊢ 根源: 文生视频锁不住角色—CFG机制天花板
⊢ 新范式: 可灵出参考图→Seedance图生视频→铸渊之眼帧分析
⊢ 成本: 可灵0.25元/图+Seedance 5元/镜
2026-06-21 21:42:19 +08:00

180 lines
6.7 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+ · 可灵视频生成 · 三层协议提示词 → 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); });