install
source · Clone the upstream repo
git clone https://github.com/Ceeon/videocut-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Ceeon/videocut-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/剪口播" ~/.claude/skills/ceeon-videocut-skills-videocut && rm -rf "$T"
manifest:
剪口播/SKILL.mdsource content
<!--
input: 视频文件 (*.mp4)
output: subtitles_words.json、auto_selected.json、review.html、video.mp4(符号链接)
pos: 转录+识别,到用户网页审核为止
架构守护者:一旦我被修改,请同步更新:
1. ../README.md 的 Skill 清单
2. /CLAUDE.md 路由表
-->
剪口播 v2
火山引擎转录 + AI 口误识别 + 网页审核
快速使用
用户: 帮我剪这个口播视频 用户: 处理一下这个视频
输出目录结构
output/ └── YYYY-MM-DD_视频名/ ├── 剪口播/ │ ├── 1_转录/ │ │ ├── audio.mp3 │ │ ├── volcengine_result.json │ │ └── subtitles_words.json │ ├── 2_分析/ │ │ ├── readable.txt │ │ ├── auto_selected.json │ │ └── 口误分析.md │ └── 3_审核/ │ ├── review.html │ └── video.mp4 → 源视频(符号链接) └── 字幕/ └── ...
规则:已有文件夹则复用,否则新建。
流程
0. 创建输出目录 ↓ 1. 提取音频 (ffmpeg) ↓ 2. 上传获取公网 URL (uguu.se) ↓ 3. 火山引擎 API 转录 ↓ 4. 生成字级别字幕 (subtitles_words.json) ↓ 5. AI 分析口误/静音,生成预选列表 (auto_selected.json) ↓ 6. 生成审核网页 (review.html) ↓ 7. 启动审核服务器,用户网页确认 ↓ 【等待用户确认】→ 网页点击「执行剪辑」或手动 /剪辑
执行步骤
步骤 0: 创建输出目录
# 变量设置(根据实际视频调整) 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: 转录
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: 生成字幕
node "$SKILL_DIR/scripts/generate_subtitles.js" volcengine_result.json # 输出: subtitles_words.json cd ..
步骤 5: 分析口误(脚本+AI)
5.1 生成易读格式
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.2) 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 生成句子列表(关键步骤)
必须先分句,再分析。按静音切分成句子列表:
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 脚本自动标记静音(必须先执行)
node -e " const words = require('../1_转录/subtitles_words.json'); const selected = []; words.forEach((w, i) => { if (w.isGap && (w.end - w.start) >= 0.2) selected.push(i); }); require('fs').writeFileSync('auto_selected.json', JSON.stringify(selected, null, 2)); console.log('≥0.2s静音数量:', selected.length); "
→ 输出
auto_selected.json(只含静音 idx)
5.5 AI 分析口误(追加到 auto_selected.json)
检测规则(按优先级):
| # | 类型 | 判断方法 | 删除范围 |
|---|---|---|---|
| 1 | 重复句 | 相邻句子开头≥5字相同 | 较短的整句 |
| 2 | 隔一句重复 | 中间是残句时,比对前后句 | 前句+残句 |
| 3 | 残句 | 话说一半+静音 | 整个残句 |
| 4 | 句内重复 | A+中间+A 模式 | 前面部分 |
| 5 | 卡顿词 | 那个那个、就是就是 | 前面部分 |
| 6 | 重说纠正 | 部分重复/否定纠正 | 前面部分 |
| 7 | 语气词 | 嗯、啊、那个 | 标记但不自动删 |
核心原则:
- 先分句,再比对:用 sentences.txt 比对相邻句子
- 整句删除:残句、重复句都要删整句,不只是删异常的几个字
- 范围整段删除:标记口误时,从 startIdx 到 endIdx 之间的所有元素(含中间的 gap,不管多短)全部加入 auto_selected。不要逐个挑选文字 idx 而跳过 gap
分段分析(循环执行):
1. Read readable.txt offset=N limit=300 2. 结合 sentences.txt 分析这300行 3. 追加口误 idx 到 auto_selected.json 4. 记录到 口误分析.md 5. N += 300,回到步骤1
🚨 关键警告:行号 ≠ idx
readable.txt 格式: idx|内容|时间 ↑ 用这个值 行号1500 → "1568|[静1.02s]|..." ← idx是1568,不是1500!
口误分析.md 格式:
## 第N段 (行号范围) | idx | 时间 | 类型 | 内容 | 处理 | |-----|------|------|------|------| | 65-75 | 15.80-17.66 | 重复句 | "这是我剪出来的一个案例" | 删 |
步骤 6-7: 审核
cd ../3_审核 # 6. 生成审核网页(传入视频文件,自动创建符号链接) node "$SKILL_DIR/scripts/generate_review.js" ../1_转录/subtitles_words.json ../2_分析/auto_selected.json "$VIDEO_PATH" # 输出: review.html, video.mp4(符号链接) # 7. 启动审核服务器 node "$SKILL_DIR/scripts/review_server.js" 8899 "$VIDEO_PATH" # 打开 http://localhost:8899
⚠️ 必须用 review_server.js,不能用
替代。 原因:视频播放依赖 HTTP Range 请求(206),python 简易服务器不支持,会导致视频无法播放/无声音。 启动时不要在命令末尾加python3 -m http.server(shell 后台),用&参数即可。run_in_background
用户在网页中:
- 播放视频画面确认
- 勾选/取消删除项
- 点击「执行剪辑」
数据格式
subtitles_words.json
[ {"text": "大", "start": 0.12, "end": 0.2, "isGap": false}, {"text": "", "start": 6.78, "end": 7.48, "isGap": true} ]
auto_selected.json
[72, 85, 120] // Claude 分析生成的预选索引
剪辑编码(硬性规则)
⚠️ 匹配原片参数重编码,帧级精确切割。
cut_video.sh 的工作方式:
- 自动检测原片编码参数(codec/profile/pix_fmt/bitrate)
- 用
trim+concat 帧级精确切割filter_complex - 以相同参数重编码:
-profile:v high -b:v {原片码率} -pix_fmt yuv420p
关键:重编码画质取决于是否匹配原片参数,不是 CRF 值。
- ✅
→ 肉眼无区别-b:v {原片码率} -profile:v high -pix_fmt yuv420p - ❌ 只指定
不指定 profile/pix_fmt → 可能有偏差-crf N
配置
火山引擎 API Key
cd /Users/chengfeng/Desktop/AIos/剪辑Agent/.claude/skills cp .env.example .env # 编辑 .env 填入 VOLCENGINE_API_KEY=xxx