mcpField
V
video-storytelling

video-storytelling

github.com/nateherkaiクリエイティブ・メディア2.3k195 回インストール2 週間前に更新
リポジトリ

video-storytelling について

video-storytelling は、長尺の台本動画、特にトーク動画のビジュアルを設計するスキルです。持続する視覚空間、カメラ移動、空間的なオープンループによって、視聴者が内容を見失わないようにします。

主な機能

  1. 01一貫して続く視覚空間の維持
  2. 02カメラ移動による注意誘導
  3. 03空間的なオープンループと目に見える結末
  4. 04統一されたビジュアルトークンとモーション規則
  5. 05ラベル付き情報に絞ったグラフィック

活用シーン

  1. 01長尺のトーク動画の映像を設計する
  2. 02一貫した図解でプロセスを説明する
  3. 03チュートリアル、比較、リスト動画を視覚的に構成する

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/nateherkai/herk-edit-kit --skill video-storytelling

インストール後、Claude Code で/video-storytellingを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

reference/
design-system.md
reference/patterns/wall-of-slots/
geom.py
README.md
wall.py

詳細

動画のストーリーテリング

長尺動画のビジュアルレイヤーを構築し、9分目でも視聴者が状況を把握できるようにする方法です。台本のあるトーキングヘッド動画全般に適用できます。ニュース解説、特定のテーマを掘り下げる動画、比較、チュートリアル、リスト形式の動画などが対象です。

ここで解消するのは、シーン単位で考えることによる失敗です。数秒ごとに新しいグラフィックを出すと、視聴者は数秒ごとに理解し直すことになります。しかも、個々のグラフィックに問題があるわけではないため、具体的な欠陥ではなく、「わかりにくい」「情報が多すぎる」といった曖昧なフィードバックが返ってきます。

中立的な練習用ビートシートについては、リポジトリのルートにある docs/STORYTELLING-WORKBOOK.md をご覧ください。再利用可能なデザインシステムは、このスキルの reference/design-system.md にあります。オープンループの形状を実例で確認するには、reference/patterns/wall-of-slots/ をご覧ください。非公開のソース動画は必要ありません。

これは hyperframes-video-beats ではありません。このスキルでは、トーキングヘッドの映像にオーバーレイカードを重ねます。こちらは、動画が「主題」とするものそのものをデザインします。

1 · 4つの原則

ひとつの持続する世界

動画はひとつの空間の中で展開し、その空間がリセットされることはありません。視聴者がすでに見たものは、置かれた場所にそのまま残り、新しい素材はそれを置き換えるのではなく、そこに追加されます。一度描いた要素は、描き直したり、移動したり、ラベルを付け直したりしません。

このスキルのほかのすべては、この原則に基づいています。空間が変わり続けるならカメラの移動先はなく、画面にほかのものがなければスポットライトは意味をなさず、リセットされる世界ではオープンループを維持できません。

また、その世界には見続ける価値がなければなりません。ですから、何かを作る前に空間の文法を決め、動画全体を通して保ちます。リファレンスの制作例では、左から右へは結果に向かう方向、上は人に向かう方向で、画面上で唯一の暖色が使われています。下は記録システムの中へ向かう方向です。最初の2分でこれを理解した視聴者は、その後の動画を迷わず読み解けます。

意味を持たせるため、意図的に文法を一度だけ破ります。リファレンスの制作例で10分間に唯一の右から左への動きは、「再開率」という言葉に合わせ、解決済みのチケットがキューへ逆戻りする場面です。その直前の3分間、方向に意味があったからこそ、言葉で説明しなくても意図が伝わります。

カメラが編集になる

新しいグラフィックに切り替えるたびに、視聴者は位置関係を見失います。その場にとどまる空間を移動しても、失うものはありません。プッシュインは「すでに理解しているものの一部を、もっとよく見てください」と伝わり、プルバックで効果が発揮されます。それまでに構築したすべてが、そこに残っているからです。

