[Matroska-cvs] [matroska] r1281 - in trunk/DvdMenuXtractor: . dmx

smssms at matroska.org smssms at matroska.org
Sat Mar 10 22:15:13 CET 2007


Author: smssms
Date: 2007-03-11 00:14:50 +0300 (Sun, 11 Mar 2007)
New Revision: 1281

Added:
   trunk/DvdMenuXtractor/dmx/
   trunk/DvdMenuXtractor/dmx/chaptermanager.cpp
   trunk/DvdMenuXtractor/dmx/chaptermanager.h
   trunk/DvdMenuXtractor/dmx/dmx.cpp
   trunk/DvdMenuXtractor/dmx/dmx.h
   trunk/DvdMenuXtractor/dmx/dmx.proj
   trunk/DvdMenuXtractor/dmx/dmx_project.h
   trunk/DvdMenuXtractor/dmx/dmxselectionitem.cpp
   trunk/DvdMenuXtractor/dmx/dmxselectionitem.h
   trunk/DvdMenuXtractor/dmx/utilities.cpp
   trunk/DvdMenuXtractor/dmx/utilities.h
Log:
Separate projects for CLI and GUI modes

Added: trunk/DvdMenuXtractor/dmx/chaptermanager.cpp
===================================================================
--- trunk/DvdMenuXtractor/dmx/chaptermanager.cpp	2007-03-08 09:04:37 UTC (rev 1280)
+++ trunk/DvdMenuXtractor/dmx/chaptermanager.cpp	2007-03-10 21:14:50 UTC (rev 1281)
@@ -0,0 +1,689 @@
+#include "chaptermanager.h"
+#include "utilities.h"
+
+#include <QtXml>
+
+QString ChapterManager::INFO_SUFFIX ("_info.xml");
+QString ChapterManager::CHAPTER_SUFFIX ("_menu.xml");
+
+bool ChapterManager::generateScript(const IFOFile& ifoFile, const QString& filename, uint16_t title, const QString& editionUID) const
+{
+	static unsigned char _PrivateVTS[] = {0x30, 0x80, 0x00, 0x00};
+	static unsigned char _PrivateTT[]  = {0x28, 0x00, 0x00, 0x00};
+	uint64_t start_timeH = uint64_t(-1);
+
+	generateInfoScript(filename, title, editionUID);
+
+	// create document
+	QDomDocument document;
+
+	// specify processing info
+	document.appendChild(document.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\""));
+	document.appendChild(document.implementation().createDocumentType("Chapters", QString::null, "matroskachapters.dtd"));
+
+	// create comments
+	document.appendChild(document.createComment("Created with " + Utilities::APPLICATION_NAME + " " + Utilities::APPLICATION_VERSION));
+
+	// create the root element
+	QDomNode root = document.createElement("Chapters");
+	document.appendChild(root);
+
+	// VTS content
+	QDomElement editionEntryElement = document.createElement("EditionEntry");
+	root.appendChild(editionEntryElement);
+	
+	editionEntryElement.appendChild(CreateDOMElement(document, "EditionUID", editionUID));
+	editionEntryElement.appendChild(CreateDOMElement(document, "EditionFlagOrdered", "1"));
+	editionEntryElement.appendChild(document.createComment("Video Title Set"));
+
+	QDomElement chapterAtomElement = document.createElement("ChapterAtom");
+	editionEntryElement.appendChild(chapterAtomElement);
+
+	QString _myUIDa = Utilities::CreateUID();
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID", _myUIDa));
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterFlagHidden", "1"));
+	
+	_PrivateVTS[2] = title >> 8;
+	_PrivateVTS[3] = title & 0xFF;
+	AddCodecPrivateData(document, chapterAtomElement, _PrivateVTS, 4);
+
+	QDomNode chapterAtomParentElement = chapterAtomElement;
+
+	if (ifoFile.VtsTitles(title) && ifoFile.VtsPGCs(title))
+	{
+		for (int i = 0; i < ifoFile.VtsTitles(title)->nr_of_srpts; i++)
+		{
+			uint64_t start_time = uint64_t(-1), found_time;
+			
+			chapterAtomParentElement.appendChild(document.createComment(QString("Title TTU_%1").arg(i + 1)));
+			QDomElement chapterAtomElement = document.createElement("ChapterAtom");
+			chapterAtomParentElement.appendChild(chapterAtomElement);
+			
+			QString _myUIDa = Utilities::CreateUID();
+			chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID", _myUIDa));
+			chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterFlagHidden", "1"));
+
+			// get the Title -> VTS+TTN map
+			const tt_srpt_t *p_map = ifoFile.TitleMap();
+
+			if ( p_map != NULL )
+			{
+				for (int j=0; j<p_map->nr_of_srpts; j++)
+				{
+					if ( ( p_map->title[j].title_set_nr == title ) && ( p_map->title[j].vts_ttn == i+1 ) )
+					{
+						_PrivateTT[1] = (j+1) >> 8;   // Title#
+						_PrivateTT[2] = (j+1) & 0xFF;
+						_PrivateTT[3] = i+1;          // VTS_TTN#
+						AddCodecPrivateData(document, chapterAtomElement, _PrivateTT, 4);
+						break;
+					}
+				}
+			}
+
+			// add the PGC that match this PTT
+			for (int k = 0; k < ifoFile.VtsPGCs(title)->nr_of_pgci_srp; k++)
+			{
+				if ((ifoFile.VtsPGCs(title)->pgci_srp[k].entry_id & 0x7F) == i+1)
+				{
+					found_time = AddPGC(document, chapterAtomElement, 
+						ifoFile.VtsPGCs(title)->pgci_srp[k].pgc, k, 0, ifoFile.CellsList(title, false), &ifoFile.VtsTitles(title)->title[i], i+1);
+					
+					if (start_time > found_time)
+						start_time = found_time;
+				}
+			}
+
+			chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterTimeStart", Utilities::FormatTime(start_time)));
+
+			if (start_timeH > start_time)
+				start_timeH = start_time;
+		}
+	}
+
+	if (start_timeH == uint64_t(-1))
+		start_timeH = 0;
+
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterTimeStart", Utilities::FormatTime(start_timeH)));
+	
+	QString output = document.toString(INDENT_COUNT);
+
+	// TODO: not a good approach
+	output.replace("-->", "-->\n");
+
+	// chapter file
+	QFile chapterFile (filename + CHAPTER_SUFFIX);
+	if (!chapterFile.open(QIODevice::WriteOnly | QIODevice::Text))
+		throw;
+
+	chapterFile.write(output.toUtf8());
+	chapterFile.close();
+
+	return true;
+}
+
+bool ChapterManager::generateMenuScript(const IFOFile& ifoFile, const QString& filename, uint16_t title, const QString& editionUID) const
+{
+	static unsigned char _PrivateFP[]  = {0x30, 0x00, 0x00, 0x00};
+	static unsigned char _PrivateVM[]  = {0x30, 0xC0, 0x00, 0x00};
+	static unsigned char _PrivateVTS[] = {0x30, 0x40, 0x00, 0x00};
+
+	// generate info script
+	generateInfoScript(filename, title, editionUID);
+
+	// create document
+	QDomDocument document;
+
+	// specify processing info
+	document.appendChild(document.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\""));
+	document.appendChild(document.implementation().createDocumentType("Chapters", QString::null, "matroskachapters.dtd"));
+
+	// create comments
+	document.appendChild(document.createComment("Created with " + Utilities::APPLICATION_NAME + " " + Utilities::APPLICATION_VERSION));
+
+	// create the root element
+	QDomNode parentElement = document.createElement("Chapters");
+	document.appendChild(parentElement);
+
+	// create Edition Entry Element and add to root
+	QDomElement currentElement = document.createElement("EditionEntry");
+	parentElement.appendChild(currentElement);
+	
+	// use Edition Entry Element as parent
+	parentElement = currentElement;
+
+	// add EditionUID and EDitionFlagOrdered elements
+	parentElement.appendChild(CreateDOMElement(document, "EditionUID", editionUID));
+	parentElement.appendChild(CreateDOMElement(document, "EditionFlagOrdered", "1"));
+
+	// Write the first play PGC
+	if (title == 0 && ifoFile.FirstPlayPGC())
+	{
+		// add comment to Edition Entry Element
+		parentElement.appendChild(document.createComment("First Play PGC"));
+
+		// add Chapter Atom
+		QDomElement chapterAtomElement = document.createElement("ChapterAtom");
+		parentElement.appendChild(chapterAtomElement);
+
+		// create and set UID
+		QString _myUID = Utilities::CreateUID();
+		chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID", _myUID));
+
+		// the First Play PGC has no cell and no time
+		AddChapterTime(document, chapterAtomElement, 0, 0);
+
+		// display the UID for the lazy rippers
+		chapterAtomElement.appendChild(document.createComment("Enter your text here"));
+
+		// add Chapter Display Element
+		currentElement = document.createElement("ChapterDisplay");
+		currentElement.appendChild(CreateDOMElement(document, "ChapterString", "First Play PGC " + _myUID));
+		chapterAtomElement.appendChild(currentElement);
+
+		// add Chapter Process Element
+		currentElement = document.createElement("ChapterProcess");
+		AddCodecPrivateData(document, currentElement, _PrivateFP, 4, false);
+		chapterAtomElement.appendChild(currentElement);
+
+		// PGC commands
+		AddPGCCommands(document, currentElement, ifoFile.FirstPlayPGC()->command_tbl);
+	}
+
+	// Write the Video Title Set
+	if (ifoFile.LanguageUnits(title) && ifoFile.LanguageUnits(title)->nr_of_lus)
+	{
+		// VTS Menu
+		if ( title == 0 )
+			parentElement.appendChild(document.createComment("Video Manager"));
+		else
+			parentElement.appendChild(document.createComment("Video Title Set"));
+
+		// add another Chapter Atom element
+		currentElement = document.createElement("ChapterAtom");
+		parentElement.appendChild(currentElement);
+
+		QString _myUIDa = Utilities::CreateUID();
+		currentElement.appendChild(CreateDOMElement(document, "ChapterUID", _myUIDa));
+		currentElement.appendChild(CreateDOMElement(document, "ChapterFlagHidden", "1"));
+
+		if ( title != 0 )
+		{
+			_PrivateVTS[2] = title >> 8;
+			_PrivateVTS[3] = title & 0xFF;
+			AddCodecPrivateData(document, currentElement, _PrivateVTS, 4);
+		}
+		else
+		{
+			_PrivateVM[2] = 0;
+			_PrivateVM[3] = 0;
+			AddCodecPrivateData(document, currentElement, _PrivateVM, 4);
+		}
+
+		uint64_t start_time = HandleLanguageUnit(document, currentElement, ifoFile, title);
+
+		currentElement.appendChild(CreateDOMElement(document, "ChapterTimeStart", Utilities::FormatTime(start_time)));
+	}
+
+	// get document string representation
+	QString output = document.toString(INDENT_COUNT);
+
+	// TODO: not a good approach
+	output.replace("-->", "-->\n");
+
+	// chapter file
+	QFile chapterFile(filename + CHAPTER_SUFFIX);
+	if (!chapterFile.open(QIODevice::WriteOnly | QIODevice::Text))
+		throw;
+	
+	// write to the chpater file
+	chapterFile.write(output.toUtf8());
+	chapterFile.close();
+
+	return true;
+}
+
+void ChapterManager::generateInfoScript(const QString &filename, uint16_t title, const QString &editionUID) const
+{
+	// create document
+	QDomDocument document;
+
+	// specify xml version, encoding and DocType
+	document.appendChild(document.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\""));
+	document.appendChild(document.implementation().createDocumentType("Chapters", QString::null, "matroskainfos.dtd"));
+
+	// add root element
+	QDomNode parentElement = document.createElement("Info");
+	document.appendChild(parentElement);
+
+	// add Segment Family Element
+	QDomElement currentElement = CreateDOMElement(document, "SegmentFamily", Utilities::EncodeHex(familyUID, 16));
+	currentElement.setAttribute("format", "hex");
+	parentElement.appendChild(currentElement);
+
+	// add Chapter Translate Element
+	currentElement = document.createElement("ChapterTranslate");
+	currentElement.appendChild(CreateDOMElement(document, "ChapterTranslateEditionUID", editionUID));
+	currentElement.appendChild(CreateDOMElement(document, "ChapterTranslateCodec", "1"));
+	
+	QDomElement chapterTranslateIDElement = CreateDOMElement(document, "ChapterTranslateID", QString("%1 00").arg(title, 2, 16, QChar('0')));
+	chapterTranslateIDElement.setAttribute("format", "hex");
+	currentElement.appendChild(chapterTranslateIDElement);
+
+	parentElement.appendChild(currentElement);
+
+	// get document string representation
+	QString output = document.toString(INDENT_COUNT);
+	
+	// segment file
+	QFile segementFile(filename + INFO_SUFFIX);
+	if (!segementFile.open(QIODevice::WriteOnly | QIODevice::Text))
+		throw;
+	
+	segementFile.write(output.toUtf8());
+	segementFile.close();
+}
+
+QDomElement ChapterManager::CreateDOMElement(QDomDocument &document, const QString &elementName, const QString &content)
+{
+	QDomElement element = document.createElement(elementName);
+	element.appendChild(document.createTextNode(content));
+
+	return element;
+}
+
+void ChapterManager::AddChapterTime(QDomDocument& document, QDomNode& parent, uint64_t start_time, uint64_t end_time)
+{
+	parent.appendChild(CreateDOMElement(document, "ChapterTimeStart", Utilities::FormatTime(start_time)));
+	parent.appendChild(CreateDOMElement(document, "ChapterTimeEnd", Utilities::FormatTime(end_time)));
+}
+
+void ChapterManager::AddCodecPrivateData(QDomDocument& document, QDomNode& parent, const unsigned char *buffer, unsigned int size, bool createChapterProcessElement /*= true*/)
+{
+	QDomNode node = parent;
+
+	if (createChapterProcessElement)
+	{
+		QDomElement chapterProcessElement = document.createElement("ChapterProcess");
+		parent.appendChild(chapterProcessElement);
+		node = chapterProcessElement;
+	}
+
+	node.appendChild(CreateDOMElement(document, "ChapterProcessCodecID", "1"));
+
+	QDomElement chapterProcessPrivateElement = CreateDOMElement(document, "ChapterProcessPrivate", Utilities::EncodeHex(buffer, size));
+	chapterProcessPrivateElement.setAttribute("format", "hex");
+	node.appendChild(chapterProcessPrivateElement);
+}
+
+void ChapterManager::AddPGCCommands(QDomDocument& document, QDomNode& parent, const pgc_command_tbl_t * command_tbl)
+{
+	if (command_tbl)
+	{
+		unsigned char *_buf = (unsigned char*) malloc(1 + command_tbl->nr_of_pre * 8);
+
+		// Pre-commands
+		if (command_tbl->nr_of_pre)
+		{
+			parent.appendChild(document.createComment("Pre commands"));
+
+			QDomElement chapterCommandElement = document.createElement("ChapterProcessCommand");
+			parent.appendChild(chapterCommandElement);
+
+			QDomElement chapterProcessTimeElement = document.createElement("ChapterProcessTime");
+			chapterProcessTimeElement.appendChild(document.createTextNode("1"));
+			chapterCommandElement.appendChild(chapterProcessTimeElement);
+
+			_buf[0] = command_tbl->nr_of_pre;
+			memcpy(&_buf[1], command_tbl->pre_cmds, command_tbl->nr_of_pre * 8);
+
+			QDomElement chapterProcessDataElement = CreateDOMElement(document, "ChapterProcessData", Utilities::EncodeHex(_buf, 1 + command_tbl->nr_of_pre * 8));
+			chapterProcessDataElement.setAttribute("format", "hex");
+			chapterCommandElement.appendChild(chapterProcessDataElement);
+		}
+
+		// Cell commands
+		if (command_tbl->nr_of_cell)
+		{
+			parent.appendChild(document.createComment("Cell commands"));
+
+			QDomElement chapterCommandElement = document.createElement("ChapterProcessCommand");
+			parent.appendChild(chapterCommandElement);
+
+			QDomElement chapterProcessTimeElement = document.createElement("ChapterProcessTime");
+			chapterProcessTimeElement.appendChild(document.createTextNode("0"));
+			chapterCommandElement.appendChild(chapterProcessTimeElement);
+
+			_buf = (unsigned char*) realloc(_buf, 1 + command_tbl->nr_of_cell * 8);
+			_buf[0] = command_tbl->nr_of_cell;
+			memcpy(&_buf[1], command_tbl->cell_cmds, command_tbl->nr_of_cell * 8);
+
+			QDomElement chapterProcessDataElement = CreateDOMElement(document, "ChapterProcessData", Utilities::EncodeHex(_buf, 1 + command_tbl->nr_of_cell * 8));
+			chapterProcessDataElement.setAttribute("format", "hex");
+			chapterCommandElement.appendChild(chapterProcessDataElement);
+		}
+
+		// Post commands
+		if (command_tbl->nr_of_post)
+		{
+			parent.appendChild(document.createComment("Post commands"));
+
+			QDomElement chapterCommandElement = document.createElement("ChapterProcessCommand");
+			parent.appendChild(chapterCommandElement);
+
+			QDomElement chapterProcessTimeElement = document.createElement("ChapterProcessTime");
+			chapterProcessTimeElement.appendChild(document.createTextNode("2"));
+			chapterCommandElement.appendChild(chapterProcessTimeElement);
+
+			_buf = (unsigned char*) realloc(_buf, 1+command_tbl->nr_of_post * 8);
+			_buf[0] = command_tbl->nr_of_post;
+			memcpy(&_buf[1], command_tbl->post_cmds, command_tbl->nr_of_post * 8);
+
+			QDomElement chapterProcessDataElement = CreateDOMElement(document, "ChapterProcessData", Utilities::EncodeHex(_buf, 1 + command_tbl->nr_of_post * 8));
+			chapterProcessDataElement.setAttribute("format", "hex");
+			chapterCommandElement.appendChild(chapterProcessDataElement);
+		}
+
+		free(_buf);
+	}
+}
+
+uint64_t ChapterManager::HandleLanguageUnit(QDomDocument& document, QDomNode& parent, const IFOFile& _ifo, int title)
+{
+	static unsigned char _PrivateLU[]  = {0x2A, 0x00, 0x00, 0x00};
+	uint64_t start_time = uint64_t(-1), found_time;
+
+	QDomElement chapterAtomElement;
+	QDomElement chapterDisplayElement;
+	QDomElement chapterStringElement;
+	QDomElement chapterTimeStartElement;
+
+	for (int i = 0 ;i < _ifo.LanguageUnits(title)->nr_of_lus; i++)
+	{
+		parent.appendChild(document.createComment("Language Units"));
+
+		chapterAtomElement = document.createElement("ChapterAtom");
+		parent.appendChild(chapterAtomElement);
+
+		// the Language Unit for a given language
+		const pgci_lu_t &_LU = _ifo.LanguageUnits(title)->lu[i];
+		_PrivateLU[1] = _LU.lang_code >> 8;
+		_PrivateLU[2] = _LU.lang_code & 0xFF;
+		_PrivateLU[3] = _LU.lang_extension;
+
+		chapterAtomElement.appendChild(document.createComment(QString("Language: %1%2").arg(QChar(_LU.lang_code >> 8)).arg(QChar(_LU.lang_code & 0xFF))));
+		chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID", Utilities::CreateUID()));
+
+		AddCodecPrivateData(document, chapterAtomElement, _PrivateLU, 4);
+
+		// display the UID for the lazy rippers
+		chapterAtomElement.appendChild(document.createComment("Enter your text here"));
+
+		chapterDisplayElement = document.createElement("ChapterDisplay");
+		chapterAtomElement.appendChild(chapterDisplayElement);
+
+		chapterStringElement = document.createElement("ChapterString");
+		chapterStringElement.appendChild(document.createTextNode(QString("Language Unit for %1%2").arg(QChar(_LU.lang_code >> 8)).arg(QChar(_LU.lang_code & 0xFF))));
+		chapterDisplayElement.appendChild(chapterStringElement);
+
+		if (_LU.pgcit)
+		{
+			for (int j = 0; j < _LU.pgcit->nr_of_pgci_srp; j++)
+			{
+				const pgci_srp_t &_PGC_SRP = _LU.pgcit->pgci_srp[j];
+
+				found_time = AddPGC(document, chapterAtomElement, _PGC_SRP.pgc, j, _PGC_SRP.entry_id, _ifo.CellsList(title, true), NULL, 0);
+
+				if (start_time > found_time)
+					start_time = found_time;
+			}
+		}
+
+		chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterTimeStart", Utilities::FormatTime(start_time)));
+	}
+
+	return start_time;
+}
+
+
+uint64_t ChapterManager::AddPGC(QDomDocument& document, QDomNode& parent, const pgc_t * pgc, uint16_t pgc_num, unsigned char pgc_type, const CellsListType & cell_list, const ttu_t * ptts, int title)
+{
+	static unsigned char _PrivatePGC[] = {0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+	uint64_t start_time = uint64_t(-1), found_time;
+
+	parent.appendChild(document.createComment(QString("PGC PGC_%1 type %2").arg(pgc_num+1).arg(GetPGCType(pgc_type))));
+
+	QDomElement chapterAtomElement = document.createElement("ChapterAtom");
+	parent.appendChild(chapterAtomElement);
+
+	QString _myUID = Utilities::CreateUID();
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID", _myUID));
+
+	// display the UID for the lazy rippers
+	if (ptts == NULL)
+	{
+		chapterAtomElement.appendChild(document.createComment("Enter your text here"));
+
+		QDomElement chapterDisplayElement = document.createElement("ChapterDisplay");
+		chapterDisplayElement.appendChild(CreateDOMElement(document, "ChapterString", "PGC type " + GetPGCType(pgc_type)));
+		chapterAtomElement.appendChild(chapterDisplayElement);
+	}
+	else
+		chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterFlagHidden", "1"));
+
+	// PGC commands
+	QDomElement chapterProcessElement = document.createElement("ChapterProcess");
+	chapterAtomElement.appendChild(chapterProcessElement);
+
+	_PrivatePGC[1] = (pgc_num+1) >> 8;
+	_PrivatePGC[2] = (pgc_num+1) & 0xFF;
+	_PrivatePGC[3] = pgc_type;
+	_PrivatePGC[4] = ((uint8_t*) &pgc->prohibited_ops)[0];
+	_PrivatePGC[5] = ((uint8_t*) &pgc->prohibited_ops)[1];
+	_PrivatePGC[6] = ((uint8_t*) &pgc->prohibited_ops)[2];
+	_PrivatePGC[7] = ((uint8_t*) &pgc->prohibited_ops)[3];
+
+	AddCodecPrivateData(document, chapterProcessElement, _PrivatePGC, 8, false);
+	AddPGCCommands(document, chapterProcessElement, pgc->command_tbl);
+
+	// Handle the Programs/Cells in the PGC
+	for (int i=0; i< pgc->nr_of_programs; i++)
+	{
+		found_time = AddProgram(document, chapterAtomElement, pgc, _myUID, i, cell_list, pgc_num, ptts, title);
+
+		if (start_time > found_time)
+			start_time = found_time;
+	}
+
+	if (start_time == uint64_t(-1))
+		start_time = 0;
+
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterTimeStart", Utilities::FormatTime(start_time)));
+
+	return start_time;
+}
+
+
+QString ChapterManager::GetPGCType(unsigned char entry_id)
+{
+	QString result ("0x%1 = ");
+	result = result.arg(entry_id, 2, 16, QChar('0'));
+
+	if (entry_id & 0x80)
+		result += "Entry + ";
+
+	switch (entry_id & ~0x80)
+	{
+	case 2:
+		result += "Title-Menu";
+		break;
+	case 3:
+		result += "Root Menu";
+		break;
+	case 4:
+		result += "Subpicture Menu";
+		break;
+	case 5:
+		result += "Audio Menu";
+		break;
+	case 6:
+		result += "Angle Menu";
+		break;
+	case 7:
+		result += "Chapter Menu";
+		break;
+	default:
+		result += "Basic PGC";
+		break;
+	}
+
+	return result;
+}
+
+
+uint64_t ChapterManager::AddProgram(QDomDocument& document, QDomNode& parent, const pgc_t *pgc, const QString& PgcUID, int program_number, const CellsListType& cell_list, int16_t pgc_num, const ttu_t *ptts, int title)
+{
+	static unsigned char _PrivatePTT[] = {0x10, 0x00};
+	static unsigned char _PrivatePG[] = {0x18, 0x00, 0x00};
+	static unsigned char _PrivateCN[] = {0x08, 0x00, 0x00, 0x00, 0x00};
+
+	uint64_t start_time = uint64_t(-1), end_time = 0;
+
+	// for this program
+	int entry_cell_num = pgc->program_map[program_number];
+	int program_last_cell_num = pgc->nr_of_cells;
+	int next_program_entry_cell_num;
+	if (program_number+1 == pgc->nr_of_programs)
+		next_program_entry_cell_num = program_last_cell_num+1;
+	else
+		next_program_entry_cell_num = pgc->program_map[program_number+1];
+
+	parent.appendChild(document.createComment(QString("Program PGN# %1 in this PGC").arg(program_number + 1)));
+
+	QDomElement chapterAtomElement = document.createElement("ChapterAtom");
+	parent.appendChild(chapterAtomElement);
+
+	QString _myUID = Utilities::CreateUID();
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID", _myUID));
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterFlagHidden", "1"));
+
+	_PrivatePG[1] = (program_number+1) >> 8;
+	_PrivatePG[2] = (program_number+1) & 0xFF;
+	AddCodecPrivateData(document, chapterAtomElement, _PrivatePG, 3);
+
+	if (ptts != NULL)
+	{
+		// find all PTT corresponding to this PGC/PG number
+		for (uint16_t pppt_nr = 0; pppt_nr < ptts->nr_of_ptts; pppt_nr++)
+		{
+			// use the start timecode of the first cell
+			const cell_position_t & position = pgc->cell_position[entry_cell_num-1];
+			const CellListElem * cell_elt = cell_list.at(position);
+			// TODO: make this test optional (create chapters even without the content)
+			if (cell_elt == NULL || !cell_elt->found)
+				continue;
+			
+			start_time = cell_elt->start_time;
+
+			QDomElement chapterAtomParent = chapterAtomElement;
+
+			if (ptts->ptt[pppt_nr].pgcn == pgc_num+1 && ptts->ptt[pppt_nr].pgn == program_number+1)
+			{
+				chapterAtomElement.appendChild(document.createComment(QString("Chapter PTT#%1 [%2.%3]").arg(pppt_nr+1).arg(pgc_num+1, 2, 10, QChar('0')).arg(program_number+1, 2, 10, QChar('0'))));
+				
+				chapterAtomElement = document.createElement("ChapterAtom");
+				chapterAtomParent.appendChild(chapterAtomElement);
+
+				chapterAtomElement.appendChild(document.createComment("Enter your text here"));
+
+				QDomElement chapterDisplayElement = document.createElement("ChapterDisplay");
+				chapterDisplayElement.appendChild(CreateDOMElement(document, "ChapterString", QString("Your Name Here For Chapter #%1.%2").arg(title).arg(pppt_nr + 1)));
+				chapterAtomElement.appendChild(chapterDisplayElement);
+
+				QString _myUID = Utilities::CreateUID();
+				chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID", _myUID));
+
+				_PrivatePTT[1] = pppt_nr+1;
+				AddCodecPrivateData(document, chapterAtomElement, _PrivatePTT, 2);
+
+				chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterTimeStart", Utilities::FormatTime(start_time)));
+			}
+
+			chapterAtomElement = chapterAtomParent;
+		}
+	}
+
+	QDomElement parentChapterAtom = chapterAtomElement;
+
+	for (int cell_num = entry_cell_num; cell_num<next_program_entry_cell_num; cell_num++)
+	{
+		// Output cells
+		const cell_position_t & position = pgc->cell_position[cell_num-1];
+		const cell_playback_t & cell = pgc->cell_playback[cell_num-1];
+
+		if (cell.block_mode != 0 && cell.block_mode != 3)
+			continue;
+
+		const CellListElem * cell_elt = cell_list.at(position);
+		// TODO: make this test optional (create chapters even without the content)
+		if (cell_elt == NULL || !cell_elt->found)
+			continue;
+
+		QString prefix = QString("Program %1 Cell CN#%2 ").arg( (cell_num == entry_cell_num) ? "Entry" : "", QString::number(cell_num));
+		QString middle = QString("[%1.%2]").arg(cell_elt->vobid, 2, 10, QChar('0')).arg(cell_elt->cellid);
+		QString suffix = QString(" (%1 angles)").arg(cell_num - entry_cell_num + 1);
+
+		if (cell.block_mode) // multi-angle
+			parentChapterAtom.appendChild(document.createComment(prefix + middle + suffix));
+		else
+			parentChapterAtom.appendChild(document.createComment(prefix + middle));
+
+		QDomElement chapterAtomElement = document.createElement("ChapterAtom");
+		parentChapterAtom.appendChild(chapterAtomElement);
+
+		QString _myUID = Utilities::CreateUID();
+		chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID", _myUID));
+		chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterFlagHidden", "1"));
+
+		_PrivateCN[1] = (position.vob_id_nr) >> 8;
+		_PrivateCN[2] = (position.vob_id_nr) & 0xFF;
+		_PrivateCN[3] = position.cell_nr;
+		_PrivateCN[4] = cell_num - entry_cell_num + 1; // Number of angles
+		AddCodecPrivateData(document, chapterAtomElement, _PrivateCN, 5);
+
+		if (cell.still_time == 0xFF) {
+			chapterAtomElement.appendChild(document.createComment("Infinite loop Still Cell"));
+
+			// output an additional tag to specify this is cell should loop infinitely
+			// post-process command, jump to timecode "st_time"
+			QDomElement chapterProcessElement = document.createElement("ChapterProcess");
+			chapterProcessElement.appendChild(CreateDOMElement(document, "ChapterProcessCodecID", "0"));
+			chapterProcessElement.appendChild(document.createComment("Post command: replay this cell"));
+			chapterAtomElement.appendChild(chapterProcessElement);
+
+			QDomElement chapterProcessCommand = document.createElement("ChapterProcessCommand");
+			chapterProcessCommand.appendChild(CreateDOMElement(document, "ChapterProcessTime", "2"));
+			chapterProcessElement.appendChild(chapterProcessCommand);
+
+			QDomElement chapterProcessDataElement = CreateDOMElement(document, "ChapterProcessData", "GotoAndPlay(" + PgcUID + ");" );
+			chapterProcessDataElement.setAttribute("format", "ascii");
+			chapterProcessCommand.appendChild(chapterProcessDataElement);
+		} else if (cell.still_time != 0)
+			chapterAtomElement.appendChild(document.createComment(QString("Still cell (%1s)").arg(cell.still_time)));
+
+		AddChapterTime(document, chapterAtomElement, cell_elt->start_time, cell_elt->start_time + cell_elt->duration);
+
+		if (end_time < cell_elt->start_time + cell_elt->duration)
+			end_time = cell_elt->start_time + cell_elt->duration;
+
+		if (start_time > cell_elt->start_time)
+			start_time = cell_elt->start_time;
+	}
+
+	if (start_time == uint64_t(-1))
+		start_time = 0; // unknown
+
+	parentChapterAtom.appendChild(CreateDOMElement(document, "ChapterTimeStart", Utilities::FormatTime(start_time)));
+
+	return start_time;
+}

Added: trunk/DvdMenuXtractor/dmx/chaptermanager.h
===================================================================
--- trunk/DvdMenuXtractor/dmx/chaptermanager.h	2007-03-08 09:04:37 UTC (rev 1280)
+++ trunk/DvdMenuXtractor/dmx/chaptermanager.h	2007-03-10 21:14:50 UTC (rev 1281)
@@ -0,0 +1,40 @@
+#ifndef CHAPTER_MANAGER_H
+#define CHAPTER_MANAGER_H
+
+#include <QString>
+#include "vobparser/IFOFile.h"
+
+class QDomNode;
+class QDomElement;
+class QDomDocument;
+
+class ChapterManager
+{
+public:
+	ChapterManager(unsigned xmlIdentCount)
+		: INDENT_COUNT(xmlIdentCount)
+	{
+	}
+	bool generateScript(const IFOFile& ifoFile, const QString& filename, uint16_t title, const QString& editionUID) const;
+	bool generateMenuScript(const IFOFile& ifoFile, const QString& filename, uint16_t title, const QString& editionUID) const;
+
+	static QString INFO_SUFFIX;
+	static QString CHAPTER_SUFFIX;
+
+private:
+	void generateInfoScript(const QString& filename, uint16_t title, const QString& editionUID) const;
+	
+	static QDomElement CreateDOMElement(QDomDocument& document, const QString& elementName, const QString& content);
+	static void AddChapterTime(QDomDocument& document, QDomNode& parent, uint64_t start_time, uint64_t end_time);
+	static void AddCodecPrivateData(QDomDocument& document, QDomNode& parent, const unsigned char *buffer, unsigned int size, bool createChapterProcessElement = true);
+	static void AddPGCCommands(QDomDocument& document, QDomNode& parent, const pgc_command_tbl_t * command_tbl);
+	static uint64_t AddProgram(QDomDocument& document, QDomNode& parent, const pgc_t *pgc, const QString& PgcUID, int program_number, const CellsListType& cell_list, int16_t pgc_num, const ttu_t *ptts, int title);
+	static uint64_t AddPGC(QDomDocument& document, QDomNode& parent, const pgc_t * pgc, uint16_t pgc_num, unsigned char pgc_type, const CellsListType & cell_list, const ttu_t * ptts, int title);
+	static QString GetPGCType(unsigned char entry_id);
+	static uint64_t HandleLanguageUnit(QDomDocument& document, QDomNode& parent, const IFOFile& _ifo, int title);
+
+	uint8_t familyUID[16];
+	unsigned INDENT_COUNT;
+};
+
+#endif //CHAPTER_MANAGER_H

Added: trunk/DvdMenuXtractor/dmx/dmx.cpp
===================================================================
--- trunk/DvdMenuXtractor/dmx/dmx.cpp	2007-03-08 09:04:37 UTC (rev 1280)
+++ trunk/DvdMenuXtractor/dmx/dmx.cpp	2007-03-10 21:14:50 UTC (rev 1281)
@@ -0,0 +1,491 @@
+#include "dmx.h"
+#include "utilities.h"
+#include "chaptermanager.h"
+
+#include <QDir>
+#include <QTime>
+#include <QMutexLocker>
+
+DMX::DMX(bool consoleMode)
+	: ifoFile_(0), consoleMode_(consoleMode), needsAbort_(false)
+{
+}
+
+DMX::~DMX()
+{
+	abort();
+
+	QMutex mutex;
+	mutex.lock();
+	delete ifoFile_;
+	ifoFile_ = 0;
+	mutex.unlock();
+}
+
+void DMX::abort()
+{
+	QMutex mutex;
+	mutex.lock();
+	needsAbort_ = true;
+	mutex.unlock();
+
+	wait();
+}
+
+void DMX::processTitle(int16_t title, int index)
+{
+	const QString editionUID = Utilities::CreateUID();
+
+	unsigned stepIndex = 0;
+	bool menu = ((index < 0) && !title) || ((index >= 0) && selection_[index].isMenu());
+	VobParser *aVobParser = 0;
+	QString str;
+	QString text = "Step %1 of 2: %3...";
+
+	do
+	{
+		printf("Treating Title %d %s VOB file(s)\n", title, menu ? "Menu" : "");
+
+		stepIndex = 1;
+
+		str = text.arg(stepIndex++).arg("Building VOB map");
+		
+		if (consoleMode_)
+			printf(qPrintable(str + '\n'));
+		else
+			emit stepChanged(str);
+
+		aVobParser = buildVobParser(title, menu);
+
+		str = text.arg(stepIndex++).arg("Splitting and demuxing");
+		
+		if (consoleMode_)
+			printf(qPrintable(str + '\n'));
+		else
+			emit stepChanged(str);
+		
+		demux(aVobParser, index, editionUID, title, menu);
+
+		delete aVobParser;
+		
+		menu = !menu && ((index < 0) || selection_[index].isMenu());
+	} while (menu && !needsAbort_);
+}
+
+bool DMX::setExtractionParameters(const QString& sourcePath, const QString& destinationPath, const QString& toolsPath, const SelectionType& selectedItems)
+{
+	QMutex mutex;
+	QMutexLocker locker (&mutex);
+
+	// set selection
+	selection_ = selectedItems;
+
+	// check if require directories exist
+	if (!QFile::exists(sourcePath))
+	{
+		fprintf(stderr, "Input folder '%s' doesn't exist\n", qPrintable(sourcePath));
+		return false;
+	}
+
+	// set source path
+	sourcePath_ = sourcePath;
+
+	if (!QFile::exists(toolsPath))
+	{
+		fprintf(stderr, "Tools folder '%s' doesn't exist\n", qPrintable(toolsPath));
+		return false;
+	}
+
+	// set "mkvmerge.exe" path
+	toolsPath_ = toolsPath;
+
+	// try to create output
+	if (!QFile::exists(destinationPath))
+	{
+		if (!QDir().mkpath(destinationPath))
+		{
+			fprintf(stderr,"Cannot create output folder: '%s'\n", qPrintable(destinationPath));
+			return false;
+		}
+		else
+			printf("Successfully created output folder: '%s'\n", qPrintable(destinationPath));
+	}
+
+	// set output path
+	destinationPath_ = destinationPath;
+
+	return true;
+}
+
+void DMX::run()
+{
+	// try to open input file
+	if (!sourcePath_.size() || !loadIFOFile(sourcePath_))
+		return;
+
+	QMutex mutex;
+	mutex.lock();
+	needsAbort_ = false;
+	mutex.unlock();
+
+	if (selection_.size()) // if selection is available
+	{
+		for (size_t index = 0; index < selection_.size(); ++index)
+		{
+			int16_t title = selection_[index].title();
+			
+			if (title >= 0)
+				processTitle(title, index);
+		}
+	} 
+	else // if no selection is available, process all titles
+	{
+		for (int16_t title = 0; title <= ifoFile_->NumberOfTitles(); ++title)
+			processTitle(title, -1);
+	}
+}
+
+IFOFile* DMX::OpenIFOFile(const QString& path)
+{
+	IFOFile *file = 0;
+	QDir info (path);
+	
+	try
+	{
+		file = new IFOFile(info.absolutePath());
+	}
+	catch(IFOException&)
+	{
+		// TODO : More verbose
+		fprintf(stderr, "Couldn't use input directory as DVD source\n");
+		return 0;
+	}
+
+	return file;
+}
+
+bool DMX::loadIFOFile(const QString& path)
+{
+	QMutex mutex;
+	QMutexLocker locker (&mutex);
+	
+	// reset last file
+	delete ifoFile_;
+	ifoFile_ = OpenIFOFile(path);
+
+	if (ifoFile_ == 0)
+		return false;
+
+	return true;
+}
+
+VobParser* DMX::buildVobParser(int16_t title, bool menu)
+{
+	VobParser* parser = 0;
+	if (consoleMode_)
+		printf("0%%");
+	else
+		emit progressChanged(0);
+	
+	try 
+	{
+		parser = new VobParser(qPrintable(sourcePath_), title, menu);
+	} catch (...)
+	{
+		if (consoleMode_)
+			printf("\r\r");
+		
+		printf("No VOB file(s) found in %s for Title %d\n", qPrintable(sourcePath_), title);
+	}
+
+	if (consoleMode_)
+		printf("\r\r100%%\n");
+	else
+		emit progressChanged(100);
+	
+	return parser;
+}
+
+void DMX::demuxAudioTrack(int16_t title, bool isMenu, const AudioTrackList& _audioTracks, size_t _stream, CompositeDemuxWriter& demuxer, const QString& filename)
+{
+	if (_stream < 0 || _stream >= _audioTracks.size())
+		return;
+
+	static const QString muxArgumentsFormat(" --track-name 0:\"aud-%1\" --language 0:%2 --timecodes 0:\"%3_%4.tmc\" \"%3.%4\"");
+
+	const audio_attr_t *_attr = _audioTracks[_stream];
+	// get the possible ID for this track for this Cell/PGC
+	uint8_t _ID = ifoFile_->GetAudioId(_stream, title, isMenu);
+
+	Writer *_muxer = 0;
+	
+	// if (_attr->lang_code == 0)
+	QString lang ("und");
+	QString langSuffix (QString("_%1_un").arg(_ID));
+	
+	if (_attr->lang_code != 0)
+	{
+		lang = QString("%1%2").arg(QString(_attr->lang_code >> 8), QString(_attr->lang_code & 0xFF));
+		langSuffix = QString("_%1_%2%3").arg(QString::number(_ID), QString(_attr->lang_code >> 8), QString(_attr->lang_code & 0xFF));
+	}
+
+	QString muxArguments;
+	QString fullPrefix (destinationPath_ + QDir::separator() + filename + langSuffix);
+
+	switch (_audioTracks[_stream]->audio_format)
+	{
+	case 0:
+		muxArguments = muxArgumentsFormat.arg(filename + langSuffix, lang, fullPrefix, "ac3");		
+		_muxer = new AC3DemuxWriter(fullPrefix, 0x80 + _ID);
+
+		if (!demuxer.AddDemuxer(SUBSTREAM_AC3_LOW + _ID, _muxer, muxArguments))
+			delete _muxer;
+		break;
+
+	case 2:
+	case 3:
+		muxArguments = muxArgumentsFormat.arg(filename + langSuffix, lang, fullPrefix, "mpa");		
+		_muxer = new MPADemuxWriter(fullPrefix, 0xC0 + _ID);
+
+		if (!demuxer.AddDemuxer(AUDIO_STREAM + _ID, _muxer, muxArguments))
+			delete _muxer;
+		break;
+
+	case 4:
+		// TODO possibly other LPCM formats ?
+		muxArguments = muxArgumentsFormat.arg(filename + langSuffix, lang, fullPrefix, "wav");
+		_muxer = new LPCMDemuxWriter(fullPrefix, 0xA0 + _ID, 48000, 16, 2);
+
+		if (!demuxer.AddDemuxer(SUBSTREAM_PCM_LOW + _ID, _muxer, muxArguments))
+			delete _muxer;
+		break;
+
+	case 6:
+		muxArguments = muxArgumentsFormat.arg(filename + langSuffix, lang, fullPrefix, "dts");
+		_muxer = new DTSDemuxWriter(fullPrefix, 0x88 + _ID);
+
+		if (!demuxer.AddDemuxer(SUBSTREAM_DTS_LOW + _ID, _muxer, muxArguments))
+			delete _muxer;
+		break;
+
+	default:
+		fprintf(stderr, "Unknown Audio Format: %d\n", _attr->audio_format);
+	}
+}
+
+void DMX::demuxSubtitleTrack(int16_t title, bool isMenu, const SubtitleTrackList& _subTracks, size_t _stream,  CompositeDemuxWriter& demuxer, const QString& filename, const uint32_t *_palette, uint16_t _width, uint16_t _height)
+{
+	if (_stream < 0 || _stream >= _subTracks.size())
+		return;
+
+	static const QString subMuxArgumentsFormat(" --track-name 0:\"sub-%1\" --language 0:%2 \"%3\"");
+
+	const subp_attr_t *_attr = 0;
+	const QString prefix = destinationPath_ + QDir::separator() + filename;
+
+	// get the possible IDs for this track for this Cell/PGC
+	IdArray _IDs = ifoFile_->GetSubsId(_stream, title, isMenu);
+
+	Writer * _muxer = 0;
+	QString lang, langSuffix;
+	
+	for (IdArray::size_type _IDidx=0; _IDidx < _IDs.size(); ++_IDidx)
+	{
+		_attr = _subTracks[_stream];
+		
+		// if (_attr->lang_code == 0)
+		lang = "und";
+		langSuffix = QString("_%1_un").arg(_IDs.at(_IDidx));
+
+		if (_attr->lang_code != 0)
+		{
+			lang = QString("%1%2 \"").arg(QString(_attr->lang_code >> 8), QString(_attr->lang_code & 0xFF));
+			langSuffix = QString("_%1_%2%3").arg(QString::number(_IDs.at(_IDidx)), QString(_attr->lang_code >> 8), QString(_attr->lang_code & 0xFF));		
+		}
+
+		_muxer = new SubDemuxWriter(QString(prefix + langSuffix), 0x20 + _IDs.at(_IDidx), _width, _height, _palette, _attr->lang_code, _attr->lang_extension == 9);
+
+		QString commandLine = subMuxArgumentsFormat.arg(filename + langSuffix, lang, prefix + langSuffix + ".idx");
+		if (!demuxer.AddDemuxer(SUBSTREAM_SUB_LOW + _IDs.at(_IDidx), _muxer, commandLine))
+			delete _muxer;
+	}
+}
+
+void DMX::demux(VobParser *aVobParser, int selectionIndex, const QString &editionUID, int16_t title, bool menu)
+{
+	// get list of all cells
+	const CellsListType *CellsList = ifoFile_->GetCellsList(title, menu);
+	
+	if (!CellsList)
+		return;
+
+	static const QString demuxArguments (" --track-name 0:\"video\" --timecodes 0:\"%1_m2v.tmc\" \"%1.m2v\"");
+	static const QString btnMuxArgumentsFormat(" --track-name 0:\"btn-%1\" --timecodes 0:\"%2_btn.tmc\" \"%2.btn\"");
+	
+	try
+	{
+		QString filename;
+		QString muxCommand;
+
+		// init filename
+		if (title == 0)
+			filename = "VMG";
+		else
+			filename = QString("VTS%1%2").arg(menu ? "M" : "").arg(title, 2, 10, QChar('0'));
+
+		const QString prefix = destinationPath_ + QDir::separator() + filename;
+
+		if (aVobParser != 0)
+		{
+			aVobParser->Reset();
+
+			const AudioTrackList & _audioTracks = ifoFile_->AudioTracks(title, menu);
+			const SubtitleTrackList & _subTracks = ifoFile_->SubsTracks(title, menu);
+			
+			double _fps = 0;
+			uint16_t _width = 0, _height = 0;
+			
+			ifoFile_->VideoSize(title, menu, _width, _height, _fps);
+			const uint32_t * _palette = ifoFile_->GetPalette(title, menu);
+
+			CompositeDemuxWriter &demuxer = aVobParser->GetDemuxer();
+
+			//emit progressChanged(CellsList->count());
+
+			demuxer.Reset();
+			printf("Processing %s\n", qPrintable(filename));
+
+			if ((selectionIndex < 0) || (selection_[selectionIndex].isVideoEnabled()))
+			{
+				Writer *_muxer = new VideoDemuxWriter(prefix, _fps);
+
+				QString commandLine = demuxArguments.arg(prefix);
+				if (!demuxer.AddDemuxer(VIDEO_STREAM, _muxer, commandLine))
+					delete _muxer;
+			}
+
+			size_t _stream = 0;
+
+			QString lang;
+			QString langSuffix;
+
+			// decide which audio streams to process
+			if (_audioTracks.size())
+			{
+				if (selectionIndex > -1)
+				{
+					const std::vector<size_t>& selectedAudioTracks = selection_[selectionIndex].audioTracks();
+					
+					for (size_t index = 0; index < selectedAudioTracks.size(); ++index)
+					{
+						_stream = selectedAudioTracks[index];
+						demuxAudioTrack(title, menu, _audioTracks, _stream,  demuxer, filename);
+					}
+				}
+				else
+				{
+					for (_stream = 0; _stream < _audioTracks.size(); ++_stream)
+						demuxAudioTrack(title, menu, _audioTracks, _stream, demuxer, filename);
+				}
+			}
+
+			// process subtitle streams
+			if (_subTracks.size())
+			{
+				if (selectionIndex > -1)
+				{
+					const std::vector<size_t>& selectedSubTracks = selection_[selectionIndex].subtitleTracks();
+
+					for (size_t index = 0; index < selectedSubTracks.size(); ++index)
+					{
+						_stream = selectedSubTracks[index];
+						demuxSubtitleTrack(title, menu, _subTracks, _stream,  demuxer, filename, _palette, _width, _height);
+					}
+				}
+				else
+				{
+					for (_stream = 0; _stream < _subTracks.size(); ++_stream)
+						demuxSubtitleTrack(title, menu, _subTracks, _stream, demuxer, filename, _palette, _width, _height);
+				}
+
+				// create a possible button demuxer too
+				Writer *_muxer = new BtnDemuxWriter(prefix, _width, _height);
+
+				QString commandLine = btnMuxArgumentsFormat.arg(filename, prefix);
+				if (!demuxer.AddDemuxer(SUBSTREAM_PCI, _muxer, commandLine))
+					delete _muxer;
+			}
+
+			if (consoleMode_)
+				printf("0%%");
+			else
+				emit progressChanged(0);
+			
+			const uint32_t maximum = aVobParser->GetPacketCount();
+			
+			while(aVobParser->ParseNextPacket(*CellsList) && !needsAbort_)
+			{
+				if (consoleMode_)
+					printf("\r\r\r%d%%", aVobParser->GetPacketIndex() * 100 / maximum);
+				else
+					emit progressChanged(aVobParser->GetPacketIndex() * 100 / maximum);
+			}
+			printf("\n");
+
+			// create the command line using the list of used files
+			// always put video first
+			if (demuxer.FileExists(VIDEO_STREAM))
+				muxCommand += demuxer.GetString(VIDEO_STREAM);
+
+			for (_stream = 0; _stream < 256; _stream++)
+			{
+				if (_stream == VIDEO_STREAM)
+					continue;
+				
+				if (demuxer.FileExists(_stream))
+					muxCommand += demuxer.GetString(_stream);
+			}
+		}
+
+		CellsListType *CellsListDone = (CellsListType *)CellsList;
+		CellsListDone->arrange();
+
+		bool addChapters = false;
+		ChapterManager chapterEditor(2 /*indent count*/);
+
+		if (menu)
+			addChapters = chapterEditor.generateMenuScript(*ifoFile_, prefix, title, editionUID);
+		else
+			addChapters = chapterEditor.generateScript(*ifoFile_, prefix, title, editionUID);
+
+		if (addChapters)
+		{
+			muxCommand += " --chapters \"" + prefix + ChapterManager::CHAPTER_SUFFIX + "\"";
+			muxCommand += " --segmentinfo \"" + prefix + ChapterManager::INFO_SUFFIX + "\"";
+		}
+
+#if (defined(WIN32) || defined(WIN64))
+		muxCommand.insert(0, "\"" + toolsPath_ + "\\mkvmerge\" -o \"" + prefix + ".mkv\"");
+		QFile muxBatchFile(prefix + "_" + QTime::currentTime().toString("hh_mm_ss") + ".bat");
+#else
+		muxCommand.insert(0, toolsPath_ + "/mkvmerge -o \"" + prefix + ".mkv\"");
+		QFile muxBatchFile(prefix + "_" + QTime::currentTime().toString("hh_mm_ss") + ".sh");
+#endif
+
+		if (!muxBatchFile.open( QIODevice::WriteOnly | QIODevice::Text ))
+			fprintf(stderr, "Could not create batch file\n");
+		
+#if !defined(WIN32) && !defined(WIN64)
+		QString shellString ("#!/bin/sh\n\n");
+		muxBatchFile.write(shellString.toUtf8());
+#endif
+		muxBatchFile.write(muxCommand.toUtf8());
+		muxBatchFile.close();
+
+		printf("Done demuxing %s\n", qPrintable(filename));
+	}
+	catch(VobParserException e)
+	{
+		fprintf(stderr, "Vob Parser Exception Occurred: %s\n", e.what());
+	}
+}

Added: trunk/DvdMenuXtractor/dmx/dmx.h
===================================================================
--- trunk/DvdMenuXtractor/dmx/dmx.h	2007-03-08 09:04:37 UTC (rev 1280)
+++ trunk/DvdMenuXtractor/dmx/dmx.h	2007-03-10 21:14:50 UTC (rev 1281)
@@ -0,0 +1,54 @@
+#ifndef DMX_H
+#define DMX_H
+
+#include <vector>
+#include <QThread>
+#include "dmxselectionitem.h"
+#include "vobparser/IFOFile.h"
+
+class DMX : public QThread
+{
+	Q_OBJECT
+
+public:
+	typedef std::vector<DMXSelectionItem> SelectionType;
+
+  DMX(bool consoleMode = false);
+  ~DMX();
+
+	static IFOFile* OpenIFOFile(const QString& path);
+	bool setExtractionParameters(const QString& sourcePath, const QString& destinationPath, const QString& toolsPath, const SelectionType& selection);
+	
+signals:
+	// Signal is emitted when the current step progress is changed
+	void progressChanged(int);
+
+	// Signal is emitted when the step is changed
+	void stepChanged(const QString&);
+
+public slots:
+	void abort();
+
+protected:
+	void run();
+
+private:
+	IFOFile *ifoFile_;
+	bool consoleMode_;
+	QString toolsPath_;
+	QString sourcePath_;
+	QString destinationPath_;
+	SelectionType selection_;
+	volatile bool needsAbort_;
+	
+	bool loadIFOFile(const QString& path);
+	void processTitle(int16_t title, int index);
+
+	VobParser* buildVobParser(int16_t title, bool isMenu);
+	
+	void demux(VobParser* aVobParser, int selectionIndex, const QString& editionUID, int16_t title, bool isMenu);
+	void demuxAudioTrack(int16_t title, bool isMenu, const AudioTrackList& _audioTracks, size_t _stream, CompositeDemuxWriter& demuxer, const QString& filename);
+	void demuxSubtitleTrack(int16_t title, bool isMenu, const SubtitleTrackList& _subTracks, size_t _stream,  CompositeDemuxWriter& demuxer, const QString& filename, const uint32_t *_palette, uint16_t _width, uint16_t _height);
+};
+
+#endif // DMX_H

Added: trunk/DvdMenuXtractor/dmx/dmx.proj
===================================================================
--- trunk/DvdMenuXtractor/dmx/dmx.proj	2007-03-08 09:04:37 UTC (rev 1280)
+++ trunk/DvdMenuXtractor/dmx/dmx.proj	2007-03-10 21:14:50 UTC (rev 1281)
@@ -0,0 +1,38 @@
+#include "*/*.proj"
+
+LIB dmx
+{
+  PROJECT_VERSION  0.9.9
+  PROJECT_BUILD    1278
+  PROJECT_NAME     "DMX"
+  PROJECT_VENDOR   qdmx
+  PROJECT_FOURCC   QDMX
+  
+  USE dvdread
+  USE vobparser
+  USE mpegparser
+  
+  DEFINE __STDC_LIMIT_MACROS
+  DEFINE(QT_NO_DEBUG) QT_NO_DEBUG_STREAM
+
+  INCLUDE libdvdread
+  
+  INCLUDE(COMPILER_MSVC) libdvdread/win32
+  INCLUDE(COMPILER_MSVC) "$(QTDIR)/include"
+  INCLUDE(COMPILER_MSVC) "$(QTDIR)/include/QtXml"
+  INCLUDE(COMPILER_MSVC) "$(QTDIR)/include/QtCore"
+  
+  INCLUDE(COMPILER_GCC)  "$(QTDIR)/include/qt4/"
+  INCLUDE(COMPILER_GCC)  "$(QTDIR)/include/qt4/QtXml"
+  INCLUDE(COMPILER_GCC)  "$(QTDIR)/include/qt4/QtCore"
+  
+  SOURCE dmx.cpp
+  SOURCE utilities.cpp
+  SOURCE chaptermanager.cpp
+  SOURCE dmxselectionitem.cpp
+
+  HEADER_QT4 dmx.h
+  HEADER utilities.h
+  HEADER chaptermanager.h
+  HEADER dmxselectionitem.h
+}

Added: trunk/DvdMenuXtractor/dmx/dmx_project.h
===================================================================
--- trunk/DvdMenuXtractor/dmx/dmx_project.h	2007-03-08 09:04:37 UTC (rev 1280)
+++ trunk/DvdMenuXtractor/dmx/dmx_project.h	2007-03-10 21:14:50 UTC (rev 1281)
@@ -0,0 +1,8 @@
+/* GENERATED FILE, DO NOT EDIT */
+
+#define PROJECT_VERSION _T("0.9.9")
+#define PROJECT_BUILD 1278
+#define PROJECT_NAME _T("DMX")
+#define PROJECT_VENDOR _T("qdmx")
+#define PROJECT_FOURCC _T("QDMX")
+

Added: trunk/DvdMenuXtractor/dmx/dmxselectionitem.cpp
===================================================================
--- trunk/DvdMenuXtractor/dmx/dmxselectionitem.cpp	2007-03-08 09:04:37 UTC (rev 1280)
+++ trunk/DvdMenuXtractor/dmx/dmxselectionitem.cpp	2007-03-10 21:14:50 UTC (rev 1281)
@@ -0,0 +1,68 @@
+#include <algorithm>
+#include "dmxselectionitem.h"
+
+DMXSelectionItem::DMXSelectionItem(int16_t title, bool isMenu, bool enableVideo = 1)
+	: title_(title), menu_(isMenu), videoEnabled_(enableVideo)
+{
+	// override for VMG
+	if (!title)
+		menu_ = true;
+}
+
+DMXSelectionItem::~DMXSelectionItem()
+{
+}
+
+int16_t DMXSelectionItem::title() const
+{
+	return title_;
+}
+
+bool DMXSelectionItem::isMenu() const
+{
+	return menu_;
+}
+
+bool DMXSelectionItem::isVideoEnabled() const
+{
+	return videoEnabled_;
+}
+
+const DMXSelectionItem::TracksContainerType& DMXSelectionItem::audioTracks() const
+{
+	return audioTracks_;
+}
+
+const DMXSelectionItem::TracksContainerType& DMXSelectionItem::subtitleTracks() const
+{
+	return subtitleTracks_;
+}
+
+void DMXSelectionItem::disableVideo()
+{
+	videoEnabled_ = false;
+}
+
+bool DMXSelectionItem::addAudioTrack(size_t trackIndex)
+{
+	// check if audio track was already selected
+	if (std::count(audioTracks_.begin(), audioTracks_.end(), trackIndex) == 0)
+	{
+		audioTracks_.push_back(trackIndex);
+		return true;
+	}
+
+	return false;
+}
+
+bool DMXSelectionItem::addSubtitleTrack(size_t trackIndex)
+{
+	// check if audio track was already selected
+	if (std::count(subtitleTracks_.begin(), subtitleTracks_.end(), trackIndex) == 0)
+	{
+		subtitleTracks_.push_back(trackIndex);
+		return true;
+	}
+
+	return false;
+}

Added: trunk/DvdMenuXtractor/dmx/dmxselectionitem.h
===================================================================
--- trunk/DvdMenuXtractor/dmx/dmxselectionitem.h	2007-03-08 09:04:37 UTC (rev 1280)
+++ trunk/DvdMenuXtractor/dmx/dmxselectionitem.h	2007-03-10 21:14:50 UTC (rev 1281)
@@ -0,0 +1,38 @@
+#ifndef DMX_SELECTION_ITEM
+#define DMX_SELECTION_ITEM
+
+#include <vector>
+#include <stdint.h>
+
+// Denotes each selected title
+class DMXSelectionItem
+{
+public:
+
+	typedef std::vector<size_t> TracksContainerType;
+
+	DMXSelectionItem(int16_t title, bool isMenu, bool enableVideo);
+	~DMXSelectionItem();
+
+	// Getters
+	bool isMenu() const;
+	int16_t title() const;
+	bool isVideoEnabled() const;
+	const TracksContainerType& audioTracks() const;
+	const TracksContainerType& subtitleTracks() const;
+
+	void disableVideo();
+	bool addAudioTrack(size_t trackIndex);
+	bool addSubtitleTrack(size_t trackIndex);
+
+private:
+	int16_t title_;
+	bool menu_;
+	bool videoEnabled_;
+
+	TracksContainerType audioTracks_;
+	TracksContainerType subtitleTracks_;
+};
+
+
+#endif // DMX_SELECTION_ITEM

Added: trunk/DvdMenuXtractor/dmx/utilities.cpp
===================================================================
--- trunk/DvdMenuXtractor/dmx/utilities.cpp	2007-03-08 09:04:37 UTC (rev 1280)
+++ trunk/DvdMenuXtractor/dmx/utilities.cpp	2007-03-10 21:14:50 UTC (rev 1281)
@@ -0,0 +1,32 @@
+#include <iostream>
+#include "utilities.h"
+
+QString Utilities::CreateUID()
+{
+	return QString("%1").arg( uint32_t(rand() | (rand() << 16)) );
+}
+
+QString Utilities::FormatTime(uint64_t a_time)
+{
+	int hour, min, sec;
+	uint32_t nano;
+
+	nano = a_time % 1000000000;
+	a_time /= 1000000000;
+	sec = a_time % 60;
+	a_time /= 60;
+	min = a_time % 60;
+	hour = a_time / 60;
+
+	return QString("%1:%2:%3.%4").arg(hour, 2, 10, QChar('0')).arg(min, 2, 10, QChar('0')).arg(sec, 2, 10, QChar('0')).arg(nano, 9, 10, QChar('0'));
+}
+
+QString Utilities::EncodeHex(const unsigned char *buffer, unsigned int size)
+{
+	QString result;
+
+	for (unsigned i = 0; i < size; ++i)
+		result += QString("%1 ").arg(buffer[i], 2, 16, QChar('0'));
+
+	return result.trimmed();
+}

Added: trunk/DvdMenuXtractor/dmx/utilities.h
===================================================================
--- trunk/DvdMenuXtractor/dmx/utilities.h	2007-03-08 09:04:37 UTC (rev 1280)
+++ trunk/DvdMenuXtractor/dmx/utilities.h	2007-03-10 21:14:50 UTC (rev 1281)
@@ -0,0 +1,20 @@
+#ifndef UTILITIES_H
+#define UTILITIES_H
+
+#include <QString>
+#include <stdint.h>
+#include "dmx_project.h"
+
+#define _T(x) (x)
+
+namespace Utilities
+{
+	static const QString APPLICATION_NAME    ("DVDMenuXtractor: DMX - a fair-use tool");
+	static const QString APPLICATION_VERSION PROJECT_VERSION;
+
+	QString CreateUID();
+	QString FormatTime(uint64_t a_time);
+	QString EncodeHex(const unsigned char *buffer, unsigned size);
+}
+
+#endif // UTILITIES_H



More information about the Matroska-cvs mailing list