mcpField
C
clipify

clipify

github.com/louisedesadeleer创意与媒体615188 次安装9 周前更新
代码仓库

关于 clipify

clipify 是面向视频剪辑的 Skill,可从对话或动作视频中筛选片段并生成独立剪辑。它结合语音转写或冲击声检测,并支持竖屏重构、变速和字幕。

主要功能

  1. 01对话与动作模式选段
  2. 02检测冲击声定位动作
  3. 03抽帧核验候选画面
  4. 04生成竖屏平移或分屏
  5. 05变速压缩揭示镜头
  6. 06制作逐词字幕

适用场景

  1. 01从播客中挑选笑点片段
  2. 02按击打声制作动作蒙太奇
  3. 03将横屏访谈改为竖屏
  4. 04为对话剪辑添加逐词字幕

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/louisedesadeleer/clipify --skill clipify

安装后在 Claude Code 中输入/clipify确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

LICENSE
README.md
.gitignore
scripts/
build_ass.py
detect_transients.py
audio_align.py
build_pan.py
analyze.py

详细介绍

Clipify

在视频中寻找精彩片段,将其剪成独立短片;可选择将 16:9 转为 9:16(人脸跟随或分屏)、调整镜头摇移速度以更紧凑地呈现内容,并逐词烧录 opus 风格字幕。

模式

在步骤 1 之前选择一种模式。所选模式会改变你寻找片段的方式,以及是否添加字幕。

  • 对话模式(播客、访谈、对镜讲话视频的默认模式):使用 Whisper 转录,然后寻找包袱、反应或尴尬的停顿。→ 步骤 1A。在步骤 5 添加字幕。
  • 动作模式(劈柴、体育、打击乐、打鼓,以及任何伴有重复撞击声的内容):完全跳过 Whisper,运行 detect_transients.py 找到每次击打,并据此剪辑。→ 步骤 1B。跳过步骤 5;击打声本身就是节奏。
  • 混合模式:对话部分使用对话模式,其余部分使用动作模式,并交替剪辑。使用 1A 和 1B。

如果用户说“我在这里没有说话”,则使用动作模式。如果用户说“每次 X 时都剪一下”(劈砍、击打、出拳、鼓点、挥动、脚步),则使用动作模式。其他情况默认使用对话模式。

