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
This commit is contained in:
Mohamed Ali Chebbi 2022-12-30 15:58:07 +01:00 committed by GitHub
parent 9ca161d1e5
commit 1280f3b849
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1236 additions and 2 deletions

View File

@ -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)

View File

@ -35,6 +35,7 @@ enum class ImageType {
webp,
xmp, ///< XMP sidecar files
qtime,
mkv,
};
} // namespace Exiv2

View File

@ -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 <size_t N>
[[nodiscard]] const MatroskaTag* findTag(const std::array<MatroskaTag, N>& 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_

View File

@ -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

View File

@ -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

976
src/matroskavideo.cpp Normal file
View File

@ -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 <stdlib.h>
#include <array>
#include <cassert>
#include <cmath>
#include <iostream>
#include <limits>
// *****************************************************************************
// 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<MatroskaTag, 198> 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<MatroskaTag, 7> 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<MatroskaTag, 4> compressionAlgorithm = {MatroskaTag(0, "zlib "), MatroskaTag(1, "bzlib"),
MatroskaTag(2, "lzo1x"), MatroskaTag(3, "Header Stripping")};
const std::array<MatroskaTag, 4> audioChannels = {MatroskaTag(1, "Mono"), MatroskaTag(2, "Stereo"),
MatroskaTag(5, "5.1 Surround Sound"),
MatroskaTag(7, "7.1 Surround Sound")};
const std::array<MatroskaTag, 5> displayUnit = {MatroskaTag(0x0, "Pixels"), MatroskaTag(0x1, "cm"),
MatroskaTag(0x2, "inches"), MatroskaTag(0x3, "display aspect ratio"),
MatroskaTag(0x2, "unknown")};
const std::array<MatroskaTag, 6> encryptionAlgorithm = {MatroskaTag(0, "Not Encrypted"), MatroskaTag(1, "DES"),
MatroskaTag(2, "3DES"), MatroskaTag(3, "Twofish"),
MatroskaTag(4, "Blowfish"), MatroskaTag(5, "AES")};
const std::array<MatroskaTag, 7> 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<MatroskaTag, 2> encodingType = {MatroskaTag(0, "Compression"), MatroskaTag(1, "Encryption")};
const std::array<MatroskaTag, 2> videoScanType = {MatroskaTag(0, "Progressive"), MatroskaTag(1, "Interlaced")};
const std::array<MatroskaTag, 2> chapterTranslateCodec = {MatroskaTag(0, "Matroska Script"),
MatroskaTag(1, "DVD Menu")};
const std::array<MatroskaTag, 3> aspectRatioType = {MatroskaTag(0, "Free Resizing"),
MatroskaTag(1, "Keep Aspect Ratio"), MatroskaTag(2, "Fixed")};
const std::array<MatroskaTag, 2> contentSignatureAlgorithm = {MatroskaTag(0, "Not Signed"), MatroskaTag(1, "RSA")};
const std::array<MatroskaTag, 3> contentSignatureHashAlgorithm = {MatroskaTag(0, "Not Signed"),
MatroskaTag(1, "SHA1-160"), MatroskaTag(2, "MD5")};
const std::array<MatroskaTag, 3> trackEnable = {MatroskaTag(0x1, "Xmp.video.Enabled"),
MatroskaTag(0x2, "Xmp.audio.Enabled"),
MatroskaTag(0x11, "Xmp.video.SubTEnabled")};
const std::array<MatroskaTag, 3> defaultOn = {MatroskaTag(0x1, "Xmp.video.DefaultOn"),
MatroskaTag(0x2, "Xmp.audio.DefaultOn"),
MatroskaTag(0x11, "Xmp.video.SubTDefaultOn")};
const std::array<MatroskaTag, 3> trackForced = {MatroskaTag(0x1, "Xmp.video.TrackForced"),
MatroskaTag(0x2, "Xmp.audio.TrackForced"),
MatroskaTag(0x11, "Xmp.video.SubTTrackForced")};
const std::array<MatroskaTag, 3> trackLacing = {MatroskaTag(0x1, "Xmp.video.TrackLacing"),
MatroskaTag(0x2, "Xmp.audio.TrackLacing"),
MatroskaTag(0x11, "Xmp.video.SubTTrackLacing")};
const std::array<MatroskaTag, 3> codecDecodeAll = {MatroskaTag(0x1, "Xmp.video.CodecDecodeAll"),
MatroskaTag(0x2, "Xmp.audio.CodecDecodeAll"),
MatroskaTag(0x11, "Xmp.video.SubTCodecDecodeAll")};
const std::array<MatroskaTag, 3> codecDownloadUrl = {MatroskaTag(0x1, "Xmp.video.CodecDownloadUrl"),
MatroskaTag(0x2, "Xmp.audio.CodecDownloadUrl"),
MatroskaTag(0x11, "Xmp.video.SubTCodecDownloadUrl")};
const std::array<MatroskaTag, 3> codecSettings = {MatroskaTag(0x1, "Xmp.video.CodecSettings"),
MatroskaTag(0x2, "Xmp.audio.CodecSettings"),
MatroskaTag(0x11, "Xmp.video.SubTCodecSettings")};
const std::array<MatroskaTag, 3> trackCodec = {MatroskaTag(0x1, "Xmp.video.Codec"),
MatroskaTag(0x2, "Xmp.audio.Compressor"),
MatroskaTag(0x11, "Xmp.video.SubTCodec")};
const std::array<MatroskaTag, 3> codecInfo = {MatroskaTag(0x1, "Xmp.video.CodecInfo"),
MatroskaTag(0x2, "Xmp.audio.CodecInfo"),
MatroskaTag(0x11, "Xmp.video.SubTCodecInfo")};
const std::array<MatroskaTag, 2> 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<size_t>(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<uint64_t>(buf[i]) << ((size - i - 1) * 8);
}
if (ret < std::numeric_limits<uint64_t>::min())
return false;
if (ret > std::numeric_limits<uint64_t>::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<int64_t>(getFloat(buf, bigEndian) * static_cast<float>(time_code_scale_) * 1000.0f);
} else {
duration_in_ms = static_cast<int64_t>(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<double>(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

View File

@ -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

View File

@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <gtest/gtest.h>
#include <array>
#include <exiv2/matroskavideo.hpp>
using namespace Exiv2;
TEST(MatroskaVideo, canBeOpenedWithEmptyMemIo) {
auto memIo = std::make_unique<MemIo>();
ASSERT_NO_THROW(MatroskaVideo mkv(std::move(memIo)));
}
TEST(MatroskaVideo, mimeTypeIsMkv) {
auto memIo = std::make_unique<MemIo>();
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<MemIo>();
MatroskaVideo mkv(std::move(memIo));
ASSERT_THROW(mkv.readMetadata(), Exiv2::Error);
}
TEST(MatroskaVideo, printStructurePrintsNothingAndthrowError) {
auto memIo = std::make_unique<MemIo>();
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<MemIo>();
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);
}