akari-video 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/vendor/.akari-capability-sources.json +1 -0
  3. package/vendor/docs/contract-2026-07-23-analysis-person-matte.md +102 -2
  4. package/vendor/packages/akari-launcher/package.json +1 -1
  5. package/vendor/packages/edit-lint/src/edit-lint.mjs +90 -18
  6. package/vendor/packages/edit-store/lib/cut-adjacency.d.ts +25 -0
  7. package/vendor/packages/edit-store/lib/cut-adjacency.js +38 -8
  8. package/vendor/packages/edit-store/lib/edit-store.d.ts +13 -2
  9. package/vendor/packages/edit-store/lib/edit-store.js +69 -3
  10. package/vendor/packages/edit-store/lib/index.d.ts +1 -0
  11. package/vendor/packages/edit-store/lib/index.js +1 -0
  12. package/vendor/packages/edit-store/lib/internal-model.d.ts +13 -0
  13. package/vendor/packages/edit-store/lib/internal-model.js +98 -18
  14. package/vendor/packages/edit-store/lib/migrate/legacy-parse.js +9 -6
  15. package/vendor/packages/edit-store/lib/timeline-map.d.ts +5 -0
  16. package/vendor/packages/edit-store/lib/timeline-map.js +40 -0
  17. package/vendor/packages/edit-store/lib/transition-vocabulary.d.ts +223 -0
  18. package/vendor/packages/edit-store/lib/transition-vocabulary.js +45 -0
  19. package/vendor/packages/edit-store/lib/webview-kernel.js +98 -0
  20. package/vendor/packages/matte-rvm/package.json +22 -0
  21. package/vendor/packages/schemas/bin/validate-edit.mjs +5 -2
  22. package/vendor/packages/schemas/edit.schema.json +2 -2
  23. package/vendor/packages/schemas/test/transition-vocabulary.test.mjs +64 -0
  24. package/vendor/packages/schemas/test/validate-edit.test.mjs +2 -2
  25. package/vendor/skills/analyze-footage/bin/person-matte/person-cutout.mjs +453 -0
  26. package/vendor/skills/analyze-footage/bin/person-matte/person-matte.mjs +163 -48
  27. package/vendor/skills/analyze-footage/person-matte.md +135 -5
@@ -28,6 +28,42 @@ var AkariEditKernel = (() => {
28
28
  transitionProgressAt: () => transitionProgressAt
29
29
  });
30
30
 
