akari-video 0.1.72 → 0.1.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.72",
3
+ "version": "0.1.73",
4
4
  "description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,7 @@
1
1
  # 生成 v0 契約 — 仮枠クリップ・9 スロット・meta.json・能力カタログ・状態
2
2
 
3
- > 正本の写し(2026-09-13)・技術部分のみ。内部正本の §1〜§9 を公開向けに収録する。
3
+ > 正本の写し(2026-09-13・2026-09-21 改訂)・技術部分のみ。内部正本の §1〜§9 と §11 を公開向けに収録する。
4
+ > 節番号の対応: 本書 §1〜§9 = 内部正本 §1〜§9、本書 §10(コマ保存)= 内部正本 §11。
4
5
 
5
6
  ## 1. 継ぎ目(仮枠とタイムライン)
6
7
 
@@ -10,6 +11,9 @@
10
11
  4. 「動画にする」は同じ item の `source.path` を mp4 に書き換える**差し替え**であり、item id・トラック・尺は変わらない(§7)
11
12
  5. 絵コンテはタイムラインの印刷(9/6 §5)。生成の入力にしない
12
13
  6. plan.json の仮枠役(`confidence` / `fill`)は退役。`plan-comments.json` の `pass: "scaffold"` の対象は `slot` から **clip id** へ
14
+ 7. **空の枠**(2026-09-21): 仮枠ツール(F)で空いているところに描いた枠は、prompt 未記入の**文字カード png** を素材に持つ media item(絵のないクリップを文字カード png で表す規則の延長)。0.5 秒刻み・端に吸着・隣に食い込まない・0.5 秒未満にしない。専用トラックは作らない(どのトラックにも置ける)
15
+ 8. **すき間から作る枠**(2026-09-21): すき間をクリック →「あいだを生成」で、すき間の位置と長さの枠を置く。前のクリップの最後のコマ・次のクリップの最初のコマを抽出して両端(②③)に入れる。動画からの抽出は 1 コマを `assets/captures/` へ書き出す(§10)
16
+ 9. 枠は**生成前から尺と場所を持ち、生成物は同じ item に入る**(4 の延長)。メディアパネルは経由しない(タイムライン正本)
13
17
 
14
18
  ## 2. 9 スロット(生成入力の正規形)
15
19
 
@@ -66,9 +70,38 @@
66
70
  2. `status: generating` かつ `started_at` から `stale_after_s` 超 = **stale**。シェルは「応答なし・再取得」を出す。既定 900 秒
67
71
  3. 状態の遷移は undo に入れない。**差し替え(§7)だけ** edit-store の undo に 1 手として入る
68
72
  4. `history[]` は消さない。失敗も残す
69
- 5. `kind: "still"` は `inputs.reference_images` に `--image=` の 1 枚(Codex)か複数枚(Nano Banana Pro)。`kind: "frames"` はパラパラ(別契約・§10)
73
+ 5. `kind: "still"` は `inputs.reference_images` に `--image=` の 1 枚(Codex)か複数枚(Nano Banana Pro)。`kind: "frames"` はパラパラ(別契約)
70
74
  6. `result.expanded_prompt` は provider が書き換えた prompt。較正では **送った prompt ではなく expanded を評価対象**に添える
71
75
 
