summaryrefslogtreecommitdiff
path: root/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/XMPFiles/FileHandlers/MPEG4_Handler.cpp')
-rw-r--r--source/XMPFiles/FileHandlers/MPEG4_Handler.cpp2651
1 files changed, 2099 insertions, 552 deletions
diff --git a/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp b/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp
index a3f92d6..06400d1 100644
--- a/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2006 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -8,6 +8,8 @@
// =================================================================================================
#include "MPEG4_Handler.hpp"
+#include "ISOBaseMedia_Support.hpp"
+#include "MOOV_Support.hpp"
#include "UnicodeConversions.hpp"
#include "MD5.h"
@@ -26,36 +28,46 @@ using namespace std;
///
// =================================================================================================
-// File and box type codes as big endian 32-bit integers, allows faster comparisons.
+// The basic content of a timecode sample description table entry. Does not include trailing boxes.
-static XMP_Uns32 kBE_ftyp = MakeUns32BE ( 0x66747970UL ); // File header Box, no version/flags.
+#pragma pack ( push, 1 )
-static XMP_Uns32 kBE_mp41 = MakeUns32BE ( 0x6D703431UL ); // Compatibility codes
-static XMP_Uns32 kBE_mp42 = MakeUns32BE ( 0x6D703432UL );
-static XMP_Uns32 kBE_f4v = MakeUns32BE ( 0x66347620UL );
+struct stsdBasicEntry {
+ XMP_Uns32 entrySize;
+ XMP_Uns32 format;
+ XMP_Uns8 reserved_1 [6];
+ XMP_Uns16 dataRefIndex;
+ XMP_Uns32 reserved_2;
+ XMP_Uns32 flags;
+ XMP_Uns32 timeScale;
+ XMP_Uns32 frameDuration;
+ XMP_Uns8 frameCount;
+ XMP_Uns8 reserved_3;
+};
-static XMP_Uns32 kBE_moov = MakeUns32BE ( 0x6D6F6F76UL ); // Container Box, no version/flags.
-static XMP_Uns32 kBE_mvhd = MakeUns32BE ( 0x6D766864UL ); // Data FullBox, has version/flags.
-static XMP_Uns32 kBE_udta = MakeUns32BE ( 0x75647461UL ); // Container Box, no version/flags.
-static XMP_Uns32 kBE_cprt = MakeUns32BE ( 0x63707274UL ); // Data FullBox, has version/flags.
-static XMP_Uns32 kBE_uuid = MakeUns32BE ( 0x75756964UL ); // Data Box, no version/flags.
-static XMP_Uns32 kBE_free = MakeUns32BE ( 0x66726565UL ); // Free space Box, no version/flags.
-static XMP_Uns32 kBE_mdat = MakeUns32BE ( 0x6D646174UL ); // Media data Box, no version/flags.
+#pragma pack ( pop )
-static XMP_Uns32 kBE_xmpUUID [4] = { MakeUns32BE ( 0xBE7ACFCBUL ),
- MakeUns32BE ( 0x97A942E8UL ),
- MakeUns32BE ( 0x9C719994UL ),
- MakeUns32BE ( 0x91E3AFACUL ) };
+// =================================================================================================
// ! The buffer and constants are both already big endian.
#define Get4CharCode(buffPtr) (*((XMP_Uns32*)(buffPtr)))
// =================================================================================================
+static inline bool IsClassicQuickTimeBox ( XMP_Uns32 boxType )
+{
+ if ( (boxType == ISOMedia::k_moov) || (boxType == ISOMedia::k_mdat) || (boxType == ISOMedia::k_pnot) ||
+ (boxType == ISOMedia::k_free) || (boxType == ISOMedia::k_skip) || (boxType == ISOMedia::k_wide) ) return true;
+ return false;
+} // IsClassicQuickTimeBox
+
+// =================================================================================================
+
// Pairs of 3 letter ISO 639-2 codes mapped to 2 letter ISO 639-1 codes from:
// http://www.loc.gov/standards/iso639-2/php/code_list.php
// Would have to write an "==" operator to use std::map, must compare values not pointers.
// ! Not fully sorted, do not use a binary search.
+
static XMP_StringPtr kKnownLangs[] =
{ "aar", "aa", "abk", "ab", "afr", "af", "aka", "ak", "alb", "sq", "sqi", "sq", "amh", "am",
"ara", "ar", "arg", "an", "arm", "hy", "hye", "hy", "asm", "as", "ava", "av", "ave", "ae",
@@ -92,91 +104,161 @@ static XMP_StringPtr kKnownLangs[] =
"tib", "bo", "bod", "bo", "tir", "ti", "ton", "to", "tsn", "tn", "tso", "ts", "tuk", "tk",
"tur", "tr", "twi", "tw", "uig", "ug", "ukr", "uk", "urd", "ur", "uzb", "uz", "ven", "ve",
"vie", "vi", "vol", "vo", "wel", "cy", "cym", "cy", "wln", "wa", "wol", "wo", "xho", "xh",
- "yid", "yi", "yor", "yo", "zha", "za", "zho", "zh", "chi", "zh", "zul", "zu",
+ "yid", "yi", "yor", "yo", "zha", "za", "zho", "zh", "chi", "zh", "zul", "zu",
0, 0 };
-static XMP_StringPtr Lookup2LetterLang ( XMP_StringPtr lang3 )
+static inline XMP_StringPtr Lookup2LetterLang ( XMP_StringPtr lang3 )
{
-
for ( size_t i = 0; kKnownLangs[i] != 0; i += 2 ) {
if ( XMP_LitMatch ( lang3, kKnownLangs[i] ) ) return kKnownLangs[i+1];
}
-
- return lang3; // Not found, return the input.
-
-} // Lookup2LetterLang
+ return "";
+}
+
+static inline XMP_StringPtr Lookup3LetterLang ( XMP_StringPtr lang2 )
+{
+ for ( size_t i = 0; kKnownLangs[i] != 0; i += 2 ) {
+ if ( XMP_LitMatch ( lang2, kKnownLangs[i+1] ) ) return kKnownLangs[i];
+ }
+ return "";
+}
// =================================================================================================
// MPEG4_CheckFormat
// =================
//
-// An MPEG-4 file is an instance of an ISO Base Media file, ISO 14496-12 and -14. An MPEG-4 file
-// must begin with an 'ftyp' box containing 'mp41', 'mp42', or 'f4v ' in the compatible brands.
+// There are 3 variations of recognized file:
+// - Normal MPEG-4 - has an 'ftyp' box containing a known compatible brand but not 'qt '.
+// - Modern QuickTime - has an 'ftyp' box containing 'qt ' as a compatible brand.
+// - Classic QuickTime - has no 'ftyp' box, should have recognized top level boxes.
+//
+// An MPEG-4 or modern QuickTime file is an instance of an ISO Base Media file, ISO 14496-12 and -14.
+// A classic QuickTime file has the same physical box structure, but somewhat different box types.
+// The ISO files must begin with an 'ftyp' box containing 'mp41', 'mp42', 'f4v ', or 'qt ' in the
+// compatible brands.
+//
+// The general box structure is:
//
-// The 'ftyp' box structure is:
+// 0 4 uns32 box size, 0 means "to EoF", 1 means 64-bit size follows
+// 4 4 uns32 box type
+// 8 8 uns64 box size, present only if uns32 size is 1
+// - * box content
+//
+// The 'ftyp' box content is:
//
-// 0 4 uns32 box size, 0 means "to EoF"
-// 4 4 uns32 box type, 'ftyp'
-// 8 8 uns64 box size, if uns32 size is 1
// - 4 uns32 major brand
// - 4 uns32 minor version
// - * uns32 sequence of compatible brands, to the end of the box
-//
-// All integers are big endian.
+
+// ! With the addition of QuickTime support there is some change in behavior in OpenFile when the
+// ! kXMPFiles_OpenStrictly option is used wth a specific file format. The kXMP_MPEG4File and
+// ! kXMP_MOVFile formats are treated uniformly, you can't force "real MOV" or "real MPEG-4". You
+// ! can check afterwards using GetFileInfo to see what the file happens to be.
bool MPEG4_CheckFormat ( XMP_FileFormat format,
XMP_StringPtr filePath,
LFA_FileRef fileRef,
XMPFiles* parent )
{
- XMP_Uns8 buffer [4096];
- XMP_Uns32 ioCount, brandCount, brandOffset, id;
- XMP_Uns64 fileSize, boxSize;
-
- // Do some basic header checks, figure out how many compatible brands are listed.
-
+ XMP_Uns8 buffer [4*1024];
+ XMP_Uns32 ioCount, brandCount, brandOffset;
+ XMP_Uns64 fileSize, nextOffset;
+ ISOMedia::BoxInfo currBox;
+
+ #define IsTolerableBoxChar(ch) ( ((0x20 <= (ch)) && ((ch) <= 0x7E)) || ((ch) == 0xA9) )
+
+ XMP_AbortProc abortProc = parent->abortProc;
+ void * abortArg = parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ bool openStrictly = XMP_OptionIsSet ( parent->openFlags, kXMPFiles_OpenStrictly);
+
+ // Get the first box's info, see if it is 'ftyp' or not.
+
+ XMP_Assert ( (parent->tempPtr == 0) && (parent->tempUI32 == 0) );
+
fileSize = LFA_Measure ( fileRef );
-
- LFA_Seek ( fileRef, 0, SEEK_SET );
- ioCount = LFA_Read ( fileRef, buffer, 16 ); // Read to the compatible brands, assuming a 32-bit size.
- if ( ioCount < 16 ) return false;
+ nextOffset = ISOMedia::GetBoxInfo ( fileRef, 0, fileSize, &currBox );
+ if ( currBox.headerSize < 8 ) return false; // Can't be an ISO or QuickTime file.
- id = Get4CharCode ( &buffer[4] ); // Is the first box an 'ftyp' box?
- if ( id != kBE_ftyp ) return false;
-
- boxSize = GetUns32BE ( &buffer[0] ); // Get the box length.
- brandOffset = 16;
+ if ( currBox.boxType == ISOMedia::k_ftyp ) {
- if ( boxSize == 0 ) {
- boxSize = fileSize;
- } else if ( boxSize == 1 ) {
- boxSize = GetUns64BE ( &buffer[8] );
- LFA_Seek ( fileRef, 24, SEEK_SET ); // Seek to the compatible brands.
- brandOffset = 24;
- }
-
- if ( (boxSize < brandOffset) || (boxSize > fileSize) ||
- ((boxSize & 0x3) != 0) || (boxSize > 4024) ) return false; // Sanity limit of 1000 brands.
+ // Have an 'ftyp' box, look through the compatible brands. If 'qt ' is present then this is
+ // a modern QuickTime file, regardless of what else is found. Otherwise this is plain ISO if
+ // any of the other recognized brands are found.
+
+ if ( currBox.contentSize < 12 ) return false; // No compatible brands at all.
+ if ( currBox.contentSize > 1024*1024 ) return false; // Sanity check and make sure count fits in 32 bits.
+ brandCount = ((XMP_Uns32)currBox.contentSize - 8) >> 2;
+
+ LFA_Seek ( fileRef, 8, SEEK_CUR ); // Skip the major and minor brands.
+ ioCount = brandOffset = 0;
+
+ bool haveCompatibleBrand = false;
+
+ for ( ; brandCount > 0; --brandCount, brandOffset += 4 ) {
+
+ if ( brandOffset >= ioCount ) {
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_CheckFormat - User abort", kXMPErr_UserAbort );
+ }
+ ioCount = 4 * brandCount;
+ if ( ioCount > sizeof(buffer) ) ioCount = sizeof(buffer);
+ ioCount = LFA_Read ( fileRef, buffer, ioCount, kLFA_RequireAll );
+ brandOffset = 0;
+ }
+
+ XMP_Uns32 brand = GetUns32BE ( &buffer[brandOffset] );
+ if ( brand == ISOMedia::k_qt ) { // Don't need to look further.
+ if ( openStrictly && (format != kXMP_MOVFile) ) return false;
+ parent->format = kXMP_MOVFile;
+ parent->tempUI32 = MOOV_Manager::kFileIsModernQT;
+ return true;
+ } else if ( (brand == ISOMedia::k_mp41) || (brand == ISOMedia::k_mp42) || (brand == ISOMedia::k_f4v) ) {
+ haveCompatibleBrand = true; // Need to keep looking in case 'qt ' follows.
+ }
- // Look for the 'mp41', 'mp42', of 'f4v ' compatible brands.
-
- brandCount = (XMP_Uns32)( (boxSize - brandOffset) / 4 );
- while ( brandCount > 0 ) {
-
- XMP_Uns32 clumpSize = brandCount * 4;
- if ( clumpSize > sizeof(buffer) ) clumpSize = sizeof(buffer);
- ioCount = LFA_Read ( fileRef, buffer, clumpSize );
- if ( ioCount < clumpSize ) return false;
-
- for ( XMP_Uns32 i = 0; i < clumpSize; i += 4 ) {
- id = Get4CharCode ( &buffer[i] );
- if ( (id == kBE_mp41) || (id == kBE_mp42) || (id == kBE_f4v) ) return true;
}
-
- brandCount -= clumpSize/4;
+
+ if ( ! haveCompatibleBrand ) return false;
+ if ( openStrictly && (format != kXMP_MPEG4File) ) return false;
+ parent->format = kXMP_MPEG4File;
+ parent->tempUI32 = MOOV_Manager::kFileIsNormalISO;
+ return true;
+
+ } else {
+
+ // No 'ftyp', look for classic QuickTime: 'moov', 'mdat', 'pnot', 'free', 'skip', and 'wide'.
+ // As an expedient, quit when a 'moov' box is found. Tolerate other boxes, they are in the
+ // wild for ill-formed files, e.g. seen when 'moov'/'udta' children get left at top level.
+
+ while ( currBox.boxType != ISOMedia::k_moov ) {
+
+ if ( ! IsClassicQuickTimeBox ( currBox.boxType ) ) {
+ // Make sure the box type is 4 ASCII characters or 0xA9 (MacRoman copyright).
+ XMP_Uns8 b1 = (XMP_Uns8) (currBox.boxType >> 24);
+ XMP_Uns8 b2 = (XMP_Uns8) ((currBox.boxType >> 16) & 0xFF);
+ XMP_Uns8 b3 = (XMP_Uns8) ((currBox.boxType >> 8) & 0xFF);
+ XMP_Uns8 b4 = (XMP_Uns8) (currBox.boxType & 0xFF);
+ bool ok = IsTolerableBoxChar(b1) && IsTolerableBoxChar(b2) &&
+ IsTolerableBoxChar(b3) && IsTolerableBoxChar(b4);
+ if ( ! ok ) return false;
+ }
+ if ( nextOffset >= fileSize ) return false;
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_CheckFormat - User abort", kXMPErr_UserAbort );
+ }
+ nextOffset = ISOMedia::GetBoxInfo ( fileRef, nextOffset, fileSize, &currBox );
+
+ }
+
+ if ( openStrictly && (format != kXMP_MOVFile) ) return false;
+ parent->format = kXMP_MOVFile;
+ parent->tempUI32 = MOOV_Manager::kFileIsTraditionalQT;
+ return true;
}
-
+
return false;
} // MPEG4_CheckFormat
@@ -196,12 +278,15 @@ XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent )
// MPEG4_MetaHandler::MPEG4_MetaHandler
// ====================================
-MPEG4_MetaHandler::MPEG4_MetaHandler ( XMPFiles * _parent ) : xmpBoxPos(0)
+MPEG4_MetaHandler::MPEG4_MetaHandler ( XMPFiles * _parent )
+ : fileMode(0), havePreferredXMP(false), xmpBoxPos(0), moovBoxPos(0), xmpBoxSize(0), moovBoxSize(0)
{
this->parent = _parent; // Inherited, can't set in the prefix.
this->handlerFlags = kMPEG4_HandlerFlags;
this->stdCharForm = kXMP_Char8Bit;
+ this->fileMode = (XMP_Uns8)_parent->tempUI32;
+ _parent->tempUI32 = 0;
} // MPEG4_MetaHandler::MPEG4_MetaHandler
@@ -212,675 +297,2137 @@ MPEG4_MetaHandler::MPEG4_MetaHandler ( XMPFiles * _parent ) : xmpBoxPos(0)
MPEG4_MetaHandler::~MPEG4_MetaHandler()
{
- // Nothing to do yet.
+ // Nothing to do.
} // MPEG4_MetaHandler::~MPEG4_MetaHandler
// =================================================================================================
-// GetBoxInfo
-// ==========
-//
-// Seek to the start of a box and extract the type and size, split the size into header and content
-// portions. Leave the file positioned at the first byte of content.
+// SecondsToXMPDate
+// ================
-// ! We're returning the content size, not the raw (full) MPEG-4 box size!
+// *** ASF has similar code with different origin, should make a shared utility.
-static void GetBoxInfo ( LFA_FileRef fileRef, XMP_Uns64 fileSize, XMP_Uns64 boxPos,
- XMP_Uns32 * boxType, XMP_Uns64 * headerSize, XMP_Uns64 * contentSize )
+static void SecondsToXMPDate ( XMP_Uns64 isoSeconds, XMP_DateTime * xmpDate )
{
- XMP_Uns8 buffer [8];
- XMP_Uns32 u32Size;
+ memset ( xmpDate, 0, sizeof(XMP_DateTime) ); // AUDIT: Using sizeof(XMP_DateTime) is safe.
- LFA_Seek ( fileRef, boxPos, SEEK_SET );
- (void) LFA_Read ( fileRef, buffer, 8, kLFA_RequireAll );
+ XMP_Int32 days = (XMP_Int32) (isoSeconds / 86400);
+ isoSeconds -= ((XMP_Uns64)days * 86400);
- u32Size = GetUns32BE ( &buffer[0] );
- *boxType = Get4CharCode ( &buffer[4] );
+ XMP_Int32 hour = (XMP_Int32) (isoSeconds / 3600);
+ isoSeconds -= ((XMP_Uns64)hour * 3600);
- if ( u32Size > 1 ) {
- *headerSize = 8; // Normal explicit size case.
- *contentSize = u32Size - 8;
- } else if ( u32Size == 0 ) {
- *headerSize = 8; // The box goes to EoF.
- *contentSize = fileSize - (boxPos + 8);
- } else {
- XMP_Uns64 u64Size; // Have a 64-bit size.
- (void) LFA_Read ( fileRef, &u64Size, 8, kLFA_RequireAll );
- u64Size = MakeUns64BE ( u64Size );
- *headerSize = 16;
- *contentSize = u64Size - 16;
+ XMP_Int32 minute = (XMP_Int32) (isoSeconds / 60);
+ isoSeconds -= ((XMP_Uns64)minute * 60);
+
+ XMP_Int32 second = (XMP_Int32)isoSeconds;
+
+ xmpDate->year = 1904; // Start with the ISO origin.
+ xmpDate->month = 1;
+ xmpDate->day = 1;
+
+ xmpDate->day += days; // Add in the delta.
+ xmpDate->hour = hour;
+ xmpDate->minute = minute;
+ xmpDate->second = second;
+
+ xmpDate->hasTimeZone = true; // ! Needed for ConvertToUTCTime to do anything.
+ SXMPUtils::ConvertToUTCTime ( xmpDate ); // Normalize the date/time.
+
+} // SecondsToXMPDate
+
+// =================================================================================================
+// XMPDateToSeconds
+// ================
+
+// *** ASF has similar code with different origin, should make a shared utility.
+
+static bool IsLeapYear ( XMP_Int32 year )
+{
+ if ( year < 0 ) year = -year + 1; // Fold the negative years, assuming there is a year 0.
+ if ( (year % 4) != 0 ) return false; // Not a multiple of 4.
+ if ( (year % 100) != 0 ) return true; // A multiple of 4 but not a multiple of 100.
+ if ( (year % 400) == 0 ) return true; // A multiple of 400.
+ return false; // A multiple of 100 but not a multiple of 400.
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static XMP_Int32 DaysInMonth ( XMP_Int32 year, XMP_Int32 month )
+{
+ static XMP_Int32 daysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
+ XMP_Int32 days = daysInMonth[month];
+ if ( (month == 2) && IsLeapYear(year) ) days += 1;
+ return days;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static void XMPDateToSeconds ( const XMP_DateTime & _xmpDate, XMP_Uns64 * isoSeconds )
+{
+ XMP_DateTime xmpDate = _xmpDate;
+ SXMPUtils::ConvertToUTCTime ( &xmpDate );
+
+ XMP_Uns64 tempSeconds = (XMP_Uns64)xmpDate.second;
+ tempSeconds += (XMP_Uns64)xmpDate.minute * 60;
+ tempSeconds += (XMP_Uns64)xmpDate.hour * 3600;
+
+ XMP_Int32 days = (xmpDate.day - 1);
+
+ --xmpDate.month;
+ while ( xmpDate.month >= 1 ) {
+ days += DaysInMonth ( xmpDate.year, xmpDate.month );
+ --xmpDate.month;
+ }
+
+ --xmpDate.year;
+ while ( xmpDate.year >= 1904 ) {
+ days += (IsLeapYear ( xmpDate.year) ? 366 : 365 );
+ --xmpDate.year;
}
-} // GetBoxInfo
+ tempSeconds += (XMP_Uns64)days * 86400;
+ *isoSeconds = tempSeconds;
+
+} // XMPDateToSeconds
// =================================================================================================
-// MPEG4_MetaHandler::CacheFileData
-// ================================
-//
-// The XMP in MPEG-4 is in a top level XMP 'uuid' box. Legacy metadata might be found in 2 places,
-// the 'moov'/'mvhd' box, and the 'moov'/'udta'/'cprt' boxes. There is at most 1 each of the 'moov',
-// 'mvhd' and 'udta' boxes. There are any number of 'cprt' boxes, including none. The 'udta' box can
-// be large, so don't cache all of it.
+// ImportMVHDItems
+// ===============
-void MPEG4_MetaHandler::CacheFileData()
+static bool ImportMVHDItems ( MOOV_Manager::BoxInfo mvhdInfo, SXMPMeta * xmp )
{
- XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) );
+ XMP_Assert ( mvhdInfo.boxType == ISOMedia::k_mvhd );
+ if ( mvhdInfo.contentSize < 4 ) return false; // Just enough to check the version/flags at first.
- LFA_FileRef fileRef = this->parent->fileRef;
+ XMP_Uns8 mvhdVersion = *mvhdInfo.content;
+ if ( mvhdVersion > 1 ) return false;
+
+ XMP_Uns64 creationTime, modificationTime, duration;
+ XMP_Uns32 timescale;
+
+ if ( mvhdVersion == 0 ) {
+
+ if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return false;
+ MOOV_Manager::Content_mvhd_0 * mvhdRaw_0 = (MOOV_Manager::Content_mvhd_0*) mvhdInfo.content;
- XMP_AbortProc abortProc = this->parent->abortProc;
- void * abortArg = this->parent->abortArg;
- const bool checkAbort = (abortProc != 0);
+ creationTime = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->creationTime );
+ modificationTime = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->modificationTime );
+ timescale = GetUns32BE ( &mvhdRaw_0->timescale );
+ duration = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->duration );
- XMP_Uns64 fileSize = LFA_Measure ( fileRef );
- XMP_Uns64 outerPos, outerSize, hSize, cSize;
- XMP_Uns32 boxType;
+ } else {
- // The outer loop looks for the top level 'moov' and 'uuid'/XMP boxes.
+ XMP_Assert ( mvhdVersion == 1 );
+ if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_1 ) ) return false;
+ MOOV_Manager::Content_mvhd_1 * mvhdRaw_1 = (MOOV_Manager::Content_mvhd_1*) mvhdInfo.content;
+
+ creationTime = GetUns64BE ( &mvhdRaw_1->creationTime );
+ modificationTime = GetUns64BE ( &mvhdRaw_1->modificationTime );
+ timescale = GetUns32BE ( &mvhdRaw_1->timescale );
+ duration = GetUns64BE ( &mvhdRaw_1->duration );
+
+ }
+
+ bool haveImports = false;
+ XMP_DateTime xmpDate;
+
+ if ( (creationTime >> 32) < 0xFF ) { // Sanity check for bogus date info.
+ SecondsToXMPDate ( creationTime, &xmpDate );
+ xmp->SetProperty_Date ( kXMP_NS_XMP, "CreateDate", xmpDate );
+ haveImports = true;
+ }
+
+ if ( (modificationTime >> 32) < 0xFF ) { // Sanity check for bogus date info.
+ SecondsToXMPDate ( modificationTime, &xmpDate );
+ xmp->SetProperty_Date ( kXMP_NS_XMP, "ModifyDate", xmpDate );
+ haveImports = true;
+ }
+
+ if ( timescale != 0 ) { // Avoid 1/0 for the scale field.
+ char buffer [32]; // A 64-bit number is at most 20 digits.
+ xmp->DeleteProperty ( kXMP_NS_DM, "duration" ); // Delete the whole struct.
+ snprintf ( buffer, sizeof(buffer), "%llu", duration ); // AUDIT: The buffer is big enough.
+ xmp->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", &buffer[0] );
+ snprintf ( buffer, sizeof(buffer), "1/%u", timescale ); // AUDIT: The buffer is big enough.
+ xmp->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", &buffer[0] );
+ haveImports = true;
+ }
+
+ return haveImports;
+
+} // ImportMVHDItems
+
+// =================================================================================================
+// ExportMVHDItems
+// ===============
+
+static void ExportMVHDItems ( const SXMPMeta & xmp, MOOV_Manager * moovMgr )
+{
+ XMP_DateTime xmpDate;
+ bool createFound, modifyFound;
+ XMP_Uns64 createSeconds = 0, modifySeconds = 0;
- bool moovFound = false;
- if ( this->parent->openFlags & kXMPFiles_OpenOnlyXMP ) moovFound = true; // Ignore legacy.
+ MOOV_Manager::BoxInfo mvhdInfo;
+ MOOV_Manager::BoxRef mvhdRef = moovMgr->GetBox ( "moov/mvhd", &mvhdInfo );
+ if ( (mvhdRef == 0) || (mvhdInfo.contentSize < 4) ) return;
- for ( outerPos = 0; (outerPos < fileSize) && ((! this->containsXMP) || (! moovFound)); outerPos += outerSize ) {
+ XMP_Uns8 version = *mvhdInfo.content;
+ if ( version > 1 ) return;
- if ( checkAbort && abortProc(abortArg) ) {
- XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
+ createFound = xmp.GetProperty_Date ( kXMP_NS_XMP, "CreateDate", &xmpDate, 0 );
+ if ( createFound ) XMPDateToSeconds ( xmpDate, &createSeconds );
+
+ modifyFound = xmp.GetProperty_Date ( kXMP_NS_XMP, "ModifyDate", &xmpDate, 0 );
+ if ( modifyFound ) XMPDateToSeconds ( xmpDate, &modifySeconds );
+
+ if ( version == 1 ) {
+
+ // Modify the v1 box in-place.
+
+ if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_1 ) ) return;
+
+ XMP_Uns64 oldCreate = GetUns64BE ( mvhdInfo.content + 4 );
+ XMP_Uns64 oldModify = GetUns64BE ( mvhdInfo.content + 12 );
+
+ if ( createFound ) {
+ if ( createSeconds != oldCreate ) PutUns64BE ( createSeconds, ((XMP_Uns8*)mvhdInfo.content + 4) );
+ moovMgr->NoteChange();
+ }
+ if ( modifyFound ) {
+ if ( modifySeconds != oldModify ) PutUns64BE ( modifySeconds, ((XMP_Uns8*)mvhdInfo.content + 12) );
+ moovMgr->NoteChange();
}
+
+ } else if ( ((createSeconds >> 32) == 0) && ((modifySeconds >> 32) == 0) ) {
- GetBoxInfo ( fileRef, fileSize, outerPos, &boxType, &hSize, &cSize );
- outerSize = hSize + cSize;
+ // Modify the v0 box in-place.
+
+ if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return;
+
+ XMP_Uns32 oldCreate = GetUns32BE ( mvhdInfo.content + 4 );
+ XMP_Uns32 oldModify = GetUns32BE ( mvhdInfo.content + 8 );
- if ( (! this->containsXMP) && (boxType == kBE_uuid) ) {
+ if ( createFound ) {
+ if ( (XMP_Uns32)createSeconds != oldCreate ) PutUns32BE ( (XMP_Uns32)createSeconds, ((XMP_Uns8*)mvhdInfo.content + 4) );
+ moovMgr->NoteChange();
+ }
+ if ( modifyFound ) {
+ if ( (XMP_Uns32)modifySeconds != oldModify ) PutUns32BE ( (XMP_Uns32)modifySeconds, ((XMP_Uns8*)mvhdInfo.content + 8) );
+ moovMgr->NoteChange();
+ }
+
+ } else {
- XMP_Uns8 uuid [16];
- LFA_Read ( fileRef, uuid, 16, kLFA_RequireAll );
-
- if ( memcmp ( uuid, kBE_xmpUUID, 16 ) == 0 ) {
+ // Replace the v0 box with a v1 box.
+
+ XMP_Assert ( createFound | modifyFound ); // One of them has high bits set.
+ if ( mvhdInfo.contentSize != sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return;
+
+ MOOV_Manager::Content_mvhd_0 * mvhdV0 = (MOOV_Manager::Content_mvhd_0*) mvhdInfo.content;
+ MOOV_Manager::Content_mvhd_1 mvhdV1;
+
+ // Copy the unchanged fields directly.
+
+ mvhdV1.timescale = mvhdV0->timescale;
+ mvhdV1.rate = mvhdV0->rate;
+ mvhdV1.volume = mvhdV0->volume;
+ mvhdV1.pad_1 = mvhdV0->pad_1;
+ mvhdV1.pad_2 = mvhdV0->pad_2;
+ mvhdV1.pad_3 = mvhdV0->pad_3;
+ for ( int i = 0; i < 9; ++i ) mvhdV1.matrix[i] = mvhdV0->matrix[i];
+ for ( int i = 0; i < 6; ++i ) mvhdV1.preDef[i] = mvhdV0->preDef[i];
+ mvhdV1.nextTrackID = mvhdV0->nextTrackID;
+
+ // Set the fields that have changes.
+
+ mvhdV1.vFlags = (1 << 24) | (mvhdV0->vFlags & 0xFFFFFF);
+ mvhdV1.duration = MakeUns64BE ( (XMP_Uns64) GetUns32BE ( &mvhdV0->duration ) );
+
+ XMP_Uns64 temp64;
+
+ temp64 = (XMP_Uns64) GetUns32BE ( &mvhdV0->creationTime );
+ if ( createFound ) temp64 = createSeconds;
+ mvhdV1.creationTime = MakeUns64BE ( temp64 );
+
+ temp64 = (XMP_Uns64) GetUns32BE ( &mvhdV0->modificationTime );
+ if ( modifyFound ) temp64 = modifySeconds;
+ mvhdV1.modificationTime = MakeUns64BE ( temp64 );
+
+ moovMgr->SetBox ( mvhdRef, &mvhdV1, sizeof ( MOOV_Manager::Content_mvhd_1 ) );
+
+ }
+
+} // ExportMVHDItems
+
+// =================================================================================================
+// ImportISOCopyrights
+// ===================
+//
+// The cached 'moov'/'udta'/'cprt' boxes are full boxes. The "real" content is a UInt16 packed 3
+// character language code and a UTF-8 or UTF-16 string.
+
+static bool ImportISOCopyrights ( const std::vector<MOOV_Manager::BoxInfo> & cprtBoxes, SXMPMeta * xmp )
+{
+ bool haveImports = false;
+
+ std::string tempStr;
+ char lang3 [4]; // The unpacked ISO-639-2/T language code with final null.
+ lang3[3] = 0;
+
+ for ( size_t i = 0, limit = cprtBoxes.size(); i < limit; ++i ) {
+
+ const MOOV_Manager::BoxInfo & currBox = cprtBoxes[i];
+ if ( currBox.contentSize < 4+2+1 ) continue; // Want enough for a non-empty value.
+ if ( *currBox.content != 0 ) continue; // Only proceed for version 0, ignore the flags.
+
+ XMP_Uns16 packedLang = GetUns16BE ( currBox.content + 4 );
+ lang3[0] = (packedLang >> 10) + 0x60;
+ lang3[1] = ((packedLang >> 5) & 0x1F) + 0x60;
+ lang3[2] = (packedLang & 0x1F) + 0x60;
+ XMP_StringPtr xmpLang = Lookup2LetterLang ( lang3 );
+ if ( *xmpLang == 0 ) continue;
+
+ XMP_StringPtr textPtr = (XMP_StringPtr) (currBox.content + 6);
+ XMP_StringLen textLen = currBox.contentSize - 6;
+
+ if ( (textLen >= 2) && (GetUns16BE(textPtr) == 0xFEFF) ) {
+ FromUTF16 ( (UTF16Unit*)textPtr, textLen/2, &tempStr, true /* big endian */ );
+ textPtr = tempStr.c_str();
+ }
+
+ xmp->SetLocalizedText ( kXMP_NS_DC, "rights", xmpLang, xmpLang, textPtr );
+ haveImports = true;
+
+ }
+
+ return haveImports;
+
+} // ImportISOCopyrights
+
+// =================================================================================================
+// ExportISOCopyrights
+// ===================
+
+static void ExportISOCopyrights ( const SXMPMeta & xmp, MOOV_Manager * moovMgr )
+{
+ bool haveMappings = false; // True if any ISO-XMP language mappings are found.
+
+ // Go through the ISO 'cprt' items and look for a corresponding XMP item. Ignore the ISO item if
+ // there is no language mapping to XMP. Update the ISO item if the mappable XMP exists, delete
+ // the ISO item if the mappable XMP does not exist. Since the import side would have made sure
+ // the mappable XMP items existed, if they don't now they must have been deleted.
+
+ MOOV_Manager::BoxInfo udtaInfo;
+ MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo );
+ if ( udtaRef == 0 ) return;
+
+ std::string xmpPath, xmpValue, xmpLang, tempStr;
+ char lang3 [4]; // An unpacked ISO-639-2/T language code.
+ lang3[3] = 0;
+
+ for ( XMP_Uns32 ordinal = udtaInfo.childCount; ordinal > 0; --ordinal ) { // ! Go backwards because of deletions.
+
+ MOOV_Manager::BoxInfo cprtInfo;
+ MOOV_Manager::BoxRef cprtRef = moovMgr->GetNthChild ( udtaRef, ordinal-1, &cprtInfo );
+ if ( (cprtRef == 0) ) break; // Sanity check, should not happen.
+ if ( (cprtInfo.boxType != ISOMedia::k_cprt) || (cprtInfo.contentSize < 6) ) continue;
+ if ( *cprtInfo.content != 0 ) continue; // Only accept version 0, ignore the flags.
+
+ XMP_Uns16 packedLang = GetUns16BE ( cprtInfo.content + 4 );
+ lang3[0] = (packedLang >> 10) + 0x60;
+ lang3[1] = ((packedLang >> 5) & 0x1F) + 0x60;
+ lang3[2] = (packedLang & 0x1F) + 0x60;
+
+ XMP_StringPtr lang2 = Lookup2LetterLang ( lang3 );
+ if ( *lang2 == 0 ) continue; // No language mapping to XMP.
+ haveMappings = true;
+
+ bool xmpFound = xmp.GetLocalizedText ( kXMP_NS_DC, "rights", lang2, lang2, &xmpLang, &xmpValue, 0 );
+ if ( xmpFound ) {
+ if ( (xmpLang.size() < 2) ||
+ ( (xmpLang.size() == 2) && (xmpLang != lang2) ) ||
+ ( (xmpLang.size() > 2) && ( (xmpLang[2] != '-') || (! XMP_LitNMatch ( xmpLang.c_str(), lang2, 2)) ) ) ) {
+ xmpFound = false; // The language does not match, the corresponding XMP does not exist.
+ }
+ }
- // Found the XMP, record the offset and size, read the packet.
+ if ( ! xmpFound ) {
- this->containsXMP = true;
- this->xmpBoxPos = outerPos;
- this->packetInfo.offset = outerPos + hSize + 16;
- this->packetInfo.length = (XMP_Int32) (cSize - 16);
+ // No XMP, delete the ISO item.
+ moovMgr->DeleteNthChild ( udtaRef, ordinal-1 );
- this->xmpPacket.reserve ( this->packetInfo.length );
- this->xmpPacket.assign ( this->packetInfo.length, ' ' );
- LFA_Read ( fileRef, (void*)this->xmpPacket.data(), this->packetInfo.length, kLFA_RequireAll );
+ } else {
+ // Update the ISO item if necessary.
+ XMP_StringPtr isoStr = (XMP_StringPtr)cprtInfo.content + 6;
+ size_t rawLen = cprtInfo.contentSize - 6;
+ if ( (rawLen >= 8) && (GetUns16BE(isoStr) == 0xFEFF) ) {
+ FromUTF16 ( (UTF16Unit*)(isoStr+2), (rawLen-2)/2, &tempStr, true /* big endian */ );
+ isoStr = tempStr.c_str();
+ }
+ if ( xmpValue != isoStr ) {
+ std::string newContent = "vfffll";
+ newContent += xmpValue;
+ memcpy ( (char*)newContent.c_str(), cprtInfo.content, 6 ); // Keep old version, flags, and language.
+ moovMgr->SetBox ( cprtRef, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) );
}
- } else if ( (! moovFound) && (boxType == kBE_moov) ) {
+ }
- // The middle loop loop looks for the 'moov'/'mvhd' and 'moov'/'udta' boxes.
+ }
- moovFound = true;
-
- XMP_Uns64 middleStart = outerPos + hSize;
- XMP_Uns64 middleEnd = outerPos + outerSize;
- XMP_Uns64 middlePos, middleSize;
+ // Go through the XMP items and look for a corresponding ISO item. Skip if found (did it above),
+ // otherwise add a new ISO item.
- bool mvhdFound = false, udtaFound = false;
+ bool haveXDefault = false;
+ XMP_Index xmpCount = xmp.CountArrayItems ( kXMP_NS_DC, "rights" );
- for ( middlePos = middleStart; (middlePos < middleEnd) && ((! mvhdFound) || (! udtaFound)); middlePos += middleSize ) {
+ for ( XMP_Index xmpIndex = 1; xmpIndex <= xmpCount; ++xmpIndex ) { // ! The first XMP array index is 1.
- if ( checkAbort && abortProc(abortArg) ) {
- XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
- }
+ SXMPUtils::ComposeArrayItemPath ( kXMP_NS_DC, "rights", xmpIndex, &xmpPath );
+ xmp.GetArrayItem ( kXMP_NS_DC, "rights", xmpIndex, &xmpValue, 0 );
+ bool hasLang = xmp.GetQualifier ( kXMP_NS_DC, xmpPath.c_str(), kXMP_NS_XML, "lang", &xmpLang, 0 );
+ if ( ! hasLang ) continue; // Sanity check.
+ if ( xmpLang == "x-default" ) {
+ haveXDefault = true; // See later special case.
+ continue;
+ }
- GetBoxInfo ( fileRef, fileSize, middlePos, &boxType, &hSize, &cSize );
- middleSize = hSize + cSize;
+ XMP_StringPtr isoLang = "";
+ size_t rootLen = xmpLang.find ( '-' );
+ if ( rootLen == std::string::npos ) rootLen = xmpLang.size();
+ if ( rootLen == 2 ) {
+ xmpLang[2] = 0;
+ isoLang = Lookup3LetterLang ( xmpLang.c_str() );
+ if ( *isoLang == 0 ) continue;
+ } else if ( rootLen == 3 ) {
+ xmpLang[3] = 0;
+ isoLang = xmpLang.c_str();
+ } else {
+ continue;
+ }
+ haveMappings = true;
- if ( (! mvhdFound) && (boxType == kBE_mvhd) ) {
+ bool isoFound = false;
+ XMP_Uns16 packedLang = ((isoLang[0] - 0x60) << 10) | ((isoLang[1] - 0x60) << 5) | (isoLang[2] - 0x60);
- // Save the entire 'moov'/'mvhd' box content, it isn't very big.
- mvhdFound = true;
- this->mvhdBox.reserve ( (size_t)cSize );
- this->mvhdBox.assign ( (size_t)cSize, ' ' );
- LFA_Read ( fileRef, (void*)this->mvhdBox.data(), (XMP_Int32)cSize, kLFA_RequireAll );
+ for ( XMP_Uns32 isoIndex = 0; (isoIndex < udtaInfo.childCount) && (! isoFound); ++isoIndex ) {
- } else if ( (! udtaFound) && (boxType == kBE_udta) ) {
+ MOOV_Manager::BoxInfo cprtInfo;
+ MOOV_Manager::BoxRef cprtRef = moovMgr->GetNthChild ( udtaRef, isoIndex, &cprtInfo );
+ if ( (cprtRef == 0) ) break; // Sanity check, should not happen.
+ if ( (cprtInfo.boxType != ISOMedia::k_cprt) || (cprtInfo.contentSize < 6) ) continue;
+ if ( *cprtInfo.content != 0 ) continue; // Only accept version 0, ignore the flags.
+ if ( packedLang != GetUns16BE ( cprtInfo.content + 4 ) ) continue; // Look for matching language.
- // The inner loop looks for the 'moov'/'udta'/'cprt' boxes.
-
- udtaFound = true;
- XMP_Uns64 innerStart = middlePos + hSize;
- XMP_Uns64 innerEnd = middlePos + middleSize;
- XMP_Uns64 innerPos, innerSize;
+ isoFound = true; // Found the language entry, whether or not we update it.
- for ( innerPos = innerStart; innerPos < innerEnd; innerPos += innerSize ) {
-
- if ( checkAbort && abortProc(abortArg) ) {
- XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
- }
+ }
+
+ if ( ! isoFound ) {
+
+ std::string newContent = "vfffll";
+ newContent += xmpValue;
+ *((XMP_Uns32*)newContent.c_str()) = 0; // Set the version and flags to zero.
+ PutUns16BE ( packedLang, (char*)newContent.c_str() + 4 );
+ moovMgr->AddChildBox ( udtaRef, ISOMedia::k_cprt, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) );
+
+ }
+
+ }
+
+ // If there were no mappings in the loops, export the XMP "x-default" value to the first ISO item.
+
+ if ( ! haveMappings ) {
+
+ MOOV_Manager::BoxInfo cprtInfo;
+ MOOV_Manager::BoxRef cprtRef = moovMgr->GetTypeChild ( udtaRef, ISOMedia::k_cprt, &cprtInfo );
- GetBoxInfo ( fileRef, fileSize, innerPos, &boxType, &hSize, &cSize );
- innerSize = hSize + cSize;
- if ( boxType != kBE_cprt ) continue;
+ if ( (cprtRef != 0) && (cprtInfo.contentSize >= 6) && (*cprtInfo.content == 0) ) {
- // ! Actually capturing structured data - the 'cprt' box content.
- this->cprtBoxes.push_back ( std::string() );
- std::string & newCprt = this->cprtBoxes.back();
- newCprt.reserve ( (size_t)cSize );
- newCprt.assign ( (size_t)cSize, ' ' );
- LFA_Read ( fileRef, (void*)newCprt.data(), (XMP_Int32)cSize, kLFA_RequireAll );
+ bool xmpFound = xmp.GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", &xmpLang, &xmpValue, 0 );
+
+ if ( xmpFound && (xmpLang == "x-default") ) {
+
+ // Update the ISO item if necessary.
+ XMP_StringPtr isoStr = (XMP_StringPtr)cprtInfo.content + 6;
+ size_t rawLen = cprtInfo.contentSize - 6;
+ if ( (rawLen >= 8) && (GetUns16BE(isoStr) == 0xFEFF) ) {
+ FromUTF16 ( (UTF16Unit*)(isoStr+2), (rawLen-2)/2, &tempStr, true /* big endian */ );
+ isoStr = tempStr.c_str();
+ }
+ if ( xmpValue != isoStr ) {
+ std::string newContent = "vfffll";
+ newContent += xmpValue;
+ memcpy ( (char*)newContent.c_str(), cprtInfo.content, 6 ); // Keep old version, flags, and language.
+ moovMgr->SetBox ( cprtRef, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) );
+ }
+
+ }
+
+ }
- } // inner loop
+ }
- } // 'moov'/'udta' box
+} // ExportISOCopyrights
- } // middle loop
+// =================================================================================================
+// ExportQuickTimeItems
+// ====================
- } // 'moov' box
+static void ExportQuickTimeItems ( const SXMPMeta & xmp, TradQT_Manager * qtMgr, MOOV_Manager * moovMgr )
+{
- } // outer loop
+ // The QuickTime 'udta' timecode items are done here for simplicity.
+
+ #define createWithZeroLang true
-} // MPEG4_MetaHandler::CacheFileData
+ qtMgr->ExportSimpleXMP ( kQTilst_Reel, xmp, kXMP_NS_DM, "tapeName" );
+ qtMgr->ExportSimpleXMP ( kQTilst_Timecode, xmp, kXMP_NS_DM, "startTimecode/xmpDM:timeValue", createWithZeroLang );
+ qtMgr->ExportSimpleXMP ( kQTilst_TimeScale, xmp, kXMP_NS_DM, "startTimeScale", createWithZeroLang );
+ qtMgr->ExportSimpleXMP ( kQTilst_TimeSize, xmp, kXMP_NS_DM, "startTimeSampleSize", createWithZeroLang );
+
+ qtMgr->UpdateChangedBoxes ( moovMgr );
+
+} // ExportQuickTimeItems
// =================================================================================================
-// MPEG4_MetaHandler::MakeLegacyDigest
-// ===================================
+// SelectTimeFormat
+// ================
-// *** Will need updating if we process the 'ilst' metadata.
+static const char * SelectTimeFormat ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo )
+{
+ const char * timeFormat = 0;
+
+ float fltFPS = (float)tmcdInfo.timeScale / (float)tmcdInfo.frameDuration;
+ int intFPS = (int) (fltFPS + 0.5);
-#define kHexDigits "0123456789ABCDEF"
+ switch ( intFPS ) {
-void MPEG4_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+ case 30:
+ if ( fltFPS >= 29.985 ) {
+ timeFormat = "30Timecode";
+ } else if ( tmcdInfo.isDropFrame ) {
+ timeFormat = "2997DropTimecode";
+ } else {
+ timeFormat = "2997NonDropTimecode";
+ }
+ break;
+
+ case 24:
+ if ( fltFPS >= 23.988 ) {
+ timeFormat = "24Timecode";
+ } else {
+ timeFormat = "23976Timecode";
+ }
+ break;
+
+ case 25:
+ timeFormat = "25Timecode";
+ break;
+
+ case 50:
+ timeFormat = "50Timecode";
+ break;
+
+ case 60:
+ if ( fltFPS >= 59.97 ) {
+ timeFormat = "60Timecode";
+ } else if ( tmcdInfo.isDropFrame ) {
+ timeFormat = "5994DropTimecode";
+ } else {
+ timeFormat = "5994NonDropTimecode";
+ }
+ break;
+
+ }
+
+ return timeFormat;
+
+} // SelectTimeFormat
+
+// =================================================================================================
+// SelectTimeFormat
+// ================
+
+static const char * SelectTimeFormat ( const SXMPMeta & xmp )
{
- MD5_CTX context;
- unsigned char digestBin [16];
- MD5Init ( &context );
+ bool ok;
+ MPEG4_MetaHandler::TimecodeTrackInfo tmcdInfo;
+
+ XMP_Int64 timeScale;
+ ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", &timeScale, 0 );
+ if ( ! ok ) return 0;
+ tmcdInfo.timeScale = (XMP_Uns32)timeScale;
+
+ XMP_Int64 frameDuration;
+ ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", &frameDuration, 0 );
+ if ( ! ok ) return 0;
+ tmcdInfo.frameDuration = (XMP_Uns32)frameDuration;
+
+ std::string timecode;
+ ok = xmp.GetProperty ( kXMP_NS_DM, "startTimecode/xmpDM:timeValue", &timecode, 0 );
+ if ( ! ok ) return 0;
+ if ( (timecode.size() == 11) && (timecode[8] == ';') ) tmcdInfo.isDropFrame = true;
+
+ return SelectTimeFormat ( tmcdInfo );
+
+} // SelectTimeFormat
+
+// =================================================================================================
+// ComposeTimecode
+// ===============
+
+static const char * kDecDigits = "0123456789";
+#define TwoDigits(val,str) (str)[0] = kDecDigits[(val)/10]; (str)[1] = kDecDigits[(val)%10]
+
+static bool ComposeTimecode ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo, std::string * strValue )
+{
+ float fltFPS = (float)tmcdInfo.timeScale / (float)tmcdInfo.frameDuration;
+ int intFPS = (int) (fltFPS + 0.5);
+ if ( (intFPS != 30) && (intFPS != 24) && (intFPS != 25) && (intFPS != 50) && (intFPS != 60) ) return false;
+
+ XMP_Uns32 framesPerDay = intFPS * 24*60*60;
+ XMP_Uns32 dropLimit = 2; // Used in the drop-frame correction.
+
+ if ( tmcdInfo.isDropFrame ) {
+ if ( intFPS == 30 ) {
+ framesPerDay = 2589408; // = 29.97 * 24*60*60
+ } else if ( intFPS == 60 ) {
+ framesPerDay = 5178816; // = 59.94 * 24*60*60
+ dropLimit = 4;
+ } else {
+ strValue->erase();
+ return false; // Dropframe can only apply to 29.97 and 59.94.
+ }
+ }
+
+ XMP_Uns32 framesPerHour = framesPerDay / 24;
+ XMP_Uns32 framesPerTenMinutes = framesPerHour / 6;
+ XMP_Uns32 framesPerMinute = framesPerTenMinutes / 10;
+
+ XMP_Uns32 frameCount = tmcdInfo.timecodeSample;
+ while (frameCount >= framesPerDay ) frameCount -= framesPerDay; // Normalize to be within 24 hours.
+
+ XMP_Uns32 hours, minHigh, minLow, seconds;
+
+ hours = frameCount / framesPerHour;
+ frameCount -= (hours * framesPerHour);
+
+ minHigh = frameCount / framesPerTenMinutes;
+ frameCount -= (minHigh * framesPerTenMinutes);
+
+ minLow = frameCount / framesPerMinute;
+ frameCount -= (minLow * framesPerMinute);
+
+ // Do some drop-frame corrections at this point: If this is drop-frame and the units of minutes
+ // is non-zero, and the seconds are zero, and the frames are zero or one, the time is illegal.
+ // Perform correction by subtracting 1 from the units of minutes and adding 1798 to the frames.Ê
+ // For example, 1:00:00 becomes 59:28, and 1:00:01 becomes 59:29. A special case can occur for
+ // when the frameCount just before the minHigh calculation is less than framesPerTenMinutes but
+ // more than 10*framesPerMinute. This happens because of roundoff, and will result in a minHigh
+ // of 0 and a minLow of 10.ÊThe drop frame correction mustÊalso be performed for this case.
+
+ if ( tmcdInfo.isDropFrame ) {
+ if ( (minLow == 10) || ((minLow != 0) && (frameCount < dropLimit)) ) {
+ minLow -= 1;
+ frameCount += framesPerMinute;
+ }
+ }
+
+ seconds = frameCount / intFPS;
+ frameCount -= (seconds * intFPS);
+
+ if ( tmcdInfo.isDropFrame ) {
+ *strValue = "hh;mm;ss;ff";
+ } else {
+ *strValue = "hh:mm:ss:ff";
+ }
+
+ char * str = (char*)strValue->c_str();
+ TwoDigits ( hours, str );
+ str[3] = kDecDigits[minHigh]; str[4] = kDecDigits[minLow];
+ TwoDigits ( seconds, str+6 );
+ TwoDigits ( frameCount, str+9 );
+
+ return true;
+
+} // ComposeTimecode
+
+// =================================================================================================
+// DecomposeTimecode
+// =================
+
+static bool DecomposeTimecode ( const char * strValue, MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo )
+{
+ float fltFPS = (float)tmcdInfo->timeScale / (float)tmcdInfo->frameDuration;
+ int intFPS = (int) (fltFPS + 0.5);
+ if ( (intFPS != 30) && (intFPS != 24) && (intFPS != 25) && (intFPS != 50) && (intFPS != 60) ) return false;
+
+ XMP_Uns32 framesPerDay = intFPS * 24*60*60;
+
+ int items, hours, minutes, seconds, frames;
+
+ if ( ! tmcdInfo->isDropFrame ) {
+ items = sscanf ( strValue, "%d:%d:%d:%d", &hours, &minutes, &seconds, &frames );
+ } else {
+ items = sscanf ( strValue, "%d;%d;%d;%d", &hours, &minutes, &seconds, &frames );
+ if ( intFPS == 30 ) {
+ framesPerDay = 2589408; // = 29.97 * 24*60*60
+ } else if ( intFPS == 60 ) {
+ framesPerDay = 5178816; // = 59.94 * 24*60*60
+ } else {
+ return false; // Dropframe can only apply to 29.97 and 59.94.
+ }
+ }
- MD5Update ( &context, (XMP_Uns8*)this->mvhdBox.c_str(), (unsigned int) this->mvhdBox.size() );
+ if ( items != 4 ) return false;
+ int minHigh = minutes / 10;
+ int minLow = minutes % 10;
+
+ XMP_Uns32 framesPerHour = framesPerDay / 24;
+ XMP_Uns32 framesPerTenMinutes = framesPerHour / 6;
+ XMP_Uns32 framesPerMinute = framesPerTenMinutes / 10;
+
+ tmcdInfo->timecodeSample = (hours * framesPerHour) + (minHigh * framesPerTenMinutes) +
+ (minLow * framesPerMinute) + (seconds * intFPS) + frames;
+
+ return true;
+
+} // DecomposeTimecode
+
+// =================================================================================================
+// FindTimecodeTrack
+// =================
+//
+// Look for a well-formed timecode track, return the .../mdia/minf/stbl box ref.
+
+static MOOV_Manager::BoxRef FindTimecodeTrack ( const MOOV_Manager & moovMgr )
+{
+
+ // Find a 'trak' box with a handler type of 'tmcd'.
- for ( size_t i = 0, limit = this->cprtBoxes.size(); i < limit; ++i ) {
- const std::string & currCprt = this->cprtBoxes[i];
- MD5Update ( &context, (XMP_Uns8*)currCprt.c_str(), (unsigned int) currCprt.size() );
+ MOOV_Manager::BoxInfo moovInfo;
+ MOOV_Manager::BoxRef moovRef = moovMgr.GetBox ( "moov", &moovInfo );
+ XMP_Assert ( moovRef != 0 );
+
+ MOOV_Manager::BoxInfo trakInfo;
+ MOOV_Manager::BoxRef trakRef;
+
+ size_t i = 0;
+ for ( ; i < moovInfo.childCount; ++i ) {
+
+ trakRef = moovMgr.GetNthChild ( moovRef, i, &trakInfo );
+ if ( trakRef == 0 ) return 0; // Sanity check, should not happen.
+ if ( trakInfo.boxType != ISOMedia::k_trak ) continue;
+
+ MOOV_Manager::BoxRef innerRef;
+ MOOV_Manager::BoxInfo innerInfo;
+
+ innerRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &innerInfo );
+ if ( innerRef == 0 ) continue;
+
+ innerRef = moovMgr.GetTypeChild ( innerRef, ISOMedia::k_hdlr, &innerInfo );
+ if ( (innerRef == 0) || (innerInfo.contentSize < sizeof ( MOOV_Manager::Content_hdlr )) ) continue;
+
+ const MOOV_Manager::Content_hdlr * hdlr = (MOOV_Manager::Content_hdlr*) innerInfo.content;
+ if ( hdlr->versionFlags != 0 ) continue;
+ if ( GetUns32BE ( &hdlr->handlerType ) == ISOMedia::k_tmcd ) break;
+
+ }
+ if ( i == moovInfo.childCount ) return 0;
+
+ // Find the .../mdia/minf/stbl box.
+
+ MOOV_Manager::BoxInfo tempInfo;
+ MOOV_Manager::BoxRef tempRef, stblRef;
+
+ tempRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &tempInfo );
+ if ( tempRef == 0 ) return 0;
+
+ tempRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_minf, &tempInfo );
+ if ( tempRef == 0 ) return 0;
+
+ stblRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stbl, &tempInfo );
+ return stblRef;
+
+} // FindTimecodeTrack
+
+// =================================================================================================
+// ImportTimecodeItems
+// ===================
+
+static bool ImportTimecodeItems ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo,
+ const TradQT_Manager & qtInfo, SXMPMeta * xmp )
+{
+ std::string xmpValue;
+ bool haveItem;
+ bool haveImports = false;
+
+ // The QT user data item '©REL' goes into xmpDM:tapeName, and the 'name' box at the end of the
+ // timecode sample description goes into xmpDM:altTapeName.
+ haveImports |= qtInfo.ImportSimpleXMP ( kQTilst_Reel, xmp, kXMP_NS_DM, "tapeName" );
+ if ( ! tmcdInfo.macName.empty() ) {
+ haveItem = ConvertFromMacLang ( tmcdInfo.macName, tmcdInfo.macLang, &xmpValue );
+ if ( haveItem ) {
+ xmp->SetProperty ( kXMP_NS_DM, "altTapeName", xmpValue.c_str() );
+ haveImports = true;
+ }
+ }
+
+ // The QT user data item '©TSC' goes into xmpDM:startTimeScale. If that isn't present, then
+ // the timecode sample description's timeScale is used.
+ haveItem = qtInfo.ImportSimpleXMP ( kQTilst_TimeScale, xmp, kXMP_NS_DM, "startTimeScale" );
+ if ( tmcdInfo.stsdBoxFound & (! haveItem) ) {
+ xmp->SetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", tmcdInfo.timeScale );
+ haveItem = true;
+ }
+ haveImports |= haveItem;
+
+ // The QT user data item '©TSZ' goes into xmpDM:startTimeSampleSize. If that isn't present, then
+ // the timecode sample description's frameDuration is used.
+ haveItem = qtInfo.ImportSimpleXMP ( kQTilst_TimeSize, xmp, kXMP_NS_DM, "startTimeSampleSize" );
+ if ( tmcdInfo.stsdBoxFound & (! haveItem) ) {
+ xmp->SetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", tmcdInfo.frameDuration );
+ haveItem = true;
}
+ haveImports |= haveItem;
- MD5Final ( digestBin, &context );
+ const char * timeFormat;
+
+ // The Timecode struct type is used for xmpDM:startTimecode and xmpDM:altTimecode. For both, only
+ // the xmpDM:timeValue and xmpDM:timeFormat fields are set.
+
+ // The QT user data item '©TIM' goes into xmpDM:startTimecode/xmpDM:timeValue. This is an already
+ // formatted timecode string. The XMP values of xmpDM:startTimeScale, xmpDM:startTimeSampleSize,
+ // and xmpDM:startTimecode/xmpDM:timeValue are used to select the timeFormat value.
+ haveImports |= qtInfo.ImportSimpleXMP ( kQTilst_Timecode, xmp, kXMP_NS_DM, "startTimecode/xmpDM:timeValue" );
+ timeFormat = SelectTimeFormat ( *xmp );
+ if ( timeFormat != 0 ) {
+ xmp->SetProperty ( kXMP_NS_DM, "startTimecode/xmpDM:timeFormat", timeFormat );
+ haveImports = true;
+ }
+
+ if ( tmcdInfo.stsdBoxFound ) {
+
+ haveItem = ComposeTimecode ( tmcdInfo, &xmpValue );
+ if ( haveItem ) {
+ xmp->SetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeValue", xmpValue.c_str() );
+ haveImports = true;
+ }
+
+ timeFormat = SelectTimeFormat ( tmcdInfo );
+ if ( timeFormat != 0 ) {
+ xmp->SetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeFormat", timeFormat );
+ haveImports = true;
+ }
- char buffer [40];
- for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
- XMP_Uns8 byte = digestBin[in];
- buffer[out] = kHexDigits [ byte >> 4 ];
- buffer[out+1] = kHexDigits [ byte & 0xF ];
}
- buffer[32] = 0;
- digestStr->erase();
- digestStr->append ( buffer, 32 );
-} // MPEG4_MetaHandler::MakeLegacyDigest
+ return haveImports;
+
+} // ImportTimecodeItems
// =================================================================================================
+// ExportTimecodeItems
+// ===================
+
+static void ExportTimecodeItems ( const SXMPMeta & xmp, MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo,
+ TradQT_Manager * qtMgr, MOOV_Manager * moovMgr )
+{
+ // Export the items that go into the timecode track:
+ // - the timescale and frame duration in the first 'stsd' table entry
+ // - the 'name' box appended to the first 'stsd' table entry
+ // - the first timecode sample
+ // ! The QuickTime 'udta' timecode items are handled in ExportQuickTimeItems.
+
+ if ( ! tmcdInfo->stsdBoxFound ) return; // Don't make changes unless there is a well-formed timecode track.
+
+ MOOV_Manager::BoxRef stblRef = FindTimecodeTrack ( *moovMgr );
+ if ( stblRef == 0 ) return;
+
+ MOOV_Manager::BoxInfo stsdInfo;
+ MOOV_Manager::BoxRef stsdRef;
+
+ stsdRef = moovMgr->GetTypeChild ( stblRef, ISOMedia::k_stsd, &stsdInfo );
+ if ( stsdRef == 0 ) return;
+ if ( stsdInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsd_entry )) ) return;
+ if ( GetUns32BE ( stsdInfo.content + 4 ) == 0 ) return; // Make sure the entry count is non-zero.
+
+ const MOOV_Manager::Content_stsd_entry * stsdRawEntry = (MOOV_Manager::Content_stsd_entry*) (stsdInfo.content + 8);
+
+ XMP_Uns32 stsdEntrySize = GetUns32BE ( &stsdRawEntry->entrySize );
+ if ( stsdEntrySize > (stsdInfo.contentSize - 4) ) stsdEntrySize = stsdInfo.contentSize - 4;
+ if ( stsdEntrySize < sizeof ( MOOV_Manager::Content_stsd_entry ) ) return;
+
+ bool ok;
+ std::string xmpValue;
+ XMP_Int64 int64; // Used to allow UInt32 values, GetProperty_Int is SInt32.
+
+ // The tmcdInfo timeScale field is set from xmpDM:startTimeScale.
+ ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", &int64, 0 );
+ if ( ok && (int64 <= 0xFFFFFFFF) && ((XMP_Uns32)int64 != tmcdInfo->timeScale) ) {
+ tmcdInfo->timeScale = (XMP_Uns32)int64;
+ PutUns32BE ( tmcdInfo->timeScale, (void*)&stsdRawEntry->timeScale );
+ moovMgr->NoteChange();
+ }
+
+ // The tmcdInfo frameDuration field is set from xmpDM:startTimeSampleSize.
+ ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", &int64, 0 );
+ if ( ok && (int64 <= 0xFFFFFFFF) && ((XMP_Uns32)int64 != tmcdInfo->frameDuration) ) {
+ tmcdInfo->frameDuration = (XMP_Uns32)int64;
+ PutUns32BE ( tmcdInfo->frameDuration, (void*)&stsdRawEntry->frameDuration );
+ moovMgr->NoteChange();
+ }
+
+ // The tmcdInfo isDropFrame flag is set from xmpDM:altTimecode/xmpDM:timeValue. The timeScale
+ // and frameDuration must be updated first, they are used by DecomposeTimecode. Compute the new
+ // UInt32 timecode sample, but it gets written to the file later by UpdateFile.
+
+ ok = xmp.GetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeValue", &xmpValue, 0 );
+ if ( ok && (xmpValue.size() == 11) ) {
+
+ bool oldDropFrame = tmcdInfo->isDropFrame;
+ tmcdInfo->isDropFrame = false;
+ if ( xmpValue[8] == ';' ) tmcdInfo->isDropFrame = true;
+ if ( oldDropFrame != tmcdInfo->isDropFrame ) {
+ XMP_Uns32 flags = GetUns32BE ( &stsdRawEntry->flags );
+ flags = (flags & 0xFFFFFFFE) | (XMP_Uns32)tmcdInfo->isDropFrame;
+ PutUns32BE ( flags, (void*)&stsdRawEntry->flags );
+ moovMgr->NoteChange();
+ }
+
+ XMP_Uns32 oldSample = tmcdInfo->timecodeSample;
+ ok = DecomposeTimecode ( xmpValue.c_str(), tmcdInfo );
+ if ( ok && (oldSample != tmcdInfo->timecodeSample) ) moovMgr->NoteChange();
-struct MVHD_v1 { // v1 v0 - offsets within the content portion of the 'mvhd' box
- XMP_Uns8 version; // 0 0
- XMP_Uns8 flags [3]; // 1 1
- XMP_Uns64 creationTime; // 4 4 - Uns32 in v0
- XMP_Uns64 modificationTime; // 12 8 - Uns32 in v0
- XMP_Uns32 timescale; // 20 12
- XMP_Uns64 duration; // 24 16 - Uns32 in v0
- XMP_Int32 rate; // 32 20
- XMP_Int16 volume; // 36 24
- XMP_Uns16 pad_1; // 38 26
- XMP_Uns32 pad_2, pad_3; // 40 28
- XMP_Int32 matrix [9]; // 48 36
- XMP_Uns32 preDef [6]; // 84 72
- XMP_Uns32 nextTrackID; // 108 96
-}; // 112 100
+ }
+
+ // The 'name' box attached to the first 'stsd' table entry is set from xmpDM:altTapeName.
+
+ bool replaceNameBox = false;
+
+ ok = xmp.GetProperty ( kXMP_NS_DM, "altTapeName", &xmpValue, 0 );
+ if ( (! ok) || xmpValue.empty() ) {
+ if ( tmcdInfo->nameOffset != 0 ) replaceNameBox = true; // No XMP, get rid of existing name.
+ } else {
+ std::string macValue;
+ ok = ConvertToMacLang ( xmpValue, tmcdInfo->macLang, &macValue );
+ if ( ok && (macValue != tmcdInfo->macName) ) {
+ tmcdInfo->macName = macValue;
+ replaceNameBox = true; // Write changed name.
+ }
+ }
+
+ if ( replaceNameBox ) {
+
+ // To replace the 'name' box we have to create an entire new 'stsd' box, and attach the
+ // new name to the first 'stsd' table entry. The 'name' box content is a UInt16 text length,
+ // UInt16 language code, and Mac encoded text with no nul termination.
+
+ if ( tmcdInfo->macName.size() > 0xFFFF ) tmcdInfo->macName.erase ( 0xFFFF );
+
+ ISOMedia::BoxInfo oldNameInfo;
+ XMP_Assert ( (oldNameInfo.headerSize == 0) && (oldNameInfo.contentSize == 0) );
+ if ( tmcdInfo->nameOffset != 0 ) {
+ const XMP_Uns8 * oldNamePtr = stsdInfo.content + tmcdInfo->nameOffset;
+ const XMP_Uns8 * oldNameLimit = stsdInfo.content + stsdInfo.contentSize;
+ (void) ISOMedia::GetBoxInfo ( oldNamePtr, oldNameLimit, &oldNameInfo );
+ }
+
+ XMP_Uns32 oldNameBoxSize = (XMP_Uns32)oldNameInfo.headerSize + (XMP_Uns32)oldNameInfo.contentSize;
+ XMP_Uns32 newNameBoxSize = 0;
+ if ( ! tmcdInfo->macName.empty() ) newNameBoxSize = 4+4 + 2+2 + (XMP_Uns32)tmcdInfo->macName.size();
+
+ XMP_Uns32 stsdNewContentSize = stsdInfo.contentSize - oldNameBoxSize + newNameBoxSize;
+ RawDataBlock stsdNewContent;
+ stsdNewContent.assign ( stsdNewContentSize, 0 ); // Get the space allocated, direct fill below.
+
+ XMP_Uns32 stsdPrefixSize = tmcdInfo->nameOffset;
+ if ( tmcdInfo->nameOffset == 0 ) stsdPrefixSize = 4+4 + sizeof ( MOOV_Manager::Content_stsd_entry );
+
+ XMP_Uns32 oldSuffixOffset = stsdPrefixSize + oldNameBoxSize;
+ XMP_Uns32 newSuffixOffset = stsdPrefixSize + newNameBoxSize;
+ XMP_Uns32 stsdSuffixSize = stsdInfo.contentSize - oldSuffixOffset;
+
+ memcpy ( &stsdNewContent[0], stsdInfo.content, stsdPrefixSize );
+ if ( stsdSuffixSize != 0 ) memcpy ( &stsdNewContent[newSuffixOffset], (stsdInfo.content + oldSuffixOffset), stsdSuffixSize );
+
+ XMP_Uns32 newEntrySize = stsdEntrySize - oldNameBoxSize + newNameBoxSize;
+ MOOV_Manager::Content_stsd_entry * stsdNewEntry = (MOOV_Manager::Content_stsd_entry*) (&stsdNewContent[0] + 8);
+ PutUns32BE ( newEntrySize, &stsdNewEntry->entrySize );
+
+ if ( newNameBoxSize != 0 ) {
+ PutUns32BE ( newNameBoxSize, &stsdNewContent[stsdPrefixSize] );
+ PutUns32BE ( ISOMedia::k_name, &stsdNewContent[stsdPrefixSize+4] );
+ PutUns16BE ( (XMP_Uns16)tmcdInfo->macName.size(), &stsdNewContent[stsdPrefixSize+8] );
+ PutUns16BE ( tmcdInfo->macLang, &stsdNewContent[stsdPrefixSize+10] );
+ memcpy ( &stsdNewContent[stsdPrefixSize+12], tmcdInfo->macName.c_str(), tmcdInfo->macName.size() );
+ }
+
+ moovMgr->SetBox ( stsdRef, &stsdNewContent[0], stsdNewContentSize );
+
+ }
-static void ExtractMVHD_v0 ( XMP_Uns8 * buffer, MVHD_v1 * mvhd ) // Always convert to the v1 form.
+} // ExportTimecodeItems
+
+// =================================================================================================
+// ImportCr8rItems
+// ===============
+
+#pragma pack ( push, 1 )
+
+struct PrmLBoxContent {
+ XMP_Uns32 magic;
+ XMP_Uns32 size;
+ XMP_Uns16 verAPI;
+ XMP_Uns16 verCode;
+ XMP_Uns32 exportType;
+ XMP_Uns16 MacVRefNum;
+ XMP_Uns32 MacParID;
+ char filePath[260];
+};
+
+enum { kExportTypeMovie = 0, kExportTypeStill = 1, kExportTypeAudio = 2, kExportTypeCustom = 3 };
+
+struct Cr8rBoxContent {
+ XMP_Uns32 magic;
+ XMP_Uns32 size;
+ XMP_Uns16 majorVer;
+ XMP_Uns16 minorVer;
+ XMP_Uns32 creatorCode;
+ XMP_Uns32 appleEvent;
+ char fileExt[16];
+ char appOptions[16];
+ char appName[32];
+};
+
+#pragma pack ( pop )
+
+// -------------------------------------------------------------------------------------------------
+
+static bool ImportCr8rItems ( const MOOV_Manager & moovMgr, SXMPMeta * xmp )
{
- mvhd->version = buffer[0];
- mvhd->flags[0] = buffer[1];
- mvhd->flags[1] = buffer[2];
- mvhd->flags[2] = buffer[3];
- mvhd->creationTime = GetUns32BE ( &buffer[ 4] );
- mvhd->modificationTime = GetUns32BE ( &buffer[ 8] );
- mvhd->timescale = GetUns32BE ( &buffer[12] );
- mvhd->duration = GetUns32BE ( &buffer[16] );
- mvhd->rate = GetUns32BE ( &buffer[20] );
- mvhd->volume = GetUns16BE ( &buffer[24] );
- mvhd->pad_1 = GetUns16BE ( &buffer[26] );
- mvhd->pad_2 = GetUns32BE ( &buffer[28] );
- mvhd->pad_3 = GetUns32BE ( &buffer[32] );
- for ( int i = 0, j = 36; i < 9; ++i, j += 4 ) mvhd->matrix[i] = GetUns32BE ( &buffer[j] );
- for ( int i = 0, j = 72; i < 6; ++i, j += 4 ) mvhd->preDef[i] = GetUns32BE ( &buffer[j] );
- mvhd->nextTrackID = GetUns32BE ( &buffer[96] );
-}
+ bool haveXMP = false;
+
+ MOOV_Manager::BoxInfo infoPrmL, infoCr8r;
+ MOOV_Manager::BoxRef refPrmL = moovMgr.GetBox ( "moov/udta/PrmL", &infoPrmL );
+ MOOV_Manager::BoxRef refCr8r = moovMgr.GetBox ( "moov/udta/Cr8r", &infoCr8r );
+
+ bool havePrmL = ( (refPrmL != 0) && (infoPrmL.contentSize == sizeof ( PrmLBoxContent )) );
+ bool haveCr8r = ( (refCr8r != 0) && (infoCr8r.contentSize == sizeof ( Cr8rBoxContent )) );
+
+ if ( havePrmL ) {
+
+ PrmLBoxContent rawPrmL;
+ XMP_Assert ( sizeof ( rawPrmL ) == 282 );
+ XMP_Assert ( sizeof ( rawPrmL.filePath ) == 260 );
+ memcpy ( &rawPrmL, infoPrmL.content, sizeof ( rawPrmL ) );
+ if ( rawPrmL.magic != 0xBEEFCAFE ) {
+ Flip4 ( &rawPrmL.exportType ); // The only numeric field that we care about.
+ }
-static void ExtractMVHD_v1 ( XMP_Uns8 * buffer, MVHD_v1 * mvhd )
-{
- mvhd->version = buffer[0];
- mvhd->flags[0] = buffer[1];
- mvhd->flags[1] = buffer[2];
- mvhd->flags[2] = buffer[3];
- mvhd->creationTime = GetUns64BE ( &buffer[ 4] );
- mvhd->modificationTime = GetUns64BE ( &buffer[12] );
- mvhd->timescale = GetUns32BE ( &buffer[20] );
- mvhd->duration = GetUns64BE ( &buffer[24] );
- mvhd->rate = GetUns32BE ( &buffer[32] );
- mvhd->volume = GetUns16BE ( &buffer[36] );
- mvhd->pad_1 = GetUns16BE ( &buffer[38] );
- mvhd->pad_2 = GetUns32BE ( &buffer[40] );
- mvhd->pad_3 = GetUns32BE ( &buffer[44] );
- for ( int i = 0, j = 48; i < 9; ++i, j += 4 ) mvhd->matrix[i] = GetUns32BE ( &buffer[j] );
- for ( int i = 0, j = 84; i < 6; ++i, j += 4 ) mvhd->preDef[i] = GetUns32BE ( &buffer[j] );
- mvhd->nextTrackID = GetUns32BE ( &buffer[108] );
-}
+ rawPrmL.filePath[259] = 0; // Ensure a terminating nul.
+ if ( rawPrmL.filePath[0] != 0 ) {
+ if ( rawPrmL.filePath[0] == '/' ) {
+ haveXMP = true;
+ xmp->SetStructField ( kXMP_NS_CreatorAtom, "macAtom",
+ kXMP_NS_CreatorAtom, "posixProjectPath", rawPrmL.filePath );
+ } else if ( XMP_LitNMatch ( rawPrmL.filePath, "\\\\?\\", 4 ) ) {
+ haveXMP = true;
+ xmp->SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom",
+ kXMP_NS_CreatorAtom, "uncProjectPath", rawPrmL.filePath );
+ }
+ }
+
+ const char * exportStr = 0;
+ switch ( rawPrmL.exportType ) {
+ case kExportTypeMovie : exportStr = "movie"; break;
+ case kExportTypeStill : exportStr = "still"; break;
+ case kExportTypeAudio : exportStr = "audio"; break;
+ case kExportTypeCustom : exportStr = "custom"; break;
+ }
+ if ( exportStr != 0 ) {
+ haveXMP = true;
+ xmp->SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", exportStr );
+ }
+
+ }
+
+ if ( haveCr8r ) {
+
+ Cr8rBoxContent rawCr8r;
+ XMP_Assert ( sizeof ( rawCr8r ) == 84 );
+ XMP_Assert ( sizeof ( rawCr8r.fileExt ) == 16 );
+ XMP_Assert ( sizeof ( rawCr8r.appOptions ) == 16 );
+ XMP_Assert ( sizeof ( rawCr8r.appName ) == 32 );
+ memcpy ( &rawCr8r, infoCr8r.content, sizeof ( rawCr8r ) );
+ if ( rawCr8r.magic != 0xBEEFCAFE ) {
+ Flip4 ( &rawCr8r.creatorCode ); // The only numeric fields that we care about.
+ Flip4 ( &rawCr8r.appleEvent );
+ }
+
+ std::string fieldPath;
+
+ SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &fieldPath );
+ if ( rawCr8r.creatorCode != 0 ) {
+ haveXMP = true;
+ xmp->SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.creatorCode ); // ! Unsigned trickery.
+ }
+
+ SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &fieldPath );
+ if ( rawCr8r.appleEvent != 0 ) {
+ haveXMP = true;
+ xmp->SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.appleEvent ); // ! Unsigned trickery.
+ }
+
+ rawCr8r.fileExt[15] = 0; // Ensure a terminating nul.
+ if ( rawCr8r.fileExt[0] != 0 ) {
+ haveXMP = true;
+ xmp->SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", rawCr8r.fileExt );
+ }
+
+ rawCr8r.appOptions[15] = 0; // Ensure a terminating nul.
+ if ( rawCr8r.appOptions[0] != 0 ) {
+ haveXMP = true;
+ xmp->SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", rawCr8r.appOptions );
+ }
+
+ rawCr8r.appName[31] = 0; // Ensure a terminating nul.
+ if ( rawCr8r.appName[0] != 0 ) {
+ haveXMP = true;
+ xmp->SetProperty ( kXMP_NS_XMP, "CreatorTool", rawCr8r.appName );
+ }
+
+ }
+
+ return haveXMP;
+
+} // ImportCr8rItems
// =================================================================================================
-// MPEG4_MetaHandler::ProcessXMP
-// =============================
+// ExportCr8rItems
+// ===============
+
+static inline void SetBufferedString ( char * dest, const std::string source, size_t limit )
+{
+ memset ( dest, 0, limit );
+ size_t count = source.size();
+ if ( count >= limit ) count = limit - 1; // Ensure a terminating nul.
+ memcpy ( dest, source.c_str(), count );
+}
-#define kAlmostMaxSeconds 0x7FFFFF00
+// -------------------------------------------------------------------------------------------------
-void MPEG4_MetaHandler::ProcessXMP()
+static void ExportCr8rItems ( const SXMPMeta & xmp, MOOV_Manager * moovMgr )
{
- if ( this->processedXMP ) return;
- this->processedXMP = true; // Make sure only called once.
+ bool haveNewCr8r = false;
+ std::string creatorCode, appleEvent, fileExt, appOptions, appName;
- if ( this->containsXMP ) {
- FillPacketInfo ( this->xmpPacket, &this->packetInfo );
- this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &creatorCode, 0 );
+ haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &appleEvent, 0 );
+ haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fileExt, 0 );
+ haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &appOptions, 0 );
+ haveNewCr8r |= xmp.GetProperty ( kXMP_NS_XMP, "CreatorTool", &appName, 0 );
+
+ MOOV_Manager::BoxInfo infoCr8r;
+ MOOV_Manager::BoxRef refCr8r = moovMgr->GetBox ( "moov/udta/Cr8r", &infoCr8r );
+ bool haveOldCr8r = ( (refCr8r != 0) && (infoCr8r.contentSize == sizeof ( Cr8rBoxContent )) );
+
+ if ( ! haveNewCr8r ) {
+ if ( haveOldCr8r ) {
+ MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", 0 );
+ moovMgr->DeleteTypeChild ( udtaRef, 0x43723872 /* 'Cr8r' */ );
+ }
+ return;
}
- if ( this->mvhdBox.empty() && this->cprtBoxes.empty() ) return; // No legacy, we're done.
+ Cr8rBoxContent newCr8r;
+ const Cr8rBoxContent * oldCr8r = (Cr8rBoxContent*) infoCr8r.content;
+
+ if ( ! haveOldCr8r ) {
+
+ memset ( &newCr8r, 0, sizeof(newCr8r) );
+ newCr8r.magic = MakeUns32BE ( 0xBEEFCAFE );
+ newCr8r.size = MakeUns32BE ( sizeof ( newCr8r ) );
+ newCr8r.majorVer = MakeUns16BE ( 1 );
+
+ } else {
+
+ memcpy ( &newCr8r, oldCr8r, sizeof(newCr8r) );
+ if ( GetUns32BE ( &newCr8r.magic ) != 0xBEEFCAFE ) { // Make sure we write BE numbers.
+ Flip4 ( &newCr8r.magic );
+ Flip4 ( &newCr8r.size );
+ Flip2 ( &newCr8r.majorVer );
+ Flip2 ( &newCr8r.minorVer );
+ Flip4 ( &newCr8r.creatorCode );
+ Flip4 ( &newCr8r.appleEvent );
+ }
+
+ }
- std::string oldDigest;
- bool oldDigestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "MPEG-4", &oldDigest, 0 );
+ if ( ! creatorCode.empty() ) {
+ newCr8r.creatorCode = MakeUns32BE ( (XMP_Uns32) strtoul ( creatorCode.c_str(), 0, 0 ) );
+ }
- if ( oldDigestFound ) {
- std::string newDigest;
- this->MakeLegacyDigest ( &newDigest );
- if ( oldDigest == newDigest ) return; // No legacy changes.
+ if ( ! appleEvent.empty() ) {
+ newCr8r.appleEvent = MakeUns32BE ( (XMP_Uns32) strtoul ( appleEvent.c_str(), 0, 0 ) );
}
- // If we get here we need to import the legacy metadata. Either there is no old digest in the
- // XMP, or the digests differ. In the former case keep any existing XMP, in the latter case take
- // new legacy values. So, oldDigestFound means digestsDiffer, else we would have returned.
+ if ( ! fileExt.empty() ) SetBufferedString ( newCr8r.fileExt, fileExt, sizeof ( newCr8r.fileExt ) );
+ if ( ! appOptions.empty() ) SetBufferedString ( newCr8r.appOptions, appOptions, sizeof ( newCr8r.appOptions ) );
+ if ( ! appName.empty() ) SetBufferedString ( newCr8r.appName, appName, sizeof ( newCr8r.appName ) );
- // *** The "official" MPEG-4 metadata might not be very interesting. It looks like the 'ilst'
- // *** metadata (invented by Apple?) is more interesting. There appears to be no official
- // *** documentation.
+ moovMgr->SetBox ( "moov/udta/Cr8r", &newCr8r, sizeof(newCr8r) );
+
+} // ExportCr8rItems
- if ( ! this->mvhdBox.empty() ) {
+// =================================================================================================
+// GetAtomInfo
+// ===========
+
+struct AtomInfo {
+ XMP_Int64 atomSize;
+ XMP_Uns32 atomType;
+ bool hasLargeSize;
+};
+
+enum { // ! Do not rearrange, code depends on this order.
+ kBadQT_NoError = 0, // No errors.
+ kBadQT_SmallInner = 1, // An extra 1..7 bytes at the end of an inner span.
+ kBadQT_LargeInner = 2, // More serious inner garbage, found as invalid atom length.
+ kBadQT_SmallOuter = 3, // An extra 1..7 bytes at the end of the file.
+ kBadQT_LargeOuter = 4 // More serious EOF garbage, found as invalid atom length.
+};
+typedef XMP_Uns8 QTErrorMode;
+
+static QTErrorMode GetAtomInfo ( const LFA_FileRef qtFile, XMP_Int64 spanSize, int nesting, AtomInfo * info )
+{
+ QTErrorMode status = kBadQT_NoError;
+ XMP_Uns8 buffer [8];
- MVHD_v1 mvhd;
- XMP_DateTime xmpDate;
+ info->hasLargeSize = false;
- if ( this->mvhdBox.size() == 100 ) { // *** Should check the version - extract to a static function.
- ExtractMVHD_v0 ( (XMP_Uns8*)(this->mvhdBox.data()), &mvhd );
- } else if ( this->mvhdBox.size() == 112 ) {
- ExtractMVHD_v1 ( (XMP_Uns8*)(this->mvhdBox.data()), &mvhd );
+ LFA_Read ( qtFile, buffer, 8, kLFA_RequireAll ); // Will throw if 8 bytes aren't available.
+ info->atomSize = GetUns32BE ( &buffer[0] ); // ! Yes, the initial size is big endian UInt32.
+ info->atomType = GetUns32BE ( &buffer[4] );
+
+ if ( info->atomSize == 0 ) { // Does the atom extend to EOF?
+
+ if ( nesting != 0 ) return kBadQT_LargeInner;
+ info->atomSize = spanSize; // This outer atom goes to EOF.
+
+ } else if ( info->atomSize == 1 ) { // Does the atom have a 64-bit size?
+
+ if ( spanSize < 16 ) { // Is there room in the span for the 16 byte header?
+ status = kBadQT_LargeInner;
+ if ( nesting == 0 ) status += 2; // Convert to "outer".
+ return status;
}
- if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_XMP, "CreateDate" )) ) {
- if ( (mvhd.creationTime >> 32) < 0xFF ) { // Sanity check for bogus date info.
+ LFA_Read ( qtFile, buffer, 8, kLFA_RequireAll );
+ info->atomSize = (XMP_Int64) GetUns64BE ( &buffer[0] );
+ info->hasLargeSize = true;
- memset ( &xmpDate, 0, sizeof(xmpDate) ); // AUDIT: Using sizeof(xmpDate) is safe.
- xmpDate.year = 1904; // Start at midnight, January 1 1904, UTC
- xmpDate.month = 1; // ! Note that the XMP binary fields are signed to allow
- xmpDate.day = 1; // ! offsets and normalization in both directions.
+ }
- while ( mvhd.creationTime > kAlmostMaxSeconds ) {
- xmpDate.second += kAlmostMaxSeconds;
- SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect.
- mvhd.creationTime -= kAlmostMaxSeconds;
- }
- xmpDate.second += (XMP_Uns32)mvhd.creationTime;
- SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect.
-
- this->xmpObj.SetProperty_Date ( kXMP_NS_XMP, "CreateDate", xmpDate );
- this->containsXMP = true;
+ XMP_Assert ( status == kBadQT_NoError );
+ return status;
- }
+} // GetAtomInfo
+
+// =================================================================================================
+// CheckAtomList
+// =============
+//
+// Check that a sequence of atoms fills a given span. The I/O position must be at the start of the
+// span, it is left just past the span on success. Recursive checks are done for top level 'moov'
+// atoms, and second level 'udta' atoms ('udta' inside 'moov').
+//
+// Checking continues for "small inner" errors. They will be reported if no other kinds of errors
+// are found, otherwise the other error is reported. Checking is immediately aborted for any "large"
+// error. The rationale is that QuickTime can apparently handle small inner errors. They might be
+// arise from updates that shorten an atom by less than 8 bytes. Larger shrinkage should introduce a
+// 'free' atom.
+
+static QTErrorMode CheckAtomList ( const LFA_FileRef qtFile, XMP_Int64 spanSize, int nesting )
+{
+ QTErrorMode status = kBadQT_NoError;
+ AtomInfo info;
+
+ const static XMP_Uns32 moovAtomType = 0x6D6F6F76; // ! Don't use MakeUns32BE, already big endian.
+ const static XMP_Uns32 udtaAtomType = 0x75647461;
+
+ for ( ; spanSize >= 8; spanSize -= info.atomSize ) {
+
+ QTErrorMode atomStatus = GetAtomInfo ( qtFile, spanSize, nesting, &info );
+ if ( atomStatus != kBadQT_NoError ) return atomStatus;
+
+ XMP_Int64 headerSize = 8;
+ if ( info.hasLargeSize ) headerSize = 16;
+
+ if ( (info.atomSize < headerSize) || (info.atomSize > spanSize) ) {
+ status = kBadQT_LargeInner;
+ if ( nesting == 0 ) status += 2; // Convert to "outer".
+ return status;
}
- if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_XMP, "ModifyDate" )) ) {
- if ( (mvhd.modificationTime >> 32) < 0xFF ) { // Sanity check for bogus date info.
+ bool doChildren = false;
+ if ( (nesting == 0) && (info.atomType == moovAtomType) ) doChildren = true;
+ if ( (nesting == 1) && (info.atomType == udtaAtomType) ) doChildren = true;
- memset ( &xmpDate, 0, sizeof(xmpDate) ); // AUDIT: Using sizeof(xmpDate) is safe.
- xmpDate.year = 1904; // Start at midnight, January 1 1904, UTC
- xmpDate.month = 1;
- xmpDate.day = 1;
+ XMP_Int64 dataSize = info.atomSize - headerSize;
- while ( mvhd.modificationTime > kAlmostMaxSeconds ) {
- xmpDate.second += kAlmostMaxSeconds;
- SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect.
- mvhd.modificationTime -= kAlmostMaxSeconds;
- }
- xmpDate.second += (XMP_Uns32)mvhd.modificationTime;
- SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect.
-
- this->xmpObj.SetProperty_Date ( kXMP_NS_XMP, "ModifyDate", xmpDate );
- this->containsXMP = true;
+ if ( ! doChildren ) {
+ LFA_Seek ( qtFile, dataSize, SEEK_CUR );
+ } else {
+ QTErrorMode innerStatus = CheckAtomList ( qtFile, dataSize, nesting+1 );
+ if ( innerStatus > kBadQT_SmallInner ) return innerStatus; // Quit for serious errors.
+ if ( status == kBadQT_NoError ) status = innerStatus; // Remember small inner errors.
+ }
- }
+ }
+
+ XMP_Assert ( status <= kBadQT_SmallInner ); // Else already returned.
+ // ! Make sure inner kBadQT_SmallInner is propagated if this span is OK.
+
+ if ( spanSize != 0 ) {
+ LFA_Seek ( qtFile, spanSize, SEEK_CUR ); // ! Skip the trailing garbage of this span.
+ status = kBadQT_SmallInner;
+ if ( spanSize >= 8 ) status = kBadQT_LargeInner;
+ if ( nesting == 0 ) status += 2; // Convert to "outer".
+ }
+
+ return status;
+
+} // CheckAtomList
+
+// =================================================================================================
+// AttemptFileRepair
+// =================
+
+static void AttemptFileRepair ( LFA_FileRef qtFile, XMP_Int64 fileSpace, QTErrorMode status )
+{
+
+ switch ( status ) {
+ case kBadQT_NoError : return; // Sanity check.
+ case kBadQT_SmallInner : return; // Fixed in normal update code for the 'udta' box.
+ case kBadQT_LargeInner : XMP_Throw ( "Can't repair QuickTime file", kXMPErr_BadFileFormat );
+ case kBadQT_SmallOuter : break; // Truncate file below.
+ case kBadQT_LargeOuter : break; // Truncate file below.
+ default : XMP_Throw ( "Invalid QuickTime error mode", kXMPErr_InternalFailure );
+ }
+
+ AtomInfo info;
+ XMP_Int64 headerSize;
+
+ // Process the top level atoms until an error is found.
+
+ LFA_Seek ( qtFile, 0, SEEK_SET );
+
+ for ( ; fileSpace >= 8; fileSpace -= info.atomSize ) {
+
+ QTErrorMode atomStatus = GetAtomInfo ( qtFile, fileSpace, 0, &info );
+
+ headerSize = 8; // ! Set this before checking atomStatus, used after the loop.
+ if ( info.hasLargeSize ) headerSize = 16;
+
+ if ( atomStatus != kBadQT_NoError ) break;
+ if ( (info.atomSize < headerSize) || (info.atomSize > fileSpace) ) break;
+
+ XMP_Int64 dataSize = info.atomSize - headerSize;
+ LFA_Seek ( qtFile, dataSize, SEEK_CUR );
+
+ }
+
+ // Truncate the file. If fileSpace >= 8 then the loop exited early due to a bad atom, seek back
+ // to the atom's start. Otherwise, the loop exited because no more atoms are possible, no seek.
+
+ if ( fileSpace >= 8 ) LFA_Seek ( qtFile, -headerSize, SEEK_CUR );
+ XMP_Int64 currPos = LFA_Tell ( qtFile );
+ LFA_Truncate ( qtFile, currPos );
+
+} // AttemptFileRepair
+
+// =================================================================================================
+// CheckQTFileStructure
+// ====================
+
+static void CheckQTFileStructure ( XMPFileHandler * thiz, bool doRepair )
+{
+ XMPFiles * parent = thiz->parent;
+ LFA_FileRef fileRef = parent->fileRef;
+ XMP_Int64 fileSize = LFA_Measure ( fileRef );
+
+ // Check the basic file structure and try to repair if asked.
+
+ LFA_Seek ( fileRef, 0, SEEK_SET );
+ QTErrorMode status = CheckAtomList ( fileRef, fileSize, 0 );
+
+ if ( status != kBadQT_NoError ) {
+ if ( doRepair || (status == kBadQT_SmallInner) || (status == kBadQT_SmallOuter) ) {
+ AttemptFileRepair ( fileRef, fileSize, status ); // Will throw if the attempt fails.
+ } else if ( status != kBadQT_SmallInner ) {
+ XMP_Throw ( "Ill-formed QuickTime file", kXMPErr_BadFileFormat );
+ } else {
+ return; // ! Ignore these, QT seems to be able to handle them.
+ // *** Might want to throw for check-only, ignore when repairing.
+ }
+ }
+
+} // CheckQTFileStructure;
+
+// =================================================================================================
+// CheckFinalBox
+// =============
+//
+// Before appending anything new, check if the final top level box has a "to EoF" length. If so, fix
+// it to have an explicit length.
+
+static void CheckFinalBox ( LFA_FileRef fileRef, XMP_AbortProc abortProc, void * abortArg )
+{
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_Uns64 fileSize = LFA_Measure ( fileRef );
+
+ // Find the last 2 boxes in the file. Need the previous to last in case it is an Apple 'wide' box.
+
+ XMP_Uns64 prevPos, lastPos, nextPos;
+ ISOMedia::BoxInfo prevBox, lastBox;
+ XMP_Uns8 buffer [16]; // Enough to create an extended header.
+
+ memset ( &prevBox, 0, sizeof(prevBox) ); // AUDIT: Using sizeof(prevBox) is safe.
+ memset ( &lastBox, 0, sizeof(lastBox) ); // AUDIT: Using sizeof(lastBox) is safe.
+ prevPos = lastPos = nextPos = 0;
+ while ( nextPos != fileSize ) {
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_MetaHandler::CheckFinalBox - User abort", kXMPErr_UserAbort );
+ }
+ prevBox = lastBox;
+ prevPos = lastPos;
+ lastPos = nextPos;
+ nextPos = ISOMedia::GetBoxInfo ( fileRef, lastPos, fileSize, &lastBox, true /* throw errors */ );
+ }
+
+ // See if the last box is valid and has a "to EoF" size.
+
+ if ( lastBox.headerSize < 8 ) XMP_Throw ( "MPEG-4 final box is invalid", kXMPErr_EnforceFailure );
+ LFA_Seek ( fileRef, lastPos, SEEK_SET );
+ LFA_Read ( fileRef, buffer, 4 );
+ XMP_Uns64 lastSize = GetUns32BE ( &buffer[0] ); // ! Yes, the file has a 32-bit value.
+ if ( lastSize != 0 ) return;
+
+ // Have a final "to EoF" box, try to write the explicit size.
+
+ lastSize = lastBox.headerSize + lastBox.contentSize;
+ if ( lastSize <= 0xFFFFFFFFUL ) {
+
+ // Fill in the 32-bit exact size.
+ PutUns32BE ( (XMP_Uns32)lastSize, &buffer[0] );
+ LFA_Seek ( fileRef, lastPos, SEEK_SET );
+ LFA_Write ( fileRef, buffer, 4 );
+
+ } else {
+
+ // Try to convert to using an extended header.
+
+ if ( (prevBox.boxType != ISOMedia::k_wide) || (prevBox.headerSize != 8) || (prevBox.contentSize != 0) ) {
+ XMP_Throw ( "Can't expand final box header", kXMPErr_EnforceFailure );
}
+ XMP_Assert ( prevPos == (lastPos - 8) );
+
+ PutUns32BE ( 1, &buffer[0] );
+ PutUns32BE ( lastBox.boxType, &buffer[4] );
+ PutUns64BE ( lastSize, &buffer[8] );
+ LFA_Seek ( fileRef, prevPos, SEEK_SET );
+ LFA_Write ( fileRef, buffer, 16 );
+
+ }
+
+} // CheckFinalBox
+
+// =================================================================================================
+// WriteBoxHeader
+// ==============
+
+static void WriteBoxHeader ( LFA_FileRef fileRef, XMP_Uns32 boxType, XMP_Uns64 boxSize )
+{
+ XMP_Uns32 u32;
+ XMP_Uns64 u64;
+ XMP_Enforce ( boxSize >= 8 ); // The size must be the full size, not just the content.
+
+ if ( boxSize <= 0xFFFFFFFF ) {
+
+ u32 = MakeUns32BE ( (XMP_Uns32)boxSize );
+ LFA_Write ( fileRef, &u32, 4 );
+ u32 = MakeUns32BE ( boxType );
+ LFA_Write ( fileRef, &u32, 4 );
+
+ } else {
+
+ u32 = MakeUns32BE ( 1 );
+ LFA_Write ( fileRef, &u32, 4 );
+ u32 = MakeUns32BE ( boxType );
+ LFA_Write ( fileRef, &u32, 4 );
+ u64 = MakeUns64BE ( boxSize );
+ LFA_Write ( fileRef, &u64, 8 );
+
+ }
+
+} // WriteBoxHeader
+
+// =================================================================================================
+// WipeBoxFree
+// ===========
+//
+// Change the box's type to 'free' (or create a 'free' box) and zero the content.
- if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) {
- if ( mvhd.timescale != 0 ) { // Avoid 1/0 for the scale field.
- char buffer [32]; // A 64-bit number is at most 20 digits.
- this->xmpObj.DeleteProperty ( kXMP_NS_DM, "duration" ); // Delete the whole struct.
- snprintf ( buffer, sizeof(buffer), "%llu", mvhd.duration ); // AUDIT: The buffer is big enough.
- this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", &buffer[0] );
- snprintf ( buffer, sizeof(buffer), "1/%u", mvhd.timescale ); // AUDIT: The buffer is big enough.
- this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", &buffer[0] );
- this->containsXMP = true;
+static XMP_Uns8 kZeroes [64*1024]; // C semantics guarantee zero initialization.
+
+static void WipeBoxFree ( LFA_FileRef fileRef, XMP_Uns64 boxOffset, XMP_Uns32 boxSize )
+{
+ if ( boxSize == 0 ) return;
+ XMP_Enforce ( boxSize >= 8 );
+
+ LFA_Seek ( fileRef, boxOffset, SEEK_SET );
+ XMP_Uns32 u32;
+ u32 = MakeUns32BE ( boxSize ); // ! The actual size should not change, but might have had a long header.
+ LFA_Write ( fileRef, &u32, 4 );
+ u32 = MakeUns32BE ( ISOMedia::k_free );
+ LFA_Write ( fileRef, &u32, 4 );
+
+ XMP_Uns32 ioCount = sizeof ( kZeroes );
+ for ( boxSize -= 8; boxSize > 0; boxSize -= ioCount ) {
+ if ( ioCount > boxSize ) ioCount = boxSize;
+ LFA_Write ( fileRef, &kZeroes[0], ioCount );
+ }
+
+} // WipeBoxFree
+
+// =================================================================================================
+// CreateFreeSpaceList
+// ===================
+
+struct SpaceInfo {
+ XMP_Uns64 offset, size;
+ SpaceInfo() : offset(0), size(0) {};
+ SpaceInfo ( XMP_Uns64 _offset, XMP_Uns64 _size ) : offset(_offset), size(_size) {};
+};
+
+typedef std::vector<SpaceInfo> FreeSpaceList;
+
+static void CreateFreeSpaceList ( LFA_FileRef fileRef, XMP_Uns64 fileSize,
+ XMP_Uns64 oldOffset, XMP_Uns32 oldSize, FreeSpaceList * spaceList )
+{
+ XMP_Uns64 boxPos, boxNext, adjacentFree;
+ ISOMedia::BoxInfo currBox;
+
+ LFA_Seek ( fileRef, 0, SEEK_SET );
+ spaceList->clear();
+
+ for ( boxPos = 0; boxPos < fileSize; boxPos = boxNext ) {
+
+ boxNext = ISOMedia::GetBoxInfo ( fileRef, boxPos, fileSize, &currBox, true /* throw errors */ );
+ XMP_Uns64 currSize = currBox.headerSize + currBox.contentSize;
+
+ if ( (currBox.boxType == ISOMedia::k_free) ||
+ (currBox.boxType == ISOMedia::k_skip) ||
+ ((boxPos == oldOffset) && (currSize == oldSize)) ) {
+
+ if ( spaceList->empty() || (boxPos != adjacentFree) ) {
+ spaceList->push_back ( SpaceInfo ( boxPos, currSize ) );
+ adjacentFree = boxPos + currSize;
+ } else {
+ SpaceInfo * lastSpace = &spaceList->back();
+ lastSpace->size += currSize;
}
+
}
+
+ }
+
+} // CreateFreeSpaceList
+
+// =================================================================================================
+// MPEG4_MetaHandler::CacheFileData
+// ================================
+//
+// There are 3 file variants: normal ISO Base Media, modern QuickTime, and classic QuickTime. The
+// XMP is placed differently between the ISO and two QuickTime forms, and there is different but not
+// colliding native metadata. The entire 'moov' subtree is cached, along with the top level 'uuid'
+// box of XMP if present.
+
+void MPEG4_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( ! this->containsXMP );
+
+ XMPFiles * parent = this->parent;
+ XMP_OptionBits openFlags = parent->openFlags;
+
+ LFA_FileRef fileRef = parent->fileRef;
+
+ XMP_AbortProc abortProc = parent->abortProc;
+ void * abortArg = parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ // First do some special case repair to QuickTime files, based on bad files in the wild.
+ const bool isUpdate = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenForUpdate );
+ const bool doRepair = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenRepairFile );
+
+ if ( isUpdate && (parent->format == kXMP_MOVFile) ) {
+ CheckQTFileStructure ( this, doRepair ); // Will throw for failure.
}
- if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "rights" )) ) {
+ // Cache the top level 'moov' and 'uuid'/XMP boxes.
- std::string tempStr;
- char lang3 [4]; // The unpacked ISO-639-2/T language code.
- lang3[3] = 0;
+ XMP_Uns64 fileSize = LFA_Measure ( fileRef );
- for ( size_t i = 0, limit = this->cprtBoxes.size(); i < limit; ++i ) {
- if ( this->cprtBoxes[i].size() < 7 ) continue;
+ XMP_Uns64 boxPos, boxNext;
+ ISOMedia::BoxInfo currBox;
- const XMP_Uns8 * currCprt = (XMP_Uns8*) (this->cprtBoxes[i].c_str()); // ! Actually structured data!
- size_t rawLen = this->cprtBoxes[i].size() - 6;
- if ( currCprt[0] != 0 ) continue; // Only proceed for version 0, ignore the flags.
+ bool xmpOnly = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenOnlyXMP );
+ bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
- XMP_Uns16 packedLang = GetUns16BE ( &currCprt[4] );
- lang3[0] = (packedLang >> 10) | 0x60;
- lang3[1] = ((packedLang >> 5) & 0x1F) | 0x60;
- lang3[2] = (packedLang & 0x1F) | 0x60;
-
- XMP_StringPtr xmpLang = Lookup2LetterLang ( lang3 );
- XMP_StringPtr xmpValue = (XMP_StringPtr) &currCprt[6];
+ bool uuidFound = (! haveISOFile); // Ignore the XMP 'uuid' box for QuickTime files.
+ bool moovIgnored = (xmpOnly & haveISOFile); // Ignore the 'moov' box for XMP-only ISO files.
+ bool moovFound = moovIgnored;
+
+ for ( boxPos = 0; boxPos < fileSize; boxPos = boxNext ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
+ }
+
+
+ boxNext = ISOMedia::GetBoxInfo ( fileRef, boxPos, fileSize, &currBox );
+
+ if ( (! moovFound) && (currBox.boxType == ISOMedia::k_moov) ) {
- if ( (rawLen >= 8) && (GetUns16BE ( xmpValue ) == 0xFEFF) ) {
- FromUTF16 ( (UTF16Unit*)xmpValue, rawLen/2, &tempStr, true /* big endian */ );
- xmpValue = tempStr.c_str();
+ XMP_Uns64 fullMoovSize = currBox.headerSize + currBox.contentSize;
+ if ( fullMoovSize > moovBoxSizeLimit ) { // From here on we know 32-bit offsets are safe.
+ XMP_Throw ( "Oversize 'moov' box", kXMPErr_EnforceFailure );
}
- this->xmpObj.SetLocalizedText ( kXMP_NS_DC, "rights", xmpLang, "", xmpValue );
- this->containsXMP = true;
+ this->moovMgr.fullSubtree.assign ( (XMP_Uns32)fullMoovSize, 0 );
+ LFA_Seek ( fileRef, boxPos, SEEK_SET );
+ LFA_Read ( fileRef, &this->moovMgr.fullSubtree[0], (XMP_Uns32)fullMoovSize );
+
+ this->moovBoxPos = boxPos;
+ this->moovBoxSize = (XMP_Uns32)fullMoovSize;
+ moovFound = true;
+ if ( uuidFound ) break; // Exit the loop when both are found.
+
+ } else if ( (! uuidFound) && (currBox.boxType == ISOMedia::k_uuid) ) {
+
+ if ( currBox.contentSize < 16 ) continue;
+
+ XMP_Uns8 uuid [16];
+ LFA_Read ( fileRef, uuid, 16, kLFA_RequireAll );
+ if ( memcmp ( uuid, ISOMedia::k_xmpUUID, 16 ) != 0 ) continue; // Check for the XMP GUID.
+
+ XMP_Uns64 fullUuidSize = currBox.headerSize + currBox.contentSize;
+ if ( fullUuidSize > moovBoxSizeLimit ) { // From here on we know 32-bit offsets are safe.
+ XMP_Throw ( "Oversize XMP 'uuid' box", kXMPErr_EnforceFailure );
+ }
+
+ this->packetInfo.offset = boxPos + currBox.headerSize + 16; // The 16 is for the UUID.
+ this->packetInfo.length = (XMP_Int32) (currBox.contentSize - 16);
+
+ this->xmpPacket.assign ( this->packetInfo.length, ' ' );
+ LFA_Read ( fileRef, (void*)this->xmpPacket.data(), this->packetInfo.length, kLFA_RequireAll );
+
+ this->xmpBoxPos = boxPos;
+ this->xmpBoxSize = (XMP_Uns32)fullUuidSize;
+ uuidFound = true;
+ if ( moovFound ) break; // Exit the loop when both are found.
}
}
+
+ if ( (! moovFound) && (! moovIgnored) ) XMP_Throw ( "No 'moov' box", kXMPErr_BadFileFormat );
-} // MPEG4_MetaHandler::ProcessXMP
+} // MPEG4_MetaHandler::CacheFileData
// =================================================================================================
-// MPEG4_MetaHandler::PickNewLocation
-// ==================================
-//
-// Pick a new location for the XMP. This is the first available 'free' space before any 'mdat' box,
-// otherwise the end of the file. Any existing XMP 'uuid' box has already been marked as 'free'.
+// MPEG4_MetaHandler::ProcessXMP
+// =============================
-void MPEG4_MetaHandler::PickNewLocation()
+void MPEG4_MetaHandler::ProcessXMP()
{
- LFA_FileRef fileRef = this->parent->fileRef;
- XMP_Uns64 fileSize = LFA_Measure ( fileRef );
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
- XMP_Uns32 xmpBoxSize = 4+4+16 + (XMP_Uns32)this->xmpPacket.size();
+ XMPFiles * parent = this->parent;
+ XMP_OptionBits openFlags = parent->openFlags;
- XMP_Uns64 currPos, prevPos; // Info about the most recent 2 boxes.
- XMP_Uns32 currType, prevType;
- XMP_Uns64 currSize, prevSize, hSize, cSize;
+ bool xmpOnly = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenOnlyXMP );
+ bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
+
+ // Process the cached XMP (from the 'uuid' box) if that is all we want and this is an ISO file.
+
+ if ( xmpOnly & haveISOFile ) {
+
+ this->containsXMP = this->havePreferredXMP = (this->packetInfo.length != 0);
+
+ if ( this->containsXMP ) {
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ this->xmpObj.DeleteProperty ( kXMP_NS_XMP, "NativeDigests" ); // No longer used.
+ }
+
+ return;
- XMP_Uns32 be32Size;
- XMP_Uns64 be64Size;
+ }
- XMP_AbortProc abortProc = this->parent->abortProc;
- void * abortArg = this->parent->abortArg;
- const bool checkAbort = (abortProc != 0);
+ // Parse the cached 'moov' subtree, parse the preferred XMP.
- bool pastMDAT = false;
+ if ( this->moovMgr.fullSubtree.empty() ) XMP_Throw ( "No 'moov' box", kXMPErr_BadFileFormat );
+ this->moovMgr.ParseMemoryTree ( this->fileMode );
- currType = 0;
- prevPos = prevSize = currSize = 0;
- for ( currPos = 0; currPos < fileSize; currPos += currSize ) {
+ if ( (this->xmpBoxPos == 0) || (! haveISOFile) ) {
- if ( checkAbort && abortProc(abortArg) ) {
- XMP_Throw ( "MPEG4_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort );
- }
+ // Look for the QuickTime moov/uuid/XMP_ box.
- prevPos += prevSize; // ! We'll go through at least once, the first box is 'ftyp'.
- prevType = currType; // ! Care is needed to preserve the prevBox and currBox info when
- prevSize = currSize; // ! the loop exits because it hits EoF.
- XMP_Assert ( (prevPos + prevSize) == currPos );
+ MOOV_Manager::BoxInfo xmpInfo;
+ MOOV_Manager::BoxRef xmpRef = this->moovMgr.GetBox ( "moov/udta/XMP_", &xmpInfo );
- GetBoxInfo ( fileRef, fileSize, currPos, &currType, &hSize, &cSize );
- currSize = hSize + cSize;
+ if ( (xmpRef != 0) && (xmpInfo.contentSize != 0) ) {
- if ( pastMDAT ) continue; // Keep scanning to the end of the file.
- if ( currType == kBE_mdat ) pastMDAT = true;
- if ( currType != kBE_free ) continue;
- if ( currSize >= xmpBoxSize ) break;
+ this->xmpBoxPos = this->moovBoxPos + this->moovMgr.GetParsedOffset ( xmpRef );
+ this->packetInfo.offset = this->xmpBoxPos + this->moovMgr.GetHeaderSize ( xmpRef );
+ this->packetInfo.length = xmpInfo.contentSize;
+
+ this->xmpPacket.assign ( (char*)xmpInfo.content, this->packetInfo.length );
+ this->havePreferredXMP = (! haveISOFile);
- if ( prevType == kBE_free ) {
- // If we get here the prevBox and currBox are both 'free' and neither big enough alone.
- // Pretend to combine them for this check and for possible following checks. We don't
- // really compbine them, we just remember the start and total length.
- currPos = prevPos;
- currSize += prevSize;
- prevSize = 0; // ! For the start of the next loop pass.
- if ( currSize >= xmpBoxSize ) break;
}
-
+
+ }
+
+ if ( this->xmpBoxPos != 0 ) {
+ this->containsXMP = true;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ this->xmpObj.DeleteProperty ( kXMP_NS_XMP, "NativeDigests" ); // No longer used.
}
- if ( currPos < fileSize ) {
+ // Import the non-XMP items. Do the imports in reverse priority order, last import wins!
+
+ MOOV_Manager::BoxInfo mvhdInfo;
+ MOOV_Manager::BoxRef mvhdRef = this->moovMgr.GetBox ( "moov/mvhd", &mvhdInfo );
+ bool mvhdFound = ((mvhdRef != 0) && (mvhdInfo.contentSize != 0));
+
+ MOOV_Manager::BoxInfo udtaInfo;
+ MOOV_Manager::BoxRef udtaRef = this->moovMgr.GetBox ( "moov/udta", &udtaInfo );
+ std::vector<MOOV_Manager::BoxInfo> cprtBoxes;
+
+ if ( udtaRef != 0 ) {
+ for ( XMP_Uns32 i = 0; i < udtaInfo.childCount; ++i ) {
+ MOOV_Manager::BoxInfo currInfo;
+ MOOV_Manager::BoxRef currRef = this->moovMgr.GetNthChild ( udtaRef, i, &currInfo );
+ if ( currRef == 0 ) break; // Sanity check, should not happen.
+ if ( currInfo.boxType != ISOMedia::k_cprt ) continue;
+ cprtBoxes.push_back ( currInfo );
+ }
+ }
+ bool cprtFound = (! cprtBoxes.empty());
+
+ bool tradQTFound = this->tradQTMgr.ParseCachedBoxes ( this->moovMgr );
+ bool tmcdFound = this->ParseTimecodeTrack();
- // Put the XMP at the start of the 'free' space. Increase the size of the XMP if the excess
- // is less than 8 bytes, otherwise write a new 'free' box header.
+ if ( this->fileMode == MOOV_Manager::kFileIsNormalISO ) {
+
+ if ( mvhdFound ) this->containsXMP |= ImportMVHDItems ( mvhdInfo, &this->xmpObj );
+ if ( cprtFound ) this->containsXMP |= ImportISOCopyrights ( cprtBoxes, &this->xmpObj );
+ } else { // This is a QuickTime file, either traditional or modern.
+
+ if ( mvhdFound ) this->containsXMP |= ImportMVHDItems ( mvhdInfo, &this->xmpObj );
+ if ( cprtFound ) this->containsXMP |= ImportISOCopyrights ( cprtBoxes, &this->xmpObj );
+ if ( tmcdFound | tradQTFound ) {
+ // Some of the timecode items are in the .../udta/©... set but handled by ImportTimecodeItems.
+ this->containsXMP |= ImportTimecodeItems ( this->tmcdInfo, this->tradQTMgr, &this->xmpObj );
+ }
+
+ this->containsXMP |= ImportCr8rItems ( this->moovMgr, &this->xmpObj );
+
+ }
+
+} // MPEG4_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// MPEG4_MetaHandler::ParseTimecodeTrack
+// =====================================
+
+bool MPEG4_MetaHandler::ParseTimecodeTrack()
+{
+ MOOV_Manager::BoxRef stblRef = FindTimecodeTrack ( this->moovMgr );
+ if ( stblRef == 0 ) return false;
+
+ // Find the .../stbl/stsd box and process the first table entry.
+
+ MOOV_Manager::BoxInfo stsdInfo;
+ MOOV_Manager::BoxRef stsdRef;
+
+ stsdRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stsd, &stsdInfo );
+ if ( stsdRef == 0 ) return false;
+ if ( stsdInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsd_entry )) ) return false;
+ if ( GetUns32BE ( stsdInfo.content + 4 ) == 0 ) return false; // Make sure the entry count is non-zero.
+
+ const MOOV_Manager::Content_stsd_entry * stsdRawEntry = (MOOV_Manager::Content_stsd_entry*) (stsdInfo.content + 8);
+
+ XMP_Uns32 stsdEntrySize = GetUns32BE ( &stsdRawEntry->entrySize );
+ if ( stsdEntrySize > (stsdInfo.contentSize - 4) ) stsdEntrySize = stsdInfo.contentSize - 4;
+ if ( stsdEntrySize < sizeof ( MOOV_Manager::Content_stsd_entry ) ) return false;
+
+ XMP_Uns32 stsdEntryFormat = GetUns32BE ( &stsdRawEntry->format );
+ if ( stsdEntryFormat != ISOMedia::k_tmcd ) return false;
+
+ this->tmcdInfo.timeScale = GetUns32BE ( &stsdRawEntry->timeScale );
+ this->tmcdInfo.frameDuration = GetUns32BE ( &stsdRawEntry->frameDuration );
+
+ XMP_Uns32 flags = GetUns32BE ( &stsdRawEntry->flags );
+ this->tmcdInfo.isDropFrame = flags & 0x1;
+
+ // Look for a trailing 'name' box on the first stsd table entry.
+
+ XMP_Uns32 stsdTrailerSize = stsdEntrySize - sizeof ( MOOV_Manager::Content_stsd_entry );
+ if ( stsdTrailerSize > 8 ) { // Room for a non-empty 'name' box?
+
+ const XMP_Uns8 * trailerStart = stsdInfo.content + 8 + sizeof ( MOOV_Manager::Content_stsd_entry );
+ const XMP_Uns8 * trailerLimit = trailerStart + stsdTrailerSize;
+ const XMP_Uns8 * trailerPos;
+ const XMP_Uns8 * trailerNext;
+ ISOMedia::BoxInfo trailerInfo;
- this->xmpBoxPos = currPos;
- XMP_Assert ( (currType == kBE_free) && (currSize >= xmpBoxSize) );
+ for ( trailerPos = trailerStart; trailerPos < trailerLimit; trailerPos = trailerNext ) {
+
+ trailerNext = ISOMedia::GetBoxInfo ( trailerPos, trailerLimit, &trailerInfo );
+
+ if ( trailerInfo.boxType == ISOMedia::k_name ) {
+
+ this->tmcdInfo.nameOffset = (XMP_Uns32) (trailerPos - stsdInfo.content);
+
+ if ( trailerInfo.contentSize > 4 ) {
+
+ XMP_Uns16 textLen = GetUns16BE ( trailerPos + trailerInfo.headerSize );
+ this->tmcdInfo.macLang = GetUns16BE ( trailerPos + trailerInfo.headerSize + 2 );
+
+ if ( trailerInfo.contentSize >= (XMP_Uns64)(textLen + 4) ) {
+ const char * textPtr = (char*) (trailerPos + trailerInfo.headerSize + 4);
+ this->tmcdInfo.macName.assign ( textPtr, textLen );
+ }
+
+ }
+
+ break; // Done after finding the first 'name' box.
- XMP_Uns64 excessSpace = currSize - xmpBoxSize;
- if ( excessSpace < 8 ) {
- this->xmpPacket.append ( (size_t)excessSpace, ' ' );
- } else {
- LFA_Seek ( fileRef, (currPos + xmpBoxSize), SEEK_SET );
- if ( excessSpace <= 0xFFFFFFFFULL ) {
- be32Size = MakeUns32BE ( (XMP_Uns32)excessSpace );
- LFA_Write ( fileRef, &be32Size, 4 );
- LFA_Write ( fileRef, &kBE_free, 4 );
- } else {
- be32Size = MakeUns32BE ( 1 );
- be64Size = MakeUns64BE ( excessSpace );
- LFA_Write ( fileRef, &be32Size, 4 );
- LFA_Write ( fileRef, &kBE_free, 4 );
- LFA_Write ( fileRef, &be64Size, 8 );
}
+
}
+ }
+
+ // Find the timecode sample.
+
+ XMP_Uns64 sampleOffset = 0;
+ MOOV_Manager::BoxInfo tempInfo;
+ MOOV_Manager::BoxRef tempRef;
+
+ tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stsc, &tempInfo );
+ if ( tempRef == 0 ) return false;
+ if ( tempInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsc_entry )) ) return false;
+ if ( GetUns32BE ( tempInfo.content + 4 ) == 0 ) return false; // Make sure the entry count is non-zero.
+
+ XMP_Uns32 firstChunkNumber = GetUns32BE ( tempInfo.content + 8 ); // Want first field of first entry.
+
+ tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stco, &tempInfo );
+
+ if ( tempRef != 0 ) {
+
+ if ( tempInfo.contentSize < (8 + 4) ) return false;
+ XMP_Uns32 stcoCount = GetUns32BE ( tempInfo.content + 4 );
+ if ( stcoCount < firstChunkNumber ) return false;
+ XMP_Uns32 * stcoPtr = (XMP_Uns32*) (tempInfo.content + 8);
+ sampleOffset = GetUns32BE ( &stcoPtr[firstChunkNumber-1] ); // ! Chunk number is 1-based.
+
} else {
- // Appending the XMP, make sure the current final box has an explicit size.
+ tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_co64, &tempInfo );
+ if ( (tempRef == 0) || (tempInfo.contentSize < (8 + 8)) ) return false;
+ XMP_Uns32 co64Count = GetUns32BE ( tempInfo.content + 4 );
+ if ( co64Count < firstChunkNumber ) return false;
+ XMP_Uns64 * co64Ptr = (XMP_Uns64*) (tempInfo.content + 8);
+ sampleOffset = GetUns64BE ( &co64Ptr[firstChunkNumber-1] ); // ! Chunk number is 1-based.
+
+ }
+
+ if ( sampleOffset != 0 ) {
+
+ // Read the timecode sample. Need to reopen the file if the XMPFile was open for read-only,
+ // normally all I/O is done within CacheFileData.
+
+ bool openForRead = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForRead);
- this->xmpBoxPos = fileSize;
- XMP_Assert ( currPos == fileSize );
+ LFA_FileRef fileRef = this->parent->fileRef;
+ if ( openForRead ) {
+ XMP_Assert ( fileRef == 0 );
+ fileRef = LFA_Open ( this->parent->filePath.c_str(), 'r' );
+ }
+
+ if ( fileRef != 0 ) { // The reopen might have failed.
+ LFA_Seek ( fileRef, sampleOffset, SEEK_SET );
+ LFA_Read ( fileRef, &this->tmcdInfo.timecodeSample, 4, kLFA_RequireAll );
+ this->tmcdInfo.timecodeSample = MakeUns32BE ( this->tmcdInfo.timecodeSample );
+ if ( openForRead ) LFA_Close ( fileRef );
+ }
- currPos -= currSize; // Move back to the final box's origin.
- LFA_Seek ( fileRef, currPos, SEEK_SET );
- LFA_Read ( fileRef, &be32Size, 4, kLFA_RequireAll );
- be32Size = MakeUns32BE ( be32Size );
+ }
+
+ // Finally update this->tmcdInfo to remember (for update) that there is an OK timecode track.
+
+ this->tmcdInfo.stsdBoxFound = true;
+ this->tmcdInfo.sampleOffset = sampleOffset;
+ return true;
- if ( be32Size == 0 ) {
+} // MPEG4_MetaHandler::ParseTimecodeTrack
+
+// =================================================================================================
+// MPEG4_MetaHandler::UpdateTopLevelBox
+// ====================================
+
+void MPEG4_MetaHandler::UpdateTopLevelBox ( XMP_Uns64 oldOffset, XMP_Uns32 oldSize,
+ const XMP_Uns8 * newBox, XMP_Uns32 newSize )
+{
+ if ( (oldSize == 0) && (newSize == 0) ) return; // Sanity check, should not happen.
+
+ LFA_FileRef fileRef = this->parent->fileRef;
+ XMP_Uns64 oldFileSize = LFA_Measure ( fileRef );
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+
+ if ( newSize == oldSize ) {
+
+ // Trivial case, update the existing box in-place.
+ LFA_Seek ( fileRef, oldOffset, SEEK_SET );
+ LFA_Write ( fileRef, newBox, oldSize );
+
+ } else if ( (oldOffset + oldSize) == oldFileSize ) {
+
+ // The old box was at the end, write the new and truncate the file if necessary.
+ LFA_Seek ( fileRef, oldOffset, SEEK_SET );
+ LFA_Write ( fileRef, newBox, newSize );
+ LFA_Truncate ( fileRef, (oldOffset + newSize) ); // Does nothing if new size is bigger.
+
+ } else if ( (newSize < oldSize) && ((oldSize - newSize) >= 8) ) {
+
+ // The new size is smaller and there is enough room to create a free box.
+ LFA_Seek ( fileRef, oldOffset, SEEK_SET );
+ LFA_Write ( fileRef, newBox, newSize );
+ WipeBoxFree ( fileRef, (oldOffset + newSize), (oldSize - newSize) );
+
+ } else {
+
+ // Look for a trailing free box with enough space. If not found, consider any free space.
+ // If still not found, append the new box and make the old one free.
+
+ ISOMedia::BoxInfo nextBoxInfo;
+ (void) ISOMedia::GetBoxInfo ( fileRef, (oldOffset + oldSize), oldFileSize, &nextBoxInfo, true /* throw errors */ );
+
+ XMP_Uns64 totalRoom = oldSize + nextBoxInfo.headerSize + nextBoxInfo.contentSize;
- // The current final box is "to-EoF", we need to write the actual size. If the size fits
- // in 32 bits then we just set it in the leading Uns32 field instead of the "to-EoF"
- // value. Otherwise we have to insert a 64-bit size. If the previous box is 'free' then
- // we take 8 bytes from the end of it, shift the size/type parts of the final box up 8
- // bytes, making space for the 64-bit size. Give up if the previous box is not 'free'.
+ bool nextIsFree = (nextBoxInfo.boxType == ISOMedia::k_free) || (nextBoxInfo.boxType == ISOMedia::k_skip);
+ bool haveEnoughRoom = (newSize == totalRoom) ||
+ ( (newSize < totalRoom) && ((totalRoom - newSize) >= 8) );
- if ( currSize <= 0xFFFFFFFFULL ) {
+ if ( nextIsFree & haveEnoughRoom ) {
- // The size fits in 32 bits, reuse the leading Uns32 size.
- be32Size = MakeUns32BE ( (XMP_Uns32)currSize );
- LFA_Seek ( fileRef, currPos, SEEK_SET );
- LFA_Write ( fileRef, &be32Size, 4 );
+ LFA_Seek ( fileRef, oldOffset, SEEK_SET );
+ LFA_Write ( fileRef, newBox, newSize );
- } else if ( prevType != kBE_free ) {
+ if ( newSize < totalRoom ) {
+ // Don't wipe, at most 7 old bytes left, it will be covered by the free header.
+ WriteBoxHeader ( fileRef, ISOMedia::k_free, (totalRoom - newSize) );
+ }
- // We need to insert a 64-bit size, but the previous box is not 'free'.
- XMP_Throw ( "MPEG4_MetaHandler::PickNewLocation - Can't set box size", kXMPErr_ExternalFailure );
+ } else {
- } else if ( prevSize == 8 ) {
+ // Create a list of all top level free space, including the old space as free. Use the
+ // earliest space that fits. If none, append.
+
+ FreeSpaceList spaceList;
+ CreateFreeSpaceList ( fileRef, oldFileSize, oldOffset, oldSize, &spaceList );
+
+ size_t freeSlot, limit;
+ for ( freeSlot = 0, limit = spaceList.size(); freeSlot < limit; ++freeSlot ) {
+ XMP_Uns64 freeSize = spaceList[freeSlot].size;
+ if ( (newSize == freeSize) || ( (newSize < freeSize) && ((freeSize - newSize) >= 8) ) ) break;
+ }
+
+ if ( freeSlot == spaceList.size() ) {
+
+ // No available free space, append the new box.
+ CheckFinalBox ( fileRef, abortProc, abortArg );
+ LFA_Seek ( fileRef, 0, SEEK_END );
+ LFA_Write ( fileRef, newBox, newSize );
+ WipeBoxFree ( fileRef, oldOffset, oldSize );
- // Absorb the whole free box.
- LFA_Seek ( fileRef, prevPos, SEEK_SET );
- be32Size = MakeUns32BE ( 1 );
- LFA_Write ( fileRef, &be32Size, 4 );
- LFA_Write ( fileRef, &currType, 4 );
- be64Size = MakeUns64BE ( currSize );
- LFA_Write ( fileRef, &be64Size, 8 );
-
} else {
+
+ // Use the available free space. Wipe non-overlapping parts of the old box. The old
+ // box is either included in the new space, or is fully disjoint.
+
+ SpaceInfo & newSpace = spaceList[freeSlot];
+
+ bool oldIsDisjoint = ((oldOffset + oldSize) <= newSpace.offset) || // Old is in front.
+ ((newSpace.offset + newSpace.size) <= oldOffset); // Old is behind.
+
+ XMP_Assert ( (newSize == newSpace.size) ||
+ ( (newSize < newSpace.size) && ((newSpace.size - newSize) >= 8) ) );
+
+ XMP_Assert ( oldIsDisjoint ||
+ ( (newSpace.offset <= oldOffset) &&
+ ((oldOffset + oldSize) <= (newSpace.offset + newSpace.size)) ) /* old is included */ );
- // Trim 8 bytes off the end of the free box.
+ XMP_Uns64 newFreeOffset = newSpace.offset + newSize;
+ XMP_Uns64 newFreeSize = newSpace.size - newSize;
+
+ LFA_Seek ( fileRef, newSpace.offset, SEEK_SET );
+ LFA_Write ( fileRef, newBox, newSize );
+
+ if ( newFreeSize > 0 ) WriteBoxHeader ( fileRef, ISOMedia::k_free, newFreeSize );
- prevSize -= 8;
-
- LFA_Seek ( fileRef, prevPos, SEEK_SET );
- LFA_Read ( fileRef, &be32Size, 4, kLFA_RequireAll );
- if ( be32Size != MakeUns32BE ( 1 ) ) {
- be32Size = MakeUns32BE ( (XMP_Uns32)prevSize );
- LFA_Seek ( fileRef, prevPos, SEEK_SET );
- LFA_Write ( fileRef, &be32Size, 4 );
+ if ( oldIsDisjoint ) {
+
+ WipeBoxFree ( fileRef, oldOffset, oldSize );
+
} else {
- be64Size = MakeUns64BE ( prevSize );
- LFA_Seek ( fileRef, (prevPos + 8), SEEK_SET );
- LFA_Write ( fileRef, &be64Size, 8 );
- }
- LFA_Seek ( fileRef, (currPos - 8), SEEK_SET );
- be32Size = MakeUns32BE ( 1 );
- LFA_Write ( fileRef, &be32Size, 4 );
- LFA_Write ( fileRef, &currType, 4 );
- be64Size = MakeUns64BE ( currSize );
- LFA_Write ( fileRef, &be64Size, 8 );
+ // Clear the exposed portion of the old box.
+
+ XMP_Uns64 zeroStart = newFreeOffset + 8;
+ if ( newFreeSize > 0xFFFFFFFF ) zeroStart += 8;
+ if ( oldOffset > zeroStart ) zeroStart = oldOffset;
+ XMP_Uns64 zeroEnd = newFreeOffset + newFreeSize;
+ if ( (oldOffset + oldSize) < zeroEnd ) zeroEnd = oldOffset + oldSize;
+
+ if ( zeroStart < zeroEnd ) { // The new box might cover the old.
+ XMP_Assert ( (zeroEnd - zeroStart) <= (XMP_Uns64)oldSize );
+ XMP_Uns32 zeroSize = (XMP_Uns32) (zeroEnd - zeroStart);
+ LFA_Seek ( fileRef, zeroStart, SEEK_SET );
+ for ( XMP_Uns32 ioCount = sizeof ( kZeroes ); zeroSize > 0; zeroSize -= ioCount ) {
+ if ( ioCount > zeroSize ) ioCount = zeroSize;
+ LFA_Write ( fileRef, &kZeroes[0], ioCount );
+ }
+ }
+ }
+
}
}
-
+
}
-
-} // MPEG4_MetaHandler::PickNewLocation
+
+} // MPEG4_MetaHandler::UpdateTopLevelBox
// =================================================================================================
// MPEG4_MetaHandler::UpdateFile
// =============================
-
-// *** There are no writebacks to legacy metadata yet. We need to resolve the questions about
-// *** standard MPEG-4 metadata versus 'ilst' metadata.
-
-// *** The current logic for the XMP is simple. Use the existing XMP if it is big enough, next look
-// *** for 'free' space before any 'mdat' boxes, finally append to the end.
-
-// ! MPEG-4 can be indexed with absolute offsets, only the 'moov' and XMP 'uuid' boxes can be moved!
+//
+// Revamp notes:
+// The 'moov' subtree and possibly the XMP 'uuid' box get updated. Compose the new copy of each and
+// see if it fits in existing space, incorporating adjacent 'free' boxes if necessary. If that won't
+// work, look for a sufficient 'free' box anywhere in the file. As a last resort, append the new copy.
+// Assume no location sensitive data within 'moov', i.e. no offsets into it. This lets it be moved
+// and its children freely rearranged.
void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate )
{
- if ( ! this->needsUpdate ) return;
+ if ( ! this->needsUpdate ) { // If needsUpdate is set then at least the XMP changed.
+ return;
+ }
+
this->needsUpdate = false; // Make sure only called once.
XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates.
XMP_AbortProc abortProc = this->parent->abortProc;
void * abortArg = this->parent->abortArg;
const bool checkAbort = (abortProc != 0);
-
+
LFA_FileRef fileRef = this->parent->fileRef;
XMP_Uns64 fileSize = LFA_Measure ( fileRef );
- XMP_Uns32 be32Size;
-
- // Make sure the XMP has a current legacy digest.
- std::string newDigest;
- this->MakeLegacyDigest ( &newDigest );
- this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "MPEG-4", newDigest.c_str(), kXMP_DeleteExisting );
- XMP_StringLen xmpLen = (XMP_StringLen)this->xmpPacket.size();
- try {
- this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat | kXMP_ExactPacketLength), xmpLen );
- } catch ( ... ) {
- this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
- }
-
- #if XMP_DebugBuild // Sanity check that the XMP is where we think it is.
+ bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
- if ( this->xmpBoxPos != 0 ) {
-
- XMP_Uns32 boxType;
- XMP_Uns64 hSize, cSize;
- XMP_Uns8 uuid [16];
+ // Update the 'moov' subtree with exports from the XMP, but not the XMP itself (for QT files).
- GetBoxInfo ( fileRef, LFA_Measure ( fileRef ), this->xmpBoxPos, &boxType, &hSize, &cSize );
- LFA_Read ( fileRef, uuid, 16, kLFA_RequireAll );
+ ExportMVHDItems ( this->xmpObj, &this->moovMgr );
+ ExportISOCopyrights ( this->xmpObj, &this->moovMgr );
+ ExportQuickTimeItems ( this->xmpObj, &this->tradQTMgr, &this->moovMgr );
+ ExportTimecodeItems ( this->xmpObj, &this->tmcdInfo, &this->tradQTMgr, &this->moovMgr );
- if ( ((this->xmpBoxPos + hSize + 16) != (XMP_Uns64)this->packetInfo.offset) ||
- ((cSize - 16) != (XMP_Uns64)this->packetInfo.length) ||
- (boxType != kBE_uuid) || (memcmp ( uuid, kBE_xmpUUID, 16 ) != 0) ) {
- XMP_Throw ( "Inaccurate MPEG-4 packet info", kXMPErr_InternalFailure );
- }
-
- }
+ if ( ! haveISOFile ) ExportCr8rItems ( this->xmpObj, &this->moovMgr );
+
+ // Try to update the XMP in-place if that is all that changed, or if it is in a preferred 'uuid' box.
+ // The XMP has already been serialized by common code to the appropriate length. Otherwise, update
+ // the 'moov'/'udta'/'XMP_' box in the MOOV_Manager, or the 'uuid' XMP box in the file.
- #endif
+ bool useUuidXMP = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
- if ( (this->xmpBoxPos != 0) && (this->xmpPacket.size() <= (size_t)this->packetInfo.length) ) {
+ if ( (this->xmpPacket.size() == (size_t)this->packetInfo.length) &&
+ ( (useUuidXMP & this->havePreferredXMP) || (! this->moovMgr.IsChanged()) ) ) {
- // Update existing XMP in-place.
-
- if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) {
- // They ought to match, cheap to be sure.
- size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size();
- this->xmpPacket.append ( extraSpace, ' ' );
- }
-
- XMP_Assert ( this->xmpPacket.size() == (size_t)this->packetInfo.length );
+ // Update the existing XMP in-place.
LFA_Seek ( fileRef, this->packetInfo.offset, SEEK_SET );
- LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() );
+ LFA_Write ( fileRef, this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() );
- } else if ( (this->xmpBoxPos != 0) &&
- ((XMP_Uns64)(this->packetInfo.offset + this->packetInfo.length) == fileSize) ) {
+ } else if ( ! useUuidXMP ) {
- // The XMP is already at the end of the file, rewrite the whole 'uuid' box.
-
- XMP_Assert ( this->xmpPacket.size() > (size_t)this->packetInfo.length );
-
- LFA_Seek ( fileRef, this->xmpBoxPos, SEEK_SET );
- be32Size = MakeUns32BE ( 4+4+16 + (XMP_Uns32)this->xmpPacket.size() ); // ! XMP must be 4GB or less!
- LFA_Write ( fileRef, &be32Size, 4 );
- LFA_Write ( fileRef, &kBE_uuid, 4 );
- LFA_Write ( fileRef, &kBE_xmpUUID, 16 );
- LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() );
+ // Don't leave an old uuid XMP around (if we know about it).
+ if ( (! havePreferredXMP) && (this->xmpBoxSize != 0) ) {
+ WipeBoxFree ( fileRef, this->xmpBoxPos, this->xmpBoxSize );
+ }
+
+ // The udta form of XMP has just the XMP packet.
+ this->moovMgr.SetBox ( "moov/udta/XMP_", this->xmpPacket.c_str(), (XMP_Uns32)this->xmpPacket.size() );
} else {
-
- // We are injecting first time XMP, or making the existing XMP larger. Pick a new location
- // for the XMP. This is the first available space before any 'mdat' box, otherwise the end
- // of the file. Available space can be any contiguous combination of 'free' space with the
- // existing XMP. Mark any existing XMP as 'free' first, this simplifies the logic and we
- // can't do it later since we might reuse the space.
-
- if ( this->xmpBoxPos != 0 ) {
- LFA_Seek ( fileRef, (this->xmpBoxPos + 4), SEEK_SET );
- LFA_Write ( fileRef, "free", 4 );
- }
+
+ // Don't leave an old 'moov'/'udta'/'XMP_' box around.
+ MOOV_Manager::BoxRef udtaRef = this->moovMgr.GetBox ( "moov/udta", 0 );
+ if ( udtaRef != 0 ) this->moovMgr.DeleteTypeChild ( udtaRef, ISOMedia::k_XMP_ );
- this->PickNewLocation(); // ! Might increase the size of the XMP packet.
+ // The uuid form of XMP has the 16-byte UUID in front of the XMP packet. Form the complete
+ // box (including size/type header) for UpdateTopLevelBox.
+ RawDataBlock uuidBox;
+ XMP_Uns32 uuidSize = 4+4 + 16 + (XMP_Uns32)this->xmpPacket.size();
+ uuidBox.assign ( uuidSize, 0 );
+ PutUns32BE ( uuidSize, &uuidBox[0] );
+ PutUns32BE ( ISOMedia::k_uuid, &uuidBox[4] );
+ memcpy ( &uuidBox[8], ISOMedia::k_xmpUUID, 16 );
+ memcpy ( &uuidBox[24], this->xmpPacket.c_str(), this->xmpPacket.size() );
+ this->UpdateTopLevelBox ( this->xmpBoxPos, this->xmpBoxSize, &uuidBox[0], uuidSize );
- LFA_Seek ( fileRef, this->xmpBoxPos, SEEK_SET );
- be32Size = MakeUns32BE ( 4+4+16 + (XMP_Uns32)this->xmpPacket.size() ); // ! XMP must be 4GB or less!
- LFA_Write ( fileRef, &be32Size, 4 );
- LFA_Write ( fileRef, &kBE_uuid, 4 );
- LFA_Write ( fileRef, &kBE_xmpUUID, 16 );
- LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() );
+ }
+
+ // Update the 'moov' subtree if necessary, and finally update the timecode sample.
+ if ( this->moovMgr.IsChanged() ) {
+ this->moovMgr.UpdateMemoryTree();
+ this->UpdateTopLevelBox ( moovBoxPos, moovBoxSize, &this->moovMgr.fullSubtree[0], (XMP_Uns32)this->moovMgr.fullSubtree.size() );
}
+ if ( this->tmcdInfo.sampleOffset != 0 ) {
+ LFA_Seek ( fileRef, this->tmcdInfo.sampleOffset, SEEK_SET );
+ XMP_Uns32 sample = MakeUns32BE ( this->tmcdInfo.timecodeSample );
+ LFA_Write ( fileRef, &sample, 4 );
+ }
+
} // MPEG4_MetaHandler::UpdateFile
// =================================================================================================
@@ -894,14 +2441,14 @@ void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate )
void MPEG4_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
{
XMP_Assert ( this->needsUpdate );
-
+
LFA_FileRef destRef = this->parent->fileRef;
-
+
LFA_Seek ( sourceRef, 0, SEEK_SET );
LFA_Seek ( destRef, 0, SEEK_SET );
LFA_Copy ( sourceRef, destRef, LFA_Measure ( sourceRef ),
this->parent->abortProc, this->parent->abortArg );
-
+
this->UpdateFile ( false );
} // MPEG4_MetaHandler::WriteFile