视频剪辑里最耗时间的不是“动手剪”这个动作,而是看完素材、做取舍、找剪辑点、反复调整的过程。最近我尝试用 AI Agent 做自动化剪辑,把整条流程拆成了 3 个 Skill:素材分析与转写、剪辑脚本生成、FFmpeg 渲染执行。跑通之后,从原始视频到成片基本可以做到“人工只审核,不手拖时间轴”。
这篇文章会把这条链路完整写一遍,包括 Skill 的组织方式、每个 Skill 的 SKILL.md 和配套脚本、FFmpeg 参数怎么选、中间产物怎么设计,以及跑通后踩到的几个典型问题。适合已经接触过 Claude Code、Codex 等支持 Skill 机制的 Agent 工具,但又不知道拿它做什么实际项目的开发者;也适合想用 AI 剪辑提高短视频生产率的博主或内容团队。
下面按“机制理解 -> 环境准备 -> 3 个 Skill 逐一实现 -> 串联运行 -> 排错 -> 生产建议”的顺序展开。
1. 先理解 Skill 在 AI 自动化剪辑中的定位
1.1 什么是 Skill,它解决什么问题
Skill 可以理解成一个“带说明书的能力包”。在 Claude Code、Codex 以及很多 Agent 框架里,Skill 通常由两部分组成:
SKILL.md:告诉 Agent 这个能力什么时候用、怎么用、有什么约束。- 配套脚本、提示词模板、参考文件:真正执行能力所需的材料。
没有 Skill 时,Agent 面对“帮我剪一段视频”这类任务,往往要反复追问用户,而且每次执行逻辑都可能飘。有了 Skill,Agent 会先读到固定规则,再按规则完成输入、处理、输出,结果稳定很多。
放到自动化剪辑场景里,Skill 的价值尤其明显:剪辑不是一个动作,而是一条流水线。流水线的每一步都有明确输入和输出,正好适合拆成独立 Skill。
1.2 为什么把剪辑流程拆成 3 个 Skill
我最初想用一个 Skill 搞定剪辑,结果发现上下文太长、Agent 经常混淆“先转写还是先剪”,而且中间产物无法复用。于是拆成 3 个,职责非常清楚。
| Skill 名称 | 职责 | 输入 | 输出 |
|---|---|---|---|
| transcribe | 素材分析与转写 | 原始视频文件 | 带时间戳的文本和素材清单 |
| script_builder | 剪辑脚本生成 | 转写文本、素材清单 | 结构化剪辑脚本 JSON |
| renderer | FFmpeg 渲染执行 | 剪辑脚本 JSON、原始素材 | 最终成片 MP4 |
拆开之后,每个 Skill 的上下文都很短,Agent 不需要在一个提示词里同时处理视频文件、字幕文本和 FFmpeg 参数。出了问题时,也只要排查对应环节。
1.3 Skill 之间的数据流
数据流如下:
原始视频 -> Skill 1: transcribe -> transcript.json + materials.json -> Skill 2: script_builder -> edit_script.json -> Skill 3: renderer -> final.mp4关键是中间产物必须是结构化数据。之前我用纯文本传结果,Agent 很容易解析错;改成 JSON 后,渲染脚本可以直接读取,不再依赖大模型二次猜测。
2. 准备视频剪辑工作台:环境、依赖与目录规范
2.1 环境要求
建议在 Linux 或 macOS 上跑,Windows 也能跑,但路径和编码问题会多一些。Python 版本建议 3.10 及以上。
| 软件 | 版本建议 | 用途 |
|---|---|---|
| Python | 3.10+ | 运行各 Skill 脚本 |
| FFmpeg | 4.4+ | 视频解析、剪辑、渲染 |
| Whisper / faster-whisper | 最新稳定版 | 语音转写 |
| ffprobe | 随 FFmpeg 安装 | 检查视频参数 |
这里的 Whisper 用于把视频里的语音转成带时间戳的文本,是后续剪辑脚本生成的依据。faster-whisper 在 CPU 上的转写速度通常比原版 Whisper 快,如果你的机器没有独立显卡,优先选它。
2.2 安装核心依赖
在 Ubuntu/Debian 上先装系统依赖:
sudo apt update sudo apt install -y ffmpeg python3-venv python3-pipmacOS 使用 Homebrew:
brew install ffmpeg python然后创建 Python 虚拟环境并安装转写依赖:
mkdir -p ~/video-agent cd ~/video-agent python3 -m venv .venv source .venv/bin/activate pip install faster-whisper验证 FFmpeg 是否可用:
ffmpeg -version ffprobe -version如果输出版本信息正常,说明后续渲染环节的基础工具已经就绪。
2.3 工作目录和输入输出规范
目录结构如下:
video-agent/ ├── skills/ │ ├── transcribe/ │ │ ├── SKILL.md │ │ └── run.py │ ├── script_builder/ │ │ ├── SKILL.md │ │ └── run.py │ └── renderer/ │ ├── SKILL.md │ └── run.py ├── input/ ├── output/ └── work/input/:放原始素材,所有 Skill 只读这个目录。work/:放中间产物,如转写结果、剪辑脚本 JSON。output/:放最终成片。
目录规范的意义是让 3 个 Skill 有共同约定。Skill 不关心文件从哪里来,只关心按约定路径读写,这样每个 Skill 都可以单独调试。
3. 第一个 Skill:素材分析与转写
3.1 Skill 的目标和输出
第一个 Skill 的目标是把“一段视频”变成“一段可阅读、可检索、可定位的文字”。
输出包括两部分:
transcript.json:每句语音的起止时间、文本内容。materials.json:素材本身的时长、分辨率、编码信息。
以后剪辑脚本生成时,只看这两个文件,就能决定哪句话留下、哪句话删掉、哪个片段作为结尾。
3.2 SKILL.md 的写法
在skills/transcribe/SKILL.md中写明触发条件、步骤和输出规范:
--- name: transcribe description: 对原始视频做语音转写,输出带时间戳的 JSON 结果。 --- # 素材分析与转写 ## 适用场景 - 需要知道视频素材里说了什么。 - 需要为后续剪辑生成带时间戳的文本依据。 ## 执行步骤 1. 用 ffprobe 读取素材基本参数。 2. 使用 faster-whisper 转写语音。 3. 将结果写入 work/transcript.json。 4. 将素材信息写入 work/materials.json。 ## 输出约束 - transcript.json 必须包含 segments 数组。 - 每个 segment 必须有 start、end、text 字段。 - 时间单位使用秒,保留 2 位小数。SKILL.md 不写具体代码,而是告诉 Agent 执行规则。代码放在run.py,Agent 只需要调用脚本。
3.3 转写脚本实现
skills/transcribe/run.py核心逻辑:
import json import sys from pathlib import Path from faster_whisper import WhisperModel def probe_material(video_path: Path) -> dict: import subprocess cmd = [ "ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", str(video_path) ] result = subprocess.run(cmd, capture_output=True, text=True) return json.loads(result.stdout) def transcribe(video_path: Path, output_dir: Path) -> None: model = WhisperModel("small", device="cpu", compute_type="int8") segments, info = model.transcribe( str(video_path), language="zh", word_timestamps=False, vad_filter=True, ) transcript = {"segments": []} for seg in segments: transcript["segments"].append({ "start": round(seg.start, 2), "end": round(seg.end, 2), "text": seg.text.strip(), }) (output_dir / "transcript.json").write_text( json.dumps(transcript, ensure_ascii=False, indent=2), encoding="utf-8", ) if __name__ == "__main__": video_path = Path(sys.argv[1]) output_dir = Path(sys.argv[2]) output_dir.mkdir(parents=True, exist_ok=True) materials = probe_material(video_path) (output_dir / "materials.json").write_text( json.dumps(materials, ensure_ascii=False, indent=2), encoding="utf-8", ) transcribe(video_path, output_dir)这里有几个关键点:
vad_filter=True可以过滤静音,减少无意义转写内容。- 中文素材建议显式传
language="zh",不传也能自动识别,但有时会在开头混入少量英文误识别。 compute_type="int8"在 CPU 上更快,如果使用 GPU,可以换成float16。
3.4 验证转写结果
运行命令:
cd ~/video-agent source .venv/bin/activate python skills/transcribe/run.py input/raw_1.mp4 work正常输出是work/transcript.json。查看内容:
{ "segments": [ { "start": 0.82, "end": 4.16, "text": "今天我们来聊一个自动剪辑的完整方案" }, { "start": 4.32, "end": 8.25, "text": "这条视频会分为三个部分" } ] }检查标准是:文本有没有明显漏字、时间戳是否连续、结尾时间是否和素材实际时长接近。如果时间戳整体偏移,可能是模型加载或音频采样导致,后面在第 7 节排查。
4. 第二个 Skill:自动生成剪辑脚本
4.1 从文本到剪辑决策
第二个 Skill 负责“决定剪哪里”。它读入转写文本,按规则选择保留片段。
剪辑规则可以很灵活,比如:
- 开头 5 秒的无效寒暄删掉。
- 包含“总结一下”的段落优先保留。
- 单句时长超过 15 秒且没有停顿的片段降权。
- 提前设置的总时长目标,超出的部分优先删语气词和重复表达。
规则越明确,输出越稳定。不要指望 Agent 凭感觉判断,需要用 JSON 把规则固化下来。
4.2 剪辑脚本 JSON 结构
edit_script.json的结构如下:
{ "version": 1, "source": "input/raw_1.mp4", "target_duration": 60, "clips": [ { "id": "clip_001", "start": 4.32, "end": 28.50, "reason": "核心观点介绍" }, { "id": "clip_002", "start": 34.10, "end": 76.40, "reason": "案例演示" } ], "order": ["clip_001", "clip_002"] }clips是真正需要保留的时间片段,order是片段顺序。渲染器只认这个字段,不做任何剪辑决策。
4.3 生成逻辑与规则约束
在 Agent 环境下,SKILL.md 会要求模型读取transcript.json和materials.json,按照规则生成 JSON。为了不依赖 Agent 也能演示,可以写一个本地规则脚本作为降级方案。
skills/script_builder/run.py的示例逻辑:
import json import sys from pathlib import Path def build_script(transcript_path: Path, materials_path: Path) -> dict: transcript = json.loads(transcript_path.read_text(encoding="utf-8")) materials = json.loads(materials_path.read_text(encoding="utf-8")) clips = [] for i, seg in enumerate(transcript["segments"]): text = seg["text"] duration = seg["end"] - seg["start"] # 规则1:删除过短且像语气词的内容 if duration < 1.2 and len(text) < 5: continue # 规则2:保留包含关键词的片段 if any(kw in text for kw in ["总结", "关键", "案例", "第一步"]): clips.append({ "id": f"clip_{i:03d}", "start": seg["start"], "end": seg["end"], "reason": "关键词命中", }) return { "version": 1, "source": materials.get("format", {}).get("filename", "input/raw_1.mp4"), "target_duration": 60, "clips": clips, "order": [c["id"] for c in clips], } if __name__ == "__main__": transcript_path = Path(sys.argv[1]) materials_path = Path(sys.argv[2]) output_path = Path(sys.argv[3]) script = build_script(transcript_path, materials_path) output_path.write_text( json.dumps(script, ensure_ascii=False, indent=2), encoding="utf-8", )这里的规则脚本偏简单,但它证明了一条重要原则:剪辑脚本可以脱离模型单独生成。实际项目中,可以把“关键词命中”升级成大模型打分,也可以把“删除语气词”升级为文本分类,但数据结构不需要变。
4.4 验证剪辑脚本
运行:
python skills/script_builder/run.py work/transcript.json work/materials.json work/edit_script.json检查生成的edit_script.json:
- 每个
start和end是否在素材时长范围内。 - 片段之间是否有重叠,重叠会导致渲染结果重复。
order中的 id 是否都能在clips中找到。
如果片段过多,说明过滤规则太宽松;如果没有片段,说明关键词规则太严。这是调规则参数的地方。
5. 第三个 Skill:FFmpeg 自动执行与渲染
5.1 把剪辑脚本翻译成 FFmpeg 命令
第三个 Skill 不关心为什么这样剪,它只负责执行。核心任务是把edit_script.json翻译成 FFmpeg 命令。
最朴素的实现方式是逐段切片,再拼接:
ffmpeg -ss 4.32 -to 28.50 -i input/raw_1.mp4 -c copy work/clip_001.mp4 ffmpeg -ss 34.10 -to 76.40 -i input/raw_1.mp4 -c copy work/clip_002.mp4再用 concat 协议或 filter 拼接。但直接-c copy在转场处容易出现时间戳错乱,所以更稳妥的方式是统一转成相同编码参数后再拼接。
5.2 渲染脚本实现
skills/renderer/run.py的核心逻辑:
import json import subprocess import sys from pathlib import Path def render(script_path: Path, output_path: Path) -> None: script = json.loads(script_path.read_text(encoding="utf-8")) source = script["source"] clips = {c["id"]: c for c in script["clips"]} part_files = [] for idx, clip_id in enumerate(script["order"]): clip = clips[clip_id] part_path = output_path.parent / f"part_{idx:02d}.mp4" part_files.append(part_path) cmd = [ "ffmpeg", "-y", "-ss", str(clip["start"]), "-to", str(clip["end"]), "-i", source, "-c:v", "libx264", "-preset", "veryfast", "-crf", "20", "-c:a", "aac", "-b:a", "192k", "-avoid_negative_ts", "make_zero", str(part_path), ] subprocess.run(cmd, check=True, capture_output=True, text=True) concat_file = output_path.parent / "concat.txt" concat_file.write_text( "\n".join(f"file '{p.name}'" for p in part_files), encoding="utf-8", ) merge_cmd = [ "ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", str(concat_file), "-c", "copy", str(output_path), ] subprocess.run(merge_cmd, check=True, capture_output=True, text=True) if __name__ == "__main__": render(Path(sys.argv[1]), Path(sys.argv[2]))这段脚本使用-c:v libx264 -preset veryfast -crf 20把每个片段统一转成 H.264,音频转成 AAC。最后用 concat 列表无损拼接。
5.3 关键参数说明
| 参数 | 含义 | 取值建议 |
|---|---|---|
-ss | 剪切起点 | 设置为片段开始时间 |
-to | 剪切终点 | 设置为片段结束时间 |
-crf | 画质控制,越小越清晰文件越大 | 20 左右适合短视频分发 |
-preset veryfast | 编码速度与压缩率平衡 | 自动化批量处理时用 veryfast |
-avoid_negative_ts make_zero | 修正切片后时间戳偏移 | 切片时建议加上 |
-c copy | 不重新编码直接复制流 | 只适合同参数拼接阶段 |
-c copy能加快最终拼接速度,但前提是前面所有分片都使用完全一致的编码参数,否则拼接后可能出现音画不同步。
6. 三个 Skill 串联运行:从原始视频到成片
6.1 串联方式
3 个 Skill 可以通过一个简单的 Shell 脚本串联。把输入素材放到input/后,依次执行:
cd ~/video-agent source .venv/bin/activate python skills/transcribe/run.py input/raw_1.mp4 work python skills/script_builder/run.py work/transcript.json work/materials.json work/edit_script.json python skills/renderer/run.py work/edit_script.json output/final.mp4看起来只是三行命令,但每一步之间都必须检查上一步输出成功后再继续。实际工程中建议写一个pipeline.sh,并在每步之间检查退出码:
set -e python skills/transcribe/run.py input/raw_1.mp4 work || exit 1 python skills/script_builder/run.py work/transcript.json work/materials.json work/edit_script.json || exit 1 python skills/renderer/run.py work/edit_script.json output/final.mp4 || exit 1set -e的作用是遇到错误立即退出,避免带着坏中间产物继续跑。
6.2 完整运行示例
假设原始素材时长 120 秒,目标剪辑出 60 秒成片。转写后,脚本生成两个片段,渲染器输出final.mp4。
运行日志:
[transcribe] 已转写 32 条片段 [script_builder] 已生成 2 个剪辑片段 [renderer] 已生成 output/final.mp4正常结果是一个时长约 60 秒、音画同步、内容连续的 MP4 文件。
6.3 验证成片质量
使用 ffprobe 检查成片信息:
ffprobe -v quiet -print_format json -show_format output/final.mp4检查字段:
duration是否接近目标时长。bit_rate是否在合理范围。- 播放时重点听片段交界处是否有爆音、画面是否卡顿。
如果成片时长和目标时长差距很大,大概率是剪辑脚本生成的片段过短或过多,回到第 4 节调整规则。
7. 常见问题与排查路径
7.1 转写结果不准、时间戳偏移
现象:字幕内容和语音对不上,或者每句的起点整体提前。
可能原因:
- 模型选择过小。
- 没有开启静音过滤。
- 视频本身有背景音乐或多人说话。
排查方式:
- 换
small或medium模型试跑一段。 - 对比语音开头和首句时间戳之间的差值。
- 检查视频是否在录音阶段就存在音频延迟。
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 文本错别字多 | 模型过小 | 换更大模型对比 | 使用 small/medium,必要时开启initial_prompt |
| 时间戳整体偏移 | 音频流采样率问题 | 用 ffprobe 查看音频参数 | 先用 ffmpeg 统一重采样到 16kHz |
| 静音内容被转写 | 没有开 VAD | 检查 segment 是否过多 | 开启vad_filter=True |
7.2 渲染后音画不同步
现象:画面正常,但声音比画面慢或快。
排查顺序:
- 检查
-ss放在-i前还是后。放前面可以快速定位到关键帧,但某些输入格式会丢帧;放后面更精确,但速度慢。 - 检查分片阶段是否所有片段统一编码。
- 检查最终 concat 阶段是否使用了
-c copy,如果分片参数不一致,这里最容易出问题。
推荐做法是切片阶段统一转码,拼接阶段才用-c copy。不要在生产流程中使用-c copy做切片,除非确认源文件关键帧结构完全一致。
7.3 Agent 没有正确加载 Skill
现象:调用 3 个 Skill 时,Agent 总是自己乱写命令,不读 SKILL.md。
常见原因:
- SKILL.md 放错了目录。
- Skill 描述不明确,Agent 没有识别到适用场景。
- 项目目录没有按约定设置。
排查方式:
- 在 Agent 工作目录下执行
/skills或对应当前工具的 skill 列表命令,确认 Skill 是否被加载。 - 打开 SKILL.md,检查 frontmatter 里的
name和description是否清晰。 - 直接把 SKILL.md 中的描述改成“当用户要求处理原始视频并生成剪辑脚本时,必须使用本 Skill”。
7.4 FFmpeg 找不到编码器
现象:
Unknown encoder 'libx264'可能原因:FFmpeg 版本较老,或者安装时没有包含 H.264 编码器。
检查方式:
ffmpeg -encoders | grep 264处理建议:
- Ubuntu 重新安装:
sudo apt install ffmpeg - macOS 重新安装:
brew install ffmpeg - 如果必须使用当前版本,可以改用
mpeg4编码器,但兼容性不如 H.264,只适合本地测试。
8. 生产环境落地建议
8.1 学习环境与生产环境的差异
跑通演示和真正上线是两回事。
| 维度 | 学习/演示环境 | 生产环境 |
|---|---|---|
| 素材来源 | 本地固定文件 | 上传队列、对象存储 |
| 任务触发 | 手动执行脚本 | 任务队列、事件触发 |
| 转写模型 | 本地 CPU int8 | GPU 或多实例并发 |
| 日志 | print 输出 | 结构化日志、任务 ID 关联 |
| 异常处理 | 脚本失败即停 | 重试、告警、人工兜底 |
| 安全 | 单人本机 | 权限隔离、素材访问控制 |
生产环境至少还要考虑:一次转写可能消耗大量 CPU,需要限制并发;FFmpeg 渲染是长耗时任务,需要记录每个任务的状态,避免重复渲染;素材和成片都要有备份和清理策略。
8.2 可复用检查清单
跑通本项目或部署到生产前,按下面清单检查:
- [ ]
ffmpeg、ffprobe是否可用。 - [ ] Python 虚拟环境是否激活,依赖是否安装完成。
- [ ] 输入素材是否存在,命名是否符合规范。
- [ ] 3 个 Skill 的目录路径是否完整。
- [ ] SKILL.md 中的输出路径是否与脚本一致。
- [ ] 转写结果中是否包含
segments。 - [ ] 剪辑脚本中的时间戳是否都在素材时长范围内。
- [ ] 片段之间是否有重叠。
- [ ] 渲染前是否确认编码器可用。
- [ ] 成片播放检查音画同步和转场。
8.3 扩展方向
跑通 3 个 Skill 之后,可以继续扩展:
- 字幕压制:在 renderer 中增加
subtitlesfilter,把转写文本自动压成视频字幕。 - 多素材拼接:让 script_builder 支持多个 sources,输出结构增加
source字段。 - 镜头级剪辑:用镜头检测算法找到视频切帧点,再结合转写文本,实现“按句+按镜头”双维度剪辑。
- 审核流程:在 script_builder 和 renderer 之间增加一个人工审核环节,只审核 JSON,不处理视频,效率会比重新剪辑高很多。
自动化剪辑真正的价值,不是完全取代剪辑师,而是把“理解素材”和“执行操作”变成标准化流程。Skill 是这套流程最好的载体:每个能力独立维护、独立测试,组合起来就是一条完整生产线。
如果只记住一条经验,我的建议是:先把剪辑决策和剪辑执行彻底分开。转写只负责生成文本,剪辑脚本只负责“决定剪哪里”,渲染只负责“执行命令”。这样 3 个 Skill 各自职责清晰,整个系统才可能稳定跑通。