akari-video 0.1.30 → 0.1.32

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 (43) hide show
  1. package/README.md +0 -14
  2. package/package.json +1 -1
  3. package/src/capture-command.mjs +0 -35
  4. package/src/cli.mjs +6 -2
  5. package/src/doctor-command.mjs +0 -1
  6. package/src/messages.mjs +0 -1
  7. package/src/runtime-diagnostics.mjs +1 -48
  8. package/vendor/docs/contract-2026-07-22-render-basics.md +4 -3
  9. package/vendor/docs/contract-2026-08-02-preview-parity.md +11 -1
  10. package/vendor/docs/contract-2026-08-03-caption-display-encoding-qc-v1.md +56 -0
  11. package/vendor/docs/contract-2026-08-18-v1-render-parity.md +2 -3
  12. package/vendor/docs/contract-2026-08-28-gpu-export-v0.md +24 -15
  13. package/vendor/docs/contract-2026-08-28-osr-export-v0.md +17 -9
  14. package/vendor/docs/contract-2026-08-30-edit-json-v2-object-tree-v0.md +20 -0
  15. package/vendor/docs/contract-2026-08-30-motion-and-keyframes-v0.md +3 -0
  16. package/vendor/packages/akari-launcher/README.md +0 -14
  17. package/vendor/packages/akari-launcher/package.json +1 -1
  18. package/vendor/packages/edit-lint/src/edit-lint.mjs +19 -0
  19. package/vendor/packages/edit-store/lib/caption-display.d.ts +16 -0
  20. package/vendor/packages/edit-store/lib/caption-display.js +53 -2
  21. package/vendor/packages/edit-store/lib/caption-store.d.ts +2 -0
  22. package/vendor/packages/edit-store/lib/caption-store.js +38 -4
  23. package/vendor/packages/edit-store/lib/caption-words-rederive.d.ts +34 -0
  24. package/vendor/packages/edit-store/lib/caption-words-rederive.js +205 -0
  25. package/vendor/packages/edit-store/lib/index.d.ts +1 -0
  26. package/vendor/packages/edit-store/lib/index.js +1 -0
  27. package/vendor/packages/gpu-export/README.ja.md +8 -0
  28. package/vendor/packages/gpu-export/README.md +9 -0
  29. package/vendor/packages/osr-export/README.md +3 -1
  30. package/vendor/packages/render-cut/package.json +1 -5
  31. package/vendor/packages/schemas/bin/validate-captions.mjs +14 -1
  32. package/vendor/packages/schemas/captions.schema.json +8 -2
  33. package/vendor/packages/schemas/test/validate-captions.test.mjs +38 -0
  34. package/vendor/skills/analyze-footage/bin/face-expression/browser.mjs +2 -2
  35. package/vendor/skills/analyze-footage/bin/face-expression/face-expression.mjs +1 -1
  36. package/vendor/skills/analyze-footage/vision-tracks.md +2 -1
  37. package/vendor/skills/beat-sync-edit/pitfalls.md +2 -2
  38. package/vendor/skills/overlay-authoring/3d.md +4 -5
  39. package/vendor/skills/overlay-authoring/thumbnail.md +3 -20
  40. package/vendor/skills/render-cut/SKILL.md +3 -4
  41. package/vendor/skills/setup-library/SKILL.md +2 -2
  42. package/vendor/skills/setup-library/tools-check.md +0 -13
  43. package/src/chrome-command.mjs +0 -100
package/README.md CHANGED
@@ -52,20 +52,6 @@ DL・sha256 検証・適用まで実行。それ以外(npm グローバル / g
52
52
  `packages/asset-resolver` の CLI への薄い委譲で、カタログ合成・entitlements 判定・
53
53
  sha256 検証・fail-closed は resolver 側の責務のまま。`src/assets-command.mjs`)。
54
54
 
55
- ### Chrome の導入
56
-
57
- 動画の書き出しに使う Chrome for Testing は、次のコマンドで導入できる。
58
-
59
- ```sh
60
- akari chrome install
61
- akari chrome install --cache-dir /path/to/cache
62
- akari chrome install --build-id 150.0.7339.16
63
- ```
64
-
65
- 保存先の既定は `~/.cache/puppeteer`。`--cache-dir` で保存先、`--build-id` で導入する
66
- buildId を指定できる。実行環境から `@puppeteer/browsers` を解決できる必要があり、
67
- AKARI Video アプリの配布物には必要なモジュールが同梱されている。
68
-
69
55
  `akari` に渡した引数はそのまま `opencode` に転送する(例: `akari --continue` は
70
56
  `opencode --continue` を起動する)。
71
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
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,7 +1,5 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { pathToFileURL } from "node:url";
5
3
 
6
4
  import { resolveLauncherAssets } from "./repo-assets.mjs";
7
5
 
@@ -13,15 +11,6 @@ export async function runCaptureCommand(argv, options = {}) {
13
11
  return { exitCode: 1 };
14
12
  }
15
13
 
16
- const isHelp = argv.includes("--help") || argv.includes("-h");
17
- if (!isHelp && captureNeedsChrome(argv, options.platform ?? process.platform)) {
18
- const browser = await resolveBrowserDiagnostics(assets, options);
19
- if (!await browser.findChromePath()) {
20
- logError(await browser.describeChromeNotFound());
21
- return { exitCode: 1 };
22
- }
23
- }
24
-
25
14
  const spawn = options.spawn ?? spawnSync;
26
15
  const result = spawn(process.execPath, [assets.captureScript, ...argv], {
27
16
  stdio: "inherit",
@@ -29,27 +18,3 @@ export async function runCaptureCommand(argv, options = {}) {
29
18
  });
30
19
  return { exitCode: typeof result.status === "number" ? result.status : 1 };
31
20
  }
