--- title: "台本エディタの保存形式を Markdown + YAML にした話 — 「アプリが死んでも原稿は読める」から始めた設計" description: "茶番劇・ボイスドラマの台本エディタ「書き庭」を、専用バイナリではなく Markdown + YAML のフォルダとして保存する設計にした理由と、そこから芋づる式に決まった記法・リモート・クラウド・動画ソフト連携の話。2か月で2,400コミット、実装はほぼ AI。作ってみて分かった「人間可読の代償」も書く。" url: https://ryuuneko.com/blog/kakiniwa-markdown-script-editor published: 2026-09-11 20:27:44.266061 updated: 2026-09-11 20:27:44.266061 --- 「[書き庭(かきにわ)](https://kakiniwa.jp/)」は、茶番劇やボイスドラマの台本を書くためのデスクトップアプリです。台本と、キャラクター設定と、世界観メモと、立ち絵や背景の素材情報を1つのフォルダで管理して、設定資料を横に置きながら台本を書けます。Windows と Mac で動き、私が個人で作って配っています。 最初のコミットは 2026 年 7 月 18 日で、この記事を書いている 9 月上旬までに 2,400 回ほどコミットしました。実装のほとんどは AI(Claude)に書かせています。そのやり方は別の記事に書いたので、ここでは**設計の話**をします。 --- ## 最初に決めた1つのこと 設計で最初に決めたのは、機能でも画面でもなく、**保存形式**でした。 > 作品データは、書き庭が無くなっても読めなければならない。 自分が動画制作者なので、実感があります。ツールが更新を止めた、PC を替えたら開けなくなった、専用形式で書き溜めた原稿が塩漬けになった。原稿はツールより長生きするべきで、逆であってはいけない。 なので保存形式は**すべて Markdown + YAML のフォルダ**にしました。 ``` マイ作品/ ├─ project.yaml # これがあるフォルダを作品と判定 ├─ characters/ソラ.md # キャラ1人=1ファイル(frontmatter=設定、本文=自由メモ) ├─ world/舞台設定.md # 世界観・設定資料 ├─ episodes/001-タイトル.md # 台本1話=1ファイル ├─ assets/ # 素材(立ち絵・背景・SE・BGM) └─ exports/ # 書き出し先 ``` つまり、書き庭で作った作品をエクスプローラー(Finder)で開くと、**普通の `.md` と画像が並んでいるだけ**です。専用の書庫も、暗号のようなバイナリもありません。 メモ帳で開けます。Git でバックアップできます。Obsidian で閲覧できます。差分が読めるので、共同作業のときに「どこを直したか」が見えます。**書き庭が消えても、原稿は残る。** この決定から、あとのほぼ全部が芋づる式に決まりました。 --- ## 記法: 「どの行も壊さない」を最優先にした 台本は Markdown の中に、こういう軽い記法で書きます。 ``` # 移動中の話 ← 台本タイトル ## 神社の前 ← シーン区切り @背景 神社_昼 ← 背景 @BGM 曲名 ← BGM @配置 ソラ:-25 ヒナ:25 ← 立ち位置(横%・縦%) ソラ: おはよう ← セリフ ヒナ(ドヤ): 遅いぞ ← 表情つきセリフ ソラ: ええと [間:0.5] …… ← 間(秒) |漢字《かんじ》 ← ルビ > ト書き // 伏線: 謎の手紙 ← コメント(伏線の追跡にも使う) ``` 記法を設計するときに決めた原則は3つです。 1. **素材指定はすべて任意。** セリフとシーン見出しだけで台本として成立する 2. **どの記法にも当てはまらない行はエラーにせず、そのまま保持する。** 変な行があっても原稿が消えない 3. **記号は全角でも受ける。** `#` `@` `[SE:…]` のように打った表記はそのまま残す 2番目は「人間可読」と表裏一体です。パーサが厳格だと、手で編集した原稿が「読めないので開けません」になり、せっかく Markdown にした意味がなくなる。書き庭のパーサは、理解できない行を「不明な行」として**そのまま**保持し、書き戻すときもそのまま出します。 3番目は日本語入力の現実です。台本を書いている最中に IME の全角・半角を意識させたくない。全角で打った記号を半角に**勝手に直さない**のもポイントで、直すと「自分が書いたのと違う」になって、差分もノイズだらけになります。 組み込みの `@` コマンドは実は4種類しかなく、それ以外の `@カメラ 引き` のような行は「カスタム演出」として、名前と値をそのまま保持します。用語を固定リストにすると、使う人の演出語彙が縛られるからです。 --- ## 「人間可読」の代償 いいことばかりではありませんでした。 ### YAML は勝手に型を付ける キャラクター設定の frontmatter を「一部だけ書き換えて保存」するとき、素直に YAML パーサで読んで書き戻すと、`007` が `7` になり、`yes` のような文字列が `true` として解釈される。**手で書いた値が、開いて閉じただけで変わる。** これは原稿の改変です。 対策として、frontmatter の往復は「元の文字列を保持する」パーサを通し、変更したキーだけを差し替える形にしました。往復で変わらないことをテストで見張っています。 ### 「まだ無い」と「読めない」を混同すると原稿が消える ファイルが**存在しない**ことと、ファイルが**読めない**(権限・一時的な I/O エラー・クラウドの応答なし)ことは、まったく別です。これを混同して「読めない → 空として扱う → 空で上書き保存」が起きると、原稿が消えます。実際、全文置換でこれをやりかけました。 いまは「読めない」を値で塗り潰さないように、`NOTFOUND` の印を返す層と、上書き前に元の内容と突き合わせる層の2段で守っています。「読めなかったことを 0 や空で塗る」書き方は、専用の静的検査で新規に書けないようにしました(既存の 200 か所以上は凍結して、増えたら落ちる)。 ### ファイルが正本だと、外から変わる エディタ内の状態が正本ではなく、ディスク上のファイルが正本です。つまり、書き庭が開いている間に、別のエディタや Git の pull でファイルが変わる。「外部変更にどう対応するか」の判断だけを純粋な関数に切り出して、パターンをテストで網羅しています。 --- ## 構成: Tauri 2 + React、判断は Rust の外へ 本体は Tauri 2(Rust)+ React + TypeScript です。 設計として意識したのは、**Rust 側には「ファイル操作と守り」だけを置き、判断はテストしやすい場所に出す**ことです。たとえば「このパスを開いてよいか」「新しい作品フォルダを作ってよいか」は、`AppHandle` に依存しない純粋な関数に分けてあり、Rust の `#[test]` で直接殴れます。UI の文脈が要る部分は薄い皮だけ。 もう1つ、パスの封じ込め(作品フォルダの外に出ない)は1つの関数に集約してあり、書き込みの入口すべてがそれを通ります。ここで痛い目を見たのは、「門番の関数にはテストがあるのに、**呼んでいることを誰も見ていない**」という穴が、TypeScript と Rust の両方で独立に出たことです。テストを関数単位で考えると、門番は対象になるが呼ぶ側は対象に見えない。いまは「門番を呼ぶ行を壊すと落ちるテストが登録されているか」を機械で見張っています。 --- ## リモート: データはサーバーを通さない 外出先のスマホから自宅 PC の書き庭を開く「リモート」があります。これは**クラウドではありません**。 仕組みは WebRTC の P2P です。 1. 自宅 PC(ホスト)が Cloudflare Worker に部屋を作り、WebSocket でシグナリングを待つ 2. スマホ(ゲスト)が同じアカウントでログインし、作品カードをタップ 3. WebRTC の DataChannel が直接つながる 4. ゲストの書き庭は、**ファイル操作のインターフェースを DataChannel 越しに RPC する** 4番が設計上の要点です。書き庭の中では、ファイルを読む・書く・一覧するといった操作が `platform` という1つのインターフェースにまとまっていて、デスクトップ版はそれを Tauri 経由で、クラウド版は HTTP で、リモートは DataChannel で実装しています。上の React 層は、自分がどの platform の上で動いているかを知りません。だからリモートのゲストは、ホストの platform をそのまま遠隔で呼ぶだけで、**アプリの全機能がそのまま動きます**。 ![React 層の下に platform という1つのインターフェースがあり、デスクトップは Tauri、クラウドは HTTP、リモートは DataChannel 越しにホストの platform を呼ぶ図](/blog/uploads/kakiniwa-platform-layers.svg) *▲ platform の差し替え。上の React 層は、自分がどの実装の上で動いているかを知らない。* 台本のデータは Worker を通りません。シグナリングだけがサーバーで、あとは DTLS で暗号化された直通です。NAT を越えられないとき(CGNAT など)だけ、Cloudflare の TURN が中継します。 実装して最初に踏んだのは「接続はできるのに作品が開かない」で、原因は接続後に作品リストを取るだけで、実際に**開く処理を誰も呼んでいなかった**という間抜けなものでした。リモートの接続まわりの不具合は、この手の「つながった後の段取り」に集中しています。 --- ## クラウド: Cloudflare だけで完結させる *ここからは少し余談で、インフラ寄りの話です。「ファイルが正本」という考え方はクラウド版にも影響しているので、その範囲で書きます。保存形式の話だけ読みたい方は、次の「動画ソフト連携」まで飛ばしても大丈夫です。* クラウド版(サーバー保存・共同編集)のバックエンドは Cloudflare Workers + D1(SQLite)+ R2(オブジェクト)+ Durable Objects です。VPS を1台も持ちません。 保存は「ホットな台本は Durable Object、冷えた台本と素材は R2」の二層で、これは採算の都合です。Durable Object の保存単価は R2 の 13 倍なので、保存が絡むプランは R2 前提でないと赤字になります。 暗号化は、**あえてアプリ層ではしていません**。検討はしましたが、個人運営で Worker(鍵)と R2(データ)が同じアカウントの下にある以上、片方が漏れるときは両方漏れるので、サーバー側のエンベロープ暗号化が守れる範囲は狭い。代わりに、アカウントと API トークンの最小権限、書き込み専用と読み取り専用の分離、といった堅牢化に寄せています。扱っているのは未公開の茶番劇の台本で、しかも利用者はいつでも平文で書き出せる、という前提もあります。 バックアップは Object Lock(WORM)つきの別バケットに追記して、**復元の検証まで自動**で回しています。バックアップは「取れている」ではなく「戻せる」まで確認しないと意味がないので。 --- ## 動画ソフト連携: 中間ファイルを規格にする 台本を書いたら、動画にしたい。書き庭には YMM4(ゆっくりMovieMaker4)向けのプラグインがあり、台本を読んでタイムラインにアイテムを並べます。 ここで決めたのは、書き庭が YMM4 のプロジェクト形式を直接吐かない、ということです。書き庭は **`timeline.json` という自前の中間ファイル**(セリフ・話者・表情・立ち位置・効果音・間)を書き出し、プラグインがそれを YMM4 に流し込みます。 理由は2つ。 - YMM4 側の内部形式は本体の更新で変わる。中間ファイルを規格として書き庭が所有していれば、壊れるのはプラグイン側だけで済む - 同じ中間ファイルから、将来ほかの動画ソフトにも出せる プラグインは C# で、OSS として公開しています。書き庭側のリポジトリから一方向にミラーしていて、初期に `/MIR` で貢献者のコミットを消しかけたのは反省点です。 --- ## 配布と料金の方針 サーバーを使わない機能は**すべて無料**です。デスクトップ版はオフラインで全機能が使えます。有料なのは、リモートとクラウドのようにこちらに維持費がかかるものだけで、料金はその維持費と開発の継続に充てます。 「機能を不便にして課金を促す」売り方はしません。それをやると、最初の「原稿はツールより長生きする」に反するからです。同じ理由で、開発が続けられなくなったときはソースコードを公開する方針をサイトに明記しています。 コード署名はしていません。個人の署名証明書は署名者の本名が全利用者に表示されるためで、配信者として本名を出さないのは非交渉の条件です。その代わり、Windows の Defender 誤検出は検知スクリプトで追って Microsoft に申請し、Mac は初回に右クリックで開く手順を添えています。 --- ## 作ってみて分かったこと - **保存形式を先に決めると、あとが楽になる。** 記法の寛容さも、リモートの設計も、「ファイルが正本」から自然に出てきた - **人間可読には代償がある。** YAML の勝手な型付け、外部からの変更、「無い」と「読めない」の混同。全部、原稿が消える方向に効くので、守りは機械で - **インターフェースを1つ切っておくと、リモートもクラウドもその実装差し替えで済む。** platform の抽象は、作った当初は「将来の Mac 版とクラウド版のため」だったのが、一番効いたのはリモートだった - **個人開発の設計判断の半分は採算と本名の問題。** 技術的に正しくても、維持費が乗る・名前が出る選択は取れない 書き庭は [kakiniwa.jp](https://kakiniwa.jp/) で無料配布しています。まだ 0.x です。次に手を入れるのは大きな作品でのパフォーマンス(仮想化・遅延読み込み・増分パース)ですが、それも「ファイルが正本」の上でやります。原稿は、ツールより長生きするので。