From 1280f3b849256ea20ffc6cfdc355468ee9c4a5c8 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Chebbi <56059847+mohamedchebbii@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:58:07 +0100 Subject: [PATCH] 1748 Video Support in V1.0: part 1/3 : support MatroskaViedo (#2413) * 1748 Video Support in V1.0: part 1/3 : support MatroskaViedo * Simplify the code of matroskavideo * protect conevrtuint64 method from overflow * use size_t instead of uint64_t --- exiv2.md | 4 +- include/exiv2/image_types.hpp | 1 + include/exiv2/matroskavideo.hpp | 196 +++++++ src/CMakeLists.txt | 2 + src/image.cpp | 3 + src/matroskavideo.cpp | 976 +++++++++++++++++++++++++++++++ unitTests/CMakeLists.txt | 1 + unitTests/test_matroskavideo.cpp | 55 ++ 8 files changed, 1236 insertions(+), 2 deletions(-) create mode 100644 include/exiv2/matroskavideo.hpp create mode 100644 src/matroskavideo.cpp create mode 100644 unitTests/test_matroskavideo.cpp diff --git a/exiv2.md b/exiv2.md index e2e3f8e80b..ea52e1a7f1 100644 --- a/exiv2.md +++ b/exiv2.md @@ -117,8 +117,8 @@ and see if `enable_bmff=1`. - Naked codestream JXL files do not contain Exif, IPTC or XMP metadata. - Support of video files is limited. Currently **exiv2** only has some - rudimentary support to read metadata from quicktime based video files (e.g. - .MOV/.MP4). + rudimentary support to read metadata from quicktime and matroskavideo based video files (e.g. + .MOV/.MP4, MKV) [TOC](#TOC) diff --git a/include/exiv2/image_types.hpp b/include/exiv2/image_types.hpp index 54237aec41..25bfeb4289 100644 --- a/include/exiv2/image_types.hpp +++ b/include/exiv2/image_types.hpp @@ -35,6 +35,7 @@ enum class ImageType { webp, xmp, ///< XMP sidecar files qtime, + mkv, }; } // namespace Exiv2 diff --git a/include/exiv2/matroskavideo.hpp b/include/exiv2/matroskavideo.hpp new file mode 100644 index 0000000000..32123e8354 --- /dev/null +++ b/include/exiv2/matroskavideo.hpp @@ -0,0 +1,196 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004-2021 Exiv2 authors + * This program is part of the Exiv2 distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. + */ +#ifndef MATROSKAVIDEO_HPP_ +#define MATROSKAVIDEO_HPP_ + +// ***************************************************************************** +#include "exiv2lib_export.h" + +// included header files +#include "image.hpp" + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +/*! + @brief Helper structure for the Matroska tags lookup table. + */ + +// ***************************************************************************** +// class definitions +namespace Internal { + +enum matroskaTypeEnum : char { + String = 's', + Integer = 'i', + UInteger = 'u', + Date = 'd', + InternalField = 'n', + Boolean = 'o', + Binary = 'b', + Master = 'm', + Float = 'f', + Utf8 = '8', + UndefinedType = 'z' + +}; + +enum matroskaProcessEnum : char { Process = 'p', Skip = 's', Composite = 'c', Undefined = 'u' }; + +struct MatroskaTag { + uint64_t _id; + std::string _label; + matroskaTypeEnum _type; + matroskaProcessEnum _process; + + MatroskaTag(uint64_t id, const std::string& label, matroskaTypeEnum type, matroskaProcessEnum process) : + _id(id), _label(label), _type(type), _process(process) { + } + + MatroskaTag(uint64_t id, const std::string& label) : + _id(id), _label(label), _type(matroskaTypeEnum::UndefinedType), _process(matroskaProcessEnum::Undefined) { + } + + bool isSkipped() const { + return _process == Skip; + } + bool isComposite() const { + return _process == Composite; + } + void dump(std::ostream& os) const { + os << " MatroskaTag " + << " id: [0x" << std::hex << _id << "] label:[" << _label << "] type:[" << _type << "] process :[" << _process + << "]\n"; + } +}; + +/// @brief Utility function to search into std::array of pairs +/// @return the searched pair if exist,else nullptr +template +[[nodiscard]] const MatroskaTag* findTag(const std::array& src, const uint64_t& key) { + const auto rc = std::find_if(src.begin(), src.end(), [&key](const MatroskaTag& tag) { return tag._id == key; }); + // the return value is of type "const MatroskaTag*", so we return the adress of the content of the input + // iterator return by find_if + return rc == std::end(src) ? nullptr : &(*rc); +} + +} // namespace Internal + +/*! + @brief Class to access Matroska video files. + */ +class EXIV2API MatroskaVideo : public Image { + public: + //! @name Creators + //@{ + /*! + @brief Constructor for a Matroska video. Since the constructor + can not return a result, callers should check the good() method + after object construction to determine success or failure. + @param io An auto-pointer that owns a BasicIo instance used for + reading and writing image metadata. \b Important: The constructor + takes ownership of the passed in BasicIo instance through the + auto-pointer. Callers should not continue to use the BasicIo + instance after it is passed to this method. Use the Image::io() + method to get a temporary reference. + */ + explicit MatroskaVideo(BasicIo::UniquePtr io); + + //! Copy constructor + MatroskaVideo(const MatroskaVideo& rhs) = delete; + //! Assignment operator + MatroskaVideo& operator=(const MatroskaVideo& rhs) = delete; + //@} + + //! @name Manipulators + //@{ + void readMetadata() override; + void writeMetadata() override; + //@} + + //! @name Accessors + //@{ + [[nodiscard]] std::string mimeType() const override; + //@} + + protected: + /*! + @brief Function used to calulate the size of a block. + This information is only stored in one byte. + The size of the block is calculated by counting + the number of leading zeros in the binary code of the byte. + Size = (No. of leading zeros + 1) bytes + @param b The byte, which stores the information to calculate the size + @return Return the size of the block. + */ + [[nodiscard]] uint32_t findBlockSize(byte b); + /*! + @brief Check for a valid tag and decode the block at the current IO position. + Calls contentManagement() or skips to next tag, if required. + */ + void decodeBlock(); + /*! + @brief Interpret tag information, and save it in the respective XMP container. + @param mt Pointer to current tag, + @param buf Pointer to the memory area with the tag information. + @param size Size of \em buf. + */ + + void decodeInternalTags(const Internal::MatroskaTag* tag, const byte* buf, size_t size); + void decodeStringTags(const Internal::MatroskaTag* tag, const byte* buf); + void decodeIntegerTags(const Internal::MatroskaTag* tag, const byte* buf, size_t size); + void decodeBooleanTags(const Internal::MatroskaTag* tag, const byte* buf, size_t size); + void decodeDateTags(const Internal::MatroskaTag* tag, const byte* buf, size_t size); + void decodeFloatTags(const Internal::MatroskaTag* tag, const byte* buf, size_t size); + /*!Internal:: + @brief Calculates Aspect Ratio of a video, and stores it in the + respective XMP container. + */ + void aspectRatio(); + + private: + //! Variable to check the end of metadata traversing. + bool continueTraversing_; + //! Variable to store height and width of a video frame. + uint64_t height_; + uint64_t width_; + uint32_t track_count_; + double time_code_scale_ = 1.0; + uint64_t stream_ = 0; + + static constexpr double bytesMB = 1048576; + +}; // class MatroskaVideo + +// ***************************************************************************** +/*! + @brief Create a new MatroskaVideo instance and return an auto-pointer to it. + Caller owns the returned object and the auto-pointer ensures that + it will be deleted. + */ +EXIV2API Image::UniquePtr newMkvInstance(BasicIo::UniquePtr io, bool create); + +//! Check if the file iIo is a Matroska Video. +EXIV2API bool isMkvType(BasicIo& iIo, bool advance); + +} // namespace Exiv2 + +#endif // #ifndef MATROSKAVIDEO_HPP_ \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 108f656012..86514c6d5c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -61,6 +61,7 @@ set(PUBLIC_HEADERS ../include/exiv2/iptc.hpp ../include/exiv2/jp2image.hpp ../include/exiv2/jpgimage.hpp + ../include/exiv2/matroskavideo.hpp ../include/exiv2/metadatum.hpp ../include/exiv2/mrwimage.hpp ../include/exiv2/orfimage.hpp @@ -105,6 +106,7 @@ add_library( exiv2lib iptc.cpp jp2image.cpp jpgimage.cpp + matroskavideo.cpp metadatum.cpp mrwimage.cpp orfimage.cpp diff --git a/src/image.cpp b/src/image.cpp index e8c24e82d8..8c22e70690 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -25,6 +25,7 @@ #include "bmpimage.hpp" #include "gifimage.hpp" #include "jp2image.hpp" +#include "matroskavideo.hpp" #include "nikonmn_int.hpp" #include "orfimage.hpp" #include "pgfimage.hpp" @@ -100,6 +101,8 @@ constexpr auto registry = std::array{ // needs to be before bmff because some ftyp files are handled as qt and // the rest should fall through to bmff Registry{ImageType::qtime, newQTimeInstance, isQTimeType, amRead, amNone, amRead, amNone}, + Registry{ImageType::mkv, newMkvInstance, isMkvType, amRead, amNone, amRead, amNone}, + #ifdef EXV_ENABLE_BMFF Registry{ImageType::bmff, newBmffInstance, isBmffType, amRead, amRead, amRead, amNone}, #endif // EXV_ENABLE_BMFF diff --git a/src/matroskavideo.cpp b/src/matroskavideo.cpp new file mode 100644 index 0000000000..a51ccd293c --- /dev/null +++ b/src/matroskavideo.cpp @@ -0,0 +1,976 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004-2021 Exiv2 authors + * This program is part of the Exiv2 distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. + */ +// ***************************************************************************** +// included header files +#include "config.h" + +//#ifdef EXV_ENABLE_VIDEO +#include "basicio.hpp" +#include "error.hpp" +#include "futils.hpp" +#include "matroskavideo.hpp" +#include "tags.hpp" +#include "tags_int.hpp" + +// + standard includes +#include +#include +#include +#include +#include +#include +// ***************************************************************************** +// class member definitions +namespace Exiv2 { +namespace Internal { + +/*! + Tag Look-up list for Matroska Type Video Files + The Tags have been categorized in 4 categories. Which are + mentioned as a comment in front of them. + s -- Tag to be Skipped + sd -- Tag to be Skipped along with its data + u -- Tag used directly for storing metadata + ui -- Tag used only internally + + see : https://www.matroska.org/technical/elements.html + https://matroska.sourceforge.net/technical/specs/chapters/index.html + */ + +enum matroskaEnum : uint64_t { + ChapterDisplay = 0x0000, + TrackType = 0x0003, + ChapterString = 0x0005, + Video_Audio_CodecID = 0x0006, + TrackDefault = 0x0008, + ChapterTrackNumber = 0x0009, + Slices = 0x000e, + ChapterTrack = 0x000f, + ChapterTimeStart = 0x0011, + ChapterTimeEnd = 0x0012, + CueRefTime = 0x0016, + CueRefCluster = 0x0017, + ChapterFlagHidden = 0x0018, + Xmp_video_VideoScanTpye = 0x001a, + BlockDuration = 0x001b, + TrackLacing = 0x001c, + Xmp_audio_ChannelType = 0x001f, + BlockGroup = 0x0020, + Block = 0x0021, + BlockVirtual = 0x0022, + SimpleBlock = 0x0023, + CodecState = 0x0024, + BlockAdditional = 0x0025, + BlockMore = 0x0026, + Position = 0x0027, + CodecDecodeAll = 0x002a, + PrevSize = 0x002b, + TrackEntry = 0x002e, + EncryptedBlock = 0x002f, + Xmp_video_Width_1 = 0x0030, + CueTime = 0x0033, + Xmp_audio_SampleRate = 0x0035, + ChapterAtom = 0x0036, + CueTrackPositions = 0x0037, + TrackUsed = 0x0039, + Xmp_video_Height_1 = 0x003a, + CuePoint = 0x003b, + CRC_32 = 0x003f, + BlockAdditionalID = 0x004b, + LaceNumber = 0x004c, + FrameNumber = 0x004d, + Delay = 0x004e, + ClusterDuration = 0x004f, + TrackNumber = 0x0057, + CueReference = 0x005b, + Video = 0x0060, + Audio = 0x0061, + Timecode = 0x0067, + TimeSlice = 0x0068, + CueCodecState = 0x006a, + CueRefCodecState = 0x006b, + Void = 0x006c, + BlockAddID = 0x006e, + CueClusterPosition = 0x0071, + CueTrack = 0x0077, + ReferencePriority = 0x007a, + ReferenceBlock = 0x007b, + ReferenceVirtual = 0x007d, + Xmp_video_ContentCompressAlgo = 0x0254, + ContentCompressionSettings = 0x0255, + Xmp_video_DocType = 0x0282, + Xmp_video_DocTypeReadVersion = 0x0285, + Xmp_video_EBMLVersion = 0x0286, + Xmp_video_DocTypeVersion = 0x0287, + EBMLMaxIDLength = 0x02f2, + EBMLMaxSizeLength = 0x02f3, + Xmp_video_EBMLReadVersion = 0x02f7, + ChapterLanguage = 0x037c, + ChapterCountry = 0x037e, + SegmentFamily = 0x0444, + Xmp_video_DateUTC = 0x0461, + Xmp_video_TagLanguage = 0x047a, + Xmp_video_TagDefault = 0x0484, + TagBinary = 0x0485, + Xmp_video_TagString = 0x0487, + Xmp_video_Duration = 0x0489, + ChapterProcessPrivate = 0x050d, + ChapterFlagEnabled = 0x0598, + Xmp_video_TagName = 0x05a3, + EditionEntry = 0x05b9, + EditionUID = 0x05bc, + EditionFlagHidden = 0x05bd, + EditionFlagDefault = 0x05db, + EditionFlagOrdered = 0x05dd, + Xmp_video_AttachFileData = 0x065c, + Xmp_video_AttachFileMIME = 0x0660, + Xmp_video_AttachFileName = 0x066e, + AttachedFileReferral = 0x0675, + Xmp_video_AttachFileDesc = 0x067e, + Xmp_video_AttachFileUID = 0x06ae, + Xmp_video_ContentEncryptAlgo = 0x07e1, + ContentEncryptionKeyID = 0x07e2, + ContentSignature = 0x07e3, + ContentSignatureKeyID = 0x07e4, + Xmp_video_ContentSignAlgo_1 = 0x07e5, + Xmp_video_ContentSignHashAlgo_1 = 0x07e6, + Xmp_video_MuxingApp = 0x0d80, + Seek = 0x0dbb, + ContentEncodingOrder = 0x1031, + ContentEncodingScope = 0x1032, + Xmp_video_ContentEncodingType = 0x1033, + ContentCompression = 0x1034, + ContentEncryption = 0x1035, + CueRefNumber = 0x135f, + Xmp_video_TrackName = 0x136e, + CueBlockNumber = 0x1378, + TrackOffset = 0x137f, + SeekID = 0x13ab, + SeekPosition = 0x13ac, + Stereo3DMode = 0x13b8, + Xmp_video_CropBottom = 0x14aa, + Xmp_video_Width_2 = 0x14b0, + Xmp_video_DisplayUnit = 0x14b2, + Xmp_video_AspectRatioType = 0x14b3, + Xmp_video_Height_2 = 0x14ba, + Xmp_video_CropTop = 0x14bb, + Xmp_video_CropLeft = 0x14cc, + Xmp_video_CropRight = 0x14dd, + TrackForced = 0x15aa, + MaxBlockAdditionID = 0x15ee, + Xmp_video_WritingApp = 0x1741, + SilentTracks = 0x1854, + SilentTrackNumber = 0x18d7, + AttachedFile = 0x21a7, + ContentEncoding = 0x2240, + Xmp_audio_BitsPerSample = 0x2264, + CodecPrivate = 0x23a2, + Targets = 0x23c0, + Xmp_video_PhysicalEquivalent = 0x23c3, + TagChapterUID = 0x23c4, + TagTrackUID = 0x23c5, + TagAttachmentUID = 0x23c6, + TagEditionUID = 0x23c9, + Xmp_video_TargetType = 0x23ca, + SignedElement = 0x2532, + TrackTranslate = 0x2624, + TrackTranslateTrackID = 0x26a5, + TrackTranslateCodec = 0x26bf, + TrackTranslateEditionUID = 0x26fc, + SimpleTag = 0x27c8, + TargetTypeValue = 0x28ca, + ChapterProcessCommand = 0x2911, + ChapterProcessTime = 0x2922, + ChapterTranslate = 0x2924, + ChapterProcessData = 0x2933, + ChapterProcess = 0x2944, + ChapterProcessCodecID = 0x2955, + ChapterTranslateID = 0x29a5, + Xmp_video_TranslateCodec = 0x29bf, + ChapterTranslateEditionUID = 0x29fc, + ContentEncodings = 0x2d80, + MinCache = 0x2de7, + MaxCache = 0x2df8, + ChapterSegmentUID = 0x2e67, + ChapterSegmentEditionUID = 0x2ebc, + TrackOverlay = 0x2fab, + Tag = 0x3373, + SegmentFileName = 0x3384, + SegmentUID = 0x33a4, + ChapterUID = 0x33c4, + TrackUID = 0x33c5, + TrackAttachmentUID = 0x3446, + BlockAdditions = 0x35a1, + Xmp_audio_OutputSampleRate = 0x38b5, + Xmp_video_Title = 0x3ba9, + ChannelPositions = 0x3d7b, + SignatureElements = 0x3e5b, + SignatureElementList = 0x3e7b, + Xmp_video_ContentSignAlgo_2 = 0x3e8a, + Xmp_video_ContentSignHashAlgo_2 = 0x3e9a, + SignaturePublicKey = 0x3ea5, + Signature = 0x3eb5, + TrackLanguage = 0x2b59c, + TrackTimecodeScale = 0x3314f, + Xmp_video_FrameRate = 0x383e3, + VideoFrameRate_DefaultDuration = 0x3e383, + Video_Audio_CodecName = 0x58688, + CodecDownloadURL = 0x6b240, + TimecodeScale = 0xad7b1, + ColorSpace = 0xeb524, + Xmp_video_OpColor = 0xfb523, + CodecSettings = 0x1a9697, + CodecInfoURL = 0x1b4040, + PrevFileName = 0x1c83ab, + PrevUID = 0x1cb923, + NextFileName = 0x1e83bb, + NextUID = 0x1eb923, + Chapters = 0x43a770, + SeekHead = 0x14d9b74, + Tags = 0x254c367, + Info = 0x549a966, + Tracks = 0x654ae6b, + SegmentHeader = 0x8538067, + Attachments = 0x941a469, + EBMLHeader = 0xa45dfa3, + SignatureSlot = 0xb538667, + Cues = 0xc53bb6b, + Cluster = 0xf43b675 +}; + +const std::array matroskaTags = { + MatroskaTag(ChapterDisplay, "ChapterDisplay", Master, Composite), + MatroskaTag(TrackType, "TrackType", Boolean, Process), + MatroskaTag(ChapterString, "ChapterString", String, Skip), + MatroskaTag(Video_Audio_CodecID, "Video.Audio.CodecID", InternalField, Skip), // process + MatroskaTag(TrackDefault, "TrackDefault", Boolean, Process), + MatroskaTag(ChapterTrackNumber, "ChapterTrackNumber", UInteger, Skip), + MatroskaTag(Slices, "Slices", Master, Composite), + MatroskaTag(ChapterTrack, "ChapterTrack", Master, Composite), + MatroskaTag(ChapterTimeStart, "ChapterTimeStart", UInteger, Skip), + MatroskaTag(ChapterTimeEnd, "ChapterTimeEnd", UInteger, Skip), + MatroskaTag(CueRefTime, "CueRefTime", UInteger, Skip), + MatroskaTag(CueRefCluster, "CueRefCluster", UInteger, Skip), + MatroskaTag(ChapterFlagHidden, "ChapterFlagHidden", UInteger, Skip), + MatroskaTag(Xmp_video_VideoScanTpye, "Xmp.video.VideoScanTpye", InternalField, Process), + MatroskaTag(BlockDuration, "BlockDuration", UInteger, Skip), + MatroskaTag(TrackLacing, "TrackLacing", Boolean, Process), + MatroskaTag(Xmp_audio_ChannelType, "Xmp.audio.ChannelType", InternalField, Process), + MatroskaTag(BlockGroup, "BlockGroup", Master, Composite), + MatroskaTag(Block, "Block", Binary, Skip), + MatroskaTag(BlockVirtual, "BlockVirtual", Binary, Skip), + MatroskaTag(SimpleBlock, "SimpleBlock", Binary, Skip), + MatroskaTag(CodecState, "CodecState", Binary, Skip), + MatroskaTag(BlockAdditional, "BlockAdditional", UInteger, Skip), + MatroskaTag(BlockMore, "BlockMore", Master, Composite), + MatroskaTag(Position, "Position", UInteger, Skip), + MatroskaTag(CodecDecodeAll, "CodecDecodeAll", Boolean, Process), + MatroskaTag(PrevSize, "PrevSize", UInteger, Skip), + MatroskaTag(TrackEntry, "TrackEntry", Master, Composite), + MatroskaTag(EncryptedBlock, "EncryptedBlock", Binary, Skip), + MatroskaTag(Xmp_video_Width_1, "Xmp.video.Width", UInteger, Process), + MatroskaTag(CueTime, "CueTime", UInteger, Skip), + MatroskaTag(Xmp_audio_SampleRate, "Xmp.audio.SampleRate", Float, Process), + MatroskaTag(ChapterAtom, "ChapterAtom", Master, Composite), + MatroskaTag(CueTrackPositions, "CueTrackPositions", Master, Composite), + MatroskaTag(TrackUsed, "TrackUsed", Boolean, Process), + MatroskaTag(Xmp_video_Height_1, "Xmp.video.Height", Integer, Process), + MatroskaTag(CuePoint, "CuePoint", Master, Composite), + MatroskaTag(CRC_32, "CRC_32", Binary, Skip), + MatroskaTag(BlockAdditionalID, "BlockAdditionalID", UInteger, Skip), + MatroskaTag(LaceNumber, "LaceNumber", UInteger, Skip), + MatroskaTag(FrameNumber, "FrameNumber", UInteger, Skip), + MatroskaTag(Delay, "Delay", UInteger, Skip), + MatroskaTag(ClusterDuration, "ClusterDuration", Float, Skip), + MatroskaTag(TrackNumber, "Xmp.video.TotalStream", String, Process), + MatroskaTag(CueReference, "CueReference", Master, Composite), + MatroskaTag(Video, "Video", Master, Composite), + MatroskaTag(Audio, "Audio", Master, Composite), + MatroskaTag(Timecode, "Timecode", UInteger, Skip), + MatroskaTag(TimeSlice, "TimeSlice", Master, Composite), + MatroskaTag(CueCodecState, "CueCodecState", UInteger, Skip), + MatroskaTag(CueRefCodecState, "CueRefCodecState", UInteger, Skip), + MatroskaTag(Void, "Void", Binary, Skip), + MatroskaTag(BlockAddID, "BlockAddID", UInteger, Skip), + MatroskaTag(CueClusterPosition, "CueClusterPosition", UInteger, Skip), + MatroskaTag(CueTrack, "CueTrack", UInteger, Skip), + MatroskaTag(ReferencePriority, "ReferencePriority", UInteger, Skip), + MatroskaTag(ReferenceBlock, "ReferenceBlock", Integer, Skip), + MatroskaTag(ReferenceVirtual, "ReferenceVirtual", Integer, Skip), + MatroskaTag(Xmp_video_ContentCompressAlgo, "Xmp.video.ContentCompressAlgo", InternalField, Process), + MatroskaTag(ContentCompressionSettings, "ContentCompressionSettings", Binary, Skip), + MatroskaTag(Xmp_video_DocType, "Xmp.video.DocType", String, Process), + MatroskaTag(Xmp_video_DocTypeReadVersion, "Xmp.video.DocTypeReadVersion", Integer, Process), + MatroskaTag(Xmp_video_EBMLVersion, "Xmp.video.EBMLVersion", Integer, Process), + MatroskaTag(Xmp_video_DocTypeVersion, "Xmp.video.DocTypeVersion", Integer, Process), + MatroskaTag(EBMLMaxIDLength, "EBMLMaxIDLength", UInteger, Skip), + MatroskaTag(EBMLMaxSizeLength, "EBMLMaxSizeLength", UInteger, Skip), + MatroskaTag(Xmp_video_EBMLReadVersion, "Xmp.video.EBMLReadVersion", UInteger, Process), + MatroskaTag(ChapterLanguage, "ChapterLanguage", String, Skip), + MatroskaTag(ChapterCountry, "ChapterCountry", Utf8, Skip), + MatroskaTag(SegmentFamily, "SegmentFamily", Binary, Skip), + MatroskaTag(Xmp_video_DateUTC, "Xmp.video.DateUTC", Date, Process), + MatroskaTag(Xmp_video_TagLanguage, "Xmp.video.TagLanguage", String, Process), + MatroskaTag(Xmp_video_TagDefault, "Xmp.video.TagDefault", Boolean, Process), + MatroskaTag(TagBinary, "TagBinary", Binary, Skip), + MatroskaTag(Xmp_video_TagString, "Xmp.video.TagString", String, Process), + MatroskaTag(Xmp_video_Duration, "Xmp.video.Duration", Date, Process), + MatroskaTag(ChapterProcessPrivate, "ChapterProcessPrivate", Master, Skip), + MatroskaTag(ChapterFlagEnabled, "ChapterFlagEnabled", Boolean, Skip), + MatroskaTag(Xmp_video_TagName, "Xmp.video.TagName", String, Process), + MatroskaTag(EditionEntry, "EditionEntry", Master, Composite), + MatroskaTag(EditionUID, "EditionUID", UInteger, Skip), + MatroskaTag(EditionFlagHidden, "EditionFlagHidden", Boolean, Skip), + MatroskaTag(EditionFlagDefault, "EditionFlagDefault", Boolean, Skip), + MatroskaTag(EditionFlagOrdered, "EditionFlagOrdered", Boolean, Skip), + MatroskaTag(Xmp_video_AttachFileData, "Xmp.video.AttachFileData", String, Process), + MatroskaTag(Xmp_video_AttachFileMIME, "Xmp.video.AttachFileMIME", String, Process), + MatroskaTag(Xmp_video_AttachFileName, "Xmp.video.AttachFileName", String, Process), + MatroskaTag(AttachedFileReferral, "AttachedFileReferral", Binary, Skip), + MatroskaTag(Xmp_video_AttachFileDesc, "Xmp.video.AttachFileDesc", String, Process), + MatroskaTag(Xmp_video_AttachFileUID, "Xmp.video.AttachFileUID", UInteger, Process), + MatroskaTag(Xmp_video_ContentEncryptAlgo, "Xmp.video.ContentEncryptAlgo", InternalField, Process), + MatroskaTag(ContentEncryptionKeyID, "ContentEncryptionKeyID", Binary, Skip), + MatroskaTag(ContentSignature, "ContentSignature", Binary, Skip), + MatroskaTag(ContentSignatureKeyID, "ContentSignatureKeyID", Binary, Skip), + MatroskaTag(Xmp_video_ContentSignAlgo_1, "Xmp.video.ContentSignAlgo", InternalField, Process), + MatroskaTag(Xmp_video_ContentSignHashAlgo_1, "Xmp.video.ContentSignHashAlgo", InternalField, Process), + MatroskaTag(Xmp_video_MuxingApp, "Xmp.video.MuxingApp", String, Process), + MatroskaTag(Seek, "Seek", Master, Composite), + MatroskaTag(ContentEncodingOrder, "ContentEncodingOrder", UInteger, Skip), + MatroskaTag(ContentEncodingScope, "ContentEncodingScope", UInteger, Skip), + MatroskaTag(Xmp_video_ContentEncodingType, "Xmp.video.ContentEncodingType", InternalField, Process), + MatroskaTag(ContentCompression, "ContentCompression", Master, Composite), + MatroskaTag(ContentEncryption, "ContentEncryption", Master, Composite), + MatroskaTag(CueRefNumber, "CueRefNumber", UInteger, Skip), + MatroskaTag(Xmp_video_TrackName, "Xmp.video.TrackName", String, Process), + MatroskaTag(CueBlockNumber, "CueBlockNumber", UInteger, Skip), + MatroskaTag(TrackOffset, "TrackOffset", Integer, Skip), + MatroskaTag(SeekID, "SeekID", Binary, Skip), + MatroskaTag(SeekPosition, "SeekPosition", UInteger, Skip), + MatroskaTag(Stereo3DMode, "Stereo3DMode", UInteger, Skip), + MatroskaTag(Xmp_video_CropBottom, "Xmp.video.CropBottom", Integer, Process), + MatroskaTag(Xmp_video_Width_2, "Xmp.video.Width", Integer, Process), + MatroskaTag(Xmp_video_DisplayUnit, "Xmp.video.DisplayUnit", InternalField, Process), + MatroskaTag(Xmp_video_AspectRatioType, "Xmp.video.AspectRatioType", InternalField, Process), + MatroskaTag(Xmp_video_Height_2, "Xmp.video.Height", Integer, Process), + MatroskaTag(Xmp_video_CropTop, "Xmp.video.CropTop", Integer, Process), + MatroskaTag(Xmp_video_CropLeft, "Xmp.video.CropLeft", Integer, Process), + MatroskaTag(Xmp_video_CropRight, "Xmp.video.CropRight", Integer, Process), + MatroskaTag(TrackForced, "TrackForced", Boolean, Process), + MatroskaTag(MaxBlockAdditionID, "MaxBlockAdditionID", UInteger, Skip), + MatroskaTag(Xmp_video_WritingApp, "Xmp.video.WritingApp", String, Process), + MatroskaTag(SilentTracks, "SilentTracks", Master, Composite), + MatroskaTag(SilentTrackNumber, "SilentTrackNumber", UInteger, Skip), + MatroskaTag(AttachedFile, "AttachedFile", Master, Composite), + MatroskaTag(ContentEncoding, "ContentEncoding", Master, Composite), + MatroskaTag(Xmp_audio_BitsPerSample, "Xmp.audio.BitsPerSample", Integer, Process), + MatroskaTag(CodecPrivate, "CodecPrivate", Binary, Skip), + MatroskaTag(Targets, "Targets", Master, Composite), + MatroskaTag(Xmp_video_PhysicalEquivalent, "Xmp.video.PhysicalEquivalent", InternalField, Process), + MatroskaTag(TagChapterUID, "TagChapterUID", UInteger, Skip), + MatroskaTag(TagTrackUID, "TagTrackUID", UInteger, Skip), + MatroskaTag(TagAttachmentUID, "TagAttachmentUID", UInteger, Skip), + MatroskaTag(TagEditionUID, "TagEditionUID", UInteger, Skip), + MatroskaTag(Xmp_video_TargetType, "Xmp.video.TargetType", String, Process), + MatroskaTag(SignedElement, "SignedElement", Binary, Skip), + MatroskaTag(TrackTranslate, "TrackTranslate", Master, Composite), + MatroskaTag(TrackTranslateTrackID, "TrackTranslateTrackID", Binary, Skip), + MatroskaTag(TrackTranslateCodec, "TrackTranslateCodec", UInteger, Skip), + MatroskaTag(TrackTranslateEditionUID, "TrackTranslateEditionUID", UInteger, Skip), + MatroskaTag(SimpleTag, "SimpleTag", Master, Composite), + MatroskaTag(TargetTypeValue, "TargetTypeValue", UInteger, Skip), + MatroskaTag(ChapterProcessCommand, "ChapterProcessCommand", Master, Composite), + MatroskaTag(ChapterProcessTime, "ChapterProcessTime", UInteger, Skip), + MatroskaTag(ChapterTranslate, "ChapterTranslate", Master, Composite), + MatroskaTag(ChapterProcessData, "ChapterProcessData", Binary, Skip), + MatroskaTag(ChapterProcess, "ChapterProcess", Master, Composite), + MatroskaTag(ChapterProcessCodecID, "ChapterProcessCodecID", UInteger, Skip), + MatroskaTag(ChapterTranslateID, "ChapterTranslateID", Binary, Skip), + MatroskaTag(Xmp_video_TranslateCodec, "Xmp.video.TranslateCodec", InternalField, Process), + MatroskaTag(ChapterTranslateEditionUID, "ChapterTranslateEditionUID", UInteger, Skip), + MatroskaTag(ContentEncodings, "ContentEncodings", Master, Composite), + MatroskaTag(MinCache, "MinCache", UInteger, Skip), + MatroskaTag(MaxCache, "MaxCache", UInteger, Skip), + MatroskaTag(ChapterSegmentUID, "ChapterSegmentUID", Binary, Skip), + MatroskaTag(ChapterSegmentEditionUID, "ChapterSegmentEditionUID", UInteger, Skip), + MatroskaTag(TrackOverlay, "TrackOverlay", UInteger, Skip), + MatroskaTag(Tag, "Tag", Master, Composite), + MatroskaTag(SegmentFileName, "SegmentFileName", Utf8, Skip), + MatroskaTag(SegmentUID, "SegmentUID", Binary, Skip), + MatroskaTag(ChapterUID, "ChapterUID", UInteger, Skip), + MatroskaTag(TrackUID, "TrackUID", UInteger, Skip), + MatroskaTag(TrackAttachmentUID, "TrackAttachmentUID", UInteger, Skip), + MatroskaTag(BlockAdditions, "BlockAdditions", Master, Composite), + MatroskaTag(Xmp_audio_OutputSampleRate, "Xmp.audio.OutputSampleRate", Float, Process), + MatroskaTag(Xmp_video_Title, "Xmp.video.Title", String, Process), + MatroskaTag(ChannelPositions, "ChannelPositions", Binary, Skip), + MatroskaTag(SignatureElements, "SignatureElements", Master, Composite), + MatroskaTag(SignatureElementList, "SignatureElementList", Master, Composite), + MatroskaTag(Xmp_video_ContentSignAlgo_2, "Xmp.video.ContentSignAlgo", InternalField, Process), + MatroskaTag(Xmp_video_ContentSignHashAlgo_2, "Xmp.video.ContentSignHashAlgo", InternalField, Process), + MatroskaTag(SignaturePublicKey, "SignaturePublicKey", Binary, Skip), + MatroskaTag(Signature, "Signature", Binary, Skip), + MatroskaTag(TrackLanguage, "TrackLanguage", String, + Skip), // Process : see values here https://www.loc.gov/standards/iso639-2/php/code_list.php + MatroskaTag(TrackTimecodeScale, "TrackTimecodeScale", Float, Skip), + MatroskaTag(Xmp_video_FrameRate, "Xmp.video.FrameRate", Float, Process), + MatroskaTag(VideoFrameRate_DefaultDuration, "VideoFrameRate.DefaultDuration", Float, Skip), + MatroskaTag(Video_Audio_CodecName, "Video.Audio.CodecName", InternalField, Process), + MatroskaTag(CodecDownloadURL, "CodecDownloadURL", InternalField, Process), + MatroskaTag(TimecodeScale, "Xmp.video.TimecodeScale", Date, Process), + MatroskaTag(ColorSpace, "ColorSpace", String, Process), + MatroskaTag(Xmp_video_OpColor, "Xmp.video.OpColor", Float, Skip), + MatroskaTag(CodecSettings, "CodecSettings", Boolean, Process), + MatroskaTag(CodecInfoURL, "CodecInfoURL", InternalField, Process), + MatroskaTag(PrevFileName, "PrevFileName", Utf8, Skip), + MatroskaTag(PrevUID, "PrevUID", Binary, Skip), + MatroskaTag(NextFileName, "NextFileName", Utf8, Skip), + MatroskaTag(NextUID, "NextUID", Binary, Skip), + MatroskaTag(Chapters, "Chapters", Master, Skip), + MatroskaTag(SeekHead, "SeekHead", Master, Composite), + MatroskaTag(Tags, "Tags", Master, Composite), + MatroskaTag(Info, "Info", Master, Composite), + MatroskaTag(Tracks, "Tracks", Master, Composite), + MatroskaTag(SegmentHeader, "SegmentHeader", Master, Composite), + MatroskaTag(Attachments, "Attachments", Master, Composite), + MatroskaTag(EBMLHeader, "EBMLHeader", Master, Composite), + MatroskaTag(SignatureSlot, "SignatureSlot", Master, Composite), + MatroskaTag(Cues, "Cues", Master, Composite), + MatroskaTag(Cluster, "Cluster", Master, Composite)}; + +std::array matroskaTrackType = { + MatroskaTag(0x1, "Video"), MatroskaTag(0x2, "Audio"), MatroskaTag(0x3, "Complex"), MatroskaTag(0x10, "Logo"), + MatroskaTag(0x11, "Subtitle"), MatroskaTag(0x12, "Buttons"), MatroskaTag(0x20, "Control")}; + +const std::array compressionAlgorithm = {MatroskaTag(0, "zlib "), MatroskaTag(1, "bzlib"), + MatroskaTag(2, "lzo1x"), MatroskaTag(3, "Header Stripping")}; + +const std::array audioChannels = {MatroskaTag(1, "Mono"), MatroskaTag(2, "Stereo"), + MatroskaTag(5, "5.1 Surround Sound"), + MatroskaTag(7, "7.1 Surround Sound")}; + +const std::array displayUnit = {MatroskaTag(0x0, "Pixels"), MatroskaTag(0x1, "cm"), + MatroskaTag(0x2, "inches"), MatroskaTag(0x3, "display aspect ratio"), + MatroskaTag(0x2, "unknown")}; + +const std::array encryptionAlgorithm = {MatroskaTag(0, "Not Encrypted"), MatroskaTag(1, "DES"), + MatroskaTag(2, "3DES"), MatroskaTag(3, "Twofish"), + MatroskaTag(4, "Blowfish"), MatroskaTag(5, "AES")}; + +const std::array chapterPhysicalEquivalent = { + MatroskaTag(10, "Index"), MatroskaTag(20, "Track"), MatroskaTag(30, "Session"), MatroskaTag(40, "Layer"), + MatroskaTag(50, "Side"), MatroskaTag(60, "CD / DVD"), MatroskaTag(70, "Set / Package")}; + +const std::array encodingType = {MatroskaTag(0, "Compression"), MatroskaTag(1, "Encryption")}; + +const std::array videoScanType = {MatroskaTag(0, "Progressive"), MatroskaTag(1, "Interlaced")}; + +const std::array chapterTranslateCodec = {MatroskaTag(0, "Matroska Script"), + MatroskaTag(1, "DVD Menu")}; + +const std::array aspectRatioType = {MatroskaTag(0, "Free Resizing"), + MatroskaTag(1, "Keep Aspect Ratio"), MatroskaTag(2, "Fixed")}; + +const std::array contentSignatureAlgorithm = {MatroskaTag(0, "Not Signed"), MatroskaTag(1, "RSA")}; + +const std::array contentSignatureHashAlgorithm = {MatroskaTag(0, "Not Signed"), + MatroskaTag(1, "SHA1-160"), MatroskaTag(2, "MD5")}; + +const std::array trackEnable = {MatroskaTag(0x1, "Xmp.video.Enabled"), + MatroskaTag(0x2, "Xmp.audio.Enabled"), + MatroskaTag(0x11, "Xmp.video.SubTEnabled")}; + +const std::array defaultOn = {MatroskaTag(0x1, "Xmp.video.DefaultOn"), + MatroskaTag(0x2, "Xmp.audio.DefaultOn"), + MatroskaTag(0x11, "Xmp.video.SubTDefaultOn")}; + +const std::array trackForced = {MatroskaTag(0x1, "Xmp.video.TrackForced"), + MatroskaTag(0x2, "Xmp.audio.TrackForced"), + MatroskaTag(0x11, "Xmp.video.SubTTrackForced")}; + +const std::array trackLacing = {MatroskaTag(0x1, "Xmp.video.TrackLacing"), + MatroskaTag(0x2, "Xmp.audio.TrackLacing"), + MatroskaTag(0x11, "Xmp.video.SubTTrackLacing")}; + +const std::array codecDecodeAll = {MatroskaTag(0x1, "Xmp.video.CodecDecodeAll"), + MatroskaTag(0x2, "Xmp.audio.CodecDecodeAll"), + MatroskaTag(0x11, "Xmp.video.SubTCodecDecodeAll")}; + +const std::array codecDownloadUrl = {MatroskaTag(0x1, "Xmp.video.CodecDownloadUrl"), + MatroskaTag(0x2, "Xmp.audio.CodecDownloadUrl"), + MatroskaTag(0x11, "Xmp.video.SubTCodecDownloadUrl")}; + +const std::array codecSettings = {MatroskaTag(0x1, "Xmp.video.CodecSettings"), + MatroskaTag(0x2, "Xmp.audio.CodecSettings"), + MatroskaTag(0x11, "Xmp.video.SubTCodecSettings")}; + +const std::array trackCodec = {MatroskaTag(0x1, "Xmp.video.Codec"), + MatroskaTag(0x2, "Xmp.audio.Compressor"), + MatroskaTag(0x11, "Xmp.video.SubTCodec")}; + +const std::array codecInfo = {MatroskaTag(0x1, "Xmp.video.CodecInfo"), + MatroskaTag(0x2, "Xmp.audio.CodecInfo"), + MatroskaTag(0x11, "Xmp.video.SubTCodecInfo")}; + +const std::array streamRate = {MatroskaTag(0x1, "Xmp.video.FrameRate"), + MatroskaTag(0x2, "Xmp.audio.DefaultDuration")}; + +/*! + @brief Function used to calculate Tags, Tags may comprise of more than + one byte. The first byte calculates size of the Tag and the remaining + bytes are used to calculate the rest of the Tag. + Returns Tag Value. + */ +[[nodiscard]] size_t returnTagValue(const byte* buf, size_t size) { + assert(size > 0 && size <= 8); + + size_t b0 = buf[0] & (0xff >> size); + size_t tag = b0 << ((size - 1) * 8); + for (size_t i = 1; i < size; ++i) { + tag |= static_cast(buf[i]) << ((size - i - 1) * 8); + } + + return tag; +} + +/*! + @brief Function used to convert buffer data into Integeral information, + information stored in BigEndian format + */ +[[nodiscard]] bool convertToUint64(const byte* buf, size_t size, uint64_t& value) { + uint64_t ret = 0; + for (size_t i = 0; i < size; ++i) { + ret |= static_cast(buf[i]) << ((size - i - 1) * 8); + } + + if (ret < std::numeric_limits::min()) + return false; + if (ret > std::numeric_limits::max()) + return false; + + value = ret; + return true; +} + +} // namespace Internal +} // namespace Exiv2 + +namespace Exiv2 { + +using namespace Exiv2::Internal; + +MatroskaVideo::MatroskaVideo(BasicIo::UniquePtr io) : Image(ImageType::mkv, mdNone, std::move(io)) { +} // MatroskaVideo::MatroskaVideo + +std::string MatroskaVideo::mimeType() const { + return "video/matroska"; +} + +void MatroskaVideo::writeMetadata() { +} + +void MatroskaVideo::readMetadata() { + if (io_->open() != 0) + throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); + + // Ensure that this is the correct image type + if (!isMkvType(*io_, false)) { + if (io_->error() || io_->eof()) + throw Error(ErrorCode::kerFailedToReadImageData); + throw Error(ErrorCode::kerNotAnImage, "Matroska"); + } + + IoCloser closer(*io_); + clearMetadata(); + continueTraversing_ = true; + height_ = width_ = 1; + + xmpData_["Xmp.video.FileName"] = io_->path(); + xmpData_["Xmp.video.FileSize"] = io_->size() / bytesMB; + xmpData_["Xmp.video.MimeType"] = mimeType(); + + while (continueTraversing_) + decodeBlock(); + + aspectRatio(); +} + +void MatroskaVideo::decodeBlock() { + byte buf[8]; + io_->read(buf, 1); + + if (io_->eof()) { + continueTraversing_ = false; + return; + } + + uint32_t block_size = findBlockSize(buf[0]); // 0-8 + if (block_size > 0) + io_->read(buf + 1, block_size - 1); + + auto tag_id = returnTagValue(buf, block_size); + const MatroskaTag* tag = findTag(matroskaTags, tag_id); + + if (!tag) { + continueTraversing_ = false; + return; + } + + // tag->dump(std::cout); + + if (tag->_id == Cues || tag->_id == Cluster) { + continueTraversing_ = false; + return; + } + + io_->read(buf, 1); + block_size = findBlockSize(buf[0]); // 0-8 + + if (block_size > 0) + io_->read(buf + 1, block_size - 1); + size_t size = returnTagValue(buf, block_size); + + if (tag->isComposite() && !tag->isSkipped()) + return; + + const size_t bufMaxSize = 200; + +#ifndef SUPPRESS_WARNINGS + if (!tag->isSkipped() && size > bufMaxSize) { + EXV_WARNING << "Size " << size << " of Matroska tag 0x" << std::hex << tag->_id << std::dec << " is greater than " + << bufMaxSize << ": ignoring it.\n"; + } +#endif + if (tag->isSkipped() || size > bufMaxSize) { + io_->seek(size, BasicIo::cur); + return; + } + + DataBuf buf2(bufMaxSize + 1); + io_->read(buf2.data(), size); + switch (tag->_type) { + case InternalField: + decodeInternalTags(tag, buf2.data(), size); + break; + case String: + case Utf8: + decodeStringTags(tag, buf2.data()); + break; + case Integer: + case UInteger: + decodeIntegerTags(tag, buf2.data(), size); + break; + case Boolean: + decodeBooleanTags(tag, buf2.data(), size); + break; + case Date: + decodeDateTags(tag, buf2.data(), size); + break; + case Float: + decodeFloatTags(tag, buf2.data(), size); + break; + case Binary: + break; + case Master: + break; + default: + break; + } +} // MatroskaVideo::decodeBlock + +void MatroskaVideo::decodeInternalTags(const MatroskaTag* tag, const byte* buf, size_t size) { + const MatroskaTag* internalMt = nullptr; + uint64_t key = 0; + if (!convertToUint64(buf, size, key)) + return; + + switch (tag->_id) { + case Xmp_video_VideoScanTpye: + internalMt = findTag(videoScanType, key); + break; + case Xmp_audio_ChannelType: + internalMt = findTag(audioChannels, key); + break; + case Xmp_video_ContentCompressAlgo: + internalMt = findTag(compressionAlgorithm, key); + break; + case Xmp_video_ContentEncryptAlgo: + internalMt = findTag(encryptionAlgorithm, key); + break; + case Xmp_video_ContentSignAlgo_1: + case Xmp_video_ContentSignAlgo_2: + internalMt = findTag(contentSignatureAlgorithm, key); + break; + case Xmp_video_ContentSignHashAlgo_1: + case Xmp_video_ContentSignHashAlgo_2: + internalMt = findTag(contentSignatureHashAlgorithm, key); + break; + case Xmp_video_ContentEncodingType: + internalMt = findTag(encodingType, key); + break; + case Xmp_video_DisplayUnit: + internalMt = findTag(displayUnit, key); + break; + case Xmp_video_AspectRatioType: + internalMt = findTag(aspectRatioType, key); + break; + case Xmp_video_PhysicalEquivalent: + internalMt = findTag(chapterPhysicalEquivalent, key); + break; + case Xmp_video_TranslateCodec: + internalMt = findTag(chapterTranslateCodec, key); + break; + case Video_Audio_CodecID: + internalMt = findTag(trackCodec, key); + break; + case Video_Audio_CodecName: + internalMt = findTag(codecInfo, key); + break; + case CodecDownloadURL: + case CodecInfoURL: + internalMt = findTag(codecDownloadUrl, key); + break; + default: + break; + } + if (internalMt) { + xmpData_[tag->_label] = internalMt->_label; + } else { + xmpData_[tag->_label] = key; + } +} + +void MatroskaVideo::decodeStringTags(const MatroskaTag* tag, const byte* buf) { + if (tag->_id == TrackNumber) { + track_count_++; + xmpData_[tag->_label] = track_count_; + } else { + xmpData_[tag->_label] = buf; + } +} + +void MatroskaVideo::decodeIntegerTags(const MatroskaTag* tag, const byte* buf, size_t size) { + uint64_t value = 0; + if (!convertToUint64(buf, size, value)) + return; + + if (tag->_id == Xmp_video_Width_1 || tag->_id == Xmp_video_Width_2) + width_ = value; + if (tag->_id == Xmp_video_Height_1 || tag->_id == Xmp_video_Height_2) + height_ = value; + xmpData_[tag->_label] = value; +} + +void MatroskaVideo::decodeBooleanTags(const MatroskaTag* tag, const byte* buf, size_t size) { + std::string str("No"); + const MatroskaTag* internalMt = nullptr; + uint64_t key = 0; + if (!convertToUint64(buf, size, key)) + return; + + switch (tag->_id) { + case TrackType: // this tags is used internally only to deduce the type of track (video or audio) + internalMt = findTag(matroskaTrackType, key); + stream_ = internalMt->_id; + internalMt = nullptr; + break; + case TrackUsed: + internalMt = findTag(trackEnable, key); + break; + case TrackDefault: + internalMt = findTag(defaultOn, key); + break; + case TrackForced: + internalMt = findTag(trackForced, key); + break; + case TrackLacing: + internalMt = findTag(trackLacing, key); + break; + case CodecDecodeAll: + internalMt = findTag(codecDecodeAll, key); + break; + case CodecSettings: + internalMt = findTag(codecSettings, key); + break; + case Xmp_video_TagDefault: + internalMt = tag; + break; + default: + break; + } + + if (internalMt) { + str = "Yes"; + xmpData_[internalMt->_label] = str; + } +} + +void MatroskaVideo::decodeDateTags(const MatroskaTag* tag, const byte* buf, size_t size) { + int64_t duration_in_ms = 0; + uint64_t value = 0; + switch (tag->_id) { + case Xmp_video_Duration: + if (size <= 4) { + duration_in_ms = + static_cast(getFloat(buf, bigEndian) * static_cast(time_code_scale_) * 1000.0f); + } else { + duration_in_ms = static_cast(getDouble(buf, bigEndian) * time_code_scale_ * 1000); + } + xmpData_[tag->_label] = duration_in_ms; + break; + case Xmp_video_DateUTC: + + if (!convertToUint64(buf, size, value)) + return; + duration_in_ms = value / 1000000000; + xmpData_[tag->_label] = duration_in_ms; + break; + + case TimecodeScale: + if (!convertToUint64(buf, size, value)) + return; + time_code_scale_ = (double)value / (double)1000000000; + xmpData_[tag->_label] = time_code_scale_; + break; + default: + break; + } +} + +void MatroskaVideo::decodeFloatTags(const MatroskaTag* tag, const byte* buf, size_t size) { + xmpData_[tag->_label] = getFloat(buf, bigEndian); + + double frame_rate = 0; + switch (tag->_id) { + case Xmp_audio_SampleRate: + case Xmp_audio_OutputSampleRate: + xmpData_[tag->_label] = getFloat(buf, bigEndian); + break; + case VideoFrameRate_DefaultDuration: + case Xmp_video_FrameRate: { + uint64_t key = 0; + if (!convertToUint64(buf, size, key)) + return; + const MatroskaTag* internalMt = findTag(streamRate, key); + if (internalMt) { + switch (stream_) { + case 1: // video + frame_rate = (double)1000000000 / (double)key; + break; + case 2: // audio + frame_rate = static_cast(key / 1000); + break; + default: + break; + } + if (frame_rate) + xmpData_[internalMt->_label] = frame_rate; + } else + xmpData_[tag->_label] = "Variable Bit Rate"; + } break; + default: + xmpData_[tag->_label] = getFloat(buf, bigEndian); + break; + } +} + +void MatroskaVideo::aspectRatio() { + double aspectRatio = (double)width_ / (double)height_; + aspectRatio = floor(aspectRatio * 10) / 10; + xmpData_["Xmp.video.AspectRatio"] = aspectRatio; + + int aR = (int)((aspectRatio * 10.0) + 0.1); + + switch (aR) { + case 13: + xmpData_["Xmp.video.AspectRatio"] = "4:3"; + break; + case 17: + xmpData_["Xmp.video.AspectRatio"] = "16:9"; + break; + case 10: + xmpData_["Xmp.video.AspectRatio"] = "1:1"; + break; + case 16: + xmpData_["Xmp.video.AspectRatio"] = "16:10"; + break; + case 22: + xmpData_["Xmp.video.AspectRatio"] = "2.21:1"; + break; + case 23: + xmpData_["Xmp.video.AspectRatio"] = "2.35:1"; + break; + case 12: + xmpData_["Xmp.video.AspectRatio"] = "5:4"; + break; + default: + xmpData_["Xmp.video.AspectRatio"] = aspectRatio; + break; + } +} + +uint32_t MatroskaVideo::findBlockSize(byte b) { + if (b & 128) + return 1; + else if (b & 64) + return 2; + else if (b & 32) + return 3; + else if (b & 16) + return 4; + else if (b & 8) + return 5; + else if (b & 4) + return 6; + else if (b & 2) + return 7; + else if (b & 1) + return 8; + else + return 0; +} + +Image::UniquePtr newMkvInstance(BasicIo::UniquePtr io, bool /*create*/) { + Image::UniquePtr image(new MatroskaVideo(std::move(io))); + if (!image->good()) { + image.reset(); + } + return image; +} + +bool isMkvType(BasicIo& iIo, bool advance) { + bool result = true; + byte tmpBuf[4]; + iIo.read(tmpBuf, 4); + + if (iIo.error() || iIo.eof()) + return false; + + if (0x1a != tmpBuf[0] || 0x45 != tmpBuf[1] || 0xdf != tmpBuf[2] || 0xa3 != tmpBuf[3]) { + result = false; + } + + if (!advance || !result) + iIo.seek(0, BasicIo::beg); + return result; +} + +} // namespace Exiv2 \ No newline at end of file diff --git a/unitTests/CMakeLists.txt b/unitTests/CMakeLists.txt index 633847fccc..15d41541b8 100644 --- a/unitTests/CMakeLists.txt +++ b/unitTests/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(unit_tests test_jp2image_int.cpp test_IptcKey.cpp test_LangAltValueRead.cpp + test_matroskavideo.cpp test_Photoshop.cpp test_pngimage.cpp test_safe_op.cpp diff --git a/unitTests/test_matroskavideo.cpp b/unitTests/test_matroskavideo.cpp new file mode 100644 index 0000000000..9378171661 --- /dev/null +++ b/unitTests/test_matroskavideo.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include +#include + +using namespace Exiv2; + +TEST(MatroskaVideo, canBeOpenedWithEmptyMemIo) { + auto memIo = std::make_unique(); + ASSERT_NO_THROW(MatroskaVideo mkv(std::move(memIo))); +} + +TEST(MatroskaVideo, mimeTypeIsMkv) { + auto memIo = std::make_unique(); + MatroskaVideo mkv(std::move(memIo)); + + ASSERT_EQ("video/matroska", mkv.mimeType()); +} + +TEST(MatroskaVideo, isMkvTypewithEmptyDataReturnsFalse) { + MemIo memIo; + ASSERT_FALSE(isMkvType(memIo, false)); +} + +TEST(MatroskaVideo, emptyThrowError) { + auto memIo = std::make_unique(); + MatroskaVideo mkv(std::move(memIo)); + ASSERT_THROW(mkv.readMetadata(), Exiv2::Error); +} + +TEST(MatroskaVideo, printStructurePrintsNothingAndthrowError) { + auto memIo = std::make_unique(); + MatroskaVideo mkv(std::move(memIo)); + + std::ostringstream stream; + + ASSERT_THROW(mkv.printStructure(stream, Exiv2::kpsNone, 1), Exiv2::Error); + + ASSERT_TRUE(stream.str().empty()); +} + +TEST(MatroskaVideo, readMetadata) { + auto memIo = std::make_unique(); + MatroskaVideo mkv(std::move(memIo)); + XmpData xmpData; + xmpData["Xmp.video.TotalStream"] = 1000; + xmpData["Xmp.video.TimecodeScale"] = 10001; + xmpData["Xmp.video.AspectRatio"] = "4:3"; + ASSERT_NO_THROW(mkv.setXmpData(xmpData)); + auto data = mkv.xmpData(); + ASSERT_FALSE(data.empty()); + ASSERT_EQ(xmpData["Xmp.video.TotalStream"].count(), 4); +} \ No newline at end of file