/////////////////////////////////////////////////////////////////////////////
// Name:        DVD.cpp
// Purpose:     The class to store a DVD Structure (Titles/Menus)
// Author:      Alex Thuering
// Created:	29.01.2003
// RCS-ID:      $Id: DVD.cpp,v 1.74 2010/03/16 20:59:26 ntalex Exp $
// Copyright:   (c) Alex Thuering
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

#include "DVD.h"
#include "Menu.h"
#include "Cache.h"
#include "Config.h"
#include <wx/file.h>
#include <wx/filename.h>
#include <wx/tokenzr.h>
#include <wx/sstream.h>
#include <wxSVGXML/svgxmlhelpr.h>
#include <wxVillaLib/utils.h>
#include <wxSVG/mediadec_ffmpeg.h>

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

//////////////////////////////// TextSub ////////////////////////////////////

TextSub::TextSub(wxString filename) {
	m_filename = filename;
	m_characterSet = wxT("ISO8859-1");
	m_font = wxT("arial.ttf");
	m_fontSize = 28;
	m_hAlignment = wxT("left");
	m_vAlignment = wxT("bottom");
	m_leftMargin = 60;
	m_rightMargin = 60;
	m_topMargin = 20;
	m_bottomMargin = 30;
	m_subtitleFps = 25;
	m_movieFps = 25;
	m_movieWidth = 720;
	m_movieHeight = 574;
}

wxSvgXmlNode* TextSub::GetXML(DVDFileType type) {
	wxSvgXmlNode* textsubNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, _T("textsub"));
	TextSub* defaults = new TextSub(wxT(""));
	textsubNode->AddProperty(_T("filename"), m_filename);
	if (m_characterSet != defaults->m_characterSet)
		textsubNode->AddProperty(_T("characterset"), m_characterSet);
	if (m_font != defaults->m_font) // || type == DVDAUTHOR_XML
		textsubNode->AddProperty(_T("font"), m_font);
	if (m_fontSize != defaults->m_fontSize)
		textsubNode->AddProperty(_T("fontsize"), wxString::Format(wxT("%g"), m_fontSize));
	if (m_hAlignment != defaults->m_hAlignment)
		textsubNode->AddProperty(_T("horizontal-alignment"), m_hAlignment);
	if (m_vAlignment != defaults->m_vAlignment)
		textsubNode->AddProperty(_T("vertical-alignment"), m_vAlignment);
	if (m_leftMargin != defaults->m_leftMargin)
		textsubNode->AddProperty(_T("left-margin"), wxString::Format(wxT("%d"), m_leftMargin));
	if (m_rightMargin != defaults->m_rightMargin)
		textsubNode->AddProperty(_T("right-margin"), wxString::Format(wxT("%d"), m_rightMargin));
	if (m_topMargin != defaults->m_topMargin)
		textsubNode->AddProperty(_T("top-margin"), wxString::Format(wxT("%d"), m_topMargin));
	if (m_bottomMargin != defaults->m_bottomMargin)
		textsubNode->AddProperty(_T("bottom-margin"), wxString::Format(wxT("%d"), m_bottomMargin));
	if (m_subtitleFps != defaults->m_subtitleFps)
		textsubNode->AddProperty(_T("subtitle-fps"), wxString::Format(wxT("%g"), m_subtitleFps));
	if (m_movieFps != defaults->m_movieFps)
		textsubNode->AddProperty(_T("movie-fps"), wxString::Format(wxT("%g"), m_movieFps));
	if (m_movieWidth != defaults->m_movieWidth)
		textsubNode->AddProperty(_T("movie-width"), wxString::Format(wxT("%d"), m_movieWidth));
	if (m_movieHeight != defaults->m_movieHeight)
		textsubNode->AddProperty(_T("movie-height"), wxString::Format(wxT("%d"), m_movieHeight));
	delete defaults;
	return textsubNode;
}

bool TextSub::PutXML(wxSvgXmlNode* node) {
	wxString val;
	long lval;
	double dval;

	if (!node->GetPropVal(wxT("filename"), &m_filename))
		return false;

	node->GetPropVal(wxT("characterset"), &m_characterSet);
	node->GetPropVal(wxT("font"), &m_font);
	if (node->GetPropVal(wxT("fontsize"), &val) && val.ToDouble(&dval))
		m_fontSize = dval;
	node->GetPropVal(wxT("horizontal-alignment"), &m_hAlignment);
	node->GetPropVal(wxT("vertical-alignment"), &m_vAlignment);
	if (node->GetPropVal(wxT("left-margin"), &val) && val.ToLong(&lval))
		m_leftMargin = lval;
	if (node->GetPropVal(wxT("right-margin"), &val) && val.ToLong(&lval))
		m_rightMargin = lval;
	if (node->GetPropVal(wxT("top-margin"), &val) && val.ToLong(&lval))
		m_topMargin = lval;
	if (node->GetPropVal(wxT("bottom-margin"), &val) && val.ToLong(&lval))
		m_bottomMargin = lval;
	if (node->GetPropVal(wxT("subtitle-fps"), &val) && val.ToDouble(&dval))
		m_subtitleFps = dval;
	if (node->GetPropVal(wxT("movie-fps"), &val) && val.ToDouble(&dval))
		m_movieFps = dval;
	if (node->GetPropVal(wxT("movie-width"), &val) && val.ToLong(&lval))
		m_movieWidth = lval;
	if (node->GetPropVal(wxT("movie-height"), &val) && val.ToLong(&lval))
		m_movieHeight = lval;

	return true;
}

bool TextSub::SaveSpumux(wxString filename) {
	wxSvgXmlDocument xml;
	wxSvgXmlNode* root = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("subpictures"));
	wxSvgXmlNode* streamNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("stream"));
	streamNode->AddChild(GetXML(DVDAUTHOR_XML));
	root->AddChild(streamNode);
	xml.SetRoot(root);
	return xml.Save(filename);
}

//////////////////////////////// Stream /////////////////////////////////////

Stream::Stream(StreamType type, wxString codecName) {
	m_type = type;
	m_sourceCodecName = codecName;
	m_destinationFormat = 1; // vfCOPY/afCOPY/sfCOPY
	m_sourceChannelNumber = -1;
}

VideoFormat Stream::GetSourceVideoFormat() {
	if (m_sourceVideoSize.GetWidth() == 720 && m_sourceVideoSize.GetHeight() == 576)
		return vfPAL;
	else if (m_sourceVideoSize.GetWidth() == 720 && m_sourceVideoSize.GetHeight() == 480)
		return vfNTSC;
	return vfNONE;
}

AudioFormat Stream::GetSourceAudioFormat() {
	if (m_sourceCodecName == wxT("mp2"))
		return afMP2;
	else if (m_sourceCodecName == wxT("liba52") || m_sourceCodecName == wxT("ac3")
			|| m_sourceCodecName == wxT("ac-3"))
		return afAC3;
	return afNONE;
}

wxString Stream::GetSourceFormat() {
	wxString result = m_sourceCodecName;
	if (result == wxT("liba52") || m_sourceCodecName == wxT("ac-3"))
		result = wxT("ac3");
	else if (result == wxT("mpeg2video"))
		result = wxT("mpeg2");
	switch (m_type) {
	case stVIDEO:
		if (m_sourceVideoSize.IsFullySpecified()) {
			result += wxString::Format(wxT(", %dx%d"),
					m_sourceVideoSize.GetWidth(), m_sourceVideoSize.GetHeight());
			if (GetSourceVideoFormat() == vfPAL)
				result += wxT(" (PAL)");
			else if (GetSourceVideoFormat() == vfNTSC)
				result += wxT(" (NTSC)");
		}
		break;
	case stAUDIO:
		if (m_sourceChannelNumber > 0) {
			result += wxT(", ");
			if (m_sourceChannelNumber == 1)
				result += _("mono");
			else if (m_sourceChannelNumber == 2)
				result += _("stereo");
			else if (m_sourceChannelNumber == 6)
				result += wxT("5:1");
			else
				result += wxString::Format(_("%d channels"), m_sourceChannelNumber);
		}
		if (m_sourceSampleRate)
			result += wxT(", ") + wxString::Format(_("%d Hz"), m_sourceSampleRate);
		break;
	default:
		break;
	}
	return result;
}

