diff -Nru kdenlive-22.04.1/CMakeLists.txt kdenlive-22.04.2/CMakeLists.txt --- kdenlive-22.04.1/CMakeLists.txt 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/CMakeLists.txt 2022-06-12 20:11:07.000000000 +0000 @@ -6,7 +6,7 @@ # KDE Application Version, managed by release script set (RELEASE_SERVICE_VERSION_MAJOR "22") set (RELEASE_SERVICE_VERSION_MINOR "04") -set (RELEASE_SERVICE_VERSION_MICRO "1") +set (RELEASE_SERVICE_VERSION_MICRO "2") set(KDENLIVE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") diff -Nru kdenlive-22.04.1/data/kdenlive.notifyrc kdenlive-22.04.2/data/kdenlive.notifyrc --- kdenlive-22.04.1/data/kdenlive.notifyrc 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/data/kdenlive.notifyrc 2022-06-12 20:11:07.000000000 +0000 @@ -53,7 +53,7 @@ Name[az]=Görüntünün işlənməsi tamamlandı Name[bs]=Iscrtavanje završeno Name[ca]=Ha acabat la renderització -Name[ca@valencia]=Ha acabat la renderització +Name[ca@valencia]=Ha acabat la renderisació Name[cs]=Renderování bylo dokončeno Name[da]=Rendering gennemført Name[de]=Rendern fertiggestellt @@ -95,7 +95,7 @@ Comment[az]=Yığım tamamlandı Comment[bs]=Iscrtavanje je gotovo Comment[ca]=La renderització ja ha acabat -Comment[ca@valencia]=La renderització ja ha acabat +Comment[ca@valencia]=La renderisació ya ha acabat Comment[cs]=Renderování je u konce Comment[da]=Renderingen er slut Comment[de]=Das Rendern ist beendet @@ -140,7 +140,7 @@ Name[az]=Görüntü işlənməsi başladıldı Name[bs]=Iscrtavanje započelo Name[ca]=Ha començat la renderització -Name[ca@valencia]=Ha començat la renderització +Name[ca@valencia]=Ha començat la renderisació Name[cs]=Renderování začalo Name[da]=Rendering startet Name[de]=Rendern wurde gestartet @@ -182,7 +182,7 @@ Comment[az]=Görüntü işlənməsi başladılıb Comment[bs]=Iscrtavanje je započelo Comment[ca]=La renderització ja ha començat -Comment[ca@valencia]=La renderització ja ha començat +Comment[ca@valencia]=La renderisació ya ha començat Comment[cs]=Vykreslování bylo zahájeno Comment[da]=Renderingen blev startet Comment[de]=Das Rendern wurde gestartet diff -Nru kdenlive-22.04.1/data/knewstuff/kdenlive_renderprofiles.knsrc kdenlive-22.04.2/data/knewstuff/kdenlive_renderprofiles.knsrc --- kdenlive-22.04.1/data/knewstuff/kdenlive_renderprofiles.knsrc 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/data/knewstuff/kdenlive_renderprofiles.knsrc 2022-06-12 20:11:07.000000000 +0000 @@ -3,7 +3,7 @@ Name[ar]=تشكيلة كدينلايف للتّصيير Name[az]=Kdenlive görüntün işlənməsi profili Name[ca]=Perfils de renderització del Kdenlive -Name[ca@valencia]=Perfils de renderització de Kdenlive +Name[ca@valencia]=Perfils de renderisació de Kdenlive Name[cs]=Profily renderování Kdenlive Name[da]=Kdenlive-renderingsprofiler Name[de]=Kdenlive-Render-Profile diff -Nru kdenlive-22.04.1/data/org.kde.kdenlive.appdata.xml kdenlive-22.04.2/data/org.kde.kdenlive.appdata.xml --- kdenlive-22.04.1/data/org.kde.kdenlive.appdata.xml 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/data/org.kde.kdenlive.appdata.xml 2022-06-12 20:11:07.000000000 +0000 @@ -242,10 +242,10 @@ + - https://kdenlive.org/ https://bugs.kde.org @@ -257,7 +257,7 @@ الخط الزمني في كدينلايڤ Kdenlive zaman qrafiki Línia de temps del Kdenlive - Línia de temps de Kdenlive + Llínea de temps de Kdenlive Časová osa Kdenlive Kdenlive tidslinje Kdenlive-Zeitleiste diff -Nru kdenlive-22.04.1/data/profiles.xml kdenlive-22.04.2/data/profiles.xml --- kdenlive-22.04.1/data/profiles.xml 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/data/profiles.xml 2022-06-12 20:11:07.000000000 +0000 @@ -8,7 +8,7 @@ qualities="15,45" defaultquality="25" audiobitrates="256,64" defaultaudiobitrate="160" args="f=mp4 movflags=+faststart vcodec=libx264 crf=%quality g=15 acodec=aac ab=%audiobitrate+'k'" - speeds="preset=slower;preset=medium;preset=faster;preset=ultrafast"/> + speeds="preset=veryslow;preset=slower;preset=slow;preset=medium;preset=faster;preset=fast;preset=faster;preset=veryfast;preset=superfast;preset=ultrafast"/> + audioqualities="10,0" defaultaudioquality="5" + args="f=webm vcodec=libvpx-vp9 crf=%quality vb=15M qcomp=1 g=15 row-mt=1 tile-columns=4 frame-parallel=1 acodec=libopus compression_level=%audioquality"/> + speeds="preset=veryslow;preset=slower;preset=slow;preset=medium;preset=faster;preset=fast;preset=faster;preset=veryfast;preset=superfast;preset=ultrafast"/> + args="f=mp4 vcodec=h264_nvenc vb=%bitrate+'k' acodec=aac ab=%audiobitrate+'k'"/> + args="f=mp4 vcodec=h264_nvenc rc=constqp vglobal_quality=%quality vq=%quality acodec=aac ab=%audiobitrate+'k'"/> 4 and (float(sys.argv[4])>0 or float(sys.argv[5])>0): - process = subprocess.Popen(['ffmpeg', '-loglevel', 'quiet', '-i', + process = subprocess.Popen([path, '-loglevel', 'quiet', '-i', sys.argv[3], '-ss', sys.argv[4], '-t', sys.argv[5], '-ar', str(sample_rate) , '-ac', '1', '-f', 's16le', '-'], stdout=subprocess.PIPE) else: - process = subprocess.Popen(['ffmpeg', '-loglevel', 'quiet', '-i', + process = subprocess.Popen([path, '-loglevel', 'quiet', '-i', sys.argv[3], '-ar', str(sample_rate) , '-ac', '1', '-f', 's16le', '-'], stdout=subprocess.PIPE) diff -Nru kdenlive-22.04.1/debian/changelog kdenlive-22.04.2/debian/changelog --- kdenlive-22.04.1/debian/changelog 2022-05-15 21:39:57.000000000 +0000 +++ kdenlive-22.04.2/debian/changelog 2022-06-12 20:11:09.000000000 +0000 @@ -1,8 +1,8 @@ -kdenlive (4:22.04.1-1~ubuntu22.04.1) jammy; urgency=low +kdenlive (4:22.04.2-1~ubuntu22.04.1) jammy; urgency=low * Auto build. - -- Vincent Pinon Sun, 15 May 2022 21:39:57 +0000 + -- Vincent Pinon Sun, 12 Jun 2022 20:11:09 +0000 kdenlive (4:21.12.1-0) focal; urgency=medium diff -Nru kdenlive-22.04.1/debian/git-build-recipe.manifest kdenlive-22.04.2/debian/git-build-recipe.manifest --- kdenlive-22.04.1/debian/git-build-recipe.manifest 2022-05-15 21:39:57.000000000 +0000 +++ kdenlive-22.04.2/debian/git-build-recipe.manifest 2022-06-12 20:11:09.000000000 +0000 @@ -1,4 +1,4 @@ -# git-build-recipe format 0.4 deb-version 4:22.04.1-1 -lp:kdenlive git-commit:0f5912285f51eab90a9e0123a38eebeb91dac7a2 +# git-build-recipe format 0.4 deb-version 4:22.04.2-1 +lp:kdenlive git-commit:0f311c6a64aed443977e044e4db1129a1460a7d9 merge packaging lp:kdenlive git-commit:7a45c5edb4ec76bf0cf9c46f6387436334eb5299 merge translations lp:kdenlive git-commit:79ba3ea15f18a547d69cc243cb578f735b12c1f6 diff -Nru kdenlive-22.04.1/.pc/.quilt_patches kdenlive-22.04.2/.pc/.quilt_patches --- kdenlive-22.04.1/.pc/.quilt_patches 2022-05-15 21:39:57.000000000 +0000 +++ kdenlive-22.04.2/.pc/.quilt_patches 2022-06-12 20:11:09.000000000 +0000 @@ -1 +1 @@ -/home/buildd/build-RECIPEBRANCHBUILD-3363228/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches +/home/buildd/build-RECIPEBRANCHBUILD-3377868/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches diff -Nru kdenlive-22.04.1/.pc/.quilt_series kdenlive-22.04.2/.pc/.quilt_series --- kdenlive-22.04.1/.pc/.quilt_series 2022-05-15 21:39:57.000000000 +0000 +++ kdenlive-22.04.2/.pc/.quilt_series 2022-06-12 20:11:09.000000000 +0000 @@ -1 +1 @@ -/home/buildd/build-RECIPEBRANCHBUILD-3363228/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches/series +/home/buildd/build-RECIPEBRANCHBUILD-3377868/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches/series diff -Nru kdenlive-22.04.1/src/assets/keyframes/view/keyframeview.cpp kdenlive-22.04.2/src/assets/keyframes/view/keyframeview.cpp --- kdenlive-22.04.1/src/assets/keyframes/view/keyframeview.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/assets/keyframes/view/keyframeview.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -53,6 +53,15 @@ setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); connect(m_model.get(), &KeyframeModelList::modelChanged, this, &KeyframeView::slotModelChanged); connect(m_model.get(), &KeyframeModelList::modelDisplayChanged, this, &KeyframeView::slotModelDisplayChanged); + m_centerConnection = connect(this, &KeyframeView::updateKeyframeOriginal, this, [&](int pos) { + m_currentKeyframeOriginal = pos; + update(); + }); +} + +KeyframeView::~KeyframeView() +{ + QObject::disconnect(m_centerConnection); } void KeyframeView::slotModelChanged() @@ -268,13 +277,11 @@ break; } Fun local_redo = [this, position = m_position]() { - m_currentKeyframeOriginal = position; - update(); + emit updateKeyframeOriginal(position); return true; }; Fun local_undo = [this, sourcePosition]() { - m_currentKeyframeOriginal = sourcePosition; - update(); + emit updateKeyframeOriginal(sourcePosition); return true; }; local_redo(); diff -Nru kdenlive-22.04.1/src/assets/keyframes/view/keyframeview.hpp kdenlive-22.04.2/src/assets/keyframes/view/keyframeview.hpp --- kdenlive-22.04.1/src/assets/keyframes/view/keyframeview.hpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/assets/keyframes/view/keyframeview.hpp 2022-06-12 20:11:07.000000000 +0000 @@ -20,6 +20,7 @@ public: explicit KeyframeView(std::shared_ptr model, int duration, QWidget *parent = nullptr); + ~KeyframeView() override; void setDuration(int dur, int inPoint); const QString getAssetId(); /** @brief Copy a keyframe parameter to selected keyframes. */ @@ -94,12 +95,14 @@ QColor m_colSelected; QColor m_colKeyframe; QColor m_colKeyframeBg; + QMetaObject::Connection m_centerConnection; signals: void seekToPos(int pos); void atKeyframe(bool isKeyframe, bool singleKeyframe); void modified(); void activateEffect(); + void updateKeyframeOriginal(int pos); }; #endif diff -Nru kdenlive-22.04.1/src/audiomixer/mixermanager.cpp kdenlive-22.04.2/src/audiomixer/mixermanager.cpp --- kdenlive-22.04.1/src/audiomixer/mixermanager.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/audiomixer/mixermanager.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -108,7 +108,9 @@ m_channelsLayout->insertWidget(0, line); m_channelsLayout->insertWidget(0, mixer.get()); m_recommendedWidth = (mixer->minimumWidth() + 12 + line->minimumWidth()) * (qMin(2, int(m_mixers.size()))); - m_channelsBox->setMinimumWidth(m_recommendedWidth); + if (!KdenliveSettings::mixerCollapse()) { + m_channelsBox->setMinimumWidth(m_recommendedWidth); + } } void MixerManager::deregisterTrack(int tid) diff -Nru kdenlive-22.04.1/src/bin/bin.cpp kdenlive-22.04.2/src/bin/bin.cpp --- kdenlive-22.04.1/src/bin/bin.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/bin/bin.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -683,7 +683,6 @@ void MyListView::mousePressEvent(QMouseEvent *event) { - QListView::mousePressEvent(event); if (event->button() == Qt::LeftButton) { QModelIndex ix = indexAt(event->pos()); if (ix.isValid()) { @@ -696,7 +695,7 @@ } emit updateDragMode(m_dragType); } - event->accept(); + QListView::mousePressEvent(event); } void MyListView::mouseMoveEvent(QMouseEvent *event) @@ -706,6 +705,7 @@ QModelIndexList indexes = selectedIndexes(); if (indexes.isEmpty()) { // Dragging from empty zone, abort + QListView::mouseMoveEvent(event); return; } auto *drag = new QDrag(this); @@ -734,7 +734,9 @@ } drag->exec(); emit processDragEnd(); + return; } + QListView::mouseMoveEvent(event); return; } QModelIndex index = indexAt(event->pos()); @@ -829,8 +831,10 @@ int distance = (event->pos() - m_startPos).manhattanLength(); if (distance >= QApplication::startDragDistance()) { dragged = performDrag(); + return; } } + QTreeView::mouseMoveEvent(event); return; } else { QModelIndex index = indexAt(event->pos()); @@ -932,6 +936,7 @@ drag->setPixmap(QPixmap::fromImage(image)); } drag->exec(); + drag->deleteLater(); emit processDragEnd(); return true; } @@ -2260,7 +2265,6 @@ m_itemView->scrollTo(m_proxyModel->mapFromSource(ix), QAbstractItemView::EnsureVisible); } } else { - m_proxyModel->selectionModel()->clearSelection(); std::shared_ptr clip = getBinClip(clipId); if (clip == nullptr) { return; @@ -2295,8 +2299,10 @@ // Set item as current so that it displays its content in clip monitor setCurrent(currentItem); AbstractProjectItem::PROJECTITEMTYPE itemType = currentItem->itemType(); + bool isClip = itemType == AbstractProjectItem::ClipItem; + bool isFolder = itemType == AbstractProjectItem::FolderItem; std::shared_ptr clip = nullptr; - if (itemType == AbstractProjectItem::ClipItem) { + if (isClip) { clip = std::static_pointer_cast(currentItem); m_tagsWidget->setTagData(clip->tags()); m_deleteAction->setText(i18n("Delete Clip")); @@ -2319,7 +2325,7 @@ if (clip && clip->statusReady()) { emit requestShowClipProperties(clip, false); m_proxyAction->blockSignals(true); - if (itemType == AbstractProjectItem::ClipItem) { + if (isClip) { emit findInTimeline(clip->clipId(), clip->timelineInstances()); } clipService = clip->getProducerProperty(QStringLiteral("mlt_service")); @@ -2335,30 +2341,30 @@ emit findInTimeline(QString()); m_openAction->setEnabled(false); } - m_clipsActionsMenu->setEnabled(itemType != AbstractProjectItem::FolderItem); - m_editAction->setVisible(itemType != AbstractProjectItem::FolderItem); + m_clipsActionsMenu->setEnabled(!isFolder); + m_editAction->setVisible(!isFolder); m_editAction->setEnabled(true); m_extractAudioAction->menuAction()->setVisible(hasAudio); m_extractAudioAction->setEnabled(hasAudio); m_openAction->setEnabled(type == ClipType::Image || type == ClipType::Audio || type == ClipType::TextTemplate || type == ClipType::Text); - m_openAction->setVisible(itemType != AbstractProjectItem::FolderItem); - m_duplicateAction->setEnabled(itemType == AbstractProjectItem::ClipItem); - m_duplicateAction->setVisible(itemType != AbstractProjectItem::FolderItem); - m_inTimelineAction->setEnabled(itemType == AbstractProjectItem::ClipItem); - m_inTimelineAction->setVisible(itemType == AbstractProjectItem::ClipItem); - m_locateAction->setEnabled(itemType != AbstractProjectItem::FolderItem && isImported); - m_locateAction->setVisible(itemType != AbstractProjectItem::FolderItem && isImported); - m_proxyAction->setEnabled(m_doc->useProxy() && itemType != AbstractProjectItem::FolderItem); - m_reloadAction->setEnabled(itemType == AbstractProjectItem::ClipItem); - m_reloadAction->setVisible(itemType != AbstractProjectItem::FolderItem); - m_replaceAction->setEnabled(itemType == AbstractProjectItem::ClipItem); - m_replaceAction->setVisible(itemType != AbstractProjectItem::FolderItem); + m_openAction->setVisible(!isFolder); + m_duplicateAction->setEnabled(isClip); + m_duplicateAction->setVisible(!isFolder); + m_inTimelineAction->setEnabled(isClip); + m_inTimelineAction->setVisible(isClip); + m_locateAction->setEnabled(!isFolder && isImported); + m_locateAction->setVisible(!isFolder && isImported); + m_proxyAction->setEnabled(m_doc->useProxy() && !isFolder); + m_reloadAction->setEnabled(isClip); + m_reloadAction->setVisible(!isFolder); + m_replaceAction->setEnabled(isClip); + m_replaceAction->setVisible(!isFolder); m_clipsActionsMenu->menuAction()->setVisible( - itemType != AbstractProjectItem::FolderItem && + !isFolder && (clipService.contains(QStringLiteral("avformat")) || clipService.contains(QStringLiteral("xml")) || clipService.contains(QStringLiteral("consumer")))); - m_transcodeAction->setEnabled(itemType != AbstractProjectItem::FolderItem); - m_transcodeAction->setVisible(itemType != AbstractProjectItem::FolderItem && (type == ClipType::Playlist || type == ClipType::Text || clipService.contains(QStringLiteral("avformat")))); + m_transcodeAction->setEnabled(!isFolder); + m_transcodeAction->setVisible(!isFolder && (type == ClipType::Playlist || type == ClipType::Text || clipService.contains(QStringLiteral("avformat")))); m_deleteAction->setEnabled(true); m_renameAction->setEnabled(true); @@ -3027,14 +3033,14 @@ view->expand(m_proxyModel->mapFromSource(ix.parent())); const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, ix.parent()); if (id.isValid() && id2.isValid()) { - m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), QItemSelectionModel::SelectCurrent); + m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current); } } else { // Ensure parent folder is currently opened m_itemView->setRootIndex(m_proxyModel->mapFromSource(ix.parent())); m_upAction->setEnabled(!ix.parent().data(AbstractProjectItem::DataId).toString().isEmpty()); if (id.isValid()) { - m_proxyModel->selectionModel()->setCurrentIndex(m_proxyModel->mapFromSource(id), QItemSelectionModel::ClearAndSelect); + m_proxyModel->selectionModel()->select(m_proxyModel->mapFromSource(id), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current); } } m_itemView->scrollTo(m_proxyModel->mapFromSource(ix), QAbstractItemView::EnsureVisible); @@ -4920,9 +4926,8 @@ { if (m_transcodingDialog == nullptr) { m_transcodingDialog = new TranscodeSeek(this); - connect(m_transcodingDialog, &QDialog::accepted, this, [&] () { - QMap ids = m_transcodingDialog->ids(); - QString firstId = ids.firstKey(); + connect(m_transcodingDialog, &QDialog::accepted, this, [&]() { + QMap ids = m_transcodingDialog->ids(); QMapIterator i(ids); while (i.hasNext()) { i.next(); @@ -4932,8 +4937,7 @@ m_transcodingDialog->deleteLater(); m_transcodingDialog = nullptr; }); - connect(m_transcodingDialog, &QDialog::rejected, this, [&] () { - QString firstId = m_transcodingDialog->ids().firstKey(); + connect(m_transcodingDialog, &QDialog::rejected, this, [&]() { m_transcodingDialog->deleteLater(); m_transcodingDialog = nullptr; }); @@ -4961,21 +4965,27 @@ m_transcodingDialog = new TranscodeSeek(this); connect(m_transcodingDialog, &QDialog::accepted, this, [&, checkProfile] () { QMap ids = m_transcodingDialog->ids(); - QString firstId = ids.firstKey(); - QMapIterator i(ids); - while (i.hasNext()) { - i.next(); - std::shared_ptr clip = m_itemModel->getClipByBinID(i.key()); - TranscodeTask::start({ObjectType::BinClip,i.key().toInt()}, i.value().first(), m_transcodingDialog->preParams(), m_transcodingDialog->params(i.value().at(1).toInt()), -1, -1, true, clip.get(), false, i.key() == firstId ? checkProfile : false); + if (!ids.isEmpty()) { + QString firstId = ids.firstKey(); + QMapIterator i(ids); + while (i.hasNext()) { + i.next(); + std::shared_ptr clip = m_itemModel->getClipByBinID(i.key()); + TranscodeTask::start({ObjectType::BinClip,i.key().toInt()}, i.value().first(), m_transcodingDialog->preParams(), m_transcodingDialog->params(i.value().at(1).toInt()), -1, -1, true, clip.get(), false, i.key() == firstId ? checkProfile : false); + } } m_transcodingDialog->deleteLater(); m_transcodingDialog = nullptr; }); connect(m_transcodingDialog, &QDialog::rejected, this, [&, checkProfile] () { - QString firstId = m_transcodingDialog->ids().firstKey(); + QMap ids = m_transcodingDialog->ids(); + QString firstId; + if (!ids.isEmpty()) { + firstId = ids.firstKey(); + } m_transcodingDialog->deleteLater(); m_transcodingDialog = nullptr; - if (checkProfile) { + if (checkProfile && !firstId.isEmpty()) { pCore->bin()->slotCheckProfile(firstId); } }); diff -Nru kdenlive-22.04.1/src/bin/filewatcher.cpp kdenlive-22.04.2/src/bin/filewatcher.cpp --- kdenlive-22.04.1/src/bin/filewatcher.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/bin/filewatcher.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -71,7 +71,7 @@ m_occurences[url].erase(binId); m_binClipPaths.erase(binId); if (m_occurences[url].empty()) { - KDirWatch::self()->removeFile(url); + m_fileWatcher->removeFile(url); m_occurences.erase(url); } } @@ -106,7 +106,7 @@ { auto checkList = m_modifiedUrls; for (const QString &path : checkList) { - if (KDirWatch::self()->ctime(path).msecsTo(QDateTime::currentDateTime()) > 2000) { + if (m_fileWatcher->ctime(path).msecsTo(QDateTime::currentDateTime()) > 2000) { for (const QString &id : m_occurences[path]) { emit binClipModified(id); } @@ -120,17 +120,17 @@ void FileWatcher::clear() { - KDirWatch::self()->stopScan(); + m_fileWatcher->stopScan(); for (const auto &f : m_occurences) { - KDirWatch::self()->removeFile(f.first); + m_fileWatcher->removeFile(f.first); } m_occurences.clear(); m_modifiedUrls.clear(); m_binClipPaths.clear(); - KDirWatch::self()->startScan(); + m_fileWatcher->startScan(); } bool FileWatcher::contains(const QString &path) const { - return KDirWatch::self()->contains(path); + return m_fileWatcher->contains(path); } diff -Nru kdenlive-22.04.1/src/bin/model/markerlistmodel.cpp kdenlive-22.04.2/src/bin/model/markerlistmodel.cpp --- kdenlive-22.04.1/src/bin/model/markerlistmodel.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/bin/model/markerlistmodel.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -54,27 +54,40 @@ connect(this, &MarkerListModel::dataChanged, this, &MarkerListModel::modelChanged); } -bool MarkerListModel::hasMarker(GenTime pos) const +int MarkerListModel::markerIdAtFrame(int pos) const { - std::map::const_iterator it = m_markerList.begin(); - while (it != m_markerList.end()) { - if (it->second.time() == pos) { - return true; - } - it++; + if (m_markerPositions.contains(pos)) { + return m_markerPositions.value(pos); } - return false; + return -1; } +bool MarkerListModel::hasMarker(GenTime pos) const +{ + int frame = pos.frames(pCore->getCurrentFps()); + return hasMarker(frame); +} + +CommentedTime MarkerListModel::markerById(int mid) const +{ + Q_ASSERT(m_markerPositions.values().contains(mid)); + return m_markerList.at(mid); +} + +CommentedTime MarkerListModel::marker(int frame) const +{ + int mid = markerIdAtFrame(frame); + if (mid > -1) { + return m_markerList.at(mid); + } + return CommentedTime(); +} CommentedTime MarkerListModel::marker(GenTime pos) const { - std::map::const_iterator it = m_markerList.begin(); - while (it != m_markerList.end()) { - if (it->second.time() == pos) { - return it->second; - } - it++; + int mid = markerIdAtFrame(pos.frames(pCore->getCurrentFps())); + if (mid > -1) { + return m_markerList.at(mid); } return CommentedTime(); } @@ -213,13 +226,14 @@ int MarkerListModel::getIdFromPos(const GenTime &pos) const { - READ_LOCK(); - std::map::const_iterator it = m_markerList.begin(); - while (it != m_markerList.end()) { - if (it->second.time() == pos) { - return it->first; - } - it++; + int frame = pos.frames(pCore->getCurrentFps()); + return getIdFromPos(frame); +} + +int MarkerListModel::getIdFromPos(int frame) const +{ + if (m_markerPositions.contains(frame)) { + return m_markerPositions.value(frame); } return -1; } @@ -233,7 +247,10 @@ return false; } int row = getRowfromId(mid); + int oldPos = m_markerList.at(mid).time().frames(pCore->getCurrentFps()); m_markerList[mid].setTime(pos); + m_markerPositions.remove(oldPos); + m_markerPositions.insert(pos.frames(pCore->getCurrentFps()), mid); emit dataChanged(index(row), index(row), {FrameRole}); return true; } @@ -248,7 +265,10 @@ int lastRow = -1; for (auto mid : markersId) { Q_ASSERT(m_markerList.count(mid) > 0); - GenTime t = m_markerList.at(mid).time() + GenTime(offset, pCore->getCurrentFps()); + GenTime t = m_markerList.at(mid).time(); + m_markerPositions.remove(t.frames(pCore->getCurrentFps())); + t += GenTime(offset, pCore->getCurrentFps()); + m_markerPositions.insert(t.frames(pCore->getCurrentFps()), mid); m_markerList[mid].setTime(t); if (!updateView) { continue; @@ -326,6 +346,7 @@ int insertionRow = static_cast(model->m_markerList.size()); model->beginInsertRows(QModelIndex(), insertionRow, insertionRow); model->m_markerList[mid] = CommentedTime(pos, comment, type); + model->m_markerPositions.insert(pos.frames(pCore->getCurrentFps()), mid); model->endInsertRows(); model->addSnapPoint(pos); return true; @@ -344,6 +365,7 @@ int row = model->getRowfromId(mid); model->beginRemoveRows(QModelIndex(), row, row); model->m_markerList.erase(mid); + model->m_markerPositions.remove(pos.frames(pCore->getCurrentFps())); model->endRemoveRows(); model->removeSnapPoint(pos); return true; @@ -434,6 +456,18 @@ return static_cast(m_markerList.size()); } +CommentedTime MarkerListModel::getMarker(int frame, bool *ok) const +{ + READ_LOCK(); + if (hasMarker(frame) == false) { + // return empty marker + *ok = false; + return CommentedTime(); + } + *ok = true; + return marker(frame); +} + CommentedTime MarkerListModel::getMarker(const GenTime &pos, bool *ok) const { READ_LOCK(); @@ -461,13 +495,12 @@ QList MarkerListModel::getMarkersInRange(int start, int end) const { - READ_LOCK(); QList markers; - for (const auto &marker : m_markerList) { - int pos = marker.second.time().frames(pCore->getCurrentFps()); - if(pos >= start && (end == -1 || pos <= end)) { - markers << marker.second; - } + QVector mids = getMarkersIdInRange(start, end); + // Now extract markers + READ_LOCK(); + for (const auto &marker : mids) { + markers << m_markerList.at(marker); } std::sort(markers.begin(), markers.end()); return markers; @@ -477,25 +510,24 @@ { READ_LOCK(); Q_ASSERT(m_markerList.count(mid) > 0); - return m_markerList.at(mid).time().frames(pCore->getCurrentFps()); + return m_markerPositions.key(mid); } QVector MarkerListModel::getMarkersIdInRange(int start, int end) const { READ_LOCK(); + // First find marker ids in range QVector markers; - // Ensure we provide sorted markers list - std::map sortedList; - - for(std::map::const_iterator it = m_markerList.begin(); it != m_markerList.end(); ++it) - sortedList.insert(std::pair(it -> second, it -> first)); - - for (const auto &marker : sortedList) { - int pos = marker.first.time().frames(pCore->getCurrentFps()); - if(pos >= start && (end == -1 || pos <= end)) { - markers << marker.second; + QMap::const_iterator i = m_markerPositions.constBegin(); + while (i != m_markerPositions.constEnd()) { + if (end > -1 && i.key() > end) { + break; + } + if (i.key() >= start) { + markers << i.value(); } + ++i; } return markers; } @@ -503,17 +535,15 @@ std::vector MarkerListModel::getSnapPoints() const { READ_LOCK(); - std::vector markers; - for (const auto &marker : m_markerList) { - markers.push_back(marker.second.time().frames(pCore->getCurrentFps())); - } + const QList positions = m_markerPositions.keys(); + std::vector markers(positions.cbegin(), positions.cend()); return markers; } bool MarkerListModel::hasMarker(int frame) const { READ_LOCK(); - return hasMarker(GenTime(frame, pCore->getCurrentFps())); + return m_markerPositions.contains(frame); } void MarkerListModel::registerSnapModel(const std::weak_ptr &snapModel) @@ -525,9 +555,10 @@ m_registeredSnaps.push_back(snapModel); // we now add the already existing markers to the snap - for (const auto &marker : m_markerList) { - qDebug() << " *- *-* REGISTERING MARKER: " << marker.second.time().frames(pCore->getCurrentFps()); - ptr->addPoint(marker.second.time().frames(pCore->getCurrentFps())); + QMap::const_iterator i = m_markerPositions.constBegin(); + while (i != m_markerPositions.constEnd()) { + ptr->addPoint(i.key()); + ++i; } } else { qDebug() << "Error: added snapmodel is null"; diff -Nru kdenlive-22.04.1/src/bin/model/markerlistmodel.hpp kdenlive-22.04.2/src/bin/model/markerlistmodel.hpp --- kdenlive-22.04.1/src/bin/model/markerlistmodel.hpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/bin/model/markerlistmodel.hpp 2022-06-12 20:11:07.000000000 +0000 @@ -89,6 +89,7 @@ /** @brief Returns a marker data at given pos */ CommentedTime getMarker(const GenTime &pos, bool *ok) const; + CommentedTime getMarker(int frame, bool *ok) const; /** @brief Returns all markers in model or – if a type is given – all markers of the given type */ QList getAllMarkers(int type = -1) const; @@ -110,8 +111,13 @@ Notice that add/remove queries are done in real time (gentime), but this request is made in frame */ Q_INVOKABLE bool hasMarker(int frame) const; + /** @brief Returns a marker id at frame pos. Returns -1 if no marker exists at that position + */ + int markerIdAtFrame(int pos) const; bool hasMarker(GenTime pos) const; CommentedTime marker(GenTime pos) const; + CommentedTime marker(int frame) const; + CommentedTime markerById(int mid) const; /** @brief Registers a snapModel to the marker model. This is intended to be used for a guide model, so that the timelines can register their snapmodel to be updated when the guide moves. This is also used @@ -185,9 +191,12 @@ mutable QReadWriteLock m_lock; std::map m_markerList; + /** @brief A list of {marker frame,marker id}, useful to quickly find a marker */ + QMap m_markerPositions; std::vector> m_registeredSnaps; int getRowfromId(int mid) const; int getIdFromPos(const GenTime &pos) const; + int getIdFromPos(int frame) const; signals: void modelChanged(); diff -Nru kdenlive-22.04.1/src/bin/projectclip.cpp kdenlive-22.04.2/src/bin/projectclip.cpp --- kdenlive-22.04.1/src/bin/projectclip.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/bin/projectclip.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -408,7 +408,6 @@ if (!xml.isNull()) { bool hashChanged = false; m_thumbsProducer.reset(); - m_clipStatus = FileStatus::StatusWaiting; ClipType::ProducerType type = clipType(); if (type != ClipType::Color && type != ClipType::Image && type != ClipType::SlideShow) { xml.removeAttribute("out"); @@ -430,6 +429,7 @@ discardAudioThumb(); } ThumbnailCache::get()->invalidateThumbsForClip(clipId()); + m_clipStatus = FileStatus::StatusWaiting; m_thumbsProducer.reset(); ClipLoadTask::start({ObjectType::BinClip,m_binId.toInt()}, xml, false, -1, -1, this); } @@ -991,6 +991,12 @@ if (secondPlaylist) { tid = -tid; } + if (m_audioProducers.find(tid) != m_audioProducers.end()) { + // Buggy project, all clips in a track should use the same track producer, fix + qDebug()<<"/// FOUND INCORRECT PRODUCER ON AUDIO TRACK; FIXING"; + std::shared_ptr prod(getTimelineProducer(tid, clipId, state, master->parent().get_int("audio_index"), speed)->cut(in, out)); + return {prod, false}; + } m_audioProducers[tid] = std::make_shared(&master->parent()); m_effectStack->loadService(m_audioProducers[tid]); return {master, true}; @@ -1002,6 +1008,12 @@ if (secondPlaylist) { tid = -tid; } + if (m_videoProducers.find(tid) != m_videoProducers.end()) { + qDebug()<<"/// FOUND INCORRECT PRODUCER ON VIDEO TRACK; FIXING"; + // Buggy project, all clips in a track should use the same track producer, fix + std::shared_ptr prod(getTimelineProducer(tid, clipId, state, master->parent().get_int("audio_index"), speed)->cut(in, out)); + return {prod, false}; + } m_videoProducers[tid] = std::make_shared(&master->parent()); m_effectStack->loadService(m_videoProducers[tid]); } else { diff -Nru kdenlive-22.04.1/src/bin/projectsortproxymodel.cpp kdenlive-22.04.2/src/bin/projectsortproxymodel.cpp --- kdenlive-22.04.1/src/bin/projectsortproxymodel.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/bin/projectsortproxymodel.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -158,15 +158,38 @@ void ProjectSortProxyModel::onCurrentRowChanged(const QItemSelection ¤t, const QItemSelection &previous) { Q_UNUSED(previous) - QModelIndexList indexes = current.indexes(); + // Warning: the "current" parameter only represents the item that was newly selected, but not all selected items + QModelIndexList indexes = m_selection->selectedIndexes(); if (indexes.isEmpty()) { + // No item selected emit selectModel(QModelIndex()); return; } - for (int ix = 0; ix < indexes.count(); ix++) { - if (indexes.at(ix).column() == 0 || indexes.at(ix).column() == 7) { - emit selectModel(indexes.at(ix)); - break; + if (indexes.contains(m_selection->currentIndex())) { + // Select current item + emit selectModel(m_selection->currentIndex()); + } else { + QModelIndexList newlySelected = current.indexes(); + if (!newlySelected.isEmpty()) { + QModelIndex ix = newlySelected.takeLast(); + while (ix.column() != 0 && !newlySelected.isEmpty()) { + ix = newlySelected.takeLast(); + } + if (ix .column() == 0) { + emit selectModel(ix); + return; + } + } else { + if (!indexes.isEmpty()) { + QModelIndex ix = indexes.takeLast(); + while (ix.column() != 0 && !indexes.isEmpty()) { + ix = indexes.takeLast(); + } + if (ix .column() == 0) { + emit selectModel(ix); + return; + } + } } } } diff -Nru kdenlive-22.04.1/src/dialogs/renderpresetdialog.cpp kdenlive-22.04.2/src/dialogs/renderpresetdialog.cpp --- kdenlive-22.04.1/src/dialogs/renderpresetdialog.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/dialogs/renderpresetdialog.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -64,6 +64,7 @@ QStringLiteral("qp_b"), QStringLiteral("ab"), QStringLiteral("aq"), + QStringLiteral("compression_level"), QStringLiteral("vbr"), QStringLiteral("ar"), QStringLiteral("display_aspect_num"), @@ -307,6 +308,9 @@ audioSampleRate->setCurrentText(preset->getParam(QStringLiteral("ar"))); QString aqParam = preset->getParam(QStringLiteral("aq")); + if (aqParam.isEmpty()) { + aqParam = preset->getParam(QStringLiteral("compression_level")); + } if (aqParam.contains(QStringLiteral("%audioquality"))) { aQuality->setValue(preset->defaultAQuality().toInt()); } else { @@ -704,9 +708,7 @@ } } else if (acodec == "libopus") { params.append(QStringLiteral("vbr=on")); - // TODO - //params.append(QStringLiteral("compression_level= ")); - //setIfNotSet(p, "compression_level", TO_ABSOLUTE(0, 10, ui->audioQualitySpinner->value())); + params.append(QStringLiteral("compression_level=%audioquality")); } else { params.append(QStringLiteral("aq=%audioquality")); } diff -Nru kdenlive-22.04.1/src/dialogs/renderwidget.cpp kdenlive-22.04.2/src/dialogs/renderwidget.cpp --- kdenlive-22.04.1/src/dialogs/renderwidget.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/dialogs/renderwidget.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -171,6 +171,7 @@ } }); m_view.optionsGroup->setVisible(m_view.options->isChecked()); + m_view.optionsGroup->setMinimumWidth(m_view.optionsGroup->width() + m_view.optionsGroup->verticalScrollBar()->width()); connect(m_view.options, &QAbstractButton::toggled, m_view.optionsGroup, &QWidget::setVisible); connect(m_view.out_file, &KUrlRequester::textChanged, this, static_cast(&RenderWidget::slotUpdateButtons)); @@ -335,7 +336,7 @@ QSize RenderWidget::sizeHint() const { // Make sure the widget has minimum size on opening - return {200, 200}; + return {200, qMax(200, screen()->availableGeometry().height())}; } RenderWidget::~RenderWidget() @@ -1333,19 +1334,25 @@ m_view.qualityGroup->setEnabled(false); } + // historically qualities are sorted from best to worse for some reason + int vmin = preset->videoQualities().last().toInt(); + int vmax = preset->videoQualities().first().toInt(); + int vrange = abs(vmax - vmin); + int amin = preset->audioQualities().last().toInt(); + int amax = preset->audioQualities().first().toInt(); + int arange = abs(amax - amin); + + m_view.quality->setMaximum(qMin(100, qMax(vrange, arange))); double percent = double(m_view.quality->value()) / double(m_view.quality->maximum()); m_view.qualityPercent->setText(QStringLiteral("%1%").arg(qRound(percent * 100))); - // historically qualities are sorted from best to worse for some reason - int min = preset->videoQualities().last().toInt(); - int max = preset->videoQualities().first().toInt(); + int val = preset->defaultVQuality().toInt(); + if (m_view.qualityGroup->isChecked()) { - if (min < max) { - int range = max - min; - val = min + int(range * percent); + if (vmin < vmax) { + val = vmin + int(vrange * percent); } else { - int range = min - max; - val = min - int(range * percent); + val = vmin - int(vrange * percent); } } params.replace(QStringLiteral("%quality"), QString::number(val)); @@ -1360,17 +1367,12 @@ // cvbr = Constrained Variable Bit Rate params.replace(QStringLiteral("%cvbr"), QString::number(val)); - // historically qualities are sorted from best to worse for some reason - min = preset->audioQualities().last().toInt(); - max = preset->audioQualities().first().toInt(); val = preset->defaultAQuality().toInt(); if (m_view.qualityGroup->isChecked()) { - if (min < max) { - int range = max - min; - val = min + int(range * percent); + if (amin < amax) { + val = amin + int(arange * percent); } else { - int range = min - max; - val = min - int(range * percent); + val = amin - int(arange * percent); } } params.replace(QStringLiteral("%audioquality"), QString::number(val)); diff -Nru kdenlive-22.04.1/src/doc/documentchecker.cpp kdenlive-22.04.2/src/doc/documentchecker.cpp --- kdenlive-22.04.1/src/doc/documentchecker.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/doc/documentchecker.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -782,7 +782,7 @@ m_missingClips.append(e); missingPaths.append(resource); } - } else if (service.startsWith(QLatin1String("avformat")) || slideshow) { + } else if (service.startsWith(QLatin1String("avformat")) || slideshow || service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) { // Check if file changed const QByteArray hash = Xml::getXmlProperty(e, "kdenlive:file_hash").toLatin1(); if (!hash.isEmpty()) { diff -Nru kdenlive-22.04.1/src/doc/kdenlivedoc.cpp kdenlive-22.04.2/src/doc/kdenlivedoc.cpp --- kdenlive-22.04.1/src/doc/kdenlivedoc.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/doc/kdenlivedoc.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -1839,8 +1839,15 @@ // replace proxy clips with originals QMap proxies = pCore->projectItemModel()->getProxies(root); - QDomNodeList producers = doc.elementsByTagName(QStringLiteral("producer")); + QDomNodeList chains = doc.elementsByTagName(QStringLiteral("chain")); + processProxyNodes(producers, root, proxies); + processProxyNodes(chains, root, proxies); +} + +void KdenliveDoc::processProxyNodes(QDomNodeList producers, const QString &root, const QMap &proxies) +{ + QString producerResource; QString producerService; QString originalProducerService; diff -Nru kdenlive-22.04.1/src/doc/kdenlivedoc.h kdenlive-22.04.2/src/doc/kdenlivedoc.h --- kdenlive-22.04.1/src/doc/kdenlivedoc.h 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/doc/kdenlivedoc.h 2022-06-12 20:11:07.000000000 +0000 @@ -173,6 +173,7 @@ double getDocumentVersion() const; /** @brief Replace proxy clips with originals for rendering. */ void useOriginals(QDomDocument &doc); + void processProxyNodes(QDomNodeList producers, const QString &root, const QMap &proxies); private: QUrl m_url; diff -Nru kdenlive-22.04.1/src/effects/effectstack/model/effectstackmodel.cpp kdenlive-22.04.2/src/effects/effectstack/model/effectstackmodel.cpp --- kdenlive-22.04.1/src/effects/effectstack/model/effectstackmodel.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/effects/effectstack/model/effectstackmodel.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -876,6 +876,24 @@ if (!m_loadingExisting) { // qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting effect in " << m_childServices.size(); effectItem->plant(m_masterService); + // Check if we have an internal effect that needs to stay on top + if (m_ownerId.first == ObjectType::Master || m_ownerId.first == ObjectType::TimelineTrack) { + // check for subtitle effect + auto ms = m_masterService.lock(); + int ct = ms->filter_count(); + QVector ixToMove; + for (int i = 0; i < ct; i++) { + if (ms->filter(i)->get_int("internal_added") > 0) { + ixToMove << i; + } + } + std::sort(ixToMove.rbegin(), ixToMove.rend()); + for (auto &ix : ixToMove) { + if (ix < ct - 1) { + ms->move_filter(ix, ct - 1); + } + } + } for (const auto &service : m_childServices) { // qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting CLONE effect in " << (void *)service.lock().get(); effectItem->plantClone(service); diff -Nru kdenlive-22.04.1/src/jobs/cliploadtask.cpp kdenlive-22.04.2/src/jobs/cliploadtask.cpp --- kdenlive-22.04.1/src/jobs/cliploadtask.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/jobs/cliploadtask.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -735,11 +735,13 @@ void ClipLoadTask::abort() { - Fun undo = []() { return true; }; - Fun redo = []() { return true; }; m_progress = 100; pCore->taskManager.taskDone(m_owner.second, this); - qDebug()<projectItemModel()->getClipByBinID(QString::number(m_owner.second))->clipUrl(); + if (pCore->taskManager.isBlocked()) { + return; + } + Fun undo = []() { return true; }; + Fun redo = []() { return true; }; QString resource = Xml::getXmlProperty(m_xml, QStringLiteral("resource")); if (!m_softDelete) { auto binClip = pCore->projectItemModel()->getClipByBinID(QString::number(m_owner.second)); diff -Nru kdenlive-22.04.1/src/jobs/taskmanager.cpp kdenlive-22.04.2/src/jobs/taskmanager.cpp --- kdenlive-22.04.1/src/jobs/taskmanager.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/jobs/taskmanager.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -22,6 +22,7 @@ TaskManager::TaskManager(QObject *parent) : QObject(parent) , m_tasksListLock(QReadWriteLock::Recursive) + , m_blockUpdates(false) { int maxThreads = qMin(4, QThread::idealThreadCount() - 1); m_taskPool.setMaxThreadCount(qMax(maxThreads, 1)); @@ -33,6 +34,11 @@ slotCancelJobs(); } +bool TaskManager::isBlocked() const +{ + return m_blockUpdates; +} + void TaskManager::updateConcurrency() { m_transcodePool.setMaxThreadCount(KdenliveSettings::proxythreads()); @@ -124,6 +130,7 @@ { m_tasksListLock.lockForRead(); // See if there is already a task for this MLT service and resource. + m_blockUpdates = true; for (const auto &task : m_taskList) { for (AbstractTask* t : task.second) { // If so, then just add ourselves to be notified upon completion. @@ -133,6 +140,7 @@ m_tasksListLock.unlock(); m_taskPool.waitForDone(); m_transcodePool.waitForDone(); + m_blockUpdates = false; updateJobCount(); } diff -Nru kdenlive-22.04.1/src/jobs/taskmanager.h kdenlive-22.04.2/src/jobs/taskmanager.h --- kdenlive-22.04.1/src/jobs/taskmanager.h 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/jobs/taskmanager.h 2022-06-12 20:11:07.000000000 +0000 @@ -63,6 +63,9 @@ /** @brief Update the number of concurrent jobs allowed */ void updateConcurrency(); + /** @brief We are aborting all tasks and don't want them to send any updates */ + bool isBlocked() const; + /** @brief return the message of a given job on a given clip (message, detailed log)*/ //QPair getJobMessageForClip(int jobId, const QString &binId) const; @@ -79,6 +82,7 @@ QThreadPool m_transcodePool; std::unordered_map > m_taskList; mutable QReadWriteLock m_tasksListLock; + bool m_blockUpdates; signals: void jobCount(int); diff -Nru kdenlive-22.04.1/src/main.cpp kdenlive-22.04.2/src/main.cpp --- kdenlive-22.04.1/src/main.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/main.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -102,6 +102,22 @@ splash.show(); qApp->processEvents(QEventLoop::AllEvents); + QString packageType; + if (qEnvironmentVariableIsSet("PACKAGE_TYPE")) { + packageType = qgetenv("PACKAGE_TYPE").toLower(); + } else { + // no package type defined, try to detected it + QString appPath = qApp->applicationDirPath(); + if (appPath.contains(QStringLiteral("/tmp/.mount_"))) { + packageType = QStringLiteral("appimage"); + } + if (appPath.contains(QStringLiteral("/snap"))) { + packageType = QStringLiteral("snap"); + } else { + qDebug() << "Could not detect package type, probably default? App dir is" << qApp->applicationDirPath(); + } + } + #ifdef Q_OS_WIN qputenv("KDE_FORK_SLAVES", "1"); QString path = qApp->applicationDirPath() + QLatin1Char(';') + qgetenv("PATH"); @@ -125,21 +141,33 @@ } } } -#endif - QString packageType; - if (qEnvironmentVariableIsSet("PACKAGE_TYPE")) { - packageType = qgetenv("PACKAGE_TYPE").toLower(); - } else { - // no package type defined, try to detected it - QString appPath = qApp->applicationDirPath(); - if (appPath.contains(QStringLiteral("/tmp/.mount_"))) { - packageType = QStringLiteral("appimage"); - } if (appPath.contains(QStringLiteral("/snap"))) { - packageType = QStringLiteral("snap"); - } else { - qDebug() << "Could not detect package type, probably default? App dir is" << qApp->applicationDirPath(); +#else + // AppImage + if (packageType == QStringLiteral("appimage")) { + QMap themeMap; + themeMap.insert("breeze", "/../icons/breeze/breeze-icons.rcc"); + themeMap.insert("breeze-dark", "/../icons/breeze-dark/breeze-icons-dark.rcc"); + + QMapIterator i(themeMap); + while (i.hasNext()) { + i.next(); + QString themePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, i.value()); + if (!themePath.isEmpty()) { + const QString iconSubdir = "/icons/" + i.key(); + if (QResource::registerResource(themePath, iconSubdir)) { + if (QFileInfo::exists(QLatin1Char(':') + iconSubdir + QStringLiteral("/index.theme"))) { + qDebug() << "Loaded icon theme:" << i.key(); + } else { + qWarning() << "No index.theme found for" << i.key(); + QResource::unregisterResource(themePath, iconSubdir); + } + } else { + qWarning() << "Invalid rcc file" << i.key(); + } + } } } +#endif bool inSandbox = false; if (packageType == QStringLiteral("appimage") || packageType == QStringLiteral("flatpak") || packageType == QStringLiteral("snap")) { @@ -153,16 +181,16 @@ KConfigGroup grp(config, "unmanaged"); if (!grp.exists()) { QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - if (env.contains(QStringLiteral("XDG_CURRENT_DESKTOP")) && env.value(QStringLiteral("XDG_CURRENT_DESKTOP")).toLower() == QLatin1String("kde")) { - qCDebug(KDENLIVE_LOG) << "KDE Desktop detected, using system icons"; + if (env.value(QStringLiteral("XDG_CURRENT_DESKTOP")).toLower() == QLatin1String("kde") && packageType != QStringLiteral("appimage")) { + qCDebug(KDENLIVE_LOG) << "KDE Desktop detected and not Appimage, using system icons"; } else { - // We are not on a KDE desktop, force breeze icon theme + // We are not on a KDE desktop or in an Appimage, force breeze icon theme // Check if breeze theme is available QStringList iconThemes = KIconTheme::list(); if (iconThemes.contains(QStringLiteral("breeze"))) { grp.writeEntry("force_breeze", true); grp.writeEntry("use_dark_breeze", true); - qCDebug(KDENLIVE_LOG) << "Non KDE Desktop detected, forcing Breeze icon theme"; + qCDebug(KDENLIVE_LOG) << "Non KDE Desktop or Appimage detected, forcing Breeze icon theme"; } } } diff -Nru kdenlive-22.04.1/src/mainwindow.cpp kdenlive-22.04.2/src/mainwindow.cpp --- kdenlive-22.04.1/src/mainwindow.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/mainwindow.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -893,8 +893,14 @@ KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup initialGroup(kconfig, "version"); - if (initialGroup.exists() && KdenliveSettings::force_breeze() && useDarkIcons != KdenliveSettings::use_dark_breeze()) { - // We need to reload icon theme + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + bool isAppimage = pCore->packageType() == QStringLiteral("appimage"); + bool isKDE = env.value(QStringLiteral("XDG_CURRENT_DESKTOP")).toLower() == QLatin1String("kde"); + bool forceBreeze = initialGroup.exists() && KdenliveSettings::force_breeze(); + if ((!isKDE || isAppimage || forceBreeze) && + ((useDarkIcons && QIcon::themeName() == QStringLiteral("breeze")) || (!useDarkIcons && QIcon::themeName() == QStringLiteral("breeze-dark")))) { + // We need to reload icon theme, on KDE desktops this is not necessary, however for the Appimage it is even on KDE Desktop + // See also https://kate-editor.org/post/2021/2021-03-07-cross-platform-light-dark-themes-and-icons/ QIcon::setThemeName(useDarkIcons ? QStringLiteral("breeze-dark") : QStringLiteral("breeze")); KdenliveSettings::setUse_dark_breeze(useDarkIcons); } @@ -2336,8 +2342,8 @@ connect(pCore->mixer(), &MixerManager::purgeCache, m_projectMonitor, &Monitor::purgeCache); getMainTimeline()->controller()->clipActions = kdenliveCategoryMap.value(QStringLiteral("timelineselection"))->actions(); - connect(m_projectMonitor, &Monitor::zoneUpdated, project, [&](const QPoint &) { project->setModified(); }); - connect(m_clipMonitor, &Monitor::zoneUpdated, project, [&](const QPoint &) { project->setModified(); }); + connect(m_projectMonitor, &Monitor::zoneUpdated, project, [project](const QPoint &) { project->setModified(); }); + connect(m_clipMonitor, &Monitor::zoneUpdated, project, [project](const QPoint &) { project->setModified(); }); connect(project, &KdenliveDoc::docModified, this, &MainWindow::slotUpdateDocumentState); if (m_renderWidget) { diff -Nru kdenlive-22.04.1/src/monitor/monitor.cpp kdenlive-22.04.2/src/monitor/monitor.cpp --- kdenlive-22.04.1/src/monitor/monitor.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/monitor/monitor.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -691,7 +691,11 @@ updateGeometry(); } -void Monitor::buildBackgroundedProducer(int pos) { +void Monitor::buildBackgroundedProducer(int pos) +{ + if (m_controller == nullptr) { + return; + } if (KdenliveSettings::monitor_background() != "black") { Mlt::Tractor trac(pCore->getCurrentProfile()->profile()); QString color = QString("color:%1").arg(KdenliveSettings::monitor_background()); @@ -713,7 +717,6 @@ } } - void Monitor::updateMarkers() { if (m_markerMenu) { @@ -953,10 +956,14 @@ m_glWidget->setParent(nullptr); m_glWidget->move(screenRect.topLeft()); m_glWidget->resize(screenRect.size()); + screenFound = true; break; } } } + if (!screenFound) { + m_glWidget->setParent(nullptr); + } } else { m_glWidget->setParent(nullptr); } @@ -1294,9 +1301,9 @@ } if (model) { - bool found = false; - CommentedTime marker = model->getMarker(GenTime(pos, pCore->getCurrentFps()), &found); - if (found) { + int mid = model->markerIdAtFrame(pos); + if (mid > -1) { + CommentedTime marker = model->markerById(mid); overlayText = marker.comment(); color = model->markerTypes.at(marker.markerType()); } @@ -1629,6 +1636,9 @@ disconnect(m_controller.get(), &ProjectClip::boundsChanged, m_glMonitor->getControllerProxy(), &MonitorProxy::updateClipBounds); disconnect(m_controller.get(), &ProjectClip::registeredClipChanged, m_controller.get(), &ProjectClip::checkClipBounds); } + } else if (controller == nullptr) { + // Nothing to do + return; } disconnect(this, &Monitor::seekPosition, this, &Monitor::seekRemap); m_controller = controller; @@ -1801,11 +1811,13 @@ void Monitor::slotPreviewResource(const QString &path, const QString &title) { - if (!QUrl::fromUserInput(path).isLocalFile()) { - warningMessage(i18n("It maybe takes a while until the preview is loaded"), 15000); + if (isPlaying()) { + stop(); } + QApplication::processEvents(); slotOpenClip(nullptr); m_streamAction->setVisible(false); + // TODO: direct loading of the producer blocks UI, we should use a task to load the producer m_glMonitor->setProducer(path); m_timePos->setRange(0, m_glMonitor->producer()->get_length() - 1); m_glMonitor->getControllerProxy()->setClipProperties(-1, ClipType::Unknown, false, title); @@ -2366,7 +2378,7 @@ } QString newComment = root->property("markerText").toString(); bool found = false; - CommentedTime oldMarker = model->getMarker(m_timePos->gentime(), &found); + CommentedTime oldMarker = model->getMarker(m_timePos->getValue(), &found); if (!found || newComment == oldMarker.comment()) { // No change return; diff -Nru kdenlive-22.04.1/src/monitor/monitor.h kdenlive-22.04.2/src/monitor/monitor.h --- kdenlive-22.04.1/src/monitor/monitor.h 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/monitor/monitor.h 2022-06-12 20:11:07.000000000 +0000 @@ -242,8 +242,6 @@ void slotForceSize(QAction *a); void buildBackgroundedProducer(int pos); void slotSeekToKeyFrame(); - /** @brief Display a non blocking error message to user **/ - void warningMessage(const QString &text, int timeout = 5000, const QList &actions = QList()); void slotLockMonitor(bool lock); void slotSwitchPlay(); void slotEditInlineMarker(); @@ -288,6 +286,8 @@ void slotRewind(double speed = 0) override; void slotRewindOneFrame(int diff = 1); void slotForwardOneFrame(int diff = 1); + /** @brief Display a non blocking error message to user **/ + void warningMessage(const QString &text, int timeout = 5000, const QList &actions = QList()); void slotStart(); /** @brief Set position and information for the trimming preview * @param pos Absolute position in frames diff -Nru kdenlive-22.04.1/src/monitor/monitorproxy.cpp kdenlive-22.04.2/src/monitor/monitorproxy.cpp --- kdenlive-22.04.1/src/monitor/monitorproxy.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/monitor/monitorproxy.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -471,7 +471,7 @@ QStringList effectInfo = effectSource.split(QLatin1Char('-')); effectInfo.prepend(effectData); if (m_clipId > -1) { - pCore->bin()->slotAddEffect(QString::number(m_clipId), effectInfo); + QMetaObject::invokeMethod(pCore->bin(), "slotAddEffect", Qt::QueuedConnection, Q_ARG(QString, QString::number(m_clipId)), Q_ARG(QStringList, effectInfo)); } else { // Dropped in project monitor emit addTimelineEffect(effectInfo); diff -Nru kdenlive-22.04.1/src/onlineresources/resourcewidget.cpp kdenlive-22.04.2/src/onlineresources/resourcewidget.cpp --- kdenlive-22.04.1/src/onlineresources/resourcewidget.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/onlineresources/resourcewidget.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -28,6 +28,7 @@ ResourceWidget::ResourceWidget(QWidget *parent) : QWidget(parent) + , m_showloadingWarning(true) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); setupUi(this); @@ -463,9 +464,18 @@ if (!m_currentItem) { return; } - blockUI(true); - emit previewClip(m_currentItem->data(previewRole).toString(), i18n("Online Resources Preview")); + const QString path = m_currentItem->data(previewRole).toString(); + if (m_showloadingWarning && !QUrl::fromUserInput(path).isLocalFile()) { + message_line->setText(i18n("It maybe takes a while until the preview is loaded")); + message_line->setMessageType(KMessageWidget::Warning); + message_line->show(); + QTimer::singleShot(6000, message_line, &KMessageWidget::animatedHide); + repaint(); + // Only show this warning once + m_showloadingWarning = false; + } + emit previewClip(path, i18n("Online Resources Preview")); blockUI(false); } diff -Nru kdenlive-22.04.1/src/onlineresources/resourcewidget.hpp kdenlive-22.04.2/src/onlineresources/resourcewidget.hpp --- kdenlive-22.04.1/src/onlineresources/resourcewidget.hpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/onlineresources/resourcewidget.hpp 2022-06-12 20:11:07.000000000 +0000 @@ -66,6 +66,7 @@ /** @brief Default icon size for the views. */ QSize m_iconSize; int wheelAccumulatedDelta; + bool m_showloadingWarning; ResourceItemInfo getItemById(const QString &id); void loadConfig(); void saveConfig(); diff -Nru kdenlive-22.04.1/src/renderpresets/renderpresetmodel.cpp kdenlive-22.04.2/src/renderpresets/renderpresetmodel.cpp --- kdenlive-22.04.1/src/renderpresets/renderpresetmodel.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/renderpresets/renderpresetmodel.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -275,14 +275,14 @@ return m_aQualities.split(QLatin1Char(','), Qt::SkipEmptyParts); #endif } else { - //int aq = ui->audioQualitySpinner->value(); + // ATTENTION: historically qualities are sorted from best to worse for some reason QString acodec = getParam(QStringLiteral("acodec")).toLower(); if (acodec == "libmp3lame") { - return {"9", "0"}; - } else if (acodec == "libvorbis" || acodec == "vorbis") { - return {"0", "10"}; + return {"0", "9"}; + } else if (acodec == "libvorbis" || acodec == "vorbis" || acodec == "libopus") { + return {"10", "0"}; } else { - return {"0", "500"}; + return {"500", "0"}; } } } @@ -315,15 +315,16 @@ return m_vQualities.split(QLatin1Char(','), Qt::SkipEmptyParts); #endif } else { + // ATTENTION: historically qualities are sorted from best to worse for some reason QString vcodec = getParam(QStringLiteral("vcodec")).toLower(); if (vcodec == "libx265" || vcodec.contains("nvenc") || vcodec.endsWith("_amf") || vcodec.startsWith("libx264") || vcodec.endsWith("_vaapi") || vcodec.endsWith("_qsv")) { - return {"51", "0"}; + return {"0", "51"}; } else if (vcodec.startsWith("libvpx") || vcodec.startsWith("libaom-")) { - return {"63", "0"}; + return {"0", "63"}; } else if (vcodec.startsWith("libwebp")) { - return {"0", "100"}; + return {"100", "0"}; } else { - return {"31", "1"}; + return {"1", "31"}; } } } diff -Nru kdenlive-22.04.1/src/timeline2/model/clipmodel.cpp kdenlive-22.04.2/src/timeline2/model/clipmodel.cpp --- kdenlive-22.04.1/src/timeline2/model/clipmodel.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/timeline2/model/clipmodel.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -1168,8 +1168,10 @@ } } - if (finalMove && tid != -1 && m_lastTrackId != m_currentTrackId) { - refreshProducerFromBin(m_currentTrackId); + if (finalMove && m_lastTrackId != m_currentTrackId) { + if (tid != -1) { + refreshProducerFromBin(m_currentTrackId); + } m_lastTrackId = m_currentTrackId; } } diff -Nru kdenlive-22.04.1/src/timeline2/model/timelinemodel.cpp kdenlive-22.04.2/src/timeline2/model/timelinemodel.cpp --- kdenlive-22.04.1/src/timeline2/model/timelinemodel.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/timeline2/model/timelinemodel.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -841,7 +841,7 @@ } else { } } - ok = ok && getTrackById(trackId)->requestClipInsertion(clipId, position, updateView, finalMove, local_undo, local_redo, groupMove, false, allowedClipMixes); + ok = ok && getTrackById(trackId)->requestClipInsertion(clipId, position, updateView, finalMove, local_undo, local_redo, groupMove, old_trackId == -1, allowedClipMixes); if (!ok) { qWarning() << "clip insertion failed"; @@ -937,12 +937,10 @@ continue; } // Make sure we have enough space in clip to resize - int maxLengthLeft = m_allClips[previousClip]->getMaxDuration(); - int maxLengthRight = m_allClips[s]->getMaxDuration(); // leftMax is the maximum frames we have to expand first clip on the right - leftMax = maxLengthLeft > -1 ? (maxLengthLeft - 1 - m_allClips[previousClip]->getOut()) : m_allClips[s]->getPlaytime(); + leftMax = m_allClips[s]->getPlaytime(); // rightMax is the maximum frames we have to expand second clip on the left - rightMax = maxLengthRight > -1 ? (m_allClips[s]->getIn()) : m_allClips[previousClip]->getPlaytime(); + rightMax = m_allClips[previousClip]->getPlaytime(); if (getTrackById_const(selectedTrack)->hasStartMix(previousClip)) { int spaceBeforeMix = m_allClips[s]->getPosition() - (m_allClips[previousClip]->getPosition() + m_allClips[previousClip]->getMixDuration()); rightMax = rightMax == -1 ? spaceBeforeMix : qMin(rightMax, spaceBeforeMix); @@ -966,12 +964,10 @@ } else { // Mix at end of selected clip // Make sure we have enough space in clip to resize - int maxLengthLeft = m_allClips[s]->getMaxDuration(); - int maxLengthRight = m_allClips[nextClip]->getMaxDuration(); // leftMax is the maximum frames we have to expand first clip on the right - leftMax = maxLengthLeft > -1 ? (maxLengthLeft - 1 - m_allClips[s]->getOut()) : m_allClips[nextClip]->getPlaytime(); + leftMax = m_allClips[nextClip]->getPlaytime(); // rightMax is the maximum frames we have to expand second clip on the left - rightMax = maxLengthRight > -1 ? (m_allClips[nextClip]->getIn()) : m_allClips[s]->getPlaytime(); + rightMax = m_allClips[s]->getPlaytime(); if (getTrackById_const(selectedTrack)->hasStartMix(s)) { int spaceBeforeMix = m_allClips[nextClip]->getPosition() - (m_allClips[s]->getPosition() + m_allClips[s]->getMixDuration()); rightMax = rightMax == -1 ? spaceBeforeMix : qMin(rightMax, spaceBeforeMix); @@ -1010,6 +1006,7 @@ if (rightMax > -1) { // Both clips have limited durations mixDurations.first = qMin(mixDuration / 2, leftMax); + mixDurations.first = qMin(mixDurations.first, leftMax); mixDurations.second = qMin(mixDuration - mixDuration / 2, rightMax); int offset = mixDuration - (mixDurations.first + mixDurations.second); if (offset > 0) { @@ -2207,6 +2204,46 @@ } } bool trackChanged = false; + if (delta_track != 0) { + //Ensure the track move is possible (not outside our current tracks) + for (int item : all_items) { + int current_track_id = old_track_ids[item]; + int current_track_position = getTrackPosition(current_track_id); + bool audioTrack = getTrackById_const(current_track_id)->isAudioTrack(); + int d = audioTrack ? audio_delta : video_delta; + int target_track_position = current_track_position + d; + bool brokenMove = target_track_position < 0 || target_track_position >= getTracksCount(); + if (!brokenMove) { + int target_id = getTrackIndexFromPosition(target_track_position); + brokenMove = audioTrack != getTrackById_const(target_id)->isAudioTrack(); + } + if (brokenMove) { + if (isClip(item)) { + int lastTid = m_allClips[item]->getFakeTrackId(); + int originalTid = m_allClips[item]->getCurrentTrackId(); + int last_position = getTrackPosition(lastTid); + int original_position = getTrackPosition(originalTid); + int lastDelta = last_position - original_position; + if (audioTrack) { + if (qAbs(audio_delta) > qAbs(lastDelta)) { + audio_delta = lastDelta; + } + if (video_delta != 0) { + video_delta = - lastDelta; + } + } else { + if (qAbs(video_delta) > qAbs(lastDelta)) { + video_delta = lastDelta; + } + if (audio_delta != 0) { + audio_delta = - lastDelta; + } + } + }; + } + } + } + // Reverse sort. We need to insert from left to right to avoid confusing the view for (int item : all_items) { @@ -6004,7 +6041,33 @@ bool res = requestCompositionDeletion(cid, undo, redo); int newId = -1; - res = res && requestCompositionInsertion(compoId, currentTrack, a_track, currentPos, duration, nullptr, newId, undo, redo); + // Check if composition should be reversed (top clip at beginning, bottom at end) + int topClip = getTrackById_const(currentTrack)->getClipByPosition(currentPos); + int bottomTid = getTrackIndexFromPosition(a_track - 1); + int bottomClip = -1; + if (bottomTid > -1) { + bottomClip = getTrackById_const(bottomTid)->getClipByPosition(currentPos); + } + bool reverse = false; + if (topClip > -1 && bottomClip > -1) { + if (getClipPosition(topClip) + getClipPlaytime(topClip) < getClipPosition(bottomClip) + getClipPlaytime(bottomClip)) { + reverse = true; + } + } + std::unique_ptr props(nullptr); + if (reverse) { + props = std::make_unique(); + if (compoId == QLatin1String("dissolve")) { + props->set("reverse", 1); + } else if (compoId == QLatin1String("composite")) { + props->set("invert", 1); + } else if (compoId == QLatin1String("wipe")) { + props->set("geometry", "0=0% 0% 100% 100% 100%;-1=0% 0% 100% 100% 0%"); + } else if (compoId == QLatin1String("slide")) { + props->set("rect", "0=0% 0% 100% 100% 100%;-1=100% 0% 100% 100% 100%"); + } + } + res = res && requestCompositionInsertion(compoId, currentTrack, a_track, currentPos, duration, std::move(props), newId, undo, redo); if (res) { if (forcedTrack > -1 && isComposition(newId)) { m_allCompositions[newId]->setForceTrack(true); diff -Nru kdenlive-22.04.1/src/timeline2/model/trackmodel.cpp kdenlive-22.04.2/src/timeline2/model/trackmodel.cpp --- kdenlive-22.04.1/src/timeline2/model/trackmodel.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/timeline2/model/trackmodel.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -1808,11 +1808,14 @@ if (remixPlaylists && source_track != dest_track) { // A list of clip ids x playlists QMap rearrangedPlaylists; + QMap>> mixParameters; int ix = 0; int moveId = m_mixList.value(clipIds.second, -1); while (moveId > -1) { int current = m_allClips[moveId]->getSubPlaylistIndex(); rearrangedPlaylists.insert(moveId, current); + QVector> params = m_sameCompositions.at(moveId)->getAllParameters(); + mixParameters.insert(moveId, params); if (hasEndMix(moveId)) { moveId = m_mixList.value(moveId, -1); } else { @@ -1820,7 +1823,6 @@ } ix++; } - rearrange_playlists = [this, rearrangedPlaylists]() { // First, remove all clips on playlist 0 QMapIterator i(rearrangedPlaylists); @@ -1876,7 +1878,8 @@ if (m_sameCompositions.count(i.key()) > 0) { // There is a mix at clip start, adjust direction Mlt::Transition &transition = *static_cast(m_sameCompositions[i.key()]->getAsset()); - transition.set("reverse", i.value()); + bool reverse = i.value() == 1; + updateCompositionDirection(transition, reverse); } } return true; @@ -1884,7 +1887,7 @@ return false; } }; - rearrange_playlists_undo = [this, rearrangedPlaylists]() { + rearrange_playlists_undo = [this, rearrangedPlaylists, mixParameters]() { // First, remove all clips on playlist 1 QMapIterator i(rearrangedPlaylists); while (i.hasNext()) { @@ -1939,7 +1942,13 @@ if (m_sameCompositions.count(i.key()) > 0) { // There is a mix at clip start, adjust direction Mlt::Transition &transition = *static_cast(m_sameCompositions[i.key()]->getAsset()); - transition.set("reverse", 1 - i.value()); + if (mixParameters.contains(i.key())) { + // Restore all original params + QVector> params = mixParameters.value(i.key()); + for (const auto &p : qAsConst(params)) { + transition.set(p.first.toUtf8().constData(), p.second.toString().toUtf8().constData()); + } + } } } return true; @@ -1962,6 +1971,9 @@ t->set("kdenlive:mixcut", secondClipCut); t->set("start", -1); t->set("accepts_blanks", 1); + if (dest_track == 0) { + t->set("reverse", 1); + } m_track->plant_transition(*t.get(), 0, 1); assetName = QStringLiteral("mix"); xml = TransitionsRepository::get()->getXml(assetName); @@ -1972,13 +1984,14 @@ xml = TransitionsRepository::get()->getXml(assetName); t->set("kdenlive:mixcut", secondClipCut); t->set("kdenlive_id", "luma"); - m_track->plant_transition(*t.get(), 0, 1); if (dest_track == 0) { t->set("reverse", 1); } + m_track->plant_transition(*t.get(), 0, 1); } - if (dest_track == 0 && Xml::hasXmlParameter(xml, QStringLiteral("reverse"))) { - Xml::setXmlParameter(xml, QStringLiteral("reverse"), QStringLiteral("1")); + if (dest_track == 0) { + // Mix should be reversed + reverseCompositionXml(mixId, xml); } std::shared_ptr asset(new AssetParameterModel(std::move(t), xml, assetName, {ObjectType::TimelineMix, clipIds.second}, QString())); m_sameCompositions[clipIds.second] = asset; @@ -2544,7 +2557,12 @@ // First remove existing mix // lock MLT playlist so that we don't end up with invalid frames in monitor const QString currentAsset = m_sameCompositions[cid]->getAssetId(); - Fun local_redo = [this, cid, composition]() { + QVector> allParams = m_sameCompositions[cid]->getAllParameters(); + // Check if mix should be reversed + + bool reverse = m_allClips[cid]->getSubPlaylistIndex() == 0; + // TODO: handle revert mixes + Fun local_redo = [this, cid, composition, reverse]() { m_playlists[0].lock(); m_playlists[1].lock(); Mlt::Transition &transition = *static_cast(m_sameCompositions[cid]->getAsset()); @@ -2562,6 +2580,10 @@ m_track->plant_transition(*t.get(), 0, 1); t->set("kdenlive:mixcut", mixCutPos); QDomElement xml = TransitionsRepository::get()->getXml(composition); + if (reverse) { + // Mix should be reversed + reverseCompositionXml(composition, xml); + } std::shared_ptr asset(new AssetParameterModel(std::move(t), xml, composition, {ObjectType::TimelineMix, cid}, QString())); m_sameCompositions[cid] = asset; } @@ -2569,7 +2591,7 @@ m_playlists[1].unlock(); return true; }; - Fun local_undo = [this, cid, currentAsset]() { + Fun local_undo = [this, cid, currentAsset, allParams]() { m_playlists[0].lock(); m_playlists[1].lock(); Mlt::Transition &transition = *static_cast(m_sameCompositions[cid]->getAsset()); @@ -2585,6 +2607,17 @@ t->set_in_and_out(in, out); m_track->plant_transition(*t.get(), 0, 1); QDomElement xml = TransitionsRepository::get()->getXml(currentAsset); + QDomNodeList xmlParams = xml.elementsByTagName(QStringLiteral("parameter")); + for (int i = 0; i < xmlParams.count(); ++i) { + QDomElement currentParameter = xmlParams.item(i).toElement(); + QString paramName = currentParameter.attribute(QStringLiteral("name")); + for (const auto &p : qAsConst(allParams)) { + if (p.first == paramName) { + currentParameter.setAttribute(QStringLiteral("value"), p.second.toString()); + break; + } + } + } std::shared_ptr asset(new AssetParameterModel(std::move(t), xml, currentAsset, {ObjectType::TimelineMix, cid}, QString())); m_sameCompositions[cid] = asset; } @@ -2595,6 +2628,116 @@ UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); } +void TrackModel::reverseCompositionXml(const QString &composition, QDomElement xml) +{ + if (composition == QLatin1String("luma") || composition == QLatin1String("dissolve") || composition == QLatin1String("mix")) { + Xml::setXmlParameter(xml, QStringLiteral("reverse"), QStringLiteral("1")); + } else if (composition == QLatin1String("composite")) { + Xml::setXmlParameter(xml, QStringLiteral("invert"), QStringLiteral("1")); + } else if (composition == QLatin1String("wipe")) { + Xml::setXmlParameter(xml, QStringLiteral("geometry"), QStringLiteral("0=0% 0% 100% 100% 100%;-1=0% 0% 100% 100% 0%")); + } else if (composition == QLatin1String("slide")) { + Xml::setXmlParameter(xml, QStringLiteral("rect"), QStringLiteral("0=0% 0% 100% 100% 100%;-1=100% 0% 100% 100% 100%")); + } +} + +void TrackModel::updateCompositionDirection(Mlt::Transition &transition, bool reverse) +{ + QString composition(transition.get("kdenlive_id")); + if (composition.isEmpty()) { + composition = transition.get("mlt_service"); + } + if (composition == QLatin1String("luma") || composition == QLatin1String("dissolve") || composition == QLatin1String("mix")) { + transition.set("reverse", reverse ? 1 : 0); + } else if (composition == QLatin1String("composite")) { + transition.set("invert", reverse ? 1 : 0); + } else if (composition == QLatin1String("wipe")) { + if (reverse) { + transition.set("geometry", "0=0% 0% 100% 100% 100%;-1=0% 0% 100% 100% 0%"); + } else { + transition.set("geometry", "0=0% 0% 100% 100% 0%;-1=0% 0% 100% 100% 100%"); + } + } else if (composition == QLatin1String("slide")) { + QString currentSlide(transition.get("rect")); + currentSlide.replace(QLatin1Char('%'), QString()); + currentSlide = currentSlide.section(QLatin1Char('='), 1); + // Check if we start centered + if (!reverse && currentSlide.startsWith(QLatin1String("0 0 "))) { + currentSlide = currentSlide.section(QLatin1Char('='), 1); + QStringList sizes = currentSlide.split(QLatin1Char(' ')); + double x = sizes.at(0).toDouble(); + double y = sizes.at(1).toDouble(); + QString result = QStringLiteral("0="); + if (x > 0) { + result.append(QStringLiteral("-100% ")); + } else if (x < 0) { + result.append(QStringLiteral("100% ")); + } else { + result.append(QStringLiteral("0% ")); + } + if (y > 0) { + result.append(QStringLiteral("-100% ")); + } else if (y < 0) { + result.append(QStringLiteral("100% ")); + } else { + result.append(QStringLiteral("0% ")); + } + result.append(QStringLiteral("100% 100% 100%;-1=0% 0% 100% 100% 100%")); + transition.set("rect", result.toUtf8().constData()); + } else if (reverse) { + QString secondPart = currentSlide.section(QLatin1Char('='), 1); + if (secondPart.startsWith(QLatin1String("0 0 "))) { + QStringList sizes = currentSlide.split(QLatin1Char(' ')); + double x = sizes.at(0).toDouble(); + double y = sizes.at(1).toDouble(); + QString result = QStringLiteral("0=0% 0% 100% 100% 100%;-1="); + if (x > 0) { + result.append(QStringLiteral("-100% ")); + } else if (x < 0) { + result.append(QStringLiteral("100% ")); + } else { + result.append(QStringLiteral("0% ")); + } + if (y > 0) { + result.append(QStringLiteral("-100% ")); + } else if (y < 0) { + result.append(QStringLiteral("100% ")); + } else { + result.append(QStringLiteral("0% ")); + } + result.append(QStringLiteral("100% 100% 100%")); + transition.set("rect", result.toUtf8().constData()); + } + } + } +} + +bool TrackModel::mixIsReversed(int cid) const +{ + if (m_sameCompositions.count(cid) > 0) { + // There is a mix at clip start, adjust direction + Mlt::Transition &transition = *static_cast(m_sameCompositions.at(cid)->getAsset()); + QString composition(transition.get("kdenlive_id")); + if (composition.isEmpty()) { + composition = transition.get("mlt_service"); + } + if (composition == QLatin1String("luma") || composition == QLatin1String("dissolve") || composition == QLatin1String("mix")) { + return transition.get_int("reverse") == 1; + } else if (composition == QLatin1String("composite")) { + return transition.get_int("invert") == 1; + } else if (composition == QLatin1String("wipe")) { + QString geom = transition.get("geometry"); + geom.replace(QLatin1Char('%'), QString()); + return geom.contains(QStringLiteral(" 100;")); + } else if (composition == QLatin1String("slide")) { + QString geom(transition.get("rect")); + geom.replace(QLatin1Char('%'), QString()); + return geom.startsWith(QStringLiteral("0=0 0 ")); + } + } + return false; +} + QVariantList TrackModel::stackZones() const { return m_effectStack->getEffectZones(); diff -Nru kdenlive-22.04.1/src/timeline2/model/trackmodel.hpp kdenlive-22.04.2/src/timeline2/model/trackmodel.hpp --- kdenlive-22.04.1/src/timeline2/model/trackmodel.hpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/timeline2/model/trackmodel.hpp 2022-06-12 20:11:07.000000000 +0000 @@ -330,6 +330,8 @@ int isOnCut(int cid); /** @brief Returns all mix info as xml */ QDomElement mixXml(QDomDocument &document, int cid) const; + /** @brief Check if a mix is reversed (moslty used in tests) */ + bool mixIsReversed(int cid) const; public slots: /** Delete the current track and all its associated clips */ @@ -359,6 +361,8 @@ /// This is a lock that ensures safety in case of concurrent access mutable QReadWriteLock m_lock; + void reverseCompositionXml(const QString &composition, QDomElement xml); + void updateCompositionDirection(Mlt::Transition &transition, bool reverse); protected: std::shared_ptr m_effectStack; diff -Nru kdenlive-22.04.1/src/timeline2/view/qml/Clip.qml kdenlive-22.04.2/src/timeline2/view/qml/Clip.qml --- kdenlive-22.04.1/src/timeline2/view/qml/Clip.qml 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/timeline2/view/qml/Clip.qml 2022-06-12 20:11:07.000000000 +0000 @@ -169,6 +169,10 @@ } clipRoot.y = Logic.getTrackById(clipRoot.fakeTid).y clipRoot.height = Logic.getTrackById(clipRoot.fakeTid).height + } else { + clipRoot.height = Qt.binding(function () { + return parentTrack.height + }) } } diff -Nru kdenlive-22.04.1/src/timeline2/view/timelinecontroller.cpp kdenlive-22.04.2/src/timeline2/view/timelinecontroller.cpp --- kdenlive-22.04.1/src/timeline2/view/timelinecontroller.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/timeline2/view/timelinecontroller.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -447,6 +447,7 @@ int TimelineController::insertNewCompositionAtPos(int tid, int position, const QString &transitionId) { + // TODO: adjust position and duration to existing clips ? return insertComposition(tid, position, transitionId, true); } @@ -517,10 +518,12 @@ props = std::make_unique(); if (transitionId == QLatin1String("dissolve")) { props->set("reverse", 1); - } else if (transitionId == QLatin1String("composite") || transitionId == QLatin1String("slide")) { + } else if (transitionId == QLatin1String("composite")) { props->set("invert", 1); } else if (transitionId == QLatin1String("wipe")) { props->set("geometry", "0=0% 0% 100% 100% 100%;-1=0% 0% 100% 100% 0%"); + } else if (transitionId == QLatin1String("slide")) { + props->set("rect", "0=0% 0% 100% 100% 100%;-1=100% 0% 100% 100% 100%"); } } if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, std::move(props), id, logUndo)) { @@ -541,7 +544,34 @@ { int id; int duration = pCore->getDurationFromString(KdenliveSettings::transition_duration()); - if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, nullptr, id, logUndo)) { + // Check if composition should be reversed (top clip at beginning, bottom at end) + int a_track = m_model->getPreviousVideoTrackPos(tid); + int topClip = m_model->getTrackById_const(tid)->getClipByPosition(position); + int bottomTid = m_model->getTrackIndexFromPosition(a_track - 1); + int bottomClip = -1; + if (bottomTid > -1) { + bottomClip = m_model->getTrackById_const(bottomTid)->getClipByPosition(position); + } + bool reverse = false; + if (topClip > -1 && bottomClip > -1) { + if (m_model->getClipPosition(topClip) + m_model->getClipPlaytime(topClip) < m_model->getClipPosition(bottomClip) + m_model->getClipPlaytime(bottomClip)) { + reverse = true; + } + } + std::unique_ptr props(nullptr); + if (reverse) { + props = std::make_unique(); + if (transitionId == QLatin1String("dissolve")) { + props->set("reverse", 1); + } else if (transitionId == QLatin1String("composite")) { + props->set("invert", 1); + } else if (transitionId == QLatin1String("wipe")) { + props->set("geometry", "0=0% 0% 100% 100% 100%;-1=0% 0% 100% 100% 0%"); + } else if (transitionId == QLatin1String("slide")) { + props->set("rect", "0=0% 0% 100% 100% 100%;-1=100% 0% 100% 100% 100%"); + } + } + if (!m_model->requestCompositionInsertion(transitionId, tid, position, duration, std::move(props), id, logUndo)) { id = -1; } return id; @@ -1272,7 +1302,7 @@ if (frame == -1) { frame = pCore->getTimelinePosition(); } - CommentedTime marker = pCore->currentDoc()->getGuideModel()->getMarker(GenTime(frame, pCore->getCurrentFps()), &markerFound); + CommentedTime marker = pCore->currentDoc()->getGuideModel()->getMarker(frame, &markerFound); if (!markerFound) { if (deleteOnly) { pCore->displayMessage(i18n("No guide found at current position"), ErrorMessage, 500); @@ -3943,6 +3973,26 @@ int trackId = m_model->m_allClips[clipId]->getFakeTrackId(); if (m_model->getClipPosition(clipId) == position && m_model->getClipTrackId(clipId) == trackId) { qDebug() << "* * ** END FAKE; NO MOVE RQSTED"; + // Ensure clip height binds again with parent track height + if (m_model->m_groups->isInGroup(clipId)) { + int groupId = m_model->m_groups->getRootId(clipId); + auto all_items = m_model->m_groups->getLeaves(groupId); + for (int item : all_items) { + if (m_model->isClip(item)) { + m_model->m_allClips[item]->setFakeTrackId(-1); + QModelIndex modelIndex = m_model->makeClipIndexFromID(item); + if (modelIndex.isValid()) { + m_model->notifyChange(modelIndex, modelIndex, TimelineModel::FakeTrackIdRole); + } + } + } + } else { + m_model->m_allClips[clipId]->setFakeTrackId(-1); + QModelIndex modelIndex = m_model->makeClipIndexFromID(clipId); + if (modelIndex.isValid()) { + m_model->notifyChange(modelIndex, modelIndex, TimelineModel::FakeTrackIdRole); + } + } return true; } if (m_model->m_groups->isInGroup(clipId)) { diff -Nru kdenlive-22.04.1/src/ui/renderwidget_ui.ui kdenlive-22.04.2/src/ui/renderwidget_ui.ui --- kdenlive-22.04.1/src/ui/renderwidget_ui.ui 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/ui/renderwidget_ui.ui 2022-06-12 20:11:07.000000000 +0000 @@ -6,14 +6,14 @@ 0 0 - 1011 - 795 + 900 + 900 Rendering - + 0 @@ -417,9 +417,6 @@ 0 - - QComboBox::AdjustToMinimumContentsLength - @@ -429,348 +426,370 @@ - - - - - - Video - - - true - - - - - - Render at Preview Resolution - - - - - - - Use Proxy Clips - - - - - - - Enabled - - - - - - - Rescale: - - - - - - - - - 1 - - - 10000 - - - - - - - - 0 - 0 - - - - x - - - - - - - 1 - - - 10000 - - - - - - - Preserve aspect ratio - - - ... - - - - .. - - - true - - - false - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - true - - - - - - - Overlay: - - - - - - - - - - Audio - - - true - - - - - - Separate file for each audio track - - - - - - - - - - Custom Quality - - - true - - - - - - - - Low - - - - - - - - 0 - 0 - - - - Compromise file size versus quality. - - - 0 - - - 4 - - - 3 - - - Qt::Horizontal - - - - - - - 75% - - - - - - - High - - - - - - - - - - - - Encoder - - - - - - For a given quality, tune the compromise between encoding time and output file size (faster encoding ends with larger file). - - - Speed: - - - - - - - - 0 - 0 - - - - For a given quality, tune the compromise between encoding time and output file size (faster encoding ends with larger file). - - - 1 - - - 2 - - - 1 - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - Encoding threads - - - Auto - - - - - - - Threads: - - - - - - - Parallel Processing (experimental!) - - - - - - - - - - 2 pass - - - - - - - Export metadata - - - - - - - Open browser window after export - - - - - - - Play after render - - - - - - - - 0 - 0 - - - - true - - - false - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - + + + QFrame::NoFrame + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 334 + 855 + + + + + + + Video + + + true + + + + + + Render at Preview Resolution + + + + + + + Use Proxy Clips + + + + + + + Enabled + + + + + + + Rescale: + + + + + + + + + 1 + + + 10000 + + + + + + + + 0 + 0 + + + + x + + + + + + + 1 + + + 10000 + + + + + + + Preserve aspect ratio + + + ... + + + + .. + + + true + + + false + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + true + + + + + + + Overlay: + + + + + + + + + + Audio + + + true + + + + + + Separate file for each audio track + + + + + + + + + + Custom Quality + + + true + + + + + + + + Low + + + + + + + + 0 + 0 + + + + Compromise file size versus quality. + + + 0 + + + 4 + + + 3 + + + Qt::Horizontal + + + + + + + 75% + + + + + + + High + + + + + + + + + + + + Encoder + + + + + + For a given quality, tune the compromise between encoding time and output file size (faster encoding ends with larger file). + + + Speed: + + + + + + + + 0 + 0 + + + + For a given quality, tune the compromise between encoding time and output file size (faster encoding ends with larger file). + + + 1 + + + 2 + + + 1 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Encoding threads + + + Auto + + + + + + + Threads: + + + + + + + Parallel Processing (experimental!) + + + + + + + + + + 2 pass + + + + + + + Export metadata + + + + + + + Open browser window after export + + + + + + + Play after render + + + + + + + + 0 + 0 + + + + true + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + @@ -1013,11 +1032,13 @@ KMessageWidget QFrame
kmessagewidget.h
+ 1 KSeparator QFrame
kseparator.h
+ 1
diff -Nru kdenlive-22.04.1/src/ui/resourcewidget_ui.ui kdenlive-22.04.2/src/ui/resourcewidget_ui.ui --- kdenlive-22.04.1/src/ui/resourcewidget_ui.ui 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/src/ui/resourcewidget_ui.ui 2022-06-12 20:11:07.000000000 +0000 @@ -6,11 +6,24 @@ 0 0 - 507 - 457 + 313 + 335 + + + + Qt::Horizontal + + + + 81 + 20 + + + + @@ -191,42 +204,38 @@ - - - - - 0 - 0 - + + + + ... - - false + + + .. - - false + + true - - - - - 40 - 0 - + + + + + 0 + 0 + - - - 100 - 16777215 - + + Import - - Qt::Horizontal + + + .. - + @@ -243,51 +252,26 @@ - - - - ... - - - - .. - - - true - - - - - - - Qt::Horizontal - - + + + - 81 - 20 + 40 + 0 - - - - - - - 0 - 0 - - - - Import + + + 100 + 16777215 + - - - .. + + Qt::Horizontal - + ... @@ -345,15 +329,31 @@ + + + + + 0 + 0 + + + + false + + + false + + + splitter service_box_2 - message_line button_preview button_import button_zoomin button_zoomout slider_zoom + message_line diff -Nru kdenlive-22.04.1/tests/mixtest.cpp kdenlive-22.04.2/tests/mixtest.cpp --- kdenlive-22.04.1/tests/mixtest.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/tests/mixtest.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -60,6 +60,7 @@ int cid2; int cid3; int cid4; + int cid5; REQUIRE(timeline->requestClipInsertion(binId, tid2, 100, cid1)); REQUIRE(timeline->requestItemResize(cid1, 10, true, true)); @@ -89,6 +90,23 @@ REQUIRE(timeline->getTrackById_const(tid1)->mixCount() == 0); REQUIRE(timeline->getTrackById_const(tid2)->mixCount() == 0); }; + + auto state0b = [&]() { + REQUIRE(timeline->getClipsCount() == 8); + REQUIRE(timeline->getClipPlaytime(cid1) == 10); + REQUIRE(timeline->getClipPosition(cid1) == 100); + REQUIRE(timeline->getClipPlaytime(cid2) == 10); + REQUIRE(timeline->getClipPosition(cid2) == 110); + REQUIRE(timeline->getClipPlaytime(cid5) == 10); + REQUIRE(timeline->getClipPosition(cid5) == 120); + REQUIRE(timeline->getClipPosition(cid3) == 500); + REQUIRE(timeline->getClipPlaytime(cid3) == 20); + REQUIRE(timeline->getClipPosition(cid4) == 520); + REQUIRE(timeline->getClipPlaytime(cid4) == 20); + REQUIRE(timeline->m_allClips[cid4]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->getTrackById_const(tid1)->mixCount() == 0); + REQUIRE(timeline->getTrackById_const(tid2)->mixCount() == 0); + }; auto state1 = [&]() { REQUIRE(timeline->getClipsCount() == 6); @@ -99,6 +117,18 @@ REQUIRE(timeline->getTrackById_const(tid3)->mixCount() == 1); REQUIRE(timeline->getTrackById_const(tid2)->mixCount() == 1); }; + + auto state1b = [&]() { + REQUIRE(timeline->getClipsCount() == 8); + REQUIRE(timeline->getClipPlaytime(cid1) > 10); + REQUIRE(timeline->getClipPosition(cid1) == 100); + REQUIRE(timeline->getClipPlaytime(cid2) > 10); + REQUIRE(timeline->getClipPosition(cid2) < 110); + REQUIRE(timeline->getClipPlaytime(cid5) == 10); + REQUIRE(timeline->getClipPosition(cid5) == 120); + REQUIRE(timeline->getTrackById_const(tid3)->mixCount() == 1); + REQUIRE(timeline->getTrackById_const(tid2)->mixCount() == 1); + }; auto state3 = [&, mixDuration]() { REQUIRE(timeline->getClipsCount() == 6); @@ -206,7 +236,7 @@ { state0(); // insert third color clip - int cid5; + cid5 = -1; REQUIRE(timeline->requestClipInsertion(binId2, tid2, 540, cid5)); REQUIRE(timeline->requestItemResize(cid5, 20, true, true)); REQUIRE(timeline->getClipPosition(cid5) == 540); @@ -333,6 +363,57 @@ undoStack->undo(); state0(); } + + SECTION("Create chained mixes on AV clips") + { + // CID 1 length=10, pos=100, CID2 length=10, pos=110 + // Default mix duration = 25 frames (12 before / 13 after) + // Resize CID2 so that it has some space to expand left + REQUIRE(timeline->requestItemResize(cid2, 30, true, true) == 30); + REQUIRE(timeline->requestItemResize(cid2, 10, false, true) == 10); + REQUIRE(timeline->requestClipMove(cid2, tid2, 110)); + state0(); + + // Create a third AV clip and make some space + cid5 = -1; + REQUIRE(timeline->requestClipInsertion(binId, tid2, 120, cid5)); + REQUIRE(timeline->requestItemResize(cid5, 30, true, true) == 30); + REQUIRE(timeline->requestItemResize(cid5, 10, false, true) == 10); + REQUIRE(timeline->requestClipMove(cid5, tid2, 120)); + + state0b(); + + // CID 1 length=10, pos=100, CID2 length=20, pos=130, CID5 length=20, pos=130 + + // Create mix between cid1 and cid2 + REQUIRE(timeline->mixClip(cid2)); + state1b(); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid2) == false); + int audio2 = timeline->getClipSplitPartner(cid2); + REQUIRE(timeline->getTrackById_const(tid3)->mixIsReversed(audio2) == false); + + // Create mix between cid2 and cid5 + REQUIRE(timeline->mixClip(cid5)); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid2) == false); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid5) == true); + REQUIRE(timeline->getTrackById_const(tid3)->mixIsReversed(audio2) == false); + int audio5 = timeline->getClipSplitPartner(cid5); + REQUIRE(timeline->getTrackById_const(tid3)->mixIsReversed(audio5) == true); + // Undo cid5 mix + undoStack->undo(); + + + state1b(); + // Undo cid2 mix + undoStack->undo(); + // Undo cid5 + undoStack->undo(); + undoStack->undo(); + undoStack->undo(); + undoStack->undo(); + + state0(); + } SECTION("Create mix on color clip and resize") { @@ -492,7 +573,7 @@ SECTION("Test chained mixes on color clips") { // Add 2 more color clips - int cid5; + cid5 = -1; int cid6; int cid7; state0(); @@ -567,6 +648,157 @@ state0(); } + + SECTION("Test chained mixes and check mix direction") + { + // Add 2 more color clips + cid5 = -1; + int cid6; + int cid7; + state0(); + REQUIRE(timeline->requestClipInsertion(binId2, tid2, 540, cid5)); + REQUIRE(timeline->requestItemResize(cid5, 20, true, true)); + REQUIRE(timeline->requestClipInsertion(binId2, tid2, 560, cid6)); + REQUIRE(timeline->requestItemResize(cid6, 40, true, true)); + REQUIRE(timeline->requestClipInsertion(binId2, tid2, 600, cid7)); + REQUIRE(timeline->requestItemResize(cid7, 20, true, true)); + + // Cid3 pos=500, duration=20 + // Cid4 pos=520, duration=20 + // Cid5 pos=540, duration=20 + // Cid6 pos=560, duration=40 + // Cid7 pos=600, duration=20 + + auto mix0 = [&]() { + REQUIRE(timeline->getClipsCount() == 9); + REQUIRE(timeline->m_allClips[cid3]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->m_allClips[cid4]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->m_allClips[cid5]->getSubPlaylistIndex() == 1); + REQUIRE(timeline->m_allClips[cid6]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->m_allClips[cid7]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->getTrackById_const(tid2)->mixCount() == 1); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid5) == false); + }; + + auto mix1 = [&]() { + REQUIRE(timeline->m_allClips[cid3]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->m_allClips[cid4]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->m_allClips[cid5]->getSubPlaylistIndex() == 1); + REQUIRE(timeline->m_allClips[cid6]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->m_allClips[cid7]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->getTrackById_const(tid2)->mixCount() == 2); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid5) == false); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid6) == true); + }; + + auto mix2 = [&]() { + REQUIRE(timeline->m_allClips[cid3]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->m_allClips[cid4]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->m_allClips[cid5]->getSubPlaylistIndex() == 1); + REQUIRE(timeline->m_allClips[cid6]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->m_allClips[cid7]->getSubPlaylistIndex() == 1); + REQUIRE(timeline->getTrackById_const(tid2)->mixCount() == 3); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid5) == false); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid6) == true); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid7) == false); + }; + + auto mix3 = [&]() { + REQUIRE(timeline->m_allClips[cid3]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->m_allClips[cid4]->getSubPlaylistIndex() == 1); + REQUIRE(timeline->m_allClips[cid5]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->m_allClips[cid6]->getSubPlaylistIndex() == 1); + REQUIRE(timeline->m_allClips[cid7]->getSubPlaylistIndex() == 0); + REQUIRE(timeline->getTrackById_const(tid2)->mixCount() == 4); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid4) == false); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid5) == true); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid6) == false); + REQUIRE(timeline->getTrackById_const(tid2)->mixIsReversed(cid7) == true); + }; + + // Mix 4 and 5 + REQUIRE(timeline->mixClip(cid5)); + mix0(); + + // Mix 5 and 6 + REQUIRE(timeline->mixClip(cid6)); + mix1(); + + // Mix 6 and 7 + REQUIRE(timeline->mixClip(cid7)); + mix2(); + + // Mix 3 and 4, this will revert all subsequent mixes + REQUIRE(timeline->mixClip(cid4)); + mix3(); + + // Undo mix 3 and 4 + undoStack->undo(); + mix2(); + + // Now switch mixes to Slide type + timeline->switchComposition(cid7, QString("slide")); + timeline->switchComposition(cid6, QString("slide")); + timeline->switchComposition(cid5, QString("slide")); + mix2(); + + // Mix 3 and 4, this will revert all subsequent mixes + REQUIRE(timeline->mixClip(cid4)); + mix3(); + + // Undo mix 3 and 4 + undoStack->undo(); + mix2(); + + // Now switch mixes to Wipe type + timeline->switchComposition(cid7, QString("wipe")); + timeline->switchComposition(cid6, QString("wipe")); + timeline->switchComposition(cid5, QString("wipe")); + mix2(); + + // Mix 3 and 4, this will revert all subsequent mixes + REQUIRE(timeline->mixClip(cid4)); + mix3(); + + // Undo mix 3 and 4 + undoStack->undo(); + mix2(); + + // Undo Wipe mix switch on cid5 + undoStack->undo(); + // Undo mix switch on cid6 + undoStack->undo(); + // Undo mix switch on cid7 + undoStack->undo(); + mix2(); + + // Undo Slide mix switch on cid5 + undoStack->undo(); + // Undo mix switch on cid6 + undoStack->undo(); + // Undo mix switch on cid7 + undoStack->undo(); + mix2(); + + // Undo mix 6 and 7 + undoStack->undo(); + mix1(); + // Undo mix 5 and 6 + undoStack->undo(); + mix0(); + // Undo mix 4 and 5 + undoStack->undo(); + + // Undo insert/resize ops + undoStack->undo(); + undoStack->undo(); + undoStack->undo(); + undoStack->undo(); + undoStack->undo(); + undoStack->undo(); + + state0(); + } binModel->clean(); pCore->m_projectManager = nullptr; } diff -Nru kdenlive-22.04.1/tests/movetest.cpp kdenlive-22.04.2/tests/movetest.cpp --- kdenlive-22.04.1/tests/movetest.cpp 1970-01-01 00:00:00.000000000 +0000 +++ kdenlive-22.04.2/tests/movetest.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -0,0 +1,99 @@ +#include "catch.hpp" +#include "doc/docundostack.hpp" +#include "test_utils.hpp" + +#include "definitions.h" +#define private public +#define protected public +#include "core.h" + +using namespace fakeit; +Mlt::Profile profile_move; + +TEST_CASE("Cut undo/redo", "[MoveClips]") +{ + // Create timeline + auto binModel = pCore->projectItemModel(); + binModel->clean(); + std::shared_ptr undoStack = std::make_shared(nullptr); + std::shared_ptr guideModel = std::make_shared(undoStack); + + // Here we do some trickery to enable testing. + // We mock the project class so that the undoStack function returns our undoStack + + Mock pmMock; + When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); + When(Method(pmMock, cacheDir)).AlwaysReturn(QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))); + + ProjectManager &mocked = pmMock.get(); + pCore->m_projectManager = &mocked; + + // We also mock timeline object to spy few functions and mock others + TimelineItemModel tim(&profile_move, undoStack); + Mock timMock(tim); + auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); + TimelineItemModel::finishConstruct(timeline, guideModel); + + // Create a request + int tid1 = TrackModel::construct(timeline, -1, -1, QString(), true); + int tid3 = TrackModel::construct(timeline, -1, -1, QString(), true); + int tid2 = TrackModel::construct(timeline); + int tid4 = TrackModel::construct(timeline); + + // Create clip with audio (40 frames long) + QString binId = createAVProducer(profile_move, binModel); + + // Setup insert stream data + QMap audioInfo; + audioInfo.insert(1,QStringLiteral("stream1")); + timeline->m_binAudioTargets = audioInfo; + + // Create AV clip 1 + int cid1; + int cid2; + int cid3; + int cid4; + + REQUIRE(timeline->requestClipInsertion(binId, tid2, 100, cid1)); + cid2 = timeline->getClipSplitPartner(cid1); + + SECTION("Ensure all clip instances on a track use the same producer") + { + REQUIRE(timeline->getItemTrackId(cid2) == tid3); + REQUIRE(timeline->getItemTrackId(cid1) == tid2); + Mlt::Producer prod1 = *(timeline->getClipPtr(cid1)); + Mlt::Producer prod2 = *(timeline->getClipPtr(cid2)); + // Clips on different tracks shoud not use the same producer + REQUIRE(!prod1.same_clip(prod2)); + + // Split clip + REQUIRE(TimelineFunctions::requestClipCut(timeline, cid1, 110)); + cid3 = timeline->getClipByPosition(tid2, 111); + cid4 = timeline->getClipSplitPartner(cid3); + REQUIRE(timeline->getItemTrackId(cid4) == tid3); + REQUIRE(timeline->getItemTrackId(cid3) == tid2); + + Mlt::Producer prod3 = *(timeline->getClipPtr(cid3)); + Mlt::Producer prod4 = *(timeline->getClipPtr(cid4)); + // Clips on different tracks shoud not use the same producer + REQUIRE(!prod3.same_clip(prod4)); + // Clips on same track shoud use the same producer + REQUIRE(prod1.same_clip(prod3)); + REQUIRE(prod2.same_clip(prod4)); + + // Undo and redo cut, then ensure the producers are still correct + undoStack->undo(); + undoStack->redo(); + + prod3 = *(timeline->getClipPtr(cid3)); + prod4 = *(timeline->getClipPtr(cid4)); + // Clips on different tracks shoud not use the same producer + REQUIRE(!prod3.same_clip(prod4)); + // Clips on same track shoud use the same producer + REQUIRE(prod1.same_clip(prod3)); + REQUIRE(prod2.same_clip(prod4)); + } + binModel->clean(); + pCore->m_projectManager = nullptr; + +} diff -Nru kdenlive-22.04.1/tests/snaptest.cpp kdenlive-22.04.2/tests/snaptest.cpp --- kdenlive-22.04.1/tests/snaptest.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/tests/snaptest.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -1,7 +1,5 @@ -#include "catch.hpp" +#include "test_utils.hpp" #include "timeline2/model/snapmodel.hpp" -#include -#include TEST_CASE("Snap points model test", "[SnapModel]") { diff -Nru kdenlive-22.04.1/tests/test_utils.cpp kdenlive-22.04.2/tests/test_utils.cpp --- kdenlive-22.04.1/tests/test_utils.cpp 2022-05-15 21:39:54.000000000 +0000 +++ kdenlive-22.04.2/tests/test_utils.cpp 2022-06-12 20:11:07.000000000 +0000 @@ -27,12 +27,12 @@ // In case the test system does not have avformat support, we can switch to the integrated blipflash producer std::shared_ptr producer = std::make_shared(prof, "blipflash"); + REQUIRE(producer->is_valid()); + producer->set("length", length); producer->set_in_and_out(0, length - 1); producer->set("kdenlive:duration", length); - REQUIRE(producer->is_valid()); - QString binId = QString::number(binModel->getFreeClipId()); auto binClip = ProjectClip::construct(binId, QIcon(), binModel, producer); binClip->forceLimitedDuration();