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, 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(enable_proxy, SIGNAL(toggled(bool)), proxy_params, SLOT(setVisible(bool)));
70 enable_proxy->setChecked(projectlist->useProxy());
71 proxy_params->setText(projectlist->proxyParams());
72 proxy_params->setVisible(projectlist->useProxy());
75 enable_proxy->setChecked(KdenliveSettings::enableproxy());
76 proxy_params->setText(KdenliveSettings::proxyparams());
77 proxy_params->setVisible(KdenliveSettings::enableproxy());
81 video_tracks->setEnabled(false);
82 audio_tracks->setEnabled(false);
85 if (m_projectList != NULL) {
87 connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
88 connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
89 } else tabWidget->widget(1)->setEnabled(false);
90 connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
91 connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
92 connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
95 void ProjectSettings::slotDeleteUnused()
98 QList <DocClipBase*> list = m_projectList->documentClipList();
99 for (int i = 0; i < list.count(); i++) {
100 DocClipBase *clip = list.at(i);
101 if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
102 KUrl url = clip->fileURL();
103 if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
107 // make sure our urls are not used in another clip
108 for (int i = 0; i < list.count(); i++) {
109 DocClipBase *clip = list.at(i);
110 if (clip->numReferences() > 0) {
111 KUrl url = clip->fileURL();
112 if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
116 if (toDelete.count() == 0) {
117 // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
118 if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
119 m_projectList->cleanup();
123 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;
124 m_projectList->trashUnusedClips();
128 void ProjectSettings::slotClearCache()
130 buttonBox->setEnabled(false);
131 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
132 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
133 buttonBox->setEnabled(true);
134 slotUpdateFiles(true);
137 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
139 KIO::DirectorySizeJob * job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
141 thumbs_count->setText(QString::number(job->totalFiles()));
142 thumbs_size->setText(KIO::convertSize(job->totalSize()));
144 if (cacheOnly) return;
147 KIO::filesize_t usedSize = 0;
148 KIO::filesize_t unUsedSize = 0;
149 QList <DocClipBase*> list = m_projectList->documentClipList();
152 // List all files that are used in the project. That also means:
153 // images included in slideshow and titles, files in playlist clips
154 // TODO: images used in luma transitions, files used for LADSPA effects?
157 QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
158 videos->setIcon(0, KIcon("video-x-generic"));
159 videos->setExpanded(true);
160 QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
161 sounds->setIcon(0, KIcon("audio-x-generic"));
162 sounds->setExpanded(true);
163 QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
164 images->setIcon(0, KIcon("image-x-generic"));
165 images->setExpanded(true);
166 QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
167 slideshows->setIcon(0, KIcon("image-x-generic"));
168 slideshows->setExpanded(true);
169 QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
170 texts->setIcon(0, KIcon("text-plain"));
171 texts->setExpanded(true);
172 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
173 others->setIcon(0, KIcon("unknown"));
174 others->setExpanded(true);
176 QStringList allFonts;
177 foreach(const QString & file, m_lumas) {
179 new QTreeWidgetItem(images, QStringList() << file);
182 for (int i = 0; i < list.count(); i++) {
183 DocClipBase *clip = list.at(i);
184 if (clip->clipType() == SLIDESHOW) {
185 QStringList subfiles = extractSlideshowUrls(clip->fileURL());
186 foreach(const QString & file, subfiles) {
188 new QTreeWidgetItem(slideshows, QStringList() << file);
190 } else if (!clip->fileURL().isEmpty()) {
191 //allFiles.append(clip->fileURL().path());
192 switch (clip->clipType()) {
194 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
197 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
200 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
203 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
206 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
211 if (clip->clipType() == TEXT) {
212 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
213 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
214 foreach(const QString & file, imagefiles) {
216 new QTreeWidgetItem(images, QStringList() << file);
219 } else if (clip->clipType() == PLAYLIST) {
220 QStringList files = extractPlaylistUrls(clip->fileURL().path());
221 foreach(const QString & file, files) {
223 new QTreeWidgetItem(others, QStringList() << file);
227 if (clip->numReferences() == 0) {
229 unUsedSize += clip->fileSize();
232 usedSize += clip->fileSize();
235 #if QT_VERSION >= 0x040500
236 allFonts.removeDuplicates();
238 // Hide unused categories
239 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
240 if (files_list->topLevelItem(i)->childCount() == 0) {
241 files_list->topLevelItem(i)->setHidden(true);
244 files_count->setText(QString::number(count));
245 fonts_list->addItems(allFonts);
246 if (allFonts.isEmpty()) {
247 fonts_list->setHidden(true);
248 label_fonts->setHidden(true);
250 used_count->setText(QString::number(used));
251 used_size->setText(KIO::convertSize(usedSize));
252 unused_count->setText(QString::number(unused));
253 unused_size->setText(KIO::convertSize(unUsedSize));
254 delete_unused->setEnabled(unused > 0);
257 void ProjectSettings::accept()
259 if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
260 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;
264 void ProjectSettings::slotUpdateDisplay()
266 QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
267 QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
268 p_size->setText(values.value("width") + 'x' + values.value("height"));
269 p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
270 p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
271 p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
272 if (values.value("progressive").toInt() == 0) {
273 p_progressive->setText(i18n("Interlaced (%1 fields per second)",
274 QString::number((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
276 p_progressive->setText(i18n("Progressive"));
278 p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
281 void ProjectSettings::slotUpdateButton(const QString &path)
283 if (path.isEmpty()) m_buttonOk->setEnabled(false);
285 m_buttonOk->setEnabled(true);
286 slotUpdateFiles(true);
290 QString ProjectSettings::selectedProfile() const
292 return profiles_list->itemData(profiles_list->currentIndex()).toString();
295 KUrl ProjectSettings::selectedFolder() const
297 return project_folder->url();
300 QPoint ProjectSettings::tracks()
303 p.setX(video_tracks->value());
304 p.setY(audio_tracks->value());
308 bool ProjectSettings::enableVideoThumbs() const
310 return video_thumbs->isChecked();
313 bool ProjectSettings::enableAudioThumbs() const
315 return audio_thumbs->isChecked();
318 bool ProjectSettings::useProxy() const
320 return enable_proxy->isChecked();
323 QString ProjectSettings::proxyParams() const
325 return proxy_params->toPlainText();
329 QStringList ProjectSettings::extractPlaylistUrls(QString path)
334 if (!file.open(QIODevice::ReadOnly))
336 if (!doc.setContent(&file)) {
341 QString root = doc.documentElement().attribute("root");
342 if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
343 QDomNodeList files = doc.elementsByTagName("producer");
344 for (int i = 0; i < files.count(); i++) {
345 QDomElement e = files.at(i).toElement();
346 QString type = EffectsList::property(e, "mlt_service");
347 if (type != "colour") {
348 QString url = EffectsList::property(e, "resource");
349 if (!url.isEmpty()) {
350 if (!url.startsWith('/')) url.prepend(root);
351 if (url.section('.', 0, -2).endsWith("/.all")) {
352 // slideshow clip, extract image urls
353 urls << extractSlideshowUrls(KUrl(url));
355 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
356 //TODO: Do something to avoid infinite loops if 2 files reference themselves...
357 urls << extractPlaylistUrls(url);
363 // luma files for transitions
364 files = doc.elementsByTagName("transition");
365 for (int i = 0; i < files.count(); i++) {
366 QDomElement e = files.at(i).toElement();
367 QString url = EffectsList::property(e, "luma");
368 if (!url.isEmpty()) {
369 if (!url.startsWith('/')) url.prepend(root);
379 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
382 QString path = url.directory(KUrl::AppendTrailingSlash);
383 QString ext = url.path().section('.', -1);
385 if (url.path().contains(".all.")) {
386 // this is a mime slideshow, like *.jpeg
388 filters << "*." + ext;
389 dir.setNameFilters(filters);
390 QStringList result = dir.entryList(QDir::Files);
391 urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
393 // this is a pattern slideshow, like sequence%4d.jpg
394 QString filter = url.fileName();
395 QString ext = filter.section('.', -1);
396 filter = filter.section('%', 0, -2);
397 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
400 QStringList result = dir.entryList(QDir::Files);
401 foreach(const QString & path, result) {
402 if (rx.exactMatch(path)) count++;
404 urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
409 void ProjectSettings::slotExportToText()
411 QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
412 if (savePath.isEmpty()) return;
414 data.append(i18n("Project folder: %1", project_folder->url().path()) + "\n");
415 data.append(i18n("Project profile: %1", profiles_list->currentText()) + "\n");
416 data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
417 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
418 if (files_list->topLevelItem(i)->childCount() > 0) {
419 data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
420 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
421 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
425 KTemporaryFile tmpfile;
426 if (!tmpfile.open()) {
427 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
430 QFile xmlf(tmpfile.fileName());
431 xmlf.open(QIODevice::WriteOnly);
432 xmlf.write(data.toUtf8());
433 if (xmlf.error() != QFile::NoError) {
438 KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
443 #include "projectsettings.moc"