/////////////////////////////////////////////////////////////////////////////
// Name:        ProgressDlg.cpp
// Purpose:     Progress of generation/burning dialog
// Author:      Alex Thuering
// Created:     14.08.2004
// RCS-ID:      $Id: ProgressDlg.cpp,v 1.90 2010/03/16 21:01:26 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 <wxSVG/SVGDocument.h>
#include <wxSVG/mediadec_ffmpeg.h>

#define DATA_FILE(fname) wxFindDataFile(_T("data") + wxString(wxFILE_SEP_PATH) + fname)
#define TMP_ISO wxT("dvd-out.iso")

////////////////////////// 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 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 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:
	/**
	 * Consructor.
	 */
	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()

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_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& WXUNUSED(event)) {
	((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());
}

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
		EndModal(wxID_OK);
}

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

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

bool ProgressDlg::Start(BurnDlg* burnDlg, DVD* dvd) {
	// disable window and redirect log messages into progress dialog details area
	m_winDisabler = new wxWindowDisabler(this);
	wxLog* previousLog = wxLog::SetActiveTarget(new ProgressDlgLog(this));
	// show dialog
	Show();
	// start
	Run(burnDlg, dvd);
	bool isOk = !IsCanceled();
	// end
	End();
	// restore log and enable window
	delete wxLog::SetActiveTarget(previousLog);
	delete m_winDisabler;
	m_winDisabler = NULL;
	// close the Window or release the controls
	if (m_autoStart)
		Close(true);
    else
    	ShowModal();
	
	return isOk;
}

int ProgressDlg::GetMenuSubSteps(wxArrayPtrVoid& menuVobs, 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;
			wxString menuFile = dvdTmpDir + wxString::Format(_T("menu%d-%d.mpg"), tsi + 1, pgci);
			Vob* vob = pgc->GetVobs()[0];
			vob->SetTmpFilename(menuFile);
			menuSubSteps += 10; // perpare
			if (vob->GetMenu()->HasVideoBackground()) {
				menuSubSteps += 200; // transcode
			} else if (vob->GetAudioFilenames().size() > 0)
				menuSubSteps += 300; // generate mpeg(100) + transcode(200)
			else
				menuSubSteps += 25; // generate mpeg
			menuSubSteps += 50; // spumux
			menuVobs.Add(vob);
		}
	}
	return menuSubSteps;
}

int ProgressDlg::GetTitleSubSteps(wxArrayPtrVoid& titleVobs, 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);
			}
		}
	}
	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()->Count();
					vob->SetTmpFilename(dvdTmpDir
							+ wxString::Format(_T("title%d-%d-%d.vob"), tsi, pgci, vobi));
					slideshowVobs.Add(vob);
				}
			}
		}
	}
	return slideshowSubSteps;
}

int ProgressDlg::GetSubtitleSubSteps(wxArrayPtrVoid& subtitleVobs, 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->GetSize(dvd) * vob->GetSubtitles().Count();
					vob->SetTmpFilename(dvdTmpDir
							+ wxString::Format(_T("title%d-%d-%d.vob"), tsi, pgci, vobi));
					subtitleVobs.Add(vob);
				}
			}
		}
	}
	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;
	int menuSubSteps = GetMenuSubSteps(menuVobs, dvd, dvdTmpDir);
	// titles
	wxArrayPtrVoid titleVobs;
	int titleSubSteps = GetTitleSubSteps(titleVobs, dvd, m_cache);
	// slideshow
	wxArrayPtrVoid slideshowVobs;
	int slideshowSubSteps = GetSlideshowSubSteps(slideshowVobs, dvd, dvdTmpDir);
	// subtitle
	wxArrayPtrVoid subtitleVobs;
	int subtitleSubSteps = GetSubtitleSubSteps(subtitleVobs, 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, menuSubSteps, dvd)
			|| !Transcode(titleVobs, titleSubSteps, dvd)
			|| !GenerateSlideshow(slideshowVobs, slideshowSubSteps, dvd)
			|| !MultiplexSubtitles(subtitleVobs, 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 ((burnDlg->DoCreateIso() || burnDlg->DoBurn()) && s_config.GetRemoveTempFiles())
		DeleteTempFiles(tmpDir, dvdTmpDir, dvdOutDir);

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

	s_config.SetKey(s_config.GetKey() + 1);
}

bool ProgressDlg::CleanTemp(const wxString& tmpDir, const wxString& dvdTmpDir, const wxString& dvdOutDir) {
	if (wxDir::Exists(tmpDir) && !DeleteTempFiles(tmpDir, dvdTmpDir, dvdOutDir))
		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, int menuSubSteps, DVD* dvd) {
	if (menuVobs.Count() == 0)
		return true;
	if (IsCanceled())
		return false;
	AddSummaryMsg(_("Generating menus"));
	SetSubSteps(menuSubSteps);
	for (int i = 0; i < (int) menuVobs.Count(); i++) {
		AddDetailMsg(wxString::Format(_("Generating menu %d of %d"), i + 1, menuVobs.Count()));
		Vob* vob = (Vob*) menuVobs[i];
		if (!GenerateMenu(vob->GetMenu(), vob->GetTmpFilename(), dvd->GetAudioFormat(),
				vob->GetAudioFilenames().size() > 0 ? vob->GetAudioFilenames()[0] : wxT(""),
				dvd->GetVideoBitrate()))
			return false;
	}
	IncStep();
	return true;
}

bool ProgressDlg::Transcode(wxArrayPtrVoid& titleVobs, int titleSubSteps, DVD* dvd) {
	if (titleVobs.Count() == 0)
		return true;
	if (IsCanceled())
		return false;
	AddSummaryMsg(_("Transcode/remultiplex"));
	SetSubSteps(titleSubSteps);
	for (int i = 0; i < (int) titleVobs.Count(); i++) {
		Vob* vob = (Vob*) titleVobs[i];
		wxString cacheFile = m_cache->Add(vob, dvd);
		AddDetailMsg(wxT("Add file to cache:") + cacheFile);
		vob->SetTmpFilename(cacheFile);
		if (!Transcode(vob->GetStreams(), vob->GetFilename(), vob->GetAudioFilenames(),
				vob->GetTmpFilename(), dvd->GetVideoBitrate())) {
			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 (int i = 0; i < (int) slideshowVobs.Count(); i++) {
		AddDetailMsg(wxString::Format(_("Generating slideshow %d of %d"), i + 1, slideshowVobs.Count()));
		Vob* vob = (Vob*) slideshowVobs[i];
		if (!GenerateSlideshow(vob->GetSlideshow(), vob->GetTmpFilename(),
				dvd->GetAudioFormat()))
			return false;
	}
	IncStep();
	return true;
}

bool ProgressDlg::MultiplexSubtitles(wxArrayPtrVoid& subtitleVobs, int subtitleSubSteps, DVD* dvd) {
	if (subtitleVobs.Count() == 0)
		return true;
	if (IsCanceled())
		return false;
	AddSummaryMsg(_("Multiplexing subtitles"));
	SetSubSteps(subtitleSubSteps);
	for (int i = 0; i < (int) subtitleVobs.Count(); i++) {
		AddDetailMsg(wxString::Format(_("Multiplexing subtitles %d of %d"), i + 1, subtitleVobs.Count()));
		Vob* vob = (Vob*) subtitleVobs[i];
		
		// Extract Video stream geometry
		wxSize subtitleSize;
		const StreamArray streams = vob->GetStreams();

		for (unsigned int stIdx = 0; stIdx < streams.GetCount(); stIdx++) {
		  Stream* stream = streams[stIdx];
		  if (stream->GetType() == stVIDEO) {
		    switch(stream->GetVideoFormat()) {
		    case vfNTSC:
		      // Use the specified destination NTSC format
		      subtitleSize.SetWidth(720);
		      subtitleSize.SetHeight(480);
		      break;
		      
		    case vfPAL:
		      // Use the specified destination PAL format
		      subtitleSize.SetWidth(720);
		      subtitleSize.SetHeight(574);
		      break;

		    case vfCOPY:
		    default:
		      // Use source format as geometry
		      subtitleSize = stream->GetSourceVideoSize();
		      break;
		    }
		    break;
		  }
		}

		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->SetMovieWidth(subtitleSize.GetWidth());
			textSub->SetMovieHeight(subtitleSize.GetHeight());

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

bool ProgressDlg::MultiplexSubtitles(const wxString& vobFile, TextSub* textSub, unsigned int streamIdx,
		const wxString& resultFile) {
	if (IsCanceled())
		return false;
	//spumux
	wxString cmd = s_config.GetSpumuxCmd();
	wxString spuFile = resultFile + wxT("_spumux.xml");
	textSub->SaveSpumux(spuFile);
	cmd.Replace(wxT("$FILE_CONF"), spuFile);
	cmd.Replace(wxT("$STREAM"), wxString::Format(wxT("%d"), streamIdx));
	if (!Exec(cmd, vobFile, resultFile)) {
		Failed();
		return false;
	}
	if (s_config.GetRemoveTempFiles()) {
		if (vobFile == resultFile + _T(".old") && !DeleteFile(vobFile))
			return false;
		if (!DeleteFile(spuFile))
			return false;
	}

	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))
			if (!DeleteFile(dvdTmpDir + fname))
				return false;
	}
	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) {
	if (burnDlg->DoCreateIso() || (burnDlg->DoBurn() && burnDlg->DoAddECC())) {
		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));
#ifdef __WXMSW__
		cmd = wxGetAppPath() + wxString(wxFILE_SEP_PATH) + _T("..") + wxString(wxFILE_SEP_PATH) + _T("dvdauthor") + 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();
		cmd.Replace(_T("$DEV"), burnDlg->GetDevice());
		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 cmd = s_config.GetBurnScanCmd();
	cmd.Replace(_T("$DEVICE"), burnDlg->GetDevice());
#ifdef __WXMSW__
	cmd = wxGetAppPath() + wxString(wxFILE_SEP_PATH) + _T("..") + wxString(wxFILE_SEP_PATH) + _T("dvdauthor") + 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."), burnDlg->GetDevice().c_str()),
				_("Burn"), wxOK|wxCANCEL, this) == wxCANCEL) {
			AddSummaryMsg(_("Aborted"), wxEmptyString, *wxRED);
			return false;
		}
	}
	// get disc size
	long discSize = 0;
	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: %d MB"), discSize / 512));
			break;
		}
	}
	if (discSize < 2290000)
		discSize = 2295104;
	// check size
	long size = 0;
	if (burnDlg->DoAddECC()) {
		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));
#ifdef __WXMSW__
		cmd = wxGetAppPath() + wxString(wxFILE_SEP_PATH) + _T("..") + wxString(wxFILE_SEP_PATH) + _T("dvdauthor") + 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: %d 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 (burnDlg->DoAddECC()) {
		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("%d"),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"), burnDlg->GetDevice());
	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);
	ProgressExecute exec(this, wxT(".*"));
	if (!exec.Execute(cmd)) {
		Failed();
		return false;
	}
	IncStep();
	return true;
}

bool ProgressDlg::Transcode(const StreamArray& streams, const wxString& mpegFile,
		const wxArrayString& audioFiles, const wxString& vobFile, int videoBitrate) {
	if (IsCanceled())
		return false;
	int subStep = GetSubStep();
	AddDetailMsg(_("Transcode video file: ") + mpegFile);
	bool disableAudio = streams.GetCount() > 1 + audioFiles.GetCount()
		&& streams[1]->GetDestinationFormat() == afNONE;
	if (disableAudio) {
		wxLogMessage(wxT("disableAudio"));
	}
	wxFfmpegMediaTranscoder transcoder(s_config.GetThreadCount());
	if (!transcoder.AddInputFile(mpegFile, false, disableAudio)) {
		Failed(wxT("Error by transcoding of ") + mpegFile);
		return false;
	}
	for (int i = 0; i < (int)audioFiles.GetCount(); i++) {
		if (!transcoder.AddInputFile(audioFiles[i])) {
			Failed(wxT("Error by transcoding of ") + audioFiles[i]);
			return false;
		}
	}
	// set output formats
	VideoFormat videoFormat = vfCOPY;
	wxArrayInt audioFormats;
	wxArrayInt subtitleFormats;
	for (unsigned int stIdx = 0; stIdx < streams.GetCount(); stIdx++) {
		Stream* stream = streams[stIdx];
		switch (stream->GetType()) {
		case stVIDEO:
			videoFormat = stream->GetVideoFormat();
			break;
		case stAUDIO:
			audioFormats.Add(stream->GetAudioFormat());
			break;
		case stSUBTITLE:
			subtitleFormats.Add(stream->GetSubtitleFormat());
			break;
		default:
			break;
		}
	}
	if (s_config.GetUseMplex()) {
		wxString videoFile = vobFile + wxT(".m2v");
		if (wxFileExists(videoFile) && !wxRemoveFile(videoFile)) {
			wxLogError(wxString::Format(_("Can't remove file '%s'"), videoFile.c_str()));
		}
		if (!transcoder.SetOutputFile(videoFile, videoFormat, afNONE, sfNONE,
				videoBitrate, s_config.GetVbr(), s_config.GetAudioBitrate())) {
			Failed(_("Error transcoding of ") + mpegFile);
			return false;
		}
		wxArrayString audioFiles;
		for (unsigned int audioIdx = 0; audioIdx < audioFormats.GetCount(); audioIdx++) {
			if (audioFormats[audioIdx] == afNONE)
				continue;
			wxString audioFile = vobFile + wxString::Format(wxT(".audio%d"), 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, (AudioFormat) audioFormats[audioIdx], sfNONE,
						videoBitrate, s_config.GetVbr(), s_config.GetAudioBitrate())) {
				Failed(_("Error transcoding of ") + mpegFile);
				return false;
			}
		}
		if (!transcoder.Run(m_cancel)) {
			wxRemoveFile(videoFile);
			for (unsigned int audioIdx = 0; audioIdx < audioFiles.GetCount(); audioIdx++)
				wxRemoveFile(audioFiles[audioIdx]);
			Failed(_("Error transcoding of ") + mpegFile);
			return false;
		}
		SetSubStep(subStep+150);
		if (!Multiplex(videoFile, audioFiles, vobFile)) {
			wxRemoveFile(videoFile);
			for (unsigned int audioIdx = 0; audioIdx < audioFiles.GetCount(); audioIdx++)
				wxRemoveFile(audioFiles[audioIdx]);
			return false;
		}
		// remove temp files
		if (s_config.GetRemoveTempFiles()) {
			if (!DeleteFile(videoFile))
				return false;
			for (unsigned int audioIdx = 0; audioIdx < audioFiles.GetCount(); audioIdx++) {
				if (!DeleteFile(audioFiles[audioIdx]))
					return false;
			}
		}
	} else {
		if (!transcoder.SetOutputFile(vobFile, videoFormat, audioFormats, subtitleFormats,
				videoBitrate, s_config.GetVbr(), s_config.GetAudioBitrate())
				|| !transcoder.Run(m_cancel)) {
			wxRemoveFile(vobFile);
			Failed(_("Error transcoding of ") + mpegFile);
			return false;
		}
	}
	transcoder.End();
	SetSubStep(subStep+200);
	return true;
}

bool ProgressDlg::Multiplex(const wxString& videoFile, const wxArrayString& audioFiles,
		const wxString& vobFile) {
	if (IsCanceled())
		return false;
	AddDetailMsg(wxString::Format(_("Multiplexing video and audio streams"),
			videoFile.c_str(), audioFiles[0].c_str()));
	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, 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 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->HasVideoBackground();
	wxYield();

	// save background and buttons
	AddDetailMsg(_("Prepare"));
	menu->SetTransparentColor();
	wxImage* images = menu->GetImages();
	wxImage bgImage = images[0];
	images[1].SaveFile(btFile);
	images[2].SaveFile(hlFile);
	images[3].SaveFile(selFile);
	delete[] images;
	// save spumux
	menu->SaveSpumux(spuFile, btFile, hlFile, selFile);
	IncSubStep(10);

	// convert jpeg to mpeg
	AddDetailMsg(_("Create menu mpeg"));
	if (s_config.GetMenuFrameCount()<25)
		s_config.SetMenuFrameCount(25);
	if (s_config.GetMenuVideoBitrate()<1000)
		s_config.SetMenuVideoBitrate(1000);

	if (!bgImage.Ok()) {
		Failed(_("Error creation of menu"));
		return false;
	}

  if (videoMenu) {
		if (audioFile.length() || !MPEG::HasNavPacks(menu->GetBackground())) {
			Vob menuVob;
			menuVob.SetFilename(menu->GetBackground());
			if (audioFile.length())
				menuVob.AddAudioFile(audioFile);
			for (unsigned int i=0; i<menuVob.GetStreams().GetCount(); i++) {
				Stream* stream = menuVob.GetStreams()[i];
				if (stream->GetType() == stVIDEO) {
					// set destination format (default COPY) if codec is not mpeg2
					if (stream->GetSourceCodecName() != wxT("mpeg2video"))
						stream->SetDestinationFormat(menu->GetVideoFormat());
				} else if (stream->GetType() == stAUDIO) {
					// set destination format (default COPY) if codec is not mp2/ac3 or sample rate is not 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);
					}
				}
			}
			if (!Transcode(menuVob.GetStreams(), menuVob.GetFilename(), menuVob.GetAudioFilenames(),
					mpegFile, videoBitrate))
				return false;
		} else
			mpegFile = menu->GetBackground();
	} else {
		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) {
					// set destination format (default COPY) if 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);
					}
				}
			}
			if (!Transcode(menuVob.GetStreams(), menuVob.GetFilename(), menuVob.GetAudioFilenames(),
					mpegFile, videoBitrate))
				return false;
			if (s_config.GetRemoveTempFiles())
				if (!DeleteFile(m2vFile))
					return false;
		}
	}

	//spumux
	if (IsCanceled())
		return false;
	AddDetailMsg(_("Multiplexing subtitles (buttons) into mpeg"));
	wxString cmd = s_config.GetSpumuxCmd();
	cmd.Replace(wxT("$FILE_CONF"), spuFile);
	cmd.Replace(wxT("$STREAM"), wxT("0"));
	if (!Exec(cmd, mpegFile, menuFile)) {
		Failed();
		return false;
	}
	if (s_config.GetRemoveTempFiles()) {
		if ((!videoMenu || mpegFile != menu->GetBackground()) && !DeleteFile(mpegFile))
			return false;
		if (!DeleteFile(btFile) || !DeleteFile(hlFile) || !DeleteFile(selFile)
				|| !DeleteFile(spuFile))
			return false;
	}
	IncSubStep(50);

	wxYield();
	return true;
}

bool ProgressDlg::GenerateSlideshow(Slideshow* slideshow, const wxString& vobFile, AudioFormat audioFormat) {
	if (IsCanceled())
		return false;
	AddDetailMsg(_("Generating slideshow"));

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

	for (unsigned i=0; i<slideshow->Count(); i++) {
		AddDetailMsg(wxString::Format(_("Converting slide %d 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();

	wxYield();
	return true;
}

bool ProgressDlg::DeleteFile(wxString fname) {
	if (!wxRemoveFile(fname)) {
		Failed(wxString::Format(_("Can't remove file '%s'"), fname.c_str()));
		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) {
	AddDetailMsg(_("Cleaning temporary directory"));
	if (wxDirExists(dvdTmpDir) && !DeleteDir(dvdTmpDir, false))
		return false;
	if (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);
}
