/////////////////////////////////////////////////////////////////////////////
// Name:        ProgressDlg.cpp
// Purpose:     Progress of generation/burning dialog
// Author:      Alex Thuering
// Created:     14.08.2004
// RCS-ID:      $Id: ProgressDlg.cpp,v 1.54 2008/03/10 19:13:10 ntalex Exp $
// Copyright:   (c) Alex Thuering
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

#include "ProgressDlg.h"
#include "Config.h"
#include "MPEG.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 <wxSVG/SVGDocument.h>
#include "mediaenc_ffmpeg.h"

#define DATA_FILE(fname) wxFindDataFile(_T("data") + wxString(wxFILE_SEP_PATH) + fname)

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

#define MAX_WARNINGS 10

class ProcessExecute: public wxPipeExecute
{
  public:
	ProcessExecute(ProgressDlg* processDlg): m_processDlg(processDlg) {}
	virtual ~ProcessExecute() {};
	
	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;
	  }
	  m_processDlg->AddDetailText(line + _T("\n"));
	}

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

/////////////////////////// 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):
  wxDialog(parent, -1, wxEmptyString, wxDefaultPosition, wxDefaultSize,
  wxCAPTION|wxRESIZE_BORDER|wxSYSTEM_MENU|wxTHICK_FRAME)
{
    // begin wxGlade: ProgressDlg::ProgressDlg
    m_summaryLabel = new wxStaticText(this, -1, _("Summary:"));
    m_summaryText = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxTE_RICH);
    m_gauge = new wxGauge(this, -1, 10, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL|wxGA_PROGRESSBAR|wxGA_SMOOTH);
    m_detailsLabel = new wxStaticText(this, -1, _("Details:"));
    m_detailsText = new wxTextCtrl(this, -1, wxT(""), 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;
}

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);
    SetAutoLayout(true);
    SetSizer(mainSizer);
    Layout();
    Centre();
    // end wxGlade
	m_panelSizer = panelSizer;
#ifdef __WXMSW__
	m_minimizeBt->Hide();
#endif
}

