ネオンのクラブで、手前の金髪の女性とソファに座る男性を描いた場面。
制作ノート

Production Deskをつくる:​ローカル映画制作ワークフローの構成

2026年10月5日

物語、​絵コンテ、​生成、​素材、​レビュー、​編集タイミングを、​明示的な​境界の​中で​扱う​ローカル制作システムの​設計記録。

エンジニアリング制作アニメーション

Production Deskは、映画制作のためのローカルな作業環境として設計されています。今回の記録では、画面の印象ではなく、Electronシェル、Next.jsサーバー、プロジェクトストア、ジョブ、メディア、タイムラインがどの境界でつながるかを整理します。

画面と制作フローの順番は、Marunouchi Animation Labs側のプロダクト側の制作記録にまとめています。ここでは、その動きを支える実装上の判断に集中します。

構成の全文はProduction Desk architecture.mdで確認できます。

A / 画面を所有する境界

フロントエンドは、状態の持ち主を決める仕事でもある

Production Deskの画面は、単なるツールバーの集合ではありません。Electronのmainプロセスがネイティブウィンドウ、プロジェクトカタログ、設定、フォルダ選択、子プロセスのライフサイクルを所有し、Next.jsサーバーが制作データ、ジョブ、素材、レビューのローカルAPIを所有します。React rendererは、その二つの境界を画面の操作へ翻訳します。

この分割は画面の作り方を決めます。Homeはカタログと作成・開始操作を持ち、Project shellはツリー、現在のコンテキスト、タブ、保存を持ちます。Boardsではキャンバスが中心になり、Timelineではフレーム編集が中心になり、Generationsではプロンプト、参照、事前確認、候補の判断が中心になります。共通のshellでありながら、どの画面にも同じ密度を押し込まないための構成です。

画面遷移には保留中の入力もあります。作成モード、選択ノード、展開状態、サイドバー、タブ、未保存のdraftを保持し、プロジェクト切り替えや終了の前にはrendererが応答します。これは見た目の確認ではなく、制作中の文脈を失わないためのインタラクション契約です。

最初に、境界を固定する

Production Deskのプロセス境界図
Electron、ループバックAPI、プロジェクトストア、ジョブ、メディアの関係。

Electronのメインプロセスがネイティブウィンドウ、カタログ、設定、フォルダ選択、プロジェクト切り替え、管理下のサーバーのライフサイクルを担います。preloadはwindow.productionDeskという狭いブリッジだけを公開します。レンダラーには絶対パス、秘密情報、シェルコマンドを渡しません。

起動時には空いているループバックポートを選び、standalone Next.jsサーバーの準備が整ってから、そのoriginだけを信頼します。作成、切り替え、終了の前にはレンダラーの応答を待ち、10秒で拒否します。ネイティブ操作とWeb側の状態を同じ受け渡しでそろえるためです。

この仕組みは失敗時の状態も明確にします。サーバーが起動しなければ新しいrootへ進まず、カタログと設定は元に戻されます。10秒以内にレンダラーから応答がなければネイティブ操作を拒否します。画面が表示されていることと、ネイティブ側の状態を安全に更新できることを分けて扱います。

制作状態をひとつのグラフで持つ

Production Deskのプロジェクト状態モデル
ノード、素材、バージョン、ボード、タイムライン、ワールドを持つProjectState。

ProjectStateはスキーマバージョン、安定したプロジェクトID、リビジョン、ノード、素材、ボード、タイムライン、ワールドを含むマニフェストです。ノードはAct、Scene、Sequence、Shotの親子関係を持ち、ボードとワールドはノードのスコープに属します。

このグラフはフロントエンドの選択範囲も決めます。Libraryは永続的なギャラリーではなく、現在のノードと素材種別に絞った選択画面です。素材カタログはバージョンを選び、Boardはショット識別子とページ順を持ち、Timelineのclipは素材、バージョン、子タイムラインを参照します。画面で選んだ対象と保存される関係が一致します。

更新には期待する改訂番号を添え、ずれていれば409相当の競合として最新状態を返します。古い状態での上書きを許さず、別の画面の判断を静かに消さないためです。素材のバイト列はSHA-256で識別し、新しい内容は新しいバージョンとして残します。

D / 競合を見せる

保存は、最新状態を隠さない

プロジェクトストアへの更新には期待する改訂番号を送り、ずれていれば409相当の応答と最新のProjectStateを返します。二つの画面が改訂18を読み、一方が保存したあと、もう一方が古い改訂18を送ると、画面上で内容を照合して反映方法を選べます。Production Deskは最新状態との調整を隠しません。

