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/quicktimevideo.cpp

1655 lines
66 KiB
C++

// ***************************************************************** -*- 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"
#include "error.hpp"
#include "tags.hpp"
#include "tags_int.hpp"
#include "quicktimevideo.hpp"
#include "futils.hpp"
#include "basicio.hpp"
// + standard includes
#include <cmath>
#include <iostream>
#include <array>
#include <string>
// *****************************************************************************
// class member definitions
namespace Exiv2 {
namespace Internal {
extern const TagVocabulary qTimeFileType[] = {
{ "3g2a", "3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-0 V1.0" },
{ "3g2b", "3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-A V1.0.0" },
{ "3g2c", "3GPP2 Media (.3G2) compliant with 3GPP2 C.S0050-B v1.0" },
{ "3ge6", "3GPP (.3GP) Release 6 MBMS Extended Presentations" },
{ "3ge7", "3GPP (.3GP) Release 7 MBMS Extended Presentations" },
{ "3gg6", "3GPP Release 6 General Profile" },
{ "3gp1", "3GPP Media (.3GP) Release 1 (probably non-existent)" },
{ "3gp2", "3GPP Media (.3GP) Release 2 (probably non-existent)" },
{ "3gp3", "3GPP Media (.3GP) Release 3 (probably non-existent)" },
{ "3gp4", "3GPP Media (.3GP) Release 4" },
{ "3gp5", "3GPP Media (.3GP) Release 5" },
{ "3gp6", "3GPP Media (.3GP) Release 6 Streaming Servers" },
{ "3gs7", "3GPP Media (.3GP) Release 7 Streaming Servers" },
{ "CAEP", "Canon Digital Camera" },
{ "CDes", "Convergent Design" },
{ "F4A ", "Audio for Adobe Flash Player 9+ (.F4A)" },
{ "F4B ", "Audio Book for Adobe Flash Player 9+ (.F4B)" },
{ "F4P ", "Protected Video for Adobe Flash Player 9+ (.F4P)" },
{ "F4V ", "Video for Adobe Flash Player 9+ (.F4V)" },
{ "JP2 ", "JPEG 2000 Image (.JP2) [ISO 15444-1 ?]" },
{ "JP20", "Unknown, from GPAC samples (prob non-existent)" },
{ "KDDI", "3GPP2 EZmovie for KDDI 3G cellphones" },
{ "M4A ", "Apple iTunes AAC-LC (.M4A) Audio" },
{ "M4B ", "Apple iTunes AAC-LC (.M4B) Audio Book" },
{ "M4P ", "Apple iTunes AAC-LC (.M4P) AES Protected Audio" },
{ "M4V ", "Apple iTunes Video (.M4V) Video" },
{ "M4VH", "Apple TV (.M4V)" },
{ "M4VP", "Apple iPhone (.M4V)" },
{ "MPPI", "Photo Player, MAF [ISO/IEC 23000-3]" },
{ "MSNV", "MPEG-4 (.MP4) for SonyPSP" },
{ "NDAS", "MP4 v2 [ISO 14496-14] Nero Digital AAC Audio" },
{ "NDSC", "MPEG-4 (.MP4) Nero Cinema Profile" },
{ "NDSH", "MPEG-4 (.MP4) Nero HDTV Profile" },
{ "NDSM", "MPEG-4 (.MP4) Nero Mobile Profile" },
{ "NDSP", "MPEG-4 (.MP4) Nero Portable Profile" },
{ "NDSS", "MPEG-4 (.MP4) Nero Standard Profile" },
{ "NDXC", "H.264/MPEG-4 AVC (.MP4) Nero Cinema Profile" },
{ "NDXH", "H.264/MPEG-4 AVC (.MP4) Nero HDTV Profile" },
{ "NDXM", "H.264/MPEG-4 AVC (.MP4) Nero Mobile Profile" },
{ "NDXP", "H.264/MPEG-4 AVC (.MP4) Nero Portable Profile" },
{ "NDXS", "H.264/MPEG-4 AVC (.MP4) Nero Standard Profile" },
{ "NIKO", "Nikon" },
{ "ROSS", "Ross Video" },
{ "avc1", "MP4 Base w/ AVC ext [ISO 14496-12:2005]" },
{ "caqv", "Casio Digital Camera" },
{ "da0a", "DMB MAF w/ MPEG Layer II aud, MOT slides, DLS, JPG/PNG/MNG images" },
{ "da0b", "DMB MAF, extending DA0A, with 3GPP timed text, DID, TVA, REL, IPMP" },
{ "da1a", "DMB MAF audio with ER-BSAC audio, JPG/PNG/MNG images" },
{ "da1b", "DMB MAF, extending da1a, with 3GPP timed text, DID, TVA, REL, IPMP" },
{ "da2a", "DMB MAF aud w/ HE-AAC v2 aud, MOT slides, DLS, JPG/PNG/MNG images" },
{ "da2b", "DMB MAF, extending da2a, with 3GPP timed text, DID, TVA, REL, IPMP" },
{ "da3a", "DMB MAF aud with HE-AAC aud, JPG/PNG/MNG images" },
{ "da3b", "DMB MAF, extending da3a w/ BIFS, 3GPP timed text, DID, TVA, REL, IPMP" },
{ "dmb1", "DMB MAF supporting all the components defined in the specification" },
{ "dmpf", "Digital Media Project" },
{ "drc1", "Dirac (wavelet compression), encapsulated in ISO base media (MP4)" },
{ "dv1a", "DMB MAF vid w/ AVC vid, ER-BSAC aud, BIFS, JPG/PNG/MNG images, TS" },
{ "dv1b", "DMB MAF, extending dv1a, with 3GPP timed text, DID, TVA, REL, IPMP" },
{ "dv2a", "DMB MAF vid w/ AVC vid, HE-AAC v2 aud, BIFS, JPG/PNG/MNG images, TS" },
{ "dv2b", "DMB MAF, extending dv2a, with 3GPP timed text, DID, TVA, REL, IPMP" },
{ "dv3a", "DMB MAF vid w/ AVC vid, HE-AAC aud, BIFS, JPG/PNG/MNG images, TS" },
{ "dv3b", "DMB MAF, extending dv3a, with 3GPP timed text, DID, TVA, REL, IPMP" },
{ "dvr1", "DVB (.DVB) over RTP" },
{ "dvt1", "DVB (.DVB) over MPEG-2 Transport Stream" },
{ "isc2", "ISMACryp 2.0 Encrypted File" },
{ "iso2", "MP4 Base Media v2 [ISO 14496-12:2005]" },
{ "isom", "MP4 Base Media v1 [IS0 14496-12:2003]" },
{ "jpm ", "JPEG 2000 Compound Image (.JPM) [ISO 15444-6]" },
{ "jpx ", "JPEG 2000 with extensions (.JPX) [ISO 15444-2]" },
{ "mj2s", "Motion JPEG 2000 [ISO 15444-3] Simple Profile" },
{ "mjp2", "Motion JPEG 2000 [ISO 15444-3] General Profile" },
{ "mmp4", "MPEG-4/3GPP Mobile Profile (.MP4/3GP) (for NTT)" },
{ "mp21", "MPEG-21 [ISO/IEC 21000-9]" },
{ "mp41", "MP4 v1 [ISO 14496-1:ch13]" },
{ "mp42", "MP4 v2 [ISO 14496-14]" },
{ "mp71", "MP4 w/ MPEG-7 Metadata [per ISO 14496-12]" },
{ "mqt ", "Sony / Mobile QuickTime (.MQV) US Patent 7,477,830 (Sony Corp)" },
{ "niko", "Nikon" },
{ "odcf", "OMA DCF DRM Format 2.0 (OMA-TS-DRM-DCF-V2_0-20060303-A)" },
{ "opf2", "OMA PDCF DRM Format 2.1 (OMA-TS-DRM-DCF-V2_1-20070724-C)" },
{ "opx2", "OMA PDCF DRM + XBS extensions (OMA-TS-DRM_XBS-V1_0-20070529-C)" },
{ "pana", "Panasonic Digital Camera" },
{ "qt ", "Apple QuickTime (.MOV/QT)" },
{ "sdv ", "SD Memory Card Video" },
{ "ssc1", "Samsung stereoscopic, single stream" },
{ "ssc2", "Samsung stereoscopic, dual stream" }
};
extern const TagVocabulary handlerClassTags[] = {
{ "dhlr", "Data Handler" },
{ "mhlr", "Media Handler" }
};
extern const TagVocabulary handlerTypeTags[] = {
{ "alis", "Alias Data" },
{ "crsm", "Clock Reference" },
{ "hint", "Hint Track" },
{ "ipsm", "IPMP" },
{ "m7sm", "MPEG-7 Stream" },
{ "mdir", "Metadata" },
{ "mdta", "Metadata Tags" },
{ "mjsm", "MPEG-J" },
{ "ocsm", "Object Content" },
{ "odsm", "Object Descriptor" },
{ "sdsm", "Scene Description" },
{ "soun", "Audio Track" },
{ "text", "Text" },
{ "tmcd", "Time Code" },
{ "url ", "URL" },
{ "vide", "Video Track" }
};
extern const TagVocabulary vendorIDTags[] = {
{ "FFMP", "FFmpeg" },
{ "appl", "Apple" },
{ "olym", "Olympus" },
{ "GIC ", "General Imaging Co." },
{ "fe20", "Olympus (fe20)" },
{ "pana", "Panasonic" },
{ "KMPI", "Konica-Minolta" },
{ "kdak", "Kodak" },
{ "pent", "Pentax" },
{ "NIKO", "Nikon" },
{ "leic", "Leica" },
{ "pr01", "Olympus (pr01)" },
{ "SMI ", "Sorenson Media Inc." },
{ "mino", "Minolta" },
{ "sany", "Sanyo" },
{ "ZORA", "Zoran Corporation" },
{ "niko", "Nikon" }
};
extern const TagVocabulary cameraByteOrderTags[] = {
{ "II", "Little-endian (Intel, II)" },
{ "MM", "Big-endian (Motorola, MM)" }
};
extern const TagDetails graphicsModetags[] = {
{ 0x0, "srcCopy" },
{ 0x1, "srcOr" },
{ 0x2, "srcXor" },
{ 0x3, "srcBic" },
{ 0x4, "notSrcCopy" },
{ 0x5, "notSrcOr" },
{ 0x6, "notSrcXor" },
{ 0x7, "notSrcBic" },
{ 0x8, "patCopy" },
{ 0x9, "patOr" },
{ 0xa, "patXor" },
{ 0xb, "patBic" },
{ 0xc, "notPatCopy" },
{ 0xd, "notPatOr" },
{ 0xe, "notPatXor" },
{ 0xf, "notPatBic" },
{ 0x20, "blend" },
{ 0x21, "addPin" },
{ 0x22, "addOver" },
{ 0x23, "subPin" },
{ 0x24, "transparent" },
{ 0x25, "addMax" },
{ 0x26, "subOver" },
{ 0x27, "addMin" },
{ 0x31, "grayishTextOr" },
{ 0x32, "hilite" },
{ 0x40, "ditherCopy" },
{ 0x100, "Alpha" },
{ 0x101, "White Alpha" },
{ 0x102, "Pre-multiplied Black Alpha" },
{ 0x110, "Component Alpha" }
};
extern const TagVocabulary userDatatags[] = {
{ "AllF", "PlayAllFrames" },
{ "CNCV", "CompressorVersion" },
{ "CNFV", "FirmwareVersion" },
{ "CNMN", "Model" },
{ "CNTH", "CanonCNTH" },
{ "DcMD", "DcMD" },
{ "FFMV", "FujiFilmFFMV" },
{ "INFO", "SamsungINFO" },
{ "LOOP", "LoopStyle" },
{ "MMA0", "MinoltaMMA0" },
{ "MMA1", "MinoltaMMA1" },
{ "MVTG", "FujiFilmMVTG" },
{ "NCDT", "NikonNCDT" },
{ "PANA", "PanasonicPANA" },
{ "PENT", "PentaxPENT" },
{ "PXMN", "MakerNotePentax5b" },
{ "PXTH", "PentaxPreview" },
{ "QVMI", "CasioQVMI" },
{ "SDLN", "PlayMode" },
{ "SelO", "PlaySelection" },
{ "TAGS", "KodakTags/KonicaMinoltaTags/MinoltaTags/NikonTags/OlympusTags/PentaxTags/SamsungTags/SanyoMOV/SanyoMP4" },
{ "WLOC", "WindowLocation" },
{ "XMP_", "XMP" },
{ "Xtra", "Xtra" },
{ "hinf", "HintTrackInfo" },
{ "hinv", "HintVersion" },
{ "hnti", "Hint" },
{ "meta", "Meta" },
{ "name", "Name" },
{ "ptv ", "PrintToVideo" },
{ "scrn", "OlympusPreview" },
{ "thmb", "MakerNotePentax5a/OlympusThumbnail" },
};
extern const TagVocabulary userDataReferencetags[] = {
{ "CNCV", "Xmp.video.CompressorVersion" },
{ "CNFV", "Xmp.video.FirmwareVersion" },
{ "CNMN", "Xmp.video.Model" },
{ "NCHD", "Xmp.video.MakerNoteType" },
{ "WLOC", "Xmp.video.WindowLocation" },
{ "SDLN", "Xmp.video.PlayMode" },
{ "FFMV", "Xmp.video.StreamName" },
{ "SelO", "Xmp.video.PlaySelection" },
{ "name", "Xmp.video.Name" },
{ "vndr", "Xmp.video.Vendor" },
{ " ART", "Xmp.video.Artist" },
{ " alb", "Xmp.video.Album" },
{ " arg", "Xmp.video.Arranger" },
{ " ark", "Xmp.video.ArrangerKeywords" },
{ " cmt", "Xmp.video.Comment" },
{ " cok", "Xmp.video.ComposerKeywords" },
{ " com", "Xmp.video.Composer" },
{ " cpy", "Xmp.video.Copyright" },
{ " day", "Xmp.video.CreateDate" },
{ " dir", "Xmp.video.Director" },
{ " ed1", "Xmp.video.Edit1" },
{ " ed2", "Xmp.video.Edit2" },
{ " ed3", "Xmp.video.Edit3" },
{ " ed4", "Xmp.video.Edit4" },
{ " ed5", "Xmp.video.Edit5" },
{ " ed6", "Xmp.video.Edit6" },
{ " ed7", "Xmp.video.Edit7" },
{ " ed8", "Xmp.video.Edit8" },
{ " ed9", "Xmp.video.Edit9" },
{ " enc", "Xmp.video.Encoder" },
{ " fmt", "Xmp.video.Format" },
{ " gen", "Xmp.video.Genre" },
{ " grp", "Xmp.video.Grouping" },
{ " inf", "Xmp.video.Information" },
{ " isr", "Xmp.video.ISRCCode" },
{ " lab", "Xmp.video.RecordLabelName" },
{ " lal", "Xmp.video.RecordLabelURL" },
{ " lyr", "Xmp.video.Lyrics" },
{ " mak", "Xmp.video.Make" },
{ " mal", "Xmp.video.MakerURL" },
{ " mod", "Xmp.video.Model" },
{ " nam", "Xmp.video.Title" },
{ " pdk", "Xmp.video.ProducerKeywords" },
{ " phg", "Xmp.video.RecordingCopyright" },
{ " prd", "Xmp.video.Producer" },
{ " prf", "Xmp.video.Performers" },
{ " prk", "Xmp.video.PerformerKeywords" },
{ " prl", "Xmp.video.PerformerURL" },
{ " req", "Xmp.video.Requirements" },
{ " snk", "Xmp.video.SubtitleKeywords" },
{ " snm", "Xmp.video.Subtitle" },
{ " src", "Xmp.video.SourceCredits" },
{ " swf", "Xmp.video.SongWriter" },
{ " swk", "Xmp.video.SongWriterKeywords" },
{ " swr", "Xmp.video.SoftwareVersion" },
{ " too", "Xmp.video.Encoder" },
{ " trk", "Xmp.video.Track" },
{ " wrt", "Xmp.video.Composer" },
{ " xyz", "Xmp.video.GPSCoordinates" },
{ "CMbo", "Xmp.video.CameraByteOrder" },
{ "Cmbo", "Xmp.video.CameraByteOrder" },
};
extern const TagDetails NikonNCTGTags[] = {
{ 0x0001, "Xmp.video.Make" },
{ 0x0002, "Xmp.video.Model" },
{ 0x0003, "Xmp.video.Software" },
{ 0x0011, "Xmp.video.CreationDate" },
{ 0x0012, "Xmp.video.DateTimeOriginal" },
{ 0x0013, "Xmp.video.FrameCount" },
{ 0x0016, "Xmp.video.FrameRate" },
{ 0x0022, "Xmp.video.FrameWidth" },
{ 0x0023, "Xmp.video.FrameHeight" },
{ 0x0032, "Xmp.audio.channelType" },
{ 0x0033, "Xmp.audio.BitsPerSample" },
{ 0x0034, "Xmp.audio.sampleRate" },
{ 0x1108822, "Xmp.video.ExposureProgram" },
{ 0x1109204, "Xmp.video.ExposureCompensation" },
{ 0x1109207, "Xmp.video.MeteringMode" },
{ 0x110a434, "Xmp.video.LensModel" },
{ 0x1200000, "Xmp.video.GPSVersionID" },
{ 0x1200001, "Xmp.video.GPSLatitudeRef" },
{ 0x1200002, "Xmp.video.GPSLatitude" },
{ 0x1200003, "Xmp.video.GPSLongitudeRef" },
{ 0x1200004, "Xmp.video.GPSLongitude" },
{ 0x1200005, "Xmp.video.GPSAltitudeRef" },
{ 0x1200006, "Xmp.video.GPSAltitude" },
{ 0x1200007, "Xmp.video.GPSTimeStamp" },
{ 0x1200008, "Xmp.video.GPSSatellites" },
{ 0x1200010, "Xmp.video.GPSImgDirectionRef" },
{ 0x1200011, "Xmp.video.GPSImgDirection" },
{ 0x1200012, "Xmp.video.GPSMapDatum" },
{ 0x120001d, "Xmp.video.GPSDateStamp" },
{ 0x2000001, "Xmp.video.MakerNoteVersion" },
{ 0x2000005, "Xmp.video.WhiteBalance" },
{ 0x200000b, "Xmp.video.WhiteBalanceFineTune" },
{ 0x200001e, "Xmp.video.ColorSpace" },
{ 0x2000023, "Xmp.video.PictureControlData" },
{ 0x2000024, "Xmp.video.WorldTime" },
{ 0x200002c, "Xmp.video.UnknownInfo" },
{ 0x2000032, "Xmp.video.UnknownInfo2" },
{ 0x2000039, "Xmp.video.LocationInfo" },
{ 0x2000083, "Xmp.video.LensType" },
{ 0x2000084, "Xmp.video.LensModel" },
{ 0x20000ab, "Xmp.video.VariProgram" },
};
extern const TagDetails NikonColorSpace[] = {
{ 1, "sRGB" },
{ 2, "Adobe RGB" },
};
extern const TagVocabulary NikonGPS_Latitude_Longitude_ImgDirection_Reference[] = {
{ "N", "North" },
{ "S", "South" },
{ "E", "East" },
{ "W", "West" },
{ "M", "Magnetic North" },
{ "T", "True North" },
};
extern const TagDetails NikonGPSAltitudeRef[] = {
{ 0, "Above Sea Level" },
{ 1, "Below Sea Level" },
};
extern const TagDetails NikonExposureProgram[] = {
{ 0, "Not Defined" },
{ 1, "Manual" },
{ 2, "Program AE" },
{ 3, "Aperture-priority AE" },
{ 4, "Shutter speed priority AE" },
{ 5, "Creative (Slow speed)" },
{ 6, "Action (High speed)" },
{ 7, "Portrait" },
{ 8, "Landscape" },
};
extern const TagDetails NikonMeteringMode[] = {
{ 0, "Unknown" },
{ 1, "Average" },
{ 2, "Center-weighted average" },
{ 3, "Spot" },
{ 4, "Multi-spot" },
{ 5, "Multi-segment" },
{ 6, "Partial" },
{ 255, "Other" },
};
extern const TagDetails PictureControlAdjust[] = {
{ 0, "Default Settings" },
{ 1, "Quick Adjust" },
{ 2, "Full Control" },
};
//! Contrast and Sharpness
extern const TagDetails NormalSoftHard[] = {
{ 0, "Normal" },
{ 1, "Soft" },
{ 2, "Hard" }
};
//! Saturation
extern const TagDetails Saturation[] = {
{ 0, "Normal" },
{ 1, "Low" },
{ 2, "High" }
};
//! YesNo, used for DaylightSavings
extern const TagDetails YesNo[] = {
{ 0, "No" },
{ 1, "Yes" }
};
//! DateDisplayFormat
extern const TagDetails DateDisplayFormat[] = {
{ 0, "Y/M/D" },
{ 1, "M/D/Y" },
{ 2, "D/M/Y" }
};
extern const TagDetails FilterEffect[] = {
{ 0x80, "Off" },
{ 0x81, "Yellow" },
{ 0x82, "Orange" },
{ 0x83, "Red" },
{ 0x84, "Green" },
{ 0xff, "n/a" },
};
extern const TagDetails ToningEffect[] = {
{ 0x80, "B&W" },
{ 0x81, "Sepia" },
{ 0x82, "Cyanotype" },
{ 0x83, "Red" },
{ 0x84, "Yellow" },
{ 0x85, "Green" },
{ 0x86, "Blue-green" },
{ 0x87, "Blue" },
{ 0x88, "Purple-blue" },
{ 0x89, "Red-purple" },
{ 0xff, "n/a" },
};
extern const TagDetails whiteBalance[] = {
{ 0, "Auto" },
{ 1, "Daylight" },
{ 2, "Shade" },
{ 3, "Fluorescent" },
{ 4, "Tungsten" },
{ 5, "Manual" },
};
enum movieHeaderTags {
MovieHeaderVersion, CreateDate, ModifyDate, TimeScale, Duration, PreferredRate, PreferredVolume,
PreviewTime = 18, PreviewDuration,PosterTime, SelectionTime, SelectionDuration, CurrentTime, NextTrackID
};
enum trackHeaderTags {
TrackHeaderVersion, TrackCreateDate, TrackModifyDate, TrackID, TrackDuration = 5, TrackLayer = 8,
TrackVolume, ImageWidth = 19, ImageHeight
};
enum mediaHeaderTags {
MediaHeaderVersion, MediaCreateDate, MediaModifyDate, MediaTimeScale, MediaDuration, MediaLanguageCode
};
enum handlerTags {
HandlerClass = 1, HandlerType, HandlerVendorID
};
enum videoHeaderTags {
GraphicsMode = 2, OpColor
};
enum stream {
Video, Audio, Hint, Null, GenMediaHeader
};
enum imageDescTags {
codec, VendorID = 4, SourceImageWidth_Height = 7, XResolution,
YResolution, CompressorName = 10, BitDepth
};
enum audioDescTags {
AudioFormat, AudioVendorID = 4, AudioChannels, AudioSampleRate = 7, MOV_AudioFormat = 13
};
/*!
@brief Function used to check equality of a Tags with a
particular string (ignores case while comparing).
@param buf Data buffer that will contain Tag to compare
@param str char* Pointer to string
@return Returns true if the buffer value is equal to string.
*/
bool equalsQTimeTag(Exiv2::DataBuf& buf ,const char* str) {
for(int i = 0; i < 4; ++i)
if(tolower(buf.data()[i]) != tolower(str[i]))
return false;
return true;
}
/*!
@brief Function used to ignore Tags and values stored in them,
since they are not necessary as metadata information
@param buf Data buffer that will contain Tag to compare
@return Returns true, if Tag is found in the ignoreList[]
*/
bool ignoreList (Exiv2::DataBuf& buf) {
const char ignoreList[13][5] = {
"mdat", "edts", "junk", "iods", "alis", "stsc", "stsz", "stco", "ctts", "stss",
"skip", "wide", "cmvd",
};
for(int i = 0 ; i < 13 ; ++i)
if(equalsQTimeTag(buf, ignoreList[i]))
return true;
return false;
}
/*!
@brief Function used to ignore Tags, basically Tags which
contain other tags inside them, since they are not necessary
as metadata information
@param buf Data buffer that will contain Tag to compare
@return Returns true, if Tag is found in the ignoreList[]
*/
bool dataIgnoreList (Exiv2::DataBuf& buf) {
const char ignoreList[8][5] = {
"moov", "mdia", "minf", "dinf", "alis", "stbl", "cmov",
"meta",
};
for(int i = 0 ; i < 8 ; ++i)
if(equalsQTimeTag(buf, ignoreList[i]))
return true;
return false;
}
/*!
@brief Function used to convert buffer data into 64-bit
signed integer, information stored in Big Endian format
@param buf Data buffer that will contain data to be converted
@return Returns a signed 64-bit integer
*/
int64_t returnBufValue(Exiv2::DataBuf& buf, int n = 4) {
int64_t temp = 0;
for(int i = n - 1; i >= 0; i--)
#ifdef _MSC_VER
temp = temp + static_cast<int64_t>(buf.data()[i]*(pow(static_cast<float>(256), n-i-1)));
#else
temp = temp + buf.data()[i]*(pow((float)256,n-i-1));
#endif
return temp;
}
/*!
@brief Function used to convert buffer data into 64-bit
unsigned integer, information stored in Big Endian format
@param buf Data buffer that will contain data to be converted
@return Returns an unsigned 64-bit integer
*/
uint64_t returnUnsignedBufValue(Exiv2::DataBuf& buf, int n = 4) {
uint64_t temp = 0;
for(int i = n-1; i >= 0; i--)
#if _MSC_VER
temp = temp + static_cast<uint64_t>(buf.data()[i]*(pow(static_cast<float>(256), n-i-1)));
#else
temp = temp + buf.data()[i]*(pow((float)256,n-i-1));
#endif
return temp;
}
}} // namespace Internal, Exiv2
namespace Exiv2 {
using namespace Exiv2::Internal;
QuickTimeVideo::QuickTimeVideo(BasicIo::UniquePtr io)
: Image(ImageType::qtime, mdNone, std::move(io))
, timeScale_(1)
{
} // QuickTimeVideo::QuickTimeVideo
std::string QuickTimeVideo::mimeType() const
{
return "video/quicktime";
}
void QuickTimeVideo::writeMetadata()
{
}
void QuickTimeVideo::readMetadata()
{
if (io_->open() != 0) throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
// Ensure that this is the correct image type
if (!isQTimeType(*io_, false)) {
if (io_->error() || io_->eof()) throw Error(ErrorCode::kerFailedToReadImageData);
throw Error(ErrorCode::kerNotAnImage, "QuickTime");
}
IoCloser closer(*io_);
clearMetadata();
continueTraversing_ = true;
height_ = width_ = 1;
xmpData_["Xmp.video.FileSize"] = (double)io_->size()/(double)1048576;
xmpData_["Xmp.video.MimeType"] = mimeType();
while (continueTraversing_) decodeBlock();
aspectRatio();
} // QuickTimeVideo::readMetadata
void QuickTimeVideo::decodeBlock()
{
const long bufMinSize = 4;
DataBuf buf(bufMinSize+1);
uint64_t size = 0;
buf.data()[4] = '\0' ;
io_->read(buf.data(), 4);
if(io_->eof()) {
continueTraversing_ = false;
return;
}
size = Exiv2::getULong(buf.data(), bigEndian);
io_->read(buf.data(), 4);
if (size == 1) {
// The box size is encoded as a uint64_t, so we need to read another 8 bytes.
DataBuf data(8);
io_->read(data.data(), data.size());
size = data.read_uint64(0, bigEndian);
}
if(size < 8)
return;
// std::cerr<<"\nTag=>"<<buf.data()<<" size=>"<<size-8 << "";
tagDecoder(buf,size-8);
} // QuickTimeVideo::decodeBlock
void QuickTimeVideo::tagDecoder(Exiv2::DataBuf &buf, unsigned long size)
{
if (ignoreList(buf))
discard(size);
else if (dataIgnoreList(buf))
decodeBlock();
else if (equalsQTimeTag(buf, "ftyp"))
fileTypeDecoder(size);
else if (equalsQTimeTag(buf, "trak"))
setMediaStream();
else if (equalsQTimeTag(buf, "mvhd"))
movieHeaderDecoder(size);
else if (equalsQTimeTag(buf, "tkhd"))
trackHeaderDecoder(size);
else if (equalsQTimeTag(buf, "mdhd"))
mediaHeaderDecoder(size);
else if (equalsQTimeTag(buf, "hdlr"))
handlerDecoder(size);
else if (equalsQTimeTag(buf, "vmhd"))
videoHeaderDecoder(size);
else if (equalsQTimeTag(buf, "udta"))
userDataDecoder(size);
else if (equalsQTimeTag(buf, "dref"))
multipleEntriesDecoder();
else if (equalsQTimeTag(buf, "stsd"))
sampleDesc(size);
else if (equalsQTimeTag(buf, "stts"))
timeToSampleDecoder();
else if (equalsQTimeTag(buf, "pnot"))
previewTagDecoder(size);
else if (equalsQTimeTag(buf, "tapt"))
trackApertureTagDecoder(size);
else if (equalsQTimeTag(buf, "keys"))
keysTagDecoder(size);
else if (equalsQTimeTag(buf, "url ")) {
io_->read(buf.data(), size);
if (currentStream_ == Video)
xmpData_["Xmp.video.URL"] = Exiv2::toString(buf.data());
else if (currentStream_ == Audio)
xmpData_["Xmp.audio.URL"] = Exiv2::toString(buf.data());
}
else if (equalsQTimeTag(buf, "urn ")) {
io_->read(buf.data(), size);
if (currentStream_ == Video)
xmpData_["Xmp.video.URN"] = Exiv2::toString(buf.data());
else if (currentStream_ == Audio)
xmpData_["Xmp.audio.URN"] = Exiv2::toString(buf.data());
}
else if (equalsQTimeTag(buf, "dcom")) {
io_->read(buf.data(), size);
xmpData_["Xmp.video.Compressor"] = Exiv2::toString(buf.data());
}
else if (equalsQTimeTag(buf, "smhd")) {
io_->read(buf.data(), 4);
io_->read(buf.data(), 4);
xmpData_["Xmp.audio.Balance"] = returnBufValue(buf, 2);
}
else {
discard(size);
}
} // QuickTimeVideo::tagDecoder
void QuickTimeVideo::discard(unsigned long size)
{
uint64_t cur_pos = io_->tell();
io_->seek(cur_pos + size, BasicIo::beg);
} // QuickTimeVideo::discard
void QuickTimeVideo::previewTagDecoder(unsigned long size)
{
DataBuf buf(4);
uint64_t cur_pos = io_->tell();
io_->read(buf.data(), 4);
xmpData_["Xmp.video.PreviewDate"] = getULong(buf.data(), bigEndian);
io_->read(buf.data(), 2);
xmpData_["Xmp.video.PreviewVersion"] = getShort(buf.data(), bigEndian);
io_->read(buf.data(), 4);
if(equalsQTimeTag(buf, "PICT"))
xmpData_["Xmp.video.PreviewAtomType"] = "QuickDraw Picture";
else
xmpData_["Xmp.video.PreviewAtomType"] = Exiv2::toString(buf.data());
io_->seek(cur_pos + size, BasicIo::beg);
} // QuickTimeVideo::previewTagDecoder
void QuickTimeVideo::keysTagDecoder(unsigned long size)
{
DataBuf buf(4);
uint64_t cur_pos = io_->tell();
io_->read(buf.data(), 4);
xmpData_["Xmp.video.PreviewDate"] = getULong(buf.data(), bigEndian);
io_->read(buf.data(), 2);
xmpData_["Xmp.video.PreviewVersion"] = getShort(buf.data(), bigEndian);
io_->read(buf.data(), 4);
if(equalsQTimeTag(buf, "PICT"))
xmpData_["Xmp.video.PreviewAtomType"] = "QuickDraw Picture";
else
xmpData_["Xmp.video.PreviewAtomType"] = Exiv2::toString(buf.data());
io_->seek(cur_pos + size, BasicIo::beg);
} // QuickTimeVideo::keysTagDecoder
void QuickTimeVideo::trackApertureTagDecoder(unsigned long size)
{
DataBuf buf(4), buf2(2);
uint64_t cur_pos = io_->tell();
byte n = 3;
while(n--) {
io_->seek(static_cast<long>(4), BasicIo::cur); io_->read(buf.data(), 4);
if(equalsQTimeTag(buf, "clef")) {
io_->seek(static_cast<long>(4), BasicIo::cur);
io_->read(buf.data(), 2); io_->read(buf2.data(), 2);
xmpData_["Xmp.video.CleanApertureWidth"] = Exiv2::toString(getUShort(buf.data(), bigEndian))
+ "." + Exiv2::toString(getUShort(buf2.data(), bigEndian));
io_->read(buf.data(), 2); io_->read(buf2.data(), 2);
xmpData_["Xmp.video.CleanApertureHeight"] = Exiv2::toString(getUShort(buf.data(), bigEndian))
+ "." + Exiv2::toString(getUShort(buf2.data(), bigEndian));
}
else if(equalsQTimeTag(buf, "prof")) {
io_->seek(static_cast<long>(4), BasicIo::cur);
io_->read(buf.data(), 2); io_->read(buf2.data(), 2);
xmpData_["Xmp.video.ProductionApertureWidth"] = Exiv2::toString(getUShort(buf.data(), bigEndian))
+ "." + Exiv2::toString(getUShort(buf2.data(), bigEndian));
io_->read(buf.data(), 2); io_->read(buf2.data(), 2);
xmpData_["Xmp.video.ProductionApertureHeight"] = Exiv2::toString(getUShort(buf.data(), bigEndian))
+ "." + Exiv2::toString(getUShort(buf2.data(), bigEndian));
}
else if(equalsQTimeTag(buf, "enof")) {
io_->seek(static_cast<long>(4), BasicIo::cur);
io_->read(buf.data(), 2); io_->read(buf2.data(), 2);
xmpData_["Xmp.video.EncodedPixelsWidth"] = Exiv2::toString(getUShort(buf.data(), bigEndian))
+ "." + Exiv2::toString(getUShort(buf2.data(), bigEndian));
io_->read(buf.data(), 2); io_->read(buf2.data(), 2);
xmpData_["Xmp.video.EncodedPixelsHeight"] = Exiv2::toString(getUShort(buf.data(), bigEndian))
+ "." + Exiv2::toString(getUShort(buf2.data(), bigEndian));
}
}
io_->seek(static_cast<long>(cur_pos + size), BasicIo::beg);
} // QuickTimeVideo::trackApertureTagDecoder
void QuickTimeVideo::CameraTagsDecoder(unsigned long size_external)
{
uint64_t cur_pos = io_->tell();
DataBuf buf(50), buf2(4);
const TagDetails* td;
io_->read(buf.data(), 4);
if(equalsQTimeTag(buf, "NIKO")) {
io_->seek(cur_pos, BasicIo::beg);
io_->read(buf.data(), 24);
xmpData_["Xmp.video.Make"] = Exiv2::toString(buf.data());
io_->read(buf.data(), 14);
xmpData_["Xmp.video.Model"] = Exiv2::toString(buf.data());
io_->read(buf.data(), 4);
xmpData_["Xmp.video.ExposureTime"] = "1/" + Exiv2::toString( ceil( getULong(buf.data(), littleEndian) / (double)10));
io_->read(buf.data(), 4); io_->read(buf2.data(), 4);
xmpData_["Xmp.video.FNumber"] = getULong(buf.data(), littleEndian) / (double)getULong(buf2.data(), littleEndian) ;
io_->read(buf.data(), 4); io_->read(buf2.data(), 4);
xmpData_["Xmp.video.ExposureCompensation"] = getULong(buf.data(), littleEndian) / (double)getULong(buf2.data(), littleEndian) ;
io_->read(buf.data(), 10); io_->read(buf.data(), 4);
td = find(whiteBalance, getULong(buf.data(), littleEndian));
if (td)
xmpData_["Xmp.video.WhiteBalance"] = exvGettext(td->label_);
io_->read(buf.data(), 4); io_->read(buf2.data(), 4);
xmpData_["Xmp.video.FocalLength"] = getULong(buf.data(), littleEndian) / (double)getULong(buf2.data(), littleEndian) ;
io_->seek(static_cast<long>(95), BasicIo::cur);
io_->read(buf.data(), 48);
xmpData_["Xmp.video.Software"] = Exiv2::toString(buf.data());
io_->read(buf.data(), 4);
xmpData_["Xmp.video.ISO"] = getULong(buf.data(), littleEndian);
}
io_->seek(cur_pos + size_external, BasicIo::beg);
} // QuickTimeVideo::CameraTagsDecoder
void QuickTimeVideo::userDataDecoder(unsigned long size_external)
{
uint64_t cur_pos = io_->tell();
const TagVocabulary* td;
const TagVocabulary* tv, *tv_internal;
const long bufMinSize = 100;
DataBuf buf(bufMinSize);
unsigned long size = 0, size_internal = size_external;
std::memset(buf.data(), 0x0, buf.size());
while((size_internal/4 != 0) && (size_internal > 0)) {
buf.data()[4] = '\0' ;
io_->read(buf.data(), 4);
size = Exiv2::getULong(buf.data(), bigEndian);
if(size > size_internal)
break;
size_internal -= size;
io_->read(buf.data(), 4);
if(buf.data()[0] == 169)
buf.data()[0] = ' ';
td = find(userDatatags, Exiv2::toString( buf.data()));
tv = find(userDataReferencetags, Exiv2::toString( buf.data()));
if(size == 0 || (size - 12) <= 0)
break;
else if(equalsQTimeTag(buf, "DcMD") || equalsQTimeTag(buf, "NCDT"))
userDataDecoder(size - 8);
else if(equalsQTimeTag(buf, "NCTG"))
NikonTagsDecoder(size - 8);
else if(equalsQTimeTag(buf, "TAGS"))
CameraTagsDecoder(size - 8);
else if(equalsQTimeTag(buf, "CNCV") || equalsQTimeTag(buf, "CNFV")
|| equalsQTimeTag(buf, "CNMN") || equalsQTimeTag(buf, "NCHD")
|| equalsQTimeTag(buf, "FFMV")) {
io_->read(buf.data(), size - 8);
xmpData_[exvGettext(tv->label_)] = Exiv2::toString(buf.data());
}
else if(equalsQTimeTag(buf, "CMbo") || equalsQTimeTag(buf, "Cmbo")) {
io_->read(buf.data(), 2);
buf.data()[2] = '\0' ;
tv_internal = find(cameraByteOrderTags, Exiv2::toString( buf.data()));
if (tv_internal)
xmpData_[exvGettext(tv->label_)] = exvGettext(tv_internal->label_);
else
xmpData_[exvGettext(tv->label_)] = Exiv2::toString(buf.data());
}
else if(tv) {
io_->read(buf.data(), 4);
io_->read(buf.data(), size-12);
xmpData_[exvGettext(tv->label_)] = Exiv2::toString(buf.data());
}
else if(td)
tagDecoder(buf,size-8);
}
io_->seek(cur_pos + size_external, BasicIo::beg);
} // QuickTimeVideo::userDataDecoder
void QuickTimeVideo::NikonTagsDecoder(unsigned long size_external)
{
uint64_t cur_pos = io_->tell();
DataBuf buf(200), buf2(4+1);
unsigned long TagID = 0;
unsigned short dataLength = 0, dataType = 2;
const TagDetails* td, *td2;
for(int i = 0 ; i < 100 ; i++) {
io_->read(buf.data(), 4);
TagID = Exiv2::getULong(buf.data(), bigEndian);
td = find(NikonNCTGTags, TagID);
io_->read(buf.data(), 2);
dataType = Exiv2::getUShort(buf.data(), bigEndian);
std::memset(buf.data(), 0x0, buf.size());
io_->read(buf.data(), 2);
if(TagID == 0x2000023) {
uint64_t local_pos = io_->tell();
dataLength = Exiv2::getUShort(buf.data(), bigEndian);
std::memset(buf.data(), 0x0, buf.size());
io_->read(buf.data(), 4); xmpData_["Xmp.video.PictureControlVersion"] = Exiv2::toString(buf.data());
io_->read(buf.data(), 20); xmpData_["Xmp.video.PictureControlName"] = Exiv2::toString(buf.data());
io_->read(buf.data(), 20); xmpData_["Xmp.video.PictureControlBase"] = Exiv2::toString(buf.data());
io_->read(buf.data(), 4); std::memset(buf.data(), 0x0, buf.size());
io_->read(buf.data(), 1);
td2 = find(PictureControlAdjust, (int)buf.data()[0] & 7 );
if(td2)
xmpData_["Xmp.video.PictureControlAdjust"] = exvGettext(td2->label_);
else
xmpData_["Xmp.video.PictureControlAdjust"] = (int)buf.data()[0] & 7 ;
io_->read(buf.data(), 1);
td2 = find(NormalSoftHard, (int)buf.data()[0] & 7 );
if(td2)
xmpData_["Xmp.video.PictureControlQuickAdjust"] = exvGettext(td2->label_);
io_->read(buf.data(), 1);
td2 = find(NormalSoftHard, (int)buf.data()[0] & 7 );
if(td2)
xmpData_["Xmp.video.Sharpness"] = exvGettext(td2->label_);
else
xmpData_["Xmp.video.Sharpness"] = (int)buf.data()[0] & 7;
io_->read(buf.data(), 1);
td2 = find(NormalSoftHard, (int)buf.data()[0] & 7 );
if(td2)
xmpData_["Xmp.video.Contrast"] = exvGettext(td2->label_);
else
xmpData_["Xmp.video.Contrast"] = (int)buf.data()[0] & 7;
io_->read(buf.data(), 1);
td2 = find(NormalSoftHard, (int)buf.data()[0] & 7 );
if(td2)
xmpData_["Xmp.video.Brightness"] = exvGettext(td2->label_);
else
xmpData_["Xmp.video.Brightness"] = (int)buf.data()[0] & 7;
io_->read(buf.data(), 1);
td2 = find(Saturation, (int)buf.data()[0] & 7 );
if(td2)
xmpData_["Xmp.video.Saturation"] = exvGettext(td2->label_);
else
xmpData_["Xmp.video.Saturation"] = (int)buf.data()[0] & 7;
io_->read(buf.data(), 1);
xmpData_["Xmp.video.HueAdjustment"] = (int)buf.data()[0] & 7;
io_->read(buf.data(), 1);
td2 = find(FilterEffect, (int)buf.data()[0]);
if(td2)
xmpData_["Xmp.video.FilterEffect"] = exvGettext(td2->label_);
else
xmpData_["Xmp.video.FilterEffect"] = (int)buf.data()[0];
io_->read(buf.data(), 1);
td2 = find(ToningEffect, (int)buf.data()[0]);
if(td2)
xmpData_["Xmp.video.ToningEffect"] = exvGettext(td2->label_);
else
xmpData_["Xmp.video.ToningEffect"] = (int)buf.data()[0];
io_->read(buf.data(), 1); xmpData_["Xmp.video.ToningSaturation"] = (int)buf.data()[0];
io_->seek(local_pos + dataLength, BasicIo::beg);
}
else if(TagID == 0x2000024) {
uint64_t local_pos = io_->tell();
dataLength = Exiv2::getUShort(buf.data(), bigEndian);
std::memset(buf.data(), 0x0, buf.size());
io_->read(buf.data(), 2); xmpData_["Xmp.video.TimeZone"] = Exiv2::getShort(buf.data(), bigEndian);
io_->read(buf.data(), 1);
td2 = find(YesNo, (int)buf.data()[0]);
if(td2)
xmpData_["Xmp.video.DayLightSavings"] = exvGettext(td2->label_);
io_->read(buf.data(), 1);
td2 = find(DateDisplayFormat, (int)buf.data()[0]);
if(td2)
xmpData_["Xmp.video.DateDisplayFormat"] = exvGettext(td2->label_);
io_->seek(local_pos + dataLength, BasicIo::beg);
}
else if(dataType == 2 || dataType == 7) {
dataLength = Exiv2::getUShort(buf.data(), bigEndian);
std::memset(buf.data(), 0x0, buf.size());
// Sanity check with an "unreasonably" large number
if (dataLength > 200) {
#ifndef SUPPRESS_WARNINGS
EXV_ERROR << "Xmp.video Nikon Tags, dataLength was found to be larger than 200."
<< " Entries considered invalid. Not Processed.\n";
#endif
io_->seek(io_->tell() + dataLength, BasicIo::beg);
} else {
io_->read(buf.data(), dataLength);
}
if(td) {
xmpData_[exvGettext(td->label_)] = Exiv2::toString(buf.data());
}
}
else if(dataType == 4) {
dataLength = Exiv2::getUShort(buf.data(), bigEndian) * 4;
std::memset(buf.data(), 0x0, buf.size());
io_->read(buf.data(), 4);
if(td)
xmpData_[exvGettext(td->label_)] = Exiv2::toString(Exiv2::getULong( buf.data(), bigEndian));
// Sanity check with an "unreasonably" large number
if (dataLength > 200 || dataLength < 4) {
#ifndef SUPPRESS_WARNINGS
EXV_ERROR << "Xmp.video Nikon Tags, dataLength was found to be of inapropriate size."
<< " Entries considered invalid. Not Processed.\n";
#endif
io_->seek(io_->tell() + dataLength - 4, BasicIo::beg);
}
else
io_->read(buf.data(), dataLength - 4);
}
else if(dataType == 3) {
dataLength = Exiv2::getUShort(buf.data(), bigEndian) * 2;
std::memset(buf.data(), 0x0, buf.size());
io_->read(buf.data(), 2);
if(td)
xmpData_[exvGettext(td->label_)] = Exiv2::toString(Exiv2::getUShort( buf.data(), bigEndian));
// Sanity check with an "unreasonably" large number
if (dataLength > 200 || dataLength < 2) {
#ifndef SUPPRESS_WARNINGS
EXV_ERROR << "Xmp.video Nikon Tags, dataLength was found to be of inapropriate size."
<< " Entries considered invalid. Not Processed.\n";
#endif
io_->seek(io_->tell() + dataLength - 2, BasicIo::beg);
}
else
io_->read(buf.data(), dataLength - 2);
}
else if(dataType == 5) {
dataLength = Exiv2::getUShort(buf.data(), bigEndian) * 8;
std::memset(buf.data(), 0x0, buf.size());
io_->read(buf.data(), 4);
io_->read(buf2.data(), 4);
if(td)
xmpData_[exvGettext(td->label_)] = Exiv2::toString((double)Exiv2::getULong( buf.data(), bigEndian) / (double)Exiv2::getULong( buf2.data(), bigEndian));
// Sanity check with an "unreasonably" large number
if (dataLength > 200 || dataLength < 8) {
#ifndef SUPPRESS_WARNINGS
EXV_ERROR << "Xmp.video Nikon Tags, dataLength was found to be of inapropriate size."
<< " Entries considered invalid. Not Processed.\n";
#endif
io_->seek(io_->tell() + dataLength - 8, BasicIo::beg);
}
else
io_->read(buf.data(), dataLength - 8);
}
else if(dataType == 8) {
dataLength = Exiv2::getUShort(buf.data(), bigEndian) * 2;
std::memset(buf.data(), 0x0, buf.size());
io_->read(buf.data(), 2);
io_->read(buf2.data(), 2);
if(td)
xmpData_[exvGettext(td->label_)] = Exiv2::toString(Exiv2::getUShort( buf.data(), bigEndian) ) + " " + Exiv2::toString(Exiv2::getUShort( buf2.data(), bigEndian));
// Sanity check with an "unreasonably" large number
if (dataLength > 200 || dataLength < 4) {
#ifndef SUPPRESS_WARNINGS
EXV_ERROR << "Xmp.video Nikon Tags, dataLength was found to be of inapropriate size."
<< " Entries considered invalid. Not Processed.\n";
#endif
io_->seek(io_->tell() + dataLength - 4, BasicIo::beg);
}
else
io_->read(buf.data(), dataLength - 4);
}
}
io_->seek(cur_pos + size_external, BasicIo::beg);
} // QuickTimeVideo::NikonTagsDecoder
void QuickTimeVideo::setMediaStream()
{
uint64_t current_position = io_->tell();
DataBuf buf(4+1);
while(!io_->eof()) {
io_->read(buf.data(), 4);
if (equalsQTimeTag(buf, "hdlr")) {
io_->read(buf.data(), 4);
io_->read(buf.data(), 4);
io_->read(buf.data(), 4);
if (equalsQTimeTag(buf, "vide"))
currentStream_ = Video;
else if(equalsQTimeTag(buf, "soun"))
currentStream_ = Audio;
else if (equalsQTimeTag(buf, "hint"))
currentStream_ = Hint;
else
currentStream_ = GenMediaHeader;
break;
}
}
io_->seek(current_position, BasicIo::beg);
} // QuickTimeVideo::setMediaStream
void QuickTimeVideo::timeToSampleDecoder()
{
DataBuf buf(4+1);
io_->read(buf.data(), 4);
io_->read(buf.data(), 4);
uint64_t noOfEntries, totalframes = 0, timeOfFrames = 0;
noOfEntries = returnUnsignedBufValue(buf);
uint64_t temp;
for(unsigned long i = 1; i <= noOfEntries; i++) {
io_->read(buf.data(), 4);
temp = returnBufValue(buf);
totalframes += temp;
io_->read(buf.data(), 4);
timeOfFrames += temp * returnBufValue(buf);
}
if (currentStream_ == Video)
xmpData_["Xmp.video.FrameRate"] = (double)totalframes * (double)timeScale_ / (double)timeOfFrames;
} // QuickTimeVideo::timeToSampleDecoder
void QuickTimeVideo::sampleDesc(unsigned long size)
{
DataBuf buf(100);
uint64_t cur_pos = io_->tell();
io_->read(buf.data(), 4);
io_->read(buf.data(), 4);
uint64_t noOfEntries;
noOfEntries = returnUnsignedBufValue(buf);
for(unsigned long i = 1; i <= noOfEntries; i++) {
if (currentStream_ == Video)
imageDescDecoder();
else if (currentStream_ == Audio)
audioDescDecoder();
}
io_->seek(cur_pos + size, BasicIo::beg);
} // QuickTimeVideo::sampleDesc
void QuickTimeVideo::audioDescDecoder()
{
DataBuf buf(40);
std::memset(buf.data(), 0x0, buf.size());
buf.data()[4] = '\0';
io_->read(buf.data(), 4);
uint64_t size = 82;
const TagVocabulary* td;
for (int i = 0; size/4 != 0 ; size -= 4, i++) {
io_->read(buf.data(), 4);
switch(i) {
case AudioFormat:
td = find(qTimeFileType, Exiv2::toString( buf.data()));
if(td)
xmpData_["Xmp.audio.Compressor"] = exvGettext(td->label_);
else
xmpData_["Xmp.audio.Compressor"] = Exiv2::toString( buf.data());
break;
case AudioVendorID:
td = find(vendorIDTags, Exiv2::toString( buf.data()));
if(td)
xmpData_["Xmp.audio.VendorID"] = exvGettext(td->label_);
break;
case AudioChannels:
xmpData_["Xmp.audio.ChannelType"] = returnBufValue(buf, 2);
xmpData_["Xmp.audio.BitsPerSample"] = (buf.data()[2] * 256 + buf.data()[3]);
break;
case AudioSampleRate:
xmpData_["Xmp.audio.SampleRate"] = returnBufValue(buf, 2) + ((buf.data()[2] * 256 + buf.data()[3]) * 0.01);
break;
default:
break;
}
}
io_->read(buf.data(), static_cast<long>(size % 4)); //cause size is so small, this cast should be right.
} // QuickTimeVideo::audioDescDecoder
void QuickTimeVideo::imageDescDecoder()
{
DataBuf buf(40);
std::memset(buf.data(), 0x0, buf.size());
buf.data()[4] = '\0';
io_->read(buf.data(), 4);
uint64_t size = 82;
const TagVocabulary* td;
for (int i = 0; size/4 != 0 ; size -= 4, i++) {
io_->read(buf.data(), 4);
switch(i) {
case codec:
td = find(qTimeFileType, Exiv2::toString( buf.data()));
if(td)
xmpData_["Xmp.video.Codec"] = exvGettext(td->label_);
else
xmpData_["Xmp.video.Codec"] = Exiv2::toString( buf.data());
break;
case VendorID:
td = find(vendorIDTags, Exiv2::toString( buf.data()));
if(td)
xmpData_["Xmp.video.VendorID"] = exvGettext(td->label_);
break;
case SourceImageWidth_Height:
xmpData_["Xmp.video.SourceImageWidth"] = returnBufValue(buf, 2);
xmpData_["Xmp.video.SourceImageHeight"] = (buf.data()[2] * 256 + buf.data()[3]);
break;
case XResolution:
xmpData_["Xmp.video.XResolution"] = returnBufValue(buf, 2) + ((buf.data()[2] * 256 + buf.data()[3]) * 0.01);
break;
case YResolution:
xmpData_["Xmp.video.YResolution"] = returnBufValue(buf, 2) + ((buf.data()[2] * 256 + buf.data()[3]) * 0.01);
io_->read(buf.data(), 3); size -= 3;
break;
case CompressorName:
io_->read(buf.data(), 32); size -= 32;
xmpData_["Xmp.video.Compressor"] = Exiv2::toString( buf.data());
break;
default:
break;
}
}
io_->read(buf.data(), static_cast<long>(size % 4));
xmpData_["Xmp.video.BitDepth"] = returnBufValue(buf, 1);
} // QuickTimeVideo::imageDescDecoder
void QuickTimeVideo::multipleEntriesDecoder()
{
DataBuf buf(4+1);
io_->read(buf.data(), 4);
io_->read(buf.data(), 4);
uint64_t noOfEntries;
noOfEntries = returnUnsignedBufValue(buf);
for(unsigned long i = 1; i <= noOfEntries; i++)
decodeBlock();
} // QuickTimeVideo::multipleEntriesDecoder
void QuickTimeVideo::videoHeaderDecoder(unsigned long size) {
DataBuf buf(3);
std::memset(buf.data(), 0x0, buf.size());
buf.data()[2] = '\0';
currentStream_ = Video;
const TagDetails* td;
for (int i = 0; size/2 != 0 ; size -= 2, i++) {
io_->read(buf.data(), 2);
switch(i) {
case GraphicsMode:
td = find(graphicsModetags, returnBufValue(buf,2));
if(td)
xmpData_["Xmp.video.GraphicsMode"] = exvGettext(td->label_);
break;
case OpColor:
xmpData_["Xmp.video.OpColor"] = returnBufValue(buf,2);
break;
default:
break;
}
}
io_->read(buf.data(), size % 2);
} // QuickTimeVideo::videoHeaderDecoder
void QuickTimeVideo::handlerDecoder(unsigned long size)
{
uint64_t cur_pos = io_->tell();
DataBuf buf(100);
std::memset(buf.data(), 0x0, buf.size());
buf.data()[4] = '\0';
const TagVocabulary* tv;
for (int i = 0; i < 5 ; i++) {
io_->read(buf.data(), 4);
switch(i) {
case HandlerClass:
tv = find(handlerClassTags, Exiv2::toString( buf.data()));
if(tv) {
if (currentStream_ == Video)
xmpData_["Xmp.video.HandlerClass"] = exvGettext(tv->label_);
else if (currentStream_ == Audio)
xmpData_["Xmp.audio.HandlerClass"] = exvGettext(tv->label_);
}
break;
case HandlerType:
tv = find(handlerTypeTags, Exiv2::toString( buf.data()));
if(tv) {
if (currentStream_ == Video)
xmpData_["Xmp.video.HandlerType"] = exvGettext(tv->label_);
else if (currentStream_ == Audio)
xmpData_["Xmp.audio.HandlerType"] = exvGettext(tv->label_);
}
break;
case HandlerVendorID:
tv = find(vendorIDTags, Exiv2::toString( buf.data()));
if(tv) {
if (currentStream_ == Video)
xmpData_["Xmp.video.HandlerVendorID"] = exvGettext(tv->label_);
else if (currentStream_ == Audio)
xmpData_["Xmp.audio.HandlerVendorID"] = exvGettext(tv->label_);
}
break;
}
}
io_->seek(cur_pos + size, BasicIo::beg);
} // QuickTimeVideo::handlerDecoder
void QuickTimeVideo::fileTypeDecoder(unsigned long size) {
DataBuf buf(5);
std::memset(buf.data(), 0x0, buf.size());
buf.data()[4] = '\0';
Exiv2::Value::UniquePtr v = Exiv2::Value::create(Exiv2::xmpSeq);
const TagVocabulary* td;
for (int i = 0; size/4 != 0; size -=4, i++) {
io_->read(buf.data(), 4);
td = find(qTimeFileType, Exiv2::toString( buf.data()));
switch(i) {
case 0:
if(td)
xmpData_["Xmp.video.MajorBrand"] = exvGettext(td->label_);
break;
case 1:
xmpData_["Xmp.video.MinorVersion"] = returnBufValue(buf);
break;
default:
if(td)
v->read(exvGettext(td->label_));
else
v->read(Exiv2::toString(buf.data()));
break;
}
}
xmpData_.add(Exiv2::XmpKey("Xmp.video.CompatibleBrands"), v.get());
io_->read(buf.data(), size%4);
} // QuickTimeVideo::fileTypeDecoder
void QuickTimeVideo::mediaHeaderDecoder(unsigned long size) {
DataBuf buf(5);
std::memset(buf.data(), 0x0, buf.size());
buf.data()[4] = '\0';
int64_t time_scale = 1;
for (int i = 0; size/4 != 0 ; size -=4, i++) {
io_->read(buf.data(), 4);
switch(i) {
case MediaHeaderVersion:
if(currentStream_ == Video)
xmpData_["Xmp.video.MediaHeaderVersion"] = returnBufValue(buf,1);
else if (currentStream_ == Audio)
xmpData_["Xmp.audio.MediaHeaderVersion"] = returnBufValue(buf,1);
break;
case MediaCreateDate:
//A 32-bit integer that specifies (in seconds since midnight, January 1, 1904) when the movie atom was created.
if(currentStream_ == Video)
xmpData_["Xmp.video.MediaCreateDate"] = returnUnsignedBufValue(buf);
else if (currentStream_ == Audio)
xmpData_["Xmp.audio.MediaCreateDate"] = returnUnsignedBufValue(buf);
break;
case MediaModifyDate:
//A 32-bit integer that specifies (in seconds since midnight, January 1, 1904) when the movie atom was created.
if(currentStream_ == Video)
xmpData_["Xmp.video.MediaModifyDate"] = returnUnsignedBufValue(buf);
else if (currentStream_ == Audio)
xmpData_["Xmp.audio.MediaModifyDate"] = returnUnsignedBufValue(buf);
break;
case MediaTimeScale:
if(currentStream_ == Video)
xmpData_["Xmp.video.MediaTimeScale"] = returnBufValue(buf);
else if (currentStream_ == Audio)
xmpData_["Xmp.audio.MediaTimeScale"] = returnBufValue(buf);
time_scale = returnBufValue(buf);
if (time_scale <= 0)
time_scale = 1;
break;
case MediaDuration:
if(currentStream_ == Video)
xmpData_["Xmp.video.MediaDuration"] = time_scale ? returnBufValue(buf)/time_scale : 0 ;
else if (currentStream_ == Audio)
xmpData_["Xmp.audio.MediaDuration"] = time_scale ? returnBufValue(buf)/time_scale : 0;
break;
case MediaLanguageCode:
if(currentStream_ == Video)
xmpData_["Xmp.video.MediaLangCode"] = returnUnsignedBufValue(buf,2);
else if (currentStream_ == Audio)
xmpData_["Xmp.audio.MediaLangCode"] = returnUnsignedBufValue(buf,2);
break;
default:
break;
}
}
io_->read(buf.data(), size%4);
} // QuickTimeVideo::mediaHeaderDecoder
void QuickTimeVideo::trackHeaderDecoder(unsigned long size) {
DataBuf buf(5);
std::memset(buf.data(), 0x0, buf.size());
buf.data()[4] = '\0';
int64_t temp = 0;
for (int i = 0; size/4 != 0 ; size -=4, i++) {
io_->read(buf.data(), 4);
switch(i) {
case TrackHeaderVersion:
if(currentStream_ == Video)
xmpData_["Xmp.video.TrackHeaderVersion"] = returnBufValue(buf,1);
else if(currentStream_ == Audio)
xmpData_["Xmp.audio.TrackHeaderVersion"] = returnBufValue(buf,1);
break;
case TrackCreateDate:
//A 32-bit integer that specifies (in seconds since midnight, January 1, 1904) when the movie atom was created.
if(currentStream_ == Video)
xmpData_["Xmp.video.TrackCreateDate"] = returnUnsignedBufValue(buf);
else if(currentStream_ == Audio)
xmpData_["Xmp.audio.TrackCreateDate"] = returnUnsignedBufValue(buf);
break;
case TrackModifyDate:
//A 32-bit integer that specifies (in seconds since midnight, January 1, 1904) when the movie atom was created.
if(currentStream_ == Video)
xmpData_["Xmp.video.TrackModifyDate"] = returnUnsignedBufValue(buf);
else if(currentStream_ == Audio)
xmpData_["Xmp.audio.TrackModifyDate"] = returnUnsignedBufValue(buf);
break;
case TrackID:
if(currentStream_ == Video)
xmpData_["Xmp.video.TrackID"] = returnBufValue(buf);
else if(currentStream_ == Audio)
xmpData_["Xmp.audio.TrackID"] = returnBufValue(buf);
break;
case TrackDuration:
if(currentStream_ == Video)
xmpData_["Xmp.video.TrackDuration"] = timeScale_ ? returnBufValue(buf)/timeScale_ : 0;
else if(currentStream_ == Audio)
xmpData_["Xmp.audio.TrackDuration"] = timeScale_ ? returnBufValue(buf)/timeScale_ : 0;
break;
case TrackLayer:
if(currentStream_ == Video)
xmpData_["Xmp.video.TrackLayer"] = returnBufValue(buf, 2);
else if(currentStream_ == Audio)
xmpData_["Xmp.audio.TrackLayer"] = returnBufValue(buf, 2);
break;
case TrackVolume:
if(currentStream_ == Video)
xmpData_["Xmp.video.TrackVolume"] = (returnBufValue(buf, 1) + (buf.data()[2] * 0.1)) * 100;
else if(currentStream_ == Audio)
xmpData_["Xmp.video.TrackVolume"] = (returnBufValue(buf, 1) + (buf.data()[2] * 0.1)) * 100;
break;
case ImageWidth:
if(currentStream_ == Video) {
temp = returnBufValue(buf, 2) + static_cast<int64_t>((buf.data()[2] * 256 + buf.data()[3]) * 0.01);
xmpData_["Xmp.video.Width"] = temp;
width_ = temp;
}
break;
case ImageHeight:
if(currentStream_ == Video) {
temp = returnBufValue(buf, 2) + static_cast<int64_t>((buf.data()[2] * 256 + buf.data()[3]) * 0.01);
xmpData_["Xmp.video.Height"] = temp;
height_ = temp;
}
break;
default:
break;
}
}
io_->read(buf.data(), size%4);
} // QuickTimeVideo::trackHeaderDecoder
void QuickTimeVideo::movieHeaderDecoder(unsigned long size) {
DataBuf buf(5);
std::memset(buf.data(), 0x0, buf.size());
buf.data()[4] = '\0';
for (int i = 0; size/4 != 0 ; size -=4, i++) {
io_->read(buf.data(), 4);
switch(i) {
case MovieHeaderVersion:
xmpData_["Xmp.video.MovieHeaderVersion"] = returnBufValue(buf,1); break;
case CreateDate:
//A 32-bit integer that specifies (in seconds since midnight, January 1, 1904) when the movie atom was created.
xmpData_["Xmp.video.DateUTC"] = returnUnsignedBufValue(buf); break;
case ModifyDate:
//A 32-bit integer that specifies (in seconds since midnight, January 1, 1904) when the movie atom was created.
xmpData_["Xmp.video.ModificationDate"] = returnUnsignedBufValue(buf); break;
case TimeScale:
xmpData_["Xmp.video.TimeScale"] = returnBufValue(buf);
timeScale_ = returnBufValue(buf);
if (timeScale_ <= 0) timeScale_ = 1;
break;
case Duration:
if(timeScale_ != 0) { // To prevent division by zero
xmpData_["Xmp.video.Duration"] = returnBufValue(buf) * 1000 / timeScale_;
}
break;
case PreferredRate:
xmpData_["Xmp.video.PreferredRate"] = returnBufValue(buf, 2) + ((buf.data()[2] * 256 + buf.data()[3]) * 0.01); break;
case PreferredVolume:
xmpData_["Xmp.video.PreferredVolume"] = (returnBufValue(buf, 1) + (buf.data()[2] * 0.1)) * 100; break;
case PreviewTime:
xmpData_["Xmp.video.PreviewTime"] = returnBufValue(buf); break;
case PreviewDuration:
xmpData_["Xmp.video.PreviewDuration"] = returnBufValue(buf); break;
case PosterTime:
xmpData_["Xmp.video.PosterTime"] = returnBufValue(buf); break;
case SelectionTime:
xmpData_["Xmp.video.SelectionTime"] = returnBufValue(buf); break;
case SelectionDuration:
xmpData_["Xmp.video.SelectionDuration"] = returnBufValue(buf); break;
case CurrentTime:
xmpData_["Xmp.video.CurrentTime"] = returnBufValue(buf); break;
case NextTrackID:
xmpData_["Xmp.video.NextTrackID"] = returnBufValue(buf); break;
default:
break;
}
}
io_->read(buf.data(), size%4);
} // QuickTimeVideo::movieHeaderDecoder
void QuickTimeVideo::aspectRatio()
{
//TODO - Make a better unified method to handle all cases of Aspect Ratio
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;
}
} // QuickTimeVideo::aspectRatio
Image::UniquePtr newQTimeInstance(BasicIo::UniquePtr io, bool /*create*/) {
Image::UniquePtr image(new QuickTimeVideo(std::move(io)));
if (!image->good()) {
image.reset();
}
return image;
}
bool isQTimeType(BasicIo& iIo, bool advance) {
auto buf = iIo.read(12);
if (iIo.error() || iIo.eof()) {
return false;
}
auto qTimeTags =
std::array{"PICT", "free", "ftyp", "junk", "mdat", "moov", "pict", "pnot", "skip", "uuid", "wide"};
bool matched = false;
for (auto const& tag : qTimeTags) {
auto tmp = buf.cmpBytes(4, tag, 4);
if (tmp == 0) {
// we only match if we actually know the video type. This is done
// to avoid matching just on ftyp because bmffimage also has that
// header.
auto td = find(qTimeFileType, std::string{buf.c_str(8), 4} );
if (td){
matched = true;
}
break;
}
}
if (!advance || !matched) {
iIo.seek(static_cast<long>(0), BasicIo::beg);
}
return matched;
}
} // namespace Exiv2