mkvparser: add support for MKV chapters

Change-Id: I2404b6886ed592fe505ee973bf05c769a9d134b1
diff --git a/mkvparser.cpp b/mkvparser.cpp
index fcf961c..aa138bd 100644
--- a/mkvparser.cpp
+++ b/mkvparser.cpp
@@ -336,7 +336,7 @@
 

     id = ReadUInt(pReader, pos, len);

 

-    if (id <= 0)

+    if (id < 0)

         return E_FILE_FORMAT_INVALID;

 

     pos += len;  //consume id

@@ -692,6 +692,7 @@
     m_pInfo(NULL),

     m_pTracks(NULL),

     m_pCues(NULL),

+    m_pChapters(NULL),

     m_clusters(NULL),

     m_clusterCount(0),

     m_clusterPreloadCount(0),

@@ -720,6 +721,7 @@
     delete m_pTracks;

     delete m_pInfo;

     delete m_pCues;

+    delete m_pChapters;

     delete m_pSeekHead;

 }

 

@@ -1022,6 +1024,26 @@
                     return status;

             }

         }

+        else if (id == 0x0043A770)  //Chapters ID

+        {

+            if (m_pChapters == NULL)

+            {

+                m_pChapters = new (std::nothrow) Chapters(

+                                this,

+                                pos,

+                                size,

+                                element_start,

+                                element_size);

+

+                if (m_pChapters == NULL)

+                  return -1;

+

+                const long status = m_pChapters->Parse();

+

+                if (status)

+                  return status;

+            }

+        }

 

         m_pos = pos + size;  //consume payload

     }

@@ -3291,10 +3313,6 @@
 

     assert(m_clusterPreloadCount > 0);

 

-    //const long long off_ = pCurr->m_pos;

-    //const long long off = off_ * ((off_ < 0) ? -1 : 1);

-    //long long pos = m_start + off;

-

     long long pos = pCurr->m_element_start;

 

     assert(m_size >= 0);  //TODO

@@ -3330,8 +3348,6 @@
     }

 

     long long off_next = 0;

-    //long long element_start_next = 0;

-    long long element_size_next = 0;

 

     while (pos < stop)

     {

@@ -3359,8 +3375,6 @@
         pos += len;  //consume length of size of element

         assert((pos + size) <= stop);  //TODO

 

-        const long long element_size = size + pos - idpos;

-

         //Pos now points to start of payload

 

         if (size == 0)  //weird

@@ -3384,8 +3398,6 @@
             if (status > 0)

             {

                 off_next = off_next_;

-                //element_start_next = idpos;

-                element_size_next = element_size;

                 break;

             }

         }

@@ -3435,7 +3447,6 @@
     Cluster* const pNext = Cluster::Create(this,

                                           -1,

                                           off_next);

-                                          //element_size_next);

     assert(pNext);

 

     const ptrdiff_t idx_next = i - m_clusters;  //insertion position

@@ -4208,6 +4219,12 @@
 }

 

 

+const Chapters* Segment::GetChapters() const

+{

+  return m_pChapters;

+}

+

+

 const SeekHead* Segment::GetSeekHead() const

 {

     return m_pSeekHead;

@@ -4221,6 +4238,515 @@
 }

 

 

+Chapters::Chapters(

+    Segment* pSegment,

+    long long payload_start,

+    long long payload_size,

+    long long element_start,

+    long long element_size) :

+    m_pSegment(pSegment),

+    m_start(payload_start),

+    m_size(payload_size),

+    m_element_start(element_start),

+    m_element_size(element_size),

+    m_editions(NULL),

+    m_editions_size(0),

+    m_editions_count(0)

+{

+}

+

+

+Chapters::~Chapters()

+{

+    while (m_editions_count > 0)

+    {

+        Edition& e = m_editions[--m_editions_count];

+        e.Clear();

+    }

+}

+

+

+long Chapters::Parse()

+{

+    IMkvReader* const pReader = m_pSegment->m_pReader;

+

+    long long pos = m_start;  // payload start

+    const long long stop = pos + m_size;  // payload stop

+

+    while (pos < stop)

+    {

+        long long id, size;

+

+        long status = ParseElementHeader(

+                        pReader,

+                        pos,

+                        stop,

+                        id,

+                        size);

+

+        if (status < 0)  // error

+            return status;

+

+        if (size == 0)  // weird

+            continue;

+

+        if (id == 0x05B9)  // EditionEntry ID

+        {

+            status = ParseEdition(pos, size);

+

+            if (status < 0)  // error

+                return status;

+        }

+

+        pos += size;

+        assert(pos <= stop);

+    }

+

+    assert(pos == stop);

+    return 0;

+}