3つの高度で作業し、隣り合う高度の間を移動します。

高度表示するもの用途
ワールド空間全体方向づけ、成果の提示、「現在地はこちら」の説明
リージョン話題にしているサブグラフ実行時間の大部分
ディテール1つの要素数値、状態変化、判断

ディテールからワールドへのジャンプは、意図的な成果提示の場面でない限り、見る人を戸惑わせます。意図的な成果提示の場面では、これが最も効果的な手法です。

学ぶたびに作り直しが必要になるルール:

  • フレーミングは倍率ではなく、ワールド上の正確なウィンドウとして指定します。 ウィンドウの境界はラベルとラベルの間に置きます。「倍率 0.6」のようにフレーミングを指定すると、フレームの端で単語が途中で切れてしまいます。
  • 同じ用途には、同じウィンドウを再利用します。 方向づけのショットに特定のウィンドウを使うなら、常にその正確なウィンドウを使います。ほぼ同じフレーミングでも、ずれとして受け取られます。
  • 移動距離に応じて長さを変えます。 全体に共通のプリセットは使いません。
  • カメラ移動と全画面ワイプは、重ねずに順番に行います。 同時に行うと、コンテンツをギロチンのように切断します。まずカメラ移動、その後にワイプです。
  • カメラがその下を移動する前に、カバーのレイヤーを不透明にします。 逆にすると、移動の一部が露出してジャンプカットに見えます。
  • フレームの端から60px以内にラベルを置きません。 要素がフレームからはみ出すと「ワールドが続いている」と受け取られますが、単語が文字の途中で切れるとバグに見えます。マスクではなく配置で解決します。フェザーでは約1800 px/sで進む刃を捉えられないためです。
  • 同じ対象に対する連続した2つの動きは、グリッチのように見えます。 フレーミングの変更が終わってからワイプが始まると、ズーム、完全な停止、そして2度目の動きとなり、クライアントからは「グリッチと一時停止」と評されます。共通の対称イーズ(power2.inOut)を使い、同じフレームに着地する1つの動きにまとめます。すると、パネルが映像を押しのけるように見えます。関連事項:映像が見えている状態で移動するときに、動きの前半に偏るイーズ(power3/4.out)を使うと、移動の約90%を前半で進み、残りはゆっくり止まります。ゆっくり止まる動きが落ち着くように見える、要素の出現時に限って使います。(作例のイントロでは、クライアントから指摘を受けて修正が必要になりました。)
注目はスポットライトです

いつでも、最大輝度で表示される要素は1つだけです。それ以外の、すでに描画された要素は、判読可能なまま控えめに表示されます。 これは任意ではありません。このルールがあるからこそ、世界に要素が12個ほど蓄積しても煩雑に感じることがなく、引きの画面も圧倒的にならずに読み取れます。

階層は、すべてを暗くするのではなく、アクティブな要素と控えめな要素の比率によって生まれます。リファレンスビルドの最初のプロトタイプは、最も明るいピクセルが101/255で、960x540では判読できませんでした。解決策は、さらに暗くすることではなく、描画色を明るくすることでした。

状態目標輝度(Y、16-235)リファレンスでの不透明度
アクティブな線とラベル200-2351.0
コンテキストとして描画され、引き続き関連性のあるもの120-1500.56
役目を終えたもの85-1050.33
すべてのラベル、すべての状態90未満にしない

実装は1つの関数で行い、ナレーションの各ビートで、話題にしているIDを渡して呼び出します(build_s01.py、lit()):

def lit(t, active, warm=(), dur=0.5, floor=FLOOR):
    active = list(active) + [g + "-l" for g in active if g + "-l" in order]
    for gid in order:
        if gid not in opac:      # never drawn: nothing to light
            continue
        tgt = 1.0 if gid in active else (WARM if gid in warm else floor)
        if gid not in active:
            tgt = min(tgt, MUTED.get(gid, 1.0))
        ...

