akari-video 1.0.0 → 1.0.2

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 (141) hide show
  1. package/bin/akari.mjs +67 -43
  2. package/package.json +1 -1
  3. package/src/cli.mjs +20 -12
  4. package/src/first-arg-guard.mjs +53 -0
  5. package/src/kits.mjs +5 -1
  6. package/src/media-bin-loader.mjs +37 -0
  7. package/src/messages.mjs +11 -0
  8. package/src/narration-command.mjs +6 -3
  9. package/src/runtime-diagnostics.mjs +1 -16
  10. package/src/store-command.mjs +16 -4
  11. package/src/store-device-connect.mjs +2 -2
  12. package/src/vendor-sources.mjs +2 -0
  13. package/src/voice-command.mjs +6 -4
  14. package/vendor/.akari-capability-sources.json +2 -0
  15. package/vendor/docs/contract-2026-07-13-asset-library.md +3 -3
  16. package/vendor/docs/contract-2026-08-01-export-nle-beta.md +11 -4
  17. package/vendor/docs/contract-2026-08-28-osr-export-v0.md +2 -2
  18. package/vendor/docs/contract-2026-09-02-shape-item-v0.md +1 -0
  19. package/vendor/docs/contract-2026-10-02-fragment-font-subset-v0.md +44 -0
  20. package/vendor/docs/contract-2026-10-02-textstyle-v1-rich.md +3 -3
  21. package/vendor/docs/contract-2026-10-04-asset-tier-v1.md +44 -0
  22. package/vendor/packages/akari-launcher/package.json +1 -1
  23. package/vendor/packages/asset-resolver/bin/akari-assets.mjs +12 -11
  24. package/vendor/packages/asset-resolver/browse/app.js +5 -4
  25. package/vendor/packages/asset-resolver/src/add.mjs +1 -1
  26. package/vendor/packages/asset-resolver/src/browse-server.mjs +41 -7
  27. package/vendor/packages/asset-resolver/src/catalog.mjs +58 -5
  28. package/vendor/packages/asset-resolver/src/entitlements.mjs +20 -13
  29. package/vendor/packages/asset-resolver/src/errors.mjs +1 -1
  30. package/vendor/packages/asset-resolver/src/fetch-file.mjs +52 -3
  31. package/vendor/packages/asset-resolver/src/installed.mjs +16 -3
  32. package/vendor/packages/asset-resolver/src/library.mjs +2 -1
  33. package/vendor/packages/asset-resolver/src/paid-zip.mjs +6 -4
  34. package/vendor/packages/asset-resolver/src/project-references.mjs +102 -12
  35. package/vendor/packages/asset-resolver/src/resolve.mjs +115 -38
  36. package/vendor/packages/asset-resolver/src/state.mjs +23 -15
  37. package/vendor/packages/asset-resolver/src/tier.mjs +16 -0
  38. package/vendor/packages/asset-resolver/test/add.test.mjs +3 -2
  39. package/vendor/packages/asset-resolver/test/automatic-network-check.test.mjs +55 -0
  40. package/vendor/packages/asset-resolver/test/browse-server.test.mjs +132 -0
  41. package/vendor/packages/asset-resolver/test/catalog-and-state.test.mjs +58 -2
  42. package/vendor/packages/asset-resolver/test/fixtures/build-fixture-library.mjs +2 -2
  43. package/vendor/packages/asset-resolver/test/installed-assets.test.mjs +62 -4
  44. package/vendor/packages/asset-resolver/test/library-check.test.mjs +1 -1
  45. package/vendor/packages/asset-resolver/test/library-migration.test.mjs +1 -1
  46. package/vendor/packages/asset-resolver/test/local-library.test.mjs +15 -4
  47. package/vendor/packages/asset-resolver/test/project-references-concurrency.test.mjs +88 -0
  48. package/vendor/packages/asset-resolver/test/resolve-category-ref.test.mjs +52 -0
  49. package/vendor/packages/asset-resolver/test/resolve-fast.test.mjs +134 -0
  50. package/vendor/packages/asset-resolver/test/resolve-installed-offline.test.mjs +37 -0
  51. package/vendor/packages/asset-resolver/test/resolve-locked.test.mjs +24 -7
  52. package/vendor/packages/asset-resolver/test/resolve-paid-zip.test.mjs +113 -7
  53. package/vendor/packages/asset-resolver/test/resolve-validate-in-process.test.mjs +52 -0
  54. package/vendor/packages/asset-resolver/test/tier-access.test.mjs +86 -0
  55. package/vendor/packages/audio-library-setup/bin/beat-grid.mjs +18 -10
  56. package/vendor/packages/audio-library-setup/bin/declare-helper.mjs +15 -2
  57. package/vendor/packages/audio-library-setup/bin/fetch-akari-sounds.mjs +1 -0
  58. package/vendor/packages/audio-library-setup/bin/gallery-helper.mjs +15 -2
  59. package/vendor/packages/audio-library-setup/bin/generate-candidates-html.mjs +16 -3
  60. package/vendor/packages/audio-library-setup/bin/register-drop-folder.mjs +18 -4
  61. package/vendor/packages/audio-library-setup/bin/suggest-bgm.mjs +14 -6
  62. package/vendor/packages/audio-library-setup/gallery-server.mjs +1 -1
  63. package/vendor/packages/audio-library-setup/shared/akari-sounds.mjs +1 -0
  64. package/vendor/packages/audio-library-setup/shared/candidates.mjs +2 -0
  65. package/vendor/packages/audio-library-setup/test/akari-sounds.test.mjs +2 -0
  66. package/vendor/packages/audio-library-setup/test/cli-arguments-and-csp.test.mjs +63 -0
  67. package/vendor/packages/edit-lint/src/edit-lint.mjs +95 -6616
  68. package/vendor/packages/edit-lint/src/lint/audio.mjs +810 -0
  69. package/vendor/packages/edit-lint/src/lint/caption-style.mjs +416 -0
  70. package/vendor/packages/edit-lint/src/lint/captions.mjs +646 -0
  71. package/vendor/packages/edit-lint/src/lint/cuts-tracks.mjs +622 -0
  72. package/vendor/packages/edit-lint/src/lint/edit-v2.mjs +805 -0
  73. package/vendor/packages/edit-lint/src/lint/engine-capabilities.mjs +182 -0
  74. package/vendor/packages/edit-lint/src/lint/external.mjs +47 -0
  75. package/vendor/packages/edit-lint/src/lint/html-fragment.mjs +82 -0
  76. package/vendor/packages/edit-lint/src/lint/intake.mjs +217 -0
  77. package/vendor/packages/edit-lint/src/lint/look-adjust.mjs +229 -0
  78. package/vendor/packages/edit-lint/src/lint/media-checks.mjs +732 -0
  79. package/vendor/packages/edit-lint/src/lint/media-probe.mjs +230 -0
  80. package/vendor/packages/edit-lint/src/lint/overlay-css.mjs +359 -0
  81. package/vendor/packages/edit-lint/src/lint/overlays.mjs +564 -0
  82. package/vendor/packages/edit-lint/src/lint/review.mjs +467 -0
  83. package/vendor/packages/edit-lint/src/lint/shared.mjs +130 -0
  84. package/vendor/packages/edit-lint/src/lint/v2-projection.mjs +180 -0
  85. package/vendor/packages/edit-store/lib/caption-display.d.ts +3 -1
  86. package/vendor/packages/edit-store/lib/caption-display.js +92 -10
  87. package/vendor/packages/edit-store/lib/caption-store.d.ts +31 -0
  88. package/vendor/packages/edit-store/lib/caption-store.js +14 -0
  89. package/vendor/packages/edit-store/lib/cut-ranges.js +1 -1
  90. package/vendor/packages/edit-store/lib/edit-v2-item-write.js +13 -2
  91. package/vendor/packages/edit-store/lib/edit-v2.d.ts +2 -0
  92. package/vendor/packages/edit-store/lib/history-store.d.ts +3 -0
  93. package/vendor/packages/edit-store/lib/history-store.js +48 -2
  94. package/vendor/packages/edit-store/lib/project.js +0 -3
  95. package/vendor/packages/edit-store/lib/shape-markup-v1.js +62 -14
  96. package/vendor/packages/edit-store/lib/shape-markup.js +12 -9
  97. package/vendor/packages/edit-store/lib/shape-source-validation.js +9 -1
  98. package/vendor/packages/edit-store/lib/transform-keyframe-edit.js +9 -0
  99. package/vendor/packages/edit-store/lib/webview-kernel.js +165 -32
  100. package/vendor/packages/edit-store/lib/write-gate.js +16 -13
  101. package/vendor/packages/gpu-export/README.ja.md +3 -3
  102. package/vendor/packages/gpu-export/README.md +3 -3
  103. package/vendor/packages/media-bin/bin/audio-measure.mjs +0 -0
  104. package/vendor/packages/media-bin/src/binary-manifest.mjs +51 -0
  105. package/vendor/packages/media-bin/test/media-bin.test.mjs +15 -1
  106. package/vendor/packages/osr-export/README.md +1 -1
  107. package/vendor/packages/overlay-runtime/src/fragment-source-write.mjs +67 -0
  108. package/vendor/packages/overlay-runtime/src/handle-geometry.js +33 -9
  109. package/vendor/packages/overlay-runtime/src/interaction.js +508 -16
  110. package/vendor/packages/overlay-runtime/src/overlay-runtime.js +51 -3
  111. package/vendor/packages/render-cut/src/font-cmap.mjs +196 -0
  112. package/vendor/packages/render-cut/src/fragment-text.mjs +288 -0
  113. package/vendor/packages/schemas/asset-meta.schema.json +44 -8
  114. package/vendor/packages/schemas/bin/validate-asset.mjs +72 -20
  115. package/vendor/packages/schemas/bin/validate-sites.mjs +0 -0
  116. package/vendor/packages/schemas/edit.schema.json +4 -1
  117. package/vendor/packages/schemas/examples/kit-manifest-v1-with-asset/assets/overlay/sample-kit-frame/meta.json +1 -0
  118. package/vendor/packages/schemas/test/asset-meta-default.test.mjs +55 -5
  119. package/vendor/packages/schemas/test/edit-v2-schema.test.mjs +15 -0
  120. package/vendor/packages/schemas/test/fixtures/asset/invalid-matched-by/audio/whoosh-transition/meta.json +1 -0
  121. package/vendor/packages/schemas/test/fixtures/asset/invalid-scene3d-model-missing-glb/scene3d/hero-missing-glb/meta.json +1 -0
  122. package/vendor/packages/schemas/test/fixtures/asset/invalid-scene3d-neither/scene3d/hero-neither/meta.json +1 -0
  123. package/vendor/packages/schemas/test/fixtures/asset/valid-catalog/audio/whoosh-transition/meta.json +1 -0
  124. package/vendor/packages/schemas/test/fixtures/asset/valid-library/overlay/lower-third-clean/meta.json +2 -1
  125. package/vendor/packages/schemas/test/fixtures/asset/valid-scene3d-texts-only/scene3d/hero-texts-only/meta.json +2 -1
  126. package/vendor/packages/schemas/test/textstyle-allowlist-drift.test.mjs +20 -2
  127. package/vendor/packages/schemas/test/validate-asset.test.mjs +67 -1
  128. package/vendor/skills/analyze-footage/bin/face-expression/face-expression.mjs +35 -15
  129. package/vendor/skills/analyze-footage/bin/test/face-expression-availability.test.mjs +59 -0
  130. package/vendor/skills/analyze-footage/bin/vision-tracks/vision-tracks.mjs +49 -6
  131. package/vendor/skills/analyze-footage/test/vision-tracks-check.test.mjs +47 -6
  132. package/vendor/skills/export-nle/SKILL.md +9 -5
  133. package/vendor/skills/harvest-asset/SKILL.md +8 -7
  134. package/vendor/skills/overlay-authoring/SKILL.md +2 -0
  135. package/vendor/skills/overlay-authoring/telop.md +8 -0
  136. package/vendor/skills/setup-library/SKILL.md +1 -1
  137. package/vendor/skills/setup-library/fetch-and-validate.md +9 -3
  138. package/vendor/skills/setup-library/starter-pack.md +1 -0
  139. package/vendor/skills/verify/SKILL.md +1 -1
  140. package/vendor/packages/edit-store/lib/motion-position-write.d.ts +0 -6
  141. package/vendor/packages/edit-store/lib/motion-position-write.js +0 -14