+

+

+int Chapters::GetEditionCount() const

+{

+    return m_editions_count;

+}

+

+

+const Chapters::Edition* Chapters::GetEdition(int idx) const

+{

+    if (idx < 0)

+        return NULL;

+

+    if (idx >= m_editions_count)

+        return NULL;

+

+    return m_editions + idx;

+}

+

+

+bool Chapters::ExpandEditionsArray()

+{

+    if (m_editions_size > m_editions_count)

+        return true;  // nothing else to do

+

+    const int size = (m_editions_size == 0) ? 1 : 2 * m_editions_size;

+

+    Edition* const editions = new (std::nothrow) Edition[size];

+

+    if (editions == NULL)

+        return false;

+

+    for (int idx = 0; idx < m_editions_count; ++idx)

+    {

+        m_editions[idx].ShallowCopy(editions[idx]);

+    }

+

+    delete[] m_editions;

+    m_editions = editions;

+

+    m_editions_size = size;

+    return true;

+}

+

+

+long Chapters::ParseEdition(

+    long long pos,

+    long long size)

+{

+    if (!ExpandEditionsArray())

+        return -1;

+

+    Edition& e = m_editions[m_editions_count++];

+    e.Init();

+

+    return e.Parse(m_pSegment->m_pReader, pos, size);

+}

+

+

+Chapters::Edition::Edition()

+{

+}

+

+

+Chapters::Edition::~Edition()

+{

+}

+

+

+void Chapters::Edition::Init()

+{

+    m_atoms = NULL;

+    m_atoms_size = 0;

+    m_atoms_count = 0;

+}

+

+

+void Chapters::Edition::ShallowCopy(Edition& rhs) const

+{

+    rhs.m_atoms = m_atoms;

+    rhs.m_atoms_size = m_atoms_size;

+    rhs.m_atoms_count = m_atoms_count;

+}

+

+

+void Chapters::Edition::Clear()

+{

+    while (m_atoms_count > 0)

+    {

+        Atom& a = m_atoms[--m_atoms_count];

+        a.Clear();

+    }

+

+    delete[] m_atoms;

+    m_atoms = NULL;

+

+    m_atoms_size = 0;

+}

+

+

+long Chapters::Edition::Parse(

+    IMkvReader* pReader,

+    long long pos,

+    long long size)

+{

+    const long long stop = pos + size;

+

+    while (pos < stop)

+    {

+        long long id, size;

+

+        long status = ParseElementHeader(

+                        pReader,

+                        pos,

+                        stop,

+                        id,

+                        size);

+

+        if (status < 0)  // error

+            return status;

+

+        if (size == 0)  // weird

+            continue;

+

+        if (id == 0x36)  // Atom ID

+        {

+            status = ParseAtom(pReader, pos, size);

+

+            if (status < 0)  // error

+                return status;

+        }

+

+        pos += size;

+        assert(pos <= stop);

+    }

+

+    assert(pos == stop);

+    return 0;

+}

+

+

+long Chapters::Edition::ParseAtom(

+    IMkvReader* pReader,

+    long long pos,

+    long long size)

+{

+    if (!ExpandAtomsArray())

+        return -1;

+

+    Atom& a = m_atoms[m_atoms_count++];

+    a.Init();

+

+    return a.Parse(pReader, pos, size);

+}

+

+

+bool Chapters::Edition::ExpandAtomsArray()

+{

+    if (m_atoms_size > m_atoms_count)

+        return true;  // nothing else to do

+

+    const int size = (m_atoms_size == 0) ? 1 : 2 * m_atoms_size;

+

+    Atom* const atoms = new (std::nothrow) Atom[size];

+

+    if (atoms == NULL)

+        return false;

+

+    for (int idx = 0; idx < m_atoms_count; ++idx)

+    {

+        m_atoms[idx].ShallowCopy(atoms[idx]);

+    }

+

+    delete[] m_atoms;

+    m_atoms = atoms;

+

+    m_atoms_size = size;

+    return true;

+}

+

+

+Chapters::Atom::Atom()

+{

+}

+

+

+Chapters::Atom::~Atom()

+{

+}

+

+

+void Chapters::Atom::Init()

+{

+    m_displays = NULL;

+    m_displays_size = 0;

+    m_displays_count = 0;

+}

+

+

+void Chapters::Atom::ShallowCopy(Atom& rhs) const

