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::CascadeConfig, "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 *playlists = new QTreeWidgetItem(files_list, QStringList() << i18n("Playlist clips"));
259 playlists->setIcon(0, KIcon("video-mlt-playlist"));
260 playlists->setExpanded(true);
261 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
262 others->setIcon(0, KIcon("unknown"));
263 others->setExpanded(true);
265 QStringList allFonts;
266 foreach(const QString & file, m_lumas) {
268 new QTreeWidgetItem(images, QStringList() << file);
271 for (int i = 0; i < list.count(); i++) {
272 DocClipBase *clip = list.at(i);
273 if (clip->clipType() == SLIDESHOW) {
274 QStringList subfiles = extractSlideshowUrls(clip->fileURL());
275 foreach(const QString & file, subfiles) {
277 new QTreeWidgetItem(slideshows, QStringList() << file);
279 } else if (!clip->fileURL().isEmpty()) {
280 //allFiles.append(clip->fileURL().path());
281 switch (clip->clipType()) {
283 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
286 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
289 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
292 new QTreeWidgetItem(playlists, QStringList() << clip->fileURL().path());
295 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
298 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
303 if (clip->clipType() == TEXT) {
304 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
305 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
306 foreach(const QString & file, imagefiles) {
308 new QTreeWidgetItem(images, QStringList() << file);
311 } else if (clip->clipType() == PLAYLIST) {
312 QStringList files = extractPlaylistUrls(clip->fileURL().path());
313 foreach(const QString & file, files) {
315 new QTreeWidgetItem(others, QStringList() << file);
319 if (clip->numReferences() == 0) {
321 unUsedSize += clip->fileSize();
324 usedSize += clip->fileSize();
327 allFonts.removeDuplicates();
328 // Hide unused categories
329 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
330 if (files_list->topLevelItem(i)->childCount() == 0) {
331 files_list->topLevelItem(i)->setHidden(true);
334 files_count->setText(QString::number(count));
335 fonts_list->addItems(allFonts);
336 if (allFonts.isEmpty()) {
337 fonts_list->setHidden(true);
338 label_fonts->setHidden(true);
340 used_count->setText(QString::number(used));
341 used_size->setText(KIO::convertSize(usedSize));
342 unused_count->setText(QString::number(unused));
343 unused_size->setText(KIO::convertSize(unUsedSize));
344 delete_unused->setEnabled(unused > 0);
347 void ProjectSettings::accept()
349 if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
350 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;
354 void ProjectSettings::slotUpdateDisplay()
357 QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
358 QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
359 p_size->setText(values.value("width") + 'x' + values.value("height"));
360 p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
361 p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
362 p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
363 if (values.value("progressive").toInt() == 0) {
364 p_progressive->setText(i18n("Interlaced (%1 fields per second)",
365 locale.toString((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
367 p_progressive->setText(i18n("Progressive"));
369 p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
372 void ProjectSettings::slotUpdateButton(const QString &path)
374 if (path.isEmpty()) m_buttonOk->setEnabled(false);
376 m_buttonOk->setEnabled(true);
377 slotUpdateFiles(true);
381 QString ProjectSettings::selectedProfile() const
383 return profiles_list->itemData(profiles_list->currentIndex()).toString();
386 KUrl ProjectSettings::selectedFolder() const
388 return project_folder->url();
391 QPoint ProjectSettings::tracks()
394 p.setX(video_tracks->value());
395 p.setY(audio_tracks->value());
399 bool ProjectSettings::enableVideoThumbs() const
401 return video_thumbs->isChecked();
404 bool ProjectSettings::enableAudioThumbs() const
406 return audio_thumbs->isChecked();
409 bool ProjectSettings::useProxy() const
411 return enable_proxy->isChecked();
414 bool ProjectSettings::generateProxy() const
416 return generate_proxy->isChecked();
419 bool ProjectSettings::generateImageProxy() const
421 return generate_imageproxy->isChecked();
424 int ProjectSettings::proxyMinSize() const
426 return proxy_minsize->value();
429 int ProjectSettings::proxyImageMinSize() const
431 return proxy_imageminsize->value();
434 QString ProjectSettings::proxyParams() const
436 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
437 return params.section(';', 0, 0);
440 QString ProjectSettings::proxyExtension() const
442 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
443 return params.section(';', 1, 1);
447 QStringList ProjectSettings::extractPlaylistUrls(QString path)
452 if (!file.open(QIODevice::ReadOnly))
454 if (!doc.setContent(&file)) {
459 QString root = doc.documentElement().attribute("root");
460 if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
461 QDomNodeList files = doc.elementsByTagName("producer");
462 for (int i = 0; i < files.count(); i++) {
463 QDomElement e = files.at(i).toElement();
464 QString type = EffectsList::property(e, "mlt_service");
465 if (type != "colour") {
466 QString url = EffectsList::property(e, "resource");
467 if (type == "framebuffer") {
468 url = url.section('?', 0, 0);
470 if (!url.isEmpty()) {
471 if (!url.startsWith('/')) url.prepend(root);
472 if (url.section('.', 0, -2).endsWith("/.all")) {
473 // slideshow clip, extract image urls
474 urls << extractSlideshowUrls(KUrl(url));
476 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
477 //TODO: Do something to avoid infinite loops if 2 files reference themselves...
478 urls << extractPlaylistUrls(url);
484 // luma files for transitions
485 files = doc.elementsByTagName("transition");
486 for (int i = 0; i < files.count(); i++) {
487 QDomElement e = files.at(i).toElement();
488 QString url = EffectsList::property(e, "luma");
489 if (!url.isEmpty()) {
490 if (!url.startsWith('/')) url.prepend(root);
500 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
503 QString path = url.directory(KUrl::AppendTrailingSlash);
504 QString ext = url.path().section('.', -1);
506 if (url.path().contains(".all.")) {
507 // this is a mime slideshow, like *.jpeg
509 filters << "*." + ext;
510 dir.setNameFilters(filters);
511 QStringList result = dir.entryList(QDir::Files);
512 urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ')');
514 // this is a pattern slideshow, like sequence%4d.jpg
515 QString filter = url.fileName();
516 QString ext = filter.section('.', -1);
517 filter = filter.section('%', 0, -2);
518 QString regexp = '^' + filter + "\\d+\\." + ext + '$';
521 QStringList result = dir.entryList(QDir::Files);
522 foreach(const QString & path, result) {
523 if (rx.exactMatch(path)) count++;
525 urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ')');
530 void ProjectSettings::slotExportToText()
532 QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
533 if (savePath.isEmpty()) return;
535 data.append(i18n("Project folder: %1", project_folder->url().path()) + '\n');
536 data.append(i18n("Project profile: %1", profiles_list->currentText()) + '\n');
537 data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
538 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
539 if (files_list->topLevelItem(i)->childCount() > 0) {
540 data.append('\n' + files_list->topLevelItem(i)->text(0) + ":\n\n");
541 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
542 data.append(files_list->topLevelItem(i)->child(j)->text(0) + '\n');
546 KTemporaryFile tmpfile;
547 if (!tmpfile.open()) {
548 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
551 QFile xmlf(tmpfile.fileName());
552 if (!xmlf.open(QIODevice::WriteOnly))
554 xmlf.write(data.toUtf8());
555 if (xmlf.error() != QFile::NoError) {
560 KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
563 void ProjectSettings::slotUpdateProxyParams()
565 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
566 proxyparams->setPlainText(params.section(';', 0, 0));
569 const QMap <QString, QString> ProjectSettings::metadata() const
571 QMap <QString, QString> metadata;
572 for (int i = 0; i < metadata_list->topLevelItemCount(); i++)
574 QTreeWidgetItem *item = metadata_list->topLevelItem(i);
575 if (!item->text(1).simplified().isEmpty()) {
576 // Insert metadata entry
577 QString key = item->data(0, Qt::UserRole).toString();
578 QString value = item->text(1);
579 metadata.insert(key, value);
585 #include "projectsettings.moc"