akari-video 0.1.27 → 0.1.29
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/package.json +1 -1
- package/src/capture-command.mjs +55 -0
- package/src/cli.mjs +4 -0
- package/src/media-command.mjs +17 -0
- package/src/migrate-command.mjs +21 -4
- package/src/repo-assets.mjs +31 -10
- package/src/runtime-diagnostics.mjs +0 -3
- package/vendor/.akari-capability-sources.json +9 -0
- package/vendor/docs/contract-2026-07-18-edit-json-v1-sources.md +7 -0
- package/vendor/docs/contract-2026-07-22-render-basics.md +9 -1
- package/vendor/docs/contract-2026-07-25-project-structure-v0.md +12 -0
- package/vendor/docs/contract-2026-08-02-preview-parity.md +62 -0
- package/vendor/docs/contract-2026-08-09-transform-keyframes-v0.md +35 -0
- package/vendor/docs/contract-2026-08-12-still-image-cut-source-v0.md +6 -0
- package/vendor/docs/contract-2026-08-23-captions-emphasis-words-v0.md +1 -0
- package/vendor/docs/contract-2026-08-28-gpu-export-v0.md +358 -12
- package/vendor/docs/contract-2026-08-28-osr-export-v0.md +29 -2
- package/vendor/docs/contract-2026-08-28-v2-audio-roles-v0.md +39 -0
- package/vendor/docs/contract-2026-08-29-capture-v0.md +176 -0
- package/vendor/docs/contract-2026-08-29-media-inspect-cli-v0.md +236 -0
- package/vendor/docs/contract-2026-08-30-edit-json-v2-object-tree-v0.md +292 -0
- package/vendor/docs/contract-2026-08-30-motion-and-keyframes-v0.md +163 -0
- package/vendor/packages/akari-launcher/package.json +1 -1
- package/vendor/packages/akari-tools/README.md +10 -0
- package/vendor/packages/akari-tools/package.json +9 -0
- package/vendor/packages/edit-lint/src/edit-lint.mjs +406 -19
- package/vendor/packages/edit-store/README.md +101 -0
- package/vendor/packages/edit-store/lib/audio-schedule.d.ts +62 -1
- package/vendor/packages/edit-store/lib/audio-schedule.js +239 -2
- package/vendor/packages/edit-store/lib/canonical.d.ts +3 -0
- package/vendor/packages/edit-store/lib/canonical.js +205 -0
- package/vendor/packages/edit-store/lib/edit-v2.d.ts +80 -2
- package/vendor/packages/edit-store/lib/edit-v2.js +171 -8
- package/vendor/packages/edit-store/lib/generated/edit-v2-keys.d.ts +27 -0
- package/vendor/packages/edit-store/lib/generated/edit-v2-keys.js +213 -0
- package/vendor/packages/edit-store/lib/index.d.ts +2 -0
- package/vendor/packages/edit-store/lib/index.js +2 -0
- package/vendor/packages/edit-store/lib/internal-model.d.ts +29 -1
- package/vendor/packages/edit-store/lib/internal-model.js +145 -35
- package/vendor/packages/edit-store/lib/migrate/index.d.ts +31 -2
- package/vendor/packages/edit-store/lib/migrate/index.js +171 -6
- package/vendor/packages/edit-store/lib/project.d.ts +35 -0
- package/vendor/packages/edit-store/lib/project.js +240 -0
- package/vendor/packages/edit-store/lib/tree-ops.d.ts +97 -0
- package/vendor/packages/edit-store/lib/tree-ops.js +533 -0
- package/vendor/packages/edit-store/lib/webview-kernel.d.ts +1 -0
- package/vendor/packages/edit-store/lib/webview-kernel.js +273 -2
- package/vendor/packages/edit-store/lib/write-gate.d.ts +6 -0
- package/vendor/packages/edit-store/lib/write-gate.js +59 -0
- package/vendor/packages/edit-store/package.json +10 -0
- package/vendor/packages/frame-engine/README.ja.md +4 -0
- package/vendor/packages/frame-engine/README.md +4 -0
- package/vendor/packages/gpu-export/README.ja.md +94 -5
- package/vendor/packages/gpu-export/README.md +100 -5
- package/vendor/packages/media-bin/src/preview-audio-sidecar.mjs +283 -0
- package/vendor/packages/media-bin/src/proxy-recipe.json +8 -0
- package/vendor/packages/media-bin/src/proxy-recipe.mjs +54 -0
- package/vendor/packages/media-bin/src/speech-atempo.mjs +13 -0
- package/vendor/packages/media-bin/test/proxy-recipe.test.mjs +50 -0
- package/vendor/packages/media-bin/test/speech-atempo.test.mjs +112 -0
- package/vendor/packages/osr-export/README.md +1 -1
- package/vendor/packages/overlay-runtime/README.md +55 -0
- package/vendor/packages/overlay-runtime/package.json +2 -1
- package/vendor/packages/schemas/analysis.schema.json +89 -0
- package/vendor/packages/schemas/bin/validate-edit.mjs +46 -0
- package/vendor/packages/schemas/bin/validate-motion.mjs +58 -0
- package/vendor/packages/schemas/edit.schema.json +247 -11
- package/vendor/packages/schemas/motion.schema.json +20 -0
- package/vendor/packages/schemas/package.json +1 -0
- package/vendor/packages/schemas/test/analysis-observations.test.mjs +56 -0
- package/vendor/packages/schemas/test/edit-v2-schema.test.mjs +3 -3
- package/vendor/packages/schemas/test/fixtures/motion/g-motion.json +1 -0
- package/vendor/packages/schemas/test/fixtures/object-tree-a-group.json +1 -0
- package/vendor/packages/schemas/test/fixtures/object-tree-b-nested.json +1 -0
- package/vendor/packages/schemas/test/fixtures/object-tree-c-html-bag.json +1 -0
- package/vendor/packages/schemas/test/fixtures/object-tree-d-captions.json +1 -0
- package/vendor/packages/schemas/test/fixtures/object-tree-e-keyframes-ref.json +1 -0
- package/vendor/packages/schemas/test/fixtures/object-tree-f-motion-animator.json +1 -0
- package/vendor/packages/schemas/test/fixtures/object-tree-g-z-order.json +1 -0
- package/vendor/packages/schemas/test/fixtures/object-tree-h-content.json +1 -0
- package/vendor/packages/schemas/test/object-tree-schema.test.mjs +101 -0
- package/vendor/skills/address-review/SKILL.md +5 -1
- package/vendor/skills/analyze-footage/SKILL.md +36 -25
- package/vendor/skills/analyze-footage/analysis-json.md +6 -4
- package/vendor/skills/analyze-footage/events-and-hooks.md +15 -1
- package/vendor/skills/analyze-footage/keyframes-and-review.md +44 -85
- package/vendor/skills/analyze-footage/media-and-transcript.md +48 -188
- package/vendor/skills/analyze-footage/person-matte.md +10 -8
- package/vendor/skills/analyze-footage/vision-tracks.md +11 -6
- package/vendor/skills/analyze-footage/workflow.md +60 -61
- package/vendor/skills/analyze-project/SKILL.md +8 -1
- package/vendor/skills/analyze-project/interpretation.md +6 -2
- package/vendor/skills/beat-sync-edit/SKILL.md +3 -0
- package/vendor/skills/critique-cut/SKILL.md +92 -0
- package/vendor/skills/critique-cut/bin/index.js +6 -0
- package/vendor/skills/critique-cut/bin/used-ranges.mjs +402 -0
- package/vendor/skills/critique-cut/bin/used-ranges.test.mjs +281 -0
- package/vendor/skills/critique-cut/workflow.md +169 -0
- package/vendor/skills/edit-lint/SKILL.md +4 -1
- package/vendor/skills/edit-plan/SKILL.md +4 -0
- package/vendor/skills/edit-plan/beat-sync.md +3 -0
- package/vendor/skills/edit-plan/beats.md +3 -0
- package/vendor/skills/edit-plan/emphasis-detection.md +3 -0
- package/vendor/skills/edit-plan/execution.md +8 -2
- package/vendor/skills/edit-plan/workflow.md +3 -0
- package/vendor/skills/export-nle/SKILL.md +4 -1
- package/vendor/skills/generate-narration/SKILL.md +4 -1
- package/vendor/skills/overlay-authoring/SKILL.md +3 -0
- package/vendor/skills/overlay-authoring/motion.md +6 -0
- package/vendor/skills/overlay-authoring/telop.md +12 -0
- package/vendor/skills/render-cut/SKILL.md +9 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
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": {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { resolveLauncherAssets } from "./repo-assets.mjs";
|
|
7
|
+
|
|
8
|
+
export async function runCaptureCommand(argv, options = {}) {
|
|
9
|
+
const logError = options.error ?? options.logError ?? ((line) => console.error(line));
|
|
10
|
+
const assets = options.assets ?? resolveLauncherAssets();
|
|
11
|
+
if (!assets.captureScript || !existsSync(assets.captureScript)) {
|
|
12
|
+
logError("akari capture の実行スクリプトが見つかりません。AKARI Video を再インストールしてください。");
|
|
13
|
+
return { exitCode: 1 };
|
|
14
|
+
}
|
|
15
|
+
|
|
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
|
+
const spawn = options.spawn ?? spawnSync;
|
|
26
|
+
const result = spawn(process.execPath, [assets.captureScript, ...argv], {
|
|
27
|
+
stdio: "inherit",
|
|
28
|
+
cwd: options.cwd ?? process.cwd(),
|
|
29
|
+
});
|
|
30
|
+
return { exitCode: typeof result.status === "number" ? result.status : 1 };
|
|
31
|
+
}
|
|
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
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
} from './update-check.mjs';
|
|
24
24
|
import { applySelfUpdate, isRunningFromAppDir, rollbackSelfUpdate } from './self-update.mjs';
|
|
25
25
|
import { runChromeCommand } from './chrome-command.mjs';
|
|
26
|
+
import { runCaptureCommand } from './capture-command.mjs';
|
|
27
|
+
import { runMediaCommand } from './media-command.mjs';
|
|
26
28
|
import { resolveRuntimePaths } from './runtime-diagnostics.mjs';
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -36,6 +38,8 @@ import { resolveRuntimePaths } from './runtime-diagnostics.mjs';
|
|
|
36
38
|
*/
|
|
37
39
|
export async function run(args, options = {}) {
|
|
38
40
|
if (args[0] === 'chrome') return runChromeCommand(args.slice(1), options);
|
|
41
|
+
if (args[0] === 'capture') return runCaptureCommand(args.slice(1), options);
|
|
42
|
+
if (args[0] === 'media') return runMediaCommand(args.slice(1), options);
|
|
39
43
|
|
|
40
44
|
const log = options.log ?? ((line) => console.log(line));
|
|
41
45
|
const assets = options.assets ?? resolveLauncherAssets();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
import { resolveLauncherAssets } from "./repo-assets.mjs";
|
|
4
|
+
|
|
5
|
+
export async function runMediaCommand(args, options = {}) {
|
|
6
|
+
const logError = options.logError ?? ((line) => console.error(line));
|
|
7
|
+
const assets = options.assets ?? resolveLauncherAssets();
|
|
8
|
+
const spawn = options.spawn ?? spawnSync;
|
|
9
|
+
if (!assets.mediaScript) {
|
|
10
|
+
logError("akari media の実行スクリプトが見つかりません。完全な AKARI Video を再導入してください:");
|
|
11
|
+
logError(" npm install -g akari-video");
|
|
12
|
+
return { exitCode: 1 };
|
|
13
|
+
}
|
|
14
|
+
const result = spawn(process.execPath, [assets.mediaScript, ...args], { stdio: "inherit" });
|
|
15
|
+
return { exitCode: typeof result?.status === "number" ? result.status : 1 };
|
|
16
|
+
}
|
|
17
|
+
|
package/src/migrate-command.mjs
CHANGED
|
@@ -39,7 +39,15 @@ export async function runMigrateCommand(args, options = {}) {
|
|
|
39
39
|
// captions.json の不在・読み取り失敗・壊れた JSON は cue なしとして移行を続ける。
|
|
40
40
|
}
|
|
41
41
|
const hasCaptions = migrate.captionsHaveRenderableCues(captionsRoot);
|
|
42
|
-
|
|
42
|
+
let editVersion;
|
|
43
|
+
try {
|
|
44
|
+
editVersion = migrate.detectEditVersion(JSON.parse(text));
|
|
45
|
+
} catch {
|
|
46
|
+
// JSON error の文言は従来どおり planMigration に一元化する。
|
|
47
|
+
}
|
|
48
|
+
const proposal = editVersion === 2
|
|
49
|
+
? migrate.planV2Normalization(projectRoot, editPath, text, { now: options.now })
|
|
50
|
+
: migrate.planMigration(projectRoot, editPath, text, { hasCaptions, now: options.now });
|
|
43
51
|
if (proposal.ok === false) {
|
|
44
52
|
if (parsed.json) {
|
|
45
53
|
log(JSON.stringify({ ok: false, error: 'このプロジェクトは変換できません', blockers: proposal.blockers }));
|
|
@@ -49,13 +57,18 @@ export async function runMigrateCommand(args, options = {}) {
|
|
|
49
57
|
}
|
|
50
58
|
return { exitCode: 2 };
|
|
51
59
|
}
|
|
60
|
+
if (proposal.noop === true) {
|
|
61
|
+
if (parsed.json) log(JSON.stringify({ ok: true, noop: true, version: proposal.version }));
|
|
62
|
+
else log('変換の必要はありません。');
|
|
63
|
+
return { exitCode: 0, proposal };
|
|
64
|
+
}
|
|
52
65
|
if (parsed.json) {
|
|
53
66
|
log(JSON.stringify({
|
|
54
67
|
ok: true, dryRun: parsed.dryRun, version: proposal.version,
|
|
55
68
|
filePath: proposal.filePath, backupPath: proposal.backupPath, changes: proposal.changes
|
|
56
69
|
}));
|
|
57
70
|
} else {
|
|
58
|
-
log(`変換対象: ${proposal.filePath} (version ${proposal.version} -> 2)`);
|
|
71
|
+
log(`変換対象: ${proposal.filePath} (version ${proposal.version}${proposal.version === 2 ? ' 正規化' : ' -> 2'})`);
|
|
59
72
|
for (const change of proposal.changes) log(`- ${change.path}: ${change.note}`);
|
|
60
73
|
log(`変換前の退避先: ${proposal.backupPath}`);
|
|
61
74
|
}
|
|
@@ -78,7 +91,11 @@ export async function runMigrateCommand(args, options = {}) {
|
|
|
78
91
|
}
|
|
79
92
|
}
|
|
80
93
|
await migrate.applyMigration(proposal);
|
|
81
|
-
if (!parsed.json)
|
|
94
|
+
if (!parsed.json) {
|
|
95
|
+
log(proposal.version === 2
|
|
96
|
+
? `version 2 を正規化しました。元ファイル: ${proposal.backupPath}`
|
|
97
|
+
: `version 2 へ変換しました。元ファイル: ${proposal.backupPath}`);
|
|
98
|
+
}
|
|
82
99
|
return { exitCode: 0, proposal };
|
|
83
100
|
}
|
|
84
101
|
|
|
@@ -121,7 +138,7 @@ export function migrateHelp() {
|
|
|
121
138
|
return [
|
|
122
139
|
'使い方: akari migrate [dir] [--yes] [--dry-run] [--json]',
|
|
123
140
|
'',
|
|
124
|
-
'v0/v1 の edit.json を v2
|
|
141
|
+
'v0/v1 の edit.json を v2 へ片道変換し、v2 は正規形へ移行します。',
|
|
125
142
|
'既定は変更内容を表示して y/n で確認し、変換前の全文を .akari/backup/ へ退避します。',
|
|
126
143
|
'',
|
|
127
144
|
' --yes, -y 表示後の確認を省略',
|
package/src/repo-assets.mjs
CHANGED
|
@@ -33,6 +33,7 @@ const AUDIO_FETCH_SCRIPT_RELATIVE = path.join('packages', 'audio-library-setup',
|
|
|
33
33
|
const ASSET_RESOLVER_CLI_RELATIVE = path.join('packages', 'asset-resolver', 'bin', 'akari-assets.mjs');
|
|
34
34
|
const BEATMAP_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'beatmap.mjs');
|
|
35
35
|
const PROBE_FRAME_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'probe-frame.mjs');
|
|
36
|
+
const CAPTURE_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'capture.mjs');
|
|
36
37
|
const RENDER_WHEN_IDLE_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'render-when-idle.sh');
|
|
37
38
|
const EYE_BAR_SCRIPT_RELATIVE = path.join('packages', 'akari-tools', 'bin', 'eye-bar.mjs');
|
|
38
39
|
|
|
@@ -53,6 +54,7 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
|
|
|
53
54
|
const probeFrameScript = path.join(repoRoot, PROBE_FRAME_SCRIPT_RELATIVE);
|
|
54
55
|
const renderWhenIdleScript = path.join(repoRoot, RENDER_WHEN_IDLE_SCRIPT_RELATIVE);
|
|
55
56
|
const eyeBarScript = path.join(repoRoot, EYE_BAR_SCRIPT_RELATIVE);
|
|
57
|
+
const mediaScript = path.join(repoRoot, 'packages', 'akari-tools', 'bin', 'media.mjs');
|
|
56
58
|
|
|
57
59
|
return {
|
|
58
60
|
repoRoot,
|
|
@@ -66,24 +68,43 @@ export function resolveRepoAssets(repoRoot = DEFAULT_REPO_ROOT_CANDIDATE) {
|
|
|
66
68
|
assetResolverCliPath: existsSync(assetResolverCliPath) ? assetResolverCliPath : null,
|
|
67
69
|
beatmapScript: existsSync(beatmapScript) ? beatmapScript : null,
|
|
68
70
|
probeFrameScript: existsSync(probeFrameScript) ? probeFrameScript : null,
|
|
71
|
+
captureScript: existsSync(path.join(repoRoot, CAPTURE_SCRIPT_RELATIVE)) ? path.join(repoRoot, CAPTURE_SCRIPT_RELATIVE) : null,
|
|
69
72
|
renderWhenIdleScript: existsSync(renderWhenIdleScript) ? renderWhenIdleScript : null,
|
|
70
|
-
eyeBarScript: existsSync(eyeBarScript) ? eyeBarScript : null
|
|
73
|
+
eyeBarScript: existsSync(eyeBarScript) ? eyeBarScript : null,
|
|
74
|
+
mediaScript: existsSync(mediaScript) ? mediaScript : null
|
|
71
75
|
};
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
/**
|
|
75
|
-
* ランチャー実行時の同梱物解決:
|
|
76
|
-
*
|
|
77
|
-
*
|
|
79
|
+
* ランチャー実行時の同梱物解決: 資産ごとにモノレポ checkout(開発時)→ vendor/
|
|
80
|
+
* (npm 配布時)の順で探す。checkout が一部の資産しか持たないパッケージ構成でも、
|
|
81
|
+
* 欠けたフィールドだけを vendor から補完する。
|
|
78
82
|
*/
|
|
79
83
|
export function resolveLauncherAssets({
|
|
80
84
|
candidateRoot = DEFAULT_REPO_ROOT_CANDIDATE,
|
|
81
85
|
vendorRoot = DEFAULT_VENDOR_ROOT
|
|
82
86
|
} = {}) {
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return
|
|
87
|
+
const candidate = resolveRepoAssets(candidateRoot);
|
|
88
|
+
const vendor = resolveRepoAssets(vendorRoot);
|
|
89
|
+
const candidateHasAssets = Object.entries(candidate)
|
|
90
|
+
.some(([key, value]) => key !== 'repoRoot' && value !== null);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
repoRoot: candidateHasAssets ? candidateRoot : vendorRoot,
|
|
94
|
+
skillsSourceDir: candidate.skillsSourceDir ?? vendor.skillsSourceDir,
|
|
95
|
+
templateDir: candidate.templateDir ?? vendor.templateDir,
|
|
96
|
+
schemasSourceDir: candidate.schemasSourceDir ?? vendor.schemasSourceDir,
|
|
97
|
+
doctorScript: candidate.doctorScript ?? vendor.doctorScript,
|
|
98
|
+
scaffoldModulePath: candidate.scaffoldModulePath ?? vendor.scaffoldModulePath,
|
|
99
|
+
creatorRootModulePath: candidate.creatorRootModulePath ?? vendor.creatorRootModulePath,
|
|
100
|
+
audioFetchScriptPath: candidate.audioFetchScriptPath ?? vendor.audioFetchScriptPath,
|
|
101
|
+
assetResolverCliPath: candidate.assetResolverCliPath ?? vendor.assetResolverCliPath,
|
|
102
|
+
beatmapScript: candidate.beatmapScript ?? vendor.beatmapScript,
|
|
103
|
+
probeFrameScript: candidate.probeFrameScript ?? vendor.probeFrameScript,
|
|
104
|
+
// 既存の部分資産 fixture に capture marker が無いため、未解決時だけキー自体を省く。
|
|
105
|
+
...(candidate.captureScript ?? vendor.captureScript ? { captureScript: candidate.captureScript ?? vendor.captureScript } : {}),
|
|
106
|
+
renderWhenIdleScript: candidate.renderWhenIdleScript ?? vendor.renderWhenIdleScript,
|
|
107
|
+
eyeBarScript: candidate.eyeBarScript ?? vendor.eyeBarScript,
|
|
108
|
+
mediaScript: candidate.mediaScript ?? vendor.mediaScript
|
|
109
|
+
};
|
|
89
110
|
}
|
|
@@ -164,9 +164,6 @@ export async function resolveDoctorReport(options = {}) {
|
|
|
164
164
|
|
|
165
165
|
export async function resolveGpuExportAvailability(options = {}) {
|
|
166
166
|
const platform = options.platform ?? process.platform;
|
|
167
|
-
if (platform !== 'darwin') {
|
|
168
|
-
return { available: false, reason: 'GPU hardware export v0 is available on macOS only', launcher_tier: null };
|
|
169
|
-
}
|
|
170
167
|
try {
|
|
171
168
|
const resolver = options.resolveGpuLauncher
|
|
172
169
|
?? (await import('../../gpu-export/src/runner.mjs')).resolveGpuLauncher;
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"docs/contract-2026-08-03-cut-candidate-bridge-v1.md",
|
|
32
32
|
"docs/contract-2026-08-03-status-integrity-v1.md",
|
|
33
33
|
"docs/contract-2026-08-05-fx-v0.md",
|
|
34
|
+
"docs/contract-2026-08-09-transform-keyframes-v0.md",
|
|
34
35
|
"docs/contract-2026-08-11-analysis-vision-tracks-v0.md",
|
|
35
36
|
"docs/contract-2026-08-11-review-session-ui-events.md",
|
|
36
37
|
"docs/contract-2026-08-12-chat-approval-v0.md",
|
|
@@ -46,9 +47,14 @@
|
|
|
46
47
|
"docs/contract-2026-08-28-osr-export-v0.md",
|
|
47
48
|
"docs/contract-2026-08-28-v2-approximation-ledger.md",
|
|
48
49
|
"docs/contract-2026-08-28-v2-audio-roles-v0.md",
|
|
50
|
+
"docs/contract-2026-08-29-capture-v0.md",
|
|
51
|
+
"docs/contract-2026-08-29-media-inspect-cli-v0.md",
|
|
52
|
+
"docs/contract-2026-08-30-edit-json-v2-object-tree-v0.md",
|
|
53
|
+
"docs/contract-2026-08-30-motion-and-keyframes-v0.md",
|
|
49
54
|
"packages/akari-launcher/package.json",
|
|
50
55
|
"packages/akari-launcher/README.md",
|
|
51
56
|
"packages/akari-tools/package.json",
|
|
57
|
+
"packages/akari-tools/README.md",
|
|
52
58
|
"packages/analysis-report/package.json",
|
|
53
59
|
"packages/analysis-report/README.md",
|
|
54
60
|
"packages/asset-resolver/package.json",
|
|
@@ -66,6 +72,7 @@
|
|
|
66
72
|
"packages/edit-lint/package.json",
|
|
67
73
|
"packages/edit-lint/README.md",
|
|
68
74
|
"packages/edit-store/package.json",
|
|
75
|
+
"packages/edit-store/README.md",
|
|
69
76
|
"packages/export-nle/package.json",
|
|
70
77
|
"packages/frame-engine/package.json",
|
|
71
78
|
"packages/frame-engine/README.ja.md",
|
|
@@ -117,6 +124,8 @@
|
|
|
117
124
|
"skills/compile-review-session/dev-fixtures/README.md",
|
|
118
125
|
"skills/compile-review-session/SKILL.md",
|
|
119
126
|
"skills/create-project/SKILL.md",
|
|
127
|
+
"skills/critique-cut/SKILL.md",
|
|
128
|
+
"skills/critique-cut/workflow.md",
|
|
120
129
|
"skills/declare-audio/after-save.md",
|
|
121
130
|
"skills/declare-audio/launch.md",
|
|
122
131
|
"skills/declare-audio/SKILL.md",
|
|
@@ -42,6 +42,12 @@
|
|
|
42
42
|
| `cuts[].src` | v1 では必須。`sources[].id` を参照する |
|
|
43
43
|
| `cuts[].in`, `cuts[].out` | 対象ソースの秒。`0 <= in < out` を満たす |
|
|
44
44
|
|
|
45
|
+
`sources[].proxy` の生成規格は [プレビュー用プロキシの規格](./contract-2026-08-02-preview-parity.md#55-プレビュー用プロキシの規格) に従う。
|
|
46
|
+
|
|
47
|
+
v2 プレビューでは `sources[].proxy` は任意の最適化である。宣言されていれば既定でそれを使い、
|
|
48
|
+
起動を速くする。宣言が無ければ `VideoDecoder.isConfigSupported` で器の実力を調べ、原本を直接読む。
|
|
49
|
+
器が原本を扱えない場合は preview-server が同じプロキシ規格で自動生成する。
|
|
50
|
+
|
|
45
51
|
参照は path ではなく安定した `id` で行う。これにより素材の差し替えや path 変更で cut や
|
|
46
52
|
サイドカーの参照が壊れない。JSON Schema は将来の任意フィールドを許容する tolerant reader とし、
|
|
47
53
|
既知フィールドの型、version ごとの必須形、`source` / `sources[]` の排他を検証する。
|
|
@@ -76,6 +82,7 @@ source 秒アンカー規則の対象外である。
|
|
|
76
82
|
- `review.json` の `annotations[]` は任意フィールド `src` を持てる。値は `sources[].id` への参照で、
|
|
77
83
|
`sourceT` または `sourceRange` と組にして source 座標を表す
|
|
78
84
|
- `src` の省略は単一ソース互換を意味する
|
|
85
|
+
- 字幕の重なり(`captions.overlap`)と並び順(`captions.order`)の判定は `src` ごとに行う
|
|
79
86
|
|
|
80
87
|
analysis サイドカーは素材単位のままとし、構造は変更しない。参照時に `src` から source path を
|
|
81
88
|
解決する。
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
素の色のまま合成されて、窓の継ぎ目で肌色が食い違った。「プロジェクト全体の色」だと
|
|
41
41
|
誤解しやすいため、ここに明記する。
|
|
42
42
|
4. **書き出しエンジンの既定(2026-08-28 改訂)**: `--engine` 省略時は `auto`。
|
|
43
|
-
darwin
|
|
43
|
+
darwin / win32 では適格なら GPU、不適格なら OSR、linux では legacy に解決する。従来経路へ戻す場合は
|
|
44
44
|
`render-cut --engine legacy` を明示する。
|
|
45
45
|
|
|
46
46
|
### 2-4. reveal 系トランジション(`reveal-down` / `reveal-up`。2026-08-14 追加)
|
|
@@ -168,3 +168,11 @@ x264、その他の環境で x264 の順に解決する。ハードウェア対
|
|
|
168
168
|
誤判定を避けるため 256x144 で試し焼きし、`AKARI_EXPORT_FORCE_X264=1` のときは試し焼きせず
|
|
169
169
|
すべて不採用とする。明示指定した Windows 向け方式が利用不能なら x264 へ暗黙移行せず停止する。
|
|
170
170
|
`AKARI_EXPORT_FORCE_X264=1` のときに Windows 向け方式を明示指定した場合も、同じく停止する。
|
|
171
|
+
|
|
172
|
+
## 7. v2 書き出しの cut 中間物(2026-08-29 追記)
|
|
173
|
+
|
|
174
|
+
OSR / GPU の v2 書き出しでは、映像エンジンが `edit.sources` から直接描画するため、cut 段と
|
|
175
|
+
tail-pad 段は音声専用中間物だけを生成する。通常は `cut-audio.mp4`、最終尺までの音声 padding が
|
|
176
|
+
必要な場合は `cut-audio-tail-padded.mp4` を使用し、どちらも `-vn` で映像を処理しない。
|
|
177
|
+
legacy 書き出しは従来どおり `cut.mp4` と必要時の `cut-tail-padded.mp4` を使用する。
|
|
178
|
+
音声入力は cut ごとに入力側シーク(`-ss` / `-t`)し、cut 頭 0.5 s の先読みガード(AAC の overlap-add 用)を設け、cut 段の費用を素材長に依存させない。
|
|
@@ -149,3 +149,15 @@ updated: 2026-08-26
|
|
|
149
149
|
- スキーマ変更(`edit.json` 等の既存契約ファイル形は不変)
|
|
150
150
|
- 「paths 宣言」(プロジェクトごとの置き場所カスタマイズ UI)の導入
|
|
151
151
|
- ルート直下 `cache/`(レガシー)の最終的な扱いの裁定(§3・§6)
|
|
152
|
+
|
|
153
|
+
## 8. 追記(2026-08-30)— `motion/` を正本ディレクトリとして登録
|
|
154
|
+
|
|
155
|
+
`contract-2026-08-30-edit-json-v2-object-tree-v0.md` / `contract-2026-08-30-motion-and-keyframes-v0.md` により、
|
|
156
|
+
プロジェクト直下に **`motion/`** を追加する。
|
|
157
|
+
|
|
158
|
+
| 用途 | 置き場 | 作成者 | 性質 |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| キーフレーム曲線の袋(`motion/<group-id>.json`。edit.json の `keyframes: { path, count }` から参照)| `motion/` | edit-store(保存時に inline から振り分け)| **正本**(再生成不可。`.akari/cache/` ではない)|
|
|
161
|
+
|
|
162
|
+
- edit.json / captions.json と同じトランザクションで保存され、同じ lint ゲートを通る
|
|
163
|
+
- §2 の「ルート直下への新規ファイル作成は正本ファイルに限る」の例外ではなく、正本ファイルの追加である
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|---|---|---|
|
|
11
11
|
| 2026-08-02 | v0 | Web UI と shell の挙動仕様を統合 |
|
|
12
12
|
| 2026-08-28 | v2 | `packages/frame-engine` の意味論へ統合し、検収をゴールデンフレームへ一本化。出口を OSR と GPU 直結の 2 本に固定し、互換経路を退役節へ移動 |
|
|
13
|
+
| 2026-08-31 | v2.1 | §5.2 に断片 CSS の `vw` / `vh` 系単位の出力サイズ基準化(`viewport-units.js`。プレビューがウィンドウ幅基準で解いていた実機報告の修正)を追記 |
|
|
13
14
|
|
|
14
15
|
## 1. 役割分担
|
|
15
16
|
|
|
@@ -195,6 +196,7 @@ SHA-256 一致を要求する。GPU は同一マシン一致率を診断値と
|
|
|
195
196
|
| GPU 直結・engine 区間 | OSR decode 比較の per-frame MAD ≤ 1.0 | 超過は不合格 |
|
|
196
197
|
| GPU 直結・字幕 | cue 代表 5 時刻の下半分 MAD ≤ 1.0 | 超過は不合格 |
|
|
197
198
|
| GPU 直結・3D | 3D active 区間 MAD ≤ 1.0 | 超過は不合格 |
|
|
199
|
+
| GPU 直結・DOM 層 | overlay 外接矩形内 MAD ≤ 1.0、t=0 を含む代表 5 時刻 | いずれかの超過または sentinel 不一致は不合格 |
|
|
198
200
|
|
|
199
201
|
OSR の比較は H.264 を再 decode した画像ではなく捕捉時の raw BGRA を使う。ソフト描画の検収と GPU の
|
|
200
202
|
診断値を混同しない。
|
|
@@ -241,6 +243,14 @@ legacy 合成経路は、移行中の既存利用者と Windows を支えるた
|
|
|
241
243
|
`edit.json` のあるディレクトリからの相対ファイルパスとして解決する。`vars` は `--` で始まるキーだけを
|
|
242
244
|
CSS カスタムプロパティとして overlay root へ適用し、それ以外のキーは DOM や JavaScript へ注入しない。
|
|
243
245
|
|
|
246
|
+
断片 CSS の `vw` / `vh` / `vmin` / `vmax`(`dvw` 等の接頭辞付き・`vi` / `vb` 含む)は**出力サイズ基準**で
|
|
247
|
+
解決する(`1vw` = `output.width / 100` px)。書き出しは出力サイズちょうどの viewport で overlay sheet を
|
|
248
|
+
描くので素のままで正しいが、器のプレビューはステージを `scale()` でペインへ収めるため素の `vw` は
|
|
249
|
+
ウィンドウ幅基準になってしまう。器は mount 時に `packages/overlay-runtime/src/viewport-units.js` で
|
|
250
|
+
`<style>` / `style=""` の `<数値><単位>` を `calc(<数値> * var(--akari-vw, 1vw))` へ書き換え、ステージ要素に
|
|
251
|
+
`--akari-vw` 等(出力サイズ / 100 px)を定義して一致させる(2026-08-31。shell / Web 共通。`@media` 等の
|
|
252
|
+
プレリュード・文字列・`url()` は書き換えない)。
|
|
253
|
+
|
|
244
254
|
### 5.3 書き込み経路
|
|
245
255
|
|
|
246
256
|
UI、API、RPC の別を問わず、`edit.json` / `captions.json` へのすべての書き込みは edit-lint ゲートを通す。
|
|
@@ -253,3 +263,55 @@ lint 実行系が見つからない場合は **fail-open**(2026-08-02 オー
|
|
|
253
263
|
ペン描画の単一正本は `packages/pen-visuals` の `PEN_TUNING` と描画プリミティブである。器や overlay
|
|
254
264
|
sheet が独自の補間、太さ、透明度、消去規則を持ってはならない。フェード時間は **600ms** を正とする
|
|
255
265
|
(2026-08-02 オーナー裁定)。
|
|
266
|
+
|
|
267
|
+
### 5.5 プレビュー用プロキシの規格
|
|
268
|
+
|
|
269
|
+
frame-engine がランダムアクセスするプレビュー用プロキシは、H.264 High Profile の 8bit
|
|
270
|
+
`yuv420p`、GOP 1 秒以下、B フレームなし、faststart とする。GOP はソースの実測 fps を丸めた
|
|
271
|
+
フレーム数を使い、`-g <fps> -keyint_min <fps> -sc_threshold 0 -bf 0` を指定する。変換後も尺と
|
|
272
|
+
コマ数はソースと一致させる。
|
|
273
|
+
29.97 fps の GOP は 30 コマで 1.001 秒になるため、doctor / lint などで機械照合するときの閾値は 1.05 秒に置く。
|
|
274
|
+
|
|
275
|
+
生成経路は shell の HEVC フォールバックと preview-server の HEVC プロキシの 2 系統であり、
|
|
276
|
+
いずれも `packages/media-bin/src/proxy-recipe.mjs` を唯一の定義として使う。レシピ版
|
|
277
|
+
`gop1s-v1` は shell のキャッシュキーと preview-server の出力名へ含め、旧規格のキャッシュを
|
|
278
|
+
次回参照時に再利用しない。
|
|
279
|
+
|
|
280
|
+
### 5.6 読み込み予算と原本 / proxy の選択規則
|
|
281
|
+
|
|
282
|
+
frame-engine のソース読み込み予算は、`Content-Length` が得られる場合
|
|
283
|
+
`max(10 秒, bytes / 8 MiB毎秒)` とする。予算を超えても受信進捗が続く間は打ち切らず、進捗が
|
|
284
|
+
5 秒間止まった場合に失敗とする。同一 URL の fetch はセッションにつき 1 回に限り、再試行では
|
|
285
|
+
取得済みバイトと解析済み moov / キーフレーム索引を再利用する。
|
|
286
|
+
|
|
287
|
+
v2 プレビューの既定選択は次の順序とする。
|
|
288
|
+
|
|
289
|
+
1. 宣言済み proxy があれば proxy を使う(`declared`)
|
|
290
|
+
2. proxy が無ければ codec をプローブし、`hw || any` で扱える場合は原本を使う
|
|
291
|
+
(`hardware-ok` / `decoder-ok`)
|
|
292
|
+
3. 扱えない場合は preview-server に自動プロキシを要求し、生成中は非致命の通知を表示する
|
|
293
|
+
(`auto-proxy`)
|
|
294
|
+
|
|
295
|
+
Web UI の `?frameEngineSource=original`、または shell の
|
|
296
|
+
`AKARI_FRAME_ENGINE_SOURCE=original` では 1 を飛ばして器の実力判定へ進む。`=proxy` では従来どおり
|
|
297
|
+
proxy を無条件に優先する。
|
|
298
|
+
`AKARI_FRAME_ENGINE_FORCE_SW=1` はハードウェアデコード不可を模擬するテスト用スイッチである。
|
|
299
|
+
HEVC は `prefer-software` が通らないため、codec プローブが `sw=false` を返した系列について
|
|
300
|
+
ClipSessionPool はソフトウェア退避を学習しない。ソフトウェア退避の学習対象は H.264 のみとする。
|
|
301
|
+
|
|
302
|
+
tkhd に 90 / 180 / 270 度の回転を持つ素材は、既定でデコーダ出力を毎フレームの
|
|
303
|
+
OffscreenCanvas へ焼き直さない。frame-engine は回転メタをフレームへ付帯し、compositor の
|
|
304
|
+
UV 逆写像で表示回転を 1 回だけ適用する。crop、framing、keyframe、既存 transform / perspective は
|
|
305
|
+
回転後の論理空間を基準とし、90 / 270 度では coded width / height を入れ替えた論理寸法を使う。
|
|
306
|
+
この規則は VideoFrame の直接 upload と copyTo の両経路に共通である。
|
|
307
|
+
|
|
308
|
+
デコーダエラーは window 全域イベントで飛ぶため、他クリップの失敗と区別できない。frame-engine は
|
|
309
|
+
検出後 `decoderErrorGraceMs`(既定 1 秒)だけ自分の操作の成功を待ち、期限内に成功した場合はその
|
|
310
|
+
エラーを無視する。prime がフレームを返さず、かつエラーを観測した場合だけ、その試行を失敗とする。
|
|
311
|
+
|
|
312
|
+
### 5.7 映像ソースの読み方とパリティ
|
|
313
|
+
|
|
314
|
+
frame-engine が MP4 を全体ストリームとして読むか、`ftyp` / `moov` の索引と必要な圧縮サンプルの
|
|
315
|
+
Range として読むかは、完成画の意味論に影響しない。どちらの読み込み経路も同じ presentation 時刻の
|
|
316
|
+
VideoFrame を §4.1 の評価点へ供給し、`elst.media_time`、B フレームの並べ替え、メディア終端を含めて
|
|
317
|
+
golden の `diff 0` を満たさなければならない。ソース取得方法の変更をパリティ差の許容理由にしてはならない。
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
lifecycle: implemented
|
|
3
|
+
created: 2026-08-09
|
|
4
|
+
updated: 2026-08-30
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 変形キーフレーム契約 v0(`layers[].keyframes` / v2 `items[].keyframes`)— 2026-08-30 復元
|
|
8
|
+
|
|
9
|
+
> **復元の注記(2026-08-30)**: 本契約は `packages/schemas/edit.schema.json`(`layerKeyframe` / `layerItem` / `keyframeV2` の `$comment`)・
|
|
10
|
+
> `packages/schemas/bin/validate-edit.mjs`・`packages/render-cut/src/layer-keyframes.mjs` ほか 10 箇所から参照されていたが、
|
|
11
|
+
> 実ファイルがどのブランチの履歴にも存在しなかった。スキーマの `$comment` に残っていた意味論からそのまま再構成した。
|
|
12
|
+
> **後継 = `contract-2026-08-30-motion-and-keyframes-v0.md` §2**(opacity の追加・easing 語彙の拡張・`motion/` 袋への参照形)。
|
|
13
|
+
> 本ファイルは v0 の意味論の記録であり、これ以上追記しない。
|
|
14
|
+
|
|
15
|
+
- 日付: 2026-08-09(実装済み・`contract-2026-07-22-render-basics.md` §4-4 に要約あり)
|
|
16
|
+
- 状態: implemented(v1 `layers[].keyframes`・v2 `items[].keyframes` の両方で有効)
|
|
17
|
+
|
|
18
|
+
## 1. 意味論
|
|
19
|
+
|
|
20
|
+
- `keyframes[]` はレイヤー / アイテムの `transform` / `crop` / `perspective` を時間で動かす共通機構
|
|
21
|
+
- `t` は**ローカル時間**: v1 `layers[].keyframes[].t` はレイヤー内秒(`layerItem.t` を 0 とする。`cuts[].framing.keyframes[].t` と同じ規約)、
|
|
22
|
+
v2 `items[].keyframes[].t` は**アイテム内の整数フレーム**(`item.at` を 0 とする)
|
|
23
|
+
- `transform` / `crop` / `perspective` はそれぞれ**独立の任意プロパティ**(プロパティごとの別トラックにはしない — 1 点で複数プロパティを同時に動かせる)
|
|
24
|
+
- ある区間の両端点が同じプロパティを持てばその間は**線形補間**。片方の端点にしか無ければ直近の宣言値を**保持(hold)**
|
|
25
|
+
- どの点にも一度も宣言されないプロパティは、レイヤー / アイテム直下の**静的値**(省略時は各 `$def` の既定値)を全区間で保持する
|
|
26
|
+
- `easing` は点ごとに設定し、**その点へ入る区間**(1 つ前の点からこの点まで)の補間カーブを決める。先頭点の `easing` は無視。省略時 `linear`。語彙は `linear` / `ease-in-out`
|
|
27
|
+
- 2 点以上・`t` 昇順・重複禁止(`validate-edit.mjs` で検証)。`keyframes` が無い、または使える点が 2 点未満のときは既存の静的値のみが効く(回帰なし・バイト等価)
|
|
28
|
+
- render-cut は cuts 合成後のベース映像へ `t` 順に合成する。プレビューは同じ補間を CSS / WebGL で再現する(`contract-2026-08-02-preview-parity.md`)
|
|
29
|
+
|
|
30
|
+
## 2. 参照元(復元時点)
|
|
31
|
+
|
|
32
|
+
`packages/schemas/edit.schema.json` / `packages/schemas/bin/validate-edit.mjs` / `packages/render-cut/src/layer-keyframes.mjs` / `packages/render-cut/src/layers.mjs` /
|
|
33
|
+
`packages/render-cut/test/layer-keyframes.test.mjs` / `packages/preview-server/test/layer-keyframes-visual.test.mjs` /
|
|
34
|
+
`apps/shell/extensions/akari-preview/src/common/edit-summary-fields.ts` / `apps/shell/extensions/akari-preview/src/common/layer-keyframes-visual.ts` /
|
|
35
|
+
`apps/shell/extensions/akari-preview/src/browser/akari-preview-open-handler.ts` とそのテスト
|
|
@@ -23,6 +23,12 @@ updated: 2026-08-12
|
|
|
23
23
|
- `contract-2026-07-17-data-contract-versioning.md`(version 整数・追加のみ・寛容リーダーの三原則)
|
|
24
24
|
- スコープ: `edit.json` の `cuts[]` がメイン時間軸で静止画ソース(png/jpg/jpeg/webp/bmp/gif)を
|
|
25
25
|
直接読めるようにする。**新しいスキーマフィールドは作らない**(判定は拡張子のみ)
|
|
26
|
+
- 2026-08-31 追記(issue #30): frame-engine 経路(`--engine gpu` / `osr`、および v2 プレビュー)でも同じ
|
|
27
|
+
意味論で描く。runtime が拡張子で `CachedStillImageSource` として登録した素材を、`plan.ts` が
|
|
28
|
+
`kind: 'image'` の base 層(`sourceTimeUs` は常に 0・尺は `out - in`・transform / crop / keyframes は
|
|
29
|
+
動画 cut と同じ・トランジションの outgoing / incoming にもなれる)として評価し、compositor は
|
|
30
|
+
layers と同じ texture cache を base の RGBA 経路へ結ぶ。それまでは `layerFromPlacement` が
|
|
31
|
+
`decode` 持ちしか受けず `no video frame source registered` で落ちていた(legacy との受理差)
|
|
26
32
|
|
|
27
33
|
## 0. 背景
|
|
28
34
|
|