]> git.sesse.net Git - kdenlive/blob - src/renderwidget.cpp
Do not allow rendering to a frame rate different than current project's profile:
[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 "renderwidget.h"
22 #include "kdenlivesettings.h"
23 #include "ui_saveprofile_ui.h"
24 #include "timecode.h"
25 #include "profilesdialog.h"
26
27 #include <KStandardDirs>
28 #include <KDebug>
29 #include <KMessageBox>
30 #include <KComboBox>
31 #include <KRun>
32 #include <KIO/NetAccess>
33 #include <KColorScheme>
34 #include <KNotification>
35 #include <KStartupInfo>
36
37 #include <QDomDocument>
38 #include <QItemDelegate>
39 #include <QTreeWidgetItem>
40 #include <QListWidgetItem>
41 #include <QHeaderView>
42 #include <QMenu>
43 #include <QInputDialog>
44 #include <QProcess>
45 #include <QDBusConnectionInterface>
46 #include <QDBusInterface>
47 #include <QThread>
48
49 const int GroupRole = Qt::UserRole;
50 const int ExtensionRole = GroupRole + 1;
51 const int StandardRole = GroupRole + 2;
52 const int RenderRole = GroupRole + 3;
53 const int ParamsRole = GroupRole + 4;
54 const int EditableRole = GroupRole + 5;
55 const int MetaGroupRole = GroupRole + 6;
56 const int ExtraRole = GroupRole + 7;
57 const int TwoPassRole = GroupRole + 8;
58 const int BitratesRole = GroupRole + 9;
59 const int DefaultBitrateRole = GroupRole + 10;
60
61 // Running job status
62 const int WAITINGJOB = 0;
63 const int RUNNINGJOB = 1;
64 const int FINISHEDJOB = 2;
65
66
67 RenderWidget::RenderWidget(const QString &projectfolder, bool enableProxy, QWidget * parent) :
68         QDialog(parent),
69         m_projectFolder(projectfolder),
70         m_blockProcessing(false)
71 {
72     m_view.setupUi(this);
73     setWindowTitle(i18n("Rendering"));
74     m_view.buttonDelete->setIcon(KIcon("trash-empty"));
75     m_view.buttonDelete->setToolTip(i18n("Delete profile"));
76     m_view.buttonDelete->setEnabled(false);
77
78     m_view.buttonEdit->setIcon(KIcon("document-properties"));
79     m_view.buttonEdit->setToolTip(i18n("Edit profile"));
80     m_view.buttonEdit->setEnabled(false);
81
82     m_view.buttonSave->setIcon(KIcon("document-new"));
83     m_view.buttonSave->setToolTip(i18n("Create new profile"));
84
85     m_view.buttonInfo->setIcon(KIcon("help-about"));
86     m_view.hide_log->setIcon(KIcon("go-down"));
87
88     m_view.buttonFavorite->setIcon(KIcon("favorites"));
89     m_view.buttonFavorite->setToolTip(i18n("Copy profile to favorites"));
90     
91     m_view.advanced_params->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
92
93     if (KdenliveSettings::showrenderparams()) {
94         m_view.buttonInfo->setDown(true);
95     } else m_view.advanced_params->hide();
96     
97     m_view.proxy_render->setHidden(!enableProxy);
98
99         m_view.encoder_threads->setMaximum(QThread::idealThreadCount());
100         m_view.encoder_threads->setValue(KdenliveSettings::encodethreads());
101         connect(m_view.encoder_threads, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateEncodeThreads(int)));
102         
103     m_view.rescale_keep->setChecked(KdenliveSettings::rescalekeepratio());
104     connect(m_view.rescale_width, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateRescaleWidth(int)));
105     connect(m_view.rescale_height, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateRescaleHeight(int)));
106     m_view.rescale_keep->setIcon(KIcon("insert-link"));
107     m_view.rescale_keep->setToolTip(i18n("Preserve aspect ratio"));
108     connect(m_view.rescale_keep, SIGNAL(clicked()), this, SLOT(slotSwitchAspectRatio()));
109
110     connect(m_view.buttonRender, SIGNAL(clicked()), this, SLOT(slotPrepareExport()));
111     connect(m_view.buttonGenerateScript, SIGNAL(clicked()), this, SLOT(slotGenerateScript()));
112
113     m_view.abort_job->setEnabled(false);
114     m_view.start_script->setEnabled(false);
115     m_view.delete_script->setEnabled(false);
116
117     m_view.format_list->setAlternatingRowColors(true);
118     m_view.size_list->setAlternatingRowColors(true);
119
120     KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme()));
121     QColor bg = scheme.background(KColorScheme::NegativeBackground).color();
122     m_view.errorBox->setStyleSheet(QString("QGroupBox { background-color: rgb(%1, %2, %3); border-radius: 5px;}; ").arg(bg.red()).arg(bg.green()).arg(bg.blue()));
123     int height = QFontInfo(font()).pixelSize();
124     m_view.errorIcon->setPixmap(KIcon("dialog-warning").pixmap(height, height));
125     m_view.errorBox->setHidden(true);
126
127     connect(m_view.export_audio, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateAudioLabel(int)));
128     m_view.export_audio->setCheckState(Qt::PartiallyChecked);
129
130     parseProfiles();
131     parseScriptFiles();
132     m_view.running_jobs->setUniformRowHeights(false);
133     m_view.scripts_list->setUniformRowHeights(false);
134     connect(m_view.start_script, SIGNAL(clicked()), this, SLOT(slotStartScript()));
135     connect(m_view.delete_script, SIGNAL(clicked()), this, SLOT(slotDeleteScript()));
136     connect(m_view.scripts_list, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckScript()));
137     connect(m_view.running_jobs, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckJob()));
138     connect(m_view.running_jobs, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotPlayRendering(QTreeWidgetItem *, int)));
139
140     connect(m_view.buttonInfo, SIGNAL(clicked()), this, SLOT(showInfoPanel()));
141
142     connect(m_view.buttonSave, SIGNAL(clicked()), this, SLOT(slotSaveProfile()));
143     connect(m_view.buttonEdit, SIGNAL(clicked()), this, SLOT(slotEditProfile()));
144     connect(m_view.buttonDelete, SIGNAL(clicked()), this, SLOT(slotDeleteProfile()));
145     connect(m_view.buttonFavorite, SIGNAL(clicked()), this, SLOT(slotCopyToFavorites()));
146
147     connect(m_view.abort_job, SIGNAL(clicked()), this, SLOT(slotAbortCurrentJob()));
148     connect(m_view.start_job, SIGNAL(clicked()), this, SLOT(slotStartCurrentJob()));
149     connect(m_view.clean_up, SIGNAL(clicked()), this, SLOT(slotCLeanUpJobs()));
150     connect(m_view.hide_log, SIGNAL(clicked()), this, SLOT(slotHideLog()));
151
152     connect(m_view.buttonClose, SIGNAL(clicked()), this, SLOT(hide()));
153     connect(m_view.buttonClose2, SIGNAL(clicked()), this, SLOT(hide()));
154     connect(m_view.buttonClose3, SIGNAL(clicked()), this, SLOT(hide()));
155     connect(m_view.rescale, SIGNAL(toggled(bool)), m_view.rescale_box, SLOT(setEnabled(bool)));
156     connect(m_view.destination_list, SIGNAL(activated(int)), this, SLOT(refreshCategory()));
157     connect(m_view.out_file, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButtons()));
158     connect(m_view.out_file, SIGNAL(urlSelected(const KUrl &)), this, SLOT(slotUpdateButtons(const KUrl &)));
159     connect(m_view.format_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshView()));
160     connect(m_view.size_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshParams()));
161
162     connect(m_view.size_list, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(slotEditItem(QListWidgetItem *)));
163
164     connect(m_view.render_guide, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
165     connect(m_view.render_zone, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
166     connect(m_view.render_full, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
167
168     connect(m_view.guide_end, SIGNAL(activated(int)), this, SLOT(slotCheckStartGuidePosition()));
169     connect(m_view.guide_start, SIGNAL(activated(int)), this, SLOT(slotCheckEndGuidePosition()));
170
171     connect(m_view.format_selection, SIGNAL(activated(int)), this, SLOT(refreshView()));
172     connect(m_view.tc_overlay, SIGNAL(toggled(bool)), m_view.tc_type, SLOT(setEnabled(bool)));
173
174     m_view.buttonRender->setEnabled(false);
175     m_view.buttonGenerateScript->setEnabled(false);
176     m_view.rescale_box->setEnabled(false);
177     m_view.guides_box->setVisible(false);
178     m_view.open_dvd->setVisible(false);
179     m_view.create_chapter->setVisible(false);
180     m_view.open_browser->setVisible(false);
181     m_view.error_box->setVisible(false);
182     m_view.tc_type->setEnabled(false);
183     m_view.checkTwoPass->setEnabled(false);
184
185     m_view.splitter->setStretchFactor(1, 5);
186     m_view.splitter->setStretchFactor(0, 2);
187
188     m_view.out_file->setMode(KFile::File);
189
190     m_view.running_jobs->setHeaderLabels(QStringList() << QString() << i18n("File") << i18n("Progress"));
191     m_jobsDelegate = new RenderViewDelegate(this);
192     m_view.running_jobs->setItemDelegate(m_jobsDelegate);
193
194     QHeaderView *header = m_view.running_jobs->header();
195     header->setResizeMode(0, QHeaderView::Fixed);
196     header->resizeSection(0, 30);
197     header->setResizeMode(1, QHeaderView::Interactive);
198     header->setResizeMode(2, QHeaderView::Fixed);
199     header->resizeSection(1, width() * 2 / 3 - 15);
200     header->setResizeMode(2, QHeaderView::Interactive);
201     //header->setResizeMode(1, QHeaderView::Fixed);
202
203
204     m_view.scripts_list->setHeaderLabels(QStringList() << QString() << i18n("Script Files"));
205     m_scriptsDelegate = new RenderViewDelegate(this);
206     m_view.scripts_list->setItemDelegate(m_scriptsDelegate);
207     header = m_view.scripts_list->header();
208     header->setResizeMode(0, QHeaderView::Fixed);
209     header->resizeSection(0, 30);
210
211     // Find path for Kdenlive renderer
212     m_renderer = QCoreApplication::applicationDirPath() + QString("/kdenlive_render");
213     if (!QFile::exists(m_renderer)) {
214         m_renderer = KStandardDirs::findExe("kdenlive_render");
215         if (m_renderer.isEmpty()) m_renderer = KStandardDirs::locate("exe", "kdenlive_render");
216         if (m_renderer.isEmpty()) m_renderer = "kdenlive_render";
217     }
218
219     QDBusConnectionInterface* interface = QDBusConnection::sessionBus().interface();
220     if (!interface || (!interface->isServiceRegistered("org.kde.ksmserver") && !interface->isServiceRegistered("org.gnome.SessionManager")))
221         m_view.shutdown->setEnabled(false);
222
223     // Hide the PAl / NTSC combobox since it's not working
224     m_view.label_3->setHidden(true);
225     m_view.format_selection->setHidden(true);
226     focusFirstVisibleItem();
227 }
228
229 RenderWidget::~RenderWidget()
230 {
231     m_view.running_jobs->blockSignals(true);
232     m_view.scripts_list->blockSignals(true);
233     m_view.running_jobs->clear();
234     m_view.scripts_list->clear();
235     delete m_jobsDelegate;
236     delete m_scriptsDelegate;
237 }
238
239 void RenderWidget::slotEditItem(QListWidgetItem *item)
240 {
241     QString edit = item->data(EditableRole).toString();
242     if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) slotSaveProfile();
243     else slotEditProfile();
244 }
245
246 void RenderWidget::showInfoPanel()
247 {
248     if (m_view.advanced_params->isVisible()) {
249         m_view.advanced_params->setVisible(false);
250         m_view.buttonInfo->setDown(false);
251         KdenliveSettings::setShowrenderparams(false);
252     } else {
253         m_view.advanced_params->setVisible(true);
254         m_view.buttonInfo->setDown(true);
255         KdenliveSettings::setShowrenderparams(true);
256     }
257 }
258
259 void RenderWidget::setDocumentPath(const QString path)
260 {
261     if (m_view.out_file->url().directory() == KUrl(m_projectFolder).directory()) {
262         const QString fileName = m_view.out_file->url().fileName();
263         m_view.out_file->setUrl(KUrl(path + fileName));
264     }
265     m_projectFolder = path;
266     parseScriptFiles();
267
268 }
269
270 void RenderWidget::slotUpdateGuideBox()
271 {
272     m_view.guides_box->setVisible(m_view.render_guide->isChecked());
273 }
274
275 void RenderWidget::slotCheckStartGuidePosition()
276 {
277     if (m_view.guide_start->currentIndex() > m_view.guide_end->currentIndex())
278         m_view.guide_start->setCurrentIndex(m_view.guide_end->currentIndex());
279 }
280
281 void RenderWidget::slotCheckEndGuidePosition()
282 {
283     if (m_view.guide_end->currentIndex() < m_view.guide_start->currentIndex())
284         m_view.guide_end->setCurrentIndex(m_view.guide_start->currentIndex());
285 }
286
287 void RenderWidget::setGuides(QDomElement guidesxml, double duration)
288 {
289     m_view.guide_start->clear();
290     m_view.guide_end->clear();
291     QDomNodeList nodes = guidesxml.elementsByTagName("guide");
292     if (nodes.count() > 0) {
293         m_view.guide_start->addItem(i18n("Beginning"), "0");
294         m_view.render_guide->setEnabled(true);
295         m_view.create_chapter->setEnabled(true);
296     } else {
297         m_view.render_guide->setEnabled(false);
298         m_view.create_chapter->setEnabled(false);
299     }
300     double fps = (double) m_profile.frame_rate_num / m_profile.frame_rate_den;
301     for (int i = 0; i < nodes.count(); i++) {
302         QDomElement e = nodes.item(i).toElement();
303         if (!e.isNull()) {
304             GenTime pos = GenTime(e.attribute("time").toDouble());
305             const QString guidePos = Timecode::getStringTimecode(pos.frames(fps), fps);
306             m_view.guide_start->addItem(e.attribute("comment") + '/' + guidePos, e.attribute("time").toDouble());
307             m_view.guide_end->addItem(e.attribute("comment") + '/' + guidePos, e.attribute("time").toDouble());
308         }
309     }
310     if (nodes.count() > 0)
311         m_view.guide_end->addItem(i18n("End"), QString::number(duration));
312 }
313
314 /**
315  * Will be called when the user selects an output file via the file dialog.
316  * File extension will be added automatically.
317  */
318 void RenderWidget::slotUpdateButtons(KUrl url)
319 {
320     if (m_view.out_file->url().isEmpty()) {
321         m_view.buttonGenerateScript->setEnabled(false);
322         m_view.buttonRender->setEnabled(false);
323     } else {
324         updateButtons(); // This also checks whether the selected format is available
325     }
326     if (url != 0) {
327         QListWidgetItem *item = m_view.size_list->currentItem();
328         if (!item) {
329             m_view.buttonRender->setEnabled(false);
330             m_view.buttonGenerateScript->setEnabled(false);
331             return;
332         }
333         QString extension = item->data(ExtensionRole).toString();
334         url = filenameWithExtension(url, extension);
335         m_view.out_file->setUrl(url);
336     }
337 }
338
339 /**
340  * Will be called when the user changes the output file path in the text line.
341  * File extension must NOT be added, would make editing impossible!
342  */
343 void RenderWidget::slotUpdateButtons()
344 {
345     if (m_view.out_file->url().isEmpty()) {
346         m_view.buttonRender->setEnabled(false);
347         m_view.buttonGenerateScript->setEnabled(false);
348     } else {
349         updateButtons(); // This also checks whether the selected format is available
350     }
351 }
352
353 void RenderWidget::slotSaveProfile()
354 {
355     //TODO: update to correctly use metagroups
356     Ui::SaveProfile_UI ui;
357     QDialog *d = new QDialog(this);
358     ui.setupUi(d);
359
360     for (int i = 0; i < m_view.destination_list->count(); i++)
361         ui.destination_list->addItem(m_view.destination_list->itemIcon(i), m_view.destination_list->itemText(i), m_view.destination_list->itemData(i, Qt::UserRole));
362
363     ui.destination_list->setCurrentIndex(m_view.destination_list->currentIndex());
364     QString dest = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
365
366     QString customGroup = m_view.format_list->currentItem()->text();
367     if (customGroup.isEmpty()) customGroup = i18nc("Group Name", "Custom");
368     ui.group_name->setText(customGroup);
369
370     ui.parameters->setText(m_view.advanced_params->toPlainText());
371     ui.extension->setText(m_view.size_list->currentItem()->data(ExtensionRole).toString());
372     ui.profile_name->setFocus();
373
374     if (d->exec() == QDialog::Accepted && !ui.profile_name->text().simplified().isEmpty()) {
375         QString newProfileName = ui.profile_name->text().simplified();
376         QString newGroupName = ui.group_name->text().simplified();
377         if (newGroupName.isEmpty()) newGroupName = i18nc("Group Name", "Custom");
378         QString newMetaGroupId = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
379
380         QDomDocument doc;
381         QDomElement profileElement = doc.createElement("profile");
382         profileElement.setAttribute("name", newProfileName);
383         profileElement.setAttribute("category", newGroupName);
384         profileElement.setAttribute("destinationid", newMetaGroupId);
385         profileElement.setAttribute("extension", ui.extension->text().simplified());
386         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
387         doc.appendChild(profileElement);
388         saveProfile(doc.documentElement());
389
390         parseProfiles(newMetaGroupId, newGroupName, newProfileName);
391     }
392     delete d;
393 }
394
395
396 void RenderWidget::saveProfile(QDomElement newprofile)
397 {
398     QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
399     QDomDocument doc;
400     QFile file(exportFile);
401     doc.setContent(&file, false);
402     file.close();
403     QDomElement documentElement;
404     QDomElement profiles = doc.documentElement();
405     if (profiles.isNull() || profiles.tagName() != "profiles") {
406         doc.clear();
407         profiles = doc.createElement("profiles");
408         profiles.setAttribute("version", 1);
409         doc.appendChild(profiles);
410     }
411     int version = profiles.attribute("version", 0).toInt();
412     if (version < 1) {
413         kDebug() << "// OLD profile version";
414         doc.clear();
415         profiles = doc.createElement("profiles");
416         profiles.setAttribute("version", 1);
417         doc.appendChild(profiles);
418     }
419
420
421     QDomNodeList profilelist = doc.elementsByTagName("profile");
422     int i = 0;
423     while (!profilelist.item(i).isNull()) {
424         // make sure a profile with same name doesn't exist
425         documentElement = profilelist.item(i).toElement();
426         QString profileName = documentElement.attribute("name");
427         if (profileName == newprofile.attribute("name")) {
428             // a profile with that same name already exists
429             bool ok;
430             QString newProfileName = QInputDialog::getText(this, i18n("Profile already exists"), i18n("This profile name already exists. Change the name if you don't want to overwrite it."), QLineEdit::Normal, profileName, &ok);
431             if (!ok) return;
432             if (profileName == newProfileName) {
433                 profiles.removeChild(profilelist.item(i));
434                 break;
435             }
436         }
437         i++;
438     }
439
440     profiles.appendChild(newprofile);
441
442     //QCString save = doc.toString().utf8();
443
444     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
445         KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
446         return;
447     }
448     QTextStream out(&file);
449     out << doc.toString();
450     if (file.error() != QFile::NoError) {
451         KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
452         file.close();
453         return;
454     }
455     file.close();
456 }
457
458 void RenderWidget::slotCopyToFavorites()
459 {
460     QListWidgetItem *item = m_view.size_list->currentItem();
461     if (!item) return;
462     QString currentGroup = m_view.format_list->currentItem()->text();
463
464     QString params = item->data(ParamsRole).toString();
465     QString extension = item->data(ExtensionRole).toString();
466     QString currentProfile = item->text();
467     QDomDocument doc;
468     QDomElement profileElement = doc.createElement("profile");
469     profileElement.setAttribute("name", currentProfile);
470     profileElement.setAttribute("category", i18nc("Category Name", "Custom"));
471     profileElement.setAttribute("destinationid", "favorites");
472     profileElement.setAttribute("extension", extension);
473     profileElement.setAttribute("args", params);
474     doc.appendChild(profileElement);
475     saveProfile(doc.documentElement());
476     parseProfiles(m_view.destination_list->itemData(m_view.destination_list->currentIndex(), Qt::UserRole).toString(), currentGroup, currentProfile);
477 }
478
479 void RenderWidget::slotEditProfile()
480 {
481     QListWidgetItem *item = m_view.size_list->currentItem();
482     if (!item) return;
483     QString currentGroup = m_view.format_list->currentItem()->text();
484
485     QString params = item->data(ParamsRole).toString();
486     QString extension = item->data(ExtensionRole).toString();
487     QString currentProfile = item->text();
488
489     Ui::SaveProfile_UI ui;
490     QDialog *d = new QDialog(this);
491     ui.setupUi(d);
492
493     for (int i = 0; i < m_view.destination_list->count(); i++)
494         ui.destination_list->addItem(m_view.destination_list->itemIcon(i), m_view.destination_list->itemText(i), m_view.destination_list->itemData(i, Qt::UserRole));
495
496     ui.destination_list->setCurrentIndex(m_view.destination_list->currentIndex());
497     QString dest = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
498
499     QString customGroup = m_view.format_list->currentItem()->text();
500     if (customGroup.isEmpty()) customGroup = i18nc("Group Name", "Custom");
501     ui.group_name->setText(customGroup);
502
503     ui.profile_name->setText(currentProfile);
504     ui.extension->setText(extension);
505     ui.parameters->setText(params);
506     ui.profile_name->setFocus();
507     d->setWindowTitle(i18n("Edit Profile"));
508     if (d->exec() == QDialog::Accepted) {
509         slotDeleteProfile(false);
510         QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
511         QDomDocument doc;
512         QFile file(exportFile);
513         doc.setContent(&file, false);
514         file.close();
515         QDomElement documentElement;
516         QDomElement profiles = doc.documentElement();
517
518         if (profiles.isNull() || profiles.tagName() != "profiles") {
519             doc.clear();
520             profiles = doc.createElement("profiles");
521             profiles.setAttribute("version", 1);
522             doc.appendChild(profiles);
523         }
524
525         int version = profiles.attribute("version", 0).toInt();
526         if (version < 1) {
527             kDebug() << "// OLD profile version";
528             doc.clear();
529             profiles = doc.createElement("profiles");
530             profiles.setAttribute("version", 1);
531             doc.appendChild(profiles);
532         }
533
534         QString newProfileName = ui.profile_name->text().simplified();
535         QString newGroupName = ui.group_name->text().simplified();
536         if (newGroupName.isEmpty()) newGroupName = i18nc("Group Name", "Custom");
537         QString newMetaGroupId = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
538         QDomNodeList profilelist = doc.elementsByTagName("profile");
539         int i = 0;
540         while (!profilelist.item(i).isNull()) {
541             // make sure a profile with same name doesn't exist
542             documentElement = profilelist.item(i).toElement();
543             QString profileName = documentElement.attribute("name");
544             if (profileName == newProfileName) {
545                 // a profile with that same name already exists
546                 bool ok;
547                 newProfileName = QInputDialog::getText(this, i18n("Profile already exists"), i18n("This profile name already exists. Change the name if you don't want to overwrite it."), QLineEdit::Normal, newProfileName, &ok);
548                 if (!ok) return;
549                 if (profileName == newProfileName) {
550                     profiles.removeChild(profilelist.item(i));
551                     break;
552                 }
553             }
554             i++;
555         }
556
557         QDomElement profileElement = doc.createElement("profile");
558         profileElement.setAttribute("name", newProfileName);
559         profileElement.setAttribute("category", newGroupName);
560         profileElement.setAttribute("destinationid", newMetaGroupId);
561         profileElement.setAttribute("extension", ui.extension->text().simplified());
562         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
563         profiles.appendChild(profileElement);
564
565         //QCString save = doc.toString().utf8();
566         delete d;
567         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
568             KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
569             return;
570         }
571         QTextStream out(&file);
572         out << doc.toString();
573         if (file.error() != QFile::NoError) {
574             KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
575             file.close();
576             return;
577         }
578         file.close();
579         parseProfiles(newMetaGroupId, newGroupName, newProfileName);
580     } else delete d;
581 }
582
583 void RenderWidget::slotDeleteProfile(bool refresh)
584 {
585     //TODO: delete a profile installed by KNewStuff the easy way
586     /*
587     QString edit = m_view.size_list->currentItem()->data(EditableRole).toString();
588     if (!edit.endsWith("customprofiles.xml")) {
589         // This is a KNewStuff installed file, process through KNS
590         KNS::Engine engine(0);
591         if (engine.init("kdenlive_render.knsrc")) {
592             KNS::Entry::List entries;
593         }
594         return;
595     }*/
596     QString currentGroup = m_view.format_list->currentItem()->text();
597     QString currentProfile = m_view.size_list->currentItem()->text();
598     QString metaGroupId = m_view.destination_list->itemData(m_view.destination_list->currentIndex(), Qt::UserRole).toString();
599
600     QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
601     QDomDocument doc;
602     QFile file(exportFile);
603     doc.setContent(&file, false);
604     file.close();
605
606     QDomElement documentElement;
607     QDomNodeList profiles = doc.elementsByTagName("profile");
608     int i = 0;
609     QString groupName;
610     QString profileName;
611     QString destination;
612
613     while (!profiles.item(i).isNull()) {
614         documentElement = profiles.item(i).toElement();
615         profileName = documentElement.attribute("name");
616         groupName = documentElement.attribute("category");
617         destination = documentElement.attribute("destinationid");
618
619         if (profileName == currentProfile && groupName == currentGroup && destination == metaGroupId) {
620             kDebug() << "// GOT it: " << profileName;
621             doc.documentElement().removeChild(profiles.item(i));
622             break;
623         }
624         i++;
625     }
626
627     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
628         KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
629         return;
630     }
631     QTextStream out(&file);
632     out << doc.toString();
633     if (file.error() != QFile::NoError) {
634         KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
635         file.close();
636         return;
637     }
638     file.close();
639     if (refresh) {
640         parseProfiles(metaGroupId, currentGroup);
641         focusFirstVisibleItem();
642     }
643 }
644
645 void RenderWidget::updateButtons()
646 {
647     if (!m_view.size_list->currentItem() || m_view.size_list->currentItem()->isHidden()) {
648         m_view.buttonSave->setEnabled(false);
649         m_view.buttonDelete->setEnabled(false);
650         m_view.buttonEdit->setEnabled(false);
651         m_view.buttonRender->setEnabled(false);
652         m_view.buttonGenerateScript->setEnabled(false);
653     } else {
654         m_view.buttonSave->setEnabled(true);
655         m_view.buttonRender->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
656         m_view.buttonGenerateScript->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
657         QString edit = m_view.size_list->currentItem()->data(EditableRole).toString();
658         if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) {
659             m_view.buttonDelete->setEnabled(false);
660             m_view.buttonEdit->setEnabled(false);
661         } else {
662             m_view.buttonDelete->setEnabled(true);
663             m_view.buttonEdit->setEnabled(true);
664         }
665     }
666 }
667
668
669 void RenderWidget::focusFirstVisibleItem()
670 {
671     if (m_view.size_list->currentItem()) {
672         updateButtons();
673         return;
674     }
675     m_view.size_list->setCurrentRow(0);
676     updateButtons();
677 }
678
679 void RenderWidget::slotPrepareExport(bool scriptExport)
680 {
681     if (!QFile::exists(KdenliveSettings::rendererpath())) {
682         KMessageBox::sorry(this, i18n("Cannot find the melt program required for rendering (part of Mlt)"));
683         return;
684     }
685     if (m_view.play_after->isChecked() && KdenliveSettings::defaultplayerapp().isEmpty()) {
686         KMessageBox::sorry(this, i18n("Cannot play video after rendering because the default video player application is not set.\nPlease define it in Kdenlive settings dialog."));
687     }
688     QString chapterFile;
689     if (m_view.create_chapter->isChecked()) chapterFile = m_view.out_file->url().path() + ".dvdchapter";
690
691     // mantisbt 1051
692     if (!KStandardDirs::makeDir(m_view.out_file->url().directory())) {
693         KMessageBox::sorry(this, i18n("The directory %1, could not be created.\nPlease make sure you have the required permissions.", m_view.out_file->url().directory()));
694         return;
695     }
696
697     emit prepareRenderingData(scriptExport, m_view.render_zone->isChecked(), chapterFile);
698 }
699
700
701 void RenderWidget::slotExport(bool scriptExport, int zoneIn, int zoneOut, const QString &playlistPath, const QString &scriptPath, bool exportAudio)
702 {
703     QListWidgetItem *item = m_view.size_list->currentItem();
704     if (!item) return;
705
706     QString dest = m_view.out_file->url().path();
707     if (dest.isEmpty()) return;
708
709     // Check whether target file has an extension.
710     // If not, ask whether extension should be added or not.
711     QString extension = item->data(ExtensionRole).toString();
712     if (!dest.endsWith(extension, Qt::CaseInsensitive)) {
713         if (KMessageBox::questionYesNo(this, i18n("File has no extension. Add extension (%1)?", extension)) == KMessageBox::Yes) {
714             dest.append("." + extension);
715         }
716     }
717
718     QFile f(dest);
719     if (f.exists()) {
720         if (KMessageBox::warningYesNo(this, i18n("Output file already exists. Do you want to overwrite it?")) != KMessageBox::Yes)
721             return;
722     }
723
724     QStringList overlayargs;
725     if (m_view.tc_overlay->isChecked()) {
726         QString filterFile = KStandardDirs::locate("appdata", "metadata.properties");
727         overlayargs << "meta.attr.timecode=1" << "meta.attr.timecode.markup=#" + QString(m_view.tc_type->currentIndex() ? "frame" : "timecode");
728         overlayargs << "-attach" << "data_feed:attr_check" << "-attach";
729         overlayargs << "data_show:" + filterFile << "_loader=1" << "dynamic=1";
730     }
731
732     QStringList render_process_args;
733
734     if (!scriptExport) render_process_args << "-erase";
735     if (KdenliveSettings::usekuiserver()) render_process_args << "-kuiserver";
736
737     double guideStart = 0;
738     double guideEnd = 0;
739
740     if (m_view.render_zone->isChecked()) render_process_args << "in=" + QString::number(zoneIn) << "out=" + QString::number(zoneOut);
741     else if (m_view.render_guide->isChecked()) {
742         double fps = (double) m_profile.frame_rate_num / m_profile.frame_rate_den;
743         guideStart = m_view.guide_start->itemData(m_view.guide_start->currentIndex()).toDouble();
744         guideEnd = m_view.guide_end->itemData(m_view.guide_end->currentIndex()).toDouble();
745         render_process_args << "in=" + QString::number(GenTime(guideStart).frames(fps)) << "out=" + QString::number(GenTime(guideEnd).frames(fps));
746     }
747
748     if (!overlayargs.isEmpty()) render_process_args << "preargs=" + overlayargs.join(" ");
749
750     render_process_args << KdenliveSettings::rendererpath() << m_profile.path << item->data(RenderRole).toString();
751     if (m_view.play_after->isChecked()) render_process_args << KdenliveSettings::KdenliveSettings::defaultplayerapp();
752     else render_process_args << "-";
753
754     QString renderArgs = m_view.advanced_params->toPlainText().simplified();
755
756     // Adjust frame scale
757     int width;
758     int height;
759     if (m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
760         width = m_view.rescale_width->value();
761         height = m_view.rescale_height->value();
762     } else {
763         width = m_profile.width;
764         height = m_profile.height;
765     }
766     renderArgs.replace("%dar", '@' + QString::number(m_profile.display_aspect_num) + '/' + QString::number(m_profile.display_aspect_den));
767     //renderArgs.replace("%width", QString::number((int)(m_profile.height * m_profile.display_aspect_num / (double) m_profile.display_aspect_den + 0.5)));
768     //renderArgs.replace("%height", QString::number((int)m_profile.height));
769
770     // Adjust scanning
771     if (m_view.scanning_list->currentIndex() == 1) renderArgs.append(" progressive=1");
772     else if (m_view.scanning_list->currentIndex() == 2) renderArgs.append(" progressive=0");
773
774     // disable audio if requested
775     if (!exportAudio) renderArgs.append(" an=1 ");
776
777     // Set the thread counts
778     renderArgs.append(QString(" threads=%1").arg(KdenliveSettings::encodethreads()));
779     renderArgs.append(QString(" real_time=-%1").arg(KdenliveSettings::mltthreads()));
780
781     // 2 pass
782     if (m_view.checkTwoPass->isEnabled() && m_view.checkTwoPass->isChecked())
783         renderArgs.append(" pass=2");
784
785     // bitrate
786     if (m_view.comboBitrates->isEnabled())
787         renderArgs.replace("%bitrate", m_view.comboBitrates->currentText());
788
789     // Check if the rendering profile is different from project profile,
790     // in which case we need to use the producer_comsumer from MLT
791     QString std = renderArgs;
792     QString destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
793     const QString currentSize = QString::number(width) + 'x' + QString::number(height);
794     QString subsize = currentSize;
795     if (std.startsWith("s=")) {
796         subsize = std.section(' ', 0, 0).toLower();
797         subsize = subsize.section('=', 1, 1);
798     } else if (std.contains(" s=")) {
799         subsize = std.section(" s=", 1, 1);
800         subsize = subsize.section(' ', 0, 0).toLower();
801     } else if (destination != "audioonly" && m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
802         subsize = QString(" s=%1x%2").arg(width).arg(height);
803         // Add current size parameter
804         renderArgs.append(subsize);
805     }
806     bool resizeProfile = (subsize != currentSize);
807     QStringList paramsList = renderArgs.split(" ", QString::SkipEmptyParts);
808     for (int i = 0; i < paramsList.count(); i++) {
809         if (paramsList.at(i).startsWith("profile=")) {
810             if (paramsList.at(i).section('=', 1) != m_profile.path) resizeProfile = true;
811             break;
812         }
813     }
814         
815     if (resizeProfile)
816         render_process_args << "consumer:" + (scriptExport ? "$SOURCE" : playlistPath);
817     else
818         render_process_args <<  (scriptExport ? "$SOURCE" : playlistPath);
819     render_process_args << (scriptExport ? "$TARGET" : KUrl(dest).url());
820     render_process_args << paramsList;
821
822     QString group = m_view.size_list->currentItem()->data(MetaGroupRole).toString();
823
824     QString scriptName;
825     if (scriptExport) {
826         // Generate script file
827         QFile file(scriptPath);
828         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
829             KMessageBox::error(this, i18n("Cannot write to file %1", scriptPath));
830             return;
831         }
832         QTextStream outStream(&file);
833         outStream << "#! /bin/sh" << "\n" << "\n";
834         outStream << "SOURCE=" << "\"" + playlistPath + "\"" << "\n";
835         outStream << "TARGET=" << "\"" + KUrl(dest).url() + "\"" << "\n";
836         outStream << "RENDERER=" << "\"" + m_renderer + "\"" << "\n";
837         outStream << "MELT=" << "\"" + render_process_args.takeFirst() + "\"" << "\n";
838         outStream << "PARAMETERS=" << "\"" + render_process_args.join(" ") + "\"" << "\n";
839         outStream << "$RENDERER $MELT $PARAMETERS" << "\n" << "\n";
840         if (file.error() != QFile::NoError) {
841             KMessageBox::error(this, i18n("Cannot write to file %1", scriptPath));
842             file.close();
843             return;
844         }
845         file.close();
846         QFile::setPermissions(scriptPath, file.permissions() | QFile::ExeUser);
847
848         QTimer::singleShot(400, this, SLOT(parseScriptFiles()));
849         m_view.tabWidget->setCurrentIndex(2);
850         return;
851     }
852
853     // Save rendering profile to document
854     QMap <QString, QString> renderProps;
855     renderProps.insert("renderdestination", m_view.size_list->currentItem()->data(MetaGroupRole).toString());
856     renderProps.insert("rendercategory", m_view.size_list->currentItem()->data(GroupRole).toString());
857     renderProps.insert("renderprofile", m_view.size_list->currentItem()->text());
858     renderProps.insert("renderurl", dest);
859     renderProps.insert("renderzone", QString::number(m_view.render_zone->isChecked()));
860     renderProps.insert("renderguide", QString::number(m_view.render_guide->isChecked()));
861     renderProps.insert("renderstartguide", QString::number(m_view.guide_start->currentIndex()));
862     renderProps.insert("renderendguide", QString::number(m_view.guide_end->currentIndex()));
863     renderProps.insert("renderendguide", QString::number(m_view.guide_end->currentIndex()));
864     renderProps.insert("renderscanning", QString::number(m_view.scanning_list->currentIndex()));
865     int export_audio = 0;
866     if (m_view.export_audio->checkState() == Qt::Checked) export_audio = 2;
867     else if (m_view.export_audio->checkState() == Qt::Unchecked) export_audio = 1;
868     renderProps.insert("renderexportaudio", QString::number(export_audio));
869     renderProps.insert("renderrescale", QString::number(m_view.rescale->isChecked()));
870     renderProps.insert("renderrescalewidth", QString::number(m_view.rescale_width->value()));
871     renderProps.insert("renderrescaleheight", QString::number(m_view.rescale_height->value()));
872     renderProps.insert("rendertcoverlay", QString::number(m_view.tc_overlay->isChecked()));
873     renderProps.insert("rendertctype", QString::number(m_view.tc_type->currentIndex()));
874     renderProps.insert("renderratio", QString::number(m_view.rescale_keep->isChecked()));
875     renderProps.insert("renderplay", QString::number(m_view.play_after->isChecked()));
876     renderProps.insert("rendertwopass", QString::number(m_view.checkTwoPass->isChecked()));
877
878     emit selectedRenderProfile(renderProps);
879
880     // insert item in running jobs list
881     QTreeWidgetItem *renderItem;
882     QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
883     if (!existing.isEmpty()) {
884         renderItem = existing.at(0);
885         if (renderItem->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) {
886             KMessageBox::information(this, i18n("There is already a job writing file:<br /><b>%1</b><br />Abort the job if you want to overwrite it...", dest), i18n("Already running"));
887             return;
888         }
889         renderItem->setData(1, Qt::UserRole + 4, QString());
890     } else {
891         renderItem = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
892     }
893     renderItem->setData(1, Qt::UserRole + 2, WAITINGJOB);
894     renderItem->setIcon(0, KIcon("media-playback-pause"));
895     renderItem->setData(1, Qt::UserRole, i18n("Waiting..."));
896     renderItem->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
897     renderItem->setData(1, Qt::UserRole + 1, QTime::currentTime());
898
899     // Set rendering type
900     if (group == "dvd") {
901         if (m_view.open_dvd->isChecked()) {
902             renderItem->setData(0, Qt::UserRole, group);
903             if (renderArgs.contains("profile=")) {
904                 // rendering profile contains an MLT profile, so pass it to the running jog item, useful for dvd
905                 QString prof = renderArgs.section("profile=", 1, 1);
906                 prof = prof.section(' ', 0, 0);
907                 kDebug() << "// render profile: " << prof;
908                 renderItem->setData(0, Qt::UserRole + 1, prof);
909             }
910         }
911     } else {
912         if (group == "websites" && m_view.open_browser->isChecked()) {
913             renderItem->setData(0, Qt::UserRole, group);
914             // pass the url
915             QString url = m_view.size_list->currentItem()->data(ExtraRole).toString();
916             renderItem->setData(0, Qt::UserRole + 1, url);
917         }
918     }
919     renderItem->setData(1, Qt::UserRole + 3, render_process_args);
920     if (exportAudio == false) renderItem->setData(1, Qt::UserRole + 5, i18n("Video without audio track"));
921     else  renderItem->setData(1, Qt::UserRole + 5, QString());
922     m_view.running_jobs->setCurrentItem(renderItem);
923     m_view.tabWidget->setCurrentIndex(1);
924     checkRenderStatus();
925 }
926
927 void RenderWidget::checkRenderStatus()
928 {
929     // check if we have a job waiting to render
930     if (m_blockProcessing) return;
931     QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
932     while (item) {
933         if (item->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) return;
934         item = m_view.running_jobs->itemBelow(item);
935     }
936     item = m_view.running_jobs->topLevelItem(0);
937     bool waitingJob = false;
938     while (item) {
939         if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) {
940             item->setData(1, Qt::UserRole + 1, QTime::currentTime());
941             waitingJob = true;
942             startRendering(item);
943             break;
944         }
945         item = m_view.running_jobs->itemBelow(item);
946     }
947     if (waitingJob == false && m_view.shutdown->isChecked()) emit shutdown();
948 }
949
950 void RenderWidget::startRendering(QTreeWidgetItem *item)
951 {
952     if (item->data(1, Qt::UserRole + 4).isNull()) {
953         // Normal render process
954         if (QProcess::startDetached(m_renderer, item->data(1, Qt::UserRole + 3).toStringList()) == false) {
955             item->setData(1, Qt::UserRole + 2, FINISHEDJOB);
956             item->setData(1, Qt::UserRole, i18n("Rendering crashed"));
957             item->setIcon(0, KIcon("dialog-close"));
958             item->setData(2, Qt::UserRole, 100);
959         } else KNotification::event("RenderStarted", i18n("Rendering <i>%1</i> started", item->text(1)), QPixmap(), this);
960     } else {
961         // Script item
962         if (QProcess::startDetached(item->data(1, Qt::UserRole + 3).toString()) == false) {
963             item->setData(1, Qt::UserRole + 2, FINISHEDJOB);
964             item->setData(1, Qt::UserRole, i18n("Rendering crashed"));
965             item->setIcon(0, KIcon("dialog-close"));
966             item->setData(2, Qt::UserRole, 100);
967         }
968     }
969 }
970
971 int RenderWidget::waitingJobsCount() const
972 {
973     int count = 0;
974     QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
975     while (item) {
976         if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) count++;
977         item = m_view.running_jobs->itemBelow(item);
978     }
979     return count;
980 }
981
982 void RenderWidget::setProfile(MltVideoProfile profile)
983 {
984     m_profile = profile;
985     //WARNING: this way to tell the video standard is a bit hackish...
986     if (m_profile.description.contains("pal", Qt::CaseInsensitive) || m_profile.description.contains("25", Qt::CaseInsensitive) || m_profile.description.contains("50", Qt::CaseInsensitive))
987         m_view.format_selection->setCurrentIndex(0);
988     else
989         m_view.format_selection->setCurrentIndex(1);
990     m_view.scanning_list->setCurrentIndex(0);
991     m_view.rescale_width->setValue(KdenliveSettings::defaultrescalewidth());
992     if (!m_view.rescale_keep->isChecked()) {
993         m_view.rescale_height->blockSignals(true);
994         m_view.rescale_height->setValue(KdenliveSettings::defaultrescaleheight());
995         m_view.rescale_height->blockSignals(false);
996     }
997     refreshView();
998 }
999
1000 void RenderWidget::refreshCategory()
1001 {
1002     m_view.format_list->blockSignals(true);
1003     m_view.format_list->clear();
1004     QListWidgetItem *sizeItem;
1005
1006     QString destination;
1007     if (m_view.destination_list->currentIndex() > 0)
1008         destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
1009
1010
1011     if (destination == "dvd") {
1012         m_view.open_dvd->setVisible(true);
1013         m_view.create_chapter->setVisible(true);
1014     } else {
1015         m_view.open_dvd->setVisible(false);
1016         m_view.create_chapter->setVisible(false);
1017     }
1018
1019     if (destination == "websites")
1020         m_view.open_browser->setVisible(true);
1021     else
1022         m_view.open_browser->setVisible(false);
1023
1024     // hide groups that are not in the correct destination
1025     for (int i = 0; i < m_renderCategory.count(); i++) {
1026         sizeItem = m_renderCategory.at(i);
1027         if (sizeItem->data(MetaGroupRole).toString() == destination) {
1028             m_view.format_list->addItem(sizeItem->clone());
1029             //kDebug() << "// SET GRP:: " << sizeItem->text() << ", METY:" << sizeItem->data(MetaGroupRole).toString();
1030         }
1031     }
1032
1033     // activate first visible item
1034     QListWidgetItem * item = m_view.format_list->currentItem();
1035     if (!item) {
1036         m_view.format_list->setCurrentRow(0);
1037         item = m_view.format_list->currentItem();
1038     }
1039     if (!item) {
1040         m_view.format_list->setEnabled(false);
1041         m_view.size_list->setEnabled(false);
1042         m_view.size_list->blockSignals(false);
1043         m_view.format_list->blockSignals(false);
1044         return;
1045     } else {
1046         m_view.format_list->setEnabled(true);
1047         m_view.size_list->setEnabled(true);
1048     }
1049
1050     if (m_view.format_list->count() > 1)
1051         m_view.format_list->setVisible(true);
1052     else
1053         m_view.format_list->setVisible(false);
1054     refreshView();
1055 }
1056
1057 void RenderWidget::refreshView()
1058 {
1059     if (!m_view.format_list->currentItem()) return;
1060     m_view.size_list->blockSignals(true);
1061     m_view.size_list->clear();
1062     QListWidgetItem *sizeItem;
1063     QString std;
1064     QString group = m_view.format_list->currentItem()->text();
1065     QString destination;
1066     if (m_view.destination_list->currentIndex() > 0)
1067         destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
1068     KIcon brokenIcon("dialog-close");
1069
1070     if (m_view.format_list->currentItem()->data(TwoPassRole).canConvert(QVariant::Bool))
1071         m_view.checkTwoPass->setEnabled(m_view.format_list->currentItem()->data(TwoPassRole).toBool());
1072     else
1073         m_view.checkTwoPass->setEnabled(true);
1074
1075     const QStringList formatsList = KdenliveSettings::supportedformats();
1076     const QStringList vcodecsList = KdenliveSettings::videocodecs();
1077     const QStringList acodecsList = KdenliveSettings::audiocodecs();
1078
1079     KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window);
1080     const QColor disabled = scheme.foreground(KColorScheme::InactiveText).color();
1081     const QColor disabledbg = scheme.background(KColorScheme::NegativeBackground).color();
1082
1083     double project_framerate = (double) m_profile.frame_rate_num / m_profile.frame_rate_den;
1084     for (int i = 0; i < m_renderItems.count(); i++) {
1085         sizeItem = m_renderItems.at(i);
1086         QListWidgetItem *dupItem = NULL;
1087         if ((sizeItem->data(GroupRole).toString() == group || sizeItem->data(GroupRole).toString().isEmpty()) && sizeItem->data(MetaGroupRole).toString() == destination) {
1088             std = sizeItem->data(StandardRole).toString();
1089             if (!std.isEmpty()) {
1090                 if (std.contains("PAL", Qt::CaseInsensitive) && m_view.format_selection->currentIndex() == 0) dupItem = sizeItem->clone();
1091                 else if (std.contains("NTSC", Qt::CaseInsensitive) && m_view.format_selection->currentIndex() == 1)  dupItem = sizeItem->clone();
1092             } else {
1093                 dupItem = sizeItem->clone();
1094             }
1095
1096             if (dupItem) {
1097                 m_view.size_list->addItem(dupItem);
1098                 std = dupItem->data(ParamsRole).toString();
1099                 // Make sure the selected profile uses the same frame rate as project profile
1100                 if (std.contains("profile=")) {
1101                     QString profile = std.section("profile=", 1, 1).section(' ', 0, 0);
1102                     MltVideoProfile p = ProfilesDialog::getVideoProfile(profile);
1103                     if (p.frame_rate_den > 0 && ((double) p.frame_rate_num / p.frame_rate_den != project_framerate)) {
1104                         dupItem->setToolTip(i18n("Frame rate not compatible with project profile"));
1105                         dupItem->setIcon(brokenIcon);
1106                         dupItem->setForeground(disabled);
1107                     }
1108                 }
1109                 
1110                 // Make sure the selected profile uses an installed avformat codec / format
1111                 if (!formatsList.isEmpty()) {
1112                     QString format;
1113                     if (std.startsWith("f=")) format = std.section("f=", 1, 1);
1114                     else if (std.contains(" f=")) format = std.section(" f=", 1, 1);
1115                     if (!format.isEmpty()) {
1116                         format = format.section(' ', 0, 0).toLower();
1117                         if (!formatsList.contains(format)) {
1118                             kDebug() << "***** UNSUPPORTED F: " << format;
1119                             //sizeItem->setHidden(true);
1120                             //sizeItem-item>setFlags(Qt::ItemIsSelectable);
1121                             dupItem->setToolTip(i18n("Unsupported video format: %1", format));
1122                             dupItem->setIcon(brokenIcon);
1123                             dupItem->setForeground(disabled);
1124                         }
1125                     }
1126                 }
1127                 if (!acodecsList.isEmpty()) {
1128                     QString format;
1129                     if (std.startsWith("acodec=")) format = std.section("acodec=", 1, 1);
1130                     else if (std.contains(" acodec=")) format = std.section(" acodec=", 1, 1);
1131                     if (!format.isEmpty()) {
1132                         format = format.section(' ', 0, 0).toLower();
1133                         if (!acodecsList.contains(format)) {
1134                             kDebug() << "*****  UNSUPPORTED ACODEC: " << format;
1135                             //sizeItem->setHidden(true);
1136                             //sizeItem->setFlags(Qt::ItemIsSelectable);
1137                             dupItem->setToolTip(i18n("Unsupported audio codec: %1", format));
1138                             dupItem->setIcon(brokenIcon);
1139                             dupItem->setForeground(disabled);
1140                             dupItem->setBackground(disabledbg);
1141                         }
1142                     }
1143                 }
1144                 if (!vcodecsList.isEmpty()) {
1145                     QString format;
1146                     if (std.startsWith("vcodec=")) format = std.section("vcodec=", 1, 1);
1147                     else if (std.contains(" vcodec=")) format = std.section(" vcodec=", 1, 1);
1148                     if (!format.isEmpty()) {
1149                         format = format.section(' ', 0, 0).toLower();
1150                         if (!vcodecsList.contains(format)) {
1151                             kDebug() << "*****  UNSUPPORTED VCODEC: " << format;
1152                             //sizeItem->setHidden(true);
1153                             //sizeItem->setFlags(Qt::ItemIsSelectable);
1154                             dupItem->setToolTip(i18n("Unsupported video codec: %1", format));
1155                             dupItem->setIcon(brokenIcon);
1156                             dupItem->setForeground(disabled);
1157                         }
1158                     }
1159                 }
1160             }
1161         }
1162     }
1163     // m_view.size_list->sortItems();
1164     focusFirstVisibleItem();
1165     m_view.size_list->blockSignals(false);
1166     m_view.format_list->blockSignals(false);
1167     refreshParams();
1168 }
1169
1170 KUrl RenderWidget::filenameWithExtension(KUrl url, QString extension)
1171 {
1172     if (url.isEmpty()) url = KUrl(m_projectFolder);
1173     QString directory = url.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
1174     QString filename = url.fileName(KUrl::ObeyTrailingSlash);
1175     QString ext;
1176
1177     if (extension.at(0) == '.') ext = extension;
1178     else ext = '.' + extension;
1179
1180     if (filename.isEmpty()) filename = i18n("untitled");
1181
1182     int pos = filename.lastIndexOf('.');
1183     if (pos == 0) filename.append(ext);
1184     else {
1185         if (!filename.endsWith(ext, Qt::CaseInsensitive)) {
1186             filename = filename.left(pos) + ext;
1187         }
1188     }
1189
1190     return KUrl(directory + filename);
1191 }
1192
1193 void RenderWidget::refreshParams()
1194 {
1195     // Format not available (e.g. codec not installed); Disable start button
1196     QListWidgetItem *item = m_view.size_list->currentItem();
1197     if (!item || item->isHidden()) {
1198         m_view.advanced_params->clear();
1199         m_view.buttonRender->setEnabled(false);
1200         m_view.buttonGenerateScript->setEnabled(false);
1201         return;
1202     }
1203     QString params = item->data(ParamsRole).toString();
1204     QString extension = item->data(ExtensionRole).toString();
1205     m_view.advanced_params->setPlainText(params);
1206     QString destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
1207     if (params.contains(" s=") || params.startsWith("s=") || destination == "audioonly") {
1208         // profile has a fixed size, do not allow resize
1209         m_view.rescale->setEnabled(false);
1210         m_view.rescale_box->setEnabled(false);
1211     } else {
1212         m_view.rescale->setEnabled(true);
1213         m_view.rescale_box->setEnabled(m_view.rescale->isChecked());
1214     }
1215     KUrl url = filenameWithExtension(m_view.out_file->url(), extension);
1216     m_view.out_file->setUrl(url);
1217 //     if (!url.isEmpty()) {
1218 //         QString path = url.path();
1219 //         int pos = path.lastIndexOf('.') + 1;
1220 //  if (pos == 0) path.append('.' + extension);
1221 //         else path = path.left(pos) + extension;
1222 //         m_view.out_file->setUrl(KUrl(path));
1223 //     } else {
1224 //         m_view.out_file->setUrl(KUrl(QDir::homePath() + "/untitled." + extension));
1225 //     }
1226     m_view.out_file->setFilter("*." + extension);
1227     QString edit = item->data(EditableRole).toString();
1228     if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) {
1229         m_view.buttonDelete->setEnabled(false);
1230         m_view.buttonEdit->setEnabled(false);
1231     } else {
1232         m_view.buttonDelete->setEnabled(true);
1233         m_view.buttonEdit->setEnabled(true);
1234     }
1235
1236     // setup comboBox with bitrates
1237     m_view.comboBitrates->clear();
1238     if (item->data(BitratesRole).canConvert(QVariant::StringList) && item->data(BitratesRole).toStringList().count()) {
1239         m_view.comboBitrates->setEnabled(true);
1240         QStringList bitrates = item->data(BitratesRole).toStringList();
1241         foreach (QString bitrate, bitrates)
1242             m_view.comboBitrates->addItem(bitrate);
1243         if (item->data(DefaultBitrateRole).canConvert(QVariant::String))
1244             m_view.comboBitrates->setCurrentIndex(bitrates.indexOf(item->data(DefaultBitrateRole).toString()));
1245     } else {
1246         m_view.comboBitrates->setEnabled(false);
1247     }
1248
1249     m_view.buttonRender->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
1250     m_view.buttonGenerateScript->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
1251 }
1252
1253 void RenderWidget::reloadProfiles()
1254 {
1255     parseProfiles();
1256 }
1257
1258 void RenderWidget::parseProfiles(QString meta, QString group, QString profile)
1259 {
1260     m_view.size_list->blockSignals(true);
1261     m_view.format_list->blockSignals(true);
1262     m_view.size_list->clear();
1263     m_view.format_list->clear();
1264     m_view.destination_list->clear();
1265     qDeleteAll(m_renderItems);
1266     qDeleteAll(m_renderCategory);
1267     m_renderItems.clear();
1268     m_renderCategory.clear();
1269     m_view.destination_list->addItem(KIcon("video-x-generic"), i18n("File rendering"));
1270     m_view.destination_list->addItem(KIcon("favorites"), i18n("Favorites"), "favorites");
1271     m_view.destination_list->addItem(KIcon("media-optical"), i18n("DVD"), "dvd");
1272     m_view.destination_list->addItem(KIcon("audio-x-generic"), i18n("Audio only"), "audioonly");
1273     m_view.destination_list->addItem(KIcon("applications-internet"), i18n("Web sites"), "websites");
1274     m_view.destination_list->addItem(KIcon("applications-multimedia"), i18n("Media players"), "mediaplayers");
1275     m_view.destination_list->addItem(KIcon("drive-harddisk"), i18n("Lossless / HQ"), "lossless");
1276     m_view.destination_list->addItem(KIcon("pda"), i18n("Mobile devices"), "mobile");
1277
1278     QString exportFile = KStandardDirs::locate("appdata", "export/profiles.xml");
1279     parseFile(exportFile, false);
1280
1281
1282     QString exportFolder = KStandardDirs::locateLocal("appdata", "export/");
1283     QDir directory = QDir(exportFolder);
1284     QStringList filter;
1285     filter << "*.xml";
1286     QStringList fileList = directory.entryList(filter, QDir::Files);
1287     // We should parse customprofiles.xml in last position, so that user profiles
1288     // can also override profiles installed by KNewStuff
1289     fileList.removeAll("customprofiles.xml");
1290     foreach(const QString &filename, fileList)
1291         parseFile(exportFolder + filename, true);
1292     if (QFile::exists(exportFolder + "customprofiles.xml")) parseFile(exportFolder + "customprofiles.xml", true);
1293
1294     if (!meta.isEmpty()) {
1295         m_view.destination_list->blockSignals(true);
1296         m_view.destination_list->setCurrentIndex(m_view.destination_list->findData(meta));
1297         m_view.destination_list->blockSignals(false);
1298     }
1299     refreshCategory();
1300     QList<QListWidgetItem *> child;
1301     if (!group.isEmpty()) child = m_view.format_list->findItems(group, Qt::MatchExactly);
1302     if (!child.isEmpty()) {
1303         for (int i = 0; i < child.count(); i++) {
1304             if (child.at(i)->data(MetaGroupRole).toString() == meta) {
1305                 m_view.format_list->setCurrentItem(child.at(i));
1306                 break;
1307             }
1308         }
1309     }
1310     child.clear();
1311     m_view.size_list->blockSignals(false);
1312     m_view.format_list->blockSignals(false);
1313     if (!profile.isEmpty()) child = m_view.size_list->findItems(profile, Qt::MatchExactly);
1314     if (!child.isEmpty()) m_view.size_list->setCurrentItem(child.at(0));
1315 }
1316
1317 void RenderWidget::parseFile(QString exportFile, bool editable)
1318 {
1319     kDebug() << "// Parsing file: " << exportFile;
1320     kDebug() << "------------------------------";
1321     QDomDocument doc;
1322     QFile file(exportFile);
1323     doc.setContent(&file, false);
1324     file.close();
1325     QDomElement documentElement;
1326     QDomElement profileElement;
1327     QString extension;
1328     QDomNodeList groups = doc.elementsByTagName("group");
1329     QListWidgetItem *item = NULL;
1330     const QStringList acodecsList = KdenliveSettings::audiocodecs();
1331     bool replaceVorbisCodec = false;
1332     if (acodecsList.contains("libvorbis")) replaceVorbisCodec = true;
1333     bool replaceLibfaacCodec = false;
1334     if (!acodecsList.contains("aac") && acodecsList.contains("libfaac")) replaceLibfaacCodec = true;
1335
1336
1337     if (editable || groups.count() == 0) {
1338         QDomElement profiles = doc.documentElement();
1339         if (editable && profiles.attribute("version", 0).toInt() < 1) {
1340             kDebug() << "// OLD profile version";
1341             // this is an old profile version, update it
1342             QDomDocument newdoc;
1343             QDomElement newprofiles = newdoc.createElement("profiles");
1344             newprofiles.setAttribute("version", 1);
1345             newdoc.appendChild(newprofiles);
1346             QDomNodeList profilelist = doc.elementsByTagName("profile");
1347             for (int i = 0; i < profilelist.count(); i++) {
1348                 QString category = i18nc("Category Name", "Custom");
1349                 QString extension;
1350                 QDomNode parent = profilelist.at(i).parentNode();
1351                 if (!parent.isNull()) {
1352                     QDomElement parentNode = parent.toElement();
1353                     if (parentNode.hasAttribute("name")) category = parentNode.attribute("name");
1354                     extension = parentNode.attribute("extension");
1355                 }
1356                 profilelist.at(i).toElement().setAttribute("category", category);
1357                 if (!extension.isEmpty()) profilelist.at(i).toElement().setAttribute("extension", extension);
1358                 QDomNode n = profilelist.at(i).cloneNode();
1359                 newprofiles.appendChild(newdoc.importNode(n, true));
1360             }
1361             if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
1362                 KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
1363                 return;
1364             }
1365             QTextStream out(&file);
1366             out << newdoc.toString();
1367             file.close();
1368             parseFile(exportFile, editable);
1369             return;
1370         }
1371
1372         QDomNode node = doc.elementsByTagName("profile").at(0);
1373         if (node.isNull()) {
1374             kDebug() << "// Export file: " << exportFile << " IS BROKEN";
1375             return;
1376         }
1377         int count = 1;
1378         while (!node.isNull()) {
1379             QDomElement profile = node.toElement();
1380             QString profileName = profile.attribute("name");
1381             QString standard = profile.attribute("standard");
1382             QString params = profile.attribute("args");
1383
1384             if (replaceVorbisCodec && params.contains("acodec=vorbis")) {
1385                 // replace vorbis with libvorbis
1386                 params = params.replace("vorbis", "libvorbis");
1387             }
1388             if (replaceLibfaacCodec && params.contains("acodec=aac")) {
1389                 // replace libfaac with aac
1390                 params = params.replace("aac", "libfaac");
1391             }
1392
1393             QString category = profile.attribute("category", i18nc("Category Name", "Custom"));
1394             QString dest = profile.attribute("destinationid");
1395             QString prof_extension = profile.attribute("extension");
1396             if (!prof_extension.isEmpty()) extension = prof_extension;
1397             bool exists = false;
1398             for (int j = 0; j < m_renderCategory.count(); j++) {
1399                 if (m_renderCategory.at(j)->text() == category && m_renderCategory.at(j)->data(MetaGroupRole) == dest) {
1400                     exists = true;
1401                     break;
1402                 }
1403             }
1404
1405             if (!exists) {
1406                 QListWidgetItem *itemcat = new QListWidgetItem(category);
1407                 itemcat->setData(MetaGroupRole, dest);
1408                 m_renderCategory.append(itemcat);
1409             }
1410
1411             // Check if item with same name already exists and replace it,
1412             // allowing to override default profiles
1413
1414
1415             for (int j = 0; j < m_renderItems.count(); j++) {
1416                 if (m_renderItems.at(j)->text() == profileName && m_renderItems.at(j)->data(MetaGroupRole) == dest) {
1417                     QListWidgetItem *duplicate = m_renderItems.takeAt(j);
1418                     delete duplicate;
1419                     j--;
1420                 }
1421             }
1422
1423             item = new QListWidgetItem(profileName); // , m_view.size_list
1424             //kDebug() << "// ADDINg item with name: " << profileName << ", GRP" << category << ", DEST:" << dest ;
1425             item->setData(GroupRole, category);
1426             item->setData(MetaGroupRole, dest);
1427             item->setData(ExtensionRole, extension);
1428             item->setData(RenderRole, "avformat");
1429             item->setData(StandardRole, standard);
1430             item->setData(ParamsRole, params);
1431             if (profile.hasAttribute("url")) item->setData(ExtraRole, profile.attribute("url"));
1432             if (editable) {
1433                 item->setData(EditableRole, exportFile);
1434                 if (exportFile.endsWith("customprofiles.xml")) item->setIcon(KIcon("emblem-favorite"));
1435                 else item->setIcon(KIcon("applications-internet"));
1436             }
1437             m_renderItems.append(item);
1438             node = doc.elementsByTagName("profile").at(count);
1439             count++;
1440         }
1441         return;
1442     }
1443
1444     int i = 0;
1445     QString groupName;
1446     QString profileName;
1447
1448     QString prof_extension;
1449     QString renderer;
1450     QString params;
1451     QString standard;
1452     QString twoPass;
1453     QString bitrates, defaultBitrate;
1454     KIcon icon;
1455
1456     while (!groups.item(i).isNull()) {
1457         documentElement = groups.item(i).toElement();
1458         QDomNode gname = documentElement.elementsByTagName("groupname").at(0);
1459         QString metagroupName;
1460         QString metagroupId;
1461         if (!gname.isNull()) {
1462             metagroupName = gname.firstChild().nodeValue();
1463             metagroupId = gname.toElement().attribute("id");
1464
1465             if (!metagroupName.isEmpty() && m_view.destination_list->findData(metagroupId) == -1) {
1466                 if (metagroupId == "dvd") icon = KIcon("media-optical");
1467                 else if (metagroupId == "audioonly") icon = KIcon("audio-x-generic");
1468                 else if (metagroupId == "websites") icon = KIcon("applications-internet");
1469                 else if (metagroupId == "mediaplayers") icon = KIcon("applications-multimedia");
1470                 else if (metagroupId == "lossless") icon = KIcon("drive-harddisk");
1471                 else if (metagroupId == "mobile") icon = KIcon("pda");
1472                 m_view.destination_list->addItem(icon, i18n(metagroupName.toUtf8().data()), metagroupId);
1473             }
1474         }
1475         groupName = documentElement.attribute("name", i18nc("Attribute Name", "Custom"));
1476         extension = documentElement.attribute("extension", QString());
1477         renderer = documentElement.attribute("renderer", QString());
1478         twoPass = documentElement.attribute("twopass", "true");
1479         bool exists = false;
1480         for (int j = 0; j < m_renderCategory.count(); j++) {
1481             if (m_renderCategory.at(j)->text() == groupName && m_renderCategory.at(j)->data(MetaGroupRole) == metagroupId) {
1482                 exists = true;
1483                 break;
1484             }
1485         }
1486         if (!exists) {
1487             QListWidgetItem *itemcat = new QListWidgetItem(groupName); //, m_view.format_list);
1488             itemcat->setData(MetaGroupRole, metagroupId);
1489             itemcat->setData(TwoPassRole, twoPass == "false" ? false : true);
1490             m_renderCategory.append(itemcat);
1491         }
1492
1493         QDomNode n = groups.item(i).firstChild();
1494         while (!n.isNull()) {
1495             if (n.toElement().tagName() != "profile") {
1496                 n = n.nextSibling();
1497                 continue;
1498             }
1499             profileElement = n.toElement();
1500             profileName = profileElement.attribute("name");
1501             standard = profileElement.attribute("standard");
1502             bitrates = profileElement.attribute("bitrates");
1503             defaultBitrate = profileElement.attribute("defaultbitrate");
1504             params = profileElement.attribute("args");
1505
1506             if (replaceVorbisCodec && params.contains("acodec=vorbis")) {
1507                 // replace vorbis with libvorbis
1508                 params = params.replace("vorbis", "libvorbis");
1509             }
1510             if (replaceLibfaacCodec && params.contains("acodec=aac")) {
1511                 // replace libfaac with aac
1512                 params = params.replace("aac", "libfaac");
1513             }
1514
1515             prof_extension = profileElement.attribute("extension");
1516             if (!prof_extension.isEmpty()) extension = prof_extension;
1517             item = new QListWidgetItem(profileName); //, m_view.size_list);
1518             item->setData(GroupRole, groupName);
1519             item->setData(MetaGroupRole, metagroupId);
1520             item->setData(ExtensionRole, extension);
1521             item->setData(RenderRole, renderer);
1522             item->setData(StandardRole, standard);
1523             item->setData(ParamsRole, params);
1524             item->setData(BitratesRole, bitrates.split(',', QString::SkipEmptyParts));
1525             item->setData(DefaultBitrateRole, defaultBitrate);
1526             if (profileElement.hasAttribute("url")) item->setData(ExtraRole, profileElement.attribute("url"));
1527             if (editable) item->setData(EditableRole, exportFile);
1528             m_renderItems.append(item);
1529             n = n.nextSibling();
1530         }
1531
1532         i++;
1533     }
1534 }
1535
1536 void RenderWidget::setRenderJob(const QString &dest, int progress)
1537 {
1538     QTreeWidgetItem *item;
1539     QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
1540     if (!existing.isEmpty()) item = existing.at(0);
1541     else {
1542         item = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
1543         item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1544         if (progress == 0) {
1545             item->setData(1, Qt::UserRole + 2, WAITINGJOB);
1546             item->setIcon(0, KIcon("media-playback-pause"));
1547             item->setData(1, Qt::UserRole, i18n("Waiting..."));
1548         }
1549     }
1550     item->setData(2, Qt::UserRole, progress);
1551     item->setData(1, Qt::UserRole + 2, RUNNINGJOB);
1552     if (progress == 0) {
1553         item->setIcon(0, KIcon("system-run"));
1554         item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1555         item->setData(1, Qt::UserRole + 1, QTime::currentTime());
1556         slotCheckJob();
1557     } else {
1558         QTime startTime = item->data(1, Qt::UserRole + 1).toTime();
1559         int seconds = startTime.secsTo(QTime::currentTime());;
1560         const QString t = i18n("Estimated time %1", QTime().addSecs(seconds * (100 - progress) / progress).toString("hh:mm:ss"));
1561         item->setData(1, Qt::UserRole, t);
1562     }
1563 }
1564
1565 void RenderWidget::setRenderStatus(const QString &dest, int status, const QString &error)
1566 {
1567     QTreeWidgetItem *item;
1568     QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
1569     if (!existing.isEmpty()) item = existing.at(0);
1570     else {
1571         item = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
1572         item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1573     }
1574     item->setData(1, Qt::UserRole + 2, FINISHEDJOB);
1575     if (status == -1) {
1576         // Job finished successfully
1577         item->setIcon(0, KIcon("dialog-ok"));
1578         item->setData(2, Qt::UserRole, 100);
1579         QTime startTime = item->data(1, Qt::UserRole + 1).toTime();
1580         int seconds = startTime.secsTo(QTime::currentTime());
1581         const QTime tm = QTime().addSecs(seconds);
1582         const QString t = i18n("Rendering finished in %1", tm.toString("hh:mm:ss"));
1583         item->setData(1, Qt::UserRole, t);
1584         QString itemGroup = item->data(0, Qt::UserRole).toString();
1585         if (itemGroup == "dvd") {
1586             emit openDvdWizard(item->text(1), item->data(0, Qt::UserRole + 1).toString());
1587         } else if (itemGroup == "websites") {
1588             QString url = item->data(0, Qt::UserRole + 1).toString();
1589             if (!url.isEmpty()) new KRun(url, this);
1590         }
1591     } else if (status == -2) {
1592         // Rendering crashed
1593         item->setData(1, Qt::UserRole, i18n("Rendering crashed"));
1594         item->setIcon(0, KIcon("dialog-close"));
1595         item->setData(2, Qt::UserRole, 100);
1596         m_view.error_log->append(i18n("<strong>Rendering of %1 crashed</strong><br />", dest));
1597         m_view.error_log->append(error);
1598         m_view.error_log->append("<hr />");
1599         m_view.error_box->setVisible(true);
1600     } else if (status == -3) {
1601         // User aborted job
1602         item->setData(1, Qt::UserRole, i18n("Rendering aborted"));
1603         item->setIcon(0, KIcon("dialog-cancel"));
1604         item->setData(2, Qt::UserRole, 100);
1605     }
1606     slotCheckJob();
1607     checkRenderStatus();
1608 }
1609
1610 void RenderWidget::slotAbortCurrentJob()
1611 {
1612     QTreeWidgetItem *current = m_view.running_jobs->currentItem();
1613     if (current) {
1614         if (current->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB)
1615             emit abortProcess(current->text(1));
1616         else {
1617             delete current;
1618             slotCheckJob();
1619             checkRenderStatus();
1620         }
1621     }
1622 }
1623
1624 void RenderWidget::slotStartCurrentJob()
1625 {
1626     QTreeWidgetItem *current = m_view.running_jobs->currentItem();
1627     if (current && current->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB)
1628         startRendering(current);
1629     m_view.start_job->setEnabled(false);
1630 }
1631
1632 void RenderWidget::slotCheckJob()
1633 {
1634     bool activate = false;
1635     QTreeWidgetItem *current = m_view.running_jobs->currentItem();
1636     if (current) {
1637         if (current->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) {
1638             m_view.abort_job->setText(i18n("Abort Job"));
1639             m_view.start_job->setEnabled(false);
1640         } else {
1641             m_view.abort_job->setText(i18n("Remove Job"));
1642             m_view.start_job->setEnabled(current->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB);
1643         }
1644         activate = true;
1645     }
1646     m_view.abort_job->setEnabled(activate);
1647     for (int i = 0; i < m_view.running_jobs->topLevelItemCount(); i++) {
1648         current = m_view.running_jobs->topLevelItem(i);
1649         if (current == m_view.running_jobs->currentItem()) {
1650             current->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 3));
1651         } else current->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1652     }
1653 }
1654
1655 void RenderWidget::slotCLeanUpJobs()
1656 {
1657     int ix = 0;
1658     QTreeWidgetItem *current = m_view.running_jobs->topLevelItem(ix);
1659     while (current) {
1660         if (current->data(1, Qt::UserRole + 2).toInt() == FINISHEDJOB)
1661             delete current;
1662         else ix++;
1663         current = m_view.running_jobs->topLevelItem(ix);
1664     }
1665     slotCheckJob();
1666 }
1667
1668 void RenderWidget::parseScriptFiles()
1669 {
1670     QStringList scriptsFilter;
1671     scriptsFilter << "*.sh";
1672     m_view.scripts_list->clear();
1673
1674     QTreeWidgetItem *item;
1675     // List the project scripts
1676     QStringList scriptFiles = QDir(m_projectFolder + "scripts").entryList(scriptsFilter, QDir::Files);
1677     for (int i = 0; i < scriptFiles.size(); ++i) {
1678         KUrl scriptpath(m_projectFolder + "scripts/" + scriptFiles.at(i));
1679         QString target;
1680         QString renderer;
1681         QString melt;
1682         QFile file(scriptpath.path());
1683         if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
1684             QTextStream stream(&file);
1685             while (!stream.atEnd()) {
1686                 QString line = stream.readLine();
1687                 if (line.startsWith("TARGET=")) {
1688                     target = line.section("TARGET=\"", 1);
1689                     target = target.section('"', 0, 0);
1690                 } else if (line.startsWith("RENDERER=\"")) {
1691                     renderer = line.section("RENDERER=", 1);
1692                     renderer = renderer.section('"', 0, 0);
1693                 } else if (line.startsWith("MELT=\"")) {
1694                     melt = line.section("MELT=", 1);
1695                     melt = melt.section('"', 0, 0);
1696                 }
1697             }
1698             file.close();
1699         }
1700         if (target.isEmpty()) continue;
1701         item = new QTreeWidgetItem(m_view.scripts_list, QStringList() << QString() << scriptpath.fileName());
1702         if (!renderer.isEmpty() && renderer.contains('/') && !QFile::exists(renderer)) {
1703             item->setIcon(0, KIcon("dialog-cancel"));
1704             item->setToolTip(1, i18n("Script contains wrong command: %1", renderer));
1705             item->setData(0, Qt::UserRole, '1');
1706         } else if (!melt.isEmpty() && melt.contains('/') && !QFile::exists(melt)) {
1707             item->setIcon(0, KIcon("dialog-cancel"));
1708             item->setToolTip(1, i18n("Script contains wrong command: %1", melt));
1709             item->setData(0, Qt::UserRole, '1');
1710         } else item->setIcon(0, KIcon("application-x-executable-script"));
1711         item->setSizeHint(0, QSize(m_view.scripts_list->columnWidth(0), fontMetrics().height() * 2));
1712         item->setData(1, Qt::UserRole, KUrl(target).path());
1713         item->setData(1, Qt::UserRole + 1, scriptpath.path());
1714     }
1715     bool activate = false;
1716     QTreeWidgetItem *script = m_view.scripts_list->topLevelItem(0);
1717     if (script) {
1718         m_view.scripts_list->setCurrentItem(script);
1719         script->setSelected(true);
1720         activate = true;
1721     }
1722 //    m_view.start_script->setEnabled(activate);
1723 //    m_view.delete_script->setEnabled(activate);
1724 }
1725
1726 void RenderWidget::slotCheckScript()
1727 {
1728     QTreeWidgetItem *current = m_view.scripts_list->currentItem();
1729     if (current == NULL) return;
1730     m_view.start_script->setEnabled(current->data(0, Qt::UserRole).toString().isEmpty());
1731     m_view.delete_script->setEnabled(true);
1732     for (int i = 0; i < m_view.scripts_list->topLevelItemCount(); i++) {
1733         current = m_view.scripts_list->topLevelItem(i);
1734         if (current == m_view.scripts_list->currentItem()) {
1735             current->setSizeHint(1, QSize(m_view.scripts_list->columnWidth(1), fontMetrics().height() * 3));
1736         } else current->setSizeHint(1, QSize(m_view.scripts_list->columnWidth(1), fontMetrics().height() * 2));
1737     }
1738 }
1739
1740 void RenderWidget::slotStartScript()
1741 {
1742     QTreeWidgetItem *item = m_view.scripts_list->currentItem();
1743     if (item) {
1744         QString destination = item->data(1, Qt::UserRole).toString();
1745         QString path = item->data(1, Qt::UserRole + 1).toString();
1746         // Insert new job in queue
1747         QTreeWidgetItem *renderItem;
1748         QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(destination, Qt::MatchExactly, 1);
1749         kDebug() << "------  START SCRIPT";
1750         if (!existing.isEmpty()) {
1751             renderItem = existing.at(0);
1752             if (renderItem->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) {
1753                 KMessageBox::information(this, i18n("There is already a job writing file:<br /><b>%1</b><br />Abort the job if you want to overwrite it...", destination), i18n("Already running"));
1754                 return;
1755             }
1756         } else renderItem = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << destination << QString());
1757         kDebug() << "------  START SCRIPT 2";
1758         renderItem->setData(2, Qt::UserRole, 0);
1759         renderItem->setData(1, Qt::UserRole + 2, WAITINGJOB);
1760         renderItem->setIcon(0, KIcon("media-playback-pause"));
1761         renderItem->setData(1, Qt::UserRole, i18n("Waiting..."));
1762         renderItem->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1763         renderItem->setData(1, Qt::UserRole + 1, QTime::currentTime());
1764         renderItem->setData(1, Qt::UserRole + 3, path);
1765         renderItem->setData(1, Qt::UserRole + 4, '1');
1766         checkRenderStatus();
1767         m_view.tabWidget->setCurrentIndex(1);
1768     }
1769 }
1770
1771 void RenderWidget::slotDeleteScript()
1772 {
1773     QTreeWidgetItem *item = m_view.scripts_list->currentItem();
1774     if (item) {
1775         QString path = item->data(1, Qt::UserRole + 1).toString();
1776         KIO::NetAccess::del(path + ".mlt", this);
1777         KIO::NetAccess::del(path, this);
1778         parseScriptFiles();
1779     }
1780 }
1781
1782 void RenderWidget::slotGenerateScript()
1783 {
1784     slotPrepareExport(true);
1785 }
1786
1787 void RenderWidget::slotHideLog()
1788 {
1789     m_view.error_box->setVisible(false);
1790 }
1791
1792 void RenderWidget::setRenderProfile(QMap <QString, QString> props)
1793 {
1794     m_view.destination_list->blockSignals(true);
1795     m_view.format_list->blockSignals(true);
1796     m_view.scanning_list->setCurrentIndex(props.value("renderscanning").toInt());
1797     int exportAudio = props.value("renderexportaudio").toInt();
1798     switch (exportAudio) {
1799     case 1:
1800         m_view.export_audio->setCheckState(Qt::Unchecked);
1801         break;
1802     case 2:
1803         m_view.export_audio->setCheckState(Qt::Checked);
1804         break;
1805     default:
1806         m_view.export_audio->setCheckState(Qt::PartiallyChecked);
1807     }
1808     if (props.contains("renderrescale")) m_view.rescale->setChecked(props.value("renderrescale").toInt());
1809     if (props.contains("renderrescalewidth")) m_view.rescale_width->setValue(props.value("renderrescalewidth").toInt());
1810     if (props.contains("renderrescaleheight")) m_view.rescale_height->setValue(props.value("renderrescaleheight").toInt());
1811     if (props.contains("rendertcoverlay")) m_view.tc_overlay->setChecked(props.value("rendertcoverlay").toInt());
1812     if (props.contains("rendertctype")) m_view.tc_type->setCurrentIndex(props.value("rendertctype").toInt());
1813     if (props.contains("renderratio")) m_view.rescale_keep->setChecked(props.value("renderratio").toInt());
1814     if (props.contains("renderplay")) m_view.play_after->setChecked(props.value("renderplay").toInt());
1815     if (props.contains("rendertwopass")) m_view.checkTwoPass->setChecked(props.value("rendertwopass").toInt());
1816
1817     if (props.value("renderzone") == "1") m_view.render_zone->setChecked(true);
1818     else if (props.value("renderguide") == "1") {
1819         m_view.render_guide->setChecked(true);
1820         m_view.guide_start->setCurrentIndex(props.value("renderstartguide").toInt());
1821         m_view.guide_end->setCurrentIndex(props.value("renderendguide").toInt());
1822     } else m_view.render_full->setChecked(true);
1823     slotUpdateGuideBox();
1824
1825     QString url = props.value("renderurl");
1826     if (!url.isEmpty()) m_view.out_file->setUrl(KUrl(url));
1827
1828     // set destination
1829     for (int i = 0; i < m_view.destination_list->count(); i++) {
1830         if (m_view.destination_list->itemData(i, Qt::UserRole) == props.value("renderdestination")) {
1831             m_view.destination_list->setCurrentIndex(i);
1832             break;
1833         }
1834     }
1835     refreshCategory();
1836
1837     // set category
1838     QString group = props.value("rendercategory");
1839     if (!group.isEmpty()) {
1840         QList<QListWidgetItem *> childs = m_view.format_list->findItems(group, Qt::MatchExactly);
1841         if (!childs.isEmpty()) {
1842             m_view.format_list->setCurrentItem(childs.at(0));
1843             m_view.format_list->scrollToItem(childs.at(0));
1844         }
1845         refreshView();
1846     }
1847
1848     // set profile
1849     QList<QListWidgetItem *> childs = m_view.size_list->findItems(props.value("renderprofile"), Qt::MatchExactly);
1850     if (!childs.isEmpty()) {
1851         m_view.size_list->setCurrentItem(childs.at(0));
1852         m_view.size_list->scrollToItem(childs.at(0));
1853     }
1854     //refreshView();
1855     m_view.destination_list->blockSignals(false);
1856     m_view.format_list->blockSignals(false);
1857
1858 }
1859
1860 bool RenderWidget::startWaitingRenderJobs()
1861 {
1862     m_blockProcessing = true;
1863     QString autoscriptFile = getFreeScriptName("auto");
1864     QFile file(autoscriptFile);
1865     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
1866         kWarning() << "//////  ERROR writing to file: " << autoscriptFile;
1867         KMessageBox::error(0, i18n("Cannot write to file %1", autoscriptFile));
1868         return false;
1869     }
1870
1871     QTextStream outStream(&file);
1872     outStream << "#! /bin/sh" << "\n" << "\n";
1873     QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
1874     while (item) {
1875         if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) {
1876             if (item->data(1, Qt::UserRole + 4).isNull()) {
1877                 // Add render process for item
1878                 const QString params = item->data(1, Qt::UserRole + 3).toStringList().join(" ");
1879                 outStream << m_renderer << " " << params << "\n";
1880             } else {
1881                 // Script item
1882                 outStream << item->data(1, Qt::UserRole + 3).toString() << "\n";
1883             }
1884         }
1885         item = m_view.running_jobs->itemBelow(item);
1886     }
1887     // erase itself when rendering is finished
1888     outStream << "rm " << autoscriptFile << "\n" << "\n";
1889     if (file.error() != QFile::NoError) {
1890         KMessageBox::error(0, i18n("Cannot write to file %1", autoscriptFile));
1891         file.close();
1892         m_blockProcessing = false;
1893         return false;
1894     }
1895     file.close();
1896     QFile::setPermissions(autoscriptFile, file.permissions() | QFile::ExeUser);
1897     QProcess::startDetached(autoscriptFile, QStringList());
1898     return true;
1899 }
1900
1901 QString RenderWidget::getFreeScriptName(const QString &prefix)
1902 {
1903     int ix = 0;
1904     QString scriptsFolder = m_projectFolder + "scripts/";
1905     KStandardDirs::makeDir(scriptsFolder);
1906     QString path;
1907     while (path.isEmpty() || QFile::exists(path)) {
1908         ix++;
1909         path = scriptsFolder + prefix + i18n("script") + QString::number(ix).rightJustified(3, '0', false) + ".sh";
1910     }
1911     return path;
1912 }
1913
1914 void RenderWidget::slotPlayRendering(QTreeWidgetItem *item, int)
1915 {
1916     if (KdenliveSettings::defaultplayerapp().isEmpty() || item->data(1, Qt::UserRole + 2).toInt() != FINISHEDJOB) return;
1917     KUrl::List urls;
1918     urls.append(KUrl(item->text(1)));
1919     KRun::run(KdenliveSettings::defaultplayerapp(), urls, this);
1920 }
1921
1922 void RenderWidget::missingClips(bool hasMissing)
1923 {
1924     if (hasMissing) {
1925         m_view.errorLabel->setText(i18n("Check missing clips"));
1926         m_view.errorBox->setHidden(false);
1927     } else m_view.errorBox->setHidden(true);
1928 }
1929
1930 void RenderWidget::slotUpdateEncodeThreads(int val)
1931 {
1932         KdenliveSettings::setEncodethreads(val);
1933 }
1934
1935 void RenderWidget::slotUpdateRescaleWidth(int val)
1936 {
1937     KdenliveSettings::setDefaultrescalewidth(val);
1938     if (!m_view.rescale_keep->isChecked()) return;
1939     m_view.rescale_height->blockSignals(true);
1940     m_view.rescale_height->setValue(val * m_profile.height / m_profile.width  + 0.5);
1941     KdenliveSettings::setDefaultrescaleheight(m_view.rescale_height->value());
1942     m_view.rescale_height->blockSignals(false);
1943 }
1944
1945 void RenderWidget::slotUpdateRescaleHeight(int val)
1946 {
1947     KdenliveSettings::setDefaultrescaleheight(val);
1948     if (!m_view.rescale_keep->isChecked()) return;
1949     m_view.rescale_width->blockSignals(true);
1950     m_view.rescale_width->setValue(val * m_profile.width / m_profile.height + 0.5);
1951     KdenliveSettings::setDefaultrescaleheight(m_view.rescale_width->value());
1952     m_view.rescale_width->blockSignals(false);
1953 }
1954
1955 void RenderWidget::slotSwitchAspectRatio()
1956 {
1957     KdenliveSettings::setRescalekeepratio(m_view.rescale_keep->isChecked());
1958     if (m_view.rescale_keep->isChecked()) slotUpdateRescaleWidth(m_view.rescale_width->value());
1959 }
1960
1961 void RenderWidget::slotUpdateAudioLabel(int ix)
1962 {
1963     if (ix == Qt::PartiallyChecked)
1964         m_view.export_audio->setText(i18n("Export audio (automatic)"));
1965     else
1966         m_view.export_audio->setText(i18n("Export audio"));
1967 }
1968
1969 bool RenderWidget::automaticAudioExport() const
1970 {
1971     return (m_view.export_audio->checkState() == Qt::PartiallyChecked);
1972 }
1973
1974 bool RenderWidget::selectedAudioExport() const
1975 {
1976     return (m_view.export_audio->checkState() != Qt::Unchecked);
1977 }
1978
1979 void RenderWidget::updateProxyConfig(bool enable)
1980 {
1981     m_view.proxy_render->setHidden(!enable);
1982 }
1983
1984 bool RenderWidget::proxyRendering()
1985 {
1986     return m_view.proxy_render->isChecked();
1987 }