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.
- package/bin/akari.mjs +67 -43
- package/package.json +1 -1
- package/src/cli.mjs +20 -12
- package/src/first-arg-guard.mjs +53 -0
- package/src/kits.mjs +5 -1
- package/src/media-bin-loader.mjs +37 -0
- package/src/messages.mjs +11 -0
- package/src/narration-command.mjs +6 -3
- package/src/runtime-diagnostics.mjs +1 -16
- package/src/store-command.mjs +16 -4
- package/src/store-device-connect.mjs +2 -2
- package/src/vendor-sources.mjs +2 -0
- package/src/voice-command.mjs +6 -4
- package/vendor/.akari-capability-sources.json +2 -0
- package/vendor/docs/contract-2026-07-13-asset-library.md +3 -3
- package/vendor/docs/contract-2026-08-01-export-nle-beta.md +11 -4
- package/vendor/docs/contract-2026-08-28-osr-export-v0.md +2 -2
- package/vendor/docs/contract-2026-09-02-shape-item-v0.md +1 -0
- package/vendor/docs/contract-2026-10-02-fragment-font-subset-v0.md +44 -0
- package/vendor/docs/contract-2026-10-02-textstyle-v1-rich.md +3 -3
- package/vendor/docs/contract-2026-10-04-asset-tier-v1.md +44 -0
- package/vendor/packages/akari-launcher/package.json +1 -1
- package/vendor/packages/asset-resolver/bin/akari-assets.mjs +12 -11
- package/vendor/packages/asset-resolver/browse/app.js +5 -4
- package/vendor/packages/asset-resolver/src/add.mjs +1 -1
- package/vendor/packages/asset-resolver/src/browse-server.mjs +41 -7
- package/vendor/packages/asset-resolver/src/catalog.mjs +58 -5
- package/vendor/packages/asset-resolver/src/entitlements.mjs +20 -13
- package/vendor/packages/asset-resolver/src/errors.mjs +1 -1
- package/vendor/packages/asset-resolver/src/fetch-file.mjs +52 -3
- package/vendor/packages/asset-resolver/src/installed.mjs +16 -3
- package/vendor/packages/asset-resolver/src/library.mjs +2 -1
- package/vendor/packages/asset-resolver/src/paid-zip.mjs +6 -4
- package/vendor/packages/asset-resolver/src/project-references.mjs +102 -12
- package/vendor/packages/asset-resolver/src/resolve.mjs +115 -38
- package/vendor/packages/asset-resolver/src/state.mjs +23 -15
- package/vendor/packages/asset-resolver/src/tier.mjs +16 -0
- package/vendor/packages/asset-resolver/test/add.test.mjs +3 -2
- package/vendor/packages/asset-resolver/test/automatic-network-check.test.mjs +55 -0
- package/vendor/packages/asset-resolver/test/browse-server.test.mjs +132 -0
- package/vendor/packages/asset-resolver/test/catalog-and-state.test.mjs +58 -2
- package/vendor/packages/asset-resolver/test/fixtures/build-fixture-library.mjs +2 -2
- package/vendor/packages/asset-resolver/test/installed-assets.test.mjs +62 -4
- package/vendor/packages/asset-resolver/test/library-check.test.mjs +1 -1
- package/vendor/packages/asset-resolver/test/library-migration.test.mjs +1 -1
- package/vendor/packages/asset-resolver/test/local-library.test.mjs +15 -4
- package/vendor/packages/asset-resolver/test/project-references-concurrency.test.mjs +88 -0
- package/vendor/packages/asset-resolver/test/resolve-category-ref.test.mjs +52 -0
- package/vendor/packages/asset-resolver/test/resolve-fast.test.mjs +134 -0
- package/vendor/packages/asset-resolver/test/resolve-installed-offline.test.mjs +37 -0
- package/vendor/packages/asset-resolver/test/resolve-locked.test.mjs +24 -7
- package/vendor/packages/asset-resolver/test/resolve-paid-zip.test.mjs +113 -7
- package/vendor/packages/asset-resolver/test/resolve-validate-in-process.test.mjs +52 -0
- package/vendor/packages/asset-resolver/test/tier-access.test.mjs +86 -0
- package/vendor/packages/audio-library-setup/bin/beat-grid.mjs +18 -10
- package/vendor/packages/audio-library-setup/bin/declare-helper.mjs +15 -2
- package/vendor/packages/audio-library-setup/bin/fetch-akari-sounds.mjs +1 -0
- package/vendor/packages/audio-library-setup/bin/gallery-helper.mjs +15 -2
- package/vendor/packages/audio-library-setup/bin/generate-candidates-html.mjs +16 -3
- package/vendor/packages/audio-library-setup/bin/register-drop-folder.mjs +18 -4
- package/vendor/packages/audio-library-setup/bin/suggest-bgm.mjs +14 -6
- package/vendor/packages/audio-library-setup/gallery-server.mjs +1 -1
- package/vendor/packages/audio-library-setup/shared/akari-sounds.mjs +1 -0
- package/vendor/packages/audio-library-setup/shared/candidates.mjs +2 -0
- package/vendor/packages/audio-library-setup/test/akari-sounds.test.mjs +2 -0
- package/vendor/packages/audio-library-setup/test/cli-arguments-and-csp.test.mjs +63 -0
- package/vendor/packages/edit-lint/src/edit-lint.mjs +95 -6616
- package/vendor/packages/edit-lint/src/lint/audio.mjs +810 -0
- package/vendor/packages/edit-lint/src/lint/caption-style.mjs +416 -0
- package/vendor/packages/edit-lint/src/lint/captions.mjs +646 -0
- package/vendor/packages/edit-lint/src/lint/cuts-tracks.mjs +622 -0
- package/vendor/packages/edit-lint/src/lint/edit-v2.mjs +805 -0
- package/vendor/packages/edit-lint/src/lint/engine-capabilities.mjs +182 -0
- package/vendor/packages/edit-lint/src/lint/external.mjs +47 -0
- package/vendor/packages/edit-lint/src/lint/html-fragment.mjs +82 -0
- package/vendor/packages/edit-lint/src/lint/intake.mjs +217 -0
- package/vendor/packages/edit-lint/src/lint/look-adjust.mjs +229 -0
- package/vendor/packages/edit-lint/src/lint/media-checks.mjs +732 -0
- package/vendor/packages/edit-lint/src/lint/media-probe.mjs +230 -0
- package/vendor/packages/edit-lint/src/lint/overlay-css.mjs +359 -0
- package/vendor/packages/edit-lint/src/lint/overlays.mjs +564 -0
- package/vendor/packages/edit-lint/src/lint/review.mjs +467 -0
- package/vendor/packages/edit-lint/src/lint/shared.mjs +130 -0
- package/vendor/packages/edit-lint/src/lint/v2-projection.mjs +180 -0
- package/vendor/packages/edit-store/lib/caption-display.d.ts +3 -1
- package/vendor/packages/edit-store/lib/caption-display.js +92 -10
- package/vendor/packages/edit-store/lib/caption-store.d.ts +31 -0
- package/vendor/packages/edit-store/lib/caption-store.js +14 -0
- package/vendor/packages/edit-store/lib/cut-ranges.js +1 -1
- package/vendor/packages/edit-store/lib/edit-v2-item-write.js +13 -2
- package/vendor/packages/edit-store/lib/edit-v2.d.ts +2 -0
- package/vendor/packages/edit-store/lib/history-store.d.ts +3 -0
- package/vendor/packages/edit-store/lib/history-store.js +48 -2
- package/vendor/packages/edit-store/lib/project.js +0 -3
- package/vendor/packages/edit-store/lib/shape-markup-v1.js +62 -14
- package/vendor/packages/edit-store/lib/shape-markup.js +12 -9
- package/vendor/packages/edit-store/lib/shape-source-validation.js +9 -1
- package/vendor/packages/edit-store/lib/transform-keyframe-edit.js +9 -0
- package/vendor/packages/edit-store/lib/webview-kernel.js +165 -32
- package/vendor/packages/edit-store/lib/write-gate.js +16 -13
- package/vendor/packages/gpu-export/README.ja.md +3 -3
- package/vendor/packages/gpu-export/README.md +3 -3
- package/vendor/packages/media-bin/bin/audio-measure.mjs +0 -0
- package/vendor/packages/media-bin/src/binary-manifest.mjs +51 -0
- package/vendor/packages/media-bin/test/media-bin.test.mjs +15 -1
- package/vendor/packages/osr-export/README.md +1 -1
- package/vendor/packages/overlay-runtime/src/fragment-source-write.mjs +67 -0
- package/vendor/packages/overlay-runtime/src/handle-geometry.js +33 -9
- package/vendor/packages/overlay-runtime/src/interaction.js +508 -16
- package/vendor/packages/overlay-runtime/src/overlay-runtime.js +51 -3
- package/vendor/packages/render-cut/src/font-cmap.mjs +196 -0
- package/vendor/packages/render-cut/src/fragment-text.mjs +288 -0
- package/vendor/packages/schemas/asset-meta.schema.json +44 -8
- package/vendor/packages/schemas/bin/validate-asset.mjs +72 -20
- package/vendor/packages/schemas/bin/validate-sites.mjs +0 -0
- package/vendor/packages/schemas/edit.schema.json +4 -1
- package/vendor/packages/schemas/examples/kit-manifest-v1-with-asset/assets/overlay/sample-kit-frame/meta.json +1 -0
- package/vendor/packages/schemas/test/asset-meta-default.test.mjs +55 -5
- package/vendor/packages/schemas/test/edit-v2-schema.test.mjs +15 -0
- package/vendor/packages/schemas/test/fixtures/asset/invalid-matched-by/audio/whoosh-transition/meta.json +1 -0
- package/vendor/packages/schemas/test/fixtures/asset/invalid-scene3d-model-missing-glb/scene3d/hero-missing-glb/meta.json +1 -0
- package/vendor/packages/schemas/test/fixtures/asset/invalid-scene3d-neither/scene3d/hero-neither/meta.json +1 -0
- package/vendor/packages/schemas/test/fixtures/asset/valid-catalog/audio/whoosh-transition/meta.json +1 -0
- package/vendor/packages/schemas/test/fixtures/asset/valid-library/overlay/lower-third-clean/meta.json +2 -1
- package/vendor/packages/schemas/test/fixtures/asset/valid-scene3d-texts-only/scene3d/hero-texts-only/meta.json +2 -1
- package/vendor/packages/schemas/test/textstyle-allowlist-drift.test.mjs +20 -2
- package/vendor/packages/schemas/test/validate-asset.test.mjs +67 -1
- package/vendor/skills/analyze-footage/bin/face-expression/face-expression.mjs +35 -15
- package/vendor/skills/analyze-footage/bin/test/face-expression-availability.test.mjs +59 -0
- package/vendor/skills/analyze-footage/bin/vision-tracks/vision-tracks.mjs +49 -6
- package/vendor/skills/analyze-footage/test/vision-tracks-check.test.mjs +47 -6
- package/vendor/skills/export-nle/SKILL.md +9 -5
- package/vendor/skills/harvest-asset/SKILL.md +8 -7
- package/vendor/skills/overlay-authoring/SKILL.md +2 -0
- package/vendor/skills/overlay-authoring/telop.md +8 -0
- package/vendor/skills/setup-library/SKILL.md +1 -1
- package/vendor/skills/setup-library/fetch-and-validate.md +9 -3
- package/vendor/skills/setup-library/starter-pack.md +1 -0
- package/vendor/skills/verify/SKILL.md +1 -1
- package/vendor/packages/edit-store/lib/motion-position-write.d.ts +0 -6
- 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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
:
|
|
78
|
-
:
|
|
79
|
-
:
|
|
80
|
-
:
|
|
81
|
-
:
|
|
82
|
-
:
|
|
83
|
-
:
|
|
84
|
-
:
|
|
85
|
-
:
|
|
86
|
-
:
|
|
87
|
-
:
|
|
88
|
-
:
|
|
89
|
-
:
|
|
90
|
-
:
|
|
91
|
-
:
|
|
92
|
-
:
|
|
93
|
-
:
|
|
94
|
-
:
|
|
95
|
-
:
|
|
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
|
|
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.
|
|
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]
|
|
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
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
log(
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
|
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 の実行ファイルまたはモデルが見つかりません" : "
|
|
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 {
|
package/src/store-command.mjs
CHANGED
|
@@ -80,15 +80,25 @@ function readJsonFile(filePath) {
|
|
|
80
80
|
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function
|
|
83
|
+
function readAssetMeta(assetRoot) {
|
|
84
84
|
try {
|
|
85
|
-
|
|
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
|
-
||
|
|
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
|
|
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 (${
|
|
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(/\/+$/, '');
|
package/src/vendor-sources.mjs
CHANGED
|
@@ -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',
|
package/src/voice-command.mjs
CHANGED
|
@@ -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
|
-
"
|
|
77
|
+
"tier": "free" // free / pro。price は廃止予定の任意フィールド
|
|
78
78
|
}
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
- `license` / `author` / `
|
|
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`、`
|
|
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
|
|