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
@@ -7,10 +7,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.readInternalEdit = readInternalEdit;
8
8
  exports.readInternalSources = readInternalSources;
9
9
  exports.visualContentEndSeconds = visualContentEndSeconds;
10
+ exports.findCrossTrackLayerEvacuations = findCrossTrackLayerEvacuations;
10
11
  exports.projectLegacyEdit = projectLegacyEdit;
11
12
  exports.toLegacyTrack = toLegacyTrack;
12
13
  exports.derivedLegacyTracks = derivedLegacyTracks;
13
14
  const edit_v2_1 = require("./edit-v2");
15
+ const cut_adjacency_1 = require("./cut-adjacency");
14
16
  const error_1 = require("./migrate/error");
15
17
  /**
16
18
  * edit.json v2 を内部表現へ読む。v0/v1 は凍結変換ユニットのみが読む。
@@ -171,6 +173,7 @@ function readV2Internal(raw) {
171
173
  // 旧 top-level audio と tracks[] 音声が同居すると、後者に加えてこの fallback も射影される。
172
174
  // どちらを優先するかは未裁定なので、旧 fixture の互換挙動を変えず二重計上の可能性を残す。
173
175
  addV2AudioItems(tracks, edit.audio, fps, legacyIndexCounters);
176
+ synthesizeHiddenTransitionHandlesForRender(tracks, fps);
174
177
  return {
175
178
  output: {
176
179
  width: edit.output.width,
@@ -190,6 +193,66 @@ function readV2Internal(raw) {
190
193
  }
191
194
  };
192
195
  }
196
+ /**
197
+ * render-cut に加え shell プレビューの summary と Web UI の readRenderEdit も
198
+ * item.declaration を読む。そのため出荷プレビューはこの合成済み実重なりを既存窓として描く。
199
+ * UI / lint が読む item の at/duration/source と legacy.value は生タイムラインのまま保ち、
200
+ * 突き合わせ境界の隠れのりしろだけを declaration 上の物理重なりへ合成して既存
201
+ * xfade/acrossfade と 2 つのプレビューへ渡す。生 cuts を直接読む消費者の隠れのりしろ経路とは
202
+ * packages/edit-store/test/transition-window-path-equivalence.test.mjs で等価性を固定する。
203
+ */
204
+ function synthesizeHiddenTransitionHandlesForRender(tracks, fps) {
205
+ const speedOf = (item) => {
206
+ const speed = item.declaration.speed;
207
+ return typeof speed === 'number' && Number.isFinite(speed) && speed > 0 ? speed : 1;
208
+ };
209
+ for (const track of tracks) {
210
+ if (track.lane !== 'visual')
211
+ continue;
212
+ const cuts = track.items.filter((item) => item.legacy.collection === 'cuts' && item.source.kind === 'media');
213
+ for (let index = 0; index + 1 < cuts.length; index++) {
214
+ const outgoing = cuts[index];
215
+ const incoming = cuts[index + 1];
216
+ if ((0, cut_adjacency_1.cutOverlapFrames)({ tlEnd: outgoing.at + outgoing.duration }, { tlStart: incoming.at }, fps) !== 0)
217
+ continue;
218
+ const transition = outgoing.declaration.transition_out;
219
+ if (!isRecord(transition)
220
+ || typeof transition.duration !== 'number'
221
+ || !Number.isFinite(transition.duration)
222
+ || transition.duration <= 0)
223
+ continue;
224
+ const incomingSpeed = speedOf(incoming);
225
+ const incomingStill = (0, cut_adjacency_1.isStillImageSourcePath)(incoming.source.path);
226
+ const plan = (0, cut_adjacency_1.planTransitionHandleWindow)({
227
+ declaredSeconds: transition.duration,
228
+ outgoingTailRoomSeconds: Number.POSITIVE_INFINITY,
229
+ incomingHeadRoomSeconds: incomingStill
230
+ ? Number.POSITIVE_INFINITY : incoming.source.in / incomingSpeed,
231
+ outgoingDurationSeconds: outgoing.duration,
232
+ incomingDurationSeconds: incoming.duration
233
+ });
234
+ if (plan.effectiveSeconds <= 0)
235
+ continue;
236
+ const outgoingSpeed = speedOf(outgoing);
237
+ outgoing.declaration = {
238
+ ...outgoing.declaration,
239
+ out: Number(outgoing.declaration.out) + plan.halfSeconds * outgoingSpeed,
240
+ transition_out: { ...transition, duration: plan.effectiveSeconds }
241
+ };
242
+ incoming.declaration = incomingStill
243
+ ? {
244
+ ...incoming.declaration,
245
+ at: Number(incoming.declaration.at) - plan.halfSeconds,
246
+ out: Number(incoming.declaration.out) + plan.halfSeconds * incomingSpeed
247
+ }
248
+ : {
249
+ ...incoming.declaration,
250
+ at: Number(incoming.declaration.at) - plan.halfSeconds,
251
+ in: Number(incoming.declaration.in) - plan.halfSeconds * incomingSpeed
252
+ };
253
+ }
254
+ }
255
+ }
193
256
  function legacyKindOfV2Track(track, chromaKeyOf, overlappingItemIds) {
194
257
  if (!('items' in track)) {
195
258
  return 'captions';
@@ -258,26 +321,16 @@ function needsLayersEngine(item, chromaKeyOf, hasOverlappingSibling = false) {
258
321
  return true;
259
322
  return false;
260
323
  }
261
- // P0 2026-08-23 cuts-cross-track-overlap: 'cuts' 経路は同一トラック内の同時表示だけでなく、
262
- // 別トラック間の同時表示も winner-take-all になり、下段を描画できない。全 visual トラックの
263
- // items[] を総当りで比較し、半開区間 [at, at+duration) が正の長さで交差する media アイテムを
264
- // layers へ退避する。終端と開始が接するだけなら交差ではない。同一トラックの交差は両方を
265
- // layers へ送る。別トラックの交差は、下段を cuts の不透明な基底として残し、上段が全画面不透明
266
- // ではない宣言を持つときだけ layers へ送る。上段が全画面不透明なら winner-take-all でも同じ絵に
267
- // なるため、従来の cuts / track-stack 経路を保つ。
268
- //
269
- // transition_out は同一トラックの隣接カットが意図的に作る狭い重なりで、cuts/xfade が表現する。
270
- // その例外は同一トラックのペアだけに適用する。別トラックとの交差は transition の有無にかかわらず
271
- // 真の同時表示なので layers へ退避する。
272
- function computeOverlappingItemIds(itemGroups) {
324
+ function analyzeOverlappingItems(itemGroups) {
273
325
  const overlapping = new Set();
274
- const entries = itemGroups.flatMap((items, trackIndex) => items.map(item => ({ item, trackIndex })));
326
+ const crossTrackEvacuations = [];
327
+ const entries = itemGroups.flatMap((group, trackIndex) => group.items.map(item => ({ item, trackIndex, trackId: group.trackId })));
275
328
  for (let i = 0; i < entries.length; i++) {
276
- const { item: a, trackIndex: aTrackIndex } = entries[i];
329
+ const { item: a, trackIndex: aTrackIndex, trackId: aTrackId } = entries[i];
277
330
  if (a.source.kind !== 'media')
278
331
  continue;
279
332
  for (let j = i + 1; j < entries.length; j++) {
280
- const { item: b, trackIndex: bTrackIndex } = entries[j];
333
+ const { item: b, trackIndex: bTrackIndex, trackId: bTrackId } = entries[j];
281
334
  if (b.source.kind !== 'media')
282
335
  continue;
283
336
  if (!(a.at < b.at + b.duration && b.at < a.at + a.duration))
@@ -291,13 +344,40 @@ function computeOverlappingItemIds(itemGroups) {
291
344
  overlapping.add(b.id);
292
345
  }
293
346
  else {
294
- const upper = aTrackIndex > bTrackIndex ? a : b;
295
- if (needsCrossTrackLayers(upper))
347
+ const upperIsA = aTrackIndex > bTrackIndex;
348
+ const upper = upperIsA ? a : b;
349
+ const lower = upperIsA ? b : a;
350
+ if (needsCrossTrackLayers(upper)) {
296
351
  overlapping.add(upper.id);
352
+ crossTrackEvacuations.push({
353
+ itemId: upper.id,
354
+ trackId: upperIsA ? aTrackId : bTrackId,
355
+ causeItemId: lower.id,
356
+ causeTrackId: upperIsA ? bTrackId : aTrackId,
357
+ overlapStartFrames: Math.max(a.at, b.at),
358
+ overlapEndFrames: Math.min(a.at + a.duration, b.at + b.duration)
359
+ });
360
+ }
297
361
  }
298
362
  }
299
363
  }
300
- return overlapping;
364
+ return { itemIds: overlapping, crossTrackEvacuations };
365
+ }
366
+ function computeOverlappingItemIds(itemGroups) {
367
+ return analyzeOverlappingItems(itemGroups.map((items, index) => ({
368
+ items,
369
+ trackId: String(index)
370
+ }))).itemIds;
371
+ }
372
+ /**
373
+ * 別 visual track との重なりが原因で upper item が layers へ退避される組を返す。
374
+ * edit-lint と UI は理由文言に必要な相手 id を、この単一定義から得る。
375
+ */
376
+ function findCrossTrackLayerEvacuations(edit) {
377
+ const parsed = (0, edit_v2_1.readEditV2)(edit);
378
+ return analyzeOverlappingItems(parsed.tracks.flatMap(track => track.lane === 'visual' && 'items' in track
379
+ ? [{ items: track.items, trackId: track.id }]
380
+ : [])).crossTrackEvacuations;
301
381
  }
302
382
  // cuts の winner-take-all が下段を隠してよいのは、上段が全画面を不透明に覆う場合だけ。
303
383
  // transform の単位元を明示しただけなら従来経路を維持する。crop / 半透明 / keyframes は、
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseEdit = parseEdit;
4
+ const transition_vocabulary_1 = require("../transition-vocabulary");
4
5
  const LAYER_BLEND_MODES = [
5
6
  'normal', 'screen', 'multiply', 'add', 'difference',
6
7
  'darken', 'lighten', 'overlay', 'hardlight', 'softlight'
@@ -78,16 +79,18 @@ function parseEdit(source) {
78
79
  let transitionOut;
79
80
  if (rawCut.transition_out !== undefined && rawCut.transition_out !== null) {
80
81
  const transition = rawCut.transition_out;
81
- const validType = transition?.type === 'dissolve'
82
- || transition?.type === 'fade-black'
83
- || transition?.type === 'fade-white'
84
- || transition?.type === 'reveal-down'
85
- || transition?.type === 'reveal-up';
82
+ // 正準語彙に加え、手編集で先行した非空文字列も読み取りだけは保持する。
83
+ // 書き込み API は isTransitionType で閉じたまま、preview の汎用フォールバックへ渡す。
84
+ const validType = (0, transition_vocabulary_1.isTransitionType)(transition?.type)
85
+ || (typeof transition?.type === 'string' && transition.type.trim().length > 0);
86
86
  const validDuration = typeof transition?.duration === 'number'
87
87
  && Number.isFinite(transition.duration) && transition.duration > 0;
88
88
  if (transition && typeof transition === 'object' && !Array.isArray(transition)
89
89
  && validType && validDuration) {
90
- transitionOut = { type: transition.type, duration: transition.duration };
90
+ transitionOut = {
91
+ type: transition.type,
92
+ duration: transition.duration
93
+ };
91
94
  }
92
95
  else {
93
96
  warnings.push(`${index + 1} 番目のクリップの transition_out が不正なため無視します。`);
@@ -56,6 +56,11 @@ export interface TimelineMapResult {
56
56
  export declare function transitionProgressAt(window: TimelineTransitionWindow, outputT: number): number;
57
57
  export declare function buildTimelineMap(cuts: readonly EditCut[], options?: {
58
58
  trackZ?: (track: number) => number;
59
+ fps?: number;
60
+ handleRoom?: (cutIndex: number) => {
61
+ tailSeconds?: number;
62
+ headSeconds?: number;
63
+ } | undefined;
59
64
  }): TimelineMapResult;
60
65
  /**
61
66
  * 出力秒 → ソース秒。トランジション重なり区間(隣接 src が重なる出力時刻)は
@@ -16,6 +16,7 @@ exports.transitionProgressAt = transitionProgressAt;
16
16
  exports.buildTimelineMap = buildTimelineMap;
17
17
  exports.outputToSource = outputToSource;
18
18
  const edit_store_1 = require("./edit-store");
19
+ const cut_adjacency_1 = require("./cut-adjacency");
19
20
  function transitionProgressAt(window, outputT) {
20
21
  if (!(window.duration > 0))
21
22
  return 0;
@@ -37,10 +38,49 @@ function buildTimelineMap(cuts, options) {
37
38
  const resolved = trackSegments.map(segment => ({
38
39
  start: segment.at,
39
40
  end: segment.end,
41
+ baseStart: segment.at,
42
+ baseEnd: segment.end,
40
43
  track: segment.track,
41
44
  cut: usableCuts[segment.index],
42
45
  cutIndex: usable[segment.index].index
43
46
  }));
47
+ const fps = options?.fps ?? cut_adjacency_1.DEFAULT_CUT_ADJACENCY_FPS;
48
+ // 隠れのりしろは、フレーム量子化で突き合わせとなる境界にだけ合成する。
49
+ // 実重なり済みの境界は一切変更せず、従来の窓計算へそのまま流す。
50
+ for (let outgoingIndex = 0; outgoingIndex < resolved.length; outgoingIndex++) {
51
+ const outgoing = resolved[outgoingIndex];
52
+ const transition = outgoing.cut.transitionOut;
53
+ if (!transition || !(typeof transition.duration === 'number' && Number.isFinite(transition.duration)
54
+ && transition.duration > 0))
55
+ continue;
56
+ const incoming = resolved.slice(outgoingIndex + 1).find(candidate => candidate.track === outgoing.track);
57
+ if (!incoming || (0, cut_adjacency_1.cutOverlapFrames)({ tlEnd: outgoing.end }, { tlStart: incoming.start }, fps) !== 0)
58
+ continue;
59
+ const outgoingRoom = options?.handleRoom?.(outgoing.cutIndex);
60
+ const incomingRoom = options?.handleRoom?.(incoming.cutIndex);
61
+ const incomingSpeed = typeof incoming.cut.speed === 'number' && incoming.cut.speed > 0
62
+ ? incoming.cut.speed : 1;
63
+ const plan = (0, cut_adjacency_1.planTransitionHandleWindow)({
64
+ declaredSeconds: transition.duration,
65
+ outgoingTailRoomSeconds: outgoingRoom?.tailSeconds ?? Number.POSITIVE_INFINITY,
66
+ incomingHeadRoomSeconds: incomingRoom?.headSeconds ?? incoming.cut.in / incomingSpeed,
67
+ outgoingDurationSeconds: outgoing.baseEnd - outgoing.baseStart,
68
+ incomingDurationSeconds: incoming.baseEnd - incoming.baseStart
69
+ });
70
+ if (plan.effectiveSeconds <= 0)
71
+ continue;
72
+ const cutPoint = outgoing.end;
73
+ outgoing.end = cutPoint + plan.halfSeconds;
74
+ outgoing.cut = {
75
+ ...outgoing.cut,
76
+ transitionOut: { ...transition, duration: plan.effectiveSeconds }
77
+ };
78
+ incoming.start = cutPoint - plan.halfSeconds;
79
+ incoming.cut = {
80
+ ...incoming.cut,
81
+ in: Math.max(0, incoming.cut.in - plan.halfSeconds * incomingSpeed)
82
+ };
83
+ }
44
84
  const segmentSlice = (entry, start, end, transitionOut = null) => {
45
85
  const cut = entry.cut;
46
86
  const speed = typeof cut.speed === 'number' && cut.speed > 0 ? cut.speed : 1;
@@ -0,0 +1,223 @@
1
+ /**
2
+ * edit.json の transition_out.type に使える正準語彙。
3
+ * JSON Schema はリテラル enum を保つため別置きだが、drift test でこの表と一致させる。
4
+ */
5
+ export declare const TRANSITION_VOCABULARY: readonly [{
6
+ readonly id: "dissolve";
7
+ readonly xfadeName: "dissolve";
8
+ readonly labelJa: "ディゾルブ";
9
+ readonly category: "フェード";
10
+ readonly previewKind: "dissolve";
11
+ readonly glyph: "D";
12
+ }, {
13
+ readonly id: "fade";
14
+ readonly xfadeName: "fade";
15
+ readonly labelJa: "クロスフェード";
16
+ readonly category: "フェード";
17
+ readonly previewKind: "fade";
18
+ readonly glyph: "F";
19
+ }, {
20
+ readonly id: "fade-black";
21
+ readonly xfadeName: "fadeblack";
22
+ readonly labelJa: "黒フェード";
23
+ readonly category: "フェード";
24
+ readonly previewKind: "fade-black";
25
+ readonly glyph: "B";
26
+ }, {
27
+ readonly id: "fade-white";
28
+ readonly xfadeName: "fadewhite";
29
+ readonly labelJa: "白フェード";
30
+ readonly category: "フェード";
31
+ readonly previewKind: "fade-white";
32
+ readonly glyph: "W";
33
+ }, {
34
+ readonly id: "fade-grays";
35
+ readonly xfadeName: "fadegrays";
36
+ readonly labelJa: "モノクロフェード";
37
+ readonly category: "フェード";
38
+ readonly previewKind: "fade-grays";
39
+ readonly glyph: "G";
40
+ }, {
41
+ readonly id: "wipe-left";
42
+ readonly xfadeName: "wipeleft";
43
+ readonly labelJa: "ワイプ(左へ)";
44
+ readonly category: "ワイプ";
45
+ readonly previewKind: "wipe-left";
46
+ readonly glyph: "←";
47
+ }, {
48
+ readonly id: "wipe-right";
49
+ readonly xfadeName: "wiperight";
50
+ readonly labelJa: "ワイプ(右へ)";
51
+ readonly category: "ワイプ";
52
+ readonly previewKind: "wipe-right";
53
+ readonly glyph: "→";
54
+ }, {
55
+ readonly id: "wipe-up";
56
+ readonly xfadeName: "wipeup";
57
+ readonly labelJa: "ワイプ(上へ)";
58
+ readonly category: "ワイプ";
59
+ readonly previewKind: "wipe-up";
60
+ readonly glyph: "↑";
61
+ }, {
62
+ readonly id: "wipe-down";
63
+ readonly xfadeName: "wipedown";
64
+ readonly labelJa: "ワイプ(下へ)";
65
+ readonly category: "ワイプ";
66
+ readonly previewKind: "wipe-down";
67
+ readonly glyph: "↓";
68
+ }, {
69
+ readonly id: "radial";
70
+ readonly xfadeName: "radial";
71
+ readonly labelJa: "時計ワイプ";
72
+ readonly category: "ワイプ";
73
+ readonly previewKind: "radial";
74
+ readonly glyph: "◷";
75
+ }, {
76
+ readonly id: "slide-left";
77
+ readonly xfadeName: "slideleft";
78
+ readonly labelJa: "スライド(左へ)";
79
+ readonly category: "スライド";
80
+ readonly previewKind: "slide-left";
81
+ readonly glyph: "←";
82
+ }, {
83
+ readonly id: "slide-right";
84
+ readonly xfadeName: "slideright";
85
+ readonly labelJa: "スライド(右へ)";
86
+ readonly category: "スライド";
87
+ readonly previewKind: "slide-right";
88
+ readonly glyph: "→";
89
+ }, {
90
+ readonly id: "slide-up";
91
+ readonly xfadeName: "slideup";
92
+ readonly labelJa: "スライド(上へ)";
93
+ readonly category: "スライド";
94
+ readonly previewKind: "slide-up";
95
+ readonly glyph: "↑";
96
+ }, {
97
+ readonly id: "slide-down";
98
+ readonly xfadeName: "slidedown";
99
+ readonly labelJa: "スライド(下へ)";
100
+ readonly category: "スライド";
101
+ readonly previewKind: "slide-down";
102
+ readonly glyph: "↓";
103
+ }, {
104
+ readonly id: "cover-left";
105
+ readonly xfadeName: "coverleft";
106
+ readonly labelJa: "カバー(左へ)";
107
+ readonly category: "カバー";
108
+ readonly previewKind: "cover-left";
109
+ readonly glyph: "←";
110
+ }, {
111
+ readonly id: "cover-right";
112
+ readonly xfadeName: "coverright";
113
+ readonly labelJa: "カバー(右へ)";
114
+ readonly category: "カバー";
115
+ readonly previewKind: "cover-right";
116
+ readonly glyph: "→";
117
+ }, {
118
+ readonly id: "cover-up";
119
+ readonly xfadeName: "coverup";
120
+ readonly labelJa: "カバー(上へ)";
121
+ readonly category: "カバー";
122
+ readonly previewKind: "cover-up";
123
+ readonly glyph: "↑";
124
+ }, {
125
+ readonly id: "cover-down";
126
+ readonly xfadeName: "coverdown";
127
+ readonly labelJa: "カバー(下へ)";
128
+ readonly category: "カバー";
129
+ readonly previewKind: "cover-down";
130
+ readonly glyph: "↓";
131
+ }, {
132
+ readonly id: "reveal-left";
133
+ readonly xfadeName: "revealleft";
134
+ readonly labelJa: "リビール(左へ)";
135
+ readonly category: "リビール";
136
+ readonly previewKind: "reveal-left";
137
+ readonly glyph: "←";
138
+ }, {
139
+ readonly id: "reveal-right";
140
+ readonly xfadeName: "revealright";
141
+ readonly labelJa: "リビール(右へ)";
142
+ readonly category: "リビール";
143
+ readonly previewKind: "reveal-right";
144
+ readonly glyph: "→";
145
+ }, {
146
+ readonly id: "reveal-down";
147
+ readonly xfadeName: "revealdown";
148
+ readonly labelJa: "上からリビール";
149
+ readonly category: "リビール";
150
+ readonly previewKind: "reveal-down";
151
+ readonly glyph: "↓";
152
+ }, {
153
+ readonly id: "reveal-up";
154
+ readonly xfadeName: "revealup";
155
+ readonly labelJa: "下からリビール";
156
+ readonly category: "リビール";
157
+ readonly previewKind: "reveal-up";
158
+ readonly glyph: "↑";
159
+ }, {
160
+ readonly id: "circle-open";
161
+ readonly xfadeName: "circleopen";
162
+ readonly labelJa: "サークル(開く)";
163
+ readonly category: "形状";
164
+ readonly previewKind: "circle-open";
165
+ readonly glyph: "○";
166
+ }, {
167
+ readonly id: "circle-close";
168
+ readonly xfadeName: "circleclose";
169
+ readonly labelJa: "サークル(閉じる)";
170
+ readonly category: "形状";
171
+ readonly previewKind: "circle-close";
172
+ readonly glyph: "●";
173
+ }, {
174
+ readonly id: "zoom-in";
175
+ readonly xfadeName: "zoomin";
176
+ readonly labelJa: "ズームイン";
177
+ readonly category: "変形";
178
+ readonly previewKind: "zoom-in";
179
+ readonly glyph: "+";
180
+ }, {
181
+ readonly id: "squeeze-h";
182
+ readonly xfadeName: "squeezeh";
183
+ readonly labelJa: "スクイーズ(縦つぶし)";
184
+ readonly category: "変形";
185
+ readonly previewKind: "squeeze-h";
186
+ readonly glyph: "↕";
187
+ }, {
188
+ readonly id: "squeeze-v";
189
+ readonly xfadeName: "squeezev";
190
+ readonly labelJa: "スクイーズ(横つぶし)";
191
+ readonly category: "変形";
192
+ readonly previewKind: "squeeze-v";
193
+ readonly glyph: "↔";
194
+ }, {
195
+ readonly id: "blur";
196
+ readonly xfadeName: "hblur";
197
+ readonly labelJa: "ブラー";
198
+ readonly category: "質感";
199
+ readonly previewKind: "blur";
200
+ readonly glyph: "B";
201
+ }, {
202
+ readonly id: "pixelize";
203
+ readonly xfadeName: "pixelize";
204
+ readonly labelJa: "ピクセレート";
205
+ readonly category: "質感";
206
+ readonly previewKind: "pixelize";
207
+ readonly glyph: "P";
208
+ }];
209
+ export type TransitionDefinition = typeof TRANSITION_VOCABULARY[number];
210
+ export type TransitionType = TransitionDefinition['id'];
211
+ export type TransitionCategory = TransitionDefinition['category'];
212
+ export type TransitionPreviewKind = TransitionDefinition['previewKind'];
213
+ declare const unknownTransitionTypeBrand: unique symbol;
214
+ /** 読み取り側だけが保持する、schema より先行した未知種別。書き込み API には使わない。 */
215
+ export type UnknownTransitionType = string & {
216
+ readonly [unknownTransitionTypeBrand]: true;
217
+ };
218
+ export type ReadableTransitionType = TransitionType | UnknownTransitionType;
219
+ export declare const TRANSITION_TYPE_IDS: readonly TransitionType[];
220
+ export declare const TRANSITION_CATEGORIES: readonly TransitionCategory[];
221
+ export declare const TRANSITION_BY_ID: Readonly<Record<TransitionType, TransitionDefinition>>;
222
+ export declare function isTransitionType(value: unknown): value is TransitionType;
223
+ export {};
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TRANSITION_BY_ID = exports.TRANSITION_CATEGORIES = exports.TRANSITION_TYPE_IDS = exports.TRANSITION_VOCABULARY = void 0;
4
+ exports.isTransitionType = isTransitionType;
5
+ /**
6
+ * edit.json の transition_out.type に使える正準語彙。
7
+ * JSON Schema はリテラル enum を保つため別置きだが、drift test でこの表と一致させる。
8
+ */
9
+ exports.TRANSITION_VOCABULARY = [
10
+ { id: 'dissolve', xfadeName: 'dissolve', labelJa: 'ディゾルブ', category: 'フェード', previewKind: 'dissolve', glyph: 'D' },
11
+ { id: 'fade', xfadeName: 'fade', labelJa: 'クロスフェード', category: 'フェード', previewKind: 'fade', glyph: 'F' },
12
+ { id: 'fade-black', xfadeName: 'fadeblack', labelJa: '黒フェード', category: 'フェード', previewKind: 'fade-black', glyph: 'B' },
13
+ { id: 'fade-white', xfadeName: 'fadewhite', labelJa: '白フェード', category: 'フェード', previewKind: 'fade-white', glyph: 'W' },
14
+ { id: 'fade-grays', xfadeName: 'fadegrays', labelJa: 'モノクロフェード', category: 'フェード', previewKind: 'fade-grays', glyph: 'G' },
15
+ { id: 'wipe-left', xfadeName: 'wipeleft', labelJa: 'ワイプ(左へ)', category: 'ワイプ', previewKind: 'wipe-left', glyph: '←' },
16
+ { id: 'wipe-right', xfadeName: 'wiperight', labelJa: 'ワイプ(右へ)', category: 'ワイプ', previewKind: 'wipe-right', glyph: '→' },
17
+ { id: 'wipe-up', xfadeName: 'wipeup', labelJa: 'ワイプ(上へ)', category: 'ワイプ', previewKind: 'wipe-up', glyph: '↑' },
18
+ { id: 'wipe-down', xfadeName: 'wipedown', labelJa: 'ワイプ(下へ)', category: 'ワイプ', previewKind: 'wipe-down', glyph: '↓' },
19
+ { id: 'radial', xfadeName: 'radial', labelJa: '時計ワイプ', category: 'ワイプ', previewKind: 'radial', glyph: '◷' },
20
+ { id: 'slide-left', xfadeName: 'slideleft', labelJa: 'スライド(左へ)', category: 'スライド', previewKind: 'slide-left', glyph: '←' },
21
+ { id: 'slide-right', xfadeName: 'slideright', labelJa: 'スライド(右へ)', category: 'スライド', previewKind: 'slide-right', glyph: '→' },
22
+ { id: 'slide-up', xfadeName: 'slideup', labelJa: 'スライド(上へ)', category: 'スライド', previewKind: 'slide-up', glyph: '↑' },
23
+ { id: 'slide-down', xfadeName: 'slidedown', labelJa: 'スライド(下へ)', category: 'スライド', previewKind: 'slide-down', glyph: '↓' },
24
+ { id: 'cover-left', xfadeName: 'coverleft', labelJa: 'カバー(左へ)', category: 'カバー', previewKind: 'cover-left', glyph: '←' },
25
+ { id: 'cover-right', xfadeName: 'coverright', labelJa: 'カバー(右へ)', category: 'カバー', previewKind: 'cover-right', glyph: '→' },
26
+ { id: 'cover-up', xfadeName: 'coverup', labelJa: 'カバー(上へ)', category: 'カバー', previewKind: 'cover-up', glyph: '↑' },
27
+ { id: 'cover-down', xfadeName: 'coverdown', labelJa: 'カバー(下へ)', category: 'カバー', previewKind: 'cover-down', glyph: '↓' },
28
+ { id: 'reveal-left', xfadeName: 'revealleft', labelJa: 'リビール(左へ)', category: 'リビール', previewKind: 'reveal-left', glyph: '←' },
29
+ { id: 'reveal-right', xfadeName: 'revealright', labelJa: 'リビール(右へ)', category: 'リビール', previewKind: 'reveal-right', glyph: '→' },
30
+ { id: 'reveal-down', xfadeName: 'revealdown', labelJa: '上からリビール', category: 'リビール', previewKind: 'reveal-down', glyph: '↓' },
31
+ { id: 'reveal-up', xfadeName: 'revealup', labelJa: '下からリビール', category: 'リビール', previewKind: 'reveal-up', glyph: '↑' },
32
+ { id: 'circle-open', xfadeName: 'circleopen', labelJa: 'サークル(開く)', category: '形状', previewKind: 'circle-open', glyph: '○' },
33
+ { id: 'circle-close', xfadeName: 'circleclose', labelJa: 'サークル(閉じる)', category: '形状', previewKind: 'circle-close', glyph: '●' },
34
+ { id: 'zoom-in', xfadeName: 'zoomin', labelJa: 'ズームイン', category: '変形', previewKind: 'zoom-in', glyph: '+' },
35
+ { id: 'squeeze-h', xfadeName: 'squeezeh', labelJa: 'スクイーズ(縦つぶし)', category: '変形', previewKind: 'squeeze-h', glyph: '↕' },
36
+ { id: 'squeeze-v', xfadeName: 'squeezev', labelJa: 'スクイーズ(横つぶし)', category: '変形', previewKind: 'squeeze-v', glyph: '↔' },
37
+ { id: 'blur', xfadeName: 'hblur', labelJa: 'ブラー', category: '質感', previewKind: 'blur', glyph: 'B' },
38
+ { id: 'pixelize', xfadeName: 'pixelize', labelJa: 'ピクセレート', category: '質感', previewKind: 'pixelize', glyph: 'P' }
39
+ ];
40
+ exports.TRANSITION_TYPE_IDS = exports.TRANSITION_VOCABULARY.map(entry => entry.id);
41
+ exports.TRANSITION_CATEGORIES = [...new Set(exports.TRANSITION_VOCABULARY.map(entry => entry.category))];
42
+ exports.TRANSITION_BY_ID = Object.fromEntries(exports.TRANSITION_VOCABULARY.map(entry => [entry.id, entry]));
43
+ function isTransitionType(value) {
44
+ return typeof value === 'string' && Object.prototype.hasOwnProperty.call(exports.TRANSITION_BY_ID, value);
45
+ }