Communitygithub.com

Istok-Media-Tool/basic

生のワンテイク動画・音声録音(トークヘッドの一発撮りでリテイクやフライングだらけのもの)から、目的のスクリプトに沿ったクリーンな最終編集を、Whisperの文字起こしと無音検出を組み合わせてフレーム精度のカットポイントを特定し、ffmpegで組み立てます。

basic とは?

このスキルは、リテイクや噛み、フライングが多数含まれる連続テイクの動画・音声録音を、スクリプトに沿ったクリーンな最終編集に仕上げるための高度なワークフローを提供します。従来のWhisperのタイムスタンプをそのまま信頼すると、無音部分や誤った開始が一つのセグメントに統合され、不正確なカットになりがちです。この問題を解決するため、本スキルでは無音検出を用いてサンプル精度の発話区間(バースト)を特定し、各バーストを個別に文字起こしすることで隠れたリテイクを表面化させます。編集プランはユーザーが確認・承認した後、`cut_and_concat.sh`スクリプトによりフレーム正確にカット・結合します。出力後は`check_video.py`でフリーズフレームやタイムスタンプの穴などのレンダリング不具合を自動チェックし、品質を保証します。主にmacOS環境で動作し、ffmpeg、Whisper、Pythonスクリプトに依存します。さらに、ズームインや字幕を追加する場合には、plan-editorスキルと連携して対話的に編集を進めることができます。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Istok-Media-Tool/video-edit-skills/tree/HEAD/video-edit/skills/basic

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

基本動画編集(生テイクをスクリプトに合わせてカット)

このスキルが存在する理由

リテイクだらけのワンコンティニュアステイクを、文字起こしだけで正確にカットするのは意外と難しい。このワークフローを初めて手動で行ったとき、Whisperがファイル全体を一度に処理したセグメントのタイムスタンプをそのまま信頼した結果、ある一文が22秒かかって発話されたようなカットプランができてしまった。実際は5.75秒だった。Whisperが16秒の無音と2回のフライング開始をそのセグメントに統合していたのだ。その文字起こしでカットすると、無音だらけの動画ができていただろう。

この教訓を忘れないでください:このワークフローの目標は正確なカットポイントであり、ポーズ・リテイク・繰り返しのある映像では、Whisper単体ではそれらを確実に得られません。 以下はすべてその問題を補うために存在します。

ワークフロー

1. ソースを分析する

何をするよりも先に、動画の基本情報を調べます。

ffprobe -v error -show_entries format=duration,size -show_entries stream=width,height,codec_name,codec_type,avg_frame_rate -of default=noprint_wrappers=0 "<video>"

文字起こし用にモノラル16kHzの音声を抽出します(Whisperがこれを要求し、フル動画よりはるかに小さく高速です)。

ffmpeg -y -i "<video>" -vn -ac 1 -ar 16000 audio.wav -loglevel error

ユーザーからスクリプト・シナリオファイル(txt、rtf、docxなど)が渡された場合は、今読んでおいて、どの内容と比較すべきか把握します。.rtfは多くの場合、textutil -convert txt -stdout file.rtf(macOS)でプレーンなテキストに変換する必要があります。

2. 最初のパス文字起こしで大まかな概要を得る

whisper audio.wav --language <lang> --model medium --output_format txt --output_format srt --output_dir .

これは、全体的な内容と、録音内で各発言がおおよそどの位置にあるかを把握するためだけに使用します。そのセグメントの開始/終了時刻を実際のカットポイントとして使ってはいけません(上記「このスキルが存在する理由」参照)。録音が長い場合、このステップはステップ3と並行して実行するか、スキップしてもかまいません。

3. 無音検出で実際の音声区間を見つける

これが実際に重要なステップです。実行:

python3 scripts/detect_bursts.py audio.wav > bursts.txt

これにより、サンプル精度の音声「バースト」(検出された無音の補集合)が得られ、0.5秒未満の小さなブロック(ほぼ常に呼吸ノイズであり言葉ではない)は除外されます。これらのタイムスタンプを信頼し、Whisperのものは信頼しないでください。

