動画を編集していると、「ここから第2章」「この拍でカットを切り替える」「この区間は字幕を再確認する」といった判断が増えていきます。
こうした情報がチャットにしか残っていないと、プロジェクトを開き直すたびに該当箇所を探す必要があります。AI Agentに続きを依頼する場合も、時間と編集意図をもう一度渡さなければなりません。
そこで、開発しているOSSのブラウザ動画エディター Timeline Studio にタイムラインマーカーを追加し、CLI・MCP・Agent Skillからも読み書きできるようにしました。この記事では、データモデル、Reactでのドラッグ操作、差分確認を伴うコマンド設計、実装中に見つかった境界条件を紹介します。
Timeline StudioはReactとViteで構築したローカルファーストの動画エディターです。実際の操作はオンラインエディターで試せます。コードはGitHubで公開しています。本記事のAgent向け機能はSkill v1.0.7に含まれます。
マーカーを「プロジェクトの注釈」として保存する
扱う情報を、次の4種類に分けました。
| 種類 | 用途 | 時間フィールド |
|---|---|---|
marker |
拍、動作の瞬間、カット位置の目印 | time |
chapter |
章や話題の開始位置 | time |
range |
見直しや作業の対象区間 |
time、endTime
|
note |
特定時刻に対する修正メモ | time |
保存形式は共通です。たとえば、5〜8秒の商品紹介を見直す場合は次のようになります。
{
"id": "product-review",
"type": "range",
"time": 5,
"endTime": 8,
"title": "商品紹介",
"notes": "字幕と映像のタイミングを確認する",
"color": "violet"
}
時刻はプロジェクト上の秒数で統一し、timelineMarkersとして可搬形式の.timelineファイルに保存します。タイトルやメモはUnicode文字列なので、日本語もそのまま扱えます。
重要なのは、マーカーを動画の長さの計算に含めないことです。60秒の動画に90秒地点の計画用マーカーを追加しても、書き出しが90秒になってはいけません。また、章マーカーは画面上のタイトルやMP4のチャプターを自動生成するものではありません。
注釈だけを編集する依頼なら、新しいプロジェクトファイルを保存すれば完了です。映像が変わらないのに再エンコードする必要はありません。なお、マーカーは絶対プロジェクト時刻を保持するため、クリップの並べ替えやリップル編集に自動追従はしません。内容に追従させる必要がある場合は、対象マーカーも明示的に更新します。
ReactのUIは、普段は小さく、必要なときに詳しく
すべてのマーカーを常時専用レーンに表示すると、ノートPCでは素材トラックの表示領域が狭くなります。そこで、通常は目盛り上に小さなフラグを表示し、ツールバー横のボタンで展開したときだけタイトルと区間を表示する形にしました。
Mで再生ヘッド位置にマーカーを追加し、Shift + Mで管理パネルを開けます。マーカー機能のUIは、日本語を含む13言語に対応しています。
ドラッグ中はReactの一時プレビュー状態で位置を更新し、ポインターを離したときにプロジェクトへ反映します。Escapeやキャンセル時にはプレビューを破棄します。連続したポインター移動と、確定した編集操作を分ける設計です。
スナップのしきい値は秒数ではなく画面距離から決める
固定の「0.2秒以内」で吸着させると、タイムラインのズーム倍率によって操作感が変わります。今回の実装では10pxを時間へ換算しています。
const thresholdSeconds =
10 / railWidth * timelineDuration;
railWidthはレールの幅、timelineDurationはそのレールに対応する時間幅です。これにより、ズームしても画面上の吸着距離をおおむね一定にできます。
吸着先には再生ヘッド、素材の境界、他のマーカーを使い、タイムラインの共通ガイドを表示します。Altキーを押している間はスナップを一時的に無効にできます。
区間の移動では両端を調べる必要があります。5〜8秒の区間の終端が12秒に吸着した場合、結果は9〜12秒です。長さ3秒を保ったまま移動します。考え方を簡略化すると次の式になります。
const nextStart = movingEdge === "end"
? targetTime - duration
: targetTime;
const nextEnd = nextStart + duration;
実装では始端と終端の候補を比較し、距離が近いものを採用します。片側だけを補正すると、「移動」のつもりが「長さ変更」になるため、区間全体の平行移動と終端のリサイズを区別しています。
CLIとMCPで編集ロジックを共有する
Agent向けの構成は次のとおりです。
Agent Skill:手順、時間の根拠、検証方法
↓
CLI / MCP:構造化された操作の入口
↓
共通コマンドエンジン:検証、適用、意味的な差分
↓
新しい .timeline ファイル
MCPアダプターは既存のCLIランナーを呼び出します。マーカーの追加・更新・削除をMCP側に再実装しないため、検証ルールの修正を両方の入口に反映できます。
プロジェクトと既存マーカーは、リポジトリのルートで次のように確認します。
npm run agent -- project.inspect /projects/input.timeline
npm run agent -- marker.inspect /projects/input.timeline
MCPには読み取り用のtimeline_marker_inspectを用意しました。書き込みはmarker.add、marker.update、marker.deleteを共通の差分確認・適用フローに渡します。
たとえば、検査したプロジェクトのrevisionが0で、product-reviewが5〜8秒の区間だったとします。これを9秒へ移し、11秒にメモを追加する計画は次のように書けます。
{
"schemaVersion": 1,
"project": "/projects/input.timeline",
"baseRevision": 0,
"operations": [
{
"id": "move-product-v1",
"type": "marker.update",
"markerId": "product-review",
"time": 9
},
{
"id": "add-ending-note-v1",
"type": "marker.add",
"markerId": "ending-note",
"markerType": "note",
"time": 11,
"title": "終わり方の調整",
"notes": "依頼者の指示:最後に少し余韻を残す",
"color": "rose"
}
],
"output": { "project": "/projects/output-marked.timeline" }
}
これは説明用の例です。実行時はパス、revision、IDを検査結果に置き換え、メモには実際に受けた指示を使います。
idは操作を識別し、markerIdは保存済みマーカーを識別します。また、typeはコマンドの種類、markerTypeはマーカーの種類です。区間のtimeだけを更新すると長さを維持するため、上の例では5〜8秒が9〜12秒になります。
計画を/projects/markers-plan.jsonに保存したら、構造の検証と差分確認を先に行います。
node skills/edit-timeline-studio/scripts/validate_edit_plan.mjs /projects/markers-plan.json
npm run agent -- project.diff /projects/markers-plan.json
差分が意図どおりなら、同じ計画を適用して出力を再検査します。
npm run agent -- project.run /projects/markers-plan.json
npm run agent -- marker.inspect /projects/output-marked.timeline
差分のchanges.markersには追加・削除・変更が入り、変更前後の内容を確認できます。Skillは「検査→差分確認→適用」を要求し、コード側はrevision、トランザクション、出力ファイルを保護します。
- revisionが一致しなければ
REVISION_CONFLICTを返す。 - 適用済みの操作IDを再送しても、その操作を重複適用しない。
- 途中の操作が失敗した場合、変更の一部だけを書き出さない。
- 入力ファイルや既存ファイルへの上書きを拒否し、新しい出力先を使う。
バッチ操作で見つかったIDと浮動小数点の問題
旧データにxというIDが3件あり、読み取り時にx、x-2、x-3へ正規化したケースを考えます。各操作のたびに正規化すると、先頭を削除した後に残りのIDが変わり、続く更新が別のマーカーを指してしまう可能性があります。
対策として、マーカーを書き込むトランザクションの開始時にプロジェクトのコピーを一度だけ正規化し、そのIDを固定しました。重複IDの接尾辞にも長さを確保し、生成後も160文字の上限内に収めています。
もう一つは1ms区間の移動です。1000秒付近の区間長を減算すると、浮動小数点誤差により0.001をわずかに下回る場合があります。厳密な比較だけでは正しい移動を拒否するため、モデルの最小区間長と数値誤差を考慮しました。実際に短すぎる区間の入力は引き続き拒否します。
Agentが使う時刻にも根拠が必要
UIのスナップは画面上の操作ですが、CLIとMCPには正確な秒数を渡します。Agentは検査済みのクリップ境界や、確認できた音声の時刻を利用します。
ソース動画中の出来事をプロジェクト時刻に変換する場合、一定速度なら次の関係です。
プロジェクト時刻 = クリップの開始時刻
+ (ソース時刻 - ソースのトリム開始位置) / 再生速度
速度カーブが有効なら、対応するソース時間マッピングが必要です。平均速度で単純に割るとずれる場合があります。拍のマーカーも同様で、現在のコマンドは時刻を保存する機能であり、自動ビート検出機能ではありません。拍の基準点や分析結果を確認してから書き込みます。
実際に確認したこと
CLIとMCPの呼び出しに加え、AgentがSkillの手順を読んで区間を5〜8秒から9〜12秒へ移し、11秒にUnicodeのメモを追加する流れを確認しました。区間長、他のマーカー、元ファイルのハッシュ、アーカイブ内のメディアバイト列を検査しています。
注釈のみの変更で媒体の長さとレンダリング計画が変わらないことも確認しました。Skillの構造検証、型チェック、プロダクションビルド、GitHub CIは通過しています。既存のlint警告は残っています。
人が残した修正メモをAgentが読み取り、Agentが作った章立てを人が調整できると、編集意図をプロジェクトと一緒に引き継げます。今回の実装では、そのために時間、ID、差分を明示することを重視しました。
実際のフラグ表示や区間スナップはTimeline Studioのオンラインエディターで試せます。実装を読む場合はGitHubリポジトリとマーカーのワークフローを参照してください。改善案や再現ケースはIssueで歓迎しています。
Codex向けSkill v1.0.7は次のコマンドでインストールできます。
gh skill install MartinDelophy/ai-video-editor edit-timeline-studio --pin v1.0.7 --agent codex --scope user
Skillのインストールはワークフローの配置です。ローカルコマンドの実行には、Timeline StudioのリポジトリとNode依存関係が必要です。MCPアダプターもそのチェックアウトから起動します。詳細はv1.0.7のリリースノートにまとめています。