32
-
33
- export function captureNeedsChrome(argv, platform = process.platform) {
34
- let engine = "auto";
35
- for (let index = 0; index < argv.length; index += 1) {
36
- if (argv[index] === "--engine" && argv[index + 1] !== undefined) engine = argv[index + 1];
37
- else if (argv[index].startsWith("--engine=")) engine = argv[index].slice("--engine=".length);
38
- }
39
- return engine === "legacy" || (engine === "auto" && !["darwin", "win32"].includes(platform));
40
- }
41
-
42
- async function resolveBrowserDiagnostics(assets, options) {
43
- if (options.findChromePath && options.describeChromeNotFound) {
44
- return {
45
- findChromePath: options.findChromePath,
46
- describeChromeNotFound: options.describeChromeNotFound,
47
- };
48
- }
49
- const modulePath = join(assets.repoRoot, "packages", "render-cut", "src", "render-cut.mjs");
50
- const module = await import(pathToFileURL(modulePath).href);
51
- return {
52
- findChromePath: options.findChromePath ?? module.findChromePath,
53
- describeChromeNotFound: options.describeChromeNotFound ?? module.describeChromeNotFound,
54
- };
55
- }
package/src/cli.mjs CHANGED
@@ -22,7 +22,6 @@ import {
22
22
  triggerBackgroundRefresh
23
23
  } from './update-check.mjs';
24
24
  import { applySelfUpdate, isRunningFromAppDir, rollbackSelfUpdate } from './self-update.mjs';
25
- import { runChromeCommand } from './chrome-command.mjs';
26
25
  import { runCaptureCommand } from './capture-command.mjs';
27
26
  import { runMediaCommand } from './media-command.mjs';
28
27
  import { resolveRuntimePaths } from './runtime-diagnostics.mjs';
@@ -37,7 +36,12 @@ import { resolveRuntimePaths } from './runtime-diagnostics.mjs';
37
36
  * options 経由で差し替え可能にしてあり、node --test から実プロセスを起動せずに分岐を検証できる。
38
37
  */