////////////////////////////////// Vob //////////////////////////////////////

Vob::Vob(const Vob& vob) {
	m_filename = vob.m_filename;
	m_audioFilenames = vob.m_audioFilenames;
	for (unsigned int i = 0; i<vob.m_subtitles.size(); i++)
		m_subtitles.Add(new TextSub(*vob.m_subtitles[i]));
	m_tmpFilename = vob.m_tmpFilename;
	m_chapters = vob.m_chapters;
	m_pause = vob.m_pause;
	m_menu = vob.m_menu;
	m_slideshow = vob.m_slideshow;
	m_duration = vob.m_duration;
	for (unsigned int i = 0; i<vob.m_streams.size(); i++)
		m_streams.Add(new Stream(*vob.m_streams[i]));
	m_doNotTranscode = vob.m_doNotTranscode;
}

Vob::~Vob() {
	if (m_menu)
		delete m_menu;
	if (m_slideshow)
		delete m_slideshow;
	WX_CLEAR_ARRAY(m_subtitles)
	WX_CLEAR_ARRAY(m_streams)
}

bool Vob::SetFilename(wxString value) {
	m_filename = value;
	wxFfmpegMediaDecoder ffmpeg;
	bool ok = m_filename.length() && ffmpeg.Load(m_filename);
	m_duration = ok && ffmpeg.GetDuration() > 0 ? ffmpeg.GetDuration() : 0;
	m_streams.RemoveAt(0, m_streams.GetCount() - m_audioFilenames.GetCount());
	for (unsigned int stIdx = 0; stIdx < ffmpeg.GetStreamCount(); stIdx++) {
		Stream* stream = new Stream(ffmpeg.GetStreamType(stIdx), ffmpeg.GetCodecName(stIdx));
		if (stream->GetType() == stAUDIO) {
			stream->SetSourceChannelNumber(ffmpeg.GetChannelNumber(stIdx));
			stream->SetSourceSampleRate(ffmpeg.GetSampleRate(stIdx));
			stream->SetSourceBitrate(ffmpeg.GetBitrate(stIdx));
		} else if (stream->GetType() == stVIDEO) {
			stream->SetSourceVideoSize(ffmpeg.GetVideoSize());
			stream->SetSourceBitrate(ffmpeg.GetBitrate(stIdx));
		} else
			continue;
		m_streams.Insert(stream, stIdx);
	}
	return ok;
}

bool Vob::HasAudio() {
	if (GetAudioFilenames().GetCount() > 0)
		return true;
	for (int i = 0; i < (int)GetStreams().GetCount(); i++) {
		if (GetStreams()[i]->GetType() == stAUDIO)
			return true;
	}
	return false;
}


unsigned int Vob::GetAudioStreamCount() {
	unsigned int cnt = 0;
	for (StreamArray::iterator it = m_streams.begin(); it != m_streams.end(); it++)
		if ((*it)->GetType() == stAUDIO)
			cnt++;
	return cnt;
}

unsigned int Vob::GetSubtitleStreamsCount() {
	return GetSubtitles().GetCount();
}

bool Vob::AddAudioFile(wxString filename) {
	wxFfmpegMediaDecoder ffmpeg;
	if (!ffmpeg.Load(filename))
		return false;
	m_audioFilenames.Add(filename);
	if (ffmpeg.GetStreamCount() > 0) {
		Stream* stream = new Stream(ffmpeg.GetStreamType(0), ffmpeg.GetCodecName(0));
		stream->SetSourceChannelNumber(ffmpeg.GetChannelNumber(0));
		stream->SetSourceSampleRate(ffmpeg.GetSampleRate(0));
		m_streams.Add(stream);
	} else
		m_streams.Add(new Stream(stAUDIO, wxT("unknown")));
	if (GetFilename().length() == 0) // menu or slideshow
		m_duration = ffmpeg.GetDuration();
	return true;
}

void Vob::RemoveAudioFile(int index) {
	m_streams.RemoveAt(m_streams.GetCount() - m_audioFilenames.GetCount() + index);
	m_audioFilenames.RemoveAt(index);
}

long Vob::GetChapterTime(int chapter) {
	long time = 0;
	if (chapter > 0) {
		wxString timeStr = m_chapters;
		while (timeStr.Find(wxT(',')) != wxNOT_FOUND && --chapter)
			timeStr = timeStr.AfterFirst(wxT(','));
		timeStr = timeStr.BeforeFirst(wxT(','));
		while (1) {
			time *= 60;
			if (timeStr.Find(wxT(':')) == wxNOT_FOUND) {
				time *= 1000;
				double t = 0;
				if (timeStr.ToDouble(&t))
					time += (int) (t*1000);
				break;
			} else {
				long t = 0;
				if (timeStr.BeforeFirst(wxT(':')).ToLong(&t))
					time += t;
				timeStr = timeStr.AfterFirst(wxT(':'));
			}
		}
	}
	return time;
}

wxString Vob::GetVideoFrameUri(int chapter, long position) {
	wxString uri = GetFilename();
	if (position == -1 && chapter > 0)
		position = GetChapterTime(chapter);
	if (position >= 0 && uri.length() > 0)
		uri += wxT('#') + wxString::Format(wxT("%d"), position);
	return uri;
}

wxSvgXmlNode* Vob::GetXML(DVDFileType type, DVD* dvd) {
	wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("vob"));
	wxString fname = GetFilename();
	if (type == DVDAUTHOR_XML) {
		if (GetTmpFilename().length())
			fname = GetTmpFilename();
	} else {
		wxFileName filename(fname);
		if (filename.GetPath() == dvd->GetPath(false))
			fname = filename.GetFullName();
		else if (dvd->GetPath(false).length() > 0 && filename.GetPath().StartsWith(dvd->GetPath(false)))
			fname = filename.GetPath().substr(dvd->GetPath(false).length() + 1) + wxFILE_SEP_PATH
					+  filename.GetFullName();
	}
	if (fname.length())
		node->AddProperty(wxT("file"), fname);
	if (type == DVDSTYLER_XML) {
		int stIdx = 0;
		for (; stIdx < (int)(GetStreams().GetCount() - GetAudioFilenames().GetCount()); stIdx++) {
			Stream* stream = GetStreams()[stIdx];
			wxString streamName;
			switch (stream->GetType()) {
			case stVIDEO:
				streamName = wxT("video");
				break;
			case stAUDIO:
				streamName = wxT("audio");
				break;
			case stSUBTITLE:
				streamName = wxT("subtitle");
				break;
			default:
				break;
			}
			if (streamName.length() == 0)
				continue;
			wxSvgXmlNode* streamNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, streamName);
			streamNode->AddProperty(wxT("format"),
					wxString::Format(wxT("%d"), stream->GetDestinationFormat()));
			node->AddChild(streamNode);
		}
		for (int i = 0; i < (int)GetAudioFilenames().GetCount(); i++) {
			Stream* stream = GetStreams()[stIdx++];
			wxSvgXmlNode* audioNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("audio"));
			audioNode->AddChild(new wxSvgXmlNode(wxSVGXML_TEXT_NODE, wxEmptyString,
					GetAudioFilenames()[i]));
			audioNode->AddProperty(wxT("format"),
					wxString::Format(wxT("%d"), stream->GetDestinationFormat()));
			node->AddChild(audioNode);
		}
		for (int i = 0;  i < (int) GetSubtitles().GetCount(); i++) {
			node->AddChild(GetSubtitles()[i]->GetXML(type));
		}
		if (GetDoNotTranscode())
			node->AddProperty(wxT("doNotTranscode"), wxT("1"));
	}

	if (GetPause() != 0) {
		wxString pause= wxT("inf");
		if (GetPause()>0)
			pause = wxString::Format(wxT("%d"), GetPause());
		node->AddProperty(wxT("pause"), pause);
	}

	if (GetChapters().length() && !GetSlideshow())
		node->AddProperty(wxT("chapters"), GetChapters());

	if (GetMenu() && type == DVDSTYLER_XML)
		node->AddChild(GetMenu()->GetXML(type));

	if (GetSlideshow()) {
		if (type == DVDSTYLER_XML)
			node->AddChild(GetSlideshow()->GetXML(type));
		else if (type == DVDAUTHOR_XML) {
			wxString chapters;
			int t = 1;
			for (unsigned i=1; i<GetSlideshow()->Count()/5; i++) {
				t += GetSlideshow()->GetDuration()*5;
				int h = t/3600;
				int m = (t%3600)/60;
				int s = t%60;
				if (chapters.length())
					chapters += wxT(",");
				chapters += wxString::Format(wxT("%d:%2d:%2d.1"), h, m, s);
			}
			node->AddProperty(wxT("chapters"), chapters);
		}
	}

	return node;
}

