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 }