/////////////////////////////////////////////////////////////////////////////
// Name:        ProgressDlg.cpp
// Purpose:     Progress of generation/burning dialog
// Author:      Alex Thuering
// Created:     14.08.2004
// RCS-ID:      $Id: ProgressDlg.cpp,v 1.168 2014/03/31 08:36:22 ntalex Exp $
// Copyright:   (c) Alex Thuering
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

#include "ProgressDlg.h"
#include "MainWin.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/textfile.h>
#include <wx/regex.h>
#include <wx/utils.h>
#include <wx/platinfo.h>
#include <wxSVG/SVGDocument.h>
#include <wxSVG/SVGImageElement.h>
#include <wxSVG/SVGAnimateElement.h>
#include <wxSVG/mediadec_ffmpeg.h>
#include <wx/math.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(wxT("data") + wxString(wxFILE_SEP_PATH) + fname)
#define TRANSITION_FILE(fname) wxFindDataFile(wxT("transitions") + wxString(wxFILE_SEP_PATH) + fname)
#define TRANSITIONS_DIR wxFindDataDirectory(wxT("transitions"))
#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_totalFrames > 0 && m_pattern.Matches(line)) {
			long frame = 0;
			m_pattern.GetMatch(line, 1).ToLong(&frame);
			m_percent = (frame * 100) / m_totalFrames;
			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) {
			ProgressExecute::ProcessOutput(line);
			return;
		}
		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(MainWin* 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;
    m_detailsText->SetFont(wxFont(8, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
}

void ProgressDlg::set_properties() {
    // begin wxGlade: ProgressDlg::set_properties
    SetTitle(_("Generate DVD"));
    SetSize(wxSize(700, 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))
		wxDELETE(m_winDisabler);
	((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());
	m_logFile.Write(text);
	m_logFile.Flush();
	wxYieldIfNeeded();
}

/** Sets the step count of generating process */
void ProgressDlg::SetSteps(int stepCount) {
	m_stepCount = stepCount;
	m_step = 0;
	m_gauge->SetRange(stepCount*100);
	((MainWin*) this->GetParent())->SetProgressBarState();
}

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

void ProgressDlg::Failed(const wxString& message) {
	AddSummaryMsg(_("Failed"), message, *wxRED);
	((MainWin*) this->GetParent())->SetProgressBarState(true);
}

void ProgressDlg::OnCancel(wxCommandEvent& WXUNUSED(event)) {
	if (!m_cancel) {
		AddSummaryMsg(_("Aborted"), wxEmptyString, *wxRED);
		End();
	} else {
		m_end = true;
		((MainWin*) this->GetParent())->SetProgressBarValue(0, 100);
		((MainWin*) this->GetParent())->SetProgressBarState();
	}
}

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

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

bool ProgressDlg::Start(BurnDlg* burnDlg, DVD* dvd) {
	// disable parent window
	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
	delete wxLog::SetActiveTarget(previousLog);
	// close the Window or release the controls
	if (m_autoStart) {
		Close(true);
	} else {
    	while (!m_end) {
    		wxMilliSleep(100);
    		wxYield();
    	}
    }
	wxDELETE(m_winDisabler);
	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;
				if (vob->GetKeepAspectRatio()) {
					vob->UpdatePad(ts->GetTitles().GetVideo().GetAspect());
				}
				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()->size();
					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, Cache* cache) {
	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;
					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
					}
					subtitleVobs.Add(vob);
					subtitleTsi.Add(tsi);
				}
			}
		}
	}
	return subtitleSubSteps;
}

