/* 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 Screen Electronics PAC subtitle files // Implemented by reverse engineering with the files we have, // maybe not accurate // Some ideas from Subtitle Edit // https://github.com/SubtitleEdit/subtitleedit/blob/main/src/libse/SubtitleFormats/Pac.cs //--------------------------------------------------------------------------- // Pre-compilation #include "MediaInfo/PreComp.h" #ifdef __BORLANDC__ #pragma hdrstop #endif //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #include "MediaInfo/Setup.h" //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #if defined(MEDIAINFO_PAC_YES) //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #include "MediaInfo/Text/File_Pac.h" #include "MediaInfo/Text/File_Pac_Codepages.h" #include "MediaInfo/MediaInfo_Config_MediaInfo.h" #include "MediaInfo/TimeCode.h" #include #include #include using namespace std; //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- namespace MediaInfoLib { //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- void Pac_Convert(Ztring& ToConvert, const codepage& CodePage) { auto Table = CodePage.List - 0x20; auto Table_Size = CodePage.Size + 0x20; for (auto& Item : ToConvert) { if (Item >= 0x20 && Item < Table_Size && Table[Item]) { Item = Table[Item]; } } } //*************************************************************************** // Constructor/Destructor //*************************************************************************** //--------------------------------------------------------------------------- File_Pac::File_Pac() :File__Analyze() { } //--------------------------------------------------------------------------- File_Pac::~File_Pac() { } //*************************************************************************** // Streams management //*************************************************************************** //--------------------------------------------------------------------------- void File_Pac::Streams_Accept() { Fill(Stream_General, 0, General_Format, "PAC"); Stream_Prepare(Stream_Text); Fill(Stream_Text, 0, Text_Format, "PAC"); Fill(Stream_Text, 0, Text_FrameRate, 24); } //--------------------------------------------------------------------------- void File_Pac::Streams_Finish() { #if MEDIAINFO_ADVANCED float64 FrameRate_F = Video_FrameRate_Rounded(Config->File_DefaultFrameRate_Get()); if (!FrameRate_F) FrameRate_F = 24; // Files I have are all with frame rate of 24 fps int64s FrameRate_Int = float64_int64s(FrameRate_F); const uint32_t FrameMax = (uint32_t)(FrameRate_Int - 1); #else //MEDIAINFO_ADVANCED const uint32_t FrameMax = 23; #endif //MEDIAINFO_ADVANCED Time_Start_Command.SetFramesMax(FrameMax); Time_End_Command.SetFramesMax(FrameMax); TimeCode Delay_TC; TimeCode Offset; if (Time_Delay.IsValid()) { Delay_TC = TimeCode(Delay, FrameMax, TimeCode::flags()); Offset = Time_Delay - Delay_TC; } else { Offset = Time_Start_Command; Offset.SetMinutes(0); Offset.SetSeconds(0); Offset.SetFrames(0); } int64s Duration = (Time_End_Command - Time_Start_Command).ToMilliseconds(); Fill(Stream_General, 0, General_Duration, Duration); Fill(Stream_Text, 0, Text_Duration, Duration); Fill(Stream_Text, 0, Text_Duration_Start_Command, (int64s)(Time_Start_Command - Offset).ToMilliseconds()); Fill(Stream_Text, 0, Text_TimeCode_FirstFrame, Time_Start_Command.ToString()); Fill(Stream_Text, 0, Text_Duration_End_Command, (int64s)(Time_End_Command - Offset).ToMilliseconds()); TimeCode LastFrame = Time_End_Command; LastFrame--; Fill(Stream_Text, 0, Text_TimeCode_LastFrame, LastFrame.ToString()); if (Time_Start.IsValid()) { Time_Start.SetFramesMax(FrameMax); Fill(Stream_Text, 0, Text_Duration_Start, (int64s)(Time_Start - Offset).ToMilliseconds()); } if (Time_End.IsValid()) { Time_End.SetFramesMax(FrameMax); Fill(Stream_Text, 0, Text_Duration_End, (int64s)(Time_End - Offset).ToMilliseconds()); } if (Time_End.IsValid() && Time_Start.IsValid()) { auto Start2End = (int64s)(Time_End - Time_Start).ToMilliseconds(); Fill(Stream_Text, 0, Text_Duration_Start2End, Start2End); } if (Delay_TC.IsValid()) { Fill(Stream_Text, 0, Text_Delay, (int64s)Delay_TC.ToMilliseconds()); } if (Frame_Count) { Fill(Stream_Text, 0, Text_Events_Total, Frame_Count - EmptyCount); } if (LineCount) { Fill(Stream_Text, 0, Text_Lines_Count, LineCount); } if (LineCount) { Fill(Stream_Text, 0, Text_Lines_MaxCountPerEvent, MaxCountOfLinesPerFrame); } if (MaxCountOfCharsPerLine) { Fill(Stream_Text, 0, Text_Lines_MaxCharacterCount, MaxCountOfCharsPerLine); } if (Count_UTF8) { Fill(Stream_Text, 0, "CharacterSet", "UTF-8"); } if (Count_2byte) { Fill(Stream_Text, 0, "CharacterSet", "2-byte"); } if (Count_1byte8) { Fill(Stream_Text, 0, "CharacterSet", "1-byte"); } if (Count_1byte7 && !Count_1byte8 && !Count_UTF8 && !Count_2byte) { Fill(Stream_Text, 0, "CharacterSet", "ASCII"); } } //*************************************************************************** // Buffer - File header //*************************************************************************** //--------------------------------------------------------------------------- void File_Pac::FileHeader_Parse() { if (Buffer_Size < 20) { Element_WaitForMoreData(); return; } for (size_t i = 0; i < 20; i++) { if ((!i && Buffer[0] != 1) || (i && Buffer[i])) { Reject(); return; } } Skip_XX(20, "Signature?"); } //*************************************************************************** // Buffer - Per element //*************************************************************************** //--------------------------------------------------------------------------- void File_Pac::Header_Parse() { auto PacTimeCode = [&](const char* Name) { Element_Begin1(Name); int16u HHMM, SSFF; Get_L2 (HHMM, "HHMM"); Get_L2 (SSFF, "SSFF"); TimeCode TC(HHMM / 100, HHMM % 100, SSFF / 100, SSFF % 100, 99); if (!TC.IsValid()) { Reject(); } Element_Info1(TC.ToString()); Element_End0(); return TC; }; int16u ContentLength, FrameNumber; int8u Type, SubType; Get_L1 (Type, "Type"); Get_L2 (FrameNumber, "Frame number"); Get_L1 (SubType, "Sub-Type?"); auto Start_Temp = PacTimeCode("Start"); auto End_Temp = PacTimeCode("End"); if (!Type) { if (Start_Temp.IsValid()) { Time_Start_Command_Temp = Start_Temp; } if (End_Temp.IsValid()) { Time_End_Command = End_Temp; } } Get_L2 (ContentLength, "Content length"); if (!Status[IsAccepted]) { if (!Frame_Count_Metadata && !Frame_Count && FrameNumber == 1) { Frame_Count_Metadata++; } if ((Type && Type != 0xFF) || (!Type && (SubType < 0x60 || SubType > 0x62 || FrameNumber != Frame_Count_Metadata + Frame_Count)) || !Start_Temp.IsValid() || !End_Temp.IsValid()) { Reject(); } } Header_Fill_Code(Type, Type?"File Footer":"Packet"); Header_Fill_Size(Element_Offset + ContentLength); } //--------------------------------------------------------------------------- void File_Pac::Data_Parse() { // Parsing if (!Element_Size) { return; } if (!Element_Code) { Skip_L1( "Vertical alignment"); if (Element_Offset != Element_Size) { int8u Probe; Peek_L1(Probe); if (Probe != 0xFE) { Skip_L1( "(Unknown)"); Skip_L1( "(Unknown)"); Skip_L1( "(Unknown)"); } } } ZtringList Content; while (Element_Size - Element_Offset >= 1) { int64u Current; if (!Element_Code) { int8u Start; Peek_L1(Start); if (Start != 0xFE) { Trusted_IsNot("0xFE"); Skip_XX(Element_Size-Element_Offset, "(Unknown)"); break; } Skip_L1( "0xFE"); Skip_L1( "Horizontal alignment"); Skip_L1( "(Unknown)"); for (Current = Element_Offset; Current < Element_Size; Current++) { if (Buffer[Buffer_Offset + Current] == 0xFE) { break; } } } else { Current = Element_Size; } Content.resize(Content.size() + 1); auto Size = Current - Element_Offset; bool IsUtf8 = false; bool IsW16 = false; while (Size) { if (Size > 2) { size_t Pos = Buffer_Offset + (size_t)Element_Offset; if (Buffer[Pos] == 0x1F && Buffer[Pos + 1] == 'C' && Buffer[Pos + 2] >= '0' && Buffer[Pos + 2] <= '9') { Skip_B3( "(Unknown)"); Size -= 3; } } if (Size >= 5) { int64u Probe; Peek_B5(Probe); if (Probe >> 8 == 0x1FEFBBBF) { Skip_B4( "UTF-8 start"); IsUtf8 = true; } if (Probe == 0x1F5731362ELL) { Skip_B5( "W16 start"); IsW16 = true; } } if (IsUtf8) { int64u End = Element_Offset; for (; End < Element_Size; End++) { const auto Data = Buffer[Buffer_Offset + End]; if (Data == 0x2E || Data == 0xFF) { break; } } Get_UTF8(End - Element_Offset, Content.back(), "Content"); if (Element_Offset < Element_Size) { if (Buffer[Buffer_Offset + Element_Offset] == 0xFF) { Skip_L1( "Dot"); Content.back() += __T('.'); } else { // 0x2E Skip_B1( "UTF-8 end"); IsUtf8 = false; } } Count_UTF8++; } else if (IsW16) { Skip_XX(Size, "Content"); Content.back().resize(Size / 2); Count_2byte++; } else { Get_ISO_8859_1(Size, Content.back(), "Content"); bool Is8bit = false; for (const auto& Character : Content.back()) { if ((wchar_t)Character >= 0x80) { Is8bit = true; } } if (Is8bit) { Count_1byte8++; auto& Line = Content.back(); if (Line.size() < numeric_limits::max()) { int Max = (int)Line.size(); for (int i = Max; i > 0; i--) { const auto Character = Line[i]; if ((wchar_t)Character >= 0xE0) { Line.erase(i, 1); // Not supported but we currently don't need the real text } } } } else { Count_1byte7++; } } Size = Current - Element_Offset; } } FILLING_BEGIN(); if (!Element_Code) { bool MayBeTags = !Frame_Count && Time_Start_Command_Temp == TimeCode(0, Time_Start_Command_Temp.GetFramesMax()) && Time_End_Command == TimeCode(8, Time_End_Command.GetFramesMax()); bool IsTags = false; bool IsFrameNumber = false; if (MayBeTags) { size_t Pac_MetadataMap_Pos = 0; Ztring Language; Ztring CatalogNumber; Ztring Title; map More; for (size_t i = 0; i < Pac_MetadataMap_Size; i++) { const auto& Item = Pac_MetadataMap[i]; More.clear(); for (const auto& Line : Content) { auto Separator_Pos = Line.find(Item.Separator, 0); if (Separator_Pos != string::npos) { auto Space_Pos = Line.rfind(__T(' '), Separator_Pos); if (Space_Pos == string::npos) { IsTags = true; Ztring Field = Line.substr(0, Separator_Pos); Separator_Pos++; if (Separator_Pos < Line.size() && Line[Separator_Pos] == __T(' ')) { Separator_Pos++; } Ztring Value = Line.substr(Separator_Pos); if (Item.Lang && Field == Item.Lang) { Pac_MetadataMap_Pos = i; Language = Value; } else { More[Field] = Value; } } } } if (!Language.empty()) { break; } } if (Pac_MetadataMap_Pos == 1) { Pac_Convert(Language, Pac_CodePages[0]); } auto LanguageS = Language.To_UTF8(); for (size_t i = 0; i < Pac_CodePages_Size; i++) { const auto& CodePage = Pac_CodePages[i]; if (CodePage.Name != LanguageS) { continue; } Codepage_Pos = i + 1; for (auto& Line : Content) { Pac_Convert(Line, CodePage); } decltype(More) More2; set More_ToDelete; for (auto& Item : More) { Ztring Field = Item.first; Pac_Convert(Field, CodePage); Pac_Convert(Item.second, CodePage); if (Field != Item.first) { More_ToDelete.insert(Item.first); More2[Field] = Item.second; } } for (auto& Item : More2) { More[Item.first] = Item.second; } for (auto& Item : More_ToDelete) { More.erase(Item); } } Language.MakeLowerCase(); for (const auto Item : Pac_LangMap) if (Language.To_UTF8() == Item.From) { Language.From_UTF8(Item.To); } Fill(Stream_Text, 0, Text_Language, Language, true); for (const auto& Item : More) { if (false) { } else if (Item.first == __T("STORY")) { Fill(Stream_General, 0, General_CatalogNumber, Item.second, true); } else if (Item.first == __T("TITLE")) { Fill(Stream_General, 0, General_Title, Item.second, true); } else { Fill(Stream_General, 0, Item.first.To_UTF8().c_str(), Item.second, true); } } } if (Codepage_Pos) { const auto& CodePage = Pac_CodePages[Codepage_Pos - 1]; for (auto& Line : Content) { Pac_Convert(Line, CodePage); } } bool FirstFrameOfVideo = !Frame_Count && Content.size() == 2 && Content[0] == __T("First Frame of Video"); if (Content.size() == 1 + FirstFrameOfVideo) { const auto& Line = Content[FirstFrameOfVideo]; if (Line.rfind(__T("FRAME "), 0) == 0 && Line.find_first_not_of(__T("0123456789"), 6) == string::npos) { IsFrameNumber = true; if (!Delay) { Delay = stoul(Line.substr(6)); Time_Delay = Time_Start_Command_Temp; } } } if (!Time_Start_Command.IsValid() && !IsTags) { Time_Start_Command = Time_Start_Command_Temp; } if (IsTags || IsFrameNumber) { Frame_Count_Metadata++; } else { auto CountOfLinesPerFrame = Content.size(); if (CountOfLinesPerFrame) { LineCount += CountOfLinesPerFrame; if (MaxCountOfLinesPerFrame < CountOfLinesPerFrame) { MaxCountOfLinesPerFrame = CountOfLinesPerFrame; } for (const auto& Line : Content) { auto CountOfCharsPerLine = Line.size(); size_t Pos = 0; for (;;) { Pos = Line.find('<', Pos); if (Pos == string::npos) { break; } Pos = Line.find('>', Pos + 1); if (Pos == string::npos) { break; } CountOfCharsPerLine += 2; // < and > are marker of italic } if (MaxCountOfCharsPerLine < CountOfCharsPerLine) { MaxCountOfCharsPerLine = CountOfCharsPerLine; } } Frame_Count++; if (!Time_Start.IsValid() && Time_Start_Command_Temp.IsValid()) { Time_Start = Time_Start_Command_Temp; } if (Time_End_Command.IsValid()) { Time_End = Time_End_Command; } } else { EmptyCount++; } } } if (!Status[IsAccepted] && (Frame_Count >= 256 || File_Offset + Buffer_Offset + Element_Size == File_Size || (File_Size == (int64u)-1 && (Frame_Count || EmptyCount) && Element_Code == 0xFF))) Accept(); FILLING_END(); } //*************************************************************************** // C++ //*************************************************************************** } //NameSpace #endif //MEDIAINFO_PAC_YES