]> git.sesse.net Git - kdenlive/blob - src/renderwidget.cpp
* Add "Progressive" checkbox to allow users enabling or disabling progressive renderi...
[kdenlive] / src / renderwidget.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *                                                                         *
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.                                   *
8  *                                                                         *
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.                          *
13  *                                                                         *
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  ***************************************************************************/
19
20
21 #include <QDomDocument>
22
23 #include <KStandardDirs>
24 #include <KDebug>
25 #include <KMessageBox>
26 #include <KComboBox>
27
28 #include "kdenlivesettings.h"
29 #include "renderwidget.h"
30 #include "ui_saveprofile_ui.h"
31
32 const int GroupRole = Qt::UserRole;
33 const int ExtensionRole = GroupRole + 1;
34 const int StandardRole = GroupRole + 2;
35 const int RenderRole = GroupRole + 3;
36 const int ParamsRole = GroupRole + 4;
37 const int EditableRole = GroupRole + 5;
38
39 RenderWidget::RenderWidget(QWidget * parent): QDialog(parent) {
40     m_view.setupUi(this);
41     m_view.buttonDelete->setIcon(KIcon("trash-empty"));
42     m_view.buttonDelete->setToolTip(i18n("Delete profile"));
43     m_view.buttonDelete->setEnabled(false);
44
45     m_view.buttonEdit->setIcon(KIcon("document-properties"));
46     m_view.buttonEdit->setToolTip(i18n("Edit profile"));
47     m_view.buttonEdit->setEnabled(false);
48
49     m_view.buttonSave->setIcon(KIcon("document-new"));
50     m_view.buttonSave->setToolTip(i18n("Create new profile"));
51
52     m_view.buttonInfo->setIcon(KIcon("help-about"));
53
54     if (KdenliveSettings::showrenderparams()) {
55         m_view.buttonInfo->setDown(true);
56     } else m_view.advanced_params->hide();
57
58     connect(m_view.buttonInfo, SIGNAL(clicked()), this, SLOT(showInfoPanel()));
59
60     connect(m_view.buttonSave, SIGNAL(clicked()), this, SLOT(slotSaveProfile()));
61     connect(m_view.buttonEdit, SIGNAL(clicked()), this, SLOT(slotEditProfile()));
62     connect(m_view.buttonDelete, SIGNAL(clicked()), this, SLOT(slotDeleteProfile()));
63     connect(m_view.buttonStart, SIGNAL(clicked()), this, SLOT(slotExport()));
64     connect(m_view.out_file, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButtons()));
65     connect(m_view.format_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshView()));
66     connect(m_view.size_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshParams()));
67
68     connect(m_view.render_guide, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
69     connect(m_view.render_zone, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
70     connect(m_view.render_full, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
71
72     connect(m_view.guide_end, SIGNAL(activated(int)), this, SLOT(slotCheckStartGuidePosition()));
73     connect(m_view.guide_start, SIGNAL(activated(int)), this, SLOT(slotCheckEndGuidePosition()));
74
75     connect(m_view.format_selection, SIGNAL(activated(int)), this, SLOT(refreshView()));
76
77     m_view.buttonStart->setEnabled(false);
78     m_view.guides_box->setVisible(false);
79     parseProfiles();
80     m_view.splitter->setStretchFactor(1, 5);
81     m_view.splitter->setStretchFactor(0, 2);
82
83     focusFirstVisibleItem();
84 }
85
86 void RenderWidget::showInfoPanel() {
87     if (m_view.advanced_params->isVisible()) {
88         m_view.advanced_params->setVisible(false);
89         m_view.buttonInfo->setDown(false);
90         KdenliveSettings::setShowrenderparams(false);
91     } else {
92         m_view.advanced_params->setVisible(true);
93         m_view.buttonInfo->setDown(true);
94         KdenliveSettings::setShowrenderparams(true);
95     }
96 }
97
98 void RenderWidget::slotUpdateGuideBox() {
99     m_view.guides_box->setVisible(m_view.render_guide->isChecked());
100 }
101
102 void RenderWidget::slotCheckStartGuidePosition() {
103     if (m_view.guide_start->currentIndex() > m_view.guide_end->currentIndex())
104         m_view.guide_start->setCurrentIndex(m_view.guide_end->currentIndex());
105 }
106
107 void RenderWidget::slotCheckEndGuidePosition() {
108     if (m_view.guide_end->currentIndex() < m_view.guide_start->currentIndex())
109         m_view.guide_end->setCurrentIndex(m_view.guide_start->currentIndex());
110 }
111
112 void RenderWidget::setGuides(QDomElement guidesxml, double duration) {
113     m_view.guide_start->clear();
114     m_view.guide_end->clear();
115     QDomNodeList nodes = guidesxml.elementsByTagName("guide");
116     if (nodes.count() > 0) {
117         m_view.guide_start->addItem(i18n("Render"), "0");
118         m_view.render_guide->setEnabled(true);
119     } else m_view.render_guide->setEnabled(false);
120     for (int i = 0; i < nodes.count(); i++) {
121         QDomElement e = nodes.item(i).toElement();
122         if (!e.isNull()) {
123             m_view.guide_start->addItem(e.attribute("comment"), e.attribute("time").toDouble());
124             m_view.guide_end->addItem(e.attribute("comment"), e.attribute("time").toDouble());
125         }
126     }
127     if (nodes.count() > 0)
128         m_view.guide_end->addItem(i18n("End"), QString::number(duration));
129 }
130
131 void RenderWidget::slotUpdateButtons() {
132     if (m_view.out_file->url().isEmpty()) m_view.buttonStart->setEnabled(false);
133     else m_view.buttonStart->setEnabled(true);
134 }
135
136 void RenderWidget::slotSaveProfile() {
137     Ui::SaveProfile_UI ui;
138     QDialog *d = new QDialog(this);
139     ui.setupUi(d);
140     QString customGroup = i18n("Custom");
141     QStringList groupNames;
142     for (int i = 0; i < m_view.format_list->count(); i++)
143         groupNames.append(m_view.format_list->item(i)->text());
144     if (!groupNames.contains(customGroup)) groupNames.prepend(customGroup);
145     ui.group_name->addItems(groupNames);
146     int pos = ui.group_name->findText(customGroup);
147     ui.group_name->setCurrentIndex(pos);
148
149     ui.parameters->setText(m_view.advanced_params->toPlainText());
150     ui.extension->setText(m_view.size_list->currentItem()->data(ExtensionRole).toString());
151     ui.profile_name->setFocus();
152     if (d->exec() == QDialog::Accepted && !ui.profile_name->text().simplified().isEmpty()) {
153         QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
154         QDomDocument doc;
155         QFile file(exportFile);
156         doc.setContent(&file, false);
157         file.close();
158
159         QDomElement documentElement;
160         bool groupExists = false;
161         QString groupName;
162         QString newProfileName = ui.profile_name->text().simplified();
163         QString newGroupName = ui.group_name->currentText();
164         QDomNodeList groups = doc.elementsByTagName("group");
165         int i = 0;
166         if (groups.count() == 0) {
167             QDomElement profiles = doc.createElement("profiles");
168             doc.appendChild(profiles);
169         } else while (!groups.item(i).isNull()) {
170                 documentElement = groups.item(i).toElement();
171                 groupName = documentElement.attribute("name");
172                 kDebug() << "// SAVE, PARSING FROUP: " << i << ", name: " << groupName << ", LOOK FR: " << newGroupName;
173                 if (groupName == newGroupName) {
174                     groupExists = true;
175                     break;
176                 }
177                 i++;
178             }
179         if (!groupExists) {
180             documentElement = doc.createElement("group");
181             documentElement.setAttribute("name", ui.group_name->currentText());
182             documentElement.setAttribute("renderer", "avformat");
183             doc.documentElement().appendChild(documentElement);
184         }
185         QDomElement profileElement = doc.createElement("profile");
186         profileElement.setAttribute("name", newProfileName);
187         profileElement.setAttribute("extension", ui.extension->text().simplified());
188         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
189         documentElement.appendChild(profileElement);
190
191         //QCString save = doc.toString().utf8();
192
193         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
194             KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
195             delete d;
196             return;
197         }
198         QTextStream out(&file);
199         out << doc.toString();
200         file.close();
201         parseProfiles(newGroupName, newProfileName);
202     }
203     delete d;
204 }
205
206 void RenderWidget::slotEditProfile() {
207     QListWidgetItem *item = m_view.size_list->currentItem();
208     if (!item) return;
209     QString currentGroup = m_view.format_list->currentItem()->text();
210
211     QString params = item->data(ParamsRole).toString();
212     QString extension = item->data(ExtensionRole).toString();
213     QString currentProfile = item->text();
214
215     Ui::SaveProfile_UI ui;
216     QDialog *d = new QDialog(this);
217     ui.setupUi(d);
218     QStringList groupNames;
219     for (int i = 0; i < m_view.format_list->count(); i++)
220         groupNames.append(m_view.format_list->item(i)->text());
221     if (!groupNames.contains(currentGroup)) groupNames.prepend(currentGroup);
222     ui.group_name->addItems(groupNames);
223     int pos = ui.group_name->findText(currentGroup);
224     ui.group_name->setCurrentIndex(pos);
225     ui.profile_name->setText(currentProfile);
226     ui.extension->setText(extension);
227     ui.parameters->setText(params);
228     ui.profile_name->setFocus();
229
230     if (d->exec() == QDialog::Accepted) {
231         slotDeleteProfile();
232         QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
233         QDomDocument doc;
234         QFile file(exportFile);
235         doc.setContent(&file, false);
236         file.close();
237
238         QDomElement documentElement;
239         bool groupExists = false;
240         QString groupName;
241         QString newProfileName = ui.profile_name->text();
242         QString newGroupName = ui.group_name->currentText();
243         QDomNodeList groups = doc.elementsByTagName("group");
244         int i = 0;
245         if (groups.count() == 0) {
246             QDomElement profiles = doc.createElement("profiles");
247             doc.appendChild(profiles);
248         } else while (!groups.item(i).isNull()) {
249                 documentElement = groups.item(i).toElement();
250                 groupName = documentElement.attribute("name");
251                 kDebug() << "// SAVE, PARSING FROUP: " << i << ", name: " << groupName << ", LOOK FR: " << newGroupName;
252                 if (groupName == newGroupName) {
253                     groupExists = true;
254                     break;
255                 }
256                 i++;
257             }
258         if (!groupExists) {
259             documentElement = doc.createElement("group");
260             documentElement.setAttribute("name", ui.group_name->currentText());
261             documentElement.setAttribute("renderer", "avformat");
262             doc.documentElement().appendChild(documentElement);
263         }
264         QDomElement profileElement = doc.createElement("profile");
265         profileElement.setAttribute("name", newProfileName);
266         profileElement.setAttribute("extension", ui.extension->text().simplified());
267         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
268         documentElement.appendChild(profileElement);
269
270         //QCString save = doc.toString().utf8();
271
272         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
273             KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
274             delete d;
275             return;
276         }
277         QTextStream out(&file);
278         out << doc.toString();
279         file.close();
280         parseProfiles(newGroupName, newProfileName);
281     }
282     delete d;
283 }
284
285 void RenderWidget::slotDeleteProfile() {
286     QString currentGroup = m_view.format_list->currentItem()->text();
287     QString currentProfile = m_view.size_list->currentItem()->text();
288
289     QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
290     QDomDocument doc;
291     QFile file(exportFile);
292     doc.setContent(&file, false);
293     file.close();
294
295     QDomElement documentElement;
296     bool groupExists = false;
297     QString groupName;
298     QDomNodeList groups = doc.elementsByTagName("group");
299     int i = 0;
300
301     while (!groups.item(i).isNull()) {
302         documentElement = groups.item(i).toElement();
303         groupName = documentElement.attribute("name");
304         if (groupName == currentGroup) {
305             QDomNodeList children = documentElement.childNodes();
306             for (int j = 0; j < children.count(); j++) {
307                 QDomElement pro = children.at(j).toElement();
308                 if (pro.attribute("name") == currentProfile) {
309                     groups.item(i).removeChild(children.at(j));
310                     if (groups.item(i).childNodes().isEmpty())
311                         doc.documentElement().removeChild(groups.item(i));
312                     break;
313                 }
314             }
315             break;
316         }
317         i++;
318     }
319
320     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
321         KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
322         return;
323     }
324     QTextStream out(&file);
325     out << doc.toString();
326     file.close();
327     parseProfiles(currentGroup);
328     focusFirstVisibleItem();
329 }
330
331 void RenderWidget::focusFirstVisibleItem() {
332     if (m_view.size_list->currentItem() && !m_view.size_list->currentItem()->isHidden()) return;
333     for (uint ix = 0; ix < m_view.size_list->count(); ix++) {
334         QListWidgetItem *item = m_view.size_list->item(ix);
335         if (item && !item->isHidden()) {
336             m_view.size_list->setCurrentRow(ix);
337             break;
338         }
339     }
340     if (!m_view.size_list->currentItem()) m_view.size_list->setCurrentRow(0);
341 }
342
343 void RenderWidget::slotExport() {
344     QListWidgetItem *item = m_view.size_list->currentItem();
345     if (!item) return;
346     QFile f(m_view.out_file->url().path());
347     if (f.exists()) {
348         if (KMessageBox::warningYesNo(this, i18n("File already exists. Do you want to overwrite it ?")) != KMessageBox::Yes)
349             return;
350     }
351     QStringList overlayargs;
352     if (m_view.tc_overlay->isChecked()) {
353         QString filterFile = KStandardDirs::locate("appdata", "metadata.properties");
354         overlayargs << "meta.attr.timecode=1" << "meta.attr.timecode.markup=#timecode";
355         overlayargs << "-attach" << "data_feed:attr_check" << "-attach";
356         overlayargs << "data_show:" + filterFile << "_fezzik=1" << "dynamic=1";
357     }
358     double startPos = -1;
359     double endPos = -1;
360     if (m_view.render_guide->isChecked()) {
361         startPos = m_view.guide_start->itemData(m_view.guide_start->currentIndex()).toDouble();
362         endPos = m_view.guide_end->itemData(m_view.guide_end->currentIndex()).toDouble();
363     }
364     QString renderArgs = m_view.advanced_params->toPlainText();
365     renderArgs.replace("%width", QString::number(m_profile.width));
366     renderArgs.replace("%height", QString::number(m_profile.height));
367     renderArgs.replace("%dar", "@" + QString::number(m_profile.display_aspect_num) + "/" + QString::number(m_profile.display_aspect_den));
368     if (m_view.force_progressive->isChecked()) renderArgs.append(" progressive=1");
369     else renderArgs.append(" progressive=0");
370     emit doRender(m_view.out_file->url().path(), item->data(RenderRole).toString(), overlayargs, renderArgs.simplified().split(' '), m_view.render_zone->isChecked(), m_view.play_after->isChecked(), startPos, endPos);
371 }
372
373 void RenderWidget::setProfile(MltVideoProfile profile) {
374     m_profile = profile;
375     //WARNING: this way to tell the video standard is a bit hackish...
376     if (m_profile.description.contains("pal", Qt::CaseInsensitive) || m_profile.description.contains("25", Qt::CaseInsensitive) || m_profile.description.contains("50", Qt::CaseInsensitive)) m_view.format_selection->setCurrentIndex(0);
377     else m_view.format_selection->setCurrentIndex(1);
378     m_view.force_progressive->setChecked(m_profile.progressive);
379     refreshView();
380 }
381
382 void RenderWidget::refreshView() {
383     QListWidgetItem *item = m_view.format_list->currentItem();
384     if (!item) {
385         m_view.format_list->setCurrentRow(0);
386         item = m_view.format_list->currentItem();
387     }
388     if (!item) return;
389     QString std;
390     QString group = item->text();
391     QListWidgetItem *sizeItem;
392     bool firstSelected = false;
393     for (int i = 0; i < m_view.size_list->count(); i++) {
394         sizeItem = m_view.size_list->item(i);
395         std = sizeItem->data(StandardRole).toString();
396         if (sizeItem->data(GroupRole) == group) {
397             if (!std.isEmpty()) {
398                 if (std.contains("PAL", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 0);
399                 else if (std.contains("NTSC", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 1);
400             } else {
401                 sizeItem->setHidden(false);
402                 if (!firstSelected) m_view.size_list->setCurrentItem(sizeItem);
403                 firstSelected = true;
404             }
405         } else sizeItem->setHidden(true);
406     }
407     focusFirstVisibleItem();
408
409 }
410
411 void RenderWidget::refreshParams() {
412     QListWidgetItem *item = m_view.size_list->currentItem();
413     if (!item) return;
414     QString params = item->data(ParamsRole).toString();
415     QString extension = item->data(ExtensionRole).toString();
416     m_view.advanced_params->setPlainText(params);
417     m_view.advanced_params->setToolTip(params);
418     KUrl url = m_view.out_file->url();
419     if (!url.isEmpty()) {
420         QString path = url.path();
421         int pos = path.lastIndexOf('.') + 1;
422         if (pos == 0) path.append('.') + extension;
423         else path = path.left(pos) + extension;
424         m_view.out_file->setUrl(KUrl(path));
425     } else {
426         m_view.out_file->setUrl(KUrl(QDir::homePath() + "/untitled." + extension));
427     }
428
429     if (item->data(EditableRole).toString().isEmpty()) {
430         m_view.buttonDelete->setEnabled(false);
431         m_view.buttonEdit->setEnabled(false);
432     } else {
433         m_view.buttonDelete->setEnabled(true);
434         m_view.buttonEdit->setEnabled(true);
435     }
436 }
437
438 void RenderWidget::parseProfiles(QString group, QString profile) {
439     m_view.size_list->clear();
440     m_view.format_list->clear();
441     QString exportFile = KStandardDirs::locate("appdata", "export/profiles.xml");
442     parseFile(exportFile, false);
443     exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
444     if (QFile::exists(exportFile)) parseFile(exportFile, true);
445     refreshView();
446     QList<QListWidgetItem *> child;
447     child = m_view.format_list->findItems(group, Qt::MatchExactly);
448     if (!child.isEmpty()) m_view.format_list->setCurrentItem(child.at(0));
449     child = m_view.size_list->findItems(profile, Qt::MatchExactly);
450     if (!child.isEmpty()) m_view.size_list->setCurrentItem(child.at(0));
451 }
452
453 void RenderWidget::parseFile(QString exportFile, bool editable) {
454     QDomDocument doc;
455     QFile file(exportFile);
456     doc.setContent(&file, false);
457     file.close();
458     QDomElement documentElement;
459     QDomElement profileElement;
460     QDomNodeList groups = doc.elementsByTagName("group");
461
462     if (groups.count() == 0) {
463         kDebug() << "// Export file: " << exportFile << " IS BROKEN";
464         return;
465     }
466
467     int i = 0;
468     QString groupName;
469     QString profileName;
470     QString extension;
471     QString prof_extension;
472     QString renderer;
473     QString params;
474     QString standard;
475     QListWidgetItem *item;
476     while (!groups.item(i).isNull()) {
477         documentElement = groups.item(i).toElement();
478         groupName = documentElement.attribute("name", QString::null);
479         extension = documentElement.attribute("extension", QString::null);
480         renderer = documentElement.attribute("renderer", QString::null);
481         if (m_view.format_list->findItems(groupName, Qt::MatchExactly).isEmpty())
482             new QListWidgetItem(groupName, m_view.format_list);
483
484         QDomNode n = groups.item(i).firstChild();
485         while (!n.isNull()) {
486             profileElement = n.toElement();
487             profileName = profileElement.attribute("name");
488             standard = profileElement.attribute("standard");
489             params = profileElement.attribute("args");
490             prof_extension = profileElement.attribute("extension");
491             if (!prof_extension.isEmpty()) extension = prof_extension;
492             item = new QListWidgetItem(profileName, m_view.size_list);
493             item->setData(GroupRole, groupName);
494             item->setData(ExtensionRole, extension);
495             item->setData(RenderRole, renderer);
496             item->setData(StandardRole, standard);
497             item->setData(ParamsRole, params);
498             if (editable) item->setData(EditableRole, "true");
499             n = n.nextSibling();
500         }
501
502         i++;
503     }
504 }
505
506
507
508 #include "renderwidget.moc"
509
510