bool Vob::PutXML(wxSvgXmlNode* node, DVD* dvd, int tsi) {
	wxString val;
	long lval;

	node->GetPropVal(wxT("file"), &val);
	if (val.length() > 0) {
		wxFileName fname(val);
		if (fname.IsRelative())
			val = dvd->GetPath() + fname.GetFullPath();
		else if (!wxFileExists(val) && wxFileExists(dvd->GetPath() + fname.GetFullName()))
			val = dvd->GetPath() + fname.GetFullName();
		SetFilename(val);
	}
	int stIdx = 0;
	wxSvgXmlNode* child = node->GetChildren();
	while (child) {
		if (child->GetName() == wxT("video")) {
			if (child->GetChildren() != NULL && child->GetChildren()->GetContent().length() > 0) {
				val = child->GetChildren()->GetContent();
				wxFileName fname(val);
				if (fname.IsRelative())
					val = dvd->GetPath() + fname.GetFullPath();
				else if (!wxFileExists(val) && wxFileExists(dvd->GetPath() + fname.GetFullName()))
					val = dvd->GetPath() + fname.GetFullName();
				SetFilename(val);
			}
			if (child->GetPropVal(wxT("format"), &val) && val.length() > 0 && val.ToLong(&lval)) {
				if ((int)m_streams.GetCount() <= stIdx || m_streams[stIdx]->GetType() != stVIDEO)
					stIdx = 0;
				while ((int)m_streams.GetCount() > stIdx) {
					if (m_streams[stIdx]->GetType() == stVIDEO) {
						m_streams[stIdx++]->SetDestinationFormat(lval);
						break;
					}
					stIdx++;
				}
			}
		} else if (child->GetName() == wxT("audio")) {
			if (child->GetChildren() != NULL && child->GetChildren()->GetContent().length() > 0) {
				val = child->GetChildren()->GetContent();
				wxFileName fname(val);
				if (fname.IsRelative())
					val = dvd->GetPath() + fname.GetFullPath();
				else if (!wxFileExists(val) && wxFileExists(dvd->GetPath() + fname.GetFullName()))
					val = dvd->GetPath() + fname.GetFullName();
				if (AddAudioFile(val) && child->GetPropVal(wxT("format"), &val) && val.length() > 0 && val.ToLong(&lval)) {
					stIdx = m_streams.GetCount() - 1;
					m_streams[stIdx++]->SetDestinationFormat(lval);
				}
			} else if (child->GetPropVal(wxT("format"), &val) && val.length() > 0 && val.ToLong(&lval)) {
				if ((int)m_streams.GetCount() <= stIdx || m_streams[stIdx]->GetType() != stAUDIO)
					stIdx = 0;
				while ((int)m_streams.GetCount() > stIdx) {
					if (m_streams[stIdx]->GetType() == stAUDIO) {
						m_streams[stIdx++]->SetDestinationFormat(lval);
						break;
					}
					stIdx++;
				}
			}
		} else if (child->GetName() == wxT("textsub")) {
			TextSub* textsub = new TextSub;
			textsub->PutXML(child);
			GetSubtitles().Add(textsub);
		} else if (child->GetName() == wxT("menu")) {
			m_menu = new Menu(dvd, tsi);
			if (!m_menu->PutXML(child))
				return false;
		} else if (child->GetName() == wxT("slideshow"))
			m_slideshow = new Slideshow(child);
		child = child->GetNext();
	}
	if (node->GetPropVal(wxT("pause"), &val)) {
		if (val == wxT("inf"))
			m_pause = -1;
		else if (val.ToLong(&lval))
			m_pause = int(lval);
	}
	if (node->GetPropVal(wxT("doNotTranscode"), &val) && val == wxT("1"))
		SetDoNotTranscode(true);
	node->GetPropVal(wxT("chapters"), &m_chapters);
	return true;
}

unsigned int Vob::GetFileSize(wxString filename) {
	return wxFile(filename).Length()/1024;
}

int Vob::GetSize(DVD* dvd, bool generated) {
	if (generated) {
		if (GetTmpFilename().length())
			return GetFileSize(GetTmpFilename());
		else if (GetFilename().length())
			return GetFileSize(GetFilename());
		return 0;
	}

	long size = 0;
	int stIdx = 0;
	if (GetMenu()) {
		double duration = m_duration;
		if (duration < 1)
			duration = s_config.GetMenuFrameCount()/(GetMenu()->GetVideoFormat() == vfPAL ? 25 : 30);
		size += (long)(duration*s_config.GetMenuVideoBitrate()/8/3);
	} else if (GetSlideshow()) {
		size += GetSlideshow()->GetDuration()*GetSlideshow()->Count()*s_config.GetSlideshowVideoBitrate()/8/3;
	} else {
		if (m_doNotTranscode) {
			size += GetFileSize(GetFilename());
		} else {
			if (GetFilename().length()) {
				int streamsCount = (int)m_streams.GetCount() - m_audioFilenames.GetCount();
				bool copyVideo = false;
				long audioSize = 0;
				for (; stIdx < streamsCount; stIdx++) {
					Stream* stream = m_streams[stIdx];
					switch (stream->GetType()) {
					case stVIDEO:
						if (stream->GetVideoFormat() == vfCOPY)
							copyVideo = true;
						else
							size += (long) (GetDuration()*dvd->GetVideoBitrate()/8);
						break;
					case stAUDIO: {
						int srcBitrate = stream->GetSourceBitrate() > 0 ? stream->GetSourceBitrate() : 64000;
						audioSize += (long) (GetDuration()*srcBitrate/8/1024);
						if (stream->GetAudioFormat() == afCOPY)
							size += (long) (GetDuration()*srcBitrate/8/1024);
						else if (stream->GetAudioFormat() != afNONE)
							size += (long) (GetDuration()*s_config.GetAudioBitrate()/8);
						break;
					}
					case stSUBTITLE:
					case stUNKNOWN:
						break;
					}
				}
				if (copyVideo)
					size += GetFileSize(GetFilename()) - audioSize;
			}
		}
	}
	for (unsigned int i=0; i<GetAudioFilenames().Count(); i++) {
		Stream* stream = m_streams[stIdx++];
		size += GetFileSize(GetAudioFilenames()[i]);
		if (stream->GetAudioFormat() == afCOPY)
			size += GetFileSize(GetAudioFilenames()[i]);
		else
			size += (long) (GetDuration()*s_config.GetAudioBitrate()/8);
	}
	for (unsigned int i=0; i<GetSubtitles().Count(); i++)
		size += GetFileSize(GetSubtitles()[i]->GetFilename());
	return size;
}

int Vob::GetRequiredSize(DVD* dvd, Cache* cache) {
	int size = GetSize(dvd);
	if (GetMenu() || GetSlideshow() || GetSubtitles().Count() > 0
			 || ((!GetDoNotTranscode() || GetAudioFilenames().Count() > 0)
					 && cache->Find(this, dvd, false).length() == 0))
		size = size * 2;
	return size;
}