输入

  • 视频文件路径(用户会提供;否则请询问)
  • 可选:所需格式(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 videotoolbox,渲染时添加 -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 — 查找子片段在较长源视频中的偏移
    • detect_transients.py — 查找尖锐撞击声(动作模式);参见 --help

工作目录:/tmp/clipify/(开始时创建,并保留产物以供调试)。


工作流

步骤 1A — 寻找对话片段(对话模式)
mkdir -p /tmp/clipify
ffmpeg -y -hwaccel videotoolbox -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 个候选片段。可留意以下有趣信号:

  • 包袱和反应: “什么”“等等”“不可能”等词语、笑声、“哈哈”、脏话
  • 反转时刻: 设问 → 意外回答
  • 尴尬的停顿: Whisper 片段之间间隔较长,或出现填充词(“呃”“嗯”)
  • 自嘲 / 值得引用的妙语: 简短、独立成句的陈述
  • 音频峰值: 使用 ffmpeg -af volumedetect 检测,或查找快速的一来一回(Whisper 片段交替且较短)

针对每个候选片段,提出:[开始时间, 结束时间, 有趣之处, 建议标题]。片段时长以 10–25 秒为宜。展示列表,并让用户确认或挑选。

步骤 1B — 找到每次击打(动作模式)

用音频瞬态检测替代 Whisper。检测器通过计算 1–6 kHz 频段的频谱通量,找到尖锐的撞击声(斧头劈木头、拳头击打垫子、鼓面、球棒击球、木头落在一堆木头上)。撞击会产生宽带脉冲,而环境噪声和风声不会。

ffmpeg -y -hwaccel videotoolbox -i "$VIDEO" -vn -ac 1 -ar 16000 /tmp/clipify/audio.wav
python3 <skill-dir>/scripts/detect_transients.py /tmp/clipify/audio.wav \
  --min-flux 30 --min-gap 0.6 > /tmp/clipify/strikes.json

调参:

  • --min-flux 30 是合理的默认值。真实撞击的数值为 50–300;环境噪声和风声为 5–15。误报太多 → 提高到 40–50。检测到的击打太少 → 降至 20。
  • --min-gap 0.6(秒)表示两次击打之间的最短间隔。快速击鼓可能需要 0.2;劈砍用 0.6–1.0 即可。
  • --band 1000:6000 覆盖木头、金属和玻璃的撞击声。沉重的闷响(底鼓、身体击打)频率更低(--band 200:2000);哨声、叮当声和小军鼓爆裂声频率更高(--band 4000:10000)。
  • 对于多片段素材(一个装有手机视频的文件夹),在每个片段上运行检测器,并从不同角度挑选多样化的片段。制作时长 45–60 秒、每个镜头约 0.9 秒的蒙太奇,目标是选取 50–65 次击打。

每个“镜头”应从击打前约 0.4 秒开始(展示蓄力动作),并在击打后约 0.5 秒结束(包含击打和短暂的后续动作)。每个节拍约为 0.9 秒。

步骤 1.5 — 确认每个候选片段都在画面内

渲染前务必执行此步骤,无论使用哪种模式。音频检测找到的是事件的声音;摄像机可能并未拍到该事件。跳过此步骤是导致用户反馈“你在我都没入镜的地方切入了劈砍镜头”的头号原因。

# Sample one frame at each candidate strike time T:
for T in $STRIKE_TIMES; do
  ffmpeg -y -ss "$T" -i "$VIDEO" -frames:v 1 -vf scale=320:-1 \
    /tmp/clipify/verify_${T}.jpg
done

查看每张验证图像,如果出现以下情况,就移除该候选片段:

  • 主体弯腰、出框或被障碍物挡住
  • 拇指遮住了镜头(没错,这种情况确实会发生——而且只有通过视觉检查才能发现)
  • 明显是其他声源(关门声、工具掉落声)触发了检测器

对于约 30 个候选片段,每个片段只需快速调用一次 ffmpeg,再批量查看一次图像。这点额外工作很值得。

步骤 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(Insta 动态)?”

步骤 4 — 如果要从 16:9 转为 9:16:在两张脸之间平移,还是分屏

使用 ffprobe 检测源视频宽高比。如果源视频是 16:9,目标是 9:16,则询问:

“有两种方案:(a) 硬切平移,跟随正在说话的人(一次只显示一张脸);或者 (b) 分屏,上下排列并同时显示两张脸。你想选哪种?”

如果画面中只有一张脸(单人说话片段),则跳过询问。对于单人说话片段,直接居中裁剪。

步骤 4a — 在两张脸之间平移(推荐用于快速剪切的对话式出镜视频)
  1. 定位两张脸的 ROI。 抽取一帧:ffmpeg -ss <middle> -i <clip> -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
    

    最多迭代两次。方框应覆盖嘴部和下巴,并避开手和麦克风。不要过度调整——帧差分对误差有容忍度。

  2. 提取每帧在各 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 -
    
  3. 构建说话者时间线(最短停留时间为 1.0 秒——较短的插话会合并到前一位说话者的片段中):

    python3 <skill-dir>/scripts/analyze.py /tmp/clipify/L.txt /tmp/clipify/R.txt 1.0 > /tmp/clipify/segments.json
    
  4. 为源视频中的 9:16 竖向条带选择平移 x 坐标。 源视频宽度 W=1920,目标宽度 W=1080 时,裁剪条带宽度 = 608。

    • LEFT_X = face_left_center_x - 304(限制为 ≥ 0)
    • RIGHT_X = face_right_center_x - 304(限制为 ≤ source_W - 608)
  5. 生成硬切 x 表达式并渲染:

    EXPR=$(python3 <skill-dir>/scripts/build_pan.py /tmp/clipify/segments.json $LEFT_X $RIGHT_X)
    ffmpeg -y -hwaccel videotoolbox -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,请先缩小尺寸,或将所有坐标加倍。

Step 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,将右侧发言者的片段组合成 between(t,a,b)+between(t,a,b)+...,以构建 <RIGHT_SPEAKER_ENABLE>。画面裁剪应以每张脸为中心,目标尺寸约为 720×640(1.125:1,与 1080×960 相匹配)。

Step 4c — 时间变速揭示(可选)

如果用户想缩短缓慢的风景/摇摄/揭示镜头(“把这段剪掉一半”),但又不想丢失叙事脉络,就使用变速坡道,而不是裁剪。裁剪会迫使你舍弃部分过程;变速坡道则能保留所有节拍,只是节奏更快。

# 2x speed: 12s arc → 6s output, audio pitch preserved via atempo
ffmpeg -y -ss "$START" -i "$CLIP" -t "$ORIG_DUR" \
  -vf "setpts=PTS/2,scale=1080:1920:flags=lanczos,fps=30" \
  -af "atempo=2.0" \
  -c:v libx264 -preset fast -crf 21 -pix_fmt yuv420p \
  -c:a aac -b:a 128k /tmp/clipify/clip_2x.mp4

经验法则:

  • 1.5x — 让人物动作显得“稍微利落”但又不至于像加速播放
  • 2x — 更有冲击力的揭示;看起来仍然自然
  • 3x–4x — 延时摄影的感觉(4x 可串联 atempo=2.0,atempo=2.0;每个滤镜实例中的 atempo 只接受 0.5–2.0)
  • 如果音频只是环境声/风声,而且变调会让人分心,就移除 -af atempo 并将该片段静音

当剪辑的其余部分富有节奏感(快速切换蒙太奇、对话来回交替),而揭示镜头原本会显得拖沓时,这种方式很有用。

Step 5 — 添加字幕

动作模式下跳过此步骤——打击声就是节奏,字幕只会让画面显得杂乱。

只询问一次(仅当用户尚未指定风格时):

“三种字幕风格:opus(大号粗体白字,当前词以黄色高亮)、karaoke(每组 4 个词,绿色高亮)、minimal(简洁的 Helvetica,不高亮)。或者粘贴你喜欢的示例。”

如果用户粘贴了参考图/示例:尽可能贴合其字体、字号、字重、颜色、位置和动画——手动编写自定义 ASS,或扩展 build_ass.py。

否则使用预设:

# Re-run whisper on the trimmed clip for accurate timestamps relative to clip start
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"
Step 6 — 交付
  • 将每个输出保存到 <source_dir>/clipify_out/(如果目录不存在则创建)
  • 每个片段打印一行:名称、时长、趣味之处、输出路径
  • 使用 open <path> 打开第一个输出,让用户检查
  • 提供继续调整的选项(更换风格、调整 ROI、切换为分屏、重新调整字幕时序)

注意事项(以往运行的教训——不要重蹈覆辙)

  • 检测到音频 ≠ 画面中出现事件。 渲染前务必执行步骤 1.5(核验每个候选帧)。检测器找出的是砍击的声音,而不是砍击者是否在画面中。主体弯腰、在镜头外,或拇指挡住镜头时,音频检测器仍会触发。渲染前先筛掉这些情况。
  • 频谱通量频段很重要。 默认的 --band 1000:6000 覆盖木头、金属和玻璃的撞击声。沉重的低音闷响(底鼓、身体撞击)需要 --band 200:2000。口哨声、碰杯声、军鼓爆裂声需要 --band 4000:10000。如果 --min-flux 30 没有结果,先尝试降到 15;如果结果有数千条,先换个频段,再考虑提高阈值。
  • 变速音频要成对处理。 atempo 单个滤镜只接受 0.5–2.0;要实现 4 倍速,请串联 atempo=2.0,atempo=2.0。如果不使用 atempo,仅用 setpts 会让音频听起来像花栗鼠。
  • 不要过度调整 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。
  • 用一句话说明计划,然后立即执行。 不要逐一描述每次迭代。

评论

支持 Markdown