wjs-segmenting-video について
wjs-segmenting-videoは、既存のSRTがある長尺動画から、対象プラットフォーム向けの独立した短いクリップを作るスキルです。SRTを読んで話題の境界を判断し、正確な時刻でカットして、後工程向けの素材を用意します。
主な機能
- 01SRTを読んで話題の境界を選定
- 02指定時刻に合わせた正確なカット
- 03画面の向きを確認し必要に応じて変更
- 04クリップ別SRTと中間フレームの作成
- 05生クリップを後処理スキルへ引き渡し
活用シーン
- 01長尺動画から単独で視聴できるクリップを抽出
- 02短尺動画プラットフォーム向けの画面比率を準備
- 03カットした動画をwjs-overlaying-videoへ渡し字幕やカバーを追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jianshuo/ claude-skills --skill wjs-segmenting-videoインストール後、Claude Code で/wjs-segmenting-videoを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/example_segments.jsonplatform_sizes.mdsegments_schema.jsonscripts/burn_subs.pycompose_cover.pymake_cover.pyprepend_intro.pysegment.py詳細
wjs-segmenting-video
長尺動画と SRT を、ターゲットプラットフォームに適した複数の独立した短尺クリップに分割します。このスキルはカットとクロップまでで終了します。未加工のクリップを /wjs-overlaying-video に引き渡し、カバー、字幕、イラスト、CTA、最終レンダリングを行います。
使用する場面
- 既存の SRT 文字起こしがある長尺動画(10 分以上)。
- 目標は、それぞれ単独で視聴できる短尺クリップです。
- ユーザー(またはあなた)が、別途
/wjs-overlaying-videoで後処理を行います。
使用しない場面
- 1 つのトピックだけをトリミングする場合 →
ffmpeg -ss A -to Bを使います。 - まだ文字起こしがない場合 → まず
/wjs-transcribing-audioを実行します(セグメントにソースと異なる言語が必要な場合は、続けて/wjs-translating-subtitlesを実行します)。 - マルチカメラ編集 →
/wjs-editing-multicamを使います。 - 1 つのトピック内に複数のカットがあるハイライト動画 → これは編集であり、セグメント分割ではありません。
このスキルでできること/できないこと
| できること | できないこと |
|---|---|
| エージェントであるあなたが SRT 全体を読み、トピックの境界を判断します | NLP によるトピックモデリング、無音検出、「バズる瞬間」のスコアリングを実行するスクリプト。トピックの境界は意味に基づいて決まります。Descript、OpusClip、Riverside Magic Clips などの競合ツールは、自動化によってこの判断を誤ります。 |
segment.py がカットし、/wjs-reframing-video が向きを調整します | エンドツーエンドの「魔法のような」パイプライン |
| デフォルトでは正確なシークでカットします(再エンコード)。クリップは指定したタイムスタンプから正確に開始します | ストリームコピーによるカット(キーフレームに合わせるため、最大 GOP 分のずれが生じます) |
| クロップ済みの未加工クリップと、クリップごとの SRT を引き渡します | 焼き込み字幕、カバー、イントロ、CTA(これらは /wjs-overlaying-video で処理します) |
パイプライン
long video + SRT
↓ (agent reads SRT, decides topics — judgment, not parsing)
segments.json
↓ segment.py --reencode (accurate seek; clip starts exactly at requested t)
clip_NN.mp4 + frame_NN.jpg
↓ ASK: target platform orientation match source?
↓ /wjs-reframing-video on each clip (if 16:9 → 9:16, etc.)
↓ re-extract frames from cropped clips
clip_NN.mp4 (now in target orientation) + clip_NN.zh-CN.burn.srt
↓
HAND OFF → /wjs-overlaying-video
(does covers + captions + illustrations + CTA + final render)
ステップ 1 — SRT を読み、segments.json を作成する
トピックの特定をスクリプトに任せないでください。 候補となる各セグメントについて、次の点を判断します。
- 単独で理解できるか? 初めて見る人が、前後の文脈なしで内容を理解できる必要があります。
- 話題が一貫しているか? 中心となる問いや洞察は 1 つです。話者がクリップの途中で話題を変える場合は、2 つのセグメントに分けます。
- プラットフォームに適した長さか? 视频号は 60~180 秒、抖音と Shorts は 30~60 秒です。30 秒未満では途中で切れた印象になり、4 分を超えると視聴維持率が下がります。
- フックと結論があるか? 主張、問い、印象的なイメージから始め、要点をまとめて終えます。文の途中で終わらせないでください。
- SRT のキュー境界に合わせる — 単語の途中では絶対にカットしないでください。
10 分のソース動画から、質の高いセグメントを 3~6 個作るのが一般的です。退屈な中盤は省きます。量より質を優先します。
スキーマ(完全な仕様は references/segments_schema.json、例は references/example_segments.json を参照):
{
"source_video": "input.mp4",
"source_srt": "input.zh-CN.srt",
"platform": "wechat_channels",
"segments": [{
"id": 1, "slug": "intent-not-code",
"title": "AI 时代不是写代码\n而是写意图",
"summary": "Two-sentence pitch — what's the insight, what's at stake.",
"start": "00:00:43.460", "end": "00:02:35.220",
"cover_prompt": "Visual concept for gpt-image-2 (style anchor, not literal scene)"
}]
}
slug はケバブケースの英語です(ファイル名に使用)。title では改行に \n を使い、2 行以内、各行 8~12 文字にします。cover_prompt は、後続の /wjs-overlaying-video にあるカバー生成ステップで使用されます。オーバーレイスキルが再確認せずに取得できるよう、ここに記述しておいてください。
ステップ 2 — 正確なシークでカットする
python3 ~/.claude/skills/wjs-segmenting-video/scripts/segment.py \
--segments segments.json --out output/ --reencode
--reencode がデフォルトの推奨モードです。ffmpeg -ss N -i src -c:v libx264 -c:a aac でカットするため、出力は指定したタイムスタンプから正確に開始します。CPU ではクリップ 1 本あたり約 30 秒かかります。また、各セグメントの中間フレームを output/frame_NN_slug.jpg に抽出します。
ストリームコピーではなく、--reencode をデフォルトにする理由:
ffmpeg -ss N -c copy によるストリームコピーでは、N より前の最も近いキーフレームにシークします(再エンコードできないためです)。そのため、出力の t=0 はソースの t=keyframe に対応し、指定した発話の前に「前置き」の内容が数分の 1 秒だけ再生されます。境界 N でマスター SRT から切り出した字幕は、GOP の分だけ音声より先行して表示されます。その結果、「字幕が声より先に出る」と感じられます。
実際には、GOP=2s の H.264 ソースでは、各クリップに 0.6~1.5 秒のずれが生じます。後工程の同期バグに見えますが、実際には前工程のカットタイミングの問題です。
ストリームコピー方式(ソースエンコードを管理している場合のみ)
各指定カット境界で -force_key_frames を使ってソースを再エンコードしている場合、ストリームコピーは正確です。ワークフローは次のとおりです。
# Build the comma-separated keyframe list from segments.json
KF=$(python3 -c "import json; s=json.load(open('segments.json'))
ts=[]
for seg in s['segments']:
ts += [seg['start'], seg['end']]
print(','.join(ts))")
# Re-encode master once, forcing keyframes at all segment boundaries
ffmpeg -i master.mp4 \
-c:v libx264 -preset medium -crf 18 \
-force_key_frames "$KF" \
-c:a copy master_kf.mp4
# Now stream-copy cuts land exactly:
python3 segment.py --segments segments.json --source master_kf.mp4 --out output/
セグメント境界を調整する場合にのみ使用してください(同じソースを何度も再カットします)。一度だけの作業では、--reencode のほうが簡単で、同じく正確です。
すでにカットされたクリップでキーフレームへのスナップを診断する
ffprobe -v error -select_streams v:0 -read_intervals "$((N-2))%$((N+5))" \
-show_entries packet=pts_time,flags -of csv=p=0 master.mp4 | grep "K_"
360.023,K__ 362.023,K__ のような出力は、GOP が 2 秒であることを示します。361.000 で -c copy カットすると、実際には 360.023 から始まり、字幕が音声より 0.977 秒先行します。後から修正するには、クリップごとの SRT オフセット調整値(requested_start − nearest_preceding_keyframe)をすべての字幕キューの開始時刻と終了時刻に加えますが、根本的な解決策は --reencode を指定して再カットすることです。
ステップ 3 — 向きを確認する(続行前に確認を求める)
ソース動画のアスペクト比をターゲットプラットフォームと比較します。
| プラットフォーム | 標準の向き | アスペクト比 |
|---|---|---|
| 视频号(WeChat Channels) | 縦向き | 9:16 |
| 抖音 / TikTok / Reels | 縦向き | 9:16 |
| 小红书(Xiaohongshu video) | 縦向き | 9:16 |
| YouTube Shorts | 縦向き | 9:16 |
| YouTube(通常) | 横向き | 16:9 |
| B站(Bilibili) | 横向き | 16:9 |
ffprobe で調べます。
ffprobe -v error -select_streams v:0 \
-show_entries stream=width,height -of csv=p=0 clip_01_*.mp4
ソースのアスペクト比がプラットフォームに合っている場合 → このステップをスキップします。
一致しない場合 → 変換する前にユーザーに確認します。確認文の例:
ソース動画は横向き(1920×1080)ですが、プラットフォームの视频号では縦向き(9:16)が必要です。各セグメントに
/wjs-reframing-videoを呼び出して縦向きに変換しますか?(crop は MediaPipe で話している 人の顔を追跡し、話者が常に画面内に収まるようにします)
確認を黙ってスキップしないでください — アップロード時になって横向きのクリップを縦向きにする必要があると判明するのは、スキルが防ごうとしている厄介な失敗です。
/wjs-reframing-video の呼び出し
crop スクリプトの実行には、Python 3.12 の venv に mediapipe + opencv + numpy が必要です(mediapipe は 3.14 以降用のホイールを提供していません)。初回のみセットアップします。
uv venv --python 3.12 /tmp/_crop_venv
/tmp/_crop_venv/bin/python -m pip install mediapipe opencv-python numpy
クリップごとの呼び出し:
for n in 01 02 03 04 05; do
slug=$(ls clip_${n}_*.mp4 | grep -v -E "_intro|_burned|_vert" | head -1 | sed -E "s/clip_${n}_(.+)\.mp4/\1/")
/tmp/_crop_venv/bin/python ~/.claude/skills/wjs-reframing-video/scripts/crop.py \
"clip_${n}_${slug}.mp4" \
--out "clip_${n}_${slug}_vert.mp4" \
--target portrait \
--bitrate 8M # 视频号 caps at 10Mbps
done
クロップ後、後続のパイプラインから見つけられるよう、クロップ版を正式な名前に差し替えます。
mkdir -p _horizontal_archive
for n in 01 02 03 04 05; do
base=$(ls clip_${n}_*_vert.mp4 | sed -E "s/_vert\.mp4$//")
mv "${base}.mp4" "_horizontal_archive/"
mv "${base}_vert.mp4" "${base}.mp4"
# Re-extract midpoint frame:
mid=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "${base}.mp4" | awk '{print $1/2}')
slug=$(echo "$base" | sed -E "s/^clip_${n}_//")
ffmpeg -hide_banner -loglevel error -ss "$mid" -i "${base}.mp4" \
-frames:v 1 -q:v 3 "frame_${n}_${slug}.jpg" -y
done
確認事項:crop ログの顔が画面に映っている検出率は、話者がカメラから約 2 m 以上離れて座っている場合、低く表示されることがあります(例:face#0: 9.6s on screen (9%))。数値が低くても問題ありません — アクティブ話者のヒステリシスと最大の顔へのフォールバックにより、適切に中央揃えされたクロップが生成されます。ただし、0 face observations / (no face / fallback): 100% は問題です。ランドマークがゼロの場合、crop はフレームの中央にフォールバックします。2 人のインタビューのセットでは、誰も映っていない話者の間の背景(暖炉や植物)が中央に来てしまいます。この状態が見られたら、MediaPipe による crop は中止し、話者の画面上の既知の位置に基づく決定論的な固定クロップを行ってください — /wjs-reframing-video →「Zero-detection fallback」を参照してください。確定する前に、中間地点のフレームを必ず抽出し、話者が中央にいることを目視で確認してください。
ステップ 4 — クリップごとの SRT を切り出す
python3 ~/.claude/skills/wjs-segmenting-video/scripts/burn_subs.py \
--segments segments.json --out output/ --no-burn
--no-burn フラグを指定すると、タイムスタンプがすでに 0 秒開始に調整されたクリップごとの SRT(clip_NN_slug.zh-CN.burn.srt)が出力されます。これは /wjs-overlaying-video の字幕が想定する入力と同じです(その合成では、マスタークロックではなく t=cover_duration から本文が始まります)。
旧名が burn_subs.py ですが、--no-burn モードでは、このステップはピクセルに字幕を焼き付けません — SRT を切り出すだけです。(ピクセルに焼き付けるモードは旧「Path A」ワークフロー向けに用意されていますが、/wjs-overlaying-video の HTML/CSS による字幕描画を優先するため、非推奨です。)
引き継ぎパッケージ — /wjs-overlaying-video に渡すもの
ステップ 1~4 の完了後、セグメントごとに以下の成果物を過不足なく渡します。
output/
clip_NN_slug.mp4 # raw cropped clip (target orientation, no subs, no cover)
clip_NN_slug.zh-CN.burn.srt # per-clip SRT, timestamps shifted to start at 0
frame_NN_slug.jpg # midpoint frame (cover reference)
segments.json # for slug/title/summary/cover_prompt metadata
続いて /wjs-overlaying-video を呼び出して、カバー、字幕、イラスト、CTA を追加し、クリップごとにアップロード可能な MP4 を作成します。オーバーレイスキルはクリップごとに最終合成動画を 1 本生成し、1 回のエンコードでレンダリングします(再エンコードを何度も重ねることはありません)。
クイックリファレンス
| タスク | コマンド |
|---|---|
| クリップのカット(正確、デフォルト) | segment.py --segments S.json --out output/ --reencode |
| ソースのアスペクト比を調べる | ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 IN.mp4 |
| 向きを変換する(先に確認) | クリップごとに /wjs-reframing-video を呼び出します |
| クリップごとの SRT を切り出す | burn_subs.py --segments S.json --out output/ --no-burn |
| キーフレームの位置を診断する | ffprobe -v error -select_streams v:0 -read_intervals A%B -show_entries packet=pts_time,flags -of csv=p=0 src.mp4 | grep K_ |
よくある間違い
- 文の途中でカットする — 必ず SRT の字幕キューの境界に合わせます。
- 動画を 100% 使おうとする — 10 分の動画から見どころのあるクリップを 3~6 本作るのは普通です。退屈な中盤はカットします。
- LLM にタイトルを書かせる — タイトルは要約ではなく、判断が求められるものです。make_cover に渡す前に見直して書き直します。
--force_key_framesによる前処理なしでストリームコピーする — 字幕より音声が最大 1 GOP 分先行するクリップが生成されます。ソースが専用に準備されている場合を除き、--reencode(デフォルト)を使います。- 向きの確認を省略する — 横向きのポッドキャストを视频号に投稿し、アップロード時に気づく事態は防げます。アスペクト比を確認し、クロップする前にユーザーに確認します。
- このスキルで字幕を焼き込む/カバーを生成する — これらの作業は
/wjs-overlaying-videoに移りました。このスキルはステップ 4 で終了します。
他のスキルとの連携
/wjs-transcribing-audio— ソース SRT がない場合は、先に作成します。正確なキュータイミングには、単語単位の Whisper 出力(または Volcano/豆包 ASR 出力)が推奨されます。セグメントの翻訳が必要な場合は、続けて/wjs-translating-subtitlesを使います。/wjs-reframing-video— ソースの向きが対象プラットフォームと一致しない場合は、ステップ 3 で呼び出します。顔追跡によるアクティブスピーカー追従で、話者をフレーム内に収めます。/wjs-editing-multicam— ソースがマルチカメラの場合は、先に同期済みの単一 MP4 をレンダリングしてから、セグメントに分割します。/wjs-overlaying-video— ステップ 4 以降のすべての作業でデフォルトの後続スキルです。カバー、字幕、イラスト、CTA、最終レンダリングはすべてここで行います。このスキルでは後処理を追加しないでください。
ファイルと参照資料
scripts/segment.py— 正確なシークとストリームコピーによるカットscripts/burn_subs.py— SRT の切り出し(--no-burnモード)。従来の libass による焼き込みモードは非推奨であり、/wjs-overlaying-videoの使用が推奨されますreferences/segments_schema.json— segments.json 用の JSON Schemareferences/example_segments.json— 作成例