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);
70 video_tracks->setEnabled(false);
71 audio_tracks->setEnabled(false);
74 if (m_projectList != NULL) {
76 connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
77 connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
78 } else tabWidget->widget(1)->setEnabled(false);
79 connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
80 connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
81 connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
84 void ProjectSettings::slotDeleteUnused()
87 QList <DocClipBase*> list = m_projectList->documentClipList();
88 for (int i = 0; i < list.count(); i++) {
89 DocClipBase *clip = list.at(i);
90 if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
91 KUrl url = clip->fileURL();
92 if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
96 // make sure our urls are not used in another clip
97 for (int i = 0; i < list.count(); i++) {
98 DocClipBase *clip = list.at(i);
99 if (clip->numReferences() > 0) {
100 KUrl url = clip->fileURL();
101 if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
105 if (toDelete.count() == 0) {
106 // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
107 if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
108 m_projectList->cleanup();
112 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;
113 m_projectList->trashUnusedClips();
117 void ProjectSettings::slotClearCache()
119 buttonBox->setEnabled(false);
120 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
121 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
122 buttonBox->setEnabled(true);
123 slotUpdateFiles(true);
126 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
128 KIO::DirectorySizeJob * job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
130 thumbs_count->setText(QString::number(job->totalFiles()));
131 thumbs_size->setText(KIO::convertSize(job->totalSize()));
133 if (cacheOnly) return;
136 KIO::filesize_t usedSize = 0;
137 KIO::filesize_t unUsedSize = 0;
138 QList <DocClipBase*> list = m_projectList->documentClipList();
141 // List all files that are used in the project. That also means:
142 // images included in slideshow and titles, files in playlist clips
143 // TODO: images used in luma transitions, files used for LADSPA effects?
146 QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
147 videos->setIcon(0, KIcon("video-x-generic"));
148 videos->setExpanded(true);
149 QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
150 sounds->setIcon(0, KIcon("audio-x-generic"));
151 sounds->setExpanded(true);
152 QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
153 images->setIcon(0, KIcon("image-x-generic"));
154 images->setExpanded(true);
155 QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
156 slideshows->setIcon(0, KIcon("image-x-generic"));
157 slideshows->setExpanded(true);
158 QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
159 texts->setIcon(0, KIcon("text-plain"));
160 texts->setExpanded(true);
161 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
162 others->setIcon(0, KIcon("unknown"));
163 others->setExpanded(true);
165 QStringList allFonts;
166 foreach(const QString & file, m_lumas) {
168 new QTreeWidgetItem(images, QStringList() << file);
171 for (int i = 0; i < list.count(); i++) {
172 DocClipBase *clip = list.at(i);
173 if (clip->clipType() == SLIDESHOW) {
174 QStringList subfiles = extractSlideshowUrls(clip->fileURL());
175 foreach(const QString & file, subfiles) {
177 new QTreeWidgetItem(slideshows, QStringList() << file);
179 } else if (!clip->fileURL().isEmpty()) {
180 //allFiles.append(clip->fileURL().path());
181 switch (clip->clipType()) {
183 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
186 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
189 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
192 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
195 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
200 if (clip->clipType() == TEXT) {
201 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
202 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
203 foreach(const QString & file, imagefiles) {
205 new QTreeWidgetItem(images, QStringList() << file);
208 } else if (clip->clipType() == PLAYLIST) {
209 QStringList files = extractPlaylistUrls(clip->fileURL().path());
210 foreach(const QString & file, files) {
212 new QTreeWidgetItem(others, QStringList() << file);
216 if (clip->numReferences() == 0) {
218 unUsedSize += clip->fileSize();
221 usedSize += clip->fileSize();
224 #if QT_VERSION >= 0x040500
225 allFonts.removeDuplicates();
227 // Hide unused categories
228 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
229 if (files_list->topLevelItem(i)->childCount() == 0) {
230 files_list->topLevelItem(i)->setHidden(true);
233 files_count->setText(QString::number(count));
234 fonts_list->addItems(allFonts);
235 if (allFonts.isEmpty()) {
236 fonts_list->setHidden(true);
237 label_fonts->setHidden(true);
239 used_count->setText(QString::number(used));
240 used_size->setText(KIO::convertSize(usedSize));
241 unused_count->setText(QString::number(unused));
242 unused_size->setText(KIO::convertSize(unUsedSize));
243 delete_unused->setEnabled(unused > 0);
246 void ProjectSettings::accept()
248 if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
249 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;
253 void ProjectSettings::slotUpdateDisplay()
255 QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
256 QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
257 p_size->setText(values.value("width") + 'x' + values.value("height"));
258 p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
259 p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
260 p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
261 if (values.value("progressive").toInt() == 0) {
262 p_progressive->setText(i18n("Interlaced (%1 fields per second)",
263 QString::number((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
265 p_progressive->setText(i18n("Progressive"));
267 p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
270 void ProjectSettings::slotUpdateButton(const QString &path)
272 if (path.isEmpty()) m_buttonOk->setEnabled(false);
274 m_buttonOk->setEnabled(true);
275 slotUpdateFiles(true);
279 QString ProjectSettings::selectedProfile() const
281 return profiles_list->itemData(profiles_list->currentIndex()).toString();
284 KUrl ProjectSettings::selectedFolder() const
286 return project_folder->url();
289 QPoint ProjectSettings::tracks()
292 p.setX(video_tracks->value());
293 p.setY(audio_tracks->value());
297 bool ProjectSettings::enableVideoThumbs() const
299 return video_thumbs->isChecked();
302 bool ProjectSettings::enableAudioThumbs() const
304 return audio_thumbs->isChecked();
309 QStringList ProjectSettings::extractPlaylistUrls(QString path)
314 if (!file.open(QIODevice::ReadOnly))
316 if (!doc.setContent(&file)) {
321 QString root = doc.documentElement().attribute("root");
322 if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
323 QDomNodeList files = doc.elementsByTagName("producer");
324 for (int i = 0; i < files.count(); i++) {
325 QDomElement e = files.at(i).toElement();
326 QString type = EffectsList::property(e, "mlt_service");
327 if (type != "colour") {
328 QString url = EffectsList::property(e, "resource");
329 if (!url.isEmpty()) {
330 if (!url.startsWith('/')) url.prepend(root);
331 if (url.section('.', 0, -2).endsWith("/.all")) {
332 // slideshow clip, extract image urls
333 urls << extractSlideshowUrls(KUrl(url));
335 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
336 //TODO: Do something to avoid infinite loops if 2 files reference themselves...
337 urls << extractPlaylistUrls(url);
343 // luma files for transitions
344 files = doc.elementsByTagName("transition");
345 for (int i = 0; i < files.count(); i++) {
346 QDomElement e = files.at(i).toElement();
347 QString url = EffectsList::property(e, "luma");
348 if (!url.isEmpty()) {
349 if (!url.startsWith('/')) url.prepend(root);
359 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
362 QString path = url.directory(KUrl::AppendTrailingSlash);
363 QString ext = url.path().section('.', -1);
365 if (url.path().contains(".all.")) {
366 // this is a mime slideshow, like *.jpeg
368 filters << "*." + ext;
369 dir.setNameFilters(filters);
370 QStringList result = dir.entryList(QDir::Files);
371 urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
373 // this is a pattern slideshow, like sequence%4d.jpg
374 QString filter = url.fileName();
375 QString ext = filter.section('.', -1);
376 filter = filter.section('%', 0, -2);
377 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
380 QStringList result = dir.entryList(QDir::Files);
381 foreach(const QString & path, result) {
382 if (rx.exactMatch(path)) count++;
384 urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
389 void ProjectSettings::slotExportToText()
391 QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
392 if (savePath.isEmpty()) return;
394 data.append(i18n("Project folder: %1", project_folder->url().path()) + "\n");
395 data.append(i18n("Project profile: %1", profiles_list->currentText()) + "\n");
396 data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
397 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
398 if (files_list->topLevelItem(i)->childCount() > 0) {
399 data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
400 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
401 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
405 KTemporaryFile tmpfile;
406 if (!tmpfile.open()) {
407 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
410 QFile xmlf(tmpfile.fileName());
411 xmlf.open(QIODevice::WriteOnly);
412 xmlf.write(data.toUtf8());
413 if (xmlf.error() != QFile::NoError) {
418 KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
423 #include "projectsettings.moc"