void ProgressDlg::OnHideDetails(wxCommandEvent& WXUNUSED(event))
{
  if (m_detailsText->IsShown())
  {
	m_detailsLabel->Hide();
	m_detailsText->Hide();
	m_detailsText->Freeze();
	m_panelSizer->Remove(m_detailsLabel);
	m_panelSizer->Remove(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());
}

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::FailedExec(const wxString& command)
{
  Failed(_("Error executing of command: ") + command);
}

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

void ProgressDlg::Start(BurnDlg* burnDlg, DVD* dvd)
{
  m_winDisabler = new wxWindowDisabler(this);
  Show();
  Run(burnDlg, dvd);
  End();
  delete m_winDisabler;
  m_winDisabler = NULL;
  ShowModal();
}

void ProgressDlg::Run(BurnDlg* burnDlg, DVD* dvd)
{
  if (IsCanceled())
	return;
  
  wxString tmpDir = burnDlg->GetTempDir();
  if (tmpDir.Last() != wxFILE_SEP_PATH)
    tmpDir += wxFILE_SEP_PATH;
  wxString tmpDVDDir = tmpDir + wxString(wxT("dvd")) + wxFILE_SEP_PATH;
  
  // menus
  wxArrayPtrVoid menuVobs;
  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 = tmpDVDDir + 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())
	  {
		if (!MPEG::HasNavPacks(vob->GetMenu()->GetBackground()))
		  menuSubSteps += 200; // fixvob(200)
	  }
	  else
        menuSubSteps += 50; // generate mpeg
      menuSubSteps += 50; // spumux
	  menuVobs.Add(vob);
	}
  }
  
  // titles
  wxArrayPtrVoid titleVobs;
  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())
          continue;
		else if (vob->GetFilename().length())
		{
		  if (MPEG::HasNavPacks(vob->GetFilename()) &&
              vob->GetAudioFilenames().Count() == 0)
			continue;
		  titleSubSteps += 200;
		}
		else
		{
		  if (!vob->GetVideoFilename().length() ||
              !vob->GetAudioFilenames().Count())
			continue;
		  titleSubSteps += 100;
		}
		vob->SetTmpFilename(tmpDVDDir + wxString::Format(_T("title%d-%d-%d.vob"), tsi, pgci, vobi));
		titleVobs.Add(vob);
	  }
	}
  }
  
  // slideshow
  wxArrayPtrVoid slideshowVobs;
  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(tmpDVDDir + wxString::Format(_T("title%d-%d-%d.vob"), tsi, pgci, vobi));
          slideshowVobs.Add(vob);
        }
	  }
	}
  }
  
  // subtitle
  wxArrayPtrVoid subtitleVobs;
  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()*vob->GetSubtitles().Count();
          vob->SetTmpFilename(tmpDVDDir + wxString::Format(_T("title%d-%d-%d.vob"), tsi, pgci, vobi));
          subtitleVobs.Add(vob);
		}
	  }
	}
  }
  
  // BEGIN
  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);
  
  // 1. Prepare
  AddSummaryMsg(_("Prepare"));
  AddDetailMsg(_("Cleaning temporary directory"));
  if (wxDir::Exists(tmpDir) && wxFindFirstFile(tmpDir + _T("*"), wxDIR|wxFILE).length())
  {
    if (wxMessageBox(_("The temporary directory isn't empty.\nThis directory must be cleaned.\nContinue?"),
	    _("Burn"), wxYES_NO|wxICON_QUESTION, this) != wxYES)
	{
	  AddSummaryMsg(_("Aborted"), wxEmptyString, *wxRED);
	  return;
	}
    if (tmpDir.length()<=3 || !DeleteTempDir(tmpDir))
    {
      Failed(wxString::Format(_("Can't remove directory '%s'"), tmpDir.c_str()));
      return;
    }
  }
  // create tmp dir
  if (!wxDir::Exists(tmpDir) && !wxMkdir(tmpDir))
  {
	Failed(wxString::Format(_("Can't create directory '%s'"), tmpDir.c_str()));
    return;
  }
  if (!wxDir::Exists(tmpDVDDir) && !wxMkdir(tmpDVDDir))
  {
	Failed(wxString::Format(_("Can't create directory '%s'"), tmpDVDDir.c_str()));
    return;
  }
        
  // 2. Generate menus
  if (menuVobs.Count()>0)
  {
	if (IsCanceled())
	  return;
	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()))
		return;
	}
	IncStep();
  }
  
  // 3. Fix mpeg-files
  if (titleVobs.Count()>0)
  {
	if (IsCanceled())
	  return;
	AddSummaryMsg(_("Create VOB files"));
	SetSubSteps(titleSubSteps);
	for (int i=0; i<(int)titleVobs.Count(); i++)
	{
	  Vob* vob = (Vob*)titleVobs[i];
      if (vob->GetFilename().length())
	  {
		if (!FixVob(vob->GetFilename(), vob->GetAudioFilenames(),
              vob->GetTmpFilename()))
		  return;
	  }
	  else
	  {
		if (!MakeVob(vob->GetVideoFilename(), vob->GetAudioFilenames(),
			  vob->GetTmpFilename()))
		  return;
	  }
	}
	IncStep();
  }
  
  // 4. Generate slideshow
  if (slideshowVobs.Count()>0)
  {
	if (IsCanceled())
	  return;
	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;
	}
	IncStep();
  }
  
  // 5. Multiplex subtitles
  if (subtitleVobs.Count()>0)
  {
	if (IsCanceled())
	  return;
	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];
      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;
          }
          vobFile = vob->GetTmpFilename() + wxT(".old");
        }
        if (!MultiplexSubtitles(vobFile, vob->GetSubtitles()[s], vob->GetTmpFilename()))
          return;
        IncSubStep(vob->GetSize());
      }
	}
	IncStep();
  }
  
  // 5. Generate dvd filesystem
  if (IsCanceled())
    return;
  wxString dvdauthFile = tmpDVDDir + _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"), tmpDVDDir.Mid(0, tmpDVDDir.length()-1));
  DVDAuthorExecute dvdauthorExec(this, dvd->GetSize(true)/1024);
  if (!dvdauthorExec.Execute(cmd))
  {
    FailedExec(cmd);
    return;
  }
  // remove temp files
  if (s_config.GetRemoveTempFiles())
  {
    wxDir d(tmpDir + wxString(wxT("dvd")));
    wxString fname;
	while (d.GetFirst(&fname, _T("*.vob"), wxDIR_FILES))
	  if (!DeleteFile(tmpDVDDir + fname))
	    return;
	while (d.GetFirst(&fname, _T("menu*"), wxDIR_FILES))
	  if (!DeleteFile(tmpDVDDir + fname))
	    return;
	if (!DeleteFile(dvdauthFile))
	    return;
  }
  IncStep();
  
  // 6. Preview
  if (burnDlg->DoPreview())
  {
    if (IsCanceled())
      return;
    AddSummaryMsg(_("Start preview"));
    wxString cmd = s_config.GetPreviewCmd();
    cmd.Replace(_T("$DIR"), tmpDVDDir.Mid(0, tmpDVDDir.length()-1));
    if (!Exec(cmd))
      FailedExec(cmd);
    if (burnDlg->DoBurn() && wxMessageBox(_("Burn this video to dvd?"),
         _("Burn"), wxYES_NO|wxICON_QUESTION, this) == wxNO)
	{
	  AddSummaryMsg(_("Aborted"), wxEmptyString, *wxRED);
      return;
	}
    if (burnDlg->DoCreateIso() && wxMessageBox(_("Create iso image of this video?"),
         _("Burn"), wxYES_NO|wxICON_QUESTION, this) == wxNO)
    {
	  AddSummaryMsg(_("Aborted"), wxEmptyString, *wxRED);
      return;
	}
  }
  
  // 7. Create ISO-Image
  if (burnDlg->DoCreateIso() || (burnDlg->DoBurn() && burnDlg->DoAddECC()))
  {
    if (IsCanceled())
      return;
    AddSummaryMsg(_("Creating ISO image"));
	SetSubSteps(100);
    wxString cmd = s_config.GetIsoCmd();
    cmd.Replace(_T("$VOL_ID"), dvd->GetName());
    cmd.Replace(_T("$DIR"), tmpDVDDir.Mid(0, tmpDVDDir.length()-1));
    cmd.Replace(_T("$FILE"), burnDlg->DoCreateIso() ? burnDlg->GetIsoFile() : tmpDir + wxT("dvd.iso"));
	ProgressExecute exec(this, wxT(".*"));
    if (!exec.Execute(cmd))
    {
      FailedExec(cmd);
      return;
    }
	IncStep();
  }
  
  // 8. Add ECC-data
  if (burnDlg->DoAddECC())
  {
    if (IsCanceled())
      return;
    AddSummaryMsg(_("Adding ECC data"));
	SetSubSteps(200);
    wxString cmd = s_config.GetAddECCCmd();
    cmd.Replace(_T("$FILE"), burnDlg->DoCreateIso() ? burnDlg->GetIsoFile() : tmpDir + wxT("dvd.iso"));
	ProgressExecute exec(this,wxT("(Preparing|Ecc).*"));
    if (!exec.Execute(cmd))
    {
      FailedExec(cmd);
      return;
    }
	IncStep();
  }

  // 9. Format DVD
  if (burnDlg->DoBurn() && burnDlg->DoFormat())
  {
    if (IsCanceled())
      return;
    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
		{
		  FailedExec(cmd);
	      return;
		}
      }
      break;
    }
	IncStep();
  }
  
  // 10. Burn DVD
  if (burnDlg->DoBurn())
  {
    if (IsCanceled())
      return;
    AddSummaryMsg(_("Burning"));
	SetSubSteps(100);
	// check disc
	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;
    	}
    }
	// 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 + wxT("dvd.iso")).Length();
    } else {
      cmd = s_config.GetIsoSizeCmd();
      cmd.Replace(_T("$DIR"), tmpDVDDir.Mid(0, tmpDVDDir.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) * 2048;
      }
    }
    AddDetailMsg(wxString::Format(wxT("ISO Size: %d MB"), size/1024/1024));
    if (size > discSize*2048
        && 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;
    }
	// burn
	if (burnDlg->DoAddECC())
    {
      cmd = s_config.GetBurnISOCmd();
      cmd.Replace(_T("$FILE"), tmpDir + wxT("dvd.iso"));
      // get iso size in sectors
      long size = wxFile(tmpDir + wxT("dvd.iso")).Length()/2048;
      cmd.Replace(_T("$SIZE"), wxString::Format(wxT("%d"),size));
    }
    else
    {
      cmd = s_config.GetBurnCmd();
      cmd.Replace(_T("$DIR"), tmpDVDDir.Mid(0, tmpDVDDir.length()-1));
    }
    cmd.Replace(_T("$VOL_ID"), dvd->GetName());
    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))
    {
      FailedExec(cmd);
      return;
    }
	IncStep();
  }
  
  if (IsCanceled())
    return;
  
  // remove tmp dir
  if ((burnDlg->DoCreateIso() || burnDlg->DoBurn()) && s_config.GetRemoveTempFiles())
    DeleteTempDir(tmpDir);
  
  if (burnDlg->DoBurn())
    AddSummaryMsg(_("Burning was successful."), wxEmptyString, wxColour(0,128,0));
  else
    AddSummaryMsg(_("Generating was successful."), wxEmptyString, wxColour(0,128,0));
  IncStep();
  
  if (s_config.GetKey()<9)
    s_config.SetKey(s_config.GetKey()+1);
}