39
38
  export async function run(args, options = {}) {
40
- if (args[0] === 'chrome') return runChromeCommand(args.slice(1), options);
39
+ const retiredBrowserCommand = 'chrome';
40
+ if (args[0] === retiredBrowserCommand) {
41
+ const error = options.error ?? ((line) => console.error(line));
42
+ error(`akari ${retiredBrowserCommand} は廃止されました(Chrome は不要になりました)`);
43
+ return { exitCode: 1 };
44
+ }
41
45
  if (args[0] === 'capture') return runCaptureCommand(args.slice(1), options);
42
46
  if (args[0] === 'media') return runMediaCommand(args.slice(1), options);
43
47
 
@@ -35,7 +35,6 @@ export function formatDoctorReport(report) {
35
35
  ['edit_lint', report.edit_lint.origin, report.edit_lint.path ?? '見つかりません'],
36
36
  ['ffmpeg', report.ffmpeg.origin, report.ffmpeg.path ?? '見つかりません'],
37
37
  ['ffprobe', report.ffprobe.origin, report.ffprobe.path ?? '見つかりません'],
38
- ['chrome', report.chrome.found ? 'found' : 'missing', report.chrome.path ?? report.chrome.cache_dir],
39
38
  ['gpu_export', report.gpu_export?.available ? 'ok' : 'unavailable', report.gpu_export?.reason ?? '診断情報がありません'],
40
39
  ['path', report.path.on_path ? 'ok' : 'missing', report.path.cli_shim_dir],
41
40
  ];
package/src/messages.mjs CHANGED
@@ -326,7 +326,6 @@ export function describeCliHelp() {
326
326
  ' (引数なし) プロジェクトを開いて AI エージェントを起動(未作成なら自動作成)',
327
327
  ' store connect アカウント連携(無料の素材パックと購入済み素材が使えるようになる)',
328
328
  ' sounds 公式音源ライブラリを一括ダウンロード(無料)',
329
- ' chrome install 動画書き出しに使う Chrome をダウンロードして導入する',
330
329
  ' doctor [--json] 必須部品の実在と解決元を診断する',
331
330
  ' update [--force] 更新を確認する(--force で install.sh 経路の本体を入れ直す)',
332
331
  ' status 接続状態を確認する',
@@ -1,9 +1,7 @@
1
- import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
- import { homedir } from 'node:os';
1
+ import { existsSync, readFileSync } from 'node:fs';
3
2
  import path, { dirname, join, resolve } from 'node:path';
4
3
  import { fileURLToPath } from 'node:url';
5
4
 
6
- import { resolveChromeCacheDir } from './chrome-command.mjs';
7
5
  import { findExecutable } from './path-lookup.mjs';
8
6
  import { readInstalledAppVersionInfo, readOwnVersion, resolveAkariHome } from './update-check.mjs';
9
7
 
@@ -133,7 +131,6 @@ export async function resolveDoctorReport(options = {}) {
133
131
  const pathExt = env.PATHEXT ?? process.env.PATHEXT;
134
132
  const runtime = resolveRuntimePaths(options);
135
133
  const mediaBin = await resolveMediaTools({ ...options, env, platform, pathEnv, pathExt });
136
- const chrome = await resolveChromeInstallation({ ...options, env });
137
134
  const gpuExport = await resolveGpuExportAvailability({ ...options, env, platform });
138
135
  const akariHome = resolveAkariHome(env);
139
136
  const cliShimDir = join(akariHome, 'cli', 'bin');
@@ -150,7 +147,6 @@ export async function resolveDoctorReport(options = {}) {
150
147
  ...runtime,
151
148
  ffmpeg: mediaBin.ffmpeg,
152
149
  ffprobe: mediaBin.ffprobe,
153
- chrome,
154
150
  gpu_export: gpuExport,
155
151
  path: {
156
152
  cli_shim_dir: cliShimDir,
@@ -234,48 +230,6 @@ function resolveMediaTool(name, resolver, options) {
234
230
  return executable ? { path: executable, origin: 'path' } : { path: null, origin: 'none' };
235
231
  }
236
232
 
237
- async function resolveChromeInstallation(options) {
238
- const cacheDir = resolveChromeCacheDir(options.homeDirectory ?? homedir(), options.chromeCacheDir);
239
- try {
240
- const browsers = options.loadBrowsers
241
- ? await options.loadBrowsers()
242
- : await import('@puppeteer/browsers');
243
- if (typeof browsers.Cache === 'function') {
244
- const installed = new browsers.Cache(cacheDir).getInstalledBrowsers();
245
- const chrome = installed.find((entry) => entry.browser === 'chrome' && existsSync(entry.executablePath));
246
- if (chrome) return { found: true, path: resolve(chrome.executablePath), cache_dir: cacheDir };
247
- }
248
- } catch {
249
- // optional dependency が無くても既定キャッシュ規約の直接探索で診断を続ける。
250
- }
251
- const executable = findChromeExecutable(cacheDir);
252
- return { found: executable !== null, path: executable, cache_dir: cacheDir };
253
- }
254
-
255
- function findChromeExecutable(cacheDir) {
256
- const executableNames = new Set(['chrome', 'chrome.exe', 'Google Chrome for Testing']);
257
- const walk = (directory, depth) => {
258
- if (depth > 7) return null;
259
- let entries;
260
- try {
261
- entries = readdirSync(directory, { withFileTypes: true });
262
- } catch {
263
- return null;
264
- }
265
- for (const entry of entries) {
266
- const candidate = join(directory, entry.name);
267
- if (entry.isFile() && executableNames.has(entry.name)) return resolve(candidate);
268
- }
269
- for (const entry of entries) {
270
- if (!entry.isDirectory()) continue;
271
- const found = walk(join(directory, entry.name), depth + 1);
272
- if (found) return found;
273
- }
274
- return null;
275
- };
276
- return walk(cacheDir, 0);
277
- }
278
-
279
233
  function pathContains(pathEnv, directory, platform) {
280
234
  const delimiter = platform === 'win32' ? ';' : path.delimiter;
281
235
  const normalize = (value) => {
@@ -308,7 +262,6 @@ function doctorNextSteps(report) {
308
262
  }
309
263
  if (report.ffmpeg.origin === 'none') steps.push('ffmpeg を導入し、PATH または AKARI_FFMPEG_BIN で参照できるようにしてください。');
310
264
  if (report.ffprobe.origin === 'none') steps.push('ffprobe を導入し、PATH または AKARI_FFPROBE_BIN で参照できるようにしてください。');
311
- if (!report.chrome.found) steps.push('Chrome が未導入です。`akari chrome install` を実行してください。');
312
265
  if (!report.path.on_path && report.render_cut.origin !== 'monorepo') {
313
266
  steps.push('`~/.akari/cli/bin` を PATH に追加してください。');
314
267
  }
@@ -1,5 +1,7 @@
1
1
  # レンダー基礎機能契約(速度 / クロマキー背景置換 / 基本トランジション / LUT / 音声マスター処理 / 画角操作 / フリーズ)
2
2
 
3
+ > **2026-09-01 終了注記:** ffmpeg フィルタグラフ合成は #130d で退役した。以下の ffmpeg 実装記述は参照記録として残す。
4
+
3
5
  - 日付: 2026-07-22(2026-08-06 追記: #6 画角操作 / #7 フリーズを増築。2026-08-09 追記:
4
6
  `layers[].keyframes` への一般化を §4-4 に追記)
5
7
  - 状態: **draft**(実装と並走で approved 化)。本書は技術仕様のみ。
@@ -40,8 +42,7 @@
40
42
  素の色のまま合成されて、窓の継ぎ目で肌色が食い違った。「プロジェクト全体の色」だと
41
43
  誤解しやすいため、ここに明記する。
42
44
  4. **書き出しエンジンの既定(2026-08-28 改訂)**: `--engine` 省略時は `auto`。
43
- darwin / win32 では適格なら GPU、不適格なら OSR、linux では legacy に解決する。従来経路へ戻す場合は
44
- `render-cut --engine legacy` を明示する。
45
+ platform で適格なら GPU、不適格なら OSR に解決する。`legacy` は廃止済み。
45
46
 
46
47
  ### 2-4. reveal 系トランジション(`reveal-down` / `reveal-up`。2026-08-14 追加)
47
48
 
@@ -133,7 +134,7 @@ perspective のレイヤー分割フォールバック — perspective は `crop
133
134
  transform キーフレーム無し、キーフレーム rotate が非 0、rotate が時間変化する構成は、rotate
134
135
  ステップが実際に出るため従来互換経路を維持する。
135
136
 
136
- ## 5. macOS の字幕レンダ用 Chrome 起動(2026-08-24 追記)
137
+ ## 5. 退役済みブラウザラスタライズの参照記録(2026-09-01 終了)
137
138
 
138
139
  - macOS では `.app` 内の Chrome 実行ファイルを子プロセスとして直接起動しない。書き出し専用の
139
140
  一意な `user-data-dir` を作り、`/usr/bin/open -na <Chrome.app> --args` で LaunchServices
@@ -61,6 +61,12 @@ cut 境界の選択は宣言順ではなく解決済みタイムラインと z-o
61
61
  指定補間を使う。
62
62
  - `cuts[].transform` は出力中央を基準に scale、rotate、x / y を適用し、`opacity` は合成前の alpha に掛ける。
63
63
  - framing と transform は一つの評価グラフで順序を固定する。器の CSS pivot や要素箱へ意味論を委ねない。
64
+ - v2 media item(edit-store が `cuts` へ投影するもの)の `crop`(静的)と `keyframes[]` の `transform` / `crop` /
65
+ `opacity` は **layer-style**(ソース実寸 × scale の box、crop 窓、box 中心の rotate。§2.3 の layer と同じ幾何)で
66
+ 評価し、frame-engine の base 経路が GPU / OSR 書き出しへそのまま描く(issue #39・2026-09-01)。`keyframes[].t` は
67
+ cut の出力ローカル秒(freeze 中も進む)。`crop` / `perspective` / 2 点以上の `keyframes` を持たない cut は従来の
68
+ fit 基準のままバイト同一。`perspective` は base 経路では未適用で、
69
+ `cut <id>: perspective is not applied by the frame-engine base path yet (issue #39)` を warning に出す(無警告で捨てない)。
64
70
  - `freeze = {at_sec, duration_sec}` は指定 frame を保持し、cut の出力尺を `duration_sec` だけ伸ばして
65
71
  後続の逐次 cut を移動する。freeze の画と独立音声予定表を混同しない。
66
72
 
@@ -151,6 +157,8 @@ SHA-256 一致を要求する。GPU は同一マシン一致率を診断値と
151
157
  | 時刻 `T`、cuts、gap、track | ✅ 評価 | ✅ 呼び出し・提示 | ✅ 呼び出し・提示 | ✅ 連番駆動 | ✅ 連番駆動 |
152
158
  | framing / transform / opacity / freeze | ✅ 評価 | ✅ 完成 frame を提示 | ✅ 完成 frame を提示 | ✅ 完成 frame を捕捉 | ✅ canvas を直結 |
153
159
  | layers / perspective / keyframes | ✅ 評価 | ✅ 完成 frame を提示 | ✅ 完成 frame を提示 | ✅ 完成 frame を捕捉 | ✅ canvas を直結 |
160
+ | cuts の crop / transform / opacity keyframes(v2 media item・layer-style) | ✅ 評価(2026-09-01) | 🟡 `public/frame-engine.bundle.js` の再生成待ち | ✅ DOM 層(`applyLayerStyleMediaLayout`) | ✅ 完成 frame を捕捉 | ✅ canvas を直結 |
161
+ | cuts の perspective | 🟡 未適用・warning のみ(issue #39) | 🟡 同左 | ✅ DOM 層 | 🟡 warning を run.json へ | 🟡 warning を run.json へ |
154
162
  | 5 transitions | ✅ 評価 | ✅ 完成 frame を提示 | ✅ 完成 frame を提示 | ✅ 完成 frame を捕捉 | ✅ canvas を直結 |
155
163
  | matte / chroma key | ✅ 評価 | ✅ stamp 同期 | ✅ stamp 同期 | ✅ stamp 同期・捕捉 | ✅ 同一 frame 評価 |
156
164
  | LUT / `bt709-limited` | ✅ 評価 | ✅ 提示 | ✅ 提示 | ✅ 捕捉・encode | ✅ LUT 後 canvas を直結 |
@@ -204,7 +212,9 @@ OSR の比較は H.264 を再 decode した画像ではなく捕捉時の raw BG
204
212
  ### 4.3 互換経路の残置と退役スケジュール
205
213
 
206
214
  Web UI と shell に残る `<video>` ベースのプレビュー、および render-cut の ffmpeg filtergraph による
207
- legacy 合成経路は、移行中の既存利用者と Windows を支えるための**互換期間の残置**である。どちらも
215
+ > **2026-09-01 退役:** 以下の互換期間の記述は履歴記録であり、現在の書き出し出口は GPU / OSR のみ。
216
+
217
+ legacy 合成経路は、移行中の既存利用者と Windows を支えるための**互換期間の残置**だった。どちらも
208
218
  エンジン意味論や完成画の仕様の正本ではない。
209
219
 
210
220
  - `<video>` ベースの二つの互換プレビューは、各器で frame-engine が既定になった後 **2 リリース**保持する。
@@ -47,6 +47,62 @@ positive `duration_sec`, a non-empty CSS `ease`, and a positive `amp`; `ease` an
47
47
  selected recipe, and `loop` repeats it for the caption lifetime. The single-line sequential policy
48
48
  continues to disable caption animation as specified above.
49
49
 
50
+ ### 1.2 Reference-height scaling for zone captions (2026-09-01, issue #40 §2)
51
+
52
+ The shared `textStyle` contract accepts an optional `reference_height_px` (integer >= 1) in both
53
+ `default_text_style` and `captions[].text_style`; the cue-level value overrides the default field by
54
+ field like every other text-style field. It declares that the pixel fields of that text style were
55
+ written for an output whose height is `reference_height_px`. The kernel resolves
56
+
57
+ `scale = output.height / reference_height_px` (omitted: `scale = 1`)
58
+
59
+ and multiplies every declared pixel field by it. The reference is the output *height* because type
60
+ size is a vertical quantity, so the same declaration stays natural for portrait outputs. Without a
61
+ declaration nothing changes: an existing project renders byte-identical CSS variables (the zone path
62
+ kept `scale = 1` before this contract, which is why a 720p `size_px: 36` came out at one third of the
63
+ frame height on a 4K export).
64
+
65
+ `reference_height_px` and `layout` (reference-pixel) are mutually exclusive. The exclusion is enforced
66
+ three times in the same shape as `zone` + `layout`: `captions.schema.json`
67
+ (`allOf: [{ not: { required: ["layout", "reference_height_px"] } }]`), the kernel
68
+ (`STYLE_LAYOUT_CONFLICT` from `validateCaptionTextStyle`, `mergeCaptionDisplayStyles`, and
69
+ `resolveCaptionStyleForOutput`), and edit-lint (`captions.text-style`). `layout` keeps its own
70
+ `output.width / reference_width_px` scale unchanged. When `reference_height_px` is present and
71
+ `output.height` is unknown, the kernel fails with `INVALID_OUTPUT_GEOMETRY`, the same code the
72
+ reference-pixel path uses.
73
+
74
+ Scaled fields are exactly the fields that produce CSS `px`. Fields declared in `em`, `%` / `pct`,
75
+ frame ratios, or unitless factors are untouched. Renderer defaults that stand in for an omitted field
76
+ (the 1.5px stroke, the 40px glow spread, the 38px default font size) are not declarations and are not
77
+ scaled, matching the reference-pixel path.
78
+
79
+ | Field | CSS variable | Scaled |
80
+ |---|---|---|
81
+ | `size_px` | `--caption-font-size` | yes |
82
+ | `stroke.width_px` | `--caption-webkit-text-stroke` / `--caption-text-shadow` (kernel), `--caption-stroke` (render-cut) | yes |
83
+ | `shadow.blur_px`, `shadow.distance_px` | `--caption-text-shadow` | yes |
84
+ | `glow.spread`, `glow.offset_x`, `glow.offset_y` | `--caption-text-shadow` | yes |
85
+ | `background.radius_px` | `--plate-radius` / `--plate-block-radius` / `--plate-ext-radius` | yes |
86
+ | `background.padding_px` | `--plate-pad-x` / `--plate-pad-y` / `--plate-ext-width` / `--plate-ext-height` | yes |
87
+ | `background.offset_x`, `background.offset_y` | `--plate-offset-x` / `--plate-offset-y` | yes |
88
+ | `letter_spacing_em` | `--caption-letter-spacing` (`em`) | no |
89
+ | `max_width_pct`, `background.width_pct`, `background.height_pct` | `--caption-line-max-width` / `--plate-ext-*` (`%`) | no |
90
+ | `line_height`, `shadow.opacity`, `shadow.angle_deg`, `glow.density`, `background.opacity` | unitless | no |
91
+ | `position.x`, `position.y` | frame ratio 0..1 | no |
92
+
93
+ The scale has one definition, `resolveCaptionReferenceScale` in
94
+ `packages/edit-store/src/caption-display.ts` (with `scaleCaptionPx`, which rounds a scaled value to
95
+ six decimals and returns the input untouched at `scale = 1`). `resolveCaptionStyleForOutput` applies
96
+ it to the fields the kernel emits (`size_px`, `stroke.width_px`, `background.radius_px`);
97
+ `packages/render-cut/src/captions.mjs` `captionTextStyleVars(style, output)` applies the same scale
98
+ to every field in the table for the zone rail that both the GPU page builder and the OSR page builder
99
+ consume through `generateCaptionOverlays`, so both export engines see the same effective pixels; the
100
+ GPU sprite manifest's `emPx` is read back from that `--caption-font-size`, not from the raw `size_px`.
101
+ Real-render evidence (task 2026-09-01-caption-style-reference-scale): one caption declared with
102
+ `size_px: 36` / `reference_height_px: 720` measures the same 4.72% of the frame height at 1280×720 and
103
+ at 3840×2160 (34 px and 102 px), while the same caption without the declaration measures 1.57% at
104
+ 3840×2160 (the pre-contract behaviour).
105
+
50
106
  ## 2. Encoding resolution
51
107
 
52
108
  `output.encoding` accepts `quality: master|high|standard|light` and
@@ -6,9 +6,8 @@ updated: 2026-08-18
6
6
 
7
7
  # v1 レンダー経路パリティ契約 — cuts[].at / cuts[].track
8
8
 
9
- > **互換期間の注記(2026-08-28):** 本書は render-cut legacy 合成経路にだけ適用する契約であり、
10
- > エンジン v2 の意味論や出口の正本ではない。Windows の OSR 移行条件が成立するまで有効とし、
11
- > legacy 経路を削除する #100b で本契約も廃止する。
9
+ > **終了注記(2026-09-01):** 本書が対象とした ffmpeg 合成経路は #130d で退役した。
10
+ > 実装記述は参照記録として残す。
12
11
 
13
12
  - 日付: 2026-08-18
14
13
  - 状態: **ドラフト**(実装と同時に確定させる)
@@ -105,8 +105,8 @@ SVG の入力は data URL に固定する。Blob URL と同一オリジン HTTP
105
105
  2 走一致、製品経路の読み戻し 0 を要求する。性能 gate は cue ラスタ p50 500 ms 以下、karaoke 44 cue の
106
106
  akari-video-pv 18 ms/コマ以下、小 fixture(360 コマ・3 cue)の RSS peak 900 MB 以下とする。
107
107
 
108
- `--engine auto` macOS / Windows で全件適格なら `gpu`、不適格なら `osr` を選ぶ。Linux の `auto` は
109
- `legacy` を維持する。明示 `--engine gpu` と不適格の組み合わせは理由を全件表示して fail-closed とし、
108
+ `--engine auto` は全 platform で全件適格なら `gpu`、不適格なら `osr` を選ぶ。明示
109
+ `--engine gpu` と不適格の組み合わせは理由を全件表示して fail-closed とし、
110
110
  黙って OSR へ変更しない。GPU launcher が利用できない明示指定も fail-closed とする。
111
111
 
112
112
  ## 3. 合成順と LUT
@@ -135,18 +135,28 @@ raw frame 読み戻しではない。
135
135
 
136
136
  ## 5. エンコード、mux、音声
137
137
 
138
- 映像は H.264 `avc1.640028`、2 秒ごとの keyframe とし、製品は hardware preference、
138
+ 映像は H.264 High profile とし、level は解像度・fps・ビットレートを満たす最小値を H.264 Table A-1
139
+ (MaxFS / MaxMBPS / MaxBR × 1.25)から導出する。下限は Level 4.0 で、1080p30 = `avc1.640028`(従来と
140
+ バイト同一)、1080p60 = `avc1.64002a`、1440p30 = `avc1.640032`、4K30 = `avc1.640033`、4K60 = `avc1.640034`。
141
+ `codec` オプションで明示の文字列に上書きできる(2026-09-01 改訂。固定 `avc1.640028` は Blink の
142
+ `VerifyCodecSupportStatic` が MaxFS 8192 MB 超を拒否するため、1440p / 4K が HW / SW を問わず全 OS で
143
+ `isConfigSupported=false` になっていた — 2026-08-29 調査 §5-4)。
144
+ 2 秒ごとの keyframe とし、製品は hardware preference、
139
145
  `--soft` は software preference を指定する。ビットレートは render-cut の quality プリセットにある
140
- GPU ビットレート値(mac では VideoToolbox 用の値と共用)を正本とし、`high = 12 Mbps`、
141
- `standard = 8 Mbps`、`light = 5 Mbps` とする。
142
- `--bitrate` の明示値は quality より優先する。`master` VideoToolbox ビットレートを宣言しないため、
146
+ GPU ビットレート値(mac では VideoToolbox 用の値と共用)を正本とし、**1080p(1920×1080)基準**で
147
+ `high = 12 Mbps`、`standard = 8 Mbps`、`light = 5 Mbps` とする。出力ピクセル数が 1080p を超えるときは
148
+ その比で増やし(4K = 4 倍で `high = 48 Mbps`、1440p ≈ 1.78 倍、100 kbps 単位に丸め)、基準未満は 1 倍に
149
+ 留める(2026-09-01 改訂。receipt の `bitrateSource` は `quality-preset-scaled`)。
150
+ `--bitrate` の明示値は quality より優先し、スケールしない。`master` は VideoToolbox ビットレートを宣言しないため、
143
151
  GPU 出口では `--bitrate` が無ければ理由付きで fail-closed にする。
144
152
 
145
- エンコード済み Annex B sample を main process へ渡し、
146
- SPS/PPS または decoder config から avcC を作って mp4box へ直接格納する。追加の映像 process は起動しない。
147
- MP4 の時刻は timescale 1,000,000 上で各境界を `round(frameIndex × timescale / fps)` として求め、
148
- sample duration を隣接境界の差にする。track `duration` / `media_duration` は全 sample duration
149
- 総和、すなわち `round(frames × timescale / fps)` と一致させ、1 コマ尺の丸めを累積してはならない。
153
+ エンコード済み Annex B sample を main process へ渡し、逐次 muxer が SPS/PPS または decoder config から
154
+ avcC を作って `out.mp4` へ直接書き足す。映像の仮ファイルも追加の映像 process も作らない。
155
+ MP4 timescale `frameRateRational(fps)` が返すレート分子、1 コマは同じ関数が返す分母ティックとし、
156
+ track duration `frames × frameTicks` にする。dts = cts とし、ctts は持たない。
157
+ `frames` から計算した上界の `free` 箱を `ftyp` の直後に予約し、finish で moov をその先頭へ上書きして
158
+ 余りを `free` として残す。mdat は先頭から 64-bit largesize、サンプル位置は co64 で表し、本文を移動せず
159
+ moov を mdat より前に置く。2026-09-01 改訂。#37(ffmpeg remux・2026-08-31〜09-01)を経てこの形へ移行した。
150
160
 
151
161
  GPU 映像は video-only である。現行の正典 audio filtergraph が入力 0 の音声を読めるよう、元の cut 音声を
152
162
  copy し、音声がない場合は `frames / fps` 秒の無音 carrier を付ける。以後の mux は `-c:v copy` と
@@ -175,7 +185,7 @@ GPU と OSR の decode 比較は、engine-only 区間の per-frame MAD 1.0 以
175
185
  "provenance": {
176
186
  "engine": "gpu",
177
187
  "launcher_tier": 2,
178
- "mux": "mp4box-direct",
188
+ "mux": "incremental-mp4",
179
189
  "video_reencode": false
180
190
  },
181
191
  "gpu": {
@@ -211,8 +221,7 @@ OSR receipt と同じ warning/hard-stop 語彙を使う。`--engine osr` と `--
211
221
 
212
222
  - 語矩形で表せない演出、色補間と幾何変形が同居する cue、縦書きの語単位字幕は glyph atlas 等の次段が必要。
213
223
  - 動的自由 HTML は OSR または事前ベイクが必要。
214
- - Windows launcher tier 1 / 2 があれば `auto` で適格時に GPU、不適格時に OSR を使う。Linux は
215
- `--engine gpu` 明示時だけ GPU を利用でき、`auto` は legacy を維持する。
224
+ - platform launcher tier 1 / 2 があれば `auto` で適格時に GPU、不適格時に OSR を使う。
216
225
  - 長尺の区間並列、複数 process 並列は非対応。
217
226
  - ~~インストール済みデスクトップアプリ経由(launcher tier 1)の GPU 書き出しは未配線~~ **2026-08-29 解消**: shell の `electron-entry.js` が `--akari-main packages/gpu-export/src/electron-main.mjs` を受け、`buildElectronArguments` が tier 1 にそれを渡す(osr 契約 §6)。`resolveGpuLauncher` の fail-closed(allowDesktop 既定 false)は v0.1.28 で解除。以下は v0.1.26〜v0.1.27 の記録: (v0.1.25 で判明)。shell の `--render` は OSR ランタイムしか読まず、`buildElectronArguments` は tier 2 にしか mainScript を渡さない。v0.1.26 から `resolveGpuLauncher` は tier 1 を候補から外す(fail-closed): `auto` は OSR へ(provenance に `engine_fallback` と理由)、`--engine gpu` 明示は拒否。tier 1 の配線(shell contribution に GPU ランタイム選択を足す)は別票。
218
227
 
@@ -222,7 +231,7 @@ OSR receipt と同じ warning/hard-stop 語彙を使う。`--engine osr` と `--
222
231
  |---|---|---|---|
223
232
  | macOS | 明示利用可 | 適格なら GPU、不適格なら OSR | tier 1 / 2(ただし tier 1 は現状未配線で fail-closed) |
224
233
  | Windows | 明示利用可 | 適格なら GPU、不適格なら OSR | tier 1 / 2(同上) |
225
- | Linux | 明示利用可 | legacy のまま | tier 1 / 2(同上) |
234
+ | Linux | 明示利用可 | 適格なら GPU、不適格なら OSR | tier 1 / 2(同上) |
226
235
 
227
236
  npm Electron の tier 2 は `node_modules/electron/path.txt` を必須とする。値は win32 が
228
237
  `electron.exe`、darwin が `Electron.app/Contents/MacOS/Electron`、linux が `electron` である。
@@ -4,18 +4,20 @@
4
4
 
5
5
  この契約は `render-cut --engine osr` が生成する映像ページと、そのページを Electron オフスクリーン描画で駆動するプロトコルを定める。
6
6
 
7
- **2026-08-28 改訂:** `--engine` の既定は `auto` とし、次のように解決する。従来経路へ戻す場合は
8
- プラットフォームを問わず `--engine legacy` を明示する。
7
+ **2026-09-01 改訂:** `--engine` の既定は `auto` とし、全 platform で同じ規則に解決する。
8
+ `legacy` は廃止済みで、OSR launcher の tier 3 は明示エラーになる。
9
9
 
10
10
  | platform | `auto` の解決 | 備考 |
11
11
  |---|---|---|
12
- | darwin | `osr` | v2 を既定とする |
13
- | win32 | 適格なら `gpu`、不適格なら `osr` | GPU / OSR launcher が利用不能なら順に `legacy` へフォールバック |
14
- | linux | `legacy` | OSR opt-in |
12
+ | darwin | 適格なら `gpu`、不適格なら `osr` | GPU 実行体なしは OSR、OSR 実行体なしはエラー |
13
+ | win32 | 適格なら `gpu`、不適格なら `osr` | GPU 実行体なしは OSR、OSR 実行体なしはエラー |
14
+ | linux | 適格なら `gpu`、不適格なら `osr` | GPU 実行体なしは OSR、OSR 実行体なしはエラー |
15
15
 
16
16
  `.akari/render.json` の provenance は、指定値を `engine_requested`、解決後の実走値を `engine` に
17
- 記録する。OSR launcher が tier 3 へフォールバックした場合、`engine` `legacy` とし、
18
- `engine_fallback: { from: "osr", reason: <launcher.reason> }` を追加する。
17
+ 記録する。OSR launcher が tier 3 の場合は、Electron の入手方法(アプリ同梱 / `npm install electron` /
18
+ `AKARI_OSR_ELECTRON`)を示して exit 2 で停止し、render.json `engine_fallback` は書かない。
19
+ `engine_fallback` は gpu → osr の 1 種だけで、`auto` が `gpu` に解決した後、その launcher が
20
+ tier 3 の場合に `{ from: "gpu", reason: <launcher.reason> }` を記録する。
19
21
 
20
22
  ## 2. ページ契約
21
23
 
@@ -93,9 +95,15 @@ ffprobe timeoutは `max(120000, frames × 100)` msとする。尺、フレーム
93
95
 
94
96
  ## 8. メモリと長尺
95
97
 
96
- - GPU描画の警戒線: 768 MiB / export、hard stop: 1,024 MiB / export
98
+ - GPU描画の警戒線: 768 MiB / export、hard stop: 1,024 MiB / export(1080p 基準)。
97
99
  - ソフト描画(SwiftShader)は1080pで1.1 GiB台に達するため、警戒線1,536 MiB / hard stop 2,048 MiBの別枠を使う。
98
- - `AKARI_OSR_MEMORY_WARN_MIB` / `AKARI_OSR_MEMORY_HARD_STOP_MIB`で正の整数MiBへ上書きでき、適用値はwarning < hard stopを必須とする。
100
+ - 出力ピクセル数が 1080p(1920×1080)を超えるときは既定値をその比で増やす(4K = 4 倍、MiB 切り上げ)。
101
+ ただしスケール後の hard stop は物理メモリの 50% を上限とし、超えるときは切り詰めて warning を hard stop の
102
+ 75% に置く(`memory.machine_capped` を receipt に残す)。1080p 以下の既定値は機種に関係なく変えない
103
+ (2026-09-01 改訂。4K の係数は予測値で未較正 — 初回の実測 peak で較正する)。
104
+ - `AKARI_OSR_MEMORY_WARN_MIB` / `AKARI_OSR_MEMORY_HARD_STOP_MIB`で正の整数MiBへ上書きでき(絶対値・スケールと上限を受けない)、
105
+ 適用値はwarning < hard stopを必須とする。hard stop だけを上書きし既定 warning がそれ以上になるときは warning を hard stop の 75% に追従させる。
106
+ 同じ変数を GPU 直結出口(gpu-export)も読む。
99
107
  - 並列予算1 worker = 1 GiBはGPU前提の値である。v0のworker数は1。
100
108
  - 10秒ごとにRSSを記録し、ウィンドウ破棄後も採る。
101
109
  - 固定Nコマごとのページ再生成は行わない。再生成を許すのはページ境界、renderer crash、watchdog回復時だけである。
@@ -114,6 +114,26 @@ updated: 2026-08-30
114
114
  - 折りたたみ・選択・フォーカス中のスコープは**表示状態**で、edit.json に保存しない
115
115
  - `id` は木全体で一意(袋から写した子の id は `<袋 id>.<名札>` を UI が合成する。明示アイテムにした時点でその id が edit.json に書かれる)
116
116
 
117
+ ### 1.8 語彙と提示(2026-09-01・オーナー裁定): 「グループ」は純グループだけを指す
118
+
119
+ データ模型は §1.2(**純グループ** = 人間が ⌘G で作る)と §1.3(**袋グループ** = 取り込み時にデータ由来でできる)を既に分けている。**UI ではこの 2 つを同じ語で呼ばない。**
120
+
121
+ | | 純グループ(§1.2) | 袋(§1.3) |
122
+ |---|---|---|
123
+ | 誰が作るか | 人間が ⌘G で作る | データがそうなっている(勝手にできる) |
124
+ | UI での呼び名 | **「グループ」** | 素材の種類で呼ぶ(「字幕」「オーバーレイ」「SFX」)。**グループとは呼ばない** |
125
+ | 展開トグル | **出す** | **出さない** |
126
+ | ばらす(⌘⇧G) | できる | **できない**(§3.3 で既定済み) |
127
+
128
+ - **1 トラック = 常に 1 行。** 葉のアイテム(子を持たないもの)は帯の中のチップであって、ヘッダ列の行ではない。ヘッダ列の行になるのは (a) 子を持つ純グループ (b) 展開中の純グループの子 だけ
129
+ - **袋の子へ届く経路は 2 つ**: (a) 帯のチップを直接クリックして選ぶ (b) ダブルクリックでフォーカスモードに入る(§7 の「中に入る」)。袋を段の上で展開する操作は**無い**
130
+ - 畳んだ帯に子の位置を刻みで示すのは従来どおり(どこに何があるかは帯で分かる)
131
+ - 「出す」(§3.1)・「テロップに変換」(§1.5)は**帯のチップに対する操作**として従来どおり使える(行が無くても届く)
132
+
133
+ **根拠**: 袋は「データがそうなっている入れ物」で、人間が作ったものでも、ばらせるものでもない。それを UI で「グループ」と呼ぶと「ではばらせるのか」「展開する必要があるのか」という**答えのない問い**が立つ。実案件(SFX 30・字幕 30 行)で葉まで行にした結果ヘッダ列が 71 行に膨らみ、この混同が破綻として顕在化した。
134
+
135
+ **変わるのは提示だけ**(§1.3 の「折りたたみは UI の表示状態であってデータではない」は維持。データ形式・直列化・lint は不変)。
136
+
117
137
  ## 2. 段(`tracks[]`)の不変条件
118
138
 
119
139
  | # | 不変条件 | 破ったとき |
@@ -75,6 +75,9 @@ updated: 2026-08-30
75
75
  - 参照形は `path`(プロジェクト相対)+ `count`(袋の中のその item の点数。lint が突き合わせる)。読み込み層は参照を解決して inline と同じ配列を消費者へ渡す(消費者は形を区別しない)
76
76
  - `<group-id>` = そのアイテムが属する**最も近いグループ**の id(段直下のアイテムは自分の id)。フォーカスモードの単位 = 袋の単位
77
77
 
78
+ media item(cuts)での適用先 = frame-engine base 経路(2026-09-01・issue #39): edit-store が cut へ投影した `crop` /
79
+ `keyframes[]`(`transform` / `crop` / `opacity`)を GPU / OSR 書き出しが layer-style の幾何で評価する。`perspective` は未適用で warning。
80
+
78
81
  ## 3. `motion/<group-id>.json` 袋
79
82
 
80
83
  ```jsonc
@@ -52,20 +52,6 @@ DL・sha256 検証・適用まで実行。それ以外(npm グローバル / g
52
52
  `packages/asset-resolver` の CLI への薄い委譲で、カタログ合成・entitlements 判定・
53
53
  sha256 検証・fail-closed は resolver 側の責務のまま。`src/assets-command.mjs`)。
54
54
 
55
- ### Chrome の導入
56
-
57
- 動画の書き出しに使う Chrome for Testing は、次のコマンドで導入できる。
58
-
59
- ```sh
60
- akari chrome install
61
- akari chrome install --cache-dir /path/to/cache
62
- akari chrome install --build-id 150.0.7339.16
63
- ```
64
-
65
- 保存先の既定は `~/.cache/puppeteer`。`--cache-dir` で保存先、`--build-id` で導入する
66
- buildId を指定できる。実行環境から `@puppeteer/browsers` を解決できる必要があり、
67
- AKARI Video アプリの配布物には必要なモジュールが同梱されている。
68
-
69
55
  `akari` に渡した引数はそのまま `opencode` に転送する(例: `akari --continue` は
70
56
  `opencode --continue` を起動する)。
71
57
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
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": [
@@ -3126,6 +3126,17 @@ function validateTextStyle(value, label, findings, path) {
3126
3126
  path,
3127
3127
  );
3128
3128
  }
3129
+ if (
3130
+ Object.hasOwn(value, "reference_height_px")
3131
+ && (!Number.isInteger(value.reference_height_px) || value.reference_height_px < 1)
3132
+ ) {
3133
+ captionFinding(
3134
+ findings,
3135
+ "captions.text-style",
3136
+ `${label}.reference_height_px must be an integer greater than or equal to one`,
3137
+ path,
3138
+ );
3139
+ }
3129
3140
  if (Object.hasOwn(value, "font_weight") && (!Number.isInteger(value.font_weight) || value.font_weight < 1 || value.font_weight > 1000)) {
3130
3141
  captionFinding(findings, "captions.text-style", `${label}.font_weight must be an integer within [1, 1000]`, path);
3131
3142
  }
@@ -3158,6 +3169,14 @@ function validateTextStyle(value, label, findings, path) {
3158
3169
  path,
3159
3170
  );
3160
3171
  }
3172
+ if (Object.hasOwn(value, "layout") && Object.hasOwn(value, "reference_height_px")) {
3173
+ captionFinding(
3174
+ findings,
3175
+ "captions.text-style",
3176
+ `${label} cannot contain both layout and reference_height_px`,
3177
+ path,
3178
+ );
3179
+ }
3161
3180
  }
3162
3181
 
3163
3182
  function validateCaptionAnimation(value, label, findings, path) {
@@ -96,6 +96,22 @@ export declare function scheduleCaptionFragments(start: number, end: number, fra
96
96
  text: string;
97
97
  }>;
98
98
  export declare function mergeCaptionDisplayStyles(base: unknown, override: unknown): UnknownRecord | undefined;
99
+ /**
100
+ * zone 方式の px 系フィールドに掛ける scale(issue #40 §2)。`reference_height_px` が無ければ 1
101
+ * (既存出力はバイト同一)。あれば output.height / reference_height_px — 基準は高さ(文字サイズは
102
+ * 縦方向の量。縦型出力でも自然)。`layout`(reference-pixel)との併用は禁止。output.height が無いと
103
+ * layout 経路の INVALID_OUTPUT_GEOMETRY と同型で fail する。render-cut の captionTextStyleVars と
104
+ * gpu-export page-builder はこの単一定義を使い、GPU / OSR の両経路で同じ実効 px になる。
105
+ */
106
+ export declare function resolveCaptionReferenceScale(style: unknown, output: {
107
+ width?: number;
108
+ height?: number;
109
+ } | undefined): number;
110
+ /**
111
+ * 宣言 px × scale。scale === 1 なら値をそのまま返す(`${value}` の文字列が従来とバイト同一)。
112
+ * それ以外は小数 6 桁へ丸めて浮動小数の端数(0.1 × 3 = 0.30000000000000004)を CSS に漏らさない。
113
+ */
114
+ export declare function scaleCaptionPx(value: number, scale: number): number;
99
115
  /**
100
116
  * text_anchor(9 点)+ position(0..1 相対)→ プレート配置の CSS 変数。単一定義
101
117
  * (プレビュー = shell captionTextStyleVars / 書き出し = render-cut captions.mjs の両消費者が