/////////////////////////////////////////////////////////////////////////////
// Name:        ProgressDlg.cpp
// Purpose:     Progress of generation/burning dialog
// Author:      Alex Thuering
// Created:     14.08.2004
// RCS-ID:      $Id: ProgressDlg.cpp,v 1.140 2012/11/05 21:10:58 ntalex Exp $
// Copyright:   (c) Alex Thuering
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

#include "ProgressDlg.h"
#include "Config.h"
#include "MPEG.h"
#include "mediaenc_ffmpeg.h"
#include "mediatrc_ffmpeg.h"
#include "Version.h"
#include <wxVillaLib/PipeExecute.h>
#include <wxVillaLib/utils.h>
#include <wx/dir.h>
#include <wx/filename.h>
#include <wx/file.h>
#include <wx/regex.h>
#include <wx/utils.h>
#include <wx/platinfo.h>
#include <wxSVG/SVGDocument.h>
#include <wxSVG/mediadec_ffmpeg.h>

#ifdef __WXMAC__
extern "C" {
	wxString GetDeviceName(const wxString& ioRegistryEntryPath);
	wxString GetDeviceNode(const wxString& ioRegistryEntryPath);
	bool IsMediaPresent(const wxString& ioRegistryEntryPath);
	bool IsMediaErasable(const wxString& ioRegistryEntryPath);
	bool IsMediaBlank(const wxString& ioRegistryEntryPath);
	long GetMediaSpaceFree(const wxString& ioRegistryEntryPath);
}
#endif

#define DATA_FILE(fname) wxFindDataFile(_T("data") + wxString(wxFILE_SEP_PATH) + fname)
#define TMP_ISO wxT("dvd-out.iso")
#define WARNING_MPLEX_MSG wxT("Warning: using of mplex (mjpegs-tool) for generation of menus with audio is disabled. This can produce not compliant DVD.")

////////////////////////// Process Execute ///////////////////////////////////

#define MAX_WARNINGS 10

class ProcessExecute: public wxPipeExecute {
public:
	ProcessExecute(ProgressDlg* processDlg): m_processDlg(processDlg) {}
	virtual ~ProcessExecute() {};

	bool Execute(wxString command, wxString inputFile = wxEmptyString, wxString outputFile = wxEmptyString) {
		m_processDlg->AddDetailMsg(_("Executing command: ") + command);
		return wxPipeExecute::Execute(command, inputFile, outputFile);
	}
	
	virtual void ProcessOutput(wxString line) {
		m_processDlg->AddDetailText(line + _T("\n"));
	}

	virtual bool IsCanceled() {
		return m_processDlg->IsCanceled();
	}

protected:
	ProgressDlg* m_processDlg;
};

class SpumuxExecute: public wxPipeExecute {
public:
	SpumuxExecute(ProgressDlg* processDlg, double fileSize): m_processDlg(processDlg) {
		m_fileSize = fileSize;
		m_error = false;
		m_progressRegEx.Compile(wxT("^INFO: ([0-9]+) bytes"));
		m_initSubStep = m_processDlg->GetSubStep();
		m_percent = 0;
	}
	virtual ~SpumuxExecute() {};

	bool Execute(wxString command, wxString inputFile = wxEmptyString, wxString outputFile = wxEmptyString) {
		m_processDlg->AddDetailMsg(_("Executing command: ") + command);
		bool result = wxPipeExecute::Execute(command, inputFile, outputFile);
		m_processDlg->SetSubStep(m_initSubStep + 50);
		return result && !m_error;
	}
	
	virtual void ProcessOutput(wxString line) {
		if (line.StartsWith(wxT("ERR:"))) {
			m_processDlg->AddDetailMsg(line, *wxRED);
			m_error = m_error || line.StartsWith(wxT("ERR:  Cannot pick button masks"));
		} else if (m_progressRegEx.Matches(line)) {
			if (m_fileSize == -1)
				return;
			double bytes = 0;
			m_progressRegEx.GetMatch(line, 1).ToDouble(&bytes);
			int percent = (bytes * 100) / m_fileSize;
			m_processDlg->SetSubStep(m_initSubStep + (int) percent/2);
			if (percent >= m_percent) {
				m_percent += 20;
				m_processDlg->AddDetailText(line + _T("\n"));
			}
		} else if (!line.StartsWith(wxT("STAT:")))
			m_processDlg->AddDetailText(line + _T("\n"));
	}

	virtual bool IsCanceled() {
		return m_processDlg->IsCanceled();
	}

protected:
	ProgressDlg* m_processDlg;
	double m_fileSize;
	bool m_error;
	wxRegEx m_progressRegEx;
	int m_initSubStep;
	int m_percent;
};

class ProgressExecute: public ProcessExecute {
public:
	ProgressExecute(ProgressDlg* processDlg, wxString filter): ProcessExecute(processDlg), m_percent(0) {
		m_percentPattern.Compile(wxT("(([0-9]+[\\.,][0-9]+)|([0-9]+))%"),wxRE_ICASE);
		m_blockPattern.Compile(wxT("([0-9]+)[[:space:]]+of[[:space:]]+([0-9]+)"),wxRE_ICASE);
		m_filterPattern.Compile(filter,wxRE_ICASE);
		m_initSubStep = m_processDlg->GetSubStep();
	}
	virtual ~ProgressExecute() {};

	virtual void ProcessOutput(wxString line) {
		// get last output if program is using \b (remove \b at begin/end, then get text after last \b)
		while (line.at(0) == wxT('\b'))
			line.Remove(0, 1);
		while (line.Last() == wxT('\b'))
			line.RemoveLast(1);
		line = line.AfterLast(wxT('\b'));
		if (m_filterPattern.Matches(line)) {
			if (m_blockPattern.Matches(line)) {
				long blocks = 0;
				long totalBlocks = 0;
				long percent = 0;
				if (m_blockPattern.GetMatch(line, 1).ToLong(&blocks)
						&& m_blockPattern.GetMatch(line, 2).ToLong(&totalBlocks)) {
					percent = (totalBlocks > 0) ? (blocks * 100) / totalBlocks : 0;
					m_processDlg->SetSubStep(m_initSubStep + (int) m_percent);
					if (percent >= m_percent) {
						m_percent += 5;
					} else {
						return;
					}
				}
			} else if (m_percentPattern.Matches(line)) {
				long percent = 0;
				wxString percentStr = m_percentPattern.GetMatch(line, 1);
				percentStr = percentStr.BeforeFirst(wxT('.')).BeforeFirst(wxT(','));
				if (percentStr.ToLong(&percent)) {
					m_processDlg->SetSubStep(m_initSubStep + (int) percent);
					if (percent >= m_percent) {
						m_percent += 5;
					} else if (percent < m_percent - 5) {
						m_initSubStep += 100;
						m_percent = 5;
					} else {
						return;
					}
				}
			}
		}
		m_processDlg->AddDetailText(line + _T("\n"));
	}

protected:
    wxRegEx m_percentPattern;
    wxRegEx m_blockPattern;
    wxRegEx m_filterPattern;
	int     m_initSubStep;
	int     m_percent;
};

class AVConvExecute: public ProcessExecute {
public:
	AVConvExecute(ProgressDlg* processDlg, long totalFrames): ProcessExecute(processDlg), m_percent(0),
			m_pattern(wxT("frame=[[:space:]]+([0-9]+).*")) {
		m_initSubStep = m_processDlg->GetSubStep();
		m_totalFrames = totalFrames;
	}
	virtual ~AVConvExecute() {}

	virtual void ProcessOutput(wxString line) {
		if (line.Find(wxT("buffer underflow i=1")) >= 0
				|| line.Find(wxT("packet too large, ignoring buffer limits")) >= 0
				|| line.Find(wxT("Last message repeated 1 times")) >= 0)
			return;
		if (m_pattern.Matches(line)) {
			long frame = 0;
			m_pattern.GetMatch(line, 1).ToLong(&frame);
			m_percent = (m_totalFrames > 0) ? (frame * 100) / m_totalFrames : 0;
			m_processDlg->SetSubStep(m_initSubStep + (int) m_percent);
		}
		m_processDlg->AddDetailText(line + _T("\n"));
	}

protected:
	long m_totalFrames;
	int m_initSubStep;
	int m_percent;
	wxRegEx m_pattern;
};

class BurnExecute: public ProgressExecute {
public:
	BurnExecute(ProgressDlg* processDlg, wxString filter): ProgressExecute(processDlg, filter), m_burnOk(false) {}

	virtual void ProcessOutput(wxString line) {
		if (line.Find(wxT(": writing lead-out")) >= 0)
			m_burnOk = true;
		ProgressExecute::ProcessOutput(line);
	}

	bool Execute(wxString command, wxString inputFile = wxEmptyString, wxString outputFile = wxEmptyString) {
		m_burnOk = false;
		bool res = ProgressExecute::Execute(command, wxEmptyString, wxEmptyString);
		return res || m_burnOk;
	}

private:
	bool m_burnOk;
};

class BlocksExecute: public ProcessExecute {
public:
	BlocksExecute(ProgressDlg* processDlg): ProcessExecute(processDlg), m_percent(0) {
		m_initSubStep = m_processDlg->GetSubStep();
	}
	virtual ~BlocksExecute() {}

