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 }