>- 当用户说"视频切片""提取精彩片段""长视频切短""切成短视频""高光剪辑""逐字字幕""转竖版短视频"时使用。 和 video-highlights 的区别:clipify 专做英文口播找笑点+动态人脸 pan;video-highlights 更通用(中文/直播皆可),静态转竖版更稳。
npx skills add https://github.com/ZJU-REAL/Easel --skill clipify
Find the funniest moments in a video, cut them as standalone clips, optionally reformat 16:9 → 9:16 (face-pan or split-screen), and burn opus-style word-by-word captions.
whisper --model tiny.en --word_timestamps True --output_format json (≈10× faster than small.en; quality fine for English). For non-English: --model base (drop --language).-hwaccel auto, or omit it (macOS: videotoolbox; Linux: vaapi/cuda/none). Add -preset ultrafast for renders. Use -c:v libx264 -crf 20 for the final master.<skill-dir>/scripts/ (where <skill-dir> is the directory containing this SKILL.md — typically ~/.claude/skills/clipify/)analyze.py — speaker timeline from two ROI motion filesbuild_pan.py — ffmpeg crop x-expression with hard cutsbuild_ass.py — opus-style ASS captions from whisper JSONaudio_align.py — find offset of a sub-clip in a longer sourceWorking dir: /tmp/clipify/ (mkdir at start, leave artifacts for debugging).
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
Read the resulting JSON (or .txt) and pick 3–5 candidate clips. Funny signals to scan for:
ffmpeg -af volumedetect or look for rapid back-and-forth (alternating short Whisper segments)For each candidate, propose: [start, end, why-it's-funny, suggested title]. Aim for 10–25s clips. Show the list and let the user confirm/pick.
ffmpeg -y -ss "$START" -t "$DURATION" -i "$VIDEO" -c copy /tmp/clipify/clip_$N.mp4
(Use -c copy for instant trim. Re-encode only if cuts must be frame-accurate.)
Ask the user (skip if they already specified): "9:16 (TikTok / Reels), 16:9 (YouTube), or 1:1 (Insta feed)?"
Detect source aspect with ffprobe. If source is 16:9 and target is 9:16, ask:
> "Two options: (a) hard-cut pan that follows whoever is speaking (single face on screen at a time), or (b) split-screen stack with both faces visible. Which do you want?"
Skip the question if there's only one face (single-talker clip). For single-talker, just center-crop.
ffmpeg -ss <middle> -i <clip> -frames:v 1 /tmp/clipify/probe.jpg. Read it. Eyeball each face's mouth+chin area as x,y,w,h in the source's pixel space. (No cv2 needed — camera is static within a clip; one frame is enough.) Verify by drawing boxes: 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
Iterate at most twice. Boxes should cover mouth + chin and avoid hands/mics. Don't over-tune — frame differencing is forgiving.
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 -
python3 <skill-dir>/scripts/analyze.py /tmp/clipify/L.txt /tmp/clipify/R.txt 1.0 > /tmp/clipify/segments.json
face_left_center_x - 304 (clamp ≥ 0)face_right_center_x - 304 (clamp ≤ source_W - 608) 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
Source 1920×1080 assumed; for 4K source either downscale first or double all coordinates.
Two stacked tiles, 1080×960 each. The active speaker's tile is on top — overlay flips at speaker changes.
[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]
Build <RIGHT_SPEAKER_ENABLE> from segments.json as between(t,a,b)+between(t,a,b)+... over the right-speaker segments. Tile crops should target ~720×640 around each face (1.125:1 to match 1080×960).
Ask once (only if user hasn't already specified a style):
> "Three subtitle styles: opus (big bold white, yellow active-word highlight), karaoke (4-word chunks, green highlight), minimal (clean Helvetica, no highlight). Or paste an example you like."
If they paste a reference image/example: match the font, size, weight, color, position, and animation as closely as possible — write a custom ASS by hand or extend build_ass.py.
Else use the preset:
# 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
Burn captions:
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"
<source_dir>/clipify_out/ (mkdir if missing)xdg-open <path>, macOS open <path>) so the user can check itffmpeg -filter:v "select='gt(scene,0.3)',showinfo" -f null - to count cuts. If a 16:9→9:16 clip has many cuts, the fixed face ROIs only work for the dominant scene; warn the user, and offer to either pick a single-take clip or accept off-center framing during cuts.audio_align.py) and trim from there.Take zju-real/clipify from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.