akari-video 0.1.85 → 0.1.86

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.
Files changed (96) hide show
  1. package/package.json +1 -1
  2. package/vendor/.akari-capability-sources.json +7 -0
  3. package/vendor/docs/contract-2026-08-03-caption-display-encoding-qc-v1.md +4 -0
  4. package/vendor/docs/contract-2026-08-30-edit-json-v2-object-tree-v0.md +10 -0
  5. package/vendor/docs/contract-2026-09-02-shape-item-v0.md +2 -0
  6. package/vendor/docs/contract-2026-09-25-asset-license-axes-v0.md +26 -0
  7. package/vendor/docs/contract-2026-09-25-canvas-v0.md +22 -0
  8. package/vendor/docs/contract-2026-09-25-color-panel-v0.md +70 -0
  9. package/vendor/docs/contract-2026-09-25-image-ai-v0.md +31 -0
  10. package/vendor/docs/contract-2026-09-25-motion-layers-v0.md +60 -0
  11. package/vendor/docs/contract-2026-09-25-photo-edit-v0.md +43 -0
  12. package/vendor/docs/contract-2026-09-25-shape-item-v1.md +38 -0
  13. package/vendor/packages/akari-launcher/package.json +1 -1
  14. package/vendor/packages/asset-resolver/src/license-axes.mjs +41 -0
  15. package/vendor/packages/asset-resolver/src/shell-reference-sync.mjs +66 -0
  16. package/vendor/packages/asset-resolver/test/license-axes.test.mjs +53 -0
  17. package/vendor/packages/edit-lint/src/edit-lint.mjs +22 -9
  18. package/vendor/packages/edit-lint/src/engine-capabilities.json +23 -10
  19. package/vendor/packages/edit-lint/src/license-axes.mjs +41 -0
  20. package/vendor/packages/edit-lint/src/license-findings.mjs +72 -0
  21. package/vendor/packages/edit-lint/src/report.mjs +1 -1
  22. package/vendor/packages/edit-lint/src/shape-lane.mjs +9 -0
  23. package/vendor/packages/edit-store/lib/canonical.js +6 -2
  24. package/vendor/packages/edit-store/lib/caption-display.js +8 -1
  25. package/vendor/packages/edit-store/lib/caption-store.d.ts +2 -0
  26. package/vendor/packages/edit-store/lib/caption-store.js +14 -2
  27. package/vendor/packages/edit-store/lib/edit-v2-item-write.d.ts +22 -0
  28. package/vendor/packages/edit-store/lib/edit-v2-item-write.js +47 -7
  29. package/vendor/packages/edit-store/lib/edit-v2.d.ts +82 -5
  30. package/vendor/packages/edit-store/lib/edit-v2.js +125 -28
  31. package/vendor/packages/edit-store/lib/generated/edit-v2-keys.d.ts +4 -4
  32. package/vendor/packages/edit-store/lib/generated/edit-v2-keys.js +9 -3
  33. package/vendor/packages/edit-store/lib/group-flatten.d.ts +9 -0
  34. package/vendor/packages/edit-store/lib/group-flatten.js +83 -0
  35. package/vendor/packages/edit-store/lib/index.d.ts +1 -0
  36. package/vendor/packages/edit-store/lib/index.js +1 -0
  37. package/vendor/packages/edit-store/lib/internal-model.d.ts +8 -2
  38. package/vendor/packages/edit-store/lib/internal-model.js +70 -13
  39. package/vendor/packages/edit-store/lib/motion-keyframe-replace.d.ts +9 -0
  40. package/vendor/packages/edit-store/lib/motion-keyframe-replace.js +35 -0
  41. package/vendor/packages/edit-store/lib/motion-position-write.d.ts +6 -0
  42. package/vendor/packages/edit-store/lib/motion-position-write.js +26 -0
  43. package/vendor/packages/edit-store/lib/shape-bubble.d.ts +14 -0
  44. package/vendor/packages/edit-store/lib/shape-bubble.js +355 -0
  45. package/vendor/packages/edit-store/lib/shape-geometry.d.ts +28 -0
  46. package/vendor/packages/edit-store/lib/shape-geometry.js +198 -0
  47. package/vendor/packages/edit-store/lib/shape-markup-v1.d.ts +6 -0
  48. package/vendor/packages/edit-store/lib/shape-markup-v1.js +193 -0
  49. package/vendor/packages/edit-store/lib/shape-markup.d.ts +5 -1
  50. package/vendor/packages/edit-store/lib/shape-markup.js +8 -1
  51. package/vendor/packages/edit-store/lib/shape-preset.d.ts +17 -0
  52. package/vendor/packages/edit-store/lib/shape-preset.js +26 -0
  53. package/vendor/packages/edit-store/lib/shape-source-validation.d.ts +3 -0
  54. package/vendor/packages/edit-store/lib/shape-source-validation.js +202 -0
  55. package/vendor/packages/edit-store/lib/tree-ops.d.ts +22 -1
  56. package/vendor/packages/edit-store/lib/tree-ops.js +108 -5
  57. package/vendor/packages/edit-store/lib/webview-kernel.js +1261 -147
  58. package/vendor/packages/overlay-runtime/package.json +1 -1
  59. package/vendor/packages/overlay-runtime/src/handle-geometry.js +122 -0
  60. package/vendor/packages/overlay-runtime/src/interaction.js +382 -74
  61. package/vendor/packages/overlay-runtime/src/item-motion.d.ts +28 -0
  62. package/vendor/packages/overlay-runtime/src/item-motion.js +210 -0
  63. package/vendor/packages/overlay-runtime/src/motion-stroke.js +87 -0
  64. package/vendor/packages/overlay-runtime/src/overlay-runtime.js +18 -10
  65. package/vendor/packages/overlay-runtime/src/parts.d.ts +1 -0
  66. package/vendor/packages/overlay-runtime/src/parts.mjs +45 -10
  67. package/vendor/packages/schemas/asset-meta.schema.json +6 -0
  68. package/vendor/packages/schemas/bin/validate-captions.mjs +5 -1
  69. package/vendor/packages/schemas/bin/validate-edit.mjs +22 -0
  70. package/vendor/packages/schemas/captions.schema.json +1 -0
  71. package/vendor/packages/schemas/edit.schema.json +112 -11
  72. package/vendor/packages/schemas/engine-capabilities.json +23 -10
  73. package/vendor/packages/schemas/test/asset-license-axes.test.mjs +15 -0
  74. package/vendor/packages/schemas/test/edit-v2-schema.test.mjs +75 -0
  75. package/vendor/packages/schemas/test/engine-capabilities.test.mjs +1 -1
  76. package/vendor/packages/schemas/test/photo-regions-schema.test.mjs +24 -0
  77. package/vendor/packages/schemas/test/validate-captions.test.mjs +10 -0
  78. package/vendor/packages/schemas/test/validate-edit.test.mjs +43 -1
  79. package/vendor/presets/luts/INDEX.md +18 -0
  80. package/vendor/presets/luts/bake-previews.mjs +54 -0
  81. package/vendor/presets/luts/cinematic/preview.webp +0 -0
  82. package/vendor/presets/luts/contact-sheet.mjs +86 -0
  83. package/vendor/presets/luts/cool-clear/preview.webp +0 -0
  84. package/vendor/presets/luts/film-warm/preview.webp +0 -0
  85. package/vendor/presets/luts/forest-soft/preview.webp +0 -0
  86. package/vendor/presets/luts/index.jsonl +10 -10
  87. package/vendor/presets/luts/mono/preview.webp +0 -0
  88. package/vendor/presets/luts/natural/preview.webp +0 -0
  89. package/vendor/presets/luts/night-neon/preview.webp +0 -0
  90. package/vendor/presets/luts/preview-art.mjs +110 -0
  91. package/vendor/presets/luts/previews.test.mjs +138 -0
  92. package/vendor/presets/luts/reference-frame.webp +0 -0
  93. package/vendor/presets/luts/silver-retain/preview.webp +0 -0
  94. package/vendor/presets/luts/sunset-gold/preview.webp +0 -0
  95. package/vendor/presets/luts/vintage-fade/preview.webp +0 -0
  96. package/vendor/presets/luts/webp-lossless.mjs +200 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.85",