規模の確認:自然な発話はおおよそ秒速2〜3語です。あるバーストの長さが、そこに収まると想定されるテキスト量に比べて極端に不均衡なら、何かがおかしいと考えられます。

4. 各バーストを個別に文字起こしする

python3 scripts/transcribe_bursts.py audio.wav bursts.txt --language <lang> --model medium

これにより、Whisperモデルを一度ロードし、各バーストを自身のショートクリップとして、condition_on_previous_text=False、temperature=0で文字起こしします。この組み合わせこそが隠れたリテイクを表面化させます。ファイル全体のパスでは1つのクリーンなラインとラベル付けされたバーストが、いったん分離されると、即座にフライング開始とそれに続く本番の読みが含まれていることが判明するのです。最初のパスの文字起こしでは静かにマージまたは抑制されていて、まったく見えません。

明らかなリテイクがある録音では、スクリプトのライン数よりも多くのバーストが生成されることが期待されます。特に話者が最も苦戦したラインでは、ラインごとに複数のフライング断片が現れることがよくあります。これは正常であり、まさにこのステップの目的です。

5. 編集プランを立てる——カットする前に確認を得る

文字起こしされたバーストを、スクリプトのどのライン・アイデアに属するかでグループ化します。各ラインに対して、典型的には以下のいずれかが見られます。

  • 1つのクリーンで完全なバースト —— 簡単、それがテイクです。
  • フライング開始に続く完全なバースト —— 完全な方を使用し、断片は破棄します。
  • 同じ文言の完全なテイクが複数 —— これらは純粋なパフォーマンスの重複です。文字起こしだけでは感情や送りを判断できないため、合理的なデフォルト(例:後半の試み、人はテイクに落ち着くことが多いため)でカットし、Dubloviモード(後述)でユーザーに選択させます。
  • 文言が本当に異なる完全なテイクが複数(話者が毎回即興で異なる内容を言った)—— これは単なるパフォーマンスの選択ではなく、内容の選択です。黙って選ぶのではなく、明示的に選択肢をユーザーに提示します。ユーザーは自分の即興フレーズを好むか、スクリプト通りの方を好むか、あるいはその逆もあります。

両方の種類の重複テイクについて、ユーザーはあなたと一緒にそれらを見て決定します。テイクはプランエディターの Dublovi モード(2つのテイクを左右に並べる「Ovaj je bolji」、すべての動画で)に入ります。チャットでテイクを説明したり、「dubl 1 ili 2」と尋ねたりしないでください。あなたのデフォルト選択でカットし、すべての重複を takes_src.json(プランエディタースキル「Dublovi」)にリストし、エディターを開きます。ユーザーが別のテイクを選んだ場合は、カット内のテイクを入れ替えます。

次に、完全なプランをユーザーへのメッセージとして書き出します:すべてのラインで保持される正確なテキスト、その正確なタイムスタンプ範囲、何が削除されるのかとその理由(無音、フライング開始、破棄された代替テイク)。カットコマンドを実行する前に、明示的な確認を待ちます。これは形式的なものではありません。動画編集は破壊的な感じがして、出力を見ずに確認するのは難しいため、ユーザーはプランを見て問題(間違ったテイクの選択、実際には自然に聞こえないラインが隣接してつなぎ合わされているなど)をレンダリング時間を費やす前に把握する必要があります。

ユーザーが合計時間について質問した場合は、保持されるセグメントの長さを合計するだけですが、カット間のパディングが追加されるため、実際の最終時間は少し長くなることに注意してください(ステップ6)。

6. カットと結合

確定した選択(出力順に1行あたり1つのstart<TAB>endペア)をファイルに書き込み、次のコマンドを実行します。

scripts/cut_and_concat.sh "<source_video>" selections.txt final_output.mp4

手動で実行したり適応させたりする前に、cut_and_concat.shの先頭にあるコメントブロックを読んでください。そこには落とし穴(ffmpegがbashループからstdinを奪う、フェードアウトが単語の最後の音節を食べる、各クリップをconcatの前に個別に再エンコードする必要がある理由など)が文書化されており、スクリプトをそのまま使わずにゼロから書き直すと静かに再導入されやすいです。

