1 /***************************************************************************
2 * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
20 #include "projectsettings.h"
21 #include "kdenlivesettings.h"
22 #include "profilesdialog.h"
23 #include "docclipbase.h"
24 #include "titlewidget.h"
25 #include "effectslist.h"
27 #include <KStandardDirs>
28 #include <KMessageBox>
30 #include <kio/directorysizejob.h>
31 #include <KIO/NetAccess>
32 #include <KTemporaryFile>
33 #include <KFileDialog>
36 #include <kmessagebox.h>
38 ProjectSettings::ProjectSettings(ProjectList *projectlist, QMap <QString, QString> metadata, QStringList lumas, int videotracks, int audiotracks, const QString projectPath, bool readOnlyTracks, bool savedProject, QWidget * parent) :
39 QDialog(parent), m_savedProject(savedProject), m_projectList(projectlist), m_lumas(lumas)
43 list_search->setTreeWidget(files_list);
45 QMap <QString, QString> profilesInfo = ProfilesDialog::getProfilesInfo();
46 QMapIterator<QString, QString> i(profilesInfo);
49 profiles_list->addItem(i.key(), i.value());
51 project_folder->setMode(KFile::Directory);
52 project_folder->setUrl(KUrl(projectPath));
53 QString currentProf = KdenliveSettings::current_profile();
55 for (int i = 0; i < profiles_list->count(); i++) {
56 if (profiles_list->itemData(i).toString() == currentProf) {
57 profiles_list->setCurrentIndex(i);
62 m_buttonOk = buttonBox->button(QDialogButtonBox::Ok);
63 //buttonOk->setEnabled(false);
64 audio_thumbs->setChecked(KdenliveSettings::audiothumbnails());
65 video_thumbs->setChecked(KdenliveSettings::videothumbnails());
66 audio_tracks->setValue(audiotracks);
67 video_tracks->setValue(videotracks);
68 connect(generate_proxy, SIGNAL(toggled(bool)), proxy_minsize, SLOT(setEnabled(bool)));
69 connect(generate_imageproxy, SIGNAL(toggled(bool)), proxy_imageminsize, SLOT(setEnabled(bool)));
70 QString proxyparameters;
71 QString proxyextension;
73 enable_proxy->setChecked(projectlist->getDocumentProperty("enableproxy").toInt());
74 generate_proxy->setChecked(projectlist->getDocumentProperty("generateproxy").toInt());
75 proxy_minsize->setValue(projectlist->getDocumentProperty("proxyminsize").toInt());
76 proxyparameters = projectlist->getDocumentProperty("proxyparams");
77 generate_imageproxy->setChecked(projectlist->getDocumentProperty("generateimageproxy").toInt());
78 proxy_imageminsize->setValue(projectlist->getDocumentProperty("proxyimageminsize").toInt());
79 proxyextension = projectlist->getDocumentProperty("proxyextension");
82 enable_proxy->setChecked(KdenliveSettings::enableproxy());
83 generate_proxy->setChecked(KdenliveSettings::generateproxy());
84 proxy_minsize->setValue(KdenliveSettings::proxyminsize());
85 proxyparameters = KdenliveSettings::proxyparams();
86 generate_imageproxy->setChecked(KdenliveSettings::generateimageproxy());
87 proxy_imageminsize->setValue(KdenliveSettings::proxyimageminsize());
88 proxyextension = KdenliveSettings::proxyextension();
92 proxy_minsize->setEnabled(generate_proxy->isChecked());
93 proxy_imageminsize->setEnabled(generate_imageproxy->isChecked());
96 // load proxy profiles
97 KConfig conf("encodingprofiles.rc", KConfig::FullConfig, "appdata");
98 KConfigGroup group(&conf, "proxy");
99 QMap <QString, QString> values = group.entryMap();
100 QMapIterator<QString, QString> k(values);
102 while (k.hasNext()) {
104 if (!k.key().isEmpty()) {
105 QString params = k.value().section(';', 0, 0);
106 QString extension = k.value().section(';', 1, 1);
107 if (ix == -1 && ((params == proxyparameters && extension == proxyextension) || (proxyparameters.isEmpty() || proxyextension.isEmpty()))) {
108 // this is the current profile
109 ix = proxy_profile->count();
111 proxy_profile->addItem(k.key(), k.value());
115 // Current project proxy settings not found
116 ix = proxy_profile->count();
117 proxy_profile->addItem(i18n("Current Settings"), QString(proxyparameters + ';' + proxyextension));
119 proxy_profile->setCurrentIndex(ix);
120 slotUpdateProxyParams();
123 proxy_showprofileinfo->setIcon(KIcon("help-about"));
124 connect(proxy_profile, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateProxyParams()));
125 proxyparams->setVisible(false);
126 proxyparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
127 connect(proxy_showprofileinfo, SIGNAL(clicked(bool)), proxyparams, SLOT(setVisible(bool)));
129 if (readOnlyTracks) {
130 video_tracks->setEnabled(false);
131 audio_tracks->setEnabled(false);
136 QTreeWidgetItem *item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Title"));
137 item->setData(0, Qt::UserRole, QString("meta.attr.title.markup"));
138 if (metadata.contains("meta.attr.title.markup")) item->setText(1, metadata.value("meta.attr.title.markup"));
139 item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
140 item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Author"));
141 item->setData(0, Qt::UserRole, QString("meta.attr.author.markup"));
142 if (metadata.contains("meta.attr.author.markup")) item->setText(1, metadata.value("meta.attr.author.markup"));
143 item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
144 item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Copyright"));
145 item->setData(0, Qt::UserRole, QString("meta.attr.copyright.markup"));
146 if (metadata.contains("meta.attr.copyright.markup")) item->setText(1, metadata.value("meta.attr.copyright.markup"));
147 item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
148 item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Year"));
149 item->setData(0, Qt::UserRole, QString("meta.attr.year.markup"));
150 if (metadata.contains("meta.attr.year.markup")) item->setText(1, metadata.value("meta.attr.year.markup"));
151 item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
154 if (m_projectList != NULL) {
156 connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
157 connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
158 connect(delete_proxies, SIGNAL(clicked()), this, SLOT(slotDeleteProxies()));
159 } else tabWidget->widget(1)->setEnabled(false);
160 connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
161 connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
162 connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
165 void ProjectSettings::slotDeleteUnused()
167 QStringList toDelete;
168 QList <DocClipBase*> list = m_projectList->documentClipList();
169 for (int i = 0; i < list.count(); i++) {
170 DocClipBase *clip = list.at(i);
171 if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
172 KUrl url = clip->fileURL();
173 if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
177 // make sure our urls are not used in another clip
178 for (int i = 0; i < list.count(); i++) {
179 DocClipBase *clip = list.at(i);
180 if (clip->numReferences() > 0) {
181 KUrl url = clip->fileURL();
182 if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
186 if (toDelete.count() == 0) {
187 // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
188 if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
189 m_projectList->cleanup();
193 if (KMessageBox::warningYesNoList(this, i18n("This will remove the following files from your hard drive.\nThis action cannot be undone, only use if you know what you are doing.\nAre you sure you want to continue?"), toDelete, i18n("Delete unused clips")) != KMessageBox::Yes) return;
194 m_projectList->trashUnusedClips();
198 void ProjectSettings::slotClearCache()
200 buttonBox->setEnabled(false);
201 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
202 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
203 buttonBox->setEnabled(true);
204 slotUpdateFiles(true);
207 void ProjectSettings::slotDeleteProxies()
209 if (KMessageBox::warningContinueCancel(this, i18n("Deleting proxy clips will disable proxies for this project.")) != KMessageBox::Continue) return;
210 buttonBox->setEnabled(false);
211 enable_proxy->setChecked(false);
212 emit disableProxies();
213 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
214 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
215 buttonBox->setEnabled(true);
216 slotUpdateFiles(true);
219 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
221 KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
223 thumbs_count->setText(QString::number(job->totalFiles()));
224 thumbs_size->setText(KIO::convertSize(job->totalSize()));
225 job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
227 proxy_count->setText(QString::number(job->totalFiles()));
228 proxy_size->setText(KIO::convertSize(job->totalSize()));
230 if (cacheOnly) return;
233 KIO::filesize_t usedSize = 0;
234 KIO::filesize_t unUsedSize = 0;
235 QList <DocClipBase*> list = m_projectList->documentClipList();
238 // List all files that are used in the project. That also means:
239 // images included in slideshow and titles, files in playlist clips
240 // TODO: images used in luma transitions?
243 QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
244 videos->setIcon(0, KIcon("video-x-generic"));
245 videos->setExpanded(true);
246 QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
247 sounds->setIcon(0, KIcon("audio-x-generic"));
248 sounds->setExpanded(true);
249 QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
250 images->setIcon(0, KIcon("image-x-generic"));
251 images->setExpanded(true);
252 QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
253 slideshows->setIcon(0, KIcon("image-x-generic"));
254 slideshows->setExpanded(true);
255 QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
256 texts->setIcon(0, KIcon("text-plain"));
257 texts->setExpanded(true);
258 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
259 others->setIcon(0, KIcon("unknown"));
260 others->setExpanded(true);
262 QStringList allFonts;
263 foreach(const QString & file, m_lumas) {
265 new QTreeWidgetItem(images, QStringList() << file);
268 for (int i = 0; i < list.count(); i++) {
269 DocClipBase *clip = list.at(i);
270 if (clip->clipType() == SLIDESHOW) {
271 QStringList subfiles = extractSlideshowUrls(clip->fileURL());
272 foreach(const QString & file, subfiles) {
274 new QTreeWidgetItem(slideshows, QStringList() << file);
276 } else if (!clip->fileURL().isEmpty()) {
277 //allFiles.append(clip->fileURL().path());
278 switch (clip->clipType()) {
280 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
283 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
286 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
289 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
292 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
297 if (clip->clipType() == TEXT) {
298 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
299 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
300 foreach(const QString & file, imagefiles) {
302 new QTreeWidgetItem(images, QStringList() << file);
305 } else if (clip->clipType() == PLAYLIST) {
306 QStringList files = extractPlaylistUrls(clip->fileURL().path());
307 foreach(const QString & file, files) {
309 new QTreeWidgetItem(others, QStringList() << file);
313 if (clip->numReferences() == 0) {
315 unUsedSize += clip->fileSize();
318 usedSize += clip->fileSize();
321 allFonts.removeDuplicates();
322 // Hide unused categories
323 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
324 if (files_list->topLevelItem(i)->childCount() == 0) {
325 files_list->topLevelItem(i)->setHidden(true);
328 files_count->setText(QString::number(count));
329 fonts_list->addItems(allFonts);
330 if (allFonts.isEmpty()) {
331 fonts_list->setHidden(true);
332 label_fonts->setHidden(true);
334 used_count->setText(QString::number(used));
335 used_size->setText(KIO::convertSize(usedSize));
336 unused_count->setText(QString::number(unused));
337 unused_size->setText(KIO::convertSize(unUsedSize));
338 delete_unused->setEnabled(unused > 0);
341 void ProjectSettings::accept()
343 if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
344 if (KMessageBox::warningContinueCancel(this, i18n("Changing the profile of your project cannot be undone.\nIt is recommended to save your project before attempting this operation that might cause some corruption in transitions.\n Are you sure you want to proceed?"), i18n("Confirm profile change")) == KMessageBox::Cancel) return;
348 void ProjectSettings::slotUpdateDisplay()
351 QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
352 QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
353 p_size->setText(values.value("width") + 'x' + values.value("height"));
354 p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
355 p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
356 p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
357 if (values.value("progressive").toInt() == 0) {
358 p_progressive->setText(i18n("Interlaced (%1 fields per second)",
359 locale.toString((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
361 p_progressive->setText(i18n("Progressive"));
363 p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
366 void ProjectSettings::slotUpdateButton(const QString &path)
368 if (path.isEmpty()) m_buttonOk->setEnabled(false);
370 m_buttonOk->setEnabled(true);
371 slotUpdateFiles(true);
375 QString ProjectSettings::selectedProfile() const
377 return profiles_list->itemData(profiles_list->currentIndex()).toString();
380 KUrl ProjectSettings::selectedFolder() const
382 return project_folder->url();
385 QPoint ProjectSettings::tracks()
388 p.setX(video_tracks->value());
389 p.setY(audio_tracks->value());
393 bool ProjectSettings::enableVideoThumbs() const
395 return video_thumbs->isChecked();
398 bool ProjectSettings::enableAudioThumbs() const
400 return audio_thumbs->isChecked();
403 bool ProjectSettings::useProxy() const
405 return enable_proxy->isChecked();
408 bool ProjectSettings::generateProxy() const
410 return generate_proxy->isChecked();
413 bool ProjectSettings::generateImageProxy() const
415 return generate_imageproxy->isChecked();
418 int ProjectSettings::proxyMinSize() const
420 return proxy_minsize->value();
423 int ProjectSettings::proxyImageMinSize() const
425 return proxy_imageminsize->value();
428 QString ProjectSettings::proxyParams() const
430 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
431 return params.section(';', 0, 0);
434 QString ProjectSettings::proxyExtension() const
436 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
437 return params.section(';', 1, 1);
441 QStringList ProjectSettings::extractPlaylistUrls(QString path)
446 if (!file.open(QIODevice::ReadOnly))
448 if (!doc.setContent(&file)) {
453 QString root = doc.documentElement().attribute("root");
454 if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
455 QDomNodeList files = doc.elementsByTagName("producer");
456 for (int i = 0; i < files.count(); i++) {
457 QDomElement e = files.at(i).toElement();
458 QString type = EffectsList::property(e, "mlt_service");
459 if (type != "colour") {
460 QString url = EffectsList::property(e, "resource");
461 if (!url.isEmpty()) {
462 if (!url.startsWith('/')) url.prepend(root);
463 if (url.section('.', 0, -2).endsWith("/.all")) {
464 // slideshow clip, extract image urls
465 urls << extractSlideshowUrls(KUrl(url));
467 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
468 //TODO: Do something to avoid infinite loops if 2 files reference themselves...
469 urls << extractPlaylistUrls(url);
475 // luma files for transitions
476 files = doc.elementsByTagName("transition");
477 for (int i = 0; i < files.count(); i++) {
478 QDomElement e = files.at(i).toElement();
479 QString url = EffectsList::property(e, "luma");
480 if (!url.isEmpty()) {
481 if (!url.startsWith('/')) url.prepend(root);
491 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
494 QString path = url.directory(KUrl::AppendTrailingSlash);
495 QString ext = url.path().section('.', -1);
497 if (url.path().contains(".all.")) {
498 // this is a mime slideshow, like *.jpeg
500 filters << "*." + ext;
501 dir.setNameFilters(filters);
502 QStringList result = dir.entryList(QDir::Files);
503 urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
505 // this is a pattern slideshow, like sequence%4d.jpg
506 QString filter = url.fileName();
507 QString ext = filter.section('.', -1);
508 filter = filter.section('%', 0, -2);
509 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
512 QStringList result = dir.entryList(QDir::Files);
513 foreach(const QString & path, result) {
514 if (rx.exactMatch(path)) count++;
516 urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
521 void ProjectSettings::slotExportToText()
523 QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
524 if (savePath.isEmpty()) return;
526 data.append(i18n("Project folder: %1", project_folder->url().path()) + "\n");
527 data.append(i18n("Project profile: %1", profiles_list->currentText()) + "\n");
528 data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
529 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
530 if (files_list->topLevelItem(i)->childCount() > 0) {
531 data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
532 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
533 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
537 KTemporaryFile tmpfile;
538 if (!tmpfile.open()) {
539 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
542 QFile xmlf(tmpfile.fileName());
543 xmlf.open(QIODevice::WriteOnly);
544 xmlf.write(data.toUtf8());
545 if (xmlf.error() != QFile::NoError) {
550 KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
553 void ProjectSettings::slotUpdateProxyParams()
555 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
556 proxyparams->setPlainText(params.section(';', 0, 0));
559 const QMap <QString, QString> ProjectSettings::metadata() const
561 QMap <QString, QString> metadata;
562 for (int i = 0; i < metadata_list->topLevelItemCount(); i++)
564 QTreeWidgetItem *item = metadata_list->topLevelItem(i);
565 if (!item->text(1).simplified().isEmpty()) {
566 // Insert metadata entry
567 QString key = item->data(0, Qt::UserRole).toString();
568 QString value = item->text(1);
569 metadata.insert(key, value);
575 #include "projectsettings.moc"