// Copyright Epic Games, Inc. All Rights Reserved. #include "Utilities/UtilsMPEG.h" #include "Utilities/Utilities.h" #include "Utilities/ElectraBitstream.h" namespace Electra { namespace MPEG { class FMP4BitReader : public FBitstreamReader { public: FMP4BitReader(const void* pData, int64 nData) : FBitstreamReader(pData, nData) { } int32 ReadMP4Length() { int32 Length = 0; for (int32 i = 0; i < 4; ++i) { uint32 Bits = GetBits(8); Length = (Length << 7) + (Bits & 0x7f); if ((Bits & 0x80) == 0) break; } return Length; } }; FESDescriptor::FESDescriptor() : ObjectTypeID(FObjectTypeID::Unknown) , StreamTypeID(FStreamType::Unknown) , BufferSize(0) , MaxBitrate(0) , AvgBitrate(0) , ESID(0) , DependsOnStreamESID(0) , StreamPriority(16) , bDependsOnStream(false) { } void FESDescriptor::SetRawData(const void* Data, int64 Size) { RawData.Empty(); if (Size) { RawData.Reserve((uint32)Size); RawData.SetNumUninitialized((uint32)Size); FMemory::Memcpy(RawData.GetData(), Data, Size); } } const TArray& FESDescriptor::GetRawData() const { return RawData; } const TArray& FESDescriptor::GetCodecSpecificData() const { return CodecSpecificData; } bool FESDescriptor::Parse() { CodecSpecificData.Empty(); FMP4BitReader BitReader(RawData.GetData(), RawData.Num()); if (BitReader.GetBits(8) != 3) { return false; } int32 ESSize = BitReader.ReadMP4Length(); ESID = (uint16)BitReader.GetBits(16); bDependsOnStream = BitReader.GetBits(1) != 0; bool bURLFlag = BitReader.GetBits(1) != 0; bool bOCRflag = BitReader.GetBits(1) != 0; StreamPriority = (uint8)BitReader.GetBits(5); if (bDependsOnStream) { DependsOnStreamESID = BitReader.GetBits(16); } if (bURLFlag) { // Skip over the URL uint32 urlLen = BitReader.GetBits(8); BitReader.SkipBytes(urlLen); } if (bOCRflag) { // Skip the OCR ES ID BitReader.SkipBits(16); } // Parse the config descriptor if (BitReader.GetBits(8) != 4) { return false; } int32 ConfigDescrSize = BitReader.ReadMP4Length(); ObjectTypeID = static_cast(BitReader.GetBits(8)); StreamTypeID = static_cast(BitReader.GetBits(6)); // Skip upstream flag BitReader.SkipBits(1); BitReader.SkipBits(1); // this bit is reserved and must be 1, but it is sometimes incorrectly set to 0, so do not check for it being 1! BufferSize = BitReader.GetBits(24); MaxBitrate = BitReader.GetBits(32); AvgBitrate = BitReader.GetBits(32); if (ConfigDescrSize > 13) { // Optional codec specific descriptor if (BitReader.GetBits(8) != 5) { return false; } int32 CodecSize = BitReader.ReadMP4Length(); CodecSpecificData.Reserve(CodecSize); for (int32 i = 0; i < CodecSize; ++i) { CodecSpecificData.Push(BitReader.GetBits(8)); } } // SL config (we do not need it, we require it to be there though as per the standard) if (BitReader.GetBits(8) != 6) { return false; } int32 nSLSize = BitReader.ReadMP4Length(); if (nSLSize != 1) { return false; } if (BitReader.GetBits(8) != 2) { return false; } return true; } bool FID3V2Metadata::Parse(const uint8* InData, int64 InDataSize) { if (!(InData[0] == 'I' && InData[1] == 'D' && InData[2] == '3' && InData[3] != 0xff && InData[4] != 0xff && InData[6] < 0x80 && InData[7] < 0x80 && InData[8] < 0x80 && InData[9] < 0x80)) { return false; } auto GetID3Size = [](const uint8* InAddr) -> int32 { return (int32) (10U + (static_cast(InAddr[0]) << 21) + (static_cast(InAddr[1]) << 14) + (static_cast(InAddr[2]) << 7) + InAddr[3]); }; auto GetTagSize = [](const uint8* InAddr) -> int32 { return (int32) (10U + (static_cast(InAddr[0]) << 24) + (static_cast(InAddr[1]) << 16) + (static_cast(InAddr[2]) << 8) + InAddr[3]); }; if (GetID3Size(InData + 6) > InDataSize) { return false; } // "Unsynchronization" is not currently supported. if ((InData[5] & 0x80) != 0) { check(!"unsynchronization not currently supported"); return false; } // Experimental headers are not supported if ((InData[5] & 0x20) != 0) { return false; } int32 HeaderSize = 10; if ((InData[5] & 0x40) != 0) { check(!"extended header not currently supported"); return false; //HeaderSize += GetTagSize(InData + 10) + xxxx; } const uint8* InDataEnd = InData + InDataSize; InData += HeaderSize; auto GetString = [](const uint8* InStr, int32 NumBytes, const uint8** OutPtr=nullptr, int32* InOutEncoding=nullptr) -> FString { if (InStr && NumBytes > 0) { int32 Encoding = (!InOutEncoding || (InOutEncoding && *InOutEncoding<0)) ? *InStr++ : *InOutEncoding; if (InOutEncoding) { *InOutEncoding = Encoding; } if (Encoding == 0) { // ISO 8859-1 encoding TArray ConvBuf; ConvBuf.Reserve(NumBytes*2); const uint8* SrcPtr = reinterpret_cast(InStr); int32 NumSrcChars = NumBytes - 1; for(int32 i=0; i= 0x20 && *SrcPtr < 0x80) { ConvBuf.Add(*SrcPtr); } else if (*SrcPtr >= 0xa0) { // We can convert straight from ISO 8859-1 to UTF8 by doing this: ConvBuf.Add(0xc0 | (*SrcPtr >> 6)); ConvBuf.Add(0x80 | (*SrcPtr & 0x3f)); } } if (OutPtr) { *OutPtr = SrcPtr; } auto Cnv = StringCast(reinterpret_cast(ConvBuf.GetData()), ConvBuf.Num()); return FString::ConstructFromPtrSize(Cnv.Get(), Cnv.Length()); } else if (Encoding == 1 && NumBytes > 1) { const uint8* SrcPtr = reinterpret_cast(InStr); if (OutPtr) { *OutPtr = SrcPtr + NumBytes; } int32 NumSrcChars = NumBytes - 1; uint16 BOM = ((uint16)SrcPtr[0] << 8) | SrcPtr[1]; // Have BOM? if (BOM == 0xfffe) { auto Cnv = StringCast(reinterpret_cast(SrcPtr+2), NumSrcChars/2-1); return FString::ConstructFromPtrSize(Cnv.Get(), Cnv.Length()); } else if (BOM == 0xfeff) { TArray be; be.AddUninitialized(NumSrcChars); for(int32 k=0; k(reinterpret_cast(be.GetData()+2), NumSrcChars/2-1); return FString::ConstructFromPtrSize(Cnv.Get(), Cnv.Length()); } } } return FString(); }; while(InData+10 < InDataEnd) { if (((InData[0] >= 'A' && InData[0] <= 'Z') || (InData[0] >= '0' && InData[0] <= '9')) && ((InData[1] >= 'A' && InData[1] <= 'Z') || (InData[1] >= '0' && InData[1] <= '9')) && ((InData[2] >= 'A' && InData[2] <= 'Z') || (InData[2] >= '0' && InData[2] <= '9')) && ((InData[3] >= 'A' && InData[3] <= 'Z') || (InData[3] >= '0' && InData[3] <= '9'))) { uint32 FrameID = (static_cast(InData[0]) << 24) | (static_cast(InData[1]) << 16) | (static_cast(InData[2]) << 8) | static_cast(InData[3]); int32 FrameSize = (int32)GetTagSize(InData + 4); if (FrameSize <= 10) { return false; } const uint8* const FrameEnd = InData + FrameSize; uint16 Flags = (InData[8] << 8) | InData[9]; // Ignore compressed, encrypted or grouped tags. if ((Flags & 0xc0) != 0) { InData += FrameSize; continue; } switch(FrameID) { default: { break; } // Track length case Utils::Make4CC('T','L','E','N'): { FString ms(GetString(InData + 10, FrameSize - 10)); int64 dur = -1; LexFromString(dur, *ms); if (dur > 0) { FTimespan Duration = FTimespan::FromMilliseconds((double)dur); FItem Item {.Value = FVariant(Duration) }; Tags.Emplace(FrameID, MoveTemp(Item)); } break; } // MPEG location lookup table case Utils::Make4CC('M','L','L','T'): { TArray Blob(InData + 10, FrameSize - 10); FItem Item {.Value = FVariant(MoveTemp(Blob)) }; Tags.Emplace(FrameID, MoveTemp(Item)); break; } // Cover image case Utils::Make4CC('A','P','I','C'): { const uint8* NextData = InData + 10; int32 TextEncoding = -1; FString MimeType(GetString(NextData, FrameEnd - NextData, &NextData, &TextEncoding)); if (NextData < FrameEnd) { uint8 PictureType = *NextData++; FString Description(GetString(NextData, FrameEnd - NextData, &NextData, &TextEncoding)); int32 ImageSize = FrameEnd - NextData; TArray PicData(NextData, ImageSize); FItem Item {.MimeType = MoveTemp(MimeType), .Value = FVariant(MoveTemp(PicData)), .ItemType = PictureType }; Tags.Emplace(FrameID, MoveTemp(Item)); } break; } // Private data case Utils::Make4CC('P','R','I','V'): { int32 TextEncoding = 0; const uint8* NextData = InData + 10; FString Owner(GetString(NextData, FrameEnd - NextData, &NextData, &TextEncoding)); int32 PrivateSize = FrameEnd - NextData; TArray PrivateData(NextData, PrivateSize); FItem& Item = PrivateItems.Emplace_GetRef(); Item.MimeType = MoveTemp(Owner); Item.Value = FVariant(MoveTemp(PrivateData)); break; } // Recognized text fields case Utils::Make4CC('T','A','L','B'): case Utils::Make4CC('T','C','O','M'): case Utils::Make4CC('T','C','O','N'): case Utils::Make4CC('T','C','O','P'): case Utils::Make4CC('T','D','A','T'): case Utils::Make4CC('T','E','N','C'): case Utils::Make4CC('T','E','X','T'): case Utils::Make4CC('T','I','M','E'): case Utils::Make4CC('T','I','T','1'): case Utils::Make4CC('T','I','T','2'): case Utils::Make4CC('T','I','T','3'): case Utils::Make4CC('T','L','A','N'): case Utils::Make4CC('T','P','E','1'): case Utils::Make4CC('T','P','E','2'): case Utils::Make4CC('T','P','E','3'): case Utils::Make4CC('T','P','E','4'): case Utils::Make4CC('T','P','O','S'): case Utils::Make4CC('T','P','U','B'): case Utils::Make4CC('T','R','C','K'): case Utils::Make4CC('T','Y','E','R'): { FString s(GetString(InData + 10, FrameSize - 10)); if (s.Len()) { FItem Item {.Value = FVariant(MoveTemp(s)) }; Tags.Emplace(FrameID, MoveTemp(Item)); } break; } } InData += FrameSize; } else { return InData[0] == 0; } } return true; } bool FID3V2Metadata::HaveTag(uint32 InTag) { const FItem* v = Tags.Find(InTag); return !!v; } bool FID3V2Metadata::GetTag(FItem& OutValue, uint32 InTag) { const FItem* v = Tags.Find(InTag); if (v) { OutValue = *v; } return !!v; } const TMap& FID3V2Metadata::GetTags() const { return Tags; } TMap& FID3V2Metadata::GetTags() { return Tags; } const TArray& FID3V2Metadata::GetPrivateItems() const { return PrivateItems; } } // namespace MPEG } // namespace Electra