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