akari-video 0.1.59 → 0.1.61
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.
|
|
3
|
+
"version": "0.1.61",
|
|
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.
|
|
3
|
+
"version": "0.1.61",
|
|
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": [
|
|
@@ -6,6 +6,7 @@ const audio_ownership_1 = require("./audio-ownership");
|
|
|
6
6
|
const ducking_1 = require("./ducking");
|
|
7
7
|
const envelope_1 = require("./envelope");
|
|
8
8
|
const timeline_map_1 = require("./timeline-map");
|
|
9
|
+
const edit_store_1 = require("./edit-store");
|
|
9
10
|
/**
|
|
10
11
|
* 解決済みタイムライン尺・正規化済み audio 宣言・デコード実尺を、Web Audio がそのまま
|
|
11
12
|
* 消費できる予定表へ落とす。fetch/decode/時計は扱わないため、実時間と OfflineAudioContext
|
|
@@ -305,21 +306,45 @@ function projectSpeechDeclarations(cuts, options) {
|
|
|
305
306
|
return { ...cut, out: cut.out + holdSec * speed };
|
|
306
307
|
});
|
|
307
308
|
const map = (0, timeline_map_1.buildTimelineMap)(virtualCuts, { fps });
|
|
309
|
+
// Keep every clip's window, including clips absent from the visual winner map.
|
|
310
|
+
// Match buildTimelineMap's usable-cut filtering before resolving track cursors.
|
|
311
|
+
const usable = virtualCuts.map((cut, index) => ({ cut, index })).filter(({ cut }) => Number.isFinite(cut.in) && Number.isFinite(cut.out) && cut.in < cut.out);
|
|
312
|
+
const clipWindows = new Map((0, edit_store_1.computeCutTrackSegments)(usable.map(entry => entry.cut)).map(segment => [
|
|
313
|
+
usable[segment.index].index,
|
|
314
|
+
{ start: segment.at, end: segment.end, cutTimelineStart: segment.at }
|
|
315
|
+
]));
|
|
316
|
+
for (const window of map.transitionWindows) {
|
|
317
|
+
for (const participant of [window.outgoing, window.incoming]) {
|
|
318
|
+
const clip = clipWindows.get(participant.cutIndex);
|
|
319
|
+
const cut = normalizedCuts[participant.cutIndex];
|
|
320
|
+
if (!clip || !cut || typeof participant.in !== 'number')
|
|
321
|
+
continue;
|
|
322
|
+
const speed = finitePositive(cut.speed) ? cut.speed : 1;
|
|
323
|
+
// Transition slices retain the source mapping after hidden-handle adjustment,
|
|
324
|
+
// even when another track completely covers both participants.
|
|
325
|
+
clip.cutTimelineStart = participant.outStart - (participant.in - cut.in) / speed;
|
|
326
|
+
}
|
|
327
|
+
const outgoing = clipWindows.get(window.outgoing.cutIndex);
|
|
328
|
+
const incoming = clipWindows.get(window.incoming.cutIndex);
|
|
329
|
+
if (outgoing)
|
|
330
|
+
outgoing.end = Math.max(outgoing.end, window.end);
|
|
331
|
+
// scheduleSpeech adds this incoming overlap back via crossfadeInSec. Keep the
|
|
332
|
+
// existing declaration boundary so the transition is not applied twice.
|
|
333
|
+
if (incoming)
|
|
334
|
+
incoming.start = Math.max(incoming.start, window.end);
|
|
335
|
+
}
|
|
308
336
|
const declarations = [];
|
|
309
|
-
for (const
|
|
310
|
-
|
|
311
|
-
continue;
|
|
312
|
-
const cut = normalizedCuts[segment.cutIndex];
|
|
337
|
+
for (const [cutIndex, clip] of clipWindows) {
|
|
338
|
+
const cut = normalizedCuts[cutIndex];
|
|
313
339
|
if (!cut || typeof cut.src !== 'string' || !cut.src)
|
|
314
340
|
continue;
|
|
315
341
|
if (!(0, audio_ownership_1.isCutAudioAudible)(cut))
|
|
316
342
|
continue;
|
|
317
343
|
const speed = finitePositive(cut.speed) ? cut.speed : 1;
|
|
318
|
-
const
|
|
319
|
-
const cutTimelineStart = segment.outStart - (segmentIn - cut.in) / speed;
|
|
344
|
+
const cutTimelineStart = clip.cutTimelineStart;
|
|
320
345
|
const baseDurationSec = Math.max(0, cut.out - cut.in) / speed;
|
|
321
346
|
const gainDb = speechGainDb(cut);
|
|
322
|
-
const baseId =
|
|
347
|
+
const baseId = speechBaseId(cut, cutIndex);
|
|
323
348
|
const holdSec = freezeDuration(cut.freeze);
|
|
324
349
|
if (!(holdSec > 0)) {
|
|
325
350
|
appendSpeechIntersection(declarations, {
|
|
@@ -327,8 +352,8 @@ function projectSpeechDeclarations(cuts, options) {
|
|
|
327
352
|
sourceIn: cut.in,
|
|
328
353
|
outputStart: cutTimelineStart,
|
|
329
354
|
outputEnd: cutTimelineStart + baseDurationSec,
|
|
330
|
-
|
|
331
|
-
|
|
355
|
+
clipStart: clip.start,
|
|
356
|
+
clipEnd: clip.end,
|
|
332
357
|
track: cut.track
|
|
333
358
|
});
|
|
334
359
|
continue;
|
|
@@ -340,8 +365,8 @@ function projectSpeechDeclarations(cuts, options) {
|
|
|
340
365
|
sourceIn: cut.in,
|
|
341
366
|
outputStart: cutTimelineStart,
|
|
342
367
|
outputEnd: cutTimelineStart + freezeAtSec,
|
|
343
|
-
|
|
344
|
-
|
|
368
|
+
clipStart: clip.start,
|
|
369
|
+
clipEnd: clip.end,
|
|
345
370
|
track: cut.track
|
|
346
371
|
});
|
|
347
372
|
appendSpeechIntersection(declarations, {
|
|
@@ -349,8 +374,8 @@ function projectSpeechDeclarations(cuts, options) {
|
|
|
349
374
|
sourceIn: freezeSourceIn,
|
|
350
375
|
outputStart: cutTimelineStart + freezeAtSec + holdSec,
|
|
351
376
|
outputEnd: cutTimelineStart + baseDurationSec + holdSec,
|
|
352
|
-
|
|
353
|
-
|
|
377
|
+
clipStart: clip.start,
|
|
378
|
+
clipEnd: clip.end,
|
|
354
379
|
track: cut.track
|
|
355
380
|
});
|
|
356
381
|
}
|
|
@@ -381,8 +406,8 @@ function speechBaseId(cut, index) {
|
|
|
381
406
|
return cut && typeof cut.id === 'string' && cut.id ? cut.id : `cut-${index}`;
|
|
382
407
|
}
|
|
383
408
|
function appendSpeechIntersection(declarations, input) {
|
|
384
|
-
const atSec = Math.max(input.outputStart, input.
|
|
385
|
-
const endSec = Math.min(input.outputEnd, input.
|
|
409
|
+
const atSec = Math.max(input.outputStart, input.clipStart);
|
|
410
|
+
const endSec = Math.min(input.outputEnd, input.clipEnd);
|
|
386
411
|
if (!(endSec > atSec))
|
|
387
412
|
return;
|
|
388
413
|
const inSec = input.sourceIn + (atSec - input.outputStart) * input.speed;
|
|
@@ -1452,18 +1452,34 @@ var AkariEditKernel = (() => {
|
|
|
1452
1452
|
return { ...cut, out: cut.out + holdSec * speed };
|
|
1453
1453
|
});
|
|
1454
1454
|
const map = buildTimelineMap(virtualCuts, { fps });
|
|
1455
|
+
const usable = virtualCuts.map((cut, index) => ({ cut, index })).filter(({ cut }) => Number.isFinite(cut.in) && Number.isFinite(cut.out) && cut.in < cut.out);
|
|
1456
|
+
const clipWindows = new Map(computeCutTrackSegments(usable.map((entry) => entry.cut)).map((segment) => [
|
|
1457
|
+
usable[segment.index].index,
|
|
1458
|
+
{ start: segment.at, end: segment.end, cutTimelineStart: segment.at }
|
|
1459
|
+
]));
|
|
1460
|
+
for (const window of map.transitionWindows) {
|
|
1461
|
+
for (const participant of [window.outgoing, window.incoming]) {
|
|
1462
|
+
const clip = clipWindows.get(participant.cutIndex);
|
|
1463
|
+
const cut = normalizedCuts[participant.cutIndex];
|
|
1464
|
+
if (!clip || !cut || typeof participant.in !== "number") continue;
|
|
1465
|
+
const speed = finitePositive(cut.speed) ? cut.speed : 1;
|
|
1466
|
+
clip.cutTimelineStart = participant.outStart - (participant.in - cut.in) / speed;
|
|
1467
|
+
}
|
|
1468
|
+
const outgoing = clipWindows.get(window.outgoing.cutIndex);
|
|
1469
|
+
const incoming = clipWindows.get(window.incoming.cutIndex);
|
|
1470
|
+
if (outgoing) outgoing.end = Math.max(outgoing.end, window.end);
|
|
1471
|
+
if (incoming) incoming.start = Math.max(incoming.start, window.end);
|
|
1472
|
+
}
|
|
1455
1473
|
const declarations = [];
|
|
1456
|
-
for (const
|
|
1457
|
-
|
|
1458
|
-
const cut = normalizedCuts[segment.cutIndex];
|
|
1474
|
+
for (const [cutIndex, clip] of clipWindows) {
|
|
1475
|
+
const cut = normalizedCuts[cutIndex];
|
|
1459
1476
|
if (!cut || typeof cut.src !== "string" || !cut.src) continue;
|
|
1460
1477
|
if (!isCutAudioAudible(cut)) continue;
|
|
1461
1478
|
const speed = finitePositive(cut.speed) ? cut.speed : 1;
|
|
1462
|
-
const
|
|
1463
|
-
const cutTimelineStart = segment.outStart - (segmentIn - cut.in) / speed;
|
|
1479
|
+
const cutTimelineStart = clip.cutTimelineStart;
|
|
1464
1480
|
const baseDurationSec = Math.max(0, cut.out - cut.in) / speed;
|
|
1465
1481
|
const gainDb = speechGainDb(cut);
|
|
1466
|
-
const baseId =
|
|
1482
|
+
const baseId = speechBaseId(cut, cutIndex);
|
|
1467
1483
|
const holdSec = freezeDuration(cut.freeze);
|
|
1468
1484
|
if (!(holdSec > 0)) {
|
|
1469
1485
|
appendSpeechIntersection(declarations, {
|
|
@@ -1474,8 +1490,8 @@ var AkariEditKernel = (() => {
|
|
|
1474
1490
|
sourceIn: cut.in,
|
|
1475
1491
|
outputStart: cutTimelineStart,
|
|
1476
1492
|
outputEnd: cutTimelineStart + baseDurationSec,
|
|
1477
|
-
|
|
1478
|
-
|
|
1493
|
+
clipStart: clip.start,
|
|
1494
|
+
clipEnd: clip.end,
|
|
1479
1495
|
track: cut.track
|
|
1480
1496
|
});
|
|
1481
1497
|
continue;
|
|
@@ -1490,8 +1506,8 @@ var AkariEditKernel = (() => {
|
|
|
1490
1506
|
sourceIn: cut.in,
|
|
1491
1507
|
outputStart: cutTimelineStart,
|
|
1492
1508
|
outputEnd: cutTimelineStart + freezeAtSec,
|
|
1493
|
-
|
|
1494
|
-
|
|
1509
|
+
clipStart: clip.start,
|
|
1510
|
+
clipEnd: clip.end,
|
|
1495
1511
|
track: cut.track
|
|
1496
1512
|
});
|
|
1497
1513
|
appendSpeechIntersection(declarations, {
|
|
@@ -1502,8 +1518,8 @@ var AkariEditKernel = (() => {
|
|
|
1502
1518
|
sourceIn: freezeSourceIn,
|
|
1503
1519
|
outputStart: cutTimelineStart + freezeAtSec + holdSec,
|
|
1504
1520
|
outputEnd: cutTimelineStart + baseDurationSec + holdSec,
|
|
1505
|
-
|
|
1506
|
-
|
|
1521
|
+
clipStart: clip.start,
|
|
1522
|
+
clipEnd: clip.end,
|
|
1507
1523
|
track: cut.track
|
|
1508
1524
|
});
|
|
1509
1525
|
}
|
|
@@ -1530,8 +1546,8 @@ var AkariEditKernel = (() => {
|
|
|
1530
1546
|
return cut && typeof cut.id === "string" && cut.id ? cut.id : `cut-${index}`;
|
|
1531
1547
|
}
|
|
1532
1548
|
function appendSpeechIntersection(declarations, input) {
|
|
1533
|
-
const atSec = Math.max(input.outputStart, input.
|
|
1534
|
-
const endSec = Math.min(input.outputEnd, input.
|
|
1549
|
+
const atSec = Math.max(input.outputStart, input.clipStart);
|
|
1550
|
+
const endSec = Math.min(input.outputEnd, input.clipEnd);
|
|
1535
1551
|
if (!(endSec > atSec)) return;
|
|
1536
1552
|
const inSec = input.sourceIn + (atSec - input.outputStart) * input.speed;
|
|
1537
1553
|
const outSec = inSec + (endSec - atSec) * input.speed;
|