feat(video-pipeline): 支持滤镜和转场从账号配置继承
- 新增 Q18 滤镜和 Q19 转场字段到账号创建参考文档 - 重构账号配置加载函数为通用 `loadAccountConfig`,支持读取滤镜和转场 - `capcut_assemble.js` 支持 CLI 参数优先、账号配置兜底的滤镜/特效继承逻辑 - 模板 `account.json` 将闭幕转场从 "黑场" 修正为 "闪黑
This commit is contained in:
@@ -93,6 +93,8 @@ Phase 4: 技术配置(有默认值,可跳过)
|
|||||||
| 16 | TTS 语气指令? | 无 | account.json 的 ttsInstruction,描述期望的语气风格 |
|
| 16 | TTS 语气指令? | 无 | account.json 的 ttsInstruction,描述期望的语气风格 |
|
||||||
| 17 | 关键字氛围词? | 开启(默认样式) | 画面中央大字叠加,增强冲击力。选项:关闭 / 默认样式 / 描述期望效果 |
|
| 17 | 关键字氛围词? | 开启(默认样式) | 画面中央大字叠加,增强冲击力。选项:关闭 / 默认样式 / 描述期望效果 |
|
||||||
| | 期望的花字风格?(选填) | 按账号风格推荐 | 根据视觉基调推荐,见下方花字推荐表 |
|
| | 期望的花字风格?(选填) | 按账号风格推荐 | 根据视觉基调推荐,见下方花字推荐表 |
|
||||||
|
| 18 | 滤镜? | 无 | account.json 的 capcut.filter,格式 `滤镜名:强度`(如 `质感电影:40`)。见下方滤镜推荐表 |
|
||||||
|
| 19 | 转场偏好? | 默认闪白 | account.json 的 capcut.transitions。见下方转场推荐表 |
|
||||||
|
|
||||||
**花字效果推荐表**(92 种免费效果,按风格分类):
|
**花字效果推荐表**(92 种免费效果,按风格分类):
|
||||||
|
|
||||||
@@ -109,6 +111,33 @@ Phase 4: 技术配置(有默认值,可跳过)
|
|||||||
> Agent 可调用 `get_text_effects` API 获取完整列表(92 种免费 + 更多 VIP)。
|
> Agent 可调用 `get_text_effects` API 获取完整列表(92 种免费 + 更多 VIP)。
|
||||||
> Q17 选"默认样式"时,Agent 根据账号风格从上表自动匹配。
|
> Q17 选"默认样式"时,Agent 根据账号风格从上表自动匹配。
|
||||||
|
|
||||||
|
**滤镜推荐表**(免费滤镜,格式 `滤镜名:强度`,强度 0-100):
|
||||||
|
|
||||||
|
| 账号风格 | 推荐滤镜 | 备选 |
|
||||||
|
|---------|---------|------|
|
||||||
|
| 暗黑/军事/权谋 | 暗调氛围:40 | 暗夜:50、质感暗调:35 |
|
||||||
|
| 电影/叙事 | 质感电影:40 | 情感电影:35、情绪电影:30 |
|
||||||
|
| 复古/怀旧 | 暗调复古电影:40 | 复古工业:45 |
|
||||||
|
| 科技/赛博 | 质感暗调:30 | — |
|
||||||
|
| 清新/生活 | 质感电影:25 | 情感电影:20 |
|
||||||
|
| 纪录/人文 | 质感电影:30 | 情绪电影:25 |
|
||||||
|
|
||||||
|
> 留空则不加滤镜。Agent 可调用 CapCut Mate API 获取完整滤镜列表。
|
||||||
|
|
||||||
|
**转场推荐表**(免费转场,`duration` 单位微秒,推荐 100000-300000):
|
||||||
|
|
||||||
|
| 转场名 | 效果 | 适用场景 |
|
||||||
|
|--------|------|---------|
|
||||||
|
| 闪白 | 明闪过渡,节奏强 | 暗黑/军事/冲击类(默认推荐) |
|
||||||
|
| 闪白 II | 明闪变体,稍柔和 | 潮酷/街头 |
|
||||||
|
| 闪黑 | 暗闪过渡,压抑感 | 沉重/悬疑/结尾 |
|
||||||
|
| 溶解 | 画面缓慢交融 | 唯美/叙事/情感 |
|
||||||
|
| 叠化 | 经典淡入淡出 | 通用/纪录片 |
|
||||||
|
| 色彩溶解 | 带色彩过渡的溶解 | 艺术/创意 |
|
||||||
|
|
||||||
|
> 默认配置使用「闪白」+ 按位置变体(hook 闪白、body 溶解、closing 闪黑),可直接采用默认。
|
||||||
|
> Agent 可调用 CapCut Mate API 获取完整转场列表。
|
||||||
|
|
||||||
**运动偏好 → 视频提示词映射**:
|
**运动偏好 → 视频提示词映射**:
|
||||||
|
|
||||||
| 用户选择 | 运动风格基调 |
|
| 用户选择 | 运动风格基调 |
|
||||||
@@ -150,6 +179,8 @@ Phase 4: 技术配置(有默认值,可跳过)
|
|||||||
- TTS音色:{Q15}
|
- TTS音色:{Q15}
|
||||||
- TTS语气:{Q16}
|
- TTS语气:{Q16}
|
||||||
- 关键字氛围词:{Q17 开启/关闭,花字风格}
|
- 关键字氛围词:{Q17 开启/关闭,花字风格}
|
||||||
|
- 滤镜:{Q18}
|
||||||
|
- 转场:{Q19}
|
||||||
|
|
||||||
确认 "开始" → 创建账号
|
确认 "开始" → 创建账号
|
||||||
修改 → 调整后重新输出
|
修改 → 调整后重新输出
|
||||||
@@ -178,6 +209,8 @@ Phase 4: 技术配置(有默认值,可跳过)
|
|||||||
- 从 `_template/account.json` 复制骨架
|
- 从 `_template/account.json` 复制骨架
|
||||||
- 填入 id、name、description、模型、画幅等
|
- 填入 id、name、description、模型、画幅等
|
||||||
- Q17 选关闭时删除 `keywordStyle` 节;选自定义花字时更新 `textEffect` 字段
|
- Q17 选关闭时删除 `keywordStyle` 节;选自定义花字时更新 `textEffect` 字段
|
||||||
|
- Q18 填入 `capcut.filter`(格式 `滤镜名:强度`)
|
||||||
|
- Q19 填入 `capcut.transitions` 配置
|
||||||
|
|
||||||
3. **生成分镜.md**
|
3. **生成分镜.md**
|
||||||
- 读取 `_template/prompts/通用分镜.md`
|
- 读取 `_template/prompts/通用分镜.md`
|
||||||
|
|||||||
@@ -233,6 +233,20 @@ async function assemble(args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf-8'))
|
const manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf-8'))
|
||||||
|
|
||||||
|
// 从 account.json 自动继承 effects / filter(CLI 参数优先)
|
||||||
|
let finalEffects = effectsStr
|
||||||
|
let finalFilter = filterStr
|
||||||
|
if (!finalEffects || !finalFilter) {
|
||||||
|
const accountData = loadAccountConfig(manifest)
|
||||||
|
if (!finalEffects && accountData.capcut?.effects?.length) {
|
||||||
|
finalEffects = accountData.capcut.effects.join(',')
|
||||||
|
}
|
||||||
|
if (!finalFilter && accountData.capcut?.filter) {
|
||||||
|
finalFilter = accountData.capcut.filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { width, height } = getResolution(format)
|
const { width, height } = getResolution(format)
|
||||||
const defaultDurationUs = parseFloat(duration) * US
|
const defaultDurationUs = parseFloat(duration) * US
|
||||||
|
|
||||||
@@ -284,6 +298,8 @@ async function assemble(args) {
|
|||||||
console.log(` 模式: ${mode} 画幅: ${format} (${width}x${height})`)
|
console.log(` 模式: ${mode} 画幅: ${format} (${width}x${height})`)
|
||||||
console.log(` 时间线: ${hasTTS ? 'TTS音频驱动' : `固定${duration}s/段`} 总时长: ${(totalDurationUs / US).toFixed(1)}s`)
|
console.log(` 时间线: ${hasTTS ? 'TTS音频驱动' : `固定${duration}s/段`} 总时长: ${(totalDurationUs / US).toFixed(1)}s`)
|
||||||
console.log(` 字幕: ${subtitles} 配音: ${voiceover} 动画: ${animation}`)
|
console.log(` 字幕: ${subtitles} 配音: ${voiceover} 动画: ${animation}`)
|
||||||
|
if (finalEffects) console.log(` 特效: ${finalEffects}`)
|
||||||
|
if (finalFilter) console.log(` 滤镜: ${finalFilter}`)
|
||||||
console.log(` 素材: ${items.length} 个可用\n`)
|
console.log(` 素材: ${items.length} 个可用\n`)
|
||||||
|
|
||||||
const steps = []
|
const steps = []
|
||||||
@@ -397,7 +413,7 @@ async function assemble(args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (changed) saveManifest(manifestFile, manifest)
|
if (changed) fs.writeFileSync(manifestFile, JSON.stringify(manifest, null, 2))
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(` OSS 上传失败,将尝试本地路径: ${err.message}\n`)
|
console.log(` OSS 上传失败,将尝试本地路径: ${err.message}\n`)
|
||||||
@@ -445,26 +461,26 @@ async function assemble(args) {
|
|||||||
|
|
||||||
// -- 添加特效 --
|
// -- 添加特效 --
|
||||||
step++; console.log(`[${step}/${totalSteps}] 添加特效...`)
|
step++; console.log(`[${step}/${totalSteps}] 添加特效...`)
|
||||||
if (effectsStr) {
|
if (finalEffects) {
|
||||||
try {
|
try {
|
||||||
await addEffects(draftUrl, effectsStr, totalDurationUs)
|
await addEffects(draftUrl, finalEffects, totalDurationUs)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(` 特效跳过: ${e.message}`)
|
console.log(` 特效跳过: ${e.message}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(' 跳过(未指定 --effects)')
|
console.log(' 跳过(未配置特效)')
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- 添加滤镜 --
|
// -- 添加滤镜 --
|
||||||
step++; console.log(`[${step}/${totalSteps}] 添加滤镜...`)
|
step++; console.log(`[${step}/${totalSteps}] 添加滤镜...`)
|
||||||
if (filterStr) {
|
if (finalFilter) {
|
||||||
try {
|
try {
|
||||||
await addFilter(draftUrl, filterStr, totalDurationUs)
|
await addFilter(draftUrl, finalFilter, totalDurationUs)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(` 滤镜跳过: ${e.message}`)
|
console.log(` 滤镜跳过: ${e.message}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(' 跳过(未指定 --filter)')
|
console.log(' 跳过(未配置滤镜)')
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- 保存草稿 --
|
// -- 保存草稿 --
|
||||||
@@ -809,31 +825,23 @@ async function addBGM(draftUrl, bgmUrl, totalDurationUs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 读取账号字幕风格配置
|
// 读取账号配置
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
function loadSubtitleStyle(manifest) {
|
function loadAccountConfig(manifest) {
|
||||||
const account = manifest.account
|
const account = manifest.account
|
||||||
if (!account) return {}
|
if (!account) return {}
|
||||||
const scriptDir = __dirname
|
const accountFile = path.join(__dirname, '..', '..', '..', '..', 'accounts', account, 'account.json')
|
||||||
const accountFile = path.join(scriptDir, '..', '..', '..', 'accounts', account, 'account.json')
|
|
||||||
if (!fs.existsSync(accountFile)) return {}
|
if (!fs.existsSync(accountFile)) return {}
|
||||||
try {
|
try { return JSON.parse(fs.readFileSync(accountFile, 'utf-8')) } catch { return {} }
|
||||||
const accountData = JSON.parse(fs.readFileSync(accountFile, 'utf-8'))
|
}
|
||||||
return accountData.capcut?.subtitleStyle || {}
|
|
||||||
} catch { return {} }
|
function loadSubtitleStyle(manifest) {
|
||||||
|
return loadAccountConfig(manifest).capcut?.subtitleStyle || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadKeywordStyle(manifest) {
|
function loadKeywordStyle(manifest) {
|
||||||
const account = manifest.account
|
return loadAccountConfig(manifest).capcut?.keywordStyle || {}
|
||||||
if (!account) return {}
|
|
||||||
const scriptDir = __dirname
|
|
||||||
const accountFile = path.join(scriptDir, '..', '..', '..', 'accounts', account, 'account.json')
|
|
||||||
if (!fs.existsSync(accountFile)) return {}
|
|
||||||
try {
|
|
||||||
const accountData = JSON.parse(fs.readFileSync(accountFile, 'utf-8'))
|
|
||||||
return accountData.capcut?.keywordStyle || {}
|
|
||||||
} catch { return {} }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -894,15 +902,7 @@ async function addKeywordOverlays(draftUrl, items, timeline, style = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadTransitions(manifest) {
|
function loadTransitions(manifest) {
|
||||||
const account = manifest.account
|
return loadAccountConfig(manifest).capcut?.transitions || null
|
||||||
if (!account) return null
|
|
||||||
const scriptDir = __dirname
|
|
||||||
const accountFile = path.join(scriptDir, '..', '..', '..', 'accounts', account, 'account.json')
|
|
||||||
if (!fs.existsSync(accountFile)) return null
|
|
||||||
try {
|
|
||||||
const accountData = JSON.parse(fs.readFileSync(accountFile, 'utf-8'))
|
|
||||||
return accountData.capcut?.transitions || null
|
|
||||||
} catch { return null }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -40,12 +40,12 @@
|
|||||||
"hook": { "name": "闪白", "duration": 100000 },
|
"hook": { "name": "闪白", "duration": 100000 },
|
||||||
"keypoint": { "name": "闪白", "duration": 120000 },
|
"keypoint": { "name": "闪白", "duration": 120000 },
|
||||||
"body": { "name": "溶解", "duration": 300000 },
|
"body": { "name": "溶解", "duration": 300000 },
|
||||||
"closing": { "name": "黑场", "duration": 200000 }
|
"closing": { "name": "闪黑", "duration": 200000 }
|
||||||
},
|
},
|
||||||
"byDirector": {
|
"byDirector": {
|
||||||
"tarantino": { "name": "闪白", "duration": 100000 },
|
"tarantino": { "name": "闪白", "duration": 100000 },
|
||||||
"kitano": { "name": "溶解", "duration": 400000 },
|
"kitano": { "name": "溶解", "duration": 400000 },
|
||||||
"fincher": { "name": "黑场", "duration": 200000 }
|
"fincher": { "name": "闪黑", "duration": 200000 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"videoStylePrompt": "prompts/视频提示词.md",
|
"videoStylePrompt": "prompts/视频提示词.md",
|
||||||
"capcut": {
|
"capcut": {
|
||||||
"effects": ["录制边框 III"],
|
"effects": ["录制边框 III"],
|
||||||
"filter": "电影感:40",
|
"filter": "质感电影:40",
|
||||||
"subtitleStyle": {
|
"subtitleStyle": {
|
||||||
"font": "思源黑体 Heavy",
|
"font": "思源黑体 Heavy",
|
||||||
"fontSize": 24,
|
"fontSize": 24,
|
||||||
@@ -55,12 +55,12 @@
|
|||||||
"hook": { "name": "闪白", "duration": 100000 },
|
"hook": { "name": "闪白", "duration": 100000 },
|
||||||
"keypoint": { "name": "闪白", "duration": 120000 },
|
"keypoint": { "name": "闪白", "duration": 120000 },
|
||||||
"body": { "name": "溶解", "duration": 300000 },
|
"body": { "name": "溶解", "duration": 300000 },
|
||||||
"closing": { "name": "黑场", "duration": 200000 }
|
"closing": { "name": "闪黑", "duration": 200000 }
|
||||||
},
|
},
|
||||||
"byDirector": {
|
"byDirector": {
|
||||||
"tarantino": { "name": "闪白", "duration": 100000 },
|
"tarantino": { "name": "闪白", "duration": 100000 },
|
||||||
"kitano": { "name": "溶解", "duration": 400000 },
|
"kitano": { "name": "溶解", "duration": 400000 },
|
||||||
"fincher": { "name": "黑场", "duration": 200000 }
|
"fincher": { "name": "闪黑", "duration": 200000 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user