3
+ "version": "0.1.86",
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": {
@@ -72,6 +72,13 @@
72
72
  "docs/contract-2026-09-13-world-map-v0.md",
73
73
  "docs/contract-2026-09-24-caption-runs-v0.md",
74
74
  "docs/contract-2026-09-24-style-v0.md",
75
+ "docs/contract-2026-09-25-asset-license-axes-v0.md",
76
+ "docs/contract-2026-09-25-canvas-v0.md",
77
+ "docs/contract-2026-09-25-color-panel-v0.md",
78
+ "docs/contract-2026-09-25-image-ai-v0.md",
79
+ "docs/contract-2026-09-25-motion-layers-v0.md",
80
+ "docs/contract-2026-09-25-photo-edit-v0.md",
81
+ "docs/contract-2026-09-25-shape-item-v1.md",
75
82
  "packages/akari-launcher/package.json",
76
83
  "packages/akari-launcher/README.md",
77
84
  "packages/akari-tools/package.json",
@@ -197,3 +197,7 @@ are `partial_run`, never `production_run`.
197
197
  Executable specifications live in the edit-store, schemas, edit-lint, render-cut, preview-server,
198
198
  shell preview, and launcher test suites. The external A4 verifier accepts an explicitly supplied
199
199
  project root and independent manifest; absence of the frozen local fixture is a skip, not a pass.
200
+
201
+ ## 置いた文字の折り返し幅(2026-09-25 追記)
202
+
203
+ `captions[].text_style.wrap_width_pct` は出力画面幅に対する文字の折り返し幅(0 より大きく 100 以下)を表す。省略時は従来の幅を使う。左右の辺の操作はこの値だけを変更し、`scale` と `size_px` を変更しない。`max_width_pct` は座布団の幅、`max_characters` は文字数による改行であり、この値とは独立する。現在のプレビューは置いた文字(`time_domain: output`)に適用する。書き出し側の対応が入るまではプレビューと書き出しの幅が異なる。
@@ -54,6 +54,16 @@ updated: 2026-08-30
54
54
  - 中身の無い入れ物。`name` 任意。自身も `at` / `duration` / `transform` / `opacity` / `blend` / `keyframes` / `motion` を持つ(= 小さなコンポジション)
55
55
  - 「まとめる」(§3.2)が作るのはこれ。変形を持たないグループで包む → 座標・時間は不変
56
56
 
57
+ ### group の子の描画の規則(2026-09-25 追記)
58
+
59
+ - 子の絶対時刻は祖先の `at` の和。子自身の時間区間と全祖先の `[at, at + duration)` の積集合だけを描く。終端は含めない。
60
+ - 見た目は祖先から順に `transform` を合成する。位置には親の拡縮・回転を適用し、拡縮は乗算、回転は加算する。不透明度も祖先と子の積にする。
61
+ - 重なり順は親の段の位置に子の配列順を差し込む。入れ子も同じ規則で展開する。字幕の袋や、袋から出した行も親の段の位置を使う。
62
+ - media の子は描画用の旧ビューでは絶対時刻の layer に投影する。元の `edit.json` の木と素材ファイルは変更しない。
63
+ - captions の袋で `source.exclude` に含めた行を子の caption item として置いた場合、元の袋からは描かず、子を 1 回だけ描く。
64
+ - group 内の caption item は段直下と同じ字幕 HTML(字体、既定 fade、フォント参照)を使う。現行の既定フォント参照と fade は GPU 適格判定で `degraded` となる既存制約がある。`render-cut --engine auto` はどちらも OSR を選び、明示的な GPU 出口は適格性エラーで拒否する。group に入れたことを理由に字幕 HTML や判定を変えない。
65
+ - group 自身の `keyframes` / `motion` はこの静的な合成の対象外。動きが必要なら描画される子へ宣言する。
66
+
57
67
  ### 1.3 袋グループ — `html` / `captions` のアイテムに `items[]` を持たせたもの
58
68
 
59
69
  **袋 = 名札つき部品の入れ物**。HTML 断片の名札は `data-akari-part="<id>"`、`captions.json` の名札は行 `id`。将来の 3D(glTF ノード名)/ Lottie(レイヤー名)も同じ形で足す(本契約では未定義・語彙予約のみ)。
@@ -1,5 +1,7 @@
1
1
  # contract — 図形アイテム v0(edit.json v2 `shape` ソースとインライン SVG 降下)
2
2
 
3
+ 拡張された保存形と降下は [図形アイテム v1](./contract-2026-09-25-shape-item-v1.md) を参照。v0 データの描画は維持する。
4
+
3
5
  - 状態: 実装済み(データ契約 + edit-store 降下。パネル露出・インスペクター UI は後続)
4
6
  - 決定日: 2026-09-02
5
7
  - 実装: `packages/schemas/edit.schema.json`(`itemSourceShapeV2` / `itemV2Shape`)/