76
+ ### 3-2. 動画予定の下書き `next`(2026-09-21)
77
+
78
+ mp4 がまだ無い「動画予定」は、**仮枠の素材の meta**(静止画なら `<still>.meta.json`、空の枠なら文字カードの meta)の中に `next` ブロックで持つ。
79
+
80
+ ```json
81
+ {
82
+ "version": 1, "kind": "still", "status": "done", // 静止画自身の記録はそのまま
83
+ "…": "…",
84
+ "next": {
85
+ "kind": "video", "status": "planned",
86
+ "model": { "id": "fal:h3-i2v" },
87
+ "inputs": { "prompt": "…", "first_frame": { "path": "…", "sha256": "…" }, "last_frame": null,
88
+ "reference_images": [], "reference_videos": [], "reference_audios": [],
89
+ "camera": null, "seed": null, "extra": {}, "frames_or_refs": "frames" },
90
+ "output": { "duration_s": 5, "resolution": "768P", "aspect": null, "audio_out": null },
91
+ "updated_at": "…"
92
+ }
93
+ }
94
+ ```
95
+
96
+ 1. **読み手の判定**: `next.kind === "video"` かつ `next.status === "planned"` = 「動画予定」。`next` が無い静止画 = 「画像のまま」= 完成品。文字カード(meta 自体が `planned`)で `next` が無いか prompt が空 = 「空の枠」
97
+ 2. `next.inputs` は §2 の 9 スロットの下書き。`first_frame` は**そのクリップの絵とは限らない**(前のクリップの最後のコマ・キャプチャ・空 = プロンプトだけ)
98
+ 3. **`inputs.frames_or_refs`** = `"frames"` / `"references"`(`next` の下書きだけが持つ欄。§2 ⑦ の `mode` = 元動画のモードとは別物)。最初 / 最後と参照が排他のとき(`frames_and_refs_exclusive: true` の行、または同じ family の i2v 行と ref 行の切替)、**下書きは両側を保持し、送るのは `frames_or_refs` の側だけ**。切り替えで中身を消さない。バリデータはこの欄を見て反対側を送信 body から外し、生成物 meta の `inputs` にはこの欄を書かない(送った側だけが残る)
99
+ 4. **送るとき**: CLI は `next`(または `--inputs`)を読み、従来どおり**生成物の隣**に video meta(`generating`)を書く。このとき **`placeholder: { path, sha256, item_id }`** = その item が今指している素材(静止画 / 文字カード)を必ず書く。item への逆引きは `placeholder` が正、`inputs.first_frame.path` は 9/13 時点の meta のための後方互換。`next` は消さない(「同じ入力でもう一度」の元)
100
+ 5. **状態の優先**: `placeholder` で結線された生成物 meta が `generating` / stale / `failed` ならそれを描く。無ければ `next` の `planned` を描く。`done` で差し替わった後は mp4 の meta が直接当たる(§7)
101
+ 6. `next` の更新は undo に入れない(§3 規則 3 と同じ)。右パネルの編集は即保存
102
+ 7. 移行: `.akari/generation/<itemId>.inputs.json` があり `next` が無いときだけ読み、次の保存で `next` へ移す。新規の書き込みはしない
103
+ 8. ビート表(`akari generate still --spec`)はビートごとに動画予定(最初だけ / 最初→最後)を指定でき、指定があれば CLI が `next` を書く。指定が無ければ「画像のまま」
104
+
72
105
  ## 4. 能力カタログ `gen-models.json`(公開リポ `packages/schemas/`)
73
106
 
74
107
  ### 4-1. 形(1 行 = 1 エンドポイント)
@@ -146,21 +179,26 @@ Kling v3 standard i2v / Kling v3 pro i2v / Veo 3.1 first-last / Veo 3.1 referenc
146
179
 
147
180
  | 状態 | タイムライン | プレビュー(編集中) | 書き出し |
148
181
  |---|---|---|---|
149
- | `planned`(文字カード) | 点線 + 「planned」 | 文字カード + 左上小札 | 文字カードのまま。lint WARN |
150
- | 静止画(meta なし or `done` の still) | 「静止画」バッジ | 静止画 + 左上小札「静止画(仮枠)」 | そのまま。小札なし |
151
- | `generating` | 黄の縞 + 進捗バー | 静止画の上にシマー + 下端の帯 | 静止画のまま |
182
+ | 空の枠(文字カード・`next` なし or prompt 空) | 点線 + 「planned」 | 文字カード + 左上小札 | 文字カードのまま。lint WARN |
183
+ | **動画予定**(`next` = video · planned。§3-2) | **紫の点線 + 上下のフィルムの穴 + 「▶ 動画予定」**。中身は 最初の絵 1 枚 ・ 生成で埋める空き ・ 最後の絵 1 枚(同じ絵を並べない)。プロンプトだけは文字。最後の絵 = 次のクリップの絵なら境目に 🔗 | 左上小札「▶ 動画予定 · <種類>」+ 最後の絵があれば右下の小窓 | 静止画 / 文字カードのまま。小札・小窓 0 px |
184
+ | 静止画 = 画像のまま(`next` なし) | 「静止画」バッジ | 静止画。**小札なし**(完成品) | そのまま |
185
+ | `generating` | 黄の縞 + 進捗バー | **参照の絵をぼかした背景** + シマー + 下端の帯。進捗は擬似でなく provider の状態 | 静止画のまま |
152
186
  | stale | 縞 + 「応答なし・再取得」 | 帯に「応答なし」 | 静止画のまま |
