summaryrefslogtreecommitdiff
path: root/source/XMPFiles/FormatSupport
diff options
context:
space:
mode:
Diffstat (limited to 'source/XMPFiles/FormatSupport')
-rw-r--r--source/XMPFiles/FormatSupport/ASF_Support.cpp18
-rw-r--r--source/XMPFiles/FormatSupport/ASF_Support.hpp2
-rw-r--r--source/XMPFiles/FormatSupport/ID3_Support.cpp1156
-rw-r--r--source/XMPFiles/FormatSupport/ID3_Support.hpp775
-rw-r--r--source/XMPFiles/FormatSupport/IPTC_Support.cpp234
-rw-r--r--source/XMPFiles/FormatSupport/IPTC_Support.hpp36
-rw-r--r--source/XMPFiles/FormatSupport/ISOBaseMedia_Support.cpp149
-rw-r--r--source/XMPFiles/FormatSupport/ISOBaseMedia_Support.hpp100
-rw-r--r--source/XMPFiles/FormatSupport/MOOV_Support.cpp542
-rw-r--r--source/XMPFiles/FormatSupport/MOOV_Support.hpp215
-rw-r--r--source/XMPFiles/FormatSupport/MacScriptExtracts.h244
-rw-r--r--source/XMPFiles/FormatSupport/PNG_Support.cpp2
-rw-r--r--source/XMPFiles/FormatSupport/PNG_Support.hpp2
-rw-r--r--source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp145
-rw-r--r--source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp2
-rw-r--r--source/XMPFiles/FormatSupport/PSIR_Support.hpp63
-rw-r--r--source/XMPFiles/FormatSupport/QuickTime_Support.cpp1157
-rw-r--r--source/XMPFiles/FormatSupport/QuickTime_Support.hpp98
-rw-r--r--source/XMPFiles/FormatSupport/RIFF.cpp879
-rw-r--r--source/XMPFiles/FormatSupport/RIFF.hpp316
-rw-r--r--source/XMPFiles/FormatSupport/RIFF_Support.cpp1467
-rw-r--r--source/XMPFiles/FormatSupport/RIFF_Support.hpp189
-rw-r--r--source/XMPFiles/FormatSupport/ReconcileIPTC.cpp721
-rw-r--r--source/XMPFiles/FormatSupport/ReconcileLegacy.cpp273
-rw-r--r--source/XMPFiles/FormatSupport/ReconcileLegacy.hpp118
-rw-r--r--source/XMPFiles/FormatSupport/ReconcileTIFF.cpp2073
-rw-r--r--source/XMPFiles/FormatSupport/Reconcile_Impl.cpp223
-rw-r--r--source/XMPFiles/FormatSupport/Reconcile_Impl.hpp79
-rw-r--r--source/XMPFiles/FormatSupport/SWF_Support.cpp6
-rw-r--r--source/XMPFiles/FormatSupport/SWF_Support.hpp2
-rw-r--r--source/XMPFiles/FormatSupport/TIFF_FileWriter.cpp667
-rw-r--r--source/XMPFiles/FormatSupport/TIFF_MemoryReader.cpp157
-rw-r--r--source/XMPFiles/FormatSupport/TIFF_Support.cpp220
-rw-r--r--source/XMPFiles/FormatSupport/TIFF_Support.hpp140
-rw-r--r--source/XMPFiles/FormatSupport/XDCAM_Support.cpp66
-rw-r--r--source/XMPFiles/FormatSupport/XDCAM_Support.hpp2
-rw-r--r--source/XMPFiles/FormatSupport/XMPScanner.cpp2
-rw-r--r--source/XMPFiles/FormatSupport/XMPScanner.hpp2
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