akari-video 0.1.21 → 0.1.23
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/migrate-command.mjs +11 -2
- package/vendor/.akari-capability-sources.json +3 -0
- package/vendor/docs/contract-2026-07-25-project-structure-v0.md +17 -0
- package/vendor/docs/contract-2026-08-02-preview-parity.md +39 -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 +544 -0
- package/vendor/packages/analysis-report/template.html +2023 -0
- package/vendor/packages/edit-lint/src/edit-lint.mjs +20 -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/index.d.ts +1 -0
- package/vendor/packages/edit-store/lib/index.js +1 -0
- package/vendor/packages/edit-store/lib/internal-model.js +20 -8
- package/vendor/packages/edit-store/lib/migrate/index.d.ts +2 -0
- package/vendor/packages/edit-store/lib/migrate/index.js +32 -15
- 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 +2 -0
- package/vendor/packages/edit-store/lib/webview-kernel.js +139 -0
- package/vendor/packages/frame-engine/README.ja.md +24 -0
- package/vendor/packages/frame-engine/README.md +24 -0
- package/vendor/packages/frame-engine/package.json +27 -0
- package/vendor/packages/preview-engine/README.md +38 -0
- package/vendor/packages/preview-server/package.json +1 -1
|
@@ -20,10 +20,16 @@ var AkariEditKernel = (() => {
|
|
|
20
20
|
// src/webview-kernel.ts
|
|
21
21
|
var webview_kernel_exports = {};
|
|
22
22
|
__export(webview_kernel_exports, {
|
|
23
|
+
TRANSITION_BY_ID: () => TRANSITION_BY_ID,
|
|
24
|
+
TRANSITION_CATEGORIES: () => TRANSITION_CATEGORIES,
|
|
25
|
+
TRANSITION_TYPE_IDS: () => TRANSITION_TYPE_IDS,
|
|
26
|
+
TRANSITION_VOCABULARY: () => TRANSITION_VOCABULARY,
|
|
23
27
|
buildTimelineMap: () => buildTimelineMap,
|
|
24
28
|
captionWindowSeconds: () => captionWindowSeconds,
|
|
29
|
+
computeTransitionVisual: () => computeTransitionVisual,
|
|
25
30
|
findActiveCaption: () => findActiveCaption,
|
|
26
31
|
findActiveResolvedCaption: () => findActiveResolvedCaption,
|
|
32
|
+
isTransitionType: () => isTransitionType,
|
|
27
33
|
outputToSource: () => outputToSource,
|
|
28
34
|
transitionProgressAt: () => transitionProgressAt
|
|
29
35
|
});
|
|
@@ -63,6 +69,9 @@ var AkariEditKernel = (() => {
|
|
|
63
69
|
var TRANSITION_TYPE_IDS = TRANSITION_VOCABULARY.map((entry) => entry.id);
|
|
64
70
|
var TRANSITION_CATEGORIES = [...new Set(TRANSITION_VOCABULARY.map((entry) => entry.category))];
|
|
65
71
|
var TRANSITION_BY_ID = Object.fromEntries(TRANSITION_VOCABULARY.map((entry) => [entry.id, entry]));
|
|
72
|
+
function isTransitionType(value) {
|
|
73
|
+
return typeof value === "string" && Object.prototype.hasOwnProperty.call(TRANSITION_BY_ID, value);
|
|
74
|
+
}
|
|
66
75
|
|
|
67
76
|
// src/edit-store.ts
|
|
68
77
|
function computeCutTrackSegments(cuts) {
|
|
@@ -296,6 +305,136 @@ var AkariEditKernel = (() => {
|
|
|
296
305
|
});
|
|
297
306
|
}
|
|
298
307
|
|
|
308
|
+
// src/transition-visual.ts
|
|
309
|
+
function computeTransitionVisual(previewKind, rawProgress, fallbackName = "") {
|
|
310
|
+
const clamp01 = (value) => Math.max(0, Math.min(1, value));
|
|
311
|
+
const progress = clamp01(Number.isFinite(rawProgress) ? rawProgress : 0);
|
|
312
|
+
const mid = 1 - Math.abs(2 * progress - 1);
|
|
313
|
+
const percent = (value) => `${value * 100}%`;
|
|
314
|
+
const translateX = (value) => `translateX(${percent(value)})`;
|
|
315
|
+
const translateY = (value) => `translateY(${percent(value)})`;
|
|
316
|
+
const base = {
|
|
317
|
+
progress,
|
|
318
|
+
engine: "none",
|
|
319
|
+
blurStdDeviationRatio: 0,
|
|
320
|
+
pixelBlockRatio: 0,
|
|
321
|
+
dissolveVisibleRatio: 0,
|
|
322
|
+
outgoingOpacity: 1,
|
|
323
|
+
incomingOpacity: 1,
|
|
324
|
+
incomingClipPath: "none",
|
|
325
|
+
outgoingTransform: "",
|
|
326
|
+
incomingTransform: "",
|
|
327
|
+
outgoingMask: "none",
|
|
328
|
+
incomingMask: "none",
|
|
329
|
+
outgoingFilter: "none",
|
|
330
|
+
incomingFilter: "none",
|
|
331
|
+
plateOpacity: 0,
|
|
332
|
+
plateColor: "transparent",
|
|
333
|
+
zSwap: false,
|
|
334
|
+
fallbackLabel: ""
|
|
335
|
+
};
|
|
336
|
+
const cross = () => ({
|
|
337
|
+
...base,
|
|
338
|
+
outgoingOpacity: 1 - progress,
|
|
339
|
+
incomingOpacity: progress
|
|
340
|
+
});
|
|
341
|
+
if (previewKind === "blur") {
|
|
342
|
+
return { ...cross(), engine: "directional-blur", blurStdDeviationRatio: mid * 0.075 };
|
|
343
|
+
}
|
|
344
|
+
if (previewKind === "pixelize") {
|
|
345
|
+
return { ...cross(), engine: "pixelize", pixelBlockRatio: mid / 22 };
|
|
346
|
+
}
|
|
347
|
+
if (previewKind === "dissolve") {
|
|
348
|
+
return { ...base, engine: "noise-dissolve", dissolveVisibleRatio: progress };
|
|
349
|
+
}
|
|
350
|
+
if (previewKind === "fade") return cross();
|
|
351
|
+
if (previewKind === "fade-black" || previewKind === "fade-white") {
|
|
352
|
+
return {
|
|
353
|
+
...cross(),
|
|
354
|
+
plateOpacity: clamp01(Math.min(progress / 0.18, (1 - progress) / 0.7)),
|
|
355
|
+
plateColor: previewKind === "fade-white" ? "#fff" : "#000"
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
if (previewKind === "fade-grays") {
|
|
359
|
+
const filter = `grayscale(${mid})`;
|
|
360
|
+
return { ...cross(), outgoingFilter: filter, incomingFilter: filter };
|
|
361
|
+
}
|
|
362
|
+
const hidden = 1 - progress;
|
|
363
|
+
if (previewKind === "wipe-left") return { ...base, incomingClipPath: `inset(0 0 0 ${percent(hidden)})` };
|
|
364
|
+
if (previewKind === "wipe-right") return { ...base, incomingClipPath: `inset(0 ${percent(hidden)} 0 0)` };
|
|
365
|
+
if (previewKind === "wipe-up") return { ...base, incomingClipPath: `inset(${percent(hidden)} 0 0 0)` };
|
|
366
|
+
if (previewKind === "wipe-down") return { ...base, incomingClipPath: `inset(0 0 ${percent(hidden)} 0)` };
|
|
367
|
+
if (previewKind === "slide-left") {
|
|
368
|
+
return { ...base, outgoingTransform: translateX(-progress), incomingTransform: translateX(hidden) };
|
|
369
|
+
}
|
|
370
|
+
if (previewKind === "slide-right") {
|
|
371
|
+
return { ...base, outgoingTransform: translateX(progress), incomingTransform: translateX(-hidden) };
|
|
372
|
+
}
|
|
373
|
+
if (previewKind === "slide-up") {
|
|
374
|
+
return { ...base, outgoingTransform: translateY(-progress), incomingTransform: translateY(hidden) };
|
|
375
|
+
}
|
|
376
|
+
if (previewKind === "slide-down") {
|
|
377
|
+
return { ...base, outgoingTransform: translateY(progress), incomingTransform: translateY(-hidden) };
|
|
378
|
+
}
|
|
379
|
+
if (previewKind === "cover-left") return { ...base, incomingTransform: translateX(hidden) };
|
|
380
|
+
if (previewKind === "cover-right") return { ...base, incomingTransform: translateX(-hidden) };
|
|
381
|
+
if (previewKind === "cover-up") return { ...base, incomingTransform: translateY(hidden) };
|
|
382
|
+
if (previewKind === "cover-down") return { ...base, incomingTransform: translateY(-hidden) };
|
|
383
|
+
if (previewKind === "reveal-left") {
|
|
384
|
+
return { ...base, outgoingTransform: translateX(-progress), zSwap: true };
|
|
385
|
+
}
|
|
386
|
+
if (previewKind === "reveal-right") {
|
|
387
|
+
return { ...base, outgoingTransform: translateX(progress), zSwap: true };
|
|
388
|
+
}
|
|
389
|
+
if (previewKind === "reveal-up") {
|
|
390
|
+
return { ...base, outgoingTransform: translateY(-progress), zSwap: true };
|
|
391
|
+
}
|
|
392
|
+
if (previewKind === "reveal-down") {
|
|
393
|
+
return { ...base, outgoingTransform: translateY(progress), zSwap: true };
|
|
394
|
+
}
|
|
395
|
+
if (previewKind === "circle-open") {
|
|
396
|
+
const c = progress * 170 - 35;
|
|
397
|
+
return {
|
|
398
|
+
...base,
|
|
399
|
+
incomingMask: `radial-gradient(circle farthest-corner, #000 ${c - 35}%, transparent ${c + 35}%)`
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (previewKind === "circle-close") {
|
|
403
|
+
const c = (1 - progress) * 170 - 35;
|
|
404
|
+
return {
|
|
405
|
+
...base,
|
|
406
|
+
outgoingMask: `radial-gradient(circle farthest-corner, #000 ${c - 35}%, transparent ${c + 35}%)`,
|
|
407
|
+
zSwap: true
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
if (previewKind === "radial") {
|
|
411
|
+
const c = progress * 424 - 32;
|
|
412
|
+
return {
|
|
413
|
+
...base,
|
|
414
|
+
incomingMask: `conic-gradient(from 0deg, #000 ${c - 16}deg, transparent ${c + 16}deg)`
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
if (previewKind === "zoom-in") {
|
|
418
|
+
return {
|
|
419
|
+
...base,
|
|
420
|
+
outgoingOpacity: progress < 0.6 ? 1 : 1 - (progress - 0.6) / 0.4,
|
|
421
|
+
outgoingTransform: `scale(${1 + 1.5 * progress})`,
|
|
422
|
+
outgoingFilter: `blur(${6 * progress}px)`,
|
|
423
|
+
zSwap: true
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (previewKind === "squeeze-h") {
|
|
427
|
+
return { ...base, outgoingTransform: `scaleY(${1 - progress})`, zSwap: true };
|
|
428
|
+
}
|
|
429
|
+
if (previewKind === "squeeze-v") {
|
|
430
|
+
return { ...base, outgoingTransform: `scaleX(${1 - progress})`, zSwap: true };
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
...cross(),
|
|
434
|
+
fallbackLabel: `${fallbackName || previewKind} \u2014 \u30D7\u30EC\u30D3\u30E5\u30FC\u8FD1\u4F3C\u306A\u3057`
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
299
438
|
// src/webview-kernel.ts
|
|
300
439
|
function findActiveResolvedCaption(cues, outputTime) {
|
|
301
440
|
return cues.find((cue) => cue.start <= outputTime && outputTime < cue.end);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[English](./README.md) | **日本語**
|
|
2
|
+
|
|
3
|
+
# @akari-video/frame-engine
|
|
4
|
+
|
|
5
|
+
`frame-engine` は edit-store が解決済みのタイムライン時刻を、1枚の完成 WebGL2 surface へ評価します。同じ `CompositedFrame` を canvas preview 出口と PBO raw-frame 出口が消費し、`evaluateFrame` 自体は preview/export モードを持ちません。
|
|
6
|
+
|
|
7
|
+
Phase 1a の対応範囲はハードカットだけです。速度、framing、transform、freeze、transition はこの版の対象外です。
|
|
8
|
+
|
|
9
|
+
## ローカル検証
|
|
10
|
+
|
|
11
|
+
ゴールデンハーネスは Chromium の WebCodecs と WebGL2 実装を必要とします。まずリポジトリにある Electron を起動し、ホストが GUI app を登録できない場合だけ同じ renderer bundle を Playwright Chromium で実行します。H.264 フィクスチャは ffmpeg で生成し、動画と証跡は gitignore 済みの `test/golden/.generated/` にだけ置きます。
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
cd packages/frame-engine
|
|
15
|
+
npm run typecheck
|
|
16
|
+
npm run test:unit
|
|
17
|
+
npm test
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`npm test` は build、unit test、Electron 実走、preview/export の RGBA・PNG hash 一致、故意の1px改変を比較器が棄却すること、MP4 encode 後の抽出フレームが静止していないことを一括検証します。段階別集計は `test/golden/.generated/metrics.json` に生成されます。
|
|
21
|
+
|
|
22
|
+
CI runner が GPU/WebCodecs 対応 Chromium を持たない場合、Electron/WebCodecs 試験はローカル専用です。unit test と typecheck はその種の CI でも実行できます。
|
|
23
|
+
|
|
24
|
+
固定した demux/decode 依存の評価は [av-cliper 保守現況](./docs/av-cliper-status.md) を参照してください。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
**English** | [日本語](./README.ja.md)
|
|
2
|
+
|
|
3
|
+
# @akari-video/frame-engine
|
|
4
|
+
|
|
5
|
+
`frame-engine` evaluates one edit-store-resolved timeline instant into one completed WebGL2 surface. The same `CompositedFrame` is consumed by the canvas preview exit and the PBO raw-frame exit; `evaluateFrame` has no preview/export mode.
|
|
6
|
+
|
|
7
|
+
Phase 1a supports hard cuts only. Speed, framing, transforms, freeze frames, and transitions remain outside this package version.
|
|
8
|
+
|
|
9
|
+
## Local verification
|
|
10
|
+
|
|
11
|
+
The golden harness needs Chromium's WebCodecs and WebGL2 implementations. It first launches the repository's Electron binary; if the host cannot register a GUI app, it runs the same renderer bundle in Playwright Chromium. It generates its H.264 fixture with ffmpeg and keeps all media and evidence under the ignored `test/golden/.generated/` directory.
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
cd packages/frame-engine
|
|
15
|
+
npm run typecheck
|
|
16
|
+
npm run test:unit
|
|
17
|
+
npm test
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`npm test` builds the package, runs unit tests, launches Electron, compares preview/export RGBA and PNG hashes, proves the comparator rejects an injected one-pixel mutation, encodes an MP4, and verifies extracted frames are not static. Stage summaries are written to `test/golden/.generated/metrics.json`.
|
|
21
|
+
|
|
22
|
+
The Electron/WebCodecs test is intentionally local-only when a CI runner has no GPU/WebCodecs-capable Chromium. Unit tests and typechecking remain suitable for such CI runners.
|
|
23
|
+
|
|
24
|
+
See [av-cliper maintenance status](./docs/av-cliper-status.md) for the pinned demux/decode dependency assessment.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@akari-video/frame-engine",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Deterministic timeline frame evaluator with a WebGL2 compositor and shared preview/export surfaces.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc -p tsconfig.json",
|
|
11
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.golden.json",
|
|
12
|
+
"bundle:golden": "mkdir -p test/golden/.generated && esbuild test/golden/renderer.ts --bundle --format=iife --platform=browser --target=chrome122 --outfile=test/golden/.generated/renderer.js",
|
|
13
|
+
"test:unit": "node --test test/*.test.mjs",
|
|
14
|
+
"test:golden": "node test/golden/run.mjs",
|
|
15
|
+
"test": "npm run build && npm run test:unit && npm run test:golden"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@akari-video/edit-store": "0.1.0",
|
|
19
|
+
"@webav/av-cliper": "1.2.8",
|
|
20
|
+
"@webav/mp4box.js": "^0.5.7"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@playwright/test": "^1.62.0",
|
|
24
|
+
"esbuild": "^0.24.0",
|
|
25
|
+
"typescript": "^5.6.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# @akari-video/preview-engine
|
|
2
2
|
|
|
3
|
+
> ## 🧊 状態: **凍結中(2026-08-27 裁定)** — 接続先未定・ビルド/配信ラインから除外済み
|
|
4
|
+
>
|
|
5
|
+
> このパッケージは **どのアプリからも import されていない**。2026-07-15〜16 の実装ラウンド以降、
|
|
6
|
+
> `packages/preview-server` が `public/preview-engine.bundle.js` へバンドル・配信していたが、
|
|
7
|
+
> `public/index.html` にも `public/app.js` にも読み込み口が無く、実行されたことは一度も無い
|
|
8
|
+
> (死蔵の確定根拠: 内部監査 2026-08-17)。維持費(ビルド時間・381KB のバンドル配信・
|
|
9
|
+
> 追随改修による保守錯覚)だけが発生していたため、**ビルド・配信ラインから外した**:
|
|
10
|
+
>
|
|
11
|
+
> - `packages/preview-server/package.json` の `build` から preview-engine の esbuild を削除
|
|
12
|
+
> - 追跡されていた `packages/preview-server/public/preview-engine.bundle.js` を削除
|
|
13
|
+
> - `packages/preview-server/test/server.spec.mjs` の「配信されるか」スモークを削除
|
|
14
|
+
>
|
|
15
|
+
> **コードは消していない**。エンジン v2 構想(合成エンジンを 1 個に統一する
|
|
16
|
+
> WebCodecs + GPU コンポジタ路線・2026-08-26 オーナー承認)で **土台候補**として実査済みであり、
|
|
17
|
+
> 「再利用するか新規に起こすか」はエンジン v2 のゲート G1(Phase 0 スパイクの実測と併せて裁定)
|
|
18
|
+
> に委ねられている。それまでは触らない — **本パッケージへの追随改修を新たに入れないこと**
|
|
19
|
+
> (入れても誰も実行しない。必要な変更は G1 の裁定後に、再利用と決まってから行う)。
|
|
20
|
+
>
|
|
21
|
+
> ### 凍結の解除(復活)手順
|
|
22
|
+
>
|
|
23
|
+
> 1. `packages/preview-server/package.json` の `build` 先頭へ次を戻す:
|
|
24
|
+
> `esbuild ../preview-engine/src/index.ts --bundle --format=esm --outfile=public/preview-engine.bundle.js --target=chrome122 --platform=browser && `
|
|
25
|
+
> 2. `public/index.html` か `public/app.js` に**実際の読み込み口**を作る(これが無かったのが死蔵の原因)
|
|
26
|
+
> 3. 必要なら `server.spec.mjs` の配信スモークを復活させる(削除前の内容は git 履歴を参照)
|
|
27
|
+
>
|
|
28
|
+
> ### 凍結中に確定した申し送り
|
|
29
|
+
>
|
|
30
|
+
> - **ducking の正本は本パッケージではない**。`src/duckingGain.ts` は「BGM を下げる区間」の
|
|
31
|
+
> 計算を純関数 + テスト付きで持つ唯一の実装だが、同じ規則が Web UI(`packages/preview-server/public/app.js`)
|
|
32
|
+
> と shell(`apps/shell/extensions/akari-preview/src/browser/akari-preview-open-handler.ts`)に
|
|
33
|
+
> インラインで各 1 本ある(= プレビュー側は計 3 実装)。2026-08-27 時点で 3 者は数値的に同値
|
|
34
|
+
> (0〜10s を 10ms 刻み × ducking on/off の 2,002 点で不一致 0 を実測)。
|
|
35
|
+
> 共有カーネル(`packages/edit-store`)への一本化は消費側 2 ファイルの書き換えを伴うため
|
|
36
|
+
> 別タスク扱い。**復活時は `src/duckingGain.ts` を残さず、カーネル側を import すること**
|
|
37
|
+
> - `src/timeline.ts` の `Timeline` は MVP 用の素朴な解決器で、`packages/edit-store` の
|
|
38
|
+
> timeline-map 共有カーネル(トラック勝者・時間写像の正本)とは別物。復活時は timeline-map を正とする
|
|
39
|
+
|
|
40
|
+
|
|
3
41
|
シェル非依存の TS プレビューエンジン(レベル3: Chromium 内完結、WebCodecs + `@webav/av-cliper` 基盤)。
|
|
4
42
|
正本契約(E1〜E5)は非公開の内部 planning で管理する(本リポには置かない方針)。
|
|
5
43
|
パラメータの根拠・設計判断の要約は本 README 「E1〜E4 の実装メモ」節を参照。
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "esbuild ../
|
|
7
|
+
"build": "esbuild ../pen-visuals/src/index.ts --bundle --format=esm --outfile=public/pen-visuals.bundle.js --target=chrome122 --platform=browser && esbuild ../overlay-runtime/src/interaction.js --bundle --format=iife --outfile=public/overlay-interaction.bundle.js --target=chrome122 --platform=browser && esbuild ../overlay-runtime/src/interaction.css --bundle --outfile=public/overlay-interaction.css --target=chrome122 && esbuild ../edit-store/src/webview-kernel.ts --bundle --format=esm --outfile=public/edit-kernel.bundle.js --target=chrome122 --platform=browser",
|
|
8
8
|
"pretest": "npm --prefix ../edit-store run build && npm --prefix ../../apps/shell/extensions/akari-preview run build && npm run build",
|
|
9
9
|
"test": "node --test test/*.test.mjs",
|
|
10
10
|
"start": "node src/server.mjs",
|