package/bin/akari.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { run, runUpdateCommand } from '../src/cli.mjs';
2
+ import { run, runSubcommands, runUpdateCommand } from '../src/cli.mjs';
3
3
  import { runInitCommand } from '../src/init-command.mjs';
4
4
  import { runNewCommand } from '../src/new-command.mjs';
5
5
  import { runNarrationCommand } from '../src/narration-command.mjs';
@@ -20,7 +20,8 @@ import { runWorldCommand } from '../src/world-command.mjs';
20
20
  import { runSkillsCommand, refreshEntrySkillOnLaunch } from '../src/skills-command.mjs';
21
21
  import { resolveRuntimePaths } from '../src/runtime-diagnostics.mjs';
22
22
  import { maybeApplyPendingUpdateOnLaunch, resolveInstalledVersionInfo } from '../src/update-check.mjs';
23
- import { describeCliHelp, describeInstalledVersions } from '../src/messages.mjs';
23
+ import { describeCliHelp, describeInstalledVersions, firstArgumentTypoError } from '../src/messages.mjs';
24
+ import { suggestFirstArgument } from '../src/first-arg-guard.mjs';
24
25
 
25
26
  // `akari --version` / `-v`: インストール済みの版を表示するだけの最小コマンド
26
27
  // (タスク契約 2026-08-11-update-u4-cli-self-update の受け入れ条件 —