Vob* Vob::CreateEmptyVob(VideoFormat videoFormat, AudioFormat audioFormat) {
	wxString filename= wxT("empty_pal_mp2.mpg");
	if (videoFormat == vfNTSC) {
		filename = audioFormat == afMP2 ? wxT("empty_ntsc_mp2.mpg") : wxT("empty_ntsc_ac3.mpg");
	} else if (audioFormat != afMP2) {
		filename = wxT("empty_pal_ac3.mpg");
	}
	return new Vob(DATA_FILE(filename));
}

////////////////////////////////// Pgc //////////////////////////////////////

Menu* Pgc::GetMenu() {
	for (int i = 0; i < (int) GetVobs().GetCount(); i++)
		if (GetVobs()[i]->GetMenu())
			return GetVobs()[i]->GetMenu();
	return NULL;
}

Slideshow* Pgc::GetSlideshow() {
	for (int i = 0; i < (int) GetVobs().GetCount(); i++)
		if (GetVobs()[i]->GetSlideshow())
			return GetVobs()[i]->GetSlideshow();
	return NULL;
}

unsigned int Pgc::GetChapterCount() {
	int result = 1;
	for (int vi = 0; vi < (int) GetVobs().Count(); vi++) {
		Vob& vob = *GetVobs()[vi];
		if (vob.GetSlideshow()) {
			result += vob.GetSlideshow()->Count() / 5;
		} else {
			wxStringTokenizer tkz(vob.GetChapters(), _T(","));
			while (tkz.HasMoreTokens()) {
				wxString token = tkz.GetNextToken();
				if (token != _T("0") || vi > 0)
					result++;
			}
		}
	}
	return result;
}

wxSvgXmlNode* Pgc::GetXML(DVDFileType type, DVD* dvd, wxString nextTitle) {
	wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("pgc"));
	if (m_entry.length())
		node->AddProperty(wxT("entry"), m_entry);

	if (m_palette.length())
		node->AddProperty(wxT("palette"), m_palette);

	if (GetMenu() && type == DVDAUTHOR_XML)
		GetMenu()->GetXML(type, node);

	if (GetSlideshow() && type == DVDAUTHOR_XML)
		GetSlideshow()->GetXML(type, node);

	for (int i = 0; i < (int) GetVobs().GetCount(); i++)
		node->AddChild(GetVobs()[i]->GetXML(type, dvd));

	if (GetPreCommands().length())
		XmlWriteValue(node, wxT("pre"), GetPreCommands());

	wxString postCommands = GetPostCommands();
	if (dvd->GetPlayAllRegister() != -1 && nextTitle.length()) {
		wxString g = wxT("g") + wxString::Format(wxT("%d"),dvd->GetPlayAllRegister());
		postCommands = wxT("if (") + g + wxT("==1) jump ") + nextTitle + wxT(";") + postCommands;
	}
	if (postCommands.length())
		XmlWriteValue(node, wxT("post"), postCommands);

	return node;
}

bool Pgc::PutXML(wxSvgXmlNode* node, DVD* dvd, int tsi) {
	WX_CLEAR_ARRAY(GetVobs());

	wxString val;
	node->GetPropVal(wxT("entry"), &m_entry);
	node->GetPropVal(wxT("palette"), &m_palette);

	wxSvgXmlNode* child = node->GetChildren();
	Menu* menu = NULL; // old

	while (child) {
		if (child->GetName() == wxT("spumux")) {
			// get spunode (old)
			menu = new Menu(dvd, tsi);
			menu->PutXML(child);
		} else if (child->GetName() == wxT("vob")) {
			// get vob
			Vob* vob;
			if (menu)
				vob = new Vob(menu);
			else
				vob = new Vob(wxT(""));
			menu = NULL;
			if (vob->PutXML(child, dvd, tsi))
				GetVobs().Add(vob);
			else {
				delete vob;
				wxLogError(_("Can't load vob"));
				return false;
			}
		} else if (child->GetName() == wxT("pre")) // get pre commands
			SetPreCommands(child->GetChildren()->GetContent());
		else if (child->GetName() == wxT("post")) // get post commands
			SetPostCommands(child->GetChildren()->GetContent());
		child = child->GetNext();
	}
	return true;
}

////////////////////////////// Video ///////////////////////////////////
wxArrayString Video::GetFormatStrings(wxString def) {
	wxArrayString ret;
	ret.Add(def);
	ret.Add(wxT("pal"));
	ret.Add(wxT("ntsc"));
	return ret;
}

wxArrayString Video::GetAspectStrings(wxString def) {
	wxArrayString ret;
	ret.Add(def);
	ret.Add(wxT("4:3"));
	ret.Add(wxT("16:9"));
	return ret;
}

wxArrayString Video::GetWidescreenStrings(wxString def) {
	wxArrayString ret;
	ret.Add(def);
	ret.Add(wxT("nopanscan"));
	ret.Add(wxT("noletterbox"));
	return ret;
}

wxSvgXmlNode* Video::GetXML(DVDFileType type) {
	wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("video"));

	if (m_format.length())
		node->AddProperty(wxT("format"), m_format);
	if (m_aspect.length())
		node->AddProperty(wxT("aspect"), m_aspect);
	if (m_resolution.length())
		node->AddProperty(wxT("resolution"), m_resolution);
	if (m_caption.length())
		node->AddProperty(wxT("caption"), m_caption);
	if (m_widescreen.length())
		node->AddProperty(wxT("widescreen"), m_widescreen);

	if (node->GetProperties() == NULL) {
		delete node;
		node = NULL;
	}
	return node;
}

bool Video::PutXML(wxSvgXmlNode* node) {
	if (node == NULL || node->GetName() != wxT("video"))
		return false;

	wxString val;
	node->GetPropVal(wxT("format"), &m_format);
	node->GetPropVal(wxT("aspect"), &m_aspect);
	node->GetPropVal(wxT("resolution"), &m_resolution);
	node->GetPropVal(wxT("caption"), &m_caption);
	node->GetPropVal(wxT("widescreen"), &m_widescreen);

	return true;
}

////////////////////////////////// PgcArray //////////////////////////////////////

unsigned int PgcArray::GetAudioStreamCount() {
	unsigned int count = 1; // at least one audio stream
	for (iterator it = begin(); it != end(); it++)
		for (VobArray::iterator it2 = (*it)->GetVobs().begin(); it2 != (*it)->GetVobs().end(); it2++)
			if (count < (*it2)->GetAudioStreamCount())
				count = (*it2)->GetAudioStreamCount();
	return count;
}

unsigned int PgcArray::GetSubtitleStreamsCount() {
	unsigned int count = 0;
	for (iterator it = begin(); it != end(); it++)
		for (VobArray::iterator it2 = (*it)->GetVobs().begin(); it2 != (*it)->GetVobs().end(); it2++)
			if (count < (*it2)->GetSubtitleStreamsCount())
				count = (*it2)->GetSubtitleStreamsCount();
	return count;
}

void PgcArray::UpdateAudioLangCodes() {
	unsigned int audioCnt = GetAudioStreamCount();
	while (m_audioLangCodes.size() < audioCnt)
		m_audioLangCodes.push_back(s_config.GetDefAudioLanguage());
	while (m_audioLangCodes.size() > audioCnt)
			m_audioLangCodes.pop_back();
}

////////////////////////////////// Menus //////////////////////////////////////
StringSet Menus::GetEntries() {
	StringSet entries;
	if (Count() > 0)
		entries.insert(wxT("root"));
	for (unsigned int pgci = 0; pgci < Count(); pgci++) {
		wxArrayString entryArray = wxStringTokenize(Item(pgci)->GetEntry(), wxT(","));
		for (unsigned int entryIdx = 0; entryIdx < entryArray.Count(); entryIdx++)
			if (entryArray[entryIdx] != wxT("root"))
				entries.insert(entryArray[entryIdx]);
	}
	return entries;
}