デフォルト:各カットインの前に0.15秒、各カットアウトの後に0.35秒のパディング、5msのフェードインのみ(フェードアウトなし)、各クリップを再エンコードしたh264(crf 18)+ aac 192k、各クリップを正確なフレーム数にカットし、オーディオも同じ長さにトリミング、ffmpegのconcatフィルターで結合(concatデマルチプレクサーは使わない——以下の「すべてのレンダリングをチェック」参照)。特定のカットで異なる処理が必要な場合(例えば、多くの後続呼吸ノイズがあるラインにはより多くのPOST_PADが必要)、PRE_PAD、POST_PAD、CRF、FADE_IN環境変数でオーバーライドします。

最初のパスで文の最後の文字がまだ切れて聞こえる場合、それはほとんどの場合、その特定のカットに対してPOST_PADがきつすぎることを意味します(話者の後続子音が無音検出のしきい値よりも長く伸びている)。そのセグメントに対してPOST_PADを増やしてください。フェードに頼ると、きついカットがさらに悪化するだけで改善しません。

タイトなペーシング——ユーザーの標準

デフォルトのパディング(0.15秒 / 0.35秒)ではすべてのカットで約0.5秒のポーズが残ります。ユーザーはそれがリズムを壊すと感じたため(「mora biti sve zbijenije i tečnije」)、彼らの動画では組み立て前にすべてのカットをタイトにします。

python3 scripts/tighten_cuts.py audio.wav selections.txt tight.txt
PRE_PAD=0.04 POST_PAD=0.08 scripts/cut_and_concat.sh "<source_video>" tight.txt final_output.mp4 <abs workdir>

これにより、各エッジが実際に音声が開始/終了する位置(-42 dB)に移動され、内部の0.30秒を超えるポーズは分割されます。実際のリールでは、すべての単語をそのままに、1:48から1:41になりました。その後、結果を文字起こして確認します。保持されたテキストのすべての単語がまだ存在している必要があります。古いカットに配置されたもの(クローズアップ、キャプション、タイトル)のタイミングは、ソース時間を通じて再マッピングする必要があり、一定のシフトではいけません。

別々にカットされた2つのパート(例:メインとアウトロ)が結合される場合、結合ポイントは最初のパートの実際のフレーム数(nb_read_frames / fps)から取得し、コンテナの長さからは取得しないでください。実際のリールでは、0.04秒の違いにより、クローズアップとアウトロの間にワイドショットの1フレームが残り、アウトロの最初のキャプションが1フレーム早く表示されました。結合ポイントで終了するオーバーレイは、そのフレームまで実行する必要があります。

すべてのレンダリングをチェック——何かを送信する前に