@@ -0,0 +1,26 @@
1
+ # 素材ライセンスの 2 軸 v0
2
+
3
+ 素材の `meta.json` の `license` に、商用利用の可否 `commercial`(`allowed` / `prohibited` / `unknown`)と帰属表示の要否 `attributionRequired`(`true` / `false` / `null`)を任意で加える。古い `scope`・`spdx`・`attribution_required` は読み取りで使い、書き換えない。`null` は判断できないことを表す。
4
+
5
+ ## 導出
6
+
7
+ | 宣言・旧値 | 商用利用 | 帰属表示の既定値 |
8
+ |---|---|---|
9
+ | `CC0-1.0`、`LicenseRef-AKARI-Assets-v0`、`LicenseRef-AKARI-Sounds-Terms-v0`、`MIT`、`OFL-1.1` | allowed | false |
10
+ | 版番号を持つ `CC-BY-*`(`CC-BY-SA-*`・`CC-BY-ND-*` など、NC を含まないもの) | allowed | true |
11
+ | `CC-BY-NC-*`(SA・ND との組み合わせを含む) | prohibited | true |
12
+ | その他、SPDX 識別子に `NC` 区切り語を含むもの | prohibited | 判別できなければ null |
13
+ | 旧 `scope: commercial-ok` | allowed | 未指定なら null |
14
+ | 旧 `scope: non-commercial` | prohibited | 未指定なら null |
15
+ | 旧 `scope: attribution` | allowed | true |
16
+ | `scope: paid-license-required` など未対応の scope と、判別できない SPDX の組み合わせ | unknown | 未指定なら null |
17
+ | `meta.json` はあるが `license` が無い・判別できない | unknown | 未指定なら null |
18
+ | `meta.json` が無い | 対象外(利用者の素材として所見を出さない) | 対象外 |
19
+
20
+ 旧 `attribution_required` の真偽値は帰属表示の軸へ写す(版番号を持つ CC-BY 系の帰属表示は true を優先)。新しい 2 軸があれば最優先する。SPDX 識別子の `NC` 区切り語による商用禁止は旧 `scope: commercial-ok` より優先する。未知の scope だけでは商用可否を決めず、既知の SPDX があればそちらの導出値を使う。どちらの軸も、もう一方を根拠に補完しない。
21
+
22
+ ## 書き出し前の表示
23
+
24
+ edit-lint は使用中の素材について `license.non-commercial` を warning、`license.unknown` と `license.attribution` を info で出す。各所見の `details` は `asset`・`name`・`credit` を持つ。クレジットは同じ素材ディレクトリの `CREDIT.txt` 先頭行を優先し、無ければ題名・作者・SPDX を組み立てる。書き出し画面は該当する行だけを表示し、名前の一覧を開閉できる。帰属表示が必要な行ではクレジットをコピーできる。これらの所見で書き出しは止めない。
25
+
26
+ 既存の `assets/` と `catalog/` のメタデータは `packages/asset-resolver/test/license-axes.test.mjs` で全件点検し、商用利用が unknown になるパスをテスト出力へ列挙する。元データは点検で変更しない。
@@ -0,0 +1,22 @@
1
+ # キャンバス v0
2
+
3
+ キャンバスは `edit.json` v2 の `source.kind: "group"` に付ける明示的な編集の器。画面では `canvas` の無い既存 group も「キャンバス」と呼ぶが、保存データは一律に変換しない。
4
+
5
+ ```json
6
+ {
7
+ "id": "canvas-1", "name": "導入", "at": 300, "duration": 150,
8
+ "source": { "kind": "group", "canvas": {
9
+ "origin": "user", "durationMode": "fixed", "intent": "最初に見せる内容",
10
+ "background": { "type": "color", "color": "#112244" }
11
+ } }, "items": []
12
+ }
13
+ ```
14
+
15
+ - `origin` は `user` または `plan`。v0 の `durationMode` は `fixed`。`content` は将来用に予約する。
16
+ - `background` は `none` または `color`。色は `#RRGGBB`。背景はキャンバス区間内で全面に表示し、子より下へ置く。音声は変えない。
17
+ - 空のキャンバスは区間を持つ仮枠。意図は空のときだけプレビューの点線枠に表示し、出力には焼き込まない。
18
+ - 子の `at` は親に対する相対時刻。親を動かすと子の絶対時刻も動く。出し入れでは各子の絶対時刻、見た目、重なり順を保つ。
19
+ - `fixed` の尺変更は表示範囲だけを変える。子の時刻・尺を変えず、親の区間外は描かない。このため `v2.child-in-parent` は警告とする。
20
+ - タイムラインの範囲端は整数フレームへ丸める。整数秒から 1 フレーム以内の端はその整数秒へ吸い付け、マウスのピクセル誤差で 0:10〜0:15 が 301/149 フレームにならないようにする。
21
+ - 同じ時刻に重なる item は自動で畳まず、トラックごとの行にそのまま並べる。重なった item をキャンバスにするのは利用者が「キャンバスにする」を押したときだけ。保存データを変えるのは明示の操作だけで、空になっても自動でほどかない。キャンバス行の見出し帯は字幕行と同じ高さ。畳んだキャンバスの子は見出し帯の上の印だけで示し、外の行には描かない。開くと子の行に表示する。
22
+ - 置いた字幕を子へ入れるときは `caption` item を作り、元の `captions` 袋の `source.exclude` に同じ字幕 id を入れて二重表示を防ぐ。移動後に外へ出すときは絶対時刻を保つ独立した `caption` item とし、`exclude` を維持する。
@@ -0,0 +1,70 @@
1
+ # 色パネル v0 契約(インスペクターの色の欄・色を作る窓・ブランドキット)
2
+
3
+ ## 位置と開き方
4
+
5
+ - 色の欄は**インスペクターの列の中**にある。色の行の丸(字幕の 文字 / 縁取り / 座布団 / 効果の色、オーバーレイの色のつまみ)を押すと、同じ列の中身が色パネルに切り替わる。左上の戻るボタンで元の列へ戻る。選択が変わると閉じる。
6
+ - 色番号の入力欄は行にも残る(直接打てる。プレビューからの「欄を開く」もこの入力欄へ焦点を当てる)。
7
+ - ほかの部品(上のバーの色の丸など)はコマンドで開く:
8
+
9
+ ```
10
+ akari.inspector.openColorPanel({
11
+ target: { kind: 'field', field: '<data-akari-field の値>' } // 今の選択のインスペクターの色の行
12
+ | { kind: 'item', itemId: '<item id>', path: '<ドット区切り>' }, // edit.json の item の中(例 source.params.fill)
13
+ allowGradient?: boolean, // 既定 false。図形・ラインの塗り / 枠 / 線だけ true
14
+ allowTransparent?: boolean, // 既定 false。塗りだけ true
15
+ title?: string, // 見出し(省略時は行の名前。「色」だけの行は「<節>の色」)
16
+ toggle?: boolean // 同じ対象で開いていたら閉じる(バーの色の丸をもう一度押したとき)
17
+ }) → boolean(開いたら true)
18
+ akari.inspector.closeColorPanel()
19
+ ```
20
+
21
+ - `item` の書き込みは edit-store の `updateItem` で行い、`path` の一番上の鍵(例 `source`)を丸ごと作り直して渡す(`updateItem` は source の中を浅くしか混ぜないため)。**書く前に `readEditV2` で読み直して検査し、その項目の契約が受け付けない値は書かずに断る**(例: 図形の契約がまだ文字列だけの版では、グラデーションを「この項目には、まだグラデーションを保存できません。」で断り、edit.json は変えない)。
22
+
23
+ ## 値の形
24
+
25
+ 図形の塗り・枠の契約(shape item v1)と同じ形を使う。
26
+
27
+ | 値 | 形 |
28
+ |---|---|
29
+ | 単色 | `#RRGGBB` か `#RRGGBBAA`(大文字にそろえる。AA = FF は落とす) |
30
+ | 透明(塗りだけ) | `'none'` = 中抜き |
31
+ | グラデーション | `{ type: 'linear', angle, stops }` / `{ type: 'radial', stops }`。`stops = [{ color: '#RRGGBB(AA)', offset: 0..1 }]`・2〜5 色・色ごとの透明度は color の AA |
32
+
33
+ - `angle` は CSS の `linear-gradient` と同じ向き(0 = 下から上・90 = 左から右・180 = 上から下)。
34
+ - 色を作る窓のスタイル 5 種はこの 2 型で表す: 横 = linear 90 / 縦 = linear 180 / 斜め ↘ = linear 135 / 放射 = radial / 斜め ↗ = linear 45。
35
+ - 色を足す・外すと `offset` は均等に振り直す(0, 1/(n-1), …, 1)。
36
+ - 純関数とテスト: `apps/shell/extensions/akari-annotations/src/browser/inspector/color-model.ts`。
37
+
38
+ ## パネルの並び(上から)
39
+
40
+ 1. 検索: 色の名前(「青」「あお」など・既定の単色の名前に当てる)/ 色番号(「#00c4cc」・3 / 6 / 8 桁)
41
+ 2. 虹の +(色を作る窓)・スポイト(`EyeDropper`。使えない環境では一言)・透明(`allowTransparent` のときだけ)・**使った色の履歴**(新しい順に 8 個・グラデーションも 1 つの丸・今の色に印。今の色が履歴に無ければ先頭に並べる)
42
+ 3. **このデザインの色**: edit.json と captions の器のファイルから、色らしい鍵(color / fill / stroke / background を含む)の値を使われている回数の多い順に最大 14
43
+ 4. **ブランドキット**: 「+ ブランドカラーを追加」で今の単色を入れる。「編集」で外す
44
+ 5. **写真の色**: 置いた画像(どのトラックでも)と B-roll(一番上の visual トラック以外の動画)から、最大 4 本・各 5 色。画素を 64px に縮め、各チャンネル 4bit の箱で数えて多い順に「既に選んだ色と十分に離れた色」を拾う(決定論)
45
+ 6. **デフォルトの単色**: 4 段 28 色(すべて表示で 6 段 42 色)
46
+ 7. **デフォルトのグラデーション**(`allowGradient` のときだけ): 3 段 21 種(すべて表示で 5 段 35 種)
47
+
48
+ 押した色はその場で書き込み、パネルは開いたまま。色の四角・色相・透明度の帯のドラッグは見た目だけ先に動かし、**離したときに 1 回だけ書く**。書き込みに失敗したら元の値へ戻して理由を出す。
49
+
50
+ ## 色を作る窓
51
+
52
+ - タブ 単色 / グラデーション(`allowGradient` のときだけタブを出す)。
53
+ - 単色: 色の四角(鮮やかさ × 明るさ)・色相の帯・色番号・スポイト。
54
+ - グラデーション: 色の丸(+ で足す・最大 5 色)・スタイル 5 種。色の丸を押すと、その色だけを直す**小窓**(色の四角・色相・透明度の帯・ゴミ箱〔2 色のときは押せない〕・色番号 + 透明度 %・スポイト)。小窓は **外を押す / Esc / 同じ丸をもう一度** で閉じ、中でドラッグしている間は閉じない。
55
+
56
+ ## 保存の置き場
57
+
58
+ | もの | 置き場 | 範囲 |
59
+ |---|---|---|
60
+ | ブランドキット | `AKARI_HOME/brand-kit.json`(`{ schema: 'akari-brand-kit/v0', colors: ['#RRGGBB(AA)'] }`・最大 60 色) | 利用者ごと。どのプロジェクトからも同じ 1 つ(★ お気に入りと同じ置き場)。ライブラリ › マイ › ブランドキット も同じファイルを読む |
61
+ | 使った色の履歴 | アプリのローカル保存(`akari.colorPanel.history.v0`) | 利用者ごと |
62
+ | 色そのもの | 各項目の保存先(字幕 = captions の textStyle・item = edit.json) | プロジェクト |
63
+
64
+ ブランドキットの読み書きは akari-project が次のコマンドで出す(拡張をまたぐので文字列の id で呼ぶ):
65
+ `akari.library.brandKit.get()` → `string[]` / `akari.library.brandKit.addColor(color)` → `string[]` / `akari.library.brandKit.removeColor(color)` → `string[]`。
66
+
67
+ ## 今の版でつないでいる所・まだの所
68
+
69
+ - つないでいる: 字幕の色の行(文字・縁取り・座布団・効果の色。グラデーション・透明は出さない)、オーバーレイの色のつまみ(単色)。
70
+ - まだ: 図形の塗り・枠・線(上のバーの色の丸・インスペクターの図形の行)。図形の契約がグラデーションと `'none'` を受け付けた後に、`{ kind: 'item', itemId, path: 'source.params.fill' | 'source.params.stroke' }` と `allowGradient: true`(塗りは `allowTransparent: true` も)で呼ぶ。
@@ -0,0 +1,31 @@
1
+ # 画像の AI v0 契約
2
+
3
+ ## 設定と秘密
4
+
5
+ - 設定の「画像の AI」は 1 サービス・1 キー。試作の既定サービスは `fal`。実行口の `imageAi.provider` を 1 か所で差し替えられる。
6
+ - 専用キーは `AKARI_IMAGE_AI_FAL_KEY`。読み上げの `FAL_KEY` が登録済みなら「同じキーを使う」を選べる。その選択は `AKARI_IMAGE_AI_USE_NARRATION_KEY=1` として記録する。専用キーを保存すると共有選択を解除する。
7
+ - いずれも既存の `credentials.env` 保存経路を使う。キーは node 側だけで読み、レンダラー、`edit.json`、由来ファイル、ログへ渡さない。
8
+ - 「接続を確かめる」は fal の読み取り専用 API に実際に GET を送り、HTTP 応答で判定する。キーが空でないことだけでは成功にしない。
9
+
10
+ ## 実行口とモデル
11
+
12
+ `ImageAiService` は `upscale(input)` と `generateBackground(input, mask?)` を公開し、provider 実装は `ImageAiProvider` に隔離する。v0 の実装は fal 1 本。
13
+
14
+ | 道具 | fal モデル ID | 入力 | 状態 |
15
+ |---|---|---|---|
16
+ | 高画質化 | `fal-ai/clarity-upscaler` | `image_url`、`upscale_factor: 2` | 利用可能 |
17
+ | 背景生成 | `fal-ai/flux-pro/v1/fill` | `image_url`、`prompt`、`mask_url` | 実行口のみ。画面は「近日」 |
18
+
19
+ 高画質化モデルの [公式 API 仕様](https://fal.ai/models/fal-ai/clarity-upscaler/api) は上記 ID・入力・データ URI・キュー API を示す。[公式モデルページ](https://fal.ai/models/fal-ai/clarity-upscaler) の表示料金は $0.03/MP。画面の目安は 2 倍の拡大による出力画素数 4 倍を基準に計算する。背景生成はマスク必須の [公式 API 仕様](https://fal.ai/models/fal-ai/flux-pro/v1/fill/api) を参照する。料金は変わりうるため画面では「目安」と明示する。
20
+
21
+ ## 高画質化の状態遷移
22
+
23
+ 1. 写真の項目を選び「高画質化」を開く。node 側が対象 ID、元素材の SHA-256、対象の宣言の版、画像のバイト数・寸法を調べる。既存の別案も `assets/generated/*.meta.json` から探す。
24
+ 2. 送信前に画像の寸法・容量、送信先、料金の目安を表示する。キーと画像の寸法を確認できない場合は送信を無効にする。キーがなければ「設定を開く」を表示する。
25
+ 3. 明示操作で fal キューへ送る。処理中は取り消せる。取り消しはローカル待機を止め、キューの取り消し口が返された場合はそこにも依頼する。処理開始後の課金取り消しは保証しない。
26
+ 4. 成功結果を `assets/generated/<sha256>.<ext>` に保存する。同名の `<sha256>.<ext>.meta.json` は provider、model、`item_id`、操作種別、入力 SHA-256、対象だけの編集版、パラメータ、作成日時を持つ。ファイルは上書きしない。結果の一時 URL は保存しない。
27
+ 5. 結果は選択中の項目に「別案」として提示する。再読み込み後も、`item_id`・現在の素材の入力 SHA-256・対象だけの編集版が一致する保存済みの別案を提示する。既存の案は再送信せず「この案にする」で採用できる。採用時に初めて `source` を差し替える。元素材は残り、この編集は undo 1 回で戻せる。
28
+
29
+ 編集の版は、対象 item の `id`・`source` と参照先の `sources` の行だけをハッシュ化する。別クリップの移動など、対象と無関係な編集は別案の採用を妨げない。採用前には対象 ID・元素材ハッシュ・この限定した版を再照合し、対象の差し替え・削除で古くなった案は自動適用しない。失敗時には分かる範囲で課金状況の不確実性、再試行、キー無効時の「設定を開く」を表示する。
30
+
31
+ 生成画像の保存形式と編集宣言はサービスに依存しない。サービス名は由来の `provider` にだけ残す。端末内の画像編集はこのキーを要求しない。
@@ -0,0 +1,60 @@
1
+ # 動きの重ね方 v0
2
+
3
+ ## 1. 時刻と保存
4
+
5
+ 評価時刻 `t` は出力動画の秒。要素と親キャンバスはそれぞれ自分の `at` を引いた秒を使う。`keyframes[].t` は保存時にはその要素の開始からのフレーム。描画経路では秒またはフレームの単位を明示して評価する。`motion.in/out.duration` と `motion.loop.period` はフレームで、出力 fps で秒へ換算する。再生とプレイヘッド移動は `edit.json` を変更しない。
6
+
7
+ ## 2. 合成
8
+
9
+ 順序は「静的値またはキーフレームの補間値 → 要素自身の登場・強調・退場 → 内側から外側へ親キャンバスの動き」。各軸は個別に補間し、その軸に点が無ければ静的値を使う。点の前後では端点の値を保つ。
10
+
11
+ | 値 | 基礎値 | 自分の動き | 親キャンバス |
12
+ |---|---|---|---|
13
+ | 位置 X・Y | 各軸の補間値または静的値 | 出力 px のずれを足す | 子の評価後に親の拡縮・回転をかけ、親の位置を足す |
14
+ | 拡縮 | 補間値または静的値 | 倍率を掛ける | 倍率を掛ける |
15
+ | 回転 | 補間値または静的値(度) | 角度を足す | 角度を足す |
16
+ | 不透明度 | 補間値または静的値 | 係数を掛ける | 係数を掛け、最終値を 0〜1 に収める |
17
+ | 見た目の切り抜き | 静的クロップ | ワイプの表示範囲 | 子の位置評価後に親を合成する |
18
+
19
+ 変形は列ベクトルで、要素の中心を原点として **拡縮 → 回転 → 平行移動** の順。親の位置と回転は出力キャンバスの座標系で、子の位置は親の局所座標系。親が複数なら子に近い親から順に適用する。親の静的変形を先に焼き込んでから動きだけを足すことはしない。
20
+
21
+ ## 3. 編集操作
22
+
23
+ 「動きを描く」はドラッグした道筋を間引き、要素の X・Y のキーフレームとして保存する入力方法。独立した動きの層は増やさない。既存の X・Y の点があるときは、置換する時刻範囲を保存前に示す。ひと筆の確定は一つの undo 操作にする。
24
+
25
+ 直接ドラッグは表示位置を基準にし、親の平行移動・逆回転・逆拡縮、自分の動きの位置ずれの逆順で基礎位置へ戻してから書く。評価関数と逆算関数は宣言を変更しない。
26
+
27
+ ## 4. プリセットが変える値
28
+
29
+ `in` は先頭、`out` は末尾、`loop` は表示中に繰り返す。退場は登場の進み方を末尾から逆に評価する。`amount` はその種類の距離・倍率・角度・強さを指定し、`ease` は区間内の進み方を変える。
30
+
31
+ | 段 | 種類 | 位置(足す) | 拡縮(掛ける) | 回転(足す) | 不透明度・表示窓 |
32
+ |---|---|---|---|---|---|
33
+ | 登場・退場 | フェード `fade` | — | — | — | 不透明度 0〜1 を掛ける |
34
+ | 登場・退場 | 4 方向のスライド `slide-*` | 既定 40 px | — | — | — |
35
+ | 登場・退場 | 拡縮 `scale` | — | 既定 0.8〜1 | — | — |
36
+ | 登場・退場 | ワイプ `wipe` | — | — | — | 残る表示窓を 0〜全体へ変える |
37
+ | 登場・退場 | ポップ `pop` | — | 既定 0.25〜1 | — | 不透明度 0〜1 を掛ける |
38
+ | 登場・退場 | ズーム `zoom` | — | 既定 1.55〜1 | — | 不透明度 0〜1 を掛ける |
39
+ | 登場・退場 | 回転 `twirl` | — | 0.35〜1 | 既定 −200〜0 度 | 不透明度 0〜1 を掛ける |
40
+ | 強調 | 脈動 `pulse` | — | 正弦波の倍率 | — | — |
41
+ | 強調 | 浮遊 `float` | Y に正弦波のずれ | — | — | — |
42
+ | 強調 | 回り続ける `spin` | — | — | 1 周の角度 | — |
43
+ | 強調 | 点滅 `blink` | — | — | — | 既定で 1 / 0.25 を掛ける |
44
+ | 強調 | 小刻みな動き `jiggle` | X に短い正弦波のずれ | — | 短い正弦波の角度 | — |
45
+
46
+ ## 5. 親子の時計と逆算
47
+
48
+ 子の登場は子の `at` から、キャンバスの登場はキャンバスの `at` から進む。入れ子の子の `at` は保存時には直上の親に対する相対フレームで、評価時には祖先を足した出力時刻へ直す。例: 30 fps でキャンバスが出力 2 秒から、子がその 1 秒後から始まるなら、出力 3.5 秒では子の時計は 0.5 秒、キャンバスの時計は 1.5 秒。子の登場が終わっていてもキャンバスの強調は続く。
49
+
50
+ 見えている位置を `V`、親の位置を `P`、親の等比倍率を `S`、親の回転を `R(θ)`、自分の動きの位置ずれを `Δ` とすると、親が 1 つのとき書き込む基礎位置は **`B = R(−θ) × (V − P) / S − Δ`**。親が複数なら外側から順に逆変形して最後に自分の `Δ` を引く。ドラッグ中は `V` を追い、確定時だけ `B` を書く。倍率が 0 の親は逆算できないので書き込みを拒む。
51
+
52
+ ## 6. 道筋から点への変換
53
+
54
+ 「動きを描く」ではプレビューに道筋の線を示す。出力 px の距離による Ramer–Douglas–Peucker の間引きで最大 32 点へ収め、X・Y のキーフレームにする。時刻は描いた経過時間に従う「速さ」方式を画面の既定とし、純関数には点へ均等に割り当てる方式もある。開始はプレイヘッドから要素の開始を引いたフレーム、終点は要素の尺内に収める。
55
+
56
+ 既存の X・Y の点があるときは描く前にその時刻範囲を示し、描いている間は置換予定の範囲を更新する。確定時は描いた範囲内の X・Y だけを置き換え、範囲外の位置の点と回転・不透明度など他の項目を保つ。一筆の保存は一つの undo 操作。字幕は対象外。
57
+
58
+ ## 7. 読み取り専用の再生と全描画経路
59
+
60
+ 再生・停止・プレイヘッド移動は読み取り専用。書くのは人による設定変更・直接ドラッグの確定・道筋の確定だけ。シェルと Web のプレビュー、frame-engine の動画・写真、render-cut の HTML シート、gpu-export の DOM とシートは同じ `evaluateItemMotion(item, t, parentChain)` を呼ぶ。各経路は `motionSource`、近い親から外側へ並べた `motionParents`、キーフレームの時刻単位、出力 fps を渡す。経路ごとに別の合成式を持たない。
@@ -0,0 +1,43 @@
1
+ # 写真の非破壊編集 v0
2
+
3
+ 写真の編集は `edit.json` v2 の media item に宣言する。線と採用マスクは、EXIF の向きを直した素材の切り抜き前の座標に結び付く。
4
+
5
+ ## 保存
6
+
7
+ - `mask` は `sources[].id`。静止画では白が表示、黒が透明の 8bit グレー PNG を参照する。動画では従来のグレースケール動画を参照する。
8
+ - 採用マスクは `assets/masks/<sha256>.png` に作り、上書きしない。同名の `.meta.json` に `inputSha256`, `engine`, `request`, `os`, `parameters`, `createdAt`, `width`, `height` を記録する。
9
+ - `erase` は順序を持つ線の配列。各線は `{ mode: "erase" | "restore", points: [[x,y], …], size, hardness }`。点は素材の幅・高さを 1 とする座標、`size` は素材短辺に対する直径の比、`hardness` は 0..1。
10
+ - `flip` は `{ h?: boolean, v?: boolean }`。切り抜き後、素材から画面への写像で適用する。
11
+ - `crop` は既存の `{x,y,w,h}`(EXIF 補正後の素材の 0..1 座標)に `rotate?: number` を加える。`rotate` は −45..45 度、時計回りの写真補正で、切り抜き窓の中心を軸に**素材を回してから窓で切る**。配置の `transform.rotate` とは独立する。窓が素材外を参照した画素は透明にする。縦横比の選択は編集 UI の状態であり、保存値は窓の四数値だけに展開する。
12
+ - 写真の `frame` は `{ stroke?: { color: "#RRGGBB", width: 0..100 }, cornerRadius?: 0..100 }`。線幅は出力幅 1920px 基準の px(出力幅に比例)で、見える端の内側に描く。`cornerRadius` は短辺の半分に対する割合で、伸縮後の実寸で真円の角を計算する。省略時は枠も角丸も無い。
13
+ - 合成したラスタは消去可能なキャッシュであり、保存の正本ではない。再読込・書き出しは PNG と宣言から再構成する。
14
+
15
+ ## 処理の順
16
+
17
+ **色 → マスク → 消しゴムの線 → 切り抜き → 反転 → 仕上げ**で固定する。色は画像全体の item `adjust` を先に適用し、続けて `regions` の補正・ぼかし・フィルターを配列順に重ねる。マスクには背景透過と `maskFeather` を含む。切り抜きには `crop.rotate` を含む。仕上げは写真の `frame`(枠線・角の丸み)である。
18
+
19
+ ## 合成
20
+
21
+ `composeStillMask` は整数演算で各線を記載順に合成する。マスクが無い場合の初期値は全面 255。消す線は透明へ、戻す線は元画像のアルファを上限として表示へ寄せる。マスクの読み込みでは色変換を行わない。画像、マスク、線は同じ素材座標で読み、切り抜き(回転を含む)と反転はその後に適用する。枠線と角の丸みは反転後の窓の見える端に適用し、マスク・消しゴムの透明度を維持する。
22
+
23
+ ## 端末で背景を消す
24
+
25
+ 対応する Mac では Apple Vision の前景インスタンスを使い、古い macOS では人物の分離を使う。推論の出力は確定した PNG として保存し、素材のハッシュまたは item id が変わった結果は適用しない。補助が使えない場合はアプリを続行し「この Mac では使えません」と表示する。保存済み PNG の再生には補助もネットワークも要らない。
26
+
27
+ ## 背景透過とエリアの層
28
+
29
+ - `maskFeather?: number` はマスク境界のなめらかさ(素材画素で 0..100)。`composeStillMask` がマスクをぼかした後で `erase` の線を順に重ねる。省略時は 0。
30
+ - `regions?: [{ id, name?, maskRef, invert?, enabled?, adjust?, filter?, blur? }]` は静止画 media item だけの順序付き配列(最大 32)。`id` は item 内で一意。`name` は候補から採用した表示名(非空文字列)で、省略時は「エリア」の番号を表示する。`maskRef` は `sources[].id` で、元写真と同じ向き・寸法の 8bit グレー PNG を指す。`invert: true` はマスクの反対側、`enabled: false` は値を残して描画だけを止める。`adjust.basic` は露出・コントラスト・彩度・色温度の 4 項目。`blur` は 0..50 画素。`filter` は `{ lut: id, intensity?: 0..1 }` で、id と画面名は既存の LUT 棚に合わせる。
31
+ - 画像全体の補正は従来の同じ item の `adjust` が正本。「色」と「写真を編集」の画像全体はここを共用し、別の全体補正を持たない。`output.look` は別の動画全体の設定で、この窓から変更しない。エリア補正は画像全体の補正後に `regions` の配列順で適用する。
32
+ - 選んだエリアのプリセット LUT は `assets/luts/photo-region/<id>.cube` に初回使用時に写し、そこを不変の参照先として描画する。元の `presets/luts` は変更しない。採用したマスクと LUT があれば、推論キャッシュを消しても再生・書き出しを再構成できる。
33
+
34
+ ## 候補と確定物
35
+
36
+ Vision の「自動」は前景全体と被写体ごとの候補、「人物だけ」は人物全体と人物ごとの候補を返す。候補の表示名は「人物 1」「被写体 1」のような番号だけで、写真の内容を推測した名前を付けない。候補は `.akari/cache/photo-masks/<入力 SHA-256>/` に置き、押して採用した候補、または選択した候補の合成結果だけを `assets/masks/<PNG SHA-256>.png` と同名の `.meta.json` に確定する。複数候補は画素ごとの最大値で合成し、「人物以外」はその反転を確定する。meta には元素材のハッシュ、エンジン、要求、OS、パラメータ、日時、寸法を残す。
37
+
38
+ クリック分割は Apple 配布の SAM 2.1 tiny Core ML float16(画像・点・マスクの 3 モデル、合計約 79.7 MB)を使う。配布元は [Apple のモデルカード](https://huggingface.co/apple/coreml-sam2.1-tiny)、ライセンスは Apache-2.0。アプリにはモデルを同梱しない。初回にモデルカードの 3 つの `.mlpackage` を取得してユーザーの `~/Library/Caches/AKARI Video/photo-models/sam2.1-tiny/` に置き、コンパイル済みの `.mlmodelc` もその下の `compiled/` に置く。事前に置く場合は各 `.mlpackage` の `Manifest.json`、`Data/com.apple.CoreML/model.mlmodel`、`Data/com.apple.CoreML/weights/weight.bin` を同じ階層にそろえる。補助プロセスは `.cpuAndGPU` だけでモデルを一度読み、写真選択後に特徴量を準備し、クリック時は点とマスクを実行する。EXIF の向きを適用した PNG を画像エンコーダへ渡し、画像制約に従う 1024 入力への変換と 0..1 のクリック位置×1024 は同じ素材座標を使う。256 画素のロジットは元写真寸法へ双線形で拡大してから 0 で二値化する。3 候補は面積順で「狭く / 中間 / 広く」を選べる。ホバーは既に得た PNG の表示だけを行う。取得・推論が使えない Mac では編集を続け、「この Mac では使えません」と示す。
39
+
40
+ 候補を返す時と採用する時は item id、素材 id・URI、元素材 SHA-256、編集の版を照合する。差し替え・削除・途中の編集後に届いた結果は書き込まない。
41
+ ## 切り抜きの分析
42
+
43
+ 切り抜きの「自動水平」は端末上の水平線検出から角度を得る。見つからない場合は 0 度を使う。「スマート切り抜き」は前景インスタンスマスク(対応しない Mac では人物分離)の重心と外接矩形を優先し、得られない場合だけ注目領域を使う。選んだ縦横比を保ったまま窓を広げて主役の大半を収め、重心を三分割の縦線へ寄せる。いずれも分析結果のラスタは保存せず、確定時の `crop` だけを edit.json に書く。
@@ -0,0 +1,38 @@
1
+ # contract — 図形アイテム v1
2
+
3
+ - 決定日: 2026-09-25
4
+ - 保存先: edit.json v2 の `source.kind: "shape"`
5
+ - 降下先: edit-store の決定論的なインライン SVG HTML オーバーレイ
6
+
7
+ ## 1. 保存形
8
+
9
+ 図形の形は `source.shape` と `source.params` に置き、位置・拡縮・回転・時間は item 共通の値を使う。棚から配置するときは形状と既定値を **値でコピー** する。`params.preset` は由来の ID であり、描画時の参照先ではない。
10
+
11
+ | 型 | 保存する値 |
12
+ | --- | --- |
13
+ | `path` | `params.path: {d, vb:[width,height], rule?}`。`d` は絶対座標の `M/L/C/Z` のみ。`rule` は `nonzero` または `evenodd` |
14
+ | 元の形 + 丸み | `shape: "path"` の `params.cornerRadius` だけを割合 0〜100(100 = 短辺の半分)として、直線同士の角を配置後の実寸 px で丸める。v0 の形名では `rounded-rect` だけが従来どおり px で丸まり、ほかの形名の同名値は描画に影響しない |
15
+ | `line` | `dash: solid/dash/dot`、`startCap` / `endCap: none/triangle/chevron/bar/square/circle/diamond`、各 `*CapFilled`、`lineCap: butt/round`。`arrow` は終端三角の別名 |
16
+ | `bubble` | `style: ellipse/rounded/rect/jagged/burst/cloud/wobble`、`count`(4〜48)、`depth`、`jitter`、`seed`、`tail: point/dots/none`、`tailAngle`(0〜360)、`tailLength`、`tailWidth`、`tailCurve`、`dash` |
17
+
18
+ `fill` / `stroke` は `#RRGGBB`、`#RRGGBBAA`、`none`、または `{type:"linear",angle,stops:[{color,offset}]}` / `{type:"radial",stops:[{color,offset}]}`。グラデーションは 2〜5 色、offset は 0〜1 で昇順。透明度は各色の AA に持つ。`strokeWidth` は 0〜100(1920px 幅基準で出力幅に比例)。閉じた形の枠は輪郭の内側だけに描き、開いた線は中心線のままにする。
19
+
20
+ 四角を丸めたいときは、棚の四角を `path` として値で写し、`params.cornerRadius` を付ける。
21
+
22
+ 棚の既定値は形 = `#a6a6a6` 塗り・枠なし、線 = `#000000`・4px、吹き出し = 白塗り・黒枠 5px。旧データは v0 の既定値と SVG 文字列を維持する。v1 の値は新しい型・フィールドを持つ item で適用する。
23
+
24
+ ## 2. 降下と外形
25
+
26
+ path は棚の viewBox 余白を外して描画領域へ写す。角丸・吹き出しは実寸の座標で作り直す。SVG の viewBox に追加の余白を置かず、見える端を選択枠と吸着の基準にする。開いた線・閉じた形・吹き出しで同じ線種を使い、点線は角形の点(長さ = 見える太さ)、破線は長さ = 見える太さの 3 倍、どちらも間隔 = `max(見える太さ×2,3px)`。端のパーツは端点から内側へ向け、見える端を動かさない。
27
+
28
+ item の非一様な拡大は `sqrt(scaleX×scaleY)` で線幅・線種・端の寸法を補正する。方向ごとの幅差は残るが、プレビューと書き出しは同じ SVG の拡大結果を描く。
29
+
30
+ グラデーションと内側枠線の SVG ID は item ごとに決定論的に分離する。SVG 出力は同じ宣言に対してバイト同一とする。プレビューは HTML オーバーレイへ降下する。GPU 出口の SVG 適格性はこの契約では変更せず、書き出しは既存の OSR フォールバックを利用する。
31
+
32
+ ## 3. 棚
33
+
34
+ `presets/shapes/index.jsonl` は 1 行 1 件で、`id/category/name/vb/d/kind/rule?/rounded_from?/defaults` を持つ。吹き出しは `kind: "bubble"` と params、線は `kind: "line"` と params を持つ。角丸のプリセットは `rounded_from: {base,radius}` を持ち、配置時は元の形と丸みをコピーする。生成スクリプトで同じバイト列を再生成できる。
35
+
36
+ ## 4. 後続
37
+
38
+ 棚の UI、配置操作、点編集、形の時間変化、GPU 適格化は別契約で扱う。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.85",
3
+ "version": "0.1.86",
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": [
@@ -0,0 +1,41 @@
1
+ const ALLOWED_COMMERCIAL = new Set(['allowed', 'prohibited', 'unknown']);
2
+ const KNOWN_FREE = new Set([
3
+ 'CC0-1.0', 'LicenseRef-AKARI-Assets-v0', 'LicenseRef-AKARI-Sounds-Terms-v0',
4
+ 'MIT', 'OFL-1.1',
5
+ ]);
6
+
7
+ /** Derive the two independent questions without rewriting older metadata. */
8
+ export function deriveLicenseAxes(license) {
9
+ if (!license || typeof license !== 'object') {
10
+ return { commercial: 'unknown', attributionRequired: null };
11
+ }
12
+ const spdx = typeof license.spdx === 'string' ? license.spdx.trim() : '';
13
+ const scope = typeof license.scope === 'string' ? license.scope.trim().toLowerCase() : '';
14
+ let commercial = 'unknown';
15
+ let attributionRequired = null;
16
+
17
+ if (KNOWN_FREE.has(spdx)) {
18
+ commercial = 'allowed';
19
+ attributionRequired = false;
20
+ } else if (/^CC-BY(?:-[A-Z]+)*-\d+(?:\.\d+)?$/i.test(spdx)) {
21
+ commercial = /(?:^|-)NC(?:-|$)/i.test(spdx) ? 'prohibited' : 'allowed';
22
+ attributionRequired = true;
23
+ } else if (/^CC-BY-NC(?:-|$)/i.test(spdx)) {
24
+ commercial = 'prohibited';
25
+ attributionRequired = true;
26
+ }
27
+
28
+ if (scope === 'commercial-ok' || scope === 'attribution') commercial = 'allowed';
29
+ if (scope === 'non-commercial') commercial = 'prohibited';
30
+ if (/(?:^|-)NC(?:-|$)/i.test(spdx)) commercial = 'prohibited';
31
+ if (scope === 'attribution') attributionRequired = true;
32
+ if (typeof license.attribution_required === 'boolean') {
33
+ attributionRequired = license.attribution_required;
34
+ }
35
+ if (/^CC-BY(?:-[A-Z]+)*-\d+(?:\.\d+)?$/i.test(spdx)) attributionRequired = true;
36
+ if (ALLOWED_COMMERCIAL.has(license.commercial)) commercial = license.commercial;
37
+ if (typeof license.attributionRequired === 'boolean' || license.attributionRequired === null) {
38
+ attributionRequired = license.attributionRequired;
39
+ }
40
+ return { commercial, attributionRequired };
41
+ }
@@ -0,0 +1,66 @@
1
+ // Synchronous counterpart of shell-reference.mjs's resolveProjectAssetPath.
2
+ // It additionally confines library files to the ledger entry's id directory,
3
+ // matching the app's asset boundary.
4
+ import { lstatSync, readFileSync, realpathSync, statSync } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { resolveAssetLibraryRoots } from '../../creator-root/src/index.mjs';
7
+ import { resolveLibraryFallback } from './project-references.mjs';
8
+
9
+ const within = (root, target) => {
10
+ const relative = path.relative(root, target);
11
+ return relative === '' || (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
12
+ };
13
+
14
+ export function declaredProjectAssetPath(declaredPath) {
15
+ if (typeof declaredPath !== 'string' || !declaredPath) throw new Error('素材パスがプロジェクトの外を指しています');
16
+ const normalized = declaredPath.replaceAll('\\', '/');
17
+ if (path.isAbsolute(normalized) || path.win32.isAbsolute(normalized)
18
+ || normalized.split('/').some(part => part === '..' || part === '.')) {
19
+ throw new Error('素材パスがプロジェクトの外を指しています');
20
+ }
21
+ return normalized;
22
+ }
23
+
24
+ function readReferencesSync(projectDir) {
25
+ // Sync counterpart of readProjectReferences: generation adapters resolve media synchronously.
26
+ try {
27
+ const value = JSON.parse(readFileSync(path.join(projectDir, '.akari', 'asset-references.json'), 'utf8'));
28
+ return value?.version === 0 && Array.isArray(value.references) ? value.references : [];
29
+ } catch { return []; }
30
+ }
31
+
32
+ /** Synchronous counterpart of resolveProjectAssetPath for generation adapters. */
33
+ export function resolveProjectAssetPathSync(project, declaredPath, env = process.env) {
34
+ const normalized = declaredProjectAssetPath(declaredPath);
35
+ const root = realpathSync(project);
36
+ const local = path.resolve(root, normalized);
37
+ if (!within(root, local)) throw new Error('素材パスがプロジェクトの外を指しています');
38
+
39
+ // Match shell-reference: an invalid local entry must not fall through to the library.
40
+ let exists = false;
41
+ try { lstatSync(local); exists = true; } catch (error) { if (error.code !== 'ENOENT') throw error; }
42
+ let ancestor = path.dirname(local);
43
+ while (within(root, ancestor)) {
44
+ try {
45
+ if (!within(root, realpathSync(ancestor))) throw new Error('素材パスがプロジェクトの外を指しています');
46
+ break;
47
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
48
+ ancestor = path.dirname(ancestor);
49
+ }
50
+ if (exists) {
51
+ const actual = realpathSync(local);
52
+ if (!within(root, actual)) throw new Error('素材パスがプロジェクトの外を指しています');
53
+ return statSync(actual).isFile() ? actual : null;
54
+ }
55
+
56
+ const references = readReferencesSync(root);
57
+ const segments = normalized.split('/');
58
+ for (const akariAssetsDir of resolveAssetLibraryRoots(env).read) {
59
+ const found = resolveLibraryFallback({ declaredPath: normalized, references, akariAssetsDir });
60
+ if (!found) continue;
61
+ // The referenced asset's id directory is the containment boundary.
62
+ const idDirectory = path.join(realpathSync(akariAssetsDir), segments[1], segments[2]);
63
+ if (within(idDirectory, found)) return found;
64
+ }
65
+ return null;
66
+ }
@@ -0,0 +1,53 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readdir, readFile } from 'node:fs/promises';
3
+ import { join, resolve } from 'node:path';
4
+ import test from 'node:test';
5
+ import { deriveLicenseAxes } from '../src/license-axes.mjs';
6
+
7
+ const cases = [
8
+ [{ spdx: 'CC0-1.0' }, 'allowed', false],
9
+ [{ spdx: 'LicenseRef-AKARI-Assets-v0' }, 'allowed', false],
10
+ [{ spdx: 'LicenseRef-AKARI-Sounds-Terms-v0' }, 'allowed', false],
11
+ [{ spdx: 'MIT' }, 'allowed', false],
12
+ [{ spdx: 'OFL-1.1' }, 'allowed', false],
13
+ [{ spdx: 'CC-BY-4.0' }, 'allowed', true],
14
+ [{ spdx: 'CC-BY-SA-4.0' }, 'allowed', true],
15
+ [{ spdx: 'CC-BY-NC-4.0', scope: 'commercial-ok' }, 'prohibited', true],
16
+ [{ spdx: 'CC-BY-NC-4.0', attribution_required: false }, 'prohibited', true],
17
+ [{ scope: 'commercial-ok' }, 'allowed', null],
18
+ [{ scope: 'non-commercial' }, 'prohibited', null],
19
+ [{ scope: 'commercial-ok', attribution_required: false }, 'allowed', false],
20
+ [{ scope: 'attribution' }, 'allowed', true],
21
+ [{ spdx: 'unknown', scope: 'unknown' }, 'unknown', null],
22
+ [{ spdx: 'unknown', attribution_required: true }, 'unknown', true],
23
+ [{ spdx: 'CC0-1.0', commercial: 'prohibited', attributionRequired: true }, 'prohibited', true],
24
+ [{ spdx: 'CC-BY-4.0', commercial: 'unknown', attributionRequired: null }, 'unknown', null],
25
+ [null, 'unknown', null],
26
+ ];
27
+
28
+ test('license axes cover SPDX, legacy scope and explicit overrides', () => {
29
+ for (const [license, commercial, attributionRequired] of cases) {
30
+ assert.deepEqual(deriveLicenseAxes(license), { commercial, attributionRequired });
31
+ }
32
+ });
33
+
34
+ async function metas(dir) {
35
+ const rows = [];
36
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
37
+ if (entry.isDirectory()) rows.push(...await metas(join(dir, entry.name)));
38
+ else if (entry.name === 'meta.json') rows.push(join(dir, entry.name));
39
+ }
40
+ return rows;
41
+ }
42
+
43
+ test('all bundled and catalog metadata is inventoried; unknown names are visible', async () => {
44
+ const root = resolve(import.meta.dirname, '../../..');
45
+ const files = [...await metas(join(root, 'assets')), ...await metas(join(root, 'catalog'))];
46
+ const unknown = [];
47
+ for (const file of files) {
48
+ const meta = JSON.parse(await readFile(file, 'utf8'));
49
+ if (deriveLicenseAxes(meta.license).commercial === 'unknown') unknown.push(file.slice(root.length + 1));
50
+ }
51
+ assert.ok(files.length >= 67);
52
+ console.log(`license inventory: ${files.length} metadata files; unknown: ${JSON.stringify(unknown.sort())}`);
53
+ });