clipify について
clipify は、動画から見どころを選び、独立したクリップにする編集スキルです。文字起こしや衝撃音検出を使い、画面の再構成、速度調整、字幕付けにも対応します。
主な機能
- 01会話・アクション別の場面選定
- 02衝撃音による動作検出
- 03候補場面のフレーム確認
- 04縦型パンまたは分割画面
- 05見せ場の速度ランプ
- 06単語単位の字幕
活用シーン
- 01ポッドキャストから笑いどころを選ぶ
- 02衝撃音に合わせたアクション集の作成
- 03横型インタビューの縦型化
- 04会話クリップへの単語字幕追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ louisedesadeleer/ clipify --skill clipifyインストール後、Claude Code で/clipifyを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEREADME.md.gitignorescripts/build_ass.pydetect_transients.pyaudio_align.pybuild_pan.pyanalyze.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— 2 つの 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 個選びます。面白さの手がかりとして、次を確認します。
- パンチラインとリアクション: 「what」「wait」「no way」などの言葉、笑い、「haha」、悪態
- 展開の逆転: 前振りとなる質問 → 意外な回答
- 気まずい沈黙: 長い間がある Whisper のセグメント、またはつなぎ言葉(「uh」「um」)
- 自虐ネタ/引用したくなる一言: 単独で成立する、短い断定文
- 音声のピーク:
ffmpeg -af volumedetectで検出するか、短い Whisper セグメントが交互に続く箇所を探します
各候補について、[start, end, why-it's-funny, suggested title] を提案します。クリップは 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(秒)は、2 回の打撃音の間隔の最小値です。速いドラム演奏では 0.2 が必要な場合がありますが、薪割りなら 0.6~1.0 で問題ありません。--band 1000:6000は、木材、金属、ガラスの衝撃音をカバーします。重い鈍い音(バスドラム、ボディショット)は低い帯域にあります(--band 200:2000)。口笛や軽い金属音、スネアの鋭い音はより高い帯域にあります(--band 4000:10000)。- 複数のクリップを含むソース(スマートフォンで撮影したクリップのフォルダー)の場合は、各クリップで検出器を実行し、さまざまなアングルからバリエーション豊かな素材を選びます。45~60 秒のモンタージュなら、1 ショット約 0.9 秒として、打撃音を 50~65 個選ぶことを目指します。
各「ショット」は、打撃音の約 0.4 秒前(振りかぶる様子が見えるように)に始まり、約 0.5 秒後(打撃音と短いフォロースルーを含む)に終わるようにします。1 拍あたり約 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 を 1 回実行し、画像を一括で 1 回読み取るだけです。簡単な保険になります。
ステップ 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 の場合は、次のように尋ねます。
「2 つの選択肢があります。(a) 話している人を追うハードカットのパン(一度に 1 人の顔を表示)、または (b) 2 人の顔を両方表示する分割画面です。どちらにしますか?」
顔が 1 つしかない場合(話し手が 1 人のクリップ)は、質問を省略します。話し手が 1 人の場合は、中央でクロップするだけです。
ステップ 4a — 顔の間でパンする(カットの速いトークヘッド形式の会話に推奨)
-
2 つの顔の ROI を特定します。 フレームを 1 枚サンプリングします。
ffmpeg -ss <middle> -i <clip> -frames:v 1 /tmp/clipify/probe.jpg。画像を読み取ります。各顔の口から顎までの範囲を、ソースのピクセル座標でx,y,w,hとして目視で指定します。(cv2 は不要です。クリップ内でカメラは固定されているため、フレーム 1 枚で十分です。)ボックスを描画して確認します。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 - -
話者のタイムラインを作成します(最短表示時間は 1.0 秒。短い割り込み発話は直前の話者にまとめます)。
python3 <skill-dir>/scripts/analyze.py /tmp/clipify/L.txt /tmp/clipify/R.txt 1.0 > /tmp/clipify/segments.json -
ソースから 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 に制限)
- LEFT_X =
-
ハードカット用の 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 のソースの場合は、先に縮小するか、すべての座標を 2 倍にしてください。
ステップ 4b — 分割画面(両方の顔が常に表示される)
2 つのタイルを縦に積み重ね、それぞれ 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 にターゲットにします(1080×960 に合わせるため 1.125:1)。
ステップ 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.5 倍 — 「やや軽快」に感じられるべき人の動きで、速送りに見えないもの
- 2 倍 — よりインパクトのあるリビール。それでも自然に見える
- 3 倍~4 倍 — タイムラプスの雰囲気(4 倍にするには
atempo=2.0,atempo=2.0を連鎖させます。atempoはフィルターインスタンスごとに 0.5~2.0 のみ受け付けます) - 音声が単なる環境音や風の音で、チップマンク効果が邪魔になる場合は、
-af atempoを外してそのセグメントをミュートします
カットの残りの部分がリズミカル(チョップモンタージュ、チャットのやり取り)で、リビールがそうでなければ間延びして見える場合に便利です。
ステップ 5 — 字幕を追加する
アクションモードではこのステップをスキップします — 打撃音がリズムであり、キャプションはビジュアルを煩雑にするだけです。
一度だけ確認します(ユーザーがすでにスタイルを指定していない場合のみ):
「字幕スタイルは 3 つあります: 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"
ステップ 6 — 納品
- 各出力を
<source_dir>/clipify_out/に保存します(存在しない場合は mkdir) - クリップごとに 1 行を出力します: 名前、長さ、何が面白かったか、出力パス
- 最初の出力を
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 を過度に調整しないでください。 最大 2 回までにしてください。モーション差分は許容範囲が広いため、口元を完全に中心に捉えていなくても、口と顎を含む広めの 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 を実行してください。
- 計画は 1 行で伝えてから、作業に取りかかってください。 反復のたびに逐一説明しないでください。