feat(video-from-script): 升级可灵视频生成使用官方 API 并添加失败重试机制

- 使用 AK/SK → JWT (HMAC-SHA256) 鉴权替代旧版 API Key
- 支持多种凭证来源:~/.config/kling/.credentials 或 config.json
- 更新 API 端点至官方规范 (v1/videos/image2video)
- 添加 `--retry-failed` 参数支持失败 item 状态重置和重试
- 更新 manifest 文档添加状态机和失败处理说明
- 调整模型名称和参数格式以匹配新 API
This commit is contained in:
2026-04-29 21:56:47 +08:00
parent 0b3ab3a2aa
commit 5619d753cc
4 changed files with 340 additions and 98 deletions

View File

@@ -531,6 +531,46 @@ async function runPipeline(manifestPath, options) {
manifest.pipeline = { phases: {} }
}
// --retry-failed: 重置失败 item 状态,允许重新处理
if (options.retryFailed) {
let resetCount = 0
for (const item of manifest.items) {
if (item.status === 'failed' || item.status === 'partial') {
// 根据 item 是否有 url 判断该重置到哪个阶段
if (item.url && item.videoPrompt && !item.video) {
// 图片已生成、有视频提示词、但没视频 → 重置为可生视频
item.status = 'done'
item.error = ''
resetCount++
} else if (!item.url && item.imagePrompt) {
// 没有图片但有提示词 → 重置为可生图
item.status = 'pending'
item.error = ''
resetCount++
}
}
}
// 重置对应阶段状态
if (phases.includes('videos')) {
const hasVideoItems = manifest.items.some(it => it.status === 'done' && it.url && it.videoPrompt && !it.video)
if (hasVideoItems) manifest.pipeline.phases.videos = 'pending'
}
if (phases.includes('images')) {
const hasImageItems = manifest.items.some(it => !it.status || it.status === 'pending')
if (hasImageItems) manifest.pipeline.phases.images = 'pending'
}
if (phases.includes('upload')) {
manifest.pipeline.phases.upload = 'pending'
}
if (phases.includes('tts')) {
manifest.pipeline.phases.tts = 'pending'
}
if (resetCount > 0) {
log('pipeline', `重置 ${resetCount} 个失败 item (--retry-failed)`)
saveManifest(manifestPath, manifest)
}
}
log('pipeline', `阶段: ${phases.join(' → ')}`)
const phaseHandlers = {
@@ -1007,6 +1047,7 @@ function parseArgs(argv) {
else if (argv[i] === '--account' && argv[i + 1]) args.account = argv[++i]
else if (argv[i] === '--phase' && argv[i + 1]) args.phases = argv[++i].split(',')
else if (argv[i] === '--resume') args.resume = true
else if (argv[i] === '--retry-failed') args.retryFailed = true
else if (argv[i] === '--mode' && argv[i + 1]) args.mode = argv[++i]
else if (argv[i] === '--items' && argv[i + 1]) args.items = argv[++i]
else if (argv[i] === '--items-file' && argv[i + 1]) args.itemsFile = argv[++i]
@@ -1045,7 +1086,7 @@ async function main() {
}
if (command === 'run') {
if (!args.manifest) { console.error('用法: pipeline.js run --manifest <path> [--account id] [--phase p1,p2] [--resume]'); process.exit(1) }
if (!args.manifest) { console.error('用法: pipeline.js run --manifest <path> [--account id] [--phase p1,p2] [--resume] [--retry-failed]'); process.exit(1) }
await runPipeline(args.manifest, args)
return
}
@@ -1066,7 +1107,7 @@ async function main() {
console.log(' pipeline.js validate-account --account <id>')
console.log(' pipeline.js init --account <id> --mode <single|framePair> --items <JSON> [--items-file <path>]')
console.log(' pipeline.js validate --manifest <path>')
console.log(' pipeline.js run --manifest <path> [--account id] [--phase p1,p2] [--resume]')
console.log(' pipeline.js run --manifest <path> [--account id] [--phase p1,p2] [--resume] [--retry-failed]')
console.log(' pipeline.js status --manifest <path>')
console.log('')
console.log('Manifest 路径约定: output/{account}_{date}_{NNN}/manifest.json同天自增序号')