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