bool ProgressDlg::MakeVob(const wxString& videoFile,
  const wxArrayString& audioFiles, const wxString& vobFile)
{
  if (IsCanceled())
	return false;
  AddSummaryMsg(wxString::Format(_("Make Vob-file from '%s' and '%s'"),
	videoFile.c_str(), audioFiles[0].c_str()));
  // remove old
  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))
  {
	FailedExec(cmd);
	return false;
  }
  return true;
}

bool ProgressDlg::FixVob(const wxString& mpegFile,
 const wxArrayString& audioFiles, const wxString& vobFile)
{
  if (IsCanceled())
	return false;
  wxString tmpDir = vobFile.BeforeLast(wxFILE_SEP_PATH) + wxFILE_SEP_PATH;
  wxString tmpFile = vobFile.AfterLast(wxFILE_SEP_PATH).BeforeLast(wxT('.'));
  AddDetailMsg(_("Fix MPEG-file: ") + mpegFile);
  // remove old
  wxDir d(tmpDir);
  wxString fname;
  while (d.GetFirst(&fname, tmpFile + _T("*"), wxDIR_FILES))
	if (!DeleteFile(tmpDir + fname))
	  return false;
  wxString cmd = s_config.GetDemplexCmd();
  cmd.Replace(_T("$FILE_IN"), mpegFile);
  cmd.Replace(_T("$FILES_OUT"), tmpDir + tmpFile);
  ProgressExecute exec(this,wxT(".*"));
  if (!exec.Execute(cmd))
  {
	FailedExec(cmd);
	return false;
  }
  cmd = s_config.GetMplexCmd();
  d.GetFirst(&fname, tmpFile + _T("*"), wxDIR_FILES);
  cmd.Replace(_T("$FILE_VIDEO"), tmpDir + fname);
  d.GetNext(&fname);
  wxString audio = tmpDir + fname;
  for (unsigned int i = 0; i<audioFiles.Count(); i++)
    audio += wxT("\" \"") + audioFiles[i];
  cmd.Replace(_T("$FILE_AUDIO"), audio);
  cmd.Replace(_T("$FILE_OUT"), vobFile);
  if (!Exec(cmd))
  {
	FailedExec(cmd);
	return false;
  }
  IncSubStep(99);
  while (d.GetFirst(&fname, tmpFile + _T("-*"), wxDIR_FILES))
	if (!DeleteFile(tmpDir + fname))
	  return false;
  IncSubStep();
  return true;
}

