by znyupup
AI Agent Skill for automated vlog editing. Feed raw footage, get a finished video. Powered by ffmpeg + Whisper + Vision API.
# Add to your Claude Code skills
git clone https://github.com/znyupup/ai-video-editing-skillGuides for using ai agents skills like ai-video-editing-skill.
Author: nyx研究所 · GitHub · B站 @nyx研究所 · 小红书 @nyx研究所 · X @znyupup_music
把一堆手机拍的旅行素材,用AI自动剪成一个完整vlog。 最小依赖:ffmpeg + Python(whisper或FunASR+Pillow) + 视觉API。系统级只装ffmpeg,其余pip装。
| 工具 | 用途 | 安装 |
|---|---|---|
| ffmpeg | 视频裁剪/编码/拼接/抽帧/音量检测 | brew install ffmpeg (macOS) / apt install ffmpeg (Linux) |
| Python 3.9+ | 脚本胶水 | macOS/Linux 系统自带 |
先检测系统是否已安装所需包,不要无脑创建 venv:
# 检测 whisper
python3 -c "import whisper; print('✅ whisper已安装:', whisper.__file__)"
# 检测 Pillow
python3 -c "from PIL import Image; print('✅ Pillow已安装')"
⚠ 重要:不要用 2>/dev/null 吃掉错误!要看到实际报错才能判断是真没装还是PATH问题。
⚠ macOS 注意: 检查系统已安装的包再决定是否需要 venv。如果系统 python3 已经能 import whisper 和 from PIL import Image,直接用就行,不要盲目创建 venv 导致找不到已有的包。
仅在检测不通过时才安装:
# 方案A: 直接装到用户环境(推荐)
pip install openai-whisper Pillow
# 方案B: 如果用户环境有冲突,再用 venv
python3 -m venv .venv && source .venv/bin/activate
pip install openai-whisper Pillow
⚠ 不要每个项目都新建 venv 重装一遍!whisper模型文件1.4GB,pip install也要几分钟。
⚠ ASR引擎二选一:Whisper 或 FunASR
本工作流支持两个ASR引擎,优先用 whisper,whisper 装不上就用 FunASR:
| Whisper (OpenAI) | FunASR (阿里达摩院) | |
|---|---|---|
| 安装 | pip install openai-whisper |
pip install funasr modelscope torchaudio |
| 模型 | medium (1.4GB, 从GitHub下载) | paraformer-zh (1.05GB, 从ModelScope下载) |
| 附加模型 | 无 | VAD模型 + 标点模型 (首次运行自动下载, 共~50MB) |
| 中文效果 | 好 | 好(与whisper medium相当,细节略多) |
| 速度 | 14s音频 ≈ 5-10s (CPU) | 14s音频 ≈ 1.9s (CPU, RTF=0.132) ⚡ |
| 时间戳粒度 | 句级别 (每句话一个start/end) | 字级别 (每个字一个时间戳) |
| 模型下载源 | GitHub (国内不稳定,容易失败) | ModelScope (国内极快,~15MB/s) |
| 隐性依赖 | torch | torch + torchaudio (⚠ 必须额外装) |
检测已安装的ASR引擎:
python3 -c "import whisper; print('✅ whisper可用')" 2>/dev/null \
|| python3 -c "from funasr import AutoModel; print('✅ funasr可用')" 2>/dev/null \
|| echo "❌ 没有可用的ASR引擎,需要安装一个"
⚠ FunASR 安装注意事项:
pip install funasr modelscope 不会自动装 torchaudio,import 时会报 No module named 'torchaudio'pip install funasr modelscope torchaudio~/.cache/modelscope/hub/models/iic/ 目录下标题卡方案自动选择:
ffmpeg -filters 2>&1 | grep drawtext
# 有drawtext → 直接用ffmpeg,不需要Pillow
# 没有 → 用Pillow生成透明PNG再overlay(macOS brew ffmpeg通常没编freetype)
可选(参考分析阶段用):
which yt-dlp && echo "✅ yt-dlp已安装" || pip install yt-dlp
python3 -c "import scenedetect" 2>/dev/null && echo "✅ scenedetect已安装" || pip install scenedetect
需要一个能理解图像内容的视觉模型API,用于分析素材画面。
要求:
推荐模型:
| 模型 | 费用 | 说明 |
|---|---|---|
| 智谱 GLM-4.6V-Flash | 免费 | 注册 https://open.bigmodel.cn 即用,中文理解好 |
| GPT-4o | 付费 | 效果最好 |
| Qwen-VL | 付费 | 阿里云,中文好 |
调用示例:
import base64, json, urllib.request
API_URL = 'YOUR_VISION_API_ENDPOINT' # 替换为你的视觉模型端点
API_KEY='***' # 替换为你的API Key
MODEL = 'YOUR_MODEL_NAME' # 替换为你的模型名
with open('frame.jpg', 'rb') as f:
img_b64 = base64.b64encode(f.read()).decode()
payload = {
'model': MODEL,
'messages': [{'role': 'user', 'content': [
{'type': 'image_url', 'image_url': {'url': f'data:image/jpeg;base64,{img_b64}'}},
{'type': 'text', 'text': '简洁描述画面内容,标注:镜头类型(远/全/中/近/特写)、拍摄手法(固定/手持/移动)、画面氛围。格式:内容|类型|手法|氛围'}
]}],
'max_tokens': 200
}
req = urllib.request.Request(API_URL, json.dumps(payload).encode(),
{'Content-Type': 'application/json', 'Authorization': f'Bearer {API_KEY}'})
with urllib.request.urlopen(req, timeout=30) as resp:
result = json.loads(resp.read())
print(result['choices'][0]['message']['content'])
⚠ 视觉API调用注意事项:
data:image/jpeg;base64,{b64} 放入 image_url目标:了解你有什么素材。
# 批量获取素材信息
for f in footage/*.{MOV,mp4,MP4}; do
echo "=== $f ==="
ffprobe -v quiet -print_format json -show_format -show_streams "$f" \
| python3 -c "import json,sys; d=json.load(sys.stdin); \
s=d['streams'][0]; f=d['format']; \
print(f\" 时长: {float(f['duration']):.1f}s\"); \
print(f\" 分辨率: {s['width']}x{s['height']}\"); \
print(f\" 编码: {s['codec_name']}\")"
done
输出一份素材清单:数量、总时长、分辨率分布、拍摄时间范围。
目标:建立"好vlog长什么样"的认知。不做这步直接剪,效果会很差。
步骤:
yt-dlp 下载720p视频 + 音频whisper 转录旁白(提取叙事结构)scenedetect 检测镜头切换(统计节奏)ffmpeg 抽关键帧 + 视觉API分析(理解画面构成)已验证的规律:
剪辑教程要点:
目标:让AI理解每条素材的内容。每条素材做三维分析:
a) 音频分析 — ASR转录(Whisper 或 FunASR)
# 提取音频(16kHz单声道WAV,两个引擎通用的最佳输入格式)
ffmpeg -i footage/INPUT.MOV -vn -acodec pcm_s16le -ar 16000 -ac 1 /tmp/audio.wav
方案1: Whisper转录(优先使用)
import whisper, json
model = whisper.load_model('medium')
result = model.transcribe('/tmp/audio.wav', language='zh')
segments = [{"start": s['start'], "end": s['end'], "text": s['text']} for s in result['segments']]
# 输出: [{"start": 0.5, "end": 2.3, "text": "我们现在在中央大街"}, ...]
方案2: FunASR转录(Whisper装不上时的替代)
import warnings
warnings.filterwarnings("ignore")
from funasr import AutoModel
model = AutoModel(
model="paraformer-zh", # Paraformer-large,中文ASR主模型(1.05GB)
vad_model="fsmn-vad", # 语音活动检测,自动切分静音段
punc_model="ct-punc", # 自动加标点
log_level="ERROR",
disable_update=True # 跳过版本检查,加快启动
)
result = model.generate(input="/tmp/audio.wav")
text = result[0]["text"] # 完整识别文本(已加标点)
timestamps = result[0].get("timestamp", []) # 字级别时间戳 [[start_ms, end_ms], ...]
# ⚠ FunASR时间戳是字级别(毫秒),需要聚合成句级别才能和whisper格式统一
# 聚合策略:按标点符号(。!?,)切分成句子
segments = []
if timestamps and text:
puncs = set("。!?,,.!?")
current_start = timestamps[0][0] / 1000.0
current_text = ""
chars = list(text)
for i, (char, ts) in enumerate(zip(chars, timestamps)):
current_text += char
if char in puncs or i == len(chars) - 1:
segments.append({
"start": round(current_start, 1),
"end": round(ts[1] / 1000.0, 1),
"text": current_text.strip()
})
if i < len(chars) - 1:
current_start = timestamps[i + 1][0] / 1000.0
current_text = ""
# 输出格式与whisper一致: [{"start": 0.6, "end": 2.9, "text": "我们现在在中央大街的入口,"}, ...]
⚠ FunASR 输出差异注意:
"",timestamps 为空 []b) 音量检测 — ffmpeg
ffmpeg -i footage/INPUT.MOV -af volumedetect -f null /dev/null 2>&1 | grep volume
# mean_volume: 平均音量(dB) max_volume: 峰值音量(dB)
# mean < -40dB 基本无声 mean > -20dB 有明显声音/语音
c) 视觉分析 — ffmpeg抽帧 + 视觉理解模型
# 抽帧策略(按时长分段)
# ≤20s → 3帧(首/中/尾)
# 20-60s → 5帧
# >60s → 每15s一帧
# 抽首/中/尾三帧示例(缩到720p节省带宽)
ffmpeg -i footage/INPUT.MOV -vf "select=eq(n\,0),scale=1280:-1" -frames:v 1 -q:v 2 frame_start.jpg
ffmpeg -i footage/INPUT.MOV -vf "select=eq(n\,MIDDLE),scale=1280:-1" -frames:v 1 -q:v 2 frame_mid.jpg
ffmpeg -i footage/INPUT.MOV -vf "reverse,scale=1280:-1" -frames:v 1 -q:v 2 frame_end.jpg
d) 输出格式(每条素材一个JSON)
{
"filename": "clip_001.MOV",
"duration": 15.3,
"resolution": "1920x1080",
"visual": [
{"time": "0:00", "description": "...", "shot_type": "近景", "camera": "手持", "mood": "温馨"}
],
"audio": {
"has_speech": true,
"mean_volume_db": -20.5,
"max_volume_db": -3.2,
"transcript": [{"start": 0.5, "end": 2.3, "text": "..."}]
}
}
目标:在给LLM之前,自动标注每条素材的"推荐有效区间",让LLM专注叙事编排。
为什么需要这步: 原始手机素材普遍存在:开头录制口令、同一句话重复多遍、前1-2s举手机晃动、说完话后拖很长的无意义画面。如果把这些原始数据直接给LLM,LLM输出的plan还需要逐条修补。
预处理做四件事:
RECORDING_CUES = [
"开始了", "好了开始", "开始录了", "好了 开始",
"走了", "好了", "来了", # 仅在前3s内出现时算口令
]
def detect_cues(transcript_segments, duration):
"""检测录制口令,返回skip zones"""
skip_zones = []
for seg in transcript_segments:
text = seg['text'].strip()
if seg['start'] < 3.0 and any(text.startswith(c) or text == c for c in RECORDING_CUES):
skip_zones.append({
'type': 'recording_cue',
'range': [seg['start'], seg['end']],
'text': text,
'action': 'skip'
})
if seg['end'] > duration - 3.0 and text in ["好了", "走了", "好了 走"]:
skip_zones.append({
'type': 'recording_cue',
'range': [seg['start'], seg['end']],
'text': text,
'action': 'skip'
})
return skip_zones
from difflib import SequenceMatcher
def detect_repeats(segments, threshold=0.6):
"""检测重复语音,标记建议跳过的重复段"""
repeats = []
for i, a in enumerate(segments):
for j, b in enumerate(segments):
if j <= i: continue
ratio = SequenceMatcher(None, a['text'], b['text']).ratio()
if ratio >= threshold and len(a['text']) > 4:
skip = a if len(a['text']) <= len(b['text']) else b
repeats.append({
'type': 'repeat',
'range': [skip['start'], skip['end']],
'text': skip['text'],
'kept': b['text'] if skip == a else a['text'],
'action': 'suggest_skip'
})
return repeats
def detect_trim_points(segments, duration, mean_volume_db):
"""基于语音+音量推荐起止点"""
suggested_start = 0.0
suggested_end = duration
if segments:
first_speech = segments[0]['start']
last_speech_end = segments[-1]['end']
if first_speech > 2.0:
suggested_start = max(0, first_speech - 0.5)
if duration - last_speech_end > 3.0:
suggested_end = last_speech_end + 1.5
else:
if duration > 5.0:
suggested_start = 1.0
return suggested_start, suggested_end
| 模式 | 纯画面上限 | 口令处理 | 重复处理 | 起手晃动 |
|---|---|---|---|---|
| strict | 8-10s | 全部去除 | 只留1遍 | 去1.5s |
| normal | 12-15s | 去除明确口令 | 去明显重复 | 去1.0s |
| loose | 20-25s | 仅去"开始了" | 保留大部分 | 去0.5s |
预处理结果追加到素材分析数据里:
━━━ clip_012.mp4 | 时长25.8s | 1920x1080 ━━━
【画面】...
【语音】...
【精修建议】模式: normal
原始区间: [0.0 - 25.8]
推荐区间: [2.4 - 25.8]
⊘ [0.0-2.4] 跳过: 录制口令 "开始了"
有效语音: [2.62-25.8]
⚠ 重要原则:预处理只做标注和建议,不做硬裁剪。LLM保留最终决定权。
目标:把预处理后的素材数据交给LLM,生成剪辑方案。
输入:素材分析 + 精修建议(推荐区间) + 参考研究结论 输出:剪辑方案JSON(结构见下方schema)
{
"title": "视频标题",
"structure": [
{
"section": "段落名 — 副标题",
"description": "本段落内容概述",
"clips": [
{
"file": "素材文件名.mp4",
"start": 0.0,
"end": 12.0,
"note": "画面内容简述",
"subtitle": "保留的语音文字"
}
]
}
],
"bgm_suggestion": "BGM风格建议(含genre/mood/instruments/tempo/bpm)",
"editing_notes": "整体剪辑说明"
}
约束:
详细prompt模板见 templates/edit_plan_prompt.md。
目标:把LLM输出的剪辑方案做成图文并茂的网页,方便用户直观Review。
Dashboard 包含两个面板:
使用 gen_dashboard.py 脚本:
# 最小用法(只生成素材面板)
python3 scripts/gen_dashboard.py \
--analysis clip_analysis.json \
--plan edit_plan.json \
--footage footage/ \
--out output/
# 完整两面板(含成品QC帧)
python3 scripts/gen_dashboard.py \
--analysis clip_analysis.json \
--plan edit_plan.json \
--footage footage/ \
--video output/final.mp4 \
--out output/
支持两种分析数据格式(自动检测):
clip_analysis.json: {filename, duration, visual: [...], audio: {...}}clips_compact.json: {file, dur, visual: "string", speech: [...]}用户确认分镜方案后,再进入渲染阶段。
目标:自动校验LLM输出的plan,确保没有把任何语音从中间截断。
def validate_speech(plan_clips, speech_data):
"""检查每个clip的start/end是否截断了语音"""
issues = []
for clip in plan_clips:
f = clip['file']
cs, ce = clip['start'], clip['end']
if f not in speech_data: continue
for ss, se, txt in speech_data[f]:
if ss < ce and se > cs: # 语音与clip有交集
if ss < cs - 0.3: # 语音开始在clip之前
issues.append(f"截断开头: {f} clip从{cs}开始,但语音\"{txt}\"从{ss}开始")
if se > ce + 0.5: # 语音结束在clip之后
issues.append(f"截断结尾: {f} clip在{ce}结束,但语音\"{txt}\"到{se}结束")
return issues
校验不通过时:自动修复start/end,不需要回LLM重新编排。
def fix_speech_cuts(plan, speech_data, margin=0.3):
"""
自动修复语音截断问题。直接修改plan中的start/end。
参数:
plan: edit_plan dict (会被原地修改)
speech_data: dict, {filename: [(start, end, text), ...]}
margin: float, 语音边界容差(秒)
返回:
fixes: list of str, 修复日志
"""
fixes = []
for sec in plan["structure"]:
for clip in sec["clips"]:
f = clip["file"]
cs = float(clip["start"])
ce = float(clip["end"])
if f not in speech_data:
continue
for ss, se, txt in speech_data[f]:
if ss >= ce or se <= cs:
continue
# 开头截断:clip.start > speech.start + margin
if ss < cs - margin and se > cs:
old_start = cs
new_start = max(0, ss - 0.1)
clip["start"] = round(new_start, 1)
fixes.append(
f" 修复开头: {f} [{old_start}→{new_start}] "
f"语音\"{txt[:20]}\"从{ss}s开始"
)
cs = new_start
# 结尾截断:clip.end < speech.end - margin
if se > ce + margin and ss < ce:
old_end = ce
new_end = se + 0.2
clip["end"] = round(new_end, 1)
fixes.append(
f" 修复结尾: {f} [{old_end}→{new_end}] "
f"语音\"{txt[:20]}\"到{se}s结束"
)
ce = new_end
return fixes
def validate_and_fix(plan, speech_data):
"""
端到端校验+修复入口。
用法:
import json
# 加载plan
plan = json.load(open('edit_plan.json'))
# 加载语音数据 (格式: {filename: [{start, end, text}, ...]})
raw = json.load(open('clip_analysis.json'))
speech_data = {}
for clip_info in raw:
fname = clip_info['filename']
if clip_info['audio']['has_speech']:
speech_data[fname] = [
(s['start'], s['end'], s['text'])
for s in clip_info['audio']['transcript']
]
# 校验
all_clips = [c for sec in plan['structure'] for c in sec['clips']]
issues = validate_speech(all_clips, speech_data)
print(f'发现 {len(issues)} 个截断问题')
# 修复
fixes = fix_speech_cuts(plan, speech_data)
print(f'自动修复 {len(fixes)} 处')
# 二次校验确认
all_clips2 = [c for sec in plan['structure'] for c in sec['clips']]
issues2 = validate_speech(all_clips2, speech_data)
assert len(issues2) == 0, f'仍有 {len(issues2)} 个问题未修复!'
print('✅ 二次校验通过')
# 保存修复后的plan
json.dump(plan, open('edit_plan_fixed.json', 'w', ensure_ascii=False), indent=2)
"""
all_clips = [c for sec in plan['structure'] for c in sec['clips']]
issues = validate_speech(all_clips, speech_data)
fixes = fix_speech_cuts(plan, speech_data)
return issues, fixes
修复策略:
| 截断类型 | 检测条件 | 修复方式 |
|---|---|---|
| 开头截断 | clip.start > speech.start + margin | clip.start → speech.start - 0.1s |
| 结尾截断 | clip.end < speech.end - margin | clip.end → speech.end + 0.2s |
边界容差 (margin=0.3s): Whisper/FunASR 时间戳有±0.2-0.3s误差。修复后必须跑二次校验。
目标:把剪辑方案转成ffmpeg命令并执行。
# 单个片段裁剪+编码
ffmpeg -y -ss 00:00:02.400 -i footage/INPUT.mp4 -t 00:00:23.400 \
-vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1" \
-r 30 -c:v libx264 -preset medium -crf 18 -pix_fmt yuv420p \
-c:a aac -b:a 128k -ar 44100 -ac 2 \
-movflags +faststart segments/seg_001.mp4
# 拼接成品
ffmpeg -y -f concat -safe 0 -i concat.txt -c copy -movflags +faststart output.mp4
# 2倍速(视频+音频同步)
ffmpeg -i input.mp4 -filter:v "setpts=0.5*PTS" -filter:a "atempo=2.0" out.mp4
从各段落选最有视觉冲击力的镜头,快切拼接放在视频最前面。
选片原则:
import subprocess, os
highlights = [
('footage/clip_a.mp4', 1.5, 2.2),
('footage/clip_b.mp4', 2.0, 2.7),
# ... 每个0.7秒左右
]
for i, (f, start, end) in enumerate(highlights, 1):
dur = end - start
cmd = f'ffmpeg -y -ss {start} -i "{f}" -t {dur} ' \
f'-vf "scale=1920:1080:force_original_aspect_ratio=decrease,' \
f'pad=1920:1080:(ow-iw)/2:(oh-ih)/2" ' \
f'-c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p ' \
f'-an highlights/hl_{i}.mp4'
subprocess.run(cmd, shell=True)
# concat
with open('highlights/concat.txt', 'w') as f:
for i in range(1, len(highlights)+1):
f.write(f"file 'hl_{i}.mp4'\n")
subprocess.run('ffmpeg -y -f concat -safe 0 -i highlights/concat.txt '
'-c copy highlights/montage.mp4', shell=True)
用 Pillow 生成透明RGBA的PNG,ffmpeg overlay到视频画面上。不要用黑底标题卡。
from PIL import Image, ImageDraw, ImageFont, ImageFilter
W, H = 1920, 1080
title = "段落标题"
img = Image.new("RGBA", (W, H), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# 字体:macOS用冬青黑体,Linux用Noto Sans CJK
# font = ImageFont.truetype("/System/Library/Fonts/Hiragino Sans GB.ttc", 60) # macOS
# font = ImageFont.truetype("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", 60) # Linux
font = ImageFont.truetype("YOUR_FONT_PATH", 60)
bbox = draw.textbbox((0, 0), title, font=font)
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
x, y = (W - tw) // 2, (H - th) // 2
# 柔和阴影
shadow = Image.new("RGBA", (W, H), (0, 0, 0, 0))
sd = ImageDraw.Draw(shadow)
sd.text((x+2, y+2), title, fill=(0, 0, 0, 180), font=font)
shadow = shadow.filter(ImageFilter.GaussianBlur(radius=6))
img = Image.alpha_composite(img, shadow)
draw = ImageDraw.Draw(img)
draw.text((x, y), title, fill=(255, 255, 255, 240), font=font)
img.save("title_overlay.png")
overlay到视频上:
# 标题显示前3秒
ffmpeg -y -i section.mp4 -i title.png \
-filter_complex "overlay=0:0:enable='between(t,0,3)'" \
-c:v libx264 -preset medium -crf 18 -pix_fmt yuv420p \
-c:a aac -b:a 128k -movflags +faststart \
section_titled.mp4
⚠ overlay关键Pitfall:
shortest=1 — PNG只有1帧,会导致视频流立刻截断overlay=0:0:enable='between(t,0,3)' 最简单可靠✅ 正确方案:逐段编码 → concat copy
echo "file 'final_sec_01.mp4'" > concat.txt
echo "file 'final_sec_02.mp4'" >> concat.txt
ffmpeg -y -f concat -safe 0 -i concat.txt -c copy -movflags +faststart output.mp4
❌ 不要用xfade链式合并: 链式xfade会导致帧数累积丢失,后半段全黑。
段落间过渡:段落内硬切即可。如需渐变,每段首尾加fade比xfade更可靠。
BGM 可以手动添加,也可以用 AI 音乐生成工具(如 MiniMax music、Suno 等)。
风格映射参考(vlog常用):
| Vlog 风格 | genre | mood | instruments | tempo |
|---|---|---|---|---|
| 轻松日常 | indie pop | cheerful, carefree | ukulele, acoustic guitar | moderate |
| 文艺治愈 | folk, acoustic | warm, gentle | acoustic guitar, piano, strings | slow |
| 美食探店 | jazz, bossa nova | cozy, playful | piano, upright bass | moderate |
| 冰雪/冬季 | cinematic, ambient | serene, majestic | piano, celesta, strings | slow |
| 热带/海岛 | tropical house | sunny, relaxed | steel drums, marimba | upbeat |
| 城市探索 | lo-fi hip hop | chill, urban | keys, vinyl crackle | moderate |
# BGM 音量 10-15%(有语音的vlog要压低BGM)
ffmpeg -y -i video_no_bgm.mp4 -i bgm.m4a \
-filter_complex "[0:a]volume=1.0[orig];[1:a]volume=0.12[bgm];[orig][bgm]amix=inputs=2:duration=first:dropout_transition=2[aout]" \
-map 0:v -map "[aout]" -c:v copy -c:a aac -b:a 192k \
-movflags +faststart output/final.mp4
BGM 音量建议:
ffmpeg -i input.mp4 -filter:v "setpts=0.5*PTS" -filter:a "atempo=2.0" out.mp4
ffmpeg -i output.mp4 -f null - 检查实际frame数pip install funasr modelscope 不够,必须 pip install funasr modelscope torchaudio,否则 import 报 No module named 'torchaudio'# 验证
ffprobe -show_entries stream=duration -select_streams v -of csv=p=0 seg.mp4 # 视频时长
ffprobe -show_entries stream=duration -select_streams a -of csv=p=0 seg.mp4 # 音频时长
# 补齐
ffmpeg -y -i seg.mp4 -af "apad" -c:v copy -c:a aac -shortest seg_fixed.mp4
# 每段提取音频(apad补齐到视频时长)
ffmpeg -y -i seg.mp4 -vn -af "apad" -ar 48000 -ac 2 -c:a pcm_s16le -t {视频时长} seg.wav
# concat filter拼接音频(不是demuxer)
ffmpeg -y -i a.wav -i b.wav -filter_complex "[0:a][1:a]concat=n=2:v=0:a=1[aout]" -map "[aout]" -c:a aac audio.m4a
# 合并
ffmpeg -y -i video.mp4 -i audio.m4a -c:v copy -c:a copy output.mp4
剪映兼容编码 — 导入剪映音画不同步的修复:-r 30 -vsync cfr(固定帧率) + -x264-params "bframes=0"(无B帧) + -ar 48000 -ac 2(统一音频)
crf值对录屏类内容要低 — 录屏文字密集,crf 22码率不够会糊。录屏用-crf 18或-b:v 3M。一般vlog素材crf 22即可
多次重编码画质累积损失 — 剪辑→拼接→加背景→叠进度条,每次编码都损失画质。尽量一次filter链搞定,减少编码次数
后台ffmpeg进程泄漏 — kill后台任务时可能只杀shell不杀ffmpeg子进程。每次kill后ps aux | grep ffmpeg确认,残留用kill -9 PID
开工前第一件事确认输出分辨率 — 全流程统一分辨率不要中途改。录屏类用1080p(文字密集对分辨率敏感),vlog素材用原始分辨率或1080p
⚠️ overlay视频时音频被覆盖/丢失 — 用filter_complex overlay叠加进度条/水印等视频时,ffmpeg可能自动选取overlay源(静音)的音频而不是原视频的音频。必须用-map明确指定音频来源:
# ❌ 不指定map,音频可能来自overlay源(静音)
ffmpeg -i main.mp4 -i overlay.mp4 -filter_complex "overlay=0:0" output.mp4
# ✅ 明确map:视频取filter输出,音频取原视频
ffmpeg -i main.mp4 -i overlay.mp4 \
-filter_complex "[0:v][1:v]overlay=0:0[vout]" \
-map "[vout]" -map 0:a \
-c:v libx264 -c:a copy output.mp4
如果还不行(音频编解码问题),分步做更可靠:
# Step1: 只处理视频(-an去掉音频)
ffmpeg -i main.mp4 -i overlay.mp4 -filter_complex "overlay" -map "[vout]" -an video_only.mp4
# Step2: 把原始音频合回来
ffmpeg -i video_only.mp4 -i main.mp4 -map 0:v -map 1:a -c:v copy -c:a copy final.mp4
-shortest会丢掉最后几秒的音频。优先用-t {精确时长}替代,或者确保音视频等长后不加-shortestmy-vlog-project/
├── footage/ # 原始素材(用户提供)
│ ├── clip_001.MOV
│ └── ...
│
├── analysis/ # 阶段3输出
│ └── clip_analysis.json
│
├── edit_plan.json # 阶段4输出
├── edit_plan_fixed.json # 阶段4.5输出
│
├── output/
│ ├── dashboard.html # Dashboard网页
│ ├── thumbnails/ # 素材缩略图
│ ├── qc_frames/ # 成品QC帧
│ ├── segments/ # 裁剪后的片段
│ ├── highlights/ # 片头高光蒙太奇
│ ├── titles/ # 段落标题PNG
│ ├── sections/ # 带标题的段落视频
│ ├── bgm/ # BGM文件
│ ├── video_no_bgm.mp4 # 无BGM的完整视频
│ └── final.mp4 # 🎬 成品视频
│
└── reference/ # 阶段2输出(可选)
见 examples/ 目录下的示例文件。
素材文件夹 footage/
│
▼
阶段1: 素材盘点 → ffprobe批量扫描 → 清单
│
▼
阶段2: 参考研究 → 下载优质vlog → 分析节奏(可选)
│
▼
阶段3: 素材三维分析 → whisper/FunASR+音量+视觉 → clip_analysis.json
│
▼
阶段3.5: 精修预处理 → 口令/重复/晃动/纯画面标注
│
▼
阶段4: LLM叙事编排 → edit_plan.json
│
▼
阶段4+: Dashboard可视化 → 浏览器预览确认
│
▼
阶段4.5: 语音截断校验 → 自动修复
│
▼
阶段5: ffmpeg渲染 → 裁剪→标题→拼接→BGM → final.mp4 🎬
Last scanned: 8/13/2026
{
"issues": [],
"status": "PASSED",
"scannedAt": "2026-08-13T05:40:53.409Z",
"npmAuditRan": true,
"pipAuditRan": true,
"promptInjectionRan": true
}把一堆手机拍的旅行素材,用 AI Agent 自动剪成一个完整 Vlog。 你只需要提供素材文件夹,剩下的交给 Agent。
Vibe Editing — 不用学剪辑软件,不用自己挑选素材,不用纠结叙事结构。 告诉 AI 你想要什么风格,它帮你从头到尾搞定。
🧑💻 by nyx研究所 — GitHub · B站 · 小红书 @nyx研究所 · X / Twitter
这是一份给 AI Agent(Claude Code / Hermes / OpenClaw / GPT 等)使用的 Skill 文件,定义了从原始素材到成品视频的完整自动剪辑工作流。
它不是一个传统的软件程序——而是一份指导 AI Agent 工作的知识文件,包含:
你去旅行拍了 80 个视频片段,回来之后:
用了这个 Skill:
系统级只需要装两样东西:
| 工具 | 用途 | 安装 |
|---|---|---|
| ffmpeg | 视频裁剪/编码/拼接/抽帧/混音 | brew install ffmpeg / apt install ffmpeg |
| Python 3.9+ | 脚本胶水 | macOS/Linux 自带 |
Python 依赖(Agent 会自动检测和安装):
openai-whisper — 语音转录Pillow — 标题图片生成还需要一个视觉理解 API(用来看懂画面内容):
| 模型 | 费用 | 说明 |
|---|---|---|
| 智谱 GLM-4.6V-Flash | 免费 | 注册 open.bigmodel.cn 即用,中文好 |
| GPT-4o | 付费 | 效果最好 |
| Qwen-VL | 付费 | 阿里云,中文好 |
不需要: 剪映 / CapCut / Premiere / moviepy / ImageMagick
┌──────────────────────────────────────────────────────────┐
│ │
│ 素材文件夹 ❶ 分析 自动,不用管 │
│ footage/ ──────────▶ Agent 理解每条素材的画面、 │
│ 语音、音量,标记问题片段 │
│ │
│ ❷ 编排 自动,不用管 │
│ Agent 像剪辑师一样规划 │
│ 叙事结构和镜头节奏 │
│ │
│ ❸ 预览 ◀── 你看一眼 │
│ 浏览器打开 Dashboard │
│ 确认方案,或提修改意见 │
│ │
│ ❹ 出片 自动,不用管 │
│ 裁剪 → 加标题 → 拼接 → BGM │
│ │ │
│ ▼ │
│ 🎬 final.mp4 │
│ │
└──────────────────────────────────────────────────────────┘
你唯一需要做的就是第 ❸ 步——看一眼方案,说"可以"。 其他全是 Agent 自动完成。
Agent 会自动对每条素材做三件事:
然后自动做预处理:去掉开头的"好了开始录了"、重复说了三遍的同一句话、举手机的晃动、说完话后的拖拽。
基于素材分析结果,Agent 会:
Agent 生成一个交互式 Dashboard 网页:
你看完说"可以",或者说"第三段换个素材"——Agent 调整后再给你看。
final.mp4 🎬复制下面的指令发给你的 AI Agent,它会自动完成安装和配置:
请从 https://github.com/znyupup/ai-video-editing-skill 克隆仓库,
阅读 SKILL.md 学习完整工作流,然后帮我把 footage/ 目录下的素材剪成一个旅行vlog。
💡 一般情况下 Agent 能自行完成 ffmpeg 检测、Python 依赖安装、视觉 API 配置等所有前置步骤。你只需要准备好素材文件夹和一个视觉模型的 API Key。
SKILL.md 加载到你的 AI Agent帮我把 footage/ 目录下的素材剪成一个旅行vlog支持的 Agent 平台:
git clone 后 /read SKILL.md 加载即使不用 AI Agent,SKILL.md 本身也是一份详细的 ffmpeg + Whisper + 视觉 API 视频剪辑手册,可以手动按步骤执行。
vlog-auto-edit/
├── SKILL.md # 🧠 核心:完整工作流定义(给 AI Agent 读的)
├── README.md # 📖 项目介绍(给人类读的)
├── LICENSE # MIT
│
├── scripts/
│ ├── gen_dashboard.py # 📊 Dashboard 生成器
│ └── gen_storyboard.py # 🎬 分镜预览生成器
│
├── templates/
│ └── edit_plan_prompt.md # 📝 LLM 叙事编排 prompt 模板
│
└── examples/
├── clip_analysis.json # 示例:素材分析数据
├── edit_plan.json # 示例:剪辑方案
└── project_structure.md # 示例:项目文件结构说明
在分析素材之前,可以让 Agent 先研究 2-3 个同类型优质 vlog(B站/YouTube),提取镜头节奏和叙事结构作为参考。这一步可选,但能显著提升成品质量。
编辑 templates/edit_plan_prompt.md 中的参数:
Agent 在分析阶段支持三种预处理模式:
strict — 激进裁剪,最短成品normal — 均衡模式(默认)loose — 保守裁剪,保留更多内容支持任何兼容 OpenAI Chat Completions 格式的视觉模型,替换 SKILL.md 中的 API 配置即可。
SKILL.md 中记录了 24 条实战踩坑经验,这里列几个关键的:
完整列表见 SKILL.md 的 Pitfalls 章节。
欢迎提 Issue 和 PR!特别欢迎:
nyx研究所 — GitHub · B站 @nyx研究所 · 小红书 @nyx研究所 · X / Twitter
MIT — 随便用,标注来源就行。
ai-video-editing-skill is an open-source ai agents skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by znyupup. AI Agent Skill for automated vlog editing. Feed raw footage, get a finished video. Powered by ffmpeg + Whisper + Vision API. It has 101 GitHub stars.
Yes. ai-video-editing-skill passed SkillsLLM's automated security scan — a dependency vulnerability audit plus prompt-injection heuristics — with no high-severity issues. You can read the full report in the Security Report section on this page.
Clone the repository with "git clone https://github.com/znyupup/ai-video-editing-skill" and add it to your Claude Code skills directory (see the Installation section above). ai-video-editing-skill ships a SKILL.md manifest, so compatible agents can discover and load it automatically.
ai-video-editing-skill is primarily written in Python. It is open-source under znyupup on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other AI Agents skills you can browse and compare side by side. Open the AI Agents category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh ai-video-editing-skill against similar tools.
No comments yet. Be the first to share your thoughts!