1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        Vob.cpp
3 // Purpose:     The class to store a VOB data
4 // Author:      Alex Thuering
5 // Created:     29.01.2003
6 // RCS-ID:      $Id: Vob.cpp,v 1.15 2016/12/17 17:27:38 ntalex Exp $
7 // Copyright:   (c) Alex Thuering
8 // Licence:     GPL
9 /////////////////////////////////////////////////////////////////////////////
10 
11 #include "Vob.h"
12 #include "Menu.h"
13 #include "Cache.h"
14 #include "Config.h"
15 #include "Utils.h"
16 #include <wx/file.h>
17 #include <wx/filename.h>
18 #include <wx/tokenzr.h>
19 #include <wx/sstream.h>
20 #include <wx/convauto.h>
21 #include <wx/progdlg.h>
22 #include <wxSVGXML/svgxmlhelpr.h>
23 #include <wxVillaLib/utils.h>
24 #include <wxSVG/mediadec_ffmpeg.h>
25 #include <wxSVG/SVGDocument.h>
26 
27 #define DATA_FILE(fname) wxFindDataFile(_T("data") + wxString(wxFILE_SEP_PATH) + fname)
28 
29 /** Constructor */
Vob()30 Vob::Vob() {
31 	Init();
32 }
33 
34 /** Constructor */
Vob(const wxString & filename)35 Vob::Vob(const wxString& filename) {
36 	Init();
37 	SetFilename(filename);
38 }
39 
40 /** Constructor */
Vob(Menu * menu)41 Vob::Vob(Menu* menu) {
42 	Init(menu);
43 }
44 
45 /** Constructor */
Vob(Slideshow * slideshow)46 Vob::Vob(Slideshow* slideshow) {
47 	Init(NULL, slideshow);
48 }
49 
Init(Menu * menu,Slideshow * slideshow)50 void Vob::Init(Menu* menu, Slideshow* slideshow) {
51 	m_pause = 0;
52 	m_duration = 0;
53 	m_startTime = 0;
54 	m_recordingTime = -1;
55 	m_menu = menu;
56 	m_slideshow = slideshow;
57 	m_doNotTranscode = false;
58 	m_interlaced = false;
59 	m_firstField = ffAUTO;
60 	m_keepAspectRatio = true;
61 	m_keepAspectCrop = false;
62 	for (int i = 0; i < 4; i++) {
63 		m_pad.push_back(0);
64 		m_crop.push_back(0);
65 	}
66 	m_fadeIn = 0;
67 	m_fadeOut = 0;
68 }
69 
70 /** Constructor */
Vob(const Vob & vob)71 Vob::Vob(const Vob& vob): m_pad(vob.m_pad), m_crop(vob.m_crop) {
72 	m_filename = vob.m_filename;
73 	m_audioFilenames = vob.m_audioFilenames;
74 	for (unsigned int i = 0; i<vob.m_subtitles.size(); i++)
75 		m_subtitles.Add(new TextSub(*vob.m_subtitles[i]));
76 	m_tmpFilename = vob.m_tmpFilename;
77 	m_pause = vob.m_pause;
78 	m_menu = vob.m_menu;
79 	m_slideshow = vob.m_slideshow != NULL ? new Slideshow(*vob.m_slideshow) : NULL;
80 	m_duration = vob.m_duration;
81 	m_startTime = vob.m_startTime;
82 	m_recordingTime = vob.m_recordingTime;
83 	for (unsigned int i = 0; i<vob.m_streams.size(); i++)
84 		m_streams.push_back(new Stream(*vob.m_streams[i]));
85 	VECTOR_COPY(vob.m_cells, m_cells, Cell);
86 	m_interlaced = vob.m_interlaced;
87 	m_firstField = vob.m_firstField;
88 	m_keepAspectRatio = vob.m_keepAspectRatio;
89 	m_keepAspectCrop = vob.m_keepAspectCrop;
90 	m_doNotTranscode = vob.m_doNotTranscode;
91 	m_fadeIn = vob.m_fadeIn;
92 	m_fadeOut = vob.m_fadeOut;
93 	m_videoFilters = vob.m_videoFilters;
94 }
95 
~Vob()96 Vob::~Vob() {
97 	if (m_menu)
98 		delete m_menu;
99 	if (m_slideshow)
100 		delete m_slideshow;
101 	WX_CLEAR_ARRAY(m_subtitles)
102 	WX_CLEAR_ARRAY(m_streams)
103 	VECTOR_CLEAR(m_cells, Cell)
104 }
105 
SetFilename(const wxString & filename)106 bool Vob::SetFilename(const wxString& filename) {
107 	wxFfmpegMediaDecoder ffmpeg;
108 	if (filename.length() == 0 || !ffmpeg.Load(filename))
109 		return false;
110 	m_filename = filename;
111 	// read streams information
112 	if (m_streams.size() > m_audioFilenames.size())
113 		m_streams.erase(m_streams.begin(), m_streams.end() - m_audioFilenames.size());
114 	for (unsigned int stIdx = 0; stIdx < ffmpeg.GetStreamCount(); stIdx++) {
115 		Stream* stream = new Stream(ffmpeg.GetStreamType(stIdx), ffmpeg.GetCodecName(stIdx));
116 		if (stream->GetType() == stAUDIO) {
117 			stream->SetSourceChannelNumber(ffmpeg.GetChannelNumber(stIdx));
118 			stream->SetSourceSampleRate(ffmpeg.GetSampleRate(stIdx));
119 			stream->SetSourceBitrate(ffmpeg.GetBitrate(stIdx));
120 		} else if (stream->GetType() == stVIDEO) {
121 			stream->SetSourceVideoSize(ffmpeg.GetVideoSize());
122 			stream->SetSourceBitrate(ffmpeg.GetBitrate(stIdx));
123 			stream->SetSourceAspectRatio(ffmpeg.GetFrameAspectRatio());
124 			stream->SetSourceFps(ffmpeg.GetFps());
125 		}
126 		map<wxString, wxString> metadata = ffmpeg.GetMetadata(stIdx);
127 		if (metadata.find(wxT("language")) != metadata.end())
128 			stream->SetLanguage(metadata[wxT("language")]);
129 		m_streams.insert(m_streams.begin() + stIdx, stream);
130 	}
131 	// get video duration
132 	m_duration = ffmpeg.GetDuration();
133 	if (m_duration < 0) {
134 		if (ffmpeg.SetPosition(360000, false))
135 			m_duration = ffmpeg.GetPosition();
136 	}
137 	// set chapters
138 	for (double t : ffmpeg.GetChapters()) {
139 		m_cells.push_back(new Cell(lround(t*1000)));
140 	}
141 	return true;
142 }
143 
HasAudio()144 bool Vob::HasAudio() {
145 	if (GetAudioFilenames().size() > 0)
146 		return true;
147 	for (int i = 0; i < (int)GetStreams().size(); i++) {
148 		if (GetStreams()[i]->GetType() == stAUDIO)
149 			return true;
150 	}
151 	return false;
152 }
153 
154 /** Returns count of audio streams */
GetAudioStreamCount()155 unsigned int Vob::GetAudioStreamCount() {
156 	unsigned int cnt = 0;
157 	for (vector<Stream*>::iterator it = m_streams.begin(); it != m_streams.end(); it++) {
158 		Stream* st = *it;
159 		if (st->GetType() == stAUDIO && st->GetAudioFormat() != afNONE)
160 			cnt++;
161 	}
162 	return cnt;
163 }
164 
165 /** Returns count of subtitle streams */
GetSubtitleStreamsCount()166 unsigned int Vob::GetSubtitleStreamsCount() {
167 	unsigned int cnt = 0;
168 	if (!m_menu) {
169 		for (vector<Stream*>::iterator it = m_streams.begin(); it != m_streams.end(); it++) {
170 			Stream* st = *it;
171 			if (st->GetType() == stSUBTITLE && st->GetSubtitleFormat() != sfNONE)
172 				cnt++;
173 		}
174 		cnt += GetSubtitles().size();
175 	} else
176 		cnt = 1;
177 	return cnt;
178 }
179 
180 /** Adds the given audio file to the vob */
AddAudioFile(wxString filename)181 bool Vob::AddAudioFile(wxString filename) {
182 	if (filename.EndsWith(wxT("txt"))) {
183 		// concat format
184 		m_audioFilenames.Add(filename);
185 		m_streams.push_back(new Stream(stAUDIO, wxT("concat")));
186 		return true;
187 	}
188 	wxFfmpegMediaDecoder ffmpeg;
189 	if (!ffmpeg.Load(filename))
190 		return false;
191 	m_audioFilenames.Add(filename);
192 	if (ffmpeg.GetStreamCount() > 0) {
193 		for (unsigned int i = 0; i < ffmpeg.GetStreamCount(); i++) {
194 			if (ffmpeg.GetStreamType(i) == stAUDIO) {
195 				Stream* stream = new Stream(ffmpeg.GetStreamType(i), ffmpeg.GetCodecName(i));
196 				stream->SetSourceChannelNumber(ffmpeg.GetChannelNumber(i));
197 				stream->SetSourceSampleRate(ffmpeg.GetSampleRate(i));
198 				m_streams.push_back(stream);
199 				break;
200 			}
201 		}
202 	} else
203 		m_streams.push_back(new Stream(stAUDIO, wxT("unknown")));
204 	if (GetFilename().length() == 0) // menu or slideshow
205 		m_duration = ffmpeg.GetDuration();
206 	return true;
207 }
208 
209 /** Adds the given subtitles file to the vob */
AddSubtitlesFile(wxString filename)210 bool Vob::AddSubtitlesFile(wxString filename) {
211 	TextSub* textSub = new TextSub(filename);
212 	// detect encoding
213 	wxFile file;
214 	if (!file.Open(filename))
215 		return false;
216 	char buf[1024];
217 	int size = file.Read(buf, 1024);
218 	wxBOM bom = wxConvAuto::DetectBOM(buf, size);
219 	if (bom == wxBOM_UTF8) {
220 		textSub->SetCharacterSet(wxT("UTF-8"));
221 	} else if (bom == wxBOM_UTF16BE) {
222 		textSub->SetCharacterSet(wxT("UTF-16BE"));
223 	} else if (bom == wxBOM_UTF16LE) {
224 		textSub->SetCharacterSet(wxT("UTF-16LE"));
225 	} else if (bom == wxBOM_UTF32BE) {
226 		textSub->SetCharacterSet(wxT("UTF-32BE"));
227 	} else if (bom == wxBOM_UTF32LE) {
228 		textSub->SetCharacterSet(wxT("UTF-32LE"));
229 	} else if (!s_config.GetSubtitlesCharacterSet().StartsWith(wxT("UTF"))) {
230 		textSub->SetCharacterSet(s_config.GetSubtitlesCharacterSet());
231 	} else {
232 		wxString encoding = wxLocale::GetSystemEncodingName();
233 		if (encoding.StartsWith(wxT("windows-"))) {
234 			encoding = wxT("CP") + encoding.substr(8);
235 		}
236 		if (encoding.length()) {
237 			textSub->SetCharacterSet(encoding);
238 		}
239 	}
240 	m_subtitles.Add(textSub);
241 	return true;
242 }
243 
244 /** Returns name of file to display */
GetFilenameDisplay()245 wxString Vob::GetFilenameDisplay() {
246 	if (m_filename.StartsWith(wxT("concat:"))) {
247 		wxString fname = m_filename.Mid(7).BeforeFirst(wxT('|'));
248 		int idx = fname.Find(wxT("VIDEO_TS"));
249 		return idx > 0 ? fname.Mid(0, idx - 1) : fname;
250 	}
251 	return m_filename;
252 }
253 
254 /** Removes audio file with given index from the vob */
RemoveAudioFile(int index)255 void Vob::RemoveAudioFile(int index) {
256 	m_streams.erase(m_streams.end() - m_audioFilenames.size() + index);
257 	m_audioFilenames.RemoveAt(index);
258 }
259 
260 /** Sets chapter list */
SetChapters(const wxString & value,bool firstVob)261 void Vob::SetChapters(const wxString& value, bool firstVob) {
262 	Stream* stream = GetVideoStream();
263 	float fps = stream != NULL ? stream->GetSourceFps() : 25;
264 	VECTOR_CLEAR(m_cells, Cell);
265 	wxStringTokenizer tkz(value, _T(","));
266 	while (tkz.HasMoreTokens()) {
267 		wxString token = tkz.GetNextToken().Strip(wxString::both);
268 		long startTime = String2Time(token, fps);
269 		if (m_cells.size() == 0 && startTime > 0)
270 			m_cells.push_back(new Cell(0, firstVob));
271 		m_cells.push_back(new Cell(startTime));
272 	}
273 	if (firstVob && m_cells.size() == 0)
274 		m_cells.push_back(new Cell(0));
275 }
276 
277 /** Returns chapter list */
GetChapters()278 wxString Vob::GetChapters() {
279 	wxString result;
280 	for (vector<Cell*>::iterator it = m_cells.begin(); it != m_cells.end(); it++)
281 		if ((*it)->IsChapter()) {
282 			if (result.length())
283 				result += wxT(",");
284 			result += (*it)->GetStartStr();
285 		}
286 	return result;
287 }
288 
289 /** Returns count of chapters */
GetChapterCount()290 int Vob::GetChapterCount() {
291 	int result = 0;
292 	for (vector<Cell*>::iterator it = m_cells.begin(); it != m_cells.end(); it++)
293 		if ((*it)->IsChapter())
294 			result++;
295 	return result;
296 }
297 
298 /** Updates pad/crop values to keep aspect ratio*/
UpdatePadCrop(AspectRatio aspectRatio)299 void Vob::UpdatePadCrop(AspectRatio aspectRatio) {
300 	Stream* videoSt = GetVideoStream();
301 	VideoFormat videoFormat = videoSt != NULL ? videoSt->GetVideoFormat() : vfCOPY;
302 	if (GetKeepAspectRatio()) {
303 		if (GetKeepAspectCrop()) {
304 			int cropx = 0;
305 			int cropy = 0;
306 			if (CalcCrop(cropx, cropy, videoFormat, aspectRatio, GetPad())) {
307 				m_crop[0] = m_crop[1] = cropx;
308 				m_crop[2] = m_crop[3] = cropy;
309 			}
310 		} else {
311 			int padx = 0;
312 			int pady = 0;
313 			if (CalcPad(padx, pady, videoFormat, aspectRatio, GetCrop())) {
314 				m_pad[0] = m_pad[1] = padx;
315 				m_pad[2] = m_pad[3] = pady;
316 			}
317 		}
318 	}
319 }
320 
321 /** Calculates pad values to keep aspect ratio*/
CalcPad(int & padx,int & pady,VideoFormat videoFormat,AspectRatio aspectRatio,const vector<int> & crop)322 bool Vob::CalcPad(int& padx, int& pady, VideoFormat videoFormat, AspectRatio aspectRatio, const vector<int>& crop) {
323 	Stream* videoSt = GetVideoStream();
324 	if (videoSt != NULL && videoSt->GetSourceAspectRatio() <= 0)
325 		return false;
326 	padx = 0;
327 	pady = 0;
328 	if (videoFormat != vfCOPY && aspectRatio != arAUTO) {
329 		float oldAspect = videoSt->GetSourceAspectRatio();
330 		wxSize oldFrameSize = videoSt->GetSourceVideoSize();
331 		int cropX = crop[0] + crop[1];
332 		int cropY = crop[2] + crop[3];
333 		if (cropX + cropY > 0 && oldFrameSize.x > cropX && oldFrameSize.y > cropY)
334 			oldAspect *= ((double) oldFrameSize.y)/oldFrameSize.x*(oldFrameSize.x - cropX)/(oldFrameSize.y - cropY);
335 		float aspect = GetFrameAspectRatio(aspectRatio);
336 		wxSize frameSize = GetFrameSize(videoFormat);
337 		if (aspect > oldAspect)
338 			padx = lround(((double)frameSize.x)*(1.0 - oldAspect/aspect)/2);
339 		else if (aspect < oldAspect)
340 			pady = lround(((double)frameSize.y)*(1.0 - aspect/oldAspect)/2);
341 	}
342 	return true;
343 }
344 
345 /** Calculates crop value to keep aspect ratio*/
CalcCrop(int & cropx,int & cropy,VideoFormat videoFormat,AspectRatio aspectRatio,const vector<int> & pad)346 bool Vob::CalcCrop(int& cropx, int& cropy, VideoFormat videoFormat, AspectRatio aspectRatio, const vector<int>& pad) {
347 	Stream* videoSt = GetVideoStream();
348 	if (videoSt != NULL && videoSt->GetSourceAspectRatio() <= 0)
349 		return false;
350 	cropx = 0;
351 	cropy = 0;
352 	if (videoFormat != vfCOPY && aspectRatio != arAUTO) {
353 		float oldAspect = videoSt->GetSourceAspectRatio();
354 		wxSize oldFrameSize = videoSt->GetSourceVideoSize();
355 		float aspect = GetFrameAspectRatio(aspectRatio);
356 		wxSize frameSize = GetFrameSize(videoFormat);
357 		int padX = pad[0] + pad[1];
358 		int padY = pad[2] + pad[3];
359 		if (padX + padY > 0 && frameSize.x > padX && frameSize.y > padY)
360 			aspect *= ((double) frameSize.y)/frameSize.x*(frameSize.x - padX)/(frameSize.y - padY);
361 		if (aspect > oldAspect)
362 			cropy = lround(((double)oldFrameSize.y)*(1.0 - oldAspect/aspect)/2);
363 		else if (aspect < oldAspect)
364 			cropx = lround(((double)oldFrameSize.x)*(1.0 - aspect/oldAspect)/2);
365 	}
366 	return true;
367 }
368 
369 /** Returns all video filters (incl. crop, pad, fade-in and fade-out) */
GetAllVideoFilters()370 wxString Vob::GetAllVideoFilters() {
371 	wxString result;
372 
373 	// add crop & pad filters
374 	VideoFormat videoFormat = vfCOPY;
375 	for (unsigned int stIdx = 0; stIdx < GetStreams().size(); stIdx++) {
376 		Stream* stream = GetStreams()[stIdx];
377 		if (stream->GetType() == stVIDEO && stream->GetVideoFormat() != vfNONE) {
378 			videoFormat = stream->GetVideoFormat();
379 			break;
380 		}
381 	}
382 	if (videoFormat != vfCOPY) {
383 		wxSize frameSize = GetFrameSize(videoFormat);
384 		bool doCrop = m_crop.size() == 4 && m_crop[0] + m_crop[1] + m_crop[2] + m_crop[3] > 0;
385 		bool doPad = m_pad.size() == 4 && m_pad[0] + m_pad[1] + m_pad[2] + m_pad[3] > 0;
386 		if (doCrop) {
387 			if (result.length())
388 				result += wxT(',');
389 			result += wxString::Format(wxT("crop=iw-%d:ih-%d:%d:%d"),
390 					m_crop[0] + m_crop[1], m_crop[2] + m_crop[3], m_crop[0], m_crop[2]);
391 		}
392 		if (doCrop || doPad) {
393 			if (result.length())
394 				result += wxT(',');
395 			result += wxString::Format(wxT("scale=%d:%d"),
396 					frameSize.GetWidth() - m_pad[0] - m_pad[1], frameSize.GetHeight() - m_pad[2] - m_pad[3]);
397 		}
398 		if (doPad) {
399 			result += wxString::Format(wxT(",pad=%d:%d:%d:%d"),
400 					frameSize.GetWidth(), frameSize.GetHeight(), m_pad[0], m_pad[2]);
401 		}
402 	}
403 
404 	// add fade-in and fade-out filters
405 	if (GetFadeIn() > 0) {
406 		if (result.length())
407 			result += wxT(',');
408 		result += wxString::Format(wxT("fade=in:st=%g:d=%g"), GetStartTime(), GetFadeIn());
409 	}
410 	if (GetFadeOut() > 0) {
411 		if (result.length())
412 			result += wxT(',');
413 		double endTime = GetStartTime() + GetResultDuration();
414 		result += wxString::Format(wxT("fade=out:st=%g:d=%g"), endTime - GetFadeOut(), GetFadeOut());
415 	}
416 
417 	// add custom filters
418 	if (GetVideoFilters().length()) {
419 		if (result.length())
420 			result += wxT(',');
421 		result += GetVideoFilters();
422 	}
423 	return result;
424 }
425 
426 /** Returns duration of result video (sec) */
GetResultDuration()427 double Vob::GetResultDuration() {
428 	if (GetSlideshow()) {
429 		return GetSlideshow()->GetResultDuration();
430 	}
431 	return GetRecordingTime() >= 0 ? GetRecordingTime() : GetDuration() - GetStartTime();
432 }
433 
434 /** Returns video stream parameters */
GetVideoStream()435 Stream* Vob::GetVideoStream() {
436 	for (int i = 0; i < (int)GetStreams().size(); i++)
437 		if (GetStreams()[i]->GetType() == stVIDEO)
438 			return GetStreams()[i];
439 	return NULL;
440 }
441 
442 /** Returns video stream index */
GetVideoStreamIndex()443 int Vob::GetVideoStreamIndex() {
444 	for (int i = 0; i < (int)GetStreams().size(); i++)
445 		if (GetStreams()[i]->GetType() == stVIDEO)
446 			return i;
447 	return -1;
448 }
449 
GetXML(DVDFileType type,DVD * dvd,int nextTitle)450 wxSvgXmlNode* Vob::GetXML(DVDFileType type, DVD* dvd, int nextTitle) {
451 	wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("vob"));
452 	wxString fname = GetFilename();
453 	if (type == DVDAUTHOR_XML) {
454 		if (GetTmpFilename().length())
455 			fname = GetTmpFilename();
456 	} else {
457 		wxFileName filename(fname);
458 		if (filename.GetPath() == dvd->GetPath(false))
459 			fname = filename.GetFullName();
460 		else if (dvd->GetPath(false).length() > 0 && filename.GetPath().StartsWith(dvd->GetPath(false)))
461 			fname = filename.GetPath().substr(dvd->GetPath(false).length() + 1) + wxFILE_SEP_PATH
462 					+  filename.GetFullName();
463 	}
464 	if (fname.length())
465 		node->AddProperty(wxT("file"), fname);
466 
467 	if (type == DVDSTYLER_XML) {
468 		int stIdx = 0;
469 		for (; stIdx < (int)(GetStreams().size() - GetAudioFilenames().size()); stIdx++) {
470 			if (GetStreams()[stIdx]->GetType() == stUNKNOWN)
471 				continue;
472 			node->AddChild(GetStreams()[stIdx]->GetXML(type, this));
473 		}
474 		for (int i = 0; i < (int)GetAudioFilenames().size(); i++) {
475 			wxSvgXmlNode* audioNode = GetStreams()[stIdx++]->GetXML(type, this);
476 			audioNode->AddChild(new wxSvgXmlNode(wxSVGXML_TEXT_NODE, wxEmptyString, GetAudioFilenames()[i]));
477 			node->AddChild(audioNode);
478 		}
479 		for (int i = 0;  i < (int) GetSubtitles().size(); i++) {
480 			node->AddChild(GetSubtitles()[i]->GetXML(type));
481 		}
482 		if (GetDoNotTranscode())
483 			node->AddProperty(wxT("doNotTranscode"), wxT("1"));
484 		if (m_pad[0] + m_pad[1] + m_pad[2] + m_pad[3] > 0) {
485 			wxSvgXmlNode* padNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("pad"));
486 			if (m_pad[0] > 0)
487 				padNode->AddProperty(wxT("left"), wxString::Format(wxT("%d"), m_pad[0]));
488 			if (m_pad[1] > 0)
489 				padNode->AddProperty(wxT("right"), wxString::Format(wxT("%d"), m_pad[1]));
490 			if (m_pad[2] > 0)
491 				padNode->AddProperty(wxT("top"), wxString::Format(wxT("%d"), m_pad[2]));
492 			if (m_pad[3] > 0)
493 				padNode->AddProperty(wxT("bottom"), wxString::Format(wxT("%d"), m_pad[3]));
494 			node->AddChild(padNode);
495 		}
496 		if (m_crop[0] + m_crop[1] + m_crop[2] + m_crop[3] > 0) {
497 			wxSvgXmlNode* cropNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("crop"));
498 			if (m_crop[0] > 0)
499 				cropNode->AddProperty(wxT("left"), wxString::Format(wxT("%d"), m_crop[0]));
500 			if (m_crop[1] > 0)
501 				cropNode->AddProperty(wxT("right"), wxString::Format(wxT("%d"), m_crop[1]));
502 			if (m_crop[2] > 0)
503 				cropNode->AddProperty(wxT("top"), wxString::Format(wxT("%d"), m_crop[2]));
504 			if (m_crop[3] > 0)
505 				cropNode->AddProperty(wxT("bottom"), wxString::Format(wxT("%d"), m_crop[3]));
506 			node->AddChild(cropNode);
507 		}
508 		// start & recording time
509 		if (GetStartTime() > 0)
510 			node->AddProperty(wxT("startTime"), wxString::Format(wxT("%f"), GetStartTime()));
511 		if (GetRecordingTime() > 0)
512 			node->AddProperty(wxT("recordingTime"), wxString::Format(wxT("%f"), GetRecordingTime()));
513 		// video filters
514 		if (GetFadeIn() > 0)
515 			node->AddProperty(wxT("fadeIn"), wxString::Format(wxT("%g"), GetFadeIn()));
516 		if (GetFadeOut() > 0)
517 			node->AddProperty(wxT("fadeOut"), wxString::Format(wxT("%g"), GetFadeOut()));
518 		if (GetVideoFilters().length() > 0)
519 			node->AddProperty(wxT("videoFilters"), GetVideoFilters());
520 	}
521 
522 	// chapters/cells
523 	bool cells = false;
524 	if (m_cells.size()) {
525 		if ((*m_cells.begin())->GetStart() > 0)
526 			cells = true;
527 		else if ((*(m_cells.end()-1))->GetEnd() != -1)
528 			cells = true;
529 	}
530 	for (vector<Cell*>::iterator it = m_cells.begin(); it != m_cells.end(); it++) {
531 		if ((!(*it)->IsChapter() && m_cells.size() > 0) || (*it)->GetEnd() != -1
532 				|| (*it)->GetPause() != 0 || (*it)->GetCommands().length() > 0) {
533 			cells = true;
534 			break;
535 		}
536 	}
537 	if (cells) {
538 		for (vector<Cell*>::iterator it = m_cells.begin(); it != m_cells.end(); it++) {
539 			Cell* cell = *it;
540 			long nextTime = it + 1 != m_cells.end() ? (*(it + 1))->GetStart() : -1;
541 			long endTime = type == DVDAUTHOR_XML && cell->GetEnd() == -1 ? nextTime : cell->GetEnd();
542 			node->AddChild(cell->GetXML(type, endTime, dvd, nextTitle));
543 		}
544 	} else {
545 		wxString chapters = GetChapters();
546 		if (chapters.length())
547 			node->AddProperty(wxT("chapters"), chapters);
548 
549 		if (GetPause() != 0) {
550 			wxString pauseStr = GetPause() > 0 ? wxString::Format(wxT("%d"), GetPause()) : wxT("inf");
551 			node->AddProperty(wxT("pause"), pauseStr);
552 		}
553 	}
554 
555 	if (GetMenu() && type == DVDSTYLER_XML)
556 		node->AddChild(GetMenu()->GetXML(type));
557 
558 	if (GetSlideshow()) {
559 		if (type == DVDSTYLER_XML)
560 			node->AddChild(GetSlideshow()->GetXML(type));
561 		else if (type == DVDAUTHOR_XML && m_cells.size() == 0) {
562 			wxString chapters;
563 			int t = 1;
564 			for (unsigned i=1; i<GetSlideshow()->size()/5; i++) {
565 				t += GetSlideshow()->GetResultDuration();
566 				int h = t/3600;
567 				int m = (t%3600)/60;
568 				int s = t%60;
569 				if (chapters.length())
570 					chapters += wxT(",");
571 				chapters += wxString::Format(wxT("%d:%2d:%2d.1"), h, m, s);
572 			}
573 			node->AddProperty(wxT("chapters"), chapters);
574 		}
575 	}
576 
577 	return node;
578 }
579 
PutXML(wxSvgXmlNode * node,DVD * dvd,int tsi,int pgci,bool menu,bool firstVob)580 bool Vob::PutXML(wxSvgXmlNode* node, DVD* dvd, int tsi, int pgci, bool menu, bool firstVob) {
581 	wxString val;
582 	long lval;
583 	double dval;
584 
585 	node->GetPropVal(wxT("file"), &val);
586 	if (val.length() > 0) {
587 		if (!val.StartsWith(wxT("concat:"))) {
588 			wxFileName fname(val);
589 			if (fname.IsRelative())
590 				val = dvd->GetPath() + fname.GetFullPath();
591 			else if (!wxFileExists(val) && wxFileExists(dvd->GetPath() + fname.GetFullName()))
592 				val = dvd->GetPath() + fname.GetFullName();
593 		}
594 		SetFilename(val);
595 	}
596 	int stIdx = 0;
597 	wxSvgXmlNode* child = node->GetChildren();
598 	while (child) {
599 		if (child->GetName() == wxT("video")) {
600 			if (child->GetChildren() != NULL && child->GetChildren()->GetContent().length() > 0) {
601 				val = child->GetChildren()->GetContent();
602 				wxFileName fname(val);
603 				if (fname.IsRelative())
604 					val = dvd->GetPath() + fname.GetFullPath();
605 				else if (!wxFileExists(val) && wxFileExists(dvd->GetPath() + fname.GetFullName()))
606 					val = dvd->GetPath() + fname.GetFullName();
607 				SetFilename(val);
608 			}
609 
610 			if (child->GetPropVal(wxT("format"), &val) && val.length() > 0 && val.ToLong(&lval)) {
611 				if ((int)m_streams.size() <= stIdx || m_streams[stIdx]->GetType() != stVIDEO)
612 					stIdx = 0;
613 				while ((int)m_streams.size() > stIdx) {
614 					if (m_streams[stIdx]->GetType() == stVIDEO) {
615 						m_streams[stIdx]->PutXML(child, this);
616 						stIdx++;
617 						break;
618 					}
619 					stIdx++;
620 				}
621 			}
622 		} else if (child->GetName() == wxT("audio")) {
623 			if (child->GetChildren() != NULL && child->GetChildren()->GetContent().length() > 0) {
624 				val = child->GetChildren()->GetContent();
625 				wxFileName fname(val);
626 				if (fname.IsRelative())
627 					val = dvd->GetPath() + fname.GetFullPath();
628 				else if (!wxFileExists(val) && wxFileExists(dvd->GetPath() + fname.GetFullName()))
629 					val = dvd->GetPath() + fname.GetFullName();
630 				if (AddAudioFile(val)) {
631 					stIdx = m_streams.size() - 1;
632 					m_streams[stIdx]->PutXML(child, this);
633 					stIdx++;
634 				}
635 			} else if (child->GetPropVal(wxT("format"), &val) && val.length() > 0 && val.ToLong(&lval)) {
636 				if ((int)m_streams.size() <= stIdx || m_streams[stIdx]->GetType() != stAUDIO)
637 					stIdx = 0;
638 				while ((int)m_streams.size() > stIdx) {
639 					if (m_streams[stIdx]->GetType() == stAUDIO) {
640 						m_streams[stIdx]->PutXML(child, this);
641 						stIdx++;
642 						break;
643 					}
644 					stIdx++;
645 				}
646 			}
647 		} else if (child->GetName() == wxT("subtitle")) {
648 			if (child->GetPropVal(wxT("format"), &val) && val.length() > 0 && val.ToLong(&lval) && lval != 1) {
649 				if ((int)m_streams.size() <= stIdx || m_streams[stIdx]->GetType() != stSUBTITLE)
650 					stIdx = 0;
651 				while ((int)m_streams.size() > stIdx) {
652 					if (m_streams[stIdx]->GetType() == stSUBTITLE) {
653 						m_streams[stIdx]->PutXML(child, this);
654 						stIdx++;
655 						break;
656 					}
657 					stIdx++;
658 				}
659 			}
660 		} else if (child->GetName() == wxT("cell")) {
661 			Cell* cell = new Cell;
662 			cell->PutXML(child);
663 			m_cells.push_back(cell);
664 		} else if (child->GetName() == wxT("pad")) {
665 			m_pad.clear();
666 			m_pad.push_back(child->GetPropVal(wxT("left"), &val) && val.ToLong(&lval) ? lval : 0);
667 			m_pad.push_back(child->GetPropVal(wxT("right"), &val) && val.ToLong(&lval) ? lval : 0);
668 			m_pad.push_back(child->GetPropVal(wxT("top"), &val) && val.ToLong(&lval) ? lval : 0);
669 			m_pad.push_back(child->GetPropVal(wxT("bottom"), &val) && val.ToLong(&lval) ? lval : 0);
670 		} else if (child->GetName() == wxT("crop")) {
671 			m_crop.clear();
672 			m_crop.push_back(child->GetPropVal(wxT("left"), &val) && val.ToLong(&lval) ? lval : 0);
673 			m_crop.push_back(child->GetPropVal(wxT("right"), &val) && val.ToLong(&lval) ? lval : 0);
674 			m_crop.push_back(child->GetPropVal(wxT("top"), &val) && val.ToLong(&lval) ? lval : 0);
675 			m_crop.push_back(child->GetPropVal(wxT("bottom"), &val) && val.ToLong(&lval) ? lval : 0);
676 		} else if (child->GetName() == wxT("textsub")) {
677 			TextSub* textsub = new TextSub;
678 			textsub->PutXML(child);
679 			GetSubtitles().Add(textsub);
680 		} else if (child->GetName() == wxT("menu")) {
681 			m_menu = new Menu(dvd, tsi, pgci);
682 			if (!m_menu->PutXML(child))
683 				return false;
684 		} else if (child->GetName() == wxT("slideshow"))
685 			m_slideshow = new Slideshow(child);
686 		child = child->GetNext();
687 	}
688 	if (node->GetPropVal(wxT("pause"), &val)) {
689 		if (val == wxT("inf"))
690 			m_pause = -1;
691 		else if (val.ToLong(&lval))
692 			m_pause = int(lval);
693 	}
694 	m_startTime = node->GetPropVal(wxT("startTime"), &val) && val.ToDouble(&dval) ? dval : 0;
695 	m_recordingTime = node->GetPropVal(wxT("recordingTime"), &val) && val.ToDouble(&dval) ? dval : -1;
696 	m_fadeIn = node->GetPropVal(wxT("fadeIn"), &val) && val.ToDouble(&dval) ? dval : 0;
697 	m_fadeOut = node->GetPropVal(wxT("fadeOut"), &val) && val.ToDouble(&dval) ? dval : 0;
698 	m_videoFilters = node->GetPropVal(wxT("videoFilters"), wxT(""));
699 
700 	if (node->GetPropVal(wxT("doNotTranscode"), &val) && val == wxT("1"))
701 		SetDoNotTranscode(true);
702 	if (node->GetPropVal(wxT("chapters"), &val))
703 		SetChapters(val, firstVob);
704 
705 	return true;
706 }
707 
GetFileSize(const wxString & filename)708 unsigned int Vob::GetFileSize(const wxString& filename) {
709 	if (filename.StartsWith(wxT("concat:"))) {
710 		unsigned int size = 0;
711 		wxString files = filename.substr(7);
712 		while (files.length() > 0) {
713 			wxString fname = files.BeforeFirst(wxT('|'));
714 			files = files.AfterFirst(wxT('|'));
715 			size += wxFile(fname).Length()/1024;
716 		}
717 		return size;
718 	}
719 	return wxFile(filename).Length()/1024;
720 }
721 
722 /**
723  * Returns size in KB
724  * @return Size of VOB file in KB
725  */
GetSize(DVD * dvd)726 int Vob::GetSize(DVD* dvd) {
727 	long size = GetOutputFileSize(dvd, 0);
728 	for (unsigned int i = 0; i<GetAudioFilenames().Count(); i++) {
729 		size += GetOutputFileSize(dvd, i + 1);
730 	}
731 	for (unsigned int i=0; i<GetSubtitles().Count(); i++)
732 		size += GetFileSize(GetSubtitles()[i]->GetFilename());
733 	return size;
734 }
735 
736 /**
737  * Returns size of output file in KB
738  */
GetOutputFileSize(DVD * dvd,int fileIdx)739 int Vob::GetOutputFileSize(DVD* dvd, int fileIdx) {
740 	if (fileIdx > 0) {
741 		int stIdx = (int)m_streams.size() - m_audioFilenames.size() + fileIdx - 1;
742 		Stream* stream = m_streams[stIdx++];
743 		if (stream->GetAudioFormat() == afCOPY)
744 			return GetFileSize(GetAudioFilenames()[fileIdx - 1]);
745 		return (int) (GetResultDuration()*dvd->GetAudioBitrate()/8);
746 	}
747 	long size = 0;
748 	int stIdx = 0;
749 	if (GetMenu()) {
750 		double duration = GetMenu()->GetSVG()->GetDuration();
751 		if (duration < 1)
752 			duration = s_config.GetMenuFrameCount()/(GetMenu()->GetVideoFormat() == vfPAL ? 25 : 30);
753 		long menuSize = (long) (duration*s_config.GetMenuVideoBitrate()/8);
754 		if (!s_config.GetMenuVideoCBR())
755 			menuSize = menuSize / 3;
756 		if (menuSize < 100)
757 			menuSize = 100;
758 		size += menuSize;
759 	} else if (GetSlideshow()) {
760 		long slideshowSize = GetSlideshow()->GetResultDuration()*s_config.GetSlideshowVideoBitrate()/8;
761 		if (!s_config.GetSlideshowVideoCBR())
762 			slideshowSize = slideshowSize / 3;
763 		size += slideshowSize;
764 	} else {
765 		if (m_doNotTranscode) {
766 			size += GetFileSize(GetFilename());
767 		} else {
768 			if (GetFilename().length()) {
769 				int streamsCount = (int)m_streams.size() - m_audioFilenames.size();
770 				bool copyVideo = false;
771 				long audioSize = 0;
772 				for (; stIdx < streamsCount; stIdx++) {
773 					Stream* stream = m_streams[stIdx];
774 					switch (stream->GetType()) {
775 					case stVIDEO:
776 						if (stream->GetVideoFormat() == vfCOPY)
777 							copyVideo = true;
778 						else
779 							size += (long) (GetResultDuration()*dvd->GetVideoBitrate()/8);
780 						break;
781 					case stAUDIO: {
782 						int srcBitrate = stream->GetSourceBitrate() > 0 ? stream->GetSourceBitrate() : 64000;
783 						audioSize += (long) (GetResultDuration()*srcBitrate/8/1024);
784 						if (stream->GetAudioFormat() == afCOPY)
785 							size += (long) (GetResultDuration()*srcBitrate/8/1024);
786 						else if (stream->GetAudioFormat() != afNONE)
787 							size += (long) (GetResultDuration()*dvd->GetAudioBitrate()/8);
788 						break;
789 					}
790 					case stSUBTITLE:
791 					case stUNKNOWN:
792 						break;
793 					}
794 				}
795 				if (copyVideo) {
796 					size += (GetFileSize(GetFilename()) - audioSize)*GetResultDuration()/GetDuration();
797 				}
798 			}
799 		}
800 	}
801 	return size;
802 }
803 
804 /**
805  * Returns bitrate in KBit/s
806  */
GetBitrate(DVD * dvd,int fileIdx)807 int Vob::GetBitrate(DVD* dvd, int fileIdx) {
808 	if (fileIdx > 0) {
809 		int stIdx = (int)m_streams.size() - m_audioFilenames.size() + fileIdx - 1;
810 		Stream* stream = m_streams[stIdx++];
811 		if (stream->GetAudioFormat() == afCOPY)
812 			return 0;
813 		return dvd->GetAudioBitrate();
814 	}
815 	if (GetMenu()) {
816 		return s_config.GetMenuVideoBitrate();
817 	} else if (GetSlideshow()) {
818 		return s_config.GetSlideshowVideoBitrate();
819 	} else if (m_doNotTranscode || GetVideoStream() == NULL || GetVideoStream()->GetDestinationFormat() == vfCOPY) {
820 		return 0;
821 	}
822 	return dvd->GetVideoBitrate();
823 }
824 
825 
826 /**
827  * Returns size after transcoding in KB
828  * @return Size of VOB file in KB
829  */
GetTranscodedSize(DVD * dvd)830 int Vob::GetTranscodedSize(DVD* dvd) {
831 	if (GetTmpFilename().length())
832 		return GetFileSize(GetTmpFilename());
833 	else if (GetFilename().length())
834 		return GetFileSize(GetFilename());
835 	return 0;
836 }
837 
GetRequiredSize(DVD * dvd,Cache * cache)838 int Vob::GetRequiredSize(DVD* dvd, Cache* cache) {
839 	int size = GetSize(dvd);
840 	if (GetMenu() || GetSlideshow() || GetSubtitles().Count() > 0
841 			 || ((!GetDoNotTranscode() || GetAudioFilenames().Count() > 0)
842 					 && cache->Find(this, dvd).length() == 0))
843 		size = size * 2;
844 	return size;
845 }
846 
CreateEmptyVob(VideoFormat videoFormat,AudioFormat audioFormat)847 Vob* Vob::CreateEmptyVob(VideoFormat videoFormat, AudioFormat audioFormat) {
848 	wxString filename= wxT("empty_pal_mp2.mpg");
849 	if (videoFormat == vfNTSC) {
850 		filename = audioFormat == afMP2 ? wxT("empty_ntsc_mp2.mpg") : wxT("empty_ntsc_ac3.mpg");
851 	} else if (audioFormat != afMP2) {
852 		filename = wxT("empty_pal_ac3.mpg");
853 	}
854 	return new Vob(DATA_FILE(filename));
855 }
856