bool ProgressDlg::GenerateMenu(Menu* menu, const wxString& menuFile, AudioFormat audioFormat)
{
  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");
  wxString silenceFile = audioFormat == afMP2 ?
    DATA_FILE(_T("silence.mp2")) : DATA_FILE(_T("silence.ac3"));
  
  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 (!MPEG::HasNavPacks(menu->GetBackground()))
	{
	  int subStep = GetSubStep();
      wxArrayString audioFiles;
	  if (!FixVob(menu->GetBackground(), audioFiles, mpegFile))
		return false;
	  SetSubStep(subStep+200);
	}
	else
	  mpegFile = menu->GetBackground();
  } else {
	bool useSilenceFile = true;
  	wxFfmpegMediaEncoder ffmpeg;
    if (!ffmpeg.BeginEncode(useSilenceFile ? m2vFile : mpegFile,
    		menu->GetVideoFormat(), useSilenceFile ? afNONE : audioFormat,
    		s_config.GetMenuVideoBitrate())
    	|| !ffmpeg.EncodeImage(bgImage, s_config.GetMenuFrameCount())) {
      Failed(_("Error creation of menu"));
      return false;
    }
    ffmpeg.EndEncode();
    IncSubStep(25);
    
    if (useSilenceFile) {
    	// mplex
		if (IsCanceled())
			return false;
		AddDetailMsg(_("Multiplexing audio and video"));
		wxString cmd = s_config.GetMplexCmd();
		cmd.Replace(_T("$FILE_VIDEO"), m2vFile);
		cmd.Replace(_T("$FILE_AUDIO"), silenceFile);
		cmd.Replace(_T("$FILE_OUT"), mpegFile);
		if (!Exec(cmd)) {
			FailedExec(cmd);
			return false;
		}
		if (s_config.GetRemoveTempFiles())
			if (!DeleteFile(m2vFile))
				return false;
	}
    IncSubStep(25);
  }
  
  //spumux
  if (IsCanceled())
    return false;
  AddDetailMsg(_("Multiplexing subtitles (buttons) into mpeg"));
  wxString cmd = s_config.GetSpumuxCmd();
  cmd.Replace(_T("$FILE_CONF"), spuFile);
  if (!Exec(cmd, mpegFile, menuFile))
  {
    FailedExec(cmd);
    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;
	if (!ffmpeg.BeginEncode(vobFile, slideshow->GetVideoFormat(), audioFormat,
			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::MultiplexSubtitles(const wxString& vobFile, TextSub* textSub,
  const wxString& resultFile)
{
  if (IsCanceled())
    return false;
  //spumux
  wxString cmd = s_config.GetSpumuxCmd();
  wxString spuFile = resultFile + wxT("_spumux.xml");
  textSub->SaveSpumux(spuFile);
  cmd.Replace(_T("$FILE_CONF"), spuFile);
  if (!Exec(cmd, vobFile, resultFile))
  {
    FailedExec(cmd);
    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::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::DeleteTempDir(wxString dir)
{
  if (dir.Last() != wxFILE_SEP_PATH)
	dir += wxFILE_SEP_PATH;
  wxDir d(dir + wxT("dvd"));
  wxString fname;
  d.GetFirst(&fname, wxEmptyString, wxDIR_DIRS);
  while (1)
  {
	if (fname.Lower() == _T("video_ts") || fname.Lower() == _T("audio_ts"))
	  if (!DeleteDir(dir + wxT("dvd") + wxFILE_SEP_PATH + fname, false))
        return false;
	if (!d.GetNext(&fname))
	  break;
  }
  d.GetFirst(&fname, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN);
  while(1)
  {
	if (fname.Mid(0,4).Lower() == _T("menu") ||
		fname.Mid(0,5).Lower() == _T("title"))
	  if (!DeleteFile(dir + wxT("dvd") + wxFILE_SEP_PATH + fname))
	    return false;
	if (!d.GetNext(&fname))
	  break;
  }
  if (wxFileName(dir + wxT("dvd.iso")).FileExists() && DeleteFile(dir + wxT("dvd.iso")))
      return false;
  d.Open(wxGetHomeDir());
  wxLogNull log;
  wxRmdir(dir + wxT("dvd"));
  wxRmdir(dir);
  return true;
}

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