vivid-editor-core 0.3.0 → 0.3.1
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/dist/ai-orchestrator.js +63 -58
- package/package.json +1 -1
package/dist/ai-orchestrator.js
CHANGED
|
@@ -348,6 +348,10 @@ export function executeAiCommands(commands, snapshot, actions) {
|
|
|
348
348
|
for (let i = 0; i < beatsAbsolute.length; i += beatInterval) {
|
|
349
349
|
cutPoints.push(beatsAbsolute[i]);
|
|
350
350
|
}
|
|
351
|
+
// Pictures from the first note: the first beat is often a few ms in
|
|
352
|
+
// (60 ms on a MiniMax track), which left a black flash at the start.
|
|
353
|
+
if (mode === 'redistribute')
|
|
354
|
+
cutPoints[0] = audioStart;
|
|
351
355
|
// Always end at audio end so the last segment fills the music.
|
|
352
356
|
if (cutPoints[cutPoints.length - 1] < audioEnd - 50)
|
|
353
357
|
cutPoints.push(audioEnd);
|
|
@@ -366,69 +370,70 @@ export function executeAiCommands(commands, snapshot, actions) {
|
|
|
366
370
|
const newCanvasObjects = [];
|
|
367
371
|
const trackId = targetVisuals[0].trackId;
|
|
368
372
|
const now = Date.now();
|
|
369
|
-
//
|
|
370
|
-
//
|
|
373
|
+
// Every cut interval gets exactly one piece of its full length, so
|
|
374
|
+
// nothing is left black between two cuts or before the end of the
|
|
375
|
+
// music. Walking the tape with a cursor:
|
|
376
|
+
// - the rest of the current segment covers the interval → take it;
|
|
377
|
+
// - it covers at least MIN_RATE of it → take it slowed down a little
|
|
378
|
+
// (a still image just stays on screen longer);
|
|
379
|
+
// - it is shorter → drop that leftover and try the next segment;
|
|
380
|
+
// - the tape runs out → start again from the first segment.
|
|
381
|
+
// If no segment can fill an interval even after a full loop, the
|
|
382
|
+
// longest one is slowed down as far as the player allows.
|
|
383
|
+
const MIN_RATE = 0.8;
|
|
384
|
+
const EPS = 1;
|
|
371
385
|
let tapeIdx = 0;
|
|
372
|
-
let
|
|
386
|
+
let cursor = 0;
|
|
387
|
+
const advance = () => { tapeIdx = (tapeIdx + 1) % tape.length; cursor = 0; };
|
|
388
|
+
const pick = (segmentMs) => {
|
|
389
|
+
for (let tries = 0; tries <= tape.length; tries++) {
|
|
390
|
+
const seg = tape[tapeIdx];
|
|
391
|
+
const avail = seg.durationMs - cursor;
|
|
392
|
+
const still = seg.sourceClip.mediaType === 'image';
|
|
393
|
+
if (avail >= segmentMs - EPS || (avail > EPS && (still || avail >= segmentMs * MIN_RATE))) {
|
|
394
|
+
const used = Math.min(avail, segmentMs);
|
|
395
|
+
const piece = {
|
|
396
|
+
seg,
|
|
397
|
+
offsetMs: seg.offsetMs + cursor,
|
|
398
|
+
rate: still || used >= segmentMs - EPS ? undefined : used / segmentMs,
|
|
399
|
+
};
|
|
400
|
+
cursor += used;
|
|
401
|
+
if (cursor >= seg.durationMs - EPS)
|
|
402
|
+
advance();
|
|
403
|
+
return piece;
|
|
404
|
+
}
|
|
405
|
+
advance();
|
|
406
|
+
}
|
|
407
|
+
const seg = tape.reduce((a, b) => (b.durationMs > a.durationMs ? b : a));
|
|
408
|
+
tapeIdx = tape.indexOf(seg);
|
|
409
|
+
advance();
|
|
410
|
+
return { seg, offsetMs: seg.offsetMs, rate: Math.max(0.25, seg.durationMs / segmentMs) };
|
|
411
|
+
};
|
|
373
412
|
for (let i = 0; i < cutPoints.length - 1; i++) {
|
|
374
413
|
const segmentMs = cutPoints[i + 1] - cutPoints[i];
|
|
375
414
|
if (segmentMs <= 0)
|
|
376
415
|
continue;
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
url: seg.sourceClip.url,
|
|
399
|
-
startMs: cutPoints[i],
|
|
400
|
-
durationMs: take,
|
|
401
|
-
originalDurationMs: seg.sourceClip.originalDurationMs,
|
|
402
|
-
sourceOffsetMs: seg.offsetMs + tapeCursorInSegment,
|
|
403
|
-
mediaType: seg.sourceClip.mediaType,
|
|
404
|
-
trackId,
|
|
405
|
-
});
|
|
406
|
-
newCanvasObjects.push({
|
|
407
|
-
id: `obj-${newId}`,
|
|
408
|
-
clipId: newId,
|
|
409
|
-
x: 0, y: 0, w: 1, h: 1,
|
|
410
|
-
rotation: 0,
|
|
411
|
-
});
|
|
412
|
-
placed = true;
|
|
413
|
-
}
|
|
414
|
-
else {
|
|
415
|
-
// Subsequent piece within the same beat — extend the
|
|
416
|
-
// previous clip's duration if and only if it's the same
|
|
417
|
-
// source asset; otherwise start a fresh clip nested in
|
|
418
|
-
// the same beat. For simplicity, we only support a
|
|
419
|
-
// single asset per beat segment (truncate trailing tape
|
|
420
|
-
// and snap to next beat).
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
tapeCursorInSegment += take;
|
|
424
|
-
remaining -= take;
|
|
425
|
-
if (tapeCursorInSegment >= seg.durationMs) {
|
|
426
|
-
tapeIdx++;
|
|
427
|
-
tapeCursorInSegment = 0;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
if (tapeIdx >= tape.length)
|
|
431
|
-
break; // tape exhausted, stop emitting
|
|
416
|
+
const { seg, offsetMs, rate } = pick(segmentMs);
|
|
417
|
+
const newId = `clip-cb-${now}-${i}`;
|
|
418
|
+
newClips.push({
|
|
419
|
+
id: newId,
|
|
420
|
+
assetId: seg.sourceClip.assetId,
|
|
421
|
+
name: seg.sourceClip.name,
|
|
422
|
+
url: seg.sourceClip.url,
|
|
423
|
+
startMs: cutPoints[i],
|
|
424
|
+
durationMs: segmentMs,
|
|
425
|
+
originalDurationMs: seg.sourceClip.originalDurationMs,
|
|
426
|
+
sourceOffsetMs: offsetMs,
|
|
427
|
+
mediaType: seg.sourceClip.mediaType,
|
|
428
|
+
trackId,
|
|
429
|
+
...(rate !== undefined ? { playbackRate: rate } : {}),
|
|
430
|
+
});
|
|
431
|
+
newCanvasObjects.push({
|
|
432
|
+
id: `obj-${newId}`,
|
|
433
|
+
clipId: newId,
|
|
434
|
+
x: 0, y: 0, w: 1, h: 1,
|
|
435
|
+
rotation: 0,
|
|
436
|
+
});
|
|
432
437
|
}
|
|
433
438
|
// Preserve audio + non-target clips intact.
|
|
434
439
|
const preservedClips = snapshot.timelineClips.filter((c) => c.mediaType === 'audio' || c.mediaType === 'color' ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vivid-editor-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "VIVID video editor core: project file schema, editor store and AI command orchestrator — shared by the vividai.tv editor and vivid-mcp.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|