wxSvgXmlNode* Menus::GetXML(DVDFileType type, DVD* dvd) {
	wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("menus"));
	UpdateAudioLangCodes();
	for (wxArrayString::iterator it = GetAudioLangCodes().begin(); it != GetAudioLangCodes().end(); it++) {
		wxSvgXmlNode* audio = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("audio"));
		audio->AddProperty(wxT("lang"), *it);
		node->AddChild(audio);
	}
	if (GetVideo().GetXML(type))
		node->AddChild(GetVideo().GetXML(type));
	for (int i = 0; i < (int) GetCount(); i++)
		node->AddChild((*this)[i]->GetXML(type, dvd, wxT("")));
	return node;
}

bool Menus::PutXML(wxSvgXmlNode* node, DVD* dvd, int tsi) {
	if (node == NULL || node->GetName() != wxT("menus"))
		return false;
	WX_CLEAR_ARRAY(*this);
	wxSvgXmlNode* child = node->GetChildren();
	while (child) {
		if (child->GetName() == wxT("audio") && child->HasAttribute(wxT("lang")))
			GetAudioLangCodes().Add(child->GetAttribute(wxT("lang")));
		else if (child->GetName() == wxT("video"))
			GetVideo().PutXML(child);
		else if (child->GetName() == wxT("pgc")) {
			Pgc* pgc = new Pgc;
			if (pgc->PutXML(child, dvd, tsi))
				Add(pgc);
			else {
				delete pgc;
				wxLogError(_("Can't load pgc"));
				return false;
			}
		}
		child = child->GetNext();
	}
	return true;
}

////////////////////////////////// Titles ////////////////////////////////////

wxSvgXmlNode* Titles::GetXML(DVDFileType type, DVD* dvd, int nextTitleset) {
	wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("titles"));
	UpdateAudioLangCodes();
	for (wxArrayString::iterator it = GetAudioLangCodes().begin(); it != GetAudioLangCodes().end(); it++) {
		wxSvgXmlNode* audio = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("audio"));
		audio->AddProperty(wxT("lang"), *it);
		node->AddChild(audio);
	}
	if (GetVideo().GetXML(type))
		node->AddChild(GetVideo().GetXML(type));
	for (int i = 0; i < (int) GetCount(); i++) {
		wxString nextTitle;
		if (i != (int) GetCount() - 1)
			nextTitle = wxString::Format(wxT("title %d"), i + 2);
		node->AddChild((*this)[i]->GetXML(type, dvd, nextTitle));
	}
	return node;
}

bool Titles::PutXML(wxSvgXmlNode* node, DVD* dvd, int tsi) {
	if (node == NULL || node->GetName() != wxT("titles"))
		return false;
	wxSvgXmlNode* child = node->GetChildren();
	while (child) {
		if (child->GetName() == wxT("audio") && child->HasAttribute(wxT("lang")))
			GetAudioLangCodes().Add(child->GetAttribute(wxT("lang")));
		else if (child->GetName() == wxT("video"))
			GetVideo().PutXML(child);
		else if (child->GetName() == wxT("pgc")) {
			Pgc* pgc = new Pgc;
			if (pgc->PutXML(child, dvd, tsi))
				Add(pgc);
			else {
				delete pgc;
				wxLogError(_("Can't load pgc"));
				return false;
			}
		}
		child = child->GetNext();
	}
	return true;
}

////////////////////////////////// Titleset //////////////////////////////////

wxSvgXmlNode* Titleset::GetXML(DVDFileType type, DVD* dvd, int nextTitleset) {
	wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("titleset"));
	if (m_menus.Count() || type != DVDAUTHOR_XML)
		node->AddChild(m_menus.GetXML(type, dvd));
	else if (dvd->IsEmptyMenuEnabled() && dvd->GetVmgm().Count()) {
		// create empty menu with post command
		Menus menus;
		Pgc* pgc = new Pgc;
		pgc->SetPreCommands(wxT("jump vmgm menu;"));
		pgc->GetVobs().Add(Vob::CreateEmptyVob(dvd->GetVideoFormat(), dvd->GetAudioFormat()));
		menus.Add(pgc);
		node->AddChild(menus.GetXML(type, dvd));
	}
	node->AddChild(m_titles.GetXML(type, dvd, nextTitleset));
	return node;
}

bool Titleset::PutXML(wxSvgXmlNode* node, DVD* dvd, int tsi) {
	if (node == NULL || node->GetName() != wxT("titleset"))
		return false;
	wxSvgXmlNode* child = node->GetChildren();
	while (child) {
		if (child->GetName() == wxT("menus")) {
			if (!GetMenus().PutXML(child, dvd, tsi)) {
				wxLogError(_("Can't load menus"));
				return false;
			}
		} else if (child->GetName() == wxT("titles")) {
			if (!GetTitles().PutXML(child, dvd, tsi)) {
				wxLogError(_("Can't load titles"));
				return false;
			}
		}
		child = child->GetNext();
	}
	return true;
}

////////////////////////////////// VMGM //////////////////////////////////////

wxSvgXmlNode* Vmgm::GetXML(DVDFileType type, DVD* dvd) {
	wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("vmgm"));
	// add fgc
	if (GetFpc().length())
		XmlWriteValue(node, wxT("fpc"), GetFpc());

	// add menus
	if (type == DVDSTYLER_XML || Count())
		node->AddChild(Menus::GetXML(type, dvd));
	else if (dvd->IsEmptyMenuEnabled() && dvd->GetTitlesets().Count()
			&& dvd->GetTitlesets()[0]->GetMenus().Count()) {
		// create empty vmgm with post command
		Menus menus;
		Pgc* pgc = new Pgc;
		pgc->SetPreCommands(wxT("jump titleset 1 menu;"));
		pgc->GetVobs().Add(Vob::CreateEmptyVob(dvd->GetVideoFormat(), dvd->GetAudioFormat()));
		menus.Add(pgc);
		node->AddChild(menus.GetXML(DVDAUTHOR_XML, dvd));
	}
	return node;
}

bool Vmgm::PutXML(wxSvgXmlNode* node, DVD* dvd) {
	wxSvgXmlNode* child = node->GetChildren();
	while (child) {
		if (child->GetName() == wxT("menus"))
			Menus::PutXML(child, dvd, -1);
		else if (child->GetName() == wxT("fpc"))
			SetFpc(child->GetChildren()->GetContent());
		child = child->GetNext();
	}
	return true;
}

////////////////////////////////// DVD //////////////////////////////////////

DVD::DVD() {
	m_label = wxT("DVD");
	m_capacity = dcDVD47;
	m_videoBitrateAuto = true;
	m_videoBitrate = 4500;
	m_jumppad = false;
	m_emptyMenu = true;
	m_playAllRegister = -1;
	m_videoFormat = vfPAL;
	m_audioFormat = afMP2;
	m_aspectRatio = ar4_3;
}

DVD::~DVD() {
	WX_CLEAR_ARRAY(m_vmgm);
	WX_CLEAR_ARRAY(m_titlesets);
}

/** Returns disc capacity in KB */
long DVD::GetCapacityValue() {
	if (m_capacity == dcDVD47)
		return 4590208;
	else if (m_capacity == dcDVD85)
		return 8343424;
	return 0;
}

