akari-video 0.1.88 → 0.1.90

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.88",
3
+ "version": "0.1.90",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.88",
3
+ "version": "0.1.90",
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 組み込みモジュールのみ)。 [akari-video npm vendor: bin/akari.mjs is reference-only. These CLI entrypoints are not included in the akari-video npm package. Use `akari doctor --json` and run the path reported in `render_cut.path`. Full installations provide it in a monorepo checkout, ~/.akari/app, /Applications/AKARI Video.app/Contents/Resources/packages, or %LOCALAPPDATA%\\Programs\\@akari-videoshell\\resources\\packages.]",
5
5
  "type": "module",
6
6
  "files": [
@@ -249,6 +249,7 @@ function applyCaptionRunsToHtml(html, displayText, runs) {
249
249
  const Segmenter = Intl.Segmenter;
250
250
  const segment = (value) => Array.from(new Segmenter(undefined, { granularity: 'grapheme' }).segment(value), item => item.segment);
251
251
  const chars = segment(displayText);
252
+ const defaultStroke = html.includes('akari-caption--single-line') ? '0 transparent' : '0.14em rgba(0,0,0,.9)';
252
253
  if (!runs.some(run => Number.isInteger(run?.from) && Number.isInteger(run?.to)
253
254
  && run.from >= 0 && run.to <= chars.length && run.from < run.to))
254
255
  return html;
@@ -277,12 +278,23 @@ function applyCaptionRunsToHtml(html, displayText, runs) {
277
278
  }
278
279
  const style = item.style;
279
280
  const css = ['display:inline-block', 'vertical-align:baseline', 'line-height:1'];
281
+ const scale = typeof style.scale === 'number' && Number.isFinite(style.scale) && style.scale > 0
282
+ ? style.scale : 1;
283
+ if (scale !== 1)
284
+ css.push(`font-size:${scale}em`);
285
+ // Re-resolve em values on the run: inherited computed lengths stay at the parent's px size.
286
+ if (typeof style.letter_spacing_em === 'number' && Number.isFinite(style.letter_spacing_em)) {
287
+ css.push(`letter-spacing:${style.letter_spacing_em}em`);
288
+ }
289
+ else if (scale !== 1) {
290
+ css.push('letter-spacing:var(--caption-letter-spacing,normal)');
291
+ }
292
+ if (scale !== 1)
293
+ css.push(`-webkit-text-stroke:var(--caption-webkit-text-stroke,var(--caption-stroke,${defaultStroke}))`);
280
294
  if (typeof style.color === 'string' && /^#(?:[\da-fA-F]{3}|[\da-fA-F]{6}|[\da-fA-F]{8})$/.test(style.color))
281
295
  css.push(`color:${style.color}`);
282
296
  if (Number.isInteger(style.font_weight) && style.font_weight >= 1 && style.font_weight <= 1000)
283
297
  css.push(`font-weight:${style.font_weight}`);
284
- if (typeof style.letter_spacing_em === 'number' && Number.isFinite(style.letter_spacing_em))
285
- css.push(`letter-spacing:${style.letter_spacing_em}em`);
286
298
  if (style.italic === true)
287
299
  css.push('font-style:italic');
288
300
  if (style.underline === true)
@@ -296,10 +308,10 @@ function applyCaptionRunsToHtml(html, displayText, runs) {
296
308
  ? style.baseline_shift_em : 0;
297
309
  const rotate = typeof style.rotate_deg === 'number' && Number.isFinite(style.rotate_deg)
298
310
  ? style.rotate_deg : 0;
299
- const scale = typeof style.scale === 'number' && Number.isFinite(style.scale) && style.scale > 0
300
- ? style.scale : 1;
301
- if (shift || rotate || scale !== 1)
302
- css.push(`transform:translateY(${shift}em) rotate(${rotate}deg) scale(${scale})`);
311
+ if (shift || rotate)
312
+ css.push(scale === 1
313
+ ? `transform:translateY(${shift}em) rotate(${rotate}deg) scale(1)`
314
+ : `transform:translateY(${shift / scale}em) rotate(${rotate}deg)`);
303
315
  return `<span class="akari-caption__run"${item.role ? ` data-role="${escape(item.role)}"` : ''} style="${css.join(';')}">${escape(character)}</span>`;
304
316
  }).join('');
305
317
  };
@@ -21,6 +21,7 @@ export * from './legacy-audio-view';
21
21
  export * from './retime';
22
22
  export * from './track-order';
23
23
  export * from './track-z';
24
+ export * from './media-planes';
24
25
  export * from './track-transition-compatibility';
25
26
  export * from './cut-adjacency';
26
27
  export * from './transition-vocabulary';
@@ -45,6 +45,7 @@ __exportStar(require("./legacy-audio-view"), exports);
45
45
  __exportStar(require("./retime"), exports);
46
46
  __exportStar(require("./track-order"), exports);
47
47
  __exportStar(require("./track-z"), exports);
48
+ __exportStar(require("./media-planes"), exports);
48
49
  __exportStar(require("./track-transition-compatibility"), exports);
49
50
  __exportStar(require("./cut-adjacency"), exports);
50
51
  __exportStar(require("./transition-vocabulary"), exports);
@@ -0,0 +1,2 @@
1
+ /** Partition decoded media around DOM stacking barriers. Browser safe: the preview embeds this function. */
2
+ export declare function partitionPreviewMediaPlanes(plan: any, summary: any): any[];
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.partitionPreviewMediaPlanes = partitionPreviewMediaPlanes;
4
+ /** Partition decoded media around DOM stacking barriers. Browser safe: the preview embeds this function. */
5
+ function partitionPreviewMediaPlanes(plan, summary) {
6
+ const tracks = Array.isArray(summary.timelineTracks) ? summary.timelineTracks : [];
7
+ const zOfTrack = (id) => tracks.findIndex((track) => track.id === id);
8
+ const zOfItem = (id, trackId, renderTrack) => Number.isInteger(summary.itemStackZ?.[String(id)]) ? summary.itemStackZ[String(id)]
9
+ : Number.isInteger(summary.trackStackZ?.[String(trackId)]) ? summary.trackStackZ[String(trackId)]
10
+ : Number.isInteger(renderTrack) ? renderTrack : zOfTrack(trackId);
11
+ const barriers = [...new Set([
12
+ ...(Array.isArray(summary.overlays) ? summary.overlays : [])
13
+ .map((item) => zOfItem(item.id, item.trackId, item.renderTrack)),
14
+ zOfItem(undefined, summary.captionTrackId),
15
+ ...Object.values(summary.itemStackZ ?? {}),
16
+ ...(Array.isArray(summary.barrierZ) ? summary.barrierZ : []),
17
+ ].filter(z => z >= 0))].sort((a, b) => a - b);
18
+ const bands = new Map();
19
+ const bandAt = (z) => {
20
+ const key = barriers.filter(barrier => barrier < z).length;
21
+ let band = bands.get(key);
22
+ if (!band) {
23
+ band = { key, zIndex: z, baseIndices: [], entries: [] };
24
+ bands.set(key, band);
25
+ }
26
+ band.zIndex = Math.max(band.zIndex, z);
27
+ return band;
28
+ };
29
+ const cutZ = (id) => {
30
+ const cut = summary.cutsById?.[id] ?? summary.cuts?.[Number(id.slice('cut-'.length))];
31
+ return zOfItem(cut?.id, cut?.trackId, cut?.renderTrack);
32
+ };
33
+ if (plan.base.length) {
34
+ const baseByBand = new Map();
35
+ plan.base.forEach((cut, index) => {
36
+ const band = bandAt(cutZ(cut.id));
37
+ const group = baseByBand.get(band.key) ?? { band, indices: [] };
38
+ group.indices.push(index);
39
+ baseByBand.set(band.key, group);
40
+ });
41
+ for (const { band, indices } of baseByBand.values()) {
42
+ // Transition pairs stay together. A lone upper cut needs a transparent layer.
43
+ if (band.key === 0 || indices.length > 1) {
44
+ band.baseIndices.push(...indices);
45
+ }
46
+ else {
47
+ const index = indices[0];
48
+ const cut = plan.base[index];
49
+ const visual = cut.visual;
50
+ band.entries.push({ baseIndex: index, spec: {
51
+ ...cut, kind: cut.kind ?? 'video', cutVisual: visual,
52
+ visual: { crop: visual.layerStyle?.crop ?? { x: 0, y: 0, width: 1, height: 1 },
53
+ perspective: null, transform: visual.transform },
54
+ mask: null, blend: 'normal', opacity: visual.opacity,
55
+ ...(visual.adjustLut ? { adjustLut: visual.adjustLut } : {}),
56
+ ...(visual.adjustFx ? { adjustFx: visual.adjustFx } : {})
57
+ } });
58
+ }
59
+ }
60
+ }
61
+ plan.layers.forEach((layer, index) => {
62
+ const declared = summary.layers?.find((item) => String(item.id) === layer.id);
63
+ const z = layer.cutVisual ? cutZ(layer.id)
64
+ : zOfItem(declared?.id ?? layer.id, declared?.trackId, declared?.renderTrack);
65
+ bandAt(z).entries.push({ layerIndex: index, spec: layer });
66
+ });
67
+ if (!bands.has(0))
68
+ bands.set(0, { key: 0, zIndex: -1, baseIndices: [], entries: [] });
69
+ return [...bands.values()].sort((a, b) => a.key - b.key);
70
+ }
@@ -98,6 +98,16 @@ export declare function putPlacedCaptionIntoCanvas(edit: EditableEditV2, caption
98
98
  at: number;
99
99
  duration: number;
100
100
  }, canvasId: string): ProjectItemV2;
