guanghulab/video-ai-system/engines/kling-api-adapter.js
冰朔 083030b453
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
D136+: Kling可灵API适配器 + S1-03版权过滤修复
kling-api-adapter.js: 接入可灵视频生成API
  endpoint: api.klingapi.com/v1/videos/text2video
  model: kling-v2.6-pro (/bin/zsh.049/s ≈ 1.8元/5秒)
  ️价格对比: 可灵 1.8元 vs Seedance 5元+ (省1/3)

导演编码修复: S1-03去广告文本→版权过滤拦截解决
2026-06-21 18:20:53 +08:00

108 lines
3.4 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.

/**
* 光湖视频AI系统 · Kling 可灵 API 适配器
* D136+ · 铸渊 ICE-GL-ZY001 · 2026-06-21
*
* 接入可灵AI视频生成API。价格: kling-v2.6-pro $0.049/s (约1.8元/5秒)
* 非Seedance——用于低成本验证提示词和流程
*/
const fs = require('fs');
const path = require('path');
const https = require('https');
// API配置
const API_KEY = 'Ze8Bas4_xw1JsPLmfULgMyykvZelFSwv57Hycc6SJm0';
const BASE_URL = 'api.klingapi.com';
const POLL_INTERVAL = 3000; // 可灵更快3秒轮询
const MAX_POLL = 60; // 最多3分钟
// 输出路径
const OUT_DIR = (() => {
const jzao = '/Volumes/JZAO/铸渊-ICE-GL-ZY001/OUT-输出/视频/zai-fu-fei-xiu-xian/ep01';
return fs.existsSync(jzao) ? jzao : path.resolve(__dirname, '../outputs/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(JSON.parse(data)); }
catch (e) { resolve({ raw: data }); }
});
});
req.on('error', reject);
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 generateKlingVideo({ prompt, duration = 5, outputPath, model = 'kling-v2.6-pro' }) {
console.log('[Kling] 提交文生视频...');
// 提交
const submit = await apiRequest('POST', '/v1/videos/text2video', {
model,
prompt,
duration: Math.min(duration, 10),
aspect_ratio: '16:9',
mode: 'standard',
});
if (!submit.task_id) {
throw new Error(`提交失败: ${JSON.stringify(submit)}`);
}
console.log(`[Kling] 任务: ${submit.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/${submit.task_id}`);
if (status.status === 'completed') {
const videoUrl = status.video_url || status.output?.video_url;
if (!videoUrl) throw new Error('任务完成但无视频URL');
const out = outputPath || path.join(OUT_DIR, `kling-${submit.task_id}.mp4`);
fs.mkdirSync(path.dirname(out), { recursive: true });
console.log(`[Kling] 下载: ${videoUrl.substring(0,60)}...`);
await downloadFile(videoUrl, out);
console.log(`[Kling] ✅ ${path.basename(out)}`);
return { videoPath: out, taskId: submit.task_id };
}
if (status.status === 'failed') {
throw new Error(`生成失败: ${status.error || JSON.stringify(status)}`);
}
if (i % 10 === 0) console.log(`[Kling] 生成中... (${i}/${MAX_POLL})`);
}
throw new Error('超时');
}
module.exports = { generateKlingVideo };