	virtual void ProcessOutput(wxString line) {
		long blocks = 0;
		long totalBlocks = 0;
		wxRegEx pattern(wxT(".*[[:space:]]+([0-9]+)[[:space:]]+of[[:space:]]+([0-9]+)[[:space:]]+.*"));
		if (pattern.Matches(line)) {
			pattern.GetMatch(line, 1).ToLong(&blocks);
			pattern.GetMatch(line, 2).ToLong(&totalBlocks);
			m_percent = (totalBlocks > 0) ? (blocks * 100) / totalBlocks : 0;
			m_processDlg->SetSubStep(m_initSubStep + (int) m_percent);
		}
		m_processDlg->AddDetailText(line + _T("\n"));
	}

protected:
	int m_initSubStep;
	int m_percent;
};

class DVDAuthorExecute: public ProgressExecute {
public:
	DVDAuthorExecute(ProgressDlg* processDlg, int totalSize): ProgressExecute(processDlg, wxT(".*")),
			m_totalSize(totalSize), m_warnings(0), m_warnStep(1), m_dvdauthorStep(0) {
		if (m_totalSize == 0)
			m_totalSize++;
	}
	virtual ~DVDAuthorExecute() {};

	virtual void ProcessOutput(wxString line) {
		if (m_dvdauthorStep)
			return ProgressExecute::ProcessOutput(line);
		if (line.Mid(0, 11) == _T("STAT: fixed")) {
			m_dvdauthorStep++;
			m_initSubStep += 200;
			m_processDlg->SetSubStep(m_initSubStep);
		} else if (line.Mid(0, 10) == _T("STAT: VOBU")) {
			long size = 0;
			wxString sizeStr = line.BeforeLast(wxT(',')).AfterLast(wxT(' '));
			if (sizeStr.Mid(sizeStr.Length() - 2) == _T("MB") && sizeStr.Remove(sizeStr.Length() - 2).ToLong(&size))
				m_processDlg->SetSubStep(m_initSubStep + (int) size * 200
						/ m_totalSize);
		} else if (line.Mid(0, 5) == _T("WARN:")) {
			m_warnings++;
			if (m_warnings > m_warnStep * 10)
				m_warnStep = m_warnStep * 10;
			else if (m_warnings % m_warnStep != 0)
				return;
		}
		if (line.Mid(0, 4) == wxT("ERR:"))
			m_processDlg->AddDetailMsg(line, *wxRED);
		else
			m_processDlg->AddDetailText(line + _T("\n"));
	}

protected:
	int m_totalSize;
	int m_warnings;
	int m_warnStep;
	int m_dvdauthorStep;
};

/////////////////////////// Process Dialog ///////////////////////////////////
class ProgressDlgLog: public wxLog {
public:
	/**
	 * Constructor
	 */
	ProgressDlgLog(ProgressDlg* dlg) {
		this->dlg = dlg;
	}
protected:
	/**
	 * Print the message into progress dialog details window.
	 */
	void DoLog(wxLogLevel level, const wxChar* szString, time_t t) {
		dlg->AddDetailMsg(szString, level <= wxLOG_Error ? *wxRED : wxColour(64,64,64));
	}
private:
	ProgressDlg* dlg;
};
/////////////////////////// Process Dialog ///////////////////////////////////

BEGIN_EVENT_TABLE(ProgressDlg, wxDialog)
  EVT_BUTTON(wxID_CANCEL, ProgressDlg::OnCancel)
  EVT_BUTTON(HIDE_BT_ID, ProgressDlg::OnHideDetails)
  EVT_BUTTON(ICONIZE_BT_ID, ProgressDlg::OnMinimize)
END_EVENT_TABLE()

/** Constructor */
ProgressDlg::ProgressDlg(wxWindow* parent, Cache* cache, bool autoStart): wxDialog(parent, -1, wxEmptyString,
		wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) {
    // begin wxGlade: ProgressDlg::ProgressDlg
    m_summaryLabel = new wxStaticText(this, wxID_ANY, _("Summary:"));
    m_summaryText = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxTE_RICH);
    m_gauge = new wxGauge(this, wxID_ANY, 10, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL|wxGA_SMOOTH);
    m_detailsLabel = new wxStaticText(this, wxID_ANY, _("Details:"));
    m_detailsText = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxTE_RICH);
    m_detailsBt = new wxButton(this, HIDE_BT_ID, _("Hide details"));
    m_minimizeBt = new wxButton(this, ICONIZE_BT_ID, _("Minimize"));
    m_cancelBt = new wxButton(this, wxID_CANCEL, _("Cancel"));

    set_properties();
    do_layout();
    // end wxGlade
    m_cancel = false;
    m_end = false;
    m_subStepCount = 0;
    m_cache = cache;
    m_autoStart = autoStart;
}

void ProgressDlg::set_properties() {
    // begin wxGlade: ProgressDlg::set_properties
    SetTitle(_("Generate DVD"));
    SetSize(wxSize(600, 600));
    // end wxGlade

	m_detailsBtLabel = m_detailsBt->GetLabel();
	m_detailsBt->SetLabel(_T("<< ") + m_detailsBtLabel);
}

void ProgressDlg::do_layout() {
    // begin wxGlade: ProgressDlg::do_layout
    wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
    wxBoxSizer* btSizer = new wxBoxSizer(wxHORIZONTAL);
    wxBoxSizer* panelSizer = new wxBoxSizer(wxVERTICAL);
    panelSizer->Add(m_summaryLabel, 0, wxBOTTOM, 2);
    panelSizer->Add(m_summaryText, 1, wxBOTTOM|wxEXPAND, 8);
    panelSizer->Add(m_gauge, 0, wxBOTTOM|wxEXPAND, 4);
    panelSizer->Add(m_detailsLabel, 0, wxBOTTOM, 2);
    panelSizer->Add(m_detailsText, 1, wxEXPAND, 0);
    mainSizer->Add(panelSizer, 1, wxLEFT|wxRIGHT|wxTOP|wxEXPAND, 10);
    btSizer->Add(m_detailsBt, 0, wxALIGN_CENTER_VERTICAL, 0);
    btSizer->Add(20, 20, 1, wxEXPAND, 0);
    btSizer->Add(m_minimizeBt, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 8);
    btSizer->Add(m_cancelBt, 0, 0, 0);
    mainSizer->Add(btSizer, 0, wxALL|wxEXPAND, 10);
    SetSizer(mainSizer);
    Layout();
    Centre();
    // end wxGlade
	m_panelSizer = panelSizer;
}

void ProgressDlg::OnHideDetails(wxCommandEvent& WXUNUSED(event)) {
	if (m_detailsText->IsShown()) {
		m_detailsLabel->Hide();
		m_detailsText->Hide();
		m_detailsText->Freeze();
		m_panelSizer->Detach(m_detailsLabel);
		m_panelSizer->Detach(m_detailsText);
		int height = m_detailsLabel->GetSize().GetY() + m_detailsText->GetSize().GetY() + 2;
		SetSize(GetSize().GetX(), GetSize().GetY() - height);
		m_detailsBt->SetLabel(_("Show details") + wxString(_T(" >>")));
	} else {
		m_detailsLabel->Show();
		m_detailsText->Show();
		m_detailsText->Thaw();
		m_panelSizer->Insert(3, m_detailsLabel, 0, wxBOTTOM, 2);
		m_panelSizer->Insert(4, m_detailsText, 1, wxEXPAND, 0);
		int height = m_detailsLabel->GetSize().GetY() + m_detailsText->GetSize().GetY() + 2;
		SetSize(GetSize().GetX(), GetSize().GetY() + height);
		m_detailsBt->SetLabel(_T("<< ") + m_detailsBtLabel);
	}
}

void ProgressDlg::OnMinimize(wxCommandEvent& event) {
	wxPlatformInfo info;
	if ((info.GetOperatingSystemId() & wxOS_WINDOWS) && !info.CheckOSVersion(6,0))
		MakeModal(false);
	((wxFrame*) GetParent())->Iconize();
}

void ProgressDlg::AddSummaryMsg(const wxString& message, const wxString& details,
		const wxColour& colour) {
	m_summaryText->SetDefaultStyle(wxTextAttr(colour.Ok() ? colour : *wxBLACK));
	m_summaryText->AppendText(message + _T("\n"));
	m_summaryText->ShowPosition(m_summaryText->GetLastPosition());
	AddDetailMsg(details.length() ? details : message, colour.Ok() ? colour : *wxBLACK);
}

void ProgressDlg::AddDetailMsg(const wxString& message, const wxColour& colour) {
	if (m_cancel)
		return;
	if (colour.Ok())
		m_detailsText->SetDefaultStyle(wxTextAttr(colour));
	AddDetailText(message + _T("\n"));
	m_detailsText->SetDefaultStyle(wxTextAttr(wxColour(64, 64, 64)));
}

void ProgressDlg::AddDetailText(const wxString& text) {
	m_detailsText->AppendText(text);
	m_detailsText->ShowPosition(m_detailsText->GetLastPosition());
	if (wxLog::GetActiveTarget()->GetVerbose())
		fprintf(stderr, "%s", (const char*) text.mb_str());
	wxYieldIfNeeded();
}

void ProgressDlg::UpdateGauge() {
	int subStep = 0;
	if (m_subStepCount > 0)
		subStep = m_subStep * 100 / m_subStepCount;
	m_gauge->SetValue(m_step * 100 + subStep);
}

void ProgressDlg::Failed(const wxString& message) {
	AddSummaryMsg(_("Failed"), message, *wxRED);
}

void ProgressDlg::OnCancel(wxCommandEvent& WXUNUSED(event)) {
	if (!m_cancel) {
		AddSummaryMsg(_("Aborted"), wxEmptyString, *wxRED);
		End();
	} else
		m_end = true;
}