153
187
  | `done`(動画) | 通常 + コマ帯 + 「生成」由来バッジ | 動画。表示なし | 通常 |
154
- | `failed` | 朱枠 + 「失敗」 | 静止画 + 朱の小札 | 静止画のまま |
188
+ | `failed` | 朱枠 + 「失敗」+ **「同じ入力でもう一度」** | 静止画 + 朱の小札 | 静止画のまま |
155
189
 
156
- - プレビューに付くのは**左上の小札と下端の帯だけ**。承認・比較の UI はプレビューに置かない
190
+ - プレビューに付くのは**左上の小札・下端の帯・動画予定の右下の小窓・コマ保存のカメラボタン(§10)だけ**。承認・比較の UI はプレビューに置かない
191
+ - 種類の表示は「プロンプトだけ / 画像から / 最初→最後 / 参照から」。枠を埋めたかで決まり、モード選択の UI は作らない
192
+ - 失敗の「同じ入力でもう一度」はクリップと右パネルの両方に出す
157
193
  - **書き出し経路には小札・帯・点線が 0 px**。画素比較で担保(w3-b)
158
194
 
159
195
  ## 7. 差し替え規則(done になったとき)
160
196
 
161
197
  ### 7-1. 何を書き換えるか
162
198
 
163
- 同じ item の `sources[].path` を mp4 に。item id・トラック・`at` は不変。静止画の path は `meta.inputs.first_frame` に残る(由来)。**1 手の undo**。
199
+ 同じ item の `sources[].path` を mp4 に。item id・トラック・`at` は不変。静止画の path は生成物 meta の `placeholder`(§3-2)に残る(由来。9/13 時点の meta は `inputs.first_frame`)。**1 手の undo**。
200
+
201
+ **クリップ側の設定は保持する**(2026-09-21)。変形(拡大率・位置・不透明度・反転)と色(LUT・明るさ等)は item の上に重ねる設定であり、差し替えで入れ替わるのは**素材だけ**。逆に、**生成に送る絵は素材のまま**で、クリップの LUT・サイズは送らない(キャプチャを枠に入れた場合は §10-2)。
164
202
 
165
203
  ### 7-2. 実尺のずれ
166
204
 
@@ -178,7 +216,7 @@ Kling v3 standard i2v / Kling v3 pro i2v / Veo 3.1 first-last / Veo 3.1 referenc
178
216
  ## 8. 費用承認(有償生成のゲート)
179
217
 
180
218
  1. 語彙: 有償生成の承認は「**費用承認**」。「判子」は 9/6 契約どおり書き出しの 1 回にだけ使う
181
- 2. 回数: 1 クリップずつが既定。複数を選んで**合計金額を出して 1 回**は可。「全部を自動で動画にする」は無い
219
+ 2. 回数: 1 クリップずつが既定。複数を選んで**合計金額を出して 1 回**は可。「全部を自動で動画にする」は無い。複数選択時の右パネル(対象一覧 + 合計見積 + まとめて費用承認 1 回)を正の UI とする。対象外(画像のまま・空の枠・生成済み)は送らない。見積は送信ボタンの横に常時出す
182
220
  3. 見積: `price.by_resolution × duration_s × audio_multiplier`、as_of 付き。`actual_usd` が取れない provider は `estimate` を写して `cost.source: "estimate"`。見積不可(price null)は明示確認で通す
183
221
  4. 停止条件は autonomy §4 と同じ(有償 or 外部送信)。Codex 画像生成は無償扱い
184
222
 
@@ -187,9 +225,18 @@ Kling v3 standard i2v / Kling v3 pro i2v / Veo 3.1 first-last / Veo 3.1 referenc
187
225
  | 語 | 意味 |
188
226
  |---|---|
189
227
  | 仮枠 | 尺と場所を持つ静止画クリップ。完成品でもある |
228
+ | 動画予定 | 仮枠に動画生成の下書き(`next`)が付いた状態。mp4 はまだ無い |
229
+ | 空の枠 | 絵も prompt もまだ無い仮枠(文字カード) |
230
+ | 画像のまま | 動画にしない静止画クリップ。完成品 |
190
231
  | 参照動画 | 動きやカメラワークを**真似る元**。貼り込まれない |
