Clipify
找到视频中最有趣的时刻,将它们剪切成独立的片段,可选将16:9格式转换为9:16(人脸跟踪或分屏),并添加类似Opus风格的逐词字幕。
输入
- 视频文件路径(用户提供;否则询问)
- 可选:要求的格式(9:16、16:9、1:1)——若未指定,在选定候选片段后询问
- 可选:字幕样式偏好——若未指定,在添加字幕前询问
工具(仅使用最快路径)
- Whisper:
whisper --model tiny.en --word_timestamps True --output_format json(比small.en快约10倍;英文字幕质量足够)。对于非英语:--model base(省略--language)。 - ffmpeg: 硬件解码为可选且平台相关——使用
-hwaccel auto,或省略(macOS:videotoolbox;Linux:vaapi/cuda/无)。渲染时添加-preset ultrafast。最终母版使用-c:v libx264 -crf 20。 - Numpy 用于音频对齐(FFT互相关)。无需scipy/cv2。
- 脚本:
<skill-dir>/scripts/(其中<skill-dir>是包含此SKILL.md的目录——通常为~/.claude/skills/clipify/)analyze.py— 根据两个感兴趣区域(ROI)的运动文件生成发言人时间线build_pan.py— 生成带硬切换的ffmpeg裁剪x表达式build_ass.py— 从whisper JSON生成Opus风格ASS字幕audio_align.py— 在较长源文件中查找子片段的偏移量
工作目录: /tmp/clipify/(开始时创建,保留工件用于调试)。
工作流
步骤 1——找到最有趣的部分
mkdir -p /tmp/clipify
ffmpeg -y -i "$VIDEO" -vn -ac 1 -ar 16000 /tmp/clipify/audio.wav
whisper /tmp/clipify/audio.wav --model tiny.en --word_timestamps True --output_format json --output_dir /tmp/clipify --language en
阅读生成的JSON(或.txt),挑选3–5个候选片段。需要扫描的有趣信号:
- 笑点和反应: 类似“what”、“wait”、“no way”、笑声、“haha”、粗话等词语
- 反转时刻: 铺垫问题 → 意想不到的答案
- 尴尬停顿: Whisper片段中有长时间间隙,或填充词(“uh”、“um”)
- 自嘲/可引用的金句: 短小独立的陈述句
- 音频峰值: 通过
ffmpeg -af volumedetect检测,或寻找快速来回对话(交替的短Whisper片段)
为每个候选片段提议:[起始时间,结束时间,为何有趣,建议标题]。目标片段时长10–25秒。列出清单,由用户确认/选择。
步骤 2——修剪每个选定的片段
ffmpeg -y -ss "$START" -t "$DURATION" -i "$VIDEO" -c copy /tmp/clipify/clip_$N.mp4
(使用-c copy即时修剪。仅在需要帧精确裁剪时重新编码。)
步骤 3——决定输出格式
询问用户(如果他们已指定则跳过):“9:16(TikTok / Reels)、16:9(YouTube)还是1:1(Instagram Feed)?”
步骤 4——如果16:9 → 9:16:人脸跟踪还是分屏
用ffprobe检测源长宽比。如果源图像是16:9且目标是9:16,则询问:
“两个选项:(a) 硬切换跟踪 跟随说话者(一次只显示一张脸),或者 (b) 分屏堆叠 同时显示两张脸。您想要哪个?”
如果只有一张脸(单人说话片段),则跳过此问题。对于单人说话,仅居中裁剪。
步骤 4a——人脸跟踪(推荐用于快速切换的对话镜头)
-
定位两个脸部的感兴趣区域(ROI)。 采样一帧:
ffmpeg -ss <中间时间> -i <片段> -frames:v 1 /tmp/clipify/probe.jpg。读取它。目测每个脸的嘴+下巴区域,以源图像的像素坐标x,y,w,h表示。(无需cv2——摄像机在片段内是静态的;一帧足够。)通过绘制框验证:ffmpeg -i probe.jpg -vf "drawbox=x=$LX:y=$LY:w=$LW:h=$LH:[email protected]:t=4,drawbox=x=$RX:y=$RY:w=$RW:h=$RH:[email protected]:t=4" verify.jpg最多迭代两次。框应覆盖嘴+下巴,避免手部或麦克风。不要过度调整——帧差法容错性高。
-
提取每个ROI中的逐帧运动能量:
ffmpeg -y -i clip.mp4 -filter_complex " [0:v]split=2[a][b]; [a]crop=$LW:$LH:$LX:$LY,format=gray,tblend=all_mode=difference,signalstats,metadata=mode=print:key=lavfi.signalstats.YAVG:file=/tmp/clipify/L.txt[la]; [b]crop=$RW:$RH:$RX:$RY,format=gray,tblend=all_mode=difference,signalstats,metadata=mode=print:key=lavfi.signalstats.YAVG:file=/tmp/clipify/R.txt[ra] " -map "[la]" -f null - -map "[ra]" -f null - -
生成发言人时间线(最短驻留时间1.0秒——短插话合并到前一个发言人):
python3 <skill-dir>/scripts/analyze.py /tmp/clipify/L.txt /tmp/clipify/R.txt 1.0 > /tmp/clipify/segments.json -
为人脸跟踪选择裁剪的x坐标,从源图像中获取9:16垂直条。假设源宽度W=1920且目标宽度W=1080,裁剪条宽度=608。
- LEFT_X =
face_left_center_x - 304(限制≥0) - RIGHT_X =
face_right_center_x - 304(限制≤ source_W - 608)
- LEFT_X =
-
生成硬切换x表达式并渲染:
EXPR=$(python3 <skill-dir>/scripts/build_pan.py /tmp/clipify/segments.json $LEFT_X $RIGHT_X) ffmpeg -y -i clip.mp4 -filter_complex \ "[0:v]crop=608:1080:x='$EXPR':y=0,scale=1080:1920:flags=lanczos[v]" \ -map "[v]" -map 0:a -c:v libx264 -preset fast -crf 20 -pix_fmt yuv420p \ -c:a aac -b:a 192k /tmp/clipify/clip_panned.mp4假设源为1920×1080;对于4K源,要么先缩小,要么将所有坐标加倍。
步骤 4b——分屏(两张脸始终可见)
两个堆叠的图块,每个1080×960。当前说话者的图块位于顶部——说话者变化时覆盖翻转。
[0:v]split=2[a0][a1];
[a0]crop=Wcrop:Hcrop:LX_tile:LY_tile,scale=1080:960,split=2[lt0][lt1];
[a1]crop=Wcrop:Hcrop:RX_tile:RY_tile,scale=1080:960,split=2[rt0][rt1];
[lt0][rt0]vstack[layoutL];
[rt1][lt1]vstack[layoutR];
[layoutL][layoutR]overlay=0:0:enable='<RIGHT_SPEAKER_ENABLE>'[v]
从segments.json构建<RIGHT_SPEAKER_ENABLE>,在右侧说话者片段上使用between(t,a,b)+between(t,a,b)+...。图块裁剪应围绕每张脸约720×640(1.125:1以匹配1080×960)。
步骤 5——添加字幕
询问一次(仅当用户尚未指定样式时):
“三种字幕样式:opus(大而粗的白色文字,黄色活动词高亮)、karaoke(4词为一块,绿色高亮)、minimal(简洁Helvetica,无高亮)。或者粘贴您喜欢的示例。”
如果用户粘贴参考图像/示例:尽可能匹配字体、大小、粗细、颜色、位置和动画——自定义编写ASS文件或扩展build_ass.py。
否则使用预设:
# 在修剪后的片段上重新运行Whisper以获得相对于片段开始的准确时间戳
whisper /tmp/clipify/clip_panned.mp4 --model tiny.en --word_timestamps True --output_format json --output_dir /tmp/clipify --language en
python3 <skill-dir>/scripts/build_ass.py /tmp/clipify/clip_panned.json /tmp/clipify/captions.ass opus
烧录字幕:
ffmpeg -y -i /tmp/clipify/clip_panned.mp4 -vf "subtitles=/tmp/clipify/captions.ass" \
-c:v libx264 -preset fast -crf 20 -c:a copy "$OUTPUT.mp4"
步骤 6——交付
- 将每个输出保存到
<源目录>/clipify_out/(如果不存在则创建目录) - 每片段打印一行:名称、时长、有趣之处、输出路径
- 打印第一个输出路径(或打开它——Linux
xdg-open <路径>,macOSopen <路径>)以便用户检查 - 提供迭代选项(不同样式、不同ROI、切换到分屏、重新调整字幕时间)
陷阱(来自先前运行的经验——避免重复)
- 不要过度调整ROI。 最多迭代两次。运动差分容错性高——覆盖嘴+下巴的较宽ROI即使不完全对准嘴部也能正常工作。
- 注意片段内的场景切换。 运行
ffmpeg -filter:v "select='gt(scene,0.3)',showinfo" -f null -以计数切换。如果16:9→9:16片段有许多切换,固定的人脸ROI仅对主要场景有效;警告用户,并提议要么选择单次拍摄的片段,要么接受在切换期间偏离中心的构图。 - 源分辨率很重要。 如果源图像为4K,先缩小到1920×1080(更快,对于9:16输出足够),或者将所有ROI/跟踪坐标乘以2。
- 源文件中已烧录字幕。 某些“原始”片段仍包含字幕。如果是这样,通过音频互相关(
audio_align.py)找到无字幕的母版,并从那里修剪。 - 如果短片段足够,不要在完整的长篇源文件上运行Whisper。 在步骤2之后对修剪后的片段运行Whisper;仅在步骤1中如果需要转录来找到有趣时刻时,才对完整源文件运行Whisper。
- 用一句话陈述计划,然后行动。 不要叙述每一次迭代。