From 715106d308855abe1885c8491352c299cfaefab5 Mon Sep 17 00:00:00 2001 From: Brian Matherly Date: Wed, 29 Jul 2026 20:42:49 -0500 Subject: [PATCH 1/2] Fix copy/paste transition causes bad audio As reported here: https://forum.shotcut.org/t/bug-with-dissolve-on-copied-track/51766/ --- src/docks/timelinedock.cpp | 173 ++++++++++++++++++++++++++++++++++--- 1 file changed, 163 insertions(+), 10 deletions(-) diff --git a/src/docks/timelinedock.cpp b/src/docks/timelinedock.cpp index 03ddc7c40e..664159b6f9 100644 --- a/src/docks/timelinedock.cpp +++ b/src/docks/timelinedock.cpp @@ -2659,14 +2659,39 @@ void TimelineDock::append(int trackIndex) if (srcTrack) { const auto trackIndex = currentTrack() + mltTrackIndex; addTrackIfNeeded(trackIndex, srcTrack.get()); + int pendingTransitionDuration = 0; + int pendingTransitionRightIn = -1; + Mlt::Producer pendingTransitionRightClip; // Insert the clips for this track Mlt::Playlist playlist(*srcTrack); for (int mltClipIndex = 0; mltClipIndex < playlist.count(); mltClipIndex++) { if (!playlist.is_blank(mltClipIndex)) { playlist.clip_info(mltClipIndex, &info); + bool isTransitionClip = info.producer && info.producer->is_valid() + && info.producer->get( + kShotcutTransitionProperty); + if (isTransitionClip) { + pendingTransitionDuration = qMax(1, info.frame_count); + pendingTransitionRightIn = -1; + pendingTransitionRightClip = Mlt::Producer(); + Mlt::Tractor transition(*info.producer); + QScopedPointer transitionRight(transition.track(1)); + if (transitionRight && transitionRight->is_valid()) { + pendingTransitionRightIn = transitionRight->get_in(); + pendingTransitionRightClip = Mlt::Producer(*transitionRight); + } + continue; + } Mlt::Producer clip(info.producer); - clip.set_in_and_out(info.frame_in, info.frame_out); + if (pendingTransitionDuration > 0 && pendingTransitionRightIn >= 0 + && pendingTransitionRightClip.is_valid() + && clip.same_clip(pendingTransitionRightClip)) { + clip.set_in_and_out(qMin(pendingTransitionRightIn, info.frame_out), + info.frame_out); + } else { + clip.set_in_and_out(info.frame_in, info.frame_out); + } bool lastClip = mltTrackIndex == tractor.count() - 1 && mltClipIndex == playlist.count() - 1; MAIN.undoStack()->push(new Timeline::AppendCommand(m_model, @@ -2674,6 +2699,29 @@ void TimelineDock::append(int trackIndex) MLT.XML(&clip), false, lastClip)); + + if (pendingTransitionDuration > 0) { + const int rightClipIndex = clipCount(trackIndex) - 1; + const int leftClipIndex = rightClipIndex - 1; + if (leftClipIndex >= 0 && rightClipIndex >= 0 + && !isBlank(trackIndex, leftClipIndex) + && !isBlank(trackIndex, rightClipIndex) + && !isTransition(trackIndex, leftClipIndex) + && !isTransition(trackIndex, rightClipIndex) + && m_model.addTransitionByTrimOutValid( + trackIndex, leftClipIndex, -pendingTransitionDuration)) { + MAIN.undoStack()->push( + new Timeline::AddTransitionByTrimOutCommand( + m_model, + trackIndex, + leftClipIndex, + -pendingTransitionDuration, + 0)); + } + pendingTransitionDuration = 0; + pendingTransitionRightIn = -1; + pendingTransitionRightClip = Mlt::Producer(); + } // AppendCommand executed immediately; record group membership if (info.cut && info.cut->property_exists(kShotcutGroupProperty)) { int group = info.cut->get_int(kShotcutGroupProperty); @@ -4202,22 +4250,75 @@ void TimelineDock::insert(int trackIndex, int position, const QString &xml, bool if (srcTrack) { const auto trackIndex = currentTrack() + mltTrackIndex; addTrackIfNeeded(trackIndex, srcTrack.get()); + int pendingTransitionDuration = 0; + int skippedTransitionDuration = 0; + int pendingTransitionRightIn = -1; + Mlt::Producer pendingTransitionRightClip; // Insert the clips for this track Mlt::Playlist playlist(*srcTrack); for (int mltClipIndex = 0; mltClipIndex < playlist.count(); mltClipIndex++) { if (!playlist.is_blank(mltClipIndex)) { playlist.clip_info(mltClipIndex, &info); + bool isTransitionClip = info.producer && info.producer->is_valid() + && info.producer->get( + kShotcutTransitionProperty); + if (isTransitionClip) { + pendingTransitionDuration = qMax(1, info.frame_count); + skippedTransitionDuration += pendingTransitionDuration; + pendingTransitionRightIn = -1; + pendingTransitionRightClip = Mlt::Producer(); + Mlt::Tractor transition(*info.producer); + QScopedPointer transitionRight(transition.track(1)); + if (transitionRight && transitionRight->is_valid()) { + pendingTransitionRightIn = transitionRight->get_in(); + pendingTransitionRightClip = Mlt::Producer(*transitionRight); + } + continue; + } Mlt::Producer clip(info.producer); - clip.set_in_and_out(info.frame_in, info.frame_out); + if (pendingTransitionDuration > 0 && pendingTransitionRightIn >= 0 + && pendingTransitionRightClip.is_valid() + && clip.same_clip(pendingTransitionRightClip)) { + clip.set_in_and_out(qMin(pendingTransitionRightIn, info.frame_out), + info.frame_out); + } else { + clip.set_in_and_out(info.frame_in, info.frame_out); + } + const int insertPosition = position + info.start + - skippedTransitionDuration; bool lastClip = mltTrackIndex == tractor.count() - 1 && mltClipIndex == playlist.count() - 1; MAIN.undoStack()->push(new Timeline::InsertCommand(m_model, m_markersModel, trackIndex, - position + info.start, + insertPosition, MLT.XML(&clip), lastClip)); + + if (pendingTransitionDuration > 0) { + const int rightClipIndex = clipIndexAtPosition(trackIndex, + insertPosition); + const int leftClipIndex = rightClipIndex - 1; + if (leftClipIndex >= 0 && rightClipIndex >= 0 + && !isBlank(trackIndex, leftClipIndex) + && !isBlank(trackIndex, rightClipIndex) + && !isTransition(trackIndex, leftClipIndex) + && !isTransition(trackIndex, rightClipIndex) + && m_model.addTransitionByTrimOutValid( + trackIndex, leftClipIndex, -pendingTransitionDuration)) { + MAIN.undoStack()->push( + new Timeline::AddTransitionByTrimOutCommand( + m_model, + trackIndex, + leftClipIndex, + -pendingTransitionDuration, + 0)); + } + pendingTransitionDuration = 0; + pendingTransitionRightIn = -1; + pendingTransitionRightClip = Mlt::Producer(); + } } } } @@ -4387,20 +4488,72 @@ void TimelineDock::overwrite(int trackIndex, int position, const QString &xml, b if (srcTrack) { const auto trackIndex = currentTrack() + mltTrackIndex; addTrackIfNeeded(trackIndex, srcTrack.get()); + int pendingTransitionDuration = 0; + int skippedTransitionDuration = 0; + int pendingTransitionRightIn = -1; + Mlt::Producer pendingTransitionRightClip; // Insert the clips for this track Mlt::Playlist playlist(*srcTrack); for (int mltClipIndex = 0; mltClipIndex < playlist.count(); mltClipIndex++) { if (!playlist.is_blank(mltClipIndex)) { playlist.clip_info(mltClipIndex, &info); + bool isTransitionClip = info.producer && info.producer->is_valid() + && info.producer->get( + kShotcutTransitionProperty); + if (isTransitionClip) { + pendingTransitionDuration = qMax(1, info.frame_count); + skippedTransitionDuration += pendingTransitionDuration; + pendingTransitionRightIn = -1; + pendingTransitionRightClip = Mlt::Producer(); + Mlt::Tractor transition(*info.producer); + QScopedPointer transitionRight(transition.track(1)); + if (transitionRight && transitionRight->is_valid()) { + pendingTransitionRightIn = transitionRight->get_in(); + pendingTransitionRightClip = Mlt::Producer(*transitionRight); + } + continue; + } Mlt::Producer clip(info.producer); - clip.set_in_and_out(info.frame_in, info.frame_out); - MAIN.undoStack()->push( - new Timeline::OverwriteCommand(m_model, - trackIndex, - position + info.start, - MLT.XML(&clip), - false)); + if (pendingTransitionDuration > 0 && pendingTransitionRightIn >= 0 + && pendingTransitionRightClip.is_valid() + && clip.same_clip(pendingTransitionRightClip)) { + clip.set_in_and_out(qMin(pendingTransitionRightIn, info.frame_out), + info.frame_out); + } else { + clip.set_in_and_out(info.frame_in, info.frame_out); + } + const int overwritePosition = position + info.start + - skippedTransitionDuration; + MAIN.undoStack()->push(new Timeline::OverwriteCommand(m_model, + trackIndex, + overwritePosition, + MLT.XML(&clip), + false)); + + if (pendingTransitionDuration > 0) { + const int rightClipIndex = clipIndexAtPosition(trackIndex, + overwritePosition); + const int leftClipIndex = rightClipIndex - 1; + if (leftClipIndex >= 0 && rightClipIndex >= 0 + && !isBlank(trackIndex, leftClipIndex) + && !isBlank(trackIndex, rightClipIndex) + && !isTransition(trackIndex, leftClipIndex) + && !isTransition(trackIndex, rightClipIndex) + && m_model.addTransitionByTrimOutValid( + trackIndex, leftClipIndex, -pendingTransitionDuration)) { + MAIN.undoStack()->push( + new Timeline::AddTransitionByTrimOutCommand( + m_model, + trackIndex, + leftClipIndex, + -pendingTransitionDuration, + 0)); + } + pendingTransitionDuration = 0; + pendingTransitionRightIn = -1; + pendingTransitionRightClip = Mlt::Producer(); + } } } } From 294e94a78f50a1064195198f59cdd43dadb8aa2a Mon Sep 17 00:00:00 2001 From: Brian Matherly Date: Fri, 31 Jul 2026 17:00:46 -0500 Subject: [PATCH 2/2] Resolve review comments to simplify the code --- src/docks/timelinedock.cpp | 267 ++++++++++++++++++++----------------- 1 file changed, 148 insertions(+), 119 deletions(-) diff --git a/src/docks/timelinedock.cpp b/src/docks/timelinedock.cpp index 664159b6f9..e5e8807d3d 100644 --- a/src/docks/timelinedock.cpp +++ b/src/docks/timelinedock.cpp @@ -69,6 +69,70 @@ static const char *kFileUrlProtocol = "file://"; static const char *kFilesUrlDelimiter = ",file://"; static const int kRecordingTimerIntervalMs = 1000; +namespace { + +struct PendingTransitionState +{ + int duration = 0; + int skippedDuration = 0; + int rightIn = -1; + Mlt::Producer rightClip; +}; + +static bool isTransitionClipboardClip(const Mlt::ClipInfo &info) +{ + return info.producer && info.producer->is_valid() + && info.producer->get(kShotcutTransitionProperty); +} + +static int transitionClipboardDuration(const Mlt::ClipInfo &info) +{ + return qMax(1, info.frame_count); +} + +static void clearPendingTransition(PendingTransitionState &state) +{ + state.duration = 0; + state.rightIn = -1; + state.rightClip = Mlt::Producer(); +} + +static void beginPendingTransition(PendingTransitionState &state, + const Mlt::ClipInfo &info, + bool trackSkippedDuration) +{ + state.duration = transitionClipboardDuration(info); + if (trackSkippedDuration) + state.skippedDuration += state.duration; + state.rightIn = -1; + state.rightClip = Mlt::Producer(); + Mlt::Tractor transition(*info.producer); + QScopedPointer transitionRight(transition.track(1)); + if (transitionRight && transitionRight->is_valid()) { + state.rightIn = transitionRight->get_in(); + state.rightClip = Mlt::Producer(*transitionRight); + } +} + +static bool matchesPendingTransitionRight(PendingTransitionState &state, Mlt::Producer &clip) +{ + return state.duration > 0 && state.rightIn >= 0 && state.rightClip.is_valid() + && clip.same_clip(state.rightClip); +} + +static void applyTransitionAwareInOut(Mlt::Producer &clip, + const Mlt::ClipInfo &info, + bool matchesPendingTransition, + int pendingTransitionRightIn) +{ + if (matchesPendingTransition) + clip.set_in_and_out(qMin(pendingTransitionRightIn, info.frame_out), info.frame_out); + else + clip.set_in_and_out(info.frame_in, info.frame_out); +} + +} // namespace + /*! \qmltype TimelineDock \inqmlmodule org.shotcut.qml @@ -2659,39 +2723,24 @@ void TimelineDock::append(int trackIndex) if (srcTrack) { const auto trackIndex = currentTrack() + mltTrackIndex; addTrackIfNeeded(trackIndex, srcTrack.get()); - int pendingTransitionDuration = 0; - int pendingTransitionRightIn = -1; - Mlt::Producer pendingTransitionRightClip; + PendingTransitionState pendingTransition; // Insert the clips for this track Mlt::Playlist playlist(*srcTrack); for (int mltClipIndex = 0; mltClipIndex < playlist.count(); mltClipIndex++) { if (!playlist.is_blank(mltClipIndex)) { playlist.clip_info(mltClipIndex, &info); - bool isTransitionClip = info.producer && info.producer->is_valid() - && info.producer->get( - kShotcutTransitionProperty); - if (isTransitionClip) { - pendingTransitionDuration = qMax(1, info.frame_count); - pendingTransitionRightIn = -1; - pendingTransitionRightClip = Mlt::Producer(); - Mlt::Tractor transition(*info.producer); - QScopedPointer transitionRight(transition.track(1)); - if (transitionRight && transitionRight->is_valid()) { - pendingTransitionRightIn = transitionRight->get_in(); - pendingTransitionRightClip = Mlt::Producer(*transitionRight); - } + if (isTransitionClipboardClip(info)) { + beginPendingTransition(pendingTransition, info, false); continue; } Mlt::Producer clip(info.producer); - if (pendingTransitionDuration > 0 && pendingTransitionRightIn >= 0 - && pendingTransitionRightClip.is_valid() - && clip.same_clip(pendingTransitionRightClip)) { - clip.set_in_and_out(qMin(pendingTransitionRightIn, info.frame_out), - info.frame_out); - } else { - clip.set_in_and_out(info.frame_in, info.frame_out); - } + const bool matchesPendingRight + = matchesPendingTransitionRight(pendingTransition, clip); + applyTransitionAwareInOut(clip, + info, + matchesPendingRight, + pendingTransition.rightIn); bool lastClip = mltTrackIndex == tractor.count() - 1 && mltClipIndex == playlist.count() - 1; MAIN.undoStack()->push(new Timeline::AppendCommand(m_model, @@ -2700,27 +2749,27 @@ void TimelineDock::append(int trackIndex) false, lastClip)); - if (pendingTransitionDuration > 0) { + if (pendingTransition.duration > 0) { const int rightClipIndex = clipCount(trackIndex) - 1; const int leftClipIndex = rightClipIndex - 1; - if (leftClipIndex >= 0 && rightClipIndex >= 0 + if (matchesPendingRight && leftClipIndex >= 0 && rightClipIndex >= 0 && !isBlank(trackIndex, leftClipIndex) && !isBlank(trackIndex, rightClipIndex) && !isTransition(trackIndex, leftClipIndex) && !isTransition(trackIndex, rightClipIndex) - && m_model.addTransitionByTrimOutValid( - trackIndex, leftClipIndex, -pendingTransitionDuration)) { + && m_model.addTransitionByTrimOutValid(trackIndex, + leftClipIndex, + -pendingTransition + .duration)) { MAIN.undoStack()->push( - new Timeline::AddTransitionByTrimOutCommand( - m_model, - trackIndex, - leftClipIndex, - -pendingTransitionDuration, - 0)); + new Timeline::AddTransitionByTrimOutCommand(m_model, + trackIndex, + leftClipIndex, + -pendingTransition + .duration, + 0)); } - pendingTransitionDuration = 0; - pendingTransitionRightIn = -1; - pendingTransitionRightClip = Mlt::Producer(); + clearPendingTransition(pendingTransition); } // AppendCommand executed immediately; record group membership if (info.cut && info.cut->property_exists(kShotcutGroupProperty)) { @@ -4250,43 +4299,26 @@ void TimelineDock::insert(int trackIndex, int position, const QString &xml, bool if (srcTrack) { const auto trackIndex = currentTrack() + mltTrackIndex; addTrackIfNeeded(trackIndex, srcTrack.get()); - int pendingTransitionDuration = 0; - int skippedTransitionDuration = 0; - int pendingTransitionRightIn = -1; - Mlt::Producer pendingTransitionRightClip; + PendingTransitionState pendingTransition; // Insert the clips for this track Mlt::Playlist playlist(*srcTrack); for (int mltClipIndex = 0; mltClipIndex < playlist.count(); mltClipIndex++) { if (!playlist.is_blank(mltClipIndex)) { playlist.clip_info(mltClipIndex, &info); - bool isTransitionClip = info.producer && info.producer->is_valid() - && info.producer->get( - kShotcutTransitionProperty); - if (isTransitionClip) { - pendingTransitionDuration = qMax(1, info.frame_count); - skippedTransitionDuration += pendingTransitionDuration; - pendingTransitionRightIn = -1; - pendingTransitionRightClip = Mlt::Producer(); - Mlt::Tractor transition(*info.producer); - QScopedPointer transitionRight(transition.track(1)); - if (transitionRight && transitionRight->is_valid()) { - pendingTransitionRightIn = transitionRight->get_in(); - pendingTransitionRightClip = Mlt::Producer(*transitionRight); - } + if (isTransitionClipboardClip(info)) { + beginPendingTransition(pendingTransition, info, true); continue; } Mlt::Producer clip(info.producer); - if (pendingTransitionDuration > 0 && pendingTransitionRightIn >= 0 - && pendingTransitionRightClip.is_valid() - && clip.same_clip(pendingTransitionRightClip)) { - clip.set_in_and_out(qMin(pendingTransitionRightIn, info.frame_out), - info.frame_out); - } else { - clip.set_in_and_out(info.frame_in, info.frame_out); - } + const bool matchesPendingRight + = matchesPendingTransitionRight(pendingTransition, clip); + applyTransitionAwareInOut(clip, + info, + matchesPendingRight, + pendingTransition.rightIn); const int insertPosition = position + info.start - - skippedTransitionDuration; + - pendingTransition.skippedDuration; bool lastClip = mltTrackIndex == tractor.count() - 1 && mltClipIndex == playlist.count() - 1; MAIN.undoStack()->push(new Timeline::InsertCommand(m_model, @@ -4296,28 +4328,28 @@ void TimelineDock::insert(int trackIndex, int position, const QString &xml, bool MLT.XML(&clip), lastClip)); - if (pendingTransitionDuration > 0) { + if (pendingTransition.duration > 0) { const int rightClipIndex = clipIndexAtPosition(trackIndex, insertPosition); const int leftClipIndex = rightClipIndex - 1; - if (leftClipIndex >= 0 && rightClipIndex >= 0 + if (matchesPendingRight && leftClipIndex >= 0 && rightClipIndex >= 0 && !isBlank(trackIndex, leftClipIndex) && !isBlank(trackIndex, rightClipIndex) && !isTransition(trackIndex, leftClipIndex) && !isTransition(trackIndex, rightClipIndex) - && m_model.addTransitionByTrimOutValid( - trackIndex, leftClipIndex, -pendingTransitionDuration)) { + && m_model.addTransitionByTrimOutValid(trackIndex, + leftClipIndex, + -pendingTransition + .duration)) { MAIN.undoStack()->push( - new Timeline::AddTransitionByTrimOutCommand( - m_model, - trackIndex, - leftClipIndex, - -pendingTransitionDuration, - 0)); + new Timeline::AddTransitionByTrimOutCommand(m_model, + trackIndex, + leftClipIndex, + -pendingTransition + .duration, + 0)); } - pendingTransitionDuration = 0; - pendingTransitionRightIn = -1; - pendingTransitionRightClip = Mlt::Producer(); + clearPendingTransition(pendingTransition); } } } @@ -4332,14 +4364,21 @@ void TimelineDock::insert(int trackIndex, int position, const QString &xml, bool const auto destTrackIndex = currentTrack() + mltTrackIndex; Mlt::Playlist srcPlaylist(*srcTrack); Mlt::ClipInfo clipInfo; + int skippedTransitionDuration = 0; for (int mltClipIndex = 0; mltClipIndex < srcPlaylist.count(); mltClipIndex++) { if (!srcPlaylist.is_blank(mltClipIndex)) { srcPlaylist.clip_info(mltClipIndex, &clipInfo); + if (isTransitionClipboardClip(clipInfo)) { + skippedTransitionDuration += transitionClipboardDuration(clipInfo); + continue; + } if (clipInfo.cut && clipInfo.cut->property_exists(kShotcutGroupProperty)) { int group = clipInfo.cut->get_int(kShotcutGroupProperty); + const int destPosition = position + clipInfo.start + - skippedTransitionDuration; int destClipIndex = clipIndexAtPosition(destTrackIndex, - position + clipInfo.start); + destPosition); if (destClipIndex >= 0 && !isBlank(destTrackIndex, destClipIndex)) { if (!groupCommands.contains(group)) groupCommands[group] = new Timeline::GroupCommand(m_model); @@ -4488,71 +4527,54 @@ void TimelineDock::overwrite(int trackIndex, int position, const QString &xml, b if (srcTrack) { const auto trackIndex = currentTrack() + mltTrackIndex; addTrackIfNeeded(trackIndex, srcTrack.get()); - int pendingTransitionDuration = 0; - int skippedTransitionDuration = 0; - int pendingTransitionRightIn = -1; - Mlt::Producer pendingTransitionRightClip; + PendingTransitionState pendingTransition; // Insert the clips for this track Mlt::Playlist playlist(*srcTrack); for (int mltClipIndex = 0; mltClipIndex < playlist.count(); mltClipIndex++) { if (!playlist.is_blank(mltClipIndex)) { playlist.clip_info(mltClipIndex, &info); - bool isTransitionClip = info.producer && info.producer->is_valid() - && info.producer->get( - kShotcutTransitionProperty); - if (isTransitionClip) { - pendingTransitionDuration = qMax(1, info.frame_count); - skippedTransitionDuration += pendingTransitionDuration; - pendingTransitionRightIn = -1; - pendingTransitionRightClip = Mlt::Producer(); - Mlt::Tractor transition(*info.producer); - QScopedPointer transitionRight(transition.track(1)); - if (transitionRight && transitionRight->is_valid()) { - pendingTransitionRightIn = transitionRight->get_in(); - pendingTransitionRightClip = Mlt::Producer(*transitionRight); - } + if (isTransitionClipboardClip(info)) { + beginPendingTransition(pendingTransition, info, true); continue; } Mlt::Producer clip(info.producer); - if (pendingTransitionDuration > 0 && pendingTransitionRightIn >= 0 - && pendingTransitionRightClip.is_valid() - && clip.same_clip(pendingTransitionRightClip)) { - clip.set_in_and_out(qMin(pendingTransitionRightIn, info.frame_out), - info.frame_out); - } else { - clip.set_in_and_out(info.frame_in, info.frame_out); - } + const bool matchesPendingRight + = matchesPendingTransitionRight(pendingTransition, clip); + applyTransitionAwareInOut(clip, + info, + matchesPendingRight, + pendingTransition.rightIn); const int overwritePosition = position + info.start - - skippedTransitionDuration; + - pendingTransition.skippedDuration; MAIN.undoStack()->push(new Timeline::OverwriteCommand(m_model, trackIndex, overwritePosition, MLT.XML(&clip), false)); - if (pendingTransitionDuration > 0) { + if (pendingTransition.duration > 0) { const int rightClipIndex = clipIndexAtPosition(trackIndex, overwritePosition); const int leftClipIndex = rightClipIndex - 1; - if (leftClipIndex >= 0 && rightClipIndex >= 0 + if (matchesPendingRight && leftClipIndex >= 0 && rightClipIndex >= 0 && !isBlank(trackIndex, leftClipIndex) && !isBlank(trackIndex, rightClipIndex) && !isTransition(trackIndex, leftClipIndex) && !isTransition(trackIndex, rightClipIndex) - && m_model.addTransitionByTrimOutValid( - trackIndex, leftClipIndex, -pendingTransitionDuration)) { + && m_model.addTransitionByTrimOutValid(trackIndex, + leftClipIndex, + -pendingTransition + .duration)) { MAIN.undoStack()->push( - new Timeline::AddTransitionByTrimOutCommand( - m_model, - trackIndex, - leftClipIndex, - -pendingTransitionDuration, - 0)); + new Timeline::AddTransitionByTrimOutCommand(m_model, + trackIndex, + leftClipIndex, + -pendingTransition + .duration, + 0)); } - pendingTransitionDuration = 0; - pendingTransitionRightIn = -1; - pendingTransitionRightClip = Mlt::Producer(); + clearPendingTransition(pendingTransition); } } } @@ -4567,14 +4589,21 @@ void TimelineDock::overwrite(int trackIndex, int position, const QString &xml, b const auto destTrackIndex = currentTrack() + mltTrackIndex; Mlt::Playlist srcPlaylist(*srcTrack); Mlt::ClipInfo clipInfo; + int skippedTransitionDuration = 0; for (int mltClipIndex = 0; mltClipIndex < srcPlaylist.count(); mltClipIndex++) { if (!srcPlaylist.is_blank(mltClipIndex)) { srcPlaylist.clip_info(mltClipIndex, &clipInfo); + if (isTransitionClipboardClip(clipInfo)) { + skippedTransitionDuration += transitionClipboardDuration(clipInfo); + continue; + } if (clipInfo.cut && clipInfo.cut->property_exists(kShotcutGroupProperty)) { int group = clipInfo.cut->get_int(kShotcutGroupProperty); + const int destPosition = position + clipInfo.start + - skippedTransitionDuration; int destClipIndex = clipIndexAtPosition(destTrackIndex, - position + clipInfo.start); + destPosition); if (destClipIndex >= 0 && !isBlank(destTrackIndex, destClipIndex)) { if (!groupCommands.contains(group)) groupCommands[group] = new Timeline::GroupCommand(m_model);