NOTE: This patch is taken from http://sourceforge.net/forum/forum.php?thread_id=1837284&forum_id=59136
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/Makefile.am lib/mp4v2/Makefile.am
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/Makefile.am 2007-09-19 05:51:59 +0900
+++ lib/mp4v2/Makefile.am 2008-05-30 22:51:27 +0900
@@ -13,6 +13,7 @@
atom_amr.cpp \
atom_avc1.cpp \
atom_avcC.cpp \
+ atom_chpl.cpp \
atom_d263.cpp \
atom_damr.cpp \
atom_dref.cpp \
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/atom_chpl.cpp lib/mp4v2/atom_chpl.cpp
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/atom_chpl.cpp 1970-01-01 09:00:00 +0900
+++ lib/mp4v2/atom_chpl.cpp 2008-05-30 22:51:27 +0900
@@ -0,0 +1,59 @@
+/*
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is MPEG4IP.
+ *
+ * The Initial Developer of the Original Code is Cisco Systems Inc.
+ * Portions created by Cisco Systems Inc. are
+ * Copyright (C) Cisco Systems Inc. 2001. All Rights Reserved.
+ *
+ * Contributed to MPEG4IP
+ * by Ullrich Pollaehne
+ *
+ * Nero chapter atom
+ */
+
+#include "mp4common.h"
+
+// MP4ChplAtom is for Nero chapter list atom which is a child of udta
+MP4ChplAtom::MP4ChplAtom () : MP4Atom("chpl")
+{
+ // it is not completely clear if version, flags, reserved and chaptercount
+ // have the right sizes but
+ // one thing is clear: chaptercount is not only 8-bit it is at least 16-bit
+
+ // add the version
+ AddVersionAndFlags();
+
+ // add reserved bytes
+ AddReserved("reserved", 1);
+
+ // define the chaptercount
+ MP4Integer32Property * counter = new MP4Integer32Property("chaptercount");
+ AddProperty(counter);
+
+ // define the chapterlist
+ MP4TableProperty * list = new MP4TableProperty("chapters", counter);
+
+ // the start time as 100 nanoseconds units
+ list->AddProperty(new MP4Integer64Property("starttime"));
+
+ // the chapter name as UTF-8
+ list->AddProperty(new MP4StringProperty("name", true));
+
+ // add the chapterslist
+ AddProperty(list);
+}
+
+void MP4ChplAtom::Generate ()
+{
+ SetVersion(1);
+}
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/atom_udta.cpp lib/mp4v2/atom_udta.cpp
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/atom_udta.cpp 2006-02-23 09:28:57 +0900
+++ lib/mp4v2/atom_udta.cpp 2008-05-30 22:51:27 +0900
@@ -24,6 +24,7 @@
MP4UdtaAtom::MP4UdtaAtom()
: MP4Atom("udta")
{
+ ExpectChildAtom("chpl", Optional, OnlyOne);
ExpectChildAtom("cprt", Optional, Many);
ExpectChildAtom("hnti", Optional, OnlyOne);
ExpectChildAtom("meta", Optional, OnlyOne);
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/atoms.h lib/mp4v2/atoms.h
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/atoms.h 2007-09-19 05:51:59 +0900
+++ lib/mp4v2/atoms.h 2008-05-30 22:51:27 +0900
@@ -405,4 +405,10 @@
void Generate(void);
};
+class MP4ChplAtom : public MP4Atom {
+ public:
+ MP4ChplAtom();
+ void Generate(void);
+};
+
#endif /* __MP4_ATOMS_INCLUDED__ */
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/libmp4v2.vcproj lib/mp4v2/libmp4v2.vcproj
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/libmp4v2.vcproj 2007-09-19 05:51:59 +0900
+++ lib/mp4v2/libmp4v2.vcproj 2008-05-30 22:51:27 +0900
@@ -163,6 +163,10 @@
>
+
+
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/mp4.cpp lib/mp4v2/mp4.cpp
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/mp4.cpp 2007-09-29 05:45:11 +0900
+++ lib/mp4v2/mp4.cpp 2008-05-30 22:51:27 +0900
@@ -1116,11 +1116,13 @@
}
extern "C" MP4TrackId MP4AddChapterTextTrack(
- MP4FileHandle hFile, MP4TrackId refTrackId)
+ MP4FileHandle hFile,
+ MP4TrackId refTrackId,
+ u_int32_t timescale)
{
if (MP4_IS_VALID_FILE_HANDLE(hFile)) {
try {
- return ((MP4File*)hFile)->AddChapterTextTrack(refTrackId);
+ return ((MP4File*)hFile)->AddChapterTextTrack(refTrackId, timescale);
}
catch (MP4Error* e) {
PRINT_ERROR(e);
@@ -1131,6 +1133,93 @@
}
+extern "C" void MP4AddQTChapter(
+ MP4FileHandle hFile,
+ MP4TrackId chapterTrackId,
+ MP4Duration chapterDuration,
+ u_int32_t chapterNr,
+ const char * chapterTitle)
+{
+ if (MP4_IS_VALID_FILE_HANDLE(hFile)) {
+ try {
+ ((MP4File*)hFile)->AddChapter(chapterTrackId, chapterDuration, chapterNr, chapterTitle);
+ }
+ catch (MP4Error* e) {
+ PRINT_ERROR(e);
+ delete e;
+ }
+ }
+}
+
+
+extern "C" void MP4AddChapter(
+ MP4FileHandle hFile,
+ MP4Timestamp chapterStart,
+ const char * chapterTitle)
+{
+ if (MP4_IS_VALID_FILE_HANDLE(hFile)) {
+ try {
+ ((MP4File*)hFile)->AddChapter(chapterStart, chapterTitle);
+ }
+ catch (MP4Error* e) {
+ PRINT_ERROR(e);
+ delete e;
+ }
+ }
+}
+
+
+extern "C" void MP4ConvertChapters(
+ MP4FileHandle hFile,
+ bool toQT)
+{
+ if (MP4_IS_VALID_FILE_HANDLE(hFile)) {
+ try {
+ ((MP4File*)hFile)->ConvertChapters(toQT);
+ }
+ catch (MP4Error* e) {
+ PRINT_ERROR(e);
+ delete e;
+ }
+ }
+}
+
+
+extern "C" void MP4DeleteChapters(
+ MP4FileHandle hFile,
+ MP4TrackId chapterTrackId,
+ bool deleteQT)
+{
+ if (MP4_IS_VALID_FILE_HANDLE(hFile)) {
+ try {
+ ((MP4File*)hFile)->DeleteChapters(chapterTrackId, deleteQT);
+ }
+ catch (MP4Error* e) {
+ PRINT_ERROR(e);
+ delete e;
+ }
+ }
+}
+
+
+extern "C" void MP4GetChaptersList(
+ MP4FileHandle hFile,
+ MP4Chapters_t ** chapterList,
+ u_int32_t * chapterCount,
+ bool getQT)
+{
+ if (MP4_IS_VALID_FILE_HANDLE(hFile)) {
+ try {
+ ((MP4File*)hFile)->GetChaptersList(chapterList, chapterCount, getQT);
+ }
+ catch (MP4Error* e) {
+ PRINT_ERROR(e);
+ delete e;
+ }
+ }
+}
+
+
extern "C" MP4TrackId MP4CloneTrack (MP4FileHandle srcFile,
MP4TrackId srcTrackId,
MP4FileHandle dstFile,
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/mp4.h lib/mp4v2/mp4.h
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/mp4.h 2007-09-29 05:45:11 +0900
+++ lib/mp4v2/mp4.h 2008-05-30 22:51:27 +0900
@@ -304,6 +304,15 @@
#define MPEG4_FGSP_L4 (0xfc)
#define MPEG4_FGSP_L5 (0xfd)
+/* chapter related definitions */
+#define CHAPTERTITLELEN 1023
+typedef struct MP4ChapterStruct {
+ MP4Duration duration; /* duration of a chapter in milliseconds*/
+ char title[CHAPTERTITLELEN+1]; /* title of the chapter */
+} MP4Chapters_t;
+/* milliseconds to 100 nanoseconds */
+#define MILLI2HUNDREDNANO 10000
+
/* MP4 API declarations */
#ifdef __cplusplus
@@ -599,7 +608,35 @@
MP4TrackId MP4AddChapterTextTrack(
MP4FileHandle hFile,
- MP4TrackId refTrackId);
+ MP4TrackId refTrackId,
+ u_int32_t timescale DEFAULT(0));
+
+void MP4AddQTChapter(
+ MP4FileHandle hFile,
+ MP4TrackId chapterTrackId,
+ MP4Duration chapterDuration,
+ u_int32_t chapterNr,
+ const char * chapterTitle DEFAULT(0));
+
+void MP4AddChapter(
+ MP4FileHandle hFile,
+ MP4Timestamp chapterStart,
+ const char * chapterTitle DEFAULT(0));
+
+void MP4ConvertChapters(
+ MP4FileHandle hFile,
+ bool toQT DEFAULT(true));
+
+void MP4DeleteChapters(
+ MP4FileHandle hFile,
+ MP4TrackId chapterTrackId DEFAULT(MP4_INVALID_TRACK_ID),
+ bool deleteQT DEFAULT(true));
+
+void MP4GetChaptersList(
+ MP4FileHandle hFile,
+ MP4Chapters_t ** chapterList,
+ u_int32_t * chapterCount,
+ bool getQT DEFAULT(true));
MP4TrackId MP4CloneTrack(
MP4FileHandle srcFile,
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/mp4atom.cpp lib/mp4v2/mp4atom.cpp
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/mp4atom.cpp 2007-05-01 05:29:28 +0900
+++ lib/mp4v2/mp4atom.cpp 2008-05-30 22:51:27 +0900
@@ -93,6 +93,8 @@
case 'c':
if (ATOMID(type) == ATOMID("chap")) {
pAtom = new MP4TrefTypeAtom(type);
+ } else if (ATOMID(type) == ATOMID("chpl")) {
+ pAtom = new MP4ChplAtom();
}
break;
case 'd':
@@ -269,11 +271,13 @@
static const char cpy[5]={0251,'c', 'p', 'y', '\0'};
static const char des[5]={0251,'d', 'e', 's','\0'};
static const char prd[5]={0251, 'p', 'r', 'd', '\0'};
+ static const char lyr[5]={0251, 'l', 'y', 'r', '\0'};
if (ATOMID(type) == ATOMID(name) ||
ATOMID(type) == ATOMID(cmt) ||
ATOMID(type) == ATOMID(cpy) ||
ATOMID(type) == ATOMID(prd) ||
- ATOMID(type) == ATOMID(des)) {
+ ATOMID(type) == ATOMID(des) ||
+ ATOMID(type) == ATOMID(lyr)) {
pAtom = new MP4Meta2Atom(type);
}
break;
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/mp4file.cpp lib/mp4v2/mp4file.cpp
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/mp4file.cpp 2007-09-29 05:45:11 +0900
+++ lib/mp4v2/mp4file.cpp 2008-05-30 22:51:27 +0900
@@ -2124,13 +2124,16 @@
return trackId;
}
-MP4TrackId MP4File::AddChapterTextTrack(MP4TrackId refTrackId)
+MP4TrackId MP4File::AddChapterTextTrack(MP4TrackId refTrackId, u_int32_t timescale)
{
// validate reference track id
(void)FindTrackIndex(refTrackId);
- MP4TrackId trackId =
- AddTrack(MP4_TEXT_TRACK_TYPE, GetTrackTimeScale(refTrackId));
+ if (0 == timescale) {
+ timescale = GetTrackTimeScale(refTrackId);
+ }
+
+ MP4TrackId trackId = AddTrack(MP4_TEXT_TRACK_TYPE, timescale);
(void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "gmhd", 0);
@@ -2168,6 +2171,340 @@
return trackId;
}
+void MP4File::AddChapter(MP4TrackId chapterTrackId, MP4Duration chapterDuration, u_int32_t chapterNr, const char * chapterTitle)
+{
+ if (0 == chapterTrackId) {
+ throw new MP4Error("No chapter track given","AddChapter");
+ }
+
+ uint32_t sampleLength = 0;
+ uint8_t sample[1040] = {0};
+ int stringLen = 0;
+ char *string = (char *)&(sample[2]);
+
+ if( chapterTitle != NULL )
+ {
+ stringLen = strlen(chapterTitle);
+ strncpy( string, chapterTitle, MIN(stringLen, 1023) );
+ }
+
+ if( stringLen == 0 || stringLen >= 1024 )
+ {
+ snprintf( string, 1023, "Chapter %03i", chapterNr );
+ stringLen = strlen(string);
+ }
+
+ sampleLength = stringLen + 2 + 12; // Account for text length code and other marker
+
+ // 2-byte length marker
+ sample[0] = (stringLen >> 8) & 0xff;
+ sample[1] = stringLen & 0xff;
+
+ int x = 2 + stringLen;
+
+ // Modifier Length Marker
+ sample[x] = 0x00;
+ sample[x+1] = 0x00;
+ sample[x+2] = 0x00;
+ sample[x+3] = 0x0C;
+
+ // Modifier Type Code
+ sample[x+4] = 'e';
+ sample[x+5] = 'n';
+ sample[x+6] = 'c';
+ sample[x+7] = 'd';
+
+ // Modifier Value
+ sample[x+8] = 0x00;
+ sample[x+9] = 0x00;
+ sample[x+10] = (256 >> 8) & 0xff;
+ sample[x+11] = 256 & 0xff;
+
+ WriteSample(chapterTrackId, sample, sampleLength, chapterDuration);
+}
+
+void MP4File::AddChapter(MP4Timestamp chapterStart, const char * chapterTitle)
+{
+ MP4Atom * pChpl = FindAtom("moov.udta.chpl");
+ if (!pChpl) {
+ pChpl = AddDescendantAtoms("", "moov.udta.chpl");
+ }
+
+ char buffer[256];
+ int bufferLen = 0;
+
+ MP4Integer32Property * pCount = (MP4Integer32Property*)pChpl->GetProperty(3);
+ pCount->IncrementValue();
+ u_int32_t count = pCount->GetValue();
+
+ if (0 == chapterTitle) {
+ snprintf( buffer, 255, "Chapter %03i", count );
+ } else {
+ int len = MIN(255, strlen(chapterTitle));
+ strncpy( buffer, chapterTitle, len );
+ buffer[len] = 0;
+ }
+ bufferLen = strlen(buffer);
+
+ MP4TableProperty * pTable;
+ if (pChpl->FindProperty("chpl.chapters", (MP4Property **)&pTable)) {
+ MP4Integer64Property * pStartTime = (MP4Integer64Property *) pTable->GetProperty(0);
+ MP4StringProperty * pName = (MP4StringProperty *) pTable->GetProperty(1);
+ if (pStartTime && pTable) {
+ pStartTime->AddValue(chapterStart);
+ pName->AddValue(buffer);
+ }
+ }
+}
+
+void MP4File::ConvertChapters(bool toQT)
+{
+ if (toQT) {
+ MP4Chapters_t * chapters = 0;
+ u_int32_t chapterCount = 0;
+ const char * name = 0;
+ MP4Duration chapterDurationSum = 0;
+
+ GetChaptersList(&chapters, &chapterCount, false);
+ if (0 == chapterCount) {
+ throw new MP4Error("Could not find chapter markers", "ConvertChapters");
+ }
+
+ // remove chapter track if there is an existing one
+ DeleteChapters();
+
+ // create the chapter track
+ MP4TrackId refTrack = FindTrackId(0, MP4_AUDIO_TRACK_TYPE);
+ MP4TrackId chapterTrack = AddChapterTextTrack(refTrack, MP4_MILLISECONDS_TIME_SCALE);
+
+ // calculate the duration of the chapter track
+ MP4Duration chapterTrackDuration = MP4ConvertTime(GetTrackDuration(refTrack),
+ GetTrackTimeScale(refTrack),
+ MP4_MILLISECONDS_TIME_SCALE);
+
+ for (u_int32_t chapterIndex = 0 ; chapterIndex < chapterCount; ++chapterIndex) {
+ // calculate the duration
+ MP4Duration duration = chapters[chapterIndex].duration;
+
+ // sum up the chapter duration
+ chapterDurationSum += duration;
+
+ // create and write the chapter track sample for the previous chapter
+ AddChapter( chapterTrack, duration, chapterIndex+1, chapters[chapterIndex].title );
+ }
+
+ MP4Free(chapters);
+ } else {
+ MP4Chapters_t * chapters = 0;
+ u_int32_t chapterCount = 0;
+
+ GetChaptersList(&chapters, &chapterCount);
+ if (0 == chapterCount) {
+ throw new MP4Error("Could not find chapter markers", "ConvertChapters");
+ }
+
+ // remove existing chapters
+ DeleteChapters(0, false);
+
+ MP4Duration startTime = 0;
+ for (u_int32_t i = 0; i < chapterCount; ++i) {
+ const char * title = chapters[i].title;
+ MP4Duration duration = chapters[i].duration;
+
+ AddChapter(startTime, title);
+ startTime += duration * MILLI2HUNDREDNANO;
+ }
+
+ MP4Free(chapters);
+ }
+}
+
+void MP4File::DeleteChapters(MP4TrackId chapterTrackId, bool deleteQT)
+{
+ if (!deleteQT) {
+ MP4Atom * pChpl = FindAtom("moov.udta.chpl");
+ if (pChpl) {
+ MP4Atom * pParent = pChpl->GetParentAtom();
+ pParent->DeleteChildAtom(pChpl);
+ }
+ return;
+ }
+
+ char trackName[128] = {0};
+
+ // no text track given, find a suitable
+ if (0 == chapterTrackId) {
+ chapterTrackId = FindChapterTrack(trackName, 127);
+ } else {
+ FindChapterReferenceTrack(chapterTrackId, trackName, 127);
+ }
+
+ if (0 != chapterTrackId && 0 != trackName[0]) {
+ // remove the reference
+ RemoveTrackReference(trackName, chapterTrackId);
+
+ // remove the chapter track
+ DeleteTrack(chapterTrackId);
+ }
+}
+
+void MP4File::GetChaptersList(MP4Chapters_t ** chapterList,
+ u_int32_t * chapterCount,
+ bool getQT)
+{
+ *chapterList = 0;
+ *chapterCount = 0;
+
+ if (!getQT) {
+ MP4Atom * pChpl = FindAtom("moov.udta.chpl");
+ if (!pChpl) {
+ throw new MP4Error("Atom moov.udta.chpl does not exist ", "GetChaptersList");
+ }
+
+ MP4Integer32Property * pCounter = 0;
+ MP4TableProperty * pTable = 0;
+ MP4Integer64Property * pStartTime = 0;
+ MP4StringProperty * pName = 0;
+ MP4Duration chapterDurationSum = 0;
+ const char * name = 0;
+
+ if (!pChpl->FindProperty("chpl.chaptercount", (MP4Property **)&pCounter)) {
+ throw new MP4Error("Chapter count does not exist ", "GetChaptersList");
+ }
+
+ u_int32_t counter = pCounter->GetValue();
+ if (0 == counter) {
+ return;
+ }
+
+ if (!pChpl->FindProperty("chpl.chapters", (MP4Property **)&pTable)) {
+ throw new MP4Error("Chapter list does not exist ", "GetChaptersList");
+ }
+
+ if (0 == (pStartTime = (MP4Integer64Property *) pTable->GetProperty(0))) {
+ throw new MP4Error("List of Chapter starttimes does not exist ", "GetChaptersList");
+ }
+ if (0 == (pName = (MP4StringProperty *) pTable->GetProperty(1))) {
+ throw new MP4Error("List of Chapter titles does not exist ", "GetChaptersList");
+ }
+
+ MP4Chapters_t * chapters = (MP4Chapters_t*)MP4Malloc(sizeof(MP4Chapters_t) * counter);
+
+ // get the name of the first chapter
+ name = pName->GetValue();
+
+ // process remaining chapters
+ u_int32_t i, j;
+ for (i = 0, j = 1; i < counter; ++i, ++j) {
+ // insert the chapter title
+ u_int32_t len = MIN(strlen(name), CHAPTERTITLELEN);
+ strncpy(chapters[i].title, name, len);
+ chapters[i].title[len] = 0;
+
+ // calculate the duration
+ MP4Duration duration = 0;
+ if (j < counter) {
+ duration = MP4ConvertTime(pStartTime->GetValue(j),
+ (MP4_NANOSECONDS_TIME_SCALE / 100),
+ MP4_MILLISECONDS_TIME_SCALE) - chapterDurationSum;
+
+ // now get the name of the chapter (to be written next)
+ name = pName->GetValue(j);
+ } else {
+ // last chapter
+ duration = MP4ConvertTime(GetDuration(), GetTimeScale(), MP4_MILLISECONDS_TIME_SCALE) - chapterDurationSum;
+ }
+
+ // sum up the chapter duration
+ chapterDurationSum += duration;
+
+ // insert the chapter duration
+ chapters[i].duration = duration;
+ }
+
+ *chapterList = chapters;
+ *chapterCount = counter;
+
+ // ok, we're done
+ return;
+ }
+
+
+ u_int8_t * sample = 0;
+ u_int32_t sampleSize = 0;
+ MP4Timestamp startTime = 0;
+ MP4Duration duration = 0;
+
+ // get the chapter track
+ MP4TrackId chapterTrackId = FindChapterTrack();
+ if (0 == chapterTrackId) {
+ throw new MP4Error("Could not find a chapter track", "GetChaptersList");
+ }
+
+ // get infos about the chapters
+ MP4Track * pChapterTrack = GetTrack(chapterTrackId);
+ u_int32_t counter = pChapterTrack->GetNumberOfSamples();
+ u_int32_t timescale = pChapterTrack->GetTimeScale();
+
+ MP4Chapters_t * chapters = (MP4Chapters_t*)MP4Malloc(sizeof(MP4Chapters_t) * counter);
+
+ // process all chapter sample
+ for (u_int32_t i = 0; i < counter; ++i) {
+ // get the sample corresponding to the starttime
+ MP4SampleId sampleId = pChapterTrack->GetSampleIdFromTime(startTime + duration, true);
+ pChapterTrack->ReadSample(sampleId, &sample, &sampleSize);
+
+ // get the starttime and duration
+ pChapterTrack->GetSampleTimes(sampleId, &startTime, &duration);
+
+ // we know that sample+2 contains the title
+ const char * title = (const char *)&(sample[2]);
+ int len = MIN(strlen(title), CHAPTERTITLELEN);
+ strncpy(chapters[i].title, title, len);
+ chapters[i].title[len] = 0;
+
+ // write the duration (in milliseconds)
+ chapters[i].duration = MP4ConvertTime(duration, timescale, MP4_MILLISECONDS_TIME_SCALE);
+
+ // we're done with this sample
+ MP4Free(sample);
+ sample = 0;
+ }
+
+ *chapterList = chapters;
+ *chapterCount = counter;
+}
+
+MP4TrackId MP4File::FindChapterTrack(char * trackName, int trackNameSize)
+{
+ for (u_int32_t i = 0; i < m_pTracks.Size(); i++) {
+ if (!strcmp(MP4_TEXT_TRACK_TYPE, m_pTracks[i]->GetType())) {
+ MP4TrackId refTrackId = FindChapterReferenceTrack(m_pTracks[i]->GetId(), trackName, trackNameSize);
+ if (0 != refTrackId) {
+ return m_pTracks[i]->GetId();
+ }
+ }
+ }
+ return 0;
+}
+
+MP4TrackId MP4File::FindChapterReferenceTrack(MP4TrackId chapterTrackId, char * trackName, int trackNameSize)
+{
+ for (u_int32_t i = 0; i < m_pTracks.Size(); i++) {
+ if (!strcmp(MP4_AUDIO_TRACK_TYPE, m_pTracks[i]->GetType())) {
+ MP4TrackId refTrackId = m_pTracks[i]->GetId();
+ char * name = MakeTrackName(refTrackId, "tref.chap");
+ if (FindTrackReference(name, chapterTrackId)) {
+ if (0 != trackName) {
+ strncpy(trackName, name, MIN(strlen(name),trackNameSize));
+ }
+ return m_pTracks[i]->GetId();
+ }
+ }
+ }
+ return 0;
+}
+
void MP4File::DeleteTrack(MP4TrackId trackId)
{
ProtectWriteOperation("MP4DeleteTrack");
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/mp4file.h lib/mp4v2/mp4file.h
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/mp4file.h 2007-09-29 05:45:11 +0900
+++ lib/mp4v2/mp4file.h 2008-05-30 22:51:27 +0900
@@ -310,7 +310,20 @@
MP4TrackId AddHintTrack(MP4TrackId refTrackId);
MP4TrackId AddTextTrack(MP4TrackId refTrackId);
- MP4TrackId AddChapterTextTrack(MP4TrackId refTrackId);
+ MP4TrackId AddChapterTextTrack(MP4TrackId refTrackId, u_int32_t timescale = 0);
+ void AddChapter(MP4TrackId chapterTrackId,
+ MP4Duration chapterDuration,
+ u_int32_t chapterNr,
+ const char * chapterTitle = 0);
+ void AddChapter(MP4Timestamp chapterStart,
+ const char * chapterTitle = 0);
+ void ConvertChapters(bool toQT = true);
+ void DeleteChapters(MP4TrackId chapterTrackId = 0, bool deleteQT = true);
+ void GetChaptersList(MP4Chapters_t ** chapterList,
+ u_int32_t * chapterCount,
+ bool getQT = true);
+ MP4TrackId FindChapterTrack(char * trackName = 0, int trackNameSize = 0);
+ MP4TrackId FindChapterReferenceTrack(MP4TrackId chapterTrackId, char * trackName = 0, int trackNameSize = 0);
MP4SampleId GetTrackNumberOfSamples(MP4TrackId trackId);
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/util/Makefile.am lib/mp4v2/util/Makefile.am
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/util/Makefile.am 2007-09-19 05:52:00 +0900
+++ lib/mp4v2/util/Makefile.am 2008-05-30 22:51:27 +0900
@@ -3,9 +3,13 @@
AM_CXXFLAGS = @BILLS_CPPWARNINGS@
-bin_PROGRAMS = mp4dump mp4extract mp4info mp4trackdump mp4tags mp4art mp4videoinfo
+bin_PROGRAMS = mp4dump mp4extract mp4info mp4trackdump mp4tags mp4chaps mp4art mp4videoinfo
check_PROGRAMS = mp4syncfiles
+mp4chaps_SOURCES = mp4chaps.cpp
+mp4chaps_LDADD = $(top_builddir)/lib/mp4v2/libmp4v2.la \
+ $(top_builddir)/lib/gnu/libmpeg4ip_gnu.la
+
mp4dump_SOURCES = mp4dump.cpp
mp4dump_LDADD = $(top_builddir)/lib/mp4v2/libmp4v2.la \
$(top_builddir)/lib/gnu/libmpeg4ip_gnu.la
@@ -40,4 +44,4 @@
$(top_builddir)/lib/gnu/libmpeg4ip_gnu.la
EXTRA_DIST = mp4dump60.dsp \
- mp4info.dsp mp4tags.dsp mp4dump.vcproj mp4info.vcproj mp4tags.vcproj
+ mp4info.dsp mp4tags.dsp mp4dump.vcproj mp4info.vcproj mp4tags.vcproj mp4chaps.vcproj
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/util/mp4chaps.cpp lib/mp4v2/util/mp4chaps.cpp
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/util/mp4chaps.cpp 1970-01-01 09:00:00 +0900
+++ lib/mp4v2/util/mp4chaps.cpp 2008-05-30 22:51:27 +0900
@@ -0,0 +1,380 @@
+/* mp4chaps -- tool to set iTunes-compatible chapter markers from Nero chapter markers
+ *
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * Contributed to MPEG4IP
+ * by Ullrich Pollaehne
+ * with help from Handbrake
+ */
+
+#include "mp4common.h"
+#include "mp4file.h"
+#include "mp4.h"
+#include "mpeg4ip_getopt.h"
+
+/* One-letter options -- if you want to rearrange these, change them
+ here, immediately below in OPT_STRING, and in the help text. */
+#define OPT_HELP 'h'
+#define OPT_VERSION 'v'
+#define OPT_REMOVE 'r'
+#define OPT_CONVERT 'c'
+#define OPT_OPTIMIZE 'o'
+#define OPT_QT 'Q'
+#define OPT_NERO 'N'
+#define OPT_EVERY 'e'
+#define OPT_LIST 'l'
+#define OPT_STRING "hvrcoNQe:l"
+
+static const char* help_text =
+"OPTION... FILE...\n"
+"\nConvert/Manage Nero chapter markers or QT/iTunes/iPod\n"
+" chapter markers in MP4 Audio files\n"
+"\n"
+" -h, -help Display this help text and exit\n"
+" -v, -version Display version information and exit\n"
+" -r, -remove Remove chapter markers\n"
+" -c, -convert Convert chapter markers\n"
+" -o, -optimize re-write file in optimized form\n"
+" -N, -Nero write/remove/convert to Nero chapter markers\n"
+" -Q, -QuickTime write/remove/convert to QuickTime chapter markers\n"
+" -e, -every n creates chapter markers every n seconds\n"
+" -l, -list list available chapter markers\n"
+"\n";
+
+
+// function prototypes
+void ListChapters(MP4FileHandle hFile, bool useQT = true);
+int CreateQTChaptersEvery(MP4FileHandle hFile, int milliSeconds, const char * filename);
+int CreateChaptersEvery(MP4FileHandle hFile, int milliSeconds, const char * filename);
+
+
+
+int main(int argc, char** argv)
+{
+ static struct option long_options[] = {
+ { "help", 0, 0, OPT_HELP },
+ { "version", 0, 0, OPT_VERSION },
+ { "remove", 0, 0, OPT_REMOVE },
+ { "convert", 0, 0, OPT_CONVERT },
+ { "optimize", 0, 0, OPT_OPTIMIZE },
+ { "Nero", 0, 0, OPT_NERO },
+ { "QuickTime", 0, 0, OPT_QT },
+ { "every", 1, 0, OPT_EVERY },
+ { "list", 0, 0, OPT_LIST },
+ { NULL, 0, 0, 0 }
+ };
+
+
+ bool optEvery = false;
+ bool optDelete = false;
+ bool optConvert = false;
+ bool optOptimize = false;
+ bool optQT = false;
+ bool optNero = false;
+ bool optList = false;
+ bool needsModification = false;
+ u_int32_t seconds = 0;
+
+ /* Option-processing loop. */
+ int c = getopt_long_only(argc, argv, OPT_STRING, long_options, NULL);
+ while (c != -1) {
+ switch(c) {
+ /* getopt() returns '?' if there was an error. It already
+ printed the error message, so just return. */
+ case '?':
+ return 1;
+
+ /* Help and version requests handled here. */
+ case OPT_HELP:
+ fprintf(stderr, "usage %s %s", argv[0], help_text);
+ return 0;
+ case OPT_VERSION:
+ fprintf(stderr, "%s - %s version %s\n", argv[0], MPEG4IP_PACKAGE,
+ MPEG4IP_VERSION);
+ return 0;
+
+ case OPT_NERO:
+ optNero = true;
+ break;
+
+ case OPT_QT:
+ optQT = true;
+ break;
+
+ case OPT_REMOVE:
+ optDelete = true;
+ needsModification = true;
+ break;
+
+ case OPT_CONVERT:
+ optConvert = true;
+ needsModification = true;
+ break;
+
+ case OPT_OPTIMIZE:
+ optOptimize = true;
+ needsModification = true;
+ break;
+
+ case OPT_LIST:
+ optList = true;
+ break;
+
+ /* Numeric arguments: convert them using sscanf(). */
+ case OPT_EVERY:
+ {
+ int r = sscanf(optarg, "%ul", &seconds);
+ if (r < 1) {
+ fprintf(stderr, "%s: option requires numeric argument -- %c\n",
+ argv[0], c);
+ return 2;
+ }
+ optEvery = true;
+ needsModification = true;
+ }
+ break;
+
+ default:
+ break;
+ } /* end switch */
+
+ c = getopt_long_only(argc, argv, OPT_STRING, long_options, NULL);
+ } /* end while */
+
+ /* Check that we have at least one non-option argument */
+ if ((argc - optind) < 1) {
+ fprintf(stderr,
+ "%s: You must specify at least one MP4 file.\n",
+ argv[0]);
+ fprintf(stderr, "usage %s %s", argv[0], help_text);
+ return 3;
+ }
+
+ /* Loop through the non-option arguments, and do the requested chapter work */
+ int rc = 0;
+ bool needsOptimize = false;
+ while (optind < argc) {
+ char *filename = argv[optind++];
+
+ MP4FileHandle h = 0;
+ if (needsModification) {
+ h = MP4Modify( filename, MP4_DETAILS_ERROR);
+ } else {
+ h = MP4Read( filename, MP4_DETAILS_ERROR );
+ }
+
+ if (h == MP4_INVALID_FILE_HANDLE) {
+ fprintf(stderr, "Could not open '%s'... aborting\n", filename);
+ return 5;
+ }
+
+ if(optDelete) {
+ if (optQT) {
+ MP4DeleteChapters(h);
+ }
+ if (optNero) {
+ MP4DeleteChapters(h, MP4_INVALID_TRACK_ID, false);
+ }
+ if (!optQT && ! optNero) {
+ MP4DeleteChapters(h);
+ MP4DeleteChapters(h, MP4_INVALID_TRACK_ID, false);
+ }
+ }
+
+ if(optEvery) {
+ if (optQT) {
+ rc = CreateQTChaptersEvery(h, seconds*MP4_MILLISECONDS_TIME_SCALE, filename);
+ }
+ if (optNero && 0 == rc) {
+ rc = CreateChaptersEvery(h, seconds*MP4_MILLISECONDS_TIME_SCALE, filename);
+ }
+ if (!optQT && !optNero) {
+ rc = CreateChaptersEvery(h, seconds*MP4_MILLISECONDS_TIME_SCALE, filename);
+ if (0 == rc) {
+ MP4ConvertChapters(h);
+ }
+ }
+ if (0 != rc) {
+ MP4Close(h);
+ continue;
+ }
+ needsOptimize = true;
+ }
+
+ if (optConvert) {
+ if (optQT) {
+ MP4ConvertChapters(h, optQT);
+ needsOptimize = true;
+ } else if (optNero) {
+ MP4ConvertChapters(h, false);
+ needsOptimize = true;
+ }
+ }
+
+ if (optList) {
+ ListChapters(h, optQT);
+ }
+
+ if (0 == rc) {
+ MP4Close(h);
+
+ if (optOptimize || needsOptimize) {
+ MP4Optimize(filename);
+ }
+ }
+ } /* end while optind < argc */
+
+ return 0;
+}
+
+/*
+ * format a duration to a readable format ("stolen" from gpac MP4Box)
+ */
+static char *format_duration(u_int64_t dur, char *szDur)
+{
+ u_int32_t h, m, s, ms;
+
+ h = (u_int32_t) (dur / 3600000);
+ m = (u_int32_t) (dur/ 60000) - h*60;
+ s = (u_int32_t) (dur/1000) - h*3600 - m*60;
+ ms = (u_int32_t) (dur) - h*3600000 - m*60000 - s*1000;
+ if (h<=24) {
+ sprintf(szDur, "%02d:%02d:%02d.%03d", h, m, s, ms);
+ } else {
+ u_int32_t d = (u_int32_t) (dur / 3600000 / 24);
+ h = (u_int32_t) (dur/3600000)-24*d;
+ if (d<=365) {
+ sprintf(szDur, "%d Days, %02d:%02d:%02d.%03d", d, h, m, s, ms);
+ } else {
+ u_int32_t y=0;
+ while (d>365) {
+ y++;
+ d-=365;
+ if (y%4) d--;
+ }
+ sprintf(szDur, "%d Years %d Days, %02d:%02d:%02d.%03d", y, d, h, m, s, ms);
+ }
+ }
+ return szDur;
+}
+
+/*
+ * List chapter start time and title
+ */
+void ListChapters(MP4FileHandle hFile, bool useQT)
+{
+ MP4Chapters_t * chapters = 0;
+ u_int32_t chapterCount = 0;
+ char szDur[20] = {0};
+ MP4Duration durationSum = 0;
+
+ // get the list of chapters
+ MP4GetChaptersList(hFile, &chapters, &chapterCount, useQT);
+ if (0 == chapterCount) {
+ return;
+ }
+
+ // start output (in mp4box format)
+ fprintf(stdout, "\nChapters:\n");
+
+ for(u_int32_t i = 0; i < chapterCount; ++i) {
+ // get the tile
+ const char * title = chapters[i].title;
+ // format the start time
+ const char * formattedDuration = format_duration(durationSum, szDur);
+
+ // print the infos
+ fprintf(stdout, "\tChapter #%u - %s - \"%s\"\n", i+1, formattedDuration, title);
+
+ // add the duration of this chapter to the sum (is the start time of the next chapter)
+ durationSum += chapters[i].duration;
+ }
+
+ // free up the memory
+ MP4Free(chapters);
+}
+
+/*
+ * Create QT/iTunes/iPod chapter markers with a duration of 'milliSeconds' milliseconds
+ */
+int CreateQTChaptersEvery(MP4FileHandle hFile, int milliSeconds, const char * filename)
+{
+ // delete previous chapter markers
+ MP4DeleteChapters(hFile);
+
+ // get the audio track that will reference the chapter track
+ MP4TrackId refTrackId = MP4FindTrackId(hFile, MP4_INVALID_TRACK_ID, MP4_AUDIO_TRACK_TYPE);
+ if (! MP4_IS_VALID_TRACK_ID(refTrackId)) {
+ MP4Close(hFile);
+ fprintf(stderr, "Could not find audio track in file '%s'... aborting\n", filename);
+ return 6;
+ }
+
+ // create the chapter track ...
+ MP4TrackId chapterTrack = MP4AddChapterTextTrack(hFile, refTrackId, MP4_MILLISECONDS_TIME_SCALE);
+
+ // get informations about the audio track
+ MP4Duration refTrackDuration = MP4GetTrackDuration(hFile, refTrackId);
+ uint32_t refTrackTimeScale = MP4GetTrackTimeScale(hFile, refTrackId);
+
+ MP4Duration chapterDuration = milliSeconds;
+ //MP4Duration chapterTrackDuration = (refTrackDuration * MP4_MILLISECONDS_TIME_SCALE) / refTrackTimeScale;
+ MP4Duration chapterTrackDuration = MP4ConvertTime(refTrackDuration, refTrackTimeScale, MP4_MILLISECONDS_TIME_SCALE);
+
+ // create chapters
+ MP4Duration chapterSum;
+ int i;
+ for (chapterSum = 0, i = 1; chapterTrackDuration > chapterSum; chapterSum += chapterDuration, ++i) {
+ if (chapterTrackDuration < (chapterSum + chapterDuration)) {
+ chapterDuration = chapterTrackDuration - chapterSum;
+ }
+
+ // chapterDuration is expected as count of samples related to the timescale of chapterTrack
+ MP4AddQTChapter( hFile, chapterTrack, chapterDuration, i );
+ }
+
+ return 0;
+}
+
+
+/*
+ * Create Nero chapter markers with a duration of 'milliSeconds' milliseconds
+ */
+int CreateChaptersEvery(MP4FileHandle hFile, int milliSeconds, const char * filename)
+{
+ // delete previous chapter markers
+ MP4DeleteChapters(hFile, MP4_INVALID_TRACK_ID, false);
+
+ // get the audio track that will reference the chapter track
+ MP4TrackId refTrackId = MP4FindTrackId(hFile, MP4_INVALID_TRACK_ID, MP4_AUDIO_TRACK_TYPE);
+ if (! MP4_IS_VALID_TRACK_ID(refTrackId)) {
+ MP4Close(hFile);
+ fprintf(stderr, "Could not find audio track in file '%s'... aborting\n", filename);
+ return 6;
+ }
+
+ // get informations about the audio track
+ MP4Duration refTrackDuration = MP4GetTrackDuration(hFile, refTrackId);
+ uint32_t refTrackTimeScale = MP4GetTrackTimeScale(hFile, refTrackId);
+
+ // we need the duration in 100 nanosecond units
+ MP4Duration durationIn100Nanos = MP4ConvertTime( refTrackDuration, refTrackTimeScale, MP4_NANOSECONDS_TIME_SCALE / 100 );
+ //MP4Duration durationIn100Nanos = ((refTrackDuration * MP4_MILLISECONDS_TIME_SCALE) / refTrackTimeScale) * 10000;
+ MP4Duration chapterDuration = milliSeconds * 10000;
+
+ // create the chapters
+ for (MP4Duration startTime = 0; durationIn100Nanos > startTime; startTime += chapterDuration) {
+ // starttime is expected to be in 100-nanosecond units
+ MP4AddChapter( hFile, startTime );
+ }
+
+ return 0;
+}
diff -urN ../mpeg4ip-1.6.1-orig/lib/mp4v2/util/mp4chaps.vcproj lib/mp4v2/util/mp4chaps.vcproj
--- ../mpeg4ip-1.6.1-orig/lib/mp4v2/util/mp4chaps.vcproj 1970-01-01 09:00:00 +0900
+++ lib/mp4v2/util/mp4chaps.vcproj 2008-05-30 22:51:27 +0900
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
--- ../mpeg4ip-1.6.1-orig/tools.sln 2007-09-29 05:45:08 +0900
+++ tools.sln 2008-05-30 22:52:53 +0900
@@ -2,6 +2,12 @@
# Visual Studio 2005
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmp4v2", "lib\mp4v2\libmp4v2.vcproj", "{56EAC9DA-F67D-433B-AEBB-7C61CF2EF505}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mp4chaps", "lib\mp4v2\util\mp4chaps.vcproj", "{990E370A-FEA4-461A-80FA-CF9B59A5350D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {9EF07C35-1D27-424D-970E-0E89D97DD111} = {9EF07C35-1D27-424D-970E-0E89D97DD111}
+ {56EAC9DA-F67D-433B-AEBB-7C61CF2EF505} = {56EAC9DA-F67D-433B-AEBB-7C61CF2EF505}
+ EndProjectSection
+EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mp4info", "lib\mp4v2\util\mp4info.vcproj", "{22B77017-61F0-4D4A-A8F4-BE726F2E917C}"
ProjectSection(ProjectDependencies) = postProject
{56EAC9DA-F67D-433B-AEBB-7C61CF2EF505} = {56EAC9DA-F67D-433B-AEBB-7C61CF2EF505}
@@ -79,6 +85,10 @@
{56EAC9DA-F67D-433B-AEBB-7C61CF2EF505}.Debug|Win32.Build.0 = Debug|Win32
{56EAC9DA-F67D-433B-AEBB-7C61CF2EF505}.Release|Win32.ActiveCfg = Release|Win32
{56EAC9DA-F67D-433B-AEBB-7C61CF2EF505}.Release|Win32.Build.0 = Release|Win32
+ {990E370A-FEA4-461A-80FA-CF9B59A5350D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {990E370A-FEA4-461A-80FA-CF9B59A5350D}.Debug|Win32.Build.0 = Debug|Win32
+ {990E370A-FEA4-461A-80FA-CF9B59A5350D}.Release|Win32.ActiveCfg = Release|Win32
+ {990E370A-FEA4-461A-80FA-CF9B59A5350D}.Release|Win32.Build.0 = Release|Win32
{22B77017-61F0-4D4A-A8F4-BE726F2E917C}.Debug|Win32.ActiveCfg = Debug|Win32
{22B77017-61F0-4D4A-A8F4-BE726F2E917C}.Debug|Win32.Build.0 = Debug|Win32
{22B77017-61F0-4D4A-A8F4-BE726F2E917C}.Release|Win32.ActiveCfg = Release|Win32