File indexing completed on 2024-04-28 04:52:15

0001 /*
0002     SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 
0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "projectsettings.h"
0008 
0009 #include "bin/bin.h"
0010 #include "bin/projectclip.h"
0011 #include "bin/projectfolder.h"
0012 #include "bin/projectitemmodel.h"
0013 #include "core.h"
0014 #include "dialogs/profilesdialog.h"
0015 #include "dialogs/wizard.h"
0016 #include "doc/kdenlivedoc.h"
0017 #include "kdenlivesettings.h"
0018 #include "mainwindow.h"
0019 #include "mltcontroller/clipcontroller.h"
0020 #include "profiles/profilemodel.hpp"
0021 #include "profiles/profilerepository.hpp"
0022 #include "project/dialogs/profilewidget.h"
0023 #include "project/dialogs/temporarydata.h"
0024 #include "titler/titlewidget.h"
0025 #include "xml/xml.hpp"
0026 
0027 #include "kdenlive_debug.h"
0028 #include "utils/KMessageBox_KdenliveCompat.h"
0029 #include <KIO/FileCopyJob>
0030 #include <KLocalizedString>
0031 #include <KMessageBox>
0032 #include <kio/directorysizejob.h>
0033 
0034 #include <QDir>
0035 #include <QFileDialog>
0036 #include <QInputDialog>
0037 #include <QStandardItemModel>
0038 #include <QStyledItemDelegate>
0039 #include <QTemporaryFile>
0040 
0041 class NoEditDelegate : public QStyledItemDelegate
0042 {
0043 public:
0044     NoEditDelegate(QObject *parent = nullptr)
0045         : QStyledItemDelegate(parent)
0046     {
0047     }
0048     QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
0049     {
0050         Q_UNUSED(parent);
0051         Q_UNUSED(option);
0052         Q_UNUSED(index);
0053         return nullptr;
0054     }
0055 };
0056 
0057 ProjectSettings::ProjectSettings(KdenliveDoc *doc, QMap<QString, QString> metadata, QStringList lumas, int videotracks, int audiotracks, int audiochannels,
0058                                  const QString & /*projectPath*/, bool readOnlyTracks, bool savedProject, QWidget *parent)
0059     : QDialog(parent)
0060     , m_savedProject(savedProject)
0061     , m_lumas(std::move(lumas))
0062     , m_newProject(doc == nullptr)
0063 {
0064     setupUi(this);
0065     tabWidget->setTabBarAutoHide(true);
0066     auto *vbox = new QVBoxLayout;
0067     vbox->setContentsMargins(0, 0, 0, 0);
0068     m_pw = new ProfileWidget(this);
0069     vbox->addWidget(m_pw);
0070     profile_box->setLayout(vbox);
0071     profile_box->setTitle(i18n("Select the profile (preset) of the project"));
0072     file_message->hide();
0073 
0074     default_folder->setText(i18n("Default: %1", QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
0075 
0076     list_search->setTreeWidget(files_list);
0077     project_folder->setMode(KFile::Directory);
0078 
0079     m_buttonOk = buttonBox->button(QDialogButtonBox::Ok);
0080     // buttonOk->setEnabled(false);
0081     audio_thumbs->setChecked(KdenliveSettings::audiothumbnails());
0082     video_thumbs->setChecked(KdenliveSettings::videothumbnails());
0083     audio_tracks->setValue(audiotracks);
0084     video_tracks->setValue(videotracks);
0085     qDebug() << "::::: GOT PROJECT AUDIO CHANNELS: " << audiochannels << "\nBBBBBBBBBBBBBBBBBBB";
0086     if (audiochannels == 4) {
0087         audio_channels->setCurrentIndex(1);
0088     } else if (audiochannels == 6) {
0089         audio_channels->setCurrentIndex(2);
0090     }
0091     connect(generate_proxy, &QAbstractButton::toggled, proxy_minsize, &QWidget::setEnabled);
0092     connect(checkProxy, &QToolButton::clicked, pCore.get(), &Core::testProxies);
0093     connect(generate_imageproxy, &QAbstractButton::toggled, proxy_imageminsize, &QWidget::setEnabled);
0094     connect(generate_imageproxy, &QAbstractButton::toggled, image_label, &QWidget::setEnabled);
0095     connect(generate_imageproxy, &QAbstractButton::toggled, proxy_imagesize, &QWidget::setEnabled);
0096     connect(video_tracks, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this]() {
0097         if (video_tracks->value() + audio_tracks->value() <= 0) {
0098             video_tracks->setValue(1);
0099         }
0100     });
0101     connect(audio_tracks, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this]() {
0102         if (video_tracks->value() + audio_tracks->value() <= 0) {
0103             audio_tracks->setValue(1);
0104         }
0105     });
0106 
0107     connect(external_proxy, &QCheckBox::toggled, this, &ProjectSettings::slotExternalProxyChanged);
0108     connect(external_proxy, &QCheckBox::toggled, external_proxy_profile, &QComboBox::setEnabled);
0109     connect(external_proxy_profile, &QComboBox::currentTextChanged, this, &ProjectSettings::slotExternalProxyProfileChanged);
0110     slotExternalProxyChanged(external_proxy->checkState());
0111 
0112     QString currentProf;
0113     if (doc) {
0114         currentProf = pCore->getCurrentProfile()->path();
0115         proxy_box->setChecked(doc->useProxy());
0116         generate_proxy->setChecked(doc->getDocumentProperty(QStringLiteral("generateproxy")).toInt() != 0);
0117         proxy_minsize->setValue(doc->getDocumentProperty(QStringLiteral("proxyminsize")).toInt());
0118         m_proxyparameters = doc->getDocumentProperty(QStringLiteral("proxyparams"));
0119         m_initialExternalProxyProfile = doc->getDocumentProperty(QStringLiteral("externalproxyparams"));
0120         generate_imageproxy->setChecked(doc->getDocumentProperty(QStringLiteral("generateimageproxy")).toInt() != 0);
0121         proxy_imageminsize->setValue(doc->getDocumentProperty(QStringLiteral("proxyimageminsize")).toInt());
0122         proxy_imagesize->setValue(doc->getDocumentProperty(QStringLiteral("proxyimagesize")).toInt());
0123         proxy_resize->setValue(doc->getDocumentProperty(QStringLiteral("proxyresize")).toInt());
0124         m_proxyextension = doc->getDocumentProperty(QStringLiteral("proxyextension"));
0125         external_proxy->setChecked(doc->getDocumentProperty(QStringLiteral("enableexternalproxy")).toInt() != 0);
0126         m_previewparams = doc->getDocumentProperty(QStringLiteral("previewparameters"));
0127         m_previewextension = doc->getDocumentProperty(QStringLiteral("previewextension"));
0128         QString storageFolder = doc->getDocumentProperty(QStringLiteral("storagefolder"));
0129         if (doc->projectTempFolder() == (QFileInfo(doc->url().toLocalFile()).absolutePath() + QStringLiteral("/cachefiles"))) {
0130             same_folder->setChecked(true);
0131         } else if (!storageFolder.isEmpty()) {
0132             custom_folder->setChecked(true);
0133         } else {
0134             default_folder->setChecked(true);
0135         }
0136         project_folder->setUrl(QUrl::fromLocalFile(doc->projectTempFolder()));
0137         auto *cacheWidget = new TemporaryData(doc, true, this);
0138         cacheWidget->buttonBox->hide();
0139         connect(cacheWidget, &TemporaryData::disableProxies, this, &ProjectSettings::disableProxies);
0140         connect(cacheWidget, &TemporaryData::disablePreview, this, &ProjectSettings::disablePreview);
0141         tabWidget->addTab(cacheWidget, i18n("Cache Data"));
0142     } else {
0143         currentProf = KdenliveSettings::default_profile();
0144         proxy_box->setChecked(KdenliveSettings::enableproxy());
0145         external_proxy->setChecked(KdenliveSettings::externalproxy());
0146         qDebug() << "//// INITIAL REPORT; ENABLE EXT PROCY: " << KdenliveSettings::externalproxy() << "\n++++++++";
0147         m_initialExternalProxyProfile = KdenliveSettings::externalProxyProfile();
0148         generate_proxy->setChecked(KdenliveSettings::generateproxy());
0149         proxy_minsize->setValue(KdenliveSettings::proxyminsize());
0150         m_proxyparameters = KdenliveSettings::proxyparams();
0151         generate_imageproxy->setChecked(KdenliveSettings::generateimageproxy());
0152         proxy_imageminsize->setValue(KdenliveSettings::proxyimageminsize());
0153         proxy_resize->setValue(KdenliveSettings::proxyscale());
0154         m_proxyextension = KdenliveSettings::proxyextension();
0155         m_previewparams = KdenliveSettings::previewparams();
0156         m_previewextension = KdenliveSettings::previewextension();
0157         if (!KdenliveSettings::defaultprojectfolder().isEmpty()) {
0158             project_folder->setUrl(QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder()));
0159         } else {
0160             project_folder->setUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
0161         }
0162         if (KdenliveSettings::customprojectfolder()) {
0163             custom_folder->setChecked(true);
0164         } else if (KdenliveSettings::sameprojectfolder()) {
0165             same_folder->setChecked(true);
0166         } else {
0167             default_folder->setChecked(true);
0168         }
0169     }
0170     external_proxy_profile->setEnabled(external_proxy->isChecked());
0171 
0172     // Select profile
0173     m_pw->loadProfile(currentProf);
0174 
0175     proxy_minsize->setEnabled(generate_proxy->isChecked());
0176     proxy_imageminsize->setEnabled(generate_imageproxy->isChecked());
0177     QString currentProfileParams;
0178     if (!m_previewparams.isEmpty() || !m_previewextension.isEmpty()) {
0179         currentProfileParams = QString("%1;%2").arg(m_previewparams, m_previewextension);
0180     }
0181     m_tlPreviewProfiles = new EncodingTimelinePreviewProfilesChooser(this, true, currentProfileParams);
0182     preview_profile_box->addWidget(m_tlPreviewProfiles);
0183     connect(m_pw, &ProfileWidget::profileChanged, this, [this]() { m_tlPreviewProfiles->filterPreviewProfiles(m_pw->selectedProfile()); });
0184     m_tlPreviewProfiles->filterPreviewProfiles(currentProf);
0185     loadProxyProfiles();
0186     loadExternalProxyProfiles();
0187 
0188     // Proxy GUI stuff
0189     proxy_showprofileinfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
0190     proxy_showprofileinfo->setToolTip(i18n("Show default profile parameters"));
0191     proxy_manageprofile->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
0192     proxy_manageprofile->setToolTip(i18n("Manage proxy profiles"));
0193     checkProxy->setIcon(QIcon::fromTheme(QStringLiteral("run-build")));
0194     checkProxy->setToolTip(i18n("Compare proxy profiles efficiency"));
0195 
0196     connect(proxy_manageprofile, &QAbstractButton::clicked, this, &ProjectSettings::slotManageEncodingProfile);
0197     proxy_profile->setToolTip(i18n("Select default proxy profile"));
0198 
0199     connect(proxy_profile, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ProjectSettings::slotUpdateProxyParams);
0200     proxyparams->setVisible(false);
0201     proxyparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
0202     connect(proxy_showprofileinfo, &QAbstractButton::clicked, proxyparams, &QWidget::setVisible);
0203 
0204     external_proxy_profile->setToolTip(i18n("Select camcorder profile"));
0205 
0206     if (readOnlyTracks) {
0207         video_tracks->setEnabled(false);
0208         audio_tracks->setEnabled(false);
0209         audio_channels->setEnabled(false);
0210     }
0211 
0212     metadata_list->setItemDelegateForColumn(0, new NoEditDelegate(this));
0213     connect(metadata_list, &QTreeWidget::itemDoubleClicked, this, &ProjectSettings::slotEditMetadata);
0214 
0215     // Metadata list
0216     QTreeWidgetItem *item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Title"));
0217     item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.title.markup"));
0218     if (metadata.contains(QStringLiteral("meta.attr.title.markup"))) {
0219         item->setText(1, metadata.value(QStringLiteral("meta.attr.title.markup")));
0220         metadata.remove(QStringLiteral("meta.attr.title.markup"));
0221     }
0222     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0223     item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Author"));
0224     item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.author.markup"));
0225     if (metadata.contains(QStringLiteral("meta.attr.author.markup"))) {
0226         item->setText(1, metadata.value(QStringLiteral("meta.attr.author.markup")));
0227         metadata.remove(QStringLiteral("meta.attr.author.markup"));
0228     } else if (metadata.contains(QStringLiteral("meta.attr.artist.markup"))) {
0229         item->setText(0, i18n("Artist"));
0230         item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.artist.markup"));
0231         item->setText(1, metadata.value(QStringLiteral("meta.attr.artist.markup")));
0232         metadata.remove(QStringLiteral("meta.attr.artist.markup"));
0233     }
0234     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0235     item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Copyright"));
0236     item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.copyright.markup"));
0237     if (metadata.contains(QStringLiteral("meta.attr.copyright.markup"))) {
0238         item->setText(1, metadata.value(QStringLiteral("meta.attr.copyright.markup")));
0239         metadata.remove(QStringLiteral("meta.attr.copyright.markup"));
0240     }
0241     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0242     item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Year"));
0243     item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.year.markup"));
0244     if (metadata.contains(QStringLiteral("meta.attr.year.markup"))) {
0245         item->setText(1, metadata.value(QStringLiteral("meta.attr.year.markup")));
0246         metadata.remove(QStringLiteral("meta.attr.year.markup"));
0247     } else if (metadata.contains(QStringLiteral("meta.attr.date.markup"))) {
0248         item->setText(0, i18n("Date"));
0249         item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.date.markup"));
0250         item->setText(1, metadata.value(QStringLiteral("meta.attr.date.markup")));
0251         metadata.remove(QStringLiteral("meta.attr.date.markup"));
0252     }
0253     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0254 
0255     QMap<QString, QString>::const_iterator meta = metadata.constBegin();
0256     while (meta != metadata.constEnd()) {
0257         item = new QTreeWidgetItem(metadata_list, QStringList() << meta.key().section(QLatin1Char('.'), 2, 2));
0258         item->setData(0, Qt::UserRole, meta.key());
0259         item->setText(1, meta.value());
0260         item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0261         ++meta;
0262     }
0263 
0264     connect(add_metadata, &QAbstractButton::clicked, this, &ProjectSettings::slotAddMetadataField);
0265     connect(delete_metadata, &QAbstractButton::clicked, this, &ProjectSettings::slotDeleteMetadataField);
0266     add_metadata->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0267     delete_metadata->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0268 
0269     // Guides categories
0270     QWidget *guidesPage = tabWidget->widget(2);
0271     m_guidesCategories = new GuideCategories(doc, this);
0272     QVBoxLayout *guidesLayout = new QVBoxLayout(guidesPage);
0273     guidesLayout->addWidget(m_guidesCategories);
0274 
0275     if (!m_newProject) {
0276         slotUpdateFiles();
0277         connect(delete_unused, &QAbstractButton::clicked, this, &ProjectSettings::slotDeleteUnused);
0278     } else {
0279         // Hide project files tab since its an empty new project
0280         tabWidget->removeTab(4);
0281     }
0282     connect(project_folder, &KUrlRequester::textChanged, this, &ProjectSettings::slotUpdateButton);
0283     connect(button_export, &QAbstractButton::clicked, this, &ProjectSettings::slotExportToText);
0284 }
0285 
0286 void ProjectSettings::slotExternalProxyChanged(bool enabled)
0287 {
0288 
0289     l_relPathOrigToProxy->setVisible(enabled);
0290     le_relPathOrigToProxy->setVisible(enabled);
0291     l_prefix_proxy->setVisible(enabled);
0292     le_prefix_proxy->setVisible(enabled);
0293     l_suffix_proxy->setVisible(enabled);
0294     le_suffix_proxy->setVisible(enabled);
0295     l_relPathProxyToOrig->setVisible(enabled);
0296     le_relPathProxyToOrig->setVisible(enabled);
0297     l_prefix_clip->setVisible(enabled);
0298     le_prefix_clip->setVisible(enabled);
0299     l_suffix_clip->setVisible(enabled);
0300     le_suffix_clip->setVisible(enabled);
0301 
0302     slotExternalProxyProfileChanged(external_proxy_profile->currentText());
0303 }
0304 
0305 void ProjectSettings::setExternalProxyProfileData(const QString &profileData)
0306 {
0307     auto params = profileData.split(";");
0308     QString val1, val2, val3, val4, val5, val6;
0309     int count = 0;
0310     while (params.count() >= 6) {
0311         if (count > 0) {
0312             val1.append(QLatin1Char(';'));
0313             val2.append(QLatin1Char(';'));
0314             val3.append(QLatin1Char(';'));
0315             val4.append(QLatin1Char(';'));
0316             val5.append(QLatin1Char(';'));
0317             val6.append(QLatin1Char(';'));
0318         }
0319         val1.append(params.at(0));
0320         val2.append(params.at(1));
0321         val3.append(params.at(2));
0322         val4.append(params.at(3));
0323         val5.append(params.at(4));
0324         val6.append(params.at(5));
0325         params = params.mid(6);
0326         count++;
0327     }
0328     le_relPathOrigToProxy->setText(val1);
0329     le_prefix_proxy->setText(val2);
0330     le_suffix_proxy->setText(val3);
0331     le_relPathProxyToOrig->setText(val4);
0332     le_prefix_clip->setText(val5);
0333     le_suffix_clip->setText(val6);
0334 }
0335 
0336 void ProjectSettings::slotExternalProxyProfileChanged(const QString &)
0337 {
0338     setExternalProxyProfileData(external_proxy_profile->currentData().toString());
0339 }
0340 
0341 void ProjectSettings::slotEditMetadata(QTreeWidgetItem *item, int)
0342 {
0343     metadata_list->editItem(item, 1);
0344 }
0345 
0346 void ProjectSettings::slotDeleteUnused()
0347 {
0348     QStringList toDelete;
0349     QStringList idsToDelete;
0350     QList<std::shared_ptr<ProjectClip>> clipList = pCore->projectItemModel()->getRootFolder()->childClips();
0351     for (const std::shared_ptr<ProjectClip> &clip : qAsConst(clipList)) {
0352         if (!clip->isIncludedInTimeline()) {
0353             idsToDelete << clip->clipId();
0354             ClipType::ProducerType type = clip->clipType();
0355             if (type != ClipType::Color && type != ClipType::Text && type != ClipType::TextTemplate) {
0356                 QUrl url = QUrl::fromLocalFile(clip->getOriginalUrl());
0357                 if (url.isValid() && !toDelete.contains(url.path()) && QFile::exists(url.path())) {
0358                     toDelete << url.path();
0359                 }
0360             }
0361         }
0362     }
0363     // make sure our urls are not used in another clip
0364     for (const std::shared_ptr<ProjectClip> &clip : qAsConst(clipList)) {
0365         if (clip->isIncludedInTimeline()) {
0366             QUrl url(clip->getOriginalUrl());
0367             if (url.isValid() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
0368         }
0369     }
0370     if (toDelete.count() == 0) {
0371         file_message->setText(i18n("No files to delete on your drive."));
0372         file_message->animatedShow();
0373         pCore->window()->slotCleanProject();
0374         slotUpdateFiles();
0375         return;
0376     }
0377     if (KMessageBox::warningTwoActionsList(
0378             this,
0379             i18n("This will remove the following files from your hard drive.\nThis action cannot be undone, only use if you know "
0380                  "what you are doing.\nAre you sure you want to continue?"),
0381             toDelete, i18n("Delete unused clips"), KStandardGuiItem::del(), KStandardGuiItem::cancel()) != KMessageBox::PrimaryAction)
0382         return;
0383     pCore->projectItemModel()->requestTrashClips(idsToDelete, toDelete);
0384     slotUpdateFiles();
0385 }
0386 
0387 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
0388 {
0389     qDebug() << "// UPDATING PROJECT FILES\n----------\n-----------";
0390     m_projectProxies.clear();
0391     m_projectThumbs.clear();
0392     if (cacheOnly) {
0393         return;
0394     }
0395     files_list->clear();
0396 
0397     // List all files that are used in the project. That also means:
0398     // images included in slideshow and titles, files in playlist clips
0399     // TODO: images used in luma transitions?
0400 
0401     // Setup categories
0402     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
0403     videos->setIcon(0, QIcon::fromTheme(QStringLiteral("video-x-generic")));
0404     videos->setExpanded(true);
0405     QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
0406     sounds->setIcon(0, QIcon::fromTheme(QStringLiteral("audio-x-generic")));
0407     sounds->setExpanded(true);
0408     QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
0409     images->setIcon(0, QIcon::fromTheme(QStringLiteral("image-x-generic")));
0410     images->setExpanded(true);
0411     QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
0412     slideshows->setIcon(0, QIcon::fromTheme(QStringLiteral("image-x-generic")));
0413     slideshows->setExpanded(true);
0414     QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
0415     texts->setIcon(0, QIcon::fromTheme(QStringLiteral("text-plain")));
0416     texts->setExpanded(true);
0417     QTreeWidgetItem *playlists = new QTreeWidgetItem(files_list, QStringList() << i18n("Playlist clips"));
0418     playlists->setIcon(0, QIcon::fromTheme(QStringLiteral("video-mlt-playlist")));
0419     playlists->setExpanded(true);
0420     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
0421     others->setIcon(0, QIcon::fromTheme(QStringLiteral("unknown")));
0422     others->setExpanded(true);
0423     QTreeWidgetItem *subtitles = new QTreeWidgetItem(files_list, QStringList() << i18n("Subtitles"));
0424     subtitles->setIcon(0, QIcon::fromTheme(QStringLiteral("text-plain")));
0425     subtitles->setExpanded(true);
0426     int count = 0;
0427     QStringList allFonts;
0428     for (const QString &file : qAsConst(m_lumas)) {
0429         count++;
0430         new QTreeWidgetItem(images, QStringList() << file);
0431     }
0432     QList<std::shared_ptr<ProjectClip>> clipList = pCore->projectItemModel()->getRootFolder()->childClips();
0433     for (const std::shared_ptr<ProjectClip> &clip : qAsConst(clipList)) {
0434         switch (clip->clipType()) {
0435         case ClipType::Color:
0436         case ClipType::Timeline:
0437             // ignore color and timeline clips in list, there is no real file
0438             break;
0439         case ClipType::SlideShow: {
0440             const QStringList subfiles = extractSlideshowUrls(clip->clipUrl());
0441             for (const QString &file : subfiles) {
0442                 count++;
0443                 new QTreeWidgetItem(slideshows, QStringList() << file);
0444             }
0445             break;
0446         }
0447         case ClipType::Text: {
0448             new QTreeWidgetItem(texts, QStringList() << clip->clipUrl());
0449             QString titleData = clip->getProducerProperty(QStringLiteral("xmldata"));
0450             const QStringList imagefiles = TitleWidget::extractImageList(titleData, pCore->currentDoc()->documentRoot());
0451             const QStringList fonts = TitleWidget::extractFontList(clip->getProducerProperty(QStringLiteral("xmldata")));
0452             for (const QString &file : imagefiles) {
0453                 new QTreeWidgetItem(images, QStringList() << file);
0454             }
0455             allFonts << fonts;
0456             break;
0457         }
0458         case ClipType::Audio:
0459             new QTreeWidgetItem(sounds, QStringList() << clip->clipUrl());
0460             break;
0461         case ClipType::Image:
0462             new QTreeWidgetItem(images, QStringList() << clip->clipUrl());
0463             break;
0464         case ClipType::Playlist: {
0465             new QTreeWidgetItem(playlists, QStringList() << clip->clipUrl());
0466             const QStringList files = extractPlaylistUrls(clip->clipUrl());
0467             for (const QString &file : files) {
0468                 new QTreeWidgetItem(others, QStringList() << file);
0469             }
0470             break;
0471         }
0472         case ClipType::Unknown:
0473             new QTreeWidgetItem(others, QStringList() << clip->clipUrl());
0474             break;
0475         default:
0476             new QTreeWidgetItem(videos, QStringList() << clip->clipUrl());
0477             break;
0478         }
0479     }
0480     // Subtitles
0481     QStringList subtitleFiles = pCore->currentDoc()->getAllSubtitlesPath(true);
0482     for (auto &path : subtitleFiles) {
0483         new QTreeWidgetItem(subtitles, QStringList() << path);
0484     }
0485 
0486     uint used = 0;
0487     uint unUsed = 0;
0488     qint64 usedSize = 0;
0489     qint64 unUsedSize = 0;
0490     pCore->bin()->getBinStats(&used, &unUsed, &usedSize, &unUsedSize);
0491     allFonts.removeDuplicates();
0492     // Hide unused categories
0493     for (int j = 0; j < files_list->topLevelItemCount(); ++j) {
0494         if (files_list->topLevelItem(j)->childCount() == 0) {
0495             files_list->topLevelItem(j)->setHidden(true);
0496         }
0497     }
0498     files_count->setText(QString::number(count));
0499     fonts_list->addItems(allFonts);
0500     if (allFonts.isEmpty()) {
0501         fonts_list->setHidden(true);
0502         label_fonts->setHidden(true);
0503     }
0504     used_count->setText(QString::number(used));
0505     used_size->setText(KIO::convertSize(static_cast<KIO::filesize_t>(usedSize)));
0506     unused_count->setText(QString::number(unUsed));
0507     unused_size->setText(KIO::convertSize(static_cast<KIO::filesize_t>(unUsedSize)));
0508     delete_unused->setEnabled(unUsed > 0);
0509 }
0510 
0511 const QString ProjectSettings::selectedPreview() const
0512 {
0513     return QString("%1;%2").arg(m_tlPreviewProfiles->currentParams(), m_tlPreviewProfiles->currentExtension());
0514 }
0515 
0516 void ProjectSettings::accept()
0517 {
0518     if (selectedProfile().isEmpty()) {
0519         KMessageBox::error(this, i18n("Please select a video profile"));
0520         return;
0521     }
0522     QString params = selectedPreview();
0523     if (!params.isEmpty()) {
0524         if (params.section(QLatin1Char(';'), 0, 0) != m_previewparams || params.section(QLatin1Char(';'), 1, 1) != m_previewextension) {
0525             // Timeline preview settings changed, warn if there are existing previews
0526             if (pCore->hasTimelinePreview() &&
0527                 KMessageBox::warningContinueCancel(this,
0528                                                    i18n("You changed the timeline preview profile. This will remove all existing timeline previews for "
0529                                                         "this project.\n Are you sure you want to proceed?"),
0530                                                    i18n("Confirm profile change")) == KMessageBox::Cancel) {
0531                 return;
0532             }
0533         }
0534     }
0535     if (!m_newProject && selectedProfile() != pCore->getCurrentProfile()->path()) {
0536         if (KMessageBox::warningContinueCancel(
0537                 this,
0538                 i18n("Changing the profile of your project cannot be undone.\nIt is recommended to save your project before attempting this operation "
0539                      "that might cause some corruption in transitions.\nAre you sure you want to proceed?"),
0540                 i18n("Confirm profile change")) == KMessageBox::Cancel) {
0541             return;
0542         }
0543     }
0544     QDialog::accept();
0545 }
0546 
0547 void ProjectSettings::slotUpdateButton(const QString &path)
0548 {
0549     if (path.isEmpty()) {
0550         m_buttonOk->setEnabled(false);
0551     } else {
0552         m_buttonOk->setEnabled(true);
0553         slotUpdateFiles(true);
0554     }
0555 }
0556 
0557 const QStringList ProjectSettings::guidesCategories() const
0558 {
0559     return m_guidesCategories->updatedGuides();
0560 }
0561 
0562 const QMap<int, int> ProjectSettings::remapGuidesCategories() const
0563 {
0564     return m_guidesCategories->remapedGuides();
0565 }
0566 
0567 QString ProjectSettings::selectedProfile() const
0568 {
0569     return m_pw->selectedProfile();
0570 }
0571 
0572 std::pair<int, int> ProjectSettings::tracks() const
0573 {
0574     return {video_tracks->value(), audio_tracks->value()};
0575 }
0576 
0577 int ProjectSettings::audioChannels() const
0578 {
0579     switch (audio_channels->currentIndex()) {
0580     case 1:
0581         return 4;
0582     case 2:
0583         return 6;
0584     default:
0585         return 2;
0586     }
0587 }
0588 
0589 bool ProjectSettings::enableVideoThumbs() const
0590 {
0591     return video_thumbs->isChecked();
0592 }
0593 
0594 bool ProjectSettings::enableAudioThumbs() const
0595 {
0596     return audio_thumbs->isChecked();
0597 }
0598 
0599 bool ProjectSettings::useProxy() const
0600 {
0601     return proxy_box->isChecked();
0602 }
0603 
0604 bool ProjectSettings::useExternalProxy() const
0605 {
0606     return external_proxy->isChecked();
0607 }
0608 
0609 bool ProjectSettings::generateProxy() const
0610 {
0611     return generate_proxy->isChecked();
0612 }
0613 
0614 bool ProjectSettings::generateImageProxy() const
0615 {
0616     return generate_imageproxy->isChecked();
0617 }
0618 
0619 bool ProjectSettings::docFolderAsStorageFolder() const
0620 {
0621     return same_folder->isChecked();
0622 }
0623 
0624 int ProjectSettings::proxyMinSize() const
0625 {
0626     return proxy_minsize->value();
0627 }
0628 
0629 int ProjectSettings::proxyImageMinSize() const
0630 {
0631     return proxy_imageminsize->value();
0632 }
0633 
0634 int ProjectSettings::proxyImageSize() const
0635 {
0636     return proxy_imagesize->value();
0637 }
0638 
0639 int ProjectSettings::proxyResize() const
0640 {
0641     return proxy_resize->value();
0642 }
0643 
0644 QString ProjectSettings::externalProxyParams() const
0645 {
0646     return external_proxy_profile->currentData().toString();
0647 }
0648 
0649 QString ProjectSettings::proxyParams() const
0650 {
0651     QString params = proxy_profile->currentData().toString();
0652     return params.section(QLatin1Char(';'), 0, 0);
0653 }
0654 
0655 QString ProjectSettings::proxyExtension() const
0656 {
0657     QString params = proxy_profile->currentData().toString();
0658     return params.section(QLatin1Char(';'), 1, 1);
0659 }
0660 
0661 QString ProjectSettings::previewParams() const
0662 {
0663     return m_tlPreviewProfiles->currentParams();
0664 }
0665 
0666 QString ProjectSettings::previewExtension() const
0667 {
0668     return m_tlPreviewProfiles->currentExtension();
0669 }
0670 
0671 // static
0672 QStringList ProjectSettings::extractPlaylistUrls(const QString &path)
0673 {
0674     QStringList urls;
0675     QDomDocument doc;
0676 
0677     if (!Xml::docContentFromFile(doc, path, false)) {
0678         return urls;
0679     }
0680     QString root = doc.documentElement().attribute(QStringLiteral("root"));
0681     if (!root.isEmpty() && !root.endsWith(QLatin1Char('/'))) {
0682         root.append(QLatin1Char('/'));
0683     }
0684     QDomNodeList chains = doc.elementsByTagName(QStringLiteral("chain"));
0685     for (int i = 0; i < chains.count(); ++i) {
0686         chains.item(i).toElement().setTagName(QStringLiteral("producer"));
0687     }
0688     QDomNodeList files = doc.elementsByTagName(QStringLiteral("producer"));
0689     for (int i = 0; i < files.count(); ++i) {
0690         QDomElement e = files.at(i).toElement();
0691         QString type = Xml::getXmlProperty(e, QStringLiteral("mlt_service"));
0692         if (type != QLatin1String("colour") && type != QLatin1String("color")) {
0693             QString url = Xml::getXmlProperty(e, QStringLiteral("resource"));
0694             if (type == QLatin1String("timewarp")) {
0695                 url = Xml::getXmlProperty(e, QStringLiteral("warp_resource"));
0696             } else if (type == QLatin1String("framebuffer")) {
0697                 url = url.section(QLatin1Char('?'), 0, 0);
0698             }
0699             if (!url.isEmpty() && url != QLatin1String("<producer>")) {
0700                 if (QFileInfo(url).isRelative()) {
0701                     url.prepend(root);
0702                 }
0703                 if (url.section(QLatin1Char('.'), 0, -2).endsWith(QLatin1String("/.all"))) {
0704                     // slideshow clip, extract image urls
0705                     urls << extractSlideshowUrls(url);
0706                 } else {
0707                     urls << url;
0708                 }
0709                 if (url.endsWith(QLatin1String(".mlt")) || url.endsWith(QLatin1String(".kdenlive"))) {
0710                     // TODO: Do something to avoid infinite loops if 2 files reference themselves...
0711                     urls << extractPlaylistUrls(url);
0712                 }
0713             }
0714         }
0715     }
0716 
0717     // TODO merge with similar logic in DocumentChecker
0718 
0719     // luma files for transitions
0720     files = doc.elementsByTagName(QStringLiteral("transition"));
0721     for (int i = 0; i < files.count(); ++i) {
0722         QDomElement e = files.at(i).toElement();
0723         QString url = Xml::getXmlProperty(e, QStringLiteral("resource"));
0724         if (url.isEmpty()) {
0725             url = Xml::getXmlProperty(e, QStringLiteral("luma"));
0726         }
0727         if (!url.isEmpty()) {
0728             if (QFileInfo(url).isRelative()) {
0729                 url.prepend(root);
0730             }
0731             urls << url;
0732         }
0733     }
0734 
0735     // urls for vidstab stabilization data and LUTs
0736     files = doc.elementsByTagName(QStringLiteral("filter"));
0737     for (int i = 0; i < files.count(); ++i) {
0738         QDomElement e = files.at(i).toElement();
0739         QString url = Xml::getXmlProperty(e, QStringLiteral("filename"));
0740         if (url.isEmpty()) {
0741             url = Xml::getXmlProperty(e, QStringLiteral("av.file"));
0742         }
0743         if (!url.isEmpty()) {
0744             if (QFileInfo(url).isRelative()) {
0745                 url.prepend(root);
0746             }
0747             urls << url;
0748         }
0749     }
0750 
0751     return urls;
0752 }
0753 
0754 // static
0755 QStringList ProjectSettings::extractSlideshowUrls(const QString &url)
0756 {
0757     QStringList urls;
0758     QString path = QFileInfo(url).absolutePath();
0759     QDir dir(path);
0760     if (url.contains(QStringLiteral(".all."))) {
0761         // this is a MIME slideshow, like *.jpeg
0762         QString ext = url.section(QLatin1Char('.'), -1);
0763         QStringList filters;
0764         filters << QStringLiteral("*.") + ext;
0765         dir.setNameFilters(filters);
0766         QStringList result = dir.entryList(QDir::Files);
0767         urls.append(path + filters.at(0) + QStringLiteral(" (") + i18np("1 image found", "%1 images found", result.count()) + QLatin1Char(')'));
0768     } else {
0769         // this is a pattern slideshow, like sequence%4d.jpg
0770         QString filter = QFileInfo(url).fileName();
0771         QString ext = filter.section(QLatin1Char('.'), -1);
0772         filter = filter.section(QLatin1Char('%'), 0, -2);
0773         QString regexp = QLatin1Char('^') + filter + QStringLiteral("\\d+\\.") + ext + QLatin1Char('$');
0774         static const QRegularExpression rx(QRegularExpression::anchoredPattern(regexp));
0775         int count = 0;
0776         const QStringList result = dir.entryList(QDir::Files);
0777         for (const QString &p : result) {
0778             if (rx.match(p).hasMatch()) {
0779                 count++;
0780             }
0781         }
0782         urls.append(url + QStringLiteral(" (") + i18np("1 image found", "%1 images found", count) + QLatin1Char(')'));
0783     }
0784     return urls;
0785 }
0786 
0787 void ProjectSettings::slotExportToText()
0788 {
0789     const QString savePath = QFileDialog::getSaveFileName(this, i18n("Save As"), QString(), i18n("Text File (*.txt)"));
0790     if (savePath.isEmpty()) {
0791         return;
0792     }
0793 
0794     QString text;
0795     text.append(i18n("Project folder: %1", project_folder->url().toLocalFile()) + '\n');
0796     text.append(i18n("Project profile: %1", m_pw->selectedProfile()) + '\n');
0797     text.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
0798     for (int i = 0; i < files_list->topLevelItemCount(); ++i) {
0799         if (files_list->topLevelItem(i)->childCount() > 0) {
0800             text.append('\n' + files_list->topLevelItem(i)->text(0) + ":\n\n");
0801             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); ++j) {
0802                 text.append(files_list->topLevelItem(i)->child(j)->text(0) + '\n');
0803             }
0804         }
0805     }
0806     QTemporaryFile tmpfile;
0807     if (!tmpfile.open()) {
0808         qCWarning(KDENLIVE_LOG) << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
0809         return;
0810     }
0811     QFile xmlf(tmpfile.fileName());
0812     if (!xmlf.open(QIODevice::WriteOnly)) {
0813         return;
0814     }
0815     xmlf.write(text.toUtf8());
0816     if (xmlf.error() != QFile::NoError) {
0817         xmlf.close();
0818         return;
0819     }
0820     xmlf.close();
0821     KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(tmpfile.fileName()), QUrl::fromLocalFile(savePath));
0822     copyjob->exec();
0823 }
0824 
0825 void ProjectSettings::slotUpdateProxyParams()
0826 {
0827     QString params = proxy_profile->currentData().toString();
0828     proxyparams->setPlainText(params.section(QLatin1Char(';'), 0, 0));
0829 }
0830 
0831 const QMap<QString, QString> ProjectSettings::metadata() const
0832 {
0833     QMap<QString, QString> metadata;
0834     for (int i = 0; i < metadata_list->topLevelItemCount(); ++i) {
0835         QTreeWidgetItem *item = metadata_list->topLevelItem(i);
0836         if (!item->text(1).simplified().isEmpty()) {
0837             // Insert metadata entry
0838             QString key = item->data(0, Qt::UserRole).toString();
0839             if (key.isEmpty()) {
0840                 key = QStringLiteral("meta.attr.") + item->text(0).simplified() + QStringLiteral(".markup");
0841             }
0842             QString value = item->text(1);
0843             metadata.insert(key, value);
0844         }
0845     }
0846     return metadata;
0847 }
0848 
0849 void ProjectSettings::slotAddMetadataField()
0850 {
0851     QString metaField = QInputDialog::getText(this, i18nc("@title:window", "Metadata"), i18n("Metadata"));
0852     if (metaField.isEmpty()) {
0853         return;
0854     }
0855     QTreeWidgetItem *item = new QTreeWidgetItem(metadata_list, QStringList() << metaField);
0856     item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0857 }
0858 
0859 void ProjectSettings::slotDeleteMetadataField()
0860 {
0861     QTreeWidgetItem *item = metadata_list->currentItem();
0862     if (item) {
0863         delete item;
0864     }
0865 }
0866 
0867 void ProjectSettings::slotManageEncodingProfile()
0868 {
0869     QPointer<EncodingProfilesDialog> d = new EncodingProfilesDialog(EncodingProfilesManager::ProxyClips);
0870     d->exec();
0871     delete d;
0872     loadProxyProfiles();
0873 }
0874 
0875 void ProjectSettings::loadProxyProfiles()
0876 {
0877     // load proxy profiles
0878     KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0879     KConfigGroup group(&conf, "proxy");
0880     QMap<QString, QString> values = group.entryMap();
0881     QMapIterator<QString, QString> k(values);
0882     int ix = -1;
0883     proxy_profile->clear();
0884     if (!KdenliveSettings::supportedHWCodecs().isEmpty()) {
0885         proxy_profile->addItem(QIcon::fromTheme(QStringLiteral("speedometer")), i18n("Automatic (%1)", Wizard::getHWCodecFriendlyName()));
0886     } else {
0887         proxy_profile->addItem(i18n("Automatic"));
0888     }
0889     const QStringList allHWCodecs = Wizard::codecs();
0890     while (k.hasNext()) {
0891         k.next();
0892         if (!k.key().isEmpty()) {
0893             QString params = k.value().section(QLatin1Char(';'), 0, 0);
0894             QString extension = k.value().section(QLatin1Char(';'), 1, 1);
0895             if (ix == -1 && ((params == m_proxyparameters && extension == m_proxyextension))) {
0896                 // this is the current profile
0897                 ix = proxy_profile->count();
0898             }
0899             QString itemCodec;
0900             QStringList values = params.split(QLatin1Char('-'));
0901             for (auto &v : values) {
0902                 if (v.startsWith(QLatin1String("vcodec ")) || v.startsWith(QLatin1String("codec:v ")) || v.startsWith(QLatin1String("c:v "))) {
0903                     itemCodec = v.section(QLatin1Char(' '), 1);
0904                     break;
0905                 }
0906             }
0907             if (!itemCodec.isEmpty() && allHWCodecs.contains(itemCodec)) {
0908                 if (KdenliveSettings::supportedHWCodecs().contains(itemCodec)) {
0909                     proxy_profile->addItem(QIcon::fromTheme(QStringLiteral("speedometer")), k.key(), k.value());
0910                 }
0911                 continue;
0912             }
0913             proxy_profile->addItem(k.key(), k.value());
0914         }
0915     }
0916     if (ix == -1) {
0917         // Current project proxy settings not found
0918         if (m_proxyparameters.isEmpty() && m_proxyextension.isEmpty()) {
0919             ix = 0;
0920         } else {
0921             ix = proxy_profile->count();
0922             proxy_profile->addItem(i18n("Current Settings"), QString(m_proxyparameters + QLatin1Char(';') + m_proxyextension));
0923         }
0924     }
0925     proxy_profile->setCurrentIndex(ix);
0926     slotUpdateProxyParams();
0927 }
0928 
0929 void ProjectSettings::loadExternalProxyProfiles()
0930 {
0931     // load proxy profiles
0932     KConfig conf(QStringLiteral("externalproxies.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0933     KConfigGroup group(&conf, "proxy");
0934     QMap<QString, QString> values = group.entryMap();
0935     QMapIterator<QString, QString> k(values);
0936     int ix = -1;
0937     external_proxy_profile->clear();
0938     while (k.hasNext()) {
0939         k.next();
0940         if (!k.key().isEmpty()) {
0941             if (ix == -1 && k.value() == m_initialExternalProxyProfile) {
0942                 // this is the current profile
0943                 ix = external_proxy_profile->count();
0944             }
0945             if (k.value().contains(QLatin1Char(';'))) {
0946                 external_proxy_profile->addItem(k.key(), k.value());
0947             }
0948         }
0949     }
0950     if (ix == -1 && !m_initialExternalProxyProfile.isEmpty()) {
0951         // Current project proxy settings not found
0952         ix = external_proxy_profile->count();
0953         external_proxy_profile->addItem(i18n("Current Settings"), m_initialExternalProxyProfile);
0954     }
0955     external_proxy_profile->setCurrentIndex(ix);
0956 }
0957 
0958 const QString ProjectSettings::storageFolder() const
0959 {
0960     if (custom_folder->isChecked()) {
0961         return project_folder->url().toLocalFile();
0962     }
0963     return QString();
0964 }