/** Calculates video bitrate if it set to auto */
wxArrayInt DVD::CalculateVideoBitrate() {
	long size = 0;
	long fixSize = s_config.GetDvdReservedSpace();
	long fixDuration = 0;
	long duration = 0;
	if (m_videoBitrateAuto)
		SetVideoBitrate(500);
	// menus
	for (int tsi = -1; tsi < (int) GetTitlesets().Count(); tsi++) {
		PgcArray& pgcs = GetPgcArray(tsi, true);
		for (int pgci = 0; pgci < (int) pgcs.Count(); pgci++) {
			Pgc* pgc = pgcs[pgci];
			for (int vobi = 0; vobi < (int) pgc->GetVobs().Count(); vobi++) {
				Vob* vob = pgc->GetVobs()[vobi];
				fixSize += vob->GetSize(this);
			}
		}
	}
	// titles
	for (int tsi = 0; tsi < (int) GetTitlesets().Count(); tsi++) {
		PgcArray& pgcs = GetPgcArray(tsi, false);
		for (int pgci = 0; pgci < (int) pgcs.Count(); pgci++) {
			Pgc* pgc = pgcs[pgci];
			for (int vobi = 0; vobi < (int) pgc->GetVobs().Count(); vobi++) {
				Vob* vob = pgc->GetVobs()[vobi];
				VideoFormat videoFormat = vfCOPY;
				if (!vob->GetSlideshow() && !vob->GetDoNotTranscode()) {
					for (int stIdx = 0; stIdx < (int) vob->GetStreams().GetCount(); stIdx++) {
						Stream* stream = vob->GetStreams()[stIdx];
						if (stream->GetType() == stVIDEO) {
							videoFormat = stream->GetVideoFormat();
							break;
						}
					}
				}
				if (videoFormat != vfCOPY) {
					duration += (long) vob->GetDuration();
					size += vob->GetSize(this);
				} else { 
					fixDuration += (long) vob->GetDuration();
					fixSize += vob->GetSize(this);
				}
			}
		}
	}
	long bitrate = GetVideoBitrate();
	if (m_videoBitrateAuto) {
		if (GetCapacity() != dcUNLIMITED) {
			long s = GetCapacityValue() - size - fixSize;
			bitrate = 500 + (s > 0 && duration > 0 ? s/duration*8 : (duration > 0 ? 0 : 7000));
			if (bitrate > 8000)
				bitrate = 8000;
		} else
			bitrate = 4500;
		SetVideoBitrate(bitrate);
	}
	wxArrayInt res;
	res.Add(bitrate);
	res.Add(fixSize);
	res.Add(fixDuration);
	return res;
}

unsigned int DVD::GetAudioStreamCount() {
	unsigned int count = 1; // at least one audio stream
	for (TitlesetArray::iterator it = GetTitlesets().begin(); it != GetTitlesets().end(); it++)
		if (count < (*it)->GetTitles().GetAudioStreamCount())
			count = (*it)->GetTitles().GetAudioStreamCount();
	return count;
}

unsigned int DVD::GetSubtitleStreamsCount() {
	unsigned int count = 0;
	for (TitlesetArray::iterator it = GetTitlesets().begin(); it != GetTitlesets().end(); it++)
		if (count < (*it)->GetTitles().GetSubtitleStreamsCount())
			count = (*it)->GetTitles().GetSubtitleStreamsCount();
	return count;
}

int DVD::AddTitleset() {
	Titleset* titleset = new Titleset;
	GetTitlesets().Add(titleset);
	return GetTitlesets().Count();
}

int DVD::AddMenu(int tsi) {
	return AddMenu(new Menu(this, tsi, m_videoFormat, m_aspectRatio), tsi);
}

int DVD::AddMenu(Menu* menu, int tsi) {
	Vob* vob = new Vob(menu);
	vob->SetPause(-1);
	Pgc* pgc = new Pgc;
	pgc->GetVobs().Add(vob);
	PgcArray& pgcs = GetPgcArray(tsi, true);
	pgcs.Add(pgc);
	return pgcs.Count()-1;
}

int DVD::AddPgc(int tsi, bool menu, Pgc* pgc)
{
  if (!pgc)
	pgc = new Pgc;
  PgcArray& pgcs = GetPgcArray(tsi, menu);
  pgcs.Add(pgc);
  return pgcs.Count()-1;
}

PgcArray& DVD::GetPgcArray(int tsi, bool menus) {
	if (tsi >= 0 && tsi < (int) GetTitlesets().Count()) {
		Titleset* ts = GetTitlesets()[tsi];
		if (menus)
			return ts->GetMenus();
		else
			return ts->GetTitles();
	}
	return GetVmgm();
}

Vob* DVD::GetVob(int tsi, bool isMenu, int pgci, int vobi) {
	PgcArray& pgcs = GetPgcArray(tsi, isMenu);
	if (pgci < 0 || pgci >= (int) pgcs.Count())
		return NULL;
	if (vobi < 0 || vobi >= (int) pgcs[pgci]->GetVobs().Count())
		return NULL;
	return pgcs[pgci]->GetVobs()[vobi];
}

Vob* DVD::GetMenuVob(int tsi, int pgci) {
	PgcArray& pgcs = GetPgcArray(tsi, true);
	if (pgci < 0 || pgci >= (int) pgcs.Count())
		return NULL;
	// find vob with menu
	for (int vobi = 0; vobi < (int) pgcs[pgci]->GetVobs().GetCount(); vobi++) {
		if (pgcs[pgci]->GetVobs()[vobi]->GetMenu() != NULL)
			return pgcs[pgci]->GetVobs()[vobi];
	}
	return NULL;
}

Menu* DVD::GetMenu(int tsi, int pgci) {
	Vob* vob = GetMenuVob(tsi, pgci);
	if (vob)
		return vob->GetMenu();
	return NULL;
}

/** Returns true if all actions are valid */
bool DVD::CheckActions() {
	bool valid = true;
	for (int tsi = -1; tsi < (int) GetTitlesets().Count(); tsi++) {
		PgcArray& pgcs = GetPgcArray(tsi, true);
		for (unsigned int pgci = 0; pgci < pgcs.GetCount(); pgci++) {
			Menu* menu = pgcs[pgci]->GetMenu();
			if (menu) {
				for (unsigned int obji = 0; obji < menu->GetObjectsCount(); obji++) {
					MenuObject* obj = menu->GetObject(obji);
					if (obj->IsButton()
							&& !obj->GetAction().IsValid(this, tsi, pgci, true, obj->GetId(true), true))
						valid = false;
				}
			}
		}
	}
	if (!valid)
		wxLogError(_("Some actions are invalid. Please click 'Details' for more information."));
	return valid;
}

/** Updates image in buttons with action jump to given title */
void DVD::UpdateButtonImageFor(int actionTsi, int actionPgci, wxString uri) {
	for (int tsi = -1; tsi < (int) GetTitlesets().GetCount(); tsi++) {
		PgcArray& pgcs = GetPgcArray(tsi, true);
		for (unsigned int pgci = 0; pgci < pgcs.GetCount(); pgci++) {
			Menu* menu = pgcs[pgci]->GetMenu();
			if (menu)
				menu->UpdateButtonImageFor(actionTsi != tsi ? actionTsi : -2, actionPgci, uri);
		}
	}
}


/** Fix coordinates of buttons if they are out of range */
void DVD::FixButtonCoordinates() {
	for (int tsi = -1; tsi < (int) GetTitlesets().GetCount(); tsi++) {
		PgcArray& pgcs = GetPgcArray(tsi, true);
		for (unsigned int pgci = 0; pgci < pgcs.GetCount(); pgci++) {
			Menu* menu = pgcs[pgci]->GetMenu();
			if (menu)
				menu->FixButtonCoordinates();
		}
	}
}

/** Loads a project file */
bool DVD::Open(wxString fname) {
	wxSvgXmlDocument xml;
	if (!xml.Load(fname)) {
		wxLogError(_("Cannot open file '%s'."), fname.c_str());
		return false;
	}

	wxSvgXmlNode* root = xml.GetRoot();
	if (root == NULL || root->GetName() != wxT("dvdstyler")) {
		wxLogError(_("'%s' is not a DVDStyler project file"), fname.c_str());
		return false;
	}

	if (fname.EndsWith(wxT(".dvdt")))
		m_templateFile = fname;
	else
		m_filename = fname;
	return PutXML(xml);
}