101
+ /** P-3 と同じ字幕袋の除外を使い、明示字幕 item を映像段へ置く。 */
102
+ export declare function putPlacedCaptionIntoTrack(edit: EditableEditV2, caption: {
103
+ id: string;
104
+ at: number;
105
+ duration: number;
106
+ }, target: {
107
+ track?: string;
108
+ insertIndex?: number;
109
+ }): ProjectItemV2;
110
+ export declare function returnPlacedCaptionToBag(edit: EditableEditV2, captionId: string): void;
101
111
  export declare function takeOutOfCanvas(edit: EditableEditV2, itemIds: readonly string[]): ProjectItemV2[];
102
112
  export declare function insertItem(edit: EditableEditV2, target: string, item: MutableItem, index?: number): ProjectItemV2;
103
113
  export declare function removeItem(edit: EditableEditV2, id: string): ProjectItemV2;
@@ -14,6 +14,8 @@ exports.moveItem = moveItem;
14
14
  exports.createCanvas = createCanvas;
15
15
  exports.putIntoCanvas = putIntoCanvas;
16
16
  exports.putPlacedCaptionIntoCanvas = putPlacedCaptionIntoCanvas;
17
+ exports.putPlacedCaptionIntoTrack = putPlacedCaptionIntoTrack;
18
+ exports.returnPlacedCaptionToBag = returnPlacedCaptionToBag;
17
19
  exports.takeOutOfCanvas = takeOutOfCanvas;