31
+ // src/transition-vocabulary.ts
32
+ var TRANSITION_VOCABULARY = [
33
+ { id: "dissolve", xfadeName: "dissolve", labelJa: "\u30C7\u30A3\u30BE\u30EB\u30D6", category: "\u30D5\u30A7\u30FC\u30C9", previewKind: "dissolve", glyph: "D" },
34
+ { id: "fade", xfadeName: "fade", labelJa: "\u30AF\u30ED\u30B9\u30D5\u30A7\u30FC\u30C9", category: "\u30D5\u30A7\u30FC\u30C9", previewKind: "fade", glyph: "F" },
35
+ { id: "fade-black", xfadeName: "fadeblack", labelJa: "\u9ED2\u30D5\u30A7\u30FC\u30C9", category: "\u30D5\u30A7\u30FC\u30C9", previewKind: "fade-black", glyph: "B" },
36
+ { id: "fade-white", xfadeName: "fadewhite", labelJa: "\u767D\u30D5\u30A7\u30FC\u30C9", category: "\u30D5\u30A7\u30FC\u30C9", previewKind: "fade-white", glyph: "W" },
37
+ { id: "fade-grays", xfadeName: "fadegrays", labelJa: "\u30E2\u30CE\u30AF\u30ED\u30D5\u30A7\u30FC\u30C9", category: "\u30D5\u30A7\u30FC\u30C9", previewKind: "fade-grays", glyph: "G" },
38
+ { id: "wipe-left", xfadeName: "wipeleft", labelJa: "\u30EF\u30A4\u30D7\uFF08\u5DE6\u3078\uFF09", category: "\u30EF\u30A4\u30D7", previewKind: "wipe-left", glyph: "\u2190" },
39
+ { id: "wipe-right", xfadeName: "wiperight", labelJa: "\u30EF\u30A4\u30D7\uFF08\u53F3\u3078\uFF09", category: "\u30EF\u30A4\u30D7", previewKind: "wipe-right", glyph: "\u2192" },
40
+ { id: "wipe-up", xfadeName: "wipeup", labelJa: "\u30EF\u30A4\u30D7\uFF08\u4E0A\u3078\uFF09", category: "\u30EF\u30A4\u30D7", previewKind: "wipe-up", glyph: "\u2191" },
41
+ { id: "wipe-down", xfadeName: "wipedown", labelJa: "\u30EF\u30A4\u30D7\uFF08\u4E0B\u3078\uFF09", category: "\u30EF\u30A4\u30D7", previewKind: "wipe-down", glyph: "\u2193" },
42
+ { id: "radial", xfadeName: "radial", labelJa: "\u6642\u8A08\u30EF\u30A4\u30D7", category: "\u30EF\u30A4\u30D7", previewKind: "radial", glyph: "\u25F7" },
43
+ { id: "slide-left", xfadeName: "slideleft", labelJa: "\u30B9\u30E9\u30A4\u30C9\uFF08\u5DE6\u3078\uFF09", category: "\u30B9\u30E9\u30A4\u30C9", previewKind: "slide-left", glyph: "\u2190" },
44
+ { id: "slide-right", xfadeName: "slideright", labelJa: "\u30B9\u30E9\u30A4\u30C9\uFF08\u53F3\u3078\uFF09", category: "\u30B9\u30E9\u30A4\u30C9", previewKind: "slide-right", glyph: "\u2192" },
45
+ { id: "slide-up", xfadeName: "slideup", labelJa: "\u30B9\u30E9\u30A4\u30C9\uFF08\u4E0A\u3078\uFF09", category: "\u30B9\u30E9\u30A4\u30C9", previewKind: "slide-up", glyph: "\u2191" },
46
+ { id: "slide-down", xfadeName: "slidedown", labelJa: "\u30B9\u30E9\u30A4\u30C9\uFF08\u4E0B\u3078\uFF09", category: "\u30B9\u30E9\u30A4\u30C9", previewKind: "slide-down", glyph: "\u2193" },
47
+ { id: "cover-left", xfadeName: "coverleft", labelJa: "\u30AB\u30D0\u30FC\uFF08\u5DE6\u3078\uFF09", category: "\u30AB\u30D0\u30FC", previewKind: "cover-left", glyph: "\u2190" },
48
+ { id: "cover-right", xfadeName: "coverright", labelJa: "\u30AB\u30D0\u30FC\uFF08\u53F3\u3078\uFF09", category: "\u30AB\u30D0\u30FC", previewKind: "cover-right", glyph: "\u2192" },
49
+ { id: "cover-up", xfadeName: "coverup", labelJa: "\u30AB\u30D0\u30FC\uFF08\u4E0A\u3078\uFF09", category: "\u30AB\u30D0\u30FC", previewKind: "cover-up", glyph: "\u2191" },
50
+ { id: "cover-down", xfadeName: "coverdown", labelJa: "\u30AB\u30D0\u30FC\uFF08\u4E0B\u3078\uFF09", category: "\u30AB\u30D0\u30FC", previewKind: "cover-down", glyph: "\u2193" },
51
+ { id: "reveal-left", xfadeName: "revealleft", labelJa: "\u30EA\u30D3\u30FC\u30EB\uFF08\u5DE6\u3078\uFF09", category: "\u30EA\u30D3\u30FC\u30EB", previewKind: "reveal-left", glyph: "\u2190" },
52
+ { id: "reveal-right", xfadeName: "revealright", labelJa: "\u30EA\u30D3\u30FC\u30EB\uFF08\u53F3\u3078\uFF09", category: "\u30EA\u30D3\u30FC\u30EB", previewKind: "reveal-right", glyph: "\u2192" },
53
+ { id: "reveal-down", xfadeName: "revealdown", labelJa: "\u4E0A\u304B\u3089\u30EA\u30D3\u30FC\u30EB", category: "\u30EA\u30D3\u30FC\u30EB", previewKind: "reveal-down", glyph: "\u2193" },
54
+ { id: "reveal-up", xfadeName: "revealup", labelJa: "\u4E0B\u304B\u3089\u30EA\u30D3\u30FC\u30EB", category: "\u30EA\u30D3\u30FC\u30EB", previewKind: "reveal-up", glyph: "\u2191" },
55
+ { id: "circle-open", xfadeName: "circleopen", labelJa: "\u30B5\u30FC\u30AF\u30EB\uFF08\u958B\u304F\uFF09", category: "\u5F62\u72B6", previewKind: "circle-open", glyph: "\u25CB" },
56
+ { id: "circle-close", xfadeName: "circleclose", labelJa: "\u30B5\u30FC\u30AF\u30EB\uFF08\u9589\u3058\u308B\uFF09", category: "\u5F62\u72B6", previewKind: "circle-close", glyph: "\u25CF" },
57
+ { id: "zoom-in", xfadeName: "zoomin", labelJa: "\u30BA\u30FC\u30E0\u30A4\u30F3", category: "\u5909\u5F62", previewKind: "zoom-in", glyph: "\uFF0B" },
58
+ { id: "squeeze-h", xfadeName: "squeezeh", labelJa: "\u30B9\u30AF\u30A4\u30FC\u30BA\uFF08\u7E26\u3064\u3076\u3057\uFF09", category: "\u5909\u5F62", previewKind: "squeeze-h", glyph: "\u2195" },
59
+ { id: "squeeze-v", xfadeName: "squeezev", labelJa: "\u30B9\u30AF\u30A4\u30FC\u30BA\uFF08\u6A2A\u3064\u3076\u3057\uFF09", category: "\u5909\u5F62", previewKind: "squeeze-v", glyph: "\u2194" },
60
+ { id: "blur", xfadeName: "hblur", labelJa: "\u30D6\u30E9\u30FC", category: "\u8CEA\u611F", previewKind: "blur", glyph: "B" },
61
+ { id: "pixelize", xfadeName: "pixelize", labelJa: "\u30D4\u30AF\u30BB\u30EC\u30FC\u30C8", category: "\u8CEA\u611F", previewKind: "pixelize", glyph: "P" }
62
+ ];
63
+ var TRANSITION_TYPE_IDS = TRANSITION_VOCABULARY.map((entry) => entry.id);
64
+ var TRANSITION_CATEGORIES = [...new Set(TRANSITION_VOCABULARY.map((entry) => entry.category))];
65
+ var TRANSITION_BY_ID = Object.fromEntries(TRANSITION_VOCABULARY.map((entry) => [entry.id, entry]));
66
+
31
67
  // src/edit-store.ts