+{

+    rhs.m_displays = m_displays;

+    rhs.m_displays_size = m_displays_size;

+    rhs.m_displays_count = m_displays_count;

+}

+

+

+void Chapters::Atom::Clear()

+{

+    while (m_displays_count > 0)

+    {

+        Display& d = m_displays[--m_displays_count];

+        d.Clear();

+    }

+

+    delete[] m_displays;

+    m_displays = NULL;

+

+    m_displays_size = 0;

+}

+

+

+long Chapters::Atom::Parse(

+    IMkvReader* pReader,

+    long long pos,

+    long long size)

+{

+    const long long stop = pos + size;

+

+    while (pos < stop)

+    {

+        long long id, size;

+

+        long status = ParseElementHeader(

+                        pReader,

+                        pos,

+                        stop,

+                        id,

+                        size);

+

+        if (status < 0)  // error

+            return status;

+

+        if (size == 0)  // weird

+            continue;

+

+        if (id == 0x00)  // Display ID

+        {

+            status = ParseDisplay(pReader, pos, size);

+

+            if (status < 0)  // error

+                return status;

+        }

+        else if (id == 0x33C4)  // UID ID

+        {

+            const long long val = UnserializeUInt(pReader, pos, size);

+

+            if (val < 0)  // error

+                return static_cast<long>(val);

+

+            m_uid = val;

+        }

+        else if (id == 0x11)  // TimeStart ID

+        {

+            const long long val = UnserializeUInt(pReader, pos, size);

+

+            if (val < 0)  // error

+                return static_cast<long>(val);

+

+            m_start_timecode = val;

+        }

+        else if (id == 0x12)  // TimeEnd ID

+        {

+            const long long val = UnserializeUInt(pReader, pos, size);

+

+            if (val < 0)  // error

+                return static_cast<long>(val);

+

+            m_stop_timecode = val;

+        }

+

+        pos += size;

+        assert(pos <= stop);

+    }

+

+    assert(pos == stop);

+    return 0;

+}

+

+

+long Chapters::Atom::ParseDisplay(

+    IMkvReader* pReader,

+    long long pos,

+    long long size)

+{

+    if (!ExpandDisplaysArray())

+        return -1;

+

+    Display& d = m_displays[m_displays_count++];

+    d.Init();

+

+    return d.Parse(pReader, pos, size);

+}

+

+

+bool Chapters::Atom::ExpandDisplaysArray()

+{

+    if (m_displays_size > m_displays_count)

+        return true;  // nothing else to do

+

+    const int size = (m_displays_size == 0) ? 1 : 2 * m_displays_size;

+

+    Display* const displays = new (std::nothrow) Display[size];

+

+    if (displays == NULL)

+        return false;

+

+    for (int idx = 0; idx < m_displays_count; ++idx)

+    {

+        m_displays[idx].ShallowCopy(displays[idx]);

+    }

+

+    delete[] m_displays;

+    m_displays = displays;

+

+    m_displays_size = size;

+    return true;

+}

+

+

+Chapters::Display::Display()

+{

+}

+

+

+Chapters::Display::~Display()

+{

+}

+

+

+const char* Chapters::Display::GetString() const

+{

+    return m_string;

+}

+

+

+const char* Chapters::Display::GetLanguage() const

+{

+    return m_language;

+}

+

+

+const char* Chapters::Display::GetCountry() const

+{

+    return m_country;

+}

+

+

+void Chapters::Display::Init()

+{

+    m_string = NULL;

+    m_language = NULL;

+    m_country = NULL;

+}

+

+

+void Chapters::Display::ShallowCopy(Display& rhs) const

+{

+    rhs.m_string = m_string;

+    rhs.m_language = m_language;

+    rhs.m_country = m_country;

+}

+

+

+void Chapters::Display::Clear()

+{

+    delete[] m_string;

+    m_string = NULL;

+

+    delete[] m_language;

+    m_language = NULL;

+

+    delete[] m_country;

+    m_country = NULL;

+}

+

+

+long Chapters::Display::Parse(

+    IMkvReader* pReader,

+    long long pos,

+    long long size)

