summaryrefslogtreecommitdiff
path: root/XMPFiles/source
diff options
context:
space:
mode:
Diffstat (limited to 'XMPFiles/source')
-rw-r--r--XMPFiles/source/FileHandlers/AIFF_Handler.cpp427
-rw-r--r--XMPFiles/source/FileHandlers/AIFF_Handler.hpp158
-rw-r--r--XMPFiles/source/FileHandlers/ASF_Handler.cpp339
-rw-r--r--XMPFiles/source/FileHandlers/ASF_Handler.hpp64
-rw-r--r--XMPFiles/source/FileHandlers/AVCHD_Handler.cpp2280
-rw-r--r--XMPFiles/source/FileHandlers/AVCHD_Handler.hpp81
-rw-r--r--XMPFiles/source/FileHandlers/Basic_Handler.cpp243
-rw-r--r--XMPFiles/source/FileHandlers/Basic_Handler.hpp117
-rw-r--r--XMPFiles/source/FileHandlers/FLV_Handler.cpp735
-rw-r--r--XMPFiles/source/FileHandlers/FLV_Handler.hpp80
-rw-r--r--XMPFiles/source/FileHandlers/InDesign_Handler.cpp419
-rw-r--r--XMPFiles/source/FileHandlers/InDesign_Handler.hpp68
-rw-r--r--XMPFiles/source/FileHandlers/JPEG_Handler.cpp1037
-rw-r--r--XMPFiles/source/FileHandlers/JPEG_Handler.hpp98
-rw-r--r--XMPFiles/source/FileHandlers/MP3_Handler.cpp748
-rw-r--r--XMPFiles/source/FileHandlers/MP3_Handler.hpp93
-rw-r--r--XMPFiles/source/FileHandlers/MPEG2_Handler.cpp220
-rw-r--r--XMPFiles/source/FileHandlers/MPEG2_Handler.hpp65
-rw-r--r--XMPFiles/source/FileHandlers/MPEG4_Handler.cpp2581
-rw-r--r--XMPFiles/source/FileHandlers/MPEG4_Handler.hpp95
-rw-r--r--XMPFiles/source/FileHandlers/P2_Handler.cpp1401
-rw-r--r--XMPFiles/source/FileHandlers/P2_Handler.hpp110
-rw-r--r--XMPFiles/source/FileHandlers/PNG_Handler.cpp248
-rw-r--r--XMPFiles/source/FileHandlers/PNG_Handler.hpp62
-rw-r--r--XMPFiles/source/FileHandlers/PSD_Handler.cpp417
-rw-r--r--XMPFiles/source/FileHandlers/PSD_Handler.hpp72
-rw-r--r--XMPFiles/source/FileHandlers/PostScript_Handler.cpp574
-rw-r--r--XMPFiles/source/FileHandlers/PostScript_Handler.hpp68
-rw-r--r--XMPFiles/source/FileHandlers/RIFF_Handler.cpp356
-rw-r--r--XMPFiles/source/FileHandlers/RIFF_Handler.hpp73
-rw-r--r--XMPFiles/source/FileHandlers/SWF_Handler.cpp330
-rw-r--r--XMPFiles/source/FileHandlers/SWF_Handler.hpp72
-rw-r--r--XMPFiles/source/FileHandlers/Scanner_Handler.cpp347
-rw-r--r--XMPFiles/source/FileHandlers/Scanner_Handler.hpp42
-rw-r--r--XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp848
-rw-r--r--XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp79
-rw-r--r--XMPFiles/source/FileHandlers/TIFF_Handler.cpp403
-rw-r--r--XMPFiles/source/FileHandlers/TIFF_Handler.hpp68
-rw-r--r--XMPFiles/source/FileHandlers/Trivial_Handler.cpp74
-rw-r--r--XMPFiles/source/FileHandlers/Trivial_Handler.hpp47
-rw-r--r--XMPFiles/source/FileHandlers/UCF_Handler.cpp856
-rw-r--r--XMPFiles/source/FileHandlers/UCF_Handler.hpp722
-rw-r--r--XMPFiles/source/FileHandlers/WAVE_Handler.cpp480
-rw-r--r--XMPFiles/source/FileHandlers/WAVE_Handler.hpp172
-rw-r--r--XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp890
-rw-r--r--XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp85
-rw-r--r--XMPFiles/source/FileHandlers/XDCAM_Handler.cpp837
-rw-r--r--XMPFiles/source/FileHandlers/XDCAM_Handler.hpp87
-rw-r--r--XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.cpp302
-rw-r--r--XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h152
-rw-r--r--XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.cpp46
-rw-r--r--XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h63
-rw-r--r--XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.cpp63
-rw-r--r--XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h40
-rw-r--r--XMPFiles/source/FormatSupport/ASF_Support.cpp1438
-rw-r--r--XMPFiles/source/FormatSupport/ASF_Support.hpp226
-rw-r--r--XMPFiles/source/FormatSupport/ID3_Support.cpp504
-rw-r--r--XMPFiles/source/FormatSupport/ID3_Support.hpp309
-rw-r--r--XMPFiles/source/FormatSupport/IFF/Chunk.cpp1182
-rw-r--r--XMPFiles/source/FormatSupport/IFF/Chunk.h462
-rw-r--r--XMPFiles/source/FormatSupport/IFF/ChunkController.cpp709
-rw-r--r--XMPFiles/source/FormatSupport/IFF/ChunkController.h246
-rw-r--r--XMPFiles/source/FormatSupport/IFF/ChunkPath.cpp239
-rw-r--r--XMPFiles/source/FormatSupport/IFF/ChunkPath.h190
-rw-r--r--XMPFiles/source/FormatSupport/IFF/IChunkBehavior.cpp595
-rw-r--r--XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h239
-rw-r--r--XMPFiles/source/FormatSupport/IFF/IChunkContainer.h87
-rw-r--r--XMPFiles/source/FormatSupport/IFF/IChunkData.h108
-rw-r--r--XMPFiles/source/FormatSupport/IPTC_Support.cpp758
-rw-r--r--XMPFiles/source/FormatSupport/IPTC_Support.hpp305
-rw-r--r--XMPFiles/source/FormatSupport/ISOBaseMedia_Support.cpp153
-rw-r--r--XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp104
-rw-r--r--XMPFiles/source/FormatSupport/MOOV_Support.cpp554
-rw-r--r--XMPFiles/source/FormatSupport/MOOV_Support.hpp217
-rw-r--r--XMPFiles/source/FormatSupport/MacScriptExtracts.h244
-rw-r--r--XMPFiles/source/FormatSupport/PNG_Support.cpp341
-rw-r--r--XMPFiles/source/FormatSupport/PNG_Support.hpp78
-rw-r--r--XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp602
-rw-r--r--XMPFiles/source/FormatSupport/PSIR_MemoryReader.cpp112
-rw-r--r--XMPFiles/source/FormatSupport/PSIR_Support.hpp328
-rw-r--r--XMPFiles/source/FormatSupport/QuickTime_Support.cpp1150
-rw-r--r--XMPFiles/source/FormatSupport/QuickTime_Support.hpp105
-rw-r--r--XMPFiles/source/FormatSupport/RIFF.cpp882
-rw-r--r--XMPFiles/source/FormatSupport/RIFF.hpp322
-rw-r--r--XMPFiles/source/FormatSupport/RIFF_Support.cpp939
-rw-r--r--XMPFiles/source/FormatSupport/RIFF_Support.hpp42
-rw-r--r--XMPFiles/source/FormatSupport/ReconcileIPTC.cpp857
-rw-r--r--XMPFiles/source/FormatSupport/ReconcileLegacy.cpp205
-rw-r--r--XMPFiles/source/FormatSupport/ReconcileLegacy.hpp272
-rw-r--r--XMPFiles/source/FormatSupport/ReconcileTIFF.cpp3443
-rw-r--r--XMPFiles/source/FormatSupport/Reconcile_Impl.cpp431
-rw-r--r--XMPFiles/source/FormatSupport/Reconcile_Impl.hpp104
-rw-r--r--XMPFiles/source/FormatSupport/SWF_Support.cpp484
-rw-r--r--XMPFiles/source/FormatSupport/SWF_Support.hpp86
-rw-r--r--XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp1986
-rw-r--r--XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp645
-rw-r--r--XMPFiles/source/FormatSupport/TIFF_Support.cpp439
-rw-r--r--XMPFiles/source/FormatSupport/TIFF_Support.hpp964
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp347
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h99
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/CartMetadata.cpp316
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/CartMetadata.h87
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.cpp240
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.h91
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/DISPMetadata.cpp138
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/DISPMetadata.h97
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/INFOMetadata.cpp260
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/INFOMetadata.h88
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.cpp231
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.h90
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.cpp682
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h215
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.cpp576
-rw-r--r--XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.h66
-rw-r--r--XMPFiles/source/FormatSupport/XDCAM_Support.cpp480
-rw-r--r--XMPFiles/source/FormatSupport/XDCAM_Support.hpp50
-rw-r--r--XMPFiles/source/FormatSupport/XMPScanner.cpp1450
-rw-r--r--XMPFiles/source/FormatSupport/XMPScanner.hpp330
-rw-r--r--XMPFiles/source/HandlerRegistry.cpp961
-rw-r--r--XMPFiles/source/HandlerRegistry.h238
-rw-r--r--XMPFiles/source/NativeMetadataSupport/IMetadata.cpp206
-rw-r--r--XMPFiles/source/NativeMetadataSupport/IMetadata.h334
-rw-r--r--XMPFiles/source/NativeMetadataSupport/IReconcile.cpp493
-rw-r--r--XMPFiles/source/NativeMetadataSupport/IReconcile.h139
-rw-r--r--XMPFiles/source/NativeMetadataSupport/MetadataSet.cpp127
-rw-r--r--XMPFiles/source/NativeMetadataSupport/MetadataSet.h100
-rw-r--r--XMPFiles/source/NativeMetadataSupport/ValueObject.h143
-rw-r--r--XMPFiles/source/PluginHandler/FileHandler.h101
-rw-r--r--XMPFiles/source/PluginHandler/FileHandlerInstance.cpp106
-rw-r--r--XMPFiles/source/PluginHandler/FileHandlerInstance.h47
-rw-r--r--XMPFiles/source/PluginHandler/HostAPIImpl.cpp637
-rw-r--r--XMPFiles/source/PluginHandler/Module.cpp169
-rw-r--r--XMPFiles/source/PluginHandler/Module.h65
-rw-r--r--XMPFiles/source/PluginHandler/ModuleUtils.h77
-rw-r--r--XMPFiles/source/PluginHandler/OS_Utils_Linux.cpp230
-rw-r--r--XMPFiles/source/PluginHandler/OS_Utils_Mac.cpp366
-rw-r--r--XMPFiles/source/PluginHandler/OS_Utils_WIN.cpp66
-rw-r--r--XMPFiles/source/PluginHandler/PluginManager.cpp779
-rw-r--r--XMPFiles/source/PluginHandler/PluginManager.h190
-rw-r--r--XMPFiles/source/PluginHandler/XMPAtoms.cpp408
-rw-r--r--XMPFiles/source/PluginHandler/XMPAtoms.h200
-rw-r--r--XMPFiles/source/WXMPFiles.cpp345
-rw-r--r--XMPFiles/source/XMPFiles.cpp1084
-rw-r--r--XMPFiles/source/XMPFiles.hpp252
-rw-r--r--XMPFiles/source/XMPFiles_Impl.cpp416
-rw-r--r--XMPFiles/source/XMPFiles_Impl.hpp329
146 files changed, 58661 insertions, 0 deletions
diff --git a/XMPFiles/source/FileHandlers/AIFF_Handler.cpp b/XMPFiles/source/FileHandlers/AIFF_Handler.cpp
new file mode 100644
index 0000000..bd59ea7
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/AIFF_Handler.cpp
@@ -0,0 +1,427 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FileHandlers/AIFF_Handler.hpp"
+#include "XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h"
+#include "XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h"
+#include "XMPFiles/source/NativeMetadataSupport/MetadataSet.h"
+#include "source/XIO.hpp"
+
+using namespace IFF_RIFF;
+
+// =================================================================================================
+/// \file AIFF_Handler.cpp
+/// \brief File format handler for AIFF.
+// =================================================================================================
+
+
+// =================================================================================================
+// AIFF_MetaHandlerCTor
+// ====================
+
+XMPFileHandler * AIFF_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new AIFF_MetaHandler ( parent );
+}
+
+// =================================================================================================
+// AIFF_CheckFormat
+// ===============
+//
+// Checks if the given file is a valid AIFF or AIFC file.
+// The first 12 bytes are checked. The first 4 must be "FORM"
+// Bytes 8 to 12 must be "AIFF" or "AIFC"
+
+bool AIFF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* file,
+ XMPFiles* parent )
+{
+ // Reset file pointer position
+ file ->Rewind();
+
+ XMP_Uns8 chunkID[12];
+ XMP_Int32 got = file->Read ( chunkID, 12 );
+
+ // Reset file pointer position
+ file ->Rewind();
+
+ // Need to have at least ID, size and Type of first chunk
+ if ( got < 12 )
+ {
+ return false;
+ }
+
+ const BigEndian& endian = BigEndian::getInstance();
+ if ( endian.getUns32(chunkID) != kChunk_FORM )
+ {
+ return false;
+ }
+
+ XMP_Uns32 type = AIFF_MetaHandler::whatAIFFFormat( &chunkID[8] );
+ if ( type == kType_AIFF || type == kType_AIFC )
+ {
+ return true;
+ }
+
+ return false;
+} // AIFF_CheckFormat
+
+
+// =================================================================================================
+// AIFF_MetaHandler::whatAIFFFormat
+// ===============
+
+XMP_Uns32 AIFF_MetaHandler::whatAIFFFormat( XMP_Uns8* buffer )
+{
+ XMP_Uns32 type = 0;
+
+ const BigEndian& endian = BigEndian::getInstance();
+
+ if( buffer != 0 )
+ {
+ if( endian.getUns32( buffer ) == kType_AIFF )
+ {
+ type = kType_AIFF;
+ }
+ else if( endian.getUns32( buffer ) == kType_AIFC )
+ {
+ type = kType_AIFC;
+ }
+ }
+
+ return type;
+} // whatAIFFFormat
+
+
+// Static inits
+
+// ChunkIdentifier
+// FORM:AIFF/APPL:XMP
+const ChunkIdentifier AIFF_MetaHandler::kAIFFXMP[2] = { { kChunk_FORM, kType_AIFF }, { kChunk_APPL, kType_XMP } };
+// FORM:AIFC/APPL:XMP
+const ChunkIdentifier AIFF_MetaHandler::kAIFCXMP[2] = { { kChunk_FORM, kType_AIFC }, { kChunk_APPL, kType_XMP } };
+// FORM:AIFF/NAME
+const ChunkIdentifier AIFF_MetaHandler::kAIFFName[2] = { { kChunk_FORM, kType_AIFF }, { kChunk_NAME, kType_NONE } };
+// FORM:AIFC/NAME
+const ChunkIdentifier AIFF_MetaHandler::kAIFCName[2] = { { kChunk_FORM, kType_AIFC }, { kChunk_NAME, kType_NONE } };
+// FORM:AIFF/AUTH
+const ChunkIdentifier AIFF_MetaHandler::kAIFFAuth[2] = { { kChunk_FORM, kType_AIFF }, { kChunk_AUTH, kType_NONE } };
+// FORM:AIFC/AUTH
+const ChunkIdentifier AIFF_MetaHandler::kAIFCAuth[2] = { { kChunk_FORM, kType_AIFC }, { kChunk_AUTH, kType_NONE } };
+// FORM:AIFF/(c)
+const ChunkIdentifier AIFF_MetaHandler::kAIFFCpr[2] = { { kChunk_FORM, kType_AIFF }, { kChunk_CPR, kType_NONE } };
+// FORM:AIFC/(c)
+const ChunkIdentifier AIFF_MetaHandler::kAIFCCpr[2] = { { kChunk_FORM, kType_AIFC }, { kChunk_CPR, kType_NONE } };
+// FORM:AIFF/ANNO
+const ChunkIdentifier AIFF_MetaHandler::kAIFFAnno[2] = { { kChunk_FORM, kType_AIFF }, { kChunk_ANNO, kType_NONE } };
+// FORM:AIFC/ANNO
+const ChunkIdentifier AIFF_MetaHandler::kAIFCAnno[2] = { { kChunk_FORM, kType_AIFC }, { kChunk_ANNO, kType_NONE } };
+
+// =================================================================================================
+// AIFF_MetaHandler::AIFF_MetaHandler
+// ================================
+
+AIFF_MetaHandler::AIFF_MetaHandler ( XMPFiles * _parent )
+ : mChunkBehavior(NULL), mChunkController(NULL),
+ mAiffMeta(), mXMPChunk(NULL),
+ mNameChunk(NULL), mAuthChunk(NULL),
+ mCprChunk(NULL), mAnnoChunk(NULL), mFileType(0)
+{
+ this->parent = _parent;
+ this->handlerFlags = kAIFF_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ this->mChunkBehavior = new AIFFBehavior();
+ this->mChunkController = new ChunkController( mChunkBehavior, true );
+
+} // AIFF_MetaHandler::AIFF_MetaHandler
+
+// =================================================================================================
+// AIFF_MetaHandler::~AIFF_MetaHandler
+// =================================
+
+AIFF_MetaHandler::~AIFF_MetaHandler()
+{
+ if( mChunkController != NULL )
+ {
+ delete mChunkController;
+ }
+
+ if( mChunkBehavior != NULL )
+ {
+ delete mChunkBehavior;
+ }
+} // AIFF_MetaHandler::~AIFF_MetaHandler
+
+
+// =================================================================================================
+// AIFF_MetaHandler::CacheFileData
+// ==============================
+
+void AIFF_MetaHandler::CacheFileData()
+{
+ // Need to determine the file type, need the first 12 bytes of the file
+
+ // Reset file pointer position
+ this->parent->ioRef ->Rewind();
+
+ XMP_Uns8 buffer[12];
+ XMP_Int32 got = this->parent->ioRef->Read ( buffer, 12 );
+ XMP_Assert( got == 12 );
+
+ XMP_Uns32 type = AIFF_MetaHandler::whatAIFFFormat( &buffer[8] );
+ XMP_Assert( type == kType_AIFF || type == kType_AIFC );
+
+ // Reset file pointer position
+ this->parent->ioRef ->Rewind();
+
+ // Add the relevant chunk paths for the determined AIFF format
+ if( type == kType_AIFF )
+ {
+ mAIFFXMPChunkPath.append( kAIFFXMP, SizeOfCIArray(kAIFFXMP) );
+ mAIFFNameChunkPath.append( kAIFFName, SizeOfCIArray(kAIFFName) );
+ mAIFFAuthChunkPath.append( kAIFFAuth, SizeOfCIArray(kAIFFAuth) );
+ mAIFFCprChunkPath.append( kAIFFCpr, SizeOfCIArray(kAIFFCpr) );
+ mAIFFAnnoChunkPath.append( kAIFFAnno, SizeOfCIArray(kAIFFAnno) );
+ }
+ else // kType_AIFC
+ {
+ mAIFFXMPChunkPath.append( kAIFCXMP, SizeOfCIArray(kAIFCXMP) );
+ mAIFFNameChunkPath.append( kAIFCName, SizeOfCIArray(kAIFCName) );
+ mAIFFAuthChunkPath.append( kAIFCAuth, SizeOfCIArray(kAIFCAuth) );
+ mAIFFCprChunkPath.append( kAIFCCpr, SizeOfCIArray(kAIFCCpr) );
+ mAIFFAnnoChunkPath.append( kAIFCAnno, SizeOfCIArray(kAIFCAnno) );
+ }
+
+ mChunkController->addChunkPath( mAIFFXMPChunkPath );
+ mChunkController->addChunkPath( mAIFFNameChunkPath );
+ mChunkController->addChunkPath( mAIFFAuthChunkPath );
+ mChunkController->addChunkPath( mAIFFCprChunkPath );
+ mChunkController->addChunkPath( mAIFFAnnoChunkPath );
+
+
+ // Parse the given file
+ // Throws exception if the file cannot be parsed
+ mChunkController->parseFile( this->parent->ioRef, &this->parent->openFlags );
+
+ // Check if the file contains XMP (last one if there are multiple chunks)
+ mXMPChunk = mChunkController->getChunk( mAIFFXMPChunkPath, true );
+
+ // Retrieve XMP packet info
+ if( mXMPChunk != NULL )
+ {
+ // subtract the type size that is contained in the XMP data chunk
+ this->packetInfo.length = static_cast<XMP_Int32>(mXMPChunk->getSize() - 4);
+ this->packetInfo.charForm = kXMP_Char8Bit;
+ this->packetInfo.writeable = true;
+
+ // Get actual the XMP packet without the 4byte type
+ this->xmpPacket.assign ( mXMPChunk->getString( this->packetInfo.length, 4 ) );
+
+ // set state
+ this->containsXMP = true;
+ }
+} // AIFF_MetaHandler::CacheFileData
+
+
+// =================================================================================================
+// AIFF_MetaHandler::ProcessXMP
+// ============================
+
+void AIFF_MetaHandler::ProcessXMP()
+{
+ // Must be done only once
+ if ( this->processedXMP )
+ {
+ return;
+ }
+ // Set the status at start, in case something goes wrong in this method
+ this->processedXMP = true;
+
+ // Parse the XMP
+ if ( ! this->xmpPacket.empty() ) {
+
+ XMP_Assert ( this->containsXMP );
+
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+
+ this->containsXMP = true;
+ }
+
+ // Then import native properties
+ MetadataSet metaSet;
+ AIFFReconcile recon;
+
+ // Fill the AIFF metadata object with values
+
+ // Get NAME (title) legacy chunk
+ mNameChunk = mChunkController->getChunk( mAIFFNameChunkPath, true );
+ if( mNameChunk != NULL )
+ {
+ mAiffMeta.setValue<std::string>( AIFFMetadata::kName, mNameChunk->getString() );
+ }
+ // Get AUTH (author) legacy chunk
+ mAuthChunk = mChunkController->getChunk( mAIFFAuthChunkPath, true );
+ if( mAuthChunk != NULL )
+ {
+ mAiffMeta.setValue<std::string>( AIFFMetadata::kAuthor, mAuthChunk->getString() );
+ }
+ // Get CPR (Copyright) legacy chunk
+ mCprChunk = mChunkController->getChunk( mAIFFCprChunkPath, true );
+ if( mCprChunk != NULL )
+ {
+ mAiffMeta.setValue<std::string>( AIFFMetadata::kCopyright, mCprChunk->getString() );
+ }
+ // Get ANNO (annotation) legacy chunk(s)
+ // Get the list of Annotation chunks and pick the last one not being empty
+ const std::vector<IChunkData*> &annoChunks = mChunkController->getChunks( mAIFFAnnoChunkPath );
+
+ mAnnoChunk = selectLastNonEmptyAnnoChunk( annoChunks );
+ if( mAnnoChunk != NULL )
+ {
+ mAiffMeta.setValue<std::string>( AIFFMetadata::kAnnotation, mAnnoChunk->getString() );
+ }
+
+ // Only interested in AIFF metadata
+ metaSet.append( &mAiffMeta );
+ // Do the import
+ if( recon.importToXMP( this->xmpObj, metaSet ) )
+ {
+ // Remember if anything has changed
+ this->containsXMP = true;
+ }
+
+} // AIFF_MetaHandler::ProcessXMP
+
+
+IChunkData* AIFF_MetaHandler::selectLastNonEmptyAnnoChunk( const std::vector<IChunkData*> &annoChunks )
+{
+ IChunkData* annoChunk = NULL;
+ for ( std::vector<IChunkData*>::const_reverse_iterator iter = annoChunks.rbegin(); iter != annoChunks.rend(); iter++ )
+ {
+ if( ! (*iter)->getString().empty() && (*iter)->getString()[0] != '\0' )
+ {
+ annoChunk = *iter;
+ break;
+ }
+ }
+ return annoChunk;
+} // selectFirstNonEmptyAnnoChunk
+
+
+// =================================================================================================
+// AIFF_MetaHandler::UpdateFile
+// ===========================
+
+void AIFF_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) { // If needsUpdate is set then at least the XMP changed.
+ return;
+ }
+
+ if ( doSafeUpdate )
+ {
+ XMP_Throw ( "AIFF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
+ }
+
+ //update/create XMP chunk
+ if( this->containsXMP )
+ {
+ this->xmpObj.SerializeToBuffer ( &(this->xmpPacket) );
+
+ if( mXMPChunk != NULL )
+ {
+ mXMPChunk->setData( reinterpret_cast<const XMP_Uns8 *>(this->xmpPacket.c_str()), this->xmpPacket.length(), true );
+ }
+ else // create XMP chunk
+ {
+ mXMPChunk = mChunkController->createChunk( kChunk_APPL, kType_XMP );
+ mXMPChunk->setData( reinterpret_cast<const XMP_Uns8 *>(this->xmpPacket.c_str()), this->xmpPacket.length(), true );
+ mChunkController->insertChunk( mXMPChunk );
+ }
+ }
+ // XMP Packet is never completely removed from the file.
+
+ // Export XMP to legacy chunks. Create/delete them if necessary
+ MetadataSet metaSet;
+ AIFFReconcile recon;
+
+ metaSet.append( &mAiffMeta );
+
+ // If anything changes, update/create/delete the legacy chunks
+ if( recon.exportFromXMP( metaSet, this->xmpObj ) )
+ {
+ updateLegacyChunk( &mNameChunk, kChunk_NAME, AIFFMetadata::kName );
+ updateLegacyChunk( &mAuthChunk, kChunk_AUTH, AIFFMetadata::kAuthor );
+ updateLegacyChunk( &mCprChunk, kChunk_CPR, AIFFMetadata::kCopyright );
+ updateLegacyChunk( &mAnnoChunk, kChunk_ANNO, AIFFMetadata::kAnnotation );
+ }
+
+ //write tree back to file
+ mChunkController->writeFile( this->parent->ioRef );
+
+ this->needsUpdate = false; // Make sure this is only called once.
+} // AIFF_MetaHandler::UpdateFile
+
+
+void AIFF_MetaHandler::updateLegacyChunk( IChunkData **chunk, XMP_Uns32 chunkID, XMP_Uns32 legacyId )
+{
+ // If there is a legacy value, update/create the appropriate chunk
+ if( mAiffMeta.valueExists( legacyId ) )
+ {
+ std::string chunkValue;
+ std::string legacyValue = mAiffMeta.getValue<std::string>( legacyId );
+
+ // If the length is < 4 we need to fill up the value with \0 to a size of 4
+ // This ensures that the overall size of text chunks is 12 bytes so that they can be
+ // converted to free chunks if necessary
+ if( legacyValue.length() < 4 )
+ {
+ char buffer[4];
+ memset( buffer, 0, 4 );
+ memcpy( buffer, legacyValue.c_str(), legacyValue.length() );
+ chunkValue.assign( buffer, 4 );
+ }
+ else // take the value as is
+ {
+ chunkValue = legacyValue;
+ }
+
+ if( *chunk != NULL )
+ {
+ (*chunk)->setData( reinterpret_cast<const XMP_Uns8 *>(chunkValue.c_str()), chunkValue.length() );
+ }
+ else
+ {
+ *chunk = mChunkController->createChunk( chunkID, kType_NONE );
+ (*chunk)->setData( reinterpret_cast<const XMP_Uns8 *>(chunkValue.c_str()), chunkValue.length() );
+ mChunkController->insertChunk( *chunk );
+ }
+ }
+ else //delete chunk if existing
+ {
+ mChunkController->removeChunk ( *chunk );
+ }
+} // updateLegacyChunk
+
+
+// =================================================================================================
+// AIFF_MetaHandler::WriteTempFile
+// ===============================
+
+void AIFF_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ XMP_Throw ( "AIFF_MetaHandler::WriteTempFile is not Implemented!", kXMPErr_Unimplemented );
+} // AIFF_MetaHandler::WriteTempFile
diff --git a/XMPFiles/source/FileHandlers/AIFF_Handler.hpp b/XMPFiles/source/FileHandlers/AIFF_Handler.hpp
new file mode 100644
index 0000000..13bfa02
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/AIFF_Handler.hpp
@@ -0,0 +1,158 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef __AIFF_Handler_hpp__
+#define __AIFF_Handler_hpp__ 1
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/IFF/ChunkController.h"
+#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h"
+#include "XMPFiles/source/FormatSupport/IFF/IChunkData.h"
+#include "source/Endian.h"
+#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h"
+#include "XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+using namespace IFF_RIFF;
+
+// =================================================================================================
+/// \file AIFF_Handler.hpp
+/// \brief File format handler for AIFF.
+// =================================================================================================
+
+/**
+ * Contructor for the handler.
+ */
+extern XMPFileHandler * AIFF_MetaHandlerCTor ( XMPFiles * parent );
+
+/**
+ * Checks the format of the file, see common code.
+ */
+extern bool AIFF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent );
+
+/** AIFF does not need kXMPFiles_CanRewrite as we can always use UpdateFile to either do
+ * in-place update or append to the file. */
+static const XMP_OptionBits kAIFF_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsSafeUpdate
+ );
+
+/**
+ * Main class for the the AIFF file handler.
+ */
+class AIFF_MetaHandler : public XMPFileHandler
+{
+public:
+ AIFF_MetaHandler ( XMPFiles* parent );
+ ~AIFF_MetaHandler();
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ /**
+ * Checks if the first 4 bytes of the given buffer are either type AIFF or AIFC
+ * @param buffer a byte buffer that must contain at least 4 bytes and point to the correct byte
+ * @return Either kType_AIFF, kType_AIFC 0 if no type could be determined
+ */
+ static XMP_Uns32 whatAIFFFormat( XMP_Uns8* buffer );
+
+private:
+ /**
+ * Updates/creates/deletes a given legacy chunk depending on the given new legacy value
+ * If the Chunk exists and the value is not empty, it is updated. If the value is empty the
+ * Chunk is removed from the tree. If the Chunk does not exist but a value is given, it is created
+ * and initialized with that value
+ *
+ * @param chunk OUT pointer to the legacy chunk
+ * @param chunkID Id of the Chunk if it needs to be created
+ * @param legacyId ID of the legacy value
+ */
+ void updateLegacyChunk( IChunkData **chunk, XMP_Uns32 chunkID, XMP_Uns32 legacyId );
+
+ /**
+ * Finds the last non-empty annotation chunk in the given list
+ * @param annoChunks list of annotation chunks
+ * @return pointer to the first non-empty chunk or NULL
+ */
+ IChunkData* selectLastNonEmptyAnnoChunk( const std::vector<IChunkData*> &annoChunks );
+
+ /** private standard Ctor, not to be used */
+ AIFF_MetaHandler (): mChunkController(NULL), mChunkBehavior(NULL),
+ mAiffMeta(), mXMPChunk(NULL),
+ mNameChunk(NULL), mAuthChunk(NULL),
+ mCprChunk(NULL), mAnnoChunk(NULL), mFileType(0) {};
+
+
+ // ----- MEMBERS ----- //
+
+ /** Controls the parsing and writing of the passed stream. */
+ ChunkController *mChunkController;
+ /** Represents the rules how chunks are added, removed or rearranged */
+ IChunkBehavior *mChunkBehavior;
+ /** container for Legacy metadata */
+ AIFFMetadata mAiffMeta;
+
+ /** pointer to the XMP chunk */
+ IChunkData *mXMPChunk;
+ /** pointer to legacy chunks */
+ IChunkData *mNameChunk;
+ IChunkData *mAuthChunk;
+ IChunkData *mCprChunk;
+ IChunkData *mAnnoChunk;
+
+ /** Type of the file, either AIFF or AIFC */
+ XMP_Uns32 mFileType;
+
+
+ // ----- CONSTANTS ----- //
+
+ /** Chunk path identifier of interest in AIFF/AIFC */
+ static const ChunkIdentifier kAIFFXMP[2];
+ static const ChunkIdentifier kAIFCXMP[2];
+ static const ChunkIdentifier kAIFFName[2];
+ static const ChunkIdentifier kAIFCName[2];
+ static const ChunkIdentifier kAIFFAuth[2];
+ static const ChunkIdentifier kAIFCAuth[2];
+ static const ChunkIdentifier kAIFFCpr[2];
+ static const ChunkIdentifier kAIFCCpr[2];
+ static const ChunkIdentifier kAIFFAnno[2];
+ static const ChunkIdentifier kAIFCAnno[2];
+
+ /** Path to XMP chunk */
+ ChunkPath mAIFFXMPChunkPath;
+
+ /** Path to NAME chunk */
+ ChunkPath mAIFFNameChunkPath;
+
+ /** Path to AUTH chunk */
+ ChunkPath mAIFFAuthChunkPath;
+
+ /** Path to COPYRIGHT chunk */
+ ChunkPath mAIFFCprChunkPath;
+
+ /** Path to ANNOTATION chunk */
+ ChunkPath mAIFFAnnoChunkPath;
+
+}; // AIFF_MetaHandler
+
+// =================================================================================================
+
+#endif /* __AIFF_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/ASF_Handler.cpp b/XMPFiles/source/FileHandlers/ASF_Handler.cpp
new file mode 100644
index 0000000..f7026bb
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/ASF_Handler.cpp
@@ -0,0 +1,339 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/ASF_Handler.hpp"
+
+// =================================================================================================
+/// \file ASF_Handler.hpp
+/// \brief File format handler for ASF.
+///
+/// This handler ...
+///
+// =================================================================================================
+
+// =================================================================================================
+// ASF_MetaHandlerCTor
+// ====================
+
+XMPFileHandler * ASF_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new ASF_MetaHandler ( parent );
+
+} // ASF_MetaHandlerCTor
+
+// =================================================================================================
+// ASF_CheckFormat
+// ===============
+
+bool ASF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent )
+{
+
+ IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent);
+ XMP_Assert ( format == kXMP_WMAVFile );
+
+ IOBuffer ioBuf;
+
+ fileRef->Rewind();
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, guidLen ) ) return false;
+
+ GUID guid;
+ memcpy ( &guid, ioBuf.ptr, guidLen );
+
+ if ( ! IsEqualGUID ( ASF_Header_Object, guid ) ) return false;
+
+ return true;
+
+} // ASF_CheckFormat
+
+// =================================================================================================
+// ASF_MetaHandler::ASF_MetaHandler
+// ==================================
+
+ASF_MetaHandler::ASF_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kASF_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+}
+
+// =================================================================================================
+// ASF_MetaHandler::~ASF_MetaHandler
+// ===================================
+
+ASF_MetaHandler::~ASF_MetaHandler()
+{
+ // Nothing extra to do.
+}
+
+// =================================================================================================
+// ASF_MetaHandler::CacheFileData
+// ===============================
+
+void ASF_MetaHandler::CacheFileData()
+{
+
+ this->containsXMP = false;
+
+ XMP_IO* fileRef ( this->parent->ioRef );
+ if ( fileRef == 0 ) return;
+
+ ASF_Support support ( &this->legacyManager );
+ ASF_Support::ObjectState objectState;
+ long numTags = support.OpenASF ( fileRef, objectState );
+ if ( numTags == 0 ) return;
+
+ if ( objectState.xmpLen != 0 ) {
+
+ // XMP present
+
+ XMP_Int32 len = XMP_Int32 ( objectState.xmpLen );
+
+ this->xmpPacket.reserve( len );
+ this->xmpPacket.assign ( len, ' ' );
+
+ bool found = ASF_Support::ReadBuffer ( fileRef, objectState.xmpPos, objectState.xmpLen,
+ const_cast<char *>(this->xmpPacket.data()) );
+ if ( found ) {
+ this->packetInfo.offset = objectState.xmpPos;
+ this->packetInfo.length = len;
+ this->containsXMP = true;
+ }
+
+ }
+
+} // ASF_MetaHandler::CacheFileData
+
+// =================================================================================================
+// ASF_MetaHandler::ProcessXMP
+// ============================
+//
+// Process the raw XMP and legacy metadata that was previously cached.
+
+void ASF_MetaHandler::ProcessXMP()
+{
+
+ this->processedXMP = true; // Make sure we only come through here once.
+
+ // Process the XMP packet.
+
+ if ( this->xmpPacket.empty() ) {
+
+ // import legacy in any case, when no XMP present
+ legacyManager.ImportLegacy ( &this->xmpObj );
+ this->legacyManager.SetDigest ( &this->xmpObj );
+
+ } else {
+
+ XMP_Assert ( this->containsXMP );
+ XMP_StringPtr packetStr = this->xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
+
+ this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
+
+ if ( ! legacyManager.CheckDigest ( this->xmpObj ) ) {
+ legacyManager.ImportLegacy ( &this->xmpObj );
+ }
+
+ }
+
+ // Assume we now have something in the XMP.
+ this->containsXMP = true;
+
+} // ASF_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// ASF_MetaHandler::UpdateFile
+// ============================
+
+void ASF_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+
+ bool updated = false;
+
+ if ( ! this->needsUpdate ) return;
+
+ XMP_IO* fileRef ( this->parent->ioRef );
+ if ( fileRef == 0 ) return;
+
+ ASF_Support support;
+ ASF_Support::ObjectState objectState;
+ long numTags = support.OpenASF ( fileRef, objectState );
+ if ( numTags == 0 ) return;
+
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
+
+ this->legacyManager.ExportLegacy ( this->xmpObj );
+ if ( this->legacyManager.hasLegacyChanged() ) {
+
+ this->legacyManager.SetDigest ( &this->xmpObj );
+
+ // serialize with updated digest
+ if ( objectState.xmpLen == 0 ) {
+
+ // XMP does not exist, use standard padding
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+
+ } else {
+
+ // re-use padding with static XMP size
+ try {
+ XMP_OptionBits compactExact = (kXMP_UseCompactFormat | kXMP_ExactPacketLength);
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, compactExact, XMP_StringLen(objectState.xmpLen) );
+ } catch ( ... ) {
+ // re-use padding with exact packet length failed (legacy-digest needed too much space): try again using standard padding
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+ }
+
+ }
+
+ }
+
+ XMP_StringPtr packetStr = xmpPacket.c_str();
+ packetLen = (XMP_StringLen)xmpPacket.size();
+ if ( packetLen == 0 ) return;
+
+ // value, when guessing for sufficient legacy padding (line-ending conversion etc.)
+ const int paddingTolerance = 50;
+
+ bool xmpGrows = ( objectState.xmpLen && (packetLen > objectState.xmpLen) && ( ! objectState.xmpIsLastObject) );
+
+ bool legacyGrows = ( this->legacyManager.hasLegacyChanged() &&
+ (this->legacyManager.getLegacyDiff() > (this->legacyManager.GetPadding() - paddingTolerance)) );
+
+ if ( doSafeUpdate || legacyGrows || xmpGrows ) {
+
+ // do a safe update in any case
+ updated = SafeWriteFile();
+
+ } else {
+
+ // possibly we can do an in-place update
+
+ if ( objectState.xmpLen < packetLen ) {
+
+ updated = SafeWriteFile();
+
+ } else {
+
+ // current XMP chunk size is sufficient -> write (in place update)
+ updated = ASF_Support::WriteBuffer(fileRef, objectState.xmpPos, packetLen, packetStr );
+
+ // legacy update
+ if ( updated && this->legacyManager.hasLegacyChanged() ) {
+
+ ASF_Support::ObjectIterator curPos = objectState.objects.begin();
+ ASF_Support::ObjectIterator endPos = objectState.objects.end();
+
+ for ( ; curPos != endPos; ++curPos ) {
+
+ ASF_Support::ObjectData object = *curPos;
+
+ // find header-object
+ if ( IsEqualGUID ( ASF_Header_Object, object.guid ) ) {
+ // update header object
+ updated = support.UpdateHeaderObject ( fileRef, object, legacyManager );
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( ! updated ) return; // If there's an error writing the chunk, bail.
+
+ this->needsUpdate = false;
+
+} // ASF_MetaHandler::UpdateFile
+
+// =================================================================================================
+// ASF_MetaHandler::WriteTempFile
+// ==============================
+
+void ASF_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ bool ok;
+ XMP_IO* originalRef = this->parent->ioRef;
+
+ ASF_Support support;
+ ASF_Support::ObjectState objectState;
+ long numTags = support.OpenASF ( originalRef, objectState );
+ if ( numTags == 0 ) return;
+
+ tempRef->Truncate ( 0 );
+
+ ASF_Support::ObjectIterator curPos = objectState.objects.begin();
+ ASF_Support::ObjectIterator endPos = objectState.objects.end();
+
+ for ( ; curPos != endPos; ++curPos ) {
+
+ ASF_Support::ObjectData object = *curPos;
+
+ // discard existing XMP object
+ if ( object.xmp ) continue;
+
+ // update header-object, when legacy needs update
+ if ( IsEqualGUID ( ASF_Header_Object, object.guid) && this->legacyManager.hasLegacyChanged( ) ) {
+ // rewrite header object
+ ok = support.WriteHeaderObject ( originalRef, tempRef, object, this->legacyManager, false );
+ if ( ! ok ) XMP_Throw ( "Failure writing ASF header object", kXMPErr_InternalFailure );
+ } else {
+ // copy any other object
+ ok = ASF_Support::CopyObject ( originalRef, tempRef, object );
+ if ( ! ok ) XMP_Throw ( "Failure copyinh ASF object", kXMPErr_InternalFailure );
+ }
+
+ // write XMP object immediately after the (one and only) top-level DataObject
+ if ( IsEqualGUID ( ASF_Data_Object, object.guid ) ) {
+ XMP_StringPtr packetStr = xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
+ ok = ASF_Support::WriteXMPObject ( tempRef, packetLen, packetStr );
+ if ( ! ok ) XMP_Throw ( "Failure writing ASF XMP object", kXMPErr_InternalFailure );
+ }
+
+ }
+
+ ok = support.UpdateFileSize ( tempRef );
+ if ( ! ok ) XMP_Throw ( "Failure updating ASF file size", kXMPErr_InternalFailure );
+
+} // ASF_MetaHandler::WriteTempFile
+
+// =================================================================================================
+// ASF_MetaHandler::SafeWriteFile
+// ==============================
+
+bool ASF_MetaHandler::SafeWriteFile()
+{
+ XMP_IO* originalFile = this->parent->ioRef;
+ XMP_IO* tempFile = originalFile->DeriveTemp();
+ if ( tempFile == 0 ) XMP_Throw ( "Failure creating ASF temp file", kXMPErr_InternalFailure );
+
+ this->WriteTempFile ( tempFile );
+ originalFile->AbsorbTemp();
+
+ return true;
+
+} // ASF_MetaHandler::SafeWriteFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/ASF_Handler.hpp b/XMPFiles/source/FileHandlers/ASF_Handler.hpp
new file mode 100644
index 0000000..2a4f63e
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/ASF_Handler.hpp
@@ -0,0 +1,64 @@
+#ifndef __ASF_Handler_hpp__
+#define __ASF_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "XMPFiles/source/FormatSupport/ASF_Support.hpp"
+
+// =================================================================================================
+/// \file ASF_Handler.hpp
+/// \brief File format handler for ASF.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// *** Could derive from Basic_Handler - buffer file tail in a temp file.
+
+extern XMPFileHandler* ASF_MetaHandlerCTor ( XMPFiles* parent );
+
+extern bool ASF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kASF_HandlerFlags = ( kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_NeedsReadOnlyPacket );
+
+class ASF_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ bool SafeWriteFile ();
+
+ ASF_MetaHandler ( XMPFiles* parent );
+ virtual ~ASF_MetaHandler();
+
+private:
+
+ ASF_LegacyManager legacyManager;
+
+}; // ASF_MetaHandler
+
+// =================================================================================================
+
+#endif /* __ASF_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp b/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp
new file mode 100644
index 0000000..5b22a13
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp
@@ -0,0 +1,2280 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/AVCHD_Handler.hpp"
+
+#include "source/UnicodeConversions.hpp"
+#include "third-party/zuid/interfaces/MD5.h"
+
+using namespace std;
+
+// AVCHD maker ID values. Panasonic has confirmed their Maker ID with us, the others come from examining
+// sample data files.
+#define kMakerIDPanasonic 0x103
+#define kMakerIDSony 0x108
+#define kMakerIDCanon 0x1011
+
+// =================================================================================================
+/// \file AVCHD_Handler.cpp
+/// \brief Folder format handler for AVCHD.
+///
+/// This handler is for the AVCHD video format.
+///
+/// A typical AVCHD layout looks like:
+///
+/// BDMV/
+/// index.bdmv
+/// MovieObject.bdmv
+/// PLAYLIST/
+/// 00000.mpls
+/// 00001.mpls
+/// STREAM/
+/// 00000.m2ts
+/// 00001.m2ts
+/// CLIPINF/
+/// 00000.clpi
+/// 00001.clpi
+/// BACKUP/
+///
+// =================================================================================================
+
+// =================================================================================================
+
+// AVCHD Format. Book 1: Playback System Basic Specifications V 1.01. p. 76
+
+struct AVCHD_blkProgramInfo
+{
+ XMP_Uns32 mLength;
+ XMP_Uns8 mReserved1[2];
+ XMP_Uns32 mSPNProgramSequenceStart;
+ XMP_Uns16 mProgramMapPID;
+ XMP_Uns8 mNumberOfStreamsInPS;
+ XMP_Uns8 mReserved2;
+
+ // Video stream.
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mVideoFormat;
+ XMP_Uns8 mFrameRate;
+ XMP_Uns8 mAspectRatio;
+ XMP_Uns8 mCCFlag;
+ } mVideoStream;
+
+ // Audio stream.
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mAudioPresentationType;
+ XMP_Uns8 mSamplingFrequency;
+ XMP_Uns8 mAudioLanguageCode[4];
+ } mAudioStream;
+
+ // Pverlay bitmap stream.
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mOBLanguageCode[4];
+ } mOverlayBitmapStream;
+
+ // Menu bitmap stream.
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mBMLanguageCode[4];
+ } mMenuBitmapStream;
+
+};
+
+// AVCHD Format, Panasonic proprietary PRO_PlayListMark block
+
+struct AVCCAM_blkProPlayListMark
+{
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mProTagID;
+ XMP_Uns8 mFillItem1;
+ XMP_Uns16 mLength;
+ XMP_Uns8 mMarkType;
+
+ // Entry mark
+ struct
+ {
+ XMP_Uns8 mGlobalClipID[32];
+ XMP_Uns8 mStartTimeCode[4];
+ XMP_Uns8 mStreamTimecodeInfo;
+ XMP_Uns8 mStartBinaryGroup[4];
+ XMP_Uns8 mLastUpdateTimeZone;
+ XMP_Uns8 mLastUpdateDate[7];
+ XMP_Uns16 mFillItem;
+ } mEntryMark;
+
+ // Shot Mark
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mShotMark;
+ XMP_Uns8 mFillItem[3];
+ } mShotMark;
+
+ // Access
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mCreatorCharacterSet;
+ XMP_Uns8 mCreatorLength;
+ XMP_Uns8 mCreator[32];
+ XMP_Uns8 mLastUpdatePersonCharacterSet;
+ XMP_Uns8 mLastUpdatePersonLength;
+ XMP_Uns8 mLastUpdatePerson[32];
+ } mAccess;
+
+ // Device
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns16 mMakerID;
+ XMP_Uns16 mMakerModelCode;
+ XMP_Uns8 mSerialNoCharacterCode;
+ XMP_Uns8 mSerialNoLength;
+ XMP_Uns8 mSerialNo[24];
+ XMP_Uns16 mFillItem;
+ } mDevice;
+
+ // Shoot
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mShooterCharacterSet;
+ XMP_Uns8 mShooterLength;
+ XMP_Uns8 mShooter[32];
+ XMP_Uns8 mStartDateTimeZone;
+ XMP_Uns8 mStartDate[7];
+ XMP_Uns8 mEndDateTimeZone;
+ XMP_Uns8 mEndDate[7];
+ XMP_Uns16 mFillItem;
+ } mShoot;
+
+ // Location
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mSource;
+ XMP_Uns32 mGPSLatitudeRef;
+ XMP_Uns32 mGPSLatitude1;
+ XMP_Uns32 mGPSLatitude2;
+ XMP_Uns32 mGPSLatitude3;
+ XMP_Uns32 mGPSLongitudeRef;
+ XMP_Uns32 mGPSLongitude1;
+ XMP_Uns32 mGPSLongitude2;
+ XMP_Uns32 mGPSLongitude3;
+ XMP_Uns32 mGPSAltitudeRef;
+ XMP_Uns32 mGPSAltitude;
+ XMP_Uns8 mPlaceNameCharacterSet;
+ XMP_Uns8 mPlaceNameLength;
+ XMP_Uns8 mPlaceName[64];
+ XMP_Uns8 mFillItem;
+ } mLocation;
+};
+
+// AVCHD Format, Panasonic proprietary extension data (AVCCAM)
+
+struct AVCCAM_Pro_PlayListInfo
+{
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mTagID;
+ XMP_Uns8 mTagVersion;
+ XMP_Uns16 mFillItem1;
+ XMP_Uns32 mLength;
+ XMP_Uns16 mNumberOfPlayListMarks;
+ XMP_Uns16 mFillItem2;
+
+ // Although a playlist may contain multiple marks, we only store the one that corresponds to
+ // the clip/shot of interest.
+ AVCCAM_blkProPlayListMark mPlayListMark;
+};
+
+// AVCHD Format, Panasonic proprietary extension data (AVCCAM)
+
+struct AVCHD_blkPanasonicPrivateData
+{
+ XMP_Uns8 mPresent;
+ XMP_Uns16 mNumberOfData;
+ XMP_Uns16 mReserved;
+
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mTagID;
+ XMP_Uns8 mTagVersion;
+ XMP_Uns16 mTagLength;
+ XMP_Uns8 mProfessionalMetaID[16];
+ } mProMetaIDBlock;
+
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mTagID;
+ XMP_Uns8 mTagVersion;
+ XMP_Uns16 mTagLength;
+ XMP_Uns8 mGlobalClipID[32];
+ XMP_Uns8 mStartTimecode[4];
+ XMP_Uns32 mStartBinaryGroup;
+ } mProClipIDBlock;
+
+ AVCCAM_Pro_PlayListInfo mProPlaylistInfoBlock;
+};
+
+// AVCHD Format. Book 2: Recording Extension Specifications, section 4.2.4.2. plus Panasonic extensions
+
+struct AVCHD_blkMakersPrivateData
+{
+ XMP_Uns8 mPresent;
+ XMP_Uns32 mLength;
+ XMP_Uns32 mDataBlockStartAddress;
+ XMP_Uns8 mReserved[3];
+ XMP_Uns8 mNumberOfMakerEntries;
+ XMP_Uns16 mMakerID;
+ XMP_Uns16 mMakerModelCode;
+ AVCHD_blkPanasonicPrivateData mPanasonicPrivateData;
+};
+
+// AVCHD Format. Book 2: Recording Extension Specifications, section 4.4.2.1
+
+struct AVCHD_blkClipInfoExt
+{
+ XMP_Uns32 mLength;
+ XMP_Uns16 mMakerID;
+ XMP_Uns16 mMakerModelCode;
+};
+
+// AVCHD Format. Book 2: Recording Extension Specifications, section 4.4.1.2
+
+struct AVCHD_blkClipExtensionData
+{
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mTypeIndicator[4];
+ XMP_Uns8 mReserved1[4];
+ XMP_Uns32 mProgramInfoExtStartAddress;
+ XMP_Uns32 mMakersPrivateDataStartAddress;
+
+ AVCHD_blkClipInfoExt mClipInfoExt;
+ AVCHD_blkMakersPrivateData mMakersPrivateData;
+};
+
+// AVCHD Format. Book 2: Recording Extension Specifications, section 4.3.3.1 -- although each playlist
+// may contain a list of these, we only record the one that matches our target shot/clip.
+
+struct AVCHD_blkPlayListMarkExt
+{
+ XMP_Uns32 mLength;
+ XMP_Uns16 mNumberOfPlaylistMarks;
+ bool mPresent;
+ XMP_Uns16 mMakerID;
+ XMP_Uns16 mMakerModelCode;
+ XMP_Uns8 mReserved1[3];
+ XMP_Uns8 mFlags; // bit 0: MarkWriteProtectFlag, bits 1-2: pulldown
+ XMP_Uns16 mRefToMarkThumbnailIndex;
+ XMP_Uns8 mBlkTimezone;
+ XMP_Uns8 mRecordDataAndTime[7];
+ XMP_Uns8 mMarkCharacterSet;
+ XMP_Uns8 mMarkNameLength;
+ XMP_Uns8 mMarkName[24];
+ XMP_Uns8 mMakersInformation[16];
+ XMP_Uns8 mBlkTimecode[4];
+ XMP_Uns16 mReserved2;
+};
+
+// AVCHD Format. Book 2: Recording Extension Specifications, section 4.3.2.1
+
+struct AVCHD_blkPlaylistMeta
+{
+ XMP_Uns32 mLength;
+ XMP_Uns16 mMakerID;
+ XMP_Uns16 mMakerModelCode;
+ XMP_Uns32 mReserved1;
+ XMP_Uns16 mRefToMenuThumbnailIndex;
+ XMP_Uns8 mBlkTimezone;
+ XMP_Uns8 mRecordDataAndTime[7];
+ XMP_Uns8 mReserved2;
+ XMP_Uns8 mPlaylistCharacterSet;
+ XMP_Uns8 mPlaylistNameLength;
+ XMP_Uns8 mPlaylistName[255];
+};
+
+// AVCHD Format. Book 2: Recording Extension Specifications, section 4.3.1.2
+
+struct AVCHD_blkPlayListExtensionData
+{
+ XMP_Uns8 mPresent;
+ char mTypeIndicator[4];
+ XMP_Uns8 mReserved[4];
+ XMP_Uns32 mPlayListMarkExtStartAddress;
+ XMP_Uns32 mMakersPrivateDataStartAddress;
+
+ AVCHD_blkPlaylistMeta mPlaylistMeta;
+ AVCHD_blkPlayListMarkExt mPlaylistMarkExt;
+ AVCHD_blkMakersPrivateData mMakersPrivateData;
+};
+
+// AVCHD Format. Book 1: Playback System Basic Specifications V 1.01. p. 38
+struct AVCHD_blkExtensionData
+{
+ XMP_Uns32 mLength;
+ XMP_Uns32 mDataBlockStartAddress;
+ XMP_Uns8 mReserved[3];
+ XMP_Uns8 mNumberOfDataEntries;
+
+ struct AVCHD_blkExtDataEntry
+ {
+ XMP_Uns16 mExtDataType;
+ XMP_Uns16 mExtDataVersion;
+ XMP_Uns32 mExtDataStartAddress;
+ XMP_Uns32 mExtDataLength;
+ } mExtDataEntry;
+};
+
+// Simple container for the various AVCHD legacy metadata structures we care about for an AVCHD clip
+
+struct AVCHD_LegacyMetadata
+{
+ AVCHD_blkProgramInfo mProgramInfo;
+ AVCHD_blkClipExtensionData mClipExtensionData;
+ AVCHD_blkPlayListExtensionData mPlaylistExtensionData;
+};
+
+// =================================================================================================
+// MakeLeafPath
+// ============
+
+static bool MakeLeafPath ( std::string * path, XMP_StringPtr root, XMP_StringPtr group,
+ XMP_StringPtr clip, XMP_StringPtr suffix, bool checkFile = false )
+{
+ size_t partialLen;
+
+ *path = root;
+ *path += kDirChar;
+ *path += "BDMV";
+ *path += kDirChar;
+ *path += group;
+ *path += kDirChar;
+ *path += clip;
+ partialLen = path->size();
+ *path += suffix;
+
+ if ( ! checkFile ) return true;
+ if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true;
+
+ // Convert the suffix to uppercase and try again. Even on Mac/Win, in case a remote file system is sensitive.
+ for ( char* chPtr = ((char*)path->c_str() + partialLen); *chPtr != 0; ++chPtr ) {
+ if ( (0x61 <= *chPtr) && (*chPtr <= 0x7A) ) *chPtr -= 0x20;
+ }
+ if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true;
+
+ if ( XMP_LitMatch ( suffix, ".clpi" ) ) { // Special case of ".cpi" for the clip file.
+
+ path->erase ( partialLen );
+ *path += ".cpi";
+ if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true;
+
+ path->erase ( partialLen );
+ *path += ".CPI";
+ if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true;
+
+ } else if ( XMP_LitMatch ( suffix, ".mpls" ) ) { // Special case of ".mpl" for the playlist file.
+
+ path->erase ( partialLen );
+ *path += ".mpl";
+ if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true;
+
+ path->erase ( partialLen );
+ *path += ".MPL";
+ if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true;
+
+ }
+
+ // Still not found, revert to the original suffix.
+ path->erase ( partialLen );
+ *path += suffix;
+ return false;
+
+} // MakeLeafPath
+
+// =================================================================================================
+// AVCHD_CheckFormat
+// =================
+//
+// This version checks for the presence of a top level BPAV directory, and the required files and
+// directories immediately within it. The CLIPINF, PLAYLIST, and STREAM subfolders are required, as
+// are the index.bdmv and MovieObject.bdmv files.
+//
+// The state of the string parameters depends on the form of the path passed by the client. If the
+// client passed a logical clip path, like ".../MyMovie/00001", the parameters are:
+// rootPath - ".../MyMovie"
+// gpName - empty
+// parentName - empty
+// leafName - "00001"
+// If the client passed a full file path, like ".../MyMovie/BDMV/CLIPINF/00001.clpi", they are:
+// rootPath - ".../MyMovie"
+// gpName - "BDMV"
+// parentName - "CLIPINF" or "PALYLIST" or "STREAM"
+// leafName - "00001"
+
+// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has
+// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the
+// ! file exists for a full file path.
+
+// ! Using explicit '/' as a separator when creating paths, it works on Windows.
+
+// ! Sample files show that the ".bdmv" extension can sometimes be ".bdm". Allow either.
+
+bool AVCHD_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent )
+{
+ if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty.
+
+ if ( ! gpName.empty() ) {
+ if ( gpName != "BDMV" ) return false;
+ if ( (parentName != "CLIPINF") && (parentName != "PLAYLIST") && (parentName != "STREAM") ) return false;
+ }
+
+ // Check the rest of the required general structure. Look for both ".bdmv" and ".bmd" extensions.
+
+ std::string bdmvPath ( rootPath );
+ bdmvPath += kDirChar;
+ bdmvPath += "BDMV";
+
+ if ( Host_IO::GetChildMode ( bdmvPath.c_str(), "CLIPINF" ) != Host_IO::kFMode_IsFolder ) return false;
+ if ( Host_IO::GetChildMode ( bdmvPath.c_str(), "PLAYLIST" ) != Host_IO::kFMode_IsFolder ) return false;
+ if ( Host_IO::GetChildMode ( bdmvPath.c_str(), "STREAM" ) != Host_IO::kFMode_IsFolder ) return false;
+
+ if ( (Host_IO::GetChildMode ( bdmvPath.c_str(), "index.bdmv" ) != Host_IO::kFMode_IsFile) &&
+ (Host_IO::GetChildMode ( bdmvPath.c_str(), "index.bdm" ) != Host_IO::kFMode_IsFile) &&
+ (Host_IO::GetChildMode ( bdmvPath.c_str(), "INDEX.BDMV" ) != Host_IO::kFMode_IsFile) && // Some usage is all caps.
+ (Host_IO::GetChildMode ( bdmvPath.c_str(), "INDEX.BDM" ) != Host_IO::kFMode_IsFile) ) return false;
+
+ if ( (Host_IO::GetChildMode ( bdmvPath.c_str(), "MovieObject.bdmv" ) != Host_IO::kFMode_IsFile) &&
+ (Host_IO::GetChildMode ( bdmvPath.c_str(), "MovieObj.bdm" ) != Host_IO::kFMode_IsFile) &&
+ (Host_IO::GetChildMode ( bdmvPath.c_str(), "MOVIEOBJECT.BDMV" ) != Host_IO::kFMode_IsFile) && // Some usage is all caps.
+ (Host_IO::GetChildMode ( bdmvPath.c_str(), "MOVIEOBJ.BDM" ) != Host_IO::kFMode_IsFile) ) return false;
+
+
+ // Make sure the .clpi file exists.
+ std::string tempPath;
+ bool foundClpi = MakeLeafPath ( &tempPath, rootPath.c_str(), "CLIPINF", leafName.c_str(), ".clpi", true /* checkFile */ );
+ if ( ! foundClpi ) return false;
+
+ // And now save the pseudo path for the handler object.
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += leafName;
+ size_t pathLen = tempPath.size() + 1; // Include a terminating nul.
+ parent->tempPtr = malloc ( pathLen );
+ if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for AVCHD clip info", kXMPErr_NoMemory );
+ memcpy ( parent->tempPtr, tempPath.c_str(), pathLen );
+
+ return true;
+
+} // AVCHD_CheckFormat
+
+// =================================================================================================
+
+static void* CreatePseudoClipPath ( const std::string & clientPath ) {
+
+ // Used to create the clip pseudo path when the CheckFormat function is skipped.
+
+ std::string pseudoPath = clientPath;
+
+ size_t pathLen;
+ void* tempPtr = 0;
+
+ if ( Host_IO::Exists ( pseudoPath.c_str() ) ) {
+
+ // The client passed a physical path. The logical clip name is the leaf name, with the
+ // extension removed. There are no extra suffixes on AVCHD files. The movie root path ends
+ // two levels up.
+
+ std::string clipName, ignored;
+
+ XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name.
+ XIO::SplitFileExtension ( &clipName, &ignored );
+
+ XIO::SplitLeafName ( &pseudoPath, &ignored ); // Remove the 2 intermediate folder levels.
+ XIO::SplitLeafName ( &pseudoPath, &ignored );
+
+ pseudoPath += kDirChar;
+ pseudoPath += clipName;
+
+ }
+
+ pathLen = pseudoPath.size() + 1; // Include a terminating nul.
+ tempPtr = malloc ( pathLen );
+ if ( tempPtr == 0 ) XMP_Throw ( "No memory for AVCHD clip info", kXMPErr_NoMemory );
+ memcpy ( tempPtr, pseudoPath.c_str(), pathLen );
+
+ return tempPtr;
+
+} // CreatePseudoClipPath
+
+// =================================================================================================
+// ReadAVCHDProgramInfo
+// ====================
+
+static bool ReadAVCHDProgramInfo ( XMPFiles_IO & cpiFile, AVCHD_blkProgramInfo& avchdProgramInfo )
+{
+ avchdProgramInfo.mLength = XIO::ReadUns32_BE ( &cpiFile );
+ cpiFile.ReadAll ( avchdProgramInfo.mReserved1, 2 );
+ avchdProgramInfo.mSPNProgramSequenceStart = XIO::ReadUns32_BE ( &cpiFile );
+ avchdProgramInfo.mProgramMapPID = XIO::ReadUns16_BE ( &cpiFile );
+ cpiFile.ReadAll ( &avchdProgramInfo.mNumberOfStreamsInPS, 1 );
+ cpiFile.ReadAll ( &avchdProgramInfo.mReserved2, 1 );
+
+ XMP_Uns16 streamPID = 0;
+ for ( int i=0; i<avchdProgramInfo.mNumberOfStreamsInPS; ++i ) {
+
+ XMP_Uns8 length = 0;
+ XMP_Uns8 streamCodingType = 0;
+
+ streamPID = XIO::ReadUns16_BE ( &cpiFile );
+ cpiFile.ReadAll ( &length, 1 );
+
+ XMP_Int64 pos = cpiFile.Offset();
+
+ cpiFile.ReadAll ( &streamCodingType, 1 );
+
+ switch ( streamCodingType ) {
+
+ case 0x1B : // Video stream case.
+ {
+ XMP_Uns8 videoFormatAndFrameRate;
+ cpiFile.ReadAll ( &videoFormatAndFrameRate, 1 );
+ avchdProgramInfo.mVideoStream.mVideoFormat = videoFormatAndFrameRate >> 4; // hi 4 bits
+ avchdProgramInfo.mVideoStream.mFrameRate = videoFormatAndFrameRate & 0x0f; // lo 4 bits
+
+ XMP_Uns8 aspectRatioAndReserved = 0;
+ cpiFile.ReadAll ( &aspectRatioAndReserved, 1 );
+ avchdProgramInfo.mVideoStream.mAspectRatio = aspectRatioAndReserved >> 4; // hi 4 bits
+
+ XMP_Uns8 ccFlag = 0;
+ cpiFile.ReadAll ( &ccFlag, 1 );
+ avchdProgramInfo.mVideoStream.mCCFlag = ccFlag;
+
+ avchdProgramInfo.mVideoStream.mPresent = 1;
+ }
+ break;
+
+ case 0x80 : // Fall through.
+ case 0x81 : // Audio stream case.
+ {
+ XMP_Uns8 audioPresentationTypeAndFrequency = 0;
+ cpiFile.ReadAll ( &audioPresentationTypeAndFrequency, 1 );
+
+ avchdProgramInfo.mAudioStream.mAudioPresentationType = audioPresentationTypeAndFrequency >> 4; // hi 4 bits
+ avchdProgramInfo.mAudioStream.mSamplingFrequency = audioPresentationTypeAndFrequency & 0x0f; // lo 4 bits
+
+ cpiFile.ReadAll ( avchdProgramInfo.mAudioStream.mAudioLanguageCode, 3 );
+ avchdProgramInfo.mAudioStream.mAudioLanguageCode[3] = 0;
+
+ avchdProgramInfo.mAudioStream.mPresent = 1;
+ }
+ break;
+
+ case 0x90 : // Overlay bitmap stream case.
+ cpiFile.ReadAll ( &avchdProgramInfo.mOverlayBitmapStream.mOBLanguageCode, 3 );
+ avchdProgramInfo.mOverlayBitmapStream.mOBLanguageCode[3] = 0;
+ avchdProgramInfo.mOverlayBitmapStream.mPresent = 1;
+ break;
+
+ case 0x91 : // Menu bitmap stream.
+ cpiFile.ReadAll ( &avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode, 3 );
+ avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode[3] = 0;
+ avchdProgramInfo.mMenuBitmapStream.mPresent = 1;
+ break;
+
+ default :
+ break;
+
+ }
+
+ cpiFile.Seek ( pos + length, kXMP_SeekFromStart );
+
+ }
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCHDExtensionData
+// ======================
+
+static bool ReadAVCHDExtensionData ( XMPFiles_IO & cpiFile, AVCHD_blkExtensionData& extensionDataHeader )
+{
+ extensionDataHeader.mLength = XIO::ReadUns32_BE ( &cpiFile );
+
+ if ( extensionDataHeader.mLength == 0 ) {
+ // Nothing to read
+ return true;
+ }
+
+ extensionDataHeader.mDataBlockStartAddress = XIO::ReadUns32_BE ( &cpiFile );
+ cpiFile.ReadAll ( extensionDataHeader.mReserved, 3 );
+ cpiFile.ReadAll ( &extensionDataHeader.mNumberOfDataEntries, 1 );
+
+ if ( extensionDataHeader.mNumberOfDataEntries != 1 ) {
+ // According to AVCHD Format. Book1. v. 1.01. p 38, "This field shall be set to 1 in this format."
+ return false;
+ }
+
+ extensionDataHeader.mExtDataEntry.mExtDataType = XIO::ReadUns16_BE ( &cpiFile );
+ extensionDataHeader.mExtDataEntry.mExtDataVersion = XIO::ReadUns16_BE ( &cpiFile );
+ extensionDataHeader.mExtDataEntry.mExtDataStartAddress = XIO::ReadUns32_BE ( &cpiFile );
+ extensionDataHeader.mExtDataEntry.mExtDataLength = XIO::ReadUns32_BE ( &cpiFile );
+
+ if ( extensionDataHeader.mExtDataEntry.mExtDataType != 0x1000 ) {
+ // According to AVCHD Format. Book1. v. 1.01. p 38, "If the metadata is for an AVCHD application,
+ // this value shall be set to 'Ox1OOO'."
+ return false;
+ }
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCCAMProMetaID
+// ===================
+//
+// Read Panasonic's proprietary PRO_MetaID block
+
+static bool ReadAVCCAMProMetaID ( XMPFiles_IO & cpiFile, XMP_Uns8 tagID, AVCHD_blkPanasonicPrivateData& extensionDataHeader )
+{
+ extensionDataHeader.mPresent = 1;
+ extensionDataHeader.mProMetaIDBlock.mPresent = 1;
+ extensionDataHeader.mProMetaIDBlock.mTagID = tagID;
+ cpiFile.ReadAll ( &extensionDataHeader.mProMetaIDBlock.mTagVersion, 1);
+ extensionDataHeader.mProMetaIDBlock.mTagLength = XIO::ReadUns16_BE ( &cpiFile );
+ cpiFile.ReadAll ( &extensionDataHeader.mProMetaIDBlock.mProfessionalMetaID, 16);
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCCAMProClipInfo
+// =====================
+//
+// Read Panasonic's proprietary PRO_ClipInfo block.
+
+static bool ReadAVCCAMProClipInfo ( XMPFiles_IO & cpiFile, XMP_Uns8 tagID, AVCHD_blkPanasonicPrivateData& extensionDataHeader )
+{
+ extensionDataHeader.mPresent = 1;
+ extensionDataHeader.mProClipIDBlock.mPresent = 1;
+ extensionDataHeader.mProClipIDBlock.mTagID = tagID;
+ cpiFile.ReadAll ( &extensionDataHeader.mProClipIDBlock.mTagVersion, 1);
+ extensionDataHeader.mProClipIDBlock.mTagLength = XIO::ReadUns16_BE ( &cpiFile );
+ cpiFile.ReadAll ( &extensionDataHeader.mProClipIDBlock.mGlobalClipID, 32);
+ cpiFile.ReadAll ( &extensionDataHeader.mProClipIDBlock.mStartTimecode, 4 );
+ extensionDataHeader.mProClipIDBlock.mStartBinaryGroup = XIO::ReadUns32_BE ( &cpiFile );
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCCAM_blkPRO_ShotMark
+// ==========================
+//
+// Read Panasonic's proprietary PRO_ShotMark block.
+
+static bool ReadAVCCAM_blkPRO_ShotMark ( XMPFiles_IO & mplFile, AVCCAM_blkProPlayListMark& proMark )
+{
+ proMark.mShotMark.mPresent = 1;
+ mplFile.ReadAll ( &proMark.mShotMark.mShotMark, 1);
+ mplFile.ReadAll ( &proMark.mShotMark.mFillItem, 3);
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCCAM_blkPRO_Access
+// ========================
+//
+// Read Panasonic's proprietary PRO_Access block.
+
+static bool ReadAVCCAM_blkPRO_Access ( XMPFiles_IO & mplFile, AVCCAM_blkProPlayListMark& proMark )
+{
+ proMark.mAccess.mPresent = 1;
+ mplFile.ReadAll ( &proMark.mAccess.mCreatorCharacterSet, 1 );
+ mplFile.ReadAll ( &proMark.mAccess.mCreatorLength, 1 );
+ mplFile.ReadAll ( &proMark.mAccess.mCreator, 32 );
+ mplFile.ReadAll ( &proMark.mAccess.mLastUpdatePersonCharacterSet, 1 );
+ mplFile.ReadAll ( &proMark.mAccess.mLastUpdatePersonLength, 1 );
+ mplFile.ReadAll ( &proMark.mAccess.mLastUpdatePerson, 32 );
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCCAM_blkPRO_Device
+// ========================
+//
+// Read Panasonic's proprietary PRO_Device block.
+
+static bool ReadAVCCAM_blkPRO_Device ( XMPFiles_IO & mplFile, AVCCAM_blkProPlayListMark& proMark )
+{
+ proMark.mDevice.mPresent = 1;
+ proMark.mDevice.mMakerID = XIO::ReadUns16_BE ( &mplFile );
+ proMark.mDevice.mMakerModelCode = XIO::ReadUns16_BE ( &mplFile );
+ mplFile.ReadAll ( &proMark.mDevice.mSerialNoCharacterCode, 1 );
+ mplFile.ReadAll ( &proMark.mDevice.mSerialNoLength, 1 );
+ mplFile.ReadAll ( &proMark.mDevice.mSerialNo, 24 );
+ mplFile.ReadAll ( &proMark.mDevice.mFillItem, 2 );
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCCAM_blkPRO_Shoot
+// =======================
+//
+// Read Panasonic's proprietary PRO_Shoot block.
+
+static bool ReadAVCCAM_blkPRO_Shoot ( XMPFiles_IO & mplFile, AVCCAM_blkProPlayListMark& proMark )
+{
+ proMark.mShoot.mPresent = 1;
+ mplFile.ReadAll ( &proMark.mShoot.mShooterCharacterSet, 1 );
+ mplFile.ReadAll ( &proMark.mShoot.mShooterLength, 1 );
+ mplFile.ReadAll ( &proMark.mShoot.mShooter, 32 );
+ mplFile.ReadAll ( &proMark.mShoot.mStartDateTimeZone, 1 );
+ mplFile.ReadAll ( &proMark.mShoot.mStartDate, 7 );
+ mplFile.ReadAll ( &proMark.mShoot.mEndDateTimeZone, 1 );
+ mplFile.ReadAll ( &proMark.mShoot.mEndDate, 7 );
+ mplFile.ReadAll ( &proMark.mShoot.mFillItem, 2 );
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCCAM_blkPRO_Location
+// ==========================
+//
+// Read Panasonic's proprietary PRO_Location block.
+
+static bool ReadAVCCAM_blkPRO_Location ( XMPFiles_IO & mplFile, AVCCAM_blkProPlayListMark& proMark )
+{
+ proMark.mLocation.mPresent = 1;
+ mplFile.ReadAll ( &proMark.mLocation.mSource, 1 );
+ proMark.mLocation.mGPSLatitudeRef = XIO::ReadUns32_BE ( &mplFile );
+ proMark.mLocation.mGPSLatitude1 = XIO::ReadUns32_BE ( &mplFile );
+ proMark.mLocation.mGPSLatitude2 = XIO::ReadUns32_BE ( &mplFile );
+ proMark.mLocation.mGPSLatitude3 = XIO::ReadUns32_BE ( &mplFile );
+ proMark.mLocation.mGPSLongitudeRef = XIO::ReadUns32_BE ( &mplFile );
+ proMark.mLocation.mGPSLongitude1 = XIO::ReadUns32_BE ( &mplFile );
+ proMark.mLocation.mGPSLongitude2 = XIO::ReadUns32_BE ( &mplFile );
+ proMark.mLocation.mGPSLongitude3 = XIO::ReadUns32_BE ( &mplFile );
+ proMark.mLocation.mGPSAltitudeRef = XIO::ReadUns32_BE ( &mplFile );
+ proMark.mLocation.mGPSAltitude = XIO::ReadUns32_BE ( &mplFile );
+ mplFile.ReadAll ( &proMark.mLocation.mPlaceNameCharacterSet, 1 );
+ mplFile.ReadAll ( &proMark.mLocation.mPlaceNameLength, 1 );
+ mplFile.ReadAll ( &proMark.mLocation.mPlaceName, 64 );
+ mplFile.ReadAll ( &proMark.mLocation.mFillItem, 1 );
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCCAMProPlaylistInfo
+// =========================
+//
+// Read Panasonic's proprietary PRO_PlayListInfo block.
+
+static bool ReadAVCCAMProPlaylistInfo ( XMPFiles_IO & mplFile,
+ XMP_Uns8 tagID,
+ XMP_Uns16 playlistMarkID,
+ AVCHD_blkPanasonicPrivateData& extensionDataHeader )
+{
+ AVCCAM_Pro_PlayListInfo& playlistBlock = extensionDataHeader.mProPlaylistInfoBlock;
+
+ playlistBlock.mTagID = tagID;
+ mplFile.ReadAll ( &playlistBlock.mTagVersion, 1);
+ mplFile.ReadAll ( &playlistBlock.mFillItem1, 2);
+ playlistBlock.mLength = XIO::ReadUns32_BE ( &mplFile );
+ playlistBlock.mNumberOfPlayListMarks = XIO::ReadUns16_BE ( &mplFile );
+ mplFile.ReadAll ( &playlistBlock.mFillItem2, 2);
+
+ if ( playlistBlock.mNumberOfPlayListMarks == 0 ) return true;
+
+ extensionDataHeader.mPresent = 1;
+
+ XMP_Uns64 blockStart = 0;
+
+ for ( int i = 0; i < playlistBlock.mNumberOfPlayListMarks; ++i ) {
+ AVCCAM_blkProPlayListMark& currMark = playlistBlock.mPlayListMark;
+
+ mplFile.ReadAll ( &currMark.mProTagID, 1);
+ mplFile.ReadAll ( &currMark.mFillItem1, 1);
+ currMark.mLength = XIO::ReadUns16_BE ( &mplFile );
+ blockStart = mplFile.Offset();
+ mplFile.ReadAll ( &currMark.mMarkType, 1 );
+
+ if ( ( currMark.mProTagID == 0x40 ) && ( currMark.mMarkType == 0x01 ) ) {
+ mplFile.ReadAll ( &currMark.mEntryMark.mGlobalClipID, 32);
+
+ // skip marks for different clips
+ if ( i == playlistMarkID ) {
+ playlistBlock.mPresent = 1;
+ currMark.mPresent = 1;
+ mplFile.ReadAll ( &currMark.mEntryMark.mStartTimeCode, 4);
+ mplFile.ReadAll ( &currMark.mEntryMark.mStreamTimecodeInfo, 1);
+ mplFile.ReadAll ( &currMark.mEntryMark.mStartBinaryGroup, 4);
+ mplFile.ReadAll ( &currMark.mEntryMark.mLastUpdateTimeZone, 1);
+ mplFile.ReadAll ( &currMark.mEntryMark.mLastUpdateDate, 7);
+ mplFile.ReadAll ( &currMark.mEntryMark.mFillItem, 2);
+
+ XMP_Uns64 currPos = mplFile.Offset();
+ XMP_Uns8 blockTag = 0;
+ XMP_Uns8 blockFill;
+ XMP_Uns16 blockLength = 0;
+
+ while ( currPos < ( blockStart + currMark.mLength ) ) {
+ mplFile.ReadAll ( &blockTag, 1);
+ mplFile.ReadAll ( &blockFill, 1);
+ blockLength = XIO::ReadUns16_BE ( &mplFile );
+ currPos += 4;
+
+ switch ( blockTag ) {
+ case 0x20:
+ if ( ! ReadAVCCAM_blkPRO_ShotMark ( mplFile, currMark ) ) return false;
+ break;
+
+
+ case 0x21:
+ if ( ! ReadAVCCAM_blkPRO_Access ( mplFile, currMark ) ) return false;
+ break;
+
+ case 0x22:
+ if ( ! ReadAVCCAM_blkPRO_Device ( mplFile, currMark ) ) return false;
+ break;
+
+ case 0x23:
+ if ( ! ReadAVCCAM_blkPRO_Shoot ( mplFile, currMark ) ) return false;
+ break;
+
+ case 0x24:
+ if (! ReadAVCCAM_blkPRO_Location ( mplFile, currMark ) ) return false;
+ break;
+
+ default : break;
+ }
+
+ currPos += blockLength;
+ mplFile.Seek ( currPos, kXMP_SeekFromStart );
+ }
+ }
+ }
+
+ mplFile.Seek ( blockStart + currMark.mLength, kXMP_SeekFromStart );
+ }
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCCAMMakersPrivateData
+// ===========================
+//
+// Read Panasonic's implementation of an AVCCAM "Maker's Private Data" block. Panasonic calls their
+// extensions "AVCCAM."
+
+static bool ReadAVCCAMMakersPrivateData ( XMPFiles_IO & fileRef,
+ XMP_Uns16 playlistMarkID,
+ AVCHD_blkPanasonicPrivateData& avccamPrivateData )
+{
+ const XMP_Uns64 blockStart = fileRef.Offset();
+
+ avccamPrivateData.mNumberOfData = XIO::ReadUns16_BE ( &fileRef );
+ fileRef.ReadAll ( &avccamPrivateData.mReserved, 2 );
+
+ for (int i = 0; i < avccamPrivateData.mNumberOfData; ++i) {
+ const XMP_Uns8 tagID = XIO::ReadUns8 ( &fileRef );
+
+ switch ( tagID ) {
+ case 0xe0: ReadAVCCAMProMetaID ( fileRef, tagID, avccamPrivateData );
+ break;
+ case 0xe2: ReadAVCCAMProClipInfo( fileRef, tagID, avccamPrivateData );
+ break;
+ case 0xf0: ReadAVCCAMProPlaylistInfo( fileRef, tagID, playlistMarkID, avccamPrivateData );
+ break;
+
+ default:
+ // Ignore any blocks we don't now or care about
+ break;
+ }
+ }
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCHDMakersPrivateData
+// ==========================
+//
+// AVCHD Format. Book 2: Recording Extension Specifications, section 4.2.4.2.
+
+static bool ReadAVCHDMakersPrivateData ( XMPFiles_IO & mplFile,
+ XMP_Uns16 playlistMarkID,
+ AVCHD_blkMakersPrivateData& avchdLegacyData )
+{
+ const XMP_Uns64 blockStart = mplFile.Offset();
+
+ avchdLegacyData.mLength = XIO::ReadUns32_BE ( &mplFile );
+
+ if ( avchdLegacyData.mLength == 0 ) return false;
+
+ avchdLegacyData.mPresent = 1;
+ avchdLegacyData.mDataBlockStartAddress = XIO::ReadUns32_BE ( &mplFile );
+ mplFile.ReadAll ( &avchdLegacyData.mReserved, 3 );
+ mplFile.ReadAll ( &avchdLegacyData.mNumberOfMakerEntries, 1 );
+
+ if ( avchdLegacyData.mNumberOfMakerEntries == 0 ) return true;
+
+ XMP_Uns16 makerID;
+ XMP_Uns16 makerModelCode;
+ XMP_Uns32 mpdStartAddress;
+ XMP_Uns32 mpdLength;
+
+ for ( int i = 0; i < avchdLegacyData.mNumberOfMakerEntries; ++i ) {
+ makerID = XIO::ReadUns16_BE ( &mplFile );
+ makerModelCode = XIO::ReadUns16_BE ( &mplFile );
+ mpdStartAddress = XIO::ReadUns32_BE ( &mplFile );
+ mpdLength = XIO::ReadUns32_BE ( &mplFile );
+
+ // We only have documentation for Panasonic's Maker's Private Data blocks, so we'll ignore everyone else's
+ if ( makerID == kMakerIDPanasonic ) {
+ avchdLegacyData.mMakerID = makerID;
+ avchdLegacyData.mMakerModelCode = makerModelCode;
+ mplFile.Seek ( blockStart + mpdStartAddress, kXMP_SeekFromStart );
+
+ if (! ReadAVCCAMMakersPrivateData ( mplFile, playlistMarkID, avchdLegacyData.mPanasonicPrivateData ) ) return false;
+ }
+ }
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCHDClipExtensionData
+// ==========================
+
+static bool ReadAVCHDClipExtensionData ( XMPFiles_IO & cpiFile, AVCHD_blkClipExtensionData& avchdExtensionData )
+{
+ const XMP_Int64 extensionBlockStart = cpiFile.Offset();
+ AVCHD_blkExtensionData extensionDataHeader;
+
+ if ( ! ReadAVCHDExtensionData ( cpiFile, extensionDataHeader ) ) {
+ return false;
+ }
+
+ if ( extensionDataHeader.mLength == 0 ) {
+ return true;
+ }
+
+ const XMP_Int64 dataBlockStart = extensionBlockStart + extensionDataHeader.mDataBlockStartAddress;
+
+ cpiFile.Seek ( dataBlockStart, kXMP_SeekFromStart );
+ cpiFile.ReadAll ( avchdExtensionData.mTypeIndicator, 4 );
+
+ if ( strncmp ( reinterpret_cast<const char*>( avchdExtensionData.mTypeIndicator ), "CLEX", 4 ) != 0 ) return false;
+
+ avchdExtensionData.mPresent = 1;
+ cpiFile.ReadAll ( avchdExtensionData.mReserved1, 4 );
+ avchdExtensionData.mProgramInfoExtStartAddress = XIO::ReadUns32_BE ( &cpiFile );
+ avchdExtensionData.mMakersPrivateDataStartAddress = XIO::ReadUns32_BE ( &cpiFile );
+
+ // read Clip info extension
+ cpiFile.Seek ( dataBlockStart + 40, kXMP_SeekFromStart );
+ avchdExtensionData.mClipInfoExt.mLength = XIO::ReadUns32_BE ( &cpiFile );
+ avchdExtensionData.mClipInfoExt.mMakerID = XIO::ReadUns16_BE ( &cpiFile );
+ avchdExtensionData.mClipInfoExt.mMakerModelCode = XIO::ReadUns16_BE ( &cpiFile );
+
+ if ( avchdExtensionData.mMakersPrivateDataStartAddress == 0 ) return true;
+
+ if ( avchdExtensionData.mClipInfoExt.mMakerID == kMakerIDPanasonic ) {
+ // Read Maker's Private Data block -- we only have Panasonic's definition for their AVCCAM models
+ // at this point, so we'll ignore the block if its from a different manufacturer.
+ cpiFile.Seek ( dataBlockStart + avchdExtensionData.mMakersPrivateDataStartAddress, kXMP_SeekFromStart );
+
+ if ( ! ReadAVCHDMakersPrivateData ( cpiFile, 0, avchdExtensionData.mMakersPrivateData ) ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// =================================================================================================
+// AVCHD_PlaylistContainsClip
+// ==========================
+//
+// Returns true of the specified AVCHD playlist block references the specified clip, or false if not.
+
+static bool AVCHD_PlaylistContainsClip ( XMPFiles_IO & mplFile, XMP_Uns16& playItemID, const std::string& strClipName )
+{
+ // Read clip header. ( AVCHD Format. Book1. v. 1.01. p 45 )
+ struct AVCHD_blkPlayList
+ {
+ XMP_Uns32 mLength;
+ XMP_Uns16 mReserved;
+ XMP_Uns16 mNumberOfPlayItems;
+ XMP_Uns16 mNumberOfSubPaths;
+ };
+
+ AVCHD_blkPlayList blkPlayList;
+ blkPlayList.mLength = XIO::ReadUns32_BE ( &mplFile );
+ mplFile.ReadAll ( &blkPlayList.mReserved, 2 );
+ blkPlayList.mNumberOfPlayItems = XIO::ReadUns16_BE ( &mplFile );
+ blkPlayList.mNumberOfSubPaths = XIO::ReadUns16_BE ( &mplFile );
+
+ // Search the play items. ( AVCHD Format. Book1. v. 1.01. p 47 )
+ struct AVCHD_blkPlayItem
+ {
+ XMP_Uns16 mLength;
+ char mClipInformationFileName[5];
+ // Note: remaining fields omitted because we don't care about them
+ };
+
+ AVCHD_blkPlayItem currPlayItem;
+ XMP_Uns64 blockStart = 0;
+ for ( playItemID = 0; playItemID < blkPlayList.mNumberOfPlayItems; ++playItemID ) {
+ currPlayItem.mLength = XIO::ReadUns16_BE ( &mplFile );
+
+ // mLength is measured from the end of mLength, not the start of the block ( AVCHD Format. Book1. v. 1.01. p 47 )
+ blockStart = mplFile.Offset();
+ mplFile.ReadAll ( currPlayItem.mClipInformationFileName, 5 );
+
+ if ( strncmp ( strClipName.c_str(), currPlayItem.mClipInformationFileName, 5 ) == 0 ) return true;
+
+ mplFile.Seek ( blockStart + currPlayItem.mLength, kXMP_SeekFromStart );
+ }
+
+ return false;
+}
+
+// =================================================================================================
+// ReadAVCHDPlaylistMetadataBlock
+// ==============================
+
+static bool ReadAVCHDPlaylistMetadataBlock ( XMPFiles_IO & mplFile,
+ AVCHD_blkPlaylistMeta& avchdLegacyData )
+{
+ avchdLegacyData.mLength = XIO::ReadUns32_BE ( &mplFile );
+
+ if ( avchdLegacyData.mLength < sizeof ( AVCHD_blkPlaylistMeta ) ) return false;
+
+ avchdLegacyData.mMakerID = XIO::ReadUns16_BE ( &mplFile );
+ avchdLegacyData.mMakerModelCode = XIO::ReadUns16_BE ( &mplFile );
+ mplFile.ReadAll ( &avchdLegacyData.mReserved1, 4 );
+ avchdLegacyData.mRefToMenuThumbnailIndex = XIO::ReadUns16_BE ( &mplFile );
+ mplFile.ReadAll ( &avchdLegacyData.mBlkTimezone, 1 );
+ mplFile.ReadAll ( &avchdLegacyData.mRecordDataAndTime, 7 );
+ mplFile.ReadAll ( &avchdLegacyData.mReserved2, 1 );
+ mplFile.ReadAll ( &avchdLegacyData.mPlaylistCharacterSet, 1 );
+ mplFile.ReadAll ( &avchdLegacyData.mPlaylistNameLength, 1 );
+ mplFile.ReadAll ( &avchdLegacyData.mPlaylistName, avchdLegacyData.mPlaylistNameLength );
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCHDPlaylistMarkExtension
+// ==============================
+
+static bool ReadAVCHDPlaylistMarkExtension ( XMPFiles_IO & mplFile,
+ XMP_Uns16 playlistMarkID,
+ AVCHD_blkPlayListMarkExt& avchdLegacyData )
+{
+ avchdLegacyData.mLength = XIO::ReadUns32_BE ( &mplFile );
+
+ if ( avchdLegacyData.mLength == 0 ) return false;
+
+ avchdLegacyData.mNumberOfPlaylistMarks = XIO::ReadUns16_BE ( &mplFile );
+
+ if ( avchdLegacyData.mNumberOfPlaylistMarks <= playlistMarkID ) return true;
+
+ // Number of bytes in blkMarkExtension, AVCHD Book 2, section 4.3.3.1
+ const XMP_Uns64 markExtensionSize = 66;
+
+ // Entries in the mark extension block correspond one-to-one with entries in
+ // blkPlaylistMark, so we'll only read the one that corresponds to the
+ // chosen clip.
+ const XMP_Uns64 markOffset = markExtensionSize * playlistMarkID;
+
+ avchdLegacyData.mPresent = 1;
+ mplFile.Seek ( markOffset, kXMP_SeekFromCurrent );
+ avchdLegacyData.mMakerID = XIO::ReadUns16_BE ( &mplFile );
+ avchdLegacyData.mMakerModelCode = XIO::ReadUns16_BE ( &mplFile );
+ mplFile.ReadAll ( &avchdLegacyData.mReserved1, 3 );
+ mplFile.ReadAll ( &avchdLegacyData.mFlags, 1 );
+ avchdLegacyData.mRefToMarkThumbnailIndex = XIO::ReadUns16_BE ( &mplFile );
+ mplFile.ReadAll ( &avchdLegacyData.mBlkTimezone, 1 );
+ mplFile.ReadAll ( &avchdLegacyData.mRecordDataAndTime, 7 );
+ mplFile.ReadAll ( &avchdLegacyData.mMarkCharacterSet, 1 );
+ mplFile.ReadAll ( &avchdLegacyData.mMarkNameLength, 1 );
+ mplFile.ReadAll ( &avchdLegacyData.mMarkName, 24 );
+ mplFile.ReadAll ( &avchdLegacyData.mMakersInformation, 16 );
+ mplFile.ReadAll ( &avchdLegacyData.mBlkTimecode, 4 );
+ mplFile.ReadAll ( &avchdLegacyData.mReserved2, 2 );
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCHDPlaylistMarkID
+// =======================
+//
+// Read the playlist mark block to find the ID of the playlist mark that matches the specified
+// playlist item.
+
+static bool ReadAVCHDPlaylistMarkID ( XMPFiles_IO & mplFile,
+ XMP_Uns16 playItemID,
+ XMP_Uns16& markID )
+{
+ XMP_Uns32 length = XIO::ReadUns32_BE ( &mplFile );
+ XMP_Uns16 numberOfPlayListMarks = XIO::ReadUns16_BE ( &mplFile );
+
+ if ( length == 0 ) return false;
+
+ XMP_Uns8 reserved;
+ XMP_Uns8 markType;
+ XMP_Uns16 refToPlayItemID;
+
+ for ( int i = 0; i < numberOfPlayListMarks; ++i ) {
+ mplFile.ReadAll ( &reserved, 1 );
+ mplFile.ReadAll ( &markType, 1 );
+ refToPlayItemID = XIO::ReadUns16_BE ( &mplFile );
+
+ if ( ( markType == 0x01 ) && ( refToPlayItemID == playItemID ) ) {
+ markID = i;
+ return true;
+ }
+
+ mplFile.Seek ( 10, kXMP_SeekFromCurrent );
+ }
+
+ return false;
+}
+
+// =================================================================================================
+// ReadAVCHDPlaylistExtensionData
+// ==============================
+
+static bool ReadAVCHDPlaylistExtensionData ( XMPFiles_IO & mplFile,
+ AVCHD_LegacyMetadata& avchdLegacyData,
+ XMP_Uns16 playlistMarkID )
+{
+ const XMP_Int64 extensionBlockStart = mplFile.Offset();
+ AVCHD_blkExtensionData extensionDataHeader;
+
+ if ( ! ReadAVCHDExtensionData ( mplFile, extensionDataHeader ) ) {
+ return false;
+ }
+
+ if ( extensionDataHeader.mLength == 0 ) {
+ return true;
+ }
+
+ const XMP_Int64 dataBlockStart = extensionBlockStart + extensionDataHeader.mDataBlockStartAddress;
+ AVCHD_blkPlayListExtensionData& extensionData = avchdLegacyData.mPlaylistExtensionData;
+ const int reserved2Len = 24;
+
+ mplFile.Seek ( dataBlockStart, kXMP_SeekFromStart );
+ mplFile.ReadAll ( extensionData.mTypeIndicator, 4 );
+
+ if ( strncmp ( extensionData.mTypeIndicator, "PLEX", 4 ) != 0 ) return false;
+
+ extensionData.mPresent = true;
+ mplFile.ReadAll ( extensionData.mReserved, 4 );
+ extensionData.mPlayListMarkExtStartAddress = XIO::ReadUns32_BE ( &mplFile );
+ extensionData.mMakersPrivateDataStartAddress = XIO::ReadUns32_BE ( &mplFile );
+ mplFile.Seek ( reserved2Len, kXMP_SeekFromCurrent );
+
+ if ( ! ReadAVCHDPlaylistMetadataBlock ( mplFile, extensionData.mPlaylistMeta ) ) return false;
+
+ mplFile.Seek ( dataBlockStart + extensionData.mPlayListMarkExtStartAddress, kXMP_SeekFromStart );
+
+ if ( ! ReadAVCHDPlaylistMarkExtension ( mplFile, playlistMarkID, extensionData.mPlaylistMarkExt ) ) return false;
+
+ if ( extensionData.mMakersPrivateDataStartAddress > 0 ) {
+
+ if ( ! avchdLegacyData.mClipExtensionData.mMakersPrivateData.mPanasonicPrivateData.mPresent ) return false;
+
+ mplFile.Seek ( dataBlockStart + extensionData.mMakersPrivateDataStartAddress, kXMP_SeekFromStart );
+
+ if ( ! ReadAVCHDMakersPrivateData ( mplFile, playlistMarkID, extensionData.mMakersPrivateData ) ) return false;
+
+ }
+
+ return true;
+}
+
+// =================================================================================================
+// ReadAVCHDLegacyClipFile
+// =======================
+//
+// Read the legacy metadata stored in an AVCHD .CPI file.
+
+static bool ReadAVCHDLegacyClipFile ( const std::string& strPath, AVCHD_LegacyMetadata& avchdLegacyData )
+{
+ bool success = false;
+
+ try {
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( strPath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return false; // The open failed.
+ XMPFiles_IO cpiFile ( hostRef, strPath.c_str(), Host_IO::openReadOnly );
+
+ memset ( &avchdLegacyData, 0, sizeof(AVCHD_LegacyMetadata) );
+
+ // Read clip header. ( AVCHD Format. Book1. v. 1.01. p 64 )
+ struct AVCHD_ClipInfoHeader
+ {
+ char mTypeIndicator[4];
+ char mTypeIndicator2[4];
+ XMP_Uns32 mSequenceInfoStartAddress;
+ XMP_Uns32 mProgramInfoStartAddress;
+ XMP_Uns32 mCPIStartAddress;
+ XMP_Uns32 mClipMarkStartAddress;
+ XMP_Uns32 mExtensionDataStartAddress;
+ XMP_Uns8 mReserved[12];
+ };
+
+ // Read the AVCHD header.
+ AVCHD_ClipInfoHeader avchdHeader;
+ cpiFile.ReadAll ( avchdHeader.mTypeIndicator, 4 );
+ cpiFile.ReadAll ( avchdHeader.mTypeIndicator2, 4 );
+
+ if ( strncmp ( avchdHeader.mTypeIndicator, "HDMV", 4 ) != 0 ) return false;
+ if ( strncmp ( avchdHeader.mTypeIndicator2, "0100", 4 ) != 0 ) return false;
+
+ avchdHeader.mSequenceInfoStartAddress = XIO::ReadUns32_BE ( &cpiFile );
+ avchdHeader.mProgramInfoStartAddress = XIO::ReadUns32_BE ( &cpiFile );
+ avchdHeader.mCPIStartAddress = XIO::ReadUns32_BE ( &cpiFile );
+ avchdHeader.mClipMarkStartAddress = XIO::ReadUns32_BE ( &cpiFile );
+ avchdHeader.mExtensionDataStartAddress = XIO::ReadUns32_BE ( &cpiFile );
+ cpiFile.ReadAll ( avchdHeader.mReserved, 12 );
+
+ // Seek to the program header. (AVCHD Format. Book1. v. 1.01. p 77 )
+ cpiFile.Seek ( avchdHeader.mProgramInfoStartAddress, kXMP_SeekFromStart );
+
+ // Read the program info block
+ success = ReadAVCHDProgramInfo ( cpiFile, avchdLegacyData.mProgramInfo );
+
+ if ( success && ( avchdHeader.mExtensionDataStartAddress != 0 ) ) {
+ // Seek to the program header. (AVCHD Format. Book1. v. 1.01. p 77 )
+ cpiFile.Seek ( avchdHeader.mExtensionDataStartAddress, kXMP_SeekFromStart );
+ success = ReadAVCHDClipExtensionData ( cpiFile, avchdLegacyData.mClipExtensionData );
+ }
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+
+ return success;
+
+} // ReadAVCHDLegacyClipFile
+
+// =================================================================================================
+// ReadAVCHDLegacyPlaylistFile
+// ===========================
+//
+// Read the legacy metadata stored in an AVCHD .MPL file.
+
+static bool ReadAVCHDLegacyPlaylistFile ( const std::string& mplPath,
+ const std::string& strClipName,
+ AVCHD_LegacyMetadata& avchdLegacyData )
+{
+
+#if 1
+ bool success = false;
+
+ try {
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( mplPath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return false; // The open failed.
+ XMPFiles_IO mplFile ( hostRef, mplPath.c_str(), Host_IO::openReadOnly );
+
+ // Read playlist header. ( AVCHD Format. Book1. v. 1.01. p 43 )
+ struct AVCHD_PlaylistFileHeader {
+ char mTypeIndicator[4];
+ char mTypeIndicator2[4];
+ XMP_Uns32 mPlaylistStartAddress;
+ XMP_Uns32 mPlaylistMarkStartAddress;
+ XMP_Uns32 mExtensionDataStartAddress;
+ };
+
+ // Read the AVCHD playlist file header.
+ AVCHD_PlaylistFileHeader avchdHeader;
+ mplFile.ReadAll ( avchdHeader.mTypeIndicator, 4 );
+ mplFile.ReadAll ( avchdHeader.mTypeIndicator2, 4 );
+
+ if ( strncmp ( avchdHeader.mTypeIndicator, "MPLS", 4 ) != 0 ) return false;
+ if ( strncmp ( avchdHeader.mTypeIndicator2, "0100", 4 ) != 0 ) return false;
+
+ avchdHeader.mPlaylistStartAddress = XIO::ReadUns32_BE ( &mplFile );
+ avchdHeader.mPlaylistMarkStartAddress = XIO::ReadUns32_BE ( &mplFile );
+ avchdHeader.mExtensionDataStartAddress = XIO::ReadUns32_BE ( &mplFile );
+
+ if ( avchdHeader.mExtensionDataStartAddress == 0 ) return false;
+
+ // Seek to the start of the Playlist block. (AVCHD Format. Book1. v. 1.01. p 45 )
+ mplFile.Seek ( avchdHeader.mPlaylistStartAddress, kXMP_SeekFromStart );
+
+ XMP_Uns16 playItemID = 0xFFFF;
+ XMP_Uns16 playlistMarkID = 0xFFFF;
+
+ if ( AVCHD_PlaylistContainsClip ( mplFile, playItemID, strClipName ) ) {
+ mplFile.Seek ( avchdHeader.mPlaylistMarkStartAddress, kXMP_SeekFromStart );
+ if ( ! ReadAVCHDPlaylistMarkID ( mplFile, playItemID, playlistMarkID ) ) return false;
+ mplFile.Seek ( avchdHeader.mExtensionDataStartAddress, kXMP_SeekFromStart );
+ success = ReadAVCHDPlaylistExtensionData ( mplFile, avchdLegacyData, playlistMarkID );
+ }
+
+ } catch ( ... ) {
+
+ success = false;
+
+ }
+
+ return success;
+
+#else
+
+ bool success = false;
+ std::string mplPath;
+ char playlistName [10];
+ const int rootPlaylistNum = atoi(strClipName.c_str());
+
+ // Find the corresponding .MPL file -- because of clip spanning the .MPL name may not match the .CPI name for
+ // a given clip -- we need to open .MPL files and look for one that contains a reference to the clip name. To speed
+ // up the search we'll start with the playlist with the same number/name as the clip and search backwards. Assuming
+ // this directory was generated by a camera, the clip numbers will increase sequentially across the playlist files,
+ // though one playlist file may reference more than one clip.
+ for ( int i = rootPlaylistNum; i >= 0; --i ) {
+
+ sprintf ( playlistName, "%05d", i );
+
+ if ( MakeLeafPath ( &mplPath, strRootPath.c_str(), "PLAYLIST", playlistName, ".mpl", true /* checkFile */ ) ) {
+
+ try {
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( mplPath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return false; // The open failed.
+ XMPFiles_IO mplFile ( hostRef, mplPath.c_str(), Host_IO::openReadOnly );
+
+ // Read playlist header. ( AVCHD Format. Book1. v. 1.01. p 43 )
+ struct AVCHD_PlaylistFileHeader
+ {
+ char mTypeIndicator[4];
+ char mTypeIndicator2[4];
+ XMP_Uns32 mPlaylistStartAddress;
+ XMP_Uns32 mPlaylistMarkStartAddress;
+ XMP_Uns32 mExtensionDataStartAddress;
+ };
+
+ // Read the AVCHD playlist file header.
+ AVCHD_PlaylistFileHeader avchdHeader;
+ mplFile.ReadAll ( avchdHeader.mTypeIndicator, 4 );
+ mplFile.ReadAll ( avchdHeader.mTypeIndicator2, 4 );
+
+ if ( strncmp ( avchdHeader.mTypeIndicator, "MPLS", 4 ) != 0 ) return false;
+ if ( strncmp ( avchdHeader.mTypeIndicator2, "0100", 4 ) != 0 ) return false;
+
+ avchdHeader.mPlaylistStartAddress = XIO::ReadUns32_BE ( &mplFile );
+ avchdHeader.mPlaylistMarkStartAddress = XIO::ReadUns32_BE ( &mplFile );
+ avchdHeader.mExtensionDataStartAddress = XIO::ReadUns32_BE ( &mplFile );
+
+ if ( avchdHeader.mExtensionDataStartAddress == 0 ) return false;
+
+ // Seek to the start of the Playlist block. (AVCHD Format. Book1. v. 1.01. p 45 )
+ mplFile.Seek ( avchdHeader.mPlaylistStartAddress, kXMP_SeekFromStart );
+
+ XMP_Uns16 playItemID = 0xFFFF;
+ XMP_Uns16 playlistMarkID = 0xFFFF;
+
+ if ( AVCHD_PlaylistContainsClip ( mplFile, playItemID, strClipName ) ) {
+ mplFile.Seek ( avchdHeader.mPlaylistMarkStartAddress, kXMP_SeekFromStart );
+
+ if ( ! ReadAVCHDPlaylistMarkID ( mplFile, playItemID, playlistMarkID ) ) return false;
+
+ mplFile.Seek ( avchdHeader.mExtensionDataStartAddress, kXMP_SeekFromStart );
+ success = ReadAVCHDPlaylistExtensionData ( mplFile, avchdLegacyData, playlistMarkID );
+ }
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+ }
+
+ }
+
+ return success;
+
+#endif
+
+} // ReadAVCHDLegacyPlaylistFile
+
+// =================================================================================================
+// FindAVCHDLegacyPlaylistFile
+// ===========================
+//
+// Find and read the legacy metadata stored in an AVCHD .MPL file.
+
+static bool FindAVCHDLegacyPlaylistFile ( const std::string& strRootPath,
+ const std::string& strClipName,
+ AVCHD_LegacyMetadata& avchdLegacyData )
+{
+ bool success = false;
+ std::string mplPath;
+
+ // Find the corresponding .MPL file -- because of clip spanning the .MPL name may not match the
+ // .CPI name for a given clip -- we need to open .MPL files and look for one that contains a
+ // reference to the clip name. To speed up the search we'll start with the playlist with the
+ // same number/name as the clip, and if that fails look into other playlist files in the
+ // directory. One playlist file may reference more than one clip.
+
+ if ( MakeLeafPath ( &mplPath, strRootPath.c_str(), "PLAYLIST", strClipName.c_str(), ".mpl", true /* checkFile */ ) ) {
+ success = ReadAVCHDLegacyPlaylistFile ( mplPath, strClipName, avchdLegacyData );
+ }
+
+ if ( ! success ) {
+
+ std::string playlistPath = strRootPath;
+ playlistPath += kDirChar;
+ playlistPath += "BDMV";
+ playlistPath += kDirChar;
+ playlistPath += "PLAYLIST";
+ playlistPath += kDirChar;
+
+ std::string childName;
+
+ if ( Host_IO::GetFileMode ( playlistPath.c_str() ) == Host_IO::kFMode_IsFolder ) {
+
+ Host_IO::AutoFolder af;
+ af.folder = Host_IO::OpenFolder ( playlistPath.c_str() );
+ if ( af.folder == Host_IO::noFolderRef ) return false;
+
+ while ( (! success) && Host_IO::GetNextChild ( af.folder, &childName ) &&
+ (childName.find(".mpl") || childName.find(".MPL")) ) {
+ mplPath = playlistPath + childName;
+ if ( Host_IO::GetFileMode ( mplPath.c_str() ) == Host_IO::kFMode_IsFile ) {
+ success = ReadAVCHDLegacyPlaylistFile ( mplPath, strClipName, avchdLegacyData );
+ }
+
+ }
+
+ af.Close();
+
+ }
+
+ }
+
+ return success;
+
+} // FindAVCHDLegacyPlaylistFile
+
+// =================================================================================================
+// ReadAVCHDLegacyMetadata
+// =======================
+//
+// Read the legacy metadata stored in an AVCHD .CPI file.
+
+static bool ReadAVCHDLegacyMetadata ( const std::string& strPath,
+ const std::string& strRootPath,
+ const std::string& strClipName,
+ AVCHD_LegacyMetadata& avchdLegacyData )
+{
+ bool success = ReadAVCHDLegacyClipFile ( strPath, avchdLegacyData );
+
+ if ( success && avchdLegacyData.mClipExtensionData.mPresent ) {
+ success = FindAVCHDLegacyPlaylistFile ( strRootPath, strClipName, avchdLegacyData );
+ }
+
+ return success;
+
+} // ReadAVCHDLegacyMetadata
+
+// =================================================================================================
+// AVCCAM_SetXMPStartTimecode
+// ==========================
+
+static void AVCCAM_SetXMPStartTimecode ( SXMPMeta& xmpObj, const XMP_Uns8* avccamTimecode, XMP_Uns8 avchdFrameRate )
+{
+ // Timecode in SMPTE 12M format, according to Panasonic's documentation
+ if ( *reinterpret_cast<const XMP_Uns32*>( avccamTimecode ) == 0xFFFFFFFF ) {
+ // 0xFFFFFFFF means timecode not specified
+ return;
+ }
+
+ const XMP_Uns8 isColor = ( avccamTimecode[0] >> 7 ) & 0x01;
+ const XMP_Uns8 isDropFrame = ( avccamTimecode[0] >> 6 ) & 0x01;
+ const XMP_Uns8 frameTens = ( avccamTimecode[0] >> 4 ) & 0x03;
+ const XMP_Uns8 frameUnits = avccamTimecode[0] & 0x0f;
+ const XMP_Uns8 secondTens = ( avccamTimecode[1] >> 4 ) & 0x07;
+ const XMP_Uns8 secondUnits = avccamTimecode[1] & 0x0f;
+ const XMP_Uns8 minuteTens = ( avccamTimecode[2] >> 4 ) & 0x07;
+ const XMP_Uns8 minuteUnits = avccamTimecode[2] & 0x0f;
+ const XMP_Uns8 hourTens = ( avccamTimecode[3] >> 4 ) & 0x03;
+ const XMP_Uns8 hourUnits = avccamTimecode[3] & 0x0f;
+ char tcSeparator = ':';
+ const char* dmTimeFormat = NULL;
+ const char* dmTimeScale = NULL;
+ const char* dmTimeSampleSize = NULL;
+
+ switch ( avchdFrameRate ) {
+ case 1 :
+ // 23.976i
+ dmTimeFormat = "23976Timecode";
+ dmTimeScale = "24000";
+ dmTimeSampleSize = "1001";
+ break;
+
+ case 2 :
+ // 24p
+ dmTimeFormat = "24Timecode";
+ dmTimeScale = "24";
+ dmTimeSampleSize = "1";
+ break;
+
+ case 3 :
+ case 6 :
+ // 50i or 25p
+ dmTimeFormat = "25Timecode";
+ dmTimeScale = "25";
+ dmTimeSampleSize = "1";
+ break;
+
+ case 4 :
+ case 7 :
+ // 29.97p or 59.94i
+ if ( isDropFrame ) {
+ dmTimeFormat = "2997DropTimecode";
+ tcSeparator = ';';
+ } else {
+ dmTimeFormat = "2997NonDropTimecode";
+ }
+
+ dmTimeScale = "30000";
+ dmTimeSampleSize = "1001";
+
+ break;
+ }
+
+ if ( dmTimeFormat != NULL ) {
+ char timecodeBuff [12];
+
+ sprintf ( timecodeBuff, "%d%d%c%d%d%c%d%d%c%d%d", hourTens, hourUnits, tcSeparator,
+ minuteTens, minuteUnits, tcSeparator, secondTens, secondUnits, tcSeparator, frameTens, frameUnits);
+
+ xmpObj.SetProperty( kXMP_NS_DM, "startTimeScale", dmTimeScale, kXMP_DeleteExisting );
+ xmpObj.SetProperty( kXMP_NS_DM, "startTimeSampleSize", dmTimeSampleSize, kXMP_DeleteExisting );
+ xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", timecodeBuff, 0 );
+ xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", dmTimeFormat, 0 );
+ }
+}
+
+// =================================================================================================
+// AVCHD_SetXMPMakeAndModel
+// ========================
+
+static bool AVCHD_SetXMPMakeAndModel ( SXMPMeta& xmpObj, const AVCHD_blkClipExtensionData& clipExtData )
+{
+ if ( ! clipExtData.mPresent ) return false;
+
+ XMP_StringPtr xmpValue = 0;
+
+ // Set the Make. Use a hex string for unknown makes.
+ {
+ char hexMakeNumber [7];
+
+ switch ( clipExtData.mClipInfoExt.mMakerID ) {
+ case kMakerIDCanon : xmpValue = "Canon"; break;
+ case kMakerIDPanasonic : xmpValue = "Panasonic"; break;
+ case kMakerIDSony : xmpValue = "Sony"; break;
+ default :
+ std::sprintf ( hexMakeNumber, "0x%04x", clipExtData.mClipInfoExt.mMakerID );
+ xmpValue = hexMakeNumber;
+
+ break;
+ }
+
+ xmpObj.SetProperty ( kXMP_NS_TIFF, "Make", xmpValue, kXMP_DeleteExisting );
+ }
+
+ // Set the Model number. Use a hex string for unknown model numbers so they can still be distinguished.
+ {
+ char hexModelNumber [7];
+
+ xmpValue = 0;
+
+ switch ( clipExtData.mClipInfoExt.mMakerID ) {
+ case kMakerIDCanon :
+ switch ( clipExtData.mClipInfoExt.mMakerModelCode ) {
+ case 0x1000 : xmpValue = "HR10"; break;
+ case 0x2000 : xmpValue = "HG10"; break;
+ case 0x2001 : xmpValue = "HG21"; break;
+ case 0x3000 : xmpValue = "HF100"; break;
+ case 0x3003 : xmpValue = "HF S10"; break;
+ default : break;
+ }
+ break;
+
+ case kMakerIDPanasonic :
+ switch ( clipExtData.mClipInfoExt.mMakerModelCode ) {
+ case 0x0202 : xmpValue = "HD-writer"; break;
+ case 0x0400 : xmpValue = "AG-HSC1U"; break;
+ case 0x0401 : xmpValue = "AG-HMC70"; break;
+ case 0x0410 : xmpValue = "AG-HMC150"; break;
+ case 0x0411 : xmpValue = "AG-HMC40"; break;
+ case 0x0412 : xmpValue = "AG-HMC80"; break;
+ case 0x0413 : xmpValue = "AG-3DA1"; break;
+ case 0x0414 : xmpValue = "AG-AF100"; break;
+ case 0x0450 : xmpValue = "AG-HMR10"; break;
+ case 0x0451 : xmpValue = "AJ-YCX250"; break;
+ case 0x0452 : xmpValue = "AG-MDR15"; break;
+ case 0x0490 : xmpValue = "AVCCAM Restorer"; break;
+ case 0x0491 : xmpValue = "AVCCAM Viewer"; break;
+ case 0x0492 : xmpValue = "AVCCAM Viewer for Mac"; break;
+ default : break;
+ }
+
+ break;
+
+ default : break;
+ }
+
+ if ( ( xmpValue == 0 ) && ( clipExtData.mClipInfoExt.mMakerID != kMakerIDSony ) ) {
+ // Panasonic has said that if we don't have a string for the model number, they'd like to see the code
+ // anyway. We'll do the same for every manufacturer except Sony, who have said that they use
+ // the same model number for multiple cameras.
+ std::sprintf ( hexModelNumber, "0x%04x", clipExtData.mClipInfoExt.mMakerModelCode );
+ xmpValue = hexModelNumber;
+ }
+
+ if ( xmpValue != 0 ) xmpObj.SetProperty ( kXMP_NS_TIFF, "Model", xmpValue, kXMP_DeleteExisting );
+ }
+
+ return true;
+}
+
+// =================================================================================================
+// AVCHD_StringFieldToXMP
+// ======================
+
+static std::string AVCHD_StringFieldToXMP ( XMP_Uns8 avchdLength,
+ XMP_Uns8 avchdCharacterSet,
+ const XMP_Uns8* avchdField,
+ XMP_Uns8 avchdFieldSize )
+{
+ std::string xmpString;
+
+ if ( avchdCharacterSet == 0x02 ) {
+ // UTF-16, Big Endian
+ UTF8Unit utf8Name [512];
+ const XMP_Uns8 avchdMaxChars = ( avchdFieldSize / 2);
+ size_t utf16Read;
+ size_t utf8Written;
+
+ // The spec doesn't say whether AVCHD length fields count bytes or characters, so we'll
+ // clamp to the max number of UTF-16 characters just in case.
+ const int stringLength = ( avchdLength > avchdMaxChars ) ? avchdMaxChars : avchdLength;
+
+ UTF16BE_to_UTF8 ( reinterpret_cast<const UTF16Unit*> ( avchdField ), stringLength,
+ utf8Name, 512, &utf16Read, &utf8Written );
+ xmpString.assign ( reinterpret_cast<const char*> ( utf8Name ), utf8Written );
+ } else {
+ // AVCHD supports many character encodings, but UTF-8 (0x01) and ASCII (0x90) are the only ones I've
+ // seen in the wild at this point. We'll treat the other character sets as UTF-8 on the assumption that
+ // at least a few characters will come across, and something is better than nothing.
+ const int stringLength = ( avchdLength > avchdFieldSize ) ? avchdFieldSize : avchdLength;
+
+ xmpString.assign ( reinterpret_cast<const char*> ( avchdField ), stringLength );
+ }
+
+ return xmpString;
+}
+
+// =================================================================================================
+// AVCHD_SetXMPShotName
+// ====================
+
+static void AVCHD_SetXMPShotName ( SXMPMeta& xmpObj, const AVCHD_blkPlayListMarkExt& markExt, const std::string& strClipName )
+{
+ if ( markExt.mPresent ) {
+ const std::string shotName = AVCHD_StringFieldToXMP ( markExt.mMarkNameLength, markExt.mMarkCharacterSet, markExt.mMarkName, 24 );
+
+ if ( ! shotName.empty() ) xmpObj.SetProperty ( kXMP_NS_DC, "shotName", shotName.c_str(), kXMP_DeleteExisting );
+ }
+}
+
+// =================================================================================================
+// BytesToHex
+// ==========
+
+#define kHexDigits "0123456789ABCDEF"
+
+static std::string BytesToHex ( const XMP_Uns8* inClipIDBytes, int inNumBytes )
+{
+ const int numChars = ( inNumBytes * 2 );
+ std::string hexStr;
+
+ hexStr.reserve(numChars);
+
+ for ( int i = 0; i < inNumBytes; ++i ) {
+ const XMP_Uns8 byte = inClipIDBytes[i];
+ hexStr.push_back ( kHexDigits [byte >> 4] );
+ hexStr.push_back ( kHexDigits [byte & 0xF] );
+ }
+
+ return hexStr;
+}
+
+// =================================================================================================
+// AVCHD_DateFieldToXMP
+// ====================
+//
+// AVCHD Format Book 2, section 4.2.2.2.
+
+static std::string AVCHD_DateFieldToXMP ( XMP_Uns8 avchdTimeZone, const XMP_Uns8* avchdDateTime )
+{
+ const XMP_Uns8 daylightSavingsTime = ( avchdTimeZone >> 6 ) & 0x01;
+ const XMP_Uns8 timezoneSign = ( avchdTimeZone >> 5 ) & 0x01;
+ const XMP_Uns8 timezoneValue = ( avchdTimeZone >> 1 ) & 0x0F;
+ const XMP_Uns8 halfHourFlag = avchdTimeZone & 0x01;
+ int utcOffsetHours = 0;
+ unsigned int utcOffsetMinutes = 0;
+
+ // It's not entirely clear how to interpret the daylightSavingsTime flag from the documentation -- my best
+ // guess is that it should only be used if trying to display local time, not the UTC-relative time that
+ // XMP specifies.
+ if ( timezoneValue != 0xF ) {
+ utcOffsetHours = timezoneSign ? -timezoneValue : timezoneValue;
+ utcOffsetMinutes = 30 * halfHourFlag;
+ }
+
+ char dateBuff [26];
+
+ sprintf ( dateBuff,
+ "%01d%01d%01d%01d-%01d%01d-%01d%01dT%01d%01d:%01d%01d:%01d%01d%+02d:%02d",
+ (avchdDateTime[0] >> 4), (avchdDateTime[0] & 0x0F),
+ (avchdDateTime[1] >> 4), (avchdDateTime[1] & 0x0F),
+ (avchdDateTime[2] >> 4), (avchdDateTime[2] & 0x0F),
+ (avchdDateTime[3] >> 4), (avchdDateTime[3] & 0x0F),
+ (avchdDateTime[4] >> 4), (avchdDateTime[4] & 0x0F),
+ (avchdDateTime[5] >> 4), (avchdDateTime[5] & 0x0F),
+ (avchdDateTime[6] >> 4), (avchdDateTime[6] & 0x0F),
+ utcOffsetHours, utcOffsetMinutes );
+
+ return std::string(dateBuff);
+}
+
+// =================================================================================================
+// AVCHD_MetaHandlerCTor
+// =====================
+
+XMPFileHandler * AVCHD_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new AVCHD_MetaHandler ( parent );
+
+} // AVCHD_MetaHandlerCTor
+
+// =================================================================================================
+// AVCHD_MetaHandler::AVCHD_MetaHandler
+// ====================================
+
+AVCHD_MetaHandler::AVCHD_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kAVCHD_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ // Extract the root path and clip name.
+
+ if ( this->parent->tempPtr == 0 ) {
+ // The CheckFormat call might have been skipped.
+ this->parent->tempPtr = CreatePseudoClipPath ( this->parent->filePath );
+ }
+
+ this->rootPath.assign ( (char*) this->parent->tempPtr );
+ free ( this->parent->tempPtr );
+ this->parent->tempPtr = 0;
+
+ XIO::SplitLeafName ( &this->rootPath, &this->clipName );
+
+} // AVCHD_MetaHandler::AVCHD_MetaHandler
+
+// =================================================================================================
+// AVCHD_MetaHandler::~AVCHD_MetaHandler
+// =====================================
+
+AVCHD_MetaHandler::~AVCHD_MetaHandler()
+{
+
+ if ( this->parent->tempPtr != 0 ) {
+ free ( this->parent->tempPtr );
+ this->parent->tempPtr = 0;
+ }
+
+} // AVCHD_MetaHandler::~AVCHD_MetaHandler
+
+// =================================================================================================
+// AVCHD_MetaHandler::MakeClipInfoPath
+// ===================================
+
+bool AVCHD_MetaHandler::MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) const
+{
+ return MakeLeafPath ( path, this->rootPath.c_str(), "CLIPINF", this->clipName.c_str(), suffix, checkFile );
+} // AVCHD_MetaHandler::MakeClipInfoPath
+
+// =================================================================================================
+// AVCHD_MetaHandler::MakeClipStreamPath
+// =====================================
+
+bool AVCHD_MetaHandler::MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) const
+{
+ return MakeLeafPath ( path, this->rootPath.c_str(), "STREAM", this->clipName.c_str(), suffix, checkFile );
+} // AVCHD_MetaHandler::MakeClipStreamPath
+
+// =================================================================================================
+// AVCHD_MetaHandler::MakePlaylistPath
+// =====================================
+
+bool AVCHD_MetaHandler::MakePlaylistPath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) const
+{
+ return MakeLeafPath ( path, this->rootPath.c_str(), "PLAYLIST", this->clipName.c_str(), suffix, checkFile );
+} // AVCHD_MetaHandler::MakePlaylistPath
+
+// =================================================================================================
+// AVCHD_MetaHandler::MakeLegacyDigest
+// ===================================
+
+void AVCHD_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ std::string strClipPath;
+ std::string strPlaylistPath;
+ std::vector<XMP_Uns8> legacyBuff;
+
+ bool ok = this->MakeClipInfoPath ( &strClipPath, ".clpi", true /* checkFile */ );
+ if ( ! ok ) return;
+
+ ok = this->MakePlaylistPath ( &strPlaylistPath, ".mpls", true /* checkFile */ );
+ if ( ! ok ) return;
+
+ try {
+ {
+ Host_IO::FileRef hostRef = Host_IO::Open ( strClipPath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return; // The open failed.
+ XMPFiles_IO cpiFile ( hostRef, strClipPath.c_str(), Host_IO::openReadOnly );
+
+ // Read at most the first 2k of data from the cpi file to use in the digest
+ // (every CPI file I've seen is less than 1k).
+ const XMP_Int64 cpiLen = cpiFile.Length();
+ const XMP_Int64 buffLen = (cpiLen <= 2048) ? cpiLen : 2048;
+
+ legacyBuff.resize ( (unsigned int) buffLen );
+ cpiFile.ReadAll ( &(legacyBuff[0]), static_cast<XMP_Int32> ( buffLen ) );
+ }
+
+ {
+ Host_IO::FileRef hostRef = Host_IO::Open ( strPlaylistPath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return; // The open failed.
+ XMPFiles_IO mplFile ( hostRef, strPlaylistPath.c_str(), Host_IO::openReadOnly );
+
+ // Read at most the first 2k of data from the cpi file to use in the digest
+ // (every playlist file I've seen is less than 1k).
+ const XMP_Int64 mplLen = mplFile.Length();
+ const XMP_Int64 buffLen = (mplLen <= 2048) ? mplLen : 2048;
+ const XMP_Int64 clipBuffLen = legacyBuff.size();
+
+ legacyBuff.resize ( (unsigned int) (clipBuffLen + buffLen) );
+ mplFile.ReadAll ( &( legacyBuff [(unsigned int)clipBuffLen] ), (XMP_Int32)buffLen );
+ }
+
+ } catch (...) {
+ return;
+ }
+
+ MD5_CTX context;
+ unsigned char digestBin [16];
+
+ MD5Init ( &context );
+ MD5Update ( &context, (XMP_Uns8*)&(legacyBuff[0]), (unsigned int) legacyBuff.size() );
+ MD5Final ( digestBin, &context );
+
+ *digestStr = BytesToHex ( digestBin, 16 );
+} // AVCHD_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+// AVCHD_MetaHandler::GetFileModDate
+// =================================
+
+static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) {
+ int compare = SXMPUtils::CompareDateTime ( left, right );
+ return (compare < 0);
+}
+
+bool AVCHD_MetaHandler::GetFileModDate ( XMP_DateTime * modDate )
+{
+
+ // The AVCHD locations of metadata:
+ // BDMV/
+ // CLIPINF/
+ // 00001.clpi
+ // PLAYLIST/
+ // 00001.mpls
+ // STREAM/
+ // 00001.xmp
+
+ bool ok, haveDate = false;
+ std::string fullPath;
+ XMP_DateTime oneDate, junkDate;
+ if ( modDate == 0 ) modDate = &junkDate;
+
+ ok = this->MakeClipInfoPath ( &fullPath, ".clpi", true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ ok = this->MakePlaylistPath ( &fullPath, ".mpls", true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ ok = this->MakeClipStreamPath ( &fullPath, ".xmp", true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ return haveDate;
+
+} // AVCHD_MetaHandler::GetFileModDate
+
+// =================================================================================================
+// AVCHD_MetaHandler::CacheFileData
+// ================================
+
+void AVCHD_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( ! this->containsXMP );
+
+ if ( this->parent->UsesClientIO() ) {
+ XMP_Throw ( "XDCAM cannot be used with client-managed I/O", kXMPErr_InternalFailure );
+ }
+
+ // See if the clip's .XMP file exists.
+
+ std::string xmpPath;
+ bool found = this->MakeClipStreamPath ( &xmpPath, ".xmp", true /* checkFile */ );
+ if ( ! found ) return;
+
+ // Read the entire .XMP file.
+
+ bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate );
+
+ XMP_Assert ( this->parent->ioRef == 0 );
+ XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), readOnly );
+ if ( xmpFile == 0 ) return; // The open failed.
+ this->parent->ioRef = xmpFile;
+
+ XMP_Int64 xmpLen = xmpFile->Length();
+ if ( xmpLen > 100*1024*1024 ) {
+ XMP_Throw ( "AVCHD XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check.
+ }
+
+ this->xmpPacket.erase();
+ this->xmpPacket.append ( (size_t ) xmpLen, ' ' );
+
+ XMP_Int32 ioCount = xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen );
+
+ this->packetInfo.offset = 0;
+ this->packetInfo.length = (XMP_Int32)xmpLen;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ this->containsXMP = true;
+
+} // AVCHD_MetaHandler::CacheFileData
+
+// =================================================================================================
+// AVCHD_MetaHandler::ProcessXMP
+// =============================
+
+void AVCHD_MetaHandler::ProcessXMP()
+{
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( this->containsXMP ) {
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ }
+
+ // read clip info
+ AVCHD_LegacyMetadata avchdLegacyData;
+ std::string strPath;
+
+ bool ok = this->MakeClipInfoPath ( &strPath, ".clpi", true /* checkFile */ );
+ if ( ok ) ReadAVCHDLegacyMetadata ( strPath, this->rootPath, this->clipName, avchdLegacyData );
+ if ( ! ok ) return;
+
+ const AVCHD_blkPlayListMarkExt& markExt = avchdLegacyData.mPlaylistExtensionData.mPlaylistMarkExt;
+ XMP_Uns8 pulldownFlag = 0;
+
+ if ( markExt.mPresent ) {
+ const std::string dateString = AVCHD_DateFieldToXMP ( markExt.mBlkTimezone, markExt.mRecordDataAndTime );
+
+ if ( ! dateString.empty() ) this->xmpObj.SetProperty ( kXMP_NS_DM, "shotDate", dateString.c_str(), kXMP_DeleteExisting );
+ AVCHD_SetXMPShotName ( this->xmpObj, markExt, this->clipName );
+ AVCCAM_SetXMPStartTimecode ( this->xmpObj, markExt.mBlkTimecode, avchdLegacyData.mProgramInfo.mVideoStream.mFrameRate );
+ pulldownFlag = (markExt.mFlags >> 1) & 0x03; // bits 1 and 2
+ }
+
+ // Video Stream. AVCHD Format v. 1.01 p. 78
+
+ const bool has2_2pulldown = (pulldownFlag == 0x01);
+ const bool has3_2pulldown = (pulldownFlag == 0x10);
+ XMP_StringPtr xmpValue = 0;
+
+ if ( avchdLegacyData.mProgramInfo.mVideoStream.mPresent ) {
+
+ // XMP videoFrameSize.
+ xmpValue = 0;
+ int frameIndex = -1;
+ bool isProgressiveHD = false;
+ const char* frameWidth[4] = { "720", "720", "1280", "1920" };
+ const char* frameHeight[4] = { "480", "576", "720", "1080" };
+
+ switch ( avchdLegacyData.mProgramInfo.mVideoStream.mVideoFormat ) {
+ case 1 : frameIndex = 0; break; // 480i
+ case 2 : frameIndex = 1; break; // 576i
+ case 3 : frameIndex = 0; break; // 480p
+ case 4 : frameIndex = 3; break; // 1080i
+ case 5 : frameIndex = 2; isProgressiveHD = true; break; // 720p
+ case 6 : frameIndex = 3; isProgressiveHD = true; break; // 1080p
+ default: break;
+ }
+
+ if ( frameIndex != -1 ) {
+ xmpValue = frameWidth[frameIndex];
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", xmpValue, 0 );
+ xmpValue = frameHeight[frameIndex];
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", xmpValue, 0 );
+ xmpValue = "pixels";
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", xmpValue, 0 );
+ }
+
+ // XMP videoFrameRate. The logic below seems pretty tortured, but matches "Table 4-4 pulldown" on page 31 of Book 2 of the AVCHD
+ // spec, if you interepret "frame_mbs_only_flag" as "isProgressiveHD", "frame-rate [Hz]" as the frame rate encoded in
+ // mVideoStream.mFrameRate, and "Video Scan Type" as the desired xmp output value. The algorithm produces correct results for
+ // all the AVCHD media I've tested.
+ xmpValue = 0;
+ if ( isProgressiveHD ) {
+
+ switch ( avchdLegacyData.mProgramInfo.mVideoStream.mFrameRate ) {
+ case 1 : xmpValue = "23.98p"; break; // "23.976"
+ case 2 : xmpValue = "24p"; break; // "24"
+ case 3 : xmpValue = "25p"; break; // "25"
+ case 4 : xmpValue = has2_2pulldown ? "29.97p" : "59.94p"; break; // "29.97"
+ case 6 : xmpValue = has2_2pulldown ? "25p" : "50p"; break; // "50"
+ case 7 : // "59.94"
+ if ( has2_2pulldown )
+ xmpValue = "29.97p";
+ else
+ xmpValue = has3_2pulldown ? "23.98p" : "59.94p";
+
+ break;
+ default: break;
+ }
+
+ } else {
+
+ switch ( avchdLegacyData.mProgramInfo.mVideoStream.mFrameRate ) {
+ case 3 : xmpValue = has2_2pulldown ? "25p" : "50i"; break; // "25" (but 1080p25 is reported as 1080i25 with 2:2 pulldown...)
+ case 4 : // "29.97"
+ if ( has2_2pulldown )
+ xmpValue = "29.97p";
+ else
+ xmpValue = has3_2pulldown ? "23.98p" : "59.94i";
+
+ break;
+ default: break;
+ }
+
+ }
+
+ if ( xmpValue != 0 ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "videoFrameRate", xmpValue, kXMP_DeleteExisting );
+ }
+
+ this->containsXMP = true;
+
+ }
+
+ // Audio Stream.
+ if ( avchdLegacyData.mProgramInfo.mAudioStream.mPresent ) {
+
+ xmpValue = 0;
+ switch ( avchdLegacyData.mProgramInfo.mAudioStream.mAudioPresentationType ) {
+ case 1 : xmpValue = "Mono"; break;
+ case 3 : xmpValue = "Stereo"; break;
+ default : break;
+ }
+ if ( xmpValue != 0 ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "audioChannelType", xmpValue, kXMP_DeleteExisting );
+ }
+
+ xmpValue = 0;
+ switch ( avchdLegacyData.mProgramInfo.mAudioStream.mSamplingFrequency ) {
+ case 1 : xmpValue = "48000"; break;
+ case 4 : xmpValue = "96000"; break;
+ case 5 : xmpValue = "192000"; break;
+ default : break;
+ }
+ if ( xmpValue != 0 ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "audioSampleRate", xmpValue, kXMP_DeleteExisting );
+ }
+
+ this->containsXMP = true;
+ }
+
+ // Proprietary vendor extensions
+ if ( AVCHD_SetXMPMakeAndModel ( this->xmpObj, avchdLegacyData.mClipExtensionData ) ) this->containsXMP = true;
+
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "title", this->clipName.c_str(), kXMP_DeleteExisting );
+ this->containsXMP = true;
+
+ if ( avchdLegacyData.mClipExtensionData.mMakersPrivateData.mPresent &&
+ ( avchdLegacyData.mClipExtensionData.mClipInfoExt.mMakerID == kMakerIDPanasonic ) ) {
+
+ const AVCHD_blkPanasonicPrivateData& panasonicClipData = avchdLegacyData.mClipExtensionData.mMakersPrivateData.mPanasonicPrivateData;
+
+ if ( panasonicClipData.mProClipIDBlock.mPresent ) {
+ const std::string globalClipIDString = BytesToHex ( panasonicClipData.mProClipIDBlock.mGlobalClipID, 32 );
+
+ this->xmpObj.SetProperty ( kXMP_NS_DC, "identifier", globalClipIDString.c_str(), kXMP_DeleteExisting );
+ }
+
+ const AVCHD_blkPanasonicPrivateData& panasonicPlaylistData =
+ avchdLegacyData.mPlaylistExtensionData.mMakersPrivateData.mPanasonicPrivateData;
+
+ if ( panasonicPlaylistData.mProPlaylistInfoBlock.mPlayListMark.mPresent ) {
+ const AVCCAM_blkProPlayListMark& playlistMark = panasonicPlaylistData.mProPlaylistInfoBlock.mPlayListMark;
+
+ if ( playlistMark.mShotMark.mPresent ) {
+ // Unlike P2, where "shotmark" is a boolean, Panasonic treats their AVCCAM shotmark as a bit field with
+ // 8 user-definable bits. For now we're going to treat any bit being set as xmpDM::good == true, and all
+ // bits being clear as xmpDM::good == false.
+ const bool isGood = ( playlistMark.mShotMark.mShotMark != 0 );
+
+ xmpObj.SetProperty_Bool ( kXMP_NS_DM, "good", isGood, kXMP_DeleteExisting );
+ }
+
+ if ( playlistMark.mAccess.mPresent && ( playlistMark.mAccess.mCreatorLength > 0 ) ) {
+ const std::string creatorString = AVCHD_StringFieldToXMP (
+ playlistMark.mAccess.mCreatorLength, playlistMark.mAccess.mCreatorCharacterSet, playlistMark.mAccess.mCreator, 32 ) ;
+
+ if ( ! creatorString.empty() ) {
+ xmpObj.DeleteProperty ( kXMP_NS_DC, "creator" );
+ xmpObj.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, creatorString.c_str() );
+ }
+ }
+
+ if ( playlistMark.mDevice.mPresent && ( playlistMark.mDevice.mSerialNoLength > 0 ) ) {
+ const std::string serialNoString = AVCHD_StringFieldToXMP (
+ playlistMark.mDevice.mSerialNoLength, playlistMark.mDevice.mSerialNoCharacterCode, playlistMark.mDevice.mSerialNo, 24 ) ;
+
+ if ( ! serialNoString.empty() ) xmpObj.SetProperty ( kXMP_NS_EXIF_Aux, "SerialNumber", serialNoString.c_str(), kXMP_DeleteExisting );
+ }
+
+ if ( playlistMark.mLocation.mPresent && ( playlistMark.mLocation.mPlaceNameLength > 0 ) ) {
+ const std::string placeNameString = AVCHD_StringFieldToXMP (
+ playlistMark.mLocation.mPlaceNameLength, playlistMark.mLocation.mPlaceNameCharacterSet, playlistMark.mLocation.mPlaceName, 64 ) ;
+
+ if ( ! placeNameString.empty() ) xmpObj.SetProperty ( kXMP_NS_DM, "shotLocation", placeNameString.c_str(), kXMP_DeleteExisting );
+ }
+ }
+ }
+
+} // AVCHD_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// AVCHD_MetaHandler::UpdateFile
+// =============================
+//
+// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here.
+
+void AVCHD_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Make sure only called once.
+
+ XMP_Assert ( this->parent->UsesLocalIO() );
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "AVCHD", newDigest.c_str(), kXMP_DeleteExisting );
+
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );
+
+ std::string xmpPath;
+ this->MakeClipStreamPath ( &xmpPath, ".xmp" );
+
+ bool haveXMP = Host_IO::Exists ( xmpPath.c_str() );
+ if ( ! haveXMP ) {
+ XMP_Assert ( this->parent->ioRef == 0 );
+ Host_IO::Create ( xmpPath.c_str() );
+ this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), Host_IO::openReadWrite );
+ if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening AVCHD XMP file", kXMPErr_ExternalFailure );
+ }
+
+ XMP_IO* xmpFile = this->parent->ioRef;
+ XMP_Assert ( xmpFile != 0 );
+ XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) );
+
+} // AVCHD_MetaHandler::UpdateFile
+
+// =================================================================================================
+// AVCHD_MetaHandler::WriteTempFile
+// ================================
+
+void AVCHD_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+
+ // ! WriteTempFile is not supposed to be called for handlers that own the file.
+ XMP_Throw ( "AVCHD_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure );
+
+} // AVCHD_MetaHandler::WriteTempFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp b/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp
new file mode 100644
index 0000000..46c417e
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp
@@ -0,0 +1,81 @@
+#ifndef __AVCHD_Handler_hpp__
+#define __AVCHD_Handler_hpp__ 1
+
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+#include "source/ExpatAdapter.hpp"
+
+// =================================================================================================
+/// \file AVCHD_Handler.hpp
+/// \brief Folder format handler for AVCHD.
+///
+/// This header ...
+///
+// =================================================================================================
+
+extern XMPFileHandler * AVCHD_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool AVCHD_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kAVCHD_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_FolderBasedFormat);
+
+class AVCHD_MetaHandler : public XMPFileHandler
+{
+public:
+
+ bool GetFileModDate ( XMP_DateTime * modDate );
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files.
+ { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); };
+
+ AVCHD_MetaHandler ( XMPFiles * _parent );
+ virtual ~AVCHD_MetaHandler();
+
+private:
+
+ AVCHD_MetaHandler() {}; // Hidden on purpose.
+
+ bool MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ) const;
+ bool MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ) const;
+ bool MakePlaylistPath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ) const;
+
+ void MakeLegacyDigest ( std::string * digestStr );
+
+ std::string rootPath, clipName;
+
+}; // AVCHD_MetaHandler
+
+// =================================================================================================
+
+#endif /* __AVCHD_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/Basic_Handler.cpp b/XMPFiles/source/FileHandlers/Basic_Handler.cpp
new file mode 100644
index 0000000..795dbd7
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/Basic_Handler.cpp
@@ -0,0 +1,243 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/Basic_Handler.hpp"
+
+using namespace std;
+
+// =================================================================================================
+/// \file Basic_Handler.cpp
+/// \brief Base class for basic handlers that only process in-place XMP.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// =================================================================================================
+// Basic_MetaHandler::~Basic_MetaHandler
+// =====================================
+
+Basic_MetaHandler::~Basic_MetaHandler()
+{
+ // ! Inherit the base cleanup.
+
+} // Basic_MetaHandler::~Basic_MetaHandler
+
+// =================================================================================================
+// Basic_MetaHandler::UpdateFile
+// =============================
+
+// ! This must be called from the destructor for all derived classes. It can't be called from the
+// ! Basic_MetaHandler destructor, by then calls to the virtual functions would not go to the
+// ! actual implementations for the derived class.
+
+void Basic_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ IgnoreParam ( doSafeUpdate );
+ XMP_Assert ( ! doSafeUpdate ); // Not supported at this level.
+ if ( ! this->needsUpdate ) return;
+
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_PacketInfo & packetInfo = this->packetInfo;
+ std::string & xmpPacket = this->xmpPacket;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ this->CaptureFileEnding ( fileRef ); // ! Do this first, before any location info changes.
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "Basic_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort );
+ }
+
+ this->NoteXMPRemoval ( fileRef );
+ this->ShuffleTrailingContent ( fileRef );
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "Basic_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort );
+ }
+
+ XMP_Int64 tempLength = this->xmpFileOffset - this->xmpPrefixSize + this->trailingContentSize;
+ fileRef->Truncate ( tempLength );
+
+ packetInfo.offset = tempLength + this->xmpPrefixSize;
+ this->NoteXMPInsertion ( fileRef );
+
+ fileRef->ToEOF();
+ this->WriteXMPPrefix ( fileRef );
+ fileRef->Write ( xmpPacket.c_str(), (XMP_StringLen)xmpPacket.size() );
+ this->WriteXMPSuffix ( fileRef );
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "Basic_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort );
+ }
+
+ this->RestoreFileEnding ( fileRef );
+
+ this->xmpFileOffset = packetInfo.offset;
+ this->xmpFileSize = packetInfo.length;
+ this->needsUpdate = false;
+
+} // Basic_MetaHandler::UpdateFile
+
+// =================================================================================================
+// Basic_MetaHandler::WriteTempFile
+// ================================
+
+// *** What about computing the new file length and pre-allocating the file?
+
+void Basic_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_IO* originalRef = this->parent->ioRef;
+
+ // Capture the "back" of the original file.
+
+ this->CaptureFileEnding ( originalRef );
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "Basic_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort );
+ }
+
+ // Seek to the beginning of the original and temp files, truncate the temp.
+
+ originalRef->Rewind();
+ tempRef->Rewind();
+ tempRef->Truncate ( 0 );
+
+ // Copy the front of the original file to the temp file. Note the XMP (pseudo) removal and
+ // insertion. This mainly updates info about the new XMP length.
+
+ XMP_Int64 xmpSectionOffset = this->xmpFileOffset - this->xmpPrefixSize;
+ XMP_Int32 oldSectionLength = this->xmpPrefixSize + this->xmpFileSize + this->xmpSuffixSize;
+
+ XIO::Copy ( originalRef, tempRef, xmpSectionOffset, abortProc, abortArg );
+ this->NoteXMPRemoval ( originalRef );
+ packetInfo.offset = this->xmpFileOffset; // ! The packet offset does not change.
+ this->NoteXMPInsertion ( tempRef );
+ tempRef->ToEOF();
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "Basic_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort );
+ }
+
+ // Write the new XMP section to the temp file.
+
+ this->WriteXMPPrefix ( tempRef );
+ tempRef->Write ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ this->WriteXMPSuffix ( tempRef );
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "Basic_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort );
+ }
+
+ // Copy the trailing file content from the original and write the "back" of the file.
+
+ XMP_Int64 remainderOffset = xmpSectionOffset + oldSectionLength;
+
+ originalRef->Seek ( remainderOffset, kXMP_SeekFromStart );
+ XIO::Copy ( originalRef, tempRef, this->trailingContentSize, abortProc, abortArg );
+ this->RestoreFileEnding ( tempRef );
+
+ // Done.
+
+ this->xmpFileOffset = packetInfo.offset;
+ this->xmpFileSize = packetInfo.length;
+ this->needsUpdate = false;
+
+} // Basic_MetaHandler::WriteTempFile
+
+// =================================================================================================
+// ShuffleTrailingContent
+// ======================
+//
+// Shuffle the trailing content portion of a file forward. This does not include the final "back"
+// portion of the file, just the arbitrary length content between the XMP section and the back.
+// Don't use XIO::Copy, that assumes separate files and hence separate I/O positions.
+
+// ! The XMP packet location and prefix/suffix sizes must still reflect the XMP section that is in
+// ! the process of being removed.
+
+void Basic_MetaHandler::ShuffleTrailingContent ( XMP_IO* fileRef )
+{
+ XMP_Int64 readOffset = this->packetInfo.offset + xmpSuffixSize;
+ XMP_Int64 writeOffset = this->packetInfo.offset - xmpPrefixSize;
+
+ XMP_Int64 remainingLength = this->trailingContentSize;
+
+ enum { kBufferSize = 64*1024 };
+ char buffer [kBufferSize];
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ while ( remainingLength > 0 ) {
+
+ XMP_Int32 ioCount = kBufferSize;
+ if ( remainingLength < kBufferSize ) ioCount = (XMP_Int32)remainingLength;
+
+ fileRef->Seek ( readOffset, kXMP_SeekFromStart );
+ fileRef->ReadAll ( buffer, ioCount );
+ fileRef->Seek ( writeOffset, kXMP_SeekFromStart );
+ fileRef->Write ( buffer, ioCount );
+
+ readOffset += ioCount;
+ writeOffset += ioCount;
+ remainingLength -= ioCount;
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "Basic_MetaHandler::ShuffleTrailingContent - User abort", kXMPErr_UserAbort );
+ }
+
+ }
+
+} // ShuffleTrailingContent
+
+// =================================================================================================
+// Dummies needed for VS.Net
+// =========================
+
+void Basic_MetaHandler::WriteXMPPrefix ( XMP_IO* fileRef )
+{
+ XMP_Throw ( "Basic_MetaHandler::WriteXMPPrefix - Needs specific override", kXMPErr_InternalFailure );
+}
+
+void Basic_MetaHandler::WriteXMPSuffix ( XMP_IO* fileRef )
+{
+ XMP_Throw ( "Basic_MetaHandler::WriteXMPSuffix - Needs specific override", kXMPErr_InternalFailure );
+}
+
+void Basic_MetaHandler::NoteXMPRemoval ( XMP_IO* fileRef )
+{
+ XMP_Throw ( "Basic_MetaHandler::NoteXMPRemoval - Needs specific override", kXMPErr_InternalFailure );
+}
+
+void Basic_MetaHandler::NoteXMPInsertion ( XMP_IO* fileRef )
+{
+ XMP_Throw ( "Basic_MetaHandler::NoteXMPInsertion - Needs specific override", kXMPErr_InternalFailure );
+}
+
+void Basic_MetaHandler::CaptureFileEnding ( XMP_IO* fileRef )
+{
+ XMP_Throw ( "Basic_MetaHandler::CaptureFileEnding - Needs specific override", kXMPErr_InternalFailure );
+}
+
+void Basic_MetaHandler::RestoreFileEnding ( XMP_IO* fileRef )
+{
+ XMP_Throw ( "Basic_MetaHandler::RestoreFileEnding - Needs specific override", kXMPErr_InternalFailure );
+}
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/Basic_Handler.hpp b/XMPFiles/source/FileHandlers/Basic_Handler.hpp
new file mode 100644
index 0000000..d5fca0f
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/Basic_Handler.hpp
@@ -0,0 +1,117 @@
+#ifndef __Basic_Handler_hpp__
+#define __Basic_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+// =================================================================================================
+/// \file Basic_Handler.hpp
+///
+/// \brief Base class for handlers that support a simple file model allowing insertion and expansion
+/// of XMP, but probably not reconciliation with other forms of metadata. Reconciliation would have
+/// to be done within the I/O model presented here.
+///
+/// \note Any specific derived handler might not be able to do insertion, but all must support
+/// expansion. If a handler can't do either it should be derived from Trivial_Handler. Common code
+/// must check the actual canInject flag where appropriate.
+///
+/// The model for a basic handler divides the file into 6 portions:
+///
+/// \li The front of the file. This portion can be arbitrarily large. Files over 4GB are supported.
+/// Adding or expanding the XMP must not require expanding this portion of the file. The XMP offset
+/// or length might be written into reserved space in this section though.
+///
+/// \li A prefix for the XMP section. The prefix and suffix for the XMP "section" are the format
+/// specific portions that surround the raw XMP packet. They must be generated on the fly, even when
+/// updating existing XMP with or without expansion. Their length must not depend on the XMP packet.
+///
+/// \li The XMP packet, as created by SXMPMeta::SerializeToBuffer. The size must be less than 2GB.
+///
+/// \li A suffix for the XMP section.
+///
+/// \li Trailing file content. This portion can be arbitarily large. It must be possible to remove
+/// the XMP, move this portion of the file forward, then reinsert the XMP after this portion. This
+/// is actually how the XMP is expanded. There must not be any embedded file offsets in this part,
+/// this content must not change if the XMP changes size.
+///
+/// \li The back of the file. This portion must have modest size, and/or be generated on the fly.
+/// When inserting XMP, part of this may be buffered in RAM (hence the modest size requirement), the
+/// XMP section is written, then this portion is rewritten. There must not be any embedded file
+/// offsets in this part, this content must not change if the XMP changes size.
+///
+/// \note There is no general promise here about crash-safe I/O. An update to an existing file might
+/// have invalid partial state, for example while moving the trailing content portion forward if the
+/// XMP increases in size or even rewriting existing XMP in-place. Crash-safe updates are managed at
+/// a higher level of XMPFiles, using a temporary file and final swap of file content.
+///
+// =================================================================================================
+
+static const XMP_OptionBits kBasic_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_AllowsSafeUpdate);
+
+class Basic_MetaHandler : public XMPFileHandler
+{
+public:
+
+ Basic_MetaHandler() :
+ xmpFileOffset(0), xmpFileSize(0), xmpPrefixSize(0), xmpSuffixSize(0), trailingContentSize(0) {};
+ ~Basic_MetaHandler();
+
+ virtual void CacheFileData() = 0; // Sets offset for insertion if no XMP yet.
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+protected:
+
+ // Write a cached or fixed prefix or suffix for the XMP. The file is passed because it could be
+ // either the original file or a safe-update temp file.
+ virtual void WriteXMPPrefix ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers!
+ virtual void WriteXMPSuffix ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers!
+
+ // Note that the XMP is being removed or inserted. The file is passed because it could be either
+ // the original file or a safe-update temp file.
+ virtual void NoteXMPRemoval ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers!
+ virtual void NoteXMPInsertion ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers!
+
+ // Capture or restore the tail portion of the file. The file is passed because it could be either
+ // the original file or a safe-update temp file.
+ virtual void CaptureFileEnding ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers!
+ virtual void RestoreFileEnding ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers!
+
+ // Move the trailing content portion forward. Excludes "back" of the file. The file is passed
+ // because it could be either the original file or a safe-update temp file.
+ void ShuffleTrailingContent ( XMP_IO* fileRef ); // Has a common implementation.
+
+ XMP_Uns64 xmpFileOffset; // The offset of the XMP in the file.
+ XMP_Uns32 xmpFileSize; // The size of the XMP in the file.
+ // ! The packetInfo offset and length are updated by PutXMP, before the file is updated!
+
+ XMP_Uns32 xmpPrefixSize; // The size of the existing header for the XMP section.
+ XMP_Uns32 xmpSuffixSize; // The size of the existing trailer for the XMP section.
+
+ XMP_Uns64 trailingContentSize; // The size of the existing trailing content. Excludes "back" of the file.
+
+}; // Basic_MetaHandler
+
+// =================================================================================================
+
+#endif /* __Basic_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/FLV_Handler.cpp b/XMPFiles/source/FileHandlers/FLV_Handler.cpp
new file mode 100644
index 0000000..8c1c745
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/FLV_Handler.cpp
@@ -0,0 +1,735 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FileHandlers/FLV_Handler.hpp"
+
+#include "source/XIO.hpp"
+#include "third-party/zuid/interfaces/MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file FLV_Handler.cpp
+/// \brief File format handler for FLV.
+///
+/// FLV is a fairly simple format, with a strong orientation to streaming use. It consists of a
+/// small file header then a sequence of tags that can contain audio data, video data, or
+/// ActionScript data. All integers in FLV are big endian.
+///
+/// For FLV version 1, the file header contains:
+///
+/// UI24 signature - the characters "FLV"
+/// UI8 version - 1
+/// UI8 flags - 0x01 = has video tags, 0x04 = has audio tags
+/// UI32 length in bytes of file header
+///
+/// For FLV version 1, each tag begins with an 11 byte header:
+///
+/// UI8 tag type - 8 = audio tag, 9 = video tag, 18 = script data tag
+/// UI24 content length in bytes
+/// UI24 time - low order 3 bytes
+/// UI8 time - high order byte
+/// UI24 stream ID
+///
+/// This is followed by the tag's content, then a UI32 "back pointer" which is the header size plus
+/// the content size. A UI32 zero is placed between the file header and the first tag as a
+/// terminator for backward scans. The time in a tag header is the start of playback for that tag.
+/// The tags must be in ascending time order. For a given time it is preferred that script data tags
+/// precede audio and video tags.
+///
+/// For metadata purposes only the script data tags are of interest. Script data information becomes
+/// accessible to ActionScript at the playback moment of the script data tag through a call to a
+/// registered data handler. The content of a script data tag contains a string and an ActionScript
+/// data value. The string is the name of the handler to be invoked, the data value is passed as an
+/// ActionScript Object parameter to the handler.
+///
+/// The XMP is placed in a script data tag with the name "onXMPData". A variety of legacy metadata
+/// is contained in a script data tag with the name "onMetaData". This contains only "internal"
+/// information (like duration or width/height), nothing that is user or author editiable (like
+/// title or description). Some of these legacy items are imported into the XMP, none are updated
+/// from the XMP.
+///
+/// A script data tag's content is:
+///
+/// UI8 0x02
+/// UI16 name length - includes nul terminator if present
+/// UI8n object name - UTF-8, possibly with nul terminator
+/// ... object value - serialized ActionScript value (SCRIPTDATAVALUE in FLV spec)
+///
+/// The onXMPData and onMetaData values are both ECMA arrays. These have more in common with XMP
+/// structs than arrays, the items have arbitrary string names. The serialized form is:
+///
+/// UI8 0x08
+/// UI32 array length - need not be exact, an optimization hint
+/// array items
+/// UI16 name length - includes nul terminator if present
+/// UI8n item name - UTF-8, possibly with nul terminator
+/// ... object value - serialized ActionScript value (SCRIPTDATAVALUE in FLV spec)
+/// UI24 0x000009 - array terminator
+///
+/// The object names and array item names in sample files do not have a nul terminator. The policy
+/// here is to treat them as optional when reading, and to omit them when writing.
+///
+/// The onXMPData array typically has one item named "liveXML". The value of this is a short or long
+/// string as necessary:
+///
+/// UI8 type - 2 for a short string, 12 for a long string
+/// UIx value length - UI16 for a short string, UI32 for a long string, includes nul terminator
+/// UI8n value - UTF-8 with nul terminator
+///
+// =================================================================================================
+
+static inline XMP_Uns32 GetUns24BE ( const void * addr )
+{
+ return (GetUns32BE(addr) >> 8);
+}
+
+static inline void PutUns24BE ( XMP_Uns32 value, void * addr )
+{
+ XMP_Uns8 * bytes = (XMP_Uns8*)addr;
+ bytes[0] = (XMP_Uns8)(value >> 16);
+ bytes[1] = (XMP_Uns8)(value >> 8);
+ bytes[2] = (XMP_Uns8)(value);
+}
+
+// =================================================================================================
+// FLV_CheckFormat
+// ===============
+//
+// Check for "FLV" and 1 in the first 4 bytes, that the header length is at least 9, that the file
+// size is at least as big as the header, and that the leading 0 back pointer is present if the file
+// is bigger than the header.
+
+#define kFLV1 0x464C5601UL
+
+bool FLV_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent )
+{
+ XMP_Uns8 buffer [9];
+
+ fileRef->Rewind();
+ XMP_Uns32 ioCount = fileRef->Read ( buffer, 9 );
+ if ( ioCount != 9 ) return false;
+
+ XMP_Uns32 fileSignature = GetUns32BE ( &buffer[0] );
+ if ( fileSignature != kFLV1 ) return false;
+
+ XMP_Uns32 headerSize = GetUns32BE ( &buffer[5] );
+ XMP_Uns64 fileSize = fileRef->Length();
+ if ( (fileSize < (headerSize + 4)) && (fileSize != headerSize) ) return false;
+
+ if ( fileSize >= (headerSize + 4) ) {
+ XMP_Uns32 bpZero;
+ fileRef->Seek ( headerSize, kXMP_SeekFromStart );
+ ioCount = fileRef->Read ( &bpZero, 4 );
+ if ( (ioCount != 4) || (bpZero != 0) ) return false;
+ }
+
+ return true;
+
+} // FLV_CheckFormat
+
+// =================================================================================================
+// FLV_MetaHandlerCTor
+// ===================
+
+XMPFileHandler * FLV_MetaHandlerCTor ( XMPFiles * parent )
+{
+
+ return new FLV_MetaHandler ( parent );
+
+} // FLV_MetaHandlerCTor
+
+// =================================================================================================
+// FLV_MetaHandler::FLV_MetaHandler
+// ================================
+
+FLV_MetaHandler::FLV_MetaHandler ( XMPFiles * _parent )
+ : flvHeaderLen(0), longXMP(false), xmpTagPos(0), omdTagPos(0), xmpTagLen(0), omdTagLen(0)
+{
+
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kFLV_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+} // FLV_MetaHandler::FLV_MetaHandler
+
+// =================================================================================================
+// FLV_MetaHandler::~FLV_MetaHandler
+// =================================
+
+FLV_MetaHandler::~FLV_MetaHandler()
+{
+
+ // Nothing to do yet.
+
+} // FLV_MetaHandler::~FLV_MetaHandler
+
+// =================================================================================================
+// GetTagInfo
+// ==========
+//
+// Seek to the start of a tag and extract the type, data size, and timestamp. Leave the file
+// positioned at the first byte of data.
+
+struct TagInfo {
+ XMP_Uns8 type;
+ XMP_Uns32 time;
+ XMP_Uns32 dataSize;
+};
+
+static void GetTagInfo ( XMP_IO* fileRef, XMP_Uns64 tagPos, TagInfo * info )
+{
+ XMP_Uns8 buffer [11];
+
+ fileRef->Seek ( tagPos, kXMP_SeekFromStart );
+ fileRef->ReadAll ( buffer, 11 );
+
+ info->type = buffer[0];
+ info->time = GetUns24BE ( &buffer[4] ) || (buffer[7] << 24);
+ info->dataSize = GetUns24BE ( &buffer[1] );
+
+} // GetTagInfo
+
+// =================================================================================================
+// GetASValueLen
+// =============
+//
+// Return the full length of a serialized ActionScript value, including the type byte, zero if unknown.
+
+static XMP_Uns32 GetASValueLen ( const XMP_Uns8 * asValue, const XMP_Uns8 * asLimit )
+{
+ XMP_Uns32 valueLen = 0;
+ const XMP_Uns8 * itemPtr;
+ XMP_Uns32 arrayCount;
+
+ switch ( asValue[0] ) {
+
+ case 0 : // IEEE double
+ valueLen = 1 + 8;
+ break;
+
+ case 1 : // UI8 Boolean
+ valueLen = 1 + 1;
+ break;
+
+ case 2 : // Short string
+ valueLen = 1 + 2 + GetUns16BE ( &asValue[1] );
+ break;
+
+ case 3 : // ActionScript object, a name and value.
+ itemPtr = &asValue[1];
+ itemPtr += 2 + GetUns16BE ( itemPtr ); // Move past the name portion.
+ itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion.
+ valueLen = (XMP_Uns32) (itemPtr - asValue);
+ break;
+
+ case 4 : // Short string (movie clip path)
+ valueLen = 1 + 2 + GetUns16BE ( &asValue[1] );
+ break;
+
+ case 5 : // Null
+ valueLen = 1;
+ break;
+
+ case 6 : // Undefined
+ valueLen = 1;
+ break;
+
+ case 7 : // UI16 reference ID
+ valueLen = 1 + 2;
+ break;
+
+ case 8 : // ECMA array, ignore the count, look for the 0x000009 terminator.
+ itemPtr = &asValue[5];
+ while ( itemPtr < asLimit ) {
+ XMP_Uns16 nameLen = GetUns16BE ( itemPtr );
+ itemPtr += 2 + nameLen; // Move past the name portion.
+ if ( (nameLen == 0) && (*itemPtr == 9) ) {
+ itemPtr += 1;
+ break; // Done, found the 0x000009 terminator.
+ }
+ itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion.
+ }
+ valueLen = (XMP_Uns32) (itemPtr - asValue);
+ break;
+
+ case 10 : // Strict array, has an exact count.
+ arrayCount = GetUns32BE ( &asValue[1] );
+ itemPtr = &asValue[5];
+ for ( ; (arrayCount > 0) && (itemPtr < asLimit); --arrayCount ) {
+ itemPtr += 2 + GetUns16BE ( itemPtr ); // Move past the name portion.
+ itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion.
+ }
+ valueLen = (XMP_Uns32) (itemPtr - asValue);
+ break;
+
+ case 11 : // Date
+ valueLen = 1 + 8 + 2;
+ break;
+
+ case 12: // Long string
+ valueLen = 1 + 4 + GetUns32BE ( &asValue[1] );
+ break;
+
+ }
+
+ return valueLen;
+
+} // GetASValueLen
+
+// =================================================================================================
+// CheckName
+// =========
+//
+// Check for the name portion of a script data tag or array item, with optional nul terminator. The
+// wantedLen must not count the terminator.
+
+static inline bool CheckName ( XMP_StringPtr inputName, XMP_Uns16 inputLen,
+ XMP_StringPtr wantedName, XMP_Uns16 wantedLen )
+{
+
+ if ( inputLen == wantedLen+1 ) {
+ if ( inputName[wantedLen] != 0 ) return false; // Extra byte must be terminating nul.
+ --inputLen;
+ }
+
+ if ( (inputLen == wantedLen) && XMP_LitNMatch ( inputName, wantedName, wantedLen ) ) return true;
+ return false;
+
+} // CheckName
+
+// =================================================================================================
+// FLV_MetaHandler::CacheFileData
+// ==============================
+//
+// Look for the onXMPData and onMetaData script data tags at time 0. Cache all of onMetaData, it
+// shouldn't be that big and this removes a need to know what is reconciled. It can't be more than
+// 16MB anyway, the size field is only 24 bits.
+
+void FLV_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( ! this->containsXMP );
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_Uns64 fileSize = fileRef->Length();
+
+ XMP_Uns8 buffer [16]; // Enough for 1+2+"onMetaData"+nul.
+ XMP_Uns32 ioCount;
+ TagInfo info;
+
+ fileRef->Seek ( 5, kXMP_SeekFromStart );
+ fileRef->ReadAll ( buffer, 4 );
+
+ this->flvHeaderLen = GetUns32BE ( &buffer[0] );
+ XMP_Uns32 firstTagPos = this->flvHeaderLen + 4; // Include the initial zero back pointer.
+
+ if ( firstTagPos >= fileSize ) return; // Quit now if the file is just a header.
+
+ for ( XMP_Uns64 tagPos = firstTagPos; tagPos < fileSize; tagPos += (11 + info.dataSize + 4) ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "FLV_MetaHandler::LookForMetadata - User abort", kXMPErr_UserAbort );
+ }
+
+ GetTagInfo ( fileRef, tagPos, &info ); // ! GetTagInfo seeks to the tag offset.
+ if ( info.time != 0 ) break;
+ if ( info.type != 18 ) continue;
+
+ XMP_Assert ( sizeof(buffer) >= (1+2+10+1) ); // 02 000B onMetaData 00
+ ioCount = fileRef->Read ( buffer, sizeof(buffer) );
+ if ( (ioCount < 4) || (buffer[0] != 0x02) ) continue;
+
+ XMP_Uns16 nameLen = GetUns16BE ( &buffer[1] );
+ XMP_StringPtr namePtr = (XMP_StringPtr)(&buffer[3]);
+
+ if ( this->onXMP.empty() && CheckName ( namePtr, nameLen, "onXMPData", 9 ) ) {
+
+ // ! Put the raw data in onXMPData, analyze the value in ProcessXMP.
+
+ this->xmpTagPos = tagPos;
+ this->xmpTagLen = 11 + info.dataSize + 4; // ! Includes the trailing back pointer.
+
+ this->packetInfo.offset = tagPos + 11 + 1+2+nameLen; // ! Not the real offset yet, the offset of the onXMPData value.
+
+ ioCount = info.dataSize - (1+2+nameLen); // Just the onXMPData value portion.
+ this->onXMP.reserve ( ioCount );
+ this->onXMP.assign ( ioCount, ' ' );
+ fileRef->Seek ( this->packetInfo.offset, kXMP_SeekFromStart );
+ fileRef->ReadAll ( (void*)this->onXMP.data(), ioCount );
+
+ if ( ! this->onMetaData.empty() ) break; // Done if we've found both.
+
+ } else if ( this->onMetaData.empty() && CheckName ( namePtr, nameLen, "onMetaData", 10 ) ) {
+
+ this->omdTagPos = tagPos;
+ this->omdTagLen = 11 + info.dataSize + 4; // ! Includes the trailing back pointer.
+
+ ioCount = info.dataSize - (1+2+nameLen); // Just the onMetaData value portion.
+ this->onMetaData.reserve ( ioCount );
+ this->onMetaData.assign ( ioCount, ' ' );
+ fileRef->Seek ( (tagPos + 11 + 1+2+nameLen), kXMP_SeekFromStart );
+ fileRef->ReadAll ( (void*)this->onMetaData.data(), ioCount );
+
+ if ( ! this->onXMP.empty() ) break; // Done if we've found both.
+
+ }
+
+ }
+
+} // FLV_MetaHandler::CacheFileData
+
+// =================================================================================================
+// FLV_MetaHandler::MakeLegacyDigest
+// =================================
+
+#define kHexDigits "0123456789ABCDEF"
+
+void FLV_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ MD5_CTX context;
+ unsigned char digestBin [16];
+
+ MD5Init ( &context );
+ MD5Update ( &context, (XMP_Uns8*)this->onMetaData.data(), (unsigned int)this->onMetaData.size() );
+ MD5Final ( digestBin, &context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->erase();
+ digestStr->append ( buffer, 32 );
+
+} // FLV_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+// FLV_MetaHandler::ExtractLiveXML
+// ===============================
+//
+// Extract the XMP packet from the cached onXMPData ECMA array's "liveXMP" item.
+
+void FLV_MetaHandler::ExtractLiveXML()
+{
+ if ( this->onXMP[0] != 0x08 ) return; // Make sure onXMPData is an ECMA array.
+ const XMP_Uns8 * ecmaArray = (const XMP_Uns8 *) this->onXMP.c_str();
+ const XMP_Uns8 * ecmaLimit = ecmaArray + this->onXMP.size();
+
+ if ( this->onXMP.size() >= 3 ) { // Omit the 0x000009 terminator, simplifies the loop.
+ if ( GetUns24BE ( ecmaLimit-3 ) == 9 ) ecmaLimit -= 3;
+ }
+
+ for ( const XMP_Uns8 * itemPtr = ecmaArray + 5; itemPtr < ecmaLimit; /* internal increment */ ) {
+
+ // Find the "liveXML" array item, make sure it is a short or long string.
+
+ XMP_Uns16 nameLen = GetUns16BE ( itemPtr );
+ const XMP_Uns8 * namePtr = itemPtr + 2;
+
+ itemPtr += (2 + nameLen); // Move to the value portion.
+ XMP_Uns32 valueLen = GetASValueLen ( itemPtr, ecmaLimit );
+ if ( valueLen == 0 ) return; // ! Unknown value type, can't look further.
+
+ if ( CheckName ( (char*)namePtr, nameLen, "liveXML", 7 ) ) {
+
+ XMP_Uns32 lenLen = 2; // Assume a short string.
+ if ( *itemPtr == 12 ) {
+ lenLen = 4;
+ this->longXMP = true;
+ } else if ( *itemPtr != 2 ) {
+ return; // Not a short or long string.
+ }
+
+ valueLen -= (1 + lenLen);
+ itemPtr += (1 + lenLen);
+
+ this->packetInfo.offset += (itemPtr - ecmaArray);
+ this->packetInfo.length += valueLen;
+
+ this->xmpPacket.reserve ( valueLen );
+ this->xmpPacket.assign ( (char*)itemPtr, valueLen );
+
+ return;
+
+ }
+
+ itemPtr += valueLen; // Move past the value portion.
+
+ }
+
+} // FLV_MetaHandler::ExtractLiveXML
+
+// =================================================================================================
+// FLV_MetaHandler::ProcessXMP
+// ===========================
+
+void FLV_MetaHandler::ProcessXMP()
+{
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( ! this->onXMP.empty() ) { // Look for the XMP packet.
+
+ this->ExtractLiveXML();
+ if ( ! this->xmpPacket.empty() ) {
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ this->containsXMP = true;
+ }
+
+ }
+
+ // Now process the legacy, if necessary.
+
+ if ( this->onMetaData.empty() ) return; // No legacy, we're done.
+
+ std::string oldDigest;
+ bool oldDigestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "FLV", &oldDigest, 0 );
+
+ if ( oldDigestFound ) {
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) return; // No legacy changes.
+ }
+
+ // *** No spec yet for what legacy to reconcile.
+
+} // FLV_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// FLV_MetaHandler::UpdateFile
+// ===========================
+
+void FLV_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates.
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_Uns64 fileSize = fileRef->Length();
+
+ // Make sure the XMP has a legacy digest if appropriate.
+
+ if ( ! this->onMetaData.empty() ) {
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests",
+ kXMP_NS_XMP, "FLV", newDigest.c_str(), kXMP_DeleteExisting );
+
+ try {
+ XMP_StringLen xmpLen = (XMP_StringLen)this->xmpPacket.size();
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat | kXMP_ExactPacketLength), xmpLen );
+ } catch ( ... ) {
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+ }
+
+ }
+
+ // Rewrite the packet in-place if it fits. Otherwise rewrite the whole file.
+
+ if ( this->xmpPacket.size() == (size_t)this->packetInfo.length ) {
+
+ fileRef->Seek ( this->packetInfo.offset, kXMP_SeekFromStart );
+ fileRef->Write ( this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() );
+
+ } else {
+
+ XMP_IO* tempRef = fileRef->DeriveTemp();
+ if ( tempRef == 0 ) XMP_Throw ( "Failure creating FLV temp file", kXMPErr_InternalFailure );
+
+ this->WriteTempFile ( tempRef );
+ fileRef->AbsorbTemp();
+
+ }
+
+ this->needsUpdate = false;
+
+} // FLV_MetaHandler::UpdateFile
+
+// =================================================================================================
+// WriteOnXMP
+// ==========
+//
+// Write the XMP packet wrapped up in an ECMA array script data tag:
+//
+// 0 UI8 tag type : 18
+// 1 UI24 content length : 1+2+9+1+4+2+7+1 + <2 or 4> + XMP packet size + 1 + 3
+// 4 UI24 time low : 0
+// 7 UI8 time high : 0
+// 8 UI24 stream ID : 0
+//
+// 11 UI8 0x02
+// 12 UI16 name length : 9
+// 14 str9 tag name : "onXMPData", no nul terminator
+// 23 UI8 value type : 8
+// 24 UI32 array count : 1
+// 28 UI16 name length : 7
+// 30 str7 item name : "liveXML", no nul terminator
+//
+// 37 UI8 value type : 2 for a short string, 12 for a long string
+// 38 UIn XMP packet size + 1, UI16 or UI32 as needed
+// -- str XMP packet, with nul terminator
+//
+// -- UI24 array terminator : 0x000009
+// -- UI32 back pointer : content length + 11
+
+static void WriteOnXMP ( XMP_IO* fileRef, const std::string & xmpPacket )
+{
+ char buffer [64];
+ bool longXMP = false;
+ XMP_Uns32 tagLen = 1+2+9+1+4+2+7+1 + 2 + (XMP_Uns32)xmpPacket.size() + 1 + 3;
+
+ if ( xmpPacket.size() > 0xFFFE ) {
+ longXMP = true;
+ tagLen += 2;
+ }
+
+ if ( tagLen > 16*1024*1024 ) XMP_Throw ( "FLV tags can't be larger than 16MB", kXMPErr_TBD );
+
+ // Fill in the script data tag header.
+
+ buffer[0] = 18;
+ PutUns24BE ( tagLen, &buffer[1] );
+ PutUns24BE ( 0, &buffer[4] );
+ buffer[7] = 0;
+ PutUns24BE ( 0, &buffer[8] );
+
+ // Fill in the "onXMPData" name, ECMA array start, and "liveXML" name.
+
+ buffer[11] = 2;
+ PutUns16BE ( 9, &buffer[12] );
+ memcpy ( &buffer[14], "onXMPData", 9 ); // AUDIT: Safe, buffer has 64 chars.
+ buffer[23] = 8;
+ PutUns32BE ( 1, &buffer[24] );
+ PutUns16BE ( 7, &buffer[28] );
+ memcpy ( &buffer[30], "liveXML", 7 ); // AUDIT: Safe, buffer has 64 chars.
+
+ // Fill in the XMP packet string type and length, write what we have so far.
+
+ fileRef->ToEOF();
+ if ( ! longXMP ) {
+ buffer[37] = 2;
+ PutUns16BE ( (XMP_Uns16)xmpPacket.size()+1, &buffer[38] );
+ fileRef->Write ( buffer, 40 );
+ } else {
+ buffer[37] = 12;
+ PutUns32BE ( (XMP_Uns32)xmpPacket.size()+1, &buffer[38] );
+ fileRef->Write ( buffer, 42 );
+ }
+
+ // Write the XMP packet, nul terminator, array terminator, and back pointer.
+
+ fileRef->Write ( xmpPacket.c_str(), (XMP_Int32)xmpPacket.size()+1 );
+ PutUns24BE ( 9, &buffer[0] );
+ PutUns32BE ( tagLen+11, &buffer[3] );
+ fileRef->Write ( buffer, 7 );
+
+} // WriteOnXMP
+
+// =================================================================================================
+// FLV_MetaHandler::WriteTempFile
+// ==============================
+//
+// Use a source (old) file and the current XMP to build a destination (new) file. All of the source
+// file is copied except for previous XMP. The current XMP is inserted after onMetaData, or at least
+// before the first time 0 audio or video tag.
+
+// ! We do not currently update anything in onMetaData.
+
+void FLV_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ if ( ! this->needsUpdate ) return;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_IO* originalRef = this->parent->ioRef;
+
+ XMP_Uns64 sourceLen = originalRef->Length();
+ XMP_Uns64 sourcePos = 0;
+
+ originalRef->Rewind();
+ tempRef->Rewind();
+ tempRef->Truncate ( 0 );
+
+ // First do whatever is needed to put the new XMP after any existing onMetaData tag, or as the
+ // first time 0 tag.
+
+ if ( this->omdTagPos == 0 ) {
+
+ // There is no onMetaData tag. Copy the file header, then write the new XMP as the first tag.
+ // Allow the degenerate case of a file with just a header, no initial back pointer or tags.
+
+ originalRef->Seek ( sourcePos, kXMP_SeekFromStart );
+ XIO::Copy ( originalRef, tempRef, this->flvHeaderLen, abortProc, abortArg );
+
+ XMP_Uns32 zero = 0; // Ensure that the initial back offset really is zero.
+ tempRef->Write ( &zero, 4 );
+ sourcePos = this->flvHeaderLen + 4;
+
+ WriteOnXMP ( tempRef, this->xmpPacket );
+
+ } else {
+
+ // There is an onMetaData tag. Copy the front of the file through the onMetaData tag,
+ // skipping any XMP that happens to be in the way. The XMP should not be before onMetaData,
+ // but let's be robust. Write the new XMP immediately after onMetaData, at the same time.
+
+ XMP_Uns64 omdEnd = this->omdTagPos + this->omdTagLen;
+
+ if ( (this->xmpTagPos != 0) && (this->xmpTagPos < this->omdTagPos) ) {
+ // The XMP tag was in front of the onMetaData tag. Copy up to it, then skip it.
+ originalRef->Seek ( sourcePos, kXMP_SeekFromStart );
+ XIO::Copy ( originalRef, tempRef, this->xmpTagPos, abortProc, abortArg );
+ sourcePos = this->xmpTagPos + this->xmpTagLen; // The tag length includes the trailing size field.
+ }
+
+ // Copy through the onMetaData tag, then write the XMP.
+ originalRef->Seek ( sourcePos, kXMP_SeekFromStart );
+ XIO::Copy ( originalRef, tempRef, (omdEnd - sourcePos), abortProc, abortArg );
+ sourcePos = omdEnd;
+
+ WriteOnXMP ( tempRef, this->xmpPacket );
+
+ }
+
+ // Copy the rest of the file, skipping any XMP that is in the way.
+
+ if ( (this->xmpTagPos != 0) && (this->xmpTagPos >= sourcePos) ) {
+ originalRef->Seek ( sourcePos, kXMP_SeekFromStart );
+ XIO::Copy ( originalRef, tempRef, (this->xmpTagPos - sourcePos), abortProc, abortArg );
+ sourcePos = this->xmpTagPos + this->xmpTagLen;
+ }
+
+ originalRef->Seek ( sourcePos, kXMP_SeekFromStart );
+ XIO::Copy ( originalRef, tempRef, (sourceLen - sourcePos), abortProc, abortArg );
+
+ this->needsUpdate = false;
+
+} // FLV_MetaHandler::WriteTempFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/FLV_Handler.hpp b/XMPFiles/source/FileHandlers/FLV_Handler.hpp
new file mode 100644
index 0000000..7a63b5c
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/FLV_Handler.hpp
@@ -0,0 +1,80 @@
+#ifndef __FLV_Handler_hpp__
+#define __FLV_Handler_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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+
+// ================================================================================================
+/// \file FLV_Handler.hpp
+/// \brief File format handler for FLV.
+///
+/// This header ...
+///
+// ================================================================================================
+
+extern XMPFileHandler * FLV_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool FLV_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kFLV_HandlerFlags = ( kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_AllowsSafeUpdate
+ );
+
+class FLV_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+
+ FLV_MetaHandler ( XMPFiles * _parent );
+ virtual ~FLV_MetaHandler();
+
+private:
+
+ FLV_MetaHandler() : flvHeaderLen(0), longXMP(false),
+ xmpTagPos(0), omdTagPos(0), xmpTagLen(0), omdTagLen(0) {}; // Hidden on purpose.
+
+ void ExtractLiveXML();
+ void MakeLegacyDigest ( std::string * digestStr );
+
+ XMP_Uns32 flvHeaderLen;
+ bool longXMP; // True if the stored XMP is a long string (4 byte length).
+
+ XMP_Uns64 xmpTagPos, omdTagPos; // The file offset and length of onXMP and onMetaData tags.
+ XMP_Uns32 xmpTagLen, omdTagLen; // Zero if the tag is not present.
+
+ std::string onXMP, onMetaData; // ! Actually contains structured binary data.
+
+}; // FLV_MetaHandler
+
+// =================================================================================================
+
+#endif // __FLV_Handler_hpp__
diff --git a/XMPFiles/source/FileHandlers/InDesign_Handler.cpp b/XMPFiles/source/FileHandlers/InDesign_Handler.cpp
new file mode 100644
index 0000000..5031cef
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/InDesign_Handler.cpp
@@ -0,0 +1,419 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FileHandlers/InDesign_Handler.hpp"
+
+#include "source/XIO.hpp"
+
+using namespace std;
+
+// =================================================================================================
+/// \file InDesign_Handler.cpp
+/// \brief File format handler for InDesign files.
+///
+/// This header ...
+///
+/// The layout of an InDesign file in terms of the Basic_MetaHandler model is:
+///
+/// \li The front of the file. This is everything up to the XMP contiguous object section. The file
+/// starts with a pair of master pages, followed by the data pages, followed by contiguous object
+/// sections, finished with padding to a page boundary.
+///
+/// \li A prefix for the XMP section. This is the contiguous object header. The offset is
+/// (this->packetInfo.offset - this->xmpPrefixSize).
+///
+/// \li The XMP packet. The offset is this->packetInfo.offset.
+///
+/// \li A suffix for the XMP section. This is the contiguous object header. The offset is
+/// (this->packetInfo.offset + this->packetInfo.length).
+///
+/// \li Trailing file content. This is the contiguous objects that follow the XMP. The offset is
+/// (this->packetInfo.offset + this->packetInfo.length + this->xmpSuffixSize).
+///
+/// \li The back of the file. This is the final padding to a page boundary. The offset is
+/// (this->packetInfo.offset + this->packetInfo.length + this->xmpSuffixSize + this->trailingContentSize).
+///
+// =================================================================================================
+
+// *** Add PutXMP overrides that throw if the file does not contain XMP.
+
+#ifndef TraceInDesignHandler
+ #define TraceInDesignHandler 0
+#endif
+
+enum { kInDesignGUIDSize = 16 };
+
+struct InDesignMasterPage {
+ XMP_Uns8 fGUID [kInDesignGUIDSize];
+ XMP_Uns8 fMagicBytes [8];
+ XMP_Uns8 fObjectStreamEndian;
+ XMP_Uns8 fIrrelevant1 [239];
+ XMP_Uns64 fSequenceNumber;
+ XMP_Uns8 fIrrelevant2 [8];
+ XMP_Uns32 fFilePages;
+ XMP_Uns8 fIrrelevant3 [3812];
+};
+
+enum {
+ kINDD_PageSize = 4096,
+ kINDD_PageMask = (kINDD_PageSize - 1),
+ kINDD_LittleEndian = 1,
+ kINDD_BigEndian = 2 };
+
+struct InDesignContigObjMarker {
+ XMP_Uns8 fGUID [kInDesignGUIDSize];
+ XMP_Uns32 fObjectUID;
+ XMP_Uns32 fObjectClassID;
+ XMP_Uns32 fStreamLength;
+ XMP_Uns32 fChecksum;
+};
+
+static const XMP_Uns8 * kINDD_MasterPageGUID =
+ (const XMP_Uns8 *) "\x06\x06\xED\xF5\xD8\x1D\x46\xE5\xBD\x31\xEF\xE7\xFE\x74\xB7\x1D";
+
+static const XMP_Uns8 * kINDDContigObjHeaderGUID =
+ (const XMP_Uns8 *) "\xDE\x39\x39\x79\x51\x88\x4B\x6C\x8E\x63\xEE\xF8\xAE\xE0\xDD\x38";
+
+static const XMP_Uns8 * kINDDContigObjTrailerGUID =
+ (const XMP_Uns8 *) "\xFD\xCE\xDB\x70\xF7\x86\x4B\x4F\xA4\xD3\xC7\x28\xB3\x41\x71\x06";
+
+// =================================================================================================
+// InDesign_MetaHandlerCTor
+// ========================
+
+XMPFileHandler * InDesign_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new InDesign_MetaHandler ( parent );
+
+} // InDesign_MetaHandlerCTor
+
+// =================================================================================================
+// InDesign_CheckFormat
+// ====================
+//
+// For InDesign we check that the pair of master pages begin with the 16 byte GUID.
+
+bool InDesign_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent )
+{
+ IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(parent);
+ XMP_Assert ( format == kXMP_InDesignFile );
+ XMP_Assert ( strlen ( (const char *) kINDD_MasterPageGUID ) == kInDesignGUIDSize );
+
+ enum { kBufferSize = 2*kINDD_PageSize };
+ XMP_Uns8 buffer [kBufferSize];
+
+ XMP_Int64 filePos = 0;
+ XMP_Uns8 * bufPtr = buffer;
+ XMP_Uns8 * bufLimit = bufPtr + kBufferSize;
+
+ fileRef->Rewind();
+ size_t bufLen = fileRef->Read ( buffer, kBufferSize );
+ if ( bufLen != kBufferSize ) return false;
+
+ if ( ! CheckBytes ( bufPtr, kINDD_MasterPageGUID, kInDesignGUIDSize ) ) return false;
+ if ( ! CheckBytes ( bufPtr+kINDD_PageSize, kINDD_MasterPageGUID, kInDesignGUIDSize ) ) return false;
+
+ return true;
+
+} // InDesign_CheckFormat
+
+// =================================================================================================
+// InDesign_MetaHandler::InDesign_MetaHandler
+// ==========================================
+
+InDesign_MetaHandler::InDesign_MetaHandler ( XMPFiles * _parent ) : streamBigEndian(0), xmpObjID(0), xmpClassID(0)
+{
+ this->parent = _parent;
+ this->handlerFlags = kInDesign_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+} // InDesign_MetaHandler::InDesign_MetaHandler
+
+// =================================================================================================
+// InDesign_MetaHandler::~InDesign_MetaHandler
+// ===========================================
+
+InDesign_MetaHandler::~InDesign_MetaHandler()
+{
+ // Nothing to do here.
+
+} // InDesign_MetaHandler::~InDesign_MetaHandler
+
+// =================================================================================================
+// InDesign_MetaHandler::CacheFileData
+// ===================================
+//
+// Look for the XMP in an InDesign database file. This is a paged database using 4K byte pages,
+// followed by redundant "contiguous object streams". Each contiguous object stream is a copy of a
+// database object stored as a contiguous byte stream. The XMP that we want is one of these.
+//
+// The first 2 pages of the database are alternating master pages. A generation number is used to
+// select the active master page. The master page contains an offset to the start of the contiguous
+// object streams. Each of the contiguous object streams contains a header and trailer, allowing
+// fast motion from one stream to the next.
+//
+// There is no unique "what am I" tagging to the contiguous object streams, so we simply pick the
+// first one that looks right. At present this is a 4 byte little endian packet size followed by the
+// packet.
+
+// ! Note that insertion of XMP is not allowed for InDesign, the XMP must be a contiguous copy of an
+// ! internal database object. So we don't set the packet offset to an insertion point if not found.
+
+void InDesign_MetaHandler::CacheFileData()
+{
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_PacketInfo & packetInfo = this->packetInfo;
+
+ IOBuffer ioBuf;
+ size_t dbPages;
+ XMP_Uns8 cobjEndian;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_Assert ( kINDD_PageSize == sizeof(InDesignMasterPage) );
+ XMP_Assert ( kIOBufferSize >= (2 * kINDD_PageSize) );
+
+ this->containsXMP = false;
+
+ // ---------------------------------------------------------------------------------
+ // Figure out which master page is active and seek to the contiguous object portion.
+
+ {
+ FillBuffer ( fileRef, 0, &ioBuf );
+ if ( ioBuf.len < (2 * kINDD_PageSize) ) XMP_Throw ( "GetMainPacket/ScanInDesignFile: Read failure", kXMPErr_ExternalFailure );
+
+ InDesignMasterPage * masters = (InDesignMasterPage *) ioBuf.ptr;
+ XMP_Uns64 seq0 = GetUns64LE ( (XMP_Uns8 *) &masters[0].fSequenceNumber );
+ XMP_Uns64 seq1 = GetUns64LE ( (XMP_Uns8 *) &masters[1].fSequenceNumber );
+
+ dbPages = GetUns32LE ( (XMP_Uns8 *) &masters[0].fFilePages );
+ cobjEndian = masters[0].fObjectStreamEndian;
+ if ( seq1 > seq0 ) {
+ dbPages = GetUns32LE ( (XMP_Uns8 *) &masters[1].fFilePages );
+ cobjEndian = masters[1].fObjectStreamEndian;
+ }
+ }
+
+ XMP_Assert ( ! this->streamBigEndian );
+ if ( cobjEndian == kINDD_BigEndian ) this->streamBigEndian = true;
+
+ // ---------------------------------------------------------------------------------------------
+ // Look for the XMP contiguous object stream. Most of the time there will be just one stream and
+ // it will be the XMP. So we might as well fill the whole buffer and not worry about reading too
+ // much and seeking back to the start of the following stream.
+
+ XMP_Int64 cobjPos = (XMP_Int64)dbPages * kINDD_PageSize; // ! Use a 64 bit multiply!
+ cobjPos -= (2 * sizeof(InDesignContigObjMarker)); // ! For the first pass in the loop.
+ XMP_Uns32 streamLength = 0; // ! For the first pass in the loop.
+
+ while ( true ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "InDesign_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort );
+ }
+
+ // Fetch the start of the next stream and check the contiguous object header.
+ // ! The writeable bit of fObjectClassID is ignored, we use the packet trailer flag.
+
+ cobjPos += streamLength + (2 * sizeof(InDesignContigObjMarker));
+ FillBuffer ( fileRef, cobjPos, &ioBuf ); // Make sure buffer starts at cobjPos for length check.
+ if ( ioBuf.len < (2 * sizeof(InDesignContigObjMarker)) ) break; // Too small, must be end of file.
+
+ const InDesignContigObjMarker * cobjHeader = (const InDesignContigObjMarker *) ioBuf.ptr;
+ if ( ! CheckBytes ( Uns8Ptr(&cobjHeader->fGUID), kINDDContigObjHeaderGUID, kInDesignGUIDSize ) ) break; // Not a contiguous object header.
+ this->xmpObjID = cobjHeader->fObjectUID; // Save these now while the buffer is good.
+ this->xmpClassID = cobjHeader->fObjectClassID;
+ streamLength = GetUns32LE ( (XMP_Uns8 *) &cobjHeader->fStreamLength );
+ ioBuf.ptr += sizeof ( InDesignContigObjMarker );
+
+ // See if this is the XMP stream. Only check for UTF-8, others get caught in fallback scanning.
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) continue; // Too small, can't possibly be XMP.
+
+ XMP_Uns32 innerLength = GetUns32LE ( ioBuf.ptr );
+ if ( this->streamBigEndian ) innerLength = GetUns32BE ( ioBuf.ptr );
+ if ( innerLength != (streamLength - 4) ) {
+ // Be tolerant of a mistake with the endian flag.
+ innerLength = Flip4 ( innerLength );
+ if ( innerLength != (streamLength - 4) ) continue; // Not legit XMP.
+ }
+ ioBuf.ptr += 4;
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, kUTF8_PacketHeaderLen ) ) continue; // Too small, can't possibly be XMP.
+
+ if ( ! CheckBytes ( ioBuf.ptr, kUTF8_PacketStart, strlen((char*)kUTF8_PacketStart) ) ) continue;
+ ioBuf.ptr += strlen((char*)kUTF8_PacketStart);
+
+ XMP_Uns8 quote = *ioBuf.ptr;
+ if ( (quote != '\'') && (quote != '"') ) continue;
+ ioBuf.ptr += 1;
+ if ( *ioBuf.ptr != quote ) {
+ if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr("\xEF\xBB\xBF"), 3 ) ) continue;
+ ioBuf.ptr += 3;
+ }
+ if ( *ioBuf.ptr != quote ) continue;
+ ioBuf.ptr += 1;
+
+ if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(" id="), 4 ) ) continue;
+ ioBuf.ptr += 4;
+ quote = *ioBuf.ptr;
+ if ( (quote != '\'') && (quote != '"') ) continue;
+ ioBuf.ptr += 1;
+ if ( ! CheckBytes ( ioBuf.ptr, kUTF8_PacketID, strlen((char*)kUTF8_PacketID) ) ) continue;
+ ioBuf.ptr += strlen((char*)kUTF8_PacketID);
+ if ( *ioBuf.ptr != quote ) continue;
+ ioBuf.ptr += 1;
+
+ // We've seen enough, it is the XMP. To fit the Basic_Handler model we need to compute the
+ // total size of remaining contiguous objects, the trailingContentSize.
+
+ this->xmpPrefixSize = sizeof(InDesignContigObjMarker) + 4;
+ this->xmpSuffixSize = sizeof(InDesignContigObjMarker);
+ packetInfo.offset = cobjPos + this->xmpPrefixSize;
+ packetInfo.length = innerLength;
+
+
+ XMP_Int64 tcStart = cobjPos + streamLength + (2 * sizeof(InDesignContigObjMarker));
+ while ( true ) {
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "InDesign_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort );
+ }
+ cobjPos += streamLength + (2 * sizeof(InDesignContigObjMarker));
+ FillBuffer ( fileRef, cobjPos, &ioBuf ); // Make sure buffer starts at cobjPos for length check.
+ if ( ioBuf.len < sizeof(InDesignContigObjMarker) ) break; // Too small, must be end of file.
+ cobjHeader = (const InDesignContigObjMarker *) ioBuf.ptr;
+ if ( ! CheckBytes ( Uns8Ptr(&cobjHeader->fGUID), kINDDContigObjHeaderGUID, kInDesignGUIDSize ) ) break; // Not a contiguous object header.
+ streamLength = GetUns32LE ( (XMP_Uns8 *) &cobjHeader->fStreamLength );
+ }
+ this->trailingContentSize = cobjPos - tcStart;
+
+ #if TraceInDesignHandler
+ XMP_Uns32 pktOffset = (XMP_Uns32)this->packetInfo.offset;
+ printf ( "Found XMP in InDesign file, offsets:\n" );
+ printf ( " CObj head %X, XMP %X, CObj tail %X, file tail %X, padding %X\n",
+ (pktOffset - this->xmpPrefixSize), pktOffset, (pktOffset + this->packetInfo.length),
+ (pktOffset + this->packetInfo.length + this->xmpSuffixSize),
+ (pktOffset + this->packetInfo.length + this->xmpSuffixSize + (XMP_Uns32)this->trailingContentSize) );
+ #endif
+
+ this->containsXMP = true;
+ break;
+
+ }
+
+ if ( this->containsXMP ) {
+ this->xmpFileOffset = packetInfo.offset;
+ this->xmpFileSize = packetInfo.length;
+ ReadXMPPacket ( this );
+ }
+
+} // InDesign_MetaHandler::CacheFileData
+
+// =================================================================================================
+// InDesign_MetaHandler::WriteXMPPrefix
+// ====================================
+
+void InDesign_MetaHandler::WriteXMPPrefix ( XMP_IO* fileRef )
+{
+ // Write the contiguous object header and the 4 byte length of the XMP packet.
+
+ XMP_Uns32 packetSize = (XMP_Uns32)this->xmpPacket.size();
+
+ InDesignContigObjMarker header;
+ memcpy ( header.fGUID, kINDDContigObjHeaderGUID, sizeof(header.fGUID) ); // AUDIT: Use of dest sizeof for length is safe.
+ header.fObjectUID = this->xmpObjID;
+ header.fObjectClassID = this->xmpClassID;
+ header.fStreamLength = MakeUns32LE ( 4 + packetSize );
+ header.fChecksum = (XMP_Uns32)(-1);
+ fileRef->Write ( &header, sizeof(header) );
+
+ XMP_Uns32 pktLength = MakeUns32LE ( packetSize );
+ if ( this->streamBigEndian ) pktLength = MakeUns32BE ( packetSize );
+ fileRef->Write ( &pktLength, sizeof(pktLength) );
+
+} // InDesign_MetaHandler::WriteXMPPrefix
+
+// =================================================================================================
+// InDesign_MetaHandler::WriteXMPSuffix
+// ====================================
+
+void InDesign_MetaHandler::WriteXMPSuffix ( XMP_IO* fileRef )
+{
+ // Write the contiguous object trailer.
+
+ XMP_Uns32 packetSize = (XMP_Uns32)this->xmpPacket.size();
+
+ InDesignContigObjMarker trailer;
+
+ memcpy ( trailer.fGUID, kINDDContigObjTrailerGUID, sizeof(trailer.fGUID) ); // AUDIT: Use of dest sizeof for length is safe.
+ trailer.fObjectUID = this->xmpObjID;
+ trailer.fObjectClassID = this->xmpClassID;
+ trailer.fStreamLength = MakeUns32LE ( 4 + packetSize );
+ trailer.fChecksum = (XMP_Uns32)(-1);
+
+ fileRef->Write ( &trailer, sizeof(trailer) );
+
+} // InDesign_MetaHandler::WriteXMPSuffix
+
+// =================================================================================================
+// InDesign_MetaHandler::NoteXMPRemoval
+// ====================================
+
+void InDesign_MetaHandler::NoteXMPRemoval ( XMP_IO* fileRef )
+{
+ // Nothing to do.
+
+} // InDesign_MetaHandler::NoteXMPRemoval
+
+// =================================================================================================
+// InDesign_MetaHandler::NoteXMPInsertion
+// ======================================
+
+void InDesign_MetaHandler::NoteXMPInsertion ( XMP_IO* fileRef )
+{
+ // Nothing to do.
+
+} // InDesign_MetaHandler::NoteXMPInsertion
+
+// =================================================================================================
+// InDesign_MetaHandler::CaptureFileEnding
+// =======================================
+
+void InDesign_MetaHandler::CaptureFileEnding ( XMP_IO* fileRef )
+{
+ // Nothing to do. The back of an InDesign file is the final zero padding.
+
+} // InDesign_MetaHandler::CaptureFileEnding
+
+// =================================================================================================
+// InDesign_MetaHandler::RestoreFileEnding
+// =======================================
+
+void InDesign_MetaHandler::RestoreFileEnding ( XMP_IO* fileRef )
+{
+ // Pad the file with zeros to a page boundary.
+
+ XMP_Int64 dataLength = fileRef->Length();
+ XMP_Int32 padLength = (kINDD_PageSize - ((XMP_Int32)dataLength & kINDD_PageMask)) & kINDD_PageMask;
+
+ XMP_Uns8 buffer [kINDD_PageSize];
+ memset ( buffer, 0, kINDD_PageSize );
+ fileRef->Write ( buffer, padLength );
+
+} // InDesign_MetaHandler::RestoreFileEnding
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/InDesign_Handler.hpp b/XMPFiles/source/FileHandlers/InDesign_Handler.hpp
new file mode 100644
index 0000000..66d9f79
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/InDesign_Handler.hpp
@@ -0,0 +1,68 @@
+#ifndef __InDesign_Handler_hpp__
+#define __InDesign_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+
+#include "XMPFiles/source/FileHandlers/Basic_Handler.hpp"
+
+// =================================================================================================
+/// \file InDesign_Handler.hpp
+/// \brief File format handler for InDesign files.
+///
+/// This header ...
+///
+// =================================================================================================
+
+extern XMPFileHandler * InDesign_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool InDesign_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kInDesign_HandlerFlags = kBasic_HandlerFlags & (~kXMPFiles_CanInjectXMP); // ! InDesign can't inject.
+
+class InDesign_MetaHandler : public Basic_MetaHandler
+{
+public:
+
+ InDesign_MetaHandler ( XMPFiles * parent );
+ ~InDesign_MetaHandler();
+
+ void CacheFileData();
+
+protected:
+
+ void WriteXMPPrefix ( XMP_IO* fileRef );
+ void WriteXMPSuffix ( XMP_IO* fileRef );
+
+ void NoteXMPRemoval ( XMP_IO* fileRef );
+ void NoteXMPInsertion ( XMP_IO* fileRef );
+
+ void CaptureFileEnding ( XMP_IO* fileRef );
+ void RestoreFileEnding ( XMP_IO* fileRef );
+
+ bool streamBigEndian; // Set from master page's fObjectStreamEndian.
+ XMP_Uns32 xmpObjID; // Set from contiguous object's fObjectID, still as little endian.
+ XMP_Uns32 xmpClassID; // Set from contiguous object's fObjectClassID, still as little endian.
+
+}; // InDesign_MetaHandler
+
+// =================================================================================================
+
+#endif /* __InDesign_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/JPEG_Handler.cpp b/XMPFiles/source/FileHandlers/JPEG_Handler.cpp
new file mode 100644
index 0000000..2cc4c31
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/JPEG_Handler.cpp
@@ -0,0 +1,1037 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/JPEG_Handler.hpp"
+
+#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp"
+#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp"
+#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp"
+#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp"
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+
+#include "third-party/zuid/interfaces/MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file JPEG_Handler.cpp
+/// \brief File format handler for JPEG.
+///
+/// This handler ...
+///
+// =================================================================================================
+
+static const char * kExifSignatureString = "Exif\0\x00";
+static const char * kExifSignatureAltStr = "Exif\0\xFF";
+static const size_t kExifSignatureLength = 6;
+static const size_t kExifMaxDataLength = 0xFFFF - 2 - kExifSignatureLength;
+
+static const char * kPSIRSignatureString = "Photoshop 3.0\0";
+static const size_t kPSIRSignatureLength = 14;
+static const size_t kPSIRMaxDataLength = 0xFFFF - 2 - kPSIRSignatureLength;
+
+static const char * kMainXMPSignatureString = "http://ns.adobe.com/xap/1.0/\0";
+static const size_t kMainXMPSignatureLength = 29;
+
+static const char * kExtXMPSignatureString = "http://ns.adobe.com/xmp/extension/\0";
+static const size_t kExtXMPSignatureLength = 35;
+static const size_t kExtXMPPrefixLength = kExtXMPSignatureLength + 32 + 4 + 4;
+
+typedef std::map < XMP_Uns32 /* offset */, std::string /* portion */ > ExtXMPPortions;
+
+struct ExtXMPContent {
+ XMP_Uns32 length;
+ ExtXMPPortions portions;
+ ExtXMPContent() : length(0) {};
+ ExtXMPContent ( XMP_Uns32 _length ) : length(_length) {};
+};
+
+typedef std::map < JPEG_MetaHandler::GUID_32 /* guid */, ExtXMPContent /* content */ > ExtendedXMPInfo;
+
+#ifndef Trace_UnlimitedJPEG
+ #define Trace_UnlimitedJPEG 0
+#endif
+
+// =================================================================================================
+// JPEG_MetaHandlerCTor
+// ====================
+
+XMPFileHandler * JPEG_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new JPEG_MetaHandler ( parent );
+
+} // JPEG_MetaHandlerCTor
+
+// =================================================================================================
+// JPEG_CheckFormat
+// ================
+
+// For JPEG we just check for the initial SOI standalone marker followed by any of the other markers
+// that might, well, follow it. A more aggressive check might be to read 4KB then check for legit
+// marker segments within that portion. Probably won't buy much, and thrashes the dCache more. We
+// tolerate only a small amount of 0xFF padding between the SOI and following marker. This formally
+// violates the rules of JPEG, but in practice there won't be any padding anyway.
+//
+// ! The CheckXyzFormat routines don't track the filePos, that is left to ScanXyzFile.
+
+bool JPEG_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent )
+{
+ IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(parent);
+ XMP_Assert ( format == kXMP_JPEGFile );
+
+ IOBuffer ioBuf;
+
+ fileRef->Rewind ( );
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; // We need at least 4, the buffer is filled anyway.
+
+ // First look for the SOI standalone marker. Then skip all 0xFF bytes, padding plus the high
+ // order byte of the next marker. Finally see if the next marker is legit.
+
+ if ( ! CheckBytes ( ioBuf.ptr, "\xFF\xD8", 2 ) ) return false;
+ ioBuf.ptr += 2; // Move past the SOI.
+ while ( (ioBuf.ptr < ioBuf.limit) && (*ioBuf.ptr == 0xFF) ) ++ioBuf.ptr;
+ if ( ioBuf.ptr == ioBuf.limit ) return false;
+
+ XMP_Uns8 id = *ioBuf.ptr;
+ if ( id >= 0xDD ) return true; // The most probable cases.
+ if ( (id < 0xC0) || ((id & 0xF8) == 0xD0) || (id == 0xD8) || (id == 0xDA) || (id == 0xDC) ) return false;
+ return true;
+
+} // JPEG_CheckFormat
+
+// =================================================================================================
+// JPEG_MetaHandler::JPEG_MetaHandler
+// ==================================
+
+JPEG_MetaHandler::JPEG_MetaHandler ( XMPFiles * _parent )
+ : exifMgr(0), psirMgr(0), iptcMgr(0), skipReconcile(false)
+{
+ this->parent = _parent;
+ this->handlerFlags = kJPEG_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+} // JPEG_MetaHandler::JPEG_MetaHandler
+
+// =================================================================================================
+// JPEG_MetaHandler::~JPEG_MetaHandler
+// ===================================
+
+JPEG_MetaHandler::~JPEG_MetaHandler()
+{
+
+ if ( exifMgr != 0 ) delete ( exifMgr );
+ if ( psirMgr != 0 ) delete ( psirMgr );
+ if ( iptcMgr != 0 ) delete ( iptcMgr );
+
+} // JPEG_MetaHandler::~JPEG_MetaHandler
+
+// =================================================================================================
+// JPEG_MetaHandler::CacheFileData
+// ===============================
+//
+// Look for the Exif metadata, Photoshop image resources, and XMP in a JPEG (JFIF) file. The native
+// thumbnail is inside the Exif. The general layout of a JPEG file is:
+// SOI marker, 2 bytes, 0xFFD8
+// Marker segments for tables and metadata
+// SOFn marker segment
+// Image data
+// EOI marker, 2 bytes, 0xFFD9
+//
+// Each marker segment begins with a 2 byte big endian marker and a 2 byte big endian length. The
+// length includes the 2 bytes of the length field but not the marker. The high order byte of a
+// marker is 0xFF, the low order byte tells what kind of marker. A marker can be preceeded by any
+// number of 0xFF fill bytes, however there are no alignment constraints.
+//
+// There are virtually no constraints on the order of the marker segments before the SOFn. A reader
+// must be prepared to handle any order.
+//
+// The Exif metadata is in an APP1 marker segment with a 6 byte signature string of "Exif\0\0" at
+// the start of the data. The rest of the data is a TIFF stream.
+//
+// The Photoshop image resources are in an APP13 marker segment with a 14 byte signature string of
+// "Photoshop 3.0\0". The rest of the data is a sequence of image resources.
+//
+// The main XMP is in an APP1 marker segment with a 29 byte signature string of
+// "http://ns.adobe.com/xap/1.0/\0". The rest of the data is the serialized XMP packet. This is the
+// only XMP if everything fits within the 64KB limit for marker segment data. If not, there will be
+// a series of XMP extension segments.
+//
+// Each XMP extension segment is an APP1 marker segment whose data contains:
+// - A 35 byte signature string of "http://ns.adobe.com/xmp/extension/\0".
+// - A 128 bit GUID stored as 32 ASCII hex digits, capital A-F, no nul termination.
+// - A 32 bit unsigned integer length for the full extended XMP serialization.
+// - A 32 bit unsigned integer offset for this portion of the extended XMP serialization.
+// - A portion of the extended XMP serialization, up to about 65400 bytes (at most 65458).
+//
+// A reader must be prepared to encounter the extended XMP portions out of order. Also to encounter
+// defective files that have differing extended XMP according to the GUID. The main XMP contains the
+// GUID for the associated extended XMP.
+
+// *** This implementation simply returns when invalid JPEG is encountered. Should we throw instead?
+
+void JPEG_MetaHandler::CacheFileData()
+{
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_PacketInfo & packetInfo = this->packetInfo;
+
+ size_t segLen;
+ bool ok;
+ IOBuffer ioBuf;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ ExtendedXMPInfo extXMP;
+
+ XMP_Assert ( ! this->containsXMP );
+ // Set containsXMP to true here only if the standard XMP packet is found.
+
+ XMP_Assert ( kPSIRSignatureLength == (strlen(kPSIRSignatureString) + 1) );
+ XMP_Assert ( kMainXMPSignatureLength == (strlen(kMainXMPSignatureString) + 1) );
+ XMP_Assert ( kExtXMPSignatureLength == (strlen(kExtXMPSignatureString) + 1) );
+
+ // -------------------------------------------------------------------------------------------
+ // Look for any of the Exif, PSIR, main XMP, or extended XMP marker segments. Quit when we hit
+ // an SOFn, EOI, or invalid/unexpected marker.
+
+ fileRef->Seek ( 2, kXMP_SeekFromStart ); // Skip the SOI. The JPEG header has already been verified.
+ ioBuf.filePos = 2;
+ RefillBuffer ( fileRef, &ioBuf );
+
+ while ( true ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "JPEG_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
+ }
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return;
+
+ if ( *ioBuf.ptr != 0xFF ) return; // All valid markers have a high byte of 0xFF.
+ while ( *ioBuf.ptr == 0xFF ) { // Skip padding 0xFF bytes and the marker's high byte.
+ ++ioBuf.ptr;
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return;
+ }
+
+ XMP_Uns16 marker = 0xFF00 + *ioBuf.ptr;
+
+ if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) break; // Quit reading at the first SOS marker or at EOI.
+
+ if ( (marker == 0xFF01) || // Ill-formed file if we encounter a TEM or RSTn marker.
+ ((0xFFD0 <= marker) && (marker <= 0xFFD7)) ) return;
+
+ if ( marker == 0xFFED ) {
+
+ // This is an APP13 marker, is it the Photoshop image resources?
+
+ ++ioBuf.ptr; // Move ioBuf.ptr to the marker segment length field.
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return;
+
+ segLen = GetUns16BE ( ioBuf.ptr );
+ if ( segLen < 2 ) return; // Invalid JPEG.
+
+ ioBuf.ptr += 2; // Move ioBuf.ptr to the marker segment content.
+ segLen -= 2; // Adjust segLen to count just the content portion.
+
+ ok = CheckFileSpace ( fileRef, &ioBuf, kPSIRSignatureLength );
+ if ( ok && (segLen >= kPSIRSignatureLength) &&
+ CheckBytes ( ioBuf.ptr, kPSIRSignatureString, kPSIRSignatureLength ) ) {
+
+ // This is the Photoshop image resources, cache the contents.
+
+ ioBuf.ptr += kPSIRSignatureLength; // Move ioBuf.ptr to the image resources.
+ segLen -= kPSIRSignatureLength; // Adjust segLen to count just the image resources.
+ ok = CheckFileSpace ( fileRef, &ioBuf, segLen ); // Buffer the full content portion.
+ if ( ! ok ) return; // Must be a truncated file.
+
+ this->psirContents.assign ( (XMP_StringPtr)ioBuf.ptr, segLen );
+ ioBuf.ptr += segLen;
+
+ } else {
+
+ // This is the not Photoshop image resources, skip the marker segment's content.
+
+ if ( segLen <= size_t(ioBuf.limit - ioBuf.ptr) ) {
+ ioBuf.ptr += segLen; // The next marker is in this buffer.
+ } else {
+ // The next marker is beyond this buffer, move to the start of it and fill the buffer.
+ size_t skipCount = segLen - (ioBuf.limit - ioBuf.ptr); // The amount to move beyond this buffer.
+ XMP_Int64 bufferEnd = ioBuf.filePos + (XMP_Int64)ioBuf.len; // File offset at the end of this buffer.
+ XMP_Int64 nextPos = bufferEnd + (XMP_Int64)skipCount;
+ MoveToOffset ( fileRef, nextPos, &ioBuf );
+ }
+
+ }
+
+ continue; // Move on to the next marker.
+
+ } else if ( marker == 0xFFE1 ) {
+
+ // This is an APP1 marker, is it the Exif, main XMP, or extended XMP?
+ // ! Check in that order, which happens to be increasing signature string length.
+
+ ++ioBuf.ptr; // Move ioBuf.ptr to the marker segment length field.
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return;
+
+ segLen = GetUns16BE ( ioBuf.ptr );
+ if ( segLen < 2 ) return; // Invalid JPEG.
+
+ ioBuf.ptr += 2; // Move ioBuf.ptr to the marker segment content.
+ segLen -= 2; // Adjust segLen to count just the content portion.
+
+ // Check for the Exif APP1 marker segment.
+
+ ok = CheckFileSpace ( fileRef, &ioBuf, kExifSignatureLength );
+ if ( ok && (segLen >= kExifSignatureLength) &&
+ (CheckBytes ( ioBuf.ptr, kExifSignatureString, kExifSignatureLength ) ||
+ CheckBytes ( ioBuf.ptr, kExifSignatureAltStr, kExifSignatureLength )) ) {
+
+ // This is the Exif metadata, cache the contents.
+
+ ioBuf.ptr += kExifSignatureLength; // Move ioBuf.ptr to the TIFF stream.
+ segLen -= kExifSignatureLength; // Adjust segLen to count just the TIFF stream.
+ ok = CheckFileSpace ( fileRef, &ioBuf, segLen ); // Buffer the full content portion.
+ if ( ! ok ) return; // Must be a truncated file.
+
+ this->exifContents.assign ( (XMP_StringPtr)ioBuf.ptr, segLen );
+ ioBuf.ptr += segLen;
+
+ continue; // Move on to the next marker.
+
+ }
+
+ // Check for the main XMP APP1 marker segment.
+
+ ok = CheckFileSpace ( fileRef, &ioBuf, kMainXMPSignatureLength );
+ if ( ok && (segLen >= kMainXMPSignatureLength) &&
+ CheckBytes ( ioBuf.ptr, kMainXMPSignatureString, kMainXMPSignatureLength ) ) {
+
+ // This is the main XMP, cache the contents.
+
+ ioBuf.ptr += kMainXMPSignatureLength; // Move ioBuf.ptr to the XMP Packet.
+ segLen -= kMainXMPSignatureLength; // Adjust segLen to count just the XMP Packet.
+ ok = CheckFileSpace ( fileRef, &ioBuf, segLen ); // Buffer the full content portion.
+ if ( ! ok ) return; // Must be a truncated file.
+
+ this->packetInfo.offset = ioBuf.filePos + (ioBuf.ptr - &ioBuf.data[0]);
+ this->packetInfo.length = (XMP_Int32)segLen;
+ this->packetInfo.padSize = 0; // Assume for now, set these properly in ProcessXMP.
+ this->packetInfo.charForm = kXMP_CharUnknown;
+ this->packetInfo.writeable = true;
+
+ this->xmpPacket.assign ( (XMP_StringPtr)ioBuf.ptr, segLen );
+ ioBuf.ptr += segLen; // ! Set this->packetInfo.offset first!
+
+ this->containsXMP = true; // Found the standard XMP packet.
+ continue; // Move on to the next marker.
+
+ }
+
+ // Check for an extension XMP APP1 marker segment.
+
+ ok = CheckFileSpace ( fileRef, &ioBuf, kExtXMPPrefixLength ); // ! The signature, GUID, length, and offset.
+ if ( ok && (segLen >= kExtXMPPrefixLength) &&
+ CheckBytes ( ioBuf.ptr, kExtXMPSignatureString, kExtXMPSignatureLength ) ) {
+
+ // This is a portion of the extended XMP, cache the contents. This is complicated by
+ // the need to tolerate files where the extension portions are not in order. The
+ // local ExtendedXMPInfo map uses the GUID as the key and maps that to a struct that
+ // has the full length and a map of the known portions. This known portion map uses
+ // the offset of the portion as the key and maps that to a string. Only fully seen
+ // extended XMP streams are kept, the right one gets picked in ProcessXMP.
+
+ segLen -= kExtXMPPrefixLength; // Adjust segLen to count just the XMP stream portion.
+
+ ioBuf.ptr += kExtXMPSignatureLength; // Move ioBuf.ptr to the GUID.
+ GUID_32 guid;
+ XMP_Assert ( sizeof(guid.data) == 32 );
+ memcpy ( &guid.data[0], ioBuf.ptr, sizeof(guid.data) ); // AUDIT: Use of sizeof(guid.data) is safe.
+
+ ioBuf.ptr += 32; // Move ioBuf.ptr to the length and offset.
+ XMP_Uns32 fullLen = GetUns32BE ( ioBuf.ptr );
+ XMP_Uns32 offset = GetUns32BE ( ioBuf.ptr+4 );
+
+ ioBuf.ptr += 8; // Move ioBuf.ptr to the XMP stream portion.
+
+ #if Trace_UnlimitedJPEG
+ printf ( "New extended XMP portion: fullLen %d, offset %d, GUID %.32s\n", fullLen, offset, guid.data );
+ #endif
+
+ // Find the ExtXMPContent for this GUID, and the string for this portion's offset.
+
+ ExtendedXMPInfo::iterator guidPos = extXMP.find ( guid );
+ if ( guidPos == extXMP.end() ) {
+ ExtXMPContent newExtContent ( fullLen );
+ guidPos = extXMP.insert ( extXMP.begin(), ExtendedXMPInfo::value_type ( guid, newExtContent ) );
+ }
+
+ ExtXMPPortions::iterator offsetPos;
+ ExtXMPContent & extContent = guidPos->second;
+
+ if ( extContent.portions.empty() ) {
+ // When new create a full size offset 0 string, to which all in-order portions will get appended.
+ offsetPos = extContent.portions.insert ( extContent.portions.begin(),
+ ExtXMPPortions::value_type ( 0, std::string() ) );
+ offsetPos->second.reserve ( extContent.length );
+ }
+
+ // Try to append this portion to a logically contiguous preceeding one.
+
+ if ( offset == 0 ) {
+ offsetPos = extContent.portions.begin();
+ XMP_Assert ( (offsetPos->first == 0) && (offsetPos->second.size() == 0) );
+ } else {
+ offsetPos = extContent.portions.lower_bound ( offset );
+ --offsetPos; // Back up to the portion whose offset is less than the new offset.
+ if ( (offsetPos->first + offsetPos->second.size()) != offset ) {
+ // Can't append, create a new portion.
+ offsetPos = extContent.portions.insert ( extContent.portions.begin(),
+ ExtXMPPortions::value_type ( offset, std::string() ) );
+ }
+ }
+
+ // Cache this portion of the extended XMP.
+
+ std::string & extPortion = offsetPos->second;
+ ok = CheckFileSpace ( fileRef, &ioBuf, segLen ); // Buffer the full content portion.
+ if ( ! ok ) return; // Must be a truncated file.
+ extPortion.append ( (XMP_StringPtr)ioBuf.ptr, segLen );
+ ioBuf.ptr += segLen;
+
+ continue; // Move on to the next marker.
+
+ }
+
+ // If we get here this is some other uninteresting APP1 marker segment, skip it.
+
+ if ( segLen <= size_t(ioBuf.limit - ioBuf.ptr) ) {
+ ioBuf.ptr += segLen; // The next marker is in this buffer.
+ } else {
+ // The next marker is beyond this buffer, move to the start of it and fill the buffer.
+ size_t skipCount = segLen - (ioBuf.limit - ioBuf.ptr); // The amount to move beyond this buffer.
+ XMP_Int64 bufferEnd = ioBuf.filePos + (XMP_Int64)ioBuf.len; // File offset at the end of this buffer.
+ XMP_Int64 nextPos = bufferEnd + (XMP_Int64)skipCount;
+ MoveToOffset ( fileRef, nextPos, &ioBuf );
+ }
+
+ } else {
+
+ // This is a non-terminating but uninteresting marker segment. Skip it.
+
+ ++ioBuf.ptr; // Move ioBuf.ptr to the marker segment length field.
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return;
+
+ segLen = GetUns16BE ( ioBuf.ptr ); // Remember that the length includes itself.
+ if ( segLen < 2 ) return; // Invalid JPEG.
+
+ if ( segLen <= size_t(ioBuf.limit - ioBuf.ptr) ) {
+ ioBuf.ptr += segLen; // The next marker is in this buffer.
+ } else {
+ // The next marker is beyond this buffer, move to the start of it and fill the buffer.
+ size_t skipCount = segLen - (ioBuf.limit - ioBuf.ptr); // The amount to move beyond this buffer.
+ XMP_Int64 bufferEnd = ioBuf.filePos + (XMP_Int64)ioBuf.len; // File offset at the end of this buffer.
+ XMP_Int64 nextPos = bufferEnd + (XMP_Int64)skipCount;
+ MoveToOffset ( fileRef, nextPos, &ioBuf );
+ }
+
+ continue; // Move on to the next marker.
+
+ }
+
+ }
+
+ if ( ! extXMP.empty() ) {
+
+ // We have extended XMP. Find out which ones are complete, collapse them into a single
+ // string, and save them for ProcessXMP.
+
+ ExtendedXMPInfo::iterator guidPos = extXMP.begin();
+ ExtendedXMPInfo::iterator guidEnd = extXMP.end();
+
+ for ( ; guidPos != guidEnd; ++guidPos ) {
+
+ ExtXMPContent & thisContent = guidPos->second;
+ ExtXMPPortions::iterator partZero = thisContent.portions.begin();
+ ExtXMPPortions::iterator partEnd = thisContent.portions.end();
+ ExtXMPPortions::iterator partPos = partZero;
+
+ #if Trace_UnlimitedJPEG
+ printf ( "Extended XMP portions for GUID %.32s, full length %d\n",
+ guidPos->first.data, guidPos->second.length );
+ printf ( " Offset %d, length %d, next offset %d\n",
+ partZero->first, partZero->second.size(), (partZero->first + partZero->second.size()) );
+ #endif
+
+ for ( ++partPos; partPos != partEnd; ++partPos ) {
+ #if Trace_UnlimitedJPEG
+ printf ( " Offset %d, length %d, next offset %d\n",
+ partPos->first, partPos->second.size(), (partPos->first + partPos->second.size()) );
+ #endif
+ if ( partPos->first != partZero->second.size() ) break; // Quit if not contiguous.
+ partZero->second.append ( partPos->second );
+ }
+
+ if ( (partPos == partEnd) && (partZero->first == 0) && (partZero->second.size() == thisContent.length) ) {
+ // This is a complete extended XMP stream.
+ this->extendedXMP.insert ( ExtendedXMPMap::value_type ( guidPos->first, partZero->second ) );
+ #if Trace_UnlimitedJPEG
+ printf ( "Full extended XMP for GUID %.32s, full length %d\n",
+ guidPos->first.data, partZero->second.size() );
+ #endif
+ }
+
+ }
+
+ }
+
+} // JPEG_MetaHandler::CacheFileData
+
+// =================================================================================================
+// TrimFullExifAPP1
+// ================
+//
+// Try to trim trailing padding from full Exif APP1 segment written by some Nikon cameras. Do a
+// temporary read-only parse of the Exif APP1 contents, determine the highest used offset, trim the
+// padding if all zero bytes.
+
+static const char * IFDNames[] = { "Primary", "TNail", "Exif", "GPS", "Interop", };
+
+static void TrimFullExifAPP1 ( std::string * exifContents )
+{
+ TIFF_MemoryReader tempMgr;
+ TIFF_MemoryReader::TagInfo tagInfo;
+ bool tagFound, isNikon;
+
+ // ! Make a copy of the data to parse! The RO memory TIFF manager will flip bytes in-place!
+ tempMgr.ParseMemoryStream ( exifContents->data(), (XMP_Uns32)exifContents->size(), true /* copy data */ );
+
+ // Only trim the Exif APP1 from Nikon cameras.
+ tagFound = tempMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_Make, &tagInfo );
+ isNikon = tagFound && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count >= 5) &&
+ (memcmp ( tagInfo.dataPtr, "NIKON", 5) == 0);
+ if ( ! isNikon ) return;
+
+ // Find the start of the padding, one past the highest used offset. Look at the IFD structure,
+ // and the thumbnail info. Ignore the MakerNote tag, Nikon says they are self-contained.
+
+ XMP_Uns32 padOffset = 0;
+
+ for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
+
+ TIFF_MemoryReader::TagInfoMap tagMap;
+ bool ifdFound = tempMgr.GetIFD ( ifd, &tagMap );
+ if ( ! ifdFound ) continue;
+
+ TIFF_MemoryReader::TagInfoMap::const_iterator mapPos = tagMap.begin();
+ TIFF_MemoryReader::TagInfoMap::const_iterator mapEnd = tagMap.end();
+
+ for ( ; mapPos != mapEnd; ++mapPos ) {
+ const TIFF_MemoryReader::TagInfo & tagInfo = mapPos->second;
+ XMP_Uns32 tagEnd = tempMgr.GetValueOffset ( ifd, tagInfo.id ) + tagInfo.dataLen;
+ if ( tagEnd > padOffset ) padOffset = tagEnd;
+ }
+
+ }
+
+ tagFound = tempMgr.GetTag ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat, &tagInfo );
+ if ( tagFound ) {
+ XMP_Uns32 tnailOffset = tempMgr.GetUns32 ( tagInfo.dataPtr );
+ tagFound = tempMgr.GetTag ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormatLength, &tagInfo );
+ if ( ! tagFound ) return; // Don't trim if there is a TNail offset but no length.
+ tnailOffset += tempMgr.GetUns32 ( tagInfo.dataPtr );
+ if ( tnailOffset > padOffset ) padOffset = tnailOffset;
+ }
+
+ // Decide if it is OK to trim the Exif segment. It is OK if the padding is all zeros. It is OK
+ // if the last non-zero byte is no more than 64 bytes into the padding and there are at least
+ // an additional 64 bytes of padding after it.
+
+ if ( padOffset >= exifContents->size() ) return; // Sanity check for an OK last used offset.
+
+ size_t lastNonZero = exifContents->size() - 1;
+ while ( (lastNonZero >= padOffset) && ((*exifContents)[lastNonZero] == 0) ) --lastNonZero;
+
+ bool ok = lastNonZero < padOffset;
+ if ( ! ok ) {
+ size_t nzSize = lastNonZero - padOffset + 1;
+ size_t finalSize = (exifContents->size() - 1) - lastNonZero;
+ if ( (nzSize < 64) && (finalSize > 64) ) {
+ padOffset = lastNonZero + 64;
+ assert ( padOffset < exifContents->size() );
+ ok = true;
+ }
+ }
+
+ if ( ok ) exifContents->erase ( padOffset );
+
+} // TrimFullExifAPP1
+
+// =================================================================================================
+// JPEG_MetaHandler::ProcessXMP
+// ============================
+//
+// Process the raw XMP and legacy metadata that was previously cached.
+
+void JPEG_MetaHandler::ProcessXMP()
+{
+
+ XMP_Assert ( ! this->processedXMP );
+ this->processedXMP = true; // Make sure we only come through here once.
+
+ // Create the PSIR and IPTC handlers, even if there is no legacy. They might be needed for updates.
+
+ XMP_Assert ( (this->psirMgr == 0) && (this->iptcMgr == 0) ); // ProcessTNail might create the exifMgr.
+
+ bool readOnly = ((this->parent->openFlags & kXMPFiles_OpenForUpdate) == 0);
+
+ if ( readOnly ) {
+ if ( this->exifMgr == 0 ) this->exifMgr = new TIFF_MemoryReader();
+ this->psirMgr = new PSIR_MemoryReader();
+ this->iptcMgr = new IPTC_Reader(); // ! Parse it later.
+ } else {
+ if ( this->exifContents.size() == (65534 - 2 - 6) ) TrimFullExifAPP1 ( &this->exifContents );
+ if ( this->exifMgr == 0 ) this->exifMgr = new TIFF_FileWriter();
+ this->psirMgr = new PSIR_FileWriter();
+ this->iptcMgr = new IPTC_Writer(); // ! Parse it later.
+ }
+
+ // Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy
+ // import if the XMP packet gets parsing errors.
+
+ TIFF_Manager & exif = *this->exifMgr; // Give the compiler help in recognizing non-aliases.
+ PSIR_Manager & psir = *this->psirMgr;
+ IPTC_Manager & iptc = *this->iptcMgr;
+
+ bool haveExif = (! this->exifContents.empty());
+ if ( haveExif ) {
+ exif.ParseMemoryStream ( this->exifContents.c_str(), (XMP_Uns32)this->exifContents.size() );
+ }
+
+ bool havePSIR = (! this->psirContents.empty());
+ if ( havePSIR ) {
+ psir.ParseMemoryResources ( this->psirContents.c_str(), (XMP_Uns32)this->psirContents.size() );
+ }
+
+ PSIR_Manager::ImgRsrcInfo iptcInfo;
+ bool haveIPTC = false;
+ if ( havePSIR ) haveIPTC = psir.GetImgRsrc ( kPSIR_IPTC, &iptcInfo );;
+ int iptcDigestState = kDigestMatches;
+
+ if ( haveIPTC ) {
+
+ bool haveDigest = false;
+ PSIR_Manager::ImgRsrcInfo digestInfo;
+ if ( havePSIR ) haveDigest = psir.GetImgRsrc ( kPSIR_IPTCDigest, &digestInfo );
+ if ( digestInfo.dataLen != 16 ) haveDigest = false;
+
+ if ( ! haveDigest ) {
+ iptcDigestState = kDigestMissing;
+ } else {
+ iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, iptcInfo.dataLen, digestInfo.dataPtr );
+ }
+
+ }
+
+ XMP_OptionBits options = 0;
+ if ( this->containsXMP ) options |= k2XMP_FileHadXMP;
+ if ( haveExif ) options |= k2XMP_FileHadExif;
+ if ( haveIPTC ) options |= k2XMP_FileHadIPTC;
+
+ // Process the main XMP packet. If it fails to parse, do a forced legacy import but still throw
+ // an exception. This tells the caller that an error happened, but gives them recovered legacy
+ // should they want to proceed with that.
+
+ bool haveXMP = false;
+
+ if ( ! this->xmpPacket.empty() ) {
+ XMP_Assert ( this->containsXMP );
+ // Common code takes care of packetInfo.charForm, .padSize, and .writeable.
+ XMP_StringPtr packetStr = this->xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
+ try {
+ this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
+ haveXMP = true;
+ } catch ( ... ) {
+ XMP_ClearOption ( options, k2XMP_FileHadXMP );
+ if ( haveIPTC ) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen );
+ if ( iptcDigestState == kDigestMatches ) iptcDigestState = kDigestMissing;
+ ImportPhotoData ( exif, iptc, psir, iptcDigestState, &this->xmpObj, options );
+ throw; // ! Rethrow the exception, don't absorb it.
+ }
+ }
+
+ // Process the extended XMP if it has a matching GUID.
+
+ if ( ! this->extendedXMP.empty() ) {
+
+ bool found;
+ GUID_32 g32;
+ std::string extGUID, extPacket;
+ ExtendedXMPMap::iterator guidPos = this->extendedXMP.end();
+
+ found = this->xmpObj.GetProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP", &extGUID, 0 );
+ if ( found && (extGUID.size() == sizeof(g32.data)) ) {
+ XMP_Assert ( sizeof(g32.data) == 32 );
+ memcpy ( g32.data, extGUID.c_str(), sizeof(g32.data) ); // AUDIT: Use of sizeof(g32.data) is safe.
+ guidPos = this->extendedXMP.find ( g32 );
+ this->xmpObj.DeleteProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP" ); // ! Must only be in the file.
+ #if Trace_UnlimitedJPEG
+ printf ( "%s extended XMP for GUID %s\n",
+ ((guidPos != this->extendedXMP.end()) ? "Found" : "Missing"), extGUID.c_str() );
+ #endif
+ }
+
+ if ( guidPos != this->extendedXMP.end() ) {
+ try {
+ XMP_StringPtr extStr = guidPos->second.c_str();
+ XMP_StringLen extLen = (XMP_StringLen)guidPos->second.size();
+ SXMPMeta extXMP ( extStr, extLen );
+ SXMPUtils::MergeFromJPEG ( &this->xmpObj, extXMP );
+ } catch ( ... ) {
+ // Ignore failures, let the rest of the XMP and legacy be kept.
+ }
+ }
+
+ }
+
+ // Process the legacy metadata.
+
+ if ( haveIPTC && (! haveXMP) && (iptcDigestState == kDigestMatches) ) iptcDigestState = kDigestMissing;
+ bool parseIPTC = (iptcDigestState != kDigestMatches) || (! readOnly);
+ if ( parseIPTC ) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen );
+ ImportPhotoData ( exif, iptc, psir, iptcDigestState, &this->xmpObj, options );
+
+ this->containsXMP = true; // Assume we had something for the XMP.
+
+} // JPEG_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// JPEG_MetaHandler::UpdateFile
+// ============================
+
+void JPEG_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates.
+
+ XMP_Int64 oldPacketOffset = this->packetInfo.offset;
+ XMP_Int32 oldPacketLength = this->packetInfo.length;
+
+ if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks.
+ if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0;
+
+ bool fileHadXMP = ((oldPacketOffset != 0) && (oldPacketLength != 0));
+
+ // Update the IPTC-IIM and native TIFF/Exif metadata. ExportPhotoData also trips the tiff: and
+ // exif: copies from the XMP, so reserialize the now final XMP packet.
+
+ ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, this->psirMgr );
+
+ try {
+ XMP_OptionBits options = kXMP_UseCompactFormat;
+ if ( fileHadXMP ) options |= kXMP_ExactPacketLength;
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, options, oldPacketLength );
+ } catch ( ... ) {
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+ }
+
+ // Decide whether to do an in-place update. This can only happen if all of the following are true:
+ // - There is a standard packet in the file.
+ // - There is no extended XMP in the file.
+ // - The are no changes to the legacy Exif or PSIR portions. (The IPTC is in the PSIR.)
+ // - The new XMP can fit in the old space, without extensions.
+
+ bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength));
+
+ if ( ! this->extendedXMP.empty() ) doInPlace = false;
+
+ if ( (this->exifMgr != 0) && (this->exifMgr->IsLegacyChanged()) ) doInPlace = false;
+ if ( (this->psirMgr != 0) && (this->psirMgr->IsLegacyChanged()) ) doInPlace = false;
+
+ if ( doInPlace ) {
+
+ #if GatherPerformanceData
+ sAPIPerf->back().extraInfo += ", JPEG in-place update";
+ #endif
+
+ if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) {
+ // They ought to match, cheap to be sure.
+ size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size();
+ this->xmpPacket.append ( extraSpace, ' ' );
+ }
+
+ XMP_IO* liveFile = this->parent->ioRef;
+ std::string & newPacket = this->xmpPacket;
+
+ XMP_Assert ( newPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic.
+
+ liveFile->Seek ( oldPacketOffset, kXMP_SeekFromStart );
+ liveFile->Write ( newPacket.c_str(), (XMP_Int32)newPacket.size() );
+
+ } else {
+
+ #if GatherPerformanceData
+ sAPIPerf->back().extraInfo += ", JPEG copy update";
+ #endif
+
+ XMP_IO* origRef = this->parent->ioRef;
+ XMP_IO* tempRef = origRef->DeriveTemp();
+
+ try {
+ XMP_Assert ( ! this->skipReconcile );
+ this->skipReconcile = true;
+ this->WriteTempFile ( tempRef );
+ this->skipReconcile = false;
+ } catch ( ... ) {
+ this->skipReconcile = false;
+ origRef->DeleteTemp();
+ throw;
+ }
+
+ origRef->AbsorbTemp();
+
+ }
+
+ this->needsUpdate = false;
+
+} // JPEG_MetaHandler::UpdateFile
+
+// =================================================================================================
+// JPEG_MetaHandler::WriteTempFile
+// ===============================
+//
+// The metadata parts of a JPEG file are APP1 marker segments for Exif and XMP, and an APP13 marker
+// segment for Photoshop image resources which contain the IPTC. Corresponding marker segments in
+// the source file are ignored, other parts of the source file are copied. Any initial APP0 marker
+// segments are copied first. Then the new Exif, XMP, and PSIR marker segments are written. Then the
+// rest of the file is copied, skipping the old Exif, XMP, and PSIR. The checking for old metadata
+// stops at the first SOFn marker.
+
+// *** What about Mac resources?
+
+void JPEG_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ XMP_IO* origRef = this->parent->ioRef;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_Uns16 marker;
+ size_t segLen; // ! Must be a size to hold at least 64k+2.
+ IOBuffer ioBuf;
+ XMP_Uns32 first4;
+
+ XMP_Assert ( kIOBufferSize >= (2 + 64*1024) ); // Enough for a marker plus maximum contents.
+
+ if ( origRef->Length() == 0 ) return; // Tolerate empty files.
+ origRef->Rewind();
+ tempRef->Truncate ( 0 );
+
+ if ( ! skipReconcile ) {
+ // Update the IPTC-IIM and native TIFF/Exif metadata, and reserialize the now final XMP packet.
+ ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, this->psirMgr );
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+ }
+
+ RefillBuffer ( origRef, &ioBuf );
+ if ( ! CheckFileSpace ( origRef, &ioBuf, 4 ) ) {
+ XMP_Throw ( "JPEG must have at least SOI and EOI markers", kXMPErr_BadJPEG );
+ }
+
+ marker = GetUns16BE ( ioBuf.ptr );
+ if ( marker != 0xFFD8 ) XMP_Throw ( "Missing SOI marker", kXMPErr_BadJPEG );
+ tempRef->Write ( ioBuf.ptr, 2 );
+ ioBuf.ptr += 2;
+
+ // Copy the leading APP0 marker segments.
+
+ while ( true ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "JPEG_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort );
+ }
+
+ if ( ! CheckFileSpace ( origRef, &ioBuf, 2 ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG );
+ marker = GetUns16BE ( ioBuf.ptr );
+ if ( marker == 0xFFFF ) {
+ tempRef->Write ( ioBuf.ptr, 1 ); // Copy the 0xFF pad byte.
+ ++ioBuf.ptr;
+ continue;
+ }
+
+ if ( marker != 0xFFE0 ) break;
+
+ if ( ! CheckFileSpace ( origRef, &ioBuf, 4 ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG );
+ segLen = GetUns16BE ( ioBuf.ptr+2 );
+ segLen += 2; // ! Don't do above in case machine does 16 bit "+".
+
+ if ( ! CheckFileSpace ( origRef, &ioBuf, segLen ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG );
+ tempRef->Write ( ioBuf.ptr, (XMP_Int32)segLen );
+ ioBuf.ptr += segLen;
+
+ }
+
+ // Write the new Exif APP1 marker segment.
+
+ if ( this->exifMgr != 0 ) {
+
+ void* exifPtr;
+ XMP_Uns32 exifLen = this->exifMgr->UpdateMemoryStream ( &exifPtr );
+ if ( exifLen > kExifMaxDataLength ) exifLen = this->exifMgr->UpdateMemoryStream ( &exifPtr, true /* compact */ );
+ if ( exifLen > kExifMaxDataLength ) {
+ // XMP_Throw ( "Overflow of Exif APP1 data", kXMPErr_BadJPEG ); ** Used to throw, now rewrite original Exif.
+ exifPtr = (void*)this->exifContents.c_str();
+ exifLen = this->exifContents.size();
+ }
+
+ if ( exifLen > 0 ) {
+ first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExifSignatureLength + exifLen );
+ tempRef->Write ( &first4, 4 );
+ tempRef->Write ( kExifSignatureString, kExifSignatureLength );
+ tempRef->Write ( exifPtr, exifLen );
+ }
+
+ }
+
+ // Write the new XMP APP1 marker segment, with possible extension marker segments.
+
+ std::string mainXMP, extXMP, extDigest;
+ SXMPUtils::PackageForJPEG ( this->xmpObj, &mainXMP, &extXMP, &extDigest );
+ XMP_Assert ( (extXMP.size() == 0) || (extDigest.size() == 32) );
+
+ first4 = MakeUns32BE ( 0xFFE10000 + 2 + kMainXMPSignatureLength + (XMP_Uns32)mainXMP.size() );
+ tempRef->Write ( &first4, 4 );
+ tempRef->Write ( kMainXMPSignatureString, kMainXMPSignatureLength );
+ tempRef->Write ( mainXMP.c_str(), (XMP_Int32)mainXMP.size() );
+
+ size_t extPos = 0;
+ size_t extLen = extXMP.size();
+
+ while ( extLen > 0 ) {
+
+ size_t partLen = extLen;
+ if ( partLen > 65000 ) partLen = 65000;
+
+ first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExtXMPPrefixLength + (XMP_Uns32)partLen );
+ tempRef->Write ( &first4, 4 );
+
+ tempRef->Write ( kExtXMPSignatureString, kExtXMPSignatureLength );
+ tempRef->Write ( extDigest.c_str(), (XMP_Int32)extDigest.size() );
+
+ first4 = MakeUns32BE ( (XMP_Int32)extXMP.size() );
+ tempRef->Write ( &first4, 4 );
+ first4 = MakeUns32BE ( (XMP_Int32)extPos );
+ tempRef->Write ( &first4, 4 );
+
+ tempRef->Write ( &extXMP[extPos], (XMP_Int32)partLen );
+
+ extPos += partLen;
+ extLen -= partLen;
+
+ }
+
+ // Write the new PSIR APP13 marker segment.
+
+ if ( this->psirMgr != 0 ) {
+
+ void* psirPtr;
+ XMP_Uns32 psirLen = this->psirMgr->UpdateMemoryResources ( &psirPtr );
+ if ( psirLen > kPSIRMaxDataLength ) XMP_Throw ( "Overflow of PSIR APP13 data", kXMPErr_BadJPEG );
+
+ if ( psirLen > 0 ) {
+ first4 = MakeUns32BE ( 0xFFED0000 + 2 + kPSIRSignatureLength + psirLen );
+ tempRef->Write ( &first4, 4 );
+ tempRef->Write ( kPSIRSignatureString, kPSIRSignatureLength );
+ tempRef->Write ( psirPtr, psirLen );
+ }
+
+ }
+
+ // Copy remaining marker segments, skipping old metadata, to the first SOS marker or to EOI.
+
+ while ( true ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "JPEG_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort );
+ }
+
+ if ( ! CheckFileSpace ( origRef, &ioBuf, 2 ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG );
+ marker = GetUns16BE ( ioBuf.ptr );
+ if ( marker == 0xFFFF ) {
+ tempRef->Write ( ioBuf.ptr, 1 ); // Copy the 0xFF pad byte.
+ ++ioBuf.ptr;
+ continue;
+ }
+
+ if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) break; // Quit at the first SOS marker or at EOI.
+
+ if ( (marker == 0xFF01) || // Ill-formed file if we encounter a TEM or RSTn marker.
+ ((0xFFD0 <= marker) && (marker <= 0xFFD7)) ) {
+ XMP_Throw ( "Unexpected TEM or RSTn marker", kXMPErr_BadJPEG );
+ }
+
+ if ( ! CheckFileSpace ( origRef, &ioBuf, 4 ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG );
+ segLen = GetUns16BE ( ioBuf.ptr+2 );
+
+ if ( ! CheckFileSpace ( origRef, &ioBuf, 2+segLen ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG );
+
+ bool copySegment = true;
+ XMP_Uns8* signaturePtr = ioBuf.ptr + 4;
+
+ if ( marker == 0xFFED ) {
+ if ( (segLen >= kPSIRSignatureLength) &&
+ CheckBytes ( signaturePtr, kPSIRSignatureString, kPSIRSignatureLength ) ) {
+ copySegment = false;
+ }
+ } else if ( marker == 0xFFE1 ) {
+ if ( (segLen >= kExifSignatureLength) &&
+ (CheckBytes ( signaturePtr, kExifSignatureString, kExifSignatureLength ) ||
+ CheckBytes ( signaturePtr, kExifSignatureAltStr, kExifSignatureLength )) ) {
+ copySegment = false;
+ } else if ( (segLen >= kMainXMPSignatureLength) &&
+ CheckBytes ( signaturePtr, kMainXMPSignatureString, kMainXMPSignatureLength ) ) {
+ copySegment = false;
+ } else if ( (segLen >= kExtXMPPrefixLength) &&
+ CheckBytes ( signaturePtr, kExtXMPSignatureString, kExtXMPSignatureLength ) ) {
+ copySegment = false;
+ }
+ }
+
+ if ( copySegment ) tempRef->Write ( ioBuf.ptr, (XMP_Int32)(2+segLen) );
+
+ ioBuf.ptr += 2+segLen;
+
+ }
+
+ // Copy the remainder of the source file.
+
+ size_t bufTail = ioBuf.len - (ioBuf.ptr - &ioBuf.data[0]);
+ tempRef->Write ( ioBuf.ptr, (XMP_Int32)bufTail );
+ ioBuf.ptr += bufTail;
+
+ while ( true ) {
+ RefillBuffer ( origRef, &ioBuf );
+ if ( ioBuf.len == 0 ) break;
+ tempRef->Write ( ioBuf.ptr, (XMP_Int32)ioBuf.len );
+ ioBuf.ptr += ioBuf.len;
+ }
+
+ this->needsUpdate = false;
+
+} // JPEG_MetaHandler::WriteTempFile
diff --git a/XMPFiles/source/FileHandlers/JPEG_Handler.hpp b/XMPFiles/source/FileHandlers/JPEG_Handler.hpp
new file mode 100644
index 0000000..568ee1b
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/JPEG_Handler.hpp
@@ -0,0 +1,98 @@
+#ifndef __JPEG_Handler_hpp__
+#define __JPEG_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! Must be the first #include!
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp"
+#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp"
+#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp"
+
+// =================================================================================================
+/// \file JPEG_Handler.hpp
+/// \brief File format handler for JPEG.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// *** Could derive from Basic_Handler - buffer file tail in a temp file.
+
+extern XMPFileHandler * JPEG_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool JPEG_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO * fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kJPEG_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_AllowsSafeUpdate);
+
+class JPEG_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ struct GUID_32 { // A hack to get an assignment operator for an array.
+ char data [32];
+ void operator= ( const GUID_32 & in )
+ {
+ memcpy ( this->data, in.data, sizeof(this->data) ); // AUDIT: Use of sizeof(this->data) is safe.
+ };
+ bool operator< ( const GUID_32 & right ) const
+ {
+ return (memcmp ( this->data, right.data, sizeof(this->data) ) < 0);
+ };
+ bool operator== ( const GUID_32 & right ) const
+ {
+ return (memcmp ( this->data, right.data, sizeof(this->data) ) == 0);
+ };
+ };
+
+ JPEG_MetaHandler ( XMPFiles * parent );
+ virtual ~JPEG_MetaHandler();
+
+private:
+
+ JPEG_MetaHandler() : exifMgr(0), psirMgr(0), iptcMgr(0), skipReconcile(false) {}; // Hidden on purpose.
+
+ std::string exifContents;
+ std::string psirContents;
+
+ TIFF_Manager * exifMgr; // The Exif manager will be created by ProcessTNail or ProcessXMP.
+ PSIR_Manager * psirMgr; // Need to use pointers so we can properly select between read-only and
+ IPTC_Manager * iptcMgr; // read-write modes of usage.
+
+ bool skipReconcile; // ! Used between UpdateFile and WriteFile.
+
+ typedef std::map < GUID_32, std::string > ExtendedXMPMap;
+
+ ExtendedXMPMap extendedXMP; // ! Only contains those with complete data.
+
+}; // JPEG_MetaHandler
+
+// =================================================================================================
+
+#endif /* __JPEG_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/MP3_Handler.cpp b/XMPFiles/source/FileHandlers/MP3_Handler.cpp
new file mode 100644
index 0000000..47f3aae
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/MP3_Handler.cpp
@@ -0,0 +1,748 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/MP3_Handler.hpp"
+
+// =================================================================================================
+/// \file MP3_Handler.cpp
+/// \brief MP3 handler class.
+// =================================================================================================
+
+// =================================================================================================
+// Helper structs and private routines
+// ====================
+struct ReconProps {
+ const char* mainID; // The stored v2.3 and v2.4 ID, also used as the main logical ID.
+ const char* v22ID; // The stored v2.2 ID.
+ const char* ns;
+ const char* prop;
+};
+
+const static XMP_Uns32 XMP_V23_ID = 0x50524956; // PRIV
+const static XMP_Uns32 XMP_V22_ID = 0x50525600; // PRV
+
+const static ReconProps reconProps[] = {
+ { "TPE1", "TP1", kXMP_NS_DM, "artist" },
+ { "TALB", "TAL", kXMP_NS_DM, "album" },
+ { "TRCK", "TRK", kXMP_NS_DM, "trackNumber" },
+ // exceptions that need attention:
+ { "TCON", "TCO", kXMP_NS_DM, "genre" }, // genres might be numeric
+ { "TIT2", "TT2", kXMP_NS_DC, "title" }, // ["x-default"] language alternatives
+ { "COMM", "COM", kXMP_NS_DM, "logComment" }, // two distinct strings, language alternative
+
+ { "TYER", "TYE", kXMP_NS_XMP, "CreateDate" }, // Year (YYYY) Deprecated in 2.4
+ { "TDAT", "TDA", kXMP_NS_XMP, "CreateDate" }, // Date (DDMM) Deprecated in 2.4
+ { "TIME", "TIM", kXMP_NS_XMP, "CreateDate" }, // Time (HHMM) Deprecated in 2.4
+ { "TDRC", "", kXMP_NS_XMP, "CreateDate" }, // assembled date/time v2.4
+
+ // new reconciliations introduced in Version 5
+ { "TCMP", "TCP", kXMP_NS_DM, "partOfCompilation" }, // presence/absence of TCMP frame dedides
+ { "USLT", "ULT", kXMP_NS_DM, "lyrics" },
+ { "TCOM", "TCM", kXMP_NS_DM, "composer" },
+ { "TPOS", "TPA", kXMP_NS_DM, "discNumber" }, // * a text field! might contain "/<total>"
+ { "TCOP", "TCR", kXMP_NS_DC, "rights" }, // ["x-default"] language alternatives
+ { "TPE4", "TP4", kXMP_NS_DM, "engineer" },
+ { "WCOP", "WCP", kXMP_NS_XMP_Rights, "WebStatement" },
+
+ { 0, 0, 0, 0 } // must be last as a sentinel
+};
+
+// =================================================================================================
+// MP3_MetaHandlerCTor
+// ====================
+
+XMPFileHandler * MP3_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new MP3_MetaHandler ( parent );
+}
+
+// =================================================================================================
+// MP3_CheckFormat
+// ===============
+
+// For MP3 we check parts .... See the MP3 spec for offset info.
+
+bool MP3_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* file,
+ XMPFiles * parent )
+{
+ IgnoreParam(filePath); IgnoreParam(parent); //supress warnings
+ XMP_Assert ( format == kXMP_MP3File ); //standard assert
+
+ if ( file->Length() < 10 ) return false;
+ file ->Rewind();
+
+ XMP_Uns8 header[3];
+ file->ReadAll ( header, 3 );
+ if ( ! CheckBytes( &header[0], "ID3", 3 ) ) return (parent->format == kXMP_MP3File);
+
+ XMP_Uns8 major = XIO::ReadUns8( file );
+ XMP_Uns8 minor = XIO::ReadUns8( file );
+
+ if ( (major < 2) || (major > 4) || (minor == 0xFF) ) return false;
+
+ XMP_Uns8 flags = XIO::ReadUns8 ( file );
+
+ //TODO
+ if ( flags & 0x10 ) XMP_Throw ( "no support for MP3 with footer", kXMPErr_Unimplemented );
+ if ( flags & 0x80 ) return false; //no support for unsynchronized MP3 (as before, also see [1219125])
+ if ( flags & 0x0F ) XMP_Throw ( "illegal header lower bits", kXMPErr_Unimplemented );
+
+ XMP_Uns32 size = XIO::ReadUns32_BE ( file );
+ if ( (size & 0x80808080) != 0 ) return false; //if any bit survives -> not a valid synchsafe 32 bit integer
+
+ return true;
+
+} // MP3_CheckFormat
+
+
+// =================================================================================================
+// MP3_MetaHandler::MP3_MetaHandler
+// ================================
+
+MP3_MetaHandler::MP3_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kMP3_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+}
+
+// =================================================================================================
+// MP3_MetaHandler::~MP3_MetaHandler
+// =================================
+
+MP3_MetaHandler::~MP3_MetaHandler()
+{
+ // free frames
+ ID3v2Frame* curFrame;
+ while ( !this->framesVector.empty() ) {
+ curFrame = this->framesVector.back();
+ delete curFrame;
+ framesVector.pop_back();
+ }
+}
+
+// =================================================================================================
+// MP3_MetaHandler::CacheFileData
+// ==============================
+
+void MP3_MetaHandler::CacheFileData()
+{
+
+ //*** abort procedures
+ this->containsXMP = false; //assume no XMP for now
+
+ XMP_IO* file = this->parent->ioRef;
+ XMP_PacketInfo &packetInfo = this->packetInfo;
+
+ file->Rewind();
+
+ this->hasID3Tag = this->id3Header.read( file );
+ this->majorVersion = this->id3Header.fields[ID3Header::o_vMajor];
+ this->minorVersion = this->id3Header.fields[ID3Header::o_vMinor];
+ this->hasExtHeader = (0 != ( 0x40 & this->id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag
+ this->hasFooter = ( 0 != ( 0x10 & this->id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag
+
+ // stored size is w/o initial header (thus adding 10)
+ // + but extended header (if existing)
+ // + padding + frames after unsynchronisation (?)
+ // (if no ID3 tag existing, constructed default correctly sets size to 10.)
+ this->oldTagSize = ID3Header::kID3_TagHeaderSize + synchToInt32(GetUns32BE( &id3Header.fields[ID3Header::o_size] ));
+
+ if ( ! hasExtHeader ) {
+
+ this->extHeaderSize = 0; // := there is no such header.
+
+ } else {
+
+ this->extHeaderSize = synchToInt32( XIO::ReadInt32_BE( file));
+ XMP_Uns8 extHeaderNumFlagBytes = XIO::ReadUns8( file );
+
+ // v2.3 doesn't include the size, while v2.4 does
+ if ( this->majorVersion < 4 ) this->extHeaderSize += 4;
+ XMP_Validate( this->extHeaderSize >= 6, "extHeader size too small", kXMPErr_BadFileFormat );
+
+ file->Seek ( this->extHeaderSize - 6, kXMP_SeekFromCurrent );
+
+ }
+
+ this->framesVector.clear(); //mac precaution
+ ID3v2Frame* curFrame = 0; // reusable
+
+ ////////////////////////////////////////////////////
+ // read frames
+
+ XMP_Uns32 xmpID = XMP_V23_ID;
+ if ( this->majorVersion == 2 ) xmpID = XMP_V22_ID;
+
+ while ( file->Offset() < this->oldTagSize ) {
+
+ curFrame = new ID3v2Frame();
+
+ try {
+ XMP_Int64 frameSize = curFrame->read ( file, this->majorVersion );
+ if ( frameSize == 0 ) {
+ delete curFrame; // ..since not becoming part of vector for latter delete.
+ break; // not a throw. There's nothing wrong with padding.
+ }
+ this->containsXMP = true;
+ } catch ( ... ) {
+ delete curFrame;
+ throw;
+ }
+
+ // these are both pointer assignments, no (copy) construction
+ // (MemLeak Note: for all things pushed, memory cleanup is taken care of in destructor.)
+ this->framesVector.push_back ( curFrame );
+
+ //remember XMP-Frame, if it occurs:
+ if ( (curFrame->id ==xmpID) &&
+ (curFrame->contentSize > 8) && CheckBytes ( &curFrame->content[0], "XMP\0", 4 ) ) {
+
+ // be sure that this is the first packet (all else would be illegal format)
+ XMP_Validate ( this->framesMap[xmpID] == 0, "two XMP packets in one file", kXMPErr_BadFileFormat );
+ //add this to map, needed on reconciliation
+ this->framesMap[xmpID] = curFrame;
+
+ this->packetInfo.length = curFrame->contentSize - 4; // content minus "XMP\0"
+ this->packetInfo.offset = ( file->Offset() - this->packetInfo.length );
+
+ this->xmpPacket.erase(); //safety
+ this->xmpPacket.assign( &curFrame->content[4], curFrame->contentSize - 4 );
+ this->containsXMP = true; // do this last, after all possible failure
+
+ }
+
+ // No space for another frame? => assume into ID3v2.4 padding.
+ XMP_Int64 newPos = file->Offset();
+ XMP_Int64 spaceLeft = this->oldTagSize - newPos; // Depends on first check below!
+ if ( (newPos > this->oldTagSize) || (spaceLeft < ID3Header::kID3_TagHeaderSize) ) break;
+
+ }
+
+ ////////////////////////////////////////////////////
+ // padding
+
+ this->oldPadding = this->oldTagSize - file->Offset();
+ this->oldFramesSize = this->oldTagSize - ID3Header::kID3_TagHeaderSize - this->oldPadding;
+
+ XMP_Validate ( (this->oldPadding >= 0), "illegal oldTagSize or padding value", kXMPErr_BadFileFormat );
+
+ for ( XMP_Int64 i = this->oldPadding; i > 0; ) {
+ if ( i >= 8 ) {
+ if ( XIO::ReadInt64_BE(file) != 0 ) XMP_Throw ( "padding not nulled out", kXMPErr_BadFileFormat );
+ i -= 8;
+ continue;
+ }
+ if ( XIO::ReadUns8(file) != 0) XMP_Throw ( "padding(2) not nulled out", kXMPErr_BadFileFormat );
+ i--;
+ }
+
+ //// read ID3v1 tag
+ if ( ! this->containsXMP ) this->containsXMP = id3v1Tag.read ( file, &this->xmpObj );
+
+} // MP3_MetaHandler::CacheFileData
+
+
+// =================================================================================================
+// MP3_MetaHandler::ProcessXMP
+// ===========================
+//
+// Process the raw XMP and legacy metadata that was previously cached.
+
+void MP3_MetaHandler::ProcessXMP()
+{
+
+ // Process the XMP packet.
+ if ( ! this->xmpPacket.empty() ) {
+ XMP_Assert ( this->containsXMP );
+ XMP_StringPtr packetStr = this->xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen) this->xmpPacket.size();
+ this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
+ this->processedXMP = true;
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ // assumptions on presence-absence "flag tags"
+ // ( unless no xmp whatsoever present )
+ if ( ! this->xmpPacket.empty() ) this->xmpObj.SetProperty ( kXMP_NS_DM, "partOfCompilation", "false" );
+
+ ////////////////////////////////////////////////////////////////////
+ // import of legacy properties
+ ID3v2Frame* curFrame;
+ XMP_Bool hasTDRC = false;
+ XMP_DateTime newDateTime;
+
+ if ( this->hasID3Tag ) { // otherwise pretty pointless...
+
+ for ( int r = 0; reconProps[r].mainID != 0; ++r ) {
+
+ //get the frame ID to look for
+ XMP_Uns32 logicalID = GetUns32BE ( reconProps[r].mainID );
+ XMP_Uns32 storedID = logicalID;
+ if ( this->majorVersion == 2 ) storedID = GetUns32BE ( reconProps[r].v22ID );
+
+ // deal with each such frame in the frameVector
+ // (since there might be several, some of them not applicable, i.e. COMM)
+
+ vector<ID3_Support::ID3v2Frame*>::iterator it;
+ for ( it = this->framesVector.begin(); it != this->framesVector.end(); ++it ) {
+
+ curFrame = *it;
+ if ( storedID != curFrame->id ) continue;
+
+ // go deal with it!
+ // get the property
+ std::string utf8string;
+ bool result = curFrame->getFrameValue ( this->majorVersion, logicalID, &utf8string );
+ if ( ! result ) continue; //ignore but preserve this frame (i.e. not applicable COMM frame)
+
+ //////////////////////////////////////////////////////////////////////////////////
+ // if we come as far as here, it's proven that there's a relevant XMP property
+
+ this->containsXMP = true;
+
+ ID3_Support::ID3v2Frame* t = this->framesMap [ storedID ];
+ if ( t != 0 ) t->active = false;
+
+ // add this to map (needed on reconciliation)
+ // note: above code reaches, that COMM/USLT frames
+ // only then reach this map, if they are 'eng'(lish)
+ // multiple occurences indeed leads to last one survives
+ // ( in this map, all survive in the file )
+ this->framesMap [ storedID ] = curFrame;
+
+ // now write away as needed;
+ // merely based on existence, relevant even if empty:
+ if ( logicalID == 0x54434D50) { // TCMP if exists: part of compilation
+
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "partOfCompilation", "true" );
+
+ } else if ( ! utf8string.empty() ) {
+
+ switch ( logicalID ) {
+
+ case 0x54495432: // TIT2 -> title["x-default"]
+ case 0x54434F50: // TCOP -> rights["x-default"]
+ this->xmpObj.SetLocalizedText ( reconProps[r].ns, reconProps[r].prop,"", "x-default", utf8string );
+ break;
+
+ case 0x54434F4E: // TCON -> genre ( might be numeric string. prior to 2.3 a one-byte numeric value? )
+ {
+ XMP_Int32 pos = 0; // going through input string
+ if ( utf8string[pos] == '(' ) { // number in brackets?
+ pos++;
+ XMP_Uns8 iGenre = (XMP_Uns8) atoi( &utf8string.c_str()[1] );
+ if ( iGenre < 127 ) {
+ utf8string.assign( Genres[iGenre] );
+ } else {
+ utf8string.assign( Genres[12] ); // "Other"
+ }
+ } else {
+ // Text, let's "try" to find it anyway (for best upper/lower casing)
+ int i;
+ const char* genreCString = utf8string.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 )) {
+ utf8string.assign ( Genres[i] ); // found, let's use the one in the list
+ break;
+ }
+ }
+ // otherwise (if for-loop runs through): leave as is
+ }
+ // write out property (derived or direct, but certainly non-numeric)
+ this->xmpObj.SetProperty ( reconProps[r].ns, reconProps[r].prop, utf8string );
+ }
+ break;
+
+ case 0x54594552: // TYER -> xmp:CreateDate.year
+ {
+ try { // Don't let wrong dates in id3 stop import.
+ if ( ! hasTDRC ) {
+ newDateTime.year = SXMPUtils::ConvertToInt ( utf8string );
+ newDateTime.hasDate = true;
+ }
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ }
+ break;
+ }
+
+ case 0x54444154: //TDAT -> xmp:CreateDate.month and day
+ {
+ try { // Don't let wrong dates in id3 stop import.
+ // only if no TDRC has been found before
+ //&& must have the format DDMM
+ if ( (! hasTDRC) && (utf8string.length() == 4) ) {
+ newDateTime.day = SXMPUtils::ConvertToInt (utf8string.substr(0,2));
+ newDateTime.month = SXMPUtils::ConvertToInt ( utf8string.substr(2,2));
+ newDateTime.hasDate = true;
+ }
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ }
+ break;
+ }
+
+ case 0x54494D45: //TIME -> xmp:CreateDate.hours and minutes
+ {
+ try { // Don't let wrong dates in id3 stop import.
+ // only if no TDRC has been found before
+ // && must have the format HHMM
+ if ( (! hasTDRC) && (utf8string.length() == 4) ) {
+ newDateTime.hour = SXMPUtils::ConvertToInt (utf8string.substr(0,2));
+ newDateTime.minute = SXMPUtils::ConvertToInt ( utf8string.substr(2,2));
+ newDateTime.hasTime = true;
+ }
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ }
+ break;
+ }
+
+ case 0x54445243: // TDRC -> xmp:CreateDate //id3 v2.4
+ {
+ try { // Don't let wrong dates in id3 stop import.
+ hasTDRC = true;
+ // This always wins over TYER, TDAT and TIME
+ SXMPUtils::ConvertToDate ( utf8string, &newDateTime );
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ }
+ break;
+ }
+
+ default:
+ // NB: COMM/USLT need no special fork regarding language alternatives/multiple occurence.
+ // relevant code forks are in ID3_Support::getFrameValue()
+ this->xmpObj.SetProperty ( reconProps[r].ns, reconProps[r].prop, utf8string );
+ break;
+
+ }//switch
+
+ }
+
+ } //for iterator
+
+ }//for reconProps
+
+ // import DateTime
+ XMP_DateTime oldDateTime;
+ xmpObj.GetProperty_Date ( kXMP_NS_XMP, "CreateDate", &oldDateTime, 0 );
+
+ // NOTE: no further validation nessesary the function "SetProperty_Date" will care about validating date and time
+ // any exception will be caught and block import
+ try {
+ bool haveNewDateTime = (newDateTime.year != 0) &&
+ ( (newDateTime.year != oldDateTime.year) ||
+ ( (newDateTime.month != 0 ) && ( (newDateTime.day != oldDateTime.day) || (newDateTime.month != oldDateTime.month) ) ) ||
+ ( newDateTime.hasTime && ( (newDateTime.hour != oldDateTime.minute) || (newDateTime.hour != oldDateTime.minute) ) ) );
+ if ( haveNewDateTime ) {
+ this->xmpObj.SetProperty_Date ( kXMP_NS_XMP, "CreateDate", newDateTime );
+ }
+ } catch ( ... ) {
+ // Dont import invalid dates from ID3
+ }
+
+ }
+
+ // very important to avoid multiple runs! (in which case I'd need to clean certain
+ // fields (i.e. regarding ->active setting)
+ this->processedXMP = true;
+
+} // MP3_MetaHandler::ProcessXMP
+
+
+// =================================================================================================
+// MP3_MetaHandler::UpdateFile
+// ===========================
+void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( doSafeUpdate ) XMP_Throw ( "MP3_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
+
+ XMP_IO* file = this->parent->ioRef;
+
+ // leave 2.3 resp. 2.4 header, since we want to let alone
+ // and don't know enough about the encoding of unrelated frames...
+ XMP_Assert( this->containsXMP );
+
+ tagIsDirty = false;
+ mustShift = false;
+
+ // write out native properties:
+ // * update existing ones
+ // * create new frames as needed
+ // * delete frames if property is gone!
+ // see what there is to do for us:
+
+ // RECON LOOP START
+ for (int r = 0; reconProps[r].mainID != 0; r++ ) {
+
+ std::string value;
+ bool needDescriptor = false;
+ bool need16LE = true;
+ bool needEncodingByte = true;
+
+ XMP_Uns32 logicalID = GetUns32BE ( reconProps[r].mainID );
+ XMP_Uns32 storedID = logicalID;
+ if ( this->majorVersion == 2 ) storedID = GetUns32BE ( reconProps[r].v22ID );
+
+ ID3v2Frame* frame = framesMap[ storedID ]; // the actual frame (if already existing)
+
+ // get XMP property
+ // * honour specific exceptions
+ // * leave value empty() if it doesn't exist ==> frame must be delete/not created
+ switch ( logicalID ) {
+
+ case 0x54434D50: // TCMP if exists: part of compilation
+ need16LE = false;
+ if ( xmpObj.GetProperty( kXMP_NS_DM, "partOfCompilation", &value, 0 ) && ( 0 == stricmp( value.c_str(), "true" ) )) {
+ value = "1"; // set a TCMP frame of value 1
+ } else {
+ value.erase(); // delete/prevent creation of frame
+ }
+ break;
+
+ case 0x54495432: // TIT2 -> title["x-default"]
+ case 0x54434F50: // TCOP -> rights["x-default"]
+ if (! xmpObj.GetLocalizedText( reconProps[r].ns, reconProps[r].prop, "", "x-default", 0, &value, 0 )) value.erase(); // if not, erase string.
+ break;
+
+ case 0x54434F4E: // TCON -> genre
+ {
+ if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) { // nothing found? -> Erase string. (Leads to Unset below)
+ value.erase();
+ break;
+ }
+ // genre: we need to get the number back, if possible
+ XMP_Int16 iFound = -1; // flag as "not found"
+ for ( int i=0; i < 127; ++i ) {
+ if ( (value.size() == strlen(Genres[i])) && (stricmp( value.c_str(), Genres[i] ) == 0) ) {
+ iFound = i; // Found
+ break;
+ }
+ }
+ if ( iFound == -1 ) break; // stick with the literal value (also for v2.3, since this is common practice!)
+
+ need16LE = false; // no unicode need for (##)
+ char strGenre[64];
+ snprintf ( strGenre, sizeof(strGenre), "(%d)", iFound ); // AUDIT: Using sizeof(strGenre) is safe.
+ value.assign(strGenre);
+ }
+ break;
+
+ case 0x434F4D4D: // COMM
+ case 0x55534C54: // USLT, both need descriptor.
+ needDescriptor = true;
+ if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase();
+ break;
+
+ case 0x54594552: //TYER
+ case 0x54444154: //TDAT
+ case 0x54494D45: //TIME
+ {
+ if ( majorVersion <= 3 ) { // TYER, TIME and TDAT depricated since v. 2.4 -> else use TDRC
+
+ XMP_DateTime dateTime;
+ if (! xmpObj.GetProperty_Date( reconProps[r].ns, reconProps[r].prop, &dateTime, 0 )) { // nothing found? -> Erase string. (Leads to Unset below)
+ value.erase();
+ break;
+ }
+
+ // TYER
+ if ( logicalID == 0x54594552 ) {
+ XMP_Validate( dateTime.year <= 9999 && dateTime.year > 0, "Year is out of range", kXMPErr_BadParam);
+ // get only Year!
+ SXMPUtils::ConvertFromInt( dateTime.year, "", &value );
+ break;
+ } else if ( logicalID == 0x54444154 && dateTime.hasDate ) {
+ std::string day, month;
+ SXMPUtils::ConvertFromInt( dateTime.day, "", &day );
+ SXMPUtils::ConvertFromInt( dateTime.month, "", &month );
+ if ( dateTime.day < 10 )
+ value = "0";
+ value += day;
+ if ( dateTime.month < 10 )
+ value += "0";
+ value += month;
+ break;
+ } else if ( logicalID == 0x54494D45 && dateTime.hasTime ) {
+ std::string hour, minute;
+ SXMPUtils::ConvertFromInt( dateTime.hour, "", &hour );
+ SXMPUtils::ConvertFromInt( dateTime.minute, "", &minute );
+ if ( dateTime.hour < 10 )
+ value = "0";
+ value += hour;
+ if ( dateTime.minute < 10 )
+ value += "0";
+ value += minute;
+ break;
+ } else {
+ value.erase();
+ break;
+ }
+ } else {
+ value.erase();
+ break;
+ }
+ }
+ break;
+
+ case 0x54445243: //TDRC (only v2.4)
+ {
+ // only export for id3 > v2.4
+ if ( majorVersion > 3 ) {
+ if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase();
+ }
+ break;
+ }
+ break;
+
+ case 0x57434F50: //WCOP
+ needEncodingByte = false;
+ need16LE = false;
+ if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase(); // if not, erase string
+ break;
+
+ case 0x5452434B: // TRCK
+ case 0x54504F53: // TPOS
+ need16LE = false;
+ // no break, go on:
+
+ default:
+ if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase(); // if not, erase string
+ break;
+
+ }
+
+ // [XMP exist] x [frame exist] => four cases:
+ // 1/4) nothing before, nothing now
+ if ( value.empty() && (frame==0)) continue; // nothing to do
+
+ // all else means there will be rewrite work to do:
+ tagIsDirty = true;
+
+ // 2/4) value before, now gone:
+ if ( value.empty() && (frame!=0)) {
+ frame->active = false; //mark for non-use
+ continue;
+ }
+
+ // 3/4) no old value, create new value
+ if ( frame != 0 ) {
+ frame->setFrameValue( value, needDescriptor, need16LE, false, needEncodingByte );
+ } else {
+ ID3v2Frame* newFrame=new ID3v2Frame( storedID );
+ newFrame->setFrameValue( value, needDescriptor, need16LE, false, needEncodingByte ); //always write as utf16-le incl. BOM
+ framesVector.push_back( newFrame );
+ framesMap[ storedID ] = newFrame;
+ continue;
+ }
+
+ } // RECON LOOP END
+
+ /////////////////////////////////////////////////////////////////////////////////
+ // (Re)Build XMP frame:
+
+ XMP_Uns32 xmpID = XMP_V23_ID;
+ if ( this->majorVersion == 2 ) xmpID = XMP_V22_ID;
+
+ ID3v2Frame* frame = framesMap[ xmpID ];
+ if ( frame != 0 ) {
+ frame->setFrameValue( this->xmpPacket, false, false, true );
+ } else {
+ ID3v2Frame* newFrame=new ID3v2Frame( xmpID );
+ newFrame->setFrameValue ( this->xmpPacket, false, false, true );
+ framesVector.push_back ( newFrame );
+ framesMap[ xmpID ] = newFrame;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // Decision making
+
+ XMP_Int32 frameHeaderSize = ID3v2Frame::kV23_FrameHeaderSize;
+ if ( this->majorVersion == 2 ) frameHeaderSize = ID3v2Frame::kV22_FrameHeaderSize;
+
+ newFramesSize = 0;
+ for ( XMP_Uns32 i = 0; i < framesVector.size(); i++ ) {
+ if ( framesVector[i]->active ) newFramesSize += (frameHeaderSize + framesVector[i]->contentSize);
+ }
+
+ mustShift = (newFramesSize > (oldTagSize - ID3Header::kID3_TagHeaderSize)) ||
+ //optimization: If more than 8K can be saved by rewriting the MP3, go do it:
+ ((newFramesSize + 8*1024) < oldTagSize );
+
+ if ( ! mustShift ) { // fill what we got
+ newTagSize = oldTagSize;
+ } else { // if need to shift anyway, get some nice 2K padding
+ newTagSize = newFramesSize + 2048 + ID3Header::kID3_TagHeaderSize;
+ }
+ newPadding = newTagSize - ID3Header::kID3_TagHeaderSize - newFramesSize;
+
+ // shifting needed? -> shift
+ if ( mustShift ) {
+ XMP_Int64 filesize = file ->Length();
+ if ( this->hasID3Tag ) {
+ XIO::Move ( file, oldTagSize, file, newTagSize, filesize - oldTagSize ); //fix [2338569]
+ } else {
+ XIO::Move ( file, 0, file, newTagSize, filesize ); // move entire file up.
+ }
+ }
+
+ // correct size stuff, write out header
+ file ->Rewind();
+ id3Header.write ( file, newTagSize );
+
+ // write out tags
+ for ( XMP_Uns32 i = 0; i < framesVector.size(); i++ ) {
+ if ( framesVector[i]->active ) framesVector[i]->write ( file, majorVersion );
+ }
+
+ // write out padding:
+ for ( XMP_Int64 i = newPadding; i > 0; ) {
+ const XMP_Uns64 zero = 0;
+ if ( i >= 8 ) {
+ file->Write ( &zero, 8 );
+ i -= 8;
+ continue;
+ }
+ file->Write ( &zero, 1 );
+ i--;
+ }
+
+ // check end of file for ID3v1 tag
+ XMP_Int64 possibleTruncationPoint = file->Seek ( -128, kXMP_SeekFromEnd );
+ bool alreadyHasID3v1 = (XIO::ReadInt32_BE( file ) & 0xFFFFFF00) == 0x54414700; // "TAG"
+ if ( ! alreadyHasID3v1 ) file->Seek ( 128, kXMP_SeekFromEnd ); // Seek will extend the file.
+ id3v1Tag.write( file, &this->xmpObj );
+
+ this->needsUpdate = false; //do last for safety reasons
+
+} // MP3_MetaHandler::UpdateFile
+
+// =================================================================================================
+// MP3_MetaHandler::WriteTempFile
+// ==============================
+
+void MP3_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ IgnoreParam(tempRef);
+ XMP_Throw ( "MP3_MetaHandler::WriteTempFile: Not supported", kXMPErr_Unimplemented );
+} // MP3_MetaHandler::WriteTempFile
diff --git a/XMPFiles/source/FileHandlers/MP3_Handler.hpp b/XMPFiles/source/FileHandlers/MP3_Handler.hpp
new file mode 100644
index 0000000..261ca3e
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/MP3_Handler.hpp
@@ -0,0 +1,93 @@
+#ifndef __MP3_Handler_hpp__
+#define __MP3_Handler_hpp__ 1
+
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "XMPFiles/source/FormatSupport/ID3_Support.hpp"
+
+using namespace std;
+using namespace ID3_Support;
+
+extern XMPFileHandler * MP3_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool MP3_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kMP3_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket|
+ kXMPFiles_CanReconcile);
+
+class MP3_MetaHandler : public XMPFileHandler
+{
+public:
+ MP3_MetaHandler ( XMPFiles * parent );
+ ~MP3_MetaHandler();
+
+ void CacheFileData();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ void ProcessXMP();
+
+private:
+ ////////////////////////////////////////////////////////////////////////////////////
+ // instance vars
+ XMP_Int64 oldTagSize; // the entire tag, including padding, including 10B header
+ XMP_Int64 oldPadding; // number of padding bytes
+ XMP_Int64 oldFramesSize; // actual space needed by frames := oldTagSize - 10 - oldPadding
+
+ XMP_Int64 newTagSize;
+ XMP_Int64 newPadding;
+ XMP_Int64 newFramesSize;
+
+ // decision making:
+ bool tagIsDirty; // true, if any legacy properties changed.
+ bool mustShift; // entire tag to rewrite? (or possibly just XMP?)
+
+
+ XMP_Uns8 majorVersion, minorVersion; // Version Number post ID3v2, i.e. 3 0 ==> ID3v2.3.0
+ bool hasID3Tag; //incoming file has an ID3 tag?
+ bool hasFooter;
+ bool legacyChanged; // tag rewrite certainly needed?
+
+ ID3Header id3Header;
+
+ XMP_Int64 extHeaderSize;
+ bool hasExtHeader;
+
+ // the frames
+ // * all to be kept till write-out
+ // * parsed/understood only if needed
+ // * vector used to free memory in handler destructor
+ std::vector<ID3_Support::ID3v2Frame*> framesVector;
+
+ // ID3v1 - treated as a single object
+ ID3v1Tag id3v1Tag;
+
+ // * also kept in a map for better import<->export access
+ // * only keeps legacy 'relevant frames' (i.e. no abused COMM frames)
+ // * only keeps last relevant frame
+ // * earlier 'relevant frames' will be deleted. This map also helps in this
+ // * key shall be the FrameID, always interpreted as BE
+ std::map<XMP_Uns32,ID3_Support::ID3v2Frame*> framesMap;
+
+}; // MP3_MetaHandler
+
+// =================================================================================================
+
+#endif /* __MP3_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp b/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp
new file mode 100644
index 0000000..cd8512e
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp
@@ -0,0 +1,220 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2005 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.
+// =================================================================================================
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/MPEG2_Handler.hpp"
+
+using namespace std;
+
+// =================================================================================================
+/// \file MPEG2_Handler.cpp
+/// \brief File format handler for MPEG2.
+///
+/// BLECH! YUCK! GAG! MPEG-2 is done using a sidecar and recognition only by file extension! BARF!!!!!
+///
+// =================================================================================================
+
+// =================================================================================================
+// FindFileExtension
+// =================
+
+static inline XMP_StringPtr FindFileExtension ( XMP_StringPtr filePath )
+{
+
+ XMP_StringPtr pathEnd = filePath + strlen(filePath);
+ XMP_StringPtr extPtr;
+
+ for ( extPtr = pathEnd-1; extPtr > filePath; --extPtr ) {
+ if ( (*extPtr == '.') || (*extPtr == '/') ) break;
+ #if XMP_WinBuild
+ if ( (*extPtr == '\\') || (*extPtr == ':') ) break;
+ #endif
+ }
+
+ if ( (extPtr < filePath) || (*extPtr != '.') ) return pathEnd;
+ return extPtr;
+
+} // FindFileExtension
+
+// =================================================================================================
+// MPEG2_MetaHandlerCTor
+// =====================
+
+XMPFileHandler * MPEG2_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new MPEG2_MetaHandler ( parent );
+
+} // MPEG2_MetaHandlerCTor
+
+// =================================================================================================
+// MPEG2_CheckFormat
+// =================
+
+// The MPEG-2 handler uses just the file extension, not the file content. Worse yet, it also uses a
+// sidecar file for the XMP. This works better if the handler owns the file, we open the sidecar
+// instead of the actual MPEG-2 file.
+
+bool MPEG2_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent )
+{
+ IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(fileRef);
+
+ XMP_Assert ( (format == kXMP_MPEGFile) || (format == kXMP_MPEG2File) );
+ XMP_Assert ( fileRef == 0 );
+
+ return ( (parent->format == kXMP_MPEGFile) || (parent->format == kXMP_MPEG2File) ); // ! Just use the first call's format hint.
+
+} // MPEG2_CheckFormat
+
+// =================================================================================================
+// MPEG2_MetaHandler::MPEG2_MetaHandler
+// ====================================
+
+MPEG2_MetaHandler::MPEG2_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kMPEG2_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+} // MPEG2_MetaHandler::MPEG2_MetaHandler
+
+// =================================================================================================
+// MPEG2_MetaHandler::~MPEG2_MetaHandler
+// =====================================
+
+MPEG2_MetaHandler::~MPEG2_MetaHandler()
+{
+ // Nothing to do.
+
+} // MPEG2_MetaHandler::~MPEG2_MetaHandler
+
+// =================================================================================================
+// MPEG2_MetaHandler::GetFileModDate
+// =================================
+
+bool MPEG2_MetaHandler::GetFileModDate ( XMP_DateTime * modDate )
+{
+
+ XMP_StringPtr filePath = this->parent->filePath.c_str();
+ XMP_StringPtr extPtr = FindFileExtension ( filePath );
+ this->sidecarPath.assign ( filePath, (extPtr - filePath) );
+ this->sidecarPath += ".xmp";
+
+ if ( ! Host_IO::Exists ( this->sidecarPath.c_str() ) ) return false;
+ return Host_IO::GetModifyDate ( this->sidecarPath.c_str(), modDate );
+
+} // MPEG2_MetaHandler::GetFileModDate
+
+// =================================================================================================
+// MPEG2_MetaHandler::CacheFileData
+// ================================
+
+void MPEG2_MetaHandler::CacheFileData()
+{
+ bool readOnly = (! (this->parent->openFlags & kXMPFiles_OpenForUpdate));
+
+ if ( this->parent->UsesClientIO() ) {
+ XMP_Throw ( "MPEG2 cannot be used with client-managed I/O", kXMPErr_InternalFailure );
+ }
+
+ this->containsXMP = false;
+ this->processedXMP = true; // Whatever we do here is all that we do for XMPFiles::OpenFile.
+
+ // Try to open the sidecar XMP file. Tolerate an open failure, there might not be any XMP.
+ // Note that MPEG2_CheckFormat can't save the sidecar path because the handler doesn't exist then.
+
+ XMP_StringPtr filePath = this->parent->filePath.c_str();
+ XMP_StringPtr extPtr = FindFileExtension ( filePath );
+ this->sidecarPath.assign ( filePath, (extPtr - filePath) );
+ this->sidecarPath += ".xmp";
+
+ if ( ! Host_IO::Exists ( this->sidecarPath.c_str() ) ) return; // OK to not have XMP.
+
+ XMPFiles_IO * localFile = XMPFiles_IO::New_XMPFiles_IO ( this->sidecarPath.c_str(), readOnly );
+ if ( localFile == 0 ) XMP_Throw ( "Failure opening MPEG-2 XMP file", kXMPErr_ExternalFailure );
+ this->parent->ioRef = localFile;
+
+ // Extract the sidecar's contents and parse.
+
+ this->packetInfo.offset = 0; // We take the whole sidecar file.
+ this->packetInfo.length = (XMP_Int32) localFile->Length();
+
+ if ( this->packetInfo.length > 0 ) {
+
+ this->xmpPacket.assign ( this->packetInfo.length, ' ' );
+ localFile->ReadAll ( (void*)this->xmpPacket.c_str(), this->packetInfo.length );
+
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ this->containsXMP = true;
+
+ }
+
+ if ( readOnly ) {
+ localFile->Close();
+ delete localFile;
+ this->parent->ioRef = 0;
+ }
+
+} // MPEG2_MetaHandler::CacheFileData
+
+// =================================================================================================
+// MPEG2_MetaHandler::UpdateFile
+// =============================
+
+void MPEG2_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+
+ XMP_Assert ( this->parent->UsesLocalIO() );
+
+ if ( this->parent->ioRef == 0 ) {
+ XMP_Assert ( ! Host_IO::Exists ( this->sidecarPath.c_str() ) );
+ Host_IO::Create ( this->sidecarPath.c_str() );
+ this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( this->sidecarPath.c_str(), Host_IO::openReadWrite );
+ if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening MPEG-2 XMP file", kXMPErr_ExternalFailure );
+ }
+
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_Assert ( fileRef != 0 );
+ XIO::ReplaceTextFile ( fileRef, this->xmpPacket, doSafeUpdate );
+
+ XMPFiles_IO* localFile = (XMPFiles_IO*)fileRef;
+ localFile->Close();
+ delete localFile;
+ this->parent->ioRef = 0;
+
+ this->needsUpdate = false;
+
+} // MPEG2_MetaHandler::UpdateFile
+
+// =================================================================================================
+// MPEG2_MetaHandler::WriteTempFile
+// ================================
+
+void MPEG2_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ IgnoreParam(tempRef);
+
+ XMP_Throw ( "MPEG2_MetaHandler::WriteTempFile: Should never be called", kXMPErr_Unavailable );
+
+} // MPEG2_MetaHandler::WriteTempFile
diff --git a/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp b/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp
new file mode 100644
index 0000000..be6d38b
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp
@@ -0,0 +1,65 @@
+#ifndef __MPEG2_Handler_hpp__
+#define __MPEG2_Handler_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2005 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+// =================================================================================================
+/// \file MPEG2_Handler.hpp
+/// \brief File format handler for MPEG2.
+///
+/// This header ...
+///
+// =================================================================================================
+
+extern XMPFileHandler * MPEG2_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool MPEG2_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO * fileRef,
+ XMPFiles * parent);
+
+static const XMP_OptionBits kMPEG2_HandlerFlags = ( kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_UsesSidecarXMP );
+
+class MPEG2_MetaHandler : public XMPFileHandler
+{
+public:
+
+ std::string sidecarPath;
+
+ MPEG2_MetaHandler ( XMPFiles * parent );
+ ~MPEG2_MetaHandler();
+
+ bool GetFileModDate ( XMP_DateTime * modDate );
+
+ void CacheFileData();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO * tempRef );
+
+
+}; // MPEG2_MetaHandler
+
+// =================================================================================================
+
+#endif /* __MPEG2_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp b/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp
new file mode 100644
index 0000000..fad238f
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp
@@ -0,0 +1,2581 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/MPEG4_Handler.hpp"
+
+#include "XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp"
+#include "XMPFiles/source/FormatSupport/MOOV_Support.hpp"
+
+#include "source/UnicodeConversions.hpp"
+#include "third-party/zuid/interfaces/MD5.h"
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+using namespace std;
+
+// =================================================================================================
+/// \file MPEG4_Handler.cpp
+/// \brief File format handler for MPEG-4, a flavor of the ISO Base Media File Format.
+///
+/// This handler ...
+///
+// =================================================================================================
+
+// The basic content of a timecode sample description table entry. Does not include trailing boxes.
+
+#pragma pack ( push, 1 )
+
+struct stsdBasicEntry {
+ XMP_Uns32 entrySize;
+ XMP_Uns32 format;
+ XMP_Uns8 reserved_1 [6];
+ XMP_Uns16 dataRefIndex;
+ XMP_Uns32 reserved_2;
+ XMP_Uns32 flags;
+ XMP_Uns32 timeScale;
+ XMP_Uns32 frameDuration;
+ XMP_Uns8 frameCount;
+ XMP_Uns8 reserved_3;
+};
+
+#pragma pack ( pop )
+
+// =================================================================================================
+
+// ! The buffer and constants are both already big endian.
+#define Get4CharCode(buffPtr) (*((XMP_Uns32*)(buffPtr)))
+
+// =================================================================================================
+
+static inline bool IsClassicQuickTimeBox ( XMP_Uns32 boxType )
+{
+ if ( (boxType == ISOMedia::k_moov) || (boxType == ISOMedia::k_mdat) || (boxType == ISOMedia::k_pnot) ||
+ (boxType == ISOMedia::k_free) || (boxType == ISOMedia::k_skip) || (boxType == ISOMedia::k_wide) ) return true;
+ return false;
+} // IsClassicQuickTimeBox
+
+// =================================================================================================
+
+// Pairs of 3 letter ISO 639-2 codes mapped to 2 letter ISO 639-1 codes from:
+// http://www.loc.gov/standards/iso639-2/php/code_list.php
+// Would have to write an "==" operator to use std::map, must compare values not pointers.
+// ! Not fully sorted, do not use a binary search.
+
+static XMP_StringPtr kKnownLangs[] =
+ { "aar", "aa", "abk", "ab", "afr", "af", "aka", "ak", "alb", "sq", "sqi", "sq", "amh", "am",
+ "ara", "ar", "arg", "an", "arm", "hy", "hye", "hy", "asm", "as", "ava", "av", "ave", "ae",
+ "aym", "ay", "aze", "az", "bak", "ba", "bam", "bm", "baq", "eu", "eus", "eu", "bel", "be",
+ "ben", "bn", "bih", "bh", "bis", "bi", "bod", "bo", "tib", "bo", "bos", "bs", "bre", "br",
+ "bul", "bg", "bur", "my", "mya", "my", "cat", "ca", "ces", "cs", "cze", "cs", "cha", "ch",
+ "che", "ce", "chi", "zh", "zho", "zh", "chu", "cu", "chv", "cv", "cor", "kw", "cos", "co",
+ "cre", "cr", "cym", "cy", "wel", "cy", "cze", "cs", "ces", "cs", "dan", "da", "deu", "de",
+ "ger", "de", "div", "dv", "dut", "nl", "nld", "nl", "dzo", "dz", "ell", "el", "gre", "el",
+ "eng", "en", "epo", "eo", "est", "et", "eus", "eu", "baq", "eu", "ewe", "ee", "fao", "fo",
+ "fas", "fa", "per", "fa", "fij", "fj", "fin", "fi", "fra", "fr", "fre", "fr", "fre", "fr",
+ "fra", "fr", "fry", "fy", "ful", "ff", "geo", "ka", "kat", "ka", "ger", "de", "deu", "de",
+ "gla", "gd", "gle", "ga", "glg", "gl", "glv", "gv", "gre", "el", "ell", "el", "grn", "gn",
+ "guj", "gu", "hat", "ht", "hau", "ha", "heb", "he", "her", "hz", "hin", "hi", "hmo", "ho",
+ "hrv", "hr", "scr", "hr", "hun", "hu", "hye", "hy", "arm", "hy", "ibo", "ig", "ice", "is",
+ "isl", "is", "ido", "io", "iii", "ii", "iku", "iu", "ile", "ie", "ina", "ia", "ind", "id",
+ "ipk", "ik", "isl", "is", "ice", "is", "ita", "it", "jav", "jv", "jpn", "ja", "kal", "kl",
+ "kan", "kn", "kas", "ks", "kat", "ka", "geo", "ka", "kau", "kr", "kaz", "kk", "khm", "km",
+ "kik", "ki", "kin", "rw", "kir", "ky", "kom", "kv", "kon", "kg", "kor", "ko", "kua", "kj",
+ "kur", "ku", "lao", "lo", "lat", "la", "lav", "lv", "lim", "li", "lin", "ln", "lit", "lt",
+ "ltz", "lb", "lub", "lu", "lug", "lg", "mac", "mk", "mkd", "mk", "mah", "mh", "mal", "ml",
+ "mao", "mi", "mri", "mi", "mar", "mr", "may", "ms", "msa", "ms", "mkd", "mk", "mac", "mk",
+ "mlg", "mg", "mlt", "mt", "mol", "mo", "mon", "mn", "mri", "mi", "mao", "mi", "msa", "ms",
+ "may", "ms", "mya", "my", "bur", "my", "nau", "na", "nav", "nv", "nbl", "nr", "nde", "nd",
+ "ndo", "ng", "nep", "ne", "nld", "nl", "dut", "nl", "nno", "nn", "nob", "nb", "nor", "no",
+ "nya", "ny", "oci", "oc", "oji", "oj", "ori", "or", "orm", "om", "oss", "os", "pan", "pa",
+ "per", "fa", "fas", "fa", "pli", "pi", "pol", "pl", "por", "pt", "pus", "ps", "que", "qu",
+ "roh", "rm", "ron", "ro", "rum", "ro", "rum", "ro", "ron", "ro", "run", "rn", "rus", "ru",
+ "sag", "sg", "san", "sa", "scc", "sr", "srp", "sr", "scr", "hr", "hrv", "hr", "sin", "si",
+ "slk", "sk", "slo", "sk", "slo", "sk", "slk", "sk", "slv", "sl", "sme", "se", "smo", "sm",
+ "sna", "sn", "snd", "sd", "som", "so", "sot", "st", "spa", "es", "sqi", "sq", "alb", "sq",
+ "srd", "sc", "srp", "sr", "scc", "sr", "ssw", "ss", "sun", "su", "swa", "sw", "swe", "sv",
+ "tah", "ty", "tam", "ta", "tat", "tt", "tel", "te", "tgk", "tg", "tgl", "tl", "tha", "th",
+ "tib", "bo", "bod", "bo", "tir", "ti", "ton", "to", "tsn", "tn", "tso", "ts", "tuk", "tk",
+ "tur", "tr", "twi", "tw", "uig", "ug", "ukr", "uk", "urd", "ur", "uzb", "uz", "ven", "ve",
+ "vie", "vi", "vol", "vo", "wel", "cy", "cym", "cy", "wln", "wa", "wol", "wo", "xho", "xh",
+ "yid", "yi", "yor", "yo", "zha", "za", "zho", "zh", "chi", "zh", "zul", "zu",
+ 0, 0 };
+
+static inline XMP_StringPtr Lookup2LetterLang ( XMP_StringPtr lang3 )
+{
+ for ( size_t i = 0; kKnownLangs[i] != 0; i += 2 ) {
+ if ( XMP_LitMatch ( lang3, kKnownLangs[i] ) ) return kKnownLangs[i+1];
+ }
+ return "";
+}
+
+static inline XMP_StringPtr Lookup3LetterLang ( XMP_StringPtr lang2 )
+{
+ for ( size_t i = 0; kKnownLangs[i] != 0; i += 2 ) {
+ if ( XMP_LitMatch ( lang2, kKnownLangs[i+1] ) ) return kKnownLangs[i];
+ }
+ return "";
+}
+
+// =================================================================================================
+// MPEG4_CheckFormat
+// =================
+//
+// There are 3 variations of recognized file:
+// - Normal MPEG-4 - has an 'ftyp' box containing a known compatible brand but not 'qt '.
+// - Modern QuickTime - has an 'ftyp' box containing 'qt ' as a compatible brand.
+// - Classic QuickTime - has no 'ftyp' box, should have recognized top level boxes.
+//
+// An MPEG-4 or modern QuickTime file is an instance of an ISO Base Media file, ISO 14496-12 and -14.
+// A classic QuickTime file has the same physical box structure, but somewhat different box types.
+// The ISO files must begin with an 'ftyp' box containing 'mp41', 'mp42', 'f4v ', or 'qt ' in the
+// compatible brands.
+//
+// The general box structure is:
+//
+// 0 4 uns32 box size, 0 means "to EoF", 1 means 64-bit size follows
+// 4 4 uns32 box type
+// 8 8 uns64 box size, present only if uns32 size is 1
+// - * box content
+//
+// The 'ftyp' box content is:
+//
+// - 4 uns32 major brand
+// - 4 uns32 minor version
+// - * uns32 sequence of compatible brands, to the end of the box
+
+// ! With the addition of QuickTime support there is some change in behavior in OpenFile when the
+// ! kXMPFiles_OpenStrictly option is used wth a specific file format. The kXMP_MPEG4File and
+// ! kXMP_MOVFile formats are treated uniformly, you can't force "real MOV" or "real MPEG-4". You
+// ! can check afterwards using GetFileInfo to see what the file happens to be.
+
+bool MPEG4_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles* parent )
+{
+ XMP_Uns8 buffer [4*1024];
+ XMP_Uns32 ioCount, brandCount, brandOffset;
+ XMP_Uns64 fileSize, nextOffset;
+ ISOMedia::BoxInfo currBox;
+
+ #define IsTolerableBoxChar(ch) ( ((0x20 <= (ch)) && ((ch) <= 0x7E)) || ((ch) == 0xA9) )
+
+ XMP_AbortProc abortProc = parent->abortProc;
+ void * abortArg = parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ bool openStrictly = XMP_OptionIsSet ( parent->openFlags, kXMPFiles_OpenStrictly);
+
+ // Get the first box's info, see if it is 'ftyp' or not.
+
+ XMP_Assert ( (parent->tempPtr == 0) && (parent->tempUI32 == 0) );
+
+ fileSize = fileRef->Length();
+ if ( fileSize < 8 ) return false;
+
+ nextOffset = ISOMedia::GetBoxInfo ( fileRef, 0, fileSize, &currBox );
+ if ( currBox.headerSize < 8 ) return false; // Can't be an ISO or QuickTime file.
+
+ if ( currBox.boxType == ISOMedia::k_ftyp ) {
+
+ // Have an 'ftyp' box, look through the compatible brands. If 'qt ' is present then this is
+ // a modern QuickTime file, regardless of what else is found. Otherwise this is plain ISO if
+ // any of the other recognized brands are found.
+
+ if ( currBox.contentSize < 12 ) return false; // No compatible brands at all.
+ if ( currBox.contentSize > 1024*1024 ) return false; // Sanity check and make sure count fits in 32 bits.
+ brandCount = ((XMP_Uns32)currBox.contentSize - 8) >> 2;
+
+ fileRef->Seek ( 8, kXMP_SeekFromCurrent ); // Skip the major and minor brands.
+ ioCount = brandOffset = 0;
+
+ bool haveCompatibleBrand = false;
+
+ for ( ; brandCount > 0; --brandCount, brandOffset += 4 ) {
+
+ if ( brandOffset >= ioCount ) {
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_CheckFormat - User abort", kXMPErr_UserAbort );
+ }
+ ioCount = 4 * brandCount;
+ if ( ioCount > sizeof(buffer) ) ioCount = sizeof(buffer);
+ ioCount = fileRef->ReadAll ( buffer, ioCount );
+ brandOffset = 0;
+ }
+
+ XMP_Uns32 brand = GetUns32BE ( &buffer[brandOffset] );
+ if ( brand == ISOMedia::k_qt ) { // Don't need to look further.
+ if ( openStrictly && (format != kXMP_MOVFile) ) return false;
+ parent->format = kXMP_MOVFile;
+ parent->tempUI32 = MOOV_Manager::kFileIsModernQT;
+ return true;
+ } else if ( (brand == ISOMedia::k_mp41) || (brand == ISOMedia::k_mp42) || (brand == ISOMedia::k_f4v) ) {
+ haveCompatibleBrand = true; // Need to keep looking in case 'qt ' follows.
+ }
+
+ }
+
+ if ( ! haveCompatibleBrand ) return false;
+ if ( openStrictly && (format != kXMP_MPEG4File) ) return false;
+ parent->format = kXMP_MPEG4File;
+ parent->tempUI32 = MOOV_Manager::kFileIsNormalISO;
+ return true;
+
+ } else {
+
+ // No 'ftyp', look for classic QuickTime: 'moov', 'mdat', 'pnot', 'free', 'skip', and 'wide'.
+ // As an expedient, quit when a 'moov' box is found. Tolerate other boxes, they are in the
+ // wild for ill-formed files, e.g. seen when 'moov'/'udta' children get left at top level.
+
+ while ( currBox.boxType != ISOMedia::k_moov ) {
+
+ if ( ! IsClassicQuickTimeBox ( currBox.boxType ) ) {
+ // Make sure the box type is 4 ASCII characters or 0xA9 (MacRoman copyright).
+ XMP_Uns8 b1 = (XMP_Uns8) (currBox.boxType >> 24);
+ XMP_Uns8 b2 = (XMP_Uns8) ((currBox.boxType >> 16) & 0xFF);
+ XMP_Uns8 b3 = (XMP_Uns8) ((currBox.boxType >> 8) & 0xFF);
+ XMP_Uns8 b4 = (XMP_Uns8) (currBox.boxType & 0xFF);
+ bool ok = IsTolerableBoxChar(b1) && IsTolerableBoxChar(b2) &&
+ IsTolerableBoxChar(b3) && IsTolerableBoxChar(b4);
+ if ( ! ok ) return false;
+ }
+ if ( nextOffset >= fileSize ) return false;
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_CheckFormat - User abort", kXMPErr_UserAbort );
+ }
+ nextOffset = ISOMedia::GetBoxInfo ( fileRef, nextOffset, fileSize, &currBox );
+
+ }
+
+ if ( openStrictly && (format != kXMP_MOVFile) ) return false;
+ parent->format = kXMP_MOVFile;
+ parent->tempUI32 = MOOV_Manager::kFileIsTraditionalQT;
+ return true;
+
+ }
+
+ return false;
+
+} // MPEG4_CheckFormat
+
+// =================================================================================================
+// MPEG4_MetaHandlerCTor
+// =====================
+
+XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent )
+{
+
+ return new MPEG4_MetaHandler ( parent );
+
+} // MPEG4_MetaHandlerCTor
+
+// =================================================================================================
+// MPEG4_MetaHandler::MPEG4_MetaHandler
+// ====================================
+
+MPEG4_MetaHandler::MPEG4_MetaHandler ( XMPFiles * _parent )
+ : fileMode(0), havePreferredXMP(false), xmpBoxPos(0), moovBoxPos(0), xmpBoxSize(0), moovBoxSize(0)
+{
+
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kMPEG4_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+ this->fileMode = (XMP_Uns8)_parent->tempUI32;
+ _parent->tempUI32 = 0;
+
+} // MPEG4_MetaHandler::MPEG4_MetaHandler
+
+// =================================================================================================
+// MPEG4_MetaHandler::~MPEG4_MetaHandler
+// =====================================
+
+MPEG4_MetaHandler::~MPEG4_MetaHandler()
+{
+
+ // Nothing to do.
+
+} // MPEG4_MetaHandler::~MPEG4_MetaHandler
+
+// =================================================================================================
+// SecondsToXMPDate
+// ================
+
+// *** ASF has similar code with different origin, should make a shared utility.
+
+static void SecondsToXMPDate ( XMP_Uns64 isoSeconds, XMP_DateTime * xmpDate )
+{
+ memset ( xmpDate, 0, sizeof(XMP_DateTime) ); // AUDIT: Using sizeof(XMP_DateTime) is safe.
+
+ XMP_Int32 days = (XMP_Int32) (isoSeconds / 86400);
+ isoSeconds -= ((XMP_Uns64)days * 86400);
+
+ XMP_Int32 hour = (XMP_Int32) (isoSeconds / 3600);
+ isoSeconds -= ((XMP_Uns64)hour * 3600);
+
+ XMP_Int32 minute = (XMP_Int32) (isoSeconds / 60);
+ isoSeconds -= ((XMP_Uns64)minute * 60);
+
+ XMP_Int32 second = (XMP_Int32)isoSeconds;
+
+ xmpDate->year = 1904; // Start with the ISO origin.
+ xmpDate->month = 1;
+ xmpDate->day = 1;
+
+ xmpDate->day += days; // Add in the delta.
+ xmpDate->hour = hour;
+ xmpDate->minute = minute;
+ xmpDate->second = second;
+
+ xmpDate->hasTimeZone = true; // ! Needed for ConvertToUTCTime to do anything.
+ SXMPUtils::ConvertToUTCTime ( xmpDate ); // Normalize the date/time.
+
+} // SecondsToXMPDate
+
+// =================================================================================================
+// XMPDateToSeconds
+// ================
+
+// *** ASF has similar code with different origin, should make a shared utility.
+
+static bool IsLeapYear ( XMP_Int32 year )
+{
+ if ( year < 0 ) year = -year + 1; // Fold the negative years, assuming there is a year 0.
+ if ( (year % 4) != 0 ) return false; // Not a multiple of 4.
+ if ( (year % 100) != 0 ) return true; // A multiple of 4 but not a multiple of 100.
+ if ( (year % 400) == 0 ) return true; // A multiple of 400.
+ return false; // A multiple of 100 but not a multiple of 400.
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static XMP_Int32 DaysInMonth ( XMP_Int32 year, XMP_Int32 month )
+{
+ static XMP_Int32 daysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
+ XMP_Int32 days = daysInMonth[month];
+ if ( (month == 2) && IsLeapYear(year) ) days += 1;
+ return days;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static void XMPDateToSeconds ( const XMP_DateTime & _xmpDate, XMP_Uns64 * isoSeconds )
+{
+ XMP_DateTime xmpDate = _xmpDate;
+ SXMPUtils::ConvertToUTCTime ( &xmpDate );
+
+ XMP_Uns64 tempSeconds = (XMP_Uns64)xmpDate.second;
+ tempSeconds += (XMP_Uns64)xmpDate.minute * 60;
+ tempSeconds += (XMP_Uns64)xmpDate.hour * 3600;
+
+ XMP_Int32 days = (xmpDate.day - 1);
+
+ --xmpDate.month;
+ while ( xmpDate.month >= 1 ) {
+ days += DaysInMonth ( xmpDate.year, xmpDate.month );
+ --xmpDate.month;
+ }
+
+ --xmpDate.year;
+ while ( xmpDate.year >= 1904 ) {
+ days += (IsLeapYear ( xmpDate.year) ? 366 : 365 );
+ --xmpDate.year;
+ }
+
+ tempSeconds += (XMP_Uns64)days * 86400;
+ *isoSeconds = tempSeconds;
+
+} // XMPDateToSeconds
+
+// =================================================================================================
+// ImportMVHDItems
+// ===============
+
+static bool ImportMVHDItems ( MOOV_Manager::BoxInfo mvhdInfo, SXMPMeta * xmp )
+{
+ XMP_Assert ( mvhdInfo.boxType == ISOMedia::k_mvhd );
+ if ( mvhdInfo.contentSize < 4 ) return false; // Just enough to check the version/flags at first.
+
+ XMP_Uns8 mvhdVersion = *mvhdInfo.content;
+ if ( mvhdVersion > 1 ) return false;
+
+ XMP_Uns64 creationTime, modificationTime, duration;
+ XMP_Uns32 timescale;
+
+ if ( mvhdVersion == 0 ) {
+
+ if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return false;
+ MOOV_Manager::Content_mvhd_0 * mvhdRaw_0 = (MOOV_Manager::Content_mvhd_0*) mvhdInfo.content;
+
+ creationTime = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->creationTime );
+ modificationTime = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->modificationTime );
+ timescale = GetUns32BE ( &mvhdRaw_0->timescale );
+ duration = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->duration );
+
+ } else {
+
+ XMP_Assert ( mvhdVersion == 1 );
+ if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_1 ) ) return false;
+ MOOV_Manager::Content_mvhd_1 * mvhdRaw_1 = (MOOV_Manager::Content_mvhd_1*) mvhdInfo.content;
+
+ creationTime = GetUns64BE ( &mvhdRaw_1->creationTime );
+ modificationTime = GetUns64BE ( &mvhdRaw_1->modificationTime );
+ timescale = GetUns32BE ( &mvhdRaw_1->timescale );
+ duration = GetUns64BE ( &mvhdRaw_1->duration );
+
+ }
+
+ bool haveImports = false;
+ XMP_DateTime xmpDate;
+
+ if ( (creationTime >> 32) < 0xFF ) { // Sanity check for bogus date info.
+ SecondsToXMPDate ( creationTime, &xmpDate );
+ xmp->SetProperty_Date ( kXMP_NS_XMP, "CreateDate", xmpDate );
+ haveImports = true;
+ }
+
+ if ( (modificationTime >> 32) < 0xFF ) { // Sanity check for bogus date info.
+ SecondsToXMPDate ( modificationTime, &xmpDate );
+ xmp->SetProperty_Date ( kXMP_NS_XMP, "ModifyDate", xmpDate );
+ haveImports = true;
+ }
+
+ if ( timescale != 0 ) { // Avoid 1/0 for the scale field.
+ char buffer [32]; // A 64-bit number is at most 20 digits.
+ xmp->DeleteProperty ( kXMP_NS_DM, "duration" ); // Delete the whole struct.
+ snprintf ( buffer, sizeof(buffer), "%llu", duration ); // AUDIT: The buffer is big enough.
+ xmp->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", &buffer[0] );
+ snprintf ( buffer, sizeof(buffer), "1/%u", timescale ); // AUDIT: The buffer is big enough.
+ xmp->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", &buffer[0] );
+ haveImports = true;
+ }
+
+ return haveImports;
+
+} // ImportMVHDItems
+
+// =================================================================================================
+// ExportMVHDItems
+// ===============
+
+static void ExportMVHDItems ( const SXMPMeta & xmp, MOOV_Manager * moovMgr )
+{
+ XMP_DateTime xmpDate;
+ bool createFound, modifyFound;
+ XMP_Uns64 createSeconds = 0, modifySeconds = 0;
+
+ MOOV_Manager::BoxInfo mvhdInfo;
+ MOOV_Manager::BoxRef mvhdRef = moovMgr->GetBox ( "moov/mvhd", &mvhdInfo );
+ if ( (mvhdRef == 0) || (mvhdInfo.contentSize < 4) ) return;
+
+ XMP_Uns8 version = *mvhdInfo.content;
+ if ( version > 1 ) return;
+
+ createFound = xmp.GetProperty_Date ( kXMP_NS_XMP, "CreateDate", &xmpDate, 0 );
+ if ( createFound ) XMPDateToSeconds ( xmpDate, &createSeconds );
+
+ modifyFound = xmp.GetProperty_Date ( kXMP_NS_XMP, "ModifyDate", &xmpDate, 0 );
+ if ( modifyFound ) XMPDateToSeconds ( xmpDate, &modifySeconds );
+
+ if ( version == 1 ) {
+
+ // Modify the v1 box in-place.
+
+ if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_1 ) ) return;
+
+ XMP_Uns64 oldCreate = GetUns64BE ( mvhdInfo.content + 4 );
+ XMP_Uns64 oldModify = GetUns64BE ( mvhdInfo.content + 12 );
+
+ if ( createFound ) {
+ if ( createSeconds != oldCreate ) PutUns64BE ( createSeconds, ((XMP_Uns8*)mvhdInfo.content + 4) );
+ moovMgr->NoteChange();
+ }
+ if ( modifyFound ) {
+ if ( modifySeconds != oldModify ) PutUns64BE ( modifySeconds, ((XMP_Uns8*)mvhdInfo.content + 12) );
+ moovMgr->NoteChange();
+ }
+
+ } else if ( ((createSeconds >> 32) == 0) && ((modifySeconds >> 32) == 0) ) {
+
+ // Modify the v0 box in-place.
+
+ if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return;
+
+ XMP_Uns32 oldCreate = GetUns32BE ( mvhdInfo.content + 4 );
+ XMP_Uns32 oldModify = GetUns32BE ( mvhdInfo.content + 8 );
+
+ if ( createFound ) {
+ if ( (XMP_Uns32)createSeconds != oldCreate ) PutUns32BE ( (XMP_Uns32)createSeconds, ((XMP_Uns8*)mvhdInfo.content + 4) );
+ moovMgr->NoteChange();
+ }
+ if ( modifyFound ) {
+ if ( (XMP_Uns32)modifySeconds != oldModify ) PutUns32BE ( (XMP_Uns32)modifySeconds, ((XMP_Uns8*)mvhdInfo.content + 8) );
+ moovMgr->NoteChange();
+ }
+
+ } else {
+
+ // Replace the v0 box with a v1 box.
+
+ XMP_Assert ( createFound | modifyFound ); // One of them has high bits set.
+ if ( mvhdInfo.contentSize != sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return;
+
+ MOOV_Manager::Content_mvhd_0 * mvhdV0 = (MOOV_Manager::Content_mvhd_0*) mvhdInfo.content;
+ MOOV_Manager::Content_mvhd_1 mvhdV1;
+
+ // Copy the unchanged fields directly.
+
+ mvhdV1.timescale = mvhdV0->timescale;
+ mvhdV1.rate = mvhdV0->rate;
+ mvhdV1.volume = mvhdV0->volume;
+ mvhdV1.pad_1 = mvhdV0->pad_1;
+ mvhdV1.pad_2 = mvhdV0->pad_2;
+ mvhdV1.pad_3 = mvhdV0->pad_3;
+ for ( int i = 0; i < 9; ++i ) mvhdV1.matrix[i] = mvhdV0->matrix[i];
+ for ( int i = 0; i < 6; ++i ) mvhdV1.preDef[i] = mvhdV0->preDef[i];
+ mvhdV1.nextTrackID = mvhdV0->nextTrackID;
+
+ // Set the fields that have changes.
+
+ mvhdV1.vFlags = (1 << 24) | (mvhdV0->vFlags & 0xFFFFFF);
+ mvhdV1.duration = MakeUns64BE ( (XMP_Uns64) GetUns32BE ( &mvhdV0->duration ) );
+
+ XMP_Uns64 temp64;
+
+ temp64 = (XMP_Uns64) GetUns32BE ( &mvhdV0->creationTime );
+ if ( createFound ) temp64 = createSeconds;
+ mvhdV1.creationTime = MakeUns64BE ( temp64 );
+
+ temp64 = (XMP_Uns64) GetUns32BE ( &mvhdV0->modificationTime );
+ if ( modifyFound ) temp64 = modifySeconds;
+ mvhdV1.modificationTime = MakeUns64BE ( temp64 );
+
+ moovMgr->SetBox ( mvhdRef, &mvhdV1, sizeof ( MOOV_Manager::Content_mvhd_1 ) );
+
+ }
+
+} // ExportMVHDItems
+
+// =================================================================================================
+// ImportISOCopyrights
+// ===================
+//
+// The cached 'moov'/'udta'/'cprt' boxes are full boxes. The "real" content is a UInt16 packed 3
+// character language code and a UTF-8 or UTF-16 string.
+
+static bool ImportISOCopyrights ( const std::vector<MOOV_Manager::BoxInfo> & cprtBoxes, SXMPMeta * xmp )
+{
+ bool haveImports = false;
+
+ std::string tempStr;
+ char lang3 [4]; // The unpacked ISO-639-2/T language code with final null.
+ lang3[3] = 0;
+
+ for ( size_t i = 0, limit = cprtBoxes.size(); i < limit; ++i ) {
+
+ const MOOV_Manager::BoxInfo & currBox = cprtBoxes[i];
+ if ( currBox.contentSize < 4+2+1 ) continue; // Want enough for a non-empty value.
+ if ( *currBox.content != 0 ) continue; // Only proceed for version 0, ignore the flags.
+
+ XMP_Uns16 packedLang = GetUns16BE ( currBox.content + 4 );
+ lang3[0] = (packedLang >> 10) + 0x60;
+ lang3[1] = ((packedLang >> 5) & 0x1F) + 0x60;
+ lang3[2] = (packedLang & 0x1F) + 0x60;
+ XMP_StringPtr xmpLang = Lookup2LetterLang ( lang3 );
+ if ( *xmpLang == 0 ) continue;
+
+ XMP_StringPtr textPtr = (XMP_StringPtr) (currBox.content + 6);
+ XMP_StringLen textLen = currBox.contentSize - 6;
+
+ if ( (textLen >= 2) && (GetUns16BE(textPtr) == 0xFEFF) ) {
+ FromUTF16 ( (UTF16Unit*)textPtr, textLen/2, &tempStr, true /* big endian */ );
+ textPtr = tempStr.c_str();
+ }
+
+ xmp->SetLocalizedText ( kXMP_NS_DC, "rights", xmpLang, xmpLang, textPtr );
+ haveImports = true;
+
+ }
+
+ return haveImports;
+
+} // ImportISOCopyrights
+
+// =================================================================================================
+// ExportISOCopyrights
+// ===================
+
+static void ExportISOCopyrights ( const SXMPMeta & xmp, MOOV_Manager * moovMgr )
+{
+ bool haveMappings = false; // True if any ISO-XMP language mappings are found.
+
+ // Go through the ISO 'cprt' items and look for a corresponding XMP item. Ignore the ISO item if
+ // there is no language mapping to XMP. Update the ISO item if the mappable XMP exists, delete
+ // the ISO item if the mappable XMP does not exist. Since the import side would have made sure
+ // the mappable XMP items existed, if they don't now they must have been deleted.
+
+ MOOV_Manager::BoxInfo udtaInfo;
+ MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo );
+ if ( udtaRef == 0 ) return;
+
+ std::string xmpPath, xmpValue, xmpLang, tempStr;
+ char lang3 [4]; // An unpacked ISO-639-2/T language code.
+ lang3[3] = 0;
+
+ for ( XMP_Uns32 ordinal = udtaInfo.childCount; ordinal > 0; --ordinal ) { // ! Go backwards because of deletions.
+
+ MOOV_Manager::BoxInfo cprtInfo;
+ MOOV_Manager::BoxRef cprtRef = moovMgr->GetNthChild ( udtaRef, ordinal-1, &cprtInfo );
+ if ( (cprtRef == 0) ) break; // Sanity check, should not happen.
+ if ( (cprtInfo.boxType != ISOMedia::k_cprt) || (cprtInfo.contentSize < 6) ) continue;
+ if ( *cprtInfo.content != 0 ) continue; // Only accept version 0, ignore the flags.
+
+ XMP_Uns16 packedLang = GetUns16BE ( cprtInfo.content + 4 );
+ lang3[0] = (packedLang >> 10) + 0x60;
+ lang3[1] = ((packedLang >> 5) & 0x1F) + 0x60;
+ lang3[2] = (packedLang & 0x1F) + 0x60;
+
+ XMP_StringPtr lang2 = Lookup2LetterLang ( lang3 );
+ if ( *lang2 == 0 ) continue; // No language mapping to XMP.
+ haveMappings = true;
+
+ bool xmpFound = xmp.GetLocalizedText ( kXMP_NS_DC, "rights", lang2, lang2, &xmpLang, &xmpValue, 0 );
+ if ( xmpFound ) {
+ if ( (xmpLang.size() < 2) ||
+ ( (xmpLang.size() == 2) && (xmpLang != lang2) ) ||
+ ( (xmpLang.size() > 2) && ( (xmpLang[2] != '-') || (! XMP_LitNMatch ( xmpLang.c_str(), lang2, 2)) ) ) ) {
+ xmpFound = false; // The language does not match, the corresponding XMP does not exist.
+ }
+ }
+
+ if ( ! xmpFound ) {
+
+ // No XMP, delete the ISO item.
+ moovMgr->DeleteNthChild ( udtaRef, ordinal-1 );
+
+ } else {
+
+ // Update the ISO item if necessary.
+ XMP_StringPtr isoStr = (XMP_StringPtr)cprtInfo.content + 6;
+ size_t rawLen = cprtInfo.contentSize - 6;
+ if ( (rawLen >= 8) && (GetUns16BE(isoStr) == 0xFEFF) ) {
+ FromUTF16 ( (UTF16Unit*)(isoStr+2), (rawLen-2)/2, &tempStr, true /* big endian */ );
+ isoStr = tempStr.c_str();
+ }
+ if ( xmpValue != isoStr ) {
+ std::string newContent = "vfffll";
+ newContent += xmpValue;
+ memcpy ( (char*)newContent.c_str(), cprtInfo.content, 6 ); // Keep old version, flags, and language.
+ moovMgr->SetBox ( cprtRef, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) );
+ }
+
+ }
+
+ }
+
+ // Go through the XMP items and look for a corresponding ISO item. Skip if found (did it above),
+ // otherwise add a new ISO item.
+
+ bool haveXDefault = false;
+ XMP_Index xmpCount = xmp.CountArrayItems ( kXMP_NS_DC, "rights" );
+
+ for ( XMP_Index xmpIndex = 1; xmpIndex <= xmpCount; ++xmpIndex ) { // ! The first XMP array index is 1.
+
+ SXMPUtils::ComposeArrayItemPath ( kXMP_NS_DC, "rights", xmpIndex, &xmpPath );
+ xmp.GetArrayItem ( kXMP_NS_DC, "rights", xmpIndex, &xmpValue, 0 );
+ bool hasLang = xmp.GetQualifier ( kXMP_NS_DC, xmpPath.c_str(), kXMP_NS_XML, "lang", &xmpLang, 0 );
+ if ( ! hasLang ) continue; // Sanity check.
+ if ( xmpLang == "x-default" ) {
+ haveXDefault = true; // See later special case.
+ continue;
+ }
+
+ XMP_StringPtr isoLang = "";
+ size_t rootLen = xmpLang.find ( '-' );
+ if ( rootLen == std::string::npos ) rootLen = xmpLang.size();
+ if ( rootLen == 2 ) {
+ if( xmpLang.size() > 2 ) xmpLang[2] = 0;
+ isoLang = Lookup3LetterLang ( xmpLang.c_str() );
+ if ( *isoLang == 0 ) continue;
+ } else if ( rootLen == 3 ) {
+ if( xmpLang.size() > 3 ) xmpLang[3] = 0;
+ isoLang = xmpLang.c_str();
+ } else {
+ continue;
+ }
+ haveMappings = true;
+
+ bool isoFound = false;
+ XMP_Uns16 packedLang = ((isoLang[0] - 0x60) << 10) | ((isoLang[1] - 0x60) << 5) | (isoLang[2] - 0x60);
+
+ for ( XMP_Uns32 isoIndex = 0; (isoIndex < udtaInfo.childCount) && (! isoFound); ++isoIndex ) {
+
+ MOOV_Manager::BoxInfo cprtInfo;
+ MOOV_Manager::BoxRef cprtRef = moovMgr->GetNthChild ( udtaRef, isoIndex, &cprtInfo );
+ if ( (cprtRef == 0) ) break; // Sanity check, should not happen.
+ if ( (cprtInfo.boxType != ISOMedia::k_cprt) || (cprtInfo.contentSize < 6) ) continue;
+ if ( *cprtInfo.content != 0 ) continue; // Only accept version 0, ignore the flags.
+ if ( packedLang != GetUns16BE ( cprtInfo.content + 4 ) ) continue; // Look for matching language.
+
+ isoFound = true; // Found the language entry, whether or not we update it.
+
+ }
+
+ if ( ! isoFound ) {
+
+ std::string newContent = "vfffll";
+ newContent += xmpValue;
+ *((XMP_Uns32*)newContent.c_str()) = 0; // Set the version and flags to zero.
+ PutUns16BE ( packedLang, (char*)newContent.c_str() + 4 );
+ moovMgr->AddChildBox ( udtaRef, ISOMedia::k_cprt, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) );
+
+ }
+
+ }
+
+ // If there were no mappings in the loops, export the XMP "x-default" value to the first ISO item.
+
+ if ( ! haveMappings ) {
+
+ MOOV_Manager::BoxInfo cprtInfo;
+ MOOV_Manager::BoxRef cprtRef = moovMgr->GetTypeChild ( udtaRef, ISOMedia::k_cprt, &cprtInfo );
+
+ if ( (cprtRef != 0) && (cprtInfo.contentSize >= 6) && (*cprtInfo.content == 0) ) {
+
+ bool xmpFound = xmp.GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", &xmpLang, &xmpValue, 0 );
+
+ if ( xmpFound && (xmpLang == "x-default") ) {
+
+ // Update the ISO item if necessary.
+ XMP_StringPtr isoStr = (XMP_StringPtr)cprtInfo.content + 6;
+ size_t rawLen = cprtInfo.contentSize - 6;
+ if ( (rawLen >= 8) && (GetUns16BE(isoStr) == 0xFEFF) ) {
+ FromUTF16 ( (UTF16Unit*)(isoStr+2), (rawLen-2)/2, &tempStr, true /* big endian */ );
+ isoStr = tempStr.c_str();
+ }
+ if ( xmpValue != isoStr ) {
+ std::string newContent = "vfffll";
+ newContent += xmpValue;
+ memcpy ( (char*)newContent.c_str(), cprtInfo.content, 6 ); // Keep old version, flags, and language.
+ moovMgr->SetBox ( cprtRef, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) );
+ }
+
+ }
+
+ }
+
+ }
+
+} // ExportISOCopyrights
+
+// =================================================================================================
+// ExportQuickTimeItems
+// ====================
+
+static void ExportQuickTimeItems ( const SXMPMeta & xmp, TradQT_Manager * qtMgr, MOOV_Manager * moovMgr )
+{
+
+ // The QuickTime 'udta' timecode items are done here for simplicity.
+
+ #define createWithZeroLang true
+
+ qtMgr->ExportSimpleXMP ( kQTilst_Reel, xmp, kXMP_NS_DM, "tapeName" );
+ qtMgr->ExportSimpleXMP ( kQTilst_Timecode, xmp, kXMP_NS_DM, "startTimecode/xmpDM:timeValue", createWithZeroLang );
+ qtMgr->ExportSimpleXMP ( kQTilst_TimeScale, xmp, kXMP_NS_DM, "startTimeScale", createWithZeroLang );
+ qtMgr->ExportSimpleXMP ( kQTilst_TimeSize, xmp, kXMP_NS_DM, "startTimeSampleSize", createWithZeroLang );
+
+ qtMgr->UpdateChangedBoxes ( moovMgr );
+
+} // ExportQuickTimeItems
+
+// =================================================================================================
+// SelectTimeFormat
+// ================
+
+static const char * SelectTimeFormat ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo )
+{
+ const char * timeFormat = 0;
+
+ float fltFPS = (float)tmcdInfo.timeScale / (float)tmcdInfo.frameDuration;
+ int intFPS = (int) (fltFPS + 0.5);
+
+ switch ( intFPS ) {
+
+ case 30:
+ if ( fltFPS >= 29.985 ) {
+ timeFormat = "30Timecode";
+ } else if ( tmcdInfo.isDropFrame ) {
+ timeFormat = "2997DropTimecode";
+ } else {
+ timeFormat = "2997NonDropTimecode";
+ }
+ break;
+
+ case 24:
+ if ( fltFPS >= 23.988 ) {
+ timeFormat = "24Timecode";
+ } else {
+ timeFormat = "23976Timecode";
+ }
+ break;
+
+ case 25:
+ timeFormat = "25Timecode";
+ break;
+
+ case 50:
+ timeFormat = "50Timecode";
+ break;
+
+ case 60:
+ if ( fltFPS >= 59.97 ) {
+ timeFormat = "60Timecode";
+ } else if ( tmcdInfo.isDropFrame ) {
+ timeFormat = "5994DropTimecode";
+ } else {
+ timeFormat = "5994NonDropTimecode";
+ }
+ break;
+
+ }
+
+ return timeFormat;
+
+} // SelectTimeFormat
+
+// =================================================================================================
+// SelectTimeFormat
+// ================
+
+static const char * SelectTimeFormat ( const SXMPMeta & xmp )
+{
+ bool ok;
+ MPEG4_MetaHandler::TimecodeTrackInfo tmcdInfo;
+
+ XMP_Int64 timeScale;
+ ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", &timeScale, 0 );
+ if ( ! ok ) return 0;
+ tmcdInfo.timeScale = (XMP_Uns32)timeScale;
+
+ XMP_Int64 frameDuration;
+ ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", &frameDuration, 0 );
+ if ( ! ok ) return 0;
+ tmcdInfo.frameDuration = (XMP_Uns32)frameDuration;
+
+ std::string timecode;
+ ok = xmp.GetProperty ( kXMP_NS_DM, "startTimecode/xmpDM:timeValue", &timecode, 0 );
+ if ( ! ok ) return 0;
+ if ( (timecode.size() == 11) && (timecode[8] == ';') ) tmcdInfo.isDropFrame = true;
+
+ return SelectTimeFormat ( tmcdInfo );
+
+} // SelectTimeFormat
+
+// =================================================================================================
+// ComposeTimecode
+// ===============
+
+static const char * kDecDigits = "0123456789";
+#define TwoDigits(val,str) (str)[0] = kDecDigits[(val)/10]; (str)[1] = kDecDigits[(val)%10]
+
+static bool ComposeTimecode ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo, std::string * strValue )
+{
+ float fltFPS = (float)tmcdInfo.timeScale / (float)tmcdInfo.frameDuration;
+ int intFPS = (int) (fltFPS + 0.5);
+ if ( (intFPS != 30) && (intFPS != 24) && (intFPS != 25) && (intFPS != 50) && (intFPS != 60) ) return false;
+
+ XMP_Uns32 framesPerDay = intFPS * 24*60*60;
+ XMP_Uns32 dropLimit = 2; // Used in the drop-frame correction.
+
+ if ( tmcdInfo.isDropFrame ) {
+ if ( intFPS == 30 ) {
+ framesPerDay = 2589408; // = 29.97 * 24*60*60
+ } else if ( intFPS == 60 ) {
+ framesPerDay = 5178816; // = 59.94 * 24*60*60
+ dropLimit = 4;
+ } else {
+ strValue->erase();
+ return false; // Dropframe can only apply to 29.97 and 59.94.
+ }
+ }
+
+ XMP_Uns32 framesPerHour = framesPerDay / 24;
+ XMP_Uns32 framesPerTenMinutes = framesPerHour / 6;
+ XMP_Uns32 framesPerMinute = framesPerTenMinutes / 10;
+
+ XMP_Uns32 frameCount = tmcdInfo.timecodeSample;
+ while (frameCount >= framesPerDay ) frameCount -= framesPerDay; // Normalize to be within 24 hours.
+
+ XMP_Uns32 hours, minHigh, minLow, seconds;
+
+ hours = frameCount / framesPerHour;
+ frameCount -= (hours * framesPerHour);
+
+ minHigh = frameCount / framesPerTenMinutes;
+ frameCount -= (minHigh * framesPerTenMinutes);
+
+ minLow = frameCount / framesPerMinute;
+ frameCount -= (minLow * framesPerMinute);
+
+ // Do some drop-frame corrections at this point: If this is drop-frame and the units of minutes
+ // is non-zero, and the seconds are zero, and the frames are zero or one, the time is illegal.
+ // Perform correction by subtracting 1 from the units of minutes and adding 1798 to the frames.Ê
+ // For example, 1:00:00 becomes 59:28, and 1:00:01 becomes 59:29. A special case can occur for
+ // when the frameCount just before the minHigh calculation is less than framesPerTenMinutes but
+ // more than 10*framesPerMinute. This happens because of roundoff, and will result in a minHigh
+ // of 0 and a minLow of 10.ÊThe drop frame correction mustÊalso be performed for this case.
+
+ if ( tmcdInfo.isDropFrame ) {
+ if ( (minLow == 10) || ((minLow != 0) && (frameCount < dropLimit)) ) {
+ minLow -= 1;
+ frameCount += framesPerMinute;
+ }
+ }
+
+ seconds = frameCount / intFPS;
+ frameCount -= (seconds * intFPS);
+
+ if ( tmcdInfo.isDropFrame ) {
+ *strValue = "hh;mm;ss;ff";
+ } else {
+ *strValue = "hh:mm:ss:ff";
+ }
+
+ char * str = (char*)strValue->c_str();
+ TwoDigits ( hours, str );
+ str[3] = kDecDigits[minHigh]; str[4] = kDecDigits[minLow];
+ TwoDigits ( seconds, str+6 );
+ TwoDigits ( frameCount, str+9 );
+
+ return true;
+
+} // ComposeTimecode
+
+// =================================================================================================
+// DecomposeTimecode
+// =================
+
+static bool DecomposeTimecode ( const char * strValue, MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo )
+{
+ float fltFPS = (float)tmcdInfo->timeScale / (float)tmcdInfo->frameDuration;
+ int intFPS = (int) (fltFPS + 0.5);
+ if ( (intFPS != 30) && (intFPS != 24) && (intFPS != 25) && (intFPS != 50) && (intFPS != 60) ) return false;
+
+ XMP_Uns32 framesPerDay = intFPS * 24*60*60;
+
+ int items, hours, minutes, seconds, frames;
+
+ if ( ! tmcdInfo->isDropFrame ) {
+ items = sscanf ( strValue, "%d:%d:%d:%d", &hours, &minutes, &seconds, &frames );
+ } else {
+ items = sscanf ( strValue, "%d;%d;%d;%d", &hours, &minutes, &seconds, &frames );
+ if ( intFPS == 30 ) {
+ framesPerDay = 2589408; // = 29.97 * 24*60*60
+ } else if ( intFPS == 60 ) {
+ framesPerDay = 5178816; // = 59.94 * 24*60*60
+ } else {
+ return false; // Dropframe can only apply to 29.97 and 59.94.
+ }
+ }
+
+ if ( items != 4 ) return false;
+ int minHigh = minutes / 10;
+ int minLow = minutes % 10;
+
+ XMP_Uns32 framesPerHour = framesPerDay / 24;
+ XMP_Uns32 framesPerTenMinutes = framesPerHour / 6;
+ XMP_Uns32 framesPerMinute = framesPerTenMinutes / 10;
+
+ tmcdInfo->timecodeSample = (hours * framesPerHour) + (minHigh * framesPerTenMinutes) +
+ (minLow * framesPerMinute) + (seconds * intFPS) + frames;
+
+ return true;
+
+} // DecomposeTimecode
+
+// =================================================================================================
+// FindTimecode_trak
+// =================
+//
+// Look for a well-formed timecode track, return the trak box ref.
+
+static MOOV_Manager::BoxRef FindTimecode_trak ( const MOOV_Manager & moovMgr )
+{
+
+ // Find a 'trak' box with a handler type of 'tmcd'.
+
+ MOOV_Manager::BoxInfo moovInfo;
+ MOOV_Manager::BoxRef moovRef = moovMgr.GetBox ( "moov", &moovInfo );
+ XMP_Assert ( moovRef != 0 );
+
+ MOOV_Manager::BoxInfo trakInfo;
+ MOOV_Manager::BoxRef trakRef;
+
+ size_t i = 0;
+ for ( ; i < moovInfo.childCount; ++i ) {
+
+ trakRef = moovMgr.GetNthChild ( moovRef, i, &trakInfo );
+ if ( trakRef == 0 ) return 0; // Sanity check, should not happen.
+ if ( trakInfo.boxType != ISOMedia::k_trak ) continue;
+
+ MOOV_Manager::BoxRef innerRef;
+ MOOV_Manager::BoxInfo innerInfo;
+
+ innerRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &innerInfo );
+ if ( innerRef == 0 ) continue;
+
+ innerRef = moovMgr.GetTypeChild ( innerRef, ISOMedia::k_hdlr, &innerInfo );
+ if ( (innerRef == 0) || (innerInfo.contentSize < sizeof ( MOOV_Manager::Content_hdlr )) ) continue;
+
+ const MOOV_Manager::Content_hdlr * hdlr = (MOOV_Manager::Content_hdlr*) innerInfo.content;
+ if ( hdlr->versionFlags != 0 ) continue;
+ if ( GetUns32BE ( &hdlr->handlerType ) == ISOMedia::k_tmcd ) break;
+
+ }
+
+ if ( i == moovInfo.childCount ) return 0;
+ return trakRef;
+
+} // FindTimecode_trak
+
+// =================================================================================================
+// FindTimecode_stbl
+// =================
+//
+// Look for the mdia/minf/stbl box within a well-formed timecode track, return the stbl box ref.
+
+static MOOV_Manager::BoxRef FindTimecode_stbl ( const MOOV_Manager & moovMgr )
+{
+
+ MOOV_Manager::BoxRef trakRef = FindTimecode_trak ( moovMgr );
+ if ( trakRef == 0 ) return 0;
+
+ MOOV_Manager::BoxInfo tempInfo;
+ MOOV_Manager::BoxRef tempRef, stblRef;
+
+ tempRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &tempInfo );
+ if ( tempRef == 0 ) return 0;
+
+ tempRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_minf, &tempInfo );
+ if ( tempRef == 0 ) return 0;
+
+ stblRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stbl, &tempInfo );
+ return stblRef;
+
+} // FindTimecode_stbl
+
+// =================================================================================================
+// FindTimecode_elst
+// =================
+//
+// Look for the edts/elst box within a well-formed timecode track, return the elst box ref.
+
+static MOOV_Manager::BoxRef FindTimecode_elst ( const MOOV_Manager & moovMgr )
+{
+
+ MOOV_Manager::BoxRef trakRef = FindTimecode_trak ( moovMgr );
+ if ( trakRef == 0 ) return 0;
+
+ MOOV_Manager::BoxInfo tempInfo;
+ MOOV_Manager::BoxRef tempRef, elstRef;
+
+ tempRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_edts, &tempInfo );
+ if ( tempRef == 0 ) return 0;
+
+ elstRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_elst, &tempInfo );
+ return elstRef;
+
+} // FindTimecode_elst
+
+// =================================================================================================
+// ImportTimecodeItems
+// ===================
+
+static bool ImportTimecodeItems ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo,
+ const TradQT_Manager & qtInfo, SXMPMeta * xmp )
+{
+ std::string xmpValue;
+ bool haveItem;
+ bool haveImports = false;
+
+ // The QT user data item '©REL' goes into xmpDM:tapeName, and the 'name' box at the end of the
+ // timecode sample description goes into xmpDM:altTapeName.
+ haveImports |= qtInfo.ImportSimpleXMP ( kQTilst_Reel, xmp, kXMP_NS_DM, "tapeName" );
+ if ( ! tmcdInfo.macName.empty() ) {
+ haveItem = ConvertFromMacLang ( tmcdInfo.macName, tmcdInfo.macLang, &xmpValue );
+ if ( haveItem ) {
+ xmp->SetProperty ( kXMP_NS_DM, "altTapeName", xmpValue.c_str() );
+ haveImports = true;
+ }
+ }
+
+ // The QT user data item '©TSC' goes into xmpDM:startTimeScale. If that isn't present, then
+ // the timecode sample description's timeScale is used.
+ haveItem = qtInfo.ImportSimpleXMP ( kQTilst_TimeScale, xmp, kXMP_NS_DM, "startTimeScale" );
+ if ( tmcdInfo.stsdBoxFound & (! haveItem) ) {
+ xmp->SetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", tmcdInfo.timeScale );
+ haveItem = true;
+ }
+ haveImports |= haveItem;
+
+ // The QT user data item '©TSZ' goes into xmpDM:startTimeSampleSize. If that isn't present, then
+ // the timecode sample description's frameDuration is used.
+ haveItem = qtInfo.ImportSimpleXMP ( kQTilst_TimeSize, xmp, kXMP_NS_DM, "startTimeSampleSize" );
+ if ( tmcdInfo.stsdBoxFound & (! haveItem) ) {
+ xmp->SetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", tmcdInfo.frameDuration );
+ haveItem = true;
+ }
+ haveImports |= haveItem;
+
+ const char * timeFormat;
+
+ // The Timecode struct type is used for xmpDM:startTimecode and xmpDM:altTimecode. For both, only
+ // the xmpDM:timeValue and xmpDM:timeFormat fields are set.
+
+ // The QT user data item '©TIM' goes into xmpDM:startTimecode/xmpDM:timeValue. This is an already
+ // formatted timecode string. The XMP values of xmpDM:startTimeScale, xmpDM:startTimeSampleSize,
+ // and xmpDM:startTimecode/xmpDM:timeValue are used to select the timeFormat value.
+ haveImports |= qtInfo.ImportSimpleXMP ( kQTilst_Timecode, xmp, kXMP_NS_DM, "startTimecode/xmpDM:timeValue" );
+ timeFormat = SelectTimeFormat ( *xmp );
+ if ( timeFormat != 0 ) {
+ xmp->SetProperty ( kXMP_NS_DM, "startTimecode/xmpDM:timeFormat", timeFormat );
+ haveImports = true;
+ }
+
+ if ( tmcdInfo.stsdBoxFound ) {
+
+ haveItem = ComposeTimecode ( tmcdInfo, &xmpValue );
+ if ( haveItem ) {
+ xmp->SetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeValue", xmpValue.c_str() );
+ haveImports = true;
+ }
+
+ timeFormat = SelectTimeFormat ( tmcdInfo );
+ if ( timeFormat != 0 ) {
+ xmp->SetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeFormat", timeFormat );
+ haveImports = true;
+ }
+
+ }
+
+ return haveImports;
+
+} // ImportTimecodeItems
+
+// =================================================================================================
+// ExportTimecodeItems
+// ===================
+
+static void ExportTimecodeItems ( const SXMPMeta & xmp, MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo,
+ TradQT_Manager * qtMgr, MOOV_Manager * moovMgr )
+{
+ // Export the items that go into the timecode track:
+ // - the timescale and frame duration in the first 'stsd' table entry
+ // - the 'name' box appended to the first 'stsd' table entry
+ // - the first timecode sample
+ // ! The QuickTime 'udta' timecode items are handled in ExportQuickTimeItems.
+
+ if ( ! tmcdInfo->stsdBoxFound ) return; // Don't make changes unless there is a well-formed timecode track.
+
+ MOOV_Manager::BoxRef stblRef = FindTimecode_stbl ( *moovMgr );
+ if ( stblRef == 0 ) return;
+
+ MOOV_Manager::BoxInfo stsdInfo;
+ MOOV_Manager::BoxRef stsdRef;
+
+ stsdRef = moovMgr->GetTypeChild ( stblRef, ISOMedia::k_stsd, &stsdInfo );
+ if ( stsdRef == 0 ) return;
+ if ( stsdInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsd_entry )) ) return;
+ if ( GetUns32BE ( stsdInfo.content + 4 ) == 0 ) return; // Make sure the entry count is non-zero.
+
+ MOOV_Manager::Content_stsd_entry * stsdRawEntry = (MOOV_Manager::Content_stsd_entry*) (stsdInfo.content + 8);
+
+ XMP_Uns32 stsdEntrySize = GetUns32BE ( &stsdRawEntry->entrySize );
+ if ( stsdEntrySize > (stsdInfo.contentSize - 4) ) stsdEntrySize = stsdInfo.contentSize - 4;
+ if ( stsdEntrySize < sizeof ( MOOV_Manager::Content_stsd_entry ) ) return;
+
+ bool ok, haveScale = false, haveDuration = false;
+ std::string xmpValue;
+ XMP_Int64 int64; // Used to allow UInt32 values, GetProperty_Int is SInt32.
+
+ // The tmcdInfo timeScale field is set from xmpDM:startTimeScale.
+ ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", &int64, 0 );
+ if ( ok && (int64 <= 0xFFFFFFFF) ) {
+ haveScale = true;
+ if ( tmcdInfo->timeScale != 0 ) { // Entry must not be created if not existing before
+ tmcdInfo->timeScale = (XMP_Uns32)int64;
+ PutUns32BE ( tmcdInfo->timeScale, (void*)&stsdRawEntry->timeScale );
+ moovMgr->NoteChange();
+ }
+ }
+
+ // The tmcdInfo frameDuration field is set from xmpDM:startTimeSampleSize.
+ ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", &int64, 0 );
+ if ( ok && (int64 <= 0xFFFFFFFF) ) {
+ haveDuration = true;
+ if ( tmcdInfo->frameDuration != 0 ) { // Entry must not be created if not existing before
+ tmcdInfo->frameDuration = (XMP_Uns32)int64;
+ PutUns32BE ( tmcdInfo->frameDuration, (void*)&stsdRawEntry->frameDuration );
+ moovMgr->NoteChange();
+ }
+ }
+
+ // The tmcdInfo frameCount field is a simple ratio of the timeScale and frameDuration.
+ if ( (haveScale & haveDuration) && (tmcdInfo->frameDuration != 0) ) {
+ float floatScale = (float) tmcdInfo->timeScale;
+ float floatDuration = (float) tmcdInfo->frameDuration;
+ XMP_Uns8 newCount = (XMP_Uns8) ( (floatScale / floatDuration) + 0.5 );
+ if ( newCount != stsdRawEntry->frameCount ) {
+ stsdRawEntry->frameCount = newCount;
+ moovMgr->NoteChange();
+ }
+ }
+
+ // The tmcdInfo isDropFrame flag is set from xmpDM:altTimecode/xmpDM:timeValue. The timeScale
+ // and frameDuration must be updated first, they are used by DecomposeTimecode. Compute the new
+ // UInt32 timecode sample, but it gets written to the file later by UpdateFile.
+
+ ok = xmp.GetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeValue", &xmpValue, 0 );
+ if ( ok && (xmpValue.size() == 11) ) {
+
+ bool oldDropFrame = tmcdInfo->isDropFrame;
+ tmcdInfo->isDropFrame = false;
+ if ( xmpValue[8] == ';' ) tmcdInfo->isDropFrame = true;
+ if ( oldDropFrame != tmcdInfo->isDropFrame ) {
+ XMP_Uns32 flags = GetUns32BE ( &stsdRawEntry->flags );
+ flags = (flags & 0xFFFFFFFE) | (XMP_Uns32)tmcdInfo->isDropFrame;
+ PutUns32BE ( flags, (void*)&stsdRawEntry->flags );
+ moovMgr->NoteChange();
+ }
+
+ XMP_Uns32 oldSample = tmcdInfo->timecodeSample;
+ ok = DecomposeTimecode ( xmpValue.c_str(), tmcdInfo );
+ if ( ok && (oldSample != tmcdInfo->timecodeSample) ) moovMgr->NoteChange();
+
+ }
+
+ // The 'name' box attached to the first 'stsd' table entry is set from xmpDM:altTapeName.
+
+ bool replaceNameBox = false;
+
+ ok = xmp.GetProperty ( kXMP_NS_DM, "altTapeName", &xmpValue, 0 );
+ if ( (! ok) || xmpValue.empty() ) {
+ if ( tmcdInfo->nameOffset != 0 ) replaceNameBox = true; // No XMP, get rid of existing name.
+ } else {
+ std::string macValue;
+ ok = ConvertToMacLang ( xmpValue, tmcdInfo->macLang, &macValue );
+ if ( ok && (macValue != tmcdInfo->macName) ) {
+ tmcdInfo->macName = macValue;
+ replaceNameBox = true; // Write changed name.
+ }
+ }
+
+ if ( replaceNameBox ) {
+
+ // To replace the 'name' box we have to create an entire new 'stsd' box, and attach the
+ // new name to the first 'stsd' table entry. The 'name' box content is a UInt16 text length,
+ // UInt16 language code, and Mac encoded text with no nul termination.
+
+ if ( tmcdInfo->macName.size() > 0xFFFF ) tmcdInfo->macName.erase ( 0xFFFF );
+
+ ISOMedia::BoxInfo oldNameInfo;
+ XMP_Assert ( (oldNameInfo.headerSize == 0) && (oldNameInfo.contentSize == 0) );
+ if ( tmcdInfo->nameOffset != 0 ) {
+ const XMP_Uns8 * oldNamePtr = stsdInfo.content + tmcdInfo->nameOffset;
+ const XMP_Uns8 * oldNameLimit = stsdInfo.content + stsdInfo.contentSize;
+ (void) ISOMedia::GetBoxInfo ( oldNamePtr, oldNameLimit, &oldNameInfo );
+ }
+
+ XMP_Uns32 oldNameBoxSize = (XMP_Uns32)oldNameInfo.headerSize + (XMP_Uns32)oldNameInfo.contentSize;
+ XMP_Uns32 newNameBoxSize = 0;
+ if ( ! tmcdInfo->macName.empty() ) newNameBoxSize = 4+4 + 2+2 + (XMP_Uns32)tmcdInfo->macName.size();
+
+ XMP_Uns32 stsdNewContentSize = stsdInfo.contentSize - oldNameBoxSize + newNameBoxSize;
+ RawDataBlock stsdNewContent;
+ stsdNewContent.assign ( stsdNewContentSize, 0 ); // Get the space allocated, direct fill below.
+
+ XMP_Uns32 stsdPrefixSize = tmcdInfo->nameOffset;
+ if ( tmcdInfo->nameOffset == 0 ) stsdPrefixSize = 4+4 + sizeof ( MOOV_Manager::Content_stsd_entry );
+
+ XMP_Uns32 oldSuffixOffset = stsdPrefixSize + oldNameBoxSize;
+ XMP_Uns32 newSuffixOffset = stsdPrefixSize + newNameBoxSize;
+ XMP_Uns32 stsdSuffixSize = stsdInfo.contentSize - oldSuffixOffset;
+
+ memcpy ( &stsdNewContent[0], stsdInfo.content, stsdPrefixSize );
+ if ( stsdSuffixSize != 0 ) memcpy ( &stsdNewContent[newSuffixOffset], (stsdInfo.content + oldSuffixOffset), stsdSuffixSize );
+
+ XMP_Uns32 newEntrySize = stsdEntrySize - oldNameBoxSize + newNameBoxSize;
+ MOOV_Manager::Content_stsd_entry * stsdNewEntry = (MOOV_Manager::Content_stsd_entry*) (&stsdNewContent[0] + 8);
+ PutUns32BE ( newEntrySize, &stsdNewEntry->entrySize );
+
+ if ( newNameBoxSize != 0 ) {
+ PutUns32BE ( newNameBoxSize, &stsdNewContent[stsdPrefixSize] );
+ PutUns32BE ( ISOMedia::k_name, &stsdNewContent[stsdPrefixSize+4] );
+ PutUns16BE ( (XMP_Uns16)tmcdInfo->macName.size(), &stsdNewContent[stsdPrefixSize+8] );
+ PutUns16BE ( tmcdInfo->macLang, &stsdNewContent[stsdPrefixSize+10] );
+ memcpy ( &stsdNewContent[stsdPrefixSize+12], tmcdInfo->macName.c_str(), tmcdInfo->macName.size() );
+ }
+
+ moovMgr->SetBox ( stsdRef, &stsdNewContent[0], stsdNewContentSize );
+
+ }
+
+} // ExportTimecodeItems
+
+// =================================================================================================
+// 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 bool ImportCr8rItems ( const MOOV_Manager & moovMgr, SXMPMeta * xmp )
+{
+ bool haveXMP = false;
+ std::string fieldPath;
+
+ MOOV_Manager::BoxInfo infoPrmL, infoCr8r;
+ MOOV_Manager::BoxRef refPrmL = moovMgr.GetBox ( "moov/udta/PrmL", &infoPrmL );
+ MOOV_Manager::BoxRef refCr8r = moovMgr.GetBox ( "moov/udta/Cr8r", &infoCr8r );
+
+ bool havePrmL = ( (refPrmL != 0) && (infoPrmL.contentSize == sizeof ( PrmLBoxContent )) );
+ bool haveCr8r = ( (refCr8r != 0) && (infoCr8r.contentSize == sizeof ( Cr8rBoxContent )) );
+
+ if ( havePrmL ) {
+
+ PrmLBoxContent rawPrmL;
+ XMP_Assert ( sizeof ( rawPrmL ) == 282 );
+ XMP_Assert ( sizeof ( rawPrmL.filePath ) == 260 );
+ memcpy ( &rawPrmL, infoPrmL.content, sizeof ( rawPrmL ) );
+ if ( rawPrmL.magic != 0xBEEFCAFE ) {
+ Flip4 ( &rawPrmL.exportType ); // The only numeric field that we care about.
+ }
+
+ rawPrmL.filePath[259] = 0; // Ensure a terminating nul.
+ if ( rawPrmL.filePath[0] != 0 ) {
+ if ( rawPrmL.filePath[0] == '/' ) {
+ haveXMP = true;
+ SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom",
+ kXMP_NS_CreatorAtom, "posixProjectPath", &fieldPath );
+ if ( ! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() ) ) {
+ xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath.c_str(), rawPrmL.filePath );
+ }
+ } else if ( XMP_LitNMatch ( rawPrmL.filePath, "\\\\?\\", 4 ) ) {
+ haveXMP = true;
+ SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "windowsAtom",
+ kXMP_NS_CreatorAtom, "uncProjectPath", &fieldPath );
+ if ( ! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() ) ) {
+ xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath.c_str(), rawPrmL.filePath );
+ }
+ }
+ }
+
+ 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;
+ SXMPUtils::ComposeStructFieldPath ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", &fieldPath );
+ if ( ! xmp->DoesPropertyExist ( kXMP_NS_DM, fieldPath.c_str() ) ) {
+ xmp->SetProperty ( kXMP_NS_DM, fieldPath.c_str(), exportStr );
+ }
+ }
+
+ }
+
+ if ( haveCr8r ) {
+
+ Cr8rBoxContent rawCr8r;
+ XMP_Assert ( sizeof ( rawCr8r ) == 84 );
+ XMP_Assert ( sizeof ( rawCr8r.fileExt ) == 16 );
+ XMP_Assert ( sizeof ( rawCr8r.appOptions ) == 16 );
+ XMP_Assert ( sizeof ( rawCr8r.appName ) == 32 );
+ memcpy ( &rawCr8r, infoCr8r.content, sizeof ( rawCr8r ) );
+ if ( rawCr8r.magic != 0xBEEFCAFE ) {
+ Flip4 ( &rawCr8r.creatorCode ); // The only numeric fields that we care about.
+ Flip4 ( &rawCr8r.appleEvent );
+ }
+
+ std::string fieldPath;
+
+ SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &fieldPath );
+ if ( (rawCr8r.creatorCode != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) {
+ haveXMP = true;
+ xmp->SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.creatorCode ); // ! Unsigned trickery.
+ }
+
+ SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &fieldPath );
+ if ( (rawCr8r.appleEvent != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) {
+ haveXMP = true;
+ xmp->SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.appleEvent ); // ! Unsigned trickery.
+ }
+
+ rawCr8r.fileExt[15] = 0; // Ensure a terminating nul.
+ SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fieldPath );
+ if ( (rawCr8r.fileExt[0] != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) {
+ haveXMP = true;
+ xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath.c_str(), rawCr8r.fileExt );
+ }
+
+ rawCr8r.appOptions[15] = 0; // Ensure a terminating nul.
+ SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &fieldPath );
+ if ( (rawCr8r.appOptions[0] != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) {
+ haveXMP = true;
+ xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath.c_str(), rawCr8r.appOptions );
+ }
+
+ rawCr8r.appName[31] = 0; // Ensure a terminating nul.
+ if ( (rawCr8r.appName[0] != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_XMP, "CreatorTool" )) ) {
+ haveXMP = true;
+ xmp->SetProperty ( kXMP_NS_XMP, "CreatorTool", rawCr8r.appName );
+ }
+
+ }
+
+ return haveXMP;
+
+} // ImportCr8rItems
+
+// =================================================================================================
+// ExportCr8rItems
+// ===============
+
+static inline void SetBufferedString ( char * dest, const std::string source, size_t limit )
+{
+ 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 ExportCr8rItems ( const SXMPMeta & xmp, MOOV_Manager * moovMgr )
+{
+ 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 );
+
+ MOOV_Manager::BoxInfo infoCr8r;
+ MOOV_Manager::BoxRef refCr8r = moovMgr->GetBox ( "moov/udta/Cr8r", &infoCr8r );
+ bool haveOldCr8r = ( (refCr8r != 0) && (infoCr8r.contentSize == sizeof ( Cr8rBoxContent )) );
+
+ if ( ! haveNewCr8r ) {
+ if ( haveOldCr8r ) {
+ MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", 0 );
+ moovMgr->DeleteTypeChild ( udtaRef, 0x43723872 /* 'Cr8r' */ );
+ }
+ return;
+ }
+
+ Cr8rBoxContent newCr8r;
+ const Cr8rBoxContent * oldCr8r = (Cr8rBoxContent*) infoCr8r.content;
+
+ if ( ! haveOldCr8r ) {
+
+ memset ( &newCr8r, 0, sizeof(newCr8r) );
+ newCr8r.magic = MakeUns32BE ( 0xBEEFCAFE );
+ newCr8r.size = MakeUns32BE ( sizeof ( newCr8r ) );
+ newCr8r.majorVer = MakeUns16BE ( 1 );
+
+ } else {
+
+ memcpy ( &newCr8r, oldCr8r, sizeof(newCr8r) );
+ if ( GetUns32BE ( &newCr8r.magic ) != 0xBEEFCAFE ) { // Make sure we write BE numbers.
+ Flip4 ( &newCr8r.magic );
+ Flip4 ( &newCr8r.size );
+ Flip2 ( &newCr8r.majorVer );
+ Flip2 ( &newCr8r.minorVer );
+ Flip4 ( &newCr8r.creatorCode );
+ Flip4 ( &newCr8r.appleEvent );
+ }
+
+ }
+
+ if ( ! creatorCode.empty() ) {
+ newCr8r.creatorCode = MakeUns32BE ( (XMP_Uns32) strtoul ( creatorCode.c_str(), 0, 0 ) );
+ }
+
+ if ( ! appleEvent.empty() ) {
+ newCr8r.appleEvent = MakeUns32BE ( (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 ) );
+
+ moovMgr->SetBox ( "moov/udta/Cr8r", &newCr8r, sizeof(newCr8r) );
+
+} // ExportCr8rItems
+
+// =================================================================================================
+// GetAtomInfo
+// ===========
+
+struct AtomInfo {
+ XMP_Int64 atomSize;
+ XMP_Uns32 atomType;
+ bool hasLargeSize;
+};
+
+enum { // ! Do not rearrange, code depends on this order.
+ kBadQT_NoError = 0, // No errors.
+ kBadQT_SmallInner = 1, // An extra 1..7 bytes at the end of an inner span.
+ kBadQT_LargeInner = 2, // More serious inner garbage, found as invalid atom length.
+ kBadQT_SmallOuter = 3, // An extra 1..7 bytes at the end of the file.
+ kBadQT_LargeOuter = 4 // More serious EOF garbage, found as invalid atom length.
+};
+typedef XMP_Uns8 QTErrorMode;
+
+static QTErrorMode GetAtomInfo ( XMP_IO* qtFile, XMP_Int64 spanSize, int nesting, AtomInfo * info )
+{
+ QTErrorMode status = kBadQT_NoError;
+ XMP_Uns8 buffer [8];
+
+ info->hasLargeSize = false;
+
+ qtFile->ReadAll ( buffer, 8 ); // Will throw if 8 bytes aren't available.
+ info->atomSize = GetUns32BE ( &buffer[0] ); // ! Yes, the initial size is big endian UInt32.
+ info->atomType = GetUns32BE ( &buffer[4] );
+
+ if ( info->atomSize == 0 ) { // Does the atom extend to EOF?
+
+ if ( nesting != 0 ) return kBadQT_LargeInner;
+ info->atomSize = spanSize; // This outer atom goes to EOF.
+
+ } else if ( info->atomSize == 1 ) { // Does the atom have a 64-bit size?
+
+ if ( spanSize < 16 ) { // Is there room in the span for the 16 byte header?
+ status = kBadQT_LargeInner;
+ if ( nesting == 0 ) status += 2; // Convert to "outer".
+ return status;
+ }
+
+ qtFile->ReadAll ( buffer, 8 );
+ info->atomSize = (XMP_Int64) GetUns64BE ( &buffer[0] );
+ info->hasLargeSize = true;
+
+ }
+
+ XMP_Assert ( status == kBadQT_NoError );
+ return status;
+
+} // GetAtomInfo
+
+// =================================================================================================
+// CheckAtomList
+// =============
+//
+// Check that a sequence of atoms fills a given span. The I/O position must be at the start of the
+// span, it is left just past the span on success. Recursive checks are done for top level 'moov'
+// atoms, and second level 'udta' atoms ('udta' inside 'moov').
+//
+// Checking continues for "small inner" errors. They will be reported if no other kinds of errors
+// are found, otherwise the other error is reported. Checking is immediately aborted for any "large"
+// error. The rationale is that QuickTime can apparently handle small inner errors. They might be
+// arise from updates that shorten an atom by less than 8 bytes. Larger shrinkage should introduce a
+// 'free' atom.
+
+static QTErrorMode CheckAtomList ( XMP_IO* qtFile, XMP_Int64 spanSize, int nesting )
+{
+ QTErrorMode status = kBadQT_NoError;
+ AtomInfo info;
+
+ const static XMP_Uns32 moovAtomType = 0x6D6F6F76; // ! Don't use MakeUns32BE, already big endian.
+ const static XMP_Uns32 udtaAtomType = 0x75647461;
+
+ for ( ; spanSize >= 8; spanSize -= info.atomSize ) {
+
+ QTErrorMode atomStatus = GetAtomInfo ( qtFile, spanSize, nesting, &info );
+ if ( atomStatus != kBadQT_NoError ) return atomStatus;
+
+ XMP_Int64 headerSize = 8;
+ if ( info.hasLargeSize ) headerSize = 16;
+
+ if ( (info.atomSize < headerSize) || (info.atomSize > spanSize) ) {
+ status = kBadQT_LargeInner;
+ if ( nesting == 0 ) status += 2; // Convert to "outer".
+ return status;
+ }
+
+ bool doChildren = false;
+ if ( (nesting == 0) && (info.atomType == moovAtomType) ) doChildren = true;
+ if ( (nesting == 1) && (info.atomType == udtaAtomType) ) doChildren = true;
+
+ XMP_Int64 dataSize = info.atomSize - headerSize;
+
+ if ( ! doChildren ) {
+ qtFile->Seek ( dataSize, kXMP_SeekFromCurrent );
+ } else {
+ QTErrorMode innerStatus = CheckAtomList ( qtFile, dataSize, nesting+1 );
+ if ( innerStatus > kBadQT_SmallInner ) return innerStatus; // Quit for serious errors.
+ if ( status == kBadQT_NoError ) status = innerStatus; // Remember small inner errors.
+ }
+
+ }
+
+ XMP_Assert ( status <= kBadQT_SmallInner ); // Else already returned.
+ // ! Make sure inner kBadQT_SmallInner is propagated if this span is OK.
+
+ if ( spanSize != 0 ) {
+ qtFile->Seek ( spanSize, kXMP_SeekFromCurrent ); // ! Skip the trailing garbage of this span.
+ status = kBadQT_SmallInner;
+ if ( spanSize >= 8 ) status = kBadQT_LargeInner;
+ if ( nesting == 0 ) status += 2; // Convert to "outer".
+ }
+
+ return status;
+
+} // CheckAtomList
+
+// =================================================================================================
+// AttemptFileRepair
+// =================
+
+static void AttemptFileRepair ( XMP_IO* qtFile, XMP_Int64 fileSpace, QTErrorMode status )
+{
+
+ switch ( status ) {
+ case kBadQT_NoError : return; // Sanity check.
+ case kBadQT_SmallInner : return; // Fixed in normal update code for the 'udta' box.
+ case kBadQT_LargeInner : XMP_Throw ( "Can't repair QuickTime file", kXMPErr_BadFileFormat );
+ case kBadQT_SmallOuter : break; // Truncate file below.
+ case kBadQT_LargeOuter : break; // Truncate file below.
+ default : XMP_Throw ( "Invalid QuickTime error mode", kXMPErr_InternalFailure );
+ }
+
+ AtomInfo info;
+ XMP_Int64 headerSize;
+
+ // Process the top level atoms until an error is found.
+
+ qtFile->Rewind();
+
+ for ( ; fileSpace >= 8; fileSpace -= info.atomSize ) {
+
+ QTErrorMode atomStatus = GetAtomInfo ( qtFile, fileSpace, 0, &info );
+
+ headerSize = 8; // ! Set this before checking atomStatus, used after the loop.
+ if ( info.hasLargeSize ) headerSize = 16;
+
+ if ( atomStatus != kBadQT_NoError ) break;
+ if ( (info.atomSize < headerSize) || (info.atomSize > fileSpace) ) break;
+
+ XMP_Int64 dataSize = info.atomSize - headerSize;
+ qtFile->Seek ( dataSize, kXMP_SeekFromCurrent );
+
+ }
+
+ // Truncate the file. If fileSpace >= 8 then the loop exited early due to a bad atom, seek back
+ // to the atom's start. Otherwise, the loop exited because no more atoms are possible, no seek.
+
+ if ( fileSpace >= 8 ) qtFile->Seek ( -headerSize, kXMP_SeekFromCurrent );
+ XMP_Int64 currPos = qtFile->Offset();
+ qtFile->Truncate ( currPos );
+
+} // AttemptFileRepair
+
+// =================================================================================================
+// CheckQTFileStructure
+// ====================
+
+static void CheckQTFileStructure ( XMPFileHandler * thiz, bool doRepair )
+{
+ XMPFiles * parent = thiz->parent;
+ XMP_IO* fileRef = parent->ioRef;
+ XMP_Int64 fileSize = fileRef->Length();
+
+ // Check the basic file structure and try to repair if asked.
+
+ fileRef->Rewind();
+ QTErrorMode status = CheckAtomList ( fileRef, fileSize, 0 );
+
+ if ( status != kBadQT_NoError ) {
+ if ( doRepair || (status == kBadQT_SmallInner) || (status == kBadQT_SmallOuter) ) {
+ AttemptFileRepair ( fileRef, fileSize, status ); // Will throw if the attempt fails.
+ } else if ( status != kBadQT_SmallInner ) {
+ XMP_Throw ( "Ill-formed QuickTime file", kXMPErr_BadFileFormat );
+ } else {
+ return; // ! Ignore these, QT seems to be able to handle them.
+ // *** Might want to throw for check-only, ignore when repairing.
+ }
+ }
+
+} // CheckQTFileStructure;
+
+// =================================================================================================
+// CheckFinalBox
+// =============
+//
+// Before appending anything new, check if the final top level box has a "to EoF" length. If so, fix
+// it to have an explicit length.
+
+static void CheckFinalBox ( XMP_IO* fileRef, XMP_AbortProc abortProc, void * abortArg )
+{
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_Uns64 fileSize = fileRef->Length();
+
+ // Find the last 2 boxes in the file. Need the previous to last in case it is an Apple 'wide' box.
+
+ XMP_Uns64 prevPos, lastPos, nextPos;
+ ISOMedia::BoxInfo prevBox, lastBox;
+ XMP_Uns8 buffer [16]; // Enough to create an extended header.
+
+ memset ( &prevBox, 0, sizeof(prevBox) ); // AUDIT: Using sizeof(prevBox) is safe.
+ memset ( &lastBox, 0, sizeof(lastBox) ); // AUDIT: Using sizeof(lastBox) is safe.
+ prevPos = lastPos = nextPos = 0;
+ while ( nextPos != fileSize ) {
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_MetaHandler::CheckFinalBox - User abort", kXMPErr_UserAbort );
+ }
+ prevBox = lastBox;
+ prevPos = lastPos;
+ lastPos = nextPos;
+ nextPos = ISOMedia::GetBoxInfo ( fileRef, lastPos, fileSize, &lastBox, true /* throw errors */ );
+ }
+
+ // See if the last box is valid and has a "to EoF" size.
+
+ if ( lastBox.headerSize < 8 ) XMP_Throw ( "MPEG-4 final box is invalid", kXMPErr_EnforceFailure );
+ fileRef->Seek ( lastPos, kXMP_SeekFromStart );
+ fileRef->Read ( buffer, 4 );
+ XMP_Uns64 lastSize = GetUns32BE ( &buffer[0] ); // ! Yes, the file has a 32-bit value.
+ if ( lastSize != 0 ) return;
+
+ // Have a final "to EoF" box, try to write the explicit size.
+
+ lastSize = lastBox.headerSize + lastBox.contentSize;
+ if ( lastSize <= 0xFFFFFFFFUL ) {
+
+ // Fill in the 32-bit exact size.
+ PutUns32BE ( (XMP_Uns32)lastSize, &buffer[0] );
+ fileRef->Seek ( lastPos, kXMP_SeekFromStart );
+ fileRef->Write ( buffer, 4 );
+
+ } else {
+
+ // Try to convert to using an extended header.
+
+ if ( (prevBox.boxType != ISOMedia::k_wide) || (prevBox.headerSize != 8) || (prevBox.contentSize != 0) ) {
+ XMP_Throw ( "Can't expand final box header", kXMPErr_EnforceFailure );
+ }
+ XMP_Assert ( prevPos == (lastPos - 8) );
+
+ PutUns32BE ( 1, &buffer[0] );
+ PutUns32BE ( lastBox.boxType, &buffer[4] );
+ PutUns64BE ( lastSize, &buffer[8] );
+ fileRef->Seek ( prevPos, kXMP_SeekFromStart );
+ fileRef->Write ( buffer, 16 );
+
+ }
+
+} // CheckFinalBox
+
+// =================================================================================================
+// WriteBoxHeader
+// ==============
+
+static void WriteBoxHeader ( XMP_IO* fileRef, XMP_Uns32 boxType, XMP_Uns64 boxSize )
+{
+ XMP_Uns32 u32;
+ XMP_Uns64 u64;
+ XMP_Enforce ( boxSize >= 8 ); // The size must be the full size, not just the content.
+
+ if ( boxSize <= 0xFFFFFFFF ) {
+
+ u32 = MakeUns32BE ( (XMP_Uns32)boxSize );
+ fileRef->Write ( &u32, 4 );
+ u32 = MakeUns32BE ( boxType );
+ fileRef->Write ( &u32, 4 );
+
+ } else {
+
+ u32 = MakeUns32BE ( 1 );
+ fileRef->Write ( &u32, 4 );
+ u32 = MakeUns32BE ( boxType );
+ fileRef->Write ( &u32, 4 );
+ u64 = MakeUns64BE ( boxSize );
+ fileRef->Write ( &u64, 8 );
+
+ }
+
+} // WriteBoxHeader
+
+// =================================================================================================
+// WipeBoxFree
+// ===========
+//
+// Change the box's type to 'free' (or create a 'free' box) and zero the content.
+
+static XMP_Uns8 kZeroes [64*1024]; // C semantics guarantee zero initialization.
+
+static void WipeBoxFree ( XMP_IO* fileRef, XMP_Uns64 boxOffset, XMP_Uns32 boxSize )
+{
+ if ( boxSize == 0 ) return;
+ XMP_Enforce ( boxSize >= 8 );
+
+ fileRef->Seek ( boxOffset, kXMP_SeekFromStart );
+ XMP_Uns32 u32;
+ u32 = MakeUns32BE ( boxSize ); // ! The actual size should not change, but might have had a long header.
+ fileRef->Write ( &u32, 4 );
+ u32 = MakeUns32BE ( ISOMedia::k_free );
+ fileRef->Write ( &u32, 4 );
+
+ XMP_Uns32 ioCount = sizeof ( kZeroes );
+ for ( boxSize -= 8; boxSize > 0; boxSize -= ioCount ) {
+ if ( ioCount > boxSize ) ioCount = boxSize;
+ fileRef->Write ( &kZeroes[0], ioCount );
+ }
+
+} // WipeBoxFree
+
+// =================================================================================================
+// CreateFreeSpaceList
+// ===================
+
+struct SpaceInfo {
+ XMP_Uns64 offset, size;
+ SpaceInfo() : offset(0), size(0) {};
+ SpaceInfo ( XMP_Uns64 _offset, XMP_Uns64 _size ) : offset(_offset), size(_size) {};
+};
+
+typedef std::vector<SpaceInfo> FreeSpaceList;
+
+static void CreateFreeSpaceList ( XMP_IO* fileRef, XMP_Uns64 fileSize,
+ XMP_Uns64 oldOffset, XMP_Uns32 oldSize, FreeSpaceList * spaceList )
+{
+ XMP_Uns64 boxPos, boxNext, adjacentFree;
+ ISOMedia::BoxInfo currBox;
+
+ fileRef->Rewind();
+ spaceList->clear();
+
+ for ( boxPos = 0; boxPos < fileSize; boxPos = boxNext ) {
+
+ boxNext = ISOMedia::GetBoxInfo ( fileRef, boxPos, fileSize, &currBox, true /* throw errors */ );
+ XMP_Uns64 currSize = currBox.headerSize + currBox.contentSize;
+
+ if ( (currBox.boxType == ISOMedia::k_free) ||
+ (currBox.boxType == ISOMedia::k_skip) ||
+ ((boxPos == oldOffset) && (currSize == oldSize)) ) {
+
+ if ( spaceList->empty() || (boxPos != adjacentFree) ) {
+ spaceList->push_back ( SpaceInfo ( boxPos, currSize ) );
+ adjacentFree = boxPos + currSize;
+ } else {
+ SpaceInfo * lastSpace = &spaceList->back();
+ lastSpace->size += currSize;
+ }
+
+ }
+
+ }
+
+} // CreateFreeSpaceList
+
+// =================================================================================================
+// MPEG4_MetaHandler::CacheFileData
+// ================================
+//
+// There are 3 file variants: normal ISO Base Media, modern QuickTime, and classic QuickTime. The
+// XMP is placed differently between the ISO and two QuickTime forms, and there is different but not
+// colliding native metadata. The entire 'moov' subtree is cached, along with the top level 'uuid'
+// box of XMP if present.
+
+void MPEG4_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( ! this->containsXMP );
+
+ XMPFiles * parent = this->parent;
+ XMP_OptionBits openFlags = parent->openFlags;
+
+ XMP_IO* fileRef = parent->ioRef;
+
+ XMP_AbortProc abortProc = parent->abortProc;
+ void * abortArg = parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ // First do some special case repair to QuickTime files, based on bad files in the wild.
+
+ const bool isUpdate = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenForUpdate );
+ const bool doRepair = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenRepairFile );
+
+ if ( isUpdate && (parent->format == kXMP_MOVFile) ) {
+ CheckQTFileStructure ( this, doRepair ); // Will throw for failure.
+ }
+
+ // Cache the top level 'moov' and 'uuid'/XMP boxes.
+
+ XMP_Uns64 fileSize = fileRef->Length();
+
+ XMP_Uns64 boxPos, boxNext;
+ ISOMedia::BoxInfo currBox;
+
+ bool xmpOnly = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenOnlyXMP );
+ bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
+
+ bool uuidFound = (! haveISOFile); // Ignore the XMP 'uuid' box for QuickTime files.
+ bool moovIgnored = (xmpOnly & haveISOFile); // Ignore the 'moov' box for XMP-only ISO files.
+ bool moovFound = moovIgnored;
+
+ for ( boxPos = 0; boxPos < fileSize; boxPos = boxNext ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
+ }
+
+
+ boxNext = ISOMedia::GetBoxInfo ( fileRef, boxPos, fileSize, &currBox );
+
+ if ( (! moovFound) && (currBox.boxType == ISOMedia::k_moov) ) {
+
+ XMP_Uns64 fullMoovSize = currBox.headerSize + currBox.contentSize;
+ if ( fullMoovSize > moovBoxSizeLimit ) { // From here on we know 32-bit offsets are safe.
+ XMP_Throw ( "Oversize 'moov' box", kXMPErr_EnforceFailure );
+ }
+
+ this->moovMgr.fullSubtree.assign ( (XMP_Uns32)fullMoovSize, 0 );
+ fileRef->Seek ( boxPos, kXMP_SeekFromStart );
+ fileRef->Read ( &this->moovMgr.fullSubtree[0], (XMP_Uns32)fullMoovSize );
+
+ this->moovBoxPos = boxPos;
+ this->moovBoxSize = (XMP_Uns32)fullMoovSize;
+ moovFound = true;
+ if ( uuidFound ) break; // Exit the loop when both are found.
+
+ } else if ( (! uuidFound) && (currBox.boxType == ISOMedia::k_uuid) ) {
+
+ if ( currBox.contentSize < 16 ) continue;
+
+ XMP_Uns8 uuid [16];
+ fileRef->ReadAll ( uuid, 16 );
+ if ( memcmp ( uuid, ISOMedia::k_xmpUUID, 16 ) != 0 ) continue; // Check for the XMP GUID.
+
+ XMP_Uns64 fullUuidSize = currBox.headerSize + currBox.contentSize;
+ if ( fullUuidSize > moovBoxSizeLimit ) { // From here on we know 32-bit offsets are safe.
+ XMP_Throw ( "Oversize XMP 'uuid' box", kXMPErr_EnforceFailure );
+ }
+
+ this->packetInfo.offset = boxPos + currBox.headerSize + 16; // The 16 is for the UUID.
+ this->packetInfo.length = (XMP_Int32) (currBox.contentSize - 16);
+
+ this->xmpPacket.assign ( this->packetInfo.length, ' ' );
+ fileRef->ReadAll ( (void*)this->xmpPacket.data(), this->packetInfo.length );
+
+ this->xmpBoxPos = boxPos;
+ this->xmpBoxSize = (XMP_Uns32)fullUuidSize;
+ uuidFound = true;
+ if ( moovFound ) break; // Exit the loop when both are found.
+
+ }
+
+ }
+
+ if ( (! moovFound) && (! moovIgnored) ) XMP_Throw ( "No 'moov' box", kXMPErr_BadFileFormat );
+
+} // MPEG4_MetaHandler::CacheFileData
+
+// =================================================================================================
+// MPEG4_MetaHandler::ProcessXMP
+// =============================
+
+void MPEG4_MetaHandler::ProcessXMP()
+{
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ XMPFiles * parent = this->parent;
+ XMP_OptionBits openFlags = parent->openFlags;
+
+ bool xmpOnly = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenOnlyXMP );
+ bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
+
+ // Process the cached XMP (from the 'uuid' box) if that is all we want and this is an ISO file.
+
+ if ( xmpOnly & haveISOFile ) {
+
+ this->containsXMP = this->havePreferredXMP = (this->packetInfo.length != 0);
+
+ if ( this->containsXMP ) {
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ this->xmpObj.DeleteProperty ( kXMP_NS_XMP, "NativeDigests" ); // No longer used.
+ }
+
+ return;
+
+ }
+
+ // Parse the cached 'moov' subtree, parse the preferred XMP.
+
+ if ( this->moovMgr.fullSubtree.empty() ) XMP_Throw ( "No 'moov' box", kXMPErr_BadFileFormat );
+ this->moovMgr.ParseMemoryTree ( this->fileMode );
+
+ if ( (this->xmpBoxPos == 0) || (! haveISOFile) ) {
+
+ // Look for the QuickTime moov/uuid/XMP_ box.
+
+ MOOV_Manager::BoxInfo xmpInfo;
+ MOOV_Manager::BoxRef xmpRef = this->moovMgr.GetBox ( "moov/udta/XMP_", &xmpInfo );
+
+ if ( (xmpRef != 0) && (xmpInfo.contentSize != 0) ) {
+
+ this->xmpBoxPos = this->moovBoxPos + this->moovMgr.GetParsedOffset ( xmpRef );
+ this->packetInfo.offset = this->xmpBoxPos + this->moovMgr.GetHeaderSize ( xmpRef );
+ this->packetInfo.length = xmpInfo.contentSize;
+
+ this->xmpPacket.assign ( (char*)xmpInfo.content, this->packetInfo.length );
+ this->havePreferredXMP = (! haveISOFile);
+
+ }
+
+ }
+
+ if ( this->xmpBoxPos != 0 ) {
+ this->containsXMP = true;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ this->xmpObj.DeleteProperty ( kXMP_NS_XMP, "NativeDigests" ); // No longer used.
+ }
+
+ // Import the non-XMP items. Do the imports in reverse priority order, last import wins!
+
+ MOOV_Manager::BoxInfo mvhdInfo;
+ MOOV_Manager::BoxRef mvhdRef = this->moovMgr.GetBox ( "moov/mvhd", &mvhdInfo );
+ bool mvhdFound = ((mvhdRef != 0) && (mvhdInfo.contentSize != 0));
+
+ MOOV_Manager::BoxInfo udtaInfo;
+ MOOV_Manager::BoxRef udtaRef = this->moovMgr.GetBox ( "moov/udta", &udtaInfo );
+ std::vector<MOOV_Manager::BoxInfo> cprtBoxes;
+
+ if ( udtaRef != 0 ) {
+ for ( XMP_Uns32 i = 0; i < udtaInfo.childCount; ++i ) {
+ MOOV_Manager::BoxInfo currInfo;
+ MOOV_Manager::BoxRef currRef = this->moovMgr.GetNthChild ( udtaRef, i, &currInfo );
+ if ( currRef == 0 ) break; // Sanity check, should not happen.
+ if ( currInfo.boxType != ISOMedia::k_cprt ) continue;
+ cprtBoxes.push_back ( currInfo );
+ }
+ }
+ bool cprtFound = (! cprtBoxes.empty());
+
+ bool tradQTFound = this->tradQTMgr.ParseCachedBoxes ( this->moovMgr );
+ bool tmcdFound = this->ParseTimecodeTrack();
+
+ if ( this->fileMode == MOOV_Manager::kFileIsNormalISO ) {
+
+ if ( mvhdFound ) this->containsXMP |= ImportMVHDItems ( mvhdInfo, &this->xmpObj );
+ if ( cprtFound ) this->containsXMP |= ImportISOCopyrights ( cprtBoxes, &this->xmpObj );
+ } else { // This is a QuickTime file, either traditional or modern.
+
+ if ( mvhdFound ) this->containsXMP |= ImportMVHDItems ( mvhdInfo, &this->xmpObj );
+ if ( cprtFound ) this->containsXMP |= ImportISOCopyrights ( cprtBoxes, &this->xmpObj );
+ if ( tmcdFound | tradQTFound ) {
+ // Some of the timecode items are in the .../udta/©... set but handled by ImportTimecodeItems.
+ this->containsXMP |= ImportTimecodeItems ( this->tmcdInfo, this->tradQTMgr, &this->xmpObj );
+ }
+
+ this->containsXMP |= ImportCr8rItems ( this->moovMgr, &this->xmpObj );
+
+ }
+
+} // MPEG4_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// MPEG4_MetaHandler::ParseTimecodeTrack
+// =====================================
+
+bool MPEG4_MetaHandler::ParseTimecodeTrack()
+{
+ MOOV_Manager::BoxRef stblRef = FindTimecode_stbl ( this->moovMgr );
+ if ( stblRef == 0 ) return false;
+
+ // Find the .../stbl/stsd box and process the first table entry.
+
+ MOOV_Manager::BoxInfo stsdInfo;
+ MOOV_Manager::BoxRef stsdRef;
+
+ stsdRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stsd, &stsdInfo );
+ if ( stsdRef == 0 ) return false;
+ if ( stsdInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsd_entry )) ) return false;
+ if ( GetUns32BE ( stsdInfo.content + 4 ) == 0 ) return false; // Make sure the entry count is non-zero.
+
+ const MOOV_Manager::Content_stsd_entry * stsdRawEntry = (MOOV_Manager::Content_stsd_entry*) (stsdInfo.content + 8);
+
+ XMP_Uns32 stsdEntrySize = GetUns32BE ( &stsdRawEntry->entrySize );
+ if ( stsdEntrySize > (stsdInfo.contentSize - 4) ) stsdEntrySize = stsdInfo.contentSize - 4;
+ if ( stsdEntrySize < sizeof ( MOOV_Manager::Content_stsd_entry ) ) return false;
+
+ XMP_Uns32 stsdEntryFormat = GetUns32BE ( &stsdRawEntry->format );
+ if ( stsdEntryFormat != ISOMedia::k_tmcd ) return false;
+
+ this->tmcdInfo.timeScale = GetUns32BE ( &stsdRawEntry->timeScale );
+ this->tmcdInfo.frameDuration = GetUns32BE ( &stsdRawEntry->frameDuration );
+
+ double floatCount = (double)this->tmcdInfo.timeScale / (double)this->tmcdInfo.frameDuration;
+ XMP_Uns8 expectedCount = (XMP_Uns8) (floatCount + 0.5);
+ if ( expectedCount != stsdRawEntry->frameCount ) {
+ double countRatio = (double)stsdRawEntry->frameCount / (double)expectedCount;
+ this->tmcdInfo.timeScale = (XMP_Uns32) (((double)this->tmcdInfo.timeScale * countRatio) + 0.5);
+ }
+
+ XMP_Uns32 flags = GetUns32BE ( &stsdRawEntry->flags );
+ this->tmcdInfo.isDropFrame = flags & 0x1;
+
+ // Look for a trailing 'name' box on the first stsd table entry.
+
+ XMP_Uns32 stsdTrailerSize = stsdEntrySize - sizeof ( MOOV_Manager::Content_stsd_entry );
+ if ( stsdTrailerSize > 8 ) { // Room for a non-empty 'name' box?
+
+ const XMP_Uns8 * trailerStart = stsdInfo.content + 8 + sizeof ( MOOV_Manager::Content_stsd_entry );
+ const XMP_Uns8 * trailerLimit = trailerStart + stsdTrailerSize;
+ const XMP_Uns8 * trailerPos;
+ const XMP_Uns8 * trailerNext;
+ ISOMedia::BoxInfo trailerInfo;
+
+ for ( trailerPos = trailerStart; trailerPos < trailerLimit; trailerPos = trailerNext ) {
+
+ trailerNext = ISOMedia::GetBoxInfo ( trailerPos, trailerLimit, &trailerInfo );
+
+ if ( trailerInfo.boxType == ISOMedia::k_name ) {
+
+ this->tmcdInfo.nameOffset = (XMP_Uns32) (trailerPos - stsdInfo.content);
+
+ if ( trailerInfo.contentSize > 4 ) {
+
+ XMP_Uns16 textLen = GetUns16BE ( trailerPos + trailerInfo.headerSize );
+ this->tmcdInfo.macLang = GetUns16BE ( trailerPos + trailerInfo.headerSize + 2 );
+
+ if ( trailerInfo.contentSize >= (XMP_Uns64)(textLen + 4) ) {
+ const char * textPtr = (char*) (trailerPos + trailerInfo.headerSize + 4);
+ this->tmcdInfo.macName.assign ( textPtr, textLen );
+ }
+
+ }
+
+ break; // Done after finding the first 'name' box.
+
+ }
+
+ }
+
+ }
+
+ // Find the timecode sample.
+
+ XMP_Uns64 sampleOffset = 0;
+ MOOV_Manager::BoxInfo tempInfo;
+ MOOV_Manager::BoxRef tempRef;
+
+ tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stsc, &tempInfo );
+ if ( tempRef == 0 ) return false;
+ if ( tempInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsc_entry )) ) return false;
+ if ( GetUns32BE ( tempInfo.content + 4 ) == 0 ) return false; // Make sure the entry count is non-zero.
+
+ XMP_Uns32 firstChunkNumber = GetUns32BE ( tempInfo.content + 8 ); // Want first field of first entry.
+
+ tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stco, &tempInfo );
+
+ if ( tempRef != 0 ) {
+
+ if ( tempInfo.contentSize < (8 + 4) ) return false;
+ XMP_Uns32 stcoCount = GetUns32BE ( tempInfo.content + 4 );
+ if ( stcoCount < firstChunkNumber ) return false;
+ XMP_Uns32 * stcoPtr = (XMP_Uns32*) (tempInfo.content + 8);
+ sampleOffset = GetUns32BE ( &stcoPtr[firstChunkNumber-1] ); // ! Chunk number is 1-based.
+
+ } else {
+
+ tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_co64, &tempInfo );
+ if ( (tempRef == 0) || (tempInfo.contentSize < (8 + 8)) ) return false;
+ XMP_Uns32 co64Count = GetUns32BE ( tempInfo.content + 4 );
+ if ( co64Count < firstChunkNumber ) return false;
+ XMP_Uns64 * co64Ptr = (XMP_Uns64*) (tempInfo.content + 8);
+ sampleOffset = GetUns64BE ( &co64Ptr[firstChunkNumber-1] ); // ! Chunk number is 1-based.
+
+ }
+
+ if ( sampleOffset != 0 ) { // Read the timecode sample.
+
+ XMPFiles_IO* localFile = 0;
+
+ if ( this->parent->ioRef == 0 ) { // Local read-only files get closed in CacheFileData.
+ XMP_Assert ( this->parent->UsesLocalIO() );
+ localFile = XMPFiles_IO::New_XMPFiles_IO ( this->parent->filePath.c_str(), Host_IO::openReadOnly );
+ XMP_Enforce ( localFile != 0 );
+ this->parent->ioRef = localFile;
+ }
+
+ this->parent->ioRef->Seek ( sampleOffset, kXMP_SeekFromStart );
+ this->parent->ioRef->ReadAll ( &this->tmcdInfo.timecodeSample, 4 );
+ this->tmcdInfo.timecodeSample = MakeUns32BE ( this->tmcdInfo.timecodeSample );
+ if ( localFile != 0 ) {
+ localFile->Close();
+ delete localFile;
+ this->parent->ioRef = 0;
+ }
+
+ }
+
+ // If this is a QT file, look for an edit list offset to add to the timecode sample. Look in the
+ // timecode track for an edts/elst box. The content is a UInt8 version, UInt8[3] flags, a UInt32
+ // entry count, and a sequence of UInt32 triples (trackDuration, mediaTime, mediaRate). Take
+ // mediaTime from the first entry, divide it by tmcdInfo.frameDuration, add that to
+ // tmcdInfo.timecodeSample.
+
+ bool isQT = (this->fileMode == MOOV_Manager::kFileIsModernQT) ||
+ (this->fileMode == MOOV_Manager::kFileIsTraditionalQT);
+
+ MOOV_Manager::BoxRef elstRef = 0;
+ if ( isQT ) elstRef = FindTimecode_elst ( this->moovMgr );
+ if ( elstRef != 0 ) {
+
+ MOOV_Manager::BoxInfo elstInfo;
+ this->moovMgr.GetBoxInfo ( elstRef, &elstInfo );
+
+ if ( elstInfo.contentSize >= (4+4+12) ) {
+ XMP_Uns32 elstCount = GetUns32BE ( elstInfo.content + 4 );
+ if ( elstCount >= 1 ) {
+ XMP_Uns32 mediaTime = GetUns32BE ( elstInfo.content + (4+4+4) );
+ this->tmcdInfo.timecodeSample += (mediaTime / this->tmcdInfo.frameDuration);
+ }
+ }
+
+ }
+
+ // Finally update this->tmcdInfo to remember (for update) that there is an OK timecode track.
+
+ this->tmcdInfo.stsdBoxFound = true;
+ this->tmcdInfo.sampleOffset = sampleOffset;
+ return true;
+
+} // MPEG4_MetaHandler::ParseTimecodeTrack
+
+// =================================================================================================
+// MPEG4_MetaHandler::UpdateTopLevelBox
+// ====================================
+
+void MPEG4_MetaHandler::UpdateTopLevelBox ( XMP_Uns64 oldOffset, XMP_Uns32 oldSize,
+ const XMP_Uns8 * newBox, XMP_Uns32 newSize )
+{
+ if ( (oldSize == 0) && (newSize == 0) ) return; // Sanity check, should not happen.
+
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_Uns64 oldFileSize = fileRef->Length();
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+
+ if ( newSize == oldSize ) {
+
+ // Trivial case, update the existing box in-place.
+ fileRef->Seek ( oldOffset, kXMP_SeekFromStart );
+ fileRef->Write ( newBox, oldSize );
+
+ } else if ( (oldOffset + oldSize) == oldFileSize ) {
+
+ // The old box was at the end, write the new and truncate the file if necessary.
+ fileRef->Seek ( oldOffset, kXMP_SeekFromStart );
+ fileRef->Write ( newBox, newSize );
+ fileRef->Truncate ( (oldOffset + newSize) ); // Does nothing if new size is bigger.
+
+ } else if ( (newSize < oldSize) && ((oldSize - newSize) >= 8) ) {
+
+ // The new size is smaller and there is enough room to create a free box.
+ fileRef->Seek ( oldOffset, kXMP_SeekFromStart );
+ fileRef->Write ( newBox, newSize );
+ WipeBoxFree ( fileRef, (oldOffset + newSize), (oldSize - newSize) );
+
+ } else {
+
+ // Look for a trailing free box with enough space. If not found, consider any free space.
+ // If still not found, append the new box and make the old one free.
+
+ ISOMedia::BoxInfo nextBoxInfo;
+ (void) ISOMedia::GetBoxInfo ( fileRef, (oldOffset + oldSize), oldFileSize, &nextBoxInfo, true /* throw errors */ );
+
+ XMP_Uns64 totalRoom = oldSize + nextBoxInfo.headerSize + nextBoxInfo.contentSize;
+
+ bool nextIsFree = (nextBoxInfo.boxType == ISOMedia::k_free) || (nextBoxInfo.boxType == ISOMedia::k_skip);
+ bool haveEnoughRoom = (newSize == totalRoom) ||
+ ( (newSize < totalRoom) && ((totalRoom - newSize) >= 8) );
+
+ if ( nextIsFree & haveEnoughRoom ) {
+
+ fileRef->Seek ( oldOffset, kXMP_SeekFromStart );
+ fileRef->Write ( newBox, newSize );
+
+ if ( newSize < totalRoom ) {
+ // Don't wipe, at most 7 old bytes left, it will be covered by the free header.
+ WriteBoxHeader ( fileRef, ISOMedia::k_free, (totalRoom - newSize) );
+ }
+
+ } else {
+
+ // Create a list of all top level free space, including the old space as free. Use the
+ // earliest space that fits. If none, append.
+
+ FreeSpaceList spaceList;
+ CreateFreeSpaceList ( fileRef, oldFileSize, oldOffset, oldSize, &spaceList );
+
+ size_t freeSlot, limit;
+ for ( freeSlot = 0, limit = spaceList.size(); freeSlot < limit; ++freeSlot ) {
+ XMP_Uns64 freeSize = spaceList[freeSlot].size;
+ if ( (newSize == freeSize) || ( (newSize < freeSize) && ((freeSize - newSize) >= 8) ) ) break;
+ }
+
+ if ( freeSlot == spaceList.size() ) {
+
+ // No available free space, append the new box.
+ CheckFinalBox ( fileRef, abortProc, abortArg );
+ fileRef->ToEOF();
+ fileRef->Write ( newBox, newSize );
+ WipeBoxFree ( fileRef, oldOffset, oldSize );
+
+ } else {
+
+ // Use the available free space. Wipe non-overlapping parts of the old box. The old
+ // box is either included in the new space, or is fully disjoint.
+
+ SpaceInfo & newSpace = spaceList[freeSlot];
+
+ bool oldIsDisjoint = ((oldOffset + oldSize) <= newSpace.offset) || // Old is in front.
+ ((newSpace.offset + newSpace.size) <= oldOffset); // Old is behind.
+
+ XMP_Assert ( (newSize == newSpace.size) ||
+ ( (newSize < newSpace.size) && ((newSpace.size - newSize) >= 8) ) );
+
+ XMP_Assert ( oldIsDisjoint ||
+ ( (newSpace.offset <= oldOffset) &&
+ ((oldOffset + oldSize) <= (newSpace.offset + newSpace.size)) ) /* old is included */ );
+
+ XMP_Uns64 newFreeOffset = newSpace.offset + newSize;
+ XMP_Uns64 newFreeSize = newSpace.size - newSize;
+
+ fileRef->Seek ( newSpace.offset, kXMP_SeekFromStart );
+ fileRef->Write ( newBox, newSize );
+
+ if ( newFreeSize > 0 ) WriteBoxHeader ( fileRef, ISOMedia::k_free, newFreeSize );
+
+ if ( oldIsDisjoint ) {
+
+ WipeBoxFree ( fileRef, oldOffset, oldSize );
+
+ } else {
+
+ // Clear the exposed portion of the old box.
+
+ XMP_Uns64 zeroStart = newFreeOffset + 8;
+ if ( newFreeSize > 0xFFFFFFFF ) zeroStart += 8;
+ if ( oldOffset > zeroStart ) zeroStart = oldOffset;
+ XMP_Uns64 zeroEnd = newFreeOffset + newFreeSize;
+ if ( (oldOffset + oldSize) < zeroEnd ) zeroEnd = oldOffset + oldSize;
+
+ if ( zeroStart < zeroEnd ) { // The new box might cover the old.
+ XMP_Assert ( (zeroEnd - zeroStart) <= (XMP_Uns64)oldSize );
+ XMP_Uns32 zeroSize = (XMP_Uns32) (zeroEnd - zeroStart);
+ fileRef->Seek ( zeroStart, kXMP_SeekFromStart );
+ for ( XMP_Uns32 ioCount = sizeof ( kZeroes ); zeroSize > 0; zeroSize -= ioCount ) {
+ if ( ioCount > zeroSize ) ioCount = zeroSize;
+ fileRef->Write ( &kZeroes[0], ioCount );
+ }
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+} // MPEG4_MetaHandler::UpdateTopLevelBox
+
+// =================================================================================================
+// MPEG4_MetaHandler::UpdateFile
+// =============================
+//
+// Revamp notes:
+// The 'moov' subtree and possibly the XMP 'uuid' box get updated. Compose the new copy of each and
+// see if it fits in existing space, incorporating adjacent 'free' boxes if necessary. If that won't
+// work, look for a sufficient 'free' box anywhere in the file. As a last resort, append the new copy.
+// Assume no location sensitive data within 'moov', i.e. no offsets into it. This lets it be moved
+// and its children freely rearranged.
+
+void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) { // If needsUpdate is set then at least the XMP changed.
+ return;
+ }
+
+ this->needsUpdate = false; // Make sure only called once.
+ XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates.
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_Uns64 fileSize = fileRef->Length();
+
+ bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
+
+ // Update the 'moov' subtree with exports from the XMP, but not the XMP itself (for QT files).
+
+ ExportMVHDItems ( this->xmpObj, &this->moovMgr );
+ ExportISOCopyrights ( this->xmpObj, &this->moovMgr );
+ ExportQuickTimeItems ( this->xmpObj, &this->tradQTMgr, &this->moovMgr );
+ ExportTimecodeItems ( this->xmpObj, &this->tmcdInfo, &this->tradQTMgr, &this->moovMgr );
+
+ if ( ! haveISOFile ) ExportCr8rItems ( this->xmpObj, &this->moovMgr );
+
+ // Try to update the XMP in-place if that is all that changed, or if it is in a preferred 'uuid' box.
+ // The XMP has already been serialized by common code to the appropriate length. Otherwise, update
+ // the 'moov'/'udta'/'XMP_' box in the MOOV_Manager, or the 'uuid' XMP box in the file.
+
+ bool useUuidXMP = (this->fileMode == MOOV_Manager::kFileIsNormalISO);
+ bool inPlaceXMP = (this->xmpPacket.size() == (size_t)this->packetInfo.length) &&
+ ( (useUuidXMP & this->havePreferredXMP) || (! this->moovMgr.IsChanged()) );
+
+
+ if ( inPlaceXMP ) {
+
+ // Update the existing XMP in-place.
+ fileRef->Seek ( this->packetInfo.offset, kXMP_SeekFromStart );
+ fileRef->Write ( this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() );
+
+ } else if ( useUuidXMP ) {
+
+ // Don't leave an old 'moov'/'udta'/'XMP_' box around.
+ MOOV_Manager::BoxRef udtaRef = this->moovMgr.GetBox ( "moov/udta", 0 );
+ if ( udtaRef != 0 ) this->moovMgr.DeleteTypeChild ( udtaRef, ISOMedia::k_XMP_ );
+
+ } else {
+ // Don't leave an old uuid XMP around (if we know about it).
+ if ( (! havePreferredXMP) && (this->xmpBoxSize != 0) ) {
+ WipeBoxFree ( fileRef, this->xmpBoxPos, this->xmpBoxSize );
+ }
+
+ // The udta form of XMP has just the XMP packet.
+ this->moovMgr.SetBox ( "moov/udta/XMP_", this->xmpPacket.c_str(), (XMP_Uns32)this->xmpPacket.size() );
+
+ }
+
+ // Update the 'moov' subtree if necessary, and finally update the timecode sample.
+
+ if ( this->moovMgr.IsChanged() ) {
+ this->moovMgr.UpdateMemoryTree();
+ this->UpdateTopLevelBox ( moovBoxPos, moovBoxSize, &this->moovMgr.fullSubtree[0], (XMP_Uns32)this->moovMgr.fullSubtree.size() );
+ }
+
+ if ( this->tmcdInfo.sampleOffset != 0 ) {
+ fileRef->Seek ( this->tmcdInfo.sampleOffset, kXMP_SeekFromStart );
+ XMP_Uns32 sample = MakeUns32BE ( this->tmcdInfo.timecodeSample );
+ fileRef->Write ( &sample, 4 );
+ }
+
+ // Update the 'uuid' XMP box if necessary.
+
+ if ( useUuidXMP & (! inPlaceXMP) ) {
+
+ // The uuid form of XMP has the 16-byte UUID in front of the XMP packet. Form the complete
+ // box (including size/type header) for UpdateTopLevelBox.
+ RawDataBlock uuidBox;
+ XMP_Uns32 uuidSize = 4+4 + 16 + (XMP_Uns32)this->xmpPacket.size();
+ uuidBox.assign ( uuidSize, 0 );
+ PutUns32BE ( uuidSize, &uuidBox[0] );
+ PutUns32BE ( ISOMedia::k_uuid, &uuidBox[4] );
+ memcpy ( &uuidBox[8], ISOMedia::k_xmpUUID, 16 );
+ memcpy ( &uuidBox[24], this->xmpPacket.c_str(), this->xmpPacket.size() );
+ this->UpdateTopLevelBox ( this->xmpBoxPos, this->xmpBoxSize, &uuidBox[0], uuidSize );
+
+ }
+
+} // MPEG4_MetaHandler::UpdateFile
+
+// =================================================================================================
+// MPEG4_MetaHandler::WriteTempFile
+// ================================
+//
+// Since the XMP and legacy is probably a miniscule part of the entire file, and since we can't
+// change the offset of most of the boxes, just copy the entire original file to the temp file, then
+// do an in-place update to the temp file.
+
+void MPEG4_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ XMP_Assert ( this->needsUpdate );
+
+ XMP_IO* originalRef = this->parent->ioRef;
+
+ originalRef->Rewind();
+ tempRef->Rewind();
+ XIO::Copy ( originalRef, tempRef, originalRef->Length(),
+ this->parent->abortProc, this->parent->abortArg );
+
+ try {
+ this->parent->ioRef = tempRef; // ! Fool UpdateFile into using the temp file.
+ this->UpdateFile ( false );
+ this->parent->ioRef = originalRef;
+ } catch ( ... ) {
+ this->parent->ioRef = originalRef;
+ throw;
+ }
+
+} // MPEG4_MetaHandler::WriteTempFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp b/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp
new file mode 100644
index 0000000..6474f0d
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp
@@ -0,0 +1,95 @@
+#ifndef __MPEG4_Handler_hpp__
+#define __MPEG4_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+#include "XMPFiles/source/FormatSupport/MOOV_Support.hpp"
+#include "XMPFiles/source/FormatSupport/QuickTime_Support.hpp"
+
+// ================================================================================================
+/// \file MPEG4_Handler.hpp
+/// \brief File format handler for MPEG-4.
+///
+/// This header ...
+///
+// ================================================================================================
+
+extern XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool MPEG4_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kMPEG4_HandlerFlags = ( kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_AllowsSafeUpdate
+ );
+
+class MPEG4_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+
+ MPEG4_MetaHandler ( XMPFiles * _parent );
+ virtual ~MPEG4_MetaHandler();
+
+ struct TimecodeTrackInfo { // Info about a QuickTime timecode track.
+ bool stsdBoxFound, isDropFrame;
+ XMP_Uns32 timeScale;
+ XMP_Uns32 frameDuration;
+ XMP_Uns32 timecodeSample;
+ XMP_Uns64 sampleOffset; // Absolute file offset of the timecode sample, 0 if none.
+ XMP_Uns32 nameOffset; // The offset of the 'name' box relative to the 'stsd' box content.
+ XMP_Uns16 macLang; // The Mac language code of the trailing 'name' box.
+ std::string macName; // The text part of the trailing 'name' box, in macLang encoding.
+ TimecodeTrackInfo()
+ : stsdBoxFound(false), isDropFrame(false), timeScale(0), frameDuration(0),
+ timecodeSample(0), sampleOffset(0), nameOffset(0), macLang(0) {};
+ };
+
+private:
+
+ MPEG4_MetaHandler() : fileMode(0), havePreferredXMP(false),
+ xmpBoxPos(0), moovBoxPos(0), xmpBoxSize(0), moovBoxSize(0) {}; // Hidden on purpose.
+
+ bool ParseTimecodeTrack();
+
+ void UpdateTopLevelBox ( XMP_Uns64 oldOffset, XMP_Uns32 oldSize, const XMP_Uns8 * newBox, XMP_Uns32 newSize );
+
+ XMP_Uns8 fileMode;
+ bool havePreferredXMP;
+ XMP_Uns64 xmpBoxPos; // The file offset of the XMP box (the size field, not the content).
+ XMP_Uns64 moovBoxPos; // The file offset of the 'moov' box (the size field, not the content).
+ XMP_Uns32 xmpBoxSize, moovBoxSize; // The full size of the boxes, not just the content.
+
+ MOOV_Manager moovMgr;
+ TradQT_Manager tradQTMgr;
+
+ TimecodeTrackInfo tmcdInfo;
+
+}; // MPEG4_MetaHandler
+
+// =================================================================================================
+
+#endif // __MPEG4_Handler_hpp__
diff --git a/XMPFiles/source/FileHandlers/P2_Handler.cpp b/XMPFiles/source/FileHandlers/P2_Handler.cpp
new file mode 100644
index 0000000..295a7a1
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/P2_Handler.cpp
@@ -0,0 +1,1401 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/P2_Handler.hpp"
+
+#include "third-party/zuid/interfaces/MD5.h"
+
+#include <cmath>
+
+using namespace std;
+
+// =================================================================================================
+/// \file P2_Handler.cpp
+/// \brief Folder format handler for P2.
+///
+/// This handler is for the P2 video format. This is a pseudo-package, visible files but with a very
+/// well-defined layout and naming rules. A typical P2 example looks like:
+///
+/// .../MyMovie
+/// CONTENTS/
+/// CLIP/
+/// 0001AB.XML
+/// 0001AB.XMP
+/// 0002CD.XML
+/// 0002CD.XMP
+/// VIDEO/
+/// 0001AB.MXF
+/// 0002CD.MXF
+/// AUDIO/
+/// 0001AB00.MXF
+/// 0001AB01.MXF
+/// 0002CD00.MXF
+/// 0002CD01.MXF
+/// ICON/
+/// 0001AB.BMP
+/// 0002CD.BMP
+/// VOICE/
+/// 0001AB.WAV
+/// 0002CD.WAV
+/// PROXY/
+/// 0001AB.MP4
+/// 0002CD.MP4
+///
+/// From the user's point of view, .../MyMovie contains P2 stuff, in this case 2 clips whose raw
+/// names are 0001AB and 0002CD. There may be mapping information for nicer clip names to the raw
+/// names, but that can be ignored for now. Each clip is stored as a collection of files, each file
+/// holding some specific aspect of the clip's data.
+///
+/// The P2 handler operates on clips. The path from the client of XMPFiles can be either a logical
+/// clip path, like ".../MyMovie/0001AB", or a full path to one of the files. In the latter case the
+/// handler must figure out the intended clip, it must not blindly use the named file.
+///
+/// Once the P2 structure and intended clip are identified, the handler only deals with the .XMP and
+/// .XML files in the CLIP folder. The .XMP file, if present, contains the XMP for the clip. The .XML
+/// file must be present to define the existance of the clip. It contains a variety of information
+/// about the clip, including some legacy metadata.
+///
+// =================================================================================================
+
+static const char * kContentFolderNames[] = { "CLIP", "VIDEO", "AUDIO", "ICON", "VOICE", "PROXY", 0 };
+static int kNumRequiredContentFolders = 6; // All 6 of the above.
+
+static inline bool CheckContentFolderName ( const std::string & folderName )
+{
+ for ( int i = 0; kContentFolderNames[i] != 0; ++i ) {
+ if ( folderName == kContentFolderNames[i] ) return true;
+ }
+ return false;
+}
+
+// =================================================================================================
+// InternalMakeClipFilePath
+// ========================
+//
+// P2_CheckFormat can't use the member function.
+
+static void InternalMakeClipFilePath ( std::string * path,
+ const std::string & rootPath,
+ const std::string & clipName,
+ XMP_StringPtr suffix )
+{
+
+ *path = rootPath;
+ *path += kDirChar;
+ *path += "CONTENTS";
+ *path += kDirChar;
+ *path += "CLIP";
+ *path += kDirChar;
+ *path += clipName;
+ *path += suffix;
+
+} // InternalMakeClipFilePath
+
+// =================================================================================================
+// P2_CheckFormat
+// ==============
+//
+// This version does fairly simple checks. The top level folder (.../MyMovie) must have a child
+// folder called CONTENTS. This must have a subfolder called CLIP. It may also have subfolders
+// called VIDEO, AUDIO, ICON, VOICE, and PROXY. Any mixture of these additional folders is allowed,
+// but no other children are allowed in CONTENTS. The CLIP folder must contain a .XML file for the
+// desired clip. The name checks are case insensitive.
+//
+// The state of the string parameters depends on the form of the path passed by the client. If the
+// client passed a logical clip path, like ".../MyMovie/0001AB", the parameters are:
+// rootPath - ".../MyMovie"
+// gpName - empty
+// parentName - empty
+// leafName - "0001AB"
+// If the client passed a full file path, like ".../MyMovie/CONTENTS/VOICE/0001AB.WAV", they are:
+// rootPath - ".../MyMovie"
+// gpName - "CONTENTS"
+// parentName - "VOICE"
+// leafName - "0001AB"
+//
+// For most of the content files the base file name is the raw clip name. Files in the AUDIO and
+// VOICE folders have an extra 2 digits appended to the raw clip name. These must be trimmed.
+
+// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has
+// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the
+// ! file exists for a full file path.
+
+bool P2_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent )
+{
+ Host_IO::AutoFolder aFolder;
+ std::string tempPath, childName;
+
+ std::string clipName = leafName;
+
+ // Do some basic checks on the gpName and parentName.
+
+ if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty.
+
+ if ( ! gpName.empty() ) {
+
+ if ( gpName != "CONTENTS" ) return false;
+ if ( ! CheckContentFolderName ( parentName ) ) return false;
+
+ if ( (parentName == "AUDIO") | (parentName == "VOICE") ) {
+ if ( clipName.size() < 3 ) return false;
+ clipName.erase ( clipName.size() - 2 );
+ }
+
+ }
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "CONTENTS";
+ if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFolder ) return false;
+
+ aFolder.folder = Host_IO::OpenFolder ( tempPath.c_str() );
+ int numChildrenFound = 0;
+ std::string childPath;
+
+ while ( ( Host_IO::GetNextChild ( aFolder.folder, &childName ) && ( numChildrenFound < kNumRequiredContentFolders ) ) ) { // Make sure the children of CONTENTS are legit.
+ if ( CheckContentFolderName ( childName ) ) {
+ childPath = tempPath;
+ childPath += kDirChar;
+ childPath += childName;
+ if ( Host_IO::GetFileMode ( childPath.c_str() ) != Host_IO::kFMode_IsFolder ) return false;
+ ++numChildrenFound;
+ }
+ }
+ aFolder.Close();
+
+ // Make sure the clip's .XML file exists.
+
+ InternalMakeClipFilePath ( &tempPath, rootPath, clipName, ".XML" );
+ if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false;
+
+ // Make a bogus path to pass the root path and clip name to the handler. A bit of a hack, but
+ // the only way to get info from here to there.
+
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += clipName;
+
+ size_t pathLen = tempPath.size() + 1; // Include a terminating nul.
+ parent->tempPtr = malloc ( pathLen );
+ if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for P2 clip path", kXMPErr_NoMemory );
+ memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above.
+
+ return true;
+
+} // P2_CheckFormat
+
+// =================================================================================================
+
+static void* CreatePseudoClipPath ( const std::string & clientPath ) {
+
+ // Used to create the clip pseudo path when the CheckFormat function is skipped.
+
+ std::string pseudoPath = clientPath;
+
+ size_t pathLen;
+ void* tempPtr = 0;
+
+ if ( Host_IO::Exists ( pseudoPath.c_str() ) ) {
+
+ // The client passed a physical path. The logical clip name is the leaf name, with the
+ // extension removed. Files in the AUDIO and VOICE folders have an extra 2 digits appended
+ // to the clip name. The movie root path ends two levels up.
+
+ std::string clipName, parentName, ignored;
+
+ XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name.
+ XIO::SplitFileExtension ( &clipName, &ignored );
+
+ XIO::SplitLeafName ( &pseudoPath, &parentName ); // Remove the 2 intermediate folder levels.
+ XIO::SplitLeafName ( &pseudoPath, &ignored );
+
+ if ( (parentName == "AUDIO") | (parentName == "VOICE") ) {
+ if ( clipName.size() >= 3 ) clipName.erase ( clipName.size() - 2 );
+ }
+
+ pseudoPath += kDirChar;
+ pseudoPath += clipName;
+
+ }
+
+ pathLen = pseudoPath.size() + 1; // Include a terminating nul.
+ tempPtr = malloc ( pathLen );
+ if ( tempPtr == 0 ) XMP_Throw ( "No memory for P2 clip info", kXMPErr_NoMemory );
+ memcpy ( tempPtr, pseudoPath.c_str(), pathLen );
+
+ return tempPtr;
+
+} // CreatePseudoClipPath
+
+// =================================================================================================
+// P2_MetaHandlerCTor
+// ==================
+
+XMPFileHandler * P2_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new P2_MetaHandler ( parent );
+
+} // P2_MetaHandlerCTor
+
+// =================================================================================================
+// P2_MetaHandler::P2_MetaHandler
+// ==============================
+
+P2_MetaHandler::P2_MetaHandler ( XMPFiles * _parent ) : expat(0), clipMetadata(0), clipContent(0)
+{
+
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kP2_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ // Extract the root path and clip name from tempPtr.
+
+ if ( this->parent->tempPtr == 0 ) {
+ // The CheckFormat call might have been skipped.
+ this->parent->tempPtr = CreatePseudoClipPath ( this->parent->filePath );
+ }
+
+ this->rootPath.assign ( (char*) this->parent->tempPtr );
+ free ( this->parent->tempPtr );
+ this->parent->tempPtr = 0;
+
+ XIO::SplitLeafName ( &this->rootPath, &this->clipName );
+
+} // P2_MetaHandler::P2_MetaHandler
+
+// =================================================================================================
+// P2_MetaHandler::~P2_MetaHandler
+// ===============================
+
+P2_MetaHandler::~P2_MetaHandler()
+{
+
+ this->CleanupLegacyXML();
+ if ( this->parent->tempPtr != 0 ) {
+ free ( this->parent->tempPtr );
+ this->parent->tempPtr = 0;
+ }
+
+} // P2_MetaHandler::~P2_MetaHandler
+
+// =================================================================================================
+// P2_MetaHandler::MakeClipFilePath
+// ================================
+
+bool P2_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ )
+{
+
+ InternalMakeClipFilePath ( path, this->rootPath, this->clipName, suffix );
+ if ( ! checkFile ) return true;
+
+ return Host_IO::Exists ( path->c_str() );
+
+} // P2_MetaHandler::MakeClipFilePath
+
+// =================================================================================================
+// P2_MetaHandler::CleanupLegacyXML
+// ================================
+
+void P2_MetaHandler::CleanupLegacyXML()
+{
+
+ if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; }
+
+ clipMetadata = 0; // ! Was a pointer into the expat tree.
+ clipContent = 0; // ! Was a pointer into the expat tree.
+
+} // P2_MetaHandler::CleanupLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::DigestLegacyItem
+// ================================
+
+void P2_MetaHandler::DigestLegacyItem ( MD5_CTX & md5Context, XML_NodePtr legacyContext, XMP_StringPtr legacyPropName )
+{
+ XML_NodePtr legacyProp = legacyContext->GetNamedElement ( this->p2NS.c_str(), legacyPropName );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &md5Context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+} // P2_MetaHandler::DigestLegacyItem
+
+// =================================================================================================
+// P2_MetaHandler::DigestLegacyRelations
+// =====================================
+
+void P2_MetaHandler::DigestLegacyRelations ( MD5_CTX & md5Context )
+{
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_Node * legacyContext = this->clipContent->GetNamedElement ( p2NS, "Relation" );
+
+ if ( legacyContext != 0 ) {
+
+ this->DigestLegacyItem ( md5Context, legacyContext, "GlobalShotID" );
+ XML_Node * legacyConnectionContext = legacyContext = this->clipContent->GetNamedElement ( p2NS, "Connection" );
+
+ if ( legacyConnectionContext != 0 ) {
+
+ legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Top" );
+
+ if ( legacyContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" );
+ }
+
+ legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Previous" );
+
+ if ( legacyContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" );
+ }
+
+ legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Next" );
+
+ if ( legacyContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" );
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::DigestLegacyRelations
+
+// =================================================================================================
+// P2_MetaHandler::SetXMPPropertyFromLegacyXML
+// ===========================================
+
+void P2_MetaHandler::SetXMPPropertyFromLegacyXML ( bool digestFound,
+ XML_NodePtr legacyContext,
+ XMP_StringPtr schemaNS,
+ XMP_StringPtr propName,
+ XMP_StringPtr legacyPropName,
+ bool isLocalized )
+{
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( schemaNS, propName )) ) {
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyProp = legacyContext->GetNamedElement ( p2NS, legacyPropName );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ if ( isLocalized ) {
+ this->xmpObj.SetLocalizedText ( schemaNS, propName, "", "x-default", legacyProp->GetLeafContentValue(), kXMP_DeleteExisting );
+ } else {
+ this->xmpObj.SetProperty ( schemaNS, propName, legacyProp->GetLeafContentValue(), kXMP_DeleteExisting );
+ }
+ this->containsXMP = true;
+ }
+
+ }
+
+} // P2_MetaHandler::SetXMPPropertyFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetRelationsFromLegacyXML
+// =========================================
+
+void P2_MetaHandler::SetRelationsFromLegacyXML ( bool digestFound )
+{
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyRelationContext = this->clipContent->GetNamedElement ( p2NS, "Relation" );
+
+ // P2 Relation blocks are optional -- they're only present when a clip is part of a multi-clip shot.
+
+ if ( legacyRelationContext != 0 ) {
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "relation" )) ) {
+
+ XML_NodePtr legacyProp = legacyRelationContext->GetNamedElement ( p2NS, "GlobalShotID" );
+ std::string relationString;
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ this->xmpObj.DeleteProperty ( kXMP_NS_DC, "relation" );
+ relationString = std::string("globalShotID:") + legacyProp->GetLeafContentValue();
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString );
+ this->containsXMP = true;
+
+ XML_NodePtr legacyConnectionContext = legacyRelationContext->GetNamedElement ( p2NS, "Connection" );
+
+ if ( legacyConnectionContext != 0 ) {
+
+ XML_NodePtr legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Top" );
+
+ if ( legacyContext != 0 ) {
+ legacyProp = legacyContext->GetNamedElement ( p2NS, "GlobalClipID" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ relationString = std::string("topGlobalClipID:") + legacyProp->GetLeafContentValue();
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString );
+ }
+ }
+
+ legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Previous" );
+
+ if ( legacyContext != 0 ) {
+ legacyProp = legacyContext->GetNamedElement ( p2NS, "GlobalClipID" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ relationString = std::string("previousGlobalClipID:") + legacyProp->GetLeafContentValue();
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString );
+ }
+ }
+
+ legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Next" );
+
+ if ( legacyContext != 0 ) {
+ legacyProp = legacyContext->GetNamedElement ( p2NS, "GlobalClipID" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ relationString = std::string("nextGlobalClipID:") + legacyProp->GetLeafContentValue();
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString );
+ }
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetRelationsFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetAudioInfoFromLegacyXML
+// =========================================
+
+void P2_MetaHandler::SetAudioInfoFromLegacyXML ( bool digestFound )
+{
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyAudioContext = this->clipContent->GetNamedElement ( p2NS, "EssenceList" );
+
+ if ( legacyAudioContext != 0 ) {
+
+ legacyAudioContext = legacyAudioContext->GetNamedElement ( p2NS, "Audio" );
+
+ if ( legacyAudioContext != 0 ) {
+
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyAudioContext, kXMP_NS_DM, "audioSampleRate", "SamplingRate", false );
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "audioSampleType" )) ) {
+ XML_NodePtr legacyProp = legacyAudioContext->GetNamedElement ( p2NS, "BitsPerSample" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ const std::string p2BitsPerSample = legacyProp->GetLeafContentValue();
+ std::string dmSampleType;
+
+ if ( p2BitsPerSample == "16" ) {
+ dmSampleType = "16Int";
+ } else if ( p2BitsPerSample == "24" ) {
+ dmSampleType = "32Int";
+ }
+
+ if ( ! dmSampleType.empty() ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "audioSampleType", dmSampleType, kXMP_DeleteExisting );
+ this->containsXMP = true;
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetAudioInfoFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetVideoInfoFromLegacyXML
+// =========================================
+
+void P2_MetaHandler::SetVideoInfoFromLegacyXML ( bool digestFound )
+{
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyVideoContext = this->clipContent->GetNamedElement ( p2NS, "EssenceList" );
+
+ if ( legacyVideoContext != 0 ) {
+
+ legacyVideoContext = legacyVideoContext->GetNamedElement ( p2NS, "Video" );
+
+ if ( legacyVideoContext != 0 ) {
+ this->SetVideoFrameInfoFromLegacyXML ( legacyVideoContext, digestFound );
+ this->SetStartTimecodeFromLegacyXML ( legacyVideoContext, digestFound );
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyVideoContext, kXMP_NS_DM, "videoFrameRate", "FrameRate", false );
+ }
+
+ }
+
+} // P2_MetaHandler::SetVideoInfoFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetDurationFromLegacyXML
+// ========================================
+
+void P2_MetaHandler::SetDurationFromLegacyXML ( bool digestFound )
+{
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) {
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyDurationProp = this->clipContent->GetNamedElement ( p2NS, "Duration" );
+ XML_NodePtr legacyEditUnitProp = this->clipContent->GetNamedElement ( p2NS, "EditUnit" );
+
+ if ( (legacyDurationProp != 0) && ( legacyEditUnitProp != 0 ) &&
+ legacyDurationProp->IsLeafContentNode() && legacyEditUnitProp->IsLeafContentNode() ) {
+
+ this->xmpObj.DeleteProperty ( kXMP_NS_DM, "duration" );
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "duration",
+ kXMP_NS_DM, "value", legacyDurationProp->GetLeafContentValue() );
+
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "duration",
+ kXMP_NS_DM, "scale", legacyEditUnitProp->GetLeafContentValue() );
+ this->containsXMP = true;
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetDurationFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetVideoFrameInfoFromLegacyXML
+// ==============================================
+
+void P2_MetaHandler::SetVideoFrameInfoFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound )
+{
+
+ // Map the P2 Codec field to various dynamic media schema fields.
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "videoFrameSize" )) ) {
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "Codec" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ const std::string p2Codec = legacyProp->GetLeafContentValue();
+ std::string dmPixelAspectRatio, dmVideoCompressor, dmWidth, dmHeight;
+
+ if ( p2Codec == "DV25_411" ) {
+ dmWidth = "720";
+ dmVideoCompressor = "DV25 4:1:1";
+ } else if ( p2Codec == "DV25_420" ) {
+ dmWidth = "720";
+ dmVideoCompressor = "DV25 4:2:0";
+ } else if ( p2Codec == "DV50_422" ) {
+ dmWidth = "720";
+ dmVideoCompressor = "DV50 4:2:2";
+ } else if ( ( p2Codec == "DV100_1080/59.94i" ) || ( p2Codec == "DV100_1080/50i" ) ) {
+ dmVideoCompressor = "DV100";
+ dmHeight = "1080";
+
+ if ( p2Codec == "DV100_1080/59.94i" ) {
+ dmWidth = "1280";
+ dmPixelAspectRatio = "3/2";
+ } else {
+ dmWidth = "1440";
+ dmPixelAspectRatio = "1920/1440";
+ }
+ } else if ( ( p2Codec == "DV100_720/59.94p" ) || ( p2Codec == "DV100_720/50p" ) ) {
+ dmVideoCompressor = "DV100";
+ dmHeight = "720";
+ dmWidth = "960";
+ dmPixelAspectRatio = "1920/1440";
+ } else if ( ( p2Codec.compare ( 0, 6, "AVC-I_" ) == 0 ) ) {
+
+ // This is AVC-Intra footage. The framerate and PAR depend on the "class" attribute in the P2 XML.
+ const XMP_StringPtr codecClass = legacyProp->GetAttrValue( "Class" );
+
+ if ( XMP_LitMatch ( codecClass, "100" ) ) {
+
+ dmVideoCompressor = "AVC-Intra 100";
+ dmPixelAspectRatio = "1/1";
+
+ if ( p2Codec.compare ( 6, 4, "1080" ) == 0 ) {
+ dmHeight = "1080";
+ dmWidth = "1920";
+ } else if ( p2Codec.compare ( 6, 3, "720" ) == 0 ) {
+ dmHeight = "720";
+ dmWidth = "1280";
+ }
+
+ } else if ( XMP_LitMatch ( codecClass, "50" ) ) {
+
+ dmVideoCompressor = "AVC-Intra 50";
+ dmPixelAspectRatio = "1920/1440";
+
+ if ( p2Codec.compare ( 6, 4, "1080" ) == 0 ) {
+ dmHeight = "1080";
+ dmWidth = "1440";
+ } else if ( p2Codec.compare ( 6, 3, "720" ) == 0 ) {
+ dmHeight = "720";
+ dmWidth = "960";
+ }
+
+ } else {
+ // Unknown codec class -- we don't have enough info to determine the
+ // codec, PAR, or aspect ratio
+ dmVideoCompressor = "AVC-Intra";
+ }
+ }
+
+ if ( dmWidth == "720" ) {
+
+ // This is SD footage -- calculate the frame height and pixel aspect ratio using the legacy P2
+ // FrameRate and AspectRatio fields.
+
+ legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "FrameRate" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ const std::string p2FrameRate = legacyProp->GetLeafContentValue();
+
+ legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "AspectRatio" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ const std::string p2AspectRatio = legacyProp->GetLeafContentValue();
+
+ if ( p2FrameRate == "50i" ) {
+ // Standard Definition PAL.
+ dmHeight = "576";
+ if ( p2AspectRatio == "4:3" ) {
+ dmPixelAspectRatio = "768/702";
+ } else if ( p2AspectRatio == "16:9" ) {
+ dmPixelAspectRatio = "1024/702";
+ }
+ } else if ( p2FrameRate == "59.94i" ) {
+ // Standard Definition NTSC.
+ dmHeight = "480";
+ if ( p2AspectRatio == "4:3" ) {
+ dmPixelAspectRatio = "10/11";
+ } else if ( p2AspectRatio == "16:9" ) {
+ dmPixelAspectRatio = "40/33";
+ }
+ }
+
+ }
+ }
+ }
+
+ if ( ! dmPixelAspectRatio.empty() ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "videoPixelAspectRatio", dmPixelAspectRatio, kXMP_DeleteExisting );
+ this->containsXMP = true;
+ }
+
+ if ( ! dmVideoCompressor.empty() ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "videoCompressor", dmVideoCompressor, kXMP_DeleteExisting );
+ this->containsXMP = true;
+ }
+
+ if ( ( ! dmWidth.empty() ) && ( ! dmHeight.empty() ) ) {
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", dmWidth, 0 );
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", dmHeight, 0 );
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", "pixel", 0 );
+ this->containsXMP = true;
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetVideoFrameInfoFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetStartTimecodeFromLegacyXML
+// =============================================
+
+void P2_MetaHandler::SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound )
+{
+
+ // Translate start timecode to the format specified by the dynamic media schema.
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "startTimecode" )) ) {
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "StartTimecode" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ std::string p2StartTimecode = legacyProp->GetLeafContentValue();
+
+ legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "FrameRate" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ const std::string p2FrameRate = legacyProp->GetLeafContentValue();
+ XMP_StringPtr p2DropFrameFlag = legacyProp->GetAttrValue ( "DropFrameFlag" );
+ if ( p2DropFrameFlag == 0 ) p2DropFrameFlag = ""; // Make tests easier.
+ std::string dmTimeFormat;
+
+ if ( ( p2FrameRate == "50i" ) || ( p2FrameRate == "25p" ) ) {
+
+ dmTimeFormat = "25Timecode";
+
+ } else if ( p2FrameRate == "23.98p" ) {
+
+ dmTimeFormat = "23976Timecode";
+
+ } else if ( p2FrameRate == "50p" ) {
+
+ dmTimeFormat = "50Timecode";
+
+ } else if ( p2FrameRate == "59.94p" ) {
+
+ if ( XMP_LitMatch ( p2DropFrameFlag, "true" ) ) {
+ dmTimeFormat = "5994DropTimecode";
+ } else if ( XMP_LitMatch ( p2DropFrameFlag, "false" ) ) {
+ dmTimeFormat = "5994NonDropTimecode";
+ }
+
+ } else if ( (p2FrameRate == "59.94i") || (p2FrameRate == "29.97p") ) {
+
+ if ( p2DropFrameFlag != 0 ) {
+
+ if ( XMP_LitMatch ( p2DropFrameFlag, "false" ) ) {
+
+ dmTimeFormat = "2997NonDropTimecode";
+
+ } else if ( XMP_LitMatch ( p2DropFrameFlag, "true" ) ) {
+
+ // Drop frame NTSC timecode uses semicolons instead of colons as separators.
+ std::string::iterator currCharIt = p2StartTimecode.begin();
+ const std::string::iterator charsEndIt = p2StartTimecode.end();
+
+ for ( ; currCharIt != charsEndIt; ++currCharIt ) {
+ if ( *currCharIt == ':' ) *currCharIt = ';';
+ }
+
+ dmTimeFormat = "2997DropTimecode";
+
+ }
+
+ }
+
+ }
+
+ if ( ( ! p2StartTimecode.empty() ) && ( ! dmTimeFormat.empty() ) ) {
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", p2StartTimecode, 0 );
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", dmTimeFormat, 0 );
+ this->containsXMP = true;
+ }
+
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetStartTimecodeFromLegacyXML
+
+
+// =================================================================================================
+// P2_MetaHandler::SetGPSPropertyFromLegacyXML
+// ===========================================
+
+void P2_MetaHandler::SetGPSPropertyFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound, XMP_StringPtr propName, XMP_StringPtr legacyPropName )
+{
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_EXIF, propName )) ) {
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyGPSProp = legacyLocationContext->GetNamedElement ( p2NS, legacyPropName );
+
+ if ( ( legacyGPSProp != 0 ) && legacyGPSProp->IsLeafContentNode() ) {
+
+ this->xmpObj.DeleteProperty ( kXMP_NS_EXIF, propName );
+
+ const std::string legacyGPSValue = legacyGPSProp->GetLeafContentValue();
+
+ if ( ! legacyGPSValue.empty() ) {
+
+ // Convert from decimal to sexagesimal GPS coordinates
+ char direction = '\0';
+ double degrees = 0.0;
+ const int numFieldsRead = sscanf ( legacyGPSValue.c_str(), "%c%lf", &direction, &degrees );
+
+ if ( numFieldsRead == 2 ) {
+ double wholeDegrees = 0.0;
+ const double fractionalDegrees = modf ( degrees, &wholeDegrees );
+ const double minutes = fractionalDegrees * 60.0;
+ char xmpValue [128];
+
+ sprintf ( xmpValue, "%d,%.5lf%c", static_cast<int>(wholeDegrees), minutes, direction );
+ this->xmpObj.SetProperty ( kXMP_NS_EXIF, propName, xmpValue );
+ this->containsXMP = true;
+
+ }
+
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetGPSPropertyFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetAltitudeFromLegacyXML
+// ========================================
+
+void P2_MetaHandler::SetAltitudeFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound )
+{
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_EXIF, "GPSAltitude" )) ) {
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyAltitudeProp = legacyLocationContext->GetNamedElement ( p2NS, "Altitude" );
+
+ if ( ( legacyAltitudeProp != 0 ) && legacyAltitudeProp->IsLeafContentNode() ) {
+
+ this->xmpObj.DeleteProperty ( kXMP_NS_EXIF, "GPSAltitude" );
+
+ const std::string legacyGPSValue = legacyAltitudeProp->GetLeafContentValue();
+
+ if ( ! legacyGPSValue.empty() ) {
+
+ int altitude = 0;
+
+ if ( sscanf ( legacyGPSValue.c_str(), "%d", &altitude ) == 1) {
+
+ if ( altitude >= 0 ) {
+ // At or above sea level.
+ this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "0" );
+ } else {
+ // Below sea level.
+ altitude = -altitude;
+ this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "1" );
+ }
+
+ char xmpValue [128];
+
+ sprintf ( xmpValue, "%d/1", altitude );
+ this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitude", xmpValue );
+ this->containsXMP = true;
+
+ }
+
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetAltitudeFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::ForceChildElement
+// =================================
+
+XML_Node * P2_MetaHandler::ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, int indent /* = 0 */ )
+{
+ XML_Node * wsNode;
+ XML_Node * childNode = parent->GetNamedElement ( this->p2NS.c_str(), localName );
+
+ if ( childNode == 0 ) {
+
+ // The indenting is a hack, assuming existing 2 spaces per level.
+
+ wsNode = new XML_Node ( parent, "", kCDataNode );
+ wsNode->value = " "; // Add 2 spaces to the existing WS before the parent's close tag.
+ parent->content.push_back ( wsNode );
+
+ childNode = new XML_Node ( parent, localName, kElemNode );
+ childNode->ns = parent->ns;
+ childNode->nsPrefixLen = parent->nsPrefixLen;
+ childNode->name.insert ( 0, parent->name, 0, parent->nsPrefixLen );
+ parent->content.push_back ( childNode );
+
+ wsNode = new XML_Node ( parent, "", kCDataNode );
+ wsNode->value = '\n';
+ for ( ; indent > 1; --indent ) wsNode->value += " "; // Indent less 1, to "outdent" the parent's close.
+ parent->content.push_back ( wsNode );
+
+ }
+
+ return childNode;
+
+} // P2_MetaHandler::ForceChildElement
+
+// =================================================================================================
+// P2_MetaHandler::MakeLegacyDigest
+// =================================
+
+// *** Early hack version.
+
+#define kHexDigits "0123456789ABCDEF"
+
+void P2_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ digestStr->erase();
+ if ( this->clipMetadata == 0 ) return; // Bail if we don't have any legacy XML.
+ XMP_Assert ( this->expat != 0 );
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyContext;
+ MD5_CTX md5Context;
+ unsigned char digestBin [16];
+ MD5Init ( &md5Context );
+
+ legacyContext = this->clipContent;
+ this->DigestLegacyItem ( md5Context, legacyContext, "ClipName" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "Duration" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "EditUnit" );
+ this->DigestLegacyRelations ( md5Context );
+
+ legacyContext = this->clipContent->GetNamedElement ( p2NS, "EssenceList" );
+
+ if ( legacyContext != 0 ) {
+
+ XML_NodePtr videoContext = legacyContext->GetNamedElement ( p2NS, "Video" );
+
+ if ( videoContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, videoContext, "AspectRatio" );
+ this->DigestLegacyItem ( md5Context, videoContext, "Codec" );
+ this->DigestLegacyItem ( md5Context, videoContext, "FrameRate" );
+ this->DigestLegacyItem ( md5Context, videoContext, "StartTimecode" );
+ }
+
+ XML_NodePtr audioContext = legacyContext->GetNamedElement ( p2NS, "Audio" );
+
+ if ( audioContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, audioContext, "SamplingRate" );
+ this->DigestLegacyItem ( md5Context, audioContext, "BitsPerSample" );
+ }
+
+ }
+
+ legacyContext = this->clipMetadata;
+ this->DigestLegacyItem ( md5Context, legacyContext, "UserClipName" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "ShotMark" );
+
+ legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Access" );
+ /* Rather return than create the digest because the "Access" element is listed as "required" in the P2 spec.
+ So a P2 file without an "Access" element does not follow the spec and might be corrupt.*/
+ if ( legacyContext == 0 ) return;
+
+ this->DigestLegacyItem ( md5Context, legacyContext, "Creator" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "CreationDate" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "LastUpdateDate" );
+
+ legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Shoot" );
+
+ if ( legacyContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, legacyContext, "Shooter" );
+
+ legacyContext = legacyContext->GetNamedElement ( p2NS, "Location" );
+
+ if ( legacyContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, legacyContext, "PlaceName" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "Longitude" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "Latitude" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "Altitude" );
+ }
+ }
+
+ legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Scenario" );
+
+ if ( legacyContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, legacyContext, "SceneNo." );
+ this->DigestLegacyItem ( md5Context, legacyContext, "TakeNo." );
+ }
+
+ legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Device" );
+
+ if ( legacyContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, legacyContext, "Manufacturer" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "SerialNo." );
+ this->DigestLegacyItem ( md5Context, legacyContext, "ModelName" );
+ }
+
+ MD5Final ( digestBin, &md5Context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->append ( buffer );
+
+} // P2_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+// P2_MetaHandler::GetFileModDate
+// ==============================
+
+static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) {
+ int compare = SXMPUtils::CompareDateTime ( left, right );
+ return (compare < 0);
+}
+
+bool P2_MetaHandler::GetFileModDate ( XMP_DateTime * modDate )
+{
+
+ // The P2 locations of metadata:
+ // CONTENTS/
+ // CLIP/
+ // 0001AB.XML
+ // 0001AB.XMP
+
+ bool ok, haveDate = false;
+ std::string fullPath;
+ XMP_DateTime oneDate, junkDate;
+ if ( modDate == 0 ) modDate = &junkDate;
+
+ ok = this->MakeClipFilePath ( &fullPath, ".XML", true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ ok = this->MakeClipFilePath ( &fullPath, ".XMP", true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ return haveDate;
+
+} // P2_MetaHandler::GetFileModDate
+
+// =================================================================================================
+// P2_MetaHandler::CacheFileData
+// =============================
+
+void P2_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( ! this->containsXMP );
+
+ if ( this->parent->UsesClientIO() ) {
+ XMP_Throw ( "XDCAM cannot be used with client-managed I/O", kXMPErr_InternalFailure );
+ }
+
+ // Make sure the clip's .XMP file exists.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, ".XMP" );
+ if ( Host_IO::GetFileMode ( xmpPath.c_str() ) != Host_IO::kFMode_IsFile ) return; // No XMP.
+
+ // Read the entire .XMP file.
+
+ bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate );
+
+ XMP_Assert ( this->parent->ioRef == 0 );
+ XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), readOnly );
+ if ( xmpFile == 0 ) return; // The open failed.
+ this->parent->ioRef = xmpFile;
+
+ XMP_Int64 xmpLen = xmpFile->Length();
+ if ( xmpLen > 100*1024*1024 ) {
+ XMP_Throw ( "P2 XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check.
+ }
+
+ this->xmpPacket.erase();
+ this->xmpPacket.append ( (size_t)xmpLen, ' ' );
+
+ XMP_Int32 ioCount = xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen );
+
+ this->packetInfo.offset = 0;
+ this->packetInfo.length = (XMP_Int32)xmpLen;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ this->containsXMP = true;
+
+} // P2_MetaHandler::CacheFileData
+
+// =================================================================================================
+// P2_MetaHandler::ProcessXMP
+// ==========================
+
+void P2_MetaHandler::ProcessXMP()
+{
+
+ // Some versions of gcc can't tolerate goto's across declarations.
+ // *** Better yet, avoid this cruft with self-cleaning objects.
+ #define CleanupAndExit \
+ { \
+ bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); \
+ if ( ! openForUpdate ) this->CleanupLegacyXML(); \
+ return; \
+ }
+
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( this->containsXMP ) {
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ }
+
+ std::string xmlPath;
+ this->MakeClipFilePath ( &xmlPath, ".XML" );
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return; // The open failed.
+ XMPFiles_IO xmlFile ( hostRef, xmlPath.c_str(), Host_IO::openReadOnly );
+
+ this->expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces );
+ if ( this->expat == 0 ) XMP_Throw ( "P2_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory );
+
+ XMP_Uns8 buffer [64*1024];
+ while ( true ) {
+ XMP_Int32 ioCount = xmlFile.Read ( buffer, sizeof(buffer) );
+ if ( ioCount == 0 ) break;
+ this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
+ }
+ this->expat->ParseBuffer ( 0, 0, true ); // End the parse.
+
+ xmlFile.Close();
+
+ // The root element should be P2Main in some namespace. At least 2 different namespaces are in
+ // use (ending in "v3.0" and "v3.1"). Take whatever this file uses.
+
+ XML_Node & xmlTree = this->expat->tree;
+ XML_NodePtr rootElem = 0;
+
+ for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) {
+ if ( xmlTree.content[i]->kind == kElemNode ) {
+ rootElem = xmlTree.content[i];
+ }
+ }
+
+ if ( rootElem == 0 ) CleanupAndExit
+ XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen;
+ if ( ! XMP_LitMatch ( rootLocalName, "P2Main" ) ) CleanupAndExit
+
+ this->p2NS = rootElem->ns;
+
+ // Now find ClipMetadata element and check the legacy digest.
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyContext, legacyProp;
+
+ legacyContext = rootElem->GetNamedElement ( p2NS, "ClipContent" );
+ if ( legacyContext == 0 ) CleanupAndExit
+
+ this->clipContent = legacyContext; // ! Save the ClipContext pointer for other use.
+
+ legacyContext = legacyContext->GetNamedElement ( p2NS, "ClipMetadata" );
+ if ( legacyContext == 0 ) CleanupAndExit
+
+ this->clipMetadata = legacyContext; // ! Save the ClipMetadata pointer for other use.
+
+ std::string oldDigest, newDigest;
+ bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "P2", &oldDigest, 0 );
+ if ( digestFound ) {
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) CleanupAndExit
+ }
+
+ // If we get here we need find and import the actual legacy elements using the current namespace.
+ // Either there is no old digest in the XMP, or the digests differ. In the former case keep any
+ // existing XMP, in the latter case take new legacy values.
+ this->SetXMPPropertyFromLegacyXML ( digestFound, this->clipContent, kXMP_NS_DC, "title", "ClipName", true );
+ this->SetXMPPropertyFromLegacyXML ( digestFound, this->clipContent, kXMP_NS_DC, "identifier", "GlobalClipID", false );
+ this->SetDurationFromLegacyXML (digestFound );
+ this->SetRelationsFromLegacyXML ( digestFound );
+ this->SetXMPPropertyFromLegacyXML ( digestFound, this->clipMetadata, kXMP_NS_DM, "shotName", "UserClipName", false );
+ this->SetAudioInfoFromLegacyXML ( digestFound );
+ this->SetVideoInfoFromLegacyXML ( digestFound );
+
+ legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Access" );
+ if ( legacyContext == 0 ) CleanupAndExit
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "creator" )) ) {
+ legacyProp = legacyContext->GetNamedElement ( p2NS, "Creator" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ this->xmpObj.DeleteProperty ( kXMP_NS_DC, "creator" );
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered,
+ legacyProp->GetLeafContentValue() );
+ this->containsXMP = true;
+ }
+ }
+
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_XMP, "CreateDate", "CreationDate", false );
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_XMP, "ModifyDate", "LastUpdateDate", false );
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "good" )) ) {
+ legacyProp = this->clipMetadata->GetNamedElement ( p2NS, "ShotMark" );
+ if ( (legacyProp == 0) || (! legacyProp->IsLeafContentNode()) ) {
+ this->xmpObj.DeleteProperty ( kXMP_NS_DM, "good" );
+ } else {
+ XMP_StringPtr markValue = legacyProp->GetLeafContentValue();
+ if ( markValue == 0 ) {
+ this->xmpObj.DeleteProperty ( kXMP_NS_DM, "good" );
+ } else if ( XMP_LitMatch ( markValue, "true" ) || XMP_LitMatch ( markValue, "1" ) ) {
+ this->xmpObj.SetProperty_Bool ( kXMP_NS_DM, "good", true, kXMP_DeleteExisting );
+ this->containsXMP = true;
+ } else if ( XMP_LitMatch ( markValue, "false" ) || XMP_LitMatch ( markValue, "0" ) ) {
+ this->xmpObj.SetProperty_Bool ( kXMP_NS_DM, "good", false, kXMP_DeleteExisting );
+ this->containsXMP = true;
+ }
+ }
+ }
+
+ legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Shoot" );
+ if ( legacyContext != 0 ) {
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Artist", "Shooter", false );
+ legacyContext = legacyContext->GetNamedElement ( p2NS, "Location" );
+ }
+
+ if ( legacyContext != 0 ) {
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "shotLocation", "PlaceName", false );
+ this->SetGPSPropertyFromLegacyXML ( legacyContext, digestFound, "GPSLongitude", "Longitude" );
+ this->SetGPSPropertyFromLegacyXML ( legacyContext, digestFound, "GPSLatitude", "Latitude" );
+ this->SetAltitudeFromLegacyXML ( legacyContext, digestFound );
+ }
+
+ legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Device" );
+ if ( legacyContext != 0 ) {
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Make", "Manufacturer", false );
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_EXIF_Aux, "SerialNumber", "SerialNo.", false );
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Model", "ModelName", false );
+ }
+
+ legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Scenario" );
+ if ( legacyContext != 0 ) {
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "scene", "SceneNo.", false );
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "takeNumber", "TakeNo.", false );
+ }
+
+ CleanupAndExit
+ #undef CleanupAndExit
+
+} // P2_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// P2_MetaHandler::UpdateFile
+// ==========================
+//
+// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here.
+
+void P2_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Make sure only called once.
+
+ XMP_Assert ( this->parent->UsesLocalIO() );
+
+ // Update the internal legacy XML tree if we have one, and set the digest in the XMP.
+
+ bool updateLegacyXML = false;
+
+ if ( this->clipMetadata != 0 ) {
+
+ XMP_Assert ( this->expat != 0 );
+
+ bool xmpFound;
+ std::string xmpValue;
+ XML_Node * xmlNode;
+
+ xmpFound = this->xmpObj.GetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", 0, &xmpValue, 0 );
+
+ if ( xmpFound ) {
+
+ xmlNode = this->ForceChildElement ( this->clipContent, "ClipName", 3 );
+
+ if ( xmpValue != xmlNode->GetLeafContentValue() ) {
+ xmlNode->SetLeafContentValue ( xmpValue.c_str() );
+ updateLegacyXML = true;
+ }
+
+ }
+
+ xmpFound = this->xmpObj.GetArrayItem ( kXMP_NS_DC, "creator", 1, &xmpValue, 0 );
+
+ if ( xmpFound ) {
+ xmlNode = this->ForceChildElement ( this->clipMetadata, "Access", 3 );
+ xmlNode = this->ForceChildElement ( xmlNode, "Creator", 4 );
+ if ( xmpValue != xmlNode->GetLeafContentValue() ) {
+ xmlNode->SetLeafContentValue ( xmpValue.c_str() );
+ updateLegacyXML = true;
+ }
+ }
+
+ }
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "P2", newDigest.c_str(), kXMP_DeleteExisting );
+
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );
+
+ // -----------------------------------------------------------------------
+ // Update the XMP file first, don't let legacy XML failures block the XMP.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, ".XMP" );
+
+ bool haveXMP = Host_IO::Exists ( xmpPath.c_str() );
+ if ( ! haveXMP ) {
+ XMP_Assert ( this->parent->ioRef == 0 );
+ Host_IO::Create ( xmpPath.c_str() );
+ this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), Host_IO::openReadWrite );
+ if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening P2 XMP file", kXMPErr_ExternalFailure );
+ }
+
+ XMP_IO* xmpFile = this->parent->ioRef;
+ XMP_Assert ( xmpFile != 0 );
+ XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) );
+
+ // --------------------------------------------
+ // Now update the legacy XML file if necessary.
+
+ if ( updateLegacyXML ) {
+
+ std::string legacyXML, xmlPath;
+ this->expat->tree.Serialize ( &legacyXML );
+ this->MakeClipFilePath ( &xmlPath, ".XML" );
+
+ bool haveXML = Host_IO::Exists ( xmlPath.c_str() );
+ if ( ! haveXML ) Host_IO::Create ( xmlPath.c_str() );
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadWrite );
+ if ( hostRef == Host_IO::noFileRef ) XMP_Throw ( "Failure opening P2 legacy XML file", kXMPErr_ExternalFailure );
+ XMPFiles_IO origXML ( hostRef, xmlPath.c_str(), Host_IO::openReadWrite );
+ XIO::ReplaceTextFile ( &origXML, legacyXML, (haveXML & doSafeUpdate) );
+ origXML.Close();
+
+ }
+
+} // P2_MetaHandler::UpdateFile
+
+// =================================================================================================
+// P2_MetaHandler::WriteTempFile
+// =============================
+
+void P2_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+
+ // ! WriteTempFile is not supposed to be called for handlers that own the file.
+ XMP_Throw ( "P2_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure );
+
+} // P2_MetaHandler::WriteTempFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/P2_Handler.hpp b/XMPFiles/source/FileHandlers/P2_Handler.hpp
new file mode 100644
index 0000000..93b2672
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/P2_Handler.hpp
@@ -0,0 +1,110 @@
+#ifndef __P2_Handler_hpp__
+#define __P2_Handler_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 "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+#include "source/ExpatAdapter.hpp"
+
+#include "third-party/zuid/interfaces/MD5.h"
+
+// =================================================================================================
+/// \file P2_Handler.hpp
+/// \brief Folder format handler for P2.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// *** Could derive from Basic_Handler - buffer file tail in a temp file.
+
+extern XMPFileHandler * P2_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool P2_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kP2_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_UsesSidecarXMP |
+ kXMPFiles_FolderBasedFormat);
+
+class P2_MetaHandler : public XMPFileHandler
+{
+public:
+
+ bool GetFileModDate ( XMP_DateTime * modDate );
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files.
+ { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); };
+
+ P2_MetaHandler ( XMPFiles * _parent );
+ virtual ~P2_MetaHandler();
+
+private:
+
+ P2_MetaHandler() : expat(0), clipMetadata(0), clipContent(0) {}; // Hidden on purpose.
+
+ bool MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false );
+ void MakeLegacyDigest ( std::string * digestStr );
+ void CleanupLegacyXML();
+
+ void DigestLegacyItem ( MD5_CTX & md5Context, XML_NodePtr legacyContext, XMP_StringPtr legacyPropName );
+ void DigestLegacyRelations ( MD5_CTX & md5Context );
+
+ void SetXMPPropertyFromLegacyXML ( bool digestFound,
+ XML_NodePtr legacyContext,
+ XMP_StringPtr schemaNS,
+ XMP_StringPtr propName,
+ XMP_StringPtr legacyPropName,
+ bool isLocalized );
+
+ void SetRelationsFromLegacyXML ( bool digestFound );
+ void SetAudioInfoFromLegacyXML ( bool digestFound );
+ void SetVideoInfoFromLegacyXML ( bool digestFound );
+ void SetDurationFromLegacyXML ( bool digestFound );
+
+ void SetVideoFrameInfoFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound );
+ void SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound );
+ void SetGPSPropertyFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound, XMP_StringPtr propName, XMP_StringPtr legacyPropName );
+ void SetAltitudeFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound );
+
+ XML_Node * ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, int indent = 0 );
+
+ std::string rootPath, clipName, p2NS;
+
+ ExpatAdapter * expat;
+ XML_Node * clipMetadata; // ! Don't delete, points into the Expat tree.
+ XML_Node * clipContent; // ! Don't delete, points into the Expat tree.
+
+}; // P2_MetaHandler
+
+// =================================================================================================
+
+#endif /* __P2_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/PNG_Handler.cpp b/XMPFiles/source/FileHandlers/PNG_Handler.cpp
new file mode 100644
index 0000000..52b65ff
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/PNG_Handler.cpp
@@ -0,0 +1,248 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FileHandlers/PNG_Handler.hpp"
+#include "XMPFiles/source/FormatSupport/PNG_Support.hpp"
+
+#include "source/XIO.hpp"
+
+using namespace std;
+
+// =================================================================================================
+/// \file PNG_Handler.hpp
+/// \brief File format handler for PNG.
+///
+/// This handler ...
+///
+// =================================================================================================
+
+// =================================================================================================
+// PNG_MetaHandlerCTor
+// ====================
+
+XMPFileHandler * PNG_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new PNG_MetaHandler ( parent );
+
+} // PNG_MetaHandlerCTor
+
+// =================================================================================================
+// PNG_CheckFormat
+// ===============
+
+bool PNG_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent )
+{
+ IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent);
+ XMP_Assert ( format == kXMP_PNGFile );
+
+ IOBuffer ioBuf;
+
+ fileRef->Rewind();
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, PNG_SIGNATURE_LEN ) ) return false; // We need at least 8, the buffer is filled anyway.
+
+ if ( ! CheckBytes ( ioBuf.ptr, PNG_SIGNATURE_DATA, PNG_SIGNATURE_LEN ) ) return false;
+
+ return true;
+
+} // PNG_CheckFormat
+
+// =================================================================================================
+// PNG_MetaHandler::PNG_MetaHandler
+// ==================================
+
+PNG_MetaHandler::PNG_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kPNG_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+}
+
+// =================================================================================================
+// PNG_MetaHandler::~PNG_MetaHandler
+// ===================================
+
+PNG_MetaHandler::~PNG_MetaHandler()
+{
+}
+
+// =================================================================================================
+// PNG_MetaHandler::CacheFileData
+// ===============================
+
+void PNG_MetaHandler::CacheFileData()
+{
+
+ this->containsXMP = false;
+
+ XMP_IO* fileRef ( this->parent->ioRef );
+ if ( fileRef == 0) return;
+
+ PNG_Support::ChunkState chunkState;
+ long numChunks = PNG_Support::OpenPNG ( fileRef, chunkState );
+ if ( numChunks == 0 ) return;
+
+ if (chunkState.xmpLen != 0)
+ {
+ // XMP present
+
+ this->xmpPacket.reserve(chunkState.xmpLen);
+ this->xmpPacket.assign(chunkState.xmpLen, ' ');
+
+ if (PNG_Support::ReadBuffer ( fileRef, chunkState.xmpPos, chunkState.xmpLen, const_cast<char *>(this->xmpPacket.data()) ))
+ {
+ this->packetInfo.offset = chunkState.xmpPos;
+ this->packetInfo.length = chunkState.xmpLen;
+ this->containsXMP = true;
+ }
+ }
+ else
+ {
+ // no XMP
+ }
+
+} // PNG_MetaHandler::CacheFileData
+
+// =================================================================================================
+// PNG_MetaHandler::ProcessXMP
+// ============================
+//
+// Process the raw XMP and legacy metadata that was previously cached.
+
+void PNG_MetaHandler::ProcessXMP()
+{
+ this->processedXMP = true; // Make sure we only come through here once.
+
+ // Process the XMP packet.
+
+ if ( ! this->xmpPacket.empty() ) {
+
+ XMP_Assert ( this->containsXMP );
+ XMP_StringPtr packetStr = this->xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
+
+ this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
+
+ this->containsXMP = true;
+
+ }
+
+} // PNG_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// PNG_MetaHandler::UpdateFile
+// ============================
+
+void PNG_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ bool updated = false;
+
+ if ( ! this->needsUpdate ) return;
+ if ( doSafeUpdate ) XMP_Throw ( "PNG_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
+
+ XMP_StringPtr packetStr = xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
+ if ( packetLen == 0 ) return;
+
+ XMP_IO* fileRef(this->parent->ioRef);
+ if ( fileRef == 0 ) return;
+
+ PNG_Support::ChunkState chunkState;
+ long numChunks = PNG_Support::OpenPNG ( fileRef, chunkState );
+ if ( numChunks == 0 ) return;
+
+ // write/update chunk
+ if (chunkState.xmpLen == 0)
+ {
+ // no current chunk -> inject
+ updated = SafeWriteFile();
+ }
+ else if (chunkState.xmpLen >= packetLen )
+ {
+ // current chunk size is sufficient -> write and update CRC (in place update)
+ updated = PNG_Support::WriteBuffer(fileRef, chunkState.xmpPos, packetLen, packetStr );
+ PNG_Support::UpdateChunkCRC(fileRef, chunkState.xmpChunk );
+ }
+ else if (chunkState.xmpLen < packetLen)
+ {
+ // XMP is too large for current chunk -> expand
+ updated = SafeWriteFile();
+ }
+
+ if ( ! updated )return; // If there's an error writing the chunk, bail.
+
+ this->needsUpdate = false;
+
+} // PNG_MetaHandler::UpdateFile
+
+// =================================================================================================
+// PNG_MetaHandler::WriteTempFile
+// ==============================
+
+void PNG_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ XMP_IO* originalRef = this->parent->ioRef;
+
+ PNG_Support::ChunkState chunkState;
+ long numChunks = PNG_Support::OpenPNG ( originalRef, chunkState );
+ if ( numChunks == 0 ) return;
+
+ tempRef->Truncate ( 0 );
+ tempRef->Write ( PNG_SIGNATURE_DATA, PNG_SIGNATURE_LEN );
+
+ PNG_Support::ChunkIterator curPos = chunkState.chunks.begin();
+ PNG_Support::ChunkIterator endPos = chunkState.chunks.end();
+
+ for (; (curPos != endPos); ++curPos)
+ {
+ PNG_Support::ChunkData chunk = *curPos;
+
+ // discard existing XMP chunk
+ if (chunk.xmp)
+ continue;
+
+ // copy any other chunk
+ PNG_Support::CopyChunk(originalRef, tempRef, chunk);
+
+ // place XMP chunk immediately after IHDR-chunk
+ if (PNG_Support::CheckIHDRChunkHeader(chunk))
+ {
+ XMP_StringPtr packetStr = xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
+
+ PNG_Support::WriteXMPChunk(tempRef, packetLen, packetStr );
+ }
+ }
+
+} // PNG_MetaHandler::WriteTempFile
+
+// =================================================================================================
+// PNG_MetaHandler::SafeWriteFile
+// ==============================
+
+bool PNG_MetaHandler::SafeWriteFile()
+{
+ XMP_IO* originalFile = this->parent->ioRef;
+ XMP_IO* tempFile = originalFile->DeriveTemp();
+ if ( tempFile == 0 ) XMP_Throw ( "Failure creating PNG temp file", kXMPErr_InternalFailure );
+
+ this->WriteTempFile ( tempFile );
+ originalFile->AbsorbTemp();
+
+ return true;
+
+} // PNG_MetaHandler::SafeWriteFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/PNG_Handler.hpp b/XMPFiles/source/FileHandlers/PNG_Handler.hpp
new file mode 100644
index 0000000..9298141
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/PNG_Handler.hpp
@@ -0,0 +1,62 @@
+#ifndef __PNG_Handler_hpp__
+#define __PNG_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/PNG_Support.hpp"
+#include "source/XIO.hpp"
+
+// =================================================================================================
+/// \file PNG_Handler.hpp
+/// \brief File format handler for PNG.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// *** Could derive from Basic_Handler - buffer file tail in a temp file.
+
+extern XMPFileHandler* PNG_MetaHandlerCTor ( XMPFiles* parent );
+
+extern bool PNG_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO * fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kPNG_HandlerFlags = ( kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_NeedsReadOnlyPacket );
+
+class PNG_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ bool SafeWriteFile();
+
+ PNG_MetaHandler ( XMPFiles* parent );
+ virtual ~PNG_MetaHandler();
+
+}; // PNG_MetaHandler
+
+// =================================================================================================
+
+#endif /* __PNG_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/PSD_Handler.cpp b/XMPFiles/source/FileHandlers/PSD_Handler.cpp
new file mode 100644
index 0000000..d57f91c
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/PSD_Handler.cpp
@@ -0,0 +1,417 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/PSD_Handler.hpp"
+
+#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp"
+#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp"
+#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp"
+#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp"
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+
+#include "third-party/zuid/interfaces/MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file PSD_Handler.cpp
+/// \brief File format handler for PSD (Photoshop).
+///
+/// This handler ...
+///
+// =================================================================================================
+
+// =================================================================================================
+// PSD_CheckFormat
+// ===============
+
+// For PSD we just check the "8BPS" signature, the following version, and that the file is at least
+// 34 bytes long. This covers the 26 byte header, the 4 byte color mode section length (which might
+// be 0), and the 4 byte image resource section length (which might be 0). The parsing logic in
+// CacheFileData will do further checks that the image resources actually exist. Those checks are
+// not needed to decide if this is a PSD file though, instead they decide if this is valid PSD.
+
+// ! The CheckXyzFormat routines don't track the filePos, that is left to ScanXyzFile.
+
+bool PSD_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent )
+{
+ IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(parent);
+ XMP_Assert ( format == kXMP_PhotoshopFile );
+
+ IOBuffer ioBuf;
+
+ fileRef->Rewind ( );
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 34 ) ) return false; // 34 = header plus 2 lengths
+
+ if ( ! CheckBytes ( ioBuf.ptr, "8BPS", 4 ) ) return false;
+ ioBuf.ptr += 4; // Move to the version.
+ XMP_Uns16 version = GetUns16BE ( ioBuf.ptr );
+ if ( (version != 1) && (version != 2) ) return false;
+
+ return true;
+
+} // PSD_CheckFormat
+
+// =================================================================================================
+// PSD_MetaHandlerCTor
+// ===================
+
+XMPFileHandler * PSD_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new PSD_MetaHandler ( parent );
+
+} // PSD_MetaHandlerCTor
+
+// =================================================================================================
+// PSD_MetaHandler::PSD_MetaHandler
+// ================================
+
+PSD_MetaHandler::PSD_MetaHandler ( XMPFiles * _parent ) : iptcMgr(0), exifMgr(0), skipReconcile(false)
+{
+ this->parent = _parent;
+ this->handlerFlags = kPSD_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+} // PSD_MetaHandler::PSD_MetaHandler
+
+// =================================================================================================
+// PSD_MetaHandler::~PSD_MetaHandler
+// =================================
+
+PSD_MetaHandler::~PSD_MetaHandler()
+{
+
+ if ( this->iptcMgr != 0 ) delete ( this->iptcMgr );
+ if ( this->exifMgr != 0 ) delete ( this->exifMgr );
+
+} // PSD_MetaHandler::~PSD_MetaHandler
+
+// =================================================================================================
+// PSD_MetaHandler::CacheFileData
+// ==============================
+//
+// Find and parse the image resource section, everything we want is in there. Don't simply capture
+// the whole section, there could be lots of stuff we don't care about.
+
+// *** This implementation simply returns when an invalid file is encountered. Should we throw instead?
+
+void PSD_MetaHandler::CacheFileData()
+{
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_PacketInfo & packetInfo = this->packetInfo;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_Assert ( ! this->containsXMP );
+ // Set containsXMP to true here only if the XMP image resource is found.
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "PSD_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
+ }
+
+ XMP_Uns8 psdHeader[30];
+ XMP_Uns32 ioLen, cmLen, psirLen;
+
+ XMP_Int64 filePos = 0;
+ fileRef->Rewind ( );
+
+ ioLen = fileRef->Read ( psdHeader, 30 );
+ if ( ioLen != 30 ) return; // Throw?
+
+ this->imageHeight = GetUns32BE ( &psdHeader[14] );
+ this->imageWidth = GetUns32BE ( &psdHeader[18] );
+
+ cmLen = GetUns32BE ( &psdHeader[26] );
+
+ XMP_Int64 psirOrigin = 26 + 4 + cmLen;
+
+ filePos = fileRef->Seek ( psirOrigin, kXMP_SeekFromStart );
+ if ( filePos != psirOrigin ) return; // Throw?
+
+ ioLen = fileRef->Read ( psdHeader, 4 );
+ if ( ioLen != 4 ) return; // Throw?
+
+ psirLen = GetUns32BE ( &psdHeader[0] );
+
+ this->psirMgr.ParseFileResources ( fileRef, psirLen );
+
+ PSIR_Manager::ImgRsrcInfo xmpInfo;
+ bool found = this->psirMgr.GetImgRsrc ( kPSIR_XMP, &xmpInfo );
+
+ if ( found ) {
+
+ // printf ( "PSD_MetaHandler::CacheFileData - XMP packet offset %d (0x%X), size %d\n",
+ // xmpInfo.origOffset, xmpInfo.origOffset, xmpInfo.dataLen );
+ this->packetInfo.offset = xmpInfo.origOffset;
+ this->packetInfo.length = xmpInfo.dataLen;
+ this->packetInfo.padSize = 0; // Assume for now, set these properly in ProcessXMP.
+ this->packetInfo.charForm = kXMP_CharUnknown;
+ this->packetInfo.writeable = true;
+
+ this->xmpPacket.assign ( (XMP_StringPtr)xmpInfo.dataPtr, xmpInfo.dataLen );
+
+ this->containsXMP = true;
+
+ }
+
+} // PSD_MetaHandler::CacheFileData
+
+// =================================================================================================
+// PSD_MetaHandler::ProcessXMP
+// ===========================
+//
+// Process the raw XMP and legacy metadata that was previously cached.
+
+void PSD_MetaHandler::ProcessXMP()
+{
+
+ this->processedXMP = true; // Make sure we only come through here once.
+
+ // Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy
+ // import if the XMP packet gets parsing errors.
+
+ bool readOnly = ((this->parent->openFlags & kXMPFiles_OpenForUpdate) == 0);
+
+ if ( readOnly ) {
+ this->iptcMgr = new IPTC_Reader();
+ this->exifMgr = new TIFF_MemoryReader();
+ } else {
+ this->iptcMgr = new IPTC_Writer(); // ! Parse it later.
+ this->exifMgr = new TIFF_FileWriter();
+ }
+
+ PSIR_Manager & psir = this->psirMgr; // Give the compiler help in recognizing non-aliases.
+ IPTC_Manager & iptc = *this->iptcMgr;
+ TIFF_Manager & exif = *this->exifMgr;
+
+ PSIR_Manager::ImgRsrcInfo iptcInfo, exifInfo;
+ bool haveIPTC = psir.GetImgRsrc ( kPSIR_IPTC, &iptcInfo );
+ bool haveExif = psir.GetImgRsrc ( kPSIR_Exif, &exifInfo );
+ int iptcDigestState = kDigestMatches;
+
+ if ( haveExif ) exif.ParseMemoryStream ( exifInfo.dataPtr, exifInfo.dataLen );
+
+ if ( haveIPTC ) {
+
+ bool haveDigest = false;
+ PSIR_Manager::ImgRsrcInfo digestInfo;
+ haveDigest = psir.GetImgRsrc ( kPSIR_IPTCDigest, &digestInfo );
+ if ( digestInfo.dataLen != 16 ) haveDigest = false;
+
+ if ( ! haveDigest ) {
+ iptcDigestState = kDigestMissing;
+ } else {
+ iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, iptcInfo.dataLen, digestInfo.dataPtr );
+ }
+
+ }
+
+ XMP_OptionBits options = 0;
+ if ( this->containsXMP ) options |= k2XMP_FileHadXMP;
+ if ( haveIPTC ) options |= k2XMP_FileHadIPTC;
+ if ( haveExif ) options |= k2XMP_FileHadExif;
+
+ // Process the XMP packet. If it fails to parse, do a forced legacy import but still throw an
+ // exception. This tells the caller that an error happened, but gives them recovered legacy
+ // should they want to proceed with that.
+
+ bool haveXMP = false;
+
+ if ( ! this->xmpPacket.empty() ) {
+ XMP_Assert ( this->containsXMP );
+ // Common code takes care of packetInfo.charForm, .padSize, and .writeable.
+ XMP_StringPtr packetStr = this->xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
+ try {
+ this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
+ haveXMP = true;
+ } catch ( ... ) {
+ XMP_ClearOption ( options, k2XMP_FileHadXMP );
+ if ( haveIPTC ) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen );
+ if ( iptcDigestState == kDigestMatches ) iptcDigestState = kDigestMissing;
+ ImportPhotoData ( exif, iptc, psir, iptcDigestState, &this->xmpObj, options );
+ throw; // ! Rethrow the exception, don't absorb it.
+ }
+ }
+
+ // Process the legacy metadata.
+
+ if ( haveIPTC && (! haveXMP) && (iptcDigestState == kDigestMatches) ) iptcDigestState = kDigestMissing;
+ bool parseIPTC = (iptcDigestState != kDigestMatches) || (! readOnly);
+ if ( parseIPTC ) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen );
+ ImportPhotoData ( exif, iptc, psir, iptcDigestState, &this->xmpObj, options );
+ this->containsXMP = true; // Assume we now have something in the XMP.
+
+} // PSD_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// PSD_MetaHandler::UpdateFile
+// ===========================
+
+void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates.
+
+ XMP_Int64 oldPacketOffset = this->packetInfo.offset;
+ XMP_Int32 oldPacketLength = this->packetInfo.length;
+
+ if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks.
+ if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0;
+
+ bool fileHadXMP = ((oldPacketOffset != 0) && (oldPacketLength != 0));
+
+ // Update the IPTC-IIM and native TIFF/Exif metadata. ExportPhotoData also trips the tiff: and
+ // exif: copies from the XMP, so reserialize the now final XMP packet.
+
+ ExportPhotoData ( kXMP_PhotoshopFile, &this->xmpObj, this->exifMgr, this->iptcMgr, &this->psirMgr );
+
+ try {
+ XMP_OptionBits options = kXMP_UseCompactFormat;
+ if ( fileHadXMP ) options |= kXMP_ExactPacketLength;
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, options, oldPacketLength );
+ } catch ( ... ) {
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+ }
+
+ // Decide whether to do an in-place update. This can only happen if all of the following are true:
+ // - There is an XMP packet in the file.
+ // - The are no changes to the legacy image resources. (The IPTC and EXIF are in the PSIR.)
+ // - The new XMP can fit in the old space.
+
+ bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength));
+ if ( this->psirMgr.IsLegacyChanged() ) doInPlace = false;
+
+ if ( doInPlace ) {
+
+ #if GatherPerformanceData
+ sAPIPerf->back().extraInfo += ", PSD in-place update";
+ #endif
+
+ if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) {
+ // They ought to match, cheap to be sure.
+ size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size();
+ this->xmpPacket.append ( extraSpace, ' ' );
+ }
+
+ XMP_IO* liveFile = this->parent->ioRef;
+
+ XMP_Assert ( this->xmpPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic.
+
+ // printf ( "PSD_MetaHandler::UpdateFile - XMP in-place packet offset %lld (0x%llX), size %d\n",
+ // oldPacketOffset, oldPacketOffset, this->xmpPacket.size() );
+ liveFile->Seek ( oldPacketOffset, kXMP_SeekFromStart );
+ liveFile->Write ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+
+ } else {
+
+ #if GatherPerformanceData
+ sAPIPerf->back().extraInfo += ", PSD copy update";
+ #endif
+
+ XMP_IO* origRef = this->parent->ioRef;
+ XMP_IO* tempRef = origRef->DeriveTemp();
+
+ try {
+ XMP_Assert ( ! this->skipReconcile );
+ this->skipReconcile = true;
+ this->WriteTempFile ( tempRef );
+ this->skipReconcile = false;
+ } catch ( ... ) {
+ this->skipReconcile = false;
+ origRef->DeleteTemp();
+ throw;
+ }
+
+ origRef->AbsorbTemp();
+
+ }
+
+ this->needsUpdate = false;
+
+} // PSD_MetaHandler::UpdateFile
+
+// =================================================================================================
+// PSD_MetaHandler::WriteTempFile
+// ==============================
+
+// The metadata parts of a Photoshop file are all in the image resources. The PSIR_Manager's
+// UpdateFileResources method will take care of the image resource portion of the file, updating
+// those resources that have changed and preserving those that have not.
+
+void PSD_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ XMP_IO* origRef = this->parent->ioRef;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_Uns64 sourceLen = origRef->Length();
+ if ( sourceLen == 0 ) return; // Tolerate empty files.
+
+ // Reconcile the legacy metadata, unless this is called from UpdateFile. Reserialize the XMP to
+ // get standard padding, PutXMP has probably done an in-place serialize. Set the XMP image resource.
+
+ if ( ! skipReconcile ) {
+ // Update the IPTC-IIM and native TIFF/Exif metadata, and reserialize the now final XMP packet.
+ ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, &this->psirMgr );
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+ }
+
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+ this->packetInfo.offset = kXMPFiles_UnknownOffset;
+ this->packetInfo.length = (XMP_StringLen)this->xmpPacket.size();
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ this->psirMgr.SetImgRsrc ( kPSIR_XMP, this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+
+ // Copy the file header and color mode section, then write the updated image resource section,
+ // and copy the tail of the source file (layer and mask section to EOF).
+
+ origRef->Rewind ( );
+ tempRef->Truncate ( 0 );
+
+ XIO::Copy ( origRef, tempRef, 26 ); // Copy the file header.
+
+ XMP_Uns32 cmLen;
+ origRef->Read ( &cmLen, 4 );
+ tempRef->Write ( &cmLen, 4 ); // Copy the color mode section length.
+ cmLen = GetUns32BE ( &cmLen );
+ XIO::Copy ( origRef, tempRef, cmLen ); // Copy the color mode section contents.
+
+ XMP_Uns32 irLen;
+ origRef->Read ( &irLen, 4 ); // Get the source image resource section length.
+ irLen = GetUns32BE ( &irLen );
+
+ this->psirMgr.UpdateFileResources ( origRef, tempRef, 0, abortProc, abortArg );
+
+ XMP_Uns64 tailOffset = 26 + 4 + cmLen + 4 + irLen;
+ XMP_Uns64 tailLength = sourceLen - tailOffset;
+
+ origRef->Seek ( tailOffset, kXMP_SeekFromStart );
+ tempRef->Seek ( 0, kXMP_SeekFromEnd );
+ XIO::Copy ( origRef, tempRef, tailLength ); // Copy the tail of the file.
+
+ this->needsUpdate = false;
+
+} // PSD_MetaHandler::WriteTempFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/PSD_Handler.hpp b/XMPFiles/source/FileHandlers/PSD_Handler.hpp
new file mode 100644
index 0000000..a64b6df
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/PSD_Handler.hpp
@@ -0,0 +1,72 @@
+#ifndef __PSD_Handler_hpp__
+#define __PSD_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp"
+#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp"
+#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp"
+
+// =================================================================================================
+/// \file PSD_Handler.hpp
+/// \brief File format handler for PSD (Photoshop).
+///
+/// This header ...
+///
+// =================================================================================================
+
+// *** Could derive from Basic_Handler - buffer file tail in a temp file.
+
+extern XMPFileHandler * PSD_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool PSD_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kPSD_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_AllowsSafeUpdate);
+
+class PSD_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ bool skipReconcile; // ! Used between UpdateFile and WriteFile.
+
+ PSD_MetaHandler ( XMPFiles * parent );
+ virtual ~PSD_MetaHandler();
+
+private:
+
+ PSD_MetaHandler() : iptcMgr(0), exifMgr(0), skipReconcile(false) {}; // Hidden on purpose.
+
+ PSIR_FileWriter psirMgr; // Don't need a pointer, the PSIR part is always file-based.
+ IPTC_Manager * iptcMgr; // Need to use pointers so we can properly select between read-only
+ TIFF_Manager * exifMgr; // and read-write modes of usage.
+
+ XMP_Uns32 imageWidth, imageHeight; // Pixel dimensions, used with thumbnail info.
+
+}; // PSD_MetaHandler
+
+// =================================================================================================
+
+#endif /* __PSD_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/PostScript_Handler.cpp b/XMPFiles/source/FileHandlers/PostScript_Handler.cpp
new file mode 100644
index 0000000..bd5d4f7
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/PostScript_Handler.cpp
@@ -0,0 +1,574 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/PostScript_Handler.hpp"
+
+#include "XMPFiles/source/FormatSupport/XMPScanner.hpp"
+#include "XMPFiles/source/FileHandlers/Scanner_Handler.hpp"
+
+using namespace std;
+
+// =================================================================================================
+/// \file PostScript_Handler.cpp
+/// \brief File format handler for PostScript and EPS files.
+///
+/// This header ...
+///
+// =================================================================================================
+
+static const char * kPSFileTag = "%!PS-Adobe-";
+static const size_t kPSFileTagLen = strlen ( kPSFileTag );
+
+// =================================================================================================
+// PostScript_MetaHandlerCTor
+// ==========================
+
+XMPFileHandler * PostScript_MetaHandlerCTor ( XMPFiles * parent )
+{
+ XMPFileHandler * newHandler = new PostScript_MetaHandler ( parent );
+
+ return newHandler;
+
+} // PostScript_MetaHandlerCTor
+
+// =================================================================================================
+// PostScript_CheckFormat
+// ======================
+
+bool PostScript_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent )
+{
+ IgnoreParam(filePath); IgnoreParam(parent);
+ XMP_Assert ( (format == kXMP_EPSFile) || (format == kXMP_PostScriptFile) );
+
+ IOBuffer ioBuf;
+
+ XMP_Int64 psOffset;
+ size_t psLength;
+ XMP_Uns32 temp1, temp2;
+
+ // Check for the binary EPSF preview header.
+
+ fileRef->Rewind();
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false;
+ temp1 = GetUns32BE ( ioBuf.ptr );
+
+ if ( temp1 == 0xC5D0D3C6 ) {
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 30 ) ) return false;
+
+ psOffset = GetUns32LE ( ioBuf.ptr+4 ); // PostScript offset.
+ psLength = GetUns32LE ( ioBuf.ptr+8 ); // PostScript length.
+
+ FillBuffer ( fileRef, psOffset, &ioBuf ); // Make sure buffer starts at psOffset for length check.
+ if ( (ioBuf.len < kIOBufferSize) && (ioBuf.len < psLength) ) return false; // Not enough PostScript.
+
+ }
+
+ // Check the start of the PostScript DSC header comment.
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, (kPSFileTagLen + 3 + 1) ) ) return false;
+ if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSFileTag), kPSFileTagLen ) ) return false;
+ ioBuf.ptr += kPSFileTagLen;
+
+ // Check the PostScript DSC major version number.
+
+ temp1 = 0;
+ while ( (ioBuf.ptr < ioBuf.limit) && ('0' <= *ioBuf.ptr) && (*ioBuf.ptr <= '9') ) {
+ temp1 = (temp1 * 10) + (*ioBuf.ptr - '0');
+ if ( temp1 > 1000 ) return false; // Overflow.
+ ioBuf.ptr += 1;
+ }
+ if ( temp1 < 3 ) return false; // The version must be at least 3.0.
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return false;
+ if ( *ioBuf.ptr != '.' ) return false; // No minor number.
+ ioBuf.ptr += 1;
+
+ // Check the PostScript DSC minor version number.
+
+ temp2 = 0;
+ while ( (ioBuf.ptr < ioBuf.limit) && ('0' <= *ioBuf.ptr) && (*ioBuf.ptr <= '9') ) {
+ temp2 = (temp2 * 10) + (*ioBuf.ptr - '0');
+ if ( temp2 > 1000 ) return false; // Overflow.
+ ioBuf.ptr += 1;
+ }
+ // We don't care about the actual minor version number.
+
+ if ( format == kXMP_PostScriptFile ) {
+
+ // Almost done for plain PostScript, check for whitespace.
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
+ if ( (*ioBuf.ptr != ' ') && (*ioBuf.ptr != kLF) && (*ioBuf.ptr != kCR) ) return false;
+ ioBuf.ptr += 1;
+
+ } else {
+
+ // Check for the EPSF keyword on the header comment.
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 6+3+1 ) ) return false;
+ if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(" EPSF-"), 6 ) ) return false;
+ ioBuf.ptr += 6;
+
+ // Check the EPS major version number.
+
+ temp1 = 0;
+ while ( (ioBuf.ptr < ioBuf.limit) && ('0' <= *ioBuf.ptr) && (*ioBuf.ptr <= '9') ) {
+ temp1 = (temp1 * 10) + (*ioBuf.ptr - '0');
+ if ( temp1 > 1000 ) return false; // Overflow.
+ ioBuf.ptr += 1;
+ }
+ if ( temp1 < 3 ) return false; // The version must be at least 3.0.
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return false;
+ if ( *ioBuf.ptr != '.' ) return false; // No minor number.
+ ioBuf.ptr += 1;
+
+ // Check the EPS minor version number.
+
+ temp2 = 0;
+ while ( (ioBuf.ptr < ioBuf.limit) && ('0' <= *ioBuf.ptr) && (*ioBuf.ptr <= '9') ) {
+ temp2 = (temp2 * 10) + (*ioBuf.ptr - '0');
+ if ( temp2 > 1000 ) return false; // Overflow.
+ ioBuf.ptr += 1;
+ }
+ // We don't care about the actual minor version number.
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
+ if ( (*ioBuf.ptr != kLF) && (*ioBuf.ptr != kCR) ) return false;
+ ioBuf.ptr += 1;
+
+ }
+
+ return true;
+
+} // PostScript_CheckFormat
+
+// =================================================================================================
+// PostScript_MetaHandler::PostScript_MetaHandler
+// ==============================================
+
+PostScript_MetaHandler::PostScript_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kPostScript_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+ this->psHint = kPSHint_NoMarker;
+
+} // PostScript_MetaHandler::PostScript_MetaHandler
+
+// =================================================================================================
+// PostScript_MetaHandler::~PostScript_MetaHandler
+// ===============================================
+
+PostScript_MetaHandler::~PostScript_MetaHandler()
+{
+ // ! Inherit the base cleanup.
+
+} // PostScript_MetaHandler::~PostScript_MetaHandler
+
+// =================================================================================================
+// PostScript_MetaHandler::FindPostScriptHint
+// ==========================================
+//
+// Search for "%ADO_ContainsXMP:" at the beginning of a line, it must be before "%%EndComments". If
+// the XMP marker is found, look for the MainFirst/MainLast/NoMain options.
+
+static const char * kPSContainsXMPString = "%ADO_ContainsXMP:";
+static const size_t kPSContainsXMPLength = strlen ( kPSContainsXMPString );
+
+static const char * kPSEndCommentString = "%%EndComments"; // ! Assumed shorter than kPSContainsXMPString.
+static const size_t kPSEndCommentLength = strlen ( kPSEndCommentString );
+
+int PostScript_MetaHandler::FindPostScriptHint()
+{
+ bool found = false;
+ IOBuffer ioBuf;
+ XMP_Uns8 ch;
+
+ XMP_IO* fileRef = this->parent->ioRef;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ // Check for the binary EPSF preview header.
+
+ fileRef->Rewind();
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false;
+ XMP_Uns32 temp1 = GetUns32BE ( ioBuf.ptr );
+
+ if ( temp1 == 0xC5D0D3C6 ) {
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 30 ) ) return false;
+
+ XMP_Uns32 psOffset = GetUns32LE ( ioBuf.ptr+4 ); // PostScript offset.
+ XMP_Uns32 psLength = GetUns32LE ( ioBuf.ptr+8 ); // PostScript length.
+
+ MoveToOffset ( fileRef, psOffset, &ioBuf );
+
+ }
+
+ // Look for the ContainsXMP comment.
+
+ while ( true ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "PostScript_MetaHandler::FindPostScriptHint - User abort", kXMPErr_UserAbort );
+ }
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsXMPLength ) ) return kPSHint_NoMarker;
+
+ if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString), kPSEndCommentLength ) ) {
+
+ // Found "%%EndComments", don't look any further.
+ return kPSHint_NoMarker;
+
+ } else if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsXMPString), kPSContainsXMPLength ) ) {
+
+ // Not "%%EndComments" or "%ADO_ContainsXMP:", skip past the end of this line.
+ do {
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMarker;
+ ch = *ioBuf.ptr;
+ ++ioBuf.ptr;
+ } while ( ! IsNewline ( ch ) );
+
+ } else {
+
+ // Found "%ADO_ContainsXMP:", look for the main packet location option.
+
+ ioBuf.ptr += kPSContainsXMPLength;
+ int xmpHint = kPSHint_NoMain; // ! From here on, a failure means "no main", not "no marker".
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain;
+ if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) return kPSHint_NoMain;
+
+ while ( true ) {
+
+ while ( true ) { // Skip leading spaces and tabs.
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain;
+ if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) break;
+ ++ioBuf.ptr;
+ }
+ if ( IsNewline ( *ioBuf.ptr ) ) return kPSHint_NoMain; // Reached the end of the ContainsXMP comment.
+
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 6 ) ) return kPSHint_NoMain;
+
+ if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("NoMain"), 6 ) ) {
+
+ ioBuf.ptr += 6;
+ xmpHint = kPSHint_NoMain;
+ break;
+
+ } else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainFi"), 6 ) ) {
+
+ ioBuf.ptr += 6;
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return kPSHint_NoMain;
+ if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("rst"), 3 ) ) {
+ ioBuf.ptr += 3;
+ xmpHint = kPSHint_MainFirst;
+ }
+ break;
+
+ } else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainLa"), 6 ) ) {
+
+ ioBuf.ptr += 6;
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return kPSHint_NoMain;
+ if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("st"), 2 ) ) {
+ ioBuf.ptr += 2;
+ xmpHint = kPSHint_MainLast;
+ }
+ break;
+
+ } else {
+
+ while ( true ) { // Skip until whitespace.
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain;
+ if ( IsWhitespace ( *ioBuf.ptr ) ) break;
+ ++ioBuf.ptr;
+ }
+
+ }
+
+ } // Look for the main packet location option.
+
+ // Make sure we found exactly a known option.
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain;
+ if ( ! IsWhitespace ( *ioBuf.ptr ) ) return kPSHint_NoMain;
+ return xmpHint;
+
+ } // Found "%ADO_ContainsXMP:".
+
+ } // Outer marker loop.
+
+ return kPSHint_NoMarker; // Should never reach here.
+
+} // PostScript_MetaHandler::FindPostScriptHint
+
+
+// =================================================================================================
+// PostScript_MetaHandler::FindFirstPacket
+// =======================================
+//
+// Run the packet scanner until we find a valid packet. The first one is the main. For simplicity,
+// the state of all snips is checked after each buffer is read. In theory only the last of the
+// previous snips might change from partial to valid, but then we would have to special case the
+// first pass when there is no previous set of snips. Since we have to get a full report to look at
+// the last snip anyway, it costs virtually nothing extra to recheck all of the snips.
+
+bool PostScript_MetaHandler::FindFirstPacket()
+{
+ int snipCount;
+ bool found = false;
+ size_t bufPos, bufLen;
+
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_Int64 fileLen = fileRef->Length();
+ XMP_PacketInfo & packetInfo = this->packetInfo;
+
+ XMPScanner scanner ( fileLen );
+ XMPScanner::SnipInfoVector snips;
+
+ enum { kBufferSize = 64*1024 };
+ XMP_Uns8 buffer [kBufferSize];
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ bufPos = 0;
+ bufLen = 0;
+
+ fileRef->Rewind(); // Seek back to the beginning of the file.
+
+ while ( true ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "PostScript_MetaHandler::FindFirstPacket - User abort", kXMPErr_UserAbort );
+ }
+
+ bufPos += bufLen;
+ bufLen = fileRef->Read ( buffer, kBufferSize );
+ if ( bufLen == 0 ) return false; // Must be at EoF, no packets found.
+
+ scanner.Scan ( buffer, bufPos, bufLen );
+ snipCount = scanner.GetSnipCount();
+ scanner.Report ( snips );
+
+ for ( int i = 0; i < snipCount; ++i ) {
+ if ( snips[i].fState == XMPScanner::eValidPacketSnip ) {
+ if ( snips[i].fLength > 0x7FFFFFFF ) XMP_Throw ( "PostScript_MetaHandler::FindFirstPacket: Oversize packet", kXMPErr_BadXMP );
+ packetInfo.offset = snips[i].fOffset;
+ packetInfo.length = (XMP_Int32)snips[i].fLength;
+ packetInfo.charForm = snips[i].fCharForm;
+ packetInfo.writeable = (snips[i].fAccess == 'w');
+ return true;
+ }
+ }
+
+ }
+
+ return false;
+
+} // FindFirstPacket
+
+
+// =================================================================================================
+// PostScript_MetaHandler::FindLastPacket
+// ======================================
+//
+// Run the packet scanner backwards until we find the start of a packet, or a valid packet. If we
+// found a packet start, resume forward scanning to see if it is a valid packet. For simplicity, all
+// of the snips are checked on each pass, for much the same reasons as in FindFirstPacket.
+
+#if 1
+
+// *** Doing this right (as described above) requires out of order scanning support which isn't
+// *** implemented yet. For now we scan the whole file and pick the last valid packet.
+
+bool PostScript_MetaHandler::FindLastPacket()
+{
+ int pkt;
+ size_t bufPos, bufLen;
+
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_Int64 fileLen = fileRef->Length();
+ XMP_PacketInfo & packetInfo = this->packetInfo;
+
+ // ------------------------------------------------------
+ // Scan the entire file to find all of the valid packets.
+
+ XMPScanner scanner ( fileLen );
+
+ enum { kBufferSize = 64*1024 };
+ XMP_Uns8 buffer [kBufferSize];
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ fileRef->Rewind(); // Seek back to the beginning of the file.
+
+ for ( bufPos = 0; bufPos < (size_t)fileLen; bufPos += bufLen ) {
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "PostScript_MetaHandler::FindLastPacket - User abort", kXMPErr_UserAbort );
+ }
+ bufLen = fileRef->Read ( buffer, kBufferSize );
+ if ( bufLen == 0 ) XMP_Throw ( "PostScript_MetaHandler::FindLastPacket: Read failure", kXMPErr_ExternalFailure );
+ scanner.Scan ( buffer, bufPos, bufLen );
+ }
+
+ // -------------------------------
+ // Pick the last the valid packet.
+
+ int snipCount = scanner.GetSnipCount();
+
+ XMPScanner::SnipInfoVector snips ( snipCount );
+ scanner.Report ( snips );
+
+ for ( pkt = snipCount-1; pkt >= 0; --pkt ) {
+ if ( snips[pkt].fState == XMPScanner::eValidPacketSnip ) break;
+ }
+
+ if ( pkt >= 0 ) {
+ if ( snips[pkt].fLength > 0x7FFFFFFF ) XMP_Throw ( "PostScript_MetaHandler::FindLastPacket: Oversize packet", kXMPErr_BadXMP );
+ packetInfo.offset = snips[pkt].fOffset;
+ packetInfo.length = (XMP_Int32)snips[pkt].fLength;
+ packetInfo.charForm = snips[pkt].fCharForm;
+ packetInfo.writeable = (snips[pkt].fAccess == 'w');
+ return true;
+ }
+
+ return false;
+
+} // PostScript_MetaHandler::FindLastPacket
+
+#else
+
+bool PostScript_MetaHandler::FindLastPacket()
+{
+ int err, snipCount;
+ bool found = false;
+ XMP_Int64 backPos, backLen;
+ size_t ioCount;
+
+ XMP_IO* fileRef = this->parent->fileRef;
+ XMP_Int64 fileLen = fileRef->Length();
+ XMP_PacketInfo & packetInfo = this->packetInfo;
+
+ XMPScanner scanner ( fileLen );
+ XMPScanner::SnipInfoVector snips;
+
+ enum { kBufferSize = 64*1024 };
+ XMP_Uns8 buffer [kBufferSize];
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ backPos = fileLen;
+ backLen = 0;
+
+ while ( true ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "PostScript_MetaHandler::FindLastPacket - User abort", kXMPErr_UserAbort );
+ }
+
+ backLen = kBufferSize;
+ if ( backPos < kBufferSize ) backLen = backPos;
+ if ( backLen == 0 ) return false; // Must be at BoF, no packets found.
+
+ backPos -= backLen;
+ fileRef->Seek ( backPos, kXMP_SeekFromStart ); // Seek back to the start of the next buffer.
+
+ #error "ioCount is 32 bits, backLen is 64"
+ ioCount = fileRef->Read ( buffer, backLen );
+ if ( ioCount != backLen ) XMP_Throw ( "PostScript_MetaHandler::FindLastPacket: Read failure", kXMPErr_ExternalFailure );
+
+ scanner.Scan ( buffer, backPos, backLen );
+ snipCount = scanner.GetSnipCount();
+ scanner.Report ( snips );
+
+ for ( int i = snipCount-1; i >= 0; --i ) {
+
+ if ( snips[i].fState == XMPScanner::eValidPacketSnip ) {
+
+ return VerifyMainPacket ( fileRef, snips[i].fOffset, snips[i].fLength, format, beLenient, mainInfo );
+
+ } else if ( snips[i].fState == XMPScanner::ePartialPacketSnip ) {
+
+ // This part is a tad tricky. We have a partial packet, so we need to scan
+ // forward from its ending to see if it is a valid packet. Snips won't recombine,
+ // the partial snip will change state. Be careful with the I/O to not clobber the
+ // backward scan positions, so that it can be resumed if necessary.
+
+ size_t fwdPos = snips[i].fOffset + snips[i].fLength;
+ fileRef->Seek ( fwdPos, kXMP_SeekFromStart ); // Seek to the end of the partial snip.
+
+ while ( (fwdPos < fileLen) && (snips[i].fState == XMPScanner::ePartialPacketSnip) ) {
+ ioCount = fileRef->Read ( buffer, kBufferSize );
+ if ( ioCount == 0 ) XMP_Throw ( "PostScript_MetaHandler::FindLastPacket: Read failure", kXMPErr_ExternalFailure );
+ scanner.Scan ( buffer, fwdPos, ioCount );
+ scanner.Report ( snips );
+ fwdPos += ioCount;
+ }
+
+ if ( snips[i].fState == XMPScanner::eValidPacketSnip ) {
+ if ( snips[i].fLength > 0x7FFFFFFF ) XMP_Throw ( "PostScript_MetaHandler::FindLastPacket: Oversize packet", kXMPErr_BadXMP );
+ packetInfo.offset = snips[i].fOffset;
+ packetInfo.length = (XMP_Int32)snips[i].fLength;
+ packetInfo.charForm = snips[i].fCharForm;
+ packetInfo.writeable = (snips[i].fAccess == 'w');
+ return true;
+ }
+
+ }
+
+ } // Backwards snip loop.
+
+ } // Backwards read loop.
+
+ return false; // Should never get here.
+
+} // PostScript_MetaHandler::FindLastPacket
+
+#endif
+
+// =================================================================================================
+// PostScript_MetaHandler::CacheFileData
+// =====================================
+
+void PostScript_MetaHandler::CacheFileData()
+{
+ this->containsXMP = false;
+ this->psHint = FindPostScriptHint();
+
+ if ( this->psHint == kPSHint_MainFirst ) {
+ this->containsXMP = FindFirstPacket();
+ } else if ( this->psHint == kPSHint_MainLast ) {
+ this->containsXMP = FindLastPacket();
+ }
+
+ if ( this->containsXMP ) ReadXMPPacket ( this );
+
+} // PostScript_MetaHandler::CacheFileData
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/PostScript_Handler.hpp b/XMPFiles/source/FileHandlers/PostScript_Handler.hpp
new file mode 100644
index 0000000..8813336
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/PostScript_Handler.hpp
@@ -0,0 +1,68 @@
+#ifndef __PostScript_Handler_hpp__
+#define __PostScript_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/FileHandlers/Trivial_Handler.hpp"
+
+// =================================================================================================
+/// \file PostScript_Handler.hpp
+/// \brief File format handler for PostScript and EPS files.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// *** This probably could be derived from Basic_Handler, buffer the file tail in a temp file.
+
+extern XMPFileHandler * PostScript_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool PostScript_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO * fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kPostScript_HandlerFlags = kTrivial_HandlerFlags;
+
+enum {
+ kPSHint_NoMarker = 0,
+ kPSHint_NoMain = 1,
+ kPSHint_MainFirst = 2,
+ kPSHint_MainLast = 3
+};
+
+class PostScript_MetaHandler : public Trivial_MetaHandler
+{
+public:
+
+ PostScript_MetaHandler ( XMPFiles * parent );
+ ~PostScript_MetaHandler();
+
+ void CacheFileData();
+
+ int psHint;
+
+protected:
+
+ int FindPostScriptHint();
+
+ bool FindFirstPacket();
+ bool FindLastPacket();
+
+}; // PostScript_MetaHandler
+
+// =================================================================================================
+
+#endif /* __PostScript_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/RIFF_Handler.cpp b/XMPFiles/source/FileHandlers/RIFF_Handler.cpp
new file mode 100644
index 0000000..7d6fdb3
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/RIFF_Handler.cpp
@@ -0,0 +1,356 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/RIFF.hpp"
+#include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp"
+#include "source/XIO.hpp"
+
+using namespace std;
+
+// =================================================================================================
+/// \file RIFF_Handler.cpp
+/// \brief File format handler for RIFF.
+// =================================================================================================
+
+// =================================================================================================
+// RIFF_MetaHandlerCTor
+// ====================
+
+XMPFileHandler * RIFF_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new RIFF_MetaHandler ( parent );
+}
+
+// =================================================================================================
+// RIFF_CheckFormat
+// ===============
+//
+// An RIFF file must begin with "RIFF", a 4 byte length, then the chunkType (AVI or WAV)
+// The specified length MUST (in practice: SHOULD) match fileSize-8, but we don't bother checking this here.
+
+bool RIFF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* file,
+ XMPFiles* parent )
+{
+ IgnoreParam(format); IgnoreParam(parent);
+ XMP_Assert ( (format == kXMP_AVIFile) || (format == kXMP_WAVFile) );
+
+ if ( file->Length() < 12 ) return false;
+ file ->Rewind();
+
+ XMP_Uns8 chunkID[12];
+ file->ReadAll ( chunkID, 12 );
+ if ( ! CheckBytes( &chunkID[0], "RIFF", 4 )) return false;
+
+ if ( CheckBytes(&chunkID[8],"AVI ",4) && format == kXMP_AVIFile ) return true;
+ if ( CheckBytes(&chunkID[8],"WAVE",4) && format == kXMP_WAVFile ) return true;
+
+ return false;
+
+} // RIFF_CheckFormat
+
+// =================================================================================================
+// RIFF_MetaHandler::RIFF_MetaHandler
+// ================================
+
+RIFF_MetaHandler::RIFF_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kRIFF_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ this->oldFileSize = this->newFileSize = this->trailingGarbageSize = 0;
+ this->level = 0;
+ this->listInfoChunk = this->listTdatChunk = 0;
+ this->dispChunk = this->bextChunk = this->cr8rChunk = this->prmlChunk = 0;
+ this->xmpChunk = 0;
+ this->lastChunk = 0;
+ this->hasListInfoINAM = false;
+}
+
+// =================================================================================================
+// RIFF_MetaHandler::~RIFF_MetaHandler
+// =================================
+
+RIFF_MetaHandler::~RIFF_MetaHandler()
+{
+ while ( ! this->riffChunks.empty() )
+ {
+ delete this->riffChunks.back();
+ this->riffChunks.pop_back();
+ }
+}
+
+// =================================================================================================
+// RIFF_MetaHandler::CacheFileData
+// ==============================
+
+void RIFF_MetaHandler::CacheFileData()
+{
+ this->containsXMP = false; //assume for now
+
+ XMP_IO* file = this->parent->ioRef;
+ this->oldFileSize = file ->Length();
+ if ( (this->parent->format == kXMP_WAVFile) && (this->oldFileSize > 0xFFFFFFFF) )
+ XMP_Throw ( "RIFF_MetaHandler::CacheFileData: WAV Files larger 4GB not supported", kXMPErr_Unimplemented );
+
+ file ->Rewind();
+ this->level = 0;
+
+ // parse top-level chunks (most likely just one, except large avi files)
+ XMP_Int64 filePos = 0;
+ while ( filePos < this->oldFileSize )
+ {
+
+ this->riffChunks.push_back( (RIFF::ContainerChunk*) RIFF::getChunk( NULL, this ) );
+
+ // Tolerate limited forms of trailing garbage in a file. Some apps append private data.
+
+ filePos = file->Offset();
+ XMP_Int64 fileTail = this->oldFileSize - filePos;
+
+ if ( fileTail != 0 ) {
+
+ if ( fileTail < 12 ) {
+
+ this->oldFileSize = filePos; // Pretend the file is smaller.
+ this->trailingGarbageSize = fileTail;
+
+ } else if ( this->parent->format == kXMP_WAVFile ) {
+
+ if ( fileTail < 1024*1024 ) {
+ this->oldFileSize = filePos; // Pretend the file is smaller.
+ this->trailingGarbageSize = fileTail;
+ } else {
+ XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat )
+ }
+
+ } else {
+
+ XMP_Int32 chunkInfo [3];
+ file->ReadAll ( &chunkInfo, 12 );
+ file->Seek ( -12, kXMP_SeekFromCurrent );
+ if ( (GetUns32LE ( &chunkInfo[0] ) != RIFF::kChunk_RIFF) || (GetUns32LE ( &chunkInfo[2] ) != RIFF::kType_AVIX) ) {
+ if ( fileTail < 1024*1024 ) {
+ this->oldFileSize = filePos; // Pretend the file is smaller.
+ this->trailingGarbageSize = fileTail;
+ } else {
+ XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat )
+ }
+ }
+
+ }
+
+ }
+
+ }
+
+ // covered before => internal error if it occurs
+ XMP_Validate( file->Offset() == this->oldFileSize,
+ "RIFF_MetaHandler::CacheFileData: unknown data at end of file",
+ kXMPErr_InternalFailure );
+
+} // RIFF_MetaHandler::CacheFileData
+
+// =================================================================================================
+// RIFF_MetaHandler::ProcessXMP
+// ============================
+
+void RIFF_MetaHandler::ProcessXMP()
+{
+ SXMPUtils::RemoveProperties ( &this->xmpObj, 0, 0, kXMPUtil_DoAllProperties );
+ // process physical packet first
+ if ( this->containsXMP ) this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ // then import native properties:
+ RIFF::importProperties( this );
+ this->processedXMP = true;
+}
+
+// =================================================================================================
+// RIFF_MetaHandler::UpdateFile
+// ===========================
+
+void RIFF_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ XMP_Validate( this->needsUpdate, "nothing to update", kXMPErr_InternalFailure );
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ //////////// PASS 1: basics, exports, packet reserialze
+ XMP_IO* file = this->parent->ioRef;
+ RIFF::containerVect *rc = &this->riffChunks;
+
+ //temptemp
+ //printf( "BEFORE:\n%s\n", rc->at(0)->toString().c_str() );
+
+ XMP_Enforce( rc->size() >= 1);
+ RIFF::ContainerChunk* mainChunk = rc->at(0);
+ this->lastChunk = rc->at( rc->size() - 1 ); // (may or may not coincide with mainChunk: )
+ XMP_Enforce( mainChunk != NULL );
+
+ RIFF::relocateWronglyPlacedXMPChunk( this );
+ // [2435625] lifted disablement for AVI
+ RIFF::exportAndRemoveProperties( this );
+
+ // always rewrite both LISTs (implicit size changes, e.g. through 0-term corrections may
+ // have very well led to size changes...)
+ // set XMP packet info, re-serialize
+ this->packetInfo.charForm = stdCharForm;
+ this->packetInfo.writeable = true;
+ this->packetInfo.offset = kXMPFiles_UnknownOffset;
+ this->packetInfo.length = kXMPFiles_UnknownLength;
+
+ // re-serialization ( needed because of above exportAndRemoveProperties() )
+ try {
+ if ( this->xmpChunk == 0 ) // new chunk? pad with 2K
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_NoOptions , 2048 );
+ else // otherwise try to match former size
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_ExactPacketLength , (XMP_Uns32) this->xmpChunk->oldSize-8 );
+ } catch ( ... ) { // if that fails, be happy with whatever.
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_NoOptions );
+ }
+
+ if ( (this->xmpPacket.size() & 1) == 1 ) this->xmpPacket += ' '; // Force the XMP to have an even size.
+
+ // if missing, add xmp packet at end:
+ if( this->xmpChunk == 0 )
+ this->xmpChunk = new RIFF::XMPChunk( this->lastChunk );
+ // * parenting happens within this call.
+ // * size computation will happen in XMPChunk::changesAndSize()
+ // * actual data will be set in XMPChunk::write()
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // PASS 2: compute sizes, optimize container structure (no writing yet)
+ {
+ this->newFileSize = 0;
+
+ // note: going through forward (not vice versa) is an important condition,
+ // so that parking LIST:Tdat,Cr8r, PrmL to last chunk is doable
+ // when encountered en route
+ for ( XMP_Uns32 chunkNo = 0; chunkNo < rc->size(); chunkNo++ )
+ {
+ RIFF::Chunk* cur = rc->at(chunkNo);
+ cur->changesAndSize( this );
+ this->newFileSize += cur->newSize;
+ if ( this->newFileSize % 2 == 1 ) this->newFileSize++; // pad byte
+ }
+ this->newFileSize += this->trailingGarbageSize;
+ } // PASS2
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // PASS 2a: verify no chunk violates 2GB boundaries
+ switch( this->parent->format )
+ {
+ // NB: <4GB for ALL chunks asserted before in ContainerChunk::changesAndSize()
+
+ case kXMP_AVIFile:
+ // ensure no chunk (single or multi, no matter) crosses 2 GB ...
+ for ( XMP_Int32 chunkNo = 0; chunkNo < (XMP_Int32)rc->size(); chunkNo++ )
+ {
+ if ( rc->at(chunkNo)->oldSize <= 0x80000000LL ) // ... if <2GB before
+ XMP_Validate( rc->at(chunkNo)->newSize <= 0x80000000LL,
+ "Chunk grew beyond 2 GB", kXMPErr_Unimplemented );
+ }
+
+ // compatibility: if single-chunk AND below <1GB, ensure <1GB
+ if ( ( rc->size() > 1 ) && ( rc->at(0)->oldSize < 0x40000000 ) )
+ {
+ XMP_Validate( rc->at(0)->newSize < 0x40000000LL, "compatibility: mainChunk must remain < 1GB" , kXMPErr_Unimplemented );
+ }
+
+ // [2473381] compatibility: if single-chunk AND >2GB,<4GB, ensure <4GB
+ if ( ( rc->size() > 1 ) &&
+ ( rc->at(0)->oldSize > 0x80000000LL ) && // 2GB
+ ( rc->at(0)->oldSize < 0x100000000LL ) ) // 4GB
+ {
+ XMP_Validate( rc->at(0)->newSize < 0x100000000LL, "compatibility: mainChunk must remain < 4GB" , kXMPErr_Unimplemented );
+ }
+
+ break;
+
+ case kXMP_WAVFile:
+ XMP_Validate( 1 == rc->size(), "WAV must be single-chunk", kXMPErr_InternalFailure );
+ XMP_Validate( rc->at(0)->newSize <= 0xFFFFFFFFLL, "WAV above 4 GB not supported", kXMPErr_Unimplemented );
+ break;
+
+ default:
+ XMP_Throw( "unknown format", kXMPErr_InternalFailure );
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // PASS 3: write avix chunk(s) if applicable (shrinks or stays)
+ // and main chunk. -- operation order depends on mainHasShrunk.
+ {
+ // if needed, extend file beforehand
+ if ( this->newFileSize > this->oldFileSize ) {
+ file->Seek ( newFileSize, kXMP_SeekFromStart );
+ file->Rewind();
+ }
+
+ RIFF::Chunk* mainChunk = rc->at(0);
+
+ XMP_Int64 mainGrowth = mainChunk->newSize - mainChunk->oldSize;
+ XMP_Enforce( mainGrowth >= 0 ); // main always stays or grows
+
+ //temptemp
+ //printf( "AFTER:\n%s\n", rc->at(0)->toString().c_str() );
+
+ if ( rc->size() > 1 ) // [2457482]
+ XMP_Validate( mainGrowth == 0, "mainChunk must not grow, if multiple RIFF chunks", kXMPErr_InternalFailure );
+
+ // back to front:
+
+ XMP_Int64 avixStart = newFileSize; // count from the back
+
+ if ( this->trailingGarbageSize != 0 ) {
+ XMP_Int64 goodDataEnd = this->newFileSize - this->trailingGarbageSize;
+ XIO::Move ( file, this->oldFileSize, file, goodDataEnd, this->trailingGarbageSize );
+ avixStart = goodDataEnd;
+ }
+
+ for ( XMP_Int32 chunkNo = ((XMP_Int32)rc->size()) -1; chunkNo >= 0; chunkNo-- )
+ {
+ RIFF::Chunk* cur = rc->at(chunkNo);
+
+ avixStart -= cur->newSize;
+ if ( avixStart % 2 == 1 ) // rewind one more
+ avixStart -= 1;
+
+ file->Seek ( avixStart , kXMP_SeekFromStart );
+
+ if ( cur->hasChange ) // need explicit write-out ?
+ cur->write( this, file, chunkNo == 0 );
+ else // or will a simple move do?
+ {
+ XMP_Enforce( cur->oldSize == cur->newSize );
+ if ( cur->oldPos != avixStart ) // important optimization: only move if there's a need to
+ XIO::Move( file, cur->oldPos, file, avixStart, cur->newSize );
+ }
+ }
+
+ // if needed, shrink file afterwards
+ if ( this->newFileSize < this->oldFileSize ) file->Truncate ( this->newFileSize );
+ } // PASS 3
+
+ this->needsUpdate = false; //do last for safety
+} // RIFF_MetaHandler::UpdateFile
+
+// =================================================================================================
+// RIFF_MetaHandler::WriteTempFile
+// ===============================
+
+void RIFF_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ IgnoreParam( tempRef );
+ XMP_Throw ( "RIFF_MetaHandler::WriteTempFile: Not supported (must go through UpdateFile", kXMPErr_Unavailable );
+}
+
diff --git a/XMPFiles/source/FileHandlers/RIFF_Handler.hpp b/XMPFiles/source/FileHandlers/RIFF_Handler.hpp
new file mode 100644
index 0000000..d8c83a2
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/RIFF_Handler.hpp
@@ -0,0 +1,73 @@
+// =================================================================================================
+// 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.
+// =================================================================================================
+#ifndef __RIFF_Handler_hpp__
+#define __RIFF_Handler_hpp__ 1
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/RIFF_Support.hpp"
+#include "source/XIO.hpp"
+
+// =================================================================================================
+/// \file RIFF_Handler.hpp
+/// \brief File format handler for RIFF (AVI, WAV).
+// =================================================================================================
+
+extern XMPFileHandler * RIFF_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool RIFF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO * fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kRIFF_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_CanReconcile
+ );
+
+class RIFF_MetaHandler : public XMPFileHandler
+{
+public:
+ RIFF_MetaHandler ( XMPFiles* parent );
+ ~RIFF_MetaHandler();
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // instance vars
+ // most often just one RIFF:* (except for AVI,[AVIX] >1 GB)
+ std::vector<RIFF::ContainerChunk*> riffChunks;
+ XMP_Int64 oldFileSize, newFileSize, trailingGarbageSize;
+
+ // state variables, needed during parsing
+ XMP_Uns8 level;
+
+ RIFF::ContainerChunk *listInfoChunk, *listTdatChunk;
+ RIFF::ValueChunk* dispChunk;
+ RIFF::ValueChunk* bextChunk;
+ RIFF::ValueChunk* cr8rChunk;
+ RIFF::ValueChunk* prmlChunk;
+ RIFF::XMPChunk* xmpChunk;
+ RIFF::ContainerChunk* lastChunk;
+ bool hasListInfoINAM; // needs to be known for the special 3-way merge around dc:title
+
+}; // RIFF_MetaHandler
+
+// =================================================================================================
+
+#endif /* __RIFF_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/SWF_Handler.cpp b/XMPFiles/source/FileHandlers/SWF_Handler.cpp
new file mode 100644
index 0000000..a0554ce
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/SWF_Handler.cpp
@@ -0,0 +1,330 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/SWF_Handler.hpp"
+#include "XMPFiles/source/FormatSupport/SWF_Support.hpp"
+
+using namespace std;
+
+// =================================================================================================
+/// \file SWF_Handler.hpp
+/// \brief File format handler for SWF.
+///
+/// This handler ...
+///
+// =================================================================================================
+
+// =================================================================================================
+// SWF_MetaHandlerCTor
+// ===================
+
+XMPFileHandler * SWF_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new SWF_MetaHandler ( parent );
+
+} // SWF_MetaHandlerCTor
+
+// =================================================================================================
+// SWF_CheckFormat
+// ===============
+
+bool SWF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO * fileRef,
+ XMPFiles * parent )
+{
+ IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent);
+ XMP_Assert ( format == kXMP_SWFFile );
+
+ // Make sure the file is long enough for an empty SWF stream. Check the signature.
+
+ if ( fileRef->Length() < SWF_IO::HeaderPrefixSize ) return false;
+
+ fileRef->Rewind();
+ XMP_Uns8 buffer [4];
+ fileRef->ReadAll ( buffer, 4 );
+ XMP_Uns32 signature = GetUns32LE ( &buffer[0] ) & 0xFFFFFF; // Discard the version byte.
+
+ return ( (signature == SWF_IO::CompressedSignature) || (signature == SWF_IO::ExpandedSignature) );
+
+} // SWF_CheckFormat
+
+// =================================================================================================
+// SWF_MetaHandler::SWF_MetaHandler
+// ================================
+
+SWF_MetaHandler::SWF_MetaHandler ( XMPFiles * _parent )
+ : isCompressed(false), hasFileAttributes(false), hasMetadata(false), brokenSWF(false), expandedSize(0), firstTagOffset(0)
+{
+ this->parent = _parent;
+ this->handlerFlags = kSWF_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+}
+
+// =================================================================================================
+// SWF_MetaHandler::~SWF_MetaHandler
+// =================================
+
+SWF_MetaHandler::~SWF_MetaHandler()
+{
+ // Nothing to do at this time.
+}
+
+// =================================================================================================
+// SWF_MetaHandler::CacheFileData
+// ==============================
+//
+// SWF files are pretty small, have simple metadata, and often have ZIP compression. Because they
+// are small and often compressed, we always cache the fully expanded SWF in memory. That is used
+// for both reading and updating. Note that SWF_CheckFormat has already done basic checks on the
+// size and signature, they don't need to be repeated here.
+//
+// Try to find the FileAttributes and Metadata tags, saving their offsets for later use if updating
+// the file. We need to be tolerant when reading, allowing the FileAttributes tag to be anywhere and
+// allowing Metadata without FileAttributes or with the HasMetadata flag clear. The SWF spec is not
+// clear enough about the rules for SWF 7 and earlier, there are 3rd party tools that don't put
+// FileAttributes first.
+
+void SWF_MetaHandler::CacheFileData() {
+
+ XMP_Assert ( (! this->processedXMP) && (! this->containsXMP) );
+ XMP_Assert ( this->expandedSWF.empty() );
+
+ XMP_IO * fileRef = this->parent->ioRef;
+ XMP_Int64 fileLength = fileRef->Length();
+ XMP_Enforce ( fileLength <= SWF_IO::MaxExpandedSize );
+
+ // Get the uncompressed SWF stream into memory.
+
+ fileRef->Rewind();
+ XMP_Uns8 buffer [SWF_IO::HeaderPrefixSize]; // Read the uncompressed file header prefix.
+ fileRef->ReadAll ( buffer, SWF_IO::HeaderPrefixSize );
+
+ XMP_Uns32 signature = GetUns32LE ( &buffer[0] ) & 0xFFFFFF; // Discard the version byte.
+ this->expandedSize = GetUns32LE ( &buffer[4] );
+ if ( signature == SWF_IO::CompressedSignature ) this->isCompressed = true;
+
+ if ( this->isCompressed ) {
+
+ // Expand the SWF file into memory.
+ this->expandedSWF.reserve ( this->expandedSize ); // Try to avoid reallocations.
+ SWF_IO::DecompressFileToMemory ( fileRef, &this->expandedSWF );
+ this->expandedSize = this->expandedSWF.size(); // Use the true length.
+
+ } else {
+
+ // Read the entire uncompressed file into memory.
+ this->expandedSize = (XMP_Uns32)fileLength; // Use the true length.
+ this->expandedSWF.insert ( this->expandedSWF.end(), (size_t)fileLength, 0 );
+ fileRef->Rewind();
+ fileRef->ReadAll ( &this->expandedSWF[0], (XMP_Uns32)fileLength );
+
+ }
+
+ // Look for the FileAttributes and Metadata tags.
+
+ this->firstTagOffset = SWF_IO::FileHeaderSize ( this->expandedSWF[SWF_IO::HeaderPrefixSize] );
+
+ XMP_Uns32 currOffset = this->firstTagOffset;
+ SWF_IO::TagInfo currTag;
+
+ for ( ; currOffset < this->expandedSize; currOffset = SWF_IO::NextTagOffset(currTag) ) {
+
+ bool ok = SWF_IO::GetTagInfo ( this->expandedSWF, currOffset, &currTag );
+ if ( ! ok ) {
+ this->brokenSWF = true; // Let the read finish, but refuse to update.
+ break;
+ }
+
+ if ( currTag.tagID == SWF_IO::FileAttributesTagID ) {
+ this->fileAttributesTag = currTag;
+ this->hasFileAttributes = true;
+ if ( this->hasMetadata ) break; // Exit if we have both.
+ }
+
+ if ( currTag.tagID == SWF_IO::MetadataTagID ) {
+ this->metadataTag = currTag;
+ this->hasMetadata = true;
+ if ( this->hasFileAttributes ) break; // Exit if we have both.
+ }
+
+ }
+
+ if ( this->hasMetadata ) {
+ this->packetInfo.offset = SWF_IO::ContentOffset ( this->metadataTag );
+ this->packetInfo.length = this->metadataTag.contentLength;
+ this->xmpPacket.assign ( (char*)&this->expandedSWF[(size_t)this->packetInfo.offset], (size_t)this->packetInfo.length );
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+ this->containsXMP = true;
+ }
+
+} // SWF_MetaHandler::CacheFileData
+
+// =================================================================================================
+// SWF_MetaHandler::ProcessXMP
+// ===========================
+
+void SWF_MetaHandler::ProcessXMP()
+{
+
+ this->processedXMP = true; // Make sure we only come through here once.
+
+ if ( ! this->xmpPacket.empty() ) {
+ XMP_Assert ( this->containsXMP );
+ XMP_StringPtr packetStr = this->xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
+ this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
+ }
+
+} // SWF_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// XMPFileHandler::GetSerializeOptions
+// ===================================
+//
+// Override default implementation to ensure omitting XMP wrapper.
+
+XMP_OptionBits SWF_MetaHandler::GetSerializeOptions()
+{
+
+ return (kXMP_OmitPacketWrapper | kXMP_OmitAllFormatting | kXMP_OmitXMPMetaElement);
+
+} // XMPFileHandler::GetSerializeOptions
+
+// =================================================================================================
+// SWF_MetaHandler::UpdateFile
+// ===========================
+//
+// Update the expanded SWF in memory, then write it to the file.
+
+void SWF_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+
+ if ( doSafeUpdate ) XMP_Throw ( "SWF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
+
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Don't come through here twice, even if there are errors.
+
+ if ( this->brokenSWF ) {
+ XMP_Throw ( "SWF is broken, can't update.", kXMPErr_BadFileFormat );
+ }
+
+ // Make sure there is a FileAttributes tag at the front, with the HasMetadata flag set.
+
+ if ( ! this->hasFileAttributes ) {
+
+ // Insert a new FileAttributes tag as the first tag.
+
+ XMP_Uns8 buffer [6]; // Two byte header plus four byte content.
+ PutUns16LE ( ((SWF_IO::FileAttributesTagID << 6) | 4), &buffer[0] );
+ PutUns32LE ( SWF_IO::HasMetadataMask, &buffer[2] );
+
+ this->expandedSWF.insert ( (this->expandedSWF.begin() + this->firstTagOffset), 6, 0 );
+ memcpy ( &this->expandedSWF[this->firstTagOffset], &buffer[0], 6 );
+
+ this->hasFileAttributes = true;
+ bool ok = SWF_IO::GetTagInfo ( this->expandedSWF, this->firstTagOffset, &this->fileAttributesTag );
+ XMP_Assert ( ok );
+
+ if ( this->hasMetadata ) this->metadataTag.tagOffset += 6; // The Metadata tag is now further back.
+
+ } else {
+
+ // Make sure the HasMetadata flag is set.
+ if ( this->fileAttributesTag.contentLength > 0 ) {
+ XMP_Uns32 flagsOffset = SWF_IO::ContentOffset ( this->fileAttributesTag );
+ this->expandedSWF[flagsOffset] |= SWF_IO::HasMetadataMask;
+ }
+
+ // Make sure the FileAttributes tag is the first tag.
+ if ( this->fileAttributesTag.tagOffset != this->firstTagOffset ) {
+
+ RawDataBlock attrTag;
+ XMP_Uns32 attrTagLength = SWF_IO::FullTagLength ( this->fileAttributesTag );
+ attrTag.assign ( attrTagLength, 0 );
+ memcpy ( &attrTag[0], &this->expandedSWF[this->fileAttributesTag.tagOffset], attrTagLength );
+
+ RawDataBlock::iterator attrTagPos = this->expandedSWF.begin() + this->fileAttributesTag.tagOffset;
+ RawDataBlock::iterator attrTagEnd = attrTagPos + attrTagLength;
+ this->expandedSWF.erase ( attrTagPos, attrTagEnd ); // Remove the old FileAttributes tag;
+
+ if ( this->hasMetadata && (this->metadataTag.tagOffset < this->fileAttributesTag.tagOffset) ) {
+ this->metadataTag.tagOffset += attrTagLength; // The FileAttributes tag will become in front.
+ }
+
+ this->expandedSWF.insert ( (this->expandedSWF.begin() + this->firstTagOffset), attrTagLength, 0 );
+ memcpy ( &this->expandedSWF[this->firstTagOffset], &attrTag[0], attrTagLength );
+
+ this->fileAttributesTag.tagOffset = this->firstTagOffset;
+
+ }
+
+ }
+
+ // Make sure the XMP is as small as possible. Write the XMP as the second tag.
+
+ XMP_Assert ( this->hasFileAttributes );
+
+ XMP_OptionBits smallOptions = kXMP_OmitPacketWrapper | kXMP_UseCompactFormat | kXMP_OmitAllFormatting | kXMP_OmitXMPMetaElement;
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, smallOptions );
+
+ if ( this->hasMetadata ) {
+ // Remove the old XMP, the size and location have probably changed.
+ XMP_Uns32 oldMetaLength = SWF_IO::FullTagLength ( this->metadataTag );
+ RawDataBlock::iterator oldMetaPos = this->expandedSWF.begin() + this->metadataTag.tagOffset;
+ RawDataBlock::iterator oldMetaEnd = oldMetaPos + oldMetaLength;
+ this->expandedSWF.erase ( oldMetaPos, oldMetaEnd );
+ }
+
+ this->metadataTag.hasLongHeader = true;
+ this->metadataTag.tagID = SWF_IO::MetadataTagID;
+ this->metadataTag.tagOffset = SWF_IO::NextTagOffset ( this->fileAttributesTag );
+ this->metadataTag.contentLength = this->xmpPacket.size();
+
+ XMP_Uns32 newMetaLength = 6 + this->metadataTag.contentLength; // Always use a long tag header.
+ this->expandedSWF.insert ( (this->expandedSWF.begin() + this->metadataTag.tagOffset), newMetaLength, 0 );
+
+ PutUns16LE ( ((SWF_IO::MetadataTagID << 6) | SWF_IO::TagLengthMask), &this->expandedSWF[this->metadataTag.tagOffset] );
+ PutUns32LE ( this->metadataTag.contentLength, &this->expandedSWF[this->metadataTag.tagOffset+2] );
+ memcpy ( &this->expandedSWF[this->metadataTag.tagOffset+6], this->xmpPacket.c_str(), this->metadataTag.contentLength );
+
+ this->hasMetadata = true;
+
+ // Rewrite the file.
+
+ XMP_IO * fileRef = this->parent->ioRef;
+ fileRef->Rewind();
+ fileRef->Truncate ( 0 );
+ fileRef->Write ( &this->expandedSWF[0], this->expandedSWF.size() );
+
+} // SWF_MetaHandler::UpdateFile
+
+// =================================================================================================
+// SWF_MetaHandler::WriteTempFile
+// ==============================
+
+// ! See important notes in SWF_Handler.hpp about file handling.
+
+void SWF_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+
+ // ! WriteTempFile is not supposed to be called for SWF.
+ XMP_Throw ( "SWF_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure );
+
+} // SWF_MetaHandler::WriteTempFile
diff --git a/XMPFiles/source/FileHandlers/SWF_Handler.hpp b/XMPFiles/source/FileHandlers/SWF_Handler.hpp
new file mode 100644
index 0000000..f2ca7cb
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/SWF_Handler.hpp
@@ -0,0 +1,72 @@
+#ifndef __SWF_Handler_hpp__
+#define __SWF_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "XMPFiles/source/FormatSupport/SWF_Support.hpp"
+
+// =================================================================================================
+/// \file SWF_Handler.hpp
+/// \brief File format handler for SWF.
+///
+/// This header ...
+///
+// =================================================================================================
+
+extern XMPFileHandler* SWF_MetaHandlerCTor ( XMPFiles* parent );
+
+extern bool SWF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO * fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kSWF_HandlerFlags = ( kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket );
+
+class SWF_MetaHandler : public XMPFileHandler {
+
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ XMP_OptionBits GetSerializeOptions();
+
+ SWF_MetaHandler ( XMPFiles* parent );
+ virtual ~SWF_MetaHandler();
+
+private:
+
+ SWF_MetaHandler() : isCompressed(false), hasFileAttributes(false), hasMetadata(false), brokenSWF(false),
+ expandedSize(0), firstTagOffset(0) {};
+
+ bool isCompressed, hasFileAttributes, hasMetadata, brokenSWF;
+ XMP_Uns32 expandedSize, firstTagOffset;
+ RawDataBlock expandedSWF;
+
+ SWF_IO::TagInfo fileAttributesTag, metadataTag;
+
+}; // SWF_MetaHandler
+
+// =================================================================================================
+
+#endif /* __SWF_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/Scanner_Handler.cpp b/XMPFiles/source/FileHandlers/Scanner_Handler.cpp
new file mode 100644
index 0000000..2d6308d
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/Scanner_Handler.cpp
@@ -0,0 +1,347 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FormatSupport/XMPScanner.hpp"
+#include "XMPFiles/source/FileHandlers/Scanner_Handler.hpp"
+
+#include <vector>
+
+using namespace std;
+
+#if EnablePacketScanning
+
+// =================================================================================================
+/// \file Scanner_Handler.cpp
+/// \brief File format handler for packet scanning.
+///
+/// This header ...
+///
+// =================================================================================================
+
+struct CandidateInfo {
+ XMP_PacketInfo packetInfo;
+ std::string xmpPacket;
+ SXMPMeta * xmpObj;
+};
+
+// =================================================================================================
+// Scanner_MetaHandlerCTor
+// =======================
+
+XMPFileHandler * Scanner_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new Scanner_MetaHandler ( parent );
+
+} // Scanner_MetaHandlerCTor
+
+// =================================================================================================
+// Scanner_MetaHandler::Scanner_MetaHandler
+// ========================================
+
+Scanner_MetaHandler::Scanner_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kScanner_HandlerFlags;
+
+} // Scanner_MetaHandler::Scanner_MetaHandler
+
+// =================================================================================================
+// Scanner_MetaHandler::~Scanner_MetaHandler
+// =========================================
+
+Scanner_MetaHandler::~Scanner_MetaHandler()
+{
+ // ! Inherit the base cleanup.
+
+} // Scanner_MetaHandler::~Scanner_MetaHandler
+
+// =================================================================================================
+// PickMainPacket
+// ==============
+//
+// Pick the main packet from the vector of candidates. The rules:
+// 1. Use the manifest find containment. Prune contained packets.
+// 2. Use the metadata date to pick the most recent.
+// 3. if lenient, pick the last writeable packet, or the last if all are read only.
+
+static int
+PickMainPacket ( std::vector<CandidateInfo>& candidates, bool beLenient )
+{
+ int pkt; // ! Must be signed.
+ int main = -1; // Assume the worst.
+ XMP_OptionBits options;
+
+ int metaCount = (int)candidates.size();
+ if ( metaCount == 0 ) return -1;
+ if ( metaCount == 1 ) return 0;
+
+ // ---------------------------------------------------------------------------------------------
+ // 1. Look at each packet to see if it has a manifest. If it does, prune all of the others that
+ // this one says it contains. Hopefully we'll end up with just one packet. Note that we have to
+ // mark all the children first, then prune. Pruning on the fly means that we won't do a proper
+ // tree discovery if we prune a parent before a child. This would happen if we happened to visit
+ // a grandparent first.
+
+ int child;
+
+ std::vector<bool> pruned ( metaCount, false );
+
+ for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) {
+
+ // First see if this candidate has a manifest.
+
+ try {
+ std::string voidValue;
+ bool found = candidates[pkt].xmpObj->GetProperty ( kXMP_NS_XMP_MM, "Manifest", &voidValue, &options );
+ if ( (! found) || (! XMP_PropIsArray ( options )) ) continue; // No manifest, or not an array.
+ } catch ( ... ) {
+ continue; // No manifest.
+ };
+
+ // Mark all other candidates that are referred to in this manifest.
+
+ for ( child = 0; child < (int)candidates.size(); ++child ) {
+ if ( pruned[child] || (child == pkt) ) continue; // Skip already pruned ones and self.
+ }
+
+ }
+
+ // Go ahead and actually remove the marked packets.
+
+ for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) {
+ if ( pruned[pkt] ) {
+ delete candidates[pkt].xmpObj;
+ candidates[pkt].xmpObj = 0;
+ metaCount -= 1;
+ }
+ }
+
+ // We're done if the containment pruning left us with 0 or 1 candidate.
+
+ if ( metaCount == 0 ) {
+ XMP_Throw ( "GetMainPacket/PickMainPacket: Recursive containment", kXMPErr_BadXMP );
+ } else if ( metaCount == 1 ) {
+ for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) {
+ if ( candidates[pkt].xmpObj != 0 ) {
+ main = pkt;
+ break;
+ }
+ }
+ }
+
+ if ( main != -1 ) return main; // We found the main.
+
+ // -------------------------------------------------------------------------------------------
+ // 2. Pick the packet with the most recent metadata date. If we are being lenient then missing
+ // dates are older than any real date, and equal dates pick the last packet. If we are being
+ // strict then any missing or equal dates mean we can't pick.
+
+ XMP_DateTime latestTime, currTime;
+
+ for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) {
+
+ if ( candidates[pkt].xmpObj == 0 ) continue; // This was pruned in the manifest stage.
+
+ bool haveDate = candidates[pkt].xmpObj->GetProperty_Date ( kXMP_NS_XMP, "MetadataDate", &currTime, &options );
+
+ if ( ! haveDate ) {
+
+ if ( ! beLenient ) return -1;
+ if ( main == -1 ) {
+ main = pkt;
+ memset ( &latestTime, 0, sizeof(latestTime) );
+ }
+
+ } else if ( main == -1 ) {
+
+ main = pkt;
+ latestTime = currTime;
+
+ } else {
+
+ int timeOp = SXMPUtils::CompareDateTime ( currTime, latestTime );
+
+ if ( timeOp > 0 ) {
+ main = pkt;
+ latestTime = currTime;
+ } else if ( timeOp == 0 ) {
+ if ( ! beLenient ) return -1;
+ main = pkt;
+ latestTime = currTime;
+ }
+
+ }
+
+ }
+
+ if ( main != -1 ) return main; // We found the main.
+
+ // --------------------------------------------------------------------------------------------
+ // 3. If we're being lenient, pick the last writeable packet, or the last if all are read only.
+
+ if ( beLenient ) {
+
+ for ( pkt = (int)candidates.size()-1; pkt >= 0; --pkt ) {
+ if ( candidates[pkt].xmpObj == 0 ) continue; // This was pruned in the manifest stage.
+ if ( candidates[pkt].packetInfo.writeable ) {
+ main = pkt;
+ break;
+ }
+ }
+
+ if ( main == -1 ) {
+ for ( pkt = (int)candidates.size()-1; pkt >= 0; --pkt ) {
+ if ( candidates[pkt].xmpObj != 0 ) {
+ main = pkt;
+ break;
+ }
+ }
+ }
+
+ }
+
+ return main;
+
+} // PickMainPacket
+
+// =================================================================================================
+// Scanner_MetaHandler::CacheFileData
+// ==================================
+
+void Scanner_MetaHandler::CacheFileData()
+{
+ XMP_IO* fileRef = this->parent->ioRef;
+ bool beLenient = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenStrictly );
+
+ int pkt;
+ XMP_Int64 bufPos;
+ size_t bufLen;
+ SXMPMeta * newMeta;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ std::vector<CandidateInfo> candidates; // ! These have SXMPMeta* fields, don't leak on exceptions.
+
+ this->containsXMP = false;
+
+ try {
+
+ // ------------------------------------------------------
+ // Scan the entire file to find all of the valid packets.
+
+ XMP_Int64 fileLen = fileRef->Length();
+ XMPScanner scanner ( fileLen );
+
+ enum { kBufferSize = 64*1024 };
+ XMP_Uns8 buffer [kBufferSize];
+
+ fileRef->Rewind();
+
+ for ( bufPos = 0; bufPos < fileLen; bufPos += bufLen ) {
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "Scanner_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort );
+ }
+ bufLen = fileRef->Read ( buffer, kBufferSize );
+ if ( bufLen == 0 ) XMP_Throw ( "Scanner_MetaHandler::LocateXMP: Read failure", kXMPErr_ExternalFailure );
+ scanner.Scan ( buffer, bufPos, bufLen );
+ }
+
+ // --------------------------------------------------------------
+ // Parse the valid packet snips, building a vector of candidates.
+
+ long snipCount = scanner.GetSnipCount();
+
+ XMPScanner::SnipInfoVector snips ( snipCount );
+ scanner.Report ( snips );
+
+ for ( pkt = 0; pkt < snipCount; ++pkt ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "Scanner_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort );
+ }
+
+ // Seek to the packet then try to parse it.
+
+ if ( snips[pkt].fState != XMPScanner::eValidPacketSnip ) continue;
+ fileRef->Seek ( snips[pkt].fOffset, kXMP_SeekFromStart );
+ newMeta = new SXMPMeta();
+ std::string xmpPacket;
+ xmpPacket.reserve ( (size_t)snips[pkt].fLength );
+
+ try {
+ for ( bufPos = 0; bufPos < snips[pkt].fLength; bufPos += bufLen ) {
+ bufLen = kBufferSize;
+ if ( (bufPos + bufLen) > (size_t)snips[pkt].fLength ) bufLen = size_t ( snips[pkt].fLength - bufPos );
+ (void) fileRef->ReadAll ( buffer, (XMP_Int32)bufLen );
+ xmpPacket.append ( (const char *)buffer, bufLen );
+ newMeta->ParseFromBuffer ( (char *)buffer, (XMP_StringLen)bufLen, kXMP_ParseMoreBuffers );
+ }
+ newMeta->ParseFromBuffer ( 0, 0, kXMP_NoOptions );
+ } catch ( ... ) {
+ delete newMeta;
+ if ( beLenient ) continue; // Skip if we're being lenient, else rethrow.
+ throw;
+ }
+
+ // It parsed OK, add it to the array of candidates.
+
+ candidates.push_back ( CandidateInfo() );
+ CandidateInfo & newInfo = candidates.back();
+ newInfo.xmpObj = newMeta;
+ newInfo.xmpPacket.swap ( xmpPacket );
+ newInfo.packetInfo.offset = snips[pkt].fOffset;
+ newInfo.packetInfo.length = (XMP_Int32)snips[pkt].fLength;
+ newInfo.packetInfo.charForm = snips[pkt].fCharForm;
+ newInfo.packetInfo.writeable = (snips[pkt].fAccess == 'w');
+
+ }
+
+ // ----------------------------------------
+ // Figure out which packet is the main one.
+
+ int main = PickMainPacket ( candidates, beLenient );
+
+ if ( main != -1 ) {
+ this->packetInfo = candidates[main].packetInfo;
+ this->xmpPacket.swap ( candidates[main].xmpPacket );
+ this->xmpObj = *candidates[main].xmpObj;
+ this->containsXMP = true;
+ this->processedXMP = true;
+ }
+
+ for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) {
+ if ( candidates[pkt].xmpObj != 0 ) delete candidates[pkt].xmpObj;
+ }
+
+ } catch ( ... ) {
+
+ // Clean up the SXMPMeta* fields from the vector of candidates.
+ for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) {
+ if ( candidates[pkt].xmpObj != 0 ) delete candidates[pkt].xmpObj;
+ }
+ throw;
+
+ }
+
+} // Scanner_MetaHandler::CacheFileData
+
+// =================================================================================================
+
+#endif // IncludePacketScanning
diff --git a/XMPFiles/source/FileHandlers/Scanner_Handler.hpp b/XMPFiles/source/FileHandlers/Scanner_Handler.hpp
new file mode 100644
index 0000000..53c4820
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/Scanner_Handler.hpp
@@ -0,0 +1,42 @@
+#ifndef __Scanner_Handler_hpp__
+#define __Scanner_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMPFiles/source/FileHandlers/Trivial_Handler.hpp"
+
+// =================================================================================================
+/// \file Scanner_Handler.hpp
+/// \brief File format handler for packet scanning.
+///
+/// This header ...
+///
+// =================================================================================================
+
+extern XMPFileHandler * Scanner_MetaHandlerCTor ( XMPFiles * parent );
+
+static const XMP_OptionBits kScanner_HandlerFlags = kTrivial_HandlerFlags;
+
+class Scanner_MetaHandler : public Trivial_MetaHandler
+{
+public:
+
+ Scanner_MetaHandler () {};
+ Scanner_MetaHandler ( XMPFiles * parent );
+
+ ~Scanner_MetaHandler();
+
+ void CacheFileData();
+
+}; // Scanner_MetaHandler
+
+// =================================================================================================
+
+#endif /* __Scanner_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp b/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp
new file mode 100644
index 0000000..863eab7
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp
@@ -0,0 +1,848 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp"
+
+#include "third-party/zuid/interfaces/MD5.h"
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+using namespace std;
+
+// =================================================================================================
+/// \file SonyHDV_Handler.cpp
+/// \brief Folder format handler for Sony HDV.
+///
+/// This handler is for the Sony HDV video format. This is a pseudo-package, visible files but with
+/// a very well-defined layout and naming rules.
+///
+/// A typical Sony HDV layout looks like:
+///
+/// .../MyMovie/
+/// VIDEO/
+/// HVR/
+/// 00_0001_2007-08-06_165555.IDX
+/// 00_0001_2007-08-06_165555.M2T
+/// 00_0001_2007-08-06_171740.M2T
+/// 00_0001_2007-08-06_171740.M2T.ese
+/// tracks.dat
+///
+/// The logical clip name can be "00_0001" or "00_0001_" plus anything. We'll find the .IDX file,
+/// which defines the existence of the clip. Full file names as input will pull out the camera/clip
+/// parts and match in the same way. The .XMP file will use the date/time suffix from the .IDX file.
+// =================================================================================================
+
+// =================================================================================================
+// SonyHDV_CheckFormat
+// ===================
+//
+// This version does fairly simple checks. The top level folder (.../MyMovie) must contain the
+// VIDEO/HVR subtree. The HVR folder must contain a .IDX file for the desired clip. The name checks
+// are case insensitive.
+//
+// The state of the string parameters depends on the form of the path passed by the client. If the
+// client passed a logical clip path, like ".../MyMovie/00_0001", the parameters are:
+// rootPath - ".../MyMovie"
+// gpName - empty
+// parentName - empty
+// leafName - "00_0001"
+//
+// If the client passed a full file path, like ".../MyMovie/VIDEO/HVR/00_0001_2007-08-06_165555.M2T",
+// they are:
+// rootPath - ".../MyMovie"
+// gpName - "VIDEO"
+// parentName - "HVR"
+// leafName - "00_0001_2007-08-06_165555.M2T"
+//
+// The logical clip name can be short like "00_0001", or long like "00_0001_2007-08-06_165555". We
+// only key off of the portion before a second underscore.
+
+// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has
+// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the
+// ! file exists for a full file path.
+
+bool SonyHDV_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent )
+{
+ // Do some basic checks on the root path and component names.
+
+ if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty.
+
+ std::string tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "VIDEO";
+
+ if ( gpName.empty() ) {
+ // This is the logical clip path case. Look for VIDEO/HVR subtree.
+ if ( Host_IO::GetChildMode ( tempPath.c_str(), "HVR" ) != Host_IO::kFMode_IsFolder ) return false;
+ } else {
+ // This is the existing file case. Check the parent and grandparent names.
+ if ( (gpName != "VIDEO") || (parentName != "HVR") ) return false;
+ }
+
+ // Look for the clip's .IDX file. If found use that as the full clip name.
+
+ tempPath += kDirChar;
+ tempPath += "HVR";
+
+ std::string clipName = leafName;
+
+#if 0
+
+ // Disabled until Sony HDV clip spanning is supported. Since segments of spanned clips are
+ // currently considered separate entities, information such as frame count needs to be
+ // considered on a per segment basis.
+
+ int usCount = 0;
+ size_t i, limit = leafName.size();
+ for ( i = 0; i < limit; ++i ) {
+ if ( clipName[i] == '_' ) {
+ ++usCount;
+ if ( usCount == 2 ) break;
+ }
+ }
+ if ( i < limit ) clipName.erase ( i );
+ clipName += '_'; // Make sure a final '_' is there for the search comparisons.
+
+ Host_IO::AutoFolder aFolder;
+ std::string childName;
+ bool found = false;
+
+ aFolder.folder = Host_IO::OpenFolder ( tempPath.c_str() );
+ while ( (! found) && Host_IO::GetNextChild ( aFolder.folder, &childName ) ) {
+ size_t childLen = childName.size();
+ if ( childLen < 4 ) continue;
+ MakeUpperCase ( &childName );
+ if ( childName.compare ( childLen-4, 4, ".IDX" ) != 0 ) continue;
+ if ( childName.compare ( 0, clipName.size(), clipName ) == 0 ) {
+ found = true;
+ clipName = childName;
+ clipName.erase ( childLen-4 );
+ }
+ }
+ aFolder.Close();
+ if ( ! found ) return false;
+
+#endif
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += clipName;
+
+ size_t pathLen = tempPath.size() + 1; // Include a terminating nul.
+ parent->tempPtr = malloc ( pathLen );
+ if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for SonyHDV clip info", kXMPErr_NoMemory );
+ memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above.
+
+ return true;
+
+} // SonyHDV_CheckFormat
+
+// =================================================================================================
+
+static void* CreatePseudoClipPath ( const std::string & clientPath ) {
+
+ // Used to create the clip pseudo path when the CheckFormat function is skipped.
+
+ std::string pseudoPath = clientPath;
+
+ size_t pathLen;
+ void* tempPtr = 0;
+
+ if ( Host_IO::Exists ( pseudoPath.c_str() ) ) {
+
+ // The client passed a physical path. The logical clip name is the leaf name, with the
+ // extension removed. There are no extra suffixes on Sony HDV files. The movie root path ends
+ // two levels up.
+
+ std::string clipName, ignored;
+
+ XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name.
+ XIO::SplitFileExtension ( &clipName, &ignored );
+
+ XIO::SplitLeafName ( &pseudoPath, &ignored ); // Remove the 2 intermediate folder levels.
+ XIO::SplitLeafName ( &pseudoPath, &ignored );
+
+ pseudoPath += kDirChar;
+ pseudoPath += clipName;
+
+ }
+
+ pathLen = pseudoPath.size() + 1; // Include a terminating nul.
+ tempPtr = malloc ( pathLen );
+ if ( tempPtr == 0 ) XMP_Throw ( "No memory for SonyHDV clip info", kXMPErr_NoMemory );
+ memcpy ( tempPtr, pseudoPath.c_str(), pathLen );
+
+ return tempPtr;
+
+} // CreatePseudoClipPath
+
+// =================================================================================================
+// ReadIDXFile
+// ===========
+
+#define ExtractTimeCodeByte(ch,mask) ( (((ch & mask) >> 4) * 10) + (ch & 0xF) )
+
+static bool ReadIDXFile ( const std::string& idxPath,
+ const std::string& clipName,
+ SXMPMeta* xmpObj,
+ bool& containsXMP,
+ MD5_CTX* md5Context,
+ bool digestFound )
+{
+ bool result = true;
+ containsXMP = false;
+
+ if ( clipName.size() != 25 ) return false;
+
+ try {
+
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( idxPath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return false; // The open failed.
+ XMPFiles_IO idxFile ( hostRef, idxPath.c_str(), Host_IO::openReadOnly );
+
+ struct SHDV_HeaderBlock
+ {
+ char mHeader[8];
+ unsigned char mValidFlag;
+ unsigned char mReserved;
+ unsigned char mECCTB;
+ unsigned char mSignalMode;
+ unsigned char mFileThousands;
+ unsigned char mFileHundreds;
+ unsigned char mFileTens;
+ unsigned char mFileUnits;
+ };
+
+ SHDV_HeaderBlock hdvHeaderBlock;
+ memset ( &hdvHeaderBlock, 0, sizeof(SHDV_HeaderBlock) );
+
+ idxFile.ReadAll ( hdvHeaderBlock.mHeader, 8 );
+ idxFile.ReadAll ( &hdvHeaderBlock.mValidFlag, 1 );
+ idxFile.ReadAll ( &hdvHeaderBlock.mReserved, 1 );
+ idxFile.ReadAll ( &hdvHeaderBlock.mECCTB, 1 );
+ idxFile.ReadAll ( &hdvHeaderBlock.mSignalMode, 1 );
+ idxFile.ReadAll ( &hdvHeaderBlock.mFileThousands, 1 );
+ idxFile.ReadAll ( &hdvHeaderBlock.mFileHundreds, 1 );
+ idxFile.ReadAll ( &hdvHeaderBlock.mFileTens, 1 );
+ idxFile.ReadAll ( &hdvHeaderBlock.mFileUnits, 1 );
+
+ const int fileCount = (hdvHeaderBlock.mFileThousands - '0') * 1000 +
+ (hdvHeaderBlock.mFileHundreds - '0') * 100 +
+ (hdvHeaderBlock.mFileTens - '0') * 10 +
+ (hdvHeaderBlock.mFileUnits - '0');
+
+ // Read file info block.
+ struct SHDV_FileBlock
+ {
+ char mDT[2];
+ unsigned char mFileNameYear;
+ unsigned char mFileNameMonth;
+ unsigned char mFileNameDay;
+ unsigned char mFileNameHour;
+ unsigned char mFileNameMinute;
+ unsigned char mFileNameSecond;
+ unsigned char mStartTimeCode[4];
+ unsigned char mTotalFrame[4];
+ };
+
+ SHDV_FileBlock hdvFileBlock;
+ memset ( &hdvFileBlock, 0, sizeof(SHDV_FileBlock) );
+
+ char filenameBuffer[256];
+ std::string fileDateAndTime = clipName.substr(8);
+
+ bool foundFileBlock = false;
+
+ for ( int i=0; ((i < fileCount) && (! foundFileBlock)); ++i ) {
+
+ idxFile.ReadAll ( hdvFileBlock.mDT, 2 );
+ idxFile.ReadAll ( &hdvFileBlock.mFileNameYear, 1 );
+ idxFile.ReadAll ( &hdvFileBlock.mFileNameMonth, 1 );
+ idxFile.ReadAll ( &hdvFileBlock.mFileNameDay, 1 );
+ idxFile.ReadAll ( &hdvFileBlock.mFileNameHour, 1 );
+ idxFile.ReadAll ( &hdvFileBlock.mFileNameMinute, 1 );
+ idxFile.ReadAll ( &hdvFileBlock.mFileNameSecond, 1 );
+ idxFile.ReadAll ( &hdvFileBlock.mStartTimeCode, 4 );
+ idxFile.ReadAll ( &hdvFileBlock.mTotalFrame, 4 );
+
+ // Compose file name we expect from file contents and break out on match.
+ sprintf ( filenameBuffer, "%02d-%02d-%02d_%02d%02d%02d",
+ hdvFileBlock.mFileNameYear + 2000,
+ hdvFileBlock.mFileNameMonth,
+ hdvFileBlock.mFileNameDay,
+ hdvFileBlock.mFileNameHour,
+ hdvFileBlock.mFileNameMinute,
+ hdvFileBlock.mFileNameSecond );
+
+ foundFileBlock = (fileDateAndTime==filenameBuffer);
+
+ }
+
+ idxFile.Close();
+ if ( ! foundFileBlock ) return false;
+
+ // If digest calculation requested, calculate it and return.
+ if ( md5Context != 0 ) {
+ MD5Update ( md5Context, (XMP_Uns8*)(&hdvHeaderBlock), sizeof(SHDV_HeaderBlock) );
+ MD5Update ( md5Context, (XMP_Uns8*)(&hdvFileBlock), sizeof(SHDV_FileBlock) );
+ }
+
+ // The xmpObj parameter must be provided in order to extract XMP
+ if ( xmpObj == 0 ) return (md5Context != 0);
+
+ // Standard def?
+ const bool isSD = ((hdvHeaderBlock.mSignalMode == 0x80) || (hdvHeaderBlock.mSignalMode == 0));
+
+ // Progressive vs interlaced extracted from high bit of ECCTB byte
+ const bool clipIsProgressive = ((hdvHeaderBlock.mECCTB & 0x80) != 0);
+
+ // Lowest three bits contain frame rate information
+ const int sfr = (hdvHeaderBlock.mECCTB & 7) + (clipIsProgressive ? 0 : 8);
+
+ // Sample scale and sample size.
+ int clipSampleScale = 0;
+ int clipSampleSize = 0;
+ std::string frameRate;
+
+ // Frame rate
+ switch ( sfr ) {
+ case 0 : break; // Not valid in spec, but it's happening in test files.
+ case 1 : clipSampleScale = 24000; clipSampleSize = 1001; frameRate = "23.98p"; break;
+ case 3 : clipSampleScale = 25; clipSampleSize = 1; frameRate = "25p"; break;
+ case 4 : clipSampleScale = 30000; clipSampleSize = 1001; frameRate = "29.97p"; break;
+ case 11 : clipSampleScale = 25; clipSampleSize = 1; frameRate = "50i"; break;
+ case 12 : clipSampleScale = 30000; clipSampleSize = 1001; frameRate = "59.94i"; break;
+ }
+
+ containsXMP = true;
+
+ // Frame size and PAR for HD (not clear on SD yet).
+ std::string xmpString;
+ XMP_StringPtr xmpValue = 0;
+
+ if ( ! isSD ) {
+
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoFrameSize" )) ) {
+
+ xmpValue = "1440";
+ xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "w", &xmpString, 0 );
+ if ( xmpString != xmpValue ) {
+ xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", xmpValue, 0 );
+ }
+
+ xmpValue = "1080";
+ xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "h", &xmpString, 0 );
+ if ( xmpString != xmpValue ) {
+ xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", xmpValue, 0 );
+ }
+
+ xmpValue = "pixels";
+ xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "unit", &xmpString, 0 );
+ if ( xmpString != xmpValue ) {
+ xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", xmpValue, 0 );
+ }
+ }
+
+ xmpValue = "4/3";
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoPixelAspectRatio" )) ) {
+ xmpObj->SetProperty ( kXMP_NS_DM, "videoPixelAspectRatio", xmpValue, kXMP_DeleteExisting );
+ }
+
+ }
+
+ // Sample size and scale.
+ if ( clipSampleScale != 0 ) {
+
+ char buffer[255];
+
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimeScale" )) ) {
+ sprintf(buffer, "%d", clipSampleScale);
+ xmpValue = buffer;
+ xmpObj->SetProperty ( kXMP_NS_DM, "startTimeScale", xmpValue, kXMP_DeleteExisting );
+ }
+
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimeSampleSize" )) ) {
+ sprintf(buffer, "%d", clipSampleSize);
+ xmpValue = buffer;
+ xmpObj->SetProperty ( kXMP_NS_DM, "startTimeSampleSize", xmpValue, kXMP_DeleteExisting );
+ }
+
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) {
+
+ const int frameCount = (hdvFileBlock.mTotalFrame[0] << 24) + (hdvFileBlock.mTotalFrame[1] << 16) +
+ (hdvFileBlock.mTotalFrame[2] << 8) + hdvFileBlock.mTotalFrame[3];
+
+ sprintf ( buffer, "%d", frameCount );
+ xmpValue = buffer;
+ xmpObj->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", xmpValue, 0 );
+
+ sprintf ( buffer, "%d/%d", clipSampleSize, clipSampleScale );
+ xmpValue = buffer;
+ xmpObj->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", xmpValue, 0 );
+
+ }
+
+ }
+
+ // Time Code.
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimecode" )) ) {
+
+ if ( (clipSampleScale != 0) && (clipSampleSize != 0) ) {
+
+ const bool dropFrame = ( (0x40 & hdvFileBlock.mStartTimeCode[0]) != 0 ) && ( sfr == 4 || sfr == 12 );
+ const char chDF = dropFrame ? ';' : ':';
+ const int tcFrames = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[0], 0x30 );
+ const int tcSeconds = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[1], 0x70 );
+ const int tcMinutes = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[2], 0x70 );
+ const int tcHours = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[3], 0x30 );
+
+ // HH:MM:SS:FF or HH;MM;SS;FF
+ char timecode[256];
+ sprintf ( timecode, "%02d%c%02d%c%02d%c%02d", tcHours, chDF, tcMinutes, chDF, tcSeconds, chDF, tcFrames );
+ std::string sonyTimeString = timecode;
+
+ xmpObj->GetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", &xmpString, 0 );
+ if ( xmpString != sonyTimeString ) {
+
+ xmpObj->SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", sonyTimeString, 0 );
+
+ std::string timeFormat;
+ if ( clipSampleSize == 1 ) {
+
+ // 24, 25, 40, 50, 60
+ switch ( clipSampleScale ) {
+ case 24 : timeFormat = "24"; break;
+ case 25 : timeFormat = "25"; break;
+ case 50 : timeFormat = "50"; break;
+ default : XMP_Assert ( false );
+ }
+
+ timeFormat += "Timecode";
+
+ } else {
+
+ // 23.976, 29.97, 59.94
+ XMP_Assert ( clipSampleSize == 1001 );
+ switch ( clipSampleScale ) {
+ case 24000 : timeFormat = "23976"; break;
+ case 30000 : timeFormat = "2997"; break;
+ case 60000 : timeFormat = "5994"; break;
+ default : XMP_Assert( false ); break;
+ }
+
+ timeFormat += dropFrame ? "DropTimecode" : "NonDropTimecode";
+
+ }
+
+ xmpObj->SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", timeFormat, 0 );
+
+ }
+
+ }
+
+ }
+
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "CreateDate" )) ) {
+
+ // Clip has date and time in the case of DT (otherwise date and time haven't been set).
+ bool clipHasDate = ((hdvFileBlock.mDT[0] == 'D') && (hdvFileBlock.mDT[1] == 'T'));
+
+ // Creation date
+ if ( clipHasDate ) {
+
+ // YYYY-MM-DDThh:mm:ssZ
+ char date[256];
+ sprintf ( date, "%4d-%02d-%02dT%02d:%02d:%02dZ",
+ hdvFileBlock.mFileNameYear + 2000,
+ hdvFileBlock.mFileNameMonth,
+ hdvFileBlock.mFileNameDay,
+ hdvFileBlock.mFileNameHour,
+ hdvFileBlock.mFileNameMinute,
+ hdvFileBlock.mFileNameSecond );
+
+ XMP_StringPtr xmpDate = date;
+ xmpObj->SetProperty ( kXMP_NS_XMP, "CreateDate", xmpDate, kXMP_DeleteExisting );
+
+ }
+
+ }
+
+ // Frame rate.
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoFrameRate" )) ) {
+
+ if ( frameRate.size() != 0 ) {
+ xmpString = frameRate;
+ xmpObj->SetProperty ( kXMP_NS_DM, "videoFrameRate", xmpString, kXMP_DeleteExisting );
+ }
+
+ }
+
+ } catch ( ... ) {
+
+ result = false;
+
+ }
+
+ return result;
+
+} // ReadIDXFile
+
+// =================================================================================================
+// SonyHDV_MetaHandlerCTor
+// =======================
+
+XMPFileHandler * SonyHDV_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new SonyHDV_MetaHandler ( parent );
+
+} // SonyHDV_MetaHandlerCTor
+
+// =================================================================================================
+// SonyHDV_MetaHandler::SonyHDV_MetaHandler
+// ========================================
+
+SonyHDV_MetaHandler::SonyHDV_MetaHandler ( XMPFiles * _parent )
+{
+
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kSonyHDV_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ // Extract the root path and clip name.
+
+ if ( this->parent->tempPtr == 0 ) {
+ // The CheckFormat call might have been skipped.
+ this->parent->tempPtr = CreatePseudoClipPath ( this->parent->filePath );
+ }
+
+ this->rootPath.assign ( (char*) this->parent->tempPtr );
+ free ( this->parent->tempPtr );
+ this->parent->tempPtr = 0;
+
+ XIO::SplitLeafName ( &this->rootPath, &this->clipName );
+
+} // SonyHDV_MetaHandler::SonyHDV_MetaHandler
+
+// =================================================================================================
+// SonyHDV_MetaHandler::~SonyHDV_MetaHandler
+// =========================================
+
+SonyHDV_MetaHandler::~SonyHDV_MetaHandler()
+{
+
+ if ( this->parent->tempPtr != 0 ) {
+ free ( this->parent->tempPtr );
+ this->parent->tempPtr = 0;
+ }
+
+} // SonyHDV_MetaHandler::~SonyHDV_MetaHandler
+
+// =================================================================================================
+// SonyHDV_MetaHandler::MakeClipFilePath
+// =====================================
+
+bool SonyHDV_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ )
+{
+
+ *path = this->rootPath;
+ *path += kDirChar;
+ *path += "VIDEO";
+ *path += kDirChar;
+ *path += "HVR";
+ *path += kDirChar;
+ *path += this->clipName;
+ *path += suffix;
+
+ if ( ! checkFile ) return true;
+ return Host_IO::Exists ( path->c_str() );
+
+} // SonyHDV_MetaHandler::MakeClipFilePath
+
+// =================================================================================================
+// SonyHDV_MetaHandler::MakeIndexFilePath
+// ======================================
+
+bool SonyHDV_MetaHandler::MakeIndexFilePath ( std::string& idxPath, const std::string& rootPath, const std::string& leafName )
+{
+ std::string tempPath;
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "VIDEO";
+ tempPath += kDirChar;
+ tempPath += "HVR";
+
+ idxPath = tempPath;
+ idxPath += kDirChar;
+ idxPath += leafName;
+ idxPath += ".IDX";
+
+ // Default case
+ if ( Host_IO::GetFileMode ( idxPath.c_str() ) == Host_IO::kFMode_IsFile ) return true;
+
+ // Spanned clip case
+
+ // Scanning code taken from SonyHDV_CheckFormat
+ // Can be isolated to a separate function.
+
+ std::string clipName = leafName;
+ int usCount = 0;
+ size_t i, limit = leafName.size();
+
+ for ( i = 0; i < limit; ++i ) {
+ if ( clipName[i] == '_' ) {
+ ++usCount;
+ if ( usCount == 2 ) break;
+ }
+ }
+
+ if ( i < limit ) clipName.erase ( i );
+ clipName += '_'; // Make sure a final '_' is there for the search comparisons.
+
+ Host_IO::AutoFolder aFolder;
+ std::string childName;
+ bool found = false;
+
+ aFolder.folder = Host_IO::OpenFolder ( tempPath.c_str() );
+ while ( (! found) && Host_IO::GetNextChild ( aFolder.folder, &childName ) ) {
+ size_t childLen = childName.size();
+ if ( childLen < 4 ) continue;
+ MakeUpperCase ( &childName );
+ if ( childName.compare ( childLen-4, 4, ".IDX" ) != 0 ) continue;
+ if ( childName.compare ( 0, clipName.size(), clipName ) == 0 ) {
+ found = true;
+ clipName = childName;
+ clipName.erase ( childLen-4 );
+ }
+ }
+ aFolder.Close();
+ if ( ! found ) return false;
+
+ idxPath = tempPath;
+ idxPath += kDirChar;
+ idxPath += clipName;
+ idxPath += ".IDX";
+
+ return true;
+
+}
+
+// =================================================================================================
+// SonyHDV_MetaHandler::MakeLegacyDigest
+// =====================================
+
+#define kHexDigits "0123456789ABCDEF"
+
+void SonyHDV_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ std::string idxPath;
+ if ( ! this->MakeIndexFilePath ( idxPath, this->rootPath, this->clipName ) ) return;
+
+ MD5_CTX context;
+ unsigned char digestBin [16];
+ bool dummy = false;
+ MD5Init ( &context );
+ ReadIDXFile ( idxPath, this->clipName, 0, dummy, &context, false );
+ MD5Final ( digestBin, &context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->erase();
+ digestStr->append ( buffer, 32 );
+
+} // MakeLegacyDigest
+
+// =================================================================================================
+// SonyHDV_MetaHandler::GetFileModDate
+// ===================================
+
+static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) {
+ int compare = SXMPUtils::CompareDateTime ( left, right );
+ return (compare < 0);
+}
+
+bool SonyHDV_MetaHandler::GetFileModDate ( XMP_DateTime * modDate )
+{
+
+ // The Sony HDV locations of metadata:
+ // VIDEO/
+ // HVR/
+ // 00_0001_2007-08-06_165555.IDX
+ // 00_0001_2007-08-06_165555.XMP
+
+ bool ok, haveDate = false;
+ std::string fullPath;
+ XMP_DateTime oneDate, junkDate;
+ if ( modDate == 0 ) modDate = &junkDate;
+
+ ok = this->MakeIndexFilePath ( fullPath, this->rootPath, this->clipName );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ ok = this->MakeClipFilePath ( &fullPath, ".XMP", true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ return haveDate;
+
+} // SonyHDV_MetaHandler::GetFileModDate
+
+// =================================================================================================
+// SonyHDV_MetaHandler::CacheFileData
+// ==================================
+
+void SonyHDV_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( ! this->containsXMP );
+
+ if ( this->parent->UsesClientIO() ) {
+ XMP_Throw ( "XDCAM cannot be used with client-managed I/O", kXMPErr_InternalFailure );
+ }
+
+ // See if the clip's .XMP file exists.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, ".XMP" );
+ if ( Host_IO::GetFileMode ( xmpPath.c_str() ) != Host_IO::kFMode_IsFile ) return; // No XMP.
+
+ // Read the entire .XMP file.
+
+ bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate );
+
+ XMP_Assert ( this->parent->ioRef == 0 );
+ XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), readOnly );
+ if ( xmpFile == 0 ) return; // The open failed.
+ this->parent->ioRef = xmpFile;
+
+ XMP_Int64 xmpLen = xmpFile->Length();
+ if ( xmpLen > 100*1024*1024 ) {
+ XMP_Throw ( "SonyHDV XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check.
+ }
+
+ this->xmpPacket.erase();
+ this->xmpPacket.append ( (size_t)xmpLen, ' ' );
+
+ XMP_Int32 ioCount = xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen );
+
+ this->packetInfo.offset = 0;
+ this->packetInfo.length = (XMP_Int32)xmpLen;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ this->containsXMP = true;
+
+} // SonyHDV_MetaHandler::CacheFileData
+
+// =================================================================================================
+// SonyHDV_MetaHandler::ProcessXMP
+// ===============================
+
+void SonyHDV_MetaHandler::ProcessXMP()
+{
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( this->containsXMP ) {
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ }
+
+ // Check the legacy digest.
+ std::string oldDigest, newDigest;
+ bool digestFound;
+ digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "SonyHDV", &oldDigest, 0 );
+ if ( digestFound ) {
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) return;
+ }
+
+ // Read the IDX legacy.
+ std::string idxPath;
+ if ( ! this->MakeIndexFilePath ( idxPath, this->rootPath, this->clipName ) ) return;
+ ReadIDXFile ( idxPath, this->clipName, &this->xmpObj, this->containsXMP, 0, digestFound );
+
+} // SonyHDV_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// SonyHDV_MetaHandler::UpdateFile
+// ===============================
+//
+// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here.
+
+void SonyHDV_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Make sure only called once.
+
+ XMP_Assert ( this->parent->UsesLocalIO() );
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "SonyHDV", newDigest.c_str(), kXMP_DeleteExisting );
+
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );
+
+ // -------------------------------------------------
+ // Update just the XMP file not the native IDX file.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, ".XMP" );
+
+ bool haveXMP = Host_IO::Exists ( xmpPath.c_str() );
+ if ( ! haveXMP ) {
+ XMP_Assert ( this->parent->ioRef == 0 );
+ Host_IO::Create ( xmpPath.c_str() );
+ this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), Host_IO::openReadWrite );
+ if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening SonyHDV XMP file", kXMPErr_ExternalFailure );
+ }
+
+ XMP_IO* xmpFile = this->parent->ioRef;
+ XMP_Assert ( xmpFile != 0 );
+ XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) );
+
+} // SonyHDV_MetaHandler::UpdateFile
+
+// =================================================================================================
+// SonyHDV_MetaHandler::WriteTempFile
+// ==================================
+
+void SonyHDV_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+
+ // ! WriteTempFile is not supposed to be called for handlers that own the file.
+ XMP_Throw ( "SonyHDV_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure );
+
+} // SonyHDV_MetaHandler::WriteTempFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp b/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp
new file mode 100644
index 0000000..ac27c66
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp
@@ -0,0 +1,79 @@
+#ifndef __SonyHDV_Handler_hpp__
+#define __SonyHDV_Handler_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 "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+#include "source/ExpatAdapter.hpp"
+
+// =================================================================================================
+/// \file SonyHDV_Handler.hpp
+/// \brief Folder format handler for SonyHDV.
+///
+/// This header ...
+///
+// =================================================================================================
+
+extern XMPFileHandler * SonyHDV_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool SonyHDV_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kSonyHDV_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_FolderBasedFormat);
+
+class SonyHDV_MetaHandler : public XMPFileHandler
+{
+public:
+
+ bool GetFileModDate ( XMP_DateTime * modDate );
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files.
+ { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); };
+
+ SonyHDV_MetaHandler ( XMPFiles * _parent );
+ virtual ~SonyHDV_MetaHandler();
+
+private:
+
+ SonyHDV_MetaHandler() {}; // Hidden on purpose.
+
+ bool MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false );
+ bool MakeIndexFilePath ( std::string& idxPath, const std::string& rootPath, const std::string& leafName );
+ void MakeLegacyDigest ( std::string * digestStr );
+
+ std::string rootPath, clipName;
+
+}; // SonyHDV_MetaHandler
+
+// =================================================================================================
+
+#endif /* __SonyHDV_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/TIFF_Handler.cpp b/XMPFiles/source/FileHandlers/TIFF_Handler.cpp
new file mode 100644
index 0000000..109fe43
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/TIFF_Handler.cpp
@@ -0,0 +1,403 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/TIFF_Handler.hpp"
+
+#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp"
+#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp"
+#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp"
+#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp"
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+
+#include "third-party/zuid/interfaces/MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file TIFF_Handler.cpp
+/// \brief File format handler for TIFF.
+///
+/// This handler ...
+///
+// =================================================================================================
+
+// =================================================================================================
+// TIFF_CheckFormat
+// ================
+
+// For TIFF we just check for the II/42 or MM/42 in the first 4 bytes and that there are at least
+// 26 bytes of data (4+4+2+12+4).
+//
+// ! The CheckXyzFormat routines don't track the filePos, that is left to ScanXyzFile.
+
+bool TIFF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent )
+{
+ IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(parent);
+ XMP_Assert ( format == kXMP_TIFFFile );
+
+ enum { kMinimalTIFFSize = 4+4+2+12+4 }; // Header plus IFD with 1 entry.
+
+ IOBuffer ioBuf;
+
+ fileRef->Rewind ( );
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, kMinimalTIFFSize ) ) return false;
+
+ bool leTIFF = CheckBytes ( ioBuf.ptr, "\x49\x49\x2A\x00", 4 );
+ bool beTIFF = CheckBytes ( ioBuf.ptr, "\x4D\x4D\x00\x2A", 4 );
+
+ return (leTIFF | beTIFF);
+
+} // TIFF_CheckFormat
+
+// =================================================================================================
+// TIFF_MetaHandlerCTor
+// ====================
+
+XMPFileHandler * TIFF_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new TIFF_MetaHandler ( parent );
+
+} // TIFF_MetaHandlerCTor
+
+// =================================================================================================
+// TIFF_MetaHandler::TIFF_MetaHandler
+// ==================================
+
+TIFF_MetaHandler::TIFF_MetaHandler ( XMPFiles * _parent ) : psirMgr(0), iptcMgr(0)
+{
+ this->parent = _parent;
+ this->handlerFlags = kTIFF_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+} // TIFF_MetaHandler::TIFF_MetaHandler
+
+// =================================================================================================
+// TIFF_MetaHandler::~TIFF_MetaHandler
+// ===================================
+
+TIFF_MetaHandler::~TIFF_MetaHandler()
+{
+
+ if ( this->psirMgr != 0 ) delete ( this->psirMgr );
+ if ( this->iptcMgr != 0 ) delete ( this->iptcMgr );
+
+} // TIFF_MetaHandler::~TIFF_MetaHandler
+
+// =================================================================================================
+// TIFF_MetaHandler::CacheFileData
+// ===============================
+//
+// The data caching for TIFF is easy to explain and implement, but does more processing than one
+// might at first expect. This seems unavoidable given the need to close the disk file after calling
+// CacheFileData. We parse the TIFF stream and cache the values for all tags of interest, and note
+// whether XMP is present. We do not parse the XMP, Photoshop image resources, or IPTC datasets.
+
+// *** This implementation simply returns when invalid TIFF is encountered. Should we throw instead?
+
+void TIFF_MetaHandler::CacheFileData()
+{
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_PacketInfo & packetInfo = this->packetInfo;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_Assert ( ! this->containsXMP );
+ // Set containsXMP to true here only if the XMP tag is found.
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "TIFF_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
+ }
+
+ this->tiffMgr.ParseFileStream ( fileRef );
+
+ TIFF_Manager::TagInfo dngInfo;
+ if ( this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, &dngInfo ) ) {
+
+ // Reject DNG files that are version 2.0 or beyond, this is being written at the time of
+ // DNG version 1.2. The DNG team says it is OK to use 2.0, not strictly 1.2. Use the
+ // DNGBackwardVersion if it is present, else the DNGVersion. Note that the version value is
+ // supposed to be type BYTE, so the file order is always essentially big endian.
+
+ XMP_Uns8 majorVersion = *((XMP_Uns8*)dngInfo.dataPtr); // Start with DNGVersion.
+ if ( this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGBackwardVersion, &dngInfo ) ) {
+ majorVersion = *((XMP_Uns8*)dngInfo.dataPtr); // Use DNGBackwardVersion if possible.
+ }
+ if ( majorVersion > 1 ) XMP_Throw ( "DNG version beyond 1.x", kXMPErr_BadTIFF );
+
+ }
+
+ TIFF_Manager::TagInfo xmpInfo;
+ bool found = this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, &xmpInfo );
+
+ if ( found ) {
+
+ this->packetInfo.offset = this->tiffMgr.GetValueOffset ( kTIFF_PrimaryIFD, kTIFF_XMP );
+ this->packetInfo.length = xmpInfo.dataLen;
+ this->packetInfo.padSize = 0; // Assume for now, set these properly in ProcessXMP.
+ this->packetInfo.charForm = kXMP_CharUnknown;
+ this->packetInfo.writeable = true;
+
+ this->xmpPacket.assign ( (XMP_StringPtr)xmpInfo.dataPtr, xmpInfo.dataLen );
+
+ this->containsXMP = true;
+
+ }
+
+} // TIFF_MetaHandler::CacheFileData
+
+// =================================================================================================
+// TIFF_MetaHandler::ProcessXMP
+// ============================
+//
+// Process the raw XMP and legacy metadata that was previously cached. The legacy metadata in TIFF
+// is messy because there are 2 copies of the IPTC and because of a Photoshop 6 bug/quirk in the way
+// Exif metadata is saved.
+
+void TIFF_MetaHandler::ProcessXMP()
+{
+
+ this->processedXMP = true; // Make sure we only come through here once.
+
+ // Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy
+ // import if the XMP packet gets parsing errors.
+
+ // ! Photoshop 6 wrote annoyingly wacky TIFF files. It buried a lot of the Exif metadata inside
+ // ! image resource 1058, itself inside of tag 34377 in the 0th IFD. Take care of this before
+ // ! doing any of the legacy metadata presence or priority analysis. Delete image resource 1058
+ // ! to get rid of the buried Exif, but don't mark the XMPFiles object as changed. This change
+ // ! should not trigger an update, but should be included as part of a normal update.
+
+ bool found;
+ bool readOnly = ((this->parent->openFlags & kXMPFiles_OpenForUpdate) == 0);
+
+ if ( readOnly ) {
+ this->psirMgr = new PSIR_MemoryReader();
+ this->iptcMgr = new IPTC_Reader();
+ } else {
+ this->psirMgr = new PSIR_FileWriter();
+ this->iptcMgr = new IPTC_Writer(); // ! Parse it later.
+ }
+
+ TIFF_Manager & tiff = this->tiffMgr; // Give the compiler help in recognizing non-aliases.
+ PSIR_Manager & psir = *this->psirMgr;
+ IPTC_Manager & iptc = *this->iptcMgr;
+
+ TIFF_Manager::TagInfo psirInfo;
+ bool havePSIR = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_PSIR, &psirInfo );
+
+ if ( havePSIR ) { // ! Do the Photoshop 6 integration before other legacy analysis.
+ psir.ParseMemoryResources ( psirInfo.dataPtr, psirInfo.dataLen );
+ PSIR_Manager::ImgRsrcInfo buriedExif;
+ found = psir.GetImgRsrc ( kPSIR_Exif, &buriedExif );
+ if ( found ) {
+ tiff.IntegrateFromPShop6 ( buriedExif.dataPtr, buriedExif.dataLen );
+ if ( ! readOnly ) psir.DeleteImgRsrc ( kPSIR_Exif );
+ }
+ }
+
+ TIFF_Manager::TagInfo iptcInfo;
+ bool haveIPTC = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_IPTC, &iptcInfo ); // The TIFF IPTC tag.
+ int iptcDigestState = kDigestMatches;
+
+ if ( haveIPTC ) {
+
+ bool haveDigest = false;
+ PSIR_Manager::ImgRsrcInfo digestInfo;
+ if ( havePSIR ) haveDigest = psir.GetImgRsrc ( kPSIR_IPTCDigest, &digestInfo );
+ if ( digestInfo.dataLen != 16 ) haveDigest = false;
+
+ if ( ! haveDigest ) {
+
+ iptcDigestState = kDigestMissing;
+
+ } else {
+
+ // Older versions of Photoshop wrote tag 33723 with type LONG, but ignored the trailing
+ // zero padding for the IPTC digest. If the full digest differs, recheck without the padding.
+
+ iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, iptcInfo.dataLen, digestInfo.dataPtr );
+
+ if ( (iptcDigestState == kDigestDiffers) && (kTIFF_TypeSizes[iptcInfo.type] > 1) ) {
+ XMP_Uns8 * endPtr = (XMP_Uns8*)iptcInfo.dataPtr + iptcInfo.dataLen - 1;
+ XMP_Uns8 * minPtr = endPtr - kTIFF_TypeSizes[iptcInfo.type] + 1;
+ while ( (endPtr >= minPtr) && (*endPtr == 0) ) --endPtr;
+ XMP_Uns32 unpaddedLen = (XMP_Uns32) (endPtr - (XMP_Uns8*)iptcInfo.dataPtr + 1);
+ iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, unpaddedLen, digestInfo.dataPtr );
+ }
+
+ }
+
+ }
+
+ XMP_OptionBits options = k2XMP_FileHadExif; // TIFF files are presumed to have Exif legacy.
+ if ( haveIPTC ) options |= k2XMP_FileHadIPTC;
+ if ( this->containsXMP ) options |= k2XMP_FileHadXMP;
+
+ // Process the XMP packet. If it fails to parse, do a forced legacy import but still throw an
+ // exception. This tells the caller that an error happened, but gives them recovered legacy
+ // should they want to proceed with that.
+
+ bool haveXMP = false;
+
+ if ( ! this->xmpPacket.empty() ) {
+ XMP_Assert ( this->containsXMP );
+ // Common code takes care of packetInfo.charForm, .padSize, and .writeable.
+ XMP_StringPtr packetStr = this->xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
+ try {
+ this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
+ haveXMP = true;
+ } catch ( ... ) {
+ XMP_ClearOption ( options, k2XMP_FileHadXMP );
+ if ( haveIPTC ) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen );
+ if ( iptcDigestState == kDigestMatches ) iptcDigestState = kDigestMissing;
+ ImportPhotoData ( tiff, iptc, psir, iptcDigestState, &this->xmpObj, options );
+ throw; // ! Rethrow the exception, don't absorb it.
+ }
+ }
+
+ // Process the legacy metadata.
+
+ if ( haveIPTC && (! haveXMP) && (iptcDigestState == kDigestMatches) ) iptcDigestState = kDigestMissing;
+ bool parseIPTC = (iptcDigestState != kDigestMatches) || (! readOnly);
+ if ( parseIPTC ) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen );
+ ImportPhotoData ( tiff, iptc, psir, iptcDigestState, &this->xmpObj, options );
+
+ this->containsXMP = true; // Assume we now have something in the XMP.
+
+} // TIFF_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// TIFF_MetaHandler::UpdateFile
+// ============================
+//
+// There is very little to do directly in UpdateFile. ExportXMPtoJTP takes care of setting all of
+// the necessary TIFF tags, including things like the 2nd copy of the IPTC in the Photoshop image
+// resources in tag 34377. TIFF_FileWriter::UpdateFileStream does all of the update-by-append I/O.
+
+// *** Need to pass the abort proc and arg to TIFF_FileWriter::UpdateFileStream.
+
+void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates.
+
+ XMP_IO* destRef = this->parent->ioRef;
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+
+ XMP_Int64 oldPacketOffset = this->packetInfo.offset;
+ XMP_Int32 oldPacketLength = this->packetInfo.length;
+
+ if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks.
+ if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0;
+
+ bool fileHadXMP = ((oldPacketOffset != 0) && (oldPacketLength != 0));
+
+ // Update the IPTC-IIM and native TIFF/Exif metadata. ExportPhotoData also trips the tiff: and
+ // exif: copies from the XMP, so reserialize the now final XMP packet.
+
+ ExportPhotoData ( kXMP_TIFFFile, &this->xmpObj, &this->tiffMgr, this->iptcMgr, this->psirMgr );
+
+ try {
+ XMP_OptionBits options = kXMP_UseCompactFormat;
+ if ( fileHadXMP ) options |= kXMP_ExactPacketLength;
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, options, oldPacketLength );
+ } catch ( ... ) {
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+ }
+
+ // Decide whether to do an in-place update. This can only happen if all of the following are true:
+ // - There is an XMP packet in the file.
+ // - The are no changes to the legacy tags. (The IPTC and PSIR are in the TIFF tags.)
+ // - The new XMP can fit in the old space.
+
+ bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength));
+ if ( this->tiffMgr.IsLegacyChanged() ) doInPlace = false;
+
+ if ( ! doInPlace ) {
+
+ #if GatherPerformanceData
+ sAPIPerf->back().extraInfo += ", TIFF append update";
+ #endif
+
+ this->tiffMgr.SetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, kTIFF_UndefinedType, (XMP_Uns32)this->xmpPacket.size(), this->xmpPacket.c_str() );
+ this->tiffMgr.UpdateFileStream ( destRef );
+
+ } else {
+
+ #if GatherPerformanceData
+ sAPIPerf->back().extraInfo += ", TIFF in-place update";
+ #endif
+
+ if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) {
+ // They ought to match, cheap to be sure.
+ size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size();
+ this->xmpPacket.append ( extraSpace, ' ' );
+ }
+
+ XMP_IO* liveFile = this->parent->ioRef;
+
+ XMP_Assert ( this->xmpPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic.
+
+ liveFile->Seek ( oldPacketOffset, kXMP_SeekFromStart );
+ liveFile->Write ( this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() );
+
+ }
+
+ this->needsUpdate = false;
+
+} // TIFF_MetaHandler::UpdateFile
+
+// =================================================================================================
+// TIFF_MetaHandler::WriteTempFile
+// ===============================
+//
+// The structure of TIFF makes it hard to do a sequential source-to-dest copy with interleaved
+// updates. So, copy the existing source to the destination and call UpdateFile.
+
+void TIFF_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ XMP_IO* origRef = this->parent->ioRef;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+
+ XMP_Int64 fileLen = origRef->Length();
+ if ( fileLen > 0xFFFFFFFFLL ) { // Check before making a copy of the file.
+ XMP_Throw ( "TIFF fles can't exceed 4GB", kXMPErr_BadTIFF );
+ }
+
+ origRef->Rewind ( );
+ tempRef->Truncate ( 0 );
+ XIO::Copy ( origRef, tempRef, fileLen, abortProc, abortArg );
+
+ try {
+ this->parent->ioRef = tempRef; // ! Make UpdateFile update the temp.
+ this->UpdateFile ( false );
+ this->parent->ioRef = origRef;
+ } catch ( ... ) {
+ this->parent->ioRef = origRef;
+ throw;
+ }
+
+} // TIFF_MetaHandler::WriteTempFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/TIFF_Handler.hpp b/XMPFiles/source/FileHandlers/TIFF_Handler.hpp
new file mode 100644
index 0000000..40dc50a
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/TIFF_Handler.hpp
@@ -0,0 +1,68 @@
+#ifndef __TIFF_Handler_hpp__
+#define __TIFF_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp"
+#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp"
+#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp"
+
+// =================================================================================================
+/// \file TIFF_Handler.hpp
+/// \brief File format handler for TIFF.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// *** Could derive from Basic_Handler - buffer file tail in a temp file.
+
+extern XMPFileHandler * TIFF_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool TIFF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kTIFF_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_AllowsSafeUpdate);
+
+class TIFF_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ TIFF_MetaHandler ( XMPFiles * parent );
+ virtual ~TIFF_MetaHandler();
+
+private:
+
+ TIFF_MetaHandler() : psirMgr(0), iptcMgr(0) {}; // Hidden on purpose.
+
+ TIFF_FileWriter tiffMgr; // The TIFF part is always file-based.
+ PSIR_Manager * psirMgr; // Need to use pointers so we can properly select between read-only and
+ IPTC_Manager * iptcMgr; // read-write modes of usage.
+
+}; // TIFF_MetaHandler
+
+// =================================================================================================
+
+#endif /* __TIFF_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/Trivial_Handler.cpp b/XMPFiles/source/FileHandlers/Trivial_Handler.cpp
new file mode 100644
index 0000000..ac8b468
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/Trivial_Handler.cpp
@@ -0,0 +1,74 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/Trivial_Handler.hpp"
+
+using namespace std;
+
+// =================================================================================================
+/// \file Trivial_Handler.cpp
+/// \brief Base class for trivial handlers that only process in-place XMP.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// =================================================================================================
+// Trivial_MetaHandler::~Trivial_MetaHandler
+// =========================================
+
+Trivial_MetaHandler::~Trivial_MetaHandler()
+{
+ // Nothing to do.
+
+} // Trivial_MetaHandler::~Trivial_MetaHandler
+
+// =================================================================================================
+// Trivial_MetaHandler::UpdateFile
+// ===============================
+
+void Trivial_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ IgnoreParam ( doSafeUpdate );
+ XMP_Assert ( ! doSafeUpdate ); // Not supported at this level.
+ if ( ! this->needsUpdate ) return;
+
+ XMP_IO* fileRef = this->parent->ioRef;
+ XMP_PacketInfo & packetInfo = this->packetInfo;
+ std::string & xmpPacket = this->xmpPacket;
+
+ fileRef->Seek ( packetInfo.offset, kXMP_SeekFromStart );
+ fileRef->Write ( xmpPacket.c_str(), packetInfo.length );
+ XMP_Assert ( xmpPacket.size() == (size_t)packetInfo.length );
+
+ this->needsUpdate = false;
+
+} // Trivial_MetaHandler::UpdateFile
+
+// =================================================================================================
+// Trivial_MetaHandler::WriteTempFile
+// ==================================
+
+void Trivial_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ IgnoreParam ( tempRef );
+
+ XMP_Throw ( "Trivial_MetaHandler::WriteTempFile: Not supported", kXMPErr_Unavailable );
+
+} // Trivial_MetaHandler::WriteTempFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/Trivial_Handler.hpp b/XMPFiles/source/FileHandlers/Trivial_Handler.hpp
new file mode 100644
index 0000000..d9e0e15
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/Trivial_Handler.hpp
@@ -0,0 +1,47 @@
+#ifndef __Trivial_Handler_hpp__
+#define __Trivial_Handler_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+// =================================================================================================
+/// \file Trivial_Handler.hpp
+/// \brief Base class for trivial handlers that only process in-place XMP.
+///
+/// This header ...
+///
+/// \note There is no general promise here about crash-safe I/O. An update to an existing file might
+/// have invalid partial state while rewriting existing XMP in-place. Crash-safe updates are managed
+/// at a higher level of XMPFiles, using a temporary file and final swap of file content.
+///
+// =================================================================================================
+
+static const XMP_OptionBits kTrivial_HandlerFlags = ( kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_AllowsSafeUpdate );
+
+class Trivial_MetaHandler : public XMPFileHandler
+{
+public:
+
+ Trivial_MetaHandler() {};
+ ~Trivial_MetaHandler();
+
+ virtual void CacheFileData() = 0;
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+}; // Trivial_MetaHandler
+
+// =================================================================================================
+
+#endif /* __Trivial_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/UCF_Handler.cpp b/XMPFiles/source/FileHandlers/UCF_Handler.cpp
new file mode 100644
index 0000000..c9f63b9
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/UCF_Handler.cpp
@@ -0,0 +1,856 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/UCF_Handler.hpp"
+
+#include "third-party/zlib/zlib.h"
+
+#include <time.h>
+
+#ifdef DYNAMIC_CRC_TABLE
+ #error "unexpectedly DYNAMIC_CRC_TABLE defined."
+ //Must implement get_crc_table prior to any multi-threading (see notes there)
+#endif
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+using namespace std;
+
+// =================================================================================================
+/// \file UCF_Handler.cpp
+/// \brief UCF handler class
+// =================================================================================================
+const XMP_Uns16 xmpFilenameLen = 21;
+const char* xmpFilename = "META-INF/metadata.xml";
+
+// =================================================================================================
+// UCF_MetaHandlerCTor
+// ====================
+XMPFileHandler* UCF_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new UCF_MetaHandler ( parent );
+} // UCF_MetaHandlerCTor
+
+// =================================================================================================
+// UCF_CheckFormat
+// ================
+// * lenght must at least be 114 bytes
+// * first bytes must be \x50\x4B\x03\x04 for *any* zip file
+// * at offset 30 it must spell "mimetype"
+
+#define MIN_UCF_LENGTH 114
+// zip minimum considerations:
+// the shortest legal zip is 100 byte:
+// 30+1* bytes file header
+//+ 0 byte content file (uncompressed)
+//+ 46+1* bytes central directory file header
+//+ 22 byte end of central directory record
+//-------
+//100 bytes
+//
+//1 byte is the shortest legal filename. anything below is no valid zip.
+//
+//==> the mandatory+first "mimetype" content file has a filename length of 8 bytes,
+// thus even if empty (arguably incorrect but tolerable),
+// the shortest legal UCF is 114 bytes (30 + 8 + 0 + 46 + 8 + 22 )
+// anything below is with certainty not a valid ucf.
+
+bool UCF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent )
+{
+ // *not* using buffer functionality here, all we need
+ // to detect UCF securely is in the first 38 bytes...
+ IgnoreParam(filePath); IgnoreParam(parent); //suppress warnings
+ XMP_Assert ( format == kXMP_UCFFile ); //standard assert
+
+ XMP_Uns8 buffer[MIN_UCF_LENGTH];
+
+ fileRef->Rewind();
+ if ( MIN_UCF_LENGTH != fileRef->Read ( buffer, MIN_UCF_LENGTH) ) //NO requireall (->no throw), just return false
+ return false;
+ if ( !CheckBytes ( &buffer[0], "\x50\x4B\x03\x04", 4 ) ) // "PK 03 04"
+ return false;
+ // UCF spec says: there must be a content file mimetype, and be first and be uncompressed...
+ if ( !CheckBytes ( &buffer[30], "mimetype", 8 ) )
+ return false;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //figure out mimetype, decide on writeability
+ // grab mimetype
+ fileRef->Seek ( 18, kXMP_SeekFromStart );
+ XMP_Uns32 mimeLength = XIO::ReadUns32_LE ( fileRef );
+ XMP_Uns32 mimeCompressedLength = XIO::ReadUns32_LE ( fileRef ); // must be same since uncompressed
+
+ XMP_Validate( mimeLength == mimeCompressedLength,
+ "mimetype compressed and uncompressed length differ",
+ kXMPErr_BadFileFormat );
+
+ XMP_Validate( mimeLength != 0, "0-byte mimetype", kXMPErr_BadFileFormat );
+
+ // determine writability based on mimetype
+ fileRef->Seek ( 30 + 8, kXMP_SeekFromStart );
+ char* mimetype = new char[ mimeLength + 1 ];
+ fileRef->ReadAll ( mimetype, mimeLength );
+ mimetype[mimeLength] = '\0';
+
+ bool okMimetype;
+
+ // be lenient on extraneous CR (0xA) [non-XMP bug #16980028]
+ if ( mimeLength > 0 ) //avoid potential crash (will properly fail below anyhow)
+ if ( mimetype[mimeLength-1] == 0xA )
+ mimetype[mimeLength-1] = '\0';
+
+ if (
+ XMP_LitMatch( mimetype, "application/vnd.adobe.xfl" ) || //Flash Diesel team
+ XMP_LitMatch( mimetype, "application/vnd.adobe.xfl+zip") || //Flash Diesel team
+ XMP_LitMatch( mimetype, "application/vnd.adobe.x-mars" ) || //Mars plugin(labs only), Acrobat8
+ XMP_LitMatch( mimetype, "application/vnd.adobe.pdfxml" ) || //Mars plugin(labs only), Acrobat 9
+ XMP_LitMatch( mimetype, "vnd.adobe.x-asnd" ) || //Adobe Sound Document (Soundbooth Team)
+ XMP_LitMatch( mimetype, "application/vnd.adobe.indesign-idml-package" ) || //inCopy (inDesign) IDML Document
+ XMP_LitMatch( mimetype, "application/vnd.adobe.incopy-package" ) || // InDesign Document
+ XMP_LitMatch( mimetype, "application/vnd.adobe.indesign-package" ) || // InDesign Document
+
+ false ) // "sentinel"
+
+ // *** ==> unknown are also treated as not acceptable
+ okMimetype = true;
+ else
+ okMimetype = false;
+
+ // not accepted (neither read nor write
+ //.air - Adobe Air Files
+ //application/vnd.adobe.air-application-installer-package+zip
+ //.airi - temporary Adobe Air Files
+ //application/vnd.adobe.air-application-intermediate-package+zip
+
+ delete [] mimetype;
+ return okMimetype;
+
+} // UCF_CheckFormat
+
+// =================================================================================================
+// UCF_MetaHandler::UCF_MetaHandler
+// ==================================
+
+UCF_MetaHandler::UCF_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kUCF_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+} // UCF_MetaHandler::UCF_MetaHandler
+
+// =================================================================================================
+// UCF_MetaHandler::~UCF_MetaHandler
+// =====================================
+
+UCF_MetaHandler::~UCF_MetaHandler()
+{
+ // nothing
+}
+
+// =================================================================================================
+// UCF_MetaHandler::CacheFileData
+// ===============================
+//
+void UCF_MetaHandler::CacheFileData()
+{
+ //*** abort procedures
+ this->containsXMP = false; //assume no XMP for now (beware of exceptions...)
+ XMP_IO* file = this->parent->ioRef;
+ XMP_PacketInfo &packetInfo = this->packetInfo;
+
+ // clear file positioning info ---------------------------------------------------
+ b=0;b2=0;x=0;x2=0;cd=0;cd2=0;cdx=0;cdx2=0;h=0;h2=0,fl=0;f2l=0;
+ al=0;bl=0;xl=0;x2l=0;cdl=0;cd2l=0;cdxl=0;cdx2l=0;hl=0,z=0,z2=0,z2l=0;
+ numCF=0;numCF2=0;
+ wasCompressed = false;
+
+ // -------------------------------------------------------------------------------
+ fl=file ->Length();
+ if ( fl < MIN_UCF_LENGTH ) XMP_Throw("file too short, can't be correct UCF",kXMPErr_Unimplemented);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // find central directory before optional comment
+ // things have to go bottom-up, since description headers are allowed in UCF
+ // "scan backwards" until feasible field found (plus sig sanity check)
+ // OS buffering should be smart enough, so not doing anything on top
+ // plus almost all comments will be zero or rather short
+
+ //no need to check anything but the 21 chars of "METADATA-INF/metadata.xml"
+ char filenameToTest[22];
+ filenameToTest[21]='\0';
+
+ XMP_Int32 zipCommentLen = 0;
+ for ( ; zipCommentLen <= EndOfDirectory::COMMENT_MAX; zipCommentLen++ )
+ {
+ file->Seek ( -zipCommentLen -2, kXMP_SeekFromEnd );
+ if ( XIO::ReadUns16_LE( file ) == zipCommentLen ) //found it?
+ {
+ //double check, might just look like comment length (actually be 'evil' comment)
+ file ->Seek ( - EndOfDirectory::FIXED_SIZE, kXMP_SeekFromCurrent );
+ if ( XIO::ReadUns32_LE( file ) == EndOfDirectory::ID ) break; //heureka, directory ID
+ // 'else': pretend nothing happended, just go on
+ }
+ }
+ //was it a break or just not found ?
+ if ( zipCommentLen > EndOfDirectory::COMMENT_MAX ) XMP_Throw( "zip broken near end or invalid comment" , kXMPErr_BadFileFormat );
+
+ ////////////////////////////////////////////////////////////////////////////
+ //read central directory
+ hl = zipCommentLen + EndOfDirectory::FIXED_SIZE;
+ h = fl - hl;
+ file ->Seek ( h , kXMP_SeekFromStart );
+
+ if ( XIO::ReadUns32_LE( file ) != EndOfDirectory::ID )
+ XMP_Throw("directory header id not found. or broken comment",kXMPErr_BadFileFormat);
+ if ( XIO::ReadUns16_LE( file ) != 0 )
+ XMP_Throw("UCF must be 'first' zip volume",kXMPErr_BadFileFormat);
+ if ( XIO::ReadUns16_LE( file ) != 0 )
+ XMP_Throw("UCF must be single-volume zip",kXMPErr_BadFileFormat);
+
+ numCF = XIO::ReadUns16_LE( file ); //number of content files
+ if ( numCF != XIO::ReadUns16_LE( file ) )
+ XMP_Throw( "per volume and total number of dirs differ" , kXMPErr_BadFileFormat );
+ cdl = XIO::ReadUns32_LE( file );
+ cd = XIO::ReadUns32_LE( file );
+ file->Seek ( 2, kXMP_SeekFromCurrent ); //skip comment len, needed since next LFA is kXMP_SeekFromCurrent !
+
+ //////////////////////////////////////////////////////////////////////////////
+ // check for zip64-end-of-CD-locator/ zip64-end-of-CD
+ // to to central directory
+ if ( cd == 0xffffffff )
+ { // deal with zip 64, otherwise continue
+ XMP_Int64 tmp = file->Seek ( -(EndOfDirectory::FIXED_SIZE + Zip64Locator::TOTAL_SIZE),
+ kXMP_SeekFromCurrent ); //go to begining of zip64 locator
+ //relative movement , absolute would imho only require another -zipCommentLen
+
+ if ( Zip64Locator::ID == XIO::ReadUns32_LE(file) ) // prevent 'coincidental length' ffffffff
+ {
+ XMP_Validate( 0 == XIO::ReadUns32_LE(file),
+ "zip64 CD disk must be 0", kXMPErr_BadFileFormat );
+
+ z = XIO::ReadUns64_LE(file);
+ XMP_Validate( z < 0xffffffffffffLL, "file in terrabyte range?", kXMPErr_BadFileFormat ); // 3* ffff, sanity test
+
+ XMP_Uns32 totalNumOfDisks = XIO::ReadUns32_LE(file);
+ /* tolerated while pkglib bug #1742179 */
+ XMP_Validate( totalNumOfDisks == 0 || totalNumOfDisks == 1,
+ "zip64 total num of disks must be 0", kXMPErr_BadFileFormat );
+
+ ///////////////////////////////////////////////
+ /// on to end-of-CD itself
+ file->Seek ( z, kXMP_SeekFromStart );
+ XMP_Validate( Zip64EndOfDirectory::ID == XIO::ReadUns32_LE(file),
+ "invalid zip64 end of CD sig", kXMPErr_BadFileFormat );
+
+ XMP_Int64 sizeOfZip64EOD = XIO::ReadUns64_LE(file);
+ file->Seek ( 12, kXMP_SeekFromCurrent );
+ //yes twice "total" and "per disk"
+ XMP_Int64 tmp64 = XIO::ReadUns64_LE(file);
+ XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (1)", kXMPErr_BadFileFormat );
+ tmp64 = XIO::ReadUns64_LE(file);
+ XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (2)", kXMPErr_BadFileFormat );
+ // cd length verification
+ tmp64 = XIO::ReadUns64_LE(file);
+ XMP_Validate( tmp64 == cdl, "CD length differs in zip64", kXMPErr_BadFileFormat );
+
+ cd = XIO::ReadUns64_LE(file); // wipe out invalid 0xffffffff with the real thing
+ //ignoring "extensible data sector (would need fullLength - fixed length) for now
+ }
+ } // of zip64 fork
+ /////////////////////////////////////////////////////////////////////////////
+ // parse central directory
+ // 'foundXMP' <=> cdx != 0
+
+ file->Seek ( cd, kXMP_SeekFromStart );
+ XMP_Int64 cdx_suspect=0;
+ XMP_Int64 cdxl_suspect=0;
+ CDFileHeader curCDHeader;
+
+ for ( XMP_Uns16 entryNum=1 ; entryNum <= numCF ; entryNum++ )
+ {
+ cdx_suspect = file->Offset(); //just suspect for now
+ curCDHeader.read( file );
+
+ if ( GetUns32LE( &curCDHeader.fields[CDFileHeader::o_sig] ) != 0x02014b50 )
+ XMP_Throw("&invalid file header",kXMPErr_BadFileFormat);
+
+ cdxl_suspect = curCDHeader.FIXED_SIZE +
+ GetUns16LE(&curCDHeader.fields[CDFileHeader::o_fileNameLength]) +
+ GetUns16LE(&curCDHeader.fields[CDFileHeader::o_extraFieldLength]) +
+ GetUns16LE(&curCDHeader.fields[CDFileHeader::o_commentLength]);
+
+ // we only look 21 characters, that's META-INF/metadata.xml, no \0 attached
+ if ( curCDHeader.filenameLen == xmpFilenameLen /*21*/ )
+ if( XMP_LitNMatch( curCDHeader.filename , "META-INF/metadata.xml", 21 ) )
+ {
+ cdx = cdx_suspect;
+ cdxl = cdxl_suspect;
+ break;
+ }
+ //hop to next
+ file->Seek ( cdx_suspect + cdxl_suspect , kXMP_SeekFromStart );
+ } //for-loop, iterating *all* central directory headers (also beyond found)
+
+ if ( !cdx ) // not found xmp
+ {
+ // b and bl remain 0, x and xl remain 0
+ // ==> a is everything before directory
+ al = cd;
+ return;
+ }
+
+ // from here is if-found-only
+ //////////////////////////////////////////////////////////////////////////////
+ //CD values needed, most serve counter-validation purposes (below) only
+ // read whole object (incl. all 3 fields) again properly
+ // to get extra Fields, etc
+ file->Seek ( cdx, kXMP_SeekFromStart );
+ xmpCDHeader.read( file );
+
+ XMP_Validate( xmpFilenameLen == GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_fileNameLength]),
+ "content file length not ok", kXMPErr_BadFileFormat );
+
+ XMP_Uns16 CD_compression = GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_compression] );
+ XMP_Validate(( CD_compression == 0 || CD_compression == 0x08),
+ "illegal compression, must be flate or none", kXMPErr_BadFileFormat );
+ XMP_Uns16 CD_flags = GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_flags] );
+ XMP_Uns32 CD_crc = GetUns32LE( &xmpCDHeader.fields[CDFileHeader::o_crc32] );
+
+ // parse (actual, non-CD!) file header ////////////////////////////////////////////////
+ x = xmpCDHeader.offsetLocalHeader;
+ file ->Seek ( x , kXMP_SeekFromStart );
+ xmpFileHeader.read( file );
+ xl = xmpFileHeader.sizeHeader() + xmpCDHeader.sizeCompressed;
+
+ //values needed
+ XMP_Uns16 fileNameLength = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_fileNameLength] );
+ XMP_Uns16 extraFieldLength = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_extraFieldLength] );
+ XMP_Uns16 compression = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_compression] );
+ XMP_Uns32 sig = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sig] );
+ XMP_Uns16 flags = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_flags] );
+ XMP_Uns32 sizeCompressed = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeCompressed] );
+ XMP_Uns32 sizeUncompressed = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] );
+ XMP_Uns32 crc = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] );
+
+ // check filename
+ XMP_Validate( fileNameLength == 21, "filename size contradiction" , kXMPErr_BadFileFormat );
+ XMP_Enforce ( xmpFileHeader.filename != 0 );
+ XMP_Validate( !memcmp( "META-INF/metadata.xml", xmpFileHeader.filename , xmpFilenameLen ) , "filename is cf header is not META-INF/metadata.xml" , kXMPErr_BadFileFormat );
+
+ // deal with data descriptor if needed
+ if ( flags & FileHeader::kdataDescriptorFlag )
+ {
+ if ( sizeCompressed!=0 || sizeUncompressed!=0 || crc!=0 ) XMP_Throw("data descriptor must mean 3x zero",kXMPErr_BadFileFormat);
+ file->Seek ( xmpCDHeader.sizeCompressed + fileNameLength + xmpCDHeader.extraFieldLen, kXMP_SeekFromCurrent ); //skip actual data to get to descriptor
+ crc = XIO::ReadUns32_LE( file );
+ if ( crc == 0x08074b50 ) //data descriptor may or may not have signature (see spec)
+ {
+ crc = XIO::ReadUns32_LE( file ); //if it does, re-read
+ }
+ sizeCompressed = XIO::ReadUns32_LE( file );
+ sizeUncompressed = XIO::ReadUns32_LE( file );
+ // *** cater for zip64 plus 'streamed' data-descriptor stuff
+ }
+
+ // more integrity checks (post data descriptor handling)
+ if ( sig != 0x04034b50 ) XMP_Throw("invalid content file header",kXMPErr_BadFileFormat);
+ if ( compression != CD_compression ) XMP_Throw("compression contradiction",kXMPErr_BadFileFormat);
+ if ( sizeUncompressed != xmpCDHeader.sizeUncompressed ) XMP_Throw("contradicting uncompressed lengths",kXMPErr_BadFileFormat);
+ if ( sizeCompressed != xmpCDHeader.sizeCompressed ) XMP_Throw("contradicting compressed lengths",kXMPErr_BadFileFormat);
+ if ( sizeUncompressed == 0 ) XMP_Throw("0-byte uncompressed size", kXMPErr_BadFileFormat );
+
+ ////////////////////////////////////////////////////////////////////
+ // packet Info
+ this->packetInfo.charForm = stdCharForm;
+ this->packetInfo.writeable = false;
+ this->packetInfo.offset = kXMPFiles_UnknownOffset; // checksum!, hide position to not give funny ideas
+ this->packetInfo.length = kXMPFiles_UnknownLength;
+
+ ////////////////////////////////////////////////////////////////////
+ // prepare packet (compressed or not)
+ this->xmpPacket.erase();
+ this->xmpPacket.reserve( sizeUncompressed );
+ this->xmpPacket.append( sizeUncompressed, ' ' );
+ XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() ); // only set after reserving the space!
+
+ // go to packet offset
+ file->Seek ( x + xmpFileHeader.FIXED_SIZE + fileNameLength + extraFieldLength , kXMP_SeekFromStart);
+
+ // compression fork --------------------------------------------------
+ switch (compression)
+ {
+ case 0x8: // FLATE
+ {
+ wasCompressed = true;
+ XMP_Uns32 bytesRead = 0;
+ XMP_Uns32 bytesWritten = 0; // for writing into packetString
+ const unsigned int CHUNK = 16384;
+
+ int ret;
+ unsigned int have; //added type
+ z_stream strm;
+ unsigned char in[CHUNK];
+ unsigned char out[CHUNK];
+ // does need this intermediate stage, no direct compressio to packetStr possible,
+ // since also partially filled buffers must be picked up. That's how it works.
+ // in addition: internal zlib variables might have 16 bit limits...
+
+ /* allocate inflate state */
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = 0;
+ strm.next_in = Z_NULL;
+
+ /* must use windowBits = -15, for raw inflate, no zlib header */
+ ret = inflateInit2(&strm,-MAX_WBITS);
+
+ if (ret != Z_OK)
+ XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
+
+ /* decompress until deflate stream ends or end of file */
+ do {
+ // must take care here not to read too much, thus whichever is smaller:
+ XMP_Int32 bytesRemaining = sizeCompressed - bytesRead;
+ if ( (XMP_Int32)CHUNK < bytesRemaining ) bytesRemaining = (XMP_Int32)CHUNK;
+ strm.avail_in=file ->ReadAll ( in , bytesRemaining );
+ bytesRead += strm.avail_in; // NB: avail_in is "unsigned_int", so might be 16 bit (not harmfull)
+
+ if (strm.avail_in == 0) break;
+ strm.next_in = in;
+
+ do {
+ strm.avail_out = CHUNK;
+ strm.next_out = out;
+ ret = inflate(&strm, Z_NO_FLUSH);
+ XMP_Assert( ret != Z_STREAM_ERROR ); /* state not clobbered */
+ switch (ret)
+ {
+ case Z_NEED_DICT:
+ (void)inflateEnd(&strm);
+ XMP_Throw("zlib error: Z_NEED_DICT",kXMPErr_ExternalFailure);
+ case Z_DATA_ERROR:
+ (void)inflateEnd(&strm);
+ XMP_Throw("zlib error: Z_DATA_ERROR",kXMPErr_ExternalFailure);
+ case Z_MEM_ERROR:
+ (void)inflateEnd(&strm);
+ XMP_Throw("zlib error: Z_MEM_ERROR",kXMPErr_ExternalFailure);
+ }
+
+ have = CHUNK - strm.avail_out;
+ memcpy( (unsigned char*) packetStr + bytesWritten , out , have );
+ bytesWritten += have;
+
+ } while (strm.avail_out == 0);
+
+ /* it's done when inflate() says it's done */
+ } while (ret != Z_STREAM_END);
+
+ /* clean up and return */
+ (void)inflateEnd(&strm);
+ if (ret != Z_STREAM_END)
+ XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
+ break;
+ }
+ case 0x0: // no compression - read directly into the right place
+ {
+ wasCompressed = false;
+ XMP_Enforce( file->ReadAll ( (char*)packetStr, sizeUncompressed ) );
+ break;
+ }
+ default:
+ {
+ XMP_Throw("illegal zip compression method (not none, not flate)",kXMPErr_BadFileFormat);
+ }
+ }
+ this->containsXMP = true; // do this last, after all possible failure/execptions
+}
+
+
+// =================================================================================================
+// UCF_MetaHandler::ProcessXMP
+// ============================
+
+void UCF_MetaHandler::ProcessXMP()
+{
+ // we have no legacy, CacheFileData did all that was needed
+ // ==> default implementation is fine
+ XMPFileHandler::ProcessXMP();
+}
+
+// =================================================================================================
+// UCF_MetaHandler::UpdateFile
+// =============================
+
+// TODO: xmp packet with data descriptor
+
+void UCF_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ //sanity
+ XMP_Enforce( (x!=0) == (cdx!=0) );
+ if (!cdx)
+ xmpCDHeader.setXMPFilename(); //if new, set filename (impacts length, thus before computation)
+ if ( ! this->needsUpdate )
+ return;
+
+ // ***
+ if ( doSafeUpdate ) XMP_Throw ( "UCF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
+
+ XMP_IO* file = this->parent->ioRef;
+
+ // final may mean compressed or not, whatever is to-be-embedded
+ uncomprPacketStr = xmpPacket.c_str();
+ uncomprPacketLen = (XMP_StringLen) xmpPacket.size();
+ finalPacketStr = uncomprPacketStr; // will be overriden if compressedXMP==true
+ finalPacketLen = uncomprPacketLen;
+ std::string compressedPacket; // moot if non-compressed, still here for scope reasons (having to keep a .c_str() alive)
+
+ if ( !x ) // if new XMP...
+ {
+ xmpFileHeader.clear();
+ xmpFileHeader.setXMPFilename();
+ // ZIP64 TODO: extra Fields, impact on cdxl2 and x2l
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // COMPRESSION DECISION
+
+ // for large files compression is bad:
+ // a) size of XMP becomes irrelevant on large files ==> why worry over compression ?
+ // b) more importantly: no such thing as padding possible, compression == ever changing sizes
+ // => never in-place rewrites, *ugly* performance impact on large files
+ inPlacePossible = false; //assume for now
+
+ if ( !x ) // no prior XMP? -> decide on filesize
+ compressXMP = ( fl > 1024*50 /* 100 kB */ ) ? false : true;
+ else
+ compressXMP = wasCompressed; // don't change a thing
+
+ if ( !wasCompressed && !compressXMP &&
+ ( GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] ) == uncomprPacketLen ))
+ {
+ inPlacePossible = true;
+ }
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // COMPRESS XMP
+ if ( compressXMP )
+ {
+ const unsigned int CHUNK = 16384;
+ int ret, flush;
+ unsigned int have;
+ z_stream strm;
+ unsigned char out[CHUNK];
+
+ /* allocate deflate state */
+ strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL;
+ if ( deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8 /*memlevel*/, Z_DEFAULT_STRATEGY) )
+ XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
+
+ //write at once, since we got it in mem anyway:
+ strm.avail_in = uncomprPacketLen;
+ flush = Z_FINISH; // that's all, folks
+ strm.next_in = (unsigned char*) uncomprPacketStr;
+
+ do {
+ strm.avail_out = CHUNK;
+ strm.next_out = out;
+
+ ret = deflate(&strm, flush); /* no bad return value (!=0 acceptable) */
+ XMP_Enforce(ret != Z_STREAM_ERROR); /* state not clobbered */
+ //fwrite(buffer,size,count,file)
+ have = CHUNK - strm.avail_out;
+ compressedPacket.append( (const char*) out, have);
+ } while (strm.avail_out == 0);
+
+ if (ret != Z_STREAM_END)
+ XMP_Throw("zlib stream incomplete ",kXMPErr_ExternalFailure);
+ XMP_Enforce(strm.avail_in == 0); // all input will be used
+ (void)deflateEnd(&strm); //clean up (do prior to checks)
+
+ finalPacketStr = compressedPacket.c_str();
+ finalPacketLen = (XMP_StringLen)compressedPacket.size();
+ }
+
+ PutUns32LE ( uncomprPacketLen, &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] );
+ PutUns32LE ( finalPacketLen, &xmpFileHeader.fields[FileHeader::o_sizeCompressed] );
+ PutUns16LE ( compressXMP ? 8:0, &xmpFileHeader.fields[FileHeader::o_compression] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // CRC (always of uncompressed data)
+ XMP_Uns32 crc = crc32( 0 , (Bytef*)uncomprPacketStr, uncomprPacketLen );
+ PutUns32LE( crc, &xmpFileHeader.fields[FileHeader::o_crc32] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // TIME calculation for timestamp
+ // will be applied both to xmp content file and CD header
+ XMP_Uns16 lastModTime, lastModDate;
+ XMP_DateTime time;
+ SXMPUtils::CurrentDateTime( &time );
+
+ if ( (time.year - 1900) < 80)
+ {
+ lastModTime = 0; // 1.1.1980 00:00h
+ lastModDate = 21;
+ }
+
+ // typedef unsigned short ush; //2 bytes
+ lastModDate = (XMP_Uns16) (((time.year) - 1980 ) << 9 | ((time.month) << 5) | time.day);
+ lastModTime = ((XMP_Uns16)time.hour << 11) | ((XMP_Uns16)time.minute << 5) | ((XMP_Uns16)time.second >> 1);
+
+ PutUns16LE ( lastModDate, &xmpFileHeader.fields[FileHeader::o_lastmodDate] );
+ PutUns16LE ( lastModTime, &xmpFileHeader.fields[FileHeader::o_lastmodTime] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // adjustments depending on 4GB Border,
+ // decisions on in-place update
+ // so far only z, zl have been determined
+
+ // Zip64 related assurances, see (15)
+ XMP_Enforce(!z2);
+ XMP_Enforce(h+hl == fl );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // COMPUTE MISSING VARIABLES
+ // A - based on xmp existence
+ //
+ // already known: x, xl, cd
+ // most left side vars,
+ //
+ // finalPacketStr, finalPacketLen
+
+ if ( x ) // previous xmp?
+ {
+ al = x;
+ b = x + xl;
+ bl = cd - b;
+ }
+ else
+ {
+ al = cd;
+ //b,bl left at zero
+ }
+
+ if ( inPlacePossible )
+ { // leave xmp right after A
+ x2 = al;
+ x2l = xmpFileHeader.sizeTotalCF(); //COULDDO: assert (x2l == xl)
+ if (b) b2 = x2 + x2l; // b follows x as last content part
+ cd2 = b2 + bl; // CD follows B2
+ }
+ else
+ { // move xmp to end
+ if (b) b2 = al; // b follows
+ // x follows as last content part (B existing or not)
+ x2 = al + bl;
+ x2l = xmpFileHeader.sizeTotalCF();
+ cd2 = x2 + x2l; // CD follows X
+ }
+
+ /// create new XMP header ///////////////////////////////////////////////////
+ // written into actual fields + generation of extraField at .write()-time...
+ // however has impact on .size() computation -- thus enter before cdx2l computation
+ xmpCDHeader.sizeUncompressed = uncomprPacketLen;
+ xmpCDHeader.sizeCompressed = finalPacketLen;
+ xmpCDHeader.offsetLocalHeader = x2;
+ PutUns32LE ( crc, &xmpCDHeader.fields[CDFileHeader::o_crc32] );
+ PutUns16LE ( compressXMP ? 8:0, &xmpCDHeader.fields[CDFileHeader::o_compression] );
+ PutUns16LE ( lastModDate, &xmpCDHeader.fields[CDFileHeader::o_lastmodDate] );
+ PutUns16LE ( lastModTime, &xmpCDHeader.fields[CDFileHeader::o_lastmodTime] );
+
+ // for
+ if ( inPlacePossible )
+ {
+ cdx2 = cdx; //same, same
+ writeOut( file, file, false, true );
+ return;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ // temporarily store (those few, small) trailing things that might not survive the move around:
+ file->Seek ( cd, kXMP_SeekFromStart ); // seek to central directory
+ cdEntries.clear(); //mac precaution
+
+ //////////////////////////////////////////////////////////////////////////////
+ // parse headers
+ // * stick together output header list
+ cd2l = 0; //sum up below
+
+ CDFileHeader tempHeader;
+ for( XMP_Uns16 pos=1 ; pos <= numCF ; pos++ )
+ {
+ if ( (cdx) && (file->Offset() == cdx) )
+ {
+ tempHeader.read( file ); //read, even if not use, to advance file pointer
+ }
+ else
+ {
+ tempHeader.read( file );
+ // adjust b2 offset for files that were behind the xmp:
+ // may (if xmp moved to back)
+ // or may not (inPlace Update) make a difference
+ if ( (x) && ( tempHeader.offsetLocalHeader > x) ) // if xmp existed before and this was a file behind it
+ tempHeader.offsetLocalHeader += b2 - b;
+ cd2l += tempHeader.size(); // prior offset change might have impact
+ cdEntries.push_back( tempHeader );
+ }
+ }
+
+ //push in XMP packet as last one (new or not)
+ cdEntries.push_back( xmpCDHeader );
+ cdx2l = xmpCDHeader.size();
+ cd2l += cdx2l; // true, no matter which order
+
+ //OLD cd2l = : cdl - cdxl + cdx2l; // (NB: cdxl might be 0)
+ numCF2 = numCF + ( (cdx)?0:1 ); //xmp packet for the first time? -> add one more CF
+
+ XMP_Validate( numCF2 > 0, "no content files", kXMPErr_BadFileFormat );
+ XMP_Validate( numCF2 <= 0xFFFE, "max number of 0xFFFE entries reached", kXMPErr_BadFileFormat );
+
+ cdx2 = cd2 + cd2l - cdx2l; // xmp content entry comes last (since beyond inPlace Update)
+
+ // zip64 decision
+ if ( ( cd2 + cd2l + hl ) > 0xffffffff ) // predict non-zip size ==> do we need a zip-64?
+ {
+ z2 = cd2 + cd2l;
+ z2l = Zip64EndOfDirectory::FIXED_SIZE + Zip64Locator::TOTAL_SIZE;
+ }
+
+ // header and output length,
+ h2 = cd2 + cd2l + z2l; // (z2l might be 0)
+ f2l = h2 + hl;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // read H (endOfCD), correct offset
+ file->Seek ( h, kXMP_SeekFromStart );
+
+ endOfCD.read( file );
+ if ( cd2 <= 0xffffffff )
+ PutUns32LE( (XMP_Int32) cd2 , &endOfCD.fields[ endOfCD.o_CdOffset ] );
+ else
+ PutUns32LE( 0xffffffff , &endOfCD.fields[ endOfCD.o_CdOffset ] );
+ PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesDisk ] );
+ PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesTotal ] );
+
+ XMP_Enforce( cd2l <= 0xffffffff ); // _size_ of directory itself certainly under 4GB
+ PutUns32LE( (XMP_Uns32)cd2l, &endOfCD.fields[ endOfCD.o_CdSize ] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // MOVING
+ writeOut( file, file, false, false );
+
+ this->needsUpdate = false; //do last for safety reasons
+} // UCF_MetaHandler::UpdateFile
+
+// =================================================================================================
+// UCF_MetaHandler::WriteTempFile
+// ==============================
+void UCF_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ IgnoreParam ( tempRef );
+ XMP_Throw ( "UCF_MetaHandler::WriteTempFile: TO BE IMPLEMENTED", kXMPErr_Unimplemented );
+}
+
+// =================================================================================================
+// own approach to unify Update and WriteFile:
+// ============================
+
+void UCF_MetaHandler::writeOut( XMP_IO* sourceFile, XMP_IO* targetFile, bool isRewrite, bool isInPlace)
+{
+ // isInPlace is only possible when it's not a complete rewrite
+ XMP_Enforce( (!isInPlace) || (!isRewrite) );
+
+ /////////////////////////////////////////////////////////
+ // A
+ if (isRewrite) //move over A block
+ XIO::Move( sourceFile , 0 , targetFile, 0 , al );
+
+ /////////////////////////////////////////////////////////
+ // B / X (not necessarily in this order)
+ if ( !isInPlace ) // B does not change a thing (important optimization)
+ {
+ targetFile ->Seek ( b2 , kXMP_SeekFromStart );
+ XIO::Move( sourceFile , b , targetFile, b2 , bl );
+ }
+
+ targetFile ->Seek ( x2 , kXMP_SeekFromStart );
+ xmpFileHeader.write( targetFile );
+ targetFile->Write ( finalPacketStr, finalPacketLen );
+ //TODO: cover reverse case / inplace ...
+
+ /////////////////////////////////////////////////////////
+ // CD
+ // No Seek here on purpose.
+ // This assert must still be valid
+
+ // if inPlace, the only thing that needs still correction is the CRC in CDX:
+ if ( isInPlace )
+ {
+ XMP_Uns32 crc; //TEMP, not actually needed
+ crc = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] );
+
+ // go there,
+ // do the job (take value directly from (non-CD-)fileheader),
+ // end of story.
+ targetFile ->Seek ( cdx2 + CDFileHeader::o_crc32 , kXMP_SeekFromStart );
+ targetFile->Write ( &xmpFileHeader.fields[FileHeader::o_crc32], 4 );
+
+ return;
+ }
+
+ targetFile ->Seek ( cd2 , kXMP_SeekFromStart );
+
+ std::vector<CDFileHeader>::iterator iter;
+ int tmptmp=1;
+ for( iter = cdEntries.begin(); iter != cdEntries.end(); iter++ ) {
+ CDFileHeader* p=&(*iter);
+ XMP_Int64 before = targetFile->Offset();
+ p->write( targetFile );
+ XMP_Int64 total = targetFile->Offset() - before;
+ XMP_Int64 tmpSize = p->size();
+ tmptmp++;
+ }
+
+ /////////////////////////////////////////////////////////
+ // Z
+ if ( z2 ) // yes, that simple
+ {
+ XMP_Assert( z2 == targetFile->Offset());
+ targetFile ->Seek ( z2 , kXMP_SeekFromStart );
+
+ //no use in copying, always construct from scratch
+ Zip64EndOfDirectory zip64EndOfDirectory( cd2, cd2l, numCF2) ;
+ Zip64Locator zip64Locator( z2 );
+
+ zip64EndOfDirectory.write( targetFile );
+ zip64Locator.write( targetFile );
+ }
+
+ /////////////////////////////////////////////////////////
+ // H
+ XMP_Assert( h2 == targetFile->Offset());
+ endOfCD.write( targetFile );
+
+ XMP_Assert( f2l == targetFile->Offset());
+ if ( f2l< fl)
+ targetFile->Truncate ( f2l ); //file may have shrunk
+}
diff --git a/XMPFiles/source/FileHandlers/UCF_Handler.hpp b/XMPFiles/source/FileHandlers/UCF_Handler.hpp
new file mode 100644
index 0000000..a9b9b55
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/UCF_Handler.hpp
@@ -0,0 +1,722 @@
+#ifndef __UCF_Handler_hpp__
+#define __UCF_Handler_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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+
+// =================================================================================================
+/// \file UCF_Handler.hpp
+//
+// underlying math:
+// __ 0 ______ 0 ______ __
+// | A | | A |
+// | | | |
+// al | | (a2l)| |
+// x |------| b2 |------|
+// xl | X | | B |_
+// b |------| (b2l)| | |
+// | B | x2 |------| | B2 could also be
+// bl | | x2l | X2 | | _after_ X2
+// cd |------| cd2 |------|<'
+// |//CD//| |//CD2/|
+// cdx |------| cdx2 |------|
+// cdxl|------| cdx2l|------|
+// cdl|//////| cd2l|//////|
+// z |------| z2|------|
+// | | | |
+// [zl]| Z | z2l | Z2 |
+// h |------| h2 |------|
+// fl | H | | H2 | f2l
+// __ hl |______| (h2l)|______| __
+//
+// fl file length pre (2 = post)
+// numCf number of content files prior to injection
+// numCf2 " post
+//
+//
+// l length variable, all else offset
+// [ ] variable is not needed
+// ( ) variable is identical to left
+// a content files prior to xmp (possibly: all)
+// b content files behind xmp (possibly: 0)
+// x xmp packet (possibly: 0)
+// cd central directory
+// h end of central directory
+//
+// z zip64 record and locator (if existing)
+//
+// general rules:
+// the bigger A, the less rewrite effort.
+// (also within the CD)
+// putting XMP at the end maximizes A.
+//
+// bool previousXMP == x!=0
+//
+// (x==0) == (cdx==0) == (xl==0) == (cdxl==0)
+//
+// std::vector<XMP_Uns32> cdOffsetsPre;
+//
+// -----------------
+// asserts:
+//( 1) a == a2 == 0, making these variables obsolete
+//( 2) a2l == al, this block is not touched
+//( 3) b2 <= b, b is only moved closer to the beginning of file
+//( 4) b2l == bl, b does not change in size
+//( 5) x2 >= x, b is only moved further down in the file
+//
+//( 6) x != 0, x2l != 0, cd != 0, cdl != 0
+// none of these blocks is at the beginning ('mimetype' by spec),
+// nor is any of them zero byte long
+//( 7) h!=0, hl >= 22 header is not at the beginning, minimum size 22
+//
+// file size computation:
+//( 8) al + bl + xl +cdl +hl = fl
+//( 9) al + bl + x2l+cd2l+hl = fl2
+//
+//(10) ( x==0 ) <=> ( cdx == 0 )
+// if there's a packet in the pre-file, or there isn't
+//(11) (x==0) => xl=0
+//(12) (cdx==0)=> cdx=0
+//
+//(13) x==0 ==> b,bl,b2,b2l==0
+// if there is no pre-xmp, B does not exist
+//(14) x!=0 ==> al:=x, b:=x+xl, bl:=cd-b
+//
+// zip 64:
+//(15) zl and z2l are basically equal, except _one_ of them is 0 :
+//
+//(16) b2l is indeed never different t
+//
+// FIXED_SIZE means the fixed (minimal) portion of a struct
+// TOTAL_SIZE indicates, that this struct indeed has a fixed, known total length
+//
+// =================================================================================================
+
+extern XMPFileHandler* UCF_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool UCF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kUCF_HandlerFlags = (
+ kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ /* kXMPFiles_PrefersInPlace | removed, only reasonable for formats where difference is significant */
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ // *** kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_NeedsReadOnlyPacket //UCF/zip has checksums...
+ );
+
+enum { // data descriptor
+ // may or may not have a signature: 0x08074b50
+ kUCF_DD_crc32 = 0,
+ kUCF_DD_sizeCompressed = 4,
+ kUCF_DD_sizeUncompressed = 8,
+};
+
+class UCF_MetaHandler : public XMPFileHandler
+{
+public:
+ UCF_MetaHandler ( XMPFiles * _parent );
+ ~UCF_MetaHandler();
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+protected:
+ const static XMP_Uns16 xmpFilenameLen = 21;
+ const static char* xmpFilename;
+
+private:
+ class Zip64EndOfDirectory {
+ private:
+
+ public:
+ const static XMP_Uns16 o_sig = 0; // 0x06064b50
+ const static XMP_Uns16 o_size = 4; // of this, excluding leading 12 bytes
+ // == FIXED_SIZE -12, since we're never creating the extensible data sector...
+ const static XMP_Uns16 o_VersionMade = 12;
+ const static XMP_Uns16 o_VersionNeededExtr = 14;
+ const static XMP_Uns16 o_numDisk = 16; // force 0
+ const static XMP_Uns16 o_numCDDisk = 20; // force 0
+ const static XMP_Uns16 o_numCFsThisDisk = 24;
+ const static XMP_Uns16 o_numCFsTotal = 32; // force equal
+ const static XMP_Uns16 o_sizeOfCD = 40; // (regular one, not Z64)
+ const static XMP_Uns16 o_offsetCD = 48; // "
+
+ const static XMP_Int32 FIXED_SIZE = 56;
+ char fields[FIXED_SIZE];
+
+ const static XMP_Uns32 ID = 0x06064b50;
+
+ Zip64EndOfDirectory( XMP_Int64 offsetCD, XMP_Int64 sizeOfCD, XMP_Uns64 numCFs )
+ {
+ memset(fields,'\0',FIXED_SIZE);
+
+ PutUns32LE(ID ,&fields[o_sig] );
+ PutUns64LE(FIXED_SIZE - 12, &fields[o_size] ); //see above
+ PutUns16LE( 45 ,&fields[o_VersionMade] );
+ PutUns16LE( 45 ,&fields[o_VersionNeededExtr] );
+ // fine at 0: o_numDisk
+ // fine at 0: o_numCDDisk
+ PutUns64LE( numCFs, &fields[o_numCFsThisDisk] );
+ PutUns64LE( numCFs, &fields[o_numCFsTotal] );
+ PutUns64LE( sizeOfCD, &fields[o_sizeOfCD] );
+ PutUns64LE( offsetCD, &fields[o_offsetCD] );
+ }
+
+ void write(XMP_IO* file)
+ {
+ XMP_Validate( ID == GetUns32LE( &this->fields[o_sig] ), "invalid header on write", kXMPErr_BadFileFormat );
+ file ->Write ( fields , FIXED_SIZE );
+ }
+
+ };
+
+ class Zip64Locator {
+ public:
+ const static XMP_Uns16 o_sig = 0; // 0x07064b50
+ const static XMP_Uns16 o_numDiskZ64CD = 4; // force 0
+ const static XMP_Uns16 o_offsZ64EOD = 8;
+ const static XMP_Uns16 o_numDisks = 16; // set 1, tolerate 0
+
+ const static XMP_Int32 TOTAL_SIZE = 20;
+ char fields[TOTAL_SIZE];
+
+ const static XMP_Uns32 ID = 0x07064b50;
+
+ Zip64Locator( XMP_Int64 offsetZ64EOD )
+ {
+ memset(fields,'\0',TOTAL_SIZE);
+ PutUns32LE(ID, &fields[Zip64Locator::o_sig] );
+ PutUns32LE(0, &fields[Zip64Locator::o_numDiskZ64CD] );
+ PutUns64LE(offsetZ64EOD, &fields[Zip64Locator::o_offsZ64EOD] );
+ PutUns32LE(1, &fields[Zip64Locator::o_numDisks] );
+ }
+
+ // writes structure to file (starting at current position)
+ void write(XMP_IO* file)
+ {
+ XMP_Validate( ID == GetUns32LE( &this->fields[o_sig] ), "invalid header on write", kXMPErr_BadFileFormat );
+ file ->Write ( fields , TOTAL_SIZE );
+ }
+ };
+
+ struct EndOfDirectory {
+ public:
+ const static XMP_Int32 FIXED_SIZE = 22; //32 bit type is important to not overrun on maxcomment
+ const static XMP_Uns32 ID = 0x06054b50;
+ const static XMP_Int32 COMMENT_MAX = 0xFFFF;
+ //offsets
+ const static XMP_Int32 o_CentralDirectorySize = 12;
+ const static XMP_Int32 o_CentralDirectoryOffset = 16;
+ };
+
+ class FileHeader {
+ private:
+ //TODO intergrate in clear()
+ void release() // avoid terminus free() since subject to a #define (mem-leak-check)
+ {
+ if (filename) delete filename;
+ if (extraField) delete extraField;
+ filename=0;
+ extraField=0;
+ }
+
+ public:
+ const static XMP_Uns32 SIG = 0x04034b50;
+ const static XMP_Uns16 kdataDescriptorFlag = 0x8;
+
+ const static XMP_Uns16 o_sig = 0;
+ const static XMP_Uns16 o_extractVersion = 4;
+ const static XMP_Uns16 o_flags = 6;
+ const static XMP_Uns16 o_compression = 8;
+ const static XMP_Uns16 o_lastmodTime = 10;
+ const static XMP_Uns16 o_lastmodDate = 12;
+ const static XMP_Uns16 o_crc32 = 14;
+ const static XMP_Uns16 o_sizeCompressed = 18;
+ const static XMP_Uns16 o_sizeUncompressed = 22;
+ const static XMP_Uns16 o_fileNameLength = 26;
+ const static XMP_Uns16 o_extraFieldLength = 28;
+ // total 30
+
+ const static int FIXED_SIZE = 30;
+ char fields[FIXED_SIZE];
+
+ char* filename;
+ char* extraField;
+ XMP_Uns16 filenameLen;
+ XMP_Uns16 extraFieldLen;
+
+ void clear()
+ {
+ this->release();
+ memset(fields,'\0',FIXED_SIZE);
+ //arm with minimal default values:
+ PutUns32LE(0x04034b50, &fields[FileHeader::o_sig] );
+ PutUns16LE(0x14, &fields[FileHeader::o_extractVersion] );
+ }
+
+ FileHeader() : filename(0),filenameLen(0),extraField(0),extraFieldLen(0)
+ {
+ clear();
+ };
+
+ // reads entire *FileHeader* structure from file (starting at current position)
+ void read(XMP_IO* file)
+ {
+ this->release();
+
+ file ->ReadAll ( fields , FIXED_SIZE );
+
+ XMP_Uns32 tmp32 = GetUns32LE( &this->fields[FileHeader::o_sig] );
+ XMP_Validate( SIG == tmp32, "invalid header", kXMPErr_BadFileFormat );
+ filenameLen = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] );
+ extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] );
+
+ // nb unlike the CDFileHeader the FileHeader will in practice never have
+ // extra fields. Reasoning: File headers never carry (their own) offsets,
+ // (un)compressed size of XMP will hardly ever reach 4 GB
+
+ if (filenameLen) {
+ filename = new char[filenameLen];
+ file->ReadAll ( filename, filenameLen );
+ }
+ if (extraFieldLen) {
+ extraField = new char[extraFieldLen];
+ file->ReadAll ( extraField, extraFieldLen );
+ // *** NB: this WOULD need parsing for content files that are
+ // compressed or uncompressed >4GB (VERY unlikely for XMP)
+ }
+ }
+
+ // writes structure to file (starting at current position)
+ void write(XMP_IO* file)
+ {
+ XMP_Validate( SIG == GetUns32LE( &this->fields[FileHeader::o_sig] ), "invalid header on write", kXMPErr_BadFileFormat );
+
+ filenameLen = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] );
+ extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] );
+
+ file ->Write ( fields , FIXED_SIZE );
+ if (filenameLen) file->Write ( filename, filenameLen );
+ if (extraFieldLen) file->Write ( extraField, extraFieldLen );
+ }
+
+ void transfer(const FileHeader &orig)
+ {
+ memcpy(fields,orig.fields,FIXED_SIZE);
+ if (orig.extraField)
+ {
+ extraFieldLen=orig.extraFieldLen;
+ extraField = new char[extraFieldLen];
+ memcpy(extraField,orig.extraField,extraFieldLen);
+ }
+ if (orig.filename)
+ {
+ filenameLen=orig.filenameLen;
+ filename = new char[filenameLen];
+ memcpy(filename,orig.filename,filenameLen);
+ }
+ };
+
+ void setXMPFilename()
+ {
+ // only needed for fresh structs, thus enforcing rather than catering to memory issues
+ XMP_Enforce( (filenameLen==0) && (extraFieldLen == 0) );
+ filenameLen = xmpFilenameLen;
+ PutUns16LE(filenameLen, &fields[FileHeader::o_fileNameLength] );
+ filename = new char[xmpFilenameLen];
+ memcpy(filename,"META-INF/metadata.xml",xmpFilenameLen);
+ }
+
+ XMP_Uns32 sizeHeader()
+ {
+ return this->FIXED_SIZE + this->filenameLen + this->extraFieldLen;
+ }
+
+ XMP_Uns32 sizeTotalCF()
+ {
+ //*** not zip64 bit safe yet, use only for non-large xmp packet
+ return this->sizeHeader() + GetUns32LE( &fields[FileHeader::o_sizeCompressed] );
+ }
+
+ ~FileHeader()
+ {
+ this->release();
+ };
+
+ }; //class FileHeader
+
+ ////// yes, this needs an own class
+ ////// offsets must be extracted, added, modified,
+ ////// come&go depending on being >0xffffff
+ ////class extraField {
+ //// private:
+
+
+ class CDFileHeader {
+ private:
+ void release() //*** needed or can go?
+ {
+ if (filename) delete filename;
+ if (extraField) delete extraField;
+ if (comment) delete comment;
+ filename=0; filenameLen=0;
+ extraField=0; extraFieldLen=0;
+ comment=0; commentLen=0;
+ }
+
+ const static XMP_Uns32 SIG = 0x02014b50;
+
+ public:
+ const static XMP_Uns16 o_sig = 0; //0x02014b50
+ const static XMP_Uns16 o_versionMadeBy = 4;
+ const static XMP_Uns16 o_extractVersion = 6;
+ const static XMP_Uns16 o_flags = 8;
+ const static XMP_Uns16 o_compression = 10;
+ const static XMP_Uns16 o_lastmodTime = 12;
+ const static XMP_Uns16 o_lastmodDate = 14;
+ const static XMP_Uns16 o_crc32 = 16;
+ const static XMP_Uns16 o_sizeCompressed = 20; // 16bit stub
+ const static XMP_Uns16 o_sizeUncompressed = 24; // 16bit stub
+ const static XMP_Uns16 o_fileNameLength = 28;
+ const static XMP_Uns16 o_extraFieldLength = 30;
+ const static XMP_Uns16 o_commentLength = 32;
+ const static XMP_Uns16 o_diskNo = 34;
+ const static XMP_Uns16 o_internalAttribs = 36;
+ const static XMP_Uns16 o_externalAttribs = 38;
+ const static XMP_Uns16 o_offsetLocalHeader = 42; // 16bit stub
+ // total size is 4+12+12+10+8=46
+
+ const static int FIXED_SIZE = 46;
+ char fields[FIXED_SIZE];
+
+ // do not bet on any zero-freeness,
+ // certainly no zero termination (pascal strings),
+ // treat as data blocks
+ char* filename;
+ char* extraField;
+ char* comment;
+ XMP_Uns16 filenameLen;
+ XMP_Uns16 extraFieldLen;
+ XMP_Uns16 commentLen;
+
+ // full, real, parsed 64 bit values
+ XMP_Int64 sizeUncompressed;
+ XMP_Int64 sizeCompressed;
+ XMP_Int64 offsetLocalHeader;
+
+ CDFileHeader() : filename(0),extraField(0),comment(0),filenameLen(0),
+ extraFieldLen(0),commentLen(0),sizeUncompressed(0),sizeCompressed(0),offsetLocalHeader(0)
+ {
+ memset(fields,'\0',FIXED_SIZE);
+ //already arm with appropriate values where applicable:
+ PutUns32LE(0x02014b50, &fields[CDFileHeader::o_sig] );
+ PutUns16LE(0x14, &fields[CDFileHeader::o_extractVersion] );
+ };
+
+ // copy constructor
+ CDFileHeader(const CDFileHeader& orig) : filename(0),extraField(0),comment(0),filenameLen(0),
+ extraFieldLen(0),commentLen(0),sizeUncompressed(0),sizeCompressed(0),offsetLocalHeader(0)
+ {
+ memcpy(fields,orig.fields,FIXED_SIZE);
+ if (orig.extraField)
+ {
+ extraFieldLen=orig.extraFieldLen;
+ extraField = new char[extraFieldLen];
+ memcpy(extraField , orig.extraField , extraFieldLen);
+ }
+ if (orig.filename)
+ {
+ filenameLen=orig.filenameLen;
+ filename = new char[filenameLen];
+ memcpy(filename , orig.filename , filenameLen);
+ }
+ if (orig.comment)
+ {
+ commentLen=orig.commentLen;
+ comment = new char[commentLen];
+ memcpy(comment , orig.comment , commentLen);
+ }
+
+ filenameLen = orig.filenameLen;
+ extraFieldLen = orig.extraFieldLen;
+ commentLen = orig.commentLen;
+
+ sizeUncompressed = orig.sizeUncompressed;
+ sizeCompressed = orig.sizeCompressed;
+ offsetLocalHeader = orig.offsetLocalHeader;
+ }
+
+ // Assignment operator
+ CDFileHeader& operator=(const CDFileHeader& obj)
+ {
+ XMP_Throw("not supported",kXMPErr_Unimplemented);
+ }
+
+ // reads entire structure from file (starting at current position)
+ void read(XMP_IO* file)
+ {
+ this->release();
+
+ file->ReadAll ( fields, FIXED_SIZE );
+ XMP_Validate( SIG == GetUns32LE( &this->fields[CDFileHeader::o_sig] ), "invalid header", kXMPErr_BadFileFormat );
+
+ filenameLen = GetUns16LE( &this->fields[CDFileHeader::o_fileNameLength] );
+ extraFieldLen = GetUns16LE( &this->fields[CDFileHeader::o_extraFieldLength] );
+ commentLen = GetUns16LE( &this->fields[CDFileHeader::o_commentLength] );
+
+ if (filenameLen) {
+ filename = new char[filenameLen];
+ file->ReadAll ( filename, filenameLen );
+ }
+ if (extraFieldLen) {
+ extraField = new char[extraFieldLen];
+ file->ReadAll ( extraField, extraFieldLen );
+ }
+ if (commentLen) {
+ comment = new char[commentLen];
+ file->ReadAll ( comment, commentLen );
+ }
+
+ ////// GET ACTUAL 64 BIT VALUES //////////////////////////////////////////////
+ // get 32bit goodies first, correct later
+ sizeUncompressed = GetUns32LE( &fields[o_sizeUncompressed] );
+ sizeCompressed = GetUns32LE( &fields[o_sizeCompressed] );
+ offsetLocalHeader = GetUns32LE( &fields[o_offsetLocalHeader] );
+
+ XMP_Int32 offset = 0;
+ while ( offset < extraFieldLen )
+ {
+ XMP_Validate( (extraFieldLen - offset) >= 4, "need 4 bytes for next header ID+len", kXMPErr_BadFileFormat);
+ XMP_Uns16 headerID = GetUns16LE( &extraField[offset] );
+ XMP_Uns16 dataSize = GetUns16LE( &extraField[offset+2] );
+ offset += 4;
+
+ XMP_Validate( (extraFieldLen - offset) <= dataSize,
+ "actual field lenght not given", kXMPErr_BadFileFormat);
+ if ( headerID == 0x1 ) //we only care about "Zip64 extended information extra field"
+ {
+ XMP_Validate( offset < extraFieldLen, "extra field too short", kXMPErr_BadFileFormat);
+ if (sizeUncompressed == 0xffffffff)
+ {
+ sizeUncompressed = GetUns64LE( &extraField[offset] );
+ offset += 8;
+ }
+ if (sizeCompressed == 0xffffffff)
+ {
+ sizeCompressed = GetUns64LE( &extraField[offset] );
+ offset += 8;
+ }
+ if (offsetLocalHeader == 0xffffffff)
+ {
+ offsetLocalHeader = GetUns64LE( &extraField[offset] );
+ offset += 8;
+ }
+ }
+ else
+ {
+ offset += dataSize;
+ } // if
+ } // while
+ } // read()
+
+ // writes structure to file (starting at current position)
+ void write(XMP_IO* file)
+ {
+ //// WRITE BACK REAL 64 BIT VALUES, CREATE EXTRA FIELD ///////////////
+ //may only wipe extra field after obtaining all Info from it
+ if (extraField) delete extraField;
+ extraFieldLen=0;
+
+ if ( ( sizeUncompressed > 0xffffffff ) ||
+ ( sizeCompressed > 0xffffffff ) ||
+ ( offsetLocalHeader > 0xffffffff ) )
+ {
+ extraField = new char[64]; // actual maxlen is 32
+ extraFieldLen = 4; //first fields are for ID, size
+ if ( sizeUncompressed > 0xffffffff )
+ {
+ PutUns64LE( sizeUncompressed, &extraField[extraFieldLen] );
+ extraFieldLen += 8;
+ sizeUncompressed = 0xffffffff;
+ }
+ if ( sizeCompressed > 0xffffffff )
+ {
+ PutUns64LE( sizeCompressed, &extraField[extraFieldLen] );
+ extraFieldLen += 8;
+ sizeCompressed = 0xffffffff;
+ }
+ if ( offsetLocalHeader > 0xffffffff )
+ {
+ PutUns64LE( offsetLocalHeader, &extraField[extraFieldLen] );
+ extraFieldLen += 8;
+ offsetLocalHeader = 0xffffffff;
+ }
+
+ //write ID, dataSize
+ PutUns16LE( 0x0001, &extraField[0] );
+ PutUns16LE( extraFieldLen-4, &extraField[2] );
+ //extraFieldSize
+ PutUns16LE( extraFieldLen, &this->fields[CDFileHeader::o_extraFieldLength] );
+ }
+
+ // write out 32-bit ('ff-stubs' or not)
+ PutUns32LE( (XMP_Uns32)sizeUncompressed, &fields[o_sizeUncompressed] );
+ PutUns32LE( (XMP_Uns32)sizeCompressed, &fields[o_sizeCompressed] );
+ PutUns32LE( (XMP_Uns32)offsetLocalHeader, &fields[o_offsetLocalHeader] );
+
+ /// WRITE /////////////////////////////////////////////////////////////////
+ XMP_Enforce( SIG == GetUns32LE( &this->fields[CDFileHeader::o_sig] ) );
+
+ file ->Write ( fields , FIXED_SIZE );
+ if (filenameLen) file->Write ( filename , filenameLen );
+ if (extraFieldLen) file->Write ( extraField , extraFieldLen );
+ if (commentLen) file->Write ( extraField , extraFieldLen );
+ }
+
+ void setXMPFilename()
+ {
+ if (filename) delete filename;
+ filenameLen = xmpFilenameLen;
+ filename = new char[xmpFilenameLen];
+ PutUns16LE(filenameLen, &fields[CDFileHeader::o_fileNameLength] );
+ memcpy(filename,"META-INF/metadata.xml",xmpFilenameLen);
+ }
+
+ XMP_Int64 size()
+ {
+ XMP_Int64 r = this->FIXED_SIZE + this->filenameLen + this->commentLen;
+ // predict serialization size
+ if ( (sizeUncompressed > 0xffffffff)||(sizeCompressed > 0xffffffff)||(offsetLocalHeader>0xffffffff) )
+ {
+ r += 4; //extra fields necessary
+ if (sizeUncompressed > 0xffffffff) r += 8;
+ if (sizeCompressed > 0xffffffff) r += 8;
+ if (offsetLocalHeader > 0xffffffff) r += 8;
+ }
+ return r;
+ }
+
+ ~CDFileHeader()
+ {
+ this->release();
+ };
+ }; // class CDFileHeader
+
+ class EndOfCD {
+ private:
+ const static XMP_Uns32 SIG = 0x06054b50;
+ void UCFECD_Free()
+ {
+ if(commentLen) delete comment;
+ commentLen = 0;
+ }
+ public:
+ const static XMP_Int32 o_Sig = 0;
+ const static XMP_Int32 o_CdNumEntriesDisk = 8; // same-same for UCF, since single-volume
+ const static XMP_Int32 o_CdNumEntriesTotal = 10;// must update both
+ const static XMP_Int32 o_CdSize = 12;
+ const static XMP_Int32 o_CdOffset = 16;
+ const static XMP_Int32 o_CommentLen = 20;
+
+ const static int FIXED_SIZE = 22;
+ char fields[FIXED_SIZE];
+
+ char* comment;
+ XMP_Uns16 commentLen;
+
+ EndOfCD() : comment(0), commentLen(0)
+ {
+ //nothing
+ };
+
+ void read (XMP_IO* file)
+ {
+ UCFECD_Free();
+
+ file->ReadAll ( fields, FIXED_SIZE );
+ XMP_Validate( this->SIG == GetUns32LE( &this->fields[o_Sig] ), "invalid header", kXMPErr_BadFileFormat );
+
+ commentLen = GetUns16LE( &this->fields[o_CommentLen] );
+ if(commentLen)
+ {
+ comment = new char[commentLen];
+ file->ReadAll ( comment, commentLen );
+ }
+ };
+
+ void write(XMP_IO* file)
+ {
+ XMP_Enforce( this->SIG == GetUns32LE( &this->fields[o_Sig] ) );
+ commentLen = GetUns16LE( &this->fields[o_CommentLen] );
+ file ->Write ( fields , FIXED_SIZE );
+ if (commentLen)
+ file->Write ( comment, commentLen );
+ }
+
+ ~EndOfCD()
+ {
+ if (comment) delete comment;
+ };
+ }; //class EndOfCD
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // EMBEDDING MATH
+ //
+ // a = content files before xmp (always 0 thus ommited)
+ // b/b2 = content files behind xmp (before/after injection)
+ // x/x2 = offset xmp content header + content file (before/after injection)
+ // cd/cd = central directory
+ // h/h2 = end of central directory record
+ XMP_Int64 b,b2,x,x2,cd,cd2,cdx,cdx2,z,z2,h,h2,
+ // length thereof ('2' only where possibly different)
+ // using XMP_Int64 here also for length (not XMP_Int32),
+ // to be prepared for zip64, our LFA functions might need things in multiple chunks...
+ al,bl,xl,x2l,cdl,cd2l,cdxl,cdx2l,z2l,hl,fl,f2l;
+ XMP_Uns16 numCF,numCF2;
+
+ bool wasCompressed; // ..before, false if no prior xmp
+ bool compressXMP; // compress this time?
+ bool inPlacePossible;
+ /* bool isZip64; <=> z2 != 0 */
+
+ FileHeader xmpFileHeader;
+ CDFileHeader xmpCDHeader;
+
+ XMP_StringPtr uncomprPacketStr;
+ XMP_StringLen uncomprPacketLen;
+ XMP_StringPtr finalPacketStr;
+ XMP_StringLen finalPacketLen;
+ std::vector<CDFileHeader> cdEntries;
+ EndOfCD endOfCD;
+ void writeOut( XMP_IO* sourceFile, XMP_IO* targetFile, bool isRewrite, bool isInPlace);
+
+}; // UCF_MetaHandler
+
+// =================================================================================================
+
+#endif /* __UCF_Handler_hpp__ */
+
+
diff --git a/XMPFiles/source/FileHandlers/WAVE_Handler.cpp b/XMPFiles/source/FileHandlers/WAVE_Handler.cpp
new file mode 100644
index 0000000..5f1b330
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/WAVE_Handler.cpp
@@ -0,0 +1,480 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FileHandlers/WAVE_Handler.hpp"
+#include "XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h"
+#include "XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.h"
+#include "XMPFiles/source/NativeMetadataSupport/MetadataSet.h"
+#include "source/XIO.hpp"
+
+using namespace IFF_RIFF;
+
+// =================================================================================================
+/// \file WAVE_Handler.cpp
+/// \brief File format handler for WAVE.
+// =================================================================================================
+
+
+// =================================================================================================
+// WAVE_MetaHandlerCTor
+// ====================
+
+XMPFileHandler * WAVE_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new WAVE_MetaHandler ( parent );
+}
+
+// =================================================================================================
+// WAVE_CheckFormat
+// ===============
+//
+// Checks if the given file is a valid WAVE file.
+// The first 12 bytes are checked. The first 4 must be "RIFF"
+// Bytes 8 to 12 must be "WAVE"
+
+bool WAVE_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* file,
+ XMPFiles* parent )
+{
+ // Reset file pointer position
+ file->Rewind();
+
+ XMP_Uns8 buffer[12];
+ XMP_Int32 got = file->Read ( buffer, 12 );
+ // Reset file pointer position
+ file->Rewind();
+
+ // Need to have at least ID, size and Type of first chunk
+ if ( got < 12 )
+ {
+ return false;
+ }
+
+ XMP_Uns32 type = WAVE_MetaHandler::whatRIFFFormat( buffer );
+ if ( type != kChunk_RIFF && type != kChunk_RF64 )
+ {
+ return false;
+ }
+
+ const BigEndian& endian = BigEndian::getInstance();
+ if ( endian.getUns32(&buffer[8]) == kType_WAVE )
+ {
+ return true;
+ }
+
+ return false;
+} // WAVE_CheckFormat
+
+
+// =================================================================================================
+// WAVE_MetaHandler::whatRIFFFormat
+// ===============
+
+XMP_Uns32 WAVE_MetaHandler::whatRIFFFormat( XMP_Uns8* buffer )
+{
+ XMP_Uns32 type = 0;
+
+ const BigEndian& endian = BigEndian::getInstance();
+
+ if( buffer != 0 )
+ {
+ if( endian.getUns32( buffer ) == kChunk_RIFF )
+ {
+ type = kChunk_RIFF;
+ }
+ else if( endian.getUns32( buffer ) == kChunk_RF64 )
+ {
+ type = kChunk_RF64;
+ }
+ }
+
+ return type;
+} // whatRIFFFormat
+
+
+// Static inits
+
+// ChunkIdentifier
+// RIFF:WAVE/PMX_
+const ChunkIdentifier WAVE_MetaHandler::kRIFFXMP[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_XMP, kType_NONE} };
+// RIFF:WAVE/LIST:INFO
+const ChunkIdentifier WAVE_MetaHandler::kRIFFInfo[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_LIST, kType_INFO } };
+// RIFF:WAVE/DISP
+const ChunkIdentifier WAVE_MetaHandler::kRIFFDisp[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_DISP, kType_NONE } };
+// RIFF:WAVE/BEXT
+const ChunkIdentifier WAVE_MetaHandler::kRIFFBext[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_bext, kType_NONE } };
+// RIFF:WAVE/cart
+const ChunkIdentifier WAVE_MetaHandler::kRIFFCart[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_cart, kType_NONE } };
+// cr8r is not yet required for WAVE
+// RIFF:WAVE/Cr8r
+// const ChunkIdentifier WAVE_MetaHandler::kWAVECr8r[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_Cr8r, kType_NONE } };
+// RF64:WAVE/PMX_
+const ChunkIdentifier WAVE_MetaHandler::kRF64XMP[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_XMP, kType_NONE} };
+// RF64:WAVE/LIST:INFO
+const ChunkIdentifier WAVE_MetaHandler::kRF64Info[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_LIST, kType_INFO } };
+// RF64:WAVE/DISP
+const ChunkIdentifier WAVE_MetaHandler::kRF64Disp[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_DISP, kType_NONE } };
+// RF64:WAVE/BEXT
+const ChunkIdentifier WAVE_MetaHandler::kRF64Bext[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_bext, kType_NONE } };
+// RF64:WAVE/cart
+const ChunkIdentifier WAVE_MetaHandler::kRF64Cart[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_cart, kType_NONE } };
+// cr8r is not yet required for WAVE
+// RF64:WAVE/Cr8r
+// const ChunkIdentifier WAVE_MetaHandler::kRF64Cr8r[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_Cr8r, kType_NONE } };
+
+// =================================================================================================
+// WAVE_MetaHandler::WAVE_MetaHandler
+// ================================
+
+WAVE_MetaHandler::WAVE_MetaHandler ( XMPFiles * _parent )
+ : mChunkBehavior(NULL), mChunkController(NULL),
+ mINFOMeta(), mBEXTMeta(), mCartMeta(), mDISPMeta(),
+ mXMPChunk(NULL), mINFOChunk(NULL),
+ mBEXTChunk(NULL), mCartChunk(NULL), mDISPChunk(NULL)
+{
+ this->parent = _parent;
+ this->handlerFlags = kWAVE_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ this->mChunkBehavior = new WAVEBehavior();
+ this->mChunkController = new ChunkController( mChunkBehavior, false );
+
+} // WAVE_MetaHandler::WAVE_MetaHandler
+
+
+// =================================================================================================
+// WAVE_MetaHandler::~WAVE_MetaHandler
+// =================================
+
+WAVE_MetaHandler::~WAVE_MetaHandler()
+{
+ if( mChunkController != NULL )
+ {
+ delete mChunkController;
+ }
+
+ if( mChunkBehavior != NULL )
+ {
+ delete mChunkBehavior;
+ }
+} // WAVE_MetaHandler::~WAVE_MetaHandler
+
+
+// =================================================================================================
+// WAVE_MetaHandler::CacheFileData
+// ==============================
+
+void WAVE_MetaHandler::CacheFileData()
+{
+ // Need to determine the file type, need the first four bytes of the file
+
+ // Reset file pointer position
+ this->parent->ioRef->Rewind();
+
+ XMP_Uns8 buffer[4];
+ XMP_Int32 got = this->parent->ioRef->Read ( buffer, 4 );
+ XMP_Assert( got == 4 );
+
+ XMP_Uns32 type = WAVE_MetaHandler::whatRIFFFormat( buffer );
+ XMP_Assert( type == kChunk_RIFF || type == kChunk_RF64 );
+
+ // Reset file pointer position
+ this->parent->ioRef->Rewind();
+
+ // Add the relevant chunk paths for the determined RIFF format
+ if( type == kChunk_RIFF )
+ {
+ mWAVEXMPChunkPath.append( kRIFFXMP, SizeOfCIArray(kRIFFXMP) );
+ mWAVEInfoChunkPath.append( kRIFFInfo, SizeOfCIArray(kRIFFInfo) );
+ mWAVEDispChunkPath.append( kRIFFDisp, SizeOfCIArray(kRIFFDisp) );
+ mWAVEBextChunkPath.append( kRIFFBext, SizeOfCIArray(kRIFFBext) );
+ mWAVECartChunkPath.append( kRIFFCart, SizeOfCIArray(kRIFFCart) );
+ // cr8r is not yet required for WAVE
+ //mWAVECr8rChunkPath.append( kWAVECr8r, SizeOfCIArray(kWAVECr8r) );
+ }
+ else // RF64
+ {
+ mWAVEXMPChunkPath.append( kRF64XMP, SizeOfCIArray(kRF64XMP) );
+ mWAVEInfoChunkPath.append( kRF64Info, SizeOfCIArray(kRF64Info) );
+ mWAVEDispChunkPath.append( kRF64Disp, SizeOfCIArray(kRF64Disp) );
+ mWAVEBextChunkPath.append( kRF64Bext, SizeOfCIArray(kRF64Bext) );
+ mWAVECartChunkPath.append( kRF64Cart, SizeOfCIArray(kRF64Cart) );
+ // cr8r is not yet required for WAVE
+ //mWAVECr8rChunkPath.append( kRF64Cr8r, SizeOfCIArray(kRF64Cr8r) );
+ }
+
+ mChunkController->addChunkPath( mWAVEXMPChunkPath );
+ mChunkController->addChunkPath( mWAVEInfoChunkPath );
+ mChunkController->addChunkPath( mWAVEDispChunkPath );
+ mChunkController->addChunkPath( mWAVEBextChunkPath );
+ mChunkController->addChunkPath( mWAVECartChunkPath );
+ // cr8r is not yet required for WAVE
+ //mChunkController->addChunkPath( mWAVECr8rChunkPath );
+
+ // Parse the given file
+ // Throws exception if the file cannot be parsed
+ mChunkController->parseFile( this->parent->ioRef, &this->parent->openFlags );
+
+ // Retrieve the file type, it must have at least FORM:WAVE
+ std::vector<XMP_Uns32> typeList = mChunkController->getTopLevelTypes();
+
+ // If file is neither WAVE, throw exception
+ XMP_Validate( typeList.at(0) == kType_WAVE , "File is not of type WAVE", kXMPErr_BadFileFormat );
+
+ // Check if the file contains XMP (last if there are duplicates)
+ mXMPChunk = mChunkController->getChunk( mWAVEXMPChunkPath, true );
+
+
+ // Retrieve XMP packet info
+ if( mXMPChunk != NULL )
+ {
+ this->packetInfo.length = static_cast<XMP_Int32>(mXMPChunk->getSize());
+ this->packetInfo.charForm = kXMP_Char8Bit;
+ this->packetInfo.writeable = true;
+
+ // Get actual the XMP packet
+ this->xmpPacket.assign ( mXMPChunk->getString( this->packetInfo.length) );
+
+ // set state
+ this->containsXMP = true;
+ }
+} // WAVE_MetaHandler::CacheFileData
+
+
+// =================================================================================================
+// WAVE_MetaHandler::ProcessXMP
+// ============================
+
+void WAVE_MetaHandler::ProcessXMP()
+{
+ // Must be done only once
+ if ( this->processedXMP )
+ {
+ return;
+ }
+ // Set the status at start, in case something goes wrong in this method
+ this->processedXMP = true;
+
+ // Parse the XMP
+ if ( ! this->xmpPacket.empty() ) {
+
+ XMP_Assert ( this->containsXMP );
+
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+
+ this->containsXMP = true;
+ }
+
+ // Then import native properties
+ MetadataSet metaSet;
+ WAVEReconcile recon;
+
+ // Parse the WAVE metadata object with values
+
+ const XMP_Uns8* buffer = NULL; // temporary buffer
+ XMP_Uns64 size = 0;
+ // Get LIST:INFO legacy chunk
+ mINFOChunk = mChunkController->getChunk( mWAVEInfoChunkPath, true );
+ if( mINFOChunk != NULL )
+ {
+ size = mINFOChunk->getData( &buffer );
+ mINFOMeta.parse( buffer, size );
+ }
+
+ // Parse Bext legacy chunk
+ mBEXTChunk = mChunkController->getChunk( mWAVEBextChunkPath, true );
+ if( mBEXTChunk != NULL )
+ {
+ size = mBEXTChunk->getData( &buffer );
+ mBEXTMeta.parse( buffer, size );
+ }
+
+ // Parse cart legacy chunk
+ mCartChunk = mChunkController->getChunk( mWAVECartChunkPath, true );
+ if( mCartChunk != NULL )
+ {
+ size = mCartChunk->getData( &buffer );
+ mCartMeta.parse( buffer, size );
+ }
+
+ // Parse DISP legacy chunk
+ const std::vector<IChunkData*>& disps = mChunkController->getChunks( mWAVEDispChunkPath );
+
+ if( ! disps.empty() )
+ {
+ for( std::vector<IChunkData*>::const_reverse_iterator iter=disps.rbegin(); iter!=disps.rend(); iter++ )
+ {
+ size = (*iter)->getData( &buffer );
+
+ if( DISPMetadata::isValidDISP( buffer, size ) )
+ {
+ mDISPChunk = (*iter);
+ break;
+ }
+ }
+ }
+
+ if( mDISPChunk != NULL )
+ {
+ size = mDISPChunk->getData( &buffer );
+ mDISPMeta.parse( buffer, size );
+ }
+
+ //cr8r is not yet required for WAVE
+ //// Parse Cr8r legacy chunk
+ //mCr8rChunk = mChunkController->getChunk( mWAVECr8rChunkPath );
+ //if( mCr8rChunk != NULL )
+ //{
+ // size = mCr8rChunk->getData( &buffer );
+ // mCr8rMeta.parse( buffer, size );
+ //}
+
+ // app legacy to the metadata list
+ metaSet.append( &mINFOMeta );
+ metaSet.append( &mBEXTMeta );
+ metaSet.append( &mCartMeta );
+ metaSet.append( &mDISPMeta );
+
+ // cr8r is not yet required for WAVE
+ // metaSet.append( &mCr8rMeta );
+
+ // Do the import
+ if( recon.importToXMP( this->xmpObj, metaSet ) )
+ {
+ // Remember if anything has changed
+ this->containsXMP = true;
+ }
+
+} // WAVE_MetaHandler::ProcessXMP
+
+
+// =================================================================================================
+// RIFF_MetaHandler::UpdateFile
+// ===========================
+
+void WAVE_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) { // If needsUpdate is set then at least the XMP changed.
+ return;
+ }
+
+ if ( doSafeUpdate )
+ {
+ XMP_Throw ( "WAVE_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
+ }
+
+ // Export XMP to legacy chunks. Create/delete them if necessary
+ MetadataSet metaSet;
+ WAVEReconcile recon;
+
+ metaSet.append( &mINFOMeta );
+ metaSet.append( &mBEXTMeta );
+ metaSet.append( &mCartMeta );
+ metaSet.append( &mDISPMeta );
+
+ // cr8r is not yet required for WAVE
+ // metaSet.append( &mCr8rMeta );
+
+ // If anything changes, update/create/delete the legacy chunks
+ if( recon.exportFromXMP( metaSet, this->xmpObj ) )
+ {
+ if ( mINFOMeta.hasChanged( ))
+ {
+ updateLegacyChunk( &mINFOChunk, kChunk_LIST, kType_INFO, mINFOMeta );
+ }
+
+ if ( mBEXTMeta.hasChanged( ))
+ {
+ updateLegacyChunk( &mBEXTChunk, kChunk_bext, kType_NONE, mBEXTMeta );
+ }
+
+ if ( mCartMeta.hasChanged( ))
+ {
+ updateLegacyChunk( &mCartChunk, kChunk_cart, kType_NONE, mCartMeta );
+ }
+
+ if ( mDISPMeta.hasChanged( ))
+ {
+ updateLegacyChunk( &mDISPChunk, kChunk_DISP, kType_NONE, mDISPMeta );
+ }
+
+ //cr8r is not yet required for WAVE
+ //if ( mCr8rMeta.hasChanged( ))
+ //{
+ // updateLegacyChunk( &mCr8rChunk, kChunk_Cr8r, kType_NONE, mCr8rMeta );
+ //}
+ }
+
+ //update/create XMP chunk
+ if( this->containsXMP )
+ {
+ this->xmpObj.SerializeToBuffer ( &(this->xmpPacket) );
+
+ if( mXMPChunk != NULL )
+ {
+ mXMPChunk->setData( reinterpret_cast<const XMP_Uns8 *>(this->xmpPacket.c_str()), this->xmpPacket.length() );
+ }
+ else // create XMP chunk
+ {
+ mXMPChunk = mChunkController->createChunk( kChunk_XMP, kType_NONE );
+ mXMPChunk->setData( reinterpret_cast<const XMP_Uns8 *>(this->xmpPacket.c_str()), this->xmpPacket.length() );
+ mChunkController->insertChunk( mXMPChunk );
+ }
+ }
+ // XMP Packet is never completely removed from the file.
+
+ //write tree back to file
+ mChunkController->writeFile( this->parent->ioRef );
+
+ this->needsUpdate = false; // Make sure this is only called once.
+} // WAVE_MetaHandler::UpdateFile
+
+
+void WAVE_MetaHandler::updateLegacyChunk( IChunkData **chunk, XMP_Uns32 chunkID, XMP_Uns32 chunkType, IMetadata &legacyData )
+{
+ // If there is a legacy value, update/create the appropriate chunk
+ if( ! legacyData.isEmpty() )
+ {
+ XMP_Uns8* buffer = NULL;
+ XMP_Uns64 size = legacyData.serialize( &buffer );
+
+ if( *chunk != NULL )
+ {
+ (*chunk)->setData( buffer, size, false );
+ }
+ else
+ {
+ *chunk = mChunkController->createChunk( chunkID, chunkType );
+ (*chunk)->setData( buffer, size, false );
+ mChunkController->insertChunk( *chunk );
+ }
+
+ delete[] buffer;
+ }
+ else //delete chunk if existing
+ {
+ mChunkController->removeChunk ( *chunk );
+ }
+}//updateLegacyChunk
+
+
+// =================================================================================================
+// RIFF_MetaHandler::WriteFile
+// ==========================
+
+void WAVE_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+ XMP_Throw( "WAVE_MetaHandler::WriteTempFile is not Implemented!", kXMPErr_Unimplemented );
+}//WAVE_MetaHandler::WriteFile
diff --git a/XMPFiles/source/FileHandlers/WAVE_Handler.hpp b/XMPFiles/source/FileHandlers/WAVE_Handler.hpp
new file mode 100644
index 0000000..49f873c
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/WAVE_Handler.hpp
@@ -0,0 +1,172 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef __WAVE_Handler_hpp__
+#define __WAVE_Handler_hpp__ 1
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/IFF/ChunkController.h"
+#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h"
+#include "XMPFiles/source/FormatSupport/IFF/IChunkData.h"
+#include "source/Endian.h"
+#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h"
+#include "XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h"
+#include "XMPFiles/source/FormatSupport/WAVE/CartMetadata.h"
+#include "XMPFiles/source/FormatSupport/WAVE/DISPMetadata.h"
+#include "XMPFiles/source/FormatSupport/WAVE/INFOMetadata.h"
+#include "source/XIO.hpp"
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+using namespace IFF_RIFF;
+
+// =================================================================================================
+/// \file WAV_Handler.hpp
+/// \brief File format handler for AIFF.
+// =================================================================================================
+
+/**
+ * Contructor for the handler.
+ */
+extern XMPFileHandler * WAVE_MetaHandlerCTor ( XMPFiles * parent );
+
+/**
+ * Checks the format of the file, see common code.
+ */
+extern bool WAVE_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO* fileRef,
+ XMPFiles * parent );
+
+/** WAVE does not need kXMPFiles_CanRewrite as we can always use UpdateFile to either do
+ * in-place update or append to the file. */
+static const XMP_OptionBits kWAVE_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_AllowsSafeUpdate
+ );
+
+/**
+ * Main class for the the WAVE file handler.
+ */
+class WAVE_MetaHandler : public XMPFileHandler
+{
+public:
+ WAVE_MetaHandler ( XMPFiles* parent );
+ ~WAVE_MetaHandler();
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ /**
+ * Checks if the first 4 bytes of the given buffer are either type RIFF or RF64
+ * @param buffer a byte buffer that must contain at least 4 bytes and point to the correct byte
+ * @return Either kChunk_RIFF, kChunk_RF64 0 if no type could be determined
+ */
+ static XMP_Uns32 whatRIFFFormat( XMP_Uns8* buffer );
+
+private:
+ /**
+ * Updates/creates/deletes a given legacy chunk depending on the given new legacy value
+ * If the Chunk exists and is not empty, it is updated. If it is empty the
+ * Chunk is removed from the tree. If the Chunk does not exist but a value is given, it is created
+ * and initialized with that value
+ *
+ * @param chunk OUT pointer to the legacy chunk
+ * @param chunkID Id of the Chunk if it needs to be created
+ * @param chunkType Type of the Chunk if it needs to be created
+ * @param legacyData the new legacy metadata object (can be empty)
+ */
+ void updateLegacyChunk( IChunkData **chunk, XMP_Uns32 chunkID, XMP_Uns32 chunkType, IMetadata &legacyData );
+
+
+ /** private standard Ctor, not to be used */
+ WAVE_MetaHandler (): mChunkController(NULL), mChunkBehavior(NULL),
+ mINFOMeta(), mBEXTMeta(), mCartMeta(), mDISPMeta(),
+ mXMPChunk(NULL), mINFOChunk(NULL),
+ mBEXTChunk(NULL), mCartChunk(NULL), mDISPChunk(NULL) {};
+
+ // ----- MEMBERS ----- //
+
+ /** Controls the parsing and writing of the passed stream. */
+ ChunkController *mChunkController;
+ /** Represents the rules how chunks are added, removed or rearranged */
+ IChunkBehavior *mChunkBehavior;
+ /** container for Legacy metadata */
+ INFOMetadata mINFOMeta;
+ BEXTMetadata mBEXTMeta;
+ CartMetadata mCartMeta;
+ DISPMetadata mDISPMeta;
+
+ // cr8r is not yet required for WAVE
+ // Cr8rMetadata mCr8rMeta;
+
+ /** pointer to the XMP chunk */
+ IChunkData *mXMPChunk;
+ /** pointer to legacy chunks */
+ IChunkData *mINFOChunk;
+ IChunkData *mBEXTChunk;
+ IChunkData *mCartChunk;
+ IChunkData *mDISPChunk;
+
+ // cr8r is not yet required for WAVE
+ // IChunkData *mCr8rChunk;
+
+ // ----- CONSTANTS ----- //
+
+ /** Chunk path identifier of interest in WAVE */
+ static const ChunkIdentifier kRIFFXMP[2];
+ static const ChunkIdentifier kRIFFInfo[2];
+ static const ChunkIdentifier kRIFFDisp[2];
+ static const ChunkIdentifier kRIFFBext[2];
+ static const ChunkIdentifier kRIFFCart[2];
+
+ // cr8r is not yet required for WAVE
+ // static const ChunkIdentifier kWAVECr8r[2];
+
+ /** Chunk path identifier of interest in RF64 */
+ static const ChunkIdentifier kRF64XMP[2];
+ static const ChunkIdentifier kRF64Info[2];
+ static const ChunkIdentifier kRF64Disp[2];
+ static const ChunkIdentifier kRF64Bext[2];
+ static const ChunkIdentifier kRF64Cart[2];
+
+ // cr8r is not yet required for WAVE
+ // static const ChunkIdentifier kRF64Cr8r[2];
+
+ /** Path to XMP chunk */
+ ChunkPath mWAVEXMPChunkPath;
+
+ /** Path to INFO chunk */
+ ChunkPath mWAVEInfoChunkPath;
+
+ /** Path to DISP chunk */
+ ChunkPath mWAVEDispChunkPath;
+
+ /** Path to BEXT chunk */
+ ChunkPath mWAVEBextChunkPath;
+
+ /** Path to cart chunk */
+ ChunkPath mWAVECartChunkPath;
+
+ //cr8r is not yet required for WAVE
+ ///** Path to Cr8r chunk */
+ //const ChunkPath mWAVECr8rChunkPath;
+
+}; // WAVE_MetaHandler
+
+// =================================================================================================
+
+#endif /* __WAVE_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp b/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp
new file mode 100644
index 0000000..350ab1f
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp
@@ -0,0 +1,890 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp"
+#include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp"
+#include "third-party/zuid/interfaces/MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file XDCAMEX_Handler.cpp
+/// \brief Folder format handler for XDCAMEX.
+///
+/// This handler is for the XDCAMEX video format.
+///
+/// .../MyMovie/
+/// BPAV/
+/// MEDIAPRO.XML
+/// MEDIAPRO.BUP
+/// CLPR/
+/// 709_001_01/
+/// 709_001_01.SMI
+/// 709_001_01.MP4
+/// 709_001_01M01.XML
+/// 709_001_01R01.BIM
+/// 709_001_01I01.PPN
+/// 709_001_02/
+/// 709_002_01/
+/// 709_003_01/
+/// TAKR/
+/// 709_001/
+/// 709_001.SMI
+/// 709_001M01.XML
+///
+/// The Backup files (.BUP) are optional. No files or directories other than those listed are
+/// allowed in the BPAV directory. The CLPR (clip root) directory may contain only clip directories,
+/// which may only contain the clip files listed. The TAKR (take root) direcory may contail only
+/// take directories, which may only contain take files. The take root directory can be empty.
+/// MEDIPRO.XML contains information on clip and take management.
+///
+/// Each clip directory contains a media file (.MP4), a clip info file (.SMI), a real time metadata
+/// file (.BIM), a non real time metadata file (.XML), and a picture pointer file (.PPN). A take
+/// directory conatins a take info and non real time take metadata files.
+// =================================================================================================
+
+// =================================================================================================
+// XDCAMEX_CheckFormat
+// ===================
+//
+// This version checks for the presence of a top level BPAV directory, and the required files and
+// directories immediately within it. The CLPR and TAKR subfolders are required, as is MEDIAPRO.XML.
+//
+// The state of the string parameters depends on the form of the path passed by the client. If the
+// client passed a logical clip path, like ".../MyMovie/012_3456_01", the parameters are:
+// rootPath - ".../MyMovie"
+// gpName - empty
+// parentName - empty
+// leafName - "012_3456_01"
+// If the client passed a full file path, like ".../MyMovie/BPAV/CLPR/012_3456_01/012_3456_01M01.XML", they are:
+// rootPath - ".../MyMovie/BPAV"
+// gpName - "CLPR"
+// parentName - "012_3456_01"
+// leafName - "012_3456_01M01"
+
+// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has
+// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the
+// ! file exists for a full file path.
+
+// ! Using explicit '/' as a separator when creating paths, it works on Windows.
+
+bool XDCAMEX_CheckFormat ( XMP_FileFormat format,
+ const std::string & _rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & _leafName,
+ XMPFiles * parent )
+{
+ std::string rootPath = _rootPath;
+ std::string clipName = _leafName;
+ std::string grandGPName;
+
+ std::string bpavPath ( rootPath );
+
+ // Do some initial checks on the gpName and parentName.
+
+ if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty.
+
+ if ( gpName.empty() ) {
+
+ // This is the logical clip path case. Make sure .../MyMovie/BPAV/CLPR is a folder.
+ bpavPath += kDirChar; // The rootPath was just ".../MyMovie".
+ bpavPath += "BPAV";
+ if ( Host_IO::GetChildMode ( bpavPath.c_str(), "CLPR" ) != Host_IO::kFMode_IsFolder ) return false;
+
+ } else {
+
+ // This is the explicit file case. Make sure the ancestry is OK, compare using the parent's
+ // length since the file can have a suffix like "M01". Use the leafName as the clipName to
+ // preserve lower case, but truncate to the parent's length to remove any suffix.
+
+ if ( gpName != "CLPR" ) return false;
+ XIO::SplitLeafName ( &rootPath, &grandGPName );
+ MakeUpperCase ( &grandGPName );
+ if ( grandGPName != "BPAV" ) return false;
+
+ if ( ! XMP_LitNMatch ( parentName.c_str(), clipName.c_str(), parentName.size() ) ) {
+ std::string tempName = clipName;
+ MakeUpperCase ( &tempName );
+ if ( ! XMP_LitNMatch ( parentName.c_str(), tempName.c_str(), parentName.size() ) ) return false;
+ }
+
+ clipName.erase ( parentName.size() );
+
+ }
+
+ // Check the rest of the required general structure.
+ if ( Host_IO::GetChildMode ( bpavPath.c_str(), "TAKR" ) != Host_IO::kFMode_IsFolder ) return false;
+ if ( Host_IO::GetChildMode ( bpavPath.c_str(), "MEDIAPRO.XML" ) != Host_IO::kFMode_IsFile ) return false;
+
+ // Make sure the clip's .MP4 and .SMI files exist.
+ std::string tempPath ( bpavPath );
+ tempPath += kDirChar;
+ tempPath += "CLPR";
+ tempPath += kDirChar;
+ tempPath += clipName;
+ tempPath += kDirChar;
+ tempPath += clipName;
+ tempPath += ".MP4";
+ if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false;
+ tempPath.erase ( tempPath.size()-3 );
+ tempPath += "SMI";
+ if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false;
+
+ // And now save the psuedo path for the handler object.
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += clipName;
+ size_t pathLen = tempPath.size() + 1; // Include a terminating nul.
+ parent->tempPtr = malloc ( pathLen );
+ if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory );
+ memcpy ( parent->tempPtr, tempPath.c_str(), pathLen );
+
+ return true;
+
+} // XDCAMEX_CheckFormat
+
+// =================================================================================================
+
+static void* CreatePseudoClipPath ( const std::string & clientPath )
+{
+
+ // Used to create the clip pseudo path when the CheckFormat function is skipped.
+
+ std::string pseudoPath = clientPath;
+
+ size_t pathLen;
+ void* tempPtr = 0;
+
+ if ( Host_IO::Exists ( pseudoPath.c_str() ) ) {
+
+ // The client passed a physical path. The logical clip name is the last folder name, the
+ // parent of the file. This is best since some files have suffixes.
+
+ std::string clipName, ignored;
+
+ XIO::SplitLeafName ( &pseudoPath, &ignored ); // Split the file name.
+ XIO::SplitLeafName ( &pseudoPath, &clipName ); // Use the parent folder name.
+
+ XIO::SplitLeafName ( &pseudoPath, &ignored ); // Remove the 2 intermediate folder levels.
+ XIO::SplitLeafName ( &pseudoPath, &ignored );
+
+ pseudoPath += kDirChar;
+ pseudoPath += clipName;
+
+ }
+
+ pathLen = pseudoPath.size() + 1; // Include a terminating nul.
+ tempPtr = malloc ( pathLen );
+ if ( tempPtr == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory );
+ memcpy ( tempPtr, pseudoPath.c_str(), pathLen );
+
+ return tempPtr;
+
+} // CreatePseudoClipPath
+
+// =================================================================================================
+// XDCAMEX_MetaHandlerCTor
+// =======================
+
+XMPFileHandler * XDCAMEX_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new XDCAMEX_MetaHandler ( parent );
+
+} // XDCAMEX_MetaHandlerCTor
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::XDCAMEX_MetaHandler
+// ========================================
+
+XDCAMEX_MetaHandler::XDCAMEX_MetaHandler ( XMPFiles * _parent ) : expat(0)
+{
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kXDCAMEX_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ // Extract the root path and clip name from tempPtr.
+
+ if ( this->parent->tempPtr == 0 ) {
+ // The CheckFormat call might have been skipped.
+ this->parent->tempPtr = CreatePseudoClipPath ( this->parent->filePath );
+ }
+
+ this->rootPath.assign ( (char*) this->parent->tempPtr );
+ free ( this->parent->tempPtr );
+ this->parent->tempPtr = 0;
+
+ XIO::SplitLeafName ( &this->rootPath, &this->clipName );
+
+} // XDCAMEX_MetaHandler::XDCAMEX_MetaHandler
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler
+// =========================================
+
+XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler()
+{
+
+ this->CleanupLegacyXML();
+ if ( this->parent->tempPtr != 0 ) {
+ free ( this->parent->tempPtr );
+ this->parent->tempPtr = 0;
+ }
+
+} // XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::MakeClipFilePath
+// =====================================
+
+bool XDCAMEX_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ )
+{
+
+ *path = this->rootPath;
+ *path += kDirChar;
+ *path += "BPAV";
+ *path += kDirChar;
+ *path += "CLPR";
+ *path += kDirChar;
+ *path += this->clipName;
+ *path += kDirChar;
+ *path += this->clipName;
+ *path += suffix;
+
+ if ( ! checkFile ) return true;
+ return Host_IO::Exists ( path->c_str() );
+
+} // XDCAMEX_MetaHandler::MakeClipFilePath
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::MakeMediaproPath
+// =====================================
+
+bool XDCAMEX_MetaHandler::MakeMediaproPath ( std::string * path, bool checkFile /* = false */ )
+{
+
+ *path = this->rootPath;
+ *path += kDirChar;
+ *path += "BPAV";
+ *path += kDirChar;
+ *path += "MEDIAPRO.XML";
+
+ if ( ! checkFile ) return true;
+ return Host_IO::Exists ( path->c_str() );
+
+} // XDCAMEX_MetaHandler::MakeMediaproPath
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::MakeLegacyDigest
+// =====================================
+
+// *** Early hack version.
+
+#define kHexDigits "0123456789ABCDEF"
+
+void XDCAMEX_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ digestStr->erase();
+ if ( this->clipMetadata == 0 ) return; // Bail if we don't have any legacy XML.
+ XMP_Assert ( this->expat != 0 );
+
+ XMP_StringPtr xdcNS = this->xdcNS.c_str();
+ XML_NodePtr legacyContext, legacyProp;
+
+ legacyContext = this->clipMetadata->GetNamedElement ( xdcNS, "Access" );
+ if ( legacyContext == 0 ) return;
+
+ MD5_CTX context;
+ unsigned char digestBin [16];
+ MD5Init ( &context );
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "Creator" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "CreationDate" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "LastUpdateDate" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ MD5Final ( digestBin, &context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->append ( buffer );
+
+} // XDCAMEX_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::CleanupLegacyXML
+// =====================================
+
+void XDCAMEX_MetaHandler::CleanupLegacyXML()
+{
+
+ if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; }
+
+ clipMetadata = 0; // ! Was a pointer into the expat tree.
+
+} // XDCAMEX_MetaHandler::CleanupLegacyXML
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::GetFileModDate
+// ===================================
+
+static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) {
+ int compare = SXMPUtils::CompareDateTime ( left, right );
+ return (compare < 0);
+}
+
+bool XDCAMEX_MetaHandler::GetFileModDate ( XMP_DateTime * modDate )
+{
+
+ // The XDCAM EX locations of metadata:
+ // BPAV/
+ // MEDIAPRO.XML // Has non-XMP metadata.
+ // CLPR/
+ // 709_3001_01:
+ // 709_3001_01M01.XML // Has non-XMP metadata.
+ // 709_3001_01M01.XMP
+
+ bool ok, haveDate = false;
+ std::string fullPath;
+ XMP_DateTime oneDate, junkDate;
+ if ( modDate == 0 ) modDate = &junkDate;
+
+ ok = this->MakeMediaproPath ( &fullPath, true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ ok = this->MakeClipFilePath ( &fullPath, "M01.XML", true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ ok = this->MakeClipFilePath ( &fullPath, "M01.XMP", true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ return haveDate;
+
+} // XDCAMEX_MetaHandler::GetFileModDate
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::CacheFileData
+// ==================================
+
+void XDCAMEX_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( ! this->containsXMP );
+
+ if ( this->parent->UsesClientIO() ) {
+ XMP_Throw ( "XDCAMEX cannot be used with client-managed I/O", kXMPErr_InternalFailure );
+ }
+
+ // See if the clip's .XMP file exists.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, "M01.XMP" );
+ if ( Host_IO::GetFileMode ( xmpPath.c_str() ) != Host_IO::kFMode_IsFile ) return; // No XMP.
+
+ // Read the entire .XMP file.
+
+ bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate );
+
+ XMP_Assert ( this->parent->ioRef == 0 );
+ XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), readOnly );
+ if ( xmpFile == 0 ) return; // The open failed.
+ this->parent->ioRef = xmpFile;
+
+ XMP_Int64 xmpLen = xmpFile->Length();
+ if ( xmpLen > 100*1024*1024 ) {
+ XMP_Throw ( "XDCAMEX XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check.
+ }
+
+ this->xmpPacket.erase();
+ this->xmpPacket.append ( (size_t)xmpLen, ' ' );
+
+ XMP_Int32 ioCount = xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen );
+
+ this->packetInfo.offset = 0;
+ this->packetInfo.length = (XMP_Int32)xmpLen;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ this->containsXMP = true;
+
+} // XDCAMEX_MetaHandler::CacheFileData
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::GetTakeDuration
+// ====================================
+
+void XDCAMEX_MetaHandler::GetTakeDuration ( const std::string & takeURI, std::string & duration )
+{
+
+ // Some versions of gcc can't tolerate goto's across declarations.
+ // *** Better yet, avoid this cruft with self-cleaning objects.
+ #define CleanupAndExit \
+ { \
+ if ( expat != 0 ) delete expat; \
+ takeXMLFile.Close(); \
+ return; \
+ }
+
+ duration.clear();
+
+ // Build a directory string to the take .xml file.
+
+ std::string takeDir ( takeURI );
+ takeDir.erase ( 0, 1 ); // Change the leading "//" to "/", then all '/' to kDirChar.
+ if ( kDirChar != '/' ) {
+ for ( size_t i = 0, limit = takeDir.size(); i < limit; ++i ) {
+ if ( takeDir[i] == '/' ) takeDir[i] = kDirChar;
+ }
+ }
+
+ std::string takePath ( this->rootPath );
+ takePath += kDirChar;
+ takePath += "BPAV";
+ takePath += takeDir;
+
+ // Replace .SMI with M01.XML.
+ if ( takePath.size() > 4 ) {
+ takePath.erase ( takePath.size() - 4, 4 );
+ takePath += "M01.XML";
+ }
+
+ // Parse MEDIAPRO.XML
+
+ XML_NodePtr takeRootElem = 0;
+ XML_NodePtr context = 0;
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( takePath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return; // The open failed.
+ XMPFiles_IO takeXMLFile ( hostRef, takePath.c_str(), Host_IO::openReadOnly );
+
+ ExpatAdapter * expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces );
+ if ( this->expat == 0 ) return;
+
+ XMP_Uns8 buffer [64*1024];
+ while ( true ) {
+ XMP_Int32 ioCount = takeXMLFile.Read ( buffer, sizeof(buffer) );
+ if ( ioCount == 0 ) break;
+ expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
+ }
+
+ expat->ParseBuffer ( 0, 0, true ); // End the parse.
+ takeXMLFile.Close();
+
+ // Get the root node of the XML tree.
+
+ XML_Node & mediaproXMLTree = expat->tree;
+ for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) {
+ if ( mediaproXMLTree.content[i]->kind == kElemNode ) {
+ takeRootElem = mediaproXMLTree.content[i];
+ }
+ }
+ if ( takeRootElem == 0 ) CleanupAndExit
+
+ XMP_StringPtr rlName = takeRootElem->name.c_str() + takeRootElem->nsPrefixLen;
+ if ( ! XMP_LitMatch ( rlName, "NonRealTimeMeta" ) ) CleanupAndExit
+
+ // MediaProfile, Contents
+ XMP_StringPtr ns = takeRootElem->ns.c_str();
+ context = takeRootElem->GetNamedElement ( ns, "Duration" );
+ if ( context != 0 ) {
+ XMP_StringPtr durationValue = context->GetAttrValue ( "value" );
+ if ( durationValue != 0 ) duration = durationValue;
+ }
+
+ CleanupAndExit
+ #undef CleanupAndExit
+
+} // XDCAMEX_MetaHandler::GetTakeDuration
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::GetMediaProMetadata
+// ========================================
+
+bool XDCAMEX_MetaHandler::GetMediaProMetadata ( SXMPMeta * xmpObjPtr,
+ const std::string& clipUMID,
+ bool digestFound )
+{
+ // Build a directory string to the MEDIAPRO file.
+
+ std::string mediaproPath;
+ this->MakeMediaproPath ( &mediaproPath );
+ return XDCAM_Support::GetMediaProLegacyMetadata ( xmpObjPtr, clipUMID, mediaproPath, digestFound );
+
+} // XDCAMEX_MetaHandler::GetMediaProMetadata
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::GetTakeUMID
+// ================================
+
+void XDCAMEX_MetaHandler::GetTakeUMID ( const std::string& clipUMID,
+ std::string& takeUMID,
+ std::string& takeXMLURI )
+{
+
+ // Some versions of gcc can't tolerate goto's across declarations.
+ // *** Better yet, avoid this cruft with self-cleaning objects.
+ #define CleanupAndExit \
+ { \
+ if (expat != 0) delete expat; \
+ mediaproXMLFile.Close(); \
+ return; \
+ }
+
+ takeUMID.clear();
+ takeXMLURI.clear();
+
+ // Build a directory string to the MEDIAPRO file.
+
+ std::string mediapropath ( this->rootPath );
+ mediapropath += kDirChar;
+ mediapropath += "BPAV";
+ mediapropath += kDirChar;
+ mediapropath += "MEDIAPRO.XML";
+
+ // Parse MEDIAPRO.XML.
+
+ XML_NodePtr mediaproRootElem = 0;
+ XML_NodePtr contentContext = 0, materialContext = 0;
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( mediapropath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return; // The open failed.
+ XMPFiles_IO mediaproXMLFile ( hostRef, mediapropath.c_str(), Host_IO::openReadOnly );
+
+ ExpatAdapter * expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces );
+ if ( this->expat == 0 ) return;
+
+ XMP_Uns8 buffer [64*1024];
+ while ( true ) {
+ XMP_Int32 ioCount = mediaproXMLFile.Read ( buffer, sizeof(buffer) );
+ if ( ioCount == 0 ) break;
+ expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
+ }
+
+ expat->ParseBuffer ( 0, 0, true ); // End the parse.
+ mediaproXMLFile.Close();
+
+ // Get the root node of the XML tree.
+
+ XML_Node & mediaproXMLTree = expat->tree;
+ for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) {
+ if ( mediaproXMLTree.content[i]->kind == kElemNode ) {
+ mediaproRootElem = mediaproXMLTree.content[i];
+ }
+ }
+
+ if ( mediaproRootElem == 0 ) CleanupAndExit
+ XMP_StringPtr rlName = mediaproRootElem->name.c_str() + mediaproRootElem->nsPrefixLen;
+ if ( ! XMP_LitMatch ( rlName, "MediaProfile" ) ) CleanupAndExit
+
+ // MediaProfile, Contents
+
+ XMP_StringPtr ns = mediaproRootElem->ns.c_str();
+ contentContext = mediaproRootElem->GetNamedElement ( ns, "Contents" );
+
+ if ( contentContext != 0 ) {
+
+ size_t numMaterialElems = contentContext->CountNamedElements ( ns, "Material" );
+
+ for ( size_t i = 0; i < numMaterialElems; ++i ) { // Iterate over Material tags.
+
+ XML_NodePtr materialElement = contentContext->GetNamedElement ( ns, "Material", i );
+ XMP_Assert ( materialElement != 0 );
+
+ XMP_StringPtr umid = materialElement->GetAttrValue ( "umid" );
+ XMP_StringPtr uri = materialElement->GetAttrValue ( "uri" );
+
+ if ( umid == 0 ) umid = "";
+ if ( uri == 0 ) uri = "";
+
+ size_t numComponents = materialElement->CountNamedElements ( ns, "Component" );
+
+ for ( size_t j = 0; j < numComponents; ++j ) {
+
+ XML_NodePtr componentElement = materialElement->GetNamedElement ( ns, "Component", j );
+ XMP_Assert ( componentElement != 0 );
+
+ XMP_StringPtr compUMID = componentElement->GetAttrValue ( "umid" );
+
+ if ( (compUMID != 0) && (compUMID == clipUMID) ) {
+ takeUMID = umid;
+ takeXMLURI = uri;
+ break;
+ }
+
+ }
+
+ if ( ! takeUMID.empty() ) break;
+
+ }
+
+ }
+
+ CleanupAndExit
+ #undef CleanupAndExit
+
+}
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::ProcessXMP
+// ===============================
+
+void XDCAMEX_MetaHandler::ProcessXMP()
+{
+
+ // Some versions of gcc can't tolerate goto's across declarations.
+ // *** Better yet, avoid this cruft with self-cleaning objects.
+ #define CleanupAndExit \
+ { \
+ bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); \
+ if ( ! openForUpdate ) this->CleanupLegacyXML(); \
+ xmlFile.Close(); \
+ return; \
+ }
+
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( this->containsXMP ) {
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ }
+
+ // NonRealTimeMeta -> XMP by schema.
+ std::string thisUMID, takeUMID, takeXMLURI, takeDuration;
+ std::string xmlPath;
+ this->MakeClipFilePath ( &xmlPath, "M01.XML" );
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return; // The open failed.
+ XMPFiles_IO xmlFile ( hostRef, xmlPath.c_str(), Host_IO::openReadOnly );
+
+ this->expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces );
+ if ( this->expat == 0 ) XMP_Throw ( "XDCAMEX_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory );
+
+ XMP_Uns8 buffer [64*1024];
+ while ( true ) {
+ XMP_Int32 ioCount = xmlFile.Read ( buffer, sizeof(buffer) );
+ if ( ioCount == 0 ) break;
+ this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
+ }
+ this->expat->ParseBuffer ( 0, 0, true ); // End the parse.
+
+ xmlFile.Close();
+
+ // The root element should be NonRealTimeMeta in some namespace. Take whatever this file uses.
+
+ XML_Node & xmlTree = this->expat->tree;
+ XML_NodePtr rootElem = 0;
+
+ for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) {
+ if ( xmlTree.content[i]->kind == kElemNode ) rootElem = xmlTree.content[i];
+ }
+
+ if ( rootElem == 0 ) CleanupAndExit
+ XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen;
+ if ( ! XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) CleanupAndExit
+
+ this->legacyNS = rootElem->ns;
+
+ // Check the legacy digest.
+
+ XMP_StringPtr legacyNS = this->legacyNS.c_str();
+ this->clipMetadata = rootElem; // ! Save the NonRealTimeMeta pointer for other use.
+
+ std::string oldDigest, newDigest;
+ bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAMEX", &oldDigest, 0 );
+ if ( digestFound ) {
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) CleanupAndExit
+ }
+
+ // If we get here we need find and import the actual legacy elements using the current namespace.
+ // Either there is no old digest in the XMP, or the digests differ. In the former case keep any
+ // existing XMP, in the latter case take new legacy values.
+ this->containsXMP = XDCAM_Support::GetLegacyMetadata ( &this->xmpObj, rootElem, legacyNS, digestFound, thisUMID );
+
+ // If this clip is part of a take, add the take number to the relation field, and get the
+ // duration from the take metadata.
+ GetTakeUMID ( thisUMID, takeUMID, takeXMLURI );
+
+ // If this clip is part of a take, update the duration to reflect the take duration rather than
+ // the clip duration, and add the take name as a shot name.
+
+ if ( ! takeXMLURI.empty() ) {
+
+ // Update duration. This property already exists from clip legacy metadata.
+ GetTakeDuration ( takeXMLURI, takeDuration );
+ if ( ! takeDuration.empty() ) {
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", takeDuration );
+ containsXMP = true;
+ }
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "shotName" )) ) {
+
+ std::string takeName;
+ XIO::SplitLeafName ( &takeXMLURI, &takeName );
+
+ // Check for the xml suffix, and delete if it exists.
+ size_t pos = takeName.rfind(".SMI");
+ if ( pos != std::string::npos ) {
+
+ takeName.erase ( pos );
+
+ // delete the take number suffix if it exists.
+ if ( takeName.size() > 3 ) {
+
+ size_t suffix = takeName.size() - 3;
+ char c1 = takeName[suffix];
+ char c2 = takeName[suffix+1];
+ char c3 = takeName[suffix+2];
+ if ( ('U' == c1) && ('0' <= c2) && (c2 <= '9') && ('0' <= c3) && (c3 <= '9') ) {
+ takeName.erase ( suffix );
+ }
+
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "shotName", takeName, kXMP_DeleteExisting );
+ containsXMP = true;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( (! takeUMID.empty()) &&
+ (digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "relation" ))) ) {
+ this->xmpObj.DeleteProperty ( kXMP_NS_DC, "relation" );
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, takeUMID );
+ this->containsXMP = true;
+ }
+
+ this->containsXMP |= GetMediaProMetadata ( &this->xmpObj, thisUMID, digestFound );
+
+ CleanupAndExit
+ #undef CleanupAndExit
+
+} // XDCAMEX_MetaHandler::ProcessXMP
+
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::UpdateFile
+// ===============================
+//
+// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here.
+
+void XDCAMEX_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Make sure only called once.
+
+ XMP_Assert ( this->parent->UsesLocalIO() );
+
+ // Update the internal legacy XML tree if we have one, and set the digest in the XMP.
+
+ bool updateLegacyXML = false;
+
+ if ( this->clipMetadata != 0 ) {
+ updateLegacyXML = XDCAM_Support::SetLegacyMetadata ( this->clipMetadata, &this->xmpObj, this->legacyNS.c_str());
+ }
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAMEX", newDigest.c_str(), kXMP_DeleteExisting );
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );
+
+ // -----------------------------------------------------------------------
+ // Update the XMP file first, don't let legacy XML failures block the XMP.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, "M01.XMP" );
+
+ bool haveXMP = Host_IO::Exists ( xmpPath.c_str() );
+ if ( ! haveXMP ) {
+ XMP_Assert ( this->parent->ioRef == 0 );
+ Host_IO::Create ( xmpPath.c_str() );
+ this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), Host_IO::openReadWrite );
+ if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening XDCAMEX XMP file", kXMPErr_ExternalFailure );
+ }
+
+ XMP_IO* xmpFile = this->parent->ioRef;
+ XMP_Assert ( xmpFile != 0 );
+ XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) );
+
+ // --------------------------------------------
+ // Now update the legacy XML file if necessary.
+
+ if ( updateLegacyXML ) {
+
+ std::string legacyXML, xmlPath;
+ this->expat->tree.Serialize ( &legacyXML );
+ this->MakeClipFilePath ( &xmlPath, "M01.XML" );
+
+ bool haveXML = Host_IO::Exists ( xmlPath.c_str() );
+ if ( ! haveXML ) Host_IO::Create ( xmlPath.c_str() );
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadWrite );
+ if ( hostRef == Host_IO::noFileRef ) XMP_Throw ( "Failure opening XDCAMEX legacy XML file", kXMPErr_ExternalFailure );
+ XMPFiles_IO origXML ( hostRef, xmlPath.c_str(), Host_IO::openReadWrite );
+ XIO::ReplaceTextFile ( &origXML, legacyXML, (haveXML & doSafeUpdate) );
+ origXML.Close();
+
+ }
+
+} // XDCAMEX_MetaHandler::UpdateFile
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::WriteTempFile
+// ==================================
+
+void XDCAMEX_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+
+ // ! WriteTempFile is not supposed to be called for handlers that own the file.
+ XMP_Throw ( "XDCAMEX_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure );
+
+} // XDCAMEX_MetaHandler::WriteTempFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp b/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp
new file mode 100644
index 0000000..a8ab89f
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp
@@ -0,0 +1,85 @@
+#ifndef __XDCAMEX_Handler_hpp__
+#define __XDCAMEX_Handler_hpp__ 1
+
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+#include "source/ExpatAdapter.hpp"
+
+// =================================================================================================
+/// \file XDCAMEX_Handler.hpp
+/// \brief Folder format handler for XDCAMEX.
+// =================================================================================================
+
+extern XMPFileHandler * XDCAMEX_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool XDCAMEX_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kXDCAMEX_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_FolderBasedFormat);
+
+class XDCAMEX_MetaHandler : public XMPFileHandler
+{
+public:
+
+ bool GetFileModDate ( XMP_DateTime * modDate );
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files.
+ { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); };
+
+ XDCAMEX_MetaHandler ( XMPFiles * _parent );
+ virtual ~XDCAMEX_MetaHandler();
+
+private:
+
+ XDCAMEX_MetaHandler() : expat(0) {}; // Hidden on purpose.
+
+ bool MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false );
+ bool MakeMediaproPath ( std::string * path, bool checkFile = false );
+ void MakeLegacyDigest ( std::string * digestStr );
+
+ void GetTakeUMID ( const std::string& clipUMID, std::string& takeUMID, std::string& takeXMLURI );
+ void GetTakeDuration ( const std::string& takeUMID, std::string& duration );
+ bool GetMediaProMetadata ( SXMPMeta * xmpObjPtr, const std::string& clipUMID, bool digestFound );
+
+ void CleanupLegacyXML();
+
+ std::string rootPath, clipName, xdcNS, legacyNS, clipUMID;
+
+ ExpatAdapter * expat;
+ XML_Node * clipMetadata;
+
+}; // XDCAMEX_MetaHandler
+
+// =================================================================================================
+
+#endif /* __XDCAMEX_Handler_hpp__ */
diff --git a/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp b/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp
new file mode 100644
index 0000000..fab0925
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp
@@ -0,0 +1,837 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FileHandlers/XDCAM_Handler.hpp"
+#include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp"
+#include "third-party/zuid/interfaces/MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file XDCAM_Handler.cpp
+/// \brief Folder format handler for XDCAM.
+///
+/// This handler is for the XDCAM video format. This is a pseudo-package, visible files but with a very
+/// well-defined layout and naming rules. There are 2 different layouts for XDCAM, called FAM and SAM.
+/// The FAM layout is used by "normal" XDCAM devices. The SAM layout is used by XDCAM-EX devices.
+///
+/// A typical FAM layout looks like (note mixed case for General, Clip, Edit, and Sub folders):
+///
+/// .../MyMovie/
+/// INDEX.XML
+/// DISCMETA.XML
+/// MEDIAPRO.XML
+/// General/
+/// unknown files
+/// Clip/
+/// C0001.MXF
+/// C0001M01.XML
+/// C0001M01.XMP
+/// C0002.MXF
+/// C0002M01.XML
+/// C0002M01.XMP
+/// Sub/
+/// C0001S01.MXF
+/// C0002S01.MXF
+/// Edit/
+/// E0001E01.SMI
+/// E0001M01.XML
+/// E0002E01.SMI
+/// E0002M01.XML
+///
+/// A typical SAM layout looks like:
+///
+/// .../MyMovie/
+/// GENERAL/
+/// unknown files
+/// PROAV/
+/// INDEX.XML
+/// INDEX.BUP
+/// DISCMETA.XML
+/// DISCINFO.XML
+/// DISCINFO.BUP
+/// CLPR/
+/// C0001/
+/// C0001C01.SMI
+/// C0001V01.MXF
+/// C0001A01.MXF
+/// C0001A02.MXF
+/// C0001R01.BIM
+/// C0001I01.PPN
+/// C0001M01.XML
+/// C0001M01.XMP
+/// C0001S01.MXF
+/// C0002/
+/// ...
+/// EDTR/
+/// E0001/
+/// E0001E01.SMI
+/// E0001M01.XML
+/// E0002/
+/// ...
+///
+/// Note that the Sony documentation uses the folder names "General", "Clip", "Sub", and "Edit". We
+/// use all caps here. Common code has already shifted the names, we want to be case insensitive.
+///
+/// From the user's point of view, .../MyMovie contains XDCAM stuff, in this case 2 clips whose raw
+/// names are C0001 and C0002. There may be mapping information for nicer clip names to the raw
+/// names, but that can be ignored for now. Each clip is stored as a collection of files, each file
+/// holding some specific aspect of the clip's data.
+///
+/// The XDCAM handler operates on clips. The path from the client of XMPFiles can be either a logical
+/// clip path, like ".../MyMovie/C0001", or a full path to one of the files. In the latter case the
+/// handler must figure out the intended clip, it must not blindly use the named file.
+///
+/// Once the XDCAM structure and intended clip are identified, the handler only deals with the .XMP
+/// and .XML files in the CLIP or CLPR/<clip> folders. The .XMP file, if present, contains the XMP
+/// for the clip. The .XML file must be present to define the existance of the clip. It contains a
+/// variety of information about the clip, including some legacy metadata.
+///
+// =================================================================================================
+
+// =================================================================================================
+// XDCAM_CheckFormat
+// =================
+//
+// This version does fairly simple checks. The top level folder (.../MyMovie) must have exactly 1
+// child, a folder called CONTENTS. This must have a subfolder called CLIP. It may also have
+// subfolders called VIDEO, AUDIO, ICON, VOICE, and PROXY. Any mixture of these additional folders
+// is allowed, but no other children are allowed in CONTENTS. The CLIP folder must contain a .XML
+// file for the desired clip. The name checks are case insensitive.
+//
+// The state of the string parameters depends on the form of the path passed by the client. If the
+// client passed a logical clip path, like ".../MyMovie/C0001", the parameters are:
+// rootPath - ".../MyMovie"
+// gpName - empty
+// parentName - empty
+// leafName - "C0001"
+//
+// If the client passed a FAM file path, like ".../MyMovie/Edit/E0001E01.SMI", they are:
+// rootPath - "..."
+// gpName - "MyMovie"
+// parentName - "EDIT" (common code has shifted the case)
+// leafName - "E0001E01"
+//
+// If the client passed a SAM file path, like ".../MyMovie/PROAV/CLPR/C0001/C0001A02.MXF", they are:
+// rootPath - ".../MyMovie/PROAV"
+// gpName - "CLPR"
+// parentName - "C0001"
+// leafName - "C0001A02"
+//
+// For both FAM and SAM the leading character of the leafName for an existing file might be coerced
+// to 'C' to form the logical clip name. And suffix such as "M01" must be removed for FAM. We don't
+// need to worry about that for SAM, that uses the <clip> folder name.
+
+// ! The FAM format supports general clip file names through an ALIAS.XML mapping file. The simple
+// ! existence check has an edge case bug, left to be fixed later. If the ALIAS.XML file exists, but
+// ! some of the clips still have "raw" names, and we're passed an existing file path in the EDIT
+// ! folder, we will fail to do the leading 'E' to 'C' coercion. We might also erroneously remove a
+// ! suffix from a mapped essence file with a name like ClipX01.MXF.
+
+// ! The common code has shifted the gpName, parentName, and leafName strings to uppercase. It has
+// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the
+// ! file exists for a full file path.
+
+bool XDCAM_CheckFormat ( XMP_FileFormat format,
+ const std::string & _rootPath,
+ const std::string & _gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent )
+{
+ std::string rootPath = _rootPath; // ! Need tweaking in the existing file cases (FAM and SAM).
+ std::string gpName = _gpName;
+
+ bool isFAM = false;
+
+ std::string tempPath, childName;
+
+ std::string clipName = leafName;
+
+ // Do some basic checks on the root path and component names. Decide if this is FAM or SAM.
+
+ if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty.
+
+ if ( gpName.empty() ) {
+
+ // This is the logical clip path case. Just look for PROAV to see if this is FAM or SAM.
+ if ( Host_IO::GetChildMode ( rootPath.c_str(), "PROAV" ) != Host_IO::kFMode_IsFolder ) isFAM = true;
+
+ } else {
+
+ // This is the existing file case. See if this is FAM or SAM, tweak the clip name as needed.
+
+ if ( (parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB") ) {
+ // ! The standard says Clip/Edit/Sub, but the caller has already shifted to upper case.
+ isFAM = true;
+ } else if ( (gpName != "CLPR") && (gpName != "EDTR") ) {
+ return false;
+ }
+
+ if ( isFAM ) {
+
+ // Put the proper root path together. Clean up the clip name if needed.
+
+ if ( ! rootPath.empty() ) rootPath += kDirChar;
+ rootPath += gpName;
+ gpName.erase();
+
+ if ( Host_IO::GetChildMode ( rootPath.c_str(), "ALIAS.XML" ) != Host_IO::kFMode_IsFile ) {
+ clipName[0] = 'C'; // ! See notes above about pending bug.
+ }
+
+ if ( clipName.size() > 3 ) {
+ size_t clipMid = clipName.size() - 3;
+ char c1 = clipName[clipMid];
+ char c2 = clipName[clipMid+1];
+ char c3 = clipName[clipMid+2];
+ if ( ('A' <= c1) && (c1 <= 'Z') &&
+ ('0' <= c2) && (c2 <= '9') && ('0' <= c3) && (c3 <= '9') ) {
+ clipName.erase ( clipMid );
+ }
+ }
+
+ } else {
+
+ // Fix the clip name. Check for and strip the "PROAV" suffix on the root path.
+
+ clipName = parentName; // ! We have a folder with the (almost) exact clip name.
+ clipName[0] = 'C';
+
+ std::string proav;
+ XIO::SplitLeafName ( &rootPath, &proav );
+ MakeUpperCase ( &proav );
+ if ( (rootPath.empty()) || (proav != "PROAV") ) return false;
+
+ }
+
+ }
+
+ // Make sure the general XDCAM package structure is legit. Set tempPath as a bogus path of the
+ // form <root>/<FAM-or-SAM>/<clip>, e.g. ".../MyMovie/FAM/C0001". This is passed the handler via
+ // the tempPtr hackery.
+
+ if ( isFAM ) {
+
+ if ( (format != kXMP_XDCAM_FAMFile) && (format != kXMP_UnknownFile) ) return false;
+
+ tempPath = rootPath;
+
+ if ( Host_IO::GetChildMode ( tempPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile ) return false;
+ if ( Host_IO::GetChildMode ( tempPath.c_str(), "DISCMETA.XML" ) != Host_IO::kFMode_IsFile ) return false;
+ if ( Host_IO::GetChildMode ( tempPath.c_str(), "MEDIAPRO.XML" ) != Host_IO::kFMode_IsFile ) return false;
+
+ tempPath += kDirChar;
+ tempPath += "Clip"; // ! Yes, mixed case.
+ tempPath += kDirChar;
+ tempPath += clipName;
+ tempPath += "M01.XML";
+ if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false;
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "FAM";
+ tempPath += kDirChar;
+ tempPath += clipName;
+
+ } else {
+
+ if ( (format != kXMP_XDCAM_SAMFile) && (format != kXMP_UnknownFile) ) return false;
+
+ // We already know about the PROAV folder, just check below it.
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "PROAV";
+
+ if ( Host_IO::GetChildMode ( tempPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile ) return false;
+ if ( Host_IO::GetChildMode ( tempPath.c_str(), "DISCMETA.XML" ) != Host_IO::kFMode_IsFile ) return false;
+ if ( Host_IO::GetChildMode ( tempPath.c_str(), "DISCINFO.XML" ) != Host_IO::kFMode_IsFile ) return false;
+ if ( Host_IO::GetChildMode ( tempPath.c_str(), "CLPR" ) != Host_IO::kFMode_IsFolder ) return false;
+
+ tempPath += kDirChar;
+ tempPath += "CLPR";
+ tempPath += kDirChar;
+ tempPath += clipName;
+ if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFolder ) return false;
+
+ tempPath += kDirChar;
+ tempPath += clipName;
+ tempPath += "M01.XML";
+ if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false;
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "SAM";
+ tempPath += kDirChar;
+ tempPath += clipName;
+
+ }
+
+ // Save the pseudo-path for the handler object. A bit of a hack, but the only way to get info
+ // from here to there.
+
+ size_t pathLen = tempPath.size() + 1; // Include a terminating nul.
+ parent->tempPtr = malloc ( pathLen );
+ if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for XDCAM clip info", kXMPErr_NoMemory );
+ memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above.
+
+ return true;
+
+} // XDCAM_CheckFormat
+
+// =================================================================================================
+
+static void* CreatePseudoClipPath ( const std::string & clientPath ) {
+
+ // Used to create the clip pseudo path when the CheckFormat function is skipped.
+
+ std::string pseudoPath = clientPath;
+ std::string clipName;
+ bool isSAM;
+
+ size_t pathLen;
+ void* tempPtr = 0;
+
+ if ( ! Host_IO::Exists ( pseudoPath.c_str() ) ) {
+
+ // This is the logical clip path case. Look for PROAV to see if this is FAM or SAM.
+
+ XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name, no extension.
+ isSAM = ( Host_IO::GetChildMode ( pseudoPath.c_str(), "PROAV" ) == Host_IO::kFMode_IsFolder );
+
+ } else {
+
+ // The client passed a physical path. We have separate cases for FAM and SAM. If the last
+ // folder, the parent of the file, is Clip, Edit, or Sub (ignoring case) then this is FAM
+ // and things are a bit messy. For SAM, the parent folder is the almost clip name.
+
+ std::string parentName, ignored;
+
+ XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name.
+ XIO::SplitFileExtension ( &clipName, &ignored );
+
+ XIO::SplitLeafName ( &pseudoPath, &parentName );
+ MakeUpperCase ( &parentName );
+ isSAM = ( (parentName != "CLIP") && (parentName != "EDIT") && (parentName != "SUB") );
+
+ if ( isSAM ) {
+
+ // SAM is easy, the parent name is almost the clip name, the first letter gets coerced
+ // to 'C'. There are 2 other folders to remove from the path.
+
+ clipName = parentName;
+ clipName[0] = 'C';
+ XIO::SplitLeafName ( &pseudoPath, &ignored ); // Remove the 2 intermediate folder levels.
+ XIO::SplitLeafName ( &pseudoPath, &ignored );
+
+ } else {
+
+ // FAM is a bit messy, study the comments and code of XDCAM_CheckFormat for details.
+
+ if ( Host_IO::GetChildMode ( pseudoPath.c_str(), "ALIAS.XML" ) != Host_IO::kFMode_IsFile ) {
+ clipName[0] = 'C'; // ! See notes in XDCAM_CheckFormat about pending bug.
+ }
+
+ if ( clipName.size() > 3 ) {
+ size_t clipMid = clipName.size() - 3;
+ char c1 = clipName[clipMid];
+ char c2 = clipName[clipMid+1];
+ char c3 = clipName[clipMid+2];
+ if ( ('A' <= c1) && (c1 <= 'Z') &&
+ ('0' <= c2) && (c2 <= '9') && ('0' <= c3) && (c3 <= '9') ) {
+ clipName.erase ( clipMid );
+ }
+ }
+
+ }
+
+ }
+
+ pseudoPath += kDirChar;
+ if ( isSAM ) {
+ pseudoPath += "SAM";
+ } else {
+ pseudoPath += "FAM";
+ }
+ pseudoPath += kDirChar;
+ pseudoPath += clipName;
+
+ pathLen = pseudoPath.size() + 1; // Include a terminating nul.
+ tempPtr = malloc ( pathLen );
+ if ( tempPtr == 0 ) XMP_Throw ( "No memory for XDCAM clip info", kXMPErr_NoMemory );
+ memcpy ( tempPtr, pseudoPath.c_str(), pathLen );
+
+ return tempPtr;
+
+} // CreatePseudoClipPath
+
+// =================================================================================================
+// XDCAM_MetaHandlerCTor
+// =====================
+
+XMPFileHandler * XDCAM_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new XDCAM_MetaHandler ( parent );
+
+} // XDCAM_MetaHandlerCTor
+
+// =================================================================================================
+// XDCAM_MetaHandler::XDCAM_MetaHandler
+// ====================================
+
+XDCAM_MetaHandler::XDCAM_MetaHandler ( XMPFiles * _parent ) : isFAM(false), expat(0)
+{
+
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kXDCAM_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ // Extract the root path, clip name, and FAM/SAM flag from tempPtr.
+
+ if ( this->parent->tempPtr == 0 ) {
+ // The CheckFormat call might have been skipped.
+ this->parent->tempPtr = CreatePseudoClipPath ( this->parent->filePath );
+ }
+
+ this->rootPath.assign ( (char*) this->parent->tempPtr );
+ free ( this->parent->tempPtr );
+ this->parent->tempPtr = 0;
+
+ XIO::SplitLeafName ( &this->rootPath, &this->clipName );
+
+ std::string temp;
+ XIO::SplitLeafName ( &this->rootPath, &temp );
+ XMP_Assert ( (temp == "FAM") || (temp == "SAM") );
+ if ( temp == "FAM" ) this->isFAM = true;
+ XMP_Assert ( this->isFAM ? (this->parent->format == kXMP_XDCAM_FAMFile) : (this->parent->format == kXMP_XDCAM_SAMFile) );
+
+} // XDCAM_MetaHandler::XDCAM_MetaHandler
+
+// =================================================================================================
+// XDCAM_MetaHandler::~XDCAM_MetaHandler
+// =====================================
+
+XDCAM_MetaHandler::~XDCAM_MetaHandler()
+{
+
+ this->CleanupLegacyXML();
+ if ( this->parent->tempPtr != 0 ) {
+ free ( this->parent->tempPtr );
+ this->parent->tempPtr = 0;
+ }
+
+} // XDCAM_MetaHandler::~XDCAM_MetaHandler
+
+// =================================================================================================
+// XDCAM_MetaHandler::MakeClipFilePath
+// ===================================
+
+bool XDCAM_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ )
+{
+
+ *path = this->rootPath;
+ *path += kDirChar;
+
+ if ( this->isFAM ) {
+ *path += "Clip"; // ! Yes, mixed case.
+ } else {
+ *path += "PROAV";
+ *path += kDirChar;
+ *path += "CLPR";
+ *path += kDirChar;
+ *path += this->clipName;
+ }
+
+ *path += kDirChar;
+ *path += this->clipName;
+ *path += suffix;
+
+ if ( ! checkFile ) return true;
+ return Host_IO::Exists ( path->c_str() );
+
+} // XDCAM_MetaHandler::MakeClipFilePath
+
+// =================================================================================================
+// XDCAM_MetaHandler::MakeMediaproPath
+// ===================================
+
+bool XDCAM_MetaHandler::MakeMediaproPath ( std::string * path, bool checkFile /* = false */ )
+{
+
+ *path = this->rootPath;
+ *path += kDirChar;
+ *path += "MEDIAPRO.XML";
+
+ if ( ! checkFile ) return true;
+ return Host_IO::Exists ( path->c_str() );
+
+} // XDCAM_MetaHandler::MakeMediaproPath
+
+// =================================================================================================
+// XDCAM_MetaHandler::MakeLegacyDigest
+// ===================================
+
+// *** Early hack version.
+
+#define kHexDigits "0123456789ABCDEF"
+
+void XDCAM_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ digestStr->erase();
+ if ( this->clipMetadata == 0 ) return; // Bail if we don't have any legacy XML.
+ XMP_Assert ( this->expat != 0 );
+
+ XMP_StringPtr xdcNS = this->xdcNS.c_str();
+ XML_NodePtr legacyContext, legacyProp;
+
+ legacyContext = this->clipMetadata->GetNamedElement ( xdcNS, "Access" );
+ if ( legacyContext == 0 ) return;
+
+ MD5_CTX context;
+ unsigned char digestBin [16];
+ MD5Init ( &context );
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "Creator" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "CreationDate" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "LastUpdateDate" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ MD5Final ( digestBin, &context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->append ( buffer );
+
+} // XDCAM_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+// P2_MetaHandler::CleanupLegacyXML
+// ================================
+
+void XDCAM_MetaHandler::CleanupLegacyXML()
+{
+
+ if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; }
+
+ clipMetadata = 0; // ! Was a pointer into the expat tree.
+
+} // XDCAM_MetaHandler::CleanupLegacyXML
+
+// =================================================================================================
+// XDCAM_MetaHandler::GetFileModDate
+// =================================
+
+static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) {
+ int compare = SXMPUtils::CompareDateTime ( left, right );
+ return (compare < 0);
+}
+
+bool XDCAM_MetaHandler::GetFileModDate ( XMP_DateTime * modDate )
+{
+
+ // The XDCAM FAM locations of metadata:
+ // MEDIAPRO.XML // Has non-XMP metadata.
+ // Clip:
+ // C0001_50i_DVCAM_43_4chM01.XML // Has non-XMP metadata.
+ // C0001_50i_DVCAM_43_4chM01.XMP
+
+ // The XDCAM SAM locations of metadata:
+ // PROAV:
+ // CLPR:
+ // C0001:
+ // C0001M01.XML // Has non-XMP metadata.
+ // C0001M01.XMP
+
+ bool ok, haveDate = false;
+ std::string fullPath;
+ XMP_DateTime oneDate, junkDate;
+ if ( modDate == 0 ) modDate = &junkDate;
+
+ std::string mediaproPath;
+ ok = MakeMediaproPath ( &mediaproPath, true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( mediaproPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ ok = this->MakeClipFilePath ( &fullPath, "M01.XML", true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ ok = this->MakeClipFilePath ( &fullPath, "M01.XMP", true /* checkFile */ );
+ if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate );
+ if ( ok ) {
+ if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate;
+ haveDate = true;
+ }
+
+ return haveDate;
+
+} // XDCAM_MetaHandler::GetFileModDate
+
+// =================================================================================================
+// XDCAM_MetaHandler::CacheFileData
+// ================================
+
+void XDCAM_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( ! this->containsXMP );
+
+ if ( this->parent->UsesClientIO() ) {
+ XMP_Throw ( "XDCAM cannot be used with client-managed I/O", kXMPErr_InternalFailure );
+ }
+
+ // See if the clip's .XMP file exists.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, "M01.XMP" );
+ if ( Host_IO::GetFileMode ( xmpPath.c_str() ) != Host_IO::kFMode_IsFile ) return; // No XMP.
+
+ // Read the entire .XMP file.
+
+ bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate );
+
+ XMP_Assert ( this->parent->ioRef == 0 );
+ XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), readOnly );
+ if ( xmpFile == 0 ) return; // The open failed.
+ this->parent->ioRef = xmpFile;
+
+ XMP_Int64 xmpLen = xmpFile->Length();
+ if ( xmpLen > 100*1024*1024 ) {
+ XMP_Throw ( "XDCAM XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check.
+ }
+
+ this->xmpPacket.erase();
+ this->xmpPacket.append ( (size_t)xmpLen, ' ' );
+
+ XMP_Int32 ioCount = xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen );
+
+ this->packetInfo.offset = 0;
+ this->packetInfo.length = (XMP_Int32)xmpLen;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ this->containsXMP = true;
+
+} // XDCAM_MetaHandler::CacheFileData
+
+// =================================================================================================
+// XDCAM_MetaHandler::GetMediaProMetadata
+// ======================================
+
+bool XDCAM_MetaHandler::GetMediaProMetadata ( SXMPMeta * xmpObjPtr,
+ const std::string& clipUMID,
+ bool digestFound )
+{
+ if (!this->isFAM) return false;
+
+ // Build a directory string to the MEDIAPRO file.
+
+ std::string mediaproPath;
+ MakeMediaproPath ( &mediaproPath );
+ return XDCAM_Support::GetMediaProLegacyMetadata ( xmpObjPtr, clipUMID, mediaproPath, digestFound );
+
+}
+
+// =================================================================================================
+// XDCAM_MetaHandler::ProcessXMP
+// =============================
+
+void XDCAM_MetaHandler::ProcessXMP()
+{
+
+ // Some versions of gcc can't tolerate goto's across declarations.
+ // *** Better yet, avoid this cruft with self-cleaning objects.
+ #define CleanupAndExit \
+ { \
+ bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); \
+ if ( ! openForUpdate ) this->CleanupLegacyXML(); \
+ return; \
+ }
+
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( this->containsXMP ) {
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ }
+
+ // NonRealTimeMeta -> XMP by schema
+ std::string xmlPath, umid;
+ this->MakeClipFilePath ( &xmlPath, "M01.XML" );
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return; // The open failed.
+ XMPFiles_IO xmlFile ( hostRef, xmlPath.c_str(), Host_IO::openReadOnly );
+
+ this->expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces );
+ if ( this->expat == 0 ) XMP_Throw ( "XDCAM_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory );
+
+ XMP_Uns8 buffer [64*1024];
+ while ( true ) {
+ XMP_Int32 ioCount = xmlFile.Read ( buffer, sizeof(buffer) );
+ if ( ioCount == 0 ) break;
+ this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
+ }
+ this->expat->ParseBuffer ( 0, 0, true ); // End the parse.
+
+ xmlFile.Close();
+
+ // The root element should be NonRealTimeMeta in some namespace. Take whatever this file uses.
+
+ XML_Node & xmlTree = this->expat->tree;
+ XML_NodePtr rootElem = 0;
+
+ for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) {
+ if ( xmlTree.content[i]->kind == kElemNode ) {
+ rootElem = xmlTree.content[i];
+ }
+ }
+
+ if ( rootElem == 0 ) CleanupAndExit
+ XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen;
+ if ( ! XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) CleanupAndExit
+
+ this->legacyNS = rootElem->ns;
+
+ // Check the legacy digest.
+
+ XMP_StringPtr legacyNS = this->legacyNS.c_str();
+
+ this->clipMetadata = rootElem; // ! Save the NonRealTimeMeta pointer for other use.
+
+ std::string oldDigest, newDigest;
+ bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAM", &oldDigest, 0 );
+ if ( digestFound ) {
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) CleanupAndExit
+ }
+
+ // If we get here we need find and import the actual legacy elements using the current namespace.
+ // Either there is no old digest in the XMP, or the digests differ. In the former case keep any
+ // existing XMP, in the latter case take new legacy values.
+
+ this->containsXMP = XDCAM_Support::GetLegacyMetadata ( &this->xmpObj, rootElem, legacyNS, digestFound, umid );
+ this->containsXMP |= GetMediaProMetadata ( &this->xmpObj, umid, digestFound );
+
+ CleanupAndExit
+ #undef CleanupAndExit
+
+} // XDCAM_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// XDCAM_MetaHandler::UpdateFile
+// =============================
+//
+// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here.
+
+void XDCAM_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Make sure only called once.
+
+ XMP_Assert ( this->parent->UsesLocalIO() );
+
+ // Update the internal legacy XML tree if we have one, and set the digest in the XMP.
+
+ bool updateLegacyXML = false;
+
+ if ( this->clipMetadata != 0 ) {
+ updateLegacyXML = XDCAM_Support::SetLegacyMetadata ( this->clipMetadata, &this->xmpObj, this->legacyNS.c_str());
+ }
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAM", newDigest.c_str(), kXMP_DeleteExisting );
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );
+
+ // -----------------------------------------------------------------------
+ // Update the XMP file first, don't let legacy XML failures block the XMP.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, "M01.XMP" );
+
+ bool haveXMP = Host_IO::Exists ( xmpPath.c_str() );
+ if ( ! haveXMP ) {
+ XMP_Assert ( this->parent->ioRef == 0 );
+ Host_IO::Create ( xmpPath.c_str() );
+ this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), Host_IO::openReadWrite );
+ if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening XDCAM XMP file", kXMPErr_ExternalFailure );
+ }
+
+ XMP_IO* xmpFile = this->parent->ioRef;
+ XMP_Assert ( xmpFile != 0 );
+ XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) );
+
+ // --------------------------------------------
+ // Now update the legacy XML file if necessary.
+
+ if ( updateLegacyXML ) {
+
+ std::string legacyXML, xmlPath;
+ this->expat->tree.Serialize ( &legacyXML );
+ this->MakeClipFilePath ( &xmlPath, "M01.XML" );
+
+ bool haveXML = Host_IO::Exists ( xmlPath.c_str() );
+ if ( ! haveXML ) Host_IO::Create ( xmlPath.c_str() );
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadWrite );
+ if ( hostRef == Host_IO::noFileRef ) XMP_Throw ( "Failure opening XDCAM XML file", kXMPErr_ExternalFailure );
+ XMPFiles_IO origXML ( hostRef, xmlPath.c_str(), Host_IO::openReadWrite );
+ XIO::ReplaceTextFile ( &origXML, legacyXML, (haveXML & doSafeUpdate) );
+ origXML.Close();
+
+ }
+
+} // XDCAM_MetaHandler::UpdateFile
+
+// =================================================================================================
+// XDCAM_MetaHandler::WriteTempFile
+// ================================
+
+void XDCAM_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
+{
+
+ // ! WriteTempFile is not supposed to be called for handlers that own the file.
+ XMP_Throw ( "XDCAM_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure );
+
+} // XDCAM_MetaHandler::WriteTempFile
+
+// =================================================================================================
diff --git a/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp b/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp
new file mode 100644
index 0000000..fbcc8bb
--- /dev/null
+++ b/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp
@@ -0,0 +1,87 @@
+#ifndef __XDCAM_Handler_hpp__
+#define __XDCAM_Handler_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 "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+#include "source/ExpatAdapter.hpp"
+
+// =================================================================================================
+/// \file XDCAM_Handler.hpp
+/// \brief Folder format handler for XDCAM.
+///
+/// This header ...
+///
+// =================================================================================================
+
+extern XMPFileHandler * XDCAM_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool XDCAM_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kXDCAM_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_FolderBasedFormat);
+
+class XDCAM_MetaHandler : public XMPFileHandler
+{
+public:
+
+ bool GetFileModDate ( XMP_DateTime * modDate );
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteTempFile ( XMP_IO* tempRef );
+
+ XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files.
+ { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); };
+
+ XDCAM_MetaHandler ( XMPFiles * _parent );
+ virtual ~XDCAM_MetaHandler();
+
+private:
+
+ XDCAM_MetaHandler() : isFAM(false), expat(0), clipMetadata(0) {}; // Hidden on purpose.
+
+ bool MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false );
+ bool MakeMediaproPath ( std::string * path, bool checkFile = false );
+ void MakeLegacyDigest ( std::string * digestStr );
+ void CleanupLegacyXML();
+
+ bool GetMediaProMetadata ( SXMPMeta * xmpObjPtr, const std::string& clipUMID, bool digestFound );
+
+ std::string rootPath, clipName, xdcNS, legacyNS;
+
+ bool isFAM;
+
+ ExpatAdapter * expat;
+ XML_Node * clipMetadata; // ! Don't delete, points into the Expat tree.
+
+}; // XDCAM_MetaHandler
+
+// =================================================================================================
+
+#endif /* __XDCAM_Handler_hpp__ */
diff --git a/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.cpp b/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.cpp
new file mode 100644
index 0000000..a97c2b0
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.cpp
@@ -0,0 +1,302 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h"
+#include "XMPFiles/source/FormatSupport/IFF/Chunk.h"
+#include "source/XMP_LibUtils.hpp"
+#include "source/XIO.hpp"
+
+#include <algorithm>
+
+using namespace IFF_RIFF;
+
+//
+// Static init
+//
+const BigEndian& AIFFBehavior::mEndian = BigEndian::getInstance();
+
+//-----------------------------------------------------------------------------
+//
+// AIFFBehavior::getRealSize(...)
+//
+// Purpose: Validate the passed in size value, identify the valid size if the
+// passed in isn't valid and return the valid size.
+// Throw an exception if the passed in size isn't valid and there's
+// no way to identify a valid size.
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 AIFFBehavior::getRealSize( const XMP_Uns64 size, const ChunkIdentifier& id, IChunkContainer& tree, XMP_IO* stream )
+{
+ if( (size & 0x80000000) > 0 )
+ {
+ XMP_Throw( "Unknown size value", kXMPErr_BadFileFormat );
+ }
+
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+//
+// AIFFBehavior::isValidTopLevelChunk(...)
+//
+// Purpose: Return true if the passed identifier is valid for top-level chunks
+// of a certain format.
+//
+//-----------------------------------------------------------------------------
+
+bool AIFFBehavior::isValidTopLevelChunk( const ChunkIdentifier& id, XMP_Uns32 chunkNo )
+{
+ return (chunkNo == 0) && (id.id == kChunk_FORM) && ((id.type == kType_AIFF) || (id.type == kType_AIFC));
+}
+
+//-----------------------------------------------------------------------------
+//
+// AIFFBehavior::getMaxChunkSize(...)
+//
+// Purpose: Return the maximum size of a single chunk, i.e. the maximum size
+// of a top-level chunk.
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 AIFFBehavior::getMaxChunkSize() const
+{
+ return 0x80000000LL; // 2 GByte
+}
+
+//-----------------------------------------------------------------------------
+//
+// AIFFBehavior::fixHierarchy(...)
+//
+// Purpose: Fix the hierarchy of chunks depending ones based on size changes of
+// one or more chunks and second based on format specific rules.
+// Throw an exception if the hierarchy can't be fixed.
+//
+//-----------------------------------------------------------------------------
+
+void AIFFBehavior::fixHierarchy( IChunkContainer& tree )
+{
+ XMP_Validate( tree.numChildren() == 1, "AIFF files should only have one top level chunk (FORM)", kXMPErr_BadFileFormat);
+ Chunk* formChunk = tree.getChildAt(0);
+
+ XMP_Validate( (formChunk->getType() == kType_AIFF) || (formChunk->getType() == kType_AIFC), "Invalid type for AIFF/AIFC top level chunk (FORM)", kXMPErr_BadFileFormat);
+
+ if( formChunk->hasChanged() )
+ {
+ //
+ // none of the modified chunks should be smaller than 12Byte
+ //
+ for( XMP_Uns32 i=0; i<formChunk->numChildren(); i++ )
+ {
+ Chunk* chunk = formChunk->getChildAt(i);
+
+ if( chunk->hasChanged() && chunk->getSize() != chunk->getOriginalSize() )
+ {
+ XMP_Validate( chunk->getSize() >= Chunk::TYPE_SIZE, "Modified chunk smaller than 12bytes", kXMPErr_InternalFailure );
+ }
+ }
+
+ //
+ // move new added chunks to temporary container
+ //
+ Chunk* tmpContainer = Chunk::createChunk( mEndian );
+ this->moveChunks( *formChunk, *tmpContainer, formChunk->numChildren() - mChunksAdded );
+
+ //
+ // for all children chunks until the last child of the initial list is reached
+ // try to arrange the chunks at the current location using exisiting free space
+ // or FREE chunks around, otherwise move the chunk to the end
+ //
+ this->arrangeChunksInPlace( *formChunk, *tmpContainer );
+
+ //
+ // for all chunks that were moved to the end try to find a FREE chunk for them
+ //
+ this->arrangeChunksInTree( *tmpContainer, *formChunk );
+
+ //
+ // append all remaining new added chunks to the end of the tree
+ //
+ this->moveChunks( *tmpContainer, *formChunk, 0 );
+ delete tmpContainer;
+
+ //
+ // check for FREE chunks at the end
+ //
+ Chunk* endFREE = this->mergeFreeChunks( *formChunk, formChunk->numChildren() - 1 );
+
+ if( endFREE != NULL )
+ {
+ formChunk->removeChildAt( formChunk->numChildren() - 1 );
+ delete endFREE;
+ }
+
+ //
+ // Fix the offset values of all chunks. Throw an exception in the case that
+ // the offset of a non-modified chunk needs to be reset.
+ //
+ XMP_Validate( formChunk->getOffset() == 0, "Invalid offset for AIFF/AIFC top level chunk (FORM)", kXMPErr_InternalFailure );
+
+ this->validateOffsets( tree );
+ }
+}
+
+void AIFFBehavior::insertChunk( IChunkContainer& tree, Chunk& chunk )
+{
+ XMP_Validate( tree.numChildren() == 1, "AIFF files should only have one top level chunk (FORM)", kXMPErr_BadFileFormat);
+ Chunk* formChunk = tree.getChildAt(0);
+
+ XMP_Validate( (formChunk->getType() == kType_AIFF) || (formChunk->getType() == kType_AIFC), "Invalid type for AIFF/AIFC top level chunk (FORM)", kXMPErr_BadFileFormat);
+
+ // add new chunk to the end of the AIFF:FORM
+ formChunk->appendChild(&chunk);
+
+ mChunksAdded++;
+}
+
+bool AIFFBehavior::removeChunk( IChunkContainer& tree, Chunk& chunk )
+{
+ XMP_Validate( chunk.getID() != kChunk_FORM, "Can't remove FORM chunk!", kXMPErr_InternalFailure );
+ XMP_Validate( chunk.getChunkMode() != CHUNK_UNKNOWN, "Cant' remove UNKNOWN Chunk", kXMPErr_InternalFailure );
+
+ XMP_Validate( tree.numChildren() == 1, "AIFF files should only have one top level chunk (FORM)", kXMPErr_BadFileFormat);
+
+ Chunk* formChunk = tree.getChildAt(0);
+
+ XMP_Validate( (formChunk->getType() == kType_AIFF) || (formChunk->getType() == kType_AIFC), "Invalid type for AIFF/AIFC top level chunk (FORM)", kXMPErr_BadFileFormat);
+
+ XMP_Uns32 i = std::find( formChunk->firstChild(), formChunk->lastChild(), &chunk ) - formChunk->firstChild();
+
+ XMP_Validate( i < formChunk->numChildren(), "Invalid chunk in tree", kXMPErr_InternalFailure );
+
+ //
+ // adjust new chunks counter
+ //
+ if( i > formChunk->numChildren() - mChunksAdded - 1 )
+ {
+ mChunksAdded--;
+ }
+
+ if( i < formChunk->numChildren()-1 )
+ {
+ //
+ // fill gap with free chunk
+ //
+ Chunk* free = this->createFREE( chunk.getPadSize( true ) );
+ formChunk->replaceChildAt( i, free );
+ free->setAsNew();
+
+ //
+ // merge JUNK chunks
+ //
+ this->mergeFreeChunks( *formChunk, i );
+ }
+ else
+ {
+ //
+ // remove chunk from tree
+ //
+ formChunk->removeChildAt( i );
+ }
+
+ return true;
+}
+
+XMP_Bool AIFFBehavior::isFREEChunk( const Chunk& chunk ) const
+{
+ XMP_Bool ret = ( chunk.getID() == kChunk_APPL && chunk.getType() == kType_FREE );
+
+ //
+ // if the signature is not 'APPL':'FREE' the it could be an annotation chunk
+ // (ID: 'ANNO') which data area is smaller than 4bytes and the data is zero
+ //
+ if( !ret && chunk.getID() == kChunk_ANNO && chunk.getSize() < Chunk::TYPE_SIZE )
+ {
+ ret = chunk.getSize() == 0;
+
+ if( !ret )
+ {
+ const XMP_Uns8* buffer;
+ chunk.getData( &buffer );
+
+ XMP_Uns8* data = new XMP_Uns8[static_cast<size_t>( chunk.getSize() )];
+ memset( data, 0, static_cast<size_t>( chunk.getSize() ) );
+
+ ret = ( memcmp( data, buffer, static_cast<size_t>( chunk.getSize() ) ) == 0 );
+
+ delete[] data;
+ }
+ }
+
+ return ret;
+}
+
+Chunk* AIFFBehavior::createFREE( XMP_Uns64 chunkSize )
+{
+ XMP_Int64 alloc = chunkSize - Chunk::HEADER_SIZE;
+
+ Chunk* chunk = NULL;
+ XMP_Uns8* data = NULL;
+
+ if( alloc > 0 )
+ {
+ data = new XMP_Uns8[static_cast<size_t>( alloc )];
+ memset( data, 0, static_cast<size_t>( alloc ) );
+ }
+
+ if( alloc < Chunk::TYPE_SIZE )
+ {
+ //
+ // if the required size is smaller than the minimum size of a 'APPL':'FREE' chunk
+ // then create an annotation chunk 'ANNO' and zero the data
+ //
+ if( alloc > 0 )
+ {
+ chunk = Chunk::createUnknownChunk( mEndian, kChunk_ANNO, 0, alloc );
+ chunk->setData( data, alloc );
+ }
+ else
+ {
+ chunk = Chunk::createHeaderChunk( mEndian, kChunk_ANNO );
+ }
+ }
+ else
+ {
+ //
+ // create a 'APPL':'FREE' chunk
+ //
+ alloc -= Chunk::TYPE_SIZE;
+
+ if( alloc > 0 )
+ {
+ chunk = Chunk::createUnknownChunk( mEndian, kChunk_APPL, kType_FREE, alloc+Chunk::TYPE_SIZE );
+ chunk->setData( data, alloc, true );
+ }
+ else
+ {
+ chunk = Chunk::createHeaderChunk( mEndian, kChunk_APPL, kType_FREE );
+ }
+ }
+
+ delete[] data;
+
+ // force set dirty flag
+ chunk->setChanged();
+
+ return chunk;
+}
+
+XMP_Uns64 AIFFBehavior::getMinFREESize() const
+{
+ // avoid creation of chunks with size==0
+ return static_cast<XMP_Uns64>( Chunk::HEADER_SIZE ) + 2;
+}
diff --git a/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h b/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h
new file mode 100644
index 0000000..deadac3
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h
@@ -0,0 +1,152 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _AIFFBEHAVIOR_h_
+#define _AIFFBEHAVIOR_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+
+#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h"
+#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h"
+#include "source/Endian.h"
+
+namespace IFF_RIFF
+{
+
+/**
+ AIFF behavior class.
+
+ Implements the IChunkBehavior interface
+*/
+
+
+class AIFFBehavior : public IChunkBehavior
+{
+public:
+ /**
+ ctor/dtor
+ */
+ AIFFBehavior() : mChunksAdded(0) {}
+ ~AIFFBehavior() {}
+
+ /**
+ Validate the passed in size value, identify the valid size if the passed in isn't valid
+ and return the valid size.
+ throw an exception if the passed in size isn't valid and there's no way to identify a
+ valid size.
+
+ @param size Size value
+ @param id Identifier of chunk
+ @param tree Chunk tree
+ @param stream Stream handle
+
+ @return Valid size value.
+ */
+ XMP_Uns64 getRealSize( const XMP_Uns64 size, const ChunkIdentifier& id, IChunkContainer& tree, XMP_IO* stream );
+
+ /**
+ Return the maximum size of a single chunk, i.e. the maximum size of a top-level chunk.
+
+ @return Maximum size
+ */
+ XMP_Uns64 getMaxChunkSize() const;
+
+ /**
+ Return true if the passed identifier is valid for top-level chunks of a certain format.
+
+ @param id Chunk identifier
+ @param chunkNo order number of top-level chunk
+ @return true, if passed id is a valid top-level chunk
+ */
+ bool isValidTopLevelChunk( const ChunkIdentifier& id, XMP_Uns32 chunkNo );
+
+ /**
+ Fix the hierarchy of chunks depending ones based on size changes of one or more chunks
+ and second based on format specific rules.
+ Throw an exception if the hierarchy can't be fixed.
+
+ @param tree Vector of root chunks.
+ */
+ void fixHierarchy( IChunkContainer& tree );
+
+ /**
+ Insert a new chunk into the hierarchy of chunks. The behavior needs to decide the position
+ of the new chunk and has to do the insertion.
+
+ @param tree Chunk tree
+ @param chunk New chunk
+ */
+ void insertChunk( IChunkContainer& tree, Chunk& chunk ) ;
+
+ /**
+ Remove the chunk described by the passed ChunkPath.
+
+ @param tree Chunk tree
+ @param path Path to the chunk that needs to be removed
+
+ @return true if the chunk was removed and need to be deleted
+ */
+ bool removeChunk( IChunkContainer& tree, Chunk& chunk ) ;
+
+private:
+
+
+ /**
+ Create a FREE chunk.
+ If the chunkSize is smaller than the header+type - size then create an annotation chunk.
+ If the passed size is odd, then add a pad byte.
+
+ @param chunkSize Total size including header
+ @return New FREE chunk
+ */
+ Chunk* createFREE( XMP_Uns64 chunkSize );
+
+ /**
+ Check if the passed chunk is a FREE chunk.
+ (Could be also a small annotation chunk with zero bytes in its data)
+
+ @param chunk A chunk
+
+ @return true if the passed chunk is a FREE chunk
+ */
+ XMP_Bool isFREEChunk( const Chunk& chunk ) const;
+
+ /**
+ Retrieve the free space at the passed position in the child list of the parent tree.
+ If there's a FREE chunk then return it.
+
+ @param outFreeBytes On return it takes the number of free bytes
+ @param tree Parent tree
+ @param index Position in the child list of the parent tree
+
+ @return FREE chunk if available
+ */
+ Chunk* getFreeSpace( XMP_Int64& outFreeBytes, const IChunkContainer& tree, XMP_Uns32 index ) const;
+
+ /**
+ Return the minimum size of a FREE chunk
+ */
+ XMP_Uns64 getMinFREESize( ) const;
+
+private:
+ XMP_Uns32 mChunksAdded;
+
+ /** AIFF is always Big Endian */
+ static const BigEndian& mEndian;
+
+}; // IFF_RIFF
+
+}
+#endif
diff --git a/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.cpp b/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.cpp
new file mode 100644
index 0000000..8fadba3
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.cpp
@@ -0,0 +1,46 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h"
+
+using namespace IFF_RIFF;
+
+//-----------------------------------------------------------------------------
+//
+// AIFFMetadata::AIFFMetadata(...)
+//
+// Purpose: ctor/dtor
+//
+//-----------------------------------------------------------------------------
+
+AIFFMetadata::AIFFMetadata()
+{
+}
+
+AIFFMetadata::~AIFFMetadata()
+{
+}
+
+//-----------------------------------------------------------------------------
+//
+// AIFFMetadata::isEmptyValue(...)
+//
+// Purpose: Is the value of the passed ValueObject and its id "empty"?
+//
+//-----------------------------------------------------------------------------
+
+bool AIFFMetadata::isEmptyValue( XMP_Uns32 id, ValueObject& valueObj )
+{
+ TValueObject<std::string>* strObj = dynamic_cast<TValueObject<std::string>*>(&valueObj);
+
+ return ( strObj == NULL || ( strObj != NULL && strObj->getValue().empty() ) );
+}
diff --git a/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h b/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h
new file mode 100644
index 0000000..13dbf1c
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h
@@ -0,0 +1,63 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _AIFFMetadata_h_
+#define _AIFFMetadata_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+
+#include "source/XMP_LibUtils.hpp"
+
+#include "XMPFiles/source/FormatSupport/IFF/IChunkData.h"
+#include "XMPFiles/source/NativeMetadataSupport/IMetadata.h"
+
+
+namespace IFF_RIFF
+{
+
+/**
+ * AIFF Metadata model.
+ * Implements the IMetadata interface
+ */
+class AIFFMetadata : public IMetadata
+{
+public:
+ enum
+ {
+ kName, // std::string
+ kAuthor, // std::string
+ kCopyright, // std::string
+ kAnnotation // std::string
+ };
+
+public:
+ AIFFMetadata();
+ ~AIFFMetadata();
+
+protected:
+ /**
+ * @see IMetadata::isEmptyValue
+ */
+ virtual bool isEmptyValue( XMP_Uns32 id, ValueObject& valueObj );
+
+private:
+ // Operators hidden on purpose
+ AIFFMetadata( const AIFFMetadata& ) {};
+ AIFFMetadata& operator=( const AIFFMetadata& ) { return *this; };
+};
+
+} // namespace
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.cpp b/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.cpp
new file mode 100644
index 0000000..af91524
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.cpp
@@ -0,0 +1,63 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h"
+#include "XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h"
+#include "XMPFiles/source/NativeMetadataSupport/MetadataSet.h"
+#include "source/XMP_LibUtils.hpp"
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+#include "source/XIO.hpp"
+
+
+using namespace IFF_RIFF;
+
+static const MetadataPropertyInfo kAIFFProperties[] =
+{
+// XMP NS XMP Property Name Native Metadata Identifier Native Datatype XMP Datatype Delete Priority ExportPolicy
+ { kXMP_NS_DC, "title", AIFFMetadata::kName, kNativeType_StrUTF8, kXMPType_Localized, true, false, kExport_Always }, // dc:title <-> FORM:AIFF/NAME
+ { kXMP_NS_DC, "creator", AIFFMetadata::kAuthor, kNativeType_StrUTF8, kXMPType_Array, true, false, kExport_Always }, // dc:creator <-> FORM:AIFF/AUTH
+ { kXMP_NS_DC, "rights", AIFFMetadata::kCopyright, kNativeType_StrUTF8, kXMPType_Localized, true, false, kExport_Always }, // dc:rights <-> FORM:AIFF/(c)
+ { kXMP_NS_DM, "logComment", AIFFMetadata::kAnnotation, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // xmpDM:logComment <-> FORM:AIFF/ANNO
+ { NULL }
+};
+
+XMP_Bool AIFFReconcile::importToXMP( SXMPMeta& outXMP, const MetadataSet& inMetaData )
+{
+ XMP_Bool changed = false;
+
+ // the reconciliation is based on the existing outXMP packet
+ AIFFMetadata *aiffMeta = inMetaData.get<AIFFMetadata>();
+
+ if (aiffMeta != NULL)
+ {
+ changed = IReconcile::importNativeToXMP( outXMP, *aiffMeta, kAIFFProperties, false );
+ }
+
+ return changed;
+}//reconcile
+
+
+XMP_Bool AIFFReconcile::exportFromXMP( MetadataSet& outMetaData, SXMPMeta& inXMP )
+{
+ XMP_Bool changed = false;
+
+ // Get the appropriate metadata container
+ AIFFMetadata *aiffMeta = outMetaData.get<AIFFMetadata>();
+
+ // If the metadata container is not available, skip that part of the process
+ if( aiffMeta != NULL )
+ {
+ changed = IReconcile::exportXMPToNative( *aiffMeta, inXMP, kAIFFProperties );
+ }//if AIFF is set
+
+ return changed;
+}//dissolve
diff --git a/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h b/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h
new file mode 100644
index 0000000..41e5989
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h
@@ -0,0 +1,40 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _AIFFReconcile_h_
+#define _AIFFReconcile_h_
+
+#include "XMPFiles/source/NativeMetadataSupport/IReconcile.h"
+
+namespace IFF_RIFF
+{
+
+class AIFFReconcile : public IReconcile
+{
+public:
+ ~AIFFReconcile() {};
+
+ /**
+ * @see IReconcile::importToXMP
+ * Legacy values are always imported.
+ * If the values are not UTF-8 they will be converted to UTF-8 except in ServerMode
+ */
+ XMP_Bool importToXMP( SXMPMeta& outXMP, const MetadataSet& inMetaData );
+
+ /**
+ * @see IReconcile::exportFromXMP
+ * XMP values are always exported to Legacy as UTF-8 encoded
+ */
+ XMP_Bool exportFromXMP( MetadataSet& outMetaData, SXMPMeta& inXMP );
+
+};
+
+}
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/ASF_Support.cpp b/XMPFiles/source/FormatSupport/ASF_Support.cpp
new file mode 100644
index 0000000..35adce6
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/ASF_Support.cpp
@@ -0,0 +1,1438 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/ASF_Support.hpp"
+#include "source/UnicodeConversions.hpp"
+#include "source/XIO.hpp"
+
+#if XMP_WinBuild
+ #define snprintf _snprintf
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+ #pragma warning ( disable : 4267 ) // *** conversion (from size_t), possible loss of date (many 64 bit related)
+#endif
+
+// =============================================================================================
+
+// Platforms other than Win
+#if ! XMP_WinBuild
+int IsEqualGUID ( const GUID& guid1, const GUID& guid2 )
+{
+ return (memcmp ( &guid1, &guid2, sizeof(GUID) ) == 0);
+}
+#endif
+
+ASF_Support::ASF_Support() : legacyManager(0), posFileSizeInfo(0) {}
+
+ASF_Support::ASF_Support ( ASF_LegacyManager* _legacyManager ) : posFileSizeInfo(0)
+{
+ legacyManager = _legacyManager;
+}
+
+ASF_Support::~ASF_Support()
+{
+ legacyManager = 0;
+}
+
+// =============================================================================================
+
+long ASF_Support::OpenASF ( XMP_IO* fileRef, ObjectState & inOutObjectState )
+{
+ XMP_Uns64 pos = 0;
+ XMP_Uns64 len;
+
+ try {
+ pos = fileRef->Rewind();
+ } catch ( ... ) {}
+
+ if ( pos != 0 ) return 0;
+
+ // read first and following chunks
+ while ( ReadObject ( fileRef, inOutObjectState, &len, pos) ) {}
+
+ return inOutObjectState.objects.size();
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::ReadObject ( XMP_IO* fileRef, ObjectState & inOutObjectState, XMP_Uns64 * objectLength, XMP_Uns64 & inOutPosition )
+{
+
+ try {
+
+ XMP_Uns64 startPosition = inOutPosition;
+ XMP_Uns32 bytesRead;
+ ASF_ObjectBase objectBase;
+
+ bytesRead = fileRef->ReadAll ( &objectBase, kASF_ObjectBaseLen );
+ if ( bytesRead != kASF_ObjectBaseLen ) return false;
+
+ *objectLength = GetUns64LE ( &objectBase.size );
+ inOutPosition += *objectLength;
+
+ ObjectData newObject;
+
+ newObject.pos = startPosition;
+ newObject.len = *objectLength;
+ newObject.guid = objectBase.guid;
+
+ // xmpIsLastObject indicates, that the XMP-object is the last top-level object
+ // reset here, if any another object is read
+ inOutObjectState.xmpIsLastObject = false;
+
+ if ( IsEqualGUID ( ASF_Header_Object, newObject.guid ) ) {
+
+ // header object ?
+ this->ReadHeaderObject ( fileRef, inOutObjectState, newObject );
+
+ } else if ( IsEqualGUID ( ASF_XMP_Metadata, newObject.guid ) ) {
+
+ // check object for XMP GUID
+ inOutObjectState.xmpPos = newObject.pos + kASF_ObjectBaseLen;
+ inOutObjectState.xmpLen = newObject.len - kASF_ObjectBaseLen;
+ inOutObjectState.xmpIsLastObject = true;
+ inOutObjectState.xmpObject = newObject;
+ newObject.xmp = true;
+
+ }
+
+ inOutObjectState.objects.push_back ( newObject );
+
+ fileRef->Seek ( inOutPosition, kXMP_SeekFromStart );
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::ReadHeaderObject ( XMP_IO* fileRef, ObjectState& inOutObjectState, const ObjectData& newObject )
+{
+ if ( ! IsEqualGUID ( ASF_Header_Object, newObject.guid) || (! legacyManager ) ) return false;
+
+ std::string buffer;
+
+ legacyManager->SetPadding(0);
+
+ try {
+
+ // read header-object structure
+ XMP_Uns64 pos = newObject.pos;
+ XMP_Uns32 bufferSize = kASF_ObjectBaseLen + 6;
+
+ buffer.clear();
+ buffer.reserve ( bufferSize );
+ buffer.assign ( bufferSize, ' ' );
+ fileRef->Seek ( pos, kXMP_SeekFromStart );
+ fileRef->ReadAll ( const_cast<char*>(buffer.data()), bufferSize );
+
+ XMP_Uns64 read = bufferSize;
+ pos += bufferSize;
+
+ // read contained header objects
+ XMP_Uns32 numberOfHeaders = GetUns32LE ( &buffer[24] );
+ ASF_ObjectBase objectBase;
+
+ while ( read < newObject.len ) {
+
+ fileRef->Seek ( pos, kXMP_SeekFromStart );
+ if ( kASF_ObjectBaseLen != fileRef->Read ( &objectBase, kASF_ObjectBaseLen, true ) ) break;
+
+ fileRef->Seek ( pos, kXMP_SeekFromStart );
+ objectBase.size = GetUns64LE ( &objectBase.size );
+
+ if ( IsEqualGUID ( ASF_File_Properties_Object, objectBase.guid) && (objectBase.size >= 104 ) ) {
+
+ buffer.clear();
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ fileRef->ReadAll ( const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size) );
+
+ // save position of filesize-information
+ posFileSizeInfo = (pos + 40);
+
+ // creation date
+ std::string sub ( buffer.substr ( 48, 8 ) );
+ legacyManager->SetField ( ASF_LegacyManager::fieldCreationDate, sub );
+
+ // broadcast flag set ?
+ XMP_Uns32 flags = GetUns32LE ( &buffer[88] );
+ inOutObjectState.broadcast = (flags & 1);
+ legacyManager->SetBroadcast ( inOutObjectState.broadcast );
+
+ legacyManager->SetObjectExists ( ASF_LegacyManager::objectFileProperties );
+
+ } else if ( IsEqualGUID ( ASF_Content_Description_Object, objectBase.guid) && (objectBase.size >= 34 ) ) {
+
+ buffer.clear();
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ fileRef->ReadAll ( const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size) );
+
+ XMP_Uns16 titleLen = GetUns16LE ( &buffer[24] );
+ XMP_Uns16 authorLen = GetUns16LE ( &buffer[26] );
+ XMP_Uns16 copyrightLen = GetUns16LE ( &buffer[28] );
+ XMP_Uns16 descriptionLen = GetUns16LE ( &buffer[30] );
+ XMP_Uns16 ratingLen = GetUns16LE ( &buffer[32] );
+
+ XMP_Uns16 fieldPos = 34;
+
+ std::string titleStr = buffer.substr ( fieldPos, titleLen );
+ fieldPos += titleLen;
+ legacyManager->SetField ( ASF_LegacyManager::fieldTitle, titleStr );
+
+ std::string authorStr = buffer.substr ( fieldPos, authorLen );
+ fieldPos += authorLen;
+ legacyManager->SetField ( ASF_LegacyManager::fieldAuthor, authorStr );
+
+ std::string copyrightStr = buffer.substr ( fieldPos, copyrightLen );
+ fieldPos += copyrightLen;
+ legacyManager->SetField ( ASF_LegacyManager::fieldCopyright, copyrightStr );
+
+ std::string descriptionStr = buffer.substr ( fieldPos, descriptionLen );
+ fieldPos += descriptionLen;
+ legacyManager->SetField ( ASF_LegacyManager::fieldDescription, descriptionStr );
+
+ /* rating is currently not part of reconciliation
+ std::string ratingStr = buffer.substr ( fieldPos, ratingLen );
+ fieldPos += ratingLen;
+ legacyData.append ( titleStr );
+ */
+
+ legacyManager->SetObjectExists ( ASF_LegacyManager::objectContentDescription );
+
+ } else if ( IsEqualGUID ( ASF_Content_Branding_Object, objectBase.guid ) ) {
+
+ buffer.clear();
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ fileRef->ReadAll ( const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size) );
+
+ XMP_Uns32 fieldPos = 28;
+
+ // copyright URL is 3. element with variable size
+ for ( int i = 1; i <= 3 ; ++i ) {
+ XMP_Uns32 len = GetUns32LE ( &buffer[fieldPos] );
+ if ( i == 3 ) {
+ std::string copyrightURLStr = buffer.substr ( fieldPos + 4, len );
+ legacyManager->SetField ( ASF_LegacyManager::fieldCopyrightURL, copyrightURLStr );
+ }
+ fieldPos += (len + 4);
+ }
+
+ legacyManager->SetObjectExists ( ASF_LegacyManager::objectContentBranding );
+
+#if ! Exclude_LicenseURL_Recon
+
+ } else if ( IsEqualGUID ( ASF_Content_Encryption_Object, objectBase.guid ) ) {
+
+ buffer.clear();
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ fileRef->ReadAll ( const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size) );
+
+ XMP_Uns32 fieldPos = 24;
+
+ // license URL is 4. element with variable size
+ for ( int i = 1; i <= 4 ; ++i ) {
+ XMP_Uns32 len = GetUns32LE ( &buffer[fieldPos] );
+ if ( i == 4 ) {
+ std::string licenseURLStr = buffer.substr ( fieldPos + 4, len );
+ legacyManager->SetField ( ASF_LegacyManager::fieldLicenseURL, licenseURLStr );
+ }
+ fieldPos += (len + 4);
+ }
+
+ legacyManager->SetObjectExists ( objectContentEncryption );
+
+#endif
+
+ } else if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) ) {
+
+ legacyManager->SetPadding ( legacyManager->GetPadding() + (objectBase.size - 24) );
+
+ } else if ( IsEqualGUID ( ASF_Header_Extension_Object, objectBase.guid ) ) {
+
+ this->ReadHeaderExtensionObject ( fileRef, inOutObjectState, pos, objectBase );
+
+ }
+
+ pos += objectBase.size;
+ read += objectBase.size;
+ }
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+
+ legacyManager->ComputeDigest();
+
+ return true;
+}
+
+// =============================================================================================
+
+bool ASF_Support::WriteHeaderObject ( XMP_IO* sourceRef, XMP_IO* destRef, const ObjectData& object, ASF_LegacyManager& _legacyManager, bool usePadding )
+{
+ if ( ! IsEqualGUID ( ASF_Header_Object, object.guid ) ) return false;
+
+ std::string buffer;
+ XMP_Uns16 valueUns16LE;
+ XMP_Uns32 valueUns32LE;
+ XMP_Uns64 valueUns64LE;
+
+ try {
+
+ // read header-object structure
+ XMP_Uns64 pos = object.pos;
+ XMP_Uns32 bufferSize = kASF_ObjectBaseLen + 6;
+
+ buffer.clear();
+ buffer.reserve ( bufferSize );
+ buffer.assign ( bufferSize, ' ' );
+ sourceRef->Seek ( pos, kXMP_SeekFromStart );
+ sourceRef->ReadAll ( const_cast<char*>(buffer.data()), bufferSize );
+
+ XMP_Uns64 read = bufferSize;
+ pos += bufferSize;
+
+ // read contained header objects
+ XMP_Uns32 numberOfHeaders = GetUns32LE ( &buffer[24] );
+ ASF_ObjectBase objectBase;
+
+ // prepare new header in memory
+ std::string header;
+
+ int changedObjects = _legacyManager.changedObjects();
+ int exportedObjects = 0;
+ int writtenObjects = 0;
+
+ header.append ( buffer.c_str(), bufferSize );
+
+ while ( read < object.len ) {
+
+ sourceRef->Seek ( pos, kXMP_SeekFromStart );
+ if ( kASF_ObjectBaseLen != sourceRef->Read ( &objectBase, kASF_ObjectBaseLen, true ) ) break;
+
+ sourceRef->Seek ( pos, kXMP_SeekFromStart );
+ objectBase.size = GetUns64LE ( &objectBase.size );
+
+ int headerStartPos = header.size();
+
+ // save position of filesize-information
+ if ( IsEqualGUID ( ASF_File_Properties_Object, objectBase.guid ) ) {
+ posFileSizeInfo = (headerStartPos + 40);
+ }
+
+ // write objects
+ if ( IsEqualGUID ( ASF_File_Properties_Object, objectBase.guid ) &&
+ (objectBase.size >= 104) && (changedObjects & ASF_LegacyManager::objectFileProperties) ) {
+
+ // copy object and replace creation-date
+ buffer.reserve ( XMP_Uns32 ( objectBase.size ) );
+ buffer.assign ( XMP_Uns32 ( objectBase.size ), ' ' );
+ sourceRef->ReadAll ( const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size) );
+ header.append ( buffer, 0, XMP_Uns32( objectBase.size ) );
+
+ if ( ! _legacyManager.GetBroadcast() ) {
+ buffer = _legacyManager.GetField ( ASF_LegacyManager::fieldCreationDate );
+ ReplaceString ( header, buffer, (headerStartPos + 48), 8 );
+ }
+
+ exportedObjects |= ASF_LegacyManager::objectFileProperties;
+
+ } else if ( IsEqualGUID ( ASF_Content_Description_Object, objectBase.guid ) &&
+ (objectBase.size >= 34) && (changedObjects & ASF_LegacyManager::objectContentDescription) ) {
+
+ // re-create object with xmp-data
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ sourceRef->ReadAll ( const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size) );
+ // write header only
+ header.append ( buffer, 0, XMP_Uns32( kASF_ObjectBaseLen ) );
+
+ // write length fields
+
+ XMP_Uns16 titleLen = _legacyManager.GetField ( ASF_LegacyManager::fieldTitle).size( );
+ valueUns16LE = MakeUns16LE ( titleLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 authorLen = _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor).size( );
+ valueUns16LE = MakeUns16LE ( authorLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 copyrightLen = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright).size( );
+ valueUns16LE = MakeUns16LE ( copyrightLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 descriptionLen = _legacyManager.GetField ( ASF_LegacyManager::fieldDescription).size( );
+ valueUns16LE = MakeUns16LE ( descriptionLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ // retrieve existing overall length of preceding fields
+ XMP_Uns16 precedingLen = 0;
+ precedingLen += GetUns16LE ( &buffer[24] ); // Title
+ precedingLen += GetUns16LE ( &buffer[26] ); // Author
+ precedingLen += GetUns16LE ( &buffer[28] ); // Copyright
+ precedingLen += GetUns16LE ( &buffer[30] ); // Description
+ // retrieve existing 'Rating' length
+ XMP_Uns16 ratingLen = GetUns16LE ( &buffer[32] ); // Rating
+ valueUns16LE = MakeUns16LE ( ratingLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ // write field contents
+
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldTitle ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldDescription ) );
+ header.append ( buffer, (34 + precedingLen), ratingLen );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ exportedObjects |= ASF_LegacyManager::objectContentDescription;
+
+ } else if ( IsEqualGUID ( ASF_Content_Branding_Object, objectBase.guid ) &&
+ (changedObjects & ASF_LegacyManager::objectContentBranding) ) {
+
+ // re-create object with xmp-data
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ sourceRef->ReadAll ( const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size) );
+
+ // calculate size of fields coming before 'Copyright URL'
+ XMP_Uns32 length = 28;
+ length += (GetUns32LE ( &buffer[length] ) + 4); // Banner Image Data
+ length += (GetUns32LE ( &buffer[length] ) + 4); // Banner Image URL
+
+ // write first part of header
+ header.append ( buffer, 0, length );
+
+ // copyright URL
+ length = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL).size( );
+ valueUns32LE = MakeUns32LE ( length );
+ header.append ( (const char*)&valueUns32LE, 4 );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL ) );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ exportedObjects |= ASF_LegacyManager::objectContentBranding;
+
+#if ! Exclude_LicenseURL_Recon
+
+ } else if ( IsEqualGUID ( ASF_Content_Encryption_Object, objectBase.guid ) &&
+ (changedObjects & ASF_LegacyManager::objectContentEncryption) ) {
+
+ // re-create object with xmp-data
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ sourceRef->ReadAll ( const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size) );
+
+ // calculate size of fields coming before 'License URL'
+ XMP_Uns32 length = 24;
+ length += (GetUns32LE ( &buffer[length] ) + 4); // Secret Data
+ length += (GetUns32LE ( &buffer[length] ) + 4); // Protection Type
+ length += (GetUns32LE ( &buffer[length] ) + 4); // Key ID
+
+ // write first part of header
+ header.append ( buffer, 0, length );
+
+ // License URL
+ length = _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL).size( );
+ valueUns32LE = MakeUns32LE ( length );
+ header.append ( (const char*)&valueUns32LE, 4 );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL ) );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ exportedObjects |= ASF_LegacyManager::objectContentEncryption;
+
+#endif
+
+ } else if ( IsEqualGUID ( ASF_Header_Extension_Object, objectBase.guid ) && usePadding ) {
+
+ // re-create object if padding needs to be used
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ sourceRef->ReadAll ( const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size) );
+
+ ASF_Support::WriteHeaderExtensionObject ( buffer, &header, objectBase, 0 );
+
+ } else if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) && usePadding ) {
+
+ // eliminate padding (will be created as last object)
+
+ } else {
+
+ // simply copy all other objects
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ sourceRef->ReadAll ( const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size) );
+
+ header.append ( buffer, 0, XMP_Uns32( objectBase.size ) );
+
+ }
+
+ pos += objectBase.size;
+ read += objectBase.size;
+
+ writtenObjects ++;
+
+ }
+
+ // any objects to create ?
+ int newObjects = (changedObjects ^ exportedObjects);
+
+ if ( newObjects ) {
+
+ // create new objects with xmp-data
+ int headerStartPos;
+ ASF_ObjectBase newObjectBase;
+ XMP_Uns32 length;
+
+ if ( newObjects & ASF_LegacyManager::objectContentDescription ) {
+
+ headerStartPos = header.size();
+ newObjectBase.guid = ASF_Content_Description_Object;
+ newObjectBase.size = 0;
+
+ // write object header
+ header.append ( (const char*)&newObjectBase, kASF_ObjectBaseLen );
+
+ XMP_Uns16 titleLen = _legacyManager.GetField ( ASF_LegacyManager::fieldTitle).size( );
+ valueUns16LE = MakeUns16LE ( titleLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 authorLen = _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor).size( );
+ valueUns16LE = MakeUns16LE ( authorLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 copyrightLen = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright).size( );
+ valueUns16LE = MakeUns16LE ( copyrightLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 descriptionLen = _legacyManager.GetField ( ASF_LegacyManager::fieldDescription).size( );
+ valueUns16LE = MakeUns16LE ( descriptionLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 ratingLen = 0;
+ valueUns16LE = MakeUns16LE ( ratingLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ // write field contents
+
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldTitle ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldDescription ) );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ newObjects &= ~ASF_LegacyManager::objectContentDescription;
+
+ writtenObjects ++;
+
+ }
+
+ if ( newObjects & ASF_LegacyManager::objectContentBranding ) {
+
+ headerStartPos = header.size();
+ newObjectBase.guid = ASF_Content_Branding_Object;
+ newObjectBase.size = 0;
+
+ // write object header
+ header.append ( (const char*)&newObjectBase, kASF_ObjectBaseLen );
+
+ // write 'empty' fields
+ header.append ( 12, '\0' );
+
+ // copyright URL
+ length = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL).size( );
+ valueUns32LE = MakeUns32LE ( length );
+ header.append ( (const char*)&valueUns32LE, 4 );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL ) );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ newObjects &= ~ASF_LegacyManager::objectContentBranding;
+
+ writtenObjects ++;
+
+ }
+
+#if ! Exclude_LicenseURL_Recon
+
+ if ( newObjects & ASF_LegacyManager::objectContentEncryption ) {
+
+ headerStartPos = header.size();
+ newObjectBase.guid = ASF_Content_Encryption_Object;
+ newObjectBase.size = 0;
+
+ // write object header
+ header.append ( (const char*)&newObjectBase, kASF_ObjectBaseLen );
+
+ // write 'empty' fields
+ header.append ( 12, '\0' );
+
+ // License URL
+ length = _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL).size( );
+ valueUns32LE = MakeUns32LE ( length );
+ header.append ( (const char*)&valueUns32LE, 4 );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL ) );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ newObjects &= ~ASF_LegacyManager::objectContentEncryption;
+
+ writtenObjects ++;
+
+ }
+
+#endif
+
+ }
+
+ // create padding object ?
+ if ( usePadding && (header.size ( ) < object.len ) ) {
+ ASF_Support::CreatePaddingObject ( &header, (object.len - header.size()) );
+ writtenObjects ++;
+ }
+
+ // update new header-object size
+ valueUns64LE = MakeUns64LE ( header.size() );
+ std::string newValue ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newValue, 16, 8 );
+
+ // update new number of Header objects
+ valueUns32LE = MakeUns32LE ( writtenObjects );
+ newValue = std::string ( (const char*)&valueUns32LE, 4 );
+ ReplaceString ( header, newValue, 24, 4 );
+
+ // if we are operating on the same file (in-place update), place pointer before writing
+ if ( sourceRef == destRef ) destRef->Seek ( object.pos, kXMP_SeekFromStart );
+
+ // write header
+ destRef->Write ( header.c_str(), header.size() );
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::UpdateHeaderObject ( XMP_IO* fileRef, const ObjectData& object, ASF_LegacyManager& _legacyManager )
+{
+ return ASF_Support::WriteHeaderObject ( fileRef, fileRef, object, _legacyManager, true );
+}
+
+// =============================================================================================
+
+bool ASF_Support::UpdateFileSize ( XMP_IO* fileRef )
+{
+ if ( fileRef == 0 ) return false;
+
+ XMP_Uns64 posCurrent = fileRef->Seek ( 0, kXMP_SeekFromCurrent );
+ XMP_Uns64 newSizeLE = MakeUns64LE ( fileRef->Length() );
+
+ if ( this->posFileSizeInfo != 0 ) {
+
+ fileRef->Seek ( this->posFileSizeInfo, kXMP_SeekFromStart );
+
+ } else {
+
+ // The position of the file size field is not known, find it.
+
+ ASF_ObjectBase objHeader;
+
+ // Read the Header object at the start of the file.
+
+ fileRef->Rewind();
+ fileRef->ReadAll ( &objHeader, kASF_ObjectBaseLen );
+ if ( ! IsEqualGUID ( ASF_Header_Object, objHeader.guid ) ) return false;
+
+ XMP_Uns32 childCount;
+ fileRef->ReadAll ( &childCount, 4 );
+ childCount = GetUns32LE ( &childCount );
+
+ fileRef->Seek ( 2, kXMP_SeekFromCurrent ); // Skip the 2 reserved bytes.
+
+ // Look for the File Properties object in the Header's children.
+
+ for ( ; childCount > 0; --childCount ) {
+ fileRef->ReadAll ( &objHeader, kASF_ObjectBaseLen );
+ if ( IsEqualGUID ( ASF_File_Properties_Object, objHeader.guid ) ) break;
+ XMP_Uns64 dataLen = GetUns64LE ( &objHeader.size ) - 24;
+ fileRef->Seek ( dataLen, kXMP_SeekFromCurrent ); // Skip this object's data.
+ }
+ if ( childCount == 0 ) return false;
+
+ // Seek to the file size field.
+
+ XMP_Uns64 fpoSize = GetUns64LE ( &objHeader.size );
+ if ( fpoSize < (16+8+16+8) ) return false;
+ fileRef->Seek ( 16, kXMP_SeekFromCurrent ); // Skip to the file size field.
+
+ }
+
+ fileRef->Write ( &newSizeLE, 8 ); // Write the new file size.
+
+ fileRef->Seek ( posCurrent, kXMP_SeekFromStart );
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::ReadHeaderExtensionObject ( XMP_IO* fileRef, ObjectState& inOutObjectState, const XMP_Uns64& _pos, const ASF_ObjectBase& _objectBase )
+{
+ if ( ! IsEqualGUID ( ASF_Header_Extension_Object, _objectBase.guid) || (! legacyManager ) ) return false;
+
+ try {
+
+ // read extended header-object structure beginning at the data part (offset = 46)
+ const XMP_Uns64 offset = 46;
+ XMP_Uns64 read = 0;
+ XMP_Uns64 data = (_objectBase.size - offset);
+ XMP_Uns64 pos = (_pos + offset);
+
+ ASF_ObjectBase objectBase;
+
+ while ( read < data ) {
+
+ fileRef->Seek ( pos, kXMP_SeekFromStart );
+ if ( kASF_ObjectBaseLen != fileRef->Read ( &objectBase, kASF_ObjectBaseLen, true ) ) break;
+
+ objectBase.size = GetUns64LE ( &objectBase.size );
+
+ if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) ) {
+ legacyManager->SetPadding ( legacyManager->GetPadding() + (objectBase.size - 24) );
+ }
+
+ pos += objectBase.size;
+ read += objectBase.size;
+
+ }
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::WriteHeaderExtensionObject ( const std::string& buffer, std::string* header, const ASF_ObjectBase& _objectBase, const int /*reservePadding*/ )
+{
+ if ( ! IsEqualGUID ( ASF_Header_Extension_Object, _objectBase.guid ) || (! header) || (buffer.size() < 46) ) return false;
+
+ const XMP_Uns64 offset = 46;
+ int startPos = header->size();
+
+ // copy header base
+ header->append ( buffer, 0, offset );
+
+ // read extended header-object structure beginning at the data part (offset = 46)
+ XMP_Uns64 read = 0;
+ XMP_Uns64 data = (_objectBase.size - offset);
+ XMP_Uns64 pos = offset;
+
+ ASF_ObjectBase objectBase;
+
+ while ( read < data ) {
+
+ memcpy ( &objectBase, &buffer[int(pos)], kASF_ObjectBaseLen );
+ objectBase.size = GetUns64LE ( &objectBase.size );
+
+ if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) ) {
+ // eliminate
+ } else {
+ // copy other objects
+ header->append ( buffer, XMP_Uns32(pos), XMP_Uns32(objectBase.size) );
+ }
+
+ pos += objectBase.size;
+ read += objectBase.size;
+
+ }
+
+ // update header extension data size
+ XMP_Uns32 valueUns32LE = MakeUns32LE ( header->size() - startPos - offset );
+ std::string newDataSize ( (const char*)&valueUns32LE, 4 );
+ ReplaceString ( *header, newDataSize, (startPos + 42), 4 );
+
+ // update new object size
+ XMP_Uns64 valueUns64LE = MakeUns64LE ( header->size() - startPos );
+ std::string newObjectSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( *header, newObjectSize, (startPos + 16), 8 );
+
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::CreatePaddingObject ( std::string* header, const XMP_Uns64 size )
+{
+ if ( ( ! header) || (size < 24) ) return false;
+
+ ASF_ObjectBase newObjectBase;
+
+ newObjectBase.guid = ASF_Padding_Object;
+ newObjectBase.size = MakeUns64LE ( size );
+
+ // write object header
+ header->append ( (const char*)&newObjectBase, kASF_ObjectBaseLen );
+
+ // write 'empty' padding
+ header->append ( XMP_Uns32 ( size - 24 ), '\0' );
+
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::WriteXMPObject ( XMP_IO* fileRef, XMP_Uns32 len, const char* inBuffer )
+{
+ bool ret = false;
+
+ ASF_ObjectBase objectBase = { ASF_XMP_Metadata, 0 };
+ objectBase.size = MakeUns64LE ( len + kASF_ObjectBaseLen );
+
+ try {
+ fileRef->Write ( &objectBase, kASF_ObjectBaseLen );
+ fileRef->Write ( inBuffer, len );
+ ret = true;
+ } catch ( ... ) {}
+
+ return ret;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::UpdateXMPObject ( XMP_IO* fileRef, const ObjectData& object, XMP_Uns32 len, const char * inBuffer )
+{
+ bool ret = false;
+
+ ASF_ObjectBase objectBase = { ASF_XMP_Metadata, 0 };
+ objectBase.size = MakeUns64LE ( len + kASF_ObjectBaseLen );
+
+ try {
+ fileRef->Seek ( object.pos, kXMP_SeekFromStart );
+ fileRef->Write ( &objectBase, kASF_ObjectBaseLen );
+ fileRef->Write ( inBuffer, len );
+ ret = true;
+ } catch ( ... ) {}
+
+ return ret;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::CopyObject ( XMP_IO* sourceRef, XMP_IO* destRef, const ObjectData& object )
+{
+ try {
+ sourceRef->Seek ( object.pos, kXMP_SeekFromStart );
+ XIO::Copy ( sourceRef, destRef, object.len );
+ } catch ( ... ) {
+ return false;
+ }
+
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::ReadBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns64 len, char * outBuffer )
+{
+ try {
+
+ if ( (fileRef == 0) || (outBuffer == 0) ) return false;
+
+ fileRef->Seek ( pos, kXMP_SeekFromStart );
+ long bytesRead = fileRef->ReadAll ( outBuffer, XMP_Int32(len) );
+ if ( XMP_Uns32 ( bytesRead ) != len ) return false;
+
+ return true;
+
+ } catch ( ... ) {}
+
+ return false;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::WriteBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns32 len, const char * inBuffer )
+{
+ try {
+
+ if ( (fileRef == 0) || (inBuffer == 0) ) return false;
+
+ fileRef->Seek ( pos, kXMP_SeekFromStart );
+ fileRef->Write ( inBuffer, len );
+
+ return true;
+
+ } catch ( ... ) {}
+
+ return false;
+
+}
+
+// =================================================================================================
+
+std::string ASF_Support::ReplaceString ( std::string& operand, std::string& str, int offset, int count )
+{
+ std::basic_string<char>::iterator iterF1, iterL1, iterF2, iterL2;
+
+ iterF1 = operand.begin() + offset;
+ iterL1 = operand.begin() + offset + count;
+ iterF2 = str.begin();
+ iterL2 = str.begin() + count;
+
+ return operand.replace ( iterF1, iterL1, iterF2, iterL2 );
+
+}
+
+// =================================================================================================
+
+ASF_LegacyManager::ASF_LegacyManager() : fields(fieldLast), broadcastSet(false), digestComputed(false),
+ imported(false), objectsExisting(0), objectsToExport(0), legacyDiff(0), padding(0)
+{
+ // Nothing more to do.
+}
+
+// =================================================================================================
+
+ASF_LegacyManager::~ASF_LegacyManager()
+{
+ // Nothing to do.
+}
+
+// =================================================================================================
+
+bool ASF_LegacyManager::SetField ( fieldType field, const std::string& value )
+{
+ if ( field >= fieldLast ) return false;
+
+ unsigned int maxSize = this->GetFieldMaxSize ( field );
+
+ if (value.size ( ) <= maxSize ) {
+ fields[field] = value;
+ } else {
+ fields[field] = value.substr ( 0, maxSize );
+ }
+
+ if ( field == fieldCopyrightURL ) NormalizeStringDisplayASCII ( fields[field] );
+
+ #if ! Exclude_LicenseURL_Recon
+ if ( field == fieldLicenseURL ) NormalizeStringDisplayASCII ( fields[field] );
+ #endif
+
+ return true;
+
+}
+
+// =================================================================================================
+
+std::string ASF_LegacyManager::GetField ( fieldType field )
+{
+ if ( field >= fieldLast ) return std::string();
+ return fields[field];
+}
+
+// =================================================================================================
+
+unsigned int ASF_LegacyManager::GetFieldMaxSize ( fieldType field )
+{
+ unsigned int maxSize = 0;
+
+ switch ( field ) {
+
+ case fieldCreationDate :
+ maxSize = 8;
+ break;
+
+ case fieldTitle :
+ case fieldAuthor :
+ case fieldCopyright :
+ case fieldDescription :
+ maxSize = 0xFFFF;
+ break;
+
+ case fieldCopyrightURL :
+#if ! Exclude_LicenseURL_Recon
+ case fieldLicenseURL :
+#endif
+ maxSize = 0xFFFFFFFF;
+ break;
+
+ default:
+ break;
+
+ }
+
+ return maxSize;
+
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::SetObjectExists ( objectType object )
+{
+ objectsExisting |= object;
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::SetBroadcast ( const bool broadcast )
+{
+ broadcastSet = broadcast;
+}
+
+// =================================================================================================
+
+bool ASF_LegacyManager::GetBroadcast()
+{
+ return broadcastSet;
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::ComputeDigest()
+{
+ MD5_CTX context;
+ MD5_Digest digest;
+ char buffer[40];
+
+ MD5Init ( &context );
+ digestStr.clear();
+ digestStr.reserve ( 160 );
+
+ for ( int type=0; type < fieldLast; ++type ) {
+
+ if (fields[type].size ( ) > 0 ) {
+ snprintf ( buffer, sizeof(buffer), "%d,", type );
+ digestStr.append ( buffer );
+ MD5Update ( &context, (XMP_Uns8*)fields[type].data(), fields[type].size() );
+ }
+
+ }
+
+ if( digestStr.size() > 0 ) digestStr[digestStr.size()-1] = ';';
+
+ MD5Final ( digest, &context );
+
+ size_t in, out;
+ 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 );
+
+ digestComputed = true;
+
+}
+
+// =================================================================================================
+
+bool ASF_LegacyManager::CheckDigest ( const SXMPMeta& xmp )
+{
+ bool ret = false;
+
+ if ( ! digestComputed ) this->ComputeDigest();
+
+ std::string oldDigest;
+
+ if ( xmp.GetProperty ( kXMP_NS_ASF, "NativeDigest", &oldDigest, 0 ) ) {
+ ret = (digestStr == oldDigest);
+ }
+
+ return ret;
+
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::SetDigest ( SXMPMeta* xmp )
+{
+ if ( ! digestComputed ) this->ComputeDigest();
+
+ xmp->SetProperty ( kXMP_NS_ASF, "NativeDigest", digestStr.c_str() );
+
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::ImportLegacy ( SXMPMeta* xmp )
+{
+ std::string utf8;
+
+ if ( ! broadcastSet ) {
+ ConvertMSDateToISODate ( fields[fieldCreationDate], &utf8 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ if ( ! utf8.empty() ) xmp->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", utf8.c_str(), kXMP_DeleteExisting );
+
+ if ( ! utf8.empty() ) xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", fields[fieldCopyrightURL].c_str(), kXMP_DeleteExisting );
+
+#if ! Exclude_LicenseURL_Recon
+ if ( ! fields[fieldLicenseURL].empty() ) xmp->SetProperty ( kXMP_NS_XMP_Rights, "Certificate", fields[fieldLicenseURL].c_str(), kXMP_DeleteExisting );
+#endif
+
+ imported = true;
+
+}
+
+// =================================================================================================
+
+int ASF_LegacyManager::ExportLegacy ( const SXMPMeta& xmp )
+{
+ int changed = 0;
+ objectsToExport = 0;
+ legacyDiff = 0;
+
+ std::string utf8;
+ std::string utf16;
+ XMP_OptionBits flags;
+
+ if ( ! broadcastSet ) {
+ if ( xmp.GetProperty ( kXMP_NS_XMP, "CreateDate", &utf8, &flags ) ) {
+ std::string date;
+ ConvertISODateToMSDate ( utf8, &date );
+ if ( fields[fieldCreationDate] != date ) {
+ legacyDiff += date.size();
+ legacyDiff -= fields[fieldCreationDate].size();
+ this->SetField ( fieldCreationDate, date );
+ objectsToExport |= objectFileProperties;
+ changed ++;
+ }
+ }
+ }
+
+ if ( xmp.GetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", 0, &utf8, &flags ) ) {
+ NormalizeStringTrailingNull ( utf8 );
+ ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false );
+ if ( fields[fieldTitle] != utf16 ) {
+ legacyDiff += utf16.size();
+ legacyDiff -= fields[fieldTitle].size();
+ this->SetField ( fieldTitle, utf16 );
+ objectsToExport |= objectContentDescription;
+ changed ++;
+ }
+ }
+
+ utf8.clear();
+ SXMPUtils::CatenateArrayItems ( xmp, kXMP_NS_DC, "creator", 0, 0, kXMPUtil_AllowCommas, &utf8 );
+ if ( ! utf8.empty() ) {
+ NormalizeStringTrailingNull ( utf8 );
+ ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false );
+ if ( fields[fieldAuthor] != utf16 ) {
+ legacyDiff += utf16.size();
+ legacyDiff -= fields[fieldAuthor].size();
+ this->SetField ( fieldAuthor, utf16 );
+ objectsToExport |= objectContentDescription;
+ changed ++;
+ }
+ }
+
+ if ( xmp.GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", 0, &utf8, &flags ) ) {
+ NormalizeStringTrailingNull ( utf8 );
+ ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false );
+ if ( fields[fieldCopyright] != utf16 ) {
+ legacyDiff += utf16.size();
+ legacyDiff -= fields[fieldCopyright].size();
+ this->SetField ( fieldCopyright, utf16 );
+ objectsToExport |= objectContentDescription;
+ changed ++;
+ }
+ }
+
+ if ( xmp.GetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", 0, &utf8, &flags ) ) {
+ NormalizeStringTrailingNull ( utf8 );
+ ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false );
+ if ( fields[fieldDescription] != utf16 ) {
+ legacyDiff += utf16.size();
+ legacyDiff -= fields[fieldDescription].size();
+ this->SetField ( fieldDescription, utf16 );
+ objectsToExport |= objectContentDescription;
+ changed ++;
+ }
+ }
+
+ if ( xmp.GetProperty ( kXMP_NS_XMP_Rights, "WebStatement", &utf8, &flags ) ) {
+ NormalizeStringTrailingNull ( utf8 );
+ if ( fields[fieldCopyrightURL] != utf8 ) {
+ legacyDiff += utf8.size();
+ legacyDiff -= fields[fieldCopyrightURL].size();
+ this->SetField ( fieldCopyrightURL, utf8 );
+ objectsToExport |= objectContentBranding;
+ changed ++;
+ }
+ }
+
+#if ! Exclude_LicenseURL_Recon
+ if ( xmp.GetProperty ( kXMP_NS_XMP_Rights, "Certificate", &utf8, &flags ) ) {
+ NormalizeStringTrailingNull ( utf8 );
+ if ( fields[fieldLicenseURL] != utf8 ) {
+ legacyDiff += utf8.size();
+ legacyDiff -= fields[fieldLicenseURL].size();
+ this->SetField ( fieldLicenseURL, utf8 );
+ objectsToExport |= objectContentEncryption;
+ changed ++;
+ }
+ }
+#endif
+
+ // find objects, that would need to be created on legacy export
+ int newObjects = (objectsToExport & !objectsExisting);
+
+ // calculate minimum storage for new objects, that might be created on export
+ if ( newObjects & objectContentDescription )
+ legacyDiff += sizeContentDescription;
+ if ( newObjects & objectContentBranding )
+ legacyDiff += sizeContentBranding;
+ if ( newObjects & objectContentEncryption )
+ legacyDiff += sizeContentEncryption;
+
+ ComputeDigest();
+
+ return changed;
+
+}
+
+// =================================================================================================
+
+bool ASF_LegacyManager::hasLegacyChanged()
+{
+ return (objectsToExport != 0);
+}
+
+// =================================================================================================
+
+XMP_Int64 ASF_LegacyManager::getLegacyDiff()
+{
+ return (this->hasLegacyChanged() ? legacyDiff : 0);
+}
+
+// =================================================================================================
+
+int ASF_LegacyManager::changedObjects()
+{
+ return objectsToExport;
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::SetPadding ( XMP_Int64 _padding )
+{
+ padding = _padding;
+}
+
+// =================================================================================================
+
+XMP_Int64 ASF_LegacyManager::GetPadding()
+{
+ return padding;
+}
+
+// =================================================================================================
+
+std::string ASF_LegacyManager::NormalizeStringDisplayASCII ( std::string& operand )
+{
+ std::basic_string<char>::iterator current = operand.begin();
+ std::basic_string<char>::iterator end = operand.end();;
+
+ for ( ; (current != end); ++current ) {
+ char element = *current;
+ if ( ( (element < 0x21) && (element != 0x00)) || (element > 0x7e) ) {
+ *current = '?';
+ }
+ }
+
+ return operand;
+
+}
+
+// =================================================================================================
+
+std::string ASF_LegacyManager::NormalizeStringTrailingNull ( std::string& operand )
+{
+ if ( ( operand.size() > 0) && (operand[operand.size() - 1] != '\0') ) {
+ operand.append ( 1, '\0' );
+ }
+
+ return operand;
+
+}
+
+// =================================================================================================
+
+int ASF_LegacyManager::DaysInMonth ( XMP_Int32 year, XMP_Int32 month )
+{
+
+ static short daysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
+
+ int days = daysInMonth [ month ];
+ if ( (month == 2) && IsLeapYear ( year ) ) days += 1;
+
+ return days;
+
+}
+
+// =================================================================================================
+
+bool ASF_LegacyManager::IsLeapYear ( long year )
+{
+
+ if ( year < 0 ) year = -year + 1; // Fold the negative years, assuming there is a year 0.
+
+ if ( (year % 4) != 0 ) return false; // Not a multiple of 4.
+ if ( (year % 100) != 0 ) return true; // A multiple of 4 but not a multiple of 100.
+ if ( (year % 400) == 0 ) return true; // A multiple of 400.
+
+ return false; // A multiple of 100 but not a multiple of 400.
+
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::ConvertMSDateToISODate ( std::string& source, std::string* dest )
+{
+
+ XMP_Int64 creationDate = GetUns64LE ( source.c_str() );
+ XMP_Int64 totalSecs = creationDate / (10*1000*1000);
+ XMP_Int32 nanoSec = ( ( XMP_Int32) (creationDate - (totalSecs * 10*1000*1000)) ) * 100;
+
+ XMP_Int32 days = (XMP_Int32) (totalSecs / 86400);
+ totalSecs -= ( ( XMP_Int64)days * 86400 );
+
+ XMP_Int32 hour = (XMP_Int32) (totalSecs / 3600);
+ totalSecs -= ( ( XMP_Int64)hour * 3600 );
+
+ XMP_Int32 minute = (XMP_Int32) (totalSecs / 60);
+ totalSecs -= ( ( XMP_Int64)minute * 60 );
+
+ XMP_Int32 second = (XMP_Int32)totalSecs;
+
+ // A little more simple code converts this to an XMP date string:
+
+ XMP_DateTime date;
+ memset ( &date, 0, sizeof ( date ) );
+
+ date.year = 1601; // The MS date origin.
+ date.month = 1;
+ date.day = 1;
+
+ date.day += days; // Add in the delta.
+ date.hour = hour;
+ date.minute = minute;
+ 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.
+
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::ConvertISODateToMSDate ( std::string& source, std::string* dest )
+{
+ XMP_DateTime date;
+ SXMPUtils::ConvertToDate ( source, &date );
+ SXMPUtils::ConvertToUTCTime ( &date );
+
+ XMP_Int64 creationDate;
+ creationDate = date.nanoSecond / 100;
+ creationDate += (XMP_Int64 ( date.second) * (10*1000*1000) );
+ creationDate += (XMP_Int64 ( date.minute) * 60 * (10*1000*1000) );
+ creationDate += (XMP_Int64 ( date.hour) * 3600 * (10*1000*1000) );
+
+ XMP_Int32 days = (date.day - 1);
+
+ --date.month;
+ while ( date.month >= 1 ) {
+ days += DaysInMonth ( date.year, date.month );
+ --date.month;
+ }
+
+ --date.year;
+ while ( date.year >= 1601 ) {
+ days += (IsLeapYear ( date.year) ? 366 : 365 );
+ --date.year;
+ }
+
+ creationDate += (XMP_Int64 ( days) * 86400 * (10*1000*1000) );
+
+ creationDate = GetUns64LE ( &creationDate );
+ dest->assign ( (const char*)&creationDate, 8 );
+
+}
diff --git a/XMPFiles/source/FormatSupport/ASF_Support.hpp b/XMPFiles/source/FormatSupport/ASF_Support.hpp
new file mode 100644
index 0000000..9d9060b
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/ASF_Support.hpp
@@ -0,0 +1,226 @@
+#ifndef __ASF_Support_hpp__
+#define __ASF_Support_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+#include "source/XIO.hpp"
+
+// currently exclude LicenseURL from reconciliation
+#define Exclude_LicenseURL_Recon 1
+#define EXCLUDE_LICENSEURL_RECON 1
+
+// Defines for platforms other than Win
+#if ! XMP_WinBuild
+
+ typedef struct _GUID
+ {
+ XMP_Uns32 Data1;
+ XMP_Uns16 Data2;
+ XMP_Uns16 Data3;
+ XMP_Uns8 Data4[8];
+ } GUID;
+
+ int IsEqualGUID ( const GUID& guid1, const GUID& guid2 );
+
+ static const GUID GUID_NULL = { 0x0, 0x0, 0x0, { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } };
+
+#endif
+
+// header object
+static const GUID ASF_Header_Object = { MakeUns32LE(0x75b22630), MakeUns16LE(0x668e), MakeUns16LE(0x11cf), { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c } };
+// contains ...
+static const GUID ASF_File_Properties_Object = { MakeUns32LE(0x8cabdca1), MakeUns16LE(0xa947), MakeUns16LE(0x11cf), { 0x8e, 0xe4, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 } };
+static const GUID ASF_Content_Description_Object = { MakeUns32LE(0x75b22633), MakeUns16LE(0x668e), MakeUns16LE(0x11cf), { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c } };
+static const GUID ASF_Content_Branding_Object = { MakeUns32LE(0x2211b3fa), MakeUns16LE(0xbd23), MakeUns16LE(0x11d2), { 0xb4, 0xb7, 0x00, 0xa0, 0xc9, 0x55, 0xfc, 0x6e } };
+static const GUID ASF_Content_Encryption_Object = { MakeUns32LE(0x2211b3fb), MakeUns16LE(0xbd23), MakeUns16LE(0x11d2), { 0xb4, 0xb7, 0x00, 0xa0, 0xc9, 0x55, 0xfc, 0x6e } };
+// padding
+// Remark: regarding to Microsofts spec only the ASF_Header_Object contains a ASF_Padding_Object
+// Real world files show, that the ASF_Header_Extension_Object contains a ASF_Padding_Object
+static const GUID ASF_Header_Extension_Object = { MakeUns32LE(0x5fbf03b5), MakeUns16LE(0xa92e), MakeUns16LE(0x11cf), { 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 } };
+static const GUID ASF_Padding_Object = { MakeUns32LE(0x1806d474), MakeUns16LE(0xcadf), MakeUns16LE(0x4509), { 0xa4, 0xba, 0x9a, 0xab, 0xcb, 0x96, 0xaa, 0xe8 } };
+
+// data object
+static const GUID ASF_Data_Object = { MakeUns32LE(0x75b22636), MakeUns16LE(0x668e), MakeUns16LE(0x11cf), { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c } };
+
+// XMP object
+static const GUID ASF_XMP_Metadata = { MakeUns32LE(0xbe7acfcb), MakeUns16LE(0x97a9), MakeUns16LE(0x42e8), { 0x9c, 0x71, 0x99, 0x94, 0x91, 0xe3, 0xaf, 0xac } };
+
+static const int guidLen = sizeof(GUID);
+
+typedef struct _ASF_ObjectBase
+{
+ GUID guid;
+ XMP_Uns64 size;
+
+} ASF_ObjectBase;
+
+static const XMP_Uns32 kASF_ObjectBaseLen = (XMP_Uns32) sizeof(ASF_ObjectBase);
+
+// =================================================================================================
+
+class ASF_LegacyManager {
+public:
+
+ enum objectType {
+ objectFileProperties = 1 << 0,
+ objectContentDescription = 1 << 1,
+ objectContentBranding = 1 << 2,
+ objectContentEncryption = 1 << 3
+ };
+
+ enum minObjectSize {
+ sizeContentDescription = 34,
+ sizeContentBranding = 40,
+ sizeContentEncryption = 40
+ };
+
+ enum fieldType {
+ // File_Properties_Object
+ fieldCreationDate = 0,
+ // Content_Description_Object
+ fieldTitle,
+ fieldAuthor,
+ fieldCopyright,
+ fieldDescription,
+ // Content_Branding_Object
+ fieldCopyrightURL,
+ #if ! Exclude_LicenseURL_Recon
+ // Content_Encryption_Object
+ fieldLicenseURL,
+ #endif
+ // last
+ fieldLast
+ };
+
+ ASF_LegacyManager();
+ virtual ~ASF_LegacyManager();
+
+ bool SetField ( fieldType field, const std::string& value );
+ std::string GetField ( fieldType field );
+ unsigned int GetFieldMaxSize ( fieldType field );
+
+ void SetObjectExists ( objectType object );
+
+ void SetBroadcast ( const bool broadcast );
+ bool GetBroadcast();
+
+ void ComputeDigest();
+ bool CheckDigest ( const SXMPMeta& xmp );
+ void SetDigest ( SXMPMeta* xmp );
+
+ void ImportLegacy ( SXMPMeta* xmp );
+ int ExportLegacy ( const SXMPMeta& xmp );
+ bool hasLegacyChanged();
+ XMP_Int64 getLegacyDiff();
+ int changedObjects();
+
+ void SetPadding ( XMP_Int64 padding );
+ XMP_Int64 GetPadding();
+
+private:
+
+ typedef std::vector<std::string> TFields;
+ TFields fields;
+ bool broadcastSet;
+
+ std::string digestStr;
+ bool digestComputed;
+
+ bool imported;
+ int objectsExisting;
+ int objectsToExport;
+ XMP_Int64 legacyDiff;
+ XMP_Int64 padding;
+
+ static std::string NormalizeStringDisplayASCII ( std::string& operand );
+ static std::string NormalizeStringTrailingNull ( std::string& operand );
+
+ static void ConvertMSDateToISODate ( std::string& source, std::string* dest );
+ static void ConvertISODateToMSDate ( std::string& source, std::string* dest );
+
+ static int DaysInMonth ( XMP_Int32 year, XMP_Int32 month );
+ static bool IsLeapYear ( long year );
+
+}; // class ASF_LegacyManager
+
+// =================================================================================================
+
+class ASF_Support {
+public:
+
+ class ObjectData {
+ public:
+ ObjectData() : pos(0), len(0), guid(GUID_NULL), xmp(false) {}
+ virtual ~ObjectData() {}
+ XMP_Uns64 pos; // file offset of object
+ XMP_Uns64 len; // length of object data
+ GUID guid; // object GUID
+ bool xmp; // object with XMP ?
+ };
+
+ typedef std::vector<ObjectData> ObjectVector;
+ typedef ObjectVector::iterator ObjectIterator;
+
+ class ObjectState {
+
+ public:
+ ObjectState() : xmpPos(0), xmpLen(0), xmpIsLastObject(false), broadcast(false) {}
+ virtual ~ObjectState() {}
+ XMP_Uns64 xmpPos;
+ XMP_Uns64 xmpLen;
+ bool xmpIsLastObject;
+ bool broadcast;
+ ObjectData xmpObject;
+ ObjectVector objects;
+ };
+
+ ASF_Support();
+ ASF_Support ( ASF_LegacyManager* legacyManager );
+ virtual ~ASF_Support();
+
+ long OpenASF ( XMP_IO* fileRef, ObjectState & inOutObjectState );
+
+ bool ReadObject ( XMP_IO* fileRef, ObjectState & inOutObjectState, XMP_Uns64 * objectLength, XMP_Uns64 & inOutPosition );
+
+ bool ReadHeaderObject ( XMP_IO* fileRef, ObjectState& inOutObjectState, const ObjectData& newObject );
+ bool WriteHeaderObject ( XMP_IO* sourceRef, XMP_IO* destRef, const ObjectData& object, ASF_LegacyManager& legacyManager, bool usePadding );
+ bool UpdateHeaderObject ( XMP_IO* fileRef, const ObjectData& object, ASF_LegacyManager& legacyManager );
+
+ bool UpdateFileSize ( XMP_IO* fileRef );
+
+ bool ReadHeaderExtensionObject ( XMP_IO* fileRef, ObjectState& inOutObjectState, const XMP_Uns64& pos, const ASF_ObjectBase& objectBase );
+ static bool WriteHeaderExtensionObject ( const std::string& buffer, std::string* header, const ASF_ObjectBase& objectBase, const int reservePadding );
+
+ static bool CreatePaddingObject ( std::string* header, const XMP_Uns64 size );
+
+ static bool WriteXMPObject ( XMP_IO* fileRef, XMP_Uns32 len, const char* inBuffer );
+ static bool UpdateXMPObject ( XMP_IO* fileRef, const ObjectData& object, XMP_Uns32 len, const char * inBuffer );
+ static bool CopyObject ( XMP_IO* sourceRef, XMP_IO* destRef, const ObjectData& object );
+
+ static bool ReadBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns64 len, char * outBuffer );
+ static bool WriteBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns32 len, const char * inBuffer );
+
+private:
+
+ ASF_LegacyManager* legacyManager;
+ XMP_Uns64 posFileSizeInfo;
+
+ static std::string ReplaceString ( std::string& operand, std::string& str, int offset, int count );
+
+}; // class ASF_Support
+
+// =================================================================================================
+
+#endif // __ASF_Support_hpp__
diff --git a/XMPFiles/source/FormatSupport/ID3_Support.cpp b/XMPFiles/source/FormatSupport/ID3_Support.cpp
new file mode 100644
index 0000000..2bc9a1f
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/ID3_Support.cpp
@@ -0,0 +1,504 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "XMPFiles/source/FormatSupport/ID3_Support.hpp"
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+
+#include "source/UnicodeConversions.hpp"
+#include "source/XIO.hpp"
+
+#include <vector>
+
+// =================================================================================================
+
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+ID3GenreMap* kMapID3GenreCodeToName = 0; // Map from a code like "21" or "RX" to the full name.
+ID3GenreMap* kMapID3GenreNameToCode = 0; // Map from the full name to a code like "21" or "RX".
+
+// =================================================================================================
+
+bool ID3_Support::InitializeGlobals()
+{
+ return true;
+}
+
+// =================================================================================================
+
+void ID3_Support::TerminateGlobals()
+{
+ // nothing yet
+}
+
+// =================================================================================================
+// ID3Header
+// =================================================================================================
+
+bool ID3_Support::ID3Header::read ( XMP_IO* file )
+{
+
+ XMP_Assert ( sizeof(fields) == kID3_TagHeaderSize );
+ file->ReadAll ( this->fields, kID3_TagHeaderSize );
+
+ if ( ! CheckBytes ( &this->fields[ID3Header::o_id], "ID3", 3 ) ) {
+ // chuck in default contents:
+ const static char defaultHeader[kID3_TagHeaderSize] = { 'I', 'D', '3', 3, 0, 0, 0, 0, 0, 0 };
+ memcpy ( this->fields, defaultHeader, kID3_TagHeaderSize );
+ return false; // no header found (o.k.) thus stick with new, default header constructed above
+ }
+
+ XMP_Uns8 major = this->fields[o_vMajor];
+ XMP_Uns8 minor = this->fields[o_vMinor];
+ XMP_Validate ( ((2 <= major) && (major <= 4)), "Invalid ID3 major version", kXMPErr_BadFileFormat );
+
+ return true;
+
+}
+
+// =================================================================================================
+
+void ID3_Support::ID3Header::write ( XMP_IO* file, XMP_Int64 tagSize )
+{
+
+ XMP_Assert ( (kID3_TagHeaderSize <= tagSize) && (tagSize < 256*1024*1024) ); // 256 MB limit due to synching.
+
+ XMP_Uns32 synchSize = int32ToSynch ( (XMP_Uns32)tagSize - kID3_TagHeaderSize );
+ PutUns32BE ( synchSize, &this->fields[ID3Header::o_size] );
+ file->Write ( this->fields, kID3_TagHeaderSize );
+
+}
+
+// =================================================================================================
+// ID3v2Frame
+// =================================================================================================
+
+#define frameDefaults id(0), flags(0), content(0), contentSize(0), active(true), changed(false)
+
+ID3_Support::ID3v2Frame::ID3v2Frame() : frameDefaults
+{
+ XMP_Assert ( sizeof(fields) == kV23_FrameHeaderSize ); // Only need to do this in one place.
+ memset ( this->fields, 0, kV23_FrameHeaderSize );
+}
+
+// =================================================================================================
+
+ID3_Support::ID3v2Frame::ID3v2Frame ( XMP_Uns32 id ) : frameDefaults
+{
+ memset ( this->fields, 0, kV23_FrameHeaderSize );
+ this->id = id;
+ PutUns32BE ( id, &this->fields[o_id] );
+}
+
+// =================================================================================================
+
+void ID3_Support::ID3v2Frame::release()
+{
+ if ( this->content != 0 ) delete this->content;
+ this->content = 0;
+ this->contentSize = 0;
+}
+
+// =================================================================================================
+
+void ID3_Support::ID3v2Frame::setFrameValue ( const std::string& rawvalue, bool needDescriptor,
+ bool utf16, bool isXMPPRIVFrame, bool needEncodingByte )
+{
+
+ std::string value;
+
+ if ( isXMPPRIVFrame ) {
+
+ XMP_Assert ( (! needDescriptor) && (! utf16) );
+
+ value.append ( "XMP\0", 4 );
+ value.append ( rawvalue );
+ value.append ( "\0", 1 ); // final zero byte
+
+ } else {
+
+ if ( needEncodingByte ) {
+ if ( utf16 ) {
+ value.append ( "\x1", 1 );
+ } else {
+ value.append ( "\x0", 1 );
+ }
+ }
+
+ if ( needDescriptor ) value.append ( "eng", 3 );
+
+ if ( utf16 ) {
+
+ if ( needDescriptor ) value.append ( "\xFF\xFE\0\0", 4 );
+
+ 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 {
+
+ std::string convertedValue;
+ ReconcileUtils::UTF8ToLatin1 ( rawvalue.c_str(), rawvalue.size(), &convertedValue );
+
+ if ( needDescriptor ) value.append ( "\0", 1 );
+ value.append ( convertedValue );
+ value.append ( "\0", 1 );
+
+ }
+
+ }
+
+ this->changed = true;
+ this->release();
+
+ this->contentSize = (XMP_Int32) value.size();
+ XMP_Validate ( (this->contentSize < 20*1024*1024), "XMP Property exceeds 20MB in size", kXMPErr_InternalFailure );
+ this->content = new char [ this->contentSize ];
+ memcpy ( this->content, value.c_str(), this->contentSize );
+
+} // ID3v2Frame::setFrameValue
+
+// =================================================================================================
+
+XMP_Int64 ID3_Support::ID3v2Frame::read ( XMP_IO* file, XMP_Uns8 majorVersion )
+{
+ XMP_Assert ( (2 <= majorVersion) && (majorVersion <= 4) );
+
+ this->release(); // ensures/allows reuse of 'curFrame'
+ XMP_Int64 start = file->Offset();
+
+ if ( majorVersion > 2 ) {
+ file->ReadAll ( this->fields, kV23_FrameHeaderSize );
+ } else {
+ // Read the 6 byte v2.2 header into the 10 byte form.
+ memset ( this->fields, 0, kV23_FrameHeaderSize ); // Clear all of the bytes.
+ file->ReadAll ( &this->fields[o_id], 3 ); // Leave the low order byte as zero.
+ file->ReadAll ( &this->fields[o_size+1], 3 ); // Read big endian UInt24.
+ }
+
+ this->id = GetUns32BE ( &this->fields[o_id] );
+
+ if ( this->id == 0 ) {
+ file->Seek ( start, kXMP_SeekFromStart ); // Zero ID must mean nothing but padding.
+ return 0;
+ }
+
+ this->flags = GetUns16BE ( &this->fields[o_flags] );
+ XMP_Validate ( (0 == (this->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
+ this->contentSize = GetUns32BE ( &this->fields[o_size] );
+ if ( majorVersion == 4 ) this->contentSize = synchToInt32 ( this->contentSize );
+
+ XMP_Validate ( (this->contentSize >= 0), "negative frame size", kXMPErr_BadFileFormat );
+ XMP_Validate ( (this->contentSize < 20*1024*1024), "single frame exceeds 20MB", kXMPErr_BadFileFormat );
+
+ this->content = new char [ this->contentSize ];
+
+ file->ReadAll ( this->content, this->contentSize );
+ return file->Offset() - start;
+
+} // ID3v2Frame::read
+
+// =================================================================================================
+
+void ID3_Support::ID3v2Frame::write ( XMP_IO* file, XMP_Uns8 majorVersion )
+{
+ XMP_Assert ( (2 <= majorVersion) && (majorVersion <= 4) );
+
+ if ( majorVersion < 4 ) {
+ PutUns32BE ( this->contentSize, &this->fields[o_size] );
+ } else {
+ PutUns32BE ( int32ToSynch ( this->contentSize ), &this->fields[o_size] );
+ }
+
+ if ( majorVersion > 2 ) {
+ file->Write ( this->fields, kV23_FrameHeaderSize );
+ } else {
+ file->Write ( &this->fields[o_id], 3 );
+ file->Write ( &this->fields[o_size+1], 3 );
+ }
+
+ file->Write ( this->content, this->contentSize );
+
+} // ID3v2Frame::write
+
+// =================================================================================================
+
+bool ID3_Support::ID3v2Frame::advancePastCOMMDescriptor ( XMP_Int32& pos )
+{
+
+ if ( (this->contentSize - pos) <= 3 ) return false; // silent error, no room left behing language tag
+ if ( ! CheckBytes ( &this->content[pos], "eng", 3 ) ) return false; // not an error, but leave all non-eng tags alone...
+
+ pos += 3; // skip lang tag
+ if ( pos >= this->contentSize ) return false; // silent error
+
+ while ( pos < this->contentSize ) {
+ if ( this->content[pos++] == 0x00 ) break;
+ }
+ if ( (pos < this->contentSize) && (this->content[pos] == 0x00) ) pos++;
+
+ if ( (pos == 5) && (this->contentSize == 6) && (GetUns16BE(&this->content[4]) == 0x0031) ) {
+ return false;
+ }
+
+ if ( pos > 4 ) {
+ std::string descriptor = std::string ( &this->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.
+
+} // ID3v2Frame::advancePastCOMMDescriptor
+
+// =================================================================================================
+
+bool ID3_Support::ID3v2Frame::getFrameValue ( XMP_Uns8 majorVersion, XMP_Uns32 logicalID, std::string* utf8string )
+{
+
+ XMP_Assert ( (this->content != 0) && (this->contentSize >= 0) && (this->contentSize < 20*1024*1024) );
+
+ if ( this->contentSize == 0 ) {
+ utf8string->erase();
+ return true; // ...it is "of interest", even if empty contents.
+ }
+
+ XMP_Int32 pos = 0;
+ XMP_Uns8 encByte = 0;
+ // WCOP does not have an encoding byte, for all others: use [0] as EncByte, advance pos
+ if ( logicalID != 0x57434F50 ) {
+ encByte = this->content[0];
+ pos++;
+ }
+
+ // mode specific forks, COMM or USLT
+ bool commMode = ( (logicalID == 0x434F4D4D) || (logicalID == 0x55534C54) );
+
+ switch ( encByte ) {
+
+ case 0: //ISO-8859-1, 0-terminated
+ {
+ if ( commMode && (! advancePastCOMMDescriptor ( pos )) ) return false; // not a frame of interest!
+
+ char* localPtr = &this->content[pos];
+ size_t localLen = this->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
+ {
+
+ if ( commMode && (! advancePastCOMMDescriptor ( pos )) ) return false; // not a frame of interest!
+
+ std::string tmp ( this->content, this->contentSize );
+ bool bigEndian = true; // assume for now (if no BOM follows)
+
+ if ( GetUns16BE ( &this->content[pos] ) == 0xFEFF ) {
+ pos += 2;
+ bigEndian = true;
+ } else if ( GetUns16BE ( &this->content[pos] ) == 0xFFFE ) {
+ pos += 2;
+ bigEndian = false;
+ }
+
+ FromUTF16 ( (UTF16Unit*)&this->content[pos], ((this->contentSize - pos)) / 2, utf8string, bigEndian );
+ break;
+
+ }
+
+ case 3: // UTF-8 unicode, terminated \0
+ {
+ if ( commMode && (! advancePastCOMMDescriptor ( pos )) ) return false; // not a frame of interest!
+
+ if ( (GetUns32BE ( &this->content[pos]) & 0xFFFFFF00 ) == 0xEFBBBF00 ) {
+ pos += 3; // swallow any BOM, just in case
+ }
+
+ utf8string->assign ( &this->content[pos], (this->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::getFrameValue
+
+// =================================================================================================
+// ID3v1Tag
+// =================================================================================================
+
+bool ID3_Support::ID3v1Tag::read ( XMP_IO* file, SXMPMeta* meta )
+{
+ // returns returns true, if ID3v1 (or v1.1) exists, otherwise false, sets XMP properties en route
+
+ if ( file->Length() <= 128 ) return false; // ensure sufficient room
+ file->Seek ( -128, kXMP_SeekFromEnd );
+
+ XMP_Uns32 tagID = XIO::ReadInt32_BE ( file );
+ tagID = tagID & 0xFFFFFF00; // wipe 4th byte
+ if ( tagID != 0x54414700 ) return false; // must be "TAG"
+ file->Seek ( -1, kXMP_SeekFromCurrent ); //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;
+
+ file->ReadAll ( buffer, 30 );
+ 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() );
+ }
+
+ file->ReadAll ( buffer, 30 );
+ 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() );
+ }
+
+ file->ReadAll ( buffer, 30 );
+ 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() );
+ }
+
+ file->ReadAll ( buffer, 4 );
+ 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() );
+ }
+
+ file->ReadAll ( buffer, 30 );
+ 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() );
+ }
+
+ if ( buffer[28] == 0 ) {
+ XMP_Uns8 trackNo = buffer[29];
+ if ( trackNo > 0 ) {
+ std::string trackStr;
+ meta->SetProperty_Int ( kXMP_NS_DM, "trackNumber", trackNo );
+ }
+ }
+
+ XMP_Uns8 genreNo = XIO::ReadUns8 ( file );
+ if ( genreNo < 127 ) {
+ meta->SetProperty ( kXMP_NS_DM, "genre", Genres[genreNo] );
+ }
+
+ return true; // ID3Tag found
+
+} // ID3v1Tag::read
+
+// =================================================================================================
+
+void ID3_Support::ID3v1Tag::write ( XMP_IO* file, SXMPMeta* meta )
+{
+
+ std::string zeros ( 128, '\0' );
+ std::string utf8, latin1;
+
+ file->Seek ( -128, kXMP_SeekFromEnd );
+ file->Write ( zeros.data(), 128 );
+
+ file->Seek ( -128, kXMP_SeekFromEnd );
+ XIO::WriteUns8 ( file, 'T' );
+ XIO::WriteUns8 ( file, 'A' );
+ XIO::WriteUns8 ( file, 'G' );
+
+ if ( meta->GetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", 0, &utf8, 0 ) ) {
+ file->Seek ( (-128 + 3), kXMP_SeekFromEnd );
+ ReconcileUtils::UTF8ToLatin1 ( utf8.c_str(), utf8.size(), &latin1 );
+ file->Write ( latin1.c_str(), MIN ( 30, (XMP_Int32)latin1.size() ) );
+ }
+
+ if ( meta->GetProperty ( kXMP_NS_DM, "artist", &utf8, 0 ) ) {
+ file->Seek ( (-128 + 33), kXMP_SeekFromEnd );
+ ReconcileUtils::UTF8ToLatin1 ( utf8.c_str(), utf8.size(), &latin1 );
+ file->Write ( latin1.c_str(), MIN ( 30, (XMP_Int32)latin1.size() ) );
+ }
+
+ if ( meta->GetProperty ( kXMP_NS_DM, "album", &utf8, 0 ) ) {
+ file->Seek ( (-128 + 63), kXMP_SeekFromEnd );
+ ReconcileUtils::UTF8ToLatin1 ( utf8.c_str(), utf8.size(), &latin1 );
+ file->Write ( latin1.c_str(), MIN ( 30, (XMP_Int32)latin1.size() ) );
+ }
+
+ if ( meta->GetProperty ( kXMP_NS_XMP, "CreateDate", &utf8, 0 ) ) {
+ XMP_DateTime dateTime;
+ SXMPUtils::ConvertToDate( utf8, &dateTime );
+ if ( dateTime.hasDate ) {
+ SXMPUtils::ConvertFromInt ( dateTime.year, "", &latin1 );
+ file->Seek ( (-128 + 93), kXMP_SeekFromEnd );
+ file->Write ( latin1.c_str(), MIN ( 4, (XMP_Int32)latin1.size() ) );
+ }
+ }
+
+ if ( meta->GetProperty ( kXMP_NS_DM, "logComment", &utf8, 0 ) ) {
+ file->Seek ( (-128 + 97), kXMP_SeekFromEnd );
+ ReconcileUtils::UTF8ToLatin1 ( utf8.c_str(), utf8.size(), &latin1 );
+ file->Write ( latin1.c_str(), MIN ( 30, (XMP_Int32)latin1.size() ) );
+ }
+
+ if ( meta->GetProperty ( kXMP_NS_DM, "genre", &utf8, 0 ) ) {
+
+ XMP_Uns8 genreNo = 0;
+
+ 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;
+ }
+ }
+
+ file->Seek ( (-128 + 127), kXMP_SeekFromEnd );
+ XIO::WriteUns8 ( file, genreNo );
+
+ }
+
+ if ( meta->GetProperty ( kXMP_NS_DM, "trackNumber", &utf8, kXMP_NoOptions ) ) {
+
+ XMP_Uns8 trackNo = 0;
+ try {
+ trackNo = (XMP_Uns8) SXMPUtils::ConvertToInt ( utf8.c_str() );
+ file->Seek ( (-128 + 125), kXMP_SeekFromEnd );
+ XIO::WriteUns8 ( file, 0 ); // ID3v1.1 extension
+ XIO::WriteUns8 ( file, trackNo );
+ } catch ( ... ) {
+ // forgive, just don't set this one.
+ }
+
+ }
+
+} // ID3v1Tag::write
diff --git a/XMPFiles/source/FormatSupport/ID3_Support.hpp b/XMPFiles/source/FormatSupport/ID3_Support.hpp
new file mode 100644
index 0000000..43b917d
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/ID3_Support.hpp
@@ -0,0 +1,309 @@
+#ifndef __ID3_Support_hpp__
+#define __ID3_Support_hpp__ 1
+
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#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
+
+// =================================================================================================
+
+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
+ };
+
+ // =============================================================================================
+
+ inline XMP_Int32 synchToInt32 ( XMP_Uns32 rawDataBE ) {
+ 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;
+ }
+
+ inline XMP_Uns32 int32ToSynch ( XMP_Int32 value ) {
+ XMP_Validate ( (0 <= 0x0FFFFFFF), "value too big", kXMPErr_InternalFailure );
+ XMP_Uns32 r = (value & 0x0000007F) + ((value & 0x00003F80) << 1) +
+ ((value & 0x001FC000) << 2) + ((value & 0x0FE00000) << 3);
+ return r;
+ }
+
+ // =============================================================================================
+
+ bool InitializeGlobals();
+ void TerminateGlobals();
+
+ // =============================================================================================
+
+ class ID3Header { // Minimal support to read and write the ID3 header.
+ public:
+
+ const static size_t o_id = 0;
+ const static size_t o_vMajor = 3;
+ const static size_t o_vMinor = 4;
+ const static size_t o_flags = 5;
+ const static size_t o_size = 6;
+
+ const static size_t kID3_TagHeaderSize = 10; // This is the same in v2.2, v2.3, and v2.4.
+ char fields [kID3_TagHeaderSize];
+
+ ~ID3Header() {};
+
+ // Read the v2 header into the fields buffer and check the version.
+ bool read ( XMP_IO* file );
+
+ // Set the size and write the the v2 header from the fields buffer.
+ void write ( XMP_IO* file, XMP_Int64 tagSize );
+
+ };
+
+ // =============================================================================================
+
+ class ID3v2Frame {
+ public:
+
+ // Applies to ID3 v2.2, v2.3, and v2.4. The metadata values are mostly the same, v2.2 has
+ // smaller frame headers and only supports UTF-16 Unicode.
+
+ 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 kV23_FrameHeaderSize = 10; // The header for v2.3 and v2.4.
+ const static int kV22_FrameHeaderSize = 6; // The header for v2.2.
+ char fields [kV23_FrameHeaderSize];
+
+ XMP_Uns32 id;
+ XMP_Uns16 flags;
+
+ char* content;
+ XMP_Int32 contentSize; // size of variable content, right as its stored in o_size
+
+ 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
+
+ ID3v2Frame();
+ ID3v2Frame ( XMP_Uns32 id );
+
+ ID3v2Frame ( const ID3v2Frame& orig ) {
+ XMP_Throw ( "ID3v2Frame copy constructor not implemented", kXMPErr_InternalFailure );
+ }
+
+ ~ID3v2Frame() { this->release(); }
+
+ void release();
+
+ void setFrameValue ( const std::string& rawvalue, bool needDescriptor = false,
+ bool utf16 = false, bool isXMPPRIVFrame = false, bool needEncodingByte = true );
+
+ XMP_Int64 read ( XMP_IO* file, XMP_Uns8 majorVersion );
+ void write ( XMP_IO* file, XMP_Uns8 majorVersion );
+
+ // 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 );
+
+ // 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 logicalID, std::string* utf8string );
+
+ };
+
+ // =============================================================================================
+
+ class ID3v1Tag { // Support for the fixed length v1 tag found at the end of the file.
+ public:
+
+ 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_zero = 125; // must be zero for trackNo to be valid
+ const static XMP_Uns16 o_trackNo = 126; // trackNo
+ const static XMP_Uns16 o_genre = 127; // last byte: index, or 255
+
+ const static int kV1_TagSize = 128;
+
+ bool read ( XMP_IO* file, SXMPMeta* meta );
+ void write ( XMP_IO* file, SXMPMeta* meta );
+
+ };
+
+}
+
+#endif // __ID3_Support_hpp__
diff --git a/XMPFiles/source/FormatSupport/IFF/Chunk.cpp b/XMPFiles/source/FormatSupport/IFF/Chunk.cpp
new file mode 100644
index 0000000..a2de741
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IFF/Chunk.cpp
@@ -0,0 +1,1182 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/IFF/Chunk.h"
+#include "source/XMP_LibUtils.hpp"
+#include "source/XIO.hpp"
+
+#include <cstdio>
+#include <typeinfo>
+
+using namespace IFF_RIFF;
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::createChunk(...)
+//
+// Purpose: [static] Static factory to create an unknown chunk
+//
+//-----------------------------------------------------------------------------
+
+Chunk* Chunk::createChunk( const IEndian& endian )
+{
+ return new Chunk( endian );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::createUnknownChunk(...)
+//
+// Purpose: [static] Static factory to create an unknown chunk with initial id,
+// sizes and offsets.
+//
+//-----------------------------------------------------------------------------
+
+Chunk* Chunk::createUnknownChunk(
+ const IEndian& endian,
+ const XMP_Uns32 id,
+ const XMP_Uns32 type,
+ const XMP_Uns64 size,
+ const XMP_Uns64 originalOffset,
+ const XMP_Uns64 offset
+)
+{
+ Chunk *chunk = new Chunk( endian );
+ chunk->setID( id );
+ chunk->mOriginalOffset = originalOffset;
+ chunk->mOffset = offset;
+
+ if (type != 0)
+ {
+ chunk->setType(type);
+ }
+
+ // sizes have to be set after type, otherwise the setType sets the size to 4.
+ chunk->mSize = chunk->mOriginalSize = size;
+ chunk->mChunkMode = CHUNK_UNKNOWN;
+ chunk->mDirty = false;
+ return chunk;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::createHeaderChunk(...)
+//
+// Purpose: [static] Static factory to create a leaf chunk with no data area or
+// only the type in the data area
+//
+//-----------------------------------------------------------------------------
+
+Chunk* Chunk::createHeaderChunk( const IEndian& endian, const XMP_Uns32 id, const XMP_Uns32 type /*= kType_NONE*/)
+{
+ Chunk *chunk = new Chunk( endian );
+ chunk->setID( id );
+
+ XMP_Uns64 size = 0;
+
+ if( type != kType_NONE )
+ {
+ chunk->setType( type );
+ size += Chunk::TYPE_SIZE;
+ }
+
+ chunk->mSize = size;
+ chunk->mOriginalSize = size;
+ chunk->mChunkMode = CHUNK_LEAF;
+ chunk->mDirty = false;
+
+ return chunk;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::Chunk(...)
+//
+// Purpose: ctor/dtor
+//
+//-----------------------------------------------------------------------------
+
+Chunk::Chunk( const IEndian& endian )
+: mEndian( endian )
+{
+ // initialize private instance variables
+ mChunkId.id = kChunk_NONE;
+ mChunkId.type = kType_NONE;
+ mSize = 0;
+ mOriginalSize = 0;
+ mBufferSize = 0;
+ mData = NULL;
+ mParent = NULL;
+ mOriginalOffset = 0;
+ mOffset = 0;
+ mDirty = false;
+ mChunkMode = CHUNK_UNKNOWN;
+}
+
+
+Chunk::~Chunk()
+{
+ for( ChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ )
+ {
+ delete *iter;
+ }
+
+ // Free allocated data buffer
+ if( mData != NULL )
+ {
+ delete [] mData;
+ }
+}
+
+
+/************************ IChunk interface implementation ************************/
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::getData(...)
+//
+// Purpose: access data area of Chunk
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 Chunk::getData( const XMP_Uns8** data ) const
+{
+ if( data == NULL )
+ {
+ XMP_Throw ( "Invalid data pointer.", kXMPErr_BadParam );
+ }
+
+ *data = mData;
+
+ return mBufferSize;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::setData(...)
+//
+// Purpose: Set new data for the chunk.
+// Will delete an existing internal buffer and recreate a new one
+// and copy the given data into that new buffer.
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::setData( const XMP_Uns8* const data, XMP_Uns64 size, XMP_Bool writeType /*=false*/ )
+{
+ // chunk nodes cannot contain data
+ if ( mChunkMode == CHUNK_NODE )
+ {
+ XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam );
+ }
+ else if ( data == NULL || size == 0 )
+ {
+ XMP_Throw ( "Invalid data pointer.", kXMPErr_BadParam );
+ }
+
+ if( mData != NULL )
+ {
+ delete [] mData;
+ }
+
+ if( writeType )
+ {
+ mBufferSize = size + TYPE_SIZE;
+ mData = new XMP_Uns8[static_cast<size_t>(mBufferSize)]; // Throws bad_alloc exception in case of being out of memory
+ setType( mChunkId.type );
+ memcpy( &mData[TYPE_SIZE], data, static_cast<size_t>(size) );
+ }
+ else
+ {
+ mBufferSize = size;
+ mData = new XMP_Uns8[static_cast<size_t>(mBufferSize)]; // Throws bad_alloc exception in case of being out of memory
+ // ! We assume that size IS the actual size of that input buffer, otherwise behavior is undefined
+ memcpy( mData, data, static_cast<size_t>(size) );
+
+ // set the type variable
+ if( mBufferSize >= TYPE_SIZE )
+ {
+ //Chunk type is always BE
+ //The first four bytes could be the type
+ mChunkId.type = BigEndian::getInstance().getUns32( mData );
+ }
+ }
+
+ mChunkMode = CHUNK_LEAF;
+ setChanged();
+ adjustSize();
+}
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::getUns32(...)
+//
+// Purpose: The following methods are getter/setter for certain data types.
+// They always take care of little-endian/big-endian issues.
+// The offset starts at the data area of the Chunk.
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns32 Chunk::getUns32( XMP_Uns64 offset ) const
+{
+ if( offset + sizeof(XMP_Uns32) > mBufferSize )
+ {
+ XMP_Throw ( "Data access out of bounds", kXMPErr_BadIndex );
+ }
+ return mEndian.getUns32( &mData[offset] );
+}
+
+
+void Chunk::setUns32( XMP_Uns32 value, XMP_Uns64 offset )
+{
+ // chunk nodes cannot contain data
+ if ( mChunkMode == CHUNK_NODE )
+ {
+ XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam );
+ }
+
+ // If the new value exceeds the size of the buffer, recreate the buffer
+ adjustInternalBuffer( offset + sizeof(XMP_Uns32) );
+ // Write the new value
+ mEndian.putUns32( value, &mData[offset] );
+ // Chunk becomes leaf chunk when adding data
+ mChunkMode = CHUNK_LEAF;
+ // Flag the chunk as dirty
+ setChanged();
+ // If the buffer is bigger than the Chunk size, adjust the Chunk size
+ adjustSize();
+
+}
+
+
+XMP_Uns64 Chunk::getUns64( XMP_Uns64 offset ) const
+{
+ if( offset + sizeof(XMP_Uns64) > mBufferSize )
+ {
+ XMP_Throw ( "Data access out of bounds", kXMPErr_BadIndex );
+ }
+ return mEndian.getUns64( &mData[offset] );
+}
+
+
+void Chunk::setUns64( XMP_Uns64 value, XMP_Uns64 offset )
+{
+ // chunk nodes cannot contain data
+ if ( mChunkMode == CHUNK_NODE )
+ {
+ XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam );
+ }
+
+ // If the new value exceeds the size of the buffer, recreate the buffer
+ adjustInternalBuffer( offset + sizeof(XMP_Uns64) );
+ // Write the new value
+ mEndian.putUns64( value, &mData[offset] );
+ // Chunk becomes leaf chunk when adding data
+ mChunkMode = CHUNK_LEAF;
+ // Flag the chunk as dirty
+ setChanged();
+ // If the buffer is bigger than the Chunk size, adjust the Chunk size
+ adjustSize();
+}
+
+
+XMP_Int32 Chunk::getInt32( XMP_Uns64 offset ) const
+{
+ if( offset + sizeof(XMP_Int32) > mBufferSize )
+ {
+ XMP_Throw ( "Data access out of bounds", kXMPErr_BadIndex );
+ }
+ return mEndian.getUns32( &mData[offset] );
+}
+
+
+void Chunk::setInt32( XMP_Int32 value, XMP_Uns64 offset )
+{
+ // chunk nodes cannot contain data
+ if ( mChunkMode == CHUNK_NODE )
+ {
+ XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam );
+ }
+
+ // If the new value exceeds the size of the buffer, recreate the buffer
+ adjustInternalBuffer( offset + sizeof(XMP_Int32) );
+ // Write the new value
+ mEndian.putUns32( value, &mData[offset] );
+ // Chunk becomes leaf chunk when adding data
+ mChunkMode = CHUNK_LEAF;
+ // Flag the chunk as dirty
+ setChanged();
+ // If the buffer is bigger than the Chunk size, adjust the Chunk size
+ adjustSize();
+}
+
+
+XMP_Int64 Chunk::getInt64( XMP_Uns64 offset ) const
+{
+ if( offset + sizeof(XMP_Int64) > mBufferSize )
+ {
+ XMP_Throw ( "Data access out of bounds", kXMPErr_BadIndex );
+ }
+ return mEndian.getUns64( &mData[offset] );
+}
+
+
+void Chunk::setInt64( XMP_Int64 value, XMP_Uns64 offset )
+{
+ // chunk nodes cannot contain data
+ if ( mChunkMode == CHUNK_NODE )
+ {
+ XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam );
+ }
+
+ // If the new value exceeds the size of the buffer, recreate the buffer
+ adjustInternalBuffer( offset + sizeof(XMP_Int64) );
+ // Write the new value
+ mEndian.putUns64( value, &mData[offset] );
+ // Chunk becomes leaf chunk when adding data
+ mChunkMode = CHUNK_LEAF;
+ // Flag the chunk as dirty
+ setChanged();
+ // If the buffer is bigger than the Chunk size, adjust the Chunk size
+ adjustSize();
+}
+
+
+std::string Chunk::getString( XMP_Uns64 size /*=0*/, XMP_Uns64 offset /*=0*/ ) const
+{
+ if( offset + size > mBufferSize )
+ {
+ XMP_Throw ( "Data access out of bounds", kXMPErr_BadIndex );
+ }
+
+ XMP_Uns64 requestedSize = size != 0 ? size : mBufferSize - offset;
+
+ std::string str((char *)&mData[offset],static_cast<size_t>(requestedSize));
+
+ return str;
+}
+
+
+void Chunk::setString( std::string value, XMP_Uns64 offset )
+{
+ if ( mChunkMode == CHUNK_NODE )
+ {
+ XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam );
+ }
+
+ // If the new value exceeds the size of the buffer, recreate the buffer
+ adjustInternalBuffer( offset + value.length() );
+ // Write the new value
+ memcpy( &mData[offset], value.data(), value.length() );
+ // Chunk becomes leaf chunk when adding data
+ mChunkMode = CHUNK_LEAF;
+ // Flag the chunk as dirty
+ setChanged();
+ // If the buffer is bigger than the Chunk size, adjust the Chunk size
+ adjustSize();
+}
+
+
+/************************ Chunk public methods ************************/
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::setID(...)
+//
+// Purpose: Sets the chunk id.
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::setID( XMP_Uns32 id )
+{
+ mChunkId.id = id;
+ setChanged();
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::setType(...)
+//
+// Purpose: Sets the chunk type
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::setType( XMP_Uns32 type )
+{
+ mChunkId.type = type;
+
+ // reserve space for type
+ // setChanged() and adjustSize() implicitly called
+ // make sure that no exception is thrown
+ ChunkMode existing = mChunkMode;
+ mChunkMode = CHUNK_UNKNOWN;
+ setUns32(0, 0);
+ mChunkMode = existing;
+
+ BigEndian::getInstance().putUns32( type, mData );
+}
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::getPadSize(...)
+//
+// Purpose: Returns the original size of the Chunk including a pad byte if
+// the size isn't a even number
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 Chunk::getOriginalPadSize( bool includeHeader /*= false*/ ) const
+{
+ XMP_Uns64 ret = this->getOriginalSize( includeHeader );
+
+ if( ret & 1 )
+ {
+ ret++;
+ }
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::getPadSize(...)
+//
+// Purpose: Returns the current size of the Chunk including a pad byte if the
+// size isn't a even number
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 Chunk::getPadSize( bool includeHeader /*= false*/ ) const
+{
+ XMP_Uns64 ret = this->getSize( includeHeader );
+
+ if( ret & 1 )
+ {
+ ret++;
+ }
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::calculateSize(...)
+//
+// Purpose: Calculate the size of this chunks based on its children sizes.
+// If this chunk has no children then no new size will be calculated.
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 Chunk::calculateSize( bool setOriginal /*= false*/ )
+{
+ XMP_Uns64 size = 0LL;
+
+ //
+ // calculate only foe nodes
+ //
+ if( this->getChunkMode() == CHUNK_NODE )
+ {
+ //
+ // calculate size of all children
+ //
+ for( ChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ )
+ {
+ XMP_Uns64 childSize = (*iter)->getSize(true);
+
+ size += childSize;
+
+ //
+ // take account of pad byte
+ //
+ if( childSize & 1 )
+ {
+ size++;
+ }
+ }
+
+ //
+ // assume that we have a type
+ //
+ size += Chunk::TYPE_SIZE;
+
+ //
+ // set dirty flag only if something has changed
+ //
+ if( size != mSize || ( setOriginal && size != mOriginalSize ) )
+ {
+ this->setChanged();
+ }
+
+ //
+ // set new size(s)
+ //
+ if( setOriginal )
+ {
+ mOriginalSize = size;
+ }
+
+ mSize = size;
+ }
+ else
+ size = mSize;
+
+ return size;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::setOffset(...)
+//
+// Purpose: Adjust the offset that this chunk has within the file
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::setOffset (XMP_Uns64 newOffset) // changes during rearranging
+{
+ XMP_Uns64 oldOffset = mOffset;
+ mOffset = newOffset;
+
+ if( mOffset != oldOffset )
+ {
+ setChanged();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::resetChanges(...)
+//
+// Purpose: Resets the dirty status for this chunk and its children to false
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::resetChanges()
+{
+ mDirty = false;
+
+ for( ChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ )
+ {
+ (*iter)->resetChanges();
+ }
+} //resetChanges
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::setAsNew(...)
+//
+// Purpose: Sets all necessary member variables to flag this chunk as a new one
+// being inserted into the tree
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::setAsNew()
+{
+ mOriginalSize = mSize;
+ mOriginalOffset = mOffset;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::toString(...)
+//
+// Purpose: Creates a string representation of the chunk (debug method)
+//
+//-----------------------------------------------------------------------------
+
+std::string Chunk::toString( std::string tabs, XMP_Bool showOriginal )
+{
+ const BigEndian &BE = BigEndian::getInstance();
+ char buffer[256];
+ XMP_Uns32 id = BE.getUns32(&this->mChunkId.id);
+ XMP_Uns32 type = BE.getUns32(&this->mChunkId.type);
+
+ XMP_Uns64 size, offset;
+
+ if ( showOriginal )
+ {
+ size = mEndian.getUns64(&this->mOriginalSize);
+ offset = mEndian.getUns64(&this->mOriginalOffset);
+ }
+ else
+ {
+ size = mEndian.getUns64(&this->mSize);
+ offset = mEndian.getUns64(&this->mOffset);
+ }
+
+ snprintf( buffer, 255, "%.4s -- "
+ "size: 0x%.8llX, "
+ "type: %.4s, "
+ "offset: 0x%.8llX",
+ (char*)(&id),
+ size,
+ (char*)(&type),
+ offset );
+ std::string str(buffer);
+
+ // Dump children
+ if ( mChildren.size() > 0)
+ {
+ tabs.append("\t");
+ }
+
+ for( ChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ )
+ {
+ str += "\n";
+ str += tabs;
+ str += (*iter)->toString(tabs , showOriginal);
+ }
+
+ return str;
+}
+
+
+/************************ file access ************************/
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::readChunk(...)
+//
+// Purpose: Read id, size and offset and create a chunk with mode CHUNK_UNKNOWN.
+// The file is expected to be open and is not closed!
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::readChunk( XMP_IO* file )
+{
+ if( file == NULL )
+ {
+ XMP_Throw( "Chunk::readChunk: Must pass a valid file pointer", kXMPErr_BadParam );
+ }
+
+ if( mChunkId.id != kChunk_NONE )
+ {
+ XMP_Throw ( "readChunk must not be called more than once", kXMPErr_InternalFailure );
+ }
+ // error handling is done in the controller
+ // determine offset in the file
+ mOriginalOffset = mOffset = file->Offset();
+ //ID is always BE
+ mChunkId.id = XIO::ReadUns32_BE( file );
+ // Size can be both
+ if (typeid(mEndian) == typeid(LittleEndian))
+ {
+ mOriginalSize = mSize = XIO::ReadUns32_LE( file );
+
+ }
+ else
+ {
+ mOriginalSize = mSize = XIO::ReadUns32_BE( file );
+ }
+
+ // For Type do not assume any format as it could be data, read it as bytes
+ if (mSize >= TYPE_SIZE)
+ {
+ mData = new XMP_Uns8[TYPE_SIZE];
+
+ for ( XMP_Uns32 i = 0; i < TYPE_SIZE ; i++ )
+ {
+ mData[i] = XIO::ReadUns8( file );
+ }
+ //Chunk type is always BE
+ //The first four bytes could be the type
+ mChunkId.type = BigEndian::getInstance().getUns32( mData );
+ }
+
+ mDirty = false;
+}//readChunk
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::cacheChunkData(...)
+//
+// Purpose: Stores the data in the class (only called if required).
+// The file is expected to be open and is not closed!
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::cacheChunkData( XMP_IO* file )
+{
+ XMP_Enforce( file != NULL );
+
+ if( mChunkMode != CHUNK_UNKNOWN )
+ {
+ XMP_Throw ( "chunk already has either data or children.", kXMPErr_BadParam );
+ }
+
+ // error handling is done in the controller
+
+ // continue only when the chunk contains data
+ if (mSize != 0)
+ {
+ mBufferSize = mSize;
+ XMP_Uns8* tmp = new XMP_Uns8[XMP_Uns32(mSize)];
+
+ // Do we have a type?
+ if (mSize >= TYPE_SIZE)
+ {
+ // add type in front of new buffer
+ for ( XMP_Uns32 i = 0; i < TYPE_SIZE ; i++ )
+ {
+ tmp[i] = mData[i];
+ }
+ // Read rest of data from file
+ if( mSize != TYPE_SIZE )
+ {
+ // Chunks that are cached are very probably not bigger than 2GB, so cast is safe
+ file->ReadAll ( &tmp[TYPE_SIZE], static_cast<XMP_Int32>(mSize - TYPE_SIZE) );
+ }
+ }
+ else
+ {
+ // Chunks that are cached are very probably not bigger than 2GB, so cast is safe
+ file->ReadAll ( tmp, static_cast<XMP_Int32>(mSize) );
+ }
+ // deletes the existing array
+ delete [] mData;
+ //assign the new buffer
+ mData = tmp;
+ }
+
+ // Remember that this method has been called
+ mDirty = false;
+ mChunkMode = CHUNK_LEAF;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::writeChunk(...)
+//
+// Purpose: Write or updates chunk (new data, new size, new position).
+// The file is expected to be open and is not closed!
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::writeChunk( XMP_IO* file )
+{
+ if( file == NULL )
+ {
+ XMP_Throw( "Chunk::writeChunk: Must pass a valid file pointer", kXMPErr_BadParam );
+ }
+
+ if (mChunkMode == CHUNK_UNKNOWN)
+ {
+ if (hasChanged())
+ {
+ XMP_Throw ( "A chunk with mode unknown must not be changed & written.", kXMPErr_BadParam );
+ }
+
+ // do nothing
+ }
+ else if (hasChanged())
+ {
+ // positions the file pointer
+ file->Seek ( mOffset, kXMP_SeekFromStart );
+
+
+ // ============ This part is identical for CHUNK_LEAF and CHUNK_TYPE ============
+
+ // writes ID (starting with offset)
+ XIO::WriteInt32_BE( file, mChunkId.id );
+
+ // writes size, which is always 32bit
+ XMP_Uns32 outSize = ( mSize >= 0x00000000FFFFFFFF ? 0xFFFFFFFF : static_cast<XMP_Uns32>( mSize & 0x00000000FFFFFFFF ) );
+
+ if (typeid(mEndian) == typeid(LittleEndian))
+ {
+ XIO::WriteUns32_LE( file, static_cast<XMP_Uns32>(mSize) );
+ }
+ else
+ {
+ XIO::WriteUns32_BE( file, static_cast<XMP_Uns32>(mSize) );
+ }
+
+
+ // ============ This part is different for CHUNK_LEAF and CHUNK_TYPE ============
+ if (mChunkMode == CHUNK_LEAF)
+ {
+ // writes buffer (including the optional type at the beginning)
+ // Cached chunks will very probably not be bigger than 2GB, so cast is safe
+ file->Write ( mData, static_cast<XMP_Int32>(mSize) );
+ if ( mSize % 2 == 1 )
+ {
+ // for odd file sizes, a pad byte is written
+ XIO::WriteUns8 ( file, 0 );
+ }
+ }
+ else // mChunkMode == CHUNK_NODE
+ {
+ // writes type if defined
+ if (mChunkId.type != kType_NONE)
+ {
+ XIO::WriteInt32_BE( file, mChunkId.type );
+ }
+
+ // calls writeChunk on it's children
+ for( ChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ )
+ {
+ (*iter)->writeChunk( file );
+ }
+ }
+ }
+
+ // set back dirty state
+ mDirty = false;
+}
+
+
+/************************ children access ************************/
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::numChildren(...)
+//
+// Purpose: Returns the number children chunks
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns32 Chunk::numChildren() const
+{
+ return static_cast<XMP_Uns32>( mChildren.size() );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::getChildAt(...)
+//
+// Purpose: Returns a child node
+//
+//-----------------------------------------------------------------------------
+
+Chunk* Chunk::getChildAt( XMP_Uns32 pos ) const
+{
+ try
+ {
+ return mChildren.at(pos);
+ }
+ catch( ... )
+ {
+ XMP_Throw ( "Non-existing child requested.", kXMPErr_BadIndex );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::appendChild(...)
+//
+// Purpose: Appends a child node at the end of the children list
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::appendChild( Chunk* child, XMP_Bool adjustSizes )
+{
+ if (mChunkMode == CHUNK_LEAF)
+ {
+ XMP_Throw ( "A chunk leaf cannot contain children.", kXMPErr_BadParam );
+ }
+
+ try
+ {
+ mChildren.push_back( child );
+ // make this the parent of the new node
+ child->mParent = this;
+ mChunkMode = CHUNK_NODE;
+
+ // set offset of new child
+ XMP_Uns64 childOffset = 0;
+
+ if( this->numChildren() == 1 )
+ {
+ // first added child
+ if( this->getID() != kChunk_NONE )
+ {
+ childOffset = this->getOffset() + Chunk::HEADER_SIZE + ( this->getType() == kType_NONE ? 0 : Chunk::TYPE_SIZE );
+ }
+ }
+ else
+ {
+ Chunk* predecessor = this->getChildAt( this->numChildren() - 2 );
+ childOffset = predecessor->getOffset() + predecessor->getPadSize( true );
+ }
+
+ child->setOffset( childOffset );
+
+ setChanged();
+
+ if ( adjustSizes )
+ {
+ // to fix the sizes of this node and parents
+ adjustSize( child->getSize(true) );
+ }
+ }
+ catch (...)
+ {
+ XMP_Throw ( "Vector error in appendChild", kXMPErr_InternalFailure );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::insertChildAt(...)
+//
+// Purpose: Inserts a child node at a certain position
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::insertChildAt( XMP_Uns32 pos, Chunk* child )
+{
+ if (mChunkMode == CHUNK_LEAF)
+ {
+ XMP_Throw ( "A chunk leaf cannot contain children.", kXMPErr_BadParam );
+ }
+
+ try
+ {
+ if (pos <= mChildren.size())
+ {
+ mChildren.insert(mChildren.begin() + pos, child);
+ // make this the parent of the new node
+ child->mParent = this;
+ mChunkMode = CHUNK_NODE;
+
+ // set offset of new child
+ XMP_Uns64 childOffset = 0;
+
+ if( pos == 0 )
+ {
+ if( this->getID() != kChunk_NONE )
+ {
+ childOffset = this->getOffset() + Chunk::HEADER_SIZE + ( this->getType() == kType_NONE ? 0 : Chunk::TYPE_SIZE );
+ }
+ }
+ else
+ {
+ Chunk* predecessor = this->getChildAt( pos-1 );
+ childOffset = predecessor->getOffset() + predecessor->getPadSize( true );
+ }
+
+ child->setOffset( childOffset );
+
+ setChanged();
+
+ // to fix the sizes of this node and parents
+ adjustSize( child->getSize(true) );
+ }
+ else
+ {
+ XMP_Throw ( "Index not valid.", kXMPErr_BadIndex );
+ }
+ }
+ catch (...)
+ {
+ XMP_Throw ( "Index not valid.", kXMPErr_BadIndex );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::removeChildAt(...)
+//
+// Purpose: Removes a child node at a given position
+//
+//-----------------------------------------------------------------------------
+
+Chunk* Chunk::removeChildAt( XMP_Uns32 pos )
+{
+ Chunk* toDelete = NULL;
+
+ try
+ {
+ toDelete = mChildren.at(pos);
+ // to fix the size of this node
+ XMP_Int64 sizeDeleted = static_cast<XMP_Int64>(toDelete->getSize(true));
+ mChildren.erase(mChildren.begin() + pos);
+
+ setChanged();
+
+ // to fix the sizes of this node and parents
+ adjustSize(-sizeDeleted);
+ }
+ catch (...)
+ {
+ XMP_Throw ( "Index not valid.", kXMPErr_BadIndex );
+ }
+
+ return toDelete;
+}
+
+//-----------------------------------------------------------------------------
+//
+// replaceChildAt(...)
+//
+// Purpose: Remove child at the passed position and insert the new chunk
+//
+//-----------------------------------------------------------------------------
+
+Chunk* Chunk::replaceChildAt( XMP_Uns32 pos, Chunk* child )
+{
+ Chunk* toDelete = NULL;
+
+ try
+ {
+ //
+ // removed old chunk
+ //
+ toDelete = mChildren.at(pos);
+ mChildren.erase(mChildren.begin() + pos);
+
+ //
+ // insert new chunk
+ //
+ mChildren.insert(mChildren.begin() + pos, child);
+ // make this the parent of the new node
+ child->mParent = this;
+ mChunkMode = CHUNK_NODE;
+
+ // set offset
+ child->setOffset( toDelete->getOffset() );
+
+ setChanged();
+
+ // to fix the sizes of this node and parents
+ adjustSize( child->getPadSize() - toDelete->getPadSize() );
+ }
+ catch (...)
+ {
+ XMP_Throw ( "Index not valid.", kXMPErr_BadIndex );
+ }
+
+ return toDelete;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::firstChild(...)
+//
+// Purpose: iterators
+//
+//-----------------------------------------------------------------------------
+
+Chunk::ConstChunkIterator Chunk::firstChild() const
+{
+ return mChildren.begin();
+}
+
+
+Chunk::ConstChunkIterator Chunk::lastChild() const
+{
+ return mChildren.end();
+}
+
+
+/******************* Private Methods ***************************/
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::setChanged(...)
+//
+// Purpose: Sets this node and all of its parents up to the tree root dirty
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::setChanged()
+{
+ mDirty = true;
+
+ if (mParent != NULL)
+ {
+ mParent->setChanged();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::adjustInternalBuffer(...)
+//
+// Purpose: Resizes the internal byte buffer to the given size if the new size
+// is bigger than the current one.
+// If the new size is smaller, the buffer is not adjusted
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::adjustInternalBuffer( XMP_Uns64 newSize )
+{
+ // only adjust if the new size is bigger than the old one.
+ // If it is smaller, leave the buffer alone
+ if( newSize > mBufferSize )
+ {
+ XMP_Uns8 *tmp = new XMP_Uns8[static_cast<size_t>(newSize)]; // Might throw bad_alloc exception
+
+ // Do we have an old buffer?
+ if( mData != NULL )
+ {
+ // Copy it to the new one and delete the old one
+ memcpy( tmp, mData, static_cast<size_t>(mBufferSize) );
+
+ delete [] mData;
+ }
+ mData = tmp;
+ mBufferSize = newSize;
+ }
+}//adjustInternalBuffer
+
+
+//-----------------------------------------------------------------------------
+//
+// Chunk::adjustSize(...)
+//
+// Purpose: Adjusts the chunk size and the parents chunk sizes
+//
+//-----------------------------------------------------------------------------
+
+void Chunk::adjustSize( XMP_Int64 sizeChange )
+{
+ // Calculate leaf sizeChange
+ if (mChunkMode == CHUNK_LEAF)
+ {
+ // Note: The leave nodes size is equal to the buffer size can have odd and even sizes.
+ XMP_Uns64 sizeInclPad = mSize + (mSize % 2);
+ sizeChange = mBufferSize - sizeInclPad;
+ mSize = mBufferSize;
+
+ // if the difference is odd, the corrected even size has be incremented by 1
+ sizeChange += abs(sizeChange % 2);
+ }
+ else // mChunkMode == CHUNK_NODE/CHUNK_UNKNOWN
+ {
+ // if the difference is odd, the corrected even size has be incremented by 1
+ // (or decremented by 1 when < 0).
+ sizeChange += sizeChange % 2;
+
+ // the chunk node gets the corrected (odd->even) size
+ mSize += sizeChange;
+ }
+
+
+ if (mParent != NULL)
+ {
+ // adjusts the parents size with the corrected (odd->even) size difference of this node
+ mParent->adjustSize(sizeChange);
+ }
+}//adjustSize
diff --git a/XMPFiles/source/FormatSupport/IFF/Chunk.h b/XMPFiles/source/FormatSupport/IFF/Chunk.h
new file mode 100644
index 0000000..2d170ed
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IFF/Chunk.h
@@ -0,0 +1,462 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _Chunk_h_
+#define _Chunk_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+
+#include "source/Endian.h"
+#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h"
+#include "XMPFiles/source/FormatSupport/IFF/IChunkData.h"
+#include "XMPFiles/source/FormatSupport/IFF/IChunkContainer.h"
+
+namespace IFF_RIFF
+{
+ /**
+ * CHUNK_UNKNOWN = Either new chunk or a chunk that was read, but not cached
+ * (it is not decided yet whether it becones a node or leaf or is not cached at all)
+ * CHUNK_NODE = Node chunk that contains children, but no own data (except the optional type)
+ * CHUNK_LEAF = Leaf chunk that contains data but no children
+ */
+ enum ChunkMode { CHUNK_UNKNOWN = 0, CHUNK_NODE = 1, CHUNK_LEAF = 2 };
+
+/**
+ * Each Chunk of the IFF/RIFF based file formats (e.g. WAVE, AVI, AIFF) are represented by
+ * instances of the class Chunk.
+ * A chunk can be a node chunk containing children, a leaf chunk containing data or an "unknown" chunk,
+ * which means that its content has not cached/loaded yet (or will never be during the file handling);
+ * see ChunkMode for more details.
+ *
+ * Note: A Chunk can either have a chunk OR a list of child chunks, but never both.
+ *
+ * This class provides access to the children or the data of a chunk, depending of the type.
+ * It keeps track of its size. When the size is changed (by adding or removing data/ or children),
+ * the size is also fixed for the parent hierarchy.
+ *
+ * The dirty flag (hasChanged()) that is set for each change of a chunk is also promoted to the parents.
+ * The chunk stores its original and new offset within the host file, but its *not* automatically correctin the offset;
+ * this is done by the IChunkBehavior class.
+ * The Chunk class provides an interface to iterate through the tree structure of Chunks.
+ * There are methods to insert, remove and move Chunk's in its children tree structure.
+ *
+ * The chunk can read itself from a host file (readChunk()), but it does not automatically read its children,
+ * because they are not necessarily used by the file handler.
+ * The method writeChunk() recurses through the complete chunk tree and writes the *changed* chunks back to the host file.
+ * It is important that the offsets have been fixed before.
+ *
+ * Table about endianess in the different RIFF file formats:
+ *
+ * | ID size type data
+ * -----------------------------------------
+ * AVI | BE LE BE LE
+ * WAV | BE LE BE LE
+ * AIFF | BE BE BE BE
+ */
+class Chunk : public IChunkData,
+ public IChunkContainer
+{
+ public:
+ /** Factory to create an empty chunk */
+ static Chunk* createChunk( const IEndian& endian );
+
+ /** Factory to create an empty chunk */
+ static Chunk* createUnknownChunk(
+ const IEndian& endian,
+ const XMP_Uns32 id,
+ const XMP_Uns32 type,
+ const XMP_Uns64 size,
+ const XMP_Uns64 originalOffset = 0,
+ const XMP_Uns64 offset = 0
+ );
+
+ /** Static factory to create a leaf chunk with no data area or only the type in the data area */
+ static Chunk* createHeaderChunk( const IEndian& endian, const XMP_Uns32 id, const XMP_Uns32 type = kType_NONE );
+
+ /**
+ * dtor
+ */
+ ~Chunk();
+
+
+ //===================== IChunkData interface implementation ================
+
+ /**
+ * Get the chunk ID
+ *
+ * @return Return the ID, 0 if the chunk does not have an ID.
+ */
+ inline XMP_Uns32 getID() const { return mChunkId.id; }
+
+ /**
+ * Get the chunk type (if available)
+ * (the first four data bytes of the chunk could be a chunk type)
+ *
+ * @return Return the type, kType_NONE if the chunk does not contain data.
+ */
+ inline XMP_Uns32 getType() const { return mChunkId.type; }
+
+ /**
+ * Get the chunk identifier [id and type]
+ *
+ * @return Return the identifier
+ */
+ inline const ChunkIdentifier& getIdentifier() const { return mChunkId; }
+
+ /**
+ * Access the data of the chunk.
+ *
+ * @param data OUT pointer to the byte array
+ * @return size of the data block, 0 if no data is available
+ */
+ XMP_Uns64 getData( const XMP_Uns8** data ) const;
+
+ /**
+ * Set new data for the chunk.
+ * Will delete an existing internal buffer and recreate a new one
+ * and copy the given data into that new buffer.
+ *
+ * @param data pointer to the data to put into the chunk
+ * @param size Size of the data block
+ * @param writeType if true, the type of the chunk (getType()) is written in front of the data block.
+ */
+ void setData( const XMP_Uns8* const data, XMP_Uns64 size, XMP_Bool writeType = false );
+
+ /**
+ * Returns the current size of the Chunk.
+ *
+ * @param includeHeader if set, the returned size will be the whole chunk size including the header
+ * @return Returns either the size of the data block of the chunk or size of the whole chunk, including the eight byte header.
+ */
+ XMP_Uns64 getSize( bool includeHeader = false ) const { return includeHeader ? mSize + HEADER_SIZE : mSize; }
+
+ /**
+ * Returns the current size of the Chunk including a pad byte if the size isn't a even number
+ *
+ * @param includeHeader if set, the returned size will be the whole chunk size including the header
+ * @return Returns either the size of the data block of the chunk or size of the whole chunk, including the eight byte header.
+ */
+ XMP_Uns64 getPadSize( bool includeHeader = false ) const;
+ /**
+ * @return Returns the mode of the chunk (see ChunkMode definition).
+ */
+ ChunkMode getChunkMode() const { return mChunkMode; }
+
+ /* The following methods are getter/setter for certain data types.
+ * They always take care of little-endian/big-endian issues.
+ * The offset starts at the data area of the Chunk. */
+
+ XMP_Uns32 getUns32( XMP_Uns64 offset=0 ) const;
+ void setUns32( XMP_Uns32 value, XMP_Uns64 offset=0 );
+
+ XMP_Uns64 getUns64( XMP_Uns64 offset=0 ) const;
+ void setUns64( XMP_Uns64 value, XMP_Uns64 offset=0 );
+
+ XMP_Int32 getInt32( XMP_Uns64 offset=0 ) const;
+ void setInt32( XMP_Int32 value, XMP_Uns64 offset=0 );
+
+ XMP_Int64 getInt64( XMP_Uns64 offset=0 ) const;
+ void setInt64( XMP_Int64 value, XMP_Uns64 offset=0 );
+
+ std::string getString( XMP_Uns64 size = 0, XMP_Uns64 offset=0 ) const;
+ void setString( std::string value, XMP_Uns64 offset=0 );
+
+
+ //===================== IChunk interface implementation ================
+
+ //FIXME XMP exception if size cast from 64 to 32 looses data
+
+ /**
+ * Sets the chunk id.
+ */
+ void setID( XMP_Uns32 id );
+
+ /**
+ * Sets the chunk type.
+ */
+ void setType( XMP_Uns32 type );
+
+ /**
+ * Sets the chunk size.
+ * NOTE: Should only be used for repairing wrong sizes in files (repair flag).
+ * Normally Size is changed by changing the data automatically!
+ */
+ inline void setSize( XMP_Uns64 newSize, bool setOriginal = false ) { mDirty = mSize != newSize; mSize = newSize; mOriginalSize = setOriginal ? newSize : mOriginalSize; }
+
+ /**
+ * Calculate the size of this chunks based on its children sizes.
+ * If this chunk has no children then no new size will be calculated.
+ */
+ XMP_Uns64 calculateSize( bool setOriginal = false );
+
+ /**
+ * @return Returns the offset of the chunk within the stream.
+ */
+ inline XMP_Uns64 getOffset () const { return mOffset; }
+
+ /**
+ * @return Returns the original offset of the chunk within the stream.
+ */
+ inline XMP_Uns64 getOriginalOffset () const { return mOriginalOffset; }
+
+ /**
+ * Returns the original size of the Chunk
+ *
+ * @param includeHeader if set, the returned original size will be the whole chunk size including the header
+ * @return Returns the original size of the chunk within the stream (inluding/excluding headerSize).
+ */
+ inline XMP_Uns64 getOriginalSize( bool includeHeader = false ) const { return includeHeader ? mOriginalSize + HEADER_SIZE : mOriginalSize; }
+
+ /**
+ * Returns the original size of the Chunk including a pad byte if the size isn't a even number
+ *
+ * @param includeHeader if set, the returned size will be the whole chunk size including the header
+ * @return Returns either the size of the data block of the chunk or size of the whole chunk, including the eight byte header.
+ */
+ XMP_Uns64 getOriginalPadSize( bool includeHeader = false ) const;
+
+ /**
+ * Adjust the offset that this chunk has within the file.
+ *
+ * @param newOffset the new offset within the file stream
+ */
+ void setOffset (XMP_Uns64 newOffset); // changes during rearranging
+
+ /**
+ * Has the Chunk class changes, or has the position within the file been changed?
+ * If the result is true the chunk has to be written back to the file
+ * (all parent chunks are also set to dirty in that case).
+ *
+ * @return Returns true if the chunk node has been modified.
+ */
+ XMP_Bool hasChanged() const { return mDirty; }
+
+ /**
+ * Sets this node and all of its parents up to the tree root dirty.
+ */
+ void setChanged();
+
+ /**
+ * Resets the dirty status for this chunk and its children to false
+ */
+ void resetChanges();
+
+ /**
+ *Sets all necessary member variables to flag this chunk as a new one being inserted into the tree
+ */
+ void setAsNew();
+
+ /**
+ * @return Returns the parent chunk (can be NULL if this is the root of the tree).
+ */
+ inline Chunk* getParent() const { return mParent; }
+
+ /**
+ * Creates a string representation of the chunk (debug method).
+ */
+ std::string toString( std::string tabs = std::string() , XMP_Bool showOriginal = false );
+
+
+ //-------------------
+ // file access
+ //-------------------
+
+ /**
+ * Read id, size and offset and create a chunk with mode CHUNK_UNKNOWN.
+ * The file is expected to be open and is not closed!
+ *
+ * @param file File reference to read the chunk from
+ */
+ void readChunk( XMP_IO* file );
+
+ /**
+ * Stores the data in the class (only called if required).
+ * The file is expected to be open and is not closed!
+ *
+ * @param file File reference to cache the chunk data
+ */
+ void cacheChunkData( XMP_IO* file );
+
+ /**
+ * Write or updates chunk (new data, new size, new position).
+ * The file is expected to be open and is not closed!
+ *
+ * Behavior for the different chunk types:
+ *
+ * CHUNK_UNKNOWN:
+ * - does not write anything back
+ * - throws exception if hasChanged == true
+ *
+ * CHUNK_LEAF:
+ * - writes ID (starting with offset)
+ * - writes size
+ * - writes buffer (including the optional type at the beginning)
+ *
+ * CHUNK_NODE:
+ * - writes ID (starting with offset)
+ * - writes size
+ * - writes type if defined
+ * - calls writeChunk on it's children
+ *
+ * Note: readChunk() and optionally cacheChunkData() has to be called before!
+ *
+ * @param file File reference to write the chunk to
+ */
+ void writeChunk( XMP_IO* file );
+
+
+ //-------------------
+ // children access
+ //-------------------
+
+ /**
+ * @return Returns the number children chunks.
+ */
+ XMP_Uns32 numChildren() const;
+
+ /**
+ * Returns a child node.
+ *
+ * @param pos position of the child node to return
+ * @return Returns the child node at the given position.
+ */
+ Chunk* getChildAt( XMP_Uns32 pos ) const;
+
+ /**
+ * Appends a child node at the end of the children list.
+ *
+ * @param node the new node
+ * @param adjustSizes adjust size of chunk and parents
+ * @return Returns the added node.
+ */
+ void appendChild( Chunk* node, XMP_Bool adjustSizes = true );
+
+ /**
+ * Inserts a child node at a certain position.
+ *
+ * @param pos position in the children list to add the new node
+ * @param node the new node
+ * @return Returns the added node.
+ */
+ void insertChildAt( XMP_Uns32 pos, Chunk* node );
+
+ /**
+ * Removes a child node at a given position.
+ *
+ * @param pos position of the node to delete in the children list
+ *
+ * @return The removed chunk
+ */
+ Chunk* removeChildAt( XMP_Uns32 pos );
+
+ /**
+ * Remove child at the passed position and insert the new chunk
+ *
+ * @param pos Position of chunk that will be replaced
+ * @param chunk New chunk
+ *
+ * @return Replaced chunk
+ */
+ Chunk* replaceChildAt( XMP_Uns32 pos, Chunk* node );
+
+ //--------------------
+ // children iteration
+ //--------------------
+
+ typedef std::vector<Chunk*>::iterator ChunkIterator;
+ typedef std::vector<Chunk*>::const_iterator ConstChunkIterator;
+
+ ConstChunkIterator firstChild() const;
+
+ ConstChunkIterator lastChild() const;
+
+ /** The size of the header (id+size) */
+ static const XMP_Uns8 HEADER_SIZE = 8;
+ /** The size of the type */
+ static const XMP_Uns8 TYPE_SIZE = 4;
+
+
+ private:
+ /** stores the chunk header */
+ ChunkIdentifier mChunkId;
+ /** Original size of chunk without the header */
+ XMP_Uns64 mOriginalSize;
+ /** size of chunk without the header */
+ XMP_Uns64 mSize;
+ /** size of the internal buffer */
+ XMP_Uns64 mBufferSize;
+ /** buffer for the chunk data without the header, but including the type (first 4 bytes). */
+ XMP_Uns8* mData;
+ /** Buffer to hold the first 4 bytes that are used for either the type or as data.
+ * Only used for ReadChunk and CacheChunk */
+ ChunkMode mChunkMode;
+
+ /**
+ * Current position in stream (file). Can only be changed by moving the chunks around
+ * (by using an IChunkBehavior class).
+ * Note: Sizes are stored in chunk because it can be changed by the handler (i.e. by changing the data)
+ */
+ XMP_Uns64 mOriginalOffset;
+ /**
+ * New position of the chunk in the stream.
+ * It is initialized to MAXINT64 when there is no new offset.
+ * If the offset has been changed the dirty flag has to be set.
+ */
+ XMP_Uns64 mOffset;
+
+ /**
+ * The dirty flag indicates that the chunk (and all parent chunks) has been modified or moved and
+ * that it therefore needs to be written to file.
+ */
+ XMP_Bool mDirty; // has Chunk data changed? has Chunk position changed?
+
+ /** The parent of this node; only the root node does not have a parent. */
+ Chunk* mParent;
+
+ /** Stores the byte order for this node.
+ * Note: The endianess does not change within one file */
+ const IEndian& mEndian;
+
+ /** The list of child nodes. */
+ std::vector<Chunk*> mChildren;
+
+ /**
+ * private ctor, prevents direct invokation.
+ *
+ * @param endian Endian util
+ */
+ Chunk( const IEndian& endian );
+
+ /**
+ * Resizes the internal byte buffer to the given size if the new size is bigger than the current one.
+ * If the new size is smaller, the buffer is not adjusted
+ */
+ void adjustInternalBuffer( XMP_Uns64 newSize );
+
+ /**
+ * Adjusts the chunk size and the parents chunk sizes.
+ * - Leaf chunks always have the size of their data, inluding the 4-byte type and excluding the header.
+ * Leaf chunks can have an ODD size!
+ * - Node chunks have the added size of all of their children, including the childrens header, but excluding it's own header.
+ * IMPORTANT: When a leaf child node has an ODD size of data,
+ * a pad byte is added during the writing process and the parent's size INCLUDES the pad byte.
+ */
+ void adjustSize( XMP_Int64 sizeChange = 0 );
+
+}; // Chunk
+
+} // namespace
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp b/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp
new file mode 100644
index 0000000..786cbd1
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp
@@ -0,0 +1,709 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FormatSupport/IFF/ChunkController.h"
+#include "XMPFiles/source/FormatSupport/IFF/Chunk.h"
+
+#include <cstdio>
+
+using namespace IFF_RIFF;
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::ChunkController(...)
+//
+// Purpose: ctor/dtor
+//
+//-----------------------------------------------------------------------------
+
+ChunkController::ChunkController( IChunkBehavior* chunkBehavior, XMP_Bool bigEndian )
+: mEndian (NULL),
+ mChunkBehavior (chunkBehavior),
+ mFileSize (0),
+ mRoot (NULL),
+ mTrailingGarbageSize (0),
+ mTrailingGarbageOffset (0)
+{
+ if (bigEndian)
+ {
+ mEndian = &BigEndian::getInstance();
+ } else {
+ mEndian = &LittleEndian::getInstance();
+ }
+
+ // create virtual root chunk
+ mRoot = Chunk::createChunk(*mEndian);
+
+ // share chunk paths with behavior
+ mChunkBehavior->setMovablePaths( &mChunkPaths );
+}
+
+ChunkController::~ChunkController()
+{
+ delete dynamic_cast<Chunk*>(mRoot);
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::addChunkPath(...)
+//
+// Purpose: Adds the given path to the array of "Chunk's of interest"
+//
+//-----------------------------------------------------------------------------
+
+void ChunkController::addChunkPath( const ChunkPath& path )
+{
+ mChunkPaths.push_back(path);
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::compareChunkPaths(...)
+//
+// Purpose: The function parses all the sibling chunks. For every chunk it
+// either caches the chunk, skips it, or calls the function recusivly
+// for the children chunks
+//
+//-----------------------------------------------------------------------------
+
+ChunkPath::MatchResult ChunkController::compareChunkPaths(const ChunkPath& currentPath)
+{
+ ChunkPath::MatchResult result = ChunkPath::kNoMatch;
+
+ for( PathIterator iter = mChunkPaths.begin(); ( result == ChunkPath::kNoMatch ) && ( iter != mChunkPaths.end() ); iter++ )
+ {
+ result = iter->match(currentPath);
+ }
+
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::parseChunks(...)
+//
+// Purpose: The function Parses all the sibling chunks. For every chunk it
+// either caches the chunk, skips it, or calls the function recusivly
+// for the children chunks
+//
+//-----------------------------------------------------------------------------
+
+void ChunkController::parseChunks( XMP_IO* stream, ChunkPath& currentPath, XMP_OptionBits* options /* = NULL */, Chunk* parent /* = NULL */)
+{
+ XMP_Uns64 filePos = stream->Offset();
+ XMP_Bool isRoot = (parent == mRoot);
+ XMP_Uns64 parseLimit = mFileSize;
+ XMP_Uns32 chunkCnt = 0;
+
+ parent = ( parent == NULL ? dynamic_cast<Chunk*>(mRoot) : parent );
+
+ //
+ // calculate the parse limit
+ //
+ if ( !isRoot )
+ {
+ parseLimit = parent->getOriginalOffset() + parent->getSize( true );
+
+ if( parseLimit > mFileSize )
+ {
+ parseLimit = mFileSize;
+ }
+ }
+
+ while ( filePos < parseLimit )
+ {
+ XMP_Uns64 fileTail = mFileSize - filePos;
+
+ //
+ // check if there is enough space (at least for id and size)
+ //
+ if ( fileTail < Chunk::HEADER_SIZE )
+ {
+ //preserve rest of bytes (fileTail)
+ mTrailingGarbageOffset = filePos;
+ mTrailingGarbageSize = fileTail;
+ break; // stop parsing
+ }
+ else
+ {
+ bool chunkJump = false;
+
+ //
+ // create a new Chunk
+ //
+ Chunk* chunk = Chunk::createChunk(* mEndian );
+
+ bool readFailure = false;
+ //
+ // read the Chunk (id, size, [type]) without caching the data
+ //
+ try
+ {
+ chunk->readChunk( stream );
+ }
+ catch( ... )
+ {
+ // remember exception during reading the chunk
+ readFailure = true;
+ }
+
+ //
+ // validate chunk ID for top-level chunks
+ //
+ if( isRoot && ! mChunkBehavior->isValidTopLevelChunk( chunk->getIdentifier(), chunkCnt ) )
+ {
+ // notValid: preserve rest of bytes (fileTail)
+ mTrailingGarbageOffset = filePos;
+ mTrailingGarbageSize = fileTail;
+ //delete unused chunk (because these are undefined trailing bytes)
+ delete chunk;
+ break; // stop parsing
+ }
+ else if ( readFailure )
+ {
+ delete chunk;
+ XMP_Throw ( "Bad RIFF chunk", kXMPErr_BadFileFormat );
+ }
+
+ //
+ // parenting
+ // (as early as possible in order to be able to clean up
+ // the tree correctly in the case of an exception)
+ //
+ parent->appendChild(chunk, false);
+
+ // count top-level chunks
+ if( isRoot )
+ {
+ chunkCnt++;
+ }
+
+ //
+ // check size if value exceeds 4GB border
+ //
+ if( chunk->getSize() >= 0x00000000FFFFFFFFLL )
+ {
+ // remember file position
+ XMP_Int64 currentFilePos = stream->Offset();
+
+ // ask for the "real" size value
+ XMP_Uns64 realSize = mChunkBehavior->getRealSize( chunk->getSize(),
+ chunk->getIdentifier(),
+ *mRoot,
+ stream );
+
+ // set new size at chunk
+ chunk->setSize( realSize, true );
+
+ // set flag if the file position changed
+ chunkJump = currentFilePos < stream->Offset();
+ }
+
+ //
+ // Repair if needed
+ //
+ if ( filePos + chunk->getSize(true) > mFileSize )
+ {
+ bool isUpdate = ( options != NULL ? XMP_OptionIsSet ( *options, kXMPFiles_OpenForUpdate ) : false );
+ bool repairFile = ( options != NULL ? XMP_OptionIsSet ( *options, kXMPFiles_OpenRepairFile ) : false );
+
+ if ( ( ! isUpdate ) || ( repairFile && isRoot ) )
+ {
+ chunk->setSize( mFileSize-filePos-Chunk::HEADER_SIZE, true );
+ }
+ else
+ {
+ XMP_Throw ( "Bad RIFF chunk size", kXMPErr_BadFileFormat );
+ }
+ }
+
+ // extend search path
+ currentPath.append( chunk->getIdentifier() );
+
+ // first 4 bytes might be already read by the chunk->readChunk function
+ XMP_Uns64 offsetOfChunkRead = stream->Offset() - filePos - Chunk::HEADER_SIZE;
+
+ switch ( compareChunkPaths(currentPath) )
+ {
+ case ChunkPath::kFullMatch :
+ {
+ chunk->cacheChunkData( stream );
+ }
+ break;
+
+ case ChunkPath::kPartMatch :
+ {
+ parseChunks( stream, currentPath, options, chunk);
+ // recalculate the size based on the sizes of its children
+ chunk->calculateSize( true );
+ }
+ break;
+
+ case ChunkPath::kNoMatch :
+ {
+ // Not a chunk we are interested in, so mark it as not changed
+ // It will then be ignored by any further logic
+ chunk->resetChanges();
+
+ if ( !chunkJump && chunk->getSize() > 0) // if chunk not empty
+ {
+ XMP_Validate( stream->Offset() + chunk->getSize() - offsetOfChunkRead <= mFileSize , "ERROR: want's to skip beyond EOF", kXMPErr_InternalFailure);
+ stream->Seek ( chunk->getSize() - offsetOfChunkRead , kXMP_SeekFromCurrent );
+ }
+ }
+ break;
+ }
+
+ // remove last identifier from current path
+ currentPath.remove();
+
+ // update current file position
+ filePos = stream->Offset();
+
+ // skip pad byte if there is one (if size odd)
+ if( filePos < mFileSize &&
+ ( ( chunkJump && ( stream->Offset() & 1 ) > 0 ) ||
+ ( !chunkJump && ( chunk->getSize() & 1 ) > 0 ) ) )
+ {
+ stream->Seek ( 1 , kXMP_SeekFromCurrent );
+ filePos++;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::parseFile(...)
+//
+// Purpose: construct the tree, parse children for list of interesting Chunks
+// All requested leaf chunks are cached, the parent chunks are created
+// but not cached and the rest is skipped
+//
+//-----------------------------------------------------------------------------
+
+void ChunkController::parseFile( XMP_IO* stream, XMP_OptionBits* options /* = NULL */ )
+{
+ // store file information in root node
+ mFileSize = stream ->Length();
+ ChunkPath currentPath;
+
+ // Make sure the tree is clean before parsing
+ cleanupTree();
+
+ try
+ {
+ parseChunks( stream, currentPath, options, dynamic_cast<Chunk*>(mRoot) );
+ }
+ catch( ... )
+ {
+ this->cleanupTree();
+ throw;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::writeFile(...)
+//
+// Purpose: Called by the handler to write back the changes to the file.
+//
+//-----------------------------------------------------------------------------
+void ChunkController::writeFile( XMP_IO* stream )
+
+{
+ //
+ // if any of the top-level chunks exceeds their maximum size then skip writing and throw an exception
+ //
+ for( XMP_Uns32 i=0; i<mRoot->numChildren(); i++ )
+ {
+ Chunk* toplevel = mRoot->getChildAt(i);
+ XMP_Validate( toplevel->getSize() < mChunkBehavior->getMaxChunkSize(), "Exceeded maximum chunk size.", kXMPErr_AssertFailure );
+ }
+
+ //
+ // if exception is thrown write chunk is skipped
+ //
+ mChunkBehavior->fixHierarchy(*mRoot);
+
+ if (mRoot->numChildren() > 0)
+ {
+ // The new file size (without trailing garbage) is the offset of the last top-level chunk + its size.
+ // NOTE: the padding bytes can be ignored, as the top-level chunk is always a node, not a leaf.
+ Chunk* lastChild = mRoot->getChildAt(mRoot->numChildren() - 1);
+ XMP_Uns64 newFileSize = lastChild->getOffset() + lastChild->getSize(true);
+
+ // Move garbage tail after last top-level chunk,
+ // BEFORE the chunks are written -- in case the file shrinks
+ if (mTrailingGarbageSize > 0 && newFileSize != mTrailingGarbageOffset)
+ {
+ XIO::Move( stream, mTrailingGarbageOffset, stream, newFileSize, mTrailingGarbageSize );
+ newFileSize += mTrailingGarbageSize;
+ }
+
+ // Write changed and new chunks to the file
+ for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ )
+ {
+ Chunk* child = mRoot->getChildAt(i);
+ child->writeChunk( stream );
+ }
+
+ // file has been completely written,
+ // truncate the file it has been bigger before
+ if (newFileSize < mFileSize)
+ {
+ stream->Truncate ( newFileSize );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::getChunk(...)
+//
+// Purpose: returns a certain Chunk
+//
+//-----------------------------------------------------------------------------
+
+IChunkData* ChunkController::getChunk( const ChunkPath& path, XMP_Bool last ) const
+{
+ IChunkData* ret = NULL;
+
+ if( path.length() > 0 )
+ {
+ ChunkPath current;
+ ret = this->findChunk( path, current, *(dynamic_cast<Chunk*>(mRoot)), last );
+ }
+
+ return ret;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::findChunk(...)
+//
+// Purpose: Find a chunk described by path in the hierarchy of chunks starting
+// at the passed chunk.
+// The position of chunk in the hierarchy is described by the parameter
+// currentPath.
+// This method is supposed to be recursively.
+//
+//-----------------------------------------------------------------------------
+
+Chunk* ChunkController::findChunk( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk, XMP_Bool last ) const
+{
+ Chunk* ret = NULL;
+ XMP_Uns32 cnt = 0;
+
+ if( path.length() > currentPath.length() )
+ {
+ for( XMP_Uns32 i=0; i<chunk.numChildren() && ret == NULL; i++ )
+ {
+ //if last is true go backwards
+ last ? cnt=chunk.numChildren()-1-i : cnt=i;
+
+ Chunk* child = NULL;
+
+ try
+ {
+ child = chunk.getChildAt(cnt);
+ }
+ catch(...)
+ {
+ child = NULL;
+ }
+
+ if( child != NULL )
+ {
+ currentPath.append( child->getIdentifier() );
+
+ switch( path.match( currentPath ) )
+ {
+ case ChunkPath::kFullMatch:
+ {
+ ret = child;
+ }
+ break;
+
+ case ChunkPath::kPartMatch:
+ {
+ ret = this->findChunk( path, currentPath, *child, last );
+ }
+ break;
+
+ case ChunkPath::kNoMatch:
+ {
+ // Nothing to do
+ }
+ break;
+ }
+
+ currentPath.remove();
+ }
+ }
+ }
+
+ return ret;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::getChunks(...)
+//
+// Purpose: Returns all chunks that match completely to the passed path.
+//
+//-----------------------------------------------------------------------------
+
+const std::vector<IChunkData*>& ChunkController::getChunks( const ChunkPath& path )
+{
+ mSearchResults.clear();
+
+ if( path.length() > 0 )
+ {
+ ChunkPath current;
+ this->findChunks( path, current, *(dynamic_cast<Chunk*>(mRoot)) );
+ }
+
+ return mSearchResults;
+}//getChunks
+
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::getTopLevelTypes(...)
+//
+// Purpose: Return an array containing the types of the top level nodes
+// Top level nodes are the ones beneath ROOT
+//
+//-----------------------------------------------------------------------------
+
+const std::vector<XMP_Uns32> ChunkController::getTopLevelTypes()
+{
+ std::vector<XMP_Uns32> typeList;
+
+ for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ )
+ {
+ typeList.push_back( mRoot->getChildAt( i )->getType() );
+ }
+
+ return typeList;
+}// getTopLevelTypes
+
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::findChunks(...)
+//
+// Purpose: Find all chunks described by path in the hierarchy of chunks starting
+// at the passed chunk.
+// The position of chunks in the hierarchy is described by the parameter
+// currentPath. Found chunks that match to the path are stored in the
+// member mSearchResults.
+// This method is supposed to be recursively.
+//
+//-----------------------------------------------------------------------------
+
+void ChunkController::findChunks( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk )
+{
+ if( path.length() > currentPath.length() )
+ {
+ for( XMP_Uns32 i=0; i<chunk.numChildren(); i++ )
+ {
+ Chunk* child = NULL;
+
+ try
+ {
+ child = chunk.getChildAt(i);
+ }
+ catch(...)
+ {
+ child = NULL;
+ }
+
+ if( child != NULL )
+ {
+ currentPath.append( child->getIdentifier() );
+
+ switch( path.match( currentPath ) )
+ {
+ case ChunkPath::kFullMatch:
+ {
+ mSearchResults.push_back( child );
+ }
+ break;
+
+ case ChunkPath::kPartMatch:
+ {
+ this->findChunks( path, currentPath, *child );
+ }
+ break;
+
+ case ChunkPath::kNoMatch:
+ {
+ // Nothing to do
+ }
+ break;
+ }
+
+ currentPath.remove();
+ }
+ }
+ }
+}//findChunks
+
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::cleanupTree(...)
+//
+// Purpose: Cleanup function called from destructor and in case of an exception
+//
+//-----------------------------------------------------------------------------
+
+void ChunkController::cleanupTree()
+{
+ delete dynamic_cast<Chunk*>(mRoot);
+ mRoot = Chunk::createChunk(*mEndian);
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::dumpTree(...)
+//
+// Purpose: dumps the tree structure
+//
+//-----------------------------------------------------------------------------
+
+std::string ChunkController::dumpTree( )
+{
+ std::string ret;
+ char buffer[256];
+
+ if ( mRoot != NULL )
+ {
+ ret = mRoot->toString();
+ }
+
+ if ( mTrailingGarbageSize != 0 )
+ {
+ snprintf( buffer, 255, "\n Trailing Bytes: %llu", mTrailingGarbageSize );
+
+ std::string str(buffer);
+ ret.append(str);
+ }
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::createChunk(...)
+//
+// Purpose: Create a new empty chunk
+//
+//-----------------------------------------------------------------------------
+
+IChunkData* ChunkController::createChunk( XMP_Uns32 id, XMP_Uns32 type /*= kType_NONE*/ )
+{
+ Chunk* chunk = Chunk::createChunk(* mEndian );
+
+ chunk->setID( id );
+ if( type != kType_NONE )
+ {
+ chunk->setType( type );
+ }
+
+ return chunk;
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::insertChunk(...)
+//
+// Purpose: Insert a new chunk. The position of this new chunk within the
+// hierarchy is determined internally by the behavior.
+// Throws an exception if a chunk cannot be inserted into the tree
+//
+//-----------------------------------------------------------------------------
+
+void ChunkController::insertChunk( IChunkData* chunk )
+{
+ XMP_Validate( chunk != NULL, "ERROR inserting Chunk. Chunk is NULL.", kXMPErr_InternalFailure );
+ Chunk* ch = dynamic_cast<Chunk*>(chunk);
+ mChunkBehavior->insertChunk( *mRoot, *ch );
+ // sets OriginalSize = Size / OriginalOffset = Offset
+ ch->setAsNew();
+ // force set dirty flag
+ ch->setChanged();
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::removeChunk(...)
+//
+// Purpose: Delete a chunk or remove/delete it from the tree.
+// If the chunk exists within the chunk hierarchy the chunk gets removed
+// from the tree and deleted.
+// If it is not in the tree, then it is only destroyed.
+//
+//-----------------------------------------------------------------------------
+
+void ChunkController::removeChunk( IChunkData* chunk )
+{
+ if( chunk != NULL )
+ {
+ Chunk* chk = dynamic_cast<Chunk*>(chunk);
+
+ if( this->isInTree( chk ) )
+ {
+ if( mChunkBehavior->removeChunk( *mRoot, *chk ) )
+ {
+ delete chk;
+ }
+ }
+ else
+ {
+ delete chk;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkController::isInTree(...)
+//
+// Purpose: return true if the passed in Chunk is part of the Chunk tree
+//
+//-----------------------------------------------------------------------------
+
+bool ChunkController::isInTree( Chunk* chunk )
+{
+ bool ret = ( mRoot == chunk );
+
+ if( !ret && chunk != NULL )
+ {
+ Chunk* parent = chunk->getParent();
+
+ while( !ret && parent != NULL )
+ {
+ ret = ( mRoot == parent );
+ parent = parent->getParent();
+ }
+ }
+
+ return ret;
+}
diff --git a/XMPFiles/source/FormatSupport/IFF/ChunkController.h b/XMPFiles/source/FormatSupport/IFF/ChunkController.h
new file mode 100644
index 0000000..933da36
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IFF/ChunkController.h
@@ -0,0 +1,246 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _ChunkController_h_
+#define _ChunkController_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "source/XMP_LibUtils.hpp"
+
+#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h"
+#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h"
+
+class IEndian;
+
+namespace IFF_RIFF
+{
+/**
+ The class ChunkController is supposed to act as an controller between the IRIFFHandler and the actual chunks (Chunk instances).
+ It controls the parsing and writing of the passed stream.
+*/
+
+class IChunkData;
+class IChunkContainer;
+class Chunk;
+
+class ChunkController
+{
+ public:
+ /**
+ * Constructor:
+ * Creates an IEndian based instance for further usage.
+ *
+ * @param IChunkBehavior* chunkBehavior : for AVI the IChunkBehavior instance would be an instance of a IChunkBehavior class, that knows
+ * about the 1,2,4 GB border, padding byte special cases, AVIX stuff and so on. That knowledge would
+ * be used during writeFile()
+ * In the case of WAVE it would be an instance of WAVEBehavior that would know how to get the 64bit
+ * size values for RF64 if required. That knowledge would be used during parseFile()
+ * @param XMP_Bool bigEndian set True if file chunk data is big endian (e.g. AIFF).
+ * Must explicitely be set, so that handlers do not accidentaly use the wrong endianess
+ */
+ ChunkController( IChunkBehavior* chunkBehavior, XMP_Bool bigEndian );
+
+ ~ChunkController();
+
+ /**
+ * Adds the given path to the array of "Chunk's of interest",
+ *
+ * @param path List of Paths that should be parsed
+ * example AVI: [ RIFF:AVI/LIST:INFO , RIFF:AVIX/LIST:INFO, RIFF:AVI/LIST:TDAT ]
+ */
+ void addChunkPath( const ChunkPath& path );
+
+ /**
+ * construct the tree, parse children for list of interesting Chunks
+ * All requested leaf chunks are cached, the parent chunks are created but not cached
+ * and the rest is skipped.
+ *
+ * @param stream the open [file] stream with file pointer at the beginning of the file
+ *
+ */
+ void parseFile( XMP_IO* stream, XMP_OptionBits* options = NULL );
+
+ /**
+ * Create a new empty chunk
+ *
+ * @param id Chunk identifier
+ * @param type Chunk type [optional]
+ * @return New IChunkData with passed id/type
+ */
+ IChunkData* createChunk( XMP_Uns32 id, XMP_Uns32 type = kType_NONE );
+
+ /**
+ * Insert a new chunk. The position of this new chunk within the hierarchy
+ * is determined internally by the behavior.
+ * Throws an exception if a chunk cannot be inserted into the tree
+ *
+ * @param chunk The chunk to insert into the tree
+ */
+ void insertChunk( IChunkData* chunk );
+
+ /**
+ * Delete a chunk or remove/delete it from the tree.
+ * If the chunk exists within the chunk hierarchy the chunk gets removed from the tree and deleted.
+ * If it is not in the tree, then it is only destroyed.
+ *
+ * @param chunk Chunk to remove/delete
+ */
+ void removeChunk( IChunkData* chunk );
+
+ /**
+ * Called by the handler to write back the changes to the file.
+ * 1. fix the file tree (ChunkBehavior#fixHierarchy),
+ * offsets are corrected, no overlapping chunks;
+ * if rearranging fails, the file is not touched
+ * 2. write the changed chunks to the file
+ *
+ * @param stream the open [file] stream for writing, the file pointer must be at the beginning
+ */
+ void writeFile( XMP_IO* stream );
+
+ /**
+ * Returns the first (or last) Chunk that matches the passed path.
+ *
+ * @param path the path of the Chunk to return
+ * @param last in case of duplicates return the last one
+ * @return Returns Chunk or NULL
+ */
+ IChunkData* getChunk( const ChunkPath& path, XMP_Bool last = false ) const;
+
+ /**
+ * Returns all chunks that match completely to the passed path.
+ * E.g. if FORM:AIFF/LIST is given, it would return all LIST chunks in FORM:AIFF
+ *
+ * @param path the path of the Chunk to return
+ * @return list of found chunks or empty list
+ */
+ const std::vector<IChunkData*>& getChunks( const ChunkPath& path );
+
+ /**
+ * returns the number of the bytes after the last valid IFF chunk
+ */
+ inline XMP_Int64 getTrailingGarbageSize() { return mTrailingGarbageSize; };
+
+ /**
+ * returns the file size
+ */
+ inline XMP_Int64 getFileSize() { return mFileSize; };
+
+ /**
+ * Return an array containing the types of the top level nodes
+ * Top level nodes are the ones beneath ROOT
+ */
+ const std::vector<XMP_Uns32> getTopLevelTypes();
+
+ /**
+ * dumps the tree structure
+ *
+ */
+ std::string dumpTree( );
+
+ protected:
+ /**
+ * Standard Constructor:
+ * Hidden on purpose. Must not be used!
+ * A Controller must have a behavior!
+ */
+ ChunkController() { XMP_Throw("Ctor hidden", kXMPErr_InternalFailure); }
+
+ /**
+ * The function Parses all the sibling chunks. For every chunk it either caches the chunk,
+ * skips it, or calls the function recusivly for the children chunks
+ *
+ * @param stream the file stream
+ * @param currentPath the path/id of the Chunk to return
+ * @param options handler options
+ * @param parent pointer to the parent chunk
+ */
+ void parseChunks( XMP_IO* stream, ChunkPath& currentPath, XMP_OptionBits* options = NULL, Chunk* parent = NULL );
+
+ /**
+ * The function parses all the sibling chunks. For every chunk it either caches the chunk,
+ * skips it, or calls the function recusivly for the children chunks
+ *
+ * @param ChunkPath& currentPath: the path/id of the Chunk to return
+ */
+ ChunkPath::MatchResult compareChunkPaths( const ChunkPath& currentPath );
+
+ /**
+ * Find a chunk described by path in the hierarchy of chunks starting at the passed chunk.
+ * The position of chunk in the hierarchy is described by the parameter currentPath.
+ * This method is supposed to be recursively.
+ */
+ Chunk* findChunk( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk, XMP_Bool last = false ) const;
+
+ /**
+ * Find all chunks described by path in the hierarchy of chunks starting at the passed chunk.
+ * The position of chunks in the hierarchy is described by the parameter currentPath.
+ * Found chunks that match to the path are stored in the member mSearchResults.
+ * This method is supposed to be recursively.
+ */
+ void findChunks( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk );
+
+ /**
+ * Cleanup function called from destructor and in case of an exception
+ */
+ void cleanupTree();
+
+ /**
+ * return true if the passed in Chunk is part of the Chunk tree
+ *
+ * @param chunk the chunk that shall be checked.
+ */
+ bool isInTree( Chunk* chunk );
+
+
+ // Members
+
+ /**
+ * Endian class. Either BigEndian oder LittleEndian. Based on the file format.
+ */
+ const IEndian* mEndian;
+
+ /**
+ * Chunk behaviour class. Has file format specific function for getting the size and
+ * rearranging the chunk tree.
+ */
+ IChunkBehavior* mChunkBehavior;
+
+ /** The list of chunks wich should be cached. */
+ std::vector<ChunkPath> mChunkPaths;
+
+ /** Iterator for the list of chunk paths */
+ typedef std::vector<ChunkPath>::iterator PathIterator;
+
+ /** The overall filesize after parsing the file stream */
+ XMP_Uns64 mFileSize;
+
+ /** The root of the Chunk Tree (top level list) */
+ IChunkContainer* mRoot;
+
+ /** Offset of trailing garbage characters */
+ XMP_Uns64 mTrailingGarbageOffset;
+
+ /** Size of trailing garbage characters */
+ XMP_Uns64 mTrailingGarbageSize;
+
+ /** search results of method getChunks(...) */
+ ChunkPath mSearchPath;
+
+ /** Cached search results */
+ std::vector<IChunkData*> mSearchResults;
+}; // ChunkController
+
+} // namespace
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/IFF/ChunkPath.cpp b/XMPFiles/source/FormatSupport/IFF/ChunkPath.cpp
new file mode 100644
index 0000000..54680f8
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IFF/ChunkPath.cpp
@@ -0,0 +1,239 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "XMPFiles/source/FormatSupport/IFF/ChunkPath.h"
+#include "source/XMP_LibUtils.hpp"
+
+#include <vector>
+
+using namespace IFF_RIFF;
+
+typedef std::vector<ChunkIdentifier>::size_type ChunkSizeType;
+
+//-----------------------------------------------------------------------------
+//
+// ChunkPath::ChunkPath(...)
+//
+// Purpose: ctor/dtor
+//
+//-----------------------------------------------------------------------------
+
+ChunkPath::ChunkPath( const ChunkIdentifier* path /*= NULL*/, XMP_Uns32 size /*=0*/ )
+{
+ if( path != NULL )
+ {
+ for( XMP_Uns32 i=0; i<size; i++ )
+ {
+ this->append( path[i] );
+ }
+ }
+}
+
+ChunkPath::ChunkPath( const ChunkPath& path )
+{
+ for( XMP_Int32 i=0; i<path.length(); i++ )
+ {
+ this->append( path.identifier(i) );
+ }
+}
+
+ChunkPath::ChunkPath( const ChunkIdentifier& identifier )
+{
+ this->append( identifier );
+}
+
+ChunkPath::~ChunkPath()
+{
+ this->clear();
+}
+
+
+ChunkPath & ChunkPath::operator=( const ChunkPath &rhs )
+{
+ for( XMP_Int32 i = 0; i < rhs.length(); i++ )
+ {
+ this->append( rhs.identifier(i) );
+ }
+
+ return *this;
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkPath::clear(...)
+//
+// Purpose: Remove all ChunkIdentifier's from the path
+//
+//-----------------------------------------------------------------------------
+
+void ChunkPath::clear()
+{
+ mPath.clear();
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkPath::append(...)
+//
+// Purpose: Append a ChunkIdentifier to the end of the path
+//
+//-----------------------------------------------------------------------------
+
+void ChunkPath::append( XMP_Uns32 id, XMP_Uns32 type /*= kType_NONE*/ )
+{
+ ChunkIdentifier ci;
+
+ ci.id = id;
+ ci.type = type;
+
+ mPath.push_back(ci);
+}
+
+
+void ChunkPath::append( const ChunkIdentifier& identifier )
+{
+ mPath.push_back(identifier);
+}
+
+
+void ChunkPath::append( const ChunkIdentifier* path, XMP_Uns32 size )
+{
+ if( path != NULL )
+ {
+ for( XMP_Uns32 i=0; i < size; i++ )
+ {
+ this->append( path[i] );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkPath::insert(...)
+//
+// Purpose: Insert an identifier
+//
+//-----------------------------------------------------------------------------
+
+void ChunkPath::insert( const ChunkIdentifier& identifier, XMP_Uns32 pos /*= 0*/ )
+{
+ if( pos >= mPath.size() )
+ {
+ this->append( identifier );
+ }
+ else
+ {
+ mPath.insert( mPath.begin() + pos, identifier );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkPath::remove(...)
+//
+// Purpose: Remove the endmost ChunkIdentifier from the path
+//
+//-----------------------------------------------------------------------------
+
+void ChunkPath::remove()
+{
+ mPath.pop_back();
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkPath::removeAt(...)
+//
+// Purpose: Remove the ChunkIdentifier at the passed position in the path
+//
+//-----------------------------------------------------------------------------
+
+void ChunkPath::removeAt( XMP_Int32 pos )
+{
+ if( ! mPath.empty() && pos >= 0 && (ChunkSizeType)pos < mPath.size() )
+ {
+ mPath.erase( mPath.begin() + pos );
+ }
+ else
+ {
+ XMP_Throw( "Index out of range.", kXMPErr_BadIndex );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkPath::identifier(...)
+//
+// Purpose: Return ChunkIdentifier at the passed position
+//
+//-----------------------------------------------------------------------------
+
+const ChunkIdentifier& ChunkPath::identifier( XMP_Int32 pos ) const
+{
+ return mPath.at(pos);
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkPath::length(...)
+//
+// Purpose: Return the number of ChunkIdentifier's in the path
+//
+//-----------------------------------------------------------------------------
+
+XMP_Int32 ChunkPath::length() const
+{
+ return (XMP_Int32)mPath.size();
+}
+
+//-----------------------------------------------------------------------------
+//
+// ChunkPath::match(...)
+//
+// Purpose: Compare the passed ChunkPath with this path.
+//
+//-----------------------------------------------------------------------------
+
+ChunkPath::MatchResult ChunkPath::match( const ChunkPath& path ) const
+{
+ MatchResult ret = kNoMatch;
+
+ if( path.length() > 0 )
+ {
+ XMP_Int32 depth = ( this->length() > path.length() ? path.length() : this->length() );
+ XMP_Int32 matchCount = 0;
+
+ for( XMP_Int32 i=0; i<depth; i++ )
+ {
+ const ChunkIdentifier& id1 = this->identifier(i);
+ const ChunkIdentifier& id2 = path.identifier(i);
+
+ if( id1.id == id2.id )
+ {
+ if( i == this->length() - 1 && id1.type == kType_NONE )
+ {
+ matchCount++;
+ }
+ else if( id1.type == id2.type )
+ {
+ matchCount++;
+ }
+ }
+ else
+ break;
+ }
+
+ if( matchCount == depth )
+ {
+ ret = ( path.length() >= this->length() ? kFullMatch : kPartMatch );
+ }
+ }
+
+ return ret;
+}
diff --git a/XMPFiles/source/FormatSupport/IFF/ChunkPath.h b/XMPFiles/source/FormatSupport/IFF/ChunkPath.h
new file mode 100644
index 0000000..c0f149b
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IFF/ChunkPath.h
@@ -0,0 +1,190 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _ChunkPath_h_
+#define _ChunkPath_h_
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+
+#include <limits.h> // For UINT_MAX.
+#include <vector>
+
+namespace IFF_RIFF
+{
+
+/**
+ A ChunkPath describes one certain chunk in the hierarchy of chunks
+ of the IFF/RIFF file format.
+ Each chunks gets identified by a structure of the type ChunkIdentifier.
+ Which consists of the 4byte ID of the chunk and, if applicable, the 4byte
+ type of the chunk.
+*/
+
+// IFF/RIFF ids
+enum {
+ // invalid ID
+ kChunk_NONE = UINT_MAX,
+
+ // format chunks
+ kChunk_RIFF = 0x52494646,
+ kChunk_RF64 = 0x52463634,
+ kChunk_FORM = 0x464F524D,
+ kChunk_JUNK = 0x4A554E4B,
+ kChunk_JUNQ = 0x4A554E51,
+
+
+ // other container chunks
+ kChunk_LIST = 0x4C495354,
+
+ // other relevant chunks
+ kChunk_XMP = 0x5F504D58, // "_PMX"
+
+ kChunk_data = 0x64617461,
+
+ //should occur only in AVI
+ kChunk_Cr8r = 0x43723872,
+ kChunk_PrmL = 0x50726D4C,
+
+ //should occur only in WAV
+ kChunk_DISP = 0x44495350,
+ kChunk_bext = 0x62657874,
+ kChunk_cart = 0x63617274,
+ kChunk_ds64 = 0x64733634,
+
+ // AIFF
+ kChunk_APPL = 0x4150504C,
+ kChunk_NAME = 0x4E414D45,
+ kChunk_AUTH = 0x41555448,
+ kChunk_CPR = 0x28632920,
+ kChunk_ANNO = 0x414E4E4F
+};
+
+// IFF/RIFF types
+enum {
+ kType_AVI_ = 0x41564920,
+ kType_AVIX = 0x41564958,
+ kType_WAVE = 0x57415645,
+ kType_AIFF = 0x41494646,
+ kType_AIFC = 0x41494643,
+ kType_INFO = 0x494E464F,
+ kType_Tdat = 0x54646174,
+
+ // AIFF
+ kType_XMP = 0x584D5020,
+ kType_FREE = 0x46524545,
+
+ kType_NONE = UINT_MAX
+};
+
+
+struct ChunkIdentifier
+{
+ XMP_Uns32 id;
+ XMP_Uns32 type;
+};
+
+/**
+* calculates the size of a ChunkIdentifier array.
+* Has to be a macro as the sizeof operator does nto work for pointer function parameters
+*/
+#define SizeOfCIArray(ciArray) ( sizeof(ciArray) / sizeof(ChunkIdentifier) )
+
+
+class ChunkPath
+{
+public:
+ /**
+ ctor/dtor
+ */
+ ChunkPath( const ChunkIdentifier* path = NULL, XMP_Uns32 size = 0 );
+ ChunkPath( const ChunkPath& path );
+ ChunkPath( const ChunkIdentifier& identifier );
+ ~ChunkPath();
+
+ ChunkPath & operator=( const ChunkPath &rhs );
+
+ /**
+ Append a ChunkIdentifier to the end of the path
+
+ @param id 4byte id of chunk
+ @param type 4byte type of chunk
+ */
+ void append( XMP_Uns32 id, XMP_Uns32 type = kType_NONE );
+ void append( const ChunkIdentifier& identifier );
+
+ /**
+ Append a whole path
+
+ @param path Array of ChunkIdentifiert objects
+ @param size number of elements in the given array
+ */
+ void append( const ChunkIdentifier* path = NULL, XMP_Uns32 size = 0 );
+
+ /**
+ Insert an identifier
+
+ @param identifier id and type
+ @param pos position within the path
+ */
+ void insert( const ChunkIdentifier& identifier, XMP_Uns32 pos = 0 );
+
+ /**
+ Remove the endmost ChunkIdentifier from the path
+ */
+ void remove();
+ /**
+ Remove the ChunkIdentifier at the passed position in the path.
+ Throw exception if the position is out of range.
+
+ @param pos Position of ChunkIdentifier in the path
+ */
+ void removeAt( XMP_Int32 pos );
+
+ /**
+ Return ChunkIdentifier at the passed position
+
+ @param pos Position of ChunkIdentifier in the path
+ @return ChunkIdentifier at passed position (throw exception if
+ the position is out of range)
+ */
+ const ChunkIdentifier& identifier( XMP_Int32 pos ) const;
+
+ /**
+ Return the number of ChunkIdentifier's in the path
+ */
+ XMP_Int32 length() const;
+
+ /**
+ Remove all ChunkIdentifier's from the path
+ */
+ void clear();
+
+ /**
+ Compare the passed ChunkPath with this path.
+
+ @param path Path to compare with this path
+ @return Match result
+ */
+ enum MatchResult
+ {
+ kNoMatch = 0,
+ kPartMatch = 1,
+ kFullMatch = 2
+ };
+
+ MatchResult match( const ChunkPath& path ) const;
+
+private:
+ std::vector<ChunkIdentifier> mPath;
+};
+
+}
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.cpp b/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.cpp
new file mode 100644
index 0000000..44159c2
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.cpp
@@ -0,0 +1,595 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h"
+#include "XMPFiles/source/FormatSupport/IFF/Chunk.h"
+
+#include <algorithm>
+
+using namespace IFF_RIFF;
+
+//-----------------------------------------------------------------------------
+//
+// getIndex(...)
+//
+// Purpose: [static] Calculate index of chunk in tree
+//
+//-----------------------------------------------------------------------------
+
+static XMP_Uns32 getIndex( const IChunkContainer& tree, const Chunk& chunk )
+{
+ const Chunk& parent = dynamic_cast<const Chunk&>( tree );
+
+ return std::find( parent.firstChild(), parent.lastChild(), &chunk ) - parent.firstChild();
+}
+
+//-----------------------------------------------------------------------------
+//
+// IChunkBehavior::arrangeChunksInPlace(...)
+//
+// Purpose: Try to arrange all chunks of the source tree at their current location.
+// In a loop the method takes each chunk of the srcTree.
+// * If a chunk is a FREE chunk then it is removed.
+// * If a chunk is a known movable chunk then adjust its offset so that there's
+// no gap to its previous chunk. If the chunk offset was adjusted and/or the
+// chunk grew/shrank in its size then remember the offset difference for
+// further processing
+// * If the chunk is neither movable nor a FREE chunk and there is a offset
+// difference then fill possible gaps with FREE chunk or move chunks to
+// the destTree.
+//
+//-----------------------------------------------------------------------------
+
+void IChunkBehavior::arrangeChunksInPlace( IChunkContainer& srcTree, IChunkContainer& destTree )
+{
+ XMP_Validate( &srcTree != &destTree, "Source and destination tree mustn't be the same", kXMPErr_InternalFailure );
+
+ XMP_Int64 offsetAdjust = 0;
+
+ for( XMP_Int32 index=0; index<static_cast<XMP_Int32>( srcTree.numChildren() ); index++ )
+ {
+ Chunk* chunk = srcTree.getChildAt( index );
+
+ //
+ // Is chunk one that might be moved in the tree
+ // (and so it's a chunk that might be modified)
+ //
+ if( this->isMovable( *chunk ) )
+ {
+ //
+ // Are there FREE chunks above this chunk?
+ // Then remove it.
+ //
+ Chunk* freeChunk = NULL;
+
+ if( index > 0 )
+ {
+ // find FREE chunk and merge possible multiple FREE chunks to one chunk
+ freeChunk = this->mergeFreeChunks( srcTree, index-1 );
+ }
+
+ if( freeChunk != NULL )
+ {
+ // update running index
+ index = ::getIndex( srcTree, *chunk );
+
+ // subtract size of FREE chunk from offset adjust value
+ offsetAdjust -= static_cast<XMP_Int64>( freeChunk->getPadSize(true) );
+
+ // remove FREE chunk from the tree
+ srcTree.removeChildAt( index-1 );
+ delete freeChunk;
+
+ // update running index because one chunk was removed from the tree
+ index--;
+ }
+
+ //
+ // offset needs to be adjusted
+ //
+ if( offsetAdjust != 0 )
+ {
+ chunk->setOffset( chunk->getOffset() + offsetAdjust );
+ }
+
+ //
+ // update adjust value if the size of the chunk has changed
+ // (and so the offsets of following chunks needs to be adjusted)
+ //
+ offsetAdjust += chunk->getPadSize() - chunk->getOriginalPadSize();
+ }
+ else if( this->isFREEChunk( *chunk ) && offsetAdjust != 0 )
+ {
+ //
+ // chunk is a FREE chunk, just remove it
+ //
+
+ // merge FREE chunks
+ chunk = this->mergeFreeChunks( srcTree, index );
+
+ // update running index (in case multiple FREE chunk were merged)
+ index = ::getIndex( srcTree, *chunk );
+
+ // update adjust value about the total size of the FREE chunk
+ offsetAdjust -= static_cast<XMP_Int64>( chunk->getPadSize(true) );
+
+ // remove FREE chunk from tree
+ srcTree.removeChildAt( index );
+ delete chunk;
+
+ // update running index
+ index--;
+ }
+ else if( offsetAdjust != 0 )
+ {
+ //
+ // the current chunk can't be moved,
+ // so we can't adjust the offset of this chunk
+ //
+ XMP_Uns64 gap = 0;
+
+ if( offsetAdjust > 0 )
+ {
+ //
+ // One or more foregoing chunks grew in their seize and so
+ // the offset of following chunks needs to be adjusted.
+ // But since the current chunk can't be moved one or more previous
+ // chunks are now overlapping over the current chunk.
+ //
+ // So now one or more of the previous chunks needs to be removed
+ // (moved to the destTree) so that the offset value of the current
+ // chunk can stay where it is.
+ // A possible gap will be filled with a FREE chunk.
+ //
+
+ Chunk* preChunk = NULL;
+
+ //
+ // count Chunks that needs to be moved
+ //
+ XMP_Validate( index-1 >= 0, "There shouldn't be an offset adjust value for the first chunk", kXMPErr_InternalFailure );
+
+ XMP_Int32 preIndex = index;
+ XMP_Uns64 preSize = 0;
+
+ do
+ {
+ preIndex--;
+ preChunk = srcTree.getChildAt( preIndex );
+
+ XMP_Validate( this->isMovable( *preChunk ) || this->isFREEChunk( *preChunk ), "Movable or FREE chunk expected", kXMPErr_InternalFailure );
+
+ preSize += preChunk->getPadSize( true );
+
+ } while( static_cast<XMP_Int64>( preSize ) < offsetAdjust && preIndex > 0 );
+
+ //
+ // move chunks
+ //
+ for( XMP_Uns32 rem=preIndex; rem<static_cast<XMP_Uns32>( index ); rem++ )
+ {
+ // always fetch chunk at the first index of the range because
+ // these chunks are removed from the tree
+ preChunk = srcTree.removeChildAt( preIndex );
+
+ if( this->isFREEChunk( *preChunk ) )
+ {
+ delete preChunk;
+ }
+ else
+ {
+ destTree.appendChild( preChunk, false );
+ }
+ }
+
+ // update current index
+ index = ::getIndex( srcTree, *chunk );
+
+ //
+ // calculate size of gap
+ //
+ XMP_Uns64 curOffset = chunk->getOffset();
+ XMP_Uns64 preOffset = Chunk::HEADER_SIZE + Chunk::TYPE_SIZE;
+
+ if( index > 0 )
+ {
+ preChunk = srcTree.getChildAt( index-1 );
+ preOffset = preChunk->getOffset() + preChunk->getPadSize( true );
+ }
+
+ gap = curOffset - preOffset;
+ }
+ else if( offsetAdjust < 0 )
+ {
+ //
+ // There is a gap between the previous chunk and the current one.
+ // Fill the gap with a FREE chunk.
+ //
+ gap = offsetAdjust * (-1);
+ }
+
+ //
+ // if there is a gap we need to fill it with a FREE chunk
+ //
+ if( gap > 0 )
+ {
+ //
+ // The gap must be at least as big as the minimum size of FREE chunks.
+ // If that's not the case we need to move more chunks to expand
+ // the gap.
+ //
+ while( gap < this->getMinFREESize() )
+ {
+ XMP_Validate( index > 0, "Not enough space to insert FREE chunk", kXMPErr_Unimplemented );
+
+ Chunk* preChunk = srcTree.removeChildAt( index-1 );
+ gap += preChunk->getPadSize(true);
+ destTree.appendChild( preChunk, false );
+
+ // update running index
+ index--;
+ }
+
+ //
+ // Fill the gap with a FREE chunk.
+ //
+ Chunk* freeChunk = this->createFREE( gap );
+ srcTree.insertChildAt( index, freeChunk );
+ freeChunk->setAsNew();
+
+ // update running index
+ index++;
+ }
+
+ // reset adjust value
+ offsetAdjust = 0;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// IChunkBehavior::arrangeChunksInTree(...)
+//
+// Purpose: This method proceeds the list of Chunks of the source tree in the
+// passed range and looks for FREE chunks in the destination tree to
+// move the source chunks to.
+// Source tree and destination tree could be one and the same but it's
+// not required. If both trees are the same then it's not allowed to
+// cross source and destination ranges.
+//
+//-----------------------------------------------------------------------------
+
+void IChunkBehavior::arrangeChunksInTree( IChunkContainer& srcTree, IChunkContainer& destTree )
+{
+ XMP_Validate( &srcTree != &destTree, "Source and destination tree mustn't be the same", kXMPErr_InternalFailure );
+
+ if( srcTree.numChildren() > 0 )
+ {
+ //
+ // for all chunks that were moved to the end try to find a FREE chunk for them
+ //
+ for( XMP_Int32 index=srcTree.numChildren()-1; index>=0; index-- )
+ {
+ Chunk* chunk = srcTree.getChildAt(index);
+
+ //
+ // find a FREE chunk where the chunk would fit in
+ //
+ XMP_Int32 freeIndex = this->findFREEChunk( destTree, chunk->getSize(true) );
+
+ if( freeIndex >= 0 )
+ {
+ Chunk* freeChunk = destTree.getChildAt( freeIndex );
+
+ // remove chunk from source tree
+ srcTree.removeChildAt( index );
+
+ // insert chunk at new location
+ destTree.insertChildAt( freeIndex, chunk );
+
+ // remove the FREE chunk
+ destTree.removeChildAt( freeIndex+1 );
+
+ //
+ // if the size of the FREE chunk is larger than the size of the chunk then fill
+ // the gap with a new FREE chunk (the method findFREEChunk takes care that the
+ // remaining space is large enough for a new FREE chunk, but findFREEChunk also
+ // takes account of possible pad bytes in its calculations! Therefore following
+ // calculations have to take account of a possible pad byte as well!)
+ //
+ if( freeChunk->getPadSize( true ) > chunk->getPadSize( true ) )
+ {
+ Chunk* remainFreeChunk = this->createFREE( freeChunk->getPadSize( true ) - chunk->getPadSize( true ) );
+ destTree.insertChildAt( freeIndex+1, remainFreeChunk );
+ remainFreeChunk->setAsNew();
+ }
+
+ delete freeChunk;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// IChunkBehavior::validateOffsets(...)
+//
+// Purpose: Fix recursively the offset values of all modified chunks.
+// At the same time the method checks the offset value of all not
+// modified chunks and throws an exception if there is any discrepance
+// with the calculated offset.
+//
+//-----------------------------------------------------------------------------
+
+void IChunkBehavior::validateOffsets( IChunkContainer& tree, XMP_Uns64 startOffset /*= 0*/ )
+{
+ XMP_Uns64 offset = startOffset;
+
+ //
+ // for all children of the tree
+ //
+ for( XMP_Uns32 i=0; i<tree.numChildren(); i++ )
+ {
+ Chunk* chunk = tree.getChildAt(i);
+
+ // the offset of a not modified chunk should match the calculated offset
+ XMP_Validate( chunk->getOffset() == offset, "Invalid offset", kXMPErr_InternalFailure );
+
+ if( !this->isMovable( *chunk ) )
+ {
+ XMP_Validate( chunk->getOffset() == chunk->getOriginalOffset(), "Invalid offset non-modified chunk", kXMPErr_InternalFailure );
+ }
+
+ // go through children
+ if( chunk->getChunkMode() == CHUNK_NODE )
+ {
+ this->validateOffsets( *chunk, offset + Chunk::HEADER_SIZE + Chunk::TYPE_SIZE );
+ }
+
+ // calculate next offset
+ offset += chunk->getPadSize(true);
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// IChunkBehavior::getFreeSpace(...)
+//
+// Purpose: Retrieve the free space at the passed position in the child list of
+// the parent tree. If there's a FREE chunk then return it.
+//
+//-----------------------------------------------------------------------------
+
+Chunk* IChunkBehavior::getFreeSpace( XMP_Int64& outFreeBytes, const IChunkContainer& tree, XMP_Uns32 index ) const
+{
+ // validate index
+ XMP_Validate( index < tree.numChildren(), "Invalid index", kXMPErr_InternalFailure );
+
+ Chunk* ret = NULL;
+
+ Chunk* chunk = tree.getChildAt( index );
+
+ if( this->isFREEChunk( *chunk ) )
+ {
+ //
+ // chunk is a FREE chunk
+ //
+ outFreeBytes = chunk->getSize( true );
+ ret = chunk;
+ }
+ else if( chunk->getChunkMode() != CHUNK_UNKNOWN && chunk->hasChanged() )
+ {
+ //
+ // chunk is NOT a FREE chunk but the size of this chunk has changed
+ //
+ outFreeBytes = chunk->getOriginalSize() - chunk->getSize();
+ }
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+//
+// IChunkBehavior::mergeFreeChunks(...)
+//
+// Purpose: Try to merge existing FREE chunks at the passed position in the
+// child list of the passed parent tree. The algorithm looks at the
+// position, before the position and after the position.
+//
+//-----------------------------------------------------------------------------
+
+Chunk* IChunkBehavior::mergeFreeChunks( IChunkContainer& tree, XMP_Uns32 index )
+{
+ // validate index
+ XMP_Validate( index < tree.numChildren(), "Invalid index", kXMPErr_InternalFailure );
+
+ Chunk* ret = NULL;
+
+ Chunk* chunk = tree.getChildAt( index );
+
+ //
+ // is chunk a FREE chunk
+ //
+ if( this->isFREEChunk( *chunk ) )
+ {
+ XMP_Uns32 indexStart = index;
+ XMP_Uns32 indexEnd = index;
+
+ XMP_Uns64 size = chunk->getPadSize( true );
+
+ //
+ // find FREE chunks before start chunk
+ //
+ if( index > 0 )
+ {
+ XMP_Int32 i = XMP_Int32( index-1 );
+ Chunk* c = NULL;
+
+ do
+ {
+ c = tree.getChildAt(i);
+
+ if( this->isFREEChunk( *c ) )
+ {
+ size += c->getPadSize( true );
+ indexStart = XMP_Uns32(i);
+ i--;
+ }
+ else
+ {
+ c = NULL;
+ }
+
+ } while( i >= 0 && c != NULL );
+ }
+
+ //
+ // find FREE chunks after start chunk
+ //
+ if( index+1 < tree.numChildren() )
+ {
+ XMP_Uns32 i = index+1;
+ Chunk* c = NULL;
+
+ do
+ {
+ c = tree.getChildAt(i);
+
+ if( this->isFREEChunk( *c ) )
+ {
+ size += c->getPadSize( true );
+ indexEnd = i;
+ i++;
+ }
+ else
+ {
+ c = NULL;
+ }
+
+ } while( i < tree.numChildren() && c != NULL );
+ }
+
+ if( indexStart < indexEnd )
+ {
+ //
+ // more than one FREE chunks, so merge them
+ //
+ for( XMP_Uns32 i=indexStart; i<=indexEnd; i++ )
+ {
+ Chunk* f = tree.getChildAt( indexStart );
+ tree.removeChildAt( indexStart );
+ delete f;
+ }
+
+ ret = this->createFREE( size );
+ tree.insertChildAt( indexStart, ret );
+ ret->setAsNew();
+ }
+ else
+ {
+ //
+ // one single FREE chunk
+ //
+ ret = chunk;
+ }
+ }
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+//
+// IChunkBehavior::findFREEChunk(...)
+//
+// Purpose: Find a FREE chunk with the passed total size (including header).
+// If the FREE chunk is found then take care of the fact that is has
+// to be that large (or larger) then the minimum size of a FREE chunk.
+// The method takes also into account that the passed size probably
+// not includes a pad byte
+//
+//-----------------------------------------------------------------------------
+
+XMP_Int32 IChunkBehavior::findFREEChunk( const IChunkContainer& tree, XMP_Uns64 requiredSize /*including header*/ )
+{
+ XMP_Int32 ret = -1;
+
+ for( XMP_Uns32 i=0; i<tree.numChildren(); i++ )
+ {
+ Chunk* chunk = tree.getChildAt(i);
+
+ XMP_Uns64 requiredSizePad = requiredSize + ( requiredSize % 2 ); // required size including pad byte
+
+ if( this->isFREEChunk( *chunk ) &&
+ ( chunk->getPadSize( true ) == requiredSizePad ||
+ chunk->getPadSize( true ) >= requiredSizePad + getMinFREESize() ) )
+ {
+ ret = i;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+//
+// IChunkBehavior::moveChunks(...)
+//
+// Purpose: Move a range of chunks from one container to another.
+//
+//-----------------------------------------------------------------------------
+
+void IChunkBehavior::moveChunks( IChunkContainer& srcTree, IChunkContainer& destTree, XMP_Uns32 start )
+{
+ XMP_Validate( &srcTree != &destTree, "Source tree and destination tree shouldn't be the same", kXMPErr_InternalFailure );
+
+ XMP_Uns32 end = srcTree.numChildren();
+
+ for( XMP_Uns32 index=start; index<end; index++ )
+ {
+ Chunk* chunk = srcTree.removeChildAt( start );
+ destTree.appendChild( chunk, true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// IChunkBehavior::isMovable(...)
+//
+// Purpose: May we move a chunk of passed id/type
+//
+//-----------------------------------------------------------------------------
+
+bool IChunkBehavior::isMovable( const Chunk& chunk ) const
+{
+ bool ret = false;
+
+ if( !this->isFREEChunk( chunk ) && mMovablePaths != NULL )
+ {
+ ChunkPath path( chunk.getIdentifier() );
+ Chunk* parent = chunk.getParent();
+
+ while( parent != NULL && parent->getID() != kChunk_NONE )
+ {
+ path.insert( parent->getIdentifier() );
+ parent = parent->getParent();
+ }
+
+ for( std::vector<ChunkPath>::iterator iter=mMovablePaths->begin(); iter!=mMovablePaths->end() && !ret; iter++ )
+ {
+ ret = ( iter->match( path ) == ChunkPath::kFullMatch );
+ }
+ }
+
+ return ret;
+}
diff --git a/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h b/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h
new file mode 100644
index 0000000..418afb3
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h
@@ -0,0 +1,239 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _IChunkBehavior_h_
+#define _IChunkBehavior_h_
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+#include <vector>
+
+namespace IFF_RIFF
+{
+
+/**
+ The IChunkBehavior is an interface that provides access to algorithm
+ for the read and write process of IFF/RIFF formated streams.
+ A file format specific instance based on this interface gets injected into
+ the class ChunkController and offers format specific algorithm wherever
+ the processing of a certain file format differs from the general specification
+ of RIFF/IFF.
+ That is e.g. the RF64 format where it is possible that the size value of the
+ top level chunk doesn't represent the real size. Or AVI, where are special rules
+ if the size of a chunk exceed the 4GB border.
+*/
+
+class IChunkContainer;
+class Chunk;
+struct ChunkIdentifier;
+class ChunkPath;
+
+class IChunkBehavior
+{
+public:
+ IChunkBehavior() : mMovablePaths(NULL) {}
+ virtual ~IChunkBehavior() {};
+
+ /**
+ * Set list of chunk paths of chunks that might be moved within the hierarchy
+ */
+ inline void setMovablePaths( std::vector<ChunkPath>* paths ) { mMovablePaths = paths; }
+
+ /**
+ Validate the passed in size value, identify the valid size if the passed in isn't valid
+ and return the valid size.
+ throw an exception if the passed in size isn't valid and there's no way to identify a
+ valid size.
+
+ @param size Size value
+ @param id Identifier of chunk
+ @param tree Chunk tree
+ @param stream Stream handle
+
+ @return Valid size value.
+ */
+ virtual XMP_Uns64 getRealSize( const XMP_Uns64 size, const ChunkIdentifier& id, IChunkContainer& tree, XMP_IO* stream ) = 0;
+
+ /**
+ Return the maximum size of a single chunk, i.e. the maximum size of a top-level chunk.
+
+ @return Maximum size
+ */
+ virtual XMP_Uns64 getMaxChunkSize() const = 0;
+
+ /**
+ Return true if the passed identifier is valid for top-level chunks of a certain format.
+
+ @param id Chunk identifier
+ @param chunkNo order number of top-level chunk
+ @return true, if passed id is a valid top-level chunk
+ */
+ virtual bool isValidTopLevelChunk( const ChunkIdentifier& id, XMP_Uns32 chunkNo ) = 0;
+
+ /**
+ Fix the hierarchy of chunks depending ones based on size changes of one or more chunks
+ and second based on format specific rules.
+ Throw an exception if the hierarchy can't be fixed.
+
+ @param tree Vector of root chunks.
+ */
+ virtual void fixHierarchy( IChunkContainer& tree ) = 0;
+
+ /**
+ Insert a new chunk into the hierarchy of chunks. The behavior needs to decide the position
+ of the new chunk and has to do the insertion.
+
+ @param tree Chunk tree
+ @param chunk New chunk
+ */
+ virtual void insertChunk( IChunkContainer& tree, Chunk& chunk ) = 0;
+
+ /**
+ Remove the chunk described by the passed ChunkPath.
+
+ @param tree Chunk tree
+ @param path Path to the chunk that needs to be removed
+
+ @return true if the chunk was removed and need to be deleted
+ */
+ virtual bool removeChunk( IChunkContainer& tree, Chunk& chunk ) = 0;
+
+protected:
+ /**
+ Create a FREE chunk.
+ If the chunkSize is smaller than the header+type - size then create an annotation chunk.
+ If the passed size is odd, then add a pad byte.
+
+ @param chunkSize Total size including header
+ @return New FREE chunk
+ */
+ virtual Chunk* createFREE( XMP_Uns64 chunkSize ) = 0;
+
+ /**
+ Check if the passed chunk is a FREE chunk.
+ (Could be also a small annotation chunk with zero bytes in its data)
+
+ @param chunk A chunk
+
+ @return true if the passed chunk is a FREE chunk
+ */
+ virtual XMP_Bool isFREEChunk( const Chunk& chunk ) const = 0;
+
+ /**
+ Return the minimum size of a FREE chunk
+ */
+ virtual XMP_Uns64 getMinFREESize( ) const
+ = 0;
+protected:
+ /************************************************************************/
+ /* END of Interface. The following are helper functions for all derived */
+ /* Behavior Classes */
+ /************************************************************************/
+
+ /**
+ Find a FREE chunk with the passed total size (including header). If the FREE chunk is found then
+ take care of the fact that is has to be that large (or larger) then the minimum size of a FREE chunk.
+ The method takes also into account that the passed size probably not includes a pad byte
+
+ @param tree Parent tree
+ @param requiredSize Required total size (including header)
+
+ @return Index of found FREE chunk
+ */
+ XMP_Int32 findFREEChunk( const IChunkContainer& tree, XMP_Uns64 requiredSize );
+
+ /**
+ May we move a chunk of passed id/type
+
+ @param identifier id and type of chunk
+ @return true if such a chunk might be moved within the tree
+ */
+ bool isMovable( const Chunk& chunk ) const;
+
+ /**
+ Validate recursively the offset values of all chunks.
+ Throws an exception if there is any discrepancy with the calculated offset.
+
+ @param tree (Sub-)tree of chunks
+ @param startOffset First offset in the (sub-)tree
+ */
+ void validateOffsets( IChunkContainer& tree, XMP_Uns64 startOffset = 0 );
+
+ /**
+ Retrieve the free space at the passed position in the child list of the parent tree.
+ If there's a FREE chunk then return it.
+
+ @param outFreeBytes On return it takes the number of free bytes
+ @param tree Parent tree
+ @param index Position in the child list of the parent tree
+
+ @return FREE chunk if available
+ */
+ Chunk* getFreeSpace( XMP_Int64& outFreeBytes, const IChunkContainer& tree, XMP_Uns32 index ) const ;
+
+ /**
+ Try to arrange all chunks of the source tree at their current location.
+ The method looks for FREE chunk around or for size changes of the chunks around and try that space.
+ If a chunk can't be arrange at its location it is moved to the end of the destination tree.
+
+ @param srcTree Tree that consists of the chunks that needs to be arranged
+ @param destTree Tree where chunks are added to if they can't be arranged
+
+ @return Index of last proceeded chunk
+ */
+ void arrangeChunksInPlace( IChunkContainer& srcTree, IChunkContainer& destTree );
+
+ /**
+ This method proceeds the list of Chunks of the source tree in the passed range and looks for FREE chunks
+ in the destination tree to move the source chunks to.
+ Source tree and destination tree could be one and the same but it's not required. If both trees are the
+ same then it's not allowed to cross source and destination ranges.
+
+ @param srcTree Tree that consists of the chunks that needs to be arranged
+ @param destTree Tree where the method looks for FREE chunks
+ @param srcStart Start index within the source tree
+ @param srcEnd End index within the source tree (if booth, srcStart and srcEnd are zero then the complete list
+ of the source tree is proceeded)
+ @param destStart Start index within the destination tree
+ @param destEnd End index within the destination tree (if booth, destStart and destEnd are zero then the complete list
+ of the destination tree is proceeded)
+ */
+ void arrangeChunksInTree( IChunkContainer& srcTree, IChunkContainer& destTree );
+
+ /**
+ Try to merge existing FREE chunks at the passed position in the child list
+ of the passed parent tree.
+ The algorithm looks at the position, before the position and after the position.
+
+ @param tree Parent tree
+ @param index Position in the child list of the parent tree
+
+ @return FREE chunk if available at the passed position (in case of a merge
+ the merged FREE chunk)
+ */
+ Chunk* mergeFreeChunks( IChunkContainer& tree, XMP_Uns32 index );
+
+ /**
+ Move a range of chunks from one container to another starting at the start index up to the
+ end of the srcTree.
+
+ @param srcTree Source container
+ @param destTree Destination container
+ @param start Start index of source container
+ */
+ void moveChunks( IChunkContainer& srcTree, IChunkContainer& destTree, XMP_Uns32 start );
+
+private:
+ std::vector<ChunkPath>* mMovablePaths;
+};
+
+} // IChunkBehavior
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/IFF/IChunkContainer.h b/XMPFiles/source/FormatSupport/IFF/IChunkContainer.h
new file mode 100644
index 0000000..04ad425
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IFF/IChunkContainer.h
@@ -0,0 +1,87 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _IChunkContainer_h_
+#define _IChunkContainer_h_
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+
+namespace IFF_RIFF
+{
+
+/**
+ The interface IChunkContainer defines the access to child chunks of
+ an existing chunk.
+*/
+
+class Chunk;
+
+class IChunkContainer
+{
+public:
+ virtual ~IChunkContainer() {};
+
+ /**
+ * @return Returns the number children chunks.
+ */
+ virtual XMP_Uns32 numChildren() const = 0;
+
+ /**
+ * Returns a child node.
+ *
+ * @param pos position of the child node to return
+ * @return Returns the child node at the given position.
+ */
+ virtual Chunk* getChildAt( XMP_Uns32 pos ) const = 0;
+
+ /**
+ * Appends a child node at the end of the children list.
+ *
+ * @param node the new node
+ * @param adjustSizes adjust size&offset of chunk and parents
+ * @return Returns the added node.
+ */
+ virtual void appendChild( Chunk* node, XMP_Bool adjustSizes = true ) = 0;
+
+ /**
+ * Inserts a child node at a certain position.
+ *
+ * @param pos position in the children list to add the new node
+ * @param node the new node
+ * @return Returns the added node.
+ */
+ virtual void insertChildAt( XMP_Uns32 pos, Chunk* node ) = 0;
+
+ /**
+ * Removes a child node at a given position.
+ *
+ * @param pos position of the node to delete in the children list
+ *
+ * @return The removed chunk
+ */
+ virtual Chunk* removeChildAt( XMP_Uns32 pos ) = 0;
+
+ /**
+ * Remove child at the passed position and insert the new chunk
+ *
+ * @param pos Position of chunk that will be replaced
+ * @param chunk New chunk
+ *
+ * @return Replaced chunk
+ */
+ virtual Chunk* replaceChildAt( XMP_Uns32 pos, Chunk* node ) = 0;
+
+ /** creates a string representation of the chunk and its children.
+ */
+ virtual std::string toString( std::string tab = std::string() , XMP_Bool showOriginal = false ) = 0;
+};
+
+}
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/IFF/IChunkData.h b/XMPFiles/source/FormatSupport/IFF/IChunkData.h
new file mode 100644
index 0000000..62674e6
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IFF/IChunkData.h
@@ -0,0 +1,108 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _IChunkData_h_
+#define _IChunkData_h_
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+#include "source/Endian.h"
+#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h"
+#include <string>
+
+namespace IFF_RIFF
+{
+
+/**
+ * This interface allow access to only the data part of a chunk.
+ */
+class IChunkData
+{
+ public:
+
+ virtual ~IChunkData() {};
+
+ /**
+ * Get the chunk ID
+ *
+ * @return Return the ID, 0 if the chunk does not have an ID.
+ */
+ virtual XMP_Uns32 getID() const = 0;
+
+ /**
+ * Get the chunk type (if available)
+ * (the first four data bytes of the chunk could be a chunk type)
+ *
+ * @return Return the type, kType_NONE if the chunk does not contain data.
+ */
+ virtual XMP_Uns32 getType() const = 0;
+
+ /**
+ * Get the chunk identifier [id and type]
+ *
+ * @return Return the identifier
+ */
+ virtual const ChunkIdentifier& getIdentifier() const = 0;
+
+ /**
+ * Access the data of the chunk.
+ *
+ * @param data OUT pointer to the byte array
+ * @return size of the data block, 0 if no data is available
+ */
+ virtual XMP_Uns64 getData( const XMP_Uns8** data ) const = 0;
+
+ /**
+ * Set new data for the chunk.
+ * Will delete an existing internal buffer and recreate a new one
+ * and copy the given data into that new buffer.
+ *
+ * @param data pointer to the data to put into the chunk
+ * @param size Size of the data block
+ */
+ virtual void setData( const XMP_Uns8* const data, XMP_Uns64 size, XMP_Bool writeType = false ) = 0;
+
+ /**
+ * Returns the current size of the Chunk without pad byte.
+ *
+ * @param includeHeader if set, the returned size will be the whole chunk size including the header
+ * @return either size of the data block of the chunk or size of the whole chunk
+ */
+ virtual XMP_Uns64 getSize( bool includeHeader = false ) const = 0;
+
+
+ /* The following methods are getter/setter for certain data types.
+ They always take care of little-endian/big-endian issues.
+ The offset starts at the data area of the Chunk. */
+
+ virtual XMP_Uns32 getUns32( XMP_Uns64 offset=0 ) const = 0;
+ virtual void setUns32( XMP_Uns32 value, XMP_Uns64 offset=0 ) = 0;
+
+ virtual XMP_Uns64 getUns64( XMP_Uns64 offset=0 ) const = 0;
+ virtual void setUns64( XMP_Uns64 value, XMP_Uns64 offset=0 ) = 0;
+
+ virtual XMP_Int32 getInt32( XMP_Uns64 offset=0 ) const = 0;
+ virtual void setInt32( XMP_Int32 value, XMP_Uns64 offset=0 ) = 0;
+
+ virtual XMP_Int64 getInt64( XMP_Uns64 offset=0 ) const = 0;
+ virtual void setInt64( XMP_Int64 value, XMP_Uns64 offset=0 ) = 0;
+
+ virtual std::string getString( XMP_Uns64 size = 0, XMP_Uns64 offset=0 ) const = 0;
+ virtual void setString( std::string value, XMP_Uns64 offset=0 ) = 0;
+
+ /**
+ * Creates a string representation of the chunk and its children.
+ */
+ virtual std::string toString( std::string tabs = std::string() , XMP_Bool showOriginal = false ) = 0;
+
+}; // IChunkData
+
+} // namespace
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/IPTC_Support.cpp b/XMPFiles/source/FormatSupport/IPTC_Support.cpp
new file mode 100644
index 0000000..e8fda45
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IPTC_Support.cpp
@@ -0,0 +1,758 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp"
+#include "source/EndianUtils.hpp"
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+#include "source/XIO.hpp"
+
+// =================================================================================================
+/// \file IPTC_Support.cpp
+/// \brief XMPFiles support for IPTC (IIM) DataSets.
+///
+// =================================================================================================
+
+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.
+ { kIPTC_Title, kIPTC_MapLangAlt, 64, kXMP_NS_DC, "title" },
+ { kIPTC_EditStatus, kIPTC_UnmappedText, 64, "", "" }, // Not mapped to XMP.
+ { kIPTC_EditorialUpdate, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP.
+ { kIPTC_Urgency, kIPTC_MapSimple, 1, kXMP_NS_Photoshop, "Urgency" },
+ { kIPTC_SubjectCode, kIPTC_MapSpecial, 236, kXMP_NS_IPTCCore, "SubjectCode" }, // Only the reference number is in the XMP.
+ { kIPTC_Category, kIPTC_MapSimple, 3, kXMP_NS_Photoshop, "Category" },
+ { kIPTC_SuppCategory, kIPTC_MapArray, 32, kXMP_NS_Photoshop, "SupplementalCategories" },
+ { kIPTC_FixtureIdentifier, kIPTC_UnmappedText, 32, "", "" }, // Not mapped to XMP.
+ { kIPTC_Keyword, kIPTC_MapArray, 64, kXMP_NS_DC, "subject" },
+ { kIPTC_ContentLocCode, kIPTC_UnmappedText, 3, "", "" }, // Not mapped to XMP.
+ { kIPTC_ContentLocName, kIPTC_UnmappedText, 64, "", "" }, // Not mapped to XMP.
+ { kIPTC_ReleaseDate, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP.
+ { kIPTC_ReleaseTime, kIPTC_UnmappedText, 11, "", "" }, // Not mapped to XMP.
+ { kIPTC_ExpDate, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP.
+ { kIPTC_ExpTime, kIPTC_UnmappedText, 11, "", "" }, // Not mapped to XMP.
+ { kIPTC_Instructions, kIPTC_MapSimple, 256, kXMP_NS_Photoshop, "Instructions" },
+ { kIPTC_ActionAdvised, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP.
+ { 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" }, // ! 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_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" },
+ { kIPTC_State, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "State" },
+ { kIPTC_CountryCode, kIPTC_MapSimple, 3, kXMP_NS_IPTCCore, "CountryCode" },
+ { kIPTC_Country, kIPTC_MapSimple, 64, kXMP_NS_Photoshop, "Country" },
+ { kIPTC_JobID, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "TransmissionReference" },
+ { 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_Map3Way, 128, "", "" }, // ! In the 3 way Exif-IPTC-XMP set.
+ { kIPTC_Contact, kIPTC_UnmappedText, 128, "", "" }, // Not mapped to XMP.
+ { 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.
+ { kIPTC_ImageOrientation, kIPTC_UnmappedText, 1, "", "" }, // Not mapped to XMP.
+ { kIPTC_LanguageID, kIPTC_UnmappedText, 3, "", "" }, // Not mapped to XMP.
+ { kIPTC_AudioType, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP.
+ { kIPTC_AudioSampleRate, kIPTC_UnmappedText, 6, "", "" }, // Not mapped to XMP.
+ { kIPTC_AudioSampleRes, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP.
+ { 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_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.
+const IntellectualGenreMapping kIntellectualGenreMappings[] =
+{ { "001", "Current" },
+ { "002", "Analysis" },
+ { "003", "Archive material" },
+ { "004", "Background" },
+ { "005", "Feature" },
+ { "006", "Forecast" },
+ { "007", "History" },
+ { "008", "Obituary" },
+ { "009", "Opinion" },
+ { "010", "Polls and surveys" },
+ { "010", "Polls & Surveys" },
+ { "011", "Profile" },
+ { "012", "Results listings and statistics" },
+ { "012", "Results Listings & Tables" },
+ { "013", "Side bar and supporting information" },
+ { "013", "Side bar & Supporting information" },
+ { "014", "Summary" },
+ { "015", "Transcript and verbatim" },
+ { "015", "Transcript & Verbatim" },
+ { "016", "Interview" },
+ { "017", "From the scene" },
+ { "017", "From the Scene" },
+ { "018", "Retrospective" },
+ { "019", "Synopsis" },
+ { "019", "Statistics" },
+ { "020", "Update" },
+ { "021", "Wrapup" },
+ { "021", "Wrap-up" },
+ { "022", "Press release" },
+ { "022", "Press Release" },
+ { "023", "Quote" },
+ { "024", "Press-digest" },
+ { "025", "Review" },
+ { "026", "Curtain raiser" },
+ { "027", "Actuality" },
+ { "028", "Question and answer" },
+ { "029", "Music" },
+ { "030", "Response to a question" },
+ { "031", "Raw sound" },
+ { "032", "Scener" },
+ { "033", "Text only" },
+ { "034", "Voicer" },
+ { "035", "Fixture" },
+ { 0, 0 } }; // ! Must be last as a sentinel.
+
+// =================================================================================================
+// FindKnownDataSet
+// ================
+
+static const DataSetCharacteristics* FindKnownDataSet ( XMP_Uns8 dsNum )
+{
+ size_t i = 0;
+
+ while ( kKnownDataSets[i].dsNum < dsNum ) ++i; // The list is short enough for a linear search.
+
+ if ( kKnownDataSets[i].dsNum != dsNum ) return 0;
+ return &kKnownDataSets[i];
+
+} // FindKnownDataSet
+
+// =================================================================================================
+// IPTC_Manager::ParseMemoryDataSets
+// =================================
+//
+// Parse the IIM block. All datasets are put into the map, although we only really care about 1:90
+// and the known 2:xx ones. This approach is tolerant of ill-formed IIM where the datasets are not
+// sorted by ascending record number.
+
+void IPTC_Manager::ParseMemoryDataSets ( const void* data, XMP_Uns32 length, bool copyData /* = true */ )
+{
+ // Get rid of any existing data.
+
+ DataSetMap::iterator dsPos = this->dataSets.begin();
+ DataSetMap::iterator dsEnd = this->dataSets.end();
+
+ for ( ; dsPos != dsEnd; ++dsPos ) this->DisposeLooseValue ( dsPos->second );
+
+ this->dataSets.clear();
+
+ if ( this->ownedContent ) free ( this->iptcContent );
+ this->ownedContent = false; // Set to true later if the content is copied.
+ this->iptcContent = 0;
+ this->iptcLength = 0;
+
+ this->changed = false;
+
+ if ( length == 0 ) return;
+ if ( (data == 0) || (*((XMP_Uns8*)data) != 0x1C) ) XMP_Throw ( "Not valid IPTC, no leading 0x1C", kXMPErr_BadIPTC );
+
+ // Allocate space for the full in-memory data and copy it.
+
+ if ( length > 10*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based IPTC", kXMPErr_BadIPTC );
+ this->iptcLength = length;
+
+ if ( ! copyData ) {
+ this->iptcContent = (XMP_Uns8*)data;
+ } else {
+ this->iptcContent = (XMP_Uns8*) malloc(length);
+ if ( this->iptcContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+ memcpy ( this->iptcContent, data, length ); // AUDIT: Safe, malloc'ed length bytes above.
+ this->ownedContent = true;
+ }
+
+ // Build the map of the DataSets. The records should be in ascending order, but we tolerate out
+ // of order IIM produced by some unknown apps. The DataSets in a record can be in any order.
+ // There are no record markers, just DataSets, so the ordering is really just clumping of
+ // DataSets by record. A normal DataSet has a 5 byte header followed by the value. An extended
+ // DataSet has a special length in the header, a variable sized value length, and the value.
+ //
+ // Normal DataSet
+ // 0 uint8 0x1C
+ // 1 uint8 record number
+ // 2 uint8 DataSet number
+ // 3 uint16 big endian value size, 0..32767, larger means extended DataSet
+ //
+ // In an extended DataSet the extended length size is the low 15 bits of the standard size. The
+ // extended length follows as a big endian unsigned number. The IPTC does not specify, but we
+ // require the extended length size to be in the range 1..4. It should only be 3 or 4, we allow
+ // the degenerate cases.
+
+ XMP_Uns8* iptcPtr = this->iptcContent;
+ XMP_Uns8* iptcEnd = iptcPtr + length;
+ XMP_Uns8* iptcLimit = iptcEnd - kMinDataSetSize;
+ XMP_Uns32 dsLen; // ! The large form can have values up to 4GB in length.
+
+ this->utf8Encoding = false;
+
+ for ( ; iptcPtr <= iptcLimit; iptcPtr += dsLen ) {
+
+ // iptcLimit - last possible DataSet, 5 bytes before IIM block end
+
+ // iptcPtr - working pointer to the current byte of interest
+ // dsPtr - pointer to the current DataSet's header
+ // dsLen - value length, does not include extended size bytes
+
+ XMP_Uns8* dsPtr = iptcPtr;
+ XMP_Uns8 oneC = *iptcPtr;
+ XMP_Uns8 recNum = *(iptcPtr+1);
+ XMP_Uns8 dsNum = *(iptcPtr+2);
+
+ if ( oneC != 0x1C ) break; // No more DataSets.
+
+ dsLen = GetUns16BE ( iptcPtr+3 ); // ! Compute dsLen before any "continue", needed for loop increment!
+ iptcPtr += 5; // Advance to the data (or extended length).
+
+ if ( (dsLen & 0x8000) != 0 ) {
+ XMP_Assert ( dsLen <= 0xFFFF );
+ XMP_Uns32 lenLen = dsLen & 0x7FFF;
+ if ( (lenLen == 0) || (lenLen > 4) ) break; // Bad DataSet, can't find the next so quit.
+ if ( iptcPtr > (iptcEnd - lenLen) ) break; // Bad final DataSet. Throw instead?
+ dsLen = 0;
+ for ( XMP_Uns16 i = 0; i < lenLen; ++i, ++iptcPtr ) {
+ dsLen = (dsLen << 8) + *iptcPtr;
+ }
+ }
+
+ if ( iptcPtr > (iptcEnd - dsLen) ) break; // Bad final DataSet. Throw instead?
+
+ // Make a special check for 1:90 denoting UTF-8 text.
+ if ( (recNum == 1) && (dsNum == 90) ) {
+ if ( (dsLen == 3) && (memcmp ( iptcPtr, "\x1B\x25\x47", 3 ) == 0) ) this->utf8Encoding = true;
+ }
+
+ XMP_Uns16 mapID = recNum*1000 + dsNum;
+ DataSetInfo dsInfo ( recNum, dsNum, dsLen, iptcPtr );
+ DataSetMap::iterator dsPos = this->dataSets.find ( mapID );
+
+ bool repeatable = false;
+
+ const DataSetCharacteristics* knownDS = FindKnownDataSet ( dsNum );
+
+ if ( (knownDS == 0) || (knownDS->mapForm == kIPTC_MapArray) ) {
+ repeatable = true; // Allow repeats for unknown DataSets.
+ } else if ( (dsNum == kIPTC_Creator) || (dsNum == kIPTC_SubjectCode) ) {
+ repeatable = true;
+ }
+
+ if ( repeatable || (dsPos == this->dataSets.end()) ) {
+ DataSetMap::value_type mapValue ( mapID, dsInfo );
+ (void) this->dataSets.insert ( this->dataSets.upper_bound ( mapID ), mapValue );
+ } else {
+ this->DisposeLooseValue ( dsPos->second );
+ dsPos->second = dsInfo; // Keep the last copy of illegal repeats.
+ }
+
+ }
+
+} // IPTC_Manager::ParseMemoryDataSets
+
+// =================================================================================================
+// IPTC_Manager::GetDataSet
+// ========================
+
+size_t IPTC_Manager::GetDataSet ( XMP_Uns8 dsNum, DataSetInfo* info, size_t which /* = 0 */ ) const
+{
+
+ XMP_Uns16 mapID = 2000 + dsNum; // ! Only deal with 2:xx datasets.
+ DataSetMap::const_iterator dsPos = this->dataSets.lower_bound ( mapID );
+ if ( (dsPos == this->dataSets.end()) || (dsPos->second.recNum != 2) || (dsNum != dsPos->second.dsNum) ) return 0;
+
+ size_t dsCount = this->dataSets.count ( mapID );
+ 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; // Can't do "dsPos += which", no iter+int operator.
+ *info = dsPos->second;
+ }
+
+ return dsCount;
+
+} // IPTC_Manager::GetDataSet
+
+// =================================================================================================
+// IPTC_Manager::GetDataSet_UTF8
+// =============================
+
+size_t IPTC_Manager::GetDataSet_UTF8 ( XMP_Uns8 dsNum, std::string * utf8Str, size_t which /* = 0 */ ) const
+{
+ if ( utf8Str != 0 ) utf8Str->erase();
+
+ DataSetInfo dsInfo;
+ size_t dsCount = GetDataSet ( dsNum, &dsInfo, which );
+ if ( dsCount == 0 ) return 0;
+
+ if ( utf8Str != 0 ) {
+ if ( this->utf8Encoding ) {
+ utf8Str->assign ( (char*)dsInfo.dataPtr, dsInfo.dataLen );
+ } 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 );
+ }
+ }
+
+ return dsCount;
+
+} // IPTC_Manager::GetDataSet_UTF8
+
+// =================================================================================================
+// IPTC_Manager::DisposeLooseValue
+// ===============================
+//
+// Dispose of loose values from SetDataSet calls after the last UpdateMemoryDataSets.
+
+// ! Don't try to make the DataSetInfo struct be self-cleaning. It is a primary public type, returned
+// ! from GetDataSet. Making it self-cleaning would get into nasty assignment and pointer ownership
+// ! issues, far worse than doing this explicit cleanup.
+
+void IPTC_Manager::DisposeLooseValue ( DataSetInfo & dsInfo )
+{
+ if ( dsInfo.dataLen == 0 ) return;
+
+ XMP_Uns8* dataBegin = this->iptcContent;
+ XMP_Uns8* dataEnd = dataBegin + this->iptcLength;
+
+ if ( ((XMP_Uns8*)dsInfo.dataPtr < dataBegin) || ((XMP_Uns8*)dsInfo.dataPtr >= dataEnd) ) {
+ free ( (void*) dsInfo.dataPtr );
+ dsInfo.dataPtr = 0;
+ }
+
+} // IPTC_Manager::DisposeLooseValue
+
+// =================================================================================================
+// IPTC_Manager::AppendDataSet
+//
+// ! Calling instance must make sure that dsPtr is large enough to hold data from dsInfo !
+// ===========================
+
+XMP_Uns8* IPTC_Manager::AppendDataSet ( XMP_Uns8* dsPtr, const DataSetInfo & dsInfo ) {
+
+ dsPtr[0] = 0x1C;
+ dsPtr[1] = dsInfo.recNum;
+ dsPtr[2] = dsInfo.dsNum;
+ dsPtr += 3;
+
+ XMP_Uns32 dsLen = dsInfo.dataLen;
+ if ( dsLen <= 0x7FFF ) {
+ PutUns16BE ( (XMP_Uns16)dsLen, dsPtr );
+ dsPtr += 2;
+ } else {
+ PutUns16BE ( 0x8004, dsPtr );
+ PutUns32BE ( dsLen, dsPtr+2 );
+ dsPtr += 6;
+ }
+ // AUDIT: Calling instance must make sure that dsPtr is large enough.
+ // Currently this function is only called from UpdateMemoryDataSets where the size of dsPtr
+ // is calculated including all data sets in the list, so the dsInfo data should always fit.
+ memcpy ( dsPtr, dsInfo.dataPtr, dsLen );
+ dsPtr += dsLen;
+
+ return dsPtr;
+
+} // IPTC_Manager::AppendDataSet
+
+// =================================================================================================
+// =================================================================================================
+
+// =================================================================================================
+// IPTC_Writer::~IPTC_Writer
+// =========================
+//
+// Dispose of loose values from SetDataSet calls after the last UpdateMemoryDataSets.
+
+IPTC_Writer::~IPTC_Writer()
+{
+ DataSetMap::iterator dsPos = this->dataSets.begin();
+ DataSetMap::iterator dsEnd = this->dataSets.end();
+
+ for ( ; dsPos != dsEnd; ++dsPos ) this->DisposeLooseValue ( dsPos->second );
+
+} // IPTC_Writer::~IPTC_Writer
+
+// =================================================================================================
+// IPTC_Writer::SetDataSet_UTF8
+// ============================
+
+void IPTC_Writer::SetDataSet_UTF8 ( XMP_Uns8 dsNum, const void* utf8Ptr, XMP_Uns32 utf8Len, long which /* = -1 */ )
+{
+ const DataSetCharacteristics* knownDS = FindKnownDataSet ( dsNum );
+ if ( knownDS == 0 ) XMP_Throw ( "Can only set known IPTC DataSets", kXMPErr_InternalFailure );
+
+ // Decide which character encoding to use and get a temporary pointer to the value.
+
+ XMP_Uns8 * tempPtr;
+ XMP_Uns32 dataLen;
+ std::string localStr;
+
+ if ( kUTF8_Mode == kUTF8_AlwaysMode ) {
+
+ // Always use UTF-8.
+ if ( ! this->utf8Encoding ) this->ConvertToUTF8();
+ tempPtr = (XMP_Uns8*) utf8Ptr;
+ dataLen = utf8Len;
+
+ } else if ( kUTF8_Mode == kUTF8_IncomingMode ) {
+
+ // 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 {
+ 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;
+ }
+
+ }
+
+ // Set the value for this DataSet, making a non-transient copy of the value. Respect UTF-8 character
+ // boundaries when truncating. This is easy to check. If the first truncated byte has 10 in the
+ // high order 2 bits then we are in the middle of a UTF-8 multi-byte character.
+ // Back up to just before a byte with 11 in the high order 2 bits.
+
+ if ( dataLen > knownDS->maxLen ) {
+ dataLen = (XMP_Uns32)knownDS->maxLen;
+ if ( this->utf8Encoding && ((tempPtr[dataLen] >> 6) == 2) ) {
+ for ( ; (dataLen > 0) && ((tempPtr[dataLen] >> 6) != 3); --dataLen ) {}
+ }
+ }
+
+ XMP_Uns16 mapID = 2000 + dsNum; // ! Only deal with 2:xx datasets.
+ DataSetMap::iterator dsPos = this->dataSets.find ( mapID );
+ long currCount = (long) this->dataSets.count ( mapID );
+
+ bool repeatable = false;
+
+ if ( knownDS->mapForm == kIPTC_MapArray ) {
+ repeatable = true;
+ } else if ( (dsNum == kIPTC_Creator) || (dsNum == kIPTC_SubjectCode) ) {
+ repeatable = true;
+ }
+
+ if ( ! repeatable ) {
+
+ if ( which > 0 ) XMP_Throw ( "Non-repeatable IPTC DataSet", kXMPErr_BadParam );
+
+ } else {
+
+ if ( which < 0 ) which = currCount; // The default is to append.
+
+ if ( which > currCount ) {
+ XMP_Throw ( "Invalid index for IPTC DataSet", kXMPErr_BadParam );
+ } else if ( which == currCount ) {
+ dsPos = this->dataSets.end(); // To make later checks do the right thing.
+ } else {
+ dsPos = this->dataSets.lower_bound ( mapID );
+ for ( ; which > 0; --which ) ++dsPos;
+ }
+
+ }
+
+ if ( dsPos != this->dataSets.end() ) {
+ if ( (dsPos->second.dataLen == dataLen) && (memcmp ( dsPos->second.dataPtr, tempPtr, dataLen ) == 0) ) {
+ return; // ! New value matches the old, don't update.
+ }
+ }
+
+ XMP_Uns8 * dataPtr = (XMP_Uns8*) malloc ( dataLen );
+ if ( dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+ memcpy ( dataPtr, tempPtr, dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above.
+
+ DataSetInfo dsInfo ( 2, dsNum, dataLen, dataPtr );
+
+ if ( dsPos != this->dataSets.end() ) {
+ this->DisposeLooseValue ( dsPos->second );
+ dsPos->second = dsInfo;
+ } else {
+ DataSetMap::value_type mapValue ( mapID, dsInfo );
+ (void) this->dataSets.insert ( this->dataSets.upper_bound ( mapID ), mapValue );
+ }
+
+ this->changed = true;
+
+} // IPTC_Writer::SetDataSet_UTF8
+
+// =================================================================================================
+// IPTC_Writer::DeleteDataSet
+// ==========================
+
+void IPTC_Writer::DeleteDataSet ( XMP_Uns8 dsNum, long which /* = -1 */ )
+{
+ XMP_Uns16 mapID = 2000 + dsNum; // ! Only deal with 2:xx datasets.
+ DataSetMap::iterator dsBegin = this->dataSets.lower_bound ( mapID ); // Set for which == -1.
+ DataSetMap::iterator dsEnd = this->dataSets.upper_bound ( mapID );
+
+ if ( dsBegin == dsEnd ) return; // Nothing to delete.
+
+ if ( which >= 0 ) {
+ long currCount = (long) this->dataSets.count ( mapID );
+ if ( which >= currCount ) return; // Nothing to delete.
+ for ( ; which > 0; --which ) ++dsBegin;
+ dsEnd = dsBegin; ++dsEnd; // ! Can't do "dsEnd = dsBegin+1"!
+ }
+
+ for ( DataSetMap::iterator dsPos = dsBegin; dsPos != dsEnd; ++dsPos ) {
+ this->DisposeLooseValue ( dsPos->second );
+ }
+
+ this->dataSets.erase ( dsBegin, dsEnd );
+ this->changed = true;
+
+} // IPTC_Writer::DeleteDataSet
+
+// =================================================================================================
+// IPTC_Writer::UpdateMemoryDataSets
+// =================================
+//
+// 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.
+
+void IPTC_Writer::UpdateMemoryDataSets()
+{
+ if ( ! this->changed ) return;
+
+ DataSetMap::iterator dsPos;
+ DataSetMap::iterator dsEnd = this->dataSets.end();
+
+ 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. All DataSets other than 1:90 and 2:xx are preserved
+ // as-is. If local text is used then 1:90 is omitted, if UTF-8 text is used then 1:90 is written
+ // to say so. The map key of (record*1000 + dataset) provides the desired overall order.
+
+ XMP_Uns32 newLength = (5+2); // We always write 2:00 for the IIM version.
+ if ( this->utf8Encoding ) newLength += (5+3); // For 1:90, if written.
+
+ for ( dsPos = this->dataSets.begin(); dsPos != dsEnd; ++dsPos ) { // Accumulate the other sizes.
+ const XMP_Uns16 mapID = dsPos->first;
+ if ( (mapID == 1090) || (mapID == 2000) ) continue; // Already dealt with 1:90 and 2:00.
+ XMP_Uns32 dsLen = dsPos->second.dataLen;
+ newLength += (5 + dsLen);
+ if ( dsLen > 0x7FFF ) newLength += 4; // We always use a 4 byte extended length for big values.
+ }
+
+ // Allocate the new IIM block.
+
+ XMP_Uns8* newContent = (XMP_Uns8*) malloc ( newLength );
+ if ( newContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+
+ XMP_Uns8* dsPtr = newContent;
+
+ // Write the record 0 DataSets. There should not be any, but let's be safe.
+
+ for ( dsPos = this->dataSets.begin(); dsPos != dsEnd; ++dsPos ) {
+ const DataSetInfo & currDS = dsPos->second;
+ if ( currDS.recNum > 0 ) break;
+ dsPtr = AppendDataSet ( dsPtr, currDS );
+ }
+
+ // Write 1:90 then any other record 1 DataSets.
+
+ 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);
+ }
+
+ for ( ; dsPos != dsEnd; ++dsPos ) {
+ const DataSetInfo & currDS = dsPos->second;
+ if ( currDS.recNum > 1 ) break;
+ XMP_Assert ( currDS.recNum == 1 );
+ if ( currDS.dsNum == 90 ) continue;
+ dsPtr = AppendDataSet ( dsPtr, currDS );
+ }
+
+ // Write 2:00 then all of the other DataSets from all records.
+
+ 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);
+ }
+
+ for ( ; dsPos != dsEnd; ++dsPos ) {
+ const DataSetInfo & currDS = dsPos->second;
+ XMP_Assert ( currDS.recNum > 1 );
+ if ( dsPos->first == 2000 ) continue; // Check both the record number and DataSet number.
+ dsPtr = AppendDataSet ( dsPtr, currDS );
+ }
+
+ XMP_Assert ( dsPtr == (newContent + newLength) );
+
+ // Parse the new block, it is the best way to reset internal info and rebuild the map.
+
+ this->ParseMemoryDataSets ( newContent, newLength, false ); // Don't make another copy of the content.
+ XMP_Assert ( this->iptcLength == newLength );
+ this->ownedContent = (newLength > 0); // We really do own the new content, if not empty.
+
+} // IPTC_Writer::UpdateMemoryDataSets
+
+// =================================================================================================
+// IPTC_Writer::ConvertToUTF8
+// ==========================
+//
+// Convert the values of existing text DataSets to UTF-8. For now we only accept text DataSets.
+
+void IPTC_Writer::ConvertToUTF8()
+{
+ XMP_Assert ( ! this->utf8Encoding );
+ std::string utf8Str;
+
+ DataSetMap::iterator dsPos = this->dataSets.begin();
+ DataSetMap::iterator dsEnd = this->dataSets.end();
+
+ for ( ; dsPos != dsEnd; ++dsPos ) {
+
+ DataSetInfo & dsInfo = dsPos->second;
+
+ ReconcileUtils::LocalToUTF8 ( dsInfo.dataPtr, dsInfo.dataLen, &utf8Str );
+ this->DisposeLooseValue ( dsInfo );
+
+ 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.
+
+ }
+
+ this->utf8Encoding = true;
+
+} // IPTC_Writer::ConvertToUTF8
+
+// =================================================================================================
+// IPTC_Writer::ConvertToLocal
+// ===========================
+//
+// Convert the values of existing text DataSets to local. For now we only accept text DataSets.
+
+void IPTC_Writer::ConvertToLocal()
+{
+ XMP_Assert ( this->utf8Encoding );
+ std::string localStr;
+
+ DataSetMap::iterator dsPos = this->dataSets.begin();
+ DataSetMap::iterator dsEnd = this->dataSets.end();
+
+ for ( ; dsPos != dsEnd; ++dsPos ) {
+
+ DataSetInfo & dsInfo = dsPos->second;
+
+ ReconcileUtils::UTF8ToLocal ( dsInfo.dataPtr, dsInfo.dataLen, &localStr );
+ this->DisposeLooseValue ( dsInfo );
+
+ 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.
+
+ }
+
+ this->utf8Encoding = false;
+
+} // IPTC_Writer::ConvertToLocal
+
+// =================================================================================================
+// IPTC_Writer::CheckRoundTripLoss
+// ===============================
+//
+// See if we still need UTF-8 because of round-trip loss. Returns true if there is loss.
+
+bool IPTC_Writer::CheckRoundTripLoss()
+{
+ XMP_Assert ( this->utf8Encoding );
+ std::string localStr, rtStr;
+
+ DataSetMap::iterator dsPos = this->dataSets.begin();
+ DataSetMap::iterator dsEnd = this->dataSets.end();
+
+ for ( ; dsPos != dsEnd; ++dsPos ) {
+
+ DataSetInfo & dsInfo = dsPos->second;
+
+ XMP_StringPtr utf8Ptr = (XMP_StringPtr) dsInfo.dataPtr;
+ XMP_StringLen utf8Len = dsInfo.dataLen;
+
+ ReconcileUtils::UTF8ToLocal ( utf8Ptr, utf8Len, &localStr );
+ ReconcileUtils::LocalToUTF8 ( localStr.data(), localStr.size(), &rtStr );
+
+ if ( (rtStr.size() != utf8Len) || (memcmp ( rtStr.data(), utf8Ptr, utf8Len ) != 0) ) {
+ return true; // Had round-trip loss, keep UTF-8.
+ }
+
+ }
+
+ return false; // No loss.
+
+} // IPTC_Writer::CheckRoundTripLoss
+
+// =================================================================================================
diff --git a/XMPFiles/source/FormatSupport/IPTC_Support.hpp b/XMPFiles/source/FormatSupport/IPTC_Support.hpp
new file mode 100644
index 0000000..4c2d2bc
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/IPTC_Support.hpp
@@ -0,0 +1,305 @@
+#ifndef __IPTC_Support_hpp__
+#define __IPTC_Support_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include <map>
+
+#include "public/include/XMP_Const.h"
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/EndianUtils.hpp"
+
+// =================================================================================================
+/// \file IPTC_Support.hpp
+/// \brief XMPFiles support for IPTC (IIM) DataSets.
+///
+/// This header provides IPTC (IIM) DataSet support specific to the needs of XMPFiles. This is not
+/// intended for general purpose IPTC processing. There is a small tree of derived classes, 1
+/// virtual base class and 2 concrete leaf classes:
+/// \code
+/// IPTC_Manager - The root virtual base class.
+/// IPTC_Reader - A derived concrete leaf class for memory-based read-only access.
+/// IPTC_Writer - A derived concrete leaf class for memory-based read-write access.
+/// \endcode
+///
+/// \c IPTC_Manager declares all of the public methods except for specialized constructors in the
+/// leaf classes. The read-only classes throw an XMP_Error exception for output methods like
+/// \c SetDataSet. They return appropriate values for "safe" methods, \c IsChanged will return false
+/// for example.
+///
+/// The IPTC DataSet organization differs from TIFF tags and Photoshop image resources in allowing
+/// muultiple occurrences for some IDs. The C++ STL multimap is a natural data structure for IPTC.
+///
+/// Support is only provided for DataSet 1:90 to decide if local or UTF-8 text encoding is used, and
+/// the following text valued DataSets: 2:05, 2:10, 2:15, 2:20, 2:25, 2:40, 2:55, 2:80, 2:85, 2:90,
+/// 2:95, 2:101, 2:103, 2:105, 2:110, 2:115, 2:116, 2:120, and 2:122. DataSet 2:00 is ignored when
+/// reading but always written.
+///
+/// \note Unlike the TIFF_Manager and PSIR_Manager class trees, IPTC_Manager only provides in-memory
+/// implementations. The total size of IPTC data is small enough to make this reasonable.
+///
+/// \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.
+// =================================================================================================
+
+
+// =================================================================================================
+// =================================================================================================
+
+enum { // List of recognized 2:* IIM DataSets. The names are from IIMv4 and IPTC4XMP.
+ kIPTC_ObjectType = 3,
+ kIPTC_IntellectualGenre = 4,
+ kIPTC_Title = 5,
+ kIPTC_EditStatus = 7,
+ kIPTC_EditorialUpdate = 8,
+ kIPTC_Urgency = 10,
+ kIPTC_SubjectCode = 12,
+ kIPTC_Category = 15,
+ kIPTC_SuppCategory = 20,
+ kIPTC_FixtureIdentifier = 22,
+ kIPTC_Keyword = 25,
+ kIPTC_ContentLocCode = 26,
+ kIPTC_ContentLocName = 27,
+ kIPTC_ReleaseDate = 30,
+ kIPTC_ReleaseTime = 35,
+ kIPTC_ExpDate = 37,
+ kIPTC_ExpTime = 38,
+ kIPTC_Instructions = 40,
+ kIPTC_ActionAdvised = 42,
+ kIPTC_RefService = 45,
+ kIPTC_RefDate = 47,
+ kIPTC_RefNumber = 50,
+ kIPTC_DateCreated = 55,
+ kIPTC_TimeCreated = 60,
+ kIPTC_DigitalCreateDate = 62,
+ kIPTC_DigitalCreateTime = 63,
+ kIPTC_OriginProgram = 65,
+ kIPTC_ProgramVersion = 70,
+ kIPTC_ObjectCycle = 75,
+ kIPTC_Creator = 80,
+ kIPTC_CreatorJobtitle = 85,
+ kIPTC_City = 90,
+ kIPTC_Location = 92,
+ kIPTC_State = 95,
+ kIPTC_CountryCode = 100,
+ kIPTC_Country = 101,
+ kIPTC_JobID = 103,
+ kIPTC_Headline = 105,
+ kIPTC_Provider = 110,
+ kIPTC_Source = 115,
+ kIPTC_CopyrightNotice = 116,
+ kIPTC_Contact = 118,
+ kIPTC_Description = 120,
+ kIPTC_DescriptionWriter = 122,
+ kIPTC_RasterizedCaption = 125,
+ kIPTC_ImageType = 130,
+ kIPTC_ImageOrientation = 131,
+ kIPTC_LanguageID = 135,
+ kIPTC_AudioType = 150,
+ kIPTC_AudioSampleRate = 151,
+ kIPTC_AudioSampleRes = 152,
+ kIPTC_AudioDuration = 153,
+ kIPTC_AudioOutcue = 154,
+ kIPTC_PreviewFormat = 200,
+ kIPTC_PreviewFormatVers = 201,
+ kIPTC_PreviewData = 202
+};
+
+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.
+};
+
+struct DataSetCharacteristics {
+ XMP_Uns8 dsNum;
+ XMP_Uns8 mapForm;
+ size_t maxLen;
+ XMP_StringPtr xmpNS;
+ XMP_StringPtr xmpProp;
+};
+
+extern const DataSetCharacteristics kKnownDataSets[];
+
+struct IntellectualGenreMapping {
+ XMP_StringPtr refNum; // The reference number as a 3 digit string.
+ XMP_StringPtr name; // The intellectual genre name.
+};
+
+extern const IntellectualGenreMapping kIntellectualGenreMappings[];
+
+// =================================================================================================
+// IPTC_Manager
+// ============
+
+class IPTC_Manager {
+public:
+
+ // ---------------------------------------------------------------------------------------------
+ // Types and constants.
+
+ struct DataSetInfo {
+ XMP_Uns8 recNum, dsNum;
+ XMP_Uns32 dataLen;
+ XMP_Uns8 * dataPtr; // ! The data is read-only. Raw data pointer, beware of character encoding.
+ DataSetInfo() : recNum(0), dsNum(0), dataLen(0), dataPtr(0) {};
+ DataSetInfo ( XMP_Uns8 _recNum, XMP_Uns8 _dsNum, XMP_Uns32 _dataLen, XMP_Uns8 * _dataPtr )
+ : recNum(_recNum), dsNum(_dsNum), dataLen(_dataLen), dataPtr(_dataPtr) {};
+ };
+
+ // ---------------------------------------------------------------------------------------------
+ // Parse a binary IPTC (IIM) block.
+
+ void ParseMemoryDataSets ( const void* data, XMP_Uns32 length, bool copyData = true );
+
+ // ---------------------------------------------------------------------------------------------
+ // Get the information about a 2:xx DataSet. Returns the number of occurrences. The "which"
+ // parameter selects the occurrence, they are numbered from 0 to count-1. Returns 0 if which is
+ // too large.
+
+ size_t GetDataSet ( XMP_Uns8 dsNum, DataSetInfo* info, size_t which = 0 ) const;
+
+ // ---------------------------------------------------------------------------------------------
+ // Get the value of a text 2:xx DataSet as UTF-8. The returned pointer must be treated as
+ // read-only. Calls GetDataSet then does a local to UTF-8 conversion if necessary.
+
+ size_t GetDataSet_UTF8 ( XMP_Uns8 dsNum, std::string * utf8Str, size_t which = 0 ) const;
+
+ // ---------------------------------------------------------------------------------------------
+ // Set the value of a text 2:xx DataSet from a UTF-8 string. Does a UTF-8 to local conversion if
+ // necessary. If the encoding mode is currently local and this value has round-trip loss, then
+ // the encoding mode will be changed to UTF-8 and all existing values will be converted.
+ // Modifies an existing occurrence if "which" is within range. Adds an occurrence if which
+ // equals the current count, or which is -1 and repeats are allowed. Throws an exception if
+ // which is too large. The dataPtr provides the raw data, text must be in the right encoding.
+
+ virtual void SetDataSet_UTF8 ( XMP_Uns8 dsNum, const void* utf8Ptr, XMP_Uns32 utf8Len, long which = -1 ) = 0;
+
+ // ---------------------------------------------------------------------------------------------
+ // Delete an existing 2:xx DataSet. Deletes all occurrences if which is -1.
+
+ virtual void DeleteDataSet ( XMP_Uns8 dsNum, long which = -1 ) = 0;
+
+ // ---------------------------------------------------------------------------------------------
+ // Determine if any 2:xx DataSets are changed.
+
+ virtual bool IsChanged() = 0;
+
+ // ---------------------------------------------------------------------------------------------
+ // Determine if UTF-8 or local text encoding is being used.
+
+ bool UsingUTF8() const { return this->utf8Encoding; };
+
+ // --------------------------------------------------
+ // Update all DataSets to reflect the changed values.
+
+ virtual void UpdateMemoryDataSets() = 0;
+
+ // ---------------------------------------------------------------------------------------------
+ // 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.
+
+ XMP_Uns32 GetBlockInfo ( void** dataPtr ) const
+ { if ( dataPtr != 0 ) *dataPtr = this->iptcContent; return this->iptcLength; };
+
+ // ---------------------------------------------------------------------------------------------
+
+ virtual ~IPTC_Manager() { if ( this->ownedContent ) free ( this->iptcContent ); };
+
+protected:
+
+ enum { kMinDataSetSize = 5 }; // 1+1+1+2
+
+ typedef std::multimap<XMP_Uns16,DataSetInfo> DataSetMap;
+ DataSetMap dataSets; // ! All datasets are in the map, key is (record*1000 + dataset).
+
+ XMP_Uns8* iptcContent;
+ XMP_Uns32 iptcLength;
+
+ 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),
+ changed(false), ownedContent(false), utf8Encoding(false) {};
+
+ void DisposeLooseValue ( DataSetInfo & dsInfo );
+ XMP_Uns8* AppendDataSet ( XMP_Uns8* dsPtr, const DataSetInfo & dsInfo );
+
+}; // IPTC_Manager
+
+
+// =================================================================================================
+// =================================================================================================
+
+
+// =================================================================================================
+// IPTC_Reader
+// ===========
+
+class IPTC_Reader : public IPTC_Manager {
+public:
+
+ IPTC_Reader() {};
+
+ void SetDataSet_UTF8 ( XMP_Uns8 dsNum, const void* utf8Ptr, XMP_Uns32 utf8Len, long which = -1 ) { NotAppropriate(); };
+
+ void DeleteDataSet ( XMP_Uns8 dsNum, long which = -1 ) { NotAppropriate(); };
+
+ bool IsChanged() { return false; };
+
+ void UpdateMemoryDataSets() { NotAppropriate(); };
+
+ virtual ~IPTC_Reader() {};
+
+private:
+
+ static inline void NotAppropriate() { XMP_Throw ( "Not appropriate for IPTC_Reader", kXMPErr_InternalFailure ); };
+
+}; // IPTC_Reader
+
+// =================================================================================================
+// IPTC_Writer
+// ===========
+
+class IPTC_Writer : public IPTC_Manager {
+public:
+
+ void SetDataSet_UTF8 ( XMP_Uns8 dsNum, const void* utf8Ptr, XMP_Uns32 utf8Len, long which = -1 );
+
+ void DeleteDataSet ( XMP_Uns8 dsNum, long which = -1 );
+
+ bool IsChanged() { return changed; };
+
+ void UpdateMemoryDataSets ();
+
+ IPTC_Writer() {};
+
+ virtual ~IPTC_Writer();
+
+private:
+
+ void ConvertToUTF8();
+ void ConvertToLocal();
+
+ bool CheckRoundTripLoss();
+
+}; // IPTC_Writer
+
+// =================================================================================================
+
+#endif // __IPTC_Support_hpp__
diff --git a/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.cpp b/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.cpp
new file mode 100644
index 0000000..670b8fb
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.cpp
@@ -0,0 +1,153 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp"
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XIO.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 ( XMP_IO* 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 ) fileRef->Seek ( boxOffset, kXMP_SeekFromStart );
+ (void) fileRef->ReadAll ( buffer, 8 );
+
+ 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 = fileRef->Length() - (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) fileRef->ReadAll ( buffer, 8 );
+ 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/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp b/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp
new file mode 100644
index 0000000..115a0e8
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp
@@ -0,0 +1,104 @@
+#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 "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.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_edts = 0x65647473UL,
+ k_elst = 0x656C7374UL,
+ 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 ( XMP_IO* fileRef, const XMP_Uns64 boxOffset, const XMP_Uns64 boxLimit,
+ BoxInfo * info, bool doSeek = true, bool throwErrors = false );
+
+// XMP_Uns32 CountChildBoxes ( XMP_IO* fileRef, const XMP_Uns64 childOffset, const XMP_Uns64 childLimit );
+
+} // namespace ISO_Media
+
+// =================================================================================================
+
+#endif // __ISOBaseMedia_Support_hpp__
diff --git a/XMPFiles/source/FormatSupport/MOOV_Support.cpp b/XMPFiles/source/FormatSupport/MOOV_Support.cpp
new file mode 100644
index 0000000..b9b4996
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/MOOV_Support.cpp
@@ -0,0 +1,554 @@
+// =================================================================================================
+// 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 "XMPFiles/source/FormatSupport/MOOV_Support.hpp"
+
+#include "XMPFiles/source/FormatSupport/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::GetBoxInfo
+// =========================
+
+void MOOV_Manager::GetBoxInfo ( const BoxRef ref, BoxInfo * info ) const
+{
+
+ this->FillBoxInfo ( *((BoxNode*)ref), info );
+
+} // MOOV_Manager::GetBoxInfo
+
+// =================================================================================================
+// 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_edts : pathSuffix = "/edts"; 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/XMPFiles/source/FormatSupport/MOOV_Support.hpp b/XMPFiles/source/FormatSupport/MOOV_Support.hpp
new file mode 100644
index 0000000..814a8bb
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/MOOV_Support.hpp
@@ -0,0 +1,217 @@
+#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 "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+
+#include "source/EndianUtils.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+#include <vector>
+
+#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.
+ // GetBoxInfo - Get the info if we already have the ref.
+ // 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;
+
+ void GetBoxInfo ( const BoxRef ref, 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/XMPFiles/source/FormatSupport/MacScriptExtracts.h b/XMPFiles/source/FormatSupport/MacScriptExtracts.h
new file mode 100644
index 0000000..9856183
--- /dev/null
+++ b/XMPFiles/source/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/XMPFiles/source/FormatSupport/PNG_Support.cpp b/XMPFiles/source/FormatSupport/PNG_Support.cpp
new file mode 100644
index 0000000..c58fc87
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/PNG_Support.cpp
@@ -0,0 +1,341 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/PNG_Support.hpp"
+
+#include "source/XIO.hpp"
+
+#include <string.h>
+
+typedef std::basic_string<unsigned char> filebuffer;
+
+namespace CRC
+{
+ /* Table of CRCs of all 8-bit messages. */
+ static unsigned long crc_table[256];
+
+ /* Flag: has the table been computed? Initially false. */
+ static int crc_table_computed = 0;
+
+ /* Make the table for a fast CRC. */
+ static void make_crc_table(void)
+ {
+ unsigned long c;
+ int n, k;
+
+ for (n = 0; n < 256; n++)
+ {
+ c = (unsigned long) n;
+ for (k = 0; k < 8; k++)
+ {
+ if (c & 1)
+ {
+ c = 0xedb88320L ^ (c >> 1);
+ }
+ else
+ {
+ c = c >> 1;
+ }
+ }
+ crc_table[n] = c;
+ }
+ crc_table_computed = 1;
+ }
+
+ /* Update a running CRC with the bytes buf[0..len-1]--the CRC
+ should be initialized to all 1's, and the transmitted value
+ is the 1's complement of the final running CRC (see the
+ crc() routine below). */
+
+ static unsigned long update_crc(unsigned long crc, unsigned char *buf, int len)
+ {
+ unsigned long c = crc;
+ int n;
+
+ if (!crc_table_computed)
+ {
+ make_crc_table();
+ }
+
+ for (n = 0; n < len; n++)
+ {
+ c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
+ }
+
+ return c;
+ }
+
+ /* Return the CRC of the bytes buf[0..len-1]. */
+ static unsigned long crc(unsigned char *buf, int len)
+ {
+ return update_crc(0xffffffffL, buf, len) ^ 0xffffffffL;
+ }
+} // namespace CRC
+
+namespace PNG_Support
+{
+ enum chunkType {
+ // Critical chunks - (shall appear in this order, except PLTE is optional)
+ IHDR = 'IHDR',
+ PLTE = 'PLTE',
+ IDAT = 'IDAT',
+ IEND = 'IEND',
+ // Ancillary chunks - (need not appear in this order)
+ cHRM = 'cHRM',
+ gAMA = 'gAMA',
+ iCCP = 'iCCP',
+ sBIT = 'sBIT',
+ sRGB = 'sRGB',
+ bKGD = 'bKGD',
+ hIST = 'hIST',
+ tRNS = 'tRNS',
+ pHYs = 'pHYs',
+ sPLT = 'sPLT',
+ tIME = 'tIME',
+ iTXt = 'iTXt',
+ tEXt = 'tEXt',
+ zTXt = 'zTXt'
+
+ };
+
+ // =============================================================================================
+
+ long OpenPNG ( XMP_IO* fileRef, ChunkState & inOutChunkState )
+ {
+ XMP_Uns64 pos = 0;
+ long name;
+ XMP_Uns32 len;
+
+ pos = fileRef->Seek ( 8, kXMP_SeekFromStart );
+ if (pos != 8) return 0;
+
+ // read first and following chunks
+ while ( ReadChunk ( fileRef, inOutChunkState, &name, &len, pos) ) {}
+
+ return (long)inOutChunkState.chunks.size();
+
+ }
+
+ // =============================================================================================
+
+ bool ReadChunk ( XMP_IO* fileRef, ChunkState & inOutChunkState, long * chunkType, XMP_Uns32 * chunkLength, XMP_Uns64 & inOutPosition )
+ {
+ try
+ {
+ XMP_Uns64 startPosition = inOutPosition;
+ long bytesRead;
+ char buffer[4];
+
+ bytesRead = fileRef->Read ( buffer, 4 );
+ if ( bytesRead != 4 ) return false;
+ inOutPosition += 4;
+ *chunkLength = GetUns32BE(buffer);
+
+ bytesRead = fileRef->Read ( buffer, 4 );
+ if ( bytesRead != 4 ) return false;
+ inOutPosition += 4;
+ *chunkType = GetUns32BE(buffer);
+
+ inOutPosition += *chunkLength;
+
+ bytesRead = fileRef->Read ( buffer, 4 );
+ if ( bytesRead != 4 ) return false;
+ inOutPosition += 4;
+ long crc = GetUns32BE(buffer);
+
+ ChunkData newChunk;
+
+ newChunk.pos = startPosition;
+ newChunk.len = *chunkLength;
+ newChunk.type = *chunkType;
+
+ // check for XMP in iTXt-chunk
+ if (newChunk.type == iTXt)
+ {
+ CheckiTXtChunkHeader(fileRef, inOutChunkState, newChunk);
+ }
+
+ inOutChunkState.chunks.push_back ( newChunk );
+
+ fileRef->Seek ( inOutPosition, kXMP_SeekFromStart );
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+
+ return true;
+
+ }
+
+ // =============================================================================================
+
+ bool WriteXMPChunk ( XMP_IO* fileRef, XMP_Uns32 len, const char* inBuffer )
+ {
+ bool ret = false;
+ unsigned long datalen = (4 + ITXT_HEADER_LEN + len);
+ unsigned char* buffer = new unsigned char[datalen];
+
+ try
+ {
+ size_t pos = 0;
+ memcpy(&buffer[pos], ITXT_CHUNK_TYPE, 4);
+ pos += 4;
+ memcpy(&buffer[pos], ITXT_HEADER_DATA, ITXT_HEADER_LEN);
+ pos += ITXT_HEADER_LEN;
+ memcpy(&buffer[pos], inBuffer, len);
+
+ unsigned long crc_value = MakeUns32BE( CalculateCRC( buffer, datalen ));
+ datalen -= 4;
+ unsigned long len_value = MakeUns32BE( datalen );
+ datalen += 4;
+
+ fileRef->Write ( &len_value, 4 );
+ fileRef->Write ( buffer, datalen );
+ fileRef->Write ( &crc_value, 4 );
+
+ ret = true;
+ }
+ catch ( ... ) {}
+
+ delete [] buffer;
+
+ return ret;
+ }
+
+ // =============================================================================================
+
+ bool CopyChunk ( XMP_IO* sourceRef, XMP_IO* destRef, ChunkData& chunk )
+ {
+ try
+ {
+ sourceRef->Seek ( chunk.pos, kXMP_SeekFromStart );
+ XIO::Copy (sourceRef, destRef, (chunk.len + 12));
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+
+ return true;
+ }
+
+ // =============================================================================================
+
+ unsigned long UpdateChunkCRC( XMP_IO* fileRef, ChunkData& inOutChunkData )
+ {
+ unsigned long ret = 0;
+ unsigned long datalen = (inOutChunkData.len + 4);
+ unsigned char* buffer = new unsigned char[datalen];
+
+ try
+ {
+ fileRef->Seek ( (inOutChunkData.pos + 4), kXMP_SeekFromStart );
+
+ size_t pos = 0;
+ long bytesRead = fileRef->Read ( &buffer[pos], (inOutChunkData.len + 4) );
+
+ unsigned long crc = CalculateCRC( buffer, (inOutChunkData.len + 4) );
+ unsigned long crc_value = MakeUns32BE( crc );
+
+ fileRef->Seek ( (inOutChunkData.pos + 4 + 4 + inOutChunkData.len), kXMP_SeekFromStart );
+ fileRef->Write ( &crc_value, 4 );
+
+ ret = crc;
+ }
+ catch ( ... ) {}
+
+ delete [] buffer;
+
+ return ret;
+ }
+
+ // =============================================================================================
+
+ bool CheckIHDRChunkHeader ( ChunkData& inOutChunkData )
+ {
+ return (inOutChunkData.type == IHDR);
+ }
+
+ // =============================================================================================
+
+ unsigned long CheckiTXtChunkHeader ( XMP_IO* fileRef, ChunkState& inOutChunkState, ChunkData& inOutChunkData )
+ {
+ try
+ {
+ fileRef->Seek ( (inOutChunkData.pos + 8), kXMP_SeekFromStart );
+
+ char buffer[ITXT_HEADER_LEN];
+ long bytesRead = fileRef->Read ( buffer, ITXT_HEADER_LEN );
+
+ if (bytesRead == ITXT_HEADER_LEN)
+ {
+ if (memcmp(buffer, ITXT_HEADER_DATA, ITXT_HEADER_LEN) == 0)
+ {
+ // return length of XMP
+ if (inOutChunkData.len > ITXT_HEADER_LEN)
+ {
+ inOutChunkState.xmpPos = inOutChunkData.pos + 8 + ITXT_HEADER_LEN;
+ inOutChunkState.xmpLen = inOutChunkData.len - ITXT_HEADER_LEN;
+ inOutChunkState.xmpChunk = inOutChunkData;
+ inOutChunkData.xmp = true;
+
+ return inOutChunkState.xmpLen;
+ }
+ }
+ }
+ }
+ catch ( ... ) {}
+
+ return 0;
+ }
+
+ bool ReadBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns32 len, char * outBuffer )
+ {
+ try
+ {
+ if ( (fileRef == 0) || (outBuffer == 0) ) return false;
+
+ fileRef->Seek ( pos, kXMP_SeekFromStart );
+ long bytesRead = fileRef->Read ( outBuffer, len );
+ if ( XMP_Uns32(bytesRead) != len ) return false;
+
+ return true;
+ }
+ catch ( ... ) {}
+
+ return false;
+ }
+
+ bool WriteBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns32 len, const char * inBuffer )
+ {
+ try
+ {
+ if ( (fileRef == 0) || (inBuffer == 0) ) return false;
+
+ fileRef->Seek ( pos, kXMP_SeekFromStart );
+ fileRef->Write ( inBuffer, len );
+
+ return true;
+ }
+ catch ( ... ) {}
+
+ return false;
+ }
+
+ unsigned long CalculateCRC( unsigned char* inBuffer, XMP_Uns32 len )
+ {
+ return CRC::update_crc(0xffffffffL, inBuffer, len) ^ 0xffffffffL;
+ }
+
+} // namespace PNG_Support
diff --git a/XMPFiles/source/FormatSupport/PNG_Support.hpp b/XMPFiles/source/FormatSupport/PNG_Support.hpp
new file mode 100644
index 0000000..6b0afbf
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/PNG_Support.hpp
@@ -0,0 +1,78 @@
+#ifndef __PNG_Support_hpp__
+#define __PNG_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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+
+#define PNG_SIGNATURE_LEN 8
+#define PNG_SIGNATURE_DATA "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"
+
+#define ITXT_CHUNK_TYPE "iTXt"
+
+#define ITXT_HEADER_LEN 22
+#define ITXT_HEADER_DATA "XML:com.adobe.xmp\0\0\0\0\0"
+
+namespace PNG_Support
+{
+ class ChunkData
+ {
+ public:
+ ChunkData() : pos(0), len(0), type(0), xmp(false) {}
+ virtual ~ChunkData() {}
+
+ // | length | type | data | crc(type+data) |
+ // | 4 | 4 | val(length) | 4 |
+ //
+ XMP_Uns64 pos; // file offset of chunk
+ XMP_Uns32 len; // length of chunk data
+ long type; // name/type of chunk
+ bool xmp; // iTXt-chunk with XMP ?
+ };
+
+ typedef std::vector<ChunkData> ChunkVector;
+ typedef ChunkVector::iterator ChunkIterator;
+
+ class ChunkState
+ {
+ public:
+ ChunkState() : xmpPos(0), xmpLen(0) {}
+ virtual ~ChunkState() {}
+
+ XMP_Uns64 xmpPos;
+ XMP_Uns32 xmpLen;
+ ChunkData xmpChunk;
+ ChunkVector chunks; /* vector of chunks */
+ };
+
+ long OpenPNG ( XMP_IO* fileRef, ChunkState& inOutChunkState );
+
+ bool ReadChunk ( XMP_IO* fileRef, ChunkState& inOutChunkState, long* chunkType, XMP_Uns32* chunkLength, XMP_Uns64& inOutPosition );
+ bool WriteXMPChunk ( XMP_IO* fileRef, XMP_Uns32 len, const char* inBuffer );
+ bool CopyChunk ( XMP_IO* sourceRef, XMP_IO* destRef, ChunkData& chunk );
+ unsigned long UpdateChunkCRC( XMP_IO* fileRef, ChunkData& inOutChunkData );
+
+ bool CheckIHDRChunkHeader ( ChunkData& inOutChunkData );
+ unsigned long CheckiTXtChunkHeader ( XMP_IO* fileRef, ChunkState& inOutChunkState, ChunkData& inOutChunkData );
+
+ bool ReadBuffer ( XMP_IO* fileRef, XMP_Uns64& pos, XMP_Uns32 len, char* outBuffer );
+ bool WriteBuffer ( XMP_IO* fileRef, XMP_Uns64& pos, XMP_Uns32 len, const char* inBuffer );
+
+ unsigned long CalculateCRC( unsigned char* inBuffer, XMP_Uns32 len );
+
+} // namespace PNG_Support
+
+#endif // __PNG_Support_hpp__
diff --git a/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp b/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp
new file mode 100644
index 0000000..aecfec1
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp
@@ -0,0 +1,602 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp"
+#include "source/EndianUtils.hpp"
+#include "source/XIO.hpp"
+
+#include <string.h>
+
+// =================================================================================================
+/// \file PSIR_FileWriter.cpp
+/// \brief Implementation of the file-based or read-write form of PSIR_Manager.
+// =================================================================================================
+
+// =================================================================================================
+// IsMetadataImgRsrc
+// =================
+
+static inline bool IsMetadataImgRsrc ( XMP_Uns16 id )
+{
+ 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
+
+// =================================================================================================
+// PSIR_FileWriter::DeleteExistingInfo
+// ===================================
+//
+// Delete all existing info about image resources.
+
+void PSIR_FileWriter::DeleteExistingInfo()
+{
+ XMP_Assert ( ! (this->memParsed && this->fileParsed) );
+
+ if ( this->memParsed ) {
+ if ( this->ownedContent ) free ( this->memContent );
+ } else if ( this->fileParsed ) {
+ InternalRsrcMap::iterator irPos = this->imgRsrcs.begin();
+ InternalRsrcMap::iterator irEnd = this->imgRsrcs.end();
+ for ( ; irPos != irEnd; ++irPos ) irPos->second.changed = true; // Fool the InternalRsrcInfo destructor.
+ }
+
+ this->imgRsrcs.clear();
+
+ this->memContent = 0;
+ this->memLength = 0;
+
+ this->changed = false;
+ this->legacyDeleted = false;
+ this->memParsed = false;
+ this->fileParsed = false;
+ this->ownedContent = false;
+
+} // PSIR_FileWriter::DeleteExistingInfo
+
+// =================================================================================================
+// PSIR_FileWriter::~PSIR_FileWriter
+// =================================
+
+PSIR_FileWriter::~PSIR_FileWriter()
+{
+ XMP_Assert ( ! (this->memParsed && this->fileParsed) );
+
+ if ( this->ownedContent ) {
+ XMP_Assert ( this->memContent != 0 );
+ free ( this->memContent );
+ }
+
+} // PSIR_FileWriter::~PSIR_FileWriter
+
+// =================================================================================================
+// PSIR_FileWriter::GetImgRsrc
+// ===========================
+
+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;
+ info->dataPtr = rsrcInfo.dataPtr;
+ info->origOffset = rsrcInfo.origOffset;
+ }
+
+ return true;
+
+} // PSIR_FileWriter::GetImgRsrc
+
+// =================================================================================================
+// PSIR_FileWriter::SetImgRsrc
+// ===========================
+
+void PSIR_FileWriter::SetImgRsrc ( XMP_Uns16 id, const void* clientPtr, XMP_Uns32 length )
+{
+ InternalRsrcInfo* rsrcPtr = 0;
+ InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id );
+
+ if ( rsrcPos == this->imgRsrcs.end() ) {
+
+ // This resource is not yet in the map, create the map entry.
+ InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, length, this->fileParsed ) );
+ rsrcPos = this->imgRsrcs.insert ( rsrcPos, mapValue );
+ rsrcPtr = &rsrcPos->second;
+
+ } else {
+
+ rsrcPtr = &rsrcPos->second;
+
+ // The resource already exists, make sure the value is actually changing.
+ if ( (length == rsrcPtr->dataLen) &&
+ (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 );
+ memcpy ( rsrcPtr->dataPtr, clientPtr, length ); // AUDIT: Safe, malloc'ed length bytes above.
+
+ this->changed = true;
+
+} // PSIR_FileWriter::SetImgRsrc
+
+// =================================================================================================
+// PSIR_FileWriter::DeleteImgRsrc
+// ==============================
+
+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;
+
+} // PSIR_FileWriter::DeleteImgRsrc
+
+// =================================================================================================
+// PSIR_FileWriter::IsLegacyChanged
+// ================================
+
+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();
+
+ for ( ; irPos != irEnd; ++irPos ) {
+ const InternalRsrcInfo & rsrcInfo = irPos->second;
+ if ( rsrcInfo.changed && (rsrcInfo.id != kPSIR_XMP) ) return true;
+ }
+
+ return false; // Can get here if the XMP is the only thing changed.
+
+} // PSIR_FileWriter::IsLegacyChanged
+
+// =================================================================================================
+// PSIR_FileWriter::ParseMemoryResources
+// =====================================
+
+void PSIR_FileWriter::ParseMemoryResources ( 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 data and copy it.
+
+ if ( ! copyData ) {
+ this->memContent = (XMP_Uns8*) data;
+ XMP_Assert ( ! this->ownedContent );
+ } else {
+ if ( length > 100*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based PSIR", kXMPErr_BadPSIR );
+ this->memContent = (XMP_Uns8*) malloc ( length );
+ if ( this->memContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+ memcpy ( this->memContent, data, length ); // AUDIT: Safe, malloc'ed length bytes above.
+ this->ownedContent = true;
+ }
+ this->memLength = length;
+
+ // Capture the info for all of the resources. We're using a map keyed by ID, so only one
+ // resource of each ID is recognized. Redundant resources are not legit, but have been seen in
+ // the field. In particular, one case has been seen of a duplicate IIM block with one empty.
+ // In general we keep the first seen copy to be compatible with Photoshop. A later non-empty
+ // copy will be taken though if the current one is empty.
+
+ // ! Don't use map[id] to lookup, that creates a default entry if none exists!
+
+ 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);
+ psirPtr += 6; // Advance to the resource name.
+
+ 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);
+ psirPtr += 4; // Advance to the resource data.
+
+ 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 ) {
+
+ XMP_Uns32 rsrcOffset = XMP_Uns32( origin - this->memContent );
+ XMP_Uns32 rsrcLength = XMP_Uns32( nextRsrc - origin ); // Includes trailing pad.
+ XMP_Assert ( (rsrcLength & 1) == 0 );
+ this->otherRsrcs.push_back ( OtherRsrcInfo ( rsrcOffset, rsrcLength ) );
+
+ } else {
+
+ InternalRsrcInfo newInfo ( id, dataLen, kIsMemoryBased );
+ newInfo.dataPtr = psirPtr;
+ newInfo.origOffset = dataOffset;
+ if ( nameLen != 0 ) newInfo.rsrcName = namePtr;
+
+ InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id );
+ if ( rsrcPos == this->imgRsrcs.end() ) {
+ this->imgRsrcs.insert ( rsrcPos, InternalRsrcMap::value_type ( id, newInfo ) );
+ } else if ( (rsrcPos->second.dataLen == 0) && (newInfo.dataLen != 0) ) {
+ rsrcPos->second = newInfo;
+ }
+
+ }
+
+ psirPtr = nextRsrc;
+
+ }
+
+} // PSIR_FileWriter::ParseMemoryResources
+
+// =================================================================================================
+// PSIR_FileWriter::ParseFileResources
+// ===================================
+
+void PSIR_FileWriter::ParseFileResources ( XMP_IO* fileRef, XMP_Uns32 length )
+{
+ bool ok;
+
+ this->DeleteExistingInfo();
+ this->fileParsed = true;
+ if ( length == 0 ) return;
+
+ // Parse the image resource block. We're using a map keyed by ID, so only one resource of each
+ // ID is recognized. Redundant resources are not legit, but have been seen in the field. In
+ // particular, one case has been seen of a duplicate IIM block with one empty. In general we
+ // keep the first seen copy to be compatible with Photoshop. A later non-empty copy will be
+ // taken though if the current one is empty.
+
+ // ! Don't use map[id] to lookup, that creates a default entry if none exists!
+
+ IOBuffer ioBuf;
+ ioBuf.filePos = fileRef->Offset();
+
+ 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.
+
+ XMP_Uns16 nameLen = ioBuf.ptr[0]; // ! The length for the Pascal string.
+ 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.
+ XMP_Uns32 dataLen = GetUns32BE(ioBuf.ptr);
+ XMP_Uns32 dataTotal = ((dataLen + 1) & 0xFFFFFFFEUL); // Round up to an even total.
+ ioBuf.ptr += 4; // Advance to the resource data.
+
+ XMP_Int64 thisDataPos = ioBuf.filePos + (ioBuf.ptr - ioBuf.data);
+ XMP_Int64 nextRsrcPos = thisDataPos + dataTotal;
+
+ if ( type != k8BIM ) {
+ XMP_Uns32 fullRsrcLen = (XMP_Uns32) (nextRsrcPos - thisRsrcPos);
+ this->otherRsrcs.push_back ( OtherRsrcInfo ( (XMP_Uns32)thisRsrcPos, fullRsrcLen ) );
+ MoveToOffset ( fileRef, nextRsrcPos, &ioBuf );
+ continue;
+ }
+
+ InternalRsrcInfo newInfo ( id, dataLen, kIsFileBased );
+ InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id );
+ if ( rsrcPos == this->imgRsrcs.end() ) {
+ rsrcPos = this->imgRsrcs.insert ( rsrcPos, InternalRsrcMap::value_type ( id, newInfo ) );
+ } else if ( (rsrcPos->second.dataLen == 0) && (newInfo.dataLen != 0) ) {
+ rsrcPos->second = newInfo;
+ } else {
+ MoveToOffset ( fileRef, nextRsrcPos, &ioBuf );
+ continue;
+ }
+ InternalRsrcInfo* rsrcPtr = &rsrcPos->second;
+
+ rsrcPtr->origOffset = (XMP_Uns32)thisDataPos;
+
+ if ( nameLen > 0 ) {
+ rsrcPtr->rsrcName = (XMP_Uns8*) malloc ( paddedLen );
+ 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;
+ }
+
+ rsrcPtr->dataPtr = malloc ( dataLen ); // ! Allocate after the IsMetadataImgRsrc check.
+ if ( rsrcPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+
+ if ( dataTotal <= kIOBufferSize ) {
+ // The image resource data fits within the I/O buffer.
+ ok = CheckFileSpace ( fileRef, &ioBuf, dataTotal );
+ if ( ! ok ) break; // Bad image resource. Throw instead?
+ memcpy ( (void*)rsrcPtr->dataPtr, ioBuf.ptr, dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above.
+ ioBuf.ptr += dataTotal; // ! Add the rounded length.
+ } else {
+ // The image resource data is bigger than the I/O buffer.
+ fileRef->Seek ( (ioBuf.filePos + (ioBuf.ptr - ioBuf.data)), kXMP_SeekFromStart );
+ fileRef->ReadAll ( (void*)rsrcPtr->dataPtr, dataLen );
+ FillBuffer ( fileRef, nextRsrcPos, &ioBuf );
+ }
+
+ }
+
+ #if 0
+ {
+ printf ( "\nPSIR_FileWriter::ParseFileResources, count = %d\n", this->imgRsrcs.size() );
+ InternalRsrcMap::iterator irPos = this->imgRsrcs.begin();
+ InternalRsrcMap::iterator irEnd = this->imgRsrcs.end();
+ for ( ; irPos != irEnd; ++irPos ) {
+ InternalRsrcInfo& thisRsrc = irPos->second;
+ printf ( " #%d, dataLen %d, origOffset %d (0x%X)%s\n",
+ thisRsrc.id, thisRsrc.dataLen, thisRsrc.origOffset, thisRsrc.origOffset,
+ (thisRsrc.changed ? ", changed" : "") );
+ }
+ }
+ #endif
+
+} // PSIR_FileWriter::ParseFileResources
+
+// =================================================================================================
+// PSIR_FileWriter::UpdateMemoryResources
+// ======================================
+
+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();
+ InternalRsrcMap::iterator irEnd = this->imgRsrcs.end();
+
+ for ( ; irPos != irEnd; ++irPos ) { // Add in the lengths for the 8BIM resources.
+ const InternalRsrcInfo & rsrcInfo = irPos->second;
+ newLength += 10;
+ newLength += ((rsrcInfo.dataLen + 1) & 0xFFFFFFFEUL);
+ if ( rsrcInfo.rsrcName == 0 ) {
+ newLength += 2;
+ } else {
+ XMP_Uns32 nameLen = rsrcInfo.rsrcName[0];
+ newLength += ((nameLen + 2) & 0xFFFFFFFEUL); // ! Yes, +2 for the length and rounding.
+ }
+ }
+
+ 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;
+ } else {
+ XMP_Uns32 nameLen = rsrcInfo.rsrcName[0];
+ XMP_Assert ( nameLen > 0 );
+ if ( (nameLen+1) > (newLength - (rsrcPtr - newContent)) ) {
+ XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure );
+ }
+ memcpy ( rsrcPtr, rsrcInfo.rsrcName, nameLen+1 ); // AUDIT: Protected by the above check.
+ rsrcPtr += nameLen+1;
+ if ( (nameLen & 1) == 0 ) {
+ *rsrcPtr = 0; // Round to an even total.
+ ++rsrcPtr;
+ }
+ }
+
+ PutUns32BE ( rsrcInfo.dataLen, rsrcPtr );
+ rsrcPtr += 4;
+ if ( rsrcInfo.dataLen > (newLength - (rsrcPtr - newContent)) ) {
+ XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure );
+ }
+ memcpy ( rsrcPtr, rsrcInfo.dataPtr, rsrcInfo.dataLen ); // AUDIT: Protected by the above check.
+ rsrcPtr += rsrcInfo.dataLen;
+ if ( (rsrcInfo.dataLen & 1) != 0 ) { // Pad to an even length if necessary.
+ *rsrcPtr = 0;
+ ++rsrcPtr;
+ }
+
+ }
+
+ for ( size_t i = 0; i < this->otherRsrcs.size(); ++i ) { // Do the non-8BIM resources.
+ XMP_Uns8* srcPtr = this->memContent + this->otherRsrcs[i].rsrcOffset;
+ XMP_Uns32 srcLen = this->otherRsrcs[i].rsrcLength;
+ if ( srcLen > (newLength - (rsrcPtr - newContent)) ) {
+ XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure );
+ }
+ 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
+
+// =================================================================================================
+// PSIR_FileWriter::UpdateFileResources
+// ====================================
+
+XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( XMP_IO* sourceRef, XMP_IO* destRef,
+ void * _ioBuf, XMP_AbortProc abortProc, void * abortArg )
+{
+ 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 = destRef->Offset();
+ XMP_Uns32 destLength = 0;
+
+ destRef->Write ( &destLength, 4 ); // Write a placeholder for the new PSIR section length.
+
+ #if 0
+ {
+ printf ( "\nPSIR_FileWriter::UpdateFileResources, count = %d\n", this->imgRsrcs.size() );
+ InternalRsrcMap::iterator irPos = this->imgRsrcs.begin();
+ InternalRsrcMap::iterator irEnd = this->imgRsrcs.end();
+ for ( ; irPos != irEnd; ++irPos ) {
+ InternalRsrcInfo& thisRsrc = irPos->second;
+ printf ( " #%d, dataLen %d, origOffset %d (0x%X)%s\n",
+ thisRsrc.id, thisRsrc.dataLen, thisRsrc.origOffset, thisRsrc.origOffset,
+ (thisRsrc.changed ? ", changed" : "") );
+ }
+ }
+ #endif
+
+ // First write all of the '8BIM' resources from the map. Use the internal data if present, else
+ // copy the data from the file.
+
+ RsrcHeader outHeader;
+ outHeader.type = MakeUns32BE ( k8BIM );
+
+ InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.begin();
+ InternalRsrcMap::iterator rsrcEnd = this->imgRsrcs.end();
+
+ // printf ( "\nPSIR_FileWriter::UpdateFileResources - 8BIM resources\n" );
+ for ( ; rsrcPos != rsrcEnd; ++rsrcPos ) {
+
+ InternalRsrcInfo& currRsrc = rsrcPos->second;
+
+ outHeader.id = MakeUns16BE ( currRsrc.id );
+ destRef->Write ( &outHeader, 6 );
+ destLength += 6;
+
+ if ( currRsrc.rsrcName == 0 ) {
+ destRef->Write ( &zero32, 2 );
+ destLength += 2;
+ } else {
+ XMP_Uns16 nameLen = currRsrc.rsrcName[0]; // ! Include room for +1.
+ XMP_Assert ( nameLen > 0 );
+ XMP_Uns16 paddedLen = (nameLen + 2) & 0xFFFE; // ! Round up to an even total. Yes, +2!
+ destRef->Write ( currRsrc.rsrcName, paddedLen );
+ destLength += paddedLen;
+ }
+
+ XMP_Uns32 dataLen = MakeUns32BE ( currRsrc.dataLen );
+ destRef->Write ( &dataLen, 4 );
+ // printf ( " #%d, offset %d (0x%X), dataLen %d\n", currRsrc.id, destLength, destLength, currRsrc.dataLen );
+
+ if ( currRsrc.dataPtr != 0 ) {
+ destRef->Write ( currRsrc.dataPtr, currRsrc.dataLen );
+ } else {
+ sourceRef->Seek ( currRsrc.origOffset, kXMP_SeekFromStart );
+ XIO::Copy ( sourceRef, destRef, currRsrc.dataLen );
+ }
+
+ destLength += 4 + currRsrc.dataLen;
+
+ if ( (currRsrc.dataLen & 1) != 0 ) {
+ destRef->Write ( &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",
+ // this->otherRsrcs[i].rsrcOffset, this->otherRsrcs[i].rsrcOffset, this->otherRsrcs[i].rsrcLength );
+ sourceRef->Seek ( this->otherRsrcs[i].rsrcOffset, kXMP_SeekFromStart );
+ XIO::Copy ( sourceRef, destRef, this->otherRsrcs[i].rsrcLength );
+ destLength += this->otherRsrcs[i].rsrcLength; // Alignment padding is already included.
+ }
+
+ // Write the final PSIR section length, seek back to the end of the file, return the length.
+
+ // printf ( "\nPSIR_FileWriter::UpdateFileResources - final length %d (0x%X)\n", destLength, destLength );
+ destRef->Seek ( destLenOffset, kXMP_SeekFromStart );
+ XMP_Uns32 outLen = MakeUns32BE ( destLength );
+ destRef->Write ( &outLen, 4 );
+ destRef->Seek ( 0, kXMP_SeekFromEnd );
+
+ // *** 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.
+
+ return destLength;
+
+} // PSIR_FileWriter::UpdateFileResources
diff --git a/XMPFiles/source/FormatSupport/PSIR_MemoryReader.cpp b/XMPFiles/source/FormatSupport/PSIR_MemoryReader.cpp
new file mode 100644
index 0000000..7432554
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/PSIR_MemoryReader.cpp
@@ -0,0 +1,112 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp"
+#include "source/EndianUtils.hpp"
+#include "source/XIO.hpp"
+
+#include <string.h>
+
+// =================================================================================================
+/// \file PSIR_MemoryReader.cpp
+/// \brief Implementation of the memory-based read-only form of PSIR_Manager.
+// =================================================================================================
+
+// =================================================================================================
+// PSIR_MemoryReader::GetImgRsrc
+// =============================
+
+bool PSIR_MemoryReader::GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const
+{
+ ImgRsrcMap::const_iterator rsrcPos = this->imgRsrcs.find ( id );
+ if ( rsrcPos == this->imgRsrcs.end() ) return false;
+
+ if ( info != 0 ) *info = rsrcPos->second;
+ return true;
+
+} // PSIR_MemoryReader::GetImgRsrc
+
+// =================================================================================================
+// PSIR_MemoryReader::ParseMemoryResources
+// =======================================
+
+void PSIR_MemoryReader::ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData /* = true */ )
+{
+ // Get rid of any existing image resources.
+
+ if ( this->ownedContent ) free ( this->psirContent );
+ this->ownedContent = false;
+ this->psirContent = 0;
+ this->psirLength = 0;
+ this->imgRsrcs.clear();
+
+ if ( length == 0 ) return;
+
+ // Allocate space for the full in-memory data and copy it.
+
+ if ( ! copyData ) {
+ this->psirContent = (XMP_Uns8*) data;
+ XMP_Assert ( ! this->ownedContent );
+ } else {
+ if ( length > 100*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based PSIR", kXMPErr_BadPSIR );
+ this->psirContent = (XMP_Uns8*) malloc(length);
+ if ( this->psirContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+ memcpy ( this->psirContent, data, length ); // AUDIT: Safe, malloc'ed length bytes above.
+ this->ownedContent = true;
+ }
+
+ this->psirLength = length;
+
+ // Capture the info for all of the resources. We're using a map keyed by ID, so only one
+ // resource of each ID is recognized. Redundant resources are not legit, but have been seen in
+ // the field. In particular, one case has been seen of a duplicate IIM block with one empty.
+ // In general we keep the first seen copy to be compatible with Photoshop. A later non-empty
+ // copy will be taken though if the current one is empty.
+
+ // ! Don't use map[id] to lookup, that creates a default entry if none exists!
+
+ XMP_Uns8* psirPtr = this->psirContent;
+ XMP_Uns8* psirEnd = psirPtr + length;
+ XMP_Uns8* psirLimit = psirEnd - kMinImgRsrcSize;
+
+ while ( psirPtr <= psirLimit ) {
+
+ XMP_Uns32 type = GetUns32BE(psirPtr);
+ XMP_Uns16 id = GetUns16BE(psirPtr+4);
+ psirPtr += 6; // Advance to the resource name.
+
+ XMP_Uns16 nameLen = psirPtr[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);
+ psirPtr += 4; // Advance to the resource data.
+ XMP_Uns32 psirOffset = (XMP_Uns32) (psirPtr - this->psirContent);
+
+ if ( (dataLen > length) || (psirPtr > psirEnd-dataLen) ) break; // Bad image resource. Throw instead?
+
+ if ( type == k8BIM ) { // For read-only usage we ignore everything other than '8BIM' resources.
+ ImgRsrcInfo newInfo ( id, dataLen, psirPtr, psirOffset );
+ ImgRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id );
+ if ( rsrcPos == this->imgRsrcs.end() ) {
+ this->imgRsrcs.insert ( rsrcPos, ImgRsrcMap::value_type ( id, newInfo ) );
+ } else if ( (rsrcPos->second.dataLen == 0) && (newInfo.dataLen != 0) ) {
+ rsrcPos->second = newInfo;
+ }
+ }
+
+ psirPtr += ((dataLen + 1) & 0xFFFFFFFEUL); // ! Round up to an even offset.
+
+ }
+
+} // PSIR_MemoryReader::ParseMemoryResources
diff --git a/XMPFiles/source/FormatSupport/PSIR_Support.hpp b/XMPFiles/source/FormatSupport/PSIR_Support.hpp
new file mode 100644
index 0000000..b0ec13a
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/PSIR_Support.hpp
@@ -0,0 +1,328 @@
+#ifndef __PSIR_Support_hpp__
+#define __PSIR_Support_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+
+#include "source/EndianUtils.hpp"
+
+#include <map>
+
+// =================================================================================================
+/// \file PSIR_Support.hpp
+/// \brief XMPFiles support for Photoshop image resources.
+///
+/// This header provides Photoshop image resource (PSIR) support specific to the needs of XMPFiles.
+/// This is not intended for general purpose PSIR processing. PSIR_Manager is an abstract base
+/// class with 2 concrete derived classes, PSIR_MemoryReader and PSIR_FileWriter.
+///
+/// PSIR_MemoryReader provides read-only support for PSIR streams that are small enough to be kept
+/// entirely in memory. This allows optimizations to reduce heap usage and processing code. It is
+/// sufficient for browsing access to the image resources (mainly the IPTC) in JPEG files. Think of
+/// PSIR_MemoryReader as "memory-based AND read-only".
+///
+/// PSIR_FileWriter is for cases where updates are needed or the PSIR stream is too large to be kept
+/// entirely in memory. Think of PSIR_FileWriter as "file-based OR read-write".
+///
+/// The needs of XMPFiles are well defined metadata access. Only a few image resources are handled.
+/// 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 1028 - IPTC
+/// \li 1034 - Copyrighted flag
+/// \li 1035 - Copyright information URL
+/// \li 1058 - Exif metadata
+/// \li 1060 - XMP
+/// \li 1061 - IPTC digest
+///
+/// \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.
+// =================================================================================================
+
+
+// These aren't inside PSIR_Manager because the static array can't be initialized there.
+
+enum {
+ k8BIM = 0x3842494DUL, // The 4 ASCII characters '8BIM'.
+ kMinImgRsrcSize = 4+2+2+4 // The minimum size for an image resource.
+};
+
+enum {
+ kPSIR_IPTC = 1028,
+ kPSIR_CopyrightFlag = 1034,
+ kPSIR_CopyrightURL = 1035,
+ 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
+// those of type "8BIM". Resources of other types are preserved in files, but can't be individually
+// accessed through the PSIR_Manager API.
+
+// =================================================================================================
+// PSIR_Manager
+// ============
+
+class PSIR_Manager { // The abstract base class.
+public:
+
+ // ---------------------------------------------------------------------------------------------
+ // Types and constants
+
+ struct ImgRsrcInfo {
+ XMP_Uns16 id;
+ XMP_Uns32 dataLen;
+ const void* dataPtr; // ! The data is read-only!
+ XMP_Uns32 origOffset; // The offset (at parse time) of the resource data.
+ ImgRsrcInfo() : id(0), dataLen(0), dataPtr(0), origOffset(0) {};
+ 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.
+
+ virtual bool GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const = 0;
+
+ // ---------------------------------------------------------------------------------------------
+ // 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;
+ virtual void ParseFileResources ( XMP_IO* fileRef, XMP_Uns32 length ) = 0;
+
+ // ---------------------------------------------------------------------------------------------
+ // Update the image resources to reflect the changed values. Both \c UpdateMemoryResources and
+ // \c UpdateFileResources return the new size of the image resource block. The dataPtr returned
+ // 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 ( XMP_IO* sourceRef, XMP_IO* destRef,
+ void * _ioBuf, XMP_AbortProc abortProc, void * abortArg ) = 0;
+ // *** Temporary hack above, _ioBuf is IOBuffer* but don't want to include XIO.hpp.
+
+ // ---------------------------------------------------------------------------------------------
+
+ virtual ~PSIR_Manager() {};
+
+protected:
+
+ PSIR_Manager() {};
+
+}; // PSIR_Manager
+
+
+// =================================================================================================
+// =================================================================================================
+
+
+// =================================================================================================
+// PSIR_MemoryReader
+// =================
+
+class PSIR_MemoryReader : public PSIR_Manager { // The leaf class for memory-based read-only access.
+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; };
+ bool IsLegacyChanged() { return false; };
+
+ void ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData = true );
+ void ParseFileResources ( XMP_IO* file, XMP_Uns32 length ) { NotAppropriate(); };
+
+ XMP_Uns32 UpdateMemoryResources ( void** dataPtr ) { if ( dataPtr != 0 ) *dataPtr = psirContent; return psirLength; };
+ XMP_Uns32 UpdateFileResources ( XMP_IO* sourceRef, XMP_IO* destRef,
+ void * _ioBuf, XMP_AbortProc abortProc, void * abortArg ) { NotAppropriate(); return 0; };
+ // *** Temporary hack above, _ioBuf is IOBuffer* but don't want to include XIO.hpp.
+
+ 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
+ // is no need to ever allocate separate blocks of memory, everything is used directly from the
+ // 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 ); };
+
+}; // PSIR_MemoryReader
+
+
+// =================================================================================================
+// =================================================================================================
+
+
+// =================================================================================================
+// PSIR_FileWriter
+// ===============
+
+class PSIR_FileWriter : public PSIR_Manager { // The leaf class for file-based read-write access.
+public:
+
+ bool GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const;
+
+ void SetImgRsrc ( XMP_Uns16 id, const void* dataPtr, XMP_Uns32 length );
+
+ void DeleteImgRsrc ( XMP_Uns16 id );
+
+ bool IsChanged() { return this->changed; };
+
+ bool IsLegacyChanged();
+
+ void ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData = true );
+ void ParseFileResources ( XMP_IO* file, XMP_Uns32 length );
+
+ XMP_Uns32 UpdateMemoryResources ( void** dataPtr );
+ XMP_Uns32 UpdateFileResources ( XMP_IO* sourceRef, XMP_IO* destRef,
+ void * _ioBuf, XMP_AbortProc abortProc, void * abortArg );
+ // *** Temporary hack above, _ioBuf is IOBuffer* but don't want to include XIO.hpp.
+
+ PSIR_FileWriter() : changed(false), legacyDeleted(false), memParsed(false), fileParsed(false),
+ ownedContent(false), memLength(0), memContent(0) {};
+
+ virtual ~PSIR_FileWriter();
+
+ // 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. The dataPtr becomes a
+ // separate allocation when SetImgRsrc is called, the rsrcName stays into the original stream.
+ // For file-based streams the dataPtr and rsrcName are always a separate allocation. Again,
+ // the dataPtr changes when SetImgRsrc is called, the rsrcName stays unchanged.
+
+ // ! 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;
+ XMP_Uns32 dataLen;
+ void* dataPtr; // ! Null if the value is not captured!
+ XMP_Uns32 origOffset; // The offset (at parse time) of the resource data.
+ XMP_Uns8* rsrcName; // ! A Pascal string, leading length byte, no nul terminator!
+
+ inline void FreeData() {
+ if ( this->fileBased || this->changed ) {
+ if ( this->dataPtr != 0 ) { free ( this->dataPtr ); this->dataPtr = 0; }
+ }
+ }
+
+ inline void FreeName() {
+ if ( this->fileBased && (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) {};
+ ~InternalRsrcInfo() { this->FreeData(); this->FreeName(); };
+
+ void operator= ( const InternalRsrcInfo & in )
+ {
+ // ! Gag! Transfer ownership of the dataPtr and rsrcName!
+ this->FreeData();
+ memcpy ( this, &in, sizeof(*this) ); // AUDIT: Use of sizeof(InternalRsrcInfo) is safe.
+ *((void**)&in.dataPtr) = 0; // The pointer is now owned by "this".
+ *((void**)&in.rsrcName) = 0; // The pointer is now owned by "this".
+ };
+
+ private:
+
+ 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.
+
+private:
+
+ bool changed, legacyDeleted;
+ bool memParsed, fileParsed;
+ bool ownedContent;
+
+ XMP_Uns32 memLength;
+ XMP_Uns8* memContent;
+
+ 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.
+ OtherRsrcInfo() : rsrcOffset(0), rsrcLength(0) {};
+ OtherRsrcInfo ( XMP_Uns32 _rsrcOffset, XMP_Uns32 _rsrcLength )
+ : rsrcOffset(_rsrcOffset), rsrcLength(_rsrcLength) {};
+ };
+ std::vector<OtherRsrcInfo> otherRsrcs;
+
+ void DeleteExistingInfo();
+
+}; // PSIR_FileWriter
+
+#endif // __PSIR_Support_hpp__
diff --git a/XMPFiles/source/FormatSupport/QuickTime_Support.cpp b/XMPFiles/source/FormatSupport/QuickTime_Support.cpp
new file mode 100644
index 0000000..5a33ab4
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/QuickTime_Support.cpp
@@ -0,0 +1,1150 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#if XMP_MacBuild
+ #include <CoreServices/CoreServices.h>
+#else
+ #include "XMPFiles/source/FormatSupport/MacScriptExtracts.h"
+#endif
+
+#include "XMPFiles/source/FormatSupport/QuickTime_Support.hpp"
+
+#include "source/UnicodeConversions.hpp"
+#include "source/UnicodeInlines.incl_cpp"
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+#include "source/XIO.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
+
+// =================================================================================================
+// 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
+// ==============
+
+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
+
+// =================================================================================================
+// =================================================================================================
+// 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.
+
+ 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".
+ }
+
+ 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());
+
+ 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;
+ }
+
+ ValueInfo * qtItem = &infoPos->second.values[0]; // ! Use the first QT entry.
+ if ( ! IsMacLangKnown ( qtItem->macLang ) ) return;
+
+ bool convertOK = ConvertToMacLang ( xmpValue, qtItem->macLang, &macValue );
+ if ( convertOK && (macValue != qtItem->macValue) ) {
+ qtItem->macValue = macValue;
+ this->changed = infoPos->second.changed = true;
+ }
+
+} // TradQT_Manager::ExportSimpleXMP
+
+// =================================================================================================
+// 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 ( 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.
+ }
+
+ ValueInfo * qtItem = &((*qtValues)[qtIndex]);
+ qtItem->marked = true; // Mark it whether updated or not, don't delete it in the next pass.
+
+ 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;
+
+ // 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.
+
+ if ( (! haveMappings) && (! qtValues->empty()) ) {
+
+ bool ok = xmp.GetLocalizedText ( ns, langArray, "", "x-default", 0, &xmpValue, 0 );
+ if ( ! ok ) return;
+
+ ValueInfo * qtItem = &((*qtValues)[0]);
+ if ( ! IsMacLangKnown ( qtItem->macLang ) ) return;
+
+ 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) );
+
+ }
+
+ }
+
+ // 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 ) {
+
+ 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 ) {
+
+ moovMgr->DeleteTypeChild ( udtaRef, qtItem->id );
+
+ } 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.
+
+ 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 );
+ }
+
+ }
+
+ }
+
+} // TradQT_Manager::UpdateChangedBoxes
+
+// =================================================================================================
diff --git a/XMPFiles/source/FormatSupport/QuickTime_Support.hpp b/XMPFiles/source/FormatSupport/QuickTime_Support.hpp
new file mode 100644
index 0000000..691e746
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/QuickTime_Support.hpp
@@ -0,0 +1,105 @@
+#ifndef __QuickTime_Support_hpp__
+#define __QuickTime_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 "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp"
+#include "XMPFiles/source/FormatSupport/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; };
+
+ 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;
+
+ InfoMap parsedBoxes;
+ bool changed;
+
+ bool ImportLangItem ( const ValueInfo & qtItem, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) const;
+
+}; // TradQT_Manager
+
+#endif // __QuickTime_Support_hpp__
diff --git a/XMPFiles/source/FormatSupport/RIFF.cpp b/XMPFiles/source/FormatSupport/RIFF.cpp
new file mode 100644
index 0000000..dcf55f2
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/RIFF.cpp
@@ -0,0 +1,882 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+// must have access to handler class fields...
+#include "XMPFiles/source/FormatSupport/RIFF.hpp"
+#include "XMPFiles/source/FormatSupport/RIFF_Support.hpp"
+#include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp"
+
+#include <utility>
+
+using namespace RIFF;
+
+namespace RIFF {
+
+// GENERAL STATIC FUNCTIONS ////////////////////////////////////////
+
+Chunk* getChunk ( ContainerChunk* parent, RIFF_MetaHandler* handler )
+{
+ XMP_IO* file = handler->parent->ioRef;
+ XMP_Uns8 level = handler->level;
+ XMP_Uns32 peek = XIO::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
+ file->Seek ( 8, kXMP_SeekFromCurrent );
+ XMP_Uns32 containerType = XIO::PeekUns32_LE ( file );
+ file->Seek ( -8, kXMP_SeekFromCurrent );
+
+ 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
+ file ->Seek ( 4, kXMP_SeekFromCurrent ); // jump DISP
+ XMP_Uns32 dispSize = XIO::ReadUns32_LE( file );
+ XMP_Uns32 dispType = XIO::ReadUns32_LE( file );
+ file ->Seek ( -12, kXMP_SeekFromCurrent ); // 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( std::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
+
+ XMP_IO* file = handler->parent->ioRef;
+
+ this->oldPos = file->Offset();
+ this->id = XIO::ReadUns32_LE( file );
+ this->oldSize = XIO::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 ) file->Seek ( (this->oldSize - 8), kXMP_SeekFromCurrent );
+
+ // "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( std::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, XMP_IO* 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
+ {
+ XMP_IO* file = handler->parent->ioRef;
+ XMP_Uns8 level = handler->level;
+
+ // get type of container chunk
+ this->containerType = XIO::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 ( file->Offset() < endOfChunk )
+ {
+ curChild = RIFF::getChunk( this, handler );
+
+ // digest pad byte - no value validation (0), since some 3rd party files have non-0-padding.
+ if ( file->Offset() % 2 == 1 )
+ {
+ // [1521093] tolerate missing pad byte at very end of file:
+ XMP_Uns8 pad;
+ file->Read ( &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( file->Offset() == 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
+ {
+ file->Seek ( (this->oldSize - 8 - 4), kXMP_SeekFromCurrent );
+ } // 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, XMP_IO* file, bool isMainChunk )
+{
+ if ( isMainChunk )
+ file ->Rewind();
+
+ // enforce even position
+ XMP_Int64 chunkStart = file->Offset();
+ 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--;
+ file->Seek ( childStart, kXMP_SeekFromStart );
+ XIO::WriteUns8( file, 0 );
+ }
+
+ // then contents
+ childStart-= cur->newSize;
+ file->Seek ( childStart, kXMP_SeekFromStart );
+ switch ( cur->chunkType )
+ {
+ case chunk_GENERAL: //COULDDO enfore no change, since not write-out-able
+ if ( cur->oldPos != childStart )
+ XIO::Move( file, cur->oldPos, file, childStart, cur->oldSize );
+ break;
+ default:
+ cur->write( handler, file, false );
+ break;
+ } // switch
+
+ } // for
+ XMP_Enforce ( chunkStart + 12 == childStart);
+ file->Seek ( chunkStart, kXMP_SeekFromStart );
+
+ XIO::WriteUns32_LE( file, this->id );
+ XIO::WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above
+ XIO::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;
+ XMP_IO* file = handler->parent->ioRef;
+ 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, ' ' );
+ file->ReadAll ( (void*)handler->xmpPacket.data(), handler->packetInfo.length );
+
+ 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, XMP_IO* file, bool isMainChunk )
+{
+ XIO::WriteUns32_LE( file, kChunk_XMP );
+ XIO::WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above
+ file->Write ( 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: -----------------
+ XMP_IO* file = handler->parent->ioRef;
+ 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' );
+ file->ReadAll ( (void*)this->oldValue.data(), length );
+
+ 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, XMP_IO* file, bool isMainChunk )
+{
+ XIO::WriteUns32_LE( file, this->id );
+ XIO::WriteUns32_LE( file, (XMP_Uns32)this->newSize - 8 );
+ file->Write ( 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, XMP_IO* file, bool isMainChunk )
+{
+ XIO::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;
+ XIO::WriteUns32_LE( file, innerSize );
+
+ // write out in 64K chunks
+ while ( innerSize > kZeroBufferSize64K )
+ {
+ file->Write ( kZeroes64K , kZeroBufferSize64K );
+ innerSize -= kZeroBufferSize64K;
+ }
+ file->Write ( kZeroes64K , innerSize );
+}
+
+} // namespace RIFF
diff --git a/XMPFiles/source/FormatSupport/RIFF.hpp b/XMPFiles/source/FormatSupport/RIFF.hpp
new file mode 100644
index 0000000..533d9a3
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/RIFF.hpp
@@ -0,0 +1,322 @@
+#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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+
+#include <vector>
+#include <map>
+
+// 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, XMP_IO* 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, XMP_IO* 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, XMP_IO* 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, XMP_IO* 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, XMP_IO* 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/XMPFiles/source/FormatSupport/RIFF_Support.cpp b/XMPFiles/source/FormatSupport/RIFF_Support.cpp
new file mode 100644
index 0000000..1e2fad1
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/RIFF_Support.cpp
@@ -0,0 +1,939 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+// must have access to handler class fields...
+#include "XMPFiles/source/FormatSupport/RIFF.hpp"
+#include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp"
+#include "XMPFiles/source/FormatSupport/RIFF_Support.hpp"
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+using namespace RIFF;
+namespace RIFF {
+
+// 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;
+
+// CR8R, PrmL have fixed sizes
+XMP_Int32 CR8R_SIZE = 0x5C;
+XMP_Int32 PRML_SIZE = 0x122;
+
+static const char* sHexChars = "0123456789ABCDEF";
+
+// 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
+
+ if ( (rawStr == 0) && (rawLen != 0) )
+ XMP_Throw ( "EncodeToHexString: null rawStr", kXMPErr_BadParam );
+ if ( encodedStr == 0 )
+ XMP_Throw ( "EncodeToHexString: null encodedStr", kXMPErr_BadParam );
+
+ encodedStr->erase();
+ if ( rawLen == 0 ) return allZero;
+ encodedStr->reserve ( rawLen * 2 );
+
+ for( XMP_Uns32 i = 0; i < rawLen; i++ )
+ {
+ // first, second nibble
+ XMP_Uns8 first = rawStr[i] >> 4;
+ XMP_Uns8 second = rawStr[i] & 0xF;
+
+ if ( allZero && (( first != 0 ) || (second != 0)))
+ allZero = false;
+
+ encodedStr->append( 1, sHexChars[first] );
+ encodedStr->append( 1, sHexChars[second] );
+ }
+
+ return allZero;
+} // EncodeToHexString
+
+// -------------------------------------------------------------------------------------------------
+// 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++;
+
+ 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 );
+
+ 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, '?' );
+ }
+ 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;
+}
+
+/**
+ * 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++ )
+ {
+ if ( cstring[size] == 0 )
+ break;
+ }
+
+ (*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;
+}
+
+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
+
+static void importPrmLToXMP( RIFF_MetaHandler* handler, ValueChunk* prmlChunk )
+{
+ bool haveXMP = false;
+
+ 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.
+ }
+
+ 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 );
+ }
+ }
+
+ 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 );
+ }
+
+ handler->containsXMP |= haveXMP; // mind the '|='
+} // importCr8rToXMP
+
+static void importCr8rToXMP( RIFF_MetaHandler* handler, ValueChunk* cr8rChunk )
+{
+ bool haveXMP = false;
+
+ 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) );
+
+ if ( rawCr8r.magic != 0xBEEFCAFE ) {
+ Flip4 ( &rawCr8r.creatorCode ); // The only numeric fields that we care about.
+ Flip4 ( &rawCr8r.appleEvent );
+ }
+
+ std::string fieldPath;
+
+ 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.
+ }
+
+ 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.
+ }
+
+ 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 );
+ }
+
+ 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 );
+ }
+
+ handler->containsXMP |= haveXMP; // mind the '|='
+} // importCr8rToXMP
+
+
+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 );
+ }
+ }
+ }
+ } // 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" );
+ }
+
+ // BWF Bext extension chunk -----------------------------------------------
+ if ( handler->parent->format == kXMP_WAVFile && // applies only to WAV
+ handler->bextChunk != 0 ) //skip if no BEXT chunk found.
+ {
+ importBextChunkToXMP( handler, handler->bextChunk );
+ }
+
+ // PrmL chunk --------------------------------------------------------------
+ if ( handler->prmlChunk != 0 && handler->prmlChunk->oldSize == PRML_SIZE )
+ {
+ importPrmLToXMP( handler, handler->prmlChunk );
+ }
+
+ // Cr8r chunk --------------------------------------------------------------
+ if ( handler->cr8rChunk != 0 && handler->cr8rChunk->oldSize == CR8R_SIZE )
+ {
+ importCr8rToXMP( handler, handler->cr8rChunk );
+ }
+
+ // LIST:INFO --------------------------------------------------------------
+ if ( handler->listInfoChunk != 0) //skip if no LIST:INFO chunk found.
+ importListChunkToXMP( handler, handler->listInfoChunk, listInfoProps, hasDigest );
+
+ // LIST:Tdat --------------------------------------------------------------
+ if ( handler->listTdatChunk != 0)
+ importListChunkToXMP( handler, handler->listTdatChunk, listTdatProps, hasDigest );
+
+ // 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;
+ }
+
+ 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
+ {
+ // 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" );
+ }
+ }
+ } // if size sufficient
+ } // handler->dispChunk
+
+} // importProperties
+
+////////////////////////////////////////////////////////////////////////////////
+// EXPORT
+////////////////////////////////////////////////////////////////////////////////
+
+void relocateWronglyPlacedXMPChunk( RIFF_MetaHandler* handler )
+{
+ XMP_IO* file = handler->parent->ioRef;
+ 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 );
+
+ 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;
+
+ // 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 ))
+ {
+ 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;
+
+ // grab props, write into buffer, remove from XMP ///////////////////////////
+ // bextDescription ------------------------------------------------
+ if ( xmp->GetProperty( bextDescription.ns, bextDescription.prop, &value, kXMP_NoOptions ) )
+ {
+ setBextField( &value, (XMP_Uns8*) buffer, 0, 256 );
+ xmp->DeleteProperty( bextDescription.ns, bextDescription.prop) ;
+ chunkUsed = true;
+ }
+ // bextOriginator -------------------------------------------------
+ if ( xmp->GetProperty( bextOriginator.ns , bextOriginator.prop, &value, kXMP_NoOptions ) )
+ {
+ 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 );
+
+ // bextUMID -------------------------------------------------------
+ if ( xmp->GetProperty( bextUMID.ns, bextUMID.prop, &value, kXMP_NoOptions ) )
+ {
+ std::string rawStr;
+
+ 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 UMID is smaller/longer than 64 byte for any reason,
+ // truncate/do a partial write (just like for any other bext property)
+
+ memcpy( (char*) &(buffer[256+32+32+10+8+8+2]), rawStr.data(), MIN( 64, rawStr.size() ) );
+ xmp->DeleteProperty( bextUMID.ns, bextUMID.prop );
+ chunkUsed = true;
+ }
+
+ // bextCodingHistory ----------------------------------------------
+ if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, kXMP_NoOptions ) )
+ {
+ 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;
+ }
+
+ // always delete old, recreate if needed
+ if ( *bextChunk != 0 )
+ {
+ (*bextChunk)->parent->replaceChildWithJunk( *bextChunk );
+ (*bextChunk) = 0; // clear direct Chunk pointer
+ }
+
+ if ( chunkUsed)
+ *bextChunk = new ValueChunk( handler->riffChunks.at(0), std::string( (char*)buffer, bextBufferSize ), kChunk_bext );
+
+ delete [] buffer; // important.
+}
+
+static inline void SetBufferedString ( char * dest, const std::string source, size_t limit )
+{
+ 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 exportXMPtoCr8rChunk ( RIFF_MetaHandler* handler, ValueChunk** cr8rChunk )
+{
+ 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 );
+
+ 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;
+
+ Cr8rBoxContent * newCr8r = (Cr8rBoxContent*) (*cr8rChunk)->newValue.data();
+
+ if ( ! haveOldCr8r ) {
+
+ newCr8r->magic = MakeUns32LE ( 0xBEEFCAFE );
+ newCr8r->size = MakeUns32LE ( sizeof(Cr8rBoxContent) );
+ newCr8r->majorVer = MakeUns16LE ( 1 );
+
+ } 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 );
+ }
+
+ }
+
+ 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 ) );
+
+}
+
+static void exportXMPtoListChunk( XMP_Uns32 id, XMP_Uns32 containerType,
+ RIFF_MetaHandler* handler, ContainerChunk** listChunk, Mapping mapping[])
+{
+ // 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);
+
+ for ( int p=0; mapping[p].chunkID != 0; ++p ) { // go through all potential property mappings
+
+ bool propExists = false;
+ std::string value, actualLang;
+
+ switch ( mapping[p].propType ) {
+
+ // 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;
+
+ 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;
+
+ case prop_ARRAYITEM:
+ propExists = xmp->GetArrayItem ( mapping[p].ns, mapping[p].prop, 1, &value, 0 );
+ break;
+
+ case prop_SIMPLE:
+ propExists = xmp->GetProperty ( mapping[p].ns, mapping[p].prop, &value, 0 );
+ break;
+
+ default:
+ XMP_Throw ( "internal error", kXMPErr_InternalFailure );
+
+ }
+
+ if ( ! propExists ) {
+
+ if ( *listChunk != 0 ) (*listChunk)->removeValue ( mapping[p].chunkID );
+
+ } else {
+
+ listChunkIsNeeded = true;
+ if ( *listChunk == 0 ) *listChunk = new ContainerChunk ( handler->riffChunks[0], id, containerType );
+
+ valueMap* cm = &(*listChunk)->childmap;
+ valueMapIter result = cm->find( mapping[p].chunkID );
+ ValueChunk* propChunk = 0;
+
+ if ( result != cm->end() ) {
+ propChunk = result->second;
+ } else {
+ propChunk = new ValueChunk ( *listChunk, std::string(), mapping[p].chunkID );
+ }
+
+ propChunk->SetValue ( value.c_str(), optionalNUL );
+
+ }
+
+ } // for each property
+
+ if ( (! listChunkIsNeeded) && (*listChunk != 0) && ((*listChunk)->children.size() == 0) ) {
+ (*listChunk)->parent->replaceChildWithJunk ( *listChunk );
+ (*listChunk) = 0; // reset direct Chunk pointer
+ }
+
+}
+
+void exportAndRemoveProperties ( RIFF_MetaHandler* handler )
+{
+ SXMPMeta xmpObj = handler->xmpObj;
+
+ exportXMPtoCr8rChunk ( handler, &handler->cr8rChunk );
+
+ // 1/4 BWF Bext extension chunk -----------------------------------------------
+ if ( handler->parent->format == kXMP_WAVFile ) { // applies only to WAV
+ exportXMPtoBextChunk ( handler, &handler->bextChunk );
+ }
+
+ // 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 );
+
+ if ( r && ( actualLang == "x-default" ) ) { // prop exists?
+
+ // 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 );
+
+ if ( handler->dispChunk == 0 ) {
+ handler->dispChunk = new RIFF::ValueChunk ( handler->riffChunks.at(0), std::string(), kChunk_DISP );
+ }
+
+ // ! 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 );
+
+ } else { // remove Disp Chunk..
+
+ 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;
+ }
+ }
+
+ }
+
+ }
+
+ // 3/4 LIST:INFO
+ exportXMPtoListChunk ( kChunk_LIST, kType_INFO, handler, &handler->listInfoChunk, listInfoProps );
+
+ // 4/4 LIST:Tdat
+ exportXMPtoListChunk ( kChunk_LIST, kType_Tdat, handler, &handler->listTdatChunk, listTdatProps );
+
+}
+
+} // namespace RIFF
diff --git a/XMPFiles/source/FormatSupport/RIFF_Support.hpp b/XMPFiles/source/FormatSupport/RIFF_Support.hpp
new file mode 100644
index 0000000..c88599b
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/RIFF_Support.hpp
@@ -0,0 +1,42 @@
+#ifndef __RIFF_Support_hpp__
+#define __RIFF_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 "public/include/XMP_Environment.h" // ! This must be the first include.
+#include <vector>
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+// ahead declaration:
+class RIFF_MetaHandler;
+
+namespace RIFF {
+
+ // declare ahead
+ class Chunk;
+ class ContainerChunk;
+ class ValueChunk;
+ class XMPChunk;
+
+ /* This rountines imports the properties found into the
+ xmp packet. Use after parsing. */
+ void importProperties( RIFF_MetaHandler* handler );
+
+ /* 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 );
+
+} // namespace RIFF
+
+#endif // __RIFF_Support_hpp__
diff --git a/XMPFiles/source/FormatSupport/ReconcileIPTC.cpp b/XMPFiles/source/FormatSupport/ReconcileIPTC.cpp
new file mode 100644
index 0000000..df9d676
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/ReconcileIPTC.cpp
@@ -0,0 +1,857 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+#include "source/XIO.hpp"
+
+#include <stdio.h>
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning)
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+// =================================================================================================
+/// \file ReconcileIPTC.cpp
+/// \brief Utilities to reconcile between XMP and legacy IPTC and PSIR metadata.
+///
+// =================================================================================================
+
+// =================================================================================================
+// NormalizeToCR
+// =============
+
+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
+
+// =================================================================================================
+// NormalizeToLF
+// =============
+
+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
+
+// =================================================================================================
+// ComputeIPTCDigest
+// =================
+//
+// Compute a 128 bit (16 byte) MD5 digest of the full IPTC block.
+
+static inline void ComputeIPTCDigest ( const void * iptcPtr, const XMP_Uns32 iptcLen, MD5_Digest * digest )
+{
+ MD5_CTX context;
+
+ MD5Init ( &context );
+ MD5Update ( &context, (XMP_Uns8*)iptcPtr, iptcLen );
+ MD5Final ( *digest, &context );
+
+} // ComputeIPTCDigest;
+
+// =================================================================================================
+// PhotoDataUtils::CheckIPTCDigest
+// ===============================
+
+int PhotoDataUtils::CheckIPTCDigest ( const void * newPtr, const XMP_Uns32 newLen, const void * oldDigest )
+{
+ MD5_Digest newDigest;
+ ComputeIPTCDigest ( newPtr, newLen, &newDigest );
+ if ( memcmp ( &newDigest, oldDigest, 16 ) == 0 ) return kDigestMatches;
+ return kDigestDiffers;
+
+} // PhotoDataUtils::CheckIPTCDigest
+
+// =================================================================================================
+// PhotoDataUtils::SetIPTCDigest
+// =============================
+
+void PhotoDataUtils::SetIPTCDigest ( void * iptcPtr, XMP_Uns32 iptcLen, PSIR_Manager * psir )
+{
+ MD5_Digest newDigest;
+
+ ComputeIPTCDigest ( iptcPtr, iptcLen, &newDigest );
+ psir->SetImgRsrc ( kPSIR_IPTCDigest, &newDigest, sizeof(newDigest) );
+
+} // PhotoDataUtils::SetIPTCDigest
+
+// =================================================================================================
+// =================================================================================================
+
+// =================================================================================================
+// PhotoDataUtils::ImportIPTC_Simple
+// =================================
+
+void PhotoDataUtils::ImportIPTC_Simple ( const IPTC_Manager & iptc, SXMPMeta * xmp,
+ XMP_Uns8 id, const char * xmpNS, const char * xmpProp )
+{
+ std::string utf8Str;
+ size_t count = iptc.GetDataSet_UTF8 ( id, &utf8Str );
+
+ if ( count != 0 ) {
+ NormalizeToLF ( &utf8Str );
+ xmp->SetProperty ( xmpNS, xmpProp, utf8Str.c_str() );
+ }
+
+} // PhotoDataUtils::ImportIPTC_Simple
+
+// =================================================================================================
+// PhotoDataUtils::ImportIPTC_LangAlt
+// ==================================
+
+void PhotoDataUtils::ImportIPTC_LangAlt ( const IPTC_Manager & iptc, SXMPMeta * xmp,
+ XMP_Uns8 id, const char * xmpNS, const char * xmpProp )
+{
+ 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() );
+ }
+
+} // PhotoDataUtils::ImportIPTC_LangAlt
+
+// =================================================================================================
+// PhotoDataUtils::ImportIPTC_Array
+// ================================
+
+void PhotoDataUtils::ImportIPTC_Array ( const IPTC_Manager & iptc, SXMPMeta * xmp,
+ XMP_Uns8 id, const char * xmpNS, const char * xmpProp )
+{
+ 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, arrayForm, utf8Str.c_str() );
+ }
+
+} // 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
+// ============================
+//
+// Import DataSet 2:04. In the IIM this is a 3 digit number, a colon, and an optional text name.
+// Even though the number is the more formal part, the IPTC4XMP rule is that the name is imported to
+// 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 )
+{
+ 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;
+ XMP_StringPtr numPtr = utf8Str.c_str();
+ for ( i = 0; kIntellectualGenreMappings[i].refNum != 0; ++i ) {
+ if ( strncmp ( numPtr, kIntellectualGenreMappings[i].refNum, 3 ) == 0 ) break;
+ }
+ if ( kIntellectualGenreMappings[i].refNum == 0 ) return;
+ namePtr = kIntellectualGenreMappings[i].name;
+ }
+
+ xmp->SetProperty ( kXMP_NS_IPTCCore, "IntellectualGenre", namePtr );
+
+} // ImportIPTC_IntellectualGenre
+
+// =================================================================================================
+// ImportIPTC_SubjectCode
+// ======================
+//
+// Import all 2:12 DataSets into an unordered array. In the IIM each DataSet is composed of 5 colon
+// separated sections: a provider name, an 8 digit reference number, and 3 optional names for the
+// 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 )
+{
+ 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 );
+
+ char * refNumPtr = (char*) utf8Str.c_str();
+ for ( ; (*refNumPtr != ':') && (*refNumPtr != 0); ++refNumPtr ) {}
+ if ( *refNumPtr == 0 ) continue; // This DataSet is ill-formed.
+
+ char * refNumEnd = refNumPtr + 1;
+ for ( ; (*refNumEnd != ':') && (*refNumEnd != 0); ++refNumEnd ) {}
+ if ( (refNumEnd - refNumPtr) != 8 ) continue; // This DataSet is ill-formed.
+ *refNumEnd = 0; // Ensure a terminating nul for the reference number portion.
+
+ xmp->AppendArrayItem ( kXMP_NS_IPTCCore, "SubjectCode", kXMP_PropArrayIsUnordered, refNumPtr );
+
+ }
+
+} // ImportIPTC_SubjectCode
+
+// =================================================================================================
+// PhotoDataUtils::Import2WayIPTC
+// ==============================
+
+void PhotoDataUtils::Import2WayIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState )
+{
+ if ( iptcDigestState == kDigestMatches ) return; // Ignore the IPTC if the digest matches.
+
+ std::string oldStr, newStr;
+ IPTC_Writer oldIPTC;
+
+ if ( iptcDigestState == kDigestDiffers ) {
+ PhotoDataUtils::ExportIPTC ( *xmp, &oldIPTC ); // Predict old IPTC DataSets based on the existing XMP.
+ }
+
+ size_t newCount;
+ IPTC_Manager::DataSetInfo newInfo, oldInfo;
+
+ for ( size_t i = 0; kKnownDataSets[i].dsNum != 255; ++i ) {
+
+ const DataSetCharacteristics & thisDS = kKnownDataSets[i];
+ if ( thisDS.mapForm >= kIPTC_Map3Way ) continue; // The mapping is handled elsewhere, or not at all.
+
+ bool haveXMP = xmp->DoesPropertyExist ( thisDS.xmpNS, thisDS.xmpProp );
+ newCount = PhotoDataUtils::GetNativeInfo ( iptc, thisDS.dsNum, iptcDigestState, haveXMP, &newInfo );
+ if ( newCount == 0 ) continue; // GetNativeInfo returns 0 for ignored local text.
+
+ if ( iptcDigestState == kDigestMissing ) {
+ if ( haveXMP ) continue; // Keep the existing XMP.
+ } else if ( ! PhotoDataUtils::IsValueDifferent ( iptc, oldIPTC, thisDS.dsNum ) ) {
+ continue; // Don't import values that match the previous export.
+ }
+
+ // The IPTC wins. Delete any existing XMP and import the DataSet.
+
+ 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, thisDS.dsNum, thisDS.xmpNS, thisDS.xmpProp );
+ break;
+
+ case kIPTC_MapLangAlt :
+ ImportIPTC_LangAlt ( iptc, xmp, thisDS.dsNum, thisDS.xmpNS, thisDS.xmpProp );
+ break;
+
+ case kIPTC_MapArray :
+ ImportIPTC_Array ( iptc, xmp, thisDS.dsNum, thisDS.xmpNS, thisDS.xmpProp );
+ break;
+
+ case kIPTC_MapSpecial :
+ if ( thisDS.dsNum == kIPTC_DateCreated ) {
+ PhotoDataUtils::ImportIPTC_Date ( thisDS.dsNum, iptc, xmp );
+ } else if ( thisDS.dsNum == kIPTC_IntellectualGenre ) {
+ ImportIPTC_IntellectualGenre ( iptc, xmp );
+ } else if ( thisDS.dsNum == kIPTC_SubjectCode ) {
+ ImportIPTC_SubjectCode ( iptc, xmp );
+ } else {
+ XMP_Assert ( false ); // Catch mapping errors.
+ }
+ break;
+
+ }
+
+ } catch ( ... ) {
+
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+
+ }
+
+ }
+
+} // PhotoDataUtils::Import2WayIPTC
+
+// =================================================================================================
+// PhotoDataUtils::ImportPSIR
+// ==========================
+//
+// There are only 2 standalone Photoshop image resources for XMP properties:
+// 1034 - Copyright Flag - 0/1 Boolean mapped to xmpRights:Marked.
+// 1035 - Copyright URL - Local OS text mapped to xmpRights:WebStatement.
+
+// ! Photoshop does not use a true/false/missing model for PSIR 1034. Instead it essentially uses a
+// ! yes/don't-know model when importing. A missing or 0 value for PSIR 1034 cause xmpRights:Marked
+// ! to be deleted.
+
+void PhotoDataUtils::ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int iptcDigestState )
+{
+ PSIR_Manager::ImgRsrcInfo rsrcInfo;
+ bool import;
+
+ 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" ));
+ if ( import && (rsrcInfo.dataLen == 1) && (*((XMP_Uns8*)rsrcInfo.dataPtr) != 0) ) {
+ xmp->SetProperty_Bool ( kXMP_NS_XMP_Rights, "Marked", true );
+ }
+ } catch ( ... ) {
+ // 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 ) {
+ 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 );
+ } 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?
+ }
+
+} // PhotoDataUtils::ImportPSIR;
+
+// =================================================================================================
+// =================================================================================================
+
+// =================================================================================================
+// ExportIPTC_Simple
+// =================
+
+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 );
+ 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 );
+
+ iptc->SetDataSet_UTF8 ( id, value.c_str(), (XMP_Uns32)value.size(), 0 ); // ! Don't append a 2nd DataSet!
+
+} // ExportIPTC_Simple
+
+// =================================================================================================
+// ExportIPTC_LangAlt
+// ==================
+
+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 );
+ 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 );
+ if ( ! found ) {
+ iptc->DeleteDataSet ( id );
+ return;
+ }
+
+ NormalizeToCR ( &value );
+
+ size_t iptcCount = iptc->GetDataSet ( id, 0 );
+ if ( iptcCount > 1 ) iptc->DeleteDataSet ( id );
+
+ iptc->SetDataSet_UTF8 ( id, value.c_str(), (XMP_Uns32)value.size(), 0 ); // ! Don't append a 2nd DataSet!
+
+} // ExportIPTC_LangAlt
+
+// =================================================================================================
+// ExportIPTC_Array
+// ================
+//
+// Array exporting needs a bit of care to preserve the detection of XMP-only updates. If the current
+// 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 ( 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 );
+ if ( ! found ) {
+ iptc->DeleteDataSet ( id );
+ return;
+ }
+
+ if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet?
+
+ 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 );
+ if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain?
+
+ NormalizeToCR ( &value );
+
+ iptc->SetDataSet_UTF8 ( id, value.c_str(), (XMP_Uns32)value.size(), ds ); // ! Appends if necessary.
+
+ }
+
+} // ExportIPTC_Array
+
+// =================================================================================================
+// ExportIPTC_IntellectualGenre
+// ============================
+//
+// Export DataSet 2:04. In the IIM this is a 3 digit number, a colon, and a text name. Even though
+// the number is the more formal part, the IPTC4XMP rule is that the name is imported to 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. Look up the XMP value in a list of known genres to get the number.
+
+static void ExportIPTC_IntellectualGenre ( const SXMPMeta & xmp, IPTC_Manager * iptc )
+{
+ std::string xmpValue;
+ XMP_OptionBits 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;
+ XMP_StringPtr namePtr = xmpValue.c_str();
+ for ( i = 0; kIntellectualGenreMappings[i].name != 0; ++i ) {
+ 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 );
+
+ iptc->SetDataSet_UTF8 ( kIPTC_IntellectualGenre, iimValue.c_str(), (XMP_Uns32)iimValue.size(), 0 ); // ! Don't append a 2nd DataSet!
+
+} // ExportIPTC_IntellectualGenre
+
+// =================================================================================================
+// ExportIPTC_SubjectCode
+// ======================
+//
+// Export 2:12 DataSets from an unordered array. In the IIM each DataSet is composed of 5 colon
+// separated sections: a provider name, an 8 digit reference number, and 3 optional names for the
+// 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 ( const SXMPMeta & xmp, IPTC_Manager * iptc )
+{
+ std::string xmpValue, iimValue;
+ XMP_OptionBits xmpFlags;
+
+ bool found = xmp.GetProperty ( kXMP_NS_IPTCCore, "SubjectCode", 0, &xmpFlags );
+ if ( ! found ) {
+ iptc->DeleteDataSet ( kIPTC_SubjectCode );
+ return;
+ }
+
+ if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet?
+
+ 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 ( kXMP_NS_IPTCCore, "SubjectCode", ds+1, &xmpValue, &xmpFlags );
+ if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain?
+ if ( xmpValue.size() != 8 ) continue; // ? Complain?
+
+ iimValue = "IPTC:";
+ iimValue += xmpValue;
+ iimValue += ":::"; // Add the separating colons for the empty name portions.
+
+ iptc->SetDataSet_UTF8 ( kIPTC_SubjectCode, iimValue.c_str(), (XMP_Uns32)iimValue.size(), ds ); // ! Appends if necessary.
+
+ }
+
+} // ExportIPTC_SubjectCode
+
+// =================================================================================================
+// 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.
+
+// *** 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
+
+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 {
+ XMP_Throw ( "Unrecognized dateID", kXMPErr_BadParam );
+ }
+
+ 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), "%04d%02d%02d", // AUDIT: Use of sizeof(iimValue) is safe.
+ xmpValue.year, xmpValue.month, xmpValue.day );
+
+ iptc->SetDataSet_UTF8 ( dateID, iimValue, 8 );
+
+ // 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 );
+ 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_Date
+
+// =================================================================================================
+// PhotoDataUtils::ExportIPTC
+// ==========================
+
+void PhotoDataUtils::ExportIPTC ( const SXMPMeta & xmp, IPTC_Manager * iptc )
+{
+
+ for ( size_t i = 0; kKnownDataSets[i].dsNum != 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.dsNum );
+ break;
+
+ case kIPTC_MapLangAlt :
+ ExportIPTC_LangAlt ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp, thisDS.dsNum );
+ break;
+
+ case kIPTC_MapArray :
+ ExportIPTC_Array ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp, thisDS.dsNum );
+ break;
+
+ case kIPTC_MapSpecial :
+ if ( thisDS.dsNum == kIPTC_DateCreated ) {
+ ExportIPTC_Date ( thisDS.dsNum, xmp, iptc );
+ } else if ( thisDS.dsNum == kIPTC_IntellectualGenre ) {
+ ExportIPTC_IntellectualGenre ( xmp, iptc );
+ } else if ( thisDS.dsNum == kIPTC_SubjectCode ) {
+ 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.dsNum == 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.dsNum, 0 ) > 0 ) ExportIPTC_Date ( thisDS.dsNum, xmp, iptc );
+ } else if ( thisDS.dsNum == kIPTC_Creator ) {
+ ExportIPTC_Array ( xmp, iptc, kXMP_NS_DC, "creator", kIPTC_Creator );
+ } else if ( thisDS.dsNum == kIPTC_CopyrightNotice ) {
+ ExportIPTC_LangAlt ( xmp, iptc, kXMP_NS_DC, "rights", kIPTC_CopyrightNotice );
+ } else if ( thisDS.dsNum == 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?
+
+ }
+
+ }
+
+} // PhotoDataUtils::ExportIPTC;
+
+// =================================================================================================
+// PhotoDataUtils::ExportPSIR
+// ==========================
+//
+// There are only 2 standalone Photoshop image resources for XMP properties:
+// 1034 - Copyright Flag - 0/1 Boolean mapped to xmpRights:Marked.
+// 1035 - Copyright URL - Local OS text mapped to xmpRights:WebStatement.
+
+// ! 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 PhotoDataUtils::ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir )
+{
+ bool found;
+ std::string utf8Value;
+
+ try { // Don't let errors with one stop the others.
+ found = xmp.GetProperty ( kXMP_NS_XMP_Rights, "Marked", &utf8Value, 0 );
+ if ( ! found ) {
+ psir->DeleteImgRsrc ( kPSIR_CopyrightFlag );
+ } else {
+ bool copyrighted = SXMPUtils::ConvertToBool ( utf8Value );
+ psir->SetImgRsrc ( kPSIR_CopyrightFlag, &copyrighted, 1 );
+ }
+ } catch ( ... ) {
+ // 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 {
+ psir->DeleteImgRsrc ( kPSIR_CopyrightURL );
+ }
+ } catch ( ... ) {
+ // Do nothing, let other exports proceed.
+ // ? Notify client?
+ }
+
+} // PhotoDataUtils::ExportPSIR;
diff --git a/XMPFiles/source/FormatSupport/ReconcileLegacy.cpp b/XMPFiles/source/FormatSupport/ReconcileLegacy.cpp
new file mode 100644
index 0000000..e71af09
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/ReconcileLegacy.cpp
@@ -0,0 +1,205 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp"
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+#include "source/XIO.hpp"
+
+// =================================================================================================
+/// \file ReconcileLegacy.cpp
+/// \brief Top level parts of utilities to reconcile between XMP and legacy metadata forms such as
+/// TIFF/Exif and IPTC.
+///
+// =================================================================================================
+
+// =================================================================================================
+// 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.
+
+#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 haveExif = XMP_OptionIsSet ( options, k2XMP_FileHadExif );
+ bool haveIPTC = XMP_OptionIsSet ( options, k2XMP_FileHadIPTC );
+
+ // 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.
+
+ bool haveOldExif = true; // Default to old Exif if no version tag.
+ TIFF_Manager::TagInfo tagInfo;
+ bool found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, &tagInfo );
+ if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) {
+ haveOldExif = (strncmp ( (char*)tagInfo.dataPtr, "0230", 4 ) < 0);
+ }
+
+ SXMPMeta savedExif;
+
+ 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" );
+
+ SXMPUtils::RemoveProperties ( xmp, kXMP_NS_TIFF, 0, kXMPUtil_DoAllProperties );
+ SXMPUtils::RemoveProperties ( xmp, kXMP_NS_EXIF, 0, kXMPUtil_DoAllProperties );
+ if ( ! haveOldExif ) SXMPUtils::RemoveProperties ( xmp, kXMP_NS_ExifEX, 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" );
+
+ // Not obvious here, but the logic in PhotoDataUtils follows the MWG reader guidelines.
+
+ PhotoDataUtils::ImportPSIR ( psir, xmp, iptcDigestState );
+
+ if ( haveIPTC ) PhotoDataUtils::Import2WayIPTC ( iptc, xmp, iptcDigestState );
+ if ( haveExif ) PhotoDataUtils::Import2WayExif ( exif, xmp, iptcDigestState );
+
+ if ( haveExif | haveIPTC ) PhotoDataUtils::Import3WayItems ( exif, iptc, xmp, iptcDigestState );
+
+ // If photoshop:DateCreated does not exist try to create it from exif:DateTimeOriginal.
+
+ 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() );
+ }
+
+} // ImportPhotoData
+
+// =================================================================================================
+// 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 ( (destFormat == kXMP_JPEGFile) || (destFormat == kXMP_TIFFFile) || (destFormat == kXMP_PhotoshopFile) );
+
+ // 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;
+
+ exif->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_IPTC ); // These remove any existing IPTC and PSIR.
+ exif->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_PSIR );
+
+ }
+
+ // 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;
+
+ 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 ( exif != 0 ) PhotoDataUtils::ExportExif ( xmp, exif );
+ if ( psir != 0 ) PhotoDataUtils::ExportPSIR ( *xmp, psir );
+
+ // 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 ( 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 );
+ 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.
+
+ bool haveOldExif = true; // Default to old Exif if no version tag.
+ if ( exif != 0 ) {
+ TIFF_Manager::TagInfo tagInfo;
+ bool found = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, &tagInfo );
+ if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) {
+ haveOldExif = (strncmp ( (char*)tagInfo.dataPtr, "0230", 4 ) < 0);
+ }
+ }
+
+ 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 );
+ if ( ! haveOldExif ) SXMPUtils::RemoveProperties ( xmp, kXMP_NS_ExifEX, 0, kXMPUtil_DoAllProperties );
+
+ RestoreExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" );
+
+} // ExportPhotoData
diff --git a/XMPFiles/source/FormatSupport/ReconcileLegacy.hpp b/XMPFiles/source/FormatSupport/ReconcileLegacy.hpp
new file mode 100644
index 0000000..16333a3
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/ReconcileLegacy.hpp
@@ -0,0 +1,272 @@
+#ifndef __ReconcileLegacy_hpp__
+#define __ReconcileLegacy_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp"
+#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp"
+#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp"
+
+// =================================================================================================
+/// \file ReconcileLegacy.hpp
+/// \brief Utilities to reconcile between XMP and photo metadata forms such as TIFF/Exif and 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 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.
+ k2XMP_FileHadIPTC = 0x0002, // Set if the file had legacy IPTC.
+ k2XMP_FileHadExif = 0x0004 // Set if the file had legacy Exif.
+};
+
+extern void ImportPhotoData ( const TIFF_Manager & exif,
+ const IPTC_Manager & iptc,
+ const PSIR_Manager & psir,
+ int iptcDigestState,
+ SXMPMeta * xmp,
+ XMP_OptionBits options = 0 );
+
+// ExportPhotoData exports XMP into TIFF/Exif and IPTC 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 );
+
+// *** 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
+// 37121 - 4 UInt8 to integer seq
+// 37385 - Flash struct
+// 37510 - explicitly encoded text to alt-text['x-default']
+// 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
+// =================================================================================================
+
+// *** What about the Camera Raw tags that MDKit maps:
+// *** 0xFDE8, 0xFDE9, 0xFDEA, 0xFE4C, 0xFE4D, 0xFE4E, 0xFE4F, 0xFE50, 0xFE51, 0xFE52, 0xFE53,
+// *** 0xFE54, 0xFE55, 0xFE56, 0xFE57, 0xFE58
+
+// =================================================================================================
+// Summary of TIFF/Exif mappings from XMP
+// ======================================
+//
+// Only a small number of properties are written back from XMP to TIFF/Exif. Most of the TIFF/Exif
+// tags mapped into XMP are information about the image or capture process, not things that users
+// should be editing. The tags that can be edited and written back to TIFF/Exif are:
+//
+// 270, 274, 282, 283, 296, 305, 306, 315, 33432; 36867, 36868, 37510, 40964
+// =================================================================================================
+
+// =================================================================================================
+// Details of TIFF/Exif mappings
+// =============================
+//
+// 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
+// 259 SHORT 1 Compression integer
+// 262 SHORT 1 PhotometricInterpretation integer
+// 270 ASCII Any ImageDescription text, dc:description['x-default']
+// 271 ASCII Any Make text
+// 272 ASCII Any Model text
+// 274 SHORT 1 Orientation integer
+// 277 SHORT 1 SamplesPerPixel integer
+// 282 RATIONAL 1 XResolution rational
+// 283 RATIONAL 1 YResolution rational
+// 284 SHORT 1 PlanarConfiguration integer
+// 296 SHORT 1 ResolutionUnit integer
+// 301 SHORT 3*256 TransferFunction integer seq
+// 305 ASCII Any Software text, xmp:CreatorTool
+// 306 ASCII 20 DateTime date, master of 37520, xmp:DateTime
+// 315 ASCII Any Artist text, dc:creator[1]
+// 318 RATIONAL 2 WhitePoint rational seq
+// 319 RATIONAL 6 PrimaryChromaticities rational seq
+// 529 RATIONAL 3 YCbCrCoefficients rational seq
+// 530 SHORT 2 YCbCrSubSampling integer seq
+// 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
+// 34852 ASCII Any SpectralSensitivity text
+// 34855 SHORT Any ISOSpeedRatings integer seq
+// 34856 UNDEFINED Any OECF OECF/SFR table
+// 36864 UNDEFINED 4 ExifVersion text, Exif has 4 ASCII chars
+// 36867 ASCII 20 DateTimeOriginal date, master of 37521
+// 36868 ASCII 20 DateTimeDigitized date, master of 37522
+// 37121 UNDEFINED 4 ComponentsConfiguration integer seq, Exif has 4 UInt8
+// 37122 RATIONAL 1 CompressedBitsPerPixel rational
+// 37377 SRATIONAL 1 ShutterSpeedValue rational
+// 37378 RATIONAL 1 ApertureValue rational
+// 37379 SRATIONAL 1 BrightnessValue rational
+// 37380 SRATIONAL 1 ExposureBiasValue rational
+// 37381 RATIONAL 1 MaxApertureValue rational
+// 37382 RATIONAL 1 SubjectDistance rational
+// 37383 SHORT 1 MeteringMode integer
+// 37384 SHORT 1 LightSource integer
+// 37385 SHORT 1 Flash Flash struct
+// 37386 RATIONAL 1 FocalLength rational
+// 37396 SHORT 2..4 SubjectArea integer seq
+// 37510 UNDEFINED Any UserComment text, explicit encoding, exif:UserComment['x-default]
+// 37520 ASCII Any SubSecTime date, with 306
+// 37521 ASCII Any SubSecTimeOriginal date, with 36867
+// 37522 ASCII Any SubSecTimeDigitized date, with 36868
+// 40960 UNDEFINED 4 FlashpixVersion text, Exif has 4 ASCII chars
+// 40961 SHORT 1 ColorSpace integer
+// 40962 SHORTorLONG 1 PixelXDimension integer
+// 40963 SHORTorLONG 1 PixelYDimension integer
+// 40964 ASCII 13 RelatedSoundFile text
+// 41483 RATIONAL 1 FlashEnergy rational
+// 41484 UNDEFINED Any SpatialFrequencyResponse OECF/SFR table
+// 41486 RATIONAL 1 FocalPlaneXResolution rational
+// 41487 RATIONAL 1 FocalPlaneYResolution rational
+// 41488 SHORT 1 FocalPlaneResolutionUnit integer
+// 41492 SHORT 2 SubjectLocation integer seq
+// 41493 RATIONAL 1 ExposureIndex rational
+// 41495 SHORT 1 SensingMethod integer
+// 41728 UNDEFINED 1 FileSource integer, Exif has UInt8
+// 41729 UNDEFINED 1 SceneType integer, Exif has UInt8
+// 41730 UNDEFINED Any CFAPattern CFA table
+// 41985 SHORT 1 CustomRendered integer
+// 41986 SHORT 1 ExposureMode integer
+// 41987 SHORT 1 WhiteBalance integer
+// 41988 RATIONAL 1 DigitalZoomRatio rational
+// 41989 SHORT 1 FocalLengthIn35mmFilm integer
+// 41990 SHORT 1 SceneCaptureType integer
+// 41991 SHORT 1 GainControl integer
+// 41992 SHORT 1 Contrast integer
+// 41993 SHORT 1 Saturation integer
+// 41994 SHORT 1 Sharpness integer
+// 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
+// 3 ASCII 2 GPSLongitudeRef longitude, with 4
+// 4 RATIONAL 3 GPSLongitude longitude, master of 3
+// 5 BYTE 1 GPSAltitudeRef integer
+// 6 RATIONAL 1 GPSAltitude rational
+// 7 RATIONAL 3 GPSTimeStamp date, master of 29
+// 8 ASCII Any GPSSatellites text
+// 9 ASCII 2 GPSStatus text
+// 10 ASCII 2 GPSMeasureMode text
+// 11 RATIONAL 1 GPSDOP rational
+// 12 ASCII 2 GPSSpeedRef text
+// 13 RATIONAL 1 GPSSpeed rational
+// 14 ASCII 2 GPSTrackRef text
+// 15 RATIONAL 1 GPSTrack rational
+// 16 ASCII 2 GPSImgDirectionRef text
+// 17 RATIONAL 1 GPSImgDirection rational
+// 18 ASCII Any GPSMapDatum text
+// 19 ASCII 2 GPSDestLatitudeRef latitude, with 20
+// 20 RATIONAL 3 GPSDestLatitude latitude, master of 19
+// 21 ASCII 2 GPSDestLongitudeRef longitude, with 22
+// 22 RATIONAL 3 GPSDestLongitude logitude, master of 21
+// 23 ASCII 2 GPSDestBearingRef text
+// 24 RATIONAL 1 GPSDestBearing rational
+// 25 ASCII 2 GPSDestDistanceRef text
+// 26 RATIONAL 1 GPSDestDistance rational
+// 27 UNDEFINED Any GPSProcessingMethod text, explicit encoding
+// 28 UNDEFINED Any GPSAreaInformation text, explicit encoding
+// 29 ASCII 11 GPSDateStamp date, with 29
+// 30 SHORT 1 GPSDifferential integer
+//
+// =================================================================================================
+
+// =================================================================================================
+
+#endif // #ifndef __ReconcileLegacy_hpp__
diff --git a/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp b/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp
new file mode 100644
index 0000000..225c91f
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp
@@ -0,0 +1,3443 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+#include "source/UnicodeConversions.hpp"
+#include "source/XIO.hpp"
+
+#include <stdio.h>
+#if XMP_WinBuild
+ #define snprintf _snprintf
+#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
+
+// =================================================================================================
+/// \file ReconcileTIFF.cpp
+/// \brief Utilities to reconcile between XMP and legacy TIFF/Exif metadata.
+///
+// =================================================================================================
+
+// =================================================================================================
+
+#ifndef SupportOldExifProperties
+ #define SupportOldExifProperties 1
+ // This controls support of the old Adobe names for things that have official names as of Exif 2.3.
+#endif
+
+// =================================================================================================
+// Tables of the TIFF/Exif tags that are mapped into XMP. For the most part, the tags have obvious
+// mappings based on their IFD, tag number, type and count. These tables do not list tags that are
+// mapped as subsidiary parts of others, e.g. TIFF SubSecTime or GPS Info GPSDateStamp. Tags that
+// have special mappings are marked by having an empty string for the XMP property name.
+
+// ! These tables have the tags listed in the order of tables 3, 4, 5, and 12 of Exif 2.2, with the
+// ! exception of ImageUniqueID (which is listed at the end of the Exif mappings). This order is
+// ! very important to consistent checking of the legacy status. The NativeDigest properties list
+// ! all possible mapped tags in this order. The NativeDigest strings are compared as a whole, so
+// ! the same tags listed in a different order would compare as different.
+
+// ! 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 * ns; // The namespace of the mapped XMP property.
+ const char * name; // The name of the mapped XMP property.
+};
+
+enum { kAnyCount = 0 };
+
+static const TIFF_MappingToXMP sPrimaryIFDMappings[] = { // A blank name indicates a special mapping.
+ { /* 256 */ kTIFF_ImageWidth, kTIFF_ShortOrLongType, 1, kExport_Never, kXMP_NS_TIFF, "ImageWidth" },
+ { /* 257 */ kTIFF_ImageLength, kTIFF_ShortOrLongType, 1, kExport_Never, kXMP_NS_TIFF, "ImageLength" },
+ { /* 258 */ kTIFF_BitsPerSample, kTIFF_ShortType, 3, kExport_Never, kXMP_NS_TIFF, "BitsPerSample" },
+ { /* 259 */ kTIFF_Compression, kTIFF_ShortType, 1, kExport_Never, kXMP_NS_TIFF, "Compression" },
+ { /* 262 */ kTIFF_PhotometricInterpretation, kTIFF_ShortType, 1, kExport_Never, kXMP_NS_TIFF, "PhotometricInterpretation" },
+ { /* 274 */ kTIFF_Orientation, kTIFF_ShortType, 1, kExport_NoDelete, kXMP_NS_TIFF, "Orientation" },
+ { /* 277 */ kTIFF_SamplesPerPixel, kTIFF_ShortType, 1, kExport_Never, kXMP_NS_TIFF, "SamplesPerPixel" },
+ { /* 284 */ kTIFF_PlanarConfiguration, kTIFF_ShortType, 1, kExport_Never, kXMP_NS_TIFF, "PlanarConfiguration" },
+ { /* 529 */ kTIFF_YCbCrCoefficients, kTIFF_RationalType, 3, kExport_Never, kXMP_NS_TIFF, "YCbCrCoefficients" },
+ { /* 530 */ kTIFF_YCbCrSubSampling, kTIFF_ShortType, 2, kExport_Never, kXMP_NS_TIFF, "YCbCrSubSampling" },
+ { /* 282 */ kTIFF_XResolution, kTIFF_RationalType, 1, kExport_NoDelete, kXMP_NS_TIFF, "XResolution" },
+ { /* 283 */ kTIFF_YResolution, kTIFF_RationalType, 1, kExport_NoDelete, kXMP_NS_TIFF, "YResolution" },
+ { /* 296 */ kTIFF_ResolutionUnit, kTIFF_ShortType, 1, kExport_NoDelete, kXMP_NS_TIFF, "ResolutionUnit" },
+ { /* 301 */ kTIFF_TransferFunction, kTIFF_ShortType, 3*256, kExport_Never, kXMP_NS_TIFF, "TransferFunction" },
+ { /* 318 */ kTIFF_WhitePoint, kTIFF_RationalType, 2, kExport_Never, kXMP_NS_TIFF, "WhitePoint" },
+ { /* 319 */ kTIFF_PrimaryChromaticities, kTIFF_RationalType, 6, kExport_Never, kXMP_NS_TIFF, "PrimaryChromaticities" },
+ { /* 531 */ kTIFF_YCbCrPositioning, kTIFF_ShortType, 1, kExport_Never, kXMP_NS_TIFF, "YCbCrPositioning" },
+ { /* 532 */ kTIFF_ReferenceBlackWhite, kTIFF_RationalType, 6, kExport_Never, kXMP_NS_TIFF, "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, kXMP_NS_TIFF, "Make" },
+ { /* 272 */ kTIFF_Model, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_TIFF, "Model" },
+ { /* 305 */ kTIFF_Software, kTIFF_ASCIIType, kAnyCount, kExport_Always, kXMP_NS_TIFF, "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.
+};
+
+static const TIFF_MappingToXMP sExifIFDMappings[] = {
+
+ // From Exif 2.3 table 7:
+ { /* 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, kXMP_NS_EXIF, "ColorSpace" },
+ { /* 42240 */ kTIFF_Gamma, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_ExifEX, "Gamma" },
+ { /* 37121 */ kTIFF_ComponentsConfiguration, kTIFF_UndefinedType, 4, kExport_InjectOnly, "", "" }, // ! Has a special mapping.
+ { /* 37122 */ kTIFF_CompressedBitsPerPixel, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "CompressedBitsPerPixel" },
+ { /* 40962 */ kTIFF_PixelXDimension, kTIFF_ShortOrLongType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "PixelXDimension" },
+ { /* 40963 */ kTIFF_PixelYDimension, kTIFF_ShortOrLongType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "PixelYDimension" },
+ { /* 37510 */ kTIFF_UserComment, kTIFF_UndefinedType, kAnyCount, kExport_Always, "", "" }, // ! Has a special mapping.
+ { /* 40964 */ kTIFF_RelatedSoundFile, kTIFF_ASCIIType, kAnyCount, kExport_Always, kXMP_NS_EXIF, "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.
+ { /* 42016 */ kTIFF_ImageUniqueID, kTIFF_ASCIIType, 33, kExport_InjectOnly, kXMP_NS_EXIF, "ImageUniqueID" },
+ { /* 42032 */ kTIFF_CameraOwnerName, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_ExifEX, "CameraOwnerName" },
+ { /* 42033 */ kTIFF_BodySerialNumber, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_ExifEX, "BodySerialNumber" },
+ { /* 42034 */ kTIFF_LensSpecification, kTIFF_RationalType, 4, kExport_InjectOnly, kXMP_NS_ExifEX, "LensSpecification" },
+ { /* 42035 */ kTIFF_LensMake, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_ExifEX, "LensMake" },
+ { /* 42036 */ kTIFF_LensModel, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_ExifEX, "LensModel" },
+ { /* 42037 */ kTIFF_LensSerialNumber, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_ExifEX, "LensSerialNumber" },
+
+ // From Exif 2.3 table 8:
+ { /* 33434 */ kTIFF_ExposureTime, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ExposureTime" },
+ { /* 33437 */ kTIFF_FNumber, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FNumber" },
+ { /* 34850 */ kTIFF_ExposureProgram, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ExposureProgram" },
+ { /* 34852 */ kTIFF_SpectralSensitivity, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_EXIF, "SpectralSensitivity" },
+ { /* 34855 */ kTIFF_PhotographicSensitivity, kTIFF_ShortType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping.
+ { /* 34856 */ kTIFF_OECF, kTIFF_UndefinedType, kAnyCount, kExport_Never, "", "" }, // ! Has a special mapping.
+ { /* 34864 */ kTIFF_SensitivityType, kTIFF_ShortType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping.
+ { /* 34865 */ kTIFF_StandardOutputSensitivity, kTIFF_LongType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping.
+ { /* 34866 */ kTIFF_RecommendedExposureIndex, kTIFF_LongType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping.
+ { /* 34867 */ kTIFF_ISOSpeed, kTIFF_LongType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping.
+ { /* 34868 */ kTIFF_ISOSpeedLatitudeyyy, kTIFF_LongType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping.
+ { /* 34869 */ kTIFF_ISOSpeedLatitudezzz, kTIFF_LongType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping.
+ { /* 37377 */ kTIFF_ShutterSpeedValue, kTIFF_SRationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ShutterSpeedValue" },
+ { /* 37378 */ kTIFF_ApertureValue, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ApertureValue" },
+ { /* 37379 */ kTIFF_BrightnessValue, kTIFF_SRationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "BrightnessValue" },
+ { /* 37380 */ kTIFF_ExposureBiasValue, kTIFF_SRationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ExposureBiasValue" },
+ { /* 37381 */ kTIFF_MaxApertureValue, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "MaxApertureValue" },
+ { /* 37382 */ kTIFF_SubjectDistance, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "SubjectDistance" },
+ { /* 37383 */ kTIFF_MeteringMode, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "MeteringMode" },
+ { /* 37384 */ kTIFF_LightSource, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "LightSource" },
+ { /* 37385 */ kTIFF_Flash, kTIFF_ShortType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping.
+ { /* 37386 */ kTIFF_FocalLength, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FocalLength" },
+ { /* 37396 */ kTIFF_SubjectArea, kTIFF_ShortType, kAnyCount, kExport_Never, kXMP_NS_EXIF, "SubjectArea" }, // ! Actually 2..4.
+ { /* 41483 */ kTIFF_FlashEnergy, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FlashEnergy" },
+ { /* 41484 */ kTIFF_SpatialFrequencyResponse, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "", "" }, // ! Has a special mapping.
+ { /* 41486 */ kTIFF_FocalPlaneXResolution, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FocalPlaneXResolution" },
+ { /* 41487 */ kTIFF_FocalPlaneYResolution, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FocalPlaneYResolution" },
+ { /* 41488 */ kTIFF_FocalPlaneResolutionUnit, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FocalPlaneResolutionUnit" },
+ { /* 41492 */ kTIFF_SubjectLocation, kTIFF_ShortType, 2, kExport_Never, kXMP_NS_EXIF, "SubjectLocation" },
+ { /* 41493 */ kTIFF_ExposureIndex, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ExposureIndex" },
+ { /* 41495 */ kTIFF_SensingMethod, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "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, kXMP_NS_EXIF, "CustomRendered" },
+ { /* 41986 */ kTIFF_ExposureMode, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ExposureMode" },
+ { /* 41987 */ kTIFF_WhiteBalance, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "WhiteBalance" },
+ { /* 41988 */ kTIFF_DigitalZoomRatio, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "DigitalZoomRatio" },
+ { /* 41989 */ kTIFF_FocalLengthIn35mmFilm, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FocalLengthIn35mmFilm" },
+ { /* 41990 */ kTIFF_SceneCaptureType, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "SceneCaptureType" },
+ { /* 41991 */ kTIFF_GainControl, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GainControl" },
+ { /* 41992 */ kTIFF_Contrast, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "Contrast" },
+ { /* 41993 */ kTIFF_Saturation, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "Saturation" },
+ { /* 41994 */ kTIFF_Sharpness, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "Sharpness" },
+ { /* 41995 */ kTIFF_DeviceSettingDescription, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "", "" }, // ! Has a special mapping.
+ { /* 41996 */ kTIFF_SubjectDistanceRange, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "SubjectDistanceRange" },
+
+ { 0xFFFF, 0, 0, 0 } // ! Must end with sentinel.
+};
+
+static const TIFF_MappingToXMP sGPSInfoIFDMappings[] = {
+ { /* 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, kXMP_NS_EXIF, "GPSAltitudeRef" },
+ { /* 6 */ kTIFF_GPSAltitude, kTIFF_RationalType, 1, kExport_Always, kXMP_NS_EXIF, "GPSAltitude" },
+ { /* 7 */ kTIFF_GPSTimeStamp, kTIFF_RationalType, 3, kExport_Always, "", "" }, // ! Has a special mapping.
+ { /* 8 */ kTIFF_GPSSatellites, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_EXIF, "GPSSatellites" },
+ { /* 9 */ kTIFF_GPSStatus, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSStatus" },
+ { /* 10 */ kTIFF_GPSMeasureMode, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSMeasureMode" },
+ { /* 11 */ kTIFF_GPSDOP, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSDOP" },
+ { /* 12 */ kTIFF_GPSSpeedRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSSpeedRef" },
+ { /* 13 */ kTIFF_GPSSpeed, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSSpeed" },
+ { /* 14 */ kTIFF_GPSTrackRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSTrackRef" },
+ { /* 15 */ kTIFF_GPSTrack, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSTrack" },
+ { /* 16 */ kTIFF_GPSImgDirectionRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSImgDirectionRef" },
+ { /* 17 */ kTIFF_GPSImgDirection, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSImgDirection" },
+ { /* 18 */ kTIFF_GPSMapDatum, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_EXIF, "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, kXMP_NS_EXIF, "GPSDestBearingRef" },
+ { /* 24 */ kTIFF_GPSDestBearing, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSDestBearing" },
+ { /* 25 */ kTIFF_GPSDestDistanceRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSDestDistanceRef" },
+ { /* 26 */ kTIFF_GPSDestDistance, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "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, kXMP_NS_EXIF, "GPSDifferential" },
+ { /* 31 */ kTIFF_GPSHPositioningError, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_ExifEX, "GPSHPositioningError" },
+ { 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;
+ const char * strEnd = strPtr + count;
+
+ while ( strPtr < strEnd ) {
+ char ch = *strPtr;
+ if ( (ch < '0') || (ch > '9') ) break;
+ value = value*10 + (ch - '0');
+ ++strPtr;
+ }
+
+ return value;
+
+} // GatherInt
+
+// =================================================================================================
+
+static size_t TrimTrailingSpaces ( char * firstChar, size_t origLen )
+{
+ if ( origLen == 0 ) return 0;
+
+ char * lastChar = firstChar + origLen - 1;
+ if ( (*lastChar != ' ') && (*lastChar != 0) ) return origLen; // Nothing to do.
+
+ while ( (firstChar <= lastChar) && ((*lastChar == ' ') || (*lastChar == 0)) ) --lastChar;
+
+ XMP_Assert ( (lastChar == firstChar-1) ||
+ ((lastChar >= firstChar) && (*lastChar != ' ') && (*lastChar != 0)) );
+
+ size_t newLen = (size_t)((lastChar+1) - firstChar);
+ XMP_Assert ( newLen <= origLen );
+
+ if ( newLen < origLen ) {
+ ++lastChar;
+ *lastChar = 0;
+ }
+
+ return newLen;
+
+} // TrimTrailingSpaces
+
+static void TrimTrailingSpaces ( TIFF_Manager::TagInfo * info )
+{
+ info->dataLen = (XMP_Uns32) TrimTrailingSpaces ( (char*)info->dataPtr, (size_t)info->dataLen );
+}
+
+static void TrimTrailingSpaces ( std::string * stdstr )
+{
+ size_t origLen = stdstr->size();
+ size_t newLen = TrimTrailingSpaces ( (char*)stdstr->c_str(), origLen );
+ if ( newLen != origLen ) stdstr->erase ( newLen );
+}
+
+// =================================================================================================
+
+bool PhotoDataUtils::GetNativeInfo ( const TIFF_Manager & exif, XMP_Uns8 ifd, XMP_Uns16 id, TIFF_Manager::TagInfo * info )
+{
+ bool haveExif = exif.GetTag ( ifd, id, info );
+
+ if ( haveExif ) {
+
+ XMP_Uns32 i;
+ char * chPtr;
+
+ XMP_Assert ( (info->dataPtr != 0) || (info->dataLen == 0) ); // Null pointer requires zero length.
+
+ bool isDate = ((id == kTIFF_DateTime) || (id == kTIFF_DateTimeOriginal) || (id == kTIFF_DateTimeOriginal));
+
+ 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.
+ }
+
+ if ( i == info->dataLen ) {
+ haveExif = false; // Ignore empty Exif.
+ } else {
+ TrimTrailingSpaces ( info );
+ if ( info->dataLen == 0 ) haveExif = false;
+ }
+
+ }
+
+ return haveExif;
+
+} // PhotoDataUtils::GetNativeInfo
+
+// =================================================================================================
+
+size_t PhotoDataUtils::GetNativeInfo ( const IPTC_Manager & iptc, XMP_Uns8 id, int digestState, bool haveXMP, IPTC_Manager::DataSetInfo * info )
+{
+ size_t iptcCount = 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.
+ }
+
+ return iptcCount;
+
+} // PhotoDataUtils::GetNativeInfo
+
+// =================================================================================================
+
+bool PhotoDataUtils::IsValueDifferent ( const TIFF_Manager::TagInfo & exifInfo, const std::string & xmpValue, std::string * exifValue )
+{
+ if ( exifInfo.dataLen == 0 ) return false; // Ignore empty Exif values.
+
+ 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 );
+ }
+
+ return (*exifValue != xmpValue);
+
+} // PhotoDataUtils::IsValueDifferent
+
+// =================================================================================================
+
+bool PhotoDataUtils::IsValueDifferent ( const IPTC_Manager & newIPTC, const IPTC_Manager & oldIPTC, XMP_Uns8 id )
+{
+ IPTC_Manager::DataSetInfo newInfo;
+ size_t newCount = newIPTC.GetDataSet ( id, &newInfo );
+ if ( newCount == 0 ) return false; // Ignore missing new IPTC values.
+
+ 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;
+
+ std::string oldStr, newStr;
+
+ for ( newCount = 0; newCount < oldCount; ++newCount ) {
+
+ 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;
+
+ }
+
+ return ( newCount != oldCount ); // Not different if all values matched.
+
+} // PhotoDataUtils::IsValueDifferent
+
+// =================================================================================================
+// =================================================================================================
+
+// =================================================================================================
+// ImportSingleTIFF_Short
+// ======================
+
+static void
+ImportSingleTIFF_Short ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others.
+
+ 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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportSingleTIFF_Short
+
+// =================================================================================================
+// ImportSingleTIFF_Long
+// =====================
+
+static void
+ImportSingleTIFF_Long ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others.
+
+ XMP_Uns32 binValue = *((XMP_Uns32*)tagInfo.dataPtr);
+ if ( ! nativeEndian ) binValue = Flip4 ( binValue );
+
+ char strValue[20];
+ snprintf ( strValue, sizeof(strValue), "%lu", (unsigned long)binValue ); // AUDIT: Using sizeof(strValue) is safe.
+
+ xmp->SetProperty ( xmpNS, xmpProp, strValue );
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportSingleTIFF_Long
+
+// =================================================================================================
+// ImportSingleTIFF_Rational
+// =========================
+
+static void
+ImportSingleTIFF_Rational ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others.
+
+ XMP_Uns32 * binPtr = (XMP_Uns32*)tagInfo.dataPtr;
+ 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", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe.
+
+ xmp->SetProperty ( xmpNS, xmpProp, strValue );
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportSingleTIFF_Rational
+
+// =================================================================================================
+// ImportSingleTIFF_SRational
+// ==========================
+
+static void
+ImportSingleTIFF_SRational ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others.
+
+ XMP_Int32 * binPtr = (XMP_Int32*)tagInfo.dataPtr;
+ 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", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe.
+
+ xmp->SetProperty ( xmpNS, xmpProp, strValue );
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportSingleTIFF_SRational
+
+// =================================================================================================
+// ImportSingleTIFF_ASCII
+// ======================
+
+static void
+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 {
+ std::string strValue;
+ if ( isUTF8 ) {
+ strValue.assign ( chPtr, tagInfo.dataLen );
+ } else {
+ if ( ignoreLocalText ) return;
+ ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue );
+ }
+ xmp->SetProperty ( xmpNS, xmpProp, strValue.c_str() );
+ }
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportSingleTIFF_ASCII
+
+// =================================================================================================
+// ImportSingleTIFF_Byte
+// =====================
+
+static void
+ImportSingleTIFF_Byte ( const TIFF_Manager::TagInfo & tagInfo,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportSingleTIFF_Byte
+
+// =================================================================================================
+// ImportSingleTIFF_SByte
+// ======================
+
+static void
+ImportSingleTIFF_SByte ( const TIFF_Manager::TagInfo & tagInfo,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportSingleTIFF_SByte
+
+// =================================================================================================
+// ImportSingleTIFF_SShort
+// =======================
+
+static void
+ImportSingleTIFF_SShort ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others.
+
+ 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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportSingleTIFF_SShort
+
+// =================================================================================================
+// ImportSingleTIFF_SLong
+// ======================
+
+static void
+ImportSingleTIFF_SLong ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others.
+
+ XMP_Int32 binValue = *((XMP_Int32*)tagInfo.dataPtr);
+ if ( ! nativeEndian ) Flip4 ( &binValue );
+
+ char strValue[20];
+ snprintf ( strValue, sizeof(strValue), "%ld", (long)binValue ); // AUDIT: Using sizeof(strValue) is safe.
+
+ xmp->SetProperty ( xmpNS, xmpProp, strValue );
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportSingleTIFF_SLong
+
+// =================================================================================================
+// ImportSingleTIFF_Float
+// ======================
+
+static void
+ImportSingleTIFF_Float ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others.
+
+ float binValue = *((float*)tagInfo.dataPtr);
+ if ( ! nativeEndian ) Flip4 ( &binValue );
+
+ xmp->SetProperty_Float ( xmpNS, xmpProp, binValue );
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportSingleTIFF_Float
+
+// =================================================================================================
+// ImportSingleTIFF_Double
+// =======================
+
+static void
+ImportSingleTIFF_Double ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others.
+
+ double binValue = *((double*)tagInfo.dataPtr);
+ if ( ! nativeEndian ) Flip8 ( &binValue );
+
+ xmp->SetProperty_Float ( xmpNS, xmpProp, binValue ); // ! Yes, SetProperty_Float.
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportSingleTIFF_Double
+
+// =================================================================================================
+// ImportSingleTIFF
+// ================
+
+static void
+ImportSingleTIFF ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+
+ // We've got a tag to map to XMP, decide how based on actual type and the expected count. Using
+ // the actual type eliminates a ShortOrLong case. Using the expected count is needed to know
+ // whether to create an XMP array. The actual count for an array could be 1. Put the most
+ // common cases first for better iCache utilization.
+
+ switch ( tagInfo.type ) {
+
+ case kTIFF_ShortType :
+ ImportSingleTIFF_Short ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_LongType :
+ ImportSingleTIFF_Long ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_RationalType :
+ ImportSingleTIFF_Rational ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_SRationalType :
+ ImportSingleTIFF_SRational ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_ASCIIType :
+ ImportSingleTIFF_ASCII ( tagInfo, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_ByteType :
+ ImportSingleTIFF_Byte ( tagInfo, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_SByteType :
+ ImportSingleTIFF_SByte ( tagInfo, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_SShortType :
+ ImportSingleTIFF_SShort ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_SLongType :
+ ImportSingleTIFF_SLong ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_FloatType :
+ ImportSingleTIFF_Float ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_DoubleType :
+ ImportSingleTIFF_Double ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ }
+
+} // ImportSingleTIFF
+
+// =================================================================================================
+// =================================================================================================
+
+// =================================================================================================
+// ImportArrayTIFF_Short
+// =====================
+
+static void
+ImportArrayTIFF_Short ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportArrayTIFF_Short
+
+// =================================================================================================
+// ImportArrayTIFF_Long
+// ====================
+
+static void
+ImportArrayTIFF_Long ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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", (unsigned long)binValue ); // AUDIT: Using sizeof(strValue) is safe.
+
+ xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue );
+
+ }
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportArrayTIFF_Long
+
+// =================================================================================================
+// ImportArrayTIFF_Rational
+// ========================
+
+static void
+ImportArrayTIFF_Rational ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe.
+
+ xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue );
+
+ }
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportArrayTIFF_Rational
+
+// =================================================================================================
+// ImportArrayTIFF_SRational
+// =========================
+
+static void
+ImportArrayTIFF_SRational ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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", (long)binNum, (long)binDenom ); // AUDIT: Using sizeof(strValue) is safe.
+
+ xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue );
+
+ }
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportArrayTIFF_SRational
+
+// =================================================================================================
+// ImportArrayTIFF_ASCII
+// =====================
+
+static void
+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 ( 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 );
+ }
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportArrayTIFF_ASCII
+
+// =================================================================================================
+// ImportArrayTIFF_Byte
+// ====================
+
+static void
+ImportArrayTIFF_Byte ( const TIFF_Manager::TagInfo & tagInfo,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportArrayTIFF_Byte
+
+// =================================================================================================
+// ImportArrayTIFF_SByte
+// =====================
+
+static void
+ImportArrayTIFF_SByte ( const TIFF_Manager::TagInfo & tagInfo,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportArrayTIFF_SByte
+
+// =================================================================================================
+// ImportArrayTIFF_SShort
+// ======================
+
+static void
+ImportArrayTIFF_SShort ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportArrayTIFF_SShort
+
+// =================================================================================================
+// ImportArrayTIFF_SLong
+// =====================
+
+static void
+ImportArrayTIFF_SLong ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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", (long)binValue ); // AUDIT: Using sizeof(strValue) is safe.
+
+ xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue );
+
+ }
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportArrayTIFF_SLong
+
+// =================================================================================================
+// ImportArrayTIFF_Float
+// =====================
+
+static void
+ImportArrayTIFF_Float ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportArrayTIFF_Float
+
+// =================================================================================================
+// ImportArrayTIFF_Double
+// ======================
+
+static void
+ImportArrayTIFF_Double ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportArrayTIFF_Double
+
+// =================================================================================================
+// ImportArrayTIFF
+// ===============
+
+static void
+ImportArrayTIFF ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+
+ // We've got a tag to map to XMP, decide how based on actual type and the expected count. Using
+ // the actual type eliminates a ShortOrLong case. Using the expected count is needed to know
+ // whether to create an XMP array. The actual count for an array could be 1. Put the most
+ // common cases first for better iCache utilization.
+
+ switch ( tagInfo.type ) {
+
+ case kTIFF_ShortType :
+ ImportArrayTIFF_Short ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_LongType :
+ ImportArrayTIFF_Long ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_RationalType :
+ ImportArrayTIFF_Rational ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_SRationalType :
+ ImportArrayTIFF_SRational ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_ASCIIType :
+ ImportArrayTIFF_ASCII ( tagInfo, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_ByteType :
+ ImportArrayTIFF_Byte ( tagInfo, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_SByteType :
+ ImportArrayTIFF_SByte ( tagInfo, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_SShortType :
+ ImportArrayTIFF_SShort ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_SLongType :
+ ImportArrayTIFF_SLong ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_FloatType :
+ ImportArrayTIFF_Float ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ case kTIFF_DoubleType :
+ ImportArrayTIFF_Double ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp );
+ break;
+
+ }
+
+} // ImportArrayTIFF
+
+// =================================================================================================
+// ImportTIFF_CheckStandardMapping
+// ===============================
+
+static bool
+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 );
+
+ if ( (tagInfo.type < kTIFF_ByteType) || (tagInfo.type > kTIFF_LastType) ) return false;
+
+ if ( tagInfo.type != mapInfo.type ) {
+ // Be tolerant of reasonable mismatches among numeric types.
+ if ( kTIFF_IsIntegerType[mapInfo.type] ) {
+ if ( ! kTIFF_IsIntegerType[tagInfo.type] ) return false;
+ } else if ( kTIFF_IsRationalType[mapInfo.type] ) {
+ if ( ! kTIFF_IsRationalType[tagInfo.type] ) return false;
+ } else if ( kTIFF_IsFloatType[mapInfo.type] ) {
+ if ( ! kTIFF_IsFloatType[tagInfo.type] ) return false;
+ } else {
+ 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;
+
+} // ImportTIFF_CheckStandardMapping
+
+// =================================================================================================
+// ImportTIFF_StandardMappings
+// ===========================
+
+static void
+ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta * xmp )
+{
+ const bool nativeEndian = tiff.IsNativeEndian();
+ TIFF_Manager::TagInfo tagInfo;
+
+ const TIFF_MappingToXMP * mappings = 0;
+
+ if ( ifd == kTIFF_PrimaryIFD ) {
+ mappings = sPrimaryIFDMappings;
+ } else if ( ifd == kTIFF_ExifIFD ) {
+ mappings = sExifIFDMappings;
+ } else if ( ifd == kTIFF_GPSInfoIFD ) {
+ mappings = sGPSInfoIFDMappings;
+ } 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];
+ const bool mapSingle = ((mapInfo.count == 1) || (mapInfo.type == kTIFF_ASCIIType));
+
+ 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, mapInfo.ns, mapInfo.name );
+ } else {
+ ImportArrayTIFF ( tagInfo, nativeEndian, xmp, mapInfo.ns, mapInfo.name );
+ }
+
+ } catch ( ... ) {
+
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+
+ }
+
+ }
+
+} // ImportTIFF_StandardMappings
+
+// =================================================================================================
+// =================================================================================================
+
+// =================================================================================================
+// ImportTIFF_Date
+// ===============
+//
+// Convert an Exif 2.2 master date/time tag plus associated fractional seconds to an XMP date/time.
+// The Exif date/time part is a 20 byte ASCII value formatted as "YYYY:MM:DD HH:MM:SS" with a
+// terminating nul. Any of the numeric portions can be blanks if unknown. The fractional seconds
+// are a nul terminated ASCII string with possible space padding. They are literally the fractional
+// 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,
+ 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.
+ 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 ); // ! 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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportTIFF_Date
+
+// =================================================================================================
+// ImportTIFF_LocTextASCII
+// =======================
+
+static void
+ImportTIFF_LocTextASCII ( const TIFF_Manager & tiff, XMP_Uns8 ifd, XMP_Uns16 tagID,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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;
+
+ 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->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", chPtr );
+ } else {
+ std::string strValue;
+ if ( isUTF8 ) {
+ strValue.assign ( chPtr, tagInfo.dataLen );
+ } else {
+ if ( ignoreLocalText ) return;
+ ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue );
+ }
+ xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", strValue.c_str() );
+ }
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportTIFF_LocTextASCII
+
+// =================================================================================================
+// ImportTIFF_EncodedString
+// ========================
+
+static void
+ImportTIFF_EncodedString ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & tagInfo,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp, bool isLangAlt = false )
+{
+ 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;
+
+ TrimTrailingSpaces ( &strValue );
+ if ( strValue.empty() ) return;
+
+ if ( ! isLangAlt ) {
+ xmp->SetProperty ( xmpNS, xmpProp, strValue.c_str() );
+ } else {
+ xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", strValue.c_str() );
+ }
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportTIFF_EncodedString
+
+// =================================================================================================
+// ImportTIFF_Flash
+// ================
+
+static void
+ImportTIFF_Flash ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others.
+
+ 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] );
+ xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Function", (function ? kXMP_TrueStr : kXMP_FalseStr) );
+ xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "RedEyeMode", (redEye ? kXMP_TrueStr : kXMP_FalseStr) );
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportTIFF_Flash
+
+// =================================================================================================
+// ImportConversionTable
+// =====================
+//
+// 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. But they are otherwise the same.
+
+static void
+ImportConversionTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ const bool isSigned = (tagInfo.id == kTIFF_OECF);
+ XMP_Assert ( (tagInfo.id == kTIFF_OECF) || (tagInfo.id == kTIFF_SpatialFrequencyResponse) );
+
+ xmp->DeleteProperty ( xmpNS, xmpProp );
+
+ try { // Don't let errors with one stop the others.
+
+ const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr;
+ const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen;
+
+ 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. Don't convert from local text, should really be ASCII.
+ for ( size_t i = columns; i > 0; --i ) {
+ size_t nameLen = strlen((XMP_StringPtr)bytePtr) + 1; // ! Include the terminating nul.
+ if ( (bytePtr + nameLen) > byteEnd ) XMP_Throw ( "OECF-SFR name overflow", kXMPErr_BadValue );
+ if ( ! ReconcileUtils::IsUTF8 ( bytePtr, nameLen ) ) XMP_Throw ( "OECF-SFR name error", kXMPErr_BadValue );
+ xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, (XMP_StringPtr)bytePtr );
+ bytePtr += nameLen;
+ }
+
+ if ( (byteEnd - bytePtr) != (8 * columns * rows) ) XMP_Throw ( "OECF-SFR data overflow", kXMPErr_BadValue );
+ 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 ) {
+ Flip4 ( &binNum );
+ Flip4 ( &binDenom );
+ }
+
+ if ( (binDenom == 0) && (binNum != 0) ) XMP_Throw ( "OECF-SFR data overflow", kXMPErr_BadValue );
+ if ( isSigned ) {
+ snprintf ( buffer, sizeof(buffer), "%ld/%ld", (long)binNum, (long)binDenom ); // AUDIT: Use of sizeof(buffer) is safe.
+ } else {
+ 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 ( ... ) {
+ xmp->DeleteProperty ( xmpNS, xmpProp );
+ // ? Notify client?
+ }
+
+} // ImportConversionTable
+
+// =================================================================================================
+// ImportTIFF_CFATable
+// ===================
+
+static void
+ImportTIFF_CFATable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others.
+
+ const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr;
+ const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen;
+
+ 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;
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportTIFF_CFATable
+
+// =================================================================================================
+// ImportTIFF_DSDTable
+// ===================
+
+static void
+ImportTIFF_DSDTable ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & tagInfo,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others.
+
+ const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr;
+ const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen;
+
+ 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;
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportTIFF_DSDTable
+
+// =================================================================================================
+// ImportTIFF_GPSCoordinate
+// ========================
+
+static void
+ImportTIFF_GPSCoordinate ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & posInfo,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ try { // Don't let errors with one stop the others. Tolerate ill-formed values where reasonable.
+
+ const bool nativeEndian = tiff.IsNativeEndian();
+
+ if ( (posInfo.type != kTIFF_RationalType) || (posInfo.count == 0) ) return;
+
+ 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.count == 0) ) return;
+ char ref = *((char*)refInfo.dataPtr);
+ if ( (ref != 'N') && (ref != 'S') && (ref != 'E') && (ref != 'W') ) return;
+
+ XMP_Uns32 * binPtr = (XMP_Uns32*)posInfo.dataPtr;
+ XMP_Uns32 degNum = 0, degDenom = 1; // Defaults for missing parts.
+ XMP_Uns32 minNum = 0, minDenom = 1;
+ XMP_Uns32 secNum = 0, secDenom = 1;
+ if ( ! nativeEndian ) {
+ degDenom = Flip4 ( degDenom ); // So they can be flipped again below.
+ minDenom = Flip4 ( minDenom );
+ secDenom = Flip4 ( secDenom );
+ }
+
+ degNum = binPtr[0];
+ degDenom = binPtr[1];
+ if ( posInfo.count >= 2 ) {
+ minNum = binPtr[2];
+ minDenom = binPtr[3];
+ if ( posInfo.count >= 3 ) {
+ secNum = binPtr[4];
+ secDenom = binPtr[5];
+ }
+ }
+
+ if ( ! nativeEndian ) {
+ degNum = Flip4 ( degNum );
+ degDenom = Flip4 ( degDenom );
+ minNum = Flip4 ( minNum );
+ minDenom = Flip4 ( minDenom );
+ secNum = Flip4 ( secNum );
+ secDenom = Flip4 ( secDenom );
+ }
+
+ // *** No check is made, whether the numerator exceed the maximum values,
+ // which is 90 for Latitude and 180 for Longitude
+ // ! The Exif spec says denominator must not be zero, but in reality they can be
+
+ char buffer[40];
+
+ // Simplest case: all denominators are 1
+ if ( (degDenom == 1) && (minDenom == 1) && (secDenom == 1) ) {
+
+ 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 if ( (degDenom == 0 && degNum != 0) || (minDenom == 0 && minNum != 0) || (secDenom == 0 && secNum != 0) ) {
+
+ // Error case: a denominator is zero and the numerator is non-zero
+ return; // Do not continue with import
+
+ } else {
+
+ // Determine precision
+ // The code rounds up to the next power of 10 to get the number of fractional digits
+ XMP_Uns32 maxDenom = degDenom;
+ if ( minDenom > maxDenom ) maxDenom = minDenom;
+ if ( secDenom > maxDenom ) maxDenom = secDenom;
+
+ int fracDigits = 1;
+ while ( maxDenom > 10 ) { ++fracDigits; maxDenom = maxDenom/10; }
+
+ // Calculate the values
+ // At this point we know that the fractions are either 0/0, 0/y or x/y
+
+ double degrees, minutes;
+
+ // Degrees
+ if ( degDenom == 0 && degNum == 0 ) {
+ degrees = 0;
+ } else {
+ degrees = (double)( (XMP_Uns32)((double)degNum / (double)degDenom) ); // Just the integral number of degrees.
+ }
+
+ // Minutes
+ if ( minDenom == 0 && minNum == 0 ) {
+ minutes = 0;
+ } else {
+ double temp = 0;
+ if( degrees != 0 ) temp = ((double)degNum / (double)degDenom) - degrees;
+
+ minutes = (temp * 60.0) + ((double)minNum / (double)minDenom);
+ }
+
+ // Seconds, are added to minutes
+ if ( secDenom != 0 && secNum != 0 ) {
+ minutes += ((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 ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportTIFF_GPSCoordinate
+
+// =================================================================================================
+// ImportTIFF_GPSTimeStamp
+// =======================
+
+static void
+ImportTIFF_GPSTimeStamp ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & timeInfo,
+ SXMPMeta * xmp, const char * xmpNS, const char * xmpProp )
+{
+ 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[4] != '-')) || ((dateStr[7] != ':') && (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];
+ XMP_Uns32 minNum = binPtr[2];
+ XMP_Uns32 minDenom = binPtr[3];
+ XMP_Uns32 secNum = binPtr[4];
+ XMP_Uns32 secDenom = binPtr[5];
+ if ( ! nativeEndian ) {
+ hourNum = Flip4 ( hourNum );
+ hourDenom = Flip4 ( hourDenom );
+ minNum = Flip4 ( minNum );
+ minDenom = Flip4 ( minDenom );
+ secNum = Flip4 ( secNum );
+ secDenom = Flip4 ( secDenom );
+ }
+
+ double fHour, fMin, fSec, fNano, temp;
+ fSec = (double)secNum / (double)secDenom;
+ temp = (double)minNum / (double)minDenom;
+ fMin = (double)((XMP_Uns32)temp);
+ fSec += (temp - fMin) * 60.0;
+ temp = (double)hourNum / (double)hourDenom;
+ fHour = (double)((XMP_Uns32)temp);
+ fSec += (temp - fHour) * 3600.0;
+ temp = (double)((XMP_Uns32)fSec);
+ fNano = ((fSec - temp) * (1000.0*1000.0*1000.0)) + 0.5; // Try to avoid n999... problems.
+ fSec = temp;
+
+ XMP_DateTime binStamp;
+ 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.hasTimeZone = true; // Exif GPS TimeStamp is implicitly UTC.
+ binStamp.tzSign = kXMP_TimeIsUTC;
+ binStamp.tzHour = binStamp.tzMinute = 0;
+
+ xmp->SetProperty_Date ( xmpNS, xmpProp, binStamp );
+
+ } catch ( ... ) {
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+ }
+
+} // ImportTIFF_GPSTimeStamp
+
+// =================================================================================================
+
+static void ImportTIFF_PhotographicSensitivity ( const TIFF_Manager & exif, SXMPMeta * xmp ) {
+
+ // PhotographicSensitivity has special cases for values over 65534 because the tag is SHORT. It
+ // has a count of "any", but all known cameras used a count of 1. Exif 2.3 still allows a count
+ // of "any" but says only 1 value should ever be recorded. We only process the first value if
+ // the count is greater than 1.
+ //
+ // Prior to Exif 2.3 the tag was called ISOSpeedRatings. Some cameras omit the tag and some
+ // write 65535. Most put the real ISO in MakerNote, which might be in the XMP from ACR.
+ //
+ // With Exif 2.3 five speed-related tags were added of type LONG: StandardOutputSensitivity,
+ // RecommendedExposureIndex, ISOSpeed, ISOSpeedLatitudeyyy, and ISOSpeedLatitudezzz. The tag
+ // SensitivityType was added to say which of these five is copied to PhotographicSensitivity.
+ //
+ // Import logic:
+ // Save exif:ISOSpeedRatings when cleaning namespaces (done in ImportPhotoData)
+ // If ExifVersion is less than "0230" (or missing)
+ // If PhotographicSensitivity[1] < 65535 or no exif:ISOSpeedRatings: set exif:ISOSpeedRatings from tag
+ // Else (ExifVersion is at least "0230")
+ // Import SensitivityType and related long tags
+ // If PhotographicSensitivity[1] < 65535: set exifEX:PhotographicSensitivity and exif:ISOSpeedRatings from tag
+ // Else (no PhotographicSensitivity tag or value is 65535)
+ // Set exifEX:PhotographicSensitivity from tag (missing or 65535)
+ // If have SensitivityType and indicated tag: set exif:ISOSpeedRatings from indicated tag
+
+ try {
+
+ bool found;
+ TIFF_Manager::TagInfo tagInfo;
+
+ bool haveOldExif = true; // Default to old Exif if no version tag.
+ bool haveTag34855 = false;
+ bool haveLowISO = false; // Set for real if haveTag34855 is true.
+
+ XMP_Uns32 valueTag34855; // ! Only care about the first value if more than 1.
+
+ found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, &tagInfo );
+ if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) {
+ haveOldExif = (strncmp ( (char*)tagInfo.dataPtr, "0230", 4 ) < 0);
+ }
+
+ haveTag34855 = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, &valueTag34855 );
+ if ( haveTag34855 ) haveLowISO = (valueTag34855 < 65535);
+
+ if ( haveOldExif ) { // Exif version is before 2.3, use just the old tag and property.
+
+ if ( haveTag34855 ) {
+ if ( haveLowISO || (! xmp->DoesPropertyExist ( kXMP_NS_EXIF, "ISOSpeedRatings" )) ) {
+ xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" );
+ xmp->AppendArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", kXMP_PropArrayIsOrdered, "" );
+ xmp->SetProperty_Int ( kXMP_NS_EXIF, "ISOSpeedRatings[1]", valueTag34855 );
+ }
+ }
+
+ } else { // Exif version is 2.3 or newer, use the Exif 2.3 tags and properties.
+
+ // Import the SensitivityType and related long tags.
+
+ XMP_Uns16 whichLongTag = 0;
+ XMP_Uns32 sensitivityType, tagValue;
+
+ bool haveSensitivityType = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_SensitivityType, &sensitivityType );
+ if ( haveSensitivityType ) {
+ xmp->SetProperty_Int ( kXMP_NS_ExifEX, "SensitivityType", sensitivityType );
+ switch ( sensitivityType ) {
+ case 1 : // Use StandardOutputSensitivity for both 1 and 4.
+ case 4 : whichLongTag = kTIFF_StandardOutputSensitivity; break;
+ case 2 : whichLongTag = kTIFF_RecommendedExposureIndex; break;
+ case 3 : // Use ISOSpeed for all of 3, 5, 6, and 7.
+ case 5 :
+ case 6 :
+ case 7 : whichLongTag = kTIFF_ISOSpeed; break;
+ }
+ }
+
+ found = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_StandardOutputSensitivity, &tagValue );
+ if ( found ) {
+ xmp->SetProperty_Int64 ( kXMP_NS_ExifEX, "StandardOutputSensitivity", tagValue );
+ }
+
+ found = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_RecommendedExposureIndex, &tagValue );
+ if ( found ) {
+ xmp->SetProperty_Int64 ( kXMP_NS_ExifEX, "RecommendedExposureIndex", tagValue );
+ }
+
+ found = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_ISOSpeed, &tagValue );
+ if ( found ) {
+ xmp->SetProperty_Int64 ( kXMP_NS_ExifEX, "ISOSpeed", tagValue );
+ }
+
+ found = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudeyyy, &tagValue );
+ if ( found ) {
+ xmp->SetProperty_Int64 ( kXMP_NS_ExifEX, "ISOSpeedLatitudeyyy", tagValue );
+ }
+
+ found = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudezzz, &tagValue );
+ if ( found ) {
+ xmp->SetProperty_Int64 ( kXMP_NS_ExifEX, "ISOSpeedLatitudezzz", tagValue );
+ }
+
+ // Deal with the special cases for exifEX:PhotographicSensitivity and exif:ISOSpeedRatings.
+
+ if ( haveTag34855 & haveLowISO ) { // The easier low ISO case.
+
+ xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" );
+ xmp->AppendArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", kXMP_PropArrayIsOrdered, "" );
+ xmp->SetProperty_Int ( kXMP_NS_EXIF, "ISOSpeedRatings[1]", valueTag34855 );
+ xmp->SetProperty_Int ( kXMP_NS_ExifEX, "PhotographicSensitivity", valueTag34855 );
+
+ } else { // The more complex high ISO case, or no PhotographicSensitivity tag.
+
+ if ( haveTag34855 ) {
+ XMP_Assert ( valueTag34855 == 65535 );
+ xmp->SetProperty_Int ( kXMP_NS_ExifEX, "PhotographicSensitivity", valueTag34855 );
+ }
+
+ if ( whichLongTag != 0 ) {
+ found = exif.GetTag ( kTIFF_ExifIFD, whichLongTag, &tagInfo );
+ if ( found && (tagInfo.type == kTIFF_LongType) && (tagInfo.count == 1) ){
+ xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" );
+ xmp->AppendArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", kXMP_PropArrayIsOrdered, "" );
+ xmp->SetProperty_Int ( kXMP_NS_EXIF, "ISOSpeedRatings[1]", exif.GetUns32 ( tagInfo.dataPtr ) );
+ }
+ }
+
+ }
+
+ }
+
+ } catch ( ... ) {
+
+ // Do nothing, don't let failures here stop other exports.
+
+ }
+
+} // ImportTIFF_PhotographicSensitivity
+
+// =================================================================================================
+// =================================================================================================
+
+// =================================================================================================
+// 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
+PhotoDataUtils::Import2WayExif ( const TIFF_Manager & exif, SXMPMeta * xmp, int iptcDigestState )
+{
+ const bool nativeEndian = exif.IsNativeEndian();
+
+ bool found, foundFromXMP;
+ TIFF_Manager::TagInfo tagInfo;
+ XMP_OptionBits flags;
+
+ ImportTIFF_StandardMappings ( kTIFF_PrimaryIFD, exif, xmp );
+ ImportTIFF_StandardMappings ( kTIFF_ExifIFD, exif, xmp );
+ ImportTIFF_StandardMappings ( kTIFF_GPSInfoIFD, exif, xmp );
+
+ // --------------------------------------------------------
+ // Add the old Adobe names for new Exif 2.3 tags if wanted.
+
+ #if SupportOldExifProperties
+
+ // CameraOwnerName -> aux:OwnerName
+ found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_CameraOwnerName, &tagInfo );
+ if ( found && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count > 0) ) {
+ ImportSingleTIFF ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF_Aux, "OwnerName" );
+ }
+
+ // BodySerialNumber -> aux:SerialNumber
+ found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_BodySerialNumber, &tagInfo );
+ if ( found && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count > 0) ) {
+ ImportSingleTIFF ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF_Aux, "SerialNumber" );
+ }
+
+ // LensModel -> aux:Lens
+ found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_LensModel, &tagInfo );
+ if ( found && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count > 0) ) {
+ ImportSingleTIFF ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF_Aux, "Lens" );
+ }
+
+ // LensSpecification -> aux:LensInfo as a single string - use the XMP form for simplicity
+ found = xmp->GetProperty ( kXMP_NS_ExifEX, "LensSpecification", 0, &flags );
+ if ( found && XMP_PropIsArray(flags) ) {
+ std::string fullStr, oneItem;
+ size_t count = (size_t) xmp->CountArrayItems ( kXMP_NS_ExifEX, "LensSpecification" );
+ if ( count > 0 ) {
+ (void) xmp->GetArrayItem ( kXMP_NS_ExifEX, "LensSpecification", 1, &fullStr, 0 );
+ for ( size_t i = 2; i <= count; ++i ) {
+ fullStr += ' ';
+ (void) xmp->GetArrayItem ( kXMP_NS_ExifEX, "LensSpecification", i, &oneItem, 0 );
+ fullStr += oneItem;
+ }
+ }
+ xmp->SetProperty ( kXMP_NS_EXIF_Aux, "LensInfo", fullStr.c_str(), kXMP_DeleteExisting );
+ }
+
+ #endif
+
+ // --------------------------------------------------------------------------------------------
+ // Fixup erroneous cases that appear to have a negative value for GPSAltitude in the Exif. This
+ // treats any value with the high bit set as a negative, which is more likely in practice than
+ // an actual value over 2 billion.
+
+ found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSAltitude, &tagInfo );
+ if ( found && (tagInfo.type == kTIFF_RationalType) && (tagInfo.count == 1) ) {
+
+ XMP_Uns32 num = exif.GetUns32 ( tagInfo.dataPtr );
+ XMP_Uns32 denom = exif.GetUns32 ( (XMP_Uns8*)tagInfo.dataPtr + 4 );
+
+ bool fixXMP = false;
+ bool numNeg = num >> 31;
+ bool denomNeg = denom >> 31;
+
+ if ( denomNeg ) { // The denominator looks negative, shift the sign to the numerator.
+ denom = -denom;
+ num = -num;
+ numNeg = num >> 31;
+ fixXMP = true;
+ }
+
+ if ( numNeg ) { // The numerator looks negative, fix the XMP.
+ xmp->SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "1" );
+ num = -num;
+ fixXMP = true;
+ }
+
+ if ( fixXMP ) {
+ char buffer [32];
+ 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 );
+ }
+
+ }
+
+ // ---------------------------------------------------------------
+ // Import DateTimeOriginal and DateTime if the XMP doss not exist.
+
+ 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" );
+ }
+
+ 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" );
+ }
+
+ // ----------------------------------------------------
+ // Import the Exif IFD tags that have special mappings.
+
+ // There are moderately complex import special cases for PhotographicSensitivity.
+ ImportTIFF_PhotographicSensitivity ( exif, xmp );
+
+ // 42032 CameraOwnerName has a standard mapping. As a special case also set dc:creator if there
+ // is no Exif Artist tag and no dc:creator in the XMP.
+ found = exif.GetTag ( kTIFF_PrimaryIFD, kTIFF_Artist, &tagInfo );
+ foundFromXMP = xmp->DoesPropertyExist ( kXMP_NS_DC, "creator" );
+ if ( (! found) && (! foundFromXMP) ) {
+ found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_CameraOwnerName, &tagInfo );
+ if ( found ) {
+ std::string xmpValue ( (char*)tagInfo.dataPtr, tagInfo.dataLen );
+ xmp->AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, xmpValue.c_str() );
+ }
+ }
+
+ // 36864 ExifVersion is 4 "undefined" ASCII characters.
+ 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;
+ xmp->SetProperty ( kXMP_NS_EXIF, "ExifVersion", str );
+ }
+
+ // 40960 FlashpixVersion is 4 "undefined" ASCII characters.
+ 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;
+ xmp->SetProperty ( kXMP_NS_EXIF, "FlashpixVersion", str );
+ }
+
+ // 37121 ComponentsConfiguration is an array of 4 "undefined" UInt8 bytes.
+ 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.
+ 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.
+ found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_OECF, &tagInfo );
+ if ( found ) {
+ ImportConversionTable ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "OECF" );
+ }
+
+ // 37385 Flash is a UInt16 collection of bit fields and is mapped to a struct in XMP.
+ 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.
+ found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_SpatialFrequencyResponse, &tagInfo );
+ if ( found ) {
+ ImportConversionTable ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "SpatialFrequencyResponse" );
+ }
+
+ // 41728 FileSource is an "undefined" UInt8.
+ 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.
+ 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.
+ 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.
+ found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_DeviceSettingDescription, &tagInfo );
+ if ( found ) {
+ ImportTIFF_DSDTable ( exif, tagInfo, xmp, kXMP_NS_EXIF, "DeviceSettingDescription" );
+ }
+
+ // --------------------------------------------------------
+ // Import the GPS Info IFD tags that have special mappings.
+
+ // 0 GPSVersionID is 4 UInt8 bytes and mapped as "n.n.n.n".
+ found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, &tagInfo );
+ if ( found && (tagInfo.type == kTIFF_ByteType) && (tagInfo.count == 4) ) {
+ const XMP_Uns8 * binValue = (const XMP_Uns8 *) tagInfo.dataPtr;
+ char strOut[20];// ! Value could be up to 16 bytes: "255.255.255.255" plus nul.
+ snprintf ( strOut, sizeof(strOut), "%u.%u.%u.%u", // AUDIT: Use of sizeof(strOut) is safe.
+ binValue[0], binValue[1], binValue[2], binValue[3] );
+ xmp->SetProperty ( kXMP_NS_EXIF, "GPSVersionID", strOut );
+ }
+
+ // 2 GPSLatitude is a GPS coordinate master.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ 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.
+ found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation, &tagInfo );
+ if ( found ) {
+ ImportTIFF_EncodedString ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSAreaInformation" );
+ }
+
+} // 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
+
+// =================================================================================================
+// 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.
+
+// -------------------------------------------------------------------------------------------------
+
+void PhotoDataUtils::Import3WayItems ( const TIFF_Manager & exif, const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState )
+{
+ 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;
+
+ 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)) );
+
+ 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() );
+ }
+
+ // -------------------------------------------------------------------------------------------
+ // 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 | kXMPUtil_AllowCommas), exifValue );
+ }
+
+ // ------------------------------------------------------------------------------
+ // 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
+
+// =================================================================================================
+// =================================================================================================
+
+// =================================================================================================
+
+static bool DecodeRational ( const char * ratio, XMP_Uns32 * num, XMP_Uns32 * denom ) {
+
+ unsigned long locNum, locDenom;
+ char nextChar; // Used to make sure sscanf consumes all of the string.
+
+ int items = sscanf ( ratio, "%lu/%lu%c", &locNum, &locDenom, &nextChar ); // AUDIT: This is safe, check the calls.
+
+ if ( items != 2 ) {
+ if ( items != 1 ) return false;
+ locDenom = 1; // The XMP was just an integer, assume a denominator of 1.
+ }
+
+ *num = (XMP_Uns32)locNum;
+ *denom = (XMP_Uns32)locDenom;
+ return true;
+
+} // DecodeRational
+
+// =================================================================================================
+// ExportSingleTIFF
+// ================
+//
+// 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 ( TIFF_Manager * tiff, XMP_Uns8 ifd, const TIFF_MappingToXMP & mapInfo,
+ bool nativeEndian, const std::string & xmpValue )
+{
+ XMP_Assert ( (mapInfo.count == 1) || (mapInfo.type == kTIFF_ASCIIType) );
+ XMP_Assert ( mapInfo.name[0] != 0 ); // Must be a standard mapping.
+
+ bool ok;
+ char nextChar; // Used to make sure sscanf consumes all of the string.
+
+ switch ( mapInfo.type ) {
+
+ 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;
+ }
+
+ 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;
+ }
+
+ case kTIFF_LongType : {
+ 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_Long ( ifd, mapInfo.id, (XMP_Uns32)binValue );
+ 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;
+ }
+
+ case kTIFF_RationalType : { // The XMP is formatted as "num/denom".
+ XMP_Uns32 num, denom;
+ ok = DecodeRational ( xmpValue.c_str(), &num, &denom );
+ if ( ! ok ) return; // ? complain? notify client?
+ tiff->SetTag_Rational ( ifd, mapInfo.id, num, 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.
+ }
+ 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;
+
+ default:
+ XMP_Assert ( false ); // Force a debug assert for unexpected types.
+
+ }
+
+} // ExportSingleTIFF
+
+// =================================================================================================
+// 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
+ExportArrayTIFF ( TIFF_Manager * tiff, XMP_Uns8 ifd, const TIFF_MappingToXMP & mapInfo, bool nativeEndian,
+ const SXMPMeta & xmp, const char * xmpNS, const char * xmpArray )
+{
+ 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) || (mapInfo.type == kTIFF_RationalType) );
+ XMP_Assert ( xmp.DoesPropertyExist ( xmpNS, xmpArray ) );
+
+ size_t arraySize = xmp.CountArrayItems ( xmpNS, xmpArray );
+ if ( arraySize == 0 ) {
+ tiff->DeleteTag ( ifd, mapInfo.id );
+ return;
+ }
+
+ if ( mapInfo.type == kTIFF_ShortType ) {
+
+ std::vector<XMP_Uns16> shortVector;
+ shortVector.assign ( arraySize, 0 );
+ XMP_Uns16 * shortPtr = (XMP_Uns16*) &shortVector[0];
+
+ std::string itemPath;
+ XMP_Int32 int32;
+ XMP_Uns16 uns16;
+ for ( size_t i = 1; i <= arraySize; ++i, ++shortPtr ) {
+ 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 );
+ *shortPtr = uns16;
+ }
+
+ tiff->SetTag ( ifd, mapInfo.id, kTIFF_ShortType, (XMP_Uns32)arraySize, &shortVector[0] );
+
+ } else if ( mapInfo.type == kTIFF_RationalType ) {
+
+ std::vector<XMP_Uns32> rationalVector;
+ rationalVector.assign ( 2*arraySize, 0 );
+ XMP_Uns32 * rationalPtr = (XMP_Uns32*) &rationalVector[0];
+
+ std::string itemPath, xmpValue;
+ XMP_Uns32 num, denom;
+ for ( size_t i = 1; i <= arraySize; ++i, rationalPtr += 2 ) {
+ SXMPUtils::ComposeArrayItemPath ( xmpNS, xmpArray, (XMP_Index)i, &itemPath );
+ xmp.GetProperty ( xmpNS, itemPath.c_str(), &xmpValue, 0 );
+ bool ok = DecodeRational ( xmpValue.c_str(), &num, &denom );
+ if ( ! ok ) return;
+ if ( ! nativeEndian ) { num = Flip4 ( num ); denom = Flip4 ( denom ); }
+ rationalPtr[0] = num;
+ rationalPtr[1] = denom;
+ }
+
+ tiff->SetTag ( ifd, mapInfo.id, kTIFF_RationalType, (XMP_Uns32)arraySize, &rationalVector[0] );
+
+ }
+
+} // ExportArrayTIFF
+
+// =================================================================================================
+// ExportTIFF_StandardMappings
+// ===========================
+
+static void
+ExportTIFF_StandardMappings ( XMP_Uns8 ifd, TIFF_Manager * tiff, const SXMPMeta & xmp )
+{
+ const bool nativeEndian = tiff->IsNativeEndian();
+ TIFF_Manager::TagInfo tagInfo;
+ std::string xmpValue;
+ XMP_OptionBits xmpForm;
+
+ const TIFF_MappingToXMP * mappings = 0;
+
+ if ( ifd == kTIFF_PrimaryIFD ) {
+ mappings = sPrimaryIFDMappings;
+ } else if ( ifd == kTIFF_ExifIFD ) {
+ mappings = sExifIFDMappings;
+ } else if ( ifd == kTIFF_GPSInfoIFD ) {
+ mappings = sGPSInfoIFDMappings;
+ } 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 ( mapInfo.ns, 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, mapInfo.ns, mapInfo.name );
+ }
+
+ }
+
+ } catch ( ... ) {
+
+ // Do nothing, let other imports proceed.
+ // ? Notify client?
+
+ }
+
+ }
+
+} // ExportTIFF_StandardMappings
+
+// =================================================================================================
+// ExportTIFF_Date
+// ===============
+//
+// Convert an XMP date/time to an Exif 2.2 master date/time tag plus associated fractional seconds.
+// The Exif date/time part is a 20 byte ASCII value formatted as "YYYY:MM:DD HH:MM:SS" with a
+// terminating nul. The fractional seconds are a nul terminated ASCII string with possible space
+// padding. They are literally the fractional part, the digits that would be to the right of the
+// decimal point.
+
+static void
+ExportTIFF_Date ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, TIFF_Manager * tiff, XMP_Uns16 mainID )
+{
+ 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;
+ }
+
+ try { // Don't let errors with one stop the others.
+
+ std::string xmpStr;
+ bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, &xmpStr, 0 );
+ if ( ! foundXMP ) {
+ tiff->DeleteTag ( mainIFD, mainID );
+ 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), "%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 );
+
+ } else {
+
+ 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 ( kTIFF_ExifIFD, fracID, buffer ); // ! The subseconds are always in the Exif IFD.
+
+ }
+
+ } catch ( ... ) {
+ // 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 | kXMPUtil_AllowCommas), &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
+// ======================
+
+static void
+ExportTIFF_LocTextASCII ( 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 xmpValue;
+
+ bool foundXMP = xmp.GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &xmpValue, 0 );
+ if ( ! foundXMP ) {
+ 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
+
+// =================================================================================================
+// ExportTIFF_EncodedString
+// ========================
+
+static void
+ExportTIFF_EncodedString ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp,
+ TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id, bool isLangAlt = false )
+{
+ 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 ( ! isLangAlt ) {
+ if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the tag?
+ } else {
+ if ( ! XMP_ArrayIsAltText ( xmpFlags ) ) return; // ? Complain? Delete the tag?
+ 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 ( (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
+
+// =================================================================================================
+// ExportTIFF_GPSCoordinate
+// ========================
+//
+// The XMP format is either "deg,min,secR" or "deg,min.fracR", where 'R' is the reference direction,
+// 'N', 'S', 'E', or 'W'. The location gets output as ( deg/1, min/1, sec/1 ) for the first form,
+// and ( deg/1, minFrac/denom, 0/1 ) for the second form.
+
+// ! We arbitrarily limit the number of fractional minute digits to 6 to avoid overflow in the
+// ! combined numerator. But we don't otherwise check for overflow or range errors.
+
+static void
+ExportTIFF_GPSCoordinate ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp,
+ TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 _id )
+{
+ 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. Tolerate ill-formed values where reasonable.
+
+ std::string xmpValue;
+ XMP_OptionBits xmpFlags;
+
+ bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, &xmpValue, &xmpFlags );
+ if ( ! foundXMP ) {
+ tiff->DeleteTag ( ifd, refID );
+ tiff->DeleteTag ( ifd, locID );
+ return;
+ }
+
+ if ( ! XMP_PropIsSimple ( xmpFlags ) ) return;
+
+ const char * chPtr = xmpValue.c_str(); // Guaranteed to have a nul terminator.
+
+ XMP_Uns32 deg=0, minNum=0, minDenom=1, sec=0;
+
+ // Extract the degree part, it must be present.
+
+ while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr;
+ if ( (*chPtr < '0') || (*chPtr > '9') ) return; // Bad XMP string.
+ for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) deg = deg*10 + (*chPtr - '0');
+ while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr;
+ if ( (*chPtr == ',') || (*chPtr == ';') ) ++chPtr;
+ while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr;
+
+ // Extract the whole minutes if present.
+
+ if ( ('0' <= *chPtr) && (*chPtr <= '9') ) {
+
+ for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) minNum = minNum*10 + (*chPtr - '0');
+
+ // Extract the fractional minutes or seconds if present.
+
+ if ( *chPtr == '.' ) {
+
+ ++chPtr; // Skip the period.
+ for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) {
+ if ( minDenom > 100*1000 ) continue; // Don't accumulate any more digits.
+ minDenom *= 10;
+ minNum = minNum*10 + (*chPtr - '0');
+ }
+
+ } else {
+
+ while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr;
+ if ( (*chPtr == ',') || (*chPtr == ';') ) ++chPtr;
+ while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr;
+ for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) sec = sec*10 + (*chPtr - '0');
+
+ }
+
+ }
+
+ // The compass direction part is required. But it isn't worth the bother to make sure it is
+ // appropriate for the tag. Little chance of ever seeing an error, it causes no great harm.
+
+ while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr;
+ if ( (*chPtr == ',') || (*chPtr == ';') ) ++chPtr; // Tolerate ',' or ';' here also.
+ while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr;
+
+ char ref[2];
+ ref[0] = *chPtr;
+ ref[1] = 0;
+
+ if ( ('a' <= ref[0]) && (ref[0] <= 'z') ) ref[0] -= 0x20;
+ if ( (ref[0] != 'N') && (ref[0] != 'S') && (ref[0] != 'E') && (ref[0] != 'W') ) return;
+
+ tiff->SetTag ( ifd, refID, kTIFF_ASCIIType, 2, &ref[0] );
+
+ XMP_Uns32 loc[6];
+ tiff->PutUns32 ( deg, &loc[0] );
+ tiff->PutUns32 ( 1, &loc[1] );
+ tiff->PutUns32 ( minNum, &loc[2] );
+ 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_GPSTimeStamp
+// =======================
+//
+// 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".
+
+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 )
+{
+
+ try { // Don't let errors with one stop the others.
+
+ 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 );
+
+ 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] );
+
+ 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 );
+ }
+
+ } catch ( ... ) {
+ // Do nothing, let other exports proceed.
+ // ? Notify client?
+ }
+
+} // ExportTIFF_GPSTimeStamp
+
+// =================================================================================================
+
+static void ExportTIFF_PhotographicSensitivity ( SXMPMeta * xmp, TIFF_Manager * exif ) {
+
+ // PhotographicSensitivity has special cases for values over 65534 because the tag is SHORT. It
+ // has a count of "any", but all known cameras used a count of 1. Exif 2.3 still allows a count
+ // of "any" but says only 1 value should ever be recorded. We only process the first value if
+ // the count is greater than 1.
+ //
+ // Prior to Exif 2.3 the tag was called ISOSpeedRatings. Some cameras omit the tag and some
+ // write 65535. Most put the real ISO in MakerNote, which be in the XMP from ACR.
+ //
+ // With Exif 2.3 five speed-related tags were added of type LONG: StandardOutputSensitivity,
+ // RecommendedExposureIndex, ISOSpeed, ISOSpeedLatitudeyyy, and ISOSpeedLatitudezzz. The tag
+ // SensitivityType was added to say which of these five is copied to PhotographicSensitivity.
+ //
+ // Export logic:
+ // If ExifVersion is less than "0230" (or missing)
+ // If exif:ISOSpeedRatings[1] <= 65535: inject tag (if missing), remove exif:ISOSpeedRatings
+ // Else (ExifVersion is at least "0230")
+ // If no exifEX:PhotographicSensitivity: set it from exif:ISOSpeedRatings[1]
+ // Remove exif:ISOSpeedRatings (to not be saved in namespace cleanup)
+ // If exifEX:PhotographicSensitivity <= 65535: inject tag (if missing)
+ // Else if exifEX:PhotographicSensitivity over 65535
+ // If no PhotographicSensitivity tag and no SensitivityType tag and no ISOSpeed tag:
+ // Inject PhotographicSensitivity tag as 65535
+ // If no exifEX:SensitivityType and no exifEX:ISOSpeed
+ // Set exifEX:SensitivityType to 3
+ // Set exifEX:ISOSpeed to exifEX:PhotographicSensitivity
+ // Inject SensitivityType and long tags (if missing)
+ // Save exif:ISOSpeedRatings when cleaning namespaces (done in ExportPhotoData)
+
+ try {
+
+ bool foundXMP, foundExif;
+ TIFF_Manager::TagInfo tagInfo;
+ std::string xmpValue;
+ XMP_OptionBits flags;
+ XMP_Int32 binValue;
+
+ bool haveOldExif = true; // Default to old Exif if no version tag.
+
+ foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, &tagInfo );
+ if ( foundExif && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) {
+ haveOldExif = (strncmp ( (char*)tagInfo.dataPtr, "0230", 4 ) < 0);
+ }
+
+ if ( haveOldExif ) { // Exif version is before 2.3, use just the old tag and property.
+
+ foundXMP = xmp->GetProperty ( kXMP_NS_EXIF, "ISOSpeedRatings", 0, &flags );
+ if ( foundXMP && XMP_PropIsArray(flags) && (xmp->CountArrayItems ( kXMP_NS_EXIF, "ISOSpeedRatings" ) > 0) ) {
+ foundXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "ISOSpeedRatings[1]", &binValue, 0 );
+ }
+
+ if ( ! foundXMP ) {
+ foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "PhotographicSensitivity", &binValue, 0 );
+ }
+
+ if ( foundXMP && (0 <= binValue) && (binValue <= 65535) ) {
+ xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" ); // So namespace cleanup won't keep it.
+ foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, &tagInfo );
+ if ( ! foundExif ) {
+ exif->SetTag_Short ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, (XMP_Uns16)binValue );
+ }
+ }
+
+ } else { // Exif version is 2.3 or newer, use the Exif 2.3 tags and properties.
+
+ // Deal with the special cases for exifEX:PhotographicSensitivity and exif:ISOSpeedRatings.
+
+ if ( ! xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "PhotographicSensitivity" ) ) {
+ foundXMP = xmp->GetProperty ( kXMP_NS_EXIF, "ISOSpeedRatings", 0, &flags );
+ if ( foundXMP && XMP_PropIsArray(flags) &&
+ (xmp->CountArrayItems ( kXMP_NS_EXIF, "ISOSpeedRatings" ) > 0) ) {
+ xmp->GetArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", 1, &xmpValue, 0 );
+ xmp->SetProperty ( kXMP_NS_ExifEX, "PhotographicSensitivity", xmpValue.c_str() );
+ }
+ }
+
+ xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" ); // Don't want it kept after namespace cleanup.
+
+ foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "PhotographicSensitivity", &binValue, 0 );
+
+ if ( foundXMP && (0 <= binValue) && (binValue <= 65535) ) { // The simpler low ISO case.
+
+ foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, &tagInfo );
+ if ( ! foundExif ) {
+ exif->SetTag_Short ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, (XMP_Uns16)binValue );
+ }
+
+ } else if ( foundXMP ) { // The more commplex high ISO case.
+
+ bool havePhotographicSensitivityTag = exif->GetTag ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, 0 );
+ bool haveSensitivityTypeTag = exif->GetTag ( kTIFF_ExifIFD, kTIFF_SensitivityType, 0 );
+ bool haveISOSpeedTag = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeed, 0 );
+
+ bool haveSensitivityTypeXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "SensitivityType" );
+ bool haveISOSpeedXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "ISOSpeed" );
+
+ if ( (! havePhotographicSensitivityTag) && (! haveSensitivityTypeTag) && (! haveISOSpeedTag) ) {
+
+ exif->SetTag_Short ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, 65535 );
+
+ if ( (! haveSensitivityTypeXMP) && (! haveISOSpeedXMP) ) {
+ xmp->SetProperty ( kXMP_NS_ExifEX, "SensitivityType", "3" );
+ xmp->SetProperty_Int ( kXMP_NS_ExifEX, "ISOSpeed", binValue );
+ }
+
+ }
+
+ }
+
+ // Export SensitivityType and the related long tags. Must be done after the special
+ // cases because they might set exifEX:SensitivityType and exifEX:ISOSpeed.
+
+ foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_SensitivityType, &tagInfo );
+ if ( ! foundExif ) {
+ foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "SensitivityType", &binValue, 0 );
+ if ( foundXMP && (0 <= binValue) && (binValue <= 65535) ) {
+ exif->SetTag_Short ( kTIFF_ExifIFD, kTIFF_SensitivityType, (XMP_Uns16)binValue );
+ }
+ }
+
+ foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_StandardOutputSensitivity, &tagInfo );
+ if ( ! foundExif ) {
+ foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "StandardOutputSensitivity", &binValue, 0 );
+ if ( foundXMP && (binValue >= 0) ) {
+ exif->SetTag_Long ( kTIFF_ExifIFD, kTIFF_StandardOutputSensitivity, (XMP_Uns32)binValue );
+ }
+ }
+
+ foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_RecommendedExposureIndex, &tagInfo );
+ if ( ! foundExif ) {
+ foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "RecommendedExposureIndex", &binValue, 0 );
+ if ( foundXMP && (binValue >= 0) ) {
+ exif->SetTag_Long ( kTIFF_ExifIFD, kTIFF_RecommendedExposureIndex, (XMP_Uns32)binValue );
+ }
+ }
+
+ foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeed, &tagInfo );
+ if ( ! foundExif ) {
+ foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "ISOSpeed", &binValue, 0 );
+ if ( foundXMP && (binValue >= 0) ) {
+ exif->SetTag_Long ( kTIFF_ExifIFD, kTIFF_ISOSpeed, (XMP_Uns32)binValue );
+ }
+ }
+
+ foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudeyyy, &tagInfo );
+ if ( ! foundExif ) {
+ foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "ISOSpeedLatitudeyyy", &binValue, 0 );
+ if ( foundXMP && (binValue >= 0) ) {
+ exif->SetTag_Long ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudeyyy, (XMP_Uns32)binValue );
+ }
+ }
+
+ foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudezzz, &tagInfo );
+ if ( ! foundExif ) {
+ foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "ISOSpeedLatitudezzz", &binValue, 0 );
+ if ( foundXMP && (binValue >= 0) ) {
+ exif->SetTag_Long ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudezzz, (XMP_Uns32)binValue );
+ }
+ }
+
+ }
+
+ } catch ( ... ) {
+
+ // Do nothing, don't let this failure stop other exports.
+
+ }
+
+} // ExportTIFF_PhotographicSensitivity
+
+// =================================================================================================
+// =================================================================================================
+
+// =================================================================================================
+// PhotoDataUtils::ExportExif
+// ==========================
+
+void
+PhotoDataUtils::ExportExif ( SXMPMeta * xmp, TIFF_Manager * exif )
+{
+ bool haveXMP;
+ std::string xmpValue;
+
+ XMP_Int32 int32;
+ XMP_Uns8 uns8;
+
+ // ---------------------------------------------------------
+ // Read the old Adobe names for new Exif 2.3 tags if wanted.
+
+ #if SupportOldExifProperties
+
+ XMP_OptionBits flags;
+
+ haveXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "PhotographicSensitivity" );
+ if ( ! haveXMP ) {
+ haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "ISOSpeedRatings", 0, &flags );
+ if ( haveXMP && XMP_PropIsArray(flags) ) {
+ haveXMP = xmp->GetArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", 1, &xmpValue, 0 );
+ if ( haveXMP ) xmp->SetProperty ( kXMP_NS_ExifEX, "PhotographicSensitivity", xmpValue.c_str() );
+ }
+ }
+
+ haveXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "CameraOwnerName" );
+ if ( ! haveXMP ) {
+ haveXMP = xmp->GetProperty ( kXMP_NS_EXIF_Aux, "OwnerName", &xmpValue, 0 );
+ if ( haveXMP ) xmp->SetProperty ( kXMP_NS_ExifEX, "CameraOwnerName", xmpValue.c_str() );
+ }
+
+ haveXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "BodySerialNumber" );
+ if ( ! haveXMP ) {
+ haveXMP = xmp->GetProperty ( kXMP_NS_EXIF_Aux, "SerialNumber", &xmpValue, 0 );
+ if ( haveXMP ) xmp->SetProperty ( kXMP_NS_ExifEX, "BodySerialNumber", xmpValue.c_str() );
+ }
+
+ haveXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "LensModel" );
+ if ( ! haveXMP ) {
+ haveXMP = xmp->GetProperty ( kXMP_NS_EXIF_Aux, "Lens", &xmpValue, 0 );
+ if ( haveXMP ) xmp->SetProperty ( kXMP_NS_ExifEX, "LensModel", xmpValue.c_str() );
+ }
+
+ haveXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "LensSpecification" );
+ if ( ! haveXMP ) {
+ haveXMP = xmp->GetProperty ( kXMP_NS_EXIF_Aux, "LensInfo", &xmpValue, 0 );
+ if ( haveXMP ) {
+ size_t start, end;
+ std::string nextItem;
+ for ( start = 0; start < xmpValue.size(); start = end + 1 ) {
+ end = xmpValue.find ( ' ', start );
+ if ( end == start ) continue;
+ if ( end == std::string::npos ) end = xmpValue.size();
+ nextItem = xmpValue.substr ( start, (end-start) );
+ xmp->AppendArrayItem ( kXMP_NS_ExifEX, "LensSpecification", kXMP_PropArrayIsOrdered, nextItem.c_str() );
+ }
+ }
+ }
+
+ #endif
+
+ // 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 );
+
+ // --------------------------------------------------------------------------------------------
+ // Fixup erroneous cases that appear to have a negative value for GPSAltitude in the Exif. This
+ // treats any value with the high bit set as a negative, which is more likely in practice than
+ // an actual value over 2 billion. The XMP was exported by the tables and is left alone since it
+ // won't be kept in the file.
+
+ TIFF_Manager::Rational altValue;
+ bool haveExif = exif->GetTag_Rational ( kTIFF_GPSInfoIFD, kTIFF_GPSAltitude, &altValue );
+ if ( haveExif ) {
+
+ bool fixExif = false;
+
+ if ( altValue.denom >> 31 ) { // Shift the sign to the numerator.
+ altValue.denom = -altValue.denom;
+ altValue.num = -altValue.num;
+ fixExif = true;
+ }
+
+ if ( altValue.num >> 31 ) { // Fix the numerator and set GPSAltitudeRef.
+ exif->SetTag_Byte ( kTIFF_GPSInfoIFD, kTIFF_GPSAltitudeRef, 1 );
+ altValue.num = -altValue.num;
+ fixExif = true;
+ }
+
+ if ( fixExif ) exif->SetTag_Rational ( kTIFF_GPSInfoIFD, kTIFF_GPSAltitude, altValue.num, altValue.denom );
+
+ }
+
+ // 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 ( exif->GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeDigitized, 0 ) ) {
+ ExportTIFF_Date ( *xmp, kXMP_NS_XMP, "CreateDate", exif, kTIFF_DateTimeDigitized );
+ }
+
+ ExportTIFF_Date ( *xmp, kXMP_NS_EXIF, "DateTimeOriginal", exif, kTIFF_DateTimeOriginal );
+ ExportTIFF_Date ( *xmp, kXMP_NS_XMP, "ModifyDate", exif, kTIFF_DateTime );
+
+ // Export the remaining TIFF, Exif, and GPS IFD tags.
+
+ ExportTIFF_ArrayASCII ( *xmp, kXMP_NS_DC, "creator", exif, kTIFF_PrimaryIFD, kTIFF_Artist );
+
+ 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() );
+ }
+
+ // There are moderately complex export special cases for PhotographicSensitivity.
+ ExportTIFF_PhotographicSensitivity ( xmp, exif );
+
+ 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
+
+ haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "GPSVersionID", &xmpValue, 0 ); // This is inject-only.
+ if ( haveXMP && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, 0 )) ) {
+ XMP_Uns8 gpsID[4]; // 0 GPSVersionID is 4 UInt8 bytes and mapped in XMP as "n.n.n.n".
+ unsigned int fields[4]; // ! Need ints for output from sscanf.
+ int count = sscanf ( xmpValue.c_str(), "%u.%u.%u.%u", &fields[0], &fields[1], &fields[2], &fields[3] );
+ if ( (count == 4) && (fields[0] <= 255) && (fields[1] <= 255) && (fields[2] <= 255) && (fields[3] <= 255) ) {
+ gpsID[0] = fields[0]; gpsID[1] = fields[1]; gpsID[2] = fields[2]; gpsID[3] = fields[3];
+ 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 );
+
+ // 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 );
+ }
+
+ 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 );
+ }
+
+ 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/XMPFiles/source/FormatSupport/Reconcile_Impl.cpp b/XMPFiles/source/FormatSupport/Reconcile_Impl.cpp
new file mode 100644
index 0000000..9dcef3d
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/Reconcile_Impl.cpp
@@ -0,0 +1,431 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+#include "source/UnicodeConversions.hpp"
+#include "source/XIO.hpp"
+
+#if XMP_WinBuild
+#elif XMP_MacBuild
+ #include <CoreServices/CoreServices.h>
+#endif
+
+// =================================================================================================
+/// \file Reconcile_Impl.cpp
+/// \brief Implementation utilities for the photo metadata reconciliation support.
+///
+// =================================================================================================
+
+// =================================================================================================
+// ReconcileUtils::IsASCII
+// =======================
+//
+// See if a string is 7 bit ASCII.
+
+bool ReconcileUtils::IsASCII ( const void * textPtr, size_t textLen )
+{
+
+ for ( const XMP_Uns8 * textPos = (XMP_Uns8*)textPtr; textLen > 0; --textLen, ++textPos ) {
+ if ( *textPos >= 0x80 ) return false;
+ }
+
+ return true;
+
+} // ReconcileUtils::IsASCII
+
+// =================================================================================================
+// ReconcileUtils::IsUTF8
+// ======================
+//
+// See if a string contains valid UTF-8. Allow nul bytes, they can appear inside of multi-part Exif
+// 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 * textPtr, size_t textLen )
+{
+ const XMP_Uns8 * textPos = (XMP_Uns8*)textPtr;
+ const XMP_Uns8 * textEnd = textPos + textLen;
+
+ while ( textPos < textEnd ) {
+
+ if ( *textPos < 0x80 ) {
+
+ ++textPos; // ASCII is UTF-8, tolerate nuls.
+
+ } else {
+
+ // -------------------------------------------------------------------------------------
+ // We've got a multibyte UTF-8 character. The first byte has the number of bytes as the
+ // number of high order 1 bits. The remaining bytes must have 1 and 0 as the top 2 bits.
+
+ #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 [ *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 = *textPos; temp > 0x7F; temp = temp << 1 ) ++bytesNeeded;
+ // *** Consider CPU-specific assembly inline, e.g. cntlzw on PowerPC.
+
+ if ( (bytesNeeded < 2) || (bytesNeeded > 4) || ((textPos+bytesNeeded) > textEnd) ) return false;
+
+ for ( --bytesNeeded, ++textPos; bytesNeeded > 0; --bytesNeeded, ++textPos ) {
+ if ( (*textPos >> 6) != 2 ) return false;
+ }
+
+ }
+
+ }
+
+ return true; // ! Returns true for empty strings.
+
+} // ReconcileUtils::IsUTF8
+
+// =================================================================================================
+// UTF8ToHostEncoding
+// ==================
+
+#if XMP_WinBuild
+
+ void ReconcileUtils::UTF8ToWinEncoding ( UINT codePage, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host )
+ {
+
+ std::string utf16; // WideCharToMultiByte wants native UTF-16.
+ ToUTF16Native ( (UTF8Unit*)utf8Ptr, utf8Len, &utf16 );
+
+ LPCWSTR utf16Ptr = (LPCWSTR) utf16.c_str();
+ size_t utf16Len = utf16.size() / 2;
+
+ int hostLen = WideCharToMultiByte ( codePage, 0, utf16Ptr, (int)utf16Len, 0, 0, 0, 0 );
+ host->assign ( hostLen, ' ' ); // Allocate space for the results.
+
+ (void) WideCharToMultiByte ( codePage, 0, utf16Ptr, (int)utf16Len, (LPSTR)host->data(), hostLen, 0, 0 );
+ XMP_Assert ( hostLen == host->size() );
+
+ } // UTF8ToWinEncoding
+
+#elif XMP_MacBuild
+
+ 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 );
+ mappingInfo.unicodeEncoding = CreateTextEncoding ( kTextEncodingUnicodeDefault,
+ kUnicodeNoSubset, kUnicodeUTF8Format );
+
+ UnicodeToTextInfo converterInfo;
+ err = CreateUnicodeToTextInfo ( &mappingInfo, &converterInfo );
+ if ( err != noErr ) XMP_Throw ( "CreateUnicodeToTextInfo failed", kXMPErr_ExternalFailure );
+
+ try { // ! Need to call DisposeUnicodeToTextInfo before exiting.
+
+ OptionBits convFlags = kUnicodeUseFallbacksMask |
+ kUnicodeLooseMappingsMask | kUnicodeDefaultDirectionMask;
+ ByteCount bytesRead, bytesWritten;
+
+ enum { kBufferLen = 1000 }; // Ought to be enough in practice, without using too much stack.
+ char buffer [kBufferLen];
+
+ host->reserve ( utf8Len ); // As good a guess as any.
+
+ while ( utf8Len > 0 ) {
+ // Ignore all errors from ConvertFromUnicodeToText. It returns info like "output
+ // buffer full" or "use substitution" as errors.
+ err = ConvertFromUnicodeToText ( converterInfo, utf8Len, (UniChar*)utf8Ptr, convFlags,
+ 0, 0, 0, 0, kBufferLen, &bytesRead, &bytesWritten, buffer );
+ if ( bytesRead == 0 ) break; // Make sure forward progress happens.
+ host->append ( &buffer[0], bytesWritten );
+ utf8Ptr += bytesRead;
+ utf8Len -= bytesRead;
+ }
+
+ DisposeUnicodeToTextInfo ( &converterInfo );
+
+ } catch ( ... ) {
+
+ DisposeUnicodeToTextInfo ( &converterInfo );
+ throw;
+
+ }
+
+ } // UTF8ToMacEncoding
+
+#elif XMP_UNIXBuild
+
+ // ! Does not exist, must not be called, for Generic UNIX builds.
+
+#endif
+
+// =================================================================================================
+// ReconcileUtils::UTF8ToLocal
+// ===========================
+
+void ReconcileUtils::UTF8ToLocal ( const void * _utf8Ptr, size_t utf8Len, std::string * local )
+{
+ const XMP_Uns8* utf8Ptr = (XMP_Uns8*)_utf8Ptr;
+
+ local->erase();
+
+ if ( ReconcileUtils::IsASCII ( utf8Ptr, utf8Len ) ) {
+ local->assign ( (const char *)utf8Ptr, utf8Len );
+ return;
+ }
+
+ #if XMP_WinBuild
+
+ UTF8ToWinEncoding ( CP_ACP, utf8Ptr, utf8Len, local );
+
+ #elif XMP_MacBuild
+
+ UTF8ToMacEncoding ( smSystemScript, kTextLanguageDontCare, utf8Ptr, utf8Len, local );
+
+ #elif XMP_UNIXBuild
+
+ XMP_Throw ( "Generic UNIX does not have conversions between local and Unicode", kXMPErr_Unavailable );
+
+ #endif
+
+} // ReconcileUtils::UTF8ToLocal
+
+// =================================================================================================
+// ReconcileUtils::UTF8ToLatin1
+// ============================
+
+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.
+
+ bool inBadRun = false;
+
+ while ( utf8Ptr < utf8End ) {
+
+ 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;
+
+ }
+
+ }
+
+ }
+
+ XMP_Assert ( utf8Ptr == utf8End );
+
+} // ReconcileUtils::UTF8ToLatin1
+
+// =================================================================================================
+// HostEncodingToUTF8
+// ==================
+
+#if XMP_WinBuild
+
+ 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 );
+ std::vector<UTF16Unit> utf16 ( utf16Len, 0 ); // MultiByteToWideChar returns native UTF-16.
+
+ (void) MultiByteToWideChar ( codePage, 0, (LPCSTR)hostPtr, (int)hostLen, (LPWSTR)&utf16[0], utf16Len );
+ FromUTF16Native ( &utf16[0], (int)utf16Len, utf8 );
+
+ } // WinEncodingToUTF8
+
+#elif XMP_MacBuild
+
+ 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 );
+ mappingInfo.unicodeEncoding = CreateTextEncoding ( kTextEncodingUnicodeDefault,
+ kUnicodeNoSubset, kUnicodeUTF8Format );
+
+ TextToUnicodeInfo converterInfo;
+ err = CreateTextToUnicodeInfo ( &mappingInfo, &converterInfo );
+ if ( err != noErr ) XMP_Throw ( "CreateTextToUnicodeInfo failed", kXMPErr_ExternalFailure );
+
+ try { // ! Need to call DisposeTextToUnicodeInfo before exiting.
+
+ ByteCount bytesRead, bytesWritten;
+
+ enum { kBufferLen = 1000 }; // Ought to be enough in practice, without using too much stack.
+ char buffer [kBufferLen];
+
+ utf8->reserve ( hostLen ); // As good a guess as any.
+
+ while ( hostLen > 0 ) {
+ // Ignore all errors from ConvertFromTextToUnicode. It returns info like "output
+ // buffer full" or "use substitution" as errors.
+ err = ConvertFromTextToUnicode ( converterInfo, hostLen, hostPtr, kNilOptions,
+ 0, 0, 0, 0, kBufferLen, &bytesRead, &bytesWritten, (UniChar*)buffer );
+ if ( bytesRead == 0 ) break; // Make sure forward progress happens.
+ utf8->append ( &buffer[0], bytesWritten );
+ hostPtr += bytesRead;
+ hostLen -= bytesRead;
+ }
+
+ DisposeTextToUnicodeInfo ( &converterInfo );
+
+ } catch ( ... ) {
+
+ DisposeTextToUnicodeInfo ( &converterInfo );
+ throw;
+
+ }
+
+ } // MacEncodingToUTF8
+
+#elif XMP_UNIXBuild
+
+ // ! Does not exist, must not be called, for Generic UNIX builds.
+
+#endif
+
+// =================================================================================================
+// ReconcileUtils::LocalToUTF8
+// ===========================
+
+void ReconcileUtils::LocalToUTF8 ( const void * _localPtr, size_t localLen, std::string * utf8 )
+{
+ const XMP_Uns8* localPtr = (XMP_Uns8*)_localPtr;
+
+ utf8->erase();
+
+ if ( ReconcileUtils::IsASCII ( localPtr, localLen ) ) {
+ utf8->assign ( (const char *)localPtr, localLen );
+ return;
+ }
+
+ #if XMP_WinBuild
+
+ WinEncodingToUTF8 ( CP_ACP, localPtr, localLen, utf8 );
+
+ #elif XMP_MacBuild
+
+ MacEncodingToUTF8 ( smSystemScript, kTextLanguageDontCare, localPtr, localLen, utf8 );
+
+ #elif XMP_UNIXBuild
+
+ XMP_Throw ( "Generic UNIX does not have conversions between local and Unicode", kXMPErr_Unavailable );
+
+ #endif
+
+} // ReconcileUtils::LocalToUTF8
+
+// =================================================================================================
+// ReconcileUtils::Latin1ToUTF8
+// ============================
+
+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.
+
+ for ( ; latin1Ptr < latin1End; ++latin1Ptr ) {
+
+ XMP_Uns8 ch8 = *latin1Ptr;
+
+ 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);
+ }
+
+ }
+
+} // ReconcileUtils::Latin1ToUTF8
+
+
+// =================================================================================================
+// ReconcileUtils::NativeToUTF8
+// ============================
+
+void ReconcileUtils::NativeToUTF8( const std::string & input, std::string & output )
+{
+ output.erase();
+ // IF it is not UTF-8
+ if( ! ReconcileUtils::IsUTF8( input.c_str(), input.length() ) )
+ {
+ // And ServerMode is not active
+ if( ! ignoreLocalText )
+ {
+ // Convert it to UTF-8
+ ReconcileUtils::LocalToUTF8( input.c_str(), input.length(), &output );
+ }
+ }
+ else // If it is already UTF-8
+ {
+ output = input;
+ }
+} // ReconcileUtils::NativeToUTF8
diff --git a/XMPFiles/source/FormatSupport/Reconcile_Impl.hpp b/XMPFiles/source/FormatSupport/Reconcile_Impl.hpp
new file mode 100644
index 0000000..ea5d407
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/Reconcile_Impl.hpp
@@ -0,0 +1,104 @@
+#ifndef __Reconcile_Impl_hpp__
+#define __Reconcile_Impl_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp"
+#include "third-party/zuid/interfaces/MD5.h"
+
+// =================================================================================================
+/// \file Reconcile_Impl.hpp
+/// \brief Implementation utilities for the legacy metadata reconciliation support.
+///
+// =================================================================================================
+
+typedef XMP_Uns8 MD5_Digest[16]; // ! Should be in MD5.h.
+
+enum {
+ kDigestMissing = -1, // A partial import is done, existing XMP is left alone.
+ kDigestDiffers = 0, // A full import is done, existing XMP is deleted or replaced.
+ kDigestMatches = +1 // No importing is done.
+};
+
+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 );
+
+ //
+ // Checks if the input string is UTF-8 encoded. If not, it tries to convert it to UTF-8
+ // This is only done, if Server Mode is not active!
+ // @param input the native input string
+ // @return The input if it is already UTF-8, the converted input
+ // or an empty string if no conversion is possible because of ServerMode
+ //
+ void NativeToUTF8 ( const std::string & input, std::string & output );
+
+ #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
+
+}; // ReconcileUtils
+
+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 );
+
+ 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 ExportIPTC ( const SXMPMeta & xmp, IPTC_Manager * iptc );
+ void ExportExif ( SXMPMeta * xmp, TIFF_Manager * exif );
+
+ // These need to be exposed for use in Import3WayItem:
+
+ 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/XMPFiles/source/FormatSupport/SWF_Support.cpp b/XMPFiles/source/FormatSupport/SWF_Support.cpp
new file mode 100644
index 0000000..ec7b9a6
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/SWF_Support.cpp
@@ -0,0 +1,484 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FormatSupport/SWF_Support.hpp"
+
+#include "third-party/zlib/zlib.h"
+
+// =================================================================================================
+
+XMP_Uns32 SWF_IO::FileHeaderSize ( XMP_Uns8 rectBits ) {
+
+ // Return the full size of the SWF header, adding the fixed size to the variable RECT size.
+
+ XMP_Uns8 bitsPerField = rectBits >> 3;
+ XMP_Uns32 rectBytes = ((5 + (4 * bitsPerField)) / 8) + 1;
+
+ return SWF_IO::HeaderFixedSize + rectBytes;
+
+} // SWF_IO::FileHeaderSize
+
+// =================================================================================================
+
+bool SWF_IO::GetTagInfo ( const RawDataBlock & swfStream, XMP_Uns32 tagOffset, SWF_IO::TagInfo * info ) {
+
+ if ( tagOffset >= swfStream.size() ) return false;
+ XMP_Uns32 spaceLeft = swfStream.size() - tagOffset;
+ XMP_Uns8 headerSize = 2;
+ if ( spaceLeft < headerSize ) return false; // The minimum empty tag is a 2 byte header.
+
+ XMP_Uns16 tagHeader = GetUns16LE ( &swfStream[tagOffset] );
+
+ info->tagID = tagHeader >> 6;
+ info->tagOffset = tagOffset;
+ info->contentLength = tagHeader & SWF_IO::TagLengthMask;
+
+ if ( info->contentLength != SWF_IO::TagLengthMask ) {
+ info->hasLongHeader = false;
+ } else {
+ headerSize = 6;
+ if ( spaceLeft < headerSize ) return false; // Make sure there is room for the extended length.
+ info->contentLength = GetUns32LE ( &swfStream[tagOffset+2] );
+ info->hasLongHeader = true;
+ }
+
+ if ( (spaceLeft - headerSize) < info->contentLength ) return false;
+
+ return true;
+
+} // SWF_IO::GetTagInfo
+
+// =================================================================================================
+
+static inline XMP_Uns32 TagHeaderSize ( const SWF_IO::TagInfo & info ) {
+
+ XMP_Uns8 headerSize = 2;
+ if ( info.hasLongHeader ) headerSize = 6;
+ return headerSize;
+
+} // TagHeaderSize
+
+// =================================================================================================
+
+XMP_Uns32 SWF_IO::FullTagLength ( const SWF_IO::TagInfo & info ) {
+
+ return TagHeaderSize ( info ) + info.contentLength;
+
+} // SWF_IO::FullTagLength
+
+// =================================================================================================
+
+XMP_Uns32 SWF_IO::ContentOffset ( const SWF_IO::TagInfo & info ) {
+
+ return info.tagOffset + TagHeaderSize ( info );
+
+} // SWF_IO::ContentOffset
+
+// =================================================================================================
+
+XMP_Uns32 SWF_IO::NextTagOffset ( const SWF_IO::TagInfo & info ) {
+
+ return info.tagOffset + FullTagLength ( info );
+
+} // SWF_IO::NextTagOffset
+
+// =================================================================================================
+
+static inline void AppendData ( RawDataBlock * dataOut, XMP_Uns8 * buffer, size_t count ) {
+
+ size_t prevSize = dataOut->size(); // ! Don't save a pointer, there might be a reallocation.
+ dataOut->insert ( dataOut->end(), count, 0 ); // Add space to the RawDataBlock.
+ memcpy ( &((*dataOut)[prevSize]), buffer, count );
+
+} // AppendData
+
+// =================================================================================================
+
+XMP_Int64 SWF_IO::DecompressFileToMemory ( XMP_IO * fileIn, RawDataBlock * dataOut ) {
+
+ fileIn->Rewind();
+ dataOut->clear();
+
+ static const size_t bufferSize = 64*1024;
+ XMP_Uns8 bufferIn [ bufferSize ];
+ XMP_Uns8 bufferOut [ bufferSize ];
+
+ int err;
+ z_stream zipState;
+ memset ( &zipState, 0, sizeof(zipState) );
+ err = inflateInit ( &zipState );
+ XMP_Enforce ( err == Z_OK );
+
+ XMP_Int32 ioCount;
+ XMP_Int64 offsetIn;
+ const XMP_Int64 lengthIn = fileIn->Length();
+ XMP_Enforce ( (SWF_IO::HeaderPrefixSize <= lengthIn) && (lengthIn <= SWF_IO::MaxExpandedSize) );
+
+ // Set the uncompressed part of the header. Save the expanded size from the file.
+
+ fileIn->ReadAll ( bufferIn, SWF_IO::HeaderPrefixSize );
+ offsetIn = SWF_IO::HeaderPrefixSize;
+ XMP_Uns32 expectedFullSize = GetUns32LE ( &bufferIn[4] );
+
+ AppendData ( dataOut, bufferIn, SWF_IO::HeaderPrefixSize ); // Copy the compressed stream's prefix.
+ PutUns32LE ( SWF_IO::ExpandedSignature, &(*dataOut)[0] ); // Change the signature.
+ (*dataOut)[3] = bufferIn[3]; // Keep the SWF version.
+
+ // Read the input file, feed it to the decompression engine, writing as needed.
+
+ zipState.next_out = &bufferOut[0]; // Initial output conditions. Must be set before the input loop!
+ zipState.avail_out = bufferSize;
+
+ while ( offsetIn < lengthIn ) {
+
+ // Read the next chunk of input.
+ ioCount = fileIn->Read ( bufferIn, bufferSize );
+ XMP_Enforce ( ioCount > 0 );
+ offsetIn += ioCount;
+ zipState.next_in = &bufferIn[0];
+ zipState.avail_in = ioCount;
+
+ // Process all of this input, writing as needed.
+
+ err = Z_OK;
+ while ( (zipState.avail_in > 0) && (err == Z_OK) ) {
+
+ XMP_Assert ( zipState.avail_out > 0 ); // Sanity check for output buffer space.
+ err = inflate ( &zipState, Z_NO_FLUSH );
+ XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) );
+
+ if ( zipState.avail_out == 0 ) {
+ AppendData ( dataOut, bufferOut, bufferSize );
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+ }
+
+ }
+
+ }
+
+ // Finish the decompression and write the final output.
+
+ do {
+
+ ioCount = bufferSize - zipState.avail_out; // Make sure there is room for inflate to do more.
+ if ( ioCount > 0 ) {
+ AppendData ( dataOut, bufferOut, ioCount );
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+ }
+
+ err = inflate ( &zipState, Z_NO_FLUSH );
+ XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) || (err == Z_BUF_ERROR) );
+
+ } while ( err == Z_OK );
+
+ ioCount = bufferSize - zipState.avail_out; // Write any final output.
+ if ( ioCount > 0 ) {
+ AppendData ( dataOut, bufferOut, ioCount );
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+ }
+
+ // Done. Make sure the file header has the true decompressed size.
+
+ XMP_Int64 lengthOut = zipState.total_out + SWF_IO::HeaderPrefixSize;
+ if ( lengthOut != expectedFullSize ) PutUns32LE ( (XMP_Uns32)lengthOut, &((*dataOut)[4]) );
+ inflateEnd ( &zipState );
+ return lengthOut;
+
+} // SWF_IO::DecompressFileToMemory
+
+// =================================================================================================
+
+XMP_Int64 SWF_IO::CompressMemoryToFile ( const RawDataBlock & dataIn, XMP_IO* fileOut ) {
+
+ fileOut->Rewind();
+ fileOut->Truncate ( 0 );
+
+ static const size_t bufferSize = 64*1024;
+ XMP_Uns8 bufferOut [ bufferSize ];
+
+ int err;
+ z_stream zipState;
+ memset ( &zipState, 0, sizeof(zipState) );
+ err = deflateInit ( &zipState, Z_DEFAULT_COMPRESSION );
+ XMP_Enforce ( err == Z_OK );
+
+ XMP_Int32 ioCount;
+ const size_t lengthIn = dataIn.size();
+ XMP_Enforce ( SWF_IO::HeaderPrefixSize <= lengthIn );
+
+ // Write the uncompressed part of the file header.
+
+ PutUns32LE ( SWF_IO::CompressedSignature, &bufferOut[0] );
+ bufferOut[3] = dataIn[3]; // Copy the SWF version.
+ PutUns32LE ( lengthIn, &bufferOut[4] );
+ fileOut->Write ( bufferOut, SWF_IO::HeaderPrefixSize );
+
+ // Feed the input to the compression engine in one step, write the output as available.
+
+ zipState.next_in = (Bytef*)&dataIn[SWF_IO::HeaderPrefixSize];
+ zipState.avail_in = lengthIn - SWF_IO::HeaderPrefixSize;
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+
+ while ( zipState.avail_in > 0 ) {
+
+ XMP_Assert ( zipState.avail_out > 0 ); // Sanity check for output buffer space.
+ err = deflate ( &zipState, Z_NO_FLUSH );
+ XMP_Enforce ( err == Z_OK );
+
+ if ( zipState.avail_out == 0 ) {
+ fileOut->Write ( bufferOut, bufferSize );
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+ }
+
+ }
+
+ // Finish the compression and write the final output.
+
+ do {
+
+ err = deflate ( &zipState, Z_FINISH );
+ XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) );
+ ioCount = bufferSize - zipState.avail_out; // See if there is output to write.
+
+ if ( ioCount > 0 ) {
+ fileOut->Write ( bufferOut, ioCount );
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+ }
+
+ } while ( err != Z_STREAM_END );
+
+ // Done.
+
+ XMP_Int64 lengthOut = zipState.total_out;
+ deflateEnd ( &zipState );
+ return lengthOut;
+
+} // SWF_IO::CompressMemoryToFile
+
+// =================================================================================================
+
+#if 0 // ! Not used, but save it for later transfer to a general ZIP utility file.
+
+XMP_Int64 SWF_IO::DecompressFileToFile ( XMP_IO * fileIn, XMP_IO * fileOut ) {
+
+ fileIn->Rewind();
+ fileOut->Rewind();
+ fileOut->Truncate ( 0 );
+
+ static const size_t bufferSize = 64*1024;
+ XMP_Uns8 bufferIn [ bufferSize ];
+ XMP_Uns8 bufferOut [ bufferSize ];
+
+ int err;
+ z_stream zipState;
+ memset ( zipState, 0, sizeof(zipState) );
+ err = inflateInit ( &zipState );
+ XMP_Enforce ( err == Z_OK );
+
+ XMP_Int32 ioCount;
+ XMP_Int64 offsetIn;
+ const XMP_Int64 lengthIn = fileIn->Length();
+ XMP_Enforce ( (lengthIn >= SWF_IO::HeaderPrefixSize) && (lengthIn <= SWF_IO::MaxExpandedSize) );
+
+ // Copy the uncompressed part of the file header. Save the expanded size from the header.
+
+ fileIn->ReadAll ( bufferIn, SWF_IO::HeaderPrefixSize );
+ fileOut.Write ( bufferIn, SWF_IO::HeaderPrefixSize );
+ offsetIn = SWF_IO::HeaderPrefixSize;
+
+ XMP_Uns32 expectedFullSize = GetUns32LE ( &bufferIn[4] );
+
+ // Read the input file, feed it to the decompression engine, writing as needed.
+
+ zipState.next_out = &bufferOut[0]; // Initial output conditions. Must be set before the input loop!
+ zipState.avail_out = bufferSize;
+
+ while ( offsetIn < lengthIn ) {
+
+ // Read the next chunk of input.
+ ioCount = fileIn->Read ( bufferIn, bufferSize );
+ XMP_Enforce ( ioCount > 0 );
+ offsetIn += ioCount;
+ zipState.next_in = &bufferIn[0];
+ zipState.avail_in = ioCount;
+
+ // Process all of this input, writing as needed.
+
+ err = Z_OK;
+ while ( (zipState.avail_in > 0) && (err == Z_OK) ) {
+
+ XMP_Assert ( zipState.avail_out > 0 ); // Sanity check for output buffer space.
+ err = inflate ( &zipState, Z_NO_FLUSH );
+ XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) );
+
+ if ( zipState.avail_out == 0 ) {
+ fileOut->write ( bufferOut, bufferSize );
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+ }
+
+ }
+
+ }
+
+ // Finish the decompression and write the final output.
+
+ do {
+
+ ioCount = bufferSize - zipState.avail_out; // Make sure there is room for inflate.
+ if ( ioCount > 0 ) {
+ fileOut->write ( bufferOut, ioCount );
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+ }
+
+ err = inflate ( &zipState, Z_NO_FLUSH );
+ XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) || (err == Z_BUF_ERROR) );
+ ioCount = bufferSize - zipState.avail_out; // See if there is output to write.
+ if ( ioCount > 0 ) {
+ fileOut->write ( bufferOut, ioCount );
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+ }
+
+ } while ( err == Z_OK );
+
+ ioCount = bufferSize - zipState.avail_out; // Write any final output.
+ if ( ioCount > 0 ) {
+ fileOut->write ( bufferOut, ioCount );
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+ }
+
+ // Done. Make sure the file header has the true decompressed size.
+
+ XMP_Int64 lengthOut = zipState.total_out;
+
+ if ( lengthOut != expectedFullSize ) {
+ PutUns32LE ( &bufferOut[0], lengthOut );
+ fileOut->Seek ( 4, kXMP_SeekFromStart );
+ fileOut.Write ( &bufferOut[0], 4 );
+ fileOut->Seek ( 0, kXMP_SeekFromEnd );
+ }
+
+ inflateEnd ( &zipState );
+ return lengthOut;
+
+} // SWF_IO::DecompressFileToFile
+
+#endif
+
+// =================================================================================================
+
+#if 0 // ! Not used, but save it for later transfer to a general ZIP utility file.
+
+XMP_Int64 SWF_IO::CompressFileToFile ( XMP_IO * fileIn, XMP_IO * fileOut ) {
+
+ fileIn->Rewind();
+ fileOut->Rewind();
+ fileOut->Truncate ( 0 );
+
+ static const size_t bufferSize = 64*1024;
+ XMP_Uns8 bufferIn [ bufferSize ];
+ XMP_Uns8 bufferOut [ bufferSize ];
+
+ int err;
+ z_stream zipState;
+ memset ( zipState, 0, sizeof(zipState) );
+ err = deflateInit ( &zipState, Z_DEFAULT_COMPRESSION );
+ XMP_Enforce ( err == Z_OK );
+
+ XMP_Int32 ioCount;
+ XMP_Int64 offsetIn;
+ const XMP_Int64 lengthIn = fileIn->Length();
+ XMP_Enforce ( (lengthIn >= SWF_IO::HeaderPrefixSize) && (lengthIn <= SWF_IO::MaxExpandedSize) );
+
+ // Write the uncompressed part of the file header.
+
+ fileIn->ReadAll ( bufferIn, SWF_IO::HeaderPrefixSize );
+ offsetIn = SWF_IO::HeaderPrefixSize;
+
+ PutUns32LE ( SWF_IO::CompressedSignature, &bufferOut[0] );
+ bufferOut[3] = bufferIn[3]; // Copy the SWF version.
+ PutUns32LE ( &bufferOut[4], lengthIn );
+ fileOut.Write ( bufferOut, SWF_IO::HeaderPrefixSize );
+
+ // Read the input file, feed it to the compression engine, writing as needed.
+
+ zipState.next_out = &bufferOut[0]; // Initial output conditions. Must be set before the input loop!
+ zipState.avail_out = bufferSize;
+
+ while ( offsetIn < lengthIn ) {
+
+ // Read the next chunk of input.
+ ioCount = fileIn->Read ( bufferIn, bufferSize );
+ XMP_Enforce ( ioCount > 0 );
+ offsetIn += ioCount;
+ zipState.next_in = &bufferIn[0];
+ zipState.avail_in = ioCount;
+
+ // Process all of this input, writing as needed. Yes, we need a loop. Compression means less
+ // output than input, but a previous read has probably left partial compression results.
+
+ while ( zipState.avail_in > 0 ) {
+
+ XMP_Assert ( zipState.avail_out > 0 ); // Sanity check for output buffer space.
+ err = deflate ( &zipState, Z_NO_FLUSH );
+ XMP_Enforce ( err == Z_OK );
+
+ if ( zipState.avail_out == 0 ) {
+ fileOut->write ( bufferOut, bufferSize );
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+ }
+
+ }
+
+ }
+
+ // Finish the compression and write the final output.
+
+ do {
+
+ err = deflate ( &zipState, Z_FINISH );
+ XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) );
+ ioCount = bufferSize - zipState.avail_out; // See if there is output to write.
+
+ if ( ioCount > 0 ) {
+ fileOut->write ( bufferOut, ioCount );
+ zipState.next_out = &bufferOut[0];
+ zipState.avail_out = bufferSize;
+ }
+
+ } while ( err != Z_STREAM_END );
+
+ // Done.
+
+ XMP_Int64 lengthOut = zipState.total_out;
+ deflateEnd ( &zipState );
+ return lengthOut;
+
+} // SWF_IO::CompressFileToFile
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/SWF_Support.hpp b/XMPFiles/source/FormatSupport/SWF_Support.hpp
new file mode 100644
index 0000000..731d64e
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/SWF_Support.hpp
@@ -0,0 +1,86 @@
+#ifndef __SWF_Support_hpp__
+#define __SWF_Support_hpp__ 1
+
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+#include "third-party/zlib/zlib.h"
+
+// =================================================================================================
+
+namespace SWF_IO {
+
+ const XMP_Int64 MaxExpandedSize = 0xFFFFFFFFUL; // The file header has a UInt32 expanded size field.
+
+ const size_t HeaderPrefixSize = 8; // The uncompressed first part of the file header.
+ const size_t HeaderFixedSize = 12; // The fixed size part of the file header, omits the RECT.
+
+ const XMP_Uns32 CompressedSignature = 0x00535743; // The low 3 bytes are "SWC".
+ const XMP_Uns32 ExpandedSignature = 0x00535746; // The low 3 bytes are "SWF".
+ // Note: Can't use char* here, it causes duplicate symbols with xcode.
+
+ const XMP_Uns16 FileAttributesTagID = 69;
+ const XMP_Uns16 MetadataTagID = 77;
+
+ const XMP_Uns8 TagLengthMask = 0x3F;
+ const XMP_Uns8 HasMetadataMask = 0x10;
+
+ // A SWF file begins with a variable length header. The header layout is:
+ //
+ // UInt8[3] - "FWS" for uncompressed SWF and "CWS" for compressed SWF
+ // UInt8 - SWF format version
+ // UInt32 - Length of uncompressed file, little endian
+ // RECT - packed bit RECT structure
+ // UInt16 - frame rate, little endian, really 8.8 fixed point
+ // UInt16 - frame count, little endian
+ //
+ // If the first 4 bytes are read as a little endian UInt32 they become "vSWC" and "vSWF", where
+ // the "v" byte is the version format version.
+ //
+ // SWF compression starts 8 bytes into the file, after the length field in the header.
+ // The length in the header is everything. If compressed this is 8 plus the decompressed size.
+ //
+ // Following the header is a sequence of tags. Each tag begins with a little endian UInt16 whose
+ // upper 10 bits are the tag ID and lower 6 bits are a length for the content. If this length is
+ // 63 (0x3F) then a little endian Int32 follows with the content length.
+ //
+ // The FileAttributes tag, #69, has a flag byte and 3 reserved bytes following the header. There
+ // is only 1 flag bit that we care about, HasMetadata with the mask 0x10.
+ //
+ // The Metadata tag, #77, has content that is the UTF-8 XMP, preferably as small as possible.
+
+ XMP_Uns32 FileHeaderSize ( XMP_Uns8 rectBits );
+
+ class TagInfo {
+ public:
+ bool hasLongHeader;
+ XMP_Uns16 tagID;
+ XMP_Uns32 tagOffset, contentLength;
+ TagInfo() : hasLongHeader(false), tagID(0), tagOffset(0), contentLength(0) {};
+ ~TagInfo() {};
+ };
+
+ bool GetTagInfo ( const RawDataBlock & swfStream, XMP_Uns32 tagOffset, TagInfo * info );
+ XMP_Uns32 FullTagLength ( const TagInfo & info );
+ XMP_Uns32 ContentOffset ( const TagInfo & info );
+ XMP_Uns32 NextTagOffset ( const TagInfo & info );
+
+ XMP_Int64 DecompressFileToMemory ( XMP_IO * fileIn, RawDataBlock * dataOut );
+ XMP_Int64 CompressMemoryToFile ( const RawDataBlock & dataIn, XMP_IO* fileOut );
+
+}; // SWF_IO
+
+#endif // __SWF_Support_hpp__
diff --git a/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp b/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp
new file mode 100644
index 0000000..40cb9bd
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp
@@ -0,0 +1,1986 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp"
+#include "source/XIO.hpp"
+
+// =================================================================================================
+/// \file TIFF_FileWriter.cpp
+/// \brief TIFF_FileWriter is used for memory-based read-write access and all file-based access.
+///
+/// \c TIFF_FileWriter is used for memory-based read-write access and all file-based access. The
+/// main internal data structure is the InternalTagMap, a std::map that uses the tag number as the
+/// key and InternalTagInfo as the value. There are 5 of these maps, one for each of the recognized
+/// IFDs. The maps contain an entry for each tag in the IFD, whether we capture the data or not. The
+/// dataPtr and dataLen fields in the InternalTagInfo are zero if the tag is not captured.
+// =================================================================================================
+
+// =================================================================================================
+// TIFF_FileWriter::TIFF_FileWriter
+// ================================
+//
+// Set big endian Get/Put routines so that routines are in place for creating TIFF without a parse.
+// Parsing will reset them to the proper endianness for the stream. Big endian is a good default
+// since JPEG and PSD files are big endian overall.
+
+TIFF_FileWriter::TIFF_FileWriter() : changed(false), legacyDeleted(false), memParsed(false),
+ fileParsed(false), ownedStream(false), memStream(0), tiffLength(0)
+{
+
+ 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
+
+// =================================================================================================
+// TIFF_FileWriter::~TIFF_FileWriter
+// =================================
+
+TIFF_FileWriter::~TIFF_FileWriter()
+{
+ XMP_Assert ( ! (this->memParsed && this->fileParsed) );
+
+ if ( this->ownedStream ) {
+ XMP_Assert ( this->memStream != 0 );
+ free ( this->memStream );
+ }
+
+} // TIFF_FileWriter::~TIFF_FileWriter
+
+// =================================================================================================
+// TIFF_FileWriter::DeleteExistingInfo
+// ===================================
+
+void TIFF_FileWriter::DeleteExistingInfo()
+{
+ XMP_Assert ( ! (this->memParsed && this->fileParsed) );
+
+ if ( this->ownedStream ) free ( this->memStream ); // ! Current TIFF might be memory-parsed.
+ this->memStream = 0;
+ this->tiffLength = 0;
+
+ for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) this->containedIFDs[ifd].clear();
+
+ this->changed = false;
+ this->legacyDeleted = false;
+ this->memParsed = false;
+ this->fileParsed = false;
+ this->ownedStream = false;
+
+} // TIFF_FileWriter::DeleteExistingInfo
+
+// =================================================================================================
+// TIFF_FileWriter::PickIFD
+// ========================
+
+XMP_Uns8 TIFF_FileWriter::PickIFD ( XMP_Uns8 ifd, XMP_Uns16 id )
+{
+ if ( ifd > kTIFF_LastRealIFD ) {
+ if ( ifd != kTIFF_KnownIFD ) XMP_Throw ( "Invalid IFD number", kXMPErr_BadParam );
+ XMP_Throw ( "kTIFF_KnownIFD not yet implemented", kXMPErr_Unimplemented );
+ // *** Likely to stay unimplemented until there is a client need.
+ }
+
+ return ifd;
+
+} // TIFF_FileWriter::PickIFD
+
+// =================================================================================================
+// TIFF_FileWriter::FindTagInIFD
+// =============================
+
+const TIFF_FileWriter::InternalTagInfo* TIFF_FileWriter::FindTagInIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) const
+{
+ ifd = PickIFD ( ifd, id );
+ const InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap;
+
+ InternalTagMap::const_iterator tagPos = currIFD.find ( id );
+ if ( tagPos == currIFD.end() ) return 0;
+ return &tagPos->second;
+
+} // TIFF_FileWriter::FindTagInIFD
+
+// =================================================================================================
+// TIFF_FileWriter::GetIFD
+// =======================
+
+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;
+ TagInfo extInfo ( intInfo.id, intInfo.type, intInfo.count, intInfo.dataPtr, intInfo.dataLen );
+ (*ifdMap)[intInfo.id] = extInfo;
+ }
+ }
+
+ return true;
+
+} // TIFF_FileWriter::GetIFD
+
+// =================================================================================================
+// TIFF_FileWriter::GetValueOffset
+// ===============================
+
+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
+{
+ const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
+ if ( thisTag == 0 ) return false;
+
+ if ( info != 0 ) {
+
+ info->id = thisTag->id;
+ info->type = thisTag->type;
+ info->count = thisTag->dataLen / (XMP_Uns32)kTIFF_TypeSizes[thisTag->type];
+ info->dataLen = thisTag->dataLen;
+ 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 )
+{
+ 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;
+
+ InternalTagInfo* tagPtr = 0;
+ InternalTagMap::iterator tagPos = currIFD.find ( id );
+
+ if ( tagPos == currIFD.end() ) {
+
+ // The tag does not yet exist, add it.
+ InternalTagMap::value_type mapValue ( id, InternalTagInfo ( id, type, count, this->fileParsed ) );
+ tagPos = currIFD.insert ( tagPos, mapValue );
+ tagPtr = &tagPos->second;
+
+ } else {
+
+ tagPtr = &tagPos->second;
+
+ // The tag already exists, make sure the value is actually changing.
+ if ( (type == tagPtr->type) && (count == tagPtr->count) &&
+ (memcmp ( clientPtr, tagPtr->dataPtr, tagPtr->dataLen ) == 0) ) {
+ return; // ! The value is unchanged, exit.
+ }
+
+ tagPtr->FreeData(); // Release any existing data allocation.
+
+ tagPtr->type = type; // These might be changing also.
+ tagPtr->count = count;
+
+ }
+
+ 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;
+ } else {
+ // The data is more than 4 bytes, make a copy.
+ tagPtr->dataPtr = (XMP_Uns8*) malloc ( fullSize );
+ 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;
+ for ( XMP_Uns32 i = 0; i < count; ++i ) Flip2 ( flipPtr[i] );
+ } else if ( typeSize == 4 ) {
+ XMP_Uns32* flipPtr = (XMP_Uns32*) tagPtr->dataPtr;
+ for ( XMP_Uns32 i = 0; i < count; ++i ) Flip4 ( flipPtr[i] );
+ } else if ( typeSize == 8 ) {
+ XMP_Uns64* flipPtr = (XMP_Uns64*) tagPtr->dataPtr;
+ for ( XMP_Uns32 i = 0; i < count; ++i ) Flip8 ( flipPtr[i] );
+ }
+ }
+
+ this->containedIFDs[ifd].changed = true;
+ this->changed = true;
+
+} // TIFF_FileWriter::SetTag
+
+// =================================================================================================
+// TIFF_FileWriter::DeleteTag
+// ==========================
+
+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.
+
+ currIFD.erase ( tagPos );
+ this->containedIFDs[ifd].changed = true;
+ this->changed = true;
+ if ( (ifd != kTIFF_PrimaryIFD) || (id != kTIFF_XMP) ) this->legacyDeleted = true;
+
+} // TIFF_FileWriter::DeleteTag
+
+// =================================================================================================
+
+static inline XMP_Uns8 GetUns8 ( const void* dataPtr )
+{
+ return *((XMP_Uns8*)dataPtr);
+}
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_Integer
+// ===============================
+
+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;
+
+ XMP_Uns32 uns32;
+ XMP_Int32 int32;
+
+ switch ( thisTag->type ) {
+
+ case kTIFF_ByteType:
+ uns32 = GetUns8 ( thisTag->dataPtr );
+ break;
+
+ case kTIFF_ShortType:
+ uns32 = this->GetUns16 ( thisTag->dataPtr );
+ break;
+
+ case kTIFF_LongType:
+ uns32 = this->GetUns32 ( thisTag->dataPtr );
+ break;
+
+ case kTIFF_SByteType:
+ int32 = (XMP_Int8) GetUns8 ( thisTag->dataPtr );
+ uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set.
+ break;
+
+ case kTIFF_SShortType:
+ int32 = (XMP_Int16) this->GetUns16 ( thisTag->dataPtr );
+ uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set.
+ break;
+
+ case kTIFF_SLongType:
+ int32 = (XMP_Int32) this->GetUns32 ( thisTag->dataPtr );
+ uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set.
+ break;
+
+ default: return false;
+
+ }
+
+ if ( data != 0 ) *data = uns32;
+ return true;
+
+} // TIFF_FileWriter::GetTag_Integer
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_Byte
+// ============================
+
+bool TIFF_FileWriter::GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const
+{
+ 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;
+
+} // TIFF_FileWriter::GetTag_Byte
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_SByte
+// =============================
+
+bool TIFF_FileWriter::GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data ) const
+{
+ 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;
+
+} // TIFF_FileWriter::GetTag_SByte
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_Short
+// =============================
+
+bool TIFF_FileWriter::GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data ) const
+{
+ 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;
+
+} // TIFF_FileWriter::GetTag_Short
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_SShort
+// ==============================
+
+bool TIFF_FileWriter::GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* data ) const
+{
+ 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;
+
+} // TIFF_FileWriter::GetTag_SShort
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_Long
+// ============================
+
+bool TIFF_FileWriter::GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const
+{
+ 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;
+
+} // TIFF_FileWriter::GetTag_Long
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_SLong
+// =============================
+
+bool TIFF_FileWriter::GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data ) const
+{
+ 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;
+
+} // TIFF_FileWriter::GetTag_SLong
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_Rational
+// ================================
+
+bool TIFF_FileWriter::GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_SRational
+// =================================
+
+bool TIFF_FileWriter::GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_Float
+// =============================
+
+bool TIFF_FileWriter::GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const
+{
+ 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 );
+ return true;
+
+} // TIFF_FileWriter::GetTag_Float
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_Double
+// ==============================
+
+bool TIFF_FileWriter::GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const
+{
+ 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 );
+ return true;
+
+} // TIFF_FileWriter::GetTag_Double
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_ASCII
+// =============================
+
+bool TIFF_FileWriter::GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const
+{
+ const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
+ 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
+
+// =================================================================================================
+// TIFF_FileWriter::GetTag_EncodedString
+// =====================================
+
+bool TIFF_FileWriter::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const
+{
+ 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;
+
+} // TIFF_FileWriter::GetTag_EncodedString
+
+// =================================================================================================
+// TIFF_FileWriter::SetTag_EncodedString
+// =====================================
+
+void TIFF_FileWriter::SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding )
+{
+ std::string encodedStr;
+
+ this->EncodeString ( utf8Str, encoding, &encodedStr );
+ this->SetTag ( ifd, id, kTIFF_UndefinedType, (XMP_Uns32)encodedStr.size(), encodedStr.c_str() );
+
+} // TIFF_FileWriter::SetTag_EncodedString
+
+// =================================================================================================
+// TIFF_FileWriter::IsLegacyChanged
+// ================================
+
+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
+
+// =================================================================================================
+// TIFF_FileWriter::ParseMemoryStream
+// ==================================
+
+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;
+ } else {
+ if ( length > 100*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based TIFF", kXMPErr_BadTIFF );
+ this->memStream = (XMP_Uns8*) malloc(length);
+ if ( this->memStream == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+ memcpy ( this->memStream, data, length ); // AUDIT: Safe, malloc'ed length bytes above.
+ this->ownedStream = true;
+ }
+
+ this->tiffLength = length;
+ XMP_Uns32 ifdLimit = this->tiffLength - 6; // An IFD must start before this offset.
+
+ // Find and process the primary, Exif, GPS, and Interoperability IFDs.
+
+ XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( this->memStream, length );
+
+ 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) ) {
+ XMP_Uns32 exifOffset = this->GetUns32 ( exifIFDTag->dataPtr );
+ (void) this->ProcessMemoryIFD ( exifOffset, kTIFF_ExifIFD );
+ }
+
+ const InternalTagInfo* gpsIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer );
+ if ( (gpsIFDTag != 0) && (gpsIFDTag->type == kTIFF_LongType) && (gpsIFDTag->dataLen == 4) ) {
+ XMP_Uns32 gpsOffset = this->GetUns32 ( gpsIFDTag->dataPtr );
+ if ( (8 <= gpsOffset) && (gpsOffset < ifdLimit) ) { // Remove a bad GPS IFD offset.
+ (void) this->ProcessMemoryIFD ( gpsOffset, kTIFF_GPSInfoIFD );
+ } else {
+ this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer );
+ }
+ }
+
+ const InternalTagInfo* interopIFDTag = this->FindTagInIFD ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer );
+ if ( (interopIFDTag != 0) && (interopIFDTag->type == kTIFF_LongType) && (interopIFDTag->dataLen == 4) ) {
+ XMP_Uns32 interopOffset = this->GetUns32 ( interopIFDTag->dataPtr );
+ if ( (8 <= interopOffset) && (interopOffset < ifdLimit) ) { // Remove a bad Interoperability IFD offset.
+ (void) this->ProcessMemoryIFD ( interopOffset, kTIFF_InteropIFD );
+ } else {
+ this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer );
+ }
+ }
+
+ #if 0
+ {
+ printf ( "\nExiting TIFF_FileWriter::ParseMemoryStream\n" );
+ for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
+ InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
+ printf ( "\n IFD %d, count %d, mapped %d, offset %d (0x%X), next IFD %d (0x%X)\n",
+ ifd, thisIFD.origCount, thisIFD.tagMap.size(),
+ thisIFD.origDataOffset, thisIFD.origDataOffset, thisIFD.origNextIFD, thisIFD.origNextIFD );
+ InternalTagMap::iterator tagPos;
+ InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
+ for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
+ InternalTagInfo & thisTag = tagPos->second;
+ printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)\n",
+ thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset );
+ }
+ }
+ printf ( "\n" );
+ }
+ #endif
+
+} // TIFF_FileWriter::ParseMemoryStream
+
+// =================================================================================================
+// TIFF_FileWriter::ProcessMemoryIFD
+// =================================
+
+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 ( (XMP_Uns32)(2 + tagCount*12 + 4) > (this->tiffLength - ifdOffset) ) {
+ 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 );
+
+ InternalTagMap::value_type mapValue ( tagID, InternalTagInfo ( tagID, tagType, tagCount, kIsMemoryBased ) );
+ InternalTagMap::iterator newPos = ifdInfo.tagMap.insert ( ifdInfo.tagMap.end(), mapValue );
+ InternalTagInfo& mapTag = newPos->second;
+
+ mapTag.dataLen = mapTag.origDataLen = mapTag.count * (XMP_Uns32)kTIFF_TypeSizes[mapTag.type];
+ mapTag.smallValue = rawTag->dataOrOffset; // Keep the value or offset in stream byte ordering.
+
+ if ( mapTag.dataLen <= 4 ) {
+ mapTag.origDataOffset = ifdOffset + 2 + (12 * (XMP_Uns32)i) + 8; // Compute the data offset.
+ } else {
+ mapTag.origDataOffset = this->GetUns32 ( &rawTag->dataOrOffset ); // Extract the data offset.
+ // printf ( "FW_ProcessMemoryIFD tag %d large value @ %.8X\n", mapTag.id, mapTag.dataPtr );
+ if ( (mapTag.origDataOffset < 8) || (mapTag.origDataOffset >= this->tiffLength) ) {
+ mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0;
+ mapTag.origDataOffset = ifdOffset + 2 + (12 * (XMP_Uns32)i) + 8; // Make this bad tag look empty
+ }
+ if ( mapTag.dataLen > (this->tiffLength - mapTag.origDataOffset) ) {
+ mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0;
+ mapTag.origDataOffset = ifdOffset + 2 + (12 * (XMP_Uns32)i) + 8; // Make this bad tag look empty
+ }
+ }
+ mapTag.dataPtr = this->memStream + mapTag.origDataOffset;
+
+ }
+
+ ifdPtr += (2 + tagCount*12);
+ ifdInfo.origNextIFD = this->GetUns32 ( ifdPtr );
+
+ return ifdInfo.origNextIFD;
+
+} // TIFF_FileWriter::ProcessMemoryIFD
+
+// =================================================================================================
+// TIFF_FileWriter::ParseFileStream
+// ================================
+//
+// The buffered I/O model is worth the logic complexity - as opposed to a simple seek/read for each
+// part of the TIFF stream. The vast majority of real-world TIFFs have the primary IFD, Exif IFD,
+// 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 ( XMP_IO* fileRef )
+{
+ bool ok;
+ IOBuffer ioBuf;
+
+ this->DeleteExistingInfo();
+ this->fileParsed = true;
+ this->tiffLength = (XMP_Uns32) fileRef->Length();
+ if ( this->tiffLength == 0 ) return;
+
+ XMP_Uns32 ifdLimit = this->tiffLength - 6; // An IFD must start before this offset.
+
+ // Find and process the primary, Exif, GPS, and Interoperability IFDs.
+
+ ioBuf.filePos = 0;
+ fileRef->Rewind ( );
+ ok = CheckFileSpace ( fileRef, &ioBuf, 8 );
+ if ( ! ok ) XMP_Throw ( "TIFF too small", kXMPErr_BadTIFF );
+
+ XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( ioBuf.ptr, this->tiffLength );
+
+ 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) ) {
+ XMP_Uns32 exifOffset = this->GetUns32 ( exifIFDTag->dataPtr );
+ (void) this->ProcessFileIFD ( kTIFF_ExifIFD, exifOffset, fileRef, &ioBuf );
+ }
+
+ const InternalTagInfo* gpsIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer );
+ if ( (gpsIFDTag != 0) && (gpsIFDTag->type == kTIFF_LongType) && (gpsIFDTag->count == 1) ) {
+ XMP_Uns32 gpsOffset = this->GetUns32 ( gpsIFDTag->dataPtr );
+ if ( (8 <= gpsOffset) && (gpsOffset < ifdLimit) ) { // Remove a bad GPS IFD offset.
+ (void) this->ProcessFileIFD ( kTIFF_GPSInfoIFD, gpsOffset, fileRef, &ioBuf );
+ } else {
+ this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer );
+ }
+ }
+
+ const InternalTagInfo* interopIFDTag = this->FindTagInIFD ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer );
+ if ( (interopIFDTag != 0) && (interopIFDTag->type == kTIFF_LongType) && (interopIFDTag->dataLen == 4) ) {
+ XMP_Uns32 interopOffset = this->GetUns32 ( interopIFDTag->dataPtr );
+ if ( (8 <= interopOffset) && (interopOffset < ifdLimit) ) { // Remove a bad Interoperability IFD offset.
+ (void) this->ProcessFileIFD ( kTIFF_InteropIFD, interopOffset, fileRef, &ioBuf );
+ } else {
+ this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer );
+ }
+ }
+
+ #if 0
+ {
+ printf ( "\nExiting TIFF_FileWriter::ParseFileStream\n" );
+ for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
+ InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
+ printf ( "\n IFD %d, count %d, mapped %d, offset %d (0x%X), next IFD %d (0x%X)\n",
+ ifd, thisIFD.origCount, thisIFD.tagMap.size(),
+ thisIFD.origDataOffset, thisIFD.origDataOffset, thisIFD.origNextIFD, thisIFD.origNextIFD );
+ InternalTagMap::iterator tagPos;
+ InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
+ for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
+ InternalTagInfo & thisTag = tagPos->second;
+ printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)\n",
+ thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset );
+ }
+ }
+ printf ( "\n" );
+ }
+ #endif
+
+} // TIFF_FileWriter::ParseFileStream
+
+// =================================================================================================
+// TIFF_FileWriter::ProcessFileIFD
+// ===============================
+
+XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, XMP_IO* fileRef, void* _ioBuf )
+{
+ IOBuffer* ioBuf = (IOBuffer*)_ioBuf; // *** Temporary hack, _ioBuf is IOBuffer* but don't want to include XIO.hpp.
+ InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] );
+
+ if ( (ifdOffset < 8) || (ifdOffset > (this->tiffLength - kEmptyIFDLength)) ) {
+ XMP_Throw ( "Bad IFD offset", kXMPErr_BadTIFF );
+ }
+
+ 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 ( (XMP_Uns32)(2 + tagCount*12 + 4) > (this->tiffLength - ifdOffset) ) {
+ 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
+ // sorted output. Plus the "map[key] = value" assignment conveniently keeps the last encountered
+ // 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 );
+
+ InternalTagMap::value_type mapValue ( tagID, InternalTagInfo ( tagID, tagType, tagCount, kIsFileBased ) );
+ InternalTagMap::iterator newPos = ifdInfo.tagMap.insert ( ifdInfo.tagMap.end(), mapValue );
+ InternalTagInfo& mapTag = newPos->second;
+
+ mapTag.dataLen = mapTag.origDataLen = mapTag.count * (XMP_Uns32)kTIFF_TypeSizes[mapTag.type];
+ mapTag.smallValue = rawTag->dataOrOffset; // Keep the value or offset in stream byte ordering.
+
+ if ( mapTag.dataLen <= 4 ) {
+ mapTag.dataPtr = (XMP_Uns8*) &mapTag.smallValue;
+ mapTag.origDataOffset = ifdOffset + 2 + (12 * i) + 8; // Compute the data offset.
+ } else {
+ mapTag.origDataOffset = this->GetUns32 ( &rawTag->dataOrOffset ); // Extract the data offset.
+ if ( (mapTag.origDataOffset < 8) || (mapTag.origDataOffset >= this->tiffLength) ) {
+ mapTag.dataPtr = (XMP_Uns8*) &mapTag.smallValue; // Make this bad tag look empty.
+ mapTag.origDataOffset = ifdOffset + 2 + (12 * i) + 8;
+ mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0;
+ }
+ if ( mapTag.dataLen > (this->tiffLength - mapTag.origDataOffset) ) {
+ mapTag.dataPtr = (XMP_Uns8*) &mapTag.smallValue; // Make this bad tag look empty.
+ mapTag.origDataOffset = ifdOffset + 2 + (12 * i) + 8;
+ mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0;
+ }
+ }
+
+ }
+
+ 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 ( (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 );
+ currTag->dataPtr = (XMP_Uns8*) malloc ( currTag->dataLen );
+ 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?
+ while ( *knownTagPtr < currTag->id ) ++knownTagPtr;
+ if ( *knownTagPtr != currTag->id ) continue; // Skip unrecognized tags.
+
+ currTag->dataPtr = (XMP_Uns8*) malloc ( currTag->dataLen );
+ if ( currTag->dataPtr == 0 ) XMP_Throw ( "No data block", kXMPErr_NoMemory );
+
+ if ( currTag->dataLen > kIOBufferSize ) {
+ // This value is bigger than the I/O buffer, read it directly and restore the file position.
+ fileRef->Seek ( currTag->origDataOffset, kXMP_SeekFromStart );
+ fileRef->ReadAll ( currTag->dataPtr, currTag->dataLen );
+ fileRef->Seek ( (ioBuf->filePos + ioBuf->len), kXMP_SeekFromStart );
+ } else {
+ // This value can fit in the I/O buffer, so use that.
+ MoveToOffset ( fileRef, currTag->origDataOffset, ioBuf );
+ ok = CheckFileSpace ( fileRef, ioBuf, currTag->dataLen );
+ 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
+
+// =================================================================================================
+// TIFF_FileWriter::IntegrateFromPShop6
+// ====================================
+//
+// See comments for ProcessPShop6IFD.
+
+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_ExifIFD );
+ this->ProcessPShop6IFD ( buriedExif, kTIFF_GPSInfoIFD );
+
+} // TIFF_FileWriter::IntegrateFromPShop6
+
+// =================================================================================================
+// TIFF_FileWriter::CopyTagToMasterIFD
+// ===================================
+//
+// Create a new master IFD entry from a buried Photoshop 6 IFD entry. Don't try to get clever with
+// large values, just create a new copy. This preserves a clean separation between the memory-based
+// and file-based TIFF processing.
+
+void* TIFF_FileWriter::CopyTagToMasterIFD ( const TagInfo & ps6Tag, InternalIFDInfo * masterIFD )
+{
+ InternalTagMap::value_type mapValue ( ps6Tag.id, InternalTagInfo ( ps6Tag.id, ps6Tag.type, ps6Tag.count, this->fileParsed ) );
+ InternalTagMap::iterator newPos = masterIFD->tagMap.insert ( masterIFD->tagMap.end(), mapValue );
+ InternalTagInfo& newTag = newPos->second;
+
+ newTag.dataLen = ps6Tag.dataLen;
+
+ if ( newTag.dataLen <= 4 ) {
+ newTag.dataPtr = (XMP_Uns8*) &newTag.smallValue;
+ newTag.smallValue = *((XMP_Uns32*)ps6Tag.dataPtr);
+ } else {
+ newTag.dataPtr = (XMP_Uns8*) malloc ( newTag.dataLen );
+ if ( newTag.dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+ memcpy ( newTag.dataPtr, ps6Tag.dataPtr, newTag.dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above.
+ }
+
+ 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
+
+// =================================================================================================
+// FlipCFATable
+// ============
+//
+// The CFA pattern table is trivial, a pair of short counts followed by n*m bytes.
+
+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
+
+// =================================================================================================
+// FlipDSDTable
+// ============
+//
+// The device settings description table is trivial, a pair of short counts followed by UTF-16
+// strings. So the whole value should be flipped as a sequence of 16 bit items.
+
+// ! The Exif 2.2 description is a bit garbled. It might be wrong. It would be nice to have a real example.
+
+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
+
+// =================================================================================================
+// FlipOECFSFRTable
+// ================
+//
+// The OECF and SFR tables have the same layout:
+// 2 short counts, columns and rows
+// c ASCII strings, null terminated, column names
+// c*r rationals
+
+static bool FlipOECFSFRTable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns16 )
+{
+ XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr;
+
+ 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
+
+// =================================================================================================
+// TIFF_FileWriter::ProcessPShop6IFD
+// =================================
+//
+// Photoshop 6 wrote wacky TIFF files that have much of the Exif metadata buried inside of image
+// resource 1058, which is itself within tag 34377 in the 0th IFD. This routine moves the buried
+// 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
+// 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
+// overall TIFF_FileWriter object is not. We don't want this integration on its own to force a file
+// update, but a file update should include these changes.
+
+// ! Be careful to not move tags that are the nasty Exif explicit offsets, e.g. the Exif or GPS IFD
+// ! "pointers". These are tags with a LONG type and count of 1, whose value is an offset into the
+// ! buried TIFF stream. We can't reliably plant that offset into the outer IFD structure.
+
+// ! To make things even more fun, the buried Exif might not have the same endianness as the outer!
+
+void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XMP_Uns8 ifd )
+{
+ 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:
+ {
+ XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr;
+ for ( size_t i = ps6Tag.count; i > 0; --i, ++u16Ptr ) Flip2 ( u16Ptr );
+ }
+ break;
+
+ case kTIFF_LongType:
+ case kTIFF_SLongType:
+ case kTIFF_FloatType:
+ {
+ XMP_Uns32* u32Ptr = (XMP_Uns32*)voidPtr;
+ for ( size_t i = ps6Tag.count; i > 0; --i, ++u32Ptr ) Flip4 ( u32Ptr );
+ }
+ break;
+
+ case kTIFF_RationalType:
+ case kTIFF_SRationalType:
+ {
+ XMP_Uns32* ratPtr = (XMP_Uns32*)voidPtr;
+ 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.
+ if ( ps6Tag.id == kTIFF_CFAPattern ) {
+ ok = FlipCFATable ( voidPtr, ps6Tag.dataLen, this->GetUns16 );
+ } else if ( ps6Tag.id == kTIFF_DeviceSettingDescription ) {
+ ok = FlipDSDTable ( voidPtr, ps6Tag.dataLen, this->GetUns16 );
+ } else if ( (ps6Tag.id == kTIFF_OECF) || (ps6Tag.id == kTIFF_SpatialFrequencyResponse) ) {
+ ok = FlipOECFSFRTable ( voidPtr, ps6Tag.dataLen, this->GetUns16 );
+ }
+ 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
+// ====================================
+
+#ifndef Trace_DetermineAppendInfo
+ #define Trace_DetermineAppendInfo 0
+#endif
+
+XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin,
+ bool appendedIFDs[kTIFF_KnownIFDCount],
+ XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount],
+ bool appendAll /* = false */ )
+{
+ 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" : "") );
+ for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
+ InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
+ printf ( "\n IFD %d, origCount %d, map.size %d, origIFDOffset %d (0x%X), origNextIFD %d (0x%X)",
+ ifd, thisIFD.origCount, thisIFD.tagMap.size(),
+ thisIFD.origIFDOffset, thisIFD.origIFDOffset, thisIFD.origNextIFD, thisIFD.origNextIFD );
+ if ( thisIFD.changed ) printf ( ", changed" );
+ if ( thisIFD.origCount < thisIFD.tagMap.size() ) printf ( ", should get appended" );
+ printf ( "\n" );
+ InternalTagMap::iterator tagPos;
+ InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
+ for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
+ InternalTagInfo & thisTag = tagPos->second;
+ printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)",
+ thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset );
+ if ( thisTag.changed ) printf ( ", changed" );
+ if ( (thisTag.dataLen > thisTag.origDataLen) && (thisTag.dataLen > 4) ) printf ( ", should get appended" );
+ printf ( "\n" );
+ }
+ }
+ 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_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.
+
+ 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();
+
+ for ( ; tagPos != tagEnd; ++tagPos ) {
+
+ InternalTagInfo & currTag ( tagPos->second );
+ if ( (! (appendAll | currTag.changed)) || (currTag.dataLen <= 4) ) continue;
+
+ if ( (currTag.dataLen <= currTag.origDataLen) && (! appendAll) ) {
+ this->PutUns32 ( currTag.origDataOffset, &currTag.smallValue ); // Reuse the old space.
+ } else {
+ this->PutUns32 ( (appendedOrigin + appendedLength), &currTag.smallValue ); // Set the appended offset.
+ appendedLength += ((currTag.dataLen + 1) & 0xFFFFFFFEUL); // Round to an even size.
+ }
+
+ }
+
+ }
+
+ // 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] );
+ }
+ if ( appendedIFDs[kTIFF_GPSInfoIFD] ) {
+ this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, newIFDOffsets[kTIFF_GPSInfoIFD] );
+ }
+ if ( appendedIFDs[kTIFF_InteropIFD] ) {
+ this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, newIFDOffsets[kTIFF_InteropIFD] );
+ }
+
+ #if Trace_DetermineAppendInfo
+ {
+ printf ( "Exiting TIFF_FileWriter::DetermineAppendInfo\n" );
+ for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
+ InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
+ printf ( "\n IFD %d, origCount %d, map.size %d, origIFDOffset %d (0x%X), origNextIFD %d (0x%X)",
+ ifd, thisIFD.origCount, thisIFD.tagMap.size(),
+ thisIFD.origIFDOffset, thisIFD.origIFDOffset, thisIFD.origNextIFD, thisIFD.origNextIFD );
+ if ( thisIFD.changed ) printf ( ", changed" );
+ if ( appendedIFDs[ifd] ) printf ( ", will be appended at %d (0x%X)", newIFDOffsets[ifd], newIFDOffsets[ifd] );
+ printf ( "\n" );
+ InternalTagMap::iterator tagPos;
+ InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
+ for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
+ InternalTagInfo & thisTag = tagPos->second;
+ printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)",
+ thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset );
+ if ( thisTag.changed ) printf ( ", changed" );
+ if ( (thisTag.dataLen > thisTag.origDataLen) && (thisTag.dataLen > 4) ) {
+ XMP_Uns32 newOffset = this->GetUns32 ( &thisTag.smallValue );
+ printf ( ", will be appended at %d (0x%X)", newOffset, newOffset );
+ }
+ printf ( "\n" );
+ }
+ }
+ printf ( "\n" );
+ }
+ #endif
+
+ return appendedLength;
+
+} // TIFF_FileWriter::DetermineAppendInfo
+
+// =================================================================================================
+// TIFF_FileWriter::UpdateMemByAppend
+// ==================================
+//
+// Normally we update TIFF in a conservative "by-append" manner. Changes are written in-place where
+// they fit, anything requiring growth is appended to the end and the old space is abandoned. The
+// end for memory-based TIFF is the end of the data block, the end for file-based TIFF is the end of
+// the file. This update-by-append model has the advantage of not perturbing any hidden offsets, a
+// common feature of proprietary MakerNotes.
+//
+// When doing the update-by-append we're only going to be modifying things that have changed. This
+// means IFDs with changed, added, or deleted tags, and large values for changed or added tags. The
+// IFDs and tag values are updated in-place if they fit, leaving holes in the stream if the new
+// value is smaller than the old.
+
+// ** Someday we might want to use the FreeOffsets and FreeByteCounts tags to track free space.
+// ** Probably not a huge win in practice though, and the TIFF spec says they are not recommended
+// ** for general interchange use.
+
+void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out,
+ bool appendAll /* = false */, XMP_Uns32 extraSpace /* = 0 */ )
+{
+ bool appendedIFDs[kTIFF_KnownIFDCount];
+ XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount];
+ XMP_Uns32 appendedOrigin = ((this->tiffLength + 1) & 0xFFFFFFFEUL); // Start at an even offset.
+ XMP_Uns32 appendedLength = DetermineAppendInfo ( appendedOrigin, appendedIFDs, newIFDOffsets, appendAll );
+
+ // 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 );
+
+ memcpy ( newStream, this->memStream, this->tiffLength ); // AUDIT: Safe, malloc'ed newLength bytes above.
+ if ( this->tiffLength < appendedOrigin ) {
+ 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();
+
+ if ( ! (appendAll | ifdInfo.changed) ) continue;
+ 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;
+
+ InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin();
+ InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end();
+
+ for ( ; tagPos != tagEnd; ++tagPos ) {
+
+ InternalTagInfo & currTag ( tagPos->second );
+
+ this->PutUns16 ( currTag.id, ifdPtr );
+ ifdPtr += 2;
+ this->PutUns16 ( currTag.type, ifdPtr );
+ ifdPtr += 2;
+ this->PutUns32 ( currTag.count, ifdPtr );
+ ifdPtr += 4;
+
+ *((XMP_Uns32*)ifdPtr) = currTag.smallValue;
+
+ if ( (appendAll | currTag.changed) && (currTag.dataLen > 4) ) {
+
+ XMP_Uns32 valueOffset = this->GetUns32 ( &currTag.smallValue );
+
+ if ( (currTag.dataLen <= currTag.origDataLen) && (! appendAll) ) {
+ XMP_Assert ( valueOffset == currTag.origDataOffset );
+ } else {
+ XMP_Assert ( valueOffset == appendedOffset );
+ appendedOffset += ((currTag.dataLen + 1) & 0xFFFFFFFEUL);
+ }
+
+ XMP_Assert ( valueOffset <= newLength ); // Provably true, valueOffset is in the old span, newLength is the new bigger span.
+ if ( currTag.dataLen > (newLength - valueOffset) ) XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure );
+ memcpy ( (newStream + valueOffset), currTag.dataPtr, currTag.dataLen ); // AUDIT: Protected by the above check.
+ if ( (currTag.dataLen & 1) != 0 ) newStream[valueOffset+currTag.dataLen] = 0;
+
+ }
+
+ 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) );
+ }
+
+ } catch ( ... ) {
+
+ free ( newStream );
+ throw;
+
+ }
+
+ *newStream_out = newStream;
+ *newLength_out = newLength;
+
+} // TIFF_FileWriter::UpdateMemByAppend
+
+// =================================================================================================
+// TIFF_FileWriter::UpdateMemByRewrite
+// ===================================
+//
+// Normally we update TIFF in a conservative "by-append" manner. Changes are written in-place where
+// they fit, anything requiring growth is appended to the end and the old space is abandoned. The
+// end for memory-based TIFF is the end of the data block, the end for file-based TIFF is the end of
+// the file. This update-by-append model has the advantage of not perturbing any hidden offsets, a
+// common feature of proprietary MakerNotes.
+//
+// The condenseStream parameter can be used to rewrite the full stream instead of appending. This
+// will discard any MakerNote tag and risks breaking offsets that are hidden. This can be necessary
+// though to try to make the TIFF fit in a JPEG file.
+//
+// We don't do most of the actual rewrite here. We set things up so that UpdateMemByAppend can be
+// called to append onto a bare TIFF header. Additional hidden offsets are then handled here.
+//
+// These tags are recognized as being hidden offsets when composing a condensed stream:
+// 273 - StripOffsets, lengths in tag 279
+// 288 - FreeOffsets, lengths in tag 289
+// 324 - TileOffsets, lengths in tag 325
+// 330 - SubIFDs, lengths within the IFDs (Plus subIFD values and possible chaining!)
+// 513 - JPEGInterchangeFormat, length in tag 514
+// 519 - JPEGQTables, each table is 64 bytes
+// 520 - JPEGDCTables, lengths ???
+// 521 - JPEGACTables, lengths ???
+// Some of these will handled and kept, some will be thrown out, some will cause the rewrite to fail.
+//
+// The hidden offsets for the Exif, GPS, and Interoperability IFDs (tags 34665, 34853, and 40965)
+// are handled by the code in DetermineAppendInfo, which is called from UpdateMemByAppend, which is
+// called from here.
+
+// ! So far, a memory-based TIFF rewrite would only be done for the Exif portion of a JPEG file.
+// ! In which case we're probably OK to handle JPEGInterchangeFormat (used for compressed thumbnails)
+// ! and complain about any of the other hidden offset tags.
+
+// tag count type
+
+// 273 n short or long
+// 279 n short or long
+// 288 n long
+// 289 n long
+// 324 n long
+// 325 n short or long
+
+// 330 n long
+
+// 513 1 long
+// 514 1 long
+
+// 519 n long
+// 520 n long
+// 521 n long
+
+static XMP_Uns16 kNoGoTags[] =
+ {
+ kTIFF_StripOffsets, // 273 *** Should be handled?
+ kTIFF_StripByteCounts, // 279 *** Should be handled?
+ kTIFF_FreeOffsets, // 288 *** Should be handled?
+ kTIFF_FreeByteCounts, // 289 *** Should be handled?
+ kTIFF_TileOffsets, // 324 *** Should be handled?
+ kTIFF_TileByteCounts, // 325 *** Should be handled?
+ kTIFF_SubIFDs, // 330 *** Should be handled?
+ kTIFF_JPEGQTables, // 519
+ kTIFF_JPEGDCTables, // 520
+ kTIFF_JPEGACTables, // 521
+ 0xFFFF // Must be last as a sentinel.
+ };
+
+static XMP_Uns16 kBanishedTags[] =
+ {
+ kTIFF_MakerNote, // *** Should someday support MakerNoteSafety.
+ 0xFFFF // Must be last as a sentinel.
+ };
+
+struct SimpleHiddenContentInfo {
+ XMP_Uns8 ifd;
+ XMP_Uns16 offsetTag, lengthTag;
+};
+
+struct SimpleHiddenContentLocations {
+ XMP_Uns32 length, oldOffset, newOffset;
+ SimpleHiddenContentLocations() : length(0), oldOffset(0), newOffset(0) {};
+};
+
+enum { kSimpleHiddenContentCount = 1 };
+
+static const SimpleHiddenContentInfo kSimpleHiddenContentInfo [kSimpleHiddenContentCount] =
+ {
+ { kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat, kTIFF_JPEGInterchangeFormatLength }
+ };
+
+// -------------------------------------------------------------------------------------------------
+
+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.
+
+ for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
+ for ( int i = 0; kBanishedTags[i] != 0xFFFF; ++i ) {
+ this->DeleteTag ( ifd, kBanishedTags[i] );
+ }
+ }
+
+ // 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 );
+ if ( (! haveLength) || (hiddenLocations[i].length == 0) ) continue;
+
+ hiddenLocations[i].newOffset = hiddenContentOrigin + hiddenContentLength;
+ this->SetTag_Long ( hiddenInfo.ifd, hiddenInfo.offsetTag, hiddenLocations[i].newOffset );
+ 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;
+
+ XMP_Uns8 bareTIFF [8];
+ if ( this->bigEndian ) {
+ bareTIFF[0] = 0x4D; bareTIFF[1] = 0x4D; bareTIFF[2] = 0x00; bareTIFF[3] = 0x2A;
+ } else {
+ 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;
+
+ XMP_Uns8* srcPtr = oldStream + hiddenLocations[i].oldOffset;
+ XMP_Uns8* destPtr = *newStream_out + hiddenLocations[i].newOffset;
+ memcpy ( destPtr, srcPtr, hiddenLocations[i].length ); // AUDIT: Safe copy, not user data, computed length.
+
+ }
+
+} // TIFF_FileWriter::UpdateMemByRewrite
+
+// =================================================================================================
+// TIFF_FileWriter::UpdateMemoryStream
+// ===================================
+//
+// Normally we update TIFF in a conservative "by-append" manner. Changes are written in-place where
+// they fit, anything requiring growth is appended to the end and the old space is abandoned. The
+// end for memory-based TIFF is the end of the data block, the end for file-based TIFF is the end of
+// the file. This update-by-append model has the advantage of not perturbing any hidden offsets, a
+// common feature of proprietary MakerNotes.
+//
+// The condenseStream parameter 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.
+
+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() ) {
+ nowEmpty = false;
+ 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
+
+// =================================================================================================
+// TIFF_FileWriter::UpdateFileStream
+// =================================
+//
+// Updating a file stream is done in the same general manner as updating a memory stream, the intro
+// comments for UpdateMemoryStream largely apply. The file update happens in 3 phases:
+// 1. Determine which IFDs will be appended, and the new offsets for the appended IFDs and tags.
+// 2. Do the in-place update for the IFDs and tag values that fit.
+// 3. Append the IFDs and tag values that grow.
+//
+// The file being updated must match the file that was previously parsed. Offsets and lengths saved
+// when parsing are used to decide if something can be updated in-place or must be appended.
+
+// *** The general linked structure of TIFF makes it very difficult to process the file in a single
+// *** sequential pass. This implementation uses a simple seek/write model for the in-place updates.
+// *** In the future we might want to consider creating a map of what gets updated, allowing use of
+// *** a possibly more efficient buffered model.
+
+// ** Someday we might want to use the FreeOffsets and FreeByteCounts tags to track free space.
+// ** Probably not a huge win in practice though, and the TIFF spec says they are not recommended
+// ** for general interchange use.
+
+#ifndef Trace_UpdateFileStream
+ #define Trace_UpdateFileStream 0
+#endif
+
+void TIFF_FileWriter::UpdateFileStream ( XMP_IO* fileRef )
+{
+ if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure );
+ if ( ! this->changed ) return;
+
+ XMP_Int64 origDataLength = fileRef->Length();
+ 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
+
+ XMP_Uns32 appendedOrigin = (XMP_Uns32)origDataLength;
+ if ( (appendedOrigin & 1) != 0 ) {
+ ++appendedOrigin; // Start at an even offset.
+ fileRef->Seek ( 0, kXMP_SeekFromEnd );
+ fileRef->Write ( "\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];
+ if ( ! thisIFD.changed ) continue;
+
+ // 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 );
+ #endif
+ fileRef->Seek ( thisIFD.origIFDOffset, kXMP_SeekFromStart );
+ 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;
+ #if Trace_UpdateFileStream
+ printf ( " Updating tag %d in IFD %d in-place at offset %d (0x%X)\n", thisTag.id, ifd, thisTag.origDataOffset, thisTag.origDataOffset );
+ #endif
+ fileRef->Seek ( thisTag.origDataOffset, kXMP_SeekFromStart );
+ fileRef->Write ( thisTag.dataPtr, thisTag.dataLen );
+ }
+
+ }
+
+ // Append the IFDs and tag values that grow.
+
+ XMP_Int64 fileEnd = fileRef->Seek ( 0, kXMP_SeekFromEnd );
+ 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] );
+ #endif
+ XMP_Assert ( newIFDOffsets[ifd] == fileRef->Length() );
+ 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;
+ #if Trace_UpdateFileStream
+ XMP_Uns32 newOffset = this->GetUns32(&thisTag.origDataOffset);
+ printf ( " Updating tag %d in IFD %d by append at offset %d (0x%X)\n", thisTag.id, ifd, newOffset, newOffset );
+ #endif
+ XMP_Assert ( this->GetUns32(&thisTag.smallValue) == fileRef->Length() );
+ fileRef->Write ( thisTag.dataPtr, thisTag.dataLen );
+ if ( (thisTag.dataLen & 1) != 0 ) fileRef->Write ( "\0", 1 );
+ }
+
+ }
+
+ // 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
+ printf ( " Back-filling offset of primary IFD, pointing to %d (0x%X)\n", newOffset, newOffset );
+ #endif
+ fileRef->Seek ( 4, kXMP_SeekFromStart );
+ fileRef->Write ( &newOffset, 4 );
+ }
+
+ // 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];
+ if ( ! thisIFD.changed ) continue;
+
+ 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;
+ thisTag.changed = false;
+ thisTag.origDataLen = thisTag.dataLen;
+ if ( thisTag.origDataLen > 4 ) thisTag.origDataOffset = this->GetUns32 ( &thisTag.smallValue );
+ }
+
+ }
+
+ this->tiffLength = (XMP_Uns32) fileRef->Length();
+ fileRef->Seek ( 0, kXMP_SeekFromEnd ); // Can't hurt.
+
+ #if Trace_UpdateFileStream
+ printf ( "\nFinished update of TIFF file stream\n" );
+ #endif
+
+} // TIFF_FileWriter::UpdateFileStream
+
+// =================================================================================================
+// TIFF_FileWriter::WriteFileIFD
+// =============================
+
+void TIFF_FileWriter::WriteFileIFD ( XMP_IO* fileRef, InternalIFDInfo & thisIFD )
+{
+ XMP_Uns16 tagCount;
+ this->PutUns16 ( (XMP_Uns16)thisIFD.tagMap.size(), &tagCount );
+ fileRef->Write ( &tagCount, 2 );
+
+ InternalTagMap::iterator tagPos;
+ InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
+
+ for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
+
+ InternalTagInfo & thisTag = tagPos->second;
+ RawIFDEntry ifdEntry;
+
+ this->PutUns16 ( thisTag.id, &ifdEntry.id );
+ this->PutUns16 ( thisTag.type, &ifdEntry.type );
+ this->PutUns32 ( thisTag.count, &ifdEntry.count );
+ ifdEntry.dataOrOffset = thisTag.smallValue; // ! Already in stream endianness.
+
+ fileRef->Write ( &ifdEntry, sizeof(ifdEntry) );
+ XMP_Assert ( sizeof(ifdEntry) == 12 );
+
+ }
+
+ XMP_Uns32 nextIFD;
+ this->PutUns32 ( thisIFD.origNextIFD, &nextIFD );
+ fileRef->Write ( &nextIFD, 4 );
+
+} // TIFF_FileWriter::WriteFileIFD
diff --git a/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp b/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp
new file mode 100644
index 0000000..3e96620
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp
@@ -0,0 +1,645 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp"
+#include "source/XIO.hpp"
+
+// =================================================================================================
+/// \file TIFF_MemoryReader.cpp
+/// \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
+/// 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
+/// memory-based reader has one block of data for the whole TIFF stream. The file-based reader has
+/// one for each IFD, plus one for the collected non-local data for each IFD. Otherwise the logic
+/// is the same in both cases.
+///
+/// The count for each IFD is extracted and a pointer set to the first entry in each IFD (serving as
+/// a normal C array pointer). The IFD entries are tweaked as follows:
+///
+/// \li The id and type fields are converted to native values.
+/// \li The count field is converted to a native byte count.
+/// \li If the data is not inline the offset is converted to a pointer.
+///
+/// The tag values, whether inline or not, are not converted to native values. The values returned
+/// from the GetTag methods are converted on the fly. The id, type, and count fields are easier to
+/// convert because their types are fixed. They are used more, and more valuable to convert.
+// =================================================================================================
+
+// =================================================================================================
+// TIFF_MemoryReader::SortIFD
+// ==========================
+//
+// Does a fairly simple minded insertion-like sort. This sort is not going to be optimal for edge
+// cases like and IFD with lots of duplicates.
+
+// *** Might be better done using read and write pointers and two loops. The first loop moves out
+// *** of order tags by a modified bubble sort, shifting the middle down one at a time in the loop.
+// *** The first loop stops when a duplicate is hit. The second loop continues by moving the tail
+// *** entries up to the appropriate slot.
+
+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 ) {
+
+ // In proper order.
+ prevTag = thisTag;
+
+ } else if ( thisTag == prevTag ) {
+
+ // Duplicate tag, keep the 2nd copy, move the tail of the array up, prevTag is unchanged.
+ memcpy ( &ifdEntries[i-1], &ifdEntries[i], 12*(tagCount-i) ); // AUDIT: Safe, moving tail forward, i >= 1.
+ --tagCount;
+ --i; // ! Don't move forward in the array, we've moved the unseen part up.
+
+ } else if ( thisTag < prevTag ) {
+
+ // Out of order, move this tag up, prevTag is unchanged. Might still be a duplicate!
+ XMP_Int32 j; // ! Need a signed value.
+ 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.
+ ifdEntries[j] = ifdEntries[i];
+ memcpy ( &ifdEntries[i], &ifdEntries[i+1], 12*(tagCount-(i+1)) ); // AUDIT: Safe, moving tail forward, i >= 1.
+ --tagCount;
+ --i; // ! Don't move forward in the array, we've moved the unseen part up.
+
+ } else {
+
+ // Move the out of order entry to position j+1, move the middle of the array down.
+ TweakedIFDEntry temp = ifdEntries[i];
+ ++j; // ! So the insertion index becomes j.
+ memcpy ( &ifdEntries[j+1], &ifdEntries[j], 12*(i-j) ); // AUDIT: Safe, moving less than i entries to a location before i.
+ ifdEntries[j] = temp;
+
+ }
+
+ }
+
+ }
+
+ thisIFD->count = tagCount; // Save the final count.
+
+} // TIFF_MemoryReader::SortIFD
+
+// =================================================================================================
+// TIFF_MemoryReader::GetIFD
+// =========================
+
+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];
+ info.dataPtr = this->GetDataPtr ( thisTag );
+
+ (*ifdMap)[info.id] = info;
+
+ }
+
+ }
+
+ return true;
+
+} // TIFF_MemoryReader::GetIFD
+
+// =================================================================================================
+// TIFF_MemoryReader::FindTagInIFD
+// ===============================
+
+const TIFF_MemoryReader::TweakedIFDEntry* TIFF_MemoryReader::FindTagInIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) const
+{
+ 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;
+ } else if ( spanMiddle->id > id ) {
+ spanLength = halfLength; // Discard the middle.
+ } else {
+ 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;
+
+} // TIFF_MemoryReader::FindTagInIFD
+
+// =================================================================================================
+// TIFF_MemoryReader::GetValueOffset
+// =================================
+
+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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag
+// =========================
+
+bool TIFF_MemoryReader::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const
+{
+ 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
+
+// =================================================================================================
+
+static inline XMP_Uns8 GetUns8 ( const void* dataPtr )
+{
+ return *((XMP_Uns8*)dataPtr);
+}
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_Integer
+// =================================
+//
+// Tolerate any size or sign.
+
+bool TIFF_MemoryReader::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const
+{
+ const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id );
+ if ( thisTag == 0 ) return false;
+
+ if ( thisTag->type > kTIFF_LastType ) return false; // Unknown type.
+ if ( thisTag->bytes != kTIFF_TypeSizes[thisTag->type] ) return false; // Wrong count.
+
+ XMP_Uns32 uns32;
+ XMP_Int32 int32;
+
+ switch ( thisTag->type ) {
+
+ case kTIFF_ByteType:
+ uns32 = GetUns8 ( this->GetDataPtr ( thisTag ) );
+ break;
+
+ case kTIFF_ShortType:
+ uns32 = this->GetUns16 ( this->GetDataPtr ( thisTag ) );
+ break;
+
+ case kTIFF_LongType:
+ uns32 = this->GetUns32 ( this->GetDataPtr ( thisTag ) );
+ break;
+
+ case kTIFF_SByteType:
+ int32 = (XMP_Int8) GetUns8 ( this->GetDataPtr ( thisTag ) );
+ uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set.
+ break;
+
+ case kTIFF_SShortType:
+ int32 = (XMP_Int16) this->GetUns16 ( this->GetDataPtr ( thisTag ) );
+ uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set.
+ break;
+
+ case kTIFF_SLongType:
+ int32 = (XMP_Int32) this->GetUns32 ( this->GetDataPtr ( thisTag ) );
+ uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set.
+ break;
+
+ default: return false;
+
+ }
+
+ if ( data != 0 ) *data = uns32;
+ return true;
+
+} // TIFF_MemoryReader::GetTag_Integer
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_Byte
+// ==============================
+
+bool TIFF_MemoryReader::GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_SByte
+// ===============================
+
+bool TIFF_MemoryReader::GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_Short
+// ===============================
+
+bool TIFF_MemoryReader::GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_SShort
+// ================================
+
+bool TIFF_MemoryReader::GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_Long
+// ==============================
+
+bool TIFF_MemoryReader::GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_SLong
+// ===============================
+
+bool TIFF_MemoryReader::GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_Rational
+// ==================================
+
+bool TIFF_MemoryReader::GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_SRational
+// ===================================
+
+bool TIFF_MemoryReader::GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_Float
+// ===============================
+
+bool TIFF_MemoryReader::GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_Double
+// ================================
+
+bool TIFF_MemoryReader::GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_ASCII
+// ===============================
+
+bool TIFF_MemoryReader::GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const
+{
+ 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
+
+// =================================================================================================
+// TIFF_MemoryReader::GetTag_EncodedString
+// =======================================
+
+bool TIFF_MemoryReader::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const
+{
+ 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;
+
+} // TIFF_MemoryReader::GetTag_EncodedString
+
+// =================================================================================================
+// TIFF_MemoryReader::ParseMemoryStream
+// ====================================
+
+// *** Need to tell TIFF/Exif from TIFF/EP, DNG files are the latter.
+
+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;
+ } else {
+ if ( length > 100*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based TIFF", kXMPErr_BadTIFF );
+ this->tiffStream = (XMP_Uns8*) malloc(length);
+ if ( this->tiffStream == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+ memcpy ( this->tiffStream, data, length ); // AUDIT: Safe, malloc'ed length bytes above.
+ this->ownedStream = true;
+ }
+
+ this->tiffLength = length;
+ XMP_Uns32 ifdLimit = this->tiffLength - 6; // An IFD must start before this offset.
+
+ // 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) ) {
+ XMP_Uns32 exifOffset = this->GetUns32 ( &exifIFDTag->dataOrPos );
+ (void) this->ProcessOneIFD ( exifOffset, kTIFF_ExifIFD );
+ }
+
+ const TweakedIFDEntry* gpsIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer );
+ if ( (gpsIFDTag != 0) && (gpsIFDTag->type == kTIFF_LongType) && (gpsIFDTag->bytes == 4) ) {
+ XMP_Uns32 gpsOffset = this->GetUns32 ( &gpsIFDTag->dataOrPos );
+ if ( (8 <= gpsOffset) && (gpsOffset < ifdLimit) ) { // Ignore a bad GPS IFD offset.
+ (void) this->ProcessOneIFD ( gpsOffset, kTIFF_GPSInfoIFD );
+ }
+ }
+
+ const TweakedIFDEntry* interopIFDTag = this->FindTagInIFD ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer );
+ if ( (interopIFDTag != 0) && (interopIFDTag->type == kTIFF_LongType) && (interopIFDTag->bytes == 4) ) {
+ XMP_Uns32 interopOffset = this->GetUns32 ( &interopIFDTag->dataOrPos );
+ if ( (8 <= interopOffset) && (interopOffset < ifdLimit) ) { // Ignore a bad Interoperability IFD offset.
+ (void) this->ProcessOneIFD ( interopOffset, kTIFF_InteropIFD );
+ }
+ }
+
+} // TIFF_MemoryReader::ParseMemoryStream
+
+// =================================================================================================
+// TIFF_MemoryReader::ProcessOneIFD
+// ================================
+
+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);
+
+ if ( ifdCount >= 0x8000 ) XMP_Throw ( "Outrageous IFD count", kXMPErr_BadTIFF );
+ if ( (XMP_Uns32)(2 + ifdCount*12 + 4) > (this->tiffLength - ifdOffset) ) {
+ XMP_Throw ( "Out of bounds IFD", kXMPErr_BadTIFF );
+ }
+
+ 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 ) {
+ Flip2 ( &thisEntry->id );
+ Flip2 ( &thisEntry->type );
+ Flip4 ( &thisEntry->bytes );
+ }
+
+ if ( thisEntry->id <= prevTag ) needsSorting = true;
+ prevTag = thisEntry->id;
+
+ if ( (thisEntry->type < kTIFF_ByteType) || (thisEntry->type > kTIFF_LastType) ) continue; // Bad type, skip this tag.
+
+ thisEntry->bytes *= (XMP_Uns32)kTIFF_TypeSizes[thisEntry->type];
+ if ( thisEntry->bytes > 4 ) {
+ if ( ! this->nativeEndian ) Flip4 ( &thisEntry->dataOrPos );
+ if ( (thisEntry->dataOrPos < 8) || (thisEntry->dataOrPos >= this->tiffLength) ) {
+ thisEntry->bytes = thisEntry->dataOrPos = 0; // Make this bad tag look empty.
+ }
+ if ( thisEntry->bytes > (this->tiffLength - thisEntry->dataOrPos) ) {
+ thisEntry->bytes = thisEntry->dataOrPos = 0; // Make this bad tag look empty.
+ }
+ }
+
+ }
+
+ 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/XMPFiles/source/FormatSupport/TIFF_Support.cpp b/XMPFiles/source/FormatSupport/TIFF_Support.cpp
new file mode 100644
index 0000000..9da9b52
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/TIFF_Support.cpp
@@ -0,0 +1,439 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp"
+#include "source/EndianUtils.hpp"
+#include "source/XIO.hpp"
+
+#include "source/UnicodeConversions.hpp"
+
+// =================================================================================================
+/// \file TIFF_Support.cpp
+/// \brief Manager for parsing and serializing TIFF streams.
+///
+// =================================================================================================
+
+// =================================================================================================
+// TIFF_Manager::TIFF_Manager
+// ==========================
+
+static bool sFirstCTor = true;
+
+TIFF_Manager::TIFF_Manager()
+ : bigEndian(false), nativeEndian(false),
+ GetUns16(0), GetUns32(0), GetFloat(0), GetDouble(0),
+ PutUns16(0), PutUns32(0), PutFloat(0), PutDouble(0)
+{
+
+ if ( sFirstCTor ) {
+ sFirstCTor = false;
+ #if XMP_DebugBuild
+ for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { // Make sure the known tag arrays are sorted.
+ for ( const XMP_Uns16* idPtr = sKnownTags[ifd]; *idPtr != 0xFFFF; ++idPtr ) {
+ XMP_Assert ( *idPtr < *(idPtr+1) );
+ }
+ }
+ #endif
+ }
+
+} // TIFF_Manager::TIFF_Manager
+
+// =================================================================================================
+// TIFF_Manager::CheckTIFFHeader
+// =============================
+//
+// Checks the 4 byte TIFF prefix for validity and endianness. Sets the endian flags and the Get
+// function pointers. Returns the 0th IFD offset.
+
+XMP_Uns32 TIFF_Manager::CheckTIFFHeader ( const XMP_Uns8* tiffPtr, XMP_Uns32 length )
+{
+ if ( length < kEmptyTIFFLength ) XMP_Throw ( "The TIFF is too small", kXMPErr_BadTIFF );
+
+ XMP_Uns32 tiffPrefix = (tiffPtr[0] << 24) | (tiffPtr[1] << 16) | (tiffPtr[2] << 8) | (tiffPtr[3]);
+
+ if ( tiffPrefix == kBigEndianPrefix ) {
+ this->bigEndian = true;
+ } else if ( tiffPrefix == kLittleEndianPrefix ) {
+ this->bigEndian = false;
+ } else {
+ XMP_Throw ( "Unrecognized TIFF prefix", kXMPErr_BadTIFF );
+ }
+
+ this->nativeEndian = (this->bigEndian == kBigEndianHost);
+
+ if ( this->bigEndian ) {
+
+ this->GetUns16 = GetUns16BE;
+ this->GetUns32 = GetUns32BE;
+ this->GetFloat = GetFloatBE;
+ this->GetDouble = GetDoubleBE;
+
+ this->PutUns16 = PutUns16BE;
+ this->PutUns32 = PutUns32BE;
+ this->PutFloat = PutFloatBE;
+ this->PutDouble = PutDoubleBE;
+
+ } else {
+
+ this->GetUns16 = GetUns16LE;
+ this->GetUns32 = GetUns32LE;
+ this->GetFloat = GetFloatLE;
+ this->GetDouble = GetDoubleLE;
+
+ this->PutUns16 = PutUns16LE;
+ this->PutUns32 = PutUns32LE;
+ this->PutFloat = PutFloatLE;
+ this->PutDouble = PutDoubleLE;
+
+ }
+
+ XMP_Uns32 mainIFDOffset = this->GetUns32 ( tiffPtr+4 ); // ! Do this after setting the Get/Put procs!
+
+ if ( mainIFDOffset != 0 ) { // Tolerate empty TIFF even though formally invalid.
+ if ( (length < (kEmptyTIFFLength + kEmptyIFDLength)) ||
+ (mainIFDOffset < kEmptyTIFFLength) || (mainIFDOffset > (length - kEmptyIFDLength)) ) {
+ XMP_Throw ( "Invalid primary IFD offset", kXMPErr_BadTIFF );
+ }
+ }
+
+ return mainIFDOffset;
+
+} // TIFF_Manager::CheckTIFFHeader
+
+// =================================================================================================
+// TIFF_Manager::SetTag_Integer
+// ============================
+
+void TIFF_Manager::SetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 data32 )
+{
+
+ if ( data32 > 0xFFFF ) {
+ this->SetTag ( ifd, id, kTIFF_LongType, 1, &data32 );
+ } else {
+ XMP_Uns16 data16 = (XMP_Uns16)data32;
+ this->SetTag ( ifd, id, kTIFF_ShortType, 1, &data16 );
+ }
+
+} // TIFF_Manager::SetTag_Integer
+
+// =================================================================================================
+// TIFF_Manager::SetTag_Byte
+// =========================
+
+void TIFF_Manager::SetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8 data )
+{
+
+ this->SetTag ( ifd, id, kTIFF_ByteType, 1, &data );
+
+} // TIFF_Manager::SetTag_Byte
+
+// =================================================================================================
+// TIFF_Manager::SetTag_SByte
+// ==========================
+
+void TIFF_Manager::SetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8 data )
+{
+
+ this->SetTag ( ifd, id, kTIFF_SByteType, 1, &data );
+
+} // TIFF_Manager::SetTag_SByte
+
+// =================================================================================================
+// TIFF_Manager::SetTag_Short
+// ==========================
+
+void TIFF_Manager::SetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 clientData )
+{
+ XMP_Uns16 streamData;
+
+ this->PutUns16 ( clientData, &streamData );
+ this->SetTag ( ifd, id, kTIFF_ShortType, 1, &streamData );
+
+} // TIFF_Manager::SetTag_Short
+
+// =================================================================================================
+// TIFF_Manager::SetTag_SShort
+// ===========================
+
+ void TIFF_Manager::SetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16 clientData )
+{
+ XMP_Int16 streamData;
+
+ this->PutUns16 ( (XMP_Uns16)clientData, &streamData );
+ this->SetTag ( ifd, id, kTIFF_SShortType, 1, &streamData );
+
+} // TIFF_Manager::SetTag_SShort
+
+// =================================================================================================
+// TIFF_Manager::SetTag_Long
+// =========================
+
+ void TIFF_Manager::SetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 clientData )
+{
+ XMP_Uns32 streamData;
+
+ this->PutUns32 ( clientData, &streamData );
+ this->SetTag ( ifd, id, kTIFF_LongType, 1, &streamData );
+
+} // TIFF_Manager::SetTag_Long
+
+// =================================================================================================
+// TIFF_Manager::SetTag_SLong
+// ==========================
+
+ void TIFF_Manager::SetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32 clientData )
+{
+ XMP_Int32 streamData;
+
+ this->PutUns32 ( (XMP_Uns32)clientData, &streamData );
+ this->SetTag ( ifd, id, kTIFF_SLongType, 1, &streamData );
+
+} // TIFF_Manager::SetTag_SLong
+
+// =================================================================================================
+// TIFF_Manager::SetTag_Rational
+// =============================
+
+struct StreamRational { XMP_Uns32 num, denom; };
+
+ void TIFF_Manager::SetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 clientNum, XMP_Uns32 clientDenom )
+{
+ StreamRational streamData;
+
+ this->PutUns32 ( clientNum, &streamData.num );
+ this->PutUns32 ( clientDenom, &streamData.denom );
+ this->SetTag ( ifd, id, kTIFF_RationalType, 1, &streamData );
+
+} // TIFF_Manager::SetTag_Rational
+
+// =================================================================================================
+// TIFF_Manager::SetTag_SRational
+// ==============================
+
+ void TIFF_Manager::SetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32 clientNum, XMP_Int32 clientDenom )
+{
+ StreamRational streamData;
+
+ this->PutUns32 ( (XMP_Uns32)clientNum, &streamData.num );
+ this->PutUns32 ( (XMP_Uns32)clientDenom, &streamData.denom );
+ this->SetTag ( ifd, id, kTIFF_SRationalType, 1, &streamData );
+
+} // TIFF_Manager::SetTag_SRational
+
+// =================================================================================================
+// TIFF_Manager::SetTag_Float
+// ==========================
+
+ void TIFF_Manager::SetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float clientData )
+{
+ float streamData;
+
+ this->PutFloat ( clientData, &streamData );
+ this->SetTag ( ifd, id, kTIFF_FloatType, 1, &streamData );
+
+} // TIFF_Manager::SetTag_Float
+
+// =================================================================================================
+// TIFF_Manager::SetTag_Double
+// ===========================
+
+ void TIFF_Manager::SetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double clientData )
+{
+ double streamData;
+
+ this->PutDouble ( clientData, &streamData );
+ this->SetTag ( ifd, id, kTIFF_DoubleType, 1, &streamData );
+
+} // TIFF_Manager::SetTag_Double
+
+// =================================================================================================
+// TIFF_Manager::SetTag_ASCII
+// ===========================
+
+void TIFF_Manager::SetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr data )
+{
+
+ this->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)(strlen(data) + 1), data ); // ! Include trailing nul.
+
+} // TIFF_Manager::SetTag_ASCII
+
+// =================================================================================================
+// UTF16_to_UTF8
+// =============
+
+static void UTF16_to_UTF8 ( const UTF16Unit * utf16Ptr, size_t utf16Len, bool bigEndian, std::string * outStr )
+{
+ UTF16_to_UTF8_Proc ToUTF8 = 0;
+ if ( bigEndian ) {
+ ToUTF8 = UTF16BE_to_UTF8;
+ } else {
+ ToUTF8 = UTF16LE_to_UTF8;
+ }
+
+ UTF8Unit buffer [1000];
+ size_t inCount, outCount;
+
+ outStr->erase();
+ outStr->reserve ( utf16Len * 2 ); // As good a guess as any.
+
+ while ( utf16Len > 0 ) {
+ ToUTF8 ( utf16Ptr, utf16Len, buffer, sizeof(buffer), &inCount, &outCount );
+ outStr->append ( (XMP_StringPtr)buffer, outCount );
+ utf16Ptr += inCount;
+ utf16Len -= inCount;
+ }
+
+} // UTF16_to_UTF8
+
+// =================================================================================================
+// TIFF_Manager::DecodeString
+// ==========================
+//
+// Convert an explicitly encoded string to UTF-8. The input must be encoded according to table 6 of
+// the Exif 2.2 specification. The input pointer is to the start of the 8 byte header, the length is
+// the full length. In other words, the pointer and length from the IFD entry. Returns true if the
+// encoding is supported and the conversion succeeds.
+
+// *** JIS encoding is not supported yet. Need a static mapping table from JIS X 208-1990 to Unicode.
+
+bool TIFF_Manager::DecodeString ( const void * encodedPtr, size_t encodedLen, std::string* utf8Str ) const
+{
+ utf8Str->erase();
+ if ( encodedLen < 8 ) return false; // Need to have at least the 8 byte header.
+
+ XMP_StringPtr typePtr = (XMP_StringPtr)encodedPtr;
+ XMP_StringPtr valuePtr = typePtr + 8;
+ size_t valueLen = encodedLen - 8;
+
+ if ( *typePtr == 'A' ) {
+
+ utf8Str->assign ( valuePtr, valueLen );
+ return true;
+
+ } 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.
+ 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.
+ }
+
+ } else if ( *typePtr == 'J' ) {
+
+ return false; // ! Ignore JIS for now.
+
+ }
+
+ return false; // ! Ignore all other encodings.
+
+} // TIFF_Manager::DecodeString
+
+// =================================================================================================
+// UTF8_to_UTF16
+// =============
+
+static void UTF8_to_UTF16 ( const UTF8Unit * utf8Ptr, size_t utf8Len, bool bigEndian, std::string * outStr )
+{
+ UTF8_to_UTF16_Proc ToUTF16 = 0;
+ if ( bigEndian ) {
+ ToUTF16 = UTF8_to_UTF16BE;
+ } else {
+ ToUTF16 = UTF8_to_UTF16LE;
+ }
+
+ enum { kUTF16Len = 1000 };
+ UTF16Unit buffer [kUTF16Len];
+ size_t inCount, outCount;
+
+ outStr->erase();
+ outStr->reserve ( utf8Len * 2 ); // As good a guess as any.
+
+ while ( utf8Len > 0 ) {
+ ToUTF16 ( utf8Ptr, utf8Len, buffer, kUTF16Len, &inCount, &outCount );
+ outStr->append ( (XMP_StringPtr)buffer, outCount*2 );
+ utf8Ptr += inCount;
+ utf8Len -= inCount;
+ }
+
+} // UTF8_to_UTF16
+
+// =================================================================================================
+// TIFF_Manager::EncodeString
+// ==========================
+//
+// Convert a UTF-8 string to an explicitly encoded form according to table 6 of the Exif 2.2
+// specification. Returns true if the encoding is supported and the conversion succeeds.
+
+// *** JIS encoding is not supported yet. Need a static mapping table to JIS X 208-1990 from Unicode.
+
+bool TIFF_Manager::EncodeString ( const std::string& utf8Str, XMP_Uns8 encoding, std::string* encodedStr )
+{
+ encodedStr -> erase();
+
+ if ( encoding == kTIFF_EncodeASCII ) {
+
+ encodedStr->assign ( "ASCII\0\0\0", 8 );
+ XMP_Assert (encodedStr->size() == 8 );
+
+ encodedStr->append ( utf8Str ); // ! Let the caller filter the value. (?)
+
+ return true;
+
+ } else if ( encoding == kTIFF_EncodeUnicode ) {
+
+ encodedStr->assign ( "UNICODE\0", 8 );
+ XMP_Assert (encodedStr->size() == 8 );
+
+ try {
+ std::string temp;
+ UTF8_to_UTF16 ( (const UTF8Unit*)utf8Str.c_str(), utf8Str.size(), this->bigEndian, &temp );
+ encodedStr->append ( temp );
+ return true;
+ } catch ( ... ) {
+ return false; // Ignore the tag if there are conversion errors.
+ }
+
+ } else if ( encoding == kTIFF_EncodeJIS ) {
+
+ XMP_Throw ( "Encoding to JIS is not implemented", kXMPErr_Unimplemented );
+
+ // encodedStr->assign ( "JIS\0\0\0\0\0", 8 );
+ // XMP_Assert (encodedStr->size() == 8 );
+
+ // ...
+
+ // return true;
+
+ } else {
+
+ XMP_Throw ( "Invalid TIFF string encoding", kXMPErr_BadParam );
+
+ }
+
+ return false; // ! Should never get here.
+
+} // TIFF_Manager::EncodeString
+
+// =================================================================================================
diff --git a/XMPFiles/source/FormatSupport/TIFF_Support.hpp b/XMPFiles/source/FormatSupport/TIFF_Support.hpp
new file mode 100644
index 0000000..447ab79
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/TIFF_Support.hpp
@@ -0,0 +1,964 @@
+#ifndef __TIFF_Support_hpp__
+#define __TIFF_Support_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include <map>
+#include <stdlib.h>
+#include <string.h>
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+#include "source/EndianUtils.hpp"
+
+// =================================================================================================
+/// \file TIFF_Support.hpp
+/// \brief XMPFiles support for TIFF streams.
+///
+/// This header provides TIFF stream support specific to the needs of XMPFiles. This is not intended
+/// for general purpose TIFF processing. TIFF_Manager is an abstract base class with 2 concrete
+/// derived classes, TIFF_MemoryReader and TIFF_FileWriter.
+///
+/// TIFF_MemoryReader provides read-only support for TIFF streams that are small enough to be kept
+/// entirely in memory. This allows optimizations to reduce heap usage and processing code. It is
+/// sufficient for browsing access to the Exif metadata in JPEG and Photoshop files. Think of
+/// TIFF_MemoryReader as "memory-based AND read-only". Since the entire TIFF stream is available,
+/// GetTag will return information about any tag in the stream.
+///
+/// TIFF_FileWriter is for cases where updates are needed or the TIFF stream is too large to be kept
+/// 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 4 IFDs are processed:
+/// \li The 0th IFD, for the primary image, the first 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 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.
+// =================================================================================================
+
+
+// =================================================================================================
+// TIFF IFD and type constants
+// ===========================
+//
+// These aren't inside TIFF_Manager because static data members can't be initialized there.
+
+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. (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.
+ kTIFF_LastRealIFD = 4,
+ kTIFF_KnownIFDCount = 5,
+ kTIFF_KnownIFD = 9 // The IFD that a tag is known to belong in.
+};
+
+enum { // Constants for the type field of a tag, as defined by TIFF.
+ kTIFF_ShortOrLongType = 0, // ! Not part of the TIFF spec, never in a tag!
+ kTIFF_ByteType = 1,
+ kTIFF_ASCIIType = 2,
+ kTIFF_ShortType = 3,
+ kTIFF_LongType = 4,
+ kTIFF_RationalType = 5,
+ kTIFF_SByteType = 6,
+ kTIFF_UndefinedType = 7,
+ kTIFF_SShortType = 8,
+ kTIFF_SLongType = 9,
+ kTIFF_SRationalType = 10,
+ kTIFF_FloatType = 11,
+ kTIFF_DoubleType = 12,
+ kTIFF_LastType = 12
+};
+
+static const size_t kTIFF_TypeSizes[] = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };
+
+static const bool kTIFF_IsIntegerType[] = { 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0 };
+static const bool kTIFF_IsRationalType[] = { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0 };
+static const bool kTIFF_IsFloatType[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 };
+
+static const char * kTIFF_TypeNames[] = { "ShortOrLong", "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
+ "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL",
+ "FLOAT", "DOUBLE" };
+
+enum { // Encodings for SetTag_EncodedString.
+ kTIFF_EncodeUndefined = 0,
+ kTIFF_EncodeASCII = 1,
+ kTIFF_EncodeUnicode = 2, // UTF-16 in the endianness of the TIFF stream.
+ kTIFF_EncodeJIS = 3, // Exif 2.2 uses JIS X 208-1990.
+ kTIFF_EncodeUnknown = 9
+};
+
+// =================================================================================================
+// Recognized TIFF tags
+// ====================
+
+// -----------------------------------------------------------------------------------------------
+// An enum of IDs for all of the tags as potential interest as metadata. The numberical order does
+// not matter. These are mostly listed in the order of the Exif specification tables for convenience
+// of checking correspondence.
+
+enum {
+
+ // General 0th IFD tags. Some of these can also be in the thumbnail IFD.
+
+ // General tags from Exif 2.3 table 4:
+ kTIFF_ImageWidth = 256,
+ kTIFF_ImageLength = 257,
+ kTIFF_BitsPerSample = 258,
+ kTIFF_Compression = 259,
+ kTIFF_PhotometricInterpretation = 262,
+ kTIFF_Orientation = 274,
+ kTIFF_SamplesPerPixel = 277,
+ kTIFF_PlanarConfiguration = 284,
+ kTIFF_YCbCrCoefficients = 529,
+ kTIFF_YCbCrSubSampling = 530,
+ kTIFF_XResolution = 282,
+ kTIFF_YResolution = 283,
+ kTIFF_ResolutionUnit = 296,
+ kTIFF_TransferFunction = 301,
+ kTIFF_WhitePoint = 318,
+ kTIFF_PrimaryChromaticities = 319,
+ kTIFF_YCbCrPositioning = 531,
+ kTIFF_ReferenceBlackWhite = 532,
+ kTIFF_DateTime = 306,
+ kTIFF_ImageDescription = 270,
+ kTIFF_Make = 271,
+ kTIFF_Model = 272,
+ kTIFF_Software = 305,
+ kTIFF_Artist = 315,
+ kTIFF_Copyright = 33432,
+
+ // Tags defined by Adobe:
+ kTIFF_XMP = 700,
+ kTIFF_IPTC = 33723,
+ kTIFF_PSIR = 34377,
+ 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,
+
+ // Tags that need special handling when rewriting memory-based TIFF.
+ kTIFF_StripOffsets = 273,
+ kTIFF_StripByteCounts = 279,
+ kTIFF_FreeOffsets = 288,
+ kTIFF_FreeByteCounts = 289,
+ kTIFF_TileOffsets = 324,
+ kTIFF_TileByteCounts = 325,
+ kTIFF_SubIFDs = 330,
+ kTIFF_JPEGQTables = 519,
+ kTIFF_JPEGDCTables = 520,
+ kTIFF_JPEGACTables = 521,
+
+ // Exif IFD tags defined in Exif 2.3 table 7.
+
+ kTIFF_ExifVersion = 36864,
+ kTIFF_FlashpixVersion = 40960,
+ kTIFF_ColorSpace = 40961,
+ kTIFF_Gamma = 42240,
+ kTIFF_ComponentsConfiguration = 37121,
+ kTIFF_CompressedBitsPerPixel = 37122,
+ kTIFF_PixelXDimension = 40962,
+ kTIFF_PixelYDimension = 40963,
+ kTIFF_MakerNote = 37500, // Gets deleted when rewriting memory-based TIFF.
+ kTIFF_UserComment = 37510,
+ kTIFF_RelatedSoundFile = 40964,
+ kTIFF_DateTimeOriginal = 36867,
+ kTIFF_DateTimeDigitized = 36868,
+ kTIFF_SubSecTime = 37520,
+ kTIFF_SubSecTimeOriginal = 37521,
+ kTIFF_SubSecTimeDigitized = 37522,
+ kTIFF_ImageUniqueID = 42016,
+ kTIFF_CameraOwnerName = 42032,
+ kTIFF_BodySerialNumber = 42033,
+ kTIFF_LensSpecification = 42034,
+ kTIFF_LensMake = 42035,
+ kTIFF_LensModel = 42036,
+ kTIFF_LensSerialNumber = 42037,
+
+ // Exif IFD tags defined in Exif 2.3 table 8.
+
+ kTIFF_ExposureTime = 33434,
+ kTIFF_FNumber = 33437,
+ kTIFF_ExposureProgram = 34850,
+ kTIFF_SpectralSensitivity = 34852,
+ kTIFF_PhotographicSensitivity = 34855, // ! Called kTIFF_ISOSpeedRatings before Exif 2.3.
+ kTIFF_OECF = 34856,
+ kTIFF_SensitivityType = 34864,
+ kTIFF_StandardOutputSensitivity = 34865,
+ kTIFF_RecommendedExposureIndex = 34866,
+ kTIFF_ISOSpeed = 34867,
+ kTIFF_ISOSpeedLatitudeyyy = 34868,
+ kTIFF_ISOSpeedLatitudezzz = 34869,
+ kTIFF_ShutterSpeedValue = 37377,
+ kTIFF_ApertureValue = 37378,
+ kTIFF_BrightnessValue = 37379,
+ kTIFF_ExposureBiasValue = 37380,
+ kTIFF_MaxApertureValue = 37381,
+ kTIFF_SubjectDistance = 37382,
+ kTIFF_MeteringMode = 37383,
+ kTIFF_LightSource = 37384,
+ kTIFF_Flash = 37385,
+ kTIFF_FocalLength = 37386,
+ kTIFF_SubjectArea = 37396,
+ kTIFF_FlashEnergy = 41483,
+ kTIFF_SpatialFrequencyResponse = 41484,
+ kTIFF_FocalPlaneXResolution = 41486,
+ kTIFF_FocalPlaneYResolution = 41487,
+ kTIFF_FocalPlaneResolutionUnit = 41488,
+ kTIFF_SubjectLocation = 41492,
+ kTIFF_ExposureIndex = 41493,
+ kTIFF_SensingMethod = 41495,
+ kTIFF_FileSource = 41728,
+ kTIFF_SceneType = 41729,
+ kTIFF_CFAPattern = 41730,
+ kTIFF_CustomRendered = 41985,
+ kTIFF_ExposureMode = 41986,
+ kTIFF_WhiteBalance = 41987,
+ kTIFF_DigitalZoomRatio = 41988,
+ kTIFF_FocalLengthIn35mmFilm = 41989,
+ kTIFF_SceneCaptureType = 41990,
+ kTIFF_GainControl = 41991,
+ kTIFF_Contrast = 41992,
+ kTIFF_Saturation = 41993,
+ kTIFF_Sharpness = 41994,
+ kTIFF_DeviceSettingDescription = 41995,
+ kTIFF_SubjectDistanceRange = 41996,
+
+ // GPS IFD tags.
+
+ kTIFF_GPSVersionID = 0,
+ kTIFF_GPSLatitudeRef = 1,
+ kTIFF_GPSLatitude = 2,
+ kTIFF_GPSLongitudeRef = 3,
+ kTIFF_GPSLongitude = 4,
+ kTIFF_GPSAltitudeRef = 5,
+ kTIFF_GPSAltitude = 6,
+ kTIFF_GPSTimeStamp = 7,
+ kTIFF_GPSSatellites = 8,
+ kTIFF_GPSStatus = 9,
+ kTIFF_GPSMeasureMode = 10,
+ kTIFF_GPSDOP = 11,
+ kTIFF_GPSSpeedRef = 12,
+ kTIFF_GPSSpeed = 13,
+ kTIFF_GPSTrackRef = 14,
+ kTIFF_GPSTrack = 15,
+ kTIFF_GPSImgDirectionRef = 16,
+ kTIFF_GPSImgDirection = 17,
+ kTIFF_GPSMapDatum = 18,
+ kTIFF_GPSDestLatitudeRef = 19,
+ kTIFF_GPSDestLatitude = 20,
+ kTIFF_GPSDestLongitudeRef = 21,
+ kTIFF_GPSDestLongitude = 22,
+ kTIFF_GPSDestBearingRef = 23,
+ kTIFF_GPSDestBearing = 24,
+ kTIFF_GPSDestDistanceRef = 25,
+ kTIFF_GPSDestDistance = 26,
+ kTIFF_GPSProcessingMethod = 27,
+ kTIFF_GPSAreaInformation = 28,
+ kTIFF_GPSDateStamp = 29,
+ kTIFF_GPSDifferential = 30,
+ kTIFF_GPSHPositioningError = 31,
+
+ // Special tags that are links to other IFDs.
+
+ kTIFF_ExifIFDPointer = 34665, // Found in 0th IFD
+ kTIFF_GPSInfoIFDPointer = 34853, // Found in 0th IFD
+ kTIFF_InteroperabilityIFDPointer = 40965 // Found in Exif IFD
+
+};
+
+// *** Temporary hack:
+#define kTIFF_ISOSpeedRatings kTIFF_PhotographicSensitivity
+
+// ------------------------------------------------------------------
+// Sorted arrays of the tags that are recognized in the various IFDs.
+
+static const XMP_Uns16 sKnownPrimaryIFDTags[] =
+{
+ kTIFF_ImageWidth, // 256
+ kTIFF_ImageLength, // 257
+ kTIFF_BitsPerSample, // 258
+ kTIFF_Compression, // 259
+ kTIFF_PhotometricInterpretation, // 262
+ kTIFF_ImageDescription, // 270
+ kTIFF_Make, // 271
+ kTIFF_Model, // 272
+ kTIFF_Orientation, // 274
+ kTIFF_SamplesPerPixel, // 277
+ kTIFF_XResolution, // 282
+ kTIFF_YResolution, // 283
+ kTIFF_PlanarConfiguration, // 284
+ kTIFF_ResolutionUnit, // 296
+ kTIFF_TransferFunction, // 301
+ kTIFF_Software, // 305
+ kTIFF_DateTime, // 306
+ kTIFF_Artist, // 315
+ kTIFF_WhitePoint, // 318
+ kTIFF_PrimaryChromaticities, // 319
+ kTIFF_YCbCrCoefficients, // 529
+ kTIFF_YCbCrSubSampling, // 530
+ kTIFF_YCbCrPositioning, // 531
+ kTIFF_ReferenceBlackWhite, // 532
+ kTIFF_XMP, // 700
+ kTIFF_Copyright, // 33432
+ kTIFF_IPTC, // 33723
+ kTIFF_PSIR, // 34377
+ kTIFF_ExifIFDPointer, // 34665
+ kTIFF_GPSInfoIFDPointer, // 34853
+ kTIFF_DNGVersion, // 50706
+ kTIFF_DNGBackwardVersion, // 50707
+ 0xFFFF // Must be last as a sentinel.
+};
+
+static const XMP_Uns16 sKnownThumbnailIFDTags[] =
+{
+ kTIFF_ImageWidth, // 256
+ kTIFF_ImageLength, // 257
+ kTIFF_Compression, // 259
+ kTIFF_JPEGInterchangeFormat, // 513
+ kTIFF_JPEGInterchangeFormatLength, // 514
+ 0xFFFF // Must be last as a sentinel.
+};
+
+static const XMP_Uns16 sKnownExifIFDTags[] =
+{
+ kTIFF_ExposureTime, // 33434
+ kTIFF_FNumber, // 33437
+ kTIFF_ExposureProgram, // 34850
+ kTIFF_SpectralSensitivity, // 34852
+ kTIFF_PhotographicSensitivity, // 34855
+ kTIFF_OECF, // 34856
+ kTIFF_SensitivityType, // 34864
+ kTIFF_StandardOutputSensitivity, // 34865
+ kTIFF_RecommendedExposureIndex, // 34866
+ kTIFF_ISOSpeed, // 34867
+ kTIFF_ISOSpeedLatitudeyyy, // 34868
+ kTIFF_ISOSpeedLatitudezzz, // 34869
+ kTIFF_ExifVersion, // 36864
+ kTIFF_DateTimeOriginal, // 36867
+ kTIFF_DateTimeDigitized, // 36868
+ kTIFF_ComponentsConfiguration, // 37121
+ kTIFF_CompressedBitsPerPixel, // 37122
+ kTIFF_ShutterSpeedValue, // 37377
+ kTIFF_ApertureValue, // 37378
+ kTIFF_BrightnessValue, // 37379
+ kTIFF_ExposureBiasValue, // 37380
+ kTIFF_MaxApertureValue, // 37381
+ kTIFF_SubjectDistance, // 37382
+ kTIFF_MeteringMode, // 37383
+ kTIFF_LightSource, // 37384
+ kTIFF_Flash, // 37385
+ kTIFF_FocalLength, // 37386
+ kTIFF_SubjectArea, // 37396
+ kTIFF_UserComment, // 37510
+ kTIFF_SubSecTime, // 37520
+ kTIFF_SubSecTimeOriginal, // 37521
+ kTIFF_SubSecTimeDigitized, // 37522
+ kTIFF_FlashpixVersion, // 40960
+ kTIFF_ColorSpace, // 40961
+ kTIFF_PixelXDimension, // 40962
+ kTIFF_PixelYDimension, // 40963
+ kTIFF_RelatedSoundFile, // 40964
+ kTIFF_FlashEnergy, // 41483
+ kTIFF_SpatialFrequencyResponse, // 41484
+ kTIFF_FocalPlaneXResolution, // 41486
+ kTIFF_FocalPlaneYResolution, // 41487
+ kTIFF_FocalPlaneResolutionUnit, // 41488
+ kTIFF_SubjectLocation, // 41492
+ kTIFF_ExposureIndex, // 41493
+ kTIFF_SensingMethod, // 41495
+ kTIFF_FileSource, // 41728
+ kTIFF_SceneType, // 41729
+ kTIFF_CFAPattern, // 41730
+ kTIFF_CustomRendered, // 41985
+ kTIFF_ExposureMode, // 41986
+ kTIFF_WhiteBalance, // 41987
+ kTIFF_DigitalZoomRatio, // 41988
+ kTIFF_FocalLengthIn35mmFilm, // 41989
+ kTIFF_SceneCaptureType, // 41990
+ kTIFF_GainControl, // 41991
+ kTIFF_Contrast, // 41992
+ kTIFF_Saturation, // 41993
+ kTIFF_Sharpness, // 41994
+ kTIFF_DeviceSettingDescription, // 41995
+ kTIFF_SubjectDistanceRange, // 41996
+ kTIFF_ImageUniqueID, // 42016
+ kTIFF_CameraOwnerName, // 42032
+ kTIFF_BodySerialNumber, // 42033
+ kTIFF_LensSpecification, // 42034
+ kTIFF_LensMake, // 42035
+ kTIFF_LensModel, // 42036
+ kTIFF_LensSerialNumber, // 42037
+ kTIFF_Gamma, // 42240
+ 0xFFFF // Must be last as a sentinel.
+};
+
+static const XMP_Uns16 sKnownGPSInfoIFDTags[] =
+{
+ kTIFF_GPSVersionID, // 0
+ kTIFF_GPSLatitudeRef, // 1
+ kTIFF_GPSLatitude, // 2
+ kTIFF_GPSLongitudeRef, // 3
+ kTIFF_GPSLongitude, // 4
+ kTIFF_GPSAltitudeRef, // 5
+ kTIFF_GPSAltitude, // 6
+ kTIFF_GPSTimeStamp, // 7
+ kTIFF_GPSSatellites, // 8
+ kTIFF_GPSStatus, // 9
+ kTIFF_GPSMeasureMode, // 10
+ kTIFF_GPSDOP, // 11
+ kTIFF_GPSSpeedRef, // 12
+ kTIFF_GPSSpeed, // 13
+ kTIFF_GPSTrackRef, // 14
+ kTIFF_GPSTrack, // 15
+ kTIFF_GPSImgDirectionRef, // 16
+ kTIFF_GPSImgDirection, // 17
+ kTIFF_GPSMapDatum, // 18
+ kTIFF_GPSDestLatitudeRef, // 19
+ kTIFF_GPSDestLatitude, // 20
+ kTIFF_GPSDestLongitudeRef, // 21
+ kTIFF_GPSDestLongitude, // 22
+ kTIFF_GPSDestBearingRef, // 23
+ kTIFF_GPSDestBearing, // 24
+ kTIFF_GPSDestDistanceRef, // 25
+ kTIFF_GPSDestDistance, // 26
+ kTIFF_GPSProcessingMethod, // 27
+ kTIFF_GPSAreaInformation, // 28
+ kTIFF_GPSDateStamp, // 29
+ kTIFF_GPSDifferential, // 30
+ kTIFF_GPSHPositioningError, // 31
+ 0xFFFF // Must be last as a sentinel.
+};
+
+static const XMP_Uns16 sKnownInteroperabilityIFDTags[] =
+{
+ // ! Yes, there are none at present.
+ 0xFFFF // Must be last as a sentinel.
+};
+
+// ! Make sure these are in the same order as the IFD enum!
+static const XMP_Uns16* sKnownTags[kTIFF_KnownIFDCount] = { sKnownPrimaryIFDTags,
+ sKnownThumbnailIFDTags,
+ sKnownExifIFDTags,
+ sKnownGPSInfoIFDTags,
+ sKnownInteroperabilityIFDTags };
+
+
+// =================================================================================================
+// =================================================================================================
+
+
+// =================================================================================================
+// TIFF_Manager
+// ============
+
+class TIFF_Manager { // The abstract base class.
+public:
+
+ // ---------------------------------------------------------------------------------------------
+ // Types and constants
+
+ static const XMP_Uns32 kBigEndianPrefix = 0x4D4D002AUL;
+ static const XMP_Uns32 kLittleEndianPrefix = 0x49492A00UL;
+
+ 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;
+ XMP_Uns32 count;
+ const void* dataPtr; // ! The data must not be changed! Stream endian format!
+ XMP_Uns32 dataLen; // The length in bytes.
+ TagInfo() : id(0), type(0), count(0), dataPtr(0), dataLen(0) {};
+ 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; };
+ struct SRational { XMP_Int32 num, denom; };
+
+ // ---------------------------------------------------------------------------------------------
+ // 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 4 IFDs:
+ // - The primary image IFD, also known as the 0th IFD. This must be present.
+ // - 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.
+ //
+ // Parsing will silently forget about certain aspects of ill-formed streams. If any tags are
+ // repeated in an IFD, only the last is kept. Any known tags that are in the wrong IFD are
+ // 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 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
+ // not change it. The ifd parameter to \c GetIFD must be for one of the recognized actual IFDs.
+ // The ifd parameter for the GetTag or SetTag methods can be a specific IFD of kTIFF_KnownIFD.
+ // Using the specific IFD will be slightly faster, saving a lookup in the known tag map. An
+ // exception is thrown if kTIFF_KnownIFD is passed to GetTag or SetTag and the tag is not known.
+ // \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
+ // count is not 1. \c SetTag_Integer replaces an existing tag regardless of type or count, the
+ // new tag will have type short or long.
+
+ 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.
+
+ // *** Should also add support for ASCII multi-strings?
+
+ virtual bool GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const = 0;
+ virtual bool GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data ) const = 0;
+ virtual bool GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data ) const = 0;
+ virtual bool GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* data ) const = 0;
+ virtual bool GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const = 0;
+ virtual bool GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data ) const = 0;
+
+ virtual bool GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* data ) const = 0;
+ virtual bool GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* data ) const = 0;
+
+ virtual bool GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const = 0;
+ 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 );
+ void SetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8 data );
+ void SetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 data );
+ void SetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16 data );
+ void SetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 data );
+ void SetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32 data );
+
+ void SetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 num, XMP_Uns32 denom );
+ void SetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32 num, XMP_Int32 denom );
+
+ void SetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float data );
+ 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 );
+
+ // ---------------------------------------------------------------------------------------------
+ // \c IsChanged returns true if a read-write stream has changes that need to be saved. This is
+ // only the case when a \c SetTag method has been called. It is not true for changes related to
+ // parsing normalization such as sorting of tags. \c IsChanged returns false for read-only streams.
+
+ virtual bool IsChanged() = 0;
+
+ // ---------------------------------------------------------------------------------------------
+ // \c IsLegacyChanged returns true if a read-write stream has changes that need to be saved to
+ // tags other than the XMP (tag 700). This only the case when a \c SetTag method has been
+ // called. It is not true for changes related to parsing normalization such as sorting of tags.
+ // \c IsLegacyChanged returns false for read-only streams.
+
+ virtual bool IsLegacyChanged() = 0;
+
+ // ---------------------------------------------------------------------------------------------
+ // \c UpdateMemoryStream is mainly applicable to memory-based read-write streams. It recomposes
+ // the memory stream to incorporate all changes. The new length and data pointer are returned.
+ // \c UpdateMemoryStream can be used with a read-only memory stream to get the raw stream info.
+ //
+ // \c UpdateFileStream updates file-based TIFF. The client must guarantee that the TIFF portion
+ // of the file matches that from the parse in the file-based constructor. Offsets saved from that
+ // parse must still be valid. The open file reference need not be the same, e.g. the client can
+ // be doing a crash-safe update into a temporary copy.
+ //
+ // Both \c UpdateMemoryStream and \c UpdateFileStream use an update-by-append model. Changes are
+ // written in-place where they fit, anything requiring growth is appended to the end and the old
+ // space is abandoned. The end for memory-based TIFF is the end of the data block, the end for
+ // file-based TIFF is the end of the file. This update-by-append model has the advantage of not
+ // perturbing any hidden offsets, a common feature of proprietary MakerNotes.
+ //
+ // 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 ( XMP_IO* 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 ( XMP_IO* 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;
+ GetDouble_Proc GetDouble;
+
+ PutUns16_Proc PutUns16; // Put values into the TIFF stream.
+ PutUns32_Proc PutUns32; // Always native endian on the outside, stream endian in the stream.
+ PutFloat_Proc PutFloat;
+ PutDouble_Proc PutDouble;
+
+ virtual ~TIFF_Manager() {};
+
+protected:
+
+ bool bigEndian, nativeEndian;
+
+ XMP_Uns32 CheckTIFFHeader ( const XMP_Uns8* tiffPtr, XMP_Uns32 length );
+
+ TIFF_Manager(); // Force clients to use the reader or writer derived classes.
+
+ struct RawIFDEntry {
+ XMP_Uns16 id;
+ XMP_Uns16 type;
+ XMP_Uns32 count;
+ XMP_Uns32 dataOrOffset;
+ };
+
+}; // TIFF_Manager
+
+
+// =================================================================================================
+// =================================================================================================
+
+
+// =================================================================================================
+// TIFF_MemoryReader
+// =================
+
+class TIFF_MemoryReader : public TIFF_Manager { // The derived class for memory-based read-only access.
+public:
+
+ 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;
+
+ bool GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const;
+ bool GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data ) const;
+ bool GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data ) const;
+ bool GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* data ) const;
+ bool GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const;
+ bool GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data ) const;
+
+ bool GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* data ) const;
+ bool GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* data ) const;
+
+ bool GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const;
+ bool GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const;
+
+ bool GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const;
+
+ 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 ( XMP_IO* 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 ( XMP_IO* fileRef ) { NotAppropriate(); };
+
+ TIFF_MemoryReader() : ownedStream(false), tiffStream(0), tiffLength(0) {};
+
+ virtual ~TIFF_MemoryReader() { if ( this->ownedStream ) free ( this->tiffStream ); };
+
+private:
+
+ bool ownedStream;
+
+ XMP_Uns8* tiffStream;
+ XMP_Uns32 tiffLength;
+
+ // Memory usage notes: TIFF_MemoryReader is for memory-based read-only usage (both apply). There
+ // is no need to ever allocate separate blocks of memory, everything is used directly from the
+ // TIFF stream. Data pointers are computed on the fly, the offset field is 4 bytes and pointers
+ // will be 8 bytes for 64-bit platforms.
+
+ struct TweakedIFDEntry { // ! Most fields are in native byte order, dataOrPos is for offsets only.
+ XMP_Uns16 id;
+ XMP_Uns16 type;
+ XMP_Uns32 bytes;
+ 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
+
+
+// =================================================================================================
+// =================================================================================================
+
+
+// =================================================================================================
+// TIFF_FileWriter
+// ===============
+
+class TIFF_FileWriter : public TIFF_Manager { // The derived class for file-based or read-write access.
+public:
+
+ 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;
+
+ bool GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const;
+ bool GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data ) const;
+ bool GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data ) const;
+ bool GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* data ) const;
+ bool GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const;
+ bool GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data ) const;
+
+ bool GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* data ) const;
+ bool GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* data ) const;
+
+ bool GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const;
+ bool GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const;
+
+ bool GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const;
+
+ 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 ( XMP_IO* fileRef );
+
+ void IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen );
+
+ XMP_Uns32 UpdateMemoryStream ( void** dataPtr, bool condenseStream = false );
+ void UpdateFileStream ( XMP_IO* fileRef );
+
+ TIFF_FileWriter();
+
+ virtual ~TIFF_FileWriter();
+
+private:
+
+ bool changed, legacyDeleted;
+ bool memParsed, fileParsed;
+ bool ownedStream;
+
+ XMP_Uns8* memStream;
+ XMP_Uns32 tiffLength;
+
+ // Memory usage notes: TIFF_FileWriter is for file-based OR read/write usage. For memory-based
+ // streams the dataPtr is initially into the stream, regardless of size. For file-based streams
+ // the dataPtr is initially a separate allocation for large values (over 4 bytes), and points to
+ // 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:
+
+ XMP_Uns16 id;
+ XMP_Uns16 type;
+ XMP_Uns32 count;
+ XMP_Uns32 dataLen;
+ XMP_Uns32 smallValue; // Small value in stream endianness, but "left" justified.
+ XMP_Uns8* dataPtr; // Parsing captures all small values, only large ones that we care about.
+ XMP_Uns32 origDataLen; // The original (parse time) data length in bytes.
+ XMP_Uns32 origDataOffset; // The original data offset, regardless of length.
+ bool changed;
+ bool fileBased;
+
+ inline void FreeData() {
+ if ( this->fileBased || this->changed ) {
+ if ( (this->dataLen > 4) && (this->dataPtr != 0) ) { free ( this->dataPtr ); this->dataPtr = 0; }
+ }
+ }
+
+ InternalTagInfo ( XMP_Uns16 _id, XMP_Uns16 _type, XMP_Uns32 _count, bool _fileBased )
+ : id(_id), type(_type), count(_count), dataLen(0), smallValue(0), dataPtr(0),
+ origDataLen(0), origDataOffset(0), changed(false), fileBased(_fileBased) {};
+ ~InternalTagInfo() { this->FreeData(); };
+
+ void operator= ( const InternalTagInfo & in )
+ {
+ // ! Gag! Transfer ownership of the dataPtr!
+ this->FreeData();
+ memcpy ( this, &in, sizeof(*this) ); // AUDIT: Use of sizeof(InternalTagInfo) is safe.
+ if ( this->dataLen <= 4 ) {
+ this->dataPtr = (XMP_Uns8*) &this->smallValue; // Don't use the copied pointer.
+ } else {
+ *((XMP_Uns8**)&in.dataPtr) = 0; // The pointer is now owned by "this".
+ }
+ };
+
+ private:
+
+ InternalTagInfo() // Hidden on purpose, fileBased must be properly set.
+ : id(0), type(0), count(0), dataLen(0), smallValue(0), dataPtr(0),
+ 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.
+ XMP_Uns32 origIFDOffset; // Original stream offset of the IFD.
+ XMP_Uns32 origNextIFD; // Original stream offset of the following IFD.
+ InternalTagMap tagMap;
+ InternalIFDInfo() : changed(false), origCount(0), origIFDOffset(0), origNextIFD(0) {};
+ inline void clear()
+ {
+ this->changed = false;
+ this->origCount = 0;
+ this->origIFDOffset = this->origNextIFD = 0;
+ 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 );
+ XMP_Uns32 ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, XMP_IO* fileRef, void* _ioBuf );
+ // *** Temporary hack above, _ioBuf is IOBuffer* but don't want to include XIO.hpp.
+
+ void ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XMP_Uns8 ifd );
+
+ void* CopyTagToMasterIFD ( const TagInfo& ps6Tag, InternalIFDInfo* masterIFD );
+
+ void PreflightIFDLinkage();
+
+ XMP_Uns32 DetermineVisibleLength();
+
+ XMP_Uns32 DetermineAppendInfo ( XMP_Uns32 appendedOrigin,
+ bool appendedIFDs[kTIFF_KnownIFDCount],
+ 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 ( XMP_IO* fileRef, InternalIFDInfo & thisIFD );
+
+}; // TIFF_FileWriter
+
+
+// =================================================================================================
+
+#endif // __TIFF_Support_hpp__
diff --git a/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp
new file mode 100644
index 0000000..a11e481
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp
@@ -0,0 +1,347 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 <string.h>
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h"
+#include "source/Endian.h"
+
+using namespace IFF_RIFF;
+
+static const XMP_Uns32 kBEXTSizeMin = 602; // at minimum 602 bytes
+
+static const XMP_Uns32 kSizeDescription = 256;
+static const XMP_Uns32 kSizeOriginator = 32;
+static const XMP_Uns32 kSizeOriginatorReference = 32;
+static const XMP_Uns32 kSizeOriginationDate = 10;
+static const XMP_Uns32 kSizeOriginationTime = 8;
+
+// Needed to be able to memcpy directly to this struct.
+#pragma pack ( push, 1 )
+ struct BEXT
+ {
+ char mDescription[256];
+ char mOriginator[32];
+ char mOriginatorReference[32];
+ char mOriginationDate[10];
+ char mOriginationTime[8];
+ XMP_Uns32 mTimeReferenceLow;
+ XMP_Uns32 mTimeReferenceHigh;
+ XMP_Uns16 mVersion;
+ XMP_Uns8 mUMID[64];
+ XMP_Uns8 mReserved[190];
+ };
+#pragma pack ( pop )
+
+//-----------------------------------------------------------------------------
+//
+// [static] convertLF(...)
+//
+// Purpose: Convert Mac/Unix line feeds to CR/LF
+//
+//-----------------------------------------------------------------------------
+
+void BEXTMetadata::NormalizeLF( std::string& str )
+{
+ XMP_Uns32 i = 0;
+ while( i < str.length() )
+ {
+ char ch = str[i];
+
+ if( ch == 0x0d )
+ {
+ //
+ // possible Mac lf
+ //
+ if( i+1 < str.length() )
+ {
+ if( str[i+1] != 0x0a )
+ {
+ //
+ // insert missing LF character
+ //
+ str.insert( i+1, 1, 0x0a );
+ }
+
+ i += 2;
+ }
+ else
+ {
+ str.push_back( 0x0a );
+ }
+ }
+ else if( ch == 0x0a )
+ {
+ //
+ // possible Unix LF
+ //
+ if( i == 0 || str[i-1] != 0x0d )
+ {
+ //
+ // insert missing CR character
+ //
+ str.insert( i, 1, 0x0d );
+ i += 2;
+ }
+ else
+ {
+ i++;
+ }
+ }
+ else
+ {
+ i++;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// BEXTMetadata::BEXTMetadata(...)
+//
+// Purpose: ctor/dtor
+//
+//-----------------------------------------------------------------------------
+
+BEXTMetadata::BEXTMetadata()
+{
+}
+
+BEXTMetadata::~BEXTMetadata()
+{
+}
+
+//-----------------------------------------------------------------------------
+//
+// BEXTMetadata::parse(...)
+//
+// Purpose: Parses the given memory block and creates a data model representation
+// The implementation expects that the memory block is the data area of
+// the BEXT chunk and its size is at least as big as the minimum size
+// of a BEXT data block.
+// Throws exceptions if parsing is not possible
+//
+//-----------------------------------------------------------------------------
+
+void BEXTMetadata::parse( const XMP_Uns8* chunkData, XMP_Uns64 size )
+{
+ if( size >= kBEXTSizeMin )
+ {
+ const LittleEndian& LE = LittleEndian::getInstance();
+
+ BEXT bext;
+ memset( &bext, 0, kBEXTSizeMin );
+
+ //
+ // copy input data into BEXT block (except CodingHistory field)
+ // Safe as fixed size matches size of struct that is #pragma packed(1)
+ //
+ memcpy( &bext, chunkData, kBEXTSizeMin );
+
+ //
+ // copy CodingHistory
+ //
+ if( size > kBEXTSizeMin )
+ {
+ this->setValue<std::string>( kCodingHistory, std::string( reinterpret_cast<const char*>(&chunkData[kBEXTSizeMin]), static_cast<std::string::size_type>(size - kBEXTSizeMin) ) );
+ }
+
+ //
+ // copy values to map
+ //
+ this->setValue<std::string>( kDescription, std::string( bext.mDescription, kSizeDescription ) );
+ this->setValue<std::string>( kOriginator, std::string( bext.mOriginator, kSizeOriginator ) );
+ this->setValue<std::string>( kOriginatorReference, std::string( bext.mOriginatorReference, kSizeOriginatorReference ) );
+ this->setValue<std::string>( kOriginationDate, std::string( bext.mOriginationDate, kSizeOriginationDate ) );
+ this->setValue<std::string>( kOriginationTime, std::string( bext.mOriginationTime, kSizeOriginationTime ) );
+
+ this->setValue<XMP_Uns64>( kTimeReference, LE.getUns64( &bext.mTimeReferenceLow ) );
+ this->setValue<XMP_Uns16>( kVersion, LE.getUns16( &bext.mVersion ) );
+
+ this->setArray<XMP_Uns8>( kUMID, bext.mUMID, 64 );
+
+ this->resetChanges();
+ }
+ else
+ {
+ XMP_Throw ( "Not a valid BEXT chunk", kXMPErr_BadFileFormat );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// BEXTMetadata::serialize(...)
+//
+// Purpose: Serializes the data model to a memory block.
+// The memory block will be the data area of a BEXT chunk.
+// Throws exceptions if serializing is not possible
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 BEXTMetadata::serialize( XMP_Uns8** outBuffer )
+{
+ XMP_Uns64 size = 0;
+
+ if( outBuffer != NULL )
+ {
+ const LittleEndian& LE = LittleEndian::getInstance();
+
+ size = kBEXTSizeMin;
+
+ std::string codingHistory;
+
+ if( this->valueExists( kCodingHistory ) )
+ {
+ codingHistory = this->getValue<std::string>( kCodingHistory );
+ NormalizeLF( codingHistory );
+
+ size += codingHistory.length();
+ }
+
+ //
+ // setup buffer
+ //
+ XMP_Uns8* buffer = new XMP_Uns8[static_cast<size_t>(size)];
+
+ //
+ // copy values and strings back to BEXT block
+ //
+ // ! Safe use of strncpy as the fixed size is consistent with the size of the destination buffer
+ // But it is intentional here that the string might not be null terminated if
+ // the size of the source is equal to the fixed size of the destination
+ //
+ BEXT bext;
+ memset( &bext, 0, kBEXTSizeMin );
+
+ if( this->valueExists( kDescription ) )
+ {
+ strncpy( bext.mDescription, this->getValue<std::string>( kDescription ).c_str(), kSizeDescription );
+ }
+ if( this->valueExists( kOriginator ) )
+ {
+ strncpy( bext.mOriginator, this->getValue<std::string>( kOriginator ).c_str(), kSizeOriginator );
+ }
+ if( this->valueExists( kOriginatorReference ) )
+ {
+ strncpy( bext.mOriginatorReference, this->getValue<std::string>( kOriginatorReference ).c_str(), kSizeOriginatorReference );
+ }
+ if( this->valueExists( kOriginationDate ) )
+ {
+ strncpy( bext.mOriginationDate, this->getValue<std::string>( kOriginationDate ).c_str(), kSizeOriginationDate );
+ }
+ if( this->valueExists( kOriginationTime ) )
+ {
+ strncpy( bext.mOriginationTime, this->getValue<std::string>( kOriginationTime ).c_str(), kSizeOriginationTime );
+ }
+
+ if( this->valueExists( kTimeReference ) )
+ {
+ LE.putUns64( this->getValue<XMP_Uns64>( kTimeReference ), &bext.mTimeReferenceLow );
+ }
+
+ if( this->valueExists( kVersion ) )
+ {
+ LE.putUns16( this->getValue<XMP_Uns16>( kVersion ), &bext.mVersion );
+ }
+ else // Special case: If no value is given, a value of "1" is the default!
+ {
+ LE.putUns16( 1, &bext.mVersion );
+ }
+
+ if( this->valueExists( kUMID ) )
+ {
+ XMP_Uns32 muidSize = 0;
+ const XMP_Uns8* const muid = this->getArray<XMP_Uns8>( kUMID, muidSize );
+
+ // Make sure to copy 64 bytes max.
+ muidSize = muidSize > 64 ? 64 : muidSize;
+ memcpy( bext.mUMID, muid, muidSize );
+ }
+ //
+ // set input buffer to zero
+ //
+ memset( buffer, 0, static_cast<size_t>(size) );
+
+ //
+ // copy BEXT block into buffer (except CodingHistory field)
+ //
+ memcpy( buffer, &bext, kBEXTSizeMin );
+
+ //
+ // copy CodingHistory field into buffer
+ //
+ if( ! codingHistory.empty() )
+ {
+ memcpy( buffer + kBEXTSizeMin, codingHistory.c_str(), static_cast<size_t>(size - kBEXTSizeMin) );
+ }
+
+ *outBuffer = buffer;
+ }
+ else
+ {
+ XMP_Throw ( "Invalid buffer", kXMPErr_InternalFailure );
+ }
+
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+//
+// BEXTMetadata::isEmptyValue(...)
+//
+// Purpose: Is the value of the passed ValueObject and its id "empty"?
+//
+//-----------------------------------------------------------------------------
+
+bool BEXTMetadata::isEmptyValue( XMP_Uns32 id, ValueObject& valueObj )
+{
+ bool ret = true;
+
+ switch( id )
+ {
+ case kDescription:
+ case kOriginator:
+ case kOriginatorReference:
+ case kOriginationDate:
+ case kOriginationTime:
+ case kCodingHistory:
+ {
+ TValueObject<std::string>* strObj = dynamic_cast<TValueObject<std::string>*>(&valueObj);
+
+ ret = ( strObj == NULL || ( strObj != NULL && strObj->getValue().empty() ) );
+ }
+ break;
+
+ case kTimeReference:
+ case kVersion:
+ ret = false;
+ break;
+ case kUMID:
+ {
+ TArrayObject<XMP_Uns8>* obj = dynamic_cast<TArrayObject<XMP_Uns8>*>(&valueObj);
+
+ if( obj != NULL )
+ {
+ XMP_Uns32 size = 0;
+ const XMP_Uns8* const buffer = obj->getArray( size );
+
+ ret = ( size == 0 );
+ }
+ }
+ break;
+
+ default:
+ ret = true;
+ }
+
+ return ret;
+}
diff --git a/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h b/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h
new file mode 100644
index 0000000..0f57246
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h
@@ -0,0 +1,99 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _BEXTMetadata_h_
+#define _BEXTMetadata_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/NativeMetadataSupport/IMetadata.h"
+
+namespace IFF_RIFF
+{
+
+/**
+ * BEXT Metadata model.
+ * Implements the IMetadata interface
+ */
+class BEXTMetadata : public IMetadata
+{
+public:
+ enum
+ {
+ kDescription, // std::string
+ kOriginator, // std::string
+ kOriginatorReference, // std::string
+ kOriginationDate, // std::string
+ kOriginationTime, // std::string
+ kTimeReference, // XMP_Uns64
+ kVersion, // XMP_Uns16
+ kUMID, // XMP_Uns8[64]
+ kCodingHistory // std::string
+ };
+
+public:
+ /**
+ *ctor/dtor
+ */
+ BEXTMetadata();
+ ~BEXTMetadata();
+
+ /**
+ * Parses the given memory block and creates a data model representation
+ * The implementation expects that the memory block is the data area of
+ * the BEXT chunk.
+ * Throws exceptions if parsing is not possible
+ *
+ * @param input The byte buffer to parse
+ * @param size Size of the given byte buffer
+ */
+ void parse( const XMP_Uns8* chunkData, XMP_Uns64 size );
+
+ /**
+ * See IMetadata::parse( const LFA_FileRef input )
+ */
+ void parse( XMP_IO* input ) { IMetadata::parse( input ); }
+
+ /**
+ * Serializes the data model to a memory block.
+ * The memory block will be the data area of a BEXT chunk.
+ * Throws exceptions if serializing is not possible
+ *
+ * @param buffer Buffer that gets filled with serialized data
+ * @param size Size of passed in buffer
+ *
+ * @return Size of serialzed data (might be smaller than buffer size)
+ */
+ XMP_Uns64 serialize( XMP_Uns8** buffer );
+
+protected:
+ /**
+ * @see IMetadata::isEmptyValue
+ */
+ virtual bool isEmptyValue( XMP_Uns32 id, ValueObject& valueObj );
+
+ /**
+ * Normalize line feeds to \CR\LF
+ * Mac <OS9: \r
+ * Linux: \n
+ * Win: \r\n
+ */
+ static void NormalizeLF( std::string& str );
+
+private:
+ // Operators hidden on purpose
+ BEXTMetadata( const BEXTMetadata& ) {};
+ BEXTMetadata& operator=( const BEXTMetadata& ) { return *this; };
+};
+
+}
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/WAVE/CartMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/CartMetadata.cpp
new file mode 100644
index 0000000..07c8969
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/CartMetadata.cpp
@@ -0,0 +1,316 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+#include "XMPFiles/source/FormatSupport/WAVE/CartMetadata.h"
+
+#include "source/EndianUtils.hpp"
+
+using namespace IFF_RIFF;
+
+// =============================================================================================
+
+// Types and globals for the stored form of the cart chunk.
+
+#pragma pack ( push, 1 )
+
+struct StoredCartChunk {
+ char Version[4]; // All of the fixed size text fields are null-filled local text,
+ char Title[64]; // but need not have a terminating null.
+ char Artist[64];
+ char CutID[64];
+ char ClientID[64];
+ char Category[64];
+ char Classification[64];
+ char OutCue[64];
+ char StartDate[10];
+ char StartTime[8];
+ char EndDate[10];
+ char EndTime[8];
+ char ProducerAppID[64];
+ char ProducerAppVersion[64];
+ char UserDef[64];
+ XMP_Int32 LevelReference; // Little endian in the file.
+ CartMetadata::StoredCartTimer PostTimer[CartMetadata::kPostTimerLength];
+ char Reserved[276];
+ char URL[1024];
+ // char TagText[]; - fills the tail of the chunk
+};
+
+static const size_t kMinimumCartChunkSize = sizeof(StoredCartChunk);
+
+#pragma pack ( pop )
+
+static const size_t kFixedTextCount = (CartMetadata::kLastFixedTextField - CartMetadata::kFirstFixedTextField + 1);
+
+struct FixedTextFieldInfo {
+ size_t length;
+ size_t offset;
+};
+
+static const FixedTextFieldInfo kFixedTextFields [kFixedTextCount] = {
+ // ! The order of the items must match the order of the mapped field enum in the header.
+ { 4, offsetof ( StoredCartChunk, Version ) },
+ { 64, offsetof ( StoredCartChunk, Title ) },
+ { 64, offsetof ( StoredCartChunk, Artist ) },
+ { 64, offsetof ( StoredCartChunk, CutID ) },
+ { 64, offsetof ( StoredCartChunk, ClientID ) },
+ { 64, offsetof ( StoredCartChunk, Category ) },
+ { 64, offsetof ( StoredCartChunk, Classification ) },
+ { 64, offsetof ( StoredCartChunk, OutCue ) },
+ { 10, offsetof ( StoredCartChunk, StartDate ) },
+ { 8, offsetof ( StoredCartChunk, StartTime ) },
+ { 10, offsetof ( StoredCartChunk, EndDate ) },
+ { 8, offsetof ( StoredCartChunk, EndTime ) },
+ { 64, offsetof ( StoredCartChunk, ProducerAppID ) },
+ { 64, offsetof ( StoredCartChunk, ProducerAppVersion ) },
+ { 64, offsetof ( StoredCartChunk, UserDef ) },
+ { 1024, offsetof ( StoredCartChunk, URL ) }
+};
+
+// =================================================================================================
+
+CartMetadata::CartMetadata() {
+ // Nothing to do.
+}
+
+// =================================================================================================
+
+CartMetadata::~CartMetadata() {
+ // Nothing to do.
+}
+
+// =================================================================================================
+
+static size_t FindZeroByte ( const char * textPtr, size_t maxLength ) {
+ // Return the offset of the first zero byte, up to maxLength.
+ size_t length = 0;
+ while ( (length < maxLength) && (textPtr[length] != 0) ) ++length;
+ return length;
+}
+
+// =================================================================================================
+
+void CartMetadata::parse ( const XMP_Uns8* chunkData, XMP_Uns64 chunkSize )
+{
+ // Make sure the chunk has a reasonable size.
+ if ( chunkSize > 1000*1000*1000 )
+ {
+ XMP_Throw ( "Not a valid Cart chunk", kXMPErr_BadFileFormat );
+ }
+
+ StoredCartChunk* fileChunk;
+
+ // If the chunk is too small, copy and pad with zeros
+ if ( chunkSize < kMinimumCartChunkSize )
+ {
+ fileChunk = new StoredCartChunk;
+ memset( fileChunk, 0, kMinimumCartChunkSize );
+ memcpy( fileChunk, chunkData, static_cast<size_t>(chunkSize) ); // AUDIT: safe, chunkSize is smaller than buffer
+ }
+ else
+ {
+ fileChunk = (StoredCartChunk*)chunkData;
+ }
+
+ try
+ {
+ std::string localStr;
+
+ // Extract the binary LevelReference field.
+
+ this->setValue<XMP_Int32> ( kLevelReference, (XMP_Int32) GetUns32LE ( &fileChunk->LevelReference ) );
+
+ // Extract the PostTimer Array
+ // first ensure the correct endianess
+ StoredCartTimer timerArray[CartMetadata::kPostTimerLength];
+ for ( XMP_Uns32 i = 0; i < CartMetadata::kPostTimerLength; i++ )
+ {
+ timerArray[i].usage = GetUns32BE( &fileChunk->PostTimer[i].usage );
+ timerArray[i].value = GetUns32LE( &fileChunk->PostTimer[i].value );
+ }
+ this->setArray<StoredCartTimer> (kPostTimer, timerArray, CartMetadata::kPostTimerLength);
+
+ // Extract the trailing TagText portion, if any. Keep the local encoding, the conversion to
+ // Unicode is done later when importing to XMP.
+
+ if ( chunkSize > kMinimumCartChunkSize ) {
+
+ const char * tagTextPtr = (char*)fileChunk + sizeof(StoredCartChunk);
+ const size_t trailerSize = (size_t)chunkSize - kMinimumCartChunkSize;
+ const size_t tagTextSize = FindZeroByte ( tagTextPtr, trailerSize );
+ localStr.assign ( tagTextPtr, tagTextSize );
+ this->setValue<std::string> ( kTagText, localStr );
+
+ }
+
+ // Extract the fixed length text fields. Keep the local encoding, the conversion to Unicode is
+ // done later when importing to XMP.
+
+ for ( int i = CartMetadata::kFirstFixedTextField; i <= CartMetadata::kLastFixedTextField; ++i ) {
+
+ const FixedTextFieldInfo& currField = kFixedTextFields[i];
+ const char * textPtr = (char*)fileChunk + currField.offset;
+ size_t textLen = FindZeroByte ( textPtr, currField.length );
+ if ( textLen > 0 ) {
+ localStr.assign ( textPtr, textLen );
+ this->setValue<std::string> ( i, localStr );
+ }
+
+ }
+
+ this->resetChanges();
+ }
+ catch ( ... ) // setValue/setArray might throw
+ {
+ // If the chunk had been too small, it has been copied to a new buffer padded with zeros
+ if ( chunkSize < kMinimumCartChunkSize )
+ {
+ delete fileChunk;
+ }
+ throw;
+ }
+
+ if ( chunkSize < kMinimumCartChunkSize )
+ {
+ delete fileChunk;
+ }
+
+} // CartMetadata::parse
+
+// =================================================================================================
+
+XMP_Uns64 CartMetadata::serialize ( XMP_Uns8** outBuffer )
+{
+
+ if ( outBuffer == NULL )
+ {
+ XMP_Throw ( "Invalid buffer", kXMPErr_InternalFailure );
+ }
+
+ *outBuffer = 0; // Default in case of early exit.
+
+ // Set up the output buffer.
+
+ size_t trailerSize = 0;
+ std::string tagTextStr;
+ if ( this->valueExists ( kTagText ) ) {
+ tagTextStr = this->getValue<std::string> ( kTagText );
+ trailerSize = tagTextStr.size() + 1; // Include the final nul.
+ }
+
+ size_t chunkSize = sizeof(StoredCartChunk) + trailerSize;
+ XMP_Uns8* buffer = new XMP_Uns8 [ chunkSize ];
+ if ( buffer == 0 ) XMP_Throw ( "Cannot allocate cart chunk buffer", kXMPErr_NoMemory );
+ memset ( buffer, 0, chunkSize ); // Fill with zeros for missing or short fields.
+
+ StoredCartChunk* newChunk = (StoredCartChunk*)buffer;
+
+ // Insert the binary LevelReference field.
+
+ if ( this->valueExists ( kLevelReference ) ) {
+ newChunk->LevelReference = MakeUns32LE ( (XMP_Uns32) this->getValue<XMP_Int32> ( kLevelReference ) );
+ }
+
+ // Insert the PostTimer Array
+ if ( this->valueExists ( kPostTimer ) ) {
+ XMP_Uns32 size = 0;
+ const StoredCartTimer* timerArray = this->getArray<StoredCartTimer> ( kPostTimer, size );
+ XMP_Assert (size == CartMetadata::kPostTimerLength );
+
+ for ( XMP_Uns32 i = 0; i<CartMetadata::kPostTimerLength; i++ )
+ {
+ newChunk->PostTimer[i].usage = MakeUns32BE(timerArray[i].usage); // ensure the FOURCC is written in Big Endian
+ newChunk->PostTimer[i].value = MakeUns32LE(timerArray[i].value); // ensure the value is written as Little Endian
+ }
+ }
+
+ // Insert the trailing TagText portion, if any. The conversion to local encoding should have
+ // been done when exporting from XMP. Include the final nul.
+
+ if ( ! tagTextStr.empty() ) {
+ XMP_Uns8* tagTextPtr = buffer + sizeof(StoredCartChunk);
+ strncpy ( (char*)tagTextPtr, tagTextStr.c_str(), trailerSize ); // AUDIT: Safe, see prior allocation.
+ }
+
+ // Insert the fixed length text fields. The conversion to local encoding should have been done
+ // when exporting from XMP.
+
+ std::string currStr;
+
+ for ( int i = CartMetadata::kFirstFixedTextField; i <= CartMetadata::kLastFixedTextField; ++i ) {
+
+ if ( ! this->valueExists ( i ) ) continue;
+ const FixedTextFieldInfo& currField = kFixedTextFields[i];
+
+ currStr = this->getValue<std::string> ( i );
+ if ( currStr.empty() ) continue;
+ if ( currStr.size() > currField.length ) currStr.erase ( currField.length );
+ XMP_Assert ( currStr.size() <= currField.length );
+
+ strncpy ( (char*)(buffer + currField.offset), currStr.c_str(), currStr.size() ); // // AUDIT: Safe, within fixed bounds.
+
+ }
+
+ // Done.
+
+ *outBuffer = buffer;
+ return chunkSize;
+
+} // CartMetadata::serialize
+
+// =================================================================================================
+
+bool CartMetadata::isEmptyValue ( XMP_Uns32 id, ValueObject& valueObj ) {
+
+ bool isEmpty = true;
+
+ switch( id )
+ {
+ case kLevelReference:
+ {
+ //XMP_Int32
+ TValueObject<XMP_Int32>* binObj = dynamic_cast<TValueObject<XMP_Int32>*>(&valueObj);
+ isEmpty = ( binObj == 0 ); // All values are valid.
+ }
+ break;
+
+ case kPostTimer:
+ {
+ // Array[8]*2
+ TArrayObject<StoredCartTimer>* obj = dynamic_cast<TArrayObject<StoredCartTimer>*>(&valueObj);
+
+ if( obj != NULL )
+ {
+ XMP_Uns32 size = 0;
+ const StoredCartTimer* const buffer = obj->getArray( size );
+
+ isEmpty = ( size == 0 );
+ }
+ }
+ break;
+
+ default:
+ {
+ // String values
+ TValueObject<std::string>* strObj = dynamic_cast<TValueObject<std::string>*>(&valueObj);
+ isEmpty = ( (strObj == 0) || strObj->getValue().empty() );
+ }
+ break;
+ }
+
+ return isEmpty;
+
+} // CartMetadata::isEmptyValue
+
+// =================================================================================================
diff --git a/XMPFiles/source/FormatSupport/WAVE/CartMetadata.h b/XMPFiles/source/FormatSupport/WAVE/CartMetadata.h
new file mode 100644
index 0000000..29266a7
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/CartMetadata.h
@@ -0,0 +1,87 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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.
+// =================================================================================================
+
+#ifndef _CartMetadata_h_
+#define _CartMetadata_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/NativeMetadataSupport/IMetadata.h"
+
+namespace IFF_RIFF {
+
+// =================================================================================================
+
+class CartMetadata : public IMetadata {
+
+public:
+
+ enum {
+
+ // IDs for the mapped cart chunk fields.
+ kVersion, // Local text, max 4 bytes
+ kTitle, // Local text, max 64 bytes
+ kArtist, // Local text, max 64 bytes
+ kCutID, // Local text, max 64 bytes
+ kClientID, // Local text, max 64 bytes
+ kCategory, // Local text, max 64 bytes
+ kClassification, // Local text, max 64 bytes
+ kOutCue, // Local text, max 64 bytes
+ kStartDate, // Local text, max 10 bytes
+ kStartTime, // Local text, max 8 bytes
+ kEndDate, // Local text, max 10 bytes
+ kEndTime, // Local text, max 8 bytes
+ kProducerAppID, // Local text, max 64 bytes
+ kProducerAppVersion, // Local text, max 64 bytes
+ kUserDef, // Local text, max 64 bytes
+ kURL, // Local text, max 1024 bytes
+ kTagText, // Local text, no limit
+ kLevelReference, // Little endian unsigned 32
+ kPostTimer, // array[8] of usage(Uns32), value(Uns32)
+ kReserved, // 276 reserved bytes
+
+ // Constants for the range of fixed length text fields.
+ kFirstFixedTextField = kVersion,
+ kLastFixedTextField = kURL
+
+ };
+
+ struct StoredCartTimer {
+ XMP_Uns32 usage;
+ XMP_Uns32 value;
+ };
+
+ enum { kPostTimerLength = 8 };
+
+ CartMetadata();
+ virtual ~CartMetadata();
+
+ void parse ( const XMP_Uns8* chunkData, XMP_Uns64 chunkSize );
+ void parse ( XMP_IO* input ) { IMetadata::parse ( input ); }
+
+ XMP_Uns64 serialize ( XMP_Uns8** buffer );
+
+protected:
+
+ virtual bool isEmptyValue ( XMP_Uns32 id, ValueObject& valueObj );
+
+private:
+
+ // Operators hidden on purpose.
+ CartMetadata ( const CartMetadata& ) {};
+ CartMetadata& operator= ( const CartMetadata& ) { return *this; };
+
+}; // CartMetadata
+
+} // namespace IFF_RIFF
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.cpp
new file mode 100644
index 0000000..c9cbc23
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.cpp
@@ -0,0 +1,240 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 <string.h>
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.h"
+#include "source/Endian.h"
+
+using namespace IFF_RIFF;
+
+static const XMP_Uns32 kCr8rSizeFix = 84; // always 84 bytes
+
+static const XMP_Uns32 kSizeFileExt = 16;
+static const XMP_Uns32 kSizeAppOtions = 16;
+static const XMP_Uns32 kSizeAppName = 32;
+
+// Needed to be able to memcpy directly to this struct.
+#pragma pack ( push, 1 )
+ struct Cr8rBoxContent
+ {
+ XMP_Uns32 mMagic;
+ XMP_Uns32 mSize;
+ XMP_Uns16 mMajorVer;
+ XMP_Uns16 mMinorVer;
+ XMP_Uns32 mCreatorCode;
+ XMP_Uns32 mAppleEvent;
+ char mFileExt[kSizeFileExt];
+ char mAppOptions[kSizeAppOtions];
+ char mAppName[kSizeAppName];
+ };
+#pragma pack ( pop )
+
+//-----------------------------------------------------------------------------
+//
+// Cr8rMetadata::Cr8rMetadata(...)
+//
+// Purpose: ctor/dtor
+//
+//-----------------------------------------------------------------------------
+
+Cr8rMetadata::Cr8rMetadata()
+{
+}
+
+Cr8rMetadata::~Cr8rMetadata()
+{
+}
+
+//-----------------------------------------------------------------------------
+//
+// Cr8rMetadata::parse(...)
+//
+// Purpose: Parses the given memory block and creates a data model representation
+// The implementation expects that the memory block is the data area of
+// the BEXT chunk and its size is at least as big as the minimum size
+// of a BEXT data block.
+// Throws exceptions if parsing is not possible
+//
+//-----------------------------------------------------------------------------
+
+void Cr8rMetadata::parse( const XMP_Uns8* chunkData, XMP_Uns64 size )
+{
+ if( size >= kCr8rSizeFix )
+ {
+ const LittleEndian& LE = LittleEndian::getInstance();
+
+ Cr8rBoxContent cr8r;
+ memset( &cr8r, 0, kCr8rSizeFix );
+
+ //
+ // copy input data into Cr8r block
+ // Safe as fixed size matches size of struct that is #pragma packed(1)
+ //
+ memcpy( &cr8r, chunkData, kCr8rSizeFix );
+
+ //
+ // copy values to map
+ //
+ this->setValue<XMP_Uns32>( kMagic, cr8r.mMagic );
+ this->setValue<XMP_Uns32>( kSize, cr8r.mSize );
+ this->setValue<XMP_Uns16>( kMajorVer, cr8r.mMajorVer );
+ this->setValue<XMP_Uns16>( kMinorVer, cr8r.mMinorVer );
+ this->setValue<XMP_Uns32>( kCreatorCode, cr8r.mCreatorCode );
+ this->setValue<XMP_Uns32>( kAppleEvent, cr8r.mAppleEvent );
+ this->setValue<std::string>( kFileExt, std::string( cr8r.mFileExt, kSizeFileExt ) );
+ this->setValue<std::string>( kAppOptions, std::string( cr8r.mAppOptions, kSizeAppOtions ) );
+ this->setValue<std::string>( kAppName, std::string( cr8r.mAppName, kSizeAppName ) );
+
+ this->resetChanges();
+ }
+ else
+ {
+ XMP_Throw ( "Not a valid Cr8r chunk", kXMPErr_BadFileFormat );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// Cr8rMetadata::serialize(...)
+//
+// Purpose: Serializes the data model to a memory block.
+// The memory block will be the data area of a BEXT chunk.
+// Throws exceptions if serializing is not possible
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 Cr8rMetadata::serialize( XMP_Uns8** outBuffer )
+{
+ XMP_Uns64 size = 0;
+
+ if( outBuffer != NULL )
+ {
+ const LittleEndian& LE = LittleEndian::getInstance();
+
+ size = kCr8rSizeFix;
+
+ //
+ // setup buffer
+ //
+ XMP_Uns8* buffer = new XMP_Uns8[static_cast<size_t>(size)];
+
+ //
+ // copy values and strings back to BEXT block
+ //
+ // ! Safe use of strncpy as the fixed size is consistent with the size of the destination buffer
+ // But it is intentional here that the string might not be null terminated if
+ // the size of the source is equal to the fixed size of the destination
+ //
+ Cr8rBoxContent cr8r;
+ memset( &cr8r, 0, kCr8rSizeFix );
+
+ if( this->valueExists( kMagic ) )
+ {
+ LE.putUns32( this->getValue<XMP_Uns32>( kMagic ), &cr8r.mMagic );
+ }
+ if( this->valueExists( kSize ) )
+ {
+ LE.putUns32( this->getValue<XMP_Uns32>( kSize ), &cr8r.mSize );
+ }
+ if( this->valueExists( kMajorVer ) )
+ {
+ LE.putUns16( this->getValue<XMP_Uns16>( kMajorVer ), &cr8r.mMajorVer );
+ }
+ if( this->valueExists( kMinorVer ) )
+ {
+ LE.putUns16( this->getValue<XMP_Uns16>( kMinorVer ), &cr8r.mMinorVer );
+ }
+ if( this->valueExists( kCreatorCode ) )
+ {
+ LE.putUns32( this->getValue<XMP_Uns32>( kCreatorCode ), &cr8r.mCreatorCode );
+ }
+ if( this->valueExists( kAppleEvent ) )
+ {
+ LE.putUns32( this->getValue<XMP_Uns32>( kAppleEvent ), &cr8r.mAppleEvent );
+ }
+ if( this->valueExists( kFileExt ) )
+ {
+ strncpy( cr8r.mFileExt, this->getValue<std::string>( kFileExt ).c_str(), kSizeFileExt );
+ }
+ if( this->valueExists( kAppOptions ) )
+ {
+ strncpy( cr8r.mAppOptions, this->getValue<std::string>( kAppOptions ).c_str(), kSizeAppOtions );
+ }
+ if( this->valueExists( kAppName ) )
+ {
+ strncpy( cr8r.mAppName, this->getValue<std::string>( kAppName ).c_str(), kSizeAppName );
+ }
+
+ //
+ // set input buffer to zero
+ //
+ memset( buffer, 0, static_cast<size_t>(size) );
+
+ //
+ // copy Cr8r block into buffer
+ //
+ memcpy( buffer, &cr8r, kCr8rSizeFix );
+
+ *outBuffer = buffer;
+ }
+ else
+ {
+ XMP_Throw ( "Invalid buffer", kXMPErr_BadParam );
+ }
+
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Cr8rMetadata::isEmptyValue(...)
+//
+// Purpose: Is the value of the passed ValueObject and its id "empty"?
+//
+//-----------------------------------------------------------------------------
+
+bool Cr8rMetadata::isEmptyValue( XMP_Uns32 id, ValueObject& valueObj )
+{
+ bool ret = true;
+
+ switch( id )
+ {
+ case kFileExt:
+ case kAppOptions:
+ case kAppName:
+ {
+ TValueObject<std::string>* strObj = dynamic_cast<TValueObject<std::string>*>(&valueObj);
+
+ ret = ( strObj == NULL || ( strObj != NULL && strObj->getValue().empty() ) );
+ }
+ break;
+
+ case kMagic:
+ case kSize:
+ case kMajorVer:
+ case kMinorVer:
+ case kCreatorCode:
+ case kAppleEvent:
+ {
+ ret = false;
+ }
+ break;
+
+ default:
+ {
+ ret = true;
+ }
+ }
+
+ return ret;
+}
diff --git a/XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.h b/XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.h
new file mode 100644
index 0000000..451f430
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.h
@@ -0,0 +1,91 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _Cr8rMetadata_h_
+#define _Cr8rMetadata_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/NativeMetadataSupport/IMetadata.h"
+
+namespace IFF_RIFF
+{
+
+/**
+ * Cr8r Metadata model.
+ * Implements the IMetadata interface
+ */
+class Cr8rMetadata : public IMetadata
+{
+public:
+ enum
+ {
+ kMagic, // XMP_Uns32
+ kSize, // XMP_Uns32
+ kMajorVer, // XMP_Uns16
+ kMinorVer, // XMP_Uns16
+ kCreatorCode, // XMP_Uns32
+ kAppleEvent, // XMP_Uns32
+ kFileExt, // char[16]
+ kAppOptions, // char[16]
+ kAppName // char[32]
+ };
+
+public:
+ /**
+ *ctor/dtor
+ */
+ Cr8rMetadata();
+ ~Cr8rMetadata();
+
+ /**
+ * Parses the given memory block and creates a data model representation
+ * The implementation expects that the memory block is the data area of
+ * the Cr8rMetadata chunk.
+ * Throws exceptions if parsing is not possible
+ *
+ * @param input The byte buffer to parse
+ * @param size Size of the given byte buffer
+ */
+ void parse( const XMP_Uns8* chunkData, XMP_Uns64 size );
+
+ /**
+ * See IMetadata::parse( const LFA_FileRef input )
+ */
+ void parse( XMP_IO* input ) { IMetadata::parse( input ); }
+
+ /**
+ * Serializes the data model to a memory block.
+ * The memory block will be the data area of a Cr8rMetadata chunk.
+ * Throws exceptions if serializing is not possible
+ *
+ * @param buffer Buffer that gets filled with serialized data
+ * @param size Size of passed in buffer
+ *
+ * @return Size of serialzed data (might be smaller than buffer size)
+ */
+ XMP_Uns64 serialize( XMP_Uns8** buffer );
+
+protected:
+ /**
+ * @see IMetadata::isEmptyValue
+ */
+ virtual bool isEmptyValue( XMP_Uns32 id, ValueObject& valueObj );
+
+private:
+ // Operators hidden on purpose
+ Cr8rMetadata( const Cr8rMetadata& ) {};
+ Cr8rMetadata& operator=( const Cr8rMetadata& ) { return *this; };
+};
+
+}
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/WAVE/DISPMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/DISPMetadata.cpp
new file mode 100644
index 0000000..9e821ef
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/DISPMetadata.cpp
@@ -0,0 +1,138 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 <string.h>
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/WAVE/DISPMetadata.h"
+#include "source/Endian.h"
+
+using namespace IFF_RIFF;
+
+//-----------------------------------------------------------------------------
+//
+// DISPMetadata::isValidDISP(...)
+//
+// Purpose: [static] Check if the passed data is a valid DISP chunk. Valid in
+// of is it a DISP chunk that XMP should process.
+//
+//-----------------------------------------------------------------------------
+
+bool DISPMetadata::isValidDISP( const XMP_Uns8* chunkData, XMP_Uns64 size )
+{
+ return ( ( size >= 4 ) && ( LittleEndian::getInstance().getUns32( chunkData ) == 0x0001 ) );
+}
+
+//-----------------------------------------------------------------------------
+//
+// DISPMetadata::DISPMetadata(...)
+//
+// Purpose: ctor/dtor
+//
+//-----------------------------------------------------------------------------
+
+DISPMetadata::DISPMetadata()
+{
+}
+
+DISPMetadata::~DISPMetadata()
+{
+}
+
+//-----------------------------------------------------------------------------
+//
+// DISPMetadata::parse(...)
+//
+// Purpose: Parses the given memory block and creates a data model representation
+// The implementation expects that the memory block is the data area of
+// the DISP chunk.
+// Throws exceptions if parsing is not possible
+//
+//-----------------------------------------------------------------------------
+
+void DISPMetadata::parse( const XMP_Uns8* chunkData, XMP_Uns64 size )
+{
+ if( DISPMetadata::isValidDISP( chunkData, size ) )
+ {
+ this->setValue<std::string>( kTitle, std::string( (char*)&chunkData[4], static_cast<std::string::size_type>(size-4) ) );
+ this->resetChanges();
+ }
+ else
+ {
+ XMP_Throw ( "Not a valid DISP chunk", kXMPErr_BadFileFormat );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// DISPMetadata::serialize(...)
+//
+// Purpose: Serializes the data model to a memory block.
+// The memory block will be the data area of a DISP chunk.
+// Throws exceptions if serializing is not possible
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 DISPMetadata::serialize( XMP_Uns8** outBuffer )
+{
+ XMP_Uns64 size = 0;
+
+ if( outBuffer != NULL && this->valueExists( kTitle ) )
+ {
+ std::string title = this->getValue<std::string>( kTitle );
+
+ size = 4 + title.length(); // at least 4bytes for the type value of the DISP chunk
+
+ // [2500563] DISP chunk must be of even length for WAVE,
+ // as pad byte is not interpreted correctly by third-party tools
+ if ( size % 2 != 0 )
+ {
+ size++;
+ }
+ XMP_Uns8* buffer = new XMP_Uns8[static_cast<size_t>(size)];
+
+ memset( buffer, 0, static_cast<size_t>(size) );
+
+ //
+ // DISP type
+ //
+ buffer[0] = 1;
+
+ //
+ // copy string into buffer
+ //
+
+ memcpy( &buffer[4], title.c_str(), title.length() );
+
+ *outBuffer = buffer;
+ }
+ else
+ {
+ XMP_Throw ( "Invalid buffer", kXMPErr_InternalFailure );
+ }
+
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+//
+// DISPMetadata::isEmptyValue(...)
+//
+// Purpose: Is the value of the passed ValueObject and its id "empty"?
+//
+//-----------------------------------------------------------------------------
+
+bool DISPMetadata::isEmptyValue( XMP_Uns32 id, ValueObject& valueObj )
+{
+ TValueObject<std::string>* strObj = dynamic_cast<TValueObject<std::string>*>(&valueObj);
+
+ return ( strObj == NULL || ( strObj != NULL && strObj->getValue().empty() ) );
+}
diff --git a/XMPFiles/source/FormatSupport/WAVE/DISPMetadata.h b/XMPFiles/source/FormatSupport/WAVE/DISPMetadata.h
new file mode 100644
index 0000000..7db1891
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/DISPMetadata.h
@@ -0,0 +1,97 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _DISPMetadata_h_
+#define _DISPMetadata_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/NativeMetadataSupport/IMetadata.h"
+
+namespace IFF_RIFF
+{
+
+/**
+ * DISP Metadata model.
+ * Implements the IMetadata interface
+ */
+class DISPMetadata : public IMetadata
+{
+public:
+ enum
+ {
+ kTitle // std::string
+ };
+
+public:
+ /**
+ * Return true if the type is 0x0001 and it's large enough for
+ * more content
+ *
+ * @param chunkData Data area of chunk
+ * @param size Size of data area
+ *
+ * @return True if it's a valid DISP chunk
+ */
+ static bool isValidDISP( const XMP_Uns8* chunkData, XMP_Uns64 size );
+
+public:
+ /**
+ *ctor/dtor
+ */
+ DISPMetadata();
+ virtual ~DISPMetadata();
+
+ /**
+ * Parses the given memory block and creates a data model representation
+ * The implementation expects that the memory block is the data area of
+ * the DISP chunk.
+ * Throws exceptions if parsing is not possible
+ *
+ * @param input The byte buffer to parse
+ * @param size Size of the given byte buffer
+ */
+ void parse( const XMP_Uns8* chunkData, XMP_Uns64 size );
+
+ /**
+ * See IMetadata::parse( const LFA_FileRef input )
+ */
+ void parse( XMP_IO* input ) { IMetadata::parse( input ); }
+
+ /**
+ * Serializes the data model to a memory block.
+ * The method creates a buffer and pass it to the parameter 'buffer'. The callee of
+ * the method is responsible to delete the buffer later on.
+ * The memory block will be the data area of a DISP chunk.
+ * Throws exceptions if serializing is not possible
+ *
+ * @param buffer Buffer that gets filled with serialized data
+ * @param size Size of passed in buffer
+ *
+ * @ return Buffer size
+ */
+ virtual XMP_Uns64 serialize( XMP_Uns8** buffer );
+
+protected:
+ /**
+ * @see IMetadata::isEmptyValue
+ */
+ virtual bool isEmptyValue( XMP_Uns32 id, ValueObject& valueObj );
+
+private:
+ // Operators hidden on purpose
+ DISPMetadata( const DISPMetadata& ) {};
+ DISPMetadata& operator=( const DISPMetadata& ) { return *this; };
+};
+
+}
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/WAVE/INFOMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/INFOMetadata.cpp
new file mode 100644
index 0000000..76faaa3
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/INFOMetadata.cpp
@@ -0,0 +1,260 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 <string.h>
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/WAVE/INFOMetadata.h"
+#include "source/Endian.h"
+
+using namespace IFF_RIFF;
+
+typedef std::map<XMP_Uns32, std::string>::const_iterator MapIterator;
+
+static const XMP_Uns32 kSizeChunkID = 4;
+static const XMP_Uns32 kSizeChunkSize = 4;
+static const XMP_Uns32 kSizeChunkType = 4;
+static const XMP_Uns32 kChunkHeaderSize = kSizeChunkID + kSizeChunkSize;
+
+static const XMP_Uns32 kType_INFO = 0x494E464F;
+
+//-----------------------------------------------------------------------------
+//
+// INFOMetadata::INFOMetadata(...)
+//
+// Purpose: ctor/dtor
+//
+//-----------------------------------------------------------------------------
+
+INFOMetadata::INFOMetadata()
+{
+}
+
+INFOMetadata::~INFOMetadata()
+{
+}
+
+//-----------------------------------------------------------------------------
+//
+// INFOMetadata::parse(...)
+//
+// Purpose: @see IMetadata::parse
+//
+//-----------------------------------------------------------------------------
+
+void INFOMetadata::parse( const XMP_Uns8* input, XMP_Uns64 size )
+{
+ if( input != NULL && size >= kSizeChunkType )
+ {
+ const BigEndian& BE = BigEndian::getInstance();
+ const LittleEndian& LE = LittleEndian::getInstance();
+
+ XMP_Uns32 type = BE.getUns32( &input[0] );
+
+ XMP_Validate( type == kType_INFO, "Invalid LIST:INFO data", kXMPErr_InternalFailure );
+
+ XMP_Uns64 offset = kSizeChunkType; // pointer into the buffer
+
+ while( offset < size )
+ {
+ //
+ // continue parsing only if the remaing buffer is greater than
+ // the chunk header size
+ //
+ if( size - offset >= kChunkHeaderSize )
+ {
+ //
+ // read: chunk id
+ // chunk size
+ // chunk data
+ XMP_Uns32 id = BE.getUns32( &input[offset] );
+ XMP_Uns32 datasize = LE.getUns32( &input[offset+kSizeChunkID] );
+
+ if( offset + kChunkHeaderSize + datasize <= size )
+ {
+ if( datasize > 0 )
+ {
+ //
+ // don't store empty values
+ //
+ std::string value( reinterpret_cast<const char*>( &input[offset+kChunkHeaderSize] ), datasize );
+
+ //
+ // set new value
+ //
+ this->setValue<std::string>( id, value );
+ }
+
+ //
+ // update pointer
+ //
+ offset = offset + datasize + kChunkHeaderSize;
+
+ if( datasize & 1 )
+ {
+ // pad byte
+ offset++;
+ }
+ }
+ else
+ {
+ //
+ // invalid chunk, clean up and throw exception
+ //
+ this->deleteAll();
+ XMP_Throw ( "Not a valid LIST:INFO chunk", kXMPErr_BadFileFormat );
+ }
+ }
+ else
+ {
+ //
+ // invalid chunk, clean up and throw exception
+ //
+ this->deleteAll();
+ XMP_Throw ( "Not a valid LIST:INFO chunk", kXMPErr_BadFileFormat );
+ }
+ }
+
+ this->resetChanges();
+ }
+ else
+ {
+ XMP_Throw ( "Not a valid LIST:INFO chunk", kXMPErr_BadFileFormat );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// INFOMetadata::serialize(...)
+//
+// Purpose: @see IMetadata::serialize
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 INFOMetadata::serialize( XMP_Uns8** outBuffer )
+{
+ XMP_Uns64 size = 0;
+
+ if( outBuffer != NULL )
+ {
+ //
+ // calculate required buffer size
+ //
+ for( ValueMap::iterator iter=mValues.begin(); iter!=mValues.end(); iter++ )
+ {
+ TValueObject<std::string>* strObj = dynamic_cast<TValueObject<std::string>*>(iter->second);
+
+ XMP_Uns32 chunkSize = kChunkHeaderSize + strObj->getValue().length();
+
+ if( chunkSize & 1 )
+ {
+ // take account of pad byte
+ chunkSize++;
+ }
+
+ size += chunkSize;
+ }
+
+ size += kSizeChunkType; // add size of type "INFO"
+
+ if( size > 0 )
+ {
+ XMP_Uns8* buffer = new XMP_Uns8[static_cast<size_t>(size)]; // output buffer
+
+ memset( buffer, 0, static_cast<size_t>(size) );
+
+ const BigEndian& BE = BigEndian::getInstance();
+ const LittleEndian& LE = LittleEndian::getInstance();
+
+ // Put type "INFO" in front of the buffer
+ XMP_Uns32 typeInfo = BE.getUns32(&kType_INFO);
+ memcpy( buffer, &typeInfo, kSizeChunkType );
+ XMP_Uns64 offset = kSizeChunkType; // pointer into the buffer
+
+ //
+ // for each stored value
+ //
+ for( ValueMap::iterator iter=mValues.begin(); iter!=mValues.end(); iter++ )
+ {
+ //
+ // get: chunk data
+ // chunk id
+ // chunk size
+ //
+ TValueObject<std::string>* strObj = dynamic_cast<TValueObject<std::string>*>(iter->second);
+ std::string value = strObj->getValue();
+ XMP_Uns32 id = iter->first;
+ XMP_Uns32 size = value.length();
+
+ if( size & 1 && strObj->hasChanged() )
+ {
+ //
+ // if we modified the value of this entry
+ // then fill chunk data with zero bytes
+ // rather than use a pad byte, i.e.
+ // size of each LIST:INFO entry has
+ // an odd size
+ //
+ size++;
+ }
+
+ //
+ // chunk id and chunk size are stored in little endian format
+ //
+ id = BE.getUns32( &id );
+ size = LE.getUns32( &size );
+
+ //
+ // copy values into output buffer
+ //
+ memcpy( buffer+offset, &id, kSizeChunkID );
+ memcpy( buffer+offset+kSizeChunkID, &size, kSizeChunkSize );
+ memcpy( buffer+offset+kChunkHeaderSize, value.c_str(), size );
+
+ //
+ // update pointer
+ //
+ offset += kChunkHeaderSize;
+ offset += size;
+
+ if( size & 1 )
+ {
+ //
+ // take account of pad byte
+ offset++;
+ }
+ }
+
+ *outBuffer = buffer;
+ }
+ }
+ else
+ {
+ XMP_Throw ( "Invalid buffer", kXMPErr_InternalFailure );
+ }
+
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+//
+// INFOMetadata::isEmptyValue(...)
+//
+// Purpose: Is the value of the passed ValueObject and its id "empty"?
+//
+//-----------------------------------------------------------------------------
+
+bool INFOMetadata::isEmptyValue( XMP_Uns32 id, ValueObject& valueObj )
+{
+ TValueObject<std::string>* strObj = dynamic_cast<TValueObject<std::string>*>(&valueObj);
+
+ return ( strObj == NULL || ( strObj != NULL && strObj->getValue().empty() ) );
+}
diff --git a/XMPFiles/source/FormatSupport/WAVE/INFOMetadata.h b/XMPFiles/source/FormatSupport/WAVE/INFOMetadata.h
new file mode 100644
index 0000000..74d024c
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/INFOMetadata.h
@@ -0,0 +1,88 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _INFOMetadata_h_
+#define _INFOMetadata_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/NativeMetadataSupport/IMetadata.h"
+
+namespace IFF_RIFF
+{
+
+/**
+ * LIST INFO Metadata model.
+ * Implements the IMetadata interface
+ */
+class INFOMetadata : public IMetadata
+{
+public:
+ enum
+ {
+ kArtist = 0x49415254, // 'IART' std::string
+ kComments = 0x49434d54, // 'ICMT' std::string
+ kCopyright = 0x49434f50, // 'ICOP' std::string
+ kCreationDate = 0x49435244, // 'ICRD' std::string
+ kEngineer = 0x49454e47, // 'IENG' std::string
+ kGenre = 0x49474e52, // 'IGNR' std::string
+ kName = 0x494e414d, // 'INAM' std::string
+ kSoftware = 0x49534654, // 'ISFT' std::string
+ kMedium = 0x494d4544, // 'IMED' std::string
+ kSourceForm = 0x49535246, // 'ISRF' std::string
+
+ // new mappings
+ kArchivalLocation = 0x4941524C, // 'IARL'
+ kCommissioned = 0x49434D53, // 'ICMS'
+ kKeywords = 0x494B4559, // 'IKEY'
+ kProduct = 0x49505244, // 'IPRD'
+ kSubject = 0x4953424A, // 'ISBJ'
+ kSource = 0x49535243, // 'ISRC'
+ kTechnican = 0x49544348, // 'ITCH'
+ };
+
+public:
+ /**
+ *ctor/dtor
+ */
+ INFOMetadata();
+ ~INFOMetadata();
+
+ /**
+ * @see IMetadata::parse
+ */
+ void parse( const XMP_Uns8* input, XMP_Uns64 size );
+
+ /**
+ * See IMetadata::parse( const LFA_FileRef input )
+ */
+ void parse( XMP_IO* input ) { IMetadata::parse( input ); }
+
+ /**
+ * @see IMetadata::serialize
+ */
+ XMP_Uns64 serialize( XMP_Uns8** outBuffer );
+
+protected:
+ /**
+ * @see IMetadata::isEmptyValue
+ */
+ virtual bool isEmptyValue( XMP_Uns32 id, ValueObject& valueObj );
+
+private:
+ // Operators hidden on purpose
+ INFOMetadata( const INFOMetadata& ) {};
+ INFOMetadata& operator=( const INFOMetadata& ) { return *this; };
+};
+
+}
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.cpp
new file mode 100644
index 0000000..c5dc42e
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.cpp
@@ -0,0 +1,231 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 <string.h>
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.h"
+#include "source/Endian.h"
+
+using namespace IFF_RIFF;
+
+static const XMP_Uns32 kPrmlSizeFix = 282; // always 282 bytes
+
+static const XMP_Uns32 kSizeFilePath = 260;
+
+// Needed to be able to memcpy directly to this struct.
+#pragma pack ( push, 1 )
+ struct PrmlBoxContent
+ {
+ XMP_Uns32 mMagic;
+ XMP_Uns32 mSize;
+ XMP_Uns16 mVerAPI;
+ XMP_Uns16 mVerCode;
+ XMP_Uns32 mExportType;
+ XMP_Uns16 mMacVRefNum;
+ XMP_Uns32 mMacParID;
+ char mFilePath[kSizeFilePath];
+ };
+#pragma pack ( pop )
+
+//-----------------------------------------------------------------------------
+//
+// PrmLMetadata::PrmLMetadata(...)
+//
+// Purpose: ctor/dtor
+//
+//-----------------------------------------------------------------------------
+
+PrmLMetadata::PrmLMetadata()
+{
+}
+
+PrmLMetadata::~PrmLMetadata()
+{
+}
+
+//-----------------------------------------------------------------------------
+//
+// PrmLMetadata::parse(...)
+//
+// Purpose: Parses the given memory block and creates a data model representation
+// The implementation expects that the memory block is the data area of
+// the BEXT chunk and its size is at least as big as the minimum size
+// of a BEXT data block.
+// Throws exceptions if parsing is not possible
+//
+//-----------------------------------------------------------------------------
+
+void PrmLMetadata::parse( const XMP_Uns8* chunkData, XMP_Uns64 size )
+{
+ if( size >= kPrmlSizeFix )
+ {
+ const LittleEndian& LE = LittleEndian::getInstance();
+
+ PrmlBoxContent prml;
+ memset( &prml, 0, kPrmlSizeFix );
+
+ //
+ // copy input data into Prml block
+ // Safe as fixed size matches size of struct that is #pragma packed(1)
+ //
+ memcpy( &prml, chunkData, kPrmlSizeFix );
+
+ //
+ // copy values to map
+ //
+ this->setValue<XMP_Uns32>( kMagic, prml.mMagic );
+ this->setValue<XMP_Uns32>( kSize, prml.mSize );
+ this->setValue<XMP_Uns16>( kVerAPI, prml.mVerAPI );
+ this->setValue<XMP_Uns16>( kVerCode, prml.mVerCode );
+ this->setValue<XMP_Uns32>( kExportType, prml.mExportType );
+ this->setValue<XMP_Uns16>( kMacVRefNum, prml.mMacVRefNum );
+ this->setValue<XMP_Uns32>( kMacParID, prml.mMacParID );
+ this->setValue<std::string>( kFilePath, std::string( prml.mFilePath, kSizeFilePath ) );
+
+ this->resetChanges();
+ }
+ else
+ {
+ XMP_Throw ( "Not a valid Prml chunk", kXMPErr_BadFileFormat );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// PrmLMetadata::serialize(...)
+//
+// Purpose: Serializes the data model to a memory block.
+// The memory block will be the data area of a BEXT chunk.
+// Throws exceptions if serializing is not possible
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 PrmLMetadata::serialize( XMP_Uns8** outBuffer )
+{
+ XMP_Uns64 size = 0;
+
+ if( outBuffer != NULL )
+ {
+ const LittleEndian& LE = LittleEndian::getInstance();
+
+ size = kPrmlSizeFix;
+
+ //
+ // setup buffer
+ //
+ XMP_Uns8* buffer = new XMP_Uns8[static_cast<size_t>(size)];
+
+ //
+ // copy values and strings back to BEXT block
+ //
+ // ! Safe use of strncpy as the fixed size is consistent with the size of the destination buffer
+ // But it is intentional here that the string might not be null terminated if
+ // the size of the source is equal to the fixed size of the destination
+ //
+ PrmlBoxContent prml;
+ memset( &prml, 0, kPrmlSizeFix );
+
+ if( this->valueExists( kMagic ) )
+ {
+ LE.putUns32( this->getValue<XMP_Uns32>( kMagic ), &prml.mMagic );
+ }
+ if( this->valueExists( kSize ) )
+ {
+ LE.putUns32( this->getValue<XMP_Uns32>( kSize ), &prml.mSize );
+ }
+ if( this->valueExists( kVerAPI ) )
+ {
+ LE.putUns16( this->getValue<XMP_Uns16>( kVerAPI ), &prml.mVerAPI );
+ }
+ if( this->valueExists( kVerCode ) )
+ {
+ LE.putUns16( this->getValue<XMP_Uns16>( kVerCode ), &prml.mVerCode );
+ }
+ if( this->valueExists( kExportType ) )
+ {
+ LE.putUns32( this->getValue<XMP_Uns32>( kExportType ), &prml.mExportType );
+ }
+ if( this->valueExists( kMacVRefNum ) )
+ {
+ LE.putUns16( this->getValue<XMP_Uns16>( kMacVRefNum ), &prml.mMacVRefNum );
+ }
+ if( this->valueExists( kMacParID ) )
+ {
+ LE.putUns32( this->getValue<XMP_Uns32>( kMacParID ), &prml.mMacParID );
+ }
+ if( this->valueExists( kFilePath ) )
+ {
+ strncpy( prml.mFilePath, this->getValue<std::string>( kFilePath ).c_str(), kSizeFilePath );
+ }
+
+ //
+ // set input buffer to zero
+ //
+ memset( buffer, 0, static_cast<size_t>(size) );
+
+ //
+ // copy Prml block into buffer
+ //
+ memcpy( buffer, &prml, kPrmlSizeFix );
+
+ *outBuffer = buffer;
+ }
+ else
+ {
+ XMP_Throw ( "Invalid buffer", kXMPErr_BadParam );
+ }
+
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+//
+// PrmLMetadata::isEmptyValue(...)
+//
+// Purpose: Is the value of the passed ValueObject and its id "empty"?
+//
+//-----------------------------------------------------------------------------
+
+bool PrmLMetadata::isEmptyValue( XMP_Uns32 id, ValueObject& valueObj )
+{
+ bool ret = true;
+
+ switch( id )
+ {
+ case kFilePath:
+ {
+ TValueObject<std::string>* strObj = dynamic_cast<TValueObject<std::string>*>(&valueObj);
+
+ ret = ( strObj == NULL || ( strObj != NULL && strObj->getValue().empty() ) );
+ }
+ break;
+
+ case kMagic:
+ case kSize:
+ case kVerAPI:
+ case kVerCode:
+ case kExportType:
+ case kMacVRefNum:
+ case kMacParID:
+ {
+ ret = false;
+ }
+ break;
+
+ default:
+ {
+ ret = true;
+ }
+ }
+
+ return ret;
+}
diff --git a/XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.h b/XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.h
new file mode 100644
index 0000000..8eb7102
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.h
@@ -0,0 +1,90 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _PrmlMetadata_h_
+#define _PrmlMetadata_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/NativeMetadataSupport/IMetadata.h"
+
+namespace IFF_RIFF
+{
+
+/**
+ * PrmL Metadata model.
+ * Implements the IMetadata interface
+ */
+class PrmLMetadata : public IMetadata
+{
+public:
+ enum
+ {
+ kMagic, // XMP_Uns32
+ kSize, // XMP_Uns32
+ kVerAPI, // XMP_Uns16
+ kVerCode, // XMP_Uns16
+ kExportType, // XMP_Uns32
+ kMacVRefNum, // XMP_Uns16
+ kMacParID, // XMP_Uns32
+ kFilePath, // char[260]
+ };
+
+public:
+ /**
+ *ctor/dtor
+ */
+ PrmLMetadata();
+ ~PrmLMetadata();
+
+ /**
+ * Parses the given memory block and creates a data model representation
+ * The implementation expects that the memory block is the data area of
+ * the PrmL chunk.
+ * Throws exceptions if parsing is not possible
+ *
+ * @param input The byte buffer to parse
+ * @param size Size of the given byte buffer
+ */
+ void parse( const XMP_Uns8* chunkData, XMP_Uns64 size );
+
+ /**
+ * See IMetadata::parse( const LFA_FileRef input )
+ */
+ void parse( XMP_IO* input ) { IMetadata::parse( input ); }
+
+ /**
+ * Serializes the data model to a memory block.
+ * The memory block will be the data area of a PrmL chunk.
+ * Throws exceptions if serializing is not possible
+ *
+ * @param buffer Buffer that gets filled with serialized data
+ * @param size Size of passed in buffer
+ *
+ * @return Size of serialzed data (might be smaller than buffer size)
+ */
+ XMP_Uns64 serialize( XMP_Uns8** buffer );
+
+protected:
+ /**
+ * @see IMetadata::isEmptyValue
+ */
+ virtual bool isEmptyValue( XMP_Uns32 id, ValueObject& valueObj );
+
+private:
+ // Operators hidden on purpose
+ PrmLMetadata( const PrmLMetadata& ) {};
+ PrmLMetadata& operator=( const PrmLMetadata& ) { return *this; };
+};
+
+}
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.cpp b/XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.cpp
new file mode 100644
index 0000000..1e98f4f
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.cpp
@@ -0,0 +1,682 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h"
+#include "XMPFiles/source/FormatSupport/IFF/Chunk.h"
+
+#include <algorithm>
+
+using namespace IFF_RIFF;
+
+//
+// Static init
+//
+const LittleEndian& WAVEBehavior::mEndian = LittleEndian::getInstance();
+
+
+//-----------------------------------------------------------------------------
+//
+// WAVEBehavior::getRealSize(...)
+//
+// Purpose: Validate the passed in size value, identify the valid size if the
+// passed in isn't valid and return the valid size.
+// Throw an exception if the passed in size isn't valid and there's
+// no way to identify a valid size.
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 WAVEBehavior::getRealSize( const XMP_Uns64 size, const ChunkIdentifier& id, IChunkContainer& tree, XMP_IO* stream )
+{
+ XMP_Uns64 realSize = size;
+
+ if( size >= kNormalRF64ChunkSize ) // 4GB
+ {
+ if( this->isRF64( tree ) )
+ {
+ //
+ // RF64 supports sizes beyond 4GB
+ //
+ DS64* rf64 = this->getDS64( tree, stream );
+
+ if( rf64 != NULL )
+ {
+ //
+ // get 64bit size from RF64 structure
+ //
+ switch( id.id )
+ {
+ case kChunk_RF64: realSize = rf64->riffSize; break;
+ case kChunk_data: realSize = rf64->dataSize; break;
+
+ default:
+ {
+ bool found = false;
+
+ //
+ // try to find size value for passed chunk id in the ds64 table
+ //
+ if( rf64->tableLength > 0 )
+ {
+ for( std::vector<ChunkSize64>::iterator iter=rf64->table.begin(); iter!=rf64->table.end(); iter++ )
+ {
+ if( iter->id == id.id )
+ {
+ realSize = iter->size;
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if( !found )
+ {
+ //
+ // no size for passed id available
+ //
+ XMP_Throw( "Unknown size value", kXMPErr_BadFileFormat );
+ }
+ }
+ }
+ }
+ else
+ {
+ //
+ // no RF64 size info available
+ //
+ XMP_Throw( "Unknown size value", kXMPErr_BadFileFormat );
+ }
+ }
+ else
+ {
+ //
+ // WAVE doesn't support that size
+ //
+ XMP_Throw( "Unknown size value", kXMPErr_BadFileFormat );
+ }
+ }
+
+ return realSize;
+}
+
+//-----------------------------------------------------------------------------
+//
+// WAVEBehavior::getMaxChunkSize(...)
+//
+// Purpose: Return the maximum size of a single chunk, i.e. the maximum size
+// of a top-level chunk.
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 WAVEBehavior::getMaxChunkSize() const
+{
+ // simple WAVE 4GByte
+ XMP_Uns64 ret = 0x00000000FFFFFFFFLL;
+
+ if( mIsRF64 )
+ {
+ // RF64: full possible 64bit size
+ ret = 0xFFFFFFFFFFFFFFFFLL;
+ }
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+//
+// WAVEBehavior::isValidTopLevelChunk(...)
+//
+// Purpose: Return true if the passed identifier is valid for top-level chunks
+// of a certain format.
+//
+//-----------------------------------------------------------------------------
+
+bool WAVEBehavior::isValidTopLevelChunk( const ChunkIdentifier& id, XMP_Uns32 chunkNo )
+{
+ return ( chunkNo == 0 ) &&
+ ( ( ( id.id == kChunk_RIFF ) && ( id.type == kType_WAVE ) ) ||
+ ( ( id.id == kChunk_RF64 ) && ( id.type == kType_WAVE ) ) );
+}
+
+//-----------------------------------------------------------------------------
+//
+// WAVEBehavior::fixHierarchy(...)
+//
+// Purpose: Fix the hierarchy of chunks depending ones based on size changes of
+// one or more chunks and second based on format specific rules.
+// Throw an exception if the hierarchy can't be fixed.
+//
+//-----------------------------------------------------------------------------
+
+void WAVEBehavior::fixHierarchy( IChunkContainer& tree )
+{
+ XMP_Validate( tree.numChildren() == 1, "WAVE files should only have one top level chunk (RIFF)", kXMPErr_BadFileFormat);
+
+ Chunk* riffChunk = tree.getChildAt(0);
+
+ XMP_Validate( (riffChunk->getType() == kType_WAVE || riffChunk->getType() == kChunk_RF64) , "Invalid type for WAVE/RF64 top level chunk (RIFF)", kXMPErr_BadFileFormat);
+
+ if( riffChunk->hasChanged() )
+ {
+ //
+ // move new added chunks to temporary container
+ //
+ Chunk* tmpContainer = Chunk::createChunk( mEndian );
+ this->moveChunks( *riffChunk, *tmpContainer, riffChunk->numChildren() - mChunksAdded );
+
+ //
+ // try to arrange chunks at their current position
+ //
+ this->arrangeChunksInPlace( *riffChunk, *tmpContainer );
+
+ //
+ // for all chunks that were moved to the end try to find a FREE chunk for them
+ //
+ this->arrangeChunksInTree( *tmpContainer, *riffChunk );
+
+ //
+ // append all remaining new added chunks to the end of the tree
+ //
+ this->moveChunks( *tmpContainer, *riffChunk, 0 );
+ delete tmpContainer;
+
+ //
+ // check for FREE chunks at the end
+ //
+ Chunk* endFREE = this->mergeFreeChunks( *riffChunk, riffChunk->numChildren() - 1 );
+
+ if( endFREE != NULL )
+ {
+ riffChunk->removeChildAt( riffChunk->numChildren() - 1 );
+ delete endFREE;
+ }
+
+ //
+ // Fix the offset values of all chunks. Throw an exception in the case that
+ // the offset of a non-modified chunk needs to be reset.
+ //
+ XMP_Validate( riffChunk->getOffset() == 0, "Invalid offset for RIFF top level chunk", kXMPErr_InternalFailure );
+
+ this->validateOffsets( tree );
+
+ //
+ // update the RF64 chunk (if this is RF64) based on the current chunk sizes
+ //
+ this->updateRF64( tree );
+ }
+}
+
+void WAVEBehavior::insertChunk( IChunkContainer& tree, Chunk& chunk )
+{
+ XMP_Validate( tree.numChildren() == 1, "WAVE files should only have one top level chunk (RIFF)", kXMPErr_BadFileFormat);
+ Chunk* riffChunk = tree.getChildAt(0);
+
+ XMP_Validate( riffChunk->getType() == kType_WAVE , "Invalid type for WAVE top level chunk (RIFF)", kXMPErr_BadFileFormat);
+
+ //
+ // add new chunk to the end of the RIFF:WAVE
+ //
+ riffChunk->appendChild(&chunk);
+
+ mChunksAdded++;
+}
+
+bool WAVEBehavior::removeChunk( IChunkContainer& tree, Chunk& chunk )
+{
+ //
+ // validate parameter
+ //
+ XMP_Validate( chunk.getID() != kChunk_RIFF, "Can't remove RIFF chunk!", kXMPErr_InternalFailure );
+ XMP_Validate( chunk.getChunkMode() != CHUNK_UNKNOWN, "Cant' remove UNKNOWN Chunk", kXMPErr_InternalFailure );
+ XMP_Validate( tree.numChildren() == 1, "WAVE files should only have one top level chunk (RIFF)", kXMPErr_BadFileFormat);
+
+ //
+ // get top-level chunk
+ //
+ Chunk* riffChunk = tree.getChildAt(0);
+
+ //
+ // validate top-level chunk
+ //
+ XMP_Validate( (riffChunk->getType() == kType_WAVE || riffChunk->getType() == kChunk_RF64) , "Invalid type for WAVE/RF64 top level chunk (RIFF)", kXMPErr_BadFileFormat);
+
+ //
+ // calculate index of chunk to remove
+ //
+ XMP_Uns32 i = std::find( riffChunk->firstChild(), riffChunk->lastChild(), &chunk ) - riffChunk->firstChild();
+
+ //
+ // validate index
+ //
+ XMP_Validate( i < riffChunk->numChildren(), "Invalid chunk in tree", kXMPErr_InternalFailure );
+
+ //
+ // adjust new chunks counter
+ //
+ if( i > riffChunk->numChildren() - mChunksAdded - 1 )
+ {
+ mChunksAdded--;
+ }
+
+ if( i < riffChunk->numChildren()-1 )
+ {
+ //
+ // fill gap with free chunk
+ //
+ Chunk* free = this->createFREE( chunk.getPadSize( true ) );
+ riffChunk->replaceChildAt( i, free );
+ free->setAsNew();
+
+ //
+ // merge JUNK chunks
+ //
+ this->mergeFreeChunks( *riffChunk, i );
+ }
+ else
+ {
+ //
+ // remove chunk from tree
+ //
+ riffChunk->removeChildAt( i );
+ }
+
+ //
+ // if there is an entry in the ds64 table for the removed chunk
+ // then update the ds64 table entry
+ //
+ if( mDS64Data != NULL && mDS64Data->tableLength > 0 )
+ {
+ for( std::vector<ChunkSize64>::iterator iter=mDS64Data->table.begin(); iter!=mDS64Data->table.end(); iter++ )
+ {
+ if( iter->id == chunk.getID() )
+ {
+ //
+ // don't remove entry but set its size to zero
+ //
+ iter->size = 0LL;
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+Chunk* WAVEBehavior::createFREE( XMP_Uns64 chunkSize )
+{
+ XMP_Int64 alloc = chunkSize - Chunk::HEADER_SIZE;
+
+ Chunk* chunk = NULL;
+
+ //
+ // create a 'JUNK' chunk
+ //
+ if( alloc > 0 )
+ {
+ XMP_Uns8* data = new XMP_Uns8[static_cast<size_t>( alloc )];
+ memset( data, 0, static_cast<size_t>( alloc ) );
+
+ chunk = Chunk::createUnknownChunk( mEndian, kChunk_JUNK, kType_NONE, alloc );
+
+ chunk->setData( data, alloc );
+
+ delete[] data;
+ }
+ else
+ {
+ chunk = Chunk::createHeaderChunk( mEndian, kChunk_JUNK );
+ }
+
+ // force set dirty flag
+ chunk->setChanged();
+
+ return chunk;
+}
+
+XMP_Bool WAVEBehavior::isFREEChunk( const Chunk& chunk ) const
+{
+ // Check for sigature JUNK and JUNQ
+ return ( chunk.getID() == kChunk_JUNK || chunk.getID() == kChunk_JUNQ );
+}
+
+
+XMP_Uns64 WAVEBehavior::getMinFREESize() const
+{
+ // avoid creation of chunks with size==0
+ return static_cast<XMP_Uns64>( Chunk::HEADER_SIZE ) + 2;
+}
+
+//-----------------------------------------------------------------------------
+//
+// WAVEBehavior::isRF64(...)
+//
+// Purpose: Is the current file a RF64 file
+//
+//-----------------------------------------------------------------------------
+
+bool WAVEBehavior::isRF64( const IChunkContainer& tree )
+{
+ // The file format will not change at runtime
+ // So if the flag is not already set, have a look at the tree
+ if( ! mIsRF64 && tree.numChildren() != 0 )
+ {
+ Chunk *chunk = tree.getChildAt(0);
+ // Only the TopLevel chunk is interesting
+ mIsRF64 = chunk->getID() == kChunk_RF64 &&
+ chunk->getType() == kType_WAVE;
+ }
+
+ return mIsRF64;
+}
+
+//-----------------------------------------------------------------------------
+//
+// WAVEBehavior::getDS64(...)
+//
+// Purpose: Return RF64 structure.
+//
+//-----------------------------------------------------------------------------
+
+WAVEBehavior::DS64* WAVEBehavior::getDS64( IChunkContainer& tree, XMP_IO* stream )
+{
+ DS64* ret = mDS64Data;
+
+ if( ret == NULL )
+ {
+ //
+ // try to find 'ds64' chunk in the tree
+ //
+ Chunk* ds64 = NULL;
+ Chunk* rf64 = NULL;
+
+ if( tree.numChildren() > 0 )
+ {
+ rf64 = tree.getChildAt(0);
+
+ if( rf64 != NULL && rf64->getID() == kChunk_RF64 && rf64->numChildren() > 0 )
+ {
+ //
+ // 'ds64' chunk needs to be the very first child of the 'RF64' chunk
+ //
+ ds64 = rf64->getChildAt(0);
+ }
+
+ //
+ // Try to create 'ds64' chunk by parsing the stream
+ //
+ if( ds64 == NULL && stream != NULL )
+ {
+ //
+ // remember file position before start reading from the stream
+ //
+ XMP_Uns64 filePos = stream->Offset();
+
+ try
+ {
+ ds64 = Chunk::createChunk( mEndian );
+ ds64->readChunk( stream );
+ }
+ catch( ... )
+ {
+ delete ds64;
+ ds64 = NULL;
+ }
+
+ if( ds64 != NULL && ds64->getID() == kChunk_ds64 )
+ {
+ //
+ // Successfully read 'ds64' chunk.
+ // Now read its data area as well and
+ // add chunk to the 'RF64' chunk
+ //
+ ds64->cacheChunkData( stream );
+
+ rf64->appendChild( ds64, false );
+ }
+ else
+ {
+ //
+ // Either the reading failed or the 'ds64' chunk
+ // doesn't exists at the expected position.
+ // Now clean up and reject the stream position.
+ //
+ delete ds64;
+ ds64 = NULL;
+
+ stream->Seek( filePos, kXMP_SeekFromStart );
+ }
+ }
+ else if( ds64 != NULL && ds64->getID() != kChunk_ds64 )
+ {
+ //
+ // first child of 'RF64' chunk is NOT 'ds64'!
+ //
+ ds64 = NULL;
+ }
+ }
+
+ //
+ // parse 'ds64' chunk, store the RF64 struct and return it
+ //
+ if( ds64 != NULL )
+ {
+ DS64* ds64data = new DS64();
+
+ if( this->parseDS64Chunk( *ds64, *ds64data ) )
+ {
+ mDS64Data = ds64data;
+ ret = mDS64Data;
+ }
+ else
+ {
+ delete ds64data;
+ }
+ }
+ }
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+//
+// WAVEBehavior::updateRF64(...)
+//
+// Purpose: update the RF64 chunk (if this is RF64) based on the current chunk sizes
+//
+//-----------------------------------------------------------------------------
+
+void WAVEBehavior::updateRF64( IChunkContainer& tree )
+{
+ if( this->isRF64( tree ) )
+ {
+ XMP_Validate( mDS64Data != NULL, "Missing DS64 structure", kXMPErr_InternalFailure );
+ XMP_Validate( tree.numChildren() == 1, "Invalid RF64 tree", kXMPErr_InternalFailure );
+
+ //
+ // Check all chunks that sizes have changed and update their related value in the DS64 chunk
+ //
+ Chunk* rf64 = tree.getChildAt(0);
+ XMP_Validate( rf64 != NULL && rf64->getID() == kChunk_RF64 && rf64->numChildren() > 0, "Invalid RF64 chunk", kXMPErr_InternalFailure );
+
+ this->doUpdateRF64( *rf64 );
+
+ //
+ // try to find 'ds64' chunk in the tree
+ // (needs to be the very first child of the 'RF64' chunk)
+ //
+ Chunk* ds64 = rf64->getChildAt(0);
+ XMP_Validate( ds64 != NULL && ds64->getID() == kChunk_ds64, "Missing 'ds64' chunk", kXMPErr_InternalFailure );
+
+ //
+ // serialize DS64 structure and write into ds64 chunk
+ //
+ this->serializeDS64Chunk( *mDS64Data, *ds64 );
+ }
+}
+
+void WAVEBehavior::doUpdateRF64( Chunk& chunk )
+{
+ //
+ // update ds64 entry for chunk if its size has changed
+ //
+ if( chunk.hasChanged() && chunk.getOriginalSize() > kNormalRF64ChunkSize )
+ {
+ switch( chunk.getID() )
+ {
+ case kChunk_RF64: mDS64Data->riffSize = chunk.getSize(); break;
+ case kChunk_data:
+ if( chunk.getSize() != chunk.getOriginalSize() )
+ {
+ XMP_Throw( "Data chunk must not change", kXMPErr_InternalFailure );
+ }
+ break;
+ default:
+ {
+ bool requireEntry = ( chunk.getSize() > kNormalRF64ChunkSize );
+ bool found = false;
+
+ //
+ // try to find entry for passed chunk id in the ds64 table
+ //
+ if( mDS64Data->tableLength > 0 )
+ {
+ for( std::vector<ChunkSize64>::iterator iter=mDS64Data->table.begin(); iter!=mDS64Data->table.end(); iter++ )
+ {
+ if( iter->id == chunk.getID() )
+ {
+ // always set new size even if it's less than 4GB
+ iter->size = chunk.getSize();
+ found = true;
+ break;
+ }
+ }
+ }
+
+ //
+ // We can't add new entries to the table. So if we found no entry within 'ds64'
+ // for the passed chunk ID and the size of the chunk is larger than 4GB then
+ // we have to throw an exception
+ //
+ XMP_Validate( found || ( ! found && ! requireEntry ), "Can't update 'ds64' chunk", kXMPErr_Unimplemented );
+ }
+ }
+ }
+
+ //
+ // go through all children to update ds64 data
+ //
+ for( XMP_Uns32 i=0; i<chunk.numChildren(); i++ )
+ {
+ Chunk* child = chunk.getChildAt(i);
+
+ this->doUpdateRF64( *child );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// WAVEBehavior::parseRF64Chunk(...)
+//
+// Purpose: Parses the data block of the given RF64 chunk into the internal data structures
+//
+//-----------------------------------------------------------------------------
+
+bool WAVEBehavior::parseDS64Chunk( const Chunk& ds64Chunk, WAVEBehavior::DS64& ds64 )
+{
+ bool ret = false;
+
+ // It is a valid ds64 chunk
+ if( ds64Chunk.getID() == kChunk_ds64 && ds64Chunk.getSize() >= kMinimumDS64ChunkSize )
+ {
+ const XMP_Uns8* data;
+ XMP_Uns64 size = ds64Chunk.getData(&data);
+
+ memset( &ds64, 0, kMinimumDS64ChunkSize );
+
+ //
+ // copy fix input data into RF64 block (except chunk size table)
+ // Safe as fixed size matches size of struct that is #pragma packed(1)
+ //
+ memcpy( &ds64, data, kMinimumDS64ChunkSize );
+
+ // If there is more data but the table length is <= 0 then this is not a valid ds64 chunk
+ if( size > kMinimumDS64ChunkSize && ds64.tableLength > 0 )
+ {
+ // copy chunk sizes table
+ //
+ XMP_Assert( size - kMinimumDS64ChunkSize >= ds64.tableLength * sizeof(ChunkSize64));
+
+ XMP_Uns32 offset = kMinimumDS64ChunkSize;
+ ChunkSize64 chunkSize;
+
+ for( XMP_Uns32 i = 0 ; i < ds64.tableLength ; i++, offset += sizeof(ChunkSize64) )
+ {
+ chunkSize.id = mEndian.getUns32( data + offset );
+ chunkSize.size = mEndian.getUns64( data + offset + 4 );
+
+ ds64.table.push_back( chunkSize );
+ }
+ }
+
+ // remember any existing table buffer
+ ds64.trailingBytes = static_cast<XMP_Uns32>(size - kMinimumDS64ChunkSize - ds64.tableLength * sizeof(ChunkSize64));
+
+ // Either a table has been correctly parsed or there was no table
+ ret = (size - kMinimumDS64ChunkSize) >= (ds64.tableLength * sizeof(ChunkSize64));
+ }
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+//
+// WAVEBehavior::serializeRF64Chunk(...)
+//
+// Purpose: Serializes the internal RF64 data structures into the data part of the given chunk
+//
+//-----------------------------------------------------------------------------
+bool WAVEBehavior::serializeDS64Chunk( const WAVEBehavior::DS64& ds64, Chunk& ds64Chunk )
+{
+ if( ds64Chunk.getID() != kChunk_ds64 )
+ {
+ return false; // not a valid ds64 chunk
+ }
+
+ // Calculate needed size
+ XMP_Uns32 size = kMinimumDS64ChunkSize + ds64.tableLength * sizeof(ChunkSize64) + ds64.trailingBytes;
+ // Create tmp buffer
+ XMP_Uns8* data = new XMP_Uns8[size];
+ memset( data, 0, size );
+
+ // copy fix input data into buffer (except chunk sizes table)
+ // Safe as fixed size matches size of struct that is #pragma packed(1)
+ memcpy( data, &ds64, kMinimumDS64ChunkSize );
+
+ // copy chunk sizes table
+ if( ds64.tableLength > 0 )
+ {
+ XMP_Uns32 offset = kMinimumDS64ChunkSize;
+
+ for( XMP_Uns32 i = 0 ; i < ds64.tableLength ; i++, offset += sizeof(ChunkSize64) )
+ {
+ mEndian.putUns32( ds64.table.at(i).id, data + offset );
+ mEndian.putUns64( ds64.table.at(i).size, data + offset + 4 );
+ }
+ }
+
+ ds64Chunk.setData( data, size );
+
+ // free tmp buffer
+ delete []data;
+
+ return true;
+}
diff --git a/XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h b/XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h
new file mode 100644
index 0000000..1395cd1
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h
@@ -0,0 +1,215 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _WAVEBEHAVIOR_h_
+#define _WAVEBEHAVIOR_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h"
+#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h"
+#include "source/Endian.h"
+
+namespace IFF_RIFF
+{
+
+/**
+ WAVE behavior class.
+
+ Implements the IChunkBehavior interface
+*/
+
+
+class WAVEBehavior : public IChunkBehavior
+{
+// Internal structure to hold RF64 related data
+public:
+#pragma pack ( push, 1 )
+ struct ChunkSize64
+ {
+ XMP_Uns64 size;
+ XMP_Uns32 id;
+ // Ctor
+ ChunkSize64(): size(0), id(0) {}
+ };
+
+ struct DS64
+ {
+ XMP_Uns64 riffSize;
+ XMP_Uns64 dataSize;
+ XMP_Uns64 sampleCount;
+ XMP_Uns32 tableLength;
+ // fix part ends here
+ XMP_Uns32 trailingBytes;
+ std::vector<ChunkSize64> table;
+
+ // ctor
+ DS64(): riffSize(0), dataSize(0), sampleCount(0), tableLength(0), trailingBytes(0) {}
+ };
+#pragma pack ( pop )
+
+
+ /**
+ ctor/dtor
+ */
+ WAVEBehavior() : mChunksAdded(0), mIsRF64(false), mDS64Data(NULL) {}
+
+ virtual ~WAVEBehavior()
+ {
+ if( mDS64Data != NULL )
+ {
+ delete mDS64Data;
+ }
+ }
+
+ /**
+ Validate the passed in size value, identify the valid size if the passed in isn't valid
+ and return the valid size.
+ throw an exception if the passed in size isn't valid and there's no way to identify a
+ valid size.
+
+ @param size Size value
+ @param id Identifier of chunk
+ @param tree Chunk tree
+ @param stream Stream handle
+
+ @return Valid size value.
+ */
+ XMP_Uns64 getRealSize( const XMP_Uns64 size, const ChunkIdentifier& id, IChunkContainer& tree, XMP_IO* stream );
+
+ /**
+ Return the maximum size of a single chunk, i.e. the maximum size of a top-level chunk.
+
+ @return Maximum size
+ */
+ XMP_Uns64 getMaxChunkSize() const;
+
+ /**
+ Return true if the passed identifier is valid for top-level chunks of a certain format.
+
+ @param id Chunk identifier
+ @param chunkNo order number of top-level chunk
+ @return true, if passed id is a valid top-level chunk
+ */
+ bool isValidTopLevelChunk( const ChunkIdentifier& id, XMP_Uns32 chunkNo );
+
+ /**
+ Fix the hierarchy of chunks depending ones based on size changes of one or more chunks
+ and second based on format specific rules.
+ Throw an exception if the hierarchy can't be fixed.
+
+ @param tree Vector of root chunks.
+ */
+ void fixHierarchy( IChunkContainer& tree );
+
+ /**
+ Insert a new chunk into the hierarchy of chunks. The behavior needs to decide the position
+ of the new chunk and has to do the insertion.
+
+ @param tree Chunk tree
+ @param chunk New chunk
+ */
+ void insertChunk( IChunkContainer& tree, Chunk& chunk ) ;
+
+ /**
+ Remove the chunk described by the passed ChunkPath.
+
+ @param tree Chunk tree
+ @param path Path to the chunk that needs to be removed
+
+ @return true if the chunk was removed and need to be deleted
+ */
+ bool removeChunk( IChunkContainer& tree, Chunk& chunk ) ;
+
+protected:
+ /**
+ Create a FREE chunk.
+ If the chunkSize is smaller than the header+type - size then create an annotation chunk.
+ If the passed size is odd, then add a pad byte.
+
+ @param chunkSize Total size including header
+ @return New FREE chunk
+ */
+ Chunk* createFREE( XMP_Uns64 chunkSize );
+
+ /**
+ Check if the passed chunk is a FREE chunk.
+ (Could be also a small annotation chunk with zero bytes in its data)
+
+ @param chunk A chunk
+
+ @return true if the passed chunk is a FREE chunk
+ */
+ XMP_Bool isFREEChunk( const Chunk& chunk ) const;
+
+ /**
+ Return the minimum size of a FREE chunk
+ */
+ XMP_Uns64 getMinFREESize( ) const;
+
+ /**
+ Is the current file a RF64 file?
+
+ @param tree The whole chunk tree beginning with the root node
+
+ @return true if the current file is in the RF64 format
+ */
+ bool isRF64( const IChunkContainer& tree );
+
+ /**
+ Return RF64 structure.
+
+ If the related chunk ('ds64') is not yet parsed then it should be parsed by this method.
+
+ @param tree The chunk tree (probably a subtree)
+ @param stream File stream
+
+ @return DS64 structure
+ */
+ DS64* getDS64( IChunkContainer& tree, XMP_IO* stream );
+
+ /**
+ update the RF64 chunk (if this is RF64) based on the current chunk sizes
+ */
+ void updateRF64( IChunkContainer& tree );
+
+ /**
+ * Parses the data block of the given RF64 chunk into the internal data structures
+ * @param rf64Chunk the RF64 Chunk
+ * @param rf64 OUT the RF64 data structure
+ * @return The parsing was successful (true) or not (false)
+ */
+ bool parseDS64Chunk( const Chunk& ds64Chunk, DS64& rf64 );
+
+ /**
+ * Serializes the internal RF64 data structures into the data part of the given chunk
+ * @param rf64 the RF64 data structure
+ * @param rf64Chunk OUT the RF64 Chunk
+ * @return The serialization was successful (true) or not (false)
+ */
+ bool serializeDS64Chunk( const WAVEBehavior::DS64& rf64, Chunk& ds64Chunk );
+
+private:
+ void doUpdateRF64( Chunk& chunk );
+
+private:
+ XMP_Uns32 mChunksAdded;
+ bool mIsRF64;
+ DS64* mDS64Data;
+
+ static const LittleEndian& mEndian; // WAVE is always Little Endian
+ static const XMP_Uns32 kNormalRF64ChunkSize = 0xFFFFFFFF;
+ static const XMP_Uns32 kMinimumDS64ChunkSize = 28;
+}; // IFF_RIFF
+
+}
+#endif
diff --git a/XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.cpp b/XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.cpp
new file mode 100644
index 0000000..3798f3d
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.cpp
@@ -0,0 +1,576 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.h"
+#include "XMPFiles/source/FormatSupport/WAVE/DISPMetadata.h"
+#include "XMPFiles/source/FormatSupport/WAVE/INFOMetadata.h"
+#include "XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h"
+#include "XMPFiles/source/FormatSupport/WAVE/CartMetadata.h"
+
+// cr8r is not yet required for WAVE
+//#include "Cr8rMetadata.h"
+
+#include "XMPFiles/source/NativeMetadataSupport/MetadataSet.h"
+
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+
+using namespace IFF_RIFF;
+
+// ************** legacy Mappings ***************** //
+
+static const MetadataPropertyInfo kBextProperties[] =
+{
+// XMP NS XMP Property Name Native Metadata Identifier Native Datatype XMP Datatype Delete Priority ExportPolicy
+ { kXMP_NS_BWF, "description", BEXTMetadata::kDescription, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always }, // bext:description <-> BEXT:Description
+ { kXMP_NS_BWF, "originator", BEXTMetadata::kOriginator, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always }, // bext:originator <-> BEXT:originator
+ { kXMP_NS_BWF, "originatorReference", BEXTMetadata::kOriginatorReference, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always }, // bext:OriginatorReference <-> BEXT:OriginatorReference
+ { kXMP_NS_BWF, "originationDate", BEXTMetadata::kOriginationDate, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always }, // bext:originationDate <-> BEXT:originationDate
+ { kXMP_NS_BWF, "originationTime", BEXTMetadata::kOriginationTime, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always }, // bext:originationTime <-> BEXT:originationTime
+ { kXMP_NS_BWF, "timeReference", BEXTMetadata::kTimeReference, kNativeType_Uns64, kXMPType_Simple, true, false, kExport_Always }, // bext:timeReference <-> BEXT:TimeReferenceLow + BEXT:TimeReferenceHigh
+ // Special case: On export BEXT:version is always written as 1
+ { kXMP_NS_BWF, "version", BEXTMetadata::kVersion, kNativeType_Uns16, kXMPType_Simple, true, false, kExport_Never }, // bext:version <-> BEXT:version
+ // special case: bext:umid <-> BEXT:UMID
+ { kXMP_NS_BWF, "codingHistory", BEXTMetadata::kCodingHistory, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always }, // bext:codingHistory <-> BEXT:codingHistory
+ { NULL }
+};
+
+static const MetadataPropertyInfo kINFOProperties[] =
+{
+// XMP NS XMP Property Name Native Metadata Identifier Native Datatype XMP Datatype Delete Priority ExportPolicy
+ { kXMP_NS_DM, "artist", INFOMetadata::kArtist, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmpDM:artist <-> IART
+ { kXMP_NS_DM, "logComment", INFOMetadata::kComments, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmpDM:logComment <-> ICMT
+ { kXMP_NS_DC, "rights", INFOMetadata::kCopyright, kNativeType_StrUTF8, kXMPType_Localized, false, true, kExport_Always }, // dc:rights <-> ICOP
+ { kXMP_NS_XMP, "CreateDate", INFOMetadata::kCreationDate, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmp:CreateDate <-> ICRD
+ { kXMP_NS_DM, "engineer", INFOMetadata::kEngineer, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmpDM:engineer <-> IENG
+ { kXMP_NS_DM, "genre", INFOMetadata::kGenre, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmpDM:genre <-> IGNR
+ { kXMP_NS_XMP, "CreatorTool", INFOMetadata::kSoftware, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmp:CreatorTool <-> ISFT
+ { kXMP_NS_DC, "source", INFOMetadata::kMedium, kNativeType_StrUTF8, kXMPType_Simple, false, false, kExport_Always }, // dc:source <-> IMED, not in old digest
+ { kXMP_NS_DC, "type", INFOMetadata::kSourceForm, kNativeType_StrUTF8, kXMPType_Array, false, false, kExport_Always }, // dc:type <-> ISRF, not in old digest
+
+ // new mappings
+ { kXMP_NS_RIFFINFO, "name", INFOMetadata::kName, kNativeType_StrUTF8, kXMPType_Localized, true, false, kExport_Always }, // riffinfo:name <-> INAM
+ { kXMP_NS_RIFFINFO, "archivalLocation", INFOMetadata::kArchivalLocation,kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // riffinfo:archivalLocation <-> IARL
+ { kXMP_NS_RIFFINFO, "commissioned", INFOMetadata::kCommissioned, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // riffinfo:commissioned <-> ICMS
+ // special case, the native value is a semicolon-separated array
+ // { kXMP_NS_DC, "subject", INFOMetadata::kKeywords, kNativeType_StrUTF8, kXMPType_Array, false, false, kExport_Always }, // dc:subject <-> IKEY
+ { kXMP_NS_RIFFINFO, "product", INFOMetadata::kProduct, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // riffinfo:product <-> IPRD
+ { kXMP_NS_DC, "description", INFOMetadata::kSubject, kNativeType_StrUTF8, kXMPType_Localized, false, false, kExport_Always }, // dc:description <-> ISBJ
+ { kXMP_NS_RIFFINFO, "source", INFOMetadata::kSource, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // riffinfo:source <-> ISRC
+ { kXMP_NS_RIFFINFO, "technician", INFOMetadata::kTechnican, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // riffinfo:technician <-> ITCH
+
+ { NULL }
+};
+
+static const MetadataPropertyInfo kDISPProperties[] =
+{
+// XMP NS XMP Property Name Native Metadata Identifier Datatype Datatype Delete Priority ExportPolicy
+ { kXMP_NS_DC, "title", DISPMetadata::kTitle, kNativeType_StrUTF8, kXMPType_Localized, false, true, kExport_Always }, // dc:title <-> DISP
+ // Special case: DISP will overwrite LIST/INFO:INAM in dc:title if existing
+ { NULL }
+};
+
+static const MetadataPropertyInfo kCartProperties[] =
+{
+// XMP NS XMP Property Name Native Metadata Identifier Datatype Datatype Delete Priority ExportPolicy
+ { kXMP_NS_AEScart, "Version", CartMetadata::kVersion, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "Title", CartMetadata::kTitle, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "Artist", CartMetadata::kArtist, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "CutID", CartMetadata::kCutID, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "ClientID", CartMetadata::kClientID, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "Category", CartMetadata::kCategory, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "Classification", CartMetadata::kClassification, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "OutCue", CartMetadata::kOutCue, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "StartDate", CartMetadata::kStartDate, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "StartTime", CartMetadata::kStartTime, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "EndDate", CartMetadata::kEndDate, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "EndTime", CartMetadata::kEndTime, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "ProducerAppID", CartMetadata::kProducerAppID, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "ProducerAppVersion", CartMetadata::kProducerAppVersion, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "UserDef", CartMetadata::kUserDef, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "URL", CartMetadata::kURL, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "TagText", CartMetadata::kTagText, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
+ { kXMP_NS_AEScart, "LevelReference", CartMetadata::kLevelReference, kNativeType_Int32, kXMPType_Simple, true, false, kExport_Always },
+ // Special case Post Timer
+ { NULL }
+};
+
+// cr8r is not yet required for WAVE
+//
+//static const MetadataPropertyInfo kCr8rProperties[] =
+//{
+//// XMP NS XMP Property Name Native Metadata Identifier Native Datatype XMP Datatype Delete Priority ExportPolicy
+// { kXMP_NS_CreatorAtom, "macAtom/creatorAtom:applicationCode", Cr8rMetadata::kCreatorCode, kNativeType_Uns32, kXMPType_Simple, false, false, kExport_Always }, // creatorAtom:macAtom/creatorAtom:applicationCode <-> Cr8r.creatorCode
+// { kXMP_NS_CreatorAtom, "macAtom/creatorAtom:invocationAppleEvent", Cr8rMetadata::kAppleEvent, kNativeType_Uns32, kXMPType_Simple, false, false, kExport_Always }, // creatorAtom:macAtom/creatorAtom:invocationAppleEvent <-> Cr8r.appleEvent
+// { kXMP_NS_CreatorAtom, "windowsAtom/creatorAtom:extension", Cr8rMetadata::kFileExt, kNativeType_Str, kXMPType_Simple, false, false, kExport_Always }, // creatorAtom:windowsAtom/creatorAtom:extension <-> Cr8r.fileExt
+// { kXMP_NS_CreatorAtom, "windowsAtom/creatorAtom:invocationFlags", Cr8rMetadata::kAppOptions, kNativeType_Str, kXMPType_Simple, false, false, kExport_Always }, // creatorAtom:windowsAtom/creatorAtom:invocationFlags <-> Cr8r.appOptions
+// { kXMP_NS_XMP, "CreatorTool", Cr8rMetadata::kAppName, kNativeType_Str, kXMPType_Simple, false, false, kExport_Always }, // xmp:CreatorTool <-> Cr8r.appName
+// { NULL }
+//};
+
+// ! PrmL atom has all special mappings
+
+// ************** legacy Mappings end ***************** //
+
+XMP_Bool WAVEReconcile::importToXMP( SXMPMeta& outXMP, const MetadataSet& inMetaData )
+{
+ bool changed = false;
+
+ // the reconciliation is based on the existing outXMP packet
+
+ //
+ // ! The existence of a digest leads to prefering pre-existing XMP over legacy properties.
+ //
+ bool hasDigest = outXMP.GetProperty( kXMP_NS_WAV, "NativeDigest", NULL , NULL );
+
+ if ( hasDigest )
+ {
+ // remove, as digests are no longer used.
+ outXMP.DeleteProperty( kXMP_NS_WAV, "NativeDigest" );
+ }
+
+
+ if ( ! ignoreLocalText )
+ {
+
+ //
+ // Import BEXT
+ //
+ BEXTMetadata *bextMeta = inMetaData.get<BEXTMetadata>();
+
+ if (bextMeta != NULL)
+ {
+ changed |= IReconcile::importNativeToXMP( outXMP, *bextMeta, kBextProperties, false );
+
+ // bext:umid <-> BEXT:UMID
+ if ( bextMeta->valueExists( BEXTMetadata::kUMID ) )
+ {
+ XMP_Uns32 umidSize = 0;
+ const XMP_Uns8* const umid = bextMeta->getArray<XMP_Uns8>( BEXTMetadata::kUMID, umidSize );
+
+ std::string xmpValue;
+ bool allZero = encodeToHexString( umid, xmpValue );
+
+ if( ! allZero )
+ {
+ outXMP.SetProperty(kXMP_NS_BWF, "umid", xmpValue.c_str());
+ changed = true;
+ }
+ }
+
+ }
+
+
+ //
+ // Import cart
+ //
+ CartMetadata* cartData = inMetaData.get<CartMetadata>();
+ if ( cartData != NULL )
+ {
+
+ if (cartData->valueExists( CartMetadata::kPostTimer ) )
+ {
+ // first get Array
+ XMP_Uns32 size = 0;
+ const CartMetadata::StoredCartTimer* timerArray = cartData->getArray<CartMetadata::StoredCartTimer> ( CartMetadata::kPostTimer, size );
+ XMP_Assert (size == CartMetadata::kPostTimerLength );
+
+ char usage [5];
+ XMP_Uns32 usageBE = 0;
+ char value [25]; // Unsigned has 10 dezimal digets (buffer has 25 just to be save)
+ std::string path = "";
+ memset ( usage, 0, 5 ); // Fill with zeros
+ memset ( value, 0, 25 ); // Fill with zeros
+
+ outXMP.DeleteProperty( kXMP_NS_AEScart, "PostTimer");
+ outXMP.AppendArrayItem( kXMP_NS_AEScart, "PostTimer", kXMP_PropArrayIsOrdered, NULL, kXMP_PropValueIsStruct );
+
+ for ( XMP_Uns32 i = 0; i<CartMetadata::kPostTimerLength; i++ )
+ {
+ // Ensure to write as Big Endian
+ usageBE = MakeUns32BE(timerArray[i].usage);
+ memcpy( usage, &usageBE, 4 );
+
+ snprintf ( value, 24, "%u", timerArray[i].value);
+
+ SXMPUtils::ComposeArrayItemPath( kXMP_NS_AEScart, "PostTimer", i+1, &path);
+ outXMP.SetStructField( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Usage", usage );
+ outXMP.SetStructField( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Value", value );
+ }
+
+ changed = true;
+ }
+
+ // import rest if cart properties
+ changed |= IReconcile::importNativeToXMP ( outXMP, *cartData, kCartProperties, false );
+ }
+
+ }
+
+ // cr8r is not yet required for WAVE
+
+ ////
+ //// import cr8r
+ //// Special case: If both Cr8r.AppName and LIST/INFO:ISFT are present, ISFT shall win.
+ //// therefor import Cr8r first.
+ ////
+ //Cr8rMetadata* cr8rMeta = inMetaData.get<Cr8rMetadata>();
+
+ //if( cr8rMeta != NULL )
+ //{
+ // changed |= IReconcile::importNativeToXMP( outXMP, *cr8rMeta, kCr8rProperties, false );
+ //}
+
+ //
+ // Import LIST/INFO
+ //
+ INFOMetadata *infoMeta = inMetaData.get<INFOMetadata>();
+ bool hasINAM = false;
+ std::string actualLang;
+ bool hasDCTitle = outXMP.GetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, NULL, NULL );
+
+ if (infoMeta != NULL)
+ {
+ //
+ // Remember if List/INFO:INAM has been imported
+ //
+ hasINAM = infoMeta->valueExists( INFOMetadata::kName );
+
+ // Keywords are a ;-separated list and is therefore handled manually,
+ // leveraging the XMPUtils functions
+ if (infoMeta->valueExists( INFOMetadata::kKeywords ) )
+ {
+ std::string keywordsUTF8;
+ outXMP.DeleteProperty( kXMP_NS_DC, "subject" );
+ ReconcileUtils::NativeToUTF8( infoMeta->getValue<std::string>( INFOMetadata::kKeywords ), keywordsUTF8 );
+ SXMPUtils::SeparateArrayItems( &outXMP, kXMP_NS_DC, "subject", kXMP_PropArrayIsUnordered, keywordsUTF8 );
+ changed = true;
+ }
+
+ //
+ // import properties
+ //
+ changed |= IReconcile::importNativeToXMP( outXMP, *infoMeta, kINFOProperties, hasDigest );
+ }
+
+ //
+ // Import DISP
+ // ! DISP will overwrite dc:title
+ //
+ bool hasDISP = false;
+
+ DISPMetadata *dispMeta = inMetaData.get<DISPMetadata>();
+
+ if( dispMeta != NULL && dispMeta->valueExists( DISPMetadata::kTitle) )
+ {
+ changed |= IReconcile::importNativeToXMP( outXMP, *dispMeta, kDISPProperties, hasDigest );
+ hasDISP = true;
+ }
+
+ if( !hasDISP )
+ {
+ //
+ // map INAM to dc:title ONLY in the case if:
+ // * DISP does NOT exists
+ // * dc:title does NOT exists
+ // * INAM exists
+ //
+ if( !hasDCTitle && hasINAM )
+ {
+ std::string xmpValue;
+ ReconcileUtils::NativeToUTF8( infoMeta->getValue<std::string>( INFOMetadata::kName ), xmpValue );
+ outXMP.SetLocalizedText( kXMP_NS_DC, "title", NULL, "x-default", xmpValue.c_str() );
+ }
+ }
+
+ return changed;
+} // importToXMP
+
+
+XMP_Bool WAVEReconcile::exportFromXMP( MetadataSet& outMetaData, SXMPMeta& inXMP )
+{
+ // tracks if anything has been exported from the XMP
+ bool changed = false;
+
+ //
+ // Export DISP
+ //
+ DISPMetadata *dispMeta = outMetaData.get<DISPMetadata>();
+ if (dispMeta != NULL)
+ {
+ // dc:title <-> DISP
+ changed |= IReconcile::exportXMPToNative( *dispMeta, inXMP, kDISPProperties );
+ }
+
+
+ if ( ! ignoreLocalText )
+ {
+ //
+ // Export BEXT
+ //
+
+ BEXTMetadata *bextMeta = outMetaData.get<BEXTMetadata>();
+
+ if (bextMeta != NULL)
+ {
+ IReconcile::exportXMPToNative( *bextMeta, inXMP, kBextProperties );
+
+ std::string xmpValue;
+
+ // bext:umid <-> RIFF:WAVE/bext.UMID
+ if (inXMP.GetProperty(kXMP_NS_BWF, "umid", &xmpValue, 0))
+ {
+ std::string umid;
+
+ if( this->decodeFromHexString( xmpValue, umid ) )
+ {
+ //
+ // if the XMP property doesn't contain a valid hex string then
+ // keep the existing value in the umid BEXT field
+ //
+ bextMeta->setArray<XMP_Uns8>(BEXTMetadata::kUMID, reinterpret_cast<const XMP_Uns8*>(umid.data()), umid.length());
+ }
+ }
+ else
+ {
+ bextMeta->deleteValue(BEXTMetadata::kUMID);
+ }
+
+ // bext:version <-> RIFF:WAVE/bext.version
+ // Special case: bext.version is always written as 1
+ if (inXMP.GetProperty(kXMP_NS_BWF, "version", NULL, 0))
+ {
+ bextMeta->setValue<XMP_Uns16>(BEXTMetadata::kVersion, 1);
+ }
+ else
+ {
+ bextMeta->deleteValue(BEXTMetadata::kVersion);
+ }
+
+ // Remove BWF properties from the XMP
+ SXMPUtils::RemoveProperties(&inXMP, kXMP_NS_BWF, NULL, kXMPUtil_DoAllProperties );
+
+ changed |= bextMeta->hasChanged();
+ }
+
+
+ //
+ // Export cart
+ //
+ CartMetadata* cartData = outMetaData.get<CartMetadata>();
+
+ if ( cartData != NULL )
+ {
+ IReconcile::exportXMPToNative ( *cartData, inXMP, kCartProperties );
+
+ // Export PostTimer
+ if ( inXMP.DoesPropertyExist( kXMP_NS_AEScart, "PostTimer" ) )
+ {
+ if( inXMP.CountArrayItems( kXMP_NS_AEScart, "PostTimer" ) == CartMetadata::kPostTimerLength )
+ {
+
+ CartMetadata::StoredCartTimer timer[CartMetadata::kPostTimerLength];
+ memset ( timer, 0, sizeof(CartMetadata::StoredCartTimer)*CartMetadata::kPostTimerLength ); // Fill with zeros
+ std::string path = "";
+ std::string usageSt = "";
+ std::string valueSt = "";
+ XMP_Bool invalidArray = false;
+ XMP_Uns32 usage = 0;
+ XMP_Uns32 value = 0;
+ XMP_Int64 tmp = 0;
+ XMP_OptionBits opts;
+
+
+ for ( XMP_Uns32 i = 0; i<CartMetadata::kPostTimerLength && !invalidArray; i++ )
+ {
+ // get options of array item
+ inXMP.GetArrayItem( kXMP_NS_AEScart, "PostTimer", i+1, NULL, &opts );
+ // copose path to array item
+ SXMPUtils::ComposeArrayItemPath( kXMP_NS_AEScart, "PostTimer", i+1, &path);
+
+ if ( opts == kXMP_PropValueIsStruct &&
+ inXMP.DoesStructFieldExist ( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Usage" ) &&
+ inXMP.DoesStructFieldExist ( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Value" ) )
+ {
+ inXMP.GetStructField( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Usage", &usageSt, NULL);
+ inXMP.GetStructField( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Value", &valueSt, NULL);
+
+ if ( stringToFOURCC( usageSt,usage ) )
+ {
+ // don't add if the String is not set or not exactly 4 characters
+ timer[i].usage = usage;
+
+ // now the value
+ if ( valueSt.length() > 0 )
+ {
+ tmp = SXMPUtils::ConvertToInt64(valueSt);
+ if ( tmp > 0 && tmp <= 0xffffffff)
+ {
+ value = static_cast<XMP_Uns32>(tmp); // save because I checked that the number is positiv.
+ timer[i].value = value;
+ }
+ // else value stays 0
+ }
+ // else value stays 0
+ }
+ }
+ else
+ {
+ invalidArray = true;
+ }
+ }
+
+ if (!invalidArray) // if the structure of the array is wrong don't add anything
+ {
+ cartData->setArray<CartMetadata::StoredCartTimer> (CartMetadata::kPostTimer, timer, CartMetadata::kPostTimerLength);
+ }
+ } // Array length is wrong: don't add anything
+ }
+ else
+ {
+ cartData->deleteValue( CartMetadata::kPostTimer );
+ }
+ SXMPUtils::RemoveProperties ( &inXMP, kXMP_NS_AEScart, 0, kXMPUtil_DoAllProperties );
+ changed |= cartData->hasChanged();
+ }
+ }
+
+ //
+ // export LIST:INFO
+ //
+ INFOMetadata *infoMeta = outMetaData.get<INFOMetadata>();
+
+ if (infoMeta != NULL)
+ {
+ IReconcile::exportXMPToNative( *infoMeta, inXMP, kINFOProperties );
+
+ if ( inXMP.DoesPropertyExist( kXMP_NS_DC, "subject" ) )
+ {
+ std::string catedStr;
+ SXMPUtils::CatenateArrayItems( inXMP, kXMP_NS_DC, "subject", NULL, NULL, kXMP_NoOptions, &catedStr );
+ infoMeta->setValue<std::string>(INFOMetadata::kKeywords, catedStr);
+ }
+ else
+ {
+ infoMeta->deleteValue( INFOMetadata::kKeywords );
+ }
+
+ // Remove RIFFINFO properties from the XMP
+ SXMPUtils::RemoveProperties(&inXMP, kXMP_NS_RIFFINFO, NULL, kXMPUtil_DoAllProperties );
+
+ changed |= infoMeta->hasChanged();
+ }
+
+ // cr8r is not yet required for WAVE
+
+ ////
+ //// export cr8r
+ ////
+ //Cr8rMetadata* cr8rMeta = outMetaData.get<Cr8rMetadata>();
+
+ //if( cr8rMeta != NULL )
+ //{
+ // changed |= IReconcile::exportXMPToNative( *cr8rMeta, inXMP, kCr8rProperties );
+ //}
+
+ //
+ // remove WAV digest
+ //
+ inXMP.DeleteProperty( kXMP_NS_WAV, "NativeDigest" );
+
+ return changed;
+} // exportFromXMP
+
+
+// ************** Helper Functions ***************** //
+
+bool WAVEReconcile::encodeToHexString ( const XMP_Uns8* input, std::string& output )
+{
+ bool allZero = true; // assume for now
+ XMP_Uns32 kFixedSize = 64; // Only used for UMID Bext field, which is fixed
+ output.erase();
+
+ if ( input != 0 )
+ {
+ output.reserve ( kFixedSize * 2 );
+
+ for( XMP_Uns32 i = 0; i < kFixedSize; i++ )
+ {
+ // first, second nibble
+ XMP_Uns8 first = input[i] >> 4;
+ XMP_Uns8 second = input[i] & 0xF;
+
+ if ( allZero && (( first != 0 ) || (second != 0)))
+ allZero = false;
+
+ output.append( 1, ReconcileUtils::kHexDigits[first] );
+ output.append( 1, ReconcileUtils::kHexDigits[second] );
+ }
+ }
+ return allZero;
+} // encodeToHexString
+
+
+bool WAVEReconcile::decodeFromHexString ( std::string input, std::string &output)
+{
+ if ( (input.length() % 2) != 0 )
+ return false;
+ output.erase();
+ output.reserve ( input.length() / 2 );
+
+ for( XMP_Uns32 i = 0; i < input.length(); )
+ {
+ XMP_Uns8 upperNibble = input[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 = input[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++;
+
+ output.append ( 1, (upperNibble + lowerNibble) );
+ }
+ return true;
+} // decodeFromHexString
+
+bool WAVEReconcile::stringToFOURCC ( std::string input, XMP_Uns32 &output )
+{
+ bool result = false;
+ std::string asciiST = "";
+
+ // convert to ACSII
+ convertToASCII(input, asciiST);
+ if ( asciiST.length() == 4 )
+ {
+
+ output = GetUns32BE( asciiST.c_str() );
+ result = true;
+ }
+
+ return result;
+}
diff --git a/XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.h b/XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.h
new file mode 100644
index 0000000..44a4ef3
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.h
@@ -0,0 +1,66 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _WAVEReconcile_h_
+#define _WAVEReconcile_h_
+
+#include "XMPFiles/source/NativeMetadataSupport/IReconcile.h"
+
+class IMetadata;
+
+namespace IFF_RIFF
+{
+
+class INFOMetadata;
+class BEXTMetadata;
+
+class WAVEReconcile : public IReconcile
+{
+public:
+ ~WAVEReconcile() {};
+
+ /**
+ * @see IReconcile::importToXMP
+ * Legacy values are always imported.
+ * If the values are not UTF-8 they will be converted to UTF-8 except in ServerMode
+ */
+ XMP_Bool importToXMP( SXMPMeta& outXMP, const MetadataSet& inMetaData );
+
+ /**
+ * @see IReconcile::exportFromXMP
+ * XMP values are always exported to Legacy as UTF-8 encoded
+ */
+ XMP_Bool exportFromXMP( MetadataSet& outMetaData, SXMPMeta& inXMP );
+
+private:
+ // Encode a string of raw data bytes into a HexString (w/o spaces, i.e. "DEADBEEF").
+ // Only used for UMID Bext field, which has fixed size of 64, so 64 chars are expected in the buffer!
+ // 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 ( const XMP_Uns8* input, std::string& output );
+
+ /**
+ * 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 ( std::string input, std::string &output);
+
+ /**
+ * convert a 4 character string to XPM_Uns32 (FOURCC)
+ */
+ static bool stringToFOURCC ( std::string input, XMP_Uns32 &output );
+};
+
+}
+
+#endif
diff --git a/XMPFiles/source/FormatSupport/XDCAM_Support.cpp b/XMPFiles/source/FormatSupport/XDCAM_Support.cpp
new file mode 100644
index 0000000..b9704b9
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/XDCAM_Support.cpp
@@ -0,0 +1,480 @@
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp"
+
+// =================================================================================================
+/// \file XDCAM_Support.cpp
+///
+// =================================================================================================
+
+// =================================================================================================
+// CreateChildElement
+// ==================
+
+static XML_Node * CreateChildElement ( XML_Node * parent,
+ XMP_StringPtr localName,
+ XMP_StringPtr legacyNS,
+ int indent /* = 0 */ )
+{
+ XML_Node * wsNode;
+ XML_Node * childNode = parent->GetNamedElement ( legacyNS, localName );
+
+ if ( childNode == 0 ) {
+
+ // The indenting is a hack, assuming existing 2 spaces per level.
+
+ wsNode = new XML_Node ( parent, "", kCDataNode );
+ wsNode->value = " "; // Add 2 spaces to the existing WS before the parent's close tag.
+ parent->content.push_back ( wsNode );
+
+ childNode = new XML_Node ( parent, localName, kElemNode );
+ childNode->ns = parent->ns;
+ childNode->nsPrefixLen = parent->nsPrefixLen;
+ childNode->name.insert ( 0, parent->name, 0, parent->nsPrefixLen );
+ parent->content.push_back ( childNode );
+
+ wsNode = new XML_Node ( parent, "", kCDataNode );
+ wsNode->value = '\n';
+ for ( ; indent > 1; --indent ) wsNode->value += " "; // Indent less 1, to "outdent" the parent's close.
+ parent->content.push_back ( wsNode );
+
+ }
+
+ return childNode;
+
+} // CreateChildElement
+
+// =================================================================================================
+// GetTimeScale
+// ============
+
+static std::string GetTimeScale ( XMP_StringPtr formatFPS )
+{
+ std::string timeScale;
+
+ if ( XMP_LitNMatch ( "25p", formatFPS, 3 ) || XMP_LitNMatch ( "50i", formatFPS, 3 ) ) {
+ timeScale = "1/25";
+ } else if ( XMP_LitNMatch ( "50p", formatFPS, 3 ) ) {
+ timeScale = "1/50";
+ } else if ( XMP_LitNMatch ( "23.98p", formatFPS, 6 ) ) {
+ timeScale = "1001/24000";
+ } else if ( XMP_LitNMatch ( "29.97p", formatFPS, 6 ) || XMP_LitNMatch( "59.94i", formatFPS, 6 ) ) {
+ timeScale = "1001/30000";
+ } else if ( XMP_LitNMatch ( "59.94p", formatFPS, 6 ) ) {
+ timeScale = "1001/60000";
+ }
+
+ return timeScale;
+
+} // GetTimeScale
+
+// =================================================================================================
+// XDCAM_Support::GetMediaProLegacyMetadata
+// ========================================
+
+bool XDCAM_Support::GetMediaProLegacyMetadata ( SXMPMeta * xmpObjPtr,
+ const std::string& clipUMID,
+ const std::string& mediaProPath,
+ bool digestFound)
+{
+ // NOTE: The logic of the form "if ( digestFound || (! XMP-prop-exists) ) Set-XMP-prop" might
+ // look odd at first, especially the digestFound part. This is OK though. If there is no digest
+ // then we want to preserve existing XMP. The handlers do not call this routine if the digest is
+ // present and matched, so here digestFound really means "found and differs".
+
+ bool containsXMP = false;
+
+ Host_IO::FileRef hostRef = Host_IO::Open ( mediaProPath.c_str(), Host_IO::openReadOnly );
+ if ( hostRef == Host_IO::noFileRef ) return false; // The open failed.
+ XMPFiles_IO xmlFile ( hostRef, mediaProPath.c_str(), Host_IO::openReadOnly );
+
+ ExpatAdapter * expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces );
+ if ( expat == 0 ) return false;
+
+ #define CleanupAndExit \
+ { if ( expat != 0 ) delete expat; return containsXMP; }
+
+ XML_NodePtr mediaproRootElem = 0;
+ XML_NodePtr contentContext = 0, materialContext = 0;
+
+ XMP_Uns8 buffer [64*1024];
+ while ( true ) {
+ XMP_Int32 ioCount = xmlFile.Read ( buffer, sizeof(buffer) );
+ if ( ioCount == 0 ) break;
+ expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
+ }
+ expat->ParseBuffer ( 0, 0, true ); // End the parse.
+ xmlFile.Close();
+
+ // Get the root node of the XML tree.
+
+ XML_Node & mediaproXMLTree = expat->tree;
+ for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) {
+ if ( mediaproXMLTree.content[i]->kind == kElemNode ) {
+ mediaproRootElem = mediaproXMLTree.content[i];
+ }
+ }
+
+ if ( mediaproRootElem == 0 ) CleanupAndExit
+ XMP_StringPtr rlName = mediaproRootElem->name.c_str() + mediaproRootElem->nsPrefixLen;
+ if ( ! XMP_LitMatch ( rlName, "MediaProfile" ) ) CleanupAndExit
+
+ // MediaProfile, Contents
+
+ XMP_StringPtr ns = mediaproRootElem->ns.c_str();
+ contentContext = mediaproRootElem->GetNamedElement ( ns, "Contents" );
+
+ if ( contentContext != 0 ) {
+
+ size_t numMaterialElems = contentContext->CountNamedElements ( ns, "Material" );
+
+ // Iterate over the Material tags, looking for one that has a matching umid.
+ for ( size_t i = 0; i < numMaterialElems; ++i ) {
+
+ XML_NodePtr materialElement = contentContext->GetNamedElement ( ns, "Material", i );
+ XMP_Assert ( materialElement != 0 );
+
+ XMP_StringPtr umid = materialElement->GetAttrValue ( "umid" );
+
+ // Found one that matches the input umid, gather what metadata we can and break.
+ if ( (umid != 0) && (umid == clipUMID) ) {
+
+ // title
+ XMP_StringPtr title = materialElement->GetAttrValue ( "title" );
+ if ( title != 0 ) {
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DC, "title" )) ) {
+ xmpObjPtr->SetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", title, kXMP_DeleteExisting );
+ containsXMP = true;
+ }
+ }
+
+ break;
+
+ }
+ }
+
+ }
+
+ CleanupAndExit
+ #undef CleanupAndExit
+
+} // XDCAM_Support::GetMediaProLegacyMetadata
+
+// =================================================================================================
+// XDCAM_Support::GetLegacyMetadata
+// ================================
+
+bool XDCAM_Support::GetLegacyMetadata ( SXMPMeta * xmpObjPtr,
+ XML_NodePtr rootElem,
+ XMP_StringPtr legacyNS,
+ bool digestFound,
+ std::string& umid )
+{
+ // NOTE: The logic of the form "if ( digestFound || (! XMP-prop-exists) ) Set-XMP-prop" might
+ // look odd at first, especially the digestFound part. This is OK though. If there is no digest
+ // then we want to preserve existing XMP. The handlers do not call this routine if the digest is
+ // present and matched, so here digestFound really means "found and differs".
+
+ bool containsXMP = false;
+
+ XML_NodePtr legacyContext = 0, legacyProp = 0;
+ XMP_StringPtr formatFPS;
+
+ // UMID
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DC, "identifier" )) ) {
+ legacyProp = rootElem->GetNamedElement ( legacyNS, "TargetMaterial" );
+ if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) {
+ XMP_StringPtr legacyValue = legacyProp->GetAttrValue ( "umidRef" );
+ if ( legacyValue != 0 ) {
+ umid = legacyValue;
+ xmpObjPtr->SetProperty ( kXMP_NS_DC, "identifier", legacyValue, kXMP_DeleteExisting );
+ containsXMP = true;
+ }
+ }
+ }
+
+ // Title
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DC, "title" )) ) {
+ legacyProp = rootElem->GetNamedElement ( legacyNS, "Title" );
+ if ( legacyProp != 0 ) {
+ XMP_StringPtr legacyValue = legacyProp->GetAttrValue ( "usAscii" );
+ if ( legacyValue != 0 ) {
+ xmpObjPtr->SetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", legacyValue, kXMP_DeleteExisting );
+ containsXMP = true;
+ }
+ }
+ }
+
+ // Creation date
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_XMP, "CreateDate" )) ) {
+ legacyProp = rootElem->GetNamedElement ( legacyNS, "CreationDate" );
+ if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) {
+ XMP_StringPtr legacyValue = legacyProp->GetAttrValue ( "value" );
+ if ( legacyValue != 0 ) {
+ xmpObjPtr->SetProperty ( kXMP_NS_XMP, "CreateDate", legacyValue, kXMP_DeleteExisting );
+ containsXMP = true;
+ }
+ }
+ }
+
+ // Modify Date
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_XMP, "ModifyDate" )) ) {
+ legacyProp = rootElem->GetNamedElement ( legacyNS, "LastUpdate" );
+ if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) {
+ XMP_StringPtr legacyValue = legacyProp->GetAttrValue ( "value" );
+ if ( legacyValue != 0 ) {
+ xmpObjPtr->SetProperty ( kXMP_NS_XMP, "ModifyDate", legacyValue, kXMP_DeleteExisting );
+ containsXMP = true;
+ }
+ }
+ }
+
+ // Metadata Modify Date
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_XMP, "MetadataDate" )) ) {
+ legacyProp = rootElem->GetNamedElement ( legacyNS, "lastUpdate" );
+ if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) {
+ XMP_StringPtr legacyValue = legacyProp->GetAttrValue ( "value" );
+ if ( legacyValue != 0 ) {
+ xmpObjPtr->SetProperty ( kXMP_NS_XMP, "MetadataDate", legacyValue, kXMP_DeleteExisting );
+ containsXMP = true;
+ }
+ }
+ }
+
+ // 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 ) {
+
+ // frame size
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DM, "videoFrameSize" )) ) {
+ legacyProp = legacyContext->GetNamedElement ( legacyNS, "VideoLayout" );
+ if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) {
+
+ XMP_StringPtr widthValue = legacyProp->GetAttrValue ( "pixel" );
+ XMP_StringPtr heightValue = legacyProp->GetAttrValue ( "numOfVerticalLine" );
+
+ if ( (widthValue != 0) && (heightValue != 0) ) {
+
+ xmpObjPtr->DeleteProperty ( kXMP_NS_DM, "videoFrameSize" );
+ xmpObjPtr->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", widthValue );
+ xmpObjPtr->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", heightValue );
+ xmpObjPtr->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", "pixels" );
+
+ containsXMP = true;
+
+ }
+
+ }
+ }
+
+ // Aspect ratio
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DM, "videoPixelAspectRatio" )) ) {
+ legacyProp = legacyContext->GetNamedElement ( legacyNS, "VideoLayout" );
+ if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) {
+ XMP_StringPtr aspectRatio = legacyProp->GetAttrValue ( "aspectRatio" );
+ if ( aspectRatio != 0 ) {
+ xmpObjPtr->SetProperty ( kXMP_NS_DM, "videoPixelAspectRatio", aspectRatio, kXMP_DeleteExisting );
+ containsXMP = true;
+ }
+ }
+ }
+
+ // Frame rate
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DM, "videoFrameRate" )) ) {
+ legacyProp = legacyContext->GetNamedElement ( legacyNS, "VideoFrame" );
+ if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) {
+ formatFPS = legacyProp->GetAttrValue ( "formatFps" );
+ if ( formatFPS != 0 ) {
+ xmpObjPtr->SetProperty ( kXMP_NS_DM, "videoFrameRate", formatFPS, kXMP_DeleteExisting );
+ containsXMP = true;
+ }
+ }
+ }
+
+ // Video codec
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DM, "videoCompressor" )) ) {
+ legacyProp = legacyContext->GetNamedElement ( legacyNS, "VideoFrame" );
+ if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) {
+ XMP_StringPtr prop = legacyProp->GetAttrValue ( "videoCodec" );
+ if ( prop != 0 ) {
+ xmpObjPtr->SetProperty ( kXMP_NS_DM, "videoCompressor", prop, kXMP_DeleteExisting );
+ containsXMP = true;
+ }
+ }
+ }
+
+ } // VideoFormat
+
+ legacyContext = rootElem->GetNamedElement ( legacyNS, "AudioFormat" );
+
+ if ( legacyContext != 0 ) {
+
+ // Audio codec
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DM, "audioCompressor" )) ) {
+ legacyProp = legacyContext->GetNamedElement ( legacyNS, "AudioRecPort" );
+ if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) {
+ XMP_StringPtr prop = legacyProp->GetAttrValue ( "audioCodec" );
+ if ( prop != 0 ) {
+ xmpObjPtr->SetProperty ( kXMP_NS_DM, "audioCompressor", prop, kXMP_DeleteExisting );
+ containsXMP = true;
+ }
+ }
+ }
+
+ } // AudioFormat
+
+ // Duration
+ if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) {
+
+ std::string durationFrames;
+ legacyProp = rootElem->GetNamedElement ( legacyNS, "Duration" );
+ if ( legacyProp != 0 ) {
+ XMP_StringPtr durationValue = legacyProp->GetAttrValue ( "value" );
+ if ( durationValue != 0 ) durationFrames = durationValue;
+ }
+
+ std::string timeScale = GetTimeScale ( formatFPS );
+
+ if ( (! timeScale.empty()) && (! durationFrames.empty()) ) {
+ xmpObjPtr->DeleteProperty ( kXMP_NS_DM, "duration" );
+ xmpObjPtr->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", durationFrames );
+ xmpObjPtr->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", timeScale );
+ containsXMP = true;
+ }
+
+ }
+
+ 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;
+
+} // XDCAM_Support::GetLegacyMetadata
+
+// =================================================================================================
+// XDCAM_Support::SetLegacyMetadata
+// ================================
+
+bool XDCAM_Support::SetLegacyMetadata ( XML_Node * clipMetadata,
+ SXMPMeta * xmpObj,
+ XMP_StringPtr legacyNS )
+{
+ bool updateLegacyXML = false;
+ bool xmpFound = false;
+ std::string xmpValue;
+ XML_Node * xmlNode = 0;
+
+ xmpFound = xmpObj->GetProperty ( kXMP_NS_DC, "title", &xmpValue, 0 );
+
+ if ( xmpFound ) {
+
+ xmlNode = CreateChildElement ( clipMetadata, "Title", legacyNS, 3 );
+ if ( xmpValue != xmlNode->GetLeafContentValue() ) {
+ xmlNode->SetLeafContentValue ( xmpValue.c_str() );
+ updateLegacyXML = true;
+ }
+
+ }
+
+ xmpFound = xmpObj->GetArrayItem ( kXMP_NS_DC, "creator", 1, &xmpValue, 0 );
+
+ if ( xmpFound ) {
+ xmlNode = CreateChildElement ( clipMetadata, "Creator", legacyNS, 3 );
+ XMP_StringPtr creatorName = xmlNode->GetAttrValue ( "name" );
+ if ( creatorName == 0 ) creatorName = "";
+ if ( xmpValue != creatorName ) {
+ xmlNode->SetAttrValue ( "name", xmpValue.c_str() );
+ updateLegacyXML = true;
+ }
+ }
+
+ xmpFound = xmpObj->GetProperty ( kXMP_NS_DC, "description", &xmpValue, 0 );
+
+ if ( xmpFound ) {
+ xmlNode = CreateChildElement ( clipMetadata, "Description", legacyNS, 3 );
+ if ( xmpValue != xmlNode->GetLeafContentValue() ) {
+ // description in non real time metadata is limited to 2047 bytes
+ if ( xmpValue.size() > 2047 ) xmpValue.resize ( 2047 );
+ xmlNode->SetLeafContentValue ( xmpValue.c_str() );
+ updateLegacyXML = true;
+ }
+ }
+
+ return updateLegacyXML;
+
+} // XDCAM_Support::SetLegacyMetadata
+
+// =================================================================================================
diff --git a/XMPFiles/source/FormatSupport/XDCAM_Support.hpp b/XMPFiles/source/FormatSupport/XDCAM_Support.hpp
new file mode 100644
index 0000000..cd209e9
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/XDCAM_Support.hpp
@@ -0,0 +1,50 @@
+#ifndef __XDCAM_Support_hpp__
+#define __XDCAM_Support_hpp__ 1
+
+// =================================================================================================
+// 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 "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/ExpatAdapter.hpp"
+
+// =================================================================================================
+/// \file XDCAM_Support.hpp
+/// \brief XMPFiles support for XDCAM streams.
+///
+// =================================================================================================
+
+namespace XDCAM_Support
+{
+
+ // Read XDCAM XML metadata from MEDIAPRO.XML and translate to appropriate XMP.
+ bool GetMediaProLegacyMetadata ( SXMPMeta * xmpObjPtr,
+ const std::string& umid,
+ const std::string& mediaProPath,
+ bool digestFound);
+
+ // Read XDCAM XML metadata and translate to appropriate XMP.
+ bool GetLegacyMetadata ( SXMPMeta * xmpObjPtr,
+ XML_NodePtr rootElem,
+ XMP_StringPtr legacyNS,
+ bool digestFound,
+ std::string& umid );
+
+ // Write XMP metadata back to XDCAM XML.
+ bool SetLegacyMetadata ( XML_Node * clipMetadata,
+ SXMPMeta * xmpObj,
+ XMP_StringPtr legacyNS );
+
+
+} // namespace XDCAM_Support
+
+// =================================================================================================
+
+#endif // __XDCAM_Support_hpp__
diff --git a/XMPFiles/source/FormatSupport/XMPScanner.cpp b/XMPFiles/source/FormatSupport/XMPScanner.cpp
new file mode 100644
index 0000000..6d8fe82
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/XMPScanner.cpp
@@ -0,0 +1,1450 @@
+// =================================================================================================
+// Copyright 2004 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.
+//
+// Adobe patent application tracking #P435, entitled 'Unique markers to simplify embedding data of
+// one format in a file with a different format', inventors: Sean Parent, Greg Gilley.
+// =================================================================================================
+
+#if WIN32
+ #pragma warning ( disable : 4127 ) // conditional expression is constant
+ #pragma warning ( disable : 4510 ) // default constructor could not be generated
+ #pragma warning ( disable : 4610 ) // user defined constructor required
+ #pragma warning ( disable : 4786 ) // debugger can't handle long symbol names
+#endif
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+
+#if TestRunnerBuild
+ #define EnablePacketScanning 1
+#else
+ #include "XMPFiles/source/XMPFiles_Impl.hpp"
+#endif
+
+#include "XMPFiles/source/FormatSupport/XMPScanner.hpp"
+
+#include <cassert>
+#include <string>
+#include <cstdlib>
+
+#if DEBUG
+ #include <iostream>
+ #include <iomanip>
+ #include <fstream>
+#endif
+
+#ifndef UseStringPushBack // VC++ 6.x does not provide push_back for strings!
+ #define UseStringPushBack 0
+#endif
+
+using namespace std;
+
+#if EnablePacketScanning
+
+// =================================================================================================
+// =================================================================================================
+// class PacketMachine
+// ===================
+//
+// This is the packet recognizer state machine. The top of the machine is FindNextPacket, this
+// calls the specific state components and handles transitions. The states are described by an
+// array of RecognizerInfo records, indexed by the RecognizerKind enumeration. Each RecognizerInfo
+// record has a function that does that state's work, the success and failure transition states,
+// and a string literal that is passed to the state function. The literal lets a common MatchChar
+// or MatchString function be used in several places.
+//
+// The state functions are responsible for consuming input to recognize their particular state.
+// This includes intervening nulls for 16 and 32 bit character forms. For the simplicity, things
+// are treated as essentially little endian and the nulls are not actually checked. The opening
+// '<' is found with a byte-by-byte search, then the number of bytes per character is determined
+// by counting the following nulls. From then on, consuming a character means incrementing the
+// buffer pointer by the number of bytes per character. Thus the buffer pointer only points to
+// the "real" bytes. This also means that the pointer can go off the end of the buffer by a
+// variable amount. The amount of overrun is saved so that the pointer can be positioned at the
+// right byte to start the next buffer.
+//
+// The state functions return a TriState value, eTriYes means the pattern was found, eTriNo means
+// the pattern was definitely not found, eTriMaybe means that the end of the buffer was reached
+// while working through the pattern.
+//
+// When eTriYes is returned, the fBufferPtr data member is left pointing to the "real" byte
+// following the last actual byte. Which might not be addressable memory! This also means that
+// a state function can be entered with nothing available in the buffer. When eTriNo is returned,
+// the fBufferPtr data member is left pointing to the byte that caused the failure. The state
+// machine starts over from the failure byte.
+//
+// The state functions must preserve their internal micro-state before returning eTriMaybe, and
+// resume processing when called with the next buffer. The fPosition data member is used to denote
+// how many actual characters have been consumed. The fNullCount data member is used to denote how
+// many nulls are left before the next actual character.
+
+// =================================================================================================
+// PacketMachine
+// =============
+
+XMPScanner::PacketMachine::PacketMachine ( XMP_Int64 bufferOffset, const void * bufferOrigin, XMP_Int64 bufferLength ) :
+
+ // Public members
+ fPacketStart ( 0 ),
+ fPacketLength ( 0 ),
+ fBytesAttr ( -1 ),
+ fCharForm ( eChar8Bit ),
+ fAccess ( ' ' ),
+ fBogusPacket ( false ),
+
+ // Private members
+ fBufferOffset ( bufferOffset ),
+ fBufferOrigin ( (const char *) bufferOrigin ),
+ fBufferPtr ( fBufferOrigin ),
+ fBufferLimit ( fBufferOrigin + bufferLength ),
+ fRecognizer ( eLeadInRecognizer ),
+ fPosition ( 0 ),
+ fBytesPerChar ( 1 ),
+ fBufferOverrun ( 0 ),
+ fQuoteChar ( ' ' )
+
+{
+ /*
+ REVIEW NOTES : Should the buffer stuff be in a class?
+ */
+
+ assert ( bufferOrigin != NULL );
+ assert ( bufferLength != 0 );
+
+} // PacketMachine
+
+// =================================================================================================
+// ~PacketMachine
+// ==============
+
+XMPScanner::PacketMachine::~PacketMachine ()
+{
+
+ // An empty placeholder.
+
+} // ~PacketMachine
+
+// =================================================================================================
+// AssociateBuffer
+// ===============
+
+void
+XMPScanner::PacketMachine::AssociateBuffer ( XMP_Int64 bufferOffset, const void * bufferOrigin, XMP_Int64 bufferLength )
+{
+
+ fBufferOffset = bufferOffset;
+ fBufferOrigin = (const char *) bufferOrigin;
+ fBufferPtr = fBufferOrigin + fBufferOverrun;
+ fBufferLimit = fBufferOrigin + bufferLength;
+
+} // AssociateBuffer
+
+// =================================================================================================
+// ResetMachine
+// ============
+
+void
+XMPScanner::PacketMachine::ResetMachine ()
+{
+
+ fRecognizer = eLeadInRecognizer;
+ fPosition = 0;
+ fBufferOverrun = 0;
+ fCharForm = eChar8Bit;
+ fBytesPerChar = 1;
+ fAccess = ' ';
+ fBytesAttr = -1;
+ fBogusPacket = false;
+
+ fAttrName.erase ( fAttrName.begin(), fAttrName.end() );
+ fAttrValue.erase ( fAttrValue.begin(), fAttrValue.end() );
+ fEncodingAttr.erase ( fEncodingAttr.begin(), fEncodingAttr.end() );
+
+} // ResetMachine
+
+// =================================================================================================
+// FindLessThan
+// ============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::FindLessThan ( PacketMachine * ths, const char * which )
+{
+
+ if ( *which == 'H' ) {
+
+ // --------------------------------------------------------------------------------
+ // We're looking for the '<' of the header. If we fail there is no packet in this
+ // part of the input, so return eTriNo.
+
+ ths->fCharForm = eChar8Bit; // We might have just failed from a bogus 16 or 32 bit case.
+ ths->fBytesPerChar = 1;
+
+ while ( ths->fBufferPtr < ths->fBufferLimit ) { // Don't skip nulls for the header's '<'!
+ if ( *ths->fBufferPtr == '<' ) break;
+ ths->fBufferPtr++;
+ }
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriNo;
+ ths->fBufferPtr++;
+ return eTriYes;
+
+ } else {
+
+ // --------------------------------------------------------------------------------
+ // We're looking for the '<' of the trailer. We're already inside the packet body,
+ // looking for the trailer. So here if we fail we must return eTriMaybe so that we
+ // keep looking for the trailer in the next buffer.
+
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ while ( ths->fBufferPtr < ths->fBufferLimit ) {
+ if ( *ths->fBufferPtr == '<' ) break;
+ ths->fBufferPtr += bytesPerChar;
+ }
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+ ths->fBufferPtr += bytesPerChar;
+ return eTriYes;
+
+ }
+
+} // FindLessThan
+
+// =================================================================================================
+// MatchString
+// ===========
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::MatchString ( PacketMachine * ths, const char * literal )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+ const char * litPtr = literal + ths->fPosition;
+ const XMP_Int32 charsToGo = (XMP_Int32) strlen ( literal ) - ths->fPosition;
+ int charsDone = 0;
+
+ while ( (charsDone < charsToGo) && (ths->fBufferPtr < ths->fBufferLimit) ) {
+ if ( *litPtr != *ths->fBufferPtr ) return eTriNo;
+ charsDone++;
+ litPtr++;
+ ths->fBufferPtr += bytesPerChar;
+ }
+
+ if ( charsDone == charsToGo ) return eTriYes;
+ ths->fPosition += charsDone;
+ return eTriMaybe;
+
+} // MatchString
+
+// =================================================================================================
+// MatchChar
+// =========
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::MatchChar ( PacketMachine * ths, const char * literal )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const char currChar = *ths->fBufferPtr;
+ if ( currChar != *literal ) return eTriNo;
+ ths->fBufferPtr += bytesPerChar;
+ return eTriYes;
+
+} // MatchChar
+
+// =================================================================================================
+// MatchOpenQuote
+// ==============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::MatchOpenQuote ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const char currChar = *ths->fBufferPtr;
+ if ( (currChar != '\'') && (currChar != '"') ) return eTriNo;
+ ths->fQuoteChar = currChar;
+ ths->fBufferPtr += bytesPerChar;
+ return eTriYes;
+
+} // MatchOpenQuote
+
+// =================================================================================================
+// MatchCloseQuote
+// ===============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::MatchCloseQuote ( PacketMachine * ths, const char * /* unused */ )
+{
+
+ return MatchChar ( ths, &ths->fQuoteChar );
+
+} // MatchCloseQuote
+
+// =================================================================================================
+// CaptureAttrName
+// ===============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::CaptureAttrName ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+ char currChar;
+
+ if ( ths->fPosition == 0 ) { // Get the first character in the name.
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ currChar = *ths->fBufferPtr;
+ if ( ths->fAttrName.size() == 0 ) {
+ if ( ! ( ( ('a' <= currChar) && (currChar <= 'z') ) ||
+ ( ('A' <= currChar) && (currChar <= 'Z') ) ||
+ (currChar == '_') || (currChar == ':') ) ) {
+ return eTriNo;
+ }
+ }
+
+ ths->fAttrName.erase ( ths->fAttrName.begin(), ths->fAttrName.end() );
+ #if UseStringPushBack
+ ths->fAttrName.push_back ( currChar );
+ #else
+ ths->fAttrName.insert ( ths->fAttrName.end(), currChar );
+ #endif
+ ths->fBufferPtr += bytesPerChar;
+
+ }
+
+ while ( ths->fBufferPtr < ths->fBufferLimit ) { // Get the remainder of the name.
+
+ currChar = *ths->fBufferPtr;
+ if ( ! ( ( ('a' <= currChar) && (currChar <= 'z') ) ||
+ ( ('A' <= currChar) && (currChar <= 'Z') ) ||
+ ( ('0' <= currChar) && (currChar <= '9') ) ||
+ (currChar == '-') || (currChar == '.') || (currChar == '_') || (currChar == ':') ) ) {
+ break;
+ }
+
+ #if UseStringPushBack
+ ths->fAttrName.push_back ( currChar );
+ #else
+ ths->fAttrName.insert ( ths->fAttrName.end(), currChar );
+ #endif
+ ths->fBufferPtr += bytesPerChar;
+
+ }
+
+ if ( ths->fBufferPtr < ths->fBufferLimit ) return eTriYes;
+ ths->fPosition = (long) ths->fAttrName.size(); // The name might span into the next buffer.
+ return eTriMaybe;
+
+} // CaptureAttrName
+
+// =================================================================================================
+// CaptureAttrValue
+// ================
+//
+// Recognize the equal sign and the quoted string value, capture the value along the way.
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::CaptureAttrValue ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+ char currChar = 0;
+ TriState result = eTriMaybe;
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ switch ( ths->fPosition ) {
+
+ case 0 : // The name should haved ended at the '=', nulls already skipped.
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+ if ( *ths->fBufferPtr != '=' ) return eTriNo;
+ ths->fBufferPtr += bytesPerChar;
+ ths->fPosition = 1;
+ // fall through OK because MatchOpenQuote will check the buffer limit and nulls ...
+
+ case 1 : // Look for the open quote.
+
+ result = MatchOpenQuote ( ths, NULL );
+ if ( result != eTriYes ) return result;
+ ths->fPosition = 2;
+ // fall through OK because the buffer limit and nulls are checked below ...
+
+ default : // Look for the close quote, capturing the value along the way.
+
+ assert ( ths->fPosition == 2 );
+
+ const char quoteChar = ths->fQuoteChar;
+
+ while ( ths->fBufferPtr < ths->fBufferLimit ) {
+ currChar = *ths->fBufferPtr;
+ if ( currChar == quoteChar ) break;
+ #if UseStringPushBack
+ ths->fAttrValue.push_back ( currChar );
+ #else
+ ths->fAttrValue.insert ( ths->fAttrValue.end(), currChar );
+ #endif
+ ths->fBufferPtr += bytesPerChar;
+ }
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+ assert ( currChar == quoteChar );
+ ths->fBufferPtr += bytesPerChar; // Advance past the closing quote.
+ return eTriYes;
+
+ }
+
+} // CaptureAttrValue
+
+// =================================================================================================
+// RecordStart
+// ===========
+//
+// Note that this routine looks at bytes, not logical characters. It has to figure out how many
+// bytes per character there are so that the other recognizers can skip intervening nulls.
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::RecordStart ( PacketMachine * ths, const char * /* unused */ )
+{
+
+ while ( true ) {
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const char currByte = *ths->fBufferPtr;
+
+ switch ( ths->fPosition ) {
+
+ case 0 : // Record the length.
+ assert ( ths->fCharForm == eChar8Bit );
+ assert ( ths->fBytesPerChar == 1 );
+ ths->fPacketStart = ths->fBufferOffset + ((ths->fBufferPtr - 1) - ths->fBufferOrigin);
+ ths->fPacketLength = 0;
+ ths->fPosition = 1;
+ // ! OK to fall through here, we didn't consume a byte in this step.
+
+ case 1 : // Look for the first null byte.
+ if ( currByte != 0 ) return eTriYes; // No nulls found.
+ ths->fCharForm = eChar16BitBig; // Assume 16 bit big endian for now.
+ ths->fBytesPerChar = 2;
+ ths->fBufferPtr++;
+ ths->fPosition = 2;
+ break; // ! Don't fall through, have to check for the end of the buffer between each byte.
+
+ case 2 : // One null was found, look for a second.
+ if ( currByte != 0 ) return eTriYes; // Just one null found.
+ ths->fBufferPtr++;
+ ths->fPosition = 3;
+ break;
+
+ case 3 : // Two nulls were found, look for a third.
+ if ( currByte != 0 ) return eTriNo; // Just two nulls is not valid.
+ ths->fCharForm = eChar32BitBig; // Assume 32 bit big endian for now.
+ ths->fBytesPerChar = 4;
+ ths->fBufferPtr++;
+ return eTriYes;
+ break;
+
+ }
+
+ }
+
+} // RecordStart
+
+// =================================================================================================
+// RecognizeBOM
+// ============
+//
+// Recognizing the byte order marker is a surprisingly messy thing to do. It can't be done by the
+// normal string matcher, there are no intervening nulls. There are 4 transitions after the opening
+// quote, the closing quote or one of the three encodings. For the actual BOM there are then 1 or 2
+// following bytes that depend on which of the encodings we're in. Not to mention that the buffer
+// might end at any point.
+//
+// The intervening null count done earlier determined 8, 16, or 32 bits per character, but not the
+// big or little endian nature for the 16/32 bit cases. The BOM must be present for the 16 and 32
+// bit cases in order to determine the endian mode. There are six possible byte sequences for the
+// quoted BOM string, ignoring the differences for quoting with ''' versus '"'.
+//
+// Keep in mind that for the 16 and 32 bit cases there will be nulls for the quote. In the table
+// below the symbol <quote> means just the one byte containing the ''' or '"'. The nulls for the
+// quote character are explicitly shown.
+//
+// <quote> <quote> - 1: No BOM, this must be an 8 bit case.
+// <quote> \xEF \xBB \xBF <quote> - 1.12-13: The 8 bit form.
+//
+// <quote> \xFE \xFF \x00 <quote> - 1.22-23: The 16 bit, big endian form
+// <quote> \x00 \xFF \xFE <quote> - 1.32-33: The 16 bit, little endian form.
+//
+// <quote> \x00 \x00 \xFE \xFF \x00 \x00 \x00 <quote> - 1.32.43-45.56-57: The 32 bit, big endian form.
+// <quote> \x00 \x00 \x00 \xFF \xFE \x00 \x00 <quote> - 1.32.43.54-57: The 32 bit, little endian form.
+
+enum {
+ eBOM_8_1 = 0xEF,
+ eBOM_8_2 = 0xBB,
+ eBOM_8_3 = 0xBF,
+ eBOM_Big_1 = 0xFE,
+ eBOM_Big_2 = 0xFF,
+ eBOM_Little_1 = eBOM_Big_2,
+ eBOM_Little_2 = eBOM_Big_1
+};
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::RecognizeBOM ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ while ( true ) { // Handle one character at a time, the micro-state (fPosition) changes for each.
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const unsigned char currChar = *ths->fBufferPtr; // ! The BOM bytes look like integers bigger than 127.
+
+ switch ( ths->fPosition ) {
+
+ case 0 : // Look for the opening quote.
+ if ( (currChar != '\'') && (currChar != '"') ) return eTriNo;
+ ths->fQuoteChar = currChar;
+ ths->fBufferPtr++;
+ ths->fPosition = 1;
+ break; // ! Don't fall through, have to check for the end of the buffer between each byte.
+
+ case 1 : // Look at the byte immediately following the opening quote.
+ if ( currChar == ths->fQuoteChar ) { // Closing quote, no BOM character, must be 8 bit.
+ if ( ths->fCharForm != eChar8Bit ) return eTriNo;
+ ths->fBufferPtr += bytesPerChar; // Skip the nulls after the closing quote.
+ return eTriYes;
+ } else if ( currChar == eBOM_8_1 ) { // Start of the 8 bit form.
+ if ( ths->fCharForm != eChar8Bit ) return eTriNo;
+ ths->fBufferPtr++;
+ ths->fPosition = 12;
+ } else if ( currChar == eBOM_Big_1 ) { // Start of the 16 bit big endian form.
+ if ( ths->fCharForm != eChar16BitBig ) return eTriNo;
+ ths->fBufferPtr++;
+ ths->fPosition = 22;
+ } else if ( currChar == 0 ) { // Start of the 16 bit little endian or either 32 bit form.
+ if ( ths->fCharForm == eChar8Bit ) return eTriNo;
+ ths->fBufferPtr++;
+ ths->fPosition = 32;
+ } else {
+ return eTriNo;
+ }
+ break;
+
+ case 12 : // Look for the second byte of the 8 bit form.
+ if ( currChar != eBOM_8_2 ) return eTriNo;
+ ths->fPosition = 13;
+ ths->fBufferPtr++;
+ break;
+
+ case 13 : // Look for the third byte of the 8 bit form.
+ if ( currChar != eBOM_8_3 ) return eTriNo;
+ ths->fPosition = 99;
+ ths->fBufferPtr++;
+ break;
+
+ case 22 : // Look for the second byte of the 16 bit big endian form.
+ if ( currChar != eBOM_Big_2 ) return eTriNo;
+ ths->fPosition = 23;
+ ths->fBufferPtr++;
+ break;
+
+ case 23 : // Look for the null before the closing quote of the 16 bit big endian form.
+ if ( currChar != 0 ) return eTriNo;
+ ths->fBufferPtr++;
+ ths->fPosition = 99;
+ break;
+
+ case 32 : // Look at the second byte of the 16 bit little endian or either 32 bit form.
+ if ( currChar == eBOM_Little_1 ) {
+ ths->fPosition = 33;
+ } else if ( currChar == 0 ) {
+ ths->fPosition = 43;
+ } else {
+ return eTriNo;
+ }
+ ths->fBufferPtr++;
+ break;
+
+ case 33 : // Look for the third byte of the 16 bit little endian form.
+ if ( ths->fCharForm != eChar16BitBig ) return eTriNo; // Null count before assumed big endian.
+ if ( currChar != eBOM_Little_2 ) return eTriNo;
+ ths->fCharForm = eChar16BitLittle;
+ ths->fPosition = 99;
+ ths->fBufferPtr++;
+ break;
+
+ case 43 : // Look at the third byte of either 32 bit form.
+ if ( ths->fCharForm != eChar32BitBig ) return eTriNo; // Null count before assumed big endian.
+ if ( currChar == eBOM_Big_1 ) {
+ ths->fPosition = 44;
+ } else if ( currChar == 0 ) {
+ ths->fPosition = 54;
+ } else {
+ return eTriNo;
+ }
+ ths->fBufferPtr++;
+ break;
+
+ case 44 : // Look for the fourth byte of the 32 bit big endian form.
+ if ( currChar != eBOM_Big_2 ) return eTriNo;
+ ths->fPosition = 45;
+ ths->fBufferPtr++;
+ break;
+
+ case 45 : // Look for the first null before the closing quote of the 32 bit big endian form.
+ if ( currChar != 0 ) return eTriNo;
+ ths->fPosition = 56;
+ ths->fBufferPtr++;
+ break;
+
+ case 54 : // Look for the fourth byte of the 32 bit little endian form.
+ ths->fCharForm = eChar32BitLittle;
+ if ( currChar != eBOM_Little_1 ) return eTriNo;
+ ths->fPosition = 55;
+ ths->fBufferPtr++;
+ break;
+
+ case 55 : // Look for the fifth byte of the 32 bit little endian form.
+ if ( currChar != eBOM_Little_2 ) return eTriNo;
+ ths->fPosition = 56;
+ ths->fBufferPtr++;
+ break;
+
+ case 56 : // Look for the next to last null before the closing quote of the 32 bit forms.
+ if ( currChar != 0 ) return eTriNo;
+ ths->fPosition = 57;
+ ths->fBufferPtr++;
+ break;
+
+ case 57 : // Look for the last null before the closing quote of the 32 bit forms.
+ if ( currChar != 0 ) return eTriNo;
+ ths->fPosition = 99;
+ ths->fBufferPtr++;
+ break;
+
+ default : // Look for the closing quote.
+ assert ( ths->fPosition == 99 );
+ if ( currChar != ths->fQuoteChar ) return eTriNo;
+ ths->fBufferPtr += bytesPerChar; // Skip the nulls after the closing quote.
+ return eTriYes;
+ break;
+
+ }
+
+ }
+
+} // RecognizeBOM
+
+// =================================================================================================
+// RecordHeadAttr
+// ==============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::RecordHeadAttr ( PacketMachine * ths, const char * /* unused */ )
+{
+
+ if ( ths->fAttrName == "encoding" ) {
+
+ assert ( ths->fEncodingAttr.empty() );
+ ths->fEncodingAttr = ths->fAttrValue;
+
+ } else if ( ths->fAttrName == "bytes" ) {
+
+ long value = 0;
+ int count = (int) ths->fAttrValue.size();
+ int i;
+
+ assert ( ths->fBytesAttr == -1 );
+
+ if ( count > 0 ) { // Allow bytes='' to be the same as no bytes attribute.
+
+ for ( i = 0; i < count; i++ ) {
+ const char currChar = ths->fAttrValue[i];
+ if ( ('0' <= currChar) && (currChar <= '9') ) {
+ value = (value * 10) + (currChar - '0');
+ } else {
+ ths->fBogusPacket = true;
+ value = -1;
+ break;
+ }
+ }
+ ths->fBytesAttr = value;
+
+ if ( CharFormIs16Bit ( ths->fCharForm ) ) {
+ if ( (ths->fBytesAttr & 1) != 0 ) ths->fBogusPacket = true;
+ } else if ( CharFormIs32Bit ( ths->fCharForm ) ) {
+ if ( (ths->fBytesAttr & 3) != 0 ) ths->fBogusPacket = true;
+ }
+
+ }
+
+ }
+
+ ths->fAttrName.erase ( ths->fAttrName.begin(), ths->fAttrName.end() );
+ ths->fAttrValue.erase ( ths->fAttrValue.begin(), ths->fAttrValue.end() );
+
+ return eTriYes;
+
+} // RecordHeadAttr
+
+// =================================================================================================
+// CaptureAccess
+// =============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::CaptureAccess ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ while ( true ) {
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const char currChar = *ths->fBufferPtr;
+
+ switch ( ths->fPosition ) {
+
+ case 0 : // Look for the opening quote.
+ if ( (currChar != '\'') && (currChar != '"') ) return eTriNo;
+ ths->fQuoteChar = currChar;
+ ths->fBufferPtr += bytesPerChar;
+ ths->fPosition = 1;
+ break; // ! Don't fall through, have to check for the end of the buffer between each byte.
+
+ case 1 : // Look for the 'r' or 'w'.
+ if ( (currChar != 'r') && (currChar != 'w') ) return eTriNo;
+ ths->fAccess = currChar;
+ ths->fBufferPtr += bytesPerChar;
+ ths->fPosition = 2;
+ break;
+
+ default : // Look for the closing quote.
+ assert ( ths->fPosition == 2 );
+ if ( currChar != ths->fQuoteChar ) return eTriNo;
+ ths->fBufferPtr += bytesPerChar;
+ return eTriYes;
+ break;
+
+ }
+
+ }
+
+} // CaptureAccess
+
+// =================================================================================================
+// RecordTailAttr
+// ==============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::RecordTailAttr ( PacketMachine * ths, const char * /* unused */ )
+{
+
+ // There are no known "general" attributes for the packet trailer.
+
+ ths->fAttrName.erase ( ths->fAttrName.begin(), ths->fAttrName.end() );
+ ths->fAttrValue.erase ( ths->fAttrValue.begin(), ths->fAttrValue.end() );
+
+ return eTriYes;
+
+} // RecordTailAttr
+
+// =================================================================================================
+// CheckPacketEnd
+// ==============
+//
+// Check for trailing padding and record the packet length. We have trailing padding if the bytes
+// attribute is present and has a value greater than the current length.
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::CheckPacketEnd ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ if ( ths->fPosition == 0 ) { // First call, decide if there is trailing padding.
+
+ const XMP_Int64 currLen64 = (ths->fBufferOffset + (ths->fBufferPtr - ths->fBufferOrigin)) - ths->fPacketStart;
+ if ( currLen64 > 0x7FFFFFFF ) throw std::runtime_error ( "Packet length exceeds 2GB-1" );
+ const XMP_Int32 currLength = (XMP_Int32)currLen64;
+
+ if ( (ths->fBytesAttr != -1) && (ths->fBytesAttr != currLength) ) {
+ if ( ths->fBytesAttr < currLength ) {
+ ths->fBogusPacket = true; // The bytes attribute value is too small.
+ } else {
+ ths->fPosition = ths->fBytesAttr - currLength;
+ if ( (ths->fPosition % ths->fBytesPerChar) != 0 ) {
+ ths->fBogusPacket = true; // The padding is not a multiple of the character size.
+ ths->fPosition = (ths->fPosition / ths->fBytesPerChar) * ths->fBytesPerChar;
+ }
+ }
+ }
+
+ }
+
+ while ( ths->fPosition > 0 ) {
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const char currChar = *ths->fBufferPtr;
+
+ if ( (currChar != ' ') && (currChar != '\t') && (currChar != '\n') && (currChar != '\r') ) {
+ ths->fBogusPacket = true; // The padding is not whitespace.
+ break; // Stop the packet here.
+ }
+
+ ths->fPosition -= bytesPerChar;
+ ths->fBufferPtr += bytesPerChar;
+
+ }
+
+ const XMP_Int64 currLen64 = (ths->fBufferOffset + (ths->fBufferPtr - ths->fBufferOrigin)) - ths->fPacketStart;
+ if ( currLen64 > 0x7FFFFFFF ) throw std::runtime_error ( "Packet length exceeds 2GB-1" );
+ ths->fPacketLength = (XMP_Int32)currLen64;
+ return eTriYes;
+
+} // CheckPacketEnd
+
+// =================================================================================================
+// CheckFinalNulls
+// ===============
+//
+// Do some special case processing for little endian characters. We have to make sure the presumed
+// nulls after the last character actually exist, i.e. that the stream does not end too soon. Note
+// that the prior character scanning has moved the buffer pointer to the address following the last
+// byte of the last character. I.e. we're already past the presumed nulls, so we can't check their
+// content. All we can do is verify that the stream does not end too soon.
+//
+// Doing this check is simple yet subtle. If we're still in the current buffer then the trailing
+// bytes obviously exist. If we're exactly at the end of the buffer then the bytes also exist.
+// The only question is when we're actually past this buffer, partly into the next buffer. This is
+// when "ths->fBufferPtr > ths->fBufferLimit" on entry. For that case we have to wait until we've
+// actually seen enough extra bytes of input.
+//
+// Since the normal buffer processing is already adjusting for this partial character overrun, all
+// that needs to be done here is wait until "ths->fBufferPtr <= ths->fBufferLimit" on entry. In
+// other words, if we're presently too far, ths->fBufferPtr will be adjusted by the amount of the
+// overflow the next time XMPScanner::Scan is called. This might still be too far, so just keep
+// waiting for enough data to pass by.
+//
+// Note that there is a corresponding special case for big endian characters, we must decrement the
+// starting offset by the number of leading nulls. But we don't do that here, we leave it to the
+// outer code. This is because the leading nulls might have been at the exact end of a previous
+// buffer, in which case we have to also decrement the length of that raw data snip.
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::CheckFinalNulls ( PacketMachine * ths, const char * /* unused */ )
+{
+
+ if ( (ths->fCharForm != eChar8Bit) && CharFormIsLittleEndian ( ths->fCharForm ) ) {
+ if ( ths->fBufferPtr > ths->fBufferLimit ) return eTriMaybe;
+ }
+
+ return eTriYes;
+
+} // CheckFinalNulls
+
+// =================================================================================================
+// SetNextRecognizer
+// =================
+
+void
+XMPScanner::PacketMachine::SetNextRecognizer ( RecognizerKind nextRecognizer )
+{
+
+ fRecognizer = nextRecognizer;
+ fPosition = 0;
+
+} // SetNextRecognizer
+
+// =================================================================================================
+// FindNextPacket
+// ==============
+
+// *** When we start validating intervening nulls for 2 and 4 bytes characters, throw an exception
+// *** for errors. Don't return eTriNo, that might skip at an optional point.
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::FindNextPacket ()
+{
+
+ TriState status;
+
+ #define kPacketHead "?xpacket begin="
+ #define kPacketID "W5M0MpCehiHzreSzNTczkc9d"
+ #define kPacketTail "?xpacket end="
+
+ static const RecognizerInfo recognizerTable [eRecognizerCount] = { // ! Would be safer to assign these explicitly.
+
+ // proc successNext failureNext literal
+
+ { NULL, eFailureRecognizer, eFailureRecognizer, NULL}, // eFailureRecognizer
+ { NULL, eSuccessRecognizer, eSuccessRecognizer, NULL}, // eSuccessRecognizer
+
+ { FindLessThan, eHeadStartRecorder, eFailureRecognizer, "H" }, // eLeadInRecognizer
+ { RecordStart, eHeadStartRecognizer, eLeadInRecognizer, NULL }, // eHeadStartRecorder
+ { MatchString, eBOMRecognizer, eLeadInRecognizer, kPacketHead }, // eHeadStartRecognizer
+
+ { RecognizeBOM, eIDTagRecognizer, eLeadInRecognizer, NULL }, // eBOMRecognizer
+
+ { MatchString, eIDOpenRecognizer, eLeadInRecognizer, " id=" }, // eIDTagRecognizer
+ { MatchOpenQuote, eIDValueRecognizer, eLeadInRecognizer, NULL }, // eIDOpenRecognizer
+ { MatchString, eIDCloseRecognizer, eLeadInRecognizer, kPacketID }, // eIDValueRecognizer
+ { MatchCloseQuote, eAttrSpaceRecognizer_1, eLeadInRecognizer, NULL }, // eIDCloseRecognizer
+
+ { MatchChar, eAttrNameRecognizer_1, eHeadEndRecognizer, " " }, // eAttrSpaceRecognizer_1
+ { CaptureAttrName, eAttrValueRecognizer_1, eLeadInRecognizer, NULL }, // eAttrNameRecognizer_1
+ { CaptureAttrValue, eAttrValueRecorder_1, eLeadInRecognizer, NULL }, // eAttrValueRecognizer_1
+ { RecordHeadAttr, eAttrSpaceRecognizer_1, eLeadInRecognizer, NULL }, // eAttrValueRecorder_1
+
+ { MatchString, eBodyRecognizer, eLeadInRecognizer, "?>" }, // eHeadEndRecognizer
+
+ { FindLessThan, eTailStartRecognizer, eBodyRecognizer, "T"}, // eBodyRecognizer
+
+ { MatchString, eAccessValueRecognizer, eBodyRecognizer, kPacketTail }, // eTailStartRecognizer
+ { CaptureAccess, eAttrSpaceRecognizer_2, eBodyRecognizer, NULL }, // eAccessValueRecognizer
+
+ { MatchChar, eAttrNameRecognizer_2, eTailEndRecognizer, " " }, // eAttrSpaceRecognizer_2
+ { CaptureAttrName, eAttrValueRecognizer_2, eBodyRecognizer, NULL }, // eAttrNameRecognizer_2
+ { CaptureAttrValue, eAttrValueRecorder_2, eBodyRecognizer, NULL }, // eAttrValueRecognizer_2
+ { RecordTailAttr, eAttrSpaceRecognizer_2, eBodyRecognizer, NULL }, // eAttrValueRecorder_2
+
+ { MatchString, ePacketEndRecognizer, eBodyRecognizer, "?>" }, // eTailEndRecognizer
+ { CheckPacketEnd, eCloseOutRecognizer, eBodyRecognizer, "" }, // ePacketEndRecognizer
+ { CheckFinalNulls, eSuccessRecognizer, eBodyRecognizer, "" } // eCloseOutRecognizer
+
+ };
+
+ while ( true ) {
+
+ switch ( fRecognizer ) {
+
+ case eFailureRecognizer :
+ return eTriNo;
+
+ case eSuccessRecognizer :
+ return eTriYes;
+
+ default :
+
+ // -------------------------------------------------------------------
+ // For everything else, the normal cases, use the state machine table.
+
+ const RecognizerInfo * thisState = &recognizerTable [fRecognizer];
+
+ status = thisState->proc ( this, thisState->literal );
+
+ switch ( status ) {
+
+ case eTriNo :
+ SetNextRecognizer ( thisState->failureNext );
+ continue;
+
+ case eTriYes :
+ SetNextRecognizer ( thisState->successNext );
+ continue;
+
+ case eTriMaybe :
+ fBufferOverrun = (unsigned char)(fBufferPtr - fBufferLimit);
+ return eTriMaybe; // Keep this recognizer intact, to be resumed later.
+
+ }
+
+ } // switch ( fRecognizer ) { ...
+
+ } // while ( true ) { ...
+
+} // FindNextPacket
+
+// =================================================================================================
+// =================================================================================================
+// class InternalSnip
+// ==================
+
+// =================================================================================================
+// InternalSnip
+// ============
+
+XMPScanner::InternalSnip::InternalSnip ( XMP_Int64 offset, XMP_Int64 length )
+{
+
+ fInfo.fOffset = offset;
+ fInfo.fLength = length;
+
+} // InternalSnip
+
+// =================================================================================================
+// InternalSnip
+// ============
+
+XMPScanner::InternalSnip::InternalSnip ( const InternalSnip & rhs ) :
+ fInfo ( rhs.fInfo ),
+ fMachine ( NULL )
+{
+
+ assert ( rhs.fMachine.get() == NULL ); // Don't copy a snip with a machine.
+ assert ( (rhs.fInfo.fEncodingAttr == 0) || (*rhs.fInfo.fEncodingAttr == 0) ); // Don't copy a snip with an encoding.
+
+} // InternalSnip
+
+// =================================================================================================
+// ~InternalSnip
+// =============
+
+XMPScanner::InternalSnip::~InternalSnip ()
+{
+} // ~InternalSnip
+
+
+// =================================================================================================
+// =================================================================================================
+// class XMPScanner
+// ================
+
+// =================================================================================================
+// DumpSnipList
+// ============
+
+#if DEBUG
+
+static const char * snipStateName [6] = { "not-seen", "pending", "raw-data", "good-packet", "partial", "bad-packet" };
+
+void
+XMPScanner::DumpSnipList ( const char * title )
+{
+ InternalSnipIterator currPos = fInternalSnips.begin();
+ InternalSnipIterator endPos = fInternalSnips.end();
+
+ cout << endl << title << " snip list: " << fInternalSnips.size() << endl;
+
+ for ( ; currPos != endPos; ++currPos ) {
+ SnipInfo * currSnip = &currPos->fInfo;
+ cout << '\t' << currSnip << ' ' << snipStateName[currSnip->fState] << ' '
+ << currSnip->fOffset << ".." << (currSnip->fOffset + currSnip->fLength - 1)
+ << ' ' << currSnip->fLength << ' ' << endl;
+ }
+} // DumpSnipList
+
+#endif
+
+// =================================================================================================
+// PrevSnip and NextSnip
+// =====================
+
+XMPScanner::InternalSnipIterator
+XMPScanner::PrevSnip ( InternalSnipIterator snipPos )
+{
+
+ InternalSnipIterator prev = snipPos;
+ return --prev;
+
+} // PrevSnip
+
+XMPScanner::InternalSnipIterator
+XMPScanner::NextSnip ( InternalSnipIterator snipPos )
+{
+
+ InternalSnipIterator next = snipPos;
+ return ++next;
+
+} // NextSnip
+
+// =================================================================================================
+// XMPScanner
+// ==========
+//
+// Initialize the scanner object with one "not seen" snip covering the whole stream.
+
+XMPScanner::XMPScanner ( XMP_Int64 streamLength ) :
+
+ fStreamLength ( streamLength )
+
+{
+ InternalSnip rootSnip ( 0, streamLength );
+
+ if ( streamLength > 0 ) fInternalSnips.push_front ( rootSnip ); // Be nice for empty files.
+ // DumpSnipList ( "New XMPScanner" );
+
+} // XMPScanner
+
+// =================================================================================================
+// ~XMPScanner
+// ===========
+
+XMPScanner::~XMPScanner()
+{
+
+} // ~XMPScanner
+
+// =================================================================================================
+// GetSnipCount
+// ============
+
+long
+XMPScanner::GetSnipCount ()
+{
+
+ return (long)fInternalSnips.size();
+
+} // GetSnipCount
+
+// =================================================================================================
+// StreamAllScanned
+// ================
+
+bool
+XMPScanner::StreamAllScanned ()
+{
+ InternalSnipIterator currPos = fInternalSnips.begin();
+ InternalSnipIterator endPos = fInternalSnips.end();
+
+ for ( ; currPos != endPos; ++currPos ) {
+ if ( currPos->fInfo.fState == eNotSeenSnip ) return false;
+ }
+ return true;
+
+} // StreamAllScanned
+
+// =================================================================================================
+// SplitInternalSnip
+// =================
+//
+// Split the given snip into up to 3 pieces. The new pieces are inserted before and after this one
+// in the snip list. The relOffset is the first byte to be kept, it is relative to this snip. If
+// the preceeding or following snips have the same state as this one, just shift the boundaries.
+// I.e. move the contents from one snip to the other, don't create a new snip.
+
+// *** To be thread safe we ought to lock the entire list during manipulation. Let data scanning
+// *** happen in parallel, serialize all mucking with the list.
+
+void
+XMPScanner::SplitInternalSnip ( InternalSnipIterator snipPos, XMP_Int64 relOffset, XMP_Int64 newLength )
+{
+
+ assert ( (relOffset + newLength) > relOffset ); // Check for overflow.
+ assert ( (relOffset + newLength) <= snipPos->fInfo.fLength );
+
+ // -----------------------------------
+ // First deal with the low offset end.
+
+ if ( relOffset > 0 ) {
+
+ InternalSnipIterator prevPos;
+ if ( snipPos != fInternalSnips.begin() ) prevPos = PrevSnip ( snipPos );
+
+ if ( (snipPos != fInternalSnips.begin()) && (snipPos->fInfo.fState == prevPos->fInfo.fState) ) {
+ prevPos->fInfo.fLength += relOffset; // Adjust the preceeding snip.
+ } else {
+ InternalSnip headExcess ( snipPos->fInfo.fOffset, relOffset );
+ headExcess.fInfo.fState = snipPos->fInfo.fState;
+ headExcess.fInfo.fOutOfOrder = snipPos->fInfo.fOutOfOrder;
+ fInternalSnips.insert ( snipPos, headExcess ); // Insert the head piece before the middle piece.
+ }
+
+ snipPos->fInfo.fOffset += relOffset; // Adjust the remainder of this snip.
+ snipPos->fInfo.fLength -= relOffset;
+
+ }
+
+ // ----------------------------------
+ // Now deal with the high offset end.
+
+ if ( newLength < snipPos->fInfo.fLength ) {
+
+ InternalSnipIterator nextPos = NextSnip ( snipPos );
+ const XMP_Int64 tailLength = snipPos->fInfo.fLength - newLength;
+
+ if ( (nextPos != fInternalSnips.end()) && (snipPos->fInfo.fState == nextPos->fInfo.fState) ) {
+ nextPos->fInfo.fOffset -= tailLength; // Adjust the following snip.
+ nextPos->fInfo.fLength += tailLength;
+ } else {
+ InternalSnip tailExcess ( (snipPos->fInfo.fOffset + newLength), tailLength );
+ tailExcess.fInfo.fState = snipPos->fInfo.fState;
+ tailExcess.fInfo.fOutOfOrder = snipPos->fInfo.fOutOfOrder;
+ fInternalSnips.insert ( nextPos, tailExcess ); // Insert the tail piece after the middle piece.
+ }
+
+ snipPos->fInfo.fLength = newLength;
+
+ }
+
+} // SplitInternalSnip
+
+// =================================================================================================
+// MergeInternalSnips
+// ==================
+
+XMPScanner::InternalSnipIterator
+XMPScanner::MergeInternalSnips ( InternalSnipIterator firstPos, InternalSnipIterator secondPos )
+{
+
+ firstPos->fInfo.fLength += secondPos->fInfo.fLength;
+ fInternalSnips.erase ( secondPos );
+ return firstPos;
+
+} // MergeInternalSnips
+
+// =================================================================================================
+// Scan
+// ====
+
+void
+XMPScanner::Scan ( const void * bufferOrigin, XMP_Int64 bufferOffset, XMP_Int64 bufferLength )
+{
+ XMP_Int64 relOffset;
+
+ #if 0
+ cout << "Scan: @ " << bufferOrigin << ", " << bufferOffset << ", " << bufferLength << endl;
+ #endif
+
+ if ( bufferLength == 0 ) return;
+
+ // ----------------------------------------------------------------
+ // These comparisons are carefully done to avoid overflow problems.
+
+ if ( (bufferOffset >= fStreamLength) ||
+ (bufferLength > (fStreamLength - bufferOffset)) ||
+ (bufferOrigin == 0) ) {
+ throw ScanError ( "Bad origin, offset, or length" );
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // This buffer must be within a not-seen snip. Find it and split it. The first snip whose whose
+ // end is beyond the buffer must be the enclosing one.
+
+ // *** It would be friendly for rescans for out of order problems to accept any buffer postion.
+
+ const XMP_Int64 endOffset = bufferOffset + bufferLength - 1;
+ InternalSnipIterator snipPos = fInternalSnips.begin();
+
+ while ( endOffset > (snipPos->fInfo.fOffset + snipPos->fInfo.fLength - 1) ) ++ snipPos;
+ if ( snipPos->fInfo.fState != eNotSeenSnip ) throw ScanError ( "Already seen" );
+
+ relOffset = bufferOffset - snipPos->fInfo.fOffset;
+ if ( (relOffset + bufferLength) > snipPos->fInfo.fLength ) throw ScanError ( "Not within existing snip" );
+
+ SplitInternalSnip ( snipPos, relOffset, bufferLength ); // *** If sequential & prev is partial, just tack on,
+
+ // --------------------------------------------------------
+ // Merge this snip with the preceeding snip if appropriate.
+
+ // *** When out of order I/O is supported we have to do something about buffers who's predecessor is not seen.
+
+ if ( snipPos->fInfo.fOffset > 0 ) {
+ InternalSnipIterator prevPos = PrevSnip ( snipPos );
+ if ( prevPos->fInfo.fState == ePartialPacketSnip ) snipPos = MergeInternalSnips ( prevPos, snipPos );
+ }
+
+ // ----------------------------------
+ // Look for packets within this snip.
+
+ snipPos->fInfo.fState = ePendingSnip;
+ PacketMachine* thisMachine = snipPos->fMachine.get();
+ // DumpSnipList ( "Before scan" );
+
+ if ( thisMachine != 0 ) {
+ thisMachine->AssociateBuffer ( bufferOffset, bufferOrigin, bufferLength );
+ } else {
+ // *** snipPos->fMachine.reset ( new PacketMachine ( bufferOffset, bufferOrigin, bufferLength ) ); VC++ lacks reset
+ #if 0
+ snipPos->fMachine = auto_ptr<PacketMachine> ( new PacketMachine ( bufferOffset, bufferOrigin, bufferLength ) );
+ #else
+ {
+ // Some versions of gcc complain about the assignment operator above. This avoids the gcc bug.
+ PacketMachine * pm = new PacketMachine ( bufferOffset, bufferOrigin, bufferLength );
+ auto_ptr<PacketMachine> ap ( pm );
+ snipPos->fMachine = ap;
+ }
+ #endif
+ thisMachine = snipPos->fMachine.get();
+ }
+
+ bool bufferDone = false;
+ while ( ! bufferDone ) {
+
+ PacketMachine::TriState foundPacket = thisMachine->FindNextPacket();
+
+ if ( foundPacket == PacketMachine::eTriNo ) {
+
+ // -----------------------------------------------------------------------
+ // No packet, mark the snip as raw data and get rid of the packet machine.
+ // We're done with this buffer.
+
+ snipPos->fInfo.fState = eRawInputSnip;
+ #if 0
+ snipPos->fMachine = auto_ptr<PacketMachine>(); // *** snipPos->fMachine.reset(); VC++ lacks reset
+ #else
+ {
+ // Some versions of gcc complain about the assignment operator above. This avoids the gcc bug.
+ auto_ptr<PacketMachine> ap ( 0 );
+ snipPos->fMachine = ap;
+ }
+ #endif
+ bufferDone = true;
+
+ } else {
+
+ // ---------------------------------------------------------------------------------------------
+ // Either a full or partial packet. First trim any excess off of the front as a raw input snip.
+ // If this is a partial packet mark the snip and keep the packet machine to be resumed later.
+ // We're done with this buffer, the partial packet by definition extends to the end. If this is
+ // a complete packet first extract the additional information from the packet machine. If there
+ // is leftover data split the snip and transfer the packet machine to the new trailing snip.
+
+ if ( thisMachine->fPacketStart > snipPos->fInfo.fOffset ) {
+
+ // There is data at the front of the current snip that must be trimmed.
+ SnipState savedState = snipPos->fInfo.fState;
+ snipPos->fInfo.fState = eRawInputSnip; // ! So it gets propagated to the trimmed front part.
+ relOffset = thisMachine->fPacketStart - snipPos->fInfo.fOffset;
+ SplitInternalSnip ( snipPos, relOffset, (snipPos->fInfo.fLength - relOffset) );
+ snipPos->fInfo.fState = savedState;
+
+ }
+
+ if ( foundPacket == PacketMachine::eTriMaybe ) {
+
+ // We have only found a partial packet.
+ snipPos->fInfo.fState = ePartialPacketSnip;
+ bufferDone = true;
+
+ } else {
+
+ // We have found a complete packet. Extract all the info for it and split any trailing data.
+
+ InternalSnipIterator packetSnip = snipPos;
+ SnipState packetState = eValidPacketSnip;
+
+ if ( thisMachine->fBogusPacket ) packetState = eBadPacketSnip;
+
+ packetSnip->fInfo.fAccess = thisMachine->fAccess;
+ packetSnip->fInfo.fCharForm = thisMachine->fCharForm;
+ packetSnip->fInfo.fBytesAttr = thisMachine->fBytesAttr;
+ packetSnip->fInfo.fEncodingAttr = thisMachine->fEncodingAttr.c_str();
+ thisMachine->fEncodingAttr.erase ( thisMachine->fEncodingAttr.begin(), thisMachine->fEncodingAttr.end() );
+
+ if ( (thisMachine->fCharForm != eChar8Bit) && CharFormIsBigEndian ( thisMachine->fCharForm ) ) {
+
+ // ------------------------------------------------------------------------------
+ // Handle a special case for big endian characters. The packet machine works as
+ // though things were little endian. The packet starting offset points to the
+ // byte containing the opening '<', and the length includes presumed nulls that
+ // follow the last "real" byte. If the characters are big endian we now have to
+ // decrement the starting offset of the packet, and also decrement the length of
+ // the previous snip.
+ //
+ // Note that we can't do this before the head trimming above in general. The
+ // nulls might have been exactly at the end of a buffer and already in the
+ // previous snip. We are doing this before trimming the tail from the raw snip
+ // containing the packet. We adjust the raw snip's size because it ends with
+ // the input buffer. We don't adjust the packet's size, it is already correct.
+ //
+ // The raw snip (the one before the packet) might entirely disappear. A simple
+ // example of this is when the packet is at the start of the file.
+
+ assert ( packetSnip != fInternalSnips.begin() ); // Leading nulls were trimmed!
+
+ if ( packetSnip != fInternalSnips.begin() ) { // ... but let's program defensibly.
+
+ InternalSnipIterator prevSnip = PrevSnip ( packetSnip );
+ const unsigned int nullsToAdd = ( CharFormIs16Bit ( thisMachine->fCharForm ) ? 1 : 3 );
+
+ assert ( nullsToAdd <= prevSnip->fInfo.fLength );
+ prevSnip->fInfo.fLength -= nullsToAdd;
+ if ( prevSnip->fInfo.fLength == 0 ) (void) fInternalSnips.erase ( prevSnip );
+
+ packetSnip->fInfo.fOffset -= nullsToAdd;
+ packetSnip->fInfo.fLength += nullsToAdd;
+ thisMachine->fPacketStart -= nullsToAdd;
+
+ }
+
+ }
+
+ if ( thisMachine->fPacketLength == snipPos->fInfo.fLength ) {
+
+ // This packet ends exactly at the end of the current snip.
+ #if 0
+ snipPos->fMachine = auto_ptr<PacketMachine>(); // *** snipPos->fMachine.reset(); VC++ lacks reset
+ #else
+ {
+ // Some versions of gcc complain about the assignment operator above. This avoids the gcc bug.
+ auto_ptr<PacketMachine> ap ( 0 );
+ snipPos->fMachine = ap;
+ }
+ #endif
+ bufferDone = true;
+
+ } else {
+
+ // There is trailing data to split from the just found packet.
+ SplitInternalSnip ( snipPos, 0, thisMachine->fPacketLength );
+
+ InternalSnipIterator tailPos = NextSnip ( snipPos );
+
+ tailPos->fMachine = snipPos->fMachine; // auto_ptr assignment - taking ownership
+ thisMachine->ResetMachine ();
+
+ snipPos = tailPos;
+
+ }
+
+ packetSnip->fInfo.fState = packetState; // Do this last to avoid messing up the tail split.
+ // DumpSnipList ( "Found a packet" );
+
+ }
+
+ }
+
+ }
+
+ // --------------------------------------------------------
+ // Merge this snip with the preceeding snip if appropriate.
+
+ // *** When out of order I/O is supported we have to check the following snip too.
+
+ if ( (snipPos->fInfo.fOffset > 0) && (snipPos->fInfo.fState == eRawInputSnip) ) {
+ InternalSnipIterator prevPos = PrevSnip ( snipPos );
+ if ( prevPos->fInfo.fState == eRawInputSnip ) snipPos = MergeInternalSnips ( prevPos, snipPos );
+ }
+
+ // DumpSnipList ( "After scan" );
+
+} // Scan
+
+// =================================================================================================
+// Report
+// ======
+
+void
+XMPScanner::Report ( SnipInfoVector& snips )
+{
+ const int count = (int)fInternalSnips.size();
+ InternalSnipIterator snipPos = fInternalSnips.begin();
+
+ int s;
+
+ // DumpSnipList ( "Report" );
+
+ snips.erase ( snips.begin(), snips.end() ); // ! Should use snips.clear, but VC++ doesn't have it.
+ snips.reserve ( count );
+
+ for ( s = 0; s < count; s += 1 ) {
+ snips.push_back ( SnipInfo ( snipPos->fInfo.fState, snipPos->fInfo.fOffset, snipPos->fInfo.fLength ) );
+ snips[s] = snipPos->fInfo; // Pick up all of the fields.
+ ++ snipPos;
+ }
+
+} // Report
+
+// =================================================================================================
+
+#endif // EnablePacketScanning
diff --git a/XMPFiles/source/FormatSupport/XMPScanner.hpp b/XMPFiles/source/FormatSupport/XMPScanner.hpp
new file mode 100644
index 0000000..1648692
--- /dev/null
+++ b/XMPFiles/source/FormatSupport/XMPScanner.hpp
@@ -0,0 +1,330 @@
+#ifndef __XMPScanner_hpp__
+#define __XMPScanner_hpp__
+
+// =================================================================================================
+// Copyright 2004 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.
+//
+// Adobe patent application tracking #P435, entitled 'Unique markers to simplify embedding data of
+// one format in a file with a different format', inventors: Sean Parent, Greg Gilley.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include <list>
+#include <vector>
+#include <string>
+#include <memory>
+#include <stdexcept>
+
+#include "public/include/XMP_Const.h"
+
+// =================================================================================================
+// The XMPScanner class is used to scan a stream of input for XMP packets. A scanner object is
+// constructed then fed the input through a series of calls to Scan. Report may be called at any
+// time to get the current knowledge of the input.
+//
+// A packet starts when a valid header is found and ends when a valid trailer is found. If the
+// header contains a "bytes" attribute, additional whitespace must follow.
+//
+// *** RESTRICTIONS: The current implementation of the scanner has the the following restrictions:
+// - The input must be presented in order.
+// - Not fully thread safe, don't make concurrent calls to the same XMPScanner object.
+// =================================================================================================
+
+class XMPScanner {
+public:
+
+ // =============================================================================================
+ // The entire input stream is represented as a series of snips. Each snip defines one portion
+ // of the input stream that either has not been seen, has been seen and contains no packets, is
+ // exactly one packet, or contains the start of an unfinished packet. Adjacent snips with the
+ // same state are merged, so the number of snips is always minimal.
+ //
+ // A newly constructed XMPScanner object has one snip covering the whole input with a state
+ // of "not seen". A block of input that contains a full XMP packet is split into 3 parts: a
+ // (possibly empty) raw input snip, the packet, and another (possibly empty) raw input snip. A
+ // block of input that contains the start of an XMP packet is split into two snips, a (possibly
+ // empty) raw input snip and the packet start; the following snip must be a "not seen" snip.
+ //
+ // It is possible to have ill-formed packets. These have a syntactically valid header and
+ // trailer, but some semantic error. For example, if the "bytes" attribute length does not span
+ // to the end of the trailer, or if the following packet begins within trailing padding.
+
+ enum {
+ eNotSeenSnip, // This snip has not been seen yet.
+ ePendingSnip, // This snip is an input buffer being processed.
+ eRawInputSnip, // This snip is raw input, it doesn't contain any part of an XMP packet.
+ eValidPacketSnip, // This snip is a complete, valid XMP packet.
+ ePartialPacketSnip, // This snip contains the start of a possible XMP packet.
+ eBadPacketSnip // This snip contains a complete, but semantically incorrect XMP packet.
+ };
+ typedef XMP_Uns8 SnipState;
+
+ enum { // The values allow easy testing for 16/32 bit and big/little endian.
+ eChar8Bit = 0,
+ eChar16BitBig = 2,
+ eChar16BitLittle = 3,
+ eChar32BitBig = 4,
+ eChar32BitLittle = 5
+ };
+ typedef XMP_Uns8 CharacterForm;
+
+ enum {
+ eChar16BitMask = 2, // These constant shouldn't be used directly, they are mainly
+ eChar32BitMask = 4, // for the CharFormIsXyz macros below.
+ eCharLittleEndianMask = 1
+ };
+
+ #define CharFormIs16Bit(f) ( ((int)(f) & XMPScanner::eChar16BitMask) != 0 )
+ #define CharFormIs32Bit(f) ( ((int)(f) & XMPScanner::eChar32BitMask) != 0 )
+
+ #define CharFormIsBigEndian(f) ( ((int)(f) & XMPScanner::eCharLittleEndianMask) == 0 )
+ #define CharFormIsLittleEndian(f) ( ((int)(f) & XMPScanner::eCharLittleEndianMask) != 0 )
+
+ struct SnipInfo {
+
+ XMP_Int64 fOffset; // The byte offset of this snip within the input stream.
+ XMP_Int64 fLength; // The length in bytes of this snip.
+ SnipState fState; // The state of this snip.
+ bool fOutOfOrder; // If true, this snip was seen before the one in front of it.
+ char fAccess; // The read-only/read-write access from the end attribute.
+ CharacterForm fCharForm; // How the packet is divided into characters.
+ const char * fEncodingAttr; // The value of the encoding attribute, if any, with nulls removed.
+ XMP_Int64 fBytesAttr; // The value of the bytes attribute, -1 if not present.
+
+ SnipInfo() :
+ fOffset ( 0 ),
+ fLength ( 0 ),
+ fState ( eNotSeenSnip ),
+ fOutOfOrder ( false ),
+ fAccess ( ' ' ),
+ fCharForm ( eChar8Bit ),
+ fEncodingAttr ( "" ),
+ fBytesAttr( -1 )
+ { }
+
+ SnipInfo ( SnipState state, XMP_Int64 offset, XMP_Int64 length ) :
+ fOffset ( offset ),
+ fLength ( length ),
+ fState ( state ),
+ fOutOfOrder ( false ),
+ fAccess ( ' ' ),
+ fCharForm ( eChar8Bit ),
+ fEncodingAttr ( "" ),
+ fBytesAttr( -1 )
+ { }
+
+ };
+
+ typedef std::vector<SnipInfo> SnipInfoVector;
+
+ XMPScanner ( XMP_Int64 streamLength );
+ // Constructs a new XMPScanner object for a stream with the given length.
+
+ ~XMPScanner();
+
+ long GetSnipCount();
+ // Returns the number of snips that the stream has been divided into.
+
+ bool StreamAllScanned();
+ // Returns true if all of the stream has been seen.
+
+ void Scan ( const void * bufferOrigin, XMP_Int64 bufferOffset, XMP_Int64 bufferLength );
+ // Scans the given part of the input, incorporating it in to the known snips.
+ // The bufferOffset is the offset of this block of input relative to the entire stream.
+ // The bufferLength is the length in bytes of this block of input.
+
+ void Report ( SnipInfoVector & snips );
+ // Produces a report of what is known about the input stream.
+
+ class ScanError : public std::logic_error {
+ public:
+ ScanError() throw() : std::logic_error ( "" ) {}
+ explicit ScanError ( const char * message ) throw() : std::logic_error ( message ) {}
+ virtual ~ScanError() throw() {}
+ };
+
+private: // XMPScanner
+
+ class PacketMachine;
+
+ class InternalSnip {
+ public:
+
+ SnipInfo fInfo; // The public info about this snip.
+ std::auto_ptr<PacketMachine> fMachine; // The state machine for "active" snips.
+
+ InternalSnip ( XMP_Int64 offset, XMP_Int64 length );
+ InternalSnip ( const InternalSnip & );
+ ~InternalSnip ();
+
+ }; // InternalSnip
+
+ typedef std::list<InternalSnip> InternalSnipList;
+ typedef InternalSnipList::iterator InternalSnipIterator;
+
+ class PacketMachine {
+ public:
+
+ XMP_Int64 fPacketStart; // Byte offset relative to the entire stream.
+ XMP_Int32 fPacketLength; // Length in bytes to the end of the trailer processing instruction.
+ XMP_Int32 fBytesAttr; // The value of the bytes attribute, -1 if not present.
+ std::string fEncodingAttr; // The value of the encoding attribute, if any, with nulls removed.
+ CharacterForm fCharForm; // How the packet is divided into characters.
+ char fAccess; // The read-only/read-write access from the end attribute.
+ bool fBogusPacket; // True if the packet has an error such as a bad "bytes" attribute value.
+
+ void ResetMachine();
+
+ enum TriState {
+ eTriNo,
+ eTriMaybe,
+ eTriYes
+ };
+
+ TriState FindNextPacket();
+
+ void AssociateBuffer ( XMP_Int64 bufferOffset, const void * bufferOrigin, XMP_Int64 bufferLength );
+
+ PacketMachine ( XMP_Int64 bufferOffset, const void * bufferOrigin, XMP_Int64 bufferLength );
+ ~PacketMachine();
+
+ private: // PacketMachine
+
+ PacketMachine() {}; // ! Hide the default constructor.
+
+ enum RecognizerKind {
+
+ eFailureRecognizer, // Not really recognizers, special states to end one buffer's processing.
+ eSuccessRecognizer,
+
+ eLeadInRecognizer, // Anything up to the next '<'.
+ eHeadStartRecorder, // Save the starting offset, count intervening nulls.
+ eHeadStartRecognizer, // The literal string "?xpacket begin=".
+
+ eBOMRecognizer, // Recognize and record the quoted byte order marker.
+
+ eIDTagRecognizer, // The literal string " id=".
+ eIDOpenRecognizer, // The opening quote for the ID.
+ eIDValueRecognizer, // The literal string "W5M0MpCehiHzreSzNTczkc9d".
+ eIDCloseRecognizer, // The closing quote for the ID.
+
+ eAttrSpaceRecognizer_1, // The space before an attribute.
+ eAttrNameRecognizer_1, // The name of an attribute.
+ eAttrValueRecognizer_1, // The equal sign and quoted string value for an attribute.
+ eAttrValueRecorder_1, // Record the value of an attribute.
+
+ eHeadEndRecognizer, // The string literal "?>".
+
+ eBodyRecognizer, // The packet body, anything up to the next '<'.
+
+ eTailStartRecognizer, // The string literal "?xpacket end=".
+ eAccessValueRecognizer, // Recognize and record the quoted r/w access mode.
+
+ eAttrSpaceRecognizer_2, // The space before an attribute.
+ eAttrNameRecognizer_2, // The name of an attribute.
+ eAttrValueRecognizer_2, // The equal sign and quoted string value for an attribute.
+ eAttrValueRecorder_2, // Record the value of an attribute.
+
+ eTailEndRecognizer, // The string literal "?>".
+ ePacketEndRecognizer, // Look for trailing padding, check and record the packet size.
+ eCloseOutRecognizer, // Look for final nulls for little endian multibyte characters.
+
+ eRecognizerCount
+
+ };
+
+ XMP_Int64 fBufferOffset; // The offset of the data buffer within the input stream.
+ const char * fBufferOrigin; // The starting address of the data buffer for this snip.
+ const char * fBufferPtr; // The current postion in the data buffer.
+ const char * fBufferLimit; // The address one past the last byte in the data buffer.
+
+ RecognizerKind fRecognizer; // Which recognizer is currently active.
+ signed long fPosition; // The internal position within a string literal, etc.
+ unsigned char fBytesPerChar; // The number of bytes per logical character, 1, 2, or 4.
+ unsigned char fBufferOverrun; // Non-zero if suspended while skipping intervening nulls.
+ char fQuoteChar; // The kind of quote seen at the start of a quoted value.
+ std::string fAttrName; // The name for an arbitrary attribute (other than "begin" and "id").
+ std::string fAttrValue; // The value for an arbitrary attribute (other than "begin" and "id").
+
+ void SetNextRecognizer ( RecognizerKind nextRecognizer );
+
+ typedef TriState (* RecognizerProc) ( PacketMachine *, const char * );
+
+ static TriState
+ FindLessThan ( PacketMachine * ths, const char * which );
+
+ static TriState
+ MatchString ( PacketMachine * ths, const char * literal );
+
+ static TriState
+ MatchChar ( PacketMachine * ths, const char * literal );
+
+ static TriState
+ MatchOpenQuote ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ MatchCloseQuote ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ CaptureAttrName ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ CaptureAttrValue ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ RecordStart ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ RecognizeBOM ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ RecordHeadAttr ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ CaptureAccess ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ RecordTailAttr ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ CheckPacketEnd ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ CheckFinalNulls ( PacketMachine * ths, const char * /* unused */ );
+
+ struct RecognizerInfo {
+ RecognizerProc proc;
+ RecognizerKind successNext;
+ RecognizerKind failureNext;
+ const char * literal;
+ };
+
+ }; // PacketMachine
+
+ XMP_Int64 fStreamLength;
+ InternalSnipList fInternalSnips;
+
+ void
+ SplitInternalSnip ( InternalSnipIterator snipPos, XMP_Int64 relOffset, XMP_Int64 newLength );
+
+ InternalSnipIterator
+ MergeInternalSnips ( InternalSnipIterator firstPos, InternalSnipIterator secondPos );
+
+ InternalSnipIterator
+ PrevSnip ( InternalSnipIterator snipPos );
+
+ InternalSnipIterator
+ NextSnip ( InternalSnipIterator snipPos );
+
+ #if DEBUG
+ void DumpSnipList ( const char * title );
+ #endif
+
+}; // XMPScanner
+
+#endif // __XMPScanner_hpp__
diff --git a/XMPFiles/source/HandlerRegistry.cpp b/XMPFiles/source/HandlerRegistry.cpp
new file mode 100644
index 0000000..144dbcf
--- /dev/null
+++ b/XMPFiles/source/HandlerRegistry.cpp
@@ -0,0 +1,961 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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 "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+#include "XMPFiles/source/HandlerRegistry.h"
+#include "XMPFiles/source/PluginHandler/XMPAtoms.h"
+
+#if EnablePhotoHandlers
+ #include "XMPFiles/source/FileHandlers/JPEG_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/PSD_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/TIFF_Handler.hpp"
+#endif
+
+#if EnableDynamicMediaHandlers
+ #include "XMPFiles/source/FileHandlers/AIFF_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/ASF_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/FLV_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/MP3_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/MPEG2_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/MPEG4_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/P2_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/WAVE_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/SWF_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/XDCAM_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp"
+#endif
+
+#if EnableMiscHandlers
+ #include "XMPFiles/source/FileHandlers/InDesign_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/PNG_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/PostScript_Handler.hpp"
+ #include "XMPFiles/source/FileHandlers/UCF_Handler.hpp"
+#endif
+
+//#if EnablePacketScanning
+//#include "XMPFiles/source/FileHandlers/Scanner_Handler.hpp"
+//#endif
+
+using namespace Common;
+using namespace XMP_PLUGIN;
+
+// =================================================================================================
+
+#if EnableDynamicMediaHandlers
+
+static const char * kP2ContentChildren[] = { "CLIP", "VIDEO", "AUDIO", "ICON", "VOICE", "PROXY", 0 };
+
+static inline bool CheckP2ContentChild ( const std::string & folderName )
+{
+ for ( int i = 0; kP2ContentChildren[i] != 0; ++i ) {
+ if ( folderName == kP2ContentChildren[i] ) return true;
+ }
+ return false;
+}
+
+#endif
+
+// =================================================================================================
+
+//
+// Static init
+//
+HandlerRegistry* HandlerRegistry::sInstance = 0;
+
+/*static*/ HandlerRegistry& HandlerRegistry::getInstance()
+{
+ if ( sInstance == 0 ) sInstance = new HandlerRegistry();
+ return *sInstance;
+}
+
+/*static*/ void HandlerRegistry::terminate()
+{
+ delete sInstance;
+ sInstance = 0;
+}
+
+HandlerRegistry::HandlerRegistry()
+{
+ mFolderHandlers = new XMPFileHandlerTable;
+ mNormalHandlers = new XMPFileHandlerTable;
+ mOwningHandlers = new XMPFileHandlerTable;
+ mReplacedHandlers = new XMPFileHandlerTable;
+}
+
+HandlerRegistry::~HandlerRegistry()
+{
+ delete mFolderHandlers;
+ delete mNormalHandlers;
+ delete mOwningHandlers;
+ delete mReplacedHandlers;
+}
+
+// =================================================================================================
+
+void HandlerRegistry::initialize()
+{
+
+ bool allOK = true; // All of the linked-in handler registrations must work, do one test at the end.
+
+ // -----------------------------------------
+ // Register the directory-oriented handlers.
+
+#if EnableDynamicMediaHandlers
+ allOK &= this->registerFolderHandler ( kXMP_P2File, kP2_HandlerFlags, P2_CheckFormat, P2_MetaHandlerCTor );
+ allOK &= this->registerFolderHandler ( kXMP_SonyHDVFile, kSonyHDV_HandlerFlags, SonyHDV_CheckFormat, SonyHDV_MetaHandlerCTor );
+ allOK &= this->registerFolderHandler ( kXMP_XDCAM_FAMFile, kXDCAM_HandlerFlags, XDCAM_CheckFormat, XDCAM_MetaHandlerCTor );
+ allOK &= this->registerFolderHandler ( kXMP_XDCAM_SAMFile, kXDCAM_HandlerFlags, XDCAM_CheckFormat, XDCAM_MetaHandlerCTor );
+ allOK &= this->registerFolderHandler ( kXMP_XDCAM_EXFile, kXDCAMEX_HandlerFlags, XDCAMEX_CheckFormat, XDCAMEX_MetaHandlerCTor );
+#endif
+
+ // ------------------------------------------------------------------------------------------
+ // Register the file-oriented handlers that don't want to open and close the file themselves.
+
+#if EnablePhotoHandlers
+ allOK &= this->registerNormalHandler ( kXMP_JPEGFile, kJPEG_HandlerFlags, JPEG_CheckFormat, JPEG_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_PhotoshopFile, kPSD_HandlerFlags, PSD_CheckFormat, PSD_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_TIFFFile, kTIFF_HandlerFlags, TIFF_CheckFormat, TIFF_MetaHandlerCTor );
+#endif
+
+#if EnableDynamicMediaHandlers
+ allOK &= this->registerNormalHandler ( kXMP_WMAVFile, kASF_HandlerFlags, ASF_CheckFormat, ASF_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_MP3File, kMP3_HandlerFlags, MP3_CheckFormat, MP3_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_WAVFile, kWAVE_HandlerFlags, WAVE_CheckFormat, WAVE_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_AVIFile, kRIFF_HandlerFlags, RIFF_CheckFormat, RIFF_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_SWFFile, kSWF_HandlerFlags, SWF_CheckFormat, SWF_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_MPEG4File, kMPEG4_HandlerFlags, MPEG4_CheckFormat, MPEG4_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_MOVFile, kMPEG4_HandlerFlags, MPEG4_CheckFormat, MPEG4_MetaHandlerCTor ); // ! Yes, MPEG-4 includes MOV.
+ allOK &= this->registerNormalHandler ( kXMP_FLVFile, kFLV_HandlerFlags, FLV_CheckFormat, FLV_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_AIFFFile, kAIFF_HandlerFlags, AIFF_CheckFormat, AIFF_MetaHandlerCTor );
+#endif
+
+#if EnableMiscHandlers
+ allOK &= this->registerNormalHandler ( kXMP_InDesignFile, kInDesign_HandlerFlags, InDesign_CheckFormat, InDesign_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_PNGFile, kPNG_HandlerFlags, PNG_CheckFormat, PNG_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_UCFFile, kUCF_HandlerFlags, UCF_CheckFormat, UCF_MetaHandlerCTor );
+ // ! EPS and PostScript have the same handler, EPS is a proper subset of PostScript.
+ allOK &= this->registerNormalHandler ( kXMP_EPSFile, kPostScript_HandlerFlags, PostScript_CheckFormat, PostScript_MetaHandlerCTor );
+ allOK &= this->registerNormalHandler ( kXMP_PostScriptFile, kPostScript_HandlerFlags, PostScript_CheckFormat, PostScript_MetaHandlerCTor );
+#endif
+
+ // ------------------------------------------------------------------------------------
+ // Register the file-oriented handlers that need to open and close the file themselves.
+
+#if EnableDynamicMediaHandlers
+ allOK &= this->registerOwningHandler ( kXMP_MPEGFile, kMPEG2_HandlerFlags, MPEG2_CheckFormat, MPEG2_MetaHandlerCTor );
+ allOK &= this->registerOwningHandler ( kXMP_MPEG2File, kMPEG2_HandlerFlags, MPEG2_CheckFormat, MPEG2_MetaHandlerCTor );
+#endif
+
+ if ( ! allOK ) XMP_Throw ( "Failure initializing linked-in file handlers", kXMPErr_InternalFailure );
+
+} // HandlerRegistry::initialize
+
+// =================================================================================================
+
+bool HandlerRegistry::registerFolderHandler( XMP_FileFormat format,
+ XMP_OptionBits flags,
+ CheckFolderFormatProc checkProc,
+ XMPFileHandlerCTor handlerCTor,
+ bool replaceExisting /*= false*/ )
+{
+ XMP_Assert ( format != kXMP_UnknownFile );
+
+ XMP_Assert ( flags & kXMPFiles_HandlerOwnsFile );
+ XMP_Assert ( flags & kXMPFiles_FolderBasedFormat );
+ XMP_Assert ( (flags & kXMPFiles_CanInjectXMP) ? (flags & kXMPFiles_CanExpand) : 1 );
+
+ if ( replaceExisting )
+ {
+ //
+ // Remember previous file handler for this format.
+ // Reject if there is already a replacement.
+ //
+ if( mReplacedHandlers->find( format ) == mReplacedHandlers->end() )
+ {
+ XMPFileHandlerInfo* standardHandler = this->getHandlerInfo( format );
+
+ if( standardHandler != NULL )
+ {
+ mReplacedHandlers->insert( mReplacedHandlers->end(), XMPFileHandlerTablePair( format, *standardHandler ) );
+ }
+ else
+ {
+ // skip registration if there is nothing to replace
+ return false;
+ }
+ }
+ else
+ {
+ // skip registration if there is already a replacing handler registered for this format
+ return false;
+ }
+
+ // remove existing handler
+ this->removeHandler ( format );
+ }
+ else
+ {
+ // skip registration if there is already a handler registered for this format
+ if ( this->getFormatInfo ( format ) ) return false;
+ }
+
+ //
+ // register handler
+ //
+ XMPFileHandlerInfo handlerInfo ( format, flags, checkProc, handlerCTor );
+ mFolderHandlers->insert ( mFolderHandlers->end(), XMPFileHandlerTablePair ( format, handlerInfo ) );
+
+ return true;
+
+} // HandlerRegistry::registerFolderHandler
+
+// =================================================================================================
+
+bool HandlerRegistry::registerNormalHandler( XMP_FileFormat format,
+ XMP_OptionBits flags,
+ CheckFileFormatProc checkProc,
+ XMPFileHandlerCTor handlerCTor,
+ bool replaceExisting /*= false*/ )
+{
+ XMP_Assert ( format != kXMP_UnknownFile );
+
+ XMP_Assert ( ! (flags & kXMPFiles_HandlerOwnsFile) );
+ XMP_Assert ( ! (flags & kXMPFiles_FolderBasedFormat) );
+ XMP_Assert ( (flags & kXMPFiles_CanInjectXMP) ? (flags & kXMPFiles_CanExpand) : 1 );
+
+ if ( replaceExisting )
+ {
+ //
+ // Remember previous file handler for this format.
+ // Reject if there is already a replacement.
+ //
+ if( mReplacedHandlers->find( format ) == mReplacedHandlers->end() )
+ {
+ XMPFileHandlerInfo* standardHandler = this->getHandlerInfo( format );
+
+ if( standardHandler != NULL )
+ {
+ mReplacedHandlers->insert( mReplacedHandlers->end(), XMPFileHandlerTablePair( format, *standardHandler ) );
+ }
+ else
+ {
+ // skip registration if there is nothing to replace
+ return false;
+ }
+ }
+ else
+ {
+ // skip registration if there is already a replacing handler registered for this format
+ return false;
+ }
+
+ // remove existing handler
+ this->removeHandler ( format );
+ }
+ else
+ {
+ // skip registration if there is already a handler registered for this format
+ if ( this->getFormatInfo ( format ) ) return false;
+ }
+
+ //
+ // register handler
+ //
+ XMPFileHandlerInfo handlerInfo ( format, flags, checkProc, handlerCTor );
+ mNormalHandlers->insert ( mNormalHandlers->end(), XMPFileHandlerTablePair ( format, handlerInfo ) );
+ return true;
+
+} // HandlerRegistry::registerNormalHandler
+
+// =================================================================================================
+
+bool HandlerRegistry::registerOwningHandler( XMP_FileFormat format,
+ XMP_OptionBits flags,
+ CheckFileFormatProc checkProc,
+ XMPFileHandlerCTor handlerCTor,
+ bool replaceExisting /*= false*/ )
+{
+ XMP_Assert ( format != kXMP_UnknownFile );
+
+ XMP_Assert ( flags & kXMPFiles_HandlerOwnsFile );
+ XMP_Assert ( ! (flags & kXMPFiles_FolderBasedFormat) );
+ XMP_Assert ( (flags & kXMPFiles_CanInjectXMP) ? (flags & kXMPFiles_CanExpand) : 1 );
+
+ if ( replaceExisting )
+ {
+ //
+ // Remember previous file handler for this format.
+ // Reject if there is already a replacement.
+ //
+ if( mReplacedHandlers->find( format ) == mReplacedHandlers->end() )
+ {
+ XMPFileHandlerInfo* standardHandler = this->getHandlerInfo( format );
+
+ if( standardHandler != NULL )
+ {
+ mReplacedHandlers->insert( mReplacedHandlers->end(), XMPFileHandlerTablePair( format, *standardHandler ) );
+ }
+ else
+ {
+ // skip registration if there is nothing to replace
+ return false;
+ }
+ }
+ else
+ {
+ // skip registration if there is already a replacing handler registered for this format
+ return false;
+ }
+
+ // remove existing handler
+ this->removeHandler ( format );
+ }
+ else
+ {
+ // skip registration if there is already a handler registered for this format
+ if ( this->getFormatInfo ( format ) ) return false;
+ }
+
+ //
+ // register handler
+ //
+ XMPFileHandlerInfo handlerInfo ( format, flags, checkProc, handlerCTor );
+ mOwningHandlers->insert ( mOwningHandlers->end(), XMPFileHandlerTablePair ( format, handlerInfo ) );
+ return true;
+
+} // HandlerRegistry::registerOwningHandler
+
+// =================================================================================================
+
+void HandlerRegistry::removeHandler ( XMP_FileFormat format ) {
+
+ XMPFileHandlerTablePos handlerPos;
+
+ handlerPos = mFolderHandlers->find ( format );
+ if ( handlerPos != mFolderHandlers->end() ) {
+ mFolderHandlers->erase ( handlerPos );
+ XMP_Assert ( ! this->getFormatInfo ( format ) );
+ return;
+ }
+
+ handlerPos = mNormalHandlers->find ( format );
+ if ( handlerPos != mNormalHandlers->end() ) {
+ mNormalHandlers->erase ( handlerPos );
+ XMP_Assert ( ! this->getFormatInfo ( format ) );
+ return;
+ }
+
+ handlerPos = mOwningHandlers->find ( format );
+ if ( handlerPos != mOwningHandlers->end() ) {
+ mOwningHandlers->erase ( handlerPos );
+ XMP_Assert ( ! this->getFormatInfo ( format ) );
+ return;
+ }
+
+} // HandlerRegistry::removeHandler
+
+// =================================================================================================
+
+XMP_FileFormat HandlerRegistry::getFileFormat( const std::string & fileExt, bool addIfNotFound /*= false*/ )
+{
+ if ( ! fileExt.empty() ) {
+ for ( int i=0; kFileExtMap[i].format != 0; ++i ) {
+ if ( fileExt == kFileExtMap[i].ext ) return kFileExtMap[i].format;
+ }
+ }
+
+ return ResourceParser::getPluginFileFormat ( fileExt, addIfNotFound );
+}
+
+// =================================================================================================
+
+XMPFileHandlerInfo* HandlerRegistry::getHandlerInfo( XMP_FileFormat format )
+{
+ XMPFileHandlerTablePos handlerPos;
+
+ handlerPos = mFolderHandlers->find( format );
+
+ if( handlerPos != mFolderHandlers->end() )
+ {
+ return &(handlerPos->second);
+ }
+
+ handlerPos = mNormalHandlers->find ( format );
+
+ if( handlerPos != mNormalHandlers->end() )
+ {
+ return &(handlerPos->second);
+ }
+
+ handlerPos = mOwningHandlers->find ( format );
+
+ if( handlerPos != mOwningHandlers->end() )
+ {
+ return &(handlerPos->second);
+ }
+
+ return NULL;
+}
+
+// =================================================================================================
+
+XMPFileHandlerInfo* HandlerRegistry::getStandardHandlerInfo( XMP_FileFormat format )
+{
+ XMPFileHandlerTablePos handlerPos = mReplacedHandlers->find( format );
+
+ if( handlerPos != mReplacedHandlers->end() )
+ {
+ return &(handlerPos->second);
+ }
+ else
+ {
+ return this->getHandlerInfo( format );
+ }
+}
+
+// =================================================================================================
+
+bool HandlerRegistry::isReplaced( XMP_FileFormat format )
+{
+ return ( mReplacedHandlers->find( format ) != mReplacedHandlers->end() );
+}
+
+// =================================================================================================
+
+bool HandlerRegistry::getFormatInfo( XMP_FileFormat format, XMP_OptionBits* flags /*= 0*/ )
+{
+ if ( flags == 0 ) flags = &voidOptionBits;
+
+ XMPFileHandlerInfo* handler = this->getHandlerInfo( format );
+
+ if( handler != NULL )
+ {
+ *flags = handler->flags;
+ }
+
+ return ( handler != NULL );
+} // HandlerRegistry::getFormatInfo
+
+// =================================================================================================
+
+XMPFileHandlerInfo* HandlerRegistry::pickDefaultHandler ( XMP_FileFormat format, const std::string & fileExt )
+{
+ if ( format == kXMP_UnknownFile ) format = this->getFileFormat ( fileExt );
+ if ( format == kXMP_UnknownFile ) return 0;
+
+ XMPFileHandlerTablePos handlerPos;
+
+ handlerPos = mNormalHandlers->find ( format );
+ if ( handlerPos != mNormalHandlers->end() ) return &handlerPos->second;
+
+ handlerPos = mOwningHandlers->find ( format );
+ if ( handlerPos != mOwningHandlers->end() ) return &handlerPos->second;
+
+ handlerPos = mFolderHandlers->find ( format );
+ if ( handlerPos != mFolderHandlers->end() ) return &handlerPos->second;
+
+ return 0;
+}
+
+// =================================================================================================
+
+XMPFileHandlerInfo* HandlerRegistry::selectSmartHandler( XMPFiles* session, XMP_StringPtr clientPath, XMP_FileFormat format, XMP_OptionBits openFlags )
+{
+
+ // The normal case for selectSmartHandler is when OpenFile is given a string file path. All of
+ // the stages described below have slight special cases when OpenFile is given an XMP_IO object
+ // for client-managed I/O. In that case the only handlers considered are those for embedded XMP
+ // that do not need to own the file.
+ //
+ // There are 4 stages in finding a handler, ending at the first success:
+ // 1. If the client passes in a format, try that handler.
+ // 2. Try all of the folder-oriented handlers.
+ // 3. Try a file-oriented handler based on the file extension.
+ // 4. Try all of the file-oriented handlers.
+ //
+ // The most common case is almost certainly #3, so we want to get there quickly. Most of the
+ // time the client won't pass in a format, so #1 takes no time. The folder-oriented handler
+ // checks are preceded by minimal folder checks. These checks are meant to be fast in the
+ // failure case. The folder-oriented checks have to go before the general file-oriented checks
+ // because the client path might be to one of the inner files, and we might have a file-oriented
+ // handler for that kind of file, but we want to recognize the clip. More details are below.
+ //
+ // In brief, the folder-oriented formats use shallow trees with specific folder names and
+ // highly stylized file names. The user thinks of the tree as a collection of clips, each clip
+ // is stored as multiple files for video, audio, metadata, etc. The folder-oriented stage has
+ // to be first because there can be files in the structure that are also covered by a
+ // file-oriented handler.
+ //
+ // In the file-oriented case, the CheckProc should do as little as possible to determine the
+ // format, based on the actual file content. If that is not possible, use the format hint. The
+ // initial CheckProc calls (steps 1 and 3) has the presumed format in this->format, the later
+ // calls (step 4) have kXMP_UnknownFile there.
+ //
+ // The folder-oriented checks need to be well optimized since the formats are relatively rare,
+ // but have to go first and could require multiple file system calls to identify. We want to
+ // get to the first file-oriented guess as quickly as possible, that is the real handler most of
+ // the time.
+ //
+ // The folder-oriented handlers are for things like P2 and XDCAM that use files distributed in a
+ // well defined folder structure. Using a portion of P2 as an example:
+ // .../MyMovie
+ // CONTENTS
+ // CLIP
+ // 0001AB.XML
+ // 0002CD.XML
+ // VIDEO
+ // 0001AB.MXF
+ // 0002CD.MXF
+ // VOICE
+ // 0001AB.WAV
+ // 0002CD.WAV
+ //
+ // The user thinks of .../MyMovie as the container of P2 stuff, in this case containing 2 clips
+ // called 0001AB and 0002CD. The exact folder structure and file layout differs, but the basic
+ // concepts carry across all of the folder-oriented handlers.
+ //
+ // The client path can be a conceptual clip path like .../MyMovie/0001AB, or a full path to any
+ // of the contained files. For file paths we have to behave the same as the implied conceptual
+ // path, e.g. we don't want .../MyMovie/CONTENTS/VOICE/0001AB.WAV to invoke the WAV handler.
+ // There might also be a mapping from user friendly names to clip names (e.g. Intro to 0001AB).
+ // If so that is private to the handler and does not affect this code.
+ //
+ // In order to properly handle the file path input we have to look for the folder-oriented case
+ // before any of the file-oriented cases. And since these are relatively rare, hence fail most of
+ // the time, we have to get in and out fast in the not handled case. That is what we do here.
+ //
+ // The folder-oriented processing done here is roughly:
+ //
+ // 1. Get the state of the client path: does-not-exist, is-file, is-folder, is-other.
+ // 2. Reject is-folder and is-other, they can't possibly be a valid case.
+ // 3. For does-not-exist:
+ // 3a. Split the client path into a leaf component and root path.
+ // 3b. Make sure the root path names an existing folder.
+ // 3c. Make sure the root folder has a viable top level child folder (e.g. CONTENTS).
+ // 4. For is-file:
+ // 4a. Split the client path into a root path, grandparent folder, parent folder, and leaf name.
+ // 4b. Make sure the parent or grandparent has a viable name (e.g. CONTENTS).
+ // 5. Try the registered folder handlers.
+ //
+ // For the common case of "regular" files, we should only get as far as 3b. This is just 1 file
+ // system call to get the client path state and some string processing.
+
+ bool readOnly = XMP_OptionIsClear( openFlags, kXMPFiles_OpenForUpdate );
+
+ Host_IO::FileMode clientMode;
+ std::string rootPath;
+ std::string leafName;
+ std::string fileExt;
+ std::string emptyStr;
+
+ XMPFileHandlerInfo* handlerInfo = 0;
+ bool foundHandler = false;
+
+ if ( openFlags & kXMPFiles_ForceGivenHandler ) {
+ // We're being told to blindly use the handler for the given format and nothing else.
+ return this->pickDefaultHandler ( format, emptyStr ); // Picks based on just the format.
+ }
+
+ if ( session->UsesClientIO() ) {
+
+ XMP_Assert ( session->ioRef != 0 );
+ clientMode = Host_IO::kFMode_IsFile;
+
+ } else {
+
+ clientMode = Host_IO::GetFileMode( clientPath );
+ if ( (clientMode == Host_IO::kFMode_IsFolder) || (clientMode == Host_IO::kFMode_IsOther) ) return 0;
+
+ rootPath = clientPath;
+ XIO::SplitLeafName ( &rootPath, &leafName );
+
+ if ( leafName.empty() ) return 0;
+
+ if ( clientMode == Host_IO::kFMode_IsFile ) {
+ // Only extract the file extension for existing files. Non-existing files can only be
+ // logical clip names, and they don't have file extensions.
+ XIO::SplitFileExtension ( &leafName, &fileExt );
+ }
+
+ }
+
+ session->format = kXMP_UnknownFile; // Make sure it is preset for later checks.
+ session->openFlags = openFlags;
+
+ // If the client passed in a format, try that first.
+
+ if( format != kXMP_UnknownFile )
+ {
+ handlerInfo = this->pickDefaultHandler( format, emptyStr ); // Picks based on just the format.
+
+ if( handlerInfo != 0 )
+ {
+ if( ( session->ioRef == 0 ) && (! ( handlerInfo->flags & kXMPFiles_HandlerOwnsFile ) ) )
+ {
+ session->ioRef = XMPFiles_IO::New_XMPFiles_IO( clientPath, readOnly );
+
+ if ( session->ioRef == 0 ) return 0;
+ }
+
+ session->format = format; // ! Hack to tell the CheckProc session is an initial call.
+
+ if( handlerInfo->flags & kXMPFiles_FolderBasedFormat )
+ {
+#if 0
+ std::string gpName, parentName;
+ if ( clientMode == Host_IO::kFMode_IsFile ) {
+ XIO::SplitLeafName( &rootPath, &parentName );
+ XIO::SplitLeafName( &rootPath, &gpName );
+ if ( format != kXMP_XDCAM_FAMFile ) MakeUpperCase( &gpName ); // ! Save the original case for XDCAM-FAM.
+ MakeUpperCase( &parentName );
+ }
+ CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (handlerInfo->checkProc);
+ foundHandler = CheckProc ( handlerInfo->format, rootPath, gpName, parentName, leafName, session );
+#else
+ // *** Don't try here yet. These are messy, needing existence checking and path processing.
+ // *** CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (handlerInfo->checkProc);
+ // *** foundHandler = CheckProc ( handlerInfo->format, rootPath, gpName, parentName, leafName, session );
+ // *** Don't let OpenStrictly cause an early exit:
+ if( openFlags & kXMPFiles_OpenStrictly ) openFlags ^= kXMPFiles_OpenStrictly;
+#endif
+ }
+ else
+ {
+ bool tryThisHandler = true;
+
+ if( session->UsesClientIO() )
+ {
+ if ( (handlerInfo->flags & kXMPFiles_UsesSidecarXMP) ||
+ (handlerInfo->flags & kXMPFiles_HandlerOwnsFile) ) tryThisHandler = false;
+ }
+
+ if( tryThisHandler )
+ {
+ CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc);
+ foundHandler = CheckProc ( format, clientPath, session->ioRef, session );
+ }
+ }
+
+ XMP_Assert( foundHandler || (session->tempPtr == 0) );
+
+ if ( foundHandler ) return handlerInfo;
+
+ handlerInfo = 0; // ! Clear again for later use.
+ }
+
+ if ( openFlags & kXMPFiles_OpenStrictly ) return 0;
+
+ }
+
+#if EnableDynamicMediaHandlers // All of the folder handlers are for dynamic media.
+
+ // Try the folder handlers if appropriate.
+
+ if( session->UsesLocalIO() )
+ {
+ XMP_Assert ( handlerInfo == 0 );
+ XMP_Assert ( (clientMode == Host_IO::kFMode_IsFile) || (clientMode == Host_IO::kFMode_DoesNotExist) );
+
+ std::string gpName, parentName;
+
+ if( clientMode == Host_IO::kFMode_DoesNotExist )
+ {
+ // 3. For does-not-exist:
+ // 3a. Split the client path into a leaf component and root path.
+ // 3b. Make sure the root path names an existing folder.
+ // 3c. Make sure the root folder has a viable top level child folder.
+
+ // ! This does "return 0" on failure, the file does not exist so a normal file handler can't apply.
+
+ if ( Host_IO::GetFileMode ( rootPath.c_str() ) != Host_IO::kFMode_IsFolder ) return 0;
+
+ session->format = checkTopFolderName ( rootPath );
+
+ if ( session->format == kXMP_UnknownFile ) return 0;
+
+ handlerInfo = this->tryFolderHandlers( session->format, rootPath, gpName, parentName, leafName, session ); // ! Parent and GP are empty.
+
+ return handlerInfo; // ! Return found handler or 0.
+ }
+
+ XMP_Assert ( clientMode == Host_IO::kFMode_IsFile );
+
+ // 4. For is-file:
+ // 4a. Split the client path into root, grandparent, parent, and leaf.
+ // 4b. Make sure the parent or grandparent has a viable name.
+
+ // ! Don't "return 0" on failure, this has to fall through to the normal file handlers.
+
+ XIO::SplitLeafName( &rootPath, &parentName );
+ XIO::SplitLeafName( &rootPath, &gpName );
+ std::string origGPName ( gpName ); // ! Save the original case for XDCAM-FAM.
+ MakeUpperCase( &parentName );
+ MakeUpperCase( &gpName );
+
+ session->format = checkParentFolderNames( rootPath, gpName, parentName, leafName );
+
+ if( session->format != kXMP_UnknownFile )
+ {
+ if( (session->format == kXMP_XDCAM_FAMFile) &&
+ ( (parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB") ) )
+ {
+ // ! The standard says Clip/Edit/Sub, but we just shifted to upper case.
+ gpName = origGPName; // ! XDCAM-FAM has just 1 level of inner folder, preserve the "MyMovie" case.
+ }
+
+ handlerInfo = tryFolderHandlers ( session->format, rootPath, gpName, parentName, leafName, session );
+ if ( handlerInfo != 0 ) return handlerInfo;
+
+ }
+ }
+
+#endif // EnableDynamicMediaHandlers
+
+ // Try an initial file-oriented handler based on the extension.
+
+ if( session->UsesLocalIO() )
+ {
+ handlerInfo = pickDefaultHandler ( kXMP_UnknownFile, fileExt ); // Picks based on just the extension.
+
+ if( handlerInfo != 0 )
+ {
+ if( (session->ioRef == 0) && (! (handlerInfo->flags & kXMPFiles_HandlerOwnsFile)) )
+ {
+ session->ioRef = XMPFiles_IO::New_XMPFiles_IO ( clientPath, readOnly );
+ if ( session->ioRef == 0 ) return 0;
+ }
+ else if( (session->ioRef != 0) && (handlerInfo->flags & kXMPFiles_HandlerOwnsFile) )
+ {
+ delete session->ioRef; // Close is implicit in the destructor.
+ session->ioRef = 0;
+ }
+
+ session->format = handlerInfo->format; // ! Hack to tell the CheckProc this is an initial call.
+ CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc);
+ foundHandler = CheckProc ( handlerInfo->format, clientPath, session->ioRef, session );
+ XMP_Assert ( foundHandler || (session->tempPtr == 0) );
+
+ if ( foundHandler ) return handlerInfo;
+ }
+ }
+
+ // Search the handlers that don't want to open the file themselves.
+
+ if( session->ioRef == 0 )
+ {
+ session->ioRef = XMPFiles_IO::New_XMPFiles_IO ( clientPath, readOnly );
+ if ( session->ioRef == 0 ) return 0;
+ }
+
+ XMPFileHandlerTablePos handlerPos = mNormalHandlers->begin();
+
+ for( ; handlerPos != mNormalHandlers->end(); ++handlerPos )
+ {
+ session->format = kXMP_UnknownFile; // ! Hack to tell the CheckProc this is not an initial call.
+ handlerInfo = &handlerPos->second;
+ CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc);
+ foundHandler = CheckProc ( handlerInfo->format, clientPath, session->ioRef, session );
+ XMP_Assert ( foundHandler || (session->tempPtr == 0) );
+ if ( foundHandler ) return handlerInfo;
+ }
+
+ // Search the handlers that do want to open the file themselves.
+
+ if( session->UsesLocalIO() )
+ {
+ delete session->ioRef; // Close is implicit in the destructor.
+ session->ioRef = 0;
+ handlerPos = mOwningHandlers->begin();
+
+ for( ; handlerPos != mOwningHandlers->end(); ++handlerPos )
+ {
+ session->format = kXMP_UnknownFile; // ! Hack to tell the CheckProc this is not an initial call.
+ handlerInfo = &handlerPos->second;
+ CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc);
+ foundHandler = CheckProc ( handlerInfo->format, clientPath, session->ioRef, session );
+ XMP_Assert ( foundHandler || (session->tempPtr == 0) );
+ if ( foundHandler ) return handlerInfo;
+ }
+ }
+
+ // Failed to find a smart handler.
+
+ return 0;
+
+} // HandlerRegistry::selectSmartHandler
+
+// =================================================================================================
+
+#if EnableDynamicMediaHandlers
+
+XMPFileHandlerInfo* HandlerRegistry::tryFolderHandlers( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parentObj )
+{
+ bool foundHandler = false;
+ XMPFileHandlerInfo * handlerInfo = 0;
+ XMPFileHandlerTablePos handlerPos;
+
+ // We know we're in a possible context for a folder-oriented handler, so try them.
+
+ if( format != kXMP_UnknownFile )
+ {
+
+ // Have an explicit format, pick that or nothing.
+ handlerPos = mFolderHandlers->find ( format );
+
+ if( handlerPos != mFolderHandlers->end() )
+ {
+ handlerInfo = &handlerPos->second;
+ CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (handlerInfo->checkProc);
+ foundHandler = CheckProc ( handlerInfo->format, rootPath, gpName, parentName, leafName, parentObj );
+ XMP_Assert ( foundHandler || (parentObj->tempPtr == 0) );
+ }
+ }
+ else
+ {
+ // Try all of the folder handlers.
+ for( handlerPos = mFolderHandlers->begin(); handlerPos != mFolderHandlers->end(); ++handlerPos )
+ {
+ handlerInfo = &handlerPos->second;
+ CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (handlerInfo->checkProc);
+ foundHandler = CheckProc ( handlerInfo->format, rootPath, gpName, parentName, leafName, parentObj );
+ XMP_Assert ( foundHandler || (parentObj->tempPtr == 0) );
+ if ( foundHandler ) break; // ! Exit before incrementing handlerPos.
+ }
+ }
+
+ if ( ! foundHandler ) handlerInfo = 0;
+
+ return handlerInfo;
+}
+
+#endif
+
+// =================================================================================================
+
+#if EnableDynamicMediaHandlers
+
+/*static*/ XMP_FileFormat HandlerRegistry::checkTopFolderName( const std::string & rootPath )
+{
+ // This is called when the input path to XMPFiles::OpenFile does not name an existing file (or
+ // existing anything). We need to quickly decide if this might be a logical path for a folder
+ // handler. See if the root contains the top content folder for any of the registered folder
+ // handlers. This check does not have to be precise, the handler will do that. This does have to
+ // be fast.
+ //
+ // Since we don't have many folder handlers, this is simple hardwired code.
+
+ std::string childPath = rootPath;
+ childPath += kDirChar;
+ size_t baseLen = childPath.size();
+
+ // P2 .../MyMovie/CONTENTS/<group>/... - only check for CONTENTS/CLIP
+ childPath += "CONTENTS";
+ childPath += kDirChar;
+ childPath += "CLIP";
+ if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFolder ) return kXMP_P2File;
+ childPath.erase ( baseLen );
+
+ // XDCAM-FAM .../MyMovie/<group>/... - only check for Clip and MEDIAPRO.XML
+ childPath += "Clip"; // ! Yes, mixed case.
+ if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFolder ) {
+ childPath.erase ( baseLen );
+ childPath += "MEDIAPRO.XML";
+ if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFile ) return kXMP_XDCAM_FAMFile;
+ }
+ childPath.erase ( baseLen );
+
+ // XDCAM-SAM .../MyMovie/PROAV/<group>/... - only check for PROAV/CLPR
+ childPath += "PROAV";
+ childPath += kDirChar;
+ childPath += "CLPR";
+ if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFolder ) return kXMP_XDCAM_SAMFile;
+ childPath.erase ( baseLen );
+
+ // XDCAM-EX .../MyMovie/BPAV/<group>/... - check for BPAV/CLPR
+ childPath += "BPAV";
+ childPath += kDirChar;
+ childPath += "CLPR";
+ if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFolder ) return kXMP_XDCAM_EXFile;
+ childPath.erase ( baseLen );
+
+ // Sony HDV .../MyMovie/VIDEO/HVR/<file>.<ext> - check for VIDEO/HVR
+ childPath += "VIDEO";
+ childPath += kDirChar;
+ childPath += "HVR";
+ if ( Host_IO::GetFileMode ( childPath.c_str() ) == Host_IO::kFMode_IsFolder ) return kXMP_SonyHDVFile;
+ childPath.erase ( baseLen );
+
+ return kXMP_UnknownFile;
+
+} // CheckTopFolderName
+
+#endif
+
+// =================================================================================================
+
+#if EnableDynamicMediaHandlers
+
+/*static*/ XMP_FileFormat HandlerRegistry::checkParentFolderNames( const std::string& rootPath,
+ const std::string& gpName,
+ const std::string& parentName,
+ const std::string& leafName )
+{
+ IgnoreParam ( parentName );
+
+ // This is called when the input path to XMPFiles::OpenFile names an existing file. We need to
+ // quickly decide if this might be inside a folder-handler's structure. See if the containing
+ // folders might match any of the registered folder handlers. This check does not have to be
+ // precise, the handler will do that. This does have to be fast.
+ //
+ // Since we don't have many folder handlers, this is simple hardwired code. Note that the caller
+ // has already shifted the names to upper case.
+
+ // P2 .../MyMovie/CONTENTS/<group>/<file>.<ext> - check CONTENTS and <group>
+ if ( (gpName == "CONTENTS") && CheckP2ContentChild ( parentName ) ) return kXMP_P2File;
+
+ // XDCAM-EX .../MyMovie/BPAV/CLPR/<clip>/<file>.<ext> - check for BPAV/CLPR
+ // ! This must be checked before XDCAM-SAM because both have a "CLPR" grandparent.
+ if ( gpName == "CLPR" ) {
+ std::string tempPath, greatGP;
+ tempPath = rootPath;
+ XIO::SplitLeafName ( &tempPath, &greatGP );
+ MakeUpperCase ( &greatGP );
+ if ( greatGP == "BPAV" ) return kXMP_XDCAM_EXFile;
+ }
+
+ // XDCAM-FAM .../MyMovie/<group>/<file>.<ext> - check that <group> is CLIP, or EDIT, or SUB
+ // ! The standard says Clip/Edit/Sub, but the caller has already shifted to upper case.
+ if ( (parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB") ) return kXMP_XDCAM_FAMFile;
+
+ // XDCAM-SAM .../MyMovie/PROAV/<group>/<clip>/<file>.<ext> - check for PROAV and CLPR or EDTR
+ if ( (gpName == "CLPR") || (gpName == "EDTR") ) {
+ std::string tempPath, greatGP;
+ tempPath = rootPath;
+ XIO::SplitLeafName ( &tempPath, &greatGP );
+ MakeUpperCase ( &greatGP );
+ if ( greatGP == "PROAV" ) return kXMP_XDCAM_SAMFile;
+ }
+
+ // Sony HDV .../MyMovie/VIDEO/HVR/<file>.<ext> - check for VIDEO and HVR
+ if ( (gpName == "VIDEO") && (parentName == "HVR") ) return kXMP_SonyHDVFile;
+
+ return kXMP_UnknownFile;
+
+} // CheckParentFolderNames
+
+#endif
diff --git a/XMPFiles/source/HandlerRegistry.h b/XMPFiles/source/HandlerRegistry.h
new file mode 100644
index 0000000..a146bfd
--- /dev/null
+++ b/XMPFiles/source/HandlerRegistry.h
@@ -0,0 +1,238 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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.
+// =================================================================================================
+
+#ifndef _HANDLERREGISTRY_h_
+#define _HANDLERREGISTRY_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XMPFiles_IO.hpp"
+
+#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h"
+#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h"
+#include "source/Endian.h"
+
+namespace Common
+{
+
+/**
+ File handler data
+ */
+struct XMPFileHandlerInfo
+{
+ XMP_FileFormat format;
+ XMP_OptionBits flags;
+ void* checkProc;
+ XMPFileHandlerCTor handlerCTor;
+
+ XMPFileHandlerInfo() : format(0), flags(0), checkProc(0), handlerCTor(0)
+ {};
+
+ XMPFileHandlerInfo( XMP_FileFormat _format,
+ XMP_OptionBits _flags,
+ CheckFileFormatProc _checkProc,
+ XMPFileHandlerCTor _handlerCTor )
+ : format(_format), flags(_flags), checkProc((void*)_checkProc), handlerCTor(_handlerCTor)
+ {};
+
+ XMPFileHandlerInfo( XMP_FileFormat _format,
+ XMP_OptionBits _flags,
+ CheckFolderFormatProc _checkProc,
+ XMPFileHandlerCTor _handlerCTor )
+ : format(_format), flags(_flags), checkProc((void*)_checkProc), handlerCTor(_handlerCTor)
+ {};
+};
+
+/**
+ The singleton class HandlerRegistry is responsible to manage all file handler.
+ It registers file handlers during initialization time and provides functionality
+ to select a file handler based on a given file format.
+*/
+
+class HandlerRegistry
+{
+public:
+ static HandlerRegistry& getInstance();
+ static void terminate();
+
+#if EnableDynamicMediaHandlers
+ static XMP_FileFormat checkTopFolderName( const std::string & rootPath );
+ static XMP_FileFormat checkParentFolderNames( const std::string& rootPath,
+ const std::string& gpName,
+ const std::string& parentName,
+ const std::string& leafName );
+#endif
+
+public:
+ /**
+ * Register all file handler
+ */
+ void initialize();
+
+ /**
+ * Register a single folder based file handler.
+ *
+ * @param format File format identifier
+ * @param flags Flags
+ * @param checkProc Check format function pointer
+ * @param handlerCTor Factory function pointer
+ * @param replaceExisting Replace an already existing handler
+ */
+ bool registerFolderHandler( XMP_FileFormat format,
+ XMP_OptionBits flags,
+ CheckFolderFormatProc checkProc,
+ XMPFileHandlerCTor handlerCTor,
+ bool replaceExisting = false );
+
+ /**
+ * Register a single normal file handler.
+ *
+ * @param format File format identifier
+ * @param flags Flags
+ * @param checkProc Check format function pointer
+ * @param handlerCTor Factory function pointer
+ * @param replaceExisting Replace an already existing handler
+ */
+ bool registerNormalHandler( XMP_FileFormat format,
+ XMP_OptionBits flags,
+ CheckFileFormatProc checkProc,
+ XMPFileHandlerCTor handlerCTor,
+ bool replaceExisting = false );
+
+ /**
+ * Register a single owning file handler.
+ *
+ * @param format File format identifier
+ * @param flags Flags
+ * @param checkProc Check format function pointer
+ * @param handlerCTor Factory function pointer
+ * @param replaceExisting Replace an already existing handler
+ */
+ bool registerOwningHandler( XMP_FileFormat format,
+ XMP_OptionBits flags,
+ CheckFileFormatProc checkProc,
+ XMPFileHandlerCTor handlerCTor,
+ bool replaceExisting = false );
+
+ // Remove a handler. Does nothing if no such handler exists.
+ void removeHandler ( XMP_FileFormat format );
+
+ /**
+ * Get file format identifier for filename extension.
+ *
+ * @param fileExt Filename extension
+ * @param addIfNotFound If true if handler doesn't exists then add now
+ */
+ XMP_FileFormat getFileFormat( const std::string & fileExt, bool addIfNotFound = false );
+
+ /**
+ * Get handler flags for file format.
+ *
+ * @param format File format identifier
+ * @param flags Return handler flag
+ * @return True on success
+ */
+ bool getFormatInfo( XMP_FileFormat format, XMP_OptionBits* flags = NULL );
+
+ /**
+ * Get handler information for passed format.
+ * The returned file handler is the default handler. I.e. that handler
+ * that is used when called from outside using the XMPFiles API.
+ *
+ * @param format File format identifier
+ * @return Return handler info if available, otherwise NULL
+ */
+ XMPFileHandlerInfo* getHandlerInfo( XMP_FileFormat format );
+
+ /**
+ * Get file handler information of the standard file handler for the
+ * file format identifier.
+ * If there is a replacement for this format then the standard handler
+ * is the replaced handler. Otherwise the standard handler and the
+ * default handler are the same.
+ *
+ * @param format File format identifier
+ * @return Return handler info if available, otherwise NULL
+ */
+ XMPFileHandlerInfo* getStandardHandlerInfo( XMP_FileFormat format );
+
+ /**
+ * Return true if there is a replacement for the file format
+ */
+ bool isReplaced( XMP_FileFormat format );
+
+ /**
+ * Select file handler based on passed information and setup XMPFiles instance with related data.
+ *
+ * @param session XMPFiles instance
+ * @param clientPath Path to file
+ * @param format File format identifier
+ * @param openFlags Flags
+ * @return File handler structure
+ */
+ XMPFileHandlerInfo* selectSmartHandler( XMPFiles* session, XMP_StringPtr clientPath, XMP_FileFormat format, XMP_OptionBits openFlags );
+
+private:
+ /**
+ * Return default file handler for file format identifier or filename extension
+ *
+ * @param format File format identifier
+ * @param fileExt Filename extension
+ * @return File handler structure for passed format/extension
+ */
+ XMPFileHandlerInfo* pickDefaultHandler ( XMP_FileFormat format, const std::string & fileExt );
+
+#if EnableDynamicMediaHandlers
+ /**
+ * Try to find folder based file handler.
+ *
+ * @param format File format identifier
+ * @param rootPath Path to root folder
+ * @param gpName Grand parent folder name
+ * @param parentName Parent folder name
+ * @param leafName File name
+ * @param parentObj XMPFiles instance
+ * @return File handler structure
+ */
+ XMPFileHandlerInfo* tryFolderHandlers( XMP_FileFormat format,
+ const std::string& rootPath,
+ const std::string& gpName,
+ const std::string& parentName,
+ const std::string& leafName,
+ XMPFiles* parentObj );
+#endif
+
+private:
+ /**
+ * ctor/dtor
+ */
+ HandlerRegistry();
+ ~HandlerRegistry();
+
+private:
+ typedef std::map <XMP_FileFormat, XMPFileHandlerInfo> XMPFileHandlerTable;
+ typedef XMPFileHandlerTable::iterator XMPFileHandlerTablePos;
+ typedef std::pair <XMP_FileFormat, XMPFileHandlerInfo> XMPFileHandlerTablePair;
+
+ XMPFileHandlerTable* mFolderHandlers; // The directory-oriented handlers.
+ XMPFileHandlerTable* mNormalHandlers; // The normal file-oriented handlers.
+ XMPFileHandlerTable* mOwningHandlers; // The file-oriented handlers that "own" the file.
+
+ XMPFileHandlerTable* mReplacedHandlers; // All file handler that where replaced by a later one
+
+ static HandlerRegistry* sInstance; // singleton instance
+};
+
+} // Common
+
+#endif // _HANDLERREGISTRY_h_
diff --git a/XMPFiles/source/NativeMetadataSupport/IMetadata.cpp b/XMPFiles/source/NativeMetadataSupport/IMetadata.cpp
new file mode 100644
index 0000000..40a864e
--- /dev/null
+++ b/XMPFiles/source/NativeMetadataSupport/IMetadata.cpp
@@ -0,0 +1,206 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "XMPFiles/source/NativeMetadataSupport/IMetadata.h"
+
+//-----------------------------------------------------------------------------
+//
+// IMetadata::IMetadata(...)
+//
+// Purpose: ctor/dtor
+//
+//-----------------------------------------------------------------------------
+
+IMetadata::IMetadata()
+: mDirty(false)
+{
+}
+
+IMetadata::~IMetadata()
+{
+ for( ValueMap::iterator iter = mValues.begin(); iter != mValues.end(); iter++ )
+ {
+ delete iter->second;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// IMetadata::parse(...)
+//
+// Purpose: Parses the given memory block and creates a data model representation
+// We assume that any metadata block is < 4GB so that we can savely use
+// 32bit sizes.
+// Throws exceptions if parsing is not possible
+//
+//-----------------------------------------------------------------------------
+
+void IMetadata::parse( const XMP_Uns8* input, XMP_Uns64 size )
+{
+ XMP_Throw ( "Method not implemented", kXMPErr_Unimplemented );
+}
+
+//-----------------------------------------------------------------------------
+//
+// IMetadata::parse(...)
+//
+// Purpose: Parses the given file and creates a data model representation
+// Throws exceptions if parsing is not possible
+//
+//-----------------------------------------------------------------------------
+
+void IMetadata::parse( XMP_IO* input )
+{
+ XMP_Throw ( "Method not implemented", kXMPErr_Unimplemented );
+}
+
+//-----------------------------------------------------------------------------
+//
+// IMetadata::serialize(...)
+//
+// Purpose: Serializes the data model to a memory block.
+// The method creates a buffer and pass it to the parameter 'buffer'.
+// The callee of the method is responsible to delete the buffer later on.
+// We assume that any metadata block is < 4GB so that we can savely use
+// 32bit sizes.
+// Throws exceptions if serializing is not possible
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns64 IMetadata::serialize( XMP_Uns8** buffer )
+{
+ XMP_Throw ( "Method not implemented", kXMPErr_Unimplemented );
+}
+
+//-----------------------------------------------------------------------------
+//
+// IMetadata::hasChanged(...)
+//
+// Purpose: Return true if any values of this container was modified
+//
+//-----------------------------------------------------------------------------
+
+bool IMetadata::hasChanged() const
+{
+ bool isDirty = mDirty;
+
+ for( ValueMap::const_iterator iter=mValues.begin(); ! isDirty && iter != mValues.end(); iter++ )
+ {
+ isDirty = iter->second->hasChanged();
+ }
+
+ return isDirty;
+}
+
+//-----------------------------------------------------------------------------
+//
+// IMetadata::resetChanges(...)
+//
+// Purpose: Reset dirty flag
+//
+//-----------------------------------------------------------------------------
+
+void IMetadata::resetChanges()
+{
+ mDirty = false;
+
+ for( ValueMap::iterator iter=mValues.begin(); iter != mValues.end(); iter++ )
+ {
+ iter->second->resetChanged();
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// IMetadata::isEmpty(...)
+//
+// Purpose: Return true if the no metadata are available in this container
+//
+//-----------------------------------------------------------------------------
+
+bool IMetadata::isEmpty() const
+{
+ return mValues.empty();
+}
+
+//-----------------------------------------------------------------------------
+//
+// IMetadata::deleteValue(...)
+//
+// Purpose: Remove value for passed identifier
+//
+//-----------------------------------------------------------------------------
+
+void IMetadata::deleteValue( XMP_Uns32 id )
+{
+ ValueMap::iterator iterator = mValues.find( id );
+
+ if( iterator != mValues.end() )
+ {
+ delete iterator->second;
+ mValues.erase( iterator );
+
+ mDirty = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// IMetadata::deleteAll(...)
+//
+// Purpose: Remove all stored values
+//
+//-----------------------------------------------------------------------------
+
+void IMetadata::deleteAll()
+{
+ mDirty = ( mValues.size() > 0 );
+
+ for( ValueMap::iterator iter = mValues.begin(); iter != mValues.end(); iter++ )
+ {
+ delete iter->second;
+ }
+
+ mValues.clear();
+}
+
+//-----------------------------------------------------------------------------
+//
+// IMetadata::valueExists(...)
+//
+// Purpose: Return true if an value for the passed identifier exists
+//
+//-----------------------------------------------------------------------------
+
+bool IMetadata::valueExists( XMP_Uns32 id ) const
+{
+ ValueMap::const_iterator iterator = mValues.find( id );
+
+ return ( iterator != mValues.end() );
+}
+
+//-----------------------------------------------------------------------------
+//
+// IMetadata::valueChanged(...)
+//
+// Purpose: Return true if the value for the passed identifier was changed
+//
+//-----------------------------------------------------------------------------
+
+bool IMetadata::valueChanged( XMP_Uns32 id ) const
+{
+ ValueMap::const_iterator iterator = mValues.find( id );
+
+ if( iterator != mValues.end() )
+ {
+ return iterator->second->hasChanged();
+ }
+
+ return false;
+}
diff --git a/XMPFiles/source/NativeMetadataSupport/IMetadata.h b/XMPFiles/source/NativeMetadataSupport/IMetadata.h
new file mode 100644
index 0000000..1a66c86
--- /dev/null
+++ b/XMPFiles/source/NativeMetadataSupport/IMetadata.h
@@ -0,0 +1,334 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _IMetadata_h_
+#define _IMetadata_h_
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+#include "source/XMP_LibUtils.hpp"
+#include "XMPFiles/source/NativeMetadataSupport/ValueObject.h"
+
+#include <map>
+
+/**
+ * The class IMetadata is supposed to store any unique metadata.
+ * It provides a generic interface to store any data types or arrays of any data types.
+ * The requirements for used datatypes are defined by the class ValueObject and its derived classes.
+ * For each single value as well as for the container as a whole modification and existing state is provided.
+ * It also provids methods to parse a byte block to distinct values or to serialize values to a byte block.
+ * IMetadata is an abstract class due to the method isEmptyValue(...). Derived classes needs to implement this
+ * method. It has to define when a certain value is "empty", since "empty" values are not stored but removed
+ * from the container.
+ *
+ */
+class IMetadata
+{
+public:
+ /**
+ * ctor/dtor
+ */
+ IMetadata();
+ virtual ~IMetadata();
+
+
+ /**
+ * Parses the given memory block and creates a data model representation
+ * We assume that any metadata block is < 4GB so that we can savely use 32bit sizes.
+ * Throws exceptions if parsing is not possible
+ *
+ * @param input The byte buffer to parse
+ * @param size Size of the given byte buffer
+ */
+ virtual void parse( const XMP_Uns8* input, XMP_Uns64 size );
+
+
+ /**
+ * Parses the given file and creates a data model representation
+ * Throws exceptions if parsing is not possible
+ *
+ * @param input The file to parse
+ */
+ virtual void parse( XMP_IO* input );
+
+
+ /**
+ * Serializes the data model to a memory block.
+ * The method creates a buffer and pass it to the parameter 'buffer'. The callee of
+ * the method is responsible to delete the buffer later on.
+ * We assume that any metadata block is < 4GB so that we can savely use 32bit sizes.
+ * Throws exceptions if serializing is not possible
+ *
+ * @param buffer Buffer that gets filled with serialized data
+ * @param size Size of passed in buffer
+ *
+ * @ return Buffer size
+ */
+ virtual XMP_Uns64 serialize( XMP_Uns8** buffer );
+
+ /**
+ * Return true if any values of this container was modified
+ */
+ virtual bool hasChanged() const;
+
+ /**
+ * Reset dirty flag
+ */
+ virtual void resetChanges();
+
+ /**
+ * Return true if the no metadata are available in this container
+ */
+ virtual bool isEmpty() const;
+
+ /**
+ * Set value for passed identifier
+ *
+ * @param id Identifier of value
+ * @param value New value of passed data type
+ */
+ template<class T> void setValue( XMP_Uns32 id, const T& value );
+
+ /**
+ * Set array for passed identifier
+ *
+ * @param id Identifier of value
+ * @param value Pointer to the array
+ * @param bufferSize Number of array elements
+ */
+ template<class T> void setArray( XMP_Uns32 id, const T* buffer, XMP_Uns32 numElements);
+
+ /**
+ * Return value for passed identifier.
+ * If the value doesn't exists an exception is thrown!
+ *
+ * @param id Identifier of value
+ * @return Value of passed data type
+ */
+ template<class T> const T& getValue( XMP_Uns32 id ) const;
+
+ /**
+ * Return array for passed identifier
+ * If the array doesn't exists an exception is thrown!
+ *
+ * @param id Identifier of value
+ * @param outBuffer Array
+ * @return Number of array elements
+ */
+ template<class T> const T* const getArray( XMP_Uns32 id, XMP_Uns32& outSize ) const;
+
+ /**
+ * Remove value for passed identifier
+ *
+ * @param id Identifier of value
+ */
+ virtual void deleteValue( XMP_Uns32 id );
+
+ /**
+ * Remove all stored values
+ */
+ virtual void deleteAll();
+
+ /**
+ * Return true if an value for the passed identifier exists
+ *
+ * @param id Identifier of value
+ */
+ virtual bool valueExists( XMP_Uns32 id ) const;
+
+ /**
+ * Return true if the value for the passed identifier was changed
+ *
+ * @param id Identifier of value
+ */
+ virtual bool valueChanged( XMP_Uns32 id ) const;
+
+protected:
+ /**
+ * Is the value of the passed ValueObject that belongs to the given id "empty"?
+ * Derived classes are required to implement this method and define "empty"
+ * for its values.
+ * Needed for setValue and setArray.
+ *
+ * @param id Identifier of passed value
+ * @param valueObj Value to inspect
+ *
+ * @return True if the value is "empty"
+ */
+ virtual bool isEmptyValue( XMP_Uns32 id, ValueObject& valueObj ) = 0;
+
+private:
+ // Operators hidden on purpose
+ IMetadata( const IMetadata& ) {};
+ IMetadata& operator=( const IMetadata& ) { return *this; };
+
+protected:
+ typedef std::map<XMP_Uns32, ValueObject*> ValueMap;
+ ValueMap mValues;
+ bool mDirty;
+};
+
+template<class T> void IMetadata::setValue( XMP_Uns32 id, const T& value )
+{
+ TValueObject<T>* valueObj = NULL;
+
+ //
+ // find existing value for id
+ //
+ ValueMap::iterator iterator = mValues.find( id );
+
+ if( iterator != mValues.end() )
+ {
+ //
+ // value exists, set new value
+ //
+ valueObj = dynamic_cast<TValueObject<T>*>( iterator->second );
+
+ if( valueObj != NULL )
+ {
+ valueObj->setValue( value );
+ }
+ else
+ {
+ XMP_Throw ( "Invalid identifier", kXMPErr_InternalFailure );
+ }
+ }
+ else
+ {
+ //
+ // value doesn't exists yet and is not "empty"
+ // so add a new value to the map
+ //
+ valueObj = new TValueObject<T>( value );
+ mValues[id] = valueObj;
+
+ mDirty = true; // the new created value isn't dirty, but the container becomes dirty
+ }
+
+ //
+ // check if the value is "empty"
+ //
+ if( this->isEmptyValue( id, *valueObj ) )
+ {
+ //
+ // value is "empty", delete it
+ //
+ this->deleteValue( id );
+ }
+}
+
+template<class T> void IMetadata::setArray( XMP_Uns32 id, const T* buffer, XMP_Uns32 numElements )
+{
+ TArrayObject<T>* arrayObj = NULL;
+
+ //
+ // find existing value for id
+ //
+ ValueMap::iterator iterator = mValues.find( id );
+
+ if( iterator != mValues.end() )
+ {
+ //
+ // value exists, set new value
+ //
+ arrayObj = dynamic_cast<TArrayObject<T>*>( iterator->second );
+
+ if( arrayObj != NULL )
+ {
+ arrayObj->setArray( buffer, numElements );
+ }
+ else
+ {
+ XMP_Throw ( "Invalid identifier", kXMPErr_InternalFailure );
+ }
+ }
+ else
+ {
+ //
+ // value doesn't exists yet and is not "empty"
+ // so add a new value to the map
+ //
+ arrayObj = new TArrayObject<T>( buffer, numElements );
+ mValues[id] = arrayObj;
+
+ mDirty = true; // the new created value isn't dirty, but the container becomes dirty
+ }
+
+ //
+ // check if the value is "empty"
+ //
+ if( this->isEmptyValue( id, *arrayObj ) )
+ {
+ //
+ // value is "empty", delete it
+ //
+ this->deleteValue( id );
+ }
+}
+
+template<class T> const T& IMetadata::getValue( XMP_Uns32 id ) const
+{
+ ValueMap::const_iterator iterator = mValues.find( id );
+
+ if( iterator != mValues.end() )
+ {
+ //
+ // values exists, return value string
+ //
+ TValueObject<T>* valueObj = dynamic_cast<TValueObject<T>*>( iterator->second );
+
+ if( valueObj != NULL )
+ {
+ return valueObj->getValue();
+ }
+ else
+ {
+ XMP_Throw ( "Invalid identifier", kXMPErr_InternalFailure );
+ }
+ }
+ else
+ {
+ //
+ // value for id doesn't exists, throw an exception
+ //
+ XMP_Throw ( "Invalid identifier", kXMPErr_InternalFailure );
+ }
+}
+
+template<class T> const T* const IMetadata::getArray( XMP_Uns32 id, XMP_Uns32& outSize ) const
+{
+ ValueMap::const_iterator iterator = mValues.find( id );
+
+ if( iterator != mValues.end() )
+ {
+ //
+ // values exists, return value string
+ //
+ TArrayObject<T>* arrayObj = dynamic_cast<TArrayObject<T>*>( iterator->second );
+
+ if( arrayObj != NULL )
+ {
+ return arrayObj->getArray( outSize );
+ }
+ else
+ {
+ XMP_Throw ( "Invalid identifier", kXMPErr_InternalFailure );
+ }
+ }
+ else
+ {
+ //
+ // value for id doesn't exists, throw an exception
+ //
+ XMP_Throw ( "Invalid identifier", kXMPErr_InternalFailure );
+ }
+}
+
+#endif
diff --git a/XMPFiles/source/NativeMetadataSupport/IReconcile.cpp b/XMPFiles/source/NativeMetadataSupport/IReconcile.cpp
new file mode 100644
index 0000000..dd34725
--- /dev/null
+++ b/XMPFiles/source/NativeMetadataSupport/IReconcile.cpp
@@ -0,0 +1,493 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "XMPFiles/source/NativeMetadataSupport/IReconcile.h"
+
+#include "XMPFiles/source/NativeMetadataSupport/MetadataSet.h"
+#include "XMPFiles/source/NativeMetadataSupport/IMetadata.h"
+
+#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
+
+//-----------------------------------------------------------------------------
+//
+// WAVEReconcile::importNativeToXMP(...)
+//
+// Purpose: [static] Import native metadata container into XMP.
+// This method is supposed to import all native metadata values
+// that are listed in the property table from the IMetadata
+// instance to the SXMPMeta instance.
+//
+//-----------------------------------------------------------------------------
+
+bool IReconcile::importNativeToXMP( SXMPMeta& outXMP, const IMetadata& nativeMeta, const MetadataPropertyInfo* propertyInfo, bool xmpPriority )
+{
+ bool changed = false;
+
+ std::string xmpValue;
+ XMP_Uns32 index = 0;
+
+ do
+ {
+ if( propertyInfo[index].mXMPSchemaNS != NULL )
+ {
+ bool xmpPropertyExists = false;
+
+ //
+ // need to know if the XMP property exists either
+ // if a priority needs to be considered or if
+ // the value isn't set by a native value
+ //
+ switch( propertyInfo[index].mXMPType )
+ {
+ case kXMPType_Simple:
+ {
+ xmpPropertyExists = outXMP.DoesPropertyExist( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName );
+ }
+ break;
+
+ case kXMPType_Localized:
+ {
+ std::string actualLang;
+ bool result = outXMP.GetLocalizedText( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName, "" , "x-default" , &actualLang, NULL, NULL );
+ // If existing and the appropriate one
+ xmpPropertyExists = result && (actualLang == "x-default");
+ }
+ break;
+
+ case kXMPType_Array:
+ case kXMPType_OrderedArray:
+ {
+ xmpPropertyExists = outXMP.DoesArrayItemExist( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName, 1 );
+ }
+ break;
+
+ default:
+ {
+ XMP_Throw( "Unknown XMP data type", kXMPErr_InternalFailure );
+ }
+ break;
+ }
+
+ //
+ // import native property if there is no priority or
+ // the XMP property doesn't exist yet
+ //
+ if( ! propertyInfo[index].mConsiderPriority || ! xmpPriority || ! xmpPropertyExists )
+ {
+ //
+ // if the native property exists
+ //
+ if( nativeMeta.valueExists( propertyInfo[index].mMetadataID ) )
+ {
+ //
+ // prepare native property value for XMP
+ // depending on the native data type
+ //
+ xmpValue.erase();
+
+ switch( propertyInfo[index].mNativeType )
+ {
+ case kNativeType_Str:
+ {
+ xmpValue = nativeMeta.getValue<std::string>( propertyInfo[index].mMetadataID );
+ }
+ break;
+
+ case kNativeType_StrASCII:
+ {
+ convertToASCII( nativeMeta.getValue<std::string>( propertyInfo[index].mMetadataID ), xmpValue );
+ }
+ break;
+
+ case kNativeType_StrLocal:
+ {
+ ReconcileUtils::NativeToUTF8( nativeMeta.getValue<std::string>( propertyInfo[index].mMetadataID ), xmpValue );
+ }
+ break;
+
+ case kNativeType_StrUTF8:
+ {
+ ReconcileUtils::NativeToUTF8( nativeMeta.getValue<std::string>( propertyInfo[index].mMetadataID ), xmpValue );
+ }
+ break;
+
+ case kNativeType_Uns64:
+ {
+ SXMPUtils::ConvertFromInt64( nativeMeta.getValue<XMP_Uns64>( propertyInfo[index].mMetadataID ), "%llu", &xmpValue );
+ }
+ break;
+
+ case kNativeType_Uns32:
+ {
+ SXMPUtils::ConvertFromInt( nativeMeta.getValue<XMP_Uns32>( propertyInfo[index].mMetadataID ), "%lu", &xmpValue );
+ }
+ break;
+
+ case kNativeType_Int32:
+ {
+ SXMPUtils::ConvertFromInt( nativeMeta.getValue<XMP_Int32>( propertyInfo[index].mMetadataID ), 0 /* default format */, &xmpValue );
+ }
+ break;
+
+ case kNativeType_Uns16:
+ {
+ SXMPUtils::ConvertFromInt( nativeMeta.getValue<XMP_Uns16>( propertyInfo[index].mMetadataID ), "%lu", &xmpValue );
+ }
+ break;
+
+ default:
+ {
+ XMP_Throw( "Unknown native data type", kXMPErr_InternalFailure );
+ }
+ break;
+ };
+
+ if( ! xmpValue.empty() )
+ {
+ //
+ // set the new XMP value depending on the
+ // XMP data type
+ //
+ switch( propertyInfo[index].mXMPType )
+ {
+ case kXMPType_Localized:
+ {
+ outXMP.SetLocalizedText( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName, NULL, "x-default", xmpValue.c_str() );
+ }
+ break;
+
+ case kXMPType_Array:
+ {
+ // Overwrite any existing array
+ outXMP.DeleteProperty( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName );
+ outXMP.AppendArrayItem( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName, kXMP_PropArrayIsUnordered, xmpValue.c_str(), kXMP_NoOptions );
+ }
+ break;
+
+ case kXMPType_OrderedArray:
+ {
+ // Overwrite any existing array
+ outXMP.DeleteProperty( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName );
+ outXMP.AppendArrayItem( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName, kXMP_PropArrayIsOrdered, xmpValue.c_str(), kXMP_NoOptions );
+ }
+ break;
+
+ default:
+ {
+ outXMP.SetProperty( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName, xmpValue.c_str() );
+ }
+ break;
+ }
+
+ changed = true;
+ }
+ }
+ else if( propertyInfo[index].mDeleteXMPIfNoNative && xmpPropertyExists )
+ {
+ //
+ // native value doesn't exists, delete the XMP property
+ //
+ outXMP.DeleteProperty( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName );
+ changed = xmpPropertyExists;
+ }
+ }
+ }
+
+ index++;
+
+ } while( propertyInfo[index].mXMPSchemaNS != NULL );
+
+ return changed;
+}
+
+//-----------------------------------------------------------------------------
+//
+// WAVEReconcile::exportXMPToNative(...)
+//
+// Purpose: [static] Export XMP values to native metadata container.
+// This method is supposed to export all native metadata values
+// that are listed in the property table from the XMP container
+// to the IMetadata instance.
+//
+//-----------------------------------------------------------------------------
+
+bool IReconcile::exportXMPToNative( IMetadata& outNativeMeta, SXMPMeta& inXMP, const MetadataPropertyInfo* propertyInfo )
+{
+ std::string xmpValue;
+ XMP_Uns32 index = 0;
+
+ do
+ {
+ if( propertyInfo[index].mXMPSchemaNS != NULL && propertyInfo[index].mExportPolicy != kExport_Never )
+ {
+ bool xmpPropertyExists = false;
+
+ //
+ // get value from XMP depending on
+ // the XMP data type
+ //
+ switch( propertyInfo[index].mXMPType )
+ {
+ case kXMPType_Localized:
+ {
+ std::string lang;
+ xmpPropertyExists = inXMP.GetLocalizedText( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName, NULL, "x-default", &lang, &xmpValue, 0 );
+ }
+ break;
+
+ case kXMPType_Array:
+ case kXMPType_OrderedArray:
+ {
+ // TOTRACK currently only the first array item is used
+ if( inXMP.CountArrayItems( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName ) > 0 )
+ {
+ xmpPropertyExists = inXMP.GetArrayItem( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName, 1, &xmpValue, 0 );
+ }
+ }
+ break;
+
+ default:
+ {
+ xmpPropertyExists = inXMP.GetProperty( propertyInfo[index].mXMPSchemaNS, propertyInfo[index].mXMPPropName, &xmpValue, 0 );
+ }
+ break;
+ }
+
+ //
+ // convert xmp value and set native property
+ // depending on the native data type and the export policy
+ //
+ if( xmpPropertyExists &&
+ ( propertyInfo[index].mExportPolicy != kExport_InjectOnly ||
+ ! outNativeMeta.valueExists( propertyInfo[index].mMetadataID ) ) )
+ {
+
+ switch( propertyInfo[index].mNativeType )
+ {
+ case kNativeType_StrASCII:
+ {
+ std::string ascii;
+ convertToASCII( xmpValue, ascii );
+ outNativeMeta.setValue<std::string>( propertyInfo[index].mMetadataID, ascii );
+ }
+ break;
+
+ case kNativeType_Str:
+ case kNativeType_StrUTF8:
+ {
+ outNativeMeta.setValue<std::string>( propertyInfo[index].mMetadataID, xmpValue );
+ }
+ break;
+
+ case kNativeType_StrLocal:
+ {
+ std::string value;
+
+ try
+ {
+ ReconcileUtils::UTF8ToLocal( xmpValue.c_str(), xmpValue.size(), &value );
+ outNativeMeta.setValue<std::string>( propertyInfo[index].mMetadataID, value );
+ }
+ catch( XMP_Error& e )
+ {
+ if( e.GetID() != kXMPErr_Unavailable )
+ {
+ // rethrow exception if it wasn't caused
+ // by missing encoding functionality (UNIX)
+ throw e;
+ }
+ }
+ }
+ break;
+
+ case kNativeType_Uns64:
+ {
+ XMP_Int64 value = 0;
+ bool error = false;
+
+ try
+ {
+ value = SXMPUtils::ConvertToInt64( xmpValue );
+ }
+ catch( XMP_Error& e )
+ {
+ if ( e.GetID() != kXMPErr_BadParam )
+ {
+ // rethrow exception if it wasn't caused by an
+ // invalid parameter for the conversion
+ throw e;
+ }
+ error = true;
+ }
+
+ // Only write the value if it could be converted to a number and has a positive value
+ if( ! error && value >= 0 )
+ {
+ outNativeMeta.setValue<XMP_Uns64>( propertyInfo[index].mMetadataID, static_cast<XMP_Uns64>(value) );
+ }
+ }
+ break;
+
+ case kNativeType_Uns32:
+ {
+ XMP_Int32 value = 0;
+ bool error = false;
+
+ try
+ {
+ value = SXMPUtils::ConvertToInt( xmpValue );
+ }
+ catch( XMP_Error& e )
+ {
+ if ( e.GetID() != kXMPErr_BadParam )
+ {
+ // rethrow exception if it wasn't caused by an
+ // invalid parameter for the conversion
+ throw e;
+ }
+ error = true;
+ }
+
+ // Only write the value if it could be converted to a number and has a positive value
+ if( ! error && value >= 0 )
+ {
+ outNativeMeta.setValue<XMP_Uns32>( propertyInfo[index].mMetadataID, static_cast<XMP_Uns32>(value) );
+ }
+ }
+ break;
+
+ case kNativeType_Int32:
+ {
+ XMP_Int32 value = 0;
+ bool error = false;
+
+ try
+ {
+ value = SXMPUtils::ConvertToInt( xmpValue );
+ }
+ catch( XMP_Error& e )
+ {
+ if ( e.GetID() != kXMPErr_BadParam )
+ {
+ // rethrow exception if it wasn't caused by an
+ // invalid parameter for the conversion
+ throw e;
+ }
+ error = true;
+ }
+
+ // Only write the value if it could be converted to a number
+ if( ! error )
+ {
+ outNativeMeta.setValue<XMP_Int32>( propertyInfo[index].mMetadataID, static_cast<XMP_Int32>(value) );
+ }
+ }
+ break;
+
+ case kNativeType_Uns16:
+ {
+ XMP_Int32 value = 0;
+ bool error = false;
+
+ try
+ {
+ value = SXMPUtils::ConvertToInt( xmpValue );
+ }
+ catch( XMP_Error& e )
+ {
+ if ( e.GetID() != kXMPErr_BadParam )
+ {
+ // rethrow exception if it wasn't caused by an
+ // invalid parameter for the conversion
+ throw e;
+ }
+ error = true;
+ }
+
+ // Only write the value if it could be converted to a number and has a positive value
+ if( ! error && value >= 0 )
+ {
+ outNativeMeta.setValue<XMP_Uns16>( propertyInfo[index].mMetadataID, static_cast<XMP_Uns16>(value) );
+ }
+ }
+ break;
+
+ default:
+ {
+ XMP_Throw( "Unknown native data type", kXMPErr_InternalFailure );
+ }
+ break;
+ }
+ }
+ else
+ {
+ if( propertyInfo[index].mExportPolicy == kExport_Always )
+ {
+ //
+ // delete native value if the corresponding XMP value doesn't exists
+ //
+ outNativeMeta.deleteValue( propertyInfo[index].mMetadataID );
+ }
+ }
+ }
+
+ index++;
+
+ } while( propertyInfo[index].mXMPSchemaNS != NULL );
+
+ return outNativeMeta.hasChanged();
+}
+
+//-----------------------------------------------------------------------------
+//
+// IReconcile::convertToASCII(...)
+//
+// Purpose: Converts input string to an ascii output string
+// - terminates at first 0
+// - replaces all non ascii with 0x3F ('?')
+//
+//-----------------------------------------------------------------------------
+
+void IReconcile::convertToASCII( const std::string& input, std::string& output )
+{
+ output.erase();
+ output.reserve( input.length() );
+
+ bool isUTF8 = ReconcileUtils::IsUTF8( input.c_str(), input.length() );
+
+ XMP_StringPtr iter = input.c_str();
+
+ for ( XMP_Uns32 i=0; i < input.length(); i++ )
+ {
+ XMP_Uns8 c = (XMP_Uns8) iter[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, '?' );
+ }
+ else // regular valid ascii. 1 byte.
+ {
+ output.append( 1, iter[i] );
+ }
+ }
+} // convertToASCII
+
diff --git a/XMPFiles/source/NativeMetadataSupport/IReconcile.h b/XMPFiles/source/NativeMetadataSupport/IReconcile.h
new file mode 100644
index 0000000..af8646d
--- /dev/null
+++ b/XMPFiles/source/NativeMetadataSupport/IReconcile.h
@@ -0,0 +1,139 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _IReconcile_h_
+#define _IReconcile_h_
+
+#include <string>
+
+#ifndef TXMP_STRING_TYPE
+ #define TXMP_STRING_TYPE std::string
+#endif
+
+#include "XMP.hpp"
+
+class MetadataSet;
+class IMetadata;
+
+enum XMPPropertyType
+{
+ kXMPType_Simple,
+ // Structs can be treated as Simple if the whole path is given to the API
+ kXMPType_Localized,
+ // Unordered array (bag)
+ kXMPType_Array,
+ // Ordered array (seq)
+ kXMPType_OrderedArray
+};
+
+/** Types that describe how the native property is interpreted */
+enum MetadataPropertyType
+{
+ kNativeType_Str, // Take the value as is
+ kNativeType_StrASCII, // Treat it as ASCII, convert if necessary
+ kNativeType_StrUTF8, // Treat it as UTF-8, convert if necessary
+ kNativeType_StrLocal, // Use local encoding
+ kNativeType_Uns64,
+ kNativeType_Uns32,
+ kNativeType_Int32,
+ kNativeType_Uns16
+};
+
+/** Types that describe how an XMP property is exported to native Metadata */
+enum ExportPolicy
+{
+ 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 MetadataPropertyInfo
+{
+ XMP_StringPtr mXMPSchemaNS;
+ XMP_StringPtr mXMPPropName;
+ XMP_Uns32 mMetadataID;
+ MetadataPropertyType mNativeType;
+ XMPPropertyType mXMPType;
+ // If true, delete the XMP property if the native one does not exist on import
+ bool mDeleteXMPIfNoNative;
+ // If true, any existing XMP has higher priority on import
+ bool mConsiderPriority;
+ ExportPolicy mExportPolicy;
+};
+
+class IReconcile
+{
+public:
+ virtual ~IReconcile() {};
+ /**
+ * Reconciles metadata from legacy formats into XMP.
+ *
+ * @param outXMP the reconciliated XMP packet contains all XMP and legacy metadata,
+ * it is created and owned by the the caller.
+ * @param inMetaData contains all legacy containers that are relevant for the processed file format AND
+ * which actually contain data.
+ * If a container is not included in the set, it is omitted by the reconciliation method.
+ * Note: inMetaData#getXMP() and outXMP can be the same object
+ * @return XMP has been changed (true) or not (false)
+ *
+ * Throws exception if reconciliation is not possible.
+ */
+ virtual XMP_Bool importToXMP( SXMPMeta& outXMP, const MetadataSet& inMetaData ) = 0;
+
+ /**
+ * Dissolves metadata from the XMP object to legacy formats.
+ *
+ * @param outMetaData contains all legacy containers that are relevant for the processed file format
+ * (and the file handler is interested in).
+ * If a container is not included in the set, it is omitted by the dissolve method.
+ * Note: outMetaData#getXMP() and inXMP can be the same object
+ * @param inXMP the XMP packet that contains all XMP and legacy metadata,
+ * it is created and owned by the the caller. The legacy data is distributed into the legacy containers.
+ * @return legacy has been changed (true) or not (false)
+ *
+ * Throws exception if dissolving is not possible.
+ */
+ virtual XMP_Bool exportFromXMP( MetadataSet& outMetaData, SXMPMeta& inXMP ) = 0;
+
+protected:
+ /**
+ Import native metadata container into XMP.
+ This method is supposed to import all native metadata values that are listed in the property table
+ from the IMetadata instance to the SXMPMeta instance.
+
+ @param outXMP Target XMP container
+ @param nativeMeta Native metadata container
+ @param propertyInfo Property table that lists all values that are supposed to be imported
+ @param xmpPriority Pass true, if an existing XMP value has higher priority than the native metadata
+
+ @return true if any XMP properties were changed
+ */
+ static bool importNativeToXMP( SXMPMeta& outXMP, const IMetadata& nativeMeta, const MetadataPropertyInfo* propertyInfo, bool xmpPriority );
+
+ /**
+ Export XMP values to native metadata container.
+ This method is supposed to export all native metadata values that are listed in the property table
+ from the XMP container to the IMetadata instance.
+
+ @param outNativeMeta Target native metadata container
+ @param inXMP XMP container
+ @param propertyInfo Property table that lists all values that are supposed to be exported
+
+ @return true if any native metadata value were changed
+ */
+ static bool exportXMPToNative( IMetadata& outNativeMeta, SXMPMeta& inXMP, const MetadataPropertyInfo* propertyInfo );
+
+ // Converts input string to an ascii output string
+ // - terminates at first 0
+ // - replaces all non ascii with 0x3F ('?')
+ static void convertToASCII( const std::string& input, std::string& output );
+};
+
+#endif
diff --git a/XMPFiles/source/NativeMetadataSupport/MetadataSet.cpp b/XMPFiles/source/NativeMetadataSupport/MetadataSet.cpp
new file mode 100644
index 0000000..f6389f4
--- /dev/null
+++ b/XMPFiles/source/NativeMetadataSupport/MetadataSet.cpp
@@ -0,0 +1,127 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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 "XMPFiles/source/NativeMetadataSupport/MetadataSet.h"
+#include "source/XMP_LibUtils.hpp"
+
+//-----------------------------------------------------------------------------
+//
+// MetadataSet::MetadataSet(...)
+//
+// Purpose: ctor/dtor
+//
+//-----------------------------------------------------------------------------
+
+MetadataSet::MetadataSet()
+: mMeta ( NULL )
+{
+}
+
+MetadataSet::~MetadataSet()
+{
+ delete mMeta;
+}
+
+//-----------------------------------------------------------------------------
+//
+// MetadataSet::append(...)
+//
+// Purpose: Append metadata container
+//
+//-----------------------------------------------------------------------------
+
+void MetadataSet::append( IMetadata* meta )
+{
+ if( mMeta == NULL )
+ {
+ mMeta = new std::vector<IMetadata*>;
+ }
+
+ mMeta->push_back( meta );
+}
+
+//-----------------------------------------------------------------------------
+//
+// MetadataSet::removeAt(...)
+//
+// Purpose: Remove metadata container at passed position
+//
+//-----------------------------------------------------------------------------
+
+void MetadataSet::removeAt( XMP_Uns32 pos )
+{
+ if( mMeta != NULL && pos < mMeta->size() )
+ {
+ mMeta->erase( mMeta->begin() + pos );
+ }
+ else
+ {
+ XMP_Throw( "Index out of range.", kXMPErr_BadIndex );
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// MetadataSet::remove(...)
+//
+// Purpose: Remove the last metadata container inside the vector
+//
+//-----------------------------------------------------------------------------
+
+void MetadataSet::remove()
+{
+ if( mMeta != NULL )
+ {
+ mMeta->pop_back();
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// MetadataSet::length(...)
+//
+// Purpose: Return the number of stored metadata container
+//
+//-----------------------------------------------------------------------------
+
+XMP_Uns32 MetadataSet::length() const
+{
+ XMP_Uns32 ret = 0;
+
+ if( mMeta != NULL )
+ {
+ ret = (XMP_Uns32)mMeta->size();
+ }
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+//
+// MetadataSet::getAt(...)
+//
+// Purpose: Return metadata container of passed position.
+//
+//-----------------------------------------------------------------------------
+
+IMetadata* MetadataSet::getAt( XMP_Uns32 pos ) const
+{
+ IMetadata* ret = NULL;
+
+ if( mMeta != NULL && pos < mMeta->size() )
+ {
+ ret = mMeta->at(pos);
+ }
+ else
+ {
+ XMP_Throw( "Index out of range.", kXMPErr_BadIndex );
+ }
+
+ return ret;
+}
diff --git a/XMPFiles/source/NativeMetadataSupport/MetadataSet.h b/XMPFiles/source/NativeMetadataSupport/MetadataSet.h
new file mode 100644
index 0000000..62bb8b5
--- /dev/null
+++ b/XMPFiles/source/NativeMetadataSupport/MetadataSet.h
@@ -0,0 +1,100 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _MetadataSet_h_
+#define _MetadataSet_h_
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+#include <vector>
+
+/**
+ The class MetadataSet acts as a container for any metadata that base on
+ the interface IMeta.
+*/
+
+class IMetadata;
+
+class MetadataSet
+{
+ public:
+ /**
+ ctor/dtor
+ */
+ MetadataSet ();
+ ~MetadataSet ();
+
+ /**
+ Append metadata container
+
+ @param meta Metadata container
+ */
+ void append ( IMetadata* meta );
+
+ /**
+ Remove metadata container at passed position
+
+ @param pos Position inside the vector of metadata
+ */
+ void removeAt ( XMP_Uns32 pos );
+
+ /**
+ Remove the last metadata container inside the vector
+ */
+ void remove ();
+
+ /**
+ Return the number of stored metadata container
+
+ @return length of vector
+ */
+ XMP_Uns32 length () const;
+
+ /**
+ Return metadata container of passed position.
+
+ @param pos Position inside of the vector of metadata
+ */
+ IMetadata* getAt ( XMP_Uns32 pos ) const;
+
+ /**
+ Return metadata container of passed type
+
+ @return stored metadata container of type T
+ */
+ template<class T> T* get () const;
+
+ private:
+ std::vector<IMetadata*>* mMeta;
+ typedef std::vector<IMetadata*>::iterator MetaIterator;
+
+}; // MetadataSet
+
+template<class T> T* MetadataSet::get() const
+{
+ T* ret = NULL;
+
+ if( mMeta != NULL )
+ {
+ for( MetaIterator iter = mMeta->begin(); iter != mMeta->end(); iter++ )
+ {
+ T* tmp = dynamic_cast<T*>( *iter );
+
+ if( tmp != NULL )
+ {
+ ret = tmp;
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
+#endif
diff --git a/XMPFiles/source/NativeMetadataSupport/ValueObject.h b/XMPFiles/source/NativeMetadataSupport/ValueObject.h
new file mode 100644
index 0000000..cc8b444
--- /dev/null
+++ b/XMPFiles/source/NativeMetadataSupport/ValueObject.h
@@ -0,0 +1,143 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2010 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.
+// =================================================================================================
+
+#ifndef _ValueObject_h_
+#define _ValueObject_h_
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+
+/**
+ * The class ValueObject acts as a base class for the two generic classes
+ * TValueObject and TValueArray.
+ * The overall purpose of these classes is to store a single value (or array)
+ * of any data types and a modification state. The modification state is
+ * always set if the existing value has changed. It is not set if a new value
+ * is set that is equal to the existing value.
+ * If a ValueObject based instance is created or a new value is passed to an
+ * instance the actual value gets copied.
+ *
+ * So there're some requirements of possible data types:
+ * - data type needs to provide a relational operator
+ * - data type needs to provide an assign operator
+ */
+
+/**
+ * The class ValueObject is the base class for the following generic classes.
+ * It provids common functionality for handle the modification state.
+ * This class can't be used directly!
+ */
+class ValueObject
+{
+public:
+ virtual ~ValueObject() {}
+
+ inline bool hasChanged() const { return mDirty; }
+ inline void resetChanged() { mDirty = false; }
+
+protected:
+ ValueObject() : mDirty(false) {}
+
+protected:
+ bool mDirty;
+};
+
+/**
+ * The generic class TValueObject is supposed to store values of any data types.
+ * It is based on the class ValueObject and support modification state functionality.
+ * See above for the requirements of possible data types.
+ */
+template <class T> class TValueObject : public ValueObject
+{
+public:
+ TValueObject( const T& value ) : mValue(value) {}
+ ~TValueObject() {}
+
+ inline const T& getValue() const { return mValue; }
+ inline void setValue( const T& value ) { mDirty = !( mValue == value ); mValue = value; }
+
+private:
+ T mValue;
+};
+
+/**
+ * The generic class TArrayObject is supposed to store arrays of any data types.
+ * It is based on the class ValueObject and supports modification state functionality.
+ * See above for the requirements of possible data types.
+ */
+template <class T> class TArrayObject : public ValueObject
+{
+public:
+ TArrayObject( const T* buffer, XMP_Uns32 bufferSize );
+ ~TArrayObject();
+
+ inline const T* const getArray( XMP_Uns32& outSize ) const { outSize = mSize; return mArray; }
+ void setArray( const T* buffer, XMP_Uns32 numElements);
+
+private:
+ T* mArray;
+ XMP_Uns32 mSize;
+};
+
+template<class T> inline TArrayObject<T>::TArrayObject( const T* buffer, XMP_Uns32 bufferSize )
+: mArray(NULL), mSize(0)
+{
+ this->setArray( buffer, bufferSize );
+ mDirty = false;
+}
+
+template<class T> inline TArrayObject<T>::~TArrayObject()
+{
+ if( mArray != NULL )
+ {
+ delete[] mArray;
+ }
+}
+
+template<class T> inline void TArrayObject<T>::setArray( const T* buffer, XMP_Uns32 numElements )
+{
+ if( buffer != NULL && numElements > 0 )
+ {
+ bool doSet = true;
+
+ if( mArray != NULL && mSize == numElements )
+ {
+ doSet = ( memcmp( mArray, buffer, numElements*sizeof(T) ) != 0 );
+ }
+
+ if( doSet )
+ {
+ if( mArray != NULL )
+ {
+ delete[] mArray;
+ }
+
+ mArray = new T[numElements];
+ mSize = numElements;
+
+ memcpy( mArray, buffer, numElements*sizeof(T) );
+
+ mDirty = true;
+ }
+ }
+ else
+ {
+ mDirty = ( mArray != NULL );
+
+ if( mArray != NULL )
+ {
+ delete[] mArray;
+ }
+
+ mArray = NULL;
+ mSize = 0;
+ }
+}
+
+#endif
diff --git a/XMPFiles/source/PluginHandler/FileHandler.h b/XMPFiles/source/PluginHandler/FileHandler.h
new file mode 100644
index 0000000..16e4c6b
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/FileHandler.h
@@ -0,0 +1,101 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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.
+// =================================================================================================
+
+#ifndef PLUGINHANDLER_H
+#define PLUGINHANDLER_H
+#include "Module.h"
+
+namespace XMP_PLUGIN
+{
+
+/** @struct CheckFormat
+ * @brief Record of byte sequences.
+ *
+ * It is a static information of the file handler provided in the resource file, if the format
+ * can be identified by one or more sequences of fixed bytes at a fixed location within the format.
+ */
+struct CheckFormat
+{
+ XMP_Int64 mOffset;
+ XMP_Uns32 mLength;
+ std::string mByteSeq;
+
+ CheckFormat(): mOffset( 0 ), mLength( 0 ) { }
+
+ inline void clear()
+ {
+ mOffset = 0; mLength = 0; mByteSeq.clear();
+ }
+
+ inline bool empty()
+ {
+ return ( mLength == 0 || mByteSeq.empty() );
+ }
+};
+
+/** @class FileHandler
+ * @brief File handler exposed through plugin architecture.
+ *
+ * At the initialization time of XMPFiles only static information from all the avalbale plugins
+ * is populated by creating instance of this class FileHandler. It's loaded later when it's actually
+ * required to get information from the format.
+ */
+class FileHandler
+{
+public:
+
+ FileHandler(std::string & uid, XMP_OptionBits handlerFlags, FileHandlerType type, ModuleSharedPtr module):
+ mVersion(0), mUID(uid), mHandlerFlags(handlerFlags), mType(type), mModule(module), mOverwrite(false) {}
+
+ virtual ~FileHandler(){}
+
+ inline XMP_Int64 getVersion() const { return mVersion; }
+ inline void setVersion( XMP_Uns32 version ) { mVersion = version; }
+
+ inline const std::string& getUID() const { return mUID; }
+ inline XMP_OptionBits getHandlerFlags() const { return mHandlerFlags; }
+ inline void setHandlerFlags( XMP_OptionBits flags ) { mHandlerFlags = flags; }
+
+ inline XMP_OptionBits getSerializeOption() const { return mSerializeOption; }
+ inline void setSerializeOption( XMP_OptionBits option ) { mSerializeOption = option; }
+
+ inline bool getOverwriteHandler() const { return mOverwrite; }
+ inline void setOverwriteHandler( bool overwrite ) { mOverwrite = overwrite; }
+
+ inline FileHandlerType getHandlerType() const { return mType; }
+ inline void setHandlerType( FileHandlerType type ) { mType = type; }
+
+ inline bool load() { return mModule->load(); }
+ inline ModuleSharedPtr getModule() const { return mModule; }
+
+ inline void addCheckFormat( const CheckFormat & checkFormat ) { mCheckFormatVec.push_back( checkFormat ); }
+ inline XMP_Uns32 getCheckFormatSize() const { return (XMP_Uns32) mCheckFormatVec.size(); }
+ inline CheckFormat getCheckFormat( XMP_Uns32 index ) const
+ {
+ CheckFormat checkFormat;
+ if( index < mCheckFormatVec.size() )
+ checkFormat = mCheckFormatVec[index];
+ return checkFormat;
+ }
+
+private:
+ typedef std::vector<CheckFormat> CheckFormatVec;
+
+ CheckFormatVec mCheckFormatVec;
+ XMP_Uns32 mVersion;
+ std::string mUID;
+ XMP_OptionBits mHandlerFlags;
+ XMP_OptionBits mSerializeOption;
+ bool mOverwrite;
+ FileHandlerType mType;
+ ModuleSharedPtr mModule;
+};
+
+} //namespace XMP_PLUGIN
+#endif //PLUGINHANDLER_H
diff --git a/XMPFiles/source/PluginHandler/FileHandlerInstance.cpp b/XMPFiles/source/PluginHandler/FileHandlerInstance.cpp
new file mode 100644
index 0000000..42de1a4
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/FileHandlerInstance.cpp
@@ -0,0 +1,106 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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 "FileHandlerInstance.h"
+
+namespace XMP_PLUGIN
+{
+
+FileHandlerInstance::FileHandlerInstance ( SessionRef object, FileHandlerSharedPtr handler, XMPFiles * _parent ):
+XMPFileHandler( _parent ), mObject( object ), mHandler( handler )
+{
+ this->handlerFlags = mHandler->getHandlerFlags();
+ this->stdCharForm = kXMP_Char8Bit;
+ PluginManager::addHandlerInstance( this->mObject, this );
+}
+
+FileHandlerInstance::~FileHandlerInstance()
+{
+ WXMP_Error error;
+ mHandler->getModule()->getPluginAPIs()->mTerminateSessionProc( this->mObject, &error );
+ PluginManager::removeHandlerInstance( this->mObject );
+ CheckError( error );
+}
+
+bool FileHandlerInstance::GetFileModDate ( XMP_DateTime * modDate )
+{
+ bool ok;
+ WXMP_Error error;
+ GetSessionFileModDateProc wGetFileModDate = mHandler->getModule()->getPluginAPIs()->mGetFileModDateProc;
+ wGetFileModDate ( this->mObject, &ok, modDate, &error );
+ CheckError ( error );
+ return ok;
+}
+
+void FileHandlerInstance::CacheFileData()
+{
+ if( this->containsXMP ) return;
+
+ WXMP_Error error;
+ XMP_StringPtr xmpStr = NULL;
+ mHandler->getModule()->getPluginAPIs()->mCacheFileDataProc( this->mObject, this->parent->ioRef, &xmpStr, &error );
+
+ if( error.mErrorID != kXMPErr_NoError )
+ {
+ if ( xmpStr != 0 ) free( (void*) xmpStr );
+ throw XMP_Error( kXMPErr_InternalFailure, error.mErrorMsg );
+ }
+
+ if( xmpStr != NULL )
+ {
+ this->xmpPacket.assign( xmpStr );
+ free( (void*) xmpStr ); // It should be freed as documentation of mCacheFileDataProc says so.
+ }
+ this->containsXMP = true;
+}
+
+void FileHandlerInstance::ProcessXMP()
+{
+ if( !this->containsXMP || this->processedXMP ) return;
+ this->processedXMP = true;
+
+ SXMPUtils::RemoveProperties ( &this->xmpObj, 0, 0, kXMPUtil_DoAllProperties );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+
+ WXMP_Error error;
+ if( mHandler->getModule()->getPluginAPIs()->mImportToXMPProc )
+ mHandler->getModule()->getPluginAPIs()->mImportToXMPProc( this->mObject, this->xmpObj.GetInternalRef(), &error );
+ CheckError( error );
+}
+
+void FileHandlerInstance::UpdateFile ( bool doSafeUpdate )
+{
+ if ( !this->needsUpdate || this->xmpPacket.size() == 0 ) return;
+
+ WXMP_Error error;
+ if( mHandler->getModule()->getPluginAPIs()->mExportFromXMPProc )
+ mHandler->getModule()->getPluginAPIs()->mExportFromXMPProc( this->mObject, this->xmpObj.GetInternalRef(), &error );
+ CheckError( error );
+
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, mHandler->getSerializeOption() );
+
+ mHandler->getModule()->getPluginAPIs()->mUpdateFileProc( this->mObject, this->parent->ioRef, doSafeUpdate, this->xmpPacket.c_str(), &error );
+ CheckError( error );
+ this->needsUpdate = false;
+}
+
+void FileHandlerInstance::WriteTempFile( XMP_IO* tempRef )
+{
+ WXMP_Error error;
+ if( mHandler->getModule()->getPluginAPIs()->mExportFromXMPProc )
+ mHandler->getModule()->getPluginAPIs()->mExportFromXMPProc( this->mObject, this->xmpObj.GetInternalRef(), &error );
+ CheckError( error );
+
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, mHandler->getSerializeOption() );
+
+ mHandler->getModule()->getPluginAPIs()->mWriteTempFileProc( this->mObject, this->parent->ioRef, tempRef, this->xmpPacket.c_str(), &error );
+ CheckError( error );
+}
+
+} //namespace XMP_PLUGIN
diff --git a/XMPFiles/source/PluginHandler/FileHandlerInstance.h b/XMPFiles/source/PluginHandler/FileHandlerInstance.h
new file mode 100644
index 0000000..5cf2420
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/FileHandlerInstance.h
@@ -0,0 +1,47 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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.
+// =================================================================================================
+
+#ifndef PLUGINHANDLERINSTANCE_H
+#define PLUGINHANDLERINSTANCE_H
+#include "FileHandler.h"
+
+namespace XMP_PLUGIN
+{
+
+/** @class FileHandlerInstance
+ * @brief This class is equivalent to the native file handlers like JPEG_MetaHandler or the simialr one.
+ *
+ * This class is equivalent to the native file handler. This class is supposed to support all the
+ * function which a native file handler support.
+ * As of now, it support only the functions required for OwningFileHandler.
+ */
+class FileHandlerInstance : public XMPFileHandler
+{
+public:
+ FileHandlerInstance ( SessionRef object, FileHandlerSharedPtr handler, XMPFiles * parent );
+ virtual ~FileHandlerInstance();
+
+ virtual bool GetFileModDate ( XMP_DateTime * modDate );
+
+ virtual void CacheFileData();
+ virtual void ProcessXMP();
+ //virtual XMP_OptionBits GetSerializeOptions(); //It should not be needed as its required only inside updateFile.
+ virtual void UpdateFile ( bool doSafeUpdate );
+ virtual void WriteTempFile ( XMP_IO* tempRef );
+
+ inline SessionRef GetSession() const { return mObject; }
+ inline FileHandlerSharedPtr GetHandlerInfo() const { return mHandler; }
+
+private:
+ SessionRef mObject;
+ FileHandlerSharedPtr mHandler;
+};
+
+} //namespace XMP_PLUGIN
+#endif
diff --git a/XMPFiles/source/PluginHandler/HostAPIImpl.cpp b/XMPFiles/source/PluginHandler/HostAPIImpl.cpp
new file mode 100644
index 0000000..04dcdd0
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/HostAPIImpl.cpp
@@ -0,0 +1,637 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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 "HostAPI.h"
+#include "PluginManager.h"
+#include "FileHandlerInstance.h"
+#include "source/XIO.hpp"
+#include "XMPFiles/source/HandlerRegistry.h"
+
+using namespace Common;
+
+namespace XMP_PLUGIN
+{
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Exception handler
+//
+
+static void HandleException( WXMP_Error* wError )
+{
+ try
+ {
+ throw;
+ }
+ catch( XMP_Error& xmpErr )
+ {
+ wError->mErrorMsg = xmpErr.GetErrMsg();
+ wError->mErrorID = xmpErr.GetID();
+ }
+ catch( std::exception& stdErr )
+ {
+ wError->mErrorMsg = stdErr.what();
+ }
+ catch( ... )
+ {
+ wError->mErrorMsg = "Caught unknown exception";
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// FileIO_API
+//
+
+static XMPErrorID FileSysRead( XMP_IORef io, void* buffer, XMP_Uns32 count, bool readAll, XMP_Uns32& byteRead, WXMP_Error * wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+
+ try
+ {
+ if( io != NULL )
+ {
+ ::XMP_IO * thiz = (::XMP_IO*)io;
+ byteRead = thiz->Read( buffer, count, readAll );
+ wError->mErrorID = kXMPErr_NoError;
+ }
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+
+ return wError->mErrorID;
+}
+
+static XMPErrorID FileSysWrite( XMP_IORef io, void* buffer, XMP_Uns32 count, WXMP_Error * wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+
+ try
+ {
+ if( io != NULL )
+ {
+ ::XMP_IO * thiz = (::XMP_IO*)io;
+ thiz->Write( buffer, count );
+ wError->mErrorID = kXMPErr_NoError;
+ }
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+
+ return wError->mErrorID;
+}
+
+static XMPErrorID FileSysSeek( XMP_IORef io, XMP_Int64& offset, SeekMode mode, WXMP_Error * wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+
+ try
+ {
+ if( io != NULL )
+ {
+ ::XMP_IO * thiz = (::XMP_IO*)io;
+ thiz->Seek( offset, mode );
+ wError->mErrorID = kXMPErr_NoError;
+ }
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+
+ return wError->mErrorID;
+}
+
+static XMPErrorID FileSysLength( XMP_IORef io, XMP_Int64& length, WXMP_Error * wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+
+ try
+ {
+ if( io != NULL )
+ {
+ ::XMP_IO * thiz = (::XMP_IO*)io;
+ length = thiz->Length();
+ wError->mErrorID = kXMPErr_NoError;
+ }
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+
+ return wError->mErrorID;
+}
+
+static XMPErrorID FileSysTruncate( XMP_IORef io, XMP_Int64 length, WXMP_Error * wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+
+ try
+ {
+ if( io != NULL )
+ {
+ ::XMP_IO * thiz = (::XMP_IO*)io;
+ thiz->Truncate( length );
+ wError->mErrorID = kXMPErr_NoError;
+ }
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+
+ return wError->mErrorID;
+}
+
+static XMPErrorID FileSysDeriveTemp( XMP_IORef io, XMP_IORef& tempIO, WXMP_Error * wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+
+ try
+ {
+ if( io != NULL )
+ {
+ ::XMP_IO * thiz = (::XMP_IO*)io;
+ tempIO = thiz->DeriveTemp();
+ wError->mErrorID = kXMPErr_NoError;
+ }
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+
+ return wError->mErrorID;
+}
+
+static XMPErrorID FileSysAbsorbTemp( XMP_IORef io, WXMP_Error * wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+
+ try
+ {
+ if( io != NULL )
+ {
+ ::XMP_IO * thiz = (::XMP_IO*)io;
+ thiz->AbsorbTemp();
+ wError->mErrorID = kXMPErr_NoError;
+ }
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+
+ return wError->mErrorID;
+}
+
+static XMPErrorID FileSysDeleteTemp( XMP_IORef io, WXMP_Error * wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+
+ try
+ {
+ if( io != NULL )
+ {
+ ::XMP_IO * thiz = (::XMP_IO*)io;
+ thiz->DeleteTemp();
+ wError->mErrorID = kXMPErr_NoError;
+ }
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+
+ return wError->mErrorID;
+}
+
+static void GetFileSysAPI( FileIO_API* fileSys )
+{
+ if( fileSys )
+ {
+ fileSys->mReadProc = FileSysRead;
+ fileSys->mWriteProc = FileSysWrite;
+ fileSys->mSeekProc = FileSysSeek;
+ fileSys->mLengthProc = FileSysLength;
+ fileSys->mTruncateProc = FileSysTruncate;
+ fileSys->mDeriveTempProc = FileSysDeriveTemp;
+ fileSys->mAbsorbTempProc = FileSysAbsorbTemp;
+ fileSys->mDeleteTempProc = FileSysDeleteTemp;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// String_API
+//
+
+static XMPErrorID CreateBuffer( StringPtr* buffer, XMP_Uns32 size, WXMP_Error * wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+
+ try
+ {
+ if( buffer != NULL )
+ {
+ *buffer = (StringPtr) malloc( size );
+ if( *buffer != NULL )
+ {
+ wError->mErrorID = kXMPErr_NoError;
+ }
+ else
+ {
+ wError->mErrorMsg = "Allocation failed";
+ }
+ }
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+
+ return wError->mErrorID;
+}
+
+static XMPErrorID ReleaseBuffer( StringPtr buffer, WXMP_Error * wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+
+ try
+ {
+ if( buffer )
+ {
+ free( buffer );
+ wError->mErrorID = kXMPErr_NoError;
+ }
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+
+ return wError->mErrorID;
+}
+
+static void GetStringAPI( String_API* strAPI )
+{
+ if( strAPI )
+ {
+ strAPI->mCreateBufferProc = CreateBuffer;
+ strAPI->mReleaseBufferProc = ReleaseBuffer;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Abort_API
+//
+
+static XMPErrorID CheckAbort( SessionRef session, bool* aborted, WXMP_Error* wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+
+ if( aborted )
+ {
+ *aborted = false;
+
+ //
+ // find FileHandlerInstance associated to session reference
+ //
+ FileHandlerInstancePtr instance = PluginManager::getHandlerInstance( session );
+
+ if( instance != NULL )
+ {
+ //
+ // execute abort procedure if available
+ //
+ wError->mErrorID = kXMPErr_NoError;
+ XMP_AbortProc proc = instance->parent->abortProc;
+ void* arg = instance->parent->abortArg;
+
+ if( proc )
+ {
+ try
+ {
+ *aborted = proc( arg );
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+ }
+ }
+ }
+ else
+ {
+ wError->mErrorMsg = "Invalid parameter";
+ }
+
+ return wError->mErrorID;
+}
+
+static void GetAbortAPI( Abort_API* abortAPI )
+{
+ if( abortAPI )
+ {
+ abortAPI->mCheckAbort = CheckAbort;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// StandardHandler_API
+//
+
+static XMPErrorID CheckFormatStandardHandler( SessionRef session, XMP_FileFormat format, StringPtr path, bool& checkOK, WXMP_Error* wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+ wError->mErrorMsg = NULL;
+ checkOK = false;
+
+ //
+ // find FileHandlerInstance associated to session reference
+ //
+ FileHandlerInstancePtr instance = PluginManager::getHandlerInstance( session );
+
+ if( instance != NULL && PluginManager::getHandlerPriority( instance ) == PluginManager::kReplacementHandler )
+ {
+ //
+ // find previous file handler for file format identifier
+ //
+ XMPFileHandlerInfo* hdlInfo = HandlerRegistry::getInstance().getStandardHandlerInfo( format );
+
+ if( hdlInfo != NULL && HandlerRegistry::getInstance().isReplaced( format ) )
+ {
+ if( hdlInfo->checkProc != NULL )
+ {
+ try
+ {
+ //
+ // setup temporary XMPFiles instance
+ //
+ XMPFiles standardClient;
+ standardClient.format = format;
+ standardClient.filePath = std::string( path );
+
+ if( hdlInfo->flags & kXMPFiles_FolderBasedFormat )
+ {
+ //
+ // process folder based handler
+ //
+ if( path != NULL )
+ {
+ // The following code corresponds to the one found in HandlerRegistry::selectSmartHandler,
+ // in the folder based handler selection section of the function
+ // In this case the format is already known, therefor no folder checks are needed here,
+ // but the path must be split into the meaningful parts to call checkFolderFormat correctly
+
+ std::string rootPath = path;
+ std::string leafName;
+ std::string fileExt;
+ std::string gpName;
+ std::string parentName;
+
+ XIO::SplitLeafName ( &rootPath, &leafName );
+
+ if( !leafName.empty() )
+ {
+ size_t extPos = leafName.size();
+
+ for ( --extPos; extPos > 0; --extPos ) if ( leafName[extPos] == '.' ) break;
+
+ if( leafName[extPos] == '.' )
+ {
+ fileExt.assign( &leafName[extPos+1] );
+ MakeLowerCase( &fileExt );
+ leafName.erase( extPos );
+ }
+
+ CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (hdlInfo->checkProc);
+
+ // The case that a logical path to a clip has been passed, which does not point to a real file
+ if( Host_IO::GetFileMode( path ) == Host_IO::kFMode_DoesNotExist )
+ {
+ checkOK = CheckProc( hdlInfo->format, rootPath, gpName, parentName, leafName, &standardClient );
+ }
+ else
+ {
+ XIO::SplitLeafName( &rootPath, &parentName );
+ XIO::SplitLeafName( &rootPath, &gpName );
+ std::string origGPName ( gpName ); // ! Save the original case for XDCAM-FAM.
+ MakeUpperCase( &parentName );
+ MakeUpperCase( &gpName );
+
+ if( format == kXMP_XDCAM_FAMFile && ( (parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB") ) )
+ {
+ // ! The standard says Clip/Edit/Sub, but we just shifted to upper case.
+ gpName = origGPName; // ! XDCAM-FAM has just 1 level of inner folder, preserve the "MyMovie" case.
+ }
+
+ checkOK = CheckProc( hdlInfo->format, rootPath, gpName, parentName, leafName, &standardClient );
+ }
+ }
+ else
+ {
+ wError->mErrorID = kXMPErr_BadParam;
+ }
+ }
+ else
+ {
+ wError->mErrorID = kXMPErr_BadParam;
+ }
+ }
+ else
+ {
+ //
+ // file based handler (requires XMP_IO object)
+ //
+ CheckFileFormatProc CheckProc = (CheckFileFormatProc) (hdlInfo->checkProc);
+ XMPFiles_IO* io = XMPFiles_IO::New_XMPFiles_IO ( path, true );
+ checkOK = CheckProc( hdlInfo->format, path, io, &standardClient );
+ delete io;
+ }
+
+ wError->mErrorID = kXMPErr_NoError;
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+ }
+ }
+ else
+ {
+ wError->mErrorMsg = "No standard handler available";
+ }
+ }
+ else
+ {
+ wError->mErrorMsg = "Standard file handler can't call prior handler";
+ }
+
+ return wError->mErrorID;
+}
+
+static XMPErrorID GetXMPStandardHandler( SessionRef session, XMP_FileFormat format, StringPtr path, XMPMetaRef xmpRef, bool* xmpExists, WXMP_Error* wError )
+{
+ if( wError == NULL ) return kXMPErr_BadParam;
+
+ wError->mErrorID = kXMPErr_InternalFailure;
+ wError->mErrorMsg = NULL;
+
+ //
+ // find FileHandlerInstance associated to session reference
+ //
+ FileHandlerInstancePtr instance = PluginManager::getHandlerInstance( session );
+
+ if( instance != NULL && PluginManager::getHandlerPriority( instance ) == PluginManager::kReplacementHandler )
+ {
+ //
+ // find previous file handler for file format identifier
+ //
+ XMPFileHandlerInfo* hdlInfo = HandlerRegistry::getInstance().getStandardHandlerInfo( format );
+
+ if( hdlInfo != NULL && HandlerRegistry::getInstance().isReplaced( format ) )
+ {
+ //
+ // check format first
+ //
+ bool suc = false;
+
+ XMPErrorID errorID = CheckFormatStandardHandler( session, format, path, suc, wError );
+
+ if( errorID == kXMPErr_NoError && suc )
+ {
+ //
+ // setup temporary XMPFiles instance
+ //
+ XMPFiles standardClient;
+ standardClient.format = format;
+ standardClient.filePath = std::string( path );
+
+ SXMPMeta meta( xmpRef );
+
+ try
+ {
+ //
+ // open with passed handler info
+ //
+ suc = standardClient.OpenFile( *hdlInfo, path, kXMPFiles_OpenForRead );
+
+ if( suc )
+ {
+ //
+ // read meta data
+ //
+ suc = standardClient.GetXMP( &meta );
+
+ if( xmpExists != NULL ) *xmpExists = suc;
+ }
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+
+ //
+ // close and cleanup
+ //
+ try
+ {
+ standardClient.CloseFile();
+ }
+ catch( ... )
+ {
+ HandleException( wError );
+ }
+ }
+ else if( errorID == kXMPErr_NoError )
+ {
+ wError->mErrorID = kXMPErr_BadFileFormat;
+ wError->mErrorMsg = "Standard handler can't process file format";
+ }
+ }
+ else
+ {
+ wError->mErrorID = kXMPErr_NoFileHandler;
+ wError->mErrorMsg = "No standard handler available";
+ }
+ }
+ else
+ {
+ wError->mErrorMsg = "Standard file handler can't call prior handler";
+ }
+
+ return wError->mErrorID;
+}
+
+static void GetStandardHandlerAPI( StandardHandler_API* standardHandlerAPI )
+{
+ if( standardHandlerAPI )
+ {
+ standardHandlerAPI->mCheckFormatStandardHandler = CheckFormatStandardHandler;
+ standardHandlerAPI->mGetXMPStandardHandler = GetXMPStandardHandler;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Init/Term Host APIs
+//
+
+void PluginManager::SetupHostAPI_V1( HostAPIRef hostAPI )
+{
+ // Get XMP_IO APIs
+ hostAPI->mFileIOAPI = new FileIO_API();
+ GetFileSysAPI( hostAPI->mFileIOAPI );
+
+ // Get String APIs
+ hostAPI->mStrAPI = new String_API();
+ GetStringAPI( hostAPI->mStrAPI );
+
+ // Get Abort API
+ hostAPI->mAbortAPI = new Abort_API();
+ GetAbortAPI( hostAPI->mAbortAPI );
+
+ // Get standard handler APIs
+ hostAPI->mStandardHandlerAPI = new StandardHandler_API();
+ GetStandardHandlerAPI( hostAPI->mStandardHandlerAPI );
+}
+
+} //namespace XMP_PLUGIN
diff --git a/XMPFiles/source/PluginHandler/Module.cpp b/XMPFiles/source/PluginHandler/Module.cpp
new file mode 100644
index 0000000..91b8ea8
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/Module.cpp
@@ -0,0 +1,169 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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 "Module.h"
+
+namespace XMP_PLUGIN
+{
+
+PluginAPIRef Module::getPluginAPIs()
+{
+ //
+ // return ref. to Plugin API, load module if not yet loaded
+ //
+ if( !mPluginAPIs && !load() )
+ {
+ XMP_Throw ( "Plugin API not available.", kXMPErr_Unavailable );
+ }
+
+ return mPluginAPIs;
+}
+
+bool Module::load()
+{
+ if( mLoaded == kModuleNotLoaded )
+ {
+ std::string errorMsg;
+
+ //
+ // load module
+ //
+ mLoaded = kModuleErrorOnLoad;
+ mHandle = LoadModule(mPath, false);
+
+ if( mHandle != NULL )
+ {
+ //
+ // get entry point function pointer
+ //
+ InitializePluginProc InitializePlugin = reinterpret_cast<InitializePluginProc>(
+ GetFunctionPointerFromModuleImpl(mHandle, "InitializePlugin") );
+
+ if( InitializePlugin != NULL )
+ {
+ //
+ // get module ID from plugin resource
+ //
+ std::string moduleID;
+ GetResourceDataFromModule(mHandle, "MODULE_IDENTIFIER", "txt", moduleID);
+ mPluginAPIs = new PluginAPI();
+
+ //
+ // initialize plugin by calling entry point function
+ //
+ WXMP_Error error;
+ InitializePlugin( moduleID.c_str(), mPluginAPIs, &error );
+
+ if( error.mErrorID == kXMPErr_NoError
+ && mPluginAPIs->mTerminatePluginProc
+ && mPluginAPIs->mSetHostAPIProc
+ && mPluginAPIs->mInitializeSessionProc
+ && mPluginAPIs->mTerminateSessionProc
+ && mPluginAPIs->mCacheFileDataProc
+ && mPluginAPIs->mUpdateFileProc
+ && mPluginAPIs->mWriteTempFileProc
+ )
+ {
+ //
+ // set host API at plugin
+ //
+ HostAPIRef hostAPI = PluginManager::getHostAPI( mPluginAPIs->mVersion );
+ mPluginAPIs->mSetHostAPIProc( hostAPI, &error );
+
+ if( error.mErrorID == kXMPErr_NoError )
+ {
+ mLoaded = kModuleLoaded;
+ }
+ else
+ {
+ errorMsg = "Incompatible plugin API version. (" + moduleID + ")";
+ }
+ }
+ else
+ {
+ if( error.mErrorID != kXMPErr_NoError )
+ {
+ errorMsg = "Plugin initialization failed. (" + moduleID + ")";
+ }
+ else
+ {
+ errorMsg = "Plugin API incomplete. (" + moduleID + ")";
+ }
+ }
+ }
+ else
+ {
+ errorMsg = "Missing plugin entry point \"InitializePlugin\" in plugin " + mPath;
+ }
+
+ if( mLoaded != kModuleLoaded )
+ {
+ //
+ // plugin wasn't loaded and initialized successfully,
+ // so unload the module
+ //
+ this->unload();
+ }
+ }
+ else
+ {
+ errorMsg = "Can't load module " + mPath;
+ }
+
+ if( mLoaded != kModuleLoaded )
+ {
+ //
+ // error occurred
+ //
+ throw XMP_Error( kXMPErr_InternalFailure, errorMsg.c_str() );
+ }
+ }
+
+ return ( mLoaded == kModuleLoaded );
+}
+
+void Module::unload()
+{
+ WXMP_Error error;
+
+ //
+ // terminate plugin
+ //
+ if( mPluginAPIs != NULL )
+ {
+ if( mPluginAPIs->mTerminatePluginProc )
+ {
+ mPluginAPIs->mTerminatePluginProc( &error );
+ }
+ delete mPluginAPIs;
+ mPluginAPIs = NULL;
+ }
+
+ //
+ // unload plugin module
+ //
+ if( mLoaded != kModuleNotLoaded )
+ {
+ UnloadModule(mHandle, false);
+ mHandle = NULL;
+ if( mLoaded == kModuleLoaded )
+ {
+ //
+ // Reset mLoaded to kModuleNotLoaded, if the module was loaded successfully.
+ // Otherwise let it remain kModuleErrorOnLoad so that we won't try to load
+ // it again if some other handler ask to do so.
+ //
+ mLoaded = kModuleNotLoaded;
+ }
+ }
+
+ CheckError( error );
+}
+
+} //namespace XMP_PLUGIN
diff --git a/XMPFiles/source/PluginHandler/Module.h b/XMPFiles/source/PluginHandler/Module.h
new file mode 100644
index 0000000..0e308a0
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/Module.h
@@ -0,0 +1,65 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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.
+// =================================================================================================
+
+#ifndef MODULE_H
+#define MODULE_H
+#include "ModuleUtils.h"
+#include "PluginManager.h"
+
+namespace XMP_PLUGIN
+{
+
+/** @class Module
+ * @brief Manages module's loading and unloading.
+ */
+class Module
+{
+public:
+ Module( std::string & path ):
+ mPath( path ), mHandle( NULL ), mPluginAPIs( NULL ), mLoaded( kModuleNotLoaded ) { }
+
+ ~Module() { unload(); }
+
+ inline OS_ModuleRef getHandle() const { return mHandle; }
+ inline const std::string & getPath() const { return mPath; }
+
+ /**
+ * Returns pluginAPI. It loads the module if not already loaded.
+ * @return pluginAPI.
+ */
+ PluginAPIRef getPluginAPIs();
+
+ /**
+ * It loads the module if not already loaded.
+ * @return true if module is loaded successfully otherwise returns false.
+ */
+ bool load();
+
+ /**
+ * Unloads the module if it is loaded.
+ * @return Void.
+ */
+ void unload();
+
+private:
+ typedef enum
+ {
+ kModuleNotLoaded = 0,
+ kModuleLoaded,
+ kModuleErrorOnLoad
+ } LoadStatus;
+
+ std::string mPath;
+ OS_ModuleRef mHandle;
+ PluginAPIRef mPluginAPIs;
+ LoadStatus mLoaded;
+};
+
+} //namespace XMP_PLUGIN
+#endif //MODULE_H
diff --git a/XMPFiles/source/PluginHandler/ModuleUtils.h b/XMPFiles/source/PluginHandler/ModuleUtils.h
new file mode 100644
index 0000000..eedf6bf
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/ModuleUtils.h
@@ -0,0 +1,77 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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.
+// =================================================================================================
+
+#ifndef MODULEUTILS_H
+#define MODULEUTILS_H
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+
+#if XMP_WinBuild
+#include <Windows.h>
+typedef HMODULE OS_ModuleRef;
+#elif XMP_MacBuild
+#include <CoreFoundation/CFBundle.h>
+#include <tr1/memory>
+typedef CFBundleRef OS_ModuleRef;
+#elif XMP_UNIXBuild
+#include <tr1/memory>
+typedef void* OS_ModuleRef;
+#else
+#error Unsupported operating system
+#endif
+
+namespace XMP_PLUGIN
+{
+
+/**
+ * Platform implementation to retrieve a function pointer of the name \param inSymbol from a module \param inOSModule
+ */
+void* GetFunctionPointerFromModuleImpl( OS_ModuleRef inOSModule, const char* inSymbol );
+
+/**
+ * @return true if @param inModulePath points to a valid shared library
+*/
+#if XMP_MacBuild
+bool IsValidLibrary( const std::string & inModulePath );
+#endif
+
+/**
+ * Load module specified by absolute path \param inModulePath
+ *
+ * Win:
+ * If \param inOnlyResourceAccess = true, only the image is loaded, no referenced dlls are loaded nor initialization code is executed.
+ * If the module is already loaded and executable, it behaves as \param inOnlyResourceAccess = false.
+ * The reference count is increased, so don't forget to call UnloadModule.
+ *
+ * Mac:
+ * If \param inOnlyResourceAccess = true, only the CFBundleRef is created. No code is loaded and executed.
+ */
+OS_ModuleRef LoadModule( const std::string & inModulePath, bool inOnlyResourceAccess = false );
+
+/**
+ * Unload module
+ * @param inModule
+ * @param inOnlyResourceAccess = true, close resource file (only relevant for Linux !!).
+ */
+void UnloadModule( OS_ModuleRef inModule, bool inOnlyResourceAccess = false );
+
+/** @brief Read resource file and fill the data in outBuffer
+ * @param inOSModule Handle of the module.
+ * @param inResourceName Name of the resource file which needs to be read.
+ * @param inResourceType Type/Extension of the resource file.
+ * @param outBuffer Output buffer where data read from the resource file will be stored.
+ * @return true on success otherwise false
+ */
+bool GetResourceDataFromModule(
+ OS_ModuleRef inOSModule,
+ const std::string & inResourceName,
+ const std::string & inResourceType,
+ std::string & outBuffer);
+
+} //namespace XMP_PLUGIN
+#endif
diff --git a/XMPFiles/source/PluginHandler/OS_Utils_Linux.cpp b/XMPFiles/source/PluginHandler/OS_Utils_Linux.cpp
new file mode 100644
index 0000000..1fe1f9f
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/OS_Utils_Linux.cpp
@@ -0,0 +1,230 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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 "ModuleUtils.h"
+#include "source/UnicodeConversions.hpp"
+#include "source/XIO.hpp"
+
+#include <iostream>
+#include <map>
+#include <limits>
+#include <dlfcn.h>
+#include <errno.h>
+
+namespace XMP_PLUGIN
+{
+
+// global map of loaded modules (handle, full path)
+typedef std::map<OS_ModuleRef, std::string> ModuleRefToPathMap;
+static ModuleRefToPathMap sMapModuleRefToPath;
+//typedef std::map<void*, std::string> ResourceFileToPathMap;
+typedef std::map<OS_ModuleRef, std::string> ResourceFileToPathMap;
+static ResourceFileToPathMap sMapResourceFileToPath;
+
+typedef std::tr1::shared_ptr<int> FilePtr;
+
+static std::string GetModulePath( OS_ModuleRef inOSModule );
+/** ************************************************************************************************************************
+** CloseFile()
+*/
+void CloseFile(
+ int* inFilePtr)
+{
+ close(*inFilePtr);
+ delete inFilePtr;
+}
+
+/** ************************************************************************************************************************
+** OpenResourceFile()
+*/
+static FilePtr OpenResourceFile(
+ OS_ModuleRef inOSModule,
+ const std::string& inResourceName,
+ const std::string& inResourceType)
+{
+ // It is assumed, that all resources reside in a folder with
+ // the same name as the shared object plus '.resources' extension
+ std::string path( GetModulePath(inOSModule) );
+
+ XMP_StringPtr extPos = path.c_str() + path.size();
+ for ( ; (extPos != path.c_str()) && (*extPos != '.'); --extPos ) {}
+ path.erase( extPos - path.c_str() ); // Remove extension
+
+ path += ".resources";
+ path += kDirChar;
+ path += inResourceName + "." + inResourceType;
+
+ FilePtr file;
+ if( Host_IO::GetFileMode(path.c_str()) == Host_IO::kFMode_IsFile )
+ {
+ int fileRef = ::open( path.c_str(), O_RDONLY );
+ if (fileRef != -1)
+ {
+ file.reset(new int(fileRef), CloseFile);
+ }
+ }
+ return file;
+}
+
+OS_ModuleRef LoadModule( const std::string & inModulePath, bool inOnlyResourceAccess)
+{
+ OS_ModuleRef result = NULL;
+ if( inOnlyResourceAccess )
+ {
+ int fileHandle = open(reinterpret_cast<const char*>(inModulePath.c_str()), O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if( !fileHandle )
+ {
+ std::cerr << "Cannot open library for resource access: " << strerror(errno) << std::endl;
+ }
+ else
+ { // success !
+ result = (void*) fileHandle;
+ ResourceFileToPathMap::const_iterator iter = sMapResourceFileToPath.find(result);
+ if (iter == sMapResourceFileToPath.end())
+ {
+ // not found, so insert
+ sMapResourceFileToPath.insert(std::make_pair(result, inModulePath));
+ }
+ }
+
+ }
+ else
+ {
+ result = dlopen(reinterpret_cast<const char*>(inModulePath.c_str()), RTLD_LAZY/*RTLD_NOW*/);
+
+ if( !result )
+ {
+ std::cerr << "Cannot open library: " << dlerror() << std::endl;
+ }
+ else
+ { // success !
+ ModuleRefToPathMap::const_iterator iter = sMapModuleRefToPath.find(result);
+ if( iter == sMapModuleRefToPath.end() )
+ {
+ // not found, so insert
+ sMapModuleRefToPath.insert(std::make_pair(result, inModulePath));
+ }
+ }
+ }
+
+ return result;
+}
+
+void UnloadModule( OS_ModuleRef inModule, bool inOnlyResourceAccess )
+{
+ if( inModule != NULL )
+ {
+ // we bluntly assume, that only one instance of the same library is loaded ad therefore added to the global map !
+ if( inOnlyResourceAccess )
+ {
+ ResourceFileToPathMap::iterator iter = sMapResourceFileToPath.find(inModule);
+ if( iter != sMapResourceFileToPath.end() )
+ {
+ close((long) inModule);
+ sMapResourceFileToPath.erase(iter);
+ }
+ else
+ {
+ XMP_Throw("OS_Utils_Linux::UnloadModule called with invalid module handle", kXMPErr_InternalFailure);
+ }
+ }
+ else
+ {
+ ModuleRefToPathMap::iterator iter = sMapModuleRefToPath.find(inModule);
+ if( iter != sMapModuleRefToPath.end() )
+ {
+ dlclose(inModule);
+ sMapModuleRefToPath.erase(iter);
+ }
+ else
+ {
+ XMP_Throw("OS_Utils_Linux::UnloadModule called with invalid module handle", kXMPErr_InternalFailure);
+ }
+ }
+ }
+}
+
+/** ************************************************************************************************************************
+** GetModulePath()
+*/
+static std::string GetModulePath(
+ OS_ModuleRef inOSModule)
+{
+ std::string result;
+
+ if( inOSModule != NULL )
+ {
+ ModuleRefToPathMap::const_iterator iter = sMapModuleRefToPath.find(inOSModule);
+ ResourceFileToPathMap::const_iterator iter2 = sMapResourceFileToPath.find(inOSModule);
+ if( (iter != sMapModuleRefToPath.end()) && (iter2 != sMapResourceFileToPath.end()) )
+ {
+ XMP_Throw("OS_Utils_Linux::GetModulePath: Module handle is present in both global maps", kXMPErr_InternalFailure);
+ }
+ if (iter != sMapModuleRefToPath.end())
+ {
+ result = iter->second;
+ }
+ else if (iter2 != sMapResourceFileToPath.end())
+ {
+ result = iter2->second;
+ }
+ else
+ {
+ XMP_Throw("OS_Utils_Linux::GetModulePath: Failed to find inOSModule in global map !", kXMPErr_InternalFailure);
+ }
+ }
+
+ return result;
+}
+
+void* GetFunctionPointerFromModuleImpl( OS_ModuleRef inOSModule, const char* inSymbol )
+{
+ void* proc = NULL;
+ if( inOSModule != NULL )
+ {
+ proc = dlsym(inOSModule, inSymbol);
+ if( !proc )
+ {
+ std::cerr << "Cannot get function " << inSymbol << " : " << dlerror() << std::endl;
+ }
+ }
+ return proc;
+}
+
+bool GetResourceDataFromModule(
+ OS_ModuleRef inOSModule,
+ const std::string & inResourceName,
+ const std::string & inResourceType,
+ std::string & outBuffer)
+{
+ bool success = false;
+ FilePtr file = OpenResourceFile( inOSModule, inResourceName, inResourceType );
+
+ if( file )
+ {
+ ssize_t size = 0;
+ ssize_t file_size = ::lseek(*file.get(), 0, SEEK_END);
+ // presumingly we don't want to load more than 2GByte at once (!)
+ if( file_size < std::numeric_limits<XMP_Int32>::max() )
+ {
+ size = file_size;
+ if( size > 0 )
+ {
+ outBuffer.resize(size);
+
+ ::lseek(*file.get(), 0, SEEK_SET);
+ ssize_t bytesRead = ::read( *file.get(), (unsigned char*)&outBuffer[0], size );
+ success = bytesRead == size;
+ }
+ }
+ }
+ return success;
+}
+
+} //namespace XMP_PLUGIN
diff --git a/XMPFiles/source/PluginHandler/OS_Utils_Mac.cpp b/XMPFiles/source/PluginHandler/OS_Utils_Mac.cpp
new file mode 100644
index 0000000..c042eb7
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/OS_Utils_Mac.cpp
@@ -0,0 +1,366 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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 "ModuleUtils.h"
+#include "source/UnicodeConversions.hpp"
+#include "source/XIO.hpp"
+
+#include <CoreFoundation/CFBundle.h>
+#include <CoreFoundation/CFDate.h>
+#include <CoreFoundation/CFNumber.h>
+#include <CoreFoundation/CFError.h>
+#include <string>
+
+
+namespace XMP_PLUGIN
+{
+
+/** ************************************************************************************************************************
+** Auto-releasing wrapper for Core Foundation objects
+** AutoCFRef is an auto-releasing wrapper for any Core Foundation data type that can be passed
+** to CFRetain and CFRelease. When constructed with a Core Foundation object, it is assumed it
+** has just been created; that constructor does not call CFRetain. This supports reference counting
+** through Core Foundation's own mechanism.
+*/
+template <class T>
+class AutoCFRef
+{
+
+public:
+ /// Default constructor creates NULL reference
+ AutoCFRef()
+ {
+ mCFTypeRef = NULL;
+ }
+
+ /// Construct with any CFXXXRef type.
+ explicit AutoCFRef(const T& value)
+ {
+ mCFTypeRef = value;
+ }
+
+ /// Copy constructor
+ AutoCFRef(const AutoCFRef<T>& rhs)
+ {
+ mCFTypeRef = rhs.mCFTypeRef;
+ if (mCFTypeRef)
+ CFRetain(mCFTypeRef);
+ }
+
+ /// Destructor
+ ~AutoCFRef()
+ {
+ if (mCFTypeRef)
+ CFRelease(mCFTypeRef);
+ }
+
+ /// Assignment operator
+ void operator=(const AutoCFRef<T>& rhs)
+ {
+ if (!Same(rhs))
+ {
+ if (mCFTypeRef)
+ CFRelease(mCFTypeRef);
+ mCFTypeRef = rhs.mCFTypeRef;
+ if (mCFTypeRef)
+ CFRetain(mCFTypeRef);
+ }
+ }
+
+ /// Equivalence operator
+ /** operator== returns logical equivalence, the meaning of which varies from class to class
+ in Core Foundation. See also AutoCFRef::Same.
+ @param rhs The AutoCFRef<T> to compare to
+ @return true if objects are logically equivalent.
+ */
+ bool operator==(const AutoCFRef<T>& rhs) const
+ {
+ if (mCFTypeRef && rhs.mCFTypeRef)
+ return CFEqual(mCFTypeRef, rhs.mCFTypeRef);
+ else
+ return mCFTypeRef == rhs.mCFTypeRef;
+ }
+
+ /// Non-equivalence operator
+ /** operator!= returns logical equivalence, the meaning of which varies from class to class
+ in Core Foundation. See also AutoCFRef::Same.
+ @param rhs The AutoCFRef<T> to compare to
+ @return true if objects are not logically equivalent.
+ */
+ bool operator!=(const AutoCFRef<T>& rhs) const
+ {
+ return !operator==(rhs);
+ }
+
+ /// References same CF object
+ /** Same returns true if both objects reference the same CF object (or both are NULL).
+ For logical equivalence (CFEqual) use == operator.
+ @param rhs The AutoCFRef<T> to compare to
+ @return true if AutoRefs reference the same object.
+ */
+ bool Same(const AutoCFRef<T>& rhs) const
+ {
+ return mCFTypeRef == rhs.mCFTypeRef;
+ }
+
+ /// Change the object referenced
+ /** Reset is used to put a new CF object into a preexisting AutoCFRef. Does NOT call CFRetain,
+ so if its ref count is 1 on entry, it will delete on destruction, barring other influences.
+ @param value (optional) the new CFXXXRef to set, default is NULL.
+ */
+ void Reset(T value = NULL)
+ {
+ if (value != mCFTypeRef)
+ {
+ if (mCFTypeRef)
+ CFRelease(mCFTypeRef);
+ mCFTypeRef = value;
+ }
+ }
+
+ /// Return true if no object referenced.
+ bool IsNull() const
+ {
+ return mCFTypeRef == NULL;
+ }
+
+ /// Return retain count.
+ /** Returns retain count of referenced object from Core Foundation. Can be used to track down
+ failures in reference management, but be aware that some objects might be returned to your
+ code by the operating system with a retain count already greater than one.
+ @return Referenced object's retain count.
+ */
+ CFIndex RetainCount()
+ {
+ return mCFTypeRef ? CFGetRetainCount(mCFTypeRef) : 0;
+ }
+
+ /// Const dereference operator
+ /** operator* returns a reference to the contained CFTypeRef. Use this to pass the object into
+ Core Foundation functions. DO NOT use this to create a new AutoCFRef; copy construct instead.
+ */
+ const T& operator*() const
+ {
+ return mCFTypeRef;
+ }
+
+ /// Nonconst dereference operator
+ /** operator* returns a reference to the contained CFTypeRef. Use this to pass the object into
+ Core Foundation functions. DO NOT use this to create a new AutoCFRef; copy construct instead.
+ */
+ T& operator*()
+ {
+ return mCFTypeRef;
+ }
+
+private:
+ T mCFTypeRef;
+};
+
+typedef AutoCFRef<CFURLRef> AutoCFURL;
+typedef AutoCFRef<CFStringRef> AutoCFString;
+
+typedef std::tr1::shared_ptr<FSIORefNum> FilePtr;
+
+
+/** ************************************************************************************************************************
+** Convert string into CFString
+*/
+static inline CFStringRef MakeCFString(const std::string& inString, CFStringEncoding inEncoding = kCFStringEncodingUTF8)
+{
+ CFStringRef str = ::CFStringCreateWithCString( NULL, inString.c_str(), inEncoding );
+ return str;
+}
+
+/** ************************************************************************************************************************
+** Convert CFString into std::string
+*/
+static std::string MacToSTDString(CFStringRef inCFString)
+{
+ std::string result;
+
+ if (inCFString == NULL)
+ {
+ return result;
+ }
+
+ // The CFStringGetLength returns the length of the string in UTF-16 encoding units.
+ ::CFIndex const stringUtf16Length = ::CFStringGetLength(inCFString);
+ if (stringUtf16Length == 0)
+ {
+ return result;
+ }
+
+ // Check if the CFStringRef can allow fast-return.
+ char const* ptr = ::CFStringGetCStringPtr(inCFString, kCFStringEncodingUTF8);
+ if (ptr != NULL)
+ {
+ result = std::string( ptr ); // This kind of assign expects '\0' termination.
+ return result;
+ }
+
+ // Since this is an encoding conversion, the converted string may not be the same length in bytes
+ // as the CFStringRef in UTF-16 encoding units.
+
+ ::UInt8 const noLossByte = 0;
+ ::Boolean const internalRepresentation = FALSE; // TRUE is external representation, with a BOM. FALSE means no BOM.
+ ::CFRange const range = ::CFRangeMake(0, stringUtf16Length); // [NOTE] length is in UTF-16 encoding units, not bytes.
+ ::CFIndex const maxBufferLength = ::CFStringGetMaximumSizeForEncoding(stringUtf16Length, kCFStringEncodingUTF8); // Convert from UTF-16 encoding units to UTF-8 worst-case-scenario encoding units, in bytes.
+ ::CFIndex usedBufferLength = 0; // In byte count.
+ char buffer[1024];
+ memset( buffer, 0, 1024 );
+ ::CFIndex numberOfUtf16EncodingUnitsConverted = ::CFStringGetBytes(inCFString, range, ::kCFStringEncodingUTF8, noLossByte, internalRepresentation, (UInt8*)buffer, maxBufferLength, &usedBufferLength);
+ result = std::string( buffer );
+
+ return result;
+}
+
+
+static void CloseFile( FSIORefNum* inFilePtr )
+{
+ FSCloseFork(*inFilePtr);
+ delete inFilePtr;
+}
+
+static FilePtr OpenResourceFile( OS_ModuleRef inOSModule, const std::string& inResourceName, const std::string& inResourceType, bool inSearchInSubFolderWithNameOfResourceType)
+{
+ FilePtr file;
+ if (inOSModule != nil)
+ {
+ AutoCFString resourceName( MakeCFString( inResourceName ) );
+ AutoCFString resourceType( MakeCFString( inResourceType ) );
+ AutoCFString subfolderName(inSearchInSubFolderWithNameOfResourceType ? MakeCFString( inResourceType ) : nil);
+
+ AutoCFURL url(
+ ::CFBundleCopyResourceURL(inOSModule, *resourceName, *resourceType, *subfolderName));
+
+ FSRef fileReference;
+ if (!url.IsNull() && ::CFURLGetFSRef(*url, &fileReference))
+ {
+ HFSUniStr255 str;
+ if (::FSGetDataForkName(&str) == noErr)
+ {
+ FSIORefNum forkRef;
+ if (::FSOpenFork(&fileReference, str.length, str.unicode, fsRdPerm, &forkRef) == noErr)
+ {
+ file.reset(new FSIORefNum(forkRef), CloseFile);
+ }
+ }
+ }
+ }
+ return file;
+}
+
+bool IsValidLibrary( const std::string & inModulePath )
+{
+ bool result = false;
+ AutoCFURL bundleURL(::CFURLCreateFromFileSystemRepresentation(
+ kCFAllocatorDefault,
+ (const UInt8*) inModulePath.c_str(),
+ inModulePath.size(),
+ false));
+ if (*bundleURL != NULL)
+ {
+ CFBundleRef bundle = ::CFBundleCreate(kCFAllocatorDefault, *bundleURL);
+ if (bundle != NULL)
+ {
+ CFArrayRef arrayRef = ::CFBundleCopyExecutableArchitectures(bundle);
+ if (arrayRef != NULL)
+ {
+ result = true;
+ ::CFRelease(arrayRef);
+ }
+
+ ::CFRelease(bundle);
+ }
+ }
+ return result;
+}
+
+OS_ModuleRef LoadModule( const std::string & inModulePath, bool inOnlyResourceAccess )
+{
+ OS_ModuleRef result = NULL;
+ AutoCFURL bundleURL(::CFURLCreateFromFileSystemRepresentation(
+ kCFAllocatorDefault,
+ (const UInt8*) inModulePath.c_str(),
+ inModulePath.size(),
+ false));
+ if (*bundleURL != NULL)
+ {
+ result = ::CFBundleCreate(kCFAllocatorDefault, *bundleURL);
+ if (!inOnlyResourceAccess && (result != NULL))
+ {
+ ::CFErrorRef errorRef = NULL;
+ Boolean loaded = ::CFBundleIsExecutableLoaded(result);
+ if (!loaded)
+ {
+ loaded = ::CFBundleLoadExecutableAndReturnError(result, &errorRef);
+ if(!loaded || errorRef != NULL)
+ {
+ AutoCFString errorDescr(::CFErrorCopyDescription(errorRef));
+ throw XMP_Error( kXMPErr_InternalFailure, MacToSTDString(*errorDescr).c_str() );
+ ::CFRelease(errorRef);
+ // release bundle and return NULL
+ ::CFRelease(result);
+ result = NULL;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+void UnloadModule( OS_ModuleRef inModule, bool inOnlyResourceAccess )
+{
+ if (inModule != NULL)
+ {
+ ::CFRelease(inModule);
+ }
+}
+
+void* GetFunctionPointerFromModuleImpl( OS_ModuleRef inOSModule, const char* inSymbol )
+{
+ void* proc = NULL;
+ if( inOSModule != NULL)
+ {
+ proc = ::CFBundleGetFunctionPointerForName( inOSModule, *AutoCFString(MakeCFString(inSymbol)) );
+ }
+ return proc;
+}
+
+bool GetResourceDataFromModule(
+ OS_ModuleRef inOSModule,
+ const std::string & inResourceName,
+ const std::string & inResourceType,
+ std::string & outBuffer)
+{
+ bool success = false;
+
+ if (FilePtr file = OpenResourceFile(inOSModule, inResourceName, inResourceType, false))
+ {
+ ByteCount size = 0;
+ SInt64 fork_size = 0;
+ ::FSGetForkSize(*file.get(), &fork_size);
+ // presumingly we don't want to load more than 2GByte at once (!)
+ if( fork_size < std::numeric_limits<XMP_Int32>::max() )
+ {
+ size = static_cast<ByteCount>(fork_size);
+ if (size > 0)
+ {
+ outBuffer.resize(size);
+ ::FSReadFork(*file.get(), fsFromStart, 0, size, (unsigned char*)&outBuffer[0], (ByteCount*)&size);
+ }
+ success = true;
+ }
+ }
+
+ return success;
+}
+
+} //namespace XMP_PLUGIN
diff --git a/XMPFiles/source/PluginHandler/OS_Utils_WIN.cpp b/XMPFiles/source/PluginHandler/OS_Utils_WIN.cpp
new file mode 100644
index 0000000..66df45a
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/OS_Utils_WIN.cpp
@@ -0,0 +1,66 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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 "ModuleUtils.h"
+#include "source/UnicodeConversions.hpp"
+
+namespace XMP_PLUGIN
+{
+
+OS_ModuleRef LoadModule( const std::string & inModulePath, bool inOnlyResourceAccess)
+{
+ std::string wideString;
+ ToUTF16 ( (UTF8Unit*)inModulePath.c_str(), inModulePath.size() + 1, &wideString, false ); // need +1 character otherwise \0 won't be converted into UTF16
+
+ DWORD flags = inOnlyResourceAccess ? LOAD_LIBRARY_AS_IMAGE_RESOURCE : 0;
+ OS_ModuleRef result = ::LoadLibraryEx((WCHAR*) wideString.c_str(), NULL, flags);
+
+ // anything below indicates error in LoadLibrary
+ if((result != NULL) && (result < OS_ModuleRef(32)))
+ {
+ result = NULL;
+ }
+ return result;
+}
+
+void UnloadModule( OS_ModuleRef inModule, bool inOnlyResourceAccess )
+{
+ ::FreeLibrary(inModule);
+}
+
+void* GetFunctionPointerFromModuleImpl( OS_ModuleRef inOSModule, const char* inSymbol )
+{
+ return reinterpret_cast<void*>( ::GetProcAddress( inOSModule, inSymbol ) );
+}
+
+bool GetResourceDataFromModule(
+ OS_ModuleRef inOSModule,
+ const std::string & inResourceName,
+ const std::string & inResourceType,
+ std::string & outBuffer)
+{
+ HRSRC src = ::FindResourceA(inOSModule, reinterpret_cast<LPCSTR>(inResourceName.c_str()), reinterpret_cast<LPCSTR>(inResourceType.c_str()));
+ if( src != NULL )
+ {
+ HGLOBAL resource = (::LoadResource(inOSModule, src));
+ HGLOBAL data = (::LockResource(resource));
+ std::size_t size = (std::size_t)(::SizeofResource(inOSModule, src));
+ if (size)
+ {
+ outBuffer.assign((const char*)data, size);
+ }
+ UnlockResource(data);
+ ::FreeResource(resource);
+
+ return true;
+ }
+ return false;
+}
+
+} //namespace XMP_PLUGIN \ No newline at end of file
diff --git a/XMPFiles/source/PluginHandler/PluginManager.cpp b/XMPFiles/source/PluginHandler/PluginManager.cpp
new file mode 100644
index 0000000..af60388
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/PluginManager.cpp
@@ -0,0 +1,779 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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 "PluginManager.h"
+#include "FileHandler.h"
+#include <algorithm>
+#include "XMPAtoms.h"
+#include "XMPFiles/source/HandlerRegistry.h"
+#include "FileHandlerInstance.h"
+#include "HostAPI.h"
+
+using namespace Common;
+using namespace std;
+
+// =================================================================================================
+
+namespace XMP_PLUGIN
+{
+
+const char* kResourceName_UIDs = "XMPPLUGINUIDS";
+const char* kLibraryExtensions[] = { "xpi" };
+
+struct FileHandlerPair
+{
+ FileHandlerSharedPtr mStandardHandler;
+ FileHandlerSharedPtr mReplacementHandler;
+};
+
+// =================================================================================================
+
+static XMP_FileFormat GetXMPFileFormatFromFilePath( XMP_StringPtr filePath )
+{
+ XMP_StringPtr pathName = filePath + strlen(filePath);
+ for ( ; pathName > filePath; --pathName ) {
+ if ( *pathName == '.' ) break;
+ }
+
+ XMP_StringPtr fileExt = pathName + 1;
+ return HandlerRegistry::getInstance().getFileFormat ( fileExt );
+}
+
+// =================================================================================================
+
+static XMPFileHandler* Plugin_MetaHandlerCTor ( FileHandlerSharedPtr handler, XMPFiles* parent )
+{
+ SessionRef object;
+ WXMP_Error error;
+
+ if( (handler == 0) || (! handler->load()) )
+ {
+ XMP_Throw ( "Plugin not loaded", kXMPErr_InternalFailure );
+ }
+
+ handler->getModule()->getPluginAPIs()->mInitializeSessionProc ( handler->getUID().c_str(), parent->filePath.c_str(), (XMP_Uns32)parent->format, (XMP_Uns32)handler->getHandlerFlags(), (XMP_Uns32)parent->openFlags, &object, &error );
+ CheckError ( error );
+
+ FileHandlerInstance* instance = new FileHandlerInstance ( object, handler, parent );
+ return instance;
+}
+
+static XMPFileHandler* Plugin_MetaHandlerCTor_Standard( XMPFiles * parent )
+{
+ FileHandlerSharedPtr handler = PluginManager::getFileHandler( parent->format, PluginManager::kStandardHandler );
+
+ return Plugin_MetaHandlerCTor( handler, parent );
+}
+
+static XMPFileHandler* Plugin_MetaHandlerCTor_Replacement( XMPFiles * parent )
+{
+ FileHandlerSharedPtr handler = PluginManager::getFileHandler( parent->format, PluginManager::kReplacementHandler );
+
+ return Plugin_MetaHandlerCTor( handler, parent );
+}
+
+// =================================================================================================
+
+static bool Plugin_CheckFileFormat ( FileHandlerSharedPtr handler, XMP_StringPtr filePath, XMP_IO * fileRef, XMPFiles * parent )
+{
+ if ( handler != 0 ) {
+
+ // call into plugin if owning handler or if manifest has no CheckFormat entry
+ if ( fileRef == 0 || handler->getCheckFormatSize() == 0) {
+
+ bool ok;
+ WXMP_Error error;
+ CheckSessionFileFormatProc checkProc = handler->getModule()->getPluginAPIs()->mCheckFileFormatProc;
+ checkProc ( handler->getUID().c_str(), filePath, fileRef, &ok, &error );
+ CheckError ( error );
+ return ok;
+
+ } else {
+
+ // all CheckFormat manifest entries must match
+ for ( XMP_Uns32 i=0; i < handler->getCheckFormatSize(); i++ ) {
+
+ CheckFormat checkFormat = handler->getCheckFormat ( i );
+
+ if ( checkFormat.empty() ) return false;
+
+ XMP_Uns8 buffer[1024];
+
+ if ( checkFormat.mLength > 1024 ) {
+ //Ideally check format string should not be that long.
+ //The check is here to handle only malicious data.
+ checkFormat.mLength = 1024;
+ }
+
+ fileRef->Seek ( checkFormat.mOffset, kXMP_SeekFromStart );
+ XMP_Uns32 len = fileRef->Read ( buffer, checkFormat.mLength );
+
+ if ( len != checkFormat.mLength ) {
+
+ // Not enough byte read from the file.
+ return false;
+
+ } else {
+
+ // Check if byteSeq is hexadecimal byte sequence, e.g 0x03045100
+
+ bool isHex = ( (checkFormat.mLength > 2) &&
+ (checkFormat.mByteSeq[0] == '0') &&
+ (checkFormat.mByteSeq[1] == 'x') &&
+ (checkFormat.mByteSeq.size() == (2 + 2*checkFormat.mLength)) );
+
+ if ( ! isHex ) {
+
+ if ( memcmp ( buffer, checkFormat.mByteSeq.c_str(), checkFormat.mLength ) != 0 ) return false;
+
+ } else {
+
+ for ( XMP_Uns32 current = 0; current < checkFormat.mLength; current++ ) {
+
+ char oneByteBuffer[3];
+ oneByteBuffer[0] = checkFormat.mByteSeq [ 2 + 2*current ];
+ oneByteBuffer[1] = checkFormat.mByteSeq [ 2 + 2*current + 1 ];
+ oneByteBuffer[2] = '\0';
+
+ XMP_Uns8 oneByte = (XMP_Uns8) strtoul ( oneByteBuffer, 0, 16 );
+ if ( oneByte != buffer[current] ) return false;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ return true; // The checkFormat string comparison passed.
+
+ }
+
+ }
+
+ return false; // Should never get here.
+} // Plugin_CheckFileFormat
+
+static bool Plugin_CheckFileFormat_Standard( XMP_FileFormat format, XMP_StringPtr filePath, XMP_IO* fileRef, XMPFiles* parent )
+{
+ FileHandlerSharedPtr handler = PluginManager::getFileHandler( format, PluginManager::kStandardHandler );
+
+ return Plugin_CheckFileFormat( handler, filePath, fileRef, parent );
+}
+
+static bool Plugin_CheckFileFormat_Replacement( XMP_FileFormat format, XMP_StringPtr filePath, XMP_IO* fileRef, XMPFiles* parent )
+{
+ FileHandlerSharedPtr handler = PluginManager::getFileHandler( format, PluginManager::kReplacementHandler );
+
+ return Plugin_CheckFileFormat( handler, filePath, fileRef, parent );
+}
+
+// =================================================================================================
+
+static bool Plugin_CheckFolderFormat( FileHandlerSharedPtr handler,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent )
+{
+ bool result = false;
+
+ if ( handler != 0 )
+ {
+ WXMP_Error error;
+ CheckSessionFolderFormatProc checkProc = handler->getModule()->getPluginAPIs()->mCheckFolderFormatProc;
+ checkProc ( handler->getUID().c_str(), rootPath.c_str(), gpName.c_str(), parentName.c_str(), leafName.c_str(), &result, &error );
+ CheckError( error );
+ }
+
+ return result;
+
+}
+
+static bool Plugin_CheckFolderFormat_Standard( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent )
+{
+ FileHandlerSharedPtr handler = PluginManager::getFileHandler( format, PluginManager::kStandardHandler );
+
+ return Plugin_CheckFolderFormat( handler, rootPath, gpName, parentName, leafName, parent );
+}
+
+static bool Plugin_CheckFolderFormat_Replacement( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent )
+{
+ FileHandlerSharedPtr handler = PluginManager::getFileHandler( format, PluginManager::kReplacementHandler );
+
+ return Plugin_CheckFolderFormat( handler, rootPath, gpName, parentName, leafName, parent );
+}
+
+// =================================================================================================
+
+PluginManager* PluginManager::msPluginManager = 0;
+
+PluginManager::PluginManager( const std::string& pluginDir, const std::string& plugins ) : mPluginDir ( pluginDir )
+{
+
+ const std::size_t count = sizeof(kLibraryExtensions) / sizeof(kLibraryExtensions[0]);
+
+ for ( std::size_t i = 0; i<count; ++i ) {
+ mExtensions.push_back ( std::string ( kLibraryExtensions[i] ) );
+ }
+
+ size_t pos = std::string::npos;
+
+ #if XMP_WinBuild
+ // convert to Win kDirChar
+ while ( (pos = mPluginDir.find ('/')) != string::npos ) {
+ mPluginDir.replace (pos, 1, "\\");
+ }
+ #else
+ while ( (pos = mPluginDir.find ('\\')) != string::npos ) {
+ mPluginDir.replace (pos, 1, "/");
+ }
+ #endif
+
+ if ( ! mPluginDir.empty() && Host_IO::Exists( mPluginDir.c_str() ) ) {
+
+ XMP_StringPtr strPtr = plugins.c_str();
+ size_t pos = 0;
+ size_t length = 0;
+
+ for ( ; ; ++strPtr, ++length ) {
+
+ if ( (*strPtr == ',') || (*strPtr == '\0') ) {
+
+ if ( length != 0 ) {
+
+ //Remove white spaces from front
+ while ( plugins[pos] == ' ' ) {
+ ++pos;
+ --length;
+ }
+
+ std::string pluginName;
+ pluginName.assign ( plugins, pos, length );
+
+ //Remove extension from the plugin name
+ size_t found = pluginName.find ( '.' );
+ if ( found != string::npos ) pluginName.erase ( found );
+
+ //Remove white spaces from the back
+ found = pluginName.find ( ' ' );
+ if ( found != string::npos ) pluginName.erase ( found );
+
+ MakeLowerCase ( &pluginName );
+ mPluginsNeeded.push_back ( pluginName );
+
+ //Reset for next plugin
+ pos = pos + length + 1;
+ length = 0;
+
+ }
+
+ if ( *strPtr == '\0' ) break;
+
+ }
+
+ }
+
+ }
+
+} // PluginManager::PluginManager
+
+// =================================================================================================
+
+PluginManager::~PluginManager()
+{
+ mPluginDir.clear();
+ mExtensions.clear();
+ mPluginsNeeded.clear();
+ mModules.clear();
+ mHandlers.clear();
+ mSessions.clear();
+
+ terminateHostAPI();
+}
+
+// =================================================================================================
+
+static bool registerHandler( XMP_FileFormat format, FileHandlerSharedPtr handler )
+{
+ bool ret = false;
+
+ HandlerRegistry& hdlrReg = HandlerRegistry::getInstance();
+ FileHandlerType type = handler->getHandlerType();
+ CheckFileFormatProc chkFileFormat = NULL;
+ CheckFolderFormatProc chkFolderFormat = NULL;
+ XMPFileHandlerCTor hdlCtor = NULL;
+
+ if( handler->getOverwriteHandler() )
+ {
+ //
+ // ctor, checkformat function pointers for replacement handler
+ //
+ hdlCtor = Plugin_MetaHandlerCTor_Replacement;
+ chkFileFormat = Plugin_CheckFileFormat_Replacement;
+ chkFolderFormat = Plugin_CheckFolderFormat_Replacement;
+ }
+ else
+ {
+ //
+ // ctor, checkformat function pointers for standard handler
+ //
+ hdlCtor = Plugin_MetaHandlerCTor_Standard;
+ chkFileFormat = Plugin_CheckFileFormat_Standard;
+ chkFolderFormat = Plugin_CheckFolderFormat_Standard;
+ }
+
+ //
+ // register handler according to its type
+ //
+ switch( handler->getHandlerType() )
+ {
+ case NormalHandler_K:
+ ret = hdlrReg.registerNormalHandler( format, handler->getHandlerFlags(), chkFileFormat,
+ hdlCtor, handler->getOverwriteHandler() );
+ break;
+
+ case OwningHandler_K:
+ ret = hdlrReg.registerOwningHandler( format, handler->getHandlerFlags(), chkFileFormat,
+ hdlCtor, handler->getOverwriteHandler() );
+ break;
+
+ case FolderHandler_K:
+ ret = hdlrReg.registerFolderHandler( format, handler->getHandlerFlags(), chkFolderFormat,
+ hdlCtor, handler->getOverwriteHandler() );
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+void PluginManager::initialize( const std::string& pluginDir, const std::string& plugins )
+{
+ try
+ {
+ HandlerRegistry & hdlrReg = HandlerRegistry::getInstance();
+ if( msPluginManager == 0 ) msPluginManager = new PluginManager( pluginDir, plugins );
+
+ msPluginManager->doScan( 2 );
+
+ //
+ // Register all the found plugin based file handler
+ //
+ for( PluginHandlerMap::iterator it = msPluginManager->mHandlers.begin(); it != msPluginManager->mHandlers.end(); ++it )
+ {
+ XMP_FileFormat format = it->first;
+ FileHandlerPair handlers = it->second;
+
+ if( handlers.mStandardHandler != NULL )
+ {
+ registerHandler( format, handlers.mStandardHandler );
+ }
+
+ if( handlers.mReplacementHandler != NULL )
+ {
+ registerHandler( format, handlers.mReplacementHandler );
+ }
+ }
+ }
+ catch( ... )
+ {
+ // Absorb exceptions. This is the plugin-architecture entry point.
+ }
+
+} // PluginManager::initialize
+
+// =================================================================================================
+
+void PluginManager::terminate()
+{
+ delete msPluginManager;
+ msPluginManager = 0;
+ ResourceParser::terminate();
+}
+
+// =================================================================================================
+
+void PluginManager::addFileHandler( XMP_FileFormat format, FileHandlerSharedPtr handler )
+{
+ if ( msPluginManager != 0 )
+ {
+ PluginHandlerMap & handlerMap = msPluginManager->mHandlers;
+
+ //
+ // Create placeholder in map for format
+ //
+ if ( handlerMap.find(format) == handlerMap.end() )
+ {
+ FileHandlerPair pair;
+ handlerMap.insert( handlerMap.end(), std::pair<XMP_FileFormat, FileHandlerPair>( format, pair) );
+ }
+
+ //
+ // if there is already a standard handler or a replacement handler for the file format
+ // then just ignore it. The first one wins.
+ //
+ if( handler->getOverwriteHandler() )
+ {
+ if( handlerMap[format].mReplacementHandler.get() == NULL ) handlerMap[format].mReplacementHandler = handler;
+ }
+ else
+ {
+ if( handlerMap[format].mStandardHandler.get() == NULL ) handlerMap[format].mStandardHandler = handler;
+ }
+ }
+}
+
+// =================================================================================================
+
+FileHandlerSharedPtr PluginManager::getFileHandler( XMP_FileFormat format, HandlerPriority priority /*= kStandardHandler*/ )
+{
+ if ( msPluginManager != 0 )
+ {
+ PluginHandlerMap::iterator it = msPluginManager->mHandlers.find( format );
+
+ if( it != msPluginManager->mHandlers.end() )
+ {
+ if( priority == kStandardHandler )
+ {
+ return it->second.mStandardHandler;
+ }
+ else if( priority == kReplacementHandler )
+ {
+ return it->second.mReplacementHandler;
+ }
+ }
+ }
+
+ return FileHandlerSharedPtr();
+}
+
+// =================================================================================================
+
+static XMP_ReadWriteLock sPluginManagerRWLock;
+
+void PluginManager::addHandlerInstance( SessionRef session, FileHandlerInstancePtr handler )
+{
+ if ( msPluginManager != 0 ) {
+ XMP_AutoLock(&sPluginManagerRWLock, kXMP_WriteLock);
+ SessionMap & sessionMap = msPluginManager->mSessions;
+ if ( sessionMap.find(session) == sessionMap.end() ) {
+ sessionMap[session] = handler;
+ }
+ }
+}
+
+// =================================================================================================
+
+void PluginManager::removeHandlerInstance( SessionRef session )
+{
+ if ( msPluginManager != 0 ) {
+ XMP_AutoLock(&sPluginManagerRWLock, kXMP_WriteLock);
+ SessionMap & sessionMap = msPluginManager->mSessions;
+ sessionMap.erase ( session );
+ }
+}
+
+// =================================================================================================
+
+FileHandlerInstancePtr PluginManager::getHandlerInstance( SessionRef session )
+{
+ FileHandlerInstancePtr ret = 0;
+ if ( msPluginManager != 0 ) {
+ XMP_AutoLock(&sPluginManagerRWLock, kXMP_ReadLock);
+ ret = msPluginManager->mSessions[session];
+ }
+ return ret;
+}
+
+// =================================================================================================
+
+PluginManager::HandlerPriority PluginManager::getHandlerPriority( FileHandlerInstancePtr handler )
+{
+ if( handler != NULL )
+ {
+ for( PluginHandlerMap::iterator it=msPluginManager->mHandlers.begin();
+ it != msPluginManager->mHandlers.end(); it++ )
+ {
+ if( it->second.mStandardHandler == handler->GetHandlerInfo() ) return kStandardHandler;
+ if( it->second.mReplacementHandler == handler->GetHandlerInfo() ) return kReplacementHandler;
+ }
+ }
+
+ return kUnknown;
+}
+
+// =================================================================================================
+
+static bool CheckPluginArchitecture ( XMLParserAdapter * xmlParser ) {
+
+ #if XMP_MacBuild
+ bool okArchitecture = true; // Missing Architecture attribute means load on Mac.
+ #else
+ bool okArchitecture = false; // Missing Architecture attribute means do not load elsewhere.
+ #endif
+
+ #if XMP_64
+ const char * nativeArchitecture = "x64";
+ #else
+ const char * nativeArchitecture = "x86";
+ #endif
+
+ size_t i, limit;
+ XML_Node & xmlTree = xmlParser->tree;
+ XML_NodePtr rootElem = 0;
+
+ // Find the outermost XML element and see if it is PluginResource.
+ for ( i = 0, limit = xmlTree.content.size(); i < limit; ++i ) {
+ if ( xmlTree.content[i]->kind == kElemNode ) {
+ rootElem = xmlTree.content[i];
+ break;
+ }
+ }
+
+ if ( (rootElem == 0) || (rootElem->name != "PluginResource") ) return okArchitecture;
+
+ // Look for the Architecture attribute and see if it matches.
+
+ XML_NodePtr archAttr = 0;
+ for ( i = 0, limit = rootElem->attrs.size(); i < limit; ++i ) {
+ if ( rootElem->attrs[i]->name == "Architecture" ) {
+ archAttr = rootElem->attrs[i];
+ break;
+ }
+ }
+
+ if ( archAttr != 0 ) okArchitecture = (archAttr->value == nativeArchitecture);
+
+ return okArchitecture;
+
+} // CheckPluginArchitecture
+
+// =================================================================================================
+
+void PluginManager::loadResourceFile( ModuleSharedPtr module )
+{
+
+ OS_ModuleRef moduleRef = LoadModule ( module->getPath(), true );
+
+ if ( moduleRef != 0 ) {
+
+ XMLParserAdapter* parser = 0;
+
+ try {
+
+ std::string buffer;
+ if ( GetResourceDataFromModule ( moduleRef, kResourceName_UIDs, "txt", buffer ) ) {
+
+ ResourceParser::initialize(); // Initialize XMPAtoms before processing resource file.
+
+ parser = XMP_NewExpatAdapter ( ExpatAdapter::kUseGlobalNamespaces );
+ parser->ParseBuffer ( (XMP_Uns8*)buffer.c_str(), buffer.size(), true );
+
+ if ( CheckPluginArchitecture ( parser ) ) {
+ ResourceParser resource ( module );
+ resource.parseElementList ( &parser->tree, true );
+ }
+
+ delete parser;
+
+ }
+
+ } catch ( ... ) {
+
+ if ( parser != 0 ) delete parser;
+ // Otherwise ignore errors.
+
+ }
+
+ UnloadModule ( moduleRef, true );
+
+ }
+
+} // PluginManager::loadResourceFile
+
+// =================================================================================================
+
+void PluginManager::scanRecursive( const std::string & tempPath, std::vector<std::string>& ioFoundLibs, XMP_Int32 inLevel, XMP_Int32 inMaxNestingLevel )
+{
+ ++inLevel;
+ Host_IO::AutoFolder aFolder;
+ if ( Host_IO::GetFileMode( tempPath.c_str() ) != Host_IO::kFMode_IsFolder ) return;
+
+ aFolder.folder = Host_IO::OpenFolder( tempPath.c_str() );
+ std::string childPath, childName;
+
+ while ( Host_IO::GetNextChild ( aFolder.folder, &childName ) ) {
+
+ // Make sure the children of CONTENTS are legit.
+ childPath = tempPath;
+ childPath += kDirChar;
+ childPath += childName;
+ Host_IO::FileMode clientMode = Host_IO::GetFileMode ( childPath.c_str() );
+
+ bool okFolder = (clientMode == Host_IO::kFMode_IsFolder);
+ #if XMP_MacBuild
+ if ( okFolder ) okFolder = ( ! IsValidLibrary ( childPath ) );
+ #endif
+
+ // only step into non-packages (neither bundle nor framework) on Mac
+ if ( okFolder ) {
+
+ if ( inLevel < inMaxNestingLevel ) {
+ scanRecursive ( childPath + kDirChar, ioFoundLibs, inLevel, inMaxNestingLevel );
+ }
+
+ } else {
+
+ if ( childName[0] == '~' ) continue; // ignore plug-ins like "~PDFL.xpi"
+
+ std::string fileExt;
+ XMP_StringPtr extPos = childName.c_str() + childName.size();
+ for ( ; (extPos != childName.c_str()) && (*extPos != '.'); --extPos ) {}
+ if ( *extPos == '.' ) {
+ fileExt.assign ( extPos+1 );
+ MakeLowerCase ( &fileExt );
+ }
+
+ StringVec::const_iterator iterFound =
+ std::find_if ( mExtensions.begin(), mExtensions.end(),
+ std::bind2nd ( std::equal_to<std::string>(), fileExt ) );
+
+ if ( iterFound != mExtensions.end() ) {
+
+ //Check if the found plugin is present in the user's demanding plugin list.
+ childName.erase ( extPos - childName.c_str() );
+ MakeLowerCase ( &childName );
+
+ StringVec::const_iterator pluginNeeded =
+ std::find_if ( mPluginsNeeded.begin(), mPluginsNeeded.end(),
+ std::bind2nd ( std::equal_to<std::string>(), childName ) );
+
+ if ( (pluginNeeded != mPluginsNeeded.end()) || mPluginsNeeded.empty() ) {
+ ioFoundLibs.push_back ( childPath );
+ }
+
+ }
+
+ }
+
+ }
+
+ aFolder.Close();
+
+} // PluginManager::scanRecursive
+
+// =================================================================================================
+
+void PluginManager::doScan( const XMP_Int32 inMaxNumOfNestedFolder )
+{
+ XMP_Assert(inMaxNumOfNestedFolder > 0);
+ if ( inMaxNumOfNestedFolder < 1 ) return; // noop, wrong parameter
+
+ // scan directory
+ std::vector<std::string> foundLibs;
+ XMP_Int32 iteration = 0;
+ scanRecursive ( mPluginDir, foundLibs, iteration, inMaxNumOfNestedFolder );
+
+ // add found modules
+ std::vector<std::string>::const_iterator iter = foundLibs.begin();
+ std::vector<std::string>::const_iterator iterEnd = foundLibs.end();
+ for ( ; iter != iterEnd; ++iter ) {
+ std::string path ( *iter );
+ ModuleSharedPtr module ( new Module ( path ) );
+ loadResourceFile ( module );
+ }
+
+} // PluginManager::doScan
+
+// =================================================================================================
+
+HostAPIRef PluginManager::getHostAPI( XMP_Uns32 version )
+{
+ HostAPIRef hostAPI = NULL;
+
+ if( msPluginManager == NULL ) return NULL;
+ if( version < 1 ) return NULL;
+
+ HostAPIMap::iterator iter = msPluginManager->mHostAPIs.find( version );
+
+ if( iter != msPluginManager->mHostAPIs.end() )
+ {
+ hostAPI = iter->second;
+ }
+ else
+ {
+ hostAPI = new HostAPI();
+ hostAPI->mSize = sizeof( HostAPI );
+ hostAPI->mVersion = version;
+
+ switch( version )
+ {
+ case 1: SetupHostAPI_V1( hostAPI ); break;
+
+ default:
+ {
+ delete hostAPI;
+ hostAPI = NULL;
+ }
+ }
+
+ if( hostAPI != NULL )
+ {
+ msPluginManager->mHostAPIs[ version ] = hostAPI;
+ }
+ }
+
+ return hostAPI;
+}
+
+// =================================================================================================
+
+void PluginManager::terminateHostAPI()
+{
+ for( HostAPIMap::iterator it = msPluginManager->mHostAPIs.begin(); it != msPluginManager->mHostAPIs.end(); ++it )
+ {
+ XMP_Uns32 version = it->first;
+ HostAPIRef hostAPI = it->second;
+
+ switch( version )
+ {
+ case 1:
+ {
+ delete hostAPI->mFileIOAPI;
+ delete hostAPI->mStrAPI;
+ delete hostAPI->mAbortAPI;
+ delete hostAPI->mStandardHandlerAPI;
+ delete hostAPI;
+ }
+ break;
+
+ default:
+ {
+ delete hostAPI;
+ }
+ }
+ }
+}
+
+} // namespace XMP_PLUGIN
diff --git a/XMPFiles/source/PluginHandler/PluginManager.h b/XMPFiles/source/PluginHandler/PluginManager.h
new file mode 100644
index 0000000..9b2240e
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/PluginManager.h
@@ -0,0 +1,190 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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.
+// =================================================================================================
+
+#ifndef PLUGINMANAGER_H
+#define PLUGINMANAGER_H
+#include "PluginHandler.h"
+#include "ModuleUtils.h"
+
+namespace XMP_PLUGIN
+{
+
+typedef XMP_Uns32 XMPAtom;
+typedef XMPAtom FileHandlerType;
+
+class Module;
+typedef std::tr1::shared_ptr<Module> ModuleSharedPtr;
+
+class FileHandler;
+typedef std::tr1::shared_ptr<FileHandler> FileHandlerSharedPtr;
+
+class FileHandlerInstance;
+typedef FileHandlerInstance* FileHandlerInstancePtr;
+
+typedef std::vector<std::string> StringVec;
+typedef std::vector<ModuleSharedPtr> ModuleVec;
+typedef std::vector<XMP_FileFormat> XMP_FileFormatVec;
+
+struct FileHandlerPair;
+
+inline void CheckError( WXMP_Error & error )
+{
+ if( error.mErrorID != kXMPErr_NoError )
+ {
+ if( error.mErrorID >= 500 /* kXMPErr_PluginInternal */
+ || error.mErrorID <= 512 /* kXMPErr_SetHostAPI */ )
+ {
+ throw XMP_Error( kXMPErr_InternalFailure, error.mErrorMsg );
+ }
+ else
+ {
+ throw XMP_Error( error.mErrorID, error.mErrorMsg );
+ }
+ }
+}
+
+/** @class PluginManager
+ * @brief Register all file handlers from all the plugins available in Plugin directory.
+ *
+ * At the initialization time of XMPFiles, PluginManager loads all the avalbale plugins
+ */
+class PluginManager
+{
+public:
+ enum HandlerPriority
+ {
+ kStandardHandler,
+ kReplacementHandler,
+ kUnknown
+ };
+
+ /**
+ * Initialize the plugin manager. It's a singleton class which manages the plugins.
+ * @param pluginDir The directory where to search for the plugins.
+ * @param plugins Comma separated list of the plugins which should be loaded from the plugin directory.
+ * By default, all plug-ins available in the pluginDir will be loaded.
+ */
+ static void initialize( const std::string& pluginDir, const std::string& plugins = std::string() );
+
+ /**
+ * Terminate the plugin manager.
+ */
+ static void terminate();
+
+ /**
+ * Add file handler corresponding to the given format
+ * @param format FileFormat supported by the file handler
+ * @param handler shared pointer to the file handler which is to be added.
+ * @return Void.
+ */
+ static void addFileHandler( XMP_FileFormat format, FileHandlerSharedPtr handler );
+
+ /**
+ * Returns file handler corresponding to the given format and priority
+ *
+ * @param format FileFormat supported by the file handler
+ * @param priority The priority of the handler (there could be one standard and
+ * one replacing handler, use the enums kStandardHandler or kReplacementHandler)
+ * @return shared pointer to the file handler. It does not need to be freed.
+ */
+ static FileHandlerSharedPtr getFileHandler( XMP_FileFormat format, HandlerPriority priority = kStandardHandler );
+
+ /**
+ * Store mapping between session reference (comes from plugin) and
+ * FileHandlerInstance.
+ * @param session Session reference from plugin
+ * @param handler FileHandlerInstance related to the session
+ */
+ static void addHandlerInstance( SessionRef session, FileHandlerInstancePtr handler );
+
+ /**
+ * Remove mapping between session reference (comes from plugin) and
+ * FilehandlerInstance
+ * @param session Session reference from plugin
+ */
+ static void removeHandlerInstance( SessionRef session );
+
+ /**
+ * Return FileHandlerInstance that is associated to the session reference
+ * @param session Session reference from plugin
+ * @return FileHandlerInstance
+ */
+ static FileHandlerInstancePtr getHandlerInstance( SessionRef session );
+
+ /**
+ * Return the priority of the handler
+ * @param handler Instance of file handler
+ * @return Return kStandardHandler or kReplacementHandler
+ */
+ static HandlerPriority getHandlerPriority( FileHandlerInstancePtr handler );
+
+ /**
+ * Return Host API
+ */
+ static HostAPIRef getHostAPI( XMP_Uns32 version );
+
+private:
+ PluginManager( const std::string& pluginDir, const std::string& plugins );
+ ~PluginManager();
+
+ /**
+ * Terminate host API
+ */
+ void terminateHostAPI();
+
+ /**
+ * Load resource file of the given module.
+ * @param module
+ * @return Void.
+ */
+ void loadResourceFile( ModuleSharedPtr module );
+
+ /**
+ * Scan mPluginDir for the plugins. It also scans nested folder upto level inMaxNumOfNestedFolder.
+ * @param inMaxNumOfNestedFolder Nested level where to scan.
+ * @return Void.
+ */
+ void doScan( const XMP_Int32 inMaxNumOfNestedFolder = 1 );
+
+ /**
+ * Scan recursively the directory /a inPath and insert the found plug-in in ioFoundLibs.
+ * @param inPath Full path of the directory name to be scanned.
+ * @param ioFoundLibs Vector of string. Found plug-in will be inserted in this vector.
+ * @param inLevel The current level
+ * @param inMaxNumOfNestedFolder Nested level where to scan upto.
+ * @return Void.
+ */
+ void scanRecursive(
+ const std::string& inPath,
+ std::vector<std::string>& ioFoundLibs,
+ XMP_Int32 inLevel,
+ XMP_Int32 inMaxNestingLevel );
+
+ /**
+ * Setup passed in HostAPI structure for the host API v1
+ */
+ static void SetupHostAPI_V1( HostAPIRef hostAPI );
+
+ typedef std::map<XMP_FileFormat, FileHandlerPair> PluginHandlerMap;
+ typedef std::map<XMP_Uns32, HostAPIRef> HostAPIMap;
+ typedef std::map<SessionRef, FileHandlerInstancePtr> SessionMap;
+
+ std::string mPluginDir;
+ StringVec mExtensions;
+ StringVec mPluginsNeeded;
+ ModuleVec mModules;
+ PluginHandlerMap mHandlers;
+ SessionMap mSessions;
+ HostAPIMap mHostAPIs;
+
+ static PluginManager* msPluginManager;
+};
+
+} //namespace XMP_PLUGIN
+#endif //PLUGINMANAGER_H
diff --git a/XMPFiles/source/PluginHandler/XMPAtoms.cpp b/XMPFiles/source/PluginHandler/XMPAtoms.cpp
new file mode 100644
index 0000000..ed01027
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/XMPAtoms.cpp
@@ -0,0 +1,408 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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 "XMPAtoms.h"
+#include "XMPFiles/source/HandlerRegistry.h"
+
+using namespace Common;
+
+namespace XMP_PLUGIN
+{
+
+struct XMPAtomMapping
+{
+ XMP_StringPtr name;
+ XMPAtom atom;
+};
+
+const XMPAtomMapping kXMPAtomVec[] =
+{
+ { "", emptyStr_K },
+ { "Handler", Handler_K },
+ { "Extensions", Extensions_K },
+ { "Extension", Extension_K },
+ { "FormatIDs", FormatIDs_K },
+ { "FormatID", FormatID_K },
+ { "HandlerType", HandlerType_K },
+ { "Priority", OverwriteHdl_K },
+ { "HandlerFlags", HandlerFlags_K },
+ { "HandlerFlag", HandlerFlag_K },
+ { "SerializeOptions", SerializeOptions_K },
+ { "SerializeOption", SerializeOption_K },
+ { "Version", Version_K },
+ { "CheckFormat", CheckFormat_K },
+ { "Name", Name_K },
+ { "Offset", Offset_K },
+ { "Length", Length_K },
+ { "ByteSeq", ByteSeq_K },
+
+ // Handler types
+ { "NormalHandler", NormalHandler_K },
+ { "OwningHandler", OwningHandler_K },
+ { "FolderHandler", FolderHandler_K },
+
+ // Handler flags
+ { "kXMPFiles_CanInjectXMP", kXMPFiles_CanInjectXMP_K },
+ { "kXMPFiles_CanExpand", kXMPFiles_CanExpand_K },
+ { "kXMPFiles_CanRewrite", kXMPFiles_CanRewrite_K },
+ { "kXMPFiles_PrefersInPlace", kXMPFiles_PrefersInPlace_K },
+ { "kXMPFiles_CanReconcile", kXMPFiles_CanReconcile_K },
+ { "kXMPFiles_AllowsOnlyXMP", kXMPFiles_AllowsOnlyXMP_K },
+ { "kXMPFiles_ReturnsRawPacket", kXMPFiles_ReturnsRawPacket_K },
+ { "kXMPFiles_HandlerOwnsFile", kXMPFiles_HandlerOwnsFile_K },
+ { "kXMPFiles_AllowsSafeUpdate", kXMPFiles_AllowsSafeUpdate_K },
+ { "kXMPFiles_NeedsReadOnlyPacket", kXMPFiles_NeedsReadOnlyPacket_K },
+ { "kXMPFiles_UsesSidecarXMP", kXMPFiles_UsesSidecarXMP_K },
+ { "kXMPFiles_FolderBasedFormat", kXMPFiles_FolderBasedFormat_K },
+
+ // Serialize option
+ { "kXMP_OmitPacketWrapper", kXMP_OmitPacketWrapper_K },
+ { "kXMP_ReadOnlyPacket", kXMP_ReadOnlyPacket_K },
+ { "kXMP_UseCompactFormat", kXMP_UseCompactFormat_K },
+ { "kXMP_UseCanonicalFormat", kXMP_UseCanonicalFormat_K },
+ { "kXMP_IncludeThumbnailPad", kXMP_IncludeThumbnailPad_K },
+ { "kXMP_ExactPacketLength", kXMP_ExactPacketLength_K },
+ { "kXMP_OmitAllFormatting", kXMP_OmitAllFormatting_K },
+ { "kXMP_OmitXMPMetaElement", kXMP_OmitXMPMetaElement_K },
+ { "kXMP_EncodingMask", kXMP_EncodingMask_K },
+ { "kXMP_EncodeUTF8", kXMP_EncodeUTF8_K },
+ { "kXMP_EncodeUTF16Big", kXMP_EncodeUTF16Big_K },
+ { "kXMP_EncodeUTF16Little", kXMP_EncodeUTF16Little_K },
+ { "kXMP_EncodeUTF32Big", kXMP_EncodeUTF32Big_K },
+ { "kXMP_EncodeUTF32Little", kXMP_EncodeUTF32Little_K }
+
+};
+
+
+XMPAtomsMap* ResourceParser::msXMPAtoms = NULL;
+void ResourceParser::clear()
+{
+ mUID.clear();
+ mFileExtensions.clear();
+ mFormatIDs.clear();
+ mCheckFormat.clear();
+ mHandler = FileHandlerSharedPtr();
+ mFlags = mSerializeOption = mType = mVersion = 0;
+}
+
+void ResourceParser::addHandler()
+{
+ if( mUID.empty() || ( mFileExtensions.empty() && mFormatIDs.empty() ) || !isValidHandlerType( mType ) || mFlags == 0 )
+ {
+ XMP_Throw( "Atleast one of uid, format, ext, typeStr, flags non-valid ...", kXMPErr_Unavailable );
+ }
+ else
+ {
+ mHandler->setHandlerFlags( mFlags );
+ mHandler->setHandlerType( mType );
+ mHandler->setSerializeOption( mSerializeOption );
+ mHandler->setOverwriteHandler( mOverwriteHandler );
+ if( mVersion != 0) mHandler->setVersion( mVersion );
+
+ // A plugin could define the XMP_FileFormat value in manifest file through keyword "FormatID" and
+ // file extensions for NormalHandler and OwningHandler through keyword "Extension".
+ // If Both are defined then give priority to FormatID.
+
+ std::set<XMP_FileFormat> formatIDs = mFormatIDs.empty() ? mFileExtensions : mFormatIDs;
+ for( std::set<XMP_FileFormat>::const_iterator it = formatIDs.begin(); it != formatIDs.end(); ++it )
+ {
+ PluginManager::addFileHandler(*it, mHandler);
+ }
+ }
+}
+
+bool ResourceParser::parseElementAttrs( const XML_Node * xmlNode, bool isTopLevel )
+{
+ XMP_OptionBits exclusiveAttrs = 0; // Used to detect attributes that are mutually exclusive.
+
+ XMPAtom nodeAtom = getXMPAtomFromString( xmlNode->name );
+ if( nodeAtom == Handler_K )
+ this->clear();
+
+ XML_cNodePos currAttr = xmlNode->attrs.begin();
+ XML_cNodePos endAttr = xmlNode->attrs.end();
+
+ for( ; currAttr != endAttr; ++currAttr )
+ {
+ XMP_OptionBits oneflag=0;
+ XMP_FileFormat formatID = kXMP_UnknownFile;
+ std::string formatStr;
+ XMPAtom attrAtom = getXMPAtomFromString( (*currAttr)->name );
+ switch(nodeAtom)
+ {
+ case Handler_K:
+ switch(attrAtom)
+ {
+ case Name_K:
+ this->mUID = (*currAttr)->value;
+ mHandler = FileHandlerSharedPtr( new FileHandler( this->mUID, 0, 0, mModule ) );
+ break;
+ case Version_K:
+ this->mVersion = atoi( (*currAttr)->value.c_str() );
+ break;
+ case HandlerType_K:
+ this->mType = getXMPAtomFromString( (*currAttr)->value );
+ break;
+ case OverwriteHdl_K:
+ this->mOverwriteHandler = ( (*currAttr)->value == "true" );
+ break;
+ default:
+ XMP_Throw( "Invalid Attr in Handler encountered in resource file", kXMPErr_Unavailable );
+ }
+ break;
+ case CheckFormat_K:
+ switch(attrAtom)
+ {
+ case Offset_K:
+ this->mCheckFormat.mOffset = atoi( (*currAttr)->value.c_str() );
+ break;
+ case Length_K:
+ this->mCheckFormat.mLength = atoi( (*currAttr)->value.c_str() );
+ break;
+ case ByteSeq_K:
+ this->mCheckFormat.mByteSeq = (*currAttr)->value;
+ break;
+ default:
+ XMP_Throw( "Invalid Attr in CheckFormat encountered in resource file", kXMPErr_Unavailable );
+ }
+ break;
+ case Extension_K:
+ switch(attrAtom)
+ {
+ case Name_K:
+ this->mFileExtensions.insert( HandlerRegistry::getInstance().getFileFormat( (*currAttr)->value, true) );
+ break;
+ default:
+ XMP_Throw( "Invalid Attr in Extension encountered in resource file", kXMPErr_Unavailable );
+ }
+ break;
+ case FormatID_K:
+ switch(attrAtom)
+ {
+ case Name_K:
+ formatStr.assign( (*currAttr)->value );
+
+ // Convert string into 4-byte string by appending space '0x20' character.
+ for( size_t j=formatStr.size(); j<4; j++ )
+ formatStr.push_back(' ');
+
+ formatID = GetUns32BE( formatStr.c_str() );
+ this->mFormatIDs.insert( formatID );
+ break;
+ default:
+ XMP_Throw( "Invalid Attr in FormatID encountered in resource file", kXMPErr_Unavailable );
+ }
+ break;
+ case HandlerFlag_K:
+ switch(attrAtom)
+ {
+ case Name_K:
+ oneflag = getHandlerFlag( (*currAttr)->value );
+ if( 0 == oneflag )
+ XMP_Throw( "Invalid handler flag found in resource file...", kXMPErr_Unavailable );
+ this->mFlags |= oneflag;
+ break;
+ default:
+ XMP_Throw( "Invalid handler flag found in resource file...", kXMPErr_Unavailable );
+ }
+ break;
+ case SerializeOption_K:
+ switch(attrAtom)
+ {
+ case Name_K:
+ oneflag = getSerializeOption( (*currAttr)->value );
+ if( 0 == oneflag )
+ XMP_Throw( "Invalid serialize option found in resource file...", kXMPErr_Unavailable );
+ this->mSerializeOption |= oneflag;
+ break;
+ default:
+ XMP_Throw( "Invalid handler flag found in resource file...", kXMPErr_Unavailable );
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if( nodeAtom == CheckFormat_K )
+ mHandler->addCheckFormat( mCheckFormat );
+
+ return (nodeAtom == Handler_K) ? true : false;
+} // parseElementAttrs
+
+void ResourceParser::parseElement( const XML_Node * xmlNode, bool isTopLevel )
+{
+ bool HandlerFound = this->parseElementAttrs( xmlNode, isTopLevel );
+ this->parseElementList ( xmlNode, false );
+
+ if(HandlerFound)
+ {
+ this->addHandler();
+ }
+}
+
+void ResourceParser::parseElementList( const XML_Node * xmlParent, bool isTopLevel )
+{
+ initialize(); //Make sure XMPAtoms are initialized.
+
+ XML_cNodePos currChild = xmlParent->content.begin();
+ XML_cNodePos endChild = xmlParent->content.end();
+
+ for( ; currChild != endChild; ++currChild )
+ {
+ if( (*currChild)->IsWhitespaceNode() ) continue;
+ this->parseElement( *currChild, isTopLevel );
+ }
+}
+
+bool ResourceParser::initialize()
+{
+ //Check if already populated.
+ if( msXMPAtoms == NULL )
+ {
+ msXMPAtoms = new XMPAtomsMap();
+
+ //Create XMPAtomMap from XMPAtomVec, as we need to do a lot of searching while processing the resource file.
+ int count = sizeof(kXMPAtomVec)/sizeof(XMPAtomMapping);
+ for( int i=0; i<count; i++ )
+ {
+ XMP_StringPtr name = kXMPAtomVec[i].name;
+ XMPAtom atom = kXMPAtomVec[i].atom;
+ (*msXMPAtoms)[name] = atom;
+ }
+ }
+ return true;
+}
+
+void ResourceParser::terminate()
+{
+ delete msXMPAtoms;
+ msXMPAtoms = NULL;
+}
+
+XMPAtom ResourceParser::getXMPAtomFromString( const std::string & stringAtom )
+{
+ XMPAtomsMap::const_iterator it = msXMPAtoms->find(stringAtom);
+
+ if( it != msXMPAtoms->end() )
+ return it->second;
+
+ return XMPAtomNull;
+}
+
+//static
+XMP_OptionBits ResourceParser::getHandlerFlag( const std::string & stringAtom )
+{
+ XMPAtom atom = getXMPAtomFromString( stringAtom );
+
+ if( !isValidXMPAtom(atom) )
+ return 0;
+
+ switch( atom )
+ {
+ case kXMPFiles_CanInjectXMP_K:
+ return kXMPFiles_CanInjectXMP;
+ case kXMPFiles_CanExpand_K:
+ return kXMPFiles_CanExpand;
+ case kXMPFiles_CanRewrite_K:
+ return kXMPFiles_CanRewrite;
+ case kXMPFiles_PrefersInPlace_K:
+ return kXMPFiles_PrefersInPlace;
+ case kXMPFiles_CanReconcile_K:
+ return kXMPFiles_CanReconcile;
+ case kXMPFiles_AllowsOnlyXMP_K:
+ return kXMPFiles_AllowsOnlyXMP;
+ case kXMPFiles_ReturnsRawPacket_K:
+ return kXMPFiles_ReturnsRawPacket;
+ case kXMPFiles_HandlerOwnsFile_K:
+ return kXMPFiles_HandlerOwnsFile;
+ case kXMPFiles_AllowsSafeUpdate_K:
+ return kXMPFiles_AllowsSafeUpdate;
+ case kXMPFiles_NeedsReadOnlyPacket_K:
+ return kXMPFiles_NeedsReadOnlyPacket;
+ case kXMPFiles_UsesSidecarXMP_K:
+ return kXMPFiles_UsesSidecarXMP;
+ case kXMPFiles_FolderBasedFormat_K:
+ return kXMPFiles_FolderBasedFormat;
+ default:
+ XMP_Throw( "Invalid PluginhandlerFlag ...", kXMPErr_Unavailable );
+ return 0;
+ }
+}
+
+//static
+XMP_OptionBits ResourceParser::getSerializeOption( const std::string & stringAtom )
+{
+ XMPAtom atom = getXMPAtomFromString( stringAtom );
+
+ if( !isValidXMPAtom(atom) )
+ return 0;
+
+ switch( atom )
+ {
+ case kXMP_OmitPacketWrapper_K:
+ return kXMP_OmitPacketWrapper;
+ case kXMP_ReadOnlyPacket_K:
+ return kXMP_ReadOnlyPacket;
+ case kXMP_UseCompactFormat_K:
+ return kXMP_UseCompactFormat;
+ case kXMP_UseCanonicalFormat_K:
+ return kXMP_UseCanonicalFormat;
+ case kXMP_IncludeThumbnailPad_K:
+ return kXMP_IncludeThumbnailPad;
+ case kXMP_ExactPacketLength_K:
+ return kXMP_ExactPacketLength;
+ case kXMP_OmitAllFormatting_K:
+ return kXMP_OmitAllFormatting;
+ case kXMP_OmitXMPMetaElement_K:
+ return kXMP_OmitXMPMetaElement;
+ case kXMP_EncodingMask_K:
+ return kXMP_EncodingMask;
+ case kXMP_EncodeUTF8_K:
+ return kXMP_EncodeUTF8;
+ case kXMP_EncodeUTF16Big_K:
+ return kXMP_EncodeUTF16Big;
+ case kXMP_EncodeUTF16Little_K:
+ return kXMP_EncodeUTF16Little;
+ case kXMP_EncodeUTF32Big_K:
+ return kXMP_EncodeUTF32Big;
+ case kXMP_EncodeUTF32Little_K:
+ return kXMP_EncodeUTF32Little;
+ default:
+ XMP_Throw( "Invalid Serialize Option ...", kXMPErr_Unavailable );
+ return 0;
+ }
+}
+
+XMP_FileFormat ResourceParser::getPluginFileFormat( const std::string & fileExt, bool AddIfNotFound )
+{
+ XMP_FileFormat format = kXMP_UnknownFile;
+
+ if( msXMPAtoms )
+ {
+ XMPAtomsMap::const_iterator iter = msXMPAtoms->find( fileExt );
+ if( iter != msXMPAtoms->end() )
+ {
+ format = iter->second;
+ }
+ else if( AddIfNotFound )
+ {
+ std::string formatStr(fileExt);
+ MakeUpperCase(&formatStr);
+ for( size_t j=formatStr.size(); j<4; j++ ) // Convert "pdf" to "PDF "
+ formatStr.push_back(' ');
+ format = GetUns32BE( formatStr.c_str() ); //Convert "PDF " into kXMP_PDFFile
+ (*msXMPAtoms)[ fileExt ] = format;
+ }
+ }
+
+ return format;
+}
+
+} //namespace XMP_PLUGIN
diff --git a/XMPFiles/source/PluginHandler/XMPAtoms.h b/XMPFiles/source/PluginHandler/XMPAtoms.h
new file mode 100644
index 0000000..5004008
--- /dev/null
+++ b/XMPFiles/source/PluginHandler/XMPAtoms.h
@@ -0,0 +1,200 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2011 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.
+// =================================================================================================
+
+#ifndef _H_XMPATOMS
+#define _H_XMPATOMS
+#include "FileHandler.h"
+#include "source/ExpatAdapter.hpp"
+#include <set>
+
+namespace XMP_PLUGIN
+{
+
+/** @brief An enum required to parse the resource file of plugin.
+ */
+enum
+{
+ emptyStr_K = 0, //Empty string
+
+ // Mandatory keys in the resource file
+ Handler_K,
+ Extensions_K,
+ Extension_K,
+ FormatIDs_K,
+ FormatID_K,
+ HandlerType_K,
+ OverwriteHdl_K,
+ HandlerFlags_K,
+ HandlerFlag_K,
+ SerializeOptions_K,
+ SerializeOption_K,
+ Version_K,
+ CheckFormat_K,
+ Name_K,
+ Offset_K,
+ Length_K,
+ ByteSeq_K,
+
+ // Handler types
+ NormalHandler_K,
+ OwningHandler_K,
+ FolderHandler_K,
+
+ // Handler flags
+ kXMPFiles_CanInjectXMP_K,
+ kXMPFiles_CanExpand_K,
+ kXMPFiles_CanRewrite_K,
+ kXMPFiles_PrefersInPlace_K,
+ kXMPFiles_CanReconcile_K,
+ kXMPFiles_AllowsOnlyXMP_K,
+ kXMPFiles_ReturnsRawPacket_K,
+ kXMPFiles_HandlerOwnsFile_K,
+ kXMPFiles_AllowsSafeUpdate_K,
+ kXMPFiles_NeedsReadOnlyPacket_K,
+ kXMPFiles_UsesSidecarXMP_K,
+ kXMPFiles_FolderBasedFormat_K,
+
+ // Serialize option
+ kXMP_OmitPacketWrapper_K,
+ kXMP_ReadOnlyPacket_K,
+ kXMP_UseCompactFormat_K,
+ kXMP_UseCanonicalFormat_K,
+ kXMP_IncludeThumbnailPad_K,
+ kXMP_ExactPacketLength_K,
+ kXMP_OmitAllFormatting_K,
+ kXMP_OmitXMPMetaElement_K,
+ kXMP_EncodingMask_K,
+ kXMP_EncodeUTF8_K,
+ kXMP_EncodeUTF16Big_K,
+ kXMP_EncodeUTF16Little_K,
+ kXMP_EncodeUTF32Big_K,
+ kXMP_EncodeUTF32Little_K,
+
+ //Last element
+ lastfinal_K
+};
+
+#define XMPAtomNull emptyStr_K
+
+struct StringCompare : std::binary_function<const std::string &, const std::string &, bool>
+{
+ bool operator() (const std::string & a, const std::string & b) const
+ {
+ return ( a.compare(b) < 0 );
+ }
+};
+typedef std::map<std::string, XMPAtom, StringCompare> XMPAtomsMap;
+
+
+/** @class ResourceParser
+ * @brief Class to parse resource file of the plugin.
+ */
+class ResourceParser
+{
+public:
+ ResourceParser(ModuleSharedPtr module)
+ : mModule(module), mFlags(0), mSerializeOption(0), mType(0), mVersion(0), mOverwriteHandler(false) {}
+
+ /**
+ * Initialize the XMPAtoms which will be used in parsing resource files.
+ * @return true on success.
+ */
+ static bool initialize();
+ static void terminate();
+
+ /** @brief Return file format corresponding to file extension \a fileExt.
+ *
+ * It is similar to GetXMPFileFormat except that it also searches in PluginManager's
+ * private file formats. If the extension is not a public file format and \a AddIfNotFound is true
+ * then it also adds the private fileformat. The priavte 4-byte file format is created by converting
+ * extension to upper case and appending space '0x20' to make it 4-byte. For i.e
+ * "pdf" -> 'PDF ' (0x50444620)
+ * "tmp" -> 'TMP ' (0x54415020)
+ * "temp" -> 'TEMP' (0x54454150)
+ *
+ * @param fileExt Extension string of the file. For i.e "pdf"
+ * @param AddIfNotFound If AddIfNotFound is true and public format is not found then
+ * priavte format is added.
+ * @return File format correspoding to file extension \a fileExt.
+ */
+ static XMP_FileFormat getPluginFileFormat( const std::string & fileExt, bool AddIfNotFound );
+
+ /**
+ * Parse the XML node's children recursively.
+ * @param xmlParent XMLNode which will be parsed.
+ * @return Void.
+ */
+ void parseElementList( const XML_Node * xmlParent, bool isTopLevel );
+
+private:
+ void clear();
+ void addHandler();
+
+ /**
+ * Parse the XML node's attribute.
+ * @param xmlNode XMLNode which will be parsed.
+ * @return true if xmlNode's name is Handler_K.
+ */
+ bool parseElementAttrs( const XML_Node * xmlNode, bool isTopLevel );
+
+ /**
+ * Parse the XML node.
+ * @param xmlNode XMLNode which will be parsed.
+ * @return Void.
+ */
+ void parseElement( const XML_Node * xmlNode, bool isTopLevel );
+
+ /**
+ * Return XMPAtom corresponding to string.
+ * @param stringAtom std::string whose XMPAtom will be return.
+ * @return XMPAtom corresponding to string.
+ */
+ static XMPAtom getXMPAtomFromString( const std::string & stringAtom );
+
+ /**
+ * Return Handler flag corresponding to XMPAtom string.
+ * @param stringAtom Handler flag string
+ * @return Handler flag.
+ */
+ static XMP_OptionBits getHandlerFlag( const std::string & stringAtom );
+
+ /**
+ * Return Serialize option corresponding to XMPAtom string.
+ * @param stringAtom Handler flag string
+ * @return Serialize option.
+ */
+ static XMP_OptionBits getSerializeOption( const std::string & stringAtom );
+
+ static inline bool isValidXMPAtom( XMPAtom atom )
+ {
+ return ( atom > emptyStr_K && atom < lastfinal_K );
+ }
+
+ static inline bool isValidHandlerType(XMPAtom atom)
+ {
+ return ( atom >= NormalHandler_K && atom <= FolderHandler_K );
+ }
+
+ ModuleSharedPtr mModule;
+ std::string mUID;
+ FileHandlerType mType;
+ XMP_OptionBits mFlags;
+ XMP_OptionBits mSerializeOption;
+ XMP_Uns32 mVersion;
+ bool mOverwriteHandler;
+ CheckFormat mCheckFormat;
+ std::set<XMP_FileFormat> mFileExtensions;
+ std::set<XMP_FileFormat> mFormatIDs;
+ FileHandlerSharedPtr mHandler;
+
+ static XMPAtomsMap * msXMPAtoms;
+};
+
+} //namespace XMP_PLUGIN
+#endif // _H_XMPATOMS
diff --git a/XMPFiles/source/WXMPFiles.cpp b/XMPFiles/source/WXMPFiles.cpp
new file mode 100644
index 0000000..f0f7b52
--- /dev/null
+++ b/XMPFiles/source/WXMPFiles.cpp
@@ -0,0 +1,345 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h"
+#include "public/include/XMP_Const.h"
+
+#include "public/include/client-glue/WXMPFiles.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "XMPFiles/source/XMPFiles.hpp"
+
+#if XMP_WinBuild
+ #if XMP_DebugBuild
+ #pragma warning ( disable : 4297 ) // function assumed not to throw an exception but does
+ #endif
+#endif
+
+#if __cplusplus
+extern "C" {
+#endif
+
+// =================================================================================================
+
+static WXMP_Result voidResult; // Used for functions that don't use the normal result mechanism.
+
+// =================================================================================================
+
+void WXMPFiles_GetVersionInfo_1 ( XMP_VersionInfo * versionInfo )
+{
+ WXMP_Result * wResult = &voidResult; // ! Needed to "fool" the EnterWrapper macro.
+ XMP_ENTER_NoLock ( "WXMPFiles_GetVersionInfo_1" )
+
+ XMPFiles::GetVersionInfo ( versionInfo );
+
+ XMP_EXIT_NoThrow
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void WXMPFiles_Initialize_1 ( XMP_OptionBits options,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_NoLock ( "WXMPFiles_Initialize_1" )
+
+ wResult->int32Result = XMPFiles::Initialize ( options, NULL, NULL );
+
+ XMP_EXIT
+}
+
+// -------------------------------------------------------------------------------------------------
+
+// -------------------------------------------------------------------------------------------------
+
+void WXMPFiles_Initialize_2 ( XMP_OptionBits options,
+ const char * pluginFolder,
+ const char * plugins,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_NoLock ( "WXMPFiles_Initialize_1" )
+
+ wResult->int32Result = XMPFiles::Initialize ( options, pluginFolder, plugins );
+
+ XMP_EXIT
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void WXMPFiles_Terminate_1()
+{
+ WXMP_Result * wResult = &voidResult; // ! Needed to "fool" the EnterWrapper macro.
+ XMP_ENTER_NoLock ( "WXMPFiles_Terminate_1" )
+
+ XMPFiles::Terminate();
+
+ XMP_EXIT_NoThrow
+}
+
+// =================================================================================================
+
+void WXMPFiles_CTor_1 ( WXMP_Result * wResult )
+{
+ XMP_ENTER_Static ( "WXMPFiles_CTor_1" ) // No lib object yet, use the static entry.
+
+ XMPFiles * newObj = new XMPFiles();
+ ++newObj->clientRefs;
+ XMP_Assert ( newObj->clientRefs == 1 );
+ wResult->ptrResult = newObj;
+
+ XMP_EXIT
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void WXMPFiles_IncrementRefCount_1 ( XMPFilesRef xmpObjRef )
+{
+ WXMP_Result * wResult = &voidResult; // ! Needed to "fool" the EnterWrapper macro.
+ XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_IncrementRefCount_1" )
+
+ ++thiz->clientRefs;
+ XMP_Assert ( thiz->clientRefs > 0 );
+
+ XMP_EXIT_NoThrow
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void WXMPFiles_DecrementRefCount_1 ( XMPFilesRef xmpObjRef )
+{
+ WXMP_Result * wResult = &voidResult; // ! Needed to "fool" the EnterWrapper macro.
+ XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_DecrementRefCount_1" )
+
+ XMP_Assert ( thiz->clientRefs > 0 );
+ --thiz->clientRefs;
+ if ( thiz->clientRefs <= 0 ) {
+ objLock.Release();
+ delete ( thiz );
+ }
+
+ XMP_EXIT_NoThrow
+}
+
+// =================================================================================================
+
+void WXMPFiles_GetFormatInfo_1 ( XMP_FileFormat format,
+ XMP_OptionBits * flags,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_Static ( "WXMPFiles_GetFormatInfo_1" )
+
+ wResult->int32Result = XMPFiles::GetFormatInfo ( format, flags );
+
+ XMP_EXIT
+}
+
+// =================================================================================================
+
+void WXMPFiles_CheckFileFormat_1 ( XMP_StringPtr filePath,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_Static ( "WXMPFiles_CheckFileFormat_1" )
+
+ wResult->int32Result = XMPFiles::CheckFileFormat ( filePath );
+
+ XMP_EXIT
+}
+
+// =================================================================================================
+
+void WXMPFiles_CheckPackageFormat_1 ( XMP_StringPtr folderPath,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_Static ( "WXMPFiles_CheckPackageFormat_1" )
+
+ wResult->int32Result = XMPFiles::CheckPackageFormat ( folderPath );
+
+ XMP_EXIT
+}
+
+// =================================================================================================
+
+void WXMPFiles_GetFileModDate_1 ( XMP_StringPtr filePath,
+ XMP_DateTime * modDate,
+ XMP_FileFormat * format,
+ XMP_OptionBits options,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_Static ( "WXMPFiles_GetFileModDate_1" )
+
+ wResult->int32Result = XMPFiles::GetFileModDate ( filePath, modDate, format, options );
+
+ XMP_EXIT
+}
+
+// =================================================================================================
+
+void WXMPFiles_OpenFile_1 ( XMPFilesRef xmpObjRef,
+ XMP_StringPtr filePath,
+ XMP_FileFormat format,
+ XMP_OptionBits openFlags,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_OpenFile_1" )
+ StartPerfCheck ( kAPIPerf_OpenFile, filePath );
+
+ bool ok = thiz->OpenFile ( filePath, format, openFlags );
+ wResult->int32Result = ok;
+
+ EndPerfCheck ( kAPIPerf_OpenFile );
+ XMP_EXIT
+}
+
+// -------------------------------------------------------------------------------------------------
+
+#if XMP_StaticBuild // ! Client XMP_IO objects can only be used in static builds.
+void WXMPFiles_OpenFile_2 ( XMPFilesRef xmpObjRef,
+ XMP_IO* clientIO,
+ XMP_FileFormat format,
+ XMP_OptionBits openFlags,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_OpenFile_2" )
+ StartPerfCheck ( kAPIPerf_OpenFile, "<client-managed>" );
+
+ bool ok = thiz->OpenFile ( clientIO, format, openFlags );
+ wResult->int32Result = ok;
+
+ EndPerfCheck ( kAPIPerf_OpenFile );
+ XMP_EXIT
+}
+#endif
+
+// -------------------------------------------------------------------------------------------------
+
+void WXMPFiles_CloseFile_1 ( XMPFilesRef xmpObjRef,
+ XMP_OptionBits closeFlags,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_CloseFile_1" )
+ StartPerfCheck ( kAPIPerf_CloseFile, "" );
+
+ thiz->CloseFile ( closeFlags );
+
+ EndPerfCheck ( kAPIPerf_CloseFile );
+ XMP_EXIT
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void WXMPFiles_GetFileInfo_1 ( XMPFilesRef xmpObjRef,
+ void * clientPath,
+ XMP_OptionBits * openFlags,
+ XMP_FileFormat * format,
+ XMP_OptionBits * handlerFlags,
+ SetClientStringProc SetClientString,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_ObjRead ( XMPFiles, "WXMPFiles_GetFileInfo_1" )
+
+ XMP_StringPtr pathStr;
+ XMP_StringLen pathLen;
+
+ bool isOpen = thiz.GetFileInfo ( &pathStr, &pathLen, openFlags, format, handlerFlags );
+ if ( isOpen && (clientPath != 0) ) (*SetClientString) ( clientPath, pathStr, pathLen );
+ wResult->int32Result = isOpen;
+
+ XMP_EXIT
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void WXMPFiles_SetAbortProc_1 ( XMPFilesRef xmpObjRef,
+ XMP_AbortProc abortProc,
+ void * abortArg,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_SetAbortProc_1" )
+
+ thiz->SetAbortProc ( abortProc, abortArg );
+
+ XMP_EXIT
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void WXMPFiles_GetXMP_1 ( XMPFilesRef xmpObjRef,
+ XMPMetaRef xmpRef,
+ void * clientPacket,
+ XMP_PacketInfo * packetInfo,
+ SetClientStringProc SetClientString,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_GetXMP_1" )
+ StartPerfCheck ( kAPIPerf_GetXMP, "" );
+
+ bool hasXMP = false;
+ XMP_StringPtr packetStr;
+ XMP_StringLen packetLen;
+
+ if ( xmpRef == 0 ) {
+ hasXMP = thiz->GetXMP ( 0, &packetStr, &packetLen, packetInfo );
+ } else {
+ SXMPMeta xmpObj ( xmpRef );
+ hasXMP = thiz->GetXMP ( &xmpObj, &packetStr, &packetLen, packetInfo );
+ }
+
+ if ( hasXMP && (clientPacket != 0) ) (*SetClientString) ( clientPacket, packetStr, packetLen );
+ wResult->int32Result = hasXMP;
+
+ EndPerfCheck ( kAPIPerf_GetXMP );
+ XMP_EXIT
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void WXMPFiles_PutXMP_1 ( XMPFilesRef xmpObjRef,
+ XMPMetaRef xmpRef, // ! Only one of the XMP object or packet are passed.
+ XMP_StringPtr xmpPacket,
+ XMP_StringLen xmpPacketLen,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_PutXMP_1" )
+ StartPerfCheck ( kAPIPerf_PutXMP, "" );
+
+ if ( xmpRef != 0 ) {
+ thiz->PutXMP ( xmpRef );
+ } else {
+ thiz->PutXMP ( xmpPacket, xmpPacketLen );
+ }
+
+ EndPerfCheck ( kAPIPerf_PutXMP );
+ XMP_EXIT
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void WXMPFiles_CanPutXMP_1 ( XMPFilesRef xmpObjRef,
+ XMPMetaRef xmpRef, // ! Only one of the XMP object or packet are passed.
+ XMP_StringPtr xmpPacket,
+ XMP_StringLen xmpPacketLen,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_CanPutXMP_1" )
+ StartPerfCheck ( kAPIPerf_CanPutXMP, "" );
+
+ if ( xmpRef != 0 ) {
+ wResult->int32Result = thiz->CanPutXMP ( xmpRef );
+ } else {
+ wResult->int32Result = thiz->CanPutXMP ( xmpPacket, xmpPacketLen );
+ }
+
+ EndPerfCheck ( kAPIPerf_CanPutXMP );
+ XMP_EXIT
+}
+
+// =================================================================================================
+
+#if __cplusplus
+}
+#endif
diff --git a/XMPFiles/source/XMPFiles.cpp b/XMPFiles/source/XMPFiles.cpp
new file mode 100644
index 0000000..b720725
--- /dev/null
+++ b/XMPFiles/source/XMPFiles.cpp
@@ -0,0 +1,1084 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! Must be the first #include!
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include <vector>
+#include <string.h>
+
+#include "source/UnicodeConversions.hpp"
+#include "source/XMPFiles_IO.hpp"
+#include "source/XIO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "XMPFiles/source/HandlerRegistry.h"
+#include "XMPFiles/source/PluginHandler/PluginManager.h"
+
+#include "XMPFiles/source/FormatSupport/ID3_Support.hpp"
+
+#if EnablePacketScanning
+ #include "XMPFiles/source/FileHandlers/Scanner_Handler.hpp"
+#endif
+
+// =================================================================================================
+/// \file XMPFiles.cpp
+/// \brief High level support to access metadata in files of interest to Adobe applications.
+///
+/// This header ...
+///
+// =================================================================================================
+
+using namespace Common;
+using namespace XMP_PLUGIN;
+
+// =================================================================================================
+
+XMP_Int32 sXMPFilesInitCount = 0;
+
+#if GatherPerformanceData
+ APIPerfCollection* sAPIPerf = 0;
+#endif
+
+// These are embedded version strings.
+
+#if XMP_DebugBuild
+ #define kXMPFiles_DebugFlag 1
+#else
+ #define kXMPFiles_DebugFlag 0
+#endif
+
+#define kXMPFiles_VersionNumber ( (kXMPFiles_DebugFlag << 31) | \
+ (XMP_API_VERSION_MAJOR << 24) | \
+ (XMP_API_VERSION_MINOR << 16) | \
+ (XMP_API_VERSION_MICRO << 8) )
+
+ #define kXMPFilesName "XMP Files"
+ #define kXMPFiles_VersionMessage kXMPFilesName " " XMPFILES_API_VERSION_STRING
+const char * kXMPFiles_EmbeddedVersion = kXMPFiles_VersionMessage;
+const char * kXMPFiles_EmbeddedCopyright = kXMPFilesName " " kXMP_CopyrightStr;
+
+// =================================================================================================
+
+#if EnablePacketScanning
+ static XMPFileHandlerInfo kScannerHandlerInfo ( kXMP_UnknownFile, kScanner_HandlerFlags,
+ (CheckFileFormatProc)0, Scanner_MetaHandlerCTor );
+#endif
+
+// =================================================================================================
+
+/* class-static */
+void
+XMPFiles::GetVersionInfo ( XMP_VersionInfo * info )
+{
+
+ memset ( info, 0, sizeof(XMP_VersionInfo) );
+
+ info->major = XMPFILES_API_VERSION_MAJOR;
+ info->minor = XMPFILES_API_VERSION_MINOR;
+ info->micro = 0; //no longer used
+ info->isDebug = kXMPFiles_DebugFlag;
+ info->flags = 0; // ! None defined yet.
+ info->message = kXMPFiles_VersionMessage;
+
+} // XMPFiles::GetVersionInfo
+
+// =================================================================================================
+
+#if XMP_TraceFilesCalls
+ FILE * xmpFilesLog = stderr;
+#endif
+
+#if UseGlobalLibraryLock & (! XMP_StaticBuild )
+ XMP_BasicMutex sLibraryLock; // ! Handled in XMPMeta for static builds.
+#endif
+
+/* class static */
+bool
+XMPFiles::Initialize( XMP_OptionBits options, const char* pluginFolder, const char* plugins /* = NULL */ )
+{
+ ++sXMPFilesInitCount;
+ if ( sXMPFilesInitCount > 1 ) return true;
+
+ #if XMP_TraceFilesCallsToFile
+ xmpFilesLog = fopen ( "XMPFilesLog.txt", "w" );
+ if ( xmpFilesLog == 0 ) xmpFilesLog = stderr;
+ #endif
+
+ #if UseGlobalLibraryLock & (! XMP_StaticBuild )
+ InitializeBasicMutex ( sLibraryLock ); // ! Handled in XMPMeta for static builds.
+ #endif
+
+ SXMPMeta::Initialize(); // Just in case the client does not.
+
+ if ( ! Initialize_LibUtils() ) return false;
+ if ( ! ID3_Support::InitializeGlobals() ) return false;
+
+ #if GatherPerformanceData
+ sAPIPerf = new APIPerfCollection;
+ #endif
+
+ XMP_Uns16 endianInt = 0x00FF;
+ XMP_Uns8 endianByte = *((XMP_Uns8*)&endianInt);
+ if ( kBigEndianHost ) {
+ if ( endianByte != 0 ) XMP_Throw ( "Big endian flag mismatch", kXMPErr_InternalFailure );
+ } else {
+ if ( endianByte != 0xFF ) XMP_Throw ( "Little endian flag mismatch", kXMPErr_InternalFailure );
+ }
+
+ XMP_Assert ( kUTF8_PacketHeaderLen == strlen ( "<?xpacket begin='xxx' id='W5M0MpCehiHzreSzNTczkc9d'" ) );
+ XMP_Assert ( kUTF8_PacketTrailerLen == strlen ( (const char *) kUTF8_PacketTrailer ) );
+
+ HandlerRegistry::getInstance().initialize();
+
+ InitializeUnicodeConversions();
+
+ ignoreLocalText = XMP_OptionIsSet ( options, kXMPFiles_IgnoreLocalText );
+ #if XMP_UNIXBuild
+ if ( ! ignoreLocalText ) XMP_Throw ( "Generic UNIX clients must pass kXMPFiles_IgnoreLocalText", kXMPErr_EnforceFailure );
+ #endif
+
+ if ( pluginFolder != 0 ) {
+ std::string pluginList;
+ if ( plugins != 0 ) pluginList.assign ( plugins );
+ PluginManager::initialize ( pluginFolder, pluginList ); // Load file handler plugins.
+ }
+
+ // Make sure the embedded info strings are referenced and kept.
+ if ( (kXMPFiles_EmbeddedVersion[0] == 0) || (kXMPFiles_EmbeddedCopyright[0] == 0) ) return false;
+ // Verify critical type sizes.
+ XMP_Assert ( sizeof(XMP_Int8) == 1 );
+ XMP_Assert ( sizeof(XMP_Int16) == 2 );
+ XMP_Assert ( sizeof(XMP_Int32) == 4 );
+ XMP_Assert ( sizeof(XMP_Int64) == 8 );
+ XMP_Assert ( sizeof(XMP_Uns8) == 1 );
+ XMP_Assert ( sizeof(XMP_Uns16) == 2 );
+ XMP_Assert ( sizeof(XMP_Uns32) == 4 );
+ XMP_Assert ( sizeof(XMP_Uns64) == 8 );
+
+ return true;
+
+} // XMPFiles::Initialize
+
+// =================================================================================================
+
+#if GatherPerformanceData
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+#include "PerfUtils.cpp"
+
+static void ReportPerformanceData()
+{
+ struct SummaryInfo {
+ size_t callCount;
+ double totalTime;
+ SummaryInfo() : callCount(0), totalTime(0.0) {};
+ };
+
+ SummaryInfo perfSummary [kAPIPerfProcCount];
+
+ XMP_DateTime now;
+ SXMPUtils::CurrentDateTime ( &now );
+ std::string nowStr;
+ SXMPUtils::ConvertFromDate ( now, &nowStr );
+
+ #if XMP_WinBuild
+ #define kPerfLogPath "C:\\XMPFilesPerformanceLog.txt"
+ #else
+ #define kPerfLogPath "/XMPFilesPerformanceLog.txt"
+ #endif
+ FILE * perfLog = fopen ( kPerfLogPath, "ab" );
+ if ( perfLog == 0 ) return;
+
+ fprintf ( perfLog, "\n\n// =================================================================================================\n\n" );
+ fprintf ( perfLog, "XMPFiles performance data\n" );
+ fprintf ( perfLog, " %s\n", kXMPFiles_VersionMessage );
+ fprintf ( perfLog, " Reported at %s\n", nowStr.c_str() );
+ fprintf ( perfLog, " %s\n", PerfUtils::GetTimerInfo() );
+
+ // Gather and report the summary info.
+
+ for ( size_t i = 0; i < sAPIPerf->size(); ++i ) {
+ SummaryInfo& summaryItem = perfSummary [(*sAPIPerf)[i].whichProc];
+ ++summaryItem.callCount;
+ summaryItem.totalTime += (*sAPIPerf)[i].elapsedTime;
+ }
+
+ fprintf ( perfLog, "\nSummary data:\n" );
+
+ for ( size_t i = 0; i < kAPIPerfProcCount; ++i ) {
+ long averageTime = 0;
+ if ( perfSummary[i].callCount != 0 ) {
+ averageTime = (long) (((perfSummary[i].totalTime/perfSummary[i].callCount) * 1000.0*1000.0) + 0.5);
+ }
+ fprintf ( perfLog, " %s, %d total calls, %d average microseconds per call\n",
+ kAPIPerfNames[i], perfSummary[i].callCount, averageTime );
+ }
+
+ fprintf ( perfLog, "\nPer-call data:\n" );
+
+ // Report the info for each call.
+
+ for ( size_t i = 0; i < sAPIPerf->size(); ++i ) {
+ long time = (long) (((*sAPIPerf)[i].elapsedTime * 1000.0*1000.0) + 0.5);
+ fprintf ( perfLog, " %s, %d microSec, ref %.8X, \"%s\"\n",
+ kAPIPerfNames[(*sAPIPerf)[i].whichProc], time,
+ (*sAPIPerf)[i].xmpFilesRef, (*sAPIPerf)[i].extraInfo.c_str() );
+ }
+
+ fclose ( perfLog );
+
+} // ReportAReportPerformanceDataPIPerformance
+
+#endif
+
+// =================================================================================================
+
+/* class static */
+void
+XMPFiles::Terminate()
+{
+ --sXMPFilesInitCount;
+ if ( sXMPFilesInitCount != 0 ) return; // Not ready to terminate, or already terminated.
+
+ #if GatherPerformanceData
+ ReportPerformanceData();
+ EliminateGlobal ( sAPIPerf );
+ #endif
+
+ PluginManager::terminate();
+ HandlerRegistry::terminate();
+
+ SXMPMeta::Terminate(); // Just in case the client does not.
+
+ ID3_Support::TerminateGlobals();
+ Terminate_LibUtils();
+
+ #if UseGlobalLibraryLock & (! XMP_StaticBuild )
+ TerminateBasicMutex ( sLibraryLock ); // ! Handled in XMPMeta for static builds.
+ #endif
+
+ #if XMP_TraceFilesCallsToFile
+ if ( xmpFilesLog != stderr ) fclose ( xmpFilesLog );
+ xmpFilesLog = stderr;
+ #endif
+
+} // XMPFiles::Terminate
+
+// =================================================================================================
+
+XMPFiles::XMPFiles() :
+ clientRefs(0),
+ format(kXMP_UnknownFile),
+ ioRef(0),
+ openFlags(0),
+ handler(0),
+ tempPtr(0),
+ tempUI32(0),
+ abortProc(0),
+ abortArg(0)
+{
+ // Nothing more to do, clientRefs is incremented in wrapper.
+
+} // XMPFiles::XMPFiles
+
+// =================================================================================================
+
+static inline void CloseLocalFile ( XMPFiles* thiz )
+{
+ if ( thiz->UsesLocalIO() ) {
+
+ XMPFiles_IO* localFile = (XMPFiles_IO*)thiz->ioRef;
+
+ if ( localFile != 0 ) {
+ localFile->Close();
+ delete localFile;
+ thiz->ioRef = 0;
+ }
+
+ }
+
+} // CloseLocalFile
+
+// =================================================================================================
+
+XMPFiles::~XMPFiles()
+{
+ XMP_Assert ( this->clientRefs <= 0 );
+
+ if ( this->handler != 0 ) {
+ delete this->handler;
+ this->handler = 0;
+ }
+
+ CloseLocalFile ( this );
+
+ if ( this->tempPtr != 0 ) free ( this->tempPtr ); // ! Must have been malloc-ed!
+
+} // XMPFiles::~XMPFiles
+
+// =================================================================================================
+
+/* class static */
+bool
+XMPFiles::GetFormatInfo ( XMP_FileFormat format,
+ XMP_OptionBits * flags /* = 0 */ )
+{
+
+ return HandlerRegistry::getInstance().getFormatInfo ( format, flags );
+
+} // XMPFiles::GetFormatInfo
+
+// =================================================================================================
+
+/* class static */
+XMP_FileFormat
+XMPFiles::CheckFileFormat ( XMP_StringPtr clientPath )
+{
+ if ( (clientPath == 0) || (*clientPath == 0) ) return kXMP_UnknownFile;
+
+ XMPFiles bogus; // Needed to provide context to SelectSmartHandler.
+ bogus.filePath = clientPath; // So that XMPFiles destructor cleans up the XMPFiles_IO object.
+ XMPFileHandlerInfo * handlerInfo = HandlerRegistry::getInstance().selectSmartHandler( &bogus, clientPath, kXMP_UnknownFile, kXMPFiles_OpenForRead );
+
+ if ( handlerInfo == 0 ) return kXMP_UnknownFile;
+ return handlerInfo->format;
+
+} // XMPFiles::CheckFileFormat
+
+// =================================================================================================
+
+/* class static */
+XMP_FileFormat
+XMPFiles::CheckPackageFormat ( XMP_StringPtr folderPath )
+{
+ // This is called with a path to a folder, and checks to see if that folder is the top level of
+ // a "package" that should be recognized by one of the folder-oriented handlers. The checks here
+ // are not overly extensive, but hopefully enough to weed out false positives.
+ //
+ // Since we don't have many folder handlers, this is simple hardwired code.
+
+ #if ! EnableDynamicMediaHandlers
+ return kXMP_UnknownFile;
+ #else
+ Host_IO::FileMode folderMode = Host_IO::GetFileMode ( folderPath );
+ if ( folderMode != Host_IO::kFMode_IsFolder ) return kXMP_UnknownFile;
+ return HandlerRegistry::checkTopFolderName ( std::string ( folderPath ) );
+ #endif
+
+} // XMPFiles::CheckPackageFormat
+
+// =================================================================================================
+
+static bool FileIsExcluded ( XMP_StringPtr clientPath, std::string * fileExt, Host_IO::FileMode * clientMode )
+{
+ // ! Return true for excluded files, false for OK files.
+
+ *clientMode = Host_IO::GetFileMode ( clientPath );
+ if ( (*clientMode == Host_IO::kFMode_IsFolder) || (*clientMode == Host_IO::kFMode_IsOther) ) return true;
+ XMP_Assert ( (*clientMode == Host_IO::kFMode_IsFile) || (*clientMode == Host_IO::kFMode_DoesNotExist) );
+
+ if ( *clientMode == Host_IO::kFMode_IsFile ) {
+
+ // Find the file extension. OK to be "wrong" for something like "C:\My.dir\file". Any
+ // filtering looks for matches with real extensions, "dir\file" won't match any of these.
+ XMP_StringPtr extPos = clientPath + strlen ( clientPath );
+ for ( ; (extPos != clientPath) && (*extPos != '.'); --extPos ) {}
+ if ( *extPos == '.' ) {
+ fileExt->assign ( extPos+1 );
+ MakeLowerCase ( fileExt );
+ }
+
+ // See if this file is one that XMPFiles should never process.
+ for ( size_t i = 0; kKnownRejectedFiles[i] != 0; ++i ) {
+ if ( *fileExt == kKnownRejectedFiles[i] ) return true;
+ }
+
+ }
+
+ return false;
+
+} // FileIsExcluded
+
+// =================================================================================================
+
+/* class static */
+bool
+XMPFiles::GetFileModDate ( XMP_StringPtr clientPath,
+ XMP_DateTime * modDate,
+ XMP_FileFormat * format, /* = 0 */
+ XMP_OptionBits options /* = 0 */ )
+{
+
+ // ---------------------------------------------------------------
+ // First try to select a smart handler. Return false if not found.
+
+ XMPFiles dummyParent; // GetFileModDate is static, but the handler needs a parent.
+ dummyParent.filePath = clientPath;
+
+ Host_IO::FileMode clientMode;
+ std::string fileExt; // Used to check for excluded files.
+ bool excluded = FileIsExcluded ( clientPath, &fileExt, &clientMode ); // ! Fills in fileExt and clientMode.
+ if ( excluded ) return false;
+
+ XMPFileHandlerInfo * handlerInfo = 0;
+ XMPFileHandlerCTor handlerCTor = 0;
+ XMP_OptionBits handlerFlags = 0;
+
+ XMP_FileFormat dummyFormat = kXMP_UnknownFile;
+ if ( format == 0 ) format = &dummyFormat;
+
+ options |= kXMPFiles_OpenForRead;
+ handlerInfo = HandlerRegistry::getInstance().selectSmartHandler ( &dummyParent, clientPath, *format, options );
+ if ( handlerInfo == 0 ) return false;
+
+ // -------------------------------------------------------------------------
+ // Fill in the format output. Call the handler to get the modification date.
+
+ dummyParent.format = handlerInfo->format;
+ *format = handlerInfo->format;
+
+ dummyParent.handler = handlerInfo->handlerCTor ( &dummyParent );
+
+ bool ok = dummyParent.handler->GetFileModDate ( modDate );
+
+ delete dummyParent.handler;
+ dummyParent.handler = 0;
+
+ return ok;
+
+} // XMPFiles::GetFileModDate
+
+// =================================================================================================
+
+static bool
+DoOpenFile ( XMPFiles * thiz,
+ XMP_IO * clientIO,
+ XMP_StringPtr clientPath,
+ XMP_FileFormat format /* = kXMP_UnknownFile */,
+ XMP_OptionBits openFlags /* = 0 */ )
+{
+ XMP_Assert ( (clientIO == 0) ? (clientPath[0] != 0) : (clientPath[0] == 0) );
+
+ openFlags &= ~kXMPFiles_ForceGivenHandler; // Don't allow this flag for OpenFile.
+
+ if ( thiz->handler != 0 ) XMP_Throw ( "File already open", kXMPErr_BadParam );
+ CloseLocalFile ( thiz ); // Sanity checks if prior call failed.
+
+ thiz->ioRef = clientIO;
+ thiz->filePath = clientPath;
+
+ thiz->format = kXMP_UnknownFile; // Make sure it is preset for later check.
+ thiz->openFlags = openFlags;
+
+ bool readOnly = XMP_OptionIsClear ( openFlags, kXMPFiles_OpenForUpdate );
+
+ Host_IO::FileMode clientMode;
+ std::string fileExt; // Used to check for camera raw files and OK to scan files.
+
+ if ( thiz->UsesClientIO() ) {
+ clientMode = Host_IO::kFMode_IsFile;
+ } else {
+ bool excluded = FileIsExcluded ( clientPath, &fileExt, &clientMode ); // ! Fills in fileExt and clientMode.
+ if ( excluded ) return false;
+ }
+
+ // Find the handler, fill in the XMPFiles member variables, cache the desired file data.
+
+ XMPFileHandlerInfo * handlerInfo = 0;
+ XMPFileHandlerCTor handlerCTor = 0;
+ XMP_OptionBits handlerFlags = 0;
+
+ if ( ! (openFlags & kXMPFiles_OpenUsePacketScanning) ) {
+ handlerInfo = HandlerRegistry::getInstance().selectSmartHandler( thiz, clientPath, format, openFlags );
+ }
+
+ #if ! EnablePacketScanning
+
+ if ( handlerInfo == 0 ) return false;
+
+ #else
+
+ if ( handlerInfo == 0 ) {
+
+ // No smart handler, packet scan if appropriate.
+
+ if ( clientMode != Host_IO::kFMode_IsFile ) return false;
+ if ( openFlags & kXMPFiles_OpenUseSmartHandler ) return false;
+
+ if ( openFlags & kXMPFiles_OpenLimitedScanning ) {
+ bool scanningOK = false;
+ for ( size_t i = 0; kKnownScannedFiles[i] != 0; ++i ) {
+ if ( fileExt == kKnownScannedFiles[i] ) { scanningOK = true; break; }
+ }
+ if ( ! scanningOK ) return false;
+ }
+
+ handlerInfo = &kScannerHandlerInfo;
+ if ( thiz->ioRef == 0 ) { // Normally opened in SelectSmartHandler, but might not be open yet.
+ thiz->ioRef = XMPFiles_IO::New_XMPFiles_IO ( clientPath, readOnly );
+ if ( thiz->ioRef == 0 ) return false;
+ }
+
+ }
+
+ #endif
+
+ XMP_Assert ( handlerInfo != 0 );
+ handlerCTor = handlerInfo->handlerCTor;
+ handlerFlags = handlerInfo->flags;
+
+ XMP_Assert ( (thiz->ioRef != 0) ||
+ (handlerFlags & kXMPFiles_UsesSidecarXMP) ||
+ (handlerFlags & kXMPFiles_HandlerOwnsFile) ||
+ (handlerFlags & kXMPFiles_FolderBasedFormat) );
+
+ if ( thiz->format == kXMP_UnknownFile ) thiz->format = handlerInfo->format; // ! The CheckProc might have set it.
+ XMPFileHandler* handler = (*handlerCTor) ( thiz );
+ XMP_Assert ( handlerFlags == handler->handlerFlags );
+
+ thiz->handler = handler;
+
+ try {
+ handler->CacheFileData();
+ } catch ( ... ) {
+ delete thiz->handler;
+ thiz->handler = 0;
+ if ( ! (handlerFlags & kXMPFiles_HandlerOwnsFile) ) CloseLocalFile ( thiz );
+ throw;
+ }
+
+ if ( handler->containsXMP ) FillPacketInfo ( handler->xmpPacket, &handler->packetInfo );
+
+ if ( (! (openFlags & kXMPFiles_OpenForUpdate)) && (! (handlerFlags & kXMPFiles_HandlerOwnsFile)) ) {
+ // Close the disk file now if opened for read-only access.
+ CloseLocalFile ( thiz );
+ }
+
+ return true;
+
+} // DoOpenFile
+
+// =================================================================================================
+
+static bool DoOpenFile( XMPFiles* thiz,
+ const Common::XMPFileHandlerInfo& hdlInfo,
+ XMP_IO* clientIO,
+ XMP_StringPtr clientPath,
+ XMP_OptionBits openFlags )
+{
+ XMP_Assert ( (clientIO == 0) ? (clientPath[0] != 0) : (clientPath[0] == 0) );
+
+ openFlags &= ~kXMPFiles_ForceGivenHandler; // Don't allow this flag for OpenFile.
+
+
+ if ( thiz->handler != 0 ) XMP_Throw ( "File already open", kXMPErr_BadParam );
+
+ //
+ // setup members
+ //
+ thiz->ioRef = clientIO;
+ thiz->filePath = std::string( clientPath );
+ thiz->format = hdlInfo.format;
+ thiz->openFlags = openFlags;
+
+ //
+ // create file handler instance
+ //
+ XMPFileHandlerCTor handlerCTor = hdlInfo.handlerCTor;
+ XMP_OptionBits handlerFlags = hdlInfo.flags;
+
+ XMPFileHandler* handler = (*handlerCTor) ( thiz );
+ XMP_Assert ( handlerFlags == handler->handlerFlags );
+
+ thiz->handler = handler;
+
+ //
+ // try to read metadata
+ //
+ try
+ {
+ handler->CacheFileData();
+
+ if( handler->containsXMP )
+ {
+ FillPacketInfo( handler->xmpPacket, &handler->packetInfo );
+ }
+
+ if( (! (openFlags & kXMPFiles_OpenForUpdate)) && (! (handlerFlags & kXMPFiles_HandlerOwnsFile)) )
+ {
+ // Close the disk file now if opened for read-only access.
+ CloseLocalFile ( thiz );
+ }
+ }
+ catch( ... )
+ {
+ delete thiz->handler;
+ thiz->handler = NULL;
+
+ if( ! (handlerFlags & kXMPFiles_HandlerOwnsFile) )
+ {
+ CloseLocalFile( thiz );
+ }
+
+ throw;
+ }
+
+ return true;
+}
+
+// =================================================================================================
+
+bool
+XMPFiles::OpenFile ( XMP_StringPtr clientPath,
+ XMP_FileFormat format /* = kXMP_UnknownFile */,
+ XMP_OptionBits openFlags /* = 0 */ )
+{
+
+ return DoOpenFile ( this, 0, clientPath, format, openFlags );
+
+}
+
+// =================================================================================================
+
+#if XMP_StaticBuild // ! Client XMP_IO objects can only be used in static builds.
+
+bool
+XMPFiles::OpenFile ( XMP_IO* clientIO,
+ XMP_FileFormat format /* = kXMP_UnknownFile */,
+ XMP_OptionBits openFlags /* = 0 */ )
+{
+
+ return DoOpenFile ( this, clientIO, "", format, openFlags );
+
+} // XMPFiles::OpenFile
+
+#endif // XMP_StaticBuild
+
+// =================================================================================================
+
+#if XMP_StaticBuild // ! Client XMP_IO objects can only be used in static builds.
+
+bool XMPFiles::OpenFile( const Common::XMPFileHandlerInfo& hdlInfo,
+ XMP_IO* clientIO,
+ XMP_OptionBits openFlags /*= 0*/ )
+{
+
+ return DoOpenFile( this, hdlInfo, clientIO, NULL, openFlags );
+}
+
+#endif // XMP_StaticBuild
+
+// =================================================================================================
+
+bool XMPFiles::OpenFile( const Common::XMPFileHandlerInfo& hdlInfo,
+ XMP_StringPtr filePath,
+ XMP_OptionBits openFlags /*= 0*/ )
+{
+
+ return DoOpenFile( this, hdlInfo, NULL, filePath, openFlags );
+}
+
+// =================================================================================================
+
+void
+XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ )
+{
+ if ( this->handler == 0 ) return; // Return if there is no open file (not an error).
+
+ bool needsUpdate = this->handler->needsUpdate;
+ XMP_OptionBits handlerFlags = this->handler->handlerFlags;
+
+ // Decide if we're doing a safe update. If so, make sure the handler supports it. All handlers
+ // that don't own the file tolerate safe update using common code below.
+
+ bool doSafeUpdate = XMP_OptionIsSet ( closeFlags, kXMPFiles_UpdateSafely );
+ #if GatherPerformanceData
+ if ( doSafeUpdate ) sAPIPerf->back().extraInfo += ", safe update"; // Client wants safe update.
+ #endif
+
+ if ( ! (this->openFlags & kXMPFiles_OpenForUpdate) ) doSafeUpdate = false;
+ if ( ! needsUpdate ) doSafeUpdate = false;
+
+ bool safeUpdateOK = ( (handlerFlags & kXMPFiles_AllowsSafeUpdate) ||
+ (! (handlerFlags & kXMPFiles_HandlerOwnsFile)) );
+ if ( doSafeUpdate && (! safeUpdateOK) ) {
+ XMP_Throw ( "XMPFiles::CloseFile - Safe update not supported", kXMPErr_Unavailable );
+ }
+
+ // Try really hard to make sure the file is closed and the handler is deleted.
+
+ try {
+
+ if ( (! doSafeUpdate) || (handlerFlags & kXMPFiles_HandlerOwnsFile) ) { // ! Includes no update case.
+
+ // Close the file without doing common crash-safe writing. The handler might do it.
+
+ if ( needsUpdate ) {
+ #if GatherPerformanceData
+ sAPIPerf->back().extraInfo += ", direct update";
+ #endif
+ this->handler->UpdateFile ( doSafeUpdate );
+ }
+
+ delete this->handler;
+ this->handler = 0;
+ CloseLocalFile ( this );
+
+ } else {
+
+ // Update the file in a crash-safe manner using common control of a temp file.
+
+ XMP_IO* tempFileRef = this->ioRef->DeriveTemp();
+ if ( tempFileRef == 0 ) XMP_Throw ( "XMPFiles::CloseFile, cannot create temp", kXMPErr_InternalFailure );
+
+ if ( handlerFlags & kXMPFiles_CanRewrite ) {
+
+ // The handler can rewrite an entire file based on the original.
+
+ #if GatherPerformanceData
+ sAPIPerf->back().extraInfo += ", file rewrite";
+ #endif
+
+ this->handler->WriteTempFile ( tempFileRef );
+
+ } else {
+
+ // The handler can only update an existing file. Copy to the temp then update.
+
+
+ #if GatherPerformanceData
+ sAPIPerf->back().extraInfo += ", copy update";
+ #endif
+
+
+ XMP_IO* origFileRef = this->ioRef;
+
+ origFileRef->Rewind();
+ XIO::Copy ( origFileRef, tempFileRef, origFileRef->Length(), abortProc, abortArg );
+
+ try {
+
+ this->ioRef = tempFileRef;
+ this->handler->UpdateFile ( false ); // We're doing the safe update, not the handler.
+ this->ioRef = origFileRef;
+
+ } catch ( ... ) {
+
+ this->ioRef->DeleteTemp();
+ this->ioRef = origFileRef;
+ throw;
+
+ }
+
+ }
+
+ this->ioRef->AbsorbTemp();
+ CloseLocalFile ( this );
+
+ delete this->handler;
+ this->handler = 0;
+
+ }
+
+ } catch ( ... ) {
+
+ // *** Don't delete the temp or copy files, not sure which is best.
+
+ try {
+ if ( this->handler != 0 ) delete this->handler;
+ } catch ( ... ) { /* Do nothing, throw the outer exception later. */ }
+
+ if( this->ioRef ) this->ioRef->DeleteTemp();
+ CloseLocalFile ( this );
+ this->filePath.clear();
+
+ this->handler = 0;
+ this->format = kXMP_UnknownFile;
+ this->ioRef = 0;
+ this->openFlags = 0;
+
+ if ( this->tempPtr != 0 ) free ( this->tempPtr ); // ! Must have been malloc-ed!
+ this->tempPtr = 0;
+ this->tempUI32 = 0;
+
+ throw;
+
+ }
+
+ // Clear the XMPFiles member variables.
+
+ CloseLocalFile ( this );
+ this->filePath.clear();
+
+ this->handler = 0;
+ this->format = kXMP_UnknownFile;
+ this->ioRef = 0;
+ this->openFlags = 0;
+
+ if ( this->tempPtr != 0 ) free ( this->tempPtr ); // ! Must have been malloc-ed!
+ this->tempPtr = 0;
+ this->tempUI32 = 0;
+
+} // XMPFiles::CloseFile
+
+// =================================================================================================
+
+bool
+XMPFiles::GetFileInfo ( XMP_StringPtr * filePath /* = 0 */,
+ XMP_StringLen * pathLen /* = 0 */,
+ XMP_OptionBits * openFlags /* = 0 */,
+ XMP_FileFormat * format /* = 0 */,
+ XMP_OptionBits * handlerFlags /* = 0 */ ) const
+{
+ if ( this->handler == 0 ) return false;
+ XMPFileHandler * handler = this->handler;
+
+ if ( filePath == 0 ) filePath = &voidStringPtr;
+ if ( pathLen == 0 ) pathLen = &voidStringLen;
+ if ( openFlags == 0 ) openFlags = &voidOptionBits;
+ if ( format == 0 ) format = &voidFileFormat;
+ if ( handlerFlags == 0 ) handlerFlags = &voidOptionBits;
+
+ *filePath = this->filePath.c_str();
+ *pathLen = (XMP_StringLen) this->filePath.size();
+ *openFlags = this->openFlags;
+ *format = this->format;
+ *handlerFlags = this->handler->handlerFlags;
+
+ return true;
+
+} // XMPFiles::GetFileInfo
+
+// =================================================================================================
+
+void
+XMPFiles::SetAbortProc ( XMP_AbortProc abortProc,
+ void * abortArg )
+{
+ this->abortProc = abortProc;
+ this->abortArg = abortArg;
+
+ XMP_Assert ( (abortProc != (XMP_AbortProc)0) || (abortArg != (void*)(unsigned long long)0xDEADBEEFULL) ); // Hack to test the assert callback.
+} // XMPFiles::SetAbortProc
+
+// =================================================================================================
+// SetClientPacketInfo
+// ===================
+//
+// Set the packet info returned to the client. This is the internal packet info at first, which
+// tells what is in the file. But once the file needs update (PutXMP has been called), we return
+// info about the latest XMP. The internal packet info is left unchanged since it is needed when
+// the file is updated to locate the old packet in the file.
+
+static void
+SetClientPacketInfo ( XMP_PacketInfo * clientInfo, const XMP_PacketInfo & handlerInfo,
+ const std::string & xmpPacket, bool needsUpdate )
+{
+
+ if ( clientInfo == 0 ) return;
+
+ if ( ! needsUpdate ) {
+ *clientInfo = handlerInfo;
+ } else {
+ clientInfo->offset = kXMPFiles_UnknownOffset;
+ clientInfo->length = (XMP_Int32) xmpPacket.size();
+ FillPacketInfo ( xmpPacket, clientInfo );
+ }
+
+} // SetClientPacketInfo
+
+// =================================================================================================
+
+bool
+XMPFiles::GetXMP ( SXMPMeta * xmpObj /* = 0 */,
+ XMP_StringPtr * xmpPacket /* = 0 */,
+ XMP_StringLen * xmpPacketLen /* = 0 */,
+ XMP_PacketInfo * packetInfo /* = 0 */ )
+{
+ if ( this->handler == 0 ) XMP_Throw ( "XMPFiles::GetXMP - No open file", kXMPErr_BadObject );
+
+ XMP_OptionBits applyTemplateFlags = kXMPTemplate_AddNewProperties | kXMPTemplate_IncludeInternalProperties;
+
+ if ( ! this->handler->processedXMP ) {
+ try {
+ this->handler->ProcessXMP();
+ } catch ( ... ) {
+ // Return the outputs then rethrow the exception.
+ if ( xmpObj != 0 ) {
+ // ! Don't use Clone, that replaces the internal ref in the local xmpObj, leaving the client unchanged!
+ xmpObj->Erase();
+ SXMPUtils::ApplyTemplate ( xmpObj, this->handler->xmpObj, applyTemplateFlags );
+ }
+ if ( xmpPacket != 0 ) *xmpPacket = this->handler->xmpPacket.c_str();
+ if ( xmpPacketLen != 0 ) *xmpPacketLen = (XMP_StringLen) this->handler->xmpPacket.size();
+ SetClientPacketInfo ( packetInfo, this->handler->packetInfo,
+ this->handler->xmpPacket, this->handler->needsUpdate );
+ throw;
+ }
+ }
+
+ if ( ! this->handler->containsXMP ) return false;
+
+ #if 0 // *** See bug 1131815. A better way might be to pass the ref up from here.
+ if ( xmpObj != 0 ) *xmpObj = this->handler->xmpObj.Clone();
+ #else
+ if ( xmpObj != 0 ) {
+ // ! Don't use Clone, that replaces the internal ref in the local xmpObj, leaving the client unchanged!
+ xmpObj->Erase();
+ SXMPUtils::ApplyTemplate ( xmpObj, this->handler->xmpObj, applyTemplateFlags );
+ }
+ #endif
+
+ if ( xmpPacket != 0 ) *xmpPacket = this->handler->xmpPacket.c_str();
+ if ( xmpPacketLen != 0 ) *xmpPacketLen = (XMP_StringLen) this->handler->xmpPacket.size();
+ SetClientPacketInfo ( packetInfo, this->handler->packetInfo,
+ this->handler->xmpPacket, this->handler->needsUpdate );
+
+ return true;
+
+} // XMPFiles::GetXMP
+
+// =================================================================================================
+
+static bool
+DoPutXMP ( XMPFiles * thiz, const SXMPMeta & xmpObj, const bool doIt )
+{
+ // Check some basic conditions to see if the Put should be attempted.
+
+ if ( thiz->handler == 0 ) XMP_Throw ( "XMPFiles::PutXMP - No open file", kXMPErr_BadObject );
+ if ( ! (thiz->openFlags & kXMPFiles_OpenForUpdate) ) {
+ XMP_Throw ( "XMPFiles::PutXMP - Not open for update", kXMPErr_BadObject );
+ }
+
+ XMPFileHandler * handler = thiz->handler;
+ XMP_OptionBits handlerFlags = handler->handlerFlags;
+ XMP_PacketInfo & packetInfo = handler->packetInfo;
+ std::string & xmpPacket = handler->xmpPacket;
+
+ if ( ! handler->processedXMP ) handler->ProcessXMP(); // Might have Open/Put with no GetXMP.
+
+ size_t oldPacketOffset = (size_t)packetInfo.offset;
+ size_t oldPacketLength = packetInfo.length;
+
+ if ( oldPacketOffset == (size_t)kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks.
+ if ( oldPacketLength == (size_t)kXMPFiles_UnknownLength ) oldPacketLength = 0;
+
+ bool fileHasPacket = (oldPacketOffset != 0) && (oldPacketLength != 0);
+
+ if ( ! fileHasPacket ) {
+ if ( ! (handlerFlags & kXMPFiles_CanInjectXMP) ) {
+ XMP_Throw ( "XMPFiles::PutXMP - Can't inject XMP", kXMPErr_Unavailable );
+ }
+ if ( handler->stdCharForm == kXMP_CharUnknown ) {
+ XMP_Throw ( "XMPFiles::PutXMP - No standard character form", kXMPErr_InternalFailure );
+ }
+ }
+
+ // Serialize the XMP and update the handler's info.
+
+ XMP_Uns8 charForm = handler->stdCharForm;
+ if ( charForm == kXMP_CharUnknown ) charForm = packetInfo.charForm;
+
+ XMP_OptionBits options = handler->GetSerializeOptions() | XMP_CharToSerializeForm ( charForm );
+ if ( handlerFlags & kXMPFiles_NeedsReadOnlyPacket ) options |= kXMP_ReadOnlyPacket;
+ if ( fileHasPacket && (thiz->format == kXMP_UnknownFile) && (! packetInfo.writeable) ) options |= kXMP_ReadOnlyPacket;
+
+ bool preferInPlace = ((handlerFlags & kXMPFiles_PrefersInPlace) != 0);
+ bool tryInPlace = (fileHasPacket & preferInPlace) || (! (handlerFlags & kXMPFiles_CanExpand));
+
+ if ( handlerFlags & kXMPFiles_UsesSidecarXMP ) tryInPlace = false;
+
+ if ( tryInPlace ) {
+ try {
+ xmpObj.SerializeToBuffer ( &xmpPacket, (options | kXMP_ExactPacketLength), (XMP_StringLen) oldPacketLength );
+ XMP_Assert ( xmpPacket.size() == oldPacketLength );
+ } catch ( ... ) {
+ if ( preferInPlace ) {
+ tryInPlace = false; // ! Try again, out of place this time.
+ } else {
+ if ( ! doIt ) return false;
+ throw;
+ }
+ }
+ }
+
+ if ( ! tryInPlace ) {
+ try {
+ xmpObj.SerializeToBuffer ( &xmpPacket, options );
+ } catch ( ... ) {
+ if ( ! doIt ) return false;
+ throw;
+ }
+ }
+
+ if ( doIt ) {
+ handler->xmpObj = xmpObj.Clone();
+ handler->containsXMP = true;
+ handler->processedXMP = true;
+ handler->needsUpdate = true;
+ }
+
+ return true;
+
+} // DoPutXMP
+
+// =================================================================================================
+
+void
+XMPFiles::PutXMP ( const SXMPMeta & xmpObj )
+{
+ (void) DoPutXMP ( this, xmpObj, true );
+
+} // XMPFiles::PutXMP
+
+// =================================================================================================
+
+void
+XMPFiles::PutXMP ( XMP_StringPtr xmpPacket,
+ XMP_StringLen xmpPacketLen /* = kXMP_UseNullTermination */ )
+{
+ SXMPMeta xmpObj ( xmpPacket, xmpPacketLen );
+ this->PutXMP ( xmpObj );
+
+} // XMPFiles::PutXMP
+
+// =================================================================================================
+
+bool
+XMPFiles::CanPutXMP ( const SXMPMeta & xmpObj )
+{
+ if ( this->handler == 0 ) XMP_Throw ( "XMPFiles::CanPutXMP - No open file", kXMPErr_BadObject );
+
+ if ( ! (this->openFlags & kXMPFiles_OpenForUpdate) ) return false;
+
+ if ( this->handler->handlerFlags & kXMPFiles_CanInjectXMP ) return true;
+ if ( ! this->handler->containsXMP ) return false;
+ if ( this->handler->handlerFlags & kXMPFiles_CanExpand ) return true;
+
+ return DoPutXMP ( this, xmpObj, false );
+
+} // XMPFiles::CanPutXMP
+
+// =================================================================================================
+
+bool
+XMPFiles::CanPutXMP ( XMP_StringPtr xmpPacket,
+ XMP_StringLen xmpPacketLen /* = kXMP_UseNullTermination */ )
+{
+ SXMPMeta xmpObj ( xmpPacket, xmpPacketLen );
+ return this->CanPutXMP ( xmpObj );
+
+} // XMPFiles::CanPutXMP
+
+// =================================================================================================
diff --git a/XMPFiles/source/XMPFiles.hpp b/XMPFiles/source/XMPFiles.hpp
new file mode 100644
index 0000000..f4f2e96
--- /dev/null
+++ b/XMPFiles/source/XMPFiles.hpp
@@ -0,0 +1,252 @@
+#ifndef __XMPFiles_hpp__
+#define __XMPFiles_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include <string>
+#define TXMP_STRING_TYPE std::string
+#include "public/include/XMP.hpp"
+
+#include "public/include/XMP_IO.hpp"
+
+class XMPFileHandler;
+namespace Common{ struct XMPFileHandlerInfo; }
+
+// =================================================================================================
+/// \file XMPFiles.hpp
+/// \brief High level support to access metadata in files of interest to Adobe applications.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// =================================================================================================
+// *** Usage Notes (eventually to become Doxygen comments) ***
+// ===========================================================
+//
+// This is the main part of the internal (DLL side) implementation of XMPFiles. Other parts are
+// the entry point wrappers and the file format handlers. The XMPFiles class distills the client
+// API from TXMPFiles.hpp, removing convenience overloads and substituting a pointer/length pair
+// for output strings.
+//
+// The wrapper functions provide a stable binary interface and perform minor impedance correction
+// between the client template API from TDocMeta.hpp and the DLL's XMPFiles class. The details of
+// the wrappers should be considered private.
+//
+// File handlers are registered during DLL initialization with hard coded calls in Init_XMPFiles.
+// Each file handler provides 2 standalone functions, CheckFormatProc and DocMetaHandlerCTor, plus a
+// class derived from DocMetaHandler. The format and capability flags are passed when registering.
+// This allows the same physical handler to be registered for multiple formats.
+//
+// -------------------------------------------------------------------------------------------------
+//
+// Basic outlines of the processing by the XMPFiles methods:
+//
+// Constructor:
+// - Minimal work to create an empty XMPFiles object, set the ref count to 1.
+//
+// Destructor:
+// - Decrement the ref count, return if greater than zero.
+// - Call LFA_Close if necessary.
+//
+// UnlockLib & UnlockObj:
+// - Release the thread lock. Same for now, no per-object lock.
+//
+// GetFormatInfo:
+// - Return the flags for the registered handler.
+//
+// OpenFile:
+// - The physical file is opened via LFA_OpenFile.
+// - A handler is selected by calling the registered format checkers.
+// - The handler object is created by calling the registered constructor proc.
+//
+// CloseFile:
+// - Return if there is no open file (not an error).
+// - If not a crash-safe update (includes read-only or no update), or the handler owns the file:
+// - Throw an exception if the handler owns the file but does not support safe update.
+// - If the file needs updating, call the handler's UpdateFile method.
+// - else:
+// - If the handler supports file rewrite:
+// - *** This might not preserve ownership and permissions.
+// - Create an empty temp file.
+// - Call the handler's WriteFile method, writing to the temp file.
+// - else
+// - *** This preserves ownership, permissions, and Mac resources.
+// - Copy the original file to a temp name (Mac data fork only).
+// - Rename the original file to a different temp name.
+// - Rename the copy file back to the original name.
+// - Call the handler's UpdateFile method for the "original as temp" file.
+// - Close both the original and temp files.
+// - Delete the file with the original name.
+// - Rename the temp file to the original name.
+// - Delete the handler object.
+// - Call LFA_Close if necessary.
+//
+// GetFileInfo:
+// - Return the file info from the XMPFiles member variables.
+//
+// GetXMP:
+// - Throw an exception if there is no open file.
+// - Call the handler's GetXMP method.
+//
+// PutXMP:
+// - Throw an exception if there is no open file.
+// - Call the handler's PutXMP method.
+//
+// CanPutXMP:
+// - Implement roughly as shown in TXMPFiles.hpp, there is no handler CanPutXMP method.
+//
+// -------------------------------------------------------------------------------------------------
+//
+// The format checker should do nothing but the minimal work to identify the overall file format.
+// In particular it should not look for XMP or other metadata. Note that the format checker has no
+// means to carry state forward, it just returns a yes/no answer about a particular file format.
+//
+// The format checker and file handler should use the LFA_* functions for all I/O. They should not
+// open or close the file themselves unless the handler sets the "handler-owns-file" flag.
+//
+// The format checker is passed the format being checked, allowing one checker to handle multiple
+// formats. It is passed the LFA file ref so that it can do additional reads if necessary. The
+// buffer is from the start of the file, the file will be positioned to the byte following the
+// buffer. The buffer length will be at least 4K, unless the file is smaller in which case it will
+// be the length of the file. This buffer may be reused for additional reads.
+//
+// Identifying some file formats can require checking variable length strings. Doing seeks and reads
+// for each is suboptimal. There are utilities to maintain a rolling buffer and ensure that a given
+// amount of data is available. See the template file handler code for details.
+//
+// -------------------------------------------------------------------------------------------------
+//
+// The file handler has no explicit open and close methods. These are implicit in the handler's
+// constructor and destructor. The file handler should use the XMPFiles member variables for the
+// active file ref (and path if necessary), unless it owns the file. Note that these might change
+// between the open and close in the case of crash-safe updates. Don't copy the XMPFiles member
+// variables in the handler's constructor, save the pointer to the XMPFiles object and access
+// directly as needed.
+//
+// The handler should have an UpdateFile method. This is called from XMPFiles::CloseFile if the
+// file needs to be updated. The handler's destructor must only close the file, not update it.
+// The handler can optionally have a WriteFile method, if it can rewrite the entire file.
+//
+// The handler is free to use its best judgement about caching parts of the file in memory. Overall
+// speed of a single open/get/put/close cycle is probably the best goal, assuming a modern processor
+// with a reasonable (significant but not enormous) amount of RAM.
+//
+// The handler methods will be called in a per-object thread safe manner. Concurrent access might
+// occur for different objects, but not for the same object. The handler's constructor and destructor
+// will always be globally serialized, so they can safely modify global data structures.
+//
+// (Testing issue: What about separate XMPFiles objects accessing the same file?)
+//
+// Handler's must not have any global objects that are heap allocated. Use pointers to objects that
+// are allocated and deleted during the XMPFiles initialization and termination process. Some
+// client apps are very picky about what they detect as memory leaks.
+//
+// static char gSomeBuffer [10*1000]; // OK, not from the heap.
+// static std::string gSomeString; // Not OK, content from the heap.
+// static std::vector<int> gSomeVector; // Not OK, content from the heap.
+// static std::string * gSomeString = 0; // OK, alloc at init, delete at term.
+// static std::vector<int> * gSomeVector = 0; // OK, alloc at init, delete at term.
+//
+// =================================================================================================
+
+class XMPFiles {
+public:
+
+ static void GetVersionInfo ( XMP_VersionInfo * info );
+
+ static bool Initialize ( XMP_OptionBits options, const char* pluginFolder, const char* plugins = NULL );
+ static void Terminate();
+
+ XMPFiles();
+ virtual ~XMPFiles();
+
+ static bool GetFormatInfo ( XMP_FileFormat format,
+ XMP_OptionBits * flags = 0 );
+
+ static XMP_FileFormat CheckFileFormat ( XMP_StringPtr filePath );
+ static XMP_FileFormat CheckPackageFormat ( XMP_StringPtr folderPath );
+
+ static bool GetFileModDate ( XMP_StringPtr filePath,
+ XMP_DateTime * modDate,
+ XMP_FileFormat * format = 0, // ! Can be null.
+ XMP_OptionBits options = 0 );
+
+ bool OpenFile ( XMP_StringPtr filePath,
+ XMP_FileFormat format = kXMP_UnknownFile,
+ XMP_OptionBits openFlags = 0 );
+
+ #if XMP_StaticBuild // ! Client XMP_IO objects can only be used in static builds.
+ bool OpenFile ( XMP_IO * clientIO,
+ XMP_FileFormat format = kXMP_UnknownFile,
+ XMP_OptionBits openFlags = 0 );
+ #endif
+
+ bool OpenFile( const Common::XMPFileHandlerInfo& hdlInfo,
+ XMP_StringPtr filePath,
+ XMP_OptionBits openFlags = 0 );
+
+#if XMP_StaticBuild // ! Client XMP_IO objects can only be used in static builds.
+ bool OpenFile( const Common::XMPFileHandlerInfo& hdlInfo,
+ XMP_IO* clientIO,
+ XMP_OptionBits openFlags = 0 );
+#endif
+
+ void CloseFile ( XMP_OptionBits closeFlags = 0 );
+
+ bool GetFileInfo ( XMP_StringPtr * filePath = 0,
+ XMP_StringLen * filePathLen = 0,
+ XMP_OptionBits * openFlags = 0,
+ XMP_FileFormat * format = 0,
+ XMP_OptionBits * handlerFlags = 0 ) const;
+
+ void SetAbortProc ( XMP_AbortProc abortProc,
+ void * abortArg );
+
+ bool GetXMP ( SXMPMeta * xmpObj = 0,
+ XMP_StringPtr * xmpPacket = 0,
+ XMP_StringLen * xmpPacketLen = 0,
+ XMP_PacketInfo * packetInfo = 0 );
+
+ void PutXMP ( const SXMPMeta & xmpObj );
+
+ void PutXMP ( XMP_StringPtr xmpPacket,
+ XMP_StringLen xmpPacketLen = kXMP_UseNullTermination );
+
+ bool CanPutXMP ( const SXMPMeta & xmpObj );
+
+ bool CanPutXMP ( XMP_StringPtr xmpPacket,
+ XMP_StringLen xmpPacketLen = kXMP_UseNullTermination );
+
+ inline bool UsesClientIO() { return this->filePath.empty(); };
+ inline bool UsesLocalIO() { return ( ! this->UsesClientIO() ); };
+
+ // Leave this data public so file handlers can see it.
+
+ XMP_Int32 clientRefs; // ! Must be signed to allow decrement from zero.
+ XMP_ReadWriteLock lock;
+
+ XMP_FileFormat format;
+ XMP_IO * ioRef; // Non-zero if a file is open.
+ std::string filePath; // Empty for client-managed I/O.
+ XMP_OptionBits openFlags;
+ XMPFileHandler * handler; // Non-null if a file is open.
+
+ void * tempPtr; // For use between the CheckProc and handler creation.
+ XMP_Uns32 tempUI32;
+
+ XMP_AbortProc abortProc;
+ void * abortArg;
+
+}; // XMPFiles
+
+#endif /* __XMPFiles_hpp__ */
diff --git a/XMPFiles/source/XMPFiles_Impl.cpp b/XMPFiles/source/XMPFiles_Impl.cpp
new file mode 100644
index 0000000..92b2ea3
--- /dev/null
+++ b/XMPFiles/source/XMPFiles_Impl.cpp
@@ -0,0 +1,416 @@
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+
+#include "public/include/XMP_Const.h"
+#include "public/include/XMP_IO.hpp"
+
+#include "XMPFiles/source/XMPFiles_Impl.hpp"
+#include "source/XIO.hpp"
+
+#include "source/UnicodeConversions.hpp"
+
+using namespace std;
+
+// Internal code should be using #if with XMP_MacBuild, XMP_WinBuild, or XMP_UNIXBuild.
+// This is a sanity check in case of accidental use of *_ENV. Some clients use the poor
+// practice of defining the *_ENV macro with an empty value.
+#if defined ( MAC_ENV )
+ #if ! MAC_ENV
+ #error "MAC_ENV must be defined so that \"#if MAC_ENV\" is true"
+ #endif
+#elif defined ( WIN_ENV )
+ #if ! WIN_ENV
+ #error "WIN_ENV must be defined so that \"#if WIN_ENV\" is true"
+ #endif
+#elif defined ( UNIX_ENV )
+ #if ! UNIX_ENV
+ #error "UNIX_ENV must be defined so that \"#if UNIX_ENV\" is true"
+ #endif
+#endif
+
+// =================================================================================================
+/// \file XMPFiles_Impl.cpp
+/// \brief ...
+///
+/// This file ...
+///
+// =================================================================================================
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4290 ) // C++ exception specification ignored except to indicate a function is not __declspec(nothrow)
+ #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning)
+#endif
+
+bool ignoreLocalText = false;
+
+XMP_FileFormat voidFileFormat = 0; // Used as sink for unwanted output parameters.
+// =================================================================================================
+
+// Add all known mappings, multiple mappings (tif, tiff) are OK.
+const FileExtMapping kFileExtMap[] =
+ { { "pdf", kXMP_PDFFile },
+ { "ps", kXMP_PostScriptFile },
+ { "eps", kXMP_EPSFile },
+
+ { "jpg", kXMP_JPEGFile },
+ { "jpeg", kXMP_JPEGFile },
+ { "jpx", kXMP_JPEG2KFile },
+ { "tif", kXMP_TIFFFile },
+ { "tiff", kXMP_TIFFFile },
+ { "dng", kXMP_TIFFFile }, // DNG files are well behaved TIFF.
+ { "gif", kXMP_GIFFile },
+ { "giff", kXMP_GIFFile },
+ { "png", kXMP_PNGFile },
+
+ { "swf", kXMP_SWFFile },
+ { "flv", kXMP_FLVFile },
+
+ { "aif", kXMP_AIFFFile },
+
+ { "mov", kXMP_MOVFile },
+ { "avi", kXMP_AVIFile },
+ { "cin", kXMP_CINFile },
+ { "wav", kXMP_WAVFile },
+ { "mp3", kXMP_MP3File },
+ { "mp4", kXMP_MPEG4File },
+ { "m4v", kXMP_MPEG4File },
+ { "m4a", kXMP_MPEG4File },
+ { "f4v", kXMP_MPEG4File },
+ { "ses", kXMP_SESFile },
+ { "cel", kXMP_CELFile },
+ { "wma", kXMP_WMAVFile },
+ { "wmv", kXMP_WMAVFile },
+ { "mxf", kXMP_MXFFile },
+
+ { "mpg", kXMP_MPEGFile },
+ { "mpeg", kXMP_MPEGFile },
+ { "mp2", kXMP_MPEGFile },
+ { "mod", kXMP_MPEGFile },
+ { "m2v", kXMP_MPEGFile },
+ { "mpa", kXMP_MPEGFile },
+ { "mpv", kXMP_MPEGFile },
+ { "m2p", kXMP_MPEGFile },
+ { "m2a", kXMP_MPEGFile },
+ { "m2t", kXMP_MPEGFile },
+ { "mpe", kXMP_MPEGFile },
+ { "vob", kXMP_MPEGFile },
+ { "ms-pvr", kXMP_MPEGFile },
+ { "dvr-ms", kXMP_MPEGFile },
+
+ { "html", kXMP_HTMLFile },
+ { "xml", kXMP_XMLFile },
+ { "txt", kXMP_TextFile },
+ { "text", kXMP_TextFile },
+
+ { "psd", kXMP_PhotoshopFile },
+ { "ai", kXMP_IllustratorFile },
+ { "indd", kXMP_InDesignFile },
+ { "indt", kXMP_InDesignFile },
+ { "aep", kXMP_AEProjectFile },
+ { "aepx", kXMP_AEProjectFile },
+ { "aet", kXMP_AEProjTemplateFile },
+ { "ffx", kXMP_AEFilterPresetFile },
+ { "ncor", kXMP_EncoreProjectFile },
+ { "prproj", kXMP_PremiereProjectFile },
+ { "prtl", kXMP_PremiereTitleFile },
+ { "ucf", kXMP_UCFFile },
+ { "xfl", kXMP_UCFFile },
+ { "pdfxml", kXMP_UCFFile },
+ { "mars", kXMP_UCFFile },
+ { "idml", kXMP_UCFFile },
+ { "idap", kXMP_UCFFile },
+ { "icap", kXMP_UCFFile },
+ { "", 0 } }; // ! Must be last as a sentinel.
+
+// Files known to contain XMP but have no smart handling, here or elsewhere.
+const char * kKnownScannedFiles[] =
+ { "gif", // GIF, public format but no smart handler.
+ "ai", // Illustrator, actually a PDF file.
+ "ait", // Illustrator template, actually a PDF file.
+ "svg", // SVG, an XML file.
+ "aet", // After Effects template project file.
+ "ffx", // After Effects filter preset file.
+ "aep", // After Effects project file in proprietary format
+ "aepx", // After Effects project file in XML format
+ "inx", // InDesign interchange, an XML file.
+ "inds", // InDesign snippet, an XML file.
+ "inpk", // InDesign package for GoLive, a text file (not XML).
+ "incd", // InCopy story, an XML file.
+ "inct", // InCopy template, an XML file.
+ "incx", // InCopy interchange, an XML file.
+ "fm", // FrameMaker file, proprietary format.
+ "book", // FrameMaker book, proprietary format.
+ "icml", // an inCopy (inDesign) format
+ "icmt", // an inCopy (inDesign) format
+ "idms", // an inCopy (inDesign) format
+ 0 }; // ! Keep a 0 sentinel at the end.
+
+
+// Extensions that XMPFiles never handles.
+const char * kKnownRejectedFiles[] =
+ {
+ // RAW files
+ "cr2", "erf", "fff", "dcr", "kdc", "mos", "mfw", "mef",
+ "raw", "nef", "orf", "pef", "arw", "sr2", "srf", "sti",
+ "3fr", "rwl", "crw", "sraw", "mos", "mrw", "nrw", "rw2",
+ "c3f",
+ // UCF subformats
+ "air",
+ // Others
+ "r3d",
+ 0 }; // ! Keep a 0 sentinel at the end.
+
+// =================================================================================================
+
+// =================================================================================================
+
+// =================================================================================================
+// GetPacketCharForm
+// =================
+//
+// The first character must be U+FEFF or ASCII, typically '<' for an outermost element, initial
+// processing instruction, or XML declaration. The second character can't be U+0000.
+// The possible input sequences are:
+// Cases with U+FEFF
+// EF BB BF -- - UTF-8
+// FE FF -- -- - Big endian UTF-16
+// 00 00 FE FF - Big endian UTF 32
+// FF FE 00 00 - Little endian UTF-32
+// FF FE -- -- - Little endian UTF-16
+// Cases with ASCII
+// nn mm -- -- - UTF-8 -
+// 00 00 00 nn - Big endian UTF-32
+// 00 nn -- -- - Big endian UTF-16
+// nn 00 00 00 - Little endian UTF-32
+// nn 00 -- -- - Little endian UTF-16
+
+static XMP_Uns8 GetPacketCharForm ( XMP_StringPtr packetStr, XMP_StringLen packetLen )
+{
+ XMP_Uns8 charForm = kXMP_CharUnknown;
+ XMP_Uns8 * unsBytes = (XMP_Uns8*)packetStr; // ! Make sure comparisons are unsigned.
+
+ if ( packetLen < 2 ) return kXMP_Char8Bit;
+
+ if ( packetLen < 4 ) {
+
+ // These cases are based on the first 2 bytes:
+ // 00 nn Big endian UTF-16
+ // nn 00 Little endian UTF-16
+ // FE FF Big endian UTF-16
+ // FF FE Little endian UTF-16
+ // Otherwise UTF-8
+
+ if ( packetStr[0] == 0 ) return kXMP_Char16BitBig;
+ if ( packetStr[1] == 0 ) return kXMP_Char16BitLittle;
+ if ( CheckBytes ( packetStr, "\xFE\xFF", 2 ) ) return kXMP_Char16BitBig;
+ if ( CheckBytes ( packetStr, "\xFF\xFE", 2 ) ) return kXMP_Char16BitLittle;
+ return kXMP_Char8Bit;
+
+ }
+
+ // If we get here the packet is at least 4 bytes, could be any form.
+
+ if ( unsBytes[0] == 0 ) {
+
+ // These cases are:
+ // 00 nn -- -- - Big endian UTF-16
+ // 00 00 00 nn - Big endian UTF-32
+ // 00 00 FE FF - Big endian UTF 32
+
+ if ( unsBytes[1] != 0 ) {
+ charForm = kXMP_Char16BitBig; // 00 nn
+ } else {
+ if ( (unsBytes[2] == 0) && (unsBytes[3] != 0) ) {
+ charForm = kXMP_Char32BitBig; // 00 00 00 nn
+ } else if ( (unsBytes[2] == 0xFE) && (unsBytes[3] == 0xFF) ) {
+ charForm = kXMP_Char32BitBig; // 00 00 FE FF
+ }
+ }
+
+ } else {
+
+ // These cases are:
+ // FE FF -- -- - Big endian UTF-16, FE isn't valid UTF-8
+ // FF FE 00 00 - Little endian UTF-32, FF isn't valid UTF-8
+ // FF FE -- -- - Little endian UTF-16
+ // nn mm -- -- - UTF-8, includes EF BB BF case
+ // nn 00 00 00 - Little endian UTF-32
+ // nn 00 -- -- - Little endian UTF-16
+
+ if ( unsBytes[0] == 0xFE ) {
+ if ( unsBytes[1] == 0xFF ) charForm = kXMP_Char16BitBig; // FE FF
+ } else if ( unsBytes[0] == 0xFF ) {
+ if ( unsBytes[1] == 0xFE ) {
+ if ( (unsBytes[2] == 0) && (unsBytes[3] == 0) ) {
+ charForm = kXMP_Char32BitLittle; // FF FE 00 00
+ } else {
+ charForm = kXMP_Char16BitLittle; // FF FE
+ }
+ }
+ } else if ( unsBytes[1] != 0 ) {
+ charForm = kXMP_Char8Bit; // nn mm
+ } else {
+ if ( (unsBytes[2] == 0) && (unsBytes[3] == 0) ) {
+ charForm = kXMP_Char32BitLittle; // nn 00 00 00
+ } else {
+ charForm = kXMP_Char16BitLittle; // nn 00
+ }
+ }
+
+ }
+
+ // XMP_Assert ( charForm != kXMP_CharUnknown );
+ return charForm;
+
+} // GetPacketCharForm
+
+// =================================================================================================
+// FillPacketInfo
+// ==============
+//
+// If a packet wrapper is present, the the packet string is roughly:
+// <?xpacket begin= ...?>
+// <outer-XML-element>
+// ... more XML ...
+// </outer-XML-element>
+// ... whitespace padding ...
+// <?xpacket end='.'?>
+
+// The 8-bit form is 14 bytes, the 16-bit form is 28 bytes, the 32-bit form is 56 bytes.
+#define k8BitTrailer "<?xpacket end="
+#define k16BitTrailer "<\0?\0x\0p\0a\0c\0k\0e\0t\0 \0e\0n\0d\0=\0"
+#define k32BitTrailer "<\0\0\0?\0\0\0x\0\0\0p\0\0\0a\0\0\0c\0\0\0k\0\0\0e\0\0\0t\0\0\0 \0\0\0e\0\0\0n\0\0\0d\0\0\0=\0\0\0"
+static XMP_StringPtr kPacketTrailiers[3] = { k8BitTrailer, k16BitTrailer, k32BitTrailer };
+
+void FillPacketInfo ( const std::string & packet, XMP_PacketInfo * info )
+{
+ XMP_StringPtr packetStr = packet.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen) packet.size();
+ if ( packetLen == 0 ) return;
+
+ info->charForm = GetPacketCharForm ( packetStr, packetLen );
+ XMP_StringLen charSize = XMP_GetCharSize ( info->charForm );
+
+ // Look for a packet wrapper. For our purposes, we can be lazy and just look for the trailer PI.
+ // If that is present we'll assume that a recognizable header is present. First do a bytewise
+ // search for '<', then a char sized comparison for the start of the trailer. We don't really
+ // care about big or little endian here. We're looking for ASCII bytes with zeroes between.
+ // Shorten the range comparisons (n*charSize) by 1 to easily tolerate both big and little endian.
+
+ XMP_StringLen padStart, padEnd;
+ XMP_StringPtr packetTrailer = kPacketTrailiers [ charSize>>1 ];
+
+ padEnd = packetLen - 1;
+ for ( ; padEnd > 0; --padEnd ) if ( packetStr[padEnd] == '<' ) break;
+ if ( (packetStr[padEnd] != '<') || ((packetLen - padEnd) < (18*charSize)) ) return;
+ if ( ! CheckBytes ( &packetStr[padEnd], packetTrailer, (13*charSize) ) ) return;
+
+ info->hasWrapper = true;
+
+ char rwFlag = packetStr [padEnd + 15*charSize];
+ if ( rwFlag == 'w' ) info->writeable = true;
+
+ // Look for the start of the padding, right after the last XML end tag.
+
+ padStart = padEnd; // Don't do the -charSize here, might wrap below zero.
+ for ( ; padStart >= charSize; padStart -= charSize ) if ( packetStr[padStart] == '>' ) break;
+ if ( padStart < charSize ) return;
+ padStart += charSize; // The padding starts after the '>'.
+
+ info->padSize = padEnd - padStart; // We want bytes of padding, not character units.
+
+} // FillPacketInfo
+
+// =================================================================================================
+// ReadXMPPacket
+// =============
+
+void ReadXMPPacket ( XMPFileHandler * handler )
+{
+ XMP_IO* fileRef = handler->parent->ioRef;
+ std::string & xmpPacket = handler->xmpPacket;
+ XMP_StringLen packetLen = handler->packetInfo.length;
+
+ if ( packetLen == 0 ) XMP_Throw ( "ReadXMPPacket - No XMP packet", kXMPErr_BadXMP );
+
+ xmpPacket.erase();
+ xmpPacket.reserve ( packetLen );
+ xmpPacket.append ( packetLen, ' ' );
+
+ XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() ); // Don't set until after reserving the space!
+
+ fileRef->Seek ( handler->packetInfo.offset, kXMP_SeekFromStart );
+ fileRef->ReadAll ( (char*)packetStr, packetLen );
+
+} // ReadXMPPacket
+
+// =================================================================================================
+// XMPFileHandler::GetFileModDate
+// ==============================
+//
+// The base implementation is only for typical embedding handlers. It returns the modification date
+// of the named file.
+
+bool XMPFileHandler::GetFileModDate ( XMP_DateTime * modDate )
+{
+
+ XMP_OptionBits flags = this->handlerFlags;
+ if ( (flags & kXMPFiles_HandlerOwnsFile) ||
+ (flags & kXMPFiles_UsesSidecarXMP) ||
+ (flags & kXMPFiles_FolderBasedFormat) ) {
+ XMP_Throw ( "Base implementation of GetFileModDate only for typical embedding handlers", kXMPErr_InternalFailure );
+ }
+
+ if ( this->parent->filePath.empty() ) {
+ XMP_Throw ( "GetFileModDate cannot be used with client-provided I/O", kXMPErr_InternalFailure );
+ }
+
+ return Host_IO::GetModifyDate ( this->parent->filePath.c_str(), modDate );
+
+} // XMPFileHandler::GetFileModDate
+
+// =================================================================================================
+// XMPFileHandler::ProcessXMP
+// ==========================
+//
+// This base implementation just parses the XMP. If the derived handler does reconciliation then it
+// must have its own implementation of ProcessXMP.
+
+void XMPFileHandler::ProcessXMP()
+{
+
+ if ( (!this->containsXMP) || this->processedXMP ) return;
+
+ if ( this->handlerFlags & kXMPFiles_CanReconcile ) {
+ XMP_Throw ( "Reconciling file handlers must implement ProcessXMP", kXMPErr_InternalFailure );
+ }
+
+ SXMPUtils::RemoveProperties ( &this->xmpObj, 0, 0, kXMPUtil_DoAllProperties );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ this->processedXMP = true;
+
+} // XMPFileHandler::ProcessXMP
+
+// =================================================================================================
+// XMPFileHandler::GetSerializeOptions
+// ===================================
+//
+// This base implementation just selects compact serialization. The character form and padding/in-place
+// settings are added in the common code before calling SerializeToBuffer.
+
+XMP_OptionBits XMPFileHandler::GetSerializeOptions()
+{
+
+ return kXMP_UseCompactFormat;
+
+} // XMPFileHandler::GetSerializeOptions
+
+// =================================================================================================
diff --git a/XMPFiles/source/XMPFiles_Impl.hpp b/XMPFiles/source/XMPFiles_Impl.hpp
new file mode 100644
index 0000000..689aa43
--- /dev/null
+++ b/XMPFiles/source/XMPFiles_Impl.hpp
@@ -0,0 +1,329 @@
+#ifndef __XMPFiles_Impl_hpp__
+#define __XMPFiles_Impl_hpp__ 1
+
+// =================================================================================================
+// 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
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! Must be the first #include!
+#include "public/include/XMP_Const.h"
+#include "build/XMP_BuildInfo.h"
+#include "source/XMP_LibUtils.hpp"
+#include "source/EndianUtils.hpp"
+
+#include <string>
+#define TXMP_STRING_TYPE std::string
+#define XMP_INCLUDE_XMPFILES 1
+#include "public/include/XMP.hpp"
+
+#include "XMPFiles/source/XMPFiles.hpp"
+#include "public/include/XMP_IO.hpp"
+#include "source/Host_IO.hpp"
+
+#include <vector>
+#include <map>
+#include <cassert>
+#include <cstring>
+#include <cstdlib>
+#include <cstdio>
+
+#if XMP_WinBuild
+
+ #define snprintf _snprintf
+
+#else
+
+ #if XMP_MacBuild
+ #include <CoreServices/CoreServices.h>
+ #endif
+
+ // POSIX headers for both Mac and generic UNIX.
+ #include <fcntl.h>
+ #include <unistd.h>
+ #include <dirent.h>
+ #include <sys/stat.h>
+ #include <sys/types.h>
+
+#endif
+
+// =================================================================================================
+// General global variables and macros
+// ===================================
+
+typedef std::vector<XMP_Uns8> RawDataBlock;
+
+extern bool ignoreLocalText;
+
+#ifndef EnablePhotoHandlers
+ #define EnablePhotoHandlers 1
+#endif
+
+#ifndef EnableDynamicMediaHandlers
+ #define EnableDynamicMediaHandlers 1
+#endif
+
+#ifndef EnableMiscHandlers
+ #define EnableMiscHandlers 1
+#endif
+
+#ifndef EnablePacketScanning
+ #define EnablePacketScanning 1
+#endif
+
+extern XMP_Int32 sXMPFilesInitCount;
+
+#ifndef GatherPerformanceData
+ #define GatherPerformanceData 0
+#endif
+
+#if ! GatherPerformanceData
+
+ #define StartPerfCheck(proc,info) /* do nothing */
+ #define EndPerfCheck(proc) /* do nothing */
+
+#else
+
+ #include "PerfUtils.hpp"
+
+ enum {
+ kAPIPerf_OpenFile,
+ kAPIPerf_CloseFile,
+ kAPIPerf_GetXMP,
+ kAPIPerf_PutXMP,
+ kAPIPerf_CanPutXMP,
+ kAPIPerfProcCount // Last, count of the procs.
+ };
+
+ static const char* kAPIPerfNames[] =
+ { "OpenFile", "CloseFile", "GetXMP", "PutXMP", "CanPutXMP", 0 };
+
+ struct APIPerfItem {
+ XMP_Uns8 whichProc;
+ double elapsedTime;
+ XMPFilesRef xmpFilesRef;
+ std::string extraInfo;
+ APIPerfItem ( XMP_Uns8 proc, double time, XMPFilesRef ref, const char * info )
+ : whichProc(proc), elapsedTime(time), xmpFilesRef(ref), extraInfo(info) {};
+ };
+
+ typedef std::vector<APIPerfItem> APIPerfCollection;
+
+ extern APIPerfCollection* sAPIPerf;
+
+ #define StartPerfCheck(proc,info) \
+ sAPIPerf->push_back ( APIPerfItem ( proc, 0.0, xmpFilesRef, info ) ); \
+ APIPerfItem & thisPerf = sAPIPerf->back(); \
+ PerfUtils::MomentValue startTime, endTime; \
+ try { \
+ startTime = PerfUtils::NoteThisMoment();
+
+ #define EndPerfCheck(proc) \
+ endTime = PerfUtils::NoteThisMoment(); \
+ thisPerf.elapsedTime = PerfUtils::GetElapsedSeconds ( startTime, endTime ); \
+ } catch ( ... ) { \
+ endTime = PerfUtils::NoteThisMoment(); \
+ thisPerf.elapsedTime = PerfUtils::GetElapsedSeconds ( startTime, endTime ); \
+ thisPerf.extraInfo += " ** THROW **"; \
+ throw; \
+ }
+
+#endif
+
+extern XMP_FileFormat voidFileFormat; // Used as sink for unwanted output parameters.
+extern XMP_PacketInfo voidPacketInfo;
+extern void * voidVoidPtr;
+extern XMP_StringPtr voidStringPtr;
+extern XMP_StringLen voidStringLen;
+extern XMP_OptionBits voidOptionBits;
+
+static const XMP_Uns8 * kUTF8_PacketStart = (const XMP_Uns8 *) "<?xpacket begin=";
+static const XMP_Uns8 * kUTF8_PacketID = (const XMP_Uns8 *) "W5M0MpCehiHzreSzNTczkc9d";
+static const size_t kUTF8_PacketHeaderLen = 51; // ! strlen ( "<?xpacket begin='xxx' id='W5M0MpCehiHzreSzNTczkc9d'" )
+
+static const XMP_Uns8 * kUTF8_PacketTrailer = (const XMP_Uns8 *) "<?xpacket end=\"w\"?>";
+static const size_t kUTF8_PacketTrailerLen = 19; // ! strlen ( kUTF8_PacketTrailer )
+
+struct FileExtMapping {
+ XMP_StringPtr ext;
+ XMP_FileFormat format;
+};
+
+extern const FileExtMapping kFileExtMap[];
+extern const char * kKnownScannedFiles[];
+extern const char * kKnownRejectedFiles[];
+
+typedef std::map < const char *, const char * > ID3GenreMap;
+extern ID3GenreMap* kMapID3GenreCodeToName; // Storage defined in ID3_Support.cpp.
+extern ID3GenreMap* kMapID3GenreNameToCode;
+
+#define Uns8Ptr(p) ((XMP_Uns8 *) (p))
+
+#define IsNewline( ch ) ( ((ch) == kLF) || ((ch) == kCR) )
+#define IsSpaceOrTab( ch ) ( ((ch) == ' ') || ((ch) == kTab) )
+#define IsWhitespace( ch ) ( IsSpaceOrTab ( ch ) || IsNewline ( ch ) )
+
+static inline void MakeLowerCase ( std::string * str )
+{
+ for ( size_t i = 0, limit = str->size(); i < limit; ++i ) {
+ char ch = (*str)[i];
+ if ( ('A' <= ch) && (ch <= 'Z') ) (*str)[i] += 0x20;
+ }
+}
+
+static inline void MakeUpperCase ( std::string * str )
+{
+ for ( size_t i = 0, limit = str->size(); i < limit; ++i ) {
+ char ch = (*str)[i];
+ if ( ('a' <= ch) && (ch <= 'z') ) (*str)[i] -= 0x20;
+ }
+}
+
+#define XMP_LitMatch(s,l) (strcmp((s),(l)) == 0)
+#define XMP_LitNMatch(s,l,n) (strncmp((s),(l),(n)) == 0)
+
+// =================================================================================================
+// Support for call tracing
+// ========================
+
+#ifndef XMP_TraceFilesCalls
+ #define XMP_TraceFilesCalls 0
+ #define XMP_TraceFilesCallsToFile 0
+#endif
+
+#if XMP_TraceFilesCalls
+
+ #undef AnnounceThrow
+ #undef AnnounceCatch
+
+ #undef AnnounceEntry
+ #undef AnnounceNoLock
+ #undef AnnounceExit
+
+ extern FILE * xmpFilesLog;
+
+ #define AnnounceThrow(msg) \
+ fprintf ( xmpFilesLog, "XMP_Throw: %s\n", msg ); fflush ( xmpFilesLog )
+ #define AnnounceCatch(msg) \
+ fprintf ( xmpFilesLog, "Catch in %s: %s\n", procName, msg ); fflush ( xmpFilesLog )
+
+ #define AnnounceEntry(proc) \
+ const char * procName = proc; \
+ fprintf ( xmpFilesLog, "Entering %s\n", procName ); fflush ( xmpFilesLog )
+ #define AnnounceNoLock(proc) \
+ const char * procName = proc; \
+ fprintf ( xmpFilesLog, "Entering %s (no lock)\n", procName ); fflush ( xmpFilesLog )
+ #define AnnounceExit() \
+ fprintf ( xmpFilesLog, "Exiting %s\n", procName ); fflush ( xmpFilesLog )
+
+#endif
+
+// =================================================================================================
+// Support for memory leak tracking
+// ================================
+
+#ifndef TrackMallocAndFree
+ #define TrackMallocAndFree 0
+#endif
+
+#if TrackMallocAndFree
+
+ static void* ChattyMalloc ( size_t size )
+ {
+ void* ptr = malloc ( size );
+ fprintf ( stderr, "Malloc %d bytes @ %.8X\n", size, ptr );
+ return ptr;
+ }
+
+ static void ChattyFree ( void* ptr )
+ {
+ fprintf ( stderr, "Free @ %.8X\n", ptr );
+ free ( ptr );
+ }
+
+ #define malloc(s) ChattyMalloc ( s )
+ #define free(p) ChattyFree ( p )
+
+#endif
+
+// =================================================================================================
+// FileHandler declarations
+// ========================
+
+extern void ReadXMPPacket ( XMPFileHandler * handler );
+
+extern void FillPacketInfo ( const XMP_VarString & packet, XMP_PacketInfo * info );
+
+class XMPFileHandler { // See XMPFiles.hpp for usage notes.
+public:
+
+#define DefaultCTorPresets \
+ handlerFlags(0), stdCharForm(kXMP_CharUnknown), \
+ containsXMP(false), processedXMP(false), needsUpdate(false)
+
+ XMPFileHandler() : parent(0), DefaultCTorPresets {};
+ XMPFileHandler (XMPFiles * _parent) : parent(_parent), DefaultCTorPresets {};
+
+ virtual ~XMPFileHandler() {}; // ! The specific handler is responsible for tnailInfo.tnailImage.
+
+ virtual bool GetFileModDate ( XMP_DateTime * modDate ); // The default implementation is for embedding handlers.
+
+ virtual void CacheFileData() = 0;
+ virtual void ProcessXMP(); // The default implementation just parses the XMP.
+
+ virtual XMP_OptionBits GetSerializeOptions(); // The default is compact.
+
+ virtual void UpdateFile ( bool doSafeUpdate ) = 0;
+ virtual void WriteTempFile ( XMP_IO* tempRef ) = 0;
+
+ // ! Leave the data members public so common code can see them.
+
+ XMPFiles * parent; // Let's the handler see the file info.
+ XMP_OptionBits handlerFlags; // Capabilities of this handler.
+ XMP_Uns8 stdCharForm; // The standard character form for output.
+
+ bool containsXMP; // True if the file has XMP or PutXMP has been called.
+ bool processedXMP; // True if the XMP is parsed and reconciled.
+ bool needsUpdate; // True if the file needs to be updated.
+
+ XMP_PacketInfo packetInfo; // ! This is always info about the packet in the file, if any!
+ std::string xmpPacket; // ! This is the current XMP, updated by XMPFiles::PutXMP.
+ SXMPMeta xmpObj;
+
+}; // XMPFileHandler
+
+typedef XMPFileHandler * (* XMPFileHandlerCTor) ( XMPFiles * parent );
+
+typedef bool (* CheckFileFormatProc ) ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ XMP_IO * fileRef,
+ XMPFiles * parent );
+
+typedef bool (*CheckFolderFormatProc ) ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent );
+
+// =================================================================================================
+
+// -------------------------------------------------------------------------------------------------
+
+static inline bool CheckBytes ( const void * left, const void * right, size_t length )
+{
+ return (memcmp ( left, right, length ) == 0);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static inline bool CheckCString ( const void * left, const void * right )
+{
+ return (strcmp ( (char*)left, (char*)right ) == 0);
+}
+
+#endif /* __XMPFiles_Impl_hpp__ */