変更できるJSONは一時ファイルへ書き出し、fsyncとrenameで確定します。素材のバイト列はSHA-256で識別します。Board、clip、選択中のバージョン、子タイムライン、Worldの出力から参照される素材は、単純な削除で消しません。画面には参照中、競合、復旧用の処理結果を説明できる保存状態を示します。

E / 読み取りから適用へ

既存の制作フォルダは、まず観察する

ソースの探索ではシンボリックリンクをたどらず、認証情報、キャッシュ、対象外のパスを除きます。採用候補のハッシュ、バイト数、警告、除外したパスをプレビューにまとめます。5RPSとTEGAKIは参照元の役割が異なるため、同じフォルダを一つの形に決め打ちしません。custom profileも、明示された相対パスの範囲内で扱います。

migrationでは、ユーザーがpreviewを確認してApplyを選んだ時だけ、採用したファイルをmanaged blob boundaryへコピーします。receiptには件数、hash、byte数、warnings、rejected paths、保持したoriginalsを記録します。folderを開いただけでは、この処理は起こりません。

source documentを直接書き換えるApplyは別の境界です。実行直前にexpected revisionとsource hashを比較し、競合時はrecovery contentを残して中断します。frontendでは、読み取り専用のmigration preview、managed projectへのmigration Apply、source documentへのApplyを別の確認として表示します。

F / 生成の準備と候補

候補を作り、判断を残す

ボード、画像生成、動画生成は、同じ準備、参照確認、承認の文脈を通ります。既知のキャラクターを画面に出す場合は、canonical bitmapとキャラクターの一致を示すidentity receiptが必要です。生成結果は評価と承認を終えるまで候補として扱います。

生成画面では、依頼した内容と実際に観測された結果を分けます。選んだモデル、スタイル、キャラクター参照、FlareやSunburstの希望はリクエストのスナップショットに残ります。実際のモデルはrunner receipt、既知のキャラクターの一致はidentity receiptで確認します。準備済みパッケージが古くなれば生成前の確認で止まり、有料送信は別の明示操作です。

候補は評価と承認を終えるまで完成品ではありません。ジョブの状態、キャンセル印、復旧可能な出力、一件ずつ処理するキューを表示し、失敗時には再試行できる処理結果を残します。Boardsの遅延自動保存とundo履歴は、確定したバージョンの変更履歴とは別の状態として画面に示します。

G / 時間を編集する

Timelineは、フレームと構成を同時に扱う

Timelineは整数フレーム、レイヤー内の重複なし、子タイムライン参照、循環のない構成を検証します。ネストしたレーンは親の構成に組み立てられ、配置、間隔保持、境界スナップ、衝突解消、undo/resetを一つの編集画面にまとめます。再生はその場で確認する操作で、renderは検証後にローカルの境界へ渡す別の操作です。

FFmpegは環境の前提であり、フロントエンドが任意のshell commandを組み立てる入口ではありません。レンダラーは/api/project/renderへリクエストを送り、サーバーがタイムライン、clip、version、frame rangeを検証してから、local runnerを引数配列で呼び出します。これにより、再生の軽さと、書き出しの失敗を説明できる境界を両立します。

H / ライフサイクルと配布

ローカルアプリとして成立する境界

レンダラーから見ると、フォルダ選択、プロジェクト切り替え、close、quitは似たクリックに見えるかもしれません。実際にはメインプロセスが操作キューで順序を管理し、現在のループバックoriginを確認し、レンダラーのライフサイクル応答を待ちます。preloadはopaque DTOと検証済みのmethodだけを公開し、絶対パス、認証情報、shell commandは渡しません。

standalone Next.js server、bundled helper、Electron packageは別々のresource boundaryを持ちます。packagingはstandalone outputを組み立ててdarwin-arm64 packageへ収め、installはguarded atomic installationを行います。packaged appは開発時のrepository rootを前提にできないため、releaseにはarchive、checksum、インストール結果、未検証のprovider境界を記録します。

I / クラウド完了を記録する

ローカル完了は、公開完了ではない

ローカルのマニフェストを正本とし、iCloud、private GCS、Supabase metadata、outboxはそれぞれ別の処理結果を持つ任意の同期経路として扱います。cloud headとlocal revisionがずれた時は409として、cloud head、backup-local、keep-local、syncの状態と選択を画面に返します。keep-localにも観測したcloud hashと確認操作が必要です。Supabaseはmetadata/indexであり、マニフェスト全体の復元先とは扱いません。

