cut-video について
Cut Videoは、長時間の録画を大幅に整理し、CapCutに取り込めるMP4を出力するスキルです。Whisperの文字起こしとMontreal Forced Alignerの単語タイミングを組み合わせ、特に複数テイクの箇所では音声に照らした境界確認を求めます。
主な機能
- 01無音、フィラー、繰り返しのカット
- 02MFAによる文字起こしと音声のアラインメント
- 03再文字起こしによるカット境界の確認
- 04編集トーンの選択
- 05整理済みMP4の出力
活用シーン
- 01長時間録画の短縮
- 02トーク動画の言い直し部分の削除
- 03CapCut編集用の素材準備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ louisedesadeleer/ cut-video --skill cut-videoインストール後、Claude Code で/cut-videoを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdLICENSEmake_review.py詳細
動画をカットする
長尺の録画を大胆に引き締めます。無音、フィラー、曖昧な表現、弱い話のつなぎ、言い直し、繰り返しを削除します。笑い声やコメディーの間は残します。レイアウト、ズーム、ミームを追加するために、そのまま CapCut に取り込めるクリーンな MP4 を出力します。
このスキルは、Whisper だけを使ったカットよりも正確です。 カットは Whisper のタイムスタンプ(±100~300ms、無音が単語の長さに含まれます)ではなく、Montreal Forced Aligner (MFA) の単語境界(約10~20msの精度で、単語間の実際の無音を明示的な区間として扱います)に基づいて行われます。Whisper は、MFA が音声にアラインメントするための文字起こしテキストの生成にのみ使用されます。その結果、より精密なカット、単語の出だしや語尾が切れないカット、そして信頼性の高い無音検出が実現します。
⚠️ ただし、MFA の時刻は確定値ではなく、あくまで下書きです(2026-07-02、「AI is bad at jokes」の DJI 素材で実証)。 MFA がアラインメントできるのは、Whisper が渡した文字起こしだけです。撮り直しの多い映像では、Whisper が繰り返されたセリフをひとつにまとめてしまうため、MFA はひとつの文字起こしを複数の発話テイクに引き延ばして割り当てます。その素材では、後半のズレが1~6秒に達しました(「the average of all working code」:MFA は217.2秒と判定しましたが、実際の発話開始は225.4秒。「what this means」の最後のテイク:MFA は114.7秒と判定しましたが、実際は130.4秒)。レンダリング前に、すべての残す範囲の境界を独立したウィンドウで再文字起こしして実際の位置を確認してください(Step 2.5)。この動画でその手順を省いていたら、後半全体のカットが台無しになっていたでしょう。
スタイルの目標(AI mogging my dad.mp4 を基準に調整):
- 約1.3~1.5秒ごとに1カット(カットの長さの中央値は約1.1秒)
- 元の長さから、全体の再生時間を約40~60%短縮
- 文と文の間だけでなく、文の途中でもカット
- 単語単位で引き締め、フィラー、曖昧な表現、弱い話し始めを削除
入力
- 動画ファイルのパス(ユーザーが指定します。指定がない場合は確認してください)
- 任意:トーンの希望(
aggressive/balanced/sentimental/documentary)— デフォルト:aggressive
作業ディレクトリ
/tmp/cut-video/<basename>/ — 最初に mkdir を実行し、デバッグ用に生成物を残します。
Step 0 — プロキシを作成する(最も大きな時短効果)
ソースが HEVC、500MB 超、または 4K 以上の場合は、最初に 1080p H.264 の作業用コピーに変換します。以降のすべての手順では、ソースではなくプロキシを使用します。
最初に向きを確認してください(ffprobe … width,height)。DJI やスマートフォンの映像は縦向きの 9:16 であることが多いため、scale=1920:1080 を固定指定すると映像がつぶれてしまいます。横向きの場合のみ scale=1920:1080 を使い、縦向きの場合は scale=1080:1920 を使います(または向きを問わない scale=-2:1080 / scale=1080:-2 を使います)。
ffmpeg -y -hwaccel videotoolbox -i "$SRC" \
-vf scale=1920:1080 \ # portrait sources: scale=1080:1920
-c:v libx264 -preset fast -crf 20 -pix_fmt yuv420p \
-c:a aac -b:a 192k \
/tmp/cut-video/$NAME/proxy.mp4
重要なフラグ:
- 入力に
-hwaccel videotoolboxを指定すると、HEVC をハードウェアデコードできます。Apple Silicon では約5~10倍高速になります。これを省くと、数秒ではなく数分待つことになります。 -preset fast— libx264 のデフォルトはmediumです。作業用コピーでは画質に実質的なメリットがないのに、約3倍遅くなります。
ハードウェアエンコードの代替案(M シリーズではさらに高速ですが、ファイルサイズはやや大きくなります):
-c:v h264_videotoolbox -b:v 8M
ステップ 1 — トランスクリプトのテキストを取得する(MFA の入力用に whisper を使用)
MFA は強制アライナーであり、文字起こしツールではありません。アラインメントにはトランスクリプトが必要です。ここでの whisper の役割はテキストを生成することだけです。切り出しに whisper の単語タイムスタンプは使用しません(誤差は ±100~300ms あり、turbo は単語の継続時間に無音部分を含めます)。タイミングはすべて MFA(ステップ 1.5)が提供します。
ffmpeg -y -hwaccel videotoolbox -i /tmp/cut-video/$NAME/proxy.mp4 \
-vn -ac 1 -ar 16000 /tmp/cut-video/$NAME/audio.wav
whisper /tmp/cut-video/$NAME/audio.wav \
--model tiny.en --word_timestamps True --output_format json \
--output_dir /tmp/cut-video/$NAME --language en
**モデルの選択(2026-07-02 改訂 —「単語さえあればよい」は誤りでした):**トランスクリプトのテキストはアラインメントの入力となるため、トランスクリプトの誤りはタイミングの誤りにつながります。AI is bad at jokes の実行では、tiny.en は以前の警告で予測されていた単語(「bland」、「slop」)を正確に落とし、「Compare this to」を「comparative is」と聞き間違え、行全体を撮り直した箇所をまとめてしまいました。その誤ったテキストをアラインメントした MFA は、後半で 1~6 秒ずれてしまいました。撮影テイクの構成を素早く確認する最初のパスに限り、tiny.en を使用してください。トランスクリプトに撮り直しや繰り返しが見られる場合、または映像が騒がしい環境や屋外で撮影されている場合は、領域ごとのグラウンドトゥルースパス(ステップ 2.5)で small.en を使えば、実際の切り出しタイミングを取得できます。(MFA の入力にも全編の small.en トランスクリプトを使うのは妥当な改善策です。5 分の動画で約 1~2 分かかります。ただし、こちらも撮り直し箇所をまとめてしまうため、ステップ 2.5 は引き続き必要です。)英語以外の言語では --model base を使用し、--language を削除してください。whisper のタイムスタンプをクロスチェック/フォールバック用に保持するためだけに --word_timestamps True を指定しています(ステップ 1.5 の注意事項、2a-legacy)。主要な切り出し元として使うことはありません。
Step 1.5 — MFA(Montreal Forced Aligner)で音声に文字起こしをアラインする — タイミングエンジン
これはこのスキルの中核です。**MFA は whisper の文字起こしテキストを音声に強制アラインし、約10~20msの精度で単語境界を返します。また、実際の単語間の無音は、明示的な空区間として返します。**カットに関するすべての判断(ギャップ、フィラー、言いよどみ、撮り直し)には、MFA の単語タイムスタンプを使います。
**MFA の自動インストール(スキルが自動で行うため、ユーザーに作業を求めないでください)。**実行のたびに、最初にこの冪等なブートストラップを実行してください。必要なものがすべて揃っていれば何もせず(数秒で完了)、初めてのマシンでは一度だけ約2~3分かけてインストールします。実際に何かをインストールする場合に限り、「MFA をインストールしています(初回のみ)…」とユーザーに伝えてください。
# 1. Ensure conda exists (MFA's only supported install path). Install miniforge via brew if missing.
if ! command -v conda >/dev/null 2>&1; then
echo "conda not found — installing miniforge (one-time)…"
brew install --cask miniforge || brew install miniforge
# make conda available in this shell
eval "$("$(brew --prefix)/bin/conda" shell.bash hook 2>/dev/null || conda shell.bash hook)"
fi
source "$(conda info --base)/etc/profile.d/conda.sh"
# 2. Ensure the mfa env exists with MFA installed
if ! conda env list | grep -q '^mfa\b'; then
echo "creating mfa conda env (one-time)…"
conda create -n mfa -c conda-forge montreal-forced-aligner -y
fi
# 3. Ensure the English acoustic model + dictionary are downloaded (idempotent; skips if present)
conda run -n mfa mfa model download acoustic english_mfa 2>/dev/null || true
conda run -n mfa mfa model download dictionary english_mfa 2>/dev/null || true
brew 自体が見つからない場合、MFA は自動インストールできません。その場合は whisper+silencedetect の手順にフォールバックし(以下のフォールバック項目を参照)、MFA の精度を利用するには brew install miniforge が必要だと、1行でユーザーに伝えてください。
実行のたびに:
# corpus = a dir with audio.wav + audio.txt (the whisper transcript as PLAIN TEXT, no timestamps)
mkdir -p /tmp/cut-video/$NAME/corpus
cp /tmp/cut-video/$NAME/audio.wav /tmp/cut-video/$NAME/corpus/
python3 -c "import json;d=json.load(open('/tmp/cut-video/$NAME/audio.json'));open('/tmp/cut-video/$NAME/corpus/audio.txt','w').write(d['text'])"
conda run -n mfa mfa align --clean /tmp/cut-video/$NAME/corpus english_mfa english_mfa /tmp/cut-video/$NAME/aligned
# → aligned/audio.TextGrid — "words" tier: (start, end, word); EMPTY-label intervals = true silences
TextGrid の words 層を解析します(pip の praatio、または20行程度のパーサーを使用します。インターバル層はプレーンテキストです)。MFA を使う利点は次のとおりです。
- 正確な単語境界 → カット前後の余白を約0.02秒まで縮められ、単語の頭や末尾が切れません。
- 実際の無音が明示的な空区間になる → 2a のギャップ表を TextGrid から直接作成でき、whisper に埋め込まれた休止の問題がなくなります。
- **注意点:**MFA がアラインするのは、渡された文字起こしテキストです。whisper が発話されたどもり(「growth… growth marketer」→「growth marketer」)をまとめてしまうと、その周辺でアラインメントが局所的にずれます。そのため、
silencedetectによるパス(2a)は相互確認として残し、0.3秒を超える不一致は再確認が必要な疑わしい領域として扱ってください。撮り直しの多い自撮り映像では、ずれは局所的ではなく累積し、数秒に達することがあります(冒頭の警告と Step 2.5 を参照)。さらに、ノイズフロアのせいで silencedetect も機能しない場合(低ダイナミックレンジ時のガード)、自動の相互確認手段はありません。その場合、Step 2.5 は任意ではなく必須です。 - **ずれを検知する警報 — 伸びた単語:**MFA または whisper で、短い単語の長さが≳1秒(2026-07-02 の実行では、「like」が1.16秒、「there's」が1.5秒、「of」が2.4秒、「the」が4.9秒)になっている場合、その中に休止、言い間違い、または完全にまとめられた撮り直し全体が隠れています。伸びた単語をすべて最初に集めてください。それぞれが、再度ウィンドウを切って確認するまで、実際の内容がわからない領域を示しています(Step 2.5)。伸びた「the」の中には、文全体をきれいに話したテイクが丸ごと含まれていたことがあり、そのテイクが採用されました。
- **フォールバック:**MFA/conda を利用できず、ユーザーがインストールを希望しない場合は、whisper の単語タイムスタンプ+音声の silencedetect(2a)にフォールバックし、計画の概要にもその旨を記載してください。whisper のタイムスタンプをカットに使用するのは、この手順のみです。
Step 2 — カットリストを作成する(デフォルトでは積極的にカット)
MFA の単語区間(フォールバック時のみ whisper JSON)から、保持する(開始、終了)区間のリストを計算します。積極的なカットでは、次の4つのカテゴリのコンテンツを削除します。
2a. 無音の間 — まずMFAの空の区間を使い、音声の無音検出で照合する(2026-06-10更新)
MFA(ステップ1.5)を使う場合、真の無音はTextGridのラベルが空の区間です。以下の間隔テーブルを直接適用してください。音声の無音検出でも照合します。ただし、しきい値は録音自体のノイズフロアに合わせて調整してください。-30dB をハードコードしてはいけません(2026-06-16追記。DJIマイクをカメラに取り付けて収録したテイクで問題が発生しました。ノイズフロアは約-24dBで、固定の-30dBではテイク全体が非無音と判定されました。その結果、すべての「無音」の間が「しきい値以上=音声あり、残す」と判定され、約8秒の無音の間がカット後も残りました)。
# 1. Measure the noise floor F and speech level S from per-0.5s-window RMS
# (slice the wav into 0.5s windows, run volumedetect on each, collect mean_volume;
# F ≈ 10th-percentile window RMS (room tone), S ≈ 90th-percentile (speech))
# 2. Set the silence threshold RELATIVE to the floor, not absolute:
THRESH=$(python3 -c "print(f'{F + 6:.0f}')") # 6 dB above the measured floor
ffmpeg -i proxy.mp4 -af silencedetect=noise=${THRESH}dB:d=0.14 -f null - 2>&1 | grep silence_
⚠️ ダイナミックレンジが狭い場合のガード(2026-06-16の重要な教訓): S − F < ~8dB の場合(エアコンのハム音がある屋外/DJI/ラベリアマイクなど、1秒ごとのRMSが発話時も無音時も同じになるノイズの多いマイク)は、エネルギーしきい値で発話と無音を分離できません。その場合、silencedetect/volumedetectはまったく信用しないでください。すべてのカットをMFAの単語境界に基づいて行うか、MFAを使わないフォールバックでは**small.enの単語の出現時刻+伸長した単語によるポーズ検出**を使ってください(ささやき声の単語の長さが通常よりはるかに長い場合、その単語の中に隠れたポーズがあります。出現時刻から約0.10 + 0.105·len(word)秒を残し、次の単語の開始位置までカットします)。プランの要約に、どちらの方法を使ったか記載してください。
MFAを使わない場合、silencedetectの処理が信頼できる間隔の検出元になるのは、ダイナミックレンジのガードを通過した場合に限ります。mlx_whisper/whisper-turbo(およびtiny.en)は、ポーズを単語の長さに含めます。3秒の「単語」は実際には「単語[長いポーズ]」であり、単語間の間隔は約0.00秒と判定されるため、間隔に基づくトリミングでは何もできません。フォールバックではtiny.enではなくsmall.enを使ってください。tinyは単語を無音にまたがってさらに引き延ばし、単語(「bland」、「slop」など)やテイク全体を気づかれないまま取りこぼします。silence_start/silence_endを解析して、該当区間を削除します(パディングは約0.04秒、MFAの精密な境界を使う場合は約0.02秒)。「ポーズがほとんどない」場合は、d=0.12、パディングは0.03にします。重複除去(2c/2d)と組み合わせ、silences ∪ dropped-word-rangesを削除します。これで中央値を約1.1~1.3秒にできます。
- whisperが隠す言いよどみ: turboは発話の繰り返し(「growth… growth marketer」→「growth marketer」)をまとめてしまい、実行ごとに結果が変わります(単語が消えたり、再び追加されたりします)。文字起こしに基づくカットでは検出できません。MFAのアラインメントのずれとsilencedetectの不一致から該当箇所を特定して再確認するか、ユーザーにタイムコードを尋ねて正確な範囲をカットしてください。
2a-legacy. 単語間の無音の間(厳格なしきい値)
| 間隔 | aggressive(デフォルト) | balanced | sentimental | documentary |
|---|---|---|---|---|
< 0.25s | 残す | 残す | 残す | 残す |
0.25–1s | 0.1sに短縮 | 0.3sに短縮 | 0.5sに短縮 | 残す |
1–3s | 0.2sに短縮 | 0.8sに短縮 | 1.5sに短縮 | 2sに短縮 |
> 3s | 0.2sに短縮 | 0.5sに短縮 | 1sに短縮 | 3sに短縮 |
aggressiveのしきい値は、参照動画のテンポに合っています(カット間隔の中央値は1.13秒)。
2b. フィラー語 — 50msのパディングを付けてカット
常にカットする語:um、uh、umm、uhh、er、erm、ah、mhm、hmm(単独で使われている場合)
2c. ヘッジ表現と弱い接続表現 — デフォルトモードでは積極的にカット
これらがフィラーとして使われている場合にカットします(実質的な内容として使われている場合は除きます)。カット対象としてマークし、レビューのステップで文脈を確認してください。
- ヘッジ表現:
like(たとえとしてではなくフィラーとして使われている場合)、you know、I mean、I guess、I think(断定ではなく、ためらいを示す場合)、kind of、sort of、basically、literally、actually(フィラーとして使われている場合) - 弱い接続表現:
so(文頭)、and so、and then、but um、but uh、okay so、right so、well - 重複した限定表現:
kind of like、sort of like、or whatever、or something - 話を戻す表現:
anyway、so anyway、the thing is、what I'm saying is、to be honest、honestly
balanced/sentimental/documentaryモードでは、ヘッジ表現が言い直しに先行する場合(言い直し、2dを参照)に限りカットします。会話的な場面では残してください。
2d. 言い直しと繰り返し — 積極的に削除する
次の点を調べて検出します。
- 言い直し: 話者が約3秒以内に同じ文の出だしをもう一度言います。パターン:
"I want — I wanted to..."、"It's a — it's an interesting..."。先に言った部分と、その間の間を削除します。 - 言い直しの前置き:
"so basically what I'm saying is..."、"the point is..."、"let me start over..."。前置きを削除し、本題を残します。 - 逐語的な繰り返し: 話者が3語以上の同じ語句を続けて2回言います。より明瞭な方を残します。
実装のヒント:連続する単語の n-gram に対してスライディングウィンドウであいまい一致を調べます。3秒以内に3-gram の類似度が 0.8 を超えたら、先に出てきた部分を削除対象としてマークします。
2d-bis. 行全体の撮り直し(自撮りのカメラ目線録画、2026-06-10 追加)
自撮りのイントロや宣伝動画には、5〜60秒離れて録られた文全体の撮り直しが含まれます(「The next person is Carmen who works. … The next person, the next guest I invited is Carmen who's social and content lead at Slate」など)。これは2dの3秒の対象範囲をはるかに超えます。検出して解決してください。
- 検出: 4-gram、類似度 > 0.7、60秒のウィンドウであいまい一致を調べます。また、撮り直しでは通常、文の冒頭から言い直すため、文の冒頭(最初の3〜5語)も照合します。
- デフォルトでは最後のテイクを残す — 話者は納得できるまで録り直します。ただし、最後のテイクが未完了または言いよどみで、前のテイクが明瞭な場合は、明瞭な方を残し、計画の要約にその例外を記載します。
- 撮り直しの境界には通常、長い間、息継ぎ、クリック音、または撮り直しを示す言葉(「okay」、「again」、「take two」など)が先行します — 間と冒頭の一致が組み合わさっている場合が、最も強い兆候です。
- Step 3 の計画では、撮り直しのグループをすべて明示します(
line → takes at [t1, t2, t3] → keeping t3)。これにより、どのテイクを残すかユーザーが変更できます。
2e. 絶対に削除しないもの — 明示的に保護する
- 笑い声。 Whisper は笑い声を無音として文字起こしします。無音を少しでもカットする前に、音声の振幅を確認します。
ウィンドウの RMS を固定の dB ではなく、測定したノイズフロア F(2a)と比較します。ウィンドウの RMS がffmpeg -i audio.wav -af "volumedetect" -f null - 2>&1 | grep mean_volumeF + ~10dBを上回る場合、聞き取れる音(笑い声、息継ぎ、反応音)が含まれているため、残します。固定の-30dB/-25dBをしきい値にすると、ノイズフロアがすでにその値を上回るノイズの多いマイクでは機能しません。その結果、無音の室内ノイズを笑い声のように「保護」してしまい、まさにそのせいで無音部分が残ります。ウィンドウごとの RMS を取得するにはffmpeg -i audio.wav -af "astats=metadata=1:reset=1,ametadata=print:key=lavfi.astats.Overall.RMS_level"を使います。(また、2a の低ダイナミックレンジに関する注意点も忘れないでください。S − F < ~8dBの場合、RMS では笑い声と室内ノイズを区別できません。その場合は MFA/単語の開始時刻に切り替え、判断が難しい箇所はすべて計画に記載して、ユーザーに判断してもらいます。) - コメディの間。 オチの後に意図的に置かれた間です。
aggressiveモードでも、0.4〜0.6秒に詰める程度にします(完全には消さず、短くするだけです)。 - 反応音。 ため息、「ooh」、「wow」、息をのむ音などは、すべてコンテンツです。
- 一言のオチ。 文の最後に間を置いて一言の決めぜりふが続く場合は、その間を残します。
2f. 厳格チェック — 繰り返しは一切許容しない(Louise、2026-06-10:「繰り返しはすべて取り除く」)
keep list を作成したら、機械的にスキャンしてください。トランスクリプトを目視で確認するだけでは不十分です。
- 保持範囲内の MFA の単語をスキャンし、(a)連続する重複単語、(b)セグメント境界をまたぐ重複バイグラム(言い直しの失敗はカットをまたぐ場合があります。「…meet her. And we're [flub] / And we're going to…」が以前、カットをすり抜けました)、(c)隣接する文で同じ文頭表現が重複していないか(「So… So…」→2つ目をカット)を確認します。
- 統計の途中にあっても、言いよどみはカットします。「I believe」、文頭の「Like」、「I guess」などです。修辞的な繰り返し(並列構造)は意図的なものなので残します。
- 単語を削除するときは、MFA の単語末尾まで完全にカットします(さらに+0.01秒)。母音が15ms残っているだけでも、その単語として聞こえてしまいます。
- MFA の OOV に関する落とし穴(2026-06-10に発覚):MFA の辞書にない頭字語や名前(「AI」やブランド名)は、アラインメントから気づかれないまま消えます。 単語が隣の単語に吸収され、keep-end を「最後の MFA の単語」に合わせると、実際に発話された単語が欠けます。対策:whisper のチャンク末尾が、最後の MFA の単語末尾より0.2秒を超えて後ろにある場合、その境界には(whisper の末尾、次の silencedetect の開始位置)のうち後ろの方を採用し、独立したセグメントの文字起こしで接続部を確認します。
- 検証時の落とし穴:出力を whisper にかけても、2つの点で誤ります。 (a)残ったつっかえをひとつにまとめます(実際には「and we're… and we're」と言っていても、1回だけ文字起こしされます。問題がないように見えても、実際は違います)。(b)カットの接続部に文脈上の単語を幻覚します(「girls」+「Enjoy」の接続部が「So enjoy」と文字起こしされる場合があります。問題があるように見えても、実際は違います)。そのため、両方を使って検証してください。保持範囲の MFA 重複スキャン(実際のつっかえを検出)と、疑わしい接続部のセグメントごとの独立した文字起こし(幻覚を除外)です。重複スキャンで何も検出されない場合に限り、納品してください。
- ⭐ ASR 間の相互検証 — 最も強力なつっかえ検出方法(2026-06-10に発見)。 MFA の重複スキャンで確認できるのは、WHISPER が文字起こしした単語だけです。whisper がフレーズの言い直しをまとめてしまった場合(「with the most recent video… with the most recent video」→1回)、MFA はその繰り返しの存在を認識できず、問題のあるカットをスキャンに通してしまいます。対策:カットを独立した2つ目の ASR にかけ、その結果を比較します。 Tella にアップロードした後の文字起こしは、この用途に最適です(別のモデルなので、見落とす箇所も異なります)。実際、whisper+MFA のパイプラインでは問題なしと判定されたのに、1回の確認で5つの繰り返し(フレーズの重複、「or, or」が残存、単語の途中で切れた断片「pers-」/「per-」が2つ、そして「into」の重複)が検出されました。手順:カットを Tella(または別の ASR)にアップロード → その文字起こしから重複単語/バイグラム/フレーズと、単語途中の断片(「xyz-」)を探す → 該当箇所を元の音声の時刻に対応づける → カットする → 再検証する。2つの ASR の結果が食い違う箇所こそ、修正するかユーザーに知らせるべき箇所です。
Step 2.5 — 独立したウィンドウで、残す境界すべてのグラウンドトゥルースを確認する(2026-07-02 追加 — この手順で作業全体を救えました)
MFAから分かるのは、発話の構成(何が、おおよそどこで話されたか)です。撮り直しの多い映像やノイズの多い映像では、カット時刻を正確に特定できるとは限りません。レンダリングの前に、残す予定の領域をすべて、small.enを使って数秒程度の独立したウィンドウで再文字起こししてください。短いウィンドウ内ではWhisperの単語タイミングは正確で、全体のパスで抜けた単語(「bland」「slop」)や、全体のパスで一つにまとめられた撮り直し(「It turns out— it turns out…」)も聞き取れます。
# windows: name:start:duration — start ≥0.5s BEFORE the expected onset
for n in "r1:5.5:8.4" "r2:17.0:6.0" "r3:30.5:6.0"; do
f=${n%%:*}; rest=${n#*:}; o=${rest%%:*}; t=${rest##*:}
ffmpeg -y -hide_banner -loglevel error -ss $o -t $t -i audio.wav gt/$f.wav
whisper gt/$f.wav --model small.en --word_timestamps True --output_format json --output_dir gt --language en >/dev/null 2>&1
python3 -c "
import json
d=json.load(open('gt/$f.json'))
print('=== $f (+$o) ===')
for s in d['segments']:
for w in s.get('words',[]):
print(f\"{$o+w['start']:7.2f}-{$o+w['end']:7.2f} {w['word']}\")"
done
(このループはzshで安全に動作します。set -- $varは使わないでください。zshでは引用符で囲まれていない変数の単語分割は行われません。)
ウィンドウの読み方に関するルール:
- ウィンドウ端にかかる単語は引き伸ばされており、信頼できません。 最初の単語がウィンドウの開始時刻ちょうどから始まっている場合や、最後の単語がウィンドウの終了時刻に達している場合、その単語は切れています。実際の開始/終了位置はウィンドウの外側にあります。その時刻を使う前に、境界を約1秒外側に移動してウィンドウを再設定してください。
- ウィンドウ内の引き伸ばされた単語にも、音声が隠れていることがあります。
small.enは全体のパスと同じように、ウィンドウ内の繰り返しも一つにまとめます(93.1~97.9の「to」には、丸ごと放棄されたテイクが含まれていました)。約1秒以上に伸びた単語があれば、ウィンドウをより狭く設定してください。全体のパスのASRではどちらも検出されなかった完全で明瞭なテイクが、ウィンドウ内に収まっていることもあります。断片をつなぎ合わせて編集する前に確認してください。 - 同じ単語について、2つのウィンドウの結果が食い違うことがあります。 一方では言い間違いを捉え、もう一方ではその箇所が切れていた場合などです。問題の箇所がウィンドウの両端から離れた中央付近に来るように、3つ目のウィンドウを設定して解決してください。
- 最終的に残す時刻:開始位置は−0.03秒、終了位置は+0.04~0.08秒の余白を加えます(余白は最小限にしてください。20か所に+0.10秒ずつ加えると、合計2秒のもたつきになります)。
- 残すテイクの中のポーズもトリミングしてください(2026-07-02 追加 —「無音をさらに減らす」)。 撮り直しやテイク間の隙間を削除するだけでは不十分です。流暢なテイクにも文中の間(「…for a living, [0.3s] I want to know…」)があり、積み重なると長くなります。残す各区間のグラウンドトゥルースの単語を確認し、単語間の間隔が0.2秒を超える箇所は、それぞれ約0.1秒まで詰めてください(
[gap_start+0.05, gap_end−0.05]をカットします)。意図的な修辞上の間(「median… or average」)は、約0.15秒より短くしないでください。 - 引き伸ばされた単語の落とし穴、残す側(2026-07-02に失敗): 伸ばして発音された単語は、ASRでは「単語+後続のポーズ」と見分けがつきません(「slooop」が
slop 285.08–286.32と文字起こしされました)。ポーズに見える部分をトリミングすると、母音の途中で単語が切れてしまいます。残す境界で引き伸ばされた単語の途中までトリミングする前に、ウィンドウを狭く設定して、実際に声が止まる位置を確認してください。レンダリング後の確認用文字起こしから、レンダリング前にあった単語が消えている場合は、トリミングでその単語を削ってしまっています。境界を外側に戻してください。
5分の動画でかかる時間:約10~15個のウィンドウで、それぞれ数秒分のsmall.enを実行するため、約2分です。きれいにカットできるか、後半全体をやり直すかを分ける手順です。
ステップ 3 — 計画を提示し、すぐにレンダリングする
承認を待たないでください。要約を表示し、同じターン内でレンダリングを開始してください(変更日:2026-07-09:Louise が質問に気づかなかったため、承認を待つことで無駄な時間が発生しました。レンダリングは破壊的な処理ではなく、やり直しも簡単なので、先にレンダリングするほうが確実に適切です)。この要約を表示してから、すぐにステップ 4 に進んでください。
Original duration: 7m 25s
Cleaned duration: 3m 58s
Cut: 3m 27s (47%)
Total keep intervals: 312
Median cut duration: 1.2s (target: 1.1–1.5s)
Hard cuts (dead air > 3s removed): 23
Filler hits: 47
Hedge/transition cuts: 89
False-start drops: 12
Notable preserved long pauses: list timestamps + context (laughs, punchlines)
ベンチマークとの照合によるセルフチェック:
- カットの中央値が 2 秒を超える場合 → 積極性が足りません。間隔のしきい値を厳しくするか、ヘッジ表現のリストを拡張して再試行してください。
- カットの中央値が 0.7 秒未満の場合 → カットしすぎです。呼吸や話のリズムを損なっている可能性があります。0.25〜1 秒のしきい値を 0.3 秒に緩めてください。
- 目標とする圧縮率:アグレッシブモードで 40〜60%。
- 例外 — 撮り直しの多い映像(2026-07-02): 実行時間の大半が途中で放棄されたテイクの場合、圧縮は単語単位の切り取りではなく、行全体を削除することで実現します。その場合、保持される部分の中央値が 3〜5 秒、圧縮率が 65〜75%となるのが正解です。代わりに圧縮率と最終スクリプトの仕上がりで判断してください。流暢なまま残すテイクを、中央値 1.1〜1.5 秒という目標に無理に合わせるためだけに、文の途中で余計なカットを入れないでください(この目標は、撮り直しがなくフィラーの多い独白を対象としています)。
セルフチェックを自分で実施し、違反があればレンダリング前に修正してください。これが品質ゲートであり、今はユーザーが確認するものではありません。その後、すぐにレンダリングしてください。ユーザーは結果を確認します。カットによって笑い声や意図的な間が失われていた場合は、保持リストを調整して再レンダリングしてください(すぐにできます。再文字起こしではなく、トリミングと連結です)。
ステップ 3.5 — 手動タイムラインエディター(任意 — Louise が希望した場合のみ)
(2026-07-02 に常時実行から変更:Louise は実際に使ってみてエディターが扱いにくいと感じ、パイプラインで自動的にもっと細かくカットする方法を好みました。ステップ 2.5 のテイク内の無音トリミングは、この結果として追加されました。手作業で確認・調整したいと希望した場合のみ、エディターを生成してください。)
レンダリングの前(または後)に無音をトリミングし、不要な部分を削除できるタイムラインエディターです。
# keeps.json must be [start, end, "transcript text"] triplets in the working dir;
# needs proxy.mp4 (and ideally audio.wav) in the same dir. Generates wave.png once.
python3 ~/.claude/skills/cut-video/make_review.py /tmp/cut-video/$NAME
open /tmp/cut-video/$NAME/review.html
このページでできること(proxy.mp4 の隣に配置する自己完結型 HTML で、サーバーは不要です。波形のピークと無音の候補はファイルに埋め込まれているため、file:// で動作します):
- 元ソース全体のキャンバス波形タイムライン。どのズームレベルでも鮮明に表示されます。ピンチまたは ctrl/⌘+ホイールでカーソルを中心にズーム(連続ズーム、全体表示 → 400px/s)。マウスホイールでパンできます。+/−/Fit ボタンもあります。クリックしてスクラブすると、再生ヘッドが動画と同期します。
- 波形をドラッグして範囲を削除 — 無音除去の主な操作です。平坦な箇所を見つけてスワイプすれば、削除できます。ブロック境界をまたいで操作でき、その範囲にかかる部分をトリミング/分割/削除します。
- 琥珀色の斜線帯 = 保持ブロック内で自動提案された無音箇所(ピークベースの適応しきい値。ジェネレーターが計算して埋め込みます)。帯をクリックしてカットすると、両側に 0.1s の間が残ります。または、最小時間スライダーを使って 「✂ Cut all」 を実行すると、すべての候補を一度に処理できます。ノイズの多いマイクでは候補の精度が下がるため、表示された候補を本人が判断します。
- ブロックの端をドラッグしてトリミングできます。ドラッグ中に動画がリアルタイムでシークするため、カット位置を音で確認できます。選択した端は ±0.05s ずつ微調整できます(ボタンまたは ←/→ キー、shift = 0.25s)。
- 再生ヘッド位置で分割(
S)、ブロックを削除/復元(D/⌫、またはそのカードをクリック)、ギャップをダブルクリックしてカットした映像を復元、元に戻す(Z、60 段階)。 - 下部のカード一覧では、セグメントごとに ▶ で再生でき、文字起こしテキストも確認できます。「Preview final cut」 を使うと、カットを飛ばして保持ブロックを連続再生できます。レンダリングせずに最終カットをリアルタイムでプレビューできます。
- 「Copy decisions for Claude」 を使うと、
{"keeps": [[a,b], ...]}がコピーされます。これはソースプロキシ上の秒数で表した、編集済みの保持リスト全体です。
決定内容の適用方法:貼り付けた keeps 配列で以前の保持リストを置き換えます。以前の keeps.json と時間的に重なる範囲を使って文字起こしテキストを引き継ぎます(ギャップから作成されたブロックにはテキストがないため、「(restored)」というラベルを付けます)。filter.txt を再構築して再レンダリングします。プロキシはすでにあるため、修正版の作成にかかる時間は数秒です。
境界の扱い:手動でドラッグした端は意図的なものなので、MFA/ASR の単語境界に再スナップしないでください。端が正解の単語境界データ上で単語の途中にある場合に限り、警告してください。該当する単語を示し、最も近い適切な境界を提示してください。
ステップ 4 — select ではなく、trim + concat でレンダリングする
重要: select/aselect ではなく、trim+concat フィルターグラフのパターンを使ってください。select フィルターは映像フレームを圧縮しますが、音声の PTS は適切に圧縮しないため、音声と映像がずれてしまいます。
保持する各区間に trim+atrim のペアを 1 つずつ配置し、concat で結合するフィルターグラフを作成します。
[0:v]trim=A1:B1,setpts=PTS-STARTPTS[v0];
[0:a]atrim=A1:B1,asetpts=PTS-STARTPTS[a0];
[0:v]trim=A2:B2,setpts=PTS-STARTPTS[v1];
[0:a]atrim=A2:B2,asetpts=PTS-STARTPTS[a1];
...
[v0][a0][v1][a1]...concat=n=N:v=1:a=1[outv][outa]
フィルターグラフはファイルに書き込みます(長くなるためです。大幅にカットする場合、7 分のソースで 200~400 区間になります)。-filter_complex_script を使ってください。レンダリングコマンド:
ffmpeg -y -hwaccel videotoolbox -i /tmp/cut-video/$NAME/proxy.mp4 \
-filter_complex_script /tmp/cut-video/$NAME/filter.txt \
-map "[outv]" -map "[outa]" \
-c:v libx264 -preset fast -crf 20 -pix_fmt yuv420p \
-c:a aac -b:a 192k \
/tmp/cut-video/$NAME/cleaned.mp4
カット数が多い場合の高速な代替方法: フィルターグラフの trim が約 500 を超える場合は、セグメントごとの抽出とロスレス結合に切り替えます。
- 保持する各区間を、ストリームコピーした個別のチャンクとして抽出します
- concat デマルチプレクサー用のファイルを作成します
ffmpeg -f concat -safe 0 -i concat.txt -c copy cleaned.mp4
これにより、トリミング処理での再エンコードが完全に不要になります。
Step 5 — 納品
- 最終ファイルを
<source_dir>/cut_out/<source_name>_cut.mp4に保存します(存在しない場合は mkdir) - 1 行で出力します:元の長さ → 編集後の長さ、カット率、カット時間の中央値、出力パス
- すぐにユーザーが確認できるよう、ファイルを
openします - 修正はすぐにできます。プロキシとグラウンドトゥルースで確認したタイムコードは作業ディレクトリに残るため、「2 つ目のセグメントを削除」/「X を詰める」といった指示にも数秒で再レンダリングできます。(Step 3.5 のエディターを依頼された場合は、決定内容を貼り戻すために引き続き利用できることを伝えてください。)
- 調整を提案します。ギャップのしきい値の再調整、トーンプリセットの切り替え、残す/カットする特定の場面の指定ができます
落とし穴 — 繰り返さないでください(過去の実行から学んだこと)
- プロキシ作成の手順で
-hwaccel videotoolboxを省略しないでください。7 分の 4K ソースでは、HEVC のソフトウェアデコードに 5 分以上かかることがあります。このフラグを使えば約 30 秒です。 - カットに
selectフィルターを使わないでください。音声の位置がずれます。trim と concat を使ってください。 -preset medium(libx264 のデフォルト)は使わないでください。作業用コピーでは、品質が変わらないのに-preset fastの約 3 倍遅くなります。- レンダリング前に承認を待たないでください(2026-07-09 に変更 — 確認の段階で、節約できた時間より多くの時間を無駄にしました)。プランを表示して、すぐにレンダリングしてください。以前懸念していた(笑い声を含む「無音」のギャップや、意図的な強調としての「つなぎ言葉」)問題には、以下の振幅チェックとサマリーの保持する間のリストで対処できます。カットに問題があっても、すぐに再レンダリングできます。
- 長い「無音」をトリミングする前に、振幅を確認してください。通常、それは笑い声、考える間、または伏線とオチの間です。
- プロキシがある場合、ソース全体を
whisperにかけないでください。プロキシから抽出したaudio.wavを対象に whisper を実行します。 - 音声を 2 回再エンコードしないでください。映像だけを変更した場合は
-c:a copyを使い、不要な AAC エンコードを省略します。 - 遠慮しないでください。デフォルトは
aggressiveにしてください。基準となるカットスタイルはテンポが速いため、出力が「無難」に感じられるなら、Louise の CapCut のテンポに合っていません。 - リテイクが多い映像やノイズの多い映像では、MFA のタイムコードだけを使ってレンダリングしないでください。2026-07-02 の DJI の実行では、1〜6 秒のずれが計測されました。まず Step 2.5 で分離したウィンドウを使って、残す箇所の境界をすべてグラウンドトゥルースで確認してください。
- 縦向きのソースを 1920:1080 にスケーリングしないでください。まず向きを確認してください。DJI/スマートフォンの映像は通常 9:16 です。
- 分離した文字起こしのウィンドウ端にある単語のタイムコードを信用しないでください。ウィンドウ境界に触れている単語はクリップまたは引き伸ばされているため、カットポイントとして使う前にウィンドウを切り直してください。
h264_videotoolbox -b:v 8Mはプロキシだけでなく、最終レンダリングにも適しています。M シリーズでは、4K-HEVC→プロキシ→18 セグメントのレンダリング全体が約 1 分で完了しました。
ベンチマークの基準
AI mogging my dad.mp4(2026-05-03、CapCut プロジェクト 0501):
- 460 秒間に 335 回カット = 1.37 秒に 1 回カット
- 中央値 1.13 秒、平均 1.37 秒
- 41% のカットが 1 秒未満、12% が 0.5 秒未満
- 5 秒を超えるカットはわずか 3 回
- 元の映像を約 60% 短縮
「aggressive」モードの調整では、この数値を正解の基準として使います。
このスキルが明示的に行わないこと
- ズーム、レイアウト、モーショングラフィックスの追加(別の作業です。CapCut または [[tella-edit]] で対応します)
- ミームや B ロールの追加(ユーザーが選定します。ショート動画のカットについては [[clipify]] を参照してください)
- 字幕の焼き込み(クリーンアップ後に別の工程で行います)
- ファイルのアップロード
このスキルは 1 つの作業に集中します。長尺の録画からテンポのよい、思い切ったカットの MP4 を素早く作成します。