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