]> git.sesse.net Git - kdenlive/blob - src/dvdwizard.cpp
Allow dvd burning with K3b or Brasero
[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 "kdenlivesettings.h"
23 #include "profilesdialog.h"
24
25 #include <KStandardDirs>
26 #include <KLocale>
27 #include <KFileDialog>
28 #include <kmimetype.h>
29 #include <KIO/NetAccess>
30 #include <KMessageBox>
31
32 #include <QFile>
33 #include <QApplication>
34 #include <QTimer>
35 #include <QDomDocument>
36 #include <QMenu>
37
38
39 DvdWizard::DvdWizard(const QString &url, const QString &profile, QWidget *parent) :
40         QWizard(parent),
41         m_dvdauthor(NULL),
42         m_mkiso(NULL),
43         m_burnMenu(new QMenu(this))
44 {
45     //setPixmap(QWizard::WatermarkPixmap, QPixmap(KStandardDirs::locate("appdata", "banner.png")));
46     setAttribute(Qt::WA_DeleteOnClose);
47     m_pageVob = new DvdWizardVob(this);
48     m_pageVob->setTitle(i18n("Select Files For Your DVD"));
49     addPage(m_pageVob);
50
51     if (!url.isEmpty()) m_pageVob->setUrl(url);
52
53     m_pageMenu = new DvdWizardMenu(profile, this);
54     m_pageMenu->setTitle(i18n("Create DVD Menu"));
55     addPage(m_pageMenu);
56
57
58     QWizardPage *page3 = new QWizardPage;
59     page3->setTitle(i18n("DVD Image"));
60     m_iso.setupUi(page3);
61     m_iso.tmp_folder->setPath(KdenliveSettings::currenttmpfolder());
62     m_iso.iso_image->setPath(QDir::homePath() + "/untitled.iso");
63     m_iso.iso_image->setFilter("*.iso");
64     m_iso.iso_image->fileDialog()->setOperationMode(KFileDialog::Saving);
65     addPage(page3);
66
67     QWizardPage *page4 = new QWizardPage;
68     page4->setTitle(i18n("Creating DVD Image"));
69     m_status.setupUi(page4);
70     m_status.error_box->setHidden(true);
71     addPage(page4);
72
73     connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(slotPageChanged(int)));
74
75     connect(m_status.button_preview, SIGNAL(clicked()), this, SLOT(slotPreview()));
76
77     QString exec = KStandardDirs::findExe("k3b");
78     if (!exec.isEmpty()) {
79         //Add K3b action
80         QAction *k3b = m_burnMenu->addAction(KIcon("k3b"), i18n("Burn with %1", "K3b"), this, SLOT(slotBurn()));
81         k3b->setData(exec);
82     }
83     exec = KStandardDirs::findExe("brasero");
84     if (!exec.isEmpty()) {
85         //Add Brasero action
86         QAction *brasero = m_burnMenu->addAction(KIcon("brasero"), i18n("Burn with %1", "Brasero"), this, SLOT(slotBurn()));
87         brasero->setData(exec);
88     }
89     if (m_burnMenu->isEmpty()) m_burnMenu->addAction(i18n("No burning program found (K3b, Brasero)"));
90     m_status.button_burn->setMenu(m_burnMenu);
91     m_status.button_burn->setIcon(KIcon("tools-media-optical-burn"));
92     m_status.button_burn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
93     m_status.button_preview->setIcon(KIcon("media-playback-start"));
94
95 }
96
97 DvdWizard::~DvdWizard()
98 {
99     // m_menuFile.remove();
100     delete m_burnMenu;
101     if (m_dvdauthor) {
102         m_dvdauthor->close();
103         delete m_dvdauthor;
104     }
105     if (m_mkiso) {
106         m_mkiso->close();
107         delete m_mkiso;
108     }
109 }
110
111
112 void DvdWizard::slotPageChanged(int page)
113 {
114     kDebug() << "// PAGE CHGD: " << page;
115     if (page == 1) {
116         m_pageMenu->setTargets(m_pageVob->selectedUrls());
117     } else if (page == 2) {
118         m_pageMenu->buttonsInfo();
119     } else if (page == 3) {
120         // clear job icons
121         for (int i = 0; i < m_status.job_progress->count(); i++)
122             m_status.job_progress->item(i)->setIcon(KIcon());
123         QString warnMessage;
124         if (KIO::NetAccess::exists(KUrl(m_iso.tmp_folder->url().path() + "/DVD"), KIO::NetAccess::SourceSide, this))
125             warnMessage.append(i18n("Folder %1 already exists. Overwrite?" + '\n', m_iso.tmp_folder->url().path() + "/DVD"));
126         if (KIO::NetAccess::exists(KUrl(m_iso.iso_image->url().path()), KIO::NetAccess::SourceSide, this))
127             warnMessage.append(i18n("Image file %1 already exists. Overwrite?", m_iso.iso_image->url().path()));
128
129         if (!warnMessage.isEmpty() && KMessageBox::questionYesNo(this, warnMessage) == KMessageBox::No) {
130             back();
131         } else {
132             KIO::NetAccess::del(KUrl(m_iso.tmp_folder->url().path() + "/DVD"), this);
133             QTimer::singleShot(300, this, SLOT(generateDvd()));
134         }
135         m_status.button_preview->setEnabled(false);
136         m_status.button_burn->setEnabled(false);
137     }
138 }
139
140
141
142 void DvdWizard::generateDvd()
143 {
144     KTemporaryFile temp1;
145     temp1.setSuffix(".png");
146     //temp1.setAutoRemove(false);
147     temp1.open();
148
149     KTemporaryFile temp2;
150     temp2.setSuffix(".png");
151     //temp2.setAutoRemove(false);
152     temp2.open();
153
154     KTemporaryFile temp3;
155     temp3.setSuffix(".png");
156     //temp3.setAutoRemove(false);
157     temp3.open();
158
159     KTemporaryFile temp4;
160     temp4.setSuffix(".png");
161     //temp4.setAutoRemove(false);
162     temp4.open();
163
164     KTemporaryFile temp5;
165     temp5.setSuffix(".vob");
166     //temp5.setAutoRemove(false);
167     temp5.open();
168
169     KTemporaryFile temp6;
170     temp6.setSuffix(".xml");
171     //temp6.setAutoRemove(false);
172     temp6.open();
173
174     m_menuFile.setSuffix(".mpg");
175     m_menuFile.setAutoRemove(false);
176     m_menuFile.open();
177
178     m_authorFile.setSuffix(".xml");
179     m_authorFile.setAutoRemove(false);
180     m_authorFile.open();
181
182     QListWidgetItem *images =  m_status.job_progress->item(0);
183     images->setIcon(KIcon("system-run"));
184     qApp->processEvents();
185     QMap <int, QRect> buttons = m_pageMenu->buttonsInfo();
186     QStringList buttonsTarget;
187
188     if (m_pageMenu->createMenu()) {
189         m_pageMenu->createButtonImages(temp1.fileName(), temp2.fileName(), temp3.fileName());
190         m_pageMenu->createBackgroundImage(temp4.fileName());
191
192
193         images->setIcon(KIcon("dialog-ok"));
194
195         kDebug() << "/// STARTING MLT VOB CREATION";
196         if (!m_pageMenu->menuMovie()) {
197             // create menu vob file
198             QListWidgetItem *vobitem =  m_status.job_progress->item(1);
199             vobitem->setIcon(KIcon("system-run"));
200             qApp->processEvents();
201
202             QStringList args;
203             args.append("-profile");
204             if (m_pageMenu->isPalMenu()) args.append("dv_pal");
205             else  args.append("dv_ntsc");
206             args.append(temp4.fileName());
207             args.append("in=0");
208             args.append("out=100");
209             args << "-consumer" << "avformat:" + temp5.fileName();
210             if (m_pageMenu->isPalMenu()) {
211                 args << "f=dvd" << "vcodec=mpeg2video" << "acodec=ac3" << "b=5000k" << "maxrate=8000k" << "minrate=0" << "bufsize=1835008" << "mux_packet_s=2048" << "mux_rate=10080000" << "ab=192k" << "ar=48000" << "s=720x576" << "g=15" << "me_range=63" << "trellis=1" << "profile=dv_pal";
212             } else {
213                 args << "f=dvd" << "vcodec=mpeg2video" << "acodec=ac3" << "b=6000k" << "maxrate=9000k" << "minrate=0" << "bufsize=1835008" << "mux_packet_s=2048" << "mux_rate=10080000" << "ab=192k" << "ar=48000" << "s=720x480" << "g=18" << "me_range=63" << "trellis=1" << "profile=dv_ntsc";
214             }
215
216             kDebug() << "MLT ARGS: " << args;
217             QProcess renderbg;
218             renderbg.start(KdenliveSettings::rendererpath(), args);
219             if (renderbg.waitForFinished()) {
220                 if (renderbg.exitStatus() == QProcess::CrashExit) {
221                     kDebug() << "/// RENDERING MENU vob crashed";
222                     QByteArray result = renderbg.readAllStandardError();
223                     vobitem->setIcon(KIcon("dialog-close"));
224                     m_status.error_log->setText(result);
225                     m_status.error_box->setHidden(false);
226                     return;
227                 }
228             } else {
229                 kDebug() << "/// RENDERING MENU vob timed out";
230                 vobitem->setIcon(KIcon("dialog-close"));
231                 m_status.error_log->setText(i18n("Rendering job timed out"));
232                 m_status.error_box->setHidden(false);
233                 return;
234             }
235             vobitem->setIcon(KIcon("dialog-ok"));
236         }
237         kDebug() << "/// STARTING SPUMUX";
238
239         // create xml spumux file
240         QListWidgetItem *spuitem =  m_status.job_progress->item(2);
241         spuitem->setIcon(KIcon("system-run"));
242         qApp->processEvents();
243         QDomDocument doc;
244         QDomElement sub = doc.createElement("subpictures");
245         doc.appendChild(sub);
246         QDomElement stream = doc.createElement("stream");
247         sub.appendChild(stream);
248         QDomElement spu = doc.createElement("spu");
249         stream.appendChild(spu);
250         spu.setAttribute("force", "yes");
251         spu.setAttribute("start", "00:00:00.00");
252         spu.setAttribute("image", temp1.fileName());
253         spu.setAttribute("select", temp2.fileName());
254         spu.setAttribute("highlight", temp3.fileName());
255         /*spu.setAttribute("autooutline", "infer");
256         spu.setAttribute("outlinewidth", "12");
257         spu.setAttribute("autoorder", "rows");*/
258
259         int max = buttons.count() - 1;
260         int i = 0;
261         QMapIterator<int, QRect> it(buttons);
262         while (it.hasNext()) {
263             it.next();
264             QDomElement but = doc.createElement("button");
265             but.setAttribute("name", 'b' + QString::number(i));
266             if (i < max) but.setAttribute("down", 'b' + QString::number(i + 1));
267             else but.setAttribute("down", "b0");
268             if (i > 0) but.setAttribute("up", 'b' + QString::number(i - 1));
269             else but.setAttribute("up", 'b' + QString::number(max));
270             QRect r = it.value();
271             int target = it.key();
272             // TODO: solve play all button
273             if (target == 0) target = 1;
274             buttonsTarget.append(QString::number(target));
275             but.setAttribute("x0", QString::number(r.x()));
276             but.setAttribute("y0", QString::number(r.y()));
277             but.setAttribute("x1", QString::number(r.right()));
278             but.setAttribute("y1", QString::number(r.bottom()));
279             spu.appendChild(but);
280             i++;
281         }
282
283         QFile data(temp6.fileName());
284         if (data.open(QFile::WriteOnly)) {
285             data.write(doc.toString().toUtf8());
286         }
287         data.close();
288
289         kDebug() << " SPUMUX DATA: " << doc.toString();
290
291         QStringList args;
292         args.append(temp6.fileName());
293         kDebug() << "SPM ARGS: " << args << temp5.fileName() << m_menuFile.fileName();
294
295         QProcess spumux;
296
297         if (m_pageMenu->menuMovie()) spumux.setStandardInputFile(m_pageMenu->menuMoviePath());
298         else spumux.setStandardInputFile(temp5.fileName());
299         spumux.setStandardOutputFile(m_menuFile.fileName());
300         spumux.start("spumux", args);
301         spumux.setProcessChannelMode(QProcess::MergedChannels);
302         if (spumux.waitForFinished()) {
303             kDebug() << QString(spumux.readAll()).simplified();
304             if (spumux.exitStatus() == QProcess::CrashExit) {
305                 QByteArray result = spumux.readAllStandardError();
306                 spuitem->setIcon(KIcon("dialog-close"));
307                 m_status.error_log->setText(result);
308                 m_status.error_box->setHidden(false);
309                 kDebug() << "/// RENDERING SPUMUX MENU crashed";
310                 return;
311             }
312         } else {
313             kDebug() << "/// RENDERING SPUMUX MENU timed out";
314             spuitem->setIcon(KIcon("dialog-close"));
315             m_status.error_log->setText(i18n("Menu job timed out"));
316             m_status.error_box->setHidden(false);
317             return;
318         }
319
320         spuitem->setIcon(KIcon("dialog-ok"));
321         kDebug() << "/// DONE: " << m_menuFile.fileName();
322     }
323
324     // create dvdauthor xml
325     QListWidgetItem *authitem =  m_status.job_progress->item(3);
326     authitem->setIcon(KIcon("system-run"));
327     qApp->processEvents();
328     KIO::NetAccess::mkdir(KUrl(m_iso.tmp_folder->url().path() + "/DVD"), this);
329
330     QDomDocument dvddoc;
331     QDomElement auth = dvddoc.createElement("dvdauthor");
332     auth.setAttribute("dest", m_iso.tmp_folder->url().path() + "/DVD");
333     dvddoc.appendChild(auth);
334     QDomElement vmgm = dvddoc.createElement("vmgm");
335     auth.appendChild(vmgm);
336
337     if (m_pageMenu->createMenu() && !m_pageVob->introMovie().isEmpty()) {
338         // intro movie
339         QDomElement menus = dvddoc.createElement("menus");
340         vmgm.appendChild(menus);
341         QDomElement pgc = dvddoc.createElement("pgc");
342         pgc.setAttribute("entry", "title");
343         menus.appendChild(pgc);
344         QDomElement menuvob = dvddoc.createElement("vob");
345         menuvob.setAttribute("file", m_pageVob->introMovie());
346         pgc.appendChild(menuvob);
347         QDomElement post = dvddoc.createElement("post");
348         QDomText call = dvddoc.createTextNode("jump titleset 1 menu;");
349         post.appendChild(call);
350         pgc.appendChild(post);
351     }
352     QDomElement titleset = dvddoc.createElement("titleset");
353     auth.appendChild(titleset);
354
355     if (m_pageMenu->createMenu()) {
356
357         // DVD main menu
358         QDomElement menus = dvddoc.createElement("menus");
359         titleset.appendChild(menus);
360         QDomElement pgc = dvddoc.createElement("pgc");
361         pgc.setAttribute("entry", "root");
362         menus.appendChild(pgc);
363         for (int i = 0; i < buttons.count(); i++) {
364             QDomElement button = dvddoc.createElement("button");
365             button.setAttribute("name", 'b' + QString::number(i));
366             QDomText nametext = dvddoc.createTextNode("jump title " + buttonsTarget.at(i) + ';');
367             button.appendChild(nametext);
368             pgc.appendChild(button);
369         }
370         QDomElement menuvob = dvddoc.createElement("vob");
371         menuvob.setAttribute("file", m_menuFile.fileName());
372         menuvob.setAttribute("pause", "inf");
373         pgc.appendChild(menuvob);
374     }
375
376     QDomElement titles = dvddoc.createElement("titles");
377     titleset.appendChild(titles);
378
379     QStringList voburls = m_pageVob->selectedUrls();
380
381     QDomElement pgc2 = dvddoc.createElement("pgc");
382     titles.appendChild(pgc2);
383     if (m_pageMenu->createMenu()) {
384         QDomElement post = dvddoc.createElement("post");
385         QDomText call = dvddoc.createTextNode("call menu;");
386         post.appendChild(call);
387         pgc2.appendChild(post);
388     }
389
390
391     for (int i = 0; i < voburls.count(); i++) {
392         if (!voburls.at(i).isEmpty()) {
393             // Add vob entry
394             QDomElement vob = dvddoc.createElement("vob");
395             vob.setAttribute("file", voburls.at(i));
396             pgc2.appendChild(vob);
397         }
398     }
399
400     /*
401         // create one pgc for each video
402         for (int i = 0; i < voburls.count(); i++) {
403             if (!voburls.at(i).isEmpty()) {
404                 // Add vob entry
405
406          QDomElement pgc2 = dvddoc.createElement("pgc");
407          titles.appendChild(pgc2);
408          if (m_pageMenu->createMenu()) {
409       QDomElement post = dvddoc.createElement("post");
410       QDomText call = dvddoc.createTextNode("call vmgm menu 1;");
411       post.appendChild(call);
412       pgc2.appendChild(post);
413          }
414
415                 QDomElement vob = dvddoc.createElement("vob");
416                 vob.setAttribute("file", voburls.at(i));
417                 pgc2.appendChild(vob);
418             }
419         }
420     */
421     QFile data2(m_authorFile.fileName());
422     if (data2.open(QFile::WriteOnly)) {
423         data2.write(dvddoc.toString().toUtf8());
424     }
425     data2.close();
426     /*kDebug() << "------------------";
427     kDebug() << dvddoc.toString();
428     kDebug() << "------------------";*/
429
430     QStringList args;
431     args << "-x" << m_authorFile.fileName();
432     kDebug() << "// DVDAUTH ARGS: " << args;
433     if (m_dvdauthor) {
434         m_dvdauthor->close();
435         delete m_dvdauthor;
436         m_dvdauthor = NULL;
437     }
438     m_creationLog.clear();
439     m_dvdauthor = new QProcess(this);
440     connect(m_dvdauthor, SIGNAL(finished(int , QProcess::ExitStatus)), this, SLOT(slotRenderFinished(int, QProcess::ExitStatus)));
441     m_dvdauthor->setProcessChannelMode(QProcess::MergedChannels);
442     m_dvdauthor->start("dvdauthor", args);
443
444 }
445
446 void DvdWizard::slotRenderFinished(int /*exitCode*/, QProcess::ExitStatus status)
447 {
448     QListWidgetItem *authitem =  m_status.job_progress->item(3);
449     if (status == QProcess::CrashExit) {
450         QByteArray result = m_dvdauthor->readAllStandardError();
451         m_status.error_log->setText(result);
452         m_status.error_box->setHidden(false);
453         kDebug() << "DVDAuthor process crashed";
454         authitem->setIcon(KIcon("dialog-close"));
455         m_dvdauthor->close();
456         delete m_dvdauthor;
457         m_dvdauthor = NULL;
458         cleanup();
459         return;
460     }
461     m_creationLog.append(m_dvdauthor->readAllStandardError());
462     m_dvdauthor->close();
463     delete m_dvdauthor;
464     m_dvdauthor = NULL;
465
466     // Check if DVD structure has the necessary infos
467     if (!QFile::exists(m_iso.tmp_folder->url().path() + "/DVD/VIDEO_TS/VIDEO_TS.IFO")) {
468         m_status.error_log->setText(m_creationLog + '\n' + i18n("DVD structure broken"));
469         m_status.error_box->setHidden(false);
470         kDebug() << "DVDAuthor process crashed";
471         authitem->setIcon(KIcon("dialog-close"));
472         cleanup();
473         return;
474     }
475     authitem->setIcon(KIcon("dialog-ok"));
476     qApp->processEvents();
477     QStringList args;
478     args << "-dvd-video" << "-v" << "-o" << m_iso.iso_image->url().path() << m_iso.tmp_folder->url().path() + "/DVD";
479
480     if (m_mkiso) {
481         m_mkiso->close();
482         delete m_mkiso;
483         m_mkiso = NULL;
484     }
485     m_mkiso = new QProcess(this);
486     connect(m_mkiso, SIGNAL(finished(int , QProcess::ExitStatus)), this, SLOT(slotIsoFinished(int, QProcess::ExitStatus)));
487     m_mkiso->setProcessChannelMode(QProcess::MergedChannels);
488     QListWidgetItem *isoitem =  m_status.job_progress->item(4);
489     isoitem->setIcon(KIcon("system-run"));
490     m_mkiso->start("mkisofs", args);
491
492 }
493
494 void DvdWizard::slotIsoFinished(int /*exitCode*/, QProcess::ExitStatus status)
495 {
496     QListWidgetItem *isoitem =  m_status.job_progress->item(4);
497     if (status == QProcess::CrashExit) {
498         QByteArray result = m_mkiso->readAllStandardError();
499         m_status.error_log->setText(result);
500         m_status.error_box->setHidden(false);
501         m_mkiso->close();
502         delete m_mkiso;
503         m_mkiso = NULL;
504         cleanup();
505         kDebug() << "Iso process crashed";
506         isoitem->setIcon(KIcon("dialog-close"));
507         return;
508     }
509
510     m_creationLog.append(m_mkiso->readAllStandardError());
511     delete m_mkiso;
512     m_mkiso = NULL;
513
514     // Check if DVD iso is ok
515     QFile iso(m_iso.iso_image->url().path());
516     if (!iso.exists() || iso.size() == 0) {
517         if (iso.exists()) {
518             KIO::NetAccess::del(m_iso.iso_image->url(), this);
519         }
520         m_status.error_log->setText(m_creationLog + '\n' + i18n("DVD ISO is broken"));
521         m_status.error_box->setHidden(false);
522         isoitem->setIcon(KIcon("dialog-close"));
523         cleanup();
524         return;
525     }
526
527     isoitem->setIcon(KIcon("dialog-ok"));
528     kDebug() << "ISO IMAGE " << m_iso.iso_image->url().path() << " Successfully created";
529     cleanup();
530     kDebug() << m_creationLog;
531
532     m_status.error_log->setText(i18n("DVD ISO image %1 successfully created.", m_iso.iso_image->url().path()));
533     m_status.button_preview->setEnabled(true);
534     m_status.button_burn->setEnabled(true);
535     m_status.error_box->setHidden(false);
536     //KMessageBox::information(this, i18n("DVD ISO image %1 successfully created.", m_iso.iso_image->url().path()));
537
538 }
539
540
541 void DvdWizard::cleanup()
542 {
543     m_authorFile.remove();
544     m_menuFile.remove();
545     KIO::NetAccess::del(KUrl(m_iso.tmp_folder->url().path() + "/DVD"), this);
546 }
547
548
549 void DvdWizard::slotPreview()
550 {
551     QString exec = KStandardDirs::findExe("xine");
552     if (exec.isEmpty()) KMessageBox::sorry(this, i18n("You need program <b>%1</b> to perform this action", "xine"));
553     else QProcess::startDetached(exec, QStringList() << "dvd://" + m_iso.iso_image->url().path());
554 }
555
556 void DvdWizard::slotBurn()
557 {
558     QAction *action = qobject_cast<QAction *>(sender());
559     QString exec = action->data().toString();
560     QStringList args;
561     if (exec.endsWith("k3b")) args << "--image" << m_iso.iso_image->url().path();
562     else args << "--image=" + m_iso.iso_image->url().path();
563     QProcess::startDetached(exec, args);
564 }
565
566
567