/*****************************************************************
|
|    AP4 - Movie
|
|    Copyright 2002-2005 Gilles Boccon-Gibod
|
|
|    This file is part of Bento4/AP4 (MP4 Atom Processing Library).
|
|    Unless you have obtained Bento4 under a difference license,
|    this version of Bento4 is Bento4|GPL.
|    Bento4|GPL is free software; you can redistribute it and/or modify
|    it under the terms of the GNU General Public License as published by
|    the Free Software Foundation; either version 2, or (at your option)
|    any later version.
|
|    Bento4|GPL is distributed in the hope that it will be useful,
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
|    GNU General Public License for more details.
|
|    You should have received a copy of the GNU General Public License
|    along with Bento4|GPL; see the file COPYING.  If not, write to the
|    Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|    02111-1307, USA.
|
 ****************************************************************/
 
/*----------------------------------------------------------------------
|       includes
+---------------------------------------------------------------------*/
#include "Ap4.h"
#include "Ap4File.h"
#include "Ap4Atom.h"
#include "Ap4TfdtAtom.h"
#include "Ap4TfhdAtom.h"
#include "Ap4TrakAtom.h"
#include "Ap4TrexAtom.h"
#include "Ap4TrunAtom.h"
#include "Ap4MfhdAtom.h"
#include "Ap4AtomFactory.h"
#include "Ap4Movie.h"
#include "Ap4Utils.h"
 
/*----------------------------------------------------------------------
|       AP4_TrackFinderById
+---------------------------------------------------------------------*/
class AP4_TrackFinderById : public AP4_List<AP4_Track>::Item::Finder
{
public:
    AP4_TrackFinderById(AP4_UI32 track_id) : m_TrackId(track_id) {}
    AP4_Result Test(AP4_Track* track) const {
        return track->GetId() == m_TrackId ? AP4_SUCCESS : AP4_FAILURE;
    }
private:
    AP4_UI32 m_TrackId;
};
 
/*----------------------------------------------------------------------
|       AP4_TrackFinderByType
+---------------------------------------------------------------------*/
class AP4_TrackFinderByType : public AP4_List<AP4_Track>::Item::Finder
{
public:
    AP4_TrackFinderByType(AP4_Track::Type type, AP4_Ordinal index = 0) : 
      m_Type(type), m_Index(index) {}
    AP4_Result Test(AP4_Track* track) const {
        if (track->GetType() == m_Type && m_Index-- == 0) {
            return AP4_SUCCESS;
        } else {
            return AP4_FAILURE;
        }
    }
private:
    AP4_Track::Type     m_Type;
    mutable AP4_Ordinal m_Index;
};
 
/*----------------------------------------------------------------------
|       AP4_Movie::AP4_Movie
+---------------------------------------------------------------------*/
AP4_Movie::AP4_Movie(AP4_UI32 time_scale) :
    m_Stream(NULL)
{
    m_MoovAtom = new AP4_MoovAtom();
    m_MvhdAtom = new AP4_MvhdAtom(0, 0, 
                                  time_scale, 
                                  0, 
                                  0x00010000,
                                  0x0100);
    m_MoovAtom->AddChild(m_MvhdAtom);
}
 
