akari-video 0.1.9 → 0.1.11

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 (108) hide show
  1. package/package.json +1 -1
  2. package/src/capability-sources.mjs +5 -2
  3. package/src/status-command.mjs +8 -1
  4. package/vendor/.akari-capability-sources.json +4 -20
  5. package/vendor/docs/contract-2026-07-22-render-basics.md +27 -1
  6. package/vendor/docs/contract-2026-07-23-analysis-person-matte.md +7 -0
  7. package/vendor/docs/contract-2026-08-02-preview-parity.md +18 -0
  8. package/vendor/docs/contract-2026-08-11-analysis-vision-tracks-v0.md +170 -13
  9. package/vendor/docs/contract-2026-08-12-region-filter-layer-v0.md +1 -1
  10. package/vendor/docs/contract-2026-08-12-still-image-cut-source-v0.md +163 -0
  11. package/vendor/docs/contract-2026-08-13-avatar-drive-v0.md +504 -0
  12. package/vendor/docs/contract-2026-08-14-avatar-vrm-v0.md +336 -0
  13. package/vendor/packages/akari-launcher/package.json +9 -5
  14. package/vendor/packages/analysis-report/package.json +8 -4
  15. package/vendor/packages/asset-resolver/src/entitlements.mjs +23 -7
  16. package/vendor/packages/asset-resolver/src/paid-zip.mjs +6 -1
  17. package/vendor/packages/asset-resolver/src/resolve.mjs +29 -7
  18. package/vendor/packages/asset-resolver/src/state.mjs +13 -5
  19. package/vendor/packages/asset-resolver/test/catalog-and-state.test.mjs +16 -1
  20. package/vendor/packages/asset-resolver/test/entitlements.test.mjs +37 -9
  21. package/vendor/packages/asset-resolver/test/resolve-paid-zip.test.mjs +97 -4
  22. package/vendor/packages/audio-library-setup/bin/fetch-akari-sounds.mjs +96 -13
  23. package/vendor/packages/audio-library-setup/test/fetch-akari-sounds.test.mjs +269 -0
  24. package/vendor/packages/bake-layer/package.json +8 -4
  25. package/vendor/packages/chat-bridge/package.json +8 -4
  26. package/vendor/packages/decision-cards/package.json +8 -4
  27. package/vendor/packages/edit-lint/src/cut-timeline.mjs +17 -0
  28. package/vendor/packages/edit-lint/src/derive-tracks.mjs +42 -0
  29. package/vendor/packages/edit-lint/src/edit-lint.mjs +4235 -0
  30. package/vendor/packages/edit-lint/src/report.mjs +100 -0
  31. package/vendor/packages/edit-store/lib/caption-display.d.ts +107 -0
  32. package/vendor/packages/edit-store/lib/caption-display.js +812 -0
  33. package/vendor/packages/edit-store/lib/caption-store.d.ts +127 -0
  34. package/vendor/packages/edit-store/lib/caption-store.js +1000 -0
  35. package/vendor/packages/edit-store/lib/caption-window.d.ts +23 -0
  36. package/vendor/packages/edit-store/lib/caption-window.js +29 -0
  37. package/vendor/packages/edit-store/lib/edit-store.d.ts +219 -0
  38. package/vendor/packages/edit-store/lib/edit-store.js +1753 -0
  39. package/vendor/packages/edit-store/lib/index.d.ts +10 -0
  40. package/vendor/packages/edit-store/lib/index.js +26 -0
  41. package/vendor/packages/edit-store/lib/timeline-map.d.ts +59 -0
  42. package/vendor/packages/edit-store/lib/timeline-map.js +160 -0
  43. package/vendor/packages/edit-store/lib/webview-kernel.d.ts +15 -0
  44. package/vendor/packages/edit-store/lib/webview-kernel.js +194 -0
  45. package/vendor/packages/edit-store/lib/write-gate.d.ts +43 -0
  46. package/vendor/packages/edit-store/lib/write-gate.js +196 -0
  47. package/vendor/packages/export-nle/package.json +8 -4
  48. package/vendor/packages/intake-form/package.json +8 -4
  49. package/vendor/packages/media-bin/src/index.mjs +27 -8
  50. package/vendor/packages/media-bin/test/media-bin.test.mjs +71 -3
  51. package/vendor/packages/preview-server/package.json +8 -3
  52. package/vendor/packages/render-cut/package.json +9 -5
  53. package/vendor/packages/schemas/analysis.schema.json +7 -1
  54. package/vendor/packages/schemas/bin/validate-edit.mjs +2 -2
  55. package/vendor/packages/schemas/captions.schema.json +2 -1
  56. package/vendor/packages/schemas/edit.schema.json +6 -4
  57. package/vendor/packages/schemas/examples/edit-cuts-still-image-source-valid/edit.json +14 -0
  58. package/vendor/packages/schemas/test/analysis-body-pose-3d-pointer.test.mjs +14 -0
  59. package/vendor/packages/schemas/test/analysis-face-expression-pointer.test.mjs +14 -0
  60. package/vendor/packages/schemas/test/validate-edit.test.mjs +10 -2
  61. package/vendor/packages/template-render/README.ja.md +6 -0
  62. package/vendor/packages/template-render/README.md +6 -0
  63. package/vendor/packages/template-render/package.json +10 -3
  64. package/vendor/presets/textanim/INDEX.md +32 -0
  65. package/vendor/presets/textanim/index.jsonl +47 -0
  66. package/vendor/skills/analyze-footage/bin/face-expression/artifacts.mjs +83 -0
  67. package/vendor/skills/analyze-footage/bin/face-expression/browser.mjs +63 -0
  68. package/vendor/skills/analyze-footage/bin/face-expression/browser.test.mjs +10 -0
  69. package/vendor/skills/analyze-footage/bin/face-expression/euler.mjs +52 -0
  70. package/vendor/skills/analyze-footage/bin/face-expression/euler.test.mjs +46 -0
  71. package/vendor/skills/analyze-footage/bin/face-expression/face-expression-schema.test.mjs +32 -0
  72. package/vendor/skills/analyze-footage/bin/face-expression/face-expression.mjs +348 -0
  73. package/vendor/skills/analyze-footage/bin/face-expression/model-resolver.mjs +78 -0
  74. package/vendor/skills/analyze-footage/bin/face-expression/model-resolver.test.mjs +80 -0
  75. package/vendor/skills/analyze-footage/bin/face-expression/render-page.mjs +74 -0
  76. package/vendor/skills/analyze-footage/bin/face-expression/render.html +7 -0
  77. package/vendor/skills/analyze-footage/bin/face-expression/runtime-artifacts.test.mjs +17 -0
  78. package/vendor/skills/analyze-footage/bin/face-expression/static-server.mjs +89 -0
  79. package/vendor/skills/analyze-footage/bin/face-expression/static-server.test.mjs +19 -0
  80. package/vendor/skills/analyze-footage/bin/face-expression/vendor/tasks-vision-0.10.17/LICENSE.txt +202 -0
  81. package/vendor/skills/analyze-footage/bin/face-expression/vendor/tasks-vision-0.10.17/README-AKARI.md +12 -0
  82. package/vendor/skills/analyze-footage/bin/face-expression/vendor/tasks-vision-0.10.17/vision_bundle.mjs +2 -0
  83. package/vendor/skills/analyze-footage/bin/face-expression/vendor/tasks-vision-0.10.17/wasm/vision_wasm_internal.js +20 -0
  84. package/vendor/skills/analyze-footage/bin/face-expression/vendor/tasks-vision-0.10.17/wasm/vision_wasm_internal.wasm +0 -0
  85. package/vendor/skills/analyze-footage/bin/face-expression/vendor/tasks-vision-0.10.17/wasm/vision_wasm_nosimd_internal.js +20 -0
  86. package/vendor/skills/analyze-footage/bin/face-expression/vendor/tasks-vision-0.10.17/wasm/vision_wasm_nosimd_internal.wasm +0 -0
  87. package/vendor/skills/analyze-footage/bin/vision-tracks/body-pose-3d-schema.test.mjs +47 -0
  88. package/vendor/skills/analyze-footage/bin/vision-tracks/vision-tracks-helper.swift +74 -6
  89. package/vendor/skills/analyze-footage/bin/vision-tracks/vision-tracks.mjs +30 -6
  90. package/vendor/skills/analyze-footage/references/vision-tracks.schema.json +136 -4
  91. package/vendor/skills/analyze-footage/test/vision-tracks-check.test.mjs +45 -3
  92. package/vendor/skills/analyze-footage/vision-tracks.md +131 -20
  93. package/vendor/skills/edit-plan/SKILL.md +5 -3
  94. package/vendor/skills/edit-plan/execution.md +48 -10
  95. package/vendor/skills/edit-plan/workflow.md +9 -5
  96. package/vendor/skills/overlay-authoring/3d.md +125 -2
  97. package/vendor/skills/overlay-authoring/motion.md +36 -0
  98. package/vendor/packages/akari-launcher/bin/akari.mjs +0 -77
  99. package/vendor/packages/analysis-report/render-analysis-report.mjs +0 -544
  100. package/vendor/packages/bake-layer/bin/bake-layer.mjs +0 -159
  101. package/vendor/packages/chat-bridge/src/telegram.mjs +0 -272
  102. package/vendor/packages/decision-cards/report-helper.mjs +0 -611
  103. package/vendor/packages/export-nle/bin/export-nle.mjs +0 -162
  104. package/vendor/packages/intake-form/intake-form-helper.mjs +0 -37
  105. package/vendor/packages/preview-server/src/server.mjs +0 -682
  106. package/vendor/packages/render-cut/bin/akari-apply-textstyle.mjs +0 -202
  107. package/vendor/packages/render-cut/bin/render-cut.mjs +0 -5
  108. package/vendor/packages/template-render/bin/akari-template-render.mjs +0 -257
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
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": {
@@ -2,7 +2,10 @@ import { execFileSync } from "node:child_process";
2
2
  import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
