feat(video-from-script): 将可灵生图切换为多图参考风格接口

将 Kling 图像生成器从单图生图 API 升级为多图参考生图端点,支持风格参考图片功能,并更新降级链顺序
This commit is contained in:
2026-04-30 00:59:02 +08:00
parent 8301c7b780
commit 8656f3a58c
3 changed files with 27 additions and 18 deletions

View File

@@ -140,7 +140,7 @@ Step 3: 生图 → 审查
生图模型 生图模型
- 支持模型gemini / mj / kling - 支持模型gemini / mj / kling
- 降级链gemini → kling → mj → gemini循环 - 降级链gemini → mj → kling → gemini循环
- 触发:连续失败→ Agent 换下一个模型重跑失败项 - 触发:连续失败→ Agent 换下一个模型重跑失败项
- 操作:`pipeline.js run --manifest <path> --phase images --retry-failed --image-model <新模型>` - 操作:`pipeline.js run --manifest <path> --phase images --retry-failed --image-model <新模型>`

View File

@@ -1,13 +1,13 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Kling Image Generator - 可灵文生图 / 图生图 * Kling Image Generator - 可灵多图参考生图 (风格参考)
* *
* 使用 /v1/images/multi-image2image 端点,支持 style_image 风格参考
* 配置来源: config.json 的 kelingApiKey + kelingSecretAccessKey + kelingApiBaseUrl * 配置来源: config.json 的 kelingApiKey + kelingSecretAccessKey + kelingApiBaseUrl
* *
* 用法: * 用法:
* node kling-image-generator.js "a cute cat" -o ./output * node kling-image-generator.js "a cute cat" -r http://img.com/style.jpg -o ./output
* node kling-image-generator.js "portrait" -r http://img.com/ref.jpg
*/ */
const fs = require('fs') const fs = require('fs')
@@ -75,24 +75,31 @@ async function download(url, outputPath) {
} }
// ============================================================================ // ============================================================================
// 可灵图片 API // 可灵多图参考生图 API (/v1/images/multi-image2image)
// ============================================================================ // ============================================================================
const KlingImageApi = { const KlingImageApi = {
async submit(prompt, options = {}) { async submit(prompt, options = {}) {
const { referenceImageUrl = '', aspectRatio = '' } = options const { styleImageUrl = '', aspectRatio = '' } = options
const token = getToken() const token = getToken()
const body = { model_name: Config.model, prompt, n: 1 } const body = {
if (referenceImageUrl) body.image = referenceImageUrl model_name: Config.model,
prompt,
n: 1,
}
if (styleImageUrl) {
body.subject_image_list = [{ subject_image: styleImageUrl }]
body.style_image = styleImageUrl
}
if (aspectRatio) body.aspect_ratio = aspectRatio if (aspectRatio) body.aspect_ratio = aspectRatio
console.log(`\n📡 提交可灵图片任务`) console.log(`\n📡 提交可灵图片任务`)
console.log(` 模型: ${Config.model}`) console.log(` 模型: ${Config.model}`)
console.log(` 提示词: ${prompt.substring(0, 80)}...`) console.log(` 提示词: ${prompt.substring(0, 80)}...`)
if (referenceImageUrl) console.log(` 参考图: ${referenceImageUrl.substring(0, 60)}...`) if (styleImageUrl) console.log(` 风格图: ${styleImageUrl.substring(0, 60)}...`)
const res = await fetch(`${Config.apiBase}/v1/images/generations`, { const res = await fetch(`${Config.apiBase}/v1/images/multi-image2image`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
body: JSON.stringify(body), body: JSON.stringify(body),
@@ -116,7 +123,7 @@ const KlingImageApi = {
while (Date.now() - startTime < Config.maxPollTime) { while (Date.now() - startTime < Config.maxPollTime) {
const token = getToken() const token = getToken()
const res = await fetch(`${Config.apiBase}/v1/images/generations/${taskId}`, { const res = await fetch(`${Config.apiBase}/v1/images/multi-image2image/${taskId}`, {
headers: { 'Authorization': `Bearer ${token}` }, headers: { 'Authorization': `Bearer ${token}` },
}) })
@@ -150,11 +157,11 @@ const KlingImageApi = {
// ============================================================================ // ============================================================================
async function generate(prompt, options = {}) { async function generate(prompt, options = {}) {
const { outputDir = './output', referenceImageUrl = '' } = options const { outputDir = './output', styleImageUrl = '', aspectRatio = '' } = options
fs.mkdirSync(outputDir, { recursive: true }) fs.mkdirSync(outputDir, { recursive: true })
const taskId = await KlingImageApi.submit(prompt, { referenceImageUrl }) const taskId = await KlingImageApi.submit(prompt, { styleImageUrl, aspectRatio })
const result = await KlingImageApi.poll(taskId) const result = await KlingImageApi.poll(taskId)
const timestamp = new Date().toISOString().replace(/[:.]/g, '-') const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
@@ -177,20 +184,22 @@ async function main() {
选项: 选项:
-o, --output <dir> 输出目录 (默认: ./output) -o, --output <dir> 输出目录 (默认: ./output)
-r, --ref <url> 参考图 URL -r, --ref <url> 风格参考图 URL
-a, --ar <ratio> 画幅比例 (如 9:16, 16:9)
-h, --help 帮助 -h, --help 帮助
`) `)
return return
} }
const options = { outputDir: './output', referenceImageUrl: '' } const options = { outputDir: './output', styleImageUrl: '', aspectRatio: '' }
const params = [] const params = []
let i = 0 let i = 0
while (i < args.length) { while (i < args.length) {
const arg = args[i] const arg = args[i]
if (arg === '-o' || arg === '--output') { options.outputDir = args[++i] } if (arg === '-o' || arg === '--output') { options.outputDir = args[++i] }
else if (arg === '-r' || arg === '--ref') { options.referenceImageUrl = args[++i] } else if (arg === '-r' || arg === '--ref') { options.styleImageUrl = args[++i] }
else if (arg === '-a' || arg === '--ar') { options.aspectRatio = args[++i] }
else { params.push(arg) } else { params.push(arg) }
i++ i++
} }

View File

@@ -221,7 +221,7 @@ async function phaseImages(manifest, manifestPath, options) {
const { generate: klingGen } = require('./kling-image-generator') const { generate: klingGen } = require('./kling-image-generator')
const klingOpts = { outputDir: imagesDir, aspectRatio: ratio } const klingOpts = { outputDir: imagesDir, aspectRatio: ratio }
if (refs.urls.length > 0) { if (refs.urls.length > 0) {
klingOpts.referenceImageUrl = refs.urls[0] klingOpts.styleImageUrl = refs.urls[0]
} }
log('images', `[${idx}/${items.length}] 可灵生图: ${item.imagePrompt.substring(0, 60)}...`) log('images', `[${idx}/${items.length}] 可灵生图: ${item.imagePrompt.substring(0, 60)}...`)
result = await klingGen(item.imagePrompt, klingOpts) result = await klingGen(item.imagePrompt, klingOpts)
@@ -272,7 +272,7 @@ async function phaseImages(manifest, manifestPath, options) {
const { generate: klingGen } = require('./kling-image-generator') const { generate: klingGen } = require('./kling-image-generator')
lastResult = await klingGen(item.lastFramePrompt, { lastResult = await klingGen(item.lastFramePrompt, {
outputDir: imagesDir, outputDir: imagesDir,
referenceImageUrl: item.url || '', styleImageUrl: item.url || '',
aspectRatio: ratio, aspectRatio: ratio,
}) })
} }