void ProgressDlg::End() {
	if (!m_cancel)
		wxBell();
	m_cancelBt->SetLabel(_("Close"));
	m_cancel = true;
}

bool ProgressDlg::IsCanceled() {
	wxYield();
	return m_cancel;
}

bool ProgressDlg::Start(BurnDlg* burnDlg, DVD* dvd) {
	// disable parent window
	MakeModal();
	wxLog* previousLog = wxLog::SetActiveTarget(new ProgressDlgLog(this));
	// show dialog
	Show();
	// start
	Run(burnDlg, dvd);
	bool isOk = !IsCanceled();
	// end
	End();
	// restore log
	delete wxLog::SetActiveTarget(previousLog);
	// close the Window or release the controls
	if (m_autoStart) {
		Close(true);
	} else {
    	while (!m_end) {
    		wxMilliSleep(100);
    		wxYield();
    	}
    }
	MakeModal(false);
	return isOk;
}

int ProgressDlg::GetMenuSubSteps(wxArrayPtrVoid& menuVobs, wxArrayInt& menuWSTypes, DVD* dvd, wxString dvdTmpDir) {
	int menuSubSteps = 0;
	for (int tsi = -1; tsi < (int) dvd->GetTitlesets().Count(); tsi++) {
		PgcArray& pgcs = dvd->GetPgcArray(tsi, true);
		for (int pgci = 0; pgci < (int) pgcs.Count(); pgci++) {
			Pgc* pgc = pgcs[pgci];
			if (pgc->GetVobs().Count() == 0)
				continue;
			// set temp file name
			wxString menuFile = dvdTmpDir + wxString::Format(_T("menu%d-%d.mpg"), tsi + 1, pgci);
			Vob* vob = pgc->GetVobs()[0];
			vob->SetTmpFilename(menuFile);
			// calculate sub steps
			Menu* menu = vob->GetMenu();
			WidescreenType widescreenType = pgcs.GetVideo().GetWidescreen();
			bool videoMenu = menu->GetSVG()->GetDuration() > 0;
			if (videoMenu) {
				menuSubSteps += 400; // generate mpeg(200) + transcode(200)
			} else if (vob->GetAudioFilenames().size() > 0)
				menuSubSteps += 300; // generate mpeg(25+75) + transcode(200)
			else
				menuSubSteps += 25; // generate mpeg
			menuSubSteps += (menu->GetAspectRatio() == ar4_3 ? 1 : widescreenType != wtAUTO ? 2 : 3)*50; // spumux
			menuVobs.Add(vob);
			menuWSTypes.Add((int) widescreenType);
		}
	}
	return menuSubSteps;
}

int ProgressDlg::GetTitleSubSteps(wxArrayPtrVoid& titleVobs, wxArrayInt& titleAspects, DVD* dvd, Cache* cache) {
	int titleSubSteps = 0;
	for (int tsi = 0; tsi < (int) dvd->GetTitlesets().Count(); tsi++) {
		Titleset* ts = dvd->GetTitlesets()[tsi];
		for (int pgci = 0; pgci < (int) ts->GetTitles().Count(); pgci++) {
			Pgc* pgc = ts->GetTitles()[pgci];
			for (int vobi = 0; vobi < (int) pgc->GetVobs().Count(); vobi++) {
				Vob* vob = pgc->GetVobs()[vobi];
				vob->SetTmpFilename(_T(""));
				if (vob->GetSlideshow() || (vob->GetDoNotTranscode() && vob->GetAudioFilenames().Count() == 0))
					continue;
				wxString cacheFile = cache->Find(vob, dvd);
				if (cacheFile.length() > 0 && wxFileExists(cacheFile)) {
					AddDetailMsg(wxString::Format(_("Found transcoded file in cache for '%s'"),
							vob->GetFilename().c_str()));
					vob->SetTmpFilename(cacheFile);
					continue; // file in cache do not need to transcode
				}
				titleSubSteps += 200;
				titleVobs.Add(vob);
				titleAspects.Add(ts->GetTitles().GetVideo().GetAspect());
			}
		}
	}
	return titleSubSteps;
}

int ProgressDlg::GetSlideshowSubSteps(wxArrayPtrVoid& slideshowVobs, DVD* dvd, wxString dvdTmpDir) {
	int slideshowSubSteps = 0;
	for (int tsi = 0; tsi < (int) dvd->GetTitlesets().Count(); tsi++) {
		Titleset* ts = dvd->GetTitlesets()[tsi];
		for (int pgci = 0; pgci < (int) ts->GetTitles().Count(); pgci++) {
			Pgc* pgc = ts->GetTitles()[pgci];
			for (int vobi = 0; vobi < (int) pgc->GetVobs().Count(); vobi++) {
				Vob* vob = pgc->GetVobs()[vobi];
				if (vob->GetSlideshow()) {
					slideshowSubSteps += 10 * vob->GetSlideshow()->GetCount();
					if (vob->GetAudioFilenames().size() > 0)
						slideshowSubSteps += 200; // transcode
					vob->SetTmpFilename(dvdTmpDir
							+ wxString::Format(_T("title%d-%d-%d.vob"), tsi, pgci, vobi));
					slideshowVobs.Add(vob);
				}
			}
		}
	}
	return slideshowSubSteps;
}

int ProgressDlg::GetSubtitleSubSteps(wxArrayPtrVoid& subtitleVobs, wxArrayInt& subtitleTsi, DVD* dvd,
		wxString dvdTmpDir) {
	int subtitleSubSteps = 0;
	for (int tsi = 0; tsi < (int) dvd->GetTitlesets().Count(); tsi++) {
		Titleset* ts = dvd->GetTitlesets()[tsi];
		for (int pgci = 0; pgci < (int) ts->GetTitles().Count(); pgci++) {
			Pgc* pgc = ts->GetTitles()[pgci];
			for (int vobi = 0; vobi < (int) pgc->GetVobs().Count(); vobi++) {
				Vob* vob = pgc->GetVobs()[vobi];
				if (vob->GetSubtitles().Count()) {
					subtitleSubSteps += vob->GetSubtitles().Count() * 50;
					vob->SetTmpFilename(dvdTmpDir
							+ wxString::Format(_T("title%d-%d-%d.vob"), tsi, pgci, vobi));
					subtitleVobs.Add(vob);
					subtitleTsi.Add(tsi);
				}
			}
		}
	}
	return subtitleSubSteps;
}

void ProgressDlg::Run(BurnDlg* burnDlg, DVD* dvd) {
	if (IsCanceled())
		return;

	// print version
	AddDetailMsg(wxT("DVDStyler v") + APP_VERSION);
	AddDetailMsg(wxGetOsDescription());
	AddDetailMsg(wxFfmpegMediaEncoder::GetBackendVersion());
	
	// prepare
	AddSummaryMsg(_("Prepare"));

	wxString tmpDir = burnDlg->GetTempDir();
	if (tmpDir.Last() != wxFILE_SEP_PATH)
		tmpDir += wxFILE_SEP_PATH;
	wxString dvdOutDir = tmpDir + wxString(wxT("dvd-out")) + wxFILE_SEP_PATH;
	wxString dvdTmpDir = tmpDir + wxString(wxT("dvd-tmp")) + wxFILE_SEP_PATH;
	
	if (!CleanTemp(tmpDir, dvdTmpDir, dvdOutDir))
		return;
	
	// check cache and calculate steps
	AddDetailMsg(_("Search for transcoded files in cache"));
	m_cache->BeginClean();
	// menus
	wxArrayPtrVoid menuVobs;
	wxArrayInt menuWSTypes;
	int menuSubSteps = GetMenuSubSteps(menuVobs, menuWSTypes, dvd, dvdTmpDir);
	// titles
	wxArrayPtrVoid titleVobs;
	wxArrayInt titleAspects;
	int titleSubSteps = GetTitleSubSteps(titleVobs, titleAspects, dvd, m_cache);
	// slideshow
	wxArrayPtrVoid slideshowVobs;
	int slideshowSubSteps = GetSlideshowSubSteps(slideshowVobs, dvd, dvdTmpDir);
	// subtitle
	wxArrayPtrVoid subtitleVobs;
	wxArrayInt subtitleTsi;
	int subtitleSubSteps = GetSubtitleSubSteps(subtitleVobs, subtitleTsi, dvd, dvdTmpDir);
	// remove unused files from cache
	m_cache->EndClean();

	// calculate step count
	int stepCount = 1;
	if (menuVobs.Count() > 0)
		stepCount++;
	if (titleVobs.Count() > 0)
		stepCount++;
	if (slideshowVobs.Count() > 0)
		stepCount++;
	if (subtitleVobs.Count() > 0)
		stepCount++;
	if (burnDlg->DoCreateIso() || (burnDlg->DoBurn() && burnDlg->DoAddECC())) {
		stepCount++;
	}
	if (burnDlg->DoAddECC())
		stepCount++;
	if (burnDlg->DoBurn()) {
		stepCount++;
		if (burnDlg->DoFormat())
			stepCount++;
	}
	SetSteps(stepCount);
	
	// Start generation
	if (!GenerateMenus(menuVobs, menuWSTypes, menuSubSteps, dvd)
			|| !Transcode(titleVobs, titleAspects, titleSubSteps, dvd)
			|| !GenerateSlideshow(slideshowVobs, slideshowSubSteps, dvd)
			|| !MultiplexSubtitles(subtitleVobs, subtitleTsi, subtitleSubSteps, dvd)
			|| !GenerateDvdFilesystem(dvd, dvdTmpDir, dvdOutDir, tmpDir)
			|| !Preview(burnDlg, dvdOutDir)
			|| !CreateIsoImage(burnDlg, dvd, dvdOutDir, tmpDir)
			|| !AddEccData(burnDlg, tmpDir)
			|| !FormatDvd(burnDlg)
			|| !BurnDvd(burnDlg, dvd, dvdOutDir, tmpDir))
		return;

	if (IsCanceled())
		return;

	// clear temp directory
	if (s_config.GetRemoveTempFiles())
		DeleteTempFiles(tmpDir, dvdTmpDir, dvdOutDir, burnDlg->DoCreateIso() || burnDlg->DoBurn());

	if (burnDlg->DoBurn())
		AddSummaryMsg(_("Burning was successful."), wxEmptyString, wxColour(0, 128, 0));
	else
		AddSummaryMsg(_("Generating was successful."), wxEmptyString, wxColour(0, 128, 0));
	wxLog::FlushActive();
}