実際のリールで、ユーザーは1:05にワイドショットの1フレームの迷いフレームを発見しました(「to ne sme da nam se dešava」)。本当の原因はもっと大きかったのです。古いcut_and_concat.shはconcatデマルチプレクサーでクリップを結合していました。これは各クリップをコンテナの長さとAACのプライミング/パディングで配置し、実際のフレームでは配置しません。その結果、10か所のカットで80msの穴(2フレーム)ができ、再エンコード後に画面上にフリーズフレームが発生し、オーディオがゆっくりと映像より先に進み、それに合わせたすべてのオーバーレイとキャプションが1〜2フレームずれました。修正されたスクリプト(ヘッダーの落とし穴#4)は、各クリップを正確なフレーム数にカットし、オーディオを正確にその長さにパディング/トリミングし、concatフィルターで結合します。

それに伴うルール:

  • すべてのレンダリング(カット、ズーム、キャプション、最終)をユーザーに送信する前に、毎回チェッカーを実行します。

    python3 scripts/check_video.py final.mp4 --ass captions.ass --edges edges.txt --cuts joins.txt
    

    joins.txt = 最終タイムライン上のカットリストのすべての結合(各パートのdurs.txtの累積和、およびパート境界)。常に渡してください。同じフレーミング内でのジャンプカットはシーンしきい値をはるかに下回るため、チェッカーはそれを渡さないと、実際のリール(1:15)で結合の3フレーム前のズーム変更を見逃しました——ユーザーが目撃した3フレームのフラッシュです。

    これにより、タイムスタンプの穴、単独のフリーズフレーム、オーディオ対ビデオの長さ、数フレームしかないショット、0.4秒未満のキャプションやピクチャーカットの1〜3フレーム前後で開始/終了するキャプション、カットから2〜5フレームのオーバーレイエッジ(edges.txt:1行に1つのクローズアップ/Bロールの開始または終了、秒単位)が報告されます。終了コード1 = 何かが見つかりました:修正して再レンダリング。「OK - 何も見つかりませんでした」のみが送信されます。

  • すべてのクリップ位置はフレーム数(nb_frames / fps、またはスクリプトが書き出すdurs.txt)から取得し、決してformat=durationから取得しないでください。

  • カットが変更された場合、クローズアップ、キャプション、タイトルはソース時間(古いタイムライン -> ソース -> 新しいタイムライン)を通じて移動し、決して一定のシフトではありません。

  • カットで終了するオーバーレイ(アウトロ前のクローズアップ)は、そのカットのフレームまで正確に実行します。キャプションはそのクリップとともに終了します。

  • 結合の6フレーム以内に入るズーム変更、クローズアップ終了、キャプションエッジはすべてその結合にスナップします(クローズアップ開始は結合がその直前にある場合のみ)。数フレーム離れた2つのピクチャー変更は常にグリッチとして認識されます。1フレーム上では1つのカットとして認識されます。

  • 最後のセグメントには小さなテール(約0.5秒)を追加し、最後のキャプションと単語が途中で切れないようにします。

7. 納品

cut_and_concat.shからのマスター出力はフル品質(crf 18で再エンコード)です。ファイルサイズ制限のあるツールを通す必要がある場合は、マスターを圧縮するのではなく、その目的のために二次的な圧縮コピーを作成します。

ffmpeg -nostdin -y -i final_output.mp4 -c:v libx264 -preset slow -crf 24 -pix_fmt yuv420p -c:a aac -b:a 128k final_output_share.mp4 -loglevel error

ユーザーがダイナミクスのための動的パンチイン/ズームクロップも希望する場合は、zoomスキルを参照してください。これはこのスキルの出力を取得して再カットするか、より効率的には、両方を1パスで組み合わせるために、ズームスキルからのセグメントごとのフィルターチェーンをcut_and_concat.shを1回実行する前にselections.txtの3列目に直接書き込むことができます。必要に応じて縦向きへの回転は別途処理されます(どちらのスキルの一部でもありません)——ユーザーに確認してください。常に望まれるとは限りません。

プラットフォームノート

  • macOSにはデフォルトでGNUのtimeoutがありません——ffmpeg呼び出しをそれでラップしないでください。短いクリップでは高速であり、呼び出し元ツールのバックグラウンド/タイムアウト処理がステップ2の長時間のファイル全体パスをカバーします。
  • バンドルされたスクリプトを使わずに、自分でwhile read ...; done < fileループを書き、その中でffmpegを呼び出す場合は、ffmpeg呼び出しに-nostdinを追加し、そのstdinを/dev/nullからリダイレクトしてください。そうしないと、ffmpegがループのread用の行を消費し、どのタイムスタンプがどの反復に適用されるかが明らかなエラーなしに破損します。

カット後:ズームとキャプションはプランエディターを通す

カット動画にズーム/パンチイン(zoomスキル)および/またはキャプション(subtitleスキル)も追加する場合、ユーザーの標準はplan-editorスキルです。これは対話型の単語クリックツールで、ズームを一緒に決定し、同じ文内でキャプションテキストを修正します(「Izmeni tekst」)。このスキルのプランから保持されるテキストをクリップごとに保持します。それがそのツールのsentences.txtになります(カットされたクリップごとに1行、selections.txtと同じ順序)。

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

関連スキル