18
20
  exports.insertItem = insertItem;
19
21
  exports.removeItem = removeItem;
@@ -284,6 +286,20 @@ function putPlacedCaptionIntoCanvas(edit, caption, canvasId) {
284
286
  }
285
287
  if (caption.at < absoluteAt(canvas))
286
288
  throw new Error('キャンバスより前の字幕は入れられません。');
289
+ const bag = ensurePlacedCaptionBag(edit, caption);
290
+ const exclude = bag.source.kind === 'captions' ? bag.source.exclude ?? [] : [];
291
+ if (bag.source.kind === 'captions' && !exclude.includes(caption.id))
292
+ bag.source.exclude = [...exclude, caption.id];
293
+ let id = `cap-${caption.id}`;
294
+ let serial = 1;
295
+ while (locate(edit, id))
296
+ id = `cap-${caption.id}-${serial++}`;
297
+ const item = { id, at: caption.at - absoluteAt(canvas), duration: caption.duration,
298
+ source: { kind: 'caption', path: 'captions.json', id: caption.id } };
299
+ ensureChildren(canvas.item).push(item);
300
+ return item;
301
+ }
302
+ function ensurePlacedCaptionBag(edit, caption) {
287
303
  let bag = allLocations(edit).find(location => location.item.source.kind === 'captions'
288
304
  && location.item.source.path === 'captions.json')?.item;
289
305
  if (!bag) {
@@ -300,6 +316,32 @@ function putPlacedCaptionIntoCanvas(edit, caption, canvasId) {
300
316
  }
301
317
  if (bag.duration < caption.at + caption.duration)
302
318
  bag.duration = caption.at + caption.duration;
319
+ return bag;
320
+ }
321
+ /** P-3 と同じ字幕袋の除外を使い、明示字幕 item を映像段へ置く。 */
322
+ function putPlacedCaptionIntoTrack(edit, caption, target) {
323
+ if (!Number.isInteger(caption.at) || caption.at < 0
324
+ || !Number.isInteger(caption.duration) || caption.duration <= 0)
325
+ throw new Error('字幕の時刻が不正です。');
326
+ const existing = allLocations(edit).find(location => location.item.source.kind === 'caption'
327
+ && location.item.source.id === caption.id);
328
+ if (existing) {
329
+ const track = target.insertIndex === undefined ? target.track
330
+ : createTrackAt(edit, 'visual', target.insertIndex).id;
331
+ if (!track)
332
+ throw new Error('置き先の段がありません。');
333
+ if (requireTrack(edit, track).lane !== 'visual')
334
+ throw new Error('置き先は映像トラックにしてください。');
335
+ updateItem(edit, existing.item.id, {
336
+ at: existing.item.at + caption.at - absoluteAt(existing), duration: caption.duration
337
+ });
338
+ return moveItem(edit, existing.item.id, { track });
339
+ }
340
+ const track = target.insertIndex === undefined ? tracksOf(edit).find(candidate => candidate.id === target.track)
341
+ : createTrackAt(edit, 'visual', target.insertIndex);
342
+ if (!track || track.lane !== 'visual')
343
+ throw new Error('置き先は映像トラックにしてください。');
344
+ const bag = ensurePlacedCaptionBag(edit, caption);
303
345
  const exclude = bag.source.kind === 'captions' ? bag.source.exclude ?? [] : [];
304
346
  if (bag.source.kind === 'captions' && !exclude.includes(caption.id))
305
347
  bag.source.exclude = [...exclude, caption.id];
@@ -307,10 +349,22 @@ function putPlacedCaptionIntoCanvas(edit, caption, canvasId) {
307
349
  let serial = 1;
308
350
  while (locate(edit, id))
309
351
  id = `cap-${caption.id}-${serial++}`;
310
- const item = { id, at: caption.at - absoluteAt(canvas), duration: caption.duration,
352
+ const item = { id, at: caption.at, duration: caption.duration,
311
353
  source: { kind: 'caption', path: 'captions.json', id: caption.id } };
312
- ensureChildren(canvas.item).push(item);
313
- return item;
354
+ return insertItem(edit, track.id, item);
355
+ }
356
+ function returnPlacedCaptionToBag(edit, captionId) {
357
+ const item = allLocations(edit).find(location => location.item.source.kind === 'caption'
358
+ && location.item.source.id === captionId);
359
+ if (item)
360
+ removeItem(edit, item.item.id);
361
+ for (const location of allLocations(edit)) {
362
+ if (location.item.source.kind !== 'captions')
363
+ continue;
364
+ const exclude = location.item.source.exclude ?? [];
365
+ if (exclude.includes(captionId))
366
+ location.item.source.exclude = exclude.filter(id => id !== captionId);
367
+ }
314
368
  }
315
369
  function takeOutOfCanvas(edit, itemIds) {
316
370
  return [...new Set(itemIds)].map(id => {
@@ -1002,7 +1002,7 @@ function marqueeHits(candidates, rect) {
1002
1002
  return;
1003
1003
  }
1004
1004
  if (!selectedOverlay && selectedId && selectionTree().length) {
1005
- if (!treeNode(selectedId)) { clearSelection(); publishScopedSelection(false); return; }
1005
+ if (!treeNode(selectedId)) { clearSelection(); publishScopedSelection(); return; }
1006
1006
  const replacement = containerById(selectedId);
1007
1007
  if (replacement) {
1008
1008
  selectedOverlay = replacement;
@@ -1063,6 +1063,7 @@ function marqueeHits(candidates, rect) {
1063
1063
  if (activeRotate?.container === container) cancelRotate();
1064
1064
  if (activeEdit?.container === container) void commitEdit();
1065
1065
  clearSelection();
1066
+ publishScopedSelection();
1066
1067
  }
1067
1068
 
1068
1069
  function selectOverlay(container) {
@@ -1202,7 +1203,10 @@ function marqueeHits(candidates, rect) {
1202
1203
  startSelectionTracking();
1203
1204
  } else if (next.selectId === null) clearSelection();
1204
1205
  else if (!selectOverlay(containerById(next.selectId))) {
1205
- if (!node?.lazy || lazyBagForScope(tree, scopeId) !== node.parentId) return false;
1206
+ if (!node?.lazy || lazyBagForScope(tree, scopeId) !== node.parentId) {
1207
+ scopeId = previousScopeId;
1208
+ return false;
1209
+ }
1206
1210
  clearSelection();
1207
1211
  selectedId = node.id;
1208
1212
  selectedIds = [node.id];
@@ -2737,7 +2741,7 @@ function marqueeHits(candidates, rect) {
2737
2741
  + '[data-akari-interaction], [data-akari-ui="preview-scope-breadcrumb"], '
2738
2742
  + '.caption-row-plate, #caption-select-box, #layer-select-box, #cut-select-box, '
2739
2743
  + '.transport-controls, [role="menu"]');
2740
- if (outside && !protectedTarget) { clearSelection(); return; }
2744
+ if (outside && !protectedTarget) { clearSelection(); publishScopedSelection(); return; }
2741
2745
  }
2742
2746
  flushNudge();
2743
2747
  hideHover();
@@ -88,6 +88,16 @@ npm run lint # eslint "extensions/*/src/**
88
88
 
89
89
  ### 既知の地雷(L1)
90
90
 
91
+ 0. **`ELECTRON_RUN_AS_NODE=1` が環境に残っていると Electron が即死する**(2026-09-27 実測・
92
+ `library-tile-cards` レーン)。この変数が立っていると Electron は GUI ではなく Node として
93
+ 起動するため、`app` が未定義のまま
94
+ ```
95
+ Cannot read properties of undefined (reading 'requestSingleInstanceLock')
96
+ ```
97
+ で落ちる。L0 §地雷 3 の「パッケージ版でのパス解決」検証(`ELECTRON_RUN_AS_NODE=1` +
98
+ `cwd=/`)を先に回した同じシェルで L1 に入ると踏む。起動時に
99
+ `env -u ELECTRON_RUN_AS_NODE <electron のパス> …` で必ず外すこと。
100
+
91
101
  1. **拡張ディレクトリ内のネスト `node_modules`(この層で最も踏まれている地雷)**: `apps/shell/extensions/<ext>/node_modules/` に `@theia/core` の 2 つ目の実体が入ると、esbuild は realpath 単位でモジュールを束ねるため 1 つの `bundle.js` に `frontend-application-config-provider` が 2 本入る。`src-gen/frontend/index.js` は片方に `set()` し、拡張側はもう片方から `get()` するので、フロントエンドが
92
102
  ```
93
103
  Failed to start the frontend application.