/** Initializes object with XML data */
bool DVD::PutXML(const wxSvgXmlDocument& xml) {
	WX_CLEAR_ARRAY(GetTitlesets());

	wxSvgXmlNode* root = xml.GetRoot();
	if (root == NULL || root->GetName() != wxT("dvdstyler")) {
		wxLogError(wxT("Invalid project XML data"));
		return false;
	}
	root->GetPropVal(wxT("name"), &m_label);

	wxString val;
	long lval;

	int format = 0;
	if (root->GetPropVal(wxT("format"), &val) && val.ToLong(&lval))
		format = int(lval);
	if (root->GetPropVal(wxT("template"), &val)) {
		if (wxFileName(val).IsAbsolute())
			m_templateFile = val;
		else
			m_templateFile = TEMPLATES_DIR + val;
	}
	if (root->GetPropVal(wxT("capacity"), &val) && val.ToLong(&lval))
		m_capacity = DiscCapacity(lval);
	if (root->GetPropVal(wxT("jumppad"), &val) && val.ToLong(&lval))
		m_jumppad = int(lval);
	if (root->GetPropVal(wxT("emptyMenu"), &val) && val.ToLong(&lval))
		m_emptyMenu = int(lval);
	if (root->GetPropVal(wxT("videoFormat"), &val) && val.ToLong(&lval))
		m_videoFormat = VideoFormat(lval + (format == 2 ? 2 : (format == 3 ? 1 : 0)));
	if (root->GetPropVal(wxT("audioFormat"), &val) && val.ToLong(&lval))
		m_audioFormat = AudioFormat(lval + (format == 2 ? 2 : (format == 3 ? 1 : 0)));
	if (root->GetPropVal(wxT("aspectRatio"), &val) && val.ToLong(&lval))
		m_aspectRatio = AspectRatio(lval);
	if (root->GetPropVal(wxT("videoBitrate"), &val) && val.ToLong(&lval)) {
		m_videoBitrateAuto = lval == -1;
		m_videoBitrate = lval == -1 ? 4500 : lval;
	}

	wxSvgXmlNode* child = root->GetChildren();
	while (child) {
		if (child->GetName() == wxT("vmgm")) {
			if (!GetVmgm().PutXML(child, this)) {
				wxLogError(_("Can't load vmgm menus"));
				return false;
			}
		} else if (child->GetName() == wxT("titleset")) {
			Titleset* titleset = new Titleset;
			if (titleset->PutXML(child, this, GetTitlesets().GetCount()))
				GetTitlesets().Add(titleset);
			else {
				delete titleset;
				wxLogError(_("Can't load titleset"));
				return false;
			}
		}
		child = child->GetNext();
	}
	return true;
}


/** Returns XML Document */
wxSvgXmlDocument* DVD::GetXml() {
	m_playAllRegister = -1;
	wxSvgXmlDocument* xml = new wxSvgXmlDocument;
	wxSvgXmlNode* root = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("dvdstyler"));
	root->AddProperty(wxT("format"), wxT("4"));
	if (m_templateFile.length() > 0) {
		wxString tplName = m_templateFile;
		wxString dir = TEMPLATES_DIR;
		if (tplName.StartsWith(dir))
			tplName = tplName.substr(dir.length());
		root->AddProperty(wxT("template"), tplName);
	}
	root->AddProperty(wxT("name"), m_label);
	root->AddProperty(wxT("capacity"), wxString::Format(wxT("%d"), m_capacity));
	root->AddProperty(wxT("jumppad"), m_jumppad ? wxT("1") : wxT("0"));
	root->AddProperty(wxT("emptyMenu"), m_emptyMenu ? wxT("1") : wxT("0"));
	root->AddProperty(wxT("videoFormat"), wxString::Format(wxT("%d"), m_videoFormat));
	root->AddProperty(wxT("audioFormat"), wxString::Format(wxT("%d"), m_audioFormat));
	root->AddProperty(wxT("aspectRatio"), wxString::Format(wxT("%d"), m_aspectRatio));
	root->AddProperty(wxT("videoBitrate"), wxString::Format(wxT("%d"), m_videoBitrateAuto ? -1 : m_videoBitrate));
	root->AddChild(GetVmgm().GetXML(DVDSTYLER_XML, this));
	for (int i=0; i<(int)m_titlesets.GetCount(); i++)
		if (m_titlesets[i]->GetMenus().Count() || m_titlesets[i]->GetTitles().Count())
			root->AddChild(m_titlesets[i]->GetXML(DVDSTYLER_XML, this));
	xml->SetRoot(root);
	return xml;
}

bool DVD::Save(wxString fname) {
	if (fname.length() > 0)
		m_filename = fname;
	wxSvgXmlDocument* xml = GetXml();
	bool result = xml->Save(m_filename);
	delete xml;
	return result;
}

/** Stores object data to string */
wxString DVD::Serialize() {
	wxSvgXmlDocument* xml = GetXml();
	wxStringOutputStream stream;
	xml->Save(stream);
	delete xml;
	return stream.GetString();
}

/** Restores object from data */
void DVD::Deserialize(const wxString& data) {
	wxStringInputStream stream(data);
	wxSvgXmlDocument xml;
	xml.Load(stream);
	PutXML(xml);
}

bool DVD::SaveDVDAuthor(wxString fname) {
	// check if we need "play all"
	m_playAllRegister = HasPlayAllButton() ? FindFreeRegister() : -1;
	// save config for dvdauthor
	wxSvgXmlDocument xml;
	wxSvgXmlNode* root = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("dvdauthor"));
	root->AddProperty(wxT("jumppad"), m_jumppad ? wxT("1") : wxT("0"));
	root->AddChild(GetVmgm().GetXML(DVDAUTHOR_XML, this));
	for (int i = 0; i < (int) m_titlesets.GetCount(); i++) {
		int nextTitleset =
				i < (int) m_titlesets.GetCount() - 1 && !m_titlesets[i + 1]->IsEmpty() ? i + 2 : -1;
		if (!m_titlesets[i]->IsEmpty())
			root->AddChild(m_titlesets[i]->GetXML(DVDAUTHOR_XML, this, nextTitleset));
	}
	xml.SetRoot(root);
	return xml.Save(fname);
}

wxString DVD::GetPath(bool withSep) {
	return withSep ? wxPathOnly(m_filename) + wxFILE_SEP_PATH : wxPathOnly(m_filename);
}

long DVD::GetSize(bool generated) {
	int size = s_config.GetDvdReservedSpace();
	for (int tsi = -1; tsi<(int)GetTitlesets().Count(); tsi++) {
		for (int m=0; m<=1; m++) {
			bool menu = m == 0;
			if (tsi == -1 && !menu) // "titleset -1" contains only vmMenus
				break;
			PgcArray& pgcs = GetPgcArray(tsi, menu);
			for (int pgci = 0; pgci<(int)pgcs.Count(); pgci++) {
				Pgc* pgc = pgcs[pgci];
				for (int vobi = 0; vobi<(int)pgc->GetVobs().Count(); vobi++)
					size += pgc->GetVobs()[vobi]->GetSize(this, generated);
			}
		}
	}
	return size;
}

long DVD::GetRequiredSize(Cache* cache) {
	int size = 0;
	for (int tsi = -1; tsi<(int)GetTitlesets().Count(); tsi++) {
		for (int m=0; m<=1; m++) {
			bool menu = m == 0;
			if (tsi == -1 && !menu) // "titleset -1" contains only vmMenus
				break;
			PgcArray& pgcs = GetPgcArray(tsi, menu);
			for (int pgci = 0; pgci<(int)pgcs.Count(); pgci++) {
				Pgc* pgc = pgcs[pgci];
				for (int vobi = 0; vobi<(int)pgc->GetVobs().Count(); vobi++)
					size += pgc->GetVobs()[vobi]->GetRequiredSize(this, cache);
			}
		}
	}
	return size;
}