ここで重要なのは、次の3点です。

  • ラベルは常に、対応する要素の状態を引き継ぎます(-l 接尾辞の取り込み)。要素が点灯しているのに、その下のラベルが読めないというのが、まさに修正対象の不具合です。
  • 役目を終えた小道具は、MUTED の上限を介して不透明度0にします。 0.3に下げるだけでは、説明のない紙吹雪のような表示がセクションの残りの間ずっと画面に残ります。ジェネレーターのルール:後から開始する lit() より前に mute() を出力してください。そうしないと、トゥイーンの完了時にミュートが上書きされます。
  • 不透明度イベントはタイムライン順に出力する必要があります。 そうしないとトラッカーが古い状態を参照し、誤った目標値を算出します。順序どおりでない出力時に例外を発生させる seq() ガードを維持してください。
  • 各要素の各ウィンドウで、不透明度を制御するのは1つだけにします。 同じ要素の不透明度を異なる値に変更する、同時にアクティブな2つのトゥイーンは、単独のセクションでは滑らかに表示されても、結合したマスターでは約10Hzでちらつきます。フレームごとに勝つトゥイーンが交互に入れ替わるためです。リファレンスビルドでは、すべてのドックでこの不具合が発生していました。lit() のようなスイープが、明示的な輝度アップまたはミュートと重なる場合、後から開始するトゥイーンだけが制御し、スイープではそれらのIDをスキップします(lit(..., skip=(...)))。このルールを徹底してください。すべてのトランジションウィンドウについて、1フレームだけ輝度が急に上下する現象がないか、結合後のレンダーをスキャンします(リファレンスビルドの qa/v4/flicker/qa-flicker.mjs)。
未完了のループは空間で示す

「後で戻ってきます」とは決して言わないでください。世界の中に、目に見える未完了のものを残し、そこに戻ります。視聴者が目にできる約束は、未解決のままだと実感できる約束です。

形が何であっても、仕組みは同じです。

  1. フックで仕掛けます。 空間の中で、約束を示すセリフに合わせて配置します。
  2. 境目ごとに再表示します。 セクションの切り替わりはすべて境目です。再表示にかかる時間は約2秒で、これが視聴維持の仕組みです。省くと、見せ場が何の成果にも結びつかなくなります。
  3. 目に見える形で進めます。 視聴者は、未完了の状態が小さくなっていく様子を見なければなりません。
  4. 最初に開いた映像で締めます。

台本の形に合わせて選びます。

台本の形世界未完了のもの
N 個の並列項目背骨に並んだ N 個のソケット空きスロット(reference/patterns/wall-of-slots/を参照)
1 つのプロセスを深掘り全体を配置したパイプライン壊れた、または欠けているリンク
レベルまたは階層上へと積み上がるスタックスタックの最上部がゴースト状態
比較1 つの共通軸上の 2 列片方の列が空、またはスコアカードが未記入
ニュースまたは発表タイムライン、または地形図右側、つまり未来が空白
チュートリアルまたは一緒に作る形式ゴースト表示された完成後のアーキテクチャまだ構築されていないすべてのノード

項目をスロットに収めるときは、約1.4秒のライブカメラ移動にします。埋まった状態の完成済み画像に切り替えてはいけません。視聴者は、たった今学んだものがタイルになる様子を見ます。動画に数字やカウンターがある場合は、同じ動きの中で走行距離計のように回転させます。両方の桁の開始、時間、イージングを同じにしないと、回転中に一続きの帯に見えてしまいます。

カウンターには数字だけでなく名前が必要です。 画面隅の数字は、視聴者に一連の中での位置を伝えますが、現在の項目が何かまでは伝えません。9分経った時点で「03」だけでは何も分かりません。数字と、現在の項目を示す1~2語のラベル(SUPPORT、VOICE AI)を組み合わせ、同じプレートの質感にし、数字と一体の固定具のように動かします。切り替えは同じ走行距離計の回転の中で行います。リファレンスのビルドに対するクライアントレビューでも、まさにこの点が求められました。映像は良かったものの、視聴者は画面に表示されているユースケースを見失っていました。