void PrintInfoLine(ProgressDlg* progressDlg, const wxString& info, unsigned int inputSize, unsigned int outputSize,
		double outputDuration, int bitrate) {
	wxString text = info.substr(0, 12);
	text.Append(wxT(' '), 12 - info.length()).Append(wxT("| "));
	// input size
	wxString str = inputSize > 0 ? wxString::Format(wxT("%0.1f"), ((float) inputSize) / 1024) + wxT(" MB") : wxT("");
	str.Append(wxT(' '), 11 - str.length());
	text.Append(str).Append(wxT("| "));
	// output duration
	str = outputDuration > 0 ? Time2String(outputDuration*1000) + wxT(" sec") : wxT("");
	str.Append(wxT(' '), 16 - str.length());
	text.Append(str).Append(wxT("| "));
	// output bitrate
	if (bitrate >= 1000)
		str = wxString::Format(wxT("%0.1f"), ((float) bitrate) / 1000) + wxT(" MB/s");
	else
		str = bitrate > 0 ? wxString::Format(wxT("%d"), bitrate) + wxT(" KB/s") : wxT("");
	str.Append(wxT(' '), 9 - str.length());
	text.Append(str).Append(wxT("| "));
	// output size
	str = wxString::Format(wxT("%0.1f"), ((float) outputSize) / 1024) + wxT(" MB");
	text.Append(str);
	progressDlg->AddDetailMsg(text);
}

void ProgressDlg::PrintProjectInfo(DVD* dvd) {
	AddDetailMsg(wxT("============================================================================="));
	AddDetailMsg(wxT("            | Input size | Output duration | Bitrate  | Estimated output size"));
	double totalDuration = 0;
	for (int tsi = -1; tsi < (int) dvd->GetTitlesets().Count(); tsi++) {
		PgcArray& pgcArray = dvd->GetPgcArray(tsi, true);
		for (int pgci = 0; pgci < (int) pgcArray.Count(); pgci++) {
			Pgc* pgc = pgcArray[pgci];
			for (int vobi = 0; vobi < (int) pgc->GetVobs().Count(); vobi++) {
				Vob* vob = pgc->GetVobs()[vobi];
				double dur = vob->GetMenu() ? vob->GetMenu()->GetSVG()->GetDuration() : 0;
				PrintInfoLine(this, wxString::Format(tsi == -1 ? wxT("VMGM menu %d") : wxT("Menu %d"), pgci + 1), 0,
						vob->GetOutputFileSize(dvd, 0), dur, vob->GetBitrate(dvd, 0));
				totalDuration += dur;
				for (unsigned int fileIdx = 0; fileIdx < vob->GetAudioFilenames().size(); fileIdx++) {
					PrintInfoLine(this, wxT("  ") + vob->GetAudioFilenames()[fileIdx].AfterLast(wxT('.')),
							vob->GetFileSize(vob->GetAudioFilenames()[fileIdx]),
							vob->GetOutputFileSize(dvd, fileIdx + 1), 0, vob->GetBitrate(dvd, fileIdx + 1));
				}
			}
		}
		if (tsi == -1)
			continue;
		Titles& titles = dvd->GetTitlesets()[tsi]->GetTitles();
		for (int pgci = 0; pgci < (int) titles.Count(); pgci++) {
			Pgc* pgc = titles[pgci];
			AddDetailMsg(wxString::Format(wxT("Title %d"), pgci + 1));
			for (int vobi = 0; vobi < (int) pgc->GetVobs().Count(); vobi++) {
				Vob* vob = pgc->GetVobs()[vobi];
				if (vob->GetSlideshow()) {
					double dur = vob->GetSlideshow()->GetResultDuration();
					PrintInfoLine(this, wxT("  slideshow"), 0, vob->GetOutputFileSize(dvd, 0), dur,
							vob->GetBitrate(dvd, 0));
					totalDuration += dur;
				} else if (vob->GetFilename().length() > 0) {
					double dur = vob->GetResultDuration();
					PrintInfoLine(this, wxT("  ") + vob->GetFilename().AfterLast(wxT('.')),
							vob->GetFileSize(vob->GetFilename()), vob->GetOutputFileSize(dvd, 0), dur,
							vob->GetBitrate(dvd, 0));
					totalDuration += dur;
				}
				for (unsigned int fileIdx = 0; fileIdx < vob->GetAudioFilenames().size(); fileIdx++) {
					PrintInfoLine(this, wxT("  ") + vob->GetAudioFilenames()[fileIdx].AfterLast(wxT('.')),
							vob->GetFileSize(vob->GetAudioFilenames()[fileIdx]),
							vob->GetOutputFileSize(dvd, fileIdx + 1), 0, vob->GetBitrate(dvd, fileIdx + 1));
				}
			}
		}
	}
	PrintInfoLine(this, wxT("Total"), 0, dvd->GetSize(), totalDuration, 0);
	AddDetailMsg(wxT("============================================================================="));
}

