]> git.sesse.net Git - kdenlive/blob - src/dvdwizard.cpp
33c568feb3da1d4592079ecc4eae6e2229f2eaa1
[kdenlive] / src / dvdwizard.cpp
1 /***************************************************************************
2  *   Copyright (C) 2007 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 "dvdwizard.h"
22 #include "dvdwizardvob.h"
23 #include "kdenlivesettings.h"
24 #include "profilesdialog.h"
25 #include "timecode.h"
26 #include "monitormanager.h"
27
28 #include <KStandardDirs>
29 #include <KLocale>
30 #include <KFileDialog>
31 #include <kmimetype.h>
32 #include <KIO/NetAccess>
33 #include <KMessageBox>
34
35 #include <QFile>
36 #include <QApplication>
37 #include <QTimer>
38 #include <QDomDocument>
39 #include <QMenu>
40 #include <QGridLayout>
41
42
43 DvdWizard::DvdWizard(MonitorManager *manager, const QString &url, QWidget *parent) :
44     QWizard(parent)
45   , m_dvdauthor(NULL)
46   , m_mkiso(NULL)
47   , m_vobitem(NULL)
48   , m_burnMenu(new QMenu(this))
49 {
50     setWindowTitle(i18n("DVD Wizard"));
51     //setPixmap(QWizard::WatermarkPixmap, QPixmap(KStandardDirs::locate("appdata", "banner.png")));
52     m_pageVob = new DvdWizardVob(this);
53     m_pageVob->setTitle(i18n("Select Files For Your DVD"));
54     addPage(m_pageVob);
55
56     m_pageChapters = new DvdWizardChapters(manager, m_pageVob->dvdFormat(), this);
57     m_pageChapters->setTitle(i18n("DVD Chapters"));
58     addPage(m_pageChapters);
59     
60     if (!url.isEmpty()) m_pageVob->setUrl(url);
61     connect(m_pageVob, SIGNAL(prepareMonitor()), this, SLOT(slotprepareMonitor()));
62
63
64
65     m_pageMenu = new DvdWizardMenu(m_pageVob->dvdFormat(), this);
66     m_pageMenu->setTitle(i18n("Create DVD Menu"));
67     addPage(m_pageMenu);
68
69     QWizardPage *page4 = new QWizardPage;
70     page4->setTitle(i18n("Creating DVD Image"));
71     m_status.setupUi(page4);
72     m_status.error_box->setHidden(true);
73     m_status.error_box->setTabBarHidden(true);
74     m_status.tmp_folder->setUrl(KUrl(KdenliveSettings::currenttmpfolder()));
75     m_status.tmp_folder->setMode(KFile::Directory | KFile::ExistingOnly);
76     m_status.iso_image->setUrl(KUrl(QDir::homePath() + "/untitled.iso"));
77     m_status.iso_image->setFilter("*.iso");
78     m_status.iso_image->setMode(KFile::File);
79     m_status.iso_image->fileDialog()->setOperationMode(KFileDialog::Saving);
80
81 #if KDE_IS_VERSION(4,7,0)
82     m_isoMessage = new KMessageWidget;
83     QGridLayout *s =  static_cast <QGridLayout*> (page4->layout());
84     s->addWidget(m_isoMessage, 5, 0, 1, -1);
85     m_isoMessage->hide();
86 #endif
87
88     addPage(page4);
89
90     connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(slotPageChanged(int)));
91     connect(m_status.button_start, SIGNAL(clicked()), this, SLOT(slotGenerate()));
92     connect(m_status.button_abort, SIGNAL(clicked()), this, SLOT(slotAbort()));
93     connect(m_status.button_preview, SIGNAL(clicked()), this, SLOT(slotPreview()));
94
95     QString programName("k3b");
96     QString exec = KStandardDirs::findExe(programName);
97     if (!exec.isEmpty()) {
98         //Add K3b action
99         QAction *k3b = m_burnMenu->addAction(KIcon(programName), i18n("Burn with %1", programName), this, SLOT(slotBurn()));
100         k3b->setData(exec);
101     }
102     programName = "brasero";
103     exec = KStandardDirs::findExe(programName);
104     if (!exec.isEmpty()) {
105         //Add Brasero action
106         QAction *brasero = m_burnMenu->addAction(KIcon(programName), i18n("Burn with %1", programName), this, SLOT(slotBurn()));
107         brasero->setData(exec);
108     }
109     if (m_burnMenu->isEmpty()) m_burnMenu->addAction(i18n("No burning program found (K3b, Brasero)"));
110     m_status.button_burn->setMenu(m_burnMenu);
111     m_status.button_burn->setIcon(KIcon("tools-media-optical-burn"));
112     m_status.button_burn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
113     m_status.button_preview->setIcon(KIcon("media-playback-start"));
114
115     setButtonText(QWizard::CustomButton1, i18n("Load"));
116     setButtonText(QWizard::CustomButton2, i18n("Save"));
117     button(QWizard::CustomButton1)->setIcon(KIcon("document-open"));
118     button(QWizard::CustomButton2)->setIcon(KIcon("document-save"));
119     connect(button(QWizard::CustomButton1), SIGNAL(clicked()), this, SLOT(slotLoad()));
120     connect(button(QWizard::CustomButton2), SIGNAL(clicked()), this, SLOT(slotSave()));
121     setOption(QWizard::HaveCustomButton1, true);
122     setOption(QWizard::HaveCustomButton2, true);
123     QList<QWizard::WizardButton> layout;
124     layout << QWizard::CustomButton1 << QWizard::CustomButton2 << QWizard::Stretch << QWizard::BackButton << QWizard::NextButton << QWizard::CancelButton << QWizard::FinishButton;
125     setButtonLayout(layout);
126 }
127
128 DvdWizard::~DvdWizard()
129 {
130     m_authorFile.remove();
131     m_menuFile.remove();
132     m_menuVobFile.remove();
133     m_letterboxMovie.remove();
134     m_menuImageBackground.remove();
135     blockSignals(true);
136     delete m_burnMenu;
137     if (m_dvdauthor) {
138         m_dvdauthor->blockSignals(true);
139         m_dvdauthor->close();
140         delete m_dvdauthor;
141     }
142     if (m_mkiso) {
143         m_mkiso->blockSignals(true);
144         m_mkiso->close();
145         delete m_mkiso;
146     }
147 }
148
149
150 void DvdWizard::slotPageChanged(int page)
151 {
152     //kDebug() << "// PAGE CHGD: " << page << ", ID: " << visitedPages();
153     if (page == 0) {
154         // Update chapters that were modified in page 1
155         m_pageChapters->stopMonitor();
156         m_pageVob->updateChapters(m_pageChapters->chaptersData());
157     } else if (page == 1) {
158         m_pageChapters->setVobFiles(m_pageVob->dvdFormat(), m_pageVob->selectedUrls(), m_pageVob->durations(), m_pageVob->chapters());
159         setTitleFormat(Qt::PlainText);
160     } else if (page == 2) {
161         m_pageChapters->stopMonitor();
162         m_pageVob->updateChapters(m_pageChapters->chaptersData());
163         m_pageMenu->setTargets(m_pageChapters->selectedTitles(), m_pageChapters->selectedTargets());
164         m_pageMenu->changeProfile(m_pageVob->dvdFormat());
165     }
166 }
167
168 void DvdWizard::slotprepareMonitor()
169 {
170     m_pageChapters->createMonitor(m_pageVob->dvdFormat());
171 }
172
173 void DvdWizard::generateDvd()
174 {
175 #if KDE_IS_VERSION(4,7,0)
176     m_isoMessage->animatedHide();
177 #endif
178     m_status.error_box->setHidden(true);
179     m_status.error_box->setCurrentIndex(0);
180     m_status.error_box->setTabBarHidden(true);
181     m_status.menu_file->clear();
182     m_status.dvd_file->clear();
183
184     m_selectedImage.setSuffix(".png");
185     //m_selectedImage.setAutoRemove(false);
186     m_selectedImage.open();
187     
188     m_selectedLetterImage.setSuffix(".png");
189     //m_selectedLetterImage.setAutoRemove(false);
190     m_selectedLetterImage.open();
191
192     m_highlightedImage.setSuffix(".png");
193     //m_highlightedImage.setAutoRemove(false);
194     m_highlightedImage.open();
195     
196     m_highlightedLetterImage.setSuffix(".png");
197     //m_highlightedLetterImage.setAutoRemove(false);
198     m_highlightedLetterImage.open();
199
200     m_menuImageBackground.setSuffix(".png");
201     m_menuImageBackground.setAutoRemove(false);
202     m_menuImageBackground.open();
203
204     m_menuVideo.setSuffix(".vob");
205     //m_menuVideo.setAutoRemove(false);
206     m_menuVideo.open();
207
208     m_menuFinalVideo.setSuffix(".vob");
209     //m_menuFinalVideo.setAutoRemove(false);
210     m_menuFinalVideo.open();
211
212     m_letterboxMovie.close();
213     m_letterboxMovie.setSuffix(".mpg");
214     m_letterboxMovie.setAutoRemove(false);
215     m_letterboxMovie.open();
216     
217
218     m_menuFile.close();
219     m_menuFile.setSuffix(".xml");
220     m_menuFile.setAutoRemove(false);
221     m_menuFile.open();
222
223     m_menuVobFile.close();
224     m_menuVobFile.setSuffix(".mpg");
225     m_menuVobFile.setAutoRemove(false);
226     m_menuVobFile.open();
227
228     m_authorFile.close();
229     m_authorFile.setSuffix(".xml");
230     m_authorFile.setAutoRemove(false);
231     m_authorFile.open();
232
233     QListWidgetItem *images =  m_status.job_progress->item(0);
234     m_status.job_progress->setCurrentRow(0);
235     images->setIcon(KIcon("system-run"));
236     m_status.error_log->clear();
237     // initialize html content
238     m_status.error_log->setText("<html></html>");
239
240     if (m_pageMenu->createMenu()) {
241         m_pageMenu->createButtonImages(m_selectedImage.fileName(), m_highlightedImage.fileName(), false);
242         m_pageMenu->createBackgroundImage(m_menuImageBackground.fileName(), false);
243         images->setIcon(KIcon("dialog-ok"));
244         connect(&m_menuJob, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessMenuStatus(int,QProcess::ExitStatus)));
245         //kDebug() << "/// STARTING MLT VOB CREATION: "<<m_selectedImage.fileName()<<m_menuImageBackground.fileName();
246         if (!m_pageMenu->menuMovie()) {
247             // create menu vob file
248             m_vobitem =  m_status.job_progress->item(1);
249             m_status.job_progress->setCurrentRow(1);
250             m_vobitem->setIcon(KIcon("system-run"));
251
252             QStringList args;
253             args << "-profile" << m_pageVob->dvdProfile();
254             args.append(m_menuImageBackground.fileName());
255             args.append("in=0");
256             args.append("out=100");
257             args << "-consumer" << "avformat:" + m_menuVideo.fileName()<<"properties=DVD";
258             m_menuJob.start(KdenliveSettings::rendererpath(), args);
259         } else {
260             // Movie as menu background, do the compositing
261             m_vobitem =  m_status.job_progress->item(1);
262             m_status.job_progress->setCurrentRow(1);
263             m_vobitem->setIcon(KIcon("system-run"));
264
265             int menuLength = m_pageMenu->menuMovieLength();
266             if (menuLength == -1) {
267                 // menu movie is invalid
268                 errorMessage(i18n("Menu movie is invalid"));
269                 m_status.button_start->setEnabled(true);
270                 m_status.button_abort->setEnabled(false);
271                 return;
272             }
273             QStringList args;
274             args.append("-profile");
275             args.append(m_pageVob->dvdProfile());
276             args.append(m_pageMenu->menuMoviePath());
277             args << "-track" << m_menuImageBackground.fileName();
278             args << "out=" + QString::number(menuLength);
279             args << "-transition" << "composite" << "always_active=1";
280             args << "-consumer" << "avformat:" + m_menuFinalVideo.fileName()<<"properties=DVD";
281             m_menuJob.start(KdenliveSettings::rendererpath(), args);
282             //kDebug()<<"// STARTING MENU JOB, image: "<<m_menuImageBackground.fileName()<<"\n-------------";
283         }
284     }
285     else processDvdauthor();
286 }
287
288 void DvdWizard::processSpumux()
289 {
290     kDebug() << "/// STARTING SPUMUX";
291     QMap <QString, QRect> buttons = m_pageMenu->buttonsInfo();
292     QStringList buttonsTarget;
293     // create xml spumux file
294     QListWidgetItem *spuitem =  m_status.job_progress->item(2);
295     m_status.job_progress->setCurrentRow(2);
296     spuitem->setIcon(KIcon("system-run"));
297     QDomDocument doc;
298     QDomElement sub = doc.createElement("subpictures");
299     doc.appendChild(sub);
300     QDomElement stream = doc.createElement("stream");
301     sub.appendChild(stream);
302     QDomElement spu = doc.createElement("spu");
303     stream.appendChild(spu);
304     spu.setAttribute("force", "yes");
305     spu.setAttribute("start", "00:00:00.00");
306     //spu.setAttribute("image", m_menuImage.fileName());
307     spu.setAttribute("select", m_selectedImage.fileName());
308     spu.setAttribute("highlight", m_highlightedImage.fileName());
309     /*spu.setAttribute("autoorder", "rows");*/
310
311     int max = buttons.count() - 1;
312     int i = 0;
313     QMapIterator<QString, QRect> it(buttons);
314     while (it.hasNext()) {
315         it.next();
316         QDomElement but = doc.createElement("button");
317         but.setAttribute("name", 'b' + QString::number(i));
318         if (i < max) but.setAttribute("down", 'b' + QString::number(i + 1));
319         else but.setAttribute("down", "b0");
320         if (i > 0) but.setAttribute("up", 'b' + QString::number(i - 1));
321         else but.setAttribute("up", 'b' + QString::number(max));
322         QRect r = it.value();
323         //int target = it.key();
324         // TODO: solve play all button
325         //if (target == 0) target = 1;
326
327         // We need to make sure that the y coordinate is a multiple of 2, otherwise button may not be displayed
328         buttonsTarget.append(it.key());
329         int y0 = r.y();
330         if (y0 % 2 == 1) y0++;
331         int y1 = r.bottom();
332         if (y1 % 2 == 1) y1--;
333         but.setAttribute("x0", QString::number(r.x()));
334         but.setAttribute("y0", QString::number(y0));
335         but.setAttribute("x1", QString::number(r.right()));
336         but.setAttribute("y1", QString::number(y1));
337         spu.appendChild(but);
338         ++i;
339     }
340
341     QFile data(m_menuFile.fileName());
342     if (data.open(QFile::WriteOnly)) {
343         data.write(doc.toString().toUtf8());
344     }
345     data.close();
346
347     //kDebug() << " SPUMUX DATA: " << doc.toString();
348
349     QStringList args;
350     args << "-s" << "0" << m_menuFile.fileName();
351     //kDebug() << "SPM ARGS: " << args << m_menuVideo.fileName() << m_menuVobFile.fileName();
352
353     QProcess spumux;
354     QString menuMovieUrl;
355
356 #if QT_VERSION >= 0x040600
357     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
358     env.insert("VIDEO_FORMAT", m_pageVob->dvdFormat() == PAL || m_pageVob->dvdFormat() == PAL_WIDE ? "PAL" : "NTSC");
359     spumux.setProcessEnvironment(env);
360 #else
361     QStringList env = QProcess::systemEnvironment();
362     env << QString("VIDEO_FORMAT=") + QString(m_pageVob->dvdFormat() == PAL || m_pageVob->dvdFormat() == PAL_WIDE ? "PAL" : "NTSC");
363     spumux.setEnvironment(env);
364 #endif
365     
366     if (m_pageMenu->menuMovie()) spumux.setStandardInputFile(m_menuFinalVideo.fileName());
367     else spumux.setStandardInputFile(m_menuVideo.fileName());
368     spumux.setStandardOutputFile(m_menuVobFile.fileName());
369     spumux.start("spumux", args);
370     if (spumux.waitForFinished()) {
371         m_status.error_log->append(spumux.readAllStandardError());
372         if (spumux.exitStatus() == QProcess::CrashExit) {
373             //TODO: inform user via messagewidget after string freeze
374             QByteArray result = spumux.readAllStandardError();
375             spuitem->setIcon(KIcon("dialog-close"));
376             m_status.error_log->append(result);
377             m_status.error_box->setHidden(false);
378             m_status.error_box->setTabBarHidden(false);
379             m_status.menu_file->setPlainText(m_menuFile.readAll());
380             m_status.dvd_file->setPlainText(m_authorFile.readAll());
381             m_status.button_start->setEnabled(true);
382             kDebug() << "/// RENDERING SPUMUX MENU crashed";
383             return;
384         }
385     } else {
386         kDebug() << "/// RENDERING SPUMUX MENU timed out";
387         errorMessage(i18n("Rendering job timed out"));
388         spuitem->setIcon(KIcon("dialog-close"));
389         m_status.error_log->append("<a name=\"result\" /><br /><strong>" + i18n("Menu job timed out"));
390         m_status.error_log->scrollToAnchor("result");
391         m_status.error_box->setHidden(false);
392         m_status.error_box->setTabBarHidden(false);
393         m_status.menu_file->setPlainText(m_menuFile.readAll());
394         m_status.dvd_file->setPlainText(m_authorFile.readAll());
395         m_status.button_start->setEnabled(true);
396         return;
397     }
398     if (m_pageVob->dvdFormat() == PAL_WIDE || m_pageVob->dvdFormat() == NTSC_WIDE) {
399         // Second step processing for 16:9 DVD, add letterbox stream
400         m_pageMenu->createButtonImages(m_selectedLetterImage.fileName(), m_highlightedLetterImage.fileName(), true);
401         buttons = m_pageMenu->buttonsInfo(true);
402
403         QDomDocument docLetter;
404         QDomElement subLetter = docLetter.createElement("subpictures");
405         docLetter.appendChild(subLetter);
406         QDomElement streamLetter = docLetter.createElement("stream");
407         subLetter.appendChild(streamLetter);
408         QDomElement spuLetter = docLetter.createElement("spu");
409         streamLetter.appendChild(spuLetter);
410         spuLetter.setAttribute("force", "yes");
411         spuLetter.setAttribute("start", "00:00:00.00");
412         spuLetter.setAttribute("select", m_selectedLetterImage.fileName());
413         spuLetter.setAttribute("highlight", m_highlightedLetterImage.fileName());
414
415         max = buttons.count() - 1;
416         i = 0;
417         QMapIterator<QString, QRect> it2(buttons);
418         while (it2.hasNext()) {
419             it2.next();
420             QDomElement but = docLetter.createElement("button");
421             but.setAttribute("name", 'b' + QString::number(i));
422             if (i < max) but.setAttribute("down", 'b' + QString::number(i + 1));
423             else but.setAttribute("down", "b0");
424             if (i > 0) but.setAttribute("up", 'b' + QString::number(i - 1));
425             else but.setAttribute("up", 'b' + QString::number(max));
426             QRect r = it2.value();
427             // We need to make sure that the y coordinate is a multiple of 2, otherwise button may not be displayed
428             buttonsTarget.append(it2.key());
429             int y0 = r.y();
430             if (y0 % 2 == 1) y0++;
431             int y1 = r.bottom();
432             if (y1 % 2 == 1) y1--;
433             but.setAttribute("x0", QString::number(r.x()));
434             but.setAttribute("y0", QString::number(y0));
435             but.setAttribute("x1", QString::number(r.right()));
436             but.setAttribute("y1", QString::number(y1));
437             spuLetter.appendChild(but);
438             ++i;
439         }
440
441         //kDebug() << " SPUMUX DATA: " << doc.toString();
442
443         if (data.open(QFile::WriteOnly)) {
444             data.write(docLetter.toString().toUtf8());
445         }
446         data.close();
447         spumux.setStandardInputFile(m_menuVobFile.fileName());
448         spumux.setStandardOutputFile(m_letterboxMovie.fileName());
449         args.clear();
450         args << "-s" << "1" << m_menuFile.fileName();
451         spumux.start("spumux", args);
452         //kDebug() << "SPM ARGS LETTERBOX: " << args << m_menuVideo.fileName() << m_letterboxMovie.fileName();
453         if (spumux.waitForFinished()) {
454             m_status.error_log->append(spumux.readAllStandardError());
455             if (spumux.exitStatus() == QProcess::CrashExit) {
456                 //TODO: inform user via messagewidget after string freeze
457                 QByteArray result = spumux.readAllStandardError();
458                 spuitem->setIcon(KIcon("dialog-close"));
459                 m_status.error_log->append(result);
460                 m_status.error_box->setHidden(false);
461                 m_status.error_box->setTabBarHidden(false);
462                 m_status.menu_file->setPlainText(m_menuFile.readAll());
463                 m_status.dvd_file->setPlainText(m_authorFile.readAll());
464                 m_status.button_start->setEnabled(true);
465                 kDebug() << "/// RENDERING SPUMUX MENU crashed";
466                 return;
467             }
468         } else {
469             kDebug() << "/// RENDERING SPUMUX MENU timed out";
470             errorMessage(i18n("Rendering job timed out"));
471             spuitem->setIcon(KIcon("dialog-close"));
472             m_status.error_log->append("<a name=\"result\" /><br /><strong>" + i18n("Menu job timed out"));
473             m_status.error_log->scrollToAnchor("result");
474             m_status.error_box->setHidden(false);
475             m_status.error_box->setTabBarHidden(false);
476             m_status.menu_file->setPlainText(m_menuFile.readAll());
477             m_status.dvd_file->setPlainText(m_authorFile.readAll());
478             m_status.button_start->setEnabled(true);
479             return;
480         }
481         menuMovieUrl = m_letterboxMovie.fileName();
482     }
483     else menuMovieUrl = m_menuVobFile.fileName();
484
485     spuitem->setIcon(KIcon("dialog-ok"));
486     kDebug() << "/// DONE: " << menuMovieUrl;
487     processDvdauthor(menuMovieUrl, buttons, buttonsTarget);
488 }
489
490 void DvdWizard::processDvdauthor(const QString &menuMovieUrl, const QMap <QString, QRect> &buttons, const QStringList &buttonsTarget)
491 {
492     // create dvdauthor xml
493     QListWidgetItem *authitem =  m_status.job_progress->item(3);
494     m_status.job_progress->setCurrentRow(3);
495     authitem->setIcon(KIcon("system-run"));
496     KIO::NetAccess::mkdir(KUrl(m_status.tmp_folder->url().path(KUrl::AddTrailingSlash) + "DVD"), this);
497
498     QDomDocument dvddoc;
499     QDomElement auth = dvddoc.createElement("dvdauthor");
500     auth.setAttribute("dest", m_status.tmp_folder->url().path(KUrl::AddTrailingSlash) + "DVD");
501     dvddoc.appendChild(auth);
502     QDomElement vmgm = dvddoc.createElement("vmgm");
503     auth.appendChild(vmgm);
504
505     if (m_pageMenu->createMenu() && !m_pageVob->introMovie().isEmpty()) {
506         // Use first movie in list as intro movie
507         QDomElement menus = dvddoc.createElement("menus");
508         vmgm.appendChild(menus);
509         QDomElement pgc = dvddoc.createElement("pgc");
510         pgc.setAttribute("entry", "title");
511         menus.appendChild(pgc);
512         QDomElement menuvob = dvddoc.createElement("vob");
513         menuvob.setAttribute("file", m_pageVob->introMovie());
514         pgc.appendChild(menuvob);
515         QDomElement post = dvddoc.createElement("post");
516         QDomText call = dvddoc.createTextNode("jump titleset 1 menu;");
517         post.appendChild(call);
518         pgc.appendChild(post);
519     }
520     QDomElement titleset = dvddoc.createElement("titleset");
521     auth.appendChild(titleset);
522
523     if (m_pageMenu->createMenu()) {
524
525         // DVD main menu
526         QDomElement menus = dvddoc.createElement("menus");
527         titleset.appendChild(menus);
528
529         QDomElement menuvideo = dvddoc.createElement("video");
530         menus.appendChild(menuvideo);
531         switch (m_pageVob->dvdFormat()) {
532         case PAL_WIDE:
533             menuvideo.setAttribute("format", "pal");
534             menuvideo.setAttribute("aspect", "16:9");
535             menuvideo.setAttribute("widescreen", "nopanscan");
536             break;
537         case NTSC_WIDE:
538             menuvideo.setAttribute("format", "ntsc");
539             menuvideo.setAttribute("aspect", "16:9");
540             menuvideo.setAttribute("widescreen", "nopanscan");
541             break;
542         case NTSC:
543             menuvideo.setAttribute("format", "ntsc");
544             menuvideo.setAttribute("aspect", "4:3");
545             break;
546         default:
547             menuvideo.setAttribute("format", "pal");
548             menuvideo.setAttribute("aspect", "4:3");
549             break;
550         }
551
552
553         if (m_pageVob->dvdFormat() == PAL_WIDE || m_pageVob->dvdFormat() == NTSC_WIDE) {
554             // Add letterbox stream info
555             QDomElement subpict = dvddoc.createElement("subpicture");
556             QDomElement stream = dvddoc.createElement("stream");
557             stream.setAttribute("id", "0");
558             stream.setAttribute("mode", "widescreen");
559             subpict.appendChild(stream);
560             QDomElement stream2 = dvddoc.createElement("stream");
561             stream2.setAttribute("id", "1");
562             stream2.setAttribute("mode", "letterbox");
563             subpict.appendChild(stream2);
564             menus.appendChild(subpict);
565         }
566         QDomElement pgc = dvddoc.createElement("pgc");
567         pgc.setAttribute("entry", "root");
568         menus.appendChild(pgc);
569         QDomElement pre = dvddoc.createElement("pre");
570         pgc.appendChild(pre);
571         QDomText nametext = dvddoc.createTextNode("{g1 = 0;}");
572         pre.appendChild(nametext);
573         QDomElement menuvob = dvddoc.createElement("vob");
574         menuvob.setAttribute("file", menuMovieUrl);
575         pgc.appendChild(menuvob);
576         for (int i = 0; i < buttons.count(); ++i) {
577             QDomElement button = dvddoc.createElement("button");
578             button.setAttribute("name", 'b' + QString::number(i));
579             nametext = dvddoc.createTextNode('{' + buttonsTarget.at(i) + ";}");
580             button.appendChild(nametext);
581             pgc.appendChild(button);
582         }
583
584         if (m_pageMenu->loopMovie()) {
585             QDomElement menuloop = dvddoc.createElement("post");
586             nametext = dvddoc.createTextNode("jump titleset 1 menu;");
587             menuloop.appendChild(nametext);
588             pgc.appendChild(menuloop);
589         } else menuvob.setAttribute("pause", "inf");
590
591     }
592
593     QDomElement titles = dvddoc.createElement("titles");
594     titleset.appendChild(titles);
595     QDomElement video = dvddoc.createElement("video");
596     titles.appendChild(video);
597     switch (m_pageVob->dvdFormat()) {
598     case PAL_WIDE:
599         video.setAttribute("format", "pal");
600         video.setAttribute("aspect", "16:9");
601         break;
602     case NTSC_WIDE:
603         video.setAttribute("format", "ntsc");
604         video.setAttribute("aspect", "16:9");
605         break;
606     case NTSC:
607         video.setAttribute("format", "ntsc");
608         video.setAttribute("aspect", "4:3");
609         break;
610     default:
611         video.setAttribute("format", "pal");
612         video.setAttribute("aspect", "4:3");
613         break;
614     }
615
616     QDomElement pgc2;
617     // Get list of clips
618     QStringList voburls = m_pageVob->selectedUrls();
619
620     for (int i = 0; i < voburls.count(); ++i) {
621         if (!voburls.at(i).isEmpty()) {
622             // Add vob entry
623             pgc2 = dvddoc.createElement("pgc");
624             pgc2.setAttribute("pause", 0);
625             titles.appendChild(pgc2);
626             QDomElement vob = dvddoc.createElement("vob");
627             vob.setAttribute("file", voburls.at(i));
628             // Add chapters
629             QStringList chaptersList = m_pageChapters->chapters(i);
630             if (!chaptersList.isEmpty()) vob.setAttribute("chapters", chaptersList.join(","));
631
632             pgc2.appendChild(vob);
633             if (m_pageMenu->createMenu()) {
634                 QDomElement post = dvddoc.createElement("post");
635                 QDomText call;
636                 if (i == voburls.count() - 1) call = dvddoc.createTextNode("{g1 = 0; call menu;}");
637                 else {
638                     call = dvddoc.createTextNode("{if ( g1 eq 999 ) { call menu; } jump title " + QString::number(i + 2).rightJustified(2, '0') + ";}");
639                 }
640                 post.appendChild(call);
641                 pgc2.appendChild(post);
642             }
643         }
644     }
645
646
647     QFile data2(m_authorFile.fileName());
648     if (data2.open(QFile::WriteOnly)) {
649         data2.write(dvddoc.toString().toUtf8());
650     }
651     data2.close();
652     /*kDebug() << "------------------";
653     kDebug() << dvddoc.toString();
654     kDebug() << "------------------";*/
655
656     QStringList args;
657     args << "-x" << m_authorFile.fileName();
658     kDebug() << "// DVDAUTH ARGS: " << args;
659     if (m_dvdauthor) {
660         m_dvdauthor->blockSignals(true);
661         m_dvdauthor->close();
662         delete m_dvdauthor;
663         m_dvdauthor = NULL;
664     }
665     m_creationLog.clear();
666     m_dvdauthor = new QProcess(this);
667     // Set VIDEO_FORMAT variable (required by dvdauthor 0.7)
668 #if QT_VERSION >= 0x040600
669     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
670     env.insert("VIDEO_FORMAT", m_pageVob->dvdFormat() == PAL || m_pageVob->dvdFormat() == PAL_WIDE ? "PAL" : "NTSC");
671     m_dvdauthor->setProcessEnvironment(env);
672 #else
673     QStringList env = QProcess::systemEnvironment();
674     env << QString("VIDEO_FORMAT=") + QString(m_pageVob->dvdFormat() == PAL || m_pageVob->dvdFormat() == PAL_WIDE ? "PAL" : "NTSC");
675     m_dvdauthor->setEnvironment(env);
676 #endif
677     connect(m_dvdauthor, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotRenderFinished(int,QProcess::ExitStatus)));
678     connect(m_dvdauthor, SIGNAL(readyReadStandardOutput()), this, SLOT(slotShowRenderInfo()));
679     m_dvdauthor->setProcessChannelMode(QProcess::MergedChannels);
680     m_dvdauthor->start("dvdauthor", args);
681     m_status.button_abort->setEnabled(true);
682     button(QWizard::FinishButton)->setEnabled(false);
683 }
684
685 void DvdWizard::slotProcessMenuStatus(int, QProcess::ExitStatus status)
686 {
687     if (status == QProcess::CrashExit) {
688         kDebug() << "/// RENDERING MENU vob crashed";
689         errorMessage(i18n("Rendering menu crashed"));
690         QByteArray result = m_menuJob.readAllStandardError();
691         if (m_vobitem) m_vobitem->setIcon(KIcon("dialog-close"));
692         m_status.error_log->append(result);
693         m_status.error_box->setHidden(false);
694         m_status.button_start->setEnabled(true);
695         m_status.button_abort->setEnabled(false);
696         return;
697     }
698     if (m_vobitem) m_vobitem->setIcon(KIcon("dialog-ok"));
699     processSpumux();
700 }
701
702 void DvdWizard::slotShowRenderInfo()
703 {
704     QString log = QString(m_dvdauthor->readAll());
705     m_status.error_log->append(log);
706     m_status.error_box->setHidden(false);
707 }
708
709 void DvdWizard::errorMessage(const QString &text) {
710 #if KDE_IS_VERSION(4,7,0)
711     m_isoMessage->setText(text);
712     m_isoMessage->setMessageType(KMessageWidget::Error);
713     m_isoMessage->animatedShow();
714 #endif
715 }
716
717 void DvdWizard::infoMessage(const QString &text) {
718 #if KDE_IS_VERSION(4,7,0)
719     m_isoMessage->setText(text);
720     m_isoMessage->setMessageType(KMessageWidget::Positive);
721     m_isoMessage->animatedShow();
722 #endif
723 }
724
725 void DvdWizard::slotRenderFinished(int exitCode, QProcess::ExitStatus status)
726 {
727     QListWidgetItem *authitem =  m_status.job_progress->item(3);
728     if (status == QProcess::CrashExit || exitCode != 0) {
729         errorMessage(i18n("DVDAuthor process crashed"));
730         QString result(m_dvdauthor->readAllStandardError());
731         result.append("<a name=\"result\" /><br /><strong>");
732         result.append(i18n("DVDAuthor process crashed.</strong><br />"));
733         m_status.error_log->append(result);
734         m_status.error_log->scrollToAnchor("result");
735         m_status.error_box->setHidden(false);
736         m_status.error_box->setTabBarHidden(false);
737         m_status.menu_file->setPlainText(m_menuFile.readAll());
738         m_status.dvd_file->setPlainText(m_authorFile.readAll());
739         kDebug() << "DVDAuthor process crashed";
740         authitem->setIcon(KIcon("dialog-close"));
741         m_dvdauthor->close();
742         delete m_dvdauthor;
743         m_dvdauthor = NULL;
744         m_status.button_start->setEnabled(true);
745         m_status.button_abort->setEnabled(false);
746         cleanup();
747         button(QWizard::FinishButton)->setEnabled(true);
748         return;
749     }
750     m_creationLog.append(m_dvdauthor->readAllStandardError());
751     m_dvdauthor->close();
752     delete m_dvdauthor;
753     m_dvdauthor = NULL;
754
755     // Check if DVD structure has the necessary infos
756     if (!QFile::exists(m_status.tmp_folder->url().path() + "/DVD/VIDEO_TS/VIDEO_TS.IFO")) {
757         errorMessage(i18n("DVD structure broken"));
758         m_status.error_log->append(m_creationLog + "<a name=\"result\" /><br /><strong>" + i18n("DVD structure broken"));
759         m_status.error_log->scrollToAnchor("result");
760         m_status.error_box->setHidden(false);
761         m_status.error_box->setTabBarHidden(false);
762         m_status.menu_file->setPlainText(m_menuFile.readAll());
763         m_status.dvd_file->setPlainText(m_authorFile.readAll());
764         kDebug() << "DVDAuthor process crashed";
765         authitem->setIcon(KIcon("dialog-close"));
766         m_status.button_start->setEnabled(true);
767         m_status.button_abort->setEnabled(false);
768         cleanup();
769         button(QWizard::FinishButton)->setEnabled(true);
770         return;
771     }
772     authitem->setIcon(KIcon("dialog-ok"));
773     QStringList args;
774     args << "-dvd-video" << "-v" << "-o" << m_status.iso_image->url().path() << m_status.tmp_folder->url().path(KUrl::AddTrailingSlash) + "DVD";
775
776     if (m_mkiso) {
777         m_mkiso->blockSignals(true);
778         m_mkiso->close();
779         delete m_mkiso;
780         m_mkiso = NULL;
781     }
782     m_mkiso = new QProcess(this);
783     connect(m_mkiso, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotIsoFinished(int,QProcess::ExitStatus)));
784     connect(m_mkiso, SIGNAL(readyReadStandardOutput()), this, SLOT(slotShowIsoInfo()));
785     m_mkiso->setProcessChannelMode(QProcess::MergedChannels);
786     QListWidgetItem *isoitem =  m_status.job_progress->item(4);
787     m_status.job_progress->setCurrentRow(4);
788     isoitem->setIcon(KIcon("system-run"));
789     if (!KStandardDirs::findExe("genisoimage").isEmpty()) m_mkiso->start("genisoimage", args);
790     else m_mkiso->start("mkisofs", args);
791
792 }
793
794 void DvdWizard::slotShowIsoInfo()
795 {
796     QString log = QString(m_mkiso->readAll());
797     m_status.error_log->append(log);
798     m_status.error_box->setHidden(false);
799 }
800
801 void DvdWizard::slotIsoFinished(int exitCode, QProcess::ExitStatus status)
802 {
803     button(QWizard::FinishButton)->setEnabled(true);
804     QListWidgetItem *isoitem =  m_status.job_progress->item(4);
805     if (status == QProcess::CrashExit || exitCode != 0) {
806         errorMessage(i18n("ISO creation process crashed."));
807         QString result(m_mkiso->readAllStandardError());
808         result.append("<a name=\"result\" /><br /><strong>");
809         result.append(i18n("ISO creation process crashed."));
810         m_status.error_log->append(result);
811         m_status.error_log->scrollToAnchor("result");
812         m_status.error_box->setHidden(false);
813         m_status.error_box->setTabBarHidden(false);
814         m_status.menu_file->setPlainText(m_menuFile.readAll());
815         m_status.dvd_file->setPlainText(m_authorFile.readAll());
816         m_mkiso->close();
817         delete m_mkiso;
818         m_mkiso = NULL;
819         cleanup();
820         kDebug() << "Iso process crashed";
821         isoitem->setIcon(KIcon("dialog-close"));
822         m_status.button_start->setEnabled(true);
823         m_status.button_abort->setEnabled(false);
824         return;
825     }
826
827     m_creationLog.append(m_mkiso->readAllStandardError());
828     delete m_mkiso;
829     m_mkiso = NULL;
830     m_status.button_start->setEnabled(true);
831     m_status.button_abort->setEnabled(false);
832
833     // Check if DVD iso is ok
834     QFile iso(m_status.iso_image->url().path());
835     if (!iso.exists() || iso.size() == 0) {
836         if (iso.exists()) {
837             KIO::NetAccess::del(m_status.iso_image->url(), this);
838         }
839         errorMessage(i18n("DVD ISO is broken"));
840         m_status.error_log->append(m_creationLog + "<br /><a name=\"result\" /><strong>" + i18n("DVD ISO is broken") + "</strong>");
841         m_status.error_log->scrollToAnchor("result");
842         m_status.error_box->setHidden(false);
843         m_status.error_box->setTabBarHidden(false);
844         m_status.menu_file->setPlainText(m_menuFile.readAll());
845         m_status.dvd_file->setPlainText(m_authorFile.readAll());
846         isoitem->setIcon(KIcon("dialog-close"));
847         cleanup();
848         return;
849     }
850
851     isoitem->setIcon(KIcon("dialog-ok"));
852     kDebug() << "ISO IMAGE " << m_status.iso_image->url().path() << " Successfully created";
853     cleanup();
854     kDebug() << m_creationLog;
855     infoMessage(i18n("DVD ISO image %1 successfully created.", m_status.iso_image->url().path()));
856
857     m_status.error_log->append("<a name=\"result\" /><strong>" + i18n("DVD ISO image %1 successfully created.", m_status.iso_image->url().path()) + "</strong>");
858     m_status.error_log->scrollToAnchor("result");
859     m_status.button_preview->setEnabled(true);
860     m_status.button_burn->setEnabled(true);
861     m_status.error_box->setHidden(false);
862     //KMessageBox::information(this, i18n("DVD ISO image %1 successfully created.", m_status.iso_image->url().path()));
863
864 }
865
866
867 void DvdWizard::cleanup()
868 {
869     KIO::NetAccess::del(KUrl(m_status.tmp_folder->url().path(KUrl::AddTrailingSlash) + "DVD"), this);
870 }
871
872
873 void DvdWizard::slotPreview()
874 {
875     QStringList programNames;
876     programNames << "xine" << "vlc";
877     QString exec;
878     foreach(const QString &prog, programNames) {
879         exec = KStandardDirs::findExe(prog);
880         if (!exec.isEmpty()) {
881             break;
882         }
883     }
884     if (exec.isEmpty()) {
885         KMessageBox::sorry(this, i18n("Previewing requires one of these applications (%1)", programNames.join(",")));
886     }
887     else QProcess::startDetached(exec, QStringList() << "dvd://" + m_status.iso_image->url().path());
888 }
889
890 void DvdWizard::slotBurn()
891 {
892     QAction *action = qobject_cast<QAction *>(sender());
893     QString exec = action->data().toString();
894     QStringList args;
895     if (exec.endsWith("k3b")) args << "--image" << m_status.iso_image->url().path();
896     else args << "--image=" + m_status.iso_image->url().path();
897     QProcess::startDetached(exec, args);
898 }
899
900
901 void DvdWizard::slotGenerate()
902 {
903     // clear job icons
904     if ((m_dvdauthor && m_dvdauthor->state() != QProcess::NotRunning) || (m_mkiso && m_mkiso->state() != QProcess::NotRunning)) return;
905     for (int i = 0; i < m_status.job_progress->count(); ++i)
906         m_status.job_progress->item(i)->setIcon(KIcon());
907     QString warnMessage;
908     if (KIO::NetAccess::exists(KUrl(m_status.tmp_folder->url().path(KUrl::AddTrailingSlash) + "DVD"), KIO::NetAccess::SourceSide, this))
909         warnMessage.append(i18n("Folder %1 already exists. Overwrite?\n", m_status.tmp_folder->url().path(KUrl::AddTrailingSlash) + "DVD"));
910     if (KIO::NetAccess::exists(KUrl(m_status.iso_image->url().path()), KIO::NetAccess::SourceSide, this))
911         warnMessage.append(i18n("Image file %1 already exists. Overwrite?", m_status.iso_image->url().path()));
912
913     if (warnMessage.isEmpty() || KMessageBox::questionYesNo(this, warnMessage) == KMessageBox::Yes) {
914         KIO::NetAccess::del(KUrl(m_status.tmp_folder->url().path(KUrl::AddTrailingSlash) + "DVD"), this);
915         QTimer::singleShot(300, this, SLOT(generateDvd()));
916         m_status.button_preview->setEnabled(false);
917         m_status.button_burn->setEnabled(false);
918         m_status.job_progress->setEnabled(true);
919         m_status.button_start->setEnabled(false);
920     }
921 }
922
923 void DvdWizard::slotAbort()
924 {
925     // clear job icons
926     if (m_dvdauthor && m_dvdauthor->state() != QProcess::NotRunning) m_dvdauthor->terminate();
927     else if (m_mkiso && m_mkiso->state() != QProcess::NotRunning) m_mkiso->terminate();
928 }
929
930 void DvdWizard::slotSave()
931 {
932     KUrl url = KFileDialog::getSaveUrl(KUrl("kfiledialog:///projectfolder"), "*.kdvd", this, i18n("Save DVD Project"));
933     if (url.isEmpty())
934         return;
935
936     if (currentId() == 0)
937         m_pageChapters->setVobFiles(m_pageVob->dvdFormat(), m_pageVob->selectedUrls(), m_pageVob->durations(), m_pageVob->chapters());
938
939     QDomDocument doc;
940     QDomElement dvdproject = doc.createElement("dvdproject");
941     dvdproject.setAttribute("profile", m_pageVob->dvdProfile());
942     dvdproject.setAttribute("tmp_folder", m_status.tmp_folder->url().path());
943     dvdproject.setAttribute("iso_image", m_status.iso_image->url().path());
944     dvdproject.setAttribute("intro_movie", m_pageVob->introMovie());
945
946     doc.appendChild(dvdproject);
947     QDomElement menu = m_pageMenu->toXml();
948     if (!menu.isNull()) dvdproject.appendChild(doc.importNode(menu, true));
949     QDomElement chaps = m_pageChapters->toXml();
950     if (!chaps.isNull()) dvdproject.appendChild(doc.importNode(chaps, true));
951
952     QFile file(url.path());
953     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
954         kWarning() << "//////  ERROR writing to file: " << url.path();
955         KMessageBox::error(this, i18n("Cannot write to file %1", url.path()));
956         return;
957     }
958
959     file.write(doc.toString().toUtf8());
960     if (file.error() != QFile::NoError) {
961         KMessageBox::error(this, i18n("Cannot write to file %1", url.path()));
962     }
963     file.close();
964 }
965
966
967 void DvdWizard::slotLoad()
968 {
969     const KUrl url = KFileDialog::getOpenUrl(KUrl("kfiledialog:///projectfolder"), "*.kdvd");
970     if (url.isEmpty())
971         return;
972     QDomDocument doc;
973     QFile file(url.path());
974     doc.setContent(&file, false);
975     file.close();
976     QDomElement dvdproject = doc.documentElement();
977     if (dvdproject.tagName() != "dvdproject") {
978         KMessageBox::error(this, i18n("File %1 is not a Kdenlive project file.", url.path()));
979         return;
980     }
981
982     QString profile = dvdproject.attribute("profile");
983     m_pageVob->setProfile(profile);
984     m_pageVob->clear();
985     m_status.tmp_folder->setUrl(KUrl(dvdproject.attribute("tmp_folder")));
986     m_status.iso_image->setUrl(KUrl(dvdproject.attribute("iso_image")));
987     QString intro = dvdproject.attribute("intro_movie");
988     if (!intro.isEmpty()) {
989         m_pageVob->slotAddVobFile(KUrl(intro));
990         m_pageVob->setUseIntroMovie(true);
991     }
992
993     QDomNodeList vobs = doc.elementsByTagName("vob");
994     for (int i = 0; i < vobs.count(); ++i) {
995         QDomElement e = vobs.at(i).toElement();
996         m_pageVob->slotAddVobFile(KUrl(e.attribute("file")), e.attribute("chapters"));
997     }
998     m_pageMenu->loadXml(m_pageVob->dvdFormat(), dvdproject.firstChildElement("menu"));
999 }
1000
1001 #include "dvdwizard.moc"