1 // =================================================================================================
2 // ADOBE SYSTEMS INCORPORATED
3 // Copyright 2008 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 // must have access to handler class fields...
20 #include "XMPFiles/source/FormatSupport/RIFF.hpp"
21 #include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp"
22 #include "XMPFiles/source/FormatSupport/RIFF_Support.hpp"
23 #include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
24
25 #include <sstream>
26
27 #define MIN(a, b) ((a) < (b) ? (a) : (b))
28
29 using namespace RIFF;
30 namespace RIFF {
31
32 // The minimum BEXT chunk size should be 610 (incl. 8 byte header/size field)
33 XMP_Int32 MIN_BEXT_SIZE = 610; // = > 8 + ( 256+32+32+10+8+4+4+2+64+190+0 )
34
35 // An assumed secure max value of 100 MB.
36 XMP_Int32 MAX_BEXT_SIZE = 100 * 1024 * 1024;
37
38 // CR8R, PrmL have fixed sizes
39 XMP_Int32 CR8R_SIZE = 0x5C;
40 XMP_Int32 PRML_SIZE = 0x122;
41
42 // IDIT chunk size
43 XMP_Int32 IDIT_SIZE = 0x1A;
44
45 static const char* sHexChars = "0123456789ABCDEF";
46
47 // Encode a string of raw data bytes into a HexString (w/o spaces, i.e. "DEADBEEF").
48 // No insertation/acceptance of whitespace/linefeeds. No output/tolerance of lowercase.
49 // returns true, if *all* characters returned are zero (or if 0 bytes are returned).
EncodeToHexString(XMP_StringPtr rawStr,XMP_StringLen rawLen,std::string * encodedStr)50 static bool EncodeToHexString ( XMP_StringPtr rawStr,
51 XMP_StringLen rawLen,
52 std::string* encodedStr )
53 {
54 bool allZero = true; // assume for now
55
56 if ( (rawStr == 0) && (rawLen != 0) )
57 XMP_Throw ( "EncodeToHexString: null rawStr", kXMPErr_BadParam );
58 if ( encodedStr == 0 )
59 XMP_Throw ( "EncodeToHexString: null encodedStr", kXMPErr_BadParam );
60
61 encodedStr->erase();
62 if ( rawLen == 0 ) return allZero;
63 encodedStr->reserve ( rawLen * 2 );
64
65 for( XMP_Uns32 i = 0; i < rawLen; i++ )
66 {
67 // first, second nibble
68 XMP_Uns8 first = rawStr[i] >> 4;
69 XMP_Uns8 second = rawStr[i] & 0xF;
70
71 if ( allZero && (( first != 0 ) || (second != 0)))
72 allZero = false;
73
74 encodedStr->append( 1, sHexChars[first] );
75 encodedStr->append( 1, sHexChars[second] );
76 }
77
78 return allZero;
79 } // EncodeToHexString
80
81 // -------------------------------------------------------------------------------------------------
82 // DecodeFromHexString
83 // ----------------
84 //
85 // Decode a hex string to raw data bytes.
86 // * Input must be all uppercase and w/o any whitespace, strictly (0-9A-Z)* (i.e. "DEADBEEF0099AABC")
87 // * No insertation/acceptance of whitespace/linefeeds.
88 // * bNo use/tolerance of lowercase.
89 // * Number of bytes in the encoded String must be even.
90 // * returns true if everything went well, false if illegal (non 0-9A-F) character encountered
91
DecodeFromHexString(XMP_StringPtr encodedStr,XMP_StringLen encodedLen,std::string * rawStr)92 static bool DecodeFromHexString ( XMP_StringPtr encodedStr,
93 XMP_StringLen encodedLen,
94 std::string* rawStr )
95 {
96 if ( (encodedLen % 2) != 0 )
97 return false;
98 rawStr->erase();
99 if ( encodedLen == 0 ) return true;
100 rawStr->reserve ( encodedLen / 2 );
101
102 for( XMP_Uns32 i = 0; i < encodedLen; )
103 {
104 XMP_Uns8 upperNibble = encodedStr[i];
105 if ( (upperNibble < 48) || ( (upperNibble > 57 ) && ( upperNibble < 65 ) ) || (upperNibble > 70) )
106 return false;
107 if ( upperNibble >= 65 )
108 upperNibble -= 7; // shift A-F area adjacent to 0-9
109 upperNibble -= 48; // 'shift' to a value [0..15]
110 upperNibble = ( upperNibble << 4 );
111 i++;
112
113 XMP_Uns8 lowerNibble = encodedStr[i];
114 if ( (lowerNibble < 48) || ( (lowerNibble > 57 ) && ( lowerNibble < 65 ) ) || (lowerNibble > 70) )
115 return false;
116 if ( lowerNibble >= 65 )
117 lowerNibble -= 7; // shift A-F area adjacent to 0-9
118 lowerNibble -= 48; // 'shift' to a value [0..15]
119 i++;
120
121 rawStr->append ( 1, (upperNibble + lowerNibble) );
122 }
123 return true;
124 } // DecodeFromHexString
125
126 // Converts input string to an ascii output string
127 // - terminates at first 0
128 // - replaces all non ascii with 0x3F ('?')
129 // - produces up to maxOut characters (note that several UTF-8 character bytes can 'melt' to one byte '?' in ascii.)
convertToASCII(XMP_StringPtr input,XMP_StringLen inputLen,std::string * output,XMP_StringLen maxOutputLen)130 static XMP_StringLen convertToASCII( XMP_StringPtr input, XMP_StringLen inputLen, std::string* output, XMP_StringLen maxOutputLen )
131 {
132 if ( (input == 0) && (inputLen != 0) )
133 XMP_Throw ( "convertToASCII: null input string", kXMPErr_BadParam );
134 if ( output == 0)
135 XMP_Throw ( "convertToASCII: null output string", kXMPErr_BadParam );
136 if ( maxOutputLen == 0)
137 XMP_Throw ( "convertToASCII: zero maxOutputLen chars", kXMPErr_BadParam );
138
139 output->reserve(inputLen);
140 output->erase();
141
142 bool isUTF8 = ReconcileUtils::IsUTF8( input, inputLen );
143 XMP_StringLen outputLen = 0;
144
145 for ( XMP_Uns32 i=0; i < inputLen; i++ )
146 {
147 XMP_Uns8 c = (XMP_Uns8) input[i];
148 if ( c == 0 ) // early 0 termination, leave.
149 break;
150 if ( c > 127 ) // uft-8 multi-byte sequence.
151 {
152 if ( isUTF8 ) // skip all high bytes
153 {
154 // how many bytes in this ?
155 if ( c >= 0xC2 && c <= 0xDF )
156 i+=1; // 2-byte sequence
157 else if ( c >= 0xE0 && c <= 0xEF )
158 i+=2; // 3-byte sequence
159 else if ( c >= 0xF0 && c <= 0xF4 )
160 i+=3; // 4-byte sequence
161 else
162 continue; //invalid sequence, look for next 'low' byte ..
163 } // thereafter and 'else': just append a question mark:
164 output->append( 1, '?' );
165 }
166 else // regular valid ascii. 1 byte.
167 {
168 output->append( 1, input[i] );
169 }
170 outputLen++;
171 if ( outputLen >= maxOutputLen )
172 break; // (may be even or even greater due to UFT-8 multi-byte jumps)
173 }
174
175 return outputLen;
176 }
177
178 /**
179 * ensures that native property gets returned as UTF-8 (may or mayn not already be UTF-8)
180 * - also takes care of "moot padding" (pre-mature zero termination)
181 * - propertyExists: it is important to know if there as an existing, non zero property
182 * even (in the event of serverMode) it is not actually returned, but an empty string instead.
183 */
nativePropertyToUTF8(XMP_StringPtr cstring,XMP_StringLen maxSize,bool * propertyExists)184 static std::string nativePropertyToUTF8 ( XMP_StringPtr cstring, XMP_StringLen maxSize, bool* propertyExists )
185 {
186 // the value might be properly 0-terminated, prematurely or not
187 // at all, hence scan through to find actual size
188 XMP_StringLen size = 0;
189 for ( size = 0; size < maxSize; size++ )
190 {
191 if ( cstring[size] == 0 )
192 break;
193 }
194
195 (*propertyExists) = ( size > 0 );
196
197 std::string utf8("");
198 if ( ReconcileUtils::IsUTF8( cstring, size ) )
199 utf8 = std::string( cstring, size ); //use utf8 directly
200 else
201 {
202 if ( ! ignoreLocalText )
203 {
204 #if ! UNIX_ENV // n/a anyway, since always ignoreLocalText on Unix
205 ReconcileUtils::LocalToUTF8( cstring, size, &utf8 );
206 #endif
207 }
208 }
209 return utf8;
210 }
211
212 // reads maxSize bytes from file (not "up to", exactly fullSize)
213 // puts it into a string, sets respective tree property
getBextField(const char * data,XMP_Uns32 offset,XMP_Uns32 maxSize)214 static std::string getBextField ( const char* data, XMP_Uns32 offset, XMP_Uns32 maxSize )
215 {
216 if (data == 0)
217 XMP_Throw ( "getBextField: null data pointer", kXMPErr_BadParam );
218 if ( maxSize == 0)
219 XMP_Throw ( "getBextField: maxSize must be greater than 0", kXMPErr_BadParam );
220
221 std::string r;
222 convertToASCII( data+offset, maxSize, &r, maxSize );
223 return r;
224 }
225
importBextChunkToXMP(RIFF_MetaHandler * handler,ValueChunk * bextChunk)226 static void importBextChunkToXMP( RIFF_MetaHandler* handler, ValueChunk* bextChunk )
227 {
228 // if there's a bext chunk, there is data...
229 handler->containsXMP = true; // very important for treatment on caller level
230
231 XMP_Enforce( bextChunk->oldSize >= MIN_BEXT_SIZE );
232 XMP_Enforce( bextChunk->oldSize < MAX_BEXT_SIZE );
233
234 const char* data = bextChunk->oldValue.data();
235 std::string value;
236
237 // register bext namespace:
238 SXMPMeta::RegisterNamespace( kXMP_NS_BWF, "bext:", 0 );
239
240 // bextDescription ------------------------------------------------
241 value = getBextField( data, 0, 256 );
242 if ( value.size() > 0 )
243 handler->xmpObj.SetProperty( bextDescription.ns, bextDescription.prop, value.c_str() );
244
245 // bextOriginator -------------------------------------------------
246 value = getBextField( data, 256, 32 );
247 if ( value.size() > 0 )
248 handler->xmpObj.SetProperty( bextOriginator.ns , bextOriginator.prop, value.c_str() );
249
250 // bextOriginatorRef ----------------------------------------------
251 value = getBextField( data, 256+32, 32 );
252 if ( value.size() > 0 )
253 handler->xmpObj.SetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, value.c_str() );
254
255 // bextOriginationDate --------------------------------------------
256 value = getBextField( data, 256+32+32, 10 );
257 if ( value.size() > 0 )
258 handler->xmpObj.SetProperty( bextOriginationDate.ns , bextOriginationDate.prop, value.c_str() );
259
260 // bextOriginationTime --------------------------------------------
261 value = getBextField( data, 256+32+32+10, 8 );
262 if ( value.size() > 0 )
263 handler->xmpObj.SetProperty( bextOriginationTime.ns , bextOriginationTime.prop, value.c_str() );
264
265 // bextTimeReference ----------------------------------------------
266 // thanx to nice byte order, all 8 bytes can be read as one:
267 XMP_Uns64 timeReferenceFull = GetUns64LE( &(data[256+32+32+10+8 ] ) );
268 value.erase();
269 SXMPUtils::ConvertFromInt64( timeReferenceFull, "%llu", &value );
270 handler->xmpObj.SetProperty( bextTimeReference.ns, bextTimeReference.prop, value );
271
272 // bextVersion ----------------------------------------------------
273 XMP_Uns16 bwfVersion = GetUns16LE( &(data[256+32+32+10+8+8] ) );
274 value.erase();
275 SXMPUtils::ConvertFromInt( bwfVersion, "", &value );
276 handler->xmpObj.SetProperty( bextVersion.ns, bextVersion.prop, value );
277
278 // bextUMID -------------------------------------------------------
279 // binary string is already in memory, must convert to hex string
280 std::string umidString;
281 bool allZero = EncodeToHexString( &(data[256+32+32+10+8+8+2]), 64, &umidString );
282 if (! allZero )
283 handler->xmpObj.SetProperty( bextUMID.ns, bextUMID.prop, umidString );
284
285 // bextCodingHistory ----------------------------------------------
286 bool hasCodingHistory = bextChunk->oldSize > MIN_BEXT_SIZE;
287
288 if ( hasCodingHistory )
289 {
290 XMP_StringLen codingHistorySize = (XMP_StringLen) (bextChunk->oldSize - MIN_BEXT_SIZE);
291 std::string codingHistory;
292 convertToASCII( &data[MIN_BEXT_SIZE-8], codingHistorySize, &codingHistory, codingHistorySize );
293 if (! codingHistory.empty() )
294 handler->xmpObj.SetProperty( bextCodingHistory.ns, bextCodingHistory.prop, codingHistory );
295 }
296 } // importBextChunkToXMP
297
GetMonth(const char * valuePtr)298 static XMP_Int32 GetMonth( const char * valuePtr )
299 {
300 // This will check 3 characters (4,5,6) of the input and return corresponding months as 1,2,...,12
301 // else it will return 0
302 // Input should as follows : wda mon dd hh:mm:ss yyyy\n\0
303 char firstChar = tolower( valuePtr[ 4 ] );
304 char secondChar = tolower( valuePtr[ 5 ] );
305 char thirdChar = tolower( valuePtr[ 6 ] );
306 if ( firstChar == 'j' && secondChar == 'a' && thirdChar == 'n' )
307 return 1;
308 if ( firstChar == 'f' && secondChar == 'e' && thirdChar == 'b' )
309 return 2;
310 if ( firstChar == 'm' && secondChar == 'a' )
311 {
312 if ( thirdChar == 'r' )
313 return 3;
314 if ( thirdChar == 'y' )
315 return 5;
316 }
317 if ( firstChar == 'a' && secondChar == 'p' && thirdChar == 'r' )
318 return 4;
319 if ( firstChar == 'j' && secondChar == 'u' )
320 {
321 if ( thirdChar == 'n' )
322 return 6;
323 if ( thirdChar == 'l' )
324 return 7;
325 }
326 if ( firstChar == 'a' && secondChar == 'u' && thirdChar == 'g' )
327 return 8;
328 if ( firstChar == 's' && secondChar == 'e' && thirdChar == 'p' )
329 return 9;
330 if ( firstChar == 'o' && secondChar == 'c' && thirdChar == 't' )
331 return 10;
332 if ( firstChar == 'n' && secondChar == 'o' && thirdChar == 'v' )
333 return 11;
334 if ( firstChar == 'd' && secondChar == 'e' && thirdChar == 'c' )
335 return 12;
336 return 0;
337 }
338
GatherUnsignedInt(const char * strPtr,size_t count)339 static XMP_Uns32 GatherUnsignedInt ( const char * strPtr, size_t count )
340 {
341 XMP_Uns32 value = 0;
342 const char * strEnd = strPtr + count;
343
344 while ( strPtr < strEnd ) {
345 if ( *strPtr == ' ' ) ++strPtr;
346 else break;
347 }
348
349 while ( strPtr < strEnd ) {
350 char ch = *strPtr;
351 if ( (ch < '0') || (ch > '9') ) break;
352 value = value*10 + (ch - '0');
353 ++strPtr;
354 }
355
356 return value;
357
358 } // GatherUnsignedInt
359
importIditChunkToXMP(RIFF_MetaHandler * handler,ValueChunk * iditChunk)360 static void importIditChunkToXMP( RIFF_MetaHandler* handler, ValueChunk* iditChunk )
361 {
362 // if there iss a IDIT chunk, there is data...
363 handler->containsXMP = true; // very important for treatment on caller level
364
365 // Size has been already checked in calling function
366 XMP_Enforce( iditChunk->oldSize == IDIT_SIZE + 8 );
367 const char * valuePtr = iditChunk->oldValue.c_str();
368 XMP_Enforce( valuePtr[ IDIT_SIZE - 2 ] == 0x0A );
369 XMP_Enforce( valuePtr[ 13 ] == ':' && valuePtr[ 16 ] == ':' );
370 XMP_DateTime dateTime;
371 dateTime.month = GetMonth( valuePtr );
372 dateTime.day = GatherUnsignedInt( valuePtr + 8, 2 );
373 dateTime.hour = GatherUnsignedInt( valuePtr + 11, 2 );
374 dateTime.minute = GatherUnsignedInt( valuePtr + 14, 2 );
375 dateTime.second = GatherUnsignedInt( valuePtr + 17, 2 );
376 dateTime.year = GatherUnsignedInt( valuePtr + 20, 4 );
377 handler->xmpObj.SetProperty_Date( kXMP_NS_EXIF, "DateTimeOriginal", dateTime );
378
379 } // importIditChunkToXMP
380
importPrmLToXMP(RIFF_MetaHandler * handler,ValueChunk * prmlChunk)381 static void importPrmLToXMP( RIFF_MetaHandler* handler, ValueChunk* prmlChunk )
382 {
383 bool haveXMP = false;
384
385 XMP_Enforce( prmlChunk->oldSize == PRML_SIZE );
386 PrmLBoxContent rawPrmL;
387 XMP_Assert ( sizeof ( rawPrmL ) == PRML_SIZE - 8 ); // double check tight packing.
388 XMP_Assert ( sizeof ( rawPrmL.filePath ) == 260 );
389 memcpy ( &rawPrmL, prmlChunk->oldValue.data(), sizeof (rawPrmL) );
390
391 if ( rawPrmL.magic != 0xBEEFCAFE ) {
392 Flip4 ( &rawPrmL.exportType ); // The only numeric field that we care about.
393 }
394
395 rawPrmL.filePath[259] = 0; // Ensure a terminating nul.
396 if ( rawPrmL.filePath[0] != 0 ) {
397 if ( rawPrmL.filePath[0] == '/' ) {
398 haveXMP = true;
399 handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom",
400 kXMP_NS_CreatorAtom, "posixProjectPath", rawPrmL.filePath );
401 } else if ( XMP_LitNMatch ( rawPrmL.filePath, "\\\\?\\", 4 ) ) {
402 haveXMP = true;
403 handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom",
404 kXMP_NS_CreatorAtom, "uncProjectPath", rawPrmL.filePath );
405 }
406 }
407
408 const char * exportStr = 0;
409 switch ( rawPrmL.exportType ) {
410 case kExportTypeMovie : exportStr = "movie"; break;
411 case kExportTypeStill : exportStr = "still"; break;
412 case kExportTypeAudio : exportStr = "audio"; break;
413 case kExportTypeCustom : exportStr = "custom"; break;
414 }
415 if ( exportStr != 0 ) {
416 haveXMP = true;
417 handler->xmpObj.SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", exportStr );
418 }
419
420 handler->containsXMP |= haveXMP; // mind the '|='
421 } // importCr8rToXMP
422
importCr8rToXMP(RIFF_MetaHandler * handler,ValueChunk * cr8rChunk)423 static void importCr8rToXMP( RIFF_MetaHandler* handler, ValueChunk* cr8rChunk )
424 {
425 bool haveXMP = false;
426
427 XMP_Enforce( cr8rChunk->oldSize == CR8R_SIZE );
428 Cr8rBoxContent rawCr8r;
429 XMP_Assert ( sizeof ( rawCr8r ) == CR8R_SIZE - 8 ); // double check tight packing.
430 memcpy ( &rawCr8r, cr8rChunk->oldValue.data(), sizeof (rawCr8r) );
431
432 if ( rawCr8r.magic != 0xBEEFCAFE ) {
433 Flip4 ( &rawCr8r.creatorCode ); // The only numeric fields that we care about.
434 Flip4 ( &rawCr8r.appleEvent );
435 }
436
437 std::string fieldPath;
438
439 SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &fieldPath );
440 if ( rawCr8r.creatorCode != 0 ) {
441 haveXMP = true;
442 handler->xmpObj.SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.creatorCode ); // ! Unsigned trickery.
443 }
444
445 SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &fieldPath );
446 if ( rawCr8r.appleEvent != 0 ) {
447 haveXMP = true;
448 handler->xmpObj.SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.appleEvent ); // ! Unsigned trickery.
449 }
450
451 rawCr8r.fileExt[15] = 0; // Ensure a terminating nul.
452 if ( rawCr8r.fileExt[0] != 0 ) {
453 haveXMP = true;
454 handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", rawCr8r.fileExt );
455 }
456
457 rawCr8r.appOptions[15] = 0; // Ensure a terminating nul.
458 if ( rawCr8r.appOptions[0] != 0 ) {
459 haveXMP = true;
460 handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", rawCr8r.appOptions );
461 }
462
463 rawCr8r.appName[31] = 0; // Ensure a terminating nul.
464 if ( rawCr8r.appName[0] != 0 ) {
465 haveXMP = true;
466 handler->xmpObj.SetProperty ( kXMP_NS_XMP, "CreatorTool", rawCr8r.appName );
467 }
468
469 handler->containsXMP |= haveXMP; // mind the '|='
470 } // importCr8rToXMP
471
472
importListChunkToXMP(RIFF_MetaHandler * handler,ContainerChunk * listChunk,Mapping mapping[],bool xmpHasPriority)473 static void importListChunkToXMP( RIFF_MetaHandler* handler, ContainerChunk* listChunk, Mapping mapping[], bool xmpHasPriority )
474 {
475 valueMap* cm = &listChunk->childmap;
476 for (int p=0; mapping[p].chunkID != 0; p++) // go through legacy chunks
477 {
478 valueMapIter result = cm->find(mapping[p].chunkID);
479 if( result != cm->end() ) // if value found
480 {
481 ValueChunk* propChunk = result->second;
482
483 bool propertyExists = false;
484 std::string utf8 = nativePropertyToUTF8(
485 propChunk->oldValue.c_str(),
486 (XMP_StringLen)propChunk->oldValue.size(), &propertyExists );
487
488 if ( utf8.size() > 0 ) // if property is not-empty, set Property
489 {
490 switch ( mapping[p].propType )
491 {
492 case prop_TIMEVALUE:
493 if ( xmpHasPriority &&
494 handler->xmpObj.DoesStructFieldExist( mapping[p].ns, mapping[p].prop, kXMP_NS_DM, "timeValue" ))
495 break; // skip if XMP has precedence and exists
496 handler->xmpObj.SetStructField( mapping[p].ns, mapping[p].prop,
497 kXMP_NS_DM, "timeValue", utf8.c_str() );
498 break;
499 case prop_LOCALIZED_TEXT:
500 if ( xmpHasPriority && handler->xmpObj.GetLocalizedText( mapping[p].ns ,
501 mapping[p].prop, "" , "x-default", 0, 0, 0 ))
502 break; // skip if XMP has precedence and exists
503 handler->xmpObj.SetLocalizedText( mapping[p].ns , mapping[p].prop,
504 "" , "x-default" , utf8.c_str() );
505 if ( mapping[p].chunkID == kPropChunkINAM )
506 handler->hasListInfoINAM = true; // needs to be known for special 3-way merge around dc:title
507 break;
508 case prop_ARRAYITEM:
509 if ( xmpHasPriority &&
510 handler->xmpObj.DoesArrayItemExist( mapping[p].ns, mapping[p].prop, 1 ))
511 break; // skip if XMP has precedence and exists
512 handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop );
513 handler->xmpObj.AppendArrayItem( mapping[p].ns, mapping[p].prop, kXMP_PropValueIsArray, utf8.c_str(), kXMP_NoOptions );
514 break;
515 case prop_SIMPLE:
516 if ( xmpHasPriority &&
517 handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop ))
518 break; // skip if XMP has precedence and exists
519 handler->xmpObj.SetProperty( mapping[p].ns, mapping[p].prop, utf8.c_str() );
520 break;
521 default:
522 XMP_Throw( "internal error" , kXMPErr_InternalFailure );
523 }
524
525 handler->containsXMP = true; // very important for treatment on caller level
526 }
527 else if ( ! propertyExists) // otherwise remove it.
528 { // [2389942] don't, if legacy value is existing but non-retrievable (due to server mode)
529 switch ( mapping[p].propType )
530 {
531 case prop_TIMEVALUE:
532 if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority
533 handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop ))
534 handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop );
535 break;
536 case prop_LOCALIZED_TEXT:
537 if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority
538 handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop ))
539 handler->xmpObj.DeleteLocalizedText( mapping[p].ns, mapping[p].prop, "", "x-default" );
540 break;
541 case prop_ARRAYITEM:
542 case prop_SIMPLE:
543 if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority
544 handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop ))
545 handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop );
546 break;
547 default:
548 XMP_Throw( "internal error" , kXMPErr_InternalFailure );
549 }
550 }
551 }
552 } // for
553 }
importProperties(RIFF_MetaHandler * handler)554 void importProperties( RIFF_MetaHandler* handler )
555 {
556 bool hasDigest = handler->xmpObj.GetProperty( kXMP_NS_WAV, "NativeDigest", NULL , NULL );
557 if ( hasDigest )
558 {
559 // remove! since it now becomse a 'new' handler file
560 handler->xmpObj.DeleteProperty( kXMP_NS_WAV, "NativeDigest" );
561 }
562
563 // BWF Bext extension chunk -----------------------------------------------
564 if ( handler->parent->format == kXMP_WAVFile && // applies only to WAV
565 handler->bextChunk != 0 ) //skip if no BEXT chunk found.
566 {
567 importBextChunkToXMP( handler, handler->bextChunk );
568 }
569
570 // PrmL chunk --------------------------------------------------------------
571 if ( handler->prmlChunk != 0 && handler->prmlChunk->oldSize == PRML_SIZE )
572 {
573 importPrmLToXMP( handler, handler->prmlChunk );
574 }
575
576 // Cr8r chunk --------------------------------------------------------------
577 if ( handler->cr8rChunk != 0 && handler->cr8rChunk->oldSize == CR8R_SIZE )
578 {
579 importCr8rToXMP( handler, handler->cr8rChunk );
580 }
581
582 // LIST:INFO --------------------------------------------------------------
583 if ( handler->listInfoChunk != 0) //skip if no LIST:INFO chunk found.
584 importListChunkToXMP( handler, handler->listInfoChunk, listInfoProps, hasDigest );
585
586 // LIST:Tdat --------------------------------------------------------------
587 if ( handler->listTdatChunk != 0)
588 importListChunkToXMP( handler, handler->listTdatChunk, listTdatProps, hasDigest );
589
590 // DISP (do last, higher priority than INAM ) -----------------------------
591 bool takeXMP = false; // assume for now
592 if ( hasDigest )
593 {
594 std::string actualLang, value;
595 bool r = handler->xmpObj.GetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, &value, NULL );
596 if ( r && (actualLang == "x-default") ) takeXMP = true;
597 }
598
599 if ( (!takeXMP) && handler->dispChunk != 0) //skip if no LIST:INFO chunk found.
600 {
601 std::string* value = &handler->dispChunk->oldValue;
602 if ( value->size() >= 4 ) // ignore contents if file too small
603 {
604 XMP_StringPtr cstring = value->c_str();
605 XMP_StringLen size = (XMP_StringLen) value->size();
606
607 size -= 4; // skip first four bytes known to contain constant
608 cstring += 4;
609
610 bool propertyExists = false;
611 std::string utf8 = nativePropertyToUTF8( cstring, size, &propertyExists );
612
613 if ( utf8.size() > 0 )
614 {
615 handler->xmpObj.SetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , utf8.c_str() );
616 handler->containsXMP = true; // very important for treatment on caller level
617 }
618 else
619 {
620 // found as part of [2389942]
621 // forward deletion may only happen if no LIST:INFO/INAM is present:
622 if ( ! handler->hasListInfoINAM &&
623 ! propertyExists ) // ..[2389942]part2: and if truly no legacy property
624 { // (not just an unreadable one due to ServerMode).
625 handler->xmpObj.DeleteProperty( kXMP_NS_DC, "title" );
626 }
627 }
628 } // if size sufficient
629 } // handler->dispChunk
630
631 // IDIT chunk --------------------------------------------------------------
632 if ( handler->parent->format == kXMP_AVIFile && // Only for AVI file
633 handler->iditChunk != 0 && handler->iditChunk->oldSize == IDIT_SIZE + 8 ) // Including header size i.e, ID + size
634 {
635 importIditChunkToXMP( handler, handler->iditChunk );
636 }
637
638 } // importProperties
639
640 ////////////////////////////////////////////////////////////////////////////////
641 // EXPORT
642 ////////////////////////////////////////////////////////////////////////////////
643
relocateWronglyPlacedXMPChunk(RIFF_MetaHandler * handler)644 void relocateWronglyPlacedXMPChunk( RIFF_MetaHandler* handler )
645 {
646 /*XMP_IO* file = handler->parent->ioRef;*/
647 RIFF::containerVect *rc = &handler->riffChunks;
648 RIFF::ContainerChunk* lastChunk = rc->at( rc->size()-1 );
649
650 // 1) XMPPacket
651 // needChunk exists but is not in lastChunk ?
652 if (
653 handler->xmpChunk != 0 && // XMP Chunk existing?
654 (XMP_Uns32)rc->size() > 1 && // more than 1 top-level chunk (otherwise pointless)
655 lastChunk->getChild( handler->xmpChunk ) == lastChunk->children.end() // not already in last chunk?
656 )
657 {
658 RIFF::ContainerChunk* cur;
659 chunkVectIter child;
660 XMP_Int32 chunkNo;
661
662 // find and relocate to last chunk:
663 for ( chunkNo = (XMP_Int32)rc->size()-2 ; chunkNo >= 0; chunkNo-- ) // ==> start with second-last chunk
664 {
665 cur = rc->at(chunkNo);
666 child = cur->getChild( handler->xmpChunk );
667 if ( child != cur->children.end() ) // found?
668 break;
669 } // for
670
671 if ( chunkNo < 0 ) // already in place? nothing left to do.
672 return;
673
674 lastChunk->children.push_back( *child ); // nb: order matters!
675 cur->replaceChildWithJunk( *child, false );
676 cur->hasChange = true; // [2414649] initialize early-on i.e: here
677 } // if
678 } // relocateWronglyPlacedXMPChunk
679
680 // writes to buffer up to max size,
681 // 0 termination only if shorter than maxSize
682 // converts down to ascii
setBextField(std::string * value,XMP_Uns8 * data,XMP_Uns32 offset,XMP_Uns32 maxSize)683 static void setBextField ( std::string* value, XMP_Uns8* data, XMP_Uns32 offset, XMP_Uns32 maxSize )
684 {
685 XMP_Validate( value != 0, "setBextField: null value string pointer", kXMPErr_BadParam );
686 XMP_Validate( data != 0, "setBextField: null data value", kXMPErr_BadParam );
687 XMP_Validate( maxSize > 0, "setBextField: maxSize must be greater than 0", kXMPErr_BadParam );
688
689 std::string ascii;
690 XMP_StringLen actualSize = convertToASCII( value->data(), (XMP_StringLen) value->size() , &ascii , maxSize );
691 strncpy( (char*)(data + offset), ascii.data(), actualSize );
692 }
693
694 // add bwf-bext related data to bext chunk, create if not existing yet.
695 // * in fact, since bext is fully fixed and known, there can be no unknown subchunks worth keeping:
696 // * prepare bext chunk in buffer
697 // * value changed/created if needed only, otherways remove chunk
698 // * remove bext-mapped properties from xmp (non-redundant storage)
699 // note: ValueChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation)
exportXMPtoBextChunk(RIFF_MetaHandler * handler,ValueChunk ** bextChunk)700 static void exportXMPtoBextChunk( RIFF_MetaHandler* handler, ValueChunk** bextChunk )
701 {
702 // register bext namespace ( if there was no import, this is news, otherwise harmless moot)
703 SXMPMeta::RegisterNamespace( kXMP_NS_BWF, "bext:", 0 );
704
705 bool chunkUsed = false; // assume for now
706 SXMPMeta* xmp = &handler->xmpObj;
707
708 // prepare buffer, need to know CodingHistory size as the only variable
709 XMP_Int32 bextBufferSize = MIN_BEXT_SIZE - 8; // -8 because of header
710 std::string value;
711 if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ))
712 {
713 bextBufferSize += ((XMP_StringLen)value.size()) + 1 ; // add to size (and a trailing zero)
714 }
715
716 // create and clear buffer
717 XMP_Uns8* buffer = new XMP_Uns8[bextBufferSize];
718 for (XMP_Int32 i = 0; i < bextBufferSize; i++ )
719 buffer[i] = 0;
720
721 // grab props, write into buffer, remove from XMP ///////////////////////////
722 // bextDescription ------------------------------------------------
723 if ( xmp->GetProperty( bextDescription.ns, bextDescription.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
724 {
725 setBextField( &value, (XMP_Uns8*) buffer, 0, 256 );
726 xmp->DeleteProperty( bextDescription.ns, bextDescription.prop) ;
727 chunkUsed = true;
728 }
729 // bextOriginator -------------------------------------------------
730 if ( xmp->GetProperty( bextOriginator.ns , bextOriginator.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
731 {
732 setBextField( &value, (XMP_Uns8*) buffer, 256, 32 );
733 xmp->DeleteProperty( bextOriginator.ns , bextOriginator.prop );
734 chunkUsed = true;
735 }
736 // bextOriginatorRef ----------------------------------------------
737 if ( xmp->GetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
738 {
739 setBextField( &value, (XMP_Uns8*) buffer, 256+32, 32 );
740 xmp->DeleteProperty( bextOriginatorRef.ns , bextOriginatorRef.prop );
741 chunkUsed = true;
742 }
743 // bextOriginationDate --------------------------------------------
744 if ( xmp->GetProperty( bextOriginationDate.ns , bextOriginationDate.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
745 {
746 setBextField( &value, (XMP_Uns8*) buffer, 256+32+32, 10 );
747 xmp->DeleteProperty( bextOriginationDate.ns , bextOriginationDate.prop );
748 chunkUsed = true;
749 }
750 // bextOriginationTime --------------------------------------------
751 if ( xmp->GetProperty( bextOriginationTime.ns , bextOriginationTime.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
752 {
753 setBextField( &value, (XMP_Uns8*) buffer, 256+32+32+10, 8 );
754 xmp->DeleteProperty( bextOriginationTime.ns , bextOriginationTime.prop );
755 chunkUsed = true;
756 }
757 // bextTimeReference ----------------------------------------------
758 // thanx to friendly byte order, all 8 bytes can be written in one go:
759 if ( xmp->GetProperty( bextTimeReference.ns, bextTimeReference.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
760 {
761 try
762 {
763 XMP_Int64 v = SXMPUtils::ConvertToInt64( value.c_str() );
764 PutUns64LE( v, &(buffer[256+32+32+10+8] ));
765 chunkUsed = true;
766 }
767 catch (XMP_Error& e)
768 {
769 if ( e.GetID() != kXMPErr_BadParam )
770 throw e; // re-throw on any other error
771 } // 'else' tolerate ( time reference remains 0x00000000 )
772 // valid or not, do not store redundantly:
773 xmp->DeleteProperty( bextTimeReference.ns, bextTimeReference.prop );
774 }
775
776 // bextVersion ----------------------------------------------------
777 // set version=1, no matter what.
778 PutUns16LE( 1, &(buffer[256+32+32+10+8+8]) );
779 xmp->DeleteProperty( bextVersion.ns, bextVersion.prop );
780
781 // bextUMID -------------------------------------------------------
782 if ( xmp->GetProperty( bextUMID.ns, bextUMID.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
783 {
784 std::string rawStr;
785
786 if ( !DecodeFromHexString( value.data(), (XMP_StringLen) value.size(), &rawStr ) )
787 {
788 delete [] buffer; // important.
789 XMP_Throw ( "EncodeFromHexString: illegal umid string. Must contain an even number of 0-9 and uppercase A-F chars.", kXMPErr_BadParam );
790 }
791
792 // if UMID is smaller/longer than 64 byte for any reason,
793 // truncate/do a partial write (just like for any other bext property)
794
795 memcpy( (char*) &(buffer[256+32+32+10+8+8+2]), rawStr.data(), MIN( 64, rawStr.size() ) );
796 xmp->DeleteProperty( bextUMID.ns, bextUMID.prop );
797 chunkUsed = true;
798 }
799
800 // bextCodingHistory ----------------------------------------------
801 if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) )
802 {
803 std::string ascii;
804 convertToASCII( value.data(), (XMP_StringLen) value.size() , &ascii, (XMP_StringLen) value.size() );
805 strncpy( (char*) &(buffer[MIN_BEXT_SIZE-8]), ascii.data(), ascii.size() );
806 xmp->DeleteProperty( bextCodingHistory.ns, bextCodingHistory.prop );
807 chunkUsed = true;
808 }
809
810 // always delete old, recreate if needed
811 if ( *bextChunk != 0 )
812 {
813 (*bextChunk)->parent->replaceChildWithJunk( *bextChunk );
814 (*bextChunk) = 0; // clear direct Chunk pointer
815 }
816
817 if ( chunkUsed)
818 *bextChunk = new ValueChunk( handler->riffChunks.at(0), std::string( (char*)buffer, bextBufferSize ), kChunk_bext );
819
820 delete [] buffer; // important.
821 }
822
SetBufferedString(char * dest,const std::string source,size_t limit)823 static inline void SetBufferedString ( char * dest, const std::string source, size_t limit )
824 {
825 memset ( dest, 0, limit );
826 size_t count = source.size();
827 if ( count >= limit ) count = limit - 1; // Ensure a terminating nul.
828 memcpy ( dest, source.c_str(), count );
829 }
830
exportXMPtoCr8rChunk(RIFF_MetaHandler * handler,ValueChunk ** cr8rChunk)831 static void exportXMPtoCr8rChunk ( RIFF_MetaHandler* handler, ValueChunk** cr8rChunk )
832 {
833 const SXMPMeta & xmp = handler->xmpObj;
834
835 // Make sure an existing Cr8r chunk has the proper fixed length.
836 bool haveOldCr8r = (*cr8rChunk != 0);
837 if ( haveOldCr8r && ((*cr8rChunk)->oldSize != sizeof(Cr8rBoxContent)+8) ) {
838 (*cr8rChunk)->parent->replaceChildWithJunk ( *cr8rChunk ); // Wrong length, the existing chunk must be bad.
839 (*cr8rChunk) = 0;
840 haveOldCr8r = false;
841 }
842
843 bool haveNewCr8r = false;
844 std::string creatorCode, appleEvent, fileExt, appOptions, appName;
845
846 haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &creatorCode, 0 );
847 haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &appleEvent, 0 );
848 haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fileExt, 0 );
849 haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &appOptions, 0 );
850 haveNewCr8r |= xmp.GetProperty ( kXMP_NS_XMP, "CreatorTool", &appName, 0 );
851
852 if ( ! haveNewCr8r ) { // Get rid of an existing Cr8r chunk if there is no new XMP.
853 if ( haveOldCr8r ) {
854 (*cr8rChunk)->parent->replaceChildWithJunk ( *cr8rChunk );
855 *cr8rChunk = 0;
856 }
857 return;
858 }
859
860 if ( ! haveOldCr8r ) {
861 *cr8rChunk = new ValueChunk ( handler->lastChunk, std::string(), kChunk_Cr8r );
862 }
863
864 std::string strValue;
865 strValue.assign ( (sizeof(Cr8rBoxContent) - 1), '\0' ); // ! Use size-1 because SetValue appends a trailing 0 byte.
866 (*cr8rChunk)->SetValue ( strValue ); // ! Just get the space available.
867 XMP_Assert ( (*cr8rChunk)->newValue.size() == sizeof(Cr8rBoxContent) );
868 (*cr8rChunk)->hasChange = true;
869
870 Cr8rBoxContent * newCr8r = (Cr8rBoxContent*) (*cr8rChunk)->newValue.data();
871
872 if ( ! haveOldCr8r ) {
873
874 newCr8r->magic = MakeUns32LE ( 0xBEEFCAFE );
875 newCr8r->size = MakeUns32LE ( sizeof(Cr8rBoxContent) );
876 newCr8r->majorVer = MakeUns16LE ( 1 );
877
878 } else {
879
880 const Cr8rBoxContent * oldCr8r = (Cr8rBoxContent*) (*cr8rChunk)->oldValue.data();
881 memcpy ( newCr8r, oldCr8r, sizeof(Cr8rBoxContent) );
882 if ( GetUns32LE ( &newCr8r->magic ) != 0xBEEFCAFE ) { // Make sure we write LE numbers.
883 Flip4 ( &newCr8r->magic );
884 Flip4 ( &newCr8r->size );
885 Flip2 ( &newCr8r->majorVer );
886 Flip2 ( &newCr8r->minorVer );
887 Flip4 ( &newCr8r->creatorCode );
888 Flip4 ( &newCr8r->appleEvent );
889 }
890
891 }
892
893 if ( ! creatorCode.empty() ) {
894 newCr8r->creatorCode = MakeUns32LE ( (XMP_Uns32) strtoul ( creatorCode.c_str(), 0, 0 ) );
895 }
896
897 if ( ! appleEvent.empty() ) {
898 newCr8r->appleEvent = MakeUns32LE ( (XMP_Uns32) strtoul ( appleEvent.c_str(), 0, 0 ) );
899 }
900
901 if ( ! fileExt.empty() ) SetBufferedString ( newCr8r->fileExt, fileExt, sizeof ( newCr8r->fileExt ) );
902 if ( ! appOptions.empty() ) SetBufferedString ( newCr8r->appOptions, appOptions, sizeof ( newCr8r->appOptions ) );
903 if ( ! appName.empty() ) SetBufferedString ( newCr8r->appName, appName, sizeof ( newCr8r->appName ) );
904
905 }
906
907 // Returns numbers of leap years between 1800 and provided year value. Both end values will be inclusive for getting this field.
908 // For leap year this will be handled later in GetIDITString() function
GetLeapYearsNumber(const XMP_Uns32 & year)909 static XMP_Uns32 GetLeapYearsNumber( const XMP_Uns32 & year )
910 {
911
912 XMP_Uns32 numLeapYears = ( year / 4 );
913 numLeapYears -= ( year / 100 );
914 numLeapYears += ( ( year + 200 ) / 400 ); // 200 is added becuase our base is 1800 which give modulas 200 by divinding with 400
915 return numLeapYears;
916
917 } //GetLeapYearsNumber
918
GetDays(const XMP_Int32 & month,const bool & isLeapYear)919 static XMP_Uns32 GetDays( const XMP_Int32 & month, const bool &isLeapYear )
920 {
921 // Adding number of days as per last month of the provided year
922 // Leap year case is handled later
923 XMP_Uns32 numDays = 0;
924 switch ( month )
925 {
926 case 2:
927 numDays = 31;
928 break;
929 case 3:
930 numDays = 31 + 28;
931 break;
932 case 4:
933 numDays = 31 + 28 + 31;
934 break;
935 case 5:
936 numDays = 31 + 28 + 31 + 30;
937 break;
938 case 6:
939 numDays = 31 + 28 + 31 + 30 + 31;
940 break;
941 case 7:
942 numDays = 31 + 28 + 31 + 30 + 31 + 30;
943 break;
944 case 8:
945 numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31;
946 break;
947 case 9:
948 numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31;
949 break;
950 case 10:
951 numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30;
952 break;
953 case 11:
954 numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31;
955 break;
956 case 12:
957 numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30;
958 break;
959 default:
960 break;
961 }
962
963 // Adding one day for leap year and month above than feb
964 if ( isLeapYear == true && month > 2 )
965 numDays += 1;
966
967 return numDays;
968
969 } // GetDays
970
GetIDITString(const XMP_DateTime & targetDate)971 static const std::string GetIDITString( const XMP_DateTime & targetDate )
972 {
973 // Date 1 Jan 1800 is treating as base date for handling this issue
974 // 1800 is chosen becuase of consistency in calender after 1752
975
976 XMP_Uns64 numOfDays = 0; // Specifies number of days after 1 jan 1800
977 XMP_Uns32 year = targetDate.year - 1800;
978
979 // 2000 was the first exception when year dividing by 100 was still a leap year
980 bool isLeapYear = ( year % 4 != 0 ) ? false : ( year % 100 != 0 ) ? true : ( ( year % 400 != 200 ) ? false : true );
981
982 // Adding days according to normal year and adjusting days for leap years
983 numOfDays = 365 * year;
984 numOfDays += GetLeapYearsNumber( year );
985
986 // Adding days according to the month
987 numOfDays += GetDays( targetDate.month, isLeapYear );
988
989 // GetLeapYearsNumber() function is also considering provided year for calculating number of leap numbers between provided year
990 // and 1800. This consideration is done by inclusive both end values. So both GetLeapYearsNumber() and GetDays() would have added
991 // extra day for higher end year for leap year.
992 // So, we need to decrease one day from number of days field
993 if ( isLeapYear )
994 --numOfDays;
995
996 // Adding days according to provided month
997 numOfDays += targetDate.day;
998
999 // Weekday starting from Wednesday i.e., Wed will be the first day of the week.
1000 // This day was choosen because 1 Jan 1800 was Wednesday
1001 XMP_Uns8 weekDayNum = numOfDays % 7;
1002 std::string weekDay;
1003 switch ( weekDayNum )
1004 {
1005 case 0:
1006 weekDay = "Tue";
1007 break;
1008 case 1:
1009 weekDay = "Wed";
1010 break;
1011 case 2:
1012 weekDay = "Thu";
1013 break;
1014 case 3:
1015 weekDay = "Fri";
1016 break;
1017 case 4:
1018 weekDay = "Sat";
1019 break;
1020 case 5:
1021 weekDay = "Sun";
1022 break;
1023 case 6:
1024 weekDay = "Mon";
1025 break;
1026 default:
1027 break;
1028 }
1029
1030 // Stream to convert into IDIT format
1031 std::stringstream iditStream;
1032 iditStream << weekDay;
1033 iditStream.put( ' ' );
1034
1035 // IDIT needs 3 character codes for month
1036 const char * monthArray[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
1037 iditStream << monthArray[ targetDate.month - 1 ];
1038 iditStream.put( ' ' );
1039
1040 if ( targetDate.day < 10 )
1041 iditStream.put( '0' );
1042 iditStream << targetDate.day;
1043 iditStream.put( ' ' );
1044
1045 if ( targetDate.hour < 10 )
1046 iditStream.put( '0' );
1047 iditStream << targetDate.hour;
1048 iditStream.put( ':' );
1049
1050 if ( targetDate.minute < 10 )
1051 iditStream.put( '0' );
1052 iditStream << targetDate.minute;
1053 iditStream.put( ':' );
1054
1055 if ( targetDate.second < 10 )
1056 iditStream.put( '0' );
1057 iditStream << targetDate.second;
1058 iditStream.put( ' ' );
1059
1060 // No need to handle casese for year 1 to 999
1061 /*
1062 if ( targetDate.year < 10 )
1063 iditStream << " ";
1064 else if ( targetDate.year < 100 )
1065 iditStream << " ";
1066 else if ( targetDate.year < 1000 )
1067 iditStream << " ";
1068 */
1069 // Year will be in the range of 1800-3999
1070 iditStream << targetDate.year;
1071
1072 // Adding new line charcter for IDIT
1073 iditStream.put( '\n' );
1074
1075 return iditStream.str();
1076
1077 } // GetIDITString
1078
exportXMPtoIDITChunk(RIFF_MetaHandler * handler)1079 static void exportXMPtoIDITChunk( RIFF_MetaHandler* handler )
1080 {
1081 // exif:DateTimeOriginal -> IDIT chunk
1082 ContainerChunk * hdlrChunk = handler->listHdlrChunk;
1083 if ( hdlrChunk == 0 )
1084 XMP_Throw( "Header of AVI file (hdlr chunk) must exists", kXMPErr_BadFileFormat );
1085
1086 XMP_DateTime dateTime;
1087 bool propExists = handler->xmpObj.GetProperty_Date( kXMP_NS_EXIF, "DateTimeOriginal", &dateTime, 0 );
1088 if ( !propExists )
1089 {
1090 if ( handler->iditChunk != 0 )
1091 {
1092 // Exception would have thrown if we don't find hdlr chunk for AVI file
1093 XMP_Assert( hdlrChunk != 0 );
1094 bool isSuccess = hdlrChunk->removeValue( kChunk_IDIT );
1095 if ( !isSuccess )
1096 XMP_Throw( "Removal of IDIT block fails", kXMPErr_InternalFailure );
1097 handler->iditChunk = 0;
1098 hdlrChunk->hasChange = true;
1099 }
1100 // Else no need to do anything
1101 }
1102 else
1103 {
1104 if ( dateTime.year < 1800 || dateTime.year > 3999 )
1105 XMP_Throw( "For IDIT block, XMP currently supports years in between 1800 and 3999 (Both inclusive).", kXMPErr_InternalFailure );
1106
1107 /*
1108 Conversion need to be done from XMP date time to IDIT structure.
1109 XMP_DateTime accepts any value but IDIT needs to have weekday, month-day, month, year and time.
1110 */
1111
1112 // Silently modifying dateTime for invalid dates.
1113 if ( dateTime.month < 1 )
1114 dateTime.month = 1;
1115 if ( dateTime.month > 12 )
1116 dateTime.month = 12;
1117 if ( dateTime.day < 1 )
1118 dateTime.day = 1;
1119 if ( dateTime.day > 31 )
1120 dateTime.day = 31;
1121
1122 const std::string iditString = GetIDITString( dateTime );
1123
1124 // If no IDIT exits then create one
1125 if ( handler->iditChunk == 0 )
1126 handler->iditChunk = new ValueChunk( hdlrChunk, std::string(), kChunk_IDIT );
1127 else if ( strncmp( iditString.c_str(), handler->iditChunk->oldValue.c_str(), IDIT_SIZE ) == 0 ) // Equal
1128 return;
1129
1130 // Setting the IDIT value
1131 handler->iditChunk->hasChange = true;
1132 handler->iditChunk->SetValue( iditString, true );
1133
1134 }
1135
1136 } // exportXMPtoIDITChunk
1137
exportXMPtoListChunk(XMP_Uns32 id,XMP_Uns32 containerType,RIFF_MetaHandler * handler,ContainerChunk ** listChunk,Mapping mapping[])1138 static void exportXMPtoListChunk( XMP_Uns32 id, XMP_Uns32 containerType,
1139 RIFF_MetaHandler* handler, ContainerChunk** listChunk, Mapping mapping[])
1140 {
1141 // note: ContainerChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation)
1142 SXMPMeta* xmp = &handler->xmpObj;
1143 bool listChunkIsNeeded = false; // assume for now
1144
1145 // ! The NUL is optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte.
1146 bool optionalNUL = (handler->parent->format == kXMP_WAVFile);
1147
1148 for ( int p=0; mapping[p].chunkID != 0; ++p ) { // go through all potential property mappings
1149
1150 bool propExists = false;
1151 std::string value, actualLang;
1152
1153 switch ( mapping[p].propType ) {
1154
1155 // get property. if existing, remove from XMP (to avoid redundant storage)
1156 case prop_TIMEVALUE:
1157 propExists = xmp->GetStructField ( mapping[p].ns, mapping[p].prop, kXMP_NS_DM, "timeValue", &value, 0 );
1158 break;
1159
1160 case prop_LOCALIZED_TEXT:
1161 propExists = xmp->GetLocalizedText ( mapping[p].ns, mapping[p].prop, "", "x-default", &actualLang, &value, 0);
1162 if ( actualLang != "x-default" ) propExists = false; // no "x-default" => nothing to reconcile !
1163 break;
1164
1165 case prop_ARRAYITEM:
1166 propExists = xmp->GetArrayItem ( mapping[p].ns, mapping[p].prop, 1, &value, 0 );
1167 break;
1168
1169 case prop_SIMPLE:
1170 propExists = xmp->GetProperty ( mapping[p].ns, mapping[p].prop, &value, 0 );
1171 break;
1172
1173 default:
1174 XMP_Throw ( "internal error", kXMPErr_InternalFailure );
1175
1176 }
1177
1178 if ( ! propExists ) {
1179
1180 if ( *listChunk != 0 ) (*listChunk)->removeValue ( mapping[p].chunkID );
1181
1182 } else {
1183
1184 listChunkIsNeeded = true;
1185 if ( *listChunk == 0 ) *listChunk = new ContainerChunk ( handler->riffChunks[0], id, containerType );
1186
1187 valueMap* cm = &(*listChunk)->childmap;
1188 valueMapIter result = cm->find( mapping[p].chunkID );
1189 ValueChunk* propChunk = 0;
1190
1191 if ( result != cm->end() ) {
1192 propChunk = result->second;
1193 } else {
1194 propChunk = new ValueChunk ( *listChunk, std::string(), mapping[p].chunkID );
1195 }
1196
1197 propChunk->SetValue ( value.c_str(), optionalNUL );
1198
1199 }
1200
1201 } // for each property
1202
1203 if ( (! listChunkIsNeeded) && (*listChunk != 0) && ((*listChunk)->children.size() == 0) ) {
1204 (*listChunk)->parent->replaceChildWithJunk ( *listChunk );
1205 (*listChunk) = 0; // reset direct Chunk pointer
1206 }
1207
1208 }
1209
exportAndRemoveProperties(RIFF_MetaHandler * handler)1210 void exportAndRemoveProperties ( RIFF_MetaHandler* handler )
1211 {
1212 SXMPMeta xmpObj = handler->xmpObj;
1213
1214 exportXMPtoCr8rChunk ( handler, &handler->cr8rChunk );
1215
1216 // 1/5 BWF Bext extension chunk -----------------------------------------------
1217 if ( handler->parent->format == kXMP_WAVFile ) { // applies only to WAV
1218 exportXMPtoBextChunk ( handler, &handler->bextChunk );
1219 }
1220
1221 // 2/5 DISP chunk
1222 if ( handler->parent->format == kXMP_WAVFile ) { // create for WAVE only
1223
1224 std::string actualLang, xmpValue;
1225 bool r = xmpObj.GetLocalizedText ( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, &xmpValue, 0 );
1226
1227 if ( r && ( actualLang == "x-default" ) ) { // prop exists?
1228
1229 // the 'right' DISP is lead by a 32 bit low endian 0x0001
1230 std::string dispValue = std::string( "\x1\0\0\0", 4 );
1231 dispValue.append ( xmpValue );
1232
1233 if ( handler->dispChunk == 0 ) {
1234 handler->dispChunk = new RIFF::ValueChunk ( handler->riffChunks.at(0), std::string(), kChunk_DISP );
1235 }
1236
1237 // ! The NUL is optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte.
1238 handler->dispChunk->SetValue ( dispValue, ValueChunk::kNULisOptional );
1239
1240 } else { // remove Disp Chunk..
1241
1242 if ( handler->dispChunk != 0 ) { // ..if existing
1243 ContainerChunk* mainChunk = handler->riffChunks.at(0);
1244 Chunk* needle = handler->dispChunk;
1245 chunkVectIter iter = mainChunk->getChild ( needle );
1246 if ( iter != mainChunk->children.end() ) {
1247 mainChunk->replaceChildWithJunk ( *iter );
1248 handler->dispChunk = 0;
1249 mainChunk->hasChange = true;
1250 }
1251 }
1252
1253 }
1254
1255 }
1256
1257 // 3/5 LIST:INFO
1258 exportXMPtoListChunk ( kChunk_LIST, kType_INFO, handler, &handler->listInfoChunk, listInfoProps );
1259
1260 // 4/5 LIST:Tdat
1261 exportXMPtoListChunk ( kChunk_LIST, kType_Tdat, handler, &handler->listTdatChunk, listTdatProps );
1262
1263 // 5/5 LIST:HDRL:IDIT
1264 if ( handler->parent->format == kXMP_AVIFile )
1265 exportXMPtoIDITChunk ( handler );
1266
1267 }
1268
1269 } // namespace RIFF
1270