Share

MPEG4IP

The forum address has changed, you have been automatically redirected. Please update any bookmarks to use the new URL.

Subscribe

Nero and Quicktime chapters patch

  1. 2007-10-03 11:22:39 UTC
    Hi

    since there is no tracker for this project I am attaching the patch to the forum.

    The patch contains infos from HandBrake and gpac to
    create/delete/convert Nero and Quicktime(iTunes/iPod) chapter markers in MP4 files.
    A sample program mp4chaps is added to the lib/mp4v2/util folder.

    Comments are welcome, since I am not sure about the right way to integrate this stuff into mp4v2.


    Store all lines below in a file and apply this patch file to the checked out mpeg4ip files:

    Index: lib/mp4v2/mp4atom.cpp
    ===================================================================
    RCS file: /cvsroot/mpeg4ip/mpeg4ip/lib/mp4v2/mp4atom.cpp,v
    retrieving revision 1.36
    diff -u -r1.36 mp4atom.cpp
    --- lib/mp4v2/mp4atom.cpp 30 Apr 2007 20:29:28 -0000 1.36
    +++ lib/mp4v2/mp4atom.cpp 3 Oct 2007 11:10:38 -0000
    @@ -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;
    Index: lib/mp4v2/atoms.h
    ===================================================================
    RCS file: /cvsroot/mpeg4ip/mpeg4ip/lib/mp4v2/atoms.h,v
    retrieving revision 1.21
    diff -u -r1.21 atoms.h
    --- lib/mp4v2/atoms.h 18 Sep 2007 20:51:59 -0000 1.21
    +++ lib/mp4v2/atoms.h 3 Oct 2007 11:10:37 -0000
    @@ -405,4 +405,10 @@
    void Generate(void);
    };

    +class MP4ChplAtom : public MP4Atom {
    + public:
    + MP4ChplAtom();
    + void Generate(void);
    +};
    +
    #endif /* __MP4_ATOMS_INCLUDED__ */
    Index: lib/mp4v2/Makefile.am
    ===================================================================
    RCS file: /cvsroot/mpeg4ip/mpeg4ip/lib/mp4v2/Makefile.am,v
    retrieving revision 1.32
    diff -u -r1.32 Makefile.am
    --- lib/mp4v2/Makefile.am 18 Sep 2007 20:51:59 -0000 1.32
    +++ lib/mp4v2/Makefile.am 3 Oct 2007 11:10:37 -0000
    @@ -13,6 +13,7 @@
    atom_amr.cpp \
    atom_avc1.cpp \
    atom_avcC.cpp \
    + atom_chlp.cpp \
    atom_d263.cpp \
    atom_damr.cpp \
    atom_dref.cpp \
    Index: lib/mp4v2/mp4.h
    ===================================================================
    RCS file: /cvsroot/mpeg4ip/mpeg4ip/lib/mp4v2/mp4.h,v
    retrieving revision 1.65
    diff -u -r1.65 mp4.h
    --- lib/mp4v2/mp4.h 28 Sep 2007 20:45:11 -0000 1.65
    +++ lib/mp4v2/mp4.h 3 Oct 2007 11:10:38 -0000
    @@ -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,
    + boolean toQT DEFAULT(true));
    +
    +void MP4DeleteChapters(
    + MP4FileHandle hFile,
    + MP4TrackId chapterTrackId DEFAULT(MP4_INVALID_TRACK_ID),
    + boolean deleteQT DEFAULT(true));
    +
    +void MP4GetChaptersList(
    + MP4FileHandle hFile,
    + MP4Chapters_t ** chapterList,
    + u_int32_t * chapterCount,
    + boolean getQT DEFAULT(true));

    MP4TrackId MP4CloneTrack(
    MP4FileHandle srcFile,
    Index: lib/mp4v2/atom_udta.cpp
    ===================================================================
    RCS file: /cvsroot/mpeg4ip/mpeg4ip/lib/mp4v2/atom_udta.cpp,v
    retrieving revision 1.4
    diff -u -r1.4 atom_udta.cpp
    --- lib/mp4v2/atom_udta.cpp 23 Feb 2006 00:28:57 -0000 1.4
    +++ lib/mp4v2/atom_udta.cpp 3 Oct 2007 11:10:37 -0000
    @@ -24,6 +24,7 @@
    MP4UdtaAtom::MP4UdtaAtom()
    : MP4Atom("udta")
    {
    + ExpectChildAtom("chpl", Optional, OnlyOne);
    ExpectChildAtom("cprt", Optional, Many);
    ExpectChildAtom("hnti", Optional, OnlyOne);
    ExpectChildAtom("meta", Optional, OnlyOne);
    Index: lib/mp4v2/mp4file.cpp
    ===================================================================
    RCS file: /cvsroot/mpeg4ip/mpeg4ip/lib/mp4v2/mp4file.cpp,v
    retrieving revision 1.66
    diff -u -r1.66 mp4file.cpp
    --- lib/mp4v2/mp4file.cpp 28 Sep 2007 20:45:11 -0000 1.66
    +++ lib/mp4v2/mp4file.cpp 3 Oct 2007 11:10:39 -0000
    @@ -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(boolean 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, boolean 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,
    + boolean 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");
    Index: lib/mp4v2/libmp4v2.vcproj
    ===================================================================
    RCS file: /cvsroot/mpeg4ip/mpeg4ip/lib/mp4v2/libmp4v2.vcproj,v
    retrieving revision 1.3
    diff -u -r1.3 libmp4v2.vcproj
    --- lib/mp4v2/libmp4v2.vcproj 18 Sep 2007 20:51:59 -0000 1.3
    +++ lib/mp4v2/libmp4v2.vcproj 3 Oct 2007 11:10:37 -0000
    @@ -163,6 +163,10 @@
    >
    </File>
    <File
    + RelativePath=".\atom_chpl.cpp"
    + >
    + </File>
    + <File
    RelativePath=".\atom_d263.cpp"
    >
    </File>
    Index: lib/mp4v2/mp4file.h
    ===================================================================
    RCS file: /cvsroot/mpeg4ip/mpeg4ip/lib/mp4v2/mp4file.h,v
    retrieving revision 1.61
    diff -u -r1.61 mp4file.h
    --- lib/mp4v2/mp4file.h 28 Sep 2007 20:45:11 -0000 1.61
    +++ lib/mp4v2/mp4file.h 3 Oct 2007 11:10:41 -0000
    @@ -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(boolean toQT = true);
    + void DeleteChapters(MP4TrackId chapterTrackId = 0, boolean deleteQT = true);
    + void GetChaptersList(MP4Chapters_t ** chapterList,
    + u_int32_t * chapterCount,
    + boolean getQT = true);
    + MP4TrackId FindChapterTrack(char * trackName = 0, int trackNameSize = 0);
    + MP4TrackId FindChapterReferenceTrack(MP4TrackId chapterTrackId, char * trackName = 0, int trackNameSize = 0);

    MP4SampleId GetTrackNumberOfSamples(MP4TrackId trackId);

    Index: lib/mp4v2/mp4.cpp
    ===================================================================
    RCS file: /cvsroot/mpeg4ip/mpeg4ip/lib/mp4v2/mp4.cpp,v
    retrieving revision 1.63
    diff -u -r1.63 mp4.cpp
    --- lib/mp4v2/mp4.cpp 28 Sep 2007 20:45:11 -0000 1.63
    +++ lib/mp4v2/mp4.cpp 3 Oct 2007 11:10:38 -0000
    @@ -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,
    + boolean 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,
    + boolean 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,
    + boolean 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,
    Index: tools.sln
    ===================================================================
    RCS file: /cvsroot/mpeg4ip/mpeg4ip/tools.sln,v
    retrieving revision 1.4
    diff -u -r1.4 tools.sln
    --- tools.sln 28 Sep 2007 20:45:08 -0000 1.4
    +++ tools.sln 3 Oct 2007 11:10:37 -0000
    @@ -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
    Index: lib/mp4v2/util/Makefile.am
    ===================================================================
    RCS file: /cvsroot/mpeg4ip/mpeg4ip/lib/mp4v2/util/Makefile.am,v
    retrieving revision 1.21
    diff -u -r1.21 Makefile.am
    --- lib/mp4v2/util/Makefile.am 18 Sep 2007 20:52:00 -0000 1.21
    +++ lib/mp4v2/util/Makefile.am 3 Oct 2007 11:10:41 -0000
    @@ -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
    Index: lib/mp4v2/util/mp4chaps.cpp
    ===================================================================
    RCS file: lib/mp4v2/util/mp4chaps.cpp
    diff -N lib/mp4v2/util/mp4chaps.cpp
    --- /dev/null 1 Jan 1970 00:00:00 -0000
    +++ lib/mp4v2/util/mp4chaps.cpp 1 Jan 1970 00:00:00 -0000
    @@ -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 <pollaehne at users.sourceforge.net>
    + * with help from Handbrake <http://handbrake.m0k.org/>
    + */
    +
    +#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, boolean 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;
    + boolean 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, boolean 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;
    +}
    Index: lib/mp4v2/util/mp4chaps.vcproj
    ===================================================================
    RCS file: lib/mp4v2/util/mp4chaps.vcproj
    diff -N lib/mp4v2/util/mp4chaps.vcproj
    --- /dev/null 1 Jan 1970 00:00:00 -0000
    +++ lib/mp4v2/util/mp4chaps.vcproj 1 Jan 1970 00:00:00 -0000
    @@ -0,0 +1,208 @@
    +<?xml version="1.0" encoding="Windows-1252"?>
    +<VisualStudioProject
    + ProjectType="Visual C++"
    + Version="8,00"
    + Name="mp4chaps"
    + ProjectGUID="{990E370A-FEA4-461A-80FA-CF9B59A5350D}"
    + RootNamespace="mp4chaps"
    + Keyword="Win32Proj"
    + >
    + <Platforms>
    + <Platform
    + Name="Win32"
    + />
    + </Platforms>
    + <ToolFiles>
    + </ToolFiles>
    + <Configurations>
    + <Configuration
    + Name="Debug|Win32"
    + OutputDirectory="$(SolutionDir)$(ConfigurationName)"
    + IntermediateDirectory="$(ConfigurationName)"
    + ConfigurationType="1"
    + CharacterSet="1"
    + >
    + <Tool
    + Name="VCPreBuildEventTool"
    + />
    + <Tool
    + Name="VCCustomBuildTool"
    + />
    + <Tool
    + Name="VCXMLDataGeneratorTool"
    + />
    + <Tool
    + Name="VCWebServiceProxyGeneratorTool"
    + />
    + <Tool
    + Name="VCMIDLTool"
    + />
    + <Tool
    + Name="VCCLCompilerTool"
    + Optimization="0"
    + AdditionalIncludeDirectories=""$(SolutionDir)/include".."
    + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
    + MinimalRebuild="true"
    + BasicRuntimeChecks="3"
    + RuntimeLibrary="3"
    + UsePrecompiledHeader="0"
    + WarningLevel="3"
    + Detect64BitPortabilityProblems="true"
    + DebugInformationFormat="4"
    + />
    + <Tool
    + Name="VCManagedResourceCompilerTool"
    + />
    + <Tool
    + Name="VCResourceCompilerTool"
    + />
    + <Tool
    + Name="VCPreLinkEventTool"
    + />
    + <Tool
    + Name="VCLinkerTool"
    + AdditionalDependencies="ws2_32.lib"
    + LinkIncremental="2"
    + GenerateManifest="true"
    + GenerateDebugInformation="true"
    + SubSystem="1"
    + TargetMachine="1"
    + />
    + <Tool
    + Name="VCALinkTool"
    + />
    + <Tool
    + Name="VCManifestTool"
    + />
    + <Tool
    + Name="VCXDCMakeTool"
    + />
    + <Tool
    + Name="VCBscMakeTool"
    + />
    + <Tool
    + Name="VCFxCopTool"
    + />
    + <Tool
    + Name="VCAppVerifierTool"
    + />
    + <Tool
    + Name="VCWebDeploymentTool"
    + />
    + <Tool
    + Name="VCPostBuildEventTool"
    + />
    + </Configuration>
    + <Configuration
    + Name="Release|Win32"
    + OutputDirectory="$(SolutionDir)$(ConfigurationName)"
    + IntermediateDirectory="$(ConfigurationName)"
    + ConfigurationType="1"
    + CharacterSet="1"
    + WholeProgramOptimization="1"
    + >
    + <Tool
    + Name="VCPreBuildEventTool"
    + />
    + <Tool
    + Name="VCCustomBuildTool"
    + />
    + <Tool
    + Name="VCXMLDataGeneratorTool"
    + />
    + <Tool
    + Name="VCWebServiceProxyGeneratorTool"
    + />
    + <Tool
    + Name="VCMIDLTool"
    + />
    + <Tool
    + Name="VCCLCompilerTool"
    + Optimization="1"
    + FavorSizeOrSpeed="2"
    + AdditionalIncludeDirectories=""$(SolutionDir)/include".."
    + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
    + RuntimeLibrary="2"
    + UsePrecompiledHeader="0"
    + WarningLevel="3"
    + Detect64BitPortabilityProblems="true"
    + DebugInformationFormat="3"
    + />
    + <Tool
    + Name="VCManagedResourceCompilerTool"
    + />
    + <Tool
    + Name="VCResourceCompilerTool"
    + />
    + <Tool
    + Name="VCPreLinkEventTool"
    + />
    + <Tool
    + Name="VCLinkerTool"
    + AdditionalDependencies="ws2_32.lib"
    + LinkIncremental="1"
    + GenerateManifest="false"
    + IgnoreAllDefaultLibraries="false"
    + IgnoreDefaultLibraryNames=""
    + GenerateDebugInformation="false"
    + SubSystem="1"
    + OptimizeReferences="2"
    + EnableCOMDATFolding="2"
    + OptimizeForWindows98="1"
    + TargetMachine="1"
    + />
    + <Tool
    + Name="VCALinkTool"
    + />
    + <Tool
    + Name="VCManifestTool"
    + />
    + <Tool
    + Name="VCXDCMakeTool"
    + />
    + <Tool
    + Name="VCBscMakeTool"
    + />
    + <Tool
    + Name="VCFxCopTool"
    + />
    + <Tool
    + Name="VCAppVerifierTool"
    + />
    + <Tool
    + Name="VCWebDeploymentTool"
    + />
    + <Tool
    + Name="VCPostBuildEventTool"
    + />
    + </Configuration>
    + </Configurations>
    + <References>
    + </References>
    + <Files>
    + <Filter
    + Name="Source Files"
    + Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
    + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
    + >
    + <File
    + RelativePath=".\mp4chaps.cpp"
    + >
    + </File>
    + </Filter>
    + <Filter
    + Name="Header Files"
    + Filter="h;hpp;hxx;hm;inl;inc;xsd"
    + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
    + >
    + </Filter>
    + <Filter
    + Name="Resource Files"
    + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
    + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
    + >
    + </Filter>
    + </Files>
    + <Globals>
    + </Globals>
    +</VisualStudioProject>
    Index: lib/mp4v2/atom_chpl.cpp
    ===================================================================
    RCS file: lib/mp4v2/atom_chpl.cpp
    diff -N lib/mp4v2/atom_chpl.cpp
    --- /dev/null 1 Jan 1970 00:00:00 -0000
    +++ lib/mp4v2/atom_chpl.cpp 1 Jan 1970 00:00:00 -0000
    @@ -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 <pollaehne at users.sourceforge.net>
    + *
    + * 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);
    +}
  2. 2007-10-22 15:46:09 UTC
    I'm not sure why no one has replied to your post, but I took your changes and built your util, mp4chaps. It works beautifully for QuickTime, with the little bit of testing I have done so far! I am working on some updates to Mp4Creator which will also include your chapter manipulation. I realize that most of your mods were taken from HandBrake - I've been looking at their code as well. Whatever I put together, I will be sure and mention your contribution. Thanks!
  3. 2007-10-24 17:59:39 UTC
    At least one (besides me) who finds this useful. ;-)

    Yes, most of the code originates from the HandBrake code and since there were some patches already integrated and other code (that was useful too) was not integrated I tried to integrate as much as possible and added support for Nero's chapter definition since this helps me to create chaptered audiobooks for my iPod (with foobar, neroaacenc and mp4chaps).
< Previous | 1 | Next >

Add a Reply

This forum does not allow anonymous participation.

Log in to add a reply. Not registered? Create an account to participate and receive email updates when replies are posted to this topic.