You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
exiv2/src/matroskavideo.cpp

1000 lines
42 KiB
C++

// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2004-2013 Andreas Huggel <ahuggel@gmx.net>
*
* 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.
*/
/*
File: matroskavideo.cpp
Version: $Rev$
Author(s): Abhinav Badola for GSoC 2012 (AB) <mail.abu.to@gmail.com>
History: 18-Jun-12, AB: created
Credits: See header file
*/
// *****************************************************************************
#include "rcsid_int.hpp"
EXIV2_RCSID("@(#) $Id$")
// *****************************************************************************
// included header files
#include "matroskavideo.hpp"
#include "futils.hpp"
#include "basicio.hpp"
#include "tags.hpp"
#include "tags_int.hpp"
#include "types.hpp"
#include "tiffimage_int.hpp"
// + standard includes
#include <cmath>
#include <cassert>
#define lengthof(x) (sizeof(x)/sizeof(x[0]))
using namespace std;
// *****************************************************************************
// class member definitions
namespace Exiv2
{
namespace Internal
{
//! List of composite tags. They are skipped and the child tags are read immediately
uint64_t compositeTagsList[] ={
0x0000, 0x000e, 0x000f, 0x0020, 0x0026, 0x002e, 0x0036,
0x0037, 0x003b, 0x005b, 0x0060, 0x0061, 0x0068, 0x05b9,
0x0dbb, 0x1034, 0x1035, 0x1854, 0x21a7, 0x2240, 0x23c0,
0x2624, 0x27c8, 0x2911, 0x2924, 0x2944, 0x2d80, 0x3373,
0x35a1, 0x3e5b, 0x3e7b,
0x14d9b74, 0x254c367, 0x549a966, 0x654ae6b, 0x8538067,
0x941a469, 0xa45dfa3, 0xb538667, 0xc53bb6b, 0xf43b675
};
//! List of tags which are ignored, i.e., tag and value won't be read
uint64_t ignoredTagsList[] ={
0x0021, 0x0023, 0x0033, 0x0071, 0x0077, 0x006c, 0x0067, 0x007b, 0x02f2, 0x02f3,
0x1031, 0x1032, 0x13ab, 0x13ac, 0x15ee, 0x23a2, 0x23c6, 0x2e67, 0x33a4, 0x33c5,
0x3446, 0x2de7, 0x2df8, 0x26bf, 0x28ca, 0x3384, 0x13b8, 0x037e, 0x0485, 0x18d7,
0x0005, 0x0009, 0x0011, 0x0012, 0x0016, 0x0017, 0x0018, 0x0022, 0x0024, 0x0025,
0x0027, 0x002b, 0x002f, 0x003f, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x006a,
0x006b, 0x006e, 0x007a, 0x007d, 0x0255, 0x3eb5, 0x3ea5, 0x3d7b, 0x33c4, 0x2fab,
0x2ebc, 0x29fc, 0x29a5, 0x2955, 0x2933, 0x135f, 0x2922, 0x26a5, 0x26fc, 0x2532,
0x23c9, 0x23c4, 0x23c5, 0x137f, 0x1378, 0x07e2, 0x07e3, 0x07e4, 0x0675, 0x05bc,
0x05bd, 0x05db, 0x05dd, 0x0598, 0x050d, 0x0444, 0x037c,
0x3314f, 0x43a770, 0x1eb923, 0x1cb923, 0xeb524, 0x1c83ab, 0x1e83bb
};
/*!
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
*/
extern const MatroskaTags matroskaTags[] ={
{ 0x0000, "ChapterDisplay" }, //s
{ 0x0003, "TrackType" }, //ui
{ 0x0005, "ChapterString" }, //sd
{ 0x0006, "VideoCodecID/AudioCodecID/CodecID" }, //ui
{ 0x0008, "TrackDefault" }, //ui
{ 0x0009, "ChapterTrackNumber" }, //sd
{ 0x000e, "Slices" }, //s
{ 0x000f, "ChapterTrack" }, //s
{ 0x0011, "ChapterTimeStart" }, //sd
{ 0x0012, "ChapterTimeEnd" }, //sd
{ 0x0016, "CueRefTime" }, //sd
{ 0x0017, "CueRefCluster" }, //sd
{ 0x0018, "ChapterFlagHidden" }, //sd
{ 0x001a, "Xmp.video.VideoScanTpye" }, //u
{ 0x001b, "BlockDuration" }, //s
{ 0x001c, "TrackLacing" }, //ui
{ 0x001f, "Xmp.audio.ChannelType" }, //u
{ 0x0020, "BlockGroup" }, //s
{ 0x0021, "Block" }, //sd
{ 0x0022, "BlockVirtual" }, //sd
{ 0x0023, "SimpleBlock" }, //sd
{ 0x0024, "CodecState" }, //sd
{ 0x0025, "BlockAdditional" }, //sd
{ 0x0026, "BlockMore" }, //s
{ 0x0027, "Position" }, //sd
{ 0x002a, "CodecDecodeAll" }, //ui
{ 0x002b, "PrevSize" }, //sd
{ 0x002e, "TrackEntry" }, //s
{ 0x002f, "EncryptedBlock" }, //sd
{ 0x0030, "Xmp.video.Width" }, //u
{ 0x0033, "CueTime" }, //sd
{ 0x0035, "Xmp.audio.SampleRate" }, //u
{ 0x0036, "ChapterAtom" }, //s
{ 0x0037, "CueTrackPositions" }, //s
{ 0x0039, "TrackUsed" }, //ui
{ 0x003a, "Xmp.video.Height" }, //u
{ 0x003b, "CuePoint" }, //s
{ 0x003f, "CRC-32" }, //sd
{ 0x004b, "BlockAdditionalID" }, //sd
{ 0x004c, "LaceNumber" }, //sd
{ 0x004d, "FrameNumber" }, //sd
{ 0x004e, "Delay" }, //sd
{ 0x004f, "ClusterDuration" }, //sd
{ 0x0057, "TrackNumber" }, //ui
{ 0x005b, "CueReference" }, //s
{ 0x0060, "Video" }, //s
{ 0x0061, "Audio" }, //s
{ 0x0067, "Timecode" }, //sd
{ 0x0068, "TimeSlice" }, //s
{ 0x006a, "CueCodecState" }, //sd
{ 0x006b, "CueRefCodecState" }, //sd
{ 0x006c, "Void" }, //sd
{ 0x006e, "BlockAddID" }, //sd
{ 0x0071, "CueClusterPosition" }, //sd
{ 0x0077, "CueTrack" }, //sd
{ 0x007a, "ReferencePriority" }, //sd
{ 0x007b, "ReferenceBlock" }, //sd
{ 0x007d, "ReferenceVirtual" }, //sd
{ 0x0254, "Xmp.video.ContentCompressAlgo" }, //u
{ 0x0255, "ContentCompressionSettings" }, //sd
{ 0x0282, "Xmp.video.DocType" }, //u
{ 0x0285, "Xmp.video.DocTypeReadVersion" }, //u
{ 0x0286, "Xmp.video.EBMLVersion" }, //u
{ 0x0287, "Xmp.video.DocTypeVersion" }, //u
{ 0x02f2, "EBMLMaxIDLength" }, //sd
{ 0x02f3, "EBMLMaxSizeLength" }, //sd
{ 0x02f7, "Xmp.video.EBMLReadVersion" }, //u
{ 0x037c, "ChapterLanguage" }, //sd
{ 0x037e, "ChapterCountry" }, //sd
{ 0x0444, "SegmentFamily" }, //sd
{ 0x0461, "Xmp.video.DateUTC" }, //Date Time Original - measured in seconds relatively to Jan 01, 2001, 0:00:00 GMT+0h
{ 0x047a, "Xmp.video.TagLanguage" }, //u
{ 0x0484, "Xmp.video.TagDefault" }, //u
{ 0x0485, "TagBinary" }, //sd
{ 0x0487, "Xmp.video.TagString" }, //u
{ 0x0489, "Xmp.video.Duration" }, //u
{ 0x050d, "ChapterProcessPrivate" }, //sd
{ 0x0598, "ChapterFlagEnabled" }, //sd
{ 0x05a3, "Xmp.video.TagName" }, //u
{ 0x05b9, "EditionEntry" }, //s
{ 0x05bc, "EditionUID" }, //sd
{ 0x05bd, "EditionFlagHidden" }, //sd
{ 0x05db, "EditionFlagDefault" }, //sd
{ 0x05dd, "EditionFlagOrdered" }, //sd
{ 0x065c, "Xmp.video.AttachFileData" }, //u
{ 0x0660, "Xmp.video.AttachFileMIME" }, //u
{ 0x066e, "Xmp.video.AttachFileName" }, //u
{ 0x0675, "AttachedFileReferral" }, //sd
{ 0x067e, "Xmp.video.AttachFileDesc" }, //u
{ 0x06ae, "Xmp.video.AttachFileUID" }, //u
{ 0x07e1, "Xmp.video.ContentEncryptAlgo" }, //u
{ 0x07e2, "ContentEncryptionKeyID" }, //sd
{ 0x07e3, "ContentSignature" }, //sd
{ 0x07e4, "ContentSignatureKeyID" }, //sd
{ 0x07e5, "Xmp.video.ContentSignAlgo" }, //u
{ 0x07e6, "Xmp.video.ContentSignHashAlgo" }, //u
{ 0x0d80, "Xmp.video.MuxingApp" }, //u
{ 0x0dbb, "Seek" }, //s
{ 0x1031, "ContentEncodingOrder" }, //sd
{ 0x1032, "ContentEncodingScope" }, //sd
{ 0x1033, "Xmp.video.ContentEncodingType" }, //u
{ 0x1034, "ContentCompression" }, //s
{ 0x1035, "ContentEncryption" }, //s
{ 0x135f, "CueRefNumber" }, //sd
{ 0x136e, "Xmp.video.TrackName" }, //u
{ 0x1378, "CueBlockNumber" }, //sd
{ 0x137f, "TrackOffset" }, //sd
{ 0x13ab, "SeekID" }, //sd
{ 0x13ac, "SeekPosition" }, //sd
{ 0x13b8, "Stereo3DMode" }, //sd
{ 0x14aa, "Xmp.video.CropBottom" }, //ui
{ 0x14b0, "Xmp.video.Width" }, //u
{ 0x14b2, "Xmp.video.DisplayUnit" }, //u
{ 0x14b3, "Xmp.video.AspectRatioType" }, //u
{ 0x14ba, "Xmp.video.Height" }, //u
{ 0x14bb, "Xmp.video.CropTop" }, //ui
{ 0x14cc, "Xmp.video.CropLeft" }, //ui
{ 0x14dd, "Xmp.video.CropRight" }, //ui
{ 0x15aa, "TrackForced" }, //ui
{ 0x15ee, "MaxBlockAdditionID" }, //sd
{ 0x1741, "Xmp.video.WritingApp" }, //u
{ 0x1854, "SilentTracks" }, //s
{ 0x18d7, "SilentTrackNumber" }, //sd
{ 0x21a7, "AttachedFile" }, //s
{ 0x2240, "ContentEncoding" }, //s
{ 0x2264, "Xmp.audio.BitsPerSample" }, //u
{ 0x23a2, "CodecPrivate" }, //sd
{ 0x23c0, "Targets" }, //s
{ 0x23c3, "Xmp.video.PhysicalEquivalent" }, //u
{ 0x23c4, "TagChapterUID" }, //sd
{ 0x23c5, "TagTrackUID" }, //sd
{ 0x23c6, "TagAttachmentUID" }, //sd
{ 0x23c9, "TagEditionUID" }, //sd
{ 0x23ca, "Xmp.video.TargetType" }, //u
{ 0x2532, "SignedElement" }, //sd
{ 0x2624, "TrackTranslate" }, //s
{ 0x26a5, "TrackTranslateTrackID" }, //sd
{ 0x26bf, "TrackTranslateCodec" }, //sd
{ 0x26fc, "TrackTranslateEditionUID" }, //sd
{ 0x27c8, "SimpleTag" }, //s
{ 0x28ca, "TargetTypeValue" }, //sd
{ 0x2911, "ChapterProcessCommand" }, //s
{ 0x2922, "ChapterProcessTime" }, //sd
{ 0x2924, "ChapterTranslate" }, //s
{ 0x2933, "ChapterProcessData" }, //sd
{ 0x2944, "ChapterProcess" }, //s
{ 0x2955, "ChapterProcessCodecID" }, //sd
{ 0x29a5, "ChapterTranslateID" }, //sd
{ 0x29bf, "Xmp.video.TranslateCodec" }, //u
{ 0x29fc, "ChapterTranslateEditionUID" }, //sd
{ 0x2d80, "ContentEncodings" }, //s
{ 0x2de7, "MinCache" }, //sd
{ 0x2df8, "MaxCache" }, //sd
{ 0x2e67, "ChapterSegmentUID" }, //sd
{ 0x2ebc, "ChapterSegmentEditionUID" }, //sd
{ 0x2fab, "TrackOverlay" }, //sd
{ 0x3373, "Tag" }, //s
{ 0x3384, "SegmentFileName" }, //sd
{ 0x33a4, "SegmentUID" }, //sd
{ 0x33c4, "ChapterUID" }, //sd
{ 0x33c5, "TrackUID" }, //sd
{ 0x3446, "TrackAttachmentUID" }, //sd
{ 0x35a1, "BlockAdditions" }, //s
{ 0x38b5, "Xmp.audio.OutputSampleRate" }, //u
{ 0x3ba9, "Xmp.video.Title" }, //u
{ 0x3d7b, "ChannelPositions" }, //sd
{ 0x3e5b, "SignatureElements" }, //s
{ 0x3e7b, "SignatureElementList" }, //s
{ 0x3e8a, "Xmp.video.ContentSignAlgo" }, //u
{ 0x3e9a, "Xmp.video.ContentSignHashAlgo" }, //u
{ 0x3ea5, "SignaturePublicKey" }, //sd
{ 0x3eb5, "Signature" }, //sd
{ 0x2b59c, "TrackLanguage" }, //ui
{ 0x3314f, "TrackTimecodeScale" }, //sd
{ 0x383e3, "Xmp.video.FrameRate" }, //u
{ 0x3e383, "VideoFrameRate/DefaultDuration" }, //ui
{ 0x58688, "VideoCodecName/AudioCodecName/CodecName" }, //ui
{ 0x6b240, "CodecDownloadURL" }, //ui
{ 0xad7b1, "TimecodeScale" }, //ui
{ 0xeb524, "ColorSpace" }, //sd
{ 0xfb523, "Xmp.video.OpColor" }, //u
{ 0x1a9697, "CodecSettings" }, //ui
{ 0x1b4040, "CodecInfoURL" }, //ui
{ 0x1c83ab, "PrevFileName" }, //sd
{ 0x1cb923, "PrevUID" }, //sd
{ 0x1e83bb, "NextFileName" }, //sd
{ 0x1eb923, "NextUID" }, //sd
{ 0x43a770, "Chapters" }, //sd
{ 0x14d9b74, "SeekHead" }, //s
{ 0x254c367, "Tags" }, //s
{ 0x549a966, "Info" }, //s
{ 0x654ae6b, "Tracks" }, //s
{ 0x8538067, "SegmentHeader" }, //s
{ 0x941a469, "Attachments" }, //s
{ 0xa45dfa3, "EBMLHeader" }, //s
{ 0xb538667, "SignatureSlot" }, //s
{ 0xc53bb6b, "Cues" }, //s
{ 0xf43b675, "Cluster" }, //s
};
//! Multimedia type.
extern const MatroskaTags matroskaTrackType[] ={
{ 0x1, "Video" },
{ 0x2, "Audio" },
{ 0x3, "Complex" },
{ 0x10, "Logo" },
{ 0x11, "Subtitle" },
{ 0x12, "Buttons" },
{ 0x20, "Control" }
};
//! Algorith information.
extern const MatroskaTags compressionAlgorithm[] ={
{ 0, "zlib " },
{ 1, "bzlib" },
{ 2, "lzo1x" },
{ 3, "Header Stripping" }
};
//! Channel Type Information.
extern const MatroskaTags audioChannels[] ={
{ 1, "Mono" },
{ 2, "Stereo" },
{ 5, "5.1 Surround Sound" },
{ 7, "7.1 Surround Sound" }
};
//! Display Information.
extern const MatroskaTags displayUnit[] ={
{ 0x0, "Pixels" },
{ 0x1, "cm" },
{ 0x2, "inches" }
};
//! Encryption Algorithm.
extern const MatroskaTags encryptionAlgorithm[] ={
{ 0, "Not Encrypted" },
{ 1, "DES" },
{ 2, "3DES" },
{ 3, "Twofish" },
{ 4, "Blowfish" },
{ 5, "AES" }
};
//! Chapter Information.
extern const MatroskaTags chapterPhysicalEquivalent[] ={
{ 10, "Index" },
{ 20, "Track" },
{ 30, "Session" },
{ 40, "Layer" },
{ 50, "Side" },
{ 60, "CD / DVD" },
{ 70, "Set / Package" },
};
//! Encoding Information.
extern const MatroskaTags encodingType[] ={
{ 0, "Compression" },
{ 1, "Encryption" }
};
//! VideoScan Information.
extern const MatroskaTags videoScanType[] =
{
{ 0, "Progressive" },
{ 1, "Interlaced" }
};
//! Chapter related Information.
extern const MatroskaTags chapterTranslateCodec[] ={
{ 0, "Matroska Script" },
{ 1, "DVD Menu" }
};
//! Aspect Ratio Information.
extern const MatroskaTags aspectRatioType[] ={
{ 0, "Free Resizing" },
{ 1, "Keep Aspect Ratio" },
{ 2, "Fixed" }
};
//! Content Signature Information.
extern const MatroskaTags contentSignatureAlgorithm[] ={
{ 0, "Not Signed" },
{ 1, "RSA" }
};
//! Content SIgnature Hash Information.
extern const MatroskaTags contentSignatureHashAlgorithm[] ={
{ 0, "Not Signed" },
{ 1, "SHA1-160" },
{ 2, "MD5" }
};
//! Track Information.
extern const MatroskaTags trackEnable[] ={
{ 0x1, "Xmp.video.Enabled" },
{ 0x2, "Xmp.audio.Enabled" },
{ 0x11, "Xmp.video.SubTEnabled" }
};
//! Default Action Information.
extern const MatroskaTags defaultOn[] ={
{ 0x1, "Xmp.video.DefaultOn" },
{ 0x2, "Xmp.audio.DefaultOn" },
{ 0x11, "Xmp.video.SubTDefaultOn" }
};
//! Track Information.
extern const MatroskaTags trackForced[] ={
{ 0x1, "Xmp.video.TrackForced" },
{ 0x2, "Xmp.audio.TrackForced" },
{ 0x11, "Xmp.video.SubTTrackForced" }
};
//! Track Information.
extern const MatroskaTags trackLacing[] ={
{ 0x1, "Xmp.video.TrackLacing" },
{ 0x2, "Xmp.audio.TrackLacing" },
{ 0x11, "Xmp.video.SubTTrackLacing" }
};
//! Codec Information.
extern const MatroskaTags codecDecodeAll[] ={
{ 0x1, "Xmp.video.CodecDecodeAll" },
{ 0x2, "Xmp.audio.CodecDecodeAll" },
{ 0x11, "Xmp.video.SubTCodecDecodeAll" }
};
//! Codec Information.
extern const MatroskaTags codecDownloadUrl[] ={
{ 0x1, "Xmp.video.CodecDownloadUrl" },
{ 0x2, "Xmp.audio.CodecDownloadUrl" },
{ 0x11, "Xmp.video.SubTCodecDownloadUrl" }
};
//! Codec Information.
extern const MatroskaTags codecSettings[] ={
{ 0x1, "Xmp.video.CodecSettings" },
{ 0x2, "Xmp.audio.CodecSettings" },
{ 0x11, "Xmp.video.SubTCodecSettings" }
};
//! Track Information.
extern const MatroskaTags trackCodec[] ={
{ 0x1, "Xmp.video.Codec" },
{ 0x2, "Xmp.audio.Compressor" },
{ 0x11, "Xmp.video.SubTCodec" }
};
//! Track Language Information.
extern const MatroskaTags trackLanguage[] ={
{ 0x1, "Xmp.video.TrackLang" },
{ 0x2, "Xmp.audio.TrackLang" },
{ 0x11, "Xmp.video.SubTLang" }
};
//! Codec Information.
extern const MatroskaTags codecInfo[] ={
{ 0x1, "Xmp.video.CodecInfo" },
{ 0x2, "Xmp.audio.CodecInfo" },
{ 0x11, "Xmp.video.SubTCodecInfo" }
};
//! Stream Rate Information.
extern const MatroskaTags streamRate[] ={
{ 0x1, "Xmp.video.FrameRate" },
{ 0x2, "Xmp.audio.DefaultDuration" }
};
/*!
* \brief reverseMatroskaTag reverse the members of a structure
* \param inputMatroskaTag
* \param outputMatroskaTag
* \param size
*/
void reverseMatroskaTag(const MatroskaTags inputMatroskaTag[],RevMatroskaTags outputMatroskaTag[] ,int size){
for (int64_t i=0; i<size ;i++){
outputMatroskaTag[i].label_ = inputMatroskaTag[i].label_;
outputMatroskaTag[i].val_ = inputMatroskaTag[i].val_;
}
}
/*!
@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.
*/
uint64_t returnTagValue(const Exiv2::byte* buf, int32_t size){
assert(size > 0 && size <= 8);
uint64_t b0 = buf[0] & (0xff >> size);
uint64_t tag = b0 << ((size - 1) * 8);
for (int32_t i = 1; i < size; ++i) tag |= static_cast<uint64_t>(buf[i]) << ((size - i - 1) * 8);
return tag;
}
/*!
@brief Function used to convert buffer data into numerical information,
information stored in BigEndian format
*/
int64_t returnValue(const Exiv2::byte* buf, int32_t size){
int64_t temp = 0;
for(int64_t i = size-1; i >= 0; i--) temp = temp + static_cast<int64_t>(buf[i]*(pow(256.0, (double)size-i-1)));
// Todo: remove debug output
// std::cerr << "size = " << size << ", val = " << temp << std::hex << " (0x" << temp << std::dec << ")";
uint64_t ret = 0;
for (int32_t i = 0; i < size; ++i) ret |= static_cast<uint64_t>(buf[i]) << ((size - i - 1) * 8);
// Todo: remove debug output
// std::cerr << ", ret = " << ret << std::hex << " (0x" << ret << std::dec << ")\n";
return ret;
}
/*!
* \brief returnBuf Return raw byte,ready to be written to the file,from value.
* \param numValue
* \param size size of rawData in number of bytes.
* \return
*/
Exiv2::byte* returnBuf(uint64_t numValue,int32_t size)
{
Exiv2::byte* retVal;
retVal = (Exiv2::byte* )malloc(size*sizeof(8));
for (int32_t i = 0; i < size; i++) {
retVal[size-i-1] = (Exiv2::byte)(((uint32_t)numValue)%(uint32_t)256);
numValue = numValue/256;
}
return retVal;
} // returnBuf
}
} // namespace Internal, Exiv2
namespace Exiv2
{
using namespace Exiv2::Internal;
class MatroskaVideo::Private
{
public:
Private()
{
continueTraversing_ = true;
height_ = 1;
width_ = 1;
m_modifyMetadata = false;
mtLocation = 1;
}
public:
//! Variable to check the end of metadata traversing.
bool continueTraversing_;
//! Variable to store height and width of a video frame.
uint64_t height_, width_;
//! Variable to decide whether to modify the metada
bool m_modifyMetadata;
int32_t mtLocation;
};
MatroskaVideo::MatroskaVideo(BasicIo::AutoPtr io)
: Image(ImageType::mkv, mdNone, io),d(new Private)
{
} // MatroskaVideo::MatroskaVideo
MatroskaVideo::~MatroskaVideo()
{
delete d;
}
std::string MatroskaVideo::mimeType() const
{
return "video/matroska";
}
void MatroskaVideo::writeMetadata()
{
if (io_->open() != 0) throw Error(9, io_->path(), strError());
IoCloser closer(*io_);
doWriteMetadata();
io_->close();
}
void MatroskaVideo::doWriteMetadata()
{
if (!io_->isopen()) throw Error(20);
if (!isMkvType(*io_, false)){
if (io_->error() || io_->eof()) throw Error(14);
throw Error(3, "Matroska");
}
d->m_modifyMetadata = true;
d->continueTraversing_ = true;
while (d->continueTraversing_) decodeBlock();
}
void MatroskaVideo::readMetadata()
{
if (io_->open() != 0) throw Error(9, io_->path(), strError());
// Ensure that this is the correct image type
if (!isMkvType(*io_, false)){
if (io_->error() || io_->eof()) throw Error(14);
throw Error(3, "Matroska");
}
IoCloser closer(*io_);
clearMetadata();
d->continueTraversing_ = true;
d->height_ = d->width_ = 1;
xmpData_["Xmp.video.FileName"] = io_->path();
xmpData_["Xmp.video.FileSize"] = (double)io_->size()/(double)1048576;
xmpData_["Xmp.video.MimeType"] = mimeType();
while (d->continueTraversing_) decodeBlock();
aspectRatio();
} // MatroskaVideo::readMetadata
void MatroskaVideo::decodeBlock()
{
Exiv2::byte buf[8];
d->mtLocation = io_->tell();
io_->read(buf, 1);
if(io_->eof()){
d->continueTraversing_ = false;
return;
}
uint32_t sz = findBlockSize(buf[0]); // 0-8
if (sz > 0) io_->read(buf + 1, sz - 1);
const MatroskaTags* mt = find(matroskaTags, returnTagValue(buf, sz));
if(mt->val_ == 0xc53bb6b || mt->val_ == 0xf43b675){
d->continueTraversing_ = false;
return;
}
bool skip = find(compositeTagsList, mt->val_) != 0;
bool ignore = find(ignoredTagsList, mt->val_) != 0;
io_->read(buf, 1);
sz = findBlockSize(buf[0]); // 0-8
if (sz > 0) io_->read(buf + 1, sz - 1);
uint64_t size = returnTagValue(buf, sz);
if (skip && !ignore) return;
const uint64_t bufMinSize = 200;
#ifndef SUPPRESS_WARNINGS
if (!ignore && size > bufMinSize){
EXV_WARNING << "Size " << size << " of Matroska tag 0x"
<< std::hex << mt->val_ << std::dec
<< " is greater than " << bufMinSize << ": ignoring it.\n";
}
#endif
if (ignore || size > bufMinSize){
io_->seek(size, BasicIo::cur);
return;
}
DataBuf buf2(bufMinSize+1);
int32_t s = static_cast<int32_t>(size) ;
io_->read(buf2.pData_,s);
contentManagement(mt, buf2.pData_,s);
} // MatroskaVideo::decodeBlock
void MatroskaVideo::contentManagement(const MatroskaTags* mt, const Exiv2::byte* buf, int32_t size)
{
int64_t duration_in_ms = 0;
static double time_code_scale = 1.0, temp = 0;
static int32_t stream = 0, track_count = 0;
char str[4] = "No";
const RevMatroskaTags* rtd = 0;
const MatroskaTags* internalMt = 0;
const RevMatroskaTags* revTagStructure = 0;
if(d->m_modifyMetadata){
}
switch (mt->val_) {
case 0x0282: case 0x0d80: case 0x1741: case 0x3ba9: case 0x066e: case 0x0660:
case 0x065c: case 0x067e: case 0x047a: case 0x0487: case 0x05a3: case 0x136e:
case 0x23ca: case 0xeb524:
if(!d->m_modifyMetadata) xmpData_[mt->label_] = buf;
else{
if(xmpData_[mt->label_].count() > 0){
Exiv2::byte* rawTagData = new byte[size];
std::string tagData = xmpData_[mt->label_].toString();
for(int32_t i=0; i<min(size,(int32_t) tagData.size()); i++) rawTagData[i] = (Exiv2::byte)tagData[i];
if(size > (int32_t) tagData.size())
for(int32_t i=(int32_t) tagData.size(); i<size; i++) rawTagData[i] = (Exiv2::byte)0;
io_->seek(-size,BasicIo::cur);
io_->write(rawTagData,size);
delete[] rawTagData;
}
}
break;
case 0x0030: case 0x003a: case 0x0287: case 0x14b0: case 0x14ba: case 0x285:
case 0x06ae: case 0x0286: case 0x02f7: case 0x2264: case 0x14aa: case 0x14bb:
case 0x14cc: case 0x14dd:
if(!d->m_modifyMetadata) xmpData_[mt->label_] = returnValue(buf, size);
else{
if(xmpData_[mt->label_].count() > 0){
Exiv2::byte* rawTagData = returnBuf((uint64_t)xmpData_[mt->label_].toFloat(),size);
io_->seek(-size,BasicIo::cur);
io_->write(rawTagData,size);
free(rawTagData);
}
}
if (mt->val_ == 0x0030 || mt->val_ == 0x14b0) d->width_ = returnValue(buf, size);
else if (mt->val_ == 0x003a || mt->val_ == 0x14ba) d->height_ = returnValue(buf, size);
break;
case 0x001a: case 0x001f: case 0x0254: case 0x07e1: case 0x07e5: case 0x07e6:
case 0x1033: case 0x14b2: case 0x14b3: case 0x23c3: case 0x29bf: case 0x3e8a:
case 0x3e9a:
switch (mt->val_){
case 0x001a: internalMt = find(videoScanType, returnValue(buf, size)); break;
case 0x001f: internalMt = find(audioChannels, returnValue(buf, size)); break;
case 0x0254: internalMt = find(compressionAlgorithm, returnValue(buf, size)); break;
case 0x07e1: internalMt = find(encryptionAlgorithm, returnValue(buf, size)); break;
case 0x1033: internalMt = find(encodingType, returnValue(buf, size)); break;
case 0x3e8a:
case 0x07e5: internalMt = find(contentSignatureAlgorithm, returnValue(buf, size)); break;
case 0x3e9a:
case 0x07e6: internalMt = find(contentSignatureHashAlgorithm, returnValue(buf, size)); break;
case 0x14b2: internalMt = find(displayUnit, returnValue(buf, size)); break;
case 0x14b3: internalMt = find(aspectRatioType, returnValue(buf, size)); break;
case 0x23c3: internalMt = find(chapterPhysicalEquivalent, returnValue(buf, size)); break;
case 0x29bf: internalMt = find(chapterTranslateCodec, returnValue(buf, size)); break;
}
if(!d->m_modifyMetadata) if (internalMt) xmpData_[mt->label_] = internalMt->label_;
else if(internalMt){
if(xmpData_[mt->label_].count() >0){
const int32_t videoTagSize = lengthof(videoScanType);
const int32_t audioTagSize = lengthof(audioChannels);
const int32_t compressionTagSize = lengthof(compressionAlgorithm);
const int32_t encryptionTagSize = lengthof(encryptionAlgorithm);
const int32_t encodingTagSize = lengthof(encodingType);
const int32_t contentTagSize = lengthof(contentSignatureAlgorithm);
const int32_t contentHashTagSize = lengthof(contentSignatureHashAlgorithm);
const int32_t displayTagSize = lengthof(displayUnit);
const int32_t aspectTagSize = lengthof(aspectRatioType);
const int32_t physicalTagSize = lengthof(chapterPhysicalEquivalent);
const int32_t translateTagSize = lengthof(chapterTranslateCodec);
switch (mt->val_){
case 0x001a:
RevMatroskaTags revVidStructure[videoTagSize];
reverseMatroskaTag(videoScanType,revVidStructure,videoTagSize);
rtd = find(revVidStructure,xmpData_[mt->label_].toString());
writeMatroskaKey(rtd,size);break;
case 0x001f:
RevMatroskaTags revAudStructure[audioTagSize];
reverseMatroskaTag(audioChannels,revAudStructure,audioTagSize);
rtd = find(revAudStructure,xmpData_[mt->label_].toString());
writeMatroskaKey(rtd,size);break;
case 0x0254:
RevMatroskaTags revComStructure[compressionTagSize];
reverseMatroskaTag(compressionAlgorithm,revComStructure,compressionTagSize);
rtd = find(revComStructure,xmpData_[mt->label_].toString());
writeMatroskaKey(rtd,size);break;
case 0x07e1:
RevMatroskaTags revEnrStructure[encryptionTagSize];
reverseMatroskaTag(encryptionAlgorithm,revEnrStructure,encryptionTagSize);
rtd = find(revEnrStructure,xmpData_[mt->label_].toString());
writeMatroskaKey(rtd,size);break;
case 0x1033:
RevMatroskaTags revEncStructure[encodingTagSize];
reverseMatroskaTag(encodingType,revEncStructure,encodingTagSize);
rtd = find(revEncStructure,xmpData_[mt->label_].toString());
writeMatroskaKey(rtd,size);break;
case 0x07e5:
RevMatroskaTags revConStructure[contentTagSize];
reverseMatroskaTag(contentSignatureAlgorithm,revConStructure,contentTagSize);
rtd = find(revConStructure,xmpData_[mt->label_].toString());
writeMatroskaKey(rtd,size);break;
case 0x07e6:
RevMatroskaTags revHasStructure[contentHashTagSize];
reverseMatroskaTag(contentSignatureHashAlgorithm,revHasStructure,contentHashTagSize);
rtd = find(revHasStructure,xmpData_[mt->label_].toString());
writeMatroskaKey(rtd,size);break;
case 0x14b2:
RevMatroskaTags revDisStructure[displayTagSize];
reverseMatroskaTag(displayUnit,revDisStructure,displayTagSize);
rtd = find(revDisStructure,xmpData_[mt->label_].toString());
writeMatroskaKey(rtd,size);break;
case 0x14b3:
RevMatroskaTags revAspStructure[aspectTagSize];
reverseMatroskaTag(aspectRatioType,revAspStructure,aspectTagSize);
rtd = find(revAspStructure,xmpData_[mt->label_].toString());
writeMatroskaKey(rtd,size);break;
case 0x23c3:
RevMatroskaTags revPhyStructure[physicalTagSize];
reverseMatroskaTag(chapterPhysicalEquivalent,revPhyStructure,physicalTagSize);
rtd = find(revPhyStructure,xmpData_[mt->label_].toString());
writeMatroskaKey(rtd,size);break;
case 0x29bf:
RevMatroskaTags revTraStructure[translateTagSize];
reverseMatroskaTag(chapterTranslateCodec,revTraStructure,translateTagSize);
rtd = find(revTraStructure,xmpData_[mt->label_].toString());
writeMatroskaKey(rtd,size);break;
}
}
}
break;
case 0x0035: case 0x38b5:
if(!d->m_modifyMetadata) xmpData_[mt->label_] = getFloat(buf, bigEndian);
else if(xmpData_[mt->label_].count() > 0){
Exiv2::byte *rawFloatValue = new byte[size];
float floatValue = xmpData_[mt->label_].toFloat();
memcpy(rawFloatValue,&floatValue,sizeof(float));
// io_->seek(-size,BasicIo::cur);//Tobe tested
// io_->write(rawFloatValue,size);
}
break;
case 0x0039: case 0x0008: case 0x15aa: case 0x001c: case 0x002a: case 0x1a9697:
case 0x0484:
if (returnValue(buf, size)) strcpy(str, "Yes");
switch (mt->val_){
case 0x0039: internalMt = find(trackEnable, stream); break;
case 0x0008: internalMt = find(defaultOn, stream); break;
case 0x15aa: internalMt = find(trackForced, stream); break;
case 0x001c: internalMt = find(trackLacing, stream); break;
case 0x002a: internalMt = find(codecDecodeAll, stream); break;
case 0x1a9697: internalMt = find(codecSettings, stream); break;
case 0x0484: internalMt = mt; break;
}
if (internalMt) xmpData_[internalMt->label_] = str;
break;
case 0x0006: case 0x2b59c: case 0x58688: case 0x6b240: case 0x1b4040:
switch (mt->val_){
case 0x0006: internalMt = find(trackCodec, stream); break;
case 0x2b59c: internalMt = find(trackLanguage, stream); break;
case 0x58688: internalMt = find(codecInfo, stream); break;
case 0x6b240:
case 0x1b4040: internalMt = find(codecDownloadUrl, stream); break;
}
if (internalMt) xmpData_[internalMt->label_] = buf;
break;
case 0x0489: case 0x0461:
if(!d->m_modifyMetadata){
switch (mt->val_){
case 0x0489:
if(size <= 4) duration_in_ms = static_cast<int64_t>(getFloat(buf, bigEndian) * time_code_scale * 1000);
else duration_in_ms = static_cast<int64_t>(getDouble(buf, bigEndian) * time_code_scale * 1000);
break;
case 0x0461: duration_in_ms = returnValue(buf, size)/1000000000; break;
}
xmpData_[mt->label_] = duration_in_ms;
}
else{
Exiv2::byte *rawDuration;
switch (mt->val_){
case 0x0489:
rawDuration = returnBuf( (uint64_t) (xmpData_[mt->label_].toFloat() /(time_code_scale*1000.0)),size);
io_->seek(-size,BasicIo::cur);
io_->write(rawDuration,size);
break;
case 0x0461:
rawDuration = returnBuf( (uint64_t) (xmpData_[mt->label_].toFloat() / 1000000000.0),size);
io_->seek(-size,BasicIo::cur);
io_->write(rawDuration,size);
break;
}
free(rawDuration);
}
break;
case 0x0057:
track_count++;
xmpData_["Xmp.video.TotalStream"] = track_count;
break;
case 0xad7b1:
if(!d->m_modifyMetadata){
time_code_scale = (double)returnValue(buf, size)/(double)1000000000;
xmpData_["Xmp.video.TimecodeScale"] = time_code_scale;
}
else{
Exiv2::byte *rawTimeCodeScale = returnBuf((uint64_t)(xmpData_["Xmp.video.TimecodeScale"]
.toFloat()*1000000000),size);
io_->seek(size,BasicIo::cur);
io_->write(rawTimeCodeScale,size);
free(rawTimeCodeScale);
}
break;
case 0x0003:
internalMt = find(matroskaTrackType, returnValue(buf, size));
stream = static_cast<int32_t>(internalMt->val_);
break;
case 0x3e383: case 0x383e3:
internalMt = find(streamRate, stream);
if (returnValue(buf, size)){
switch (stream)
{
case 1: temp = (double)1000000000/(double)returnValue(buf, size); break;
case 2: temp = static_cast<double>(returnValue(buf, size) / 1000); break;
}
if (internalMt) xmpData_[internalMt->label_] = temp;
}
else if (internalMt) xmpData_[internalMt->label_] = "Variable Bit Rate"; break;
default: break;
}
} // MatroskaVideo::contentManagement
void MatroskaVideo::aspectRatio()
{
//TODO - Make a better unified method to handle all cases of Aspect Ratio
double aspectRatio = (double)d->width_ / (double)d->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;
}
} // MatroskaVideo::aspectRatio
uint32_t MatroskaVideo::findBlockSize(Exiv2::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;
} // MatroskaVideo::findBlockSize
Image::AutoPtr newMkvInstance(BasicIo::AutoPtr io, bool /*create*/)
{
Image::AutoPtr image(new MatroskaVideo(io));
if (!image->good()) image.reset();
return image;
}
void MatroskaVideo::writeStringData(Exiv2::Xmpdatum xmpStringData, int32_t size, int32_t skipOffset)
{
if(xmpStringData.count() > 0){
std::string dataString = xmpStringData.toString();
Exiv2::byte* rawData = new byte[(uint8_t)size];
int32_t newSize = (int32_t)dataString.size();
for(int32_t i=0; i<min(newSize,size); i++) rawData[i] = (Exiv2::byte)dataString[i];
if(size > newSize)
for(int32_t i=newSize; i<size; i++) rawData[i] = (Exiv2::byte)0;
io_->write(rawData,size);
io_->seek((int)skipOffset,BasicIo::cur);
delete[] rawData;
}
else io_->seek((size+skipOffset),BasicIo::cur);
}
void MatroskaVideo::writeMatroskaKey(const RevMatroskaTags* revInternalMt,int32_t size)
{
if(revInternalMt){
Exiv2::byte* rawMatroskaKey = returnBuf(revInternalMt->val_,size);
io_->seek(-size,BasicIo::cur);
io_->write(rawMatroskaKey,size);
free(rawMatroskaKey);
}
}
bool isMkvType(BasicIo& iIo, bool advance)
{
bool result = true;
Exiv2::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