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

[email protected] Sun, 11 Mar 2007 00:15:13 +0300 (MSK)
Newsgroups gmane.comp.multimedia.matroska.cvs
Message-ID <[email protected]>
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
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- 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 QStrin=
g& filename, uint16_t title, const QString& editionUID) const
+{
+	static unsigned char _PrivateVTS[] =3D {0x30, 0x80, 0x00, 0x00};
+	static unsigned char _PrivateTT[]  =3D {0x28, 0x00, 0x00, 0x00};
+	uint64_t start_timeH =3D uint64_t(-1);
+
+	generateInfoScript(filename, title, editionUID);
+
+	// create document
+	QDomDocument document;
+
+	// specify processing info
+	document.appendChild(document.createProcessingInstruction("xml", "versi=
on=3D\"1.0\" encoding=3D\"UTF-8\" standalone=3D\"no\""));
+	document.appendChild(document.implementation().createDocumentType("Chap=
ters", QString::null, "matroskachapters.dtd"));
+
+	// create comments
+	document.appendChild(document.createComment("Created with " + Utilities=
::APPLICATION_NAME + " " + Utilities::APPLICATION_VERSION));
+
+	// create the root element
+	QDomNode root =3D document.createElement("Chapters");
+	document.appendChild(root);
+
+	// VTS content
+	QDomElement editionEntryElement =3D document.createElement("EditionEntr=
y");
+	root.appendChild(editionEntryElement);
+=09
+	editionEntryElement.appendChild(CreateDOMElement(document, "EditionUID"=
, editionUID));
+	editionEntryElement.appendChild(CreateDOMElement(document, "EditionFlag=
Ordered", "1"));
+	editionEntryElement.appendChild(document.createComment("Video Title Set=
"));
+
+	QDomElement chapterAtomElement =3D document.createElement("ChapterAtom"=
);
+	editionEntryElement.appendChild(chapterAtomElement);
+
+	QString _myUIDa =3D Utilities::CreateUID();
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID",=
 _myUIDa));
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterFlagH=
idden", "1"));
+=09
+	_PrivateVTS[2] =3D title >> 8;
+	_PrivateVTS[3] =3D title & 0xFF;
+	AddCodecPrivateData(document, chapterAtomElement, _PrivateVTS, 4);
+
+	QDomNode chapterAtomParentElement =3D chapterAtomElement;
+
+	if (ifoFile.VtsTitles(title) && ifoFile.VtsPGCs(title))
+	{
+		for (int i =3D 0; i < ifoFile.VtsTitles(title)->nr_of_srpts; i++)
+		{
+			uint64_t start_time =3D uint64_t(-1), found_time;
+		=09
+			chapterAtomParentElement.appendChild(document.createComment(QString("=
Title TTU_%1").arg(i + 1)));
+			QDomElement chapterAtomElement =3D document.createElement("ChapterAto=
m");
+			chapterAtomParentElement.appendChild(chapterAtomElement);
+		=09
+			QString _myUIDa =3D Utilities::CreateUID();
+			chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID=
", _myUIDa));
+			chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterFla=
gHidden", "1"));
+
+			// get the Title -> VTS+TTN map
+			const tt_srpt_t *p_map =3D ifoFile.TitleMap();
+
+			if ( p_map !=3D NULL )
+			{
+				for (int j=3D0; j<p_map->nr_of_srpts; j++)
+				{
+					if ( ( p_map->title[j].title_set_nr =3D=3D title ) && ( p_map->titl=
e[j].vts_ttn =3D=3D i+1 ) )
+					{
+						_PrivateTT[1] =3D (j+1) >> 8;   // Title#
+						_PrivateTT[2] =3D (j+1) & 0xFF;
+						_PrivateTT[3] =3D i+1;          // VTS_TTN#
+						AddCodecPrivateData(document, chapterAtomElement, _PrivateTT, 4);
+						break;
+					}
+				}
+			}
+
+			// add the PGC that match this PTT
+			for (int k =3D 0; k < ifoFile.VtsPGCs(title)->nr_of_pgci_srp; k++)
+			{
+				if ((ifoFile.VtsPGCs(title)->pgci_srp[k].entry_id & 0x7F) =3D=3D i+1=
)
+				{
+					found_time =3D AddPGC(document, chapterAtomElement,=20
+						ifoFile.VtsPGCs(title)->pgci_srp[k].pgc, k, 0, ifoFile.CellsList(t=
itle, false), &ifoFile.VtsTitles(title)->title[i], i+1);
+				=09
+					if (start_time > found_time)
+						start_time =3D found_time;
+				}
+			}
+
+			chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterTim=
eStart", Utilities::FormatTime(start_time)));
+
+			if (start_timeH > start_time)
+				start_timeH =3D start_time;
+		}
+	}
+
+	if (start_timeH =3D=3D uint64_t(-1))
+		start_timeH =3D 0;
+
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterTimeS=
tart", Utilities::FormatTime(start_timeH)));
+=09
+	QString output =3D 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 QS=
tring& filename, uint16_t title, const QString& editionUID) const
+{
+	static unsigned char _PrivateFP[]  =3D {0x30, 0x00, 0x00, 0x00};
+	static unsigned char _PrivateVM[]  =3D {0x30, 0xC0, 0x00, 0x00};
+	static unsigned char _PrivateVTS[] =3D {0x30, 0x40, 0x00, 0x00};
+
+	// generate info script
+	generateInfoScript(filename, title, editionUID);
+
+	// create document
+	QDomDocument document;
+
+	// specify processing info
+	document.appendChild(document.createProcessingInstruction("xml", "versi=
on=3D\"1.0\" encoding=3D\"UTF-8\" standalone=3D\"no\""));
+	document.appendChild(document.implementation().createDocumentType("Chap=
ters", QString::null, "matroskachapters.dtd"));
+
+	// create comments
+	document.appendChild(document.createComment("Created with " + Utilities=
::APPLICATION_NAME + " " + Utilities::APPLICATION_VERSION));
+
+	// create the root element
+	QDomNode parentElement =3D document.createElement("Chapters");
+	document.appendChild(parentElement);
+
+	// create Edition Entry Element and add to root
+	QDomElement currentElement =3D document.createElement("EditionEntry");
+	parentElement.appendChild(currentElement);
+=09
+	// use Edition Entry Element as parent
+	parentElement =3D currentElement;
+
+	// add EditionUID and EDitionFlagOrdered elements
+	parentElement.appendChild(CreateDOMElement(document, "EditionUID", edit=
ionUID));
+	parentElement.appendChild(CreateDOMElement(document, "EditionFlagOrdere=
d", "1"));
+
+	// Write the first play PGC
+	if (title =3D=3D 0 && ifoFile.FirstPlayPGC())
+	{
+		// add comment to Edition Entry Element
+		parentElement.appendChild(document.createComment("First Play PGC"));
+
+		// add Chapter Atom
+		QDomElement chapterAtomElement =3D document.createElement("ChapterAtom=
");
+		parentElement.appendChild(chapterAtomElement);
+
+		// create and set UID
+		QString _myUID =3D 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 =3D document.createElement("ChapterDisplay");
+		currentElement.appendChild(CreateDOMElement(document, "ChapterString",=
 "First Play PGC " + _myUID));
+		chapterAtomElement.appendChild(currentElement);
+
+		// add Chapter Process Element
+		currentElement =3D document.createElement("ChapterProcess");
+		AddCodecPrivateData(document, currentElement, _PrivateFP, 4, false);
+		chapterAtomElement.appendChild(currentElement);
+
+		// PGC commands
+		AddPGCCommands(document, currentElement, ifoFile.FirstPlayPGC()->comma=
nd_tbl);
+	}
+
+	// Write the Video Title Set
+	if (ifoFile.LanguageUnits(title) && ifoFile.LanguageUnits(title)->nr_of=
_lus)
+	{
+		// VTS Menu
+		if ( title =3D=3D 0 )
+			parentElement.appendChild(document.createComment("Video Manager"));
+		else
+			parentElement.appendChild(document.createComment("Video Title Set"));
+
+		// add another Chapter Atom element
+		currentElement =3D document.createElement("ChapterAtom");
+		parentElement.appendChild(currentElement);
+
+		QString _myUIDa =3D Utilities::CreateUID();
+		currentElement.appendChild(CreateDOMElement(document, "ChapterUID", _m=
yUIDa));
+		currentElement.appendChild(CreateDOMElement(document, "ChapterFlagHidd=
en", "1"));
+
+		if ( title !=3D 0 )
+		{
+			_PrivateVTS[2] =3D title >> 8;
+			_PrivateVTS[3] =3D title & 0xFF;
+			AddCodecPrivateData(document, currentElement, _PrivateVTS, 4);
+		}
+		else
+		{
+			_PrivateVM[2] =3D 0;
+			_PrivateVM[3] =3D 0;
+			AddCodecPrivateData(document, currentElement, _PrivateVM, 4);
+		}
+
+		uint64_t start_time =3D HandleLanguageUnit(document, currentElement, i=
foFile, title);
+
+		currentElement.appendChild(CreateDOMElement(document, "ChapterTimeStar=
t", Utilities::FormatTime(start_time)));
+	}
+
+	// get document string representation
+	QString output =3D 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;
+=09
+	// 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", "versi=
on=3D\"1.0\" encoding=3D\"UTF-8\" standalone=3D\"no\""));
+	document.appendChild(document.implementation().createDocumentType("Chap=
ters", QString::null, "matroskainfos.dtd"));
+
+	// add root element
+	QDomNode parentElement =3D document.createElement("Info");
+	document.appendChild(parentElement);
+
+	// add Segment Family Element
+	QDomElement currentElement =3D CreateDOMElement(document, "SegmentFamil=
y", Utilities::EncodeHex(familyUID, 16));
+	currentElement.setAttribute("format", "hex");
+	parentElement.appendChild(currentElement);
+
+	// add Chapter Translate Element
+	currentElement =3D document.createElement("ChapterTranslate");
+	currentElement.appendChild(CreateDOMElement(document, "ChapterTranslate=
EditionUID", editionUID));
+	currentElement.appendChild(CreateDOMElement(document, "ChapterTranslate=
Codec", "1"));
+=09
+	QDomElement chapterTranslateIDElement =3D CreateDOMElement(document, "C=
hapterTranslateID", 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 =3D document.toString(INDENT_COUNT);
+=09
+	// segment file
+	QFile segementFile(filename + INFO_SUFFIX);
+	if (!segementFile.open(QIODevice::WriteOnly | QIODevice::Text))
+		throw;
+=09
+	segementFile.write(output.toUtf8());
+	segementFile.close();
+}
+
+QDomElement ChapterManager::CreateDOMElement(QDomDocument &document, con=
st QString &elementName, const QString &content)
+{
+	QDomElement element =3D document.createElement(elementName);
+	element.appendChild(document.createTextNode(content));
+
+	return element;
+}
+
+void ChapterManager::AddChapterTime(QDomDocument& document, QDomNode& pa=
rent, uint64_t start_time, uint64_t end_time)
+{
+	parent.appendChild(CreateDOMElement(document, "ChapterTimeStart", Utili=
ties::FormatTime(start_time)));
+	parent.appendChild(CreateDOMElement(document, "ChapterTimeEnd", Utiliti=
es::FormatTime(end_time)));
+}
+
+void ChapterManager::AddCodecPrivateData(QDomDocument& document, QDomNod=
e& parent, const unsigned char *buffer, unsigned int size, bool createCha=
pterProcessElement /*=3D true*/)
+{
+	QDomNode node =3D parent;
+
+	if (createChapterProcessElement)
+	{
+		QDomElement chapterProcessElement =3D document.createElement("ChapterP=
rocess");
+		parent.appendChild(chapterProcessElement);
+		node =3D chapterProcessElement;
+	}
+
+	node.appendChild(CreateDOMElement(document, "ChapterProcessCodecID", "1=
"));
+
+	QDomElement chapterProcessPrivateElement =3D CreateDOMElement(document,=
 "ChapterProcessPrivate", Utilities::EncodeHex(buffer, size));
+	chapterProcessPrivateElement.setAttribute("format", "hex");
+	node.appendChild(chapterProcessPrivateElement);
+}
+
+void ChapterManager::AddPGCCommands(QDomDocument& document, QDomNode& pa=
rent, const pgc_command_tbl_t * command_tbl)
+{
+	if (command_tbl)
+	{
+		unsigned char *_buf =3D (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 =3D document.createElement("Chapter=
ProcessCommand");
+			parent.appendChild(chapterCommandElement);
+
+			QDomElement chapterProcessTimeElement =3D document.createElement("Cha=
pterProcessTime");
+			chapterProcessTimeElement.appendChild(document.createTextNode("1"));
+			chapterCommandElement.appendChild(chapterProcessTimeElement);
+
+			_buf[0] =3D command_tbl->nr_of_pre;
+			memcpy(&_buf[1], command_tbl->pre_cmds, command_tbl->nr_of_pre * 8);
+
+			QDomElement chapterProcessDataElement =3D CreateDOMElement(document, =
"ChapterProcessData", Utilities::EncodeHex(_buf, 1 + command_tbl->nr_of_p=
re * 8));
+			chapterProcessDataElement.setAttribute("format", "hex");
+			chapterCommandElement.appendChild(chapterProcessDataElement);
+		}
+
+		// Cell commands
+		if (command_tbl->nr_of_cell)
+		{
+			parent.appendChild(document.createComment("Cell commands"));
+
+			QDomElement chapterCommandElement =3D document.createElement("Chapter=
ProcessCommand");
+			parent.appendChild(chapterCommandElement);
+
+			QDomElement chapterProcessTimeElement =3D document.createElement("Cha=
pterProcessTime");
+			chapterProcessTimeElement.appendChild(document.createTextNode("0"));
+			chapterCommandElement.appendChild(chapterProcessTimeElement);
+
+			_buf =3D (unsigned char*) realloc(_buf, 1 + command_tbl->nr_of_cell *=
 8);
+			_buf[0] =3D command_tbl->nr_of_cell;
+			memcpy(&_buf[1], command_tbl->cell_cmds, command_tbl->nr_of_cell * 8)=
;
+
+			QDomElement chapterProcessDataElement =3D CreateDOMElement(document, =
"ChapterProcessData", Utilities::EncodeHex(_buf, 1 + command_tbl->nr_of_c=
ell * 8));
+			chapterProcessDataElement.setAttribute("format", "hex");
+			chapterCommandElement.appendChild(chapterProcessDataElement);
+		}
+
+		// Post commands
+		if (command_tbl->nr_of_post)
+		{
+			parent.appendChild(document.createComment("Post commands"));
+
+			QDomElement chapterCommandElement =3D document.createElement("Chapter=
ProcessCommand");
+			parent.appendChild(chapterCommandElement);
+
+			QDomElement chapterProcessTimeElement =3D document.createElement("Cha=
pterProcessTime");
+			chapterProcessTimeElement.appendChild(document.createTextNode("2"));
+			chapterCommandElement.appendChild(chapterProcessTimeElement);
+
+			_buf =3D (unsigned char*) realloc(_buf, 1+command_tbl->nr_of_post * 8=
);
+			_buf[0] =3D command_tbl->nr_of_post;
+			memcpy(&_buf[1], command_tbl->post_cmds, command_tbl->nr_of_post * 8)=
;
+
+			QDomElement chapterProcessDataElement =3D CreateDOMElement(document, =
"ChapterProcessData", Utilities::EncodeHex(_buf, 1 + command_tbl->nr_of_p=
ost * 8));
+			chapterProcessDataElement.setAttribute("format", "hex");
+			chapterCommandElement.appendChild(chapterProcessDataElement);
+		}
+
+		free(_buf);
+	}
+}
+
+uint64_t ChapterManager::HandleLanguageUnit(QDomDocument& document, QDom=
Node& parent, const IFOFile& _ifo, int title)
+{
+	static unsigned char _PrivateLU[]  =3D {0x2A, 0x00, 0x00, 0x00};
+	uint64_t start_time =3D uint64_t(-1), found_time;
+
+	QDomElement chapterAtomElement;
+	QDomElement chapterDisplayElement;
+	QDomElement chapterStringElement;
+	QDomElement chapterTimeStartElement;
+
+	for (int i =3D 0 ;i < _ifo.LanguageUnits(title)->nr_of_lus; i++)
+	{
+		parent.appendChild(document.createComment("Language Units"));
+
+		chapterAtomElement =3D document.createElement("ChapterAtom");
+		parent.appendChild(chapterAtomElement);
+
+		// the Language Unit for a given language
+		const pgci_lu_t &_LU =3D _ifo.LanguageUnits(title)->lu[i];
+		_PrivateLU[1] =3D _LU.lang_code >> 8;
+		_PrivateLU[2] =3D _LU.lang_code & 0xFF;
+		_PrivateLU[3] =3D _LU.lang_extension;
+
+		chapterAtomElement.appendChild(document.createComment(QString("Languag=
e: %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 =3D document.createElement("ChapterDisplay");
+		chapterAtomElement.appendChild(chapterDisplayElement);
+
+		chapterStringElement =3D document.createElement("ChapterString");
+		chapterStringElement.appendChild(document.createTextNode(QString("Lang=
uage Unit for %1%2").arg(QChar(_LU.lang_code >> 8)).arg(QChar(_LU.lang_co=
de & 0xFF))));
+		chapterDisplayElement.appendChild(chapterStringElement);
+
+		if (_LU.pgcit)
+		{
+			for (int j =3D 0; j < _LU.pgcit->nr_of_pgci_srp; j++)
+			{
+				const pgci_srp_t &_PGC_SRP =3D _LU.pgcit->pgci_srp[j];
+
+				found_time =3D AddPGC(document, chapterAtomElement, _PGC_SRP.pgc, j,=
 _PGC_SRP.entry_id, _ifo.CellsList(title, true), NULL, 0);
+
+				if (start_time > found_time)
+					start_time =3D found_time;
+			}
+		}
+
+		chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterTime=
Start", 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 Cell=
sListType & cell_list, const ttu_t * ptts, int title)
+{
+	static unsigned char _PrivatePGC[] =3D {0x20, 0x00, 0x00, 0x00, 0x00, 0=
x00, 0x00, 0x00};
+	uint64_t start_time =3D 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 =3D document.createElement("ChapterAtom"=
);
+	parent.appendChild(chapterAtomElement);
+
+	QString _myUID =3D Utilities::CreateUID();
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID",=
 _myUID));
+
+	// display the UID for the lazy rippers
+	if (ptts =3D=3D NULL)
+	{
+		chapterAtomElement.appendChild(document.createComment("Enter your text=
 here"));
+
+		QDomElement chapterDisplayElement =3D document.createElement("ChapterD=
isplay");
+		chapterDisplayElement.appendChild(CreateDOMElement(document, "ChapterS=
tring", "PGC type " + GetPGCType(pgc_type)));
+		chapterAtomElement.appendChild(chapterDisplayElement);
+	}
+	else
+		chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterFlag=
Hidden", "1"));
+
+	// PGC commands
+	QDomElement chapterProcessElement =3D document.createElement("ChapterPr=
ocess");
+	chapterAtomElement.appendChild(chapterProcessElement);
+
+	_PrivatePGC[1] =3D (pgc_num+1) >> 8;
+	_PrivatePGC[2] =3D (pgc_num+1) & 0xFF;
+	_PrivatePGC[3] =3D pgc_type;
+	_PrivatePGC[4] =3D ((uint8_t*) &pgc->prohibited_ops)[0];
+	_PrivatePGC[5] =3D ((uint8_t*) &pgc->prohibited_ops)[1];
+	_PrivatePGC[6] =3D ((uint8_t*) &pgc->prohibited_ops)[2];
+	_PrivatePGC[7] =3D ((uint8_t*) &pgc->prohibited_ops)[3];
+
+	AddCodecPrivateData(document, chapterProcessElement, _PrivatePGC, 8, fa=
lse);
+	AddPGCCommands(document, chapterProcessElement, pgc->command_tbl);
+
+	// Handle the Programs/Cells in the PGC
+	for (int i=3D0; i< pgc->nr_of_programs; i++)
+	{
+		found_time =3D AddProgram(document, chapterAtomElement, pgc, _myUID, i=
, cell_list, pgc_num, ptts, title);
+
+		if (start_time > found_time)
+			start_time =3D found_time;
+	}
+
+	if (start_time =3D=3D uint64_t(-1))
+		start_time =3D 0;
+
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterTimeS=
tart", Utilities::FormatTime(start_time)));
+
+	return start_time;
+}
+
+
+QString ChapterManager::GetPGCType(unsigned char entry_id)
+{
+	QString result ("0x%1 =3D ");
+	result =3D result.arg(entry_id, 2, 16, QChar('0'));
+
+	if (entry_id & 0x80)
+		result +=3D "Entry + ";
+
+	switch (entry_id & ~0x80)
+	{
+	case 2:
+		result +=3D "Title-Menu";
+		break;
+	case 3:
+		result +=3D "Root Menu";
+		break;
+	case 4:
+		result +=3D "Subpicture Menu";
+		break;
+	case 5:
+		result +=3D "Audio Menu";
+		break;
+	case 6:
+		result +=3D "Angle Menu";
+		break;
+	case 7:
+		result +=3D "Chapter Menu";
+		break;
+	default:
+		result +=3D "Basic PGC";
+		break;
+	}
+
+	return result;
+}
+
+
+uint64_t ChapterManager::AddProgram(QDomDocument& document, QDomNode& pa=
rent, 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[] =3D {0x10, 0x00};
+	static unsigned char _PrivatePG[] =3D {0x18, 0x00, 0x00};
+	static unsigned char _PrivateCN[] =3D {0x08, 0x00, 0x00, 0x00, 0x00};
+
+	uint64_t start_time =3D uint64_t(-1), end_time =3D 0;
+
+	// for this program
+	int entry_cell_num =3D pgc->program_map[program_number];
+	int program_last_cell_num =3D pgc->nr_of_cells;
+	int next_program_entry_cell_num;
+	if (program_number+1 =3D=3D pgc->nr_of_programs)
+		next_program_entry_cell_num =3D program_last_cell_num+1;
+	else
+		next_program_entry_cell_num =3D pgc->program_map[program_number+1];
+
+	parent.appendChild(document.createComment(QString("Program PGN# %1 in t=
his PGC").arg(program_number + 1)));
+
+	QDomElement chapterAtomElement =3D document.createElement("ChapterAtom"=
);
+	parent.appendChild(chapterAtomElement);
+
+	QString _myUID =3D Utilities::CreateUID();
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID",=
 _myUID));
+	chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterFlagH=
idden", "1"));
+
+	_PrivatePG[1] =3D (program_number+1) >> 8;
+	_PrivatePG[2] =3D (program_number+1) & 0xFF;
+	AddCodecPrivateData(document, chapterAtomElement, _PrivatePG, 3);
+
+	if (ptts !=3D NULL)
+	{
+		// find all PTT corresponding to this PGC/PG number
+		for (uint16_t pppt_nr =3D 0; pppt_nr < ptts->nr_of_ptts; pppt_nr++)
+		{
+			// use the start timecode of the first cell
+			const cell_position_t & position =3D pgc->cell_position[entry_cell_nu=
m-1];
+			const CellListElem * cell_elt =3D cell_list.at(position);
+			// TODO: make this test optional (create chapters even without the co=
ntent)
+			if (cell_elt =3D=3D NULL || !cell_elt->found)
+				continue;
+		=09
+			start_time =3D cell_elt->start_time;
+
+			QDomElement chapterAtomParent =3D chapterAtomElement;
+
+			if (ptts->ptt[pppt_nr].pgcn =3D=3D pgc_num+1 && ptts->ptt[pppt_nr].pg=
n =3D=3D program_number+1)
+			{
+				chapterAtomElement.appendChild(document.createComment(QString("Chapt=
er PTT#%1 [%2.%3]").arg(pppt_nr+1).arg(pgc_num+1, 2, 10, QChar('0')).arg(=
program_number+1, 2, 10, QChar('0'))));
+			=09
+				chapterAtomElement =3D document.createElement("ChapterAtom");
+				chapterAtomParent.appendChild(chapterAtomElement);
+
+				chapterAtomElement.appendChild(document.createComment("Enter your te=
xt here"));
+
+				QDomElement chapterDisplayElement =3D document.createElement("Chapte=
rDisplay");
+				chapterDisplayElement.appendChild(CreateDOMElement(document, "Chapte=
rString", QString("Your Name Here For Chapter #%1.%2").arg(title).arg(ppp=
t_nr + 1)));
+				chapterAtomElement.appendChild(chapterDisplayElement);
+
+				QString _myUID =3D Utilities::CreateUID();
+				chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUI=
D", _myUID));
+
+				_PrivatePTT[1] =3D pppt_nr+1;
+				AddCodecPrivateData(document, chapterAtomElement, _PrivatePTT, 2);
+
+				chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterTi=
meStart", Utilities::FormatTime(start_time)));
+			}
+
+			chapterAtomElement =3D chapterAtomParent;
+		}
+	}
+
+	QDomElement parentChapterAtom =3D chapterAtomElement;
+
+	for (int cell_num =3D entry_cell_num; cell_num<next_program_entry_cell_=
num; cell_num++)
+	{
+		// Output cells
+		const cell_position_t & position =3D pgc->cell_position[cell_num-1];
+		const cell_playback_t & cell =3D pgc->cell_playback[cell_num-1];
+
+		if (cell.block_mode !=3D 0 && cell.block_mode !=3D 3)
+			continue;
+
+		const CellListElem * cell_elt =3D cell_list.at(position);
+		// TODO: make this test optional (create chapters even without the con=
tent)
+		if (cell_elt =3D=3D NULL || !cell_elt->found)
+			continue;
+
+		QString prefix =3D QString("Program %1 Cell CN#%2 ").arg( (cell_num =3D=
=3D entry_cell_num) ? "Entry" : "", QString::number(cell_num));
+		QString middle =3D QString("[%1.%2]").arg(cell_elt->vobid, 2, 10, QCha=
r('0')).arg(cell_elt->cellid);
+		QString suffix =3D QString(" (%1 angles)").arg(cell_num - entry_cell_n=
um + 1);
+
+		if (cell.block_mode) // multi-angle
+			parentChapterAtom.appendChild(document.createComment(prefix + middle =
+ suffix));
+		else
+			parentChapterAtom.appendChild(document.createComment(prefix + middle)=
);
+
+		QDomElement chapterAtomElement =3D document.createElement("ChapterAtom=
");
+		parentChapterAtom.appendChild(chapterAtomElement);
+
+		QString _myUID =3D Utilities::CreateUID();
+		chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterUID"=
, _myUID));
+		chapterAtomElement.appendChild(CreateDOMElement(document, "ChapterFlag=
Hidden", "1"));
+
+		_PrivateCN[1] =3D (position.vob_id_nr) >> 8;
+		_PrivateCN[2] =3D (position.vob_id_nr) & 0xFF;
+		_PrivateCN[3] =3D position.cell_nr;
+		_PrivateCN[4] =3D cell_num - entry_cell_num + 1; // Number of angles
+		AddCodecPrivateData(document, chapterAtomElement, _PrivateCN, 5);
+
+		if (cell.still_time =3D=3D 0xFF) {
+			chapterAtomElement.appendChild(document.createComment("Infinite loop =
Still Cell"));
+
+			// output an additional tag to specify this is cell should loop infin=
itely
+			// post-process command, jump to timecode "st_time"
+			QDomElement chapterProcessElement =3D document.createElement("Chapter=
Process");
+			chapterProcessElement.appendChild(CreateDOMElement(document, "Chapter=
ProcessCodecID", "0"));
+			chapterProcessElement.appendChild(document.createComment("Post comman=
d: replay this cell"));
+			chapterAtomElement.appendChild(chapterProcessElement);
+
+			QDomElement chapterProcessCommand =3D document.createElement("Chapter=
ProcessCommand");
+			chapterProcessCommand.appendChild(CreateDOMElement(document, "Chapter=
ProcessTime", "2"));
+			chapterProcessElement.appendChild(chapterProcessCommand);
+
+			QDomElement chapterProcessDataElement =3D CreateDOMElement(document, =
"ChapterProcessData", "GotoAndPlay(" + PgcUID + ");" );
+			chapterProcessDataElement.setAttribute("format", "ascii");
+			chapterProcessCommand.appendChild(chapterProcessDataElement);
+		} else if (cell.still_time !=3D 0)
+			chapterAtomElement.appendChild(document.createComment(QString("Still =
cell (%1s)").arg(cell.still_time)));
+
+		AddChapterTime(document, chapterAtomElement, cell_elt->start_time, cel=
l_elt->start_time + cell_elt->duration);
+
+		if (end_time < cell_elt->start_time + cell_elt->duration)
+			end_time =3D cell_elt->start_time + cell_elt->duration;
+
+		if (start_time > cell_elt->start_time)
+			start_time =3D cell_elt->start_time;
+	}
+
+	if (start_time =3D=3D uint64_t(-1))
+		start_time =3D 0; // unknown
+
+	parentChapterAtom.appendChild(CreateDOMElement(document, "ChapterTimeSt=
art", Utilities::FormatTime(start_time)));
+
+	return start_time;
+}

Added: trunk/DvdMenuXtractor/dmx/chaptermanager.h
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/DvdMenuXtractor/dmx/chaptermanager.h	2007-03-08 09:04:37 UTC (r=
ev 1280)
+++ trunk/DvdMenuXtractor/dmx/chaptermanager.h	2007-03-10 21:14:50 UTC (r=
ev 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, ui=
nt16_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;
+=09
+	static QDomElement CreateDOMElement(QDomDocument& document, const QStri=
ng& elementName, const QString& content);
+	static void AddChapterTime(QDomDocument& document, QDomNode& parent, ui=
nt64_t start_time, uint64_t end_time);
+	static void AddCodecPrivateData(QDomDocument& document, QDomNode& paren=
t, const unsigned char *buffer, unsigned int size, bool createChapterProc=
essElement =3D true);
+	static void AddPGCCommands(QDomDocument& document, QDomNode& parent, co=
nst pgc_command_tbl_t * command_tbl);
+	static uint64_t AddProgram(QDomDocument& document, QDomNode& parent, co=
nst pgc_t *pgc, const QString& PgcUID, int program_number, const CellsLis=
tType& 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 CellsListTyp=
e & cell_list, const ttu_t * ptts, int title);
+	static QString GetPGCType(unsigned char entry_id);
+	static uint64_t HandleLanguageUnit(QDomDocument& document, QDomNode& pa=
rent, const IFOFile& _ifo, int title);
+
+	uint8_t familyUID[16];
+	unsigned INDENT_COUNT;
+};
+
+#endif //CHAPTER_MANAGER_H

Added: trunk/DvdMenuXtractor/dmx/dmx.cpp
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- 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_ =3D 0;
+	mutex.unlock();
+}
+
+void DMX::abort()
+{
+	QMutex mutex;
+	mutex.lock();
+	needsAbort_ =3D true;
+	mutex.unlock();
+
+	wait();
+}
+
+void DMX::processTitle(int16_t title, int index)
+{
+	const QString editionUID =3D Utilities::CreateUID();
+
+	unsigned stepIndex =3D 0;
+	bool menu =3D ((index < 0) && !title) || ((index >=3D 0) && selection_[=
index].isMenu());
+	VobParser *aVobParser =3D 0;
+	QString str;
+	QString text =3D "Step %1 of 2: %3...";
+
+	do
+	{
+		printf("Treating Title %d %s VOB file(s)\n", title, menu ? "Menu" : ""=
);
+
+		stepIndex =3D 1;
+
+		str =3D text.arg(stepIndex++).arg("Building VOB map");
+	=09
+		if (consoleMode_)
+			printf(qPrintable(str + '\n'));
+		else
+			emit stepChanged(str);
+
+		aVobParser =3D buildVobParser(title, menu);
+
+		str =3D text.arg(stepIndex++).arg("Splitting and demuxing");
+	=09
+		if (consoleMode_)
+			printf(qPrintable(str + '\n'));
+		else
+			emit stepChanged(str);
+	=09
+		demux(aVobParser, index, editionUID, title, menu);
+
+		delete aVobParser;
+	=09
+		menu =3D !menu && ((index < 0) || selection_[index].isMenu());
+	} while (menu && !needsAbort_);
+}
+
+bool DMX::setExtractionParameters(const QString& sourcePath, const QStri=
ng& destinationPath, const QString& toolsPath, const SelectionType& selec=
tedItems)
+{
+	QMutex mutex;
+	QMutexLocker locker (&mutex);
+
+	// set selection
+	selection_ =3D selectedItems;
+
+	// check if require directories exist
+	if (!QFile::exists(sourcePath))
+	{
+		fprintf(stderr, "Input folder '%s' doesn't exist\n", qPrintable(source=
Path));
+		return false;
+	}
+
+	// set source path
+	sourcePath_ =3D sourcePath;
+
+	if (!QFile::exists(toolsPath))
+	{
+		fprintf(stderr, "Tools folder '%s' doesn't exist\n", qPrintable(toolsP=
ath));
+		return false;
+	}
+
+	// set "mkvmerge.exe" path
+	toolsPath_ =3D toolsPath;
+
+	// try to create output
+	if (!QFile::exists(destinationPath))
+	{
+		if (!QDir().mkpath(destinationPath))
+		{
+			fprintf(stderr,"Cannot create output folder: '%s'\n", qPrintable(dest=
inationPath));
+			return false;
+		}
+		else
+			printf("Successfully created output folder: '%s'\n", qPrintable(desti=
nationPath));
+	}
+
+	// set output path
+	destinationPath_ =3D destinationPath;
+
+	return true;
+}
+
+void DMX::run()
+{
+	// try to open input file
+	if (!sourcePath_.size() || !loadIFOFile(sourcePath_))
+		return;
+
+	QMutex mutex;
+	mutex.lock();
+	needsAbort_ =3D false;
+	mutex.unlock();
+
+	if (selection_.size()) // if selection is available
+	{
+		for (size_t index =3D 0; index < selection_.size(); ++index)
+		{
+			int16_t title =3D selection_[index].title();
+		=09
+			if (title >=3D 0)
+				processTitle(title, index);
+		}
+	}=20
+	else // if no selection is available, process all titles
+	{
+		for (int16_t title =3D 0; title <=3D ifoFile_->NumberOfTitles(); ++tit=
le)
+			processTitle(title, -1);
+	}
+}
+
+IFOFile* DMX::OpenIFOFile(const QString& path)
+{
+	IFOFile *file =3D 0;
+	QDir info (path);
+=09
+	try
+	{
+		file =3D 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);
+=09
+	// reset last file
+	delete ifoFile_;
+	ifoFile_ =3D OpenIFOFile(path);
+
+	if (ifoFile_ =3D=3D 0)
+		return false;
+
+	return true;
+}
+
+VobParser* DMX::buildVobParser(int16_t title, bool menu)
+{
+	VobParser* parser =3D 0;
+	if (consoleMode_)
+		printf("0%%");
+	else
+		emit progressChanged(0);
+=09
+	try=20
+	{
+		parser =3D new VobParser(qPrintable(sourcePath_), title, menu);
+	} catch (...)
+	{
+		if (consoleMode_)
+			printf("\r\r");
+	=09
+		printf("No VOB file(s) found in %s for Title %d\n", qPrintable(sourceP=
ath_), title);
+	}
+
+	if (consoleMode_)
+		printf("\r\r100%%\n");
+	else
+		emit progressChanged(100);
+=09
+	return parser;
+}
+
+void DMX::demuxAudioTrack(int16_t title, bool isMenu, const AudioTrackLi=
st& _audioTracks, size_t _stream, CompositeDemuxWriter& demuxer, const QS=
tring& filename)
+{
+	if (_stream < 0 || _stream >=3D _audioTracks.size())
+		return;
+
+	static const QString muxArgumentsFormat(" --track-name 0:\"aud-%1\" --l=
anguage 0:%2 --timecodes 0:\"%3_%4.tmc\" \"%3.%4\"");
+
+	const audio_attr_t *_attr =3D _audioTracks[_stream];
+	// get the possible ID for this track for this Cell/PGC
+	uint8_t _ID =3D ifoFile_->GetAudioId(_stream, title, isMenu);
+
+	Writer *_muxer =3D 0;
+=09
+	// if (_attr->lang_code =3D=3D 0)
+	QString lang ("und");
+	QString langSuffix (QString("_%1_un").arg(_ID));
+=09
+	if (_attr->lang_code !=3D 0)
+	{
+		lang =3D QString("%1%2").arg(QString(_attr->lang_code >> 8), QString(_=
attr->lang_code & 0xFF));
+		langSuffix =3D 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 + l=
angSuffix);
+
+	switch (_audioTracks[_stream]->audio_format)
+	{
+	case 0:
+		muxArguments =3D muxArgumentsFormat.arg(filename + langSuffix, lang, f=
ullPrefix, "ac3");	=09
+		_muxer =3D new AC3DemuxWriter(fullPrefix, 0x80 + _ID);
+
+		if (!demuxer.AddDemuxer(SUBSTREAM_AC3_LOW + _ID, _muxer, muxArguments)=
)
+			delete _muxer;
+		break;
+
+	case 2:
+	case 3:
+		muxArguments =3D muxArgumentsFormat.arg(filename + langSuffix, lang, f=
ullPrefix, "mpa");	=09
+		_muxer =3D new MPADemuxWriter(fullPrefix, 0xC0 + _ID);
+
+		if (!demuxer.AddDemuxer(AUDIO_STREAM + _ID, _muxer, muxArguments))
+			delete _muxer;
+		break;
+
+	case 4:
+		// TODO possibly other LPCM formats ?
+		muxArguments =3D muxArgumentsFormat.arg(filename + langSuffix, lang, f=
ullPrefix, "wav");
+		_muxer =3D new LPCMDemuxWriter(fullPrefix, 0xA0 + _ID, 48000, 16, 2);
+
+		if (!demuxer.AddDemuxer(SUBSTREAM_PCM_LOW + _ID, _muxer, muxArguments)=
)
+			delete _muxer;
+		break;
+
+	case 6:
+		muxArguments =3D muxArgumentsFormat.arg(filename + langSuffix, lang, f=
ullPrefix, "dts");
+		_muxer =3D 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 SubtitleT=
rackList& _subTracks, size_t _stream,  CompositeDemuxWriter& demuxer, con=
st QString& filename, const uint32_t *_palette, uint16_t _width, uint16_t=
 _height)
+{
+	if (_stream < 0 || _stream >=3D _subTracks.size())
+		return;
+
+	static const QString subMuxArgumentsFormat(" --track-name 0:\"sub-%1\" =
--language 0:%2 \"%3\"");
+
+	const subp_attr_t *_attr =3D 0;
+	const QString prefix =3D destinationPath_ + QDir::separator() + filenam=
e;
+
+	// get the possible IDs for this track for this Cell/PGC
+	IdArray _IDs =3D ifoFile_->GetSubsId(_stream, title, isMenu);
+
+	Writer * _muxer =3D 0;
+	QString lang, langSuffix;
+=09
+	for (IdArray::size_type _IDidx=3D0; _IDidx < _IDs.size(); ++_IDidx)
+	{
+		_attr =3D _subTracks[_stream];
+	=09
+		// if (_attr->lang_code =3D=3D 0)
+		lang =3D "und";
+		langSuffix =3D QString("_%1_un").arg(_IDs.at(_IDidx));
+
+		if (_attr->lang_code !=3D 0)
+		{
+			lang =3D QString("%1%2 \"").arg(QString(_attr->lang_code >> 8), QStri=
ng(_attr->lang_code & 0xFF));
+			langSuffix =3D QString("_%1_%2%3").arg(QString::number(_IDs.at(_IDidx=
)), QString(_attr->lang_code >> 8), QString(_attr->lang_code & 0xFF));	=09
+		}
+
+		_muxer =3D new SubDemuxWriter(QString(prefix + langSuffix), 0x20 + _ID=
s.at(_IDidx), _width, _height, _palette, _attr->lang_code, _attr->lang_ex=
tension =3D=3D 9);
+
+		QString commandLine =3D subMuxArgumentsFormat.arg(filename + langSuffi=
x, lang, prefix + langSuffix + ".idx");
+		if (!demuxer.AddDemuxer(SUBSTREAM_SUB_LOW + _IDs.at(_IDidx), _muxer, c=
ommandLine))
+			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 =3D ifoFile_->GetCellsList(title, menu);
+=09
+	if (!CellsList)
+		return;
+
+	static const QString demuxArguments (" --track-name 0:\"video\" --timec=
odes 0:\"%1_m2v.tmc\" \"%1.m2v\"");
+	static const QString btnMuxArgumentsFormat(" --track-name 0:\"btn-%1\" =
--timecodes 0:\"%2_btn.tmc\" \"%2.btn\"");
+=09
+	try
+	{
+		QString filename;
+		QString muxCommand;
+
+		// init filename
+		if (title =3D=3D 0)
+			filename =3D "VMG";
+		else
+			filename =3D QString("VTS%1%2").arg(menu ? "M" : "").arg(title, 2, 10=
, QChar('0'));
+
+		const QString prefix =3D destinationPath_ + QDir::separator() + filena=
me;
+
+		if (aVobParser !=3D 0)
+		{
+			aVobParser->Reset();
+
+			const AudioTrackList & _audioTracks =3D ifoFile_->AudioTracks(title, =
menu);
+			const SubtitleTrackList & _subTracks =3D ifoFile_->SubsTracks(title, =
menu);
+		=09
+			double _fps =3D 0;
+			uint16_t _width =3D 0, _height =3D 0;
+		=09
+			ifoFile_->VideoSize(title, menu, _width, _height, _fps);
+			const uint32_t * _palette =3D ifoFile_->GetPalette(title, menu);
+
+			CompositeDemuxWriter &demuxer =3D aVobParser->GetDemuxer();
+
+			//emit progressChanged(CellsList->count());
+
+			demuxer.Reset();
+			printf("Processing %s\n", qPrintable(filename));
+
+			if ((selectionIndex < 0) || (selection_[selectionIndex].isVideoEnable=
d()))
+			{
+				Writer *_muxer =3D new VideoDemuxWriter(prefix, _fps);
+
+				QString commandLine =3D demuxArguments.arg(prefix);
+				if (!demuxer.AddDemuxer(VIDEO_STREAM, _muxer, commandLine))
+					delete _muxer;
+			}
+
+			size_t _stream =3D 0;
+
+			QString lang;
+			QString langSuffix;
+
+			// decide which audio streams to process
+			if (_audioTracks.size())
+			{
+				if (selectionIndex > -1)
+				{
+					const std::vector<size_t>& selectedAudioTracks =3D selection_[selec=
tionIndex].audioTracks();
+				=09
+					for (size_t index =3D 0; index < selectedAudioTracks.size(); ++inde=
x)
+					{
+						_stream =3D selectedAudioTracks[index];
+						demuxAudioTrack(title, menu, _audioTracks, _stream,  demuxer, file=
name);
+					}
+				}
+				else
+				{
+					for (_stream =3D 0; _stream < _audioTracks.size(); ++_stream)
+						demuxAudioTrack(title, menu, _audioTracks, _stream, demuxer, filen=
ame);
+				}
+			}
+
+			// process subtitle streams
+			if (_subTracks.size())
+			{
+				if (selectionIndex > -1)
+				{
+					const std::vector<size_t>& selectedSubTracks =3D selection_[selecti=
onIndex].subtitleTracks();
+
+					for (size_t index =3D 0; index < selectedSubTracks.size(); ++index)
+					{
+						_stream =3D selectedSubTracks[index];
+						demuxSubtitleTrack(title, menu, _subTracks, _stream,  demuxer, fil=
ename, _palette, _width, _height);
+					}
+				}
+				else
+				{
+					for (_stream =3D 0; _stream < _subTracks.size(); ++_stream)
+						demuxSubtitleTrack(title, menu, _subTracks, _stream, demuxer, file=
name, _palette, _width, _height);
+				}
+
+				// create a possible button demuxer too
+				Writer *_muxer =3D new BtnDemuxWriter(prefix, _width, _height);
+
+				QString commandLine =3D btnMuxArgumentsFormat.arg(filename, prefix);
+				if (!demuxer.AddDemuxer(SUBSTREAM_PCI, _muxer, commandLine))
+					delete _muxer;
+			}
+
+			if (consoleMode_)
+				printf("0%%");
+			else
+				emit progressChanged(0);
+		=09
+			const uint32_t maximum =3D aVobParser->GetPacketCount();
+		=09
+			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 +=3D demuxer.GetString(VIDEO_STREAM);
+
+			for (_stream =3D 0; _stream < 256; _stream++)
+			{
+				if (_stream =3D=3D VIDEO_STREAM)
+					continue;
+			=09
+				if (demuxer.FileExists(_stream))
+					muxCommand +=3D demuxer.GetString(_stream);
+			}
+		}
+
+		CellsListType *CellsListDone =3D (CellsListType *)CellsList;
+		CellsListDone->arrange();
+
+		bool addChapters =3D false;
+		ChapterManager chapterEditor(2 /*indent count*/);
+
+		if (menu)
+			addChapters =3D chapterEditor.generateMenuScript(*ifoFile_, prefix, t=
itle, editionUID);
+		else
+			addChapters =3D chapterEditor.generateScript(*ifoFile_, prefix, title=
, editionUID);
+
+		if (addChapters)
+		{
+			muxCommand +=3D " --chapters \"" + prefix + ChapterManager::CHAPTER_S=
UFFIX + "\"";
+			muxCommand +=3D " --segmentinfo \"" + prefix + ChapterManager::INFO_S=
UFFIX + "\"";
+		}
+
+#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");
+	=09
+#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
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- 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 =3D false);
+  ~DMX();
+
+	static IFOFile* OpenIFOFile(const QString& path);
+	bool setExtractionParameters(const QString& sourcePath, const QString& =
destinationPath, const QString& toolsPath, const SelectionType& selection=
);
+=09
+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_;
+=09
+	bool loadIFOFile(const QString& path);
+	void processTitle(int16_t title, int index);
+
+	VobParser* buildVobParser(int16_t title, bool isMenu);
+=09
+	void demux(VobParser* aVobParser, int selectionIndex, const QString& ed=
itionUID, int16_t title, bool isMenu);
+	void demuxAudioTrack(int16_t title, bool isMenu, const AudioTrackList& =
_audioTracks, size_t _stream, CompositeDemuxWriter& demuxer, const QStrin=
g& filename);
+	void demuxSubtitleTrack(int16_t title, bool isMenu, const SubtitleTrack=
List& _subTracks, size_t _stream,  CompositeDemuxWriter& demuxer, const Q=
String& filename, const uint32_t *_palette, uint16_t _width, uint16_t _he=
ight);
+};
+
+#endif // DMX_H

Added: trunk/DvdMenuXtractor/dmx/dmx.proj
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- 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
+ =20
+  USE dvdread
+  USE vobparser
+  USE mpegparser
+ =20
+  DEFINE __STDC_LIMIT_MACROS
+  DEFINE(QT_NO_DEBUG) QT_NO_DEBUG_STREAM
+
+  INCLUDE libdvdread
+ =20
+  INCLUDE(COMPILER_MSVC) libdvdread/win32
+  INCLUDE(COMPILER_MSVC) "$(QTDIR)/include"
+  INCLUDE(COMPILER_MSVC) "$(QTDIR)/include/QtXml"
+  INCLUDE(COMPILER_MSVC) "$(QTDIR)/include/QtCore"
+ =20
+  INCLUDE(COMPILER_GCC)  "$(QTDIR)/include/qt4/"
+  INCLUDE(COMPILER_GCC)  "$(QTDIR)/include/qt4/QtXml"
+  INCLUDE(COMPILER_GCC)  "$(QTDIR)/include/qt4/QtCore"
+ =20
+  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
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- 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
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/DvdMenuXtractor/dmx/dmxselectionitem.cpp	2007-03-08 09:04:37 UT=
C (rev 1280)
+++ trunk/DvdMenuXtractor/dmx/dmxselectionitem.cpp	2007-03-10 21:14:50 UT=
C (rev 1281)
@@ -0,0 +1,68 @@
+#include <algorithm>
+#include "dmxselectionitem.h"
+
+DMXSelectionItem::DMXSelectionItem(int16_t title, bool isMenu, bool enab=
leVideo =3D 1)
+	: title_(title), menu_(isMenu), videoEnabled_(enableVideo)
+{
+	// override for VMG
+	if (!title)
+		menu_ =3D 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::audioTrac=
ks() const
+{
+	return audioTracks_;
+}
+
+const DMXSelectionItem::TracksContainerType& DMXSelectionItem::subtitleT=
racks() const
+{
+	return subtitleTracks_;
+}
+
+void DMXSelectionItem::disableVideo()
+{
+	videoEnabled_ =3D false;
+}
+
+bool DMXSelectionItem::addAudioTrack(size_t trackIndex)
+{
+	// check if audio track was already selected
+	if (std::count(audioTracks_.begin(), audioTracks_.end(), trackIndex) =3D=
=3D 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(), trackInd=
ex) =3D=3D 0)
+	{
+		subtitleTracks_.push_back(trackIndex);
+		return true;
+	}
+
+	return false;
+}

Added: trunk/DvdMenuXtractor/dmx/dmxselectionitem.h
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- 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
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- 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 =3D a_time % 1000000000;
+	a_time /=3D 1000000000;
+	sec =3D a_time % 60;
+	a_time /=3D 60;
+	min =3D a_time % 60;
+	hour =3D 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 s=
ize)
+{
+	QString result;
+
+	for (unsigned i =3D 0; i < size; ++i)
+		result +=3D QString("%1 ").arg(buffer[i], 2, 16, QChar('0'));
+
+	return result.trimmed();
+}

Added: trunk/DvdMenuXtractor/dmx/utilities.h
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/DvdMenuXtractor/dmx/utilities.h	2007-03-08 09:04:37 UTC (rev 12=
80)
+++ trunk/DvdMenuXtractor/dmx/utilities.h	2007-03-10 21:14:50 UTC (rev 12=
81)
@@ -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 fai=
r-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