akari-video 0.1.22 → 0.1.24
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/README.md +14 -0
- package/package.json +1 -1
- package/src/chrome-command.mjs +96 -0
- package/src/cli.mjs +3 -0
- package/src/messages.mjs +1 -0
- package/src/migrate-command.mjs +11 -2
- package/vendor/.akari-capability-sources.json +10 -0
- package/vendor/docs/contract-2026-07-20-edit-json-v1-narration.md +12 -0
- package/vendor/docs/contract-2026-07-22-render-basics.md +21 -0
- package/vendor/docs/contract-2026-07-23-analysis-person-matte.md +26 -0
- package/vendor/docs/contract-2026-07-25-project-structure-v0.md +17 -0
- package/vendor/docs/contract-2026-07-25-r6-audio-tracks-and-trim.md +8 -0
- package/vendor/docs/contract-2026-08-02-preview-parity.md +39 -0
- package/vendor/docs/contract-2026-08-28-osr-export-v0.md +119 -0
- package/vendor/docs/contract-2026-08-28-v2-audio-roles-v0.md +115 -0
- package/vendor/packages/akari-launcher/README.md +14 -0
- package/vendor/packages/akari-launcher/package.json +1 -1
- package/vendor/packages/analysis-report/package.json +4 -8
- package/vendor/packages/analysis-report/render-analysis-report.mjs +588 -0
- package/vendor/packages/analysis-report/template.html +2033 -0
- package/vendor/packages/decision-log-report/README.md +50 -0
- package/vendor/packages/decision-log-report/package.json +13 -0
- package/vendor/packages/decision-log-report/render-decision-log-report.mjs +484 -0
- package/vendor/packages/decision-log-report/template.html +471 -0
- package/vendor/packages/edit-lint/README.md +44 -0
- package/vendor/packages/edit-lint/src/edit-lint.mjs +355 -24
- package/vendor/packages/edit-store/lib/audio-schedule.d.ts +62 -0
- package/vendor/packages/edit-store/lib/audio-schedule.js +284 -0
- package/vendor/packages/edit-store/lib/caption-display.d.ts +10 -0
- package/vendor/packages/edit-store/lib/caption-display.js +57 -0
- package/vendor/packages/edit-store/lib/ducking.d.ts +25 -0
- package/vendor/packages/edit-store/lib/ducking.js +31 -0
- package/vendor/packages/edit-store/lib/edit-store.d.ts +2 -0
- package/vendor/packages/edit-store/lib/index.d.ts +3 -0
- package/vendor/packages/edit-store/lib/index.js +3 -0
- package/vendor/packages/edit-store/lib/internal-model.js +32 -11
- package/vendor/packages/edit-store/lib/migrate/index.d.ts +2 -0
- package/vendor/packages/edit-store/lib/migrate/index.js +49 -21
- package/vendor/packages/edit-store/lib/migrate/legacy-parse.js +4 -0
- package/vendor/packages/edit-store/lib/transition-visual.d.ts +23 -0
- package/vendor/packages/edit-store/lib/transition-visual.js +141 -0
- package/vendor/packages/edit-store/lib/webview-kernel.d.ts +4 -0
- package/vendor/packages/edit-store/lib/webview-kernel.js +443 -0
- package/vendor/packages/frame-engine/README.ja.md +43 -0
- package/vendor/packages/frame-engine/README.md +43 -0
- package/vendor/packages/frame-engine/package.json +34 -0
- package/vendor/packages/osr-export/README.md +63 -0
- package/vendor/packages/osr-export/package.json +27 -0
- package/vendor/packages/preview-engine/README.md +38 -0
- package/vendor/packages/preview-server/package.json +2 -1
- package/vendor/packages/schemas/analysis.schema.json +11 -1
- package/vendor/packages/schemas/edit.schema.json +4 -1
- package/vendor/packages/schemas/examples/edit-narration-trim-valid/edit.json +33 -0
- package/vendor/packages/schemas/test/edit-narration-trim.test.mjs +30 -0
- package/vendor/skills/analyze-footage/bin/person-matte/mask-format.mjs +25 -0
- package/vendor/skills/analyze-footage/bin/person-matte/mask-from-alpha.mjs +249 -0
- package/vendor/skills/analyze-footage/bin/person-matte/mask-roundtrip.mjs +206 -0
- package/vendor/skills/analyze-footage/bin/person-matte/person-cutout.mjs +3 -0
- package/vendor/skills/analyze-footage/bin/person-matte/person-matte.mjs +119 -4
- package/vendor/skills/analyze-footage/person-matte.md +20 -0
- package/vendor/skills/analyze-footage/test/person-matte-mask.test.mjs +85 -0
- package/vendor/skills/analyze-project/decision-record.md +3 -0
- package/vendor/skills/edit-plan/SKILL.md +3 -1
- package/vendor/skills/edit-plan/approvals-and-generation.md +2 -1
- package/vendor/skills/edit-plan/execution.md +1 -0
- package/vendor/skills/edit-plan/report-guide.md +8 -0
- package/vendor/skills/edit-plan/workflow.md +3 -0
package/README.md
CHANGED
|
@@ -52,6 +52,20 @@ DL・sha256 検証・適用まで実行。それ以外(npm グローバル / g
|
|
|
52
52
|
`packages/asset-resolver` の CLI への薄い委譲で、カタログ合成・entitlements 判定・
|
|
53
53
|
sha256 検証・fail-closed は resolver 側の責務のまま。`src/assets-command.mjs`)。
|
|
54
54
|
|
|
55
|
+
### Chrome の導入
|
|
56
|
+
|
|
57
|
+
動画の書き出しに使う Chrome for Testing は、次のコマンドで導入できる。
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
akari chrome install
|
|
61
|
+
akari chrome install --cache-dir /path/to/cache
|
|
62
|
+
akari chrome install --build-id 150.0.7339.16
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
保存先の既定は `~/.cache/puppeteer`。`--cache-dir` で保存先、`--build-id` で導入する
|
|
66
|
+
buildId を指定できる。実行環境から `@puppeteer/browsers` を解決できる必要があり、
|
|
67
|
+
AKARI Video アプリの配布物には必要なモジュールが同梱されている。
|
|
68
|
+
|
|
55
69
|
`akari` に渡した引数はそのまま `opencode` に転送する(例: `akari --continue` は
|
|
56
70
|
`opencode --continue` を起動する)。
|
|
57
71
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
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,96 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const usage = [
|
|
5
|
+
'使い方: akari chrome install [options]',
|
|
6
|
+
'',
|
|
7
|
+
'オプション:',
|
|
8
|
+
' --cache-dir <dir> Chrome の保存先(既定: ~/.cache/puppeteer)',
|
|
9
|
+
' --build-id <id> 導入する Chrome の buildId(既定: stable の最新版)',
|
|
10
|
+
' -h, --help このヘルプを表示',
|
|
11
|
+
].join('\n');
|
|
12
|
+
|
|
13
|
+
class PublicError extends Error {
|
|
14
|
+
constructor(message, exitCode = 1) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.exitCode = exitCode;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseInstallArguments(argv, homeDirectory) {
|
|
21
|
+
const parsed = {
|
|
22
|
+
cacheDir: join(homeDirectory, '.cache', 'puppeteer'),
|
|
23
|
+
buildId: undefined,
|
|
24
|
+
};
|
|
25
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
26
|
+
const argument = argv[index];
|
|
27
|
+
if (argument !== '--cache-dir' && argument !== '--build-id') {
|
|
28
|
+
throw new PublicError(`不明な引数です: ${argument}`, 2);
|
|
29
|
+
}
|
|
30
|
+
const value = argv[index + 1];
|
|
31
|
+
if (value === undefined || value.startsWith('--')) {
|
|
32
|
+
throw new PublicError(`${argument} の値がありません`, 2);
|
|
33
|
+
}
|
|
34
|
+
index += 1;
|
|
35
|
+
if (argument === '--cache-dir') parsed.cacheDir = resolve(value);
|
|
36
|
+
else parsed.buildId = value;
|
|
37
|
+
}
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function runChromeCommand(argv, options = {}) {
|
|
42
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
43
|
+
const error = options.error ?? ((line) => console.error(line));
|
|
44
|
+
const loadBrowsers = options.loadBrowsers ?? (() => import('@puppeteer/browsers'));
|
|
45
|
+
const homeDirectory = options.homeDirectory ?? homedir();
|
|
46
|
+
|
|
47
|
+
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h'
|
|
48
|
+
|| (argv[0] === 'install' && (argv.includes('--help') || argv.includes('-h')))) {
|
|
49
|
+
log(usage);
|
|
50
|
+
return { exitCode: 0 };
|
|
51
|
+
}
|
|
52
|
+
if (argv[0] !== 'install') {
|
|
53
|
+
error(`不明なサブコマンドです: ${argv[0]}\n${usage}`);
|
|
54
|
+
return { exitCode: 2 };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = parseInstallArguments(argv, homeDirectory);
|
|
60
|
+
} catch (cause) {
|
|
61
|
+
error(`${cause instanceof Error ? cause.message : String(cause)}\n${usage}`);
|
|
62
|
+
return { exitCode: cause instanceof PublicError ? cause.exitCode : 1 };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let browsers;
|
|
66
|
+
try {
|
|
67
|
+
browsers = await loadBrowsers();
|
|
68
|
+
} catch (cause) {
|
|
69
|
+
error([
|
|
70
|
+
`@puppeteer/browsers を読み込めないため Chrome を導入できません: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
71
|
+
'システムの node がある場合は `npx puppeteer browsers install chrome` でも導入できます。',
|
|
72
|
+
].join('\n'));
|
|
73
|
+
return { exitCode: 1 };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const platform = browsers.detectBrowserPlatform();
|
|
78
|
+
const buildId = parsed.buildId
|
|
79
|
+
?? await browsers.resolveBuildId('chrome', platform, 'stable');
|
|
80
|
+
const installed = await browsers.install({
|
|
81
|
+
browser: 'chrome',
|
|
82
|
+
buildId,
|
|
83
|
+
cacheDir: parsed.cacheDir,
|
|
84
|
+
downloadProgressCallback: 'default',
|
|
85
|
+
});
|
|
86
|
+
if (typeof installed?.executablePath !== 'string' || installed.executablePath.length === 0) {
|
|
87
|
+
throw new Error('導入後の実行ファイルを特定できませんでした。');
|
|
88
|
+
}
|
|
89
|
+
log(`buildId: ${buildId}`);
|
|
90
|
+
log(`実行ファイル: ${resolve(installed.executablePath)}`);
|
|
91
|
+
return { exitCode: 0 };
|
|
92
|
+
} catch (cause) {
|
|
93
|
+
error(`Chrome の導入に失敗しました: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
94
|
+
return { exitCode: 1 };
|
|
95
|
+
}
|
|
96
|
+
}
|
package/src/cli.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
triggerBackgroundRefresh
|
|
23
23
|
} from './update-check.mjs';
|
|
24
24
|
import { applySelfUpdate, isRunningFromAppDir, rollbackSelfUpdate } from './self-update.mjs';
|
|
25
|
+
import { runChromeCommand } from './chrome-command.mjs';
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* `akari` ランチャーの本体。3 入口契約(ターミナル `akari` / セッション内 `/akari` /
|
|
@@ -33,6 +34,8 @@ import { applySelfUpdate, isRunningFromAppDir, rollbackSelfUpdate } from './self
|
|
|
33
34
|
* options 経由で差し替え可能にしてあり、node --test から実プロセスを起動せずに分岐を検証できる。
|
|
34
35
|
*/
|
|
35
36
|
export async function run(args, options = {}) {
|
|
37
|
+
if (args[0] === 'chrome') return runChromeCommand(args.slice(1), options);
|
|
38
|
+
|
|
36
39
|
const log = options.log ?? ((line) => console.log(line));
|
|
37
40
|
const assets = options.assets ?? resolveLauncherAssets();
|
|
38
41
|
const scaffold = options.scaffold ?? defaultScaffold;
|
package/src/messages.mjs
CHANGED
|
@@ -303,6 +303,7 @@ export function describeCliHelp() {
|
|
|
303
303
|
' (引数なし) プロジェクトを開いて AI エージェントを起動(未作成なら自動作成)',
|
|
304
304
|
' store connect アカウント連携(無料の素材パックと購入済み素材が使えるようになる)',
|
|
305
305
|
' sounds 公式音源ライブラリを一括ダウンロード(無料)',
|
|
306
|
+
' chrome install 動画書き出しに使う Chrome をダウンロードして導入する',
|
|
306
307
|
' update [--force] 更新を確認する(--force で本体を入れ直す)',
|
|
307
308
|
' status 接続状態を確認する',
|
|
308
309
|
' migrate [dir] 古い edit.json を退避バックアップ付きで v2 へ変換',
|
package/src/migrate-command.mjs
CHANGED
|
@@ -22,15 +22,24 @@ export async function runMigrateCommand(args, options = {}) {
|
|
|
22
22
|
}
|
|
23
23
|
const projectRoot = parsed.projectRoot;
|
|
24
24
|
const editPath = path.join(projectRoot, 'edit.json');
|
|
25
|
+
const captionsPath = path.join(projectRoot, 'captions.json');
|
|
26
|
+
const readText = options.readFile ?? readFile;
|
|
25
27
|
let text;
|
|
26
28
|
try {
|
|
27
|
-
text = await (
|
|
29
|
+
text = await readText(editPath, 'utf8');
|
|
28
30
|
} catch (cause) {
|
|
29
31
|
error(`edit.json を読めません: ${editPath} (${messageOf(cause)})`);
|
|
30
32
|
return { exitCode: 2 };
|
|
31
33
|
}
|
|
32
34
|
const migrate = options.migrate ?? loadMigrateModule(options.assets ?? resolveLauncherAssets());
|
|
33
|
-
|
|
35
|
+
let captionsRoot;
|
|
36
|
+
try {
|
|
37
|
+
captionsRoot = JSON.parse(await readText(captionsPath, 'utf8'));
|
|
38
|
+
} catch {
|
|
39
|
+
// captions.json の不在・読み取り失敗・壊れた JSON は cue なしとして移行を続ける。
|
|
40
|
+
}
|
|
41
|
+
const hasCaptions = migrate.captionsHaveRenderableCues(captionsRoot);
|
|
42
|
+
const proposal = migrate.planMigration(projectRoot, editPath, text, { hasCaptions, now: options.now });
|
|
34
43
|
if (proposal.ok === false) {
|
|
35
44
|
if (parsed.json) {
|
|
36
45
|
log(JSON.stringify({ ok: false, error: 'このプロジェクトは変換できません', blockers: proposal.blockers }));
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"docs/contract-2026-08-18-v1-render-parity.md",
|
|
43
43
|
"docs/contract-2026-08-23-captions-emphasis-words-v0.md",
|
|
44
44
|
"docs/contract-2026-08-23-stroke-persistence.md",
|
|
45
|
+
"docs/contract-2026-08-28-osr-export-v0.md",
|
|
46
|
+
"docs/contract-2026-08-28-v2-audio-roles-v0.md",
|
|
45
47
|
"packages/akari-launcher/package.json",
|
|
46
48
|
"packages/akari-launcher/README.md",
|
|
47
49
|
"packages/akari-tools/package.json",
|
|
@@ -57,13 +59,21 @@
|
|
|
57
59
|
"packages/creator-root/package.json",
|
|
58
60
|
"packages/decision-cards/package.json",
|
|
59
61
|
"packages/decision-cards/README.md",
|
|
62
|
+
"packages/decision-log-report/package.json",
|
|
63
|
+
"packages/decision-log-report/README.md",
|
|
60
64
|
"packages/edit-lint/package.json",
|
|
65
|
+
"packages/edit-lint/README.md",
|
|
61
66
|
"packages/edit-store/package.json",
|
|
62
67
|
"packages/export-nle/package.json",
|
|
68
|
+
"packages/frame-engine/package.json",
|
|
69
|
+
"packages/frame-engine/README.ja.md",
|
|
70
|
+
"packages/frame-engine/README.md",
|
|
63
71
|
"packages/intake-form/package.json",
|
|
64
72
|
"packages/intake-form/README.md",
|
|
65
73
|
"packages/matte-rvm/package.json",
|
|
66
74
|
"packages/media-bin/package.json",
|
|
75
|
+
"packages/osr-export/package.json",
|
|
76
|
+
"packages/osr-export/README.md",
|
|
67
77
|
"packages/overlay-runtime/package.json",
|
|
68
78
|
"packages/overlay-runtime/README.md",
|
|
69
79
|
"packages/pen-visuals/package.json",
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"id": "n-0001", // 必須。^n-\d{4}$。edit.json 内で一意
|
|
40
40
|
"path": "out/narration/n-0001.mp3", // 必須。edit.json からの相対パス
|
|
41
41
|
"t": 12.5, // 必須。タイムライン秒。0 以上
|
|
42
|
+
"in": 5.8, // 任意。素材秒。省略時 0
|
|
43
|
+
"out": 9.7, // 任意。素材秒。省略時 素材末尾
|
|
42
44
|
"gain_db": 0, // 任意。既定 0。[-60, 12](bgm/sfx と同一。範囲外はエラー)
|
|
43
45
|
"script": "こんにちは、AKARI Videoです。", // 任意。表示原稿(人間が読む正本)
|
|
44
46
|
"reading": "こんにちわ、あかりびでおです。", // 任意。読み原稿(かな化後・生成に使った実テキスト)
|
|
@@ -63,6 +65,8 @@
|
|
|
63
65
|
| `audio.narration[].id` | string | 必須(要素内) | — | `^n-\d{4}$`。edit.json 内で一意 |
|
|
64
66
|
| `audio.narration[].path` | string | 必須(要素内) | — | edit.json からの相対(`audio.sfx[].path` と同一規約) |
|
|
65
67
|
| `audio.narration[].t` | number | 必須(要素内) | — | **タイムライン秒**(`audio.sfx[].t` / `overlays[].start` と同じ座標系)。0 以上 |
|
|
68
|
+
| `audio.narration[].in` | number | 否 | `0` | 素材秒。再生窓 `[in, out)` の始点。0 以上 |
|
|
69
|
+
| `audio.narration[].out` | number | 否 | 素材末尾 | 素材秒。再生窓 `[in, out)` の終点。0 より大きく `out > in` |
|
|
66
70
|
| `audio.narration[].gain_db` | number | 否 | `0.0` | dB。クランプ範囲 `[-60, 12]`(`audio.bgm` / `audio.sfx` と同一) |
|
|
67
71
|
| `audio.narration[].script` | string | 否 | — | 表示原稿(人間が読む正本。字幕連携等の元テキスト) |
|
|
68
72
|
| `audio.narration[].reading` | string | 否 | — | 読み原稿(かな化後・TTS 生成に実際に使ったテキスト) |
|
|
@@ -79,6 +83,14 @@
|
|
|
79
83
|
(ナレーションも `t` という 1 点情報を持つシーン単位の演出であり、BGM のような
|
|
80
84
|
「プロジェクト全体で 1 本」という制約は当てはまらない)。
|
|
81
85
|
|
|
86
|
+
### 1.1 ナレーション素材のトリム
|
|
87
|
+
|
|
88
|
+
`in` / `out` は `audio.sfx[]` と同じ素材秒の語彙を使う。再生区間は `[in, out)`、タイムライン上の
|
|
89
|
+
開始は従来どおり `t` とする。`in` 省略時は 0、`out` 省略時は素材末尾まで再生する。
|
|
90
|
+
`out <= in` は edit-lint が error とし、実尺との整合は render-cut が解決する。`in` が素材実尺以上なら
|
|
91
|
+
0 にクランプして warning、`out` が素材実尺を超えれば素材末尾へクランプして warning、クランプ後に
|
|
92
|
+
`out <= in` となる場合はその narration 要素だけを skip して warning とする。
|
|
93
|
+
|
|
82
94
|
## 2. パス解決規約
|
|
83
95
|
|
|
84
96
|
`audio.narration[].path` は `audio.bgm.path` / `audio.sfx[].path` と**同一規約**:
|
|
@@ -144,3 +144,24 @@ perspective のレイヤー分割フォールバック — perspective は `crop
|
|
|
144
144
|
- Chrome 不在、`.app` でない実行ファイル、`DevToolsActivePort` 待機または接続の失敗は、
|
|
145
145
|
「字幕レンダ用ブラウザの起動に失敗した」ことと Chrome の確認を日本語で示し、書き出しを
|
|
146
146
|
非 0 で停止する。別スタイルの簡易字幕へは切り替えない。
|
|
147
|
+
|
|
148
|
+
## 6. H.264 エンコーダ選択(2026-08-28 追記)
|
|
149
|
+
|
|
150
|
+
`--encoder` の語彙は `auto` / `videotoolbox` / `nvenc` / `qsv` / `amf` / `mf` / `x264` とする。
|
|
151
|
+
`master` 品質は x264 専用であり、ハードウェアエンコーダを明示した場合は拒否する。
|
|
152
|
+
|
|
153
|
+
| 値 | ffmpeg エンコーダ | 対応環境・品質制御 |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| `videotoolbox` | `h264_videotoolbox` | macOS。ビットレート制御 |
|
|
156
|
+
| `nvenc` | `h264_nvenc` | Windows / NVIDIA。VBR + CQ |
|
|
157
|
+
| `qsv` | `h264_qsv` | Windows / Intel。global quality |
|
|
158
|
+
| `amf` | `h264_amf` | Windows / AMD。CQP |
|
|
159
|
+
| `mf` | `h264_mf` | Windows Media Foundation。quality 0..100 |
|
|
160
|
+
| `x264` | `libx264` | 全環境。CRF |
|
|
161
|
+
|
|
162
|
+
`auto` は macOS で VideoToolbox → x264、Windows で NVENC → QSV → AMF → Media Foundation →
|
|
163
|
+
x264、その他の環境で x264 の順に解決する。ハードウェア対応は `ffmpeg -encoders` の一覧だけで
|
|
164
|
+
決めず、実際の1フレーム試し焼きにも成功した場合だけ採用する。Windows 向け4方式は最小解像度の
|
|
165
|
+
誤判定を避けるため 256x144 で試し焼きし、`AKARI_EXPORT_FORCE_X264=1` のときは試し焼きせず
|
|
166
|
+
すべて不採用とする。明示指定した Windows 向け方式が利用不能なら x264 へ暗黙移行せず停止する。
|
|
167
|
+
`AKARI_EXPORT_FORCE_X264=1` のときに Windows 向け方式を明示指定した場合も、同じく停止する。
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# analysis.json v0 人物マット(tracks.person_matte)データ契約
|
|
2
2
|
|
|
3
3
|
- 日付: 2026-07-23
|
|
4
|
+
- 改訂: 2026-08-27(§8 の format 席を開き mask_path / mask_format を追加)
|
|
4
5
|
- 状態: 実装ラウンドの SSOT(`tracks.person_matte` の値のみ確定)
|
|
5
6
|
- 前提: `contract-2026-07-17-data-contract-versioning.md`(版必須・追加のみ進化・明示マイグレの三原則)、
|
|
6
7
|
`contract-2026-07-13-m5-analysis-report.md`(`analysis.json` v0 の器と `tracks` の枠。
|
|
@@ -56,6 +57,8 @@
|
|
|
56
57
|
|
|
57
58
|
"person_matte": { // null(未生成)/ string(旧形)も有効
|
|
58
59
|
"path": "matte/person-matte.webm", // 必須。VP9 alpha WebM への相対 or 絶対パス
|
|
60
|
+
"mask_path": "matte/person-matte.mask.mp4", // 任意。追加のグレースケール H.264 マスク
|
|
61
|
+
"mask_format": "gray-h264-fullrange", // 任意。mask_path の形式
|
|
59
62
|
"fps": 24, // 必須。マット動画の fps(元素材と異なってよい)
|
|
60
63
|
"quality": "balanced", // 任意。fast / balanced / accurate / best を例示(enum 強制はしない)
|
|
61
64
|
"generated_at": "2026-07-23T01:33:30.069Z", // 任意。ISO8601
|
|
@@ -218,6 +221,7 @@ ffmpeg / ffprobe は OS の PATH 名を直接起動せず、`packages/media-bin`
|
|
|
218
221
|
|---|---|
|
|
219
222
|
| `packages/schemas/analysis.schema.json` | `$defs/personMatteTrack` として object 形の構造(`path` / `fps` の必須・型・範囲、`additionalProperties: false`)を定義し、`tracks.person_matte` から `null` / string / object の `oneOf` として参照する |
|
|
220
223
|
| 生成ヘルパー(`bin/person-matte/person-matte.mjs`) | 書き出した WebM が `codec_name = vp9` かつコンテナタグ `alpha_mode = 1` であることを ffprobe で確認してから成功を返す。アルファが落ちた出力を成功扱いにしない。タグキーの大文字小文字は書き込み経路によって変わりうるため、照合は大文字小文字非依存で行う |
|
|
224
|
+
| 追加マスク(`mask_path`) | `codec_name = h264`、`color_range = pc`、幅・高さ・`r_frame_rate`・`nb_frames` が VP9 alpha WebM と一致し、`start_pts = 0` であることを ffprobe で確認する |
|
|
221
225
|
| `skills/analyze-footage/analysis-json.md` の意味制約 | JSON Schema で表せない条件(`path` が解決でき実ファイルがある、マット動画の時刻 0 が素材の時刻 0 と一致する、`fps` がマット動画の実 fps と一致する)を確定前に人が検査する |
|
|
222
226
|
|
|
223
227
|
**`analysis.json` 専用の検証 CLI は本契約では新設しない。** `packages/schemas/bin/` には
|
|
@@ -242,6 +246,27 @@ JSON として読めるかまでしか見ていない(`analysis.schema` チェ
|
|
|
242
246
|
いずれも別契約で扱う。本契約は `tracks.person_matte` の器だけを確定し、これらの席が将来開く
|
|
243
247
|
可能性があることを記録するに留める。
|
|
244
248
|
|
|
249
|
+
### 8.1 `mask_path` / `mask_format`(2026-08-27 追加)
|
|
250
|
+
|
|
251
|
+
§8 の `format` の席を、VP9 alpha WebM を置換しない追加出力として開く。`path` が指す VP9 alpha
|
|
252
|
+
WebM の生成と既存消費は不変であり、`mask_path` は v2 frame-engine がハードウェアデコード可能な
|
|
253
|
+
グレースケールマスクを使うための追加物である。
|
|
254
|
+
|
|
255
|
+
| 項目 | 規格 |
|
|
256
|
+
|---|---|
|
|
257
|
+
| コンテナ / codec / profile | mp4 / H.264(`libx264`)/ High |
|
|
258
|
+
| 画素 | `yuv420p`、Y = アルファ(0 = 透明、255 = 不透明)、U/V = 128 |
|
|
259
|
+
| レンジ・色タグ | full range(`color_range=pc`)、BT.709 primaries / transfer / colorspace |
|
|
260
|
+
| GOP・品質 | GOP 1 秒以下、`crf 6`、`preset medium`、B frame なし |
|
|
261
|
+
| 時間 | 解像度・fps・尺・フレーム数はカラー元と一致し、先頭 PTS は 0 |
|
|
262
|
+
| 命名 | `<VP9 alpha WebM の basename>.mask.mp4` |
|
|
263
|
+
|
|
264
|
+
`mask_format` の現行値は `"gray-h264-fullrange"` とする。`mask_path` の相対パスは `path` と同じく
|
|
265
|
+
**analysis.json の所在ディレクトリ**を基準に解決し、区切りは `/` を使う。
|
|
266
|
+
|
|
267
|
+
`mask_path` が無い、または解決・検証できない場合、消費側は `path` の VP9 alpha WebM から一度だけ
|
|
268
|
+
同規格のマスクへ取り込み変換する。それも失敗した場合は人物演出を諦め、映像本体の処理は止めない。
|
|
269
|
+
|
|
245
270
|
## 9. 既知の追随事項(本契約が作る宿題)
|
|
246
271
|
|
|
247
272
|
- **`packages/analysis-report`** の軽量チェックは `tracks.person_matte` を「string か null」に
|
|
@@ -249,6 +274,7 @@ JSON として読めるかまでしか見ていない(`analysis.schema` チェ
|
|
|
249
274
|
本タスクのファイル境界外のため未修正。object 形を実運用へ載せる前に、この読み手を
|
|
250
275
|
`oneOf` へ追随させる必要がある(表示側の「人物マット: あり / なし」の判定も
|
|
251
276
|
`tracks.person_matte` の真偽値で動くため、object 形でもそのまま「あり」になる)
|
|
277
|
+
- `packages/analysis-report` は object 形と `quality` / マスク併産の表示へ追随済み(2026-08-28)
|
|
252
278
|
- **`<video>` の時刻同期が未実装**である(`packages/overlay-runtime`)。したがって
|
|
253
279
|
「動く人物の text-behind-person」は本契約が整っても**まだ本番品質ではない**。
|
|
254
280
|
マットが取れることと、それを本番の合成として出せることは別である
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
lifecycle: draft
|
|
3
|
+
created: 2026-07-25
|
|
4
|
+
updated: 2026-08-26
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# project-structure-v0(生成物の置き場所)契約
|
|
2
8
|
|
|
3
9
|
- 日付: 2026-07-25
|
|
@@ -60,6 +66,17 @@
|
|
|
60
66
|
3 ファイル以外の生成物は、性質に応じて §1 の層へ振り分ける(具体例は §4)
|
|
61
67
|
- 新しいトップレベル契約ファイルを追加する場合(将来の話)は、本契約の改訂を通す
|
|
62
68
|
|
|
69
|
+
#### 2026-08-26 追記: ルート直下の判断記録・分析レポート
|
|
70
|
+
|
|
71
|
+
- `decision-log.md` は analyze-project と edit-plan が共同追記する判断履歴の単一 SSOT であり、
|
|
72
|
+
プロジェクトルート直下の正当な契約ファイルとする(根拠:
|
|
73
|
+
`skills/edit-plan/report-guide.md` の `decision_log` 節)
|
|
74
|
+
- `analysis-report.html` は analyze-project が生成し edit-plan が読む正式な分析レポートであり、
|
|
75
|
+
プロジェクトルート直下の正当な契約ファイルとする(根拠:
|
|
76
|
+
`skills/edit-plan/workflow.md` §1)
|
|
77
|
+
- この追記は上記 2 ファイルを §2-1 の許可リストへ追加するものであり、その他のファイルに
|
|
78
|
+
ルート直下配置を許可しない
|
|
79
|
+
|
|
63
80
|
### 2-2. 削除安全の定義
|
|
64
81
|
|
|
65
82
|
`.akari/work/` と `.akari/cache/` は**再生成可能・削除安全**と定義する。
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
- `sfxItem` に optional `in` / `out` を追加(**素材秒**。`in` ≥ 0 省略時 0、
|
|
32
32
|
`out` > `in` 省略時 素材末尾)。再生区間 = 素材の [in, out)、
|
|
33
33
|
タイムライン上の開始は従来どおり `t`(timeline 秒)、表示尺 = out − in
|
|
34
|
+
- `narrationItem` にも同じ optional `in` / `out` を追加する。再生区間・素材秒・既定値・
|
|
35
|
+
`out > in` の検証分担は sfx と同一で、タイムライン上の開始は narration の `t` とする
|
|
34
36
|
- `bgm` に optional `in` を追加(BGM ファイル内の開始オフセット素材秒。ループ・全体尺
|
|
35
37
|
トリムの既存意味論は不変)
|
|
36
38
|
- edit-lint: `out <= in` を error。実尺越えの検知は lint では行わない
|
|
@@ -40,6 +42,12 @@
|
|
|
40
42
|
### 消費(render-cut + preview)
|
|
41
43
|
|
|
42
44
|
- render-cut: sfx の [in, out) 切り出しを出力に反映。bgm の `in` オフセット反映
|
|
45
|
+
- render-cut: narration の [in, out) 切り出しも出力に反映する。`in` が素材実尺以上なら 0 へ、
|
|
46
|
+
`out` が素材実尺を超えれば素材末尾へクランプして warning を出す。クランプ後に `out <= in` なら
|
|
47
|
+
その narration 要素だけを skip する。`in` / `out` の有無にかかわらず、各 narration 要素は
|
|
48
|
+
デコード可否の判定を兼ねて実尺を従来と同じ 1 回だけ probe し、デコードできなければ従来どおり
|
|
49
|
+
その要素だけを skip して warning を出す一方、両方省略された要素には `atrim` を前置きせず、
|
|
50
|
+
従来とバイト同一のフィルタ文字列を保つ
|
|
43
51
|
- preview(previewAudio): 同意味論で再生。実尺越え in/out は素材末尾へクランプ
|
|
44
52
|
|
|
45
53
|
### UI
|
|
@@ -581,3 +581,42 @@ ffmpeg の `perspective` フィルタの制約(式に時刻変数を持たな
|
|
|
581
581
|
段階計画・差分マップの正本: 内部リポ `planning/notes-2026-08-01-webui-shell-convergence.md`。
|
|
582
582
|
Phase 2 以降(共有カーネル抽出: edit-store / ペン / タイムライン写像 / overlay-runtime 一本化)は
|
|
583
583
|
本契約への適合を保ったまま実装を共通化する。
|
|
584
|
+
|
|
585
|
+
### 4.1 `packages/preview-engine` の処遇 — **凍結**(2026-08-27 裁定・実行済み)
|
|
586
|
+
|
|
587
|
+
WebCodecs ベースの `packages/preview-engine` は、Web UI からも shell からも import されない
|
|
588
|
+
死蔵コードだった(`packages/preview-server` が `public/preview-engine.bundle.js` へバンドル・
|
|
589
|
+
配信していたが、`index.html` / `app.js` に読み込み口が無い)。本契約の 3 面
|
|
590
|
+
(render-cut / Web UI / shell)のどれにも属さない 4 つ目の実装であり、パリティの対象外である
|
|
591
|
+
ことを明記したうえで**凍結**する。
|
|
592
|
+
|
|
593
|
+
- **実行済み**: preview-server の `build` から esbuild 行を削除・追跡されていた
|
|
594
|
+
`public/preview-engine.bundle.js` を削除・`test/server.spec.mjs` の配信スモークを削除。
|
|
595
|
+
以後 `npm run build` の成果物に preview-engine 由来のファイルは含まれない
|
|
596
|
+
- **削除はしない**: 合成エンジンを 1 個へ統一する「エンジン v2」路線(WebCodecs +
|
|
597
|
+
GPU コンポジタ)の土台候補として資産価値を実査済みであり、再利用 / 新規の裁定は
|
|
598
|
+
エンジン v2 のゲート(Phase 0 スパイクの実測と併せて裁定)に委ねる。
|
|
599
|
+
詳細と復活手順は `packages/preview-engine/README.md` 冒頭の凍結ノートを正とする
|
|
600
|
+
- **凍結中の規律**: 本パッケージへ追随改修を新たに入れない。スキーマや語彙が増えても
|
|
601
|
+
preview-engine 側の追随は不要(誰も実行しないため、追随は保守錯覚にしかならない)
|
|
602
|
+
|
|
603
|
+
### 4.2 ducking(BGM の narration ダッキング)の実装本数と正本
|
|
604
|
+
|
|
605
|
+
§2.5 が定める「narration 再生区間で BGM -12dB」は、**プレビュー側に 3 実装ある**:
|
|
606
|
+
|
|
607
|
+
| 面 | 実装 | 位置づけ |
|
|
608
|
+
|---|---|---|
|
|
609
|
+
| Web UI | `packages/preview-server/public/app.js`(`hasNarration` / `duckDb`) | 稼働中 |
|
|
610
|
+
| shell | `apps/shell/extensions/akari-preview/src/browser/akari-preview-open-handler.ts`(`duckGainDbAt`) | 稼働中 |
|
|
611
|
+
| preview-engine | `packages/preview-engine/src/duckingGain.ts`(純関数 + `node --test`) | **凍結(§4.1)** |
|
|
612
|
+
|
|
613
|
+
- **現時点で 3 者は数値的に同値**(2026-08-27 実測: 0〜10s を 10ms 刻み × `ducking` on/off の
|
|
614
|
+
2,002 点で不一致 0)。したがって本契約 §2.5 への適合は 3 面とも保たれている
|
|
615
|
+
- **-12dB 固定は正(render-cut)とは挙動の違う近似である**。render-cut は
|
|
616
|
+
`sidechaincompress=threshold=0.063:ratio=8:attack=5:release=300` で、減衰量は narration の
|
|
617
|
+
実レベルに依存し attack 5ms / release 300ms の時間応答を持つ。プレビュー 3 実装は
|
|
618
|
+
「区間内なら常に -12dB・立ち上がり/戻りは瞬時」であり、**§2.4 系と同じ宣言済みの近似**として扱う
|
|
619
|
+
- **正本の一本化は未了**(稼働中 2 実装の書き換えを伴うため別タスク)。一本化先は共有カーネル
|
|
620
|
+
`packages/edit-store`(`computeDuckIntervals` / `computeBgmDuckGainDb` 相当を昇格し、
|
|
621
|
+
shell は直接 import・Web UI は `edit-kernel.bundle.js` 経由で消費)。それまでは
|
|
622
|
+
**どれか 1 面の -12dB や区間規則を単独で変えないこと**(3 面同時に変えるか、一本化を先に済ませる)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# ページ全体 OSR 書き出し v0 契約
|
|
2
|
+
|
|
3
|
+
## 1. 適用範囲
|
|
4
|
+
|
|
5
|
+
この契約は `render-cut --engine osr` が生成する映像ページと、そのページを Electron オフスクリーン描画で駆動するプロトコルを定める。既定の engine は `legacy` であり、OSR は opt-in とする。
|
|
6
|
+
|
|
7
|
+
## 2. ページ契約
|
|
8
|
+
|
|
9
|
+
ページは出力幅 `W`、映像高 `H` に検証用1行を加えた `W × (H + 1)` で構成する。映像領域の重ね順は下から次の4層である。
|
|
10
|
+
|
|
11
|
+
1. frame-engine canvas。cuts、layers、transition、matte、LUTを評価する。
|
|
12
|
+
2. `captions.json` から生成したDOM字幕。
|
|
13
|
+
3. `edit.json` の自由HTML。
|
|
14
|
+
4. 自由HTML内のThree.js canvas。
|
|
15
|
+
|
|
16
|
+
字幕、自由HTML、3Dは render-cut と同じ overlay sheet 生成器により、透明な同一オリジン iframe として canvas 上へ置く。無効なトラックは最初からDOMへ入れず、活性区間ごとのDOM再構築は行わない。
|
|
17
|
+
|
|
18
|
+
ページは次のAPIを公開する。
|
|
19
|
+
|
|
20
|
+
- `window.__akariReady`: フォント、画像、動画、3D、frame-engineのprime完了を表すPromise。
|
|
21
|
+
- `window.__akariSeek(seconds, frameNumber)`: frame-engine評価、overlay sheetのシーク、スタンプ更新、2回の`requestAnimationFrame`待機を順に完了する。
|
|
22
|
+
- `window.__akariSettle()`: 検証不一致時に2回の`requestAnimationFrame`を進める。
|
|
23
|
+
|
|
24
|
+
CSS animationはpauseし、`currentTime`を合成時刻へ設定する。Three.jsは対象区間のローカル時刻で描画する。動画要素は提示フレームの確定まで待つ。`frameNumber`はmainから明示的に渡し、秒から再計算しない。
|
|
25
|
+
|
|
26
|
+
## 3. スタンプ行
|
|
27
|
+
|
|
28
|
+
最下1行はフレーム番号 `n mod 65536` を次で符号化する。
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
R = n & 255
|
|
32
|
+
G = (n >> 8) & 255
|
|
33
|
+
B = 0x55
|
|
34
|
+
A = 255
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
BGRA bitmapでは `[0x55, G, R, 255]` となる。左端、中央、右端の3画素を復号し、期待番号との全点一致を要求する。確認後、ffmpegへ渡す前に `buffer.subarray(0, W * H * 4)` で最下行を除く。
|
|
38
|
+
|
|
39
|
+
`--verify stamp|hash|off` を持ち、既定は `stamp` とする。`hash` は直前の映像領域と同じSHA-256ならsettle後に再取得し、静止画で上限へ達した場合は曖昧件数を記録して受理する。`off` は比較計測用である。通常書き出しではverifyを無効にしない。
|
|
40
|
+
|
|
41
|
+
## 4. 駆動プロトコル
|
|
42
|
+
|
|
43
|
+
各コマは次の順で処理する。
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
seek → ready → invalidate → paint → verify → write
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`paint`が既定10秒以内に届かなければ失敗として記録する。bitmapは必ず `W × (H + 1)` と照合する。不一致時はsettleして再度`invalidate`し、最大8回で停止する。
|
|
50
|
+
|
|
51
|
+
映像領域のBGRAは深さ3を既定とするbounded queueへ渡す。ffmpeg stdinの`write()`がfalseなら必ず`drain`を待つ。無制限のpre-bufferは禁止する。v0は先頭から末尾まで連番1 workerで評価する。
|
|
52
|
+
|
|
53
|
+
`run.json` はseek、paint、toBitmap、verify、writeのp50/p95、1000コマ区切りmedian、先頭と末尾のdriftRatio、paint timeout、verify retry、verify前delta histogram、backpressure、メモリ、ffprobe結果を記録する。
|
|
54
|
+
|
|
55
|
+
## 5. LUT
|
|
56
|
+
|
|
57
|
+
`output.look` のLUTはframe-engine canvas内のsampler3Dで適用する。ページ全体へCSS filterを掛けない。したがって字幕、自由HTML、3DはLUTの外にあり、映像canvasだけが色変換の対象となる。
|
|
58
|
+
|
|
59
|
+
## 6. Electronの器
|
|
60
|
+
|
|
61
|
+
起動は次の3段で解決する。
|
|
62
|
+
|
|
63
|
+
1. インストール済みAKARI VideoのElectron実行体を`--render`付きで再利用する。
|
|
64
|
+
2. npm optionalDependencyの`electron`を使う。`dist`のライセンス2ファイル、version、プラットフォーム実行体が揃うことを検査する。
|
|
65
|
+
3. Electronが無ければ警告を出し、現行render-cut経路へフォールバックする。
|
|
66
|
+
|
|
67
|
+
第1段・第2段とも、実プロセスのコマンドラインに`--force-device-scale-factor=1`、`--force-color-profile=srgb`、background throttling無効化スイッチを渡す。npm Electronではスクリプトパスを`argv[1]`に保ち、その後へChromiumスイッチを置く。ソフト描画時は加えてGPU無効化とSwiftShaderスイッチを渡す。
|
|
68
|
+
|
|
69
|
+
パッケージ版のTheiaではelectron-main contributionが`--render`を捕捉する。contribution開始は初期ウィンドウ表示とbackend起動の後なので、v0ではスプラッシュが一瞬表示され得る。通常起動で`--render`が無い場合、contributionは何もしない。
|
|
70
|
+
|
|
71
|
+
Linux v0は第3段を使用する。将来の差し替え席として、Chrome headlessと`HeadlessExperimental.beginFrame`を使うlauncherを第1段と第2段の間へ追加できるものとする。この契約では実装しない。
|
|
72
|
+
|
|
73
|
+
## 7. エンコード、音声、照合
|
|
74
|
+
|
|
75
|
+
ffmpeg入力は `-f rawvideo -pixel_format bgra -video_size WxH -framerate fps -i -` とする。品質とエンコーダはrender-cutの`master|high|standard|light`および`auto|videotoolbox|x264`を使用する。映像は1世代だけH.264へ圧縮し、その後の音声処理とmuxでは映像をcopyする。
|
|
76
|
+
|
|
77
|
+
ffprobe timeoutは `max(120000, frames × 100)` msとする。尺、フレーム数、解像度をplanと照合する。
|
|
78
|
+
|
|
79
|
+
## 8. メモリと長尺
|
|
80
|
+
|
|
81
|
+
- GPU描画の警戒線: 768 MiB / export、hard stop: 1,024 MiB / export。
|
|
82
|
+
- ソフト描画(SwiftShader)は1080pで1.1 GiB台に達するため、警戒線1,536 MiB / hard stop 2,048 MiBの別枠を使う。
|
|
83
|
+
- `AKARI_OSR_MEMORY_WARN_MIB` / `AKARI_OSR_MEMORY_HARD_STOP_MIB`で正の整数MiBへ上書きでき、適用値はwarning < hard stopを必須とする。
|
|
84
|
+
- 並列予算1 worker = 1 GiBはGPU前提の値である。v0のworker数は1。
|
|
85
|
+
- 10秒ごとにRSSを記録し、ウィンドウ破棄後も採る。
|
|
86
|
+
- 固定Nコマごとのページ再生成は行わない。再生成を許すのはページ境界、renderer crash、watchdog回復時だけである。
|
|
87
|
+
|
|
88
|
+
非連番seekは描画履歴が変わり得るため、チャンク分割・並列化はbyte再現モードと両立しない。将来導入する場合は先頭からのwarm-up履歴または完成画の別検収を必要とする。
|
|
89
|
+
|
|
90
|
+
## 9. 検収
|
|
91
|
+
|
|
92
|
+
CIはソフト描画の連番2走について全コマSHA-256一致を要求する。製品はGPUを既定とし、同一マシン2走の一致率、`differingPixels`、`maxDelta`を診断値として記録する。GPUのbyte-exactは合否条件にしない。差分調査はH.264を再デコードした画像ではなく、捕捉時のraw BGRAを使用する。
|
|
93
|
+
|
|
94
|
+
legacyとの比較は字幕、自由HTML、3Dの各指定時刻についてMADと`differingPixels`を記録する。
|
|
95
|
+
|
|
96
|
+
## 10. 使用しない中間規律
|
|
97
|
+
|
|
98
|
+
OSR経路では次を使用しない。
|
|
99
|
+
|
|
100
|
+
- アルファ付き中間動画。
|
|
101
|
+
- PNG連番。
|
|
102
|
+
- ffmpeg overlay。
|
|
103
|
+
- 二重の映像エンコード。
|
|
104
|
+
- 3Dの別キャプチャ。
|
|
105
|
+
- 字幕の活性区間ごとのDOM再構築。
|
|
106
|
+
- 静止コマの重複除去。
|
|
107
|
+
- 固定Nコマごとのページ再生成。
|
|
108
|
+
|
|
109
|
+
## 11. 既知の限界
|
|
110
|
+
|
|
111
|
+
### 11.1 Bフレーム素材の並べ替え遅延
|
|
112
|
+
|
|
113
|
+
負のDTSで始まるBフレーム素材では、エンジンのデコード経路が並べ替え遅延ぶん手前のコマを返す。`has_b_frames=2`の1920×1080 / 30fps素材では、先頭コマだけが一致し、その後は一定2コマ手前になることを実測した。同一素材を`-bf 0`で生成するとlegacyと完全に一致する。v0はこのずれを補正しない。
|
|
114
|
+
|
|
115
|
+
### 11.2 legacyとの全画面画素差
|
|
116
|
+
|
|
117
|
+
同じraw BGRAを比較した場合、ffmpegが未タグ素材へ既定で使うbt601換算に対してMAD 9.28 / maxDelta 155、bt709換算に対してMAD 0.886であった。残差はクロマ補間による。エンジンは`bt709-limited`で合成する。
|
|
118
|
+
|
|
119
|
+
ベースを単色にしたfixtureでlegacyとOSRの最終MP4を比較すると、MAD 0.019〜0.345 / maxDelta 7〜78であった。字幕・自由HTML・3Dの描画は一致し、全画面差の主因はベース映像のYUV→RGB変換である。オーバーレイ層の突き合わせは単色ベースで行う。
|