Communitygithub.com

原片变短视频:切出带字幕的 9:16 竖屏片段

在长视频里找出高光时刻(用 Whisper 找笑点、反应和停顿,或用音频瞬态检测找劈砍、击打和鼓点),确认每个时刻确实在画面内,切成独立片段,用人脸跟随平移或分屏把 16:9 改成 9:16,并烧录逐字字幕。

原片变短视频:切出带字幕的 9:16 竖屏片段 是什么?

「原片变短视频」配方第 2 步(切成竖屏),来自 louisedesadeleer/clipify,一个 GitHub 600+ 星的 Claude Code 技能,用 npx skills add louisedesadeleer/clipify 安装。先选模式:对白模式用 Whisper 转写(英文用 tiny.en 词级时间戳,其他语言用 base),扫描笑点、反转、尴尬停顿和音量峰值;动作模式跳过 Whisper,在 1–6 kHz 频段做谱通量瞬态检测,每一击都切;混合模式两者穿插。它给出 3–5 个候选,在每个候选时间点抽帧确认动作确实入镜,切出片段,为 9:16 生成人脸跟随裁切或分屏,时间重映射平移让揭示更紧凑,最后用 ffmpeg 烧录 opus 风格的逐字 ASS 字幕(母版 libx264、CRF 20)。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add louisedesadeleer/clipify

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

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 —— 验证每个候选片段是否在画面内

在任何模式下,渲染之前务必执行此步骤。 音频检测只能找到事件的声音;相机可能并未捕捉到画面。跳过此步骤是用户反馈“你在我甚至没出现的时候进行了切割”的首要原因。

# 在每个候选撞击时间 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(Instagram 信息流)?”

步骤 4 —— 如果 16:9 → 9:16:面部间平移 vs 分屏

使用 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 源,要么先降低分辨率,要么将所有坐标加倍。

步骤 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)。

步骤 4c —— 时间扭曲揭示效果(可选)

当用户想要收紧一个缓慢的风景/平移/揭示镜头(“把它减半”)但又不失去其弧线时,使用速度渐变而不是修剪。修剪迫使你放弃部分弧线;速度渐变则以更高的速度保留所有节拍。

# 2 倍速度:12 秒弧线 → 6 秒输出,通过 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 —— 延时摄影氛围(使用 atempo=2.0,atempo=2.0 链实现 4x;每个滤镜实例 atempo 仅接受 0.5–2.0)
  • 如果音频只是环境/风声,并且花栗鼠音效会分散注意力,则去掉 -af atempo 并静音该段

当剪辑的其他部分具有节奏感(砍柴蒙太奇、对话来回切换),而揭示效果可能会像是冷场时,此功能非常有用。

步骤 5 —— 添加字幕

在动作模式下跳过此步骤——撞击声就是节奏,字幕只会使画面杂乱。

仅询问一次(仅当用户尚未指定样式时):

“三种字幕样式:opus(大号粗体白色,黄色活动词高亮)、卡拉OK(4 个词一组,绿色高亮)、极简(简洁的 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 —— 交付

  • 将每个输出保存到 <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。
  • 用一句话说明计划,然后执行。 不要叙述每一次迭代。

相关技能