bool DVD::HasPlayAllButton() {
	for (int tsi = -1; tsi<(int)GetTitlesets().Count(); tsi++) {
		PgcArray& pgcs = GetPgcArray(tsi, true);
		for (int pgci = 0; pgci<(int)pgcs.Count(); pgci++) {
			Pgc* pgc = pgcs[pgci];
			for (int vobi = 0; vobi<(int)pgc->GetVobs().Count(); vobi++) {
				Vob* vob = pgc->GetVobs()[vobi];
				if (vob->GetMenu()) {
					Menu* menu = vob->GetMenu();
					for (unsigned int obji = 0; obji<menu->GetObjectsCount(); obji++)
						if (menu->GetObject(obji)->IsButton()
								&& menu->GetObject(obji)->GetAction().IsPlayAll())
							return true;
				}
			}
		}
	}
	return false;
}

int DVD::FindFreeRegister() {
	for (int g=0; g<=12; g++) {
		if (!IsRegisterUsed(g))
			return g;
	}
	return -1;
}

bool DVD::IsRegisterUsed(int g) {
	wxString gstr = wxT("g") + wxString::Format(wxT("%d"),g);
	for (int tsi = -1; tsi<(int)GetTitlesets().Count(); tsi++) {
		for (int m=0; m<=1; m++) {
			bool menu = m == 0;
			if (tsi == -1 && !menu) // titleset -1 is Video-Manager and contains only vmMenus
				continue;
			PgcArray& pgcs = GetPgcArray(tsi, menu);
			for (int pgci = 0; pgci<(int)pgcs.Count(); pgci++) {
				Pgc* pgc = pgcs[pgci];
				if (pgc->GetPostCommands().Find(gstr) != -1
						|| pgc->GetPreCommands().Find(gstr) != -1)
					return true;
				for (int vobi = 0; vobi<(int)pgc->GetVobs().Count(); vobi++) {
					Vob* vob = pgc->GetVobs()[vobi];
					if (vob->GetMenu()) {
						Menu* menu = vob->GetMenu();
						for (unsigned int obji = 0; obji<menu->GetObjectsCount(); obji++) {
							MenuObject* obj = menu->GetObject(obji);
							if (obj->IsButton() && obj->GetAction().IsCustom()
									&& obj->GetAction().GetCustom().Find(gstr) != -1)
								return true;
						}
						for (unsigned int aIdx = 0; aIdx<menu->GetActionsCount(); aIdx++) {
							DVDAction* action = menu->GetAction(aIdx);
							if (action->IsCustom() && action->GetCustom().Find(gstr) != -1)
								return true;
						}
					}
				}
			}
		}
	}
	return false;
}

wxArrayString DVD::GetVideoFormatLabels(bool copy, bool none) {
	wxArrayString formats;
	if (none)
		formats.Add(_("None"));
	if (copy)
		formats.Add(_("Copy"));
	formats.Add(wxT("PAL 720x576"));
	formats.Add(wxT("NTSC 720x480"));
	return formats;
}

wxArrayString DVD::GetAudioFormatLabels(bool copy, bool none) {
	wxArrayString formats;
	if (none)
		formats.Add(_("None"));
	if (copy)
		formats.Add(_("Copy"));
	formats.Add(wxT("MP2 48 kHz"));
	formats.Add(wxT("AC3 48 kHz"));
	return formats;
}

wxArrayString DVD::GetAspectRatioLabels(bool autom) {
	wxArrayString formats;
	if (autom)
		formats.Add(_("Auto"));
	formats.Add(wxT("4:3"));
	formats.Add(wxT("16:9"));
	return formats;
}

wxArrayString DVD::GetCapacityLabels() {
	wxArrayString labels;
	labels.Add(wxT("4.7 GB"));
	labels.Add(wxT("8.5 GB"));
	labels.Add(wxT("Unlimited"));
	return labels;
}

wxArrayString DVD::GetVideoBitrateLabels() {
	wxArrayString labels;
	labels.Add(_("Auto"));
	for (int i=8; i>=2; i--)
		labels.Add(wxString::Format(_("%d MBit/s"), i));
	labels.Add(_("Custom"));
	return labels;
}

wxArrayString DVD::GetVideoFormatNames() {
	wxArrayString formats;
	formats.Add(wxT("none"));
	formats.Add(wxT("copy"));
	formats.Add(wxT("pal"));
	formats.Add(wxT("ntsc"));
	return formats;
}

wxArrayString DVD::GetAudioFormatNames() {
	wxArrayString formats;
	formats.Add(wxT("none"));
	formats.Add(wxT("copy"));
	formats.Add(wxT("mp2"));
	formats.Add(wxT("ac3"));
	return formats;
}

VideoFormat DVD::GetVideoFormatByName(wxString name) {
	int idx = GetVideoFormatNames().Index(name, false);
	return idx > 0 ? (VideoFormat) idx : vfNONE;
}

AudioFormat DVD::GetAudioFormatByName(wxString name) {
	int idx = GetAudioFormatNames().Index(name, false);
	return idx > 0 ? (AudioFormat) idx : afNONE;
}

wxString DVD::GetVideoFormatName(VideoFormat format) {
	return GetVideoFormatNames()[(int)format];
}

wxString DVD::GetAudioFormatName(AudioFormat format) {
	return GetAudioFormatNames()[(int)format];
}

const wxString audioLangCodesArray[] = { wxT("AA"), wxT("AB"), wxT("AF"), wxT("AM"), wxT("AR"), wxT("AS"),
		wxT("AY"), wxT("AZ"), wxT("BA"), wxT("BE"), wxT("BG"), wxT("BH"), wxT("BI"), wxT("BN"), wxT("BO"),
		wxT("BR"), wxT("CA"), wxT("CO"), wxT("CS"), wxT("CY"), wxT("DA"), wxT("DE"), wxT("DZ"), wxT("EL"),
		wxT("EN"), wxT("EO"), wxT("ES"), wxT("ET"), wxT("EU"), wxT("FA"), wxT("FI"), wxT("FJ"), wxT("FO"),
		wxT("FR"), wxT("FY"), wxT("GA"), wxT("GD"), wxT("GL"), wxT("GN"), wxT("GU"), wxT("HA"), wxT("HI"),
		wxT("HR"), wxT("HU"), wxT("HY"), wxT("IA"), wxT("IE"), wxT("IK"), wxT("IN"), wxT("IS"), wxT("IT"),
		wxT("IW"), wxT("JA"), wxT("JI"), wxT("JV"), wxT("KA"), wxT("KK"), wxT("KL"), wxT("KM"), wxT("KN"),
		wxT("KO"), wxT("KS"), wxT("KU"), wxT("KY"), wxT("LA"), wxT("LN"), wxT("LO"), wxT("LT"), wxT("LV"),
		wxT("MG"), wxT("MI"), wxT("MK"), wxT("ML"), wxT("MN"), wxT("MO"), wxT("MR"), wxT("MS"), wxT("MT"),
		wxT("MY"), wxT("NA"), wxT("NE"), wxT("NL"), wxT("NO"), wxT("OC"), wxT("OM"), wxT("OR"), wxT("PA"),
		wxT("PL"), wxT("PS"), wxT("PT"), wxT("QU"), wxT("RM"), wxT("RN"), wxT("RO"), wxT("RU"), wxT("RW"),
		wxT("SA"), wxT("SD"), wxT("SG"), wxT("SH"), wxT("SI"), wxT("SK"), wxT("SL"), wxT("SM"), wxT("SN"),
		wxT("SO"), wxT("SQ"), wxT("SR"), wxT("SS"), wxT("ST"), wxT("SU"), wxT("SV"), wxT("SW"), wxT("TA"),
		wxT("TE"), wxT("TG"), wxT("TH"), wxT("TI"), wxT("TK"), wxT("TL"), wxT("TN"), wxT("TO"), wxT("TR"),
		wxT("TS"), wxT("TT"), wxT("TW"), wxT("UK"), wxT("UR"), wxT("UZ"), wxT("VI"), wxT("VO"), wxT("WO"),
		wxT("XH"), wxT("YO"), wxT("ZH"), wxT("ZU") };
static wxArrayString audioLanguageCodes(136, audioLangCodesArray);

wxArrayString DVD::GetAudioLanguageCodes() {
	return audioLanguageCodes;
}