191
232
  | 元動画 | **続ける・直す元**(extend / edit / motion / frame-edit) |
192
233
  | 費用承認 | 有償生成の実行前ゲート |
193
234
  | 判子 | 書き出しの 1 回(9/6) |
194
235
  | 事実帯 | モデル選択の 1 行(価格・尺・入力・音声・較正)。レーダーの代わり |
195
236
 
237
+ ## 10. コマ保存(キャプチャ)(2026-09-21)
238
+
239
+ 1. プレビューの**カメラのアイコンだけのボタン**(文字なし・絵文字にしない)= 今のコマを **見たまま(合成後・LUT とサイズ込み)** で `assets/captures/frame-<t>.png` に保存し、トーストを出す。素材パネルに出る
240
+ 2. キャプチャを生成の入力(②③④)に使うときは、**その png が素材**。LUT 込みで撮ったなら込みで送られる(§7-1 の「送る絵は素材のまま」と矛盾しない — 素材がそういう絵である)。UI は枠に入れるとき「見たままのコマ(色・サイズ込み)」と明示する
241
+ 3. 「あいだを生成」(§1-8)の前後コマ抽出は**素材の画素**(合成前)を使う。見たままが要るときは人がカメラボタンで撮る
242
+ 4. 書き出し経路にボタンは 0 px(§6)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.72",
3
+ "version": "0.1.73",
4
4
  "description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。 [akari-video npm vendor: bin/akari.mjs is reference-only. These CLI entrypoints are not included in the akari-video npm package. Use `akari doctor --json` and run the path reported in `render_cut.path`. Full installations provide it in a monorepo checkout, ~/.akari/app, /Applications/AKARI Video.app/Contents/Resources/packages, or %LOCALAPPDATA%\\Programs\\@akari-videoshell\\resources\\packages.]",
5
5
  "type": "module",
