بنقرة واحدة
videocut-talkcut
口播视频转录和口误识别。生成审查稿和删除任务清单。触发词:talkcut、处理视频、识别口误
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
口播视频转录和口误识别。生成审查稿和删除任务清单。触发词:talkcut、处理视频、识别口误
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
视频剪辑 Agent,专为口播视频设计 功能: - 语义理解:AI 逐句分析,识别重说/纠正/卡顿 - 静音检测:自动标记过长静音段 - 重复句检测:删除重复内容,保留最佳表达 - 文案生成:基于保留内容自动生成视频介绍草稿 - subtitles生成:Whisper 转录 + 词典纠错 - 自更新:记住用户偏好,越用越准 触发场景: - 用户说"/videocut:install" - 用户说"/videocut:cut [视频文件]" - 用户说"/videocut:subtitle [视频文件]" - 用户说"/videocut:update"
evolution skills。记录用户反馈,更新方法论和规则。触发词:更新规则、记录反馈、改进skill
环境准备。install依赖、配置 API Key、验证环境。触发词:install、环境准备、初始化
subtitles生成与烧录。火山引擎转录→词典纠错→审核→烧录。触发词:加subtitles、生成subtitles、subtitles
| name | videocut:talkcut |
| description | 口播视频转录和口误识别。生成审查稿和删除任务清单。触发词:talkcut、处理视频、识别口误 |
语音转录 + AI 口误识别 + 网页审核
补充资料:
用户: 帮我剪这个口播视频
用户: 处理一下这个视频
{DEFAULT_OUTPUT_DIR}/
└── YYYY-MM-DD_视频名/
├── talkcut/
│ ├── 1_transcribe/
│ │ ├── audio.wav
│ │ ├── audio_timeline.json
│ │ ├── volcengine_result.json (仅火山引擎方案)
│ │ └── subtitles_words.json
│ ├── 2_analysis/
│ │ ├── readable.txt
│ │ ├── sentences.txt
│ │ ├── auto_selected.json
│ │ └── 口误分析.md
│ └── 3_review/
│ ├── audio.wav
│ ├── audio_timeline.json
│ ├── review.html
│ └── 视频介绍草稿.md
└── subtitles/
└── ...
规则:已有文件夹则复用,否则新建。
0. 创建输出目录
↓
1. 提取和视频时间轴对齐的审核音频
↓
2. 选择转录方案(读取 .env 的 ASR_ENGINE)
├─ volcengine: 上传 → 火山引擎 API → generate_subtitles.js
└─ whisper: 本地 whisper_transcribe.py
↓
3. 得到 subtitles_words.json(两条路径汇合)
↓
4. AI 分析口误/静音,生成预选列表 (auto_selected.json)
↓
4.2 规范化预选列表(补短停顿桥接)
↓
4.5 生成视频介绍草稿 (视频介绍草稿.md)
↓
5. 生成审核网页 (review.html)
↓
6. 启动审核服务器,用户网页确认
↓
【等待用户确认】→ 网页点击「执行剪辑」或手动 /剪辑
# 读取 .env 中的 DEFAULT_OUTPUT_DIR
SKILL_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
ENV_FILE="$SKILL_ROOT/.env"
OUTPUT_ROOT=$(grep DEFAULT_OUTPUT_DIR "$ENV_FILE" | cut -d'=' -f2)
如果 DEFAULT_OUTPUT_DIR 为空(首次使用):
.env 的 DEFAULT_OUTPUT_DIR= 行output/ 子目录# 变量设置(根据实际视频调整)
VIDEO_PATH="/path/to/视频.mp4"
VIDEO_NAME=$(basename "$VIDEO_PATH" .mp4)
DATE=$(date +%Y-%m-%d)
BASE_DIR="${OUTPUT_ROOT}/${DATE}_${VIDEO_NAME}/talkcut"
# 创建子目录
mkdir -p "$BASE_DIR/1_transcribe" "$BASE_DIR/2_analysis" "$BASE_DIR/3_review"
cd "$BASE_DIR"
cd 1_transcribe
# 提取和视频时间轴对齐的审核音频,并写出时间轴元数据
node "$SKILL_DIR/scripts/extract_review_audio.js" "$VIDEO_PATH" audio.wav audio_timeline.json
说明:
audio.wavaudio_timeline.json 用来把审核页时间轴和源视频时间轴对齐,避免不同视频的音频起点差异导致切点偏移读取 .env 中的 ASR_ENGINE:
volcengine → 方案 Awhisper → 方案 B# 1. 转成便于上传的 mp3
ffmpeg -y -i audio.wav -c:a libmp3lame 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
node "$SKILL_DIR/scripts/volcengine_transcribe.js" "https://h.uguu.se/xxx.mp3"
# 输出: volcengine_result.json
# 4. 生成字级别subtitles
node "$SKILL_DIR/scripts/generate_subtitles.js" volcengine_result.json
# 输出: subtitles_words.json
先检查 mlx-whisper 是否已install,未install则自动install:
python3 -c "import mlx_whisper" 2>/dev/null || pip3 install mlx-whisper
执行转录(首次运行会自动下载模型,约 1.5GB):
python3 "$SKILL_DIR/scripts/whisper_transcribe.py" audio.wav
# 直接输出: subtitles_words.json(已包含 gap 检测,无需再调 generate_subtitles.js)
cd ..
→ 两条路径都输出 subtitles_words.json,后续步骤完全一致。
cd 2_analysis
node -e "
const data = require('../1_transcribe/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'));
"
先读 user_rules/ 目录下所有规则文件。
必须先分句,再分析。按静音切分成句子列表:
node -e "
const data = require('../1_transcribe/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
node -e "
const words = require('../1_transcribe/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)
检测规则(按优先级):
| # | 类型 | 判断方法 | 删除范围 |
|---|---|---|---|
| 1 | 重复句 | 相邻句子开头≥5字相同 | 较短的整句 |
| 2 | 隔一句重复 | 中间是残句时,比对前后句 | 前句+残句 |
| 3 | 残句 | 话说一半+静音 | 整个残句 |
| 4 | 句内重复 | A+中间+A 模式 | 前面部分 |
| 5 | 卡顿词 | 那个那个、就是就是 | 前面部分 |
| 6 | 重说纠正 | 部分重复/否定纠正 | 前面部分 |
| 7 | 语气词 | 嗯、啊、那个 | 标记但不自动删 |
核心原则:
分段分析(循环执行):
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 | 重复句 | "这是我剪出来的一个案例" | 删 |
被删句子之间夹着的短 gap 也应自动标记删除,避免审核页留下碎片:
node -e "
const words = require('../1_transcribe/subtitles_words.json');
const selected = new Set(require('./auto_selected.json'));
let added = 0;
words.forEach((w, i) => {
if (!w.isGap || selected.has(i)) return;
let prev = i - 1;
while (prev >= 0 && words[prev].isGap) prev--;
let next = i + 1;
while (next < words.length && words[next].isGap) next++;
if (prev >= 0 && next < words.length && selected.has(prev) && selected.has(next)) {
selected.add(i);
added++;
}
});
const sorted = Array.from(selected).sort((a, b) => a - b);
require('fs').writeFileSync('auto_selected.json', JSON.stringify(sorted, null, 2) + '\\n');
if (added) console.log('🧹 自动清理孤立小间隙:', added, '个');
"
AI 补完索引后,再执行一次规范化,补上夹在两个待删片段之间的短停顿:
node "$SKILL_DIR/scripts/refine_auto_selected.js" \
"../1_transcribe/subtitles_words.json" \
"auto_selected.json"
补充规则:
<0.5s 的停顿,前后最近的口播词都已经在 auto_selected.json 里,这个停顿也默认删掉subtitles_words.json 里会按整段保留,不再拆成很多个 1.0s这一步必须由 Claude 直接完成,不要用本地模板脚本代写。
按 show-notes.md 的要求,基于当前准备保留的内容生成:
../3_review/视频介绍草稿.md
要求:
../3_review/视频介绍草稿.md,不能只在对话里临时输出review.htmlcd ../3_review
# 先确认视频介绍草稿已经落盘
test -s "视频介绍草稿.md"
# 6. 生成审核网页
node "$SKILL_DIR/scripts/generate_review.js" ../1_transcribe/subtitles_words.json ../2_analysis/auto_selected.json ../1_transcribe/audio.wav
# 输出: review.html
# 7. 启动审核服务器
node "$SKILL_DIR/scripts/review_server.js" 8899 "$VIDEO_PATH"
# 打开 http://localhost:8899
用户在网页中:
如果 test -s "视频介绍草稿.md" 失败:
注意:
audio_source.wav 试听;如果源路径失效,则继续复用当前目录已有的源音轨文件audio_timeline.json 会一并复制到审核目录,导出时按每个视频动态校准时间轴,不写死固定偏移[
{"text": "大", "start": 0.12, "end": 0.2, "isGap": false},
{"text": "", "start": 6.78, "end": 7.48, "isGap": true}
]
[72, 85, 120] // Claude 分析生成的预选索引
VOLCENGINE_API_KEY=xxx # 火山引擎 API Key(方案 A 需要)
DEFAULT_OUTPUT_DIR=xxx # 默认输出目录
ASR_ENGINE= # 转录方案: volcengine / whisper,留空每次询问
CUT_KEEP_PADDING_MS=300 # 保留片段语音边界前后缓冲
CUT_MIN_DELETE_MS=120 # 小于该时长的删除段默认忽略
CROSSFADE_MS=30 # 片段音频接缝淡化
获取指南:https://my.feishu.cn/wiki/Gh0MwxHePidsYfkIx7zcvJQynqc?from=from_copylink
# 编辑 .env 填入 VOLCENGINE_API_KEY=xxx