File indexing completed on 2024-04-28 08:43:29
0001 /* 0002 SPDX-FileCopyrightText: 2017 Nicolas Carion 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 0006 #include "clipcreator.hpp" 0007 #include "bin/bin.h" 0008 #include "core.h" 0009 #include "dialogs/clipcreationdialog.h" 0010 #include "doc/kdenlivedoc.h" 0011 #include "kdenlivesettings.h" 0012 #include "klocalizedstring.h" 0013 #include "macros.hpp" 0014 #include "mainwindow.h" 0015 #include "profiles/profilemodel.hpp" 0016 #include "project/projectmanager.h" 0017 #include "projectitemmodel.h" 0018 #include "titler/titledocument.h" 0019 #include "utils/devices.hpp" 0020 #include "xml/xml.hpp" 0021 0022 #include "utils/KMessageBox_KdenliveCompat.h" 0023 #include <KMessageBox> 0024 #include <QApplication> 0025 #include <QDomDocument> 0026 #include <QMimeDatabase> 0027 #include <QProgressDialog> 0028 #include <utility> 0029 0030 namespace { 0031 QDomElement createProducer(QDomDocument &xml, ClipType::ProducerType type, const QString &resource, const QString &name, int duration, const QString &service) 0032 { 0033 QDomElement prod = xml.createElement(QStringLiteral("producer")); 0034 xml.appendChild(prod); 0035 prod.setAttribute(QStringLiteral("type"), int(type)); 0036 if (type == ClipType::Timeline) { 0037 // Uuid can be passed through the servce property 0038 if (!service.isEmpty()) { 0039 prod.setAttribute(QStringLiteral("kdenlive:uuid"), service); 0040 } else { 0041 const QUuid uuid = QUuid::createUuid(); 0042 prod.setAttribute(QStringLiteral("kdenlive:uuid"), uuid.toString()); 0043 } 0044 } 0045 prod.setAttribute(QStringLiteral("in"), QStringLiteral("0")); 0046 prod.setAttribute(QStringLiteral("length"), duration); 0047 std::unordered_map<QString, QString> properties; 0048 if (!resource.isEmpty()) { 0049 properties[QStringLiteral("resource")] = resource; 0050 } 0051 if (!name.isEmpty()) { 0052 properties[QStringLiteral("kdenlive:clipname")] = name; 0053 } 0054 if (!service.isEmpty()) { 0055 properties[QStringLiteral("mlt_service")] = service; 0056 } 0057 Xml::addXmlProperties(prod, properties); 0058 return prod; 0059 } 0060 0061 } // namespace 0062 0063 QString ClipCreator::createTitleClip(const std::unordered_map<QString, QString> &properties, int duration, const QString &name, const QString &parentFolder, 0064 const std::shared_ptr<ProjectItemModel> &model) 0065 { 0066 QDomDocument xml; 0067 auto prod = createProducer(xml, ClipType::Text, QString(), name, duration, QStringLiteral("kdenlivetitle")); 0068 Xml::addXmlProperties(prod, properties); 0069 0070 QString id; 0071 std::function<void(const QString &)> callBack = [](const QString &binId) { pCore->activeBin()->selectClipById(binId); }; 0072 bool res = model->requestAddBinClip(id, xml.documentElement(), parentFolder, i18n("Create title clip"), callBack); 0073 return res ? id : QStringLiteral("-1"); 0074 } 0075 0076 QString ClipCreator::createColorClip(const QString &color, int duration, const QString &name, const QString &parentFolder, 0077 const std::shared_ptr<ProjectItemModel> &model) 0078 { 0079 QDomDocument xml; 0080 0081 auto prod = createProducer(xml, ClipType::Color, color, name, duration, QStringLiteral("color")); 0082 0083 QString id; 0084 std::function<void(const QString &)> callBack = [](const QString &binId) { pCore->activeBin()->selectClipById(binId); }; 0085 bool res = model->requestAddBinClip(id, xml.documentElement(), parentFolder, i18n("Create color clip"), callBack); 0086 return res ? id : QStringLiteral("-1"); 0087 } 0088 0089 QString ClipCreator::createPlaylistClip(const QString &name, std::pair<int, int> tracks, const QString &parentFolder, 0090 const std::shared_ptr<ProjectItemModel> &model) 0091 { 0092 const QUuid uuid = QUuid::createUuid(); 0093 std::shared_ptr<Mlt::Tractor> timeline(new Mlt::Tractor(pCore->getProjectProfile())); 0094 timeline->lock(); 0095 Mlt::Producer bk(pCore->getProjectProfile(), "colour:0"); 0096 bk.set_in_and_out(0, 1); 0097 bk.set("kdenlive:playlistid", "black_track"); 0098 timeline->insert_track(bk, 0); 0099 // Audio tracks 0100 for (int ix = 1; ix <= tracks.first; ix++) { 0101 Mlt::Playlist pl(pCore->getProjectProfile()); 0102 timeline->insert_track(pl, ix); 0103 std::unique_ptr<Mlt::Producer> track(timeline->track(ix)); 0104 track->set("kdenlive:audio_track", 1); 0105 track->set("kdenlive:timeline_active", 1); 0106 } 0107 // Video tracks 0108 for (int ix = tracks.first + 1; ix <= (tracks.first + tracks.second); ix++) { 0109 Mlt::Playlist pl(pCore->getProjectProfile()); 0110 timeline->insert_track(pl, ix); 0111 std::unique_ptr<Mlt::Producer> track(timeline->track(ix)); 0112 track->set("kdenlive:timeline_active", 1); 0113 } 0114 timeline->unlock(); 0115 timeline->set("kdenlive:uuid", uuid.toString().toUtf8().constData()); 0116 timeline->set("kdenlive:clipname", name.toUtf8().constData()); 0117 timeline->set("kdenlive:duration", 1); 0118 timeline->set("kdenlive:producer_type", ClipType::Timeline); 0119 std::shared_ptr<Mlt::Producer> prod(new Mlt::Producer(timeline->get_producer())); 0120 prod->set("id", uuid.toString().toUtf8().constData()); 0121 prod->set("kdenlive:uuid", uuid.toString().toUtf8().constData()); 0122 prod->set("kdenlive:clipname", name.toUtf8().constData()); 0123 prod->set("kdenlive:duration", 1); 0124 prod->set("kdenlive:producer_type", ClipType::Timeline); 0125 QString id; 0126 Fun undo = []() { return true; }; 0127 Fun redo = []() { return true; }; 0128 // Create the timelines folder to store timeline clips 0129 bool res = false; 0130 if (tracks.first > 0) { 0131 timeline->set("kdenlive:sequenceproperties.hasAudio", 1); 0132 prod->set("kdenlive:sequenceproperties.hasAudio", 1); 0133 } 0134 if (tracks.second > 0) { 0135 timeline->set("kdenlive:sequenceproperties.hasVideo", 1); 0136 prod->set("kdenlive:sequenceproperties.hasVideo", 1); 0137 } 0138 timeline->set("kdenlive:sequenceproperties.tracksCount", tracks.first + tracks.second); 0139 prod->set("kdenlive:sequenceproperties.tracksCount", tracks.first + tracks.second); 0140 0141 res = model->requestAddBinClip(id, prod, parentFolder, undo, redo); 0142 if (res) { 0143 // Open playlist timeline 0144 pCore->projectManager()->initSequenceProperties(uuid, tracks); 0145 pCore->projectManager()->openTimeline(id, uuid); 0146 std::shared_ptr<TimelineItemModel> model = pCore->currentDoc()->getTimeline(uuid); 0147 Fun local_redo = [uuid, id, model]() { return pCore->projectManager()->openTimeline(id, uuid, -1, false, model); }; 0148 Fun local_undo = [uuid]() { 0149 pCore->projectManager()->closeTimeline(uuid, true, false); 0150 return true; 0151 }; 0152 pCore->currentDoc()->checkUsage(uuid); 0153 UPDATE_UNDO_REDO_NOLOCK(local_redo, local_undo, undo, redo); 0154 } 0155 pCore->pushUndo(undo, redo, i18n("Create sequence")); 0156 return res ? id : QStringLiteral("-1"); 0157 } 0158 0159 QString ClipCreator::createPlaylistClipWithUndo(const QString &name, std::pair<int, int> tracks, const QString &parentFolder, 0160 const std::shared_ptr<ProjectItemModel> &model, Fun &undo, Fun &redo) 0161 { 0162 const QUuid uuid = QUuid::createUuid(); 0163 std::shared_ptr<Mlt::Tractor> timeline(new Mlt::Tractor(pCore->getProjectProfile())); 0164 timeline->lock(); 0165 Mlt::Producer bk(pCore->getProjectProfile(), "colour:0"); 0166 bk.set_in_and_out(0, 1); 0167 bk.set("kdenlive:playlistid", "black_track"); 0168 timeline->insert_track(bk, 0); 0169 // Audio tracks 0170 for (int ix = 1; ix <= tracks.first; ix++) { 0171 Mlt::Playlist pl(pCore->getProjectProfile()); 0172 timeline->insert_track(pl, ix); 0173 std::unique_ptr<Mlt::Producer> track(timeline->track(ix)); 0174 track->set("kdenlive:audio_track", 1); 0175 track->set("kdenlive:timeline_active", 1); 0176 } 0177 // Video tracks 0178 for (int ix = tracks.first + 1; ix <= (tracks.first + tracks.second); ix++) { 0179 Mlt::Playlist pl(pCore->getProjectProfile()); 0180 timeline->insert_track(pl, ix); 0181 std::unique_ptr<Mlt::Producer> track(timeline->track(ix)); 0182 track->set("kdenlive:timeline_active", 1); 0183 } 0184 timeline->unlock(); 0185 timeline->set("kdenlive:uuid", uuid.toString().toUtf8().constData()); 0186 timeline->set("kdenlive:clipname", name.toUtf8().constData()); 0187 timeline->set("kdenlive:duration", 1); 0188 timeline->set("kdenlive:producer_type", ClipType::Timeline); 0189 std::shared_ptr<Mlt::Producer> prod(new Mlt::Producer(timeline->get_producer())); 0190 prod->set("id", uuid.toString().toUtf8().constData()); 0191 prod->set("kdenlive:uuid", uuid.toString().toUtf8().constData()); 0192 prod->set("kdenlive:clipname", name.toUtf8().constData()); 0193 prod->set("kdenlive:duration", 1); 0194 prod->set("kdenlive:producer_type", ClipType::Timeline); 0195 QString id; 0196 // Create the timelines folder to store timeline clips 0197 bool res = false; 0198 if (tracks.first > 0) { 0199 timeline->set("kdenlive:sequenceproperties.hasAudio", 1); 0200 prod->set("kdenlive:sequenceproperties.hasAudio", 1); 0201 } 0202 if (tracks.second > 0) { 0203 timeline->set("kdenlive:sequenceproperties.hasVideo", 1); 0204 prod->set("kdenlive:sequenceproperties.hasVideo", 1); 0205 } 0206 timeline->set("kdenlive:sequenceproperties.tracksCount", tracks.first + tracks.second); 0207 prod->set("kdenlive:sequenceproperties.tracksCount", tracks.first + tracks.second); 0208 0209 res = model->requestAddBinClip(id, prod, parentFolder, undo, redo); 0210 if (res) { 0211 // Open playlist timeline 0212 pCore->projectManager()->initSequenceProperties(uuid, tracks); 0213 pCore->projectManager()->openTimeline(id, uuid); 0214 std::shared_ptr<TimelineItemModel> model = pCore->currentDoc()->getTimeline(uuid); 0215 Fun local_redo = [uuid, id, model]() { return pCore->projectManager()->openTimeline(id, uuid, -1, false, model); }; 0216 Fun local_undo = [uuid]() { 0217 pCore->projectManager()->closeTimeline(uuid, true, false); 0218 return true; 0219 }; 0220 UPDATE_UNDO_REDO_NOLOCK(local_redo, local_undo, undo, redo); 0221 } 0222 return res ? id : QStringLiteral("-1"); 0223 } 0224 0225 QString ClipCreator::createPlaylistClip(const QString &parentFolder, const std::shared_ptr<ProjectItemModel> &model, std::shared_ptr<Mlt::Producer> producer, 0226 const QMap<QString, QString> mainProperties) 0227 { 0228 QString id; 0229 Fun undo = []() { return true; }; 0230 Fun redo = []() { return true; }; 0231 QMapIterator<QString, QString> i(mainProperties); 0232 while (i.hasNext()) { 0233 i.next(); 0234 producer->set(i.key().toUtf8().constData(), i.value().toUtf8().constData()); 0235 } 0236 QString folderId; 0237 bool res = false; 0238 if (parentFolder == QLatin1String("-1")) { 0239 // Create timeline folder 0240 folderId = model->getFolderIdByName(i18n("Sequences")); 0241 if (folderId.isEmpty()) { 0242 res = model->requestAddFolder(folderId, i18n("Sequences"), QStringLiteral("-1"), undo, redo); 0243 } else { 0244 res = true; 0245 } 0246 } 0247 if (!res) { 0248 folderId = parentFolder; 0249 } 0250 res = model->requestAddBinClip(id, producer, folderId, undo, redo); 0251 pCore->pushUndo(undo, redo, i18n("Create sequence")); 0252 return res ? id : QStringLiteral("-1"); 0253 } 0254 0255 QDomDocument ClipCreator::getXmlFromUrl(const QString &path) 0256 { 0257 QDomDocument xml; 0258 QUrl fileUrl = QUrl::fromLocalFile(path); 0259 if (fileUrl.matches(pCore->currentDoc()->url(), QUrl::RemoveScheme | QUrl::NormalizePathSegments)) { 0260 // Cannot embed a project in itself 0261 KMessageBox::error(QApplication::activeWindow(), i18n("You cannot add a project inside itself."), i18n("Cannot create clip")); 0262 return xml; 0263 } 0264 QMimeDatabase db; 0265 QMimeType type = db.mimeTypeForUrl(fileUrl); 0266 0267 QDomElement prod; 0268 qDebug() << "=== GOT DROPPED MIME: " << type.name(); 0269 if (type.name().startsWith(QLatin1String("image/")) && !type.name().contains(QLatin1String("image/gif"))) { 0270 int duration = pCore->getDurationFromString(KdenliveSettings::image_duration()); 0271 prod = createProducer(xml, ClipType::Image, path, QString(), duration, QString()); 0272 } else if (type.inherits(QStringLiteral("application/x-kdenlivetitle"))) { 0273 // opening a title file 0274 QDomDocument txtdoc(QStringLiteral("titledocument")); 0275 if (!Xml::docContentFromFile(txtdoc, path, false)) { 0276 return QDomDocument(); 0277 } 0278 // extract embedded images 0279 QDomNodeList items = txtdoc.elementsByTagName(QStringLiteral("content")); 0280 for (int j = 0; j < items.count(); ++j) { 0281 QDomElement content = items.item(j).toElement(); 0282 if (content.hasAttribute(QStringLiteral("base64"))) { 0283 QString titlesFolder = pCore->currentDoc()->projectDataFolder() + QStringLiteral("/titles/"); 0284 QString imgPath = TitleDocument::extractBase64Image(titlesFolder, content.attribute(QStringLiteral("base64"))); 0285 if (!imgPath.isEmpty()) { 0286 content.setAttribute(QStringLiteral("url"), imgPath); 0287 content.removeAttribute(QStringLiteral("base64")); 0288 } 0289 } 0290 } 0291 prod = createProducer(xml, ClipType::Text, path, QString(), -1, QString()); 0292 QString titleData = txtdoc.toString(); 0293 prod.setAttribute(QStringLiteral("xmldata"), titleData); 0294 } else { 0295 // it is a "normal" file, just use a producer 0296 prod = xml.createElement(QStringLiteral("producer")); 0297 xml.appendChild(prod); 0298 QMap<QString, QString> properties; 0299 properties.insert(QStringLiteral("resource"), path); 0300 Xml::addXmlProperties(prod, properties); 0301 } 0302 return xml; 0303 } 0304 0305 QString ClipCreator::createClipFromFile(const QString &path, const QString &parentFolder, const std::shared_ptr<ProjectItemModel> &model, Fun &undo, Fun &redo, 0306 const std::function<void(const QString &)> &readyCallBack) 0307 { 0308 qDebug() << "/////////// createClipFromFile" << path << parentFolder; 0309 QDomDocument xml = getXmlFromUrl(path); 0310 if (xml.isNull()) { 0311 return QStringLiteral("-1"); 0312 } 0313 qDebug() << "/////////// final xml" << xml.toString(); 0314 QString id; 0315 bool res = model->requestAddBinClip(id, xml.documentElement(), parentFolder, undo, redo, readyCallBack); 0316 return res ? id : QStringLiteral("-1"); 0317 } 0318 0319 bool ClipCreator::createClipFromFile(const QString &path, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model) 0320 { 0321 Fun undo = []() { return true; }; 0322 Fun redo = []() { return true; }; 0323 auto id = ClipCreator::createClipFromFile(path, parentFolder, std::move(model), undo, redo); 0324 bool ok = (id != QStringLiteral("-1")); 0325 if (ok) { 0326 pCore->pushUndo(undo, redo, i18nc("@action", "Add clip")); 0327 } 0328 return ok; 0329 } 0330 0331 QString ClipCreator::createSlideshowClip(const QString &path, int duration, const QString &name, const QString &parentFolder, 0332 const std::unordered_map<QString, QString> &properties, const std::shared_ptr<ProjectItemModel> &model) 0333 { 0334 QDomDocument xml; 0335 0336 auto prod = createProducer(xml, ClipType::SlideShow, path, name, duration, QString()); 0337 Xml::addXmlProperties(prod, properties); 0338 0339 QString id; 0340 std::function<void(const QString &)> callBack = [](const QString &binId) { pCore->activeBin()->selectClipById(binId); }; 0341 bool res = model->requestAddBinClip(id, xml.documentElement(), parentFolder, i18n("Create slideshow clip"), callBack); 0342 return res ? id : QStringLiteral("-1"); 0343 } 0344 0345 QString ClipCreator::createTitleTemplate(const QString &path, const QString &text, const QString &name, const QString &parentFolder, 0346 const std::shared_ptr<ProjectItemModel> &model) 0347 { 0348 QDomDocument xml; 0349 0350 // We try to retrieve duration for template 0351 int duration = 0; 0352 QDomDocument titledoc; 0353 if (Xml::docContentFromFile(titledoc, path, false)) { 0354 if (titledoc.documentElement().hasAttribute(QStringLiteral("duration"))) { 0355 duration = titledoc.documentElement().attribute(QStringLiteral("duration")).toInt(); 0356 } else { 0357 // keep some time for backwards compatibility - 26/12/12 0358 duration = titledoc.documentElement().attribute(QStringLiteral("out")).toInt(); 0359 } 0360 } 0361 0362 // Duration not found, we fall-back to defaults 0363 if (duration == 0) { 0364 duration = pCore->getDurationFromString(KdenliveSettings::title_duration()); 0365 } 0366 auto prod = createProducer(xml, ClipType::TextTemplate, path, name, duration, QString()); 0367 if (!text.isEmpty()) { 0368 prod.setAttribute(QStringLiteral("templatetext"), text); 0369 } 0370 0371 QString id; 0372 std::function<void(const QString &)> callBack = [](const QString &binId) { pCore->activeBin()->selectClipById(binId); }; 0373 bool res = model->requestAddBinClip(id, xml.documentElement(), parentFolder, i18n("Create title template"), callBack); 0374 return res ? id : QStringLiteral("-1"); 0375 } 0376 0377 const QString ClipCreator::createClipsFromList(const QList<QUrl> &list, bool checkRemovable, const QString &parentFolder, 0378 const std::shared_ptr<ProjectItemModel> &model, Fun &undo, Fun &redo, bool topLevel) 0379 { 0380 QString createdItem; 0381 // Check for duplicates 0382 QList<QUrl> cleanList; 0383 QStringList duplicates; 0384 bool firstClip = topLevel; 0385 const QUuid uuid = model->uuid(); 0386 pCore->bin()->shouldCheckProfile = 0387 (KdenliveSettings::default_profile().isEmpty() || KdenliveSettings::checkfirstprojectclip()) && !pCore->bin()->hasUserClip(); 0388 for (const QUrl &url : list) { 0389 if (!pCore->projectItemModel()->urlExists(url.toLocalFile()) || QFileInfo(url.toLocalFile()).isDir()) { 0390 cleanList << url; 0391 } else { 0392 duplicates << url.toLocalFile(); 0393 } 0394 } 0395 if (!duplicates.isEmpty()) { 0396 if (KMessageBox::warningTwoActionsList(QApplication::activeWindow(), 0397 i18n("The following clips are already inserted in the project. Do you want to duplicate them?"), duplicates, {}, 0398 KGuiItem(i18n("Duplicate")), KStandardGuiItem::cancel()) == KMessageBox::PrimaryAction) { 0399 cleanList = list; 0400 } 0401 } 0402 0403 qDebug() << "/////////// creatclipsfromlist" << cleanList << checkRemovable << parentFolder; 0404 QMimeDatabase db; 0405 QList<QDir> checkedDirectories; 0406 bool removableProject = checkRemovable ? isOnRemovableDevice(pCore->currentDoc()->projectDataFolder()) : false; 0407 int urlsCount = cleanList.count(); 0408 bool stopProcess = false; 0409 QObject progressOwner; 0410 QMetaObject::Connection stopConnect = QObject::connect(pCore.get(), &Core::stopProgressTask, &progressOwner, [&stopProcess]() { stopProcess = true; }); 0411 int current = 0; 0412 int lastCount = -1; 0413 for (const QUrl &file : qAsConst(cleanList)) { 0414 current++; 0415 if (stopProcess) { 0416 pCore->displayMessage(QString(), OperationCompletedMessage, 100); 0417 break; 0418 } 0419 if (model->uuid() != uuid) { 0420 // Project was closed, abort 0421 qDebug() << "/// PROJECT UUID MISMATCH; ABORTING"; 0422 pCore->displayMessage(QString(), OperationCompletedMessage, 100); 0423 return QString(); 0424 } 0425 QFileInfo info(file.toLocalFile()); 0426 if (!info.exists()) { 0427 qDebug() << "/// File does not exist: " << info.absoluteFilePath(); 0428 continue; 0429 } 0430 if (urlsCount > 3 && !stopProcess) { 0431 int count = int(100 * current / urlsCount); 0432 if (count != lastCount) { 0433 lastCount = count; 0434 pCore->loadingClips(lastCount, true); 0435 } 0436 qApp->processEvents(); 0437 } 0438 if (info.isDir()) { 0439 // user dropped a folder, import its files 0440 QDir dir(file.toLocalFile()); 0441 bool ok = false; 0442 QDir thumbFolder = pCore->currentDoc()->getCacheDir(CacheAudio, &ok); 0443 if (ok && thumbFolder == dir) { 0444 // Do not try to import our thumbnail folder 0445 continue; 0446 } 0447 thumbFolder = pCore->currentDoc()->getCacheDir(CacheThumbs, &ok); 0448 if (ok && thumbFolder == dir) { 0449 // Do not try to import our thumbnail folder 0450 continue; 0451 } 0452 thumbFolder = pCore->currentDoc()->getCacheDir(CacheProxy, &ok); 0453 if (ok && thumbFolder == dir) { 0454 // Do not try to import our thumbnail folder 0455 continue; 0456 } 0457 thumbFolder = pCore->currentDoc()->getCacheDir(CachePreview, &ok); 0458 if (ok && thumbFolder == dir) { 0459 // Do not try to import our thumbnail folder 0460 continue; 0461 } 0462 QString folderId; 0463 Fun local_undo = []() { return true; }; 0464 Fun local_redo = []() { return true; }; 0465 QStringList subfolders = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); 0466 dir.setNameFilters(ClipCreationDialog::getExtensions()); 0467 QStringList result = dir.entryList(QDir::Files); 0468 QList<QUrl> folderFiles; 0469 for (const QString &path : qAsConst(result)) { 0470 QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(path)); 0471 folderFiles.append(url); 0472 } 0473 if (folderFiles.isEmpty()) { 0474 QList<QUrl> sublist; 0475 for (const QString &sub : qAsConst(subfolders)) { 0476 QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(sub)); 0477 if (!list.contains(url)) { 0478 sublist << url; 0479 } 0480 } 0481 if (!sublist.isEmpty()) { 0482 if (!KdenliveSettings::ignoresubdirstructure() || topLevel) { 0483 // Create main folder 0484 bool folderCreated = pCore->projectItemModel()->requestAddFolder(folderId, dir.dirName(), parentFolder, local_undo, local_redo); 0485 if (!folderCreated) { 0486 continue; 0487 } 0488 } else { 0489 folderId = parentFolder; 0490 } 0491 0492 createdItem = folderId; 0493 // load subfolders 0494 const QString clipId = createClipsFromList(sublist, checkRemovable, folderId, model, undo, redo, false); 0495 if (createdItem.isEmpty() && clipId != QLatin1String("-1")) { 0496 createdItem = clipId; 0497 } 0498 } 0499 } else { 0500 if (!KdenliveSettings::ignoresubdirstructure() || topLevel) { 0501 // Create main folder 0502 bool folderCreated = pCore->projectItemModel()->requestAddFolder(folderId, dir.dirName(), parentFolder, local_undo, local_redo); 0503 if (!folderCreated) { 0504 continue; 0505 } 0506 } else { 0507 folderId = parentFolder; 0508 } 0509 createdItem = folderId; 0510 const QString clipId = createClipsFromList(folderFiles, checkRemovable, folderId, model, local_undo, local_redo, false); 0511 if (clipId.isEmpty() || clipId == QLatin1String("-1")) { 0512 local_undo(); 0513 } else { 0514 if (createdItem.isEmpty()) { 0515 createdItem = clipId; 0516 } 0517 UPDATE_UNDO_REDO_NOLOCK(local_redo, local_undo, undo, redo) 0518 } 0519 // Check subfolders 0520 QList<QUrl> sublist; 0521 for (const QString &sub : qAsConst(subfolders)) { 0522 QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(sub)); 0523 if (!list.contains(url)) { 0524 sublist << url; 0525 } 0526 } 0527 if (!sublist.isEmpty()) { 0528 // load subfolders 0529 createClipsFromList(sublist, checkRemovable, folderId, model, undo, redo, false); 0530 } 0531 } 0532 } else { 0533 // file is not a directory 0534 if (checkRemovable && !removableProject) { 0535 // Check if the directory was already checked 0536 QDir fileDir = QFileInfo(file.toLocalFile()).absoluteDir(); 0537 if (checkedDirectories.contains(fileDir)) { 0538 // Folder already checked, continue 0539 } else if (isOnRemovableDevice(file)) { 0540 int answer = KMessageBox::warningContinueCancel(QApplication::activeWindow(), 0541 i18n("Clip <b>%1</b><br /> is on a removable device, will not be available when device is " 0542 "unplugged or mounted at a different position.\nYou " 0543 "may want to copy it first to your hard-drive. Would you like to add it anyways?", 0544 file.path()), 0545 i18n("Removable device"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), 0546 QStringLiteral("confirm_removable_device")); 0547 0548 if (answer == KMessageBox::Cancel) { 0549 break; 0550 } 0551 } 0552 checkedDirectories << fileDir; 0553 } 0554 std::function<void(const QString &)> callBack = [](const QString &) {}; 0555 if (firstClip) { 0556 callBack = [](const QString &binId) { pCore->activeBin()->selectClipById(binId); }; 0557 firstClip = false; 0558 } 0559 if (model->uuid() != uuid) { 0560 // Project was closed, abort 0561 pCore->displayMessage(QString(), OperationCompletedMessage, 100); 0562 qDebug() << "/// PROJECT UUID MISMATCH; ABORTING"; 0563 return QString(); 0564 } 0565 const QString clipId = ClipCreator::createClipFromFile(file.toLocalFile(), parentFolder, model, undo, redo, callBack); 0566 if (createdItem.isEmpty() && clipId != QLatin1String("-1")) { 0567 createdItem = clipId; 0568 } 0569 } 0570 if (!stopProcess) { 0571 qApp->processEvents(); 0572 } 0573 } 0574 QObject::disconnect(stopConnect); 0575 pCore->displayMessage(i18n("Loading done"), OperationCompletedMessage, 100); 0576 return createdItem == QLatin1String("-1") ? QString() : createdItem; 0577 } 0578 0579 const QString ClipCreator::createClipsFromList(const QList<QUrl> &list, bool checkRemovable, const QString &parentFolder, 0580 std::shared_ptr<ProjectItemModel> model) 0581 { 0582 Fun undo = []() { return true; }; 0583 Fun redo = []() { return true; }; 0584 const QString id = ClipCreator::createClipsFromList(list, checkRemovable, parentFolder, std::move(model), undo, redo); 0585 if (!id.isEmpty()) { 0586 pCore->pushUndo(undo, redo, i18np("Add clip", "Add clips", list.size())); 0587 } 0588 return id; 0589 }