/*  Copyright (c) MediaArea.net SARL. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license that can
 *  be found in the License.html file in the root of the source tree.
 */

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// Information about DVD objects
// (.ifo files on DVD-Video)
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// Mainly from http://dvd.sourceforge.net/dvdinfo/ifo.html
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//---------------------------------------------------------------------------
// Pre-compilation
#include "MediaInfo/PreComp.h"
#ifdef __BORLANDC__
    #pragma hdrstop
#endif
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#include "MediaInfo/Setup.h"
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#include <vector>
using namespace std;
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#if defined(MEDIAINFO_DVDV_YES)
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#include "MediaInfo/Multiple/File_Dvdv.h"
#include "MediaInfo/MediaInfo_Config.h"
#include "MediaInfo/MediaInfo_Internal.h"
//---------------------------------------------------------------------------

namespace MediaInfoLib
{

//---------------------------------------------------------------------------
static const char*  IFO_VTS_Category[]=
{
    "Normal",
    "Karaoke",
};

static const char*  IFO_Format_V[]=
{
    "MPEG Video",
    "MPEG Video",
    "",
    "",
};

static const char*  IFO_Format_Version_V[]=
{
    "Version 1",
    "Version 2",
    "",
    "",
};

static const char*  IFO_CodecV[]=
{
    "MPEG-1V",
    "MPEG-2V",
    "",
    "",
};

static const char*  IFO_Standard[]=
{
    "NTSC",
    "PAL",
    "",
    "",
};

static const float32  IFO_AspectRatio[]=
{
    (float32)1.333,
    (float32)0.000,
    (float32)0.000,
    (float32)1.778,
};

static const char*  IFO_BitRate_Mode[]=
{
    "VBR",
    "CBR",
};

static const size_t IFO_Width[]=
{720, 704, 352, 352,   0,   0,   0,   0};

static const size_t IFO_Height[4][8]=
{{480, 480, 480, 240,   0,   0,   0,   0}, //NTSC
 {576, 576, 576, 288,   0,   0,   0,   0}, //PAL
 {  0,   0,   0,   0,   0,   0,   0,   0}, //Unknown
 {  0,   0,   0,   0,   0,   0,   0,   0}, //Unknown
 };

static const float64 IFO_FrameRate[]=
{29.970, 25.000};

static const char*  IFO_Format_A[]=
{
    "AC-3",
    "",
    "MPEG Audio",
    "MPEG Audio",
    "PCM",
    "",
    "DTS",
    "SDDS",
};

static const char*  IFO_Format_Profile_A[]=
{
    "",
    "",
    "Version 1",
    "Version 2",
    "",
    "",
    "",
    "",
};

static const char*  IFO_CodecA[]=
{
    "AC3",
    "",
    "MPEG-1A",
    "MPEG-2A",
    "LPCM (Big Endian)",
    "",
    "DTS",
    "SDDS",
};

static const char*  IFO_ModeA[]=
{
    "",
    "Karaoke",
    "Surround",
    "",
};

static const char*  IFO_ResolutionA[]=
{
    "16",
    "20",
    "24",
    "DRC",
};

static const int16u IFO_SamplingRate[]=
{48000, 0, 0, 0, 0, 0, 0, 0};

static const char*  IFO_Language_MoreA[]=
{
    "",
    "",
    "For visually impaired",
    "Director's comments",
    "Director's comments",
    "",
    "",
    "",
};

static const char*  IFO_Format_T[]=
{
    "RLE",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
};

static const char*  IFO_Resolution_T[]=
{
    "2",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
};

static const char*  IFO_CodecT[]=
{
    "2-bit RLE",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
};

static const char*  IFO_Language_MoreT[]=
{
    "",
    "Normal",
    "Large",
    "Children",
    "",
    "",
    "Large",
    "Children",
    "",
    "Forced",
    "",
    "",
    "",
    "Director comments",
    "Director comments large",
    "Director comments children",
};

static const size_t IFO_PlaybackTime_FrameRate[]=
{0, 25, 0, 30};

static const char*  IFO_MenuType[]=
{
    "",
    "",
    "",
    "root",
    "sub-picture",
    "audio",
    "angle",
    "PTT (chapter)",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
    "",
};

//---------------------------------------------------------------------------
extern const char*  AC3_ChannelPositions[];
extern const char*  AC3_ChannelPositions2[];

//***************************************************************************
// Constructor/Destructor
//***************************************************************************

//---------------------------------------------------------------------------
File_Dvdv::File_Dvdv()
:File__Analyze()
{
    //Temp
    VTS_Attributes_AreHere=false;
    Program_Pos=0;
    Time_Pos=0;
}

//***************************************************************************
// Streams management
//***************************************************************************

//---------------------------------------------------------------------------
void File_Dvdv::Streams_Finish()
{
    //Manage related VOB files
    if (!IsSub && !Config->File_IsReferenced_Get() && File_Name.size()>=5 && File_Name.find(__T("0.IFO"), File_Name.size()-5)!=string::npos)
    {
        Ztring VOB_File=File_Name.substr(0, File_Name.size()-5)+__T("1.VOB");

        MediaInfo_Internal MI;
        MI.Option(__T("File_IsReferenced"), __T("1"));
        if (MI.Open(VOB_File))
            Merge(MI);
        auto SeparatorPos=VOB_File.find_last_of(__T("/\\"));
        if (SeparatorPos!=string::npos)
        {
            auto FileSize=Retrieve_Const(Stream_General, 0, General_FileSize).To_int64u();
            FileSize+=MI.Get(Stream_General, 0, General_FileSize).To_int64u();
            Fill(Stream_General, 0, General_FileSize, FileSize, 10, true);
            VOB_File.erase(0, SeparatorPos+1);
            for (size_t StreamKind=Stream_General+1; StreamKind<Stream_Max; StreamKind++)
                for (size_t StreamPos=0; StreamPos<Count_Get((stream_t)StreamKind); StreamPos++)
                    Fill((stream_t)StreamKind, StreamPos, "Source", VOB_File);
        }
    }

    //Remove menus with short duration
    #if MEDIAINFO_ADVANCED
        auto Trigger=MediaInfoLib::Config.Collection_Trigger_Get();
    #else
        auto const Trigger=-2;
    #endif
    int64u MaxDuration;
    if (Trigger>=0)
        MaxDuration=(int64u)Trigger;
    else
    {
        MaxDuration=0;
        for (size_t Pos=0; Pos<Count_Get(Stream_Menu); Pos++)
        {
            int64u Duration=Retrieve_Const(Stream_Menu, Pos, Menu_Duration).To_int64u();
            if (MaxDuration<Duration)
                MaxDuration=Duration;
        }
        MaxDuration/=(int64u)(-Trigger);
    }
    for (size_t Pos=0; Pos<Count_Get(Stream_Menu);)
    {
        int64u Duration=Retrieve_Const(Stream_Menu, Pos, Menu_Duration).To_int64u();
        if (Duration>=MaxDuration)
            Pos++;
        else
            Stream_Erase(Stream_Menu, Pos);
    }

    //Fill stream duration
    map<Ztring, int64u> Durations;
    auto Menu_Count=Count_Get(Stream_Menu);
    for (size_t Pos=0; Pos<Menu_Count; Pos++)
    {
        const auto& Duration=Retrieve_Const(Stream_Menu, Pos, Menu_Duration);
        if (!Duration.empty())
            Durations[Retrieve_Const(Stream_Menu, Pos, "Source")]+=Duration.To_int64u();
    }
    for (size_t StreamKind=Stream_General+1; StreamKind<Stream_Max; StreamKind++)
    {
        if (StreamKind==Stream_Menu)
            continue;
        auto Count=Count_Get((stream_t)StreamKind);
        for (size_t Pos=0; Pos<Count; Pos++)
        {
            const auto Duration=Durations.find(Retrieve_Const((stream_t)StreamKind, Pos, "Source"));
            if (Duration!=Durations.end())
                Fill((stream_t)StreamKind, Pos, Fill_Parameter((stream_t)StreamKind, Generic_Duration), Duration->second, 10, true);
        }
    }
    if (!Durations.empty())
    {
        int64u Duration_Total=0;
        for (const auto& Duration : Durations)
            Duration_Total+=Duration.second;
        Fill(Stream_General, 0, General_Duration, Duration_Total, 10, true);
    }

    //Duration offsets
    TimeCode Duration_Total;
    for (const auto& Title : Titles)
    {
        Fill(Stream_Menu, Title.second.Pos, Menu_Delay, (int64s)Duration_Total.ToMilliseconds(), 10, true);
        Duration_Total+=Title.second.Duration;
    }

    //Purge what is not needed anymore
    if (!File_Name.empty()) //Only if this is not a buffer, with buffer we can have more data
        Sectors.clear();
}

//***************************************************************************
// Buffer
//***************************************************************************

//---------------------------------------------------------------------------
void File_Dvdv::FileHeader_Parse()
{
    //Parsing
    int64u Identifier;
    int32u Type;
    Get_C8 (Identifier,                                         "Identifier");
    Get_C4 (Type,                                               "Type");

    FILLING_BEGIN();
        //Identifier
        if (Identifier!=CC8("DVDVIDEO"))
        {
            Reject("DVD Video");
            return;
        }

        Accept("DVD Video");

        Fill(Stream_General, 0, General_Format, "DVD Video");

        //Versions
        switch (Type)
        {
            case Dvdv::VMG : VMG(); break;
            case Dvdv::VTS : VTS(); break;
            default        :
                        Reject("DVD Video");
                        return;
        }
    FILLING_END();
}

//---------------------------------------------------------------------------


//***************************************************************************
// Elements
//***************************************************************************

#define SUBELEMENT(_ELEMENT) \
    { \
        Element_Begin1(#_ELEMENT); \
        _ELEMENT(); \
        Element_End0(); \
    } \

//---------------------------------------------------------------------------
void File_Dvdv::VMG()
{
    int32u Sector_Pointer_LastSector, Sector_Pointer_TT_SRPT, Sector_Pointer_VMGM_PGCI_UT, Sector_Pointer_VMG_PTL_MAIT, Sector_Pointer_VMG_VTS_ATRT, Sector_Pointer_VMG_TXTDT_MG, Sector_Pointer_VMGM_C_ADT, Sector_Pointer_VMGM_VOBU_ADMAP;
    int16u Version, Audio_Count, Text_Count;
    Element_Info1("DVD Video - VMG");
    Element_Begin1("Header");
        Info_B4(LastSector,                                     "Last sector of VMG set (last sector of BUP)"); Param_Info2((LastSector+1)*2048, " bytes");
        Skip_XX(12,                                             "Unknown");
        Get_B4 (Sector_Pointer_LastSector,                      "last sector of IFO");
        Get_B2 (Version,                                        "version number"); Param_Info1(Ztring::ToZtring((Version&0x00F0)>>4)+__T(".")+Ztring::ToZtring(Version&0x000F));
        Info_B4(Category,                                       "VMG category");
        Skip_B2(                                                "number of volumes");
        Skip_B2(                                                "volume number");
        Skip_B1(                                                "side ID");
        Skip_XX(19,                                             "Unknown");
        Skip_B2(                                                "number of title sets");
        Skip_Local(32,                                          "Provider ID");
        Skip_B8(                                                "VMG POS");
        Skip_XX(24,                                             "Unknown");
        Skip_B4(                                                "end byte address of VMGI_MAT");
        Skip_B4(                                                "start address of FP_PGC (First Play program chain)");
        Skip_XX(56,                                             "Unknown");
        Info_B4(Sector_Pointer_Menu,                            "start sector of Menu VOB");
        Get_B4 (Sector_Pointer_TT_SRPT,                         "sector pointer to TT_SRPT (table of titles)");
        Get_B4 (Sector_Pointer_VMGM_PGCI_UT,                    "sector pointer to VMGM_PGCI_UT (Menu Program Chain table)");
        Get_B4 (Sector_Pointer_VMG_PTL_MAIT,                    "sector pointer to VMG_PTL_MAIT (Parental Management masks)");
        Get_B4 (Sector_Pointer_VMG_VTS_ATRT,                    "sector pointer to VMG_VTS_ATRT (copies of VTS audio/sub-picture attributes)");
        Get_B4 (Sector_Pointer_VMG_TXTDT_MG,                    "sector pointer to VMG_TXTDT_MG (text data)");
        Get_B4 (Sector_Pointer_VMGM_C_ADT,                      "sector pointer to VMGM_C_ADT (menu cell address table)");
        Get_B4 (Sector_Pointer_VMGM_VOBU_ADMAP,                 "sector pointer to VMGM_VOBU_ADMAP (menu VOBU address map)");
        Skip_XX(32,                                             "Unknown");
    Element_End0();

    //-VTSM
    VTS_Attributes_AreHere=true;
    Element_Begin1("VMGM (VMG for Menu)");
        Element_Begin1("Video streams");
            Element_Info2(1, " streams");
            SUBELEMENT(Video)
        Element_End0();
        Element_Begin1("Audio streams");
            Get_B2 (Audio_Count,                                "number of audio streams in VMGM_VOBS");
            Element_Info2(Audio_Count, " streams");
            for (int16u Pos=0; Pos<8; Pos++)
            {
                if (Pos<Audio_Count)
                    SUBELEMENT(Audio)
                else
                    Skip_XX(8,                                  "Reserved for Audio");
            }
            Skip_XX(16,                                         "Unknown");
        Element_End0();
        Element_Begin1("Text streams");
            Get_B2 (Text_Count,                                 "number of subpicture streams in VMGM_VOBS");
            Element_Info2(Text_Count, " streams");
            for (int16u Pos=0; Pos<1; Pos++)
            {
                if (Pos<Text_Count)
                    SUBELEMENT(Text)
                else
                    Skip_XX(6,                                  "Reserved for Text");
            }
            Skip_XX(164,                                        "Unknown");
        Element_End0();
    Element_End0();
    Skip_XX(2048-Element_Offset,                                "Junk");

    //Filling
    FILLING_BEGIN();
        Fill(Stream_General, 0, General_Format_Profile, "Menu");

        if (Version>0x001F)
            return;
        Sectors.resize(Sector_Pointer_LastSector+1);
        if (Sector_Pointer_TT_SRPT<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_TT_SRPT]=Sector_TT_SRPT;
        if (Sector_Pointer_VMGM_PGCI_UT<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VMGM_PGCI_UT]=Sector_VMGM_PGCI_UT;
        if (Sector_Pointer_VMG_PTL_MAIT<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VMG_PTL_MAIT]=Sector_VMG_PTL_MAIT;
        if (Sector_Pointer_VMG_VTS_ATRT<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VMG_VTS_ATRT]=Sector_VMG_VTS_ATRT;
        if (Sector_Pointer_VMG_TXTDT_MG<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VMG_TXTDT_MG]=Sector_VMG_TXTDT_MG;
        if (Sector_Pointer_VMGM_C_ADT<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VMGM_C_ADT]=Sector_VMGM_C_ADT;
        if (Sector_Pointer_VMGM_VOBU_ADMAP<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VMGM_VOBU_ADMAP]=Sector_VMGM_VOBU_ADMAP;
    FILLING_END();
}

//---------------------------------------------------------------------------
void File_Dvdv::VTS()
{
    //Parsing
    int32u Sector_Pointer_LastSector, Sector_Pointer_VTS_PTT_SRPT, Sector_Pointer_VTS_PGCI, Sector_Pointer_VTSM_PGCI_UT, Sector_Pointer_VTS_TMAPTI, Sector_Pointer_VTSM_C_ADT, Sector_Pointer_VTSM_VOBU_ADMAP, Sector_Pointer_VTS_C_ADT, Sector_Pointer_VTS_VOBU_ADMAP;
    int16u Version, Audio_Count, Text_Count;
    Element_Info1("DVD Video - VTS (Video Title Set)");
    Element_Begin1("Header");
        Info_B4(LastSector,                                     "Last sector of Title set (last sector of BUP)"); Param_Info2((LastSector+1)*2048, " bytes");
        Skip_XX(12,                                             "Unknown");
        Get_B4 (Sector_Pointer_LastSector,                      "last sector of IFO");
        Get_B2 (Version,                                        "version number"); Param_Info1(Ztring::ToZtring((Version&0x00F0)>>4)+__T(".")+Ztring::ToZtring(Version&0x000F));
        Info_B4(Category,                                       "VTS category");
        #if MEDIAINFO_TRACE
            if (Category<2) Param_Info1(IFO_VTS_Category[Category]);
        #endif //MEDIAINFO_TRACE
        Skip_XX(90,                                             "Unknown");
        Skip_B4(                                                "end byte address of VTS_MAT");
        Skip_XX(60,                                             "Unknown");
        Info_B4(StartSector_Menu,                               "start sector of Menu VOB"); Param_Info2((StartSector_Menu+1)*2048, " bytes");
        Info_B4(StartSector_Title,                              "start sector of Title Vob"); Param_Info2((StartSector_Title+1)*2048, " bytes");
        Get_B4 (Sector_Pointer_VTS_PTT_SRPT,                    "sector pointer to VTS_PTT_SRPT (Table of Titles and Chapters)");
        Get_B4 (Sector_Pointer_VTS_PGCI,                        "sector pointer to VTS_PGCI (Title Program Chain table)");
        Get_B4 (Sector_Pointer_VTSM_PGCI_UT,                    "sector pointer to VTSM_PGCI_UT (Menu Program Chain table)");
        Get_B4 (Sector_Pointer_VTS_TMAPTI,                      "sector pointer to VTS_TMAPTI (Time map)");
        Get_B4 (Sector_Pointer_VTSM_C_ADT,                      "sector pointer to VTSM_C_ADT (Menu cell address table)");
        Get_B4 (Sector_Pointer_VTSM_VOBU_ADMAP,                 "sector pointer to VTSM_VOBU_ADMAP(menu VOBU address map)");
        Get_B4 (Sector_Pointer_VTS_C_ADT,                       "sector pointer to VTS_C_ADT (Title set cell address table)");
        Get_B4 (Sector_Pointer_VTS_VOBU_ADMAP,                  "sector pointer to VTS_VOBU_ADMAP (Title set VOBU address map)");
        Skip_XX(24,                                             "Unknown");
    Element_End0();

    //-VTSM
    Element_Begin1("VTSM (VTS for Menu, Vob 0)");
        Element_Begin1("Video streams");
            Element_Info2(1, " streams");
            SUBELEMENT(Video)
        Element_End0();
        Element_Begin1("Audio streams");
            Get_B2 (Audio_Count,                                "number of audio streams in VTSM_VOBS");
            Element_Info2(Audio_Count, " streams");
            for (int16u Pos=0; Pos<8; Pos++)
            {
                if (Pos<Audio_Count)
                    SUBELEMENT(Audio)
                else
                    Skip_XX(8,                                  "Reserved for Audio");
            }
            Skip_XX(16,                                         "Unknown");
        Element_End0();
        Element_Begin1("Text streams");
            Get_B2 (Text_Count,                                 "number of subpicture streams in VTSM_VOBS");
            Element_Info2(Text_Count, " streams");
            for (int16u Pos=0; Pos<1; Pos++)
            {
                if (Pos<Text_Count)
                    SUBELEMENT(Text)
                else
                    Skip_XX(6,                                  "Reserved for Text");
            }
            Skip_XX(164,                                        "Unknown");
        Element_End0();
    Element_End0();

    //-VTS
    VTS_Attributes_AreHere=true;
    Element_Begin1("VTS (VTS for movie, Vob 1-9)");
        Element_Begin1("Video streams");
            Element_Info2(1, " streams");
            SUBELEMENT(Video)
        Element_End0();
        Element_Begin1("Audio streams");
            Get_B2 (Audio_Count,                                "number of audio streams in VMGM_VOBS");
            Element_Info2(Audio_Count, " streams");
            for (int16u Pos=0; Pos<8; Pos++)
            {
                if (Pos<Audio_Count)
                    SUBELEMENT(Audio)
                else
                    Skip_XX(8,                                  "Reserved for Audio");
            }
            Skip_XX(16,                                         "Unknown");
        Element_End0();
        Element_Begin1("Text streams");
            Get_B2 (Text_Count,                                 "number of subpicture streams in VMGM_VOBS");
            Element_Info2(Text_Count, " streams");
            for (int16u Pos=0; Pos<32; Pos++)
            {
                if (Pos<Text_Count)
                    SUBELEMENT(Text)
                else
                    Skip_XX(6,                                  "Reserved for Text");
            }
            Skip_XX(2,                                          "Unknown");
        Element_End0();
        Element_Begin1("MultiChannel Info");
            Element_Info2(Audio_Count, " streams");
            for (int16u Pos=0; Pos<8; Pos++)
            {
                if (Pos<Audio_Count)
                    SUBELEMENT(MultiChannel)
                else
                    Skip_XX(24,                                 "Reserved for multichannel extension");
            }
        Element_End0();
    Element_End0();
    Skip_XX(2048-Element_Offset,                                "Junk");

    //Filling
    FILLING_BEGIN();
        Fill(Stream_General, 0, General_Format_Profile, "Program");

        if (Version>0x001F)
            return;
        if (Sector_Pointer_LastSector==(int32u)-1 || Sector_Pointer_LastSector+1>File_Size/2048)
            Sector_Pointer_LastSector=(int32u)(File_Size/2048);
        Sectors.resize(Sector_Pointer_LastSector+1);
        if (Sector_Pointer_VTS_PTT_SRPT<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VTS_PTT_SRPT]=Sector_VTS_PTT_SRPT;
        if (Sector_Pointer_VTS_PGCI<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VTS_PGCI]=Sector_VTS_PGCI;
        if (Sector_Pointer_VTSM_PGCI_UT<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VTSM_PGCI_UT]=Sector_VTSM_PGCI_UT;
        if (Sector_Pointer_VTS_TMAPTI<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VTS_TMAPTI]=Sector_VTS_TMAPTI;
        if (Sector_Pointer_VTSM_C_ADT<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VTSM_C_ADT]=Sector_VTSM_C_ADT;
        if (Sector_Pointer_VTSM_VOBU_ADMAP<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VTSM_VOBU_ADMAP]=Sector_VTSM_VOBU_ADMAP;
        if (Sector_Pointer_VTS_C_ADT<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VTS_C_ADT]=Sector_VTS_C_ADT;
        if (Sector_Pointer_VTS_VOBU_ADMAP<=Sector_Pointer_LastSector)
            Sectors[Sector_Pointer_VTS_VOBU_ADMAP]=Sector_VTS_VOBU_ADMAP;
    FILLING_END();
}

//---------------------------------------------------------------------------
void File_Dvdv::Video()
{
    //Parsing
    int32u Codec, Standard, AspectRatio, Resolution, BitRate_Mode;
    BS_Begin();
    Get_BS (2, Codec,                                           "Coding mode"); Param_Info1(IFO_CodecV[Codec]);
    Get_BS (2, Standard,                                        "Standard"); Param_Info1(IFO_Standard[Standard]);
    Get_BS (2, AspectRatio,                                     "Aspect ratio"); Param_Info1(IFO_AspectRatio[AspectRatio]);
    Info_BS(1, Pan,                                             "Automatic Pan/Scan"); Param_Info1(Pan?"No":"Yes");
    Info_BS(1, Letter,                                          "Automatic Letterbox"); Param_Info1(Letter?"No":"Yes");
    Skip_BS(1,                                                  "CC for line 21 field 1 in GOP (NTSC only)");
    Skip_BS(1,                                                  "CC for line 21 field 2 in GOP (NTSC only)");
    Get_BS (3, Resolution,                                      "Resolution"); Param_Info1(Ztring::ToZtring(IFO_Width[Resolution])+__T("x")+Ztring::ToZtring(IFO_Height[Standard][Resolution]));
    Info_BS(1, Letterboxed,                                     "Letterboxed"); Param_Info1(Letter?"Yes":"No");
    Get_BS (1, BitRate_Mode,                                    "Bitrate mode"); Param_Info1(IFO_BitRate_Mode[BitRate_Mode]);
    Info_BS(1, Camera,                                          "Camera/Film"); Param_Info1(Letter?"Film":"Camera");
    BS_End();

    //Filling
    FILLING_BEGIN();
        if (VTS_Attributes_AreHere)
        {
            Stream_Prepare(Stream_Video);
            Fill(Stream_Video, StreamPos_Last, Video_Format, IFO_Format_V[Codec]);
            Fill(Stream_Video, StreamPos_Last, Video_Format_Version, IFO_Format_Version_V[Codec]);
            Fill(Stream_Video, StreamPos_Last, Video_Codec, IFO_CodecV[Codec]);
            Fill(Stream_Video, StreamPos_Last, Video_Width, IFO_Width[Resolution]);
            Fill(Stream_Video, StreamPos_Last, Video_Height, IFO_Height[Standard][Resolution]);
            Fill(Stream_Video, StreamPos_Last, Video_DisplayAspectRatio, IFO_AspectRatio[AspectRatio], 3, true);
            Fill(Stream_Video, StreamPos_Last, Video_FrameRate, IFO_FrameRate[Standard]);
            Fill(Stream_Video, StreamPos_Last, Video_BitRate_Mode, IFO_BitRate_Mode[BitRate_Mode]);
            Fill(Stream_Video, StreamPos_Last, General_ID, __T("224"));
            Fill(Stream_Video, StreamPos_Last, General_ID_String, __T("224 (0xE0)"), Unlimited, true);
        }
    FILLING_END();
}

//---------------------------------------------------------------------------
void File_Dvdv::Audio()
{
    //Parsing
    Ztring Language;
    int32u Codec, LanguageType, Mode, Resolution, SamplingRate, Channels;
    int8u Language_Extension, ChannelsK=(int8u)-1;
    BS_Begin();
    Get_BS (3, Codec,                                           "Coding mode"); Param_Info1(IFO_CodecA[Codec]);
    Info_BS(1, MultiChannel,                                    "Multichannel extension present"); Param_Info1(MultiChannel?"Yes":"No");
    Get_BS (2, LanguageType,                                    "Language type"); Param_Info1(LanguageType==1?"2CC":"Unknown");
    Get_BS (2, Mode,                                            "Application mode"); Param_Info1(IFO_ModeA[Mode]);
    Get_BS (2, Resolution,                                      "Resolution"); Param_Info1C((Codec==2 || Codec==3), IFO_ResolutionA[Resolution]); Param_Info1C((Codec==4), Mode?"DRC":"No DRC");
    Get_BS (2, SamplingRate,                                    "Sampling rate"); Param_Info1(Ztring::ToZtring(IFO_SamplingRate[SamplingRate]));
    Get_BS (4, Channels,                                        "Channels"); Param_Info2(Channels+1, " channels");
    BS_End();
    Get_UTF8(3, Language,                                       "Language code");
    if (!Language.empty() && (unsigned)Language[0]>=0x80)
        Language.clear(); //this is 0xFF...
    if (Language==__T("iw"))
        Language=__T("he"); //Hebrew patch, is "iw" in DVDs
    Get_B1 (Language_Extension,                                 "Language extension"); Param_Info1C((Language_Extension<8), IFO_Language_MoreA[Language_Extension]);
    Skip_B1(                                                    "Unknown");
    switch (Mode)
    {
        case 1 : //Karaoke
            {
            BS_Begin();
            Skip_BS(1,                                          "Zero");
            Get_S1 (3, ChannelsK,                               "Channels");
                #ifdef MEDIAINFO_AC3_YES
                    Param_Info1(AC3_ChannelPositions[ChannelsK]);
                #endif //MEDIAINFO_AC3_YES
            Skip_BS(2,                                          "Version");
            Info_BS(1, MC,                                      "MC intro present"); Param_Info1(MC?"Yes":"No");
            Info_BS(1, Duet,                                    "Duet"); Param_Info1(Duet?"Duet":"Solo");
            BS_End();
            }
            break;
        case 2 : //Surround
            {
            BS_Begin();
            Skip_BS(4,                                          "Reserved");
            Info_BS(1, DolbyDecode,                             "Suitable for Dolby surround decoding"); Param_Info1(DolbyDecode?"Yes":"No");
            Skip_BS(3,                                          "Reserved");
            BS_End();
            }
            break;
    default:
            {
            Skip_B1(                                            "Reserved");
            }
    }

    //Filling
    FILLING_BEGIN();
        if (VTS_Attributes_AreHere)
        {
            Stream_Prepare(Stream_Audio);
            Fill(Stream_Audio, StreamPos_Last, Audio_Format, IFO_Format_A[Codec]);
            Fill(Stream_Audio, StreamPos_Last, Audio_Format_Profile, IFO_Format_Profile_A[Codec]);
            Fill(Stream_Audio, StreamPos_Last, Audio_Codec, IFO_CodecA[Codec]);
            Fill(Stream_Audio, StreamPos_Last, Audio_SamplingRate, IFO_SamplingRate[SamplingRate]);
            Fill(Stream_Audio, StreamPos_Last, Audio_Channel_s_, Channels+1);
            if (Codec==3)
                Fill(Stream_Audio, StreamPos_Last, Audio_BitDepth, IFO_ResolutionA[Resolution]);
            else if (Codec==4 && Mode)
                Fill(Stream_Audio, StreamPos_Last, Audio_BitDepth, "DRC");
            Fill(Stream_Audio, StreamPos_Last, Audio_Language, Language);
            if (Language_Extension<8)
                Fill(Stream_Audio, StreamPos_Last, Audio_Language_More, IFO_Language_MoreA[Language_Extension]);
            #ifdef MEDIAINFO_AC3_YES
                if (Codec==0 && ChannelsK!=(int8u)-1) //AC-3
                {
                    Fill(Stream_Audio, 0, Audio_ChannelPositions, AC3_ChannelPositions[ChannelsK]);
                    Fill(Stream_Audio, 0, Audio_ChannelPositions_String2, AC3_ChannelPositions2[ChannelsK]);
                }
            #endif //MEDIAINFO_AC3_YES
        }
    FILLING_END();
}

//---------------------------------------------------------------------------
void File_Dvdv::Text()
{
    //Parsing
    Ztring Language;
    int32u Codec, LanguageType;
    int8u Language_Extension;
    BS_Begin();
    Get_BS (3, Codec,                                           "Coding mode"); Param_Info1(IFO_CodecT[Codec]);
    Skip_BS(3,                                                  "Reserved");
    Get_BS (2, LanguageType,                                    "Language type"); Param_Info1(LanguageType==1?"2CC":"Unknown");
    BS_End();
    Skip_B1(                                                    "Reserved");
    Get_UTF8(3, Language,                                       "Language code");
    if (!Language.empty() && (unsigned)Language[0]>=0x80)
        Language.clear(); //this is 0xFF...
    if (Language==__T("iw"))
        Language=__T("he"); //Hebrew patch, is "iw" in DVDs
    Get_B1 (Language_Extension,                                 "Language extension"); Param_Info1C((Language_Extension<16), IFO_Language_MoreT[Language_Extension]);

    //Filling
    FILLING_BEGIN();
        if (VTS_Attributes_AreHere)
        {
            Stream_Prepare(Stream_Text);
            Fill(Stream_Text, StreamPos_Last, Text_Format, IFO_Format_T[Codec]);
            Fill(Stream_Text, StreamPos_Last, Text_BitDepth, IFO_Resolution_T[Codec]);
            Fill(Stream_Text, StreamPos_Last, Text_Codec, IFO_CodecT[Codec]);
            Fill(Stream_Text, StreamPos_Last, Text_Language, Language);

            if (Language_Extension<16)
                 Fill(Stream_Text, StreamPos_Last, Text_Language_More, IFO_Language_MoreT[Language_Extension]);
        }
    FILLING_END();
}

//---------------------------------------------------------------------------
void File_Dvdv::MultiChannel()
{
    //Parsing
    BS_Begin();
    Element_Begin1("ACH0");
        Skip_BS(7,                                              "Reserved");
        Skip_BS(1,                                              "ACH0 Guide Melody exists");
    Element_End0();
    Element_Begin1("ACH1");
        Skip_BS(7,                                              "Reserved");
        Skip_BS(1,                                              "ACH1 Guide Melody exists");
    Element_End0();
    Element_Begin1("ACH2");
        Skip_BS(4,                                              "Reserved");
        Skip_BS(1,                                              "ACH2 Guide Vocal 1 exists");
        Skip_BS(1,                                              "ACH2 Guide Vocal 2 exists");
        Skip_BS(1,                                              "ACH2 Guide Melody 1 exists");
        Skip_BS(1,                                              "ACH2 Guide Melody 2 exists");
    Element_End0();
    Element_Begin1("ACH3");
        Skip_BS(4,                                              "Reserved");
        Skip_BS(1,                                              "ACH3 Guide Vocal 1 exists");
        Skip_BS(1,                                              "ACH3 Guide Vocal 2 exists");
        Skip_BS(1,                                              "ACH3 Guide Melody A exists");
        Skip_BS(1,                                              "ACH3 Sound Effect A exists");
    Element_End0();
    Element_Begin1("ACH4");
        Skip_BS(4,                                              "Reserved");
        Skip_BS(1,                                              "ACH4 Guide Vocal 1 exists");
        Skip_BS(1,                                              "ACH4 Guide Vocal 2 exists");
        Skip_BS(1,                                              "ACH4 Guide Melody B exists");
        Skip_BS(1,                                              "ACH4 Sound Effect B exists");
    Element_End0();
    BS_End();
    Skip_XX(19,                                                 "Unknown");
}

//***************************************************************************
// Buffer
//***************************************************************************

//---------------------------------------------------------------------------
void File_Dvdv::Header_Parse()
{
    //Calculating
    size_t Sector_Pos=(size_t)((File_Offset+Buffer_Offset)/2048);
    size_t Sector_Count=1;
    while (Sector_Pos+Sector_Count<Sectors.size() && Sectors[Sector_Pos+Sector_Count]==Sector_Nothing)
        Sector_Count++;

    //Filling
    Header_Fill_Size(Sector_Count*2048);
}

//---------------------------------------------------------------------------
void File_Dvdv::Data_Parse()
{
    //Parsing
    size_t Sector_Pos=(size_t)((File_Offset+Buffer_Offset)/2048);
    if (Sector_Pos>=Sectors.size())
    {
        Accept("DVD Video");
        Finish("DVD Video");
        return;
    }

    //Parsing
    switch(Sectors[Sector_Pos])
    {
        case Sector_VTS_PTT_SRPT    : VTS_PTT_SRPT(); break;
        case Sector_VTS_PGCI        : VTS_PGCI(); break;
        case Sector_VTSM_PGCI_UT    : VTSM_PGCI_UT(); break;
        case Sector_VTS_TMAPTI      : VTS_TMAPTI(); break;
        case Sector_VTSM_C_ADT      : VTSM_C_ADT(); break;
        case Sector_VTSM_VOBU_ADMAP : VTSM_VOBU_ADMAP(); break;
        case Sector_VTS_C_ADT       : VTS_C_ADT(); break;
        case Sector_VTS_VOBU_ADMAP  : VTS_VOBU_ADMAP(); break;
        case Sector_TT_SRPT         : TT_SRPT(); break;
        case Sector_VMGM_PGCI_UT    : VMGM_PGCI_UT(); break;
        case Sector_VMG_PTL_MAIT    : VMG_PTL_MAIT(); break;
        case Sector_VMG_VTS_ATRT    : VMG_VTS_ATRT(); break;
        case Sector_VMG_TXTDT_MG    : VMG_TXTDT_MG(); break;
        case Sector_VMGM_C_ADT      : VMGM_C_ADT(); break;
        case Sector_VMGM_VOBU_ADMAP : VMGM_VOBU_ADMAP(); break;
        default                     : ;
    }
}

//***************************************************************************
// Elements
//***************************************************************************

//---------------------------------------------------------------------------
void File_Dvdv::VTS_PTT_SRPT ()
{
    Element_Name("Table of Titles and Chapters");

    //Parsing
    int32u Element_RealSize;
    Element_Begin1("Header");
        Skip_B2(                                                "Count of elements");
        Skip_B2(                                                "Unknown");
        Get_B4 (Element_RealSize,                               "End address");
        Element_RealSize++; //Last byte
    Element_End0();
    Element_Begin1("Extra data");
        int32u Offset;
        Get_B4 (Offset,                                         "Offset of first element");
        int64u Extra_Size=Offset-Element_Offset;
        if (Extra_Size>0)
            Skip_XX(Extra_Size,                                 "Extra data (Unknown)");
    Element_End0();

    //For each chapter
    while (Element_Offset<Element_RealSize)
    {
        //VTS_PTT
        int16u PGCN, PGN;
        Element_Begin0();
        Get_B2 (PGCN,                                           "Program Chain (PGCN)");
        Get_B2 (PGN,                                            "Program (PGN)");
        Element_Name("Chapter"); Element_Info1(Ztring::ToZtring(PGCN)); Element_Info1(Ztring::ToZtring(PGN));
        Element_End0();
    }
}

//---------------------------------------------------------------------------
void File_Dvdv::VTS_PGCI ()
{
    Element_Name("Title Program Chain table");

    //Parsing
    int32u EndAddress;
    Element_Begin1("Header");
        int32u Offset;
        Skip_B2(                                                "Number of Program Chains");
        Skip_B2(                                                "Reserved");
        Get_B4 (EndAddress,                                     "End address");
        if (EndAddress>=Element_Size)
            EndAddress=(int32u)Element_Size-1;
        Element_Begin1("PGC category");
            BS_Begin();
            Skip_BS(1,                                          "entry PGC");
            Skip_BS(7,                                          "title number");
            BS_End();
            Skip_B1(                                            "Unknown");
            Skip_B2(                                            "parental management mask");
        Element_End0();
        Get_B4 (Offset,                                         "offset to VTS_PGC - relative to VTS_PGCI");
        if (Offset-16>0)
            Skip_XX(Offset-16,                                  "Unknown");
    Element_End0();

    //For each Program
    //DETAILLEVEL_SET(1.0);
    while (Element_Offset<=EndAddress)
    {
        PGC(true);
    }
}

//---------------------------------------------------------------------------
void File_Dvdv::VTSM_PGCI_UT ()
{
    Element_Name("Menu Program Chain table");

    //Parsing
    int16u LU_Count;
    Element_Begin1("Header");
        int32u EndAddress, Offset;
        int8u Flags;
        Get_B2 (LU_Count,                                       "Number of Language Units");
        Skip_B2(                                                "Reserved");
        Get_B4 (EndAddress,                                     "End address");
        if (EndAddress>=Element_Size)
            EndAddress=(int32u)Element_Size-1;
        Skip_C3(                                                "Language");
        Get_B1 (Flags,                                          "Menu existence flags");
            Skip_Flags(Flags, 3,                                "PTT");
            Skip_Flags(Flags, 4,                                "angle");
            Skip_Flags(Flags, 5,                                "audio");
            Skip_Flags(Flags, 6,                                "sub-picture");
            Skip_Flags(Flags, 7,                                "root");
        Get_B4 (Offset,                                         "Offset to VTSM_LU relative to VTSM_PGCI_UT");
        if (Offset-16>0)
            Skip_XX(Offset-16,                                  "Unknown");
    Element_End0();

    for (int16u LU_Pos=0; LU_Pos<LU_Count; LU_Pos++)
    {
        Element_Begin1("Language Unit");
        int32u LU_Size;
        int16u PGC_Count;
        Element_Begin1("Header");
            Get_B2 (PGC_Count,                                  "Number of Program Chains");
            Skip_B2(                                            "Reserved");
            Get_B4 (LU_Size,                                    "end address (last byte of last PGC in this LU) relative to VTSM_LU");
            LU_Size++; //Last byte
            Element_Begin1("PGC category");
                int32u EntryPGC;
                BS_Begin();
                Get_BS (1, EntryPGC,                            "Entry PGC");
                Skip_BS(3,                                      "Unknown");
                if (EntryPGC)
                {
                    Info_BS(4, MenuType,                        "menu type"); Param_Info1(IFO_MenuType[MenuType]);
                }
                else
                {
                    Skip_BS(4,                                  "Reserved");
                }
                BS_End();
                Skip_B1(                                        "Unknown");
                Skip_B2(                                        "parental management mask");
            Element_End0();
            Get_B4 (Offset,                                     "offset to VTSM_PGC relative to VTSM_LU");
            if (Offset-16>0)
                Skip_XX(Offset-16,                              "Unknown");
        Element_End0();
        for (int16u PGC_Pos=0; PGC_Pos<PGC_Count; PGC_Pos++)
            PGC();

        Element_End0();
    }
}

//---------------------------------------------------------------------------
void File_Dvdv::VTS_TMAPTI ()
{
    Element_Name("Time map");

    //Parsing
    Element_Begin1("Header");
        int32u EndAddress, Offset;
        Skip_B2(                                                "Number of program chains");
        Skip_B2(                                                "Reserved");
        Get_B4 (EndAddress,                                     "End address");
        if (EndAddress>=Element_Size)
            EndAddress=(int32u)Element_Size-1;
        Get_B4 (Offset,                                         "Offset to VTS_TMAP 1");
        if (Offset-12>0)
            Skip_XX(Offset-12,                                  "Unknown");
    Element_End0();

    //DETAILLEVEL_SET(1.0);
    while (Element_Offset<=EndAddress)
    {
        //VTS_TMAP
        Element_Begin1("Time Map");
        //std::vector<size_t> Sector_Times;
        int8u Sector_Times_SecondsPerTime;
        int16u Count;
        Get_B1 (Sector_Times_SecondsPerTime,                    "Time unit (seconds)");
        Skip_B1(                                                "Unknown");
        Get_B2 (Count,                                          "Number of entries in map");
        //Sector_Times.resize(Count);
        BS_Begin();
        for (int16u Pos=0; Pos<Count; Pos++)
        {
            Element_Begin1("Sector Offset");
            int32u SectorOffset;
            Skip_BS( 1,                                         "discontinuous with previous");
            Get_BS (31, SectorOffset,                           "Sector offset within VOBS of nearest VOBU");
            //Get_B4 (Sector_Times[Pos],                          Sector offset within VOBS of nearest VOBU);// Param_Info1(Ztring().Duration_From_Milliseconds((Pos+1)*Sectors_Times_SecondsPerTime[Program_Pos]));
            //Sector_Times[Pos]&=0x7FFFFFFF; //bit 31 is set if VOBU time codes are discontinuous with previous
            Element_Info1(SectorOffset);
            Element_End0();
        }
        BS_End();
        Element_End0();

        //Filling
        //Sectors_Times.push_back(Sector_Times);
        //Sectors_Times_SecondsPerTime.push_back(Sector_Times_SecondsPerTime);
    }
}

//---------------------------------------------------------------------------
void File_Dvdv::VTSM_C_ADT ()
{
    Element_Name("Menu cell address table");

    //Parsing
    int32u EndAddress;
    Element_Begin1("Header");
        Skip_B2(                                                "Number of cells");
        Skip_B2(                                                "Reserved");
        Get_B4 (EndAddress,                                     "End address");
        if (EndAddress>=Element_Size)
            EndAddress=(int32u)Element_Size-1;
    Element_End0();

    //DETAILLEVEL_SET(1.0);
    while (Element_Offset<=EndAddress)
    {
        //ADT
        Element_Begin1("Entry");
        Skip_B2(                                                "VOBidn");
        Skip_B1(                                                "CELLidn");
        Skip_B1(                                                "Unknown");
        Skip_B4(                                                "Starting sector within VOB");
        Skip_B4(                                                "Ending sector within VOB");
        Element_End0();
    }
}

//---------------------------------------------------------------------------
void File_Dvdv::VTSM_VOBU_ADMAP ()
{
    Element_Name("Menu VOBU address map");

    //Parsing
    int32u EndAddress;
    Element_Begin1("Header");
        Get_B4 (EndAddress,                                     "End address");
        if (EndAddress>=Element_Size)
            EndAddress=(int32u)Element_Size-1;
    Element_End0();

    //DETAILLEVEL_SET(1.0);
    while (Element_Offset<=EndAddress)
    {
        //ADMAP
        Skip_B4(                                                "Starting sector within VOB of first VOBU");
    }
}

//---------------------------------------------------------------------------
void File_Dvdv::VTS_C_ADT ()
{
    Element_Name("Title set cell address table");

    //Parsing
    int32u EndAddress;
    Element_Begin1("Header");
        Skip_B2(                                                "Number of cells");
        Skip_B2(                                                "Reserved");
        Get_B4 (EndAddress,                                     "End address");
        if (EndAddress>=Element_Size)
            EndAddress=(int32u)Element_Size-1;
    Element_End0();

    //DETAILLEVEL_SET(1.0);
    while (Element_Offset<=EndAddress)
    {
        //ADT
        Element_Begin1("Entry");
        int32u Start, End;
        int16u VOBidn;
        int8u CELLidn;
        Get_B2 (VOBidn,                                         "VOBidn");
        Get_B1 (CELLidn,                                        "CELLidn");
        Skip_B1(                                                "Unknown");
        Get_B4 (Start,                                          "Starting sector within VOB"); Param_Info1(Time_ADT(Start));
        Get_B4 (End,                                            "Ending sector within VOB"); Param_Info1(Time_ADT(End));
        Element_End0();

        //Filling
        FILLING_BEGIN();
        FILLING_END();
        //Fill(Ztring::ToZtring(CELLidn).To_Local().c_str(), Time_ADT(Start));
    }
}

//---------------------------------------------------------------------------
void File_Dvdv::VTS_VOBU_ADMAP ()
{
    Element_Name("Title set VOBU address map");

    //Parsing
    int32u EndAddress;
    Element_Begin1("Header");
        Get_B4 (EndAddress,                                     "End address");
        if (EndAddress>=Element_Size)
            EndAddress=(int32u)Element_Size-1;
    Element_End0();

    //DETAILLEVEL_SET(1.0);
    while (Element_Offset<Element_Size)
    {
        //ADMAP
        Skip_B4(                                                "Starting sector within VOB of first VOBU");
    }
}

//***************************************************************************
// Helpers
//***************************************************************************

//---------------------------------------------------------------------------
Ztring File_Dvdv::Time_ADT(int32u)
{
    return Ztring();
    /*
    if (Sectors_Times.empty())
        return Ztring(); //TODO: it can be empty?

    while (Time_Pos<Sectors_Times[Program_Pos].size() && Sectors_Times[Program_Pos][Time_Pos]<Value)
        Time_Pos++;
    if (Time_Pos<Sectors_Times[Program_Pos].size())
    {
        int32u Time=(Time_Pos+1)*Sectors_Times_SecondsPerTime[Program_Pos]*1000;
        float32 Part;
        //True time is between Time and Time+Sectors_Times_SecondsPerTime, finding where...
        int32u Sectors_Count;
        if (Time_Pos==0)
            Sectors_Count=Sectors_Times[Program_Pos][Time_Pos];
        else
            Sectors_Count=Sectors_Times[Program_Pos][Time_Pos]-Sectors_Times[Program_Pos][Time_Pos-1];
        Part=(Sectors_Times[Program_Pos][Time_Pos]-Value); //Count of more sectors after
        Part/=Sectors_Count; //Percentage
        Time=(int32u)((Time_Pos+1-Part)*Sectors_Times_SecondsPerTime[Program_Pos]*1000);

        return Ztring().Duration_From_Milliseconds(Time*1000);
    }
    else
    {
        int32u Time=(Time_Pos+1)*Sectors_Times_SecondsPerTime[Program_Pos]*1000;
        float32 Part;
        //True time is between Time and Time+Sectors_Times_SecondsPerTime, finding where... but with the last offset diffrence
        int32u Sectors_Count=Sectors_Times[Program_Pos][Time_Pos-1]-Sectors_Times[Program_Pos][Time_Pos-2];
        Part=((int32s)Sectors_Times[Program_Pos][Time_Pos-1])-((int32s)Value); //Count of more sectors after
        Part/=Sectors_Count; //Percentage
        Part+=1; //We were one offset less
        Time=(int32u)((Time_Pos+1-Part)*Sectors_Times_SecondsPerTime[Program_Pos]*1000);

        return Ztring().Duration_From_Milliseconds(Time*1000);
    }
    */
}


//---------------------------------------------------------------------------
static unsigned BCD_to_Decimal(unsigned Value)
{
    int Tens=Value>>4;
    int Units=Value&0xF;
    if (Tens>=10 || Units>=10)
        return (unsigned)-1;
    return Tens*10+Units;
}

void File_Dvdv::Get_Duration(TimeCode &Duration, const Ztring &Name)
{
    int32u FrameRate, FF;
    int8u HH, MM, SS;
    Element_Begin1(Name);
        Get_C1 (HH,                                     "Hours (BCD)");
        Get_C1 (MM,                                     "Minutes (BCD)");
        Get_C1 (SS,                                     "Seconds (BCD)");
        BS_Begin();
        Get_BS (2, FrameRate,                           "Frame rate"); Param_Info2C(IFO_PlaybackTime_FrameRate[FrameRate], IFO_PlaybackTime_FrameRate[FrameRate], " fps");
        Get_BS (6, FF,                                  "Frames (BCD)");
        BS_End();

        auto hh=BCD_to_Decimal(HH);
        auto mm=BCD_to_Decimal(MM);
        auto ss=BCD_to_Decimal(SS);
        auto ff=BCD_to_Decimal(FF);
        if (hh==(int8u)-1 || mm==(int8u)-1 || ss==(int8u)-1 || ff==(int8u)-1 || !IFO_PlaybackTime_FrameRate[FrameRate])
        {
            Duration=TimeCode();
            Element_End0();
            return;
        }

        bool Drop=IFO_PlaybackTime_FrameRate[FrameRate]==30;
        Duration=TimeCode((uint32_t)hh, (uint8_t)mm, (uint8_t)ss, (uint8_t)ff, IFO_PlaybackTime_FrameRate[FrameRate]-1);
        Element_Info1(Duration.ToString());
    Element_End0();
}


void File_Dvdv::PGC(bool Title)
{
        vector<int8u> Stream_Control_Audio;
        vector<int8u> Stream_Control_SubPicture_43;
        vector<int8u> Stream_Control_SubPicture_Wide;
        vector<int8u> Stream_Control_SubPicture_Letterbox;
        vector<int8u> Stream_Control_SubPicture_PanScan;
        vector<TimeCode> CellDurations;
        vector<int8u> ProgramMap;

        //VTS_PGC
        int64u Offset = Element_Offset;
        Element_Begin1("PGC");
        int16u commands, program_map, cell_playback, cell_position;
        int8u Program_Count;
        Element_Begin1("Header");
            int32u Flags;
            int8u Cells;
            TimeCode TotalDuration;
            Skip_B2(                                            "Unknown");
            Get_B1 (Program_Count,                              "number of programs");
            Get_B1 (Cells,                                      "number of cells");
            Get_Duration(TotalDuration,                         "Duration");
            Get_B4 (Flags,                                      "prohibited user ops");
                /*Skip_Flags(Flags,  0,                           "Time play or search");
                Skip_Flags(Flags,  1, PTT play or search);
                Skip_Flags(Flags,  2, Title play);
                Skip_Flags(Flags,  3, Stop);
                Skip_Flags(Flags,  4, GoUp);
                Skip_Flags(Flags,  5, Time or PTT search);
                Skip_Flags(Flags,  6, TopPG or PrevPG search);
                Skip_Flags(Flags,  7, NextPG search);
                Skip_Flags(Flags,  8, Forward scan);
                Skip_Flags(Flags,  9, Backward scan);
                Skip_Flags(Flags, 10, Menu call - Title);
                Skip_Flags(Flags, 11, Menu call - Root);
                Skip_Flags(Flags, 12, Menu call - Subpicture);
                Skip_Flags(Flags, 13, Menu call - Audio);
                Skip_Flags(Flags, 14, Menu call - Angle);
                Skip_Flags(Flags, 15, Menu call - PTT);
                Skip_Flags(Flags, 16, Resume);
                Skip_Flags(Flags, 17, Button select or activate);
                Skip_Flags(Flags, 18, Still off);
                Skip_Flags(Flags, 19, Pause on);
                Skip_Flags(Flags, 20, Audio stream change);
                Skip_Flags(Flags, 21, Subpicture stream change);
                Skip_Flags(Flags, 22, Angle change);
                Skip_Flags(Flags, 23, Karaoke audio mix change);
                Skip_Flags(Flags, 24, Video presentation mode change);
                */
                /*
                Skip_Flags(Flags,  0, Video presentation mode change);
                Skip_Flags(Flags,  1, Karaoke audio mix change);
                Skip_Flags(Flags,  2, Angle change);
                Skip_Flags(Flags,  3, Subpicture stream change);
                Skip_Flags(Flags,  4, Audio stream change);
                Skip_Flags(Flags,  5, Pause on);
                Skip_Flags(Flags,  6, Still off);
                Skip_Flags(Flags,  7, Button select or activate);
                Skip_Flags(Flags,  8, Resume);
                Skip_Flags(Flags,  9, Menu call - PTT);
                Skip_Flags(Flags, 10, Menu call - Angle);
                Skip_Flags(Flags, 11, Menu call - Audio);
                Skip_Flags(Flags, 12, Menu call - Subpicture);
                Skip_Flags(Flags, 13, Menu call - Root);
                Skip_Flags(Flags, 14, Menu call - Title);
                Skip_Flags(Flags, 15, Backward scan);
                Skip_Flags(Flags, 16, Forward scan);
                Skip_Flags(Flags, 17, NextPG search);
                Skip_Flags(Flags, 18, TopPG or PrevPG search);
                Skip_Flags(Flags, 19, Time or PTT search);
                Skip_Flags(Flags, 20, GoUp);
                Skip_Flags(Flags, 21, Stop);
                Skip_Flags(Flags, 22, Title play);
                Skip_Flags(Flags, 23, PTT play or search);
                Skip_Flags(Flags, 24,                               Time play or search);
                */
            Element_Begin1("Audio Stream Controls");
            for (size_t Pos=0; Pos<8; Pos++)
            {
                Element_Begin1("Audio Stream Control");
                Element_Info1(Ztring::ToZtring(Pos));
                int8u Number;
                bool  Available;
                BS_Begin();
                Get_SB (   Available,                           "Stream available");
                Get_S1 (7, Number,                              "Stream number");
                BS_End();
                Skip_B1(                                        "Reserved");
                Element_End0();
                if (Available)
                    Stream_Control_Audio.push_back(Number);

                if (Available && Retrieve(Stream_Audio, Pos, Text_ID).empty() && Sectors[(size_t)((File_Offset+Buffer_Offset)/2048)]==Sector_VTS_PGCI)
                {
                    while (Pos>Count_Get(Stream_Audio))
                        Stream_Prepare(Stream_Audio);

                    int8u ToAdd=0;
                    if (Retrieve(Stream_Audio, Pos, Audio_Format)==__T("AC-3"))
                        ToAdd=0x80;
                    if (Retrieve(Stream_Audio, Pos, Audio_Format)==__T("DTS"))
                        ToAdd=0x88;
                    if (Retrieve(Stream_Audio, Pos, Audio_Format)==__T("LPCM"))
                        ToAdd=0xA0;
                    auto ID=ToAdd+Number;
                    Fill(Stream_Audio, Pos, Audio_ID, __T("189-")+Ztring::ToZtring(ID));
                    Fill(Stream_Audio, Pos, Audio_ID_String, __T("189 (0xBD)-")+Get_Hex_ID(ID), true);
                }
            }
            Element_End0();
            Element_Begin1("Subpicture Stream Controls");
            for (size_t Pos=0; Pos<32; Pos++)
            {
                Element_Begin1("Subpicture Stream Control");
                Element_Info1(Ztring::ToZtring(Pos));
                int8u Number_43, Number_Wide, Number_Letterbox, Number_PanScan;
                bool  Available;
                BS_Begin();
                Get_SB (   Available,                           "Stream available");
                Get_S1 (7, Number_43,                           "Stream number for 4/3");
                BS_End();
                Get_B1 (Number_Wide,                            "Stream number for Wide");
                Get_B1 (Number_Letterbox,                       "Stream number for Letterbox");
                Get_B1 (Number_PanScan,                         "Stream number for Pan&Scan");
                Element_End0();
                if (Available)
                {
                    Stream_Control_SubPicture_43.push_back(Number_43);
                    Stream_Control_SubPicture_Wide.push_back(Number_Wide);
                    Stream_Control_SubPicture_Letterbox.push_back(Number_Letterbox);
                    Stream_Control_SubPicture_PanScan.push_back(Number_PanScan);
                }

                if (Available && Retrieve(Stream_Text, Pos, Text_ID).empty() && Sectors[(size_t)((File_Offset+Buffer_Offset)/2048)]==Sector_VTS_PGCI)
                {
                    while (Pos>Count_Get(Stream_Text))
                        Stream_Prepare(Stream_Text);

                    auto ID=0x20+Number_Wide;
                    Fill(Stream_Text, Pos, Text_ID, __T("189-")+Ztring::ToZtring(ID));
                    Fill(Stream_Text, Pos, Text_ID_String, __T("189 (0xBD)-")+Get_Hex_ID(ID), true);
                }
            }
            Element_End0();
            Skip_B2(                                            "next PGCN");
            Skip_B2(                                            "previous PGCN");
            Skip_B2(                                            "goup PGCN");
            Skip_B1(                                            "PGC still time - 255=infinite");
            Skip_B1(                                            "PG playback mode");
            Element_Begin1("palette");
            for (int Pos=0; Pos<16; Pos++)
            {
                Skip_B4(                                        "palette (0 - Y - Cr - Cb)");
            }
            Element_End0();
            Get_B2 (commands,                                   "offset within PGC to commands");
            Get_B2 (program_map,                                "offset within PGC to program map");
            Get_B2 (cell_playback,                              "offset within PGC to cell playback information table");
            Get_B2 (cell_position,                              "offset within PGC to cell position information table");
        Element_End0();

        //commands
        if (commands>0)
        {
            if (Element_Offset<Offset+commands)
            {
                if (Offset+commands>Element_Size)
                {
                    Skip_XX(Element_Size-Element_Offset,            "Unknown");
                    return;
                }
                Skip_XX(Offset+commands-Element_Offset,             "Unknown");
            }
            Element_Begin1("commands");
            int16u PreCommands_Count, PostCommands_Count, CellCommands_Count, EndAdress;
            Get_B2 (PreCommands_Count,                          "Number of pre commands");
            Get_B2 (PostCommands_Count,                         "Number of post commands");
            Get_B2 (CellCommands_Count,                         "Number of cell commands");
            Get_B2 (EndAdress,                                  "End address relative to command table");
            if (PreCommands_Count>0)
            {
                Element_Begin1("Pre commands");
                    for (int16u Pos=0; Pos<PreCommands_Count; Pos++)
                    {
                        Element_Begin1("Pre command");
                        Skip_XX(8,                              "Pre command");
                        Element_End0();
                    }
                Element_End0();
            }
            if (PostCommands_Count>0)
            {
                Element_Begin1("Post commands");
                    for (int16u Pos=0; Pos<PostCommands_Count; Pos++)
                    {
                        Element_Begin1("Post command");
                        Skip_XX(8,                              "Post command");
                        Element_End0();
                    }
                Element_End0();
            }
            if (CellCommands_Count>0)
            {
                Element_Begin1("Cell commands");
                    for (int16u Pos=0; Pos<CellCommands_Count; Pos++)
                    {
                        Element_Begin1("Cell command");
                        Skip_XX(8,                              "Cell command");
                        Element_End0();
                    }
                Element_End0();
            }
            Element_End0();
        }

        //program map
        if (program_map>0)
        {
            if (Element_Offset<Offset+program_map)
                Skip_XX(Offset+program_map-Element_Offset,          "Unknown");
            Element_Begin1("program map");
            for (int8u Pos=0; Pos<Program_Count; Pos++)
            {
                Element_Begin1("Entry");
                int8u entry;
                Get_B1( entry,  "Entry cell number");
                ProgramMap.push_back(entry);
                //Skip_B1(                                        "Entry cell number");
                Element_End0();
            }
            Element_End0();
        }

        //cell playback
        int32u FirstFirst, Last=0;
        if (cell_playback>0)
        {
            if (Element_Offset<Offset+cell_playback)
                Skip_XX(Offset+cell_playback-Element_Offset,        "Unknown");
            Element_Begin1("cell playback");
            for (int8u Pos=0; Pos<Cells; Pos++)
            {
                TimeCode CellDuration;
                int32u First;
                Element_Begin1("cell");
                Skip_XX(4,                                      "ToDo");
                Get_Duration(CellDuration,                      "Time");
                Get_B4 (First,                                  "first VOBU start sector");
                if (!Pos)
                    FirstFirst=First;
                Skip_B4(                                        "first ILVU end sector");
                Skip_B4(                                        "last VOBU start sector");
                Get_B4 (Last,                                   "last VOBU end sector");
                Element_Info1(Ztring::ToZtring(Pos)); Element_Info1(CellDuration.ToString());
                Element_End0();
                CellDurations.push_back(CellDuration);
            }
            Element_End0();
        }

        //cell position
        if (cell_position>0)
        {
            if (Element_Offset<Offset+cell_position)
                Skip_XX(Offset+cell_position-Element_Offset,        "Unknown");
            Element_Begin1("cell position");
            for (int8u Pos=0; Pos<Cells; Pos++)
            {
                Element_Begin1("cell");
                Skip_B2(                                        "VOBid");
                Skip_B1(                                        "reserved");
                Skip_B1(                                        "Cell id");
                Element_End0();
            }
            Element_End0();
        }

        Element_End0();

        FILLING_BEGIN();
            if (Title)
            {
                Stream_Prepare(Stream_Menu);
                if (Title && Last)
                    Titles[FirstFirst]={ StreamPos_Last, Last, TotalDuration };

                TimeCode ProgramTotalDuration;
                Fill(Stream_Menu, StreamPos_Last, Menu_Chapters_Pos_Begin, Count_Get(Stream_Menu, StreamPos_Last), 10, true);
                for (int8u Pos=0; Pos<ProgramMap.size(); Pos++)
                {
                    Fill(StreamKind_Last, StreamPos_Last, Ztring().Duration_From_Milliseconds((int64s)ProgramTotalDuration.ToMilliseconds()).To_Local().c_str(), Ztring(__T("Chapter "))+Ztring::ToZtring(Pos+1));

                    int8u End;
                    if (Pos+1>=Program_Count)
                        End=Cells+1;
                    else
                        End=ProgramMap[Pos+1];

                    TimeCode ProgramDuration;
                    if (Pos<ProgramMap.size())
                        for (int8u CellPos=ProgramMap[Pos]; CellPos<End; CellPos++)
                            if (CellPos && CellPos<=CellDurations.size())
                                ProgramDuration+=CellDurations[CellPos-1];
                    ProgramTotalDuration+=ProgramDuration;
                }
                Fill(Stream_Menu, StreamPos_Last, Menu_Chapters_Pos_End, Count_Get(Stream_Menu, StreamPos_Last), 10, true);
                auto FrameRate=TotalDuration.GetFramesMax()+1;
                Fill(Stream_Menu, StreamPos_Last, Menu_Duration, (TotalDuration.GetHours()*3600+TotalDuration.GetMinutes()*60+TotalDuration.GetSeconds())*1000+(TotalDuration.GetFrames()*1000/FrameRate));
                Fill(Stream_Menu, StreamPos_Last, Menu_FrameRate, FrameRate/(TotalDuration.Is1001fps()?1.001:1));
                Fill(Stream_Menu, StreamPos_Last, Menu_FrameRate_Num, FrameRate*(TotalDuration.Is1001fps()?1000:1));
                Fill(Stream_Menu, StreamPos_Last, Menu_FrameRate_Den, TotalDuration.Is1001fps()?1001:1);
                Fill(Stream_Menu, StreamPos_Last, Menu_FrameCount, (int64s)TotalDuration.ToFrames());

                for (size_t Pos=0; Pos<Stream_Control_Audio.size(); Pos++)
                {
                    Fill(StreamKind_Last, StreamPos_Last, "List (Audio)", Stream_Control_Audio[Pos]);
                }
                for (size_t Pos=0; Pos<Stream_Control_SubPicture_43.size(); Pos++)
                {
                    Fill(StreamKind_Last, StreamPos_Last, "List (Subtitles 4/3)", Stream_Control_SubPicture_43[Pos]);
                }
                for (size_t Pos=0; Pos<Stream_Control_SubPicture_Wide.size(); Pos++)
                {
                    Fill(StreamKind_Last, StreamPos_Last, "List (Subtitles Wide)", Stream_Control_SubPicture_Wide[Pos]);
                }
                for (size_t Pos=0; Pos<Stream_Control_SubPicture_Letterbox.size(); Pos++)
                {
                    Fill(StreamKind_Last, StreamPos_Last, "List (Subtitles Letterbox)", Stream_Control_SubPicture_Letterbox[Pos]);
                }
                for (size_t Pos=0; Pos<Stream_Control_SubPicture_PanScan.size(); Pos++)
                {
                    Fill(StreamKind_Last, StreamPos_Last, "List (Subtitles Pan&Scan)", Stream_Control_SubPicture_PanScan[Pos]);
                }
            }
        FILLING_END();
}

//---------------------------------------------------------------------------
void File_Dvdv::TT_SRPT()
{
    Element_Name("table of titles");
}

//---------------------------------------------------------------------------
void File_Dvdv::VMGM_PGCI_UT()
{
    Element_Name("Menu Program Chain table");
}

//---------------------------------------------------------------------------
void File_Dvdv::VMG_PTL_MAIT()
{
    Element_Name("Parental Management masks");
}

//---------------------------------------------------------------------------
void File_Dvdv::VMG_VTS_ATRT()
{
    Element_Name("copies of VTS audio/sub-picture attributes");

    //Parsing
    int32u EndAddress;
    Element_Begin1("Header");
        int32u Offset;
        Skip_B4(                                                "Number of title sets");
        Get_B4 (EndAddress,                                     "End address");
        if (EndAddress>=Element_Size)
            EndAddress=(int32u)Element_Size-1;
        Get_B4 (Offset,                                         "Offset to VTSM_LU relative to VTSM_PGCI_UT");
        if (Offset-12>0)
            Skip_XX(Offset-12,                                  "Unknown");
    Element_End0();

    while (Element_Offset<=EndAddress)
    {
        Element_Begin1("VTS_ATRT");
            Element_Begin1("Header");
                int32u Size;
                Get_B4 (Size,                                   "End address");
                Size++; //Last byte
            Element_End0();
            Element_Begin1("Copy of VTS Category");
                Skip_B4(                                        "VTS Category");
            Element_End0();
            Element_Begin1("Copy of VTS attributes");
                Skip_XX(Size-8,                                 "VTS attributes");
            Element_End0();
        Element_End0();
    }
}

//---------------------------------------------------------------------------
void File_Dvdv::VMG_TXTDT_MG()
{
    Element_Name("text data");
}

//---------------------------------------------------------------------------
void File_Dvdv::VMGM_C_ADT()
{
    Element_Name("menu cell address table");
}

//---------------------------------------------------------------------------
void File_Dvdv::VMGM_VOBU_ADMAP()
{
    Element_Name("menu VOBU address map");
}

} //NameSpace

#endif //MEDIAINFO_DVDV_YES