2 · 規律

言葉が付かないものは描かない

形として描くのは、ラベル付きの要素、ラベル付き要素同士をつなぐ線、移動する対象、または背景だけです。それ以外は何も描きません。

これは、理解度が10点中7点と評価されたミュートテストのレビューから生まれました。原因は、図はすっきりしていたのに、装飾はそうではなかったことです。意味のある要素と同じ線の太さ、同じ色調で、ラベルのない形が十数個描かれていたため、初見の視聴者には重要な情報と質感を区別できず、解読を意図していないものを理解するために注意を費やしていました。これは「ごちゃごちゃしすぎ」という問題を、言葉から形に移しただけです。

目安はセクションあたり画面上の語数を約20語です。文は使いません。ラベルは1~2語で、要素の下に置きます。リファレンスのセクション1は、84秒間で21語です。

系として、アンチパターンの映像を推奨案に重ねてはいけません。 まず世界をほぼ黒にするか、良い図が表示されていない場所に反例を置きます。

視聴者が追うものを1つにする

状態が目に見えて変化する、移動する1つの対象にストーリーを託します。データ行とスコアバーを持つチップ、ドキュメント、リクエストなどです。行が埋まり、バーが動き、枠線の色が変わります。視聴者が1つのオブジェクトを見るだけでセクション全体を追えるようにし、そのオブジェクトを瞬間移動させてはいけません。描かれたコネクターに沿って、一定の速度で移動させます。

1 つのグローバルトークンセット

詳細は reference/design-system.md を参照してください。

トークン値
線幅1.5 / 2.5 / 4 / 7 のみ
文字サイズ22 / 30 / 42 / 60 / 84
角丸10 / 22 / 40。常に superellipse(2)

色は 5 色で、それぞれの意味は固定されており、装飾はありません。温かみのある色は人が関与していることを意味し、それ以外のものに温かみのある色を使うことはありません。文字に純白は使いません。暗い背景では光がにじむためです。

このルールで防ぐ失敗は、最初のビルドのクラフトレビューで指摘されたものです。「すべての判断は個別には正しかったが、全体を見渡した判断はひとつもなかった。」 線幅は 18 種類、文字サイズは 9 種類あり、その大半の差は 0.2 でした。ひとつひとつは気づかないほどでも、全体としては致命的です。目に入るのは階層ではなくノイズです。トークンリンターでチェックし、ビルドが失敗するようにしてください。

モーション

入場には cubic-bezier(0.16,1,0.3,1) を使います。退場は入場より速くし、速度の変化は質量に応じて変えます。290px のノードと 40px のラベルを同じように減速させないでください。また、back.out は セクションごとに厳密に 1 回だけ、ふさわしいビートで使います。

クロスフェードは一切禁止です。 全画面レイヤーは、すでに不透明な背景の上でクリップによって入退場させます。どの瞬間でも、ピクセルが表示するのはグラフィックか映像のどちらかです。