32
68
  function computeCutTrackSegments(cuts) {
33
69
  const cursorByTrack = /* @__PURE__ */ new Map();
@@ -50,6 +86,32 @@ var AkariEditKernel = (() => {
50
86
  return segments;
51
87
  }
52
88
 
89
+ // src/cut-adjacency.ts
90
+ var DEFAULT_CUT_ADJACENCY_FPS = 30;
91
+ function effectiveCutFps(fps) {
92
+ return Number.isFinite(fps) && fps > 0 ? fps : DEFAULT_CUT_ADJACENCY_FPS;
93
+ }
94
+ function cutOverlapFrames(earlier, later, fps = DEFAULT_CUT_ADJACENCY_FPS) {
95
+ const resolvedFps = effectiveCutFps(fps);
96
+ return Math.round(earlier.tlEnd * resolvedFps) - Math.round(later.tlStart * resolvedFps);
97
+ }
98
+ var nonNegativeRoom = (value) => value === Number.POSITIVE_INFINITY ? value : Number.isFinite(value) && value > 0 ? value : 0;
99
+ function planTransitionHandleWindow(input) {
100
+ const declaredSeconds = Number.isFinite(input.declaredSeconds) && input.declaredSeconds > 0 ? input.declaredSeconds : 0;
101
+ const effectiveSeconds = Math.max(0, Math.min(
102
+ declaredSeconds,
103
+ 2 * nonNegativeRoom(input.outgoingTailRoomSeconds),
104
+ 2 * nonNegativeRoom(input.incomingHeadRoomSeconds),
105
+ 2 * nonNegativeRoom(input.outgoingDurationSeconds),
106
+ 2 * nonNegativeRoom(input.incomingDurationSeconds)
107
+ ));
108
+ return {
109
+ effectiveSeconds,
110
+ halfSeconds: effectiveSeconds / 2,
111
+ outcome: effectiveSeconds <= 0 ? "none" : effectiveSeconds < declaredSeconds ? "clamped" : "full"
112
+ };
113
+ }
114
+
53
115
  // src/timeline-map.ts
54
116
  function transitionProgressAt(window, outputT) {
55
117
  if (!(window.duration > 0)) return 0;
@@ -68,10 +130,46 @@ var AkariEditKernel = (() => {
68
130
  const resolved = trackSegments.map((segment) => ({
69
131
  start: segment.at,
70
132
  end: segment.end,
133
+ baseStart: segment.at,
134
+ baseEnd: segment.end,
71
135
  track: segment.track,
72
136
  cut: usableCuts[segment.index],
73
137
  cutIndex: usable[segment.index].index
74
138
  }));
139
+ const fps = options?.fps ?? DEFAULT_CUT_ADJACENCY_FPS;
140
+ for (let outgoingIndex = 0; outgoingIndex < resolved.length; outgoingIndex++) {
141
+ const outgoing = resolved[outgoingIndex];
142
+ const transition = outgoing.cut.transitionOut;
143
+ if (!transition || !(typeof transition.duration === "number" && Number.isFinite(transition.duration) && transition.duration > 0)) continue;
144
+ const incoming = resolved.slice(outgoingIndex + 1).find((candidate) => candidate.track === outgoing.track);
145
+ if (!incoming || cutOverlapFrames(
146
+ { tlEnd: outgoing.end },
147
+ { tlStart: incoming.start },
148
+ fps
149
+ ) !== 0) continue;
150
+ const outgoingRoom = options?.handleRoom?.(outgoing.cutIndex);
151
+ const incomingRoom = options?.handleRoom?.(incoming.cutIndex);
152
+ const incomingSpeed = typeof incoming.cut.speed === "number" && incoming.cut.speed > 0 ? incoming.cut.speed : 1;
153
+ const plan = planTransitionHandleWindow({
154
+ declaredSeconds: transition.duration,
155
+ outgoingTailRoomSeconds: outgoingRoom?.tailSeconds ?? Number.POSITIVE_INFINITY,
156
+ incomingHeadRoomSeconds: incomingRoom?.headSeconds ?? incoming.cut.in / incomingSpeed,
157
+ outgoingDurationSeconds: outgoing.baseEnd - outgoing.baseStart,
158
+ incomingDurationSeconds: incoming.baseEnd - incoming.baseStart
159
+ });
160
+ if (plan.effectiveSeconds <= 0) continue;
161
+ const cutPoint = outgoing.end;
162
+ outgoing.end = cutPoint + plan.halfSeconds;
163
+ outgoing.cut = {
164
+ ...outgoing.cut,
165
+ transitionOut: { ...transition, duration: plan.effectiveSeconds }
166
+ };
167
+ incoming.start = cutPoint - plan.halfSeconds;
168
+ incoming.cut = {
169
+ ...incoming.cut,
170
+ in: Math.max(0, incoming.cut.in - plan.halfSeconds * incomingSpeed)
171
+ };
172
+ }
75
173
  const segmentSlice = (entry, start, end, transitionOut = null) => {
76
174
  const cut = entry.cut;
77
175
  const speed = typeof cut.speed === "number" && cut.speed > 0 ? cut.speed : 1;
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@akari-video/matte-rvm",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "Robust Video Matting model deployment and raw BGRA inference helper.",
7
+ "main": "src/index.mjs",
8
+ "exports": {
9
+ ".": "./src/index.mjs"
10
+ },
11
+ "scripts": {
12
+ "postinstall": "node scripts/fetch-models.mjs",
13
+ "fetch:resnet50": "node scripts/fetch-models.mjs --model resnet50",
14
+ "test": "node --test \"test/*.test.mjs\""
15
+ },
16
+ "dependencies": {
17
+ "onnxruntime-node": "1.29.0"
18
+ },
19
+ "engines": {
20
+ "node": ">=20"
21
+ }
22
+ }
@@ -6,6 +6,9 @@
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
+ import { createRequire } from "node:module";
10
+
11
+ const { TRANSITION_TYPE_IDS } = createRequire(import.meta.url)("../../edit-store/lib/index.js");
9
12
 
10
13
  const LAYER_KINDS = new Set(["baked", "video", "filter"]);
11
14
  const LAYER_BLEND_MODES = new Set([
@@ -1063,8 +1066,8 @@ function validateTransitionOut(value, label) {
1063
1066
  fail(`${label} は object である必要があります`);
1064
1067
  return;
1065
1068
  }
1066
- if (!["dissolve", "fade-black", "fade-white", "reveal-down", "reveal-up"].includes(value.type)) {
1067
- fail(`${label}.type は dissolve/fade-black/fade-white/reveal-down/reveal-up のいずれかである必要があります`);
1069
+ if (!TRANSITION_TYPE_IDS.includes(value.type)) {
1070
+ fail(`${label}.type は ${TRANSITION_TYPE_IDS.join("/")} のいずれかである必要があります`);
1068
1071
  }
1069
1072
  if (!isFiniteNumber(value.duration) || value.duration <= 0) {
1070
1073
  fail(`${label}.duration は 0 より大きい有限数である必要があります`);
@@ -279,10 +279,10 @@
279
279
  "additionalProperties": true,
280
280
  "required": ["type", "duration"],
281
281
  "properties": {
282
- "type": { "enum": ["dissolve", "fade-black", "fade-white", "reveal-down", "reveal-up"] },
282
+ "type": { "enum": ["dissolve", "fade", "fade-black", "fade-white", "fade-grays", "wipe-left", "wipe-right", "wipe-up", "wipe-down", "radial", "slide-left", "slide-right", "slide-up", "slide-down", "cover-left", "cover-right", "cover-up", "cover-down", "reveal-left", "reveal-right", "reveal-down", "reveal-up", "circle-open", "circle-close", "zoom-in", "squeeze-h", "squeeze-v", "blur", "pixelize"] },
283
283
  "duration": { "$ref": "#/$defs/positiveNumber" }
284
284
  },
285
- "$comment": "docs/contract-2026-07-22-render-basics.md #3。v0 は指定されたカット境界のみ xfade 経路(残裁定3)。ffmpeg xfade の transition 名へ直接対応: dissolve/fadeblack/fadewhite/revealdown/revealup。reveal-down / reveal-up は前カットが丸ごとその方向へ動いて画面外へ抜け、空いた側から次カットが現れる(2026-08-14 追加)。"
285
+ "$comment": "docs/contract-2026-07-22-render-basics.md #3。v0 は指定されたカット境界のみ xfade 経路(残裁定3)。enum は packages/edit-store の正準トランジション語彙と drift test で一致させる。"
286
286
  },
287
287
  "cutTransform": {
288
288
  "type": "object",
@@ -0,0 +1,64 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { createRequire } from 'node:module';
4
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import test from 'node:test';
9
+
10
+ import Ajv2020 from 'ajv/dist/2020.js';
11
+
12
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
13
+ const schema = JSON.parse(readFileSync(join(packageRoot, 'edit.schema.json'), 'utf8'));
14
+ const validate = new Ajv2020({ allErrors: true, strict: false }).compile(schema);
15
+ const { TRANSITION_TYPE_IDS } = createRequire(import.meta.url)(
16
+ '../../edit-store/lib/index.js'
17
+ );
18
+
19
+ const editFor = type => ({
20
+ version: 0,
21
+ output: { width: 1280, height: 720, fps: 30 },
22
+ source: { path: 'source.mp4', proxy: null },
23
+ cuts: [
24
+ { in: 0, out: 2, transition_out: { type, duration: 0.5 } },
25
+ { in: 2, out: 4 },
26
+ ],
27
+ overlays: [],
28
+ });
29
+
30
+ test('正準 29 種は JSON Schema と validate-edit CLI を往復する', () => {
31
+ assert.deepEqual(schema.$defs.transitionOut.properties.type.enum, TRANSITION_TYPE_IDS);
32
+ const root = mkdtempSync(join(tmpdir(), 'akari-transition-schema-'));
33
+ try {
34
+ for (const type of TRANSITION_TYPE_IDS) {
35
+ const edit = editFor(type);
36
+ assert.equal(validate(edit), true, `${type}: ${JSON.stringify(validate.errors)}`);
37
+ const editPath = join(root, `${type}.json`);
38
+ writeFileSync(editPath, `${JSON.stringify(edit)}\n`);
39
+ const executed = spawnSync(process.execPath, [join(packageRoot, 'bin', 'validate-edit.mjs'), editPath], {
40
+ encoding: 'utf8'
41
+ });
42
+ assert.equal(executed.status, 0, `${type}: ${executed.stderr}`);
43
+ }
44
+ } finally {
45
+ rmSync(root, { recursive: true, force: true });
46
+ }
47
+ });
48
+
49
+ test('未知種別は schema と validate-edit CLI の双方で拒否される', () => {
50
+ const edit = editFor('future-transition');
51
+ assert.equal(validate(edit), false);
52
+ const root = mkdtempSync(join(tmpdir(), 'akari-transition-schema-invalid-'));
53
+ try {
54
+ const editPath = join(root, 'edit.json');
55
+ writeFileSync(editPath, `${JSON.stringify(edit)}\n`);
56
+ const executed = spawnSync(process.execPath, [join(packageRoot, 'bin', 'validate-edit.mjs'), editPath], {
57
+ encoding: 'utf8'
58
+ });
59
+ assert.equal(executed.status, 1);
60
+ assert.match(executed.stderr, /future-transition|いずれか/u);
61
+ } finally {
62
+ rmSync(root, { recursive: true, force: true });
63
+ }
64
+ });
@@ -214,12 +214,12 @@ test("source.chroma_key.color is required", () => {
214
214
  assert.match(executed.stderr, /source\.chroma_key\.color は空でない文字列である必要があります/);
215
215
  });
216
216
 
217
- test("cuts[].transition_out.type must be dissolve/fade-black/fade-white/reveal-down/reveal-up", () => {
217
+ test("cuts[].transition_out.type must be one of the canonical transition vocabulary", () => {
218
218
  const executed = run("edit-transition-invalid");
219
219
  assert.equal(executed.status, 1, executed.stdout);
220
220
  assert.match(
221
221
  executed.stderr,
222
- /cuts\[0\]\.transition_out\.type は dissolve\/fade-black\/fade-white\/reveal-down\/reveal-up のいずれかである必要があります/,
222
+ /cuts\[0\]\.transition_out\.type は .*pixelize のいずれかである必要があります/,
223
223
  );
224
224
  });
225
225