剪口播 v2
火山引擎转录 + AI 口误识别 + 网页审核
快速使用
code
用户: 帮我剪这个口播视频 用户: 处理一下这个视频
输出目录结构
code
output/
└── YYYY-MM-DD_视频名/
├── 剪口播/
│ ├── 1_转录/
│ │ ├── audio.mp3
│ │ ├── volcengine_result.json
│ │ └── subtitles_words.json
│ ├── 2_分析/
│ │ ├── readable.txt
│ │ ├── auto_selected.json
│ │ └── 口误分析.md
│ └── 3_审核/
│ └── review.html
└── 字幕/
└── ...
规则:已有文件夹则复用,否则新建。
流程
code
0. 创建输出目录
↓
1. 提取音频 (ffmpeg)
↓
2. 上传获取公网 URL (uguu.se)
↓
3. 火山引擎 API 转录
↓
4. 生成字级别字幕 (subtitles_words.json)
↓
5. AI 分析口误/静音,生成预选列表 (auto_selected.json)
↓
6. 生成审核网页 (review.html)
↓
7. 启动审核服务器,用户网页确认
↓
【等待用户确认】→ 网页点击「执行剪辑」或手动 /剪辑
↓
8. 执行剪辑 (cut_video.ps1)
↓
9. 【可选】导出到剪映草稿 (-ExportToJianying)
执行步骤
步骤 0: 创建输出目录
bash
# 变量设置(根据实际视频调整)
VIDEO_PATH="/path/to/视频.mp4"
VIDEO_NAME=$(basename "$VIDEO_PATH" .mp4)
DATE=$(date +%Y-%m-%d)
BASE_DIR="output/${DATE}_${VIDEO_NAME}/剪口播"
# 创建子目录
mkdir -p "$BASE_DIR/1_转录" "$BASE_DIR/2_分析" "$BASE_DIR/3_审核"
cd "$BASE_DIR"
步骤 1-3: 转录
bash
cd 1_转录
# 1. 提取音频(文件名有冒号需加 file: 前缀)
ffmpeg -i "file:$VIDEO_PATH" -vn -acodec libmp3lame -y audio.mp3
# 2. 上传获取公网 URL
curl -s -F "files[]=@audio.mp3" https://uguu.se/upload
# 返回: {"success":true,"files":[{"url":"https://h.uguu.se/xxx.mp3"}]}
# 3. 调用火山引擎 API
SKILL_DIR="/Users/chengfeng/Desktop/AIos/剪辑Agent/.claude/skills/剪口播"
"$SKILL_DIR/scripts/volcengine_transcribe.sh" "https://h.uguu.se/xxx.mp3"
# 输出: volcengine_result.json
步骤 4: 生成字幕
bash
node "$SKILL_DIR/scripts/generate_subtitles.js" volcengine_result.json # 输出: subtitles_words.json cd ..
步骤 5: 分析口误(脚本+AI)
5.1 生成易读格式
bash
cd 2_分析
node -e "
const data = require('../1_转录/subtitles_words.json');
let output = [];
data.forEach((w, i) => {
if (w.isGap) {
const dur = (w.end - w.start).toFixed(2);
if (dur >= 0.5) output.push(i + '|[静' + dur + 's]|' + w.start.toFixed(2) + '-' + w.end.toFixed(2));
} else {
output.push(i + '|' + w.text + '|' + w.start.toFixed(2) + '-' + w.end.toFixed(2));
}
});
require('fs').writeFileSync('readable.txt', output.join('\\n'));
"
5.2 读取用户习惯
先读 用户习惯/ 目录下所有规则文件。
5.3 生成句子列表(关键步骤)
必须先分句,再分析。按静音切分成句子列表:
bash
node -e "
const data = require('../1_转录/subtitles_words.json');
let sentences = [];
let curr = { text: '', startIdx: -1, endIdx: -1 };
data.forEach((w, i) => {
const isLongGap = w.isGap && (w.end - w.start) >= 0.5;
if (isLongGap) {
if (curr.text.length > 0) sentences.push({...curr});
curr = { text: '', startIdx: -1, endIdx: -1 };
} else if (!w.isGap) {
if (curr.startIdx === -1) curr.startIdx = i;
curr.text += w.text;
curr.endIdx = i;
}
});
if (curr.text.length > 0) sentences.push(curr);
sentences.forEach((s, i) => {
console.log(i + '|' + s.startIdx + '-' + s.endIdx + '|' + s.text);
});
" > sentences.txt
5.4 脚本自动标记静音(必须先执行)
bash
node -e "
const words = require('../1_转录/subtitles_words.json');
const selected = [];
words.forEach((w, i) => {
if (w.isGap && (w.end - w.start) >= 0.5) selected.push(i);
});
require('fs').writeFileSync('auto_selected.json', JSON.stringify(selected, null, 2));
console.log('≥0.5s静音数量:', selected.length);
"
→ 输出 auto_selected.json(只含静音 idx)
5.5 AI 分析口误(追加到 auto_selected.json)
检测规则(按优先级):
| # | 类型 | 判断方法 | 删除范围 |
|---|---|---|---|
| 1 | 重复句 | 相邻句子开头≥5字相同 | 较短的整句 |
| 2 | 隔一句重复 | 中间是残句时,比对前后句 | 前句+残句 |
| 3 | 残句 | 话说一半+静音 | 整个残句 |
| 4 | 句内重复 | A+中间+A 模式 | 前面部分 |
| 5 | 卡顿词 | 那个那个、就是就是 | 前面部分 |
| 6 | 重说纠正 | 部分重复/否定纠正 | 前面部分 |
| 7 | 语气词 | 嗯、啊、那个 | 标记但不自动删 |
核心原则:
- •先分句,再比对:用 sentences.txt 比对相邻句子
- •整句删除:残句、重复句都要删整句,不只是删异常的几个字
分段分析(循环执行):
code
1. Read readable.txt offset=N limit=300 2. 结合 sentences.txt 分析这300行 3. 追加口误 idx 到 auto_selected.json 4. 记录到 口误分析.md 5. N += 300,回到步骤1
🚨 关键警告:行号 ≠ idx
code
readable.txt 格式: idx|内容|时间
↑ 用这个值
行号1500 → "1568|[静1.02s]|..." ← idx是1568,不是1500!
口误分析.md 格式:
markdown
## 第N段 (行号范围) | idx | 时间 | 类型 | 内容 | 处理 | |-----|------|------|------|------| | 65-75 | 15.80-17.66 | 重复句 | "这是我剪出来的一个案例" | 删 |
3. 环境配置 (Configuration)
在 .agent/skills/.env 中配置以下关键路径和密钥:
| 配置项 | 说明 | 示例值 |
|---|---|---|
VOLCENGINE_API_KEY | 火山引擎 API Key | your_api_key_here |
RAW_VIDEO_DIR | 原始视频存放目录 | F:\Focusee\Video |
JIANYING_DRAFT_DIR | 剪映草稿存放目录 | E:\剪映草稿\JianyingPro Drafts |
注意:脚本在导出剪映时,会优先从
RAW_VIDEO_DIR寻找原始视频以保证同步精度。
步骤 6-7: 审核
bash
cd ../3_审核 # 6. 生成审核网页 node "$SKILL_DIR/scripts/generate_review.js" ../1_转录/subtitles_words.json ../2_分析/auto_selected.json ../1_转录/audio.mp3 # 输出: review.html # 7. 启动审核服务器 node "$SKILL_DIR/scripts/review_server.js" 8899 "$VIDEO_PATH" # 打开 http://localhost:8899
用户在网页中:
- •播放视频片段确认
- •勾选/取消删除项
- •点击「执行剪辑」
数据格式
subtitles_words.json
json
[
{"text": "大", "start": 0.12, "end": 0.2, "isGap": false},
{"text": "", "start": 6.78, "end": 7.48, "isGap": true}
]
auto_selected.json
json
[72, 85, 120] // Claude 分析生成的预选索引
配置
火山引擎 API Key
bash
cd F:\dai ma\剪辑\.agent\skills cp .env.example .env # 编辑 .env 填入 VOLCENGINE_API_KEY=xxx
导出到剪映
剪辑完成后,可以将结果导出到剪映草稿,方便进行二次编辑。
剪映草稿导出 (Jianying Export)
为了实现从自动化剪辑到剪映二次创作的无缝衔接,必须遵循以下“最佳实践”以保证音画同步和极佳的初剪体验。
1. 核心逻辑:时间轴重建 (Timeline Rebuild)
禁止:将剪辑后的成品 mp4 导入剪映。这会导致字幕出现毫秒级的累积位移。
推荐:使用原始长视频素材,配合 keep_segments.json。通过 Python 脚本在剪映草稿中将原始视频切成段并“接起来”。
- •优点:字幕与画面永远保持原始转录时的物理时间对齐,且保留了所有的剪辑点方便手动微调。
2. 字幕断句规范 (Semantic Segmentation)
为了让字幕看起来“像人写的”,必须放弃简单的字数统计(如每12字一断),改用以下优先级逻辑:
- •停顿优先:利用转录数据中的
isGap(停顿 > 0.3s) 作为第一断句点。 - •语义优先:在语气词(如:的、了、是、好、吧、呢、啊)之后断句。
- •字数兜底:单句控制在 12-14 字,若超长则回退寻找最近的语义点。
- •效果示例:避免将“我是盛凡”截断为“我是盛/凡”。
3. 真正的字幕轨道 (Native Captions vs Text)
- •不要使用
TextSegment(文本片段),因为难以批量修改样式。 - •必须使用
script.import_srt()接口,将生成的 SRT 文件导入为剪映原生的“字幕轨道”。 - •比例规则:默认使用 16:9 横屏 (1920x1080);字幕置底偏移量
transform_y = -0.8。
4. 环境与目录规范
- •依赖:
pip install pyJianYingDraft。 - •结构:
text
JianyingPro Drafts/ └── [草稿名称]/ ├── draft_content.json # 由脚本生成 └── draft_meta_info.json # 必须存在,否则剪映无法加载
5. 常用命令 (自动化运行)
powershell
# 建议通过 cut_video.ps1 自动触发 ./cut_video.ps1 -VideoPath "..." -ExportToJianying
- •
--drafts-folder, -d: 剪映草稿目录(默认:E:\剪映草稿\JianyingPro Drafts)
keep_segments.json 格式
json
[
{"start": 0.0, "end": 5.5},
{"start": 8.2, "end": 15.0}
]
注意事项
- •剪映版本兼容性:pyJianYingDraft 支持生成新草稿,剪映会自动处理格式
- •刷新草稿列表:导出后在剪映中可能需要刷新或重启才能看到新草稿
- •素材路径:草稿会引用原始视频路径,请确保路径可访问