+{

+    const long long stop = pos + size;

+

+    while (pos < stop)

+    {

+        long long id, size;

+

+        long status = ParseElementHeader(

+                        pReader,

+                        pos,

+                        stop,

+                        id,

+                        size);

+

+        if (status < 0)  // error

+            return status;

+

+        if (size == 0)  // weird

+            continue;

+

+        if (id == 0x05)  // ChapterString ID

+        {

+            status = UnserializeString(pReader, pos, size, m_string);

+

+            if (status)

+              return status;

+        }

+        else if (id == 0x037C)  // ChapterLanguage ID

+        {

+            status = UnserializeString(pReader, pos, size, m_language);

+

+            if (status)

+              return status;

+        }

+        else if (id == 0x037E)  // ChapterCountry ID

+        {

+            status = UnserializeString(pReader, pos, size, m_country);

+

+            if (status)

+              return status;

+        }

+

+        pos += size;

+        assert(pos <= stop);

+    }

+

+    assert(pos == stop);

+    return 0;

+}

+

+

 SegmentInfo::SegmentInfo(

     Segment* pSegment,

     long long start,

diff --git a/mkvparser.hpp b/mkvparser.hpp
index 4247f52..ab4b68b 100644
--- a/mkvparser.hpp
+++ b/mkvparser.hpp
@@ -511,6 +511,121 @@
 };

 

 

+class Chapters

+{

+    Chapters(const Chapters&);

+    Chapters& operator=(const Chapters&);

+

+public:

+    Segment* const m_pSegment;

+    const long long m_start;

+    const long long m_size;

+    const long long m_element_start;

+    const long long m_element_size;

+

+    Chapters(

+        Segment*,

+        long long payload_start,

+        long long payload_size,

+        long long element_start,

+        long long element_size);

+

+    ~Chapters();

+

+    long Parse();

+

+    class Atom;

+    class Edition;

+

+    class Display

+    {

+        friend class Atom;

+        Display();

+        Display(const Display&);

+        ~Display();

+        Display& operator=(const Display&);

+    public:

+        const char* GetString() const;

+        const char* GetLanguage() const;

+        const char* GetCountry() const;

+    private:

+        void Init();

+        void ShallowCopy(Display&) const;

+        void Clear();

+        long Parse(IMkvReader*, long long pos, long long size);

+

+        char* m_string;

+        char* m_language;

+        char* m_country;

+    };

+

+    class Atom

+    {

+        friend class Edition;

+        Atom();

+        Atom(const Atom&);

+        ~Atom();

+        Atom& operator=(const Atom&);

+    public:

+        int GetDisplayCount() const;

+        const Atom* GetAtom(int index) const;

+    private:

+        void Init();

+        void ShallowCopy(Atom&) const;

+        void Clear();

+        long Parse(IMkvReader*, long long pos, long long size);

+

+        long ParseDisplay(IMkvReader*, long long pos, long long size);

+        bool ExpandDisplaysArray();

+

+        unsigned long long m_uid;

+        // TODO(matthewjheaney): Cue Identifier (string)

+        unsigned long long m_start_timecode;

+        unsigned long long m_stop_timecode;

+

+        Display* m_displays;

+        int m_displays_size;

+        int m_displays_count;

+    };

+

+    class Edition

+    {

+        friend class Chapters;

+        Edition();

+        Edition(const Edition&);

+        ~Edition();

+        Edition& operator=(const Edition&);

+    public:

+        int GetAtomCount() const;

+        const Atom& GetAtom(int index) const;

+    private:

+        void Init();

+        void ShallowCopy(Edition&) const;

+        void Clear();

+        long Parse(IMkvReader*, long long pos, long long size);

+

+        long ParseAtom(IMkvReader*, long long pos, long long size);

+        bool ExpandAtomsArray();

+

+        Atom* m_atoms;

+        int m_atoms_size;

+        int m_atoms_count;

+    };

+

+    int GetEditionCount() const;

+    const Edition* GetEdition(int index) const;

+

+private:

+    long ParseEdition(long long pos, long long size);

+    bool ExpandEditionsArray();

+

+    Edition* m_editions;

+    int m_editions_size;

+    int m_editions_count;

+

+};

+

+

 class SegmentInfo

 {

     SegmentInfo(const SegmentInfo&);

@@ -863,6 +978,7 @@
     const Tracks* GetTracks() const;

     const SegmentInfo* GetInfo() const;

     const Cues* GetCues() const;

+    const Chapters* GetChapters() const;

 

     long long GetDuration() const;

 

@@ -890,6 +1006,7 @@
     SegmentInfo* m_pInfo;

     Tracks* m_pTracks;

     Cues* m_pCues;

+    Chapters* m_pChapters;

     Cluster** m_clusters;

     long m_clusterCount;         //number of entries for which m_index >= 0

     long m_clusterPreloadCount;  //number of entries for which m_index < 0