void PrintInfoLine(ProgressDlg* progressDlg, const wxString& info, int transcodedSize) {
	wxString text = info.substr(0, 12);
	text.Append(wxT(' '), 12 - info.length()).Append(wxT("| "));
	// transcoded size
	wxString str = wxString::Format(wxT("%0.1f"), ((float) transcodedSize) / 1024) + wxT(" MB");
	text.Append(str);
	progressDlg->AddDetailMsg(text);
}


void ProgressDlg::PrintProjectTranscodedInfo(DVD* dvd) {
	AddDetailMsg(wxT("========================="));
	AddDetailMsg(wxT("            | Result size"));
	for (int tsi = -1; tsi < (int) dvd->GetTitlesets().Count(); tsi++) {
		PgcArray& pgcArray = dvd->GetPgcArray(tsi, true);
		for (int pgci = 0; pgci < (int) pgcArray.Count(); pgci++) {
			Pgc* pgc = pgcArray[pgci];
			for (int vobi = 0; vobi < (int) pgc->GetVobs().Count(); vobi++) {
				Vob* vob = pgc->GetVobs()[vobi];
				PrintInfoLine(this, wxString::Format(tsi == -1 ? wxT("VMGM menu %d") : wxT("Menu %d"), pgci + 1),
						vob->GetTranscodedSize(dvd));
			}
		}
		if (tsi == -1)
			continue;
		Titles& titles = dvd->GetTitlesets()[tsi]->GetTitles();
		for (int pgci = 0; pgci < (int) titles.Count(); pgci++) {
			Pgc* pgc = titles[pgci];
			for (int vobi = 0; vobi < (int) pgc->GetVobs().Count(); vobi++) {
				Vob* vob = pgc->GetVobs()[vobi];
				wxString titleStr = wxString::Format(wxT("Title %d"), pgci + 1);
				if (vobi > 0)
					titleStr += wxString::Format(wxT("-%d"), vobi + 1);
				PrintInfoLine(this, titleStr, vob->GetTranscodedSize(dvd));
			}
		}
	}
	PrintInfoLine(this, wxT("Total"), dvd->GetSize(true));
	AddDetailMsg(wxT("========================="));
}