/*----------------------------------------------------------------------
|       AP4_Movie::AP4_Moovie
+---------------------------------------------------------------------*/
AP4_Movie::AP4_Movie(AP4_MoovAtom* moov, AP4_ByteStream& mdat) :
    m_MoovAtom(moov),
    m_Stream(NULL)
{
    // ignore null atoms
    if (moov == NULL) return;
 
    // get the time scale
    AP4_UI32 time_scale;
    m_MvhdAtom = dynamic_cast<AP4_MvhdAtom*>(moov->GetChild(AP4_ATOM_TYPE_MVHD));
    if (m_MvhdAtom) {
        time_scale = m_MvhdAtom->GetTimeScale();
    } else {
        time_scale = 0;
    }
 
    // get all tracks
    AP4_List<AP4_TrakAtom>* trak_atoms;
    trak_atoms = &moov->GetTrakAtoms();
    AP4_List<AP4_TrakAtom>::Item* item = trak_atoms->FirstItem();
    while (item) {
        AP4_Track* track = new AP4_Track(*item->GetData(), 
                                         mdat,
                                         time_scale);
        // small hack for tracks with the same numbers
        AP4_UI32 trackId = track->GetId();
        AP4_Track* oldTrack = GetTrack(trackId);
        while (oldTrack) {
            trackId++;
            track->SetId(trackId);
            oldTrack = GetTrack(trackId);
        }
        //
 
        m_Tracks.Add(track);
        item = item->GetNext();
    }
}
    
/*----------------------------------------------------------------------
|       AP4_Movie::~AP4_Movie
+---------------------------------------------------------------------*/
AP4_Movie::~AP4_Movie()
{
    m_Tracks.DeleteReferences();
    delete m_MoovAtom;
 
    for (auto [id, fragmentsData] : m_fragmentsDataEntries) {
        auto& moof = fragmentsData.MoofAtomEntries;
        for (AP4_Cardinal i = 0; i < moof.ItemCount(); i++) {
            if (moof[i]) {
                delete moof[i]; moof[i] = nullptr;
            }
        }
    }
 
    AP4_RELEASE(m_Stream);
}
 
/*----------------------------------------------------------------------
|       AP4_Movie::Inspect
+---------------------------------------------------------------------*/
AP4_Result
AP4_Movie::Inspect(AP4_AtomInspector& inspector)
{
    // dump the moov atom
    return m_MoovAtom->Inspect(inspector);
}
 
/*----------------------------------------------------------------------
|       AP4_Movie::GetTrack
+---------------------------------------------------------------------*/
AP4_Track*
AP4_Movie::GetTrack(AP4_UI32 track_id)
{
    AP4_Track* track = NULL;
    if (AP4_SUCCEEDED(m_Tracks.Find(AP4_TrackFinderById(track_id), track))) {
        return track;
    } else {
        return NULL;
    }
}
 
/*----------------------------------------------------------------------
|       AP4_Movie::GetTrack
+---------------------------------------------------------------------*/
AP4_Track*
AP4_Movie::GetTrack(AP4_Track::Type track_type, AP4_Ordinal index)
{
    AP4_Track* track = NULL;
    if (AP4_SUCCEEDED(m_Tracks.Find(AP4_TrackFinderByType(track_type, index), track))) {
        return track;
    } else {
        return NULL;
    }
}
 
/*----------------------------------------------------------------------
|       AP4_Movie::AddTrack
+---------------------------------------------------------------------*/
AP4_Result
AP4_Movie::AddTrack(AP4_Track* track)
{
    // assign an ID to the track unless it already has one
    if (track->GetId() == 0) {
        track->SetId(m_Tracks.ItemCount()+1);
    }
 
    // if we don't have a time scale, use the one from the track
    if (m_MvhdAtom->GetTimeScale() == 0) {
        m_MvhdAtom->SetTimeScale(track->GetMediaTimeScale());
    }
 
    // adjust the parent time scale of the track
    track->SetMovieTimeScale(m_MvhdAtom->GetTimeScale());
 
    // update the movie duration
    if (m_MvhdAtom->GetDuration() < track->GetDuration()) {
        m_MvhdAtom->SetDuration(track->GetDuration());
    }
    
    // attach the track as a child
    m_MoovAtom->AddChild(track->GetTrakAtom());
    m_Tracks.Add(track);
 
    return AP4_SUCCESS;
}
 
/*----------------------------------------------------------------------
|       AP4_Movie::GetTimeScale
+---------------------------------------------------------------------*/
AP4_UI32
AP4_Movie::GetTimeScale()
{
    if (m_MvhdAtom) {
        return m_MvhdAtom->GetTimeScale();
    } else {
        return 0;
    }
}
 
