ビデオオーバーレイ
ラフカットから、話者が各フレーズを発した正確なフレームに配置されたアニメーションオーバーレイのセットと、Resolveインポートファイルを生成します。
ブランド設定はこちら
以下の設定を scripts/scene.html の :root ブロック(先頭)で一度だけ編集してください。その他の設定はすべてこれらを参照します。
| 設定 | CSS変数 | デフォルト値 |
|---|---|---|
| アクセントカラー | --brand-accent (+ --brand-accent-rgb を r,g,b 形式で) | #4a90e2 |
| 明るいテキストカラー | --brand-light (+ --brand-light-rgb) | #f2f2f2 |
| 暗いインクカラー | --brand-ink | #0d131a |
| 表示フォント(見出し、数字) | --font-display | 'Anton','Bebas Neue',Impact,sans-serif |
| 本文フォント | --font-body | 'Inter',-apple-system,sans-serif |
表示フォントはローカルにインストールされている必要があります(例:~/Library/Fonts)。これは、クリップがオフラインのヘッドレスChromeでレンダリングされるためです。AntonとBebas NeueはGoogle Fontsで無料で入手できます。コード内の BLUE という名前は単に「アクセントカラー」を意味します。
ハウススタイル:インシーンタイプ。 カードは使用しません。テキストは話者の頭の横の部屋内に配置され、大きなタイトルは頭を挟み込み、質問は話されたとおりにタイプされます。常にブランドカラーと表示フォントを使用します。
最も重要な唯一のルール: グラフィックスは話者の頭の後ろか横に配置します。顔の前に何も配置せず、背景色も使用しません。図も話者の横の壁に配置します。すべてはマット処理され、手がグラフィックスの前を通過できるようになります。
1回の実行で生成されるもの: N個の透明な .mov クリップ、PLACEMENT.txt、2つの .fcpxml ファイル、および短い PREVIEW.mp4。
デフォルトでは音声はありません。 特に要求がない限り、音楽や効果音は追加しないでください。
スタイルのデフォルト(お好みで変更可)
- 何も言われる前に表示されない。 すべてのクリップをフレーズの単語レベルのタイムスタンプ + 約0.12秒に固定し、単語の直後に表示されるようにします。セクションレベルのタイミングは早すぎると見なされます。
- 短く。 2.5秒から6.5秒。ほとんどは約3秒です。ソーシャルメディアのように前後にカットされるべきで、その場に留まりません。
- 頭の後ろか横に、決して話者の上に重ならない。 カード、背景、暗転は使用しません。これにより、新しい動画でのコーナーチップ、すべての
full-*カード、defineは使用されなくなります。 - メカニズムを表示する。 文章の前に図を探しましょう。インシーン図タイプ(
dots、flow、bars、compare、math)を使用します。これらは話者の横の壁に配置されます。 - 2つの半分があるものは分割する。 古い方法と新しい方法は2つのクリップです。10%と20%は2つのクリップです。3つのオプションは3つのクリップで、それぞれ名前が呼ばれたときに配置されます。
- すべての行はその単語に配置される。 リスト、サイドライン、タイプされた単語には、均等な間隔ではなく、単語のタイムスタンプからの行ごとのオフセット(
at、t)が含まれます。scripts/plan.example.pyがその方法を示しています:フレーズを引用し、キューとなる単語に名前を付け、ルックアップを実行します。 - 良いバランス(11分あたり約48クリップ):約4分の1がインシーン図(主に
flow)、次にside、pair、type、list2、num。pairは決めゼリフのために取っておきます。 - 大きなタイトルは頭の周りに配置し、後ろには配置しない。
pairは各行を単語境界で2つの半分に分割し、頭を挟み込みます。ギャップは顔のボックスの幅の1.9倍(髪と耳を含む)で、両方の半分が画面内に収まるようにスケーリングされます。したがって、すべてのpair行は2単語以上必要です(「ONE HOUR」、ただし「HOUR」は不可):1単語は分割できず、隠れてしまいます。data内の"around": falseは中央揃えの外観に戻します。アウトライン行は150px、ストローク5px、明るいアクセントフィル(35%)で、読みやすさを確保します。
開始前:カットは確定していますか?
オーバーレイは正確な単語に固定されるため、後で再トリミングするとすべてのクリップがずれます。ユーザーから生のフッテージを渡された場合、またはカットが確定していない場合は、最初に paper-edit スキルを実行し、それが生成するタイムラインにオーバーレイを配置します。
パイプライン
1. カットを取得して調査する
Resolveからの書き出し(例:~/Movies/ へ)を依頼するか、最初にペーパーエディットのために生のフッテージを <YOUR_FOOTAGE_DRIVE> に置くよう依頼します。ビデオファイルで問題ありません。オーディオを個別に書き出す必要はありません。Resolveが開いている場合は、書き出しを依頼する代わりに、MCPを介してタイムラインを読み取ります。
ffprobe -v error -show_entries format=duration -show_entries stream=codec_type,r_frame_rate -of default=nw=1 "<file>"
フレームレート(レンダラーは23.976 = 24000/1001 を想定。他のレートの場合は render.mjs の FPSN/FPSD を変更)を確認し、タイムラインが 00:00:00:00 から始まるか 01:00:00:00 から始まるか(Resolveのデフォルトは01)を尋ねます。
2. 単語レベルのタイムスタンプで文字起こし
ffmpeg -y -i "<file>" -ac 1 -ar 16000 -c:a pcm_s16le cut.wav
whisper-cli -m ~/.cache/whisper-models/ggml-small.en.bin -f cut.wav -oj -ml 1 -sow -of cut
-ml 1 -sow はセグメントごとに1つのトークンを生成し、これにより正確なアンカー固定が可能になります。次に、cut.json 内のトークン実行を照合して、各フレーズの開始時間を見つけます(transcription[].offsets.from はミリ秒単位)。
3. 瞬間を選ぶ
まずトランスクリプト全体を読みます。おおよそ動画15秒あたり1つのクリップを目指します。それぞれについて:アンカーフレーズ、タイプ、そしてアニメーションが着地してバランスを取るのに最も短い時間。
内容は、書かれたメモではなく、実際に話された内容に基づいてください。メモに「30分」とあり、カメラでは「30分かそこら」だった場合、録画が優先され、その違いをユーザーにフラグしてください。
4. clips.json を書く
scripts/clips.example.json をモデルとしてコピーします。クリップごとに1つのオブジェクト:
{"id":"06-math-10","start":52.96,"dur":3.6,"type":"dots","cue":"\"if 10 out of 100 meals are home cooked\"",
"data":{"lit":10,"pct":"10%","count":"10 meals"}}
スキルフォルダの外に置き(スクラッチパッドパスで問題ありません)、CLIPS= で渡します。start は秒単位の生のフレーズ時間です。レンダラーが0.12秒のリードを自身で追加します。id の順序がファイルの順序を設定するため、番号を付けます。
5. クリーンなカットをレンダリングする(インシーンタイプに必要)
インシーンタイプは、頭の位置を読み取り、オーバーレイのないカットのコピーから話者を切り抜きます。Resolveから、すべてのオーバーレイクリップを無効にした状態で、確定したタイムラインをH.264 1080pでスクラッチパッドにレンダリングします。APIでは、V2の SetTrackEnable はResolve 19では機能しませんでした。代わりに、トラック内の各クリップに対して item.SetClipEnabled(False) が機能しました。生のフッテージはオンラインである必要があるため、フッテージドライブがマウントされている必要があります。マウントされていない場合は、ユーザーにプラグインを依頼し、完了したマスターをレンダリングする代わりにマウントを監視するように指示します(古いオーバーレイが焼き付いている可能性があります)。
6. レンダリング
cd <skill>/scripts && npm i # 初回のみ、puppeteer-coreをインストール
swiftc -O person.swift -o person # 初回のみ、マット+ヘッドファインダー
TITLE="<video name>" CLIPS=/path/to/clips.json FOOTAGE=/path/to/clean.mp4 OUT_DIR="$HOME/Movies/<video-name>-overlays" node render.mjs
# 1つまたは数個のクリップのみ:
ONLY=06-math-10,07-math-30 OUT_DIR=... node render.mjs
オーバーレイ1秒あたり、およそ10~15秒の実時間がかかります(pair はより遅く、すべてのフレームをマット処理します)。バックグラウンドで実行し、後で確認するのが適切な方法です。最後に PLACEMENT.txt と両方の .fcpxml ファイルを書き出します。
フルレンダリングの前に、常に実際のフッテージでプルーフを行ってください。 node proof.mjs <clean.mp4> <clips.json> out.jpg [email protected] [email protected] ... は、実際のショット上にクリップごとに1フレーム(prefix@クリップ開始からの秒数)をマット込みでレンダリングし、1つの画像にタイル状に並べます。それを確認してください。フレームからはみ出たテキスト、目を横切るタイトル、忙しない背景のテキスト:すべてコードでは見えず、画像で明らかになります。ダイアグラムの場合、グレーでの合成でも問題ありません。
プルーフフレーム:最初にPNGに抽出します。 qtrle .mov を -ss で lavfi カラーソースに直接オーバーレイすると、空白のフレームが生成されます。フレームを引き出し(ffmpeg -ss 2.8 -i clip.mov -frames:v 1 f.png)、その後PNGを合成します。
Resolveでの配置: 確定したタイムラインを YouTube Final として複製し、movファイルを Overlays ビンにインポートし、各ファイルを trackIndex: 2 と recordFrame(PLACEMENT.txtのINタイムコードから。((h*3600+m*60+s)*24+ff) なので、01:00:00:00 = 86400)で AppendToTimeline します。PLACEMENT.txt を1行ずつ解析します(line.split()[0] がid、[1] がタイムコード)。ファイル全体の正規表現は、キューイングテキスト(例:"60-second")をクリップIDとして読み取り、次の行のタイムコードを盗む可能性があるため、1つのクリップがサイレントに未配置になります。配置された数がクリップ数と等しいことを確認してください。
7. プレビュー、そして引き渡し
実際のフッテージ上に10~15秒のプレビューを構築し、ユーザーが配置前に判断できるようにします:
ffmpeg -y -ss <t> -t 11 -i "<cut>" -itsoffset <a> -i "<clip1>.mov" -itsoffset <b> -i "<clip2>.mov" \
-filter_complex "[0:v][1:v]overlay=0:0:eof_action=pass[v1];[v1][2:v]overlay=0:0:eof_action=pass[vo];[vo]scale=1280:-2[v]" \
-map "[v]" -map 0:a -c:v libx264 -crf 20 -preset veryfast -c:a aac PREVIEW.mp4
元のオーディオはフッテージと一緒に引き継がれ、プレビューに必要なのはこれだけです。
次に、インポート手順(File > Import > Timeline > Import AAF, EDL, XML、starts-01 ファイルを選択し、クリップを01:00:00:00のフッテージの上のトラックにコピー)を説明します。
Resolve内で直接作業する(Resolveが開いている場合に推奨)
resolve MCPサーバー(例えばサードパーティのbarckley75/resolve-claude-mcp)がインストールされている場合、Resolve StudioのスクリプトAPIと通信します。paper-edit スキルが ensure_resolve.sh を提供している場合は、最初にそれを実行して、Resolveが閉じている場合に起動します。Preferences > System > General > External scripting using: Local を使用する場合、書き出しとインポートの手順よりも優先します。
これにより変更される点:
- 書き出したカットは不要。 開いているタイムラインから実際のタイムコード、フレームレート、開始タイムコード、クリップレイアウトを読み取り、それにオーバーレイを固定します。これにより、「トリミングするたびにタイムコードがずれる」問題が修正されます。
- 手動インポートは不要。 レンダリングされた
.movクリップをビデオトラックに直接配置します。 - より良い瞬間の選択。 カットが既にどこにあるかを確認できるため、オーバーレイをカットと戦わせるのではなく、それに合わせて配置できます。
- オーバーレイが配置されたら、レンダリングを開始することもできます。
使用ルール:
- 書き込む前に読む。 最初にプロジェクト名、タイムライン名、開始タイムコード、フレームレートを確認し、ユーザーに伝えます。
- マスタータイムラインには決して書き込まない。 最初にそれを複製し、コピーで作業します。これにより、ミスが無駄になりません。
- 通常のツールでジョブを実行できない場合を除き、
execute_resolve_codeの使用を避ける。 これはユーザーのプロジェクトに対して任意のPythonコードを実行します。それが唯一の方法である場合は、最初にコードを表示します。 - ローカルセッションのみ。 ResolveはこのMac上で実行されている必要があります。そのため、これはクラウドやスケジュール実行では機能しません。その場合はFCPXMLにフォールバックします。
- FCPXMLパスは引き続き機能し、フォールバックとして使用されます。 両方のファイルを生成し続けます。数秒のコストです。
オーディオ文字起こし: 一部のResolve MCPサーバーは独自のmlx-whisper文字起こしを提供しますが、上記のwhisper.cpp手順はこのパイプラインで実績があります。
クリップタイプ
以下はすべて新しい動画では廃止されました(古い動画を再カットできるように保持)。次のセクションのインシーンタイプを使用してください。フルフレームタイプは、ソリッドなインク背景とソフトなアクセントグローを描画するため、話者を完全に隠します。チップは左下に配置されます。
| タイプ | 使用用途 | data |
|---|---|---|
full-dots | 廃止、dots を使用 | lit、pct、count |
full-flow | 廃止、flow を使用 | label、steps[]、good |
full-compare | 廃止、compare を使用 | title、left{label,value,sub}、right{...,hi}、vs |
full-bars | 廃止、bars を使用 | title、bars[{label,value,value_label,hi}] |
full-math | 廃止、math を使用 | parts[](演算子 x = + → はアクセントカラーでレンダリング)、sub |
full-pack | 廃止、bars または side を使用 | name、note、size 0-1、hi |
full-stat | 廃止、pair を使用 | label、value、sub、hi |
full-title | 廃止、pair または type を使用 | title(<br> 使用可)、size、sub |
full-list | 廃止、list2 を使用 | title、items[]、step 秒 |
chip | 廃止、side または num を使用 | num、title、sub、hi |
chip-stack | 廃止、list2 を使用 | items[] |
廃止されたタイプもレンダリングは可能で、古い動画の再カットに使用できます。
インシーンタイプ(デフォルト)
カードはなく、テキストは話者の頭の横の部屋内に配置され、大きなタイトルは頭の周りに配置されます。
| タイプ | 使用用途 | data |
|---|---|---|
pair | 決めゼリフのタイトル:頭の上にアウトライン行、額の高さにソリッド行 | top、bottom、outline(top/bottom) |
side | 頭の横に2~3行の短い行、各行はその単語に配置 | lines[]、accent(インデックス、大きくアクセントカラー)、at[] |
type | 話されたとおりに単語ごとにタイプされる引用または質問 | words[{w,t,hi,br}] |
define | 廃止:ショットを暗くします。side または pair を使用 | word、sub |
num | アウトライン数字が描画され、その横に単語("01 週を計画する") | num、lines[] |
list2 | 頭の左および/または右に構築されるアイテム | left[]、right[]、at[](順番に、左が先) |
インシーン図(同じ壁配置とマット):
| タイプ | 使用用途 | data |
|---|---|---|
dots | 100あたりの割合、話者の横にグリッドが埋まる | lit、pct、count |
flow | プロセス、ステップが上から下にチェーン、最後はアクセントカラー | label、steps[]、at[](オプション) |
bars | サイズによる2つまたは3つの数字 | title、bars[{label,value,value_label,hi}] |
compare | 2つのオプション、頭の両側に1つずつ | left{label,value,sub}、right{...,hi} |
math | 壁に沿って読まれる方程式、演算子が各行をリード | parts[]、sub |
at と t はクリップ開始からの秒数です。単語のタイムスタンプから構築し、すべての行がその単語に配置されるようにします。
これらには FOOTAGE= が必要です。オーバーレイクリップが無効になった状態でタイムラインをレンダリングした、オーバーレイのないカットです。レンダラーは各クリップの顔のボックスと背景の明るさグリッドを読み取るため、テキストは空の壁を見つけ、フレームからはみ出す場合は辺りを反転または縮小し、明るい背景にはインク、暗い背景には明るいテキストに切り替わります。すべてのインシーンクリップのすべてのフレームから話者をマット処理し(Apple Vision人物セグメンテーション、person.swift)、タイトルが頭の後ろに配置され、手がグラフィックスの前を通過できるようにします。swiftc -O person.swift -o person で一度コンパイルします。FOOTAGE がない場合、インシーンタイプはフォールバックできず、エラーで失敗します。フルフレームタイプは FOOTAGE を必要としません。
タイミングモデル
各クリップについて、レンダラーは start に render.mjs で定義されたリード(デフォルト0.12秒)を追加して、書き出された .mov の in ポイントを設定します。FCPXMLはこのオフセットを含むため、クリップはちょうど聞こえた後に配置されます。
start は、whisper.cppがフレーズを終了したと報告する秒単位の時間です。つまり、最終音節の終わりです。0.12秒のリードにより、アニメーションは音が完全に止まる前に開始されます。
内部動作: plan.py は cut.json をルックアップテーブルとして使用し、開始時間を解決します。
プランニング(ソース付き)
scripts/plan.example.py は、トランスクリプトからクリップを計画するための推奨される方法を示しています。文字起こし出力を解析し、結果の clips.json を生成します。まだコードを書いていない場合は、そこからコピーして編集することをお勧めします。
# plan.example.py の簡略化された構造
# 1. トランスクリプトを読み込む(cut.json)
# 2. 各フレーズについて、タイプ、データ、タイミングを定義
# 3. clips.json として書き出す
注:完全なSKILL.mdには、より詳細なプランニングの例、トラブルシューティング、FAQが含まれていますが、この翻訳では主要なワークフローとパラメータに焦点を当てています。