この境界では、制作画面に処理結果を記録して完了状態を示します。バックアップ失敗、コピー失敗、provider unavailable、public upload未実施は成功の中に含めません。認証情報はkeychainまたはマシン内の環境変数からnative側で読み、project file、bundle、log、commitには渡しません。

J / 実装上の判断

画面の速さと、保存の確かさを分けて考える

Reactは選択、入力、下書き、プレビューをすぐに反映させます。ローカルAPIは、検証、改訂番号、永続化、ジョブの状態を管理します。この役割分担なら、ブラウザで画面を開発しやすい利点を残しながら、Electronの子プロセス、起動確認、ネイティブ操作との受け渡しを別の契約として扱えます。下書きの変更を毎回ディスクへ書かずに済み、保存済みの表示はAPIの応答を根拠にできます。

ProjectStateのCASは、細かなフィールドごとではなくグラフ全体をひとつの改訂単位として守ります。そのため、二つの画面が改訂18を読み、片方が改訂19を書いたあと、もう片方が古い改訂18を送れば、変更箇所が別でも拒否されます。これは無条件の再試行で解決する問題ではありません。最新スナップショットを表示し、下書きとの差分を確認してから、ユーザーが反映方法を選ぶ必要があります。

改訂番号の確認と、JSONを安全に書くことは別の問題です。CASは古い書き手を見つけ、一時ファイルへの書き出しからfsyncとrenameへ進む処理は、途中でプロセスが落ちたときの壊れたJSONを避けます。素材はSHA-256で同じバイト列を同じ物として識別し、ショットの安定IDを保ったまま、ラフ、塗り、productionizedのバージョンを分けます。Boardの自動保存とundo履歴は編集の流れであり、採用したバージョンの変更履歴とは役割が異なります。

時間軸も単位を固定することで画面の曖昧さを減らします。例えば24fpsなら、フレーム24から48までの区間を一秒分として扱うように、整数フレームを基準にします。子タイムラインが親を直接または間接に参照すると、構成が循環して再生も書き出しも終わらなくなるため、保存前に検出します。スクラブと再生は確認のための操作で、renderは検証済みの範囲だけをサーバーへ渡す操作です。

生成では、準備済みリクエストを不変のスナップショットとして扱います。下書きの参照画像やプロンプトが変わったまま古いpackageを送れば、料金や結果が別の意図に結びつくため、preflightでstaleとして止めます。非同期jobは状態と処理結果を保存し、キャンセル印と、可能な範囲でprocess groupの終了を残します。一度に一件だけ実行する設計はスループットを上げるものではありませんが、ローカル資源と請求の見通しを保ちます。

この記録は、実装契約とend-to-endの証明を分けて残します。architectureのスナップショットは2026年10月5日で、originとlifecycle、packaging、fixtureによる検証項目は記載されています。キーボード操作、ネストした編集、providerの実行、paid generation、cloud propagation、GCS sync、restore UIは、ソースに契約があっても完了した証拠にはしません。次の実装やレビューは、その差分から始められます。

画面と二つのレンダー

制作デスクのホーム画面。プロジェクトカード、接続状態、新規作成とフォルダを開く操作を表示。
制作デスクのホーム画面。プロジェクトカード、接続状態、新規作成とフォルダを開く操作を表示。
脚本ワークスペース。プロジェクトナビゲーションと編集操作の横にシーケンス文書を表示。
脚本ワークスペース。プロジェクトナビゲーションと編集操作の横にシーケンス文書を表示。
ボードワークスペース。ラフ絵コンテの9パネルグリッドとショット詳細を表示。
ボードワークスペース。ラフ絵コンテの9パネルグリッドとショット詳細を表示。
プロダクションデスクの動画生成ワークスペース。空のプロンプト、参照素材の選択操作、動画設定、キャラクター確認、動画生成操作、完了した動画ジョブのレールを表示。
動画生成ワークスペース。参照素材と動画設定から「動画を生成」へ進み、完了したジョブでは全画面、詳細、タイムラインで使用、削除を選べる。
タイムラインワークスペース。アセット一覧、ショットプレビュー、シーケンス確認用の操作を表示。
タイムラインワークスペース。アセット一覧、ショットプレビュー、シーケンス確認用の操作を表示。

制作レンダー。ネオンのナイトクラブ内で座る人物を描いた映像。

制作レンダー。ネオンのナイトクラブ場面をより広く見せる映像。