/*----------------------------------------------------------------------
|       AP4_Movie::GetDuration
+---------------------------------------------------------------------*/
AP4_UI64
AP4_Movie::GetDuration()
{
    if (m_MvhdAtom) {
        return m_MvhdAtom->GetDuration();
    } else {
        return 0;
    }
}
 
/*----------------------------------------------------------------------
|       AP4_Movie::GetDurationMs
+---------------------------------------------------------------------*/
AP4_Duration
AP4_Movie::GetDurationMs()
{
    if (m_MvhdAtom) {
        return m_MvhdAtom->GetDurationMs();
    } else {
        return 0;
    }
}
 
/*----------------------------------------------------------------------
|   AP4_Movie::HasFragments
+---------------------------------------------------------------------*/
bool
AP4_Movie::HasFragments()
{
    if (m_MoovAtom == NULL) return false;
    if (m_MoovAtom->GetChild(AP4_ATOM_TYPE_MVEX)) {
        return true;
    } else {
        return false;
    }
}
 
/*----------------------------------------------------------------------
|   AP4_Movie::GetFragmentsDuration
+---------------------------------------------------------------------*/
AP4_Duration
AP4_Movie::GetFragmentsDuration()
{
    if (!m_fragmentsDataEntries.empty()) {
        const auto id = m_Tracks.FirstItem()->GetData()->GetId();
        return m_fragmentsDataEntries[id].SidxAtom->GetDuration();
    }
    return 0;
}
 
/*----------------------------------------------------------------------
|   AP4_Movie::GetFragmentsDurationMs
+---------------------------------------------------------------------*/
AP4_Duration
AP4_Movie::GetFragmentsDurationMs()
{
    if (!m_fragmentsDataEntries.empty()) {
        const auto id = m_Tracks.FirstItem()->GetData()->GetId();
        const auto& sidx = m_fragmentsDataEntries[id].SidxAtom;
        return AP4_ConvertTime(sidx->GetDuration(), sidx->GetTimeScale(), 1000);
    }
    return 0;
}
 
/*----------------------------------------------------------------------
|   AP4_Movie::HasFragmentsIndex
+---------------------------------------------------------------------*/
const bool
AP4_Movie::HasFragmentsIndex()
{
    if (!m_fragmentsDataEntries.empty()) {
        const auto id = m_Tracks.FirstItem()->GetData()->GetId();
        return m_fragmentsDataEntries[id].FragmentsIndexEntries.ItemCount() > 0;
    }
 
    return false;
}
 
/*----------------------------------------------------------------------
|   AP4_Movie::GetFragmentsIndexEntries
+---------------------------------------------------------------------*/
const AP4_Array<AP4_IndexTableEntry>&
AP4_Movie::GetFragmentsIndexEntries()
{
    const auto id = m_Tracks.FirstItem()->GetData()->GetId();
    return m_fragmentsDataEntries[id].FragmentsIndexEntries;
}
 