6
6
  "files": [
@@ -26,21 +26,113 @@ function resolveV2Write(parsed, command) {
26
26
  if (!itemId) {
27
27
  throw new Error('v2 アイテムの id を特定できません');
28
28
  }
29
- let item;
30
- for (const track of edit.tracks) {
31
- if (track.lane !== 'visual' || !('items' in track))
32
- continue;
33
- const found = track.items.find(candidate => candidate.id === itemId);
34
- if (found) {
35
- item = found;
36
- break;
29
+ const children = (item) => item.items
30
+ ?? (Array.isArray(item.children)
31
+ ? item.children : []);
32
+ const find = (items, id, ancestors = []) => {
33
+ for (const candidate of items) {
34
+ if (candidate.id === id)
35
+ return { item: candidate, ancestors };
36
+ const nested = find(children(candidate), id, [...ancestors, candidate]);
37
+ if (nested)
38
+ return nested;
39
+ }
40
+ return undefined;
41
+ };
42
+ const roots = edit.tracks.flatMap(track => track.lane === 'visual' && 'items' in track ? track.items : []);
43
+ // Only overlay writes gain recursive addressing. Other preview systems keep
44
+ // their existing addressing/coordinate contract.
45
+ let target = command.kind === 'overlay' ? find(roots, itemId)
46
+ : roots.filter(candidate => candidate.id === itemId).map(item => ({ item, ancestors: [] }))[0];
47
+ let materialized = false;
48
+ if (!target && command.kind === 'overlay' && itemId.includes('#')) {
49
+ const separator = itemId.lastIndexOf('#');
50
+ const bag = find(roots, itemId.slice(0, separator));
51
+ const part = itemId.slice(separator + 1);
52
+ if (bag?.item.source.kind === 'html' && !bag.item.source.part && part
53
+ && !bag.item.source.exclude?.includes(part)) {
54
+ const existing = children(bag.item).find(child => child.source.kind === 'html' && child.source.part === part);
55
+ if (existing)
56
+ target = { item: existing, ancestors: [...bag.ancestors, bag.item] };
57
+ else {
58
+ // Object-tree contract §1.3: touched projections become explicit
59
+ // children; §3.1: materialize the projection. Do not exclude it.
60
+ const ids = new Set();
61
+ const collect = (items) => {
62
+ for (const entry of items) {
63
+ ids.add(entry.id);
64
+ collect(children(entry));
65
+ }
66
+ };
67
+ for (const track of edit.tracks)
68
+ if ('items' in track)
69
+ collect(track.items);
70
+ const base = `${bag.item.id}.${part}`;
71
+ let id = base;
72
+ for (let suffix = 2; ids.has(id); suffix++)
73
+ id = `${base}-${suffix}`;
74
+ const child = {
75
+ id, at: 0, duration: bag.item.duration,
76
+ source: { kind: 'html', path: bag.item.source.path, part }
77
+ };
78
+ (bag.item.items ??= []).push(child);
79
+ target = { item: child, ancestors: [...bag.ancestors, bag.item] };
80
+ materialized = true;
81
+ }
37
82
  }
38
83
  }
39
- if (!item) {
84
+ if (!target)
40
85
  throw new Error(`アイテムが見つかりません: ${itemId}`);
86
+ const item = target.item;
87
+ if (command.kind === 'overlay') {
88
+ // parts.mjs composes groups, but a bag supplies per-key defaults that
89
+ // its part overrides. Only group ancestors form an invertible parent.
90
+ // Preserve the original top-level merge/serialization byte for byte.
91
+ if (command.patch.transform && target.ancestors.length) {
92
+ const compose = (parent, child = {}) => {
93
+ const angle = parent.rotate * Math.PI / 180;
94
+ const x = child.x ?? 0, y = child.y ?? 0;
95
+ return {
96
+ x: parent.x + parent.scale * (Math.cos(angle) * x - Math.sin(angle) * y),
97
+ y: parent.y + parent.scale * (Math.sin(angle) * x + Math.cos(angle) * y),
98
+ scale: parent.scale * (child.scale ?? 1), rotate: parent.rotate + (child.rotate ?? 0)
99
+ };
100
+ };
101
+ const parent = target.ancestors.filter(ancestor => ancestor.source.kind === 'group')
102
+ .reduce((world, ancestor) => compose(world, ancestor.transform), { x: 0, y: 0, scale: 1, rotate: 0 });
103
+ if (!Number.isFinite(parent.scale) || parent.scale === 0) {
104
+ throw new Error(`親の変形を逆変換できません: ${itemId}`);
105
+ }
106
+ const patch = command.patch.transform;
107
+ const bag = target.ancestors.at(-1);
108
+ const bagDefaults = item.source.kind === 'html' && item.source.part && bag?.source.kind === 'html'
109
+ ? bag.transform : undefined;
110
+ const world = { ...compose(parent, { ...bagDefaults, ...item.transform }), ...patch };
111
+ const local = {};
112
+ if (patch.x !== undefined || patch.y !== undefined) {
113
+ const angle = -parent.rotate * Math.PI / 180;
114
+ const dx = world.x - parent.x, dy = world.y - parent.y;
115
+ local.x = (Math.cos(angle) * dx - Math.sin(angle) * dy) / parent.scale;
116
+ local.y = (Math.sin(angle) * dx + Math.cos(angle) * dy) / parent.scale;
117
+ }
118
+ if (patch.scale !== undefined)
119
+ local.scale = world.scale / parent.scale;
120
+ if (patch.rotate !== undefined)
121
+ local.rotate = world.rotate - parent.rotate;
122
+ command = { ...command, patch: { ...command.patch, transform: local } };
123
+ }
124
+ if (item.source.kind === 'group') {
125
+ if (command.patch.html !== undefined || command.patch.vars !== undefined || command.patch.params !== undefined) {
126
+ throw new Error(`グループアイテムには HTML 本文・vars・HTML params を書き戻せません: ${itemId}`);
127
+ }
128
+ if (!command.patch.transform)
129
+ return {};
130
+ item.transform = { ...recordOf(item.transform), ...command.patch.transform };
131
+ return { candidateText: stringifyEdit(edit) };
132
+ }
41
133
  }
42
134
  let htmlPath;
43
- let editChanged = false;
135
+ let editChanged = materialized;
44
136
  if (command.kind === 'overlay') {
45
137
  if (item.source.kind !== 'html' && item.source.kind !== 'shape') {
46
138
  throw new Error(`HTML/図形アイテムではありません: ${itemId}`);