3
3
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
4
4
 
5
- export function discoverCheckoutCapabilitySources(repoRoot, { trackedFiles = null } = {}) {
5
+ export function discoverCheckoutCapabilitySources(repoRoot, {
6
+ trackedFiles = null,
7
+ includeBinTargets = true,
8
+ } = {}) {
6
9
  const root = realpathSync(resolve(repoRoot));
7
10
  const tracked = trackedFiles ?? execFileSync("git", ["-C", root, "ls-files", "-z"], {
8
11
  maxBuffer: 64 * 1024 * 1024,
@@ -25,7 +28,7 @@ export function discoverCheckoutCapabilitySources(repoRoot, { trackedFiles = nul
25
28
  throw new Error(`${manifestPath} package.json#bin target is not tracked: ${canonical}`);
26
29
  }
27
30
  assertRegularContainedFile(root, canonical, `${manifestPath} package.json#bin`);
28
- selected.add(canonical);
31
+ if (includeBinTargets) selected.add(canonical);
29
32
  }
30
33
  }
31
34
  const paths = [...selected].sort(compare);
@@ -30,10 +30,17 @@ export async function runStatusCommand(argv, options = {}) {
30
30
  const status = parsed.full
31
31
  ? await resolveFullProjectStatus(parsed.projectRoot)
32
32
  : resolveProjectStatus(parsed.projectRoot, { mode: "fast" });
33
- log(parsed.json ? serializeStatus(status) : `${formatStatusSummary(status)}\n`);
33
+ log(parsed.json ? serializeStatus(status) : `${formatStatusOutput(status)}\n`);
34
34
  return { exitCode: status.state_health === "inconclusive" ? 1 : 0, status };
35
35
  }
36
36
 
37
+ export function formatStatusOutput(status) {
38
+ const summary = formatStatusSummary(status);
39
+ if (status.state_health !== "inconclusive" || status.problems.length === 0) return summary;
40
+ const problems = status.problems.map((problem) => ` - ${problem}`).join("\n");
41
+ return `${summary}\nCould not determine:\n${problems}\nNext: run \`akari status --json\` for the full machine-readable report, or resolve the items above and re-run \`akari status\`.`;
42
+ }
43
+
37
44
  export function parseStatusArguments(argv, cwd = process.cwd()) {
38
45
  let projectRoot = cwd;
39
46
  let pathSeen = false;
@@ -36,36 +36,27 @@
36
36
  "docs/contract-2026-08-12-chat-approval-v0.md",
37
37
  "docs/contract-2026-08-12-color-range-normalization-v0.md",
38
38
  "docs/contract-2026-08-12-region-filter-layer-v0.md",
39
- "packages/akari-launcher/bin/akari.mjs",
39
+ "docs/contract-2026-08-12-still-image-cut-source-v0.md",
40
+ "docs/contract-2026-08-13-avatar-drive-v0.md",
41
+ "docs/contract-2026-08-14-avatar-vrm-v0.md",
40
42
  "packages/akari-launcher/package.json",
41
43
  "packages/akari-launcher/README.md",
42
44
  "packages/akari-tools/package.json",
43
45
  "packages/analysis-report/package.json",
44
46
  "packages/analysis-report/README.md",
45
- "packages/analysis-report/render-analysis-report.mjs",
46
- "packages/asset-resolver/bin/akari-assets.mjs",
47
47
  "packages/asset-resolver/package.json",
48
48
  "packages/asset-resolver/README.md",
49
- "packages/audio-library-setup/bin/gallery-helper.mjs",
50
- "packages/audio-library-setup/bin/generate-candidates-html.mjs",
51
- "packages/audio-library-setup/bin/register-drop-folder.mjs",
52
49
  "packages/audio-library-setup/package.json",
53
50
  "packages/audio-library-setup/README.md",
54
- "packages/bake-layer/bin/bake-layer.mjs",
55
51
  "packages/bake-layer/package.json",
56
52
  "packages/bake-layer/README.md",
57
53
  "packages/chat-bridge/package.json",
58
- "packages/chat-bridge/src/telegram.mjs",
59
54
  "packages/creator-root/package.json",
60
55
  "packages/decision-cards/package.json",
61
56
  "packages/decision-cards/README.md",
62
- "packages/decision-cards/report-helper.mjs",
63
- "packages/edit-lint/bin/edit-lint.mjs",
64
57
  "packages/edit-lint/package.json",
65
58
  "packages/edit-store/package.json",
66
- "packages/export-nle/bin/export-nle.mjs",
67
59
  "packages/export-nle/package.json",
68
- "packages/intake-form/intake-form-helper.mjs",
69
60
  "packages/intake-form/package.json",
70
61
  "packages/intake-form/README.md",
71
62
  "packages/media-bin/package.json",
@@ -75,22 +66,15 @@
75
66
  "packages/preview-engine/package.json",
76
67
  "packages/preview-engine/README.md",
77
68
  "packages/preview-server/package.json",
78
- "packages/preview-server/src/server.mjs",
79
69
  "packages/project-scaffold/package.json",
80
- "packages/render-cut/bin/akari-apply-textstyle.mjs",
81
- "packages/render-cut/bin/render-cut.mjs",
82
70
  "packages/render-cut/package.json",
83
- "packages/schemas/bin/validate-asset.mjs",
84
- "packages/schemas/bin/validate-intake.mjs",
85
- "packages/schemas/bin/validate-plan.mjs",
86
- "packages/schemas/bin/validate-review.mjs",
87
71
  "packages/schemas/package.json",
88
- "packages/template-render/bin/akari-template-render.mjs",
89
72
  "packages/template-render/package.json",
90
73
  "packages/template-render/README.ja.md",
91
74
  "packages/template-render/README.md",
92
75
  "skills/address-review/SKILL.md",
93
76
  "skills/analyze-footage/analysis-json.md",
77
+ "skills/analyze-footage/bin/face-expression/vendor/tasks-vision-0.10.17/README-AKARI.md",
94
78
  "skills/analyze-footage/events-and-hooks.md",
95
79
  "skills/analyze-footage/keyframes-and-review.md",
96
80
  "skills/analyze-footage/media-and-transcript.md",
@@ -17,7 +17,7 @@
17
17
  |---|---|---|---|---|
18
18
  | 1 | 定速変更(クリップ単位の倍速/スロー) | `cuts[].speed`(number・既定 1.0・v0 は定速のみ、ランプは将来) | `setpts` + `atempo`(>2x/<0.5x の段組み) | 出力尺が理論値と一致(ffprobe)・音程/同期の実聴確認 1 点 |
19
19
  | 2 | クロマキー背景置換 | `source.chroma_key`: {color, similarity, blend, background(色 or 画像/動画パス)} | `chromakey`/`colorkey` + 背景入力の `overlay` | 緑背景フィクスチャで背景が置換された出力のピクセルサンプル検証 |
20
- | 3 | 基本トランジション | `cuts[].transition_out`: {type: dissolve/fade-black/fade-white, duration} | `xfade`(transition 指定があるカット境界のみ xfade 経路) | 境界フレームの中間ブレンド実在をフレーム抽出で確認・指定なし境界はハードカット維持 |
20
+ | 3 | 基本トランジション | `cuts[].transition_out`: {type: dissolve/fade-black/fade-white/**reveal-down/reveal-up**, duration} | `xfade`(transition 指定があるカット境界のみ xfade 経路)。reveal 系は ffmpeg の `revealdown` / `revealup` | 境界フレームの中間ブレンド実在をフレーム抽出で確認・指定なし境界はハードカット維持。reveal 系は色が混ざらないため、遷移中間フレームの**上半分と下半分を別々に測って**前後カットが同居することを確認する |
21
21
  | 4 | 色調フィルター(LUT) | `output.look`: {lut(プリセット参照 or パス), intensity} | `lut3d`(intensity は `blend` 併用) | LUT 有無 2 出力のフレームピクセル差分・プリセット表 `presets/luts/`(初期 2〜3 本。2026-07-29 に `catalog/luts/` から移設) |
22
22
  | 5 | 音声マスター処理 | `audio.master`: {denoise(off/std/strong), loudnorm(target LUFS・既定 -14)} | `afftdn` / `loudnorm`(2 パスでなく 1 パス許容 v0) | 出力のラウドネス実測(ffmpeg ebur128)が目標 ±1LU |
23
23
  | 6 | 画角操作(静的クロップ / ズームキーフレーム / 段階縮小) | `cuts[].framing`: `{crop?: {x,y,w,h}(0..1 の出力相対・静的), keyframes?: [{t,scale,cx?,cy?}](t=カット内秒・線形補間。2 点でズーム、3 点以上で段階縮小・cx/cy 省略時 0.5)}` | 出力キャンバスへフィット済みの frame を `crop` で窓抜きし `scale` で再拡大(punch-in)。静的 `crop` は `w/h/x/y` とも定数。ズームは `crop` 自身の `w/h` が実機検証で init 時一度しか評価されない制約があるため、`scale` 側を `eval=frame` で `scale(t)` 倍に広げ、`crop` は固定 `w=width:h=height` のまま `x/y` だけを `t` の関数で追わせる方式(詳細 §4-1) | 静的 crop は出力フレームの画素でクロップ位置が宣言どおりであることを実測・ズームは開始/中間/終端フレームで可視要素の実測サイズから逆算したスケールが線形補間の理論値と一致(±5%)・3 点キーフレームは 2 段階の縮小がフレーム抽出で確認できる |
@@ -31,6 +31,32 @@
31
31
  edit-lint / fixtures / test を同時追随
32
32
  2. プレビュー(preview-engine)は v0 では**近似不要・無視でよい**(出力最優先。
33
33
  「プレビューは近似・書き出しが正」の哲学を全項目に適用。プレビュー追随は別契約)
34
+ 3. **`output.look`(#4 の LUT)の適用範囲は `cuts[]` の本編映像だけ**である。
35
+ `layers[]`(PinP / 人物マット / B-roll)と `overlays[]` には**掛からない**。
36
+ 同じ絵の一部として重ねる素材の色を本編に合わせたいときは、`layers[].filter`
37
+ (`{type:"lut", id, intensity}`。正本 = `contract-2026-08-12-region-filter-layer-v0.md` §4)
38
+ へ**同じ `id` / `intensity` を明示的に宣言する**。
39
+ 実害例(2026-08-14・リール制作): 本編にだけ `cinematic` が乗り、重ねた人物切り抜きが
40
+ 素の色のまま合成されて、窓の継ぎ目で肌色が食い違った。「プロジェクト全体の色」だと
41
+ 誤解しやすいため、ここに明記する。
42
+
43
+ ### 2-4. reveal 系トランジション(`reveal-down` / `reveal-up`。2026-08-14 追加)
44
+
45
+ **前カットが丸ごとその方向へ動いて画面外へ抜け、空いた側から次カットが現れる**
46
+ (前カットは動きながら画面端でクロップされる)。ディゾルブのように混ざらないので、
47
+ **同じ構図が続くトークシーンでも「場面が入れ替わった」ことが読める**のが採用理由
48
+ (オーナー指定 2026-08-14「テンプレの基本トランジションとして必要」)。
49
+
50
+ - `reveal-down` = 前カットが下へ降りる(画面上部から次カットが出てくる)
51
+ - `reveal-up` = 前カットが上へ抜ける(画面下部から次カットが出てくる)
52
+ - 実測(64x64・10fps・duration 1s・遷移中間 t=2.5s): `reveal-down` で上半分 RGB(0,0,253)=次カット /
53
+ 下半分 RGB(252,0,0)=前カット。`reveal-up` はこの上下が入れ替わる
54
+ - **他の xfade と同じく、遷移の重なり分だけタイムラインが縮む**(境界 1 つにつき `duration` 秒)。
55
+ `layers[]` / `overlays[]` / `audio.sfx[]` を**タイムライン秒で手置き**しているプロジェクトでは、
56
+ トランジションを足すと後続の配置が全部ずれる。字幕は (`src`, source 秒) で書くのでエンジンが
57
+ 追随するが、手置きの要素は自分で引き直す必要がある。尺を変えたくない場合は、
58
+ トランジションではなくオーバーレイで表現する(前カット最終フレームを焼いて動かす)という
59
+ 逃げ道もあるが、静止画になるうえプロジェクト固有の焼き込みが要るので既定にはしない
34
60
 
35
61
  ## 3. 残裁定
36
62
 
@@ -121,6 +121,13 @@ HEVC alpha MOV は「Apple 系ツールへの受け渡しが要るとき」の
121
121
  がそのまま適用される。`beats` / `emphasis_words` と同じ扱いである
122
122
  - マットに `--ss` 相当のオフセットを持たせない。素材の途中区間だけを切り出したマットを
123
123
  `person_matte` に載せない(載せると時刻 0 の一致が壊れる)
124
+ - 一方で、**カット単位に切り出したマットを `layers[].src` へ直接置く**運用(`analysis.json` を
125
+ 経由しない、プロジェクト固有の `assets/matte/*.mov` など)は本契約の管轄外である。
126
+ その場合は本節の「時刻 0 一致」が成り立たないので、**切り出しの由来(元素材 / in / out /
127
+ speed / fps)を素材の隣に必ず残す**こと。由来が無いと消費側は素材の頭が何の時刻かを
128
+ 推測するしかなく、十数フレーム単位でずれたまま気づけない(2026-08-14 に実害)。
129
+ 運用上の注意は `docs/contract-2026-08-02-preview-parity.md` §2.4 と
130
+ `skills/edit-plan/execution.md`「レイヤー素材の時間基準」を参照
124
131
  - 消費側は表示・書き出しのたびに `cuts[]` から timeline 秒へ射影する。射影結果を永続化しない。
125
132
  同一 source 区間が複数回現れれば、1 本のマットが複数の timeline 位置へ射影される
126
133
  - マットの尺が素材の尺より短い場合、超えた範囲は**マット無し**として扱う(エラーではない)。
@@ -57,6 +57,23 @@
57
57
  ### 2.4 レイヤー(B-roll)
58
58
  - `t` 〜 `t + duration` の窓外では非表示。**初期状態も非表示**(窓に入るまで描画しない)
59
59
  - 表示中は `currentTime` を出力時刻に同期する
60
+ - **素材内オフセット(in トリム)は無い。素材の先頭が常に `t` に対応する**。`duration` は
61
+ 素材の先頭から何秒使うかであって、素材のどこを使うかは選べない(`cuts[].in/out` に相当する
62
+ ものが `layers[]` には存在しない)。素材の途中区間を重ねたいときは**素材そのものを切り出す**
63
+ 必要がある。
64
+ - 切り出した素材は「何を・どこから・どの速度で切り出したか」が失われるため、
65
+ **由来(元素材 / in / out / speed / fps)を素材の隣に必ず残す**こと。残っていないと、
66
+ 次に触る人(人間・AI とも)が「素材の頭が何の時刻なのか」を推測することになり、
67
+ 十数フレーム単位でズレたまま気づけない。
68
+ - 実害例(2026-08-14・リール制作): カット単位に切り出した人物マットを「先行表示分の
69
+ プリロールを持っているはず」と**推測**して頭をトリムしたところ、実際は切り出し済みで
70
+ プリロールが無く、11〜23 フレームずれた。さらに `duration` を詰めた結果、区間の末尾で
71
+ マットが尽きて「人物が消えて背景だけ」になった。
72
+ - 素材とカットの時間対応を後から実測する場合、**フレーム差分の絶対値(`blend=difference`)は
73
+ 使わない**。色調整(`output.look` は本編にしか掛からない = §2.4 冒頭の別項)で素材と本編の
74
+ 色が違うと、その色差が支配して指標が平坦になり誤った結論を導く。**フレーム間差分エネルギーの
75
+ 時系列(`tblend=all_mode=difference` → `signalstats` の YAVG)を正規化して相互相関**させると、
76
+ 色に不変で lag を特定できる。
60
77
 
61
78
  #### 2.4.1 空間クロップ(`layers[].crop`。2026-08-06 導入)
62
79
  - `crop = { x, y, w, h }`(**0..1 正規化・ソースフレーム相対・静的**)。省略時は既定
@@ -539,6 +556,7 @@ ffmpeg の `perspective` フィルタの制約(式に時刻変数を持たな
539
556
  | `layers[].perspective`(2026-08-06 実装) | ✅(§2.4.4。実ブラウザ実測済み) | ✅(§2.4.4。tsc -b + ユニット + Web 同一計算式で担保) |
540
557
  | `cuts[].fx`(2026-08-07 実装・近似あり) | 🟡(§2.4.5。5 種対応、3 種は近似バッジ付き) | ❌(未実装) |
541
558
  | `layers[].keyframes`(2026-08-09 実装) | ✅(§2.4.7。transform/crop は連続補間。perspective は blend:"normal" のみ・書き出しの段階保持とサンプル点で一致) | ✅(§2.4.7。同左) |
559
+ | `cuts[].static-image-source`(2026-08-12 実装。正本: `contract-2026-08-12-still-image-cut-source-v0.md`) | 🟡(`<img>`/`<video>` 出し分け + preview-engine ClipSession/Timeline の image 対応を実装。framing/freeze/transform は流用。実ブラウザでの対話的スクラブ・複数区間切替の実機検証は未実施) | ❌(スコープ外・未対応) |
542
560
 
543
561
  - `cuts[].framing`(静的クロップ / ズームキーフレーム)・`cuts[].freeze`(フリーズ)は
544
562
  `contract-2026-07-22-render-basics.md` #6/#7 としてレンダ(render-cut)に加え、
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  lifecycle: draft
3
3
  created: 2026-08-11
4
- updated: 2026-08-11
4
+ updated: 2026-08-14
5
5
  ---
6
6
 
7
- # 分析トラック契約 v0 — Vision ランドマーク・トラック(face-landmarks / hand-pose)と keyframes 消費
7
+ # 分析トラック契約 v0 — Vision ランドマーク・トラック(face-landmarks / hand-pose / body-pose-3d / face-expression)と keyframes 消費
8
8
 
9
9
  - 日付: 2026-08-11
10
10
  - 状態: **ドラフト**(v0 実装と同時に確定させる。実装で判明した齟齬は追記で解消)
@@ -13,8 +13,9 @@ updated: 2026-08-11
13
13
  - `contract-2026-07-23-analysis-person-matte.md`(Swift サイドカーの流儀・analysis.json の tracks 契約・検証責務の分担)
14
14
  - `contract-2026-07-25-project-structure-v0.md`(分析サイドカーの置き場 = `.akari/sidecars/`)
15
15
  - `contract-2026-08-02-preview-parity.md`(render/Web/shell 3 面パリティの原則)
16
- - スコープ: 動画から抽出する**ランドマーク・トラック**(顔・手)のデータ契約、生成サイドカーの
17
- 入出力、消費(`layers[].keyframes` への変換)の責務分担。**新しいレンダー機構は作らない**
16
+ - スコープ: 動画から抽出する**ランドマーク・トラック**(顔・手・3D ボディポーズ)のデータ契約、生成サイドカーの
17
+ 入出力、消費(`layers[].keyframes` への変換)の責務分担、および MediaPipe 由来の頭部姿勢・
18
+ 表情トラック。**新しいレンダー機構は作らない**
18
19
 
19
20
  ## 0. 設計原則
20
21
 
@@ -34,7 +35,7 @@ updated: 2026-08-11
34
35
 
35
36
  ## 1. analysis.json への追加(additive)
36
37
 
37
- `tracks` に **optional キー** 2 つを追加する。`tracks.required`(speakers / faces /
38
+ `tracks` に **optional キー** 3 つを追加する。`tracks.required`(speakers / faces /
38
39
  person_matte)には**入れない**(person_matte が必須なのは既存消費者の事情であり、新トラックは
39
40
  真に任意として追加する)。
40
41
 
@@ -48,18 +49,40 @@ person_matte)には**入れない**(person_matte が必須なのは既存消
48
49
  "tool": "vision-tracks.mjs v0",
49
50
  "generated_at": "2026-08-11T12:00:00Z"
50
51
  },
51
- "hand_pose": { /* 同形 */ }
52
+ "hand_pose": { /* 同形 */ },
53
+ "body_pose_3d": { // 任意。macOS 14+ の Vision 3D ボディポーズ
54
+ "path": "vision/body-pose-3d.json",
55
+ "sample_fps": 24,
56
+ "provider": "apple-vision",
57
+ "tool": "vision-tracks.mjs v0",
58
+ "generated_at": "2026-08-13T12:00:00Z"
59
+ }
60
+ }
61
+ ```
62
+
63
+ 2026-08-14 additive: 同じ optional pointer 形で `face_expression` を追加する。既存 3 キーと同様に
64
+ `tracks.required` には入れず、未生成はキー無しで表す。
65
+
66
+ ```jsonc
67
+ "face_expression": {
68
+ "path": "vision/face-expression.json",
69
+ "sample_fps": 24,
70
+ "provider": "mediapipe-face-landmarker",
71
+ "tool": "face-expression.mjs v0",
72
+ "generated_at": "2026-08-14T12:00:00Z",
73
+ "features": ["head-pose-ypr-radians", "mediapipe-blendshapes-52"]
52
74
  }
53
75
  ```
54
76
 
55
77
  ## 2. トラックファイル形式(vision-tracks v0)
56
78
 
57
- `path` が指す JSON ファイル。1 ファイル 1 種類(face-landmarks と hand-pose は別ファイル)。
79
+ `path` が指す JSON ファイル。1 ファイル 1 種類(face-landmarks / hand-pose /
80
+ body-pose-3d / face-expression は別ファイル)。
58
81
 
59
82
  ```jsonc
60
83
  {
61
84
  "version": 0,
62
- "kind": "face-landmarks", // "face-landmarks" | "hand-pose"
85
+ "kind": "face-landmarks", // "face-landmarks" | "hand-pose" | "body-pose-3d"
63
86
  "source": { "path": "../..(元動画への相対)", "duration": 12.5 },
64
87
  "sample_fps": 24,
65
88
  "provider": { "name": "apple-vision", "os": "macOS 15.5" },
@@ -70,8 +93,11 @@ person_matte)には**入れない**(person_matte が必須なのは既存消
70
93
  }
71
94
  ```
72
95
 
73
- - **座標系(最重要規約)**: すべて **0〜1 正規化・左上原点**(動画ピクセル系と同じ向き)。
96
+ - **画像座標系(最重要規約)**: face-landmarks / hand-pose と body-pose-3d の
97
+ `projection` はすべて **0〜1 正規化・左上原点**(動画ピクセル系と同じ向き)。
74
98
  Vision framework は左下原点で返すため、**y 反転はサイドカーの責務**。消費側は変換しない
99
+ - body-pose-3d の `position` だけは画像座標ではない。Vision が返すモデル座標
100
+ (root/hip 相対メートル)を変換せず保存する(§2.4)
75
101
  - `samples[].t` は元動画の秒(`in`/`out` と同じ時間軸)。サンプリングは `sample_fps` の等間隔
76
102
  - 生値主義: 平滑化・補間済みの値を入れない。信頼度(`conf`)を必ず併記する
77
103
 
@@ -126,6 +152,86 @@ person_matte)には**入れない**(person_matte が必須なのは既存消
126
152
  (§2.1 の必須 6 キーが揃わない detection を出力に混ぜない)。実測(1 名がほぼ映り続ける
127
153
  26.3 秒素材)ではこの除外は 0 件だった。
128
154
 
155
+ ### 2.4 body-pose-3d の detection(additive・2026-08-13)
156
+
157
+ `VNDetectHumanBodyPose3DRequest` revision 1 が返す 17 関節を保存する。API は macOS 14+
158
+ 限定である。トラックは分析の生値であり、平滑化・間引き・低信頼度除外を行わない。
159
+
160
+ ```jsonc
161
+ {
162
+ "conf": 0.86,
163
+ "joints": {
164
+ "root": {
165
+ "position": [0.0, 0.0, 0.0], // root/hip 相対メートル
166
+ "projection": [0.51, 0.63], // 0〜1 正規化・左上原点
167
+ "conf": 0.86
168
+ },
169
+ "right_hip": { /* 同形 */ },
170
+ "right_knee": { /* 同形 */ },
171
+ "right_ankle": { /* 同形 */ },
172
+ "left_hip": { /* 同形 */ },
173
+ "left_knee": { /* 同形 */ },
174
+ "left_ankle": { /* 同形 */ },
175
+ "spine": { /* 同形 */ },
176
+ "center_shoulder": { /* 同形 */ },
177
+ "center_head": { /* 同形 */ },
178
+ "top_head": { /* 同形 */ },
179
+ "left_shoulder": { /* 同形 */ },
180
+ "left_elbow": { /* 同形 */ },
181
+ "left_wrist": { /* 同形 */ },
182
+ "right_shoulder": { /* 同形 */ },
183
+ "right_elbow": { /* 同形 */ },
184
+ "right_wrist": { /* 同形 */ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ - `position`: `VNHumanBodyRecognizedPoint3D.position` の平行移動成分 `[x,y,z]`。
190
+ Vision のモデル座標で root/hip 相対、単位はメートル。カメラ相対座標へ変換しない
191
+ - `projection`: `VNHumanBodyPose3DObservation.pointInImage` が返す画像投影を y 反転し、
192
+ `[0,1]` へクランプした `[x,y]`
193
+ - `conf`: Vision 3D API は関節別 confidence を公開しないため、apple-vision provider の v0 は
194
+ `VNHumanBodyPose3DObservation.confidence` を各関節へ複製する。これは関節別推定値ではなく、
195
+ 観測全体 confidence の由来明示である。消費者の `min-confidence` はこの値を使う
196
+ - 17 関節のいずれかを Vision から取得できない観測は detection ごと省略する。存在しない
197
+ 関節を補間・捏造せず、`detections: []` のフレームは時刻 `t` とともに残す
198
+ - pose-skeleton の v0 は各フレーム先頭の 1 人(`bodyIndex=0` 固定)のみを消費し、複数人には非対応
199
+
200
+ ### 2.5 face-expression の detection(additive・2026-08-14)
201
+
202
+ MediaPipe Face Landmarker が返す `facialTransformationMatrixes` と 52 blendshape category を、
203
+ 平滑化・補間せず保存する。1 ファイル 1 kind の `kind` は `face-expression`、analysis pointer は
204
+ `tracks.face_expression`、既定ファイル名は `vision/face-expression.json` とする。顔が検出されない
205
+ frame も `{ "t": ..., "detections": [] }` として残す。
206
+
207
+ ```jsonc
208
+ {
209
+ "head": {
210
+ "yaw": 0.12,
211
+ "pitch": -0.04,
212
+ "roll": 0.02
213
+ },
214
+ "blendshapes": {
215
+ "_neutral": 0.07,
216
+ "eyeBlinkLeft": 0.01,
217
+ "mouthSmileLeft": 0.64
218
+ // MediaPipe 固定 category 全 52 キー。各値は 0..1 の生 score
219
+ },
220
+ "conf": 0.64
221
+ }
222
+ ```
223
+
224
+ - `head` は 4x4 row-major 同次変換の上左 3x3 を行正規化し、
225
+ `R = Rz(roll) * Ry(yaw) * Rx(pitch)` で分解した**ラジアン**。右手系の解釈は +X=画像右、
226
+ +Y=画像下、+Z=canonical face 前方で、yaw 正=画面右向き、pitch 正=上向き、
227
+ roll 正=時計回り。gimbal lock は `roll=0` に固定する
228
+ - `blendshapes` は `_neutral` を含む MediaPipe の固定 52 category。キーは表現の生 score で、
229
+ 並びだけ byte 安定のため名前順へ正規化する。値の平滑化・クランプ・感情ラベル化はしない
230
+ - MediaPipe Web API は内部の face-presence score を結果へ公開しない。`conf` は捏造した定数ではなく、
231
+ 同じ detection に返った 52 生 score の最大値を signal confidence として決定論的に記録する。
232
+ face detection confidence そのものではないため、消費者は検出有無と blendshape 個別値を主に使う
233
+ - v0 は `numFaces=1`。複数人追跡・人物同一性の連結はしない
234
+
129
235
  ## 3. サイドカー(生成側)
130
236
 
131
237
  person-matte と同じ分離: **Swift ヘルパーはフレーム変換だけ、コンテナ・時刻・組み立ては
@@ -135,25 +241,73 @@ person-matte と同じ分離: **Swift ヘルパーはフレーム変換だけ、
135
241
  - `vision-tracks-helper.swift` — stdin から raw BGRA フレーム列、stdout へ **JSON Lines
136
242
  (1 フレーム 1 行の検出結果)**。`swiftc -O` オンデマンドビルド・バイナリは `.gitignore`
137
243
  - `vision-tracks.mjs` — `ffmpeg`(デコード・fps/幅統一)→ helper → トラックファイル組み立て →
138
- `analysis.json` の tracks へ追記(原子的置換)。`--kinds face,hand` / `--fps` / `--check`
139
- (macOS / swiftc / ffmpeg の可用性確認)
244
+ `analysis.json` の tracks へ追記(原子的置換)。`--kinds face,hand,body-pose-3d` /
245
+ `--fps` / `--check`(macOS 14+ / swiftc / ffmpeg の可用性確認。macOS 14 未満は
246
+ capability 不足として理由付きで拒否)
140
247
  - 手順書: `skills/analyze-footage/vision-tracks.md`(person-matte.md と同格の任意工程)。
141
248
  `SKILL.md` の実行順・ハードルールに配線する
142
249
  - 起動主体はエージェント(スキル手順に従い bash で直接叩く)。CLI サブコマンドにはしない
143
250
  (判断を伴う工程はスキル、の境界裁定に従う)
144
251
 
252
+ ### 3.1 face-expression の headless Chromium 生成器(additive・2026-08-14)
253
+
254
+ `skills/analyze-footage/bin/face-expression/face-expression.mjs` は face-expression 専用の独立生成器。
255
+ 既存 Swift helper は変更せず、ffmpeg で 24 fps(`--fps` 変更可)・幅 1280 以下の PNG 列へ
256
+ デコードし、Chrome for Testing + `puppeteer-core` のページ内で CPU/WASM 版 Face Landmarker を
257
+ 順に実行する。Chrome の探索順・起動引数・ページ処理後の結果吸い上げは avatar-vrm の既存
258
+ headless 経路を踏襲する。
259
+
260
+ JS 版を選んだ理由は、`@mediapipe/tasks-vision` がブラウザ/WASM 専用である一方、製品には既に
261
+ headless Chromium 経路があり、Python wheel や Swift helper という新しい実行系を増やさずに済むため。
262
+ 固定 Chrome・固定 WASM・CPU delegate・固定時刻入力により、同一環境では同じ行列分解と score 列を
263
+ 得られる。Python 版は arm64 wheel という別インストール面とバージョン解決を増やすため v0 では採らない。
264
+
265
+ モデルはリポジトリへ置かず、初回だけ次を取得する。`AKARI_HOME` があればそれを優先し、既定は
266
+ `~/.akari/models/mediapipe/face-landmarker/float16-1/face_landmarker.task`。既存ファイルも毎回
267
+ SHA-256 検査し、不一致時は再取得で隠さず即エラーにする。新規取得は `.tmp-<pid>` へ書き、検証後に
268
+ rename する。
269
+
270
+ - URL: `https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task`
271
+ - SHA-256: `64184e229b263107bc2b804c6625db1341ff2bb731874b0bcc2fe6544e0bc9ff`
272
+
273
+ ブラウザ runtime はネットワーク変動を避けるため `@mediapipe/tasks-vision@0.10.17` の必要ファイルを
274
+ 無変換で vendor した。ライセンスは Apache-2.0。
275
+
276
+ | artifact | SHA-256 |
277
+ |---|---|
278
+ | npm tarball | `d3dd0759295f1adcf5455f22aa652c58b8c1d537c0d14c8db7df78646011d523` |
279
+ | `vision_bundle.mjs` | `1ada13431ea2a8ed7ea449e6c3595122d43fea2a8a4788056ed7da271469b402` |
280
+ | `vision_wasm_internal.js` | `33a4125f825b343d2d9773951a73692f40bee368c9b591af8ff652fd501af90b` |
281
+ | `vision_wasm_internal.wasm` | `c88cf472dd5cab0a3954b071e5f442102ded3701dcccc987a7a02ee8f54aae85` |
282
+ | `vision_wasm_nosimd_internal.js` | `4e8d07dcf8cbb55b343cd76b7fc30d4303220f049d5529d6412f6f93296726a8` |
283
+ | `vision_wasm_nosimd_internal.wasm` | `f840f69d7229f89dedaed39c7ac7a52f0964a7cec02d6cb1ac9eff891db86dc2` |
284
+ | `LICENSE.txt` | `b070d77bfb2c52a1dd6996de0ce5f64c49a0ca55c889b163a963ddf5cb001ee2` |
285
+
286
+ > 注: `LICENSE.txt` のみ npm tarball には含まれず(tarball 展開で実測確認済み)、Apache-2.0 の定型全文を手動で添えたもの(出所は `vendor/.../README-AKARI.md` 参照)。他の行は全て tarball 由来の実ファイル。
287
+
288
+ tarball は `tar -xzf` 後、上記 bundle/WASM だけをコピーし、esbuild 等の再ビルドは行っていない。
289
+ tarball 自体の npm integrity は
290
+ `sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==`。
291
+
145
292
  ## 4. 消費(変換器)
146
293
 
147
- トラック → `edit.json` への反映は**決定論の変換器**が行う。v0 の消費者は 2 つ(別契約で
294
+ トラック → `edit.json` への反映は**決定論の変換器**が行う。v0 の消費者は 3 つ(別契約で
148
295
  実装しても本契約の §2 形式だけを入力にする):
149
296
 
150
297
  | 消費者 | 入力 | 出力 |
151
298
  |---|---|---|
152
299
  | eye-bar(目線黒帯) | face_landmarks(両瞳) | 黒帯レイヤー + `layers[].keyframes` の transform(x/y/rotate/scale) |
153
300
  | finger-frame(指フレーム) | hand_pose(両手の thumb_tip / index_tip = 4 点) | 対象レイヤーの `layers[].keyframes` の perspective(4 隅 corner-pin)+ 発動区間 |
301
+ | pose-skeleton | body_pose_3d(17 関節の 2D `projection`) | アルファ付きスティックフィギュアを事前ベイクした `kind: "baked"` layer |
302
+
303
+ `face_expression` のアバター駆動への結線は次の消費側契約に委ねる。本契約では生成 SSOT までとし、
304
+ 既存の「あいうえお」口パクや avatar-vrm / avatar-drive を変更しない。
154
305
 
155
306
  - 平滑化(移動平均・One Euro 等)・キーフレーム間引き・欠測補間は**変換器の責務**。
156
307
  パラメータは変換器の引数で決定論に
308
+ - pose-skeleton は欠測と cut 境界で平滑化状態をリセットし、低 confidence 関節を含む骨を
309
+ 非表示にする。欠測区間を hold せず、別 baked clip / layer へ分割する
310
+ - pose-skeleton の v0 は `bodyIndex=0` 固定で各フレーム先頭の 1 人だけを対象とし、複数人には非対応
157
311
  - perspective keyframes の ffmpeg 側は既存の時間窓分割フォールバック
158
312
  (`expandLayerForPerspectiveKeyframes`)に乗る。新しいレンダー経路は作らない
159
313
  - `cuts[].fx` は**使わない**(全画面ポスト効果の器。空間追跡系はレイヤー機構が正)
@@ -163,10 +317,13 @@ person-matte と同じ分離: **Swift ヘルパーはフレーム変換だけ、
163
317
  - `packages/schemas/bin/` に新しいバリデータ CLI は**作らない**(person-matte 契約 §7 の分担を
164
318
  継続)。トラックファイルの JSON Schema は `skills/analyze-footage/references/
165
319
  vision-tracks.schema.json` に置き、スキル手順の jsonschema 検証が担う
166
- - `analysis.schema.json` への tracks 2 キー追加は additive のみ
320
+ - `analysis.schema.json` への tracks 4 キー追加は additive のみ
167
321
  - **`packages/analysis-report/render-analysis-report.mjs` の軽量チェックを同時に更新する**
168
322
  (person_matte が残した「消費者の追随債務」を新トラックで繰り返さない)
169
323
  - verify は L0(該当 package / skill の `node --test`)。GUI を触らないため L1/L2 は対象外
324
+ - face-expression は schema wiring、52 キー固定、同一行列の Euler 分解一致、モデル初回配置と
325
+ cached/downloaded 両方の SHA-256 不一致拒否を unit test する。実素材の検出率・CPU 時間・
326
+ 実時間比は fieldtest 12 秒窓で別途記録し、値を捏造して本契約へ先書きしない
170
327
 
171
328
  ## 6. やらないこと(v0)
172
329
 
@@ -88,7 +88,7 @@ LUT の `intensity` 省略時は `1` として描画する。
88
88
 
89
89
  ## 7. 実装対応
90
90
 
91
- - `packages/render-cut/src/layers.mjs`: ベース映像の split、ルック適用、白 quad mask、`maskedmerge` による region 内合成を行う。filter layer は追加の `-i` を作らない。
91
+ - `packages/render-cut/src/filter-mask.mjs` / `layers.mjs`: corner keyframes からフレーム単位の縮小 gray8 quad mask を生成し、ベース映像の split、ルック適用、拡大したマスクとの `maskedmerge` による region 内合成を行う。filter layer ごとにマスク動画用の `-i` を 1 本追加する。
92
92
  - `packages/akari-tools/bin/finger-frame.mjs`: `--kind filter --filter invert|lut:<id>|saturation:<value>` から、既存と同じ gesture window と corner keyframes を持つ layer を生成する。
93
93
 
94
94
  ## 8. 検証