/*----------------------------------------------------------------------
|   AP4_Movie::ProcessMoof
+---------------------------------------------------------------------*/
void
AP4_Movie::ProcessMoof(AP4_ContainerAtom* moof, AP4_ByteStream& stream, AP4_Offset offset, AP4_Duration dts/* = 0*/, bool bClearSampleTable/* = false*/)
{
    if (moof) {
        AP4_Offset moof_offset = offset - moof->GetSize();
        AP4_Offset mdat_payload_offset = offset + AP4_ATOM_HEADER_SIZE;
 
        AP4_MfhdAtom* mfhd = AP4_DYNAMIC_CAST(AP4_MfhdAtom, moof->GetChild(AP4_ATOM_TYPE_MFHD));
        if (mfhd) {
            for (AP4_List<AP4_Atom>::Item* item = moof->GetChildren().FirstItem();
                                           item;
                                           item = item->GetNext()) {
                AP4_Atom* atom = item->GetData();
                if (atom->GetType() == AP4_ATOM_TYPE_TRAF) {
                    AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
                    if (traf) {
                        AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD));
                        if (!tfhd) {
                            continue;
                        }
                        AP4_Track* track = GetTrack(tfhd->GetTrackId());
                        if (!track) {
                            continue;
                        }
 
                        AP4_TfdtAtom* tfdt = AP4_DYNAMIC_CAST(AP4_TfdtAtom, traf->GetChild(AP4_ATOM_TYPE_TFDT));
 
                        AP4_TrexAtom*      trex = NULL;
                        AP4_ContainerAtom* mvex = AP4_DYNAMIC_CAST(AP4_ContainerAtom, m_MoovAtom->GetChild(AP4_ATOM_TYPE_MVEX));
                        if (mvex) {
                            for (AP4_List<AP4_Atom>::Item* child_item = mvex->GetChildren().FirstItem();
                                                           child_item;
                                                           child_item = child_item->GetNext()) {
                                AP4_Atom* child_atom = child_item->GetData();
                                if (child_atom->GetType() == AP4_ATOM_TYPE_TREX) {
                                    trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, child_atom);
                                    if (trex && trex->GetTrackId() == tfhd->GetTrackId()) break;
                                    trex = NULL;
                                }
                            }
                        }
 
                        AP4_FragmentSampleTable& sampleTable = track->GetFragmentSampleTable();
                        if (bClearSampleTable) {
                            sampleTable.Clear();
                        }
 
                        AP4_Cardinal sample_count = 0;
                        for (AP4_List<AP4_Atom>::Item* child_item = traf->GetChildren().FirstItem();
                                                       child_item;
                                                       child_item = child_item->GetNext()) {
                            AP4_Atom* child_atom = child_item->GetData();
                            if (child_atom->GetType() == AP4_ATOM_TYPE_TRUN) {
                                AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, child_atom);
                                if (trun) {
                                    sample_count += trun->GetEntries().ItemCount();
                                }
                            }
                        }
 
                        if (!sample_count) {
                            return;
                        }
 
                        if (sampleTable.GetSampleCount() == 0) {
                            track->CreateFragmentFromStdSamples();
                        }
 
                        if (AP4_FAILED(sampleTable.EnsureCapacity(sample_count + sampleTable.GetSampleCount()))) {
                            return;
                        }
 
                        AP4_UI64 dts_origin = tfdt ? tfdt->GetBaseMediaDecodeTime() : dts;
                        for (AP4_List<AP4_Atom>::Item* child_item = traf->GetChildren().FirstItem();
                                                       child_item;
                                                       child_item = child_item->GetNext()) {
                            AP4_Atom* child_atom = child_item->GetData();
                            if (child_atom->GetType() == AP4_ATOM_TYPE_TRUN) {
                                AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, child_atom);
                                if (trun) {
                                    sampleTable.AddTrun(trun, tfhd, trex, stream, dts_origin, moof_offset, mdat_payload_offset);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
 
/*----------------------------------------------------------------------
|   AP4_Movie::SetSidxAtom
+---------------------------------------------------------------------*/
AP4_Result
AP4_Movie::SetSidxAtoms(std::map<AP4_UI32, AP4_SidxAtom*> sidxAtoms, AP4_ByteStream& stream)
{
    if (!m_fragmentsDataEntries.empty() || sidxAtoms.empty()) {
        return AP4_FAILURE;
    }
 
    m_Stream = &stream;
    m_Stream->AddReference();
 
    for (const auto[id, sidx] : sidxAtoms) {
        auto& fragmentsData = m_fragmentsDataEntries[id];
        fragmentsData.SidxAtom = sidx;
 
        AP4_UI32 mediaTimeScale = sidx->GetTimeScale();
        AP4_Track* track = GetTrack(id);
        if (track) {
            mediaTimeScale = track->GetMediaTimeScale();
        }
 
        AP4_Array<AP4_SidxAtom::Fragments>& fragments = sidx->GetSampleTable();
        auto& fragmentsEntries = fragmentsData.FragmentsIndexEntries;
        fragmentsEntries.SetItemCount(fragments.ItemCount());
        for (AP4_Cardinal i = 0; i < fragments.ItemCount(); i++) {
            AP4_SidxAtom::Fragments& fragment = fragments[i];
            auto convertTime = [&](AP4_Duration& time) {
                time = AP4_ConvertTime(time, sidx->GetTimeScale(), mediaTimeScale);
            };
 
            convertTime(fragment.m_StartTime);
            convertTime(fragment.m_Duration);
 
            AP4_IndexTableEntry& entry = fragmentsEntries[i];
            entry.m_cts = (AP4_SI64)fragment.m_StartTime;
            entry.m_index = i;
            entry.m_offset = fragment.m_Offset;
            entry.m_rt = (REFERENCE_TIME)(10000000.0 / sidx->GetTimeScale() * fragment.m_StartTime);
        }
 
        fragmentsData.MoofAtomEntries.SetItemCount(fragments.ItemCount());
        fragmentsData.MoofOffsetEntries.SetItemCount(fragments.ItemCount());
    }
 
    return AP4_SUCCESS;
}
 
/*----------------------------------------------------------------------
|   AP4_Movie::SelectMoof
+---------------------------------------------------------------------*/
AP4_Result
AP4_Movie::SelectMoof(const REFERENCE_TIME rt)
{
    if (!m_fragmentsDataEntries.empty()) {
        size_t cnt = 0;
        for (auto& [id, fragmentsData] : m_fragmentsDataEntries) {
            auto& CurrentMoof = fragmentsData.CurrentMoof;
            const AP4_TimeStamp ts = (AP4_TimeStamp)((double(rt) * fragmentsData.SidxAtom->GetTimeScale() + 5000000) / 10000000);
            AP4_Array<AP4_SidxAtom::Fragments>& fragments = fragmentsData.SidxAtom->GetSampleTable();
            for (AP4_Cardinal index = fragments.ItemCount() - 1; index >= 0; index--) {
                if (fragments[index].m_StartTime <= ts || index == 0) {
                    if (CurrentMoof == index) {
                        cnt++;
                        break;
                    }
                    if (AP4_SUCCEEDED(SwitchMoof(id, index, fragments[index].m_Offset, fragments[index].m_Size, fragments[index].m_StartTime))) {
                        CurrentMoof = index;
                        cnt++;
                        break;
                    }
                    break;
                }
            }
        }
 
        if (cnt == m_fragmentsDataEntries.size()) {
            return AP4_SUCCESS;
        }
    }
 
    return AP4_FAILURE;
}
 
/*----------------------------------------------------------------------
|   AP4_Movie::SwitchMoof
+---------------------------------------------------------------------*/
AP4_Result
AP4_Movie::SwitchMoof(AP4_UI32 id, AP4_Cardinal index, AP4_UI64 offset, AP4_UI64 size, AP4_Duration dts)
{
    if (!m_fragmentsDataEntries.empty()) {
        AP4_UI64 segment_size = size;
        AP4_Atom* atom = NULL;
        auto& fragmentsData = m_fragmentsDataEntries[id];
        auto& MoofAtomEntrie = fragmentsData.MoofAtomEntries;
        auto& MoofOffsetEntrie = fragmentsData.MoofOffsetEntries;
        if (MoofAtomEntrie[index]) {
            atom = MoofAtomEntrie[index];
            offset = MoofOffsetEntrie[index];
        } else {
            m_Stream->Seek(offset);
            if (AP4_SUCCEEDED(AP4_AtomFactory::DefaultFactory.CreateAtomFromStream(*m_Stream, size, atom, NULL))) {
                m_Stream->Tell(offset);
            }
        }
        if (atom && atom->GetType() == AP4_ATOM_TYPE_MOOF) {
            ProcessMoof(AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom),
                        *m_Stream, offset, dts, true);
 
            if (!MoofAtomEntrie[index]) {
                MoofAtomEntrie[index] = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
                MoofOffsetEntrie[index] = offset;
            }
 
            segment_size -= atom->GetSize();
 
            for (;;) {
                m_Stream->Seek(offset);
                AP4_Atom* next_atom = NULL;
                if (AP4_SUCCEEDED(AP4_AtomFactory::DefaultFactory.CreateAtomFromStream(*m_Stream, segment_size, next_atom, NULL))) {
                    if (next_atom->GetType() == AP4_ATOM_TYPE_MDAT && segment_size) {
                        delete next_atom;
                        if (AP4_SUCCEEDED(AP4_AtomFactory::DefaultFactory.CreateAtomFromStream(*m_Stream, segment_size, next_atom, NULL))) {
                            m_Stream->Tell(offset);
                            if (next_atom->GetType() == AP4_ATOM_TYPE_MOOF) {
                                ProcessMoof(AP4_DYNAMIC_CAST(AP4_ContainerAtom, next_atom),
                                            *m_Stream, offset, dts);
                                delete next_atom;
                                continue;
                            }
                            delete next_atom;
                        }
                    }
                    delete next_atom;
                }
                break;
            }
 
            return AP4_SUCCESS;
        }
 
        if (atom) {
            delete atom;
        }
    }
 
    return AP4_FAILURE;
}
 
/*----------------------------------------------------------------------
|   AP4_Movie::SwitchNextMoof
+---------------------------------------------------------------------*/
AP4_Result
AP4_Movie::SwitchNextMoof()
{
    if (!m_fragmentsDataEntries.empty()) {
        size_t cnt = 0;
        for (auto& [id, fragmentsData] : m_fragmentsDataEntries) {
            auto& CurrentMoof = fragmentsData.CurrentMoof;
            AP4_Array<AP4_SidxAtom::Fragments>& fragments = fragmentsData.SidxAtom->GetSampleTable();
            if (CurrentMoof >= 0 && CurrentMoof < fragments.ItemCount() - 1) {
                AP4_SidxAtom::Fragments& fragment = fragments[CurrentMoof + 1];
                if (AP4_SUCCEEDED(SwitchMoof(id, CurrentMoof + 1, fragment.m_Offset, fragment.m_Size, fragment.m_StartTime))) {
                    CurrentMoof++;
                    cnt++;
                }
            }
        }
 
        if (cnt == m_fragmentsDataEntries.size()) {
            return AP4_SUCCESS;
        }
    }
 
    return AP4_FAILURE;
}
 
/*----------------------------------------------------------------------
|   AP4_Movie::SwitchFirstMoof
+---------------------------------------------------------------------*/
AP4_Result
AP4_Movie::SwitchFirstMoof()
{
    if (!m_fragmentsDataEntries.empty()) {
        size_t cnt = 0;
        for (const auto& [id, fragmentsData] : m_fragmentsDataEntries) {
            AP4_Array<AP4_SidxAtom::Fragments>& fragments = fragmentsData.SidxAtom->GetSampleTable();
            if (fragments.ItemCount()) {
                AP4_SidxAtom::Fragments& fragment = fragments[0];
                if (AP4_SUCCEEDED(SwitchMoof(id, 0, fragment.m_Offset, fragment.m_Size, fragment.m_StartTime))) {
                    cnt++;
                }
            }
        }
 
        if (cnt == m_fragmentsDataEntries.size()) {
            return AP4_SUCCESS;
        }
    }
 
    return AP4_FAILURE;
}

V654 The condition 'index >= 0' of loop is always true.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: m_Tracks.

V586 The 'delete' operator is called twice for deallocation of the same memory space.