1 // =================================================================================================
2 // ADOBE SYSTEMS INCORPORATED
3 // Copyright 2006 Adobe Systems Incorporated
4 // All Rights Reserved
5 //
6 // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
7 // of the Adobe license agreement accompanying it.
8 // =================================================================================================
9 
10 #include "public/include/XMP_Environment.h"	// ! XMP_Environment.h must be the first included header.
11 
12 #include "public/include/XMP_Const.h"
13 #include "public/include/XMP_IO.hpp"
14 
15 #include "XMPFiles/source/XMPFiles_Impl.hpp"
16 #include "source/XMPFiles_IO.hpp"
17 #include "source/XIO.hpp"
18 
19 #include "XMPFiles/source/FileHandlers/MPEG4_Handler.hpp"
20 
21 #include "XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp"
22 #include "XMPFiles/source/FormatSupport/MOOV_Support.hpp"
23 
24 #include "source/XMP_ProgressTracker.hpp"
25 #include "source/UnicodeConversions.hpp"
26 #include "third-party/zuid/interfaces/MD5.h"
27 #include <math.h>
28 
29 #if XMP_WinBuild
30 	#pragma warning ( disable : 4996 )	// '...' was declared deprecated
31 #endif
32 
33 using namespace std;
34 
35 // =================================================================================================
36 /// \file MPEG4_Handler.cpp
37 /// \brief File format handler for MPEG-4, a flavor of the ISO Base Media File Format.
38 ///
39 /// This handler ...
40 ///
41 // =================================================================================================
42 
43 // The basic content of a timecode sample description table entry. Does not include trailing boxes.
44 
45 #if SUNOS_SPARC || SUNOS_X86
46 #pragma pack ( 1 )
47 #else
48 #pragma pack ( push, 1 )
49 #endif //#if SUNOS_SPARC || SUNOS_X86
50 
51 struct stsdBasicEntry {
52 	XMP_Uns32 entrySize;
53 	XMP_Uns32 format;
54 	XMP_Uns8  reserved_1 [6];
55 	XMP_Uns16 dataRefIndex;
56 	XMP_Uns32 reserved_2;
57 	XMP_Uns32 flags;
58 	XMP_Uns32 timeScale;
59 	XMP_Uns32 frameDuration;
60 	XMP_Uns8  frameCount;
61 	XMP_Uns8  reserved_3;
62 };
63 
64 #if SUNOS_SPARC || SUNOS_X86
65 #pragma pack (  )
66 #else
67 #pragma pack ( pop )
68 #endif //#if SUNOS_SPARC || SUNOS_X86
69 
70 // =================================================================================================
71 
72 // ! The buffer and constants are both already big endian.
73 #define Get4CharCode(buffPtr) (*((XMP_Uns32*)(buffPtr)))
74 
75 // =================================================================================================
76 
IsClassicQuickTimeBox(XMP_Uns32 boxType)77 static inline bool IsClassicQuickTimeBox ( XMP_Uns32 boxType )
78 {
79 	if ( (boxType == ISOMedia::k_moov) || (boxType == ISOMedia::k_mdat) || (boxType == ISOMedia::k_pnot) ||
80 		 (boxType == ISOMedia::k_free) || (boxType == ISOMedia::k_skip) || (boxType == ISOMedia::k_wide) ) return true;
81 	return false;
82 }	// IsClassicQuickTimeBox
83 
84 // =================================================================================================
85 
86 // Pairs of 3 letter ISO 639-2 codes mapped to 2 letter ISO 639-1 codes from:
87 //   http://www.loc.gov/standards/iso639-2/php/code_list.php
88 // Would have to write an "==" operator to use std::map, must compare values not pointers.
89 // ! Not fully sorted, do not use a binary search.
90 
91 static XMP_StringPtr kKnownLangs[] =
92 	{ "aar", "aa", "abk", "ab", "afr", "af", "aka", "ak", "alb", "sq", "sqi", "sq", "amh", "am",
93 	  "ara", "ar", "arg", "an", "arm", "hy", "hye", "hy", "asm", "as", "ava", "av", "ave", "ae",
94 	  "aym", "ay", "aze", "az", "bak", "ba", "bam", "bm", "baq", "eu", "eus", "eu", "bel", "be",
95 	  "ben", "bn", "bih", "bh", "bis", "bi", "bod", "bo", "tib", "bo", "bos", "bs", "bre", "br",
96 	  "bul", "bg", "bur", "my", "mya", "my", "cat", "ca", "ces", "cs", "cze", "cs", "cha", "ch",
97 	  "che", "ce", "chi", "zh", "zho", "zh", "chu", "cu", "chv", "cv", "cor", "kw", "cos", "co",
98 	  "cre", "cr", "cym", "cy", "wel", "cy", "cze", "cs", "ces", "cs", "dan", "da", "deu", "de",
99 	  "ger", "de", "div", "dv", "dut", "nl", "nld", "nl", "dzo", "dz", "ell", "el", "gre", "el",
100 	  "eng", "en", "epo", "eo", "est", "et", "eus", "eu", "baq", "eu", "ewe", "ee", "fao", "fo",
101 	  "fas", "fa", "per", "fa", "fij", "fj", "fin", "fi", "fra", "fr", "fre", "fr", "fre", "fr",
102 	  "fra", "fr", "fry", "fy", "ful", "ff", "geo", "ka", "kat", "ka", "ger", "de", "deu", "de",
103 	  "gla", "gd", "gle", "ga", "glg", "gl", "glv", "gv", "gre", "el", "ell", "el", "grn", "gn",
104 	  "guj", "gu", "hat", "ht", "hau", "ha", "heb", "he", "her", "hz", "hin", "hi", "hmo", "ho",
105 	  "hrv", "hr", "scr", "hr", "hun", "hu", "hye", "hy", "arm", "hy", "ibo", "ig", "ice", "is",
106 	  "isl", "is", "ido", "io", "iii", "ii", "iku", "iu", "ile", "ie", "ina", "ia", "ind", "id",
107 	  "ipk", "ik", "isl", "is", "ice", "is", "ita", "it", "jav", "jv", "jpn", "ja", "kal", "kl",
108 	  "kan", "kn", "kas", "ks", "kat", "ka", "geo", "ka", "kau", "kr", "kaz", "kk", "khm", "km",
109 	  "kik", "ki", "kin", "rw", "kir", "ky", "kom", "kv", "kon", "kg", "kor", "ko", "kua", "kj",
110 	  "kur", "ku", "lao", "lo", "lat", "la", "lav", "lv", "lim", "li", "lin", "ln", "lit", "lt",
111 	  "ltz", "lb", "lub", "lu", "lug", "lg", "mac", "mk", "mkd", "mk", "mah", "mh", "mal", "ml",
112 	  "mao", "mi", "mri", "mi", "mar", "mr", "may", "ms", "msa", "ms", "mkd", "mk", "mac", "mk",
113 	  "mlg", "mg", "mlt", "mt", "mol", "mo", "mon", "mn", "mri", "mi", "mao", "mi", "msa", "ms",
114 	  "may", "ms", "mya", "my", "bur", "my", "nau", "na", "nav", "nv", "nbl", "nr", "nde", "nd",
115 	  "ndo", "ng", "nep", "ne", "nld", "nl", "dut", "nl", "nno", "nn", "nob", "nb", "nor", "no",
116 	  "nya", "ny", "oci", "oc", "oji", "oj", "ori", "or", "orm", "om", "oss", "os", "pan", "pa",
117 	  "per", "fa", "fas", "fa", "pli", "pi", "pol", "pl", "por", "pt", "pus", "ps", "que", "qu",
118 	  "roh", "rm", "ron", "ro", "rum", "ro", "rum", "ro", "ron", "ro", "run", "rn", "rus", "ru",
119 	  "sag", "sg", "san", "sa", "scc", "sr", "srp", "sr", "scr", "hr", "hrv", "hr", "sin", "si",
120 	  "slk", "sk", "slo", "sk", "slo", "sk", "slk", "sk", "slv", "sl", "sme", "se", "smo", "sm",
121 	  "sna", "sn", "snd", "sd", "som", "so", "sot", "st", "spa", "es", "sqi", "sq", "alb", "sq",
122 	  "srd", "sc", "srp", "sr", "scc", "sr", "ssw", "ss", "sun", "su", "swa", "sw", "swe", "sv",
123 	  "tah", "ty", "tam", "ta", "tat", "tt", "tel", "te", "tgk", "tg", "tgl", "tl", "tha", "th",
124 	  "tib", "bo", "bod", "bo", "tir", "ti", "ton", "to", "tsn", "tn", "tso", "ts", "tuk", "tk",
125 	  "tur", "tr", "twi", "tw", "uig", "ug", "ukr", "uk", "urd", "ur", "uzb", "uz", "ven", "ve",
126 	  "vie", "vi", "vol", "vo", "wel", "cy", "cym", "cy", "wln", "wa", "wol", "wo", "xho", "xh",
127 	  "yid", "yi", "yor", "yo", "zha", "za", "zho", "zh", "chi", "zh", "zul", "zu",
128 	  0, 0 };
129 
Lookup2LetterLang(XMP_StringPtr lang3)130 static inline XMP_StringPtr Lookup2LetterLang ( XMP_StringPtr lang3 )
131 {
132 	for ( size_t i = 0; kKnownLangs[i] != 0; i += 2 ) {
133 		if ( XMP_LitMatch ( lang3, kKnownLangs[i] ) ) return kKnownLangs[i+1];
134 	}
135 	return "";
136 }
137 
Lookup3LetterLang(XMP_StringPtr lang2)138 static inline XMP_StringPtr Lookup3LetterLang ( XMP_StringPtr lang2 )
139 {
140 	for ( size_t i = 0; kKnownLangs[i] != 0; i += 2 ) {
141 		if ( XMP_LitMatch ( lang2, kKnownLangs[i+1] ) ) return kKnownLangs[i];
142 	}
143 	return "";
144 }
145 
146 
147 #define IsTolerableBoxChar(ch)	( ((0x20 <= (ch)) && ((ch) <= 0x7E)) || ((ch) == 0xA9) )
148 
IsTolerableBox(XMP_Uns32 boxType)149 static inline bool IsTolerableBox ( XMP_Uns32 boxType )
150 {
151 	// Make sure the box type is 4 ASCII characters or 0xA9 (MacRoman copyright).
152 	XMP_Uns8 b1 = (XMP_Uns8) (boxType >> 24);
153 	XMP_Uns8 b2 = (XMP_Uns8) ((boxType >> 16) & 0xFF);
154 	XMP_Uns8 b3 = (XMP_Uns8) ((boxType >> 8) & 0xFF);
155 	XMP_Uns8 b4 = (XMP_Uns8) (boxType & 0xFF);
156 	bool ok = IsTolerableBoxChar(b1) && IsTolerableBoxChar(b2) &&
157 		IsTolerableBoxChar(b3) && IsTolerableBoxChar(b4);
158 	return ok;
159 }
160 
IsXMPUUID(XMP_IO * fileRef,XMP_Uns64 contentSize,bool unmovedFilePtr=false)161 static inline bool IsXMPUUID ( XMP_IO * fileRef,XMP_Uns64 contentSize, bool unmovedFilePtr=false )
162 {
163 	if ( contentSize < 16 ) return false;
164 	XMP_Uns8 uuid [16];
165 	fileRef->ReadAll ( uuid, 16 );
166 	if (unmovedFilePtr) fileRef->Seek ( -16, kXMP_SeekFromCurrent );
167 	if ( memcmp ( uuid, ISOMedia::k_xmpUUID, 16 ) != 0 ) return false;	// Check for the XMP GUID.
168 	return true;
169 }
170 
171 // =================================================================================================
172 // MPEG4_CheckFormat
173 // =================
174 //
175 // There are 3 variations of recognized file:
176 //	- Normal MPEG-4 - has an 'ftyp' box containing a known compatible brand but not 'qt  '.
177 //	- Modern QuickTime - has an 'ftyp' box containing 'qt  ' as a compatible brand.
178 //	- Classic QuickTime - has no 'ftyp' box, should have recognized top level boxes.
179 //
180 // An MPEG-4 or modern QuickTime file is an instance of an ISO Base Media file, ISO 14496-12 and -14.
181 // A classic QuickTime file has the same physical box structure, but somewhat different box types.
182 // The ISO files must begin with an 'ftyp' box containing 'mp41', 'mp42', 'f4v ', 'qt  ', 'isom','3gp4',
183 // '3g2a','3g2b' or '3g2c' in the compatible brands.
184 //
185 // The general box structure is:
186 //
187 //   0  4  uns32  box size, 0 means "to EoF", 1 means 64-bit size follows
188 //   4  4  uns32  box type
189 //   8  8  uns64  box size, present only if uns32 size is 1
190 //   -  *  box content
191 //
192 // The 'ftyp' box content is:
193 //
194 //   -  4  uns32  major brand
195 //   -  4  uns32  minor version
196 //   -  *  uns32  sequence of compatible brands, to the end of the box
197 
198 // ! With the addition of QuickTime support there is some change in behavior in OpenFile when the
199 // ! kXMPFiles_OpenStrictly option is used wth a specific file format. The kXMP_MPEG4File and
200 // ! kXMP_MOVFile formats are treated uniformly, you can't force "real MOV" or "real MPEG-4". You
201 // ! can check afterwards using GetFileInfo to see what the file happens to be.
202 
MPEG4_CheckFormat(XMP_FileFormat format,XMP_StringPtr,XMP_IO * fileRef,XMPFiles * parent)203 bool MPEG4_CheckFormat ( XMP_FileFormat format,
204 						 XMP_StringPtr  /*filePath*/,
205 						 XMP_IO*    fileRef,
206 						 XMPFiles*      parent )
207 {
208 	XMP_Uns8  buffer [4*1024];
209 	XMP_Uns32 ioCount, brandCount, brandOffset;
210 	XMP_Uns64 fileSize, nextOffset;
211 	ISOMedia::BoxInfo currBox;
212 
213 	XMP_AbortProc abortProc  = parent->abortProc;
214 	void *        abortArg   = parent->abortArg;
215 	const bool    checkAbort = (abortProc != 0);
216 
217 	bool openStrictly = XMP_OptionIsSet ( parent->openFlags, kXMPFiles_OpenStrictly);
218 
219 	// Get the first box's info, see if it is 'ftyp' or not.
220 
221 	XMP_Assert ( (parent->tempPtr == 0) && (parent->tempUI32 == 0) );
222 
223 	fileSize = fileRef->Length();
224 	if ( fileSize < 8 ) return false;
225 
226 	nextOffset = ISOMedia::GetBoxInfo ( fileRef, 0, fileSize, &currBox );
227 	if ( currBox.headerSize < 8 ) return false;	// Can't be an ISO or QuickTime file.
228 
229 	if ( currBox.boxType == ISOMedia::k_ftyp ) {
230 
231 		// Have an 'ftyp' box, look through the compatible brands. If 'qt  ' is present then this is
232 		// a modern QuickTime file, regardless of what else is found. Otherwise this is plain ISO if
233 		// any of the other recognized brands are found.
234 
235 		if ( currBox.contentSize < 12 ) return false;	// No compatible brands at all.
236 		if ( currBox.contentSize > 1024*1024 ) return false;	// Sanity check and make sure count fits in 32 bits.
237 		brandCount = ((XMP_Uns32)currBox.contentSize - 8) >> 2;
238 
239 		fileRef->Seek ( 8, kXMP_SeekFromCurrent );	// Skip the major and minor brands.
240 		ioCount = brandOffset = 0;
241 
242 		bool haveCompatibleBrand = false;
243 
244 		for ( ; brandCount > 0; --brandCount, brandOffset += 4 ) {
245 
246 			if ( brandOffset >= ioCount ) {
247 				if ( checkAbort && abortProc(abortArg) ) {
248 					XMP_Throw ( "MPEG4_CheckFormat - User abort", kXMPErr_UserAbort );
249 				}
250 				ioCount = 4 * brandCount;
251 				if ( ioCount > sizeof(buffer) ) ioCount = sizeof(buffer);
252 				ioCount = fileRef->ReadAll ( buffer, ioCount );
253 				brandOffset = 0;
254 			}
255 
256 			XMP_Uns32 brand = GetUns32BE ( &buffer[brandOffset] );
257 			if ( brand == ISOMedia::k_qt ) {	// Don't need to look further.
258 				if ( openStrictly && (format != kXMP_MOVFile) ) return false;
259 				parent->format = kXMP_MOVFile;
260 				parent->tempUI32 = MOOV_Manager::kFileIsModernQT;
261 				return true;
262 			}
263 			else if ( ( brand == ISOMedia::k_mp41 ) || ( brand == ISOMedia::k_mp42 ) ||
264 				( brand == ISOMedia::k_f4v ) || ( brand == ISOMedia::k_avc1 ) || ( brand == ISOMedia::k_isom ) ||
265 				( brand == ISOMedia::k_3gp4 ) || ( brand == ISOMedia::k_3g2a ) || ( brand == ISOMedia::k_3g2b ) ||
266 				( brand == ISOMedia::k_3g2c ) ) {
267 				haveCompatibleBrand = true;	// Need to keep looking in case 'qt  ' follows.
268 			}
269 
270 		}
271 
272 		if ( ! haveCompatibleBrand ) return false;
273 		if ( openStrictly && (format != kXMP_MPEG4File) ) return false;
274 		parent->format = kXMP_MPEG4File;
275 		parent->tempUI32 = MOOV_Manager::kFileIsNormalISO;
276 		return true;
277 
278 	} else {
279 		// No 'ftyp', look for classic QuickTime: 'moov', 'mdat', 'pnot', 'free', 'skip', and 'wide'.
280 		// As an expedient, quit when a 'moov' box is found. Tolerate other boxes, they are in the
281 		// wild for ill-formed files, e.g. seen when 'moov'/'udta' children get left at top level.
282 
283 		while ( currBox.boxType != ISOMedia::k_moov ) {
284 
285 			if ( ! IsClassicQuickTimeBox ( currBox.boxType ) ) {
286 				if ( ! IsTolerableBox(currBox.boxType) ) return false;
287 			}
288 			if ( nextOffset >= fileSize ) return false;
289 			if ( checkAbort && abortProc(abortArg) ) {
290 				XMP_Throw ( "MPEG4_CheckFormat - User abort", kXMPErr_UserAbort );
291 			}
292 			nextOffset = ISOMedia::GetBoxInfo ( fileRef, nextOffset, fileSize, &currBox );
293 
294 		}
295 
296 		if ( openStrictly && (format != kXMP_MOVFile) ) return false;
297 		parent->format = kXMP_MOVFile;
298 		parent->tempUI32 = MOOV_Manager::kFileIsTraditionalQT;
299 		return true;
300 
301 	}
302 
303 	return false;
304 
305 }	// MPEG4_CheckFormat
306 
307 // =================================================================================================
308 // MPEG4_MetaHandlerCTor
309 // =====================
310 
MPEG4_MetaHandlerCTor(XMPFiles * parent)311 XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent )
312 {
313 
314 	return new MPEG4_MetaHandler ( parent );
315 
316 }	// MPEG4_MetaHandlerCTor
317 
318 // =================================================================================================
319 // MPEG4_MetaHandler::MPEG4_MetaHandler
320 // ====================================
321 
MPEG4_MetaHandler(XMPFiles * _parent)322 MPEG4_MetaHandler::MPEG4_MetaHandler ( XMPFiles * _parent )
323 	: fileMode(0), havePreferredXMP(false), xmpBoxPos(0), moovBoxPos(0), xmpBoxSize(0), moovBoxSize(0)
324 {
325 
326 	this->parent = _parent;	// Inherited, can't set in the prefix.
327 	this->handlerFlags = kMPEG4_HandlerFlags;
328 	this->stdCharForm  = kXMP_Char8Bit;
329 	this->fileMode = (XMP_Uns8)_parent->tempUI32;
330 	_parent->tempUI32 = 0;
331 
332 }	// MPEG4_MetaHandler::MPEG4_MetaHandler
333 
334 // =================================================================================================
335 // MPEG4_MetaHandler::~MPEG4_MetaHandler
336 // =====================================
337 
~MPEG4_MetaHandler()338 MPEG4_MetaHandler::~MPEG4_MetaHandler()
339 {
340 
341 	// Nothing to do.
342 
343 }	// MPEG4_MetaHandler::~MPEG4_MetaHandler
344 
345 // =================================================================================================
346 // SecondsToXMPDate
347 // ================
348 
349 // *** ASF has similar code with different origin, should make a shared utility.
350 
SecondsToXMPDate(XMP_Uns64 isoSeconds,XMP_DateTime * xmpDate)351 static void SecondsToXMPDate ( XMP_Uns64 isoSeconds, XMP_DateTime * xmpDate )
352 {
353 	memset ( xmpDate, 0, sizeof(XMP_DateTime) );	// AUDIT: Using sizeof(XMP_DateTime) is safe.
354 
355 	XMP_Int32 days = (XMP_Int32) (isoSeconds / 86400);
356 	isoSeconds -= ((XMP_Uns64)days * 86400);
357 
358 	XMP_Int32 hour = (XMP_Int32) (isoSeconds / 3600);
359 	isoSeconds -= ((XMP_Uns64)hour * 3600);
360 
361 	XMP_Int32 minute = (XMP_Int32) (isoSeconds / 60);
362 	isoSeconds -= ((XMP_Uns64)minute * 60);
363 
364 	XMP_Int32 second = (XMP_Int32)isoSeconds;
365 
366 	xmpDate->year  = 1904;	// Start with the ISO origin.
367 	xmpDate->month = 1;
368 	xmpDate->day   = 1;
369 
370 	xmpDate->day += days;	// Add in the delta.
371 	xmpDate->hour = hour;
372 	xmpDate->minute = minute;
373 	xmpDate->second = second;
374 
375 	xmpDate->hasTimeZone = true;	// ! Needed for ConvertToUTCTime to do anything.
376 	SXMPUtils::ConvertToUTCTime ( xmpDate );	// Normalize the date/time.
377 
378 }	// SecondsToXMPDate
379 
380 // =================================================================================================
381 // XMPDateToSeconds
382 // ================
383 
384 // *** ASF has similar code with different origin, should make a shared utility.
385 
IsLeapYear(XMP_Int32 year)386 static bool IsLeapYear ( XMP_Int32 year )
387 {
388 	if ( year < 0 ) year = -year + 1;		// Fold the negative years, assuming there is a year 0.
389 	if ( (year % 4) != 0 ) return false;	// Not a multiple of 4.
390 	if ( (year % 100) != 0 ) return true;	// A multiple of 4 but not a multiple of 100.
391 	if ( (year % 400) == 0 ) return true;	// A multiple of 400.
392 	return false;							// A multiple of 100 but not a multiple of 400.
393 }
394 
395 // -------------------------------------------------------------------------------------------------
396 
DaysInMonth(XMP_Int32 year,XMP_Int32 month)397 static XMP_Int32 DaysInMonth ( XMP_Int32 year, XMP_Int32 month )
398 {
399 	static XMP_Int32 daysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
400 										 // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
401 	XMP_Int32 days = daysInMonth[month];
402 	if ( (month == 2) && IsLeapYear(year) ) days += 1;
403 	return days;
404 }
405 
406 // -------------------------------------------------------------------------------------------------
407 
XMPDateToSeconds(const XMP_DateTime & _xmpDate,XMP_Uns64 * isoSeconds)408 static void XMPDateToSeconds ( const XMP_DateTime & _xmpDate, XMP_Uns64 * isoSeconds )
409 {
410 	XMP_DateTime xmpDate = _xmpDate;
411 	SXMPUtils::ConvertToUTCTime ( &xmpDate );
412 
413 	XMP_Uns64 tempSeconds = (XMP_Uns64)xmpDate.second;
414 	tempSeconds += (XMP_Uns64)xmpDate.minute * 60;
415 	tempSeconds += (XMP_Uns64)xmpDate.hour * 3600;
416 
417 	XMP_Int32 days = (xmpDate.day - 1);
418 
419 	--xmpDate.month;
420 	while ( xmpDate.month >= 1 ) {
421 		days += DaysInMonth ( xmpDate.year, xmpDate.month );
422 		--xmpDate.month;
423 	}
424 
425 	--xmpDate.year;
426 	while ( xmpDate.year >= 1904 ) {
427 		days += (IsLeapYear ( xmpDate.year) ? 366 : 365 );
428 		--xmpDate.year;
429 	}
430 
431 	tempSeconds += (XMP_Uns64)days * 86400;
432 	*isoSeconds = tempSeconds;
433 
434 }	// XMPDateToSeconds
435 
436 // =================================================================================================
437 // ImportMVHDItems
438 // ===============
439 
ImportMVHDItems(MOOV_Manager::BoxInfo mvhdInfo,SXMPMeta * xmp)440 static bool ImportMVHDItems ( MOOV_Manager::BoxInfo mvhdInfo, SXMPMeta * xmp )
441 {
442 	XMP_Assert ( mvhdInfo.boxType == ISOMedia::k_mvhd );
443 	if ( mvhdInfo.contentSize < 4 ) return false;	// Just enough to check the version/flags at first.
444 
445 	XMP_Uns8 mvhdVersion = *mvhdInfo.content;
446 	if ( mvhdVersion > 1 ) return false;
447 
448 	XMP_Uns64 creationTime, modificationTime, duration;
449 	XMP_Uns32 timescale;
450 
451 	if ( mvhdVersion == 0 ) {
452 
453 		if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return false;
454 		MOOV_Manager::Content_mvhd_0 * mvhdRaw_0 = (MOOV_Manager::Content_mvhd_0*) mvhdInfo.content;
455 
456 		creationTime = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->creationTime );
457 		modificationTime = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->modificationTime );
458 		timescale = GetUns32BE ( &mvhdRaw_0->timescale );
459 		duration = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->duration );
460 
461 	} else {
462 
463 		XMP_Assert ( mvhdVersion == 1 );
464 		if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_1 ) ) return false;
465 		MOOV_Manager::Content_mvhd_1 * mvhdRaw_1 = (MOOV_Manager::Content_mvhd_1*) mvhdInfo.content;
466 
467 		creationTime = GetUns64BE ( &mvhdRaw_1->creationTime );
468 		modificationTime = GetUns64BE ( &mvhdRaw_1->modificationTime );
469 		timescale = GetUns32BE ( &mvhdRaw_1->timescale );
470 		duration = GetUns64BE ( &mvhdRaw_1->duration );
471 
472 	}
473 
474 	bool haveImports = false;
475 	XMP_DateTime xmpDate;
476 
477 	if ( (creationTime >> 32) < 0xFF ) {		// Sanity check for bogus date info.
478 		SecondsToXMPDate ( creationTime, &xmpDate );
479 		xmp->SetProperty_Date ( kXMP_NS_XMP, "CreateDate", xmpDate );
480 		haveImports = true;
481 	}
482 
483 	if ( (modificationTime >> 32) < 0xFF ) {	// Sanity check for bogus date info.
484 		SecondsToXMPDate ( modificationTime, &xmpDate );
485 		xmp->SetProperty_Date ( kXMP_NS_XMP, "ModifyDate", xmpDate );
486 		haveImports = true;
487 	}
488 
489 	if ( timescale != 0 ) {	// Avoid 1/0 for the scale field.
490 		char buffer [32];	// A 64-bit number is at most 20 digits.
491 		xmp->DeleteProperty ( kXMP_NS_DM, "duration" );	// Delete the whole struct.
492 		snprintf ( buffer, sizeof(buffer), "%llu", (long long unsigned)duration );	// AUDIT: The buffer is big enough.
493 		xmp->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", &buffer[0] );
494 		snprintf ( buffer, sizeof(buffer), "1/%u", timescale );	// AUDIT: The buffer is big enough.
495 		xmp->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", &buffer[0] );
496 		haveImports = true;
497 	}
498 
499 	return haveImports;
500 
501 }	// ImportMVHDItems
502 
503 // =================================================================================================
504 // ExportMVHDItems
505 // ===============
506 
ExportMVHDItems(const SXMPMeta & xmp,MOOV_Manager * moovMgr)507 static void ExportMVHDItems ( const SXMPMeta & xmp, MOOV_Manager * moovMgr )
508 {
509 	XMP_DateTime xmpDate;
510 	bool createFound, modifyFound;
511 	XMP_Uns64 createSeconds = 0, modifySeconds = 0;
512 
513 	MOOV_Manager::BoxInfo mvhdInfo;
514 	MOOV_Manager::BoxRef  mvhdRef = moovMgr->GetBox ( "moov/mvhd", &mvhdInfo );
515 	if ( (mvhdRef == 0) || (mvhdInfo.contentSize < 4) ) return;
516 
517 	XMP_Uns8 version = *mvhdInfo.content;
518 	if ( version > 1 ) return;
519 
520 	createFound = xmp.GetProperty_Date ( kXMP_NS_XMP, "CreateDate", &xmpDate, 0 );
521 	if ( createFound ) XMPDateToSeconds ( xmpDate, &createSeconds );
522 
523 	modifyFound = xmp.GetProperty_Date ( kXMP_NS_XMP, "ModifyDate", &xmpDate, 0 );
524 	if ( modifyFound ) XMPDateToSeconds ( xmpDate, &modifySeconds );
525 
526 	if ( version == 1 ) {
527 
528 		// Modify the v1 box in-place.
529 
530 		if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_1 ) ) return;
531 
532 		XMP_Uns64 oldCreate = GetUns64BE ( mvhdInfo.content + 4 );
533 		XMP_Uns64 oldModify = GetUns64BE ( mvhdInfo.content + 12 );
534 
535 		if ( createFound ) {
536 			if ( createSeconds != oldCreate ) PutUns64BE ( createSeconds, ((XMP_Uns8*)mvhdInfo.content + 4) );
537 			moovMgr->NoteChange();
538 		}
539 		if ( modifyFound  ) {
540 			if ( modifySeconds != oldModify ) PutUns64BE ( modifySeconds, ((XMP_Uns8*)mvhdInfo.content + 12) );
541 			moovMgr->NoteChange();
542 		}
543 
544 	} else if ( ((createSeconds >> 32) == 0) && ((modifySeconds >> 32) == 0) ) {
545 
546 		// Modify the v0 box in-place.
547 
548 		if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return;
549 
550 		XMP_Uns32 oldCreate = GetUns32BE ( mvhdInfo.content + 4 );
551 		XMP_Uns32 oldModify = GetUns32BE ( mvhdInfo.content + 8 );
552 
553 		if ( createFound ) {
554 			if ( (XMP_Uns32)createSeconds != oldCreate ) PutUns32BE ( (XMP_Uns32)createSeconds, ((XMP_Uns8*)mvhdInfo.content + 4) );
555 			moovMgr->NoteChange();
556 		}
557 		if ( modifyFound ) {
558 			if ( (XMP_Uns32)modifySeconds != oldModify ) PutUns32BE ( (XMP_Uns32)modifySeconds, ((XMP_Uns8*)mvhdInfo.content + 8) );
559 			moovMgr->NoteChange();
560 		}
561 
562 	} else {
563 
564 		// Replace the v0 box with a v1 box.
565 
566 		XMP_Assert ( createFound | modifyFound );	// One of them has high bits set.
567 		if ( mvhdInfo.contentSize != sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return;
568 
569 		MOOV_Manager::Content_mvhd_0 * mvhdV0 = (MOOV_Manager::Content_mvhd_0*) mvhdInfo.content;
570 		MOOV_Manager::Content_mvhd_1   mvhdV1;
571 
572 		// Copy the unchanged fields directly.
573 
574 		mvhdV1.timescale = mvhdV0->timescale;
575 		mvhdV1.rate = mvhdV0->rate;
576 		mvhdV1.volume = mvhdV0->volume;
577 		mvhdV1.pad_1 = mvhdV0->pad_1;
578 		mvhdV1.pad_2 = mvhdV0->pad_2;
579 		mvhdV1.pad_3 = mvhdV0->pad_3;
580 		for ( int i = 0; i < 9; ++i ) mvhdV1.matrix[i] = mvhdV0->matrix[i];
581 		for ( int i = 0; i < 6; ++i ) mvhdV1.preDef[i] = mvhdV0->preDef[i];
582 		mvhdV1.nextTrackID = mvhdV0->nextTrackID;
583 
584 		// Set the fields that have changes.
585 
586 		mvhdV1.vFlags = (1 << 24) | (mvhdV0->vFlags & 0xFFFFFF);
587 		mvhdV1.duration = MakeUns64BE ( (XMP_Uns64) GetUns32BE ( &mvhdV0->duration ) );
588 
589 		XMP_Uns64 temp64;
590 
591 		temp64 = (XMP_Uns64) GetUns32BE ( &mvhdV0->creationTime );
592 		if ( createFound ) temp64 = createSeconds;
593 		mvhdV1.creationTime = MakeUns64BE ( temp64 );
594 
595 		temp64 = (XMP_Uns64) GetUns32BE ( &mvhdV0->modificationTime );
596 		if ( modifyFound ) temp64 = modifySeconds;
597 		mvhdV1.modificationTime = MakeUns64BE ( temp64 );
598 
599 		moovMgr->SetBox ( mvhdRef, &mvhdV1, sizeof ( MOOV_Manager::Content_mvhd_1 ) );
600 
601 	}
602 
603 }	// ExportMVHDItems
604 
605 // =================================================================================================
606 // ImportISOCopyrights
607 // ===================
608 //
609 // The cached 'moov'/'udta'/'cprt' boxes are full boxes. The "real" content is a UInt16 packed 3
610 // character language code and a UTF-8 or UTF-16 string.
611 
ImportISOCopyrights(const std::vector<MOOV_Manager::BoxInfo> & cprtBoxes,SXMPMeta * xmp)612 static bool ImportISOCopyrights ( const std::vector<MOOV_Manager::BoxInfo> & cprtBoxes, SXMPMeta * xmp )
613 {
614 	bool haveImports = false;
615 
616 	std::string tempStr;
617 	char lang3 [4];	// The unpacked ISO-639-2/T language code with final null.
618 	lang3[3] = 0;
619 
620 	for ( size_t i = 0, limit = cprtBoxes.size(); i < limit; ++i ) {
621 
622 		const MOOV_Manager::BoxInfo & currBox = cprtBoxes[i];
623 		if ( currBox.contentSize < 4+2+1 ) continue;	// Want enough for a non-empty value.
624 		if ( *currBox.content != 0 ) continue;	// Only proceed for version 0, ignore the flags.
625 
626 		XMP_Uns16 packedLang = GetUns16BE ( currBox.content + 4 );
627 		lang3[0] = (packedLang >> 10) + 0x60;
628 		lang3[1] = ((packedLang >> 5) & 0x1F) + 0x60;
629 		lang3[2] = (packedLang & 0x1F) + 0x60;
630 		XMP_StringPtr xmpLang  = Lookup2LetterLang ( lang3 );
631 		if ( *xmpLang == 0 ) continue;
632 
633 		XMP_StringPtr textPtr = (XMP_StringPtr) (currBox.content + 6);
634 		XMP_StringLen textLen = currBox.contentSize - 6;
635 
636 		if ( (textLen >= 2) && (GetUns16BE(textPtr) == 0xFEFF) ) {
637 			FromUTF16 ( (UTF16Unit*)textPtr, textLen/2, &tempStr, true /* big endian */ );
638 			textPtr = tempStr.c_str();
639 		}
640 
641 		xmp->SetLocalizedText ( kXMP_NS_DC, "rights", xmpLang, xmpLang, textPtr );
642 		haveImports = true;
643 
644 	}
645 
646 	return haveImports;
647 
648 }	// ImportISOCopyrights
649 
650 // =================================================================================================
651 // ExportISOCopyrights
652 // ===================
653 
ExportISOCopyrights(const SXMPMeta & xmp,MOOV_Manager * moovMgr)654 static void ExportISOCopyrights ( const SXMPMeta & xmp, MOOV_Manager * moovMgr )
655 {
656 	bool haveMappings = false;	// True if any ISO-XMP language mappings are found.
657 
658 	// Go through the ISO 'cprt' items and look for a corresponding XMP item. Ignore the ISO item if
659 	// there is no language mapping to XMP. Update the ISO item if the mappable XMP exists, delete
660 	// the ISO item if the mappable XMP does not exist. Since the import side would have made sure
661 	// the mappable XMP items existed, if they don't now they must have been deleted.
662 
663 	MOOV_Manager::BoxInfo udtaInfo;
664 	MOOV_Manager::BoxRef  udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo );
665 	if ( udtaRef == 0 ) return;
666 
667 	std::string xmpPath, xmpValue, xmpLang, tempStr;
668 	char lang3 [4];	// An unpacked ISO-639-2/T language code.
669 	lang3[3] = 0;
670 
671 	for ( XMP_Uns32 ordinal = udtaInfo.childCount; ordinal > 0; --ordinal ) {	// ! Go backwards because of deletions.
672 
673 		MOOV_Manager::BoxInfo cprtInfo;
674 		MOOV_Manager::BoxRef  cprtRef = moovMgr->GetNthChild ( udtaRef, ordinal-1, &cprtInfo );
675 		if ( cprtRef == 0 ) break;	// Sanity check, should not happen.
676 		if ( (cprtInfo.boxType != ISOMedia::k_cprt) || (cprtInfo.contentSize < 6) ) continue;
677 		if ( *cprtInfo.content != 0 ) continue;	// Only accept version 0, ignore the flags.
678 
679 		XMP_Uns16 packedLang = GetUns16BE ( cprtInfo.content + 4 );
680 		lang3[0] = (packedLang >> 10) + 0x60;
681 		lang3[1] = ((packedLang >> 5) & 0x1F) + 0x60;
682 		lang3[2] = (packedLang & 0x1F) + 0x60;
683 
684 		XMP_StringPtr lang2  = Lookup2LetterLang ( lang3 );
685 		if ( *lang2 == 0 ) continue;	// No language mapping to XMP.
686 		haveMappings = true;
687 
688 		bool xmpFound = xmp.GetLocalizedText ( kXMP_NS_DC, "rights", lang2, lang2, &xmpLang, &xmpValue, 0 );
689 		if ( xmpFound ) {
690 			if ( (xmpLang.size() < 2) ||
691 				 ( (xmpLang.size() == 2) && (xmpLang != lang2) ) ||
692 				 ( (xmpLang.size() > 2) && ( (xmpLang[2] != '-') || (! XMP_LitNMatch ( xmpLang.c_str(), lang2, 2)) ) ) ) {
693 				xmpFound = false;	// The language does not match, the corresponding XMP does not exist.
694 			}
695 		}
696 
697 		if ( ! xmpFound ) {
698 
699 			// No XMP, delete the ISO item.
700 			moovMgr->DeleteNthChild ( udtaRef, ordinal-1 );
701 
702 		} else {
703 
704 			// Update the ISO item if necessary.
705 			XMP_StringPtr isoStr = (XMP_StringPtr)cprtInfo.content + 6;
706 			size_t rawLen = cprtInfo.contentSize - 6;
707 			if ( (rawLen >= 8) && (GetUns16BE(isoStr) == 0xFEFF) ) {
708 				FromUTF16 ( (UTF16Unit*)(isoStr+2), (rawLen-2)/2, &tempStr, true /* big endian */ );
709 				isoStr = tempStr.c_str();
710 			}
711 			if ( xmpValue != isoStr ) {
712 				std::string newContent = "vfffll";
713 				newContent += xmpValue;
714 				memcpy ( (char*)newContent.c_str(), cprtInfo.content, 6 ); // Keep old version, flags, and language.
715 				moovMgr->SetBox ( cprtRef, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) );
716 			}
717 
718 		}
719 
720 	}
721 
722 	// Go through the XMP items and look for a corresponding ISO item. Skip if found (did it above),
723 	// otherwise add a new ISO item.
724 
725 	/*bool haveXDefault = false;*/
726 	XMP_Index xmpCount = xmp.CountArrayItems ( kXMP_NS_DC, "rights" );
727 
728 	for ( XMP_Index xmpIndex = 1; xmpIndex <= xmpCount; ++xmpIndex ) {	// ! The first XMP array index is 1.
729 
730 		SXMPUtils::ComposeArrayItemPath ( kXMP_NS_DC, "rights", xmpIndex, &xmpPath );
731 		xmp.GetArrayItem ( kXMP_NS_DC, "rights", xmpIndex, &xmpValue, 0 );
732 		bool hasLang = xmp.GetQualifier ( kXMP_NS_DC, xmpPath.c_str(), kXMP_NS_XML, "lang", &xmpLang, 0 );
733 		if ( ! hasLang ) continue;	// Sanity check.
734 		if ( xmpLang == "x-default" ) {
735 			/*haveXDefault = true;*/	// See later special case.
736 			continue;
737 		}
738 
739 		XMP_StringPtr isoLang = "";
740 		size_t rootLen = xmpLang.find ( '-' );
741 		if ( rootLen == std::string::npos ) rootLen = xmpLang.size();
742 		if ( rootLen == 2 ) {
743 			if( xmpLang.size() > 2 ) xmpLang[2] = 0;
744 			isoLang = Lookup3LetterLang ( xmpLang.c_str() );
745 			if ( *isoLang == 0 ) continue;
746 		} else if ( rootLen == 3 ) {
747 			if( xmpLang.size() > 3 ) xmpLang[3] = 0;
748 			isoLang = xmpLang.c_str();
749 		} else {
750 			continue;
751 		}
752 		haveMappings = true;
753 
754 		bool isoFound = false;
755 		XMP_Uns16 packedLang = ((isoLang[0] - 0x60) << 10) | ((isoLang[1] - 0x60) << 5) | (isoLang[2] - 0x60);
756 
757 		for ( XMP_Uns32 isoIndex = 0; (isoIndex < udtaInfo.childCount) && (! isoFound); ++isoIndex ) {
758 
759 			MOOV_Manager::BoxInfo cprtInfo;
760 			MOOV_Manager::BoxRef  cprtRef = moovMgr->GetNthChild ( udtaRef, isoIndex, &cprtInfo );
761 			if ( cprtRef == 0 ) break;	// Sanity check, should not happen.
762 			if ( (cprtInfo.boxType != ISOMedia::k_cprt) || (cprtInfo.contentSize < 6) ) continue;
763 			if ( *cprtInfo.content != 0 ) continue;	// Only accept version 0, ignore the flags.
764 			if ( packedLang != GetUns16BE ( cprtInfo.content + 4 ) ) continue;	// Look for matching language.
765 
766 			isoFound = true;	// Found the language entry, whether or not we update it.
767 
768 		}
769 
770 		if ( ! isoFound ) {
771 
772 			std::string newContent = "vfffll";
773 			newContent += xmpValue;
774 			*((XMP_Uns32*)newContent.c_str()) = 0;	// Set the version and flags to zero.
775 			PutUns16BE ( packedLang, (char*)newContent.c_str() + 4 );
776 			moovMgr->AddChildBox ( udtaRef, ISOMedia::k_cprt, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) );
777 
778 		}
779 
780 	}
781 
782 	// If there were no mappings in the loops, export the XMP "x-default" value to the first ISO item.
783 
784 	if ( ! haveMappings ) {
785 
786 		MOOV_Manager::BoxInfo cprtInfo;
787 		MOOV_Manager::BoxRef  cprtRef = moovMgr->GetTypeChild ( udtaRef, ISOMedia::k_cprt, &cprtInfo );
788 
789 		if ( (cprtRef != 0) && (cprtInfo.contentSize >= 6) && (*cprtInfo.content == 0) ) {
790 
791 			bool xmpFound = xmp.GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", &xmpLang, &xmpValue, 0 );
792 
793 			if ( xmpFound && (xmpLang == "x-default") ) {
794 
795 				// Update the ISO item if necessary.
796 				XMP_StringPtr isoStr = (XMP_StringPtr)cprtInfo.content + 6;
797 				size_t rawLen = cprtInfo.contentSize - 6;
798 				if ( (rawLen >= 8) && (GetUns16BE(isoStr) == 0xFEFF) ) {
799 					FromUTF16 ( (UTF16Unit*)(isoStr+2), (rawLen-2)/2, &tempStr, true /* big endian */ );
800 					isoStr = tempStr.c_str();
801 				}
802 				if ( xmpValue != isoStr ) {
803 					std::string newContent = "vfffll";
804 					newContent += xmpValue;
805 					memcpy ( (char*)newContent.c_str(), cprtInfo.content, 6 ); // Keep old version, flags, and language.
806 					moovMgr->SetBox ( cprtRef, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) );
807 				}
808 
809 			}
810 
811 		}
812 
813 	}
814 
815 }	// ExportISOCopyrights
816 
817 // =================================================================================================
818 // ExportQuickTimeItems
819 // ====================
820 
ExportQuickTimeItems(const SXMPMeta & xmp,TradQT_Manager * qtMgr,MOOV_Manager * moovMgr)821 static void ExportQuickTimeItems ( const SXMPMeta & xmp, TradQT_Manager * qtMgr, MOOV_Manager * moovMgr )
822 {
823 
824 	// The QuickTime 'udta' timecode items are done here for simplicity.
825 
826 	#define createWithZeroLang true
827 
828 	qtMgr->ExportSimpleXMP ( kQTilst_Reel, xmp, kXMP_NS_DM, "tapeName" );
829 	qtMgr->ExportSimpleXMP ( kQTilst_Timecode, xmp, kXMP_NS_DM, "startTimecode/xmpDM:timeValue", createWithZeroLang );
830 	qtMgr->ExportSimpleXMP ( kQTilst_TimeScale, xmp, kXMP_NS_DM, "startTimeScale", createWithZeroLang );
831 	qtMgr->ExportSimpleXMP ( kQTilst_TimeSize, xmp, kXMP_NS_DM, "startTimeSampleSize", createWithZeroLang );
832 
833 	qtMgr->UpdateChangedBoxes ( moovMgr );
834 
835 }	// ExportQuickTimeItems
836 
837 // =================================================================================================
838 // SelectTimeFormat
839 // ================
840 
SelectTimeFormat(const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo)841 static const char * SelectTimeFormat ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo )
842 {
843 	const char * timeFormat = 0;
844 
845 	float fltFPS = (float)tmcdInfo.timeScale / (float)tmcdInfo.frameDuration;
846 	int   intFPS = (int) (fltFPS + 0.5);
847 
848 	switch ( intFPS ) {
849 
850 		case 30:
851 			if ( fltFPS >= 29.985 ) {
852 				timeFormat = "30Timecode";
853 			} else if ( tmcdInfo.isDropFrame ) {
854 				timeFormat = "2997DropTimecode";
855 			} else {
856 				timeFormat = "2997NonDropTimecode";
857 			}
858 			break;
859 
860 		case 24:
861 			if ( fltFPS >= 23.988 ) {
862 				timeFormat = "24Timecode";
863 			} else {
864 				timeFormat = "23976Timecode";
865 			}
866 			break;
867 
868 		case 25:
869 			timeFormat = "25Timecode";
870 			break;
871 
872 		case 50:
873 			timeFormat = "50Timecode";
874 			break;
875 
876 		case 60:
877 			if ( fltFPS >= 59.97 ) {
878 				timeFormat = "60Timecode";
879 			} else if ( tmcdInfo.isDropFrame ) {
880 				timeFormat = "5994DropTimecode";
881 			} else {
882 				timeFormat = "5994NonDropTimecode";
883 			}
884 			break;
885 
886 	}
887 
888 	return timeFormat;
889 
890 }	// SelectTimeFormat
891 
892 // =================================================================================================
893 // SelectTimeFormat
894 // ================
895 
SelectTimeFormat(const SXMPMeta & xmp)896 static const char * SelectTimeFormat ( const SXMPMeta & xmp )
897 {
898 	bool ok;
899 	MPEG4_MetaHandler::TimecodeTrackInfo tmcdInfo;
900 
901 	XMP_Int64 timeScale;
902 	ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", &timeScale, 0 );
903 	if ( ! ok ) return 0;
904 	tmcdInfo.timeScale = (XMP_Uns32)timeScale;
905 
906 	XMP_Int64 frameDuration;
907 	ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", &frameDuration, 0 );
908 	if ( ! ok ) return 0;
909 	tmcdInfo.frameDuration = (XMP_Uns32)frameDuration;
910 
911 	std::string timecode;
912 	ok = xmp.GetProperty ( kXMP_NS_DM, "startTimecode/xmpDM:timeValue", &timecode, 0 );
913 	if ( ! ok ) return 0;
914 	if ( (timecode.size() == 11) && (timecode[8] == ';') ) tmcdInfo.isDropFrame = true;
915 
916 	return SelectTimeFormat ( tmcdInfo );
917 
918 }	// SelectTimeFormat
919 
920 // =================================================================================================
921 // ComposeTimecode
922 // ===============
923 
924 static const char * kDecDigits = "0123456789";
925 #define TwoDigits(val,str)	(str)[0] = kDecDigits[(val)/10]; (str)[1] = kDecDigits[(val)%10]
926 
ComposeTimecode(const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo,std::string * strValue)927 static bool ComposeTimecode ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo, std::string * strValue )
928 {
929 	float fltFPS = (float)tmcdInfo.timeScale / (float)tmcdInfo.frameDuration;
930 	int   intFPS = (int) (fltFPS + 0.5);
931 	if ( (intFPS != 30) && (intFPS != 24) && (intFPS != 25) && (intFPS != 50) && (intFPS != 60) ) return false;
932 
933 	XMP_Uns32 framesPerDay = intFPS * 24*60*60;
934 	XMP_Uns32 dropLimit = 2;	// Used in the drop-frame correction.
935 
936 	if ( tmcdInfo.isDropFrame ) {
937 		if ( intFPS == 30 ) {
938 			framesPerDay = 2589408;	// = 29.97 * 24*60*60
939 		} else if ( intFPS == 60 ) {
940 			framesPerDay = 5178816;	// = 59.94 * 24*60*60
941 			dropLimit = 4;
942 		} else {
943 			strValue->erase();
944 			return false;	// Dropframe can only apply to 29.97 and 59.94.
945 		}
946 	}
947 
948 	XMP_Uns32 framesPerHour = framesPerDay / 24;
949 	XMP_Uns32 framesPerTenMinutes = framesPerHour / 6;
950 	XMP_Uns32 framesPerMinute = framesPerTenMinutes / 10;
951 
952 	XMP_Uns32 frameCount = tmcdInfo.timecodeSample;
953 	while (frameCount >= framesPerDay ) frameCount -= framesPerDay;	// Normalize to be within 24 hours.
954 
955 	XMP_Uns32 hours, minHigh, minLow, seconds;
956 
957 	hours = frameCount / framesPerHour;
958 	frameCount -= (hours * framesPerHour);
959 
960 	minHigh = frameCount / framesPerTenMinutes;
961 	frameCount -= (minHigh * framesPerTenMinutes);
962 
963 	minLow = frameCount / framesPerMinute;
964 	frameCount -= (minLow * framesPerMinute);
965 
966 	// Do some drop-frame corrections at this point: If this is drop-frame and the units of minutes
967 	// is non-zero, and the seconds are zero, and the frames are zero or one, the time is illegal.
968 	// Perform correction by subtracting 1 from the units of minutes and adding 1798 to the frames.�
969 	// For example, 1:00:00 becomes 59:28, and 1:00:01 becomes 59:29. A special case can occur for
970 	// when the frameCount just before the minHigh calculation is less than framesPerTenMinutes but
971 	// more than 10*framesPerMinute. This happens because of roundoff, and will result in a minHigh
972 	// of 0 and a minLow of 10.�The drop frame correction must�also be performed for this case.
973 
974 	if ( tmcdInfo.isDropFrame ) {
975 		if ( (minLow == 10) || ((minLow != 0) && (frameCount < dropLimit)) ) {
976 			minLow -= 1;
977 			frameCount += framesPerMinute;
978 		}
979 	}
980 
981 	seconds = frameCount / intFPS;
982 	frameCount -= (seconds * intFPS);
983 
984 	if ( tmcdInfo.isDropFrame ) {
985 		*strValue = "hh;mm;ss;ff";
986 	} else {
987 		*strValue = "hh:mm:ss:ff";
988 	}
989 
990 	char * str = (char*)strValue->c_str();
991 	TwoDigits ( hours, str );
992 	str[3] = kDecDigits[minHigh]; str[4] = kDecDigits[minLow];
993 	TwoDigits ( seconds, str+6 );
994 	TwoDigits ( frameCount, str+9 );
995 
996 	return true;
997 
998 }	// ComposeTimecode
999 
1000 // =================================================================================================
1001 // DecomposeTimecode
1002 // =================
1003 
DecomposeTimecode(const char * strValue,MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo)1004 static bool DecomposeTimecode ( const char * strValue, MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo )
1005 {
1006 	float fltFPS = (float)tmcdInfo->timeScale / (float)tmcdInfo->frameDuration;
1007 	int   intFPS = (int) (fltFPS + 0.5);
1008 	if ( (intFPS != 30) && (intFPS != 24) && (intFPS != 25) && (intFPS != 50) && (intFPS != 60) ) return false;
1009 
1010 	XMP_Uns32 framesPerDay = intFPS * 24*60*60;
1011 
1012 	int items, hours, minutes, seconds, frames;
1013 
1014 	if ( ! tmcdInfo->isDropFrame ) {
1015 		items = sscanf ( strValue, "%d:%d:%d:%d", &hours, &minutes, &seconds, &frames );
1016 	} else {
1017 		items = sscanf ( strValue, "%d;%d;%d;%d", &hours, &minutes, &seconds, &frames );
1018 		if ( intFPS == 30 ) {
1019 			framesPerDay = 2589408;	// = 29.97 * 24*60*60
1020 		} else if ( intFPS == 60 ) {
1021 			framesPerDay = 5178816;	// = 59.94 * 24*60*60
1022 		} else {
1023 			return false;	// Dropframe can only apply to 29.97 and 59.94.
1024 		}
1025 	}
1026 
1027 	if ( items != 4 ) return false;
1028 	int minHigh = minutes / 10;
1029 	int minLow = minutes % 10;
1030 
1031 	XMP_Uns32 framesPerHour = framesPerDay / 24;
1032 	XMP_Uns32 framesPerTenMinutes = framesPerHour / 6;
1033 	XMP_Uns32 framesPerMinute = framesPerTenMinutes / 10;
1034 
1035 	tmcdInfo->timecodeSample = (hours * framesPerHour) + (minHigh * framesPerTenMinutes) +
1036 							   (minLow * framesPerMinute) + (seconds * intFPS) + frames;
1037 
1038 	return true;
1039 
1040 }	// DecomposeTimecode
1041 
1042 // =================================================================================================
1043 // FindTimecode_trak
1044 // =================
1045 //
1046 // Look for a well-formed timecode track, return the trak box ref.
1047 
FindTimecode_trak(const MOOV_Manager & moovMgr)1048 static MOOV_Manager::BoxRef FindTimecode_trak ( const MOOV_Manager & moovMgr )
1049 {
1050 
1051 	// Find a 'trak' box with a handler type of 'tmcd'.
1052 
1053 	MOOV_Manager::BoxInfo moovInfo;
1054 	MOOV_Manager::BoxRef  moovRef = moovMgr.GetBox ( "moov", &moovInfo );
1055 	XMP_Assert ( moovRef != 0 );
1056 
1057 	MOOV_Manager::BoxInfo trakInfo;
1058 	MOOV_Manager::BoxRef  trakRef;
1059 
1060 	size_t i = 0;
1061 	for ( ; i < moovInfo.childCount; ++i ) {
1062 
1063 		trakRef = moovMgr.GetNthChild ( moovRef, i, &trakInfo );
1064 		if ( trakRef == 0 ) return 0;	// Sanity check, should not happen.
1065 		if ( trakInfo.boxType != ISOMedia::k_trak ) continue;
1066 
1067 		MOOV_Manager::BoxRef  innerRef;
1068 		MOOV_Manager::BoxInfo innerInfo;
1069 
1070 		innerRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &innerInfo );
1071 		if ( innerRef == 0 ) continue;
1072 
1073 		innerRef = moovMgr.GetTypeChild ( innerRef, ISOMedia::k_hdlr, &innerInfo );
1074 		if ( (innerRef == 0) || (innerInfo.contentSize < sizeof ( MOOV_Manager::Content_hdlr )) ) continue;
1075 
1076 		const MOOV_Manager::Content_hdlr * hdlr = (MOOV_Manager::Content_hdlr*) innerInfo.content;
1077 		if ( hdlr->versionFlags != 0 ) continue;
1078 		if ( GetUns32BE ( &hdlr->handlerType ) == ISOMedia::k_tmcd ) break;
1079 
1080 	}
1081 
1082 	if ( i == moovInfo.childCount ) return 0;
1083 	return trakRef;
1084 
1085 }	// FindTimecode_trak
1086 
1087 // =================================================================================================
1088 // FindTimecode_dref
1089 // =================
1090 //
1091 // Look for the mdia/minf/dinf/dref box within a well-formed timecode track, return the dref box ref.
1092 
FindTimecode_dref(const MOOV_Manager & moovMgr)1093 static MOOV_Manager::BoxRef FindTimecode_dref ( const MOOV_Manager & moovMgr )
1094 {
1095 
1096 	MOOV_Manager::BoxRef trakRef = FindTimecode_trak ( moovMgr );
1097 	if ( trakRef == 0 ) return 0;
1098 
1099 	MOOV_Manager::BoxInfo tempInfo;
1100 	MOOV_Manager::BoxRef  tempRef, drefRef;
1101 
1102 	tempRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &tempInfo );
1103 	if ( tempRef == 0 ) return 0;
1104 
1105 	tempRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_minf, &tempInfo );
1106 	if ( tempRef == 0 ) return 0;
1107 
1108 	tempRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_dinf, &tempInfo );
1109 	if ( tempRef == 0 ) return 0;
1110 
1111 	drefRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_dref, &tempInfo );
1112 
1113 	return drefRef;
1114 
1115 }	// FindTimecode_dref
1116 
1117 // =================================================================================================
1118 // FindTimecode_stbl
1119 // =================
1120 //
1121 // Look for the mdia/minf/stbl box within a well-formed timecode track, return the stbl box ref.
1122 
FindTimecode_stbl(const MOOV_Manager & moovMgr)1123 static MOOV_Manager::BoxRef FindTimecode_stbl ( const MOOV_Manager & moovMgr )
1124 {
1125 
1126 	MOOV_Manager::BoxRef trakRef = FindTimecode_trak ( moovMgr );
1127 	if ( trakRef == 0 ) return 0;
1128 
1129 	MOOV_Manager::BoxInfo tempInfo;
1130 	MOOV_Manager::BoxRef  tempRef, stblRef;
1131 
1132 	tempRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &tempInfo );
1133 	if ( tempRef == 0 ) return 0;
1134 
1135 	tempRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_minf, &tempInfo );
1136 	if ( tempRef == 0 ) return 0;
1137 
1138 	stblRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stbl, &tempInfo );
1139 	return stblRef;
1140 
1141 }	// FindTimecode_stbl
1142 
1143 // =================================================================================================
1144 // FindTimecode_elst
1145 // =================
1146 //
1147 // Look for the edts/elst box within a well-formed timecode track, return the elst box ref.
1148 
FindTimecode_elst(const MOOV_Manager & moovMgr)1149 static MOOV_Manager::BoxRef FindTimecode_elst ( const MOOV_Manager & moovMgr )
1150 {
1151 
1152 	MOOV_Manager::BoxRef trakRef = FindTimecode_trak ( moovMgr );
1153 	if ( trakRef == 0 ) return 0;
1154 
1155 	MOOV_Manager::BoxInfo tempInfo;
1156 	MOOV_Manager::BoxRef  tempRef, elstRef;
1157 
1158 	tempRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_edts, &tempInfo );
1159 	if ( tempRef == 0 ) return 0;
1160 
1161 	elstRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_elst, &tempInfo );
1162 	return elstRef;
1163 
1164 }	// FindTimecode_elst
1165 
1166 // =================================================================================================
1167 // ImportTimecodeItems
1168 // ===================
1169 
ImportTimecodeItems(const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo,const TradQT_Manager & qtInfo,SXMPMeta * xmp)1170 static bool ImportTimecodeItems ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo,
1171 								  const TradQT_Manager & qtInfo, SXMPMeta * xmp )
1172 {
1173 	std::string xmpValue;
1174 	bool haveItem;
1175 	bool haveImports = false;
1176 
1177 	// The QT user data item '�REL' goes into xmpDM:tapeName, and the 'name' box at the end of the
1178 	// timecode sample description goes into xmpDM:altTapeName.
1179 	haveImports |= qtInfo.ImportSimpleXMP ( kQTilst_Reel, xmp, kXMP_NS_DM, "tapeName" );
1180 	if ( ! tmcdInfo.macName.empty() ) {
1181 		haveItem = ConvertFromMacLang ( tmcdInfo.macName, tmcdInfo.macLang, &xmpValue );
1182 		if ( haveItem ) {
1183 			xmp->SetProperty ( kXMP_NS_DM, "altTapeName", xmpValue.c_str() );
1184 			haveImports = true;
1185 		}
1186 	}
1187 
1188 	// The QT user data item '�TSC' goes into xmpDM:startTimeScale. If that isn't present, then
1189 	// the timecode sample description's timeScale is used.
1190 	haveItem = qtInfo.ImportSimpleXMP ( kQTilst_TimeScale, xmp, kXMP_NS_DM, "startTimeScale" );
1191 	if ( tmcdInfo.stsdBoxFound & (! haveItem) ) {
1192 		xmp->SetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", tmcdInfo.timeScale );
1193 		haveItem = true;
1194 	}
1195 	haveImports |= haveItem;
1196 
1197 	// The QT user data item '�TSZ' goes into xmpDM:startTimeSampleSize. If that isn't present, then
1198 	// the timecode sample description's frameDuration is used.
1199 	haveItem = qtInfo.ImportSimpleXMP ( kQTilst_TimeSize, xmp, kXMP_NS_DM, "startTimeSampleSize" );
1200 	if ( tmcdInfo.stsdBoxFound & (! haveItem) ) {
1201 		xmp->SetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", tmcdInfo.frameDuration );
1202 		haveItem = true;
1203 	}
1204 	haveImports |= haveItem;
1205 
1206 	const char * timeFormat;
1207 
1208 	// The Timecode struct type is used for xmpDM:startTimecode and xmpDM:altTimecode. For both, only
1209 	// the xmpDM:timeValue and xmpDM:timeFormat fields are set.
1210 
1211 	// The QT user data item '�TIM' goes into xmpDM:startTimecode/xmpDM:timeValue. This is an already
1212 	// formatted timecode string. The XMP values of xmpDM:startTimeScale, xmpDM:startTimeSampleSize,
1213 	// and xmpDM:startTimecode/xmpDM:timeValue are used to select the timeFormat value.
1214 	haveImports |= qtInfo.ImportSimpleXMP ( kQTilst_Timecode, xmp, kXMP_NS_DM, "startTimecode/xmpDM:timeValue" );
1215 	timeFormat = SelectTimeFormat ( *xmp );
1216 	if ( timeFormat != 0 ) {
1217 		xmp->SetProperty ( kXMP_NS_DM, "startTimecode/xmpDM:timeFormat", timeFormat );
1218 		haveImports = true;
1219 	}
1220 
1221 	if ( tmcdInfo.stsdBoxFound ) {
1222 
1223 		haveItem = ComposeTimecode ( tmcdInfo, &xmpValue );
1224 		if ( haveItem ) {
1225 			xmp->SetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeValue", xmpValue.c_str() );
1226 			haveImports = true;
1227 		}
1228 
1229 		timeFormat = SelectTimeFormat ( tmcdInfo );
1230 		if ( timeFormat != 0 ) {
1231 			xmp->SetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeFormat", timeFormat );
1232 			haveImports = true;
1233 		}
1234 
1235 	}
1236 
1237 	return haveImports;
1238 
1239 }	// ImportTimecodeItems
1240 
1241 // =================================================================================================
1242 // ExportTimecodeItems
1243 // ===================
1244 
ExportTimecodeItems(const SXMPMeta & xmp,MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo,TradQT_Manager *,MOOV_Manager * moovMgr)1245 static void ExportTimecodeItems ( const SXMPMeta & xmp, MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo,
1246 								  TradQT_Manager * /*qtMgr*/, MOOV_Manager * moovMgr )
1247 {
1248 	// Export the items that go into the timecode track:
1249 	//  - the timescale and frame duration in the first 'stsd' table entry
1250 	//  - the 'name' box appended to the first 'stsd' table entry
1251 	//  - the first timecode sample
1252 	// ! The QuickTime 'udta' timecode items are handled in ExportQuickTimeItems.
1253 
1254 	if ( ! tmcdInfo->stsdBoxFound ) return;	// Don't make changes unless there is a well-formed timecode track.
1255 
1256 	MOOV_Manager::BoxRef stblRef = FindTimecode_stbl ( *moovMgr );
1257 	if ( stblRef == 0 ) return;
1258 
1259 	MOOV_Manager::BoxInfo stsdInfo;
1260 	MOOV_Manager::BoxRef  stsdRef;
1261 
1262 	stsdRef = moovMgr->GetTypeChild ( stblRef, ISOMedia::k_stsd, &stsdInfo );
1263 	if ( stsdRef == 0 ) return;
1264 	if ( stsdInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsd_entry )) ) return;
1265 	if ( GetUns32BE ( stsdInfo.content + 4 ) == 0 ) return;	// Make sure the entry count is non-zero.
1266 
1267 	MOOV_Manager::Content_stsd_entry * stsdRawEntry = (MOOV_Manager::Content_stsd_entry*) (stsdInfo.content + 8);
1268 
1269 	XMP_Uns32 stsdEntrySize = GetUns32BE ( &stsdRawEntry->entrySize );
1270 	if ( stsdEntrySize > (stsdInfo.contentSize - 4) ) stsdEntrySize = stsdInfo.contentSize - 4;
1271 	if ( stsdEntrySize < sizeof ( MOOV_Manager::Content_stsd_entry ) ) return;
1272 
1273 	bool ok, haveScale = false, haveDuration = false;
1274 	std::string xmpValue;
1275 	XMP_Int64 int64;	// Used to allow UInt32 values, GetProperty_Int is SInt32.
1276 
1277 	// The tmcdInfo timeScale field is set from xmpDM:startTimeScale.
1278 	 ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", &int64, 0 );
1279 	 if ( ok && (int64 <= 0xFFFFFFFF) ) {
1280 	 	haveScale = true;
1281 	 	if ( tmcdInfo->timeScale != 0 ) { // Entry must not be created if not existing before
1282 			tmcdInfo->timeScale = (XMP_Uns32)int64;
1283 			PutUns32BE ( tmcdInfo->timeScale, (void*)&stsdRawEntry->timeScale );
1284 			moovMgr->NoteChange();
1285 	 	}
1286 	 }
1287 
1288 	// The tmcdInfo frameDuration field is set from xmpDM:startTimeSampleSize.
1289 	 ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", &int64, 0 );
1290 	 if ( ok && (int64 <= 0xFFFFFFFF) ) {
1291 	 	haveDuration = true;
1292 	 	if ( tmcdInfo->frameDuration != 0 ) { // Entry must not be created if not existing before
1293 			tmcdInfo->frameDuration = (XMP_Uns32)int64;
1294 			PutUns32BE ( tmcdInfo->frameDuration, (void*)&stsdRawEntry->frameDuration );
1295 			moovMgr->NoteChange();
1296 	 	}
1297 	 }
1298 
1299 	 // The tmcdInfo frameCount field is a simple ratio of the timeScale and frameDuration.
1300 	 if ( (haveScale & haveDuration) && (tmcdInfo->frameDuration != 0) ) {
1301 	 	float floatScale = (float) tmcdInfo->timeScale;
1302 	 	float floatDuration = (float) tmcdInfo->frameDuration;
1303 		XMP_Uns8 newCount = (XMP_Uns8) ( (floatScale / floatDuration) + 0.5 );
1304 		if ( newCount != stsdRawEntry->frameCount ) {
1305 			stsdRawEntry->frameCount = newCount;
1306 			moovMgr->NoteChange();
1307 		}
1308 	 }
1309 
1310 	// The tmcdInfo isDropFrame flag is set from xmpDM:altTimecode/xmpDM:timeValue. The timeScale
1311 	// and frameDuration must be updated first, they are used by DecomposeTimecode. Compute the new
1312 	// UInt32 timecode sample, but it gets written to the file later by UpdateFile.
1313 
1314 	ok = xmp.GetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeValue", &xmpValue, 0 );
1315 	if ( ok && (xmpValue.size() == 11) ) {
1316 
1317 		bool oldDropFrame = tmcdInfo->isDropFrame;
1318 		tmcdInfo->isDropFrame = false;
1319 		if ( xmpValue[8] == ';' ) tmcdInfo->isDropFrame = true;
1320 		if ( oldDropFrame != tmcdInfo->isDropFrame ) {
1321 			XMP_Uns32 flags = GetUns32BE ( &stsdRawEntry->flags );
1322 			flags = (flags & 0xFFFFFFFE) | (XMP_Uns32)tmcdInfo->isDropFrame;
1323 			PutUns32BE ( flags, (void*)&stsdRawEntry->flags );
1324 		 	moovMgr->NoteChange();
1325 		}
1326 
1327 		XMP_Uns32 oldSample = tmcdInfo->timecodeSample;
1328 		ok = DecomposeTimecode ( xmpValue.c_str(), tmcdInfo );
1329 	 	if ( ok && (oldSample != tmcdInfo->timecodeSample) ) moovMgr->NoteChange();
1330 
1331 	}
1332 
1333 	// The 'name' box attached to the first 'stsd' table entry is set from xmpDM:altTapeName.
1334 
1335 	bool replaceNameBox = false;
1336 
1337 	ok = xmp.GetProperty ( kXMP_NS_DM, "altTapeName", &xmpValue, 0 );
1338 	if ( (! ok) || xmpValue.empty() ) {
1339 		if ( tmcdInfo->nameOffset != 0 ) replaceNameBox = true;	// No XMP, get rid of existing name.
1340 	} else {
1341 		std::string macValue;
1342 		ok = ConvertToMacLang ( xmpValue, tmcdInfo->macLang, &macValue );
1343 		if ( ok && (macValue != tmcdInfo->macName) ) {
1344 			tmcdInfo->macName = macValue;
1345 			replaceNameBox = true;	// Write changed name.
1346 		}
1347 	}
1348 
1349 	if ( replaceNameBox ) {
1350 
1351 		// To replace the 'name' box we have to create an entire new 'stsd' box, and attach the
1352 		// new name to the first 'stsd' table entry. The 'name' box content is a UInt16 text length,
1353 		// UInt16 language code, and Mac encoded text with no nul termination.
1354 
1355 		if ( tmcdInfo->macName.size() > 0xFFFF ) tmcdInfo->macName.erase ( 0xFFFF );
1356 
1357 		ISOMedia::BoxInfo oldNameInfo;
1358 		XMP_Assert ( (oldNameInfo.headerSize == 0) && (oldNameInfo.contentSize == 0) );
1359 		if ( tmcdInfo->nameOffset != 0 ) {
1360 			const XMP_Uns8 * oldNamePtr = stsdInfo.content + tmcdInfo->nameOffset;
1361 			const XMP_Uns8 * oldNameLimit = stsdInfo.content + stsdInfo.contentSize;
1362 			(void) ISOMedia::GetBoxInfo ( oldNamePtr, oldNameLimit, &oldNameInfo );
1363 		}
1364 
1365 		XMP_Uns32 oldNameBoxSize = (XMP_Uns32)oldNameInfo.headerSize + (XMP_Uns32)oldNameInfo.contentSize;
1366 		XMP_Uns32 newNameBoxSize = 0;
1367 		if ( ! tmcdInfo->macName.empty() ) newNameBoxSize = 4+4 + 2+2 + (XMP_Uns32)tmcdInfo->macName.size();
1368 
1369 		XMP_Uns32 stsdNewContentSize = stsdInfo.contentSize - oldNameBoxSize + newNameBoxSize;
1370 		RawDataBlock stsdNewContent;
1371 		stsdNewContent.assign ( stsdNewContentSize, 0 );	// Get the space allocated, direct fill below.
1372 
1373 		XMP_Uns32 stsdPrefixSize = tmcdInfo->nameOffset;
1374 		if ( tmcdInfo->nameOffset == 0 ) stsdPrefixSize = 4+4 + sizeof ( MOOV_Manager::Content_stsd_entry );
1375 
1376 		XMP_Uns32 oldSuffixOffset = stsdPrefixSize + oldNameBoxSize;
1377 		XMP_Uns32 newSuffixOffset = stsdPrefixSize + newNameBoxSize;
1378 		XMP_Uns32 stsdSuffixSize  = stsdInfo.contentSize - oldSuffixOffset;
1379 
1380 		memcpy ( &stsdNewContent[0], stsdInfo.content, stsdPrefixSize );
1381 		if ( stsdSuffixSize != 0 ) memcpy ( &stsdNewContent[newSuffixOffset], (stsdInfo.content + oldSuffixOffset), stsdSuffixSize );
1382 
1383 		XMP_Uns32 newEntrySize = stsdEntrySize - oldNameBoxSize + newNameBoxSize;
1384 		MOOV_Manager::Content_stsd_entry * stsdNewEntry = (MOOV_Manager::Content_stsd_entry*) (&stsdNewContent[0] + 8);
1385 		PutUns32BE ( newEntrySize, &stsdNewEntry->entrySize );
1386 
1387 		if ( newNameBoxSize != 0 ) {
1388 			PutUns32BE ( newNameBoxSize, &stsdNewContent[stsdPrefixSize] );
1389 			PutUns32BE ( ISOMedia::k_name, &stsdNewContent[stsdPrefixSize+4] );
1390 			PutUns16BE ( (XMP_Uns16)tmcdInfo->macName.size(), &stsdNewContent[stsdPrefixSize+8] );
1391 			PutUns16BE ( tmcdInfo->macLang, &stsdNewContent[stsdPrefixSize+10] );
1392 			memcpy ( &stsdNewContent[stsdPrefixSize+12], tmcdInfo->macName.c_str(), tmcdInfo->macName.size() );
1393 		}
1394 
1395 		moovMgr->SetBox ( stsdRef, &stsdNewContent[0], stsdNewContentSize );
1396 
1397 	}
1398 
1399 }	// ExportTimecodeItems
1400 
1401 // =================================================================================================
1402 // ImportCr8rItems
1403 // ===============
1404 
1405 #if SUNOS_SPARC || SUNOS_X86
1406 #pragma pack ( 1 )
1407 #else
1408 #pragma pack ( push, 1 )
1409 #endif //#if SUNOS_SPARC || SUNOS_X86
1410 
1411 struct PrmLBoxContent {
1412 	XMP_Uns32 magic;
1413 	XMP_Uns32 size;
1414 	XMP_Uns16 verAPI;
1415 	XMP_Uns16 verCode;
1416 	XMP_Uns32 exportType;
1417 	XMP_Uns16 MacVRefNum;
1418 	XMP_Uns32 MacParID;
1419 	char filePath[260];
1420 };
1421 
1422 enum { kExportTypeMovie = 0, kExportTypeStill = 1, kExportTypeAudio = 2, kExportTypeCustom = 3 };
1423 
1424 struct Cr8rBoxContent {
1425 	XMP_Uns32 magic;
1426 	XMP_Uns32 size;
1427 	XMP_Uns16 majorVer;
1428 	XMP_Uns16 minorVer;
1429 	XMP_Uns32 creatorCode;
1430 	XMP_Uns32 appleEvent;
1431 	char fileExt[16];
1432 	char appOptions[16];
1433 	char appName[32];
1434 };
1435 
1436 #if SUNOS_SPARC || SUNOS_X86
1437 #pragma pack ( )
1438 #else
1439 #pragma pack ( pop )
1440 #endif //#if SUNOS_SPARC || SUNOS_X86
1441 
1442 // -------------------------------------------------------------------------------------------------
1443 
ImportCr8rItems(const MOOV_Manager & moovMgr,SXMPMeta * xmp)1444 static bool ImportCr8rItems ( const MOOV_Manager & moovMgr, SXMPMeta * xmp )
1445 {
1446 	bool haveXMP = false;
1447 	std::string fieldPath1;
1448 
1449 	MOOV_Manager::BoxInfo infoPrmL, infoCr8r;
1450 	MOOV_Manager::BoxRef  refPrmL = moovMgr.GetBox ( "moov/udta/PrmL", &infoPrmL );
1451 	MOOV_Manager::BoxRef  refCr8r = moovMgr.GetBox ( "moov/udta/Cr8r", &infoCr8r );
1452 
1453 	bool havePrmL = ( (refPrmL != 0) && (infoPrmL.contentSize == sizeof ( PrmLBoxContent )) );
1454 	bool haveCr8r = ( (refCr8r != 0) && (infoCr8r.contentSize == sizeof ( Cr8rBoxContent )) );
1455 
1456 	if ( havePrmL ) {
1457 
1458 		PrmLBoxContent rawPrmL;
1459 		XMP_Assert ( sizeof ( rawPrmL ) == 282 );
1460 		XMP_Assert ( sizeof ( rawPrmL.filePath ) == 260 );
1461 		memcpy ( &rawPrmL, infoPrmL.content, sizeof ( rawPrmL ) );
1462 		if ( rawPrmL.magic != 0xBEEFCAFE ) {
1463 			Flip4 ( &rawPrmL.exportType );	// The only numeric field that we care about.
1464 		}
1465 
1466 		rawPrmL.filePath[259] = 0;	// Ensure a terminating nul.
1467 		if ( rawPrmL.filePath[0] != 0 ) {
1468 			if ( rawPrmL.filePath[0] == '/' ) {
1469 				haveXMP = true;
1470 				SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom",
1471 												    kXMP_NS_CreatorAtom, "posixProjectPath", &fieldPath1 );
1472 				if ( ! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath1.c_str() ) ) {
1473 					xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath1.c_str(), rawPrmL.filePath );
1474 				}
1475 			} else if ( XMP_LitNMatch ( rawPrmL.filePath, "\\\\?\\", 4 ) ) {
1476 				haveXMP = true;
1477 				SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "windowsAtom",
1478 												    kXMP_NS_CreatorAtom, "uncProjectPath", &fieldPath1 );
1479 				if ( ! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath1.c_str() ) ) {
1480 					xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath1.c_str(), rawPrmL.filePath );
1481 				}
1482 			}
1483 		}
1484 
1485 		const char * exportStr = 0;
1486 		switch ( rawPrmL.exportType ) {
1487 			case kExportTypeMovie  : exportStr = "movie";  break;
1488 			case kExportTypeStill  : exportStr = "still";  break;
1489 			case kExportTypeAudio  : exportStr = "audio";  break;
1490 			case kExportTypeCustom : exportStr = "custom"; break;
1491 		}
1492 		if ( exportStr != 0 ) {
1493 			haveXMP = true;
1494 			SXMPUtils::ComposeStructFieldPath ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", &fieldPath1 );
1495 			if ( ! xmp->DoesPropertyExist ( kXMP_NS_DM, fieldPath1.c_str() ) ) {
1496 				xmp->SetProperty ( kXMP_NS_DM, fieldPath1.c_str(), exportStr );
1497 			}
1498 		}
1499 
1500 	}
1501 
1502 	if ( haveCr8r ) {
1503 
1504 		Cr8rBoxContent rawCr8r;
1505 		XMP_Assert ( sizeof ( rawCr8r ) == 84 );
1506 		XMP_Assert ( sizeof ( rawCr8r.fileExt ) == 16 );
1507 		XMP_Assert ( sizeof ( rawCr8r.appOptions ) == 16 );
1508 		XMP_Assert ( sizeof ( rawCr8r.appName ) == 32 );
1509 		memcpy ( &rawCr8r, infoCr8r.content, sizeof ( rawCr8r ) );
1510 		if ( rawCr8r.magic != 0xBEEFCAFE ) {
1511 			Flip4 ( &rawCr8r.creatorCode );	// The only numeric fields that we care about.
1512 			Flip4 ( &rawCr8r.appleEvent );
1513 		}
1514 
1515 		std::string fieldPath;
1516 
1517 		SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &fieldPath );
1518 		if ( (rawCr8r.creatorCode != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) {
1519 			haveXMP = true;
1520 			xmp->SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.creatorCode );	// ! Unsigned trickery.
1521 		}
1522 
1523 		SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &fieldPath );
1524 		if ( (rawCr8r.appleEvent != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) {
1525 			haveXMP = true;
1526 			xmp->SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.appleEvent );	// ! Unsigned trickery.
1527 		}
1528 
1529 		rawCr8r.fileExt[15] = 0;	// Ensure a terminating nul.
1530 		SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fieldPath );
1531 		if ( (rawCr8r.fileExt[0] != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) {
1532 			haveXMP = true;
1533 			xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath.c_str(), rawCr8r.fileExt );
1534 		}
1535 
1536 		rawCr8r.appOptions[15] = 0;	// Ensure a terminating nul.
1537 		SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &fieldPath );
1538 		if ( (rawCr8r.appOptions[0] != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) {
1539 			haveXMP = true;
1540 			xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath.c_str(), rawCr8r.appOptions );
1541 		}
1542 
1543 		rawCr8r.appName[31] = 0;	// Ensure a terminating nul.
1544 		if ( (rawCr8r.appName[0] != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_XMP, "CreatorTool" )) ) {
1545 			haveXMP = true;
1546 			xmp->SetProperty ( kXMP_NS_XMP, "CreatorTool", rawCr8r.appName );
1547 		}
1548 
1549 	}
1550 
1551 	return haveXMP;
1552 
1553 }	// ImportCr8rItems
1554 
1555 // =================================================================================================
1556 // ExportCr8rItems
1557 // ===============
1558 
SetBufferedString(char * dest,const std::string source,size_t limit)1559 static inline void SetBufferedString ( char * dest, const std::string source, size_t limit )
1560 {
1561 	memset ( dest, 0, limit );
1562 	size_t count = source.size();
1563 	if ( count >= limit ) count = limit - 1;	// Ensure a terminating nul.
1564 	memcpy ( dest, source.c_str(), count );
1565 }
1566 
1567 // -------------------------------------------------------------------------------------------------
1568 
ExportCr8rItems(const SXMPMeta & xmp,MOOV_Manager * moovMgr)1569 static void ExportCr8rItems ( const SXMPMeta & xmp, MOOV_Manager * moovMgr )
1570 {
1571 	bool haveNewCr8r = false;
1572 	std::string creatorCode, appleEvent, fileExt, appOptions, appName;
1573 
1574 	haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &creatorCode, 0 );
1575 	haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &appleEvent, 0 );
1576 	haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fileExt, 0 );
1577 	haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &appOptions, 0 );
1578 	haveNewCr8r |= xmp.GetProperty ( kXMP_NS_XMP, "CreatorTool", &appName, 0 );
1579 
1580 	MOOV_Manager::BoxInfo infoCr8r;
1581 	MOOV_Manager::BoxRef  refCr8r = moovMgr->GetBox ( "moov/udta/Cr8r", &infoCr8r );
1582 	bool haveOldCr8r = ( (refCr8r != 0) && (infoCr8r.contentSize == sizeof ( Cr8rBoxContent )) );
1583 
1584 	if ( ! haveNewCr8r ) {
1585 		if ( haveOldCr8r ) {
1586 			MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", 0 );
1587 			moovMgr->DeleteTypeChild ( udtaRef, 0x43723872 /* 'Cr8r' */ );
1588 		}
1589 		return;
1590 	}
1591 
1592 	Cr8rBoxContent newCr8r;
1593 	const Cr8rBoxContent * oldCr8r = (Cr8rBoxContent*) infoCr8r.content;
1594 
1595 	if ( ! haveOldCr8r ) {
1596 
1597 		memset ( &newCr8r, 0, sizeof(newCr8r) );
1598 		newCr8r.magic = MakeUns32BE ( 0xBEEFCAFE );
1599 		newCr8r.size = MakeUns32BE ( sizeof ( newCr8r ) );
1600 		newCr8r.majorVer = MakeUns16BE ( 1 );
1601 
1602 	} else {
1603 
1604 		memcpy ( &newCr8r, oldCr8r, sizeof(newCr8r) );
1605 		if ( GetUns32BE ( &newCr8r.magic ) != 0xBEEFCAFE ) {	// Make sure we write BE numbers.
1606 			Flip4 ( &newCr8r.magic );
1607 			Flip4 ( &newCr8r.size );
1608 			Flip2 ( &newCr8r.majorVer );
1609 			Flip2 ( &newCr8r.minorVer );
1610 			Flip4 ( &newCr8r.creatorCode );
1611 			Flip4 ( &newCr8r.appleEvent );
1612 		}
1613 
1614 	}
1615 
1616 	if ( ! creatorCode.empty() ) {
1617 		newCr8r.creatorCode = MakeUns32BE ( (XMP_Uns32) strtoul ( creatorCode.c_str(), 0, 0 ) );
1618 	}
1619 
1620 	if ( ! appleEvent.empty() ) {
1621 		newCr8r.appleEvent = MakeUns32BE ( (XMP_Uns32) strtoul ( appleEvent.c_str(), 0, 0 ) );
1622 	}
1623 
1624 	if ( ! fileExt.empty() ) SetBufferedString ( newCr8r.fileExt, fileExt, sizeof ( newCr8r.fileExt ) );
1625 	if ( ! appOptions.empty() ) SetBufferedString ( newCr8r.appOptions, appOptions, sizeof ( newCr8r.appOptions ) );
1626 	if ( ! appName.empty() ) SetBufferedString ( newCr8r.appName, appName, sizeof ( newCr8r.appName ) );
1627 
1628 	moovMgr->SetBox ( "moov/udta/Cr8r", &newCr8r, sizeof(newCr8r) );
1629 
1630 }	// ExportCr8rItems
1631 
1632 // =================================================================================================
1633 // GetAtomInfo
1634 // ===========
1635 
1636 struct AtomInfo {
1637 	XMP_Int64 atomSize;
1638 	XMP_Uns32 atomType;
1639 	bool hasLargeSize;
1640 };
1641 
1642 enum {	// ! Do not rearrange, code depends on this order.
1643 	kBadQT_NoError		= 0,	// No errors.
1644 	kBadQT_SmallInner	= 1,	// An extra 1..7 bytes at the end of an inner span.
1645 	kBadQT_LargeInner	= 2,	// More serious inner garbage, found as invalid atom length.
1646 	kBadQT_SmallOuter	= 3,	// An extra 1..7 bytes at the end of the file.
1647 	kBadQT_LargeOuter	= 4		// More serious EOF garbage, found as invalid atom length.
1648 };
1649 typedef XMP_Uns8 QTErrorMode;
1650 
GetAtomInfo(XMP_IO * qtFile,XMP_Int64 spanSize,int nesting,AtomInfo * info)1651 static QTErrorMode GetAtomInfo ( XMP_IO* qtFile, XMP_Int64 spanSize, int nesting, AtomInfo * info )
1652 {
1653 	QTErrorMode status = kBadQT_NoError;
1654 	XMP_Uns8 buffer [8];
1655 
1656 	info->hasLargeSize = false;
1657 
1658 	qtFile->ReadAll ( buffer, 8 );	// Will throw if 8 bytes aren't available.
1659 	info->atomSize = GetUns32BE ( &buffer[0] );	// ! Yes, the initial size is big endian UInt32.
1660 	info->atomType = GetUns32BE ( &buffer[4] );
1661 
1662 	if ( info->atomSize == 0 ) {	// Does the atom extend to EOF?
1663 
1664 		if ( nesting != 0 ) return kBadQT_LargeInner;
1665 		info->atomSize = spanSize;	// This outer atom goes to EOF.
1666 
1667 	} else if ( info->atomSize == 1 ) {	// Does the atom have a 64-bit size?
1668 
1669 		if ( spanSize < 16 ) {	// Is there room in the span for the 16 byte header?
1670 			status = kBadQT_LargeInner;
1671 			if ( nesting == 0 ) status += 2;	// Convert to "outer".
1672 			return status;
1673 		}
1674 
1675 		qtFile->ReadAll ( buffer, 8 );
1676 		info->atomSize = (XMP_Int64) GetUns64BE ( &buffer[0] );
1677 		info->hasLargeSize = true;
1678 
1679 	}
1680 
1681 	XMP_Assert ( status == kBadQT_NoError );
1682 	return status;
1683 
1684 }	// GetAtomInfo
1685 
1686 // =================================================================================================
1687 // CheckAtomList
1688 // =============
1689 //
1690 // Check that a sequence of atoms fills a given span. The I/O position must be at the start of the
1691 // span, it is left just past the span on success. Recursive checks are done for top level 'moov'
1692 // atoms, and second level 'udta' atoms ('udta' inside 'moov').
1693 //
1694 // Checking continues for "small inner" errors. They will be reported if no other kinds of errors
1695 // are found, otherwise the other error is reported. Checking is immediately aborted for any "large"
1696 // error. The rationale is that QuickTime can apparently handle small inner errors. They might be
1697 // arise from updates that shorten an atom by less than 8 bytes. Larger shrinkage should introduce a
1698 // 'free' atom.
1699 
CheckAtomList(XMP_IO * qtFile,XMP_Int64 spanSize,int nesting)1700 static QTErrorMode CheckAtomList ( XMP_IO* qtFile, XMP_Int64 spanSize, int nesting )
1701 {
1702 	QTErrorMode status = kBadQT_NoError;
1703 	AtomInfo    info;
1704 
1705 	const static XMP_Uns32 moovAtomType = 0x6D6F6F76;	// ! Don't use MakeUns32BE, already big endian.
1706 	const static XMP_Uns32 udtaAtomType = 0x75647461;
1707 
1708 	for ( ; spanSize >= 8; spanSize -= info.atomSize ) {
1709 
1710 		QTErrorMode atomStatus = GetAtomInfo ( qtFile, spanSize, nesting, &info );
1711 		if ( atomStatus != kBadQT_NoError ) return atomStatus;
1712 
1713 		XMP_Int64 headerSize = 8;
1714 		if ( info.hasLargeSize ) headerSize = 16;
1715 
1716 		if ( (info.atomSize < headerSize) || (info.atomSize > spanSize) ) {
1717 			status = kBadQT_LargeInner;
1718 			if ( nesting == 0 ) status += 2;	// Convert to "outer".
1719 			return status;
1720 		}
1721 
1722 		bool doChildren = false;
1723 		if ( (nesting == 0) && (info.atomType == moovAtomType) ) doChildren = true;
1724 		if ( (nesting == 1) && (info.atomType == udtaAtomType) ) doChildren = true;
1725 
1726 		XMP_Int64 dataSize = info.atomSize - headerSize;
1727 
1728 		if ( ! doChildren ) {
1729 			qtFile->Seek ( dataSize, kXMP_SeekFromCurrent );
1730 		} else {
1731 			QTErrorMode innerStatus = CheckAtomList ( qtFile, dataSize, nesting+1 );
1732 			if ( innerStatus > kBadQT_SmallInner ) return innerStatus;	// Quit for serious errors.
1733 			if ( status == kBadQT_NoError ) status = innerStatus;	// Remember small inner errors.
1734 		}
1735 
1736 	}
1737 
1738 	XMP_Assert ( status <= kBadQT_SmallInner );	// Else already returned.
1739 	// ! Make sure inner kBadQT_SmallInner is propagated if this span is OK.
1740 
1741 	if ( spanSize != 0 ) {
1742 		qtFile->Seek ( spanSize, kXMP_SeekFromCurrent );	// ! Skip the trailing garbage of this span.
1743 		status = kBadQT_SmallInner;
1744 		if ( nesting == 0 ) status += 2;	// Convert to "outer".
1745 	}
1746 
1747 	return status;
1748 
1749 }	// CheckAtomList
1750 
1751 // =================================================================================================
1752 // AttemptFileRepair
1753 // =================
1754 
AttemptFileRepair(XMP_IO * qtFile,XMP_Int64 fileSpace,QTErrorMode status,GenericErrorCallback * ec)1755 static void AttemptFileRepair ( XMP_IO* qtFile, XMP_Int64 fileSpace, QTErrorMode status, GenericErrorCallback * ec )
1756 {
1757 
1758 	switch ( status ) {
1759 		case kBadQT_NoError    : return;	// Sanity check.
1760 		case kBadQT_SmallInner : return;	// Fixed in normal update code for the 'udta' box.
1761 		case kBadQT_LargeInner :
1762 			{
1763 				XMP_Error error ( kXMPErr_BadFileFormat,"Can't repair QuickTime file" );
1764 				XMPFileHandler::NotifyClient(ec, kXMPErrSev_FileFatal, error);
1765 				break;// will never be here
1766 			}
1767 		case kBadQT_SmallOuter :	// Truncate file below.
1768 		case kBadQT_LargeOuter : 	// Truncate file below.
1769 			{
1770 				break;
1771 			}
1772 		default                :
1773 			{
1774 				XMP_Error error ( kXMPErr_InternalFailure, "Invalid QuickTime error mode" );
1775 				XMPFileHandler::NotifyClient(ec, kXMPErrSev_FileFatal, error);
1776 			}
1777 	}
1778 
1779 	AtomInfo info;
1780 	XMP_Int64 headerSize(0);
1781 
1782 	// Process the top level atoms until an error is found.
1783 
1784 	qtFile->Rewind();
1785 
1786 	for ( ; fileSpace >= 8; fileSpace -= info.atomSize ) {
1787 
1788 		QTErrorMode atomStatus = GetAtomInfo ( qtFile, fileSpace, 0, &info );
1789 
1790 		headerSize = 8;	// ! Set this before checking atomStatus, used after the loop.
1791 		if ( info.hasLargeSize ) headerSize = 16;
1792 
1793 		if ( atomStatus != kBadQT_NoError ) break;
1794 		// If the atom size is less than header -case of kBadQT_SmallOuter
1795 		// If the atom size is more than left filespace -case of kBadQT_LargeOuter
1796 		if ( (info.atomSize < headerSize) || (info.atomSize > fileSpace ) ) break;
1797 
1798 		XMP_Int64 dataSize = info.atomSize - headerSize;
1799 		qtFile->Seek ( dataSize, kXMP_SeekFromCurrent );
1800 
1801 	}
1802 	// Truncate only if the last box type was XMP boxes
1803 	// Refrain from truncating a known box as it
1804 	// might have some useful data which is incomplete
1805 	if ( fileSpace < 8 ||
1806 		 ! ISOMedia::IsKnownBoxType ( info.atomType ) ||
1807 		 ((info.atomType == ISOMedia::k_uuid) && IsXMPUUID(qtFile,info.atomSize-headerSize,true)) ||
1808 		  (info.atomType == ISOMedia::k_XMP_)
1809 		 ){
1810 
1811 		XMP_Error error ( kXMPErr_BadFileFormat,"Truncate outer EOF Garbage" );
1812 		XMPFileHandler::NotifyClient(ec, kXMPErrSev_Recoverable, error);
1813 		// Truncate the file. If fileSpace >= 8 then the loop exited early due to a bad atom, seek back
1814 		// to the atom's start. Otherwise, the loop exited because no more atoms are possible, no seek.
1815 
1816 		if ( fileSpace >= 8 ) qtFile->Seek ( -headerSize, kXMP_SeekFromCurrent );
1817 		XMP_Int64 currPos = qtFile->Offset();
1818 		qtFile->Truncate ( currPos );
1819 	}
1820 	else{
1821 
1822 		XMP_Error error ( kXMPErr_BadFileFormat,"Missing box Data at EOF" );
1823 		XMPFileHandler::NotifyClient(ec, kXMPErrSev_FileFatal, error);
1824 	}
1825 
1826 }	// AttemptFileRepair
1827 
1828 // =================================================================================================
1829 // CheckQTFileStructure
1830 // ====================
1831 
CheckQTFileStructure(XMPFileHandler * thiz,bool doRepair,GenericErrorCallback * ec)1832 static void CheckQTFileStructure ( XMPFileHandler * thiz,bool doRepair, GenericErrorCallback * ec )
1833 {
1834 	XMPFiles * parent = thiz->parent;
1835 	XMP_IO* fileRef  = parent->ioRef;
1836 	XMP_Int64 fileSize = fileRef->Length();
1837 
1838 	// Check the basic file structure and try to repair if asked.
1839 
1840 	fileRef->Rewind();
1841 	QTErrorMode status = CheckAtomList ( fileRef, fileSize, 0 );
1842 
1843 	if ( status != kBadQT_NoError ) {
1844 		if ( doRepair || (status == kBadQT_SmallInner) || (status == kBadQT_SmallOuter) ) {
1845 			AttemptFileRepair ( fileRef, fileSize, status,ec );	// Will throw if the attempt fails.
1846 		} else if ( status != kBadQT_SmallInner ) {
1847 			//don't truncate Large Outer EOF garbage unless the client wants it to
1848 			// Clients can pass their intent by setting the flag kXMPFiles_OpenRepairFile
1849 			XMP_Error error ( kXMPErr_BadFileFormat,"Ill-formed QuickTime file" );
1850 			XMPFileHandler::NotifyClient(ec, kXMPErrSev_FileFatal, error);
1851 		}
1852 	}
1853 
1854 }	// CheckQTFileStructure;
1855 
1856 // =================================================================================================
1857 // CheckFinalBox
1858 // =============
1859 //
1860 // Before appending anything new, check if the final top level box has a "to EoF" length. If so, fix
1861 // it to have an explicit length.
1862 
CheckFinalBox(XMP_IO * fileRef,XMP_AbortProc abortProc,void * abortArg)1863 static void CheckFinalBox ( XMP_IO* fileRef, XMP_AbortProc abortProc, void * abortArg )
1864 {
1865 	const bool checkAbort = (abortProc != 0);
1866 
1867 	XMP_Uns64 fileSize = fileRef->Length();
1868 
1869 	// Find the last 2 boxes in the file. Need the previous to last in case it is an Apple 'wide' box.
1870 
1871 	XMP_Uns64 prevPos, lastPos, nextPos;
1872 	ISOMedia::BoxInfo prevBox, lastBox;
1873 	XMP_Uns8 buffer [16];	// Enough to create an extended header.
1874 
1875 	memset ( &prevBox, 0, sizeof(prevBox) );	// AUDIT: Using sizeof(prevBox) is safe.
1876 	memset ( &lastBox, 0, sizeof(lastBox) );	// AUDIT: Using sizeof(lastBox) is safe.
1877 	prevPos = lastPos = nextPos = 0;
1878 	while ( nextPos != fileSize ) {
1879 		if ( checkAbort && abortProc(abortArg) ) {
1880 			XMP_Throw ( "MPEG4_MetaHandler::CheckFinalBox - User abort", kXMPErr_UserAbort );
1881 		}
1882 		prevBox = lastBox;
1883 		prevPos = lastPos;
1884 		lastPos = nextPos;
1885 		nextPos = ISOMedia::GetBoxInfo ( fileRef, lastPos, fileSize, &lastBox, true /* throw errors */ );
1886 	}
1887 
1888 	// See if the last box is valid and has a "to EoF" size.
1889 
1890 	if ( lastBox.headerSize < 8 ) XMP_Throw ( "MPEG-4 final box is invalid", kXMPErr_EnforceFailure );
1891 	fileRef->Seek ( lastPos, kXMP_SeekFromStart );
1892 	fileRef->Read ( buffer, 4 );
1893 	XMP_Uns64 lastSize = GetUns32BE ( &buffer[0] );	// ! Yes, the file has a 32-bit value.
1894 	if ( lastSize != 0 ) return;
1895 
1896 	// Have a final "to EoF" box, try to write the explicit size.
1897 
1898 	lastSize = lastBox.headerSize + lastBox.contentSize;
1899 	if ( lastSize <= 0xFFFFFFFFUL ) {
1900 
1901 		// Fill in the 32-bit exact size.
1902 		PutUns32BE ( (XMP_Uns32)lastSize, &buffer[0] );
1903 		fileRef->Seek ( lastPos, kXMP_SeekFromStart );
1904 		fileRef->Write ( buffer, 4 );
1905 
1906 	} else {
1907 
1908 		// Try to convert to using an extended header.
1909 
1910 		if ( (prevBox.boxType != ISOMedia::k_wide) || (prevBox.headerSize != 8) || (prevBox.contentSize != 0) ) {
1911 			XMP_Throw ( "Can't expand final box header", kXMPErr_EnforceFailure );
1912 		}
1913 		XMP_Assert ( prevPos == (lastPos - 8) );
1914 
1915 		PutUns32BE ( 1, &buffer[0] );
1916 		PutUns32BE ( lastBox.boxType, &buffer[4] );
1917 		PutUns64BE ( lastSize, &buffer[8] );
1918 		fileRef->Seek ( prevPos, kXMP_SeekFromStart );
1919 		fileRef->Write ( buffer, 16 );
1920 
1921 	}
1922 
1923 }	// CheckFinalBox
1924 
1925 // =================================================================================================
1926 // WriteBoxHeader
1927 // ==============
1928 
WriteBoxHeader(XMP_IO * fileRef,XMP_Uns32 boxType,XMP_Uns64 boxSize)1929 static void WriteBoxHeader ( XMP_IO* fileRef, XMP_Uns32 boxType, XMP_Uns64 boxSize )
1930 {
1931 	XMP_Uns32 u32;
1932 	XMP_Uns64 u64;
1933 	XMP_Enforce ( boxSize >= 8 );	// The size must be the full size, not just the content.
1934 
1935 	if ( boxSize <= 0xFFFFFFFF ) {
1936 
1937 		u32 = MakeUns32BE ( (XMP_Uns32)boxSize );
1938 		fileRef->Write ( &u32, 4 );
1939 		u32 = MakeUns32BE ( boxType );
1940 		fileRef->Write ( &u32, 4 );
1941 
1942 	} else {
1943 
1944 		u32 = MakeUns32BE ( 1 );
1945 		fileRef->Write ( &u32, 4 );
1946 		u32 = MakeUns32BE ( boxType );
1947 		fileRef->Write ( &u32, 4 );
1948 		u64 = MakeUns64BE ( boxSize );
1949 		fileRef->Write ( &u64, 8 );
1950 
1951 	}
1952 
1953 }	// WriteBoxHeader
1954 
1955 // =================================================================================================
1956 // WipeBoxFree
1957 // ===========
1958 //
1959 // Change the box's type to 'free' (or create a 'free' box) and zero the content.
1960 
1961 static XMP_Uns8 kZeroes [64*1024];	// C semantics guarantee zero initialization.
1962 
WipeBoxFree(XMP_IO * fileRef,XMP_Uns64 boxOffset,XMP_Uns32 boxSize)1963 static void WipeBoxFree ( XMP_IO* fileRef, XMP_Uns64 boxOffset, XMP_Uns32 boxSize )
1964 {
1965 	if ( boxSize == 0 ) return;
1966 	XMP_Enforce ( boxSize >= 8 );
1967 
1968 	fileRef->Seek ( boxOffset, kXMP_SeekFromStart );
1969 	XMP_Uns32 u32;
1970 	u32 = MakeUns32BE ( boxSize );	// ! The actual size should not change, but might have had a long header.
1971 	fileRef->Write ( &u32, 4 );
1972 	u32 = MakeUns32BE ( ISOMedia::k_free );
1973 	fileRef->Write ( &u32, 4 );
1974 
1975 	XMP_Uns32 ioCount = sizeof ( kZeroes );
1976 	for ( boxSize -= 8; boxSize > 0; boxSize -= ioCount ) {
1977 		if ( ioCount > boxSize ) ioCount = boxSize;
1978 		fileRef->Write ( &kZeroes[0], ioCount );
1979 	}
1980 
1981 }	// WipeBoxFree
1982 
1983 // =================================================================================================
1984 // CreateFreeSpaceList
1985 // ===================
1986 
1987 struct SpaceInfo {
1988 	XMP_Uns64 offset, size;
SpaceInfoSpaceInfo1989 	SpaceInfo() : offset(0), size(0) {};
SpaceInfoSpaceInfo1990 	SpaceInfo ( XMP_Uns64 _offset, XMP_Uns64 _size ) : offset(_offset), size(_size) {};
1991 };
1992 
1993 typedef std::vector<SpaceInfo> FreeSpaceList;
1994 
CreateFreeSpaceList(XMP_IO * fileRef,XMP_Uns64 fileSize,XMP_Uns64 oldOffset,XMP_Uns32 oldSize,FreeSpaceList * spaceList)1995 static void CreateFreeSpaceList ( XMP_IO* fileRef, XMP_Uns64 fileSize,
1996 								  XMP_Uns64 oldOffset, XMP_Uns32 oldSize, FreeSpaceList * spaceList )
1997 {
1998 	XMP_Uns64 boxPos=0, boxNext=0, adjacentFree=0;
1999 	ISOMedia::BoxInfo currBox;
2000 
2001 	fileRef->Rewind();
2002 	spaceList->clear();
2003 
2004 	for ( boxPos = 0; boxPos < fileSize; boxPos = boxNext ) {
2005 
2006 		boxNext = ISOMedia::GetBoxInfo ( fileRef, boxPos, fileSize, &currBox, true /* throw errors */ );
2007 		XMP_Uns64 currSize = currBox.headerSize + currBox.contentSize;
2008 
2009 		if ( (currBox.boxType == ISOMedia::k_free) ||
2010 			 (currBox.boxType == ISOMedia::k_skip) ||
2011 			 ((boxPos == oldOffset) && (currSize == oldSize)) ) {
2012 
2013 			if ( spaceList->empty() || (boxPos != adjacentFree) ) {
2014 				spaceList->push_back ( SpaceInfo ( boxPos, currSize ) );
2015 				adjacentFree = boxPos + currSize;
2016 			} else {
2017 				SpaceInfo * lastSpace = &spaceList->back();
2018 				lastSpace->size += currSize;
2019 			}
2020 
2021 		}
2022 
2023 	}
2024 
2025 }	// CreateFreeSpaceList
2026 
2027 // =================================================================================================
2028 // MPEG4_MetaHandler::CacheFileData
2029 // ================================
2030 //
2031 // There are 3 file variants: normal ISO Base Media, modern QuickTime, and classic QuickTime. The
2032 // XMP is placed differently between the ISO and two QuickTime forms, and there is different but not
2033 // colliding native metadata. The entire 'moov' subtree is cached, along with the top level 'uuid'
2034 // box of XMP if present.
2035 
CacheFileData()2036 void MPEG4_MetaHandler::CacheFileData()
2037 {
2038 	XMP_Assert ( ! this->containsXMP );
2039 
2040 	XMPFiles * parent = this->parent;
2041 	XMP_OptionBits openFlags = parent->openFlags;
2042 
2043 	XMP_IO* fileRef  = parent->ioRef;
2044 
2045 	XMP_AbortProc abortProc  = parent->abortProc;
2046 	void *        abortArg   = parent->abortArg;
2047 	const bool    checkAbort = (abortProc != 0);
2048 
2049 	// First do some special case repair to QuickTime files, based on bad files in the wild.
2050 
2051 	const bool isUpdate = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenForUpdate );
2052 	const bool doRepair = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenRepairFile );
2053 
2054 	if ( isUpdate ) {
2055 		CheckQTFileStructure ( this, doRepair, &parent->errorCallback );	// Will throw for failure.
2056 	}
2057 
2058 	// Cache the top level 'moov' and 'uuid'/XMP boxes.
2059 
2060 	XMP_Uns64 fileSize = fileRef->Length();
2061 
2062 	XMP_Uns64 boxPos, boxNext;
2063 	ISOMedia::BoxInfo currBox;
2064 
2065 	bool xmpOnly = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenOnlyXMP );
2066 	bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
2067 
2068 	bool xmpUuidFound = (! haveISOFile);			// Ignore the XMP 'uuid' box for QuickTime files.
2069 	bool moovIgnored = (xmpOnly & haveISOFile);	// Ignore the 'moov' box for XMP-only ISO files.
2070 	bool moovFound = moovIgnored;
2071 
2072 	for ( boxPos = 0; boxPos < fileSize; boxPos = boxNext ) {
2073 
2074 		if ( checkAbort && abortProc(abortArg) ) {
2075 			XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
2076 		}
2077 
2078 
2079 		boxNext = ISOMedia::GetBoxInfo ( fileRef, boxPos, fileSize, &currBox );
2080 
2081 		if ( (! moovFound) && (currBox.boxType == ISOMedia::k_moov) ) {
2082 
2083 			XMP_Uns64 fullMoovSize = currBox.headerSize + currBox.contentSize;
2084 			if ( fullMoovSize > moovBoxSizeLimit ) {	// From here on we know 32-bit offsets are safe.
2085 				XMP_Throw ( "Oversize 'moov' box", kXMPErr_EnforceFailure );
2086 			}
2087 
2088 			this->moovMgr.fullSubtree.assign ( (XMP_Uns32)fullMoovSize, 0 );
2089 			fileRef->Seek ( boxPos, kXMP_SeekFromStart );
2090 			fileRef->Read ( &this->moovMgr.fullSubtree[0], (XMP_Uns32)fullMoovSize );
2091 
2092 			this->moovBoxPos = boxPos;
2093 			this->moovBoxSize = (XMP_Uns32)fullMoovSize;
2094 			moovFound = true;
2095 			if ( xmpUuidFound ) break;	// Exit the loop when both are found.
2096 
2097 		} else if ( (! xmpUuidFound) && (currBox.boxType == ISOMedia::k_uuid) && ( memcmp( currBox.idUUID, ISOMedia::k_xmpUUID, 16 ) == 0 ) ) {
2098 
2099 			XMP_Uns64 fullUuidSize = currBox.headerSize + currBox.contentSize;
2100 			if ( fullUuidSize > moovBoxSizeLimit ) {	// From here on we know 32-bit offsets are safe.
2101 				XMP_Throw ( "Oversize XMP 'uuid' box", kXMPErr_EnforceFailure );
2102 			}
2103 
2104 			this->packetInfo.offset = boxPos + currBox.headerSize ;
2105 			this->packetInfo.length = (XMP_Int32) (currBox.contentSize);
2106 
2107 			this->xmpPacket.assign ( this->packetInfo.length, ' ' );
2108 			fileRef->ReadAll ( (void*)this->xmpPacket.data(), this->packetInfo.length );
2109 
2110 			this->xmpBoxPos = boxPos;
2111 			this->xmpBoxSize = (XMP_Uns32)fullUuidSize;
2112 			xmpUuidFound = true;
2113 			if ( moovFound ) break;	// Exit the loop when both are found.
2114 
2115 		}
2116 
2117 	}
2118 
2119 	if ( (! moovFound) && (! moovIgnored) ){
2120 		XMP_Error error ( kXMPErr_BadFileFormat,"No 'moov' box" );
2121 		XMPFileHandler::NotifyClient(&parent->errorCallback, kXMPErrSev_FileFatal, error);
2122 	}
2123 
2124 }	// MPEG4_MetaHandler::CacheFileData
2125 
2126 // =================================================================================================
2127 // MPEG4_MetaHandler::ProcessXMP
2128 // =============================
2129 
ProcessXMP()2130 void MPEG4_MetaHandler::ProcessXMP()
2131 {
2132 	if ( this->processedXMP ) return;
2133 	this->processedXMP = true;	// Make sure only called once.
2134 
2135 	XMPFiles * parent = this->parent;
2136 	XMP_OptionBits openFlags = parent->openFlags;
2137 
2138 	bool xmpOnly = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenOnlyXMP );
2139 	bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
2140 
2141 	// Process the cached XMP (from the 'uuid' box) if that is all we want and this is an ISO file.
2142 
2143 	if ( xmpOnly & haveISOFile ) {
2144 
2145 		this->containsXMP = this->havePreferredXMP = (this->packetInfo.length != 0);
2146 
2147 		if ( this->containsXMP ) {
2148 			FillPacketInfo ( this->xmpPacket, &this->packetInfo );
2149 			this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
2150 			this->xmpObj.DeleteProperty ( kXMP_NS_XMP, "NativeDigests" );	// No longer used.
2151 		}
2152 
2153 		return;
2154 
2155 	}
2156 
2157 	// Parse the cached 'moov' subtree, parse the preferred XMP.
2158 
2159 	if ( this->moovMgr.fullSubtree.empty() ) {
2160 		XMP_Error error ( kXMPErr_BadFileFormat,"No 'moov' box" );
2161 		XMPFileHandler::NotifyClient(&parent->errorCallback, kXMPErrSev_FileFatal, error);
2162 	}
2163 	this->moovMgr.ParseMemoryTree ( this->fileMode );
2164 
2165 	if ( (this->xmpBoxPos == 0) || (! haveISOFile) ) {
2166 
2167 		// Look for the QuickTime moov/uuid/XMP_ box.
2168 
2169 		MOOV_Manager::BoxInfo xmpInfo;
2170 		MOOV_Manager::BoxRef  xmpRef = this->moovMgr.GetBox ( "moov/udta/XMP_", &xmpInfo );
2171 
2172 		if ( (xmpRef != 0) && (xmpInfo.contentSize != 0) ) {
2173 
2174 			this->xmpBoxPos = this->moovBoxPos + this->moovMgr.GetParsedOffset ( xmpRef );
2175 			this->packetInfo.offset = this->xmpBoxPos + this->moovMgr.GetHeaderSize ( xmpRef );
2176 			this->packetInfo.length = xmpInfo.contentSize;
2177 
2178 			this->xmpPacket.assign ( (char*)xmpInfo.content, this->packetInfo.length );
2179 			this->havePreferredXMP = (! haveISOFile);
2180 
2181 		}
2182 
2183 	}
2184 
2185 	if ( this->xmpBoxPos != 0 ) {
2186 		this->containsXMP = true;
2187 		FillPacketInfo ( this->xmpPacket, &this->packetInfo );
2188 		this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
2189 		this->xmpObj.DeleteProperty ( kXMP_NS_XMP, "NativeDigests" );	// No longer used.
2190 	}
2191 
2192 	// Import the non-XMP items. Do the imports in reverse priority order, last import wins!
2193 
2194 	MOOV_Manager::BoxInfo mvhdInfo;
2195 	MOOV_Manager::BoxRef  mvhdRef = this->moovMgr.GetBox ( "moov/mvhd", &mvhdInfo );
2196 	bool mvhdFound = ((mvhdRef != 0) && (mvhdInfo.contentSize != 0));
2197 
2198 	MOOV_Manager::BoxInfo udtaInfo;
2199 	MOOV_Manager::BoxRef  udtaRef = this->moovMgr.GetBox ( "moov/udta", &udtaInfo );
2200 	std::vector<MOOV_Manager::BoxInfo> cprtBoxes;
2201 
2202 	if ( udtaRef != 0 ) {
2203 		for ( XMP_Uns32 i = 0; i < udtaInfo.childCount; ++i ) {
2204 			MOOV_Manager::BoxInfo currInfo;
2205 			MOOV_Manager::BoxRef  currRef = this->moovMgr.GetNthChild ( udtaRef, i, &currInfo );
2206 			if ( currRef == 0 ) break;	// Sanity check, should not happen.
2207 			if ( currInfo.boxType != ISOMedia::k_cprt ) continue;
2208 			cprtBoxes.push_back ( currInfo );
2209 		}
2210 	}
2211 	bool cprtFound = (! cprtBoxes.empty());
2212 
2213 	bool tradQTFound = this->tradQTMgr.ParseCachedBoxes ( this->moovMgr );
2214 	bool tmcdFound = this->ParseTimecodeTrack();
2215 
2216 	if ( this->fileMode == MOOV_Manager::kFileIsNormalISO ) {
2217 
2218 		if ( mvhdFound )   this->containsXMP |= ImportMVHDItems ( mvhdInfo, &this->xmpObj );
2219 		if ( cprtFound )   this->containsXMP |= ImportISOCopyrights ( cprtBoxes, &this->xmpObj );
2220 		if ( tmcdFound )   this->containsXMP |= ImportTimecodeItems ( this->tmcdInfo, this->tradQTMgr, &this->xmpObj );
2221 	} else {	// This is a QuickTime file, either traditional or modern.
2222 
2223 		if ( mvhdFound )   this->containsXMP |= ImportMVHDItems ( mvhdInfo, &this->xmpObj );
2224 		if ( cprtFound )   this->containsXMP |= ImportISOCopyrights ( cprtBoxes, &this->xmpObj );
2225 		if ( tmcdFound | tradQTFound ) {
2226 			// Some of the timecode items are in the .../udta/�... set but handled by ImportTimecodeItems.
2227 			this->containsXMP |= ImportTimecodeItems ( this->tmcdInfo, this->tradQTMgr, &this->xmpObj );
2228 		}
2229 
2230 		this->containsXMP |= ImportCr8rItems ( this->moovMgr, &this->xmpObj );
2231 
2232 	}
2233 
2234 }	// MPEG4_MetaHandler::ProcessXMP
2235 
2236 // =================================================================================================
2237 // MPEG4_MetaHandler::ParseTimecodeTrack
2238 // =====================================
2239 
ParseTimecodeTrack()2240 bool MPEG4_MetaHandler::ParseTimecodeTrack()
2241 {
2242 	MOOV_Manager::BoxInfo drefInfo;
2243 	MOOV_Manager::BoxRef drefRef = FindTimecode_dref ( this->moovMgr );
2244 	bool qtTimecodeIsExternal=false;
2245 	if( drefRef != 0 )
2246 	{
2247 		this->moovMgr.GetBoxInfo( drefRef , &drefInfo );
2248 		// After dref atom in a QT file we should only
2249 		// proceed further to check the Data refernces
2250 		// if the total size of the content is greater
2251 		// than 8 bytes which suggests that there is atleast
2252 		// one data reference to check for external references.
2253 		if ( drefInfo.contentSize>8)
2254 		{
2255 			XMP_Uns32 noOfDrefs=GetUns32BE(drefInfo.content+4);
2256 			if(noOfDrefs>0)
2257 			{
2258 				const XMP_Uns8* dataReference = drefInfo.content + 8;
2259 				const XMP_Uns8* nextDataref   = 0;
2260 				const XMP_Uns8* boxlimit      = drefInfo.content + drefInfo.contentSize;
2261 				ISOMedia::BoxInfo dataRefernceInfo;
2262 				while(noOfDrefs--)
2263 				{
2264 					nextDataref= ISOMedia::GetBoxInfo( dataReference , boxlimit,
2265 						&dataRefernceInfo);
2266 					//The content atleast contains the flag and some data
2267 					if ( dataRefernceInfo.contentSize > 4 )
2268 					{
2269 						if (dataRefernceInfo.boxType==ISOMedia::k_alis &&
2270 							*((XMP_Uns8*)(dataReference + dataRefernceInfo.headerSize + 4)) !=1 )
2271 						{
2272 							qtTimecodeIsExternal=true;
2273 							break;
2274 						}
2275 					}
2276 					dataReference=nextDataref;
2277 				}
2278 			}
2279 		}
2280 	}
2281 
2282 	MOOV_Manager::BoxRef stblRef = FindTimecode_stbl ( this->moovMgr );
2283 	if ( stblRef == 0 ) return false;
2284 
2285 	// Find the .../stbl/stsd box and process the first table entry.
2286 
2287 	MOOV_Manager::BoxInfo stsdInfo;
2288 	MOOV_Manager::BoxRef  stsdRef;
2289 
2290 	stsdRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stsd, &stsdInfo );
2291 	if ( stsdRef == 0 ) return false;
2292 	if ( stsdInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsd_entry )) ) return false;
2293 	if ( GetUns32BE ( stsdInfo.content + 4 ) == 0 ) return false;	// Make sure the entry count is non-zero.
2294 
2295 	const MOOV_Manager::Content_stsd_entry * stsdRawEntry = (MOOV_Manager::Content_stsd_entry*) (stsdInfo.content + 8);
2296 
2297 	XMP_Uns32 stsdEntrySize = GetUns32BE ( &stsdRawEntry->entrySize );
2298 	if ( stsdEntrySize > (stsdInfo.contentSize - 4) ) stsdEntrySize = stsdInfo.contentSize - 4;
2299 	if ( stsdEntrySize < sizeof ( MOOV_Manager::Content_stsd_entry ) ) return false;
2300 
2301 	XMP_Uns32 stsdEntryFormat = GetUns32BE ( &stsdRawEntry->format );
2302 	if ( stsdEntryFormat != ISOMedia::k_tmcd ) return false;
2303 
2304 	// If frame duration is zero it means tmcd sample is invalid
2305 	if(GetUns32BE(&stsdRawEntry->frameDuration)==0)
2306 		return false;
2307 
2308 	this->tmcdInfo.timeScale = GetUns32BE ( &stsdRawEntry->timeScale );
2309 	this->tmcdInfo.frameDuration = GetUns32BE ( &stsdRawEntry->frameDuration );
2310 
2311 	double floatCount = (double)this->tmcdInfo.timeScale / (double)this->tmcdInfo.frameDuration;
2312 	XMP_Uns8 expectedCount = (XMP_Uns8) (floatCount + 0.5);
2313 	if( expectedCount == 0 )	return false;
2314 	if ( expectedCount != stsdRawEntry->frameCount ) {
2315 		double countRatio = (double)stsdRawEntry->frameCount / (double)expectedCount;
2316 		this->tmcdInfo.timeScale = (XMP_Uns32) (((double)this->tmcdInfo.timeScale * countRatio) + 0.5);
2317 	}
2318 
2319 	XMP_Uns32 flags = GetUns32BE ( &stsdRawEntry->flags );
2320 	this->tmcdInfo.isDropFrame = flags & 0x1;
2321 
2322 	// Look for a trailing 'name' box on the first stsd table entry.
2323 
2324 	XMP_Uns32 stsdTrailerSize = stsdEntrySize - sizeof ( MOOV_Manager::Content_stsd_entry );
2325 	if ( stsdTrailerSize > 8 ) {	// Room for a non-empty 'name' box?
2326 
2327 		const XMP_Uns8 * trailerStart = stsdInfo.content + 8 + sizeof ( MOOV_Manager::Content_stsd_entry );
2328 		const XMP_Uns8 * trailerLimit = trailerStart + stsdTrailerSize;
2329 		const XMP_Uns8 * trailerPos;
2330 		const XMP_Uns8 * trailerNext;
2331 		ISOMedia::BoxInfo trailerInfo;
2332 
2333 		for ( trailerPos = trailerStart; trailerPos < trailerLimit; trailerPos = trailerNext ) {
2334 
2335 			trailerNext = ISOMedia::GetBoxInfo ( trailerPos, trailerLimit, &trailerInfo );
2336 
2337 			if ( trailerInfo.boxType == ISOMedia::k_name ) {
2338 
2339 				this->tmcdInfo.nameOffset = (XMP_Uns32) (trailerPos - stsdInfo.content);
2340 
2341 				if ( trailerInfo.contentSize > 4 ) {
2342 
2343 					XMP_Uns16 textLen = GetUns16BE ( trailerPos + trailerInfo.headerSize );
2344 					this->tmcdInfo.macLang = GetUns16BE ( trailerPos + trailerInfo.headerSize + 2 );
2345 
2346 					if ( trailerInfo.contentSize >= (XMP_Uns64)(textLen + 4) ) {
2347 						const char * textPtr = (char*) (trailerPos + trailerInfo.headerSize + 4);
2348 						this->tmcdInfo.macName.assign ( textPtr, textLen );
2349 					}
2350 
2351 				}
2352 
2353 				break;	// Done after finding the first 'name' box.
2354 
2355 			}
2356 
2357 		}
2358 
2359 	}
2360 
2361 	// Find the timecode sample.
2362 	// Read the timecode only if we are sure that it is not External
2363 	// This way we never find stsdBox and ExportTimecodeItems and
2364 	// ImportTimecodeItems doesn't do anything with timeCodeSample
2365 	// Also because sampleOffset is/remains zero UpdateFile doesn't
2366 	// update the timeCodeSample value
2367 	if(!qtTimecodeIsExternal)
2368 	{
2369 		XMP_Uns64 sampleOffset = 0;
2370 		MOOV_Manager::BoxInfo tempInfo;
2371 		MOOV_Manager::BoxRef  tempRef;
2372 
2373 		tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stsc, &tempInfo );
2374 		if ( tempRef == 0 ) return false;
2375 		if ( tempInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsc_entry )) ) return false;
2376 		if ( GetUns32BE ( tempInfo.content + 4 ) == 0 ) return false;	// Make sure the entry count is non-zero.
2377 
2378 		XMP_Uns32 firstChunkNumber = GetUns32BE ( tempInfo.content + 8 );	// Want first field of first entry.
2379 
2380 		tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stco, &tempInfo );
2381 
2382 		if ( tempRef != 0 ) {
2383 
2384 			if ( tempInfo.contentSize < (8 + 4) ) return false;
2385 			XMP_Uns32 stcoCount = GetUns32BE ( tempInfo.content + 4 );
2386 			if ( stcoCount < firstChunkNumber ) return false;
2387 			XMP_Uns32 * stcoPtr = (XMP_Uns32*) (tempInfo.content + 8);
2388 			sampleOffset = GetUns32BE ( &stcoPtr[firstChunkNumber-1] );	// ! Chunk number is 1-based.
2389 
2390 		} else {
2391 
2392 			tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_co64, &tempInfo );
2393 			if ( (tempRef == 0) || (tempInfo.contentSize < (8 + 8)) ) return false;
2394 			XMP_Uns32 co64Count = GetUns32BE ( tempInfo.content + 4 );
2395 			if ( co64Count < firstChunkNumber ) return false;
2396 			XMP_Uns64 * co64Ptr = (XMP_Uns64*) (tempInfo.content + 8);
2397 			sampleOffset = GetUns64BE ( &co64Ptr[firstChunkNumber-1] );	// ! Chunk number is 1-based.
2398 
2399 		}
2400 
2401 		if ( sampleOffset != 0 ) {	// Read the timecode sample.
2402 
2403 			XMPFiles_IO* localFile = 0;
2404 
2405 			if ( this->parent->ioRef == 0 ) {	// Local read-only files get closed in CacheFileData.
2406 				XMP_Assert ( this->parent->UsesLocalIO() );
2407 				localFile = XMPFiles_IO::New_XMPFiles_IO ( this->parent->GetFilePath().c_str(), Host_IO::openReadOnly, &this->parent->errorCallback);
2408 				XMP_Enforce ( localFile != 0 );
2409 				this->parent->ioRef = localFile;
2410 			}
2411 
2412 			this->parent->ioRef->Seek ( sampleOffset, kXMP_SeekFromStart  );
2413 			this->parent->ioRef->ReadAll ( &this->tmcdInfo.timecodeSample, 4 );
2414 			this->tmcdInfo.timecodeSample = MakeUns32BE ( this->tmcdInfo.timecodeSample );
2415 			if ( localFile != 0 ) {
2416 				localFile->Close();
2417 				delete localFile;
2418 				this->parent->ioRef = 0;
2419 			}
2420 
2421 		}
2422 
2423 		// If this is a QT file, look for an edit list offset to add to the timecode sample. Look in the
2424 		// timecode track for an edts/elst box. The content is a UInt8 version, UInt8[3] flags, a UInt32
2425 		// entry count, and a sequence of UInt32 triples (trackDuration, mediaTime, mediaRate). Take
2426 		// mediaTime from the first entry, divide it by tmcdInfo.frameDuration, add that to
2427 		// tmcdInfo.timecodeSample.
2428 
2429 		bool isQT = (this->fileMode == MOOV_Manager::kFileIsModernQT) ||
2430 					(this->fileMode == MOOV_Manager::kFileIsTraditionalQT);
2431 
2432 		MOOV_Manager::BoxRef elstRef = 0;
2433 		if ( isQT ) elstRef = FindTimecode_elst ( this->moovMgr );
2434 		if ( elstRef != 0 ) {
2435 
2436 			MOOV_Manager::BoxInfo elstInfo;
2437 			this->moovMgr.GetBoxInfo ( elstRef, &elstInfo );
2438 
2439 			if ( elstInfo.contentSize >= (4+4+12) ) {
2440 				XMP_Uns32 elstCount = GetUns32BE ( elstInfo.content + 4 );
2441 				if ( elstCount >= 1 ) {
2442 					XMP_Uns32 mediaTime = GetUns32BE ( elstInfo.content + (4+4+4) );
2443 					this->tmcdInfo.timecodeSample += (mediaTime / this->tmcdInfo.frameDuration);
2444 				}
2445 			}
2446 
2447 		}
2448 
2449 		// Finally update this->tmcdInfo to remember (for update) that there is an OK timecode track.
2450 
2451 		this->tmcdInfo.stsdBoxFound = true;
2452 		this->tmcdInfo.sampleOffset = sampleOffset;
2453 	}
2454 	return true;
2455 
2456 }	// MPEG4_MetaHandler::ParseTimecodeTrack
2457 
2458 // =================================================================================================
2459 // MPEG4_MetaHandler::UpdateTopLevelBox
2460 // ====================================
2461 
UpdateTopLevelBox(XMP_Uns64 oldOffset,XMP_Uns32 oldSize,const XMP_Uns8 * newBox,XMP_Uns32 newSize)2462 void MPEG4_MetaHandler::UpdateTopLevelBox ( XMP_Uns64 oldOffset, XMP_Uns32 oldSize,
2463 											const XMP_Uns8 * newBox, XMP_Uns32 newSize )
2464 {
2465 	if ( (oldSize == 0) && (newSize == 0) ) return;	// Sanity check, should not happen.
2466 
2467 	XMP_IO* fileRef = this->parent->ioRef;
2468 	XMP_Uns64 oldFileSize = fileRef->Length();
2469 
2470 	XMP_AbortProc abortProc = this->parent->abortProc;
2471 	void *        abortArg  = this->parent->abortArg;
2472 
2473 	if ( newSize == oldSize ) {
2474 
2475 		// Trivial case, update the existing box in-place.
2476 		fileRef->Seek ( oldOffset, kXMP_SeekFromStart );
2477 		fileRef->Write ( newBox, oldSize );
2478 
2479 	} else if ( (oldOffset + oldSize) == oldFileSize ) {
2480 
2481 		// The old box was at the end, write the new and truncate the file if necessary.
2482 		fileRef->Seek ( oldOffset, kXMP_SeekFromStart );
2483 		fileRef->Write ( newBox, newSize );
2484 		fileRef->Truncate ( (oldOffset + newSize) );	// Does nothing if new size is bigger.
2485 
2486 	} else if ( (newSize < oldSize) && ((oldSize - newSize) >= 8) ) {
2487 
2488 		// The new size is smaller and there is enough room to create a free box.
2489 		fileRef->Seek ( oldOffset, kXMP_SeekFromStart );
2490 		fileRef->Write ( newBox, newSize );
2491 		WipeBoxFree ( fileRef, (oldOffset + newSize), (oldSize - newSize) );
2492 
2493 	} else {
2494 
2495 		// Look for a trailing free box with enough space. If not found, consider any free space.
2496 		// If still not found, append the new box and make the old one free.
2497 
2498 		ISOMedia::BoxInfo nextBoxInfo;
2499 		(void) ISOMedia::GetBoxInfo ( fileRef, (oldOffset + oldSize), oldFileSize, &nextBoxInfo, true /* throw errors */ );
2500 
2501 		XMP_Uns64 totalRoom = oldSize + nextBoxInfo.headerSize + nextBoxInfo.contentSize;
2502 
2503 		bool nextIsFree = (nextBoxInfo.boxType == ISOMedia::k_free) || (nextBoxInfo.boxType == ISOMedia::k_skip);
2504 		bool haveEnoughRoom = (newSize == totalRoom) ||
2505 							  ( (newSize < totalRoom) && ((totalRoom - newSize) >= 8) );
2506 
2507 		if ( nextIsFree & haveEnoughRoom ) {
2508 
2509 			fileRef->Seek ( oldOffset, kXMP_SeekFromStart );
2510 			fileRef->Write ( newBox, newSize );
2511 
2512 			if ( newSize < totalRoom ) {
2513 				// Don't wipe, at most 7 old bytes left, it will be covered by the free header.
2514 				WriteBoxHeader ( fileRef, ISOMedia::k_free, (totalRoom - newSize) );
2515 			}
2516 
2517 		} else {
2518 
2519 			// Create a list of all top level free space, including the old space as free. Use the
2520 			// earliest space that fits. If none, append.
2521 
2522 			FreeSpaceList spaceList;
2523 			CreateFreeSpaceList ( fileRef, oldFileSize, oldOffset, oldSize, &spaceList );
2524 
2525 			size_t freeSlot, limit;
2526 			for ( freeSlot = 0, limit = spaceList.size(); freeSlot < limit; ++freeSlot ) {
2527 				XMP_Uns64 freeSize = spaceList[freeSlot].size;
2528 				if ( (newSize == freeSize) || ( (newSize < freeSize) && ((freeSize - newSize) >= 8) ) ) break;
2529 			}
2530 
2531 			if ( freeSlot == spaceList.size() ) {
2532 
2533 				// No available free space, append the new box.
2534 				CheckFinalBox ( fileRef, abortProc, abortArg );
2535 				fileRef->ToEOF();
2536 				fileRef->Write ( newBox, newSize );
2537 				WipeBoxFree ( fileRef, oldOffset, oldSize );
2538 
2539 			} else {
2540 
2541 				// Use the available free space. Wipe non-overlapping parts of the old box. The old
2542 				// box is either included in the new space, or is fully disjoint.
2543 
2544 				SpaceInfo & newSpace = spaceList[freeSlot];
2545 
2546 				bool oldIsDisjoint = ((oldOffset + oldSize) <= newSpace.offset) ||		// Old is in front.
2547 									 ((newSpace.offset + newSpace.size) <= oldOffset);	// Old is behind.
2548 
2549 				XMP_Assert ( (newSize == newSpace.size) ||
2550 							 ( (newSize < newSpace.size) && ((newSpace.size - newSize) >= 8) ) );
2551 
2552 				XMP_Assert ( oldIsDisjoint ||
2553 							 ( (newSpace.offset <= oldOffset) &&
2554 							   ((oldOffset + oldSize) <= (newSpace.offset + newSpace.size)) ) /* old is included */ );
2555 
2556 				XMP_Uns64 newFreeOffset = newSpace.offset + newSize;
2557 				XMP_Uns64 newFreeSize   = newSpace.size - newSize;
2558 
2559 				fileRef->Seek ( newSpace.offset, kXMP_SeekFromStart );
2560 				fileRef->Write ( newBox, newSize );
2561 
2562 				if ( newFreeSize > 0 ) WriteBoxHeader ( fileRef, ISOMedia::k_free, newFreeSize );
2563 
2564 				if ( oldIsDisjoint ) {
2565 
2566 					WipeBoxFree ( fileRef, oldOffset, oldSize );
2567 
2568 				} else {
2569 
2570 					// Clear the exposed portion of the old box.
2571 
2572 					XMP_Uns64 zeroStart = newFreeOffset + 8;
2573 					if ( newFreeSize > 0xFFFFFFFF ) zeroStart += 8;
2574 					if ( oldOffset > zeroStart ) zeroStart = oldOffset;
2575 					XMP_Uns64 zeroEnd = newFreeOffset + newFreeSize;
2576 					if ( (oldOffset + oldSize) < zeroEnd ) zeroEnd = oldOffset + oldSize;
2577 
2578 					if ( zeroStart < zeroEnd ) {	// The new box might cover the old.
2579 						XMP_Assert ( (zeroEnd - zeroStart) <= (XMP_Uns64)oldSize );
2580 						XMP_Uns32 zeroSize = (XMP_Uns32) (zeroEnd - zeroStart);
2581 						fileRef->Seek ( zeroStart, kXMP_SeekFromStart );
2582 						for ( XMP_Uns32 ioCount = sizeof ( kZeroes ); zeroSize > 0; zeroSize -= ioCount ) {
2583 							if ( ioCount > zeroSize ) ioCount = zeroSize;
2584 							fileRef->Write ( &kZeroes[0], ioCount );
2585 						}
2586 					}
2587 
2588 				}
2589 
2590 			}
2591 
2592 		}
2593 
2594 	}
2595 
2596 }	// MPEG4_MetaHandler::UpdateTopLevelBox
2597 
2598 // =================================================================================================
2599 // AdjustOffset
2600 // ============
2601 //
2602 // A utility for OptimizeFileLayout, adjusts a 'stco' or 'co64' table entry for the new layout. The
2603 // map is keyed by the original box's last content offset, so that map.lower_bound does what we want.
2604 
2605 struct LayoutInfo {
2606 	XMP_Uns32 boxType;
2607 	XMP_Uns64 boxSize;	// The full size, including the header.
2608 	XMP_Uns64 oldOffset, newOffset;
LayoutInfoLayoutInfo2609 	LayoutInfo() : boxType(0), boxSize(0), oldOffset(0), newOffset(0) {};
LayoutInfoLayoutInfo2610 	LayoutInfo ( XMP_Uns32 type, XMP_Uns64 size, XMP_Uns64 offset )
2611 		: boxType(type), boxSize(size), oldOffset(offset), newOffset(0) {};
2612 };
2613 
2614 typedef std::vector < LayoutInfo > LayoutVector;
2615 typedef std::map < XMP_Uns64, LayoutInfo* > LayoutMap;
2616 
AdjustOffset(XMP_Uns64 oldOffset,const LayoutMap & newMap,GenericErrorCallback * ec)2617 static XMP_Uns64 AdjustOffset ( XMP_Uns64 oldOffset, const LayoutMap & newMap , GenericErrorCallback * ec)
2618 {
2619 
2620 	LayoutMap::const_iterator mapEntry = newMap.lower_bound ( oldOffset );
2621 	if ( (mapEntry == newMap.end()) || (oldOffset < mapEntry->second->oldOffset) ) {
2622 		XMP_Error error ( kXMPErr_BadFileFormat,"Offset from 'stco' or 'co64' is not into kept box" );
2623 		XMPFileHandler::NotifyClient(ec, kXMPErrSev_FileFatal, error);
2624 	}
2625 
2626 	XMP_Assert ( (mapEntry->second->oldOffset <= oldOffset) &&
2627 				 (oldOffset <= (mapEntry->second->oldOffset + mapEntry->second->boxSize)) );
2628 
2629 	return mapEntry->second->newOffset + (oldOffset - mapEntry->second->oldOffset);
2630 
2631 }	// AdjustOffset
2632 
2633 // =================================================================================================
2634 // MPEG4_MetaHandler::OptimizeFileLayout
2635 // =====================================
2636 //
2637 // Make sure the file is acceptable for streaming use: the 'moov' and XMP 'uuid' boxes must be
2638 // before any 'mdat' box, other top level boxes after 'mdat' are accepted. If the file needs
2639 // optimization, it is fully rewritten in this order: 'ftyp' (if ISO), 'moov', XMP 'uuid', other
2640 // non-'mdat', all 'mdat' boxes. Top level 'free' and 'skip' boxes will be removed. Offsets in the
2641 // 'stco' and 'co64' boxes will be adjusted.
2642 
OptimizeFileLayout()2643 void MPEG4_MetaHandler::OptimizeFileLayout()
2644 {
2645 	XMP_IO* originalFile = this->parent->ioRef;
2646 	XMP_Uns64 originalSize = originalFile->Length();
2647 
2648 	XMP_AbortProc abortProc  = parent->abortProc;
2649 	void *        abortArg   = parent->abortArg;
2650 	/*const bool    checkAbort = (abortProc != 0);*/
2651 
2652 	XMP_Uns64 currPos, nextPos;
2653 	ISOMedia::BoxInfo currBox1;
2654 
2655 	size_t boxCount = 0;
2656 	size_t moovIndex = 0, xmpIndex = 0;
2657 
2658 	// Go through the top level boxes to see if the file layout needs to be optimized. Look until
2659 	// we find both the 'moov' and XMP 'uuid' boxes, saving their relative index in the file.
2660 
2661 	bool needsOptimization = false;
2662 	bool moovFound = false, xmpFound = false, mdatFound = false;
2663 
2664 	for ( currPos = 0; currPos < originalSize; currPos = nextPos ) {
2665 
2666 		nextPos = ISOMedia::GetBoxInfo ( originalFile, currPos, originalSize, &currBox1 );
2667 		if ( (currBox1.boxType == ISOMedia::k_free) ||
2668 			 (currBox1.boxType == ISOMedia::k_skip) ||
2669 			 (currBox1.boxType == ISOMedia::k_wide) ) continue;
2670 
2671 		++boxCount;	// ! Must be counted for all, continue statements below skip an end of loop increment.
2672 
2673 		if ( currBox1.boxType == ISOMedia::k_mdat ) {
2674 
2675 			mdatFound = true;
2676 			XMP_Assert ( (! moovFound) | (! xmpFound) );	// The other cases should be exiting.
2677 
2678 		} else if ( currBox1.boxType == ISOMedia::k_moov ) {
2679 
2680 			moovFound = true;
2681 			moovIndex = boxCount-1;	// Need later for optimization.
2682 			needsOptimization = mdatFound;
2683 			if ( xmpFound ) break;	// Don't need to look further.
2684 
2685 		} else if ( currBox1.boxType == ISOMedia::k_uuid && ( memcmp( currBox1.idUUID, ISOMedia::k_xmpUUID, 16 ) == 0 ) ) {
2686 
2687 			xmpFound = true;
2688 			xmpIndex = boxCount-1;	// Need later for optimization.
2689 			needsOptimization = mdatFound;
2690 			if ( moovFound ) break;	// Don't need to look further.
2691 
2692 		}
2693 
2694 	}
2695 
2696 	if ( ! needsOptimization ) return;
2697 
2698 	// The file needs to be optimized. Make sure that a file over 4 GB has 'co64', not 'stco' boxes.
2699 	// These are needed to hold 64-bit offsets. We don't go to the effort of changing from 'stco'
2700 	// to 'co64', the file needs to be OK from the start. (Yes, this eliminates a marginal case of
2701 	// a file growing beyond 4 GB due to metadata growth.)
2702 
2703 	if ( originalSize >= 0xFFFFFFFF ) {
2704 
2705 		MOOV_Manager::BoxRef  moovRef, trakRef, tempRef, stcoRef;
2706 		MOOV_Manager::BoxInfo boxInfo;
2707 
2708 		moovRef = this->moovMgr.GetBox ( "moov", &boxInfo );
2709 		XMP_Enforce ( moovRef != 0 );
2710 
2711 		for ( size_t i = 0, limit = boxInfo.childCount; i < limit; ++i ) {
2712 
2713 			trakRef = this->moovMgr.GetNthChild ( moovRef, i, &boxInfo );
2714 			if ( boxInfo.boxType != ISOMedia::k_trak ) continue;
2715 
2716 			tempRef = this->moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, 0 );
2717 			if ( tempRef == 0 ) continue;
2718 			tempRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_minf, 0 );
2719 			if ( tempRef == 0 ) continue;
2720 			tempRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stbl, 0 );
2721 			if ( tempRef == 0 ) continue;
2722 
2723 			stcoRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stco, 0 );
2724 			if ( stcoRef != 0 ) {
2725 				XMP_Error error ( kXMPErr_BadFileFormat,"Large MPEG-4 file must use 'co64' boxes" );
2726 				XMPFileHandler::NotifyClient(&parent->errorCallback, kXMPErrSev_FileFatal, error);
2727 			}
2728 
2729 		}
2730 
2731 	}
2732 
2733 	// Build a vector of info for the top level boxes, ignoring 'free', 'skip', and 'wide' boxes.
2734 	// Then determine the new offsets and create a map keyed by the new offset.
2735 
2736 	// ! The box indices saved in the prior loop must match those in the vector built here!
2737 
2738 	LayoutVector fileBoxes;
2739 	LayoutMap optLayout;
2740 
2741 	for ( currPos = 0; currPos < originalSize; currPos = nextPos ) {
2742 		nextPos = ISOMedia::GetBoxInfo ( originalFile, currPos, originalSize, &currBox1 );
2743 		if ( (currBox1.boxType == ISOMedia::k_free) ||
2744 			 (currBox1.boxType == ISOMedia::k_skip) ||
2745 			 (currBox1.boxType == ISOMedia::k_wide) ) continue;
2746 		--boxCount;	// For sanity check below.
2747 		fileBoxes.push_back ( LayoutInfo ( currBox1.boxType, (currBox1.headerSize + currBox1.contentSize), currPos ) );
2748 	}
2749 
2750 	XMP_Assert ( boxCount == 0 );	// Must get the same count in both loops.
2751 	XMP_Assert ( fileBoxes.size() >= 2 );	// At least 'mdat', and 'moov' or XMP 'uuid'.
2752 	XMP_Assert ( (!moovFound) || (fileBoxes[moovIndex].boxType == ISOMedia::k_moov) );
2753 	XMP_Assert ( (!xmpFound) || (fileBoxes[xmpIndex].boxType == ISOMedia::k_uuid) );
2754 
2755 	size_t currIndex = 0, limit1 = fileBoxes.size();
2756 	XMP_Uns64 newSize = 0;
2757 
2758 	if ( fileBoxes[0].boxType == ISOMedia::k_ftyp ) {
2759 		optLayout.insert ( optLayout.end(), LayoutMap::value_type ( 0, &fileBoxes[0] ) );
2760 		newSize = fileBoxes[0].boxSize;
2761 		currIndex = 1;	// Keep the 'ftyp' box in front.
2762 	}
2763 
2764 	if ( moovFound ) {
2765 		optLayout.insert ( optLayout.end(), LayoutMap::value_type ( newSize, &fileBoxes[moovIndex] ) );
2766 		fileBoxes[moovIndex].newOffset = newSize;
2767 		newSize += fileBoxes[moovIndex].boxSize;
2768 	}
2769 
2770 	if ( xmpFound ) {
2771 		optLayout.insert ( optLayout.end(), LayoutMap::value_type ( newSize, &fileBoxes[xmpIndex] ) );
2772 		fileBoxes[xmpIndex].newOffset = newSize;
2773 		newSize += fileBoxes[xmpIndex].boxSize;
2774 	}
2775 
2776 	for ( ; currIndex < limit1; ++currIndex ) {	// Add all of the other non-'mdat' boxes to the map.
2777 		if ( moovFound && (currIndex == moovIndex) ) continue;
2778 		if ( xmpFound && (currIndex == xmpIndex) ) continue;
2779 		if ( fileBoxes[currIndex].boxType == ISOMedia::k_mdat ) continue;
2780 		optLayout.insert ( optLayout.end(), LayoutMap::value_type ( newSize, &fileBoxes[currIndex] ) );
2781 		fileBoxes[currIndex].newOffset = newSize;
2782 		newSize += fileBoxes[currIndex].boxSize;
2783 	}
2784 
2785 	for ( currIndex = 0; currIndex < limit1; ++currIndex ) {	// Add all of the 'mdat' boxes to the map.
2786 		if ( fileBoxes[currIndex].boxType != ISOMedia::k_mdat ) continue;
2787 		optLayout.insert ( optLayout.end(), LayoutMap::value_type ( newSize, &fileBoxes[currIndex] ) );
2788 		fileBoxes[currIndex].newOffset = newSize;
2789 		newSize += fileBoxes[currIndex].boxSize;
2790 	}
2791 
2792 	// Adjust the progress tracking if necessary.
2793 
2794 	XMP_ProgressTracker * progressTracker = this->parent->progressTracker;
2795 	if ( progressTracker != 0 ) {
2796 		XMP_Assert ( progressTracker->WorkInProgress() );
2797 		progressTracker->AddTotalWork ( (float) newSize );
2798 	}
2799 
2800 	// Create a temp file for the optimized layout, write it, update the offset tables.
2801 
2802 	XMP_IO* tempFile = originalFile->DeriveTemp();
2803 	XMP_Enforce ( tempFile != 0 );
2804 
2805 	// Iterate the map and write the new layout.
2806 
2807 	LayoutMap::iterator layoutPos = optLayout.begin();
2808 	LayoutMap::iterator layoutEnd = optLayout.end();
2809 
2810 	for ( ; layoutPos != layoutEnd; ++layoutPos ) {
2811 		LayoutInfo * currBox = layoutPos->second;
2812 		XMP_Assert ( (XMP_Int64)currBox->newOffset == tempFile->Length() );
2813 		originalFile->Seek ( currBox->oldOffset, kXMP_SeekFromStart );
2814 		XIO::Copy ( originalFile, tempFile, currBox->boxSize, abortProc, abortArg );
2815 	}
2816 
2817 	// Update the offset tables in the temp file. Create a layout map ordered by the last actual
2818 	// offset of the old box's content to enable fast lookup within AdjustOffset.
2819 
2820 	LayoutMap oldEndMap;
2821 	for ( size_t i = 0, limit = fileBoxes.size(); i < limit; ++i ) {
2822 		XMP_Uns64 oldEnd = fileBoxes[i].oldOffset + fileBoxes[i].boxSize - 1;	// ! Want the last actual offset!
2823 		oldEndMap.insert ( oldEndMap.end(), LayoutMap::value_type ( oldEnd, &fileBoxes[i] ) );
2824 	}
2825 
2826 	MOOV_Manager::BoxRef moovRef, trakRef, tempRef, stcoRef, co64Ref;
2827 	MOOV_Manager::BoxInfo boxInfo;
2828 
2829 	moovRef = this->moovMgr.GetBox ( "moov", &boxInfo );
2830 	XMP_Enforce ( moovRef != 0 );
2831 
2832 	for ( size_t i1 = 0, limit = boxInfo.childCount; i1 < limit; ++i1 ) {
2833 
2834 		trakRef = this->moovMgr.GetNthChild ( moovRef, i1, &boxInfo );
2835 		if ( boxInfo.boxType != ISOMedia::k_trak ) continue;
2836 
2837 		tempRef = this->moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, 0 );
2838 		if ( tempRef == 0 ) continue;
2839 		tempRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_minf, 0 );
2840 		if ( tempRef == 0 ) continue;
2841 		tempRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stbl, 0 );
2842 		if ( tempRef == 0 ) continue;
2843 
2844 		co64Ref = 0;
2845 		XMP_Uns32 entrySize = 4;
2846 		stcoRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stco, &boxInfo );
2847 		if ( stcoRef == 0 ) {
2848 			co64Ref = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_co64, &boxInfo );
2849 			if ( co64Ref == 0 ) continue;
2850 			entrySize = 8;
2851 		}
2852 
2853 		XMP_Uns32 offsetCount = GetUns32BE ( boxInfo.content + 4 );
2854 		if ( boxInfo.contentSize < (4+4 + entrySize*offsetCount) ) {
2855 			XMP_Error error ( kXMPErr_BadFileFormat, "Bad 'stco' size or count" );
2856 			XMPFileHandler::NotifyClient(&parent->errorCallback, kXMPErrSev_FileFatal, error);
2857 		}
2858 
2859 		if ( stcoRef != 0 ) {
2860 
2861 			XMP_Uns64 stcoTableOffset = fileBoxes[moovIndex].newOffset +
2862 										(XMP_Uns64) this->moovMgr.GetParsedOffset ( stcoRef ) +
2863 										(XMP_Uns64) this->moovMgr.GetHeaderSize ( stcoRef ) + 4+4;
2864 			tempFile->Seek ( stcoTableOffset, kXMP_SeekFromStart );
2865 
2866 			XMP_Uns32 * rawOldU32 = (XMP_Uns32*) (boxInfo.content + 4+4);
2867 			for ( XMP_Uns32 i = 0; i < offsetCount; ++i, ++rawOldU32 ) {
2868 				XMP_Uns64 newOffset = AdjustOffset ( (XMP_Uns64)GetUns32BE(rawOldU32), oldEndMap,&parent->errorCallback );
2869 				XMP_Uns32 u32 = MakeUns32BE ( (XMP_Uns32)newOffset );
2870 				tempFile->Write ( &u32, 4 );
2871 			}
2872 
2873 		} else {
2874 
2875 			XMP_Uns64 co64TableOffset = fileBoxes[moovIndex].newOffset +
2876 										(XMP_Uns64) this->moovMgr.GetParsedOffset ( co64Ref ) +
2877 										(XMP_Uns64) this->moovMgr.GetHeaderSize ( co64Ref ) + 4+4;
2878 			tempFile->Seek ( co64TableOffset, kXMP_SeekFromStart );
2879 
2880 			XMP_Uns64 * rawOldU64 = (XMP_Uns64*) (boxInfo.content + 4+4);
2881 			for ( XMP_Uns32 i = 0; i < offsetCount; ++i, ++rawOldU64 ) {
2882 				XMP_Uns64 newOffset = AdjustOffset ( GetUns64BE(rawOldU64), oldEndMap,&parent->errorCallback );
2883 				XMP_Uns64 u64 = MakeUns64BE ( newOffset );
2884 				tempFile->Write ( &u64, 8 );
2885 			}
2886 
2887 		}
2888 
2889 	}
2890 
2891 	// Swap the temp and original files.
2892 
2893 	originalFile->AbsorbTemp();
2894 
2895 }	// MPEG4_MetaHandler::OptimizeFileLayout
2896 
2897 // =================================================================================================
2898 // MPEG4_MetaHandler::UpdateFile
2899 // =============================
2900 //
2901 // Revamp notes:
2902 // The 'moov' subtree and possibly the XMP 'uuid' box get updated. Compose the new copy of each and
2903 // see if it fits in existing space, incorporating adjacent 'free' boxes if necessary. If that won't
2904 // work, look for a sufficient 'free' box anywhere in the file. As a last resort, append the new copy.
2905 // Assume no location sensitive data within 'moov', i.e. no offsets into it. This lets it be moved
2906 // and its children freely rearranged.
2907 
UpdateFile(bool doSafeUpdate)2908 void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate )
2909 {
2910 
2911 	bool optimizeFileLayout = false;
2912 	if ( this->parent)
2913 	{
2914 		optimizeFileLayout = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OptimizeFileLayout );
2915 	}
2916 
2917 	if ( ! this->needsUpdate ) {	// If needsUpdate is set then at least the XMP changed.
2918 		if ( optimizeFileLayout ) this->OptimizeFileLayout();
2919 		return;
2920 	}
2921 
2922 	this->needsUpdate = false;	// Make sure only called once.
2923 	IgnoreParam(doSafeUpdate);
2924 	XMP_Assert ( ! doSafeUpdate );	// This should only be called for "unsafe" updates.
2925 
2926 	/*XMP_AbortProc abortProc  = this->parent->abortProc;*/
2927 	/*void *        abortArg   = this->parent->abortArg;*/
2928 	/*const bool    checkAbort = (abortProc != 0);*/
2929 
2930 	XMP_IO* fileRef  = this->parent->ioRef;
2931 	/*XMP_Uns64   fileSize =*/ fileRef->Length();
2932 
2933 	bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
2934 
2935 	// Update the 'moov' subtree with exports from the XMP, but not the XMP itself (for QT files).
2936 
2937 	ExportMVHDItems ( this->xmpObj, &this->moovMgr );
2938 	ExportISOCopyrights ( this->xmpObj, &this->moovMgr );
2939 	ExportQuickTimeItems ( this->xmpObj, &this->tradQTMgr, &this->moovMgr );
2940 	ExportTimecodeItems ( this->xmpObj, &this->tmcdInfo, &this->tradQTMgr, &this->moovMgr );
2941 
2942 	if ( ! haveISOFile ) ExportCr8rItems ( this->xmpObj, &this->moovMgr );
2943 
2944 	// Set up progress tracking if necessary. At this point just include the XMP size, we don't
2945 	// know the 'moov' box size until later.
2946 
2947 	bool localProgressTracking = false;
2948 	XMP_ProgressTracker* progressTracker = this->parent->progressTracker;
2949 	if ( progressTracker != 0 ) {
2950 		float xmpSize = (float)this->xmpPacket.size();
2951 		if ( progressTracker->WorkInProgress() ) {
2952 			progressTracker->AddTotalWork ( xmpSize );
2953 		} else {
2954 			localProgressTracking = true;
2955 			progressTracker->BeginWork ( xmpSize );
2956 		}
2957 	}
2958 
2959 	// Try to update the XMP in-place if that is all that changed, or if it is in a preferred 'uuid' box.
2960 	// The XMP has already been serialized by common code to the appropriate length. Otherwise, update
2961 	// the 'moov'/'udta'/'XMP_' box in the MOOV_Manager, or the 'uuid' XMP box in the file.
2962 
2963 	bool useUuidXMP = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
2964 	bool inPlaceXMP = (this->xmpPacket.size() == (size_t)this->packetInfo.length) &&
2965 	                  ( (useUuidXMP & this->havePreferredXMP) || (! this->moovMgr.IsChanged()) );
2966 
2967 
2968 	if ( inPlaceXMP ) {
2969 
2970 		// Update the existing XMP in-place.
2971 		fileRef->Seek ( this->packetInfo.offset, kXMP_SeekFromStart );
2972 		fileRef->Write ( this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() );
2973 
2974 	} else if ( useUuidXMP ) {
2975 
2976 		// Don't leave an old 'moov'/'udta'/'XMP_' box around.
2977 		MOOV_Manager::BoxRef udtaRef = this->moovMgr.GetBox ( "moov/udta", 0 );
2978 		if ( udtaRef != 0 ) this->moovMgr.DeleteTypeChild ( udtaRef, ISOMedia::k_XMP_ );
2979 
2980 	} else {
2981 
2982 		// Don't leave an old uuid XMP around (if we know about it).
2983 		if ( (! havePreferredXMP) && (this->xmpBoxSize != 0) ) {
2984 			WipeBoxFree ( fileRef, this->xmpBoxPos, this->xmpBoxSize );
2985 		}
2986 
2987 		// The udta form of XMP has just the XMP packet.
2988 		this->moovMgr.SetBox ( "moov/udta/XMP_", this->xmpPacket.c_str(), (XMP_Uns32)this->xmpPacket.size() );
2989 
2990 	}
2991 
2992 	// Update the 'moov' subtree if necessary, and finally update the timecode sample.
2993 
2994 	if ( this->moovMgr.IsChanged() ) {
2995 		this->moovMgr.UpdateMemoryTree();
2996 		if ( progressTracker != 0 ) {
2997 			progressTracker->AddTotalWork ( (float)this->moovMgr.fullSubtree.size() );
2998 		}
2999 		this->UpdateTopLevelBox ( moovBoxPos, moovBoxSize, &this->moovMgr.fullSubtree[0],
3000 								  (XMP_Uns32)this->moovMgr.fullSubtree.size() );
3001 	}
3002 
3003 	if ( this->tmcdInfo.sampleOffset != 0 ) {
3004 		fileRef->Seek ( this->tmcdInfo.sampleOffset, kXMP_SeekFromStart );
3005 		XMP_Uns32 sample = MakeUns32BE ( this->tmcdInfo.timecodeSample );
3006 		fileRef->Write ( &sample, 4 );
3007 	}
3008 
3009 	// Update the 'uuid' XMP box if necessary.
3010 
3011 	if ( useUuidXMP & (! inPlaceXMP) ) {
3012 
3013 		// The uuid form of XMP has the 16-byte UUID in front of the XMP packet. Form the complete
3014 		// box (including size/type header) for UpdateTopLevelBox.
3015 		RawDataBlock uuidBox;
3016 		XMP_Uns32 uuidSize = 4 + 4 + 16 + (XMP_Uns32)this->xmpPacket.size();
3017 		uuidBox.assign ( uuidSize, 0 );
3018 		PutUns32BE ( uuidSize, &uuidBox[0] );
3019 		PutUns32BE ( ISOMedia::k_uuid, &uuidBox[4] );
3020 		memcpy ( &uuidBox[8], ISOMedia::k_xmpUUID, 16 );
3021 		memcpy ( &uuidBox[24], this->xmpPacket.c_str(), this->xmpPacket.size() );
3022 		this->UpdateTopLevelBox ( this->xmpBoxPos, this->xmpBoxSize, &uuidBox[0], uuidSize );
3023 
3024 	}
3025 
3026 	// Finally, optimize the file layout if asked.
3027 	if ( optimizeFileLayout ) this->OptimizeFileLayout();
3028 
3029 	if ( localProgressTracking ) progressTracker->WorkComplete();
3030 
3031 }	// MPEG4_MetaHandler::UpdateFile
3032 
3033 // =================================================================================================
3034 // MPEG4_MetaHandler::WriteTempFile
3035 // ================================
3036 //
3037 // Since the XMP and legacy is probably a miniscule part of the entire file, and since we can't
3038 // change the offset of most of the boxes, just copy the entire original file to the temp file, then
3039 // do an in-place update to the temp file.
3040 
WriteTempFile(XMP_IO * tempRef)3041 void MPEG4_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
3042 {
3043 	XMP_Assert ( this->needsUpdate );
3044 
3045 	XMP_IO* originalRef = this->parent->ioRef;
3046 	XMP_ProgressTracker* progressTracker = this->parent->progressTracker;
3047 
3048 	tempRef->Rewind();
3049 	originalRef->Rewind();
3050 	if ( progressTracker != 0 ) progressTracker->BeginWork ( (float) originalRef->Length() );
3051 	XIO::Copy ( originalRef, tempRef, originalRef->Length(),
3052 			    this->parent->abortProc, this->parent->abortArg );
3053 
3054 	try {
3055 		this->parent->ioRef = tempRef;	// ! Fool UpdateFile into using the temp file.
3056 		this->UpdateFile ( false );
3057 		this->parent->ioRef = originalRef;
3058 	} catch ( ... ) {
3059 		this->parent->ioRef = originalRef;
3060 		throw;
3061 	}
3062 
3063 	if ( progressTracker != 0 ) progressTracker->WorkComplete();
3064 
3065 }	// MPEG4_MetaHandler::WriteTempFile
3066 
3067 // =================================================================================================
3068