@@ -47,54 +48,77 @@ async function printCliHelp() {
47
48
  return { exitCode: 0 };
48
49
  }
49
50
 
50
- // 起動の頭で保留中の自動適用があれば適用する(契約 §11・タスク契約
51
- // 2026-08-11-update-u5-cli-auto-update)。すべてのサブコマンド分岐より前に置く —
52
- // 「起動」= akari バイナリの実行そのものであり、`--version` や `akari update` の
53
- // 入口でも既に新版へ切り替わっている必要がある(受け入れ条件: 2 回目の起動で
54
- // `akari --version` が新版を返す。スワップは rename ベースで `~/.akari/app` の
55
- // 実体を差し替えるだけなので、このプロセス自身がこの後 `readOwnVersion()` を
56
- // 読み直せば新版の内容を観測できる — 同一 argv での re-exec はしない設計判断
57
- // (理由は report.md 参照))。失敗は他の起動時副作用と同じく握りつぶし、
58
- // claude/opencode 起動やサブコマンド実行を止めない。
59
- const argv = process.argv.slice(2);
60
- const doctorJson = argv[0] === 'doctor' && argv.includes('--json');
61
- try {
62
- maybeApplyPendingUpdateOnLaunch({ env: process.env, log: doctorJson ? () => {} : (line) => console.log(line) });
63
- } catch (error) {
64
- console.error(`自動更新の適用確認でエラーが発生しました(続行します): ${error instanceof Error ? error.message : String(error)}`);
65
- }
66
- refreshEntrySkillOnLaunch({ env: process.env });
67
-
68
51
  // `akari update` / `akari init` / `akari new` / `akari narration` / `akari internal` /
69
52
  // `akari sounds` / `akari status` / `akari accept` / `akari capability` / `akari store` /
70
53
  // `akari assets` / `akari clean` は claude へ転送せず、専用のサブコマンドとして扱う(契約 §4-1 /
71
54
  // タスク契約 launcher-init(内部リポ)/ 音源カタログ既定化のオーナー裁定 2026-08-03 /
72
55
  // AKARI Store 連携 / タスク契約 2026-08-09-agent-assets-discovery)。
73
56
  // それ以外の引数はすべて従来どおり claude へ転送する。
74
- const invoke = (argv[0] === '--version' || argv[0] === '-v') ? printVersion()
75
- : (argv[0] === '--help' || argv[0] === '-h') ? printCliHelp()
76
- : argv[0] === 'doctor' ? runDoctorCommand(argv.slice(1))
77
- : argv[0] === 'update' ? runUpdateCommand(argv.slice(1))
78
- : argv[0] === 'init' ? runInitCommand(argv.slice(1))
79
- : argv[0] === 'new' ? runNewCommand(argv.slice(1))
80
- : argv[0] === 'skills' ? runSkillsCommand(argv.slice(1))
81
- : argv[0] === 'narration' ? runNarrationCommand(argv.slice(1))
82
- : argv[0] === 'voice' ? runVoiceCommand(argv.slice(1))
83
- : argv[0] === 'internal' ? runInternalCommand(argv.slice(1))
84
- : argv[0] === 'sounds' ? runSoundsCommand(argv.slice(1))
85
- : argv[0] === 'status' ? runStatusCommand(argv.slice(1))
86
- : argv[0] === 'accept' ? runAcceptCommand(argv.slice(1))
87
- : argv[0] === 'capability' ? runCapabilityCommand(argv.slice(1))
88
- : argv[0] === 'store' ? runStoreCommand(argv.slice(1))
89
- : argv[0] === 'assets' ? runAssetsCommand(argv.slice(1))
90
- : argv[0] === 'migrate' ? runMigrateCommand(argv.slice(1))
91
- : argv[0] === 'clean' ? runCleanCommand(argv.slice(1))
92
- : argv[0] === 'generate' ? runGenerateCommand(argv.slice(1))
93
- : argv[0] === 'storyboard' ? runStoryboardCommand(argv.slice(1))
94
- : argv[0] === 'world' ? runWorldCommand(argv.slice(1))
95
- : run(argv);
57
+ // 候補一覧は、この表と cli.mjs の振り分け表のキーから作る。
58
+ // 後者のコマンドは run を通し、直接 run を呼ぶ利用者とも呼び方を揃える。
59
+ const commands = {
60
+ doctor: runDoctorCommand,
61
+ update: runUpdateCommand,
62
+ init: runInitCommand,
63
+ new: runNewCommand,
64
+ skills: runSkillsCommand,
65
+ narration: runNarrationCommand,
66
+ voice: runVoiceCommand,
67
+ internal: runInternalCommand,
68
+ sounds: runSoundsCommand,
69
+ status: runStatusCommand,
70
+ accept: runAcceptCommand,
71
+ capability: runCapabilityCommand,
72
+ store: runStoreCommand,
73
+ assets: runAssetsCommand,
74
+ migrate: runMigrateCommand,
75
+ clean: runCleanCommand,
76
+ generate: runGenerateCommand,
77
+ storyboard: runStoryboardCommand,
78
+ world: runWorldCommand,
79
+ };
80
+
81
+ // トップレベルのフラグも、振り分けと候補判定に同じ表を使う。
82
+ const flags = {
83
+ '--version': printVersion,
84
+ '-v': printVersion,
85
+ '--help': printCliHelp,
86
+ '-h': printCliHelp,
87
+ };
88
+
89
+ async function main(argv) {
90
+ const suggestion = suggestFirstArgument(argv[0], {
91
+ flags: Object.keys(flags),
92
+ commands: [...Object.keys(commands), ...Object.keys(runSubcommands)],
93
+ });
94
+ if (suggestion) {
95
+ console.error(firstArgumentTypoError(argv[0], suggestion));
96
+ return { exitCode: 2 };
97
+ }
98
+
99
+ // 起動の頭で保留中の自動適用があれば適用する(契約 §11・タスク契約
100
+ // 2026-08-11-update-u5-cli-auto-update)。すべてのサブコマンド分岐より前に置く —
101
+ // 「起動」= akari バイナリの実行そのものであり、`--version` や `akari update` の
102
+ // 入口でも既に新版へ切り替わっている必要がある(受け入れ条件: 2 回目の起動で
103
+ // `akari --version` が新版を返す。スワップは rename ベースで `~/.akari/app` の
104
+ // 実体を差し替えるだけなので、このプロセス自身がこの後 `readOwnVersion()` を
105
+ // 読み直せば新版の内容を観測できる — 同一 argv での re-exec はしない設計判断
106
+ // (理由は report.md 参照))。失敗は他の起動時副作用と同じく握りつぶし、
107
+ // claude/opencode 起動やサブコマンド実行を止めない。
108
+ const doctorJson = argv[0] === 'doctor' && argv.includes('--json');
109
+ try {
110
+ maybeApplyPendingUpdateOnLaunch({ env: process.env, log: doctorJson ? () => {} : (line) => console.log(line) });
111
+ } catch (error) {
112
+ console.error(`自動更新の適用確認でエラーが発生しました(続行します): ${error instanceof Error ? error.message : String(error)}`);
113
+ }
114
+ refreshEntrySkillOnLaunch({ env: process.env });
115
+
116
+ if (Object.hasOwn(flags, argv[0])) return flags[argv[0]]();
117
+ if (Object.hasOwn(commands, argv[0])) return commands[argv[0]](argv.slice(1));
118
+ return run(argv);
119
+ }
96
120
 
97
- const result = await invoke.catch((error) => {
121
+ const result = await main(process.argv.slice(2)).catch((error) => {
98
122
  console.error(error instanceof Error ? error.message : String(error));
99
123
  return { exitCode: 1 };
100
124
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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": {
package/src/cli.mjs CHANGED
@@ -6,7 +6,7 @@ import { resolveLauncherAssets } from './repo-assets.mjs';
6
6
  import { detectProjectState } from './project-state.mjs';
7
7
  import { findClaudeExecutable, findExecutable, findOpencodeExecutable } from './path-lookup.mjs';
8
8
  import { loadTaskLabels } from './task-labels.mjs';
9
- import { describeForceReinstall, describeInstalledVersions, describeIntake, claudeMissingGuidance, opencodeMissingGuidance, describeUpdateCacheFallback, describeUpdateCommand, describeVersionStatus, formatUpdateNotice } from './messages.mjs';
9
+ import { describeForceReinstall, describeInstalledVersions, describeIntake, claudeMissingGuidance, opencodeMissingGuidance, describeUpdateCacheFallback, describeUpdateCommand, describeVersionStatus, formatUpdateNotice, nonInteractiveUninitializedGuidance } from './messages.mjs';
10
10
  import { defaultLoadCreatorRootModule, resolveEffectiveProjectRoot } from './first-run.mjs';
11
11
  import { maybeShowAssetIntroNotice } from './sounds-setup.mjs';
12
12
  import {
@@ -29,6 +29,15 @@ import { runMediaCommand } from './media-command.mjs';
29
29
  import { runWordBookCommand } from './word-book-command.mjs';
30
30
  import { resolveRuntimePaths } from './runtime-diagnostics.mjs';
31
31
 
32
+ // run を直接呼ぶ利用者と入口で、同じサブコマンドの表を使う。
33
+ export const runSubcommands = {
34
+ 'decision-log': runDecisionLogCommand,
35
+ captions: runCaptionsCommand,
36
+ capture: runCaptureCommand,
37
+ media: runMediaCommand,
38
+ 'word-book': runWordBookCommand,
39
+ };
40
+
32
41
  /**
33
42
  * `akari` ランチャーの本体。3 入口契約(ターミナル `akari` / セッション内 `/akari` /
34
43
  * アプリ接続ボタン)のうち、ターミナル入口を実装する:
@@ -45,11 +54,7 @@ export async function run(args, options = {}) {
45
54
  error(`akari ${retiredBrowserCommand} は廃止されました(Chrome は不要になりました)`);
46
55
  return { exitCode: 1 };
47
56
  }
48
- if (args[0] === 'decision-log') return runDecisionLogCommand(args.slice(1), options);
49
- if (args[0] === 'captions') return runCaptionsCommand(args.slice(1), options);
50
- if (args[0] === 'capture') return runCaptureCommand(args.slice(1), options);
51
- if (args[0] === 'media') return runMediaCommand(args.slice(1), options);
52
- if (args[0] === 'word-book') return runWordBookCommand(args.slice(1), options);
57
+ if (Object.hasOwn(runSubcommands, args[0])) return runSubcommands[args[0]](args.slice(1), options);
53
58
 
54
59
  const log = options.log ?? ((line) => console.log(line));
55
60
  const assets = options.assets ?? resolveLauncherAssets();
@@ -75,12 +80,15 @@ export async function run(args, options = {}) {
75
80
  );
76
81
 
77
82
  let projectRoot = options.projectRoot ?? process.cwd();
78
- const bareNonInteractive = args.length === 0
79
- && !(options.isTTY ?? Boolean(process.stdin.isTTY && process.stdout.isTTY));
80
- if (bareNonInteractive && !detectProjectState(projectRoot).scaffolded) {
81
- log(`このフォルダーは AKARI Video プロジェクトとしてまだセットアップされていません: ${projectRoot}`);
82
- log('非対話シェルのため状態確認のみで終了します。');
83
- return { exitCode: 0, scaffolded: false, claudeLaunched: false, opencodeLaunched: false };
83
+ const isTTY = options.isTTY ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
84
+ const bareNonInteractive = args.length === 0 && !isTTY;
85
+ if (!isTTY && !autoConfirm && !hereOnly && !detectProjectState(projectRoot).scaffolded) {
86
+ const output = bareNonInteractive ? log : (options.error ?? ((line) => console.error(line)));
87
+ output(`このフォルダーは AKARI Video プロジェクトとしてまだセットアップされていません: ${projectRoot}`);
88
+ output(bareNonInteractive
89
+ ? '非対話シェルのため状態確認のみで終了します。'
90
+ : nonInteractiveUninitializedGuidance());
91
+ return { exitCode: bareNonInteractive ? 0 : 2, scaffolded: false, claudeLaunched: false, opencodeLaunched: false };
84
92
  }
85
93
 
86
94
  // 作業場(creator-root)の初回動線(契約 §5・§6-1)。`--here` はお試しモード強制
@@ -0,0 +1,53 @@
1
+ // 1 文字の挿入・削除・置換・隣接する文字の入れ替えを 1 回の編集として数える。
2
+ // フラグとサブコマンドの候補判定に同じ距離を使う。
3
+ function editDistance(left, right) {
4
+ const rows = Array.from({ length: left.length + 1 }, () => Array(right.length + 1).fill(0));
5
+ for (let i = 0; i <= left.length; i++) rows[i][0] = i;
6
+ for (let j = 0; j <= right.length; j++) rows[0][j] = j;
7
+ for (let i = 1; i <= left.length; i++) {
8
+ for (let j = 1; j <= right.length; j++) {
9
+ rows[i][j] = Math.min(
10
+ rows[i - 1][j] + 1,
11
+ rows[i][j - 1] + 1,
12
+ rows[i - 1][j - 1] + Number(left[i - 1] !== right[j - 1]),
13
+ );
14
+ if (i > 1 && j > 1 && left[i - 1] === right[j - 2] && left[i - 2] === right[j - 1]) {
15
+ rows[i][j] = Math.min(rows[i][j], rows[i - 2][j - 2] + 1);
16
+ }
17
+ }
18
+ }
19
+ return rows[left.length][right.length];
20
+ }
21
+
22
+ /** 第 1 引数の候補を返す。候補が無ければ null を返す。 */
23
+ export function suggestFirstArgument(firstArg, { flags, commands }) {
24
+ if (!firstArg) return null;
25
+ const argument = firstArg.trim();
26
+ if (!argument) return null;
27
+ if (flags.includes(argument) || commands.includes(argument)) {
28
+ return argument === firstArg ? null : argument;
29
+ }
30
+ if (/\s/u.test(argument)) return null;
31
+
32
+ if (argument.startsWith('-')) {
33
+ // 短いフラグや区切り・標準入力を表す引数は候補を探さず、そのまま渡す。
34
+ if (argument.length <= 2) return null;
35
+ const near = flags
36
+ .map(candidate => ({ candidate, distance: editDistance(argument, candidate) }))
37
+ .filter(({ distance }) => distance === 1)
38
+ .sort((a, b) => b.candidate.length - a.candidate.length || a.candidate.localeCompare(b.candidate));
39
+ if (near.length) return near[0].candidate;
40
+ return flags.filter(candidate => argument.startsWith(candidate))
41
+ .sort((a, b) => b.length - a.length || a.localeCompare(b))[0] ?? null;
42
+ }
43
+
44
+ // 短いコマンドで距離 2 まで許すと、普通の 1 語も多数止まってしまう。
45
+ // プロジェクト内から AI エージェントへ渡す短い依頼を保つため、
46
+ // 4〜5 文字は距離 1、6 文字以上だけ距離 2 まで候補にする。
47
+ const near = commands
48
+ .filter(command => command.length >= 4)
49
+ .map(candidate => ({ candidate, distance: editDistance(argument, candidate) }))
50
+ .filter(({ candidate, distance }) => distance >= 1 && distance <= (candidate.length >= 6 ? 2 : 1))
51
+ .sort((a, b) => a.distance - b.distance || a.candidate.localeCompare(b.candidate));
52
+ return near[0]?.candidate ?? null;
53
+ }
package/src/kits.mjs CHANGED
@@ -124,8 +124,9 @@ function listAssetFiles(assetRoot, current = assetRoot) {
124
124
  function installedAssetItem(source, asset, manifest) {
125
125
  const assetRoot = realpathSync(source);
126
126
  let title = asset.id;
127
+ let meta = null;
127
128
  try {
128
- const meta = JSON.parse(readFileSync(path.join(assetRoot, 'meta.json'), 'utf8'));
129
+ meta = JSON.parse(readFileSync(path.join(assetRoot, 'meta.json'), 'utf8'));
129
130
  if (typeof meta.title === 'string' && meta.title) title = meta.title;
130
131
  } catch {
131
132
  // validate-asset が検査済み。配布環境で検査器が無い場合だけ id へフォールバックする。
@@ -133,6 +134,9 @@ function installedAssetItem(source, asset, manifest) {
133
134
  return {
134
135
  id: asset.id,
135
136
  title,
137
+ tier: Object.hasOwn(asset, 'tier') ? (asset.tier === 'free' ? 'free' : 'pro')
138
+ : meta && Object.hasOwn(meta, 'tier') ? (meta.tier === 'free' ? 'free' : 'pro')
139
+ : manifest.tier === 'free' ? 'free' : 'pro',
136
140
  path: ['assets', asset.category, asset.id].join('/'),
137
141
  version: manifest.version,
138
142
  files: listAssetFiles(assetRoot)
@@ -0,0 +1,37 @@
1
+ // モノレポと npm 配布物の両方から media-bin を読み、同期の起動箇所へ解決結果を渡す。
2
+ let preloadedMediaBin = null;
3
+
4
+ export async function loadMediaBinModule() {
5
+ const candidates = [
6
+ new URL('../../media-bin/src/index.mjs', import.meta.url),
7
+ new URL('../vendor/packages/media-bin/src/index.mjs', import.meta.url),
8
+ ];
9
+ let lastError;
10
+ for (const candidate of candidates) {
11
+ try {
12
+ return await import(candidate.href);
13
+ } catch (error) {
14
+ lastError = error;
15
+ }
16
+ }
17
+ throw lastError ?? new Error('media-bin is unavailable');
18
+ }
19
+
20
+ export async function preloadMediaBin(load = loadMediaBinModule) {
21
+ try {
22
+ preloadedMediaBin = await load();
23
+ } catch {
24
+ preloadedMediaBin = null;
25
+ }
26
+ return preloadedMediaBin;
27
+ }
28
+
29
+ export function resolveMediaCommand(name, { env = process.env, mediaBin = preloadedMediaBin } = {}) {
30
+ const resolver = name === 'ffmpeg' ? mediaBin?.resolveFfmpeg : mediaBin?.resolveFfprobe;
31
+ if (typeof resolver !== 'function') return name;
32
+ try {
33
+ return resolver({ env });
34
+ } catch {
35
+ return name;
36
+ }
37
+ }
package/src/messages.mjs CHANGED
@@ -53,6 +53,17 @@ export function opencodeMissingGuidance() {
53
53
  ].join('\n');
54
54
  }
55
55
 
56
+ /** 入口で第 1 引数の打ち間違いを検出したときの案内。 */
57
+ export function firstArgumentTypoError(argument, suggestion) {
58
+ const visibleArgument = argument.replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/\t/g, '\\t');
59
+ return `第 1 引数「${visibleArgument}」は打ち間違いかもしれません。${suggestion} のことですか?プロジェクトの作成も AI エージェントの起動もしていません。\nそのまま AI エージェントへ渡すには \`akari -- ${visibleArgument}\` と入力してください。`;
60
+ }
61
+
62
+ /** 非対話の未作成フォルダーで、作成を明示する引数がないときの案内。 */
63
+ export function nonInteractiveUninitializedGuidance() {
64
+ return 'プロジェクトではないフォルダーなので何もしませんでした。作る場合は `--yes` を付けてください。このフォルダーに作る場合は `--here` を付けてください。';
65
+ }
66
+
56
67
  /**
57
68
  * `akari` 起動時、claude 起動直前に出す 1 行通知(契約 §4-1)。
58
69
  * `checkForUpdateSync` が返す状態から組み立てる。新版が無ければ null。
@@ -7,10 +7,13 @@ import crypto from "node:crypto";
7
7
  import path from "node:path";
8
8
  import { spawn, spawnSync } from "node:child_process";
9
9
  import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { preloadMediaBin, resolveMediaCommand } from "./media-bin-loader.mjs";
10
11
  import { resolveLauncherAssets } from "./repo-assets.mjs";
11
12
  import { listProfiles, readFalKey, readProviderKey, resolveVoiceProfile } from "./voice-command.mjs";
12
13
  import { FAL_TTS_ENGINES, DIRECT_TTS_ENGINES, falTtsEngine, ttsEngine, estimateTtsCost, referenceDataUri } from "./tts-engines.mjs";
13
14
 
15
+ await preloadMediaBin();
16
+
14
17
  const VOICEVOX_BASE_URL = "http://127.0.0.1:50021";
15
18
  const VOICEVOX_RUN_ENV = "VOICEVOX_RUN";
16
19
  const VOICEVOX_STARTUP_TIMEOUT_MS = 60_000;
@@ -60,7 +63,7 @@ const commandUsage = [
60
63
  " start VOICEVOX エンジンを起動する",
61
64
  " stop AKARI が起動した VOICEVOX エンジンを止める",
62
65
  " voices 声一覧を表示する",
63
- " verify 生成音声をこの Mac で聞き取り、字幕と照合する",
66
+ " verify 生成音声をこのパソコンで聞き取り、字幕と照合する",
64
67
  "",
65
68
  usage,
66
69
  ].join("\n");
@@ -149,7 +152,7 @@ function verifyBackend(requested, runtime, transcribe) {
149
152
  if (requested === "speechanalyzer" && speech || requested === "whisper" && whisper || requested === "auto" && (speech || whisper)) {
150
153
  return requested === "auto" ? speech ? "speech-analyzer" : "whisper-cpp" : requested === "whisper" ? "whisper-cpp" : "speech-analyzer";
151
154
  }
152
- throw new PublicError(requested === "speechanalyzer" ? "SpeechAnalyzer を利用できません" : requested === "whisper" ? "whisper.cpp の実行ファイルまたはモデルが見つかりません" : "この Mac に利用できる文字起こし backend がありません(SpeechAnalyzer / whisper.cpp)", 3);
155
+ throw new PublicError(requested === "speechanalyzer" ? "SpeechAnalyzer を利用できません" : requested === "whisper" ? "whisper.cpp の実行ファイルまたはモデルが見つかりません" : "このパソコンに利用できる文字起こし backend がありません(SpeechAnalyzer / whisper.cpp)", 3);
153
156
  }
154
157
 
155
158
  async function runVerify(options, io, runtime = {}) {
@@ -1003,7 +1006,7 @@ function durationForAudio(buffer, outputPath, engine, warnings) {
1003
1006
  }
1004
1007
  }
1005
1008
  } else {
1006
- const result = spawnSync("ffprobe", ["-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", outputPath], { encoding: "utf8" });
1009
+ const result = spawnSync(resolveMediaCommand("ffprobe"), ["-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", outputPath], { encoding: "utf8" });
1007
1010
  const seconds = Number(result.stdout?.trim());
1008
1011
  if (result.status === 0 && Number.isFinite(seconds) && seconds >= 0) return Number(seconds.toFixed(3));
1009
1012
  }
@@ -4,6 +4,7 @@ import path, { dirname, join, resolve } from 'node:path';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
 
6
6
  import { findExecutable } from './path-lookup.mjs';
7
+ import { loadMediaBinModule } from './media-bin-loader.mjs';
7
8
  import { readInstalledAppVersionInfo, readOwnVersion, resolveAkariHome } from './update-check.mjs';
8
9
  import { resolveLauncherAssets } from './repo-assets.mjs';
9
10
 
@@ -247,22 +248,6 @@ async function resolveMediaTools(options) {
247
248
  };
248
249
  }
249
250
 
250
- async function loadMediaBinModule() {
251
- const candidates = [
252
- new URL('../../media-bin/src/index.mjs', import.meta.url),
253
- new URL('../vendor/packages/media-bin/src/index.mjs', import.meta.url),
254
- ];
255
- let lastError;
256
- for (const candidate of candidates) {
257
- try {
258
- return await import(candidate.href);
259
- } catch (error) {
260
- lastError = error;
261
- }
262
- }
263
- throw lastError ?? new Error('media-bin is unavailable');
264
- }
265
-
266
251
  function resolveMediaTool(name, resolver, options) {
267
252
  if (typeof resolver === 'function') {
268
253
  try {
@@ -80,15 +80,25 @@ function readJsonFile(filePath) {
80
80
  return JSON.parse(readFileSync(filePath, 'utf8'));
81
81
  }
82
82
 
83
- function titleFromMeta(assetRoot) {
83
+ function readAssetMeta(assetRoot) {
84
84
  try {
85
- const meta = readJsonFile(path.join(assetRoot, 'meta.json'));
86
- return typeof meta.title === 'string' && meta.title ? meta.title : null;
85
+ return readJsonFile(path.join(assetRoot, 'meta.json'));
87
86
  } catch {
88
87
  return null;
89
88
  }
90
89
  }
91
90
 
91
+ function declaredTier(...sources) {
92
+ for (const source of sources) {
93
+ if (source && Object.hasOwn(source, 'tier')) return source.tier === 'free' ? 'free' : 'pro';
94
+ }
95
+ for (const source of sources) {
96
+ if (source?.price === 0) return 'free';
97
+ if (typeof source?.price === 'number' && source.price > 0) return 'pro';
98
+ }
99
+ return 'pro';
100
+ }
101
+
92
102
  function isSafePathSegment(value) {
93
103
  return typeof value === 'string' && value.length > 0
94
104
  && value !== '.' && value !== '..'
@@ -124,6 +134,7 @@ function flattenPackContents(pack, packRoot) {
124
134
  throw new Error('PACK.json の contents[] に不正な id / path があります');
125
135
  }
126
136
  const assetRoot = pathWithin(packRoot, asset.path);
137
+ const meta = readAssetMeta(assetRoot);
127
138
  if (!Array.isArray(asset.files) || asset.files.length === 0) {
128
139
  throw new Error(`PACK.json の item に files[] がありません: ${asset.id}`);
129
140
  }
@@ -147,9 +158,10 @@ function flattenPackContents(pack, packRoot) {
147
158
  items.push({
148
159
  id: asset.id,
149
160
  title: (typeof asset.title === 'string' && asset.title)
150
- || titleFromMeta(assetRoot)
161
+ || (typeof meta?.title === 'string' && meta.title)
151
162
  || (typeof parentTitle === 'string' && parentTitle)
152
163
  || asset.id,
164
+ tier: declaredTier(asset, meta, pack),
153
165
  path: asset.path,
154
166
  version,
155
167
  files
@@ -2,7 +2,7 @@ import { spawnSync } from 'node:child_process';
2
2
  import {
3
3
  chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync
4
4
  } from 'node:fs';
5
- import { homedir, hostname } from 'node:os';
5
+ import { homedir } from 'node:os';
6
6
  import path from 'node:path';
7
7
 
8
8
  import { DEFAULT_STORE_BASE_URL, normalizeAkariUrl } from './service-urls.cjs';
@@ -111,7 +111,7 @@ export async function validateAndSaveCredentials(
111
111
  export async function startDeviceConnection({
112
112
  fetchImpl = fetch,
113
113
  baseUrl = DEFAULT_STORE_BASE_URL,
114
- label = `AKARI Video (${hostname()})`,
114
+ label = `AKARI Video (${process.platform === 'win32' ? 'Windows' : process.platform === 'darwin' ? 'Mac' : 'Linux'})`,
115
115
  openBrowser
116
116
  } = {}) {
117
117
  const normalizedBaseUrl = normalizeAkariUrl(baseUrl).replace(/\/+$/, '');
@@ -24,6 +24,8 @@ export const VENDOR_SOURCES = [
24
24
  'packages/render-cut/src/render-inputs.mjs',
25
25
  'packages/render-cut/src/caption-font.mjs',
26
26
  'packages/render-cut/src/html-scan.mjs',
27
+ 'packages/render-cut/src/font-cmap.mjs',
28
+ 'packages/render-cut/src/fragment-text.mjs',
27
29
  'packages/render-cut/src/library-reference.mjs',
28
30
  'packages/word-book/src/index.mjs',
29
31
  'packages/akari-tools/src/world/invariants.mjs',
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawnSync } from 'node:child_process';
7
7
  import { pathToFileURL } from 'node:url';
8
+ import { preloadMediaBin, resolveMediaCommand } from './media-bin-loader.mjs';
8
9
  import { resolveLauncherAssets } from './repo-assets.mjs';
9
10
  import { resolveAkariHome as fallbackResolveAkariHome } from './update-check.mjs';
10
11
  import { FAL_TTS_ENGINES, DIRECT_TTS_ENGINES, referenceDataUri } from './tts-engines.mjs';
@@ -23,6 +24,7 @@ try {
23
24
  } catch {
24
25
  // 部分的な vendor や壊れた optional module でもランチャー全体は起動させる。
25
26
  }
27
+ await preloadMediaBin();
26
28
 
27
29
  export const VOICE_SCRIPTS = Object.freeze({
28
30
  'quick-v1': 'こんにちは。今日は、いつもの調子で、ゆっくり話してみます。朝、コーヒーを淹れながら、今日やることを整理します。窓の外では、街がゆっくり動き出しています。準備ができたら、ひとつずつ、形にしていきましょう。',
@@ -175,7 +177,7 @@ function profileSummary(meta, id, avatar, legacy, env) {
175
177
  }
176
178
  function audioLevels(audio, runtime) {
177
179
  if (runtime.measureAudio) return runtime.measureAudio(audio);
178
- const result = spawnSync('ffmpeg', ['-v', 'error', '-i', audio, '-ac', '1', '-ar', '16000', '-f', 's16le', 'pipe:1'], { maxBuffer: 16 * 1024 * 1024 });
180
+ const result = spawnSync(resolveMediaCommand('ffmpeg'), ['-v', 'error', '-i', audio, '-ac', '1', '-ar', '16000', '-f', 's16le', 'pipe:1'], { maxBuffer: 16 * 1024 * 1024 });
179
181
  if (result.error || result.status !== 0) throw new VoiceError(result.error?.code === 'ENOENT' ? 'ffmpeg がありません' : `音声を測定できません: ${result.stderr?.toString().slice(0, 200)}`);
180
182
  const pcm = result.stdout, count = Math.floor(pcm.length / 2);
181
183
  if (!count) throw new VoiceError('音声が空です');
@@ -220,7 +222,7 @@ export async function checkVoiceRecording({ audio, script, backend = 'auto' }, r
220
222
  }
221
223
  function ffmpegConvert(source, destination, runtime, sampleRate = 48000, maxDurationS = null) {
222
224
  if (runtime.convertAudio) return runtime.convertAudio(source, destination, maxDurationS, sampleRate);
223
- const result = spawnSync('ffmpeg', ['-v', 'error', '-y', '-i', source,
225
+ const result = spawnSync(resolveMediaCommand('ffmpeg'), ['-v', 'error', '-y', '-i', source,
224
226
  ...(maxDurationS === null ? [] : ['-t', String(maxDurationS)]), '-ac', '1', '-ar', String(sampleRate), '-c:a', 'pcm_s16le', destination]);
225
227
  if (result.error || result.status !== 0) throw new VoiceError(result.error?.code === 'ENOENT' ? 'ffmpeg がありません' : '録音を wav に変換できません');
226
228
  }
@@ -254,7 +256,7 @@ function durationFromWav(buffer) {
254
256
  return bytesPerSecond && offset >= 0 ? Number((buffer.readUInt32LE(offset + 4) / bytesPerSecond).toFixed(3)) : null;
255
257
  }
256
258
  function probeDuration(file) {
257
- const result = spawnSync('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', file], { encoding: 'utf8' });
259
+ const result = spawnSync(resolveMediaCommand('ffprobe'), ['-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', file], { encoding: 'utf8' });
258
260
  const value = Number(result.stdout?.trim());
259
261
  return result.status === 0 && Number.isFinite(value) && value >= 0 ? Number(value.toFixed(3)) : null;
260
262
  }
@@ -372,7 +374,7 @@ async function execute(sub, o, runtime, env) {
372
374
  try {
373
375
  if (runtime.concatAudio) await runtime.concatAudio(recording, o.audio, staged);
374
376
  else {
375
- const result = spawnSync('ffmpeg', ['-v', 'error', '-y', '-i', recording, '-i', o.audio,
377
+ const result = spawnSync(resolveMediaCommand('ffmpeg'), ['-v', 'error', '-y', '-i', recording, '-i', o.audio,
376
378
  '-filter_complex', '[0:a][1:a]concat=n=2:v=0:a=1', '-ac', '1', '-ar', '48000', '-c:a', 'pcm_s16le', staged]);
377
379
  if (result.error || result.status !== 0) throw new VoiceError(result.error?.code === 'ENOENT' ? 'ffmpeg がありません' : '録音を連結できません');
378
380
  }
@@ -79,7 +79,9 @@
79
79
  "docs/contract-2026-09-25-motion-layers-v0.md",
80
80
  "docs/contract-2026-09-25-photo-edit-v0.md",
81
81
  "docs/contract-2026-09-25-shape-item-v1.md",
82
+ "docs/contract-2026-10-02-fragment-font-subset-v0.md",
82
83
  "docs/contract-2026-10-02-textstyle-v1-rich.md",
84
+ "docs/contract-2026-10-04-asset-tier-v1.md",
83
85
  "packages/akari-launcher/package.json",
84
86
  "packages/akari-launcher/README.md",
85
87
  "packages/akari-tools/package.json",
@@ -74,11 +74,11 @@ assets/ ← 当面はローカルディレクトリ。コミ
74
74
  "author": "akari",
75
75
  "license": { "spdx": "MIT", "scope": "commercial-ok", "attribution_required": false,
76
76
  "ai_training_allowed": true }, // Fab の NoAI タグに相当する予約(市場化で必ず問われる)
77
- "price": null // 予約フィールド(null = 無料。将来のマーケットプレイス用)
77
+ "tier": "free" // free / pro。price は廃止予定の任意フィールド
78
78
  }
79
79
  ```
80
80
 
81
- - `license` / `author` / `price` は**最初から予約**(後の販売プラットフォーム化で再梱包不要に)
81
+ - `license` / `author` / `tier` は素材メタデータに記録する。`tier` は必須(free / pro)。
82
82
  - **`knobs.unit` は「値に付く CSS 単位」**(2026-07-29 明確化)。`px` / `s` のように実際に
83
83
  値へ付く単位だけを書き、**無単位の倍率・比率(短辺比など)では `unit` を省略する**。
84
84
  意味は `label` に書く(例「木枠の太さ(短辺比。0 で枠なし)」)。この規律により、
@@ -448,7 +448,7 @@ AKARI 配布元は URL を解析し、ホスト github.com かつパスの最初
448
448
  `license.attribution_required: true`、source がある場合はその attribution_required も true にする。
449
449
 
450
450
  取り込みの既定は `license.scope: "private-owned"`、`spdx: "LicenseRef-user-owned"`、
451
- `ai_training_allowed: false`、`price: 0`。利用者の手持ち素材として保管するための値であり、
451
+ `ai_training_allowed: false`、`tier: "free"`。利用者の手持ち素材として保管するための値であり、
452
452
  素材そのものの著作権帰属・商用可否・再配布権を認定するものではない。
453
453
  サイト由来は source ブロックに配布ページと元の利用条件を残す。
454
454
 
@@ -53,17 +53,24 @@
53
53
  | transition_out fade-black/white | cross dissolve **近似** + dropped | 同左 | dip to color は手動再設定 |
54
54
  | layers(baked/video) | lane 2+ の connected clip | 上位 video track | アルファ付き mov はただのクリップ |
55
55
  | layers[].blend | adjust-blend mode(⚠未検証) | **落ちる**(warning) | |
56
- | audio.narration | lane -1 / role dialogue | audio track | gain は adjust-volume / audiolevels |
57
- | audio.sfx | lane -2-(track)/ role effects | audio track(sfx track 単位) | in/out 対応 |
58
- | audio.bgm | 実尺までループ展開 + fade キーフレーム(⚠推定) | 同左 | 実尺不明時は全体尺 1 クリップ + warning |
56
+ | tracks[] または audio.narration | lane -1 / role dialogue | audio track | gain は adjust-volume / audiolevels |
57
+ | tracks[] または audio.sfx | lane -2-(track)/ role effects | audio track(sfx track 単位) | in/out 対応。tracks[] では sfx を含む audio lane 順に 0 から振り直す(空番は詰める) |
58
+ | tracks[] または audio.bgm | 実尺までループ展開 + fade キーフレーム(⚠推定) | 同左 | 実尺不明時は全体尺 1 クリップ + warning |
59
59
  | beats / emphasis_words | クリップ上の marker(source 秒のまま) | シーケンスマーカー(timeline へ写像) | 意味層はマーカーへ退化 |
60
60
  | captions.json | —(SRT へ) | —(SRT へ) | display_text 優先・プレーンテキスト |
61
61
 
62
62
  ### 移らない(dropped[] に全件列挙 — 黙って落とさない)
63
63
 
64
+ `tracks[]` の audio lane でミュートされた item、speech role、2 本目以降の bgm は理由付きで報告する。
65
+ `tracks[]` に audio item がない文書は従来どおり `audio.*` 宣言から書き出す。
66
+ ducking はトップレベル `audio.bgm.ducking` キーがあればそれを優先し、無ければ書き出す bgm item から読み、
67
+ 従来どおり `audio.bgm.ducking` として報告する(migrate 後は bgm item に移る)。
68
+ audio.master は従来どおりトップレベル `audio.master` から読み、報告する。
69
+ bgm item の `at > 0` は書き出し器が 0 秒始まりで配置するため移らず、`tracks[…].items[…].at` として報告する。
70
+
64
71
  audio.bgm.ducking / audio.master(loudnorm・denoise)/ output.look(LUT)/
65
72
  sources[].chroma_key・layers[].chroma_key / direction / 字幕スタイル(style・text_style・words)/
66
- カット範囲外の beats・emphasis_words アンカー
73
+ カット範囲外の beats・emphasis_words アンカー / tracks[] の書き出さない audio item・bgm item の at > 0
67
74
 
68
75
  ## 5. 既知の近似(ベータの明示的トレードオフ)
69
76