bool ProgressDlg::CleanTemp(const wxString& tmpDir, const wxString& dvdTmpDir, const wxString& dvdOutDir) {
	if (wxDir::Exists(tmpDir) && !DeleteTempFiles(tmpDir, dvdTmpDir, dvdOutDir, true)) {
		Failed(wxT(""));
		return false;
	}
	
	// create temporary directories
	if (!wxDir::Exists(tmpDir) && !wxMkdir(tmpDir)) {
		Failed(wxString::Format(_("Can't create directory '%s'"), tmpDir.c_str()));
		return false;
	}
	if (!wxDir::Exists(dvdTmpDir) && !wxMkdir(dvdTmpDir)) {
		Failed(wxString::Format(_("Can't create directory '%s'"), dvdTmpDir.c_str()));
		return false;
	}
	if (!wxDir::Exists(dvdOutDir) && !wxMkdir(dvdOutDir)) {
		Failed(wxString::Format(_("Can't create directory '%s'"), dvdOutDir.c_str()));
		return false;
	}
	return true;
}

bool ProgressDlg::GenerateMenus(wxArrayPtrVoid& menuVobs, wxArrayInt& menuWSTypes, int menuSubSteps, DVD* dvd) {
	if (menuVobs.Count() == 0)
		return true;
	if (IsCanceled())
		return false;
	AddSummaryMsg(_("Generating menus"));
	SetSubSteps(menuSubSteps);
	for (unsigned int i = 0; i < menuVobs.Count(); i++) {
		AddDetailMsg(wxString::Format(_("Generating menu %u of %lu"), i + 1, menuVobs.Count()));
		Vob* vob = (Vob*) menuVobs[i];
		WidescreenType wsType = (WidescreenType) menuWSTypes[i];
		if (!GenerateMenu(vob->GetMenu(), wsType, vob->GetTmpFilename(), (AudioFormat) dvd->GetAudioFormat(),
				vob->GetAudioFilenames().size() > 0 ? vob->GetAudioFilenames()[0] : wxString(),
				dvd->GetVideoBitrate()))
			return false;
	}
	IncStep();
	return true;
}

bool ProgressDlg::Transcode(wxArrayPtrVoid& titleVobs, wxArrayInt& titleAspects, int titleSubSteps, DVD* dvd) {
	if (titleVobs.Count() == 0)
		return true;
	if (IsCanceled())
		return false;
	AddSummaryMsg(_("Transcode/remultiplex"));
	SetSubSteps(titleSubSteps);
	int videoBitrate = dvd->GetVideoBitrate();
	if (dvd->GetCapacity() != dcUNLIMITED && dvd->GetSize() > dvd->GetCapacityValue()*0.965)
		videoBitrate *= 0.965;
	for (int i = 0; i < (int) titleVobs.Count(); i++) {
		Vob* vob = (Vob*) titleVobs[i];
		AspectRatio aspect = (AspectRatio) titleAspects[i];
		wxString cacheFile = m_cache->Add(vob, dvd);
		AddDetailMsg(wxT("Add file to cache:") + cacheFile);
		vob->SetTmpFilename(cacheFile);
		if (!Transcode(vob, aspect, videoBitrate, s_config.GetUseMplex(), dvd->GetAudioFormat())) {
			if (wxFileExists(vob->GetTmpFilename()))
				wxRemoveFile(vob->GetTmpFilename());
			return false;
		}
	}
	IncStep();
	return true;
}

bool ProgressDlg::GenerateSlideshow(wxArrayPtrVoid& slideshowVobs, int slideshowSubSteps, DVD* dvd) {
	if (slideshowVobs.Count() == 0)
		return true;
	if (IsCanceled())
		return false;
	AddSummaryMsg(_("Generate slideshow"));
	SetSubSteps(slideshowSubSteps);
	for (unsigned int i = 0; i < slideshowVobs.Count(); i++) {
		AddDetailMsg(wxString::Format(_("Generating slideshow %u of %lu"), i + 1, slideshowVobs.Count()));
		Vob* vob = (Vob*) slideshowVobs[i];
		if (!GenerateSlideshow(vob->GetSlideshow(), vob->GetTmpFilename(), dvd->GetAudioFormat(),
				vob->GetAudioFilenames().size() > 0 ? vob->GetAudioFilenames()[0] : wxString()))
			return false;
	}
	IncStep();
	return true;
}

bool ProgressDlg::MultiplexSubtitles(wxArrayPtrVoid& subtitleVobs, wxArrayInt& subtitleTsi, int subtitleSubSteps,
		DVD* dvd) {
	if (subtitleVobs.Count() == 0)
		return true;
	if (IsCanceled())
		return false;
	AddSummaryMsg(_("Multiplexing subtitles"));
	SetSubSteps(subtitleSubSteps);
	for (unsigned int i = 0; i < subtitleVobs.Count(); i++) {
		AddDetailMsg(wxString::Format(_("Multiplexing subtitles %u of %lu"), i + 1, subtitleVobs.Count()));
		Vob* vob = (Vob*) subtitleVobs[i];
		AspectRatio aspectRatio = dvd->GetTitlesets()[subtitleTsi[i]]->GetTitles().GetVideo().GetAspect();
		
		// Extract Video stream geometry
		wxSize movieSize;
		VideoFormat videoFormat = vfPAL;
		Stream* stream = vob->GetVideoStream();

		if (stream != NULL) {
			if (stream->GetVideoFormat() == vfCOPY) {
				movieSize = stream->GetSourceVideoSize();
				videoFormat = movieSize.GetHeight() == 480 || movieSize.GetHeight() == 240 ? vfNTSC : vfPAL;
			} else {
				videoFormat = stream->GetVideoFormat();
				movieSize = GetFrameSize(videoFormat);
			}
		}
		
		int subCount = vob->GetSubtitleStreamsCount() - vob->GetSubtitles().Count();
		for (unsigned int s = 0; s < vob->GetSubtitles().Count(); s++) {
			wxString vobFile = vob->GetFilename();
			if (wxFileExists(vob->GetTmpFilename())) {
				if (!wxRenameFile(vob->GetTmpFilename(), vob->GetTmpFilename() + wxT(".old"))) {
					Failed(wxString::Format(_("Can't rename temporary file '%s'"),vob->GetTmpFilename().c_str()));
					return false;
				}
				vobFile = vob->GetTmpFilename() + wxT(".old");
			}
			
			// Force geometry of subtitle
			TextSub* textSub = vob->GetSubtitles()[s];
			textSub->SetMovieSize(movieSize);
			textSub->SetAspectRatio(aspectRatio);

			if (!MultiplexSubtitles(vobFile, vob->GetSubtitles()[s], s + subCount, videoFormat, vob->GetTmpFilename()))
				return false;
			IncSubStep(vob->GetSize(dvd));
		}
	}
	IncStep();
	return true;
}

bool ProgressDlg::MultiplexSubtitles(const wxString& vobFile, TextSub* textSub, unsigned int streamIdx,
		VideoFormat videoFormat, const wxString& resultFile) {
	if (IsCanceled())
		return false;
	//spumux
	wxString cmd = s_config.GetSpumuxCmd();
	wxString spuFile = resultFile + wxT("_spumux.xml");
	textSub->SaveSpumux(spuFile, videoFormat);
	cmd.Replace(wxT("$FILE_CONF"), spuFile);
	cmd.Replace(wxT("$STREAM"), wxString::Format(wxT("%u"), streamIdx));
	wxULongLong fileSize = wxFileName::GetSize(vobFile);
	SpumuxExecute exec(this, fileSize != wxInvalidSize ? fileSize.ToDouble() : -1);
	if (!exec.Execute(cmd, vobFile, resultFile)) {
		Failed();
		return false;
	}
	if (s_config.GetRemoveTempFiles()) {
		if (vobFile == resultFile + _T(".old"))
			DeleteFile(vobFile);
		DeleteFile(spuFile);
	}

	wxYield();
	return true;
}

bool ProgressDlg::GenerateDvdFilesystem(DVD* dvd, const wxString& dvdTmpDir, const wxString& dvdOutDir,
		const wxString& tmpDir) {
	if (IsCanceled())
		return false;
	wxString dvdauthFile = dvdTmpDir + _T("dvdauthor.xml");
	dvd->SaveDVDAuthor(dvdauthFile);
	AddSummaryMsg(_("Generating DVD"));
	SetSubSteps(300);
	wxString cmd = s_config.GetDvdauthorCmd();
	cmd.Replace(_T("$FILE_CONF"), dvdauthFile);
	cmd.Replace(_T("$DIR"), dvdOutDir.Mid(0, dvdOutDir.length() - 1));
	DVDAuthorExecute dvdauthorExec(this, dvd->GetSize(true) / 1024);
	if (!dvdauthorExec.Execute(cmd)) {
		Failed();
		return false;
	}
	// remove temp files
	if (s_config.GetRemoveTempFiles()) {
		wxDir d(dvdTmpDir);
		wxString fname;
		while (d.GetFirst(&fname, wxEmptyString, wxDIR_FILES))
			DeleteFile(dvdTmpDir + fname);
	}
	IncStep();
	return true;
}

bool ProgressDlg::Preview(BurnDlg* burnDlg, const wxString& dvdOutDir) {
	if (!burnDlg->DoPreview())
		return true;
	if (IsCanceled())
		return false;
	AddSummaryMsg(_("Start preview"));
	wxString cmd = s_config.GetPreviewCmd();
	if (cmd.length() > 0) {
		if (cmd == wxT("wmplayer")) {
			cmd = wxT("start wmplayer \"$DIR\\VIDEO_TS\\VIDEO_TS.VOB\"");
			cmd.Replace(wxT("$DIR"), dvdOutDir.Mid(0, dvdOutDir.length() - 1));
			if (!wxShell(cmd)) {
				wxString msg = _("Starting of DVD player is failed. \
Please check the path to the DVD player in the 'Settings/Core/Preview command' \
or open the following directory with your DVD player: ");
				msg += dvdOutDir;
				wxMessageBox(msg, _("Burn"), wxOK|wxICON_ERROR, this);
			}
		} else {
			if (cmd.Find(wxT("$DIR")) < 0) {
				if (cmd[0] != wxT('"'))
					cmd = wxT('"') + cmd + wxT('"');
				if (cmd.Find(wxT("wmplayer")) >= 0)
					cmd += wxT(" \"$DIR\\VIDEO_TS\\VIDEO_TS.VOB\"");
				else
					cmd += wxT(" \"dvd:///$DIR\"");
			}
			cmd.Replace(wxT("$DIR"), dvdOutDir.Mid(0, dvdOutDir.length() - 1));
			if (!Exec(cmd))
				Failed();
		}
	} else {
		wxString msg =
			_("Unfortunately there is no DVD player specified in the DVDStyler settings. \
Please set the path to the DVD player in the 'Settings/Core/Preview command' \
or open the following directory with your DVD player: ");
		msg += dvdOutDir;
		wxMessageBox(msg, _("Burn"), wxOK|wxICON_INFORMATION, this);
	}
	if (burnDlg->DoBurn() || burnDlg->DoCreateIso()) {
		wxString msg = burnDlg->DoBurn() ? _("Do you want to burn this video to DVD?") : _("Do you want to create an iso image of this video?");
		if (wxMessageBox(msg, _("Burn"), wxYES_NO|wxICON_QUESTION, this) == wxNO) {
			AddSummaryMsg(_("Aborted"), wxEmptyString, *wxRED);
			return false;
		}
	}
	return true;
}

long GetFreeSpaceOn(wxString dir) {
	wxDiskspaceSize_t pFree;
	wxGetDiskSpace(dir, NULL, &pFree);
	return (long) (pFree.ToDouble() / 1024);
}

bool ProgressDlg::CreateIsoImage(BurnDlg* burnDlg, DVD* dvd, const wxString& dvdOutDir, const wxString& tmpDir) {
#ifdef __WXMAC__
	bool createISO = burnDlg->DoCreateIso() || burnDlg->DoBurn();
#else
	bool createISO = burnDlg->DoCreateIso() || (burnDlg->DoBurn() && burnDlg->DoAddECC());
#endif
	if (createISO)  {
		if (IsCanceled())
			return false;
		wxString isoFile = burnDlg->DoCreateIso() ? burnDlg->GetIsoFile() : tmpDir + TMP_ISO;
		// check if there is enough space
		long size = 0;
		wxString cmd = s_config.GetIsoSizeCmd();
		cmd.Replace(_T("$DIR"), dvdOutDir.Mid(0, dvdOutDir.length() - 1));
#if defined(__WXMSW__) || defined(__WXMAC__)
		cmd = wxGetAppPath() + wxString(wxFILE_SEP_PATH) + cmd;
#endif
		wxArrayString output;
		wxExecute(cmd, output, wxEXEC_SYNC | wxEXEC_NODISABLE);
		if (output.Count() > 0 && output[0].length() > 0) {
			output[0].ToLong(&size);
			size = (size + 254)*2;
		}
		long freeSpace = GetFreeSpaceOn(wxFileName(isoFile).GetPath());
		if (size > freeSpace && freeSpace == GetFreeSpaceOn(m_cache->GetTempDir())) {
			AddDetailMsg(_("There is not enough space to store ISO: cache emptying."));
			m_cache->Clear();
		}
		AddSummaryMsg(_("Creating ISO image"));
		SetSubSteps(100);
		cmd = s_config.GetIsoCmd();
		cmd.Replace(_T("$VOL_ID"), dvd->GetLabel());
		cmd.Replace(_T("$DIR"), dvdOutDir.Mid(0, dvdOutDir.length() - 1));
		cmd.Replace(_T("$FILE"), isoFile);
		ProgressExecute exec(this, wxT(".*"));
		if (!exec.Execute(cmd)) {
			Failed();
			return false;
		}
		IncStep();
	}
	return true;
}

bool ProgressDlg::AddEccData(BurnDlg* burnDlg, const wxString& tmpDir) {
	if (!burnDlg->DoAddECC() || (!burnDlg->DoCreateIso() && !burnDlg->DoBurn()))
		return true;
	if (IsCanceled())
		return false;
	AddSummaryMsg(_("Adding ECC data"));
	SetSubSteps(200);
	wxString cmd = s_config.GetAddECCCmd();
	cmd.Replace(_T("$FILE"), burnDlg->DoCreateIso() ? burnDlg->GetIsoFile() : tmpDir + TMP_ISO);
	ProgressExecute exec(this, wxT("(Preparing|Ecc).*"));
	if (!exec.Execute(cmd)) {
		Failed();
		return false;
	}
	IncStep();
	return true;
}

bool ProgressDlg::FormatDvd(BurnDlg* burnDlg) {
	if (!burnDlg->DoBurn() || !burnDlg->DoFormat())
		return true;
	if (IsCanceled())
		return false;
	AddSummaryMsg(_("Formatting DVD-RW"));
	while (1) {
		SetSubSteps(100);
		wxString cmd = s_config.GetFormatCmd();
		wxString device = burnDlg->GetDevice();
#ifdef __WXMAC__
		if (device.Mid(0,5) != wxT("/dev/")) {
			while (!IsMediaPresent(device) || !IsMediaErasable(device)) {
				if (IsMediaBlank(device))
					return true;
				if (wxMessageBox(wxString::Format(_("Please insert erasable DVD into the device %s."),
						GetDeviceName(device).c_str()), _("Burn"), wxOK|wxCANCEL, this) == wxCANCEL) {
					AddSummaryMsg(_("Aborted"), wxEmptyString, *wxRED);
					return false;
				}
			}
		}
#endif		
		cmd.Replace(wxT("$DEV"), device);
		if (!Exec(cmd)) {
			int repeat = wxMessageBox(_("Formatting DVD-RW failed. Try again?"),
			_("Burn"), wxYES_NO|wxCANCEL | wxICON_QUESTION, this);
			if (repeat == wxYES) {
				continue;
			} else if (repeat == wxNO) {
				AddSummaryMsg(_("-> skipped <-"), wxEmptyString, wxColour(128, 64, 64));
				break;
			} else {
				Failed();
				return false;
			}
		}
		break;
	}
	IncStep();
	return true;
}

bool ProgressDlg::BurnDvd(BurnDlg* burnDlg, DVD* dvd, const wxString& dvdOutDir, const wxString& tmpDir) {
	if (!burnDlg->DoBurn())
		return true;
	if (IsCanceled())
		return false;
	AddSummaryMsg(_("Burning"));
	SetSubSteps(100);
	// check disc
	wxString device = burnDlg->GetDevice();
	wxString cmd;
#ifdef __WXMAC__
	while (!IsMediaPresent(device)) {
		if (wxMessageBox(wxString::Format(_("Please insert empty DVD into the device %s."),
				GetDeviceName(device).c_str()), _("Burn"), wxOK|wxCANCEL, this) == wxCANCEL) {
		   AddSummaryMsg(_("Aborted"), wxEmptyString, *wxRED);
		   return false;
		}
	}
	long discSize = GetMediaSpaceFree(device); // size in 2048 blocks
	AddDetailMsg(wxString::Format(wxT("Disc size: %ld MB"), discSize / 512));
#else
	cmd = s_config.GetBurnScanCmd();
	cmd.Replace(wxT("$DEVICE"), device);
#ifdef __WXMSW__
	cmd = wxGetAppPath() + wxString(wxFILE_SEP_PATH) + cmd;
#endif
	wxArrayString output;
	while (true) {
		if (wxExecute(cmd, output, wxEXEC_SYNC | wxEXEC_NODISABLE) == 0)
			break;
		if (wxMessageBox(wxString::Format(_("Please insert empty DVD into the device %s."), device.c_str()),
				_("Burn"), wxOK|wxCANCEL, this) == wxCANCEL) {
			AddSummaryMsg(_("Aborted"), wxEmptyString, *wxRED);
			return false;
		}
	}
	// get disc size
	long discSize = 0; // size in 2048 blocks
	for (unsigned int i = 0; i < output.Count(); i++) {
		if (output[i].length() > 12 && output[i].SubString(1, 12) == wxT("Free Blocks:")) {
			wxString discSizeStr = output[i].AfterFirst(wxT(':')).Trim(false).BeforeFirst(wxT('*'));
			discSizeStr.ToLong(&discSize);
			AddDetailMsg(wxString::Format(wxT("Disc size: %ld MB"), discSize / 512));
			break;
		}
	}
	if (discSize < 2290000)
		discSize = 2295104;
#endif
	// check size
	long size = 0;
#ifdef __WXMAC__
	bool burnIso = true;
#else
	bool burnIso = burnDlg->DoAddECC();
#endif
	if (burnIso) {
		size = wxFile(tmpDir + TMP_ISO).Length() / 2048; // size in 2048 blocks
	} else {
		cmd = s_config.GetIsoSizeCmd();
		cmd.Replace(_T("$DIR"), dvdOutDir.Mid(0, dvdOutDir.length() - 1));
#if defined(__WXMSW__) || defined(__WXMAC__)
		cmd = wxGetAppPath() + wxString(wxFILE_SEP_PATH) + cmd;
#endif
		wxArrayString output;
		wxExecute(cmd, output, wxEXEC_SYNC | wxEXEC_NODISABLE);
		if (output.Count() > 0 && output[0].length() > 0) {
			output[0].ToLong(&size);
			size = size + 254;
		}
	}
	AddDetailMsg(wxString::Format(wxT("ISO Size: %ld MB"), size / 512));
	if (size > discSize && wxMessageBox(wxString::Format(_("Size of Disc Image > %.2f GB. Do you want to continue?"), (double) discSize / 512
			/ 1024), _("Burn"), wxYES_NO|wxICON_QUESTION, this) == wxNO) {
		AddSummaryMsg(_("Aborted"), wxEmptyString, *wxRED);
		return false;
	}
	// burn
	if (burnIso) {
		cmd = s_config.GetBurnISOCmd();
		cmd.Replace(_T("$FILE"), tmpDir + TMP_ISO);
		// get iso size in sectors
		long size = wxFile(tmpDir + TMP_ISO).Length() / 2048;
		cmd.Replace(_T("$SIZE"), wxString::Format(wxT("%ld"), size));
	} else {
		cmd = s_config.GetBurnCmd();
		cmd.Replace(_T("$DIR"), dvdOutDir.Mid(0, dvdOutDir.length() - 1));
	}
	cmd.Replace(_T("$VOL_ID"), dvd->GetLabel());
	cmd.Replace(_T("$DEV"), device);
	wxString speedStr;
	if (burnDlg->GetSpeed() > 0) {
		speedStr = s_config.GetBurnSpeedOpt();
		speedStr.Replace(_T("$SPEED"), wxString::Format(_T("%d"), burnDlg->GetSpeed()));
	}
	cmd.Replace(_T("$SPEEDSTR"), speedStr);
	BurnExecute exec(this, wxT(".*"));
	if (!exec.Execute(cmd)) {
		Failed();
		return false;
	}
	IncStep();
	return true;
}

bool ProgressDlg::Transcode(Vob* vob, AspectRatio aspect, int videoBitrate, bool useMplex, AudioFormat defAudioFormat) {
	if (IsCanceled())
		return false;
	int subStep = GetSubStep();
	AddDetailMsg(_("Transcode video file: ") + vob->GetFilename());
	// set output formats
	double startTime = vob->GetStartTime();
	double recordingTime = vob->GetRecordingTime();
	if (vob->GetSlideshow())
		recordingTime = vob->GetSlideshow()->GetDuration() * vob->GetSlideshow()->GetCount();
	bool needEncode = vob->GetAudioFilenames().GetCount() != vob->GetAudioStreamCount()
			|| startTime != 0 || recordingTime != -1; // some files are not elementary streams
	VideoFormat videoFormat = vfCOPY;
	bool ntscFilm = false;
	wxArrayInt audioFormats;
	wxArrayInt subtitleFormats;
	for (unsigned int stIdx = 0; stIdx < vob->GetStreams().GetCount(); stIdx++) {
		Stream* stream = vob->GetStreams()[stIdx];
		switch (stream->GetType()) {
		case stVIDEO:
			videoFormat = stream->GetVideoFormat();
			ntscFilm = lround(stream->GetSourceFps()) == 24;
			needEncode = needEncode || stream->GetVideoFormat() != vfCOPY;
			break;
		case stAUDIO:
			audioFormats.Add(stream->GetAudioFormat());
			needEncode = needEncode || stream->GetAudioFormat() != afCOPY;
			break;
		case stSUBTITLE:
			subtitleFormats.Add(stream->GetSubtitleFormat());
			break;
		default:
			break;
		}
	}
	
	AddDetailMsg(wxString(wxT("Need encode: ")) + (needEncode ? wxT("true") : wxT("false"))
			+ wxString(wxT(", use mplex: ")) + (useMplex ? wxT("true") : wxT("false")));
	
	// transcode
	if (useMplex && !needEncode) {
		if (!Multiplex(vob->GetFilename(), vob->GetAudioFilenames(), vob->GetTmpFilename()))
			return false;
	} else {
		// add input files
		bool disableAudio = vob->GetStreams().GetCount() > 1 + vob->GetAudioFilenames().GetCount()
			&& vob->GetStreams()[1]->GetType() == stAUDIO && vob->GetStreams()[1]->GetDestinationFormat() == afNONE;
		wxFfmpegMediaTranscoder transcoder(s_config.GetThreadCount());
		if (!transcoder.AddInputFile(vob->GetFilename(), false, disableAudio)) {
			Failed(wxT("Error by transcoding of ") + vob->GetFilename());
			return false;
		}
		for (int i = 0; i < (int)vob->GetAudioFilenames().size(); i++) {
			int stIdx = vob->GetStreams().size() - vob->GetAudioFilenames().size() + i;
			if (!transcoder.AddInputFile(vob->GetAudioFilenames()[i], false, false, false,
					vob->GetStreams()[stIdx]->GetTsOffset())) {
				Failed(wxT("Error by transcoding of ") + vob->GetAudioFilenames()[i]);
				return false;
			}
		}
		if (vob && vob->GetFilename().length() && !vob->HasAudio()) {
			wxString zero = wxT("pipe:-1");
			if (!transcoder.AddInputFile(zero, false, false, false, 0)) {
				Failed(wxT("Error by transcoding of ") + zero);
				return false;
			}
			audioFormats.Add(defAudioFormat);
		}
		
		// set output options
		transcoder.SetInterlaced(vob->GetInterlaced());
		transcoder.SetFirstField(vob->GetFirstField());
		if (vob->GetKeepAspectRatio()) {
			vob->UpdatePad(aspect);
		}
		
		// set video filters
		wxString videoFilters = vob->GetAllVideoFilters();
		if (videoFilters.length() > 0) {
			wxLogMessage(wxT("Video filters: ") + videoFilters);
		}
		transcoder.SetVideoFilters(videoFilters);
		
		// set audio volume
		int audioIdx = 0;
		for (unsigned int stIdx = 0; stIdx < vob->GetStreams().GetCount(); stIdx++) {
			Stream* stream = vob->GetStreams()[stIdx];
			if (stream->GetType() == stAUDIO) {
				if (stream->GetAudioVolume() != 256)
					transcoder.SetAudioVolume(audioIdx, stream->GetAudioVolume());
				if (stream->GetChannelNumber() > 0)
					transcoder.SetChannelNumber(audioIdx, stream->GetChannelNumber());
				audioIdx++;
			}
		}
		
		if (startTime != 0) {
			wxLogMessage(wxT("startTime: %f"), startTime);
			transcoder.SetStartTime(startTime);
		}
		if (recordingTime > 0) {
			wxLogMessage(wxT("recordingTime: %f"), recordingTime);
			transcoder.SetRecordingTime(recordingTime);
		}
		
		if (!useMplex) {
			double fps = GetFps(videoFormat, ntscFilm);
			AVConvExecute exec(this, lround(vob->GetDuration() * fps));
			if (!transcoder.SetOutputFile(vob->GetTmpFilename(), videoFormat, ntscFilm, audioFormats, subtitleFormats,
					videoBitrate, s_config.GetVbr(), s_config.GetAudioBitrate())
					|| !exec.Execute(transcoder.GetCmd())) {
				if (wxFileExists(vob->GetTmpFilename()))
					wxRemoveFile(vob->GetTmpFilename());
				Failed(_("Error transcoding of ") + vob->GetFilename());
				return false;
			}
		} else {
			wxString videoFile = vob->GetTmpFilename() + wxT(".m2v");
			if (wxFileExists(videoFile) && !wxRemoveFile(videoFile)) {
				wxLogError(wxString::Format(_("Can't remove file '%s'"), videoFile.c_str()));
			}
			if (!transcoder.SetOutputFile(videoFile, videoFormat, ntscFilm, afNONE, sfNONE,
					videoBitrate, s_config.GetVbr(), s_config.GetAudioBitrate(), vob->GetVideoStreamIndex())) {
				Failed(_("Error transcoding of ") + vob->GetFilename());
				return false;
			}
			wxArrayString audioFiles;
			for (unsigned int audioIdx = 0; audioIdx < audioFormats.GetCount(); audioIdx++) {
				if (audioFormats[audioIdx] == afNONE)
					continue;
				wxString audioFile = vob->GetTmpFilename() + wxString::Format(wxT(".audio%u"), audioIdx);
				audioFiles.Add(audioFile);
				if (wxFileExists(audioFile) && !wxRemoveFile(audioFile)) {
					wxLogError(wxString::Format(_("Can't remove file '%s'"), audioFile.c_str()));
				}
				if (!transcoder.SetOutputFile(audioFile, vfNONE, false, (AudioFormat) audioFormats[audioIdx], sfNONE,
						videoBitrate, s_config.GetVbr(), s_config.GetAudioBitrate(), audioIdx + 1)) {
					Failed(_("Error transcoding of ") + vob->GetFilename());
					return false;
				}
			}
			double fps = GetFps(videoFormat, ntscFilm);
			AVConvExecute exec(this, lround(vob->GetDuration() * fps));
			if (!exec.Execute(transcoder.GetCmd())) {
				if (wxFileExists(videoFile))
					wxRemoveFile(videoFile);
				for (unsigned int audioIdx = 0; audioIdx < audioFiles.GetCount(); audioIdx++)
					if (wxFileExists(audioFiles[audioIdx]))
						wxRemoveFile(audioFiles[audioIdx]);
				Failed(_("Error transcoding of ") + vob->GetFilename());
				return false;
			}
			SetSubStep(subStep+150);
			if (!Multiplex(videoFile, audioFiles, vob->GetTmpFilename())) {
				if (wxFileExists(videoFile))
					wxRemoveFile(videoFile);
				for (unsigned int audioIdx = 0; audioIdx < audioFiles.GetCount(); audioIdx++)
					if (wxFileExists(audioFiles[audioIdx]))
						wxRemoveFile(audioFiles[audioIdx]);
				return false;
			}
			// remove temp files
			if (s_config.GetRemoveTempFiles()) {
				DeleteFile(videoFile);
				for (unsigned int audioIdx = 0; audioIdx < audioFiles.GetCount(); audioIdx++)
					DeleteFile(audioFiles[audioIdx]);
			}
		}
	}
	SetSubStep(subStep+200);
	return true;
}

bool ProgressDlg::Multiplex(const wxString& videoFile, const wxArrayString& audioFiles,
		const wxString& vobFile) {
	if (IsCanceled())
		return false;
	AddDetailMsg(_("Multiplexing video and audio streams"));
	wxString cmd = s_config.GetMplexCmd();
	cmd.Replace(_T("$FILE_VIDEO"), videoFile);
	wxString audio;
	for (unsigned int i = 0; i < audioFiles.Count(); i++)
		audio += (i > 0 ? wxT("\" \"") : wxT("")) + audioFiles[i];
	cmd.Replace(_T("$FILE_AUDIO"), audio);
	cmd.Replace(_T("$FILE_OUT"), vobFile);
	if (!Exec(cmd)) {
		Failed();
		return false;
	}
	return true;
}

bool ProgressDlg::GenerateMenu(Menu* menu, WidescreenType widescreenType, const wxString& menuFile,
		AudioFormat audioFormat, wxString audioFile, int videoBitrate) {
	if (IsCanceled())
		return false;
	wxString mpegFile = menuFile + _T("_bg.mpg");
	wxString m2vFile = menuFile + _T("_bg.m2v");
	wxString audioFileTmp = menuFile + _T("_bg.audio");
	wxString btFile = menuFile + _T("_buttons.png");
	wxString hlFile = menuFile + _T("_highlight.png");
	wxString selFile = menuFile + _T("_select.png");
	wxString spuFile = menuFile + _T("_spumux.xml");

	bool videoMenu = menu->GetSVG()->GetDuration() > 0;
	wxYield();

	AddDetailMsg(_("Create menu MPEG"));
	if (s_config.GetMenuVideoBitrate() < 1000)
		s_config.SetMenuVideoBitrate(1000);
	
	if (videoMenu) {
		// get background audio format
		AudioFormat bgAudioFormat = afNONE;
		wxString bgAudioOutputFormat;
		if (menu->HasVideoBackground()) {
			wxFfmpegMediaDecoder ffmpeg;
			if (!ffmpeg.Load(menu->GetBackground()))
				return false;
			for (unsigned int i = 0; i < ffmpeg.GetStreamCount(); i++) {
				if (ffmpeg.GetStreamType(i) == stAUDIO) {
					if (ffmpeg.GetCodecName(i) != wxT("mp2") && ffmpeg.GetCodecName(i) != wxT("ac3")) {
						bgAudioFormat = audioFormat;
					} else if (ffmpeg.GetSampleRate(i) != 48000) {
						bgAudioFormat = ffmpeg.GetCodecName(i) == wxT("mp2") ? afMP2 : afAC3;
					} else {
						bgAudioFormat = afCOPY;
						bgAudioOutputFormat = ffmpeg.GetCodecName(i);
					}
					break;
				}
			}
		}
		// encode video
		bool hasAudio = audioFile.length() || bgAudioFormat != afNONE;
		wxFfmpegMediaEncoder ffmpeg(s_config.GetThreadCount());
		if (!ffmpeg.BeginEncode(hasAudio ? m2vFile : mpegFile, menu->GetVideoFormat(), hasAudio ? afNONE : audioFormat,
				menu->GetAspectRatio(), s_config.GetMenuVideoBitrate())) {
			Failed(_("Error creation of menu"));
			return false;
		}
	    wxSVGDocument* svg = menu->GetBackgroundSVG();
	    int width = menu->GetFrameResolution().GetWidth();
	    int height = menu->GetFrameResolution().GetHeight();
	    AddDetailMsg(wxString::Format(_("Video duration: %f sec"), svg->GetDuration()));
	    int frameCount = (int) (svg->GetDuration() * (menu->GetVideoFormat() == vfPAL ? 25.0 : 30000.0 / 1001));
	    if (frameCount == 0)
	    	frameCount = s_config.GetMenuFrameCount();
		for (int f = 0; f < frameCount; f++) {
			if (IsCanceled())
				return false;
			if (f % 100 == 0)
				AddDetailMsg(wxString::Format(_("Encoding frame %d of %d"), f, frameCount));
			wxImage img = svg->Render(width, height, NULL, false);
			if (!ffmpeg.EncodeImage(img, 1)) {
				Failed(_("Error creation of menu"));
				return false;
			}
			svg->SetCurrentTime(menu->GetVideoFormat() == vfPAL ? ((double) f) / 25 : ((double) f) * 1001 / 30000);
			SetSubStep(200 * (f + 1) / frameCount);
		}
		ffmpeg.EndEncode();
		if (hasAudio) {
			Vob menuVob;
			menuVob.SetFilename(m2vFile);
			menuVob.GetVideoStream()->SetDestinationFormat(vfCOPY);
			if (audioFile.length()) {
				menuVob.AddAudioFile(audioFile);
			} else {
				AddDetailMsg(_("Transcode audio from") + wxString(wxT(" ")) + menu->GetBackground());
				// transcode audio
				wxFfmpegMediaTranscoder transcoder(s_config.GetThreadCount());
				if (!transcoder.AddInputFile(menu->GetBackground(), true)) {
					Failed(wxT("Error by transcoding of ") + menu->GetBackground());
					return false;
				}
				transcoder.SetOutputFormat(bgAudioOutputFormat);
				if (!transcoder.SetOutputFile(audioFileTmp, vfNONE, false, bgAudioFormat, sfNONE, 0, false,
						s_config.GetAudioBitrate())) {
					Failed(_("Error transcoding of ") + menu->GetBackground());
					return false;
				}
				AVConvExecute exec(this, -1);
				if (!exec.Execute(transcoder.GetCmd())) {
					if (wxFileExists(audioFileTmp))
						wxRemoveFile(audioFileTmp);
					Failed(_("Error transcoding of ") + menu->GetBackground());
					return false;
				}
				menuVob.AddAudioFile(audioFileTmp);
			}
			AddDetailMsg(_("Multiplexing audio and video"));
			for (unsigned int i=0; i<menuVob.GetStreams().GetCount(); i++) {
				Stream* stream = menuVob.GetStreams()[i];
				if (stream->GetType() == stAUDIO && stream->GetAudioFormat() == afCOPY) {
					// change destination format if it set to COPY and codec is not mp2/ac3 or sample rate != 48 kHz
					if (stream->GetSourceCodecName() != wxT("mp2") && stream->GetSourceCodecName() != wxT("ac3")) {
						stream->SetDestinationFormat(audioFormat);
					} else if (stream->GetSourceSampleRate() != 48000) {
						stream->SetDestinationFormat(stream->GetSourceCodecName() == wxT("mp2") ? afMP2 : afAC3);
					}
				}
			}
			menuVob.SetKeepAspectRatio(false);
			menuVob.SetTmpFilename(mpegFile);
			if (!s_config.GetUseMplexForMenus())
				AddDetailMsg(WARNING_MPLEX_MSG, *wxRED);
			if (!Transcode(&menuVob, menu->GetAspectRatio(), s_config.GetMenuVideoBitrate(),
					s_config.GetUseMplexForMenus()))
				return false;
			if (s_config.GetRemoveTempFiles()) {
				DeleteFile(m2vFile);
				if (wxFileExists(audioFileTmp))
					wxRemoveFile(audioFileTmp);
			}
		}
	} else {
		wxImage bgImage = menu->GetBackgroundImage();
		if (!bgImage.Ok()) {
			Failed(_("Error creation of menu"));
			return false;
		}
		int frameCount = s_config.GetMenuFrameCount();
		if (audioFile.length() > 0) {
			wxFfmpegMediaDecoder decoder;
			if (decoder.Load(audioFile)) {
				double duration = decoder.GetDuration();
				if (duration > 0) {
					AddDetailMsg(wxString::Format(_("Audio duration: %f sec"), duration));
					if (menu->GetVideoFormat() == vfPAL)
						frameCount = (int) (duration * 25);
					else
						frameCount = (int) (duration * 30000/1001);
				}
			}
		}
		wxFfmpegMediaEncoder ffmpeg(s_config.GetThreadCount());
		if (!ffmpeg.BeginEncode(audioFile.length() ? m2vFile : mpegFile, menu->GetVideoFormat(),
				audioFile.length() ? afNONE : audioFormat, menu->GetAspectRatio(), s_config.GetMenuVideoBitrate())
				|| !ffmpeg.EncodeImage(bgImage, frameCount, &m_cancel)) {
			Failed(_("Error creation of menu"));
			return false;
		}
		ffmpeg.EndEncode();
		IncSubStep(25);

		if (audioFile.length()) {
			IncSubStep(75);
			// mplex (and optionally transcode audio)
			if (IsCanceled())
				return false;
			AddDetailMsg(_("Multiplexing audio and video"));
			Vob menuVob;
			menuVob.SetFilename(m2vFile);
			menuVob.AddAudioFile(audioFile);
			for (unsigned int i=0; i<menuVob.GetStreams().GetCount(); i++) {
				Stream* stream = menuVob.GetStreams()[i];
				if (stream->GetType() == stAUDIO && stream->GetAudioFormat() == afCOPY) {
					// change destination format if it set to COPY and codec is not mp2/ac3 or sample rate != 48 kHz
					if (stream->GetSourceCodecName() != wxT("mp2") && stream->GetSourceCodecName() != wxT("ac3")) {
						stream->SetDestinationFormat(audioFormat);
					} else if (stream->GetSourceSampleRate() != 48000) {
						stream->SetDestinationFormat(stream->GetSourceCodecName() == wxT("mp2") ? afMP2 : afAC3);
					}
				}
			}
			menuVob.SetTmpFilename(mpegFile);
			if (!s_config.GetUseMplexForMenus())
				AddDetailMsg(WARNING_MPLEX_MSG, *wxRED);
			if (!Transcode(&menuVob, menu->GetAspectRatio(), s_config.GetMenuVideoBitrate(),
					s_config.GetUseMplexForMenus()))
				return false;
			if (s_config.GetRemoveTempFiles())
				DeleteFile(m2vFile);
		}
	}

	//spumux
	if (IsCanceled())
		return false;
	AddDetailMsg(_("Multiplexing subpictures into mpeg"));
	int stCount = menu->GetAspectRatio() == ar4_3 ? 1 : widescreenType != wtAUTO ? 2 : 3;
	for (int stIdx = 0; stIdx < stCount; stIdx++) {
		if (stIdx > 0) {
			if (mpegFile == menu->GetBackground())
				mpegFile = menuFile + _T("_bg.mpg");
			if (!wxRenameFile(menuFile, mpegFile, false)) {
				Failed(wxString::Format(_("Can't rename file '%s' in '%s'"), menuFile.c_str(), mpegFile.c_str()));
				return false;
			}
		}
		// save subpictures
		SubStreamMode mode = menu->GetAspectRatio() == ar4_3 ? ssmNORMAL : ssmWIDESCREEN;
		if (stIdx == 1)
			mode = widescreenType == wtNOLETTERBOX ? ssmPANSCAN : ssmLETTERBOX;
		else if (stIdx == 2)
			mode = ssmPANSCAN;
		wxImage* images = menu->GetSubPictures(mode);
		images[0].SaveFile(btFile);
		images[1].SaveFile(hlFile);
		images[2].SaveFile(selFile);
		delete[] images;
		// save spumux
		menu->SaveSpumux(spuFile, mode, btFile, hlFile, selFile);
		wxString cmd = s_config.GetSpumuxCmd();
		cmd.Replace(wxT("$FILE_CONF"), spuFile);
		cmd.Replace(wxT("$STREAM"), wxString::Format(wxT("%d"), stIdx));
		wxULongLong fileSize = wxFileName::GetSize(mpegFile);
		SpumuxExecute exec(this, fileSize != wxInvalidSize ? fileSize.ToDouble() : -1);
		if (!exec.Execute(cmd, mpegFile, menuFile)) {
			Failed();
			return false;
		}
		if (s_config.GetRemoveTempFiles() || stIdx + 1 < stCount) {
			if ((!videoMenu || mpegFile != menu->GetBackground()))
				DeleteFile(mpegFile);
			DeleteFile(btFile);
			DeleteFile(hlFile);
			DeleteFile(selFile);
			DeleteFile(spuFile);
		}
	}
	
	wxYield();
	return true;
}

bool ProgressDlg::GenerateSlideshow(Slideshow* slideshow, const wxString& vobFile, AudioFormat audioFormat,
		wxString audioFile) {
	if (IsCanceled())
		return false;
	AddDetailMsg(_("Generating slideshow"));
	
	wxString m2vFile = vobFile + wxT("_video.m2v");

	wxFfmpegMediaEncoder ffmpeg(s_config.GetThreadCount());
	if (!ffmpeg.BeginEncode(audioFile.length() ? m2vFile : vobFile, slideshow->GetVideoFormat(),
			audioFile.length() ? afNONE : audioFormat, slideshow->GetAspectRatio(),
			s_config.GetSlideshowVideoBitrate())) {
		Failed(_("Error creation of slideshow"));
		return false;
	}

	for (unsigned i = 0; i < slideshow->GetCount(); i++) {
		AddDetailMsg(wxString::Format(_("Converting slide %u image to video"), i + 1));
		wxYield();
		wxImage img = slideshow->GetImage(i);
		if (!ffmpeg.EncodeImage(img, (int)(slideshow->GetDuration()*slideshow->GetFPS()))) {
			Failed(_("Error creation of slideshow"));
			return false;
		}
		IncSubStep(10);
	}
	ffmpeg.EndEncode();
	
	if (audioFile.length()) {
		// mplex (and optionally transcode audio)
		if (IsCanceled())
			return false;
		AddDetailMsg(_("Multiplexing audio and video"));
		Vob slideShowVob;
		slideShowVob.SetFilename(m2vFile);
		slideShowVob.AddAudioFile(audioFile);
		for (unsigned int i=0; i<slideShowVob.GetStreams().GetCount(); i++) {
			Stream* stream = slideShowVob.GetStreams()[i];
			if (stream->GetType() == stAUDIO && stream->GetAudioFormat() == afCOPY) {
				// set destination format if it set to COPY and codec is not mp2/ac3 or sample rate != 48 kHz
				if (stream->GetSourceCodecName() != wxT("mp2")
						&& stream->GetSourceCodecName() != wxT("ac3")
						&& stream->GetSourceCodecName() != wxT("liba52")) {
					stream->SetDestinationFormat(audioFormat);
				} else if (stream->GetSourceSampleRate() != 48000) {
					stream->SetDestinationFormat(stream->GetSourceCodecName() == wxT("mp2") ? afMP2 : afAC3);
				}
			}
		}
		slideShowVob.SetTmpFilename(vobFile);
		if (!Transcode(&slideShowVob, slideshow->GetAspectRatio(), s_config.GetSlideshowVideoBitrate(),
				s_config.GetUseMplexForMenus()))
			return false;
		if (s_config.GetRemoveTempFiles())
			DeleteFile(m2vFile);
	}

	wxYield();
	return true;
}

bool ProgressDlg::DeleteFile(wxString fname) {
	if (!wxRemoveFile(fname)) {
		AddDetailMsg(wxString::Format(_("Can't remove file '%s'"), fname.c_str()), *wxRED);
		return false;
	}
	return true;
}

bool ProgressDlg::DeleteDir(wxString dir, bool subdirs) {
	if (dir.Last() != wxFILE_SEP_PATH)
		dir += wxFILE_SEP_PATH;
	wxDir d(dir);
	wxString fname;
	while (subdirs && d.GetFirst(&fname, wxEmptyString, wxDIR_DIRS))
		if (!DeleteDir(dir + fname, true))
			return false;
	while (d.GetFirst(&fname, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN))
		if (!DeleteFile(dir + fname))
			return false;
	d.Open(wxGetHomeDir());
	wxLogNull log;
	wxRmdir(dir);
	return true;
}

bool ProgressDlg::DeleteTempFiles(const wxString& tmpDir, const wxString& dvdTmpDir, const wxString& dvdOutDir,
		bool deleteOutDir) {
	AddDetailMsg(_("Cleaning temporary directory"));
	if (wxDirExists(dvdTmpDir) && !DeleteDir(dvdTmpDir, false))
		return false;
	if (deleteOutDir && wxDirExists(dvdOutDir) && !DeleteDir(dvdOutDir, true))
		return false;
	if (wxFileExists(tmpDir + TMP_ISO) && !DeleteFile(tmpDir + TMP_ISO))
		return false;
	return true;
}

bool ProgressDlg::Exec(wxString command, wxString inputFile, wxString outputFile) {
	ProcessExecute exec(this);
	return exec.Execute(command, inputFile, outputFile);
}
