Fix preview for anchor + carry chain runs with frame overlap

Record scene in-points minus chain frame_overlap between generative
units (not editorial subclips). Reseek on scene boundaries during
playback, guard the tick loop when video is missing, and skip pending
clips in pool sync.
This commit is contained in:
digital-garbage
2026-06-10 14:56:53 +03:00
parent aacb35ce4a
commit 8afa687b29
2 changed files with 32 additions and 9 deletions
+15 -5
View File
@@ -117,7 +117,7 @@
}
function _syncPool() {
const urls = new Set(_clips.map((c) => _urlFor(c.media)));
const urls = new Set(_clips.filter((c) => c.media).map((c) => _urlFor(c.media)));
urls.forEach((u) => _ensureVideo(u));
for (const [u, v] of [...pool]) {
if (urls.has(u)) continue;
@@ -207,11 +207,18 @@
const end = _currentClip.startSec + _currentClip.durationSec;
const next = _clips.find((c) => !c.pending && c.startSec >= end - 0.02 && c !== _currentClip);
if (!next) { _phSec = end; _pause(); return; }
const contiguous = _urlFor(next.media) === _urlFor(_currentClip.media)
&& Math.abs((next.inSec || 0) - ((_currentClip.inSec || 0) + _currentClip.durationSec)) < 0.05;
const prev = _currentClip;
const sameSource = next.media && prev.media
&& _urlFor(next.media) === _urlFor(prev.media);
const contiguous = sameSource
&& Math.abs((next.inSec || 0) - ((prev.inSec || 0) + prev.durationSec)) < 0.05;
_phSec = next.startSec;
_currentClip = next;
if (!contiguous) _goto(next, 0, _playing); // jump (different source / non-contiguous)
// Reseek on gen-unit / scene boundaries — chain carry scenes overlap in the source file.
const needSeek = !sameSource || !contiguous
|| (next.sceneId && prev.sceneId && next.sceneId !== prev.sceneId)
|| (next.ghostId && prev.ghostId && next.ghostId !== prev.ghostId);
if (needSeek) _goto(next, 0, _playing);
_notifyPh();
}
@@ -229,7 +236,10 @@
if (_playing) _raf = requestAnimationFrame(_tick);
return;
}
if (!_active) return;
if (!_active) {
if (_playing) _pause();
return;
}
const within = _active.currentTime - (_currentClip.inSec || 0);
if (within >= _currentClip.durationSec - 0.001) { _advance(); }
else { _phSec = _currentClip.startSec + Math.max(0, within); try { _applyFx(_currentClip, within); } catch (_) {} _notifyPh(); }
+17 -4
View File
@@ -552,20 +552,33 @@
state.sceneGhosts = (state.sceneGhosts || []).filter((g) => !ids.has(g.afterSceneId) && !ids.has(g.id));
}
// Pixel overlap between consecutive chain-sampler scenes (carry/i2v guide runs).
function _chainOverlapSec(runSceneCount) {
if (!runSceneCount || runSceneCount < 2) return 0;
const frames = +(state.project?.sampler_inputs?.frame_overlap ?? 16);
const fps = state.project?.frame_rate || 25;
return Math.max(0, frames / fps);
}
function _recordSegment(mediaList, targetSceneIds) {
if (!mediaList || !mediaList.length || !targetSceneIds || !targetSceneIds.length) return;
const primary = mediaList.find((m) => m.kind === "videos" || m.kind === "gifs") || mediaList[0];
let inAcc = 0;
const overlapSec = _chainOverlapSec(targetSceneIds.length);
let sourceEnd = 0;
let clearedRating = false;
_pruneGhostsAfterRegen(targetSceneIds);
for (const id of targetSceneIds) {
for (let i = 0; i < targetSceneIds.length; i++) {
const id = targetSceneIds[i];
const sc = scene(id); if (!sc) continue;
state.sceneRenders[id] = { media: primary, inSec: inAcc };
const prev = i > 0 ? scene(targetSceneIds[i - 1]) : null;
const sameUnit = prev && genUnitId(prev) === genUnitId(sc);
const inSec = i === 0 ? 0 : (sameUnit ? sourceEnd : Math.max(0, sourceEnd - overlapSec));
state.sceneRenders[id] = { media: primary, inSec };
// New render invalidates the prior RLHF rating — UI shows "— rate —"; server
// uses continue-refinement (session memory, no new rating) on the next Studio run.
const root = genUnitRoot(genUnitId(sc));
if (root && root.rating) { root.rating = ""; clearedRating = true; }
inAcc += sceneDurationSec(sc);
sourceEnd = inSec + sceneDurationSec(sc);
}
if (clearedRating) scheduleSaveSilent();
}