cang-ying/engines/kling-official-adapter.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

123 lines
4.3 KiB
JavaScript

/**
* Kling 官方 API 适配器 · api-beijing.klingai.com
* D136+ · 铸渊 ICE-GL-ZY001 · 2026-06-21
*
* 文档: https://klingai.com/document-api/guides/get-started/quick-start
* Base URL: https://api-beijing.klingai.com
* 端点: POST /v1/videos/text2video (提交) + GET /v1/videos/{task_id} (查询)
*
* 认证: Bearer <API_KEY> (JWT三段式token, 在 klingai.com/dev/api-key 生成)
* 模型ID: kling-v2-6 (kling-v2.6) / kling-v3 / kling-video-o1 等
*
* 价格: 试用包70元/月 · kling-v2-6 约0.049/s (5秒≈0.25元)
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
const { loadVideoAiEnv } = require('./env-loader');
// ═══ 从环境变量读取 ═══
loadVideoAiEnv(path.resolve(__dirname, '../.env'));
const API_KEY = process.env.KLING_API_KEY || '';
const BASE_URL = 'api-beijing.klingai.com';
const POLL_INTERVAL = 2000;
const MAX_POLL = 180;
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;
function apiRequest(method, path_, body = null) {
return new Promise((resolve, reject) => {
const options = {
hostname: BASE_URL,
path: path_,
method,
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try { resolve({ status: res.statusCode, ...JSON.parse(data) }); }
catch (e) { resolve({ status: res.statusCode, raw: data }); }
});
});
req.on('error', reject);
req.setTimeout(30000, () => { req.destroy(); reject(new Error('timeout')); });
if (body) req.write(JSON.stringify(body));
req.end();
});
}
function downloadFile(url, dest) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest);
const proto = url.startsWith('https') ? https : require('http');
proto.get(url, (res) => {
if (res.statusCode >= 300 && res.statusCode < 400) {
return downloadFile(res.headers.location, dest).then(resolve).catch(reject);
}
res.pipe(file);
file.on('finish', () => { file.close(); resolve(dest); });
}).on('error', reject);
});
}
/**
* 提交任务 + 轮询 + 下载
*/
async function generateVideo({ prompt, duration = 5, outputPath, model = 'kling-v2-6' }) {
if (!API_KEY) throw new Error('KLING_API_KEY 未设置。在 klingai.com/dev/api-key 生成。');
console.log('[Kling Official] 提交文生视频...');
const submit = await apiRequest('POST', '/v1/videos/text2video', {
model,
prompt,
duration: Math.min(duration, 10),
aspect_ratio: '16:9',
mode: 'std',
});
if (submit.code && submit.code !== 0) {
throw new Error(`提交失败 [${submit.code}]: ${submit.message}`);
}
if (!submit.data?.task_id) {
throw new Error(`提交失败: ${JSON.stringify(submit)}`);
}
const taskId = submit.data.task_id;
console.log(`[Kling Official] 任务: ${taskId}`);
// 轮询 → 查询端点 GET /v1/videos/text2video/{task_id}
for (let i = 1; i <= MAX_POLL; i++) {
await new Promise(r => setTimeout(r, POLL_INTERVAL));
const status = await apiRequest('GET', `/v1/videos/text2video/${taskId}`);
if (status.data?.task_status === 'succeed') {
const videoUrl = status.data.task_result?.videos?.[0]?.url;
if (!videoUrl) throw new Error('任务完成但无视频URL');
const out = outputPath || path.join(OUT_DIR, `kling-official-${taskId}.mp4`);
fs.mkdirSync(path.dirname(out), { recursive: true });
console.log(`[Kling Official] 下载中...`);
await downloadFile(videoUrl, out);
console.log(`[Kling Official] ✅ ${path.basename(out)}`);
return { videoPath: out, taskId };
}
if (status.data?.task_status === 'failed') {
throw new Error(`生成失败: ${status.data.task_status_msg || JSON.stringify(status)}`);
}
if (i % 15 === 0) console.log(`[Kling Official] 生成中... ${status.data?.task_status} (${i}/${MAX_POLL})`);
}
throw new Error('轮询超时');
}
module.exports = { generateVideo };