Fix multi-shot chain preview inSec for scenes 2+.

Preview now uses the same render.inSec + source_in mapping as export for
every scene in a shared chain file, including converted video clips.
This commit is contained in:
digital-garbage
2026-06-11 22:04:30 +03:00
parent cc464395ba
commit 393edf93ff
3 changed files with 22 additions and 9 deletions
+9 -5
View File
@@ -182,14 +182,14 @@
sceneId: sc.id,
startSec,
durationSec: clipDur,
inSec: sc.source_in || 0,
inSec: S.clipSourceInSec ? S.clipSourceInSec(sc, r) : (sc.source_in || 0),
fx,
vol,
});
continue;
}
const inSec = (sc.source_in || 0) + (r?.inSec || 0);
const inSec = S.clipSourceInSec ? S.clipSourceInSec(sc, r) : ((sc.source_in || 0) + (r?.inSec || 0));
if (r && r.media) {
out.push({
media: r.media, sceneId: sc.id, startSec, durationSec: dur, inSec,
@@ -567,14 +567,18 @@
return;
}
const offset = _phSec - clip.startSec;
const sameClip = _currentClip === clip
|| (_currentClip && clip && _currentClip.sceneId === clip.sceneId && _sameVideoUrl(_currentClip, clip));
const sameVideo = !clip.pending && _active && _sameVideoUrl(_currentClip, clip)
const prevClip = _currentClip;
const sameClip = prevClip === clip
|| (prevClip && clip && prevClip.sceneId === clip.sceneId && _sameVideoUrl(prevClip, clip));
const sameVideo = !clip.pending && _active && _sameVideoUrl(prevClip || clip, clip)
&& (clip.media || clip.binUrl);
if (sameVideo && _active.readyState >= 1) {
const crossScene = prevClip && clip && prevClip !== clip
&& (prevClip.sceneId !== clip.sceneId || Math.abs((prevClip.inSec || 0) - (clip.inSec || 0)) > 0.05);
_currentClip = clip;
_applyClipUi(clip);
const target = (clip.inSec || 0) + Math.max(0, offset);
if (crossScene) _flushVideoSeek(true);
_scheduleVideoSeek(target, clip, offset);
} else if (!sameClip || clip.pending) {
_flushVideoSeek(true);
+12 -4
View File
@@ -1492,7 +1492,7 @@
if (separatedTrackForScene(sceneId)) return;
_historyRecord();
const idx = state.project.scenes.indexOf(sc);
const inSec = (r.inSec || 0) + (sc.source_in || 0);
const inSec = clipSourceInSec(sc, r);
const dur = sc.source_dur != null ? sc.source_dur : sceneDurationSec(sc);
const savedVol = sc.audio_volume != null ? sc.audio_volume : 1;
state.project.audio_tracks = state.project.audio_tracks || [];
@@ -2312,6 +2312,14 @@
await _saveBlobAs(API.mediaUrl(mediaId), name, { types });
}
// Source in-point for preview/export: chain segment offset + per-scene trim.
function clipSourceInSec(sc, render) {
if (!sc) return 0;
const trim = sc.source_in || 0;
if (render?.media) return trim + (render.inSec || 0);
return trim;
}
// Ordered list of rendered clips in timeline order: {media, inSec, durationSec} for
// each non-excluded scene that has a render. This is what plays/exports — so splits
// and deletes are honoured (deleted scenes simply aren't here).
@@ -2324,7 +2332,7 @@
const fps = (sc.fps_mode !== "project" && sc.fps != null ? sc.fps : p.frame_rate) || 25;
const tFrames = sc.transition_frames || 0;
const base = {
inSec: (r?.inSec || 0) + (sc.source_in || 0),
inSec: clipSourceInSec(sc, r),
durationSec: sc.source_dur != null ? sc.source_dur : sceneDurationSec(sc),
fx: sc.effects || {}, fps,
w: (sc.width != null ? sc.width : p.width) || null,
@@ -2431,7 +2439,7 @@
filename: r.media.filename,
subfolder: r.media.subfolder || "",
type: r.media.type || "output",
in: (r.inSec || 0) + (sc.source_in || 0),
in: clipSourceInSec(sc, r),
dur: sc.source_dur != null ? sc.source_dur : sceneDurationSec(sc),
},
name: `${proj}_scene${idx >= 0 ? idx + 1 : "x"}_${_stamp()}.mp4`,
@@ -2866,7 +2874,7 @@
sceneCharacterIds, toggleSceneCharacter,
genUnitId, isGenSubclip, genUnitRoot, genUnitSceneIds,
renderPromptForScene, renderPromptMismatch, renderAnchorMismatch, renderMediaLabel, renderIsStale,
buildPreviewSegments, previewTotalSec, segmentDurationSec, sceneDurationSec,
buildPreviewSegments, previewTotalSec, segmentDurationSec, sceneDurationSec, clipSourceInSec,
addAudioTrack, updateAudioTrack, removeAudioTrack, separateSceneAudio, separatedTrackForScene,
separatedTrackMedia, separatedTrackInSec, separatedTrackDurSec,
isOverlayAudioTrack, isSeparatedAudioTrack,
+1
View File
@@ -81,6 +81,7 @@
scenes: (p.scenes || []).map((s) => [
s.id, s.excluded, s.frames, s.fps, s.frames_mode, s.fps_mode,
s.audio_volume, s.audio_separated, s.source_in, s.source_dur, s.effects,
s.source?.type, s.source?.media_ref,
]),
renders: st.sceneRenders,
ghosts: st.sceneGhosts,