diff --git a/movie_editor/frontend/player.js b/movie_editor/frontend/player.js index ad8ac59c..141f0e3e 100644 --- a/movie_editor/frontend/player.js +++ b/movie_editor/frontend/player.js @@ -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); diff --git a/movie_editor/frontend/store.js b/movie_editor/frontend/store.js index c0782240..3458650a 100644 --- a/movie_editor/frontend/store.js +++ b/movie_editor/frontend/store.js @@ -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, diff --git a/movie_editor/frontend/view_bus.js b/movie_editor/frontend/view_bus.js index 79f5c19d..0de957dd 100644 --- a/movie_editor/frontend/view_bus.js +++ b/movie_editor/frontend/view_bus.js @@ -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,