コードによるシームレスモーションループ、Scenario MCPから被写体を合成
概要
ここでのループは、時間の純粋関数です。 draw(ctx, w, h, t, P) は、t秒の時点のフレームを描画し、すべてのモーションはループ長(12秒)を分割する周期で繰り返されるため、12秒のフレームは0秒のフレームと同一になり、クリップは継ぎ目なくループします。生成による処理はありません。歪みやちらつきはなく、1つの色を変更するだけでブランドに合わせることができ、同じコードで任意のサイズのレンダリングが可能です。scenario.comのMotion Loopsエクスプロレーションでは、この方法で作成された80のループ(背景40、シェイプアニメーション40)が、それぞれのコードとともに公開されています。 scripts/loop-template.html には、これらのループが記述されたのと同じヘルパー(M.*)、パレット(P)、および tile(...) 登録が含まれているため、それらのタイルコードをそのまま変更せずに実行できます。
Scenario MCPは、コードでは描画できないもの、つまりユーザーの被写体を提供します。マスコット、製品、キャラクターが、透明な背景の切り抜きとしてScenarioから出力され、ループページはそれをループのタイミングに合わせてループ上に描画するため、同じモーションブラーが適用され、合成後もループが継続します。被写体が実際に動く場合は、その切り抜きからAIビデオショットが生成され、Scenarioで背景が除去され、レンダリングされたループの上にffmpegで重ねられます。ループ自体はモデルを必要とせず、レンダリングにコストはかかりません。
Node 18以降、Playwright(npm install playwright、その後 npx playwright install chromium、またはGoogle Chromeがインストールされている場合は --channel chrome)、ffmpeg、ffprobeが必要です。Scenario MCPは被写体にのみ必要です。接続、モデルの選択、生成ループについては、scenarioスキル(インストールされている場合)、または references/scenario-mcp.md を参照してください。
クイックリファレンス
cp scripts/loop-template.html loop.html # ループごとに1ファイル:設定、draw()、オーバーレイ
# loop.htmlをブラウザで開く:ライブプレビュー; ?t=3で固定、?tone=accentまたは?tone=lightでトーン切り替え
node scripts/render-loop.mjs loop.html --check # 決定性とループ継ぎ目チェック、ビデオ出力なし
node scripts/render-loop.mjs loop.html --stills look --at 0,3,6,9 # 確認用静止画
node scripts/render-loop.mjs loop.html --out loop.mp4 # 12秒、1920x1080、24fps
node scripts/render-loop.mjs loop.html --params "overlay=subject.png" --out loop.mp4 # 切り抜きと合成
node scripts/render-loop.mjs loop.html --width 1080 --height 1920 --out loop-9x16.mp4 # 縦長
node scripts/render-loop.mjs --qa loop.mp4 composite.mp4 # 任意のMP4のシームとフォーマットチェック
Playwrightがインストールされているフォルダから実行します。既存のファイルは、--forceなしでは上書きされません。ページチェックは、同じ瞬間を2回レンダリングし(一致する必要があります)、12秒のフレームと0秒のフレームを比較し、ループポイントをまたぐステップとその周辺および他のサイクル境界のモーションを比較します。レンダリングは、PASSが出力されない限りエンコードを拒否します。--ignore-seamは、ループポイントに配置するように設計されたカット専用です。
| ルール | 理由 |
|---|---|
すべての周期が12秒を分割する(1、1.5、2、3、4、6、12);M.phase(t, period, offset)を使用する | 12秒のフレームが0秒のフレームと等しくなる |
| スクロールは周期あたりパターン繰り返しの整数倍だけ移動する | 端数が繰り返されると継ぎ目でジャンプする |
ランダム性はM.rand(i, salt)からのみ;フレーム間でMath.random、Date、状態を使用しない | モーションブラーとチェックがフレームを順不同でレンダリングするため |
サイズはu = h / 100、カウントはwから | 16:9、9:16、1:1、サムネイルサイズで正しく表示される |
色はPからのみ(bg、fg、fg2、mid、accent、paper、ink)、ブランドカラーあたり3トーン | BRANDを変更することでブランド変更;トーン変更でコントラスト調整 |
イージングはM.easeから、アンチシペーション、オーバーシュート、ホールド付き;スクロールのみ等速 | 機械的な動きではなく、設計された動き |
出荷時の80のループのレンダリング設定とスクリプトのデフォルト:24fps、180度シャッターで8モーションブラーサブフレーム、2倍スーパーサンプリング、H.264 High、CRF 16、4:2:0、BT.709 リミテッドレンジ、faststart。ループを閉じるパターン、深度トリック、レビューの習慣: references/loop-craft.md。
| Scenario MCP ステップ | 呼び出し |
|---|---|
| 接続、チームとプロジェクトの選択 | teams_list(複数ある場合はユーザーに問い合わせ) |
| ユーザーのアートを取り込む | upload_asset、各パーツのcurl PUT、upload_asset_complete |
| モデルの選択 | recommend(txt2img、img2img、img2video、video2video)、またはsearch |
| 入力の読み取り | model_schema_get |
| 価格提示、その後ユーザー確認後に実行 | dry_run: trueでmodel_run、その後model_runとjobs_wait |
| 結果の取得 | asset_download、その後curl -L -o |
実例:ブランドマスコットがコード化ループに浮かぶ
目標:ブランドカラーで12秒のシームレスな1920x1080ループにブランドのマスコットが浮かび、オプションでマスコットが手を振るバージョンも作成する。
-
接続。
teams_listを呼び出します。複数のチームまたはプロジェクトが返ってきた場合は、どれを使用するかユーザーに尋ね、そのteam_idとproject_idを後続のすべての呼び出しに渡します。サインインアカウントを使用する場合、search、model_schema_get、model_runはこれらなしでは実行を拒否します。 -
まずループを構築(ローカル、無料)。
cp scripts/loop-template.html mascot-loop.htmlを実行し、BRAND.accentをブランドカラーに設定して、サンプルタイル(遠近法フロア、カメラに向かって流れるグリッド)を保持するか、独自のdraw()を記述します。ファイルを開いてプレビューし、次のコマンドを実行します。node scripts/render-loop.mjs mascot-loop.html --check node scripts/render-loop.mjs mascot-loop.html --stills look --at 0,3,6,9静止画を確認します。
--checkはPASSで終了する必要があります。 -
マスコットを取り込む。 ユーザーがファイルとして持っている場合は、アップロードします。
{ "file_name": "mascot.png", "content_type": "image/png", "kind": "image", "file_size": 482113 }(
file_sizeはwc -c mascot.pngで取得)curl で返されたURLにパーツをPUTし、upload_asset_completeを{"upload_id": "<アップロードID>"}で呼び出し、アセットIDを保持します。 -
モデルを選択。
recommendを次のパラメータで実行します。{ "capability": "img2img", "prompt": "参考画像のマスコット、全身、斜め3/4の角度、軽やかに浮かぶポーズ、透明な背景にクリーンなエッジで切り抜き、影なし、テキストなし", "limit": 5 }ファイルがない場合は、
"capability": "txt2img"を使用してマスコットを説明します。next_step.typeがask_userの場合は、オプションを表示してユーザーの入力を待ちます。スキーマに透明な背景オプションがあるモデルを優先します。それ以外の場合は、その後の背景除去ステップを計画します。 -
スキーマを読み取り、価格を提示。
model_schema_getを{"model_id": "<recommendからのID>"}で実行します。スキーマからパラメータを構築します。参考画像フィールドはアップロードしたアセットIDを取得し(スキーマで指定されている場合は配列として)、透明な背景オプションを設定します。その後、次のコマンドを実行します。{ "model_id": "<recommendからのID>", "dry_run": true, "parameters": { "prompt": "<上記のプロンプト>", "<参考画像フィールド>": ["<マスコットアセットID>"], "<背景フィールド>": "transparent" } }ユーザーに価格を伝え、実行の許可を待ちます。
-
実行してダウンロード。 同じパラメータで
model_runを"wait": falseで実行し、jobs_waitを{"job_ids": ["<ジョブID>"]}で実行します。その後、asset_downloadを{"asset_id": "<アセットID>", "format": "png"}で実行し、curl -L -o subject.png "<url>"をmascot-loop.htmlのフォルダに実行します。ループの背景色に対して100%でPNGを開きます。暗いトーンで輪郭にハローが表示された場合は、非表示にするのではなく再生成します。 -
キャンバスに合成してレンダリング。
node scripts/render-loop.mjs mascot-loop.html --params "overlay=subject.png" --stills look --at 0,0.75,1.5,2.25 --force node scripts/render-loop.mjs mascot-loop.html --params "overlay=subject.png" --out mascot-loop.mp4 --psnr 4drawOverlay()は、マスコットを3秒の上下動と6秒の揺れで浮かべ、コンタクトシャドウを付けます。すべての周期は12を分割するため、合成後もループします。サイズと配置はそこで調整します。スクリプトはページをチェックし、288フレームをレンダリング、エンコードし、ファイルのループポイントをチェックします。--psnrは、エンコードされた1フレームをロスレスレンダリングと比較します。 -
オプション:マスコットが動くショット。
recommendを"capability": "img2video"で実行し、切り抜きのアセットIDを開始画像として、プロンプトは「マスコットが手を振り頭を向ける、無地のフラットな背景、固定カメラ、カットなし」とします。スキーマ取得、価格提示、許可取得、実行、jobs_wait、ダウンロード。その後、recommendを"capability": "video2video"で実行し、「このクリップから背景を除去し、被写体をアルファチャンネル付きで維持」とプロンプトします。出力としてアルファ付きのWebM VP9を選択します。スキーマ取得、価格提示、許可取得、実行、ダウンロード。--params overlay=...なしでループをもう一度レンダリングし(--out loop-plain.mp4)、ffmpegでその上にショットを重ねます(コマンドは references/scenario-mcp.md にあります)。合成はショットの長さと同じです。ショットがループしない限り、シームレスなループにはなりません。 -
成果物の引き渡し HTML(ループは編集可能なまま)、MP4、静止画、および合成物を引き渡します。
よくある間違い
- 12秒での継ぎ目ジャンプ。 12を分割しない周期(5秒サイクル)、または2.5回の繰り返しを移動するスクロール。
--checkはSEAM JUMPを報告します(12秒のフレームが0秒のフレームと異なります)。周期を修正してください。終端をクロスフェードしないでください。 - タイル内の状態、
Math.random、またはDate。 同じtで異なるフレームが生成され、モーションブラーのサブフレームが一致せず、ループを閉じることができません。--checkはNOT DETERMINISTICを報告します。すべてをtとM.rand(i, salt)から計算してください。 - 16進数のみを読み取るカラーヘルパー。
P.midとM.mixColorはrgb()文字列を返し、16進数のみのパーサーはそれらを黙って黒に変換します。テンプレートのM.rgbaとM.mixColorは両方を読み取ります。 - 速い動きでのストロボ。 8つのサブフレームは、フレーム全体のプッシュには不十分です。ステップ状のコピーが表示されます。ビルド内の全ページスワイプには40が必要でした。非常に高速なループの場合は
--samplesを増やしてください。 - ワイヤーフレームが乱雑に映る。 3D波動メッシュがそうでした。非表示線の尾根線(各行が自分の下の背景を埋め、遠くから近くへ描画)として再構築すると、地形のように見えました。
- 深度による2回の除算。 投影が焦点距離を深度で再度スケーリングすると、シーンが中央に縮小します。
M.projで一度だけ投影してください。 - 3秒のサイクルを1.5秒ごとの静止画でレビュー。 それでは2つのポーズしか表示されません。1サイクル内とループポイントの両側をサンプリングしてください(
--at 11.9,11.95,0,0.05)。 - すべてのフラグをバグとして扱う。 設計されたスナップ(ポップ、バースト)またはループポイントでのホールドは、シームまたはグリッチ検出をトリップさせる可能性があります。まずフレームを見てください。スクリプトはループポイントを周囲のモーションおよび他のサイクル境界と比較するため、これらのほとんどを回避できます。ビルドの80のループすべてが通過しました。
- ラベルまたはカウンターのラップが正しくない。 サイクルによってインデックスされるものはすべて
M.modでラップする必要があります。ビルドでは、ページカウンターがループポイントで「06 / 05」と表示されていましたが、修正されるまで続きました。 - 1つのトーンでの低コントラスト。 そのループでは、色を追加する代わりに、トーン(ダーク、アクセント、ライト)を変更してください。
- ディスクからのオーバーレイPNG。 ローカル画像を描画してピクセルを読み戻す
file://から開かれたページは、画像がキャンバスを汚染するため失敗します。render-loop.mjsはフォルダをローカルアドレスで提供するため、ブラウザプレビューは表示のみを行い、影響を受けません。 - フォントが読み込まれる前のテキスト。 キャンバステキストはWebフォントを待機しません。最初のフレームの前に
document.fonts.load(...)を待機するか、ループでテキストを描画しないでください。 - ビットレートの追求。 フラットな色では、x264は非常に少ないビットを使用し、ファイルは小さく見えます。ビルドでは、PSNRはエンコーダがほとんど損失を追加しないことを示しました。唯一の実際の損失は、細く彩度の高い線での4:2:0クロマです。
--psnrで確認し、ビットレートを上げる代わりに細い色付きの線を太くしてください。 - VP9ショットのアルファを失う。 ffmpegの組み込みVP9デコーダはアルファを無視します。
-i shot.webmの前に-c:v libvpx-vp9を配置してください。 - ビデオモデルでループを作る。 生成されたクリップの最後のフレームは、最初のフレームと正確に一致することはほとんどありません。