コードによるビデオ制作
ビデオを純粋関数 render(t) とステージ化されたアサーション可能なパイプラインとして扱います。このタスククラスにおけるほぼすべての故障モードは、ユニット/符号/チャンネル/メトリックのバグが静かにすり抜けることです。そのため、以下のワークフローは、それらを一つひとつ観測可能にすることを中心に構成されています。
0. 描画を始める前に契約を固定する
- 単一の時間ソース。 すべては
render(t)の中に存在し、フレーム↔時間マッピング(t = frame/fps)が公開API全体です。CSSアニメーション、タイマー、フレーム間状態はありません。結果:フレームは並列化可能、再開可能、比較可能であり、バグは時間範囲に局所化可能です。 - 決定論。 シード付きPRNG(
hash(i)/mulberry32)、決してMath.randomは使用せず、Date.nowも使用しません。フォントとテクスチャをデータURIとして埋め込み、実行中にネットワークやフォントフォールバックがピクセルを変更できないようにします。 - 仕様をアサーションとして記述し、散文として記述しない。 継続時間、fps、解像度、コーデック、ピクセルフォーマット、ラウドネス、トゥルーピーク、セーフエリア/マージン、禁止テクニック、ファイルパス、CLIフラグ、終了コード、JSON出力形式。ブリーフに数値があれば、それはチェックに入れなければなりません。
- つなぎ目ポリシーを事前に決定する — ワンショット/ループ/連結セグメント。ループの場合:すべての発振器は整数
kを持つsin(2π·k·t/D)であり、映像はそれ自身の最初の状態に収束する(「似ているように見える」ではありません)。イベントテーブルと画面上のカウンター/ラベルは最終遷移内でリセットされます。render(0)とrender(D)のレンダリングを検証します。 - フレームあたりの予算。 コンテンツを構築する前に
render(t)を測定します(1080pキャンバス描画は2–30 ms、3サブフレーム×5400フレームでそれが増加します)。予算を記録し、それを守ります。 - インターフェースを固定し、ハッシュ化する。 キャンバスサイズ、ページ/APIサーフェス、ワーカーCLI、出力パス。すべてのQCはそのリビジョンハッシュにバインドされます。古いハッシュに対してQCされたレンダリングは価値がありません。
- 意図的な逸脱を記録する。 現実がブリーフから逸脱するすべての箇所(「クリーンな」線上のグリッドの揺れ、指定された最小値よりも小さいマイクロフォント、指定された範囲外のスプリングレート)は、理由とともに短いリストに入れられ、レビュアーが発見するのではなく判断できるようにします。
1. 初日の3つのプローブ
| プローブ | アサート | 理由 |
|---|---|---|
| キャプチャ完全性 | 正確なピクセル、アルファ == 255、カラー範囲、最初/最後のフレームが存在 | 半透明の蓄積、範囲/色空間のミスが静かに映像全体を台無しにする |
| コスト | シーンごとの render(t) ms分布、メモリ | アーキテクチャの変更は今は安価だが、後で高価になる |
| モーション | 全期間にわたるフレーム間差分系列 + 最長静止実行 | すぐにデッドタイム範囲を示す |
実際の失敗から生まれたルール:
- 白い合成の罠。 アルファを白に合成するビューアを通してフレームをレビューすると、半透明性が隠れます。エンコードは黒に合成されます。レビューパスを納品パスとまったく同じように合成し、両者の間のピクセルを差分します。
- メトリックが何を見ることができるかを知る。 12倍ダウンスケール後、細い線のサブピクセルモーションは平均絶対差を約0.03/255変化させますが、フレーム全体の明るさの変化は約1.5/255変化させます。受け入れメトリックがダウンスケールされた平均である場合、最も安価な実際のレバレッジは、より大きな揺れではなく、グローバル値チャンネル(例えば、ビートレートでの2 Hz ±3 % 輝度パルス)です。
- アセットの前にメトリックを検証する。 チェックが失敗したときは、最初に生の信号(1 msでのエンベロープ、ピクセル行プロファイル、フレーム系列)をダンプします。「アセットが間違っている」の半分は検出器であることが判明します。
- 検出器を自己記述的にする。 系統的なバイアス(フィルターグループ遅延、因果分析ウィンドウラグ)をモデル化して補償します。最も大きなものよりもイベントに最も近い候補を優先します。しきい値を通過するまで調整しないでください。
- 正確な主張と近似的な主張。 「render(0) ≡ render(D)、PSNR ∞」は証明です。「最初と最後のエンコードされたフレームが2/255異なる(1フレームのモーション)」は観測です。レポートでこの2つを決して曖昧にしないでください。
バンドル済み:scripts/av_audit.mjs は、この半分(コンテナ/モーション/ループ/ラウドネス/シート)を実装します(ffmpegのみ、npm依存関係なし)。
2. レビューループは機能であり、オーバーヘッドではない
- グリッド化する:何百もの静止画→数十枚のコンタクトシートにし、1回のパスでレビューします。
- ガイドを焼き込む:セーフエリアボックス、センタークロスヘア、タイムコード、イベントID。レビュアーはレイアウトを頭の中に保持する必要はありません。
- 固定間隔ではなく、イベントごとにサンプリングする:各意図されたイベントで1フレーム、各移動の途中で1フレーム。
- つなぎ目を最も注意深く見る:シーン遷移、ループラップ、セグメント境界、最初/最後のフレーム。
- 疑わしい要素を空白のキャンバス上に4–6倍で分離してズームする:ほとんどのバグはフルフレームでは見えません。
- アーティファクトは使い捨て可能:1つのコマンドで再生成されます。レビューフレームを手動で修正しないでください。
- 問題リスト(乱雑/オフビート/重複/セーフエリア外/読み取り不能/フリーズ)を保持し、修正ラウンドごとに再レビューします。
3. モーションとループのクラフト
- アンビエント ≠ アクション。 すべてのシーンには、アクションとは異なる周波数の連続チャンネル(ドリフト、ウェーブ、アイドル回転、バリューパルス)が必要で、ゼロクロッシングがビートに合わせられているため、アクションはきれいに着地します。これが「何も静止しない」を満たします。
- 1つのイージングファミリー。 臨界減衰スプリング
1-(1+x)e^-x(オーバーシュートしない)を移動に使用します。ワイプ、プレス、終了には指数イーズアウト/インを使用します。着地をイベントと一致させます:開始 = イベント − 継続時間、進捗がイベントで正確に1になるように正規化します。 - モーションブラーを2箇所に:フレーム関数内(2–4シャッターサンプルをランニングアベレージ —
alpha = 1/(k+1)— で平均化し、完全に不透明で終了)、イベント周辺でより密に。さらにエンコーダー側のタイムブレンドも追加。インデックスコード化フィクスチャ(フレーム番号をエンコードするピクセルを持つフレームをフィードし、どのインデックスが平均化されたかを読み戻す)でフィルターウィンドウセマンティクスを証明し、ドキュメントからは決して証明しないでください。 - 構築による連続性。 1つのジオメトリ/状態モデルをシーン間で保持し、それを変換する(モーフ、マテリアルスワップ、ハードエッジワイプフラッド、スキャンライン)。カットやディゾルブは使用しません。
- 長さ保持モーフ:形状を角度/弧長でパラメータ化し、ブレンドが拡大するようにします。
- デフォルト禁止リスト(要求されない限り削除):パーティクル、グロー、レンズフレア、カメラシェイク、RGBスプリット、クロスフェード、一般的なグラデーション、弾むオーバーシュート。
4. レンダリングとエンコード
- フォーマット vs ターゲット:まずスループットを測定します。ロスレスPNGフレームは数MBあり、パイプに負荷をかけます。高品質JPEGは約1.5倍高速です。一度決定したら、それを維持します。
- スプールではなくパイプ:フレームをffmpegのstdinにフィードします(
-f image2pipe -framerate <fps*sub> -vcodec png -i -)。tmixでブレンドし、グループの最後のフレームを選択し、再タイミングし、-frames:v <n>でトリミングします。 - フレーム数ではなくコストで並列化する。 シーンごとのmsを測定し、ほぼ等しい時間のチャンクに分割します。均一なフレーム数では、最も遅いワーカーがウォールクロックを設定します。
- セグメントはconcat-safeでなければならない:各端で2つの境界フレームを繰り返し、同一のエンコーダーパラメーターとタイムベース、ffprobeでフレーム/継続時間を検証し、
-c copyconcatを使用します。 - パイプラインをステージ化する:1つのステージの失敗が1つのステージのコストで済むようにします。レンダリング→ブレンド→エンコード→オーディオ→マルチプレックス→測定。各ステージはファイルを書き込みます。
- ステージ間でロスレスビデオマスターを保持する(どこでも
-c copy)、ラウドネスは最後に行います。 - 未知のツールチェーンを依存する前にプローブする:フィルター/エンコーダーリスト、ブラウザ実行ファイル、バージョン。
$FFMPEG/$FFPROBEのオーバーライドを許可します。PATHの推測よりもピン留めされたローカルバイナリを優先します。ブラウザに同梱された「メディア」ffmpegは通常、x264/tmix/loudnormを欠いています。 - 子プロセスの出力はパイプではなくファイル記述子を通してキャプチャする。 制限されたサンドボックス内では、
spawn/execSyncをstdio: 'pipe'で使用すると、同じバイナリがシェルから正常に実行されるにもかかわらず、EPERMで失敗する可能性があります。代わりに開いたファイルハンドルを渡します:spawnSync(cmd, a, { stdio: ['ignore', fdOut, fdErr] })とし、ファイルを読み取ります。純粋な存在/バージョンプローブにはstdio: 'ignore'を使用します。stdoutとstderrを別々のファイルにルーティングし、バイナリフレームデータがログテキストとインターリーブされないようにします。 - ソースからのエンドツーエンドのリビルドで締めくくる、納品されたファイルが幸運なキャッシュではないことを確認します。
5. オーディオとマルチプレックス
- 画像と同じタイムベースと決定論(オフラインレンダリング、シード付き、リアルタイム依存関係なし)。
- 1つのキュー表(
{t, name})を、すべての可聴/可視イベントの唯一の真実のソースとして使用します。SFXは独自のバスに配置し、音楽より静かにします。 - 2パス loudnorm(
print_format=json→ measured_* →linear=trueを使用した2パス目)で正規化し、独立した3回目の測定で検証します。統合ラウドネスが±0.5 LU以内、トゥルーピークがターゲット以下であることをアサートします。 - ループ:テール(約40 ms)を正確にゼロにフェードアウトし、最初のサンプルがクリックしないようにします。パーカッシブヒットのグリッドへのアライメントを測定し、報告します。
- マルチプレックス
-c:v copy -c:a aac -b:a <spec>、次にffprobeでコンテナを「見る」のではなくアサーションとしてプローブします。 -b:a NkはffmpegのネイティブAACエンコーダーのターゲットであり、下限ではない(静かな素材で320kリクエストに対して約180–250 kbpsと測定されました。-aac_coder twoloopが役立ちます)。宣言されたbit_rateを読み戻し、要求された数値を繰り返す代わりに測定された平均を報告し、仕様がビットレートを要求するときは明示的にそう述べます。
バンドル済み:scripts/mux_normalize.mjs。
6. 描画レイヤーを安価で決定論的に保つ
- 時間不変のものすべてをキャッシュする:焼き込み背景、テキストマスク、パターン、Path2D。キャッシュキーには、ピクセルを変更できるすべてのパラメーターが含まれます。
- キャンバスプールはソースキャンバスの寸法からサイズ設定されなければならず、論理コンテンツサイズからではありません。そうしないと
drawImageが静かに伸縮またはクロップします(これにより、クリップ/変形されたタイプが生成されました)。 - フレームループ内で
shadowBlur/filterを避け、ジオメトリを優先します。 - メモリ:1080pキャンバスは約8 MB。プールを制限します。
- サブフレーム数がフレーム予算を線形に増加させることを覚えておいてください。
7. コラボレーション
- 作成者と検証者を分割する。 検証者はQCスクリプトを所有し、成果物を不合格にする権限を持ち、作成者が行わなかった次元(アルファ、ページとの忠実度、ラウドネス、ループクロージャ)を独立してチェックします。これにより、作成者が見逃すクラスのバグをキャッチします。
- 互いに素な書き込みスコープ + 固定されたインターフェースにより、並行作業が衝突しないようにします。リビジョンをハッシュ化し、変更後にQCを再実行します。
- 大きな音で失敗する:非ゼロ終了、クレームごとに1行の
PASS/FAIL、機械可読なものにはJSON、静かなフォールバックはありません。
8. アンチパターン
- 白い合成されたレビュー画像を通して色を判断する。
- 特性を把握していないメトリックを満たすためにアセットを編集する。
- モデルを修正する代わりに検出器のしきい値を調整する。
- 20秒のページサイドプローブで答えられる質問のためにフルレンダリングを再実行する。
- コストが大きく異なるシーンにわたって均一なフレーム数チャンクを使用する。
- 納品パスと異なるレビューパス(スケール、カラー範囲、コンポジット)。
- アサーションではなく「問題なく見えた」。失敗してもexit 0。
- 非ASCIIを壊すツールでのテキスト/アセット編集(編集後にエンコーディングを検証)。
- ソースを修正する代わりにレビューフレームを手動で修正する。
- クロスフェードの背後に状態の不連続性を隠す。
9. チェックリスト
フライト前 — 仕様→アサーション;render(t) シグネチャ固定;つなぎ目ポリシー + ループ代数;アセット埋め込み;3つのプローブが存在;フレームごとの予算設定;FFMPEG/FFPROBEが解決され、機能がプローブ済み。
飛行中 — 各シーン後:コストプローブ、モーション系列、イベントサンプリングされたレビューグリッド;問題リストクローズ;構文/型チェックと編集済みソースの非ASCIIチェック;リビジョンをハッシュ化。
納品前 — ソースからリビルド;フルレンダリング(コスト加重、境界フレーム、concat検証済み);ラウドネス2パス+3回目の検証;独立した測定スクリプト→PASS/FAIL;正確な主張と近似的な主張を明示的に報告;一時スクリプト/スクリーンショットを削除し、それぞれの1コマンド再生成を文書化。
完了の定義 — コンテナメタデータが正確;つなぎ目の動作が証明済み;フリーズ実行制限を満たしている;ラウドネス/トゥルーピークを満たしている;画面上のテキストがブリーフと逐語的に一致;1コマンドでソースからリビルド;すべてのQCが凍結されたハッシュでグリーン。
10. バンドルされたスクリプト
両方とも単一ファイルで、依存関係ゼロ(ffmpeg/ffprobeのみ)。ツールを $FFMPEG/$FFPROBE → PATH → 一般的な場所から解決し、クレームごとに1行の PASS/FAIL を出力し、--json でJSONを出力し、失敗時に非ゼロで終了します。このスキル自身のパスからディレクトリを解決します。
# 完成したファイルのQC:コンテナ vs 期待値、フリーズ実行制限、ループつなぎ目、
# ラウドネス/トゥルーピーク、人間によるレビューのためのオプションのコンタクトシート
node av_audit.mjs film.mp4 --expect-duration 30 --expect-fps 60 --expect-size 1920x1080 \
--static-limit 0.5 --sheets ./sheets # exit 0 = すべてのチェック合格
# WAVをターゲットに正規化し、エンコードされたビデオに多重化(ビデオストリームはコピー)、
# 次に3回目の独立した測定パスで検証
node mux_normalize.mjs silent.mp4 score.wav film.mp4 --lufs -14 --tp -1 --abr 320k
av_audit.mjs は意図的に低解像度(160×90)のMAD系列を使用します。これはフレーム全体の値の変化とブロックの動きを捉えますが、細い線のサブピクセルモーションは捉えません。§1のメトリック感度ルールと組み合わせて使用してください。ループの場合、first_vs_last_psnr_db を1フレームのモーションに関する観測として扱い、レンダラー自体の2つのレンダリングを比較して正確性を別途証明します。
1行要約 — 成功は、どれも描画スキルではない3つのことに依存します:時間関数が純粋で検証可能であること、メトリクスが最適化される前に理解されていること、そしてレビューと納品パスが同じパスであること。