| name | video-edit |
| description | 動画の無音カット・言い直しカット・フィラー除去を自動で行う動画編集スキル。「動画編集して」「この動画をカットして」「無音カットして」で起動。 |
| user-invocable | true |
| argument-hint | ["動画ファイルパス"] |
| color | blue |
動画編集スキル
トーキングヘッド動画(カメラに向かって喋る形式)の自動編集スキル。
無音カット・言い直しカット・フィラー除去を一括で行い、編集済みMP4を出力する。
ワークフロー概要
STEP 1: Scribe で文字起こし → 単語タイムスタンプ + ハードカットオフ設定
STEP 2: Claude が文字起こしを読んで判断 → 言い直し・フィラー・冗長はい除去
STEP 3: 単語間 gap クリーニング → 0.43秒以上の gap をカット + 単語内無音検出 + 短gapフィラー処理
STEP 3.5: @video-reviewer → 技術レビュー + コンテンツレビュー
STEP 4: エンコード → +7dB / CRF18 / 200ms プリロール(安全チェック付き) / 30ms フェード
STEP 5: 出力検証 → librosa で無音区間スキャン → 問題あれば報告・再エンコード
STEP 0: 準備
- 引数で動画ファイルパスが指定されていればそれを使う
- 指定がなければユーザーに聞く
- ffprobe で動画情報を確認(尺・解像度・コーデック)
- 出力先:
<プロジェクトルート>/edit/ フォルダ
- 作業用一時ディレクトリ:
C:/temp/videdit/(日本語パス回避)
- ffmpeg で音声を WAV に抽出(16kHz, mono)
前提条件
- ffmpeg がインストール済み
- Python + librosa, numpy, requests がインストール済み
.env に ELEVENLABS_API_KEY が設定済み
STEP 1: 文字起こし(ElevenLabs Scribe)
元動画の音声を ElevenLabs Scribe で文字起こしする。
url = "https://api.elevenlabs.io/v1/speech-to-text"
data = {"model_id": "scribe_v1", "language_code": "ja"}
出力
- 単語レベルのタイムスタンプ(各単語の start / end)→
C:/temp/videdit/transcript.json
- 文単位にグルーピング →
C:/temp/videdit/sentences.json
- 読みやすい形式 →
C:/temp/videdit/readable.txt
ハードカットオフ設定
最後の単語の end + 0.5秒 をハードカットオフに設定する。
これ以降は音声の有無にかかわらず全カット(OBS操作音等を防止)。
STEP 2: Claude が文字起こしを読んで判断
readable.txt を読み、以下を判断して除去対象の文インデックスを決定する。
a. 文単位の言い直し(リテイク)
同じ内容を複数回言い直している場合、最後の方(最も完成度が高い方)を採用し、それ以前を除去する。
b. 文中の言いかけ(false start)
文の途中で別の言葉を言いかけて訂正している場合、言いかけ部分を除去して正しい方に直結する。
文脈を理解して判断する。 ルールベースではなく、発言内容の意味を考えて決める。
c. フィラー語の自動検出
以下のフィラー語を全単語スキャンで自動検出し除去する:
- えっと / えーと / えーっと / ええと
- あの / あのー / あのう
- うーん / えー / あー / うー
Scribe が単語を細かく分割する場合があるため、1〜3単語の結合でマッチングする。
d. 冗長な「はい」「うん」の整理
文と文の間に挟まる独立した「はい」「うん」は除去する。
ただし文脈上必要なもの(話の切り替え等)は残す。
e. 文内の繰り返しパターン検出
1文の中で同じフレーズが「はい」「うん」等を挟んで繰り返されている場合、最初のフレーズ + 挟まれた「はい」を除去し、2回目のフレーズに直結する。
見つけ方: 各文を読む際に、同じ単語やフレーズが近い位置で2回出現していないか注意する。特に「はい」「うん」を挟んで同じ出だしが繰り返されるパターン。
例:
- 「こんな感じでCloud、はい、こんな感じでCloud Code...」→「こんな感じでCloud Code...」
- 「先ほど、はい、先ほど図解で...」→「先ほど図解で...」
- 「なので皆さん、はい、なので皆さん...」→「なので皆さん...」
f. 表現違いの同内容リテイク検出
同じ内容を異なる表現で繰り返している場合、最後の方を採用する。「同じ単語が繰り返されているか」ではなく「同じ意味のことを言っているか」で判断する。
判断基準: 文の冒頭が似た出だしで、伝えたい内容が同じなら、表現が違っても「言い直し」として扱う。迷ったら「動画の視聴者がこの2つの文を続けて聞いたら、同じ話を2回されていると感じるか?」で判断する。
例:
- 「最初から部署分けするんじゃなくて、いろいろ業務で壁打ちをして...」→「最初から部署を分けておくんじゃなくて、いろんな業務をしていく中で...」→ 同じ内容。後者を採用
- 「仕事を渡すことができると思いますので、皆さんも慣れるまで頑張ってください」→「どこで仕事をサブエージェントに渡せばいいのかっていうのがなんとなくわかってくるので...」→ 同じ趣旨。後者を採用
グループ分け
残った文を gap 0.43秒以上 でグループ分けする。
STEP 3: 単語間 gap クリーニング
各セグメント内の単語と単語の間で 0.43秒以上の gap をカットする。
ルール
- 各単語グループの末尾に +80ms バッファ(語尾の残響保護)
- ただし、直後が除去コンテンツ(フィラー・はい・言い直し)の場合、バッファ = 0ms(フェードアウト 30ms が語尾を保護するので問題ない。バッファを入れると除去コンテンツの頭を拾ってしまう)
- 音量(librosa)だけでカット位置を決めない(語尾カット防止)
単語内の無音検出(必須)
Scribe は単語の end に後続の無音を含めることがある(例:「で」に1.86秒を割り当て、実際の発話は0.3秒で残り1.5秒が無音)。以下のルールで検出する:
- 全単語をスキャンし、文字数に対して duration が異常に長い単語を検出する:
- 1文字(句読点除く): 0.8秒超 → 異常フラグ
- 2〜3文字: 1.2秒超 → 異常フラグ
- 4文字以上: 2.0秒超 → 異常フラグ
- フラグが立った単語に対して librosa で実際の発話終了位置を特定する:
librosa.effects.split(y, top_db=30) で発話区間を検出
- 発話終了位置 + 80ms をその単語の実効 end とする
- ただし Scribe の元の end より長くはしない
- 実効 end と次の単語の start の間が 0.43秒以上ならセグメント境界とする
短 gap フィラーの処理
gap が 0.43秒未満のフィラー(えー、えっと等)はハードカットすると音が不自然に飛ぶ。以下のルールで対処する:
- gap 0.2秒未満: フィラーを残す(カットするとほぼ確実にジャンプが目立つ)
- gap 0.2〜0.43秒: フィラーをカットし、オーディオクロスフェードを 50ms に延長して繋ぎを滑らかにする。ただし前後の単語が1つの文の途中にあり、フィラーが短い(0.15秒未満)場合は残す
- カットする場合、セグメントを分割してフィラー区間を除去する
出力
セグメント一覧を C:/temp/videdit/segments.json に保存する。
STEP 3.5: セルフレビュー(video-reviewer サブエージェント)
STEP 3 完了後、video-reviewer サブエージェントを起動してレビューを行う。
サブエージェント定義: .claude/agents/video-reviewer.md
起動
@"video-reviewer (agent)" セグメント一覧・文字起こし・音声ファイルを確認して、技術レビューとコンテンツレビューを行ってください。
サブエージェントの役割
-
技術レビュー
- Scribe の単語間 gap(0.43秒以上) でのみ再分割する(librosa の音量ベースでは再分割しない。語尾の音量低下を無音と誤判定してカットしてしまうため)
- Scribe が1単語に2秒以上のタイムスタンプを割り当てていないかチェック → 異常がある場合のみ librosa で検証し、実際に発話がない区間を特定して再分割
- 語尾は絶対にカットしない。Scribe の単語 end + 80ms バッファを尊重する
-
コンテンツレビュー
- 編集後の文の流れが自然に繋がっているか確認
- 言い直し・フィラーの取りこぼしがないか
- 「はい」の「ハ」だけが残っていないか(セグメント末尾 80ms バッファ内)
結果
C:/temp/videdit/review_result.json にレビュー結果が保存される。
STEP 3.75: reviewer 結果の検証(Claude 自身が実施)
reviewer の結果を鵜呑みにせず、Claude 自身が以下を最終チェックする。
1. 単語間 gap と単語内無音の検証
reviewer が返した全セグメントに対して、Scribe の単語タイムスタンプを再確認する。
- セグメント内に 0.43秒以上の単語間 gap が残っていないかチェック
- STEP 3 の「単語内の無音検出」ルール(文字数ベースの duration 上限)が正しく適用されているか確認
- reviewer が見逃した異常 duration の単語がないか再スキャン
- librosa の音量だけでカット位置を決めない(語尾カット防止)
2. フィラー残りの検証
reviewer が返したセグメント一覧の時間範囲に含まれる単語を再スキャンし、以下が残っていないか確認する:
- 文中の「はい、」(フィラー的に挟まれたもの)
- 「えっと」「あの」「えー」等
- 同じフレーズの繰り返し(e. パターン)
3. 問題があれば修正
検証で問題が見つかった場合、セグメントを直接修正してから STEP 4 に進む。
reviewer への再依頼はしない(無限ループ防止)。
STEP 4: エンコード
レビュー結果を反映し、ffmpeg の filter_complex で一括処理する。
エンコード設定
- プリロール: 200ms(各セグメント開始を 200ms 前倒し。話し始めの子音が切れない)
- ただし: 前倒し先が除去済みコンテンツ(フィラー・「はい」・言い直し)の時間範囲に重なる場合、プリロール 0ms(前倒ししない)にして、代わりに フェードインを 80ms に延長する。除去コンテンツの残響を拾うより、ソフトスタートで対処する方が確実。
- 音量: +7dB(
volume=7dB フィルター)
- 映像: libx264, CRF 18(ほぼ無劣化)
- 音声: AAC 192kbps
- オーディオフェード: 30ms(各セグメント境界でイン/アウト。プチノイズ防止)
- 最終セグメント: +0.5秒 余韻
出力
<プロジェクトルート>/edit/final.mp4(連番: final2, final3...)
- 一時ファイル(
C:/temp/videdit/)は処理後に削除
実装上の注意
- Python スクリプトを
C:/temp/videdit/run.py に書き出して実行する(bash heredoc の文字化け回避)
- 日本語パスは ffmpeg/ffprobe のみ直接扱い、Python は WAV 経由で処理する
- filter_complex はファイルに書き出して
-filter_complex_script で渡す
- 浮動小数点は round(x, 2) で丸める(ffmpeg エラー防止)
STEP 5: 出力検証(エンコード後の品質チェック)
エンコード完了後、出力動画の音声を検証する。
1. 無音区間スキャン
出力動画から音声を抽出し、librosa で無音区間を検出する:
y, sr = librosa.load(output_wav, sr=16000)
intervals = librosa.effects.split(y, top_db=30)
2. 問題が見つかった場合
- 該当箇所の出力動画上のタイムスタンプを報告する
- ユーザーに「修正して再エンコードしますか?」と確認を取る
- 承認されたら該当セグメントを修正して STEP 4 から再実行
- 一時ファイルは再エンコード完了後に削除(検証前に消さない)
3. 問題がない場合
一時ファイル(C:/temp/videdit/)を削除して完了。
処理完了後
ユーザーに以下を伝える:
- 出力ファイルパス
- 元動画と編集後の尺・サイズ比較
- カット内容の概要(言い直し何箇所、フィラー何件等)