feat(skills): 完善视频生产 pipeline 及新增健身跟练账号

- SKILL.md: 新增工作流阶段定义、质量卡点、分镜规则
- manifest-schema.md: 补充完整字段规范及类型定义
- phase-tts.js: 优化 TTS 合成长逻辑,添加进度追踪
- capcut-tracks.js: 扩展轨道构建能力,支持更多元素类型
- capcut-timeline.js: 改进时间线生成,支持淡入淡出
- capcut_assemble.js: 新增 assemble 阶段完整实现
- cmd-init.js: 完善 init 命令逻辑
- qwen-tts.js: 调整超时配置
- accounts/禁忌帝王学: 更新拆分/图像/台词提示词
- accounts/健身跟练: 新增账号含 account.json 及全套提示词模板
- 新增 workflow-issues-20260501.md 参考文档

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
lc
2026-05-06 22:53:37 +08:00
parent e6daf7a8d8
commit 6eec0e8889
28 changed files with 2199 additions and 253 deletions

View File

@@ -22,7 +22,7 @@ const { buildTimeline, adjustVideoSpeed } = require('./lib/capcut-timeline')
const {
loadAccountConfig, loadSubtitleStyle,
loadKenBurns, loadTransitions,
addImages, addVideos, addKenBurns,
addImages, addVideos, addSlotsLocally,
addVoiceover, addBGM,
addSubtitles,
addEffects, addFilter,
@@ -65,23 +65,43 @@ async function batchUploadToOSS(inputDir, files, concurrency = 3) {
async function batchUploadAudio(inputDir, items) {
const urls = {}
for (const item of items) {
if (!item.audio || item.audio.startsWith('http')) {
if (item.audio) urls[item.audio] = item.audio
continue
// 处理主音频
if (item.audio && !item.audio.startsWith('http')) {
if (!urls[item.audio]) {
const filePath = path.isAbsolute(item.audio)
? item.audio
: path.resolve(inputDir, item.audio)
if (fs.existsSync(filePath)) {
try {
urls[item.audio] = await uploadToOSS(filePath)
console.log(` 上传: ${path.basename(filePath)} -> OK`)
} catch (err) {
console.error(` 上传失败: ${path.basename(filePath)} - ${err.message}`)
}
}
}
} else if (item.audio) {
urls[item.audio] = item.audio
}
if (urls[item.audio]) continue
const filePath = path.isAbsolute(item.audio)
? item.audio
: path.resolve(inputDir, item.audio)
if (!fs.existsSync(filePath)) {
console.error(` 音频文件不存在: ${filePath}`)
continue
}
try {
urls[item.audio] = await uploadToOSS(filePath)
console.log(` 上传: ${path.basename(filePath)} -> OK`)
} catch (err) {
console.error(` 上传失败: ${path.basename(filePath)} - ${err.message}`)
// 处理分段音频
if (item.segments && item.segments.length > 0) {
for (const seg of item.segments) {
if (!seg.audio || seg.error) continue
if (urls[seg.audio]) continue
const filePath = path.isAbsolute(seg.audio)
? seg.audio
: path.resolve(inputDir, seg.audio)
if (!fs.existsSync(filePath)) {
console.error(` 音频文件不存在: ${filePath}`)
continue
}
try {
urls[seg.audio] = await uploadToOSS(filePath)
console.log(` 上传: ${path.basename(filePath)} -> OK`)
} catch (err) {
console.error(` 上传失败: ${path.basename(filePath)} - ${err.message}`)
}
}
}
}
return urls
@@ -288,7 +308,11 @@ async function assemble(args) {
}
}
}
await addVideos(draftUrl, inputDir, items, timeline, width, height, transitionConfig)
const segmentIds = await addVideos(draftUrl, inputDir, items, timeline, width, height, transitionConfig)
// 将 segment_ids 附加到 items供后续 addSlotsLocally 使用
if (segmentIds && segmentIds.length > 0) {
items.forEach((item, i) => { item._segmentId = segmentIds[i] || null })
}
}
// -- Ken Burns --
@@ -383,6 +407,13 @@ async function assemble(args) {
await syncToLocalJianying(draftUrl, draftId, totalDurationUs)
console.log(' 同步完成\n')
// -- 视频轨道 slot 写入(在 syncToLocalJianying 之后执行,此时本地草稿文件已存在)--
if (mode !== 'images') {
step++; console.log(`[${step}/${totalSteps}] 写入视频轨道时间线...`)
await addSlotsLocally(draftUrl, items, timeline, null, { draftId })
console.log(' 视频轨道写入完成\n')
}
// -- 云渲染(可选)--
if (apiKey) {
console.log('提交云渲染...')