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 }