diff options
Diffstat (limited to 'source/XMPFiles/FormatSupport')
38 files changed, 7896 insertions, 4646 deletions
diff --git a/source/XMPFiles/FormatSupport/ASF_Support.cpp b/source/XMPFiles/FormatSupport/ASF_Support.cpp index 3baa7d0..1180f9d 100644 --- a/source/XMPFiles/FormatSupport/ASF_Support.cpp +++ b/source/XMPFiles/FormatSupport/ASF_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -1120,26 +1120,27 @@ void ASF_LegacyManager::ImportLegacy ( SXMPMeta* xmp ) if ( ! broadcastSet ) { ConvertMSDateToISODate ( fields[fieldCreationDate], &utf8 ); - xmp->SetProperty ( kXMP_NS_XMP, "CreateDate", utf8.c_str(), kXMP_DeleteExisting ); + if ( ! utf8.empty() ) xmp->SetProperty ( kXMP_NS_XMP, "CreateDate", utf8.c_str(), kXMP_DeleteExisting ); } FromUTF16 ( (UTF16Unit*)fields[fieldTitle].c_str(), (fields[fieldTitle].size() / 2), &utf8, false ); - xmp->SetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); + if ( ! utf8.empty() ) xmp->SetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); xmp->DeleteProperty ( kXMP_NS_DC, "creator" ); FromUTF16 ( (UTF16Unit*)fields[fieldAuthor].c_str(), (fields[fieldAuthor].size() / 2), &utf8, false ); - SXMPUtils::SeparateArrayItems ( xmp, kXMP_NS_DC, "creator", kXMPUtil_AllowCommas, utf8.c_str() ); + if ( ! utf8.empty() ) SXMPUtils::SeparateArrayItems ( xmp, kXMP_NS_DC, "creator", + (kXMP_PropArrayIsOrdered | kXMPUtil_AllowCommas), utf8.c_str() ); FromUTF16 ( (UTF16Unit*)fields[fieldCopyright].c_str(), (fields[fieldCopyright].size() / 2), &utf8, false ); - xmp->SetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); + if ( ! utf8.empty() ) xmp->SetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); FromUTF16 ( (UTF16Unit*)fields[fieldDescription].c_str(), (fields[fieldDescription].size() / 2), &utf8, false ); - xmp->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); + if ( ! utf8.empty() ) xmp->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); - xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", fields[fieldCopyrightURL].c_str(), kXMP_DeleteExisting ); + if ( ! utf8.empty() ) xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", fields[fieldCopyrightURL].c_str(), kXMP_DeleteExisting ); #if ! Exclude_LicenseURL_Recon - xmp->SetProperty ( kXMP_NS_XMP_Rights, "Certificate", fields[fieldLicenseURL].c_str(), kXMP_DeleteExisting ); + if ( ! fields[fieldLicenseURL].empty() ) xmp->SetProperty ( kXMP_NS_XMP_Rights, "Certificate", fields[fieldLicenseURL].c_str(), kXMP_DeleteExisting ); #endif imported = true; @@ -1393,6 +1394,7 @@ void ASF_LegacyManager::ConvertMSDateToISODate ( std::string& source, std::strin date.second = second; date.nanoSecond = nanoSec; + date.hasTimeZone = true; // ! Needed for ConvertToUTCTime to do anything. SXMPUtils::ConvertToUTCTime ( &date ); // Normalize the date/time. SXMPUtils::ConvertFromDate ( date, dest ); // Convert to an ISO 8601 string. diff --git a/source/XMPFiles/FormatSupport/ASF_Support.hpp b/source/XMPFiles/FormatSupport/ASF_Support.hpp index 5260cb3..e170cb2 100644 --- a/source/XMPFiles/FormatSupport/ASF_Support.hpp +++ b/source/XMPFiles/FormatSupport/ASF_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/ID3_Support.cpp b/source/XMPFiles/FormatSupport/ID3_Support.cpp deleted file mode 100644 index ee8c008..0000000 --- a/source/XMPFiles/FormatSupport/ID3_Support.cpp +++ /dev/null @@ -1,1156 +0,0 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -#include "XMP_Environment.h" // ! This must be the first include. -#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed. - -#include "XMP_Const.h" - -#include "ID3_Support.hpp" - -#include "UnicodeConversions.hpp" -#include "Reconcile_Impl.hpp" - -#include <stdio.h> - -#if XMP_WinBuild - #pragma warning ( disable : 4996 ) // '...' was declared deprecated -#endif - -// For more information about the id3v2 specification -// Please refer to http://www.id3.org/develop.html - -namespace ID3_Support { - - char Genres[128][32]={ - "Blues", // 0 - "Classic Rock", // 1 - "Country", // 2 - "Dance", - "Disco", - "Funk", - "Grunge", - "Hip-Hop", - "Jazz", // 8 - "Metal", - "New Age", // 10 - "Oldies", - "Other", // 12 - "Pop", - "R&B", - "Rap", - "Reggae", // 16 - "Rock", // 17 - "Techno", - "Industrial", - "Alternative", - "Ska", - "Death Metal", - "Pranks", - "Soundtrack", // 24 - "Euro-Techno", - "Ambient", - "Trip-Hop", - "Vocal", - "Jazz+Funk", - "Fusion", - "Trance", - "Classical", // 32 - "Instrumental", - "Acid", - "House", - "Game", - "Sound Clip", - "Gospel", - "Noise", - "AlternRock", - "Bass", - "Soul", //42 - "Punk", - "Space", - "Meditative", - "Instrumental Pop", - "Instrumental Rock", - "Ethnic", - "Gothic", - "Darkwave", - "Techno-Industrial", - "Electronic", - "Pop-Folk", - "Eurodance", - "Dream", - "Southern Rock", - "Comedy", - "Cult", - "Gangsta", - "Top 40", - "Christian Rap", - "Pop/Funk", - "Jungle", - "Native American", - "Cabaret", - "New Wave", // 66 - "Psychadelic", - "Rave", - "Showtunes", - "Trailer", - "Lo-Fi", - "Tribal", - "Acid Punk", - "Acid Jazz", - "Polka", - "Retro", - "Musical", - "Rock & Roll", - "Hard Rock", - "Folk", // 80 - "Folk-Rock", - "National Folk", - "Swing", - "Fast Fusion", - "Bebob", - "Latin", - "Revival", - "Celtic", - "Bluegrass", // 89 - "Avantgarde", - "Gothic Rock", - "Progressive Rock", - "Psychedelic Rock", - "Symphonic Rock", - "Slow Rock", - "Big Band", - "Chorus", - "Easy Listening", - "Acoustic", - "Humour", // 100 - "Speech", - "Chanson", - "Opera", - "Chamber Music", - "Sonata", - "Symphony", - "Booty Bass", - "Primus", - "Porn Groove", - "Satire", - "Slow Jam", - "Club", - "Tango", - "Samba", - "Folklore", - "Ballad", - "Power Ballad", - "Rhythmic Soul", - "Freestyle", - "Duet", - "Punk Rock", - "Drum Solo", - "A capella", - "Euro-House", - "Dance Hall", - "Unknown" // 126 - }; - - // Some types - #ifndef XMP_Int64 - typedef unsigned long long XMP_Int64; - #endif - - static bool FindXMPFrame(LFA_FileRef inFileRef, XMP_Int64 &posXMP, XMP_Int64 &posPAD, unsigned long &dwExtendedTag, unsigned long &dwLen); - static unsigned long SkipExtendedHeader(LFA_FileRef inFileRef, XMP_Uns8 bVersion, XMP_Uns8 flag); - static bool GetFrameInfo(LFA_FileRef inFileRef, XMP_Uns8 bVersion, char *strFrameID, XMP_Uns8 &cflag1, XMP_Uns8 &cflag2, unsigned long &dwSize); - static bool ReadSize(LFA_FileRef inFileRef, XMP_Uns8 bVersion, unsigned long &dwSize); - static unsigned long CalculateSize(XMP_Uns8 bVersion, unsigned long dwSizeIn); - static bool LoadTagHeaderAndUnknownFrames(LFA_FileRef inFileRef, char *strBuffer, size_t strBufferLen, bool fRecon, unsigned long &posPad); - - #define GetFilePosition(file) LFA_Seek ( file, 0, SEEK_CUR ) - - const unsigned long k_dwTagHeaderSize = 10; - const unsigned long k_dwFrameHeaderSize = 10; - const unsigned long k_dwXMPLabelSize = 4; // 4 (size of "XMP\0") - -// ID3v2 flags %abcd0000 -// Where: -// a - Unsynchronisation -// b - Extended header -// c - Experimental indicator -// d - Footer present - const unsigned char flagUnsync = 0x80; // (MSb) - const unsigned char flagExt = 0x40; - const unsigned char flagExp = 0x20; - const unsigned char flagFooter = 0x10; - - -#ifndef Trace_ID3_Support - #define Trace_ID3_Support 0 -#endif - -// ================================================================================================= - -#if XMP_WinBuild - - #define stricmp _stricmp - -#else - - static int stricmp ( const char * left, const char * right ) // Case insensitive ASCII compare. - { - char chL = *left; // ! Allow for 0 passes in the loop (one string is empty). - char chR = *right; // ! Return -1 for stricmp ( "a", "Z" ). - - for ( ; (*left != 0) && (*right != 0); ++left, ++right ) { - chL = *left; - chR = *right; - if ( chL == chR ) continue; - if ( ('A' <= chL) && (chL <= 'Z') ) chL |= 0x20; - if ( ('A' <= chR) && (chR <= 'Z') ) chR |= 0x20; - if ( chL != chR ) break; - } - - if ( chL == chR ) return 0; - if ( chL < chR ) return -1; - return 1; - - } - -#endif - -// ================================================================================================= - -// *** Load Scenario: -// - Check for id3v2 tag -// -// - Parse through the frame for "PRIV" + UTF8 Encoding + "XMP" -// -// - If found, load it. -bool GetMetaData ( LFA_FileRef inFileRef, char* buffer, unsigned long* pBufferSize, ::XMP_Int64* fileOffset ) -{ - - if ( pBufferSize == 0 ) return false; - - unsigned long dwSizeIn = *pBufferSize; - *pBufferSize = 0; - XMP_Int64 posXMP = 0ULL, posPAD = 0ULL; - unsigned long dwLen = 0, dwExtendedTag = 0; - if ( ! FindXMPFrame ( inFileRef, posXMP, posPAD, dwExtendedTag, dwLen ) ) return false; - - // Found the XMP frame! Get the rest of frame into the buffer - unsigned long dwXMPBufferLen = dwLen - k_dwXMPLabelSize; - *pBufferSize = dwXMPBufferLen; - - if ( fileOffset != 0 ) *fileOffset = posXMP + k_dwXMPLabelSize; - - if ( buffer != 0 ) { - // Seek 4 bytes ahead to get the XMP data. - LFA_Seek ( inFileRef, posXMP+k_dwXMPLabelSize, SEEK_SET ); - if ( dwXMPBufferLen > dwSizeIn ) dwXMPBufferLen = dwSizeIn; - LFA_Read ( inFileRef, buffer, dwXMPBufferLen ); // Get the XMP frame - } - - return true; - -} - -// ================================================================================================= - -bool FindFrame ( LFA_FileRef inFileRef, char* strFrame, XMP_Int64 & posFrame, unsigned long & dwLen ) -{ - // Taking into account that the first Tag is the ID3 tag - bool fReturn = false; - LFA_Seek ( inFileRef, 0ULL, SEEK_SET ); - - #if Trace_ID3_Support - fprintf ( stderr, "ID3_Support::FindFrame : Looking for %s\n", strFrame ); - #endif - - // Read the tag name - char szID[4] = {"xxx"}; - long bytesRead = LFA_Read ( inFileRef, szID, 3 ); - if ( bytesRead == 0 ) return fReturn; - - // Check for "ID3" - if ( strcmp ( szID, "ID3" ) != 0 ) return fReturn; - - // Read the version, flag and size - XMP_Uns8 v1 = 0, v2 = 0, flags = 0; - unsigned long dwTagSize = 0; - - if ( ! GetTagInfo ( inFileRef, v1, v2, flags, dwTagSize ) ) return fReturn; - if ( dwTagSize == 0 ) return fReturn; - if ( v1 > 4 ) return fReturn; // We don't support anything newer than id3v2 4.0 - - // If there's an extended header, ignore it - XMP_Int32 dwExtendedTag = SkipExtendedHeader(inFileRef, v1, flags); - dwTagSize -= dwExtendedTag; - - // Enumerate through the frames - XMP_Int64 posCur = 0ULL; - posCur = GetFilePosition ( inFileRef ); - XMP_Int64 posEnd = posCur + dwTagSize; - - while ( posCur < posEnd ) { - - if ( (posEnd - posCur) < k_dwTagHeaderSize ) break; // Not enough room for a header, must be padding. - - char szFrameID[5] = {"xxxx"}; - unsigned long dwFrameSize = 0; - XMP_Uns8 cflag1 = 0, cflag2 = 0; - - // Get the next frame - if ( ! GetFrameInfo ( inFileRef, v1, szFrameID, cflag1, cflag2, dwFrameSize ) ) break; - - // Are we in a padding frame? - if ( dwFrameSize == 0 ) break; - - // Is it the Frame we're looking for? - if ( strcmp ( szFrameID, strFrame ) == 0 ) { - posFrame = GetFilePosition ( inFileRef ); - dwLen = dwFrameSize; - fReturn = true; - break; - } else { - // Jump to the next frame - LFA_Seek ( inFileRef, dwFrameSize, SEEK_CUR ); - } - - posCur = GetFilePosition ( inFileRef ); - } - - #if Trace_ID3_Support - if ( fReturn ) { - fprintf ( stderr, " Found %s, offset %d, length %d\n", strFrame, (long)posFrame, dwLen ); - } - #endif - - return fReturn; -} - -// ================================================================================================= - -bool GetFrameData ( LFA_FileRef inFileRef, char* strFrame, char* buffer, unsigned long &dwBufferSize ) -{ - char strData[TAG_MAX_SIZE+4]; // Plus 4 for two worst case UTF-16 nul terminators. - size_t sdPos = 0; // Offset within strData to the value. - memset ( &strData[0], 0, sizeof(strData) ); - - if ( (buffer == 0) || (dwBufferSize > TAG_MAX_SIZE) ) return false; - - const unsigned long dwSizeIn = dwBufferSize; - XMP_Int64 posFrame = 0ULL; - unsigned long dwLen = 0; - XMP_Uns8 bEncoding = 0; - - // Find the frame - if ( ! FindFrame ( inFileRef, strFrame, posFrame, dwLen ) ) return false; - #if Trace_ID3_Support - fprintf ( stderr, " Getting frame data\n" ); - #endif - - if ( dwLen <= 0 ) { - - dwBufferSize = 1; - buffer[0] = 0; - - } else { - - // Get the value for a typical text frame, having an encoding byte followed by the value. - // COMM frames are special, see below. First get encoding, and the frame data into strData. - - dwBufferSize = dwLen - 1; // Don't count the encoding byte. - - // Seek to the frame - LFA_Seek ( inFileRef, posFrame, SEEK_SET ); - - // Read the Encoding - LFA_Read ( inFileRef, &bEncoding, 1 ); - if ( bEncoding > 3 ) return false; - - // Get the frame - if ( dwBufferSize > dwSizeIn ) dwBufferSize = dwSizeIn; - - if ( dwBufferSize >= TAG_MAX_SIZE ) return false; // No room for data. - LFA_Read ( inFileRef, &strData[0], dwBufferSize ); - - if ( strcmp ( strFrame, "COMM" ) == 0 ) { - - // A COMM frame has a 3 byte language tag, then an encoded and nul terminated description - // string, then the encoded value string. Set dwOffset to the offset to the value. - - unsigned long dwOffset = 3; // Skip the 3 byte language code. - - if ( (bEncoding == 0) || (bEncoding == 3) ) { - dwOffset += (unsigned long)(strlen ( &strData[3] ) + 1); // Skip the descriptor and nul. - } else { - UTF16Unit* u16Ptr = (UTF16Unit*) (&strData[3]); - for ( ; *u16Ptr != 0; ++u16Ptr ) dwOffset += 2; // Skip the descriptor. - dwOffset += 2; // Skip the nul also. - } - - if ( dwOffset >= dwBufferSize ) return false; - dwBufferSize -= dwOffset; - - sdPos = dwOffset; - - #if Trace_ID3_Support - fprintf ( stderr, " COMM frame, dwOffset %d\n", dwOffset ); - #endif - - } - - // Encoding translation - switch ( bEncoding ) { - - case 1: // UTF-16 with a BOM. (Might be missing for empty string.) - case 2: // Big endian UTF-16 with no BOM. - { - bool bigEndian = true; // Assume big endian if no BOM. - UTF16Unit* u16Ptr = (UTF16Unit*) &strData[sdPos]; - - if ( GetUns16BE ( u16Ptr ) == 0xFEFF ) { - ++u16Ptr; // Don't translate the BOM. - } else if ( GetUns16BE ( u16Ptr ) == 0xFFFE ) { - bigEndian = false; - ++u16Ptr; // Don't translate the BOM. - } - - size_t u16Len = 0; // Count the UTF-16 units, not bytes. - for ( UTF16Unit* temp = u16Ptr; *temp != 0; ++temp ) ++u16Len; - - std::string utf8Str; - FromUTF16 ( u16Ptr, u16Len, &utf8Str, bigEndian ); - if ( utf8Str.size() >= (sizeof(strData) - sdPos) ) return false; - strcpy ( &strData[sdPos], utf8Str.c_str() ); // AUDIT: Protected by the above check. - } - break; - - case 0: // ISO Latin-1 (8859-1). - { - std::string utf8Str; - char* localPtr = &strData[sdPos]; - size_t localLen = dwBufferSize; - - ReconcileUtils::Latin1ToUTF8 ( localPtr, localLen, &utf8Str ); - if ( utf8Str.size() >= (sizeof(strData) - sdPos) ) return false; - strcpy ( &strData[sdPos], utf8Str.c_str() ); // AUDIT: Protected by the above check. - } - break; - - case 3: // UTF-8 - default: - // Handled appropriately - break; - - } - - char * strTemp = &strData[sdPos]; - - if ( strcmp ( strFrame, "TCON" ) == 0 ) { - - char str[TAG_MAX_SIZE]; - str[0] = 0; - if ( strlen ( &strData[sdPos] ) >= sizeof(str) ) return false; - strcpy ( str, &strData[sdPos] ); // AUDIT: Protected by the above check. - - #if Trace_ID3_Support - fprintf ( stderr, " TCON frame, first char '%c'\n", str[0] ); - #endif - - // Genre: let's get the "string" value - if ( str[0] == '(' ) { - int iGenre = atoi(str+1); - if ( (iGenre > 0) && (iGenre < 127) ) { - strTemp = Genres[iGenre]; - } else { - strTemp = Genres[12]; - } - } else { - // Text, let's "try" to find it anyway - int i = 0; - for ( i=0; i < 127; ++i ) { - if ( stricmp ( str, Genres[i] ) == 0 ) { - strTemp = Genres[i]; // Found, let's use the one in the list - break; - } - } - if ( i == 127 ) strTemp = Genres[12]; // Not found - } - - } - - #if Trace_ID3_Support - fprintf ( stderr, " Have data, length %d, \"%s\"\n", strlen(strTemp), strTemp ); - #endif - - if ( strlen(strTemp) >= dwSizeIn ) return false; - strcpy ( buffer, strTemp ); // AUDIT: Protected by the above check. - - } - - return true; - -} - -// ================================================================================================= - -bool AddXMPTagToID3Buffer ( char * strCur, unsigned long * pdwCurOffset, unsigned long dwMaxSize, XMP_Uns8 bVersion, - char *strFrameName, const char * strXMPTagTemp, unsigned long dwXMPLengthTemp ) -{ - char strGenre[64]; - const char * strXMPTag = strXMPTagTemp; - XMP_Int32 dwCurOffset = *pdwCurOffset; - XMP_Uns8 bEncoding = 0; - long dwXMPLength = dwXMPLengthTemp; - - if ( dwXMPLength == 0 ) return false; - - if ( strcmp ( strFrameName, "TCON" ) == 0 ) { - - // Genre: we need to get the number back... - int iFound = 12; - - for ( int i=0; i < 127; ++i ) { - if ( stricmp ( strXMPTag, Genres[i] ) == 0 ) { - iFound = i; // Found - break; - } - } - - snprintf ( strGenre, sizeof(strGenre), "(%d)", iFound ); // AUDIT: Using sizeof(strGenre) is safe. - strXMPTag = strGenre; - dwXMPLength = (long)strlen(strXMPTag); - - } - - // Stick with the ID3v2.3 encoding choices, they are a proper subset of ID3v2.4. - // 0 - ISO Latin-1 - // 1 - UTF-16 with BOM - // For 3rd party reliability we always write UTF-16 as little endian. For example, Windows - // Media Player fails to honor the BOM, it assumes little endian. - - std::string tempLatin1, tempUTF8; - ReconcileUtils::UTF8ToLatin1 ( strXMPTag, dwXMPLength, &tempLatin1 ); - ReconcileUtils::Latin1ToUTF8 ( tempLatin1.data(), tempLatin1.size(), &tempUTF8 ); - if ( ((size_t)dwXMPLength != tempUTF8.size()) || (memcmp ( strXMPTag, tempUTF8.data(), dwXMPLength ) != 0) ) { - bEncoding = 1; // Will convert to UTF-16 later. - } else { - strXMPTag = tempLatin1.c_str(); // Use the Latin-1 encoding for output. - dwXMPLength = (long)tempLatin1.size(); - } - - std::string strUTF16; - if ( bEncoding == 1 ) { - ToUTF16 ( (UTF8Unit*)strXMPTag, dwXMPLength, &strUTF16, false /* little endian */ ); - dwXMPLength = (long)strUTF16.size() + 2; // ! Include the (to be inserted) BOM in the count. - } - - // Frame Structure - // Frame ID $xx xx xx xx (four characters) - // Size 4 * %0xxxxxxx <<--- IMPORTANT NOTE: This is true only in v4.0 (v3.0 uses a UInt32) - // Flags $xx xx - // Encoding $xx (Not included in the frame header) - // Special case: "COMM" which we have to include "XXX\0" in front of it (also not included in the frame header) - - unsigned long dwFrameSize = dwXMPLength + 1; // 1 == Encoding; - - bool fCOMM = (strcmp ( strFrameName, "COMM" ) == 0); - if ( fCOMM ) { - dwFrameSize += 3; // The "XXX" language part. - dwFrameSize += ((bEncoding == 0) ? 1 : 4 ); // The empty descriptor string. - } - - if ( (dwCurOffset + k_dwFrameHeaderSize + dwFrameSize) > dwMaxSize ) return false; - - unsigned long dwCalculated = CalculateSize ( bVersion, dwFrameSize ); - - // FrameID - if ( (dwMaxSize - dwCurOffset) < 4 ) return false; - memcpy ( strCur+dwCurOffset, strFrameName, 4 ); // AUDIT: Protected by the above check. - dwCurOffset += 4; - - // Frame Size - written as big endian - strCur[dwCurOffset] = (char)(dwCalculated >> 24); - ++dwCurOffset; - strCur[dwCurOffset] = (char)((dwCalculated >> 16) & 0xFF); - ++dwCurOffset; - strCur[dwCurOffset] = (char)((dwCalculated >> 8) & 0xFF); - ++dwCurOffset; - strCur[dwCurOffset] = (char)(dwCalculated & 0xFF); - ++dwCurOffset; - - // Flags - strCur[dwCurOffset] = 0; - ++dwCurOffset; - strCur[dwCurOffset] = 0; - ++dwCurOffset; - - // Encoding - strCur[dwCurOffset] = bEncoding; - ++dwCurOffset; - - // COMM extras: XXX language and empty encoded descriptor string. - if ( fCOMM ) { - if ( (dwMaxSize - dwCurOffset) < 3 ) return false; - memcpy ( strCur+dwCurOffset, "XXX", 3 ); // AUDIT: Protected by the above check. - dwCurOffset += 3; - if ( bEncoding == 0 ) { - strCur[dwCurOffset] = 0; - ++dwCurOffset; - } else { - strCur[dwCurOffset] = 0xFF; - ++dwCurOffset; - strCur[dwCurOffset] = 0xFE; - ++dwCurOffset; - strCur[dwCurOffset] = 0; - ++dwCurOffset; - strCur[dwCurOffset] = 0; - ++dwCurOffset; - } - } - - if ( bEncoding == 1 ) { - // Add the BOM "FFFE" - strCur[dwCurOffset] = 0xFF; - ++dwCurOffset; - strCur[dwCurOffset] = 0xFE; - ++dwCurOffset; - dwXMPLength -= 2; // The BOM was included above. - // Copy the Unicode data - if ( (long)(dwMaxSize - dwCurOffset) < dwXMPLength ) return false; - memcpy ( strCur+dwCurOffset, strUTF16.data(), dwXMPLength ); // AUDIT: Protected by the above check. - dwCurOffset += dwXMPLength; - } else { - // Copy the data - if ( (long)(dwMaxSize - dwCurOffset) < dwXMPLength ) return false; - memcpy ( strCur+dwCurOffset, strXMPTag, dwXMPLength ); // AUDIT: Protected by the above check. - dwCurOffset += dwXMPLength; - } - - *pdwCurOffset = dwCurOffset; - - return true; - -} - -// ================================================================================================= - -static void OffsetAudioData ( LFA_FileRef inFileRef, XMP_Int64 audioOffset, XMP_Int64 oldAudioBase ) -{ - enum { kBuffSize = 64*1024 }; - XMP_Uns8 buffer [kBuffSize]; - - const XMP_Int64 posEOF = LFA_Measure ( inFileRef ); - XMP_Int64 posCurrentCopy; // ! Must be a signed type! - - posCurrentCopy = posEOF; - while ( posCurrentCopy >= (oldAudioBase + kBuffSize) ) { - posCurrentCopy -= kBuffSize; // *** Xcode 2.3 seemed to generate bad code using a for loop. - LFA_Seek ( inFileRef, posCurrentCopy, SEEK_SET ); - LFA_Read ( inFileRef, buffer, kBuffSize ); - LFA_Seek ( inFileRef, (posCurrentCopy + audioOffset), SEEK_SET ); - LFA_Write ( inFileRef, buffer, kBuffSize ); - } - - if ( posCurrentCopy != oldAudioBase ) { - XMP_Uns32 remainder = (XMP_Uns32) (posCurrentCopy - oldAudioBase); - XMP_Assert ( remainder < kBuffSize ); - LFA_Seek ( inFileRef, oldAudioBase, SEEK_SET ); - LFA_Read ( inFileRef, buffer, remainder ); - LFA_Seek ( inFileRef, (oldAudioBase + audioOffset), SEEK_SET ); - LFA_Write ( inFileRef, buffer, remainder ); - } - -} - -// ================================================================================================= - -bool SetMetaData ( LFA_FileRef inFileRef, char* strXMPPacket, unsigned long dwXMPPacketSize, - char* strLegacyFrames, unsigned long dwFullLegacySize, bool fRecon ) -{ - // The ID3 section layout: - // ID3 header, 10 bytes - // Unrecognized ID3 frames - // Legacy ID3 metadata frames (artist, album, genre, etc.) - // XMP frame, content is "XMP\0" plus the packet - // padding - - // ID3 Buffer vars - const unsigned long kiMaxBuffer = 100*1000; - char szID3Buffer [kiMaxBuffer]; // Must be enough for the ID3 header, unknown ID3 frames, and legacy ID3 metadata. - unsigned long id3BufferLen = 0; // The amount of stuff currently in the buffer. - - unsigned long dwOldID3ContentSize = 0; // The size of the existing ID3 content (not counting the header). - unsigned long dwNewID3ContentSize = 0; // The size of the updated ID3 content (not counting the header). - - unsigned long newPadSize = 0; - - XMP_Uns8 bMajorVersion = 3; - - bool fFoundID3 = FindID3Tag ( inFileRef, dwOldID3ContentSize, bMajorVersion ); - if ( (bMajorVersion > 4) || (bMajorVersion < 3) ) return false; // Not supported - - // Now that we know the version of the ID3 tag, let's format the size of the XMP frame. - - #define k_XMPPrefixSize (k_dwFrameHeaderSize + k_dwXMPLabelSize) - char szXMPPrefix [k_XMPPrefixSize] = { 'P', 'R', 'I', 'V', 0, 0, 0, 0, 0, 0, 'X', 'M', 'P', 0 }; - unsigned long dwXMPContentSize = k_dwXMPLabelSize + dwXMPPacketSize; - unsigned long dwFullXMPFrameSize = k_dwFrameHeaderSize + dwXMPContentSize; - - unsigned long dwFormattedTemp = CalculateSize ( bMajorVersion, dwXMPContentSize ); - - szXMPPrefix[4] = (char)(dwFormattedTemp >> 24); - szXMPPrefix[5] = (char)((dwFormattedTemp >> 16) & 0xFF); - szXMPPrefix[6] = (char)((dwFormattedTemp >> 8) & 0xFF); - szXMPPrefix[7] = (char)(dwFormattedTemp & 0xFF); - - // Set up the ID3 buffer with the ID3 header and any existing unrecognized ID3 frames. - - if ( ! fFoundID3 ) { - - // Case 1 - No id3v2 tag: Create the tag with the XMP frame. - // Create the tag - // ID3v2/file identifier "ID3" - // ID3v2 version $03 00 - // ID3v2 flags %abcd0000 - // ID3v2 size 4 * %0xxxxxxx - - XMP_Assert ( dwOldID3ContentSize == 0 ); - - char szID3Header [k_dwTagHeaderSize] = { 'I', 'D', '3', 3, 0, 0, 0, 0, 0, 0 }; - - // Copy the ID3 header - if ( sizeof(szID3Buffer) < k_dwTagHeaderSize ) return false; - memcpy ( szID3Buffer, szID3Header, k_dwTagHeaderSize ); // AUDIT: Protected by the above check. - id3BufferLen = k_dwTagHeaderSize; - - newPadSize = 100; - dwNewID3ContentSize = dwFullLegacySize + dwFullXMPFrameSize + newPadSize; - - } else { - - // Case 2 - id3v2 tag is present - // 1. Copy all the unknown tags - // 2. Make the rest padding (to be used right there). - - if ( (k_dwFrameHeaderSize + dwOldID3ContentSize) > kiMaxBuffer ) { - // The ID3Buffer is not big enough to fit the id3v2 tag... let's bail... - return false; - } - - LoadTagHeaderAndUnknownFrames ( inFileRef, szID3Buffer, sizeof(szID3Buffer), fRecon, id3BufferLen ); - - unsigned long spareLen = (k_dwFrameHeaderSize + dwOldID3ContentSize) - id3BufferLen; - - if ( spareLen >= (dwFullLegacySize + dwFullXMPFrameSize) ) { - - // The exising ID3 header can hold the update. - dwNewID3ContentSize = dwOldID3ContentSize; - newPadSize = spareLen - (dwFullLegacySize + dwFullXMPFrameSize); - - } else { - - // The existing ID3 header is too small, it will have to grow. - newPadSize = 100; - dwNewID3ContentSize = (id3BufferLen - k_dwTagHeaderSize) + - dwFullLegacySize + dwFullXMPFrameSize + newPadSize; - - } - - } - - // Move the audio data if the ID3 frame is new or has to grow. - - XMP_Assert ( dwNewID3ContentSize >= dwOldID3ContentSize ); - - if ( dwNewID3ContentSize > dwOldID3ContentSize ) { - unsigned long audioOffset = dwNewID3ContentSize - dwOldID3ContentSize; - unsigned long oldAudioBase = k_dwTagHeaderSize + dwOldID3ContentSize; - if ( ! fFoundID3 ) { - // We're injecting an entire ID3 section. - audioOffset = k_dwTagHeaderSize + dwNewID3ContentSize; - oldAudioBase = 0; - } - OffsetAudioData ( inFileRef, audioOffset, oldAudioBase ); - } - - // Set the new size for the ID3 content. This always uses the 4x7 format. - - dwFormattedTemp = CalculateSize ( 4, dwNewID3ContentSize ); - szID3Buffer[6] = (char)(dwFormattedTemp >> 24); - szID3Buffer[7] = (char)((dwFormattedTemp >> 16) & 0xFF); - szID3Buffer[8] = (char)((dwFormattedTemp >> 8) & 0xFF); - szID3Buffer[9] = (char)(dwFormattedTemp & 0xFF); - - // Write the partial ID3 buffer (ID3 header plus unknown tags) - LFA_Seek ( inFileRef, 0, SEEK_SET ); - LFA_Write ( inFileRef, szID3Buffer, id3BufferLen ); - - // Append the new legacy metadata frames - if ( dwFullLegacySize > 0 ) { - LFA_Write ( inFileRef, strLegacyFrames, dwFullLegacySize ); - } - - // Append the XMP frame prefix - LFA_Write ( inFileRef, szXMPPrefix, k_XMPPrefixSize ); - - // Append the XMP packet - LFA_Write ( inFileRef, strXMPPacket, dwXMPPacketSize ); - - // Append the padding. - if ( newPadSize > 0 ) { - std::string szPad; - szPad.reserve ( newPadSize ); - szPad.assign ( newPadSize, '\0' ); - LFA_Write ( inFileRef, const_cast<char *>(szPad.data()), newPadSize ); - } - - LFA_Flush ( inFileRef ); - - return true; - -} - -// ================================================================================================= - -bool LoadTagHeaderAndUnknownFrames ( LFA_FileRef inFileRef, char * strBuffer, size_t strBufferLen, bool fRecon, unsigned long & posPad ) -{ - - LFA_Seek ( inFileRef, 3ULL, SEEK_SET ); // Point after the "ID3" - - // Get the tag info - unsigned long dwOffset = 0; - XMP_Uns8 v1 = 0, v2 = 0, flags = 0; - unsigned long dwTagSize = 0; - GetTagInfo ( inFileRef, v1, v2, flags, dwTagSize ); - - unsigned long dwExtendedTag = SkipExtendedHeader ( inFileRef, v1, flags ); - - LFA_Seek ( inFileRef, 0ULL, SEEK_SET ); - XMP_Assert ( strBufferLen >= k_dwTagHeaderSize ); - LFA_Read ( inFileRef, strBuffer, k_dwTagHeaderSize ); - dwOffset += k_dwTagHeaderSize; - - // Completely ignore the Extended Header - if ( ((flags & flagExt) == flagExt) && (dwExtendedTag > 0) ) { - strBuffer[5] = strBuffer[5] & 0xBF; // If the flag has been set, let's reset it - LFA_Seek ( inFileRef, dwExtendedTag, SEEK_CUR ); // And let's seek up to after the extended header - } - - // Enumerate through the frames - XMP_Int64 posCur = 0ULL; - posCur = GetFilePosition ( inFileRef ); - XMP_Int64 posEnd = posCur + dwTagSize; - - while ( posCur < posEnd ) { - - XMP_Assert ( k_dwTagHeaderSize == 10 ); - if ( (posEnd - posCur) < k_dwTagHeaderSize ) break; // Not enough room for a header, must be padding. - - char szFrameID[5] = {"xxxx"}; - unsigned long dwFrameSize = 0; - XMP_Uns8 cflag1 = 0, cflag2 = 0; - - // Get the next frame - if ( ! GetFrameInfo ( inFileRef, v1, szFrameID, cflag1, cflag2, dwFrameSize ) ) break; - - // Are we in a padding frame? - if ( dwFrameSize == 0 ) break; // We just hit a padding frame - - bool fIgnore = false; - bool knownID = (strcmp ( szFrameID, "TIT2" ) == 0) || - (strcmp ( szFrameID, "TYER" ) == 0) || - (strcmp ( szFrameID, "TDRV" ) == 0) || - (strcmp ( szFrameID, "TPE1" ) == 0) || - (strcmp ( szFrameID, "TALB" ) == 0) || - (strcmp ( szFrameID, "TCON" ) == 0) || - (strcmp ( szFrameID, "COMM" ) == 0) || - (strcmp ( szFrameID, "TRCK" ) == 0); - - // If a known frame, just ignore - // Note: If recon is turned off, let's consider all known frames as unknown - if ( knownID && fRecon ) { - - fIgnore = true; - - } else if ( strcmp ( szFrameID, "PRIV" ) == 0 ) { - - // Read the "PRIV" frame - // <Header for "PRIV"> - // Short content descrip. <text string according to encoding> $00 (00) - // The actual data <full text string according to encoding> - - // Get the PRIV descriptor - char szXMPTag[4] = {"xxx"}; - if ( LFA_Read ( inFileRef, &szXMPTag, k_dwXMPLabelSize ) != 0 ) { - // Is it a XMP "PRIV" - if ( (szXMPTag[3] == 0) && (strcmp ( szXMPTag, "XMP" ) == 0) ) fIgnore = true; - LFA_Seek ( inFileRef, -(long)k_dwXMPLabelSize, SEEK_CUR ); - } - - } - - if ( fIgnore ) { - LFA_Seek ( inFileRef, dwFrameSize, SEEK_CUR ); - } else { - // Unknown frame, let's copy it - LFA_Seek ( inFileRef, -(long)k_dwFrameHeaderSize, SEEK_CUR ); - if ( (dwOffset > strBufferLen) || ((dwFrameSize + k_dwFrameHeaderSize) > (strBufferLen - dwOffset)) ) { - XMP_Throw ( "Avoiding I/O buffer overflow", kXMPErr_InternalFailure ); - } - LFA_Read ( inFileRef, (strBuffer + dwOffset), (dwFrameSize + k_dwFrameHeaderSize) ); - dwOffset += dwFrameSize+k_dwFrameHeaderSize; - } - - posCur = GetFilePosition ( inFileRef ); - - } - - posPad = dwOffset; - - return true; - -} - -// ================================================================================================= - -bool FindID3Tag ( LFA_FileRef inFileRef, unsigned long & dwLen, XMP_Uns8 & bMajorVer ) -{ - // id3v2 tag: - // ID3v2/file identifier "ID3" - // ID3v2 version $04 00 - // ID3v2 flags %abcd0000 - // ID3v2 size 4 * %0xxxxxxx - - // Taking into account that the first Tag is the ID3 tag - LFA_Seek ( inFileRef, 0ULL, SEEK_SET ); - - // Read the tag name - char szID[4] = {"xxx"}; - long bytesRead = LFA_Read ( inFileRef, szID, 3 ); - if ( bytesRead == 0 ) return false; - - // Check for "ID3" - if ( strcmp ( szID, "ID3" ) != 0 ) return false; - - // Read the version, flag and size - XMP_Uns8 v2 = 0, flags = 0; - if ( ! GetTagInfo ( inFileRef, bMajorVer, v2, flags, dwLen ) ) return false; - - return true; - -} - -// ================================================================================================= - -bool GetTagInfo ( LFA_FileRef inFileRef, XMP_Uns8 & v1, XMP_Uns8 & v2, XMP_Uns8 & flags, unsigned long & dwTagSize ) -{ - - if ((LFA_Read(inFileRef, &v1, 1)) == 0) return false; - if ((LFA_Read(inFileRef, &v2, 1)) == 0) return false; - if ((LFA_Read(inFileRef, &flags, 1)) == 0) return false; - if (!ReadSize(inFileRef, 4, dwTagSize)) return false; // Tag size is always using the size reading method. - - return true; - -} - -// ================================================================================================= - -static bool FindXMPFrame ( LFA_FileRef inFileRef, XMP_Int64 & posXMP, XMP_Int64 & posPAD, unsigned long & dwExtendedTag, unsigned long & dwLen ) -{ - // Taking into account that the first Tag is the ID3 tag - bool fReturn = false; - dwExtendedTag = 0; - posPAD = 0; - - LFA_Seek ( inFileRef, 0ULL, SEEK_SET ); - - // Read the tag name - char szID[4] = {"xxx"}; - long bytesRead = LFA_Read ( inFileRef, szID, 3 ); - if ( bytesRead == 0 ) return fReturn; - - // Check for "ID3" - if ( strcmp ( szID, "ID3") != 0 ) return fReturn; - - // Read the version, flag and size - XMP_Uns8 v1 = 0, v2 = 0, flags = 0; - unsigned long dwTagSize = 0; - if ( ! GetTagInfo ( inFileRef, v1, v2, flags, dwTagSize ) ) return fReturn; - if ( dwTagSize == 0 ) return fReturn; - if ( v1 > 4 ) return fReturn; // We don't support anything newer than id3v2 4.0 - - // If there's an extended header, ignore it - dwExtendedTag = SkipExtendedHeader(inFileRef, v1, flags); - dwTagSize -= dwExtendedTag; - - // Enumerate through the frames - XMP_Int64 posCur = 0ULL; - posCur = GetFilePosition ( inFileRef ); - XMP_Int64 posEnd = posCur + dwTagSize; - - while ( posCur < posEnd ) { - - if ( (posEnd - posCur) < k_dwTagHeaderSize ) break; // Not enough room for a header, must be padding. - - char szFrameID[5] = {"xxxx"}; - unsigned long dwFrameSize = 0; - XMP_Uns8 cflag1 = 0, cflag2 = 0; - - // Get the next frame - if ( ! GetFrameInfo ( inFileRef, v1, szFrameID, cflag1, cflag2, dwFrameSize ) ) { - // Set the file pointer to the XMP or the start - LFA_Seek ( inFileRef, fReturn ? posXMP : 0ULL, SEEK_SET ); - break; - } - - // Are we in a padding frame? - if ( dwFrameSize == 0 ) { - - // We just hit a padding frame - LFA_Seek ( inFileRef, -(long)k_dwFrameHeaderSize, SEEK_CUR ); - posPAD = GetFilePosition ( inFileRef ); - - // Set the file pointer to the XMP or the start - LFA_Seek ( inFileRef, fReturn ? posXMP : 0ULL, SEEK_SET ); - break; - - } - - // Is it a "PRIV"? - if ( strcmp(szFrameID, "PRIV") != 0 ) { - - // Jump to the next frame - LFA_Seek ( inFileRef, dwFrameSize, SEEK_CUR ); - - } else { - - // Read the "PRIV" frame - // <Header for "PRIV"> - // Short content descrip. <text string according to encoding> $00 (00) - // The actual data <full text string according to encoding> - - unsigned long dwBytesRead = 0; - - // Get the PRIV descriptor - char szXMPTag[4] = {"xxx"}; - if (LFA_Read(inFileRef, &szXMPTag, k_dwXMPLabelSize) == 0) return fReturn; - dwBytesRead += k_dwXMPLabelSize; - - // Is it a XMP "PRIV" - if ( (szXMPTag[3] == 0) && (strcmp ( szXMPTag, "XMP" ) == 0) ) { - dwLen = dwFrameSize; - LFA_Seek ( inFileRef, -(long)k_dwXMPLabelSize, SEEK_CUR ); - posXMP = GetFilePosition ( inFileRef ); - fReturn = true; - dwBytesRead -= k_dwXMPLabelSize; - } - - // Didn't find it, let skip the rest of the frame and continue - LFA_Seek ( inFileRef, dwFrameSize - dwBytesRead, SEEK_CUR ); - - } - - posCur = GetFilePosition ( inFileRef ); - - } - - return fReturn; - -} - -// ================================================================================================= - -// Returns the size of the extended header -static unsigned long SkipExtendedHeader ( LFA_FileRef inFileRef, XMP_Uns8 bVersion, XMP_Uns8 flags ) -{ - if ( flags & flagExt ) { - - unsigned long dwExtSize = 0; // <-- This will include the size (full extended header size) - - if ( ReadSize ( inFileRef, bVersion, dwExtSize ) ) { - if ( bVersion < 4 ) dwExtSize += 4; // v3 doesn't include the size, while v4 does. - LFA_Seek ( inFileRef, (size_t)(dwExtSize - 4), SEEK_CUR ); - } - - return dwExtSize; - - } - - return 0; - -} - -// ================================================================================================= - -static bool GetFrameInfo ( LFA_FileRef inFileRef, XMP_Uns8 bVersion, char * strFrameID, XMP_Uns8 & cflag1, XMP_Uns8 & cflag2, unsigned long & dwSize) -{ - // Frame ID $xx xx xx xx (four characters) - // Size 4 * %0xxxxxxx <<--- IMPORTANT NOTE: This is true only in v4.0 (v3.0 uses a XMP_Int32) - // Flags $xx xx - - if ( strFrameID == 0 ) return false; - - if ( LFA_Read ( inFileRef, strFrameID, 4 ) == 0 ) return false; - if ( ! ReadSize ( inFileRef, bVersion, dwSize ) ) return false; - if ( LFA_Read ( inFileRef, &cflag1, 1 ) == 0 ) return false; - if ( LFA_Read ( inFileRef, &cflag2, 1 ) == 0 ) return false; - - return true; - -} - -// ================================================================================================= - -static bool ReadSize ( LFA_FileRef inFileRef, XMP_Uns8 bVersion, unsigned long & dwSize ) -{ - char s4 = 0, s3 = 0, s2 = 0, s1 = 0; - - if ( LFA_Read ( inFileRef, &s4, 1 ) == 0 ) return false; - if ( LFA_Read ( inFileRef, &s3, 1 ) == 0 ) return false; - if ( LFA_Read ( inFileRef, &s2, 1 ) == 0 ) return false; - if ( LFA_Read ( inFileRef, &s1, 1 ) == 0 ) return false; - - if ( bVersion > 3 ) { - dwSize = ((s4 & 0x7f) << 21) | ((s3 & 0x7f) << 14) | ((s2 & 0x7f) << 7) | (s1 & 0x7f); - } else { - dwSize = ((s4 << 24) | (s3 << 16) | (s2 << 8) | s1); - } - - return true; - -} - -// ================================================================================================= - -static unsigned long CalculateSize ( XMP_Uns8 bVersion, unsigned long dwSizeIn ) -{ - unsigned long dwReturn; - - if ( bVersion <= 3 ) { - dwReturn = dwSizeIn; - } else { - dwReturn = dwSizeIn & 0x7f; // Expand to 7 bits per byte. - dwSizeIn = dwSizeIn >> 7; - dwReturn |= ((dwSizeIn & 0x7f) << 8); - dwSizeIn = dwSizeIn >> 7; - dwReturn |= ((dwSizeIn & 0x7f) << 16); - dwSizeIn = dwSizeIn >> 7; - dwReturn |= ((dwSizeIn & 0x7f) << 24); - } - - return dwReturn; - -} - -} // namespace ID3_Support - -// ================================================================================================= - -#endif // XMP_UNIXBuild diff --git a/source/XMPFiles/FormatSupport/ID3_Support.hpp b/source/XMPFiles/FormatSupport/ID3_Support.hpp index 975ccf4..e243d24 100644 --- a/source/XMPFiles/FormatSupport/ID3_Support.hpp +++ b/source/XMPFiles/FormatSupport/ID3_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -11,31 +11,774 @@ // ================================================================================================= #include "XMP_Environment.h" // ! This must be the first include. -#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed. - -#include <vector> - #include "XMP_Const.h" #include "XMPFiles_Impl.hpp" +#include "UnicodeConversions.hpp" +#include "Reconcile_Impl.hpp" +#include <vector> + +#if XMP_WinBuild + #define stricmp _stricmp +#else + static int stricmp ( const char * left, const char * right ) // Case insensitive ASCII compare. + { + char chL = *left; // ! Allow for 0 passes in the loop (one string is empty). + char chR = *right; // ! Return -1 for stricmp ( "a", "Z" ). -#define TAG_MAX_SIZE 5024 + for ( ; (*left != 0) && (*right != 0); ++left, ++right ) { + chL = *left; + chR = *right; + if ( chL == chR ) continue; + if ( ('A' <= chL) && (chL <= 'Z') ) chL |= 0x20; + if ( ('A' <= chR) && (chR <= 'Z') ) chR |= 0x20; + if ( chL != chR ) break; + } + + if ( chL == chR ) return 0; + if ( chL < chR ) return -1; + return 1; + } +#endif + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) namespace ID3_Support { + // Genres + static char Genres[128][32]={ + "Blues", // 0 + "Classic Rock", // 1 + "Country", // 2 + "Dance", + "Disco", + "Funk", + "Grunge", + "Hip-Hop", + "Jazz", // 8 + "Metal", + "New Age", // 10 + "Oldies", + "Other", // 12 + "Pop", + "R&B", + "Rap", + "Reggae", // 16 + "Rock", // 17 + "Techno", + "Industrial", + "Alternative", + "Ska", + "Death Metal", + "Pranks", + "Soundtrack", // 24 + "Euro-Techno", + "Ambient", + "Trip-Hop", + "Vocal", + "Jazz+Funk", + "Fusion", + "Trance", + "Classical", // 32 + "Instrumental", + "Acid", + "House", + "Game", + "Sound Clip", + "Gospel", + "Noise", + "AlternRock", + "Bass", + "Soul", //42 + "Punk", + "Space", + "Meditative", + "Instrumental Pop", + "Instrumental Rock", + "Ethnic", + "Gothic", + "Darkwave", + "Techno-Industrial", + "Electronic", + "Pop-Folk", + "Eurodance", + "Dream", + "Southern Rock", + "Comedy", + "Cult", + "Gangsta", + "Top 40", + "Christian Rap", + "Pop/Funk", + "Jungle", + "Native American", + "Cabaret", + "New Wave", // 66 + "Psychadelic", + "Rave", + "Showtunes", + "Trailer", + "Lo-Fi", + "Tribal", + "Acid Punk", + "Acid Jazz", + "Polka", + "Retro", + "Musical", + "Rock & Roll", + "Hard Rock", + "Folk", // 80 + "Folk-Rock", + "National Folk", + "Swing", + "Fast Fusion", + "Bebob", + "Latin", + "Revival", + "Celtic", + "Bluegrass", // 89 + "Avantgarde", + "Gothic Rock", + "Progressive Rock", + "Psychedelic Rock", + "Symphonic Rock", + "Slow Rock", + "Big Band", + "Chorus", + "Easy Listening", + "Acoustic", + "Humour", // 100 + "Speech", + "Chanson", + "Opera", + "Chamber Music", + "Sonata", + "Symphony", + "Booty Bass", + "Primus", + "Porn Groove", + "Satire", + "Slow Jam", + "Club", + "Tango", + "Samba", + "Folklore", + "Ballad", + "Power Ballad", + "Rhythmic Soul", + "Freestyle", + "Duet", + "Punk Rock", + "Drum Solo", + "A capella", + "Euro-House", + "Dance Hall", + "Unknown" // 126 + }; - bool GetMetaData ( LFA_FileRef inFileRef, char* buffer, unsigned long* pBufferSize, XMP_Int64* fileOffset ); - bool SetMetaData ( LFA_FileRef inFileRef, char * buffer, unsigned long bufferSize, - char * strReconciliatedFrames, unsigned long dwReconciliatedFramesSize, bool fRecon ); + ////////////////////////////////////////////////////////// + // ID3 specific helper routines + inline XMP_Int32 synchToInt32(XMP_Uns32 rawDataBE) // see part 6.2 of spec + { + XMP_Validate( 0 == (rawDataBE & 0x80808080),"input not synchsafe", kXMPErr_InternalFailure ); + XMP_Int32 r = + ( rawDataBE & 0x0000007F ) + + ( rawDataBE >> 1 & 0x00003F80 ) + + ( rawDataBE >> 2 & 0x001FC000 ) + + ( rawDataBE >> 3 & 0x0FE00000 ); + return r; + } - bool GetTagInfo ( LFA_FileRef inFileRef, XMP_Uns8 & v1, XMP_Uns8 & v2, XMP_Uns8 & flags, unsigned long & dwTagSize ); - bool FindID3Tag ( LFA_FileRef inFileRef, unsigned long & dwLen, XMP_Uns8 & bMajorVer ); + inline XMP_Uns32 int32ToSynch(XMP_Int32 value) + { + XMP_Validate( 0 <= 0x0FFFFFFF, "value too big", kXMPErr_InternalFailure ); + XMP_Uns32 r = + ( (value & 0x0000007F) << 0 ) + + ( (value & 0x00003F80) << 1 ) + + ( (value & 0x001FC000) << 2 ) + + ( (value & 0x0FE00000) << 3 ); + return r; + } - bool AddXMPTagToID3Buffer ( char * strCur, unsigned long * pdwCurOffset, unsigned long dwMaxSize, - XMP_Uns8 bVersion, char * strFrameName, const char * strXMPTag, unsigned long dwXMPLength ); + ////////////////////////////////////////////////////////// + // data structures + class ID3Header + { + public: + const static XMP_Uns16 o_id = 0; + const static XMP_Uns16 o_version_major = 3; + const static XMP_Uns16 o_version_minor = 4; + const static XMP_Uns16 o_flags = 5; + const static XMP_Uns16 o_size = 6; - bool GetFrameData ( LFA_FileRef inFileRef, char * strFrame, char * buffer, unsigned long & dwBufferSize ); + const static int FIXED_SIZE = 10; + char fields[FIXED_SIZE]; -} // namespace ID3_Support + bool read(LFA_FileRef file) + { + LFA_Read( file, fields, FIXED_SIZE, true ); + + if ( !CheckBytes( &fields[ID3Header::o_id], "ID3", 3 ) ) + { + // chuck in default contents: + const static char defaultHeader[FIXED_SIZE] = { 'I', 'D', '3', 3, 0, 0, 0, 0, 0, 0 }; + memcpy( this->fields, defaultHeader, FIXED_SIZE); // NBA: implicitly protected by FIXED_SIZE design. + + return false; // no header found (o.k.) thus stick with new, default header constructed above + } + + XMP_Uns8 major = fields[o_version_major]; + XMP_Uns8 minor = fields[o_version_minor]; + XMP_Validate( major==3 || major==4, "invalid ID3 major version", kXMPErr_BadFileFormat ); + XMP_Validate( minor != 0xFF, "invalid ID3 minor version", kXMPErr_BadFileFormat ); + + return true; + } + + void write(LFA_FileRef file, XMP_Int64 tagSize) + { + XMP_Assert( tagSize < 0x10000000 ); // 256 MB limit due to synching. + XMP_Assert( tagSize > 0x0 ); // 256 MB limit due to synching. + PutUns32BE( ( int32ToSynch ( (XMP_Uns32)tagSize - 10 )), &this->fields[ID3Header::o_size] ); + + LFA_Write( file, fields, FIXED_SIZE ); + } + + ~ID3Header() + { + //nothing + }; + }; // ID3Header + + + class ID3v2Frame + { + public: + // fixed + const static XMP_Uns16 o_id = 0; + const static XMP_Uns16 o_size = 4; // size after unsync, excludes frame header. + const static XMP_Uns16 o_flags = 8; + + const static int FIXED_SIZE = 10; + char fields[FIXED_SIZE]; + + XMP_Uns32 id; + XMP_Uns16 flags; + + bool active; //default: true. flag is lowered, if another frame with replaces this one as "last meaningful frame of its kind" + bool changed; //default: false. flag is raised, if setString() is used + + // variable-size frame content + char* content; + XMP_Int32 contentSize; // size of variable content, right as its stored in o_size (total size - 10) + + /** + * a mere empty hull for reading in frames. + */ + ID3v2Frame():id(0),content(0),contentSize(0),flags(0),active(true),changed(false) + { + memset(fields, 0, FIXED_SIZE); + } + + /** + * frame constructor + */ + ID3v2Frame( XMP_Uns32 id ):id(0),content(0),contentSize(0),flags(0),active(true),changed(false) + { + memset(fields, 0, FIXED_SIZE); + + this->id = id; + PutUns32BE( id, &fields[o_id]); + this->flags = 0x0000; + PutUns16BE( flags, &fields[o_flags]); + } + + /** + * copy constructor: + */ + ID3v2Frame( const ID3v2Frame& orig) + { + XMP_Throw("not implemented",kXMPErr_InternalFailure); + } + + /** + * sets a new Content (aka property) + */ + void setFrameValue( const std::string& rawvalue, bool needDescriptor = false , bool utf16le = false, bool isXMPPRIVFrame = false, bool needEncodingByte = true ) + { + // the thing to insert + std::string value; + value.erase(); + + if ( isXMPPRIVFrame ) + { + // this flag must be used exclusive: + XMP_Assert( ! needDescriptor ); + XMP_Assert( ! utf16le ); + + value.append( "XMP\0", 4 ); + value.append( rawvalue ); + value.append( "\0", 1 ); // final zero byte + } + else // not an XMP Priv frame + { + if (needEncodingByte) // don't add encoding byte for example for WCOP + { + if ( utf16le ) + value.append( "\x1", 1 ); // 'unicode encoding' (must be UTF-16, be or le) + else + value.append( "\x0", 1 ); + } + + if ( needDescriptor ) // language descriptor + value.append( "eng", 3 ); // language, empty content description + if ( utf16le ) + { + if ( needDescriptor ) + value.append( "\xFF\xFE\0\0", 4 ); // BOM, 16-bit zero (empty descriptor) + + value.append( "\xFF\xFE", 2 ); + std::string utf16str; + ToUTF16( (XMP_Uns8*) rawvalue.c_str(), rawvalue.size(), &utf16str, false ); + value.append( utf16str ); + value.append( "\0\0", 2 ); + } + else // write as plain Latin-1 + { + std::string convertedValue; // just precaution (mostly used for plain ascii like numbers, etc) + convertedValue.erase(); + ReconcileUtils::UTF8ToLatin1( rawvalue.c_str(), rawvalue.size(), &convertedValue ); + + if ( needDescriptor ) + value.append( "\0", 1 ); // BOM, 16-bit zero (empty descriptor) + value.append( convertedValue ); + value.append( "\0", 1 ); // final zero byte + } + } // of "not an XMP Priv frame" + + this->changed = true; + this->release(); + + XMP_StringPtr valueCStr = value.c_str(); + this->contentSize = (XMP_Int32) value.size(); + XMP_Validate( this->contentSize < 0xA00000, "XMP Property exceeds 20MB in size", kXMPErr_InternalFailure ); + content = new char[ this->contentSize ]; + memcpy( content , valueCStr , this->contentSize ); + } + + void write(LFA_FileRef file, XMP_Uns8 majorVersion) + { + // write back size field: + if ( majorVersion < 4 ) + PutUns32BE( contentSize, &fields[o_size] ); + else + PutUns32BE( int32ToSynch( contentSize ), &fields[o_size] ); + + // write out fixed fields + LFA_Write( file, this->fields, FIXED_SIZE); + // write out contents + LFA_Write( file, this->content, contentSize ); + } + + /** + * majorVersion must be 3 or 4 + * @returns total size of frame + */ + XMP_Int64 read(LFA_FileRef file, XMP_Uns8 majorVersion ) + { + XMP_Assert( majorVersion == 3 || majorVersion == 4 ); + this->release(); // ensures/allows reuse of 'curFrame' + + XMP_Int64 start = LFA_Tell( file ); + LFA_Read( file, fields, FIXED_SIZE, true ); + + id = GetUns32BE( &fields[o_id] ); + + //only padding to come? + if (id == 0x00000000) + { + LFA_Seek( file, -10, SEEK_CUR ); //rewind 10 byte + return 0; // zero signals, not a valid frame + } + //verify id to be valid (4x A-Z and 0-9) + XMP_Validate( + (( fields[0]>0x40 && fields[0]<0x5B) || ( fields[0]>0x2F && fields[0]<0x3A)) && //A-Z,0-9 + (( fields[1]>0x40 && fields[1]<0x5B) || ( fields[1]>0x2F && fields[1]<0x3A)) && + (( fields[2]>0x40 && fields[2]<0x5B) || ( fields[2]>0x2F && fields[2]<0x3A)) && + (( fields[3]>0x40 && fields[3]<0x5B) || ( fields[3]>0x2F && fields[3]<0x3A)) + ,"invalid Frame ID", kXMPErr_BadFileFormat); + + flags = GetUns16BE( &fields[o_flags] ); + XMP_Validate( 0==(flags & 0xEE ),"invalid lower bits in frame flags",kXMPErr_BadFileFormat ); + + //*** flag handling, spec :429ff aka line 431ff (i.e. Frame should be discarded) + // compression and all of that..., unsynchronisation + contentSize = ( majorVersion < 4) ? + GetUns32BE( &fields[o_size] ) + : synchToInt32(GetUns32BE( &fields[o_size] )); + + XMP_Validate( contentSize >= 0, "negative frame size", kXMPErr_BadFileFormat ); + // >20MB in a single frame?, assume broken file + XMP_Validate( contentSize < 0xA00000, "single frame exceeds 20MB", kXMPErr_BadFileFormat ); -#endif // XMP_UNIXBuild + content = new char[ contentSize ]; + + LFA_Read( file, content, contentSize, true ); + XMP_Assert( (LFA_Tell( file ) - start) == FIXED_SIZE + contentSize ); //trivial + return LFA_Tell( file ) - start; + } + + /** + * two types of COMM frames should be preserved but otherwise ignored + * * a 6-field long header just having + * encoding(1 byte),lang(3 bytes) and 0x00 31 (no descriptor, "1" as content") + * perhaps only used to indicate client language + * * COMM frames whose description begins with engiTun, these are iTunes flags + * + * returns true: job done as expted + * false: do not use this frame, but preserve (i.e. iTunes marker COMM frame) + */ + bool advancePastCOMMDescriptor(XMP_Int32& pos) + { + if ( (contentSize-pos) <= 3 ) + return false; // silent error, no room left behing language tag + + if ( !CheckBytes( &content[pos], "eng", 3 ) ) + return false; // not an error, but leave all non-eng tags alone... + + pos += 3; // skip lang tag + if ( pos >= contentSize ) + return false; // silent error + + // skip past descriptor: + while ( pos < contentSize ) + if ( content[pos++] == 0x00 ) + break; + // skip yet-another double zero if applicable + if ( (pos<contentSize) && (content[pos] == 0x00) ) + pos++; + + // check for "1" language indicator comment + if (pos == 5) + if ( contentSize == 6) + if ( GetUns16BE(&content[4]) == 0x0031 ) + return false; + + // check for iTunes tag-comment + if ( pos > 4 ) //there's a content descriptor ( 1 + 3 + zeroTerminator) + { + std::string descriptor = std::string(&content[4],pos-1); + if ( 0 == descriptor.substr(0,4).compare( "iTun" )) // begins with engiTun ? + return false; // leave alone, then + } + + return true; //o.k., descriptor skipped, time for the real thing. + } + + /* + * returns the frame content as a proper UTF8 string + * * w/o the initial encoding byte + * * dealing with BOM's + * + * @returns: by value: character string with the value + * as return value: false if frame is "not of intereset" despite a generally + * "interesting" frame ID, these are + * * iTunes-'marker' COMM frame + */ + bool getFrameValue( XMP_Uns8 majorVersion, XMP_Uns32 frameID, std::string* utf8string ) + { + XMP_Assert( contentSize >= 0 ); + XMP_Assert( contentSize < 0xA00000 ); // more than 20 MB per Propety would be very odd... + XMP_Assert( content != 0 ); + + if ( contentSize == 0) + { + utf8string->erase(); + return true; // ...it is "of interest", even if empty contents. + } + + XMP_Int32 pos = 0; // going through input string + XMP_Uns8 encByte = 0; + // WCOP does not have an encoding byte + // => for all others: use [0] as EncByte, advance pos + if ( frameID != 0x57434F50 ) + { + encByte = content[0]; + pos++; + } + + // mode specific forks (so far only COMM) + bool commMode = ( + ( frameID == 0x434F4D4D ) || // COMM + ( frameID == 0x55534C54 ) // USLT + ); + + switch (encByte) + { + case 0: //ISO-8859-1, 0-terminated + { + if (commMode) + if (! advancePastCOMMDescriptor( pos )) // do and check result + return false; // not a frame of interest! + + char* localPtr = &content[pos]; + size_t localLen = contentSize - pos; + ReconcileUtils::Latin1ToUTF8 ( localPtr, localLen, utf8string ); + } + break; + case 1: // Unicode, v2.4: UTF-16 (undetermined Endianess), with BOM, terminated 0x00 00 + case 2: // UTF-16BE without BOM, terminated 0x00 00 + { + std::string tmp(this->content, this->contentSize); + + if (commMode) + if (! advancePastCOMMDescriptor( pos )) // do and check result + return false; // not a frame of interest! + + bool bigEndian = true; // assume for now (if no BOM follows) + if ( GetUns16BE(&content[pos]) == 0xFEFF ) + { + pos += 2; + bigEndian = true; + } + else if ( GetUns16BE(&content[pos]) == 0xFFFE ) + { + pos += 2; + bigEndian = false; + } + + FromUTF16( (UTF16Unit*)&content[pos], (contentSize - pos) / 2, utf8string, bigEndian ); + } + break; + case 3: // UTF-8 unicode, terminated \0 + // swallow any BOM, just in case + if ( (GetUns32BE(&content[pos]) & 0xFFFFFF00 ) == 0xEFBBBF00 ) + pos += 3; + + if (commMode) + if (! advancePastCOMMDescriptor( pos )) // do and check result + return false; // not a frame of interest! + + utf8string->assign( &content[pos], contentSize - pos ); + break; + default: + XMP_Throw ( "unknown text encoding", kXMPErr_BadFileFormat); //COULDDO assume latin-1 or utf-8 as best-effort + break; + } + return true; + } + + ~ID3v2Frame() + { + this->release(); + } + + void release() + { + if ( content ) + delete content; + content = 0; + contentSize = 0; + } + }; // ID3Header + + + + class ID3v1Tag + { + public: + // fixed + const static XMP_Uns16 o_tag = 0; // must be "TAG" + const static XMP_Uns16 o_title = 3; + const static XMP_Uns16 o_artist = 33; + const static XMP_Uns16 o_album = 63; + const static XMP_Uns16 o_year = 93; + const static XMP_Uns16 o_comment = 97; + const static XMP_Uns16 o_genre = 127; // last by: index, or 255 + + // optional (ID3v1.1) track number) + const static XMP_Uns16 o_zero = 125; //must be zero, for trackNo to be valid + const static XMP_Uns16 o_trackNo = 126; //trackNo + + const static int FIXED_SIZE = 128; + + /** + * @returns returns true, if ID3v1 (or v1.1) exists, otherwise false. + // sets XMP properties en route + */ + bool read( LFA_FileRef file, SXMPMeta* meta ) + { + if ( LFA_Measure( file ) <= 128 ) // ensure sufficient room + return false; + LFA_Seek( file, -128, SEEK_END); + + XMP_Uns32 tagID = LFA_ReadInt32_BE( file ); + tagID = tagID & 0xFFFFFF00; // wipe 4th byte + if ( tagID != 0x54414700 ) + return false; // must be "TAG" + LFA_Seek( file, -1, SEEK_CUR ); //rewind 1 + + ///////////////////////////////////////////////////////// + XMP_Uns8 buffer[31]; // nothing is bigger here, than 30 bytes (offsets [0]-[29]) + buffer[30]=0; // wipe last byte + std::string utf8string; + + // title ////////////////////////////////////////////////////// + LFA_Read( file, buffer, 30, true ); + std::string title( (char*) buffer ); //security: guaranteed to 0-terminate after 30 bytes + if ( ! title.empty() ) + { + ReconcileUtils::Latin1ToUTF8 ( title.c_str(), title.size(), &utf8string ); + meta->SetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , utf8string.c_str() ); + } + // artist ////////////////////////////////////////////////////// + LFA_Read( file, buffer, 30, true ); + std::string artist( (char*) buffer ); + if ( ! artist.empty() ) + { + ReconcileUtils::Latin1ToUTF8 ( artist.c_str(), artist.size(), &utf8string ); + meta->SetProperty( kXMP_NS_DM, "artist", utf8string.c_str() ); + } + // album ////////////////////////////////////////////////////// + LFA_Read( file, buffer, 30, true ); + std::string album( (char*) buffer ); + if ( ! album.empty() ) + { + ReconcileUtils::Latin1ToUTF8 ( album.c_str(), album.size(), &utf8string ); + meta->SetProperty( kXMP_NS_DM, "album", utf8string.c_str() ); + } + // year ////////////////////////////////////////////////////// + LFA_Read( file, buffer, 4, true ); + buffer[4]=0; // ensure 0-term + std::string year( (char*) buffer ); + if ( ! year.empty() ) + { // should be moot for a year, but let's be safe: + ReconcileUtils::Latin1ToUTF8 ( year.c_str(), year.size(), &utf8string ); + meta->SetProperty( kXMP_NS_XMP, "CreateDate", utf8string.c_str() ); + } + // comment ////////////////////////////////////////////////////// + LFA_Read( file, buffer, 30, true ); + std::string comment( (char*) buffer ); + if ( ! comment.empty() ) + { + ReconcileUtils::Latin1ToUTF8 ( comment.c_str(), comment.size(), &utf8string ); + meta->SetProperty( kXMP_NS_DM, "logComment", utf8string.c_str() ); + } + // trackNo (ID3v1.1) ///////////////////////////////////////////// + if ( buffer[28] == 0 ) + { + XMP_Uns8 trackNo = buffer[29]; + if ( trackNo > 0 ) // 0 := unset + { + std::string trackStr; + SXMPUtils::ConvertFromInt( trackNo, 0, &trackStr ); + meta->SetProperty( kXMP_NS_DM, "trackNumber", trackStr.c_str() ); + } + } + // genre ////////////////////////////////////////////////////// + XMP_Uns8 genreNo = LFA_ReadUns8( file ); + if ( (genreNo > 0) && (genreNo < 127) ) // 0 := unset, 127 := 'unknown' + { + meta->SetProperty( kXMP_NS_DM, "genre", Genres[genreNo] ); + } + + return true; // ID3Tag found + } + + void write( LFA_FileRef file, SXMPMeta* meta ) + { + // move in position (extension if applicable happens by caller) + std::string zeros( 128, '\0' ); + std::string utf8, latin1; + + LFA_Seek( file, -128, SEEK_END); + LFA_Write( file, zeros.data(), 128 ); + + // TAG ///////////////////////////////////////////// + LFA_Seek( file, -128, SEEK_END); + LFA_WriteUns8( file, 'T' ); + LFA_WriteUns8( file, 'A' ); + LFA_WriteUns8( file, 'G' ); + + // title ////////////////////////////////////////////////////// + if ( meta->GetLocalizedText( kXMP_NS_DC, "title", "", "x-default", 0, &utf8, kXMP_NoOptions )) + { + LFA_Seek( file, -128 + 3, SEEK_END); + ReconcileUtils::UTF8ToLatin1( utf8.c_str(), utf8.size(), &latin1 ); + LFA_Write( file, latin1.c_str(), MIN( 30, (XMP_Int32)latin1.size() ) ); + } + // artist ////////////////////////////////////////////////////// + if ( meta->GetProperty( kXMP_NS_DM, "artist", &utf8, kXMP_NoOptions )) + { + LFA_Seek( file, -128 + 33, SEEK_END); + ReconcileUtils::UTF8ToLatin1( utf8.c_str(), utf8.size(), &latin1 ); + LFA_Write( file, latin1.c_str(), MIN( 30, (XMP_Int32)latin1.size() ) ); + } + // album ////////////////////////////////////////////////////// + if ( meta->GetProperty( kXMP_NS_DM, "album", &utf8, kXMP_NoOptions )) + { + LFA_Seek( file, -128 + 63, SEEK_END); + ReconcileUtils::UTF8ToLatin1( utf8.c_str(), utf8.size(), &latin1 ); + LFA_Write( file, latin1.c_str(), MIN( 30, (XMP_Int32)latin1.size() ) ); + } + // year ////////////////////////////////////////////////////// + if ( meta->GetProperty( kXMP_NS_XMP, "CreateDate", &utf8, kXMP_NoOptions )) + { + XMP_DateTime dateTime; + SXMPUtils::ConvertToDate( utf8, &dateTime ); + if ( dateTime.hasDate ) + { + SXMPUtils::ConvertFromInt ( dateTime.year, "", &latin1 ); + LFA_Seek ( file, -128 + 93, SEEK_END ); + LFA_Write ( file, latin1.c_str(), MIN ( 4, (XMP_Int32)latin1.size() ) ); + } + } + // comment (write 30 bytes first, see truncation later) //////////// + if ( meta->GetProperty( kXMP_NS_DM, "logComment", &utf8, kXMP_NoOptions )) + { + LFA_Seek ( file, -128 + 97, SEEK_END ); + ReconcileUtils::UTF8ToLatin1 ( utf8.c_str(), utf8.size(), &latin1 ); + LFA_Write ( file, latin1.c_str(), MIN ( 30, (XMP_Int32)latin1.size() ) ); + } + // genre //////////////////////////////////////////////////////////////// + if ( meta->GetProperty( kXMP_NS_DM, "genre", &utf8, kXMP_NoOptions )) + { + XMP_Uns8 genreNo = 0; + + // try to find (case insensitive) match: + int i; + const char* genreCString = utf8.c_str(); + for ( i=0; i < 127; ++i ) { + if ( (strlen( genreCString ) == strlen(Genres[i])) && //fixing buggy stricmp behaviour on PPC + (stricmp( genreCString, Genres[i] ) == 0 )) { + genreNo = i; // found + break; + } // if + } // for + + LFA_Seek( file, -128 + 127, SEEK_END); + LFA_WriteUns8( file, genreNo ); + } + + // trackNo //////////////////////////////////////////////////////////// + if ( meta->GetProperty( kXMP_NS_DM, "trackNumber", &utf8, kXMP_NoOptions )) + { + XMP_Uns8 trackNo = 0; + try + { + trackNo = (XMP_Uns8) SXMPUtils::ConvertToInt( utf8.c_str() ); + + LFA_Seek( file, -128 + 125, SEEK_END); + LFA_WriteUns8( file , 0 ); // ID3v1.1 extension + LFA_WriteUns8( file, trackNo ); + } + catch ( XMP_Error& ) + { + // forgive, just don't set this one. + } + } + } + + }; // ID3v1Tag + +} // namespace ID3_Support #endif // __ID3_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/IPTC_Support.cpp b/source/XMPFiles/FormatSupport/IPTC_Support.cpp index 48e0309..b1514dd 100644 --- a/source/XMPFiles/FormatSupport/IPTC_Support.cpp +++ b/source/XMPFiles/FormatSupport/IPTC_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -17,6 +17,11 @@ /// // ================================================================================================= +enum { kUTF8_IncomingMode = 0, kUTF8_LosslessMode = 1, kUTF8_AlwaysMode = 2 }; +#ifndef kUTF8_Mode + #define kUTF8_Mode kUTF8_AlwaysMode +#endif + const DataSetCharacteristics kKnownDataSets[] = { { kIPTC_ObjectType, kIPTC_UnmappedText, 67, "", "" }, // Not mapped to XMP. { kIPTC_IntellectualGenre, kIPTC_MapSpecial, 68, kXMP_NS_IPTCCore, "IntellectualGenre" }, // Only the name part is in the XMP. @@ -40,14 +45,14 @@ const DataSetCharacteristics kKnownDataSets[] = { kIPTC_RefService, kIPTC_UnmappedText, 10, "", "" }, // Not mapped to XMP. ! Interleave 2:45, 2:47, 2:50! { kIPTC_RefDate, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP. ! Interleave 2:45, 2:47, 2:50! { kIPTC_RefNumber, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP. ! Interleave 2:45, 2:47, 2:50! - { kIPTC_DateCreated, kIPTC_MapSpecial, 8, kXMP_NS_Photoshop, "DateCreated" }, // Combined with 2:60, TimeCreated. - { kIPTC_TimeCreated, kIPTC_MapSpecial, 11, "", "" }, // Combined with 2:55, DateCreated. - { kIPTC_DigitalCreateDate, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP. - { kIPTC_DigitalCreateTime, kIPTC_UnmappedText, 11, "", "" }, // Not mapped to XMP. + { kIPTC_DateCreated, kIPTC_MapSpecial, 8, kXMP_NS_Photoshop, "DateCreated" }, // ! Reformatted date. Combined with 2:60, TimeCreated. + { kIPTC_TimeCreated, kIPTC_UnmappedText, 11, "", "" }, // ! Combined with 2:55, DateCreated. + { kIPTC_DigitalCreateDate, kIPTC_Map3Way, 8, "", "" }, // ! 3 way Exif-IPTC-XMP date/time set. Combined with 2:63, DigitalCreateTime. + { kIPTC_DigitalCreateTime, kIPTC_UnmappedText, 11, "", "" }, // ! Combined with 2:62, DigitalCreateDate. { kIPTC_OriginProgram, kIPTC_UnmappedText, 32, "", "" }, // Not mapped to XMP. { kIPTC_ProgramVersion, kIPTC_UnmappedText, 10, "", "" }, // Not mapped to XMP. { kIPTC_ObjectCycle, kIPTC_UnmappedText, 1, "", "" }, // Not mapped to XMP. - { kIPTC_Creator, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "Author" }, // ! Aliased to dc:creator[1]. + { kIPTC_Creator, kIPTC_Map3Way, 32, "", "" }, // ! In the 3 way Exif-IPTC-XMP set. { kIPTC_CreatorJobtitle, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "AuthorsPosition" }, { kIPTC_City, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "City" }, { kIPTC_Location, kIPTC_MapSimple, 32, kXMP_NS_IPTCCore, "Location" }, @@ -58,9 +63,9 @@ const DataSetCharacteristics kKnownDataSets[] = { kIPTC_Headline, kIPTC_MapSimple, 256, kXMP_NS_Photoshop, "Headline" }, { kIPTC_Provider, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "Credit" }, { kIPTC_Source, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "Source" }, - { kIPTC_CopyrightNotice, kIPTC_MapLangAlt, 128, kXMP_NS_DC, "rights" }, + { kIPTC_CopyrightNotice, kIPTC_Map3Way, 128, "", "" }, // ! In the 3 way Exif-IPTC-XMP set. { kIPTC_Contact, kIPTC_UnmappedText, 128, "", "" }, // Not mapped to XMP. - { kIPTC_Description, kIPTC_MapLangAlt, 2000, kXMP_NS_DC, "description" }, + { kIPTC_Description, kIPTC_Map3Way, 2000, "", "" }, // ! In the 3 way Exif-IPTC-XMP set. { kIPTC_DescriptionWriter, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "CaptionWriter" }, { kIPTC_RasterizedCaption, kIPTC_UnmappedBin, 7360, "", "" }, // Not mapped to XMP. ! Binary data! { kIPTC_ImageType, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP. @@ -72,8 +77,8 @@ const DataSetCharacteristics kKnownDataSets[] = { kIPTC_AudioDuration, kIPTC_UnmappedText, 6, "", "" }, // Not mapped to XMP. { kIPTC_AudioOutcue, kIPTC_UnmappedText, 64, "", "" }, // Not mapped to XMP. { kIPTC_PreviewFormat, kIPTC_UnmappedBin, 2, "", "" }, // Not mapped to XMP. ! Binary data! - { kIPTC_PreviewFormatVers, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP. ! Binary data! - { kIPTC_PreviewData, kIPTC_UnmappedText, 256000, "", "" }, // Not mapped to XMP. ! Binary data! + { kIPTC_PreviewFormatVers, kIPTC_UnmappedBin, 2, "", "" }, // Not mapped to XMP. ! Binary data! + { kIPTC_PreviewData, kIPTC_UnmappedBin, 256000, "", "" }, // Not mapped to XMP. ! Binary data! { 255, kIPTC_MapSpecial, 0, 0, 0 } }; // ! Must be last as a sentinel. // A combination of the IPTC "Subject Reference System Guidelines" and IIMv4.1 Appendix G. @@ -142,7 +147,7 @@ static const DataSetCharacteristics* FindKnownDataSet ( XMP_Uns8 id ) // IPTC_Manager::ParseMemoryDataSets // ================================= // -// Parse the legacy IIM block, keeping information about all 2:* DataSets and size of other records. +// Parse the legacy IIM block. Offsets and lengths are kept for any 1:90 DataSet and all of Record 2. void IPTC_Manager::ParseMemoryDataSets ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) { @@ -188,7 +193,8 @@ void IPTC_Manager::ParseMemoryDataSets ( const void* data, XMP_Uns32 length, boo this->utf8Encoding = false; - bool foundRec2 = false; + bool found190 = false; + bool found2xx = false; for ( ; iptcPtr <= iptcLimit; iptcPtr += dsLen ) { @@ -217,25 +223,28 @@ void IPTC_Manager::ParseMemoryDataSets ( const void* data, XMP_Uns32 length, boo if ( recNum == 0 ) continue; // Should not be a record 0. Throw instead? if ( recNum == 1 ) { - if ( (dsNum == 90) && (dsLen >= 3) ) { - if ( memcmp ( iptcPtr, "\x1B\x25\x47", 3 ) == 0 ) this->utf8Encoding = true; + if ( dsNum == 90 ) { + found190 = true; + this->offset190 = (XMP_Uns32) (dsPtr - this->iptcContent); + this->length190 = 5 + dsLen; + if ( (dsLen == 3) && (memcmp ( iptcPtr, "\x1B\x25\x47", 3 ) == 0) ) this->utf8Encoding = true; } continue; // Ignore all other record 1 DataSets. } if ( recNum == 2 ) { - if ( ! foundRec2 ) { - foundRec2 = true; - this->rec2Offset = (XMP_Uns32) (dsPtr - this->iptcContent); - this->rec2Length = this->iptcLength - this->rec2Offset; // ! In case there are no other records. + if ( ! found2xx ) { + found2xx = true; + this->offset2xx = (XMP_Uns32) (dsPtr - this->iptcContent); + this->length2xx = this->iptcLength - this->offset2xx; // ! In case there are no other records. } } else { - this->rec2Length = (XMP_Uns32) (dsPtr - (this->iptcContent + this->rec2Offset)); - break; // The records are in ascending order, done. + this->length2xx = (XMP_Uns32) (dsPtr - (this->iptcContent + this->offset2xx)); + break; // The records are in ascending order, done with 2:xx DataSets. } XMP_Assert ( recNum == 2 ); - if ( dsNum == 0 ) continue; // Ignore 2:00 when reading. + if ( dsNum == 0 ) continue; // Ignore 2:00 when reading, it is created explicitly when writing. DataSetInfo dsInfo ( dsNum, dsLen, iptcPtr ); DataSetMap::iterator dsPos = this->dataSets.find ( dsNum ); @@ -246,7 +255,7 @@ void IPTC_Manager::ParseMemoryDataSets ( const void* data, XMP_Uns32 length, boo if ( (knownDS == 0) || (knownDS->mapForm == kIPTC_MapArray) ) { repeatable = true; // Allow repeats for unknown DataSets. - } else if ( dsNum == kIPTC_SubjectCode ) { + } else if ( (dsNum == kIPTC_Creator) || (dsNum == kIPTC_SubjectCode) ) { repeatable = true; } @@ -276,7 +285,7 @@ size_t IPTC_Manager::GetDataSet ( XMP_Uns8 id, DataSetInfo* info, size_t which / if ( which >= dsCount ) return 0; // Valid range for which is 0 .. count-1. if ( info != 0 ) { - for ( size_t i = 0; i < which; ++i ) ++dsPos; // ??? dsPos += which; + for ( size_t i = 0; i < which; ++i ) ++dsPos; // Can't do "dsPos += which", no iter+int operator. *info = dsPos->second; } @@ -290,6 +299,8 @@ size_t IPTC_Manager::GetDataSet ( XMP_Uns8 id, DataSetInfo* info, size_t which / size_t IPTC_Manager::GetDataSet_UTF8 ( XMP_Uns8 id, std::string * utf8Str, size_t which /* = 0 */ ) const { + if ( utf8Str != 0 ) utf8Str->erase(); + DataSetInfo dsInfo; size_t dsCount = GetDataSet ( id, &dsInfo, which ); if ( dsCount == 0 ) return 0; @@ -297,14 +308,10 @@ size_t IPTC_Manager::GetDataSet_UTF8 ( XMP_Uns8 id, std::string * utf8Str, size_ if ( utf8Str != 0 ) { if ( this->utf8Encoding ) { utf8Str->assign ( (char*)dsInfo.dataPtr, dsInfo.dataLen ); - } else { - #if ! XMP_UNIXBuild - ReconcileUtils::LocalToUTF8 ( dsInfo.dataPtr, dsInfo.dataLen, utf8Str ); - #else - // ! Hack until legacy-as-local issues are resolved for generic UNIX. - if ( ! ReconcileUtils::IsUTF8 ( dsInfo.dataPtr, dsInfo.dataLen ) ) return 0; - utf8Str->assign ( (char*)dsInfo.dataPtr, dsInfo.dataLen ); - #endif + } else if ( ! ignoreLocalText ) { + ReconcileUtils::LocalToUTF8 ( dsInfo.dataPtr, dsInfo.dataLen, utf8Str ); + } else if ( ReconcileUtils::IsASCII ( dsInfo.dataPtr, dsInfo.dataLen ) ) { + utf8Str->assign ( (char*)dsInfo.dataPtr, dsInfo.dataLen ); } } @@ -339,8 +346,6 @@ void IPTC_Manager::DisposeLooseValue ( DataSetInfo & dsInfo ) // ================================================================================================= // ================================================================================================= -#if ! XMP_UNIXBuild // ! Disable IPTC output for generic UNIX until the legacy-as-local issues are resolved. - // ================================================================================================= // IPTC_Writer::~IPTC_Writer // ========================= @@ -367,39 +372,57 @@ void IPTC_Writer::SetDataSet_UTF8 ( XMP_Uns8 id, const void* utf8Ptr, XMP_Uns32 // Decide which character encoding to use and get a temporary pointer to the value. - XMP_Uns8 * tempPtr; - XMP_Uns32 dataLen; - - std::string localStr, rtStr; - - if ( this->utf8Encoding ) { + XMP_Uns8 * tempPtr; + XMP_Uns32 dataLen; + std::string localStr; - // We're already using UTF-8. + if ( kUTF8_Mode == kUTF8_AlwaysMode ) { + + // Always use UTF-8. + if ( ! this->utf8Encoding ) this->ConvertToUTF8(); tempPtr = (XMP_Uns8*) utf8Ptr; dataLen = utf8Len; - } else { + } else if ( kUTF8_Mode == kUTF8_IncomingMode ) { -// *** Disable the round trip loss checking for now. We only use UTF-8 if the input had it. - - ReconcileUtils::UTF8ToLocal ( utf8Ptr, utf8Len, &localStr ); -// ReconcileUtils::LocalToUTF8 ( localStr.data(), localStr.size(), &rtStr ); - -// if ( (rtStr.size() == utf8Len) && (memcmp ( rtStr.data(), utf8Ptr, utf8Len ) == 0) ) { - - // It round-tripped without loss, keep local encoding. + // Only use UTF-8 if that was what the parsed block used. + if ( this->utf8Encoding ) { + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } else if ( ! ignoreLocalText ) { + ReconcileUtils::UTF8ToLocal ( utf8Ptr, utf8Len, &localStr ); tempPtr = (XMP_Uns8*) localStr.data(); - dataLen = (XMP_Uns32)localStr.size(); - -// } else { - -// // Had round-trip loss, change to UTF-8 for all text DataSets. -// this->ConvertToUTF8(); -// XMP_Assert ( this->utf8Encoding ); -// tempPtr = (XMP_Uns8*) utf8Ptr; -// dataLen = utf8Len; - -// } + dataLen = (XMP_Uns32) localStr.size(); + } else { + if ( ! ReconcileUtils::IsASCII ( utf8Ptr, utf8Len ) ) return; + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } + + } else if ( kUTF8_Mode == kUTF8_LosslessMode ) { + + // Convert to UTF-8 if needed to prevent round trip loss. + if ( this->utf8Encoding ) { + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } else if ( ! ignoreLocalText ) { + std::string rtStr; + ReconcileUtils::UTF8ToLocal ( utf8Ptr, utf8Len, &localStr ); + ReconcileUtils::LocalToUTF8 ( localStr.data(), localStr.size(), &rtStr ); + if ( (rtStr.size() == utf8Len) && (memcmp ( rtStr.data(), utf8Ptr, utf8Len ) == 0) ) { + tempPtr = (XMP_Uns8*) localStr.data(); // No loss, keep local encoding. + dataLen = (XMP_Uns32) localStr.size(); + } else { + this->ConvertToUTF8(); // Had loss, change everything to UTF-8. + XMP_Assert ( this->utf8Encoding ); + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } + } else { + if ( ! ReconcileUtils::IsASCII ( utf8Ptr, utf8Len ) ) return; + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } } @@ -422,7 +445,7 @@ void IPTC_Writer::SetDataSet_UTF8 ( XMP_Uns8 id, const void* utf8Ptr, XMP_Uns32 if ( knownDS->mapForm == kIPTC_MapArray ) { repeatable = true; - } else if ( id == kIPTC_SubjectCode ) { + } else if ( (id == kIPTC_Creator) || (id == kIPTC_SubjectCode) ) { repeatable = true; } @@ -500,35 +523,43 @@ void IPTC_Writer::DeleteDataSet ( XMP_Uns8 id, long which /* = -1 */ ) // IPTC_Writer::UpdateMemoryDataSets // ================================= // -// Reconstruct the entire IIM block. Start with DataSet 1:0 and 1:90 if UTF-8 encoding is used, -// then 2:0, then 2:xx DataSets that have values. This does not include any alignment padding, that -// is an artifact of some specific wrappers such as Photoshop image resources. +// Reconstruct the entire IIM block. This does not include any final alignment padding, that is an +// artifact of some specific wrappers such as Photoshop image resources. -XMP_Uns32 IPTC_Writer::UpdateMemoryDataSets ( void** dataPtr ) +void IPTC_Writer::UpdateMemoryDataSets() { - if ( ! this->changed ) { - if ( dataPtr != 0 ) *dataPtr = this->iptcContent; - return this->iptcLength; - } + if ( ! this->changed ) return; DataSetMap::iterator dsPos; DataSetMap::iterator dsEnd = this->dataSets.end(); -// if ( this->utf8Encoding ) { *** Disable round trip loss checking for now. *** -// if ( ! this->CheckRoundTripLoss() ) this->ConvertToLocal(); -// } + if ( kUTF8_Mode == kUTF8_LosslessMode ) { + if ( this->utf8Encoding ) { + if ( ! this->CheckRoundTripLoss() ) this->ConvertToLocal(); + } else { + if ( this->CheckRoundTripLoss() ) this->ConvertToUTF8(); + } + } // Compute the length of the new IIM block, including space for records other than 2. All other - // records are preserved as-is, including 1:90. If we ever start changing the encoding, we will - // have to remove any existing 1:90 and insert a new one. + // records are preserved as-is, except for 1:90. If local text is used then 1:90 is omitted, + // if UTF-8 text is used then 1:90 is written. + + XMP_Uns32 midOffset = this->offset190 + this->length190; + XMP_Uns32 midLength = this->offset2xx - midOffset; + + XMP_Uns32 suffixOffset = this->offset2xx + this->length2xx; + XMP_Uns32 suffixLength = this->iptcLength - suffixOffset; - XMP_Uns32 newLength = (5+2); // For 2:0. - newLength += (this->iptcLength - rec2Length); // For records other than 2. + XMP_Uns32 newLength = this->offset190 + (5+2); // For things before 1:90 and 2:0. + if ( this->utf8Encoding ) newLength += (5+3); // For 1:90, if written. + newLength += midLength; // For things between 1:90 and 2:xx. + newLength += suffixLength; // For records after 2. - for ( dsPos = this->dataSets.begin(); dsPos != dsEnd; ++dsPos ) { + for ( dsPos = this->dataSets.begin(); dsPos != dsEnd; ++dsPos ) { // Accumulate the 2:xx sizes. XMP_Uns32 dsLen = dsPos->second.dataLen; newLength += (5 + dsLen); - if ( dsLen > 0x7FFF ) newLength += 4; // We always use a 4 byte extended length. + if ( dsLen > 0x7FFF ) newLength += 4; // We always use a 4 byte extended length for big values. } // Allocate the new IIM block. @@ -538,25 +569,35 @@ XMP_Uns32 IPTC_Writer::UpdateMemoryDataSets ( void** dataPtr ) XMP_Uns8* dsPtr = newContent; - XMP_Uns32 prefixLength = this->rec2Offset; - XMP_Uns32 suffixOffset = this->rec2Offset + this->rec2Length; - XMP_Uns32 suffixLength = this->iptcLength - suffixOffset; + // Write the 1:xx DataSets, including whatever is appropriate for 1:90. + + if ( this->offset190 > 0 ) { // Whatever was before an existing 1:90. + memcpy ( dsPtr, this->iptcContent, this->offset190 ); // AUDIT: Within range of allocation. + dsPtr += this->offset190; + } - if ( prefixLength > 0 ) { // Write the records before 2. - memcpy ( dsPtr, this->iptcContent, prefixLength ); // AUDIT: Within range of allocation. - dsPtr += prefixLength; + if ( this->utf8Encoding ) { // Write 1:90 only if text is UTF-8. + memcpy ( dsPtr, "\x1C\x01\x5A\x00\x03\x1B\x25\x47", (5+3) ); // AUDIT: Within range of allocation. + dsPtr += (5+3); } - if ( ! this->utf8Encoding ) { + if ( midLength > 0 ) { // Write the remainder before record 2. + memcpy ( dsPtr, (this->iptcContent + midOffset), midLength ); // AUDIT: Within range of allocation. + dsPtr += midLength; + } + + // Write the 2:xx DataSets, starting with an explicit 2:00. + + if ( this->utf8Encoding ) { + // Start with 2:00 for version 4. + memcpy ( dsPtr, "\x1C\x02\x00\x00\x02\x00\x04", (5+2) ); // AUDIT: Within range of allocation. + dsPtr += (5+2); + } else { // Start with 2:00 for version 2. // *** We should probably write version 4 all the time. This is a late CS3 change, don't want // *** to risk breaking other apps that might be strict about version checking. memcpy ( dsPtr, "\x1C\x02\x00\x00\x02\x00\x02", (5+2) ); // AUDIT: Within range of allocation. dsPtr += (5+2); - } else { - // Start with 2:00 for version 4. - memcpy ( dsPtr, "\x1C\x02\x00\x00\x02\x00\x04", (5+2) ); // AUDIT: Within range of allocation. - dsPtr += (5+2); } // Fill in the record 2 DataSets that have values. @@ -601,15 +642,8 @@ XMP_Uns32 IPTC_Writer::UpdateMemoryDataSets ( void** dataPtr ) XMP_Assert ( this->iptcLength == newLength ); this->ownedContent = (newLength > 0); // We really do own the new content, if not empty. - // Done. - - if ( dataPtr != 0 ) *dataPtr = this->iptcContent; - return this->iptcLength; - } // IPTC_Writer::UpdateMemoryDataSets -#if 0 // *** Disable the round trip loss checking for now. - // ================================================================================================= // IPTC_Writer::ConvertToUTF8 // ========================== @@ -631,7 +665,7 @@ void IPTC_Writer::ConvertToUTF8() ReconcileUtils::LocalToUTF8 ( dsInfo.dataPtr, dsInfo.dataLen, &utf8Str ); this->DisposeLooseValue ( dsInfo ); - dsInfo.dataLen = utf8Str.size(); + dsInfo.dataLen = (XMP_Uns32)utf8Str.size(); dsInfo.dataPtr = (XMP_Uns8*) malloc ( dsInfo.dataLen ); if ( dsInfo.dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); memcpy ( dsInfo.dataPtr, utf8Str.data(), dsInfo.dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above. @@ -663,7 +697,7 @@ void IPTC_Writer::ConvertToLocal() ReconcileUtils::UTF8ToLocal ( dsInfo.dataPtr, dsInfo.dataLen, &localStr ); this->DisposeLooseValue ( dsInfo ); - dsInfo.dataLen = localStr.size(); + dsInfo.dataLen = (XMP_Uns32)localStr.size(); dsInfo.dataPtr = (XMP_Uns8*) malloc ( dsInfo.dataLen ); if ( dsInfo.dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); memcpy ( dsInfo.dataPtr, localStr.data(), dsInfo.dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above. @@ -708,8 +742,4 @@ bool IPTC_Writer::CheckRoundTripLoss() } // IPTC_Writer::CheckRoundTripLoss -#endif // Round-trip loss checking - // ================================================================================================= - -#endif // ! XMP_UNIXBuild diff --git a/source/XMPFiles/FormatSupport/IPTC_Support.hpp b/source/XMPFiles/FormatSupport/IPTC_Support.hpp index 25ac9c2..470a6d4 100644 --- a/source/XMPFiles/FormatSupport/IPTC_Support.hpp +++ b/source/XMPFiles/FormatSupport/IPTC_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -114,11 +114,12 @@ enum { // List of recognized 2:* IIM DataSets. The names are from IIMv4 and IPTC kIPTC_PreviewData = 202 }; -enum { // Forms of mapping legacy IPTC to XMP. +enum { // Forms of mapping legacy IPTC to XMP. Order is significant, see PhotoDataUtils::Import2WayIPTC! kIPTC_MapSimple, // The XMP is simple, the last DataSet occurrence is kept. kIPTC_MapLangAlt, // The XMP is a LangAlt x-default item, the last DataSet occurrence is kept. kIPTC_MapArray, // The XMP is an unordered array, all DataSets are kept. kIPTC_MapSpecial, // The mapping requires DataSet specific code. + kIPTC_Map3Way, // Has a 3 way mapping between Exif, IPTC, and XMP. kIPTC_UnmappedText, // A text DataSet that is not mapped to XMP. kIPTC_UnmappedBin // A binary DataSet that is not mapped to XMP. }; @@ -201,12 +202,18 @@ public: bool UsingUTF8() const { return this->utf8Encoding; }; + // -------------------------------------------------- + // Update the DataSets to reflect the changed values. + + virtual void UpdateMemoryDataSets() = 0; + // --------------------------------------------------------------------------------------------- - // Update the DataSets to reflect the changed values. Returns the new size of the DataSets. The - // returned dataPtr must be treated as read only. It exists until the IPTC_Manager destructor - // is called. Can be used with read-only instances to get the raw data block info. + // Get the location and size of the full IPTC block. The client must call UpdateMemoryDataSets + // first if appropriate. The returned dataPtr must be treated as read only. It exists until the + // IPTC_Manager destructor is called. - virtual XMP_Uns32 UpdateMemoryDataSets ( void** dataPtr ) = 0; + XMP_Uns32 GetBlockInfo ( void** dataPtr ) const + { if ( dataPtr != 0 ) *dataPtr = this->iptcContent; return this->iptcLength; }; // --------------------------------------------------------------------------------------------- @@ -221,13 +228,13 @@ protected: DataSetMap dataSets; XMP_Uns8* iptcContent; - XMP_Uns32 iptcLength, rec2Offset, rec2Length; + XMP_Uns32 iptcLength, offset190, length190, offset2xx, length2xx; bool changed; bool ownedContent; // True if IPTC_Manager destructor needs to release the content block. bool utf8Encoding; // True if text values are encoded as UTF-8. - IPTC_Manager() : iptcContent(0), iptcLength(0), rec2Offset(0), rec2Length(0), + IPTC_Manager() : iptcContent(0), iptcLength(0), offset190(0), length190(0), offset2xx(0), length2xx(0), changed(false), ownedContent(false), utf8Encoding(false) {}; void DisposeLooseValue ( DataSetInfo & dsInfo ); @@ -254,8 +261,7 @@ public: bool IsChanged() { return false; }; - XMP_Uns32 UpdateMemoryDataSets ( void** dataPtr ) - { if ( dataPtr != 0 ) *dataPtr = iptcContent; return iptcLength; }; + void UpdateMemoryDataSets() { NotAppropriate(); }; virtual ~IPTC_Reader() {}; @@ -269,8 +275,6 @@ private: // IPTC_Writer // =========== -#if ! XMP_UNIXBuild // ! Disable IPTC output for generic UNIX until the legacy-as-local issues are resolved. - class IPTC_Writer : public IPTC_Manager { public: @@ -280,7 +284,7 @@ public: bool IsChanged() { return changed; }; - XMP_Uns32 UpdateMemoryDataSets ( void** dataPtr ); + void UpdateMemoryDataSets (); IPTC_Writer() {}; @@ -288,19 +292,13 @@ public: private: -#if 0 - void ConvertToUTF8(); void ConvertToLocal(); bool CheckRoundTripLoss(); - -#endif // *** Disable the round trip loss checking for now. }; // IPTC_Writer -#endif // ! XMP_UNIXBuild - // ================================================================================================= #endif // __IPTC_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.cpp b/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.cpp new file mode 100644 index 0000000..7d94734 --- /dev/null +++ b/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.cpp @@ -0,0 +1,149 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "ISOBaseMedia_Support.hpp" +#include "XMPFiles_Impl.hpp" + +// ================================================================================================= +/// \file ISOBaseMedia_Support.cpp +/// \brief Manager for parsing and serializing ISO Base Media files ( MPEG-4 and JPEG-2000). +/// +// ================================================================================================= + +namespace ISOMedia { + +static BoxInfo voidInfo; + +// ================================================================================================= +// GetBoxInfo - from memory +// ======================== + +const XMP_Uns8 * GetBoxInfo ( const XMP_Uns8 * boxPtr, const XMP_Uns8 * boxLimit, + BoxInfo * info, bool throwErrors /* = false */ ) +{ + XMP_Uns32 u32Size; + + if ( info == 0 ) info = &voidInfo; + info->boxType = info->headerSize = 0; + info->contentSize = 0; + + if ( boxPtr >= boxLimit ) XMP_Throw ( "Bad offset to GetBoxInfo", kXMPErr_InternalFailure ); + + if ( (boxLimit - boxPtr) < 8 ) { // Is there enough space for a standard box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO box header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxPtr); + return boxLimit; + } + + u32Size = GetUns32BE ( boxPtr ); + info->boxType = GetUns32BE ( boxPtr+4 ); + + if ( u32Size >= 8 ) { + info->headerSize = 8; // Normal explicit size case. + info->contentSize = u32Size - 8; + } else if ( u32Size == 0 ) { + info->headerSize = 8; // The box goes to EoF - treat it as "to limit". + info->contentSize = (boxLimit - boxPtr) - 8; + } else if ( u32Size != 1 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box size, 2..7", kXMPErr_BadFileFormat ); + info->headerSize = 8; // Bad total size in the range of 2..7, treat as 8. + info->contentSize = 0; + } else { + if ( (boxLimit - boxPtr) < 16 ) { // Is there enough space for an extended box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO extended header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxPtr); + return boxLimit; + } + XMP_Uns64 u64Size = GetUns64BE ( boxPtr+8 ); + if ( u64Size < 16 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO extended box size, < 16", kXMPErr_BadFileFormat ); + u64Size = 16; // Treat bad total size as 16. + } + info->headerSize = 16; + info->contentSize = u64Size - 16; + } + + XMP_Assert ( (XMP_Uns64)(boxLimit - boxPtr) >= (XMP_Uns64)info->headerSize ); + if ( info->contentSize > (XMP_Uns64)((boxLimit - boxPtr) - info->headerSize) ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box content size", kXMPErr_BadFileFormat ); + info->contentSize = ((boxLimit - boxPtr) - info->headerSize); // Trim a bad content size to the limit. + } + + return (boxPtr + info->headerSize + info->contentSize); + +} // GetBoxInfo + +// ================================================================================================= +// GetBoxInfo - from a file +// ======================== + +XMP_Uns64 GetBoxInfo ( LFA_FileRef fileRef, const XMP_Uns64 boxOffset, const XMP_Uns64 boxLimit, + BoxInfo * info, bool doSeek /* = true */, bool throwErrors /* = false */ ) +{ + XMP_Uns8 buffer [8]; + XMP_Uns32 u32Size; + + if ( info == 0 ) info = &voidInfo; + info->boxType = info->headerSize = 0; + info->contentSize = 0; + + if ( boxOffset >= boxLimit ) XMP_Throw ( "Bad offset to GetBoxInfo", kXMPErr_InternalFailure ); + + if ( (boxLimit - boxOffset) < 8 ) { // Is there enough space for a standard box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO box header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxOffset); + return boxLimit; + } + + if ( doSeek ) LFA_Seek ( fileRef, boxOffset, SEEK_SET ); + (void) LFA_Read ( fileRef, buffer, 8, kLFA_RequireAll ); + + u32Size = GetUns32BE ( &buffer[0] ); + info->boxType = GetUns32BE ( &buffer[4] ); + + if ( u32Size >= 8 ) { + info->headerSize = 8; // Normal explicit size case. + info->contentSize = u32Size - 8; + } else if ( u32Size == 0 ) { + info->headerSize = 8; // The box goes to EoF. + info->contentSize = LFA_Measure(fileRef) - (boxOffset + 8); + } else if ( u32Size != 1 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box size, 2..7", kXMPErr_BadFileFormat ); + info->headerSize = 8; // Bad total size in the range of 2..7, treat as 8. + info->contentSize = 0; + } else { + if ( (boxLimit - boxOffset) < 16 ) { // Is there enough space for an extended box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO extended header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxOffset); + return boxLimit; + } + (void) LFA_Read ( fileRef, buffer, 8, kLFA_RequireAll ); + XMP_Uns64 u64Size = GetUns64BE ( &buffer[0] ); + if ( u64Size < 16 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO extended box size, < 16", kXMPErr_BadFileFormat ); + u64Size = 16; // Treat bad total size as 16. + } + info->headerSize = 16; + info->contentSize = u64Size - 16; + } + + XMP_Assert ( (boxLimit - boxOffset) >= info->headerSize ); + if ( info->contentSize > (boxLimit - boxOffset - info->headerSize) ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box content size", kXMPErr_BadFileFormat ); + info->contentSize = (boxLimit - boxOffset - info->headerSize); // Trim a bad content size to the limit. + } + + return (boxOffset + info->headerSize + info->contentSize); + +} // GetBoxInfo + +} // namespace ISO_Media + + +// ================================================================================================= diff --git a/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.hpp b/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.hpp new file mode 100644 index 0000000..4f50df7 --- /dev/null +++ b/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.hpp @@ -0,0 +1,100 @@ +#ifndef __ISOBaseMedia_Support_hpp__ +#define __ISOBaseMedia_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMP_Environment.h" // ! This must be the first include. + +#include "XMP_Const.h" +#include "LargeFileAccess.hpp" + +// ================================================================================================= +/// \file ISOBaseMedia_Support.hpp +/// \brief XMPFiles support for the ISO Base Media File Format. +/// +/// \note These classes are for use only when directly compiled and linked. They should not be +/// packaged in a DLL by themselves. They do not provide any form of C++ ABI protection. +// ================================================================================================= + +namespace ISOMedia { + + enum { + k_ftyp = 0x66747970UL, // File header Box, no version/flags. + + k_mp41 = 0x6D703431UL, // Compatible brand codes + k_mp42 = 0x6D703432UL, + k_f4v = 0x66347620UL, + k_qt = 0x71742020UL, + + k_moov = 0x6D6F6F76UL, // Container Box, no version/flags. + k_mvhd = 0x6D766864UL, // Data FullBox, has version/flags. + k_hdlr = 0x68646C72UL, + k_udta = 0x75647461UL, // Container Box, no version/flags. + k_cprt = 0x63707274UL, // Data FullBox, has version/flags. + k_uuid = 0x75756964UL, // Data Box, no version/flags. + k_free = 0x66726565UL, // Free space Box, no version/flags. + k_mdat = 0x6D646174UL, // Media data Box, no version/flags. + + k_trak = 0x7472616BUL, // Types for the QuickTime timecode track. + k_tkhd = 0x746B6864UL, + k_mdia = 0x6D646961UL, + k_mdhd = 0x6D646864UL, + k_tmcd = 0x746D6364UL, + k_mhlr = 0x6D686C72UL, + k_minf = 0x6D696E66UL, + k_stbl = 0x7374626CUL, + k_stsd = 0x73747364UL, + k_stsc = 0x73747363UL, + k_stco = 0x7374636FUL, + k_co64 = 0x636F3634UL, + + k_meta = 0x6D657461UL, // Types for the iTunes metadata boxes. + k_ilst = 0x696C7374UL, + k_mdir = 0x6D646972UL, + k_mean = 0x6D65616EUL, + k_name = 0x6E616D65UL, + k_data = 0x64617461UL, + k_hyphens = 0x2D2D2D2DUL, + + k_skip = 0x736B6970UL, // Additional classic QuickTime top level boxes. + k_wide = 0x77696465UL, + k_pnot = 0x706E6F74UL, + + k_XMP_ = 0x584D505FUL // The QuickTime variant XMP box. + }; + + static XMP_Uns32 k_xmpUUID [4] = { MakeUns32BE ( 0xBE7ACFCBUL ), + MakeUns32BE ( 0x97A942E8UL ), + MakeUns32BE ( 0x9C719994UL ), + MakeUns32BE ( 0x91E3AFACUL ) }; + + struct BoxInfo { + XMP_Uns32 boxType; // In memory as native endian! + XMP_Uns32 headerSize; // Normally 8 or 16, less than 8 if available space is too small. + XMP_Uns64 contentSize; // Always the real size, never 0 for "to EoF". + BoxInfo() : boxType(0), headerSize(0), contentSize(0) {}; + }; + + // Get basic info about a box in memory, returning a pointer to the following box. + const XMP_Uns8 * GetBoxInfo ( const XMP_Uns8 * boxPtr, const XMP_Uns8 * boxLimit, + BoxInfo * info, bool throwErrors = false ); + + // Get basic info about a box in a file, returning the offset of the following box. The I/O + // pointer is left at the start of the box's content. Returns the offset of the following box. + XMP_Uns64 GetBoxInfo ( LFA_FileRef fileRef, const XMP_Uns64 boxOffset, const XMP_Uns64 boxLimit, + BoxInfo * info, bool doSeek = true, bool throwErrors = false ); + +// XMP_Uns32 CountChildBoxes ( LFA_FileRef fileRef, const XMP_Uns64 childOffset, const XMP_Uns64 childLimit ); + +} // namespace ISO_Media + +// ================================================================================================= + +#endif // __ISOBaseMedia_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/MOOV_Support.cpp b/source/XMPFiles/FormatSupport/MOOV_Support.cpp new file mode 100644 index 0000000..1c89176 --- /dev/null +++ b/source/XMPFiles/FormatSupport/MOOV_Support.cpp @@ -0,0 +1,542 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "MOOV_Support.hpp" + +#include "ISOBaseMedia_Support.hpp" + +#include <string.h> + +// ================================================================================================= +/// \file MOOV_Support.cpp +/// \brief XMPFiles support for the 'moov' box in MPEG-4 and QuickTime files. +// ================================================================================================= + +// ================================================================================================= +// ================================================================================================= +// MOOV_Manager - The parsing and reading routines are all commmon +// ================================================================================================= +// ================================================================================================= + +#ifndef TraceParseMoovTree + #define TraceParseMoovTree 0 +#endif + +#ifndef TraceUpdateMoovTree + #define TraceUpdateMoovTree 0 +#endif + +// ================================================================================================= +// MOOV_Manager::PickContentPtr +// ============================ + +XMP_Uns8 * MOOV_Manager::PickContentPtr ( const BoxNode & node ) const +{ + if ( node.contentSize == 0 ) { + return 0; + } else if ( node.changed ) { + return (XMP_Uns8*) &node.changedContent[0]; + } else { + return (XMP_Uns8*) &this->fullSubtree[0] + node.offset + node.headerSize; + } +} // MOOV_Manager::PickContentPtr + +// ================================================================================================= +// MOOV_Manager::FillBoxInfo +// ========================= + +void MOOV_Manager::FillBoxInfo ( const BoxNode & node, BoxInfo * info ) const +{ + if ( info == 0 ) return; + + info->boxType = node.boxType; + info->childCount = (XMP_Uns32)node.children.size(); + info->contentSize = node.contentSize; + info->content = PickContentPtr ( node ); + +} // MOOV_Manager::FillBoxInfo + +// ================================================================================================= +// MOOV_Manager::GetBox +// ==================== +// +// Find a box given the type path. Pick the first child of each type. + +MOOV_Manager::BoxRef MOOV_Manager::GetBox ( const char * boxPath, BoxInfo * info ) const +{ + size_t pathLen = strlen(boxPath); + XMP_Assert ( (pathLen >= 4) && XMP_LitNMatch ( boxPath, "moov", 4 ) ); + if ( info != 0 ) memset ( info, 0, sizeof(BoxInfo) ); + + const char * pathPtr = boxPath + 5; // Skip the "moov/" portion. + const char * pathEnd = boxPath + pathLen; + + BoxRef currRef = &this->moovNode; + + while ( pathPtr < pathEnd ) { + + XMP_Assert ( (pathEnd - pathPtr) >= 4 ); + XMP_Uns32 boxType = GetUns32BE ( pathPtr ); + pathPtr += 5; // ! Don't care that the last step goes 1 too far. + + currRef = this->GetTypeChild ( currRef, boxType, 0 ); + if ( currRef == 0 ) return 0; + + } + + this->FillBoxInfo ( *((BoxNode*)currRef), info ); + return currRef; + +} // MOOV_Manager::GetBox + +// ================================================================================================= +// MOOV_Manager::GetNthChild +// ========================= + +MOOV_Manager::BoxRef MOOV_Manager::GetNthChild ( BoxRef parentRef, size_t childIndex, BoxInfo * info ) const +{ + XMP_Assert ( parentRef != 0 ); + const BoxNode & parent = *((BoxNode*)parentRef); + if ( info != 0 ) memset ( info, 0, sizeof(BoxInfo) ); + + if ( childIndex >= parent.children.size() ) return 0; + + const BoxNode & currNode = parent.children[childIndex]; + + this->FillBoxInfo ( currNode, info ); + return &currNode; + +} // MOOV_Manager::GetNthChild + +// ================================================================================================= +// MOOV_Manager::GetTypeChild +// ========================== + +MOOV_Manager::BoxRef MOOV_Manager::GetTypeChild ( BoxRef parentRef, XMP_Uns32 childType, BoxInfo * info ) const +{ + XMP_Assert ( parentRef != 0 ); + const BoxNode & parent = *((BoxNode*)parentRef); + if ( info != 0 ) memset ( info, 0, sizeof(BoxInfo) ); + if ( parent.children.empty() ) return 0; + + size_t i = 0, limit = parent.children.size(); + for ( ; i < limit; ++i ) { + const BoxNode & currNode = parent.children[i]; + if ( currNode.boxType == childType ) { + this->FillBoxInfo ( currNode, info ); + return &currNode; + } + } + + return 0; + +} // MOOV_Manager::GetTypeChild + +// ================================================================================================= +// MOOV_Manager::GetParsedOffset +// ============================= + +XMP_Uns32 MOOV_Manager::GetParsedOffset ( BoxRef ref ) const +{ + XMP_Assert ( ref != 0 ); + const BoxNode & node = *((BoxNode*)ref); + + if ( node.changed ) return 0; + return node.offset; + +} // MOOV_Manager::GetParsedOffset + +// ================================================================================================= +// MOOV_Manager::GetHeaderSize +// =========================== + +XMP_Uns32 MOOV_Manager::GetHeaderSize ( BoxRef ref ) const +{ + XMP_Assert ( ref != 0 ); + const BoxNode & node = *((BoxNode*)ref); + + if ( node.changed ) return 0; + return node.headerSize; + +} // MOOV_Manager::GetHeaderSize + +// ================================================================================================= +// MOOV_Manager::ParseMemoryTree +// ============================= +// +// Parse the fullSubtree data, building the BoxNode tree for the stuff that we care about. Tolerate +// errors like content ending too soon, make a best effoert to parse what we can. + +void MOOV_Manager::ParseMemoryTree ( XMP_Uns8 fileMode ) +{ + this->fileMode = fileMode; + + this->moovNode.offset = this->moovNode.boxType = 0; + this->moovNode.headerSize = this->moovNode.contentSize = 0; + this->moovNode.children.clear(); + this->moovNode.changedContent.clear(); + this->moovNode.changed = false; + + if ( this->fullSubtree.empty() ) return; + + ISOMedia::BoxInfo moovInfo; + const XMP_Uns8 * moovOrigin = &this->fullSubtree[0]; + const XMP_Uns8 * moovLimit = moovOrigin + this->fullSubtree.size(); + + (void) ISOMedia::GetBoxInfo ( moovOrigin, moovLimit, &moovInfo ); + XMP_Enforce ( moovInfo.boxType == ISOMedia::k_moov ); + + XMP_Uns64 fullMoovSize = moovInfo.headerSize + moovInfo.contentSize; + if ( fullMoovSize > moovBoxSizeLimit ) { // From here on we know 32-bit offsets are safe. + XMP_Throw ( "Oversize 'moov' box", kXMPErr_EnforceFailure ); + } + + this->moovNode.boxType = ISOMedia::k_moov; + this->moovNode.headerSize = moovInfo.headerSize; + this->moovNode.contentSize = (XMP_Uns32)moovInfo.contentSize; + + bool ignoreMetaBoxes = (fileMode == kFileIsTraditionalQT); // ! Don't want, these don't follow ISO spec. + #if TraceParseMoovTree + fprintf ( stderr, "Parsing 'moov' subtree, moovNode @ 0x%X, ignoreMetaBoxes = %d\n", + &this->moovNode, ignoreMetaBoxes ); + #endif + this->ParseNestedBoxes ( &this->moovNode, "moov", ignoreMetaBoxes ); + +} // MOOV_Manager::ParseMemoryTree + +// ================================================================================================= +// MOOV_Manager::ParseNestedBoxes +// ============================== +// +// Add the current level of child boxes to the parent node, recurse as appropriate. + +void MOOV_Manager::ParseNestedBoxes ( BoxNode * parentNode, const std::string & parentPath, bool ignoreMetaBoxes ) +{ + ISOMedia::BoxInfo isoInfo; + const XMP_Uns8 * moovOrigin = &this->fullSubtree[0]; + const XMP_Uns8 * childOrigin = moovOrigin + parentNode->offset + parentNode->headerSize; + const XMP_Uns8 * childLimit = childOrigin + parentNode->contentSize; + const XMP_Uns8 * nextChild; + + parentNode->contentSize = 0; // Exclude nested box size. + if ( parentNode->boxType == ISOMedia::k_meta ) { // ! The 'meta' box is a FullBox. + parentNode->contentSize = 4; + childOrigin += 4; + } + + for ( const XMP_Uns8 * currChild = childOrigin; currChild < childLimit; currChild = nextChild ) { + + nextChild = ISOMedia::GetBoxInfo ( currChild, childLimit, &isoInfo ); + if ( (isoInfo.boxType == 0) && + (isoInfo.headerSize < 8) && + (isoInfo.contentSize == 0) ) continue; // Skip trailing padding that QT sometimes writes. + + XMP_Uns32 childOffset = (XMP_Uns32) (currChild - moovOrigin); + parentNode->children.push_back ( BoxNode ( childOffset, isoInfo.boxType, isoInfo.headerSize, (XMP_Uns32)isoInfo.contentSize ) ); + BoxNode * newChild = &parentNode->children.back(); + + #if TraceParseMoovTree + size_t depth = (parentPath.size()+1) / 5; + for ( size_t i = 0; i < depth; ++i ) fprintf ( stderr, " " ); + XMP_Uns32 be32 = MakeUns32BE ( newChild->boxType ); + XMP_Uns32 addr32 = (XMP_Uns32) this->PickContentPtr ( *newChild ); + fprintf ( stderr, " Parsed %s/%.4s, offset 0x%X, size %d, content @ 0x%X, BoxNode @ 0x%X\n", + parentPath.c_str(), &be32, newChild->offset, newChild->contentSize, addr32, newChild ); + #endif + + const char * pathSuffix = 0; // Set to non-zero for boxes of interest. + char buffer[6]; buffer[0] = 0; + + switch ( isoInfo.boxType ) { // Want these boxes regardless of parent. + case ISOMedia::k_udta : pathSuffix = "/udta"; break; + case ISOMedia::k_meta : pathSuffix = "/meta"; break; + case ISOMedia::k_ilst : pathSuffix = "/ilst"; break; + case ISOMedia::k_trak : pathSuffix = "/trak"; break; + case ISOMedia::k_mdia : pathSuffix = "/mdia"; break; + case ISOMedia::k_minf : pathSuffix = "/minf"; break; + case ISOMedia::k_stbl : pathSuffix = "/stbl"; break; + } + if ( pathSuffix != 0 ) { + this->ParseNestedBoxes ( newChild, (parentPath + pathSuffix), ignoreMetaBoxes ); + } + + } + +} // MOOV_Manager::ParseNestedBoxes + +// ================================================================================================= +// MOOV_Manager::NoteChange +// ======================== + +void MOOV_Manager::NoteChange() +{ + + this->moovNode.changed = true; + +} // MOOV_Manager::NoteChange + +// ================================================================================================= +// MOOV_Manager::SetBox +// ==================== +// +// Save the new data, set this box's changed flag, and set the top changed flag. + +void MOOV_Manager::SetBox ( BoxRef theBox, const void* dataPtr, XMP_Uns32 size ) +{ + XMP_Enforce ( size < moovBoxSizeLimit ); + BoxNode * node = (BoxNode*)theBox; + + if ( node->contentSize == size ) { + + XMP_Uns8 * oldContent = PickContentPtr ( *node ); + if ( memcmp ( oldContent, dataPtr, size ) == 0 ) return; // No change. + memcpy ( oldContent, dataPtr, size ); // Update the old content in-place + this->moovNode.changed = true; + + #if TraceUpdateMoovTree + XMP_Uns32 be32 = MakeUns32BE ( node->boxType ); + fprintf ( stderr, "Updated '%.4s', parse offset 0x%X, same size\n", &be32, node->offset ); + #endif + + } else { + + node->changedContent.assign ( size, 0 ); // Fill with 0's first to get the storage. + memcpy ( &node->changedContent[0], dataPtr, size ); + node->contentSize = size; + node->changed = true; + this->moovNode.changed = true; + + #if TraceUpdateMoovTree + XMP_Uns32 be32 = MakeUns32BE ( node->boxType ); + XMP_Uns32 addr32 = (XMP_Uns32) this->PickContentPtr ( *node ); + fprintf ( stderr, "Updated '%.4s', parse offset 0x%X, new size %d, new content @ 0x%X\n", + &be32, node->offset, node->contentSize, addr32 ); + #endif + + } + +} // MOOV_Manager::SetBox + +// ================================================================================================= +// MOOV_Manager::SetBox +// ==================== +// +// Like above, but create the path to the box if necessary. + +void MOOV_Manager::SetBox ( const char * boxPath, const void* dataPtr, XMP_Uns32 size ) +{ + XMP_Enforce ( size < moovBoxSizeLimit ); + + size_t pathLen = strlen(boxPath); + XMP_Assert ( (pathLen >= 4) && XMP_LitNMatch ( boxPath, "moov", 4 ) ); + + const char * pathPtr = boxPath + 5; // Skip the "moov/" portion. + const char * pathEnd = boxPath + pathLen; + + BoxRef parentRef = 0; + BoxRef currRef = &this->moovNode; + + while ( pathPtr < pathEnd ) { + + XMP_Assert ( (pathEnd - pathPtr) >= 4 ); + XMP_Uns32 boxType = GetUns32BE ( pathPtr ); + pathPtr += 5; // ! Don't care that the last step goes 1 too far. + + parentRef = currRef; + currRef = this->GetTypeChild ( parentRef, boxType, 0 ); + if ( currRef == 0 ) currRef = this->AddChildBox ( parentRef, boxType, 0, 0 ); + + } + + this->SetBox ( currRef, dataPtr, size ); + +} // MOOV_Manager::SetBox + +// ================================================================================================= +// MOOV_Manager::AddChildBox +// ========================= + +MOOV_Manager::BoxRef MOOV_Manager::AddChildBox ( BoxRef parentRef, XMP_Uns32 childType, const void* dataPtr, XMP_Uns32 size ) +{ + BoxNode * parent = (BoxNode*)parentRef; + XMP_Assert ( parent != 0 ); + + parent->children.push_back ( BoxNode ( 0, childType, 0, 0 ) ); + BoxNode * newNode = &parent->children.back(); + this->SetBox ( newNode, dataPtr, size ); + + return newNode; + +} // MOOV_Manager::AddChildBox + +// ================================================================================================= +// MOOV_Manager::DeleteNthChild +// ============================ + +bool MOOV_Manager::DeleteNthChild ( BoxRef parentRef, size_t childIndex ) +{ + BoxNode * parent = (BoxNode*)parentRef; + + if ( childIndex >= parent->children.size() ) return false; + + parent->children.erase ( parent->children.begin() + childIndex ); + return true; + +} // MOOV_Manager::DeleteNthChild + +// ================================================================================================= +// MOOV_Manager::DeleteTypeChild +// ============================= + +bool MOOV_Manager::DeleteTypeChild ( BoxRef parentRef, XMP_Uns32 childType ) +{ + BoxNode * parent = (BoxNode*)parentRef; + + BoxListPos child = parent->children.begin(); + BoxListPos limit = parent->children.end(); + + for ( ; child != limit; ++child ) { + if ( child->boxType == childType ) { + parent->children.erase ( child ); + this->moovNode.changed = true; + return true; + } + } + + return false; + +} // MOOV_Manager::DeleteTypeChild + +// ================================================================================================= +// MOOV_Manager::NewSubtreeSize +// ============================ +// +// Determine the new (changed) size of a subtree. Ignore 'free' and 'wide' boxes. + +XMP_Uns32 MOOV_Manager::NewSubtreeSize ( const BoxNode & node, const std::string & parentPath ) +{ + XMP_Uns32 subtreeSize = 8 + node.contentSize; // All boxes will have 8 byte headers. + + if ( (node.boxType == ISOMedia::k_free) || (node.boxType == ISOMedia::k_wide) ) { + } + + for ( size_t i = 0, limit = node.children.size(); i < limit; ++i ) { + + char suffix[6]; + suffix[0] = '/'; + PutUns32BE ( node.boxType, &suffix[1] ); + suffix[5] = 0; + std::string nodePath = parentPath + suffix; + + subtreeSize += this->NewSubtreeSize ( node.children[i], nodePath ); + XMP_Enforce ( subtreeSize < moovBoxSizeLimit ); + + } + + return subtreeSize; + +} // MOOV_Manager::NewSubtreeSize + +// ================================================================================================= +// MOOV_Manager::AppendNewSubtree +// ============================== +// +// Append this node's header, content, and children. Because the 'meta' box is a FullBox with nested +// boxes, there can be both content and children. Ignore 'free' and 'wide' boxes. + +#define IncrNewPtr(count) { newPtr += count; XMP_Enforce ( newPtr <= newEnd ); } + +#if TraceUpdateMoovTree + static XMP_Uns8 * newOrigin; +#endif + +XMP_Uns8 * MOOV_Manager::AppendNewSubtree ( const BoxNode & node, const std::string & parentPath, + XMP_Uns8 * newPtr, XMP_Uns8 * newEnd ) +{ + if ( (node.boxType == ISOMedia::k_free) || (node.boxType == ISOMedia::k_wide) ) { + } + + XMP_Assert ( (node.boxType != ISOMedia::k_meta) ? (node.children.empty() || (node.contentSize == 0)) : + (node.children.empty() || (node.contentSize == 4)) ); + + XMP_Enforce ( (XMP_Uns32)(newEnd - newPtr) >= (8 + node.contentSize) ); + + #if TraceUpdateMoovTree + XMP_Uns32 be32 = MakeUns32BE ( node.boxType ); + XMP_Uns32 newOffset = (XMP_Uns32) (newPtr - newOrigin); + XMP_Uns32 addr32 = (XMP_Uns32) this->PickContentPtr ( node ); + fprintf ( stderr, " Appending %s/%.4s @ 0x%X, size %d, content @ 0x%X\n", + parentPath.c_str(), &be32, newOffset, node.contentSize, addr32 ); + #endif + + // Leave the size as 0 for now, append the type and content. + + XMP_Uns8 * boxOrigin = newPtr; // Save origin to fill in the final size. + PutUns32BE ( node.boxType, (newPtr + 4) ); + IncrNewPtr ( 8 ); + + if ( node.contentSize != 0 ) { + const XMP_Uns8 * content = PickContentPtr( node ); + memcpy ( newPtr, content, node.contentSize ); + IncrNewPtr ( node.contentSize ); + } + + // Append the nested boxes. + + if ( ! node.children.empty() ) { + + char suffix[6]; + suffix[0] = '/'; + PutUns32BE ( node.boxType, &suffix[1] ); + suffix[5] = 0; + std::string nodePath = parentPath + suffix; + + for ( size_t i = 0, limit = node.children.size(); i < limit; ++i ) { + newPtr = this->AppendNewSubtree ( node.children[i], nodePath, newPtr, newEnd ); + } + + } + + // Fill in the final size. + + PutUns32BE ( (XMP_Uns32)(newPtr - boxOrigin), boxOrigin ); + + return newPtr; + +} // MOOV_Manager::AppendNewSubtree + +// ================================================================================================= +// MOOV_Manager::UpdateMemoryTree +// ============================== + +void MOOV_Manager::UpdateMemoryTree() +{ + if ( ! this->IsChanged() ) return; + + XMP_Uns32 newSize = this->NewSubtreeSize ( this->moovNode, "" ); + XMP_Enforce ( newSize < moovBoxSizeLimit ); + + RawDataBlock newData; + newData.assign ( newSize, 0 ); // Prefill with zeroes, can't append multiple items to a vector. + + XMP_Uns8 * newPtr = &newData[0]; + XMP_Uns8 * newEnd = newPtr + newSize; + + #if TraceUpdateMoovTree + fprintf ( stderr, "Starting MOOV_Manager::UpdateMemoryTree\n" ); + newOrigin = newPtr; + #endif + + XMP_Uns8 * trueEnd = this->AppendNewSubtree ( this->moovNode, "", newPtr, newEnd ); + XMP_Enforce ( trueEnd == newEnd ); + + this->fullSubtree.swap ( newData ); + this->ParseMemoryTree ( this->fileMode ); + +} // MOOV_Manager::UpdateMemoryTree diff --git a/source/XMPFiles/FormatSupport/MOOV_Support.hpp b/source/XMPFiles/FormatSupport/MOOV_Support.hpp new file mode 100644 index 0000000..3e19a9d --- /dev/null +++ b/source/XMPFiles/FormatSupport/MOOV_Support.hpp @@ -0,0 +1,215 @@ +#ifndef __MOOV_Support_hpp__ +#define __MOOV_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMP_Environment.h" // ! This must be the first include. + +#include <vector> + +#include "XMP_Const.h" +#include "XMPFiles_Impl.hpp" +#include "EndianUtils.hpp" + +typedef vector<XMP_Uns8> RawDataBlock; + +#define moovBoxSizeLimit 100*1024*1024 + +// ================================================================================================= +// MOOV_Manager +// ============ + +class MOOV_Manager { +public: + + // --------------------------------------------------------------------------------------------- + // Types and constants + + enum { // Values for fileMode. + kFileIsNormalISO = 0, // A "normal" MPEG-4 file, no 'qt ' compatible brand. + kFileIsModernQT = 1, // Has an 'ftyp' box and 'qt ' compatible brand. + kFileIsTraditionalQT = 2 // Old QuickTime, no 'ftyp' box. + }; + + typedef const void * BoxRef; // Valid until a sibling or higher box is added or deleted. + + struct BoxInfo { + XMP_Uns32 boxType; // In memory as native endian, compares work with ISOMedia::k_* constants. + XMP_Uns32 childCount; // ! A 'meta' box has both content (version/flags) and children! + XMP_Uns32 contentSize; // Does not include the size of nested boxes. + const XMP_Uns8 * content; // Null if contentSize is zero. + BoxInfo() : boxType(0), childCount(0), contentSize(0), content(0) {}; + }; + + // --------------------------------------------------------------------------------------------- + // GetBox - Pick a box given a '/' separated list of box types. Picks the 1st of each type. + // GetNthChild - Pick the overall n-th child of the parent, zero based. + // GetTypeChild - Pick the first child of the given type. + // GetParsedOffset - Get the box's offset in the parsed tree, 0 if changed since parsing. + // GetHeaderSize - Get the box's header size in the parsed tree, 0 if changed since parsing. + + BoxRef GetBox ( const char * boxPath, BoxInfo * info ) const; + + BoxRef GetNthChild ( BoxRef parentRef, size_t childIndex, BoxInfo * info ) const; + BoxRef GetTypeChild ( BoxRef parentRef, XMP_Uns32 childType, BoxInfo * info ) const; + + XMP_Uns32 GetParsedOffset ( BoxRef ref ) const; + XMP_Uns32 GetHeaderSize ( BoxRef ref ) const; + + // --------------------------------------------------------------------------------------------- + // NoteChange - Note overall change, value was directly replaced. + // SetBox(ref) - Replace the content with a copy of the given data. + // SetBox(path) - Like above, but creating path to the box if necessary. + // AddChildBox - Add a child of the given type, using a copy of the given data (may be null) + + void NoteChange(); + + void SetBox ( BoxRef theBox, const void* dataPtr, XMP_Uns32 size ); + void SetBox ( const char * boxPath, const void* dataPtr, XMP_Uns32 size ); + + BoxRef AddChildBox ( BoxRef parentRef, XMP_Uns32 childType, const void * dataPtr, XMP_Uns32 size ); + + // --------------------------------------------------------------------------------------------- + // DeleteNthChild - Delete the overall n-th child, return true if there was one. + // DeleteTypeChild - Delete the first child of the given type, return true if there was one. + + bool DeleteNthChild ( BoxRef parentRef, size_t childIndex ); + bool DeleteTypeChild ( BoxRef parentRef, XMP_Uns32 childType ); + + // --------------------------------------------------------------------------------------------- + + bool IsChanged() const { return this->moovNode.changed; }; + + // --------------------------------------------------------------------------------------------- + // The client is expected to fill in fullSubtree before calling ParseMemoryTree, and directly + // use fullSubtree after calling UpdateMemoryTree. + // + // IMPORTANT: We only support cases where the 'moov' subtree is significantly less than 4 GB, in + // particular with a threshold of probably 100 MB. This has 2 big impacts: we can safely use + // 32-bit offsets and sizes, and comfortably assume everything will fit in available heap space. + + RawDataBlock fullSubtree; // The entire 'moov' box, straight from the file or from UpdateMemoryTree. + + void ParseMemoryTree ( XMP_Uns8 fileMode ); + void UpdateMemoryTree(); + + // --------------------------------------------------------------------------------------------- + + #pragma pack (1) // ! These must match the file layout! + + struct Content_mvhd_0 { + XMP_Uns32 vFlags; // 0 + XMP_Uns32 creationTime; // 4 + XMP_Uns32 modificationTime; // 8 + XMP_Uns32 timescale; // 12 + XMP_Uns32 duration; // 16 + XMP_Int32 rate; // 20 + XMP_Int16 volume; // 24 + XMP_Uns16 pad_1; // 26 + XMP_Uns32 pad_2, pad_3; // 28 + XMP_Int32 matrix [9]; // 36 + XMP_Uns32 preDef [6]; // 72 + XMP_Uns32 nextTrackID; // 96 + }; // 100 + + struct Content_mvhd_1 { + XMP_Uns32 vFlags; // 0 + XMP_Uns64 creationTime; // 4 + XMP_Uns64 modificationTime; // 12 + XMP_Uns32 timescale; // 20 + XMP_Uns64 duration; // 24 + XMP_Int32 rate; // 32 + XMP_Int16 volume; // 36 + XMP_Uns16 pad_1; // 38 + XMP_Uns32 pad_2, pad_3; // 40 + XMP_Int32 matrix [9]; // 48 + XMP_Uns32 preDef [6]; // 84 + XMP_Uns32 nextTrackID; // 108 + }; // 112 + + struct Content_hdlr { // An 'hdlr' box as defined by ISO 14496-12. Maps OK to the QuickTime box. + XMP_Uns32 versionFlags; // 0 + XMP_Uns32 preDef; // 4 + XMP_Uns32 handlerType; // 8 + XMP_Uns32 reserved [3]; // 12 + // Plus optional component name string, null terminated UTF-8. + }; // 24 + + struct Content_stsd_entry { + XMP_Uns32 entrySize; // 0 + XMP_Uns32 format; // 4 + XMP_Uns8 reserved_1 [6]; // 8 + XMP_Uns16 dataRefIndex; // 14 + XMP_Uns32 reserved_2; // 16 + XMP_Uns32 flags; // 20 + XMP_Uns32 timeScale; // 24 + XMP_Uns32 frameDuration; // 28 + XMP_Uns8 frameCount; // 32 + XMP_Uns8 reserved_3; // 33 + // Plus optional trailing ISO boxes. + }; // 34 + + struct Content_stsc_entry { + XMP_Uns32 firstChunkNumber; // 0 + XMP_Uns32 samplesPerChunk; // 4 + XMP_Uns32 sampleDescrID; // 8 + }; // 12 + + // --------------------------------------------------------------------------------------------- + + MOOV_Manager() : fileMode(0) + { + XMP_Assert ( sizeof ( Content_mvhd_0 ) == 100 ); // Make sure the structs really are packed. + XMP_Assert ( sizeof ( Content_mvhd_1 ) == 112 ); + XMP_Assert ( sizeof ( Content_hdlr ) == 24 ); + XMP_Assert ( sizeof ( Content_stsd_entry ) == 34 ); + XMP_Assert ( sizeof ( Content_stsc_entry ) == 12 ); + }; + + virtual ~MOOV_Manager() {}; + +private: + + struct BoxNode; + typedef std::vector<BoxNode> BoxList; + typedef BoxList::iterator BoxListPos; + + struct BoxNode { + // ! Don't have a parent link, it will get destroyed by vector growth! + + XMP_Uns32 offset; // The offset in the fullSubtree, 0 if not in the parse. + XMP_Uns32 boxType; + XMP_Uns32 headerSize; // The actual header size in the fullSubtree, 0 if not in the parse. + XMP_Uns32 contentSize; // The current content size, does not include nested boxes. + BoxList children; + RawDataBlock changedContent; // Might be empty even if changed is true. + bool changed; // If true, the content is in changedContent, else in fullSubtree. + + BoxNode() : offset(0), boxType(0), headerSize(0), contentSize(0), changed(false) {}; + BoxNode ( XMP_Uns32 _offset, XMP_Uns32 _boxType, XMP_Uns32 _headerSize, XMP_Uns32 _contentSize ) + : offset(_offset), boxType(_boxType), headerSize(_headerSize), contentSize(_contentSize), changed(false) {}; + + }; + + XMP_Uns8 fileMode; + BoxNode moovNode; + + void ParseNestedBoxes ( BoxNode * parentNode, const std::string & parentPath, bool ignoreMetaBoxes ); + + XMP_Uns8 * PickContentPtr ( const BoxNode & node ) const; + void FillBoxInfo ( const BoxNode & node, BoxInfo * info ) const; + + XMP_Uns32 NewSubtreeSize ( const BoxNode & node, const std::string & parentPath ); + XMP_Uns8 * AppendNewSubtree ( const BoxNode & node, const std::string & parentPath, + XMP_Uns8 * newPtr, XMP_Uns8 * newEnd ); + +}; // MOOV_Manager + +#endif // __MOOV_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/MacScriptExtracts.h b/source/XMPFiles/FormatSupport/MacScriptExtracts.h new file mode 100644 index 0000000..9856183 --- /dev/null +++ b/source/XMPFiles/FormatSupport/MacScriptExtracts.h @@ -0,0 +1,244 @@ +#ifndef __MacScriptExtracts__ +#define __MacScriptExtracts__ + +// Extracts of script (smXyz) and language (langXyz) enums from Apple's old Script.h. +// These are used to support "traditional" QuickTime metadata processing. + +/* + Script codes: + These specify a Mac OS encoding that is related to a FOND ID range. + Some of the encodings have several variants (e.g. for different localized systems) + which all share the same script code. + Not all of these script codes are currently supported by Apple software. + Notes: + - Script code 0 (smRoman) is also used (instead of smGreek) for the Greek encoding + in the Greek localized system. + - Script code 28 (smEthiopic) is also used for the Inuit encoding in the Inuktitut + system. +*/ +enum { + smRoman = 0, + smJapanese = 1, + smTradChinese = 2, /* Traditional Chinese*/ + smKorean = 3, + smArabic = 4, + smHebrew = 5, + smGreek = 6, + smCyrillic = 7, + smRSymbol = 8, /* Right-left symbol*/ + smDevanagari = 9, + smGurmukhi = 10, + smGujarati = 11, + smOriya = 12, + smBengali = 13, + smTamil = 14, + smTelugu = 15, + smKannada = 16, /* Kannada/Kanarese*/ + smMalayalam = 17, + smSinhalese = 18, + smBurmese = 19, + smKhmer = 20, /* Khmer/Cambodian*/ + smThai = 21, + smLao = 22, + smGeorgian = 23, + smArmenian = 24, + smSimpChinese = 25, /* Simplified Chinese*/ + smTibetan = 26, + smMongolian = 27, + smEthiopic = 28, + smGeez = 28, /* Synonym for smEthiopic*/ + smCentralEuroRoman = 29, /* For Czech, Slovak, Polish, Hungarian, Baltic langs*/ + smVietnamese = 30, + smExtArabic = 31, /* extended Arabic*/ + smUninterp = 32 /* uninterpreted symbols, e.g. palette symbols*/ +}; + +/* Extended script code for full Unicode input*/ +enum { + smUnicodeScript = 0x7E +}; + +/* Obsolete script code names (kept for backward compatibility):*/ +enum { + smChinese = 2, /* (Use smTradChinese or smSimpChinese)*/ + smRussian = 7, /* Use smCyrillic*/ + /* smMaldivian = 25: deleted, no code for Maldivian*/ + smLaotian = 22, /* Use smLao */ + smAmharic = 28, /* Use smEthiopic or smGeez*/ + smSlavic = 29, /* Use smCentralEuroRoman*/ + smEastEurRoman = 29, /* Use smCentralEuroRoman*/ + smSindhi = 31, /* Use smExtArabic*/ + smKlingon = 32 +}; + +/* + Language codes: + These specify a language implemented using a particular Mac OS encoding. + Not all of these language codes are currently supported by Apple software. +*/ +enum { + langEnglish = 0, /* smRoman script*/ + langFrench = 1, /* smRoman script*/ + langGerman = 2, /* smRoman script*/ + langItalian = 3, /* smRoman script*/ + langDutch = 4, /* smRoman script*/ + langSwedish = 5, /* smRoman script*/ + langSpanish = 6, /* smRoman script*/ + langDanish = 7, /* smRoman script*/ + langPortuguese = 8, /* smRoman script*/ + langNorwegian = 9, /* (Bokmal) smRoman script*/ + langHebrew = 10, /* smHebrew script*/ + langJapanese = 11, /* smJapanese script*/ + langArabic = 12, /* smArabic script*/ + langFinnish = 13, /* smRoman script*/ + langGreek = 14, /* Greek script (monotonic) using smRoman script code*/ + langIcelandic = 15, /* modified smRoman/Icelandic script*/ + langMaltese = 16, /* Roman script*/ + langTurkish = 17, /* modified smRoman/Turkish script*/ + langCroatian = 18, /* modified smRoman/Croatian script*/ + langTradChinese = 19, /* Chinese (Mandarin) in traditional characters*/ + langUrdu = 20, /* smArabic script*/ + langHindi = 21, /* smDevanagari script*/ + langThai = 22, /* smThai script*/ + langKorean = 23 /* smKorean script*/ +}; + +enum { + langLithuanian = 24, /* smCentralEuroRoman script*/ + langPolish = 25, /* smCentralEuroRoman script*/ + langHungarian = 26, /* smCentralEuroRoman script*/ + langEstonian = 27, /* smCentralEuroRoman script*/ + langLatvian = 28, /* smCentralEuroRoman script*/ + langSami = 29, /* language of the Sami people of N. Scandinavia */ + langFaroese = 30, /* modified smRoman/Icelandic script */ + langFarsi = 31, /* modified smArabic/Farsi script*/ + langPersian = 31, /* Synonym for langFarsi*/ + langRussian = 32, /* smCyrillic script*/ + langSimpChinese = 33, /* Chinese (Mandarin) in simplified characters*/ + langFlemish = 34, /* smRoman script*/ + langIrishGaelic = 35, /* smRoman or modified smRoman/Celtic script (without dot above) */ + langAlbanian = 36, /* smRoman script*/ + langRomanian = 37, /* modified smRoman/Romanian script*/ + langCzech = 38, /* smCentralEuroRoman script*/ + langSlovak = 39, /* smCentralEuroRoman script*/ + langSlovenian = 40, /* modified smRoman/Croatian script*/ + langYiddish = 41, /* smHebrew script*/ + langSerbian = 42, /* smCyrillic script*/ + langMacedonian = 43, /* smCyrillic script*/ + langBulgarian = 44, /* smCyrillic script*/ + langUkrainian = 45, /* modified smCyrillic/Ukrainian script*/ + langByelorussian = 46, /* smCyrillic script*/ + langBelorussian = 46 /* Synonym for langByelorussian */ +}; + +enum { + langUzbek = 47, /* Cyrillic script*/ + langKazakh = 48, /* Cyrillic script*/ + langAzerbaijani = 49, /* Azerbaijani in Cyrillic script*/ + langAzerbaijanAr = 50, /* Azerbaijani in Arabic script*/ + langArmenian = 51, /* smArmenian script*/ + langGeorgian = 52, /* smGeorgian script*/ + langMoldavian = 53, /* smCyrillic script*/ + langKirghiz = 54, /* Cyrillic script*/ + langTajiki = 55, /* Cyrillic script*/ + langTurkmen = 56, /* Cyrillic script*/ + langMongolian = 57, /* Mongolian in smMongolian script*/ + langMongolianCyr = 58, /* Mongolian in Cyrillic script*/ + langPashto = 59, /* Arabic script*/ + langKurdish = 60, /* smArabic script*/ + langKashmiri = 61, /* Arabic script*/ + langSindhi = 62, /* Arabic script*/ + langTibetan = 63, /* smTibetan script*/ + langNepali = 64, /* smDevanagari script*/ + langSanskrit = 65, /* smDevanagari script*/ + langMarathi = 66, /* smDevanagari script*/ + langBengali = 67, /* smBengali script*/ + langAssamese = 68, /* smBengali script*/ + langGujarati = 69, /* smGujarati script*/ + langPunjabi = 70 /* smGurmukhi script*/ +}; + +enum { + langOriya = 71, /* smOriya script*/ + langMalayalam = 72, /* smMalayalam script*/ + langKannada = 73, /* smKannada script*/ + langTamil = 74, /* smTamil script*/ + langTelugu = 75, /* smTelugu script*/ + langSinhalese = 76, /* smSinhalese script*/ + langBurmese = 77, /* smBurmese script*/ + langKhmer = 78, /* smKhmer script*/ + langLao = 79, /* smLao script*/ + langVietnamese = 80, /* smVietnamese script*/ + langIndonesian = 81, /* smRoman script*/ + langTagalog = 82, /* Roman script*/ + langMalayRoman = 83, /* Malay in smRoman script*/ + langMalayArabic = 84, /* Malay in Arabic script*/ + langAmharic = 85, /* smEthiopic script*/ + langTigrinya = 86, /* smEthiopic script*/ + langOromo = 87, /* smEthiopic script*/ + langSomali = 88, /* smRoman script*/ + langSwahili = 89, /* smRoman script*/ + langKinyarwanda = 90, /* smRoman script*/ + langRuanda = 90, /* synonym for langKinyarwanda*/ + langRundi = 91, /* smRoman script*/ + langNyanja = 92, /* smRoman script*/ + langChewa = 92, /* synonym for langNyanja*/ + langMalagasy = 93, /* smRoman script*/ + langEsperanto = 94 /* Roman script*/ +}; + +enum { + langWelsh = 128, /* modified smRoman/Celtic script*/ + langBasque = 129, /* smRoman script*/ + langCatalan = 130, /* smRoman script*/ + langLatin = 131, /* smRoman script*/ + langQuechua = 132, /* smRoman script*/ + langGuarani = 133, /* smRoman script*/ + langAymara = 134, /* smRoman script*/ + langTatar = 135, /* Cyrillic script*/ + langUighur = 136, /* Arabic script*/ + langDzongkha = 137, /* (lang of Bhutan) smTibetan script*/ + langJavaneseRom = 138, /* Javanese in smRoman script*/ + langSundaneseRom = 139, /* Sundanese in smRoman script*/ + langGalician = 140, /* smRoman script*/ + langAfrikaans = 141 /* smRoman script */ +}; + +enum { + langBreton = 142, /* smRoman or modified smRoman/Celtic script */ + langInuktitut = 143, /* Inuit script using smEthiopic script code */ + langScottishGaelic = 144, /* smRoman or modified smRoman/Celtic script */ + langManxGaelic = 145, /* smRoman or modified smRoman/Celtic script */ + langIrishGaelicScript = 146, /* modified smRoman/Gaelic script (using dot above) */ + langTongan = 147, /* smRoman script */ + langGreekAncient = 148, /* Classical Greek, polytonic orthography */ + langGreenlandic = 149, /* smRoman script */ + langAzerbaijanRoman = 150, /* Azerbaijani in Roman script */ + langNynorsk = 151 /* Norwegian Nyorsk in smRoman*/ +}; + +enum { + langUnspecified = 32767 /* Special code for use in resources (such as 'itlm') */ +}; + +/* + Obsolete language code names (kept for backward compatibility): + Misspelled, ambiguous, misleading, considered pejorative, archaic, etc. +*/ +enum { + langPortugese = 8, /* Use langPortuguese*/ + langMalta = 16, /* Use langMaltese*/ + langYugoslavian = 18, /* (use langCroatian, langSerbian, etc.)*/ + langChinese = 19, /* (use langTradChinese or langSimpChinese)*/ + langLettish = 28, /* Use langLatvian */ + langLapponian = 29, /* Use langSami*/ + langLappish = 29, /* Use langSami*/ + langSaamisk = 29, /* Use langSami */ + langFaeroese = 30, /* Use langFaroese */ + langIrish = 35, /* Use langIrishGaelic */ + langGalla = 87, /* Use langOromo */ + langAfricaans = 141, /* Use langAfrikaans */ + langGreekPoly = 148 /* Use langGreekAncient*/ +}; + +#endif /* __MacScriptExtracts__ */ diff --git a/source/XMPFiles/FormatSupport/PNG_Support.cpp b/source/XMPFiles/FormatSupport/PNG_Support.cpp index f074729..988edda 100644 --- a/source/XMPFiles/FormatSupport/PNG_Support.cpp +++ b/source/XMPFiles/FormatSupport/PNG_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/PNG_Support.hpp b/source/XMPFiles/FormatSupport/PNG_Support.hpp index 1a5aaae..b10f899 100644 --- a/source/XMPFiles/FormatSupport/PNG_Support.hpp +++ b/source/XMPFiles/FormatSupport/PNG_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp b/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp index f8e6290..0e57b49 100644 --- a/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp +++ b/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -20,26 +20,15 @@ // ================================================================================================= // IsMetadataImgRsrc // ================= -// -// The only image resources of possible interest as metadata have type '8BIM' and IDs: -// 1008, 1020, 1028, 1034, 1035, 1036, 1058, 1060, 1061 static inline bool IsMetadataImgRsrc ( XMP_Uns16 id ) { - - if ( (id < 1008) || (id > 1061) ) { - return false; - } else if ( id >= 1058 ) { - if ( id == 1059 ) return false; - } else if ( id > 1036 ) { - return false; - } else if ( id > 1028 ) { - if ( id < 1034 ) return false; - } else if ( id < 1028 ) { - if ( (id != 1008) && (id != 1020) ) return false; - } - - return true; + if ( id == 0 ) return false; + + int i; + for ( i = 0; id < kPSIR_MetadataIDs[i]; ++i ) {} + if ( id == kPSIR_MetadataIDs[i] ) return true; + return false; } // IsMetadataImgRsrc @@ -97,9 +86,9 @@ bool PSIR_FileWriter::GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const { InternalRsrcMap::const_iterator rsrcPos = this->imgRsrcs.find ( id ); if ( rsrcPos == this->imgRsrcs.end() ) return false; - + const InternalRsrcInfo & rsrcInfo = rsrcPos->second; - + if ( info != 0 ) { info->id = rsrcInfo.id; info->dataLen = rsrcInfo.dataLen; @@ -108,7 +97,7 @@ bool PSIR_FileWriter::GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const } return true; - + } // PSIR_FileWriter::GetImgRsrc // ================================================================================================= @@ -126,7 +115,7 @@ void PSIR_FileWriter::SetImgRsrc ( XMP_Uns16 id, const void* clientPtr, XMP_Uns3 InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, length, this->fileParsed ) ); rsrcPos = this->imgRsrcs.insert ( rsrcPos, mapValue ); rsrcPtr = &rsrcPos->second; - + } else { rsrcPtr = &rsrcPos->second; @@ -136,12 +125,12 @@ void PSIR_FileWriter::SetImgRsrc ( XMP_Uns16 id, const void* clientPtr, XMP_Uns3 (memcmp ( rsrcPtr->dataPtr, clientPtr, length ) == 0) ) { return; } - + rsrcPtr->FreeData(); // Release any existing data allocation. rsrcPtr->dataLen = length; // And this might be changing. } - + rsrcPtr->changed = true; rsrcPtr->dataPtr = malloc ( length ); if ( rsrcPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); @@ -159,7 +148,7 @@ void PSIR_FileWriter::DeleteImgRsrc ( XMP_Uns16 id ) { InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id ); if ( rsrcPos == this->imgRsrcs.end() ) return; // Nothing to delete. - + this->imgRsrcs.erase ( id ); this->changed = true; if ( id != kPSIR_XMP ) this->legacyDeleted = true; @@ -175,7 +164,7 @@ bool PSIR_FileWriter::IsLegacyChanged() if ( ! this->changed ) return false; if ( this->legacyDeleted ) return true; - + InternalRsrcMap::iterator irPos = this->imgRsrcs.begin(); InternalRsrcMap::iterator irEnd = this->imgRsrcs.end(); @@ -185,7 +174,7 @@ bool PSIR_FileWriter::IsLegacyChanged() } return false; // Can get here if the XMP is the only thing changed. - + } // PSIR_FileWriter::IsLegacyChanged // ================================================================================================= @@ -199,7 +188,7 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, if ( length == 0 ) return; // Allocate space for the full in-memory data and copy it. - + if ( ! copyData ) { this->memContent = (XMP_Uns8*) data; XMP_Assert ( ! this->ownedContent ); @@ -211,15 +200,15 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, this->ownedContent = true; } this->memLength = length; - + // Capture the info for all of the resources. - + XMP_Uns8* psirPtr = this->memContent; XMP_Uns8* psirEnd = psirPtr + length; XMP_Uns8* psirLimit = psirEnd - kMinImgRsrcSize; - + while ( psirPtr <= psirLimit ) { - + XMP_Uns8* origin = psirPtr; // The beginning of this resource. XMP_Uns32 type = GetUns32BE(psirPtr); XMP_Uns16 id = GetUns16BE(psirPtr+4); @@ -228,7 +217,7 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, XMP_Uns8* namePtr = psirPtr; XMP_Uns16 nameLen = namePtr[0]; // ! The length for the Pascal string, w/ room for "+2". psirPtr += ((nameLen + 2) & 0xFFFE); // ! Round up to an even offset. Yes, +2! - + if ( psirPtr > psirEnd-4 ) break; // Bad image resource. Throw instead? XMP_Uns32 dataLen = GetUns32BE(psirPtr); @@ -236,9 +225,9 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, XMP_Uns32 dataOffset = (XMP_Uns32) ( psirPtr - this->memContent ); XMP_Uns8* nextRsrc = psirPtr + ((dataLen + 1) & 0xFFFFFFFEUL); // ! Round up to an even offset. - + if ( (dataLen > length) || (psirPtr > psirEnd-dataLen) ) break; // Bad image resource. Throw instead? - + if ( type == k8BIM ) { InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, dataLen, kIsMemoryBased ) ); InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.insert ( this->imgRsrcs.end(), mapValue ); @@ -252,9 +241,9 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, XMP_Assert ( (rsrcLength & 1) == 0 ); this->otherRsrcs.push_back ( OtherRsrcInfo ( rsrcOffset, rsrcLength ) ); } - + psirPtr = nextRsrc; - + } } // PSIR_FileWriter::ParseMemoryResources @@ -266,28 +255,28 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length ) { bool ok; - + this->DeleteExistingInfo(); this->fileParsed = true; if ( length == 0 ) return; - + // Parse the image resource block. IOBuffer ioBuf; ioBuf.filePos = LFA_Seek ( fileRef, 0, SEEK_CUR ); - + XMP_Int64 psirOrigin = ioBuf.filePos; // Need this to determine the resource data offsets. XMP_Int64 fileEnd = ioBuf.filePos + length; - + std::string rsrcPName; - + while ( (ioBuf.filePos + (ioBuf.ptr - ioBuf.data)) < fileEnd ) { - + ok = CheckFileSpace ( fileRef, &ioBuf, 12 ); // The minimal image resource takes 12 bytes. if ( ! ok ) break; // Bad image resource. Throw instead? XMP_Int64 thisRsrcPos = ioBuf.filePos + (ioBuf.ptr - ioBuf.data); - + XMP_Uns32 type = GetUns32BE(ioBuf.ptr); XMP_Uns16 id = GetUns16BE(ioBuf.ptr+4); ioBuf.ptr += 6; // Advance to the resource name. @@ -296,7 +285,7 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length XMP_Uns16 paddedLen = (nameLen + 2) & 0xFFFE; // ! Round up to an even total. Yes, +2! ok = CheckFileSpace ( fileRef, &ioBuf, paddedLen+4 ); // Get the name text and the data length. if ( ! ok ) break; // Bad image resource. Throw instead? - + if ( nameLen > 0 ) rsrcPName.assign ( (char*)(ioBuf.ptr), paddedLen ); // ! Include the length byte and pad. ioBuf.ptr += paddedLen; // Move to the data length. @@ -317,7 +306,7 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, dataLen, kIsFileBased ) ); InternalRsrcMap::iterator newRsrc = this->imgRsrcs.insert ( this->imgRsrcs.end(), mapValue ); InternalRsrcInfo* rsrcPtr = &newRsrc->second; - + rsrcPtr->origOffset = (XMP_Uns32)thisDataPos; if ( nameLen > 0 ) { @@ -325,7 +314,7 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length if ( rsrcPtr->rsrcName == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); memcpy ( (void*)rsrcPtr->rsrcName, rsrcPName.c_str(), paddedLen ); // AUDIT: Safe, allocated enough bytes above. } - + if ( ! IsMetadataImgRsrc ( id ) ) { MoveToOffset ( fileRef, nextRsrcPos, &ioBuf ); continue; @@ -346,9 +335,9 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length LFA_Read ( fileRef, (void*)rsrcPtr->dataPtr, dataLen ); FillBuffer ( fileRef, nextRsrcPos, &ioBuf ); } - + } - + #if 0 { printf ( "\nPSIR_FileWriter::ParseFileResources, count = %d\n", this->imgRsrcs.size() ); @@ -362,7 +351,7 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length } } #endif - + } // PSIR_FileWriter::ParseFileResources // ================================================================================================= @@ -372,9 +361,9 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr ) { if ( this->fileParsed ) XMP_Throw ( "Not memory based", kXMPErr_EnforceFailure ); - + // Compute the size and allocate the new image resource block. - + XMP_Uns32 newLength = 0; InternalRsrcMap::iterator irPos = this->imgRsrcs.begin(); @@ -391,27 +380,27 @@ XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr ) newLength += ((nameLen + 2) & 0xFFFFFFFEUL); // ! Yes, +2. } } - + for ( size_t i = 0; i < this->otherRsrcs.size(); ++i ) { // Add in the non-8BIM resources. newLength += this->otherRsrcs[i].rsrcLength; } - + XMP_Uns8* newContent = (XMP_Uns8*) malloc ( newLength ); if ( newContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); - + // Fill in the new image resource block. - + XMP_Uns8* rsrcPtr = newContent; for ( irPos = this->imgRsrcs.begin(); irPos != irEnd; ++irPos ) { // Do the 8BIM resources. const InternalRsrcInfo & rsrcInfo = irPos->second; - + PutUns32BE ( k8BIM, rsrcPtr ); rsrcPtr += 4; PutUns16BE ( rsrcInfo.id, rsrcPtr ); rsrcPtr += 2; - + if ( rsrcInfo.rsrcName == 0 ) { PutUns16BE ( 0, rsrcPtr ); rsrcPtr += 2; @@ -427,7 +416,7 @@ XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr ) ++rsrcPtr; } } - + PutUns32BE ( rsrcInfo.dataLen, rsrcPtr ); rsrcPtr += 4; if ( rsrcInfo.dataLen > (newLength - (rsrcPtr - newContent)) ) { @@ -451,17 +440,17 @@ XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr ) memcpy ( rsrcPtr, srcPtr, srcLen ); // AUDIT: Protected by the above check. rsrcPtr += srcLen; // No need to pad, included in the original resource length. } - + XMP_Assert ( rsrcPtr == (newContent + newLength) ); - + // Parse the rebuilt image resource block. This is the easiest way to reconstruct the map. - + this->ParseMemoryResources ( newContent, newLength, false ); this->ownedContent = (newLength > 0); // ! We really do own the new content, if not empty. - + if ( dataPtr != 0 ) *dataPtr = newContent; return newLength; - + } // PSIR_FileWriter::UpdateMemoryResources // ================================================================================================= @@ -473,20 +462,20 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File { IgnoreParam(ioBuf); const XMP_Uns32 zero32 = 0; - + const bool checkAbort = (abortProc != 0); - + struct RsrcHeader { XMP_Uns32 type; XMP_Uns16 id; }; XMP_Assert ( (offsetof(RsrcHeader,type) == 0) && (offsetof(RsrcHeader,id) == 4) ); - + if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure ); - + XMP_Int64 destLenOffset = LFA_Seek ( destRef, 0, SEEK_CUR ); XMP_Uns32 destLength = 0; - + LFA_Write ( destRef, &destLength, 4 ); // Write a placeholder for the new PSIR section length. #if 0 @@ -502,7 +491,7 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File } } #endif - + // First write all of the '8BIM' resources from the map. Use the internal data if present, else // copy the data from the file. @@ -516,11 +505,11 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File for ( ; rsrcPos != rsrcEnd; ++rsrcPos ) { InternalRsrcInfo& currRsrc = rsrcPos->second; - + outHeader.id = MakeUns16BE ( currRsrc.id ); LFA_Write ( destRef, &outHeader, 6 ); destLength += 6; - + if ( currRsrc.rsrcName == 0 ) { LFA_Write ( destRef, &zero32, 2 ); destLength += 2; @@ -531,7 +520,7 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File LFA_Write ( destRef, currRsrc.rsrcName, paddedLen ); destLength += paddedLen; } - + XMP_Uns32 dataLen = MakeUns32BE ( currRsrc.dataLen ); LFA_Write ( destRef, &dataLen, 4 ); // printf ( " #%d, offset %d (0x%X), dataLen %d\n", currRsrc.id, destLength, destLength, currRsrc.dataLen ); @@ -542,18 +531,18 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File LFA_Seek ( sourceRef, currRsrc.origOffset, SEEK_SET ); LFA_Copy ( sourceRef, destRef, currRsrc.dataLen ); } - + destLength += 4 + currRsrc.dataLen; - + if ( (currRsrc.dataLen & 1) != 0 ) { LFA_Write ( destRef, &zero32, 1 ); // ! Pad the data to an even length. ++destLength; } } - + // Now write all of the non-8BIM resources. Copy the entire resource chunk from the source file. - + // printf ( "\nPSIR_FileWriter::UpdateFileResources - other resources\n" ); for ( size_t i = 0; i < this->otherRsrcs.size(); ++i ) { // printf ( " offset %d (0x%X), length %d", @@ -570,7 +559,7 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File XMP_Uns32 outLen = MakeUns32BE ( destLength ); LFA_Write ( destRef, &outLen, 4 ); LFA_Seek ( destRef, 0, SEEK_END ); - + // *** Not rebuilding the internal map - turns out we never want it, why pay for the I/O. // *** Should probably add an option for all of these cases, memory and file based. diff --git a/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp b/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp index 592f3e2..c372dce 100644 --- a/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp +++ b/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/PSIR_Support.hpp b/source/XMPFiles/FormatSupport/PSIR_Support.hpp index a651bef..8b5c507 100644 --- a/source/XMPFiles/FormatSupport/PSIR_Support.hpp +++ b/source/XMPFiles/FormatSupport/PSIR_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -38,8 +38,6 @@ /// This is the case for all of the derived classes, even though the memory based ones happen to /// have all of the image resources in memory. Being "handled" means being in the image resource /// map used by GetImgRsrc. The handled image resources are: -/// \li 1008 - Ancient caption PString -/// \li 1020 - Ancient caption string /// \li 1028 - IPTC /// \li 1034 - Copyrighted flag /// \li 1035 - Copyright information URL @@ -51,7 +49,7 @@ /// packaged in a DLL by themselves. They do not provide any form of C++ ABI protection. // ================================================================================================= - + // These aren't inside PSIR_Manager because the static array can't be initialized there. enum { @@ -59,26 +57,23 @@ enum { kMinImgRsrcSize = 4+2+2+4 // The minimum size for an image resource. }; -enum { kPSIR_MetadataCount = 9 }; // 1 2 3 4 5 6 7 8 9 -static const XMP_Uns16 kPSIR_MetadataIDs[] = { 1008, 1020, 1028, 1034, 1035, 1036, 1058, 1060, 1061, 0 }; - enum { - kPSIR_OldCaptionPStr = 1008, - kPSIR_OldCaption = 1020, kPSIR_IPTC = 1028, kPSIR_CopyrightFlag = 1034, kPSIR_CopyrightURL = 1035, - kPSIR_Thumbnail = 1036, kPSIR_Exif = 1058, kPSIR_XMP = 1060, kPSIR_IPTCDigest = 1061 }; +enum { kPSIR_MetadataCount = 6 }; +static const XMP_Uns16 kPSIR_MetadataIDs[] = // ! Must be in descending order with 0 sentinel. + { kPSIR_IPTCDigest, kPSIR_XMP, kPSIR_Exif, kPSIR_CopyrightURL, kPSIR_CopyrightFlag, kPSIR_IPTC, 0 }; // ================================================================================================= // ================================================================================================= -// NOTE: Although Photoshop image resources have a type and ID, for metadatya we only care about +// NOTE: Although Photoshop image resources have a type and ID, for metadatya we only care about // those of type "8BIM". Resources of other types are preserved in files, but can't be individually // accessed through the PSIR_Manager API. @@ -101,10 +96,10 @@ public: ImgRsrcInfo ( XMP_Uns16 _id, XMP_Uns32 _dataLen, void* _dataPtr, XMP_Uns32 _origOffset ) : id(_id), dataLen(_dataLen), dataPtr(_dataPtr), origOffset(_origOffset) {}; }; - + // The origOffset is the absolute file offset for file parses, the memory block offset for // memory parses. It is the offset of the resource data portion, not the overall resource. - + // --------------------------------------------------------------------------------------------- // Get the information about a "handled" image resource. Returns false if the image resource is // not handled, even if it was present in the parsed input. @@ -113,20 +108,20 @@ public: // --------------------------------------------------------------------------------------------- // Set the value for an image resource. It can be any resource, even one not originally handled. - + virtual void SetImgRsrc ( XMP_Uns16 id, const void* dataPtr, XMP_Uns32 length ) = 0; // --------------------------------------------------------------------------------------------- // Delete an image resource. Does nothing if the image resource does not exist. - + virtual void DeleteImgRsrc ( XMP_Uns16 id ) = 0; // --------------------------------------------------------------------------------------------- // Determine if the image resources are changed. - + virtual bool IsChanged() = 0; virtual bool IsLegacyChanged() = 0; - + // --------------------------------------------------------------------------------------------- virtual void ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData = true ) = 0; @@ -138,7 +133,7 @@ public: // by \c UpdateMemoryResources must be treated as read only. It exists until the PSIR_Manager // destructor is called. UpdateMemoryResources can be used on a read-only instance to get the // raw data block info. - + virtual XMP_Uns32 UpdateMemoryResources ( void** dataPtr ) = 0; virtual XMP_Uns32 UpdateFileResources ( LFA_FileRef sourceRef, LFA_FileRef destRef, IOBuffer * ioBuf, XMP_AbortProc abortProc, void * abortArg ) = 0; @@ -166,9 +161,9 @@ class PSIR_MemoryReader : public PSIR_Manager { // The leaf class for memory-bas public: bool GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const; - + void SetImgRsrc ( XMP_Uns16 id, const void* dataPtr, XMP_Uns32 length ) { NotAppropriate(); }; - + void DeleteImgRsrc ( XMP_Uns16 id ) { NotAppropriate(); }; bool IsChanged() { return false; }; @@ -184,7 +179,7 @@ public: PSIR_MemoryReader() : ownedContent(false), psirLength(0), psirContent(0) {}; virtual ~PSIR_MemoryReader() { if ( this->ownedContent ) free ( this->psirContent ); }; - + private: // Memory usage notes: PSIR_MemoryReader is for memory-based read-only usage (both apply). There @@ -192,12 +187,12 @@ private: // PSIR stream. bool ownedContent; - + XMP_Uns32 psirLength; XMP_Uns8* psirContent; - + typedef std::map<XMP_Uns16,ImgRsrcInfo> ImgRsrcMap; - + ImgRsrcMap imgRsrcs; static inline void NotAppropriate() { XMP_Throw ( "Not appropriate for PSIR_Reader", kXMPErr_InternalFailure ); }; @@ -225,10 +220,10 @@ public: bool IsChanged() { return this->changed; }; bool IsLegacyChanged(); - + void ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData = true ); void ParseFileResources ( LFA_FileRef file, XMP_Uns32 length ); - + XMP_Uns32 UpdateMemoryResources ( void** dataPtr ); XMP_Uns32 UpdateFileResources ( LFA_FileRef sourceRef, LFA_FileRef destRef, IOBuffer * ioBuf, XMP_AbortProc abortProc, void * abortArg ); @@ -241,16 +236,16 @@ public: // Memory usage notes: PSIR_FileWriter is for file-based OR read/write usage. For memory-based // streams the dataPtr and rsrcName are initially into the stream, they become a separate // allocation if changed. For file-based streams they are always a separate allocation. - + // ! The working data values are always big endian, no matter where stored. It is the client's // ! responsibility to flip them as necessary. - + static const bool kIsFileBased = true; // For use in the InternalRsrcInfo constructor. static const bool kIsMemoryBased = false; struct InternalRsrcInfo { public: - + bool changed; bool fileBased; XMP_Uns16 id; @@ -270,7 +265,7 @@ public: if ( this->rsrcName != 0 ) { free ( this->rsrcName ); this->rsrcName = 0; } } } - + InternalRsrcInfo ( XMP_Uns16 _id, XMP_Uns32 _dataLen, bool _fileBased ) : changed(false), fileBased(_fileBased), id(_id), dataLen(_dataLen), dataPtr(0), origOffset(0), rsrcName(0) {}; @@ -289,9 +284,9 @@ public: InternalRsrcInfo() // Hidden on purpose, fileBased must be properly set. : changed(false), fileBased(false), id(0), dataLen(0), dataPtr(0), origOffset(0), rsrcName(0) {}; - + }; - + // The origOffset is the absolute file offset for file parses, the memory block offset for // memory parses. It is the offset of the resource data portion, not the overall resource. @@ -306,7 +301,7 @@ private: typedef std::map<XMP_Uns16,InternalRsrcInfo> InternalRsrcMap; InternalRsrcMap imgRsrcs; - + struct OtherRsrcInfo { // For the resources of types other than "8BIM". XMP_Uns32 rsrcOffset; // The offset of the resource origin, the type field. XMP_Uns32 rsrcLength; // The full length of the resource, offset to the next resource. @@ -315,7 +310,7 @@ private: : rsrcOffset(_rsrcOffset), rsrcLength(_rsrcLength) {}; }; std::vector<OtherRsrcInfo> otherRsrcs; - + void DeleteExistingInfo(); }; // PSIR_FileWriter diff --git a/source/XMPFiles/FormatSupport/QuickTime_Support.cpp b/source/XMPFiles/FormatSupport/QuickTime_Support.cpp index 8ba1221..31091ea 100644 --- a/source/XMPFiles/FormatSupport/QuickTime_Support.cpp +++ b/source/XMPFiles/FormatSupport/QuickTime_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2009 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -8,84 +8,1141 @@ // ================================================================================================= #include "XMP_Environment.h" -#if ! ( XMP_64 || XMP_UNIXBuild) // Closes at very bottom. + +#if XMP_MacBuild + #include "Script.h" +#else + #include "MacScriptExtracts.h" +#endif #include "QuickTime_Support.hpp" -#if XMP_MacBuild - #include <Movies.h> -#elif XMP_WinBuild - #include "QTML.h" - #include "Movies.h" +#include "UnicodeConversions.hpp" +#include "UnicodeInlines.incl_cpp" +#include "Reconcile_Impl.hpp" + +// ================================================================================================= + +static const char * kMacRomanUTF8 [128] = { // UTF-8 mappings for MacRoman 80..FF. + "\xC3\x84", "\xC3\x85", "\xC3\x87", "\xC3\x89", "\xC3\x91", "\xC3\x96", "\xC3\x9C", "\xC3\xA1", + "\xC3\xA0", "\xC3\xA2", "\xC3\xA4", "\xC3\xA3", "\xC3\xA5", "\xC3\xA7", "\xC3\xA9", "\xC3\xA8", + "\xC3\xAA", "\xC3\xAB", "\xC3\xAD", "\xC3\xAC", "\xC3\xAE", "\xC3\xAF", "\xC3\xB1", "\xC3\xB3", + "\xC3\xB2", "\xC3\xB4", "\xC3\xB6", "\xC3\xB5", "\xC3\xBA", "\xC3\xB9", "\xC3\xBB", "\xC3\xBC", + "\xE2\x80\xA0", "\xC2\xB0", "\xC2\xA2", "\xC2\xA3", "\xC2\xA7", "\xE2\x80\xA2", "\xC2\xB6", "\xC3\x9F", + "\xC2\xAE", "\xC2\xA9", "\xE2\x84\xA2", "\xC2\xB4", "\xC2\xA8", "\xE2\x89\xA0", "\xC3\x86", "\xC3\x98", + "\xE2\x88\x9E", "\xC2\xB1", "\xE2\x89\xA4", "\xE2\x89\xA5", "\xC2\xA5", "\xC2\xB5", "\xE2\x88\x82", "\xE2\x88\x91", + "\xE2\x88\x8F", "\xCF\x80", "\xE2\x88\xAB", "\xC2\xAA", "\xC2\xBA", "\xCE\xA9", "\xC3\xA6", "\xC3\xB8", + "\xC2\xBF", "\xC2\xA1", "\xC2\xAC", "\xE2\x88\x9A", "\xC6\x92", "\xE2\x89\x88", "\xE2\x88\x86", "\xC2\xAB", + "\xC2\xBB", "\xE2\x80\xA6", "\xC2\xA0", "\xC3\x80", "\xC3\x83", "\xC3\x95", "\xC5\x92", "\xC5\x93", + "\xE2\x80\x93", "\xE2\x80\x94", "\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2\x80\x98", "\xE2\x80\x99", "\xC3\xB7", "\xE2\x97\x8A", + "\xC3\xBF", "\xC5\xB8", "\xE2\x81\x84", "\xE2\x82\xAC", "\xE2\x80\xB9", "\xE2\x80\xBA", "\xEF\xAC\x81", "\xEF\xAC\x82", + "\xE2\x80\xA1", "\xC2\xB7", "\xE2\x80\x9A", "\xE2\x80\x9E", "\xE2\x80\xB0", "\xC3\x82", "\xC3\x8A", "\xC3\x81", + "\xC3\x8B", "\xC3\x88", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F", "\xC3\x8C", "\xC3\x93", "\xC3\x94", + "\xEF\xA3\xBF", "\xC3\x92", "\xC3\x9A", "\xC3\x9B", "\xC3\x99", "\xC4\xB1", "\xCB\x86", "\xCB\x9C", + "\xC2\xAF", "\xCB\x98", "\xCB\x99", "\xCB\x9A", "\xC2\xB8", "\xCB\x9D", "\xCB\x9B", "\xCB\x87" +}; + +static const XMP_Uns32 kMacRomanCP [128] = { // Unicode codepoints for MacRoman 80..FF. + 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, + 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, + 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, + 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, + 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, + 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, + 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, + 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, + 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, + 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, + 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, + 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, + 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, + 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, // ! U+F8FF is private use solid Apple icon. + 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7 +}; + +// ------------------------------------------------------------------------------------------------- + +static const XMP_Uns16 kMacLangToScript_0_94 [95] = { + + /* langEnglish (0) */ smRoman, + /* langFrench (1) */ smRoman, + /* langGerman (2) */ smRoman, + /* langItalian (3) */ smRoman, + /* langDutch (4) */ smRoman, + /* langSwedish (5) */ smRoman, + /* langSpanish (6) */ smRoman, + /* langDanish (7) */ smRoman, + /* langPortuguese (8) */ smRoman, + /* langNorwegian (9) */ smRoman, + + /* langHebrew (10) */ smHebrew, + /* langJapanese (11) */ smJapanese, + /* langArabic (12) */ smArabic, + /* langFinnish (13) */ smRoman, + /* langGreek (14) */ smRoman, + /* langIcelandic (15) */ smRoman, + /* langMaltese (16) */ smRoman, + /* langTurkish (17) */ smRoman, + /* langCroatian (18) */ smRoman, + /* langTradChinese (19) */ smTradChinese, + + /* langUrdu (20) */ smArabic, + /* langHindi (21) */ smDevanagari, + /* langThai (22) */ smThai, + /* langKorean (23) */ smKorean, + /* langLithuanian (24) */ smCentralEuroRoman, + /* langPolish (25) */ smCentralEuroRoman, + /* langHungarian (26) */ smCentralEuroRoman, + /* langEstonian (27) */ smCentralEuroRoman, + /* langLatvian (28) */ smCentralEuroRoman, + /* langSami (29) */ kNoMacScript, // ! Not known, missing from Apple comments. + + /* langFaroese (30) */ smRoman, + /* langFarsi (31) */ smArabic, + /* langRussian (32) */ smCyrillic, + /* langSimpChinese (33) */ smSimpChinese, + /* langFlemish (34) */ smRoman, + /* langIrishGaelic (35) */ smRoman, + /* langAlbanian (36) */ smRoman, + /* langRomanian (37) */ smRoman, + /* langCzech (38) */ smCentralEuroRoman, + /* langSlovak (39) */ smCentralEuroRoman, + + /* langSlovenian (40) */ smRoman, + /* langYiddish (41) */ smHebrew, + /* langSerbian (42) */ smCyrillic, + /* langMacedonian (43) */ smCyrillic, + /* langBulgarian (44) */ smCyrillic, + /* langUkrainian (45) */ smCyrillic, + /* langBelorussian (46) */ smCyrillic, + /* langUzbek (47) */ smCyrillic, + /* langKazakh (48) */ smCyrillic, + /* langAzerbaijani (49) */ smCyrillic, + + /* langAzerbaijanAr (50) */ smArabic, + /* langArmenian (51) */ smArmenian, + /* langGeorgian (52) */ smGeorgian, + /* langMoldavian (53) */ smCyrillic, + /* langKirghiz (54) */ smCyrillic, + /* langTajiki (55) */ smCyrillic, + /* langTurkmen (56) */ smCyrillic, + /* langMongolian (57) */ smMongolian, + /* langMongolianCyr (58) */ smCyrillic, + /* langPashto (59) */ smArabic, + + /* langKurdish (60) */ smArabic, + /* langKashmiri (61) */ smArabic, + /* langSindhi (62) */ smArabic, + /* langTibetan (63) */ smTibetan, + /* langNepali (64) */ smDevanagari, + /* langSanskrit (65) */ smDevanagari, + /* langMarathi (66) */ smDevanagari, + /* langBengali (67) */ smBengali, + /* langAssamese (68) */ smBengali, + /* langGujarati (69) */ smGujarati, + + /* langPunjabi (70) */ smGurmukhi, + /* langOriya (71) */ smOriya, + /* langMalayalam (72) */ smMalayalam, + /* langKannada (73) */ smKannada, + /* langTamil (74) */ smTamil, + /* langTelugu (75) */ smTelugu, + /* langSinhalese (76) */ smSinhalese, + /* langBurmese (77) */ smBurmese, + /* langKhmer (78) */ smKhmer, + /* langLao (79) */ smLao, + + /* langVietnamese (80) */ smVietnamese, + /* langIndonesian (81) */ smRoman, + /* langTagalog (82) */ smRoman, + /* langMalayRoman (83) */ smRoman, + /* langMalayArabic (84) */ smArabic, + /* langAmharic (85) */ smEthiopic, + /* langTigrinya (86) */ smEthiopic, + /* langOromo (87) */ smEthiopic, + /* langSomali (88) */ smRoman, + /* langSwahili (89) */ smRoman, + + /* langKinyarwanda (90) */ smRoman, + /* langRundi (91) */ smRoman, + /* langNyanja (92) */ smRoman, + /* langMalagasy (93) */ smRoman, + /* langEsperanto (94) */ smRoman + +}; // kMacLangToScript_0_94 + +static const XMP_Uns16 kMacLangToScript_128_151 [24] = { + + /* langWelsh (128) */ smRoman, + /* langBasque (129) */ smRoman, + + /* langCatalan (130) */ smRoman, + /* langLatin (131) */ smRoman, + /* langQuechua (132) */ smRoman, + /* langGuarani (133) */ smRoman, + /* langAymara (134) */ smRoman, + /* langTatar (135) */ smCyrillic, + /* langUighur (136) */ smArabic, + /* langDzongkha (137) */ smTibetan, + /* langJavaneseRom (138) */ smRoman, + /* langSundaneseRom (139) */ smRoman, + + /* langGalician (140) */ smRoman, + /* langAfrikaans (141) */ smRoman, + /* langBreton (142) */ smRoman, + /* langInuktitut (143) */ smEthiopic, + /* langScottishGaelic (144) */ smRoman, + /* langManxGaelic (145) */ smRoman, + /* langIrishGaelicScript (146) */ smRoman, + /* langTongan (147) */ smRoman, + /* langGreekAncient (148) */ smGreek, + /* langGreenlandic (149) */ smRoman, + + /* langAzerbaijanRoman (150) */ smRoman, + /* langNynorsk (151) */ smRoman + +}; // kMacLangToScript_128_151 + +// ------------------------------------------------------------------------------------------------- + +static const char * kMacToXMPLang_0_94 [95] = { + + /* langEnglish (0) */ "en", + /* langFrench (1) */ "fr", + /* langGerman (2) */ "de", + /* langItalian (3) */ "it", + /* langDutch (4) */ "nl", + /* langSwedish (5) */ "sv", + /* langSpanish (6) */ "es", + /* langDanish (7) */ "da", + /* langPortuguese (8) */ "pt", + /* langNorwegian (9) */ "no", + + /* langHebrew (10) */ "he", + /* langJapanese (11) */ "ja", + /* langArabic (12) */ "ar", + /* langFinnish (13) */ "fi", + /* langGreek (14) */ "el", + /* langIcelandic (15) */ "is", + /* langMaltese (16) */ "mt", + /* langTurkish (17) */ "tr", + /* langCroatian (18) */ "hr", + /* langTradChinese (19) */ "zh", + + /* langUrdu (20) */ "ur", + /* langHindi (21) */ "hi", + /* langThai (22) */ "th", + /* langKorean (23) */ "ko", + /* langLithuanian (24) */ "lt", + /* langPolish (25) */ "pl", + /* langHungarian (26) */ "hu", + /* langEstonian (27) */ "et", + /* langLatvian (28) */ "lv", + /* langSami (29) */ "se", + + /* langFaroese (30) */ "fo", + /* langFarsi (31) */ "fa", + /* langRussian (32) */ "ru", + /* langSimpChinese (33) */ "zh", + /* langFlemish (34) */ "nl", + /* langIrishGaelic (35) */ "ga", + /* langAlbanian (36) */ "sq", + /* langRomanian (37) */ "ro", + /* langCzech (38) */ "cs", + /* langSlovak (39) */ "sk", + + /* langSlovenian (40) */ "sl", + /* langYiddish (41) */ "yi", + /* langSerbian (42) */ "sr", + /* langMacedonian (43) */ "mk", + /* langBulgarian (44) */ "bg", + /* langUkrainian (45) */ "uk", + /* langBelorussian (46) */ "be", + /* langUzbek (47) */ "uz", + /* langKazakh (48) */ "kk", + /* langAzerbaijani (49) */ "az", + + /* langAzerbaijanAr (50) */ "az", + /* langArmenian (51) */ "hy", + /* langGeorgian (52) */ "ka", + /* langMoldavian (53) */ "ro", + /* langKirghiz (54) */ "ky", + /* langTajiki (55) */ "tg", + /* langTurkmen (56) */ "tk", + /* langMongolian (57) */ "mn", + /* langMongolianCyr (58) */ "mn", + /* langPashto (59) */ "ps", + + /* langKurdish (60) */ "ku", + /* langKashmiri (61) */ "ks", + /* langSindhi (62) */ "sd", + /* langTibetan (63) */ "bo", + /* langNepali (64) */ "ne", + /* langSanskrit (65) */ "sa", + /* langMarathi (66) */ "mr", + /* langBengali (67) */ "bn", + /* langAssamese (68) */ "as", + /* langGujarati (69) */ "gu", + + /* langPunjabi (70) */ "pa", + /* langOriya (71) */ "or", + /* langMalayalam (72) */ "ml", + /* langKannada (73) */ "kn", + /* langTamil (74) */ "ta", + /* langTelugu (75) */ "te", + /* langSinhalese (76) */ "si", + /* langBurmese (77) */ "my", + /* langKhmer (78) */ "km", + /* langLao (79) */ "lo", + + /* langVietnamese (80) */ "vi", + /* langIndonesian (81) */ "id", + /* langTagalog (82) */ "tl", + /* langMalayRoman (83) */ "ms", + /* langMalayArabic (84) */ "ms", + /* langAmharic (85) */ "am", + /* langTigrinya (86) */ "ti", + /* langOromo (87) */ "om", + /* langSomali (88) */ "so", + /* langSwahili (89) */ "sw", + + /* langKinyarwanda (90) */ "rw", + /* langRundi (91) */ "rn", + /* langNyanja (92) */ "ny", + /* langMalagasy (93) */ "mg", + /* langEsperanto (94) */ "eo" + +}; // kMacToXMPLang_0_94 + +static const char * kMacToXMPLang_128_151 [24] = { + + /* langWelsh (128) */ "cy", + /* langBasque (129) */ "eu", + + /* langCatalan (130) */ "ca", + /* langLatin (131) */ "la", + /* langQuechua (132) */ "qu", + /* langGuarani (133) */ "gn", + /* langAymara (134) */ "ay", + /* langTatar (135) */ "tt", + /* langUighur (136) */ "ug", + /* langDzongkha (137) */ "dz", + /* langJavaneseRom (138) */ "jv", + /* langSundaneseRom (139) */ "su", + + /* langGalician (140) */ "gl", + /* langAfrikaans (141) */ "af", + /* langBreton (142) */ "br", + /* langInuktitut (143) */ "iu", + /* langScottishGaelic (144) */ "gd", + /* langManxGaelic (145) */ "gv", + /* langIrishGaelicScript (146) */ "ga", + /* langTongan (147) */ "to", + /* langGreekAncient (148) */ "", // ! Has no ISO 639-1 2 letter code. + /* langGreenlandic (149) */ "kl", + + /* langAzerbaijanRoman (150) */ "az", + /* langNynorsk (151) */ "nn" + +}; // kMacToXMPLang_128_151 + +// ------------------------------------------------------------------------------------------------- + +#if XMP_WinBuild + +static UINT kMacScriptToWinCP[34] = { + /* smRoman (0) */ 10000, // There don't seem to be symbolic constants. + /* smJapanese (1) */ 10001, // From http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx + /* smTradChinese (2) */ 10002, + /* smKorean (3) */ 10003, + /* smArabic (4) */ 10004, + /* smHebrew (5) */ 10005, + /* smGreek (6) */ 10006, + /* smCyrillic (7) */ 10007, + /* smRSymbol (8) */ 0, + /* smDevanagari (9) */ 0, + /* smGurmukhi (10) */ 0, + /* smGujarati (11) */ 0, + /* smOriya (12) */ 0, + /* smBengali (13) */ 0, + /* smTamil (14) */ 0, + /* smTelugu (15) */ 0, + /* smKannada (16) */ 0, + /* smMalayalam (17) */ 0, + /* smSinhalese (18) */ 0, + /* smBurmese (19) */ 0, + /* smKhmer (20) */ 0, + /* smThai (21) */ 10021, + /* smLao (22) */ 0, + /* smGeorgian (23) */ 0, + /* smArmenian (24) */ 0, + /* smSimpChinese (25) */ 10008, + /* smTibetan (26) */ 0, + /* smMongolian (27) */ 0, + /* smEthiopic (28) */ 0, + /* smGeez (28) */ 0, + /* smCentralEuroRoman (29) */ 10029, + /* smVietnamese (30) */ 0, + /* smExtArabic (31) */ 0, + /* smUninterp (32) */ 0 +}; // kMacScriptToWinCP + +static UINT kMacToWinCP_0_94 [95] = { + + /* langEnglish (0) */ 0, + /* langFrench (1) */ 0, + /* langGerman (2) */ 0, + /* langItalian (3) */ 0, + /* langDutch (4) */ 0, + /* langSwedish (5) */ 0, + /* langSpanish (6) */ 0, + /* langDanish (7) */ 0, + /* langPortuguese (8) */ 0, + /* langNorwegian (9) */ 0, + + /* langHebrew (10) */ 10005, + /* langJapanese (11) */ 10001, + /* langArabic (12) */ 10004, + /* langFinnish (13) */ 0, + /* langGreek (14) */ 10006, + /* langIcelandic (15) */ 10079, + /* langMaltese (16) */ 0, + /* langTurkish (17) */ 10081, + /* langCroatian (18) */ 10082, + /* langTradChinese (19) */ 10002, + + /* langUrdu (20) */ 0, + /* langHindi (21) */ 0, + /* langThai (22) */ 10021, + /* langKorean (23) */ 10003, + /* langLithuanian (24) */ 0, + /* langPolish (25) */ 0, + /* langHungarian (26) */ 0, + /* langEstonian (27) */ 0, + /* langLatvian (28) */ 0, + /* langSami (29) */ 0, + + /* langFaroese (30) */ 0, + /* langFarsi (31) */ 0, + /* langRussian (32) */ 0, + /* langSimpChinese (33) */ 10008, + /* langFlemish (34) */ 0, + /* langIrishGaelic (35) */ 0, + /* langAlbanian (36) */ 0, + /* langRomanian (37) */ 10010, + /* langCzech (38) */ 0, + /* langSlovak (39) */ 0, + + /* langSlovenian (40) */ 0, + /* langYiddish (41) */ 0, + /* langSerbian (42) */ 0, + /* langMacedonian (43) */ 0, + /* langBulgarian (44) */ 0, + /* langUkrainian (45) */ 10017, + /* langBelorussian (46) */ 0, + /* langUzbek (47) */ 0, + /* langKazakh (48) */ 0, + /* langAzerbaijani (49) */ 0, + + /* langAzerbaijanAr (50) */ 0, + /* langArmenian (51) */ 0, + /* langGeorgian (52) */ 0, + /* langMoldavian (53) */ 0, + /* langKirghiz (54) */ 0, + /* langTajiki (55) */ 0, + /* langTurkmen (56) */ 0, + /* langMongolian (57) */ 0, + /* langMongolianCyr (58) */ 0, + /* langPashto (59) */ 0, + + /* langKurdish (60) */ 0, + /* langKashmiri (61) */ 0, + /* langSindhi (62) */ 0, + /* langTibetan (63) */ 0, + /* langNepali (64) */ 0, + /* langSanskrit (65) */ 0, + /* langMarathi (66) */ 0, + /* langBengali (67) */ 0, + /* langAssamese (68) */ 0, + /* langGujarati (69) */ 0, + + /* langPunjabi (70) */ 0, + /* langOriya (71) */ 0, + /* langMalayalam (72) */ 0, + /* langKannada (73) */ 0, + /* langTamil (74) */ 0, + /* langTelugu (75) */ 0, + /* langSinhalese (76) */ 0, + /* langBurmese (77) */ 0, + /* langKhmer (78) */ 0, + /* langLao (79) */ 0, + + /* langVietnamese (80) */ 0, + /* langIndonesian (81) */ 0, + /* langTagalog (82) */ 0, + /* langMalayRoman (83) */ 0, + /* langMalayArabic (84) */ 0, + /* langAmharic (85) */ 0, + /* langTigrinya (86) */ 0, + /* langOromo (87) */ 0, + /* langSomali (88) */ 0, + /* langSwahili (89) */ 0, + + /* langKinyarwanda (90) */ 0, + /* langRundi (91) */ 0, + /* langNyanja (92) */ 0, + /* langMalagasy (93) */ 0, + /* langEsperanto (94) */ 0 + +}; // kMacToWinCP_0_94 + #endif -namespace QuickTime_Support +// ================================================================================================= +// GetMacScript +// ============ + +static XMP_Uns16 GetMacScript ( XMP_Uns16 macLang ) { + XMP_Uns16 macScript = kNoMacScript; + + if ( macLang <= 94 ) { + macScript = kMacLangToScript_0_94[macLang]; + } else if ( (128 <= macLang) && (macLang <= 151) ) { + macScript = kMacLangToScript_0_94[macLang-128]; + } + + return macScript; + +} // GetMacScript + +// ================================================================================================= +// GetWinCP +// ======== + +#if XMP_WinBuild + +static UINT GetWinCP ( XMP_Uns16 macLang ) +{ + UINT winCP = 0; + + if ( macLang <= 94 ) winCP = kMacToWinCP_0_94[macLang]; + + if ( winCP == 0 ) { + XMP_Uns16 macScript = GetMacScript ( macLang ); + if ( macScript != kNoMacScript ) winCP = kMacScriptToWinCP[macScript]; + } + + return winCP; + +} // GetWinCP + +#endif + +// ================================================================================================= +// GetXMPLang +// ========== + +static XMP_StringPtr GetXMPLang ( XMP_Uns16 macLang ) +{ + XMP_StringPtr xmpLang = ""; + + if ( macLang <= 94 ) { + xmpLang = kMacToXMPLang_0_94[macLang]; + } else if ( (128 <= macLang) && (macLang <= 151) ) { + xmpLang = kMacToXMPLang_128_151[macLang-128]; + } + + return xmpLang; + +} // GetXMPLang + +// ================================================================================================= +// GetMacLang +// ========== + +static XMP_Uns16 GetMacLang ( std::string * xmpLang ) +{ + if ( *xmpLang == "" ) return kNoMacLang; + + size_t hyphenPos = xmpLang->find ( '-' ); // Make sure the XMP language is "generic". + if ( hyphenPos != std::string::npos ) xmpLang->erase ( hyphenPos ); + + for ( XMP_Uns16 i = 0; i <= 94; ++i ) { // Using std::map would be faster. + if ( *xmpLang == kMacToXMPLang_0_94[i] ) return i; + } + + for ( XMP_Uns16 i = 128; i <= 151; ++i ) { // Using std::map would be faster. + if ( *xmpLang == kMacToXMPLang_128_151[i-128] ) return i; + } + + return kNoMacLang; + +} // GetMacLang + +// ================================================================================================= +// MacRomanToUTF8 +// ============== - bool sMainInitOK = false; +static void MacRomanToUTF8 ( const std::string & macRoman, std::string * utf8 ) +{ + utf8->erase(); + + for ( XMP_Uns8* chPtr = (XMP_Uns8*)macRoman.c_str(); *chPtr != 0; ++chPtr ) { // ! Don't trust that char is unsigned. + if ( *chPtr < 0x80 ) { + (*utf8) += (char)*chPtr; + } else { + (*utf8) += kMacRomanUTF8[(*chPtr)-0x80]; + } + } + +} // MacRomanToUTF8 + +// ================================================================================================= +// UTF8ToMacRoman +// ============== + +static void UTF8ToMacRoman ( const std::string & utf8, std::string * macRoman ) +{ + macRoman->erase(); + bool inNonMRSpan = false; + + for ( const XMP_Uns8 * chPtr = (XMP_Uns8*)utf8.c_str(); *chPtr != 0; ++chPtr ) { // ! Don't trust that char is unsigned. + if ( *chPtr < 0x80 ) { + (*macRoman) += (char)*chPtr; + inNonMRSpan = false; + } else { + XMP_Uns32 cp = GetCodePoint ( &chPtr ); + --chPtr; // Make room for the loop increment. + XMP_Uns8 mr; + for ( mr = 0; (mr < 0x80) && (cp != kMacRomanCP[mr]); ++mr ) {}; // Using std::map would be faster. + if ( mr < 0x80 ) { + (*macRoman) += (char)(mr+0x80); + inNonMRSpan = false; + } else if ( ! inNonMRSpan ) { + (*macRoman) += '?'; + inNonMRSpan = true; + } + } + } + +} // UTF8ToMacRoman + +// ================================================================================================= +// IsMacLangKnown +// ============== + +static inline bool IsMacLangKnown ( XMP_Uns16 macLang ) +{ + XMP_Uns16 macScript = GetMacScript ( macLang ); + if ( macScript == kNoMacScript ) return false; + + #if XMP_UNIXBuild + if ( macScript != smRoman ) return false; + #elif XMP_WinBuild + if ( GetWinCP(macLang) == 0 ) return false; + #endif + + return true; + +} // IsMacLangKnown + +// ================================================================================================= +// ConvertToMacLang +// ================ + +bool ConvertToMacLang ( const std::string & utf8Value, XMP_Uns16 macLang, std::string * macValue ) +{ + macValue->erase(); + if ( macLang == kNoMacLang ) macLang = 0; // *** Zero is English, ought to use the "active" OS lang. + if ( ! IsMacLangKnown ( macLang ) ) return false; + + #if XMP_MacBuild + XMP_Uns16 macScript = GetMacScript ( macLang ); + ReconcileUtils::UTF8ToMacEncoding ( macScript, macLang, (XMP_Uns8*)utf8Value.c_str(), utf8Value.size(), macValue ); + #elif XMP_UNIXBuild + UTF8ToMacRoman ( utf8Value, macValue ); + #elif XMP_WinBuild + UINT winCP = GetWinCP ( macLang ); + ReconcileUtils::UTF8ToWinEncoding ( winCP, (XMP_Uns8*)utf8Value.c_str(), utf8Value.size(), macValue ); + #endif + + return true; + +} // ConvertToMacLang + +// ================================================================================================= +// ConvertFromMacLang +// ================== + +bool ConvertFromMacLang ( const std::string & macValue, XMP_Uns16 macLang, std::string * utf8Value ) +{ + utf8Value->erase(); + if ( ! IsMacLangKnown ( macLang ) ) return false; + + #if XMP_MacBuild + XMP_Uns16 macScript = GetMacScript ( macLang ); + ReconcileUtils::MacEncodingToUTF8 ( macScript, macLang, (XMP_Uns8*)macValue.c_str(), macValue.size(), utf8Value ); + #elif XMP_UNIXBuild + MacRomanToUTF8 ( macValue, utf8Value ); + #elif XMP_WinBuild + UINT winCP = GetWinCP ( macLang ); + ReconcileUtils::WinEncodingToUTF8 ( winCP, (XMP_Uns8*)macValue.c_str(), macValue.size(), utf8Value ); + #endif + + return true; - // ============================================================================================= +} // ConvertFromMacLang - bool MainInitialize ( bool ignoreInit ) - { - OSStatus err = noErr; +// ================================================================================================= +// ================================================================================================= +// TradQT_Manager +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// TradQT_Manager::ParseCachedBoxes +// ================================ +// +// Parse the cached '©...' children of the 'moov'/'udta' box. The contents of each cached box are +// a sequence of "mini boxes" analogous to XMP AltText arrays. Each mini box has a 16-bit size, +// 16-bit language code, and text. The size is only the text size. The language codes are Macintosh +// Script Manager langXyz codes. The text encoding is implicit in the language, see comments in +// Apple's Script.h header. + +bool TradQT_Manager::ParseCachedBoxes ( const MOOV_Manager & moovMgr ) +{ + MOOV_Manager::BoxInfo udtaInfo; + MOOV_Manager::BoxRef udtaRef = moovMgr.GetBox ( "moov/udta", &udtaInfo ); + if ( udtaRef == 0 ) return false; + + for ( XMP_Uns32 i = 0; i < udtaInfo.childCount; ++i ) { + + MOOV_Manager::BoxInfo currInfo; + MOOV_Manager::BoxRef currRef = moovMgr.GetNthChild ( udtaRef, i, &currInfo ); + if ( currRef == 0 ) break; // Sanity check, should not happen. + if ( (currInfo.boxType >> 24) != 0xA9 ) continue; + if ( currInfo.contentSize < 2+2+1 ) continue; // Want enough for a non-empty value. + + InfoMapPos newInfo = this->parsedBoxes.insert ( this->parsedBoxes.end(), + InfoMap::value_type ( currInfo.boxType, ParsedBoxInfo ( currInfo.boxType ) ) ); + std::vector<ValueInfo> * newValues = &newInfo->second.values; + + XMP_Uns8 * boxPtr = (XMP_Uns8*) currInfo.content; + XMP_Uns8 * boxEnd = boxPtr + currInfo.contentSize; + XMP_Uns16 miniLen, macLang; + + for ( ; boxPtr < boxEnd-4; boxPtr += miniLen ) { + + miniLen = 4 + GetUns16BE ( boxPtr ); // ! Include header in local miniLen. + macLang = GetUns16BE ( boxPtr+2); + if ( (miniLen <= 4) || (miniLen > (boxEnd - boxPtr)) ) continue; // Ignore bad or empty values. + + XMP_StringPtr valuePtr = (char*)(boxPtr+4); + size_t valueLen = miniLen - 4; + + newValues->push_back ( ValueInfo() ); + ValueInfo * newValue = &newValues->back(); + + // Only set the XMP language if the Mac script is known, i.e. the value can be converted. + + newValue->macLang = macLang; + if ( IsMacLangKnown ( macLang ) ) newValue->xmpLang = GetXMPLang ( macLang ); + newValue->macValue.assign ( valuePtr, valueLen ); + + } + + } + + return (! this->parsedBoxes.empty()); + +} // TradQT_Manager::ParseCachedBoxes + +// ================================================================================================= +// TradQT_Manager::ImportSimpleXMP +// =============================== +// +// Update a simple XMP property if the QT value looks newer. + +bool TradQT_Manager::ImportSimpleXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr prop ) const +{ + + try { + + InfoMapCPos infoPos = this->parsedBoxes.find ( id ); + if ( infoPos == this->parsedBoxes.end() ) return false; + if ( infoPos->second.values.empty() ) return false; + + std::string xmpValue, tempValue; + XMP_OptionBits flags; + bool xmpExists = xmp->GetProperty ( ns, prop, &xmpValue, &flags ); + if ( xmpExists && (! XMP_PropIsSimple ( flags )) ) { + XMP_Throw ( "TradQT_Manager::ImportSimpleXMP - XMP property must be simple", kXMPErr_BadParam ); + } + + bool convertOK; + const ValueInfo & qtItem = infoPos->second.values[0]; // ! Use the first QT entry. + + if ( xmpExists ) { + convertOK = ConvertToMacLang ( xmpValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + if ( tempValue == qtItem.macValue ) return false; // QT value matches back converted XMP value. + } + + convertOK = ConvertFromMacLang ( qtItem.macValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + xmp->SetProperty ( ns, prop, tempValue.c_str() ); + return true; + + } catch ( ... ) { + + return false; // Don't let one failure abort other imports. + + } + +} // TradQT_Manager::ImportSimpleXMP + +// ================================================================================================= +// TradQT_Manager::ImportLangItem +// ============================== +// +// Update a specific XMP AltText item if the QuickTime value looks newer. + +bool TradQT_Manager::ImportLangItem ( const ValueInfo & qtItem, SXMPMeta * xmp, + XMP_StringPtr ns, XMP_StringPtr langArray ) const +{ + + try { + + XMP_StringPtr genericLang, specificLang; + if ( qtItem.xmpLang[0] != 0 ) { + genericLang = qtItem.xmpLang; + specificLang = qtItem.xmpLang; + } else { + genericLang = ""; + specificLang = "x-default"; + } + + bool convertOK; + std::string xmpValue, tempValue, actualLang; + bool xmpExists = xmp->GetLocalizedText ( ns, langArray, genericLang, specificLang, &actualLang, &xmpValue, 0 ); + if ( xmpExists ) { + convertOK = ConvertToMacLang ( xmpValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + if ( tempValue == qtItem.macValue ) return true; // QT value matches back converted XMP value. + specificLang = actualLang.c_str(); + } + + convertOK = ConvertFromMacLang ( qtItem.macValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + xmp->SetLocalizedText ( ns, langArray, "", specificLang, tempValue.c_str() ); + return true; + + } catch ( ... ) { + + return false; // Don't let one failure abort other imports. + + } + +} // TradQT_Manager::ImportLangItem + +// ================================================================================================= +// TradQT_Manager::ImportLangAltXMP +// ================================ +// +// Update items in the XMP array if the QT value looks newer. + +bool TradQT_Manager::ImportLangAltXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) const +{ + + try { + + InfoMapCPos infoPos = this->parsedBoxes.find ( id ); + if ( infoPos == this->parsedBoxes.end() ) return false; + if ( infoPos->second.values.empty() ) return false; // Quit now if there are no values. + + XMP_OptionBits flags; + bool xmpExists = xmp->GetProperty ( ns, langArray, 0, &flags ); + if ( ! xmpExists ) { + xmp->SetProperty ( ns, langArray, 0, kXMP_PropArrayIsAltText ); + } else if ( ! XMP_ArrayIsAltText ( flags ) ) { + XMP_Throw ( "TradQT_Manager::ImportLangAltXMP - XMP array must be AltText", kXMPErr_BadParam ); + } + + // Process all of the QT values, looking up the appropriate XMP language for each. - if ( ignoreInit ) { - sMainInitOK = true; - return true; + bool haveMappings = false; + const ValueVector & qtValues = infoPos->second.values; + + for ( size_t i = 0, limit = qtValues.size(); i < limit; ++i ) { + const ValueInfo & qtItem = qtValues[i]; + if ( *qtItem.xmpLang == 0 ) continue; // Only do known mappings in the loop. + haveMappings |= this->ImportLangItem ( qtItem, xmp, ns, langArray ); + } + + if ( ! haveMappings ) { + // If nothing mapped, process the first QT item to XMP's "x-default". + haveMappings = this->ImportLangItem ( qtValues[0], xmp, ns, langArray ); // ! No xmpLang implies "x-default". } - #if XMP_WinBuild - err = ::InitializeQTML ( 0 ); - #endif + return haveMappings; + + } catch ( ... ) { + + return false; // Don't let one failure abort other imports. + + } + +} // TradQT_Manager::ImportLangAltXMP + +// ================================================================================================= +// TradQT_Manager::ExportSimpleXMP +// =============================== +// +// Export a simple XMP value to the first existing QuickTime item. Delete all of the QT values if the +// XMP value is empty or the XMP does not exist. + +// ! We don't create new QuickTime items since we don't know the language. + +void TradQT_Manager::ExportSimpleXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr prop, + bool createWithZeroLang /* = false */ ) +{ + std::string xmpValue, macValue; + + InfoMapPos infoPos = this->parsedBoxes.find ( id ); + bool qtFound = (infoPos != this->parsedBoxes.end()) && (! infoPos->second.values.empty()); - if ( err == noErr ) err = ::EnterMovies(); - if ( err == noErr ) sMainInitOK = true; + bool xmpFound = xmp.GetProperty ( ns, prop, &xmpValue, 0 ); + if ( (! xmpFound) || (xmpValue.empty()) ) { + if ( qtFound ) { + this->parsedBoxes.erase ( infoPos ); + this->changed = true; + } + return; + } + + XMP_Assert ( xmpFound ); + if ( ! qtFound ) { + if ( ! createWithZeroLang ) return; + infoPos = this->parsedBoxes.insert ( this->parsedBoxes.end(), + InfoMap::value_type ( id, ParsedBoxInfo ( id ) ) ); + ValueVector * newValues = &infoPos->second.values; + newValues->push_back ( ValueInfo() ); + ValueInfo * newValue = &newValues->back(); + newValue->macLang = 0; // Happens to be langEnglish. + newValue->xmpLang = kMacToXMPLang_0_94[0]; + this->changed = infoPos->second.changed = true; + } - return sMainInitOK; + ValueInfo * qtItem = &infoPos->second.values[0]; // ! Use the first QT entry. + if ( ! IsMacLangKnown ( qtItem->macLang ) ) return; - } // MainInitialize + bool convertOK = ConvertToMacLang ( xmpValue, qtItem->macLang, &macValue ); + if ( convertOK && (macValue != qtItem->macValue) ) { + qtItem->macValue = macValue; + this->changed = infoPos->second.changed = true; + } - // ============================================================================================= +} // TradQT_Manager::ExportSimpleXMP - void MainTerminate ( bool ignoreInit ) - { +// ================================================================================================= +// TradQT_Manager::ExportLangAltXMP +// ================================ +// +// Export XMP LangAlt array items to QuickTime, where the language and encoding mappings are known. +// If there are no known language and encoding mappings, map the XMP default item to the first +// existing QuickTime item. + +void TradQT_Manager::ExportLangAltXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) +{ + bool haveMappings = false; + std::string xmpPath, xmpValue, xmpLang, macValue; + + InfoMapPos infoPos = this->parsedBoxes.find ( id ); + if ( infoPos == this->parsedBoxes.end() ) { + infoPos = this->parsedBoxes.insert ( this->parsedBoxes.end(), + InfoMap::value_type ( id, ParsedBoxInfo ( id ) ) ); + } + + ValueVector * qtValues = &infoPos->second.values; + XMP_Index xmpCount = xmp.CountArrayItems ( ns, langArray ); + bool convertOK; + + if ( xmpCount == 0 ) { + // Delete the "mappable" QuickTime items if there are no XMP values. Leave the others alone. + for ( int i = (int)qtValues->size()-1; i > 0; --i ) { // ! Need a signed index. + if ( (*qtValues)[i].xmpLang[0] != 0 ) { + qtValues->erase ( qtValues->begin() + i ); + this->changed = infoPos->second.changed = true; + } + } + return; + } + + // Go through the XMP and look for a related macLang QuickTime item to update or create. + + for ( XMP_Index xmpIndex = 1; xmpIndex <= xmpCount; ++xmpIndex ) { // ! XMP index starts at 1! + + SXMPUtils::ComposeArrayItemPath ( ns, langArray, xmpIndex, &xmpPath ); + xmp.GetProperty ( ns, xmpPath.c_str(), &xmpValue, 0 ); + xmp.GetQualifier ( ns, xmpPath.c_str(), kXMP_NS_XML, "lang", &xmpLang, 0 ); + if ( xmpLang == "x-default" ) continue; + + XMP_Uns16 macLang = GetMacLang ( &xmpLang ); + if ( macLang == kNoMacLang ) continue; + + size_t qtIndex, qtLimit; + for ( qtIndex = 0, qtLimit = qtValues->size(); qtIndex < qtLimit; ++qtIndex ) { + if ( (*qtValues)[qtIndex].macLang == macLang ) break; + } - if ( ignoreInit ) return; + if ( qtIndex == qtLimit ) { + // No existing QuickTime item, try to create one. + if ( ! IsMacLangKnown ( macLang ) ) continue; + qtValues->push_back ( ValueInfo() ); + qtIndex = qtValues->size() - 1; + ValueInfo * newItem = &((*qtValues)[qtIndex]); + newItem->macLang = macLang; + newItem->xmpLang = GetXMPLang ( macLang ); // ! Use the 2 character root language. + } - ::ExitMovies(); + ValueInfo * qtItem = &((*qtValues)[qtIndex]); + qtItem->marked = true; // Mark it whether updated or not, don't delete it in the next pass. - #if XMP_WinBuild - ::TerminateQTML(); - #endif + convertOK = ConvertToMacLang ( xmpValue, qtItem->macLang, &macValue ); + if ( convertOK && (macValue != qtItem->macValue) ) { + qtItem->macValue.swap ( macValue ); // No need to make a copy. + haveMappings = true; + } + + } + this->changed |= haveMappings; + infoPos->second.changed |= haveMappings; - } // MainTerminate + // Go through the QuickTime items that are unmarked and delete those that have an xmpLang + // and known macScript. Clear all marks. + + for ( int i = (int)qtValues->size()-1; i > 0; --i ) { // ! Need a signed index. + ValueInfo * qtItem = &((*qtValues)[i]); + if ( qtItem->marked ) { + qtItem->marked = false; + } else if ( (qtItem->xmpLang[0] != 0) && IsMacLangKnown ( qtItem->macLang ) ) { + qtValues->erase ( qtValues->begin() + i ); + this->changed = infoPos->second.changed = true; + } + } - // ============================================================================================= + // If there were no mappings, export the XMP default item to the first QT item. - bool ThreadInitialize() - { - OSStatus err = noErr; + if ( (! haveMappings) && (! qtValues->empty()) ) { + + bool ok = xmp.GetLocalizedText ( ns, langArray, "", "x-default", 0, &xmpValue, 0 ); + if ( ! ok ) return; - #if XMP_MacBuild - err = ::EnterMoviesOnThread ( 0 ); - #endif + ValueInfo * qtItem = &((*qtValues)[0]); + if ( ! IsMacLangKnown ( qtItem->macLang ) ) return; - return (err == noErr); + convertOK = ConvertToMacLang ( xmpValue, qtItem->macLang, &macValue ); + if ( convertOK && (macValue != qtItem->macValue) ) { + qtItem->macValue.swap ( macValue ); // No need to make a copy. + this->changed = infoPos->second.changed = true; + } + + } + +} // TradQT_Manager::ExportLangAltXMP + +// ================================================================================================= +// TradQT_Manager::UpdateChangedBoxes +// ================================== + +void TradQT_Manager::UpdateChangedBoxes ( MOOV_Manager * moovMgr ) +{ + MOOV_Manager::BoxInfo udtaInfo; + MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo ); + XMP_Assert ( (udtaRef != 0) || (udtaInfo.childCount == 0) ); + + if ( udtaRef != 0 ) { // Might not have been a moov/udta box in the parse. + + // First go through the moov/udta/©... children and delete those that are not in the map. + + for ( XMP_Uns32 ordinal = udtaInfo.childCount; ordinal > 0; --ordinal ) { // ! Go backwards because of deletions. + + MOOV_Manager::BoxInfo currInfo; + MOOV_Manager::BoxRef currRef = moovMgr->GetNthChild ( udtaRef, (ordinal-1), &currInfo ); + if ( currRef == 0 ) break; // Sanity check, should not happen. + if ( (currInfo.boxType >> 24) != 0xA9 ) continue; + if ( currInfo.contentSize < 2+2+1 ) continue; // These were skipped by ParseCachedBoxes. + + InfoMapPos infoPos = this->parsedBoxes.find ( currInfo.boxType ); + if ( infoPos == this->parsedBoxes.end() ) moovMgr->DeleteNthChild ( udtaRef, (ordinal-1) ); - } // ThreadInitialize + } - // ============================================================================================= + } + + // Now go through the changed items in the map and update them in the moov/udta subtree. + + InfoMapCPos infoPos = this->parsedBoxes.begin(); + InfoMapCPos infoEnd = this->parsedBoxes.end(); + + for ( ; infoPos != infoEnd; ++infoPos ) { - void ThreadTerminate() - { + ParsedBoxInfo * qtItem = (ParsedBoxInfo*) &infoPos->second; + if ( ! qtItem->changed ) continue; + qtItem->changed = false; + + XMP_Uns32 qtTotalSize = 0; // Total size of the QT values, ignoring empty values. + for ( size_t i = 0, limit = qtItem->values.size(); i < limit; ++i ) { + if ( ! qtItem->values[i].macValue.empty() ) { + if ( qtItem->values[i].macValue.size() > 0xFFFF ) qtItem->values[i].macValue.erase ( 0xFFFF ); + qtTotalSize += (XMP_Uns32)(2+2 + qtItem->values[i].macValue.size()); + } + } + + if ( udtaRef == 0 ) { // Might not have been a moov/udta box in the parse. + moovMgr->SetBox ( "moov/udta", 0, 0 ); + udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo ); + XMP_Assert ( udtaRef != 0 ); + } + + if ( qtTotalSize == 0 ) { - #if XMP_MacBuild - ::ExitMoviesOnThread(); - #endif + moovMgr->DeleteTypeChild ( udtaRef, qtItem->id ); - } // ThreadTerminate + } else { + + // Compose the complete box content. + + RawDataBlock fullValue; + fullValue.assign ( qtTotalSize, 0 ); + XMP_Uns8 * valuePtr = &fullValue[0]; + + for ( size_t i = 0, limit = qtItem->values.size(); i < limit; ++i ) { + XMP_Assert ( qtItem->values[i].macValue.size() <= 0xFFFF ); + XMP_Uns16 textSize = (XMP_Uns16)qtItem->values[i].macValue.size(); + if ( textSize == 0 ) continue; + PutUns16BE ( textSize, valuePtr ); valuePtr += 2; + PutUns16BE ( qtItem->values[i].macLang, valuePtr ); valuePtr += 2; + memcpy ( valuePtr, qtItem->values[i].macValue.c_str(), textSize ); valuePtr += textSize; + } + + // Look for an existing box to update, else add a new one. -} // namespace QuickTime_Support + MOOV_Manager::BoxInfo itemInfo; + MOOV_Manager::BoxRef itemRef = moovMgr->GetTypeChild ( udtaRef, qtItem->id, &itemInfo ); + + if ( itemRef != 0 ) { + moovMgr->SetBox ( itemRef, &fullValue[0], qtTotalSize ); + } else { + moovMgr->AddChildBox ( udtaRef, qtItem->id, &fullValue[0], qtTotalSize ); + } + + } -#endif + } + +} // TradQT_Manager::UpdateChangedBoxes + +// ================================================================================================= diff --git a/source/XMPFiles/FormatSupport/QuickTime_Support.hpp b/source/XMPFiles/FormatSupport/QuickTime_Support.hpp index 24f903d..160dfc8 100644 --- a/source/XMPFiles/FormatSupport/QuickTime_Support.hpp +++ b/source/XMPFiles/FormatSupport/QuickTime_Support.hpp @@ -3,27 +3,101 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2009 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // ================================================================================================= -#include "XMP_Environment.h" // ! This must be the first include. -#if ! ( XMP_64 || XMP_UNIXBuild) // Closes at very bottom. +#include "XMP_Environment.h" // ! This must be the first include. -namespace QuickTime_Support -{ - extern bool sMainInitOK; +#include <string> +#include <vector> +#include <map> + +#include "XMPFiles_Impl.hpp" +#include "LargeFileAccess.hpp" +#include "ISOBaseMedia_Support.hpp" +#include "MOOV_Support.hpp" + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// TradQT_Manager +// ============== + +// Support for selected traditional QuickTime metadata items. The supported items are the children +// of the 'moov'/'udta' box whose type begins with 0xA9, a MacRoman copyright symbol. Each of these +// is a box whose contents are a sequence of "mini boxes" analogous to XMP AltText arrays. Each mini +// box has a 16-bit size, 16-bit language code, and text. The language code values are the old +// Macintosh Script Manager langXyz codes, the text encoding is implicit, see Mac Script.h. + +enum { // List of recognized items from the QuickTime 'moov'/'udta' box. + // These items are defined by Adobe. + kQTilst_Reel = 0xA952454CUL, // '©REL' + kQTilst_Timecode = 0xA954494DUL, // '©TIM' + kQTilst_TimeScale = 0xA9545343UL, // '©TSC' + kQTilst_TimeSize = 0xA954535AUL // '©TSZ' +}; + +enum { + kNoMacLang = 0xFFFF, + kNoMacScript = 0xFFFF +}; + +extern bool ConvertToMacLang ( const std::string & utf8Value, XMP_Uns16 macLang, std::string * macValue ); +extern bool ConvertFromMacLang ( const std::string & macValue, XMP_Uns16 macLang, std::string * utf8Value ); + +class TradQT_Manager { +public: + + TradQT_Manager() : changed(false) {}; + + bool ParseCachedBoxes ( const MOOV_Manager & moovMgr ); + + bool ImportSimpleXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr prop ) const; + bool ImportLangAltXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) const; + + void ExportSimpleXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr prop, + bool createWithZeroLang = false ); + void ExportLangAltXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr langArray ); + + bool IsChanged() const { return this->changed; }; - bool MainInitialize ( bool ignoreInit ); // For the main thread. - void MainTerminate ( bool ignoreInit ); + void UpdateChangedBoxes ( MOOV_Manager * moovMgr ); + +private: + + struct ValueInfo { + bool marked; + XMP_Uns16 macLang; + XMP_StringPtr xmpLang; // ! Only set if macLang is known, i.e. the value can be converted. + std::string macValue; + ValueInfo() : marked(false), macLang(kNoMacLang), xmpLang("") {}; + }; + typedef std::vector<ValueInfo> ValueVector; + typedef ValueVector::iterator ValueInfoPos; + typedef ValueVector::const_iterator ValueInfoCPos; + + struct ParsedBoxInfo { + XMP_Uns32 id; + ValueVector values; + bool changed; + ParsedBoxInfo() : id(0), changed(false) {}; + ParsedBoxInfo ( XMP_Uns32 _id ) : id(_id), changed(false) {}; + }; + + typedef std::map < XMP_Uns32, ParsedBoxInfo > InfoMap; // Metadata item kind and content info. + typedef InfoMap::iterator InfoMapPos; + typedef InfoMap::const_iterator InfoMapCPos; - bool ThreadInitialize(); // For background threads. - void ThreadTerminate(); + InfoMap parsedBoxes; + bool changed; + + bool ImportLangItem ( const ValueInfo & qtItem, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) const; -} // namespace QuickTime_Support +}; // TradQT_Manager -#endif // XMP_64 || XMP_UNIXBuild #endif // __QuickTime_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/RIFF.cpp b/source/XMPFiles/FormatSupport/RIFF.cpp new file mode 100644 index 0000000..3992edd --- /dev/null +++ b/source/XMPFiles/FormatSupport/RIFF.cpp @@ -0,0 +1,879 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +// must have access to handler class fields... +#include "RIFF.hpp" +#include "RIFF_Support.hpp" +#include "RIFF_Handler.hpp" + +using namespace RIFF; + +namespace RIFF { + +// GENERAL STATIC FUNCTIONS //////////////////////////////////////// + +Chunk* getChunk ( ContainerChunk* parent, RIFF_MetaHandler* handler ) +{ + LFA_FileRef file = handler->parent->fileRef; + XMP_Uns8 level = handler->level; + XMP_Uns32 peek = LFA_PeekUns32_LE( file ); + + if ( level == 0 ) + { + XMP_Validate( peek == kChunk_RIFF, "expected RIFF chunk not found", kXMPErr_BadFileFormat ); + XMP_Enforce( parent == NULL ); + } + else + { + XMP_Validate( peek != kChunk_RIFF, "unexpected RIFF chunk below top-level", kXMPErr_BadFileFormat ); + XMP_Enforce( parent != NULL ); + } + + switch( peek ) + { + case kChunk_RIFF: + return new ContainerChunk( parent, handler ); + case kChunk_LIST: + { + if ( level != 1 ) break; // only care on this level + + // look further (beyond 4+4 = beyond id+size) to check on relevance + LFA_Seek( file, 8, SEEK_CUR ); + XMP_Uns32 containerType = LFA_PeekUns32_LE( file ); + LFA_Seek( file, -8, SEEK_CUR ); + + bool isRelevantList = ( containerType== kType_INFO || containerType == kType_Tdat ); + if ( !isRelevantList ) break; + + return new ContainerChunk( parent, handler ); + } + case kChunk_XMP: + if ( level != 1 ) break; // ignore on inappropriate levels (might be compound metadata?) + return new XMPChunk( parent, handler ); + case kChunk_DISP: + { + if ( level != 1 ) break; // only care on this level + // peek even further to see if type is 0x001 and size is reasonable + LFA_Seek( file , 4, SEEK_CUR ); // jump DISP + XMP_Uns32 dispSize = LFA_ReadUns32_LE( file ); + XMP_Uns32 dispType = LFA_ReadUns32_LE( file ); + LFA_Seek( file , -12, SEEK_CUR); // rewind, be in front of chunkID again + + // only take as a relevant disp if both criteria met, + // otherwise treat as generic chunk! + if ( (dispType == 0x0001) && ( dispSize < 256 * 1024 ) ) + { + ValueChunk* r = new ValueChunk( parent, handler ); + handler->dispChunk = r; + return r; + } + break; // treat as irrelevant (non-0x1) DISP chunks as generic chunk + } + case kChunk_bext: + { + if ( level != 1 ) break; // only care on this level + // store for now in a value chunk + ValueChunk* r = new ValueChunk( parent, handler ); + handler->bextChunk = r; + return r; + } + case kChunk_PrmL: + { + if ( level != 1 ) break; // only care on this level + ValueChunk* r = new ValueChunk( parent, handler ); + handler->prmlChunk = r; + return r; + } + case kChunk_Cr8r: + { + if ( level != 1 ) break; // only care on this level + ValueChunk* r = new ValueChunk( parent, handler ); + handler->cr8rChunk = r; + return r; + } + case kChunk_JUNQ: + case kChunk_JUNK: + { + JunkChunk* r = new JunkChunk( parent, handler ); + return r; + } + } + // this "default:" section must be ouside switch bracket, to be + // reachable by all those break statements above: + + + // digest 'valuable' container chunks: LIST:INFO, LIST:Tdat + bool insideRelevantList = ( level==2 && parent->id == kChunk_LIST + && ( parent->containerType== kType_INFO || parent->containerType == kType_Tdat )); + + if ( insideRelevantList ) + { + ValueChunk* r = new ValueChunk( parent, handler ); + return r; + } + + // general chunk of no interest, treat as unknown blob + return new Chunk( parent, handler, true, chunk_GENERAL ); +} + +// BASE CLASS CHUNK /////////////////////////////////////////////// +// ad hoc creation +Chunk::Chunk( ContainerChunk* parent, ChunkType c, XMP_Uns32 id ) +{ + this->chunkType = c; // base class assumption + this->parent = parent; + this->id = id; + this->oldSize = 0; + this->newSize = 8; + this->oldPos = 0; // inevitable for ad-hoc + this->needSizeFix = false; + + // good parenting for latter destruction + if ( this->parent != NULL ) + { + this->parent->children.push_back( this ); + if( this->chunkType == chunk_VALUE ) + this->parent->childmap.insert( make_pair( this->id, (ValueChunk*) this ) ); + } +} + +// parsing creation +Chunk::Chunk( ContainerChunk* parent, RIFF_MetaHandler* handler, bool skip, ChunkType c ) +{ + chunkType = c; // base class assumption + this->parent = parent; + this->oldSize = 0; + this->hasChange = false; // [2414649] valid assumption at creation time + + LFA_FileRef file = handler->parent->fileRef; + + this->oldPos = LFA_Tell( file ); + this->id = LFA_ReadUns32_LE( file ); + this->oldSize = LFA_ReadUns32_LE( file ) + 8; + + // Make sure the size is within expected bounds. + XMP_Int64 chunkEnd = this->oldPos + this->oldSize; + XMP_Int64 chunkLimit = handler->oldFileSize; + if ( parent != 0 ) chunkLimit = parent->oldPos + parent->oldSize; + if ( chunkEnd > chunkLimit ) { + bool isUpdate = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenForUpdate ); + bool repairFile = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenRepairFile ); + if ( (! isUpdate) || (repairFile && (parent == 0)) ) { + this->oldSize = chunkLimit - this->oldPos; + } else { + XMP_Throw ( "Bad RIFF chunk size", kXMPErr_BadFileFormat ); + } + } + + this->newSize = this->oldSize; + this->needSizeFix = false; + + if ( skip ) + { + bool ok; + LFA_Seek( file, this->oldSize - 8 , SEEK_CUR, &ok ); + XMP_Validate( ok , "skipped beyond end of file (truncated file?)", kXMPErr_BadFileFormat ); + } + + // "good parenting", essential for latter destruction. + if ( this->parent != NULL ) + { + this->parent->children.push_back( this ); + if( this->chunkType == chunk_VALUE ) + this->parent->childmap.insert( make_pair( this->id, (ValueChunk*) this ) ); + } +} + +void Chunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + // only unknown chunks should reach this method, + // all others must reach overloads, hence little to do here: + hasChange = false; // unknown chunk ==> no change, naturally + this->newSize = this->oldSize; +} + +std::string Chunk::toString(XMP_Uns8 level ) +{ + char buffer[256]; + snprintf( buffer, 255, "%.4s -- " + "oldSize: 0x%.8llX, " + "newSize: 0x%.8llX, " + "oldPos: 0x%.8llX\n", + (char*)(&this->id), this->oldSize, this->newSize, this->oldPos ); + return std::string(buffer); +} + +void Chunk::write( RIFF_MetaHandler* handler, LFA_FileRef file , bool isMainChunk ) +{ + throw new XMP_Error(kXMPErr_InternalFailure, "Chunk::write never to be called for unknown chunks."); +} + +Chunk::~Chunk() +{ + //nothing +} + +// CONTAINER CHUNK ///////////////////////////////////////////////// +// a) creation +// [2376832] expectedSize - minimum padding "parking size" to use, if not available append to end +ContainerChunk::ContainerChunk( ContainerChunk* parent, XMP_Uns32 id, XMP_Uns32 containerType ) : Chunk( NULL /* !! */, chunk_CONTAINER, id ) +{ + // accept no unparented ConatinerChunks + XMP_Enforce( parent != NULL ); + + this->containerType = containerType; + this->newSize = 12; + this->parent = parent; + + chunkVect* siblings = &parent->children; + + // add at end. ( oldSize==0 will flag optimization later in the process) + siblings->push_back( this ); +} + +// b) parsing +ContainerChunk::ContainerChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_CONTAINER ) +{ + bool repairMode = ( 0 != ( handler->parent->openFlags & kXMPFiles_OpenRepairFile )); + + try + { + LFA_FileRef file = handler->parent->fileRef; + XMP_Uns8 level = handler->level; + + // get type of container chunk + this->containerType = LFA_ReadUns32_LE( file ); + + // ensure legality of top-level chunks + if ( level == 0 && handler->riffChunks.size() > 0 ) + { + XMP_Validate( handler->parent->format == kXMP_AVIFile, "only AVI may have multiple top-level chunks", kXMPErr_BadFileFormat ); + XMP_Validate( this->containerType == kType_AVIX, "all chunks beyond main chunk must be type AVIX", kXMPErr_BadFileFormat ); + } + + // has *relevant* subChunks? (there might be e.g. non-INFO LIST chunks we don't care about) + bool hasSubChunks = ( ( this->id == kChunk_RIFF ) || + ( this->id == kChunk_LIST && this->containerType == kType_INFO ) || + ( this->id == kChunk_LIST && this->containerType == kType_Tdat ) + ); + XMP_Int64 endOfChunk = this->oldPos + this->oldSize; + + // this statement catches beyond-EoF-offsets on any level + // exception: level 0, tolerate if in repairMode + if ( (level == 0) && repairMode && (endOfChunk > handler->oldFileSize) ) + { + endOfChunk = handler->oldFileSize; // assign actual file size + this->oldSize = endOfChunk - this->oldPos; //reversely calculate correct oldSize + } + + XMP_Validate( endOfChunk <= handler->oldFileSize, "offset beyond EoF", kXMPErr_BadFileFormat ); + + Chunk* curChild = 0; + if ( hasSubChunks ) + { + handler->level++; + while ( LFA_Tell( file ) < endOfChunk ) + { + curChild = RIFF::getChunk( this, handler ); + + // digest pad byte - no value validation (0), since some 3rd party files have non-0-padding. + if ( LFA_Tell(file) % 2 == 1 ) + { + // [1521093] tolerate missing pad byte at very end of file: + XMP_Uns8 pad; + LFA_Read ( file, &pad, 1 ); // Read the pad, tolerate being at EOF. + + } + + // within relevant LISTs, relentlesly delete junk chunks (create a single one + // at end as part of updateAndChanges() + if ( (containerType== kType_INFO || containerType == kType_Tdat) + && ( curChild->chunkType == chunk_JUNK ) ) + { + this->children.pop_back(); + delete curChild; + } // for other chunks: join neighouring Junk chunks into one + else if ( (curChild->chunkType == chunk_JUNK) && ( this->children.size() >= 2 ) ) + { + // nb: if there are e.g 2 chunks, then last one is at(1), prev one at(0) ==> '-2' + Chunk* prevChunk = this->children.at( this->children.size() - 2 ); + if ( prevChunk->chunkType == chunk_JUNK ) + { + // stack up size to prior chunk + prevChunk->oldSize += curChild->oldSize; + prevChunk->newSize += curChild->newSize; + XMP_Enforce( prevChunk->oldSize == prevChunk->newSize ); + // destroy current chunk + this->children.pop_back(); + delete curChild; + } + } + } + handler->level--; + XMP_Validate( LFA_Tell( file ) == endOfChunk, "subchunks exceed outer chunk size", kXMPErr_BadFileFormat ); + + // pointers for later legacy processing + if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_INFO ) + handler->listInfoChunk = this; + if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_Tdat ) + handler->listTdatChunk = this; + } + else // skip non-interest container chunk + { + bool ok; + LFA_Seek( file, this->oldSize - 8 - 4, SEEK_CUR, &ok ); + XMP_Validate( ok , "skipped beyond end of file 2 (truncated file?)", kXMPErr_BadFileFormat ); + } // if - else + + } // try + catch (XMP_Error& e) { + this->release(); // free resources + if ( this->parent != 0) + this->parent->children.pop_back(); // hereby taken care of, so removing myself... + + throw e; // re-throw + } +} + +void ContainerChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + + // Walk the container subtree adjusting the children that have size changes. The only containers + // are RIFF and LIST chunks, they are treated differently. + // + // LISTs get recomposed as a whole. Existing JUNK children of a LIST are removed, existing real + // children are left in order with their new size, new children have already been appended. The + // LIST as a whole gets a new size that is the sum of the final children. + // + // Special rules apply to various children of a RIFF container. FIrst, adjacent JUNK children + // are combined, this simplifies maximal reuse. The children are recursively adjusted in order + // to get their final size. + // + // Try to determine the final placement of each RIFF child using general rules: + // - if the size is unchanged: leave at current location + // - if the chunk is at the end of the last RIFF chunk and grows: leave at current location + // - if there is enough following JUNK: add part of the JUNK, adjust remaining JUNK size + // - if it shrinks by 9 bytes or more: carve off trailing JUNK + // - try to find adequate JUNK in the current parent + // + // Use child-specific rules as a last resort: + // - if it is LIST:INFO: delete it, must be in first RIFF chunk + // - for others: move to end of last RIFF chunk, make old space JUNK + + // ! Don't create any junk chunks of exactly 8 bytes, just a header and no content. That has a + // ! size field of zero, which hits a crashing bug in some versions of Windows Media Player. + + bool isRIFFContainer = (this->id == kChunk_RIFF); + bool isLISTContainer = (this->id == kChunk_LIST); + XMP_Enforce ( isRIFFContainer | isLISTContainer ); + + XMP_Index childIndex; // Could be local to the loops, this simplifies debuging. Need a signed type! + Chunk * currChild; + + if ( this->children.empty() ) { + if ( isRIFFContainer) { + this->newSize = 12; // Keep a minimal size container. + } else { + this->newSize = 0; // Will get removed from parent in outer call. + } + this->hasChange = true; + return; // Nothing more to do without children. + } + + // Collapse adjacent RIFF junk children, remove all LIST junk children. Work back to front to + // simplify the effect of .erase() on the loop. Purposely ignore the first chunk. + + for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex > 0; --childIndex ) { + + currChild = this->children[childIndex]; + if ( currChild->chunkType != chunk_JUNK ) continue; + + if ( isRIFFContainer ) { + Chunk * prevChild = this->children[childIndex-1]; + if ( prevChild->chunkType != chunk_JUNK ) continue; + prevChild->oldSize += currChild->oldSize; + prevChild->newSize += currChild->newSize; + prevChild->hasChange = true; + } + + this->children.erase ( this->children.begin() + childIndex ); + delete currChild; + this->hasChange = true; + + } + + // Process the children of RIFF and LIST containers to get their final size. Remove empty + // children. Work back to front to simplify the effect of .erase() on the loop. Do not ignore + // the first chunk. + + for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex >= 0; --childIndex ) { + + currChild = this->children[childIndex]; + + ++handler->level; + currChild->changesAndSize ( handler ); + --handler->level; + + if ( (currChild->newSize == 8) || (currChild->newSize == 0) ) { // ! The newSIze is supposed to include the header. + this->children.erase ( this->children.begin() + childIndex ); + delete currChild; + this->hasChange = true; + } else { + this->hasChange |= currChild->hasChange; + currChild->needSizeFix = (currChild->newSize != currChild->oldSize); + if ( currChild->needSizeFix && (currChild->newSize > currChild->oldSize) && + (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) { + // Let an existing last-in-file chunk grow in-place. Shrinking is conceptually OK, + // but complicates later sanity check that the main AVI chunk is not OK to append + // other chunks later. Ignore new chunks, they might reuse junk space. + if ( currChild->oldSize != 0 ) currChild->needSizeFix = false; + } + } + + } + + // Go through the children of a RIFF container, adjusting the placement as necessary. In brief, + // things can only grow at the end of the last RIFF chunk, and non-junk chunks can't be shifted. + + if ( isRIFFContainer ) { + + for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) { + + currChild = this->children[childIndex]; + if ( ! currChild->needSizeFix ) continue; + currChild->needSizeFix = false; + + XMP_Int64 sizeDiff = currChild->newSize - currChild->oldSize; // Positive for growth. + XMP_Uns8 padSize = (currChild->newSize & 1); // Need a pad for odd size. + + // See if the following chunk is junk that can be utilized. + + Chunk * nextChild = 0; + if ( childIndex+1 < (XMP_Index)this->children.size() ) nextChild = this->children[childIndex+1]; + + if ( (nextChild != 0) && (nextChild->chunkType == chunk_JUNK) ) { + if ( nextChild->newSize >= (9 + sizeDiff + padSize) ) { + + // Incorporate part of the trailing junk, or make the trailing junk grow. + nextChild->newSize -= sizeDiff; + nextChild->newSize -= padSize; + nextChild->hasChange = true; + continue; + + } else if ( nextChild->newSize == (sizeDiff + padSize) ) { + + // Incorporate all of the trailing junk. + this->children.erase ( this->children.begin() + childIndex + 1 ); + delete nextChild; + continue; + + } + } + + // See if the chunk shrinks enough to turn the leftover space into junk. + + if ( (sizeDiff + padSize) <= -9 ) { + this->children.insert ( (this->children.begin() + childIndex + 1), new JunkChunk ( NULL, ((-sizeDiff) - padSize) ) ); + continue; + } + + // Look through the parent for a usable span of junk. + + XMP_Index junkIndex; + Chunk * junkChunk = 0; + for ( junkIndex = 0; junkIndex < (XMP_Index)this->children.size(); ++junkIndex ) { + junkChunk = this->children[junkIndex]; + if ( junkChunk->chunkType != chunk_JUNK ) continue; + if ( (junkChunk->newSize >= (9 + currChild->newSize + padSize)) || + (junkChunk->newSize == (currChild->newSize + padSize)) ) break; + } + + if ( junkIndex < (XMP_Index)this->children.size() ) { + + // Use part or all of the junk for the relocated chunk, replace the old space with junk. + + if ( junkChunk->newSize == (currChild->newSize + padSize) ) { + + // The found junk is an exact fit. + this->children[junkIndex] = currChild; + delete junkChunk; + + } else { + + // The found junk has excess space. Insert the moving chunk and shrink the junk. + XMP_Assert ( junkChunk->newSize >= (9 + currChild->newSize + padSize) ); + junkChunk->newSize -= (currChild->newSize + padSize); + junkChunk->hasChange = true; + this->children.insert ( (this->children.begin() + junkIndex), currChild ); + if ( junkIndex < childIndex ) ++childIndex; // The insertion moved the current child. + + } + + if ( currChild->oldSize != 0 ) { + this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize ); // Replace the old space with junk. + } else { + this->children.erase ( this->children.begin() + childIndex ); // Remove the newly created chunk's old location. + --childIndex; // Make the next loop iteration not skip a chunk. + } + + continue; + + } + + // If this is a LIST:INFO chunk not in the last of multiple RIFF chunks, then give up + // and replace it with oldSize junk. Preserve the first RIFF chunk's original size. + + bool isListInfo = (currChild->id == kChunk_LIST) && (currChild->chunkType == chunk_CONTAINER) && + (((ContainerChunk*)currChild)->containerType == kType_INFO); + + if ( isListInfo && (handler->riffChunks.size() > 1) && + (this->id == kChunk_RIFF) && (this != handler->lastChunk) ) { + + if ( currChild->oldSize != 0 ) { + this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize ); + } else { + this->children.erase ( this->children.begin() + childIndex ); + --childIndex; // Make the next loop iteration not skip a chunk. + } + + delete currChild; + continue; + + } + + // Move the chunk to the end of the last RIFF chunk and make the old space junk. + + if ( (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) continue; // Already last. + + handler->lastChunk->children.push_back( currChild ); + if ( currChild->oldSize != 0 ) { + this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize ); // Replace the old space with junk. + } else { + this->children.erase ( this->children.begin() + childIndex ); // Remove the newly created chunk's old location. + --childIndex; // Make the next loop iteration not skip a chunk. + } + + } + + } + + // Compute the finished container's new size (for both RIFF and LIST). + + this->newSize = 12; // Start with standard container header. + for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) { + currChild = this->children[childIndex]; + this->newSize += currChild->newSize; + this->newSize += (this->newSize & 1); // Round up if odd. + } + + XMP_Validate ( (this->newSize <= 0xFFFFFFFFLL), "No single chunk may be above 4 GB", kXMPErr_Unimplemented ); + +} + +std::string ContainerChunk::toString(XMP_Uns8 level ) +{ + XMP_Int64 offset= 12; // compute offsets, just for informational purposes + // (actually only correct for first chunk) + + char buffer[256]; + snprintf( buffer, 255, "%.4s:%.4s, " + "oldSize: 0x%8llX, " + "newSize: 0x%.8llX, " + "oldPos: 0x%.8llX\n", + (char*)(&this->id), (char*)(&this->containerType), this->oldSize, this->newSize, this->oldPos ); + + std::string r(buffer); + chunkVectIter iter; + for( iter = this->children.begin(); iter != this->children.end(); iter++ ) + { + char buffer[256]; + snprintf( buffer, 250, "offset 0x%.8llX", offset ); + r += std::string ( level*4, ' ' ) + std::string( buffer ) + ":" + (*iter)->toString( level + 1 ); + offset += (*iter)->newSize; + if ( offset % 2 == 1 ) + offset++; + } + return std::string(r); +} + +void ContainerChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk ) +{ + bool ok; + if ( isMainChunk ) + LFA_Rewind( file ); + + // enforce even position + XMP_Int64 chunkStart = LFA_Tell(file); + XMP_Int64 chunkEnd = chunkStart + this->newSize; + XMP_Enforce( chunkStart % 2 == 0 ); + chunkVect *rc = &this->children; + + // [2473303] have to write back-to-front to avoid stomp-on-feet + XMP_Int64 childStart = chunkEnd; + for ( XMP_Int32 chunkNo = (XMP_Int32)(rc->size() -1); chunkNo >= 0; chunkNo-- ) + { + Chunk* cur = rc->at(chunkNo); + + // pad byte first + if ( cur->newSize % 2 == 1 ) + { + childStart--; + LFA_Seek( file, childStart, SEEK_SET ); + LFA_WriteUns8( file, 0 ); + } + + // then contents + childStart-= cur->newSize; + LFA_Seek( file, childStart, SEEK_SET ); + switch ( cur->chunkType ) + { + case chunk_GENERAL: //COULDDO enfore no change, since not write-out-able + if ( cur->oldPos != childStart ) + LFA_Move( file, cur->oldPos, file, childStart, cur->oldSize, 0, 0 ); + break; + default: + cur->write( handler, file, false ); + break; + } // switch + + } // for + XMP_Enforce ( chunkStart + 12 == childStart); + LFA_Seek( file, chunkStart, SEEK_SET, &ok ); + + LFA_WriteUns32_LE( file, this->id ); + LFA_WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above + LFA_WriteUns32_LE( file, this->containerType ); + +} + +void ContainerChunk::release() +{ + // free subchunks + Chunk* curChunk; + while( ! this->children.empty() ) + { + curChunk = this->children.back(); + delete curChunk; + this->children.pop_back(); + } +} + +ContainerChunk::~ContainerChunk() +{ + this->release(); // free resources +} + +// XMP CHUNK /////////////////////////////////////////////// +// a) create + +// a) creation +XMPChunk::XMPChunk( ContainerChunk* parent ) : Chunk( parent, chunk_XMP , kChunk_XMP ) +{ + // nothing +} + +// b) parse +XMPChunk::XMPChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_XMP ) +{ + chunkType = chunk_XMP; + LFA_FileRef file = handler->parent->fileRef; + XMP_Uns8 level = handler->level; + + handler->packetInfo.offset = this->oldPos + 8; + handler->packetInfo.length = (XMP_Int32) this->oldSize - 8; + + handler->xmpPacket.reserve ( handler->packetInfo.length ); + handler->xmpPacket.assign ( handler->packetInfo.length, ' ' ); + LFA_Read ( file, (void*)handler->xmpPacket.data(), handler->packetInfo.length, kLFA_RequireAll ); + + handler->containsXMP = true; // last, after all possible failure + + // pointer for later processing + handler->xmpChunk = this; +} + +void XMPChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + XMP_Enforce( &handler->xmpPacket != 0 ); + XMP_Enforce( handler->xmpPacket.size() > 0 ); + this->newSize = 8 + handler->xmpPacket.size(); + + XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure ); + + // a complete no-change would have been caught in XMPFiles common code anyway + this->hasChange = true; +} + +void XMPChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk ) +{ + LFA_WriteUns32_LE( file, kChunk_XMP ); + LFA_WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above + LFA_Write( file, handler->xmpPacket.data(), (XMP_Int32)handler->xmpPacket.size() ); +} + +// Value CHUNK /////////////////////////////////////////////// +// a) creation +ValueChunk::ValueChunk( ContainerChunk* parent, std::string value, XMP_Uns32 id ) : Chunk( parent, chunk_VALUE, id ) +{ + this->oldValue = std::string(); + this->SetValue( value ); +} + +// b) parsing +ValueChunk::ValueChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_VALUE ) +{ + // set value: ----------------- + LFA_FileRef file = handler->parent->fileRef; + XMP_Uns8 level = handler->level; + + // unless changed through reconciliation, assume for now. + // IMPORTANT to stay true to the original (no \0 cleanup or similar) + // since unknown value chunks might not be fully understood, + // hence must be precisely preserved !!! + + XMP_Int32 length = (XMP_Int32) this->oldSize - 8; + this->oldValue.reserve( length ); + this->oldValue.assign( length + 1, '\0' ); + LFA_Read ( file, (void*)this->oldValue.data(), length, kLFA_RequireAll ); + + this->newValue = this->oldValue; + this->newSize = this->oldSize; +} + +void ValueChunk::SetValue( std::string value, bool optionalNUL /* = false */ ) +{ + this->newValue.assign( value ); + if ( (! optionalNUL) || ((value.size() & 1) == 1) ) { + // ! The NUL should be optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte. + this->newValue.append( 1, '\0' ); // append zero termination as explicit part of string + } + this->newSize = this->newValue.size() + 8; +} + +void ValueChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + // Don't simply assign to this->hasChange, it might already be true. + if ( this->newValue.size() != this->oldValue.size() ) { + this->hasChange = true; + } else if ( strncmp ( this->oldValue.c_str(), this->newValue.c_str(), this->newValue.size() ) != 0 ) { + this->hasChange = true; + } +} + +void ValueChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk ) +{ + LFA_WriteUns32_LE( file, this->id ); + LFA_WriteUns32_LE( file, (XMP_Uns32)this->newSize - 8 ); + LFA_Write( file, this->newValue.data() , (XMP_Int32)this->newSize - 8 ); +} + +/* remove value chunk if existing. + return true if it was existing. */ +bool ContainerChunk::removeValue( XMP_Uns32 id ) +{ + valueMap* cm = &this->childmap; + valueMapIter iter = cm->find( id ); + + if( iter == cm->end() ) + return false; //not found + + ValueChunk* propChunk = iter->second; + + // remove from vector (difficult) + chunkVect* cv = &this->children; + chunkVectIter cvIter; + for (cvIter = cv->begin(); cvIter != cv->end(); ++cvIter ) + { + if ( (*cvIter)->id == id ) + break; // found! + } + XMP_Validate( cvIter != cv->end(), "property not found in children vector", kXMPErr_InternalFailure ); + cv->erase( cvIter ); + + // remove from map (easy) + cm->erase( iter ); + + delete propChunk; + return true; // found and removed +} + +/* returns iterator to (first) occurence of this chunk. + iterator to the end of the map if chunk pointer is not found */ +chunkVectIter ContainerChunk::getChild( Chunk* needle ) +{ + chunkVectIter iter; + for( iter = this->children.begin(); iter != this->children.end(); iter++ ) + { + Chunk* temp1 = *iter; + Chunk* temp2 = needle; + if ( (*iter) == needle ) return iter; + } + return this->children.end(); +} + +/* replaces a chunk by a JUNK chunk. + Also frees memory of prior chunk. */ +void ContainerChunk::replaceChildWithJunk( Chunk* child, bool deleteChild ) +{ + chunkVectIter iter = getChild( child ); + if ( iter == this->children.end() ) { + throw new XMP_Error(kXMPErr_InternalFailure, "replaceChildWithJunk: childChunk not found."); + } + + *iter = new JunkChunk ( NULL, child->oldSize ); + if ( deleteChild ) delete child; + + this->hasChange = true; +} + +// JunkChunk /////////////////////////////////////////////////// +// a) creation +JunkChunk::JunkChunk( ContainerChunk* parent, XMP_Int64 size ) : Chunk( parent, chunk_JUNK, kChunk_JUNK ) +{ + XMP_Assert( size >= 8 ); + this->oldSize = size; + this->newSize = size; + this->hasChange = true; +} + +// b) parsing +JunkChunk::JunkChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, true, chunk_JUNK ) +{ + chunkType = chunk_JUNK; +} + +void JunkChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + this->newSize = this->oldSize; // optimization at a later stage + XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure ); + if ( this->id == kChunk_JUNQ ) this->hasChange = true; // Force ID change to JUNK. +} + +// zeroBuffer, etc to write out empty native padding +const static XMP_Uns32 kZeroBufferSize64K = 64 * 1024; +static XMP_Uns8 kZeroes64K [ kZeroBufferSize64K ]; // C semantics guarantee zero initialization. + +void JunkChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk ) +{ + LFA_WriteUns32_LE( file, kChunk_JUNK ); // write JUNK, never JUNQ + XMP_Enforce( this->newSize < 0xFFFFFFFF ); + XMP_Enforce( this->newSize >= 8 ); // minimum size of any chunk + XMP_Uns32 innerSize = (XMP_Uns32)this->newSize - 8; + LFA_WriteUns32_LE( file, innerSize ); + + // write out in 64K chunks + while ( innerSize > kZeroBufferSize64K ) + { + LFA_Write( file, kZeroes64K , kZeroBufferSize64K ); + innerSize -= kZeroBufferSize64K; + } + LFA_Write( file, kZeroes64K , innerSize ); +} + +} // namespace RIFF diff --git a/source/XMPFiles/FormatSupport/RIFF.hpp b/source/XMPFiles/FormatSupport/RIFF.hpp new file mode 100644 index 0000000..34b2100 --- /dev/null +++ b/source/XMPFiles/FormatSupport/RIFF.hpp @@ -0,0 +1,316 @@ +#ifndef __RIFF_hpp__ +#define __RIFF_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMP_Environment.h" // ! This must be the first include. +#include <vector> +#include <map> +#include "XMPFiles_Impl.hpp" + +// ahead declaration: +class RIFF_MetaHandler; + +namespace RIFF { + + enum ChunkType { + chunk_GENERAL, //unknown or not relevant + chunk_CONTAINER, + chunk_XMP, + chunk_VALUE, + chunk_JUNK, + NO_CHUNK // used as precessor to first chunk, etc. + }; + + // ahead declarations + class Chunk; + class ContainerChunk; + class ValueChunk; + class XMPChunk; + + // (scope: only used in RIFF_Support and RIFF_Handler.cpp + // ==> no need to overspecify with lengthy names ) + + typedef std::vector<Chunk*> chunkVect; // coulddo: narrow down toValueChunk (could give trouble with JUNK though) + typedef chunkVect::iterator chunkVectIter; // or refactor ?? + + typedef std::vector<ContainerChunk*> containerVect; + typedef containerVect::iterator containerVectIter; + + typedef std::map<XMP_Uns32,ValueChunk*> valueMap; + typedef valueMap::iterator valueMapIter; + + + // format chunks+types + const XMP_Uns32 kChunk_RIFF = 0x46464952; + const XMP_Uns32 kType_AVI_ = 0x20495641; + const XMP_Uns32 kType_AVIX = 0x58495641; + const XMP_Uns32 kType_WAVE = 0x45564157; + + const XMP_Uns32 kChunk_JUNK = 0x4B4E554A; + const XMP_Uns32 kChunk_JUNQ = 0x514E554A; + + // other container chunks + const XMP_Uns32 kChunk_LIST = 0x5453494C; + const XMP_Uns32 kType_INFO = 0x4F464E49; + const XMP_Uns32 kType_Tdat = 0x74616454; + + // other relevant chunks + const XMP_Uns32 kChunk_XMP = 0x584D505F; // "_PMX" + + // relevant for Index Correction + // LIST: + const XMP_Uns32 kType_hdrl = 0x6C726468; + const XMP_Uns32 kType_strl = 0x6C727473; + const XMP_Uns32 kChunk_indx = 0x78646E69; + const XMP_Uns32 kChunk_ixXX = 0x58587869; + const XMP_Uns32 kType_movi = 0x69766F6D; + + //should occur only in AVI + const XMP_Uns32 kChunk_Cr8r = 0x72387243; + const XMP_Uns32 kChunk_PrmL = 0x4C6D7250; + + //should occur only in WAV + const XMP_Uns32 kChunk_DISP = 0x50534944; + const XMP_Uns32 kChunk_bext = 0x74786562; + + // LIST/INFO constants + const XMP_Uns32 kPropChunkIART = 0x54524149; + const XMP_Uns32 kPropChunkICMT = 0x544D4349; + const XMP_Uns32 kPropChunkICOP = 0x504F4349; + const XMP_Uns32 kPropChunkICRD = 0x44524349; + const XMP_Uns32 kPropChunkIENG = 0x474E4549; + const XMP_Uns32 kPropChunkIGNR = 0x524E4749; + const XMP_Uns32 kPropChunkINAM = 0x4D414E49; + const XMP_Uns32 kPropChunkISFT = 0x54465349; + const XMP_Uns32 kPropChunkIARL = 0x4C524149; + + const XMP_Uns32 kPropChunkIMED = 0x44454D49; + const XMP_Uns32 kPropChunkISRF = 0x46525349; + const XMP_Uns32 kPropChunkICMS = 0x4C524149; + const XMP_Uns32 kPropChunkIPRD = 0x534D4349; + const XMP_Uns32 kPropChunkISRC = 0x44525049; + const XMP_Uns32 kPropChunkITCH = 0x43525349; + + const XMP_Uns32 kPropChunk_tc_O =0x4F5F6374; + const XMP_Uns32 kPropChunk_tc_A =0x415F6374; + const XMP_Uns32 kPropChunk_rn_O =0x4F5F6E72; + const XMP_Uns32 kPropChunk_rn_A =0x415F6E72; + + /////////////////////////////////////////////////////////////// + + enum PropType { // from a simplified, opinionated legacy angle + prop_SIMPLE, + prop_TIMEVALUE, + prop_LOCALIZED_TEXT, + prop_ARRAYITEM, // ( here: a solitary one) + }; + + struct Mapping { + XMP_Uns32 chunkID; + const char* ns; + const char* prop; + PropType propType; + }; + + // bext Mappings, piece-by-piece: + static Mapping bextDescription = { 0, kXMP_NS_BWF, "description", prop_SIMPLE }; + static Mapping bextOriginator = { 0, kXMP_NS_BWF, "originator", prop_SIMPLE }; + static Mapping bextOriginatorRef = { 0, kXMP_NS_BWF, "originatorReference", prop_SIMPLE }; + static Mapping bextOriginationDate = { 0, kXMP_NS_BWF, "originationDate", prop_SIMPLE }; + static Mapping bextOriginationTime = { 0, kXMP_NS_BWF, "originationTime", prop_SIMPLE }; + static Mapping bextTimeReference = { 0, kXMP_NS_BWF, "timeReference", prop_SIMPLE }; + static Mapping bextVersion = { 0, kXMP_NS_BWF, "version", prop_SIMPLE }; + static Mapping bextUMID = { 0, kXMP_NS_BWF, "umid", prop_SIMPLE }; + static Mapping bextCodingHistory = { 0, kXMP_NS_BWF, "codingHistory", prop_SIMPLE }; + + // LIST:INFO properties + static Mapping listInfoProps[] = { + // reconciliations CS4 and before: + { kPropChunkIART, kXMP_NS_DM, "artist" , prop_SIMPLE }, + { kPropChunkICMT, kXMP_NS_DM, "logComment" , prop_SIMPLE }, + { kPropChunkICOP, kXMP_NS_DC, "rights" , prop_LOCALIZED_TEXT }, + { kPropChunkICRD, kXMP_NS_XMP, "CreateDate" , prop_SIMPLE }, + { kPropChunkIENG, kXMP_NS_DM, "engineer" , prop_SIMPLE }, + { kPropChunkIGNR, kXMP_NS_DM, "genre" , prop_SIMPLE }, + { kPropChunkINAM, kXMP_NS_DC, "title" , prop_LOCALIZED_TEXT }, // ( was wrongly dc:album in pre-CS4) + { kPropChunkISFT, kXMP_NS_XMP, "CreatorTool", prop_SIMPLE }, + + // RIFF/*/LIST/INFO properties, new in CS5, both AVI and WAV + + { kPropChunkIMED, kXMP_NS_DC, "source" , prop_SIMPLE }, + { kPropChunkISRF, kXMP_NS_DC, "type" , prop_ARRAYITEM }, + // TO ENABLE { kPropChunkIARL, kXMP_NS_DC, "subject" , prop_SIMPLE }, // array !! (not x-default language alternative) + //{ kPropChunkICMS, to be decided, "" , prop_SIMPLE }, + //{ kPropChunkIPRD, to be decided, "" , prop_SIMPLE }, + //{ kPropChunkISRC, to be decided, "" , prop_SIMPLE }, + //{ kPropChunkITCH, to be decided, "" , prop_SIMPLE }, + + { 0, 0, 0 } // sentinel + }; + + static Mapping listTdatProps[] = { + // reconciliations CS4 and before: + { kPropChunk_tc_O, kXMP_NS_DM, "startTimecode" , prop_TIMEVALUE }, // special: must end up in dm:timeValue child + { kPropChunk_tc_A, kXMP_NS_DM, "altTimecode" , prop_TIMEVALUE }, // special: must end up in dm:timeValue child + { kPropChunk_rn_O, kXMP_NS_DM, "tapeName" , prop_SIMPLE }, + { kPropChunk_rn_A, kXMP_NS_DM, "altTapeName" , prop_SIMPLE }, + { 0, 0, 0 } // sentinel + }; + + // ================================================================================================= + // ImportCr8rItems + // =============== + #pragma pack ( push, 1 ) + struct PrmLBoxContent { + XMP_Uns32 magic; + XMP_Uns32 size; + XMP_Uns16 verAPI; + XMP_Uns16 verCode; + XMP_Uns32 exportType; + XMP_Uns16 MacVRefNum; + XMP_Uns32 MacParID; + char filePath[260]; + }; + + enum { kExportTypeMovie = 0, kExportTypeStill = 1, kExportTypeAudio = 2, kExportTypeCustom = 3 }; + + struct Cr8rBoxContent { + XMP_Uns32 magic; + XMP_Uns32 size; + XMP_Uns16 majorVer; + XMP_Uns16 minorVer; + XMP_Uns32 creatorCode; + XMP_Uns32 appleEvent; + char fileExt[16]; + char appOptions[16]; + char appName[32]; + }; + #pragma pack ( pop ) + + // static getter, determines appropriate chunkType (peeking)and returns + // the respective constructor. It's the caller's responsibility to + // delete obtained chunk. + Chunk* getChunk ( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + class Chunk + { + public: + ChunkType chunkType; // set by constructor + ContainerChunk* parent; // 0 on top-level + + XMP_Uns32 id; // the first four bytes, first byte of highest value + XMP_Int64 oldSize; // actual chunk size INCLUDING the 8/12 header bytes, + XMP_Int64 oldPos; // file position of this chunk + + // both set as part of changesAndSize() + XMP_Int64 newSize; + bool hasChange; + bool needSizeFix; // used in changesAndSize() only + + // Constructors /////////////////////// + // parsing + Chunk( ContainerChunk* parent, RIFF_MetaHandler* handler, bool skip, ChunkType c /*= chunk_GENERAL*/ ); + // ad-hoc creation + Chunk( ContainerChunk* parent, ChunkType c, XMP_Uns32 id ); + + /* returns true, if something has changed in chunk (which needs specific write-out, + this->newSize is expected to be set by this routine */ + virtual void changesAndSize( RIFF_MetaHandler* handler ); + virtual std::string toString(XMP_Uns8 level = 0); + virtual void write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk = false ); + + virtual ~Chunk(); + + }; // class Chunk + + class XMPChunk : public Chunk + { + public: + XMPChunk( ContainerChunk* parent ); + XMPChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + void changesAndSize( RIFF_MetaHandler* handler ); + void write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk = false ); + + }; + + // any chunk, whose value should be stored, e.g. LIST:INFO, LIST:Tdat + class ValueChunk : public Chunk + { + public: + std::string oldValue, newValue; + + // for ad-hoc creation (upon write) + ValueChunk( ContainerChunk* parent, std::string value, XMP_Uns32 id ); + + // for parsing + ValueChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + enum { kNULisOptional = true }; + + void SetValue( std::string value, bool optionalNUL = false ); + void changesAndSize( RIFF_MetaHandler* handler ); + void write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk = false ); + + // own destructor not needed. + }; + + // relevant (level 1) JUNQ and JUNK chunks... + class JunkChunk : public Chunk + { + public: + // construction + JunkChunk( ContainerChunk* parent, XMP_Int64 size ); + // parsing + JunkChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + // own destructor not needed. + + void changesAndSize( RIFF_MetaHandler* handler ); + void write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk = false ); + }; + + + class ContainerChunk : public Chunk + { + public: + XMP_Uns32 containerType; // e.g. kType_INFO as in "LIST:INFO" + + chunkVect children; // used for cleanup/destruction, ordering... + valueMap childmap; // only for efficient *value* access (inside LIST), *not* used for other containers + + // construct + ContainerChunk( ContainerChunk* parent, XMP_Uns32 id, XMP_Uns32 containerType ); + // parse + ContainerChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + bool removeValue( XMP_Uns32 id ); + + /* returns iterator to (first) occurence of this chunk. + iterator to the end of the map if chunk pointer is not found */ + chunkVectIter getChild( Chunk* needle ); + + void replaceChildWithJunk( Chunk* child, bool deleteChild = true ); + + void changesAndSize( RIFF_MetaHandler* handler ); + std::string toString(XMP_Uns8 level = 0); + void write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk = false ); + + // destroy + void release(); // used by destructor and on error in constructor + ~ContainerChunk(); + + }; // class ContainerChunk + +} // namespace RIFF + + +#endif // __RIFF_hpp__ diff --git a/source/XMPFiles/FormatSupport/RIFF_Support.cpp b/source/XMPFiles/FormatSupport/RIFF_Support.cpp index 9574119..fe7e568 100644 --- a/source/XMPFiles/FormatSupport/RIFF_Support.cpp +++ b/source/XMPFiles/FormatSupport/RIFF_Support.cpp @@ -1,851 +1,930 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // ================================================================================================= -#include "RIFF_Support.hpp" +#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#if XMP_WinBuild - #pragma warning ( disable : 4996 ) // '...' was declared deprecated -#endif +// must have access to handler class fields... +#include "RIFF.hpp" +#include "RIFF_Handler.hpp" +#include "RIFF_Support.hpp" +#include "Reconcile_Impl.hpp" -namespace RIFF_Support { +using namespace RIFF; +namespace RIFF { - #define ckidPremierePadding MakeFourCC ('J','U','N','Q') - #define formtypeAVIX MakeFourCC ('A', 'V', 'I', 'X') +// The minimum BEXT chunk size should be 610 (incl. 8 byte header/size field) +XMP_Int32 MIN_BEXT_SIZE = 610; // = > 8 + ( 256+32+32+10+8+4+4+2+64+190+0 ) +// An assumed secure max value of 100 MB. +XMP_Int32 MAX_BEXT_SIZE = 100 * 1024 * 1024; - #ifndef AVIMAXCHUNKSIZE - #define AVIMAXCHUNKSIZE ((UInt32) 0x80000000) /* 2 GB */ - #endif +// CR8R, PrmL have fixed sizes +XMP_Int32 CR8R_SIZE = 0x5C; +XMP_Int32 PRML_SIZE = 0x122; +static const char* sHexChars = "0123456789ABCDEF"; - typedef struct - { - long id; - UInt32 len; - } atag; +// Encode a string of raw data bytes into a HexString (w/o spaces, i.e. "DEADBEEF"). +// No insertation/acceptance of whitespace/linefeeds. No output/tolerance of lowercase. +// returns true, if *all* characters returned are zero (or if 0 bytes are returned). +static bool EncodeToHexString ( XMP_StringPtr rawStr, + XMP_StringLen rawLen, + std::string* encodedStr ) +{ + bool allZero = true; // assume for now - // Local function declarations - static bool ReadTag ( LFA_FileRef inFileRef, long * outTag, UInt32 * outLength, long * subtype, UInt64 & inOutPosition, UInt64 maxOffset ); - static void AddTag ( RiffState & inOutRiffState, long tag, UInt32 len, UInt64 & inOutPosition, long parentID, long parentnum, long subtypeID ); - static long SubRead ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long parentid, UInt32 parentlen, UInt64 & inOutPosition ); - static bool ReadChunk ( LFA_FileRef inFileRef, UInt64 & pos, UInt32 len, char * outBuffer ); + if ( (rawStr == 0) && (rawLen != 0) ) + XMP_Throw ( "EncodeToHexString: null rawStr", kXMPErr_BadParam ); + if ( encodedStr == 0 ) + XMP_Throw ( "EncodeToHexString: null encodedStr", kXMPErr_BadParam ); - #define GetFilePosition(file) LFA_Seek ( file, 0, SEEK_CUR ) - - // ============================================================================================= + encodedStr->erase(); + if ( rawLen == 0 ) return allZero; + encodedStr->reserve ( rawLen * 2 ); - bool GetMetaData ( LFA_FileRef inFileRef, long tagID, char * outBuffer, unsigned long * outBufferSize ) + for( XMP_Uns32 i = 0; i < rawLen; i++ ) { - RiffState riffState; - - long numTags = OpenRIFF ( inFileRef, riffState ); - if ( numTags == 0 ) return false; - - return GetRIFFChunk ( inFileRef, riffState, tagID, 0, 0, outBuffer, outBufferSize ); - - } + // first, second nibble + XMP_Uns8 first = rawStr[i] >> 4; + XMP_Uns8 second = rawStr[i] & 0xF; - // ============================================================================================= + if ( allZero && (( first != 0 ) || (second != 0))) + allZero = false; - bool SetMetaData ( LFA_FileRef inFileRef, long riffType, long tagID, const char * inBuffer, unsigned long inBufferSize ) - { - RiffState riffState; - - long numTags = OpenRIFF ( inFileRef, riffState ); - if ( numTags == 0 ) return false; - - return PutChunk ( inFileRef, riffState, riffType, tagID, inBuffer, inBufferSize ); - + encodedStr->append( 1, sHexChars[first] ); + encodedStr->append( 1, sHexChars[second] ); } - // ============================================================================================= + return allZero; +} // EncodeToHexString - bool MarkChunkAsPadding ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, long tagID, long subtypeID ) - { - UInt32 len; - UInt64 pos; - atag tag; - - try { - - bool found = FindChunk ( inOutRiffState, tagID, riffType, subtypeID, NULL, &len, &pos ); - if ( ! found ) return false; - - if ( subtypeID != 0 ) { - pos -= 12; - } else { - pos -= 8; - } +// ------------------------------------------------------------------------------------------------- +// DecodeFromHexString +// ---------------- +// +// Decode a hex string to raw data bytes. +// * Input must be all uppercase and w/o any whitespace, strictly (0-9A-Z)* (i.e. "DEADBEEF0099AABC") +// * No insertation/acceptance of whitespace/linefeeds. +// * bNo use/tolerance of lowercase. +// * Number of bytes in the encoded String must be even. +// * returns true if everything went well, false if illegal (non 0-9A-F) character encountered + +static bool DecodeFromHexString ( XMP_StringPtr encodedStr, + XMP_StringLen encodedLen, + std::string* rawStr ) +{ + if ( (encodedLen % 2) != 0 ) + return false; + rawStr->erase(); + if ( encodedLen == 0 ) return true; + rawStr->reserve ( encodedLen / 2 ); + + for( XMP_Uns32 i = 0; i < encodedLen; ) + { + XMP_Uns8 upperNibble = encodedStr[i]; + if ( (upperNibble < 48) || ( (upperNibble > 57 ) && ( upperNibble < 65 ) ) || (upperNibble > 70) ) + return false; + if ( upperNibble >= 65 ) + upperNibble -= 7; // shift A-F area adjacent to 0-9 + upperNibble -= 48; // 'shift' to a value [0..15] + upperNibble = ( upperNibble << 4 ); + i++; + + XMP_Uns8 lowerNibble = encodedStr[i]; + if ( (lowerNibble < 48) || ( (lowerNibble > 57 ) && ( lowerNibble < 65 ) ) || (lowerNibble > 70) ) + return false; + if ( lowerNibble >= 65 ) + lowerNibble -= 7; // shift A-F area adjacent to 0-9 + lowerNibble -= 48; // 'shift' to a value [0..15] + i++; - tag.id = MakeUns32LE ( ckidPremierePadding ); - LFA_Seek ( inFileRef, pos, SEEK_SET ); - LFA_Write ( inFileRef, &tag, 4 ); - - pos += 8; - AddTag ( inOutRiffState, ckidPremierePadding, len, pos, 0, 0, 0 ); - - } catch(...) { + rawStr->append ( 1, (upperNibble + lowerNibble) ); + } + return true; +} // DecodeFromHexString + +// Converts input string to an ascii output string +// - terminates at first 0 +// - replaces all non ascii with 0x3F ('?') +// - produces up to maxOut characters (note that several UTF-8 character bytes can 'melt' to one byte '?' in ascii.) +static XMP_StringLen convertToASCII( XMP_StringPtr input, XMP_StringLen inputLen, std::string* output, XMP_StringLen maxOutputLen ) +{ + if ( (input == 0) && (inputLen != 0) ) + XMP_Throw ( "convertToASCII: null input string", kXMPErr_BadParam ); + if ( output == 0) + XMP_Throw ( "convertToASCII: null output string", kXMPErr_BadParam ); + if ( maxOutputLen == 0) + XMP_Throw ( "convertToASCII: zero maxOutputLen chars", kXMPErr_BadParam ); - return false; // If a write fails, it throws, so we return false. + output->reserve(inputLen); + output->erase(); + bool isUTF8 = ReconcileUtils::IsUTF8( input, inputLen ); + XMP_StringLen outputLen = 0; + + for ( XMP_Uns32 i=0; i < inputLen; i++ ) + { + XMP_Uns8 c = (XMP_Uns8) input[i]; + if ( c == 0 ) // early 0 termination, leave. + break; + if ( c > 127 ) // uft-8 multi-byte sequence. + { + if ( isUTF8 ) // skip all high bytes + { + // how many bytes in this ? + if ( c >= 0xC2 && c <= 0xDF ) + i+=1; // 2-byte sequence + else if ( c >= 0xE0 && c <= 0xEF ) + i+=2; // 3-byte sequence + else if ( c >= 0xF0 && c <= 0xF4 ) + i+=3; // 4-byte sequence + else + continue; //invalid sequence, look for next 'low' byte .. + } // thereafter and 'else': just append a question mark: + output->append( 1, '?' ); } - - return true; + else // regular valid ascii. 1 byte. + { + output->append( 1, input[i] ); + } + outputLen++; + if ( outputLen >= maxOutputLen ) + break; // (may be even or even greater due to UFT-8 multi-byte jumps) } - // ============================================================================================= + return outputLen; +} - bool PutChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, long tagID, const char * inBuffer, UInt32 inBufferSize ) +/** + * ensures that native property gets returned as UTF-8 (may or mayn not already be UTF-8) + * - also takes care of "moot padding" (pre-mature zero termination) + * - propertyExists: it is important to know if there as an existing, non zero property + * even (in the event of serverMode) it is not actually returned, but an empty string instead. + */ +static std::string nativePropertyToUTF8 ( XMP_StringPtr cstring, XMP_StringLen maxSize, bool* propertyExists ) +{ + // the value might be properly 0-terminated, prematurely or not + // at all, hence scan through to find actual size + XMP_StringLen size = 0; + for ( size = 0; size < maxSize; size++ ) { - UInt32 len; - UInt64 pos; - atag tag; - - // Make sure we're writting an even number of bytes. Required by the RIFF specification. - XMP_Assert ( (inBufferSize & 1) == 0 ); - - try { - - bool found = FindChunk ( inOutRiffState, tagID, 0, 0, NULL, &len, &pos ); - if ( found ) { + if ( cstring[size] == 0 ) + break; + } - if ( len == inBufferSize ) { - LFA_Seek ( inFileRef, pos, SEEK_SET ); - LFA_Write ( inFileRef, inBuffer, inBufferSize ); - return true; - } - - pos -= 8; - tag.id = MakeUns32LE ( ckidPremierePadding ); - LFA_Seek ( inFileRef, pos, SEEK_SET ); - LFA_Write ( inFileRef, &tag, 4 ); - - if ( len > inBufferSize ) { - pos += 8; - AddTag ( inOutRiffState, ckidPremierePadding, len, pos, 0, 0, 0 ); - } + (*propertyExists) = ( size > 0 ); + + std::string utf8(""); + if ( ReconcileUtils::IsUTF8( cstring, size ) ) + utf8 = std::string( cstring, size ); //use utf8 directly + else + { + if ( ! ignoreLocalText ) + { + #if ! UNIX_ENV // n/a anyway, since always ignoreLocalText on Unix + ReconcileUtils::LocalToUTF8( cstring, size, &utf8 ); + #endif + } + } + return utf8; +} - } +// reads maxSize bytes from file (not "up to", exactly fullSize) +// puts it into a string, sets respective tree property +static std::string getBextField ( const char* data, XMP_Uns32 offset, XMP_Uns32 maxSize ) +{ + if (data == 0) + XMP_Throw ( "getBextField: null data pointer", kXMPErr_BadParam ); + if ( maxSize == 0) + XMP_Throw ( "getBextField: maxSize must be greater than 0", kXMPErr_BadParam ); + + std::string r; + convertToASCII( data+offset, maxSize, &r, maxSize ); + return r; +} - } catch ( ... ) { +static void importBextChunkToXMP( RIFF_MetaHandler* handler, ValueChunk* bextChunk ) +{ + // if there's a bext chunk, there is data... + handler->containsXMP = true; // very important for treatment on caller level + + XMP_Enforce( bextChunk->oldSize >= MIN_BEXT_SIZE ); + XMP_Enforce( bextChunk->oldSize < MAX_BEXT_SIZE ); + + const char* data = bextChunk->oldValue.data(); + std::string value; + + // register bext namespace: + SXMPMeta::RegisterNamespace( kXMP_NS_BWF, "bext:", 0 ); + + // bextDescription ------------------------------------------------ + value = getBextField( data, 0, 256 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextDescription.ns, bextDescription.prop, value.c_str() ); + + // bextOriginator ------------------------------------------------- + value = getBextField( data, 256, 32 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginator.ns , bextOriginator.prop, value.c_str() ); + + // bextOriginatorRef ---------------------------------------------- + value = getBextField( data, 256+32, 32 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, value.c_str() ); + + // bextOriginationDate -------------------------------------------- + value = getBextField( data, 256+32+32, 10 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginationDate.ns , bextOriginationDate.prop, value.c_str() ); + + // bextOriginationTime -------------------------------------------- + value = getBextField( data, 256+32+32+10, 8 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginationTime.ns , bextOriginationTime.prop, value.c_str() ); + + // bextTimeReference ---------------------------------------------- + // thanx to nice byte order, all 8 bytes can be read as one: + XMP_Uns64 timeReferenceFull = GetUns64LE( &(data[256+32+32+10+8 ] ) ); + value.erase(); + SXMPUtils::ConvertFromInt64( timeReferenceFull, "%llu", &value ); + handler->xmpObj.SetProperty( bextTimeReference.ns, bextTimeReference.prop, value ); + + // bextVersion ---------------------------------------------------- + XMP_Uns16 bwfVersion = GetUns16LE( &(data[256+32+32+10+8+8] ) ); + value.erase(); + SXMPUtils::ConvertFromInt( bwfVersion, "", &value ); + handler->xmpObj.SetProperty( bextVersion.ns, bextVersion.prop, value ); + + // bextUMID ------------------------------------------------------- + // binary string is already in memory, must convert to hex string + std::string umidString; + bool allZero = EncodeToHexString( &(data[256+32+32+10+8+8+2]), 64, &umidString ); + if (! allZero ) + handler->xmpObj.SetProperty( bextUMID.ns, bextUMID.prop, umidString ); + + // bextCodingHistory ---------------------------------------------- + bool hasCodingHistory = bextChunk->oldSize > MIN_BEXT_SIZE; + + if ( hasCodingHistory ) + { + XMP_StringLen codingHistorySize = (XMP_StringLen) (bextChunk->oldSize - MIN_BEXT_SIZE); + std::string codingHistory; + convertToASCII( &data[MIN_BEXT_SIZE-8], codingHistorySize, &codingHistory, codingHistorySize ); + if (! codingHistory.empty() ) + handler->xmpObj.SetProperty( bextCodingHistory.ns, bextCodingHistory.prop, codingHistory ); + } +} // importBextChunkToXMP - // If a write fails, it throws, so we return false - return false; +static void importPrmLToXMP( RIFF_MetaHandler* handler, ValueChunk* prmlChunk ) +{ + bool haveXMP = false; - } - - bool ok = MakeChunk ( inFileRef, inOutRiffState, riffType, (inBufferSize + 8) ); - if ( ! ok ) return false; - - return WriteChunk ( inFileRef, tagID, inBuffer, inBufferSize ); + XMP_Enforce( prmlChunk->oldSize == PRML_SIZE ); + PrmLBoxContent rawPrmL; + XMP_Assert ( sizeof ( rawPrmL ) == PRML_SIZE - 8 ); // double check tight packing. + XMP_Assert ( sizeof ( rawPrmL.filePath ) == 260 ); + memcpy ( &rawPrmL, prmlChunk->oldValue.data(), sizeof (rawPrmL) ); + if ( rawPrmL.magic != 0xBEEFCAFE ) { + Flip4 ( &rawPrmL.exportType ); // The only numeric field that we care about. } - // ============================================================================================= - - bool RewriteChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long tagID, long parentID, const char * inData ) - { - UInt32 len; - UInt64 pos; - - try { - if ( FindChunk ( inOutRiffState, tagID, parentID, 0, NULL, &len, &pos ) ) { - LFA_Seek ( inFileRef, pos, SEEK_SET ); - LFA_Write ( inFileRef, inData, len ); - } - } catch ( ... ) { - return false; + rawPrmL.filePath[259] = 0; // Ensure a terminating nul. + if ( rawPrmL.filePath[0] != 0 ) { + if ( rawPrmL.filePath[0] == '/' ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", + kXMP_NS_CreatorAtom, "posixProjectPath", rawPrmL.filePath ); + } else if ( XMP_LitNMatch ( rawPrmL.filePath, "\\\\?\\", 4 ) ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", + kXMP_NS_CreatorAtom, "uncProjectPath", rawPrmL.filePath ); } - - return true; - } - // ============================================================================================= - - bool MakeChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, UInt32 len ) - { - long starttag, taglen; - UInt32 rifflen, avail; - UInt64 pos; - - /* look for top level Premiere padding chunk */ - starttag = 0; - while ( FindChunk ( inOutRiffState, ckidPremierePadding, riffType, 0, &starttag, reinterpret_cast<unsigned long*>(&taglen), &pos ) ) { - - pos -= 8; - taglen += 8; - long extra = taglen - len; - if ( extra < 0 ) continue; - - RiffIterator iter = inOutRiffState.tags.begin(); - iter += (starttag - 1); + const char * exportStr = 0; + switch ( rawPrmL.exportType ) { + case kExportTypeMovie : exportStr = "movie"; break; + case kExportTypeStill : exportStr = "still"; break; + case kExportTypeAudio : exportStr = "audio"; break; + case kExportTypeCustom : exportStr = "custom"; break; + } + if ( exportStr != 0 ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", exportStr ); + } - if ( extra == 0 ) { - - iter->len = 0; + handler->containsXMP |= haveXMP; // mind the '|=' +} // importCr8rToXMP - } else { +static void importCr8rToXMP( RIFF_MetaHandler* handler, ValueChunk* cr8rChunk ) +{ + bool haveXMP = false; - atag pad; - UInt64 padpos; + XMP_Enforce( cr8rChunk->oldSize == CR8R_SIZE ); + Cr8rBoxContent rawCr8r; + XMP_Assert ( sizeof ( rawCr8r ) == CR8R_SIZE - 8 ); // double check tight packing. + memcpy ( &rawCr8r, cr8rChunk->oldValue.data(), sizeof (rawCr8r) ); - /* need 8 bytes extra to be able to split it */ - extra -= 8; - if ( extra < 0 ) continue; - - try{ - padpos = pos + len; - LFA_Seek ( inFileRef, padpos, SEEK_SET ); - pad.id = MakeUns32LE ( ckidPremierePadding ); - pad.len = MakeUns32LE ( extra ); - LFA_Write ( inFileRef, &pad, sizeof(pad) ); - } catch ( ... ) { - return false; - } - - iter->pos = padpos + 8; - iter->len = extra; + if ( rawCr8r.magic != 0xBEEFCAFE ) { + Flip4 ( &rawCr8r.creatorCode ); // The only numeric fields that we care about. + Flip4 ( &rawCr8r.appleEvent ); + } - } + std::string fieldPath; - /* seek back to start of original padding chunk */ - LFA_Seek ( inFileRef, pos, SEEK_SET ); + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &fieldPath ); + if ( rawCr8r.creatorCode != 0 ) { + haveXMP = true; + handler->xmpObj.SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.creatorCode ); // ! Unsigned trickery. + } - return true; + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &fieldPath ); + if ( rawCr8r.appleEvent != 0 ) { + haveXMP = true; + handler->xmpObj.SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.appleEvent ); // ! Unsigned trickery. + } - } - - /* can't take padding chunk, so append new chunk to end of file */ - - rifflen = inOutRiffState.rifflen + 8; - avail = AVIMAXCHUNKSIZE - rifflen; - - LFA_Seek ( inFileRef, 0, SEEK_END ); - pos = GetFilePosition ( inFileRef ); - - if ( (pos & 1) == 1 ) { - // The file length is odd, need a pad byte. - XMP_Uns8 pad = 0; - LFA_Write ( inFileRef, &pad, 1 ); - ++pos; - } - - if ( avail < len ) { + rawCr8r.fileExt[15] = 0; // Ensure a terminating nul. + if ( rawCr8r.fileExt[0] != 0 ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", rawCr8r.fileExt ); + } - /* if needed, create new AVIX chunk */ - ltag avix; - - avix.id = MakeUns32LE ( FOURCC_RIFF ); - avix.len = MakeUns32LE ( 4 + len ); - avix.subid = MakeUns32LE ( formtypeAVIX ); - LFA_Write(inFileRef, &avix, sizeof(avix)); + rawCr8r.appOptions[15] = 0; // Ensure a terminating nul. + if ( rawCr8r.appOptions[0] != 0 ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", rawCr8r.appOptions ); + } + + rawCr8r.appName[31] = 0; // Ensure a terminating nul. + if ( rawCr8r.appName[0] != 0 ) { + haveXMP = true; + handler->xmpObj.SetProperty ( kXMP_NS_XMP, "CreatorTool", rawCr8r.appName ); + } - pos += 12; - AddTag ( inOutRiffState, avix.id, len, pos, 0, 0, 0 ); + handler->containsXMP |= haveXMP; // mind the '|=' +} // importCr8rToXMP - } else { - /* otherwise, rewrite length of last RIFF chunk in file */ - pos = inOutRiffState.riffpos + 4; - rifflen = inOutRiffState.rifflen + len; - XMP_Uns32 fileLen = MakeUns32LE ( rifflen ); - LFA_Seek ( inFileRef, pos, SEEK_SET ); - LFA_Write ( inFileRef, &fileLen, 4 ); - inOutRiffState.rifflen = rifflen; - - /* prepare to write data */ - LFA_Seek ( inFileRef, 0, SEEK_END ); +static void importListChunkToXMP( RIFF_MetaHandler* handler, ContainerChunk* listChunk, Mapping mapping[], bool xmpHasPriority ) +{ + valueMap* cm = &listChunk->childmap; + for (int p=0; mapping[p].chunkID != 0; p++) // go through legacy chunks + { + valueMapIter result = cm->find(mapping[p].chunkID); + if( result != cm->end() ) // if value found + { + ValueChunk* propChunk = result->second; + + bool propertyExists = false; + std::string utf8 = nativePropertyToUTF8( + propChunk->oldValue.c_str(), + (XMP_StringLen)propChunk->oldValue.size(), &propertyExists ); + + if ( utf8.size() > 0 ) // if property is not-empty, set Property + { + switch ( mapping[p].propType ) + { + case prop_TIMEVALUE: + if ( xmpHasPriority && + handler->xmpObj.DoesStructFieldExist( mapping[p].ns, mapping[p].prop, kXMP_NS_DM, "timeValue" )) + break; // skip if XMP has precedence and exists + handler->xmpObj.SetStructField( mapping[p].ns, mapping[p].prop, + kXMP_NS_DM, "timeValue", utf8.c_str() ); + break; + case prop_LOCALIZED_TEXT: + if ( xmpHasPriority && handler->xmpObj.GetLocalizedText( mapping[p].ns , + mapping[p].prop, "" , "x-default", 0, 0, 0 )) + break; // skip if XMP has precedence and exists + handler->xmpObj.SetLocalizedText( mapping[p].ns , mapping[p].prop, + "" , "x-default" , utf8.c_str() ); + if ( mapping[p].chunkID == kPropChunkINAM ) + handler->hasListInfoINAM = true; // needs to be known for special 3-way merge around dc:title + break; + case prop_ARRAYITEM: + if ( xmpHasPriority && + handler->xmpObj.DoesArrayItemExist( mapping[p].ns, mapping[p].prop, 1 )) + break; // skip if XMP has precedence and exists + handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop ); + handler->xmpObj.AppendArrayItem( mapping[p].ns, mapping[p].prop, kXMP_PropValueIsArray, utf8.c_str(), kXMP_NoOptions ); + break; + case prop_SIMPLE: + if ( xmpHasPriority && + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + break; // skip if XMP has precedence and exists + handler->xmpObj.SetProperty( mapping[p].ns, mapping[p].prop, utf8.c_str() ); + break; + default: + XMP_Throw( "internal error" , kXMPErr_InternalFailure ); + } + handler->containsXMP = true; // very important for treatment on caller level + } + else if ( ! propertyExists) // otherwise remove it. + { // [2389942] don't, if legacy value is existing but non-retrievable (due to server mode) + switch ( mapping[p].propType ) + { + case prop_TIMEVALUE: + if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop ); + break; + case prop_LOCALIZED_TEXT: + if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + handler->xmpObj.DeleteLocalizedText( mapping[p].ns, mapping[p].prop, "", "x-default" ); + break; + case prop_ARRAYITEM: + case prop_SIMPLE: + if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop ); + break; + default: + XMP_Throw( "internal error" , kXMPErr_InternalFailure ); + } + } } - - return true; - + } // for +} +void importProperties( RIFF_MetaHandler* handler ) +{ + bool hasDigest = handler->xmpObj.GetProperty( kXMP_NS_WAV, "NativeDigest", NULL , NULL ); + if ( hasDigest ) + { + // remove! since it now becomse a 'new' handler file + handler->xmpObj.DeleteProperty( kXMP_NS_WAV, "NativeDigest" ); } - // ============================================================================================= - - bool WriteChunk ( LFA_FileRef inFileRef, long tagID, const char * data, UInt32 len ) + // BWF Bext extension chunk ----------------------------------------------- + if ( handler->parent->format == kXMP_WAVFile && // applies only to WAV + handler->bextChunk != 0 ) //skip if no BEXT chunk found. { - atag ck; - ck.id = MakeUns32LE ( tagID ); - ck.len = MakeUns32LE ( len ); - - try { - LFA_Write ( inFileRef, &ck, 8 ); - LFA_Write ( inFileRef, data, len ); - } catch ( ... ) { - return false; - } - - return true; + importBextChunkToXMP( handler, handler->bextChunk ); } - // ============================================================================================= - - long OpenRIFF ( LFA_FileRef inFileRef, RiffState & inOutRiffState ) + // PrmL chunk -------------------------------------------------------------- + if ( handler->prmlChunk != 0 && handler->prmlChunk->oldSize == PRML_SIZE ) { - UInt64 pos = 0; - long tag, subtype; - UInt32 len; - - const XMP_Int64 fileLen = LFA_Measure ( inFileRef ); - if ( fileLen < 8 ) return 0; - - LFA_Seek ( inFileRef, 0, SEEK_SET ); - - while ( ReadTag ( inFileRef, &tag, &len, &subtype, pos, fileLen ) ) { - if ( tag != FOURCC_RIFF ) break; - AddTag ( inOutRiffState, tag, len, pos, 0, 0, subtype ); - if ( subtype != 0 ) SubRead ( inFileRef, inOutRiffState, subtype, len, pos ); - } - - return (long) inOutRiffState.tags.size(); - + importPrmLToXMP( handler, handler->prmlChunk ); } - // ============================================================================================= - - static bool ReadTag ( LFA_FileRef inFileRef, long * outTag, UInt32 * outLength, long * subtype, UInt64 & inOutPosition, UInt64 maxOffset ) + // Cr8r chunk -------------------------------------------------------------- + if ( handler->cr8rChunk != 0 && handler->cr8rChunk->oldSize == CR8R_SIZE ) { - UInt32 realLength; - - long bytesRead; - bytesRead = LFA_Read ( inFileRef, outTag, 4 ); - if ( bytesRead != 4 ) return false; - *outTag = GetUns32LE ( outTag ); - - bytesRead = LFA_Read ( inFileRef, outLength, 4 ); - if ( bytesRead != 4 ) return false; - *outLength = GetUns32LE ( outLength ); - - realLength = *outLength; - realLength += (realLength & 1); // Round up to an even value. - - inOutPosition = GetFilePosition ( inFileRef ); // The file offset of the data portion. - UInt64 maxLength = maxOffset - inOutPosition; - - if ( (inOutPosition > maxOffset) || ((UInt64)(*outLength) > maxLength) ) { - - bool ignoreLastPad = true; // Ignore cases where a final pad byte is missing. - UInt64 fileLen = LFA_Measure ( inFileRef ); - if ( inOutPosition > (maxOffset + 1) ) ignoreLastPad = false; - if ( (UInt64)(*outLength) > (maxLength + 1) ) ignoreLastPad = false; - - if ( ! ignoreLastPad ) { - - // Workaround for bad files in the field that have a bad size in the outermost RIFF - // chunk. Do a "runtime repair" of cases where the length is too long (beyond EOF). - // This handles read-only usage, update usage is repaired (or not) in the handler. - - bool oversizeRIFF = (inOutPosition == 8) && // Is this the initial 'RIFF' chunk? - (fileLen >= 8); // Is the file at least of the minimal size? - - if ( ! oversizeRIFF ) { - XMP_Throw ( "RIFF tag exceeds maximum length", kXMPErr_BadValue ); - } else { - *outLength = (UInt32)(fileLen) - 8; - realLength = *outLength; - realLength += (realLength & 1); // Round up to an even value. - } + importCr8rToXMP( handler, handler->cr8rChunk ); + } - } + // LIST:INFO -------------------------------------------------------------- + if ( handler->listInfoChunk != 0) //skip if no LIST:INFO chunk found. + importListChunkToXMP( handler, handler->listInfoChunk, listInfoProps, hasDigest ); - } - - *subtype = 0; + // LIST:Tdat -------------------------------------------------------------- + if ( handler->listTdatChunk != 0) + importListChunkToXMP( handler, handler->listTdatChunk, listTdatProps, hasDigest ); - if ( (*outTag != FOURCC_LIST) && (*outTag != FOURCC_RIFF) ) { + // DISP (do last, higher priority than INAM ) ----------------------------- + bool takeXMP = false; // assume for now + if ( hasDigest ) + { + std::string actualLang, value; + bool r = handler->xmpObj.GetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, &value, NULL ); + if ( r && (actualLang == "x-default") ) takeXMP = true; + } - UInt64 tempPos = inOutPosition + realLength; - if ( tempPos <= maxOffset ) { - LFA_Seek ( inFileRef, tempPos, SEEK_SET ); - } else if ( (tempPos == (maxOffset + 1)) && (maxOffset == (UInt64)LFA_Measure(inFileRef)) ) { - LFA_Seek ( inFileRef, 0, SEEK_END ); // Hack to tolerate a missing final pad byte. - } else { - XMP_Throw ( "Bad RIFF offset", kXMPErr_BadValue ); + if ( (!takeXMP) && handler->dispChunk != 0) //skip if no LIST:INFO chunk found. + { + std::string* value = &handler->dispChunk->oldValue; + if ( value->size() >= 4 ) // ignore contents if file too small + { + XMP_StringPtr cstring = value->c_str(); + XMP_StringLen size = (XMP_StringLen) value->size(); + + size -= 4; // skip first four bytes known to contain constant + cstring += 4; + + bool propertyExists = false; + std::string utf8 = nativePropertyToUTF8( cstring, size, &propertyExists ); + + if ( utf8.size() > 0 ) + { + handler->xmpObj.SetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , utf8.c_str() ); + handler->containsXMP = true; // very important for treatment on caller level } - - } else { - - bytesRead = LFA_Read ( inFileRef, subtype, 4 ); - if ( bytesRead != 4 ) return false; - *subtype = GetUns32LE ( subtype ); - - *outLength -= 4; - realLength -= 4; - - // Special case: - // Since the 'movi' chunk can contain billions of subchunks, skip over the 'movi' subchunk. - // - // The 'movi' subtype is added to the list as the TAG. - // The subtype is returned empty so nobody will try to parse the subchunks. - - if ( *subtype == listtypeAVIMOVIE ) { - inOutPosition = GetFilePosition ( inFileRef ); - UInt64 tempPos = inOutPosition + realLength; - if ( tempPos <= maxOffset ) { - LFA_Seek ( inFileRef, tempPos, SEEK_SET ); - } else if ( (tempPos == (maxOffset + 1)) && (maxOffset == (UInt64)LFA_Measure(inFileRef)) ) { - LFA_Seek ( inFileRef, 0, SEEK_END ); // Hack to tolerate a missing final pad byte. - } else { - XMP_Throw ( "Bad RIFF offset", kXMPErr_BadValue ); + else + { + // found as part of [2389942] + // forward deletion may only happen if no LIST:INFO/INAM is present: + if ( ! handler->hasListInfoINAM && + ! propertyExists ) // ..[2389942]part2: and if truly no legacy property + { // (not just an unreadable one due to ServerMode). + handler->xmpObj.DeleteProperty( kXMP_NS_DC, "title" ); } - *outLength += 4; - *outTag = *subtype; - *subtype = 0; } + } // if size sufficient + } // handler->dispChunk - inOutPosition = GetFilePosition ( inFileRef ); +} // importProperties - } +//////////////////////////////////////////////////////////////////////////////// +// EXPORT +//////////////////////////////////////////////////////////////////////////////// + +void relocateWronglyPlacedXMPChunk( RIFF_MetaHandler* handler ) +{ + LFA_FileRef file = handler->parent->fileRef; + RIFF::containerVect *rc = &handler->riffChunks; + RIFF::ContainerChunk* lastChunk = rc->at( rc->size()-1 ); + + // 1) XMPPacket + // needChunk exists but is not in lastChunk ? + if ( + handler->xmpChunk != 0 && // XMP Chunk existing? + (XMP_Uns32)rc->size() > 1 && // more than 1 top-level chunk (otherwise pointless) + lastChunk->getChild( handler->xmpChunk ) == lastChunk->children.end() // not already in last chunk? + ) + { + RIFF::ContainerChunk* cur; + chunkVectIter child; + XMP_Int32 chunkNo; + + // find and relocate to last chunk: + for ( chunkNo = (XMP_Int32)rc->size()-2 ; chunkNo >= 0; chunkNo-- ) // ==> start with second-last chunk + { + cur = rc->at(chunkNo); + child = cur->getChild( handler->xmpChunk ); + if ( child != cur->children.end() ) // found? + break; + } // for + + if ( chunkNo < 0 ) // already in place? nothing left to do. + return; + + lastChunk->children.push_back( *child ); // nb: order matters! + cur->replaceChildWithJunk( *child, false ); + cur->hasChange = true; // [2414649] initialize early-on i.e: here + } // if +} // relocateWronglyPlacedXMPChunk + +// writes to buffer up to max size, +// 0 termination only if shorter than maxSize +// converts down to ascii +static void setBextField ( std::string* value, XMP_Uns8* data, XMP_Uns32 offset, XMP_Uns32 maxSize ) +{ + XMP_Validate( value != 0, "setBextField: null value string pointer", kXMPErr_BadParam ); + XMP_Validate( data != 0, "setBextField: null data value", kXMPErr_BadParam ); + XMP_Validate( maxSize > 0, "setBextField: maxSize must be greater than 0", kXMPErr_BadParam ); - return true; + std::string ascii; + XMP_StringLen actualSize = convertToASCII( value->data(), (XMP_StringLen) value->size() , &ascii , maxSize ); + strncpy( (char*)(data + offset), ascii.data(), actualSize ); +} - } +// add bwf-bext related data to bext chunk, create if not existing yet. +// * in fact, since bext is fully fixed and known, there can be no unknown subchunks worth keeping: +// * prepare bext chunk in buffer +// * value changed/created if needed only, otherways remove chunk +// * remove bext-mapped properties from xmp (non-redundant storage) +// note: ValueChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation) +static void exportXMPtoBextChunk( RIFF_MetaHandler* handler, ValueChunk** bextChunk ) +{ + // register bext namespace ( if there was no import, this is news, otherwise harmless moot) + SXMPMeta::RegisterNamespace( kXMP_NS_BWF, "bext:", 0 ); - // ============================================================================================= + bool chunkUsed = false; // assume for now + SXMPMeta* xmp = &handler->xmpObj; - static void AddTag ( RiffState & inOutRiffState, long tag, UInt32 len, UInt64 & inOutPosition, long parentID, long parentnum, long subtypeID ) + // prepare buffer, need to know CodingHistory size as the only variable + XMP_Int32 bextBufferSize = MIN_BEXT_SIZE - 8; // -8 because of header + std::string value; + if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, kXMP_NoOptions )) { - RiffTag newTag; - - newTag.pos = inOutPosition; - newTag.tagID = tag; - newTag.len = len; - newTag.parent = parentnum; - newTag.parentID = parentID; - newTag.subtypeID = subtypeID; - - inOutRiffState.tags.push_back ( newTag ); - - if ( tag == FOURCC_RIFF ) { - inOutRiffState.riffpos = inOutPosition - 12; - inOutRiffState.rifflen = len + 4; - } - + bextBufferSize += ((XMP_StringLen)value.size()) + 1 ; // add to size (and a trailing zero) } - // ============================================================================================= + // create and clear buffer + XMP_Uns8* buffer = new XMP_Uns8[bextBufferSize]; + for (XMP_Int32 i = 0; i < bextBufferSize; i++ ) + buffer[i] = 0; - static long SubRead ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long parentid, UInt32 parentlen, UInt64 & inOutPosition ) + // grab props, write into buffer, remove from XMP /////////////////////////// + // bextDescription ------------------------------------------------ + if ( xmp->GetProperty( bextDescription.ns, bextDescription.prop, &value, kXMP_NoOptions ) ) { - long tag; - long subtype = 0; - long parentnum; - UInt32 len, total, childlen; - UInt64 oldpos; - - total = 0; - parentnum = (long) inOutRiffState.tags.size() - 1; - - UInt64 maxOffset = inOutPosition + parentlen; - - while ( parentlen > 0 ) { - - oldpos = inOutPosition; - ReadTag ( inFileRef, &tag, &len, &subtype, inOutPosition, maxOffset ); - AddTag ( inOutRiffState, tag, len, inOutPosition, parentid, parentnum, subtype ); - len += (len & 1); //padding byte - - if ( subtype == 0 ) { - childlen = 8 + len; - } else { - childlen = 12 + SubRead ( inFileRef, inOutRiffState, subtype, len, inOutPosition ); - } - - if ( parentlen < childlen ) parentlen = childlen; - parentlen -= childlen; - total += childlen; - - } - - return total; - + setBextField( &value, (XMP_Uns8*) buffer, 0, 256 ); + xmp->DeleteProperty( bextDescription.ns, bextDescription.prop) ; + chunkUsed = true; } - - // ============================================================================================= - - bool GetRIFFChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long tagID, - long parentID, long subtypeID, char * outBuffer, unsigned long * outBufferSize, - UInt64* posPtr ) + // bextOriginator ------------------------------------------------- + if ( xmp->GetProperty( bextOriginator.ns , bextOriginator.prop, &value, kXMP_NoOptions ) ) { - UInt32 len; - UInt64 pos; - - bool found = FindChunk ( inOutRiffState, tagID, parentID, subtypeID, 0, &len, &pos ); - if ( ! found ) return false; - - if ( posPtr != 0 ) - *posPtr = pos; // return position CBR - - if ( outBuffer == 0 ) { - *outBufferSize = (unsigned long)len; - return true; // Found, but not wanted. - } - - if ( len > *outBufferSize ) - len = *outBufferSize; - found = ReadChunk ( inFileRef, pos, len, outBuffer ); - - return found; + setBextField( &value, (XMP_Uns8*) buffer, 256, 32 ); + xmp->DeleteProperty( bextOriginator.ns , bextOriginator.prop ); + chunkUsed = true; + } + // bextOriginatorRef ---------------------------------------------- + if ( xmp->GetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, &value, kXMP_NoOptions ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 256+32, 32 ); + xmp->DeleteProperty( bextOriginatorRef.ns , bextOriginatorRef.prop ); + chunkUsed = true; + } + // bextOriginationDate -------------------------------------------- + if ( xmp->GetProperty( bextOriginationDate.ns , bextOriginationDate.prop, &value, kXMP_NoOptions ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 256+32+32, 10 ); + xmp->DeleteProperty( bextOriginationDate.ns , bextOriginationDate.prop ); + chunkUsed = true; + } + // bextOriginationTime -------------------------------------------- + if ( xmp->GetProperty( bextOriginationTime.ns , bextOriginationTime.prop, &value, kXMP_NoOptions ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 256+32+32+10, 8 ); + xmp->DeleteProperty( bextOriginationTime.ns , bextOriginationTime.prop ); + chunkUsed = true; + } + // bextTimeReference ---------------------------------------------- + // thanx to friendly byte order, all 8 bytes can be written in one go: + if ( xmp->GetProperty( bextTimeReference.ns, bextTimeReference.prop, &value, kXMP_NoOptions ) ) + { + try + { + XMP_Int64 v = SXMPUtils::ConvertToInt64( value.c_str() ); + PutUns64LE( v, &(buffer[256+32+32+10+8] )); + chunkUsed = true; + } + catch (XMP_Error& e) + { + if ( e.GetID() != kXMPErr_BadParam ) + throw e; // re-throw on any other error + } // 'else' tolerate ( time reference remains 0x00000000 ) + // valid or not, do not store redundantly: + xmp->DeleteProperty( bextTimeReference.ns, bextTimeReference.prop ); } - // ============================================================================================= + // bextVersion ---------------------------------------------------- + // set version=1, no matter what. + PutUns16LE( 1, &(buffer[256+32+32+10+8+8]) ); + xmp->DeleteProperty( bextVersion.ns, bextVersion.prop ); - bool FindChunk ( RiffState & inOutRiffState, long tagID, long parentID, long subtypeID, - long * startTagIndex, UInt32 * len, UInt64 * pos) + // bextUMID ------------------------------------------------------- + if ( xmp->GetProperty( bextUMID.ns, bextUMID.prop, &value, kXMP_NoOptions ) ) { - std::vector<RiffTag>::iterator iter = inOutRiffState.tags.begin(); - std::vector<RiffTag>::iterator endIter = inOutRiffState.tags.end(); - - // If we're using the next index, skip the iterator. - if ( startTagIndex != 0 ) iter += *startTagIndex; + std::string rawStr; - for ( ; iter != endIter ; ++iter ) { + if ( !DecodeFromHexString( value.data(), (XMP_StringLen) value.size(), &rawStr ) ) + { + delete [] buffer; // important. + XMP_Throw ( "EncodeFromHexString: illegal umid string. Must contain an even number of 0-9 and uppercase A-F chars.", kXMPErr_BadParam ); + } - if ( startTagIndex != 0 ) *startTagIndex += 1; - - if ( (parentID!= 0) && (iter->parentID != parentID) ) continue; - if ( (tagID != 0) && (iter->tagID != tagID) ) continue; - if ( (subtypeID != 0) && (iter->subtypeID != subtypeID) ) continue; - - if ( len != 0 ) *len = iter->len; - if ( pos != 0 ) *pos = iter->pos; - - return true; + // if UMID is smaller/longer than 64 byte for any reason, + // truncate/do a partial write (just like for any other bext property) - } - - return false; + memcpy( (char*) &(buffer[256+32+32+10+8+8+2]), rawStr.data(), MIN( 64, rawStr.size() ) ); + xmp->DeleteProperty( bextUMID.ns, bextUMID.prop ); + chunkUsed = true; } - // ============================================================================================= - - static bool ReadChunk ( LFA_FileRef inFileRef, UInt64 & pos, UInt32 len, char * outBuffer ) + // bextCodingHistory ---------------------------------------------- + if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, kXMP_NoOptions ) ) { - - if ( (inFileRef == 0) || (outBuffer == 0) ) return false; - - LFA_Seek (inFileRef, pos, SEEK_SET ); - UInt32 bytesRead = LFA_Read ( inFileRef, outBuffer, len ); - if ( bytesRead != len ) return false; - - return true; - + std::string ascii; + convertToASCII( value.data(), (XMP_StringLen) value.size() , &ascii, (XMP_StringLen) value.size() ); + strncpy( (char*) &(buffer[MIN_BEXT_SIZE-8]), ascii.data(), ascii.size() ); + xmp->DeleteProperty( bextCodingHistory.ns, bextCodingHistory.prop ); + chunkUsed = true; } -} // namespace RIFF_Support - -// ================================================================================================= - -// *** Could be moved to a separate header - -#pragma pack(push,1) - -// [TODO] Can we switch to using just a full path here? -struct FSSpecLegacy -{ - short vRefNum; - long parID; - char name[260]; // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp -- 260 is "old school", 32000 is "new school". -}; - -struct CR8R_CreatorAtom -{ - unsigned long magicLu; - - long atom_sizeL; // Size of this structure. - short atom_vers_majorS; // Major atom version. - short atom_vers_minorS; // Minor atom version. - - // mac - unsigned long creator_codeLu; // Application code on MacOS. - unsigned long creator_eventLu; // Invocation appleEvent. - - // windows - char creator_extAC[16]; // Extension allowing registry search to app. - char creator_flagAC[16]; // Flag passed to app at invocation time. - - char creator_nameAC[32]; // Name of the creator application. -}; - -typedef CR8R_CreatorAtom** CR8R_CreatorAtomHandle; + // always delete old, recreate if needed + if ( *bextChunk != 0 ) + { + (*bextChunk)->parent->replaceChildWithJunk( *bextChunk ); + (*bextChunk) = 0; // clear direct Chunk pointer + } -#define PR_PROJECT_LINK_MAGIC 0x600DF00D // GoodFood + if ( chunkUsed) + *bextChunk = new ValueChunk( handler->riffChunks.at(0), std::string( (char*)buffer, bextBufferSize ), kChunk_bext ); -typedef enum -{ - Embed_ExportTypeMovie = 0, - Embed_ExportTypeStill, - Embed_ExportTypeAudio, - Embed_ExportTypeCustom + delete [] buffer; // important. } -Embed_ExportType; - -struct Embed_ProjectLinkAtom +static inline void SetBufferedString ( char * dest, const std::string source, size_t limit ) { - unsigned long magicLu; - long atom_sizeL; - short atom_vers_apiS; - short atom_vers_codeS; - unsigned long exportType; // See enum. The type of export that generated the file - FSSpecLegacy fullPath; // Full path of the project file -}; - -#pragma pack(pop) - -// ------------------------------------------------------------------------------------------------- - -#define kCreatorTool "CreatorTool" -#define AdobeCreatorAtomVersion_Major 1 -#define AdobeCreatorAtomVersion_Minor 0 -#define AdobeCreatorAtom_Magic 0xBEEFCAFE - -#define myCreatorAtom MakeFourCC ( 'C','r','8','r' ) + memset ( dest, 0, limit ); + size_t count = source.size(); + if ( count >= limit ) count = limit - 1; // Ensure a terminating nul. + memcpy ( dest, source.c_str(), count ); +} -static void CreatorAtom_Initialize ( CR8R_CreatorAtom& creatorAtom ) +static void exportXMPtoCr8rChunk ( RIFF_MetaHandler* handler, ValueChunk** cr8rChunk ) { - memset ( &creatorAtom, 0, sizeof(CR8R_CreatorAtom) ); - creatorAtom.magicLu = AdobeCreatorAtom_Magic; - creatorAtom.atom_vers_majorS = AdobeCreatorAtomVersion_Major; - creatorAtom.atom_vers_minorS = AdobeCreatorAtomVersion_Minor; - creatorAtom.atom_sizeL = sizeof(CR8R_CreatorAtom); -} + const SXMPMeta & xmp = handler->xmpObj; + + // Make sure an existing Cr8r chunk has the proper fixed length. + bool haveOldCr8r = (*cr8rChunk != 0); + if ( haveOldCr8r && ((*cr8rChunk)->oldSize != sizeof(Cr8rBoxContent)+8) ) { + (*cr8rChunk)->parent->replaceChildWithJunk ( *cr8rChunk ); // Wrong length, the existing chunk must be bad. + (*cr8rChunk) = 0; + haveOldCr8r = false; + } -// ------------------------------------------------------------------------------------------------- + bool haveNewCr8r = false; + std::string creatorCode, appleEvent, fileExt, appOptions, appName; + + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &creatorCode, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &appleEvent, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fileExt, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &appOptions, 0 ); + haveNewCr8r |= xmp.GetProperty ( kXMP_NS_XMP, "CreatorTool", &appName, 0 ); -static void CreatorAtom_MakeValid ( CR8R_CreatorAtom * creator_atomP ) -{ - // If already valid, no conversion is needed. - if ( creator_atomP->magicLu == AdobeCreatorAtom_Magic ) return; + if ( ! haveNewCr8r ) { // Get rid of an existing Cr8r chunk if there is no new XMP. + if ( haveOldCr8r ) { + (*cr8rChunk)->parent->replaceChildWithJunk ( *cr8rChunk ); + *cr8rChunk = 0; + } + return; + } + + if ( ! haveOldCr8r ) { + *cr8rChunk = new ValueChunk ( handler->lastChunk, std::string(), kChunk_Cr8r ); + } + + std::string strValue; + strValue.assign ( (sizeof(Cr8rBoxContent) - 1), '\0' ); // ! Use size-1 because SetValue appends a trailing 0 byte. + (*cr8rChunk)->SetValue ( strValue ); // ! Just get the space available. + XMP_Assert ( (*cr8rChunk)->newValue.size() == sizeof(Cr8rBoxContent) ); + (*cr8rChunk)->hasChange = true; - Flip4 ( &creator_atomP->magicLu ); - Flip2 ( &creator_atomP->atom_vers_majorS ); - Flip2 ( &creator_atomP->atom_vers_minorS ); + Cr8rBoxContent * newCr8r = (Cr8rBoxContent*) (*cr8rChunk)->newValue.data(); + + if ( ! haveOldCr8r ) { - Flip4 ( &creator_atomP->atom_sizeL ); - Flip4 ( &creator_atomP->creator_codeLu ); - Flip4 ( &creator_atomP->creator_eventLu ); + newCr8r->magic = MakeUns32LE ( 0xBEEFCAFE ); + newCr8r->size = MakeUns32LE ( sizeof(Cr8rBoxContent) ); + newCr8r->majorVer = MakeUns16LE ( 1 ); - XMP_Assert ( creator_atomP->magicLu == AdobeCreatorAtom_Magic ); -} + } else { -// ------------------------------------------------------------------------------------------------- + const Cr8rBoxContent * oldCr8r = (Cr8rBoxContent*) (*cr8rChunk)->oldValue.data(); + memcpy ( newCr8r, oldCr8r, sizeof(Cr8rBoxContent) ); + if ( GetUns32LE ( &newCr8r->magic ) != 0xBEEFCAFE ) { // Make sure we write LE numbers. + Flip4 ( &newCr8r->magic ); + Flip4 ( &newCr8r->size ); + Flip2 ( &newCr8r->majorVer ); + Flip2 ( &newCr8r->minorVer ); + Flip4 ( &newCr8r->creatorCode ); + Flip4 ( &newCr8r->appleEvent ); + } -static void CreatorAtom_ToBE ( CR8R_CreatorAtom * creator_atomP ) -{ - creator_atomP->atom_vers_majorS = MakeUns16BE ( creator_atomP->atom_vers_majorS ); - creator_atomP->atom_vers_minorS = MakeUns16BE ( creator_atomP->atom_vers_minorS ); + } + + if ( ! creatorCode.empty() ) { + newCr8r->creatorCode = MakeUns32LE ( (XMP_Uns32) strtoul ( creatorCode.c_str(), 0, 0 ) ); + } + + if ( ! appleEvent.empty() ) { + newCr8r->appleEvent = MakeUns32LE ( (XMP_Uns32) strtoul ( appleEvent.c_str(), 0, 0 ) ); + } + + if ( ! fileExt.empty() ) SetBufferedString ( newCr8r->fileExt, fileExt, sizeof ( newCr8r->fileExt ) ); + if ( ! appOptions.empty() ) SetBufferedString ( newCr8r->appOptions, appOptions, sizeof ( newCr8r->appOptions ) ); + if ( ! appName.empty() ) SetBufferedString ( newCr8r->appName, appName, sizeof ( newCr8r->appName ) ); - creator_atomP->magicLu = MakeUns32BE ( creator_atomP->magicLu ); - creator_atomP->atom_sizeL = MakeUns32BE ( creator_atomP->atom_sizeL ); - creator_atomP->creator_codeLu = MakeUns32BE ( creator_atomP->creator_codeLu ); - creator_atomP->creator_eventLu = MakeUns32BE ( creator_atomP->creator_eventLu ); } -// ------------------------------------------------------------------------------------------------- - -static void ProjectLinkAtom_MakeValid ( Embed_ProjectLinkAtom * link_atomP ) +static void exportXMPtoListChunk( XMP_Uns32 id, XMP_Uns32 containerType, + RIFF_MetaHandler* handler, ContainerChunk** listChunk, Mapping mapping[]) { - // If already valid, no conversion is needed. - if ( link_atomP->magicLu == PR_PROJECT_LINK_MAGIC ) return; - - // do the header - Flip4 ( &link_atomP->magicLu ); - Flip4 ( &link_atomP->atom_sizeL ); - Flip2 ( &link_atomP->atom_vers_apiS ); - Flip2 ( &link_atomP->atom_vers_codeS ); + // note: ContainerChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation) + SXMPMeta* xmp = &handler->xmpObj; + bool listChunkIsNeeded = false; // assume for now + + // ! The NUL is optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte. + bool optionalNUL = (handler->parent->format == kXMP_WAVFile); - // do the FSSpec data - Flip2 ( &link_atomP->fullPath.vRefNum ); - Flip4 ( &link_atomP->fullPath.parID ); + for ( int p=0; mapping[p].chunkID != 0; ++p ) { // go through all potential property mappings - XMP_Assert ( link_atomP->magicLu == PR_PROJECT_LINK_MAGIC ); -} + bool propExists = false; + std::string value, actualLang; -// ------------------------------------------------------------------------------------------------- + switch ( mapping[p].propType ) { -static std::string CharsToString ( const char* buffer, int maxBuffer ) -{ - // convert possibly non-zero terminated char buffer to std::string - std::string result; + // get property. if existing, remove from XMP (to avoid redundant storage) + case prop_TIMEVALUE: + propExists = xmp->GetStructField ( mapping[p].ns, mapping[p].prop, kXMP_NS_DM, "timeValue", &value, 0 ); + break; - char bufferz[256]; - XMP_Assert ( maxBuffer < 256 ); - if ( maxBuffer >= 256 ) return result; + case prop_LOCALIZED_TEXT: + propExists = xmp->GetLocalizedText ( mapping[p].ns, mapping[p].prop, "", "x-default", &actualLang, &value, 0); + if ( actualLang != "x-default" ) propExists = false; // no "x-default" => nothing to reconcile ! + break; - memcpy ( bufferz, buffer, maxBuffer ); - bufferz[maxBuffer] = 0; + case prop_ARRAYITEM: + propExists = xmp->GetArrayItem ( mapping[p].ns, mapping[p].prop, 1, &value, 0 ); + break; - result = bufferz; - return result; + case prop_SIMPLE: + propExists = xmp->GetProperty ( mapping[p].ns, mapping[p].prop, &value, 0 ); + break; -} + default: + XMP_Throw ( "internal error", kXMPErr_InternalFailure ); -// ------------------------------------------------------------------------------------------------- + } -bool CreatorAtom::Import ( SXMPMeta& xmpObj, - LFA_FileRef fileRef, - RIFF_Support::RiffState& riffState ) -{ - static const long myProjectLink = MakeFourCC ( 'P','r','m','L' ); - - unsigned long projectLinkSize; - bool ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myProjectLink, 0, 0, 0, &projectLinkSize ); - if ( ok ) { + if ( ! propExists ) { - Embed_ProjectLinkAtom epla; + if ( *listChunk != 0 ) (*listChunk)->removeValue ( mapping[p].chunkID ); - std::string projectPathString; - RIFF_Support::GetRIFFChunk ( fileRef, riffState, myProjectLink, 0, 0, (char*) &epla, &projectLinkSize ); - if ( ok ) { - ProjectLinkAtom_MakeValid ( &epla ); - projectPathString = epla.fullPath.name; - } + } else { - if ( ! projectPathString.empty() ) { + listChunkIsNeeded = true; + if ( *listChunk == 0 ) *listChunk = new ContainerChunk ( handler->riffChunks[0], id, containerType ); - if ( projectPathString[0] == '/' ) { - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", - kXMP_NS_CreatorAtom, "posixProjectPath", projectPathString, 0 ); - } else if ( projectPathString.substr(0,4) == std::string("\\\\?\\") ) { - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", - kXMP_NS_CreatorAtom, "uncProjectPath", projectPathString, 0 ); - } + valueMap* cm = &(*listChunk)->childmap; + valueMapIter result = cm->find( mapping[p].chunkID ); + ValueChunk* propChunk = 0; - std::string projectTypeString; - switch ( epla.exportType ) { - case Embed_ExportTypeMovie : projectTypeString = "movie"; break; - case Embed_ExportTypeStill : projectTypeString = "still"; break; - case Embed_ExportTypeAudio : projectTypeString = "audio"; break; - case Embed_ExportTypeCustom : projectTypeString = "custom"; break; + if ( result != cm->end() ) { + propChunk = result->second; + } else { + propChunk = new ValueChunk ( *listChunk, std::string(), mapping[p].chunkID ); } - if ( ! projectTypeString.empty() ) { - xmpObj.SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", projectTypeString.c_str() ); - } + propChunk->SetValue ( value.c_str(), optionalNUL ); } - } - - unsigned long creatorAtomSize = 0; - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, 0, &creatorAtomSize ); - if ( ok ) { - - CR8R_CreatorAtom creatorAtom; - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, (char*) &creatorAtom, &creatorAtomSize ); + } // for each property - if ( ok ) { - - CreatorAtom_MakeValid ( &creatorAtom ); - - char buffer[256]; - std::string xmpString; - - sprintf ( buffer, "%d", creatorAtom.creator_codeLu ); - xmpString = buffer; - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", xmpString, 0 ); - - sprintf ( buffer, "%d", creatorAtom.creator_eventLu ); - xmpString = buffer; - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", xmpString, 0 ); + if ( (! listChunkIsNeeded) && (*listChunk != 0) && ((*listChunk)->children.size() == 0) ) { + (*listChunk)->parent->replaceChildWithJunk ( *listChunk ); + (*listChunk) = 0; // reset direct Chunk pointer + } - xmpString = CharsToString ( creatorAtom.creator_extAC, sizeof(creatorAtom.creator_extAC) ); - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", xmpString, 0 ); +} - xmpString = CharsToString ( creatorAtom.creator_flagAC, sizeof(creatorAtom.creator_flagAC) ); - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", xmpString, 0 ); +void exportAndRemoveProperties ( RIFF_MetaHandler* handler ) +{ + SXMPMeta xmpObj = handler->xmpObj; - xmpString = CharsToString ( creatorAtom.creator_nameAC, sizeof(creatorAtom.creator_nameAC) ); - xmpObj.SetProperty ( kXMP_NS_XMP, "CreatorTool", xmpString, 0 ); - - } + exportXMPtoCr8rChunk ( handler, &handler->cr8rChunk ); + // 1/4 BWF Bext extension chunk ----------------------------------------------- + if ( handler->parent->format == kXMP_WAVFile ) { // applies only to WAV + exportXMPtoBextChunk ( handler, &handler->bextChunk ); } - return ok; - -} + // 2/4 DISP chunk + if ( handler->parent->format == kXMP_WAVFile ) { // create for WAVE only -// ------------------------------------------------------------------------------------------------- + std::string actualLang, xmpValue; + bool r = xmpObj.GetLocalizedText ( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, &xmpValue, 0 ); -// *** Not in C library: -#ifndef min - #define min(a,b) ( (a < b) ? a : b ) -#endif + if ( r && ( actualLang == "x-default" ) ) { // prop exists? -#define EnsureFinalNul(buffer) buffer [ sizeof(buffer) - 1 ] = 0 + // the 'right' DISP is lead by a 32 bit low endian 0x0001 + std::string dispValue = std::string( "\x1\0\0\0", 4 ); + dispValue.append ( xmpValue ); -bool CreatorAtom::Update ( SXMPMeta& xmpObj, - LFA_FileRef fileRef, - long riffType, - RIFF_Support::RiffState& riffState ) -{ + if ( handler->dispChunk == 0 ) { + handler->dispChunk = new RIFF::ValueChunk ( handler->riffChunks.at(0), std::string(), kChunk_DISP ); + } - // Creator Atom related values. - bool found = false; - std::string posixPathString, uncPathString; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "posixProjectPath", &posixPathString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "uncProjectPath", &uncPathString, 0 ) ) found = true; + // ! The NUL is optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte. + handler->dispChunk->SetValue ( dispValue, ValueChunk::kNULisOptional ); - std::string applicationCodeString, invocationAppleEventString, extensionString, invocationFlagsString, creatorToolString; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &applicationCodeString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &invocationAppleEventString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &extensionString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &invocationFlagsString, 0 ) ) found = true; - if ( xmpObj.GetProperty ( kXMP_NS_XMP, "CreatorTool", &creatorToolString, 0 ) ) found = true; + } else { // remove Disp Chunk.. - // No Creator Atom information present. - if ( ! found ) return true; + if ( handler->dispChunk != 0 ) { // ..if existing + ContainerChunk* mainChunk = handler->riffChunks.at(0); + Chunk* needle = handler->dispChunk; + chunkVectIter iter = mainChunk->getChild ( needle ); + if ( iter != mainChunk->children.end() ) { + mainChunk->replaceChildWithJunk ( *iter ); + handler->dispChunk = 0; + mainChunk->hasChange = true; + } + } - // Read Legacy Creator Atom. - unsigned long creatorAtomSize = 0; - CR8R_CreatorAtom creatorAtomLegacy; - CreatorAtom_Initialize ( creatorAtomLegacy ); - bool ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, 0, &creatorAtomSize ); - if ( ok ) { - XMP_Assert ( creatorAtomSize == sizeof(CR8R_CreatorAtom) ); - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, (char*) &creatorAtomLegacy, &creatorAtomSize ); - CreatorAtom_MakeValid ( &creatorAtomLegacy ); - } + } - // Generate new Creator Atom from XMP. - CR8R_CreatorAtom creatorAtomViaXMP; - CreatorAtom_Initialize ( creatorAtomViaXMP ); - if ( ! applicationCodeString.empty() ) { - creatorAtomViaXMP.creator_codeLu = strtoul ( applicationCodeString.c_str(), 0, 0 ); - } - if ( ! invocationAppleEventString.empty() ) { - creatorAtomViaXMP.creator_eventLu = strtoul ( invocationAppleEventString.c_str(), 0, 0 ); - } - if ( ! extensionString.empty() ) { - strncpy ( creatorAtomViaXMP.creator_extAC, extensionString.c_str(), sizeof(creatorAtomViaXMP.creator_extAC) ); - EnsureFinalNul ( creatorAtomViaXMP.creator_extAC ); - } - if ( ! invocationFlagsString.empty() ) { - strncpy ( creatorAtomViaXMP.creator_flagAC, invocationFlagsString.c_str(), sizeof(creatorAtomViaXMP.creator_flagAC) ); - EnsureFinalNul ( creatorAtomViaXMP.creator_flagAC ); - } - if ( ! creatorToolString.empty() ) { - strncpy ( creatorAtomViaXMP.creator_nameAC, creatorToolString.c_str(), sizeof(creatorAtomViaXMP.creator_nameAC) ); - EnsureFinalNul ( creatorAtomViaXMP.creator_nameAC ); } - // Write new Creator Atom, if necessary. - if ( memcmp ( &creatorAtomViaXMP, &creatorAtomLegacy, sizeof(CR8R_CreatorAtom) ) != 0 ) { - CreatorAtom_ToBE ( &creatorAtomViaXMP ); - ok = RIFF_Support::PutChunk ( fileRef, riffState, riffType, myCreatorAtom, (char*)&creatorAtomViaXMP, sizeof(CR8R_CreatorAtom) ); - } + // 3/4 LIST:INFO + exportXMPtoListChunk ( kChunk_LIST, kType_INFO, handler, &handler->listInfoChunk, listInfoProps ); - return ok; + // 4/4 LIST:Tdat + exportXMPtoListChunk ( kChunk_LIST, kType_Tdat, handler, &handler->listTdatChunk, listTdatProps ); } + +} // namespace RIFF diff --git a/source/XMPFiles/FormatSupport/RIFF_Support.hpp b/source/XMPFiles/FormatSupport/RIFF_Support.hpp index 8065e38..a0b972b 100644 --- a/source/XMPFiles/FormatSupport/RIFF_Support.hpp +++ b/source/XMPFiles/FormatSupport/RIFF_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2009 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -11,183 +11,32 @@ // ================================================================================================= #include "XMP_Environment.h" // ! This must be the first include. - #include <vector> - #include "XMPFiles_Impl.hpp" -#define MakeFourCC(a,b,c,d) ((long)a | ((long)b << 8) | ((long)c << 16) | ((long)d << 24)) - -#if XMP_WinBuild - #include <vfw.h> -#else - #ifndef FOURCC_RIFF - #define FOURCC_RIFF MakeFourCC ('R', 'I', 'F', 'F') - #endif - #ifndef FOURCC_LIST - #define FOURCC_LIST MakeFourCC ('L', 'I', 'S', 'T') - #endif - #ifndef listtypeAVIMOVIE - #define listtypeAVIMOVIE MakeFourCC ('m', 'o', 'v', 'i') - #endif -#endif - -namespace RIFF_Support -{ - // Some types, if not already defined - #ifndef UInt64 - typedef unsigned long long UInt64; - #endif - #ifndef UInt32 - typedef unsigned long UInt32; - #endif +// ahead declaration: +class RIFF_MetaHandler; - /** - ** Obtain the meta-data for the tagID provided. - ** Returns true for success - */ - bool GetMetaData ( LFA_FileRef inFileRef, long tagID, char * outBuffer, unsigned long * outBufferSize ); +namespace RIFF { - /** - ** Write the meta-data for the tagID provided. - ** Returns true for success - */ - bool SetMetaData ( LFA_FileRef inFileRef, long riffType, long tagID, const char * inBuffer, unsigned long inBufferSize ); - - - - /** - ** A class to hold the information - ** about a particular chunk. - */ - class RiffTag { - public: + // declare ahead + class Chunk; + class ContainerChunk; + class ValueChunk; + class XMPChunk; - RiffTag() : pos(0), tagID(0), len(0), parent(0), parentID(0), subtypeID(0) {} - virtual ~RiffTag() {} - - UInt64 pos; /* file offset of chunk data */ - long tagID; /* ckid of chunk */ - UInt32 len; /* length of chunk data */ - long parent; /* chunk# of parent */ - long parentID; /* FOURCC of parent */ - long subtypeID; /* Subtype of the tag (aka LIST ID) */ - - }; - - typedef std::vector<RiffTag> RiffVector; - typedef RiffVector::iterator RiffIterator; - - /** - ** A class to hold a table of the parsed - ** chunks from a file. Its validity - ** expires when new chunks are added. - */ - class RiffState { - public: - - RiffState() : riffpos(0), rifflen(0), next(0) {} - virtual ~RiffState() {} - - UInt64 riffpos; /* file offset of current RIFF */ - long rifflen; /* length of RIFF incl. header */ - long next; /* next one to search */ - RiffVector tags; /* vector of chunks */ - - }; - - struct ltag { - long id; - UInt32 len; - long subid; - }; - - /** - ** Read from the RIFF file, and build a table of the chunks - ** in the RIFFState class provided. - ** Returns the number of chunks found. - */ - long OpenRIFF ( LFA_FileRef inFileRef, RiffState & inOutRiffState ); + /* This rountines imports the properties found into the + xmp packet. Use after parsing. */ + void importProperties( RIFF_MetaHandler* handler ); - /** - ** Get a chunk from an existing RIFFState, obtained from - ** a call to OpenRIFF. - ** If NULL is passed for the outBuffer, the outBufferSize parameter - ** will contain the field size if true if returned. - ** - ** Returns true if the chunk is found. - ** - ** position of chunk _contents_ is returned in postPtr - */ - bool GetRIFFChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long tagID, long parentID, - long subtypeID, char * outBuffer, unsigned long * outBufferSize, UInt64* posPtr = 0); + /* This rountines exports XMP properties to the respective Chunks, + creating those if needed. No writing to file here. */ + void exportAndRemoveProperties( RIFF_MetaHandler* handler ); + /* will relocated a wrongly placed chunk (one of XMP, LIST:Info, LIST:Tdat= + from RIFF::avix back to main chunk. Chunk itself not touched. */ + void relocateWronglyPlacedXMPChunk( RIFF_MetaHandler* handler ); - /** - ** The routine finds an existing list and tags it as Padding - ** - ** Returns true if success - */ - bool MarkChunkAsPadding ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, long tagID, long subtypeID ); - - - /** - ** The routine finds an existing location to put the chunk into if - ** available, otherwise it creates a new chunk and writes to it. - ** - ** Returns true if success - */ - bool PutChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, long tagID, const char * inBuffer, UInt32 inBufferSize ); - - /** - ** Locates the position of a chunk. - ** All parameters except the RiffState are optional. - ** - ** Return if found. - */ - bool FindChunk ( RiffState & inOutRiffState, long tagID, long parentID, long subtypeID, long * starttag, UInt32 * len, UInt64 * pos ); - - /** - ** Low level routine to write a chunk. - ** - ** Returns true if write succeeded. - */ - bool WriteChunk ( LFA_FileRef inFileRef, long tagID, const char * data, UInt32 len ); - - /** - ** Rewrites data into an existing chunk, not writing the header like WriteChunk - ** - ** Returns true if found and write succeeded. - */ - bool RewriteChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long tagID, long parentID, const char * inData ); - - /** - ** Attempts to find a location to write a chunk, and if not found, prepares a chunk - ** at the end of the file. - ** - ** Returns true if successful. - */ - bool MakeChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, UInt32 len ); - -} // namespace RIFF_Support - -// ================================================================================================= - -// *** Could be moved to a separate header - -namespace CreatorAtom { - - bool Import ( SXMPMeta& xmpObj, - LFA_FileRef fileRef, - RIFF_Support::RiffState& riffState ); - - bool Update ( SXMPMeta& xmpObj, - LFA_FileRef fileRef, - long riffType, - RIFF_Support::RiffState& riffState ); - -} - -// ================================================================================================= +} // namespace RIFF #endif // __RIFF_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp b/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp index a81e8af..bef1b11 100644 --- a/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp +++ b/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -32,11 +32,11 @@ static inline void NormalizeToCR ( std::string * value ) { char * strPtr = (char*) value->data(); char * strEnd = strPtr + value->size(); - + for ( ; strPtr < strEnd; ++strPtr ) { if ( *strPtr == kLF ) *strPtr = kCR; } - + } // NormalizeToCR // ================================================================================================= @@ -47,11 +47,11 @@ static inline void NormalizeToLF ( std::string * value ) { char * strPtr = (char*) value->data(); char * strEnd = strPtr + value->size(); - + for ( ; strPtr < strEnd; ++strPtr ) { if ( *strPtr == kCR ) *strPtr = kLF; } - + } // NormalizeToLF // ================================================================================================= @@ -60,70 +60,52 @@ static inline void NormalizeToLF ( std::string * value ) // // Compute a 128 bit (16 byte) MD5 digest of the full IPTC block. -static inline void ComputeIPTCDigest ( IPTC_Manager * iptc, MD5_Digest * digest ) +static inline void ComputeIPTCDigest ( const void * iptcPtr, const XMP_Uns32 iptcLen, MD5_Digest * digest ) { - MD5_CTX context; - void * iptcData; - XMP_Uns32 iptcLen; + MD5_CTX context; - iptcLen = iptc->UpdateMemoryDataSets ( &iptcData ); - MD5Init ( &context ); - MD5Update ( &context, (XMP_Uns8*)iptcData, iptcLen ); + MD5Update ( &context, (XMP_Uns8*)iptcPtr, iptcLen ); MD5Final ( *digest, &context ); } // ComputeIPTCDigest; // ================================================================================================= -// ReconcileUtils::CheckIPTCDigest +// PhotoDataUtils::CheckIPTCDigest // =============================== -int ReconcileUtils::CheckIPTCDigest ( IPTC_Manager * iptc, const PSIR_Manager & psir ) +int PhotoDataUtils::CheckIPTCDigest ( const void * newPtr, const XMP_Uns32 newLen, const void * oldDigest ) { MD5_Digest newDigest; - PSIR_Manager::ImgRsrcInfo ir1061; - - ComputeIPTCDigest ( iptc, &newDigest ); - bool found = psir.GetImgRsrc ( kPSIR_IPTCDigest, &ir1061 ); - - if ( ! found ) return kDigestMissing; - if ( ir1061.dataLen != 16 ) return kDigestMissing; - - if ( memcmp ( newDigest, ir1061.dataPtr, 16 ) == 0 ) return kDigestMatches; + ComputeIPTCDigest ( newPtr, newLen, &newDigest ); + if ( memcmp ( &newDigest, oldDigest, 16 ) == 0 ) return kDigestMatches; return kDigestDiffers; - -} // ReconcileUtils::CheckIPTCDigest + +} // PhotoDataUtils::CheckIPTCDigest // ================================================================================================= -// ReconcileUtils::SetIPTCDigest -// =============================== +// PhotoDataUtils::SetIPTCDigest +// ============================= -void ReconcileUtils::SetIPTCDigest ( IPTC_Manager * iptc, PSIR_Manager * psir ) +void PhotoDataUtils::SetIPTCDigest ( void * iptcPtr, XMP_Uns32 iptcLen, PSIR_Manager * psir ) { MD5_Digest newDigest; - - ComputeIPTCDigest ( iptc, &newDigest ); + + ComputeIPTCDigest ( iptcPtr, iptcLen, &newDigest ); psir->SetImgRsrc ( kPSIR_IPTCDigest, &newDigest, sizeof(newDigest) ); - -} // ReconcileUtils::SetIPTCDigest + +} // PhotoDataUtils::SetIPTCDigest // ================================================================================================= // ================================================================================================= // ================================================================================================= -// ImportIPTC_Simple -// ================= +// PhotoDataUtils::ImportIPTC_Simple +// ================================= -static void ImportIPTC_Simple ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) +void PhotoDataUtils::ImportIPTC_Simple ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) { - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; - } - std::string utf8Str; size_t count = iptc.GetDataSet_UTF8 ( id, &utf8Str ); @@ -132,59 +114,183 @@ static void ImportIPTC_Simple ( const IPTC_Manager & iptc, SXMPMeta * xmp, int d xmp->SetProperty ( xmpNS, xmpProp, utf8Str.c_str() ); } -} // ImportIPTC_Simple +} // PhotoDataUtils::ImportIPTC_Simple // ================================================================================================= -// ImportIPTC_LangAlt -// ================== +// PhotoDataUtils::ImportIPTC_LangAlt +// ================================== -static void ImportIPTC_LangAlt ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) +void PhotoDataUtils::ImportIPTC_LangAlt ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) { - if ( digestState == kDigestDiffers ) { - std::string xdItemPath = xmpProp; // Delete just the x-default item, not the whole array. - xdItemPath += "[?xml:lang='x-default']"; - xmp->DeleteProperty ( xmpNS, xdItemPath.c_str() ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; // Check the entire array here. - } - std::string utf8Str; - size_t count = iptc.GetDataSet_UTF8 ( id, &utf8Str ); - + if ( count != 0 ) { NormalizeToLF ( &utf8Str ); xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", utf8Str.c_str() ); } -} // ImportIPTC_LangAlt +} // PhotoDataUtils::ImportIPTC_LangAlt // ================================================================================================= -// ImportIPTC_Array -// ================ +// PhotoDataUtils::ImportIPTC_Array +// ================================ -static void ImportIPTC_Array ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) +void PhotoDataUtils::ImportIPTC_Array ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) { - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; - } - std::string utf8Str; size_t count = iptc.GetDataSet ( id, 0 ); + xmp->DeleteProperty ( xmpNS, xmpProp ); + + XMP_OptionBits arrayForm = kXMP_PropArrayIsUnordered; + if ( XMP_LitMatch ( xmpNS, kXMP_NS_DC ) && XMP_LitMatch ( xmpProp, "creator" ) ) arrayForm = kXMP_PropArrayIsOrdered; + for ( size_t ds = 0; ds < count; ++ds ) { (void) iptc.GetDataSet_UTF8 ( id, &utf8Str, ds ); NormalizeToLF ( &utf8Str ); - xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsUnordered, utf8Str.c_str() ); + xmp->AppendArrayItem ( xmpNS, xmpProp, arrayForm, utf8Str.c_str() ); } -} // ImportIPTC_Array +} // PhotoDataUtils::ImportIPTC_Array + +// ================================================================================================= +// PhotoDataUtils::ImportIPTC_Date +// =============================== +// +// An IPTC (IIM) date is 8 characters, YYYYMMDD. Include the time portion if it is present. The IPTC +// time is HHMMSSxHHMM, where 'x' is '+' or '-'. Be tolerant of some ill-formed dates and times. +// Apparently some non-Adobe apps put strings like "YYYY-MM-DD" or "HH:MM:SSxHH:MM" in the IPTC. +// Allow a missing time zone portion. + +// *** The date/time handling differs from the MWG 1.0.1 policy, following a proposed tweak to MWG: +// *** Exif DateTimeOriginal <-> XMP exif:DateTimeOriginal +// *** IPTC DateCreated <-> XMP photoshop:DateCreated +// *** Exif DateTimeDigitized <-> IPTC DigitalCreateDate <-> XMP xmp:CreateDate + +void PhotoDataUtils::ImportIPTC_Date ( XMP_Uns8 dateID, const IPTC_Manager & iptc, SXMPMeta * xmp ) +{ + XMP_Uns8 timeID; + XMP_StringPtr xmpNS, xmpProp; + + if ( dateID == kIPTC_DateCreated ) { + timeID = kIPTC_TimeCreated; + xmpNS = kXMP_NS_Photoshop; + xmpProp = "DateCreated"; + } else if ( dateID == kIPTC_DigitalCreateDate ) { + timeID = kIPTC_DigitalCreateTime; + xmpNS = kXMP_NS_XMP; + xmpProp = "CreateDate"; + } else { + XMP_Throw ( "Unrecognized dateID", kXMPErr_BadParam ); + } + + // First gather the date portion. + + IPTC_Manager::DataSetInfo dsInfo; + size_t count = iptc.GetDataSet ( dateID, &dsInfo ); + if ( count == 0 ) return; + + size_t chPos, digits; + XMP_DateTime xmpDate; + memset ( &xmpDate, 0, sizeof(xmpDate) ); + + chPos = 0; + for ( digits = 0; digits < 4; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.year = (xmpDate.year * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + + if ( dsInfo.dataPtr[chPos] == '-' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.month = (xmpDate.month * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.month < 1 ) xmpDate.month = 1; + if ( xmpDate.month > 12 ) xmpDate.month = 12; + + if ( dsInfo.dataPtr[chPos] == '-' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.day = (xmpDate.day * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.day < 1 ) xmpDate.day = 1; + if ( xmpDate.day > 31 ) xmpDate.day = 28; // Close enough. + + if ( chPos != dsInfo.dataLen ) return; // The DataSet is ill-formed. + xmpDate.hasDate = true; + + // Now add the time portion if present. + + count = iptc.GetDataSet ( timeID, &dsInfo ); + if ( count != 0 ) { + + chPos = 0; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.hour = (xmpDate.hour * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.hour < 0 ) xmpDate.hour = 0; + if ( xmpDate.hour > 23 ) xmpDate.hour = 23; + + if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.minute = (xmpDate.minute * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.minute < 0 ) xmpDate.minute = 0; + if ( xmpDate.minute > 59 ) xmpDate.minute = 59; + + if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.second = (xmpDate.second * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.second < 0 ) xmpDate.second = 0; + if ( xmpDate.second > 59 ) xmpDate.second = 59; + + xmpDate.hasTime = true; + + if ( (dsInfo.dataPtr[chPos] != ' ') && (dsInfo.dataPtr[chPos] != 0) ) { // Tolerate a missing TZ. + + if ( dsInfo.dataPtr[chPos] == '+' ) { + xmpDate.tzSign = kXMP_TimeEastOfUTC; + } else if ( dsInfo.dataPtr[chPos] == '-' ) { + xmpDate.tzSign = kXMP_TimeWestOfUTC; + } else if ( chPos != dsInfo.dataLen ) { + return; // The DataSet is ill-formed. + } + + ++chPos; // Move past the time zone sign. + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.tzHour = (xmpDate.tzHour * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.tzHour < 0 ) xmpDate.tzHour = 0; + if ( xmpDate.tzHour > 23 ) xmpDate.tzHour = 23; + + if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.tzMinute = (xmpDate.tzMinute * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.tzMinute < 0 ) xmpDate.tzMinute = 0; + if ( xmpDate.tzMinute > 59 ) xmpDate.tzMinute = 59; + + if ( chPos != dsInfo.dataLen ) return; // The DataSet is ill-formed. + xmpDate.hasTimeZone = true; + + } + + } + + // Finally, set the XMP property. + + xmp->SetProperty_Date ( xmpNS, xmpProp, xmpDate ); + +} // PhotoDataUtils::ImportIPTC_Date // ================================================================================================= // ImportIPTC_IntellectualGenre @@ -195,24 +301,16 @@ static void ImportIPTC_Array ( const IPTC_Manager & iptc, SXMPMeta * xmp, int di // XMP and the number is dropped. Also, even though IIMv4.1 says that 2:04 is repeatable, the XMP // property to which it is mapped is simple. -static void ImportIPTC_IntellectualGenre ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - const char * xmpNS, const char * xmpProp ) +static void ImportIPTC_IntellectualGenre ( const IPTC_Manager & iptc, SXMPMeta * xmp ) { - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; - } - std::string utf8Str; size_t count = iptc.GetDataSet_UTF8 ( kIPTC_IntellectualGenre, &utf8Str ); if ( count == 0 ) return; NormalizeToLF ( &utf8Str ); - + XMP_StringPtr namePtr = utf8Str.c_str() + 4; - + if ( utf8Str.size() <= 4 ) { // No name in the IIM. Look up the number in our list of known genres. int i; @@ -224,7 +322,7 @@ static void ImportIPTC_IntellectualGenre ( const IPTC_Manager & iptc, SXMPMeta * namePtr = kIntellectualGenreMappings[i].name; } - xmp->SetProperty ( xmpNS, xmpProp, namePtr ); + xmp->SetProperty ( kXMP_NS_IPTCCore, "IntellectualGenre", namePtr ); } // ImportIPTC_IntellectualGenre @@ -237,19 +335,11 @@ static void ImportIPTC_IntellectualGenre ( const IPTC_Manager & iptc, SXMPMeta * // levels of the reference number hierarchy. The IPTC4XMP mapping rule is that only the reference // number is imported to XMP. -static void ImportIPTC_SubjectCode ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - const char * xmpNS, const char * xmpProp ) +static void ImportIPTC_SubjectCode ( const IPTC_Manager & iptc, SXMPMeta * xmp ) { - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; - } - std::string utf8Str; size_t count = iptc.GetDataSet_UTF8 ( kIPTC_SubjectCode, 0 ); - + for ( size_t ds = 0; ds < count; ++ds ) { (void) iptc.GetDataSet_UTF8 ( kIPTC_SubjectCode, &utf8Str, ds ); @@ -263,179 +353,92 @@ static void ImportIPTC_SubjectCode ( const IPTC_Manager & iptc, SXMPMeta * xmp, if ( (refNumEnd - refNumPtr) != 8 ) continue; // This DataSet is ill-formed. *refNumEnd = 0; // Ensure a terminating nul for the reference number portion. - xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsUnordered, refNumPtr ); + xmp->AppendArrayItem ( kXMP_NS_IPTCCore, "SubjectCode", kXMP_PropArrayIsUnordered, refNumPtr ); } } // ImportIPTC_SubjectCode // ================================================================================================= -// ImportIPTC_DateCreated -// ====================== -// -// An IPTC (IIM) date is 8 charcters YYYYMMDD. Include the time portion from 2:60 if it is present. -// The IPTC time is HHMMSSxHHMM, where 'x' is '+' or '-'. Be tolerant of some ill-formed dates and -// times. Apparently some non-Adobe apps put strings like "YYYY-MM-DD" or "HH:MM:SSxHH:MM" in the -// IPTC. Allow a missing time zone portion to mean UTC. +// PhotoDataUtils::Import2WayIPTC +// ============================== -static void ImportIPTC_DateCreated ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - const char * xmpNS, const char * xmpProp ) +void PhotoDataUtils::Import2WayIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ) { - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; - } + if ( iptcDigestState == kDigestMatches ) return; // Ignore the IPTC if the digest matches. - // First gather the date portion. - - IPTC_Manager::DataSetInfo dsInfo; - size_t count = iptc.GetDataSet ( kIPTC_DateCreated, &dsInfo ); - if ( count == 0 ) return; - - size_t chPos, digits; - XMP_DateTime xmpDate; - memset ( &xmpDate, 0, sizeof(xmpDate) ); - - for ( chPos = 0, digits = 0; digits < 4; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.year = (xmpDate.year * 10) + (dsInfo.dataPtr[chPos] - '0'); + std::string oldStr, newStr; + IPTC_Writer oldIPTC; + + if ( iptcDigestState == kDigestDiffers ) { + PhotoDataUtils::ExportIPTC ( *xmp, &oldIPTC ); // Predict old IPTC DataSets based on the existing XMP. } - if ( dsInfo.dataPtr[chPos] == '-' ) ++chPos; - for ( digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.month = (xmpDate.month * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.month < 1 ) xmpDate.month = 1; - if ( xmpDate.month > 12 ) xmpDate.month = 12; + size_t newCount; + IPTC_Manager::DataSetInfo newInfo, oldInfo; - if ( dsInfo.dataPtr[chPos] == '-' ) ++chPos; - for ( digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.day = (xmpDate.day * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.day < 1 ) xmpDate.day = 1; - if ( xmpDate.day > 31 ) xmpDate.day = 28; // Close enough. - - if ( chPos != dsInfo.dataLen ) return; // The DataSet is ill-formed. + for ( size_t i = 0; kKnownDataSets[i].id != 255; ++i ) { - // Now add the time portion if present. - - count = iptc.GetDataSet ( kIPTC_TimeCreated, &dsInfo ); - if ( count != 0 ) { - - for ( chPos = 0, digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.hour = (xmpDate.hour * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.hour < 0 ) xmpDate.hour = 0; - if ( xmpDate.hour > 23 ) xmpDate.hour = 23; - - if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; - for ( digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.minute = (xmpDate.minute * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.minute < 0 ) xmpDate.minute = 0; - if ( xmpDate.minute > 59 ) xmpDate.minute = 59; + const DataSetCharacteristics & thisDS = kKnownDataSets[i]; + if ( thisDS.mapForm >= kIPTC_Map3Way ) continue; // The mapping is handled elsewhere, or not at all. - if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; - for ( digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.second = (xmpDate.second * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.second < 0 ) xmpDate.second = 0; - if ( xmpDate.second > 59 ) xmpDate.second = 59; + bool haveXMP = xmp->DoesPropertyExist ( thisDS.xmpNS, thisDS.xmpProp ); + newCount = PhotoDataUtils::GetNativeInfo ( iptc, thisDS.id, iptcDigestState, haveXMP, &newInfo ); + if ( newCount == 0 ) continue; // GetNativeInfo returns 0 for ignored local text. - if ( dsInfo.dataPtr[chPos] == '+' ) { - xmpDate.tzSign = kXMP_TimeEastOfUTC; - } else if ( dsInfo.dataPtr[chPos] == '-' ) { - xmpDate.tzSign = kXMP_TimeWestOfUTC; - } else if ( chPos != dsInfo.dataLen ) { - return; // The DataSet is ill-formed. + if ( iptcDigestState == kDigestMissing ) { + if ( haveXMP ) continue; // Keep the existing XMP. + } else if ( ! PhotoDataUtils::IsValueDifferent ( iptc, oldIPTC, thisDS.id ) ) { + continue; // Don't import values that match the previous export. } - ++chPos; // Move past the time zone sign. - for ( chPos = 0, digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.tzHour = (xmpDate.tzHour * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.tzHour < 0 ) xmpDate.tzHour = 0; - if ( xmpDate.tzHour > 23 ) xmpDate.tzHour = 23; + // The IPTC wins. Delete any existing XMP and import the DataSet. - if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; - for ( digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.tzMinute = (xmpDate.tzMinute * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.tzMinute < 0 ) xmpDate.tzMinute = 0; - if ( xmpDate.tzMinute > 59 ) xmpDate.tzMinute = 59; - - if ( chPos != dsInfo.dataLen ) return; // The DataSet is ill-formed. - - } - - // Finally, set the XMP property. - - xmp->SetProperty_Date ( xmpNS, xmpProp, xmpDate ); - -} // ImportIPTC_DateCreated - -// ================================================================================================= -// ReconcileUtils::ImportIPTC -// ========================== - -void ReconcileUtils::ImportIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState ) -{ - if ( digestState == kDigestMatches ) return; - - for ( size_t i = 0; kKnownDataSets[i].id != 255; ++i ) { - - const DataSetCharacteristics & thisDS = kKnownDataSets[i]; + xmp->DeleteProperty ( thisDS.xmpNS, thisDS.xmpProp ); try { // Don't let errors with one stop the others. - + switch ( thisDS.mapForm ) { - + case kIPTC_MapSimple : - ImportIPTC_Simple ( iptc, xmp, digestState, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); + ImportIPTC_Simple ( iptc, xmp, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); break; - + case kIPTC_MapLangAlt : - ImportIPTC_LangAlt ( iptc, xmp, digestState, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); + ImportIPTC_LangAlt ( iptc, xmp, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); break; - + case kIPTC_MapArray : - ImportIPTC_Array ( iptc, xmp, digestState, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); + ImportIPTC_Array ( iptc, xmp, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); break; - + case kIPTC_MapSpecial : - if ( thisDS.id == kIPTC_IntellectualGenre ) { - ImportIPTC_IntellectualGenre ( iptc, xmp, digestState, thisDS.xmpNS, thisDS.xmpProp ); + if ( thisDS.id == kIPTC_DateCreated ) { + PhotoDataUtils::ImportIPTC_Date ( thisDS.id, iptc, xmp ); + } else if ( thisDS.id == kIPTC_IntellectualGenre ) { + ImportIPTC_IntellectualGenre ( iptc, xmp ); } else if ( thisDS.id == kIPTC_SubjectCode ) { - ImportIPTC_SubjectCode ( iptc, xmp, digestState, thisDS.xmpNS, thisDS.xmpProp ); - } else if ( thisDS.id == kIPTC_DateCreated ) { - ImportIPTC_DateCreated ( iptc, xmp, digestState, thisDS.xmpNS, thisDS.xmpProp ); - } + ImportIPTC_SubjectCode ( iptc, xmp ); + } else { + XMP_Assert ( false ); // Catch mapping errors. + } break; - + } } catch ( ... ) { - + // Do nothing, let other imports proceed. // ? Notify client? - + } } - -} // ReconcileUtils::ImportIPTC; + +} // PhotoDataUtils::Import2WayIPTC // ================================================================================================= -// ReconcileUtils::ImportPSIR +// PhotoDataUtils::ImportPSIR // ========================== // // There are only 2 standalone Photoshop image resources for XMP properties: @@ -446,21 +449,13 @@ void ReconcileUtils::ImportIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int // ! yes/don't-know model when importing. A missing or 0 value for PSIR 1034 cause xmpRights:Marked // ! to be deleted. -// **** What about 1008 and 1020? - -void ReconcileUtils::ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int digestState ) +void PhotoDataUtils::ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int iptcDigestState ) { PSIR_Manager::ImgRsrcInfo rsrcInfo; bool import; - - if ( digestState == kDigestMatches ) return; - - if ( digestState == kDigestDiffers ) { - // Delete the mapped XMP. This forces replacement and catches legacy deletions. - xmp->DeleteProperty ( kXMP_NS_XMP_Rights, "Marked" ); - xmp->DeleteProperty ( kXMP_NS_XMP_Rights, "WebStatement" ); - } - + + if ( iptcDigestState == kDigestMatches ) return; + try { // Don't let errors with one stop the others. import = psir.GetImgRsrc ( kPSIR_CopyrightFlag, &rsrcInfo ); if ( import ) import = (! xmp->DoesPropertyExist ( kXMP_NS_XMP_Rights, "Marked" )); @@ -471,29 +466,27 @@ void ReconcileUtils::ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int // Do nothing, let other imports proceed. // ? Notify client? } - + try { // Don't let errors with one stop the others. import = psir.GetImgRsrc ( kPSIR_CopyrightURL, &rsrcInfo ); if ( import ) import = (! xmp->DoesPropertyExist ( kXMP_NS_XMP_Rights, "WebStatement" )); if ( import ) { - #if ! XMP_UNIXBuild - std::string utf8; + std::string utf8; + if ( ReconcileUtils::IsUTF8 ( rsrcInfo.dataPtr, rsrcInfo.dataLen ) ) { + utf8.assign ( (char*)rsrcInfo.dataPtr, rsrcInfo.dataLen ); + } else if ( ! ignoreLocalText ) { ReconcileUtils::LocalToUTF8 ( rsrcInfo.dataPtr, rsrcInfo.dataLen, &utf8 ); - xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", utf8.c_str() ); - #else - // ! Hack until legacy-as-local issues are resolved for generic UNIX. - if ( ReconcileUtils::IsUTF8 ( rsrcInfo.dataPtr, rsrcInfo.dataLen ) ) { - std::string utf8 ( (char*)rsrcInfo.dataPtr, rsrcInfo.dataLen ); - xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", utf8.c_str() ); - } - #endif + } else { + import = false; // Inhibit the SetProperty call. + } + if ( import ) xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", utf8.c_str() ); } } catch ( ... ) { // Do nothing, let other imports proceed. // ? Notify client? } - -} // ReconcileUtils::ImportPSIR; + +} // PhotoDataUtils::ImportPSIR; // ================================================================================================= // ================================================================================================= @@ -502,22 +495,22 @@ void ReconcileUtils::ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int // ExportIPTC_Simple // ================= -static void ExportIPTC_Simple ( SXMPMeta * xmp, IPTC_Manager * iptc, +static void ExportIPTC_Simple ( const SXMPMeta & xmp, IPTC_Manager * iptc, const char * xmpNS, const char * xmpProp, XMP_Uns8 id ) { std::string value; XMP_OptionBits xmpFlags; - bool found = xmp->GetProperty ( xmpNS, xmpProp, &value, &xmpFlags ); + bool found = xmp.GetProperty ( xmpNS, xmpProp, &value, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( id ); return; } - + if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? - + NormalizeToCR ( &value ); - + size_t iptcCount = iptc->GetDataSet ( id, 0 ); if ( iptcCount > 1 ) iptc->DeleteDataSet ( id ); @@ -529,21 +522,21 @@ static void ExportIPTC_Simple ( SXMPMeta * xmp, IPTC_Manager * iptc, // ExportIPTC_LangAlt // ================== -static void ExportIPTC_LangAlt ( SXMPMeta * xmp, IPTC_Manager * iptc, +static void ExportIPTC_LangAlt ( const SXMPMeta & xmp, IPTC_Manager * iptc, const char * xmpNS, const char * xmpProp, XMP_Uns8 id ) { std::string value; XMP_OptionBits xmpFlags; - bool found = xmp->GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); + bool found = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( id ); return; } if ( ! XMP_ArrayIsAltText ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? - - found = xmp->GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &value, 0 ); + + found = xmp.GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &value, 0 ); if ( ! found ) { iptc->DeleteDataSet ( id ); return; @@ -566,13 +559,13 @@ static void ExportIPTC_LangAlt ( SXMPMeta * xmp, IPTC_Manager * iptc, // XMP and IPTC array sizes differ, delete the entire IPTC and append all new values. If they match, // set the individual values in order - which lets SetDataSet apply its no-change optimization. -static void ExportIPTC_Array ( SXMPMeta * xmp, IPTC_Manager * iptc, +static void ExportIPTC_Array ( const SXMPMeta & xmp, IPTC_Manager * iptc, const char * xmpNS, const char * xmpProp, XMP_Uns8 id ) { std::string value; XMP_OptionBits xmpFlags; - bool found = xmp->GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); + bool found = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( id ); return; @@ -580,14 +573,14 @@ static void ExportIPTC_Array ( SXMPMeta * xmp, IPTC_Manager * iptc, if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? - XMP_Index xmpCount = xmp->CountArrayItems ( xmpNS, xmpProp ); + XMP_Index xmpCount = xmp.CountArrayItems ( xmpNS, xmpProp ); XMP_Index iptcCount = (XMP_Index) iptc->GetDataSet ( id, 0 ); - + if ( xmpCount != iptcCount ) iptc->DeleteDataSet ( id ); for ( XMP_Index ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0. - (void) xmp->GetArrayItem ( xmpNS, xmpProp, ds+1, &value, &xmpFlags ); + (void) xmp.GetArrayItem ( xmpNS, xmpProp, ds+1, &value, &xmpFlags ); if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? NormalizeToCR ( &value ); @@ -607,20 +600,19 @@ static void ExportIPTC_Array ( SXMPMeta * xmp, IPTC_Manager * iptc, // number is dropped. Also, even though IIMv4.1 says that 2:04 is repeatable, the XMP property to // which it is mapped is simple. Look up the XMP value in a list of known genres to get the number. -static void ExportIPTC_IntellectualGenre ( SXMPMeta * xmp, IPTC_Manager * iptc, - const char * xmpNS, const char * xmpProp ) +static void ExportIPTC_IntellectualGenre ( const SXMPMeta & xmp, IPTC_Manager * iptc ) { std::string xmpValue; XMP_OptionBits xmpFlags; - bool found = xmp->GetProperty ( xmpNS, xmpProp, &xmpValue, &xmpFlags ); + bool found = xmp.GetProperty ( kXMP_NS_IPTCCore, "IntellectualGenre", &xmpValue, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( kIPTC_IntellectualGenre ); return; } - + if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? - + NormalizeToCR ( &xmpValue ); int i; @@ -629,11 +621,11 @@ static void ExportIPTC_IntellectualGenre ( SXMPMeta * xmp, IPTC_Manager * iptc, if ( strcmp ( namePtr, kIntellectualGenreMappings[i].name ) == 0 ) break; } if ( kIntellectualGenreMappings[i].name == 0 ) return; // Not a known genre, don't export it. - + std::string iimValue = kIntellectualGenreMappings[i].refNum; iimValue += ':'; iimValue += xmpValue; - + size_t iptcCount = iptc->GetDataSet ( kIPTC_IntellectualGenre, 0 ); if ( iptcCount > 1 ) iptc->DeleteDataSet ( kIPTC_IntellectualGenre ); @@ -650,13 +642,12 @@ static void ExportIPTC_IntellectualGenre ( SXMPMeta * xmp, IPTC_Manager * iptc, // levels of the reference number hierarchy. The IPTC4XMP mapping rule is that only the reference // number is imported to XMP. We export with a fixed provider of "IPTC" and no optional names. -static void ExportIPTC_SubjectCode ( SXMPMeta * xmp, IPTC_Manager * iptc, - const char * xmpNS, const char * xmpProp ) +static void ExportIPTC_SubjectCode ( const SXMPMeta & xmp, IPTC_Manager * iptc ) { std::string xmpValue, iimValue; XMP_OptionBits xmpFlags; - bool found = xmp->GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); + bool found = xmp.GetProperty ( kXMP_NS_IPTCCore, "SubjectCode", 0, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( kIPTC_SubjectCode ); return; @@ -664,14 +655,14 @@ static void ExportIPTC_SubjectCode ( SXMPMeta * xmp, IPTC_Manager * iptc, if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? - XMP_Index xmpCount = xmp->CountArrayItems ( xmpNS, xmpProp ); + XMP_Index xmpCount = xmp.CountArrayItems ( kXMP_NS_IPTCCore, "SubjectCode" ); XMP_Index iptcCount = (XMP_Index) iptc->GetDataSet ( kIPTC_SubjectCode, 0 ); - + if ( xmpCount != iptcCount ) iptc->DeleteDataSet ( kIPTC_SubjectCode ); for ( XMP_Index ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0. - (void) xmp->GetArrayItem ( xmpNS, xmpProp, ds+1, &xmpValue, &xmpFlags ); + (void) xmp.GetArrayItem ( kXMP_NS_IPTCCore, "SubjectCode", ds+1, &xmpValue, &xmpFlags ); if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? if ( xmpValue.size() != 8 ) continue; // ? Complain? @@ -686,119 +677,136 @@ static void ExportIPTC_SubjectCode ( SXMPMeta * xmp, IPTC_Manager * iptc, } // ExportIPTC_SubjectCode // ================================================================================================= -// ExportIPTC_DateCreated -// ====================== +// ExportIPTC_Date +// =============== // // The IPTC date and time are "YYYYMMDD" and "HHMMSSxHHMM" where 'x' is '+' or '-'. Export the IPTC // time only if already present, or if the XMP has a time portion. -static void ExportIPTC_DateCreated ( SXMPMeta * xmp, IPTC_Manager * iptc, - const char * xmpNS, const char * xmpProp ) -{ - std::string xmpStr; - XMP_DateTime xmpValue; - XMP_OptionBits xmpFlags; +// *** The date/time handling differs from the MWG 1.0 policy, following a proposed tweak to MWG: +// *** Exif DateTimeOriginal <-> IPTC DateCreated <-> XMP photoshop:DateCreated +// *** Exif DateTimeDigitized <-> IPTC DigitalCreateDate <-> XMP xmp:CreateDate - bool xmpHasTime = false; - - bool found = xmp->GetProperty ( xmpNS, xmpProp, &xmpStr, &xmpFlags ); - if ( found ) { - SXMPUtils::ConvertToDate ( xmpStr.c_str(), &xmpValue ); - if ( xmpStr.size() > 10 ) xmpHasTime = true; // Date-only values are up to "YYYY-MM-DD". +static void ExportIPTC_Date ( XMP_Uns8 dateID, const SXMPMeta & xmp, IPTC_Manager * iptc ) +{ + XMP_Uns8 timeID; + XMP_StringPtr xmpNS, xmpProp; + + if ( dateID == kIPTC_DateCreated ) { + timeID = kIPTC_TimeCreated; + xmpNS = kXMP_NS_Photoshop; + xmpProp = "DateCreated"; + } else if ( dateID == kIPTC_DigitalCreateDate ) { + timeID = kIPTC_DigitalCreateTime; + xmpNS = kXMP_NS_XMP; + xmpProp = "CreateDate"; } else { - iptc->DeleteDataSet ( kIPTC_DateCreated ); - iptc->DeleteDataSet ( kIPTC_TimeCreated ); - return; + XMP_Throw ( "Unrecognized dateID", kXMPErr_BadParam ); } - char iimValue[16]; - - // Set the IIM date portion. + iptc->DeleteDataSet ( dateID ); // ! Either the XMP does not exist and we want to + iptc->DeleteDataSet ( timeID ); // ! delete the IPTC, or we're replacing the IPTC. + + XMP_DateTime xmpValue; + bool found = xmp.GetProperty_Date ( xmpNS, xmpProp, &xmpValue, 0 ); + if ( ! found ) return; + + char iimValue[16]; // AUDIT: Big enough for "YYYYMMDD" (8) and "HHMMSS+HHMM" (11). + + // Set the IIM date portion as YYYYMMDD with zeroes for unknown parts. - snprintf ( iimValue, sizeof(iimValue), "%.4d%.2d%.2d", // AUDIT: Use of sizeof(iimValue) is safe. + snprintf ( iimValue, sizeof(iimValue), "%04d%02d%02d", // AUDIT: Use of sizeof(iimValue) is safe. xmpValue.year, xmpValue.month, xmpValue.day ); - if ( iimValue[8] != 0 ) return; // ? Complain? Delete the DataSet? - - size_t iptcCount = iptc->GetDataSet ( kIPTC_DateCreated, 0 ); - if ( iptcCount > 1 ) iptc->DeleteDataSet ( kIPTC_DateCreated ); - iptc->SetDataSet_UTF8 ( kIPTC_DateCreated, iimValue, 8, 0 ); // ! Don't append a 2nd DataSet! - - // Set the IIM time portion. + iptc->SetDataSet_UTF8 ( dateID, iimValue, 8 ); - iptcCount = iptc->GetDataSet ( kIPTC_TimeCreated, 0 ); - - if ( (iptcCount > 0) || xmpHasTime ) { - - snprintf ( iimValue, sizeof(iimValue), "%.2d%.2d%.2d%c%.2d%.2d", // AUDIT: Use of sizeof(iimValue) is safe. + // Set the IIM time portion as HHMMSS+HHMM (or -HHMM). Allow a missing time zone. + + if ( xmpValue.hasTimeZone ) { + snprintf ( iimValue, sizeof(iimValue), "%02d%02d%02d%c%02d%02d", // AUDIT: Use of sizeof(iimValue) is safe. xmpValue.hour, xmpValue.minute, xmpValue.second, ((xmpValue.tzSign == kXMP_TimeWestOfUTC) ? '-' : '+'), xmpValue.tzHour, xmpValue.tzMinute ); - if ( iimValue[11] != 0 ) return; // ? Complain? Delete the DataSet? - - if ( iptcCount > 1 ) iptc->DeleteDataSet ( kIPTC_TimeCreated ); - - iptc->SetDataSet_UTF8 ( kIPTC_TimeCreated, iimValue, 11, 0 ); // ! Don't append a 2nd DataSet! - + iptc->SetDataSet_UTF8 ( timeID, iimValue, 11 ); + } else if ( xmpValue.hasTime ) { + snprintf ( iimValue, sizeof(iimValue), "%02d%02d%02d", // AUDIT: Use of sizeof(iimValue) is safe. + xmpValue.hour, xmpValue.minute, xmpValue.second ); + iptc->SetDataSet_UTF8 ( timeID, iimValue, 6 ); + } else { + iptc->DeleteDataSet ( timeID ); } -} // ExportIPTC_DateCreated +} // ExportIPTC_Date // ================================================================================================= -// ReconcileUtils::ExportIPTC +// PhotoDataUtils::ExportIPTC // ========================== -void ReconcileUtils::ExportIPTC ( SXMPMeta * xmp, IPTC_Manager * iptc ) +void PhotoDataUtils::ExportIPTC ( const SXMPMeta & xmp, IPTC_Manager * iptc ) { - #if XMP_UNIXBuild - return; // ! Hack until the legacy-as-local issues are resolved for generic UNIX. - #endif - for ( size_t i = 0; kKnownDataSets[i].id != 255; ++i ) { - + try { // Don't let errors with one stop the others. - + const DataSetCharacteristics & thisDS = kKnownDataSets[i]; - + if ( thisDS.mapForm >= kIPTC_UnmappedText ) continue; + switch ( thisDS.mapForm ) { - + case kIPTC_MapSimple : ExportIPTC_Simple ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp, thisDS.id ); break; - + case kIPTC_MapLangAlt : ExportIPTC_LangAlt ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp, thisDS.id ); break; - + case kIPTC_MapArray : ExportIPTC_Array ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp, thisDS.id ); break; - + case kIPTC_MapSpecial : - if ( thisDS.id == kIPTC_IntellectualGenre ) { - ExportIPTC_IntellectualGenre ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp ); + if ( thisDS.id == kIPTC_DateCreated ) { + ExportIPTC_Date ( thisDS.id, xmp, iptc ); + } else if ( thisDS.id == kIPTC_IntellectualGenre ) { + ExportIPTC_IntellectualGenre ( xmp, iptc ); } else if ( thisDS.id == kIPTC_SubjectCode ) { - ExportIPTC_SubjectCode ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp ); - } else if ( thisDS.id == kIPTC_DateCreated ) { - ExportIPTC_DateCreated ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp ); - } + ExportIPTC_SubjectCode ( xmp, iptc ); + } else { + XMP_Assert ( false ); // Catch mapping errors. + } break; - + + case kIPTC_Map3Way : // The 3 way case is special for import, not for export. + if ( thisDS.id == kIPTC_DigitalCreateDate ) { + // ! Special case: Don't create IIM DigitalCreateDate. This can avoid PSD + // ! full rewrite due to new mapping from xmp:CreateDate. + if ( iptc->GetDataSet ( thisDS.id, 0 ) > 0 ) ExportIPTC_Date ( thisDS.id, xmp, iptc ); + } else if ( thisDS.id == kIPTC_Creator ) { + ExportIPTC_Array ( xmp, iptc, kXMP_NS_DC, "creator", kIPTC_Creator ); + } else if ( thisDS.id == kIPTC_CopyrightNotice ) { + ExportIPTC_LangAlt ( xmp, iptc, kXMP_NS_DC, "rights", kIPTC_CopyrightNotice ); + } else if ( thisDS.id == kIPTC_Description ) { + ExportIPTC_LangAlt ( xmp, iptc, kXMP_NS_DC, "description", kIPTC_Description ); + } else { + XMP_Assert ( false ); // Catch mapping errors. + } + } } catch ( ... ) { - + // Do nothing, let other exports proceed. // ? Notify client? - + } } - -} // ReconcileUtils::ExportIPTC; + +} // PhotoDataUtils::ExportIPTC; // ================================================================================================= -// ReconcileUtils::ExportPSIR +// PhotoDataUtils::ExportPSIR // ========================== // // There are only 2 standalone Photoshop image resources for XMP properties: @@ -811,11 +819,11 @@ void ReconcileUtils::ExportIPTC ( SXMPMeta * xmp, IPTC_Manager * iptc ) // ! We don't bother with the CR<->LF normalization for xmpRights:WebStatement. Very little chance // ! of having a raw CR character in a URI. -void ReconcileUtils::ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir ) +void PhotoDataUtils::ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir ) { bool found; std::string utf8Value; - + try { // Don't let errors with one stop the others. bool copyrighted = false; found = xmp.GetProperty ( kXMP_NS_XMP_Rights, "Marked", &utf8Value, 0 ); @@ -825,24 +833,23 @@ void ReconcileUtils::ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir ) // Do nothing, let other exports proceed. // ? Notify client? } - + try { // Don't let errors with one stop the others. found = xmp.GetProperty ( kXMP_NS_XMP_Rights, "WebStatement", &utf8Value, 0 ); if ( ! found ) { psir->DeleteImgRsrc ( kPSIR_CopyrightURL ); + } else if ( ! ignoreLocalText ) { + std::string localValue; + ReconcileUtils::UTF8ToLocal ( utf8Value.c_str(), utf8Value.size(), &localValue ); + psir->SetImgRsrc ( kPSIR_CopyrightURL, localValue.c_str(), (XMP_Uns32)localValue.size() ); + } else if ( ReconcileUtils::IsASCII ( utf8Value.c_str(), utf8Value.size() ) ) { + psir->SetImgRsrc ( kPSIR_CopyrightURL, utf8Value.c_str(), (XMP_Uns32)utf8Value.size() ); } else { - #if ! XMP_UNIXBuild - std::string localValue; - ReconcileUtils::UTF8ToLocal ( utf8Value.c_str(), utf8Value.size(), &localValue ); - psir->SetImgRsrc ( kPSIR_CopyrightURL, localValue.c_str(), (XMP_Uns32)localValue.size() ); - #else - // ! Hack until legacy-as-local issues are resolved for generic UNIX. - psir->DeleteImgRsrc ( kPSIR_CopyrightURL ); - #endif + psir->DeleteImgRsrc ( kPSIR_CopyrightURL ); } } catch ( ... ) { // Do nothing, let other exports proceed. // ? Notify client? } -} // ReconcileUtils::ExportPSIR; +} // PhotoDataUtils::ExportPSIR; diff --git a/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp b/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp index 7c9f1f4..78eeaa4 100644 --- a/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp +++ b/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -14,201 +14,172 @@ // ================================================================================================= /// \file ReconcileLegacy.cpp -/// \brief Top level parts of utilities to reconcile between XMP and legacy metadata forms such as +/// \brief Top level parts of utilities to reconcile between XMP and legacy metadata forms such as /// TIFF/Exif and IPTC. /// // ================================================================================================= // ================================================================================================= -// ImportJTPtoXMP -// ============== +// ImportPhotoData +// =============== // // Import legacy metadata for JPEG, TIFF, and Photoshop files into the XMP. The caller must have // already done the file specific processing to select the appropriate sources of the TIFF stream, // the Photoshop image resources, and the IPTC. -// ! Note that kLegacyJTP_None does not literally mean no legacy. It means no IPTC-like legacy, i.e. -// ! stuff that Photoshop pre-7 would reconcile into the IPTC and thus affect the import order below. - -void ImportJTPtoXMP ( XMP_FileFormat srcFormat, - RecJTP_LegacyPriority lastLegacy, - TIFF_Manager * tiff, // ! Need for UserComment and RelatedSoundFile hack. - const PSIR_Manager & psir, - IPTC_Manager * iptc, // ! Need to call UpdateDataSets. - SXMPMeta * xmp, - XMP_OptionBits options /* = 0 */ ) +#define SaveExifTag(ns,prop) \ + if ( xmp->DoesPropertyExist ( ns, prop ) ) SXMPUtils::DuplicateSubtree ( *xmp, &savedExif, ns, prop ) +#define RestoreExifTag(ns,prop) \ + if ( savedExif.DoesPropertyExist ( ns, prop ) ) SXMPUtils::DuplicateSubtree ( savedExif, xmp, ns, prop ) + +void ImportPhotoData ( const TIFF_Manager & exif, + const IPTC_Manager & iptc, + const PSIR_Manager & psir, + int iptcDigestState, + SXMPMeta * xmp, + XMP_OptionBits options /* = 0 */ ) { bool haveXMP = XMP_OptionIsSet ( options, k2XMP_FileHadXMP ); - bool haveIPTC = XMP_OptionIsSet ( options, k2XMP_FileHadIPTC ); bool haveExif = XMP_OptionIsSet ( options, k2XMP_FileHadExif ); + bool haveIPTC = XMP_OptionIsSet ( options, k2XMP_FileHadIPTC ); - int iptcDigestState = kDigestMatches; // Default is to do no imports. - int tiffDigestState = kDigestMatches; - int exifDigestState = kDigestMatches; - - if ( ! haveXMP ) { + // Save some new Exif writebacks that can be XMP-only from older versions, delete all of the + // XMP's tiff: and exif: namespaces (they should only reflect native Exif), then put back the + // saved writebacks (which might get replaced by the native Exif values in the Import calls). + // The value of exif:ISOSpeedRatings is saved for special case handling of ISO over 65535. - // If there is no XMP then what we have differs. - if ( haveIPTC) iptcDigestState = kDigestDiffers; - if ( haveExif ) tiffDigestState = exifDigestState = kDigestDiffers; + SXMPMeta savedExif; - } else { - - // If there is XMP then check the digests for what we have. No legacy at all means the XMP - // is OK, and the CheckXyzDigest routines return true when there is no digest. This matches - // Photoshop, and avoids importing when an app adds XMP but does not export to the legacy or - // write a digest. - - if ( haveIPTC ) iptcDigestState = ReconcileUtils::CheckIPTCDigest ( iptc, psir ); - if ( iptcDigestState == kDigestMissing ) { - // *** Temporary hack to approximate Photoshop's behavior. Need fully documented policies! - tiffDigestState = exifDigestState = kDigestMissing; - } else if ( haveExif ) { - tiffDigestState = ReconcileUtils::CheckTIFFDigest ( *tiff, *xmp ); - exifDigestState = ReconcileUtils::CheckExifDigest ( *tiff, *xmp ); // ! Yes, the Exif is in the TIFF stream. - } - - } + SaveExifTag ( kXMP_NS_EXIF, "DateTimeOriginal" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSLatitude" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSLongitude" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSTimeStamp" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSAltitude" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSAltitudeRef" ); + SaveExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); - if ( lastLegacy > kLegacyJTP_TIFF_IPTC ) { - XMP_Throw ( "Invalid JTP legacy priority", kXMPErr_InternalFailure ); - } + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_TIFF, 0, kXMPUtil_DoAllProperties ); + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_EXIF, 0, kXMPUtil_DoAllProperties ); + + RestoreExifTag ( kXMP_NS_EXIF, "DateTimeOriginal" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSLatitude" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSLongitude" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSTimeStamp" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSAltitude" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSAltitudeRef" ); + RestoreExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); - // A TIFF file with tags 270, 315, or 33432 is currently the only case where the IPTC is less - // important than the TIFF/Exif. If there is no IPTC or no TIFF/Exif then the order does not - // matter. The order only affects collisions between those 3 TIFF tags and their IPTC counterparts. + // Not obvious here, but the logic in PhotoDataUtils follows the MWG reader guidelines. - if ( lastLegacy == kLegacyJTP_TIFF_TIFF_Tags ) { + PhotoDataUtils::ImportPSIR ( psir, xmp, iptcDigestState ); - if ( iptcDigestState != kDigestMatches ) { - ReconcileUtils::ImportIPTC ( *iptc, xmp, iptcDigestState ); - ReconcileUtils::ImportPSIR ( psir, xmp, iptcDigestState ); - } - if ( tiffDigestState != kDigestMatches ) ReconcileUtils::ImportTIFF ( *tiff, xmp, tiffDigestState, srcFormat ); - if ( exifDigestState != kDigestMatches ) ReconcileUtils::ImportExif ( *tiff, xmp, exifDigestState ); + if ( haveIPTC ) PhotoDataUtils::Import2WayIPTC ( iptc, xmp, iptcDigestState ); + if ( haveExif ) PhotoDataUtils::Import2WayExif ( exif, xmp, iptcDigestState ); - } else { + if ( haveExif | haveIPTC ) PhotoDataUtils::Import3WayItems ( exif, iptc, xmp, iptcDigestState ); - if ( tiffDigestState != kDigestMatches ) ReconcileUtils::ImportTIFF ( *tiff, xmp, tiffDigestState, srcFormat ); - if ( exifDigestState != kDigestMatches ) ReconcileUtils::ImportExif ( *tiff, xmp, exifDigestState ); - if ( iptcDigestState != kDigestMatches ) { - ReconcileUtils::ImportIPTC ( *iptc, xmp, iptcDigestState ); - ReconcileUtils::ImportPSIR ( psir, xmp, iptcDigestState ); - } - - } - - // ! Older versions of Photoshop did not import the UserComment or RelatedSoundFile tags. Note - // ! whether the initial XMP has these tags. Don't delete them from the TIFF when saving unless - // ! they were in the XMP to begin with. Can't do this in ReconcileUtils::ImportExif, that is - // ! only called when the Exif is newer than the XMP. + // If photoshop:DateCreated does not exist try to create it from exif:DateTimeOriginal. - tiff->xmpHadUserComment = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "UserComment" ); - tiff->xmpHadRelatedSoundFile = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "RelatedSoundFile" ); + if ( ! xmp->DoesPropertyExist ( kXMP_NS_Photoshop, "DateCreated" ) ) { + std::string exifValue; + bool haveExifDTO = xmp->GetProperty ( kXMP_NS_EXIF, "DateTimeOriginal", &exifValue, 0 ); + if ( haveExifDTO ) xmp->SetProperty ( kXMP_NS_Photoshop, "DateCreated", exifValue.c_str() ); + } -} // ImportJTPtoXMP +} // ImportPhotoData // ================================================================================================= -// ExportXMPtoJTP -// ============== - -void ExportXMPtoJTP ( XMP_FileFormat destFormat, - SXMPMeta * xmp, - TIFF_Manager * tiff, - PSIR_Manager * psir, - IPTC_Manager * iptc, - XMP_OptionBits options /* = 0 */ ) +// ExportPhotoData +// =============== + +void ExportPhotoData ( XMP_FileFormat destFormat, + SXMPMeta * xmp, + TIFF_Manager * exif, // Pass 0 if not wanted. + IPTC_Manager * iptc, // Pass 0 if not wanted. + PSIR_Manager * psir, // Pass 0 if not wanted. + XMP_OptionBits options /* = 0 */ ) { - XMP_Assert ( xmp != 0 ); XMP_Assert ( (destFormat == kXMP_JPEGFile) || (destFormat == kXMP_TIFFFile) || (destFormat == kXMP_PhotoshopFile) ); - - #if XMP_UNIXBuild - // ! Hack until the legacy-as-local issues are resolved for generic UNIX. - iptc = 0; // Strip IIM from the file. - if ( tiff != 0 ) tiff->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_IPTC ); - if ( psir != 0 ) { - psir->DeleteImgRsrc ( kPSIR_IPTC ); - psir->DeleteImgRsrc ( kPSIR_IPTCDigest ); - } - #endif - // Save the IPTC changed flag specially. SetIPTCDigest will call UpdateMemoryDataSets, which - // will clear the IsChanged flag. Also, UpdateMemoryDataSets can be called twice, once for the - // general IPTC-in-PSIR case and once for the IPTC-as-TIFF-tag case. - - bool iptcChanged = false; - - // Do not write legacy IPTC (IIM) or PSIR in DNG files (which are a variant of TIFF). - - if ( (destFormat == kXMP_TIFFFile) && (tiff != 0) && - tiff->GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, 0 ) ) { - + // Do not write IPTC-IIM or PSIR in DNG files (which are a variant of TIFF). + + if ( (destFormat == kXMP_TIFFFile) && (exif != 0) && + exif->GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, 0 ) ) { + iptc = 0; // These prevent calls to ExportIPTC and ExportPSIR. psir = 0; - - tiff->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_IPTC ); // These remove any existing IPTC and PSIR. - tiff->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_PSIR ); - - } - - // Export the individual metadata items to the legacy forms. The PSIR and IPTC must be done - // before the TIFF and Exif. The PSIR and IPTC have side effects that can modify the XMP, and - // thus the values written to TIFF and Exif. The side effects are the CR<->LF normalization that - // is done to match Photoshop. - - if ( psir != 0) ReconcileUtils::ExportPSIR ( *xmp, psir ); - if ( iptc != 0 ) { - ReconcileUtils::ExportIPTC ( xmp, iptc ); - iptcChanged = iptc->IsChanged(); // ! Do after calling ExportIPTC and before calling SetIPTCDigest. - if ( psir != 0 ) ReconcileUtils::SetIPTCDigest ( iptc, psir ); // ! Do always, in case the digest was missing before. - } + exif->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_IPTC ); // These remove any existing IPTC and PSIR. + exif->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_PSIR ); - if ( tiff != 0 ) { - ReconcileUtils::ExportTIFF ( *xmp, tiff ); - ReconcileUtils::ExportExif ( *xmp, tiff ); - ReconcileUtils::SetTIFFDigest ( *tiff, xmp ); // ! Do always, in case the digest was missing before. - ReconcileUtils::SetExifDigest ( *tiff, xmp ); // ! Do always, in case the digest was missing before. } + + // Export the individual metadata items to the non-XMP forms. Set the IPTC digest whether or not + // it changed, it might not have been present or correct before. + + bool iptcChanged = false; // Save explicitly, internal flag is reset by UpdateMemoryDataSets. + + void * iptcPtr = 0; + XMP_Uns32 iptcLen = 0; - // Now update the collections of metadata, e.g. the IPTC in PSIR 1028 or XMP in TIFF tag 700. - // - All of the formats have the IPTC in the PSIR portion. - // - JPEG has nothing else special. - // - PSD has the XMP and Exif in the PSIR portion. - // - TIFF has the XMP, IPTC, and PSIR in primary IFD tags. Yes, a 2nd copy of the IPTC. - - if ( (iptc != 0) && (psir != 0) && iptcChanged ) { - void* iptcPtr; - XMP_Uns32 iptcLen = iptc->UpdateMemoryDataSets ( &iptcPtr ); - psir->SetImgRsrc ( kPSIR_IPTC, iptcPtr, iptcLen ); + if ( iptc != 0 ) { + PhotoDataUtils::ExportIPTC ( *xmp, iptc ); + iptcChanged = iptc->IsChanged(); + if ( iptcChanged ) iptc->UpdateMemoryDataSets(); + iptcLen = iptc->GetBlockInfo ( &iptcPtr ); + if ( psir != 0 ) PhotoDataUtils::SetIPTCDigest ( iptcPtr, iptcLen, psir ); } - - if ( destFormat == kXMP_PhotoshopFile ) { - XMP_Assert ( psir != 0 ); + if ( exif != 0 ) PhotoDataUtils::ExportExif ( xmp, exif ); + if ( psir != 0 ) PhotoDataUtils::ExportPSIR ( *xmp, psir ); - if ( (tiff != 0) && tiff->IsChanged() ) { - void* exifPtr; - XMP_Uns32 exifLen = tiff->UpdateMemoryStream ( &exifPtr ); - psir->SetImgRsrc ( kPSIR_Exif, exifPtr, exifLen ); - } + // Now update the non-XMP collections of metadata according to the file format. Do not update + // the XMP here, that is done in the file handlers after deciding if an XMP-only in-place + // update should be done. + // - JPEG has the IPTC in PSIR 1028, the Exif and PSIR are marker segments. + // - TIFF has the IPTC and PSIR in primary IFD tags. + // - PSD has everything in PSIRs. + + if ( destFormat == kXMP_JPEGFile ) { + + if ( iptcChanged && (psir != 0) ) psir->SetImgRsrc ( kPSIR_IPTC, iptcPtr, iptcLen ); } else if ( destFormat == kXMP_TIFFFile ) { - - XMP_Assert ( tiff != 0 ); - if ( (iptc != 0) && iptcChanged ) { - void* iptcPtr; - XMP_Uns32 iptcLen = iptc->UpdateMemoryDataSets ( &iptcPtr ); - tiff->SetTag ( kTIFF_PrimaryIFD, kTIFF_IPTC, kTIFF_UndefinedType, iptcLen, iptcPtr ); - } + XMP_Assert ( exif != 0 ); + + if ( iptcChanged ) exif->SetTag ( kTIFF_PrimaryIFD, kTIFF_IPTC, kTIFF_UndefinedType, iptcLen, iptcPtr ); if ( (psir != 0) && psir->IsChanged() ) { void* psirPtr; XMP_Uns32 psirLen = psir->UpdateMemoryResources ( &psirPtr ); - tiff->SetTag ( kTIFF_PrimaryIFD, kTIFF_PSIR, kTIFF_UndefinedType, psirLen, psirPtr ); + exif->SetTag ( kTIFF_PrimaryIFD, kTIFF_PSIR, kTIFF_UndefinedType, psirLen, psirPtr ); + } + + } else if ( destFormat == kXMP_PhotoshopFile ) { + + XMP_Assert ( psir != 0 ); + + if ( iptcChanged ) psir->SetImgRsrc ( kPSIR_IPTC, iptcPtr, iptcLen ); + + if ( (exif != 0) && exif->IsChanged() ) { + void* exifPtr; + XMP_Uns32 exifLen = exif->UpdateMemoryStream ( &exifPtr ); + psir->SetImgRsrc ( kPSIR_Exif, exifPtr, exifLen ); } - + } + + // Strip the tiff: and exif: namespaces from the XMP, we're done with them. Save the Exif + // ISOSpeedRatings if any of the values are over 0xFFFF, the native tag is SHORT. Lower level + // code already kept or stripped the XMP form. + + SXMPMeta savedExif; + SaveExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_TIFF, 0, kXMPUtil_DoAllProperties ); + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_EXIF, 0, kXMPUtil_DoAllProperties ); + + RestoreExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); -} // ExportXMPtoJTP +} // ExportPhotoData diff --git a/source/XMPFiles/FormatSupport/ReconcileLegacy.hpp b/source/XMPFiles/FormatSupport/ReconcileLegacy.hpp index 2542309..59918cf 100644 --- a/source/XMPFiles/FormatSupport/ReconcileLegacy.hpp +++ b/source/XMPFiles/FormatSupport/ReconcileLegacy.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -18,31 +18,16 @@ // ================================================================================================= /// \file ReconcileLegacy.hpp -/// \brief Utilities to reconcile between XMP and legacy metadata forms such as TIFF/Exif and IPTC. +/// \brief Utilities to reconcile between XMP and photo metadata forms such as TIFF/Exif and IPTC. /// // ================================================================================================= -// ImportJTPtoXMP imports legacy metadata for JPEG, TIFF, and Photoshop files into XMP. The caller -// must have already done the file specific processing to select the appropriate sources of the TIFF -// stream, the Photoshop image resources, and the IPTC. +// ImportPhotoData imports TIFF/Exif and IPTC metadata from JPEG, TIFF, and Photoshop files into +// XMP. The caller must have already done the file specific processing to select the appropriate +// sources of the TIFF stream, the Photoshop image resources, and the IPTC. // -// The reconciliation logic used here is not identical to that used in Photoshop CS2, but should be -// similar enough. The details of both approaches are documented in LegacyReconcile.pdf. The logic -// used by Photoshop is more processor and memory intensive. That overhead is acceptable when -// opening a file in Photoshop. Client's like Bridge need a lighter weight approach for quick -// read-only access to the reconciled metadata. - -enum { // JTP "last-seen" legacy priorities from Photoshop. Higher numbers are more important. - kLegacyJTP_None = 0, // No legacy metadata. - kLegacyJTP_JPEG_TIFF_Tags = 1, // A JPEG file with TIFF tags 270, 315, or 33432. - kLegacyJTP_PSIR_IPTC = 2, // IPTC from Photoshop image resource 1028. - kLegacyJTP_PSIR_OldCaption = 3, // Old caption from Photoshop image resource 1008 or 1020. - kLegacyJTP_TIFF_TIFF_Tags = 4, // A TIFF file with TIFF tags 270, 315, or 33432. - kLegacyJTP_TIFF_IPTC = 5, // A TIFF file with TIFF tag 33723. - kLegacyJTP_Mac_pnot = 6, // KeyW and Desc items from Macintosh pnot 0 resource. - kLegacyJTP_ANPA_IPTC = 7 // IPTC from Macintosh ANPA 10000 resource. -}; -typedef XMP_Uns8 RecJTP_LegacyPriority; +// The reconciliation logic used here is based on the Metadata Working Group guidelines. This is a +// simpler approach than used previously - which was modeled after historical Photoshop behavior. enum { // Bits for the options to ImportJTPtoXMP. k2XMP_FileHadXMP = 0x0001, // Set if the file had an XMP packet. @@ -50,77 +35,66 @@ enum { // Bits for the options to ImportJTPtoXMP. k2XMP_FileHadExif = 0x0004 // Set if the file had legacy Exif. }; -extern void ImportJTPtoXMP ( XMP_FileFormat srcFormat, - RecJTP_LegacyPriority lastLegacy, - TIFF_Manager * tiff, // ! Need to modify for UserComment and RelatedSoundFile hack. - const PSIR_Manager & psir, - IPTC_Manager * iptc, // ! Need to modify for UpdateDataSets. - SXMPMeta * xmp, - XMP_OptionBits options = 0 ); +extern void ImportPhotoData ( const TIFF_Manager & exif, + const IPTC_Manager & iptc, + const PSIR_Manager & psir, + int iptcDigestState, + SXMPMeta * xmp, + XMP_OptionBits options = 0 ); -#if 0 // Activate if we want to support the Mac pnot resource. -extern void ImportJTPtoXMP ( XMP_FileFormat srcFormat, - RecJTP_LegacyPriority lastLegacy, - const TIFF_Manager & tiff, - const PSIR_Manager & psir, - IPTC_Manager * iptc, - const void * macKeyW, // The STR# for pnot 0 KeyW item. - const std::string & macDesc, // The TEXT for pnot 0 Desc item. - SXMPMeta * xmp, - XMP_OptionBits options = 0 ); -#endif +// ExportPhotoData exports XMP into TIFF/Exif and IPTC metadata for JPEG, TIFF, and Photoshop files. -// ExportXMPtoJTP exports XMP into legacy metadata for JPEG, TIFF, and Photoshop files. +extern void ExportPhotoData ( XMP_FileFormat destFormat, + SXMPMeta * xmp, + TIFF_Manager * exif, // Pass 0 if not wanted. + IPTC_Manager * iptc, // Pass 0 if not wanted. + PSIR_Manager * psir, // Pass 0 if not wanted. + XMP_OptionBits options = 0 ); -extern void ExportXMPtoJTP ( XMP_FileFormat destFormat, - SXMPMeta * xmp, - TIFF_Manager * tiff, // Pass 0 if not wanted. - PSIR_Manager * psir, // Pass 0 if not wanted. - IPTC_Manager * iptc, // Pass 0 if not wanted. - XMP_OptionBits options = 0 ); +// *** Mapping notes need revision for MWG related changes. // ================================================================================================= // Summary of TIFF/Exif mappings to XMP // ==================================== -// +// // The mapping for each tag is driven mainly by the tag ID, and secondarily by the type. E.g. there // is no blanket rule that all ASCII tags are mapped to simple strings in XMP. Some, such as // SubSecTime or GPSLatitudeRef, are combined with other tags; others, like Flash, are reformated. // However, most tags are in fact mapped in an obvious manner based on their type and count. -// +// // Photoshop practice has been to truncate ASCII tags at the first NUL, not supporting the TIFF // specification's notion of multi-part ASCII values. -// +// // Rational values are mapped to XMP as "num/denom". -// +// // The tags of UNDEFINED type that are mapped to XMP text are either special cases like ExifVersion // or the strings with an explicit encoding like UserComment. -// +// // Latitude and logitude are mapped to XMP as "DDD,MM,SSk" or "DDD,MM.mmk"; k is N, S, E, or W. -// +// // Flash struct in XMP separates the Fired, Return, Mode, Function, and RedEyeMode portions of the // Exif value. Fired, Function, and RedEyeMode are Boolean; Return and Mode are integers. -// +// // The OECF/SFR, CFA, and DeviceSettings tables are described in the XMP spec. -// +// // Instead of iterating through all tags in the various IFDs, it is probably more efficient to have // explicit processing for the tags that get special treatment, and a static table listing those // that get mapped by type and count. The type and count processing will verify that the actual // type and count are as expected, if not the tag is ignored. -// +// // Here are the primary (0th) IFD tags that get special treatment: -// +// // 270, 33432 - ASCII mapped to alt-text['x-default'] // 306 - DateTime master // 315 - ASCII mapped to text seq[1] -// +// // Here are the primary (0th) IFD tags that get mapped by type and count: -// +// // 256, 257, 258, 259, 262, 271, 272, 274, 277, 282, 283, 284, 296, 301, 305, 318, 319, // 529, 530, 531, 532 -// +// // Here are the Exif IFD tags that get special treatment: -// +// // 34856, 41484 - OECF/SFR table // 36864, 40960 - 4 ASCII chars to text // 36867, 36868 - DateTime master @@ -130,22 +104,22 @@ extern void ExportXMPtoJTP ( XMP_FileFormat destFormat, // 41728, 41729 - UInt8 to integer // 41730 - CFA table // 41995 - DeviceSettings table -// +// // Here are the Exif IFD tags that get mapped by type and count: -// +// // 33434, 33437, 34850, 34852, 34855, 37122, 37377, 37378, 37379, 37380, 37381, 37382, 37383, 37384, // 37386, 37396, 40961, 40962, 40963, 40964, 41483, 41486, 41487, 41488, 41492, 41493, 41495, 41985, // 41986, 41987, 41988, 41989, 41990, 41991, 41992, 41993, 41994, 41996, 42016 -// +// // Here are the GPS IFD tags that get special treatment: -// +// // 0 - 4 UInt8 to text "n.n.n.n" // 2, 4, 20, 22 - Latitude or longitude master // 7 - special DateTime master, the time part // 27, 28 - explicitly encoded text -// +// // Here are the GPS IFD tags that get mapped by type and count: -// +// // 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 23, 24, 25, 26, 30 // ================================================================================================= @@ -170,7 +144,7 @@ extern void ExportXMPtoJTP ( XMP_FileFormat destFormat, // // General (primary and thumbnail, 0th and 1st) IFD tags // tag TIFF type count Name XMP mapping -// +// // 256 SHORTorLONG 1 ImageWidth integer // 257 SHORTorLONG 1 ImageLength integer // 258 SHORT 3 BitsPerSample integer seq @@ -196,10 +170,10 @@ extern void ExportXMPtoJTP ( XMP_FileFormat destFormat, // 531 SHORT 1 YCbCrPositioning integer // 532 RATIONAL 6 ReferenceBlackWhite rational seq // 33432 ASCII Any Copyright text, dc:rights['x-default'] -// +// // Exif IFD tags // tag TIFF type count Name XMP mapping -// +// // 33434 RATIONAL 1 ExposureTime rational // 33437 RATIONAL 1 FNumber rational // 34850 SHORT 1 ExposureProgram integer @@ -255,10 +229,10 @@ extern void ExportXMPtoJTP ( XMP_FileFormat destFormat, // 41995 UNDEFINED Any DeviceSettingDescription DeviceSettings table // 41996 SHORT 1 SubjectDistanceRange integer // 42016 ASCII 33 ImageUniqueID text -// +// // GPS IFD tags // tag TIFF type count Name XMP mapping -// +// // 0 BYTE 4 GPSVersionID text, "n.n.n.n", Exif has 4 UInt8 // 1 ASCII 2 GPSLatitudeRef latitude, with 2 // 2 RATIONAL 3 GPSLatitude latitude, master of 2 diff --git a/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp b/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp index 4ae7564..892c683 100644 --- a/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp +++ b/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -19,6 +19,8 @@ #endif #if XMP_WinBuild + #pragma warning ( disable : 4146 ) // unary minus operator applied to unsigned type + #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' #pragma warning ( disable : 4996 ) // '...' was declared deprecated #endif @@ -44,132 +46,148 @@ // ! The sentinel tag value can't be 0, that is a valid GPS Info tag, 0xFFFF is unused so far. +enum { + kExport_Never = 0, // Never export. + kExport_Always = 1, // Add, modify, or delete. + kExport_NoDelete = 2, // Add or modify, do not delete if no XMP. + kExport_InjectOnly = 3 // Add tag if new, never modify or delete existing values. +}; + struct TIFF_MappingToXMP { XMP_Uns16 id; XMP_Uns16 type; XMP_Uns32 count; // Zero means any. + XMP_Uns8 exportMode; const char * name; // The name of the mapped XMP property. The namespace is implicit. }; enum { kAnyCount = 0 }; -static const TIFF_MappingToXMP sPrimaryIFDMappings[] = { - { /* 256 */ kTIFF_ImageWidth, kTIFF_ShortOrLongType, 1, "ImageWidth" }, - { /* 257 */ kTIFF_ImageLength, kTIFF_ShortOrLongType, 1, "ImageLength" }, - { /* 258 */ kTIFF_BitsPerSample, kTIFF_ShortType, 3, "BitsPerSample" }, - { /* 259 */ kTIFF_Compression, kTIFF_ShortType, 1, "Compression" }, - { /* 262 */ kTIFF_PhotometricInterpretation, kTIFF_ShortType, 1, "PhotometricInterpretation" }, - { /* 274 */ kTIFF_Orientation, kTIFF_ShortType, 1, "Orientation" }, - { /* 277 */ kTIFF_SamplesPerPixel, kTIFF_ShortType, 1, "SamplesPerPixel" }, - { /* 284 */ kTIFF_PlanarConfiguration, kTIFF_ShortType, 1, "PlanarConfiguration" }, - { /* 530 */ kTIFF_YCbCrSubSampling, kTIFF_ShortType, 2, "YCbCrSubSampling" }, - { /* 531 */ kTIFF_YCbCrPositioning, kTIFF_ShortType, 1, "YCbCrPositioning" }, - { /* 282 */ kTIFF_XResolution, kTIFF_RationalType, 1, "XResolution" }, - { /* 283 */ kTIFF_YResolution, kTIFF_RationalType, 1, "YResolution" }, - { /* 296 */ kTIFF_ResolutionUnit, kTIFF_ShortType, 1, "ResolutionUnit" }, - { /* 301 */ kTIFF_TransferFunction, kTIFF_ShortType, 3*256, "TransferFunction" }, - { /* 318 */ kTIFF_WhitePoint, kTIFF_RationalType, 2, "WhitePoint" }, - { /* 319 */ kTIFF_PrimaryChromaticities, kTIFF_RationalType, 6, "PrimaryChromaticities" }, - { /* 529 */ kTIFF_YCbCrCoefficients, kTIFF_RationalType, 3, "YCbCrCoefficients" }, - { /* 532 */ kTIFF_ReferenceBlackWhite, kTIFF_RationalType, 6, "ReferenceBlackWhite" }, - { /* 306 */ kTIFF_DateTime, kTIFF_ASCIIType, 20, "" }, // ! Has a special mapping. - { /* 270 */ kTIFF_ImageDescription, kTIFF_ASCIIType, kAnyCount, "" }, // ! Has a special mapping. - { /* 271 */ kTIFF_Make, kTIFF_ASCIIType, kAnyCount, "Make" }, - { /* 272 */ kTIFF_Model, kTIFF_ASCIIType, kAnyCount, "Model" }, - { /* 305 */ kTIFF_Software, kTIFF_ASCIIType, kAnyCount, "Software" }, // Has alias to xmp:CreatorTool. - { /* 315 */ kTIFF_Artist, kTIFF_ASCIIType, kAnyCount, "" }, // ! Has a special mapping. - { /* 33432 */ kTIFF_Copyright, kTIFF_ASCIIType, kAnyCount, "" }, +static const TIFF_MappingToXMP sPrimaryIFDMappings[] = { // A blank name indicates a special mapping. + { /* 256 */ kTIFF_ImageWidth, kTIFF_ShortOrLongType, 1, kExport_Never, "ImageWidth" }, + { /* 257 */ kTIFF_ImageLength, kTIFF_ShortOrLongType, 1, kExport_Never, "ImageLength" }, + { /* 258 */ kTIFF_BitsPerSample, kTIFF_ShortType, 3, kExport_Never, "BitsPerSample" }, + { /* 259 */ kTIFF_Compression, kTIFF_ShortType, 1, kExport_Never, "Compression" }, + { /* 262 */ kTIFF_PhotometricInterpretation, kTIFF_ShortType, 1, kExport_Never, "PhotometricInterpretation" }, + { /* 274 */ kTIFF_Orientation, kTIFF_ShortType, 1, kExport_NoDelete, "Orientation" }, + { /* 277 */ kTIFF_SamplesPerPixel, kTIFF_ShortType, 1, kExport_Never, "SamplesPerPixel" }, + { /* 284 */ kTIFF_PlanarConfiguration, kTIFF_ShortType, 1, kExport_Never, "PlanarConfiguration" }, + { /* 530 */ kTIFF_YCbCrSubSampling, kTIFF_ShortType, 2, kExport_Never, "YCbCrSubSampling" }, + { /* 531 */ kTIFF_YCbCrPositioning, kTIFF_ShortType, 1, kExport_Never, "YCbCrPositioning" }, + { /* 282 */ kTIFF_XResolution, kTIFF_RationalType, 1, kExport_NoDelete, "XResolution" }, + { /* 283 */ kTIFF_YResolution, kTIFF_RationalType, 1, kExport_NoDelete, "YResolution" }, + { /* 296 */ kTIFF_ResolutionUnit, kTIFF_ShortType, 1, kExport_NoDelete, "ResolutionUnit" }, + { /* 301 */ kTIFF_TransferFunction, kTIFF_ShortType, 3*256, kExport_Never, "TransferFunction" }, + { /* 318 */ kTIFF_WhitePoint, kTIFF_RationalType, 2, kExport_Never, "WhitePoint" }, + { /* 319 */ kTIFF_PrimaryChromaticities, kTIFF_RationalType, 6, kExport_Never, "PrimaryChromaticities" }, + { /* 529 */ kTIFF_YCbCrCoefficients, kTIFF_RationalType, 3, kExport_Never, "YCbCrCoefficients" }, + { /* 532 */ kTIFF_ReferenceBlackWhite, kTIFF_RationalType, 6, kExport_Never, "ReferenceBlackWhite" }, + { /* 306 */ kTIFF_DateTime, kTIFF_ASCIIType, 20, kExport_Always, "" }, // ! Has a special mapping. + { /* 270 */ kTIFF_ImageDescription, kTIFF_ASCIIType, kAnyCount, kExport_Always, "" }, // ! Has a special mapping. + { /* 271 */ kTIFF_Make, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, "Make" }, + { /* 272 */ kTIFF_Model, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, "Model" }, + { /* 305 */ kTIFF_Software, kTIFF_ASCIIType, kAnyCount, kExport_Always, "Software" }, // Has alias to xmp:CreatorTool. + { /* 315 */ kTIFF_Artist, kTIFF_ASCIIType, kAnyCount, kExport_Always, "" }, // ! Has a special mapping. + { /* 33432 */ kTIFF_Copyright, kTIFF_ASCIIType, kAnyCount, kExport_Always, "" }, // ! Has a special mapping. { 0xFFFF, 0, 0, 0 } // ! Must end with sentinel. }; +// ! A special need, easier than looking up the entry in sExifIFDMappings: +static const TIFF_MappingToXMP kISOSpeedMapping = { kTIFF_ISOSpeedRatings, kTIFF_ShortType, kAnyCount, kExport_InjectOnly, "ISOSpeedRatings" }; + static const TIFF_MappingToXMP sExifIFDMappings[] = { - { /* 36864 */ kTIFF_ExifVersion, kTIFF_UndefinedType, 4, "" }, // ! Has a special mapping. - { /* 40960 */ kTIFF_FlashpixVersion, kTIFF_UndefinedType, 4, "" }, // ! Has a special mapping. - { /* 40961 */ kTIFF_ColorSpace, kTIFF_ShortType, 1, "ColorSpace" }, - { /* 37121 */ kTIFF_ComponentsConfiguration, kTIFF_UndefinedType, 4, "" }, // ! Has a special mapping. - { /* 37122 */ kTIFF_CompressedBitsPerPixel, kTIFF_RationalType, 1, "CompressedBitsPerPixel" }, - { /* 40962 */ kTIFF_PixelXDimension, kTIFF_ShortOrLongType, 1, "PixelXDimension" }, - { /* 40963 */ kTIFF_PixelYDimension, kTIFF_ShortOrLongType, 1, "PixelYDimension" }, - { /* 37510 */ kTIFF_UserComment, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 40964 */ kTIFF_RelatedSoundFile, kTIFF_ASCIIType, 13, "RelatedSoundFile" }, - { /* 36867 */ kTIFF_DateTimeOriginal, kTIFF_ASCIIType, 20, "" }, // ! Has a special mapping. - { /* 36868 */ kTIFF_DateTimeDigitized, kTIFF_ASCIIType, 20, "" }, // ! Has a special mapping. - { /* 33434 */ kTIFF_ExposureTime, kTIFF_RationalType, 1, "ExposureTime" }, - { /* 33437 */ kTIFF_FNumber, kTIFF_RationalType, 1, "FNumber" }, - { /* 34850 */ kTIFF_ExposureProgram, kTIFF_ShortType, 1, "ExposureProgram" }, - { /* 34852 */ kTIFF_SpectralSensitivity, kTIFF_ASCIIType, kAnyCount, "SpectralSensitivity" }, - { /* 34855 */ kTIFF_ISOSpeedRatings, kTIFF_ShortType, kAnyCount, "ISOSpeedRatings" }, - { /* 34856 */ kTIFF_OECF, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 37377 */ kTIFF_ShutterSpeedValue, kTIFF_SRationalType, 1, "ShutterSpeedValue" }, - { /* 37378 */ kTIFF_ApertureValue, kTIFF_RationalType, 1, "ApertureValue" }, - { /* 37379 */ kTIFF_BrightnessValue, kTIFF_SRationalType, 1, "BrightnessValue" }, - { /* 37380 */ kTIFF_ExposureBiasValue, kTIFF_SRationalType, 1, "ExposureBiasValue" }, - { /* 37381 */ kTIFF_MaxApertureValue, kTIFF_RationalType, 1, "MaxApertureValue" }, - { /* 37382 */ kTIFF_SubjectDistance, kTIFF_RationalType, 1, "SubjectDistance" }, - { /* 37383 */ kTIFF_MeteringMode, kTIFF_ShortType, 1, "MeteringMode" }, - { /* 37384 */ kTIFF_LightSource, kTIFF_ShortType, 1, "LightSource" }, - { /* 37385 */ kTIFF_Flash, kTIFF_ShortType, 1, "" }, // ! Has a special mapping. - { /* 37386 */ kTIFF_FocalLength, kTIFF_RationalType, 1, "FocalLength" }, - { /* 37396 */ kTIFF_SubjectArea, kTIFF_ShortType, kAnyCount, "SubjectArea" }, // ! Actually 2..4. - { /* 41483 */ kTIFF_FlashEnergy, kTIFF_RationalType, 1, "FlashEnergy" }, - { /* 41484 */ kTIFF_SpatialFrequencyResponse, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 41486 */ kTIFF_FocalPlaneXResolution, kTIFF_RationalType, 1, "FocalPlaneXResolution" }, - { /* 41487 */ kTIFF_FocalPlaneYResolution, kTIFF_RationalType, 1, "FocalPlaneYResolution" }, - { /* 41488 */ kTIFF_FocalPlaneResolutionUnit, kTIFF_ShortType, 1, "FocalPlaneResolutionUnit" }, - { /* 41492 */ kTIFF_SubjectLocation, kTIFF_ShortType, 2, "SubjectLocation" }, - { /* 41493 */ kTIFF_ExposureIndex, kTIFF_RationalType, 1, "ExposureIndex" }, - { /* 41495 */ kTIFF_SensingMethod, kTIFF_ShortType, 1, "SensingMethod" }, - { /* 41728 */ kTIFF_FileSource, kTIFF_UndefinedType, 1, "" }, // ! Has a special mapping. - { /* 41729 */ kTIFF_SceneType, kTIFF_UndefinedType, 1, "" }, // ! Has a special mapping. - { /* 41730 */ kTIFF_CFAPattern, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 41985 */ kTIFF_CustomRendered, kTIFF_ShortType, 1, "CustomRendered" }, - { /* 41986 */ kTIFF_ExposureMode, kTIFF_ShortType, 1, "ExposureMode" }, - { /* 41987 */ kTIFF_WhiteBalance, kTIFF_ShortType, 1, "WhiteBalance" }, - { /* 41988 */ kTIFF_DigitalZoomRatio, kTIFF_RationalType, 1, "DigitalZoomRatio" }, - { /* 41989 */ kTIFF_FocalLengthIn35mmFilm, kTIFF_ShortType, 1, "FocalLengthIn35mmFilm" }, - { /* 41990 */ kTIFF_SceneCaptureType, kTIFF_ShortType, 1, "SceneCaptureType" }, - { /* 41991 */ kTIFF_GainControl, kTIFF_ShortType, 1, "GainControl" }, - { /* 41992 */ kTIFF_Contrast, kTIFF_ShortType, 1, "Contrast" }, - { /* 41993 */ kTIFF_Saturation, kTIFF_ShortType, 1, "Saturation" }, - { /* 41994 */ kTIFF_Sharpness, kTIFF_ShortType, 1, "Sharpness" }, - { /* 41995 */ kTIFF_DeviceSettingDescription, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 41996 */ kTIFF_SubjectDistanceRange, kTIFF_ShortType, 1, "SubjectDistanceRange" }, - { /* 42016 */ kTIFF_ImageUniqueID, kTIFF_ASCIIType, 33, "ImageUniqueID" }, + { /* 36864 */ kTIFF_ExifVersion, kTIFF_UndefinedType, 4, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 40960 */ kTIFF_FlashpixVersion, kTIFF_UndefinedType, 4, kExport_Never, "" }, // ! Has a special mapping. + { /* 40961 */ kTIFF_ColorSpace, kTIFF_ShortType, 1, kExport_InjectOnly, "ColorSpace" }, + { /* 37121 */ kTIFF_ComponentsConfiguration, kTIFF_UndefinedType, 4, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 37122 */ kTIFF_CompressedBitsPerPixel, kTIFF_RationalType, 1, kExport_InjectOnly, "CompressedBitsPerPixel" }, + { /* 40962 */ kTIFF_PixelXDimension, kTIFF_ShortOrLongType, 1, kExport_InjectOnly, "PixelXDimension" }, + { /* 40963 */ kTIFF_PixelYDimension, kTIFF_ShortOrLongType, 1, kExport_InjectOnly, "PixelYDimension" }, + { /* 37510 */ kTIFF_UserComment, kTIFF_UndefinedType, kAnyCount, kExport_Always, "" }, // ! Has a special mapping. + { /* 40964 */ kTIFF_RelatedSoundFile, kTIFF_ASCIIType, kAnyCount, kExport_Always, "RelatedSoundFile" }, // ! Exif spec says count of 13. + { /* 36867 */ kTIFF_DateTimeOriginal, kTIFF_ASCIIType, 20, kExport_Always, "" }, // ! Has a special mapping. + { /* 36868 */ kTIFF_DateTimeDigitized, kTIFF_ASCIIType, 20, kExport_Always, "" }, // ! Has a special mapping. + { /* 33434 */ kTIFF_ExposureTime, kTIFF_RationalType, 1, kExport_InjectOnly, "ExposureTime" }, + { /* 33437 */ kTIFF_FNumber, kTIFF_RationalType, 1, kExport_InjectOnly, "FNumber" }, + { /* 34850 */ kTIFF_ExposureProgram, kTIFF_ShortType, 1, kExport_InjectOnly, "ExposureProgram" }, + { /* 34852 */ kTIFF_SpectralSensitivity, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, "SpectralSensitivity" }, + { /* 34855 */ kTIFF_ISOSpeedRatings, kTIFF_ShortType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 34856 */ kTIFF_OECF, kTIFF_UndefinedType, kAnyCount, kExport_Never, "" }, // ! Has a special mapping. + { /* 37377 */ kTIFF_ShutterSpeedValue, kTIFF_SRationalType, 1, kExport_InjectOnly, "ShutterSpeedValue" }, + { /* 37378 */ kTIFF_ApertureValue, kTIFF_RationalType, 1, kExport_InjectOnly, "ApertureValue" }, + { /* 37379 */ kTIFF_BrightnessValue, kTIFF_SRationalType, 1, kExport_InjectOnly, "BrightnessValue" }, + { /* 37380 */ kTIFF_ExposureBiasValue, kTIFF_SRationalType, 1, kExport_InjectOnly, "ExposureBiasValue" }, + { /* 37381 */ kTIFF_MaxApertureValue, kTIFF_RationalType, 1, kExport_InjectOnly, "MaxApertureValue" }, + { /* 37382 */ kTIFF_SubjectDistance, kTIFF_RationalType, 1, kExport_InjectOnly, "SubjectDistance" }, + { /* 37383 */ kTIFF_MeteringMode, kTIFF_ShortType, 1, kExport_InjectOnly, "MeteringMode" }, + { /* 37384 */ kTIFF_LightSource, kTIFF_ShortType, 1, kExport_InjectOnly, "LightSource" }, + { /* 37385 */ kTIFF_Flash, kTIFF_ShortType, 1, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 37386 */ kTIFF_FocalLength, kTIFF_RationalType, 1, kExport_InjectOnly, "FocalLength" }, + { /* 37396 */ kTIFF_SubjectArea, kTIFF_ShortType, kAnyCount, kExport_Never, "SubjectArea" }, // ! Actually 2..4. + { /* 41483 */ kTIFF_FlashEnergy, kTIFF_RationalType, 1, kExport_InjectOnly, "FlashEnergy" }, + { /* 41484 */ kTIFF_SpatialFrequencyResponse, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 41486 */ kTIFF_FocalPlaneXResolution, kTIFF_RationalType, 1, kExport_InjectOnly, "FocalPlaneXResolution" }, + { /* 41487 */ kTIFF_FocalPlaneYResolution, kTIFF_RationalType, 1, kExport_InjectOnly, "FocalPlaneYResolution" }, + { /* 41488 */ kTIFF_FocalPlaneResolutionUnit, kTIFF_ShortType, 1, kExport_InjectOnly, "FocalPlaneResolutionUnit" }, + { /* 41492 */ kTIFF_SubjectLocation, kTIFF_ShortType, 2, kExport_Never, "SubjectLocation" }, + { /* 41493 */ kTIFF_ExposureIndex, kTIFF_RationalType, 1, kExport_InjectOnly, "ExposureIndex" }, + { /* 41495 */ kTIFF_SensingMethod, kTIFF_ShortType, 1, kExport_InjectOnly, "SensingMethod" }, + { /* 41728 */ kTIFF_FileSource, kTIFF_UndefinedType, 1, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 41729 */ kTIFF_SceneType, kTIFF_UndefinedType, 1, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 41730 */ kTIFF_CFAPattern, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 41985 */ kTIFF_CustomRendered, kTIFF_ShortType, 1, kExport_Never, "CustomRendered" }, + { /* 41986 */ kTIFF_ExposureMode, kTIFF_ShortType, 1, kExport_InjectOnly, "ExposureMode" }, + { /* 41987 */ kTIFF_WhiteBalance, kTIFF_ShortType, 1, kExport_InjectOnly, "WhiteBalance" }, + { /* 41988 */ kTIFF_DigitalZoomRatio, kTIFF_RationalType, 1, kExport_InjectOnly, "DigitalZoomRatio" }, + { /* 41989 */ kTIFF_FocalLengthIn35mmFilm, kTIFF_ShortType, 1, kExport_InjectOnly, "FocalLengthIn35mmFilm" }, + { /* 41990 */ kTIFF_SceneCaptureType, kTIFF_ShortType, 1, kExport_InjectOnly, "SceneCaptureType" }, + { /* 41991 */ kTIFF_GainControl, kTIFF_ShortType, 1, kExport_InjectOnly, "GainControl" }, + { /* 41992 */ kTIFF_Contrast, kTIFF_ShortType, 1, kExport_InjectOnly, "Contrast" }, + { /* 41993 */ kTIFF_Saturation, kTIFF_ShortType, 1, kExport_InjectOnly, "Saturation" }, + { /* 41994 */ kTIFF_Sharpness, kTIFF_ShortType, 1, kExport_InjectOnly, "Sharpness" }, + { /* 41995 */ kTIFF_DeviceSettingDescription, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 41996 */ kTIFF_SubjectDistanceRange, kTIFF_ShortType, 1, kExport_InjectOnly, "SubjectDistanceRange" }, + { /* 42016 */ kTIFF_ImageUniqueID, kTIFF_ASCIIType, 33, kExport_InjectOnly, "ImageUniqueID" }, { 0xFFFF, 0, 0, 0 } // ! Must end with sentinel. }; static const TIFF_MappingToXMP sGPSInfoIFDMappings[] = { - { /* 0 */ kTIFF_GPSVersionID, kTIFF_ByteType, 4, "" }, // ! Has a special mapping. - { /* 2 */ kTIFF_GPSLatitude, kTIFF_RationalType, 3, "" }, // ! Has a special mapping. - { /* 4 */ kTIFF_GPSLongitude, kTIFF_RationalType, 3, "" }, // ! Has a special mapping. - { /* 5 */ kTIFF_GPSAltitudeRef, kTIFF_ByteType, 1, "GPSAltitudeRef" }, - { /* 6 */ kTIFF_GPSAltitude, kTIFF_RationalType, 1, "GPSAltitude" }, - { /* 7 */ kTIFF_GPSTimeStamp, kTIFF_RationalType, 3, "" }, // ! Has a special mapping. - { /* 8 */ kTIFF_GPSSatellites, kTIFF_ASCIIType, kAnyCount, "GPSSatellites" }, - { /* 9 */ kTIFF_GPSStatus, kTIFF_ASCIIType, 2, "GPSStatus" }, - { /* 10 */ kTIFF_GPSMeasureMode, kTIFF_ASCIIType, 2, "GPSMeasureMode" }, - { /* 11 */ kTIFF_GPSDOP, kTIFF_RationalType, 1, "GPSDOP" }, - { /* 12 */ kTIFF_GPSSpeedRef, kTIFF_ASCIIType, 2, "GPSSpeedRef" }, - { /* 13 */ kTIFF_GPSSpeed, kTIFF_RationalType, 1, "GPSSpeed" }, - { /* 14 */ kTIFF_GPSTrackRef, kTIFF_ASCIIType, 2, "GPSTrackRef" }, - { /* 15 */ kTIFF_GPSTrack, kTIFF_RationalType, 1, "GPSTrack" }, - { /* 16 */ kTIFF_GPSImgDirectionRef, kTIFF_ASCIIType, 2, "GPSImgDirectionRef" }, - { /* 17 */ kTIFF_GPSImgDirection, kTIFF_RationalType, 1, "GPSImgDirection" }, - { /* 18 */ kTIFF_GPSMapDatum, kTIFF_ASCIIType, kAnyCount, "GPSMapDatum" }, - { /* 20 */ kTIFF_GPSDestLatitude, kTIFF_RationalType, 3, "" }, // ! Has a special mapping. - { /* 22 */ kTIFF_GPSDestLongitude, kTIFF_RationalType, 3, "" }, // ! Has a special mapping. - { /* 23 */ kTIFF_GPSDestBearingRef, kTIFF_ASCIIType, 2, "GPSDestBearingRef" }, - { /* 24 */ kTIFF_GPSDestBearing, kTIFF_RationalType, 1, "GPSDestBearing" }, - { /* 25 */ kTIFF_GPSDestDistanceRef, kTIFF_ASCIIType, 2, "GPSDestDistanceRef" }, - { /* 26 */ kTIFF_GPSDestDistance, kTIFF_RationalType, 1, "GPSDestDistance" }, - { /* 27 */ kTIFF_GPSProcessingMethod, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 28 */ kTIFF_GPSAreaInformation, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 30 */ kTIFF_GPSDifferential, kTIFF_ShortType, 1, "GPSDifferential" }, + { /* 0 */ kTIFF_GPSVersionID, kTIFF_ByteType, 4, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 2 */ kTIFF_GPSLatitude, kTIFF_RationalType, 3, kExport_Always, "" }, // ! Has a special mapping. + { /* 4 */ kTIFF_GPSLongitude, kTIFF_RationalType, 3, kExport_Always, "" }, // ! Has a special mapping. + { /* 5 */ kTIFF_GPSAltitudeRef, kTIFF_ByteType, 1, kExport_Always, "GPSAltitudeRef" }, + { /* 6 */ kTIFF_GPSAltitude, kTIFF_RationalType, 1, kExport_Always, "GPSAltitude" }, + { /* 7 */ kTIFF_GPSTimeStamp, kTIFF_RationalType, 3, kExport_Always, "" }, // ! Has a special mapping. + { /* 8 */ kTIFF_GPSSatellites, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, "GPSSatellites" }, + { /* 9 */ kTIFF_GPSStatus, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSStatus" }, + { /* 10 */ kTIFF_GPSMeasureMode, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSMeasureMode" }, + { /* 11 */ kTIFF_GPSDOP, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSDOP" }, + { /* 12 */ kTIFF_GPSSpeedRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSSpeedRef" }, + { /* 13 */ kTIFF_GPSSpeed, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSSpeed" }, + { /* 14 */ kTIFF_GPSTrackRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSTrackRef" }, + { /* 15 */ kTIFF_GPSTrack, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSTrack" }, + { /* 16 */ kTIFF_GPSImgDirectionRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSImgDirectionRef" }, + { /* 17 */ kTIFF_GPSImgDirection, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSImgDirection" }, + { /* 18 */ kTIFF_GPSMapDatum, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, "GPSMapDatum" }, + { /* 20 */ kTIFF_GPSDestLatitude, kTIFF_RationalType, 3, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 22 */ kTIFF_GPSDestLongitude, kTIFF_RationalType, 3, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 23 */ kTIFF_GPSDestBearingRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSDestBearingRef" }, + { /* 24 */ kTIFF_GPSDestBearing, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSDestBearing" }, + { /* 25 */ kTIFF_GPSDestDistanceRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSDestDistanceRef" }, + { /* 26 */ kTIFF_GPSDestDistance, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSDestDistance" }, + { /* 27 */ kTIFF_GPSProcessingMethod, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 28 */ kTIFF_GPSAreaInformation, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 30 */ kTIFF_GPSDifferential, kTIFF_ShortType, 1, kExport_InjectOnly, "GPSDifferential" }, { 0xFFFF, 0, 0, 0 } // ! Must end with sentinel. }; // ================================================================================================= +static void // ! Needed by Import2WayExif +ExportTIFF_Date ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, TIFF_Manager * tiff, XMP_Uns16 mainID ); + +// ================================================================================================= + static XMP_Uns32 GatherInt ( const char * strPtr, size_t count ) { XMP_Uns32 value = 0; @@ -184,178 +202,142 @@ static XMP_Uns32 GatherInt ( const char * strPtr, size_t count ) return value; -} +} // GatherInt // ================================================================================================= -// ================================================================================================= - -// ================================================================================================= -// ComputeTIFFDigest -// ================= -// -// Compute a 128 bit (16 byte) MD5 digest of the mapped TIFF tags and format it as a string like: -// 256,257,...;A0FCE844924381619820B6F7117C8B83 -// The first portion is a decimal list of the tags from sPrimaryIFDMappings, the last part is the -// MD5 digest as 32 hex digits using capital A-F. - -// ! The order of listing for the tags is crucial for the change comparisons to work! -static void -ComputeTIFFDigest ( const TIFF_Manager & tiff, std::string * digestStr ) +static void TrimTrailingSpaces ( TIFF_Manager::TagInfo * info ) { - MD5_CTX context; - MD5_Digest digest; - char buffer[40]; - size_t in, out; + if ( info->dataLen == 0 ) return; + XMP_Assert ( info->dataPtr != 0 ); - TIFF_Manager::TagInfo tagInfo; - - MD5Init ( &context ); - digestStr->clear(); - digestStr->reserve ( 160 ); // The current length is 134. - - for ( size_t i = 0; sPrimaryIFDMappings[i].id != 0xFFFF; ++i ) { - snprintf ( buffer, sizeof(buffer), "%d,", sPrimaryIFDMappings[i].id ); // AUDIT: Use of sizeof(buffer) is safe. - digestStr->append ( buffer ); - bool found = tiff.GetTag ( kTIFF_PrimaryIFD, sPrimaryIFDMappings[i].id, &tagInfo ); - if ( found ) MD5Update ( &context, (XMP_Uns8*)tagInfo.dataPtr, tagInfo.dataLen ); - } - - size_t endPos = digestStr->size() - 1; - (*digestStr)[endPos] = ';'; - - MD5Final ( digest, &context ); - - for ( in = 0, out = 0; in < 16; in += 1, out += 2 ) { - XMP_Uns8 byte = digest[in]; - buffer[out] = ReconcileUtils::kHexDigits [ byte >> 4 ]; - buffer[out+1] = ReconcileUtils::kHexDigits [ byte & 0xF ]; - } - buffer[32] = 0; + char * firstChar = (char*)info->dataPtr; + char * lastChar = firstChar + info->dataLen - 1; + + if ( (*lastChar != ' ') && (*lastChar != 0) ) return; // Nothing to do. + + while ( (firstChar <= lastChar) && ((*lastChar == ' ') || (*lastChar == 0)) ) --lastChar; + + XMP_Assert ( (lastChar == firstChar-1) || + ((lastChar >= firstChar) && (*lastChar != ' ') && (*lastChar != 0)) ); + + ++lastChar; + XMP_Uns32 newLen = (XMP_Uns32)(lastChar - firstChar) + 1; + XMP_Assert ( newLen <= info->dataLen ); - digestStr->append ( buffer ); + *lastChar = 0; + info->dataLen = newLen; -} // ComputeTIFFDigest; +} // TrimTrailingSpaces // ================================================================================================= -// ComputeExifDigest -// ================= -// -// Compute a 128 bit (16 byte) MD5 digest of the mapped Exif andf GPS tags and format it as a string like: -// 36864,40960,...;A0FCE844924381619820B6F7117C8B83 -// The first portion is a decimal list of the tags, the last part is the MD5 digest as 32 hex -// digits using capital A-F. The listed tags are those from sExifIFDMappings followed by those from -// sGPSInfoIFDMappings. - -// ! The order of listing for the tags is crucial for the change comparisons to work! -static void -ComputeExifDigest ( const TIFF_Manager & exif, std::string * digestStr ) +bool PhotoDataUtils::GetNativeInfo ( const TIFF_Manager & exif, XMP_Uns8 ifd, XMP_Uns16 id, TIFF_Manager::TagInfo * info ) { - MD5_CTX context; - MD5_Digest digest; - char buffer[40]; - size_t in, out; + bool haveExif = exif.GetTag ( ifd, id, info ); - TIFF_Manager::TagInfo tagInfo; - - MD5Init ( &context ); - digestStr->clear(); - digestStr->reserve ( 440 ); // The current length is 414. + if ( haveExif ) { - for ( size_t i = 0; sExifIFDMappings[i].id != 0xFFFF; ++i ) { - snprintf ( buffer, sizeof(buffer), "%d,", sExifIFDMappings[i].id ); // AUDIT: Use of sizeof(buffer) is safe. - digestStr->append ( buffer ); - bool found = exif.GetTag ( kTIFF_ExifIFD, sExifIFDMappings[i].id, &tagInfo ); - if ( found ) MD5Update ( &context, (XMP_Uns8*)tagInfo.dataPtr, tagInfo.dataLen ); - } + XMP_Uns32 i; + char * chPtr; + + XMP_Assert ( (info->dataPtr != 0) || (info->dataLen == 0) ); // Null pointer requires zero length. - for ( size_t i = 0; sGPSInfoIFDMappings[i].id != 0xFFFF; ++i ) { - snprintf ( buffer, sizeof(buffer), "%d,", sGPSInfoIFDMappings[i].id ); // AUDIT: Use of sizeof(buffer) is safe. - digestStr->append ( buffer ); - bool found = exif.GetTag ( kTIFF_GPSInfoIFD, sGPSInfoIFDMappings[i].id, &tagInfo ); - if ( found ) MD5Update ( &context, (XMP_Uns8*)tagInfo.dataPtr, tagInfo.dataLen ); - } + bool isDate = ((id == kTIFF_DateTime) || (id == kTIFF_DateTimeOriginal) || (id == kTIFF_DateTimeOriginal)); - size_t endPos = digestStr->size() - 1; - (*digestStr)[endPos] = ';'; + for ( i = 0, chPtr = (char*)info->dataPtr; i < info->dataLen; ++i, ++chPtr ) { + if ( isDate && (*chPtr == ':') ) continue; // Ignore colons, empty dates have spaces and colons. + if ( (*chPtr != ' ') && (*chPtr != 0) ) break; // Break if the Exif value is non-empty. + } - MD5Final ( digest, &context ); + if ( i == info->dataLen ) { + haveExif = false; // Ignore empty Exif. + } else { + TrimTrailingSpaces ( info ); + if ( info->dataLen == 0 ) haveExif = false; + } - for ( in = 0, out = 0; in < 16; in += 1, out += 2 ) { - XMP_Uns8 byte = digest[in]; - buffer[out] = ReconcileUtils::kHexDigits [ byte >> 4 ]; - buffer[out+1] = ReconcileUtils::kHexDigits [ byte & 0xF ]; } - buffer[32] = 0; - digestStr->append ( buffer ); + return haveExif; -} // ComputeExifDigest; +} // PhotoDataUtils::GetNativeInfo // ================================================================================================= -// ReconcileUtils::CheckTIFFDigest -// =============================== -int -ReconcileUtils::CheckTIFFDigest ( const TIFF_Manager & tiff, const SXMPMeta & xmp ) +size_t PhotoDataUtils::GetNativeInfo ( const IPTC_Manager & iptc, XMP_Uns8 id, int digestState, bool haveXMP, IPTC_Manager::DataSetInfo * info ) { - std::string newDigest, oldDigest; + size_t iptcCount = 0; - ComputeTIFFDigest ( tiff, &newDigest ); - bool found = xmp.GetProperty ( kXMP_NS_TIFF, "NativeDigest", &oldDigest, 0 ); + if ( (digestState == kDigestDiffers) || ((digestState == kDigestMissing) && (! haveXMP)) ) { + iptcCount = iptc.GetDataSet ( id, info ); + } + + if ( ignoreLocalText && (iptcCount > 0) && (! iptc.UsingUTF8()) ) { + // Check to see if the new value(s) should be ignored. + size_t i; + IPTC_Manager::DataSetInfo tmpInfo; + for ( i = 0; i < iptcCount; ++i ) { + (void) iptc.GetDataSet ( id, &tmpInfo, i ); + if ( ReconcileUtils::IsASCII ( tmpInfo.dataPtr, tmpInfo.dataLen ) ) break; + } + if ( i == iptcCount ) iptcCount = 0; // Return 0 if value(s) should be ignored. + } - if ( ! found ) return kDigestMissing; - if ( newDigest == oldDigest ) return kDigestMatches; - return kDigestDiffers; + return iptcCount; -} // ReconcileUtils::CheckTIFFDigest; +} // PhotoDataUtils::GetNativeInfo // ================================================================================================= -// ReconcileUtils::CheckExifDigest -// =============================== -int -ReconcileUtils::CheckExifDigest ( const TIFF_Manager & tiff, const SXMPMeta & xmp ) +bool PhotoDataUtils::IsValueDifferent ( const TIFF_Manager::TagInfo & exifInfo, const std::string & xmpValue, std::string * exifValue ) { - std::string newDigest, oldDigest; + if ( exifInfo.dataLen == 0 ) return false; // Ignore empty Exif values. - ComputeExifDigest ( tiff, &newDigest ); - bool found = xmp.GetProperty ( kXMP_NS_EXIF, "NativeDigest", &oldDigest, 0 ); + if ( ReconcileUtils::IsUTF8 ( exifInfo.dataPtr, exifInfo.dataLen ) ) { // ! Note that ASCII is UTF-8. + exifValue->assign ( (char*)exifInfo.dataPtr, exifInfo.dataLen ); + } else { + if ( ignoreLocalText ) return false; + ReconcileUtils::LocalToUTF8 ( exifInfo.dataPtr, exifInfo.dataLen, exifValue ); + } - if ( ! found ) return kDigestMissing; - if ( newDigest == oldDigest ) return kDigestMatches; - return kDigestDiffers; + return (*exifValue != xmpValue); -} // ReconcileUtils::CheckExifDigest; +} // PhotoDataUtils::IsValueDifferent // ================================================================================================= -// ReconcileUtils::SetTIFFDigest -// ============================= -void -ReconcileUtils::SetTIFFDigest ( const TIFF_Manager & tiff, SXMPMeta * xmp ) +bool PhotoDataUtils::IsValueDifferent ( const IPTC_Manager & newIPTC, const IPTC_Manager & oldIPTC, XMP_Uns8 id ) { - std::string newDigest; + IPTC_Manager::DataSetInfo newInfo; + size_t newCount = newIPTC.GetDataSet ( id, &newInfo ); + if ( newCount == 0 ) return false; // Ignore missing new IPTC values. - ComputeTIFFDigest ( tiff, &newDigest ); - xmp->SetProperty ( kXMP_NS_TIFF, "NativeDigest", newDigest.c_str() ); + IPTC_Manager::DataSetInfo oldInfo; + size_t oldCount = oldIPTC.GetDataSet ( id, &oldInfo ); + if ( oldCount == 0 ) return true; // Missing old IPTC values differ. + + if ( newCount != oldCount ) return true; -} // ReconcileUtils::SetTIFFDigest; + std::string oldStr, newStr; -// ================================================================================================= -// ReconcileUtils::SetExifDigest -// ============================= + for ( newCount = 0; newCount < oldCount; ++newCount ) { -void -ReconcileUtils::SetExifDigest ( const TIFF_Manager & tiff, SXMPMeta * xmp ) -{ - std::string newDigest; + if ( ignoreLocalText & (! newIPTC.UsingUTF8()) ) { // Check to see if the new value should be ignored. + (void) newIPTC.GetDataSet ( id, &newInfo, newCount ); + if ( ! ReconcileUtils::IsASCII ( newInfo.dataPtr, newInfo.dataLen ) ) continue; + } + + (void) newIPTC.GetDataSet_UTF8 ( id, &newStr, newCount ); + (void) oldIPTC.GetDataSet_UTF8 ( id, &oldStr, newCount ); + if ( newStr.size() == 0 ) continue; // Ignore empty new IPTC. + if ( newStr != oldStr ) break; - ComputeExifDigest ( tiff, &newDigest ); - xmp->SetProperty ( kXMP_NS_EXIF, "NativeDigest", newDigest.c_str() ); + } -} // ReconcileUtils::SetExifDigest; + return ( newCount != oldCount ); // Not different if all values matched. + +} // PhotoDataUtils::IsValueDifferent // ================================================================================================= // ================================================================================================= @@ -372,10 +354,10 @@ ImportSingleTIFF_Short ( const TIFF_Manager::TagInfo & tagInfo, const bool nativ XMP_Uns16 binValue = *((XMP_Uns16*)tagInfo.dataPtr); if ( ! nativeEndian ) binValue = Flip2 ( binValue ); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -397,10 +379,10 @@ ImportSingleTIFF_Long ( const TIFF_Manager::TagInfo & tagInfo, const bool native XMP_Uns32 binValue = *((XMP_Uns32*)tagInfo.dataPtr); if ( ! nativeEndian ) binValue = Flip4 ( binValue ); - + char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%lu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%lu", (unsigned long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -427,10 +409,10 @@ ImportSingleTIFF_Rational ( const TIFF_Manager::TagInfo & tagInfo, const bool na binNum = Flip4 ( binNum ); binDenom = Flip4 ( binDenom ); } - + char strValue[40]; - snprintf ( strValue, sizeof(strValue), "%lu/%lu", binNum, binDenom ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%lu/%lu", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -457,10 +439,10 @@ ImportSingleTIFF_SRational ( const TIFF_Manager::TagInfo & tagInfo, const bool n Flip4 ( &binNum ); Flip4 ( &binDenom ); } - + char strValue[40]; - snprintf ( strValue, sizeof(strValue), "%ld/%ld", binNum, binDenom ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%ld/%ld", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -479,11 +461,14 @@ ImportSingleTIFF_ASCII ( const TIFF_Manager::TagInfo & tagInfo, SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) { try { // Don't let errors with one stop the others. + + TrimTrailingSpaces ( (TIFF_Manager::TagInfo*) &tagInfo ); + if ( tagInfo.dataLen == 0 ) return; // Ignore empty tags. const char * chPtr = (const char *)tagInfo.dataPtr; const bool hasNul = (chPtr[tagInfo.dataLen-1] == 0); const bool isUTF8 = ReconcileUtils::IsUTF8 ( chPtr, tagInfo.dataLen ); - + if ( isUTF8 && hasNul ) { xmp->SetProperty ( xmpNS, xmpProp, chPtr ); } else { @@ -491,11 +476,8 @@ ImportSingleTIFF_ASCII ( const TIFF_Manager::TagInfo & tagInfo, if ( isUTF8 ) { strValue.assign ( chPtr, tagInfo.dataLen ); } else { - #if ! XMP_UNIXBuild - ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); - #else - return; // ! Hack until legacy-as-local issues are resolved for generic UNIX. - #endif + if ( ignoreLocalText ) return; + ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); } xmp->SetProperty ( xmpNS, xmpProp, strValue.c_str() ); } @@ -518,10 +500,10 @@ ImportSingleTIFF_Byte ( const TIFF_Manager::TagInfo & tagInfo, try { // Don't let errors with one stop the others. XMP_Uns8 binValue = *((XMP_Uns8*)tagInfo.dataPtr); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -542,10 +524,10 @@ ImportSingleTIFF_SByte ( const TIFF_Manager::TagInfo & tagInfo, try { // Don't let errors with one stop the others. XMP_Int8 binValue = *((XMP_Int8*)tagInfo.dataPtr); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -567,10 +549,10 @@ ImportSingleTIFF_SShort ( const TIFF_Manager::TagInfo & tagInfo, const bool nati XMP_Int16 binValue = *((XMP_Int16*)tagInfo.dataPtr); if ( ! nativeEndian ) Flip2 ( &binValue ); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -592,10 +574,10 @@ ImportSingleTIFF_SLong ( const TIFF_Manager::TagInfo & tagInfo, const bool nativ XMP_Int32 binValue = *((XMP_Int32*)tagInfo.dataPtr); if ( ! nativeEndian ) Flip4 ( &binValue ); - + char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%ld", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%ld", (long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -617,7 +599,7 @@ ImportSingleTIFF_Float ( const TIFF_Manager::TagInfo & tagInfo, const bool nativ float binValue = *((float*)tagInfo.dataPtr); if ( ! nativeEndian ) Flip4 ( &binValue ); - + xmp->SetProperty_Float ( xmpNS, xmpProp, binValue ); } catch ( ... ) { @@ -639,7 +621,7 @@ ImportSingleTIFF_Double ( const TIFF_Manager::TagInfo & tagInfo, const bool nati double binValue = *((double*)tagInfo.dataPtr); if ( ! nativeEndian ) Flip8 ( &binValue ); - + xmp->SetProperty_Float ( xmpNS, xmpProp, binValue ); // ! Yes, SetProperty_Float. } catch ( ... ) { @@ -727,17 +709,19 @@ ImportArrayTIFF_Short ( const TIFF_Manager::TagInfo & tagInfo, const bool native try { // Don't let errors with one stop the others. XMP_Uns16 * binPtr = (XMP_Uns16*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Uns16 binValue = *binPtr; if ( ! nativeEndian ) binValue = Flip2 ( binValue ); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -758,17 +742,19 @@ ImportArrayTIFF_Long ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeE try { // Don't let errors with one stop the others. XMP_Uns32 * binPtr = (XMP_Uns32*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Uns32 binValue = *binPtr; if ( ! nativeEndian ) binValue = Flip4 ( binValue ); - + char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%lu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%lu", (unsigned long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -789,21 +775,23 @@ ImportArrayTIFF_Rational ( const TIFF_Manager::TagInfo & tagInfo, const bool nat try { // Don't let errors with one stop the others. XMP_Uns32 * binPtr = (XMP_Uns32*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, binPtr += 2 ) { - + XMP_Uns32 binNum = binPtr[0]; XMP_Uns32 binDenom = binPtr[1]; if ( ! nativeEndian ) { binNum = Flip4 ( binNum ); binDenom = Flip4 ( binDenom ); } - + char strValue[40]; - snprintf ( strValue, sizeof(strValue), "%lu/%lu", binNum, binDenom ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%lu/%lu", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -824,21 +812,23 @@ ImportArrayTIFF_SRational ( const TIFF_Manager::TagInfo & tagInfo, const bool na try { // Don't let errors with one stop the others. XMP_Int32 * binPtr = (XMP_Int32*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, binPtr += 2 ) { - + XMP_Int32 binNum = binPtr[0]; XMP_Int32 binDenom = binPtr[1]; if ( ! nativeEndian ) { Flip4 ( &binNum ); Flip4 ( &binDenom ); } - + char strValue[40]; - snprintf ( strValue, sizeof(strValue), "%ld/%ld", binNum, binDenom ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%ld/%ld", (long)binNum, (long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -857,28 +847,30 @@ ImportArrayTIFF_ASCII ( const TIFF_Manager::TagInfo & tagInfo, SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) { try { // Don't let errors with one stop the others. + + TrimTrailingSpaces ( (TIFF_Manager::TagInfo*) &tagInfo ); + if ( tagInfo.dataLen == 0 ) return; // Ignore empty tags. const char * chPtr = (const char *)tagInfo.dataPtr; const char * chEnd = chPtr + tagInfo.dataLen; const bool hasNul = (chPtr[tagInfo.dataLen-1] == 0); const bool isUTF8 = ReconcileUtils::IsUTF8 ( chPtr, tagInfo.dataLen ); - + std::string strValue; - + if ( (! isUTF8) || (! hasNul) ) { if ( isUTF8 ) { strValue.assign ( chPtr, tagInfo.dataLen ); } else { - #if ! XMP_UNIXBuild - ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); - #else - return; // ! Hack until legacy-as-local issues are resolved for generic UNIX. - #endif + if ( ignoreLocalText ) return; + ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); } chPtr = strValue.c_str(); chEnd = chPtr + strValue.size(); } - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( ; chPtr < chEnd; chPtr += (strlen(chPtr) + 1) ) { xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, chPtr ); } @@ -901,16 +893,18 @@ ImportArrayTIFF_Byte ( const TIFF_Manager::TagInfo & tagInfo, try { // Don't let errors with one stop the others. XMP_Uns8 * binPtr = (XMP_Uns8*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Uns8 binValue = *binPtr; - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -931,16 +925,18 @@ ImportArrayTIFF_SByte ( const TIFF_Manager::TagInfo & tagInfo, try { // Don't let errors with one stop the others. XMP_Int8 * binPtr = (XMP_Int8*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Int8 binValue = *binPtr; - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -961,17 +957,19 @@ ImportArrayTIFF_SShort ( const TIFF_Manager::TagInfo & tagInfo, const bool nativ try { // Don't let errors with one stop the others. XMP_Int16 * binPtr = (XMP_Int16*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Int16 binValue = *binPtr; if ( ! nativeEndian ) Flip2 ( &binValue ); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -992,17 +990,19 @@ ImportArrayTIFF_SLong ( const TIFF_Manager::TagInfo & tagInfo, const bool native try { // Don't let errors with one stop the others. XMP_Int32 * binPtr = (XMP_Int32*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Int32 binValue = *binPtr; if ( ! nativeEndian ) Flip4 ( &binValue ); - + char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%ld", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%ld", (long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -1023,17 +1023,19 @@ ImportArrayTIFF_Float ( const TIFF_Manager::TagInfo & tagInfo, const bool native try { // Don't let errors with one stop the others. float * binPtr = (float*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + float binValue = *binPtr; if ( ! nativeEndian ) Flip4 ( &binValue ); - + std::string strValue; SXMPUtils::ConvertFromFloat ( binValue, "", &strValue ); - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue.c_str() ); - + } } catch ( ... ) { @@ -1054,17 +1056,19 @@ ImportArrayTIFF_Double ( const TIFF_Manager::TagInfo & tagInfo, const bool nativ try { // Don't let errors with one stop the others. double * binPtr = (double*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + double binValue = *binPtr; if ( ! nativeEndian ) Flip8 ( &binValue ); - + std::string strValue; SXMPUtils::ConvertFromFloat ( binValue, "", &strValue ); // ! Yes, ConvertFromFloat. - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue.c_str() ); - + } } catch ( ... ) { @@ -1139,45 +1143,11 @@ ImportArrayTIFF ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian } // ImportArrayTIFF // ================================================================================================= -// ImportTIFF_VerifyImport -// ======================= -// -// Decide whether to proceed with the import based on the digest state and presence of the legacy -// and XMP. Will also delete existing XMP if appropriate. - -static bool -ImportTIFF_VerifyImport ( const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState, - XMP_Uns8 tiffIFD, XMP_Uns16 tiffID, const char * xmpNS, const char * xmpProp, - TIFF_Manager::TagInfo * tagInfo ) -{ - bool found = false; - - try { // Don't let errors with one stop the others. - - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return false; - } - - found = tiff.GetTag ( tiffIFD, tiffID, tagInfo ); - - } catch ( ... ) { - found = false; - } - - return found; - -} // ImportTIFF_VerifyImport - -// ================================================================================================= // ImportTIFF_CheckStandardMapping // =============================== static bool -ImportTIFF_CheckStandardMapping ( const TIFF_Manager::TagInfo & tagInfo, - const TIFF_MappingToXMP & mapInfo ) +ImportTIFF_CheckStandardMapping ( const TIFF_Manager::TagInfo & tagInfo, const TIFF_MappingToXMP & mapInfo ) { XMP_Assert ( (kTIFF_ByteType <= tagInfo.type) && (tagInfo.type <= kTIFF_LastType) ); XMP_Assert ( mapInfo.type <= kTIFF_LastType ); @@ -1189,7 +1159,9 @@ ImportTIFF_CheckStandardMapping ( const TIFF_Manager::TagInfo & tagInfo, if ( (tagInfo.type != kTIFF_ShortType) && (tagInfo.type != kTIFF_LongType) ) return false; } - if ( (tagInfo.count != mapInfo.count) && (mapInfo.count != kAnyCount) ) return false; + if ( (tagInfo.count != mapInfo.count) && // Maybe there is a problem because the counts don't match. + // (mapInfo.count != kAnyCount) && ... don't need this because of the new check below ... + (mapInfo.count == 1) ) return false; // Be tolerant of mismatch in expected array size. return true; @@ -1200,7 +1172,7 @@ ImportTIFF_CheckStandardMapping ( const TIFF_Manager::TagInfo & tagInfo, // =========================== static void -ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState ) +ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta * xmp ) { const bool nativeEndian = tiff.IsNativeEndian(); TIFF_Manager::TagInfo tagInfo; @@ -1227,22 +1199,16 @@ ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta const TIFF_MappingToXMP & mapInfo = mappings[i]; const bool mapSingle = ((mapInfo.count == 1) || (mapInfo.type == kTIFF_ASCIIType)); - - // Skip tags that have special mappings, they are handled individually later. Delete any - // existing XMP property before going further. But after the special mapping check since we - // don't have the XMP property name for those. This lets legacy deletions propagate and - // eliminates any problems with existing XMP property form. Make sure the actual tag has - // the expected type and count, ignore it (pretend it is not present) if not. - - if ( mapInfo.name[0] == 0 ) continue; // Skip special mappings. - - bool ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, ifd, mapInfo.id, xmpNS, mapInfo.name, &tagInfo ); - if (! ok ) continue; - - XMP_Assert ( tagInfo.type != kTIFF_UndefinedType ); // These have a special mapping. + + if ( mapInfo.name[0] == 0 ) continue; // Skip special mappings, handled higher up. + + bool found = tiff.GetTag ( ifd, mapInfo.id, &tagInfo ); + if ( ! found ) continue; + + XMP_Assert ( tagInfo.type != kTIFF_UndefinedType ); // These must have a special mapping. if ( tagInfo.type == kTIFF_UndefinedType ) continue; if ( ! ImportTIFF_CheckStandardMapping ( tagInfo, mapInfo ) ) continue; - + if ( mapSingle ) { ImportSingleTIFF ( tagInfo, nativeEndian, xmp, xmpNS, mapInfo.name ); } else { @@ -1250,10 +1216,10 @@ ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta } } catch ( ... ) { - + // Do nothing, let other imports proceed. // ? Notify client? - + } } @@ -1274,38 +1240,55 @@ ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta // part, the digits that would be to the right of the decimal point. static void -ImportTIFF_Date ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & dateInfo, XMP_Uns16 secID, +ImportTIFF_Date ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & dateInfo, SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) { + XMP_Uns16 secID; + switch ( dateInfo.id ) { + case kTIFF_DateTime : secID = kTIFF_SubSecTime; break; + case kTIFF_DateTimeOriginal : secID = kTIFF_SubSecTimeOriginal; break; + case kTIFF_DateTimeDigitized : secID = kTIFF_SubSecTimeDigitized; break; + } + try { // Don't let errors with one stop the others. + + if ( (dateInfo.type != kTIFF_ASCIIType) || (dateInfo.count != 20) ) return; const char * dateStr = (const char *) dateInfo.dataPtr; if ( (dateStr[4] != ':') || (dateStr[7] != ':') || (dateStr[10] != ' ') || (dateStr[13] != ':') || (dateStr[16] != ':') ) return; - + XMP_DateTime binValue; - + binValue.year = GatherInt ( &dateStr[0], 4 ); binValue.month = GatherInt ( &dateStr[5], 2 ); binValue.day = GatherInt ( &dateStr[8], 2 ); + if ( (binValue.year != 0) | (binValue.month != 0) | (binValue.day != 0) ) binValue.hasDate = true; + binValue.hour = GatherInt ( &dateStr[11], 2 ); binValue.minute = GatherInt ( &dateStr[14], 2 ); binValue.second = GatherInt ( &dateStr[17], 2 ); binValue.nanoSecond = 0; // Get the fractional seconds later. - binValue.tzSign = binValue.tzHour = binValue.tzMinute = 0; - SXMPUtils::SetTimeZone ( &binValue ); // Assume local time. - + if ( (binValue.hour != 0) | (binValue.minute != 0) | (binValue.second != 0) ) binValue.hasTime = true; + + binValue.tzSign = 0; // ! Separate assignment, avoid VS integer truncation warning. + binValue.tzHour = binValue.tzMinute = 0; + binValue.hasTimeZone = false; // Exif times have no zone. + + // *** Consider looking at the TIFF/EP TimeZoneOffset tag? + TIFF_Manager::TagInfo secInfo; - bool found = tiff.GetTag ( kTIFF_ExifIFD, secID, &secInfo ); - + bool found = tiff.GetTag ( kTIFF_ExifIFD, secID, &secInfo ); // ! Subseconds are all in the Exif IFD. + if ( found && (secInfo.type == kTIFF_ASCIIType) ) { const char * fracPtr = (const char *) secInfo.dataPtr; binValue.nanoSecond = GatherInt ( fracPtr, secInfo.dataLen ); size_t digits = 0; for ( ; (('0' <= *fracPtr) && (*fracPtr <= '9')); ++fracPtr ) ++digits; for ( ; digits < 9; ++digits ) binValue.nanoSecond *= 10; + if ( binValue.nanoSecond != 0 ) binValue.hasTime = true; } - + xmp->SetProperty_Date ( xmpNS, xmpProp, binValue ); } catch ( ... ) { @@ -1326,14 +1309,14 @@ ImportTIFF_LocTextASCII ( const TIFF_Manager & tiff, XMP_Uns8 ifd, XMP_Uns16 tag try { // Don't let errors with one stop the others. TIFF_Manager::TagInfo tagInfo; - + bool found = tiff.GetTag ( ifd, tagID, &tagInfo ); if ( (! found) || (tagInfo.type != kTIFF_ASCIIType) ) return; - + const char * chPtr = (const char *)tagInfo.dataPtr; const bool hasNul = (chPtr[tagInfo.dataLen-1] == 0); const bool isUTF8 = ReconcileUtils::IsUTF8 ( chPtr, tagInfo.dataLen ); - + if ( isUTF8 && hasNul ) { xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", chPtr ); } else { @@ -1341,11 +1324,8 @@ ImportTIFF_LocTextASCII ( const TIFF_Manager & tiff, XMP_Uns8 ifd, XMP_Uns16 tag if ( isUTF8 ) { strValue.assign ( chPtr, tagInfo.dataLen ); } else { - #if ! XMP_UNIXBuild - ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); - #else - return; // ! Hack until legacy-as-local issues are resolved for generic UNIX. - #endif + if ( ignoreLocalText ) return; + ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); } xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", strValue.c_str() ); } @@ -1368,7 +1348,7 @@ ImportTIFF_EncodedString ( const TIFF_Manager & tiff, const TIFF_Manager::TagInf try { // Don't let errors with one stop the others. std::string strValue; - + bool ok = tiff.DecodeString ( tagInfo.dataPtr, tagInfo.dataLen, &strValue ); if ( ! ok ) return; @@ -1382,7 +1362,7 @@ ImportTIFF_EncodedString ( const TIFF_Manager & tiff, const TIFF_Manager::TagInf // Do nothing, let other imports proceed. // ? Notify client? } - + } // ImportTIFF_EncodedString // ================================================================================================= @@ -1397,15 +1377,15 @@ ImportTIFF_Flash ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, XMP_Uns16 binValue = *((XMP_Uns16*)tagInfo.dataPtr); if ( ! nativeEndian ) binValue = Flip2 ( binValue ); - + bool fired = (bool)(binValue & 1); // Avoid implicit 0/1 conversion. int rtrn = (binValue >> 1) & 3; int mode = (binValue >> 3) & 3; bool function = (bool)((binValue >> 5) & 1); bool redEye = (bool)((binValue >> 6) & 1); - + static const char * sTwoBits[] = { "0", "1", "2", "3" }; - + xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Fired", (fired ? kXMP_TrueStr : kXMP_FalseStr) ); xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Return", sTwoBits[rtrn] ); xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Mode", sTwoBits[mode] ); @@ -1422,7 +1402,7 @@ ImportTIFF_Flash ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, // ================================================================================================= // ImportTIFF_OECFTable // ==================== -// +// // Although the XMP for the OECF and SFR tables is the same, the Exif is not. The OECF table has // signed rational values and the SFR table has unsigned. @@ -1434,25 +1414,25 @@ ImportTIFF_OECFTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr; const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen; - - XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); + + XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); XMP_Uns16 rows = *((XMP_Uns16*)(bytePtr+2)); if ( ! nativeEndian ) { columns = Flip2 ( columns ); rows = Flip2 ( rows ); } - + char buffer[40]; - + snprintf ( buffer, sizeof(buffer), "%d", columns ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Columns", buffer ); snprintf ( buffer, sizeof(buffer), "%d", rows ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Rows", buffer ); - + std::string arrayPath; - + SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Names", &arrayPath ); - + bytePtr += 4; // Move to the list of names. for ( size_t i = columns; i > 0; --i ) { size_t nameLen = strlen((XMP_StringPtr)bytePtr) + 1; // ! Include the terminating nul. @@ -1460,26 +1440,26 @@ ImportTIFF_OECFTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, (XMP_StringPtr)bytePtr ); bytePtr += nameLen; } - + if ( (byteEnd - bytePtr) != (8 * columns * rows) ) { xmp->DeleteProperty ( xmpNS, xmpProp ); return; }; // Make sure the values are present. SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Values", &arrayPath ); - + XMP_Int32 * binPtr = (XMP_Int32*)bytePtr; for ( size_t i = (columns * rows); i > 0; --i, binPtr += 2 ) { - + XMP_Int32 binNum = binPtr[0]; XMP_Int32 binDenom = binPtr[1]; if ( ! nativeEndian ) { Flip4 ( &binNum ); Flip4 ( &binDenom ); } - - snprintf ( buffer, sizeof(buffer), "%ld/%ld", binNum, binDenom ); // AUDIT: Use of sizeof(buffer) is safe. - + + snprintf ( buffer, sizeof(buffer), "%ld/%ld", (long)binNum, (long)binDenom ); // AUDIT: Use of sizeof(buffer) is safe. + xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, buffer ); - + } - + return; } catch ( ... ) { @@ -1492,7 +1472,7 @@ ImportTIFF_OECFTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, // ================================================================================================= // ImportTIFF_SFRTable // =================== -// +// // Although the XMP for the OECF and SFR tables is the same, the Exif is not. The OECF table has // signed rational values and the SFR table has unsigned. @@ -1504,25 +1484,25 @@ ImportTIFF_SFRTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr; const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen; - - XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); + + XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); XMP_Uns16 rows = *((XMP_Uns16*)(bytePtr+2)); if ( ! nativeEndian ) { columns = Flip2 ( columns ); rows = Flip2 ( rows ); } - + char buffer[40]; - + snprintf ( buffer, sizeof(buffer), "%d", columns ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Columns", buffer ); snprintf ( buffer, sizeof(buffer), "%d", rows ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Rows", buffer ); - + std::string arrayPath; - + SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Names", &arrayPath ); - + bytePtr += 4; // Move to the list of names. for ( size_t i = columns; i > 0; --i ) { size_t nameLen = strlen((XMP_StringPtr)bytePtr) + 1; // ! Include the terminating nul. @@ -1530,26 +1510,26 @@ ImportTIFF_SFRTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, (XMP_StringPtr)bytePtr ); bytePtr += nameLen; } - + if ( (byteEnd - bytePtr) != (8 * columns * rows) ) { xmp->DeleteProperty ( xmpNS, xmpProp ); return; }; // Make sure the values are present. SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Values", &arrayPath ); - + XMP_Uns32 * binPtr = (XMP_Uns32*)bytePtr; for ( size_t i = (columns * rows); i > 0; --i, binPtr += 2 ) { - + XMP_Uns32 binNum = binPtr[0]; XMP_Uns32 binDenom = binPtr[1]; if ( ! nativeEndian ) { binNum = Flip4 ( binNum ); binDenom = Flip4 ( binDenom ); } - - snprintf ( buffer, sizeof(buffer), "%lu/%lu", binNum, binDenom ); // AUDIT: Use of sizeof(buffer) is safe. - + + snprintf ( buffer, sizeof(buffer), "%lu/%lu", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Use of sizeof(buffer) is safe. + xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, buffer ); - + } - + return; } catch ( ... ) { @@ -1571,34 +1551,34 @@ ImportTIFF_CFATable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr; const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen; - - XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); + + XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); XMP_Uns16 rows = *((XMP_Uns16*)(bytePtr+2)); if ( ! nativeEndian ) { columns = Flip2 ( columns ); rows = Flip2 ( rows ); } - + char buffer[20]; std::string arrayPath; - + snprintf ( buffer, sizeof(buffer), "%d", columns ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Columns", buffer ); snprintf ( buffer, sizeof(buffer), "%d", rows ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Rows", buffer ); - + bytePtr += 4; // Move to the matrix of values. if ( (byteEnd - bytePtr) != (columns * rows) ) goto BadExif; // Make sure the values are present. - + SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Values", &arrayPath ); - + for ( size_t i = (columns * rows); i > 0; --i, ++bytePtr ) { snprintf ( buffer, sizeof(buffer), "%hu", *bytePtr ); // AUDIT: Use of sizeof(buffer) is safe. xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, buffer ); } - + return; - + BadExif: // Ignore the tag if the table is ill-formed. xmp->DeleteProperty ( xmpNS, xmpProp ); return; @@ -1622,55 +1602,55 @@ ImportTIFF_DSDTable ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & t const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr; const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen; - - XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); + + XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); XMP_Uns16 rows = *((XMP_Uns16*)(bytePtr+2)); if ( ! tiff.IsNativeEndian() ) { columns = Flip2 ( columns ); rows = Flip2 ( rows ); } - + char buffer[20]; - + snprintf ( buffer, sizeof(buffer), "%d", columns ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Columns", buffer ); snprintf ( buffer, sizeof(buffer), "%d", rows ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Rows", buffer ); - + std::string arrayPath; SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Settings", &arrayPath ); - + bytePtr += 4; // Move to the list of settings. UTF16Unit * utf16Ptr = (UTF16Unit*)bytePtr; UTF16Unit * utf16End = (UTF16Unit*)byteEnd; - + std::string utf8; - + // Figure 17 in the Exif 2.2 spec is unclear. It has counts for rows and columns, but the // settings are listed as 1..n, not as a rectangular matrix. So, ignore the counts and copy // strings until the end of the Exif value. - + while ( utf16Ptr < utf16End ) { - + size_t nameLen = 0; while ( utf16Ptr[nameLen] != 0 ) ++nameLen; ++nameLen; // ! Include the terminating nul. if ( (utf16Ptr + nameLen) > utf16End ) goto BadExif; - + try { FromUTF16 ( utf16Ptr, nameLen, &utf8, tiff.IsBigEndian() ); } catch ( ... ) { goto BadExif; // Ignore the tag if there are conversion errors. } - + xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, utf8.c_str() ); - + utf16Ptr += nameLen; - + } - + return; - + BadExif: // Ignore the tag if the table is ill-formed. xmp->DeleteProperty ( xmpNS, xmpProp ); return; @@ -1693,13 +1673,13 @@ ImportTIFF_GPSCoordinate ( const TIFF_Manager & tiff, const TIFF_Manager::TagInf try { // Don't let errors with one stop the others. const bool nativeEndian = tiff.IsNativeEndian(); - + XMP_Uns16 refID = posInfo.id - 1; // ! The GPS refs and coordinates are all tag n and n+1. TIFF_Manager::TagInfo refInfo; bool found = tiff.GetTag ( kTIFF_GPSInfoIFD, refID, &refInfo ); if ( (! found) || (refInfo.type != kTIFF_ASCIIType) || (refInfo.count != 2) ) return; char ref = *((char*)refInfo.dataPtr); - + XMP_Uns32 * binPtr = (XMP_Uns32*)posInfo.dataPtr; XMP_Uns32 degNum = binPtr[0]; XMP_Uns32 degDenom = binPtr[1]; @@ -1715,32 +1695,32 @@ ImportTIFF_GPSCoordinate ( const TIFF_Manager & tiff, const TIFF_Manager::TagInf secNum = Flip4 ( secNum ); secDenom = Flip4 ( secDenom ); } - + char buffer[40]; - + if ( (degDenom == 1) && (minDenom == 1) && (secDenom == 1) ) { - - snprintf ( buffer, sizeof(buffer), "%lu,%lu,%lu%c", degNum, minNum, secNum, ref ); // AUDIT: Using sizeof(buffer is safe. - + + snprintf ( buffer, sizeof(buffer), "%lu,%lu,%lu%c", (unsigned long)degNum, (unsigned long)minNum, (unsigned long)secNum, ref ); // AUDIT: Using sizeof(buffer is safe. + } else { - + XMP_Uns32 maxDenom = degDenom; if ( minDenom > degDenom ) maxDenom = minDenom; if ( secDenom > degDenom ) maxDenom = secDenom; - + int fracDigits = 1; while ( maxDenom > 10 ) { ++fracDigits; maxDenom = maxDenom/10; } - + double temp = (double)degNum / (double)degDenom; double degrees = (double)((XMP_Uns32)temp); // Just the integral number of degrees. double minutes = ((temp - degrees) * 60.0) + ((double)minNum / (double)minDenom) + (((double)secNum / (double)secDenom) / 60.0); - + snprintf ( buffer, sizeof(buffer), "%.0f,%.*f%c", degrees, fracDigits, minutes, ref ); // AUDIT: Using sizeof(buffer is safe. - + } - + xmp->SetProperty ( xmpNS, xmpProp, buffer ); } catch ( ... ) { @@ -1761,18 +1741,18 @@ ImportTIFF_GPSTimeStamp ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo try { // Don't let errors with one stop the others. const bool nativeEndian = tiff.IsNativeEndian(); - + bool haveDate; TIFF_Manager::TagInfo dateInfo; haveDate = tiff.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDateStamp, &dateInfo ); if ( ! haveDate ) haveDate = tiff.GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeOriginal, &dateInfo ); if ( ! haveDate ) haveDate = tiff.GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeDigitized, &dateInfo ); if ( ! haveDate ) return; - + const char * dateStr = (const char *) dateInfo.dataPtr; if ( (dateStr[4] != ':') || (dateStr[7] != ':') ) return; if ( (dateStr[10] != 0) && (dateStr[10] != ' ') ) return; - + XMP_Uns32 * binPtr = (XMP_Uns32*)timeInfo.dataPtr; XMP_Uns32 hourNum = binPtr[0]; XMP_Uns32 hourDenom = binPtr[1]; @@ -1788,8 +1768,8 @@ ImportTIFF_GPSTimeStamp ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo secNum = Flip4 ( secNum ); secDenom = Flip4 ( secDenom ); } - - double fHour, fMin, fSec, fNano, temp; + + double fHour, fMin, fSec, fNano, temp; fSec = (double)secNum / (double)secDenom; temp = (double)minNum / (double)minDenom; fMin = (double)((XMP_Uns32)temp); @@ -1798,20 +1778,21 @@ ImportTIFF_GPSTimeStamp ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo fHour = (double)((XMP_Uns32)temp); fSec += (temp - fHour) * 3600.0; temp = (double)((XMP_Uns32)fSec); - fNano = (fSec - temp) * (1000.0*1000.0*1000.0); + fNano = ((fSec - temp) * (1000.0*1000.0*1000.0)) + 0.5; // Try to avoid n999... problems. fSec = temp; - + XMP_DateTime binStamp; - binStamp.tzSign = kXMP_TimeIsUTC; - binStamp.tzHour = binStamp.tzMinute = 0; binStamp.year = GatherInt ( dateStr, 4 ); binStamp.month = GatherInt ( dateStr+5, 2 ); binStamp.day = GatherInt ( dateStr+8, 2 ); binStamp.hour = (XMP_Int32)fHour; binStamp.minute = (XMP_Int32)fMin; binStamp.second = (XMP_Int32)fSec; - binStamp.nanoSecond = (XMP_Int32)fNano; - + binStamp.nanoSecond = (XMP_Int32)fNano; + binStamp.hasTimeZone = true; // Exif GPS TimeStamp is implicitly UTC. + binStamp.tzSign = kXMP_TimeIsUTC; + binStamp.tzHour = binStamp.tzMinute = 0; + xmp->SetProperty_Date ( xmpNS, xmpProp, binStamp ); } catch ( ... ) { @@ -1825,76 +1806,99 @@ ImportTIFF_GPSTimeStamp ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo // ================================================================================================= // ================================================================================================= -// ReconcileUtils::ImportTIFF -// ========================== +// PhotoDataUtils::Import2WayExif +// ============================== +// +// Import the TIFF/Exif tags that have 2 way mappings to XMP, i.e. no correspondence to IPTC. +// These are always imported for the tiff: and exif: namespaces, but not for others. void -ReconcileUtils::ImportTIFF ( const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState, XMP_FileFormat srcFormat ) +PhotoDataUtils::Import2WayExif ( const TIFF_Manager & exif, SXMPMeta * xmp, int iptcDigestState ) { + const bool nativeEndian = exif.IsNativeEndian(); + + bool found, foundFromXMP; TIFF_Manager::TagInfo tagInfo; - bool ok; - ImportTIFF_StandardMappings ( kTIFF_PrimaryIFD, tiff, xmp, digestState ); + ImportTIFF_StandardMappings ( kTIFF_PrimaryIFD, exif, xmp ); + ImportTIFF_StandardMappings ( kTIFF_ExifIFD, exif, xmp ); + ImportTIFF_StandardMappings ( kTIFF_GPSInfoIFD, exif, xmp ); + + // ----------------------------------------------------------------- + // Fixup erroneous files that have a negative value for GPSAltitude. - // 306 DateTime is a date master with 37520 SubSecTime and is mapped to xmp:ModifyDate. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_PrimaryIFD, kTIFF_DateTime, - kXMP_NS_XMP, "ModifyDate", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count == 20) ) { - ImportTIFF_Date ( tiff, tagInfo, kTIFF_SubSecTime, xmp, kXMP_NS_XMP, "ModifyDate" ); - } + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSAltitude, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_RationalType) && (tagInfo.count == 1) ) { - if ( srcFormat != kXMP_PhotoshopFile ) { - - // ! TIFF tags 270, 315, and 33432 are ignored for Photoshop files. - - XMP_Assert ( (srcFormat == kXMP_JPEGFile) || (srcFormat == kXMP_TIFFFile) ); - - // 270 ImageDescription is an ASCII tag and is mapped to dc:description["x-default"]. - // Call ImportTIFF_VerifyImport using the x-default item path, don't delete the whole array. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_PrimaryIFD, kTIFF_ImageDescription, - kXMP_NS_DC, "description[?xml:lang='x-default']", &tagInfo ); - if ( ok ) ImportTIFF_LocTextASCII ( tiff, kTIFF_PrimaryIFD, kTIFF_ImageDescription, - xmp, kXMP_NS_DC, "description" ); - - // 315 Artist is an ASCII tag and is mapped to dc:creator[*]. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_PrimaryIFD, kTIFF_Artist, - kXMP_NS_DC, "creator", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ASCIIType) ) { - ImportArrayTIFF_ASCII ( tagInfo, xmp, kXMP_NS_DC, "creator" ); + XMP_Uns32 num = exif.GetUns32 ( tagInfo.dataPtr ); + XMP_Uns32 denom = exif.GetUns32 ( (XMP_Uns8*)tagInfo.dataPtr + 4 ); + bool numNeg = num >> 31; + bool denomNeg = denom >> 31; + + if ( (numNeg != denomNeg) || numNeg ) { // Does the GPSAltitude look negative? + if ( denomNeg ) { + denom = -denom; + num = -num; + numNeg = num >> 31; + } + if ( numNeg ) { + char buffer [32]; + num = -num; + snprintf ( buffer, sizeof(buffer), "%lu/%lu", (unsigned long) num, (unsigned long) denom ); // AUDIT: Using sizeof(buffer) is safe. + xmp->SetProperty ( kXMP_NS_EXIF, "GPSAltitude", buffer ); + xmp->SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "1" ); + } } - // 33432 Copyright is mapped to dc:rights["x-default"]. - // Call ImportTIFF_VerifyImport using the x-default item path, don't delete the whole array. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_PrimaryIFD, kTIFF_Copyright, - kXMP_NS_DC, "rights[?xml:lang='x-default']", &tagInfo ); - if ( ok ) ImportTIFF_LocTextASCII ( tiff, kTIFF_PrimaryIFD, kTIFF_Copyright, xmp, kXMP_NS_DC, "rights" ); - } + + // --------------------------------------------------------------- + // Import DateTimeOriginal and DateTime if the XMP doss not exist. -} // ReconcileUtils::ImportTIFF; + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeOriginal, &tagInfo ); + foundFromXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "DateTimeOriginal" ); + + if ( found && (! foundFromXMP) && (tagInfo.type == kTIFF_ASCIIType) ) { + ImportTIFF_Date ( exif, tagInfo, xmp, kXMP_NS_EXIF, "DateTimeOriginal" ); + } -// ================================================================================================= -// ReconcileUtils::ImportExif -// ========================== + found = exif.GetTag ( kTIFF_PrimaryIFD, kTIFF_DateTime, &tagInfo ); + foundFromXMP = xmp->DoesPropertyExist ( kXMP_NS_XMP, "ModifyDate" ); + + if ( found && (! foundFromXMP) && (tagInfo.type == kTIFF_ASCIIType) ) { + ImportTIFF_Date ( exif, tagInfo, xmp, kXMP_NS_XMP, "ModifyDate" ); + } -void -ReconcileUtils::ImportExif ( const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState ) -{ - const bool nativeEndian = tiff.IsNativeEndian(); + // ---------------------------------------------------- + // Import the Exif IFD tags that have special mappings. + + // 34855 ISOSpeedRatings has special cases for ISO over 65535. The tag is SHORT, some cameras + // omit the tag and some write 65535, all put the real ISO in MakerNote - which ACR might + // extract and leave in the XMP. There are 3 import cases: + // 1. No native tag: Leave existing XMP. + // 2. All of the native values are under 65535: Clear any XMP and import. + // 3. One or more of the native values are 65535: Leave existing XMP, else import. + + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeedRatings, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_ShortType) && (tagInfo.count > 0) ) { - TIFF_Manager::TagInfo tagInfo; - bool ok; + bool keepXMP = false; + XMP_Uns16 * itemPtr = (XMP_Uns16*) tagInfo.dataPtr; + for ( XMP_Uns32 i = 0; i < tagInfo.count; ++i, ++itemPtr ) { + if ( *itemPtr == 0xFFFF ) { keepXMP = true; break; } // ! Don't care about BE or LF, same either way. + } - ImportTIFF_StandardMappings ( kTIFF_ExifIFD, tiff, xmp, digestState ); - ImportTIFF_StandardMappings ( kTIFF_GPSInfoIFD, tiff, xmp, digestState ); + if ( ! keepXMP ) xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + + if ( ! xmp->DoesPropertyExist ( kXMP_NS_EXIF, "ISOSpeedRatings" ) ) { + ImportArrayTIFF ( tagInfo, exif.IsNativeEndian(), xmp, kXMP_NS_EXIF, "ISOSpeedRatings" ); + } - // ------------------------------------------------------ - // Here are the Exif IFD tags that have special mappings: + } // 36864 ExifVersion is 4 "undefined" ASCII characters. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_ExifVersion, - kXMP_NS_EXIF, "ExifVersion", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { char str[5]; *((XMP_Uns32*)str) = *((XMP_Uns32*)tagInfo.dataPtr); str[4] = 0; @@ -1902,9 +1906,8 @@ ReconcileUtils::ImportExif ( const TIFF_Manager & tiff, SXMPMeta * xmp, int dige } // 40960 FlashpixVersion is 4 "undefined" ASCII characters. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_FlashpixVersion, - kXMP_NS_EXIF, "FlashpixVersion", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_FlashpixVersion, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { char str[5]; *((XMP_Uns32*)str) = *((XMP_Uns32*)tagInfo.dataPtr); str[4] = 0; @@ -1912,94 +1915,65 @@ ReconcileUtils::ImportExif ( const TIFF_Manager & tiff, SXMPMeta * xmp, int dige } // 37121 ComponentsConfiguration is an array of 4 "undefined" UInt8 bytes. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_ComponentsConfiguration, - kXMP_NS_EXIF, "ComponentsConfiguration", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ComponentsConfiguration, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { ImportArrayTIFF_Byte ( tagInfo, xmp, kXMP_NS_EXIF, "ComponentsConfiguration" ); } // 37510 UserComment is a string with explicit encoding. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_UserComment, - kXMP_NS_EXIF, "UserComment", &tagInfo ); - if ( ok ) { - ImportTIFF_EncodedString ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "UserComment", true /* isLangAlt */ ); - } - - // 36867 DateTimeOriginal is a date master with 37521 SubSecTimeOriginal. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_DateTimeOriginal, - kXMP_NS_EXIF, "DateTimeOriginal", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count == 20) ) { - ImportTIFF_Date ( tiff, tagInfo, kTIFF_SubSecTimeOriginal, xmp, kXMP_NS_EXIF, "DateTimeOriginal" ); - } - if ( ! xmp->DoesPropertyExist ( kXMP_NS_XMP, "CreateDate" ) ) { - std::string exifDate; - ok = xmp->GetProperty ( kXMP_NS_EXIF, "DateTimeOriginal", &exifDate, 0 ); - if ( ok ) xmp->SetProperty ( kXMP_NS_XMP, "CreateDate", exifDate.c_str() ); - } - - // 36868 DateTimeDigitized is a date master with 37522 SubSecTimeDigitized. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_DateTimeDigitized, - kXMP_NS_EXIF, "DateTimeDigitized", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count == 20) ) { - ImportTIFF_Date ( tiff, tagInfo, kTIFF_SubSecTimeDigitized, xmp, kXMP_NS_EXIF, "DateTimeDigitized" ); + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_UserComment, &tagInfo ); + if ( found ) { + ImportTIFF_EncodedString ( exif, tagInfo, xmp, kXMP_NS_EXIF, "UserComment", true /* isLangAlt */ ); } // 34856 OECF is an OECF/SFR table. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_OECF, - kXMP_NS_EXIF, "OECF", &tagInfo ); - if ( ok ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_OECF, &tagInfo ); + if ( found ) { ImportTIFF_OECFTable ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "OECF" ); } // 37385 Flash is a UInt16 collection of bit fields and is mapped to a struct in XMP. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_Flash, - kXMP_NS_EXIF, "Flash", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ShortType) && (tagInfo.count == 1) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_Flash, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_ShortType) && (tagInfo.count == 1) ) { ImportTIFF_Flash ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "Flash" ); } // 41484 SpatialFrequencyResponse is an OECF/SFR table. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_SpatialFrequencyResponse, - kXMP_NS_EXIF, "SpatialFrequencyResponse", &tagInfo ); - if ( ok ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_SpatialFrequencyResponse, &tagInfo ); + if ( found ) { ImportTIFF_SFRTable ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "SpatialFrequencyResponse" ); } // 41728 FileSource is an "undefined" UInt8. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_FileSource, - kXMP_NS_EXIF, "FileSource", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 1) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_FileSource, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 1) ) { ImportSingleTIFF_Byte ( tagInfo, xmp, kXMP_NS_EXIF, "FileSource" ); } // 41729 SceneType is an "undefined" UInt8. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_SceneType, - kXMP_NS_EXIF, "SceneType", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 1) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_SceneType, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 1) ) { ImportSingleTIFF_Byte ( tagInfo, xmp, kXMP_NS_EXIF, "SceneType" ); } // 41730 CFAPattern is a custom table. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_CFAPattern, - kXMP_NS_EXIF, "CFAPattern", &tagInfo ); - if ( ok ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_CFAPattern, &tagInfo ); + if ( found ) { ImportTIFF_CFATable ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "CFAPattern" ); } // 41995 DeviceSettingDescription is a custom table. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_DeviceSettingDescription, - kXMP_NS_EXIF, "DeviceSettingDescription", &tagInfo ); - if ( ok ) { - ImportTIFF_DSDTable ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "DeviceSettingDescription" ); + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_DeviceSettingDescription, &tagInfo ); + if ( found ) { + ImportTIFF_DSDTable ( exif, tagInfo, xmp, kXMP_NS_EXIF, "DeviceSettingDescription" ); } - // ---------------------------------------------------------- - // Here are the GPS Info IFD tags that have special mappings: + // -------------------------------------------------------- + // Import the GPS Info IFD tags that have special mappings. // 0 GPSVersionID is 4 UInt8 bytes and mapped as "n.n.n.n". - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, - kXMP_NS_EXIF, "GPSVersionID", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ByteType) && (tagInfo.count == 4) ) { + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_ByteType) && (tagInfo.count == 4) ) { const char * strIn = (const char *) tagInfo.dataPtr; char strOut[8]; strOut[0] = strIn[0]; @@ -2012,213 +1986,418 @@ ReconcileUtils::ImportExif ( const TIFF_Manager & tiff, SXMPMeta * xmp, int dige } // 2 GPSLatitude is a GPS coordinate master. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSLatitude, - kXMP_NS_EXIF, "GPSLatitude", &tagInfo ); - if ( ok ) { - ImportTIFF_GPSCoordinate ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSLatitude" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSLatitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSLatitude" ); } // 4 GPSLongitude is a GPS coordinate master. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSLongitude, - kXMP_NS_EXIF, "GPSLongitude", &tagInfo ); - if ( ok ) { - ImportTIFF_GPSCoordinate ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSLongitude" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSLongitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSLongitude" ); } // 7 GPSTimeStamp is a UTC time as 3 rationals, mated with the optional GPSDateStamp. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp, - kXMP_NS_EXIF, "GPSTimeStamp", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_RationalType) && (tagInfo.count == 3) ) { - ImportTIFF_GPSTimeStamp ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSTimeStamp" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_RationalType) && (tagInfo.count == 3) ) { + ImportTIFF_GPSTimeStamp ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSTimeStamp" ); } // 20 GPSDestLatitude is a GPS coordinate master. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSDestLatitude, - kXMP_NS_EXIF, "GPSDestLatitude", &tagInfo ); - if ( ok ) { - ImportTIFF_GPSCoordinate ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSDestLatitude" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLatitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSDestLatitude" ); } // 22 GPSDestLongitude is a GPS coordinate master. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSDestLongitude, - kXMP_NS_EXIF, "GPSDestLongitude", &tagInfo ); - if ( ok ) { - ImportTIFF_GPSCoordinate ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSDestLongitude" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLongitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSDestLongitude" ); } // 27 GPSProcessingMethod is a string with explicit encoding. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSProcessingMethod, - kXMP_NS_EXIF, "GPSProcessingMethod", &tagInfo ); - if ( ok ) { - ImportTIFF_EncodedString ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSProcessingMethod" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSProcessingMethod, &tagInfo ); + if ( found ) { + ImportTIFF_EncodedString ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSProcessingMethod" ); } // 28 GPSAreaInformation is a string with explicit encoding. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation, - kXMP_NS_EXIF, "GPSAreaInformation", &tagInfo ); - if ( ok ) { - ImportTIFF_EncodedString ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSAreaInformation" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation, &tagInfo ); + if ( found ) { + ImportTIFF_EncodedString ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSAreaInformation" ); } -} // ReconcileUtils::ImportExif; +} // PhotoDataUtils::Import2WayExif // ================================================================================================= -// ================================================================================================= +// Import3WayDateTime +// ================== + +static void Import3WayDateTime ( XMP_Uns16 exifTag, const TIFF_Manager & exif, const IPTC_Manager & iptc, + SXMPMeta * xmp, int iptcDigestState, const IPTC_Manager & oldIPTC ) +{ + XMP_Uns8 iptcDS; + XMP_StringPtr xmpNS, xmpProp; + + if ( exifTag == kTIFF_DateTimeOriginal ) { + iptcDS = kIPTC_DateCreated; + xmpNS = kXMP_NS_Photoshop; + xmpProp = "DateCreated"; + } else if ( exifTag == kTIFF_DateTimeDigitized ) { + iptcDS = kIPTC_DigitalCreateDate; + xmpNS = kXMP_NS_XMP; + xmpProp = "CreateDate"; + } else { + XMP_Throw ( "Unrecognized dateID", kXMPErr_BadParam ); + } + + size_t iptcCount; + bool haveXMP, haveExif, haveIPTC; // ! These are manipulated to simplify MWG-compliant logic. + std::string xmpValue, exifValue, iptcValue; + TIFF_Manager::TagInfo exifInfo; + IPTC_Manager::DataSetInfo iptcInfo; + + // Get the basic info about available values. + haveXMP = xmp->GetProperty ( xmpNS, xmpProp, &xmpValue, 0 ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, iptcDS, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_ExifIFD, exifTag, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC ) { + + PhotoDataUtils::ImportIPTC_Date ( iptcDS, iptc, xmp ); + + } else if ( haveExif && (exifInfo.type == kTIFF_ASCIIType) ) { + + // Only import the Exif form if the non-TZ information differs from the XMP. + + TIFF_FileWriter exifFromXMP; + TIFF_Manager::TagInfo infoFromXMP; + + ExportTIFF_Date ( *xmp, xmpNS, xmpProp, &exifFromXMP, exifTag ); + bool foundFromXMP = exifFromXMP.GetTag ( kTIFF_ExifIFD, exifTag, &infoFromXMP ); + + if ( (! foundFromXMP) || (exifInfo.dataLen != infoFromXMP.dataLen) || + (! XMP_LitNMatch ( (char*)exifInfo.dataPtr, (char*)infoFromXMP.dataPtr, exifInfo.dataLen )) ) { + ImportTIFF_Date ( exif, exifInfo, xmp, xmpNS, xmpProp ); + } + + } + +} // Import3WayDateTime // ================================================================================================= -// ExportSingleTIFF_Short -// ====================== +// PhotoDataUtils::Import3WayItems +// =============================== +// +// Handle the imports that involve all 3 of Exif, IPTC, and XMP. There are only 4 properties with +// 3-way mappings, copyright, description, creator, and date/time. Following the MWG guidelines, +// this general policy is applied separately to each: +// +// If the new IPTC digest differs from the stored digest (favor IPTC over Exif and XMP) +// If the IPTC value differs from the predicted old IPTC value +// Import the IPTC value, including deleting the XMP +// Else if the Exif is non-empty and differs from the XMP +// Import the Exif value (does not delete existing XMP) +// Else if the stored IPTC digest is missing (favor Exif over IPTC, or IPTC over missing XMP) +// If the Exif is non-empty and differs from the XMP +// Import the Exif value (does not delete existing XMP) +// Else if the XMP is missing and the Exif is missing or empty +// Import the IPTC value +// Else (the new IPTC digest matches the stored digest - ignore the IPTC) +// If the Exif is non-empty and differs from the XMP +// Import the Exif value (does not delete existing XMP) +// +// Note that missing or empty Exif will never cause existing XMP to be deleted. This is a pragmatic +// choice to improve compatibility with pre-MWG software. There are few Exif-only editors for these +// 3-way properties, there are important existing IPTC-only editors. -static void -ExportSingleTIFF_Short ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, - TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +// ------------------------------------------------------------------------------------------------- + +void PhotoDataUtils::Import3WayItems ( const TIFF_Manager & exif, const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ) { - try { // Don't let errors with one stop the others. + size_t iptcCount; - long xmpValue; + bool haveXMP, haveExif, haveIPTC; // ! These are manipulated to simplify MWG-compliant logic. + std::string xmpValue, exifValue, iptcValue; - bool foundXMP = xmp.GetProperty_Int ( xmpNS, xmpProp, &xmpValue, 0 ); - if ( ! foundXMP ) { - tiff->DeleteTag ( ifd, id ); - return; - } + TIFF_Manager::TagInfo exifInfo; + IPTC_Manager::DataSetInfo iptcInfo; - if ( (xmpValue < 0) || (xmpValue > 0xFFFF) ) return; // ? Complain? Peg to limit? Delete the tag? + IPTC_Writer oldIPTC; + if ( iptcDigestState == kDigestDiffers ) { + PhotoDataUtils::ExportIPTC ( *xmp, &oldIPTC ); // Predict old IPTC DataSets based on the existing XMP. + } + + // --------------------------------------------------------------------------------- + // Process the copyright. Replace internal nuls in the Exif to "merge" the portions. + + // Get the basic info about available values. + haveXMP = xmp->GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", 0, &xmpValue, 0 ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, kIPTC_CopyrightNotice, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_Copyright, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); - tiff->SetTag_Short ( ifd, id, (XMP_Uns16)xmpValue ); + if ( haveExif && (exifInfo.dataLen > 1) ) { // Replace internal nul characters with linefeed. + for ( XMP_Uns32 i = 0; i < exifInfo.dataLen-1; ++i ) { + if ( ((char*)exifInfo.dataPtr)[i] == 0 ) ((char*)exifInfo.dataPtr)[i] = 0x0A; + } + } + + if ( haveIPTC ) { + PhotoDataUtils::ImportIPTC_LangAlt ( iptc, xmp, kIPTC_CopyrightNotice, kXMP_NS_DC, "rights" ); + } else if ( haveExif && PhotoDataUtils::IsValueDifferent ( exifInfo, xmpValue, &exifValue ) ) { + xmp->SetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", exifValue.c_str() ); + } + + // ------------------------ + // Process the description. + + // Get the basic info about available values. + haveXMP = xmp->GetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", 0, &xmpValue, 0 ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, kIPTC_Description, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_ImageDescription, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC ) { + PhotoDataUtils::ImportIPTC_LangAlt ( iptc, xmp, kIPTC_Description, kXMP_NS_DC, "description" ); + } else if ( haveExif && PhotoDataUtils::IsValueDifferent ( exifInfo, xmpValue, &exifValue ) ) { + xmp->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", exifValue.c_str() ); + } - } catch ( ... ) { - // Do nothing, let other exports proceed. - // ? Notify client? + // ------------------------------------------------------------------------------------------- + // Process the creator. The XMP and IPTC are arrays, the Exif is a semicolon separated string. + + // Get the basic info about available values. + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_DC, "creator" ); + haveExif = PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_Artist, &exifInfo ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, kIPTC_Creator, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_Artist, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC ) { + PhotoDataUtils::ImportIPTC_Array ( iptc, xmp, kIPTC_Creator, kXMP_NS_DC, "creator" ); + } else if ( haveExif && PhotoDataUtils::IsValueDifferent ( exifInfo, xmpValue, &exifValue ) ) { + SXMPUtils::SeparateArrayItems ( xmp, kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, exifValue ); } -} // ExportSingleTIFF_Short + // ------------------------------------------------------------------------------ + // Process DateTimeDigitized; DateTimeOriginal and DateTime are 2-way. + // *** Exif DateTimeOriginal <-> XMP exif:DateTimeOriginal + // *** IPTC DateCreated <-> XMP photoshop:DateCreated + // *** Exif DateTimeDigitized <-> IPTC DigitalCreateDate <-> XMP xmp:CreateDate + // *** TIFF DateTime <-> XMP xmp:ModifyDate + + Import3WayDateTime ( kTIFF_DateTimeDigitized, exif, iptc, xmp, iptcDigestState, oldIPTC ); + +} // PhotoDataUtils::Import3WayItems // ================================================================================================= -// ExportSingleTIFF_Rational -// ========================= +// ================================================================================================= + +// ================================================================================================= +// ExportSingleTIFF +// ================ // -// An XMP (unsigned) rational is supposed to be written as a string "num/denom". +// This is only called when the XMP exists and will be exported. And only for standard mappings. + +// ! Only implemented for the types known to be needed. static void -ExportSingleTIFF_Rational ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, - TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +ExportSingleTIFF ( TIFF_Manager * tiff, XMP_Uns8 ifd, const TIFF_MappingToXMP & mapInfo, + bool nativeEndian, const std::string & xmpValue ) { - try { // Don't let errors with one stop the others. + XMP_Assert ( (mapInfo.count == 1) || (mapInfo.type == kTIFF_ASCIIType) ); + XMP_Assert ( mapInfo.name[0] != 0 ); // Must be a standard mapping. + + char nextChar; // Used to make sure sscanf consumes all of the string. + + switch ( mapInfo.type ) { - std::string strValue; - XMP_OptionBits xmpFlags; + case kTIFF_ByteType : { + unsigned short binValue; + int items = sscanf ( xmpValue.c_str(), "%hu%c", &binValue, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 1 ) return; // ? complain? notify client? + tiff->SetTag_Byte ( ifd, mapInfo.id, (XMP_Uns8)binValue ); + break; + } - bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, &strValue, &xmpFlags ); - if ( ! foundXMP ) { - tiff->DeleteTag ( ifd, id ); - return; + case kTIFF_ShortType : { + unsigned long binValue; + int items = sscanf ( xmpValue.c_str(), "%lu%c", &binValue, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 1 ) return; // ? complain? notify client? + tiff->SetTag_Short ( ifd, mapInfo.id, (XMP_Uns16)binValue ); + break; } - - if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the tag? - - XMP_Uns32 newNum, newDenom; - const char* partPtr; - size_t partLen; - - partPtr = strValue.c_str(); - for ( partLen = 0; partPtr[partLen] != 0; ++partLen ) { - if ( (partPtr[partLen] < '0') || (partPtr[partLen] > '9') ) break; + + case kTIFF_ShortOrLongType : { + unsigned long binValue; + int items = sscanf ( xmpValue.c_str(), "%lu%c", &binValue, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 1 ) return; // ? complain? notify client? + if ( binValue <= 0xFFFF ) { + tiff->SetTag_Short ( ifd, mapInfo.id, (XMP_Uns16)binValue ); + } else { + tiff->SetTag_Long ( ifd, mapInfo.id, (XMP_Uns32)binValue ); + } + break; } - if ( partLen == 0 ) return; // ? Complain? Delete the tag? - newNum = GatherInt ( partPtr, partLen ); - - if ( partPtr[partLen] == 0 ) { - newDenom = 1; // Tolerate bad XMP that just has the numerator. - } else if ( partPtr[partLen] != '/' ) { - return; // ? Complain? Delete the tag? - } else { - partPtr += partLen+1; - for ( partLen = 0; partPtr[partLen] != 0; ++partLen ) { - if ( (partPtr[partLen] < '0') || (partPtr[partLen] > '9') ) break; + + case kTIFF_RationalType : { // The XMP is formatted as "num/denom". + unsigned long num, denom; + int items = sscanf ( xmpValue.c_str(), "%lu/%lu%c", &num, &denom, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 2 ) { + if ( items != 1 ) return; // ? complain? notify client? + denom = 1; // The XMP was just an integer, assume a denominator of 1. + } + tiff->SetTag_Rational ( ifd, mapInfo.id, (XMP_Uns32)num, (XMP_Uns32)denom ); + break; + } + + case kTIFF_SRationalType : { // The XMP is formatted as "num/denom". + signed long num, denom; + int items = sscanf ( xmpValue.c_str(), "%ld/%ld%c", &num, &denom, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 2 ) { + if ( items != 1 ) return; // ? complain? notify client? + denom = 1; // The XMP was just an integer, assume a denominator of 1. } - if ( (partLen == 0) || (partPtr[partLen] != 0) ) return; // ? Complain? Delete the tag? - newDenom = GatherInt ( partPtr, partLen ); + tiff->SetTag_SRational ( ifd, mapInfo.id, (XMP_Int32)num, (XMP_Int32)denom ); + break; } + + case kTIFF_ASCIIType : + tiff->SetTag ( ifd, mapInfo.id, kTIFF_ASCIIType, (XMP_Uns32)(xmpValue.size()+1), xmpValue.c_str() ); + break; - tiff->SetTag_Rational ( ifd, id, newNum, newDenom ); + default: + XMP_Assert ( false ); // Force a debug assert for unexpected types. - } catch ( ... ) { - // Do nothing, let other exports proceed. - // ? Notify client? } - -} // ExportSingleTIFF_Rational + +} // ExportSingleTIFF // ================================================================================================= -// ExportSingleTIFF_ASCII -// ====================== +// ExportArrayTIFF +// ================ +// +// This is only called when the XMP exists and will be exported. And only for standard mappings. + +// ! Only implemented for the types known to be needed. static void -ExportSingleTIFF_ASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, - TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +ExportArrayTIFF ( TIFF_Manager * tiff, XMP_Uns8 ifd, const TIFF_MappingToXMP & mapInfo, bool nativeEndian, + const SXMPMeta & xmp, const char * xmpNS, const char * xmpArray ) { - try { // Don't let errors with one stop the others. - - std::string xmpValue; - XMP_OptionBits xmpFlags; - - bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, &xmpValue, &xmpFlags ); - if ( ! foundXMP ) { - tiff->DeleteTag ( ifd, id ); - return; - } - - if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the tag? - - tiff->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)( xmpValue.size()+1 ), xmpValue.c_str() ); - - } catch ( ... ) { - // Do nothing, let other exports proceed. - // ? Notify client? + XMP_Assert ( (mapInfo.count != 1) && (mapInfo.type != kTIFF_ASCIIType) ); + XMP_Assert ( mapInfo.name[0] != 0 ); // Must be a standard mapping. + XMP_Assert ( mapInfo.type == kTIFF_ShortType ); // ! So far ISOSpeedRatings is the only standard array export. + XMP_Assert ( xmp.DoesPropertyExist ( xmpNS, xmpArray ) ); + + if ( mapInfo.type != kTIFF_ShortType ) return; // ! So far ISOSpeedRatings is the only standard array export. + + size_t arraySize = xmp.CountArrayItems ( xmpNS, xmpArray ); + if ( arraySize == 0 ) { + tiff->DeleteTag ( ifd, mapInfo.id ); + return; } -} // ExportSingleTIFF_ASCII + std::vector<XMP_Uns16> vecValue; + vecValue.assign ( arraySize, 0 ); + XMP_Uns16 * binPtr = (XMP_Uns16*) &vecValue[0]; + + std::string itemPath; + XMP_Int32 int32; + XMP_Uns16 uns16; + for ( size_t i = 1; i <= arraySize; ++i, ++binPtr ) { + SXMPUtils::ComposeArrayItemPath ( xmpNS, xmpArray, (XMP_Index)i, &itemPath ); + xmp.GetProperty_Int ( xmpNS, itemPath.c_str(), &int32, 0 ); + uns16 = (XMP_Uns16)int32; + if ( ! nativeEndian ) uns16 = Flip2 ( uns16 ); + *binPtr = uns16; + } + + tiff->SetTag ( ifd, mapInfo.id, kTIFF_ShortType, (XMP_Uns32)arraySize, &vecValue[0] ); + +} // ExportArrayTIFF // ================================================================================================= -// ExportArrayTIFF_ASCII -// ===================== -// -// Catenate all of the XMP array values into a string with separating nul characters. +// ExportTIFF_StandardMappings +// =========================== static void -ExportArrayTIFF_ASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, - TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +ExportTIFF_StandardMappings ( XMP_Uns8 ifd, TIFF_Manager * tiff, const SXMPMeta & xmp ) { - try { // Don't let errors with one stop the others. + const bool nativeEndian = tiff->IsNativeEndian(); + TIFF_Manager::TagInfo tagInfo; + std::string xmpValue; + XMP_OptionBits xmpForm; - std::string itemValue, fullValue; - XMP_OptionBits xmpFlags; + const TIFF_MappingToXMP * mappings = 0; + const char * xmpNS = 0; - bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); - if ( ! foundXMP ) { - tiff->DeleteTag ( ifd, id ); - return; - } + if ( ifd == kTIFF_PrimaryIFD ) { + mappings = sPrimaryIFDMappings; + xmpNS = kXMP_NS_TIFF; + } else if ( ifd == kTIFF_ExifIFD ) { + mappings = sExifIFDMappings; + xmpNS = kXMP_NS_EXIF; + } else if ( ifd == kTIFF_GPSInfoIFD ) { + mappings = sGPSInfoIFDMappings; + xmpNS = kXMP_NS_EXIF; // ! Yes, the GPS Info tags are in the exif: namespace. + } else { + XMP_Throw ( "Invalid IFD for standard mappings", kXMPErr_InternalFailure ); + } + + for ( size_t i = 0; mappings[i].id != 0xFFFF; ++i ) { + + try { // Don't let errors with one stop the others. + + const TIFF_MappingToXMP & mapInfo = mappings[i]; + + if ( mapInfo.exportMode == kExport_Never ) continue; + if ( mapInfo.name[0] == 0 ) continue; // Skip special mappings, handled higher up. + + bool haveTIFF = tiff->GetTag ( ifd, mapInfo.id, &tagInfo ); + if ( haveTIFF && (mapInfo.exportMode == kExport_InjectOnly) ) continue; + + bool haveXMP = xmp.GetProperty ( xmpNS, mapInfo.name, &xmpValue, &xmpForm ); + if ( ! haveXMP ) { + + if ( haveTIFF && (mapInfo.exportMode == kExport_Always) ) tiff->DeleteTag ( ifd, mapInfo.id ); + + } else { + + XMP_Assert ( tagInfo.type != kTIFF_UndefinedType ); // These must have a special mapping. + if ( tagInfo.type == kTIFF_UndefinedType ) continue; + + const bool mapSingle = ((mapInfo.count == 1) || (mapInfo.type == kTIFF_ASCIIType)); + if ( mapSingle ) { + if ( ! XMP_PropIsSimple ( xmpForm ) ) continue; // ? Notify client? + ExportSingleTIFF ( tiff, ifd, mapInfo, nativeEndian, xmpValue ); + } else { + if ( ! XMP_PropIsArray ( xmpForm ) ) continue; // ? Notify client? + ExportArrayTIFF ( tiff, ifd, mapInfo, nativeEndian, xmp, xmpNS, mapInfo.name ); + } + + } + + } catch ( ... ) { + + // Do nothing, let other imports proceed. + // ? Notify client? - if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the tag? - - size_t count = xmp.CountArrayItems ( xmpNS, xmpProp ); - for ( size_t i = 1; i <= count; ++i ) { // ! XMP arrays are indexed from 1. - (void) xmp.GetArrayItem ( xmpNS, xmpProp, (XMP_Index)i, &itemValue, &xmpFlags ); - if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? - fullValue.append ( itemValue ); - fullValue.append ( 1, '\x0' ); } - - tiff->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)fullValue.size(), fullValue.c_str() ); // ! Already have trailing nul. - } catch ( ... ) { - // Do nothing, let other exports proceed. - // ? Notify client? } - -} // ExportArrayTIFF_ASCII + +} // ExportTIFF_StandardMappings // ================================================================================================= // ExportTIFF_Date @@ -2231,35 +2410,77 @@ ExportArrayTIFF_ASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * x // decimal point. static void -ExportTIFF_Date ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, - TIFF_Manager * tiff, XMP_Uns8 mainIFD, XMP_Uns16 mainID, XMP_Uns8 fracIFD, XMP_Uns16 fracID ) +ExportTIFF_Date ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, TIFF_Manager * tiff, XMP_Uns16 mainID ) { - try { // Don't let errors with one stop the others. + XMP_Uns8 mainIFD = kTIFF_ExifIFD; + XMP_Uns16 fracID; + switch ( mainID ) { + case kTIFF_DateTime : mainIFD = kTIFF_PrimaryIFD; fracID = kTIFF_SubSecTime; break; + case kTIFF_DateTimeOriginal : fracID = kTIFF_SubSecTimeOriginal; break; + case kTIFF_DateTimeDigitized : fracID = kTIFF_SubSecTimeDigitized; break; + } - XMP_DateTime xmpValue; + try { // Don't let errors with one stop the others. - bool foundXMP = xmp.GetProperty_Date ( xmpNS, xmpProp, &xmpValue, 0 ); + std::string xmpStr; + bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, &xmpStr, 0 ); if ( ! foundXMP ) { tiff->DeleteTag ( mainIFD, mainID ); - tiff->DeleteTag ( fracIFD, fracID ); + tiff->DeleteTag ( kTIFF_ExifIFD, fracID ); // ! The subseconds are always in the Exif IFD. return; } + + // Format using all of the numbers. Then overwrite blanks for missing fields. The fields + // missing from the XMP are detected with length checks: YYYY-MM-DDThh:mm:ss + // < 18 - no seconds + // < 15 - no minutes + // < 12 - no hours + // < 9 - no day + // < 6 - no month + // < 1 - no year + + XMP_DateTime xmpBin; + SXMPUtils::ConvertToDate ( xmpStr.c_str(), &xmpBin ); char buffer[24]; - snprintf ( buffer, sizeof(buffer), "%.4d:%.2d:%.2d %.2d:%.2d:%.2d", // AUDIT: Use of sizeof(buffer) is safe. - xmpValue.year, xmpValue.month, xmpValue.day, xmpValue.hour, xmpValue.minute, xmpValue.second ); - + snprintf ( buffer, sizeof(buffer), "%04d:%02d:%02d %02d:%02d:%02d", // AUDIT: Use of sizeof(buffer) is safe. + xmpBin.year, xmpBin.month, xmpBin.day, xmpBin.hour, xmpBin.minute, xmpBin.second ); + + size_t xmpLen = xmpStr.size(); + if ( xmpLen < 18 ) { + buffer[17] = buffer[18] = ' '; + if ( xmpLen < 15 ) { + buffer[14] = buffer[15] = ' '; + if ( xmpLen < 12 ) { + buffer[11] = buffer[12] = ' '; + if ( xmpLen < 9 ) { + buffer[8] = buffer[9] = ' '; + if ( xmpLen < 6 ) { + buffer[5] = buffer[6] = ' '; + if ( xmpLen < 1 ) { + buffer[0] = buffer[1] = buffer[2] = buffer[3] = ' '; + } + } + } + } + } + } + tiff->SetTag_ASCII ( mainIFD, mainID, buffer ); + + if ( xmpBin.nanoSecond == 0 ) { + + tiff->DeleteTag ( kTIFF_ExifIFD, fracID ); - if ( xmpValue.nanoSecond != 0 ) { + } else { - snprintf ( buffer, sizeof(buffer), "%09d", xmpValue.nanoSecond ); // AUDIT: Use of sizeof(buffer) is safe. + snprintf ( buffer, sizeof(buffer), "%09d", xmpBin.nanoSecond ); // AUDIT: Use of sizeof(buffer) is safe. for ( size_t i = strlen(buffer)-1; i > 0; --i ) { if ( buffer[i] != '0' ) break; buffer[i] = 0; // Strip trailing zero digits. } - tiff->SetTag_ASCII ( fracIFD, fracID, buffer ); + tiff->SetTag_ASCII ( kTIFF_ExifIFD, fracID, buffer ); // ! The subseconds are always in the Exif IFD. } @@ -2267,10 +2488,55 @@ ExportTIFF_Date ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp // Do nothing, let other exports proceed. // ? Notify client? } - + } // ExportTIFF_Date // ================================================================================================= +// ExportTIFF_ArrayASCII +// ===================== +// +// Catenate all of the XMP array values into a string. Use a "; " separator for Artist, nul for others. + +static void +ExportTIFF_ArrayASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, + TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +{ + try { // Don't let errors with one stop the others. + + std::string itemValue, fullValue; + XMP_OptionBits xmpFlags; + + bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); + if ( ! foundXMP ) { + tiff->DeleteTag ( ifd, id ); + return; + } + + if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the tag? + + if ( id == kTIFF_Artist ) { + SXMPUtils::CatenateArrayItems ( xmp, xmpNS, xmpProp, 0, 0, kXMP_PropArrayIsOrdered, &fullValue ); + fullValue += '\x0'; // ! Need explicit final nul for SetTag below. + } else { + size_t count = xmp.CountArrayItems ( xmpNS, xmpProp ); + for ( size_t i = 1; i <= count; ++i ) { // ! XMP arrays are indexed from 1. + (void) xmp.GetArrayItem ( xmpNS, xmpProp, (XMP_Index)i, &itemValue, &xmpFlags ); + if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? + fullValue.append ( itemValue ); + fullValue.append ( 1, '\x0' ); + } + } + + tiff->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)fullValue.size(), fullValue.c_str() ); // ! Already have trailing nul. + + } catch ( ... ) { + // Do nothing, let other exports proceed. + // ? Notify client? + } + +} // ExportTIFF_ArrayASCII + +// ================================================================================================= // ExportTIFF_LocTextASCII // ====================== @@ -2287,14 +2553,14 @@ ExportTIFF_LocTextASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * tiff->DeleteTag ( ifd, id ); return; } - + tiff->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)( xmpValue.size()+1 ), xmpValue.c_str() ); } catch ( ... ) { // Do nothing, let other exports proceed. // ? Notify client? } - + } // ExportTIFF_LocTextASCII // ================================================================================================= @@ -2323,22 +2589,22 @@ ExportTIFF_EncodedString ( const SXMPMeta & xmp, const char * xmpNS, const char bool ok = xmp.GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &xmpValue, 0 ); if ( ! ok ) return; // ? Complain? Delete the tag? } - + XMP_Uns8 encoding = kTIFF_EncodeASCII; for ( size_t i = 0; i < xmpValue.size(); ++i ) { - if ( xmpValue[i] >= 0x80 ) { + if ( (XMP_Uns8)xmpValue[i] >= 0x80 ) { encoding = kTIFF_EncodeUnicode; break; } } - + tiff->SetTag_EncodedString ( ifd, id, xmpValue.c_str(), encoding ); } catch ( ... ) { // Do nothing, let other exports proceed. // ? Notify client? } - + } // ExportTIFF_EncodedString // ================================================================================================= @@ -2358,9 +2624,9 @@ ExportTIFF_GPSCoordinate ( const SXMPMeta & xmp, const char * xmpNS, const char { XMP_Uns16 refID = _id-1; // ! The GPS refs and locations are all tag N-1 and N pairs. XMP_Uns16 locID = _id; - + XMP_Assert ( (locID & 1) == 0 ); - + try { // Don't let errors with one stop the others. std::string xmpValue; @@ -2372,20 +2638,20 @@ ExportTIFF_GPSCoordinate ( const SXMPMeta & xmp, const char * xmpNS, const char tiff->DeleteTag ( ifd, locID ); return; } - + if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; - + const char * chPtr = xmpValue.c_str(); - + XMP_Uns32 deg=0, minNum=0, minDenom=1, sec=0; - + for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) deg = deg*10 + (*chPtr - '0'); if ( *chPtr != ',' ) return; // Bad XMP string. ++chPtr; // Skip the comma. - + for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) minNum = minNum*10 + (*chPtr - '0'); if ( (*chPtr != ',') && (*chPtr != '.') ) return; // Bad XMP string. - + if ( *chPtr == ',' ) { ++chPtr; // Skip the comma. @@ -2402,15 +2668,15 @@ ExportTIFF_GPSCoordinate ( const SXMPMeta & xmp, const char * xmpNS, const char } } - + if ( *(chPtr+1) != 0 ) return; // Bad XMP string. - + char ref[2]; ref[0] = *chPtr; ref[1] = 0; - + tiff->SetTag ( ifd, refID, kTIFF_ASCIIType, 2, &ref[0] ); - + XMP_Uns32 loc[6]; tiff->PutUns32 ( deg, &loc[0] ); tiff->PutUns32 ( 1, &loc[1] ); @@ -2418,120 +2684,251 @@ ExportTIFF_GPSCoordinate ( const SXMPMeta & xmp, const char * xmpNS, const char tiff->PutUns32 ( minDenom, &loc[3] ); tiff->PutUns32 ( sec, &loc[4] ); tiff->PutUns32 ( 1, &loc[5] ); - + tiff->SetTag ( ifd, locID, kTIFF_RationalType, 3, &loc[0] ); } catch ( ... ) { // Do nothing, let other exports proceed. // ? Notify client? } - -} // ExportTIFF_GPSCoordinate -// ================================================================================================= -// ================================================================================================= +} // ExportTIFF_GPSCoordinate // ================================================================================================= -// ReconcileUtils::ExportTIFF -// ========================== +// ExportTIFF_GPSTimeStamp +// ======================= // -// Only a few tags are written back from XMP to the primary IFD, they are each handled explicitly. -// The writeback tags are: -// 270 - ImageDescription -// 274 - Orientation -// 282 - XResolution -// 283 - YResolution -// 296 - ResolutionUnit -// 305 - Software -// 306 - DateTime -// 315 - Artist -// 33432 - Copyright - -// *** need to determine if the XMP has changed - only export when necessary +// The Exif is in 2 tags, GPSTimeStamp and GPSDateStamp. The time is 3 rationals for the hour, minute, +// and second in UTC. The date is a nul terminated string "YYYY:MM:DD". -void -ReconcileUtils::ExportTIFF ( const SXMPMeta & xmp, TIFF_Manager * tiff ) +static const double kBillion = 1000.0*1000.0*1000.0; +static const double mMaxSec = 4.0*kBillion - 1.0; + +static void +ExportTIFF_GPSTimeStamp ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, TIFF_Manager * tiff ) { - ExportTIFF_LocTextASCII ( xmp, kXMP_NS_DC, "description", - tiff, kTIFF_PrimaryIFD, kTIFF_ImageDescription ); - - ExportSingleTIFF_Short ( xmp, kXMP_NS_TIFF, "Orientation", - tiff, kTIFF_PrimaryIFD, kTIFF_Orientation ); - - ExportSingleTIFF_Rational ( xmp, kXMP_NS_TIFF, "XResolution", - tiff, kTIFF_PrimaryIFD, kTIFF_XResolution ); + try { // Don't let errors with one stop the others. - ExportSingleTIFF_Rational ( xmp, kXMP_NS_TIFF, "YResolution", - tiff, kTIFF_PrimaryIFD, kTIFF_YResolution ); + XMP_DateTime binXMP; + bool foundXMP = xmp.GetProperty_Date ( xmpNS, xmpProp, &binXMP, 0 ); + if ( ! foundXMP ) { + tiff->DeleteTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp ); + tiff->DeleteTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDateStamp ); + return; + } + + SXMPUtils::ConvertToUTCTime ( &binXMP ); - ExportSingleTIFF_Short ( xmp, kXMP_NS_TIFF, "ResolutionUnit", - tiff, kTIFF_PrimaryIFD, kTIFF_ResolutionUnit ); + XMP_Uns32 exifTime[6]; + tiff->PutUns32 ( binXMP.hour, &exifTime[0] ); + tiff->PutUns32 ( 1, &exifTime[1] ); + tiff->PutUns32 ( binXMP.minute, &exifTime[2] ); + tiff->PutUns32 ( 1, &exifTime[3] ); + if ( binXMP.nanoSecond == 0 ) { + tiff->PutUns32 ( binXMP.second, &exifTime[4] ); + tiff->PutUns32 ( 1, &exifTime[5] ); + } else { + double fSec = (double)binXMP.second + ((double)binXMP.nanoSecond / kBillion ); + XMP_Uns32 denom = 1000*1000; // Choose microsecond resolution by default. + TIFF_Manager::TagInfo oldInfo; + bool hadExif = tiff->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp, &oldInfo ); + if ( hadExif && (oldInfo.type == kTIFF_RationalType) && (oldInfo.count == 3) ) { + XMP_Uns32 oldDenom = tiff->GetUns32 ( &(((XMP_Uns32*)oldInfo.dataPtr)[5]) ); + if ( oldDenom != 1 ) denom = oldDenom; + } + fSec *= denom; + while ( fSec > mMaxSec ) { fSec /= 10; denom /= 10; } + tiff->PutUns32 ( (XMP_Uns32)fSec, &exifTime[4] ); + tiff->PutUns32 ( denom, &exifTime[5] ); + } + tiff->SetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp, kTIFF_RationalType, 3, &exifTime[0] ); - ExportSingleTIFF_ASCII ( xmp, kXMP_NS_XMP, "CreatorTool", - tiff, kTIFF_PrimaryIFD, kTIFF_Software ); + char exifDate[16]; // AUDIT: Long enough, only need 11. + snprintf ( exifDate, 12, "%04d:%02d:%02d", binXMP.year, binXMP.month, binXMP.day ); + if ( exifDate[10] == 0 ) { // Make sure there is no value overflow. + tiff->SetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDateStamp, kTIFF_ASCIIType, 11, exifDate ); + } - ExportTIFF_Date ( xmp, kXMP_NS_XMP, "ModifyDate", - tiff, kTIFF_PrimaryIFD, kTIFF_DateTime, kTIFF_ExifIFD, kTIFF_SubSecTime ); - - ExportArrayTIFF_ASCII ( xmp, kXMP_NS_DC, "creator", - tiff, kTIFF_PrimaryIFD, kTIFF_Artist ); + } catch ( ... ) { + // Do nothing, let other exports proceed. + // ? Notify client? + } - ExportTIFF_LocTextASCII ( xmp, kXMP_NS_DC, "rights", - tiff, kTIFF_PrimaryIFD, kTIFF_Copyright ); +} // ExportTIFF_GPSTimeStamp -} // ReconcileUtils::ExportTIFF; +// ================================================================================================= +// ================================================================================================= // ================================================================================================= -// ReconcileUtils::ExportExif +// PhotoDataUtils::ExportExif // ========================== -// -// Only a few tags are written back from XMP to the Exif and GPS IFDs, they are each handled -// explicitly. The Exif writeback tags are: -// 36867 - DateTimeOriginal (plus 37521 SubSecTimeOriginal) -// 36868 - DateTimeDigitized (plus 37522 SubSecTimeDigitized) -// 37510 - UserComment -// 40964 - RelatedSoundFile -// The GPS writeback tags are: -// 1 - GPSLatitudeRef -// 2 - GPSLatitude -// 3 - GPSLongitudeRef -// 4 - GPSLongitude - -// ! Older versions of Photoshop did not import the UserComment or RelatedSoundFile tags. Don't -// ! export the current XMP unless the original XMP had the tag or the current XMP has the tag. -// ! That is, don't delete the Exif tag if the XMP never had the property. void -ReconcileUtils::ExportExif ( const SXMPMeta & xmp, TIFF_Manager * tiff ) +PhotoDataUtils::ExportExif ( SXMPMeta * xmp, TIFF_Manager * exif ) { + bool haveXMP, haveExif; + std::string xmpValue; + XMP_Int32 int32; + XMP_Uns8 uns8; + + // Do all of the table driven standard exports. + + ExportTIFF_StandardMappings ( kTIFF_PrimaryIFD, exif, *xmp ); + ExportTIFF_StandardMappings ( kTIFF_ExifIFD, exif, *xmp ); + ExportTIFF_StandardMappings ( kTIFF_GPSInfoIFD, exif, *xmp ); + + // Export dc:description to TIFF ImageDescription, and exif:UserComment to EXIF UserComment. + + // *** This is not following the MWG guidelines. The policy here tries to be more backward compatible. + + ExportTIFF_LocTextASCII ( *xmp, kXMP_NS_DC, "description", + exif, kTIFF_PrimaryIFD, kTIFF_ImageDescription ); + + ExportTIFF_EncodedString ( *xmp, kXMP_NS_EXIF, "UserComment", + exif, kTIFF_ExifIFD, kTIFF_UserComment, true /* isLangAlt */ ); + + // Export all of the date/time tags. + // ! Special case: Don't create Exif DateTimeDigitized. This can avoid PSD full rewrite due to + // ! new mapping from xmp:CreateDate. - if ( xmp.DoesPropertyExist ( kXMP_NS_EXIF, "DateTimeOriginal" ) ) { - ExportTIFF_Date ( xmp, kXMP_NS_EXIF, "DateTimeOriginal", - tiff, kTIFF_ExifIFD, kTIFF_DateTimeOriginal, kTIFF_ExifIFD, kTIFF_SubSecTimeOriginal ); + if ( exif->GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeDigitized, 0 ) ) { + ExportTIFF_Date ( *xmp, kXMP_NS_XMP, "CreateDate", exif, kTIFF_DateTimeDigitized ); } - if ( xmp.DoesPropertyExist ( kXMP_NS_EXIF, "DateTimeDigitized" ) ) { - ExportTIFF_Date ( xmp, kXMP_NS_EXIF, "DateTimeDigitized", - tiff, kTIFF_ExifIFD, kTIFF_DateTimeDigitized, kTIFF_ExifIFD, kTIFF_SubSecTimeDigitized ); + ExportTIFF_Date ( *xmp, kXMP_NS_EXIF, "DateTimeOriginal", exif, kTIFF_DateTimeOriginal ); + ExportTIFF_Date ( *xmp, kXMP_NS_XMP, "ModifyDate", exif, kTIFF_DateTime ); + + // 34855 ISOSpeedRatings has special cases for ISO over 65535. The tag is SHORT, some cameras + // omit the tag and some write 65535, all put the real ISO in MakerNote - which ACR might + // extract and leave in the XMP. There are 2 export cases: + // 1. No XMP property, or one or more of the XMP values are over 65535: + // Leave both the XMP and native tag alone. + // 1. Have XMP property and all of the XMP values are under 65535: + // Leave existing native tag, else export; strip the XMP either way. + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + if ( haveXMP ) { + + XMP_Index i, count; + std::string isoValue; + bool haveHighISO = false; + + for ( i = 1, count = xmp->CountArrayItems ( kXMP_NS_EXIF, "ISOSpeedRatings" ); i <= count; ++i ) { + xmp->GetArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", i, &isoValue, 0 ); + if ( SXMPUtils::ConvertToInt ( isoValue.c_str() ) > 0xFFFF ) { haveHighISO = true; break; } + } + + if ( ! haveHighISO ) { + haveExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeedRatings, 0 ); + if ( ! haveExif ) { // ISOSpeedRatings has an inject-only mapping. + ExportArrayTIFF ( exif, kTIFF_ExifIFD, kISOSpeedMapping, exif->IsNativeEndian(), *xmp, kXMP_NS_EXIF, "ISOSpeedRatings" ); + } + xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings"); + } + } + + // Export the remaining TIFF, Exif, and GPS IFD tags. + + ExportTIFF_ArrayASCII ( *xmp, kXMP_NS_DC, "creator", exif, kTIFF_PrimaryIFD, kTIFF_Artist ); - if ( tiff->xmpHadUserComment || xmp.DoesPropertyExist ( kXMP_NS_EXIF, "UserComment" ) ) { - ExportTIFF_EncodedString ( xmp, kXMP_NS_EXIF, "UserComment", - tiff, kTIFF_ExifIFD, kTIFF_UserComment, true /* isLangAlt */ ); + ExportTIFF_LocTextASCII ( *xmp, kXMP_NS_DC, "rights", exif, kTIFF_PrimaryIFD, kTIFF_Copyright ); + + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "ExifVersion", &xmpValue, 0 ); + if ( haveXMP && (xmpValue.size() == 4) && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, 0 )) ) { + // 36864 ExifVersion is 4 "undefined" ASCII characters. + exif->SetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, kTIFF_UndefinedType, 4, xmpValue.data() ); + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "ComponentsConfiguration" ); + if ( haveXMP && (xmp->CountArrayItems ( kXMP_NS_EXIF, "ComponentsConfiguration" ) == 4) && + (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_ComponentsConfiguration, 0 )) ) { + // 37121 ComponentsConfiguration is an array of 4 "undefined" UInt8 bytes. + XMP_Uns8 compConfig[4]; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[1]", &int32, 0 ); + compConfig[0] = (XMP_Uns8)int32; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[2]", &int32, 0 ); + compConfig[1] = (XMP_Uns8)int32; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[3]", &int32, 0 ); + compConfig[2] = (XMP_Uns8)int32; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[4]", &int32, 0 ); + compConfig[3] = (XMP_Uns8)int32; + exif->SetTag ( kTIFF_ExifIFD, kTIFF_ComponentsConfiguration, kTIFF_UndefinedType, 4, &compConfig[0] ); + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "Flash" ); + if ( haveXMP && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_Flash, 0 )) ) { + // 37385 Flash is a UInt16 collection of bit fields and is mapped to a struct in XMP. + XMP_Uns16 binFlash = 0; + bool field; + haveXMP = xmp->GetProperty_Bool ( kXMP_NS_EXIF, "Flash/exif:Fired", &field, 0 ); + if ( haveXMP & field ) binFlash |= 0x0001; + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "Flash/exif:Return", &int32, 0 ); + if ( haveXMP ) binFlash |= (int32 & 3) << 1; + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "Flash/exif:Mode", &int32, 0 ); + if ( haveXMP ) binFlash |= (int32 & 3) << 3; + haveXMP = xmp->GetProperty_Bool ( kXMP_NS_EXIF, "Flash/exif:Function", &field, 0 ); + if ( haveXMP & field ) binFlash |= 0x0020; + haveXMP = xmp->GetProperty_Bool ( kXMP_NS_EXIF, "Flash/exif:RedEyeMode", &field, 0 ); + if ( haveXMP & field ) binFlash |= 0x0040; + exif->SetTag_Short ( kTIFF_ExifIFD, kTIFF_Flash, binFlash ); + } + + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "FileSource", &int32, 0 ); + if ( haveXMP && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_FileSource, 0 )) ) { + // 41728 FileSource is an "undefined" UInt8. + uns8 = (XMP_Uns8)int32; + exif->SetTag ( kTIFF_ExifIFD, kTIFF_FileSource, kTIFF_UndefinedType, 1, &uns8 ); } + + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "SceneType", &int32, 0 ); + if ( haveXMP && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_SceneType, 0 )) ) { + // 41729 SceneType is an "undefined" UInt8. + uns8 = (XMP_Uns8)int32; + exif->SetTag ( kTIFF_ExifIFD, kTIFF_SceneType, kTIFF_UndefinedType, 1, &uns8 ); + } + + // *** Deferred inject-only tags: SpatialFrequencyResponse, DeviceSettingDescription, CFAPattern - if ( tiff->xmpHadRelatedSoundFile || xmp.DoesPropertyExist ( kXMP_NS_EXIF, "RelatedSoundFile" ) ) { - ExportSingleTIFF_ASCII ( xmp, kXMP_NS_EXIF, "RelatedSoundFile", - tiff, kTIFF_ExifIFD, kTIFF_RelatedSoundFile ); + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "GPSVersionID", &xmpValue, 0 ); // This is inject-only. + if ( haveXMP && (xmpValue.size() == 7) && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, 0 )) ) { + char gpsID[4]; // 0 GPSVersionID is 4 UInt8 bytes and mapped in XMP as "n.n.n.n". + gpsID[0] = xmpValue[0]; + gpsID[1] = xmpValue[2]; + gpsID[2] = xmpValue[4]; + gpsID[3] = xmpValue[6]; + exif->SetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, kTIFF_ByteType, 4, &gpsID[0] ); } + + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSLatitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSLatitude ); + + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSLongitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSLongitude ); + + ExportTIFF_GPSTimeStamp ( *xmp, kXMP_NS_EXIF, "GPSTimeStamp", exif ); - if ( xmp.DoesPropertyExist ( kXMP_NS_EXIF, "GPSLatitude" ) ) { - ExportTIFF_GPSCoordinate ( xmp, kXMP_NS_EXIF, "GPSLatitude", tiff, kTIFF_GPSInfoIFD, kTIFF_GPSLatitude ); + // The following GPS tags are inject-only. + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "GPSDestLatitude" ); + if ( haveXMP && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLatitude, 0 )) ) { + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSDestLatitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSDestLatitude ); + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "GPSDestLongitude" ); + if ( haveXMP && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLongitude, 0 )) ) { + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSDestLongitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSDestLongitude ); } - if ( xmp.DoesPropertyExist ( kXMP_NS_EXIF, "GPSLongitude" ) ) { - ExportTIFF_GPSCoordinate ( xmp, kXMP_NS_EXIF, "GPSLongitude", tiff, kTIFF_GPSInfoIFD, kTIFF_GPSLongitude ); + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "GPSProcessingMethod", &xmpValue, 0 ); + if ( haveXMP && (! xmpValue.empty()) && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSProcessingMethod, 0 )) ) { + // 27 GPSProcessingMethod is a string with explicit encoding. + ExportTIFF_EncodedString ( *xmp, kXMP_NS_EXIF, "GPSProcessingMethod", exif, kTIFF_GPSInfoIFD, kTIFF_GPSProcessingMethod ); } -} // ReconcileUtils::ExportExif; + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "GPSAreaInformation", &xmpValue, 0 ); + if ( haveXMP && (! xmpValue.empty()) && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation, 0 )) ) { + // 28 GPSAreaInformation is a string with explicit encoding. + ExportTIFF_EncodedString ( *xmp, kXMP_NS_EXIF, "GPSAreaInformation", exif, kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation ); + } + +} // PhotoDataUtils::ExportExif diff --git a/source/XMPFiles/FormatSupport/Reconcile_Impl.cpp b/source/XMPFiles/FormatSupport/Reconcile_Impl.cpp index 1f06083..7d27769 100644 --- a/source/XMPFiles/FormatSupport/Reconcile_Impl.cpp +++ b/source/XMPFiles/FormatSupport/Reconcile_Impl.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -20,26 +20,26 @@ // ================================================================================================= /// \file Reconcile_Impl.cpp -/// \brief Implementation utilities for the legacy metadata reconciliation support. +/// \brief Implementation utilities for the photo metadata reconciliation support. /// // ================================================================================================= // ================================================================================================= -// IsASCII -// ======= +// ReconcileUtils::IsASCII +// ======================= // // See if a string is 7 bit ASCII. -static inline bool IsASCII ( const void * strPtr, size_t strLen ) +bool ReconcileUtils::IsASCII ( const void * textPtr, size_t textLen ) { - for ( const XMP_Uns8 * strPos = (XMP_Uns8*)strPtr; strLen > 0; --strLen, ++strPos ) { - if ( *strPos >= 0x80 ) return false; + for ( const XMP_Uns8 * textPos = (XMP_Uns8*)textPtr; textLen > 0; --textLen, ++textPos ) { + if ( *textPos >= 0x80 ) return false; } return true; -} // IsASCII +} // ReconcileUtils::IsASCII // ================================================================================================= // ReconcileUtils::IsUTF8 @@ -49,16 +49,16 @@ static inline bool IsASCII ( const void * strPtr, size_t strLen ) // strings. We don't use CodePoint_from_UTF8_Multi in UnicodeConversions because it throws an // exception for non-Unicode and we don't need to actually compute the code points. -bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) +bool ReconcileUtils::IsUTF8 ( const void * textPtr, size_t textLen ) { - const XMP_Uns8 * utf8Pos = (XMP_Uns8*)utf8Ptr; - const XMP_Uns8 * utf8End = utf8Pos + utf8Len; + const XMP_Uns8 * textPos = (XMP_Uns8*)textPtr; + const XMP_Uns8 * textEnd = textPos + textLen; - while ( utf8Pos < utf8End ) { + while ( textPos < textEnd ) { - if ( *utf8Pos < 0x80 ) { + if ( *textPos < 0x80 ) { - ++utf8Pos; // ASCII is UTF-8, tolerate nuls. + ++textPos; // ASCII is UTF-8, tolerate nuls. } else { @@ -68,26 +68,26 @@ bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) #if 0 // *** This might be a more effcient way to count the bytes. static XMP_Uns8 kByteCounts[16] = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 3, 4 }; - size_t bytesNeeded = kByteCounts [ *utf8Pos >> 4 ]; - if ( (bytesNeeded < 2) || ((bytesNeeded == 4) && ((*utf8Pos & 0x08) != 0)) ) return false; - if ( (utf8Pos + bytesNeeded) > utf8End ) return false; + size_t bytesNeeded = kByteCounts [ *textPos >> 4 ]; + if ( (bytesNeeded < 2) || ((bytesNeeded == 4) && ((*textPos & 0x08) != 0)) ) return false; + if ( (textPos + bytesNeeded) > textEnd ) return false; #endif size_t bytesNeeded = 0; // Count the high order 1 bits in the first byte. - for ( XMP_Uns8 temp = *utf8Pos; temp > 0x7F; temp = temp << 1 ) ++bytesNeeded; + for ( XMP_Uns8 temp = *textPos; temp > 0x7F; temp = temp << 1 ) ++bytesNeeded; // *** Consider CPU-specific assembly inline, e.g. cntlzw on PowerPC. - if ( (bytesNeeded < 2) || (bytesNeeded > 4) || ((utf8Pos+bytesNeeded) > utf8End) ) return false; + if ( (bytesNeeded < 2) || (bytesNeeded > 4) || ((textPos+bytesNeeded) > textEnd) ) return false; - for ( --bytesNeeded, ++utf8Pos; bytesNeeded > 0; --bytesNeeded, ++utf8Pos ) { - if ( (*utf8Pos >> 6) != 2 ) return false; + for ( --bytesNeeded, ++textPos; bytesNeeded > 0; --bytesNeeded, ++textPos ) { + if ( (*textPos >> 6) != 2 ) return false; } } } - return true; + return true; // ! Returns true for empty strings. } // ReconcileUtils::IsUTF8 @@ -97,8 +97,7 @@ bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) #if XMP_WinBuild - static void UTF8ToWinEncoding ( UINT codePage, - const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ) + void ReconcileUtils::UTF8ToWinEncoding ( UINT codePage, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ) { std::string utf16; // WideCharToMultiByte wants native UTF-16. @@ -117,11 +116,15 @@ bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) #elif XMP_MacBuild - static void UTF8ToMacEncoding ( TextEncoding & destEncoding, - const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ) + void ReconcileUtils::UTF8ToMacEncoding ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ) { OSStatus err; + TextEncoding destEncoding; + if ( macLang == langUnspecified ) macLang = kTextLanguageDontCare; + err = UpgradeScriptInfoToTextEncoding ( macScript, macLang, kTextRegionDontCare, 0, &destEncoding ); + if ( err != noErr ) XMP_Throw ( "UpgradeScriptInfoToTextEncoding failed", kXMPErr_ExternalFailure ); + UnicodeMapping mappingInfo; mappingInfo.mappingVersion = kUnicodeUseLatestMapping; mappingInfo.otherEncoding = GetTextEncodingBase ( destEncoding ); @@ -167,8 +170,7 @@ bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) #elif XMP_UNIXBuild - // ! Does not exist, must not be called, for Generic UNIX builds. It is not clear at this time - // ! what notion of local encoding should be used for generic UNIX, especially in a server product. + // ! Does not exist, must not be called, for Generic UNIX builds. #endif @@ -176,17 +178,13 @@ bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) // ReconcileUtils::UTF8ToLocal // =========================== -#if ! XMP_UNIXBuild -// ! Does not exist, must not be called, for Generic UNIX builds. It is not clear at this time -// ! what notion of local encoding should be used for generic UNIX, especially in a server product. - void ReconcileUtils::UTF8ToLocal ( const void * _utf8Ptr, size_t utf8Len, std::string * local ) { const XMP_Uns8* utf8Ptr = (XMP_Uns8*)_utf8Ptr; local->erase(); - if ( IsASCII ( utf8Ptr, utf8Len ) ) { + if ( ReconcileUtils::IsASCII ( utf8Ptr, utf8Len ) ) { local->assign ( (const char *)utf8Ptr, utf8Len ); return; } @@ -197,76 +195,87 @@ void ReconcileUtils::UTF8ToLocal ( const void * _utf8Ptr, size_t utf8Len, std::s #elif XMP_MacBuild - OSStatus err; - - TextEncoding localEncoding; - err = UpgradeScriptInfoToTextEncoding ( smSystemScript, - kTextLanguageDontCare, kTextRegionDontCare, 0, &localEncoding ); - if ( err != noErr ) XMP_Throw ( "UpgradeScriptInfoToTextEncoding failed", kXMPErr_ExternalFailure ); - - UTF8ToMacEncoding ( localEncoding, utf8Ptr, utf8Len, local ); + UTF8ToMacEncoding ( smSystemScript, kTextLanguageDontCare, utf8Ptr, utf8Len, local ); #elif XMP_UNIXBuild - #error "No generic UNIX implementation" + XMP_Throw ( "Generic UNIX does not have conversions between local and Unicode", kXMPErr_Unavailable ); #endif } // ReconcileUtils::UTF8ToLocal -#endif - // ================================================================================================= // ReconcileUtils::UTF8ToLatin1 // ============================ -// -// Actually to the Windows code page 1252 superset of 8859-1. - -#if ! XMP_UNIXBuild -// ! Does not exist, must not be called, for Generic UNIX builds. At some point we could consider -// ! creating our own private implementation. So far only needed for the ID3 legacy in MP3 files. void ReconcileUtils::UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std::string * latin1 ) { const XMP_Uns8* utf8Ptr = (XMP_Uns8*)_utf8Ptr; + const XMP_Uns8* utf8End = utf8Ptr + utf8Len; latin1->erase(); + latin1->reserve ( utf8Len ); // As good a guess as any, at least enough, exact for ASCII. - if ( IsASCII ( utf8Ptr, utf8Len ) ) { - latin1->assign ( (const char *)utf8Ptr, utf8Len ); - return; - } - - #if XMP_WinBuild - - UTF8ToWinEncoding ( 1252, utf8Ptr, utf8Len, latin1 ); + bool inBadRun = false; - #elif XMP_MacBuild - - TextEncoding latin1Encoding; - latin1Encoding = CreateTextEncoding ( kTextEncodingWindowsLatin1, - kTextEncodingDefaultVariant, kTextEncodingDefaultFormat ); - - UTF8ToMacEncoding ( latin1Encoding, utf8Ptr, utf8Len, latin1 ); + while ( utf8Ptr < utf8End ) { - #elif XMP_UNIXBuild + if ( *utf8Ptr <= 0x7F ) { + + (*latin1) += (char)*utf8Ptr; // Have an ASCII character. + inBadRun = false; + ++utf8Ptr; + + } else if ( utf8Ptr == (utf8End - 1) ) { + + inBadRun = false; + ++utf8Ptr; // Ignore a bad end to the UTF-8. + + } else { + + XMP_Assert ( (utf8End - utf8Ptr) >= 2 ); + XMP_Uns16 ch16 = GetUns16BE ( utf8Ptr ); // A Latin-1 80..FF is 2 UTF-8 bytes. + + if ( (0xC280 <= ch16) && (ch16 <= 0xC2BF) ) { + + (*latin1) += (char)(ch16 & 0xFF); // UTF-8 C280..C2BF are Latin-1 80..BF. + inBadRun = false; + utf8Ptr += 2; + + } else if ( (0xC380 <= ch16) && (ch16 <= 0xC3BF) ) { + + (*latin1) += (char)((ch16 & 0xFF) + 0x40); // UTF-8 C380..C3BF are Latin-1 C0..FF. + inBadRun = false; + utf8Ptr += 2; + + } else { + + if ( ! inBadRun ) { + inBadRun = true; + (*latin1) += "(?)"; // Mark the run of out of scope UTF-8. + } + + ++utf8Ptr; // Skip the presumably well-formed UTF-8 character. + while ( (utf8Ptr < utf8End) && ((*utf8Ptr & 0xC0) == 0x80) ) ++utf8Ptr; + + } + + } - #error "No generic UNIX implementation" + } - #endif + XMP_Assert ( utf8Ptr == utf8End ); } // ReconcileUtils::UTF8ToLatin1 -#endif - // ================================================================================================= // HostEncodingToUTF8 // ================== #if XMP_WinBuild - static void WinEncodingToUTF8 ( UINT codePage, - const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ) + void ReconcileUtils::WinEncodingToUTF8 ( UINT codePage, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ) { int utf16Len = MultiByteToWideChar ( codePage, 0, (LPCSTR)hostPtr, (int)hostLen, 0, 0 ); @@ -279,11 +288,15 @@ void ReconcileUtils::UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std:: #elif XMP_MacBuild - static void MacEncodingToUTF8 ( TextEncoding & srcEncoding, - const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ) + void ReconcileUtils::MacEncodingToUTF8 ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ) { OSStatus err; + TextEncoding srcEncoding; + if ( macLang == langUnspecified ) macLang = kTextLanguageDontCare; + err = UpgradeScriptInfoToTextEncoding ( macScript, macLang, kTextRegionDontCare, 0, &srcEncoding ); + if ( err != noErr ) XMP_Throw ( "UpgradeScriptInfoToTextEncoding failed", kXMPErr_ExternalFailure ); + UnicodeMapping mappingInfo; mappingInfo.mappingVersion = kUnicodeUseLatestMapping; mappingInfo.otherEncoding = GetTextEncodingBase ( srcEncoding ); @@ -327,8 +340,7 @@ void ReconcileUtils::UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std:: #elif XMP_UNIXBuild - // ! Does not exist, must not be called, for Generic UNIX builds. It is not clear at this time - // ! what notion of local encoding should be used for generic UNIX, especially in a server product. + // ! Does not exist, must not be called, for Generic UNIX builds. #endif @@ -336,17 +348,13 @@ void ReconcileUtils::UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std:: // ReconcileUtils::LocalToUTF8 // =========================== -#if ! XMP_UNIXBuild -// ! Does not exist, must not be called, for Generic UNIX builds. It is not clear at this time -// ! what notion of local encoding should be used for generic UNIX, especially in a server product. - void ReconcileUtils::LocalToUTF8 ( const void * _localPtr, size_t localLen, std::string * utf8 ) { const XMP_Uns8* localPtr = (XMP_Uns8*)_localPtr; utf8->erase(); - if ( IsASCII ( localPtr, localLen ) ) { + if ( ReconcileUtils::IsASCII ( localPtr, localLen ) ) { utf8->assign ( (const char *)localPtr, localLen ); return; } @@ -357,63 +365,42 @@ void ReconcileUtils::LocalToUTF8 ( const void * _localPtr, size_t localLen, std: #elif XMP_MacBuild - OSStatus err; - - TextEncoding localEncoding; - err = UpgradeScriptInfoToTextEncoding ( smSystemScript, kTextLanguageDontCare, kTextRegionDontCare, 0, &localEncoding ); - if ( err != noErr ) XMP_Throw ( "UpgradeScriptInfoToTextEncoding failed", kXMPErr_ExternalFailure ); - - MacEncodingToUTF8 ( localEncoding, localPtr, localLen, utf8 ); + MacEncodingToUTF8 ( smSystemScript, kTextLanguageDontCare, localPtr, localLen, utf8 ); #elif XMP_UNIXBuild - #error "No generic UNIX implementation" + XMP_Throw ( "Generic UNIX does not have conversions between local and Unicode", kXMPErr_Unavailable ); #endif } // ReconcileUtils::LocalToUTF8 -#endif - // ================================================================================================= // ReconcileUtils::Latin1ToUTF8 // ============================ -// -// Actually from the Windows code page 1252 superset of 8859-1. - -#if ! XMP_UNIXBuild -// ! Does not exist, must not be called, for Generic UNIX builds. At some point we could consider -// ! creating our own private implementation. So far only needed for the ID3 legacy in MP3 files. void ReconcileUtils::Latin1ToUTF8 ( const void * _latin1Ptr, size_t latin1Len, std::string * utf8 ) { const XMP_Uns8* latin1Ptr = (XMP_Uns8*)_latin1Ptr; + const XMP_Uns8* latin1End = latin1Ptr + latin1Len; utf8->erase(); + utf8->reserve ( latin1Len ); // As good a guess as any, exact for ASCII. - if ( IsASCII ( latin1Ptr, latin1Len ) ) { - utf8->assign ( (const char *)latin1Ptr, latin1Len ); - return; - } - - #if XMP_WinBuild + for ( ; latin1Ptr < latin1End; ++latin1Ptr ) { - WinEncodingToUTF8 ( 1252, latin1Ptr, latin1Len, utf8 ); + XMP_Uns8 ch8 = *latin1Ptr; - #elif XMP_MacBuild - - TextEncoding latin1Encoding; - latin1Encoding = CreateTextEncoding ( kTextEncodingWindowsLatin1, - kTextEncodingDefaultVariant, kTextEncodingDefaultFormat ); - - MacEncodingToUTF8 ( latin1Encoding, latin1Ptr, latin1Len, utf8 ); - - #elif XMP_UNIXBuild + if ( ch8 <= 0x7F ) { + (*utf8) += (char)ch8; // Have an ASCII character. + } else if ( ch8 <= 0xBF ) { + (*utf8) += 0xC2; // Latin-1 80..BF are UTF-8 C280..C2BF. + (*utf8) += (char)ch8; + } else { + (*utf8) += 0xC3; // Latin-1 C0..FF are UTF-8 C380..C3BF. + (*utf8) += (char)(ch8 - 0x40); + } - #error "No generic UNIX implementation" + } - #endif - } // ReconcileUtils::Latin1ToUTF8 - -#endif diff --git a/source/XMPFiles/FormatSupport/Reconcile_Impl.hpp b/source/XMPFiles/FormatSupport/Reconcile_Impl.hpp index 5fe59a7..19ea865 100644 --- a/source/XMPFiles/FormatSupport/Reconcile_Impl.hpp +++ b/source/XMPFiles/FormatSupport/Reconcile_Impl.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -30,37 +30,66 @@ enum { }; namespace ReconcileUtils { - + + // *** These ought to be with the Unicode conversions. + static const char * kHexDigits = "0123456789ABCDEF"; + + bool IsASCII ( const void * _textPtr, size_t textLen ); + bool IsUTF8 ( const void * _textPtr, size_t textLen ); + + void UTF8ToLocal ( const void * _utf8Ptr, size_t utf8Len, std::string * local ); + void UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std::string * latin1 ); + void LocalToUTF8 ( const void * _localPtr, size_t localLen, std::string * utf8 ); + void Latin1ToUTF8 ( const void * _latin1Ptr, size_t latin1Len, std::string * utf8 ); - bool IsUTF8 ( const void * _utf8Ptr, size_t utf8Len ); - - #if ! XMP_UNIXBuild // Remove from generic UNIX until legacy-as-local issues are resolved. - void UTF8ToLocal ( const void * _utf8Ptr, size_t utf8Len, std::string * local ); - void UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std::string * latin1 ); - void LocalToUTF8 ( const void * _localPtr, size_t localLen, std::string * utf8 ); - void Latin1ToUTF8 ( const void * _latin1Ptr, size_t latin1Len, std::string * utf8 ); - // *** These ought to be with the Unicode conversions. + #if XMP_WinBuild + void UTF8ToWinEncoding ( UINT codePage, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ); + void WinEncodingToUTF8 ( UINT codePage, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ); + #elif XMP_MacBuild + void UTF8ToMacEncoding ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ); + void MacEncodingToUTF8 ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ); #endif - int CheckIPTCDigest ( IPTC_Manager * iptc, const PSIR_Manager & psir ); - int CheckTIFFDigest ( const TIFF_Manager & tiff, const SXMPMeta & xmp ); - int CheckExifDigest ( const TIFF_Manager & tiff, const SXMPMeta & xmp ); +}; // ReconcileUtils - void SetIPTCDigest ( IPTC_Manager * iptc, PSIR_Manager * psir ); - void SetTIFFDigest ( const TIFF_Manager & tiff, SXMPMeta * xmp ); - void SetExifDigest ( const TIFF_Manager & tiff, SXMPMeta * xmp ); - - void ImportIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState ); - void ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int digestState ); - void ImportTIFF ( const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState, XMP_FileFormat srcFormat ); - void ImportExif ( const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState ); +namespace PhotoDataUtils { + + int CheckIPTCDigest ( const void * newPtr, const XMP_Uns32 newLen, const void * oldDigest ); + void SetIPTCDigest ( void * iptcPtr, XMP_Uns32 iptcLen, PSIR_Manager * psir ); + + bool GetNativeInfo ( const TIFF_Manager & exif, XMP_Uns8 ifd, XMP_Uns16 id, TIFF_Manager::TagInfo * info ); + size_t GetNativeInfo ( const IPTC_Manager & iptc, XMP_Uns8 id, int digestState, + bool haveXMP, IPTC_Manager::DataSetInfo * info ); - void ExportIPTC ( SXMPMeta * xmp, IPTC_Manager * iptc ); // ! Has XMP side effects! + bool IsValueDifferent ( const TIFF_Manager::TagInfo & exifInfo, + const std::string & xmpValue, std::string * exifValue ); + bool IsValueDifferent ( const IPTC_Manager & newIPTC, const IPTC_Manager & oldIPTC, XMP_Uns8 id ); + + void ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int iptcDigestState ); + + void Import2WayIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ); + void Import2WayExif ( const TIFF_Manager & exif, SXMPMeta * xmp, int iptcDigestState ); + + void Import3WayItems ( const TIFF_Manager & exif, const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ); + void ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir ); - void ExportTIFF ( const SXMPMeta & xmp, TIFF_Manager * tiff ); - void ExportExif ( const SXMPMeta & xmp, TIFF_Manager * tiff ); + void ExportIPTC ( const SXMPMeta & xmp, IPTC_Manager * iptc ); + void ExportExif ( SXMPMeta * xmp, TIFF_Manager * exif ); + + // These need to be exposed for use in Import3WayItem: -}; // ReconcileUtils + void ImportIPTC_Simple ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ); + + void ImportIPTC_LangAlt ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ); + + void ImportIPTC_Array ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ); + + void ImportIPTC_Date ( XMP_Uns8 dateID, const IPTC_Manager & iptc, SXMPMeta * xmp ); + +}; // PhotoDataUtils #endif // __Reconcile_Impl_hpp__ diff --git a/source/XMPFiles/FormatSupport/SWF_Support.cpp b/source/XMPFiles/FormatSupport/SWF_Support.cpp index 0a22cf0..d9b8115 100644 --- a/source/XMPFiles/FormatSupport/SWF_Support.cpp +++ b/source/XMPFiles/FormatSupport/SWF_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -16,7 +16,7 @@ namespace SWF_Support // ============================================================================================= - int CalcHeaderSize ( IO::InputStream* inputStream ) + static int CalcHeaderSize ( IO::InputStream* inputStream ) { int size = 0; @@ -44,7 +44,7 @@ namespace SWF_Support // ============================================================================================= - unsigned long CheckTag ( IO::InputStream* inputStream, TagState& inOutTagState, TagData& inOutTagData ) + static unsigned long CheckTag ( IO::InputStream* inputStream, TagState& inOutTagState, TagData& inOutTagData ) { unsigned long ret = 0; XMP_Uns8 * buffer = 0; diff --git a/source/XMPFiles/FormatSupport/SWF_Support.hpp b/source/XMPFiles/FormatSupport/SWF_Support.hpp index ca301b9..db20ab2 100644 --- a/source/XMPFiles/FormatSupport/SWF_Support.hpp +++ b/source/XMPFiles/FormatSupport/SWF_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/TIFF_FileWriter.cpp b/source/XMPFiles/FormatSupport/TIFF_FileWriter.cpp index 05372b8..3eb6eb2 100644 --- a/source/XMPFiles/FormatSupport/TIFF_FileWriter.cpp +++ b/source/XMPFiles/FormatSupport/TIFF_FileWriter.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -33,15 +33,15 @@ TIFF_FileWriter::TIFF_FileWriter() : changed(false), legacyDeleted(false), memPa { XMP_Uns8 bogusTIFF [kEmptyTIFFLength]; - + bogusTIFF[0] = 0x4D; bogusTIFF[1] = 0x4D; bogusTIFF[2] = 0x00; bogusTIFF[3] = 0x2A; bogusTIFF[4] = bogusTIFF[5] = bogusTIFF[6] = bogusTIFF[7] = 0x00; - + (void) this->CheckTIFFHeader ( bogusTIFF, sizeof ( bogusTIFF ) ); - + } // TIFF_FileWriter::TIFF_FileWriter // ================================================================================================= @@ -52,7 +52,6 @@ TIFF_FileWriter::~TIFF_FileWriter() { XMP_Assert ( ! (this->memParsed && this->fileParsed) ); - if ( this->fileParsed && (this->jpegTNailPtr != 0) ) free ( this->jpegTNailPtr ); if ( this->ownedStream ) { XMP_Assert ( this->memStream != 0 ); free ( this->memStream ); @@ -117,17 +116,17 @@ const TIFF_FileWriter::InternalTagInfo* TIFF_FileWriter::FindTagInIFD ( XMP_Uns8 // TIFF_FileWriter::GetIFD // ======================= -bool TIFF_FileWriter::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const +bool TIFF_FileWriter::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const { if ( ifd > kTIFF_LastRealIFD ) XMP_Throw ( "Invalid IFD number", kXMPErr_BadParam ); const InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap; InternalTagMap::const_iterator tagPos = currIFD.begin(); InternalTagMap::const_iterator tagEnd = currIFD.end(); - + if ( ifdMap != 0 ) ifdMap->clear(); if ( tagPos == tagEnd ) return false; // Empty IFD. - + if ( ifdMap != 0 ) { for ( ; tagPos != tagEnd; ++tagPos ) { const InternalTagInfo& intInfo = tagPos->second; @@ -135,7 +134,7 @@ bool TIFF_FileWriter::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const (*ifdMap)[intInfo.id] = extInfo; } } - + return true; } // TIFF_FileWriter::GetIFD @@ -148,20 +147,20 @@ XMP_Uns32 TIFF_FileWriter::GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const { const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( (thisTag == 0) || (thisTag->origDataLen == 0) ) return 0; - + return thisTag->origDataOffset; - + } // TIFF_FileWriter::GetValueOffset // ================================================================================================= // TIFF_FileWriter::GetTag // ======================= -bool TIFF_FileWriter::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const +bool TIFF_FileWriter::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const { const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; - + if ( info != 0 ) { info->id = thisTag->id; @@ -171,21 +170,21 @@ bool TIFF_FileWriter::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const info->dataPtr = (const void*)(thisTag->dataPtr); } - + return true; - + } // TIFF_FileWriter::GetTag // ================================================================================================= // TIFF_FileWriter::SetTag // ======================= -void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* clientPtr ) +void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* clientPtr ) { if ( (type < kTIFF_ByteType) || (type > kTIFF_LastType) ) XMP_Throw ( "Invalid TIFF tag type", kXMPErr_BadParam ); size_t typeSize = kTIFF_TypeSizes[type]; size_t fullSize = count * typeSize; - + ifd = PickIFD ( ifd, id ); InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap; @@ -210,7 +209,7 @@ void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_U } tagPtr->FreeData(); // Release any existing data allocation. - + tagPtr->type = type; // These might be changing also. tagPtr->count = count; @@ -218,7 +217,7 @@ void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_U tagPtr->changed = true; tagPtr->dataLen = (XMP_Uns32)fullSize; - + if ( fullSize <= 4 ) { // The data is less than 4 bytes, store it in the smallValue field using native endianness. tagPtr->dataPtr = (XMP_Uns8*) &tagPtr->smallValue; @@ -228,7 +227,7 @@ void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_U if ( tagPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); } memcpy ( tagPtr->dataPtr, clientPtr, fullSize ); // AUDIT: Safe, space guaranteed to be fullSize. - + if ( ! this->nativeEndian ) { if ( typeSize == 2 ) { XMP_Uns16* flipPtr = (XMP_Uns16*) tagPtr->dataPtr; @@ -241,7 +240,7 @@ void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_U for ( XMP_Uns32 i = 0; i < count; ++i ) Flip8 ( flipPtr[i] ); } } - + this->containedIFDs[ifd].changed = true; this->changed = true; @@ -251,11 +250,11 @@ void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_U // TIFF_FileWriter::DeleteTag // ========================== -void TIFF_FileWriter::DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) +void TIFF_FileWriter::DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) { ifd = PickIFD ( ifd, id ); InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap; - + InternalTagMap::iterator tagPos = currIFD.find ( id ); if ( tagPos == currIFD.end() ) return; // ! Don't set the changed flags if the tag didn't exist. @@ -270,15 +269,15 @@ void TIFF_FileWriter::DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) // TIFF_FileWriter::GetTag_Integer // =============================== -bool TIFF_FileWriter::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const +bool TIFF_FileWriter::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const { const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( thisTag->count != 1 ) return false; - + static XMP_Uns32 voidValue; if ( data == 0 ) data = &voidValue; - + if ( thisTag->type == kTIFF_ShortType ) { *data = this->GetUns16 ( thisTag->dataPtr ); } else if ( thisTag->type == kTIFF_LongType ) { @@ -286,7 +285,7 @@ bool TIFF_FileWriter::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* da } else { return false; } - + return true; } // TIFF_FileWriter::GetTag_Integer @@ -300,7 +299,7 @@ bool TIFF_FileWriter::GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_ByteType) || (thisTag->dataLen != 1) ) return false; - + if ( data != 0 ) *data = *thisTag->dataPtr; return true; @@ -315,7 +314,7 @@ bool TIFF_FileWriter::GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SByteType) || (thisTag->dataLen != 1) ) return false; - + if ( data != 0 ) *data = *thisTag->dataPtr; return true; @@ -330,7 +329,7 @@ bool TIFF_FileWriter::GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_ShortType) || (thisTag->dataLen != 2) ) return false; - + if ( data != 0 ) *data = this->GetUns16 ( thisTag->dataPtr ); return true; @@ -345,7 +344,7 @@ bool TIFF_FileWriter::GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* dat const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SShortType) || (thisTag->dataLen != 2) ) return false; - + if ( data != 0 ) *data = (XMP_Int16) this->GetUns16 ( thisTag->dataPtr ); return true; @@ -360,7 +359,7 @@ bool TIFF_FileWriter::GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_LongType) || (thisTag->dataLen != 4) ) return false; - + if ( data != 0 ) *data = this->GetUns32 ( thisTag->dataPtr ); return true; @@ -375,7 +374,7 @@ bool TIFF_FileWriter::GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SLongType) || (thisTag->dataLen != 4) ) return false; - + if ( data != 0 ) *data = (XMP_Int32) this->GetUns32 ( thisTag->dataPtr ); return true; @@ -390,13 +389,13 @@ bool TIFF_FileWriter::GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* da const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false; if ( (thisTag->type != kTIFF_RationalType) || (thisTag->dataLen != 8) ) return false; - + if ( data != 0 ) { XMP_Uns32* dataPtr = (XMP_Uns32*)thisTag->dataPtr; data->num = this->GetUns32 ( dataPtr ); data->denom = this->GetUns32 ( dataPtr+1 ); } - + return true; } // TIFF_FileWriter::GetTag_Rational @@ -410,13 +409,13 @@ bool TIFF_FileWriter::GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false; if ( (thisTag->type != kTIFF_SRationalType) || (thisTag->dataLen != 8) ) return false; - + if ( data != 0 ) { XMP_Uns32* dataPtr = (XMP_Uns32*)thisTag->dataPtr; data->num = (XMP_Int32) this->GetUns32 ( dataPtr ); data->denom = (XMP_Int32) this->GetUns32 ( dataPtr+1 ); } - + return true; } // TIFF_FileWriter::GetTag_SRational @@ -430,8 +429,8 @@ bool TIFF_FileWriter::GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) c const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_FloatType) || (thisTag->dataLen != 4) ) return false; - - if ( data != 0 ) *data = this->GetFloat ( thisTag->dataPtr ); + + if ( data != 0 ) *data = this->GetFloat ( thisTag->dataPtr ); return true; } // TIFF_FileWriter::GetTag_Float @@ -445,8 +444,8 @@ bool TIFF_FileWriter::GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false; if ( (thisTag->type != kTIFF_DoubleType) || (thisTag->dataLen != 8) ) return false; - - if ( data != 0 ) *data = this->GetDouble ( thisTag->dataPtr ); + + if ( data != 0 ) *data = this->GetDouble ( thisTag->dataPtr ); return true; } // TIFF_FileWriter::GetTag_Double @@ -461,10 +460,10 @@ bool TIFF_FileWriter::GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* if ( thisTag == 0 ) return false; if ( (thisTag->dataLen > 4) && (thisTag->dataPtr == 0) ) return false; if ( thisTag->type != kTIFF_ASCIIType ) return false; - + if ( dataPtr != 0 ) *dataPtr = (XMP_StringPtr)thisTag->dataPtr; if ( dataLen != 0 ) *dataLen = thisTag->dataLen; - + return true; } // TIFF_FileWriter::GetTag_ASCII @@ -478,9 +477,9 @@ bool TIFF_FileWriter::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::st const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( thisTag->type != kTIFF_UndefinedType ) return false; - + if ( utf8Str == 0 ) return true; // Return true if the converted string is not wanted. - + bool ok = this->DecodeString ( thisTag->dataPtr, thisTag->dataLen, utf8Str ); return ok; @@ -492,8 +491,10 @@ bool TIFF_FileWriter::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::st void TIFF_FileWriter::SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ) { + std::string encodedStr; - XMP_Throw ( "Not yet implemented", kXMPErr_Unimplemented ); + this->EncodeString ( utf8Str, encoding, &encodedStr ); + this->SetTag ( ifd, id, kTIFF_UndefinedType, (XMP_Uns32)encodedStr.size(), encodedStr.c_str() ); } // TIFF_FileWriter::SetTag_EncodedString @@ -506,22 +507,22 @@ bool TIFF_FileWriter::IsLegacyChanged() if ( ! this->changed ) return false; if ( this->legacyDeleted ) return true; - + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; if ( ! thisIFD.changed ) continue; - + InternalTagMap::iterator tagPos; InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); - + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { InternalTagInfo & thisTag = tagPos->second; if ( thisTag.changed && (thisTag.id != kTIFF_XMP) ) return true; } } - + return false; // Can get here if the XMP tag is the only one changed. } // TIFF_FileWriter::IsLegacyChanged @@ -530,14 +531,14 @@ bool TIFF_FileWriter::IsLegacyChanged() // TIFF_FileWriter::ParseMemoryStream // ================================== -void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) +void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) { this->DeleteExistingInfo(); this->memParsed = true; if ( length == 0 ) return; // Allocate space for the full in-memory stream and copy it. - + if ( ! copyData ) { XMP_Assert ( ! this->ownedStream ); this->memStream = (XMP_Uns8*) data; @@ -551,11 +552,10 @@ void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bo this->tiffLength = length; // Find and process the primary, Exif, GPS, and Interoperability IFDs. - + XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( this->memStream, length ); - XMP_Uns32 tnailIFDOffset = 0; - - if ( primaryIFDOffset != 0 ) tnailIFDOffset = this->ProcessMemoryIFD ( primaryIFDOffset, kTIFF_PrimaryIFD ); + + if ( primaryIFDOffset != 0 ) (void) this->ProcessMemoryIFD ( primaryIFDOffset, kTIFF_PrimaryIFD ); const InternalTagInfo* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (exifIFDTag->dataLen == 4) ) { @@ -574,19 +574,7 @@ void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bo XMP_Uns32 interopOffset = this->GetUns32 ( interopIFDTag->dataPtr ); (void) this->ProcessMemoryIFD ( interopOffset, kTIFF_InteropIFD ); } - - // Process the thumbnail IFD. We only do this for Exif-compliant TIFF streams. Extract the - // JPEG thumbnail image pointer (tag 513) for later use by GetTNailInfo. - - if ( (tnailIFDOffset != 0) && (! this->containedIFDs[kTIFF_ExifIFD].tagMap.empty()) ) { - (void) this->ProcessMemoryIFD ( tnailIFDOffset, kTIFF_TNailIFD ); - const InternalTagInfo* jpegInfo = FindTagInIFD ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat ); - if ( jpegInfo != 0 ) { - XMP_Uns32 tnailImageOffset = this->GetUns32 ( jpegInfo->dataPtr ); - this->jpegTNailPtr = (XMP_Uns8*)this->memStream + tnailImageOffset; - } - } - + #if 0 { printf ( "\nExiting TIFF_FileWriter::ParseMemoryStream\n" ); @@ -616,27 +604,27 @@ void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bo XMP_Uns32 TIFF_FileWriter::ProcessMemoryIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) { InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); - + if ( (ifdOffset < 8) || (ifdOffset > (this->tiffLength - kEmptyIFDLength)) ) { XMP_Throw ( "Bad IFD offset", kXMPErr_BadTIFF ); } - + XMP_Uns8* ifdPtr = this->memStream + ifdOffset; XMP_Uns16 tagCount = this->GetUns16 ( ifdPtr ); RawIFDEntry* ifdEntries = (RawIFDEntry*)(ifdPtr+2); if ( tagCount >= 0x8000 ) XMP_Throw ( "Outrageous IFD count", kXMPErr_BadTIFF ); if ( (ifdOffset + 2 + tagCount*12 + 4) > this->tiffLength ) XMP_Throw ( "Out of bounds IFD", kXMPErr_BadTIFF ); - + ifdInfo.origIFDOffset = ifdOffset; ifdInfo.origCount = tagCount; - + for ( size_t i = 0; i < tagCount; ++i ) { - + RawIFDEntry* rawTag = &ifdEntries[i]; XMP_Uns16 tagType = this->GetUns16 ( &rawTag->type ); if ( (tagType < kTIFF_ByteType) || (tagType > kTIFF_LastType) ) continue; // Bad type, skip this tag. - + XMP_Uns16 tagID = this->GetUns16 ( &rawTag->id ); XMP_Uns32 tagCount = this->GetUns32 ( &rawTag->count ); @@ -654,64 +642,17 @@ XMP_Uns32 TIFF_FileWriter::ProcessMemoryIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd // printf ( "FW_ProcessMemoryIFD tag %d large value @ %.8X\n", mapTag.id, mapTag.dataPtr ); } mapTag.dataPtr = this->memStream + mapTag.origDataOffset; - + } - + ifdPtr += (2 + tagCount*12); ifdInfo.origNextIFD = this->GetUns32 ( ifdPtr ); - + return ifdInfo.origNextIFD; } // TIFF_FileWriter::ProcessMemoryIFD // ================================================================================================= -// CaptureJPEGTNail -// ================ -// -// Capture the JPEG image stream for an Exif compressed thumbnail. - -static XMP_Uns8* CaptureJPEGTNail ( LFA_FileRef fileRef, IOBuffer* ioBuf, const TIFF_Manager& tiff ) -{ - bool ok; - XMP_Uns8* jpegPtr = 0; - XMP_Uns32 jpegOffset, jpegLen; - - ok = tiff.GetTag_Integer ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat, &jpegOffset ); - if ( ok ) ok = tiff.GetTag_Integer ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormatLength, &jpegLen ); - if ( ! ok ) return 0; - - if ( jpegLen > 1024*1024 ) return 0; // ? XMP_Throw ( "Outrageous JPEG TNail length", kXMPErr_BadTIFF ); - - jpegPtr = (XMP_Uns8*) malloc ( jpegLen ); - if ( jpegPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); - - try { - - if ( jpegLen > kIOBufferSize ) { - // This value is bigger than the I/O buffer, read it directly and restore the file position. - LFA_Seek ( fileRef, jpegOffset, SEEK_SET ); - LFA_Read ( fileRef, jpegPtr, jpegLen, kLFA_RequireAll ); - LFA_Seek ( fileRef, (ioBuf->filePos + ioBuf->len), SEEK_SET ); - } else { - // This value can fit in the I/O buffer, so use that. - MoveToOffset ( fileRef, jpegOffset, ioBuf ); - ok = CheckFileSpace ( fileRef, ioBuf, jpegLen ); - if ( ! ok ) XMP_Throw ( "EOF in data block", kXMPErr_BadTIFF ); - memcpy ( jpegPtr, ioBuf->ptr, jpegLen ); // AUDIT: Safe, malloc'ed jpegLen bytes above. - } - - } catch ( ... ) { - - free ( jpegPtr ); - throw; - - } - - return jpegPtr; - -} // CaptureJPEGTNail - -// ================================================================================================= // TIFF_FileWriter::ParseFileStream // ================================ // @@ -720,7 +661,7 @@ static XMP_Uns8* CaptureJPEGTNail ( LFA_FileRef fileRef, IOBuffer* ioBuf, const // and all of their interesting tag values within the first 64K of the file. Well, at least before // we get around to our edit-by-append approach. -void TIFF_FileWriter::ParseFileStream ( LFA_FileRef fileRef ) +void TIFF_FileWriter::ParseFileStream ( LFA_FileRef fileRef ) { bool ok; IOBuffer ioBuf; @@ -729,17 +670,16 @@ void TIFF_FileWriter::ParseFileStream ( LFA_FileRef fileRef ) this->fileParsed = true; this->tiffLength = (XMP_Uns32) LFA_Measure ( fileRef ); if ( this->tiffLength == 0 ) return; - + // Find and process the primary, Exif, GPS, and Interoperability IFDs. - + ioBuf.filePos = LFA_Seek ( fileRef, 0, SEEK_SET ); ok = CheckFileSpace ( fileRef, &ioBuf, 8 ); if ( ! ok ) XMP_Throw ( "TIFF too small", kXMPErr_BadTIFF ); - + XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( ioBuf.ptr, this->tiffLength ); - XMP_Uns32 tnailIFDOffset = 0; - - if ( primaryIFDOffset != 0 ) tnailIFDOffset = this->ProcessFileIFD ( kTIFF_PrimaryIFD, primaryIFDOffset, fileRef, &ioBuf ); + + if ( primaryIFDOffset != 0 ) (void) this->ProcessFileIFD ( kTIFF_PrimaryIFD, primaryIFDOffset, fileRef, &ioBuf ); const InternalTagInfo* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (exifIFDTag->count == 1) ) { @@ -758,15 +698,7 @@ void TIFF_FileWriter::ParseFileStream ( LFA_FileRef fileRef ) XMP_Uns32 interopOffset = this->GetUns32 ( interopIFDTag->dataPtr ); (void) this->ProcessFileIFD ( kTIFF_InteropIFD, interopOffset, fileRef, &ioBuf ); } - - // Process the thumbnail IFD. We only do this for Exif-compliant TIFF streams. Do this after - // the others since they are often within the first 64K of the file and the thumbnail is not. - if ( (tnailIFDOffset != 0) && (! this->containedIFDs[kTIFF_ExifIFD].tagMap.empty()) ) { - (void) this->ProcessFileIFD ( kTIFF_TNailIFD, tnailIFDOffset, fileRef, &ioBuf ); - this->jpegTNailPtr = CaptureJPEGTNail ( fileRef, &ioBuf, *this ); - } - #if 0 { printf ( "\nExiting TIFF_FileWriter::ParseFileStream\n" ); @@ -793,22 +725,22 @@ void TIFF_FileWriter::ParseFileStream ( LFA_FileRef fileRef ) // TIFF_FileWriter::ProcessFileIFD // =============================== -XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, LFA_FileRef fileRef, IOBuffer* ioBuf ) +XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, LFA_FileRef fileRef, IOBuffer* ioBuf ) { InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); - + MoveToOffset ( fileRef, ifdOffset, ioBuf ); // Move to the start of the IFD. - + bool ok = CheckFileSpace ( fileRef, ioBuf, 2 ); if ( ! ok ) XMP_Throw ( "IFD count missing", kXMPErr_BadTIFF ); XMP_Uns16 tagCount = this->GetUns16 ( ioBuf->ptr ); if ( tagCount >= 0x8000 ) XMP_Throw ( "Outrageous IFD count", kXMPErr_BadTIFF ); if ( (ifdOffset + 2 + tagCount*12 + 4) > this->tiffLength ) XMP_Throw ( "Out of bounds IFD", kXMPErr_BadTIFF ); - + ifdInfo.origIFDOffset = ifdOffset; ifdInfo.origCount = tagCount; - + // --------------------------------------------------------------------------------------------- // First create all of the IFD map entries, capturing short values, and get the next IFD offset. // We're using a std::map for storage, it automatically eliminates duplicates and provides @@ -816,15 +748,15 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, L // value, following Photoshop's behavior. ioBuf->ptr += 2; // Move to the first IFD entry. - + for ( XMP_Uns16 i = 0; i < tagCount; ++i, ioBuf->ptr += 12 ) { - + if ( ! CheckFileSpace ( fileRef, ioBuf, 12 ) ) XMP_Throw ( "EOF within IFD", kXMPErr_BadTIFF ); - + RawIFDEntry* rawTag = (RawIFDEntry*)ioBuf->ptr; XMP_Uns16 tagType = this->GetUns16 ( &rawTag->type ); if ( (tagType < kTIFF_ByteType) || (tagType > kTIFF_LastType) ) continue; // Bad type, skip this tag. - + XMP_Uns16 tagID = this->GetUns16 ( &rawTag->id ); XMP_Uns32 tagCount = this->GetUns32 ( &rawTag->count ); @@ -841,37 +773,37 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, L } else { mapTag.origDataOffset = this->GetUns32 ( &rawTag->dataOrOffset ); // Extract the data offset. } - + } - + if ( ! CheckFileSpace ( fileRef, ioBuf, 4 ) ) XMP_Throw ( "EOF at next IFD offset", kXMPErr_BadTIFF ); ifdInfo.origNextIFD = this->GetUns32 ( ioBuf->ptr ); - + // --------------------------------------------------------------------------------------------- // Go back over the tag map and extract the data for large recognized tags. This is done in 2 // passes, in order to lessen the typical amount of I/O. On the first pass make sure we have at // least 32K of data following the IFD in the buffer, and extract all of the values in that // portion. This should cover an original file, or the appended values with an appended IFD. - + if ( (ioBuf->limit - ioBuf->ptr) < 32*1024 ) RefillBuffer ( fileRef, ioBuf ); - + InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); - + const XMP_Uns16* knownTagPtr = sKnownTags[ifd]; // Points into the ordered recognized tag list. - + XMP_Uns32 bufBegin = (XMP_Uns32)ioBuf->filePos; // TIFF stream bounds for the current buffer. XMP_Uns32 bufEnd = bufBegin + (XMP_Uns32)ioBuf->len; - + for ( ; tagPos != tagEnd; ++tagPos ) { - + InternalTagInfo* currTag = &tagPos->second; if ( currTag->dataLen <= 4 ) continue; // Short values are already in the smallValue field. while ( *knownTagPtr < currTag->id ) ++knownTagPtr; if ( *knownTagPtr != currTag->id ) continue; // Skip unrecognized tags. if ( currTag->dataLen > 1024*1024 ) XMP_Throw ( "Outrageous data length", kXMPErr_BadTIFF ); - + if ( (bufBegin <= currTag->origDataOffset) && ((currTag->origDataOffset + currTag->dataLen) <= bufEnd) ) { // This value is already fully within the current I/O buffer, copy it. MoveToOffset ( fileRef, currTag->origDataOffset, ioBuf ); @@ -879,18 +811,18 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, L if ( currTag->dataPtr == 0 ) XMP_Throw ( "No data block", kXMPErr_NoMemory ); memcpy ( currTag->dataPtr, ioBuf->ptr, currTag->dataLen ); // AUDIT: Safe, malloc'ed currTag->dataLen bytes above. } - + } - + // --------------------------------------------------------------------------------------------- // Now the second large value pass. This will reposition the I/O buffer as necessary. Hopefully // just once, to pick up the span of data not covered in the first pass. - + tagPos = ifdInfo.tagMap.begin(); // Reset both map/array positions. knownTagPtr = sKnownTags[ifd]; - + for ( ; tagPos != tagEnd; ++tagPos ) { - + InternalTagInfo* currTag = &tagPos->second; if ( (currTag->dataLen <= 4) || (currTag->dataPtr != 0) ) continue; // Done this tag? @@ -913,11 +845,11 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, L if ( ! ok ) XMP_Throw ( "EOF in data block", kXMPErr_BadTIFF ); memcpy ( currTag->dataPtr, ioBuf->ptr, currTag->dataLen ); // AUDIT: Safe, malloc'ed currTag->dataLen bytes above. } - + } - + // Done, return the next IFD offset. - + return ifdInfo.origNextIFD; } // TIFF_FileWriter::ProcessFileIFD @@ -928,13 +860,12 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, L // // See comments for ProcessPShop6IFD. -void TIFF_FileWriter::IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ) +void TIFF_FileWriter::IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ) { TIFF_MemoryReader buriedExif; buriedExif.ParseMemoryStream ( buriedPtr, (XMP_Uns32) buriedLen ); - + this->ProcessPShop6IFD ( buriedExif, kTIFF_PrimaryIFD ); - this->ProcessPShop6IFD ( buriedExif, kTIFF_TNailIFD ); this->ProcessPShop6IFD ( buriedExif, kTIFF_ExifIFD ); this->ProcessPShop6IFD ( buriedExif, kTIFF_GPSInfoIFD ); @@ -955,7 +886,7 @@ void* TIFF_FileWriter::CopyTagToMasterIFD ( const TagInfo & ps6Tag, InternalIFDI InternalTagInfo& newTag = newPos->second; newTag.dataLen = ps6Tag.dataLen; - + if ( newTag.dataLen <= 4 ) { newTag.dataPtr = (XMP_Uns8*) &newTag.smallValue; newTag.smallValue = *((XMP_Uns32*)ps6Tag.dataPtr); @@ -967,9 +898,9 @@ void* TIFF_FileWriter::CopyTagToMasterIFD ( const TagInfo & ps6Tag, InternalIFDI newTag.changed = true; // ! See comments with ProcessPShop6IFD. XMP_Assert ( (newTag.origDataLen == 0) && (newTag.origDataOffset == 0) ); - + masterIFD->changed = true; - + return newPos->second.dataPtr; // ! Return the address within the map entry for small values. } // TIFF_FileWriter::CopyTagToMasterIFD @@ -983,17 +914,17 @@ void* TIFF_FileWriter::CopyTagToMasterIFD ( const TagInfo & ps6Tag, InternalIFDI static bool FlipCFATable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns16 ) { if ( tagLen < 4 ) return false; - + XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr; Flip2 ( &u16Ptr[0] ); // Flip the counts to match the master TIFF. Flip2 ( &u16Ptr[1] ); - + XMP_Uns16 columns = GetUns16 ( &u16Ptr[0] ); // Fetch using the master TIFF's routine. XMP_Uns16 rows = GetUns16 ( &u16Ptr[1] ); - + if ( tagLen != (XMP_Uns32)(4 + columns*rows) ) return false; - + return true; } // FlipCFATable @@ -1010,12 +941,12 @@ static bool FlipCFATable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns static bool FlipDSDTable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns16 ) { if ( tagLen < 4 ) return false; - + XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr; for ( size_t i = tagLen/2; i > 0; --i, ++u16Ptr ) Flip2 ( u16Ptr ); - + return true; - + } // FlipDSDTable // ================================================================================================= @@ -1033,20 +964,20 @@ static bool FlipOECFSFRTable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc Ge Flip2 ( &u16Ptr[0] ); // Flip the data to match the master TIFF. Flip2 ( &u16Ptr[1] ); - + XMP_Uns16 columns = GetUns16 ( &u16Ptr[0] ); // Fetch using the master TIFF's routine. XMP_Uns16 rows = GetUns16 ( &u16Ptr[1] ); - + XMP_Uns32 minLen = 4 + columns + (8 * columns * rows); // Minimum legit tag size. if ( tagLen < minLen ) return false; - + // Compute the start of the rationals from the end of value. No need to walk through the names. XMP_Uns32* u32Ptr = (XMP_Uns32*) ((XMP_Uns8*)voidPtr + tagLen - (8 * columns * rows)); for ( size_t i = 2*columns*rows; i > 0; --i, ++u32Ptr ) Flip4 ( u32Ptr ); - + return true; - + } // FlipOECFSFRTable // ================================================================================================= @@ -1058,7 +989,7 @@ static bool FlipOECFSFRTable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc Ge // tags up to the parent file. Existing tags are not replaced. // // While it is tempting to try to directly use the TIFF_MemoryReader's tweaked IFD info, making that -// visible would compromise implementation separation. Better to pay the modest runtime cost of +// visible would compromise implementation separation. Better to pay the modest runtime cost of // using the official GetIFD method, letting it build the map. // // The tags that get moved are marked as being changed, as is the IFD they are moved into, but the @@ -1075,41 +1006,41 @@ void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XM { bool ok, found; TagInfoMap ps6IFD; - + found = buriedExif.GetIFD ( ifd, &ps6IFD ); if ( ! found ) return; - + bool needsFlipping = (this->bigEndian != buriedExif.IsBigEndian()); - + InternalIFDInfo* masterIFD = &this->containedIFDs[ifd]; - + TagInfoMap::const_iterator ps6Pos = ps6IFD.begin(); TagInfoMap::const_iterator ps6End = ps6IFD.end(); - + for ( ; ps6Pos != ps6End; ++ps6Pos ) { - + // Copy buried tags to the master IFD if they don't already exist there. - + const TagInfo& ps6Tag = ps6Pos->second; - + if ( this->FindTagInIFD ( ifd, ps6Tag.id ) != 0 ) continue; // Keep existing master tags. if ( needsFlipping && (ps6Tag.id == 37500) ) continue; // Don't copy an unflipped MakerNote. if ( (ps6Tag.id == kTIFF_ExifIFDPointer) || // Skip the tags that are explicit offsets. (ps6Tag.id == kTIFF_GPSInfoIFDPointer) || (ps6Tag.id == kTIFF_JPEGInterchangeFormat) || (ps6Tag.id == kTIFF_InteroperabilityIFDPointer) ) continue; - + void* voidPtr = this->CopyTagToMasterIFD ( ps6Tag, masterIFD ); - + if ( needsFlipping ) { switch ( ps6Tag.type ) { - + case kTIFF_ByteType: case kTIFF_SByteType: case kTIFF_ASCIIType: // Nothing more to do. break; - + case kTIFF_ShortType: case kTIFF_SShortType: { @@ -1117,7 +1048,7 @@ void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XM for ( size_t i = ps6Tag.count; i > 0; --i, ++u16Ptr ) Flip2 ( u16Ptr ); } break; - + case kTIFF_LongType: case kTIFF_SLongType: case kTIFF_FloatType: @@ -1126,7 +1057,7 @@ void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XM for ( size_t i = ps6Tag.count; i > 0; --i, ++u32Ptr ) Flip4 ( u32Ptr ); } break; - + case kTIFF_RationalType: case kTIFF_SRationalType: { @@ -1134,14 +1065,14 @@ void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XM for ( size_t i = (2 * ps6Tag.count); i > 0; --i, ++ratPtr ) Flip4 ( ratPtr ); } break; - + case kTIFF_DoubleType: { XMP_Uns64* u64Ptr = (XMP_Uns64*)voidPtr; for ( size_t i = ps6Tag.count; i > 0; --i, ++u64Ptr ) Flip8 ( u64Ptr ); } break; - + case kTIFF_UndefinedType: // Fix up the few kinds of special tables that Exif 2.2 defines. ok = true; // Keep everything that isn't a special table. @@ -1154,20 +1085,92 @@ void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XM } if ( ! ok ) this->DeleteTag ( ifd, ps6Tag.id ); break; - + default: // ? XMP_Throw ( "Unexpected tag type", kXMPErr_InternalFailure ); this->DeleteTag ( ifd, ps6Tag.id ); break; - + } } - + } - + } // TIFF_FileWriter::ProcessPShop6IFD // ================================================================================================= +// TIFF_FileWriter::PreflightIFDLinkage +// ==================================== +// +// Preflight special cases for the linkage between IFDs. Three of the IFDs are found through an +// explicit tag, the Exif, GPS, and Interop IFDs. The presence or absence of those IFDs affects the +// presence or absence of the linkage tag, which can affect the IFD containing the linkage tag. The +// thumbnail IFD is chained from the primary IFD, so if the thumbnail IFD is present we make sure +// that the primary IFD isn't empty. + +void TIFF_FileWriter::PreflightIFDLinkage() +{ + + // Do the tag-linked IFDs bottom up, Interop then GPS then Exif. + + if ( this->containedIFDs[kTIFF_InteropIFD].tagMap.empty() ) { + this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer ); + } else if ( ! this->GetTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0 ) ) { + this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0xABADABAD ); + } + + if ( this->containedIFDs[kTIFF_GPSInfoIFD].tagMap.empty() ) { + this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer ); + } else if ( ! this->GetTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0 ) ) { + this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0xABADABAD ); + } + + if ( this->containedIFDs[kTIFF_ExifIFD].tagMap.empty() ) { + this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); + } else if ( ! this->GetTag ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0 ) ) { + this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0xABADABAD ); + } + + // Make sure that the primary IFD is not empty if the thumbnail IFD is not empty. + + if ( this->containedIFDs[kTIFF_PrimaryIFD].tagMap.empty() && + (! this->containedIFDs[kTIFF_TNailIFD].tagMap.empty()) ) { + this->SetTag_Short ( kTIFF_PrimaryIFD, kTIFF_ResolutionUnit, 2 ); // Set Resolution unit to inches. + } + +} // TIFF_FileWriter::PreflightIFDLinkage + +// ================================================================================================= +// TIFF_FileWriter::DetermineVisibleLength +// ======================================= + +XMP_Uns32 TIFF_FileWriter::DetermineVisibleLength() +{ + XMP_Uns32 visibleLength = 8; // Start with the TIFF header size. + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); + size_t tagCount = ifdInfo.tagMap.size(); + if ( tagCount == 0 ) continue; + + visibleLength += (XMP_Uns32)( 6 + (12 * tagCount) ); + + InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); + InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); + + for ( ; tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & currTag ( tagPos->second ); + if ( currTag.dataLen > 4 ) visibleLength += ((currTag.dataLen + 1) & 0xFFFFFFFE); // ! Round to even lengths. + } + + } + + return visibleLength; + +} // TIFF_FileWriter::DetermineVisibleLength + +// ================================================================================================= // TIFF_FileWriter::DetermineAppendInfo // ==================================== @@ -1182,7 +1185,7 @@ XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, { XMP_Uns32 appendedLength = 0; XMP_Assert ( (appendedOrigin & 1) == 0 ); // Make sure it is even. - + #if Trace_DetermineAppendInfo { printf ( "\nEntering TIFF_FileWriter::DetermineAppendInfo%s\n", (appendAll ? ", append all" : "") ); @@ -1208,59 +1211,56 @@ XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, printf ( "\n" ); } #endif - + // Determine which of the IFDs will be appended. If the Exif, GPS, or Interoperability IFDs are // appended, set dummy values for their offsets in the "owning" IFD. This must be done first // since this might cause the owning IFD to grow. - + if ( ! appendAll ) { for ( int i = 0; i < kTIFF_KnownIFDCount ;++i ) appendedIFDs[i] = false; } else { for ( int i = 0; i < kTIFF_KnownIFDCount ;++i ) appendedIFDs[i] = (this->containedIFDs[i].tagMap.size() > 0); } - + appendedIFDs[kTIFF_InteropIFD] |= (this->containedIFDs[kTIFF_InteropIFD].origCount < this->containedIFDs[kTIFF_InteropIFD].tagMap.size()); if ( appendedIFDs[kTIFF_InteropIFD] ) { this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0xABADABAD ); } - + appendedIFDs[kTIFF_GPSInfoIFD] |= (this->containedIFDs[kTIFF_GPSInfoIFD].origCount < this->containedIFDs[kTIFF_GPSInfoIFD].tagMap.size()); if ( appendedIFDs[kTIFF_GPSInfoIFD] ) { this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0xABADABAD ); } - + appendedIFDs[kTIFF_ExifIFD] |= (this->containedIFDs[kTIFF_ExifIFD].origCount < this->containedIFDs[kTIFF_ExifIFD].tagMap.size()); if ( appendedIFDs[kTIFF_ExifIFD] ) { this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0xABADABAD ); } - - appendedIFDs[kTIFF_TNailIFD] |= (this->containedIFDs[kTIFF_TNailIFD].origCount < - this->containedIFDs[kTIFF_TNailIFD].tagMap.size()); - + appendedIFDs[kTIFF_PrimaryIFD] |= (this->containedIFDs[kTIFF_PrimaryIFD].origCount < this->containedIFDs[kTIFF_PrimaryIFD].tagMap.size()); // The appended data (if any) will be a sequence of an IFD followed by its large values. // Determine the new offsets for the appended IFDs and tag values, and the total amount of - // appended stuff. - + // appended stuff. + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount ;++ifd ) { - + InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); size_t tagCount = ifdInfo.tagMap.size(); if ( ! (appendAll | ifdInfo.changed) ) continue; if ( tagCount == 0 ) continue; - + newIFDOffsets[ifd] = ifdInfo.origIFDOffset; if ( appendedIFDs[ifd] ) { newIFDOffsets[ifd] = appendedOrigin + appendedLength; appendedLength += (XMP_Uns32)( 6 + (12 * tagCount) ); } - + InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); @@ -1277,11 +1277,11 @@ XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, } } - + } - + // If the Exif, GPS, or Interoperability IFDs get appended, update the tag values for their new offsets. - + if ( appendedIFDs[kTIFF_ExifIFD] ) { this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, newIFDOffsets[kTIFF_ExifIFD] ); } @@ -1291,7 +1291,7 @@ XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, if ( appendedIFDs[kTIFF_InteropIFD] ) { this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, newIFDOffsets[kTIFF_InteropIFD] ); } - + #if Trace_DetermineAppendInfo { printf ( "Exiting TIFF_FileWriter::DetermineAppendInfo\n" ); @@ -1320,9 +1320,9 @@ XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, printf ( "\n" ); } #endif - + return appendedLength; - + } // TIFF_FileWriter::DetermineAppendInfo // ================================================================================================= @@ -1354,7 +1354,7 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n // Allocate the new block of memory for the full stream. Copy the original stream. Write the // modified IFDs and values. Finally rebuild the internal IFD info and tag map. - + XMP_Uns32 newLength = appendedOrigin + appendedLength; XMP_Uns8* newStream = (XMP_Uns8*) malloc ( newLength + extraSpace ); if ( newStream == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); @@ -1364,16 +1364,16 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n XMP_Assert ( appendedOrigin == (this->tiffLength + 1) ); newStream[this->tiffLength] = 0; // Clear the pad byte. } - + try { // We might get exceptions from the next part and must delete newStream on the way out. - + // Write the modified IFDs and values. Rewrite the full IFD from scratch to make sure the // tags are now unique and sorted. Copy large changed values to their appropriate location. - + XMP_Uns32 appendedOffset = appendedOrigin; - + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { - + InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); size_t tagCount = ifdInfo.tagMap.size(); @@ -1381,12 +1381,12 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n if ( tagCount == 0 ) continue; XMP_Uns8* ifdPtr = newStream + newIFDOffsets[ifd]; - + if ( appendedIFDs[ifd] ) { XMP_Assert ( newIFDOffsets[ifd] == appendedOffset ); appendedOffset += (XMP_Uns32)( 6 + (12 * tagCount) ); } - + this->PutUns16 ( (XMP_Uns16)tagCount, ifdPtr ); ifdPtr += 2; @@ -1426,67 +1426,31 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n ifdPtr += 4; } - + this->PutUns32 ( ifdInfo.origNextIFD, ifdPtr ); ifdPtr += 4; - + } - + XMP_Assert ( appendedOffset == newLength ); - + // Back fill the offsets for the primary and thumnbail IFDs, if they are now appended. - + if ( appendedIFDs[kTIFF_PrimaryIFD] ) { this->PutUns32 ( newIFDOffsets[kTIFF_PrimaryIFD], (newStream + 4) ); } - - if ( appendedIFDs[kTIFF_TNailIFD] ) { - size_t primaryIFDCount = this->containedIFDs[kTIFF_PrimaryIFD].tagMap.size(); - XMP_Uns32 tnailRefOffset = newIFDOffsets[kTIFF_PrimaryIFD] + 2 + (12 * (XMP_Uns32)primaryIFDCount); - this->PutUns32 ( newIFDOffsets[kTIFF_TNailIFD], (newStream + tnailRefOffset) ); - } - + } catch ( ... ) { - + free ( newStream ); throw; - + } - + *newStream_out = newStream; *newLength_out = newLength; - -} // TIFF_FileWriter::UpdateMemByAppend -// ================================================================================================= -// TIFF_FileWriter::DetermineVisibleLength -// ======================================= - -XMP_Uns32 TIFF_FileWriter::DetermineVisibleLength() -{ - XMP_Uns32 visibleLength = 8; // Start with the TIFF header size. - - for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { - - InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); - size_t tagCount = ifdInfo.tagMap.size(); - if ( tagCount == 0 ) continue; - - visibleLength += (XMP_Uns32)( 6 + (12 * tagCount) ); - - InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); - InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); - - for ( ; tagPos != tagEnd; ++tagPos ) { - InternalTagInfo & currTag ( tagPos->second ); - if ( currTag.dataLen > 4 ) visibleLength += ((currTag.dataLen + 1) & 0xFFFFFFFE); // ! Round to even lengths. - } - - } - - return visibleLength; - -} // TIFF_FileWriter::DetermineVisibleLength +} // TIFF_FileWriter::UpdateMemByAppend // ================================================================================================= // TIFF_FileWriter::UpdateMemByRewrite @@ -1582,54 +1546,39 @@ static const SimpleHiddenContentInfo kSimpleHiddenContentInfo [kSimpleHiddenCont // ------------------------------------------------------------------------------------------------- -void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out ) +void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out ) { const InternalTagInfo* tagInfo; - + // Check for tags that we don't tolerate because they have data we can't (or refuse to) find. - + for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { for ( int i = 0; kNoGoTags[i] != 0xFFFF; ++i ) { tagInfo = this->FindTagInIFD ( ifd, kNoGoTags[i] ); if ( tagInfo != 0 ) XMP_Throw ( "Tag not tolerated for TIFF rewrite", kXMPErr_Unimplemented ); } } - - // Delete unwanted tags. - + + // Delete unwanted tags. + for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { for ( int i = 0; kBanishedTags[i] != 0xFFFF; ++i ) { this->DeleteTag ( ifd, kBanishedTags[i] ); } } - - // Make sure the "pointer" tags for the Exif, GPS, and Interop IFDs exist. The order is - // important, adding the Interop pointer can cause the Exif IFD to exist. - - if ( ! this->containedIFDs[kTIFF_InteropIFD].tagMap.empty() ) { - this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0xABADABAD ); - } - - if ( ! this->containedIFDs[kTIFF_GPSInfoIFD].tagMap.empty() ) { - this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0xABADABAD ); - } - - if ( ! this->containedIFDs[kTIFF_ExifIFD].tagMap.empty() ) { - this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0xABADABAD ); - } // Determine the offsets and additional size for the hidden offset content. Set the offset tags // to the new offset. - + XMP_Uns32 hiddenContentLength = 0; XMP_Uns32 hiddenContentOrigin = this->DetermineVisibleLength(); - + SimpleHiddenContentLocations hiddenLocations [kSimpleHiddenContentCount]; - + for ( int i = 0; i < kSimpleHiddenContentCount; ++i ) { const SimpleHiddenContentInfo & hiddenInfo ( kSimpleHiddenContentInfo[i] ); - + bool haveLength = this->GetTag_Integer ( hiddenInfo.ifd, hiddenInfo.lengthTag, &hiddenLocations[i].length ); bool haveOffset = this->GetTag_Integer ( hiddenInfo.ifd, hiddenInfo.offsetTag, &hiddenLocations[i].oldOffset ); if ( haveLength != haveOffset ) XMP_Throw ( "Unpaired simple hidden content tag", kXMPErr_BadTIFF ); @@ -1640,7 +1589,7 @@ void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* hiddenContentLength += ((hiddenLocations[i].length + 1) & 0xFFFFFFFE); // ! Round up for even offsets. } - + // Save any old memory stream for the content behind hidden offsets. Setup a bare TIFF header. XMP_Uns8* oldStream = this->memStream; @@ -1652,20 +1601,20 @@ void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* bareTIFF[0] = 0x49; bareTIFF[1] = 0x49; bareTIFF[2] = 0x2A; bareTIFF[3] = 0x00; } *((XMP_Uns32*)&bareTIFF[4]) = 0; - + this->memStream = &bareTIFF[0]; this->tiffLength = sizeof ( bareTIFF ); this->ownedStream = false; // Call UpdateMemByAppend to write the new stream, telling it to append everything. - + this->UpdateMemByAppend ( newStream_out, newLength_out, true, hiddenContentLength ); // Copy the hidden content and update the output stream length; XMP_Assert ( *newLength_out == hiddenContentOrigin ); *newLength_out += hiddenContentLength; - + for ( int i = 0; i < kSimpleHiddenContentCount; ++i ) { if ( hiddenLocations[i].length == 0 ) continue; @@ -1675,7 +1624,7 @@ void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* memcpy ( destPtr, srcPtr, hiddenLocations[i].length ); // AUDIT: Safe copy, not user data, computed length. } - + } // TIFF_FileWriter::UpdateMemByRewrite // ================================================================================================= @@ -1692,15 +1641,17 @@ void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* // will discard any MakerNote tags and risks breaking offsets that are hidden. This can be necessary // though to try to make the TIFF fit in a JPEG file. -XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStream /* = false */ ) +XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStream /* = false */ ) { if ( this->fileParsed ) XMP_Throw ( "Not memory based", kXMPErr_EnforceFailure ); - + if ( ! this->changed ) { if ( dataPtr != 0 ) *dataPtr = this->memStream; return this->tiffLength; } - + + this->PreflightIFDLinkage(); + bool nowEmpty = true; for ( size_t i = 0; i < kTIFF_KnownIFDCount; ++i ) { if ( ! this->containedIFDs[i].tagMap.empty() ) { @@ -1708,39 +1659,39 @@ XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStr break; } } - + XMP_Uns8* newStream = 0; XMP_Uns32 newLength = 0; - + if ( nowEmpty ) { - + this->DeleteExistingInfo(); // Prepare for an empty reparse. - + } else { if ( this->tiffLength == 0 ) { // ! An empty parse does set this->memParsed. condenseStream = true; // Makes "conjured" TIFF take the full rewrite path. } - + if ( condenseStream ) this->changed = true; // A prior regular call would have cleared this->changed. - + if ( condenseStream ) { this->UpdateMemByRewrite ( &newStream, &newLength ); } else { this->UpdateMemByAppend ( &newStream, &newLength ); } - + } - + // Parse the revised stream. This is the cleanest way to rebuild the tag map. - + this->ParseMemoryStream ( newStream, newLength, kDoNotCopyData ); XMP_Assert ( this->tiffLength == newLength ); this->ownedStream = (newLength > 0); // ! We really do own the new stream, if not empty. - + if ( dataPtr != 0 ) *dataPtr = this->memStream; return newLength; - + } // TIFF_FileWriter::UpdateMemoryStream // ================================================================================================= @@ -1769,17 +1720,17 @@ XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStr #define Trace_UpdateFileStream 0 #endif -void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) +void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) { if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure ); if ( ! this->changed ) return; - + XMP_Int64 origDataLength = LFA_Measure ( fileRef ); if ( (origDataLength >> 32) != 0 ) XMP_Throw ( "TIFF files can't exceed 4GB", kXMPErr_BadTIFF ); - + bool appendedIFDs[kTIFF_KnownIFDCount]; XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount]; - + #if Trace_UpdateFileStream printf ( "\nStarting update of TIFF file stream\n" ); #endif @@ -1791,15 +1742,17 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) LFA_Write ( fileRef, "\0", 1 ); } + this->PreflightIFDLinkage(); + XMP_Uns32 appendedLength = DetermineAppendInfo ( appendedOrigin, appendedIFDs, newIFDOffsets ); if ( appendedLength > (0xFFFFFFFFUL - appendedOrigin) ) XMP_Throw ( "TIFF files can't exceed 4GB", kXMPErr_BadTIFF ); // Do the in-place update for the IFDs and tag values that fit. This part does separate seeks // and writes for the IFDs and values. Things to be updated can be anywhere in the file. - + // *** This might benefit from a map of the in-place updates. This would allow use of a possibly // *** more efficient sequential I/O model. Could even incorporate the safe update file copying. - + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; @@ -1807,7 +1760,7 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) // In order to get a little bit of locality, write the IFD first then the changed tags that // have large values and fit in-place. - + if ( ! appendedIFDs[ifd] ) { #if Trace_UpdateFileStream printf ( " Updating IFD %d in-place at offset %d (0x%X)\n", ifd, thisIFD.origIFDOffset, thisIFD.origIFDOffset ); @@ -1815,10 +1768,10 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) LFA_Seek ( fileRef, thisIFD.origIFDOffset, SEEK_SET ); this->WriteFileIFD ( fileRef, thisIFD ); } - + InternalTagMap::iterator tagPos; InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); - + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { InternalTagInfo & thisTag = tagPos->second; if ( (! thisTag.changed) || (thisTag.dataLen <= 4) || (thisTag.dataLen > thisTag.origDataLen) ) continue; @@ -1835,12 +1788,12 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) XMP_Int64 fileEnd = LFA_Seek ( fileRef, 0, SEEK_END ); XMP_Assert ( fileEnd == appendedOrigin ); - + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; if ( ! thisIFD.changed ) continue; - + if ( appendedIFDs[ifd] ) { #if Trace_UpdateFileStream printf ( " Updating IFD %d by append at offset %d (0x%X)\n", ifd, newIFDOffsets[ifd], newIFDOffsets[ifd] ); @@ -1848,10 +1801,10 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) XMP_Assert ( newIFDOffsets[ifd] == LFA_Measure(fileRef) ); this->WriteFileIFD ( fileRef, thisIFD ); } - + InternalTagMap::iterator tagPos; InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); - + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { InternalTagInfo & thisTag = tagPos->second; if ( (! thisTag.changed) || (thisTag.dataLen <= 4) || (thisTag.dataLen <= thisTag.origDataLen) ) continue; @@ -1866,10 +1819,10 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) } - // Back-fill the offsets for the primary and thumnbail IFDs, if they are now appended. - + // Back-fill the offset for the primary IFD, if it is now appended. + XMP_Uns32 newOffset; - + if ( appendedIFDs[kTIFF_PrimaryIFD] ) { this->PutUns32 ( newIFDOffsets[kTIFF_PrimaryIFD], &newOffset ); #if TraceUpdateFileStream @@ -1878,30 +1831,10 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) LFA_Seek ( fileRef, 4, SEEK_SET ); LFA_Write ( fileRef, &newOffset, 4 ); } - - InternalIFDInfo & primaryIFD = this->containedIFDs[kTIFF_PrimaryIFD]; - InternalIFDInfo & tnailIFD = this->containedIFDs[kTIFF_TNailIFD]; - - if ( appendedIFDs[kTIFF_TNailIFD] && (primaryIFD.origNextIFD == tnailIFD.origIFDOffset) ) { - - size_t primaryIFDCount = primaryIFD.tagMap.size(); - XMP_Uns32 tnailRefOffset = newIFDOffsets[kTIFF_PrimaryIFD] + 2 + (12 * (XMP_Uns32)primaryIFDCount); - - this->PutUns32 ( newIFDOffsets[kTIFF_TNailIFD], &newOffset ); - #if TraceUpdateFileStream - printf ( " Back-filling offset of thumbnail IFD, offset at %d (0x%X), pointing to %d (0x%X)\n", - tnailRefOffset, tnailRefOffset, newOffset, newOffset ); - #endif - LFA_Seek ( fileRef, tnailRefOffset, SEEK_SET ); - LFA_Write ( fileRef, &newOffset, 4 ); - - primaryIFD.origNextIFD = newIFDOffsets[kTIFF_TNailIFD]; // ! Ought to be below, easier here. - } - // Reset the changed flags and original length/offset values. This simulates a reparse of the // updated file. - + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; @@ -1910,10 +1843,10 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) thisIFD.changed = false; thisIFD.origCount = (XMP_Uns16)( thisIFD.tagMap.size() ); thisIFD.origIFDOffset = newIFDOffsets[ifd]; - + InternalTagMap::iterator tagPos; InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); - + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { InternalTagInfo & thisTag = tagPos->second; if ( ! thisTag.changed ) continue; @@ -1926,7 +1859,7 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) this->tiffLength = (XMP_Uns32) LFA_Measure ( fileRef ); LFA_Seek ( fileRef, 0, SEEK_END ); // Can't hurt. - + #if Trace_UpdateFileStream printf ( "\nFinished update of TIFF file stream\n" ); #endif @@ -1942,7 +1875,7 @@ void TIFF_FileWriter::WriteFileIFD ( LFA_FileRef fileRef, InternalIFDInfo & this XMP_Uns16 tagCount; this->PutUns16 ( (XMP_Uns16)thisIFD.tagMap.size(), &tagCount ); LFA_Write ( fileRef, &tagCount, 2 ); - + InternalTagMap::iterator tagPos; InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); @@ -1960,7 +1893,7 @@ void TIFF_FileWriter::WriteFileIFD ( LFA_FileRef fileRef, InternalIFDInfo & this XMP_Assert ( sizeof(ifdEntry) == 12 ); } - + XMP_Uns32 nextIFD; this->PutUns32 ( thisIFD.origNextIFD, &nextIFD ); LFA_Write ( fileRef, &nextIFD, 4 ); diff --git a/source/XMPFiles/FormatSupport/TIFF_MemoryReader.cpp b/source/XMPFiles/FormatSupport/TIFF_MemoryReader.cpp index 4ca9cac..316cea0 100644 --- a/source/XMPFiles/FormatSupport/TIFF_MemoryReader.cpp +++ b/source/XMPFiles/FormatSupport/TIFF_MemoryReader.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -14,7 +14,7 @@ /// \brief Implementation of the memory-based read-only TIFF_Manager. /// /// The read-only forms of TIFF_Manager are derived from TIFF_Reader. The GetTag methods are common -/// implementations in TIFF_Reader. The parsing code is different in the TIFF_MemoryReader and +/// implementations in TIFF_Reader. The parsing code is different in the TIFF_MemoryReader and /// TIFF_FileReader constructors. There are also separate destructors to release captured info. /// /// The read-only implementations use runtime data that is simple tweaks on the stored form. The @@ -52,9 +52,9 @@ void TIFF_MemoryReader::SortIFD ( TweakedIFDInfo* thisIFD ) XMP_Uns16 tagCount = thisIFD->count; TweakedIFDEntry* ifdEntries = thisIFD->entries; XMP_Uns16 prevTag = ifdEntries[0].id; - + for ( size_t i = 1; i < tagCount; ++i ) { - + XMP_Uns16 thisTag = ifdEntries[i].id; if ( thisTag > prevTag ) { @@ -76,7 +76,7 @@ void TIFF_MemoryReader::SortIFD ( TweakedIFDInfo* thisIFD ) for ( j = (XMP_Int32)i-1; j >= 0; --j ) { if ( ifdEntries[j].id <= thisTag ) break; } - + if ( (j >= 0) && (ifdEntries[j].id == thisTag) ) { // Out of order duplicate, move it to position j, move the tail of the array up. @@ -96,11 +96,11 @@ void TIFF_MemoryReader::SortIFD ( TweakedIFDInfo* thisIFD ) } } - + } - + thisIFD->count = tagCount; // Save the final count. - + } // TIFF_MemoryReader::SortIFD // ================================================================================================= @@ -111,15 +111,16 @@ bool TIFF_MemoryReader::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const { if ( ifd > kTIFF_LastRealIFD ) XMP_Throw ( "Invalid IFD requested", kXMPErr_InternalFailure ); const TweakedIFDInfo* thisIFD = &containedIFDs[ifd]; - + if ( ifdMap != 0 ) ifdMap->clear(); if ( thisIFD->count == 0 ) return false; - + if ( ifdMap != 0 ) { - + for ( size_t i = 0; i < thisIFD->count; ++i ) { TweakedIFDEntry* thisTag = &(thisIFD->entries[i]); + if ( (thisTag->type < kTIFF_ByteType) || (thisTag->type > kTIFF_LastType) ) continue; // Bad type, skip this tag. TagInfo info ( thisTag->id, thisTag->type, 0, 0, thisTag->bytes ); info.count = info.dataLen / (XMP_Uns32)kTIFF_TypeSizes[info.type]; @@ -128,9 +129,9 @@ bool TIFF_MemoryReader::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const (*ifdMap)[info.id] = info; } - + } - + return true; } // TIFF_MemoryReader::GetIFD @@ -144,23 +145,23 @@ const TIFF_MemoryReader::TweakedIFDEntry* TIFF_MemoryReader::FindTagInIFD ( XMP_ if ( ifd == kTIFF_KnownIFD ) { // ... lookup the tag in the known tag map } - + if ( ifd > kTIFF_LastRealIFD ) XMP_Throw ( "Invalid IFD requested", kXMPErr_InternalFailure ); const TweakedIFDInfo* thisIFD = &containedIFDs[ifd]; - + if ( thisIFD->count == 0 ) return 0; - + XMP_Uns32 spanLength = thisIFD->count; const TweakedIFDEntry* spanBegin = &(thisIFD->entries[0]); - + while ( spanLength > 1 ) { XMP_Uns32 halfLength = spanLength >> 1; // Since spanLength > 1, halfLength > 0. const TweakedIFDEntry* spanMiddle = spanBegin + halfLength; - + // There are halfLength entries below spanMiddle, then the spanMiddle entry, then // spanLength-halfLength-1 entries above spanMiddle (which can be none). - + if ( spanMiddle->id == id ) { spanBegin = spanMiddle; break; @@ -170,9 +171,9 @@ const TIFF_MemoryReader::TweakedIFDEntry* TIFF_MemoryReader::FindTagInIFD ( XMP_ spanBegin = spanMiddle; // Keep a valid spanBegin for the return check, don't use spanMiddle+1. spanLength -= halfLength; } - + } - + if ( spanBegin->id != id ) spanBegin = 0; return spanBegin; @@ -186,11 +187,11 @@ XMP_Uns32 TIFF_MemoryReader::GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const { const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return 0; - + XMP_Uns8 * valuePtr = (XMP_Uns8*) this->GetDataPtr ( thisTag ); - + return (XMP_Uns32)(valuePtr - this->tiffStream); // ! TIFF streams can't exceed 4GB. - + } // TIFF_MemoryReader::GetValueOffset // ================================================================================================= @@ -201,20 +202,21 @@ bool TIFF_MemoryReader::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) con { const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; - + if ( (thisTag->type < kTIFF_ByteType) || (thisTag->type > kTIFF_LastType) ) return false; // Bad type, skip this tag. + if ( info != 0 ) { info->id = thisTag->id; info->type = thisTag->type; info->count = thisTag->bytes / (XMP_Uns32)kTIFF_TypeSizes[thisTag->type]; info->dataLen = thisTag->bytes; - + info->dataPtr = this->GetDataPtr ( thisTag ); } - + return true; - + } // TIFF_MemoryReader::GetTag // ================================================================================================= @@ -225,7 +227,7 @@ bool TIFF_MemoryReader::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* { const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; - + if ( data != 0 ) { if ( thisTag->type == kTIFF_ShortType ) { if ( thisTag->bytes != 2 ) return false; // Wrong count. @@ -237,7 +239,7 @@ bool TIFF_MemoryReader::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* return false; } } - + return true; } // TIFF_MemoryReader::GetTag_Integer @@ -251,11 +253,11 @@ bool TIFF_MemoryReader::GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_ByteType) || (thisTag->bytes != 1) ) return false; - + if ( data != 0 ) { *data = * ( (XMP_Uns8*) this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_Byte @@ -269,11 +271,11 @@ bool TIFF_MemoryReader::GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* dat const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SByteType) || (thisTag->bytes != 1) ) return false; - + if ( data != 0 ) { *data = * ( (XMP_Int8*) this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_SByte @@ -287,11 +289,11 @@ bool TIFF_MemoryReader::GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* da const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_ShortType) || (thisTag->bytes != 2) ) return false; - + if ( data != 0 ) { *data = this->GetUns16 ( this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_Short @@ -305,11 +307,11 @@ bool TIFF_MemoryReader::GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* d const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SShortType) || (thisTag->bytes != 2) ) return false; - + if ( data != 0 ) { *data = (XMP_Int16) this->GetUns16 ( this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_SShort @@ -323,11 +325,11 @@ bool TIFF_MemoryReader::GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* dat const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_LongType) || (thisTag->bytes != 4) ) return false; - + if ( data != 0 ) { *data = this->GetUns32 ( this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_Long @@ -341,11 +343,11 @@ bool TIFF_MemoryReader::GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* da const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SLongType) || (thisTag->bytes != 4) ) return false; - + if ( data != 0 ) { *data = (XMP_Int32) this->GetUns32 ( this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_SLong @@ -359,13 +361,13 @@ bool TIFF_MemoryReader::GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_RationalType) || (thisTag->bytes != 8) ) return false; - + if ( data != 0 ) { XMP_Uns32* dataPtr = (XMP_Uns32*) this->GetDataPtr ( thisTag ); data->num = this->GetUns32 ( dataPtr ); data->denom = this->GetUns32 ( dataPtr+1 ); } - + return true; } // TIFF_MemoryReader::GetTag_Rational @@ -379,13 +381,13 @@ bool TIFF_MemoryReader::GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SRationalType) || (thisTag->bytes != 8) ) return false; - + if ( data != 0 ) { XMP_Uns32* dataPtr = (XMP_Uns32*) this->GetDataPtr ( thisTag ); data->num = (XMP_Int32) this->GetUns32 ( dataPtr ); data->denom = (XMP_Int32) this->GetUns32 ( dataPtr+1 ); } - + return true; } // TIFF_MemoryReader::GetTag_SRational @@ -399,11 +401,11 @@ bool TIFF_MemoryReader::GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_FloatType) || (thisTag->bytes != 4) ) return false; - + if ( data != 0 ) { *data = this->GetFloat ( this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_Float @@ -417,12 +419,12 @@ bool TIFF_MemoryReader::GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_DoubleType) || (thisTag->bytes != 8) ) return false; - + if ( data != 0 ) { double* dataPtr = (double*) this->GetDataPtr ( thisTag ); *data = this->GetDouble ( dataPtr ); } - + return true; } // TIFF_MemoryReader::GetTag_Double @@ -436,13 +438,13 @@ bool TIFF_MemoryReader::GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( thisTag->type != kTIFF_ASCIIType ) return false; - + if ( dataPtr != 0 ) { *dataPtr = (XMP_StringPtr) this->GetDataPtr ( thisTag ); } - + if ( dataLen != 0 ) *dataLen = thisTag->bytes; - + return true; } // TIFF_MemoryReader::GetTag_ASCII @@ -456,9 +458,9 @@ bool TIFF_MemoryReader::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std:: const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( thisTag->type != kTIFF_UndefinedType ) return false; - + if ( utf8Str == 0 ) return true; // Return true if the converted string is not wanted. - + bool ok = this->DecodeString ( this->GetDataPtr ( thisTag ), thisTag->bytes, utf8Str ); return ok; @@ -473,21 +475,21 @@ bool TIFF_MemoryReader::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std:: void TIFF_MemoryReader::ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) { // Get rid of any current TIFF. - + if ( this->ownedStream ) free ( this->tiffStream ); this->ownedStream = false; this->tiffStream = 0; this->tiffLength = 0; - + for ( size_t i = 0; i < kTIFF_KnownIFDCount; ++i ) { this->containedIFDs[i].count = 0; this->containedIFDs[i].entries = 0; } - + if ( length == 0 ) return; // Allocate space for the full in-memory stream and copy it. - + if ( ! copyData ) { XMP_Assert ( ! this->ownedStream ); this->tiffStream = (XMP_Uns8*) data; @@ -500,13 +502,16 @@ void TIFF_MemoryReader::ParseMemoryStream ( const void* data, XMP_Uns32 length, } this->tiffLength = length; - + // Find and process the primary, Exif, GPS, and Interoperability IFDs. - + XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( this->tiffStream, length ); XMP_Uns32 tnailIFDOffset = 0; - + if ( primaryIFDOffset != 0 ) tnailIFDOffset = this->ProcessOneIFD ( primaryIFDOffset, kTIFF_PrimaryIFD ); + + // ! Need the thumbnail IFD for checking full Exif APP1 in some JPEG files! + if ( tnailIFDOffset != 0 ) (void) this->ProcessOneIFD ( tnailIFDOffset, kTIFF_TNailIFD ); const TweakedIFDEntry* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (exifIFDTag->bytes == 4) ) { @@ -525,18 +530,6 @@ void TIFF_MemoryReader::ParseMemoryStream ( const void* data, XMP_Uns32 length, XMP_Uns32 interopOffset = this->GetUns32 ( &interopIFDTag->dataOrPos ); (void) this->ProcessOneIFD ( interopOffset, kTIFF_InteropIFD ); } - - // Process the thumbnail IFD. We only do this for Exif-compliant TIFF streams. Extract the - // JPEG thumbnail image pointer (tag 513) for later use by GetTNailInfo. - - if ( (tnailIFDOffset != 0) && (this->containedIFDs[kTIFF_ExifIFD].count > 0) ) { - (void) this->ProcessOneIFD ( tnailIFDOffset, kTIFF_TNailIFD ); - const TweakedIFDEntry* jpegInfo = FindTagInIFD ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat ); - if ( jpegInfo != 0 ) { - XMP_Uns32 tnailImageOffset = this->GetUns32 ( &jpegInfo->dataOrPos ); - this->jpegTNailPtr = (XMP_Uns8*)this->tiffStream + tnailImageOffset; - } - } } // TIFF_MemoryReader::ParseMemoryStream @@ -547,11 +540,11 @@ void TIFF_MemoryReader::ParseMemoryStream ( const void* data, XMP_Uns32 length, XMP_Uns32 TIFF_MemoryReader::ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) { TweakedIFDInfo& ifdInfo = this->containedIFDs[ifd]; - + if ( (ifdOffset < 8) || (ifdOffset > (this->tiffLength - kEmptyIFDLength)) ) { XMP_Throw ( "Bad IFD offset", kXMPErr_BadTIFF ); } - + XMP_Uns8* ifdPtr = this->tiffStream + ifdOffset; XMP_Uns16 ifdCount = this->GetUns16 ( ifdPtr ); TweakedIFDEntry* ifdEntries = (TweakedIFDEntry*)(ifdPtr+2); @@ -561,11 +554,11 @@ XMP_Uns32 TIFF_MemoryReader::ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) ifdInfo.count = ifdCount; ifdInfo.entries = ifdEntries; - + XMP_Int32 prevTag = -1; // ! The GPS IFD has a tag 0, so we need a signed initial value. bool needsSorting = false; for ( size_t i = 0; i < ifdCount; ++i ) { - + TweakedIFDEntry* thisEntry = &ifdEntries[i]; // Tweak the IFD entry to be more useful. if ( ! this->nativeEndian ) { @@ -573,7 +566,7 @@ XMP_Uns32 TIFF_MemoryReader::ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) Flip2 ( &thisEntry->type ); Flip4 ( &thisEntry->bytes ); } - + if ( thisEntry->id <= prevTag ) needsSorting = true; prevTag = thisEntry->id; @@ -586,12 +579,12 @@ XMP_Uns32 TIFF_MemoryReader::ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) } } - + ifdPtr += (2 + ifdCount*12); XMP_Uns32 nextIFDOffset = this->GetUns32 ( ifdPtr ); - + if ( needsSorting ) SortIFD ( &ifdInfo ); // ! Don't perturb the ifdCount used to find the next IFD offset. - + return nextIFDOffset; } // TIFF_MemoryReader::ProcessOneIFD diff --git a/source/XMPFiles/FormatSupport/TIFF_Support.cpp b/source/XMPFiles/FormatSupport/TIFF_Support.cpp index 8aecc10..87a96c9 100644 --- a/source/XMPFiles/FormatSupport/TIFF_Support.cpp +++ b/source/XMPFiles/FormatSupport/TIFF_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -25,10 +25,9 @@ static bool sFirstCTor = true; TIFF_Manager::TIFF_Manager() - : bigEndian(false), nativeEndian(false), jpegTNailPtr(0), + : bigEndian(false), nativeEndian(false), GetUns16(0), GetUns32(0), GetFloat(0), GetDouble(0), - PutUns16(0), PutUns32(0), PutFloat(0), PutDouble(0), - xmpHadUserComment(false), xmpHadRelatedSoundFile(false) + PutUns16(0), PutUns32(0), PutFloat(0), PutDouble(0) { if ( sFirstCTor ) { @@ -318,10 +317,20 @@ bool TIFF_Manager::DecodeString ( const void * encodedPtr, size_t encodedLen, st } else if ( *typePtr == 'U' ) { try { + const UTF16Unit * utf16Ptr = (const UTF16Unit *) valuePtr; size_t utf16Len = valueLen >> 1; // The number of UTF-16 storage units, not bytes. - UTF16_to_UTF8 ( utf16Ptr, utf16Len, this->bigEndian, utf8Str ); + if ( utf16Len == 0 ) return false; + bool isBigEndian = this->bigEndian; // Default to stream endian, unless there is a BOM ... + if ( (*utf16Ptr == 0xFEFF) || (*utf16Ptr == 0xFFFE) ) { // Check for an explicit BOM + isBigEndian = (*((XMP_Uns8*)utf16Ptr) == 0xFE); + utf16Ptr += 1; // Don't translate the BOM. + utf16Len -= 1; + if ( utf16Len == 0 ) return false; + } + UTF16_to_UTF8 ( utf16Ptr, utf16Len, isBigEndian, utf8Str ); return true; + } catch ( ... ) { return false; // Ignore the tag if there are conversion errors. } @@ -423,204 +432,3 @@ bool TIFF_Manager::EncodeString ( const std::string& utf8Str, XMP_Uns8 encoding, } // TIFF_Manager::EncodeString // ================================================================================================= -// GetJPEGDimensions -// ================= -// -// Get the internal dimensions for a JPEG compressed image. These are the X (width) and Y (height) -// components of the SOFn marker segment. A 0 value for Y says that the height is in the NL -// component of the DNL marker segment at the end of the first scan. We'll use the first SOF, in -// the case of hierarchical JPEG which has multiple frames. -// -// For this logic a JPEG stream is: -// SOI standalone marker -// Optional marker segments -// First frame: -// SOFn marker segment -// First scan: -// Optional marker segments -// SOS marker segment -// Image data and RST standalone markers -// Optional DNL marker segment -// Optional additional scans -// Optional additional frames -// EOI standalone marker -// -// There is no explicit length for the image data portion of a scan. It ends at the first non-RST -// marker. So we look no further than the first non-RST marker after the first SOS marker segment. -// That is the one and only DNL marker segment, if it exists. Hopefully we stop at the first SOFn. -// -// The first 5 bytes of the SOFn contents are: -// Uns8 - ignored here -// Uns16 - Y, height, big endian -// Uns16 - X, width, big endian -// -// A DNL marker segment contains just 2 bytes of data, the big endian Uns16 number of lines. - -static void GetJPEGDimensions ( const void * jpegStream, size_t jpegLength, XMP_Uns32 * width, XMP_Uns32 * height ) -{ - const XMP_Uns8 * jpegPtr = (const XMP_Uns8 *) jpegStream; - const XMP_Uns8 * jpegEnd = jpegPtr + jpegLength; - - XMP_Uns16 marker, length; - - *width = *height = 0; // Assume the worst. - - marker = GetUns16BE ( jpegPtr ); - if ( marker != 0xFFD8 ) return; // Check for the SOI. - jpegPtr += 2; - - // Scan for the first SOFn marker and extract the Y and X components. - - while ( jpegPtr < jpegEnd ) { - marker = GetUns16BE ( jpegPtr ); - if ( ((marker & 0xFFF0) == 0xFFC0) && - (marker != 0xFFC4) && (marker != 0xFFC8) && (marker != 0xFFCC) ) break; - jpegPtr += 2; - if ( (jpegPtr < jpegEnd) && ((marker & 0xFFF8) != 0xFFD0) && - (marker != 0xFF01) && (marker != 0xFFD8) && (marker != 0xFFD9) ) { - jpegPtr += GetUns16BE ( jpegPtr ); - } - } - - if ( jpegPtr >= jpegEnd ) return; // Ran out of data. - if ( (marker & 0xFFF0) != 0xFFC0 ) return; // Not an SOFn marker. - jpegPtr += 2; - length = GetUns16BE ( jpegPtr ); - if ( length < 7 ) return; // Bad length, the SOFn marker segment is too short. - - *height = GetUns16BE ( jpegPtr+3 ); - *width = GetUns16BE ( jpegPtr+5 ); - if ( *height != 0 ) return; // Done if the Y component is non-zero. - jpegPtr += length; - - // Need to look for a DNL marker segment. Scan for the first SOS marker. - - while ( jpegPtr < jpegEnd ) { - marker = GetUns16BE ( jpegPtr ); - if ( marker == 0xFFDA ) break; - jpegPtr += 2; - if ( (jpegPtr < jpegEnd) && ((marker & 0xFFF8) != 0xFFD0) && - (marker != 0xFF01) && (marker != 0xFFD8) && (marker != 0xFFD9) ) { - jpegPtr += GetUns16BE ( jpegPtr ); - } - } - - if ( jpegPtr >= jpegEnd ) return; // Ran out of data. - if ( marker != 0xFFDA ) return; // Not an SOS marker. - jpegPtr += 2; - length = GetUns16BE ( jpegPtr ); - jpegPtr += length; - - // Now look for a non-RST marker. We're in the image data, must scan one byte at a time. - - while ( jpegPtr < jpegEnd ) { - if ( *jpegPtr != 0xFF ) { - ++jpegPtr; - } else { - marker = GetUns16BE ( jpegPtr ); - if ( (0xFF01 <= marker) && (marker <= 0xFFFE) && ((marker & 0xFFF8) != 0xFFD0) ) break; - jpegPtr += 2; - } - } - - if ( jpegPtr >= jpegEnd ) return; // Ran out of data. - if ( marker != 0xFFDC ) return; // Not a DNL marker. - jpegPtr += 2; - length = GetUns16BE ( jpegPtr ); - if ( length != 4 ) return; // Bad DNL marker segment length. - - *height = GetUns16BE ( jpegPtr+2 ); - -} // GetJPEGDimensions - -// ================================================================================================= -// TIFF_Manager::GetTNailInfo -// ========================== -// -// Gather the info for a native Exif thumbnail, if there is one. We only return full info for a JPEG -// compressed thumbnail. -// - There must be at least 2 top level IFDs, the second is the thumbnail. -// - The Exif IFD must be present. -// - The thumbnail IFD must have tag 259, Compression. -// - A JPEG compressed thumbnail must have tags 513 and 514, JPEGInterchangeFormat and JPEGInterchangeFormatLength. -// -// Tag 259 (Compression) in the thumbnail IFD defines the thumbnail compression scheme. It is 1 for -// uncompressed and 6 for JPEG compressed. If the thumbnail is JPEG compressed, then tag 513 -// (JPEGInterchangeFormat) in the thumbnail IFD is the offset of the thumbnail image stream (to the -// SOI) and tag 514 (JPEGInterchangeFormatLength) is the length of the stream in bytes. Yes, -// another stupid Exif mistake of putting an explicit offset in the TIFF info (type LONG, count 1) -// instead of a properly typed data block! -// -// The full image dimensions for an Exif-compliant compressed JPEG image are in tags 40962 -// (PixelXDimension) and 40963 (PixelYDimension) of the Exif IFD. -// -// The dimensions of an Exif-compliant uncompressed (TIFF) thumbnail are in tags 256 (ImageWidth) -// and 257 (ImageLength) of the thumbnail IFD. The dimensions of an Exif-compliant compressed -// (JPEG) thumbnail are within the JPEG stream of the thumbnail. The JPEG dimensions should be in -// the X (width) and Y (height) components of the SOF marker segment. A 0 value for Y says that the -// height is in the NL component of the DNL marker segment at the end of the first scan. - -bool TIFF_Manager::GetTNailInfo ( XMP_ThumbnailInfo * tnailInfo ) const -{ - bool found; - XMP_Uns16 compression; - - enum { kUncompressedTNail = 1, kJPEGCompressedTNail = 6 }; - - if ( tnailInfo == 0 ) return false; - - // Make sure the required IFDs and tags are present. - - if ( (! this->HasExifIFD()) || (! this->HasThumbnailIFD()) ) return false; - - found = this->GetTag_Short ( kTIFF_TNailIFD, kTIFF_Compression, &compression ); - if ( ! found ) return false; - if ( (compression != kUncompressedTNail) && (compression != kJPEGCompressedTNail) ) return false; - - // Gather the info that depends on the thumbnail format. - - if ( compression == kUncompressedTNail ) { - - // Gather the info for an uncompressed thumbnail. Just the format, width, and height. - - tnailInfo->tnailFormat = kXMP_TIFFTNail; - (void) this->GetTag_Integer ( kTIFF_TNailIFD, kTIFF_ImageWidth, &tnailInfo->tnailWidth ); - (void) this->GetTag_Integer ( kTIFF_TNailIFD, kTIFF_ImageLength, &tnailInfo->tnailHeight ); - - } else { - - // Gather the info for a JPEG compressed thumbnail. The JPEG stream pointer is special, the - // type/count of tag 513 is LONG/1 - thank once again Exif! The pointer was set when parsing - // the TIFF stream. That is when we have to capture the stream for file-based TIFF. - - XMP_Uns32 jpegOffset, jpegLength; - found = this->GetTag_Long ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat, &jpegOffset ); - if ( ! found ) return false; - found = this->GetTag_Long ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormatLength, &jpegLength ); - if ( ! found ) return false; - - XMP_Assert ( this->jpegTNailPtr != 0 ); - - tnailInfo->tnailFormat = kXMP_JPEGTNail; - tnailInfo->tnailImage = this->jpegTNailPtr; - tnailInfo->tnailSize = jpegLength; - - GetJPEGDimensions ( tnailInfo->tnailImage, tnailInfo->tnailSize, - &tnailInfo->tnailWidth, &tnailInfo->tnailHeight ); - - } - - // If we get here there is a thumbnail of some sort. Gether remaining common info. - - (void) this->GetTag_Integer ( kTIFF_ExifIFD, kTIFF_PixelXDimension, &tnailInfo->fullWidth ); - (void) this->GetTag_Integer ( kTIFF_ExifIFD, kTIFF_PixelYDimension, &tnailInfo->fullHeight ); - (void) this->GetTag_Short ( kTIFF_PrimaryIFD, kTIFF_Orientation, &tnailInfo->fullOrientation ); - - found = this->GetTag_Short ( kTIFF_TNailIFD, kTIFF_Orientation, &tnailInfo->tnailOrientation ); - if ( ! found ) tnailInfo->tnailOrientation = tnailInfo->fullOrientation; - - return true; - -} // TIFF_Manager::GetTNailInfo - -// ================================================================================================= diff --git a/source/XMPFiles/FormatSupport/TIFF_Support.hpp b/source/XMPFiles/FormatSupport/TIFF_Support.hpp index 1394b99..94f6198 100644 --- a/source/XMPFiles/FormatSupport/TIFF_Support.hpp +++ b/source/XMPFiles/FormatSupport/TIFF_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -38,15 +38,12 @@ /// entirely in memory. Think of TIFF_FileWriter as "file-based OR read-write". TIFF_FileWriter only /// maintains information for tags of interest as metadata. /// -/// The needs of XMPFiles are well defined metadata access. Only 5 IFDs are recognized: +/// The needs of XMPFiles are well defined metadata access. Only 4 IFDs are processed: /// \li The 0th IFD, for the primary image, the first one in the outer list of IFDs. -/// \li The 1st IFD, for an Exif thumbnail, the second one in the outer list of IFDs. /// \li The Exif general metadata IFD, from tag 34665 in the primary image IFD. /// \li The Exif GPS Info metadata IFD, from tag 34853 in the primary image IFD. /// \li The Exif Interoperability IFD, from tag 40965 in the Exif general metadata IFD. /// -/// \note In the future we should add support for the non-Exif thumbnails in DNG (TIFF/EP) files. -/// /// \note These classes are for use only when directly compiled and linked. They should not be /// packaged in a DLL by themselves. They do not provide any form of C++ ABI protection. // ================================================================================================= @@ -60,7 +57,7 @@ enum { // Constants for the recognized IFDs. kTIFF_PrimaryIFD = 0, // The primary image IFD, also called the 0th IFD. - kTIFF_TNailIFD = 1, // The thumbnail image IFD also called the 1st IFD. + kTIFF_TNailIFD = 1, // The thumbnail image IFD also called the 1st IFD. (not used) kTIFF_ExifIFD = 2, // The Exif general metadata IFD. kTIFF_GPSInfoIFD = 3, // The Exif GPS Info IFD. kTIFF_InteropIFD = 4, // The Exif Interoperability IFD. @@ -143,7 +140,7 @@ enum { kTIFF_GPSInfoIFDPointer = 34853, kTIFF_DNGVersion = 50706, kTIFF_DNGBackwardVersion = 50707, - + // Additional thumbnail IFD tags. We also care about 256, 257, and 259 in thumbnails. kTIFF_JPEGInterchangeFormat = 513, kTIFF_JPEGInterchangeFormatLength = 514, @@ -218,7 +215,7 @@ enum { kTIFF_DeviceSettingDescription = 41995, kTIFF_SubjectDistanceRange = 41996, kTIFF_ImageUniqueID = 42016, - + kTIFF_MakerNote = 37500, // Gets deleted when rewriting memory-based TIFF. // GPS IFD tags. @@ -254,7 +251,7 @@ enum { kTIFF_GPSAreaInformation = 28, kTIFF_GPSDateStamp = 29, kTIFF_GPSDifferential = 30 - + }; // ------------------------------------------------------------------ @@ -437,7 +434,7 @@ public: static const size_t kEmptyTIFFLength = 8; // Just the header. static const size_t kEmptyIFDLength = 2 + 4; // Entry count and next-IFD offset. static const size_t kIFDEntryLength = 12; - + struct TagInfo { XMP_Uns16 id; XMP_Uns16 type; @@ -448,7 +445,7 @@ public: TagInfo ( XMP_Uns16 _id, XMP_Uns16 _type, XMP_Uns32 _count, const void* _dataPtr, XMP_Uns32 _dataLen ) : id(_id), type(_type), count(_count), dataPtr(_dataPtr), dataLen(_dataLen) {}; }; - + typedef std::map<XMP_Uns16,TagInfo> TagInfoMap; struct Rational { XMP_Uns32 num, denom; }; @@ -458,15 +455,14 @@ public: // The IsXyzEndian methods return the external endianness of the original parsed TIFF stream. // The \c GetTag methods return native endian values, the \c SetTag methods take native values. // The original endianness is preserved in output. - + bool IsBigEndian() const { return this->bigEndian; }; bool IsLittleEndian() const { return (! this->bigEndian); }; bool IsNativeEndian() const { return this->nativeEndian; }; - + // --------------------------------------------------------------------------------------------- - // The TIFF_Manager only keeps explicit knowledge of up to 5 IFDs: + // The TIFF_Manager only keeps explicit knowledge of up to 4 IFDs: // - The primary image IFD, also known as the 0th IFD. This must be present. - // - A possible thumbnail IFD, also known as the 1st IFD, chained from the primary image IFD. // - A possible Exif general metadata IFD, found from tag 34665 in the primary image IFD. // - A possible Exif GPS metadata IFD, found from tag 34853 in the primary image IFD. // - A possible Exif Interoperability IFD, found from tag 40965 in the Exif general metadata IFD. @@ -476,11 +472,10 @@ public: // removed. Parsing will sort the tags into ascending order, AppendTIFF and ComposeTIFF will // preserve the sorted order. These fixes do not cause IsChanged to return true, that only // happens if the client makes explicit changes using SetTag or DeleteTag. - - virtual bool HasThumbnailIFD() const = 0; + virtual bool HasExifIFD() const = 0; virtual bool HasGPSInfoIFD() const = 0; - + // --------------------------------------------------------------------------------------------- // These are the basic methods to get a map of all of the tags in an IFD, to get or set a tag, // or to delete a tag. The dataPtr returned by \c GetTag is consided read-only, the client must @@ -491,17 +486,17 @@ public: // \c SetTag replaces an existing tag regardless of type or count. \c DeleteTag deletes a tag, // it is a no-op if the tag does not exist. \c GetValueOffset returns the offset within the // parsed stream of the tag's value. It returns 0 if the tag was not in the parsed input. - + virtual bool GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const = 0; - + virtual bool GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const = 0; virtual void SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* dataPtr ) = 0; - + virtual void DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) = 0; - + virtual XMP_Uns32 GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const = 0; - + // --------------------------------------------------------------------------------------------- // These methods are for tags whose type can be short or long, depending on the actual value. // \c GetTag_Integer returns false if an existing tag's type is not short, or long, or if the @@ -511,7 +506,7 @@ public: virtual bool GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const = 0; void SetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 data ); - + // --------------------------------------------------------------------------------------------- // These are customized forms of GetTag that verify the type and return a typed value. False is // returned if the type does not match or if the count is not 1. @@ -532,7 +527,7 @@ public: virtual bool GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const = 0; virtual bool GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const = 0; - + // --------------------------------------------------------------------------------------------- void SetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8 data ); @@ -549,18 +544,14 @@ public: void SetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double data ); void SetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr dataPtr ); - + // --------------------------------------------------------------------------------------------- virtual bool GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const = 0; virtual void SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ) = 0; - + bool DecodeString ( const void * encodedPtr, size_t encodedLen, std::string* utf8Str ) const; bool EncodeString ( const std::string& utf8Str, XMP_Uns8 encoding, std::string* encodedStr ); - - // --------------------------------------------------------------------------------------------- - - bool GetTNailInfo ( XMP_ThumbnailInfo * tnailInfo ) const; // --------------------------------------------------------------------------------------------- // \c IsChanged returns true if a read-write stream has changes that need to be saved. This is @@ -596,17 +587,17 @@ public: // The condenseStream parameter to UpdateMemoryStream can be used to rewrite the full stream // instead of appending. This will discard any MakerNote tags and risks breaking offsets that // are hidden. This can be necessary though to try to make the TIFF fit in a JPEG file. - + virtual void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true ) = 0; virtual void ParseFileStream ( LFA_FileRef fileRef ) = 0; - + virtual void IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ) = 0; - + virtual XMP_Uns32 UpdateMemoryStream ( void** dataPtr, bool condenseStream = false ) = 0; virtual void UpdateFileStream ( LFA_FileRef fileRef ) = 0; - + // --------------------------------------------------------------------------------------------- - + GetUns16_Proc GetUns16; // Get values from the TIFF stream. GetUns32_Proc GetUns32; // Always native endian on the outside, stream endian in the stream. GetFloat_Proc GetFloat; @@ -618,18 +609,13 @@ public: PutDouble_Proc PutDouble; virtual ~TIFF_Manager() {}; - - // ! Hacks to help the reconciliation code accomodate Photoshop behavior: - bool xmpHadUserComment, xmpHadRelatedSoundFile; protected: bool bigEndian, nativeEndian; - - XMP_Uns8 * jpegTNailPtr; XMP_Uns32 CheckTIFFHeader ( const XMP_Uns8* tiffPtr, XMP_Uns32 length ); - + TIFF_Manager(); // Force clients to use the reader or writer derived classes. struct RawIFDEntry { @@ -653,18 +639,17 @@ protected: class TIFF_MemoryReader : public TIFF_Manager { // The derived class for memory-based read-only access. public: - bool HasThumbnailIFD() const { return (containedIFDs[kTIFF_TNailIFD].count != 0); }; bool HasExifIFD() const { return (containedIFDs[kTIFF_ExifIFD].count != 0); }; bool HasGPSInfoIFD() const { return (containedIFDs[kTIFF_GPSInfoIFD].count != 0); }; bool GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const; - + bool GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const; void SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* dataPtr ) { NotAppropriate(); }; - + void DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) { NotAppropriate(); }; - + XMP_Uns32 GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const; bool GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const; @@ -687,18 +672,18 @@ public: bool GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const; void SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ) { NotAppropriate(); }; - + bool IsChanged() { return false; }; bool IsLegacyChanged() { return false; }; - + void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true ); void ParseFileStream ( LFA_FileRef fileRef ) { NotAppropriate(); }; - + void IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ) { NotAppropriate(); }; - + XMP_Uns32 UpdateMemoryStream ( void** dataPtr, bool condenseStream = false ) { if ( dataPtr != 0 ) *dataPtr = tiffStream; return tiffLength; }; void UpdateFileStream ( LFA_FileRef fileRef ) { NotAppropriate(); }; - + TIFF_MemoryReader() : ownedStream(false), tiffStream(0), tiffLength(0) {}; virtual ~TIFF_MemoryReader() { if ( this->ownedStream ) free ( this->tiffStream ); }; @@ -722,26 +707,26 @@ private: XMP_Uns32 dataOrPos; TweakedIFDEntry() : id(0), type(0), bytes(0), dataOrPos(0) {}; }; - + struct TweakedIFDInfo { XMP_Uns16 count; TweakedIFDEntry* entries; TweakedIFDInfo() : count(0), entries(0) {}; }; - + TweakedIFDInfo containedIFDs[kTIFF_KnownIFDCount]; static void SortIFD ( TweakedIFDInfo* thisIFD ); XMP_Uns32 ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ); - + const TweakedIFDEntry* FindTagInIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) const; - + const inline void* GetDataPtr ( const TweakedIFDEntry* tifdEntry ) const { if ( tifdEntry->bytes <= 4 ) return &tifdEntry->dataOrPos; else return (this->tiffStream + tifdEntry->dataOrPos); }; static inline void NotAppropriate() { XMP_Throw ( "Not appropriate for TIFF_Reader", kXMPErr_InternalFailure ); }; - + }; // TIFF_MemoryReader @@ -756,18 +741,17 @@ private: class TIFF_FileWriter : public TIFF_Manager { // The derived class for file-based or read-write access. public: - bool HasThumbnailIFD() const { return this->containedIFDs[kTIFF_TNailIFD].tagMap.size() != 0; }; bool HasExifIFD() const { return this->containedIFDs[kTIFF_ExifIFD].tagMap.size() != 0; }; bool HasGPSInfoIFD() const { return this->containedIFDs[kTIFF_GPSInfoIFD].tagMap.size() != 0; }; bool GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const; - + bool GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const; void SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* dataPtr ); - + void DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ); - + XMP_Uns32 GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const; bool GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const; @@ -790,13 +774,13 @@ public: bool GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const; void SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ); - + bool IsChanged() { return this->changed; }; - + bool IsLegacyChanged(); - + enum { kDoNotCopyData = false }; - + void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true ); void ParseFileStream ( LFA_FileRef fileRef ); @@ -824,13 +808,13 @@ private: // the smallValue field for small values. This is also the usage when a tag is changed (for both // memory and file cases), the dataPtr is a separate allocation for large values (over 4 bytes), // and points to the smallValue field for small values. - + // ! The working data values are always stream endian, no matter where stored. They are flipped // ! as necessary by GetTag and SetTag. - + static const bool kIsFileBased = true; // For use in the InternalTagInfo constructor. static const bool kIsMemoryBased = false; - + class InternalTagInfo { public: @@ -875,9 +859,9 @@ private: origDataLen(0), origDataOffset(0), changed(false), fileBased(false) {}; }; - + typedef std::map<XMP_Uns16,InternalTagInfo> InternalTagMap; - + struct InternalIFDInfo { bool changed; XMP_Uns16 origCount; // Original number of IFD entries. @@ -893,12 +877,12 @@ private: this->tagMap.clear(); }; }; - + InternalIFDInfo containedIFDs[kTIFF_KnownIFDCount]; - + static XMP_Uns8 PickIFD ( XMP_Uns8 ifd, XMP_Uns16 id ); const InternalTagInfo* FindTagInIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) const; - + void DeleteExistingInfo(); XMP_Uns32 ProcessMemoryIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ); @@ -908,10 +892,8 @@ private: void* CopyTagToMasterIFD ( const TagInfo& ps6Tag, InternalIFDInfo* masterIFD ); - void UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out, - bool appendAll = false, XMP_Uns32 extraSpace = 0 ); - void UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out ); - + void PreflightIFDLinkage(); + XMP_Uns32 DetermineVisibleLength(); XMP_Uns32 DetermineAppendInfo ( XMP_Uns32 appendedOrigin, @@ -919,8 +901,12 @@ private: XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount], bool appendAll = false ); + void UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out, + bool appendAll = false, XMP_Uns32 extraSpace = 0 ); + void UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out ); + void WriteFileIFD ( LFA_FileRef fileRef, InternalIFDInfo & thisIFD ); - + }; // TIFF_FileWriter diff --git a/source/XMPFiles/FormatSupport/XDCAM_Support.cpp b/source/XMPFiles/FormatSupport/XDCAM_Support.cpp index ac0fe1e..b86e69e 100644 --- a/source/XMPFiles/FormatSupport/XDCAM_Support.cpp +++ b/source/XMPFiles/FormatSupport/XDCAM_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -94,7 +94,7 @@ bool GetLegacyMetaData ( SXMPMeta * xmpObjPtr, } // Modify Date - if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_XMP, "LastUpdate" )) ) { + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_XMP, "ModifyDate" )) ) { legacyProp = rootElem->GetNamedElement ( legacyNS, "LastUpdate" ); if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) { XMP_StringPtr legacyValue = legacyProp->GetAttrValue ( "value" ); @@ -106,7 +106,7 @@ bool GetLegacyMetaData ( SXMPMeta * xmpObjPtr, } // Metadata Modify Date - if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_XMP, "lastUpdate" )) ) { + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_XMP, "MetadataDate" )) ) { legacyProp = rootElem->GetNamedElement ( legacyNS, "lastUpdate" ); if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) { XMP_StringPtr legacyValue = legacyProp->GetAttrValue ( "value" ); @@ -117,6 +117,18 @@ bool GetLegacyMetaData ( SXMPMeta * xmpObjPtr, } } + // Description + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DC, "description" )) ) { + legacyProp = rootElem->GetNamedElement ( legacyNS, "Description" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + XMP_StringPtr legacyValue = legacyProp->GetLeafContentValue(); + if ( legacyValue != 0 ) { + xmpObjPtr->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", legacyValue, kXMP_DeleteExisting ); + containsXMP = true; + } + } + } + legacyContext = rootElem->GetNamedElement ( legacyNS, "VideoFormat" ); if ( legacyContext != 0 ) { @@ -228,6 +240,54 @@ bool GetLegacyMetaData ( SXMPMeta * xmpObjPtr, } + legacyContext = rootElem->GetNamedElement ( legacyNS, "Device" ); + if ( legacyContext != 0 ) { + + std::string model; + + // manufacturer string + XMP_StringPtr manufacturer = legacyContext->GetAttrValue ( "manufacturer" ); + if ( manufacturer != 0 ) { + model += manufacturer; + } + + // model string + XMP_StringPtr modelName = legacyContext->GetAttrValue ( "modelName" ); + if ( modelName != 0 ) { + if ( model.size() > 0 ) { + model += " "; + } + model += modelName; + } + + + // For the dm::cameraModel property, concat the make and model. + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DM, "cameraModel" )) ) { + if ( model.size() != 0 ) { + xmpObjPtr->SetProperty ( kXMP_NS_DM, "cameraModel", model, kXMP_DeleteExisting ); + containsXMP = true; + } + } + + // EXIF Model + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_TIFF, "Model" )) ) { + xmpObjPtr->SetProperty ( kXMP_NS_TIFF, "Model", modelName, kXMP_DeleteExisting ); + } + + // EXIF Make + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_TIFF, "Make" )) ) { + xmpObjPtr->SetProperty ( kXMP_NS_TIFF, "Make", manufacturer, kXMP_DeleteExisting ); + } + + // EXIF-AUX Serial number + XMP_StringPtr serialNumber = legacyContext->GetAttrValue ( "serialNo" ); + if ( serialNumber != 0 && (digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_EXIF_Aux, "SerialNumber" ))) ) { + xmpObjPtr->SetProperty ( kXMP_NS_EXIF_Aux, "SerialNumber", serialNumber, kXMP_DeleteExisting ); + } + + } + + return containsXMP; } diff --git a/source/XMPFiles/FormatSupport/XDCAM_Support.hpp b/source/XMPFiles/FormatSupport/XDCAM_Support.hpp index 3fe1e65..51811c7 100644 --- a/source/XMPFiles/FormatSupport/XDCAM_Support.hpp +++ b/source/XMPFiles/FormatSupport/XDCAM_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/XMPScanner.cpp b/source/XMPFiles/FormatSupport/XMPScanner.cpp index 4396a25..a67c2c6 100644 --- a/source/XMPFiles/FormatSupport/XMPScanner.cpp +++ b/source/XMPFiles/FormatSupport/XMPScanner.cpp @@ -1,5 +1,5 @@ // ================================================================================================= -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/XMPScanner.hpp b/source/XMPFiles/FormatSupport/XMPScanner.hpp index 2c8b6fa..472a43e 100644 --- a/source/XMPFiles/FormatSupport/XMPScanner.hpp +++ b/source/XMPFiles/FormatSupport/XMPScanner.hpp @@ -2,7 +2,7 @@ #define __XMPScanner_hpp__ // ================================================================================================= -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms |