Communitygithub.com

Istok-Media-Tool/zoom

ユーザーと一緒にplan-editorツールでズーム計画を立て、カット済みのトーキングヘッド動画に対して、被写体を正しく中央に配置し適切なヘッドルームを確保した動的なパンチインズームを、コンテンツに応じた瞬間に追加します。これにより、異なるズームレベルのカットが被写体を画面内で飛び跳ねさせるのではなく、意図的な映画的カメラワークとして機能します。

zoom とは?

このスキルは、カット済みトーキングヘッド動画に「動的ズーム」を追加するためのものです。主な目的は、単純にすべてのカットでズームするのではなく、台本の内容に基づいて、キーワードや重要な瞬間にパンチイン(拡大)、説明部分でプルバック(引く)など、意図的な編集判断を適用することです。このスキルの核心的な特徴は、被写体を常にフレームの正しい位置(中央、適切なヘッドルーム)に保つことであり、経験的に得られた以下の知見に基づいています:①すべてのセグメントで個別に被写体位置を測定する必要があり、同じ録画内でも座る位置が変わると不整合が生じる、②ワイドショットも広いながらも同じ光学中心とヘッドルームでクロップしないと被写体が飛び跳ねるカットになる、③ズーム計画はplan-editorツールでユーザーと共同で決める(ワード単位でズームを割り当てる)、④ズームレベルは台本のエネルギーカーブに従う(説明=ワイド、盛り上がり=パンチイン、間=プルバック)。このスキルは、Claude CodeやClineなどのAIエージェントで利用可能であり、ffmpegベースで実装されています。具体的なワークフローは、(1) plan-editorでズーム計画を立案、(2) 各クリップの被写体位置を測定、(3) compute_crop.pyでクロップ計算、(4) 単一クリップで出力確認、(5) 確定計画に基づいて全クリップをレンダリング、というステップで構成されます。また、スピーカーがタブレットなどの物体を画面内に表示している場合、その物体もズームの中に収まるように調整する機能も含まれています。

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

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

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

ドキュメント

$SKILLS は、このスキルのフォルダを保持するフォルダです。スキルが読み込まれたときに表示されるベースディレクトリの親(プラグインの skills/ フォルダ)です。

動的ズーム

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

カットされたトーキングヘッド動画に「動的」ズームを追加することは、見た目は簡単そうに見えて、実際に結果を視聴してみるまでわからないミスを犯しやすいものです。数値だけでは判断できません。実際のケースでは、同じ動画が正しくなるまで何度も修正フィードバックを受けました。それぞれの修正から得られた知見を、ゼロから再学習する価値があるものです:

  1. すべてのカットに単純にズームをかけるアプローチは安っぽくギミックに見える。実際の編集判断はもっと控えめで意図的です。

  2. 目の高さを中心に左右対称にクロップするのではなく、実際の頭頂部を基準にすると、スケールを戻したときに頭の上に過剰な空きスペースができる(約17%のヘッドルームに対し、約9%が適切)。

  3. 同じ録画の別のセグメントのクロップ計算に、あるセグメントで測定した頭部位置を再利用すると、数分前後の別のセグメントで被写体が席をずらしている場合、目に見えて中央からずれる。同じ連続録音であっても同様です。

  4. ズームショットだけを補正し、「ワイド」/非ズームショットをまったくクロップしないままにすると、ワイドショットとズームショットの間のカットごとに、被写体が横に飛び跳ねるように見える。ワイドショットにも同じ再中心化とヘッドルーム処理が必要であり、ズームレベルだけが低いのです。これにより、カットが一貫性のあるカメラワークのように感じられます。

  5. クリップごとに1つのズームレベルを設定し、各文の中間点に機械的にパンチインを配置すると、どちらもユーザーにはランダムに見えます。ズームは台本に従う必要があります。すべての変更は、それを正当化する特定の単語やフレーズ(暴露、キーワード、個人的な質問、オチ)に着地し、プルバックは規模に関する単語(「univerzum」、「milijarde godina」)に着地します。変更はクリップ内で発生し、クリップ内で複数回発生することもあります。

  6. ユーザーは、plan-editorツールを使って、単語ごとに一緒にズーム計画を決定したいと考えています(ステップ1)。常にこれを使用してください。どんな台本でも。

  7. ユーザーはズームステップを選択しました:1.0x、1.5x、1.7x、2.0x、2.2x(計画レベル0-4)。「1.0x」(レベル0)は生のフレームではありません。生のクロップされていないフレームとしてレンダリングすると、被写体が中央からずれ、頭の上にスペースがありすぎました。ユーザーは、他のすべてのショットと同じヘッドルームで中央に配置するよう依頼しました。そのため、レベル0 = --baseline クロップ(9%のヘッドルームを満たす最もルーズなクロップ、再中心化済み。実際には約1.2倍)です。build_zoom_selections.py が自動的にこれを行います。ツール内のラベルは「1,0x」のままにします。これは「最も広く、中央揃え」を意味します。

ステップ1:plan-editorツールで一緒にズームを計画する

すべての動画で必須:plan-editor スキール($SKILLS/plan-editor/SKILL.md)をロードして従ってください。これはユーザーがあなたと一緒にズームを決定し、「Izmeni tekst」を使用して同じ文内のキャプションテキストを修正するための標準的なクリック・ア・ワードツールです。ユーザーがそこで形作っていない計画からズームをレンダリングしないでください。

このステップでのあなたの役割は、開始提案を作成することです:コンテンツのエネルギー曲線から構築します。導入/落ち着いた部分=ワイド、ひねりやオチはそのキーワードでプッシュイン、説明のビートは息継ぎのためにプルバック、クライマックスは最もタイトなフレーム。決定的なレベルの差(微妙なクリープはなし)。オープニング後に少なくとも1回はワイドに戻ること。各変更の理由を述べてください。ユーザーの返信(PLAN_JSON、SENTENCES_JSON)が確定された計画です。ステップ5でそれを正確にレンダリングします。

計画を適用するには、単語のタイムスタンプ(ステップ5)とクリップごとの被写体位置(ステップ2)が必要です。

examples/sample/ は、transpose=1 で回転された横向きの動画の実際の確定セット(文、ユーザーの計画、位置、カット)を保持しています。ファイル形式と、このユーザーが選択する計画の種類の参考例です。

ステップ2:被写体の位置を測定する - セグメントごとに、毎回

別のセグメントのクロップ計算に、一度測定した頭部位置を再利用しないでください。たとえ同じ連続録画内であってもです。長いテイクの数分後には誰かが席をずらしている可能性があり、初期のセグメントのフレームから計算されたクロップは、後のセグメントで被写体を中央からずらしたり、タイトなズームでは実際にクリップしたりする可能性があります。そのセグメントは似ていますが、まったく同じフレームではありません。

クロップしようとしている各セグメントについて(「ワイド」のものも含む。ステップ4を参照):

ffmpeg -nostdin -y -ss <mid_timestamp_of_that_segment> -i source.mp4 -frames:v 1 ref.png
ffmpeg -nostdin -y -i ref.png -vf "drawgrid=w=54:h=96:t=1:[email protected]" grid.png

(54x96は、1080x1920フレーム上で20x20グリッドを与えます。他の解像度に比例してスケーリングしてください。)grid.png を見て、以下を読み取ってください:

  • cx - 顔/鼻の中心のx座標
  • head_top_y - 髪のてっぺんのy座標(目ではありません。ヘッドルームは頭頂部から測定されます)

ステップ3:クロップを計算する

python3 scripts/compute_crop.py --cx <measured_x> --head-top-y <measured_y> --zoom 1.7
python3 scripts/compute_crop.py --cx <measured_x> --head-top-y <measured_y> --baseline
  • --zoom Z は明示的なパンチインレベル(例:170%ショットの場合は 1.7)用です。
  • --baseline は「ワイド」ショット用です。ヘッドルーム目標を満たす最もルーズなクロップを見つけ、水平方向に再中心化します。これにより、ワイドショットはわずかにクロップされるだけ(文字通り100%/未加工ではない)ですが、ズームショットとまったく同じ光学中心とヘッドルームを共有するため、被写体がジャンプしているように見えるカットを実際に防ぐことができます。ワイドショットのクロップをスキップしないでください。 ズームショットだけを修正しながら生のままにしておくことは、テストで「何かがおかしい」ように見える最大の原因であり、個々のズームレベルを間違えるよりも大きな問題でした。

デフォルトのヘッドルームはクロップされたフレームの高さの9%であり、テストで正しく見えました。それ以下は窮屈に見えました。そして、これは美的な問題だけではありません。縦型プラットフォーム(Instagram/TikTok/Shorts)では、ユーザー名/キャプション/フェードUIがフレームの最上部に重なります。そのため、そのマージンはプラットフォームが覆う機能的なスペースであり、余裕ではありません。特定のプロジェクトで別の設定が必要な場合は --headroom で上書きしますが、それらのプラットフォームに向けたものでは約9%を大幅に下回らないようにしてください。

上記の完全な理由は scripts/compute_crop.py のdocstringに記載されています。短いので、特定のケースで不明な点があれば読んでください。

ステップ4:完全レンダリングの前に実際のレンダリング出力を検証する

静止フレームでは正しく見えるクロップでも、動きを確認すると被写体をクリップしたり中央からずらしたりする可能性があります。各セグメントについて(特に、基準フレームを選択したときに最も体の動き(前かがみ、頭の傾き)を示したもの):

  1. そのセグメントだけをクロップを適用してレンダリングします。
  2. 実際の再生時間全体にわたって3フレーム以上をサンプリングします(測定したものだけではありません)。例:ffmpeg -i clip.mp4 -vf fps=3 check_%02d.png
  3. それらを見てください。頭が切り取られていたり、端に詰められすぎていたりする場合は、先に進む前にそのセグメントのズームをルーズにするか、マージンを広げてください。

これが合格した場合にのみ、すべてのセグメントをレンダリングして連結してください。

ステップ5:確定された計画を単語に固定されたカットに変換し、組み立てる

  1. 同じカット(プレーンカット、または以前のズームレンダリング。パディングは一致している必要があります)のレンダリングから単語のタイムスタンプを取得し、そのレンダリングのクリップごとの再生時間を記録します(各 clip_NNN.mp4 を作業ディレクトリでffprobe):
    python3 scripts/word_times.py cut_render.mp4 words.json --language sr
    
  2. 選択ファイルを構築します。cuts.txt = 基本的なスキルのパディングされていない start<TAB>end をクリップごとに。pos.json = ステップ2からのクリップごとの [[cx, head_top_y], ...](--pre-filter が回転する場合は、回転されたフレームで測定):
    python3 scripts/build_zoom_selections.py --cuts cuts.txt --positions pos.json \
      --plan plan.json --sentences sentences.txt --words words.json \
      --durations 6.28,6.08,... [--pre-filter transpose=1] > selections.txt
    
    出力されるマッチレポートを読んでください。すべての計画された単語は exact(または適切な fuzzy~ スペルバリアント。Whisperは名前を異なるスペルやキリル文字で書き起こす可能性があります)と表示されるはずです。proportional (no match!) の行は、レンダリング前に手動で修正する必要があります。変更は単語の0.08秒前(--lead)に適用されます。複数のレベルを持つクリップは、1つの連続したクリップ(分割+タイミングオーバーレイ)としてレンダリングされるため、文の途中のズームがカットやオーディオの継ぎ目を追加することはありません。
  3. すべてのズーム変更の前後0.2秒のフレームをスポットチェックし(ステップ4のロジック:頭がクリップされていない、変更がはっきり見える)、その後レンダリングします:
scripts/cut_and_concat.sh "<source_video>" selections.txt final_output.mp4 "$PWD/build"

作業ディレクトリは絶対パスで渡してください。相対パスの場合、最後の連結ステップで concat_list.txt が見つからずに失敗します。

レンダリングされたクリップの再生時間は、以前のレンダリングのものと数百分の1秒異なる場合があるため、キャプション(字幕スキル)が以前のレンダリングに対してタイミングが合わせられていた場合は、バーンインする前に各キャプションをクリップの新しい開始位置にシフトしてください。

cut_and_concat.sh を適応する前に、上部のコメントブロックを読んでください。ffmpeg/bashの注意点(ループ内のstdinの盗用、フェードアウトがカットの最後の音節を食べてしまう、連結前にクリップを個別に再エンコードする必要がある理由など)が文書化されており、そうしないと静かに再導入されやすいです。

ベースライン(ワイド)セグメントと2つのパンチインを組み合わせた選択ファイルの例:

63.82	69.57	crop=908:1614:152:304,scale=1080:1920
80.37	85.90	crop=636:1130:288:348,scale=1080:1920
97.25	100.81	crop=908:1614:152:304,scale=1080:1920

ソースに回転/向きの修正も必要な場合、それは別途処理されます(このスキルの一部ではありません。常に必要とは限らないため)。合意された回転フィルターを各ラインのフィルターチェーンに自分で追加してください。該当する場合。

納品について

マスター出力がツールのファイルサイズ制限を超える場合は、マスターを圧縮するのではなく、二次的な圧縮コピーを作成してください:

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

スピーカーが表示するオブジェクトはすべてのズームで全体が表示されなければならない

実際のリールでは、スピーカーは隣のタブレットに写真を表示しています。顔に中心を置いたクロップは、その左端を切り取ってしまいます(「ne sme tablet da bude isečen ovako nigde」)。レンダリング前に、その動画が対象としているフレーム内のすべてのもの(タブレット、製品、掲げられたページ)をリストアップし、グリッド上のそのボックスを測定してください。すべてのズームクロップは、そのボックスを約30ピクセルの余裕を持って含める必要があります。クロップのx / yをその方向にシフトしてください(顔は最大約70%まで中央からずれても構いません)。顔が端に触れる場合は、その変更にはよりルーズなレベル(実際のリールでは1.7倍 -> 1.6倍)を使用し、どちらも切り取らないようにしてください。すべてのズーム範囲のフレームを確認して、両方が全体として表示されていることを確認してください。

Individual skills in this repo

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

関連スキル