File indexing completed on 2024-04-28 08:43:34
0001 /* 0002 SPDX-FileCopyrightText: 2012 Till Theato <root@ttill.de> 0003 SPDX-FileCopyrightText: 2014 Jean-Baptiste Mardelle <jb@kdenlive.org> 0004 SPDX-FileCopyrightText: 2017 Nicolas Carion 0005 This file is part of Kdenlive. See www.kdenlive.org. 0006 0007 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0008 */ 0009 0010 #include "projectitemmodel.h" 0011 #include "abstractprojectitem.h" 0012 #include "binplaylist.hpp" 0013 #include "core.h" 0014 #include "doc/kdenlivedoc.h" 0015 #include "filewatcher.hpp" 0016 #include "jobs/audiolevelstask.h" 0017 #include "jobs/cliploadtask.h" 0018 #include "kdenlivesettings.h" 0019 #include "lib/localeHandling.h" 0020 #include "macros.hpp" 0021 #include "profiles/profilemodel.hpp" 0022 #include "project/projectmanager.h" 0023 #include "projectclip.h" 0024 #include "projectfolder.h" 0025 #include "projectsubclip.h" 0026 #include "utils/thumbnailcache.hpp" 0027 #include "xml/xml.hpp" 0028 0029 #include <KLocalizedString> 0030 0031 #include <QIcon> 0032 #include <QJsonArray> 0033 #include <QJsonDocument> 0034 #include <QJsonObject> 0035 #include <QMimeData> 0036 #include <QProgressDialog> 0037 0038 #include <mlt++/Mlt.h> 0039 #include <queue> 0040 #include <qvarlengtharray.h> 0041 #include <utility> 0042 0043 ProjectItemModel::ProjectItemModel(QObject *parent) 0044 : AbstractTreeModel(parent) 0045 , closing(false) 0046 , m_lock(QReadWriteLock::Recursive) 0047 , m_binPlaylist(nullptr) 0048 , m_fileWatcher(new FileWatcher()) 0049 , m_nextId(1) 0050 , m_blankThumb() 0051 , m_dragType(PlaylistState::Disabled) 0052 , m_uuid(QUuid::createUuid()) 0053 , m_sequenceFolderId(-1) 0054 { 0055 QPixmap pix(QSize(160, 90)); 0056 pix.fill(Qt::lightGray); 0057 m_blankThumb.addPixmap(pix); 0058 connect(m_fileWatcher.get(), &FileWatcher::binClipModified, this, &ProjectItemModel::reloadClip); 0059 connect(m_fileWatcher.get(), &FileWatcher::binClipWaiting, this, &ProjectItemModel::setClipWaiting); 0060 connect(m_fileWatcher.get(), &FileWatcher::binClipMissing, this, &ProjectItemModel::setClipInvalid); 0061 missingClipTimer.setInterval(500); 0062 missingClipTimer.setSingleShot(true); 0063 connect(&missingClipTimer, &QTimer::timeout, this, &ProjectItemModel::slotUpdateInvalidCount); 0064 } 0065 0066 std::shared_ptr<ProjectItemModel> ProjectItemModel::construct(QObject *parent) 0067 { 0068 std::shared_ptr<ProjectItemModel> self(new ProjectItemModel(parent)); 0069 self->rootItem = ProjectFolder::construct(self); 0070 return self; 0071 } 0072 0073 ProjectItemModel::~ProjectItemModel() = default; 0074 0075 void ProjectItemModel::buildPlaylist(const QUuid uuid) 0076 { 0077 m_uuid = uuid; 0078 m_fileWatcher->clear(); 0079 m_extraPlaylists.clear(); 0080 Q_ASSERT(m_projectTractor.use_count() <= 1); 0081 m_projectTractor.reset(); 0082 m_binPlaylist.reset(new BinPlaylist(uuid)); 0083 m_projectTractor.reset(new Mlt::Tractor(pCore->getProjectProfile())); 0084 m_projectTractor->set("kdenlive:projectTractor", 1); 0085 m_binPlaylist->setRetainIn(m_projectTractor.get()); 0086 } 0087 0088 int ProjectItemModel::mapToColumn(int column) const 0089 { 0090 switch (column) { 0091 case 0: 0092 return AbstractProjectItem::DataName; 0093 break; 0094 case 1: 0095 return AbstractProjectItem::DataDate; 0096 break; 0097 case 2: 0098 return AbstractProjectItem::DataDescription; 0099 break; 0100 case 3: 0101 return AbstractProjectItem::ClipType; 0102 break; 0103 case 4: 0104 return AbstractProjectItem::DataTag; 0105 break; 0106 case 5: 0107 return AbstractProjectItem::DataDuration; 0108 break; 0109 case 6: 0110 return AbstractProjectItem::DataId; 0111 break; 0112 case 7: 0113 return AbstractProjectItem::DataRating; 0114 break; 0115 case 8: 0116 return AbstractProjectItem::UsageCount; 0117 break; 0118 default: 0119 return AbstractProjectItem::DataName; 0120 } 0121 } 0122 0123 QList<int> ProjectItemModel::mapDataToColumn(AbstractProjectItem::DataType type) const 0124 { 0125 // Some data types are used in several columns, for example usage count has its 0126 // own column for sorting but is also displayed in column 0 0127 switch (type) { 0128 case AbstractProjectItem::DataName: 0129 case AbstractProjectItem::DataThumbnail: 0130 case AbstractProjectItem::IconOverlay: 0131 case AbstractProjectItem::JobProgress: 0132 return {0}; 0133 break; 0134 case AbstractProjectItem::DataDate: 0135 return {1}; 0136 break; 0137 case AbstractProjectItem::DataDescription: 0138 return {2}; 0139 break; 0140 case AbstractProjectItem::ClipType: 0141 return {3}; 0142 break; 0143 case AbstractProjectItem::DataTag: 0144 return {0, 4}; 0145 break; 0146 case AbstractProjectItem::DataDuration: 0147 return {0, 5}; 0148 break; 0149 case AbstractProjectItem::DataId: 0150 return {6}; 0151 break; 0152 case AbstractProjectItem::DataRating: 0153 return {7}; 0154 break; 0155 case AbstractProjectItem::UsageCount: 0156 return {0, 8}; 0157 break; 0158 default: 0159 return {}; 0160 } 0161 } 0162 0163 QVariant ProjectItemModel::data(const QModelIndex &index, int role) const 0164 { 0165 READ_LOCK(); 0166 if (!index.isValid()) { 0167 return QVariant(); 0168 } 0169 if (role == Qt::DisplayRole || role == Qt::EditRole) { 0170 std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(index); 0171 auto type = static_cast<AbstractProjectItem::DataType>(mapToColumn(index.column())); 0172 QVariant ret = item->getData(type); 0173 return ret; 0174 } 0175 if (role == Qt::DecorationRole) { 0176 if (index.column() != 0) { 0177 return QVariant(); 0178 } 0179 // Data has to be returned as icon to allow the view to scale it 0180 std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(index); 0181 return item->icon(); 0182 } 0183 std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(index); 0184 return item->getData(static_cast<AbstractProjectItem::DataType>(role)); 0185 } 0186 0187 bool ProjectItemModel::setData(const QModelIndex &index, const QVariant &value, int role) 0188 { 0189 QWriteLocker locker(&m_lock); 0190 std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(index); 0191 if (item->rename(value.toString(), index.column())) { 0192 Q_EMIT dataChanged(index, index, {role}); 0193 return true; 0194 } 0195 // Item name was not changed 0196 return false; 0197 } 0198 0199 Qt::ItemFlags ProjectItemModel::flags(const QModelIndex &index) const 0200 { 0201 /*return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable;*/ 0202 READ_LOCK(); 0203 if (!index.isValid()) { 0204 return Qt::ItemIsDropEnabled; 0205 } 0206 std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(index); 0207 AbstractProjectItem::PROJECTITEMTYPE type = item->itemType(); 0208 switch (type) { 0209 case AbstractProjectItem::FolderItem: 0210 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable; 0211 break; 0212 case AbstractProjectItem::ClipItem: 0213 if (!item->statusReady()) { 0214 return Qt::ItemIsEnabled | Qt::ItemIsSelectable; 0215 } 0216 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable; 0217 break; 0218 case AbstractProjectItem::SubClipItem: 0219 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; 0220 break; 0221 default: 0222 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; 0223 } 0224 } 0225 0226 // cppcheck-suppress unusedFunction 0227 bool ProjectItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) 0228 { 0229 Q_UNUSED(row) 0230 Q_UNUSED(column) 0231 QWriteLocker locker(&m_lock); 0232 if (action == Qt::IgnoreAction) { 0233 return true; 0234 } 0235 0236 if (data->hasUrls()) { 0237 Q_EMIT urlsDropped(data->urls(), parent); 0238 return true; 0239 } 0240 0241 if (data->hasFormat(QStringLiteral("text/producerslist"))) { 0242 // Dropping an Bin item 0243 const QStringList ids = QString(data->data(QStringLiteral("text/producerslist"))).split(QLatin1Char(';')); 0244 if (ids.constFirst().contains(QLatin1Char('/'))) { 0245 // subclip zone 0246 QStringList clipData = ids.constFirst().split(QLatin1Char('/')); 0247 if (clipData.length() >= 3) { 0248 QString bid = clipData.at(0); 0249 if (bid.startsWith(QLatin1Char('A')) || bid.startsWith(QLatin1Char('V'))) { 0250 bid.remove(0, 1); 0251 } 0252 std::shared_ptr<ProjectClip> masterClip = getClipByBinID(bid); 0253 std::shared_ptr<ProjectSubClip> sub = masterClip->getSubClip(clipData.at(1).toInt(), clipData.at(2).toInt()); 0254 if (sub != nullptr) { 0255 // This zone already exists 0256 return false; 0257 } 0258 QString id; 0259 return requestAddBinSubClip(id, clipData.at(1).toInt(), clipData.at(2).toInt(), {}, bid); 0260 } else { 0261 // error, malformed clip zone, abort 0262 return false; 0263 } 0264 } else { 0265 Q_EMIT itemDropped(ids, parent); 0266 } 0267 return true; 0268 } 0269 0270 if (data->hasFormat(QStringLiteral("kdenlive/effect"))) { 0271 // Dropping effect on a Bin item 0272 QStringList effectData; 0273 effectData << QString::fromUtf8(data->data(QStringLiteral("kdenlive/effect"))); 0274 QStringList source = QString::fromUtf8(data->data(QStringLiteral("kdenlive/effectsource"))).split(QLatin1Char(',')); 0275 effectData << source; 0276 Q_EMIT effectDropped(effectData, parent); 0277 return true; 0278 } 0279 0280 if (data->hasFormat(QStringLiteral("kdenlive/clip"))) { 0281 const QStringList list = QString(data->data(QStringLiteral("kdenlive/clip"))).split(QLatin1Char(';')); 0282 QString id; 0283 return requestAddBinSubClip(id, list.at(1).toInt(), list.at(2).toInt(), {}, list.at(0)); 0284 } 0285 0286 if (data->hasFormat(QStringLiteral("kdenlive/tag"))) { 0287 // Dropping effect on a Bin item 0288 QString tag = QString::fromUtf8(data->data(QStringLiteral("kdenlive/tag"))); 0289 Q_EMIT addTag(tag, parent); 0290 return true; 0291 } 0292 0293 return false; 0294 } 0295 0296 QVariant ProjectItemModel::headerData(int section, Qt::Orientation orientation, int role) const 0297 { 0298 READ_LOCK(); 0299 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { 0300 QVariant columnName; 0301 switch (section) { 0302 case 0: 0303 columnName = i18n("Name"); 0304 break; 0305 case 1: 0306 columnName = i18n("Date"); 0307 break; 0308 case 2: 0309 columnName = i18n("Description"); 0310 break; 0311 case 3: 0312 columnName = i18n("Type"); 0313 break; 0314 case 4: 0315 columnName = i18n("Tag"); 0316 break; 0317 case 5: 0318 columnName = i18n("Duration"); 0319 break; 0320 case 6: 0321 columnName = i18n("Id"); 0322 break; 0323 case 7: 0324 columnName = i18n("Rating"); 0325 break; 0326 case 8: 0327 columnName = i18n("Usage"); 0328 break; 0329 default: 0330 columnName = i18n("Unknown"); 0331 break; 0332 } 0333 return columnName; 0334 } 0335 return QAbstractItemModel::headerData(section, orientation, role); 0336 } 0337 0338 int ProjectItemModel::columnCount(const QModelIndex &parent) const 0339 { 0340 READ_LOCK(); 0341 if (parent.isValid()) { 0342 return getBinItemByIndex(parent)->supportedDataCount(); 0343 } 0344 return std::static_pointer_cast<ProjectFolder>(rootItem)->supportedDataCount(); 0345 } 0346 0347 // cppcheck-suppress unusedFunction 0348 Qt::DropActions ProjectItemModel::supportedDropActions() const 0349 { 0350 return Qt::CopyAction | Qt::MoveAction; 0351 } 0352 0353 QStringList ProjectItemModel::mimeTypes() const 0354 { 0355 QStringList types{QStringLiteral("text/producerslist"), QStringLiteral("text/uri-list"), QStringLiteral("kdenlive/clip"), QStringLiteral("kdenlive/effect"), 0356 QStringLiteral("kdenlive/tag")}; 0357 return types; 0358 } 0359 0360 QMimeData *ProjectItemModel::mimeData(const QModelIndexList &indices) const 0361 { 0362 READ_LOCK(); 0363 // Mime data is a list of id's separated by ';'. 0364 // Clip ids are represented like: 2 (where 2 is the clip's id) 0365 // Clip zone ids are represented like: 2/10/200 (where 2 is the clip's id, 10 and 200 are in and out points) 0366 // Folder ids are represented like: #2 (where 2 is the folder's id) 0367 auto *mimeData = new QMimeData(); 0368 QStringList list; 0369 QString parentId; 0370 size_t duration = 0; 0371 for (int i = 0; i < indices.count(); i++) { 0372 QModelIndex ix = indices.at(i); 0373 if (!ix.isValid() || ix.column() != 0) { 0374 continue; 0375 } 0376 std::shared_ptr<AbstractProjectItem> item = getBinItemByIndex(ix); 0377 if (!item->statusReady()) { 0378 continue; 0379 } 0380 if (parentId.isEmpty()) { 0381 parentId = ix.parent().data(AbstractProjectItem::DataId).toString(); 0382 } 0383 AbstractProjectItem::PROJECTITEMTYPE type = item->itemType(); 0384 if (type == AbstractProjectItem::ClipItem) { 0385 ClipType::ProducerType cType = item->clipType(); 0386 QString dragId = item->clipId(); 0387 if ((cType == ClipType::AV || cType == ClipType::Playlist || cType == ClipType::Timeline)) { 0388 switch (m_dragType) { 0389 case PlaylistState::AudioOnly: 0390 dragId.prepend(QLatin1Char('A')); 0391 break; 0392 case PlaylistState::VideoOnly: 0393 dragId.prepend(QLatin1Char('V')); 0394 break; 0395 default: 0396 break; 0397 } 0398 } 0399 list << dragId; 0400 duration += (std::static_pointer_cast<ProjectClip>(item))->frameDuration(); 0401 } else if (type == AbstractProjectItem::SubClipItem) { 0402 QPoint p = item->zone(); 0403 std::shared_ptr<ProjectClip> master = std::static_pointer_cast<ProjectSubClip>(item)->getMasterClip(); 0404 QString dragId = master->clipId(); 0405 ClipType::ProducerType cType = master->clipType(); 0406 if ((cType == ClipType::AV || cType == ClipType::Playlist || cType == ClipType::Timeline)) { 0407 switch (m_dragType) { 0408 case PlaylistState::AudioOnly: 0409 dragId.prepend(QLatin1Char('A')); 0410 break; 0411 case PlaylistState::VideoOnly: 0412 dragId.prepend(QLatin1Char('V')); 0413 break; 0414 default: 0415 break; 0416 } 0417 } 0418 list << dragId + QLatin1Char('/') + QString::number(p.x()) + QLatin1Char('/') + QString::number(p.y()); 0419 } else if (type == AbstractProjectItem::FolderItem) { 0420 list << "#" + item->clipId(); 0421 } 0422 } 0423 if (!list.isEmpty()) { 0424 QByteArray data; 0425 data.append(list.join(QLatin1Char(';')).toUtf8()); 0426 mimeData->setData(QStringLiteral("text/producerslist"), data); 0427 mimeData->setData(QStringLiteral("text/rootId"), parentId.toLatin1()); 0428 mimeData->setData(QStringLiteral("text/dragid"), QUuid::createUuid().toByteArray()); 0429 mimeData->setText(QString::number(duration)); 0430 } 0431 return mimeData; 0432 } 0433 0434 void ProjectItemModel::onItemUpdated(const std::shared_ptr<AbstractProjectItem> &item, const QVector<int> &roles) 0435 { 0436 int minColumn = -1; 0437 int maxColumn = -1; 0438 for (auto &r : roles) { 0439 const QList<int> indexes = mapDataToColumn((AbstractProjectItem::DataType)r); 0440 for (auto &ix : indexes) { 0441 if (minColumn == -1 || ix < minColumn) { 0442 minColumn = ix; 0443 } 0444 if (maxColumn == -1 || ix > maxColumn) { 0445 maxColumn = ix; 0446 } 0447 } 0448 } 0449 if (minColumn == -1) { 0450 return; 0451 } 0452 QWriteLocker locker(&m_lock); 0453 auto tItem = std::static_pointer_cast<TreeItem>(item); 0454 auto ptr = tItem->parentItem().lock(); 0455 if (ptr) { 0456 auto index = getIndexFromItem(tItem, minColumn); 0457 if (minColumn == maxColumn) { 0458 Q_EMIT dataChanged(index, index, roles); 0459 } else { 0460 auto index2 = getIndexFromItem(tItem, maxColumn); 0461 Q_EMIT dataChanged(index, index2, roles); 0462 } 0463 } 0464 } 0465 0466 void ProjectItemModel::onItemUpdated(const QString &binId, int role) 0467 { 0468 QWriteLocker locker(&m_lock); 0469 std::shared_ptr<AbstractProjectItem> item = getItemByBinId(binId); 0470 if (item) { 0471 onItemUpdated(item, {role}); 0472 } 0473 } 0474 0475 std::shared_ptr<ProjectClip> ProjectItemModel::getClipByBinID(const QString &binId) 0476 { 0477 READ_LOCK(); 0478 auto search = m_allClipItems.find(binId.toInt()); 0479 if (search != m_allClipItems.end()) { 0480 return search->second; 0481 } 0482 return nullptr; 0483 } 0484 0485 const QVector<uint8_t> ProjectItemModel::getAudioLevelsByBinID(const QString &binId, int stream) 0486 { 0487 READ_LOCK(); 0488 auto search = m_allClipItems.find(binId.toInt()); 0489 if (search != m_allClipItems.end()) { 0490 return search->second->audioFrameCache(stream); 0491 } 0492 return QVector<uint8_t>(); 0493 } 0494 0495 double ProjectItemModel::getAudioMaxLevel(const QString &binId, int stream) 0496 { 0497 READ_LOCK(); 0498 auto search = m_allClipItems.find(binId.toInt()); 0499 if (search != m_allClipItems.end()) { 0500 return search->second->getAudioMax(stream); 0501 } 0502 return 0; 0503 } 0504 0505 bool ProjectItemModel::hasClip(const QString &binId) 0506 { 0507 READ_LOCK(); 0508 return getClipByBinID(binId) != nullptr; 0509 } 0510 0511 std::shared_ptr<ProjectFolder> ProjectItemModel::getFolderByBinId(const QString &binId) 0512 { 0513 READ_LOCK(); 0514 for (const auto &clip : m_allItems) { 0515 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 0516 if (c->itemType() == AbstractProjectItem::FolderItem && c->clipId() == binId) { 0517 return std::static_pointer_cast<ProjectFolder>(c); 0518 } 0519 } 0520 return nullptr; 0521 } 0522 0523 QList<std::shared_ptr<ProjectFolder>> ProjectItemModel::getFolders() 0524 { 0525 READ_LOCK(); 0526 QList<std::shared_ptr<ProjectFolder>> folders; 0527 for (const auto &clip : m_allItems) { 0528 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 0529 if (c->itemType() == AbstractProjectItem::FolderItem) { 0530 folders << std::static_pointer_cast<ProjectFolder>(c); 0531 } 0532 } 0533 return folders; 0534 } 0535 0536 const QString ProjectItemModel::getFolderIdByName(const QString &folderName) 0537 { 0538 READ_LOCK(); 0539 for (const auto &clip : m_allItems) { 0540 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 0541 if (c->itemType() == AbstractProjectItem::FolderItem && c->name() == folderName) { 0542 return c->clipId(); 0543 } 0544 } 0545 return QString(); 0546 } 0547 0548 std::shared_ptr<AbstractProjectItem> ProjectItemModel::getItemByBinId(const QString &binId) 0549 { 0550 READ_LOCK(); 0551 for (const auto &clip : m_allItems) { 0552 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 0553 if (c->clipId() == binId) { 0554 return c; 0555 } 0556 } 0557 return nullptr; 0558 } 0559 0560 void ProjectItemModel::setBinEffectsEnabled(bool enabled) 0561 { 0562 QWriteLocker locker(&m_lock); 0563 return std::static_pointer_cast<AbstractProjectItem>(rootItem)->setBinEffectsEnabled(enabled); 0564 } 0565 0566 QStringList ProjectItemModel::getEnclosingFolderInfo(const QModelIndex &index) const 0567 { 0568 READ_LOCK(); 0569 QStringList noInfo; 0570 noInfo << QString::number(-1); 0571 noInfo << QString(); 0572 if (!index.isValid()) { 0573 return noInfo; 0574 } 0575 0576 std::shared_ptr<AbstractProjectItem> currentItem = getBinItemByIndex(index); 0577 auto folder = currentItem->getEnclosingFolder(true); 0578 if ((folder == nullptr) || folder == rootItem) { 0579 return noInfo; 0580 } 0581 QStringList folderInfo; 0582 folderInfo << currentItem->clipId(); 0583 folderInfo << currentItem->name(); 0584 return folderInfo; 0585 } 0586 0587 void ProjectItemModel::clean() 0588 { 0589 QWriteLocker locker(&m_lock); 0590 closing = true; 0591 m_extraPlaylists.clear(); 0592 std::vector<std::shared_ptr<AbstractProjectItem>> toDelete; 0593 toDelete.reserve(size_t(rootItem->childCount())); 0594 for (int i = 0; i < rootItem->childCount(); ++i) { 0595 qDebug() << "... FOUND CLIP: " << std::static_pointer_cast<AbstractProjectItem>(rootItem->child(i))->clipId() << " = " 0596 << std::static_pointer_cast<AbstractProjectItem>(rootItem->child(i))->name(); 0597 toDelete.push_back(std::static_pointer_cast<AbstractProjectItem>(rootItem->child(i))); 0598 } 0599 Fun undo = []() { return true; }; 0600 Fun redo = []() { return true; }; 0601 for (const auto &child : toDelete) { 0602 requestBinClipDeletion(child, undo, redo); 0603 } 0604 Q_ASSERT(rootItem->childCount() == 0); 0605 closing = false; 0606 m_nextId = 1; 0607 m_uuid = QUuid::createUuid(); 0608 m_sequenceFolderId = -1; 0609 buildPlaylist(m_uuid); 0610 ThumbnailCache::get()->clearCache(); 0611 } 0612 0613 std::shared_ptr<ProjectFolder> ProjectItemModel::getRootFolder() const 0614 { 0615 READ_LOCK(); 0616 return std::static_pointer_cast<ProjectFolder>(rootItem); 0617 } 0618 0619 void ProjectItemModel::loadSubClips(const QString &id, const QString &clipData, bool logUndo) 0620 { 0621 QWriteLocker locker(&m_lock); 0622 Fun undo = []() { return true; }; 0623 Fun redo = []() { return true; }; 0624 loadSubClips(id, clipData, undo, redo); 0625 if (logUndo) { 0626 Fun update_subs = [this, binId = id]() { 0627 std::shared_ptr<AbstractProjectItem> parentItem = getItemByBinId(binId); 0628 if (parentItem && parentItem->itemType() == AbstractProjectItem::ClipItem) { 0629 auto clipItem = std::static_pointer_cast<ProjectClip>(parentItem); 0630 clipItem->updateZones(); 0631 } 0632 return true; 0633 }; 0634 PUSH_LAMBDA(update_subs, undo); 0635 PUSH_LAMBDA(update_subs, redo); 0636 update_subs(); 0637 pCore->pushUndo(undo, redo, i18n("Add sub clips")); 0638 } 0639 } 0640 0641 void ProjectItemModel::loadSubClips(const QString &id, const QString &dataMap, Fun &undo, Fun &redo) 0642 { 0643 if (dataMap.isEmpty()) { 0644 return; 0645 } 0646 QWriteLocker locker(&m_lock); 0647 std::shared_ptr<ProjectClip> clip = getClipByBinID(id); 0648 if (!clip) { 0649 qWarning() << "Clip not loaded"; 0650 return; 0651 } 0652 auto json = QJsonDocument::fromJson(dataMap.toUtf8()); 0653 if (!json.isArray()) { 0654 qWarning() << "Error loading zones: no json array"; 0655 return; 0656 } 0657 int maxFrame = clip->duration().frames(pCore->getCurrentFps()) - 1; 0658 auto list = json.array(); 0659 for (const auto &entry : qAsConst(list)) { 0660 if (!entry.isObject()) { 0661 qWarning() << "Skipping invalid marker data"; 0662 continue; 0663 } 0664 auto entryObj = entry.toObject(); 0665 if (!entryObj.contains(QLatin1String("name"))) { 0666 qWarning() << "Skipping invalid zone (does not contain name)"; 0667 continue; 0668 } 0669 int in = entryObj[QLatin1String("in")].toInt(); 0670 int out = entryObj[QLatin1String("out")].toInt(); 0671 QMap<QString, QString> zoneProperties; 0672 zoneProperties.insert(QStringLiteral("name"), entryObj[QLatin1String("name")].toString(i18n("Zone"))); 0673 zoneProperties.insert(QStringLiteral("rating"), QString::number(entryObj[QLatin1String("rating")].toInt())); 0674 zoneProperties.insert(QStringLiteral("tags"), entryObj[QLatin1String("tags")].toString(QString())); 0675 if (in >= out) { 0676 qWarning() << "Invalid zone" << zoneProperties.value("name") << in << out; 0677 continue; 0678 } 0679 if (maxFrame > 0) { 0680 out = qMin(out, maxFrame); 0681 } 0682 QString subId; 0683 requestAddBinSubClip(subId, in, out, zoneProperties, id, undo, redo); 0684 } 0685 } 0686 0687 std::shared_ptr<AbstractProjectItem> ProjectItemModel::getBinItemByIndex(const QModelIndex &index) const 0688 { 0689 READ_LOCK(); 0690 return std::static_pointer_cast<AbstractProjectItem>(getItemById(int(index.internalId()))); 0691 } 0692 0693 bool ProjectItemModel::requestBinClipDeletion(const std::shared_ptr<AbstractProjectItem> &clip, Fun &undo, Fun &redo) 0694 { 0695 QWriteLocker locker(&m_lock); 0696 Q_ASSERT(clip); 0697 if (!clip) return false; 0698 int parentId = -1; 0699 QString parentBinId; 0700 int binId = clip->clipId().toInt(); 0701 if (binId == m_sequenceFolderId) { 0702 setSequencesFolder(-1); 0703 } 0704 if (auto ptr = clip->parent()) { 0705 parentId = ptr->getId(); 0706 parentBinId = ptr->clipId(); 0707 } 0708 bool isSubClip = clip->itemType() == AbstractProjectItem::SubClipItem; 0709 if (!clip->selfSoftDelete(undo, redo)) { 0710 return false; 0711 } 0712 int id = clip->getId(); 0713 Fun operation = removeProjectItem_lambda(binId, id); 0714 Fun reverse = addItem_lambda(clip, parentId); 0715 bool res = operation(); 0716 if (res) { 0717 if (isSubClip) { 0718 Fun update_doc = [this, parentBinId]() { 0719 std::shared_ptr<AbstractProjectItem> parentItem = getItemByBinId(parentBinId); 0720 if (parentItem && parentItem->itemType() == AbstractProjectItem::ClipItem) { 0721 auto clipItem = std::static_pointer_cast<ProjectClip>(parentItem); 0722 clipItem->updateZones(); 0723 } 0724 return true; 0725 }; 0726 update_doc(); 0727 PUSH_LAMBDA(update_doc, operation); 0728 PUSH_LAMBDA(update_doc, reverse); 0729 } else { 0730 Fun checkAudio = clip->getAudio_lambda(); 0731 PUSH_LAMBDA(checkAudio, reverse); 0732 } 0733 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0734 } 0735 return res; 0736 } 0737 0738 void ProjectItemModel::registerItem(const std::shared_ptr<TreeItem> &item) 0739 { 0740 QWriteLocker locker(&m_lock); 0741 auto clip = std::static_pointer_cast<AbstractProjectItem>(item); 0742 m_binPlaylist->manageBinItemInsertion(clip); 0743 AbstractTreeModel::registerItem(item); 0744 m_allIds.append(clip->clipId().toInt()); 0745 if (clip->itemType() == AbstractProjectItem::ClipItem) { 0746 auto clipItem = std::static_pointer_cast<ProjectClip>(clip); 0747 m_allClipItems[clip->clipId().toInt()] = clipItem; 0748 updateWatcher(clipItem); 0749 if (clipItem->clipType() == ClipType::Timeline && clipItem->statusReady()) { 0750 const QString uuid = clipItem->getSequenceUuid().toString(); 0751 std::shared_ptr<Mlt::Tractor> trac(new Mlt::Tractor(clipItem->originalProducer()->parent())); 0752 storeSequence(uuid, trac, false); 0753 } 0754 } 0755 } 0756 0757 void ProjectItemModel::deregisterItem(int id, TreeItem *item) 0758 { 0759 QWriteLocker locker(&m_lock); 0760 auto clip = static_cast<AbstractProjectItem *>(item); 0761 m_allIds.removeAll(clip->clipId().toInt()); 0762 m_allClipItems.erase(clip->clipId().toInt()); 0763 m_binPlaylist->manageBinItemDeletion(clip); 0764 // TODO : here, we should suspend jobs belonging to the item we delete. They can be restarted if the item is reinserted by undo 0765 AbstractTreeModel::deregisterItem(id, item); 0766 if (clip->itemType() == AbstractProjectItem::ClipItem) { 0767 auto clipItem = static_cast<ProjectClip *>(clip); 0768 m_fileWatcher->removeFile(clipItem->clipId()); 0769 if (clipItem->clipType() == ClipType::Timeline) { 0770 const QString uuid = clipItem->getSequenceUuid().toString(); 0771 if (m_extraPlaylists.count(uuid) > 0) { 0772 m_extraPlaylists.erase(uuid); 0773 } 0774 } 0775 } 0776 } 0777 0778 bool ProjectItemModel::hasSequenceId(const QUuid &uuid) const 0779 { 0780 return m_binPlaylist->hasSequenceId(uuid); 0781 } 0782 0783 std::shared_ptr<ProjectClip> ProjectItemModel::getSequenceClip(const QUuid &uuid) 0784 { 0785 const QString binId = getSequenceId(uuid); 0786 return getClipByBinID(binId); 0787 } 0788 0789 QMap<QUuid, QString> ProjectItemModel::getAllSequenceClips() const 0790 { 0791 return m_binPlaylist->getAllSequenceClips(); 0792 } 0793 0794 const QString ProjectItemModel::getSequenceId(const QUuid &uuid) 0795 { 0796 return m_binPlaylist->getSequenceId(uuid); 0797 } 0798 0799 int ProjectItemModel::getFreeFolderId() 0800 { 0801 while (!isIdFree(QString::number(++m_nextId))) { 0802 }; 0803 return m_nextId; 0804 } 0805 0806 int ProjectItemModel::getFreeClipId() 0807 { 0808 while (!isIdFree(QString::number(++m_nextId))) { 0809 }; 0810 return m_nextId; 0811 } 0812 0813 bool ProjectItemModel::addItem(const std::shared_ptr<AbstractProjectItem> &item, const QString &parentId, Fun &undo, Fun &redo) 0814 { 0815 QWriteLocker locker(&m_lock); 0816 std::shared_ptr<AbstractProjectItem> parentItem = getItemByBinId(parentId); 0817 if (!parentItem) { 0818 qCDebug(KDENLIVE_LOG) << " / / ERROR IN PARENT FOLDER"; 0819 return false; 0820 } 0821 if (item->itemType() == AbstractProjectItem::ClipItem && parentItem->itemType() != AbstractProjectItem::FolderItem) { 0822 qCDebug(KDENLIVE_LOG) << " / / ERROR when inserting clip: a clip should be inserted in a folder"; 0823 return false; 0824 } 0825 if (item->itemType() == AbstractProjectItem::SubClipItem && parentItem->itemType() != AbstractProjectItem::ClipItem) { 0826 qCDebug(KDENLIVE_LOG) << " / / ERROR when inserting subclip: a subclip should be inserted in a clip"; 0827 return false; 0828 } 0829 Fun operation = addItem_lambda(item, parentItem->getId()); 0830 0831 int itemId = item->getId(); 0832 int binId = item->clipId().toInt(); 0833 Fun reverse = removeProjectItem_lambda(binId, itemId); 0834 bool res = operation(); 0835 Q_ASSERT(item->isInModel()); 0836 if (res) { 0837 Fun checkAudio = item->getAudio_lambda(); 0838 PUSH_LAMBDA(checkAudio, operation); 0839 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0840 } 0841 return res; 0842 } 0843 0844 bool ProjectItemModel::requestAddFolder(QString &id, const QString &name, const QString &parentId, Fun &undo, Fun &redo) 0845 { 0846 QWriteLocker locker(&m_lock); 0847 if (!id.isEmpty() && !isIdFree(id)) { 0848 id.clear(); 0849 } 0850 if (id.isEmpty()) { 0851 id = QString::number(getFreeFolderId()); 0852 } 0853 std::shared_ptr<ProjectFolder> new_folder = ProjectFolder::construct(id, name, std::static_pointer_cast<ProjectItemModel>(shared_from_this())); 0854 return addItem(new_folder, parentId, undo, redo); 0855 } 0856 0857 bool ProjectItemModel::requestAddBinClip(QString &id, const QDomElement &description, const QString &parentId, Fun &undo, Fun &redo, 0858 const std::function<void(const QString &)> &readyCallBack) 0859 { 0860 if (id.isEmpty()) { 0861 id = Xml::getXmlProperty(description, QStringLiteral("kdenlive:id"), QStringLiteral("-1")); 0862 if (id == QStringLiteral("-1") || !isIdFree(id)) { 0863 id = QString::number(getFreeClipId()); 0864 } 0865 } 0866 Q_ASSERT(isIdFree(id)); 0867 QWriteLocker locker(&m_lock); 0868 std::shared_ptr<ProjectClip> new_clip = 0869 ProjectClip::construct(id, description, m_blankThumb, std::static_pointer_cast<ProjectItemModel>(shared_from_this())); 0870 locker.unlock(); 0871 bool res = addItem(new_clip, parentId, undo, redo); 0872 if (res) { 0873 ClipLoadTask::start(ObjectId(KdenliveObjectType::BinClip, id.toInt(), QUuid()), description, false, -1, -1, this, false, std::bind(readyCallBack, id)); 0874 } 0875 return res; 0876 } 0877 0878 bool ProjectItemModel::requestAddBinClip(QString &id, const QDomElement &description, const QString &parentId, const QString &undoText, 0879 const std::function<void(const QString &)> &readyCallBack) 0880 { 0881 QWriteLocker locker(&m_lock); 0882 Fun undo = []() { return true; }; 0883 Fun redo = []() { return true; }; 0884 bool res = requestAddBinClip(id, description, parentId, undo, redo, readyCallBack); 0885 if (res) { 0886 pCore->pushUndo(undo, redo, undoText.isEmpty() ? i18n("Add bin clip") : undoText); 0887 } 0888 return res; 0889 } 0890 0891 bool ProjectItemModel::requestAddBinClip(QString &id, std::shared_ptr<Mlt::Producer> &producer, const QString &parentId, Fun &undo, Fun &redo, 0892 const std::function<void(const QString &)> &readyCallBack) 0893 { 0894 QWriteLocker locker(&m_lock); 0895 if (id.isEmpty()) { 0896 if (producer->property_exists("kdenlive:id")) { 0897 id = QString::number(producer->get_int("kdenlive:id")); 0898 } 0899 if (!isIdFree(id)) { 0900 id = QString::number(getFreeClipId()); 0901 } 0902 } 0903 Q_ASSERT(isIdFree(id)); 0904 std::shared_ptr<ProjectClip> new_clip = ProjectClip::construct(id, m_blankThumb, std::static_pointer_cast<ProjectItemModel>(shared_from_this()), producer); 0905 bool res = addItem(new_clip, parentId, undo, redo); 0906 if (res) { 0907 new_clip->importEffects(producer); 0908 const std::function<void()> readyCallBackExec = std::bind(readyCallBack, id); 0909 QMetaObject::invokeMethod(qApp, [readyCallBackExec] { readyCallBackExec(); }); 0910 } 0911 return res; 0912 } 0913 0914 bool ProjectItemModel::requestAddBinSubClip(QString &id, int in, int out, const QMap<QString, QString> &zoneProperties, const QString &parentId, Fun &undo, 0915 Fun &redo) 0916 { 0917 QWriteLocker locker(&m_lock); 0918 if (id.isEmpty()) { 0919 id = QString::number(getFreeClipId()); 0920 } 0921 Q_ASSERT(isIdFree(id)); 0922 QString subId = parentId; 0923 if (subId.startsWith(QLatin1Char('A')) || subId.startsWith(QLatin1Char('V'))) { 0924 subId.remove(0, 1); 0925 } 0926 auto clip = getClipByBinID(subId); 0927 Q_ASSERT(clip->itemType() == AbstractProjectItem::ClipItem); 0928 auto tc = pCore->currentDoc()->timecode().getDisplayTimecodeFromFrames(in, KdenliveSettings::frametimecode()); 0929 std::shared_ptr<ProjectSubClip> new_clip = 0930 ProjectSubClip::construct(id, clip, std::static_pointer_cast<ProjectItemModel>(shared_from_this()), in, out, tc, zoneProperties); 0931 bool res = addItem(new_clip, subId, undo, redo); 0932 return res; 0933 } 0934 bool ProjectItemModel::requestAddBinSubClip(QString &id, int in, int out, const QMap<QString, QString> &zoneProperties, const QString &parentId) 0935 { 0936 QWriteLocker locker(&m_lock); 0937 Fun undo = []() { return true; }; 0938 Fun redo = []() { return true; }; 0939 bool res = requestAddBinSubClip(id, in, out, zoneProperties, parentId, undo, redo); 0940 if (res) { 0941 Fun update_doc = [this, parentId]() { 0942 std::shared_ptr<AbstractProjectItem> parentItem = getItemByBinId(parentId); 0943 if (parentItem && parentItem->itemType() == AbstractProjectItem::ClipItem) { 0944 auto clipItem = std::static_pointer_cast<ProjectClip>(parentItem); 0945 clipItem->updateZones(); 0946 } 0947 return true; 0948 }; 0949 update_doc(); 0950 PUSH_LAMBDA(update_doc, undo); 0951 PUSH_LAMBDA(update_doc, redo); 0952 pCore->pushUndo(undo, redo, i18n("Add a sub clip")); 0953 } 0954 return res; 0955 } 0956 0957 Fun ProjectItemModel::requestRenameFolder_lambda(const std::shared_ptr<AbstractProjectItem> &folder, const QString &newName) 0958 { 0959 int id = folder->getId(); 0960 return [this, id, newName]() { 0961 auto currentFolder = std::static_pointer_cast<AbstractProjectItem>(m_allItems[id].lock()); 0962 if (!currentFolder) { 0963 return false; 0964 } 0965 currentFolder->setName(newName); 0966 m_binPlaylist->manageBinFolderRename(currentFolder); 0967 auto index = getIndexFromItem(currentFolder); 0968 Q_EMIT dataChanged(index, index, {AbstractProjectItem::DataName}); 0969 return true; 0970 }; 0971 } 0972 0973 bool ProjectItemModel::requestRenameFolder(const std::shared_ptr<AbstractProjectItem> &folder, const QString &name, Fun &undo, Fun &redo) 0974 { 0975 QWriteLocker locker(&m_lock); 0976 QString oldName = folder->name(); 0977 auto operation = requestRenameFolder_lambda(folder, name); 0978 if (operation()) { 0979 auto reverse = requestRenameFolder_lambda(folder, oldName); 0980 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 0981 return true; 0982 } 0983 return false; 0984 } 0985 0986 bool ProjectItemModel::requestRenameFolder(std::shared_ptr<AbstractProjectItem> folder, const QString &name) 0987 { 0988 QWriteLocker locker(&m_lock); 0989 Fun undo = []() { return true; }; 0990 Fun redo = []() { return true; }; 0991 bool res = requestRenameFolder(std::move(folder), name, undo, redo); 0992 if (res) { 0993 pCore->pushUndo(undo, redo, i18n("Rename Folder")); 0994 } 0995 return res; 0996 } 0997 0998 bool ProjectItemModel::requestCleanupUnused() 0999 { 1000 QWriteLocker locker(&m_lock); 1001 Fun undo = []() { return true; }; 1002 Fun redo = []() { return true; }; 1003 std::vector<std::shared_ptr<AbstractProjectItem>> to_delete; 1004 // Iterate to find clips that are not in timeline 1005 for (const auto &clip : m_allItems) { 1006 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 1007 if (c->itemType() == AbstractProjectItem::ClipItem && !c->isIncludedInTimeline() && c->clipType() != ClipType::Timeline) { 1008 to_delete.push_back(c); 1009 } 1010 } 1011 // it is important to execute deletion in a separate loop, because otherwise 1012 // the iterators of m_allItems get messed up 1013 for (const auto &c : to_delete) { 1014 bool res = requestBinClipDeletion(c, undo, redo); 1015 if (!res) { 1016 bool undone = undo(); 1017 Q_ASSERT(undone); 1018 return false; 1019 } 1020 } 1021 pCore->pushUndo(undo, redo, i18n("Clean Project")); 1022 return true; 1023 } 1024 1025 bool ProjectItemModel::requestTrashClips(QStringList &ids, QStringList &urls) 1026 { 1027 QWriteLocker locker(&m_lock); 1028 Fun undo = []() { return true; }; 1029 Fun redo = []() { return true; }; 1030 std::vector<std::shared_ptr<AbstractProjectItem>> to_delete; 1031 // Iterate to find clips that are not in timeline 1032 for (const auto &clip : m_allItems) { 1033 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 1034 if (ids.contains(c->clipId())) { 1035 to_delete.push_back(c); 1036 } 1037 } 1038 1039 // it is important to execute deletion in a separate loop, because otherwise 1040 // the iterators of m_allItems get messed up 1041 for (const auto &c : to_delete) { 1042 bool res = requestBinClipDeletion(c, undo, redo); 1043 if (!res) { 1044 bool undone = undo(); 1045 Q_ASSERT(undone); 1046 return false; 1047 } 1048 } 1049 for (const auto &url : urls) { 1050 QFile::remove(url); 1051 } 1052 // don't push undo/redo: the files are deleted we can't redo/undo 1053 return true; 1054 } 1055 1056 std::vector<QString> ProjectItemModel::getAllClipIds() const 1057 { 1058 READ_LOCK(); 1059 std::vector<QString> result; 1060 for (const auto &clip : m_allItems) { 1061 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 1062 if (c->itemType() == AbstractProjectItem::ClipItem) { 1063 result.push_back(c->clipId()); 1064 } 1065 } 1066 return result; 1067 } 1068 1069 void ProjectItemModel::updateCacheThumbnail(std::unordered_map<QString, std::vector<int>> &thumbData) 1070 { 1071 READ_LOCK(); 1072 for (const auto &clip : m_allItems) { 1073 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 1074 if (c->itemType() == AbstractProjectItem::ClipItem) { 1075 int frameNumber = qMax(0, std::static_pointer_cast<ProjectClip>(c)->getThumbFrame()); 1076 thumbData[c->clipId()].push_back(frameNumber); 1077 } else if (c->itemType() == AbstractProjectItem::SubClipItem) { 1078 QPoint p = c->zone(); 1079 thumbData[std::static_pointer_cast<ProjectSubClip>(c)->getMasterClip()->clipId()].push_back(p.x()); 1080 } 1081 } 1082 } 1083 1084 QStringList ProjectItemModel::getClipByUrl(const QFileInfo &url) const 1085 { 1086 READ_LOCK(); 1087 QStringList result; 1088 if (url.filePath().isEmpty()) { 1089 // Invalid url 1090 return result; 1091 } 1092 for (const auto &clip : m_allItems) { 1093 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 1094 if (c->itemType() == AbstractProjectItem::ClipItem) { 1095 if (QFileInfo(std::static_pointer_cast<ProjectClip>(c)->clipUrl()) == url) { 1096 result << c->clipId(); 1097 } 1098 } 1099 } 1100 return result; 1101 } 1102 1103 bool ProjectItemModel::loadFolders(Mlt::Properties &folders, std::unordered_map<QString, QString> &binIdCorresp) 1104 { 1105 QWriteLocker locker(&m_lock); 1106 // At this point, we expect the folders properties to have a name of the form "x.y" where x is the id of the parent folder and y the id of the child. 1107 // Note that for root folder, x = -1 1108 // The value of the property is the name of the child folder 1109 1110 std::unordered_map<int, std::vector<int>> downLinks; // key are parents, value are children 1111 std::unordered_map<int, int> upLinks; // key are children, value are parent 1112 std::unordered_map<int, QString> newIds; // we store the correspondence to the new ids 1113 std::unordered_map<int, QString> folderNames; 1114 newIds[-1] = getRootFolder()->clipId(); 1115 1116 if (folders.count() == 0) return true; 1117 1118 for (int i = 0; i < folders.count(); i++) { 1119 QString folderName = folders.get(i); 1120 QString id = folders.get_name(i); 1121 1122 int parentId = id.section(QLatin1Char('.'), 0, 0).toInt(); 1123 int folderId = id.section(QLatin1Char('.'), 1, 1).toInt(); 1124 downLinks[parentId].push_back(folderId); 1125 upLinks[folderId] = parentId; 1126 folderNames[folderId] = folderName; 1127 } 1128 1129 // In case there are some non-existent parent, we fall back to root 1130 for (const auto &f : downLinks) { 1131 if (upLinks.count(f.first) == 0) { 1132 upLinks[f.first] = -1; 1133 } 1134 if (f.first != -1 && downLinks.count(upLinks[f.first]) == 0) { 1135 qWarning() << f.first << "has invalid parent folder" << upLinks[f.first] << "it will be placed in top directory"; 1136 upLinks[f.first] = -1; 1137 } 1138 } 1139 1140 // We now do a BFS to construct the folders in order 1141 Q_ASSERT(downLinks.count(-1) > 0); 1142 Fun undo = []() { return true; }; 1143 Fun redo = []() { return true; }; 1144 std::queue<int> queue; 1145 std::unordered_set<int> seen; 1146 queue.push(-1); 1147 while (!queue.empty()) { 1148 int current = queue.front(); 1149 seen.insert(current); 1150 queue.pop(); 1151 if (current != -1) { 1152 QString id = QString::number(current); 1153 bool res = requestAddFolder(id, folderNames[current], newIds[upLinks[current]], undo, redo); 1154 if (!res) { 1155 bool undone = undo(); 1156 Q_ASSERT(undone); 1157 return false; 1158 } 1159 binIdCorresp[QString::number(current)] = id; 1160 newIds[current] = id; 1161 } 1162 for (int c : downLinks[current]) { 1163 queue.push(c); 1164 } 1165 } 1166 return true; 1167 } 1168 1169 bool ProjectItemModel::isIdFree(const QString &id) const 1170 { 1171 READ_LOCK(); 1172 if (id.isEmpty()) { 1173 return false; 1174 } 1175 return !m_allIds.contains(id.toInt()); 1176 } 1177 1178 QList<QUuid> ProjectItemModel::loadBinPlaylist(Mlt::Service *documentTractor, std::unordered_map<QString, QString> &binIdCorresp, QStringList &expandedFolders, 1179 const QUuid &activeUuid, int &zoomLevel, QProgressDialog *progressDialog) 1180 { 1181 QWriteLocker locker(&m_lock); 1182 clean(); 1183 QList<QUuid> brokenSequences; 1184 QList<QUuid> foundSequences; 1185 QList<int> foundIds; 1186 Mlt::Properties retainList(mlt_properties(documentTractor->get_data("xml_retain"))); 1187 if (retainList.is_valid()) { 1188 Mlt::Playlist playlist(mlt_playlist(retainList.get_data(BinPlaylist::binPlaylistId.toUtf8().constData()))); 1189 if (playlist.is_valid() && playlist.type() == mlt_service_playlist_type) { 1190 if (progressDialog == nullptr && playlist.count() > 0) { 1191 // Display message on splash screen 1192 Q_EMIT pCore->loadingMessageUpdated(i18n("Loading project clips…")); 1193 } 1194 // Load folders 1195 Mlt::Properties folderProperties; 1196 Mlt::Properties playlistProps(playlist.get_properties()); 1197 expandedFolders = QString(playlistProps.get("kdenlive:expandedFolders")).split(QLatin1Char(';')); 1198 folderProperties.pass_values(playlistProps, "kdenlive:folder."); 1199 loadFolders(folderProperties, binIdCorresp); 1200 m_sequenceFolderId = -1; 1201 if (playlistProps.property_exists("kdenlive:sequenceFolder")) { 1202 int sequenceFolder = playlistProps.get_int("kdenlive:sequenceFolder"); 1203 if (sequenceFolder > -1 && binIdCorresp.count(QString::number(sequenceFolder)) > 0) { 1204 setSequencesFolder(binIdCorresp.at(QString::number(sequenceFolder)).toInt()); 1205 } 1206 } 1207 1208 // Load Zoom level 1209 if (playlistProps.property_exists("kdenlive:binZoom")) { 1210 zoomLevel = playlistProps.get_int("kdenlive:binZoom"); 1211 } 1212 1213 // Read notes 1214 QString notes = playlistProps.get("kdenlive:documentnotes"); 1215 pCore->projectManager()->setDocumentNotes(notes); 1216 1217 Fun undo = []() { return true; }; 1218 Fun redo = []() { return true; }; 1219 int max = playlist.count(); 1220 if (progressDialog) { 1221 progressDialog->setMaximum(progressDialog->maximum() + max); 1222 } 1223 QMap<int, std::shared_ptr<Mlt::Producer>> binProducers; 1224 for (int i = 0; i < max; i++) { 1225 if (progressDialog) { 1226 progressDialog->setValue(i); 1227 } else { 1228 Q_EMIT pCore->loadingMessageUpdated(QString(), 1); 1229 } 1230 QScopedPointer<Mlt::Producer> prod(playlist.get_clip(i)); 1231 if (prod->is_blank() || !prod->is_valid() || prod->parent().property_exists("kdenlive:remove")) { 1232 qDebug() << "==== IGNORING BIN PRODUCER: " << prod->parent().get("kdenlive:id"); 1233 continue; 1234 } 1235 std::shared_ptr<Mlt::Producer> producer; 1236 if (prod->parent().property_exists("kdenlive:uuid")) { 1237 const QUuid uuid(prod->parent().get("kdenlive:uuid")); 1238 if (foundSequences.contains(uuid)) { 1239 qDebug() << "FOUND DUPLICATED SEQUENCE: " << uuid.toString(); 1240 Q_ASSERT(false); 1241 } 1242 if (prod->parent().type() == mlt_service_tractor_type) { 1243 // Load sequence properties 1244 foundSequences << uuid; 1245 Mlt::Properties sequenceProps; 1246 sequenceProps.pass_values(prod->parent(), "kdenlive:sequenceproperties."); 1247 pCore->currentDoc()->loadSequenceProperties(uuid, sequenceProps); 1248 1249 std::shared_ptr<Mlt::Tractor> trac = std::make_shared<Mlt::Tractor>(prod->parent()); 1250 int id(prod->parent().get_int("kdenlive:id")); 1251 trac->set("kdenlive:id", id); 1252 trac->set("kdenlive:uuid", uuid.toString().toUtf8().constData()); 1253 trac->set("length", prod->parent().get("length")); 1254 trac->set("out", prod->parent().get("out")); 1255 trac->set("kdenlive:clipname", prod->parent().get("kdenlive:clipname")); 1256 trac->set("kdenlive:description", prod->parent().get("kdenlive:description")); 1257 trac->set("kdenlive:folderid", prod->parent().get("kdenlive:folderid")); 1258 trac->set("kdenlive:duration", prod->parent().get("kdenlive:duration")); 1259 trac->set("kdenlive:producer_type", ClipType::Timeline); 1260 trac->set("kdenlive:maxduration", prod->parent().get_int("kdenlive:maxduration")); 1261 trac->set("_kdenlive_processed", 1); 1262 std::shared_ptr<Mlt::Producer> prod2(trac->cut()); 1263 1264 prod2->set("kdenlive:id", id); 1265 prod2->set("kdenlive:uuid", uuid.toString().toUtf8().constData()); 1266 prod2->set("length", prod->parent().get("length")); 1267 prod2->set("out", prod->parent().get("out")); 1268 prod2->set("kdenlive:clipname", prod->parent().get("kdenlive:clipname")); 1269 prod2->set("kdenlive:description", prod->parent().get("kdenlive:description")); 1270 prod2->set("kdenlive:folderid", prod->parent().get("kdenlive:folderid")); 1271 prod2->set("kdenlive:duration", prod->parent().get("kdenlive:duration")); 1272 prod2->set("kdenlive:producer_type", ClipType::Timeline); 1273 prod2->set("kdenlive:maxduration", prod->parent().get_int("kdenlive:maxduration")); 1274 prod2->set("_kdenlive_processed", 1); 1275 if (foundIds.contains(id)) { 1276 qWarning() << "ERROR, several Sequence Clips using the same id: " << id << ", UUID: " << uuid.toString() 1277 << "\n____________________"; 1278 brokenSequences << uuid; 1279 } 1280 foundIds << id; 1281 binProducers.insert(id, prod2); 1282 } else { 1283 const QString resource = prod->parent().get("resource"); 1284 qDebug() << "/// INCORRECT SEQUENCE FOUND IN PROJECT BIN: " << resource; 1285 if (resource.endsWith(QLatin1String("<tractor>"))) { 1286 // Buggy internal xml producer, drop 1287 qDebug() << "/// AARGH INCORRECT SEQUENCE CLIP IN PROJECT BIN... TRY TO RECOVER"; 1288 brokenSequences.append(QUuid(uuid)); 1289 } 1290 } 1291 continue; 1292 } 1293 producer.reset(new Mlt::Producer(prod->parent())); 1294 int id = producer->get_int("kdenlive:id"); 1295 if (!id) { 1296 qDebug() << "WARNING THIS SHOULD NOT HAPPEN, BIN CLIP WITHOUT ID: " << producer->parent().get("resource") 1297 << ", ID: " << producer->parent().get("id") << "\n\nFZFZFZFZFZFZFZFZFZFZFZFZFZ"; 1298 // Using a temporary negative reference so we don't mess with yet unloaded clips 1299 id = -getFreeClipId(); 1300 } else { 1301 if (foundIds.contains(id)) { 1302 qWarning() << "ERROR, several Bin Clips using the same id: " << id << "\n____________________"; 1303 Q_ASSERT(false); 1304 } 1305 foundIds << id; 1306 } 1307 binProducers.insert(id, producer); 1308 } 1309 // Ensure active playlist is in the project bin 1310 if (!foundSequences.contains(activeUuid)) { 1311 // Project corruption, try to restore sequence from the ProjectTractor 1312 Mlt::Tractor tractor(*documentTractor); 1313 std::shared_ptr<Mlt::Producer> tk(tractor.track(0)); 1314 std::shared_ptr<Mlt::Producer> producer(new Mlt::Producer(tk->parent())); 1315 const QUuid uuid(producer->get("kdenlive:uuid")); 1316 if (!uuid.isNull() && uuid == activeUuid) { 1317 // TODO: Show user info about the recovered corruption 1318 qWarning() << "Recovering corrupted sequence: " << uuid.toString(); 1319 int id = producer->get_int("kdenlive:id"); 1320 binProducers.insert(id, std::move(producer)); 1321 foundSequences << activeUuid; 1322 } else { 1323 brokenSequences << activeUuid; 1324 } 1325 } 1326 // Do the real insertion 1327 QList<int> binIds = binProducers.keys(); 1328 1329 while (!binProducers.isEmpty()) { 1330 int bid = binIds.takeFirst(); 1331 std::shared_ptr<Mlt::Producer> prod = binProducers.take(bid); 1332 QString newId = QString::number(getFreeClipId()); 1333 QString parentId = qstrdup(prod->get("kdenlive:folderid")); 1334 if (parentId.isEmpty()) { 1335 parentId = QStringLiteral("-1"); 1336 } else { 1337 if (binIdCorresp.count(parentId.section(QLatin1Char('.'), -1)) == 0) { 1338 // Error, folder was lost 1339 parentId = QStringLiteral("-1"); 1340 } 1341 } 1342 prod->set("_kdenlive_processed", 1); 1343 requestAddBinClip(newId, prod, parentId, undo, redo); 1344 qApp->processEvents(); 1345 binIdCorresp[QString::number(bid)] = newId; 1346 } 1347 } 1348 } else { 1349 qDebug() << "HHHHHHHHHHHH\nINVALID BIN PLAYLIST..."; 1350 } 1351 return brokenSequences; 1352 } 1353 1354 void ProjectItemModel::loadTractorPlaylist(Mlt::Tractor documentTractor, std::unordered_map<QString, QString> &binIdCorresp) 1355 { 1356 QWriteLocker locker(&m_lock); 1357 QList<int> processedIds; 1358 QMap<int, std::shared_ptr<Mlt::Producer>> binProducers; 1359 for (int i = 1; i < documentTractor.count(); i++) { 1360 std::unique_ptr<Mlt::Producer> track(documentTractor.track(i)); 1361 Mlt::Service service(track->get_service()); 1362 Mlt::Tractor tractor(service); 1363 if (tractor.count() < 2) { 1364 qDebug() << "// TRACTOR PROBLEM"; 1365 return; 1366 } 1367 for (int j = 0; j < tractor.count(); j++) { 1368 std::unique_ptr<Mlt::Producer> trackProducer(tractor.track(j)); 1369 Mlt::Playlist trackPlaylist(mlt_playlist(trackProducer->get_service())); 1370 for (int k = 0; k < trackPlaylist.count(); k++) { 1371 if (trackPlaylist.is_blank(k)) { 1372 continue; 1373 } 1374 std::unique_ptr<Mlt::Producer> clip(trackPlaylist.get_clip(k)); 1375 if (clip->parent().property_exists("kdenlive:id")) { 1376 int cid = clip->parent().get_int("kdenlive:id"); 1377 if (processedIds.contains(cid)) { 1378 continue; 1379 } 1380 const QString service = clip->parent().get("mlt_service"); 1381 const QString resource = clip->parent().get("resource"); 1382 const QString hash = clip->parent().get("kdenlive:file_hash"); 1383 ClipType::ProducerType matchType; 1384 if (service.startsWith(QLatin1String("avformat"))) { 1385 int cType = clip->parent().get_int("kdenlive:clip_type"); 1386 switch (cType) { 1387 case 1: 1388 matchType = ClipType::Audio; 1389 break; 1390 case 2: 1391 matchType = ClipType::Video; 1392 break; 1393 default: 1394 matchType = ClipType::AV; 1395 break; 1396 } 1397 } else { 1398 matchType = ClipLoadTask::getTypeForService(service, resource); 1399 } 1400 qDebug() << ":::: LOOKING FOR A MATCHING ITEM TYPE: " << matchType; 1401 // Try to find matching clip 1402 bool found = false; 1403 for (const auto &clip : m_allItems) { 1404 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 1405 if (c->itemType() == AbstractProjectItem::ClipItem) { 1406 if (c->clipType() == matchType) { 1407 std::shared_ptr<ProjectClip> pClip = std::static_pointer_cast<ProjectClip>(c); 1408 qDebug() << "::::::\nTRYING TO MATCH: " << pClip->getProducerProperty(QStringLiteral("resource")) << " = " << resource; 1409 if (pClip->getProducerProperty(QStringLiteral("kdenlive:file_hash")) == hash) { 1410 // Found a match 1411 binIdCorresp[QString::number(cid)] = pClip->clipId(); 1412 found = true; 1413 break; 1414 } 1415 } 1416 } 1417 } 1418 if (!found) { 1419 std::shared_ptr<Mlt::Producer> clipProd(new Mlt::Producer(clip->parent())); 1420 binProducers.insert(cid, clipProd); 1421 } 1422 processedIds << cid; 1423 } 1424 qDebug() << ":::: FOUND CLIPS IN PLAYLIST: " << i << ": " << trackPlaylist.count(); 1425 } 1426 } 1427 } 1428 Fun undo = []() { return true; }; 1429 Fun redo = []() { return true; }; 1430 // Do the real insertion 1431 QList<int> binIds = binProducers.keys(); 1432 while (!binProducers.isEmpty()) { 1433 int bid = binIds.takeFirst(); 1434 std::shared_ptr<Mlt::Producer> prod = binProducers.take(bid); 1435 QString newId = QString::number(getFreeClipId()); 1436 QString parentId = qstrdup(prod->get("kdenlive:folderid")); 1437 if (parentId.isEmpty()) { 1438 parentId = QStringLiteral("-1"); 1439 } else { 1440 if (binIdCorresp.count(parentId.section(QLatin1Char('.'), -1)) == 0) { 1441 // Error, folder was lost 1442 parentId = QStringLiteral("-1"); 1443 } 1444 } 1445 prod->set("_kdenlive_processed", 1); 1446 qDebug() << "======\nADDING NEW CLIP: " << newId << " = " << prod->is_valid(); 1447 requestAddBinClip(newId, prod, parentId, undo, redo); 1448 binIdCorresp[QString::number(bid)] = newId; 1449 } 1450 } 1451 1452 void ProjectItemModel::storeSequence(const QString uuid, std::shared_ptr<Mlt::Tractor> tractor, bool internalSave) 1453 { 1454 if (m_extraPlaylists.count(uuid) > 0) { 1455 m_extraPlaylists.erase(uuid); 1456 } 1457 m_extraPlaylists.insert({uuid, std::move(tractor)}); 1458 if (internalSave) { 1459 // Ensure we never use the mapped ids when re-opening an already opened sequence 1460 setExtraTimelineSaved(uuid); 1461 } 1462 } 1463 1464 int ProjectItemModel::sequenceCount() const 1465 { 1466 return m_extraPlaylists.size(); 1467 } 1468 1469 std::shared_ptr<Mlt::Tractor> ProjectItemModel::projectTractor() 1470 { 1471 return m_projectTractor; 1472 } 1473 1474 const QString ProjectItemModel::sceneList(const QString &root, const QString &fullPath, const QString &filterData, Mlt::Tractor *activeTractor, int duration) 1475 { 1476 LocaleHandling::resetLocale(); 1477 QString playlist; 1478 Mlt::Consumer xmlConsumer(pCore->getProjectProfile(), "xml", fullPath.isEmpty() ? "kdenlive_playlist" : fullPath.toUtf8().constData()); 1479 if (!root.isEmpty()) { 1480 xmlConsumer.set("root", root.toUtf8().constData()); 1481 } 1482 if (!xmlConsumer.is_valid()) { 1483 return QString(); 1484 } 1485 xmlConsumer.set("store", "kdenlive"); 1486 xmlConsumer.set("time_format", "clock"); 1487 // Disabling meta creates cleaner files, but then we don't have access to metadata on the fly (meta channels, etc) 1488 // And we must use "avformat" instead of "avformat-novalidate" on project loading which causes a big delay on project opening 1489 // xmlConsumer.set("no_meta", 1); 1490 1491 // Add active timeline as playlist of the main tractor so that when played through melt, the .kdenlive file reads the playlist 1492 if (m_projectTractor->count() > 0) { 1493 m_projectTractor->remove_track(0); 1494 } 1495 m_projectTractor->insert_track(*activeTractor->cut(0, duration), 0); 1496 1497 Mlt::Service s(m_projectTractor->get_service()); 1498 std::unique_ptr<Mlt::Filter> filter = nullptr; 1499 if (!filterData.isEmpty()) { 1500 filter = std::make_unique<Mlt::Filter>(pCore->getProjectProfile(), QString("dynamictext:%1").arg(filterData).toUtf8().constData()); 1501 filter->set("fgcolour", "#ffffff"); 1502 filter->set("bgcolour", "#bb333333"); 1503 s.attach(*filter.get()); 1504 } 1505 xmlConsumer.connect(s); 1506 xmlConsumer.run(); 1507 if (filter) { 1508 s.detach(*filter.get()); 1509 } 1510 playlist = fullPath.isEmpty() ? QString::fromUtf8(xmlConsumer.get("kdenlive_playlist")) : fullPath; 1511 return playlist; 1512 } 1513 1514 std::shared_ptr<Mlt::Tractor> ProjectItemModel::getExtraTimeline(const QString &uuid) 1515 { 1516 if (m_extraPlaylists.count(uuid) > 0) { 1517 return m_extraPlaylists.at(uuid); 1518 } 1519 return nullptr; 1520 } 1521 1522 void ProjectItemModel::setExtraTimelineSaved(const QString &uuid) 1523 { 1524 if (m_extraPlaylists.count(uuid) > 0) { 1525 m_extraPlaylists.at(uuid)->set("_dontmapids", 1); 1526 } 1527 } 1528 1529 void ProjectItemModel::removeReferencedClips(const QUuid &uuid, bool onDeletion) 1530 { 1531 QList<std::shared_ptr<ProjectClip>> clipList = getRootFolder()->childClips(); 1532 for (const std::shared_ptr<ProjectClip> &clip : qAsConst(clipList)) { 1533 if (clip->refCount() > 0) { 1534 clip->purgeReferences(uuid, onDeletion); 1535 } 1536 } 1537 } 1538 1539 /** @brief Save document properties in MLT's bin playlist */ 1540 void ProjectItemModel::saveDocumentProperties(const QMap<QString, QString> &props, const QMap<QString, QString> &metadata) 1541 { 1542 m_binPlaylist->saveDocumentProperties(props, metadata); 1543 } 1544 1545 void ProjectItemModel::saveProperty(const QString &name, const QString &value) 1546 { 1547 m_binPlaylist->saveProperty(name, value); 1548 } 1549 1550 QMap<QString, QString> ProjectItemModel::getProxies(const QString &root) 1551 { 1552 READ_LOCK(); 1553 return m_binPlaylist->getProxies(root); 1554 } 1555 1556 void ProjectItemModel::reloadClip(const QString &binId) 1557 { 1558 QWriteLocker locker(&m_lock); 1559 std::shared_ptr<ProjectClip> clip = getClipByBinID(binId); 1560 if (clip) { 1561 clip->reloadProducer(); 1562 } 1563 } 1564 1565 void ProjectItemModel::setClipWaiting(const QString &binId) 1566 { 1567 QWriteLocker locker(&m_lock); 1568 std::shared_ptr<ProjectClip> clip = getClipByBinID(binId); 1569 if (clip) { 1570 clip->setClipStatus(FileStatus::StatusWaiting); 1571 } 1572 } 1573 1574 void ProjectItemModel::setClipInvalid(const QString &binId) 1575 { 1576 QWriteLocker locker(&m_lock); 1577 std::shared_ptr<ProjectClip> clip = getClipByBinID(binId); 1578 if (clip) { 1579 clip->setClipStatus(FileStatus::StatusMissing); 1580 // TODO: set producer as blank invalid 1581 } 1582 } 1583 1584 void ProjectItemModel::slotUpdateInvalidCount() 1585 { 1586 READ_LOCK(); 1587 int missingCount = 0; 1588 int missingUsed = 0; 1589 for (const auto &clip : m_allItems) { 1590 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 1591 if (c->clipStatus() == FileStatus::StatusMissing) { 1592 int usage = c->getData(AbstractProjectItem::UsageCount).toInt(); 1593 if (usage > 0) { 1594 missingUsed++; 1595 } 1596 missingCount++; 1597 } 1598 } 1599 Q_EMIT pCore->gotMissingClipsCount(missingCount, missingUsed); 1600 } 1601 1602 void ProjectItemModel::updateWatcher(const std::shared_ptr<ProjectClip> &clipItem) 1603 { 1604 QWriteLocker locker(&m_lock); 1605 ClipType::ProducerType type = clipItem->clipType(); 1606 if (type == ClipType::AV || type == ClipType::Audio || type == ClipType::Image || type == ClipType::Video || type == ClipType::Playlist || 1607 type == ClipType::TextTemplate || type == ClipType::Animation) { 1608 m_fileWatcher->removeFile(clipItem->clipId()); 1609 QFileInfo check_file(clipItem->clipUrl()); 1610 // check if file exists and if yes: Is it really a file and no directory? 1611 if ((check_file.exists() && check_file.isFile()) || clipItem->clipStatus() == FileStatus::StatusMissing) { 1612 m_fileWatcher->addFile(clipItem->clipId(), clipItem->clipUrl()); 1613 } 1614 } 1615 } 1616 1617 void ProjectItemModel::setDragType(PlaylistState::ClipState type) 1618 { 1619 QWriteLocker locker(&m_lock); 1620 m_dragType = type; 1621 } 1622 1623 int ProjectItemModel::clipsCount() const 1624 { 1625 READ_LOCK(); 1626 return m_binPlaylist->count(); 1627 } 1628 1629 bool ProjectItemModel::hasProxies() const 1630 { 1631 READ_LOCK(); 1632 for (const auto &clip : m_allItems) { 1633 auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock()); 1634 if (c->itemType() == AbstractProjectItem::ClipItem) { 1635 if (std::static_pointer_cast<ProjectClip>(c)->hasProxy()) { 1636 return true; 1637 } 1638 } 1639 } 1640 return false; 1641 } 1642 1643 bool ProjectItemModel::validateClip(const QString &binId, const QString &clipHash) 1644 { 1645 QWriteLocker locker(&m_lock); 1646 std::shared_ptr<ProjectClip> clip = getClipByBinID(binId); 1647 if (clip) { 1648 return clip->hash() == clipHash; 1649 } 1650 return false; 1651 } 1652 1653 QString ProjectItemModel::validateClipInFolder(const QString &folderId, const QString &clipHash) 1654 { 1655 QWriteLocker locker(&m_lock); 1656 std::shared_ptr<ProjectFolder> folder = getFolderByBinId(folderId); 1657 if (folder) { 1658 return folder->childByHash(clipHash); 1659 } 1660 return QString(); 1661 } 1662 1663 bool ProjectItemModel::urlExists(const QString &path) const 1664 { 1665 return m_fileWatcher->contains(path); 1666 } 1667 1668 bool ProjectItemModel::canBeEmbeded(const QUuid destUuid, const QUuid srcUuid) 1669 { 1670 const QString srcId = getSequenceId(destUuid); 1671 QList<QUuid> checkedUuids; 1672 std::shared_ptr<ProjectClip> clip = getClipByBinID(srcId); 1673 if (clip) { 1674 // Get a list of all dependencies 1675 QList<QUuid> updatedUUids = clip->registeredUuids(); 1676 while (!updatedUUids.isEmpty()) { 1677 QUuid uuid = updatedUUids.takeFirst(); 1678 if (!checkedUuids.contains(uuid)) { 1679 checkedUuids.append(uuid); 1680 const QString secId = getSequenceId(uuid); 1681 std::shared_ptr<ProjectClip> subclip = getClipByBinID(secId); 1682 updatedUUids.append(subclip->registeredUuids()); 1683 } 1684 } 1685 if (checkedUuids.contains(srcUuid)) { 1686 return false; 1687 } 1688 } else { 1689 qDebug() << "::: CLIP NOT FOUND FOR : " << srcId; 1690 } 1691 return true; 1692 } 1693 1694 int ProjectItemModel::defaultSequencesFolder() const 1695 { 1696 return m_sequenceFolderId; 1697 } 1698 1699 void ProjectItemModel::setSequencesFolder(int id) 1700 { 1701 m_sequenceFolderId = id; 1702 saveProperty(QStringLiteral("kdenlive:sequenceFolder"), QString::number(id)); 1703 if (id > -1) { 1704 Q_ASSERT(getFolderByBinId(QString::number(id)) != nullptr); 1705 onItemUpdated(QString::number(id), Qt::DecorationRole); 1706 } 1707 } 1708 1709 Fun ProjectItemModel::removeProjectItem_lambda(int binId, int id) 1710 { 1711 return [this, binId, id]() { 1712 if (binId > -1) { 1713 Q_EMIT pCore->binClipDeleted(binId); 1714 } 1715 Fun operation = removeItem_lambda(id); 1716 bool result = operation(); 1717 return result; 1718 }; 1719 } 1720 1721 void ProjectItemModel::checkSequenceIntegrity(const QString activeSequenceId) 1722 { 1723 QStringList sequencesIds = pCore->currentDoc()->getTimelinesIds(); 1724 Q_ASSERT(sequencesIds.contains(activeSequenceId)); 1725 QStringList allMltIds = m_binPlaylist->getAllMltIds(); 1726 for (auto &i : sequencesIds) { 1727 Q_ASSERT(allMltIds.contains(i)); 1728 } 1729 } 1730 1731 std::shared_ptr<EffectStackModel> ProjectItemModel::getClipEffectStack(int itemId) 1732 { 1733 std::shared_ptr<ProjectClip> clip = getClipByBinID(QString::number(itemId)); 1734 Q_ASSERT(clip != nullptr); 1735 return clip->getEffectStack(); 1736 }