void ProgressDlg::Run(BurnDlg* burnDlg, DVD* dvd) {
	if (IsCanceled())
		return;
	
	// check if libav or ffmpeg is present
#ifdef __WXMSW__
	if (!wxFileExists(wxGetAppPath() + s_config.GetAVConvCmd() + wxT(".exe"))
			&& !wxFileExists(wxGetAppPath() + s_config.GetAVConvCmd())) {
		if (wxFileExists(wxGetAppPath() + wxT("avconv.exe")))
			s_config.SetAVConvCmd(wxT("avconv"));
		else if (wxFileExists(wxGetAppPath() + wxT("ffmpeg.exe")))
			s_config.SetAVConvCmd(wxT("ffmpeg"));
	}
#endif
	
	wxString tmpDir = burnDlg->GetTempDir();
	if (tmpDir.Last() != wxFILE_SEP_PATH)
		tmpDir += wxFILE_SEP_PATH;
	wxString dvdTmpDir = tmpDir + wxString(wxT("dvd-tmp")) + wxFILE_SEP_PATH;
	wxString dvdOutDir = tmpDir + wxString(wxT("dvd-out")) + wxFILE_SEP_PATH;
	if (burnDlg->DoGenerate()) {
		dvdOutDir = burnDlg->GetOutputDir();
		if (dvdOutDir.Last() != wxFILE_SEP_PATH)
			dvdOutDir += wxFILE_SEP_PATH;
	}
	
	// create log file
	m_logFile.Open(tmpDir + wxT("dvdstyler.log"), wxT("w"));

	// print version
	AddDetailMsg(wxT("DVDStyler v") + APP_VERSION);
	AddDetailMsg(wxGetOsDescription());
	AddDetailMsg((s_config.GetAVConvCmd() == wxT("ffmpeg") ? wxT("FFmpeg: ") : wxT("Libav: "))
			+ wxFfmpegMediaEncoder::GetBackendVersion());

	// prepare
	AddSummaryMsg(_("Prepare"));

	// clean temp dir
	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, m_cache);
	// 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);

	// print dvd info
	PrintProjectInfo(dvd);
	
	// 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];
		
		wxString audioFile;
		AudioFormat audioFormat = dvd->GetAudioFormat();
		if (vob->GetAudioFilenames().size() > 0 && vob->GetStreams().size() > 0
				&& vob->GetStreams()[0]->GetType() == stAUDIO) {
			audioFile = vob->GetAudioFilenames()[0];
			Stream* stream = vob->GetStreams()[0];
			audioFormat = stream->GetAudioFormat();
			if (audioFormat == 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")) {
					audioFormat = dvd->GetAudioFormat();
				} else if (stream->GetSourceSampleRate() != 48000) {
					audioFormat = stream->GetSourceCodecName() == wxT("mp2") ? afMP2 : afAC3;
				}
			}
		}
		
		if (!GenerateMenu(vob->GetMenu(), wsType, vob->GetTmpFilename(), audioFormat, audioFile, dvd->GetAudioBitrate()))
			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, dvd->GetAudioBitrate(), 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];
		AudioFormat audioFormat = dvd->GetAudioFormat();
		if (vob->GetStreams().size() > 0 && vob->GetStreams()[0]->GetType() == stAUDIO) {
			Stream* stream = vob->GetStreams()[0];
			audioFormat = stream->GetAudioFormat();
			if (audioFormat == 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")) {
					audioFormat = dvd->GetAudioFormat();
				} else if (stream->GetSourceSampleRate() != 48000) {
					audioFormat = stream->GetSourceCodecName() == wxT("mp2") ? afMP2 : afAC3;
				}
			}
		}
		if (!GenerateSlideshow(vob->GetSlideshow(), vob->GetTmpFilename(), audioFormat,
				vob->GetAudioFilenames().size() > 0 ? vob->GetAudioFilenames()[0] : wxString(), dvd->GetAudioBitrate()))
			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();
		if (vob->GetTmpFilename().length() == 0) {
			wxString cacheFile = m_cache->Add(vob, dvd);
			AddDetailMsg(wxT("Add file to cache:") + cacheFile);
			vob->SetTmpFilename(cacheFile);
		}
		
		// 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)) {
		if (wxFileExists(resultFile))
			wxRemoveFile(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;
	PrintProjectTranscodedInfo(dvd);
	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.StartsWith(wxT("/Applications/VLC.app/Contents/MacOS/VLC"))) {
			if (!wxFileExists(wxT("/Applications/VLC.app/Contents/MacOS/VLC"))) {
				wxString msg = _("VLC player is not found. \
Please install VLC player (http://www.videolan.org) in Applications directory \
or open the following directory with your favorite DVD player: ");
				msg += dvdOutDir;
				wxMessageBox(msg, _("Burn"), wxOK|wxICON_ERROR, this);
			} else {
				cmd += wxT(" \"dvd:///$DIR\"");
				cmd.Replace(wxT("$DIR"), dvdOutDir.Mid(0, dvdOutDir.length() - 1));
				if (wxExecute(cmd, wxEXEC_ASYNC) < 0)
					Failed();
			}
		} 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();
		wxString label = dvd->GetLabel();
		label.Replace(wxT("\""), wxT("\\\""));
		cmd.Replace(_T("$VOL_ID"), label);
		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 a rewritable 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 a blank or rewritable 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 a blank or rewritable 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));
	}
	wxString label = dvd->GetLabel();
	label.Replace(wxT("\""), wxT("\\\""));
	cmd.Replace(_T("$VOL_ID"), label);
	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, int audioBitrate, 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();
	bool needEncode = vob->GetAudioFilenames().size() != 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().size(); 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
		wxFfmpegMediaTranscoder transcoder(s_config.GetThreadCount());
		if (!transcoder.AddInputFile(vob->GetFilename())) {
			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;
			long tsOffset = vob->GetStreams()[stIdx]->GetTsOffset();
			if (!transcoder.AddInputFile(vob->GetAudioFilenames()[i], wxT(""), tsOffset)) {
				Failed(wxT("Error by transcoding of ") + vob->GetAudioFilenames()[i]);
				return false;
			}
		}
		if (vob && vob->GetFilename().length() && !vob->HasAudio()) {
#ifdef __WXMSW__
			wxString zero = wxT("aevalsrc=0");
			wxString format = wxT("lavfi");
#else
			wxString zero = wxT("/dev/zero");
			wxString format = wxT("");
#endif
			if (!transcoder.AddInputFile(zero, format)) {
				Failed(wxT("Error by transcoding of ") + zero);
				return false;
			}
			audioFormats.Add(defAudioFormat);
			if (recordingTime <= 0)
				recordingTime = vob->GetDuration() > 0 ? vob->GetDuration() : 1.0;
		}
		
		// set output options
		transcoder.SetInterlaced(vob->GetInterlaced());
		transcoder.SetFirstField(vob->GetFirstField());
		if (vob->GetKeepAspectRatio()) {
			vob->UpdatePad(aspect);
		}
		
		// set video filters
		transcoder.SetVideoFilters(vob->GetAllVideoFilters());
		
		// set audio filters
		int audioIdx = 0;
		for (unsigned int stIdx = 0; stIdx < vob->GetStreams().size(); stIdx++) {
			Stream* stream = vob->GetStreams()[stIdx];
			if (stream->GetType() == stAUDIO) {
				wxString audioFilters = stream->GetAllAudioFilters();
				if (audioFilters.length())
					transcoder.SetAudioFilters(audioIdx, audioFilters);
				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, aspect, audioFormats,
					subtitleFormats, videoBitrate, s_config.GetVbr(), audioBitrate)
					|| !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()));
				return false;
			}
			if (!transcoder.SetOutputFile(videoFile, videoFormat, ntscFilm, aspect, afNONE, sfNONE,
					videoBitrate, s_config.GetVbr(), audioBitrate, 0, vob->GetVideoStreamIndex())) {
				Failed(_("Error transcoding of ") + vob->GetFilename());
				return false;
			}
			wxArrayString audioFiles;
			for (unsigned int audioIdx = 0; audioIdx < audioFormats.size(); 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()));
					return false;
				}
				if (startTime != 0)
					transcoder.SetStartTime(startTime);
				if (recordingTime > 0)
					transcoder.SetRecordingTime(recordingTime);
				int audioFileIdx = audioIdx + 1 - audioFormats.size() + vob->GetAudioFilenames().size();
				int audioStreamIdx = audioIdx;
				if (audioFileIdx <= 0) {
					audioFileIdx = 0;
					if (audioStreamIdx >= vob->GetVideoStreamIndex())
						audioStreamIdx++;
				} else
					audioStreamIdx = 0;
				if (!transcoder.SetOutputFile(audioFile, vfNONE, false, aspect, (AudioFormat) audioFormats[audioIdx],
						sfNONE, videoBitrate, s_config.GetVbr(), audioBitrate, audioFileIdx, audioStreamIdx)) {
					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.size(); 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.size(); 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.size(); 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 audioBitrate) {
	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() && audioFile.length() == 0) {
			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 || s_config.GetUseMplexForMenus();
		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) {
			if (audioFile.length() == 0 && bgAudioFormat == afNONE) {
				// encode audio
				audioFileTmp = menuFile + wxString(wxT("_bg.")) + (audioFormat == afAC3 ? wxT("ac3") : wxT("mp2"));
				double duration = (double) frameCount / GetFps(menu->GetVideoFormat(), false);
				if (!ffmpeg.BeginEncode(audioFileTmp, vfNONE, audioFormat, menu->GetAspectRatio(), s_config.GetMenuVideoBitrate())
						|| !ffmpeg.EncodeAudio(duration, &m_cancel)) {
					Failed(_("Error creation of menu"));
					return false;
				}
				ffmpeg.EndEncode();
			}
			Vob menuVob;
			menuVob.SetFilename(m2vFile);
			menuVob.GetVideoStream()->SetDestinationFormat(vfCOPY);
			if (audioFile.length()) {
				menuVob.AddAudioFile(audioFile);
			} else if (bgAudioFormat == afNONE) {
				menuVob.AddAudioFile(audioFileTmp);
			} else {
				AddDetailMsg(_("Transcode audio from") + wxString(wxT(" ")) + menu->GetBackground());
				// transcode audio
				wxFfmpegMediaTranscoder transcoder(s_config.GetThreadCount());
				if (!transcoder.AddInputFile(menu->GetBackground())) {
					Failed(wxT("Error by transcoding of ") + menu->GetBackground());
					return false;
				}
				transcoder.SetOutputFormat(bgAudioOutputFormat);
				if (!transcoder.SetOutputFile(audioFileTmp, vfNONE, false, menu->GetAspectRatio(), bgAudioFormat,
						sfNONE, 0, false, audioBitrate)) {
					Failed(_("Error transcoding of ") + menu->GetBackground());
					return false;
				}
				AVConvExecute exec(this, -1);
				if (!exec.Execute(transcoder.GetCmd())) {
					DeleteFile(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().size(); 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(), audioBitrate,
					s_config.GetUseMplexForMenus()))
				return false;
			if (s_config.GetRemoveTempFiles()) {
				DeleteFile(m2vFile);
				DeleteFile(audioFileTmp);
			}
		}
	} else { // menu with still image
		wxImage bgImage = menu->GetBackgroundImage();
		if (!bgImage.Ok()) {
			Failed(_("Error creation of menu"));
			return false;
		}
		
		bool hasAudio = audioFile.length() || s_config.GetUseMplexForMenus();
		int frameCount = s_config.GetMenuFrameCount();
		if (audioFile.length() > 0) {
			// get audio duration
			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);
				}
			}
		}
		
		// encode video
		wxFfmpegMediaEncoder ffmpeg(s_config.GetThreadCount());
		if (!ffmpeg.BeginEncode(hasAudio ? m2vFile : mpegFile, menu->GetVideoFormat(),
				hasAudio ? 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 (hasAudio) {
			if (audioFile.length() == 0) {
				// encode audio
				audioFileTmp = menuFile + wxString(wxT("_bg.")) + (audioFormat == afAC3 ? wxT("ac3") : wxT("mp2"));
				double duration = (double) frameCount / GetFps(menu->GetVideoFormat(), false);
				if (!ffmpeg.BeginEncode(audioFileTmp, vfNONE, audioFormat, menu->GetAspectRatio(), s_config.GetMenuVideoBitrate())
						|| !ffmpeg.EncodeAudio(duration, &m_cancel)) {
					Failed(_("Error creation of menu"));
					return false;
				}
				ffmpeg.EndEncode();
			}
			IncSubStep(75);
			// mplex (and optionally transcode audio)
			if (IsCanceled())
				return false;
			AddDetailMsg(_("Multiplexing audio and video"));
			Vob menuVob;
			menuVob.SetFilename(m2vFile);
			menuVob.AddAudioFile(audioFile.length() ? audioFile : audioFileTmp);
			for (unsigned int i=0; i<menuVob.GetStreams().size(); i++) {
				Stream* stream = menuVob.GetStreams()[i];
				if (stream->GetType() == stAUDIO)
					stream->SetDestinationFormat(audioFile.length() ? audioFormat : afCOPY);
			}
			menuVob.SetTmpFilename(mpegFile);
			if (!s_config.GetUseMplexForMenus())
				AddDetailMsg(WARNING_MPLEX_MSG, *wxRED);
			if (!Transcode(&menuVob, menu->GetAspectRatio(), s_config.GetMenuVideoBitrate(), audioBitrate,
					s_config.GetUseMplexForMenus()))
				return false;
			if (s_config.GetRemoveTempFiles()) {
				DeleteFile(m2vFile);
				DeleteFile(audioFileTmp);
			}
		}
	}

	//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;
}

void SetAnimationDur(wxSVGElement* parent, double dur) {
	wxSVGElement* elem = (wxSVGElement*) parent->GetChildren();
	while (elem) {
		if (elem->GetType() == wxSVGXML_ELEMENT_NODE) {
			if (elem->GetDtd() == wxSVG_ANIMATE_ELEMENT) {
				wxSVGAnimateElement* animateElem = (wxSVGAnimateElement*) elem;
				animateElem->SetDur(dur);
			} else if (elem->GetChildren()) {
				SetAnimationDur(elem, dur);
			}
		}
		elem = (wxSVGElement*) elem->GetNext();
	}
}

bool ProgressDlg::LoadTransition(wxSVGDocument& svg, const wxString& fileName, Slideshow* slideshow, Slide* slide1,
		Slide* slide2) {
	if (!svg.Load(TRANSITION_FILE(fileName))) {
		Failed(_("Error loading transition definition"));
		return false;
	}
	svg.GetRootElement()->SetWidth(slideshow->GetResolution().GetWidth());
	svg.GetRootElement()->SetHeight(slideshow->GetResolution().GetHeight());
	
	wxSVGImageElement* img1 = (wxSVGImageElement*) svg.GetElementById(wxT("image1"));
	wxSVGImageElement* img2 = (wxSVGImageElement*) svg.GetElementById(wxT("image2"));
	if (!img1 || !img2) {
		Failed(wxT("Transition: image element with id 'image1' and/or 'image2' is not defined"));
		return false;
	}
	
	img1->SetHref(slide1->GetFilename());
	img2->SetHref(slide2->GetFilename());
	
	SetAnimationDur(svg.GetRootElement(), s_config.GetDefTransitionDuration());
	return true;
}

bool ProgressDlg::GenerateSlideshow(Slideshow* slideshow, const wxString& vobFile, AudioFormat audioFormat,
		wxString audioFile, int audioBitrate) {
	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;
	}
	
	int width = GetFrameSize(slideshow->GetVideoFormat()).GetWidth();
	int height = GetFrameSize(slideshow->GetVideoFormat()).GetHeight();
	
	wxArrayString transitions;
	wxString fname = wxFindFirstFile(TRANSITIONS_DIR + _T("*.xml"));
	while (!fname.IsEmpty()) {
		transitions.Add(wxFileName(fname).GetFullName());
		fname = wxFindNextFile();
	}
	
	int tLast = -1;
	srand(time(NULL));
	for (unsigned i = 0; i < slideshow->size(); i++) {
		if (IsCanceled())
			return false;
		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;
		}
		// Transition
		wxString transition = slideshow->GetSlide(i)->GetTransition();
		if (transition.length() == 0)
			transition = slideshow->GetTransition();
		if (transition.length() && transition != wxT("none") && i != slideshow->size() - 1) {
			if (transition == wxT("random")) {
				int t;
				do {
					t = rand() % transitions.size();
				} while (t == tLast);
				tLast = t;
				transition = transitions[t];
			}
			wxSVGDocument svg;
			if (!LoadTransition(svg, transition, slideshow, slideshow->GetSlide(i), slideshow->GetSlide(i + 1)))
				return false;
			int frameCount = slideshow->GetFPS();
			for (int f = 0; f < frameCount; f++) {
				wxImage img = svg.Render(width, height, NULL, false);
				if (!ffmpeg.EncodeImage(img, 1)) {
					Failed(_("Error creation of slideshow"));
					return false;
				}
				svg.SetCurrentTime(((double) f) / slideshow->GetFPS());
			}
		}
		IncSubStep(10);
	}
	ffmpeg.EndEncode();
	
	if (audioFile.length()) {
		// mplex (and optionally transcode audio)
		if (IsCanceled())
			return false;
		
		wxFfmpegMediaDecoder ffmpeg;
		if (!ffmpeg.Load(audioFile))
			return false;
		double audioDuration = ffmpeg.GetDuration();
		AddDetailMsg(_("Audio file duration: ") + Time2String(audioDuration * 1000));
		
		int resultDuration = slideshow->GetResultDuration();
		wxString concatFile;
		if (audioDuration > 0 && audioDuration < resultDuration) {
			// concate
			AddDetailMsg(_("Concat audio files"));
			int n = resultDuration / audioDuration + 1;
//			concatFile = vobFile + wxT("_audio.txt");
//			wxTextFile file(concatFile);
//			file.Create();
//			file.AddLine(wxT("ffconcat version 1.0"));
//			for (int i = 0; i < n; i++)
//				file.AddLine(wxT("file ") + audioFile);
//			file.Write();
			//ffmpeg -i 1.mp2 -i 1.mp2 -filter_complex "[0:0] [1:0] concat=n=2:v=0:a=1 [a]" -map "[a]"
			concatFile = vobFile + wxString(wxT("_audio.")) + (audioFormat  == afMP2 ? wxT("mp2") : wxT("ac3"));
			wxString avCmd = s_config.GetAVConvCmd();
			wxString filter;
			for (int i = 0; i < n; i++) {
				avCmd += wxT(" -i \"") + audioFile + wxT("\"");
				filter += wxString::Format(wxT("[%d:0] "), i);
			}
			filter += wxString::Format(wxT("concat=n=%d:v=0:a=1 [a]"), n);
			avCmd += wxT(" -filter_complex \"") + filter + wxT("\" -map \"[a]\" \"") + concatFile + wxT("\"");
			AVConvExecute exec(this, 0);
			if (!exec.Execute(avCmd)) {
				if (wxFileExists(concatFile))
					wxRemoveFile(concatFile);
				Failed(_("Error transcoding of ") + audioFile);
				return false;
			}
		}
		
		AddDetailMsg(_("Multiplexing audio and video"));
		
		Vob slideShowVob;
		slideShowVob.SetFilename(m2vFile);
		slideShowVob.AddAudioFile(concatFile.length() ? concatFile : audioFile);
		for (unsigned int i=0; i<slideShowVob.GetStreams().size(); i++) {
			Stream* stream = slideShowVob.GetStreams()[i];
			if (stream->GetType() == stAUDIO)
				stream->SetAudioFormat(audioFormat);
		}
		slideShowVob.SetTmpFilename(vobFile);
		slideShowVob.SetRecordingTime(resultDuration);
		if (!Transcode(&slideShowVob, slideshow->GetAspectRatio(), s_config.GetSlideshowVideoBitrate(),
				audioBitrate, s_config.GetUseMplexForMenus()))
			return false;
		if (s_config.GetRemoveTempFiles()) {
			DeleteFile(m2vFile);
			if (concatFile.length())
				DeleteFile(concatFile);
		}
	}

	wxYield();
	return true;
}

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

bool ProgressDlg::DeleteDir(wxString dir) {
	if (dir.Last() != wxFILE_SEP_PATH)
		dir += wxFILE_SEP_PATH;
	wxDir d(dir);
	wxString fname;
	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)) {
		wxLogError(_("Cannot remove directory '%s'"), dvdTmpDir.c_str());
		return false;
	}
	if (deleteOutDir && wxDirExists(dvdOutDir)) {
		if (!DeleteDir(dvdOutDir + wxT("VIDEO_TS"))) {
			return false;
		}
		if (!DeleteDir(dvdOutDir + wxT("AUDIO_TS"))) {
			return false;
		}
		if (!wxRmdir(dvdOutDir)) {
			wxLogError(_("Cannot remove directory '%s'"), dvdOutDir.c_str());
			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);
}