3 · ビルドの契約

  • セクションごとにジェネレーターを 1 つだけ使います。 scripts/build_<id>.py は compositions/<id>.html のみを書き出します。編集するのはジェネレーターであり、生成済みの HTML ではありません。セクションのルートである index-*.html は手動で管理するため、フレーミングの変更はそちらで行います。
  • 共有モジュールは共有のままにします。 共有ジオメトリを定義するものはすべて、各セクションからインポートします。並列エージェントでセクションをビルドする場合は、共有モジュールに触れないようにします。ジオメトリの不整合は、すべての引き継ぎを気づかないうちに壊します。
  • 組み立ての前に SVG defs の名前空間を分離します。 各セクションで独自の gEdge、shadow、edgeFade を定義します。組み立て済みの DOM では、url(#id) はドキュメント順で最初に一致するものを解決します。そのため、先行するコンポジションがアンマウントされると、後続のグラフィックがすべて 何も描画しなくなります。リファレンスビルドでの影響は、すべてのゲートが緑なのに 35 秒間画面が空白になることでした。組み立て済みのレンダーを確認して初めて問題が見つかりました。
  • 単語単位のトランスクリプトを使い、すべてを 単語の開始時刻 に合わせます。アンカーとなる単語の 0.15~0.25 秒 前 に表示を開始します。セクションの長さをビート数で均等に分割してはいけません。
  • 間隔を監査します。 何も起きない時間が約 1.5 秒を超える箇所は、画面が停滞しています。ジェネレーターにその箇所を出力させます。
  • カットに余白を残します。 ポーズを厳しく上限設定すると、文の境界で間隔がほぼゼロになり、クライアントには「どもり」やジャンプカットのように聞こえます。話者自身の通常の間隔を基準に調整します。文の境界の間隔が中央値より約 10 倍短い箇所にフラグを立てます(リファレンスの話者では中央値が 470ms で、フラグが立った箇所は 20~40ms でした)。そのうえで、単語には触れずに 4~8 フレームの余白を挿入して修正します。余白にはトラック内の実際のルームトーンから切り出したスライスを使い、10ms のフェードを加えます。anullsrc は使いません。-50dBFS のノイズフロアに対するデジタル無音は、音が途切れたように聞こえます。映像はフレームを保持します。文と文の間では話者の動きがほぼ止まっているため、4~8 フレームの保持は気づかれません。挿入のみの変更であれば、トランスクリプトの時刻は決定論的にずれるため、再トランスクリプションは不要です。挿入箇所の端は、音声のエンベロープに合わせる必要があります。 単語の末尾にドナー音を 1ms で継ぎ合わせると、自然に 60~120ms かけて消える余韻が切り取られ、クライアントにはその急な切れ目がどもりのように聞こえます。ハードな継ぎ目は作らず、末尾の減衰に 40ms の等電力クロスフェードでつなぎます。
  • 確定済みの時刻を変更する場合は、スクリプトでシフトします。 ジェネレーターには数百のプロト時刻リテラルが埋め込まれており、セクションのオフセット、継ぎ目のテーブル、ルートのトゥイーンもすべて絶対時刻を保持しています。グラフィック作成後にタイムラインを変更する場合(2 秒の余白調整であっても)、レビュー済みの AST/パースレベルの変換を使って、各編集ポイント以降のすべてのリテラルをシフトし、その後に差分レビューを行います。手作業で編集してはいけません。すべてのシフトは正規の編集リスト自体から導き出します。リファレンスの調整では、事前計算したオフセットを再導出して照合した結果、監査で 2 フレームの誤りが見つかりました。
  • レンダーでは --workers 1 を指定します。並列ワーカーを使うと、映像レイヤーが黒く描画されます。

4 · ゲート

すべて実行し、その後も目視で確認します。

node scripts/qa-tokens.mjs          # 0 off-system strokes / type sizes / radii
node scripts/qa-no-crossfade.mjs    # 0 full-bleed layers animating opacity
node scripts/qa-legibility.mjs <render>
node scripts/qa-seamjump.mjs <render> plan/cover-seams.json --control <cut>
node scripts/qa-deadframe.mjs <render> --control <cut>
node scripts/qa-presence.mjs <render> --every 1      # speaker on-screen share
node scripts/integrate.mjs --check                   # 0 gaps, 0 stale mounts
npx hyperframes lint

ゲートは目視確認の代わりにはなりません。一度も失敗したことのないゲートは、ゲートとは言えません。

  • 動きの滑らかさは、拍のフレームではなく、連続したフレームで確認します。 拍ごとに1フレーム確認すれば構図は証明できますが、あらゆる動きの欠陥――動きへの唐突な移行、2つ目の動きの前に這うように進んで急停止する動き、フリーズ、1フレームだけの飛び出し――は拍のフレームの間に存在し、ほかのすべてのゲートをすり抜けます。納品前には、すべての切り替わり区間(カメラ移動、ワイプ、リフレーム、ドック、セクションの継ぎ目)にわたる連続したフレーム列を並べ、読み取ってください。

    ffmpeg -i render.mp4 -vf "select='between(n,A,B)',scale=480:270,tile=layout=6x7" \
      -frames:v 1 tile-<window>.png
    

    読み取るべき欠陥の特徴:大きく飛んだ直後に、ほぼ同じフレームが何枚も続く(動き始めに偏ったイージング/急停止);動きが止まり、別の動きが始まる(ひとつのジェスチャーであるべき動きが2つに分かれている);隣接する両方のフレームとは異なるフレームが1枚だけある(飛び出し)。実例のイントロには、トークン、クロスフェード、可読性、デッドギャップ、拍ごとのフレーム確認をすべて通過したズームフリーズの不具合が残っており、クライアントは一度見ただけで気づきました。この種の不具合は繰り返し発生します。このフレーム列の確認は任意ではなく、不可欠な工程として扱ってください。

  • 可読性は自動化できません。 絶対的なピクセル占有率のしきい値で測れるのはテキストの量であり、可読性ではありません。文字数の多いビルドほど、余白の多いものより高いスコアになります。すべての主要な拍について、960x540に縮小した画像を目視で確認してください。

  • 各ゲートは、そのゲートを確実に作動させることが分かっているコントロールを使って検証してください。 壊れたビルドで継ぎ目ゲートがゼロを報告するなら、そのゲートはないほうがましです。

  • セクションのプロトタイプではなく、組み立てた成果物を検証してください。 このビルドのゲートをすり抜けた不具合の大半は、組み立て後にしか存在しませんでした。

  • 音声は文字起こしではなく、音響レベルで監査してください。 編集前の音声から作成した文字起こしでは、編集によって生じたアーティファクトを構造上検出できず、テキストを一通り確認しても「問題なし」と判定される一方で、クライアントには音の途切れが聞こえます。実例のビルドで検出率が高かった順に、実際に効果があった方法を挙げます。トラック全体の無音区間スキャン(実際のノイズフロアが約-70dBFSであるはずの休止部分で、デジタルゼロのサンプルが連続していると無音の穴として検出されます。ルームトーンで埋めてください);すべての接続端での1ms分解能のエンベロープ解析(サンプルの導関数によるクリック検査では、耳にはすぐ分かるエンベロープの崖を見逃します);納品音声に対する新たなASRを実行し、期待されるテキストとの差分を確認すること。音素の繰り返しを自己相関で調べても何も見つかりませんでした。クライアントが「音の途切れ」と呼ぶものは、通常、単語の繰り返しではなく、途切れるような急な切断や無音の穴です。

登場時間:グラフィック主体の動画では、話者が画面に映る時間を約40%にすることを目指してください。全幅が必要な見せ場では、長く画面に映らなくても問題ありません。上限を設けるのではなく、監査してください。

5 · 作業の順序

  1. カットを確定し、単語単位の文字起こしを用意します。すべてのタイミングはこれに合わせます。
  2. 世界観、空間の文法、移動する対象を選びます。制作に入る前に書き留めてください。
  3. 台本の形に合わせてオープンループを選び、まず共有モジュールを構築します。そのジオメトリはフック、すべての境界、見せ場で参照されます。
  4. セクションを1つだけ最後まで作り、レンダリングします。ほかのセクションに着手する前に、そこでシステムを検証してください。
  5. そのセクションを目視で、音を消して確認し、装飾と可読性をチェックします。セクションではなく、システムを修正してください。
  6. 検証済みのセクションを基準に残りを構築します。その後、名前空間を設定し、組み立て、レンダリングし、ゲートで確認して、組み立てたレンダリングを目視で確認します。

品質を左右するのはステップ4と5です。実例のビルドにおけるビジュアルシステム